# Spike S3 — distortion ground truth

**Question (PLAN.md):** which direction do the production `DISTORT_POLY3` coeffs run?
EVT/DVT configs used `DISTORT_DPOLY3` with negative leading coeffs (−0.22); production B1
uses `DISTORT_POLY3` with positive ones (+0.24). Public reimplementations (Monado) evaluate
the DPOLY3 family as `d = 1/(1 + k1·r² + k2·r⁴ + k3·r⁶)`. Is POLY3 the same curve fitted the
other way (`d = 1 + …` direct), and is the `0.5/(1+grow_for_undistort)` output rescale right?

**Pass criterion:** standalone evaluator matches an OpenVR `ComputeDistortion` UV-grid dump
from one dev unit within sub-pixel tolerance (max error < 1 px at 2544 px/eye).

## Files

| file | role |
|---|---|
| `poly3.py` | standalone evaluator, 4 hypothesis variants (form × grow-rescale) |
| `dump_openvr.py` | ground-truth dump — needs SteamVR running with the Beyond (one sanctioned dev-time run) |
| `verify.py` | tests all hypotheses against a dump; `--selftest` for synthetic round-trip |

## Run order (on the rig with the headset)

```
pip install openvr
# 1. Start SteamVR with the Beyond connected (it takes direct mode — fine).
python dump_openvr.py                # writes distortion_dump_<serial>.json
# 2. Quit SteamVR.
python verify.py distortion_dump_<serial>.json "C:\Program Files (x86)\Steam\config\lighthouse\<lhr-serial>\config.json"
```

The config path is SteamVR's cache of the unit's SIP flash JSON (refreshed by the same
SteamVR run). `verify.py` warns if dump and config serials differ.

Bonus ground truth captured in the same dump: `GetProjectionRaw` (settles intrinsics→frustum
convention incl. cy sign), `GetEyeToHeadTransform` (settles canting matrix + which side gets
+ipd/2) — both cross-checked by `verify.py` for M2.
