# Spike S1 — IMU without SteamVR

**Verdict: PASS** (2026-06-10)

**Question:** does the Tundra stream IMU unconditionally, or only after SteamVR wakes it?
This was the project's highest-leverage unknown (BASELINE risk #1).

**Method:** Python + hidapi, blocking interrupt reads on `28DE:2300` MI_00 ("IMU"), 20 s
capture (`capture.py`). Evidence in `data-stage1/` (`stats.json`, `environment.txt`; raw
capture + per-sample CSV kept locally, gitignored).

**Conditions:** SteamVR (vrserver/vrcompositor) never ran since the machine's last reboot
(user-confirmed). Steam client was running but does not touch Watchman devices. No feature
report, no wake sequence — device streamed from the first cold `ReadFile`.

## Measurements

| Quantity | Value |
|---|---|
| Report ID / size | only `0x20`, all 52 B (19429 reports, zero other IDs) |
| Report rate | 971.4 /s |
| Window structure | **sliding 3-sample window** — consecutive reports overlap; ~⅔ of report boundaries repeat samples |
| Unique sample rate | **993.8 Hz** (19877 unique by `(timecode, seq)` in 20.0 s) — matches config `firmware_config.imu_rate: 1000` |
| Sample period | median Δtimecode 48307 ticks ≈ 1.0064 ms at 48 MHz nominal device clock |
| Accel scale | mean rest magnitude 4093.3 LSB → **4096 LSB/g (±8 g) re-confirmed by our own measurement** |
| Gyro at rest | bias ≈ (1.8, 2.9, 4.3) LSB, max abs 8 — quiet |
| Magnetometer | **no evidence on MI_00** (no other report IDs) |

## Consumer implications (for the tracking layer)

- Parse **all 3 samples** per report and dedupe the window overlap by `(timecode, seq)`.
  (Predecessor parsed only sample 0 and misread the timecode as µs.)
- `timecode` is a 48 MHz device clock, uint32 → wraps every ~89.5 s. Handle wrap.
- `HidD_GetInputReport` fails (error 87) — interrupt reads only (`ReadFile`/hidapi).
- Sequence number is per-sample uint8, increments by 1, wraps at 256.
- **28DE:2300 is NOT unique to the Beyond** (found in M1 field test): any wired
  Watchman device — Index controller, Tundra tracker — enumerates with the same
  VID/PID/interfaces and can enumerate *ahead* of the Beyond. An idle desk
  controller streams plausible-looking 0x20 reports at ~250 Hz with gravity-sane
  accel; only motion-deadness gives it away. Select by LHR serial or by pairing
  with the Beyond MCU (35BD:0101) via USB location-path ancestor (the Tundra and
  MCU sit on sibling hubs inside the headset — common grandparent).

## Still open (deliberately)

- **Gyro scale — RESOLVED in M2 gate 2 (2026-06-10):** measured against SteamVR
  pose ground truth (tools/gyro_cal_vr.py, least-squares over ~60 s of free
  motion on LHR-1F8E25F1): **0.0010660 rad/s/LSB**, per-axis spread 0.44%. That
  is the standard MEMS **±2000 °/s full scale over int16**: nominal
  2000·(π/180)/32768 = 0.00106526 rad/s/LSB, measured +0.07% off — neither of
  the S1 priors (1/1024, 0.001). Treat the nominal constant as universal pending
  the enlyzeam unit's confirmation run. Same capture verified the config
  imu plus_x/plus_z basis on a real unit (axis columns 1.2–2.0° from config,
  det +1) — the gate-3 rotation-sense check.
- Theoretical residual: a wake persisting in Tundra RAM across a host reboot while the
  headset stayed powered. No supporting evidence anywhere; revisit only if a field unit
  contradicts.
