Skip to content

feat(marshaling): activate dense signal in normal flow + densify mock (graph fidelity)#250

Merged
ryan-johnson2 merged 1 commit into
develfrom
signal-fidelity
Jun 26, 2026
Merged

feat(marshaling): activate dense signal in normal flow + densify mock (graph fidelity)#250
ryan-johnson2 merged 1 commit into
develfrom
signal-fidelity

Conversation

@ryan-johnson2

Copy link
Copy Markdown
Contributor

Problem

The marshaling RSSI graph rendered at too-low fidelity — only the coarse path-1 stream (one scalar SignalChunk per node_data heartbeat → a handful of samples per node). The dense path-2 (current_marshal_data / get_pilotraceSignalHistory, which signal_trace prefers) was built but dormant: RotorHazard only exposes a run's dense per-tick history for a saved heat, and the production staging loop never made a heat current (default practice-mode save_laps is a no-op).

Fix — two levers

1. Activate path-2 in the normal flow. The subtlety that drove the design: RotorHazard only records live laps for a node with a seated pilot once a heat is current — a selected empty heat rejects every crossing (server.py's pass gate pilot_id != PILOT_ID_NONE or current_heat is HEAT_ID_NONE). So selecting a heat at stage-time would silently break live recording (verified — it does). Instead:

  • The heat runs in RH practice mode (live laps flow; the node interface still accumulates the dense history_values throughout).
  • At heat-END the driver makes a savable heat current (add_heatheat_dataset_current_heat, driver-thread-synchronous), then stops the race → DONE auto-triggers the transport's save_lapsrace_listget_pilotrace pull, which persists and returns the accumulated dense trace into the finishing heat's log.
  • disarm now drives this graceful finish (stop + a dense-pull settle window routing the resulting SignalHistory into the right heat) rather than just clearing the armed slot.
  • Heat selection is never emitted from the socket callback — heat_data is broadcast on every heat mutation, so an emit-per-event there floods and drops the link (a real drop I hit and fixed). The driver stashes the newest heat id (take_savable_heat) and selects once, on its own thread.

2. Densify the mock. The testkit CSV held the dense peak/nadir history columns flat, so RH's run-length dedup collapsed the history. node_csv/plan_csv now emit a textured per-tick peak/nadir envelope with distinct first/last times (two history entries per tick), while keeping the coarse node_peak (col 4) stable so the existing fidelity assertion still holds.

Adapter/transport: new RawHeatData parse + take_heat_ids/take_savable_heat + ensure_savable_heat. Also fixes a pre-existing non-exhaustive Event match in the rh_live test helper that blocked the live build.

Verification (throwaway dockerized RH — never :8080)

Through the production spawn_registry_bridge flow (rh_connect_live), running a real heat end-to-end:

coarse stream = 30 samples → dense history = 168 samples (full-fidelity upgrade
                                          activated by the normal heat loop)

The rh_signal dense gate (now driven via ensure_savable_heat, the production precondition): coarse 5 → dense ~80. The fidelity test still asserts exact coarse samples (== 180). The graph's SignalTraceView renders the dense trace unchanged (prefer-dense fold + canvas downsample).

Gates

  • cargo xtask ci green (fmt + clippy + 78 test suites + binding drift).
  • cargo xtask live: 16 targets pass — incl. rh_live, all 3 rh_signal, rh_connect_live (both tests, with the new path-2 production assertion), and every engine format e2e (the densified mock didn't regress any).
  • Pre-existing, unrelated gridfpv-server full_event_live compile breakage on devel (router(state) vs EventRegistry) is untouched — out of scope.
  • Did not deploy; did not touch :8080.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SL34wThSSbdutQoiLdJ2ZJ

… (graph fidelity)

The marshaling RSSI graph showed only the coarse path-1 stream (one scalar
`SignalChunk` per `node_data` heartbeat — a handful of samples), because the
dense path-2 (`current_marshal_data`/`get_pilotrace` → `SignalHistory`) only
yields data for a *saved* RotorHazard heat, and the production staging loop
never made a heat current. Two levers fix it:

1. Activate path-2 in the normal flow. RotorHazard only persists a run's dense
   per-tick history for a current (saved) heat, AND only records LIVE laps for a
   node with a seated pilot once a heat is current (an empty selected heat
   rejects every crossing — `server.py`'s pass gate). So selecting a heat at
   stage-time would break live recording. Instead the heat runs in RH practice
   mode (laps flow; the node interface still accumulates the dense history), and
   at heat-END the driver makes a savable heat current, then stops the race →
   DONE auto-triggers `save_laps` → `race_list` → `get_pilotrace`, persisting and
   returning the accumulated dense trace into the finishing heat's log. The
   `disarm` path now drives this finish (stop + dense-pull settle) rather than
   just clearing the armed slot. Heat selection is driver-thread-synchronous
   (`add_heat` → `heat_data` → `set_current_heat`), never emitted from the socket
   callback — `heat_data` is broadcast on every heat mutation, so an
   emit-per-event there floods and drops the link.

2. Densify the mock. The testkit CSV held the dense peak/nadir history columns
   flat, so RH's run-length-dedup collapsed the history. `node_csv`/`plan_csv`
   now emit a textured per-tick peak/nadir envelope with distinct first/last
   times (two history entries per tick), keeping the coarse `node_peak` (col 4)
   stable so the existing fidelity assertion still holds.

Adapter: parse `heat_data` (`RawHeatData`) → `take_heat_ids`; transport stashes
the newest savable heat id for the driver (`take_savable_heat`) +
`ensure_savable_heat`. Also fixes a pre-existing non-exhaustive `Event` match in
the `rh_live` test helper that blocked the `live` build.

Verified on a throwaway dockerized RH (NOT :8080) via the production
`spawn_registry_bridge` flow (rh_connect_live): coarse stream 30 → dense history
~168 samples; rh_signal dense path coarse 5 → dense ~80. `cargo xtask ci` green;
16 live targets pass. (Pre-existing, unrelated `gridfpv-server full_event_live`
compile breakage on devel is untouched.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SL34wThSSbdutQoiLdJ2ZJ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant