Add `runner/helpers/replay_mode.py` (NullFrameSink, NullFcInboundEmitter, default_frame_period_ms, load_replay_json, resolve_replay_subdir, imu_replay_noop) and rewire all 13 scenarios off their local `_resolve_*` / `_drive_*` / `_push_*` NotImplementedError stubs. Closes the offline FDR-replay execution path. `grep raise NotImplementedError` under `e2e/tests/` now returns zero matches. +17 unit tests (626 total, up from 608). Unit-test behaviour unchanged (scenarios still skip via b75 sitl_replay_ready gate when E2E_SITL_REPLAY_DIR is unset). Co-authored-by: Cursor <cursoragent@cursor.com>
4.3 KiB
Batch 77 Report — replay_mode helpers + 13 scenario stub rewires (cycle 1, batch 11 of test phase)
Batch: 77
Date: 2026-05-17
Context: Test implementation (greenfield Step 10 — Implement Tests)
Tasks: AZ-597 (3 cp) — 1 task (scenario stub cleanup bundle)
Cycle: 1
Verdict: COMPLETE — PASS (self-reviewed; see reviews/batch_77_review.md)
Summary
Closes the offline FDR-replay path that AZ-594 (b74), AZ-595 (b75),
and AZ-596 (b76) opened. After those three batches, the only remaining
NotImplementedError stubs in the scenario suite were a grab-bag of
local _resolve_* / _drive_* / _push_* helpers duplicated across
13 scenario files. They all reduced to the same FDR-replay pattern —
either a no-op counter (frame sink, FC inbound emitter, IMU replay
driver) or a JSON read from ${E2E_SITL_REPLAY_DIR}/ (per-frame GT,
single-image observation, outage frames subdir).
This batch bundles those into one shared runner/helpers/replay_mode.py
module + rewires the 13 scenarios off their local stubs. After the
batch:
grep raise NotImplementedErrorundere2e/tests/returns zero matches.- Once the SITL replay fixture builder lands (separate ticket), every scenario becomes runnable end-to-end with no further per-scenario edits.
- Unit-test mode is unchanged — the b75
sitl_replay_readyskip gate keeps the loaders unreached whenE2E_SITL_REPLAY_DIRis unset.
AZ-597 — replay_mode helpers + 13 scenario rewires (3 cp)
runner/helpers/replay_mode.py(new):NullFrameSink— counter-onlyFrameSink(frames_written: int).NullFcInboundEmitter— counter-onlyFcInboundEmitter(samples_emitted: int).default_frame_period_ms() -> int+DEFAULT_FRAME_PERIOD_MS = 33(30 fps).load_replay_json(filename)— generic JSON loader. RaisesFileNotFoundError(env-unset / file-missing) orValueError(malformed, file pointer included).resolve_replay_subdir(name)— directory loader. RaisesFileNotFoundError(env-unset / subdir-missing).imu_replay_noop(csv_path)— explicit no-op; signature mirrorsimu_replay.ImuReplayer.replayfor future live-mode parity.- Single shared
_resolve_replay_root_or_raise(reason)enforces theE2E_SITL_REPLAY_DIRsemantics exactly once.
- 13 scenarios rewired (all
_resolve_*/_drive_*/_push_*stubs deleted):_resolve_frame_sink→NullFrameSink()in: FT-P-01, FT-P-02, FT-P-04, FT-P-05, FT-P-07, FT-P-08, FT-P-09-AP, FT-P-09-iNav, FT-P-10, FT-P-11, FT-N-01, FT-N-02, FT-N-03, FT-N-04._resolve_fc_inbound_emitter→NullFcInboundEmitter()in: FT-P-02, FT-P-04, FT-P-10._drive_imu_replay→imu_replay_noop(...)in: FT-P-07, FT-N-02._resolve_frame_period_ms→default_frame_period_ms()in: FT-N-03, FT-N-04._resolve_outage_injection_frames→resolve_replay_subdir("outage_frames")in: FT-N-03._resolve_gt_per_frame→load_replay_json("gt_per_frame.json")- dataclass projection in: FT-N-01.
_push_single_image_and_observe→load_replay_json("single_image_observation.json")- tuple projection in: FT-P-03/14.
e2e/_unit_tests/test_directory_layout.py— registers the newrunner/helpers/replay_mode.pypath.
Out of scope (deferred)
- The actual SITL replay fixture builder (separate ticket — will
populate
${E2E_SITL_REPLAY_DIR}/withgps_state.json,gt_per_frame.json,single_image_observation.json,outage_frames/,ekf_divergence_events.json, etc.). - Live MAVLink router / pymavlink plumbing (separate live-mode infrastructure ticket).
Test Results
- New unit tests: 17 (2 null-sink, 2 null-emitter, 1 frame-period, 2 imu-replay-noop, 6 load_replay_json, 4 resolve_replay_subdir).
- Full
e2e/_unit_testssuite: 626 passed in 127 s (previous cumulative: 608 → +18 net = +17 new replay_mode tests + 1 new directory-layout parametrize entry). - No new linter errors.
grep raise NotImplementedErrorundere2e/tests/returns zero matches.
State
- Spec moved:
_docs/02_tasks/todo/AZ-597_scenario_stub_cleanup.md→_docs/02_tasks/done/. _docs/_autodev_state.mdadvanced tolast_completed_batch: 77.last_cumulative_reviewremainsbatches_73-75; next K=3 cumulative review fires at the end of batch 78.