Skip to content

CAMP live sonar coverage tile cache (Part B of #121, opt-in, anti-entropy)#139

Merged
rolker merged 10 commits into
jazzyfrom
feature/issue-121
Jun 28, 2026
Merged

CAMP live sonar coverage tile cache (Part B of #121, opt-in, anti-entropy)#139
rolker merged 10 commits into
jazzyfrom
feature/issue-121

Conversation

@rolker

@rolker rolker commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Part B of #121 — the CAMP-managed live tile cache. (Part A, the source-agnostic
RasterFieldSource abstraction, is split out to #134 to emerge from this second
implementation.)

Closes #121.

Summary

CAMP now displays live sonar coverage as it's collected (roadmap "Tool 2"): it
subscribes to the boat's SonarVisualizationTile anti-entropy transport (uma#230, cube#78),
caches tiles in memory + on disk, renders them through the existing GGGS GL path, and
reconciles against the catalog (request missing / prune absent) — surviving CAMP restarts.

The live cache is a display-grade preview (lossy/quantized) — explicitly not data of
record; the authoritative full-precision data always stays on the boat. (ADR-0006.)

Activation — discover automatically, subscribe on opt-in

Per operator decision (bandwidth, #71 — cell links): discovery is automatic and cheap, but
the large coverage_tiles stream is opt-in. CAMP detects the topic and spawns a
Live Coverage [<source>] layer in an inactive (available) state (subscribing only to the
small transient_local catalog). "Enable live coverage" subscribes to the tile stream,
warm-loads the disk cache, and starts request/prune; the enabled state persists per source.

What's included

  • ADR-0006 — live tile cache persistence contract (in-memory render, crash-safe
    write-through, warm-load, anti-entropy; discover-auto / subscribe-opt-in stated as a
    conscious ADR-0005 tension).
  • SonarLiveTile — in-memory dequantized Float32 tile (value = raw*scale + offset),
    generic per-band patch-apply + auto-range. No GDAL on the hot path.
  • SonarLiveCacheLayer (ros::Layer) — GL render (currently duplicated from
    GggsTileLayer, marked for unification via Unified source-agnostic RasterFieldSource render abstraction (Part A of #121) #134), ROS subscribe, coalesced + joinable
    write-through (atomic temp+rename, off the GUI thread), warm-load, opt-in toggle, per-source
    persistence. Reconciler/tile mutation stays single-threaded on the GUI thread; ROS callbacks
    only copy + queued-signal (ADR-0001).
  • SonarLiveCacheManager — topic discovery → one inactive layer per source. Lives in
    ros/live_coverage/ (depends on marine_interfaces/marine_tiled_raster_store, so it's in
    camp_map_ros; the camp_map core stays ROS-free per ADR-0002).
  • Wired in ros/node.cpp; item_types.h + package.xml updated.

Tests

test_sonar_live_cache (headless — no Qt loop, no ROS node, no boat): PatchApplyDequantize,
WarmLoadRoundTrip, DowntimeGapReconcile (the issue's acceptance scenario: seed A,B,C →
catalog A,B,D → request={D}, prune={C} → converge), PruneTimestampGate. Full suite after
integrating current jazzy: 135 tests, 0 failures, 1 skipped.

Review

Driven via /run-issue; 2 rounds of pre-push review-code (Deep — ~1700 LOC + concurrency
across the ROS↔GUI boundary). Round 1 caught a real write-through race (uncoalesced concurrent
workers on a shared temp file) — fixed. Final: approved, 0 must-fix. Branch merged up to
current jazzy (no conflicts) and rebuilt green before this PR.

Deferred / follow-ups


Authored-By: Claude Code Agent
Model: Claude Opus 4.8 (1M context)

Claude Code Agent and others added 10 commits June 28, 2026 17:32
CAMP-managed live tile cache (Part B): SonarLiveCacheLayer + anti-entropy reconciler + write-through persistence + downtime-gap test
Add the live sonar-tile cache consumer: discover the SonarVisualizationTile
topic, render dequantized in-memory Float32 tiles through a GL path duplicated
from GggsTileLayer (no GDAL on the hot path), and drive anti-entropy
convergence with marine_tiled_raster_store::TileCatalogReconciler. Part A
(RasterFieldSource) stays deferred to camp#134.

Activation is opt-in (ADR-0006 D5, serves #71): discovery auto-spawns a
discovered-but-inactive layer that may subscribe to the cheap transient-local
coverage_catalog, but subscribes to the best-effort coverage_tiles stream and
publishes TileRequest only when the operator enables it. The per-source enabled
flag persists via QSettings, so an enabled source re-subscribes on warm restart
while a never-enabled one stays passive.

Persistence (ADR-0006): in-memory state is authoritative; write-through is
atomic temp+rename, crash-safe only, all received bands (band name = GDAL band
description); warm-load reads the cache dir before subscribing and seeds the
reconciler at version 0 to close the downtime gap. One int64 version unit
(ns since epoch) across newest-wins, markHave, and the prune timestamp-gate.

Threading (ADR-0001): ROS callbacks only copy the message + marshal to the GUI
thread (queued invokeMethod); the GUI-thread slot does all reconcile/markHave/
drop/applyPatch/write-through. The reconciler is never touched off-thread.

New TUs live in ros/live_coverage/ (not raster/) so camp_map stays ROS-free;
they compile into camp_map_ros, which gains marine_tiled_raster_store + GDAL.

Files:
- docs/decisions/0006-live-tile-cache-persistence.md (new ADR)
- ros/live_coverage/sonar_live_tile.{h,cpp} (in-memory tile, dequantize,
  warm-load/write-through, wire<->gggs conversions)
- ros/live_coverage/sonar_live_cache_layer.{h,cpp} (render + subscribe +
  reconcile + opt-in toggle)
- ros/live_coverage/sonar_live_cache_manager.{h,cpp} (topic discovery)
- map/item_types.h: + SonarLiveCacheLayerType
- ros/node.cpp: wire the manager
- CMakeLists.txt / package.xml: + marine_tiled_raster_store, GDAL, new test
- test/test_sonar_live_cache.cpp: warm-load, patch/dequantize, downtime-gap
  reconcile, prune timestamp-gate (headless; no Qt loop / ROS node / boat)

Tests: 129 tests, 0 failures, 4 skipped (offscreen-GL renders skip in-container).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 28, 2026 20:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@rolker rolker merged commit b35542e into jazzy Jun 28, 2026
1 check failed
@rolker rolker deleted the feature/issue-121 branch June 28, 2026 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unified band/colormap RasterFieldSource render + CAMP-managed live tile cache (anti-entropy)

2 participants