feat(marshaling): activate dense signal in normal flow + densify mock (graph fidelity)#250
Merged
Conversation
… (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
This was referenced Jun 26, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The marshaling RSSI graph rendered at too-low fidelity — only the coarse path-1 stream (one scalar
SignalChunkpernode_dataheartbeat → a handful of samples per node). The dense path-2 (current_marshal_data/get_pilotrace→SignalHistory, whichsignal_traceprefers) 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-modesave_lapsis 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 gatepilot_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:history_valuesthroughout).add_heat→heat_data→set_current_heat, driver-thread-synchronous), then stops the race →DONEauto-triggers the transport'ssave_laps→race_list→get_pilotracepull, which persists and returns the accumulated dense trace into the finishing heat's log.disarmnow drives this graceful finish (stop + a dense-pull settle window routing the resultingSignalHistoryinto the right heat) rather than just clearing the armed slot.heat_datais 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_csvnow emit a textured per-tick peak/nadir envelope with distinct first/last times (two history entries per tick), while keeping the coarsenode_peak(col 4) stable so the existing fidelity assertion still holds.Adapter/transport: new
RawHeatDataparse +take_heat_ids/take_savable_heat+ensure_savable_heat. Also fixes a pre-existing non-exhaustiveEventmatch in therh_livetest helper that blocked thelivebuild.Verification (throwaway dockerized RH — never :8080)
Through the production
spawn_registry_bridgeflow (rh_connect_live), running a real heat end-to-end:The
rh_signaldense gate (now driven viaensure_savable_heat, the production precondition): coarse 5 → dense ~80. The fidelity test still asserts exact coarse samples (== 180). The graph'sSignalTraceViewrenders the dense trace unchanged (prefer-dense fold + canvas downsample).Gates
cargo xtask cigreen (fmt + clippy + 78 test suites + binding drift).cargo xtask live: 16 targets pass — incl.rh_live, all 3rh_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).gridfpv-server full_event_livecompile breakage ondevel(router(state)vsEventRegistry) is untouched — out of scope.🤖 Generated with Claude Code
https://claude.ai/code/session_01SL34wThSSbdutQoiLdJ2ZJ