[AZ-597] Batch 77: replay_mode helpers + 13 scenario stub rewires

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>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-17 09:52:05 +03:00
parent 6554d568f1
commit f49d803252
22 changed files with 798 additions and 85 deletions
@@ -0,0 +1,96 @@
# 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 NotImplementedError` under `e2e/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_ready` skip
gate keeps the loaders unreached when `E2E_SITL_REPLAY_DIR` is unset.
### AZ-597 — replay_mode helpers + 13 scenario rewires (3 cp)
* **`runner/helpers/replay_mode.py`** (new):
* `NullFrameSink` — counter-only `FrameSink` (`frames_written: int`).
* `NullFcInboundEmitter` — counter-only `FcInboundEmitter`
(`samples_emitted: int`).
* `default_frame_period_ms() -> int` + `DEFAULT_FRAME_PERIOD_MS = 33`
(30 fps).
* `load_replay_json(filename)` — generic JSON loader. Raises
`FileNotFoundError` (env-unset / file-missing) or `ValueError`
(malformed, file pointer included).
* `resolve_replay_subdir(name)` — directory loader. Raises
`FileNotFoundError` (env-unset / subdir-missing).
* `imu_replay_noop(csv_path)` — explicit no-op; signature mirrors
`imu_replay.ImuReplayer.replay` for future live-mode parity.
* Single shared `_resolve_replay_root_or_raise(reason)` enforces
the `E2E_SITL_REPLAY_DIR` semantics 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 new
`runner/helpers/replay_mode.py` path.
## Out of scope (deferred)
* The actual SITL replay fixture builder (separate ticket — will
populate `${E2E_SITL_REPLAY_DIR}/` with `gps_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_tests` suite: **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 NotImplementedError` under `e2e/tests/` returns
**zero** matches.
## State
* Spec moved: `_docs/02_tasks/todo/AZ-597_scenario_stub_cleanup.md`
`_docs/02_tasks/done/`.
* `_docs/_autodev_state.md` advanced to `last_completed_batch: 77`.
* `last_cumulative_review` remains `batches_73-75`; next K=3
cumulative review fires at the end of batch 78.