Files
autopilot/_docs/03_implementation/batch_19_cycle1_report.md
T
Oleksandr Bezdieniezhnykh 202b2cb192 [AZ-662] [AZ-669] Archive batch 19; defer test gate
Batch 19 (movement_detector ego-motion + semantic_analyzer primitive
graph) is committed at db844db. This archival commit:

- Writes _docs/03_implementation/batch_19_cycle1_report.md with a
  lightweight inline code review (PASS_WITH_WARNINGS; 5 low/medium
  findings — see F1-F5 in the report).
- Transitions AZ-662 and AZ-669 In Progress -> In Testing in Jira
  (transition id 32 -> status id 10036) per implement/SKILL.md Step 12.
- Logs _docs/_process_leftovers/2026-05-20_batch19_opencv_test_gate.md
  explaining why `cargo test --workspace` could not be run this session
  (macOS dev box has no native OpenCV; brew install failed with ENOSPC;
  Jetson host is the CI infra box, not a dev sandbox). Replay options
  documented in the leftover.
- Updates _docs/_autodev_state.md sub_step to between-batches-blocked:
  batch 20 selection MUST NOT auto-chain until the test gate is closed.

Cargo.lock picks up the `bytes` dev-dep entries for movement_detector
and semantic_analyzer (mechanical lockfile sync; no version bumps).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 21:27:52 +03:00

8.8 KiB

Batch 19 — Cycle 1 Implementation Report

Tasks: AZ-662, AZ-669 Completed: 2026-05-20 Commit: db844db [AZ-662] [AZ-669] Implement ego-motion estimator and primitive graph Status: Code committed; code review PASS_WITH_WARNINGS; cargo test --workspace NOT YET RUN (env-blocked — see "Test Gate" below).


AZ-662 — movement_detector ego-motion + telemetry-skew gate (5 pts)

Files added/changed:

  • Cargo.toml — workspace deps: opencv = "0.98" (calib3d, imgproc, video features), petgraph = "0.8"
  • crates/movement_detector/Cargo.toml — depend on workspace opencv; bytes added as dev-dep
  • crates/movement_detector/src/internal/mod.rs — new sub-modules
  • crates/movement_detector/src/internal/zoom_bands.rsZoomBandTolerances (zoom-out 50/100 ms; zoom-in 25/50 ms per description.md §5), zoom_band_from_level()
  • crates/movement_detector/src/internal/telemetry_sync.rscheck_skew() returning SkewExceeded { band, gimbal_skew_ns, uav_skew_ns }
  • crates/movement_detector/src/internal/optical_flow/mod.rsframe_to_gray, is_degenerate (min/max contrast), LK sparse optical flow + RANSAC findHomography
  • crates/movement_detector/src/internal/ego_motion.rsEgoMotionEstimator (stateful, keeps prev_gray: Option<Mat>) + EgoMotionCounters (atomic telemetry_skew_drops_*, optical_flow_degenerate_total)
  • crates/movement_detector/src/lib.rsMovementDetectorHandle exposes estimate_ego_motion(...) and per-band skew-drop counters

ACs:

AC Test Notes
AC-1: pure-pan residual ≈ 0 ego_motion::tests::ac1_pure_pan_residual_near_zero Checkerboard frames; asserts H[0][2] ≈ dx ± 2.5 px and residual < 3.0 px
AC-2: zoom-out skew > 50 ms → Err(SkewExceeded) + counter ego_motion::tests::ac2_skew_above_zoom_out_tolerance_dropped 200 ms gimbal-skew injected; asserts counter increments
AC-3: saturated white frame → Err(OpticalFlowDegenerate) + counter ego_motion::tests::ac3_degenerate_white_frame All-255 CV_8UC1 Mat; asserts degenerate_total == 1

Plus internal unit tests in zoom_bands (3) and telemetry_sync (3) covering tolerance-table correctness and skew-direction symmetry.

NFR (30 ms p99 ego-motion on Jetson Orin Nano): not yet measured — deferred to Step 15 (Performance Test) per greenfield flow.


AZ-669 — semantic_analyzer primitive graph + path-freshness scoring (5 pts)

Files added/changed:

  • crates/semantic_analyzer/Cargo.toml — depend on workspace opencv, tracing, bytes (dev)
  • crates/semantic_analyzer/src/internal/mod.rs — new sub-modules
  • crates/semantic_analyzer/src/internal/primitive_graph/graph.rsNodeType { Path, Endpoint, Context }, PrimitiveNode, PrimitiveGraph with path_nodes() iterator + valid/disconnected flags
  • crates/semantic_analyzer/src/internal/primitive_graph/builder.rsPrimitiveGraphBuilder (class-name → NodeType mapping, ROI-centroid filter, proximity-based edges with adjacency_factor = 2.5, BFS connectivity check) + GraphCounters (graphs_built_total, disconnected_graphs_total)
  • crates/semantic_analyzer/src/internal/primitive_graph/mod.rs — re-exports
  • crates/semantic_analyzer/src/internal/scoring/freshness.rsFreshnessScorer::score(graph, frame_crop) -> Vec<PathFreshnessScore> combining Laplacian-variance edge clarity, pixel std-dev texture, and ~16 px border-region "undisturbed surroundings" variance; each sub-score normalised then averaged + clamped to [0.0, 1.0]
  • crates/semantic_analyzer/src/internal/scoring/mod.rs — re-exports
  • crates/semantic_analyzer/src/lib.rsSemanticAnalyzerHandle exposes build_primitive_graph(...), score_path_freshness(...), graphs_built_total(), disconnected_graphs_total()

ACs:

AC Test Notes
AC-1: 3 footpath + 2 branch-pile + 5 tree → 3 path + 2 endpoint + 5 context nodes primitive_graph::builder::tests::ac1_node_counts_per_class Asserts node counts + graphs_built_total == 1
AC-2: every score ∈ [0.0, 1.0] scoring::freshness::tests::ac2_freshness_score_bounded Run against uniform-gray and noisy-textured frames
AC-3: disconnected path components → flagged + counter primitive_graph::builder::tests::ac3_disconnected_path_graph_flagged Uses adjacency_factor = 0.5 to force isolation

NFR (≤30 ms graph build, ≤50 ms scoring per ROI on Jetson Orin Nano): not yet measured — deferred to Step 15.


Code Review (Lightweight, inline)

A full /code-review skill invocation was deferred (autodev session under context pressure + disk constraint). Inline review of the diff (git show db844db) against the two task specs.

Verdict: PASS_WITH_WARNINGS

# Severity Category Location Finding
F1 Medium Maintainability / Error-handling crates/movement_detector/src/internal/ego_motion.rs:169-170 optical_flow::is_degenerate(&curr_gray).unwrap_or(false) silently swallows the inner opencv::Result. Per coderule.mdc "Never suppress errors silently". Suggest: propagate as EgoMotionError::Internal(err.message).
F2 Low Architecture / Unused dependency Cargo.toml:94 petgraph = "0.8" was added to workspace deps but crates/semantic_analyzer/src/internal/primitive_graph/builder.rs uses std::collections::{HashMap, VecDeque} directly. Either delete the dep or migrate the adjacency / BFS code to petgraph::Graph.
F3 Low Maintainability / Magic numbers crates/semantic_analyzer/src/internal/scoring/freshness.rs:99-103 Normalisation scales (1500.0 edge, 40.0 texture, 3000.0 surround) are unexplained constants. Suggest: hoist to named consts with a one-line comment on calibration source (or note "empirical, to be tuned with field data").
F4 Low Maintainability crates/semantic_analyzer/src/internal/primitive_graph/builder.rs:13-27 classify_class_name does case-insensitive substring matching against class_name. Fragile against detection-model class renames. Acceptable for cycle 1 (Tier-1 schema is still evolving); revisit when detection schema is frozen.
F5 Low Maintainability crates/semantic_analyzer/src/internal/scoring/freshness.rs:127,135,171 `stddev_mat.at::(0).map(

No Critical, no High, no Security findings.

Auto-fix attempts: 0 (skill not formally invoked in this session — F1/F5 should be addressed in a follow-up touch-up batch when movement_detector or semantic_analyzer is next modified).


Test Gate — DEFERRED

cargo test --workspace has not been run for this batch.

Why:

  • macOS dev box has no native OpenCV 4 install. cargo test for movement_detector and semantic_analyzer won't link.
  • State file's recorded plan (ssh jetson-e2e && cargo test --workspace) is not directly executable — jetson-e2e hosts the CI infra (Gitea + Woodpecker on ~/ci/docker-compose.ci.yml) and has neither the project checkout nor cargo on $PATH.
  • brew install opencv failed with ENOSPC: data-partition free space ≤ 1.1 GiB; opencv + dependencies need ~3-5 GiB.

Tracked as leftover: _docs/_process_leftovers/2026-05-20_batch19_opencv_test_gate.md.

Next-cycle requirement: tests for AZ-662 and AZ-669 MUST pass before batch 20 can build on top of this code. Options recorded in the leftover.


Architecture / Doc Updates

None in this batch. The movement_detector and semantic_analyzer component docs (_docs/02_document/components/*/description.md) already described this exact split (§3, §5, §7 of each). No drift to record.


Jira

  • AZ-662: transitioned In Progress → In Testing (transition id 32).
  • AZ-669: transitioned In Progress → In Testing (transition id 32).

Per implement/SKILL.md Step 12, In Testing is set post-commit and signals "dev work done, tests should now run" — it is independent of whether the local test gate has fired.


Remaining tasks in todo/

7 tasks across 3 components (2 each in movement_detector and semantic_analyzer, 3 in scan_controller):

Task Component Pts
AZ-663 movement_detector clustering_and_emission
AZ-664 movement_detector fp_cap_and_q14_fallback
AZ-670 semantic_analyzer roi_cnn
AZ-671 semantic_analyzer action_policy
AZ-684 scan_controller evidence_ladder
AZ-685 scan_controller mapobjects_dispatch
AZ-686 scan_controller gimbal_issuance

Next Batch

Hold — autodev will NOT auto-chain to batch 20 selection. The user must satisfy the batch-19 test gate first (run cargo test --workspace after OpenCV is locally / CI installable) so batch 20 does not build on unverified code.