# Batch 101 — Cycle 2 — AZ-700

**Date**: 2026-05-20
**Tasks**: AZ-700 (replay map visualization).
**Story points**: 3.
**Jira status**: AZ-700 → `In Testing`.

## What shipped

A new operator-side console-script `gps-denied-render-map` that
renders a self-contained HTML map (folium / Leaflet) of the
estimator's track vs the tlog ground-truth track, with start/end
markers, 100 m + 50 m scale circles, optional summary banner from
AZ-699, and an `--offline-tiles` mode for Jetsons without internet
access.

folium is gated behind a new `[operator-tools]` optional-dependency
group so the airborne binary never pays for it.

## Files changed

Production (2):

- `src/gps_denied_onboard/cli/render_map.py` (new)
- `pyproject.toml` (new optional-deps group + console script)

Tests (1):

- `tests/unit/test_az700_render_map.py` (14 tests, all PASS local)

Docs:

- `_docs/02_document/tests/environment.md` — refined the 2026-05-20
  "Jetson-only" policy to: unit tests local-OK, e2e Jetson-only.
- `.cursor/rules/testing.mdc` — added the refined policy as an
  always-applied agent rule.
- `_docs/02_tasks/done/AZ-700_replay_map_visualization.md` —
  Implementation Notes appended; moved from `todo/`.

## AC coverage

| AC   | Test / Artefact                                                                          | Result |
| ---- | ---------------------------------------------------------------------------------------- | ------ |
| AC-1 | `test_cli_writes_html_with_default_tiles`                                                | PASS (local).  |
| AC-2 | `test_render_map_html_emits_two_polylines`, `…emits_four_markers_and_two_circles`        | PASS   |
| AC-3 | `test_render_map_html_emits_two_polylines`, `…emits_four_markers_and_two_circles`        | PASS — exactly 2 polylines + 4 markers + 2 scale circles. |
| AC-4 | Visual smoke on Tier-2 Jetson with operator-opened `map.html`                            | DEFERRED to Jetson (correctly per refined test-env policy).  |
| AC-5 | `test_render_map_html_offline_tiles_omits_openstreetmap`, `…_template_uses_local_url`    | PASS   |

## Test run

```
tests/unit/test_az700_render_map.py        14 PASS in 2.5 s
Wider regression slice                    107 PASS  1 SKIP
```

The 1 skipped test is the pre-existing AZ-698 AC-5 e2e smoke
(needs the real video in `_docs/00_problem/input_data/flight_derkachi/`).

## Strict typing

```
mypy --strict src/gps_denied_onboard/cli/render_map.py
→ Success: no issues found in 1 source file.
```

The lazy folium import uses
`# type: ignore[import-untyped, import-not-found, unused-ignore]`
so strict passes cleanly whether or not `[operator-tools]` is
installed.

## Refined test-environment policy

Mid-batch the user clarified the existing "Jetson-only across all
tiers" policy: **unit tests may run locally, e2e tests stay
Jetson-only.** Rationale: the unit suite is fully synthetic, so a
local PASS = Jetson PASS for that tier; the e2e suite is bound to
Jetson hardware / latency / SITL and a local run is meaningless.

Captured in:

- `_docs/02_document/tests/environment.md` — banner + new
  "Where each tier runs (active policy)" table + Test Execution
  section rewritten.
- `.cursor/rules/testing.mdc` — appended "Test environment (this
  project)" section so future agent sessions cannot drift back to
  running e2e locally.

## Next batch

Batch 102 — **AZ-701** (HTTP replay API service). Depends on
AZ-697 (truth source) and AZ-699 (report writer). Last task in
cycle 2.
