CAMP live sonar coverage tile cache (Part B of #121, opt-in, anti-entropy)#139
Merged
Conversation
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>
… on prune; dtor sub teardown
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part B of #121 — the CAMP-managed live tile cache. (Part A, the source-agnostic
RasterFieldSourceabstraction, is split out to #134 to emerge from this secondimplementation.)
Closes #121.
Summary
CAMP now displays live sonar coverage as it's collected (roadmap "Tool 2"): it
subscribes to the boat's
SonarVisualizationTileanti-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_tilesstream is opt-in. CAMP detects the topic and spawns aLive Coverage [<source>]layer in an inactive(available)state (subscribing only to thesmall
transient_localcatalog). "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
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 fromGggsTileLayer, marked for unification via Unified source-agnostic RasterFieldSource render abstraction (Part A of #121) #134), ROS subscribe, coalesced + joinablewrite-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 inros/live_coverage/(depends onmarine_interfaces/marine_tiled_raster_store, so it's incamp_map_ros; thecamp_mapcore stays ROS-free per ADR-0002).ros/node.cpp;item_types.h+package.xmlupdated.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 afterintegrating current
jazzy: 135 tests, 0 failures, 1 skipped.Review
Driven via
/run-issue; 2 rounds of pre-pushreview-code(Deep — ~1700 LOC + concurrencyacross 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
RasterFieldSourcerender abstraction (unifies the GLpath duplicated here).
display-grade, self-heals).
Authored-By:
Claude Code AgentModel:
Claude Opus 4.8 (1M context)