Skip to content

ground-plan load: no repeated stamp and record work, one near-first order, no level-18 page before its mesh - #3204

Draft
timohueser wants to merge 6 commits into
ground-planfrom
claude/gp-load-order
Draft

timohueser wants to merge 6 commits into
ground-planfrom
claude/gp-load-order

Conversation

@timohueser

Copy link
Copy Markdown
Owner

Steps 1a (#3192) and 2 (#3195) of the ground plan's load plan (epic #3096). The client repeats work while a view loads, and its queues serve the far content before the near content. This change removes the repeated work that has a byte-safe cut, puts stamps and records into one near-first order, and holds a level-18 page until its cell's mesh is ready.

What changes

  • Stamp worker (terrain/bins.ts, stamp-worker.ts): the junctions, runs and kerbs of a tile's lines are made once per decoded tile, not once per node. On the plan path, a node makes only what the plan path reads: the runs, the kerbs and the partners are not made, and a way's segment leaves before its record words. The worker keeps its last four tiles above zoom 13 past the byte budget, so a zoom-8 tile's shapes are made once while its nodes are stamped. No stamp worker is added.
  • Crate and bridge (cell.rs, records::near, transport.rs, ground-plan-wasm): a cell builds without the far records, which nothing read. build_cell_with takes a cache of parsed tiles; the bridge's build keeps six per instance, so each zoom-13 tile parses once per plan worker. build_bytes (check 7, timing page) stays uncached. Heights and the records encoding do not change.
  • One near-first order (stamps.ts, model.ts, terrain.ts): each frame states its stamp wishes; the worker takes them by rank: the pages of the eye field and the nodes that the meshes in view read first, then every wish by its distance from the eye. A mesh cell's stamp nodes are wished for before its mesh comes. The records build the target cells and the mesh cells with their eight neighbours in one list, nearest first, each neighbour as near as its mesh cell.
  • Mesh-ready guard (terrain.ts): a level-18 page of a cell whose mesh comes does not draw until the mesh holds its heights. The coarser page draws until then. A cell with no mesh to come (empty, failed, over, records failed) holds nothing.

Commits: #3192 is ee503c4ee and 8a0ffbd8c; #3195 is 396c2517f, 83555f90a, 30051e582 and e3ac8d8e3.

Not done: the margin cut (#3195, last item)

A probe over the 62 cells of the v17 view counted the overlay booleans. The booleans in the cell's frame (anchor 0) take 677 ms and never repeat across cells: the frame is the cell's own by construction. The booleans about a point anchor take 157 ms, and 41 ms of them repeat. So a stroke cache keyed by tile saves less than 1 % unless the booleans move to a shared frame, and that changes bytes. A 3 × 3 batch build has the same problem. This change keeps the border rule and the bytes, so it does not cut the margin. A shared boolean frame needs its own issue with a byte-change plan.

Gates (against c3f5693)

Gate Result
Records byte-equal Native, the 62 cells of v17 (load-arch/requests-v17): records, faces, rims and 29 meshes equal, with and without a shared tile cache. WASM: the cached build equals build_bytes for the 62 cells.
Stamp bytes nodeStamps, plan and not: 0 of 2,232 nodes differ (tiles z8–z13 of v17, v2, m1).
check-bytes 256 of 256 cells give the native digest
GPU = Rust probe 3 places, 49,741 points: max 0.119 mm (tolerance 2 mm), kinds differ 0. Probe inputs equal to the base.
Tests crate 101/101; clippy crate, bridge, bridge wasm32 clean; vitest trailview 408/408; npm run check 0 errors 0 warnings; check:wgsl 69 ok
Final frames the 20 owner views of the brief (fixture, --pages 1): 0 pixels differ in all 20
Frame time (GP0's method, 3 alternating runs, ground=plan) GPU per frame equal (walk 4.13 → 4.10, drive 3.97 → 3.92, zoom-out 2.05 → 2.02 ms). CPU median: walk 3.7–3.9 → 3.9–4.0 ms (+0.1 to +0.3, at the clock's 0.1 ms step), drive 3.3 = 3.3, zoom-out 1.3 → 1.2.
No torn frame contact sheets of v2 and v17 during the load: the base shows the far look up close for 7–21 s (v17) and 9–29 s (v2); this branch shows none.

Time to a clean frame (diff stays under 1 % of the final frame; shots every 0.5 s; live data of :8710; dev servers with the real page settings; three runs each, median, runs alternate base / new / develop; runs 1–2 on 30051e5, run 3 on the head). The PC ran other agents' work the whole time (load 3 to 16, once 49):

View base cold / warm this branch cold / warm develop cold / warm
v17 (Engelberg town, eye 1.7 m) 21.7 / 21.3 s 16.7 / 11.1 s 15.7 / 11.3 s
m1 (Trübsee, eye 1.7 m) 16.4 / 12.6 s 7.2 / 6.3 s 11.0 / 10.0 s
v2 (lake road, alt 471.5, 2 runs) 30.5, 25.3 s 10.5, 6.7 s –

CPU per load (one Chrome trace per side, cold): v17 total busy 66.7 → 41.4 s (develop 47.4), stamp worker 20.5 → 12.0 s (develop 14.7), plan workers 3.7 + 3.3 → 2.6 + 2.5 s. m1 is noisy (two runs per side): stamp worker 9.2, 12.7 → 12.9, 10.9 s (develop 10.6). Native record build of the 62 v17 cells: 3.9 → 2.6 s (median of 3, −32 %).

Not met or not changed:

  • At v2 the first picture is still the coarse terrain with a house at the eye for about one shot (1–2 s) before the lake. Develop shows the same state (LOADING's live-v2-develop-warm, 2.6–5.4 s): it is the coarse first picture, not the plan's order.
  • The walk's CPU median may be about 0.1–0.2 ms higher; I did not find a cause in the per-frame code that costs that much.

Public docs: not changed.

Requirements: none

🤖 Generated with Claude Code

timohueser and others added 6 commits November 7, 2026 13:16
…tiles in the stamp worker

The junctions, runs and kerbs of a tile's lines are made once per decoded tile, not once per node.
The stamp worker keeps its last four tiles above zoom 13 past the byte budget, so the shapes of a
zoom 8 tile are made once while its nodes are stamped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stamps read

With the plan the records hold the ways and the junctions: a node's stamps no longer make the
runs, the kerbs or the partners of its tile, and a way's segment leaves before its record words.
The bytes of every node stay the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A viewer's cell reads only the faces and the near records; the far records took about a sixth of
a build. `records::near` gives what `records::build` gives without them, and a test holds the two
equal. Native, the 62 cells of the v17 view give the same records, faces, rims and 29 meshes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`cell::build_cell_with` builds a cell with a cache of parsed tiles that the caller keeps; the
bridge's `build` keeps six per instance, so the cells about the eye share their parses.
`build_bytes` stays uncached for check 7 and the timing page. In WASM the 62 cells of the v17 view
give the same records and rims through both paths; the heap grows from 24.5 to 29.8 MiB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…18 page before its mesh

The stamp worker takes each frame's wishes by rank, not the first ones: the pages of the eye field
and the nodes that the meshes in view read come first, then every wish by its distance from the
eye. A mesh cell's stamp nodes are wished for before its mesh comes. The records build the mesh
cells and their neighbours in one near-first list with the target, each neighbour as near as its
mesh cell, so a mesh no longer waits behind farther target cells.

A level-18 page of a cell whose mesh comes waits until the mesh holds its heights: the coarser
page draws until then, never the far look up close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The coarse pages over the eye draw until the eye's own pages do, so they go with the eye field and
the nodes of the meshes in view: every wish within EYE_M, but for the nodes of a mesh out of view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Share

This branch has not been deployed

No deployments
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.

1 participant