Repository navigation
feat(core): PRD-624 release the native engine as the default (PRD-573 v1) - #493
Merged
Merged
Conversation
jonit-dev
added a commit
that referenced
this pull request
Oct 12, 2026
Owner order 2026-10-12: one PRD and one PR (#493) for the whole epic. docs/PRDs/performance/PRD-573-unreal-class-performance-in-release-cycles.md replaces the epic folder (README, ROADMAP, PRD-573..578). Phases are the release cycles v1..v5, each with its KPI and stability gate as boxes; v1 phases are the slices from the slice plan (0 charter, ticked; 0b CI scope; 1 core fixes; 2a..8) plus the scorecard. No inbound links existed.
This was referenced Oct 12, 2026
jonit-dev
marked this pull request as draft
October 12, 2026 04:32
Contributor
Author
Contributor
Author
PRD-573 v1.11 step 3: host projection (visual judge: PASS)The native renderer batches the source scene itself; core's JS mirror is skipped. R5@4096 projection phase 8.0 → 0.57 ms; thread CPU without present legacy 12.87 ms vs native 7.65 ms (rounds 1.63×, 2.26×, 1.90×, host loaded).
Picture identical BEFORE vs AFTER (judge PASS, 2 runs). Open before any KPI claim: a fairness audit of the 21k-triangle gap vs legacy. |
Both branches fixed some of the same native-engine bugs. Each conflict keeps the more complete fix and both sides' tests. - browser-tsl.ts: Fn binds its inputs at call time (positional, array, node or one named object, runtime-fixes) and its result chains setLayout (post-bindings). Every Fn now builds per call, also when the callback takes no parameters. Keeps rtt, quad mesh, storage, struct and wgslFn from post-bindings and the shared defineTslExtras and the normalView mark from runtime-fixes. The mark builds normalView only for vec4(x, 0), so the call order that starter-post.spec.ts checks does not change. The runtime-fixes spec mock gains compute, readStorage, deleteStorage. - core-tsl.mjs: post-bindings imports and storage, struct, rtt and wgslFn bindings, with saturate, densityFogFactor, fog, context and velocity from defineTslExtras. - bundle-native-engine.mjs: the upstream check and installed package roots were identical on both sides. Adds the runtime-fixes packageImports plugin for player facade sibling imports. - build.ts: keeps the post-bindings decoder-free web cook (no KTX2, no Meshopt, own runtime identity). It is the superset of the runtime-fixes Meshopt-only cook. - web-engine.ts, post-effects-web.ts: the display addons are the union. TRAA, Film and Lut3D are real (post-bindings). FXAA, Godrays, SSGI, SSR and Sharpen refuse on use (runtime-fixes). - catalog.json: both sides added a property to the same class, so both are kept. three.d.ts and tn_abi.h come from `pnpm generate`. - starter-post.spec.ts: types the Fn callback parameter as an array (type only). Proof: vitest 46 files, 590 tests pass; tsc passes for three-native, core, create-threenative and the root except one error that already exists on post-bindings; ctest -L native-engine 404 of 404.
…e's draw tally three draws the normal target in the one scene pass it counts once. The engine draws it in a separate pass whose draws and triangles landed in the shadow tally, so the web platformer reported 873 draws and 279,870 triangles against three's 431 and 177,973, and failed its production-performance budget. The extra passes now tally nowhere: 442 draws and 177,940 triangles, and the scenario passes. The render-database test fails without the change.
Picks up 0f643c6 (puzzle runs on the native engine) and 38770ef (TSL operators for SoftBody3D compute). The merge was clean. The runtime-fixes tip (4612e3d and the commits before it) was already in the first merge: build.ts keeps the post-bindings decoder-free web cook, which covers the 4612e3d Meshopt fix, and both tests stay. Proof: vitest on three-native, create-threenative, runtime-native specs: 41 files, 332 tests pass; three-native typecheck passes; ctest -L native-engine 405 of 405.
three returns the vector, so `mesh.worldToLocal(p).multiplyScalar(2)` chains. The binding answered the object, which stopped the racing template on the web native engine.
colorWrite = false draws depth only, with every colour channel masked (the tower-defense tap pads use it). The material constructor refused the key on the web native engine. The flag reaches the pipeline target, the draw's pipeline key, the uniform-group signature and the glTF material copy.
…e web facade The engine answers updateRanges as JSON, so core's `derived.updateRanges.push(...)` (the world impostor surface, the rts template) threw "push is not a function". The facade now wraps the getter in the same write-back array the V8 adapter installs.
…d in upstream three The V8 adapter built a callback without parameters once at definition and returned the same graph for every call. Wasm builds per call (and upstream three builds the body when the node is built), so one Fn gave different graphs per back end. v8_adapter_test tsl_api asserts the deferral: red 0001111111111101 before, green after. ctest -L native-engine 405 of 405.
…m packed tarballs The packed runtime depends on @threenative/core, assets and three-native, which are unpublished, so four distribution tests failed with a registry 404 (also on prd617/post-bindings). The consumer manifests now carry pnpm overrides to file: tarballs packed from the workspace. 65 of 65 pass.
Core's picking calls object.raycast(raycaster, hits) for every object it has no tree for (a Group, a skinned mesh), and the engine bound no such method, so a tap on the tower-defense board threw "e.raycast is not a function". The V8 adapter and the web facade now define it over Raycaster.intersectObject(object, false): a Mesh pushes its own hits, a Group none.
…SkeletonUtils path (PRD-617)
Layer: engine. The shooter template stopped on three bindings the native
engine lacked.
- three/addons/geometries/DecalGeometry.js is three's own clipping, ported to
a facade (packages/three-native/src/addons/decal.ts) over the engine's
classes, for the V8 player and the Wasm web engine. A vitest spec compares
it with upstream three over indexed, non-indexed and normal-less meshes.
- BufferGeometry.setFromPoints is bound in C++ (Vector3 and Vector2 points,
new or existing position attribute). player-imports compares it with
three: red before the binding ("setFromPoints is not a function"), green
after.
- three/examples/jsm/... resolves as three/addons/... in the native bundler
and the web engine, so SkeletonUtils.clone no longer reads as unbound.
- Type fix in starter-post.spec.ts so the three-native typecheck is clean.
…oad beside it (PRD-617) Layer: engine. The cook writes a model's textures once under shared/images/ and names them in the GLB by relative uri. The native package carried the model but not those files, so the player refused the shooter's player-viewmodel.glb with TN_NATIVE_GLTF_IMAGE_UNSUPPORTED (undecoded images). - packages/assets: nativePackageEntries adds each shared image a model declares as a raw Buffer entry named by its output path. A spec packs a fixture model and finds the image entry (red with the loop disabled). - gltf::LoadOptions gains readUri, a host callback for a relative uri. The V8 player answers it from the package entries beside the model, so the loader decodes the bytes. gltf_hierarchy_test covers the callback (with a percent-escaped uri) and the no-host case.
… error The renderer names a transparent, sprite or alpha-tested draw once a frame because the post pass reads a depth-derived normal there. The web host printed every renderer note to stderr, which the browser shows as a console error, so every rts playtest failed its no-console-errors policy. Faults still go to stderr.
Picks up colorWrite, updateRanges as a live array, the vector-returning localToWorld and worldToLocal, and the draw tally fix for the normal and velocity passes. - render_database_test.cpp: kept both test lists (multi_material_groups from post-bindings, color_write_masks_the_draw and normal_pass_is_not_tallied from runtime-fixes). - catalog.json: both sides added a property to the same class (fragmentNode, colorWrite), so both stay. sync-native-status then added colorWrite for a sibling class the registry binds. native-registry.json is regenerated from tn-native-engine-registry-dump (two colorWrite entries), which fixes native_engine_registry_snapshot. three.d.ts and tn_abi.h come from `pnpm generate`. - update-ranges.ts (runtime-fixes): the mutator call no longer casts IRange to a function; it fails tsc (TS2352). Same behavior through Reflect.apply. Proof: vitest 43 files, 514 tests pass; tsc clean at the root; ctest -L native-engine 408 of 408.
# Conflicts: # packages/three-native/__tests__/starter-post.spec.ts
…rges setFromPoints is bound on the geometries (post-bindings 43ff539) and the registry dump lists it. native_engine_registry_snapshot was the one failure of 409 in ctest -L native-engine; it passes now, and the other 408 passed in the same run.
This reverts commit 09b1def.
…rames and InstancedMesh bounds (PRD-617) Layer: engine. The shooter's boot reached three calls the native engine answered wrongly or not at all: - Object3D.localToWorld and worldToLocal returned the object, where three returns the vector it was handed. `group.localToWorld(v).project(camera)` threw "project is not a function". - CatmullRomCurve3.computeFrenetFrames(segments, closed) is bound to the C++ computeFrenetFrames that already served TubeGeometry. - InstancedMesh.computeBoundingSphere and the boundingSphere member are bound to the C++ sphere that already existed. player-imports compares all three with upstream three over the same inputs; each was red (not a function, or the object returned) before its binding.
Review finding (ds hunt #3): prepareEnvironmentAsync looped until the bake finished, so an environment that never finishes kept drawing behind the cover after warm-up gave up. It now takes the warm-up deadline and returns false there; warm-up reports the incomplete preparation as before. Facade and warmup specs fail on the previous commit (the facade test hangs to the 60 s vitest timeout) and pass now, 59/59.
Retired TextureIds went into one global list that the first Renderer to sweep consumed. A sibling or the probe capture renderer that cached the same texture kept an in-flight PMREM preparation for a disposed source and never released its GPU copy. A subscription queue now delivers every retirement to every Renderer, and a sweep drops its map groups lazily, only when it is about to release a texture it did cache. captureFace rendered once and read the probe target even when render() returned 0 because the capture renderer's scene environment was still baking, publishing the previous target as a valid face. It now pumps the frame-pumped PMREM until the face is actually drawn, bounded at 60 s. Tests: native_engine_renderer_shared_retired_sweep and native_engine_probe_capture_environment. Both were red before the fix.
Owner decision (PRD-624 box 2): split budgets. On 3-CPU SwiftShader the full-quality PMREM bake alone takes about 20 s, so with one clock warm-up timed out with nothing compiled. The bake now has its own budget (environmentBudgetMs, default STARTUP_HOLD_BUDGET_MS = 45 s, the existing startup bound for launch work); the unchanged 15 s compile budget starts once lighting is ready. A bake that outlasts its own budget still abandons warm-up without compiling. New spec fails on the previous commit and passes now; the two old-contract specs keep their assertions on the environment budget; warmup.spec 48/48, pnpm typecheck exit 0.
Review of 1699bf5: captureFace pumped completions with a 60 s sleep loop. On the web the bake's GPU completions arrive only after the call returns, so that loop could only time out there. captureFace now returns GpuStatus::Pending without submitting while the capture renderer's environment bakes, and ProbeBaker::process maps it to its existing CaptureResult::Pending so the next step captures again. probe_capture_environment now retries through Pending and asserts it saw Pending. 113/113 native_engine_(render_|renderer_|traa_|gpu_|pmrem|probe), serial.
… box 2 measured open
…ransitive fixes 0.2.2 is published from jonit-dev/threenative-sculpt-mcp fix/sdk-1.31 (2a3732c): @modelcontextprotocol/sdk 1.31.0 with proxy-addr 2.0.8 and fast-uri 3.1.8, 62/62 tests. The sculpt tool surface is recaptured from the published 0.2.2 tarball (12 tools). MCP, scaffold and budgets contracts: 127 passed.
386e7fb raised the core dependency to 0.2.2 but left MCP_PACKAGES at 0.2.1, so the doctor spec "pins every MCP server at the version @threenative/core actually installs" failed (0.2.1 vs 0.2.2). doctor, create-threenative and budgets specs: 1678 passed.
…roven (5/7, prd:50%)
jonit-dev
marked this pull request as ready for review
October 18, 2026 23:16
A RenderTarget draws through a sibling Renderer created on its first render. The sibling owned a private PipelineCache and built its own WGPUPipelineLayout handles, so every render target re-created every material pipeline and made the driver JIT it again. A render-target readback waits on that GPU work, so PRD-624's host-projection case spent seconds per target on the software adapter until its 120 s watchdog fired. Sibling and probe-capture renderers now share the device's pipeline cache and an interned layout cache, as three's one renderer shares its program cache. Layouts are interned by program key, so equal programs in different renderers reach the same pipeline-cache key. Proof: renderer_caches sibling_pipelines fails before and passes after; on lavapipe with the shader cache disabled the same six-target readback loop went from ~315 ms steady to ~38 ms and the mixed check's pipeline creations from 104 to 17; host_projection on the RTX lane 2.73 s; the whole native_engine_(player|render_|renderer_) serial board 114/114. The full case on a loaded local cold-cache lavapipe run still exceeded 120 s, so hosted-CI green is unverified.
…pipeline-cache fix
A player with a window is paced by the swap/present; the headless path had no pacing and called renderFrame() on every loop iteration. On the CI image's software Vulkan (lavapipe, 3 CPUs) an empty 1280x720 frame still costs about 2.4 ms of GPU work while the loop submits around 860 frames a second, so the queue grew without bound. A render-target readback is ordered behind every frame submitted before it: the host-projection case's host frame 1 waited 47.4 s for a 14-draw frame, and the mixed check hit its 120 s watchdog. Draw once per simulation tick when there is no presenter, and keep polling and draining the event queue on the iterations that skip the draw so promises still settle. Windowed presentation pacing is unchanged. Proof on the CI-image lane (docker --cpus=3 --memory=9g, MESA_SHADER_CACHE_DISABLE=true, lavapipe ICD, 3 CPUs): the mixed check's host f0 render/readback 4264/2778 ms -> 73/1349 ms, mirror f0 11027/6049 -> 26/789, host f1 5753/47436 -> 2/42, mirror f1 5215 + watchdog -> 2/40; the whole host-projection test goes from the 120 s timeout to 4.9 s cold. RTX: native_engine_player_host_projection 2.78 s, and ctest -L native-engine serially 435/436 with only the known local-config native_engine_target_graph failure.
The relocated starter on a cold Metal shader cache spent 20 s inside TN_STARTUP_WARMUP with attempted:0. When the renderer exposes a covered output pass, warmUpScene called that render instead of compileAsync, so every pipeline it touched was created synchronously on the game thread; the 15 s web UI ready wait runs on that same thread and could not deliver tn:ready. Run the renderer's compileAsync first, so native PipelineCache::getAsync and Three's createRenderPipelineAsync build the world pipelines while frames pump. Then lend the covered output render Three's pending/skip pipeline path: its getForRender receives a promise list, creates asynchronously, and draws whose pipeline is not ready are skipped. The warm-up awaits that list before the first visible frame, so no synchronous pipeline creation remains there. Red-green: the new warm-up test creates 3/3 pipelines synchronously before the fix and 0/3 after (3 async). Core vitest 3739 passed, warmup+renderer 102 passed; native ctest 434/436 with native_engine_target_graph (known local red) and a flaky player_async_bridge that then passed 3/3 consecutive reruns.
The async covered pass skipped the render entirely once the compile budget was spent, leaving its post/reflection pipelines to a synchronous first frame. Issue it as before and skip only the wait.
jonit-dev
added a commit
that referenced
this pull request
Oct 19, 2026
* ci: fix three flakes seen on the 0.3.5 release runs - terrain prop-streaming "drains canopy admission": the admission budget is wall time, so a loaded runner left a backlog after 128 frames (main CI 39197879234: backlog 4). The spec now drives performance.now with a 0.01 ms-per-read clock. Red check: a 0.5 ms step reproduces it (backlog 32). - procedural-animal-canvas: all 4 layouts passed, then afterAll browser.close() hit the 10 s hook timeout on a loaded runner (main CI 39205153764). Launch and close get a 60 s process-lifetime timeout. The assertions are unchanged. - native-platforms Android emulator: the emulator dropped to "offline" after conformance passed 79/0, and `adb logcat -d` exited 255 (PR CI 39184795139). The script now runs `adb reconnect offline` and waits for the device for up to 60 s before the dump. A device that stays gone still records TN_ADB_LOGCAT_FAILED. * ci: give the shooter nonvisual leg a 90-minute cap The native web engine default (#493) made shooter's enemy scenarios take 3 to 7 minutes each on hosted SwiftShader, against about 35 s before. Every #493 run and main run 39205153764 timed out at 30 minutes with all completed scenarios passing. Shooter joins rain on a larger cap; every scenario and assertion stays. * test(terrain): write the deterministic clock as a block for biome
jonit-dev
added a commit
that referenced
this pull request
Oct 28, 2026
v1 makes the native engine the default, but 7 of 13 templates (starter among them) pin engine: "legacy" because the V8 engine bundler refuses their post and TSL imports (film, lut3D, traa, luminance, rtt, wgslFn, DepthTexture, ...). Two phases: the starter first, then snow, rain, shooter, action-rpg, runner and sailing. Extends the existing definePostEffects live-effect path; the PR #493 pin ratchet test is the inventory and the done signal.
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.







TL;DR
This PR is the release vehicle for the new native engine. The owned C++ engine (WASM in the browser, C++ on desktop and Android) is the default.
engine: "threejs"stays as the named override.It is mergeable with
develop, but not release-ready. PRD-624 has 5 of 7 boxes ticked, and the current full CI board is queued (0 red so far).Merge, publication, tags and registry are João's decision. The PR stays draft.
Main goals
developwithout losing CI or engine fixes.What ships
Engine
engine: "threenative"is the default.Runtime and rendering repairs (already in the PR)
Planning
docs/PRDs/unreal-class-performance/folder is gone.Status and CI
develop5cbeadeab; head is nowa069e1450. PR is MERGEABLE.prd:50%), per local commit3b3490d50(push pending). A box needs green proof on the PR head.a069e1450testandtest-unit(4/4). Fixes are in progress. A new full board follows the next push.2c261246b,e5ec2c179)PRD-624 checklist
Last updated: 2026-10-18 21:09 UTC
Mirrored from
docs/PRDs/production-readiness/critical/PRD-624-release-the-new-native-engine.md. Boxes follow the PRD file. A progress line is a status note, not a tick.Phase 1: Finish startup readiness
18eb7c539, bounded infe493a075). A red-green pass found 2 native bugs, fixed in1699bf5a1(texture retirement reaches every renderer, and a probe capture no longer publishes a black face during a bake) and2c83ba331(capture reports Pending instead of blocking). Native renderer ctest: 113/113. Native conformance: 6/6 PMREM cases pass (max 0.00342 against 0.01). Strict SwiftShader parity: Runner 0.039, action-rpg 0.001 against 0.12, with no device loss. Visual judge: IMPROVEMENT. Ticked in the PRD file ona069e1450.870dfd578). The PMREM bake gets its own 45 s budget (STARTUP_HOLD_BUDGET_MS). The unchanged 15 s compile budget starts after the bake.warmup.spec: 48/48, and the new spec is red on the old code. Open, needs an owner decision: NVIDIA completes. On 3-CPU SwiftShader the full-quality PMREM is about 8 fence groups of about 10 s each, roughly 80 s of real compute. Warmup reports timedOut at 45 s (Runner 45003 ms, action-rpg 45035 ms). No bound inside the 180 s launch contract (at least 3x) fits. Lighting is still correct at capture.Phase 2: Freeze a clean, installable candidate
origin/develop.5cbeadeab. The PR is MERGEABLE. This box is ticked in the PRD file.threenative-sculpt-mcp0.2.2 is published and the core floor is 0.2.2. Fresh consumers resolve@modelcontextprotocol/sdk1.31.0,proxy-addr2.0.8 andfast-uri3.1.8 (checked at386e7fbcf). Ticked in local commit3b3490d50, push pending.386e7fbcf). Still missing: theruntime-native-v0.3.5prebuilt release (native release owner).386e7fbcf).Phase 3: Qualify the exact release source
a069e1450has 2 reds (test,test-unit4/4), which are being fixed. A new full board follows the next push.Owner actions: decide the Three.js warmup threshold on SwiftShader (box 2, see its progress line), publish the
runtime-native-v0.3.5prebuilt release, and decide the merge and publication.Risks and follow-ups
engine: "threenative"becomes the default.engine: "threejs"selects the legacy renderer.How to verify
pnpm typecheck pnpm lint pnpm test pnpm release:prepare gh pr view 493 --json mergeable,headRefOidscripts/engine-parity.ts, then the affected native conformance case (pnpm parity).playtests/performance.playtest.jsonandplaytests/live/render-motion.playtest.jsonin the installed game, with the shipped runner and--browser-recipe webgpu.ci-required.Not run: any full-board qualification on the final head. The fixes in flight are not pushed.
References
PRD-624 (release) · PRD-573 (epic,
docs/PRDs/performance/PRD-573-unreal-class-performance-in-release-cycles.md) · supersedes #438 and #494.History: earlier status notes and visual evidence (superseded where they conflict with the sections above)
One PRD and one PR for the whole Unreal-class performance epic (owner order, 2026-10-12).
Current candidate status — a62f8c3, release qualification pending
Develop
e16a59274362a7c3f0226755950ea05f71732693is integrated. Completed full qualification with Chromium diagnostic logs is 39126674780, dispatched on exact published head5df46d637f81842b1641279118df23460df3a9f1. That run predates the latest candidate and finished with 62 successful checks, seven failed template parity captures, and the downstreamci-requiredfailure. No other product check failed. Candidatea62f8c324818c6988cd8daf1f517a2aa17d363beadds failure-only, bounded GPU-watchdog histogram capture before browser teardown. Parent typecheck, full lint, 279 focused/document/CI tests, 22 final document checks, and normal pre-push drift checks passed; a fresh read-only review passed. Hosted histogram availability remains unverified. These logging changes are diagnostic and do not claim to fix the GPU failures. The exact integrated develop base failed macOS and downstream receipts; the added engine-parity step did not exist on that base. Passing base scenarios therefore do not prove base parity passes. The PR remains draft under the owner's merge/publication hold.2761ef87c. ActionRPG, Racing, RTS, Shooter, Tower Defense and Runner lost the Three.js SwiftShader device. Minimal produced a uniform blank capture; its cause remains distinct and unproven. Browser, all four unit shards, native tests, desktop parity, macOS, iOS, Linux, Networking and native coverage passed that run. Draft-skipped checks are not green.5cb2a22edhardware-WASM motion passed the unchanged 1 px guard (40-second maximum 0.2931 px; separate stationary/moving 60-second passes). Fresh independent visual judgments preserved the supplied legacy ocean/ship appearance. Current archive-generated canonical source passes typecheck/build and all seven functional scenarios in its shipped Three.js configuration. Its performance result remains RED: 95.9 ms versus 33 ms on a loaded desktop. The queued Intel timing job never launched a browser and was stopped through the supported helper. Current-cohort explicit-WASM live motion now passes on NVIDIA Turing: 0.303 px maximum jitter versus the unchanged 1 px limit, 840 renders, 832 fractional frames, 215 readbacks and 1.7904 m hull motion; all seven assertions and strict zero diagnostics pass. The original scenario used the preserved canonical WASM build via a verified strict-port Vite preview after waiting for the shared GPU lease. Valid timing qualification remains open.5cb2a22ed; these are scoped evidence, not final-head full qualification.[email protected]pins the affected SDK 1.30.0; a local upstream repair is being validated against the actual published package. Workspace overrides would not repair downstream installs. Publication remains held; later performance epic cycles stay open.PRD:
docs/PRDs/performance/PRD-573-unreal-class-performance-in-release-cycles.mdengine: "threenative"is the default; a game selects the legacy renderer explicitly withengine: "threejs".In this PR so far:
fc261bbd1charter amendment: one opt-in C++ engine behind the Three.js API (was docs(charter): admit one opt-in C++ engine behind the Three.js API #494, closed into this PR). Phase v1 slice 0 is ticked.93656da00the epic folded into one PRD. The olddocs/PRDs/unreal-class-performance/folder (README, ROADMAP, PRD-574..578) is removed.Initial draft gates:
pnpm check:docsexit 0; prose lane 244/244 tests passed;pnpm prd:progress1/26 phases, 3/60 boxes.Owner waiver: this PRD has 26 phases, over the 3-phase limit, because the owner asked for one PRD for the epic.
Supersedes #438 and #494.
Snow Heightfield validation (2026-10-16)
Fresh read-only judge: NEUTRAL in both native and both browser comparisons; no visible regression. Native pair 2 differs by one simulation tick, limiting exact contact-state equivalence. Four native runs passed eight logical assertions plus the separately evaluated unchanged pixel guard; four browser runs passed the original footsteps scenario.
Five valid hardware pairs reduced render P50 by 31.3–33.6 ms, exceeding the measured 8.0 ms A/A noise band. The original tail-latency gate still fails; private Xvfb does not qualify release FPS or end-to-end performance. Focused repaired template contracts: 119/119 passed. Full template/native matrix and release qualification remain open.
Raw capture hashes.
Starter browser repair — 2026-10-16
Owned Wasm GPU timing now reports completed samples with their original frame IDs (
09359fc4f). Two owned runs and two legacy runs pass the original render-chain scenario at high quality (24/24 assertions total), headed on hardware NVIDIA/Turing at 1280×720, with zero diagnostics. Fresh independent visual judgement: NEUTRAL for both pairs and overall; appearance-preservation passes. Still frames do not establish temporal AA stability.Snow’s fresh performance replay passes 5/6 checks; frame P95 remains 55.1 ms against 33 ms. Native legacy screenshot capture, paired CPU, remaining templates, full gates and PGO remain open. No release-readiness claim.
Deterministic native capture repair
Native screenshots now present the current frozen frame without advancing simulation. The host waits by elapsed time rather than idle-loop iterations; the owned inspect endpoint routes the render-only request.
Latest follow-up: the original native Starter render-chain now passes 6/6 on both owned and legacy players at high tier, RTX 2080, 1280×720, with zero reported diagnostics. This closes the previously unrun native render-chain check, but does not establish equal-quality CPU comparability: owned reports antialias in its chain and legacy does not. Paired CPU qualification remains open until that difference is resolved.
RTS surface repair — 2026-10-17
Headed WebGPU, NVIDIA Turing, 1280×720. Fresh independent judge: IMPROVEMENT, both pairs and overall; Three.js reference comparison passes. The BEFORE startup exception prevents a game-pose comparison.
Sailing visual proof — 2026-10-17
Fresh read-only ocean judge: IMPROVEMENT for both pairs. Fresh ship judge: NEUTRAL/PASS for both corrected pairs; captures exclude the development scene-warning overlay, and static images do not establish motion correctness. The original obstructed capture was rejected. Both supplied legacy runs were compared before the completion ping.
Sailing startup-camera preservation — final candidate
Fresh independent visual verdict: NEUTRAL/PASS for both runs and renderers. These labelled native frames show the same authored pose before and after. Full raw pairs and metadata are in the immutable capture folder.
SMAA explicit-mip appearance preservation
Fresh read-only judge: NEUTRAL/PASS, both pairs and overall. Headed hardware WebGPU, NVIDIA Turing, same SMAA fixture/pose at 1280×720. The original implicit-sampling arm and explicit-mip0 arm preserve edge quality, silhouettes, seams, shadows, exposure and HUD. Static captures do not qualify Windows FXC or motion.