# M3 step 7 research — onboard IMU-cal store

Research-only deliverable (step 7 is strictly gated; no writes performed).
Question asked by the plan: does the Watchman protocol expose a config
*write* at all — and if unclear, STOP.

## Verdict

**Watchman flash write: STOP.** No documented write path. libsurvive's
Watchman support is read-only for the config block (our `calib_dump` /
`ReadWatchmanConfig` lineage); no public tooling writes Tundra config
flash, and the tracking calibration lives there — worst-case brick is
the tracking module. Per the plan's own gate, this branch is closed.

**Better target found: the Beyond MCU user-signature flash.** The cal
does not have to live next to the factory calibration at all — the MCU
(35BD:0101, the device `McuProx` already drives) has a fully documented
512-byte persistent config region with a write path designed for hosts:

- TLV+CRC8 entries: `tag(1) len(1) value(N) crc8(1)`, sequential, tag
  `0xFF` = end marker. CRC8 poly `0x07`, init `0xFF`, over the entry
  minus its CRC byte.
- HID commands: `U` READ_SIG (block 0–15, 32 B), `W` WRITE_SIG (block +
  32 B), `V` SAVE_SIG (commit). All 16 blocks must be written before
  SAVE (the save erases the region, then writes the RAM image).
- Firmware tags 0x01–0x0F in use (serials 0x01/0x08/0x09, prox params,
  brightness 0x0A, …). Source: beyond_synaptics wiki
  `Persistent-Configuration.md`, `USB-HID-Commands.md`.

**Unknown tags are safe.** Verified in firmware source
(`src/Drivers/signature.c`, beyond_synaptics): `sigv2_find_tag_loc`
walks entries and advances by `len+3` whether or not the tag matches —
a foreign tag with valid structure is simply skipped by every firmware
lookup. `sigv2_add_new_tag` itself appends at the first blank, so the
format is explicitly extensible.

## Proposed design (when unblocked)

- Tag: pick from the far end of the space, e.g. `0x40` ("sauna gyro
  cal"), payload = version byte + 3× float32 per-axis gyro scale (+
  optional 3× float32 bias), ~16–25 B of 512. CRC8 per the firmware
  routine.
- Procedure (mirrors the plan's scratch-key discipline):
  1. Read all 16 blocks twice, byte-identical → archive off-machine
     (per-serial, like the step 6 Watchman dumps). HARD PRECONDITION.
  2. Append our tag in the RAM image only (no firmware entries moved —
     byte-identical prefix preserved, including factory serials).
  3. Write 16 blocks, SAVE, read back all 16 → expect prefix identical
     + our entry appended.
  4. Power cycle → read again → identical.
- Failure surface: SAVE = erase-then-write; power loss in between
  leaves the region blank (factory serials in RAM image only). The
  archived dump restores via the same U/W/V commands — which is why
  the backup is the precondition, not a nicety.
- Restore tooling precedent exists upstream: beyond_synaptics
  `scripts/restore_config.py`, `config_lib.py`.

## Still gated on

1. Enlyzeam unit backups (Watchman step 6 dump for LHR-599F3B91 AND a
   16-block MCU signature dump for both units).
2. gyro_cal_vr confirmation run on enlyzeam: if ±2000 dps/int16 holds
   universally, step 7 is moot — the constant ships in code and no
   onboard store is needed at all.

## Out of scope confirmed

Writing anything to Watchman/Tundra flash. The factory IMU/distortion
calibration stays read-only forever (step 6 reads it; nothing writes).
