Skip to content

PRD-566: measure terrain LOD blend CPU work - #485

Draft
jonit-dev wants to merge 20 commits into
developfrom
feat/prd566-terrain-lod
Draft

jonit-dev wants to merge 20 commits into
developfrom
feat/prd566-terrain-lod

Conversation

@jonit-dev

@jonit-dev jonit-dev commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR — TerrainTiles changes a tile's level with a three-frame blend (LOD_TRANSITION_FRAMES = 3, packages/core/src/world-tiles.ts:219). Each blend frame runs updateLodTransitionGeometry (world-tiles.ts:1292). It visits every vertex of the finer level. For each vertex it calls tile.field.heightAt and tile.field.normalAt, interpolates the coarser level, and writes position.setY and normal.setXYZ. normalAt reads five heights, so a 65×65 tile makes about 25,000 field reads per blend frame. The same file records that this access pattern cost 46 ms when level building used it (fieldHeightGrid, world-tiles.ts:489). No measurement exists for the blend frames. blendingTiles (world-tiles.ts:2064) counts the tiles that pay the cost.

25% by the PRD's own boxes: 0/3 phases (2/6 boxes), 1/3 acceptance criteria. PRD: docs/PRDs/unreal-source-borrowing/PRD-566-terrain-lod-morphs-on-the-gpu-and-picks-by-screen-error.md.

Phases

Phase 1: Measure the blend frame — 1/2

  • The blend frame's field reads per tile are counted, and its time per 65×65 tile is recorded here. proof: TN_BENCH=1 pnpm exec vitest run packages/core/__tests__/world-tiles-cost.spec.ts — 2026-10-10: exit 0, 20/20 tests pass; 4,225 normalAt and 25,350 heightAt calls per tile. The final separate -t 'times one blending' run (exit 0, 1 passed/19 filtered) measured 2.416 ms mean, 1.692 ms median, 10.069 ms p99 across 100 frames after 20 warm-ups; earlier isolated mean 1.239 ms. No spies in the timed loop; one resident 65×65 tile, validation/merging off, no admissions or transition completion during the timed process() call. This includes the one-tile process bookkeeping; transition-start follow() and completion/restoration are outside the timer. Final full bench also measured 1.575 ms per 289-tile walking follow()+process() and 0.310 ms settled. A bounded repeat during PRD-564’s allowed CPU window measured 1.266 ms mean, 1.171 ms median, 2.613 ms p99 (same 100 samples/20 warm-ups; 1 passed/19 filtered; desktop load 16.67/20.41/24.00). This was lower contention, not an unloaded measurement. Logs: /tmp/prd566-bench-final.log, /tmp/prd566-bench-final-isolated.log, /tmp/prd566-quiescent-bench.log.
  • The terrain walk reports the largest per-frame blendingTiles and whether blend frames sit in the worst 1%. proof: the AC-2 playtest command — Partial: the same scenario against a separately started, warmed server completed 180 ticks (exit 1, 20/26 assertions pass); the added peak assertion passed with 0→4 blending tiles. The probe retained 552 per-world-draw blend counts, including completion frames; transitions 0→33, max LOD pop 5.2491774559021, max transition frames 3. Worst-1% remains unverified: TN_PLAYTEST_SOFTWARE_ADAPTER (swiftshader), TN_CAPTURE_BLANK, console/runtime errors, topology failures and existing seam/stitch triviality failures prevent valid walk proof. Earlier cold runs returned TN_PLAYTEST_BRIDGE_MISSING; scene doctor reached the bridge after 12.3 s but reported Google SwiftShader and a blank frame. Logs: /tmp/prd566-terrain-warm-green.log, /tmp/prd566-warm-green-artifacts/, /tmp/prd566-scene-doctor-2.log.

Phase 2: Morph targets replace the CPU blend — 1/2

  • Stock morph arithmetic, residency accounting, disposal and focused terrain regressions pass. proof: pnpm exec vitest run packages/core/__tests__/world-terrain-tiles.spec.ts packages/core/__tests__/world-tiles-cost.spec.ts packages/core/__tests__/world-terrain-merge.spec.ts packages/core/__tests__/world-terrain-merge-walk.spec.ts packages/core/__tests__/world-terrain-merge-inflight.spec.ts packages/core/__tests__/world-terrain-jobs.spec.ts packages/core/__tests__/world-streaming-recipe.spec.ts packages/core/__tests__/constraints.spec.ts packages/core/__tests__/build.spec.ts — 2026-10-10: 9 files pass, 124 tests pass/4 opt-in timing cases skipped, including exact-fit/one-byte-short memory cases (9×9 and padded 65×65), the 8,192-row fallback case and zero/duplicate effective LODs. Core typecheck passes. After the accounting follow-up, pnpm --filter @threenative/core build and its publint step both pass (exit 0; /tmp/prd566-accounting-build.log). Biome passes (9 complexity warnings across the accounting follow-up's two checked files); pnpm check:docs checks 3,025 links. Latest focused log: /tmp/prd566-residency-green.log. A 65×65 GPU blend frame reads zero field heights/normals; the validating CPU case retains 4,225 normalAt / 25,350 heightAt reads. No new performance timing or GPU/visual run is claimed.
  • The same walk runs on the desktop host with matching transition counts; no mobile claim. proof: node packages/playtest/dist/runner/cli.js examples/abyss-framework/playtests/terrain.playtest.json --target desktop --executable <pkg>

Phase 3: Screen-error selection with the distance floor — 0/2

  • Two viewports choose different levels for the same tile, and neither switches nearer than the floor. proof: pnpm exec vitest run packages/core/__tests__/world-terrain-tiles.spec.ts
  • The guide documents the floor and the shared pixel budget. proof: pnpm check:docs

Acceptance criteria

  • AC-1 [local]: A morph frame writes no position and no normal attribute on the CPU, and the tile's rendered surface at morph progress p equals the CPU blend at p within 1 mm. proof: red-green pnpm exec vitest run packages/core/__tests__/world-terrain-tiles.spec.ts — 2026-10-10: red on the unchanged CPU path (morphTargetsRelative false); green unit parity uses stock Mesh.getVertexPosition and normalized normal deltas against the unchanged validate: true CPU reference for isolated, two-tile and nine-tile residency. It covers skipped levels, refinement, interruption, shared-edge activation/removal, skirts, stable base-attribute versions/identity and the unchanged three-frame duration. This is stock morph arithmetic proof; actual browser/native GPU rendering and visual judgement remain open under AC-2/Phase 2.
  • AC-2 [local]: The abyss-framework terrain walk keeps lodTransitions > 0, maxLodPop ≤ 16 and maxLodTransitionFrames unchanged, with 0 console errors on a named WebGPU adapter. proof: node packages/playtest/dist/runner/cli.js examples/abyss-framework/playtests/terrain.playtest.json --url 'http://127.0.0.1:5183/?terrain' --server-command 'pnpm --filter abyss-framework dev --host 127.0.0.1 --port 5183 --strictPort' --browser-recipe webgpu — Evidence: pending.
  • AC-3 [local]: Under PRD-461's recipe, every screen-error switch distance at 1280×720 and at 2560×1440 is at least fog near (180 m) and at least the lodDistances floor. proof: pnpm exec vitest run packages/core/__tests__/world-streaming-recipe.spec.ts — Evidence: pending.

Still open

  • Phase 1: Measure the blend frame: The terrain walk reports the largest per-frame blendingTiles and whether blend frames sit in the worst 1%. proof: the AC-2 playtest command — Partial: the same scenario against a separately started, warmed server completed 180 ticks (exit 1, 20/26 assertions pass); the added peak assertion passed with 0→4 blending tiles. The probe retained 552 per-world-draw blend counts, including completion frames; transitions 0→33, max LOD pop 5.2491774559021, max transition frames 3. Worst-1% remains unverified: TN_PLAYTEST_SOFTWARE_ADAPTER (swiftshader), TN_CAPTURE_BLANK, console/runtime errors, topology failures and existing seam/stitch triviality failures prevent valid walk proof. Earlier cold runs returned TN_PLAYTEST_BRIDGE_MISSING; scene doctor reached the bridge after 12.3 s but reported Google SwiftShader and a blank frame. Logs: /tmp/prd566-terrain-warm-green.log, /tmp/prd566-warm-green-artifacts/, /tmp/prd566-scene-doctor-2.log.
  • Phase 2: Morph targets replace the CPU blend: The same walk runs on the desktop host with matching transition counts; no mobile claim. proof: node packages/playtest/dist/runner/cli.js examples/abyss-framework/playtests/terrain.playtest.json --target desktop --executable <pkg>
  • Phase 3: Screen-error selection with the distance floor: Two viewports choose different levels for the same tile, and neither switches nearer than the floor. proof: pnpm exec vitest run packages/core/__tests__/world-terrain-tiles.spec.ts
  • Phase 3: Screen-error selection with the distance floor: The guide documents the floor and the shared pixel budget. proof: pnpm check:docs
  • Acceptance Criteria: AC-2 [local]: The abyss-framework terrain walk keeps lodTransitions > 0, maxLodPop ≤ 16 and maxLodTransitionFrames unchanged, with 0 console errors on a named WebGPU adapter. proof: node packages/playtest/dist/runner/cli.js examples/abyss-framework/playtests/terrain.playtest.json --url 'http://127.0.0.1:5183/?terrain' --server-command 'pnpm --filter abyss-framework dev --host 127.0.0.1 --port 5183 --strictPort' --browser-recipe webgpu — Evidence: pending.
  • Acceptance Criteria: AC-3 [local]: Under PRD-461's recipe, every screen-error switch distance at 1280×720 and at 2560×1440 is at least fog near (180 m) and at least the lodDistances floor. proof: pnpm exec vitest run packages/core/__tests__/world-streaming-recipe.spec.ts — Evidence: pending.

Generated from the PRD's checkboxes. Regenerate on each push so the PR and the PRD never disagree.

@jonit-dev jonit-dev added the prd:25% PRD phase 1 verified label Oct 10, 2026
@jonit-dev

Copy link
Copy Markdown
Contributor Author

Functional check at local candidate d2848bbca (fresh built example): doctor exits 0, actual WebGPU adapter NVIDIA/Turing, nonblank 1280×720. The unchanged headed terrain scenario exits 1: 19/31 assertions pass; 12 fail. No retry or source/settings/assertion changes.

Observed movement reaches x=373.153 m; both endpoint resident sets contain nine matching tiles. Main residency/peak is 17,133,840 bytes. Parity advances 12→47 frames / 125,580→412,620 vertices with zero position/normal error, but active morph frames and LOD transitions remain zero, so this does not prove active GPU morph behavior. Failures include the three topology metrics, required blend/stitch/pop observations, and triviality rejection of initially satisfied seam/parity assertions. The complete pipeline census records four programs / 40 pipeline creations; no pipeline gate is claimed.

Exact commands, source/bundle/scenario hashes, adapter/GPU metadata, report and snapshots are preserved locally under artifacts/prd566-functional-d2848bbca/. The owned preview/browser/display are stopped. The single candidate screenshot is UNJUDGED; it is not a historical before/after comparison. Runtime acceptance, native, visual and performance gates remain open. PRD dated outcome updated locally for review; no commit/push.

@jonit-dev

Copy link
Copy Markdown
Contributor Author

Local candidate 45775a1b5 (results dbdfb0696; not pushed yet) completed one unchanged, headed NVIDIA/Turing WebGPU terrain walk at 1280×720. Normal core/playtest/example builds and both scene doctors passed.

Observation Earlier d2848bbca Local candidate
Same terrain main program: pipeline creations 37 1
Scenario checks passed 19/31 21/31

The complete new census has four creations, four programs and four pipeline identities, with no failed, pending, dropped or overflowed events. This is an unblended functional result, not an active-morph, native or performance qualification; the desktop GPU was loaded.

Direct baseline and endpoint snapshots now record all nine resident tiles at current/target LOD0. Next-coarser errors are 1.388–8.374 px initially and 1.573–9.502 px finally, above the unchanged 1 px budget and 0.85 px coarsening boundary. Zero counters alone are no longer the basis for that observation. Ten original checks remain failed; scene, camera, defaults, scenario constraints and thresholds were not changed.

Evidence is retained in the task checkout under artifacts/prd566-functional-lod-debug/ (receipt, full report, complete census, LOD/camera snapshots, commands and source/bundle hashes). The single AFTER capture remains unjudged and is not presented as a paired comparison. Fresh paired visual proof is still pending; the requested Claude Code visual-judge skill was not found. Progress remains 50%.

Current GitHub state: draft, 32 skipped checks; no real CI pass is claimed. The completed PRD and PR will stay open for João’s inspection.

Probe renders blending tiles to a float world-position target via fragmentNode and compares with the CPU reference. Expose only the newest 64 LOD-trace samples so the bridge payload stays under 1 MB.
@jonit-dev
jonit-dev force-pushed the feat/prd566-terrain-lod branch from 092cdfe to fbb5a31 Compare October 11, 2026 21:13
@jonit-dev jonit-dev added prd:50% PRD half the phases verified and removed prd:25% PRD phase 1 verified labels Oct 11, 2026

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

prd:50% PRD half the phases verified

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant