Skip to content

feat(core): PRD-624 release the native engine as the default (PRD-573 v1) - #493

Merged
jonit-dev merged 641 commits into
developfrom
docs/unreal-class-epic
Oct 19, 2026
Merged

jonit-dev merged 641 commits into
developfrom
docs/unreal-class-epic

Conversation

@jonit-dev

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

Copy link
Copy Markdown
Contributor

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

  • Ship the first release of the new engine through its real browser-WASM and native consumers (PRD-624).
  • Carry the whole Unreal-class performance epic (PRD-573) as one PR, by owner order. Its cycles v2 to v5 stay open and do not block this release.
  • Make the PR mergeable with current develop without losing CI or engine fixes.
  • Qualify one frozen candidate: one full exact-source CI board, a fresh installable cohort, and Sailing inside its 33 ms gate.

What ships

Engine

  • The C++ engine behind the shared Three.js API. engine: "threenative" is the default.
  • Charter amendment for the single opt-in C++ engine (was docs(charter): admit one opt-in C++ engine behind the Three.js API #494, closed into this PR).
  • Fixes in flight, not pushed yet: the native PMREM bake waits for GPU completion and works in cost groups, so SwiftShader does not lose the GPU. It keeps its progress across passes.
  • Fixes in flight, not pushed yet: the Three.js arm now fences async PMREM per cost group, not per face. The old cost was about 120 ms per face, and the environment was missing at capture.

Runtime and rendering repairs (already in the PR)

  • Owned Wasm GPU timing reports completed samples with their original frame IDs.
  • Native screenshots present the frozen frame without advancing the simulation.
  • RTS surface, Sailing ocean and ship, Snow heightfield and SMAA explicit-mip repairs, each with a visual judge verdict (see the history below).

Planning

  • PRD-573 is one PRD for the epic (phases are release cycles v1 to v5). The old docs/PRDs/unreal-class-performance/ folder is gone.
  • PRD-624 holds only the outstanding release work.

Status and CI

Gate State
Merge with develop Done in 5cbeadeab; head is now a069e1450. PR is MERGEABLE.
PRD-624 progress 5/7 boxes ticked (prd:50%), per local commit 3b3490d50 (push pending). A box needs green proof on the PR head.
Full-board CI run 39164145931, head a069e1450 2 reds: test and test-unit (4/4). Fixes are in progress. A new full board follows the next push.
Earlier boards (2c261246b, e5ec2c179) Red on budgets, template parity (racing, minimal, runner, platformer), test-unit and test-native. Fixes are in the current head or in local commits not pushed yet.
Draft state Draft. Draft-skipped checks do not count as green.

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

  • Native PMREM becomes fully ready before covered rendering.
    • Progress: startup waits for the native bake (18eb7c539, bounded in fe493a075). A red-green pass found 2 native bugs, fixed in 1699bf5a1 (texture retirement reaches every renderer, and a probe capture no longer publishes a black face during a bake) and 2c83ba331 (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 on a069e1450.
  • Three.js startup completes preparation within the existing contract.
    • Progress: the owner chose split budgets (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

  • Resolve PR feat(core): PRD-624 release the native engine as the default (PRD-573 v1) #493 against current origin/develop.
    • Progress: done in 5cbeadeab. The PR is MERGEABLE. This box is ticked in the PRD file.
  • The fresh core consumer resolves the repaired public dependency chain.
    • Progress: threenative-sculpt-mcp 0.2.2 is published and the core floor is 0.2.2. Fresh consumers resolve @modelcontextprotocol/sdk 1.31.0, proxy-addr 2.0.8 and fast-uri 3.1.8 (checked at 386e7fbcf). Ticked in local commit 3b3490d50, push pending.
  • All candidate packages come from one frozen source and install normally.
    • Progress: one pack of 15 archives, byte-identical on the desktop and the laptop. 15/15 package identities match. The installed WASM equals a clean rebuild. The starter builds and 4/4 shipped playtests pass on Intel gen-12lp hardware (checked at 386e7fbcf). Still missing: the runtime-native-v0.3.5 prebuilt release (native release owner).
  • Sailing passes its original performance gate on the final cohort.
    • Progress: frame p95 15.3 ms and 15.1 ms against the 33 ms gate. Rendered-motion playtest passes with 0.094 px jitter. The laptop was quiet, load 1.4 to 2.9 (checked at 386e7fbcf).

Phase 3: Qualify the exact release source

  • The frozen candidate passes the complete required qualification.
    • Progress: open. Full board run 39164145931 on a069e1450 has 2 reds (test, test-unit 4/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.5 prebuilt release, and decide the merge and publication.

Risks and follow-ups

  • Breaking change: engine: "threenative" becomes the default. engine: "threejs" selects the legacy renderer.
  • Performance: Sailing frame p95 is 15.3 ms and 15.1 ms against the 33 ms gate on a quiet laptop (earlier 95.9 ms was on a loaded desktop).
  • Blocked, native release owner: the complete prebuilt native matrix for the final cohort does not exist. The partial 0.3.5 artifacts are not enough.
  • Open, João: the Android pump qualification and the host-projection intermittency are unverified. This PR grants no waiver.
  • Rollback: revert the PR before publication. After publication, a new release is required.

How to verify

pnpm typecheck
pnpm lint
pnpm test
pnpm release:prepare
gh pr view 493 --json mergeable,headRefOid
  • Strict stock Runner parity: scripts/engine-parity.ts, then the affected native conformance case (pnpm parity).
  • Sailing: playtests/performance.playtest.json and playtests/live/render-motion.playtest.json in the installed game, with the shipped runner and --browser-recipe webgpu.
  • Final proof: completed exact-head full qualification jobs and receipts, including 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 e16a59274362a7c3f0226755950ea05f71732693 is integrated. Completed full qualification with Chromium diagnostic logs is 39126674780, dispatched on exact published head 5df46d637f81842b1641279118df23460df3a9f1. That run predates the latest candidate and finished with 62 successful checks, seven failed template parity captures, and the downstream ci-required failure. No other product check failed. Candidate a62f8c324818c6988cd8daf1f517a2aa17d363be adds 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.

  • CI failures: previous full run 39122999774 ended in failure on 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.
  • Windows and Android: Windows D3D12/WARP created the actual SMAA weights pipeline; starter, relocated and clean-PATH launches rendered 300 frames, with installer gameplay/uninstall passing. Production collection passed with advisory budgets, not hardware timing qualification. Android passed 79 contracts with 30 expected capability blocks and explicit emulator logcat collection. Its CI pump observer and production performance collector remain BLOCKED. Separately, the unchanged evaluator accepts the actual marker's structural shape offline; that is not CI observer or timing qualification.
  • Sailing: older frozen 5cb2a22ed hardware-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.
  • Verified local repairs: full workspace typecheck, root lint, frozen install, all 13 scaffold fingerprints, original 1800-tick replay and focused shader/Android contracts pass. Four headed SMAA before/after captures received independent NEUTRAL/PASS judgments. Native aggregate coverage is 49,339/61,051 lines (80.82%; engine 82.50%); full ASan passes 6/6. Older quiet-laptop units passed 11,697 tests on 5cb2a22ed; these are scoped evidence, not final-head full qualification.
  • Release blockers: seven parity failures, fresh final-head qualification, current Sailing timing, and absent supported published native 0.3.5 asset cohort. External production dependency [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.md

  • Phases are release cycles v1–v5. Each cycle has one headline KPI from a same-run benchmark and a stability gate.
  • v1 phases land the C++ engine behind the shared Three.js API: engine: "threenative" is the default; a game selects the legacy renderer explicitly with engine: "threejs".
  • Phases land as commits on this PR.

In this PR so far:

Initial draft gates: pnpm check:docs exit 0; prose lane 244/244 tests passed; pnpm prd:progress 1/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.

Snow native-compare1: BEFORE / AFTER

Snow native-compare2: BEFORE / AFTER

Snow browser-compare1: BEFORE / AFTER

Snow browser-compare2: BEFORE / AFTER

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.

Starter matching high-tier comparison 1: BEFORE legacy, AFTER owned

Starter matching high-tier comparison 2: BEFORE legacy, AFTER owned

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.

  • Native: original React HUD scenarios pass 3/3 on each engine. Four additional matched-pose runs pass 3/3 each: 18/18 assertions across six runs. Fresh visual judge: NEUTRAL/PASS for both pairs (static pose only).
  • Browser: original high-tier render-chain passes 6/6 twice, headed on hardware WebGPU.
  • Checks: 5,243 core/playtest tests pass (7 skipped), 61 local plugin/GitMCP tests pass; native inspect 4/4, typecheck, lint and 267 documentation/CI-contract tests pass.
  • Full test run remains red: 11 native failures from missing test binaries, stale SBOM receipts and the gradle-wrapper dependency-name parser. Baseline provenance remains unproven; no full qualification claimed.
  • Still pending: native high-quality render-chain and paired CPU; Snow's 33 ms P95; remaining template matrix, fresh coverage/CI and valid matched PGO.

Native capture pair 1
Native capture pair 2

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.

RTS BEFORE 1

RTS AFTER 1

RTS BEFORE 2

RTS AFTER 2

Three.js reference

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.

Ocean BEFORE 1

Ocean AFTER 1

Ocean BEFORE 2

Ocean AFTER 2

Ship BEFORE 1

Ship AFTER 1

Ship BEFORE 2

Ship AFTER 2

Final legacy comparison, run 1

Final legacy comparison, run 2

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.

Sailing BEFORE, run 1, WASM

Sailing AFTER, run 1, WASM

Sailing BEFORE, run 2, WASM

Sailing AFTER, run 2, WASM

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.

SMAA BEFORE, run 1

SMAA AFTER, run 1

SMAA BEFORE, run 2

SMAA AFTER, run 2

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.
@jonit-dev jonit-dev changed the title docs(prd): Unreal-class performance epic as release cycles PRD-573: Unreal-class performance in release cycles (whole epic) Oct 12, 2026
@jonit-dev jonit-dev added the prd:25% PRD phase 1 verified label Oct 12, 2026
@jonit-dev
jonit-dev marked this pull request as draft October 12, 2026 04:32
@jonit-dev jonit-dev changed the title PRD-573: Unreal-class performance in release cycles (whole epic) PRD-573: Unreal-class performance in release cycles (whole epic) - v1 Oct 12, 2026
@jonit-dev

Copy link
Copy Markdown
Contributor Author

PRD-573 v1.11: native engine skinning fix (visual judge: IMPROVEMENT)

On R5 the native arm drew the 50 foxes in bind pose: the SkinnedMesh binding kept skeleton, bindMatrix and bindMatrixInverse read-only, so core's projection copy dropped them. Red-green: native_engine_v8_skeletal.

BEFORE (native) AFTER (native) LEGACY (reference)

A fresh read-only judge compared 2 runs per arm: the foxes now match the legacy poses, with no artifacts and no change outside the foxes.

KPI progress chart (legacy ÷ native CPU per bench point, target 2×):

@jonit-dev

Copy link
Copy Markdown
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).

BEFORE (JS mirror) AFTER (host projection) LEGACY

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.
…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.
@jonit-dev jonit-dev added prd:25% PRD phase 1 verified and removed prd:0% PRD implementation has no verified phase yet labels Oct 18, 2026
…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.
@jonit-dev jonit-dev added prd:50% PRD half the phases verified and removed prd:25% PRD phase 1 verified labels Oct 18, 2026
@jonit-dev
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.
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
jonit-dev merged commit 4e8fe84 into develop Oct 19, 2026
79 of 84 checks passed
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

critical prd:50% PRD half the phases verified

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant