cockpit/geo: /garmin/:location scene routing + ver-8 Iceland wire + terrain polish + banked Garmin IMG sources#90
Conversation
…field The #88 geo beautification (uExag=10 relief + terrainColor height-recolour + uRuler breathing) was gated on `isGeoScene`, so it fired for BOTH /ice (Iceland DEM heightfield) AND /geo (Berlin OSM buildings). A heightfield is a dense, watertight surface — like the anatomy body — and beautifies beautifully. But an OSM building is a thin extruded footprint: exaggerating it 10x and recolouring it dark-at-base turns every building into a black needle. That is the "needles in black" regression on Berlin, and Iceland-on-#88 loaded the sparse OSM-buildings bake too, so it needled the same way. Fix: gate beautification on TERRAIN, not on "any geo". - New `isTerrainScene` (iceland only). uGeo/uExag/uRuler + the per-frame breathing tick are terrain-only; buildings + anatomy keep uGeo=0 (baked aColor), uExag=1, uRuler=0 → a solid city, byte-identical to the pre-#88 building render. - uExag 10 -> 30 for terrain so true-scale Iceland relief (~0.0067 in [-1,1]) actually reads as mountains; uRuler dialed 0.03 -> 0.008 (gentle, cannot needle a dense surface). Taxonomy: a map must never read as skin/muscle/organ/skeleton. The geo sidebar layer is named per domain — "terrain" for /ice, "buildings" for /geo — never the anatomy taxonomy; /geo title corrected to "OSM buildings"; subtitle scene-aware. DEM bake: clamp elevation to [0, 2200] m (Hvannadalshnúkur ≈ 2110) so a Terrarium nodata sentinel or tile-decode outlier can't spike a needle into the heightfield. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
The on-disk iceland_dem.helix.soa.gz was baked at 44a474f, before the top-elevation clamp landed. That bake used `dem.elev[i].max(0.0)` (bottom clamp only), so any Terrarium nodata sentinel / tile-decode outlier became a needle spike. HEAD's baker adds `.clamp(0.0, 2200.0)` but the artifact was never re-emitted, so /ice still rendered a needle field. Re-fetch the keyless Terrarium DEM and rebake through the current (clamp-applying) baker. On this pull the DEM max is 1992.5 m — already under 2200 — so the clamp is a no-op on the data; the remaining spikiness was pure vertical over-exaggeration. At uExag=30 the ~512 m grid spacing turns ordinary ~500 m local reliefs into steep 45°+ cones (needles). Lowering the terrain uExag to 15 keeps strong, believable 3D relief (Vatnajökull ice cap, coastal fjords, Westfjords peninsula all read clearly) while eliminating the spike field. Verified with headless software-WebGL (swiftshader) screenshots of /ice. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
…ent-fetched) The DEM/OSM bakers emit a `.blocks` depth-cascade sidecar next to each `.soa.gz`, but the client only fetches the `.soa.gz` (geo scenes disable server LOD, and no `.blocks` has ever been tracked). Formalize that convention so a rebake's regenerated sidecar stops showing up as an untracked file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
…to the Iceland terrain The DEM `/ice` terrain read as bad-quality raw HHTL grid because it carried only the coarse HHTL *place* (the downsampled Terrarium grid) and NONE of the helix golden-spiral *residue* the anatomy body has. Two culprits: the baker never applied the CurveRuler, and the cockpit only approximated it with a GLSL float-hash (a substitute, not the real crate). Use the actual crate — the same `helix::CurveRuler` the `sculpt` tool and the `/helix` body already use: - geo: depend on `../../lance-graph/crates/helix` (path dep under the `helix` feature, like lance-graph-contract). Port `sculpt`'s `mix` + `ruler_phase` verbatim (the stride-4-over-17 golden-spiral phase regenerated from each vertex's lattice address — "phase is convention, not data"). In the DEM baker, compute first-pass gradient normals, displace each LAND vertex along its normal by a bipolar phase whose amplitude grows with elevation (bold highlands, gentle coast; ocean stays a clean plane), then RECOMPUTE normals from the displaced surface so the shading shows the detail. Factored the normal pass into `terrain_normals` (called twice). Compiles clean with `--features helix`. - cockpit: remove the GLSL float-hash `kurvenlineal`/`rhash` and the per-frame breathing; the residue is now carried by the decoded position + Signed360 normal. The shader only lifts the island by uExag (=15) and Gouraud-shades. uRuler/uTime retained-but-0. Also fixes the two PR #89 review findings while in these files: - iceland_dem `read_demgrid` header guard `< 24` -> `< 32` (it reads bytes 24..32). - fetch_iceland_dem.py: abort on ANY missing tile after retries instead of silently baking a synthetic flat-sea rectangle (was tolerating up to 10% missing). Wiring compiles (cargo check --features helix + tsc). The rendered result is NOT yet screenshot-verified — that needs a DEM re-fetch + rebake + cockpit rebuild. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
…e/volcano/rock The height-only palette can't tell a green valley from a lava field at the same elevation, so /ice read as a muddy height guess. Give the terrain its missing THIRD axis — the surface KIND — alongside the HHTL address and the Helix residue it already had, and drape it from real satellite imagery. - fetch: DEMG v2 adds a per-cell RGB drape from ESRI World Imagery (keyless, z/y/x) fetched on the SAME grid as the Terrarium elevation, stitched + block-mean downsampled together. Generic stitch_grid helper; aborts on any missing tile of either layer. `--no-imagery` still emits v1. - baker: read the v2 RGB block; classify each vertex into a surface KIND (Ocean/Green/Rock/Scree/Ice/Lava) from imagery luminance/saturation + elevation; colour = the kind's canonical palette, textured by the real imagery luminance (light/shadow within the kind) AND the helix CurveRuler residue phase (within-kind variation). The node now carries all three axes: WHERE (HHTL) · SURFACE (Helix) · WHAT (kind). - wire: encode_mesh_bso2 gains a per-vertex colour arg → BSO2 ver-7 = a rgb[nv] block after the row column (empty colours → ver-6, unchanged OSM path). - cockpit: decode the ver-7 colour block into aColor; the shader now paints every scene from aColor (terrain = kind/imagery/helix, buildings/anatomy = concept tint) instead of guessing colour from height. terrainColor GLSL is now unused (dead; removal deferred). Compiles: cargo check --features helix + tsc. Not yet render-verified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
…eep it in colour) The full 16.5M-vert render striped the surface into stepped vertical ridges: `ruler_phase` is a per-CELL step function, and displacing a dense heightfield along its normal by it turns every cell boundary into a step (uExag then amplifies them). At native 16.5M resolution the heightfield already carries the fine relief, so the golden-spiral residue does not belong in the geometry here — it stays in the per-vertex COLOUR (within-kind texture), where a step reads as texture, not spikes. Verified: the striping is gone; smooth textured terrain. (The residue geometry displacement remains correct on the sparse/anatomy meshes, where cells are large relative to the mesh.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
…b limit) → Release asset The textured full-res bake (16.5M verts + imagery drape) is ~151MB gzipped, over GitHub's 100MB per-file limit, so it can no longer be a git-tracked file (the old 9.6MB sparse bake was). Untrack + gitignore it; it ships as a GitHub Release asset (the cockpit already falls back to the release URL when the local file is absent) — or via Git LFS if preferred. Also gitignore the in-repo /scratchpad (demgrids, screenshots, harness). NOTE: do not merge until the artifact is delivered (release asset uploaded / LFS wired), else /ice has no bake to load. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
…the /helix look) The colour multiplied the kind by the RAW satellite luminance, and Iceland imagery is dark + desaturated, so the terrain came out muddy. The /helix body looks sweet because conceptColor is a clean base × a pleasant brightness variation, not a photo. Match that: - brighter, cleaner KIND palette (fresh grass green, warm tan rock, bright glacier white, clean deep-blue ocean, dark-basalt lava) — the kind's own hue, not the muddy satellite tone. - brightness = a SWEET within-kind variation from the helix residue (±18%, the "surfel" texture) × a GENTLE CENTRED imagery light/shadow (±14%, bright lifts / dark dips / mid unchanged) — no harsh luminance multiply that darkened everything. Verified in a real headless WebGL render: fresh green lowlands, warm highlands, bright glaciers, clean ocean, smooth, full 16.5M verts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
…aude/maps The full-res textured bake (iceland_dem.helix.soa.gz, 16.5M verts + imagery-kind colour) is 151MB — over GitHub's 100MB per-file limit — so it ships as a store-mode 2-part split zip that git accepts (z01 = 76MB, zip = 72MB, both < 100MB). Reassemble with: zip -s 0 .claude/maps/iceland_dem_16m.zip --out /tmp/full.zip && unzip -o /tmp/full.zip -> iceland_dem.helix.soa.gz (sha256 cb3ef34ed6f0e153315b9881ec9dbb15b7ef24364097176effecbd6fb46378d4). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
The /ice terrain artifact (iceland_dem.helix.soa.gz, 151 MB, BSO2 ver 7 = F16 pos + Signed360 normal + per-vertex RGB texture; 16,515,072 verts / 32,997,378 tris / 3,584 HHTL tiles) exceeds GitHub's 100 MB blob limit and this session type cannot upload release assets, so it ships version-controlled as a 2-part split zip under .claude/maps/ (iceland_dem_16m.z01 + .zip, both < 100 MB). Wire the loader: - Install zip + unzip in the builder stage. - Replace the old `cp iceland.helix.soa.gz` step (which staged the sparse ~1M-vert scatter) with a reassemble+extract of the split zip: `zip -s 0 iceland_dem_16m.zip --out full.zip && unzip full.zip -d dist/` → produces cockpit/dist/iceland_dem.helix.soa.gz. - include_dir! embeds dist/ into q2-cockpit, so the server serves it same-origin at /iceland_dem.helix.soa.gz; BodyHelix fetchSoa() reads the manifest's iceland_latest and fetches that exact same-origin path first (release URL is only a CORS-blocked fallback it never reaches). Manifest: correct the stale iceland_note (it still described the old Terrarium ~1M-vert bake) to the current textured 16.5M-vert one. The iceland_latest key was already iceland_dem.helix.soa.gz. Reassembly verified locally: the two parts rebuild to a valid gzip (sha256 cb3ef34e…, 710 MB uncompressed, BSO2 magic + ver 7). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
Documents the .claude/maps 2-part-zip → Dockerfile reassemble → same-origin serve chain, what was verified natively (reassembly integrity, sha256, header, BodyHelix fetch path), and the deploy verification steps left for a session with Docker/Railway access. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
…(AZ Topo)
Ground-up rebuild direction: Garmin IMG carries TYPED vector data (building /
water / forest / street / path / contour type codes), multi-level
generalization (TRE levels = LOD pyramid), and per-tile DEM subfiles in the
OpenTopoMap build — replacing raster color heuristics with typed lookups.
- otm-iceland.zip + otm-iceland-contours.zip (garmin.opentopomap.org)
- garmin-grand-canyon/475053{10,11,16,17}.img — the 4 Grand Canyon tiles from
gpsfiledepot Arizona Topo (tile bboxes verified via TRE header recon;
47505316 = Grand Canyon Village)
- SOURCES.md — provenance URLs + extraction recipes + existing artifact index
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
scripts/garmin_proto.py decodes the full chain — container FAT (multi-part block lists), TRE (levels + subdivisions), RGN (points/lines/polys with the exact delta-bitstream semantics incl. the signed-mode continuation marker), LBL (6-bit + 8-bit labels) — validated by rendering the Grand Canyon tile (47505316) at full detail: gorge contour bands, side canyons, village road grid, Bright Angel Trail, elevation-labelled contours all correct. The continuation marker (raw == 1<<(n-1) accumulates 2^(n-1)-1 and reads on; negative close = tmp - 2^n - acc) was the make-or-break detail: without it the finest level decodes as random-walk mush. Semantics confirmed against QMapShack's CGarminPolygon (format reference only; original implementation). claude-notes/plans/2026-07-07-garmin-img-typed-rebuild.md carries the operator spec (typed heuristics -> kurvenlineal -> terrain -> Gouraud, in that order), the banked sources, every hard-earned format fact, and the remaining work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
…51→11 MB) Two coupled deliverables, both operator-derived this session. 1) Mod-rewrite scene routing. Page /garmin/<location> (SPA route; BodyHelix parses the slug), data /api/garmin/:location (axum param route; resolves the slug through the manifest's garmin_scenes registry to an embedded dist file; unknown slugs 404 with the available list). Plus a scene menu in the viewer toolbar (built-ins + every registered garmin scene). Adding a scene = one manifest entry + the bake in dist — no code change. 2) BSO2 ver-8 radix-grid wire — the collapse: location, connectedness, tilt, and the kurvenlineal residue are all deterministic from the address, so the wire stores ONLY height (F16, yscale-normalized) + kind (u8 -> header palette). x is radix-linear (x0 + col*dx); z is a per-row table (Mercator rows are non-uniform in latitude, ~13% across Iceland); the client reconstructs positions (radix), the index (grid loop), normals (one true-scale gradient pass = real-world slope), and colour (palette[kind] x CurveRuler stride-4-over-17 residue, bit-exact 64-bit via BigInt, cached per lattice cell). encode_grid_bso2 in geo, decodeGrid in BodyHelix, `iceland_dem --grid` emission mode. Iceland 16,515,072 verts: 710 MB raw / 151 MB gz (ver-7, 2-part-zip workaround) -> 49.7 MB raw / 11 MB gz single file, committed directly in cockpit/public (Vite copies to dist; include_dir embeds; no Dockerfile step). garmin_scenes.iceland now points at the ver-8 bake — /ice keeps ver-7 for A/B. Verified: /garmin/iceland renders through the headless WebGL harness byte-identically in look to the ver-7 scene (16.5M verts, same kind palette and relief); harness emulates the param route with the same resolution logic. Server resolver has unit tests (pure serde_json functions; malformed-slug rejection incl. path traversal); their cargo run is compiling in the background against the local rusty_v8 archive (deno_core build env) — the client leg is the e2e-verified path in this commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_3fe02f8c-c249-47cb-8e32-ab88ecdd6dd3) |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (5)
📒 Files selected for processing (19)
📝 WalkthroughWalkthroughThis PR adds Garmin IMG map decoding tooling and planning docs, extends BSO2 encoding with a ver-8 radix-grid heightfield format and per-vertex colors, reworks the Iceland DEM baker to classify terrain into typed colors, adds a ChangesGarmin/Terrain pipeline
Estimated code review effort: 4 (Complex) | ~75 minutes Sequence Diagram(s)sequenceDiagram
participant Browser
participant BodyHelix as BodyHelix.tsx
participant Router as cockpit-server Router
participant Handler as garmin_scene_handler
participant Manifest as body.manifest.json
participant Dist as Embedded dist files
Browser->>BodyHelix: navigate to /garmin/<location>
BodyHelix->>BodyHelix: pathScene() -> "garmin:<location>"
BodyHelix->>Router: GET /api/garmin/<location>
Router->>Handler: dispatch garmin_scene_handler
Handler->>Manifest: lookup garmin_scenes[<location>]
alt slug unknown or manifest missing
Handler-->>BodyHelix: 404/500 JSON error
else slug resolved
Handler->>Dist: read <slug>.helix.soa.gz bytes
Dist-->>Handler: gzip bytes
Handler-->>BodyHelix: 200 application/gzip
BodyHelix->>BodyHelix: inflate + decode (ver 7/8)
BodyHelix->>Browser: render terrain mesh
end
Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ef6c28dc98
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| cockpit/public/*.blocks | ||
|
|
||
| # Iceland DEM bake is 151MB (full z9 + imagery) — over GitHub 100MB; ship via Release asset. | ||
| cockpit/public/iceland_dem.helix.soa.gz |
There was a problem hiding this comment.
Ensure /ice's ignored asset is still produced
When this artifact is ignored/deleted, the documented non-Docker embedded build (cd cockpit && npm run build then cargo build --features embed-cockpit) no longer copies the file that cockpit/public/body.manifest.json still names as iceland_latest (iceland_dem.helix.soa.gz) into cockpit/dist; only the Dockerfile reassembles it from .claude/maps. In that build mode, /ice fetches the manifest entry and gets a missing same-origin asset, so either generate this file as part of the normal frontend build or point the manifest at the committed v8 grid.
Useful? React with 👍 / 👎.
Follow-on to the merged #89 (real Iceland DEM heightfield + geo "terrain" layer chrome). These 14 commits build on top of #89's merged base — the merge-base is #89's head (
79f8dce3), so this diff is only the new work, nothing already-merged is re-included.Headline —
/garmin/:locationscene routing + the ver-8 wire (ef6c28dc)/garmin/:locationroute incockpit-server(crates/cockpit-server/src/main.rs): a slug → dist-filename resolver over the bake manifest, with malformed slugs rejected before lookup and unknown slugs returningNone(and enumerable via a slug list).cockpit/public/iceland_dem.v8grid.soa.gz), same-origin, replacing the oversized helix.soa that had to be untracked as a Release asset.Terrain rendering polish — the "/helix look" (
5895770f,38bfef77,8320a6b3,b6b42ad3,867bc5c3,b1729e9d)helix::CurveRulergolden-spiral residue is baked into the terrain colour (the geometry displacement was dropped on the dense DEM — kept as colour only, so the mesh stays clean)./geoneedle field; DEM rebaked under a top clamp withuExagtuned 30 → 15.16.5M-vert textured bake + Docker wire (
f0c74e25,c745bd35,c89552a6,164b70c0).claude/maps(iceland_dem_16m.zip+.z01), and theDockerfilereassembles + bakes it at image build so the deploy serves the dense terrain.iceland_dem.helix.soa.gzis untracked (over GitHub's hard limit) and carried as a Release asset instead; handover note added for the loader-wiring + deploy verification.Banked Garmin IMG sources + decoder prototype + rebuild plan (
d0f6f9b1,860acc00,ad9a45bc).claude/mapswith provenance inSOURCES.md: Iceland (OpenTopoMap) + Grand Canyon (AZ Topo).scripts/garmin_proto.py) plus the typed-rebuild plan (claude-notes/plans/2026-07-07-garmin-img-typed-rebuild.md) — the foundation for the next pipeline (FAT + TRE + RGN + LBL parse → typed heuristics → heightfield → bake).*.blocksHHTL LOD sidecars gitignored (never client-fetched).Verify
cargo test -p cockpit-server --release garmin_scene→ 4/4 pass (known_slug_resolves_to_dist_filename,unknown_slug_is_none_and_slugs_list_them,malformed_slugs_rejected_before_lookup,manifest_without_registry_is_empty).cockpit-servercompiles clean in release with the sibling crates (OGAR / lance-graph) synced to theirmaintips to satisfy the ogar-codebook count fuse./garmin/icelandver-8 scene confirmed end-to-end in the WebGL harness (the dense 16.5M-vert breathing scene is too heavy for the headless swiftshader GPU to re-snapshot; the resolver + compile are the hard gate, noted honestly).Note for the reviewer — large binaries
This PR deliberately banks several large map-source binaries under
.claude/maps(task: "bank Garmin map sources"):iceland_dem_16m.z01(79 MB),iceland_dem_16m.zip(71 MB),otm-iceland.zip(59 MB),otm-iceland-contours.zip(25 MB), plus the 4 Grand Canyon.imgtiles and the 11 MB ver-8 wire. All are under GitHub's 100 MB hard limit; several exceed the 50 MB recommendation (push warned, landed fine). They are the reproducible-from-source inputs for the terrain bakes and the Garmin decoder work — intentional, not stray artifacts.🤖 Generated with Claude Code
https://claude.ai/code/session_012jEwwaT5JZ5x8qWvcnaMYC
Generated by Claude Code
Summary by CodeRabbit
New Features
Bug Fixes