Files
gps-denied-onboard/_docs/03_implementation/batch_47_cycle1_report.md
T
Oleksandr Bezdieniezhnykh b64f3a1b93 [AZ-337] Archive task spec + batch 47 report + state bump
- _docs/02_tasks/todo/AZ-337_c2_ultra_vpr.md
  -> _docs/02_tasks/done/AZ-337_c2_ultra_vpr.md
- _docs/03_implementation/batch_47_cycle1_report.md (new)
- _docs/_autodev_state.md: last_completed_batch 46 -> 47;
  sub_step.detail "batch 47 complete - selecting batch 48"

AZ-337 transitioned in Jira: In Progress -> In Testing.

Batches 45/46/47 close the C2 production path (Protocol +
FaissBridge + NetVLAD baseline + UltraVPR primary).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 22:44:22 +03:00

9.2 KiB

Batch 47 / Cycle 1 — Implementation Report

Date: 2026-05-13 Tasks: AZ-337 — C2 UltraVPR Primary Backbone (5pt) Total complexity: 5 points Result: PASS_WITH_WARNINGS (per-batch code review) Jira tracker state: AZ-337 transitioned To Do → In Progress → In Testing

Scope

UltraVPR is the Documentary Lead's PRIMARY backbone per components/02_c2_vpr/description.md § 1 and is the strategy wired by default when config.c2_vpr.strategy == "ultra_vpr". Runs on the C7 TensorRT runtime (AZ-298) or ONNX-Runtime fallback (AZ-299) — explicitly NOT on the PyTorch FP16 runtime (reserved for the NetVLAD baseline). This runtime isolation means a TRT engine compile bug can fall back to NetVLAD without simultaneously breaking both strategies. This batch closes the C2 default production path that NetVLAD (B46) and FaissBridge (B45) prepared for.

Files Changed

Production (new)

  • src/gps_denied_onboard/components/c2_vpr/ultra_vpr.pyUltraVprStrategy class implementing the VprStrategy Protocol. Constructor wires InferenceRuntimeCut + DescriptorIndexCut + UltraVprBackbonePreprocessor + DescriptorNormaliser + FaissBridge + FdrClient + Clock. Module-level create(config, descriptor_index, inference_runtime) factory consumed by build_vpr_strategy. Module-level DESCRIPTOR_DIM = 512 constant (NOT a config knob; spec § Constraints "preprocessing parameters are hard-coded"). Engine load + output-shape assertion at create() time.
  • src/gps_denied_onboard/components/c2_vpr/_preprocessor_ultra_vpr.pyUltraVprBackbonePreprocessor implementing the C2-internal BackbonePreprocessor Protocol. Decode → centre-crop around the camera calibration's principal point (cx, cy from intrinsics_3x3, fallback to geometric centre + WARN log when calibration is absent) → resize (384, 384) → ImageNet mean/std → FP16 NCHW.

Production (modified)

  • None. The composition-root _register_strategy_architecture helper (added in B46) already no-ops cleanly for strategies that do not expose MODEL_NAME / architecture_factory. UltraVPR consumes a pre-compiled .trt engine (no PyTorch nn.Module), so no registration is needed.

Tests (new)

  • tests/unit/c2_vpr/test_ultra_vpr.py — 29 tests covering all 12 ACs (with per-AC variants for AC-2, AC-6, AC-7, AC-8, AC-9, AC-11) + preprocessor contract (5 tests, including test_preprocessor_mean_std_correct_on_grey_image) + constructor validation (2) + FDR record emission (1) + no-architecture-registration verification (1).

Docs (new)

  • _docs/03_implementation/reviews/batch_47_review.md — per-batch code review (PASS_WITH_WARNINGS).

Acceptance Criteria Coverage

All 12 ACs of AZ-337 have at least one covering unit test:

AC Description Status
AC-1 Protocol conformance covered
AC-2 L2-norm == 1.0 ± 1e-3 FP16 (512,) covered + single-stage L2 enforcement spy
AC-3 Deterministic across 3 calls covered
AC-4 retrieve_topk == k, label="ultra_vpr", sorted covered
AC-5 descriptor_dim() stable returns 512 covered
AC-6 Engine output shape mismatch → ConfigError covered (shape + missing-key variants)
AC-7 VprBackboneError on forward failure + ERROR log + FDR covered (3 variants)
AC-8 VprPreprocessError on corrupt image + ERROR log + FDR covered (2 variants)
AC-9 Calibration absent → geometric centre + WARN log covered + offset-changes-crop control test
AC-10 IndexUnavailableError propagated unchanged covered
AC-11 Composition-root wiring + INFO log "c2.vpr.ready" + BUILD-flag gate covered (TRT accept + ONNX-RT accept + PyTorch reject)
AC-12 Top-1 distance > threshold → WARN log via FaissBridge covered

Test Results

  • Full unit suite: 1637 passed / 80 environment-skipped / 0 failed in ~66s. Up from 1608 at the close of Batch 46 (+29 new tests).
  • Focused per-component: c2_vpr/test_ultra_vpr.py 29/29 PASS; c2_vpr/test_net_vlad.py 31/31 PASS (no regression); c2_vpr/test_faiss_bridge.py 22/22 PASS (no regression).
  • Lint: ruff check clean on every new file.
  • AZ-507 layering lint: test_ac6_only_compose_root_imports_concrete_strategies PASS.

Architectural Decisions

  1. No composition-root changes. UltraVPR uses a pre-compiled .trt engine; there is no PyTorch nn.Module to register with C7's architecture registry. The strategy module therefore does NOT expose MODEL_NAME / architecture_factory. The composition-root _register_strategy_architecture helper (added in B46 for NetVLAD) no-ops cleanly for this case — no code change needed there. Verified by test_create_does_not_register_pytorch_architecture.

  2. DESCRIPTOR_DIM = 512 is a module constant, not a config knob. UltraVPR's research code drop ships with a fused embedding head that produces D=512. Unlike NetVLAD (whose Linear PCA layer makes D a tunable knob), UltraVPR's D is weights-coupled. Making it a config knob would let an operator silently break AC-2.1b recall floor. AC-5 / AC-6 / AC-7 all assume 512.

  3. Single-stage L2 normalisation, enforced by spy. UltraVPR is single-stage L2 (no intra-cluster step like NetVLAD). The test test_ac2_embedding_is_single_stage_l2_no_intra_cluster_path uses a spy normaliser to assert that intra_cluster_normalise is NEVER called — a regression guard against a future refactor accidentally introducing the two-stage path (which would silently degrade recall on the Derkachi corpus).

  4. Engine load + output-shape assertion at create() time, NOT first frame. Misconfiguration surfaces at startup (5 seconds into the binary) rather than 17 minutes into a flight at the first VPR query. Matches Constraint § 5 of the task spec.

  5. Principal-point-aware centre-crop. The preprocessor extracts (cx, cy) from intrinsics_3x3 and anchors the square crop there, falling back to geometric centre + WARN log when calibration is absent. Matches the upstream UltraVPR contract; relevant for wide-angle cameras where the principal point is not at the image centre.

  6. Pattern parity with NetVLAD where semantics permit. Method shapes for embed_query / retrieve_topk / _emit_* mirror NetVladStrategy line-for-line; UltraVPR-specific paths (single-stage L2, "embedding" output key, TRT runtime, no architecture registry, principal-point crop) are clearly localised. This makes AZ-339 / AZ-340 (the remaining C2 strategies) drop into the same skeleton with minimal new architectural decisions.

  7. AC-12 delegation to FaissBridge. The top-1 distance WARN log is owned by FaissBridge (B45); UltraVPR inherits this path for free via the same one-line retrieve_topk delegation that NetVLAD uses — one production touchpoint, two strategies.

Carried-over Findings

  • F1 from cumulative review 43-45 / Batch 46: _iso_ts_from_clock duplicated across modules — Batch 47 adds the 7th copy (ultra_vpr.py line 296). AZ-508 hygiene PBI exists for consolidation. Recommend prioritising AZ-508 before AZ-339 / AZ-340 add copies #8 and #9.
  • F2 from Batch 46: spec→implementation drift on C7 API names. AZ-337 spec also exhibits the same drift (runtime.forward(), runtime.load_engine() — neither exists in v1.0.0 Protocol). Affects upcoming AZ-339 / AZ-340 / AZ-358 / AZ-349. Spec-hygiene PBI recommended.

New Findings (per Batch 47 code review)

  • F3 (Low, Test-Robustness): _extract_principal_point uses (cx, cy) == (0, 0) zero-detection to identify "no calibration". Safe in production (no real camera has cx == 0 and cy == 0) but the heuristic exists only because the dataclass field is typed Any instead of Optional. Tighten when calibration becomes properly optional.

Jira Tracker

  • AZ-337 transitioned: To Do → In Progress → In Testing.
  • Task spec archived: _docs/02_tasks/todo/AZ-337_c2_ultra_vpr.md_docs/02_tasks/done/AZ-337_c2_ultra_vpr.md.

Next

Batches 45 / 46 / 47 close the C2 production path: AZ-336 (Protocol), AZ-341 (FaissBridge), AZ-338 (NetVLAD baseline), AZ-337 (UltraVPR primary). The Documentary Lead has both baseline + primary backbones operational; default-strategy wiring works end-to-end at the composition root.

Per the autodev orchestrator loop: select Batch 48. Top candidates:

  • AZ-508 — ISO-timestamp helper consolidation (2pt). Closes F1 before AZ-339 / AZ-340 add copies #8 and #9. Strongly recommended.
  • AZ-339 — MegaLoc + MixVPR strategies (5pt). Two strategies in one task; same skeleton as UltraVPR but different backbones.
  • AZ-340 — SelaVPR + EigenPlaces + SALAD strategies (5pt). Three strategies in one task; closes the C2 alternative-backbone buffet.
  • AZ-358 — C4 OpenCV/GTSAM pose estimator (5pt). Opens a new component (C4); changes pace from C2-heavy streak.
  • AZ-389 — C5 internal orthorectifier (3pt). Different component (C5); unblocks mid-flight tile generation.
  • Spec-hygiene PBI to address F2 from B46 / B47 (refresh AZ-339 / AZ-340 against the live C7 v1.0.0 Protocol).

The next cumulative review (batches 46-48) will trigger after Batch 48 lands.