File created successfully at: C:\Users\decid\Documents\projects\sauna\spikes\s3-distortion-truth\analyze.py (file state is current in your context — no need to Read it back)

"""Spike S3 empirical analysis: discover Valve's actual POLY3 evaluation.

The 4 naive hypotheses all failed because the source-UV space is the
GetProjectionRaw frustum, not a grow-rescaled NDC. This script fits the
general radial model per eye/channel:

    t   = (2u-1, 2v-1)            panel NDC (v down, OpenVR viewport)
    r2  = |t - c|^2
    tan = a + s * (t - c) * d(r2)   (s, a 2-vector; tan = raw-projection
                                     tangent space, y-down: tan = T+v*(B-T))

with d either  poly: 1 + k1 r2 + k2 r2^2 + k3 r2^3
        or    recip: 1 / (1 + k1 r2 + k2 r2^2 + k3 r2^3)

Free params per channel: c(2) a(2) s(2) k(3) = 9. Ground truth: 4225 pts.
Then compares fitted c,k against the config block and fitted a,s against
intrinsics/projection_raw to recover the closed form.

Usage: python analyze.py distortion_dump_*.json config_*.json
"""

import json
import sys

import numpy as np
from scipy.optimize import least_squares

CHANNELS = {"r": "distortion_red", "g": "distortion", "b": "distortion_blue"}

def load_eye_points(eye_dump):
    uv, tgt = [], {"r": [], "g": [], "b": []}
    for pt in eye_dump["points"]:
        if pt is None:
            continue
        uv.append(pt["uv"])
        for ch in ("r", "g", "b"):
            tgt[ch].append(pt[ch])
    uv = np.array(uv)
    L, R, T, B = eye_dump["projection_raw"]
    tans = {}
    for ch in ("r", "g", "b"):
        suv = np.array(tgt[ch])
        # source UV -> tangent space of the raw projection (v=0 top -> T)
        tans[ch] = np.stack([L + suv[:, 0] * (R - L), T + suv[:, 1] * (B - T)], axis=1)
    return uv, tans

def model(params, t, form):
    cx, cy, ax, ay, sx, sy, k1, k2, k3 = params
    d0 = t - np.array([cx, cy])
    r2 = (d0 ** 2).sum(axis=1)
    p = 1 + r2 * (k1 + r2 * (k2 + r2 * k3))
    d = p if form == "poly" else 1.0 / p
    return np.array([ax, ay]) + np.array([sx, sy]) * d0 * d[:, None]

def fit(uv, tan, form, k0):
    t = 2 * uv - 1
    x0 = np.array([0.0, 0.0, 0.0, 0.0, 0.7, 0.7] + list(k0))

    def resid(params):
        return (model(params, t, form) - tan).ravel()

    return least_squares(resid, x0, method="lm", max_nfev=20000)

def main():
    dump = json.load(open(sys.argv[1]))
    cfg = json.load(open(sys.argv[2]))
    rt = dump["recommended_render_target"][0]

    for eye_idx, eye_dump in enumerate(dump["eyes"]):
        tte = cfg["tracking_to_eye_transform"][eye_idx]
        K = tte["intrinsics"]
        L, R, T, B = eye_dump["projection_raw"]
        print(f"\n=== {eye_dump['eye'].upper()} ===")
        print(f"projection_raw L{L:+.5f} R{R:+.5f} T{T:+.5f} B{B:+.5f}")
        print(f"intrinsics fx {K[0][0]:.5f} fy {K[1][1]:.5f} K02 {K[0][2]:+.5f} K12 {K[1][2]:+.5f}")
        print("config eye_to_head:")
        for row in tte["eye_to_head"]:
            print("   ", [round(x, 6) for x in row])
        print("dump eye_to_head:")
        for row in eye_dump["eye_to_head"]:
            print("   ", [round(x, 6) for x in row])

        uv, tans = load_eye_points(eye_dump)
        for ch in ("g", "r", "b"):
            blk = tte[CHANNELS[ch]]
            kcfg = blk["coeffs"]
            for form in ("poly", "recip"):
                res = fit(uv, tans[ch], form, kcfg)
                cx, cy, ax, ay, sx, sy, k1, k2, k3 = res.x
                # residual in source pixels
                err = res.fun.reshape(-1, 2)
                err_px = np.linalg.norm(err, axis=1) * rt / abs(R - L)
                print(f" {ch}/{form:5s} rms {err_px.std() + err_px.mean():7.3f}px max {err_px.max():8.3f}px | "
                      f"c ({cx:+.5f},{cy:+.5f}) a ({ax:+.5f},{ay:+.5f}) s ({sx:+.5f},{sy:+.5f})")
                print(f"          k_fit  [{k1:+.5f}, {k2:+.5f}, {k3:+.5f}]")
                print(f"          k_cfg  [{kcfg[0]:+.5f}, {kcfg[1]:+.5f}, {kcfg[2]:+.5f}]  "
                      f"center_cfg ({blk['center_x']:+.5f},{blk['center_y']:+.5f})  "
                      f"1/fx {1/K[0][0]:.5f} 1/fy {1/K[1][1]:.5f}")

if __name__ == "__main__":
    main()


PowerShell

cd C:\Users\decid\Documents\projects\sauna\spikes\s3-distortion-truth; python analyze.py distortion_dump_lhr_599f3b91.json config_lhr_599f3b91.json

Run empirical distortion fit


=== LEFT ===
projection_raw L-1.39998 R+0.91304 T-1.37976 B+0.92332
intrinsics fx 1.41904 fy 1.42073 K02 +0.00974 K12 +0.02671
config eye_to_head:
    [0.994207, 0.0, 0.107479]
    [-0.011514, 0.994245, 0.106511]
    [-0.10686, -0.107132, 0.988486]
dump eye_to_head:
    [1.0, 0.0, 0.0, -0.031]
    [0.0, 1.0, 0.0, 0.0]
    [0.0, 0.0, 1.0, 0.0]
 g/poly  rms   0.000px max    0.000px | c (-0.00974,+0.02671) a (-0.25051,-0.20900) s (+0.72282,+0.71971)
          k_fit  [+0.20891, +0.13323, +0.05599]
          k_cfg  [+0.20891, +0.13323, +0.05599]  center_cfg (-0.00974,+0.02671)  1/fx 0.70470 1/fy 0.70386
 g/recip rms   1.003px max   16.833px | c (-0.00976,+0.02676) a (-0.25053,-0.20895) s (+0.72114,+0.71803)
          k_fit  [-0.21189, -0.10899, +0.03457]
          k_cfg  [+0.20891, +0.13323, +0.05599]  center_cfg (-0.00974,+0.02671)  1/fx 0.70470 1/fy 0.70386
 r/poly  rms   0.000px max    0.000px | c (-0.00974,+0.02671) a (-0.25051,-0.20900) s (+0.72282,+0.71971)
          k_fit  [+0.18585, +0.16702, +0.03718]
          k_cfg  [+0.18585, +0.16702, +0.03718]  center_cfg (-0.00974,+0.02671)  1/fx 0.70470 1/fy 0.70386
 r/recip rms   1.501px max   21.851px | c (-0.00977,+0.02678) a (-0.25054,-0.20893) s (+0.71801,+0.71492)
          k_fit  [-0.21341, -0.10665, +0.03496]
          k_cfg  [+0.18585, +0.16702, +0.03718]  center_cfg (-0.00974,+0.02671)  1/fx 0.70470 1/fy 0.70386
 b/poly  rms   0.000px max    0.000px | c (-0.00974,+0.02671) a (-0.25051,-0.20900) s (+0.72282,+0.71971)
          k_fit  [+0.25042, +0.07241, +0.08984]
          k_cfg  [+0.25042, +0.07241, +0.08984]  center_cfg (-0.00974,+0.02671)  1/fx 0.70470 1/fy 0.70386
 b/recip rms   0.641px max    7.471px | c (-0.00975,+0.02673) a (-0.25052,-0.20898) s (+0.72698,+0.72385)
          k_fit  [-0.20787, -0.11496, +0.03458]
          k_cfg  [+0.25042, +0.07241, +0.08984]  center_cfg (-0.00974,+0.02671)  1/fx 0.70470 1/fy 0.70386

=== RIGHT ===
projection_raw L-0.90444 R+1.40201 T-1.34110 B+0.94106
intrinsics fx 1.42484 fy 1.42668 K02 -0.00056 K12 +0.01886
config eye_to_head:
    [0.994207, 0.0, -0.107479]
    [0.010021, 0.995644, 0.092699]
    [0.107011, -0.093239, 0.989876]
dump eye_to_head:
    [1.0, 0.0, 0.0, 0.031]
    [0.0, 1.0, 0.0, 0.0]
    [0.0, 0.0, 1.0, 0.0]
 g/poly  rms   0.000px max    0.000px | c (+0.00056,+0.01886) a (+0.24919,-0.18657) s (+0.72076,+0.71318)
          k_fit  [+0.20536, +0.14656, +0.04697]
          k_cfg  [+0.20536, +0.14656, +0.04697]  center_cfg (+0.00056,+0.01886)  1/fx 0.70183 1/fy 0.70093
 g/recip rms   1.121px max   14.450px | c (+0.00056,+0.01891) a (+0.24919,-0.18652) s (+0.71787,+0.71031)
          k_fit  [-0.21878, -0.10272, +0.03367]
          k_cfg  [+0.20536, +0.14656, +0.04697]  center_cfg (+0.00056,+0.01886)  1/fx 0.70183 1/fy 0.70093
 r/poly  rms   0.000px max    0.000px | c (+0.00056,+0.01886) a (+0.24919,-0.18657) s (+0.72076,+0.71318)
          k_fit  [+0.18230, +0.18034, +0.02817]
          k_cfg  [+0.18230, +0.18034, +0.02817]  center_cfg (+0.00056,+0.01886)  1/fx 0.70183 1/fy 0.70093
 r/recip rms   1.623px max   18.610px | c (+0.00056,+0.01892) a (+0.24919,-0.18651) s (+0.71484,+0.70732)
          k_fit  [-0.21979, -0.10106, +0.03432]
          k_cfg  [+0.18230, +0.18034, +0.02817]  center_cfg (+0.00056,+0.01886)  1/fx 0.70183 1/fy 0.70093
 b/poly  rms   0.000px max    0.000px | c (+0.00056,+0.01886) a (+0.24919,-0.18657) s (+0.72076,+0.71318)
          k_fit  [+0.24687, +0.08574, +0.08082]
          k_cfg  [+0.24687, +0.08574, +0.08082]  center_cfg (+0.00056,+0.01886)  1/fx 0.70183 1/fy 0.70093
 b/recip rms   0.529px max    6.647px | c (+0.00056,+0.01888) a (+0.24919,-0.18655) s (+0.72353,+0.71592)
          k_fit  [-0.21563, -0.10752, +0.03325]
          k_cfg  [+0.24687, +0.08574, +0.08082]  center_cfg (+0.00056,+0.01886)  1/fx 0.70183 1/fy 0.70093


Edit

            r2 = tx * tx + ty * ty
            if r2 > self.r2_cutoff:
                r2 = self.r2_cutoff

            # NOTE (S3 finding): ComputeDistortion does NOT clamp r2 at
            # undistort_r2_cutoff — ground truth fits exactly with no clamp
            # across the full panel (corner r2 ~ 2.1 > cutoff 1.0). The
            # cutoff presumably gates mesh/hidden-area logic elsewhere.
            r2 = tx * tx + ty * ty


beyond connected + steamvr up on enlyzeam


The file C:\Users\decid\Documents\projects\sauna\spikes\s3-distortion-truth\poly3.py has been updated successfully. (file state is current in your context — no need to Read it back)

            # NOTE (S3 finding): ComputeDistortion does NOT clamp r2 at
            # undistort_r2_cutoff — ground truth fits exactly with no clamp
            # across the full panel (corner r2 ~ 2.1 > cutoff 1.0). The
            # cutoff presumably gates mesh/hidden-area logic elsewhere.
            r2 = tx * tx + ty * ty

"""Standalone Beyond distortion evaluator — spike S3.

Maps panel/output UV -> pre-distortion render-target UV, per color channel,
from the device config JSON (tracking_to_eye_transform block). No SteamVR.

The open question this spike settles: production B1 configs use type
DISTORT_POLY3 with POSITIVE leading coeffs (+0.2438 on this unit), while
EVT/DVT used DISTORT_DPOLY3 with NEGATIVE leading coeffs (-0.22). Public
reimplementations (Monado u_compute_distortion_vive) evaluate the Vive
DPOLY3 family as a RECIPROCAL: d = 1/(1 + k1*r2 + k2*r4 + k3*r6).
Hypothesis: POLY3 is the same curve fitted the other way round, i.e.
d = 1 + k1*r2 + k2*r4 + k3*r6 directly. Four candidate forms are
implemented; verify.py picks the one matching OpenVR ComputeDistortion.

Hypothesis axes:
  form: 'poly'  -> d = 1 + k1*r2 + k2*r4 + k3*r6
        'recip' -> d = 1 / (1 + k1*r2 + k2*r4 + k3*r6)
  grow: True  -> output scale 0.5/(1+grow_for_undistort)   (Monado)
        False -> output scale 0.5                          (no grow rescale)
"""

import json

CHANNELS = ("distortion_red", "distortion", "distortion_blue")  # r, g, b
HYPOTHESES = [
    ("poly+grow", "poly", True),
    ("poly", "poly", False),
    ("recip+grow", "recip", True),
    ("recip", "recip", False),
]

class EyeDistortion:
    """Distortion model for one eye, built from tracking_to_eye_transform[eye]."""

    def __init__(self, tte):
        self.grow = tte["grow_for_undistort"]
        self.r2_cutoff = tte["undistort_r2_cutoff"]
        # center is shared across channels within an eye on the Beyond,
        # but read per channel anyway (B2 may differ).
        self.channels = []
        for key in CHANNELS:
            block = tte[key]
            self.channels.append({
                "cx": block["center_x"],
                "cy": block["center_y"],
                "k": list(block["coeffs"]),
                "type": block["type"],
            })

    def compute(self, u, v, form="poly", use_grow=True, aspect_x_over_y=1.0):
        """Panel UV (0..1, origin upper-left per OpenVR) -> source UV per channel.

        Returns ((ur, vr), (ug, vg), (ub, vb)).
        """
        scale = 0.5 / (1.0 + self.grow) if use_grow else 0.5
        out = []
        for ch in self.channels:
            tx = 2.0 * u - 1.0
            ty = (2.0 * v - 1.0) / aspect_x_over_y
            tx -= ch["cx"]
            ty -= ch["cy"]
            r2 = tx * tx + ty * ty
            if r2 > self.r2_cutoff:
                r2 = self.r2_cutoff
            k1, k2, k3 = ch["k"]
            p = 1.0 + r2 * (k1 + r2 * (k2 + r2 * k3))
            d = p if form == "poly" else 1.0 / p
            sx = 0.5 + (tx * d + ch["cx"]) * scale
            sy = 0.5 + (ty * d + ch["cy"]) * scale * aspect_x_over_y
            out.append((sx, sy))
        return tuple(out)

def load_config(path):
    """Load device config.json -> (left EyeDistortion, right EyeDistortion, raw dict)."""
    with open(path, "r") as f:
        cfg = json.load(f)
    tte = cfg["tracking_to_eye_transform"]
    return EyeDistortion(tte[0]), EyeDistortion(tte[1]), cfg

+            # NOTE (S3 finding): ComputeDistortion does NOT clamp r2 at

+            # undistort_r2_cutoff — ground truth fits exactly with no clamp

+            # across the full panel (corner r2 ~ 2.1 > cutoff 1.0). The

+            # cutoff presumably gates mesh/hidden-area logic elsewhere.


PowerShell

cd C:\Users\decid\Documents\projects\sauna\spikes\s3-distortion-truth; python verify.py distortion_dump_lhr_599f3b91.json config_lhr_599f3b91.json

Re-run verification with clamp removed


dump unit: LHR-599F3B91   config unit: LHR-599F3B91

hypothesis   overall max px   per-channel rms px (L/R averaged)
poly+grow            0.0002   r:   0.000 g:   0.000 b:   0.000
poly              1795.4725   r: 569.161 g: 574.258 b: 583.472
recip+grow        2578.0168   r: 519.908 g: 532.533 b: 555.124
recip             2329.3700   r: 349.681 g: 361.978 b: 384.304

Winner: poly+grow  (overall max 0.0002 px)  ->  PASS (criterion: sub-pixel)

Cross-checks (dump vs config):
  left projection_raw  dump L-1.39998 R+0.91304 T-1.37976 B+0.92332
  left from intrinsics      L-0.69784 R+0.71157 T-0.68506 B+0.72266
  left eye_to_head rot max-abs-diff 1.07e-01; translation x -31.000 mm (ipd.default_mm/2 expected)
  right projection_raw  dump L-0.90444 R+1.40201 T-1.34110 B+0.94106
  right from intrinsics      L-0.70222 R+0.70144 T-0.68771 B+0.71415
  right eye_to_head rot max-abs-diff 1.07e-01; translation x +31.000 mm (ipd.default_mm/2 expected)
  config ipd block: {'default_mm': 69}


ssh decid@enlyzeam "Get-Content 'C:\Program Files (x86)\Steam\config\steamvr.vrsettings' -Raw"

Check steamvr.vrsettings on enlyzeam for ipdOffset


Context: C:\Program Files (x86)\Steam\steamapps\common\SteamVR\resources\backgrounds\skybox_dome_blue.png


Write

# Spike S3 — FINDINGS: distortion ground truth

**Status: PASS, 2026-06-09.** Standalone evaluator matches OpenVR `ComputeDistortion`
to **0.0002 px max error** (float32 noise floor) across 65×65 grid × 3 channels × 2 eyes
on unit LHR-599F3B91 (enlyzeam rig). Criterion was sub-pixel; beaten by ~4 orders.

## The verified formula (per eye, per color channel)

Input: panel/viewport UV `(u,v)`, origin top-left of the eye's viewport (OpenVR
`ComputeDistortion` convention). Output: source UV into the eye's render target.

```
t   = (2u−1, 2v−1)                      # per-eye NDC, y down
t  -= (center_x, center_y)              # shared across channels within an eye
r²  = t·t                               # NO clamp (see below)
d   = 1 + k1·r² + k2·r⁴ + k3·r⁶         # DIRECT polynomial — NOT reciprocal
uv_src = 0.5 + (t·d + center) · 0.5/(1 + grow_for_undistort)
```

`(k1,k2,k3)` = the `coeffs` of `distortion_red` / `distortion` / `distortion_blue`
verbatim from the device config JSON. Implementation: `poly3.py` (hypothesis
`poly+grow` in `verify.py`).

### Answers to the open questions

1. **POLY3 direction:** production `DISTORT_POLY3` (positive leading coeffs) is the
   **direct** polynomial `d = 1 + k1·r² + …` applied panel→source. The Monado/OpenHMD
   Vive evaluator's reciprocal form `d = 1/(1+…)` is **wrong for POLY3** — it loses by
   2578 px max. The historical EVT/DVT `DISTORT_DPOLY3` negative coeffs are explained:
   a reciprocal re-fit of the dump lands near −0.21 leading (vs +0.209 config), i.e.
   DPOLY3(−k) ≈ 1/POLY3(+k) — same optics, opposite fit direction. Only POLY3 is
   validated here; all production Beyonds are POLY3, so DPOLY3 handling is moot.
2. **`grow_for_undistort` normalization:** exactly `0.5/(1+grow)` on the output, exactly
   as Monado does it. Confirmed numerically: fitted scale/offset reproduce
   `0.31250 = 0.5/1.6` and `0.5 + center·0.3125` to 5 decimals.
3. **`undistort_r2_cutoff` is NOT applied** by `ComputeDistortion`: panel corners reach
   r² ≈ 2.1 > cutoff 1.0 and still follow the polynomial exactly. Clamping at the cutoff
   was the sole reason the otherwise-correct hypothesis initially failed (1395 px corner
   error). The cutoff presumably gates something else (mesh/hidden-area). Our warp must
   not clamp within the panel.

## Bonus ground truth captured (load-bearing for M2)

- **`GetEyeToHeadTransform` = identity rotation + (∓31 mm, 0, 0)** — the lighthouse
  driver does NOT expose the config `eye_to_head` canting rotation. Config has
  `parallel_render_cameras: true`: SteamVR renders parallel cameras and folds the
  ±6.17° yaw / ~−5° pitch cant into the **asymmetric projection** instead.
- **`GetProjectionRaw` ≠ intrinsics frustum.** Dump (left eye): L −1.400 R +0.913
  T −1.380 B +0.923 vs intrinsics-derived ±0.70. The raw projection = grow-expanded,
  cant-folded window; the `uv_src` formula above lands in exactly this window
  (`tan = L + u_src·(R−L)`, y-down, v=0 ↦ T). M2 must either replicate this fold
  (derive closed form config → frustum; the dump + fitted per-eye offset/scale
  `a=(−0.2505,−0.2090)L /(+0.2492,−0.1866)R`, `s=(0.7228,0.7197)L /(0.7208,0.7132)R`
  are the ground truth to derive against) or render genuinely canted cameras with its
  own consistent warp math — NOT mix the two.
- **IPD translation comes from a user setting, not the config:** dump says 62.0 mm,
  config `ipd.default_mm` = 69. Source: `steamvr.vrsettings →
  driver_BeyondSteamVR.ipd_mm = 62.000003814697266` (Bigscreen's SteamVR driver
  utility setting; `steamvr.ipdOffset` ≈ 0 here). v1: default to config
  `ipd.default_mm`, expose a user override exactly like Bigscreen does.
- **Intrinsics sign convention settled:** distortion `center_x = −K[0][2]`,
  `center_y = +K[1][2]` (confirmed on both eyes of this unit; matches the B1
  production template pattern).
- `steamvr.vrsettings` also confirms the direct-mode EDID whitelist entry
  (`direct_mode: edidVid3 9993 = 0x2709, edidPid3 4660 = 0x1234`) and that users can
  carry per-channel color-gain overrides (`hmdDisplayColorGain* = 0.8/0.85/1.2` on this
  rig — remember when judging eyes-in color).

## Method

1. `dump_openvr.py` — pyopenvr `VRApplication_Background` against a running SteamVR
   with the Beyond connected (the one sanctioned dev-time SteamVR run). 65×65 UV grid
   per eye, all 4225 points ok both eyes; plus `GetProjectionRaw`,
   `GetEyeToHeadTransform`, recommended RT (3104×3104 @ supersampleScale 0.76).
2. `verify.py` — scores 4 closed-form hypotheses {poly, recip} × {grow rescale on/off}
   in panel pixels (2544/eye). Result: poly+grow 0.0002 px; next best 1795 px. PASS.
3. `analyze.py` — the step that found the formula after all 4 original hypotheses
   failed: 9-param free fit (center, offset, scale, k1..3) per eye/channel in
   `GetProjectionRaw` tangent space. Direct-poly fit converged to **0.000 px with
   fitted center and coeffs equal to the config values to 5 decimals** — proving the
   config values are used verbatim and isolating the normalization algebra, which then
   reduced exactly to the formula above.
4. Ops note: OpenVR IPC refuses connections from SSH logon sessions. Workaround that
   works: `schtasks /create … /it` + `schtasks /run` to execute the dump inside the
   interactive session (see git history for the exact incantation).

Artifacts in this directory: `distortion_dump_lhr_599f3b91.json` (ground truth, 1.4 MB),
`config_lhr_599f3b91.json` (unit config snapshot), `poly3.py` (the evaluator to port to
the compositor shader in M2), `verify.py`, `analyze.py`, `dump_openvr.py`.

## Eyes-in follow-up (pending)

Numeric criterion passed. Visual close-out planned: bake the winning warp into the S2
presenter path and eyeball a structure-revealing grid through the lens on enlyzeam
(straight lines must look straight). Tracked as the next step before M1 glue.


File created successfully at: C:\Users\decid\Documents\projects\sauna\spikes\s3-distortion-truth\FINDINGS.md (file state is current in your context — no need to Read it back)

# Spike S3 — FINDINGS: distortion ground truth

**Status: PASS, 2026-06-09.** Standalone evaluator matches OpenVR `ComputeDistortion`
to **0.0002 px max error** (float32 noise floor) across 65×65 grid × 3 channels × 2 eyes
on unit LHR-599F3B91 (enlyzeam rig). Criterion was sub-pixel; beaten by ~4 orders.

## The verified formula (per eye, per color channel)

Input: panel/viewport UV `(u,v)`, origin top-left of the eye's viewport (OpenVR
`ComputeDistortion` convention). Output: source UV into the eye's render target.

```
t   = (2u−1, 2v−1)                      # per-eye NDC, y down
t  -= (center_x, center_y)              # shared across channels within an eye
r²  = t·t                               # NO clamp (see below)
d   = 1 + k1·r² + k2·r⁴ + k3·r⁶         # DIRECT polynomial — NOT reciprocal
uv_src = 0.5 + (t·d + center) · 0.5/(1 + grow_for_undistort)
```

`(k1,k2,k3)` = the `coeffs` of `distortion_red` / `distortion` / `distortion_blue`
verbatim from the device config JSON. Implementation: `poly3.py` (hypothesis
`poly+grow` in `verify.py`).

### Answers to the open questions

1. **POLY3 direction:** production `DISTORT_POLY3` (positive leading coeffs) is the
   **direct** polynomial `d = 1 + k1·r² + …` applied panel→source. The Monado/OpenHMD
   Vive evaluator's reciprocal form `d = 1/(1+…)` is **wrong for POLY3** — it loses by
   2578 px max. The historical EVT/DVT `DISTORT_DPOLY3` negative coeffs are explained:
   a reciprocal re-fit of the dump lands near −0.21 leading (vs +0.209 config), i.e.
   DPOLY3(−k) ≈ 1/POLY3(+k) — same optics, opposite fit direction. Only POLY3 is
   validated here; all production Beyonds are POLY3, so DPOLY3 handling is moot.
2. **`grow_for_undistort` normalization:** exactly `0.5/(1+grow)` on the output, exactly
   as Monado does it. Confirmed numerically: fitted scale/offset reproduce
   `0.31250 = 0.5/1.6` and `0.5 + center·0.3125` to 5 decimals.
3. **`undistort_r2_cutoff` is NOT applied** by `ComputeDistortion`: panel corners reach
   r² ≈ 2.1 > cutoff 1.0 and still follow the polynomial exactly. Clamping at the cutoff
   was the sole reason the otherwise-correct hypothesis initially failed (1395 px corner
   error). The cutoff presumably gates something else (mesh/hidden-area). Our warp must
   not clamp within the panel.

## Bonus ground truth captured (load-bearing for M2)

- **`GetEyeToHeadTransform` = identity rotation + (∓31 mm, 0, 0)** — the lighthouse
  driver does NOT expose the config `eye_to_head` canting rotation. Config has
  `parallel_render_cameras: true`: SteamVR renders parallel cameras and folds the
  ±6.17° yaw / ~−5° pitch cant into the **asymmetric projection** instead.
- **`GetProjectionRaw` ≠ intrinsics frustum.** Dump (left eye): L −1.400 R +0.913
  T −1.380 B +0.923 vs intrinsics-derived ±0.70. The raw projection = grow-expanded,
  cant-folded window; the `uv_src` formula above lands in exactly this window
  (`tan = L + u_src·(R−L)`, y-down, v=0 ↦ T). M2 must either replicate this fold
  (derive closed form config → frustum; the dump + fitted per-eye offset/scale
  `a=(−0.2505,−0.2090)L /(+0.2492,−0.1866)R`, `s=(0.7228,0.7197)L /(0.7208,0.7132)R`
  are the ground truth to derive against) or render genuinely canted cameras with its
  own consistent warp math — NOT mix the two.
- **IPD translation comes from a user setting, not the config:** dump says 62.0 mm,
  config `ipd.default_mm` = 69. Source: `steamvr.vrsettings →
  driver_BeyondSteamVR.ipd_mm = 62.000003814697266` (Bigscreen's SteamVR driver
  utility setting; `steamvr.ipdOffset` ≈ 0 here). v1: default to config
  `ipd.default_mm`, expose a user override exactly like Bigscreen does.
- **Intrinsics sign convention settled:** distortion `center_x = −K[0][2]`,
  `center_y = +K[1][2]` (confirmed on both eyes of this unit; matches the B1
  production template pattern).
- `steamvr.vrsettings` also confirms the direct-mode EDID whitelist entry
  (`direct_mode: edidVid3 9993 = 0x2709, edidPid3 4660 = 0x1234`) and that users can
  carry per-channel color-gain overrides (`hmdDisplayColorGain* = 0.8/0.85/1.2` on this
  rig — remember when judging eyes-in color).

## Method

1. `dump_openvr.py` — pyopenvr `VRApplication_Background` against a running SteamVR
   with the Beyond connected (the one sanctioned dev-time SteamVR run). 65×65 UV grid
   per eye, all 4225 points ok both eyes; plus `GetProjectionRaw`,
   `GetEyeToHeadTransform`, recommended RT (3104×3104 @ supersampleScale 0.76).
2. `verify.py` — scores 4 closed-form hypotheses {poly, recip} × {grow rescale on/off}
   in panel pixels (2544/eye). Result: poly+grow 0.0002 px; next best 1795 px. PASS.
3. `analyze.py` — the step that found the formula after all 4 original hypotheses
   failed: 9-param free fit (center, offset, scale, k1..3) per eye/channel in
   `GetProjectionRaw` tangent space. Direct-poly fit converged to **0.000 px with
   fitted center and coeffs equal to the config values to 5 decimals** — proving the
   config values are used verbatim and isolating the normalization algebra, which then
   reduced exactly to the formula above.
4. Ops note: OpenVR IPC refuses connections from SSH logon sessions. Workaround that
   works: `schtasks /create … /it` + `schtasks /run` to execute the dump inside the
   interactive session (see git history for the exact incantation).

Artifacts in this directory: `distortion_dump_lhr_599f3b91.json` (ground truth, 1.4 MB),
`config_lhr_599f3b91.json` (unit config snapshot), `poly3.py` (the evaluator to port to
the compositor shader in M2), `verify.py`, `analyze.py`, `dump_openvr.py`.

## Eyes-in follow-up (pending)

Numeric criterion passed. Visual close-out planned: bake the winning warp into the S2
presenter path and eyeball a structure-revealing grid through the lens on enlyzeam
(straight lines must look straight). Tracked as the next step before M1 glue.


Edit

| **S3** | Which direction do the production POLY3 coeffs run? | Standalone evaluator matches an OpenVR `ComputeDistortion` UV-grid dump from one dev unit (SteamVR run once, dev-time only) within sub-pixel tolerance. |

| **S3** ✅ PASS 2026-06-09 | Which direction do the production POLY3 coeffs run? | PASS — `DISTORT_POLY3` is the **direct** polynomial `d = 1+k1r²+k2r⁴+k3r⁶` (not Monado's reciprocal), output rescale `0.5/(1+grow)`, **no r² clamp** (`undistort_r2_cutoff` unused by `ComputeDistortion`). Evaluator matches ground truth to 0.0002 px max (65×65×3ch×2 eyes, unit LHR-599F3B91). Bonus: driver folds canting into asymmetric `GetProjectionRaw` (eye_to_head reported identity; `parallel_render_cameras: true`), IPD translation = user setting not config default. See `spikes/s3-distortion-truth/FINDINGS.md`. Eyes-in warp check pending (pre-M1). |


The file C:\Users\decid\Documents\projects\sauna\PLAN.md has been updated successfully. (file state is current in your context — no need to Read it back)

# Sauna — v1 Plan

Output of the design interview closed 2026-06-09. Language: `CONTEXT.md`. Facts and risk
register: `reference/BASELINE.md`. Architecture decision: `docs/adr/0001`.

## v1 in one sentence

A standalone Windows+NVIDIA app that reads the Tundra IMU for 3DOF pose, captures the desktop's
monitors, composites them as head-tracked virtual screens, warps per-eye using the per-unit
calibration, and presents to the Beyond in Vulkan direct mode — no SteamVR, no lighthouses, no
firmware changes.

## Locked decisions

| # | Decision | Notes |
|---|---|---|
| 1 | v1 experience = virtual screen(s) via our own compositor, direct mode on stock EDID | ADR-0001 (proposed; gated on spike S2) |
| 2 | Windows+NVIDIA first, present-backend seam | *(amended by S2)* Windows backend = D3D12+NVAPI DirectMode; `VK_EXT_acquire_drm_display` = Steam Deck rung later; Vulkan render core still viable via shared-handle interop |
| 3 | Per-unit calibration via native Watchman HID config read | port from vendored libsurvive; JSON file-import kept as dev scaffold |
| 4 | Own minimal AHRS (Mahony-style) for 3DOF | config IMU mount frame + biases; recenter UX owns yaw drift; magnetometer = bonus if S1 finds one |
| 5 | Content = multi-monitor desktop capture | Windows.Graphics.Capture; layout mirrors OS monitor arrangement; PipeWire backend later |
| 6 | Frame-source abstraction from day one | capture / remote-stream / test-pattern; only capture implemented in v1 — keeps the streaming-bridge stretch additive |

UX defaults: proximity-sensor display sleep when doffed + idle timeout (burn-in safety is ours
once SteamVR is out of the loop); conservative persistence + brightness defaults; 75 Hz default
with 90 Hz opt-in flag; recenter on a configurable global hotkey; settings = plain desktop window
(visible in-headset via capture).

## Architecture (components)

- **Device layer** — Tundra HID `28DE:2300` MI_00 (IMU stream + config read); MCU HID `35BD:0101`
  (display power, per-eye panel-on, persistence, brightness, proximity, EDID/refresh).
- **Tracking** — Watchman packet parse → bias/scale correction → AHRS → head pose; recenter.
- **Frame sources** — trait per decision 6; v1 backend: Windows.Graphics.Capture per monitor.
- **Compositor** — scene of virtual screens (OS layout); per-eye render from `intrinsics`,
  `eye_to_head` canting, IPD; distortion pass (per-channel poly3 + `grow_for_undistort`);
  `display_color_mult`.
- **Present** — per-platform backend behind one interface: Windows = D3D12 + NVAPI DirectMode
  (proven, S2); Linux = Vulkan + DRM lease (unproven rung). Fallback: extended-mode SBS fullscreen.
- **App shell** — tray + settings window, hotkeys, safety policy.

## Spike ladder (run before architecture code; in this order)

| Spike | Question | Pass criterion |
|---|---|---|
| **S1** ✅ PASS 2026-06-10 | Does the Tundra stream IMU with Steam never run? | Streams unconditionally — 993.8 Hz unique samples, no wake needed, SteamVR never ran since boot. 4096 LSB/g re-confirmed; sliding 3-sample window (dedupe by timecode+seq); no mag on MI_00; gyro scale still unverified (→ M2). See `spikes/s1-imu-no-steam/FINDINGS.md`. |
| **S2** ✅ PASS 2026-06-10 | Can our own process acquire + continuously present in direct mode? | PASS via **NVAPI/D3D12** (75.07 fps native 5088×2544, zero errors; 10-min soak logged). Vulkan winrt rung DEAD on consumer Windows — specialized displays edition-gated. Windows backend = D3D12+NVAPI DM (NDA lib, referenced not vendored); Linux rung unchanged (VK DRM). See `spikes/s2-vulkan-direct/FINDINGS.md`. |
| **S3** | Which direction do the production POLY3 coeffs run? | Standalone evaluator matches an OpenVR `ComputeDistortion` UV-grid dump from one dev unit (SteamVR run once, dev-time only) within sub-pixel tolerance. |

## Build order after spikes

1. **M1 first light** — S1+S2 glued: head-locked test pattern in-headset from a cold boot.
2. **M2 tracked + warped** — AHRS pose + distortion/canting/IPD pipeline: one virtual screen
   fixed in space, fusion-correct binocular view.
3. **M3 product shape** — multi-monitor capture + OS layout, recenter, prox/idle safety,
   settings UI, native calibration read replacing dev import.
4. **M4 hardening** — pacing (capture Hz vs panel Hz), color correctness, install story.

## Out of scope for v1

SteamVR driver mode; Linux/Steam Deck port (first post-v1 target); streaming bridge (seam only);
6DOF; linkbox-less (hardware adapter, separate track); IddCx virtual display driver.

-| **S3** | Which direction do the production POLY3 coeffs run? | Standalone evaluator matches an OpenVR `ComputeDistortion` UV-grid dump from one dev unit (SteamVR run once, dev-time only) within sub-pixel tolerance. |

+| **S3** ✅ PASS 2026-06-09 | Which direction do the production POLY3 coeffs run? | PASS — `DISTORT_POLY3` is the **direct** polynomial `d = 1+k1r²+k2r⁴+k3r⁶` (not Monado's reciprocal), output rescale `0.5/(1+grow)`, **no r² clamp** (`undistort_r2_cutoff` unused by `ComputeDistortion`). Evaluator matches ground truth to 0.0002 px max (65×65×3ch×2 eyes, unit LHR-599F3B91). Bonus: driver folds canting into asymmetric `GetProjectionRaw` (eye_to_head reported identity; `parallel_render_cameras: true`), IPD translation = user setting not config default. See `spikes/s3-distortion-truth/FINDINGS.md`. Eyes-in warp check pending (pre-M1). |


PowerShell

cd C:\Users\decid\Documents\projects\sauna; git add -A; git commit -m @'
Spike S3 PASS: DISTORT_POLY3 is the direct polynomial, verified to 0.0002 px

Ground truth: one sanctioned SteamVR run on enlyzeam unit LHR-599F3B91,
65x65 ComputeDistortion grid per eye per channel via dump_openvr.py
(schtasks /it workaround for OpenVR-over-SSH session isolation).

Verdict: uv_src = 0.5 + ((t - c) * (1 + k1 r^2 + k2 r^4 + k3 r^6) + c) * 0.5/(1+grow),
coeffs and center verbatim from the device config, no undistort_r2_cutoff clamp.
Reciprocal (Monado DPOLY3) form loses by 2578 px; the EVT/DVT negative-coeff era
is explained as the same optics fitted in the reciprocal direction.

analyze.py (new): 9-param free fit per eye/channel that recovered the formula
after all closed-form hypotheses failed - fitted coeffs/center reproduced the
config values to 5 decimals at 0.000 px residual.

Bonus M2 ground truth captured in the dump and documented in FINDINGS.md:
- driver reports identity eye_to_head + ipd/2 translation; canting is folded
  into the asymmetric GetProjectionRaw frustum (parallel_render_cameras: true)
- IPD translation comes from driver_BeyondSteamVR.ipd_mm user setting (62 mm),
  not config ipd.default_mm (69)
- center_x = -K[0][2], center_y = +K[1][2] sign convention confirmed

PLAN.md spike ladder updated; eyes-in warp validation on enlyzeam still pending.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
'@; git push

Commit and push S3 PASS


warning: in the working copy of 'PLAN.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'spikes/s3-distortion-truth/poly3.py', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'spikes/s3-distortion-truth/FINDINGS.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'spikes/s3-distortion-truth/analyze.py', LF will be replaced by CRLF the next time Git touches it
[main df66b07] Spike S3 PASS: DISTORT_POLY3 is the direct polynomial, verified to 0.0002 px
 6 files changed, 690 insertions(+), 3 deletions(-)
 create mode 100644 spikes/s3-distortion-truth/FINDINGS.md
 create mode 100644 spikes/s3-distortion-truth/analyze.py
 create mode 100644 spikes/s3-distortion-truth/config_lhr_599f3b91.json
 create mode 100644 spikes/s3-distortion-truth/distortion_dump_lhr_599f3b91.json
To https://github.com/FEAT-HER3/sauna.git
   4002b15..df66b07  main -> main


S3 numeric core done + pushed (df66b07). Now eyes-in: assessing s2_nvapi for warp injection.


spikes\s2-vulkan-direct\bringup.py
spikes\s2-vulkan-direct\main.cpp
spikes\s2-vulkan-direct\dispctl.cpp
spikes\s2-vulkan-direct\CMakeLists.txt
spikes\s2-vulkan-direct\run_factory_dsc_test.py
spikes\s2-vulkan-direct\s2_nvapi.cpp
spikes\s2-vulkan-direct\FINDINGS.md


225:    // structure-revealing pattern: left eye green field, right eye red field,


1	// Spike S2, NVAPI rung: own-process DirectMode presenter for the Beyond.
2	// Ported from the first-party mmi_test directmode sample (BigscreenVR/mmi_test).
3	// Requires the NDA HMD NVAPI headers/lib from a local mmi_test checkout —
4	// referenced in place, NOT vendored into this repo (licensing).
5	//
6	//   s2_nvapi present <seconds> [--force-dsc]
7	//
8	// Exit codes: 0 ok, 2 no DM display, 3 acquire/modeset failed, 10 other.
9
10	#include <windows.h>
11
12	#include <d3d12.h>
13	#include <dxgi1_4.h>
14	#include <wrl/client.h>
15
16	#include <nvapi.h>
17
18	#include <chrono>
19	#include <cmath>
20	#include <cstdio>
21	#include <cstring>
22	#include <vector>
23
24	using Microsoft::WRL::ComPtr;
25
26	static const NvU32 VENDOR_ID = 0x2709;  // EDID "BIG"
27
28	#define NVCHECK(call)                                                       \
29	  do {                                                                      \
30	    NvAPI_Status s_ = (call);                                               \
31	    if (s_ != NVAPI_OK) {                                                   \
32	      fprintf(stderr, "FATAL %s = 0x%x (line %d)\n", #call, s_, __LINE__);  \
33	      return 10;                                                            \
34	    }                                                                       \
35	  } while (0)
36
37	#define HRCHECK(call)                                                       \
38	  do {                                                                      \
39	    HRESULT h_ = (call);                                                    \
40	    if (FAILED(h_)) {                                                       \
41	      fprintf(stderr, "FATAL %s = 0x%lx (line %d)\n", #call, h_, __LINE__); \
42	      return 10;                                                            \
43	    }                                                                       \
44	  } while (0)
45
46	int main(int argc, char** argv) {
47	  setbuf(stdout, nullptr);
48	  setbuf(stderr, nullptr);
49	  double seconds = argc > 2 ? atof(argv[2]) : 15.0;
50	  // Defaults = the visually-validated config (2026-06-10, both rigs):
51	  // X8R8G8B8 mode + forced DSC 1.1/4-slice/8bpp per the device JSON.
52	  bool forceDsc = true, powerOff = false;
53	  int wantMode = -1;
54	  for (int i = 1; i < argc; i++) {
55	    if (strcmp(argv[i], "--force-dsc") == 0) forceDsc = true;
56	    if (strcmp(argv[i], "--no-force-dsc") == 0) forceDsc = false;
57	    if (strcmp(argv[i], "--power-off") == 0) powerOff = true;
58	    if (strcmp(argv[i], "--mode") == 0 && i + 1 < argc) wantMode = atoi(argv[i + 1]);
59	  }
60
61	  NVCHECK(NvAPI_Initialize());
62	  NvAPI_Status st = NvAPI_DISP_EnableDirectMode(VENDOR_ID, 0);
63	  printf("EnableDirectMode(0x%x) = 0x%x\n", VENDOR_ID, st);
64
65	  // --- enumerate DM displays ---
66	  NvU32 numDisplays = 0;
67	  st = NvAPI_DISP_EnumerateDirectModeDisplays(VENDOR_ID, &numDisplays, nullptr,
68	                                              NV_ENUM_DIRECTMODE_DISPLAY_ENABLED);
69	  printf("EnumerateDirectModeDisplays count = %u (status 0x%x)\n", numDisplays, st);
70	  if (numDisplays == 0) { printf("RESULT: no DM display\n"); return 2; }
71	  std::vector<NV_DIRECT_MODE_DISPLAY_HANDLE> displays(numDisplays);
72	  NVCHECK(NvAPI_DISP_EnumerateDirectModeDisplays(VENDOR_ID, &numDisplays,
73	                                                 displays.data(),
74	                                                 NV_ENUM_DIRECTMODE_DISPLAY_ENABLED));
75	  NV_DIRECT_MODE_DISPLAY_HANDLE display = displays[0];
76	  printf("using displayId 0x%x\n", display.displayId);
77
78	  // --- list modes ---
79	  NvU32 modeCount = 0;
80	  NVCHECK(NvAPI_D3D_DirectModeGetDisplayModes(&display, &modeCount, nullptr,
81	                                              NV_DIRECTMODE_GETMODES_FLAG_SUPPORTED));
82	  std::vector<NV_DIRECT_MODE_INFO> modes(modeCount);
83	  for (auto& m : modes) { memset(&m, 0, sizeof(m)); m.version = NV_DIRECT_MODE_INFO_VER; }
84	  NVCHECK(NvAPI_D3D_DirectModeGetDisplayModes(&display, &modeCount, modes.data(),
85	                                              NV_DIRECTMODE_GETMODES_FLAG_SUPPORTED));
86	  int best = -1;
87	  double bestScore = -1;
88	  for (NvU32 i = 0; i < modeCount; i++) {
89	    auto& m = modes[i];
90	    double hz = m.refresh.denominator
91	                    ? (double)m.refresh.numerator / m.refresh.denominator
92	                    : 0;
93	    printf("mode[%u]: %ux%u @ %.3f Hz format=%d dscMode=%d dscVer=%d slices=%d bppx16=%u\n",
94	           i, m.width, m.height, hz, (int)m.format, (int)m.dscParams.dscMode,
95	           (int)m.dscParams.dscVersion, (int)m.dscParams.sliceCount,
96	           m.dscParams.outputBPPx16);
97	    double score = (double)m.width * m.height - fabs(hz - 75.0) * 1e4;
98	    if (m.format == 22) score += 1;  // prefer X8R8G8B8 — the validated format
99	    if (score > bestScore) { bestScore = score; best = (int)i; }
100	  }
101	  if (wantMode >= 0 && wantMode < (int)modeCount) best = wantMode;
102	  NV_DIRECT_MODE_INFO mode = modes[best];
103	  double modeHz = mode.refresh.denominator
104	                      ? (double)mode.refresh.numerator / mode.refresh.denominator
105	                      : 0;
106	  printf("using mode[%d]: %ux%u @ %.3f Hz\n", best, mode.width, mode.height, modeHz);
107	  if (forceDsc) {
108	    mode.dscParams.dscMode = NV_DIRECT_MODE_DSC_MODE_FORCE_ENABLED;
109	    mode.dscParams.dscVersion = NV_DIRECT_MODE_DSC_VERSION_V11;
110	    mode.dscParams.sliceCount = NV_DIRECT_MODE_DSC_SLICE_COUNT_4;
111	    mode.dscParams.outputBPPx16 = 128;
112	    printf("forcing DSC 1.1, 4 slices, 8 bpp\n");
113	  }
114
115	  // --- D3D12 device on the display's adapter ---
116	  NvPhysicalGpuHandle gpu;
117	  NVCHECK(NvAPI_SYS_GetPhysicalGpuFromDisplayId(display.displayId, &gpu));
118	  ComPtr<IDXGIAdapter> adapter;
119	  NVCHECK(NvAPI_D3D_GetIDXGIAdapter(gpu, adapter.GetAddressOf()));
120	  ComPtr<ID3D12Device> dev;
121	  HRCHECK(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0,
122	                            IID_PPV_ARGS(&dev)));
123	  D3D12_COMMAND_QUEUE_DESC qd{};
124	  qd.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
125	  ComPtr<ID3D12CommandQueue> queue;
126	  HRCHECK(dev->CreateCommandQueue(&qd, IID_PPV_ARGS(&queue)));
127	  ComPtr<ID3D12CommandAllocator> alloc;
128	  HRCHECK(dev->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
129	                                      IID_PPV_ARGS(&alloc)));
130	  ComPtr<ID3D12GraphicsCommandList> list;
131	  HRCHECK(dev->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, alloc.Get(),
132	                                 nullptr, IID_PPV_ARGS(&list)));
133	  HRCHECK(list->Close());
134	  ComPtr<ID3D12Fence> fence;
135	  HRCHECK(dev->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)));
136	  HANDLE fenceEvt = CreateEventA(nullptr, FALSE, FALSE, nullptr);
137	  UINT64 fenceVal = 0;
138
139	  // --- acquire ---
140	  st = NvAPI_D3D_AcquireDirectModeDisplay(VENDOR_ID, dev.Get(), &display);
141	  printf("AcquireDirectModeDisplay = 0x%x%s\n", st,
142	         st == NVAPI_HDCP_DISABLED ? " (HDCP disabled, continuing)" : "");
143	  if (st != NVAPI_OK && st != NVAPI_HDCP_DISABLED) return 3;
144
145	  // --- surfaces ---
146	  const int NBUF = 2;
147	  NV_DIRECT_MODE_SURFACE_HANDLE surf[NBUF];
148	  HANDLE shared[NBUF];
149	  ComPtr<ID3D12Resource> res[NBUF];
150	  for (int i = 0; i < NBUF; i++) {
151	    st = NvAPI_D3D_DirectModeCreateSurface(&display, &mode, &surf[i], &shared[i]);
152	    if (st != NVAPI_OK) { fprintf(stderr, "CreateSurface[%d] = 0x%x\n", i, st); return 3; }
153	    HRCHECK(dev->OpenSharedHandle(shared[i], IID_PPV_ARGS(&res[i])));
154	  }
155	  auto rdesc = res[0]->GetDesc();
156	  printf("surface: %llux%u format=%d\n", (unsigned long long)rdesc.Width,
157	         rdesc.Height, (int)rdesc.Format);
158	  // DM surfaces come back TYPELESS — an RTV needs an explicit typed format
159	  // (nullptr desc on a typeless resource silently fails in release builds:
160	  // clears never land and the panels scan out uninitialized VRAM).
161	  DXGI_FORMAT rtvFmt;
162	  switch (rdesc.Format) {
163	    case DXGI_FORMAT_B8G8R8A8_TYPELESS: rtvFmt = DXGI_FORMAT_B8G8R8A8_UNORM; break;
164	    case DXGI_FORMAT_B8G8R8X8_TYPELESS: rtvFmt = DXGI_FORMAT_B8G8R8X8_UNORM; break;
165	    case DXGI_FORMAT_R8G8B8A8_TYPELESS: rtvFmt = DXGI_FORMAT_R8G8B8A8_UNORM; break;
166	    case DXGI_FORMAT_R10G10B10A2_TYPELESS: rtvFmt = DXGI_FORMAT_R10G10B10A2_UNORM; break;
167	    case DXGI_FORMAT_R16G16B16A16_TYPELESS: rtvFmt = DXGI_FORMAT_R16G16B16A16_FLOAT; break;
168	    default:
169	      rtvFmt = rdesc.Format;
170	      fprintf(stderr, "WARNING: surface format %d has no typed mapping — "
171	                      "RTV will be invalid if it is typeless\n", (int)rdesc.Format);
172	      break;
173	  }
174	  printf("rtv format: %d\n", (int)rtvFmt);
175
176	  ComPtr<ID3D12DescriptorHeap> rtvHeap;
177	  D3D12_DESCRIPTOR_HEAP_DESC hd{};
178	  hd.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
179	  hd.NumDescriptors = NBUF;
180	  HRCHECK(dev->CreateDescriptorHeap(&hd, IID_PPV_ARGS(&rtvHeap)));
181	  UINT rtvStep = dev->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
182	  D3D12_CPU_DESCRIPTOR_HANDLE rtv[NBUF];
183	  for (int i = 0; i < NBUF; i++) {
184	    rtv[i] = rtvHeap->GetCPUDescriptorHandleForHeapStart();
185	    rtv[i].ptr += (SIZE_T)i * rtvStep;
186	    D3D12_RENDER_TARGET_VIEW_DESC rd{};
187	    rd.Format = rtvFmt;
188	    rd.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
189	    rd.Texture2D.MipSlice = 0;
190	    rd.Texture2D.PlaneSlice = 0;
191	    dev->CreateRenderTargetView(res[i].Get(), &rd, rtv[i]);
192	  }
193
194	  // --- modeset + power on ---
195	  st = NvAPI_D3D_DirectModeSetDisplayMode(&display, &mode);
196	  if (st == NVAPI_HDCP_DISABLED) { printf("SetDisplayMode: HDCP disabled, ok\n"); st = NVAPI_OK; }
197	  printf("SetDisplayMode = 0x%x\n", st);
198	  if (st != NVAPI_OK) return 3;
199	  st = NvAPI_DISP_DirectModeDisplayControl(&display, NV_DM_DISPLAY_CONTROL_POWER_ON);
200	  printf("DisplayControl(POWER_ON) = 0x%x\n", st);
201
202	  HANDLE waitEvt = NULL;
203	  st = NvAPI_DISP_DirectModeGetPresentWaitableObject(
204	      &display, reinterpret_cast<NvNotifyHandle*>(&waitEvt));
205	  bool haveWaitable = st == NVAPI_OK;
206	  printf("GetPresentWaitableObject = 0x%x\n", st);
207
208	  // --- present loop: animated dim hue clear ---
209	  auto t0 = std::chrono::steady_clock::now();
210	  uint64_t frames = 0, presentErrors = 0, waitTimeouts = 0;
211	  while (true) {
212	    double t = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
213	    if (t >= seconds) break;
214	    int i = (int)(frames % NBUF);
215
216	    HRCHECK(alloc->Reset());
217	    HRCHECK(list->Reset(alloc.Get(), nullptr));
218	    D3D12_RESOURCE_BARRIER b{};
219	    b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
220	    b.Transition.pResource = res[i].Get();
221	    b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
222	    b.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
223	    b.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
224	    list->ResourceBarrier(1, &b);
225	    // structure-revealing pattern: left eye green field, right eye red field,
226	    // synchronized white bar sweeping across each eye. Any DSC/packing fault
227	    // shows immediately as wrong colors per eye or a broken/duplicated bar.
228	    {
229	      LONG W = (LONG)rdesc.Width, H = (LONG)rdesc.Height, half = W / 2;
230	      float lc[4] = {0.0f, 0.25f, 0.08f, 1.0f};
231	      float rc[4] = {0.25f, 0.0f, 0.08f, 1.0f};
232	      float wc[4] = {0.4f, 0.4f, 0.4f, 1.0f};
233	      D3D12_RECT rl{0, 0, half, H}, rr{half, 0, W, H};
234	      list->ClearRenderTargetView(rtv[i], lc, 1, &rl);
235	      list->ClearRenderTargetView(rtv[i], rc, 1, &rr);
236	      LONG barW = 64;
237	      LONG bx = (LONG)(fmod(t * 0.25, 1.0) * (half - barW));
238	      D3D12_RECT bars[2] = {{bx, 0, bx + barW, H},
239	                            {half + bx, 0, half + bx + barW, H}};
240	      list->ClearRenderTargetView(rtv[i], wc, 2, bars);
241	    }
242	    D3D12_RESOURCE_BARRIER b2 = b;
243	    b2.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
244	    b2.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON;
245	    list->ResourceBarrier(1, &b2);
246	    HRCHECK(list->Close());
247	    ID3D12CommandList* lists[] = {list.Get()};
248	    queue->ExecuteCommandLists(1, lists);
249	    HRCHECK(queue->Signal(fence.Get(), ++fenceVal));
250	    if (fence->GetCompletedValue() < fenceVal) {
251	      fence->SetEventOnCompletion(fenceVal, fenceEvt);
252	      WaitForSingleObject(fenceEvt, 1000);
253	    }
254
255	    st = NvAPI_D3D_DirectModePresent12(&display, surf[i],
256	                                       NV_DIRECTMODE_PRESENT_FLAG_VSYNC,
257	                                       queue.Get(), list.Get());
258	    if (st != NVAPI_OK) {
259	      presentErrors++;
260	      fprintf(stderr, "Present12 = 0x%x at frame %llu\n", st,
261	              (unsigned long long)frames);
262	      if (presentErrors > 10) break;
263	    }
264	    if (haveWaitable && WaitForSingleObject(waitEvt, 1000) == WAIT_TIMEOUT)
265	      waitTimeouts++;
266	    frames++;
267	    if (frames % 300 == 0)
268	      printf("  t=%6.1fs frames=%llu avg=%.2f fps\n", t,
269	             (unsigned long long)frames, frames / t);
270	  }
271
272	  double total = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
273	  printf("PRESENT RESULT: %llu frames in %.1fs = %.2f fps avg, presentErrors %llu, waitTimeouts %llu\n",
274	         (unsigned long long)frames, total, frames / total,
275	         (unsigned long long)presentErrors, (unsigned long long)waitTimeouts);
276
277	  if (powerOff)
278	    NvAPI_DISP_DirectModeDisplayControl(&display, NV_DM_DISPLAY_CONTROL_POWER_OFF);
279	  NvAPI_D3D_ReleaseDirectModeDisplay(VENDOR_ID, &display);
280	  printf("display released\n");
281	  // NvAPI_Unload deliberately skipped: unloading before ComPtr/D3D teardown
282	  // crashes in driver DLL atexit order. Process exit cleans up regardless.
283
284	  double expectHz = modeHz > 1 ? modeHz : 75.0;
285	  bool pass = frames > 0 && presentErrors == 0 && frames / total > expectHz * 0.93;
286	  printf("VERDICT: %s\n", pass ? "PASS" : (frames > 0 ? "MARGINAL" : "FAIL"));
287	  return 0;
288	}
289

// Spike S2, NVAPI rung: own-process DirectMode presenter for the Beyond.
// Ported from the first-party mmi_test directmode sample (BigscreenVR/mmi_test).
// Requires the NDA HMD NVAPI headers/lib from a local mmi_test checkout —
// referenced in place, NOT vendored into this repo (licensing).
//
//   s2_nvapi present <seconds> [--force-dsc]
//
// Exit codes: 0 ok, 2 no DM display, 3 acquire/modeset failed, 10 other.

#include <windows.h>

#include <d3d12.h>
#include <dxgi1_4.h>
#include <wrl/client.h>

#include <nvapi.h>

#include <chrono>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <vector>

using Microsoft::WRL::ComPtr;

static const NvU32 VENDOR_ID = 0x2709;  // EDID "BIG"

#define NVCHECK(call)                                                       \
  do {                                                                      \
    NvAPI_Status s_ = (call);                                               \
    if (s_ != NVAPI_OK) {                                                   \
      fprintf(stderr, "FATAL %s = 0x%x (line %d)\n", #call, s_, __LINE__);  \
      return 10;                                                            \
    }                                                                       \
  } while (0)

#define HRCHECK(call)                                                       \
  do {                                                                      \
    HRESULT h_ = (call);                                                    \
    if (FAILED(h_)) {                                                       \
      fprintf(stderr, "FATAL %s = 0x%lx (line %d)\n", #call, h_, __LINE__); \
      return 10;                                                            \
    }                                                                       \
  } while (0)

int main(int argc, char** argv) {
  setbuf(stdout, nullptr);
  setbuf(stderr, nullptr);
  double seconds = argc > 2 ? atof(argv[2]) : 15.0;
  // Defaults = the visually-validated config (2026-06-10, both rigs):
  // X8R8G8B8 mode + forced DSC 1.1/4-slice/8bpp per the device JSON.
  bool forceDsc = true, powerOff = false;
  int wantMode = -1;
  for (int i = 1; i < argc; i++) {
    if (strcmp(argv[i], "--force-dsc") == 0) forceDsc = true;
    if (strcmp(argv[i], "--no-force-dsc") == 0) forceDsc = false;
    if (strcmp(argv[i], "--power-off") == 0) powerOff = true;
    if (strcmp(argv[i], "--mode") == 0 && i + 1 < argc) wantMode = atoi(argv[i + 1]);
  }

  NVCHECK(NvAPI_Initialize());
  NvAPI_Status st = NvAPI_DISP_EnableDirectMode(VENDOR_ID, 0);
  printf("EnableDirectMode(0x%x) = 0x%x\n", VENDOR_ID, st);

  // --- enumerate DM displays ---
  NvU32 numDisplays = 0;
  st = NvAPI_DISP_EnumerateDirectModeDisplays(VENDOR_ID, &numDisplays, nullptr,
                                              NV_ENUM_DIRECTMODE_DISPLAY_ENABLED);
  printf("EnumerateDirectModeDisplays count = %u (status 0x%x)\n", numDisplays, st);
  if (numDisplays == 0) { printf("RESULT: no DM display\n"); return 2; }
  std::vector<NV_DIRECT_MODE_DISPLAY_HANDLE> displays(numDisplays);
  NVCHECK(NvAPI_DISP_EnumerateDirectModeDisplays(VENDOR_ID, &numDisplays,
                                                 displays.data(),
                                                 NV_ENUM_DIRECTMODE_DISPLAY_ENABLED));
  NV_DIRECT_MODE_DISPLAY_HANDLE display = displays[0];
  printf("using displayId 0x%x\n", display.displayId);

  // --- list modes ---
  NvU32 modeCount = 0;
  NVCHECK(NvAPI_D3D_DirectModeGetDisplayModes(&display, &modeCount, nullptr,
                                              NV_DIRECTMODE_GETMODES_FLAG_SUPPORTED));
  std::vector<NV_DIRECT_MODE_INFO> modes(modeCount);
  for (auto& m : modes) { memset(&m, 0, sizeof(m)); m.version = NV_DIRECT_MODE_INFO_VER; }
  NVCHECK(NvAPI_D3D_DirectModeGetDisplayModes(&display, &modeCount, modes.data(),
                                              NV_DIRECTMODE_GETMODES_FLAG_SUPPORTED));
  int best = -1;
  double bestScore = -1;
  for (NvU32 i = 0; i < modeCount; i++) {
    auto& m = modes[i];
    double hz = m.refresh.denominator
                    ? (double)m.refresh.numerator / m.refresh.denominator
                    : 0;
    printf("mode[%u]: %ux%u @ %.3f Hz format=%d dscMode=%d dscVer=%d slices=%d bppx16=%u\n",
           i, m.width, m.height, hz, (int)m.format, (int)m.dscParams.dscMode,
           (int)m.dscParams.dscVersion, (int)m.dscParams.sliceCount,
           m.dscParams.outputBPPx16);
    double score = (double)m.width * m.height - fabs(hz - 75.0) * 1e4;
    if (m.format == 22) score += 1;  // prefer X8R8G8B8 — the validated format
    if (score > bestScore) { bestScore = score; best = (int)i; }
  }
  if (wantMode >= 0 && wantMode < (int)modeCount) best = wantMode;
  NV_DIRECT_MODE_INFO mode = modes[best];
  double modeHz = mode.refresh.denominator
                      ? (double)mode.refresh.numerator / mode.refresh.denominator
                      : 0;
  printf("using mode[%d]: %ux%u @ %.3f Hz\n", best, mode.width, mode.height, modeHz);
  if (forceDsc) {
    mode.dscParams.dscMode = NV_DIRECT_MODE_DSC_MODE_FORCE_ENABLED;
    mode.dscParams.dscVersion = NV_DIRECT_MODE_DSC_VERSION_V11;
    mode.dscParams.sliceCount = NV_DIRECT_MODE_DSC_SLICE_COUNT_4;
    mode.dscParams.outputBPPx16 = 128;
    printf("forcing DSC 1.1, 4 slices, 8 bpp\n");
  }

  // --- D3D12 device on the display's adapter ---
  NvPhysicalGpuHandle gpu;
  NVCHECK(NvAPI_SYS_GetPhysicalGpuFromDisplayId(display.displayId, &gpu));
  ComPtr<IDXGIAdapter> adapter;
  NVCHECK(NvAPI_D3D_GetIDXGIAdapter(gpu, adapter.GetAddressOf()));
  ComPtr<ID3D12Device> dev;
  HRCHECK(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0,
                            IID_PPV_ARGS(&dev)));
  D3D12_COMMAND_QUEUE_DESC qd{};
  qd.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
  ComPtr<ID3D12CommandQueue> queue;
  HRCHECK(dev->CreateCommandQueue(&qd, IID_PPV_ARGS(&queue)));
  ComPtr<ID3D12CommandAllocator> alloc;
  HRCHECK(dev->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
                                      IID_PPV_ARGS(&alloc)));
  ComPtr<ID3D12GraphicsCommandList> list;
  HRCHECK(dev->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, alloc.Get(),
                                 nullptr, IID_PPV_ARGS(&list)));
  HRCHECK(list->Close());
  ComPtr<ID3D12Fence> fence;
  HRCHECK(dev->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)));
  HANDLE fenceEvt = CreateEventA(nullptr, FALSE, FALSE, nullptr);
  UINT64 fenceVal = 0;

  // --- acquire ---
  st = NvAPI_D3D_AcquireDirectModeDisplay(VENDOR_ID, dev.Get(), &display);
  printf("AcquireDirectModeDisplay = 0x%x%s\n", st,
         st == NVAPI_HDCP_DISABLED ? " (HDCP disabled, continuing)" : "");
  if (st != NVAPI_OK && st != NVAPI_HDCP_DISABLED) return 3;

  // --- surfaces ---
  const int NBUF = 2;
  NV_DIRECT_MODE_SURFACE_HANDLE surf[NBUF];
  HANDLE shared[NBUF];
  ComPtr<ID3D12Resource> res[NBUF];
  for (int i = 0; i < NBUF; i++) {
    st = NvAPI_D3D_DirectModeCreateSurface(&display, &mode, &surf[i], &shared[i]);
    if (st != NVAPI_OK) { fprintf(stderr, "CreateSurface[%d] = 0x%x\n", i, st); return 3; }
    HRCHECK(dev->OpenSharedHandle(shared[i], IID_PPV_ARGS(&res[i])));
  }
  auto rdesc = res[0]->GetDesc();
  printf("surface: %llux%u format=%d\n", (unsigned long long)rdesc.Width,
         rdesc.Height, (int)rdesc.Format);
  // DM surfaces come back TYPELESS — an RTV needs an explicit typed format
  // (nullptr desc on a typeless resource silently fails in release builds:
  // clears never land and the panels scan out uninitialized VRAM).
  DXGI_FORMAT rtvFmt;
  switch (rdesc.Format) {
    case DXGI_FORMAT_B8G8R8A8_TYPELESS: rtvFmt = DXGI_FORMAT_B8G8R8A8_UNORM; break;
    case DXGI_FORMAT_B8G8R8X8_TYPELESS: rtvFmt = DXGI_FORMAT_B8G8R8X8_UNORM; break;
    case DXGI_FORMAT_R8G8B8A8_TYPELESS: rtvFmt = DXGI_FORMAT_R8G8B8A8_UNORM; break;
    case DXGI_FORMAT_R10G10B10A2_TYPELESS: rtvFmt = DXGI_FORMAT_R10G10B10A2_UNORM; break;
    case DXGI_FORMAT_R16G16B16A16_TYPELESS: rtvFmt = DXGI_FORMAT_R16G16B16A16_FLOAT; break;
    default:
      rtvFmt = rdesc.Format;
      fprintf(stderr, "WARNING: surface format %d has no typed mapping — "
                      "RTV will be invalid if it is typeless\n", (int)rdesc.Format);
      break;
  }
  printf("rtv format: %d\n", (int)rtvFmt);

  ComPtr<ID3D12DescriptorHeap> rtvHeap;
  D3D12_DESCRIPTOR_HEAP_DESC hd{};
  hd.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
  hd.NumDescriptors = NBUF;
  HRCHECK(dev->CreateDescriptorHeap(&hd, IID_PPV_ARGS(&rtvHeap)));
  UINT rtvStep = dev->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
  D3D12_CPU_DESCRIPTOR_HANDLE rtv[NBUF];
  for (int i = 0; i < NBUF; i++) {
    rtv[i] = rtvHeap->GetCPUDescriptorHandleForHeapStart();
    rtv[i].ptr += (SIZE_T)i * rtvStep;
    D3D12_RENDER_TARGET_VIEW_DESC rd{};
    rd.Format = rtvFmt;
    rd.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
    rd.Texture2D.MipSlice = 0;
    rd.Texture2D.PlaneSlice = 0;
    dev->CreateRenderTargetView(res[i].Get(), &rd, rtv[i]);
  }

  // --- modeset + power on ---
  st = NvAPI_D3D_DirectModeSetDisplayMode(&display, &mode);
  if (st == NVAPI_HDCP_DISABLED) { printf("SetDisplayMode: HDCP disabled, ok\n"); st = NVAPI_OK; }
  printf("SetDisplayMode = 0x%x\n", st);
  if (st != NVAPI_OK) return 3;
  st = NvAPI_DISP_DirectModeDisplayControl(&display, NV_DM_DISPLAY_CONTROL_POWER_ON);
  printf("DisplayControl(POWER_ON) = 0x%x\n", st);

  HANDLE waitEvt = NULL;
  st = NvAPI_DISP_DirectModeGetPresentWaitableObject(
      &display, reinterpret_cast<NvNotifyHandle*>(&waitEvt));
  bool haveWaitable = st == NVAPI_OK;
  printf("GetPresentWaitableObject = 0x%x\n", st);

  // --- present loop: animated dim hue clear ---
  auto t0 = std::chrono::steady_clock::now();
  uint64_t frames = 0, presentErrors = 0, waitTimeouts = 0;
  while (true) {
    double t = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
    if (t >= seconds) break;
    int i = (int)(frames % NBUF);

    HRCHECK(alloc->Reset());
    HRCHECK(list->Reset(alloc.Get(), nullptr));
    D3D12_RESOURCE_BARRIER b{};
    b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    b.Transition.pResource = res[i].Get();
    b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
    b.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
    b.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
    list->ResourceBarrier(1, &b);
    // structure-revealing pattern: left eye green field, right eye red field,
    // synchronized white bar sweeping across each eye. Any DSC/packing fault
    // shows immediately as wrong colors per eye or a broken/duplicated bar.
    {
      LONG W = (LONG)rdesc.Width, H = (LONG)rdesc.Height, half = W / 2;
      float lc[4] = {0.0f, 0.25f, 0.08f, 1.0f};
      float rc[4] = {0.25f, 0.0f, 0.08f, 1.0f};
      float wc[4] = {0.4f, 0.4f, 0.4f, 1.0f};
      D3D12_RECT rl{0, 0, half, H}, rr{half, 0, W, H};
      list->ClearRenderTargetView(rtv[i], lc, 1, &rl);
      list->ClearRenderTargetView(rtv[i], rc, 1, &rr);
      LONG barW = 64;
      LONG bx = (LONG)(fmod(t * 0.25, 1.0) * (half - barW));
      D3D12_RECT bars[2] = {{bx, 0, bx + barW, H},
                            {half + bx, 0, half + bx + barW, H}};
      list->ClearRenderTargetView(rtv[i], wc, 2, bars);
    }
    D3D12_RESOURCE_BARRIER b2 = b;
    b2.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
    b2.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON;
    list->ResourceBarrier(1, &b2);
    HRCHECK(list->Close());
    ID3D12CommandList* lists[] = {list.Get()};
    queue->ExecuteCommandLists(1, lists);
    HRCHECK(queue->Signal(fence.Get(), ++fenceVal));
    if (fence->GetCompletedValue() < fenceVal) {
      fence->SetEventOnCompletion(fenceVal, fenceEvt);
      WaitForSingleObject(fenceEvt, 1000);
    }

    st = NvAPI_D3D_DirectModePresent12(&display, surf[i],
                                       NV_DIRECTMODE_PRESENT_FLAG_VSYNC,
                                       queue.Get(), list.Get());
    if (st != NVAPI_OK) {
      presentErrors++;
      fprintf(stderr, "Present12 = 0x%x at frame %llu\n", st,
              (unsigned long long)frames);
      if (presentErrors > 10) break;
    }
    if (haveWaitable && WaitForSingleObject(waitEvt, 1000) == WAIT_TIMEOUT)
      waitTimeouts++;
    frames++;
    if (frames % 300 == 0)
      printf("  t=%6.1fs frames=%llu avg=%.2f fps\n", t,
             (unsigned long long)frames, frames / t);
  }

  double total = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
  printf("PRESENT RESULT: %llu frames in %.1fs = %.2f fps avg, presentErrors %llu, waitTimeouts %llu\n",
         (unsigned long long)frames, total, frames / total,
         (unsigned long long)presentErrors, (unsigned long long)waitTimeouts);

  if (powerOff)
    NvAPI_DISP_DirectModeDisplayControl(&display, NV_DM_DISPLAY_CONTROL_POWER_OFF);
  NvAPI_D3D_ReleaseDirectModeDisplay(VENDOR_ID, &display);
  printf("display released\n");
  // NvAPI_Unload deliberately skipped: unloading before ComPtr/D3D teardown
  // crashes in driver DLL atexit order. Process exit cleans up regardless.

  double expectHz = modeHz > 1 ? modeHz : 75.0;
  bool pass = frames > 0 && presentErrors == 0 && frames / total > expectHz * 0.93;
  printf("VERDICT: %s\n", pass ? "PASS" : (frames > 0 ? "MARGINAL" : "FAIL"));
  return 0;
}


Write

"""Generate a pre-distorted grid test image for the eyes-in S3 check.

For every panel pixel (per eye, per color channel) evaluate the verified
S3 warp (poly3.py, poly+grow form) to get the source UV, then sample a
procedural grid that is STRAIGHT in source (tangent-linear) space. Through
the Beyond's lens the grid must therefore look straight, and because each
channel is warped with its own coefficients, the lines must show minimal
color fringing. Wrong formula direction / wrong eye / flipped v shows up
as curved lines, rainbow fringes, or an upside-down marker.

Pattern (source UV space, per eye):
  - grid lines every 0.05, intensity white
  - thicker center cross at (0.5, 0.5)
  - filled triangle ABOVE center pointing up (settles v orientation eyes-in)
  - left eye additionally gets a small filled square left of center
    (settles eye assignment)
  - outside source [0,1]: black (matches the grown render target bounds)

Output: raw B8G8R8X8 bytes, row-major, W*H*4 — loaded by s2_nvapi --image.

Usage: python make_warped_grid.py <config.json> [out.raw] [W H]
       (default 5088 2544 — the validated native DM mode, left half = left eye)
"""

import json
import sys

import numpy as np

from poly3 import load_config

GRID_STEP = 0.05
LINE_HW = 0.0018      # line half-width in source UV
CROSS_HW = 0.005      # center cross half-width
GAMMA = 2.2

def grid_intensity(su, sv):
    """Procedural pattern in source UV space -> intensity [0,1] array."""
    inside = (su >= 0) & (su <= 1) & (sv >= 0) & (sv <= 1)
    # grid lines
    du = np.abs(((su / GRID_STEP) + 0.5) % 1.0 - 0.5) * GRID_STEP
    dv = np.abs(((sv / GRID_STEP) + 0.5) % 1.0 - 0.5) * GRID_STEP
    lines = (du < LINE_HW) | (dv < LINE_HW)
    # center cross (thicker)
    cross = (np.abs(su - 0.5) < CROSS_HW) | (np.abs(sv - 0.5) < CROSS_HW)
    # up marker: filled triangle, apex at v=0.30, base at v=0.38 (v=0 is top)
    tri_h = 0.08
    tv = (sv - 0.30) / tri_h
    tri = (tv >= 0) & (tv <= 1) & (np.abs(su - 0.5) < 0.04 * tv)
    out = np.where(lines | cross | tri, 1.0, 0.06)  # faint floor shows extent
    return np.where(inside, out, 0.0)

def left_eye_marker(su, sv):
    """Small filled square left of center, left eye only."""
    return ((np.abs(su - 0.35) < 0.02) & (np.abs(sv - 0.5) < 0.02)).astype(float)

def render_eye(model, W, H):
    """Return (H, W, 3) float intensity for one eye panel."""
    u = (np.arange(W) + 0.5) / W
    v = (np.arange(H) + 0.5) / H
    uu, vv = np.meshgrid(u, v)
    img = np.zeros((H, W, 3), dtype=np.float32)
    scale = 0.5 / (1.0 + model.grow)
    for ci, ch in enumerate(model.channels):  # r, g, b
        tx = 2.0 * uu - 1.0 - ch["cx"]
        ty = 2.0 * vv - 1.0 - ch["cy"]
        r2 = tx * tx + ty * ty
        k1, k2, k3 = ch["k"]
        d = 1.0 + r2 * (k1 + r2 * (k2 + r2 * k3))
        su = 0.5 + (tx * d + ch["cx"]) * scale
        sv = 0.5 + (ty * d + ch["cy"]) * scale
        img[:, :, ci] = grid_intensity(su, sv)
    return img

def main():
    cfg_path = sys.argv[1]
    out_path = sys.argv[2] if len(sys.argv) > 2 else "warped_grid.raw"
    W = int(sys.argv[3]) if len(sys.argv) > 4 else 5088
    H = int(sys.argv[4]) if len(sys.argv) > 4 else 2544
    half = W // 2

    left, right, cfg = load_config(cfg_path)
    print(f"unit {cfg.get('device_serial_number')}  image {W}x{H} ({half}x{H}/eye)")

    frame = np.zeros((H, W, 3), dtype=np.float32)
    for eye_idx, model in ((0, left), (1, right)):
        img = render_eye(model, half, H)
        if eye_idx == 0:
            # left-eye marker rendered in green channel source space
            scale = 0.5 / (1.0 + model.grow)
            u = (np.arange(half) + 0.5) / half
            v = (np.arange(H) + 0.5) / H
            uu, vv = np.meshgrid(u, v)
            ch = model.channels[1]
            tx = 2.0 * uu - 1.0 - ch["cx"]
            ty = 2.0 * vv - 1.0 - ch["cy"]
            r2 = tx * tx + ty * ty
            k1, k2, k3 = ch["k"]
            d = 1.0 + r2 * (k1 + r2 * (k2 + r2 * k3))
            su = 0.5 + (tx * d + ch["cx"]) * scale
            sv = 0.5 + (ty * d + ch["cy"]) * scale
            m = left_eye_marker(su, sv)
            img = np.maximum(img, m[:, :, None])
        frame[:, eye_idx * half:(eye_idx + 1) * half, :] = img

    # gamma encode, pack B8G8R8X8 little-endian (byte order B,G,R,X)
    enc = (np.clip(frame, 0, 1) ** (1.0 / GAMMA) * 255.0 + 0.5).astype(np.uint8)
    out = np.zeros((H, W, 4), dtype=np.uint8)
    out[:, :, 0] = enc[:, :, 2]  # B
    out[:, :, 1] = enc[:, :, 1]  # G
    out[:, :, 2] = enc[:, :, 0]  # R
    out.tofile(out_path)
    print(f"wrote {out_path} ({out.nbytes} bytes)")

if __name__ == "__main__":
    main()


File created successfully at: C:\Users\decid\Documents\projects\sauna\spikes\s3-distortion-truth\make_warped_grid.py (file state is current in your context — no need to Read it back)

"""Generate a pre-distorted grid test image for the eyes-in S3 check.

For every panel pixel (per eye, per color channel) evaluate the verified
S3 warp (poly3.py, poly+grow form) to get the source UV, then sample a
procedural grid that is STRAIGHT in source (tangent-linear) space. Through
the Beyond's lens the grid must therefore look straight, and because each
channel is warped with its own coefficients, the lines must show minimal
color fringing. Wrong formula direction / wrong eye / flipped v shows up
as curved lines, rainbow fringes, or an upside-down marker.

Pattern (source UV space, per eye):
  - grid lines every 0.05, intensity white
  - thicker center cross at (0.5, 0.5)
  - filled triangle ABOVE center pointing up (settles v orientation eyes-in)
  - left eye additionally gets a small filled square left of center
    (settles eye assignment)
  - outside source [0,1]: black (matches the grown render target bounds)

Output: raw B8G8R8X8 bytes, row-major, W*H*4 — loaded by s2_nvapi --image.

Usage: python make_warped_grid.py <config.json> [out.raw] [W H]
       (default 5088 2544 — the validated native DM mode, left half = left eye)
"""

import json
import sys

import numpy as np

from poly3 import load_config

GRID_STEP = 0.05
LINE_HW = 0.0018      # line half-width in source UV
CROSS_HW = 0.005      # center cross half-width
GAMMA = 2.2

def grid_intensity(su, sv):
    """Procedural pattern in source UV space -> intensity [0,1] array."""
    inside = (su >= 0) & (su <= 1) & (sv >= 0) & (sv <= 1)
    # grid lines
    du = np.abs(((su / GRID_STEP) + 0.5) % 1.0 - 0.5) * GRID_STEP
    dv = np.abs(((sv / GRID_STEP) + 0.5) % 1.0 - 0.5) * GRID_STEP
    lines = (du < LINE_HW) | (dv < LINE_HW)
    # center cross (thicker)
    cross = (np.abs(su - 0.5) < CROSS_HW) | (np.abs(sv - 0.5) < CROSS_HW)
    # up marker: filled triangle, apex at v=0.30, base at v=0.38 (v=0 is top)
    tri_h = 0.08
    tv = (sv - 0.30) / tri_h
    tri = (tv >= 0) & (tv <= 1) & (np.abs(su - 0.5) < 0.04 * tv)
    out = np.where(lines | cross | tri, 1.0, 0.06)  # faint floor shows extent
    return np.where(inside, out, 0.0)

def left_eye_marker(su, sv):
    """Small filled square left of center, left eye only."""
    return ((np.abs(su - 0.35) < 0.02) & (np.abs(sv - 0.5) < 0.02)).astype(float)

def render_eye(model, W, H):
    """Return (H, W, 3) float intensity for one eye panel."""
    u = (np.arange(W) + 0.5) / W
    v = (np.arange(H) + 0.5) / H
    uu, vv = np.meshgrid(u, v)
    img = np.zeros((H, W, 3), dtype=np.float32)
    scale = 0.5 / (1.0 + model.grow)
    for ci, ch in enumerate(model.channels):  # r, g, b
        tx = 2.0 * uu - 1.0 - ch["cx"]
        ty = 2.0 * vv - 1.0 - ch["cy"]
        r2 = tx * tx + ty * ty
        k1, k2, k3 = ch["k"]
        d = 1.0 + r2 * (k1 + r2 * (k2 + r2 * k3))
        su = 0.5 + (tx * d + ch["cx"]) * scale
        sv = 0.5 + (ty * d + ch["cy"]) * scale
        img[:, :, ci] = grid_intensity(su, sv)
    return img

def main():
    cfg_path = sys.argv[1]
    out_path = sys.argv[2] if len(sys.argv) > 2 else "warped_grid.raw"
    W = int(sys.argv[3]) if len(sys.argv) > 4 else 5088
    H = int(sys.argv[4]) if len(sys.argv) > 4 else 2544
    half = W // 2

    left, right, cfg = load_config(cfg_path)
    print(f"unit {cfg.get('device_serial_number')}  image {W}x{H} ({half}x{H}/eye)")

    frame = np.zeros((H, W, 3), dtype=np.float32)
    for eye_idx, model in ((0, left), (1, right)):
        img = render_eye(model, half, H)
        if eye_idx == 0:
            # left-eye marker rendered in green channel source space
            scale = 0.5 / (1.0 + model.grow)
            u = (np.arange(half) + 0.5) / half
            v = (np.arange(H) + 0.5) / H
            uu, vv = np.meshgrid(u, v)
            ch = model.channels[1]
            tx = 2.0 * uu - 1.0 - ch["cx"]
            ty = 2.0 * vv - 1.0 - ch["cy"]
            r2 = tx * tx + ty * ty
            k1, k2, k3 = ch["k"]
            d = 1.0 + r2 * (k1 + r2 * (k2 + r2 * k3))
            su = 0.5 + (tx * d + ch["cx"]) * scale
            sv = 0.5 + (ty * d + ch["cy"]) * scale
            m = left_eye_marker(su, sv)
            img = np.maximum(img, m[:, :, None])
        frame[:, eye_idx * half:(eye_idx + 1) * half, :] = img

    # gamma encode, pack B8G8R8X8 little-endian (byte order B,G,R,X)
    enc = (np.clip(frame, 0, 1) ** (1.0 / GAMMA) * 255.0 + 0.5).astype(np.uint8)
    out = np.zeros((H, W, 4), dtype=np.uint8)
    out[:, :, 0] = enc[:, :, 2]  # B
    out[:, :, 1] = enc[:, :, 1]  # G
    out[:, :, 2] = enc[:, :, 0]  # R
    out.tofile(out_path)
    print(f"wrote {out_path} ({out.nbytes} bytes)")

if __name__ == "__main__":
    main()


PowerShell

cd C:\Users\decid\Documents\projects\sauna\spikes\s3-distortion-truth; python make_warped_grid.py config_lhr_599f3b91.json warped_grid_599f3b91.raw

Generate pre-warped grid for enlyzeam unit


unit LHR-599F3B91  image 5088x2544 (2544x2544/eye)
wrote warped_grid_599f3b91.raw (51775488 bytes)


cd C:\Users\decid\Documents\projects\sauna\spikes\s3-distortion-truth; python -c "
import numpy as np
from PIL import Image
raw = np.fromfile('warped_grid_599f3b91.raw', dtype=np.uint8).reshape(2544, 5088, 4)
rgb = raw[:, :, [2,1,0]]
Image.fromarray(rgb).resize((1272, 636), Image.LANCZOS).save('preview.png')
print('ok')
"

Render preview PNG of warped grid


ok


Edit

//   s2_nvapi present <seconds> [--force-dsc]

//   s2_nvapi present <seconds> [--force-dsc] [--image <file.raw>]
//
// --image: raw B8G8R8X8 (byte order B,G,R,X) frame, W*H*4 bytes matching the
// selected mode, presented statically instead of the animated pattern. Used
// by spike S3 for the eyes-in pre-distorted grid check (R8G8B8A8 surfaces get
// a CPU swizzle; 10/16-bit surface flavors are rejected).


The file C:\Users\decid\Documents\projects\sauna\spikes\s2-vulkan-direct\s2_nvapi.cpp has been updated successfully. (file state is current in your context — no need to Read it back)

//   s2_nvapi present <seconds> [--force-dsc] [--image <file.raw>]
//
// --image: raw B8G8R8X8 (byte order B,G,R,X) frame, W*H*4 bytes matching the
// selected mode, presented statically instead of the animated pattern. Used
// by spike S3 for the eyes-in pre-distorted grid check (R8G8B8A8 surfaces get
// a CPU swizzle; 10/16-bit surface flavors are rejected).

// Requires the NDA HMD NVAPI headers/lib from a local mmi_test checkout —

// referenced in place, NOT vendored into this repo (licensing).

-//   s2_nvapi present <seconds> [--force-dsc]

+//   s2_nvapi present <seconds> [--force-dsc] [--image <file.raw>]

+// --image: raw B8G8R8X8 (byte order B,G,R,X) frame, W*H*4 bytes matching the

+// selected mode, presented statically instead of the animated pattern. Used

+// by spike S3 for the eyes-in pre-distorted grid check (R8G8B8A8 surfaces get

+// a CPU swizzle; 10/16-bit surface flavors are rejected).

// Exit codes: 0 ok, 2 no DM display, 3 acquire/modeset failed, 10 other.


  bool forceDsc = true, powerOff = false;
  int wantMode = -1;
  for (int i = 1; i < argc; i++) {
    if (strcmp(argv[i], "--force-dsc") == 0) forceDsc = true;
    if (strcmp(argv[i], "--no-force-dsc") == 0) forceDsc = false;
    if (strcmp(argv[i], "--power-off") == 0) powerOff = true;
    if (strcmp(argv[i], "--mode") == 0 && i + 1 < argc) wantMode = atoi(argv[i + 1]);
  }

  bool forceDsc = true, powerOff = false;
  int wantMode = -1;
  const char* imagePath = nullptr;
  for (int i = 1; i < argc; i++) {
    if (strcmp(argv[i], "--force-dsc") == 0) forceDsc = true;
    if (strcmp(argv[i], "--no-force-dsc") == 0) forceDsc = false;
    if (strcmp(argv[i], "--power-off") == 0) powerOff = true;
    if (strcmp(argv[i], "--mode") == 0 && i + 1 < argc) wantMode = atoi(argv[i + 1]);
    if (strcmp(argv[i], "--image") == 0 && i + 1 < argc) imagePath = argv[++i];
  }


  bool forceDsc = true, powerOff = false;
  int wantMode = -1;
  for (int i = 1; i < argc; i++) {
    if (strcmp(argv[i], "--force-dsc") == 0) forceDsc = true;
    if (strcmp(argv[i], "--no-force-dsc") == 0) forceDsc = false;
    if (strcmp(argv[i], "--power-off") == 0) powerOff = true;
    if (strcmp(argv[i], "--mode") == 0 && i + 1 < argc) wantMode = atoi(argv[i + 1]);
  }

  bool forceDsc = true, powerOff = false;
  int wantMode = -1;
  const char* imagePath = nullptr;
  for (int i = 1; i < argc; i++) {
    if (strcmp(argv[i], "--force-dsc") == 0) forceDsc = true;
    if (strcmp(argv[i], "--no-force-dsc") == 0) forceDsc = false;
    if (strcmp(argv[i], "--power-off") == 0) powerOff = true;
    if (strcmp(argv[i], "--mode") == 0 && i + 1 < argc) wantMode = atoi(argv[i + 1]);
    if (strcmp(argv[i], "--image") == 0 && i + 1 < argc) imagePath = argv[++i];
  }

// X8R8G8B8 mode + forced DSC 1.1/4-slice/8bpp per the device JSON.

+    if (strcmp(argv[i], "--image") == 0 && i + 1 < argc) imagePath = argv[++i];


  // --- optional static image: load raw frame into every surface once ---
  bool haveImage = false;
  ComPtr<ID3D12Resource> upload;
  if (imagePath) {
    if (rtvFmt != DXGI_FORMAT_B8G8R8X8_UNORM && rtvFmt != DXGI_FORMAT_B8G8R8A8_UNORM &&
        rtvFmt != DXGI_FORMAT_R8G8B8A8_UNORM) {
      fprintf(stderr, "FATAL: --image only supports 8-bit BGRX/BGRA/RGBA surfaces, got rtv format %d\n",
              (int)rtvFmt);
      return 10;
    }
    UINT W = (UINT)rdesc.Width, H = rdesc.Height;
    size_t rowBytes = (size_t)W * 4;
    UINT64 pitch = (rowBytes + 255) & ~(size_t)255;
    std::vector<uint8_t> data(rowBytes * H);
    FILE* f = fopen(imagePath, "rb");
    if (!f) { fprintf(stderr, "FATAL: cannot open %s\n", imagePath); return 10; }
    size_t got = fread(data.data(), 1, data.size(), f);
    fclose(f);
    if (got != data.size()) {
      fprintf(stderr, "FATAL: %s is %zu bytes, mode wants %zu (%ux%u*4)\n",
              imagePath, got, data.size(), W, H);
      return 10;
    }
    if (rtvFmt == DXGI_FORMAT_R8G8B8A8_UNORM) {  // file is BGRX -> swizzle
      for (size_t p = 0; p < data.size(); p += 4) {
        uint8_t t = data[p]; data[p] = data[p + 2]; data[p + 2] = t;
      }
      printf("image swizzled BGRX -> RGBA for surface format\n");
    }

    D3D12_HEAP_PROPERTIES hp{};
    hp.Type = D3D12_HEAP_TYPE_UPLOAD;
    D3D12_RESOURCE_DESC bd{};
    bd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
    bd.Width = pitch * H;
    bd.Height = 1;
    bd.DepthOrArraySize = 1;
    bd.MipLevels = 1;
    bd.Format = DXGI_FORMAT_UNKNOWN;
    bd.SampleDesc.Count = 1;
    bd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
    HRCHECK(dev->CreateCommittedResource(&hp, D3D12_HEAP_FLAG_NONE, &bd,
                                         D3D12_RESOURCE_STATE_GENERIC_READ,
                                         nullptr, IID_PPV_ARGS(&upload)));
    uint8_t* dst = nullptr;
    D3D12_RANGE noRead{0, 0};
    HRCHECK(upload->Map(0, &noRead, (void**)&dst));
    for (UINT y = 0; y < H; y++)
      memcpy(dst + y * pitch, data.data() + y * rowBytes, rowBytes);
    upload->Unmap(0, nullptr);

    for (int i = 0; i < NBUF; i++) {
      HRCHECK(alloc->Reset());
      HRCHECK(list->Reset(alloc.Get(), nullptr));
      D3D12_RESOURCE_BARRIER cb{};
      cb.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
      cb.Transition.pResource = res[i].Get();
      cb.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
      cb.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
      cb.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
      list->ResourceBarrier(1, &cb);
      D3D12_TEXTURE_COPY_LOCATION srcLoc{}, dstLoc{};
      srcLoc.pResource = upload.Get();
      srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
      srcLoc.PlacedFootprint.Footprint.Format = rtvFmt;
      srcLoc.PlacedFootprint.Footprint.Width = W;
      srcLoc.PlacedFootprint.Footprint.Height = H;
      srcLoc.PlacedFootprint.Footprint.Depth = 1;
      srcLoc.PlacedFootprint.Footprint.RowPitch = (UINT)pitch;
      dstLoc.pResource = res[i].Get();
      dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
      dstLoc.SubresourceIndex = 0;
      list->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr);
      cb.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
      cb.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON;
      list->ResourceBarrier(1, &cb);
      HRCHECK(list->Close());
      ID3D12CommandList* lists[] = {list.Get()};
      queue->ExecuteCommandLists(1, lists);
      HRCHECK(queue->Signal(fence.Get(), ++fenceVal));
      if (fence->GetCompletedValue() < fenceVal) {
        fence->SetEventOnCompletion(fenceVal, fenceEvt);
        WaitForSingleObject(fenceEvt, 2000);
      }
    }
    haveImage = true;
    printf("static image %s loaded into %d surfaces\n", imagePath, NBUF);
  }

  // --- modeset + power on ---


  // --- optional static image: load raw frame into every surface once ---
  bool haveImage = false;
  ComPtr<ID3D12Resource> upload;
  if (imagePath) {
    if (rtvFmt != DXGI_FORMAT_B8G8R8X8_UNORM && rtvFmt != DXGI_FORMAT_B8G8R8A8_UNORM &&
        rtvFmt != DXGI_FORMAT_R8G8B8A8_UNORM) {
      fprintf(stderr, "FATAL: --image only supports 8-bit BGRX/BGRA/RGBA surfaces, got rtv format %d\n",
              (int)rtvFmt);
      return 10;
    }
    UINT W = (UINT)rdesc.Width, H = rdesc.Height;
    size_t rowBytes = (size_t)W * 4;
    UINT64 pitch = (rowBytes + 255) & ~(size_t)255;
    std::vector<uint8_t> data(rowBytes * H);
    FILE* f = fopen(imagePath, "rb");
    if (!f) { fprintf(stderr, "FATAL: cannot open %s\n", imagePath); return 10; }
    size_t got = fread(data.data(), 1, data.size(), f);
    fclose(f);
    if (got != data.size()) {
      fprintf(stderr, "FATAL: %s is %zu bytes, mode wants %zu (%ux%u*4)\n",
              imagePath, got, data.size(), W, H);
      return 10;
    }
    if (rtvFmt == DXGI_FORMAT_R8G8B8A8_UNORM) {  // file is BGRX -> swizzle
      for (size_t p = 0; p < data.size(); p += 4) {
        uint8_t t = data[p]; data[p] = data[p + 2]; data[p + 2] = t;
      }
      printf("image swizzled BGRX -> RGBA for surface format\n");
    }

    D3D12_HEAP_PROPERTIES hp{};
    hp.Type = D3D12_HEAP_TYPE_UPLOAD;
    D3D12_RESOURCE_DESC bd{};
    bd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
    bd.Width = pitch * H;
    bd.Height = 1;
    bd.DepthOrArraySize = 1;
    bd.MipLevels = 1;
    bd.Format = DXGI_FORMAT_UNKNOWN;
    bd.SampleDesc.Count = 1;
    bd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
    HRCHECK(dev->CreateCommittedResource(&hp, D3D12_HEAP_FLAG_NONE, &bd,
                                         D3D12_RESOURCE_STATE_GENERIC_READ,
                                         nullptr, IID_PPV_ARGS(&upload)));
    uint8_t* dst = nullptr;
    D3D12_RANGE noRead{0, 0};
    HRCHECK(upload->Map(0, &noRead, (void**)&dst));
    for (UINT y = 0; y < H; y++)
      memcpy(dst + y * pitch, data.data() + y * rowBytes, rowBytes);
    upload->Unmap(0, nullptr);

    for (int i = 0; i < NBUF; i++) {
      HRCHECK(alloc->Reset());
      HRCHECK(list->Reset(alloc.Get(), nullptr));
      D3D12_RESOURCE_BARRIER cb{};
      cb.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
      cb.Transition.pResource = res[i].Get();
      cb.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
      cb.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
      cb.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
      list->ResourceBarrier(1, &cb);
      D3D12_TEXTURE_COPY_LOCATION srcLoc{}, dstLoc{};
      srcLoc.pResource = upload.Get();
      srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
      srcLoc.PlacedFootprint.Footprint.Format = rtvFmt;
      srcLoc.PlacedFootprint.Footprint.Width = W;
      srcLoc.PlacedFootprint.Footprint.Height = H;
      srcLoc.PlacedFootprint.Footprint.Depth = 1;
      srcLoc.PlacedFootprint.Footprint.RowPitch = (UINT)pitch;
      dstLoc.pResource = res[i].Get();
      dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
      dstLoc.SubresourceIndex = 0;
      list->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr);
      cb.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
      cb.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON;
      list->ResourceBarrier(1, &cb);
      HRCHECK(list->Close());
      ID3D12CommandList* lists[] = {list.Get()};
      queue->ExecuteCommandLists(1, lists);
      HRCHECK(queue->Signal(fence.Get(), ++fenceVal));
      if (fence->GetCompletedValue() < fenceVal) {
        fence->SetEventOnCompletion(fenceVal, fenceEvt);
        WaitForSingleObject(fenceEvt, 2000);
      }
    }
    haveImage = true;
    printf("static image %s loaded into %d surfaces\n", imagePath, NBUF);
  }

  // --- modeset + power on ---

+  // --- optional static image: load raw frame into every surface once ---

+    if (rtvFmt != DXGI_FORMAT_B8G8R8X8_UNORM && rtvFmt != DXGI_FORMAT_B8G8R8A8_UNORM &&

+        rtvFmt != DXGI_FORMAT_R8G8B8A8_UNORM) {

+      fprintf(stderr, "FATAL: --image only supports 8-bit BGRX/BGRA/RGBA surfaces, got rtv format %d\n",

+    UINT W = (UINT)rdesc.Width, H = rdesc.Height;

+    UINT64 pitch = (rowBytes + 255) & ~(size_t)255;

+    std::vector<uint8_t> data(rowBytes * H);

+    if (!f) { fprintf(stderr, "FATAL: cannot open %s\n", imagePath); return 10; }

+    size_t got = fread(data.data(), 1, data.size(), f);

+      fprintf(stderr, "FATAL: %s is %zu bytes, mode wants %zu (%ux%u*4)\n",

+              imagePath, got, data.size(), W, H);

+    if (rtvFmt == DXGI_FORMAT_R8G8B8A8_UNORM) {  // file is BGRX -> swizzle

+      for (size_t p = 0; p < data.size(); p += 4) {

+        uint8_t t = data[p]; data[p] = data[p + 2]; data[p + 2] = t;

+      printf("image swizzled BGRX -> RGBA for surface format\n");

+    bd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;

+    bd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;

+    HRCHECK(dev->CreateCommittedResource(&hp, D3D12_HEAP_FLAG_NONE, &bd,

+                                         D3D12_RESOURCE_STATE_GENERIC_READ,

+                                         nullptr, IID_PPV_ARGS(&upload)));

+    HRCHECK(upload->Map(0, &noRead, (void**)&dst));

+      memcpy(dst + y * pitch, data.data() + y * rowBytes, rowBytes);

+      HRCHECK(list->Reset(alloc.Get(), nullptr));

+      cb.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;

+      cb.Transition.pResource = res[i].Get();

+      cb.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;

+      cb.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;

+      cb.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;

+      D3D12_TEXTURE_COPY_LOCATION srcLoc{}, dstLoc{};

+      srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;

+      srcLoc.PlacedFootprint.Footprint.Format = rtvFmt;

+      srcLoc.PlacedFootprint.Footprint.Width = W;

+      srcLoc.PlacedFootprint.Footprint.Height = H;

+      srcLoc.PlacedFootprint.Footprint.Depth = 1;

+      srcLoc.PlacedFootprint.Footprint.RowPitch = (UINT)pitch;

+      dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;

+      list->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr);

+      cb.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;

+      cb.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON;

+      ID3D12CommandList* lists[] = {list.Get()};

+      queue->ExecuteCommandLists(1, lists);

+      HRCHECK(queue->Signal(fence.Get(), ++fenceVal));

+      if (fence->GetCompletedValue() < fenceVal) {

+        fence->SetEventOnCompletion(fenceVal, fenceEvt);

+        WaitForSingleObject(fenceEvt, 2000);

+    printf("static image %s loaded into %d surfaces\n", imagePath, NBUF);

st = NvAPI_D3D_DirectModeSetDisplayMode(&display, &mode);

if (st == NVAPI_HDCP_DISABLED) { printf("SetDisplayMode: HDCP disabled, ok\n"); st = NVAPI_OK; }


    HRCHECK(alloc->Reset());
    HRCHECK(list->Reset(alloc.Get(), nullptr));
    D3D12_RESOURCE_BARRIER b{};
    b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    b.Transition.pResource = res[i].Get();
    b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
    b.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
    b.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
    list->ResourceBarrier(1, &b);
    // structure-revealing pattern: left eye green field, right eye red field,
    // synchronized white bar sweeping across each eye. Any DSC/packing fault
    // shows immediately as wrong colors per eye or a broken/duplicated bar.
    {
      LONG W = (LONG)rdesc.Width, H = (LONG)rdesc.Height, half = W / 2;
      float lc[4] = {0.0f, 0.25f, 0.08f, 1.0f};
      float rc[4] = {0.25f, 0.0f, 0.08f, 1.0f};
      float wc[4] = {0.4f, 0.4f, 0.4f, 1.0f};
      D3D12_RECT rl{0, 0, half, H}, rr{half, 0, W, H};
      list->ClearRenderTargetView(rtv[i], lc, 1, &rl);
      list->ClearRenderTargetView(rtv[i], rc, 1, &rr);
      LONG barW = 64;
      LONG bx = (LONG)(fmod(t * 0.25, 1.0) * (half - barW));
      D3D12_RECT bars[2] = {{bx, 0, bx + barW, H},
                            {half + bx, 0, half + bx + barW, H}};
      list->ClearRenderTargetView(rtv[i], wc, 2, bars);
    }
    D3D12_RESOURCE_BARRIER b2 = b;
    b2.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
    b2.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON;
    list->ResourceBarrier(1, &b2);
    HRCHECK(list->Close());

    HRCHECK(alloc->Reset());
    HRCHECK(list->Reset(alloc.Get(), nullptr));
    if (!haveImage) {
      D3D12_RESOURCE_BARRIER b{};
      b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
      b.Transition.pResource = res[i].Get();
      b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
      b.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
      b.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
      list->ResourceBarrier(1, &b);
      // structure-revealing pattern: left eye green field, right eye red field,
      // synchronized white bar sweeping across each eye. Any DSC/packing fault
      // shows immediately as wrong colors per eye or a broken/duplicated bar.
      {
        LONG W = (LONG)rdesc.Width, H = (LONG)rdesc.Height, half = W / 2;
        float lc[4] = {0.0f, 0.25f, 0.08f, 1.0f};
        float rc[4] = {0.25f, 0.0f, 0.08f, 1.0f};
        float wc[4] = {0.4f, 0.4f, 0.4f, 1.0f};
        D3D12_RECT rl{0, 0, half, H}, rr{half, 0, W, H};
        list->ClearRenderTargetView(rtv[i], lc, 1, &rl);
        list->ClearRenderTargetView(rtv[i], rc, 1, &rr);
        LONG barW = 64;
        LONG bx = (LONG)(fmod(t * 0.25, 1.0) * (half - barW));
        D3D12_RECT bars[2] = {{bx, 0, bx + barW, H},
                              {half + bx, 0, half + bx + barW, H}};
        list->ClearRenderTargetView(rtv[i], wc, 2, bars);
      }
      D3D12_RESOURCE_BARRIER b2 = b;
      b2.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
      b2.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON;
      list->ResourceBarrier(1, &b2);
    }
    // --image: surfaces pre-filled; submit the empty list to keep the
    // validated present pacing structure identical.
    HRCHECK(list->Close());


    HRCHECK(alloc->Reset());
    HRCHECK(list->Reset(alloc.Get(), nullptr));
    D3D12_RESOURCE_BARRIER b{};
    b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    b.Transition.pResource = res[i].Get();
    b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
    b.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
    b.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
    list->ResourceBarrier(1, &b);
    // structure-revealing pattern: left eye green field, right eye red field,
    // synchronized white bar sweeping across each eye. Any DSC/packing fault
    // shows immediately as wrong colors per eye or a broken/duplicated bar.
    {
      LONG W = (LONG)rdesc.Width, H = (LONG)rdesc.Height, half = W / 2;
      float lc[4] = {0.0f, 0.25f, 0.08f, 1.0f};
      float rc[4] = {0.25f, 0.0f, 0.08f, 1.0f};
      float wc[4] = {0.4f, 0.4f, 0.4f, 1.0f};
      D3D12_RECT rl{0, 0, half, H}, rr{half, 0, W, H};
      list->ClearRenderTargetView(rtv[i], lc, 1, &rl);
      list->ClearRenderTargetView(rtv[i], rc, 1, &rr);
      LONG barW = 64;
      LONG bx = (LONG)(fmod(t * 0.25, 1.0) * (half - barW));
      D3D12_RECT bars[2] = {{bx, 0, bx + barW, H},
                            {half + bx, 0, half + bx + barW, H}};
      list->ClearRenderTargetView(rtv[i], wc, 2, bars);
    }
    D3D12_RESOURCE_BARRIER b2 = b;
    b2.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
    b2.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON;
    list->ResourceBarrier(1, &b2);
    HRCHECK(list->Close());

    HRCHECK(alloc->Reset());
    HRCHECK(list->Reset(alloc.Get(), nullptr));
    if (!haveImage) {
      D3D12_RESOURCE_BARRIER b{};
      b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
      b.Transition.pResource = res[i].Get();
      b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
      b.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
      b.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
      list->ResourceBarrier(1, &b);
      // structure-revealing pattern: left eye green field, right eye red field,
      // synchronized white bar sweeping across each eye. Any DSC/packing fault
      // shows immediately as wrong colors per eye or a broken/duplicated bar.
      {
        LONG W = (LONG)rdesc.Width, H = (LONG)rdesc.Height, half = W / 2;
        float lc[4] = {0.0f, 0.25f, 0.08f, 1.0f};
        float rc[4] = {0.25f, 0.0f, 0.08f, 1.0f};
        float wc[4] = {0.4f, 0.4f, 0.4f, 1.0f};
        D3D12_RECT rl{0, 0, half, H}, rr{half, 0, W, H};
        list->ClearRenderTargetView(rtv[i], lc, 1, &rl);
        list->ClearRenderTargetView(rtv[i], rc, 1, &rr);
        LONG barW = 64;
        LONG bx = (LONG)(fmod(t * 0.25, 1.0) * (half - barW));
        D3D12_RECT bars[2] = {{bx, 0, bx + barW, H},
                              {half + bx, 0, half + bx + barW, H}};
        list->ClearRenderTargetView(rtv[i], wc, 2, bars);
      }
      D3D12_RESOURCE_BARRIER b2 = b;
      b2.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
      b2.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON;
      list->ResourceBarrier(1, &b2);
    }
    // --image: surfaces pre-filled; submit the empty list to keep the
    // validated present pacing structure identical.
    HRCHECK(list->Close());

-    b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;

-    b.Transition.pResource = res[i].Get();

-    b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;

-    b.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;

-    b.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;

-    // structure-revealing pattern: left eye green field, right eye red field,

-    // synchronized white bar sweeping across each eye. Any DSC/packing fault

-    // shows immediately as wrong colors per eye or a broken/duplicated bar.

-      LONG W = (LONG)rdesc.Width, H = (LONG)rdesc.Height, half = W / 2;

-      D3D12_RECT rl{0, 0, half, H}, rr{half, 0, W, H};

-      list->ClearRenderTargetView(rtv[i], lc, 1, &rl);

-      list->ClearRenderTargetView(rtv[i], rc, 1, &rr);

-      LONG bx = (LONG)(fmod(t * 0.25, 1.0) * (half - barW));

-      D3D12_RECT bars[2] = {{bx, 0, bx + barW, H},

-                            {half + bx, 0, half + bx + barW, H}};

-      list->ClearRenderTargetView(rtv[i], wc, 2, bars);

+      b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;

+      b.Transition.pResource = res[i].Get();

+      b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;

+      b.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;

+      b.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;

+      // structure-revealing pattern: left eye green field, right eye red field,

+      // synchronized white bar sweeping across each eye. Any DSC/packing fault

+      // shows immediately as wrong colors per eye or a broken/duplicated bar.

+        LONG W = (LONG)rdesc.Width, H = (LONG)rdesc.Height, half = W / 2;

+        D3D12_RECT rl{0, 0, half, H}, rr{half, 0, W, H};

+        list->ClearRenderTargetView(rtv[i], lc, 1, &rl);

+        list->ClearRenderTargetView(rtv[i], rc, 1, &rr);

+        LONG bx = (LONG)(fmod(t * 0.25, 1.0) * (half - barW));

+        D3D12_RECT bars[2] = {{bx, 0, bx + barW, H},

+                              {half + bx, 0, half + bx + barW, H}};

+        list->ClearRenderTargetView(rtv[i], wc, 2, bars);

+      b2.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;

+      b2.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON;

-    b2.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;

-    b2.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON;

+    // --image: surfaces pre-filled; submit the empty list to keep the

+    // validated present pacing structure identical.

ID3D12CommandList* lists[] = {list.Get()};


1	cmake_minimum_required(VERSION 3.20)
2	project(s2_vulkan_direct CXX)
3
4	add_executable(s2 main.cpp)
5	target_include_directories(s2 PRIVATE external/Vulkan-Headers/include)
6	target_compile_features(s2 PRIVATE cxx_std_17)
7
8	add_executable(dispctl dispctl.cpp)
9	target_link_libraries(dispctl PRIVATE user32)
10	target_compile_features(dispctl PRIVATE cxx_std_17)
11
12	# NVAPI rung: needs the NDA HMD NVAPI from a local mmi_test checkout (not vendored).
13	set(MMI_TEST_DIR "C:/Users/decid/Documents/projects/mmi_test" CACHE PATH "BigscreenVR/mmi_test checkout")
14	if(EXISTS "${MMI_TEST_DIR}/directmode/nvapi/HMD-NDA-developer/nvapi.h")
15	  add_executable(s2_nvapi s2_nvapi.cpp)
16	  target_include_directories(s2_nvapi PRIVATE "${MMI_TEST_DIR}/directmode/nvapi/HMD-NDA-developer")
17	  target_link_libraries(s2_nvapi PRIVATE
18	    "${MMI_TEST_DIR}/directmode/nvapi/HMD-NDA-developer/amd64/nvapi64.lib"
19	    d3d12 dxgi)
20	  target_compile_features(s2_nvapi PRIVATE cxx_std_17)
21	endif()
22

cmake_minimum_required(VERSION 3.20)
project(s2_vulkan_direct CXX)

add_executable(s2 main.cpp)
target_include_directories(s2 PRIVATE external/Vulkan-Headers/include)
target_compile_features(s2 PRIVATE cxx_std_17)

add_executable(dispctl dispctl.cpp)
target_link_libraries(dispctl PRIVATE user32)
target_compile_features(dispctl PRIVATE cxx_std_17)

# NVAPI rung: needs the NDA HMD NVAPI from a local mmi_test checkout (not vendored).
set(MMI_TEST_DIR "C:/Users/decid/Documents/projects/mmi_test" CACHE PATH "BigscreenVR/mmi_test checkout")
if(EXISTS "${MMI_TEST_DIR}/directmode/nvapi/HMD-NDA-developer/nvapi.h")
  add_executable(s2_nvapi s2_nvapi.cpp)
  target_include_directories(s2_nvapi PRIVATE "${MMI_TEST_DIR}/directmode/nvapi/HMD-NDA-developer")
  target_link_libraries(s2_nvapi PRIVATE
    "${MMI_TEST_DIR}/directmode/nvapi/HMD-NDA-developer/amd64/nvapi64.lib"
    d3d12 dxgi)
  target_compile_features(s2_nvapi PRIVATE cxx_std_17)
endif()


PowerShell

cmake --build C:\Users\decid\Documents\projects\sauna\spikes\s2-vulkan-direct\build --config Release --target s2_nvapi 2>&1 | Select-Object -Last 6

Build s2_nvapi Release


MSBuild version 17.14.40+3e7442088 for .NET Framework

  s2_nvapi.cpp
  s2_nvapi.vcxproj -> C:\Users\decid\Documents\projects\sauna\spikes\s2-vulkan-direct\build\Release\s2_nvapi.exe


Build clean. Deploying to enlyzeam. SteamVR must die first — it holds direct mode.


Deploy exe, image, updated scripts to enlyzeam


(PowerShell completed with no output)


ssh decid@enlyzeam "Get-Process vrserver, vrcompositor, vrmonitor, vrstartup -ErrorAction SilentlyContinue | Select-Object Name, Id"

Check SteamVR processes on enlyzeam


Exit code 1


ssh decid@enlyzeam "cd C:\Users\decid\sauna-spikes\s3-distortion-truth; .\s2_nvapi.exe present 120 --image warped_grid_599f3b91.raw"

Run eyes-in warped grid presentation on enlyzeam