Repository navigation
Every template runs on the native engine; TSL compute; CI cache, release, runner and GI calibration progress - #605
Merged
Merged
Conversation
The AC needs the sum of per-job queue wait over a full board; the shipped KPI reported only p90. fullBoardQueueSumMedianMin is the median of the window's finished full boards, from the same per-job started_at - created_at the merge-latency audit already uses, and every recorded iteration keeps it in extra. Covered by ci-board-kpis.spec.ts (red without the field).
…ner wait The 60-minute AC-2 target is not met and is out of reach at today's capacity: the board's own jobs cannot wait less than 100-129 min on 26 slots. Cause, floor table and four options recorded; both AC-2 and the phase box stay open.
…er, 2026-11-03); median 11.2 min today
…und the caches (PRD-628) Phase 1. The payload key hashed all of `download-deps.mjs`, so an unrelated dependency edit produced a key no asset was published for. `scripts/v8-recipe-hash.mjs` hashes the `v8-android` DEPS entry alone (a brace scanner that skips comments and strings) plus the builder, the 16 KB check and `gradle.properties`; the action runs it in place of `hashFiles(…)`. Six-case spec, red first, plus a mutation check. A payload a pull request built was reachable only by that pull request, because an Actions cache is scoped to the ref that saved it. The action now also publishes it as a workflow artifact named `v8-android-<recipe>`, which has no ref scope, and reads it back before falling through to the cache and the source build. Only a run that built and verified the payload publishes, so it is one upload per recipe (25 MB). The resumable Ninja state saves on a cancelled pull request too, since that progress is the only thing the next push can resume from. `scripts/ci-cache-prune.mjs` bounds it to one entry per (os, arch, recipe) and fits the repository total under a stated 9 GiB budget, never dropping a hot key; `prune-cache` in ci-janitor.yml applies it after every CI run. On the live list (2026-11-03, 56 entries, 10.00 GiB) it plans two deletions and 8.51 GiB, touching no develop-scoped key. Phase 2. `CCACHE_BASEDIR` in all five ccache jobs, so objects built in one checkout hash the same in another. Run 41441113316 measured `Hits: 256 / 459 (55.77%)` when the previous develop run's entry was still there; 41575174097 restored nothing and measured 0.46%, which is the eviction the janitor now prevents. The cache table from that full board is in the PRD. Phase 3. `scripts/ci-runners.sh prune` drops dangling images, build cache older than 72 h and stopped CI containers; `up` prunes then refuses below a 20 GB floor; the balancer withdraws its slots while the host is under it and re-advertises when the space returns; the systemd unit prunes in ExecStartPre. Spec runs the script against stubbed docker/gh/df. Real host: 349 GB free, `prune: 04:46:04Z (0 min ago)`.
…cer alive (PRD-628) The artifact bridge could be poisoned: any run, a fork's pull request run included, can upload `v8-android-<hash>`, `--verify` only proves the payload matches its own receipt, and a trusted run that restored one republished it as the release asset. The read is now pull-request-only, filtered by `v8-artifact-pick.mjs` to this repository's own head on main, develop or the run's own head branch, a payload that fails `--verify` cold-builds, and `publish-android-v8` needs the action's new `built` output. Also: a failing hash script failed the step silently, so the asset name could be empty; the balancer died when its prune called `fail`; a `df` it could not read counted as 0 GB free and paused the pool; the docker root was read once for a loop that runs for hours; the cache janitor could delete another ref's bounded state and two janitors could race.
Review round 2 on the payload bridge. The release asset lost its seed. Gating the release write on `built` meant a develop or main push that hit the develop-scoped `android-v8-payload-*` cache never republished a missing asset: the payload was already there, so nothing built, and the run published nothing. The action now exports `trusted` — true when this run compiled the payload, or restored it from the Actions cache on a non-pull_request event — and `publish-android-v8` requires it. A pull request's cache is written by pull request code and an artifact is any run's bytes, so neither is trusted; a run that downloaded the release asset has nothing to publish and skips as before. `ci-native-qualification.mjs` pins the new gate, and the trust spec evaluates the shipped expression and the job gate over the five cases instead of restating them in prose. The janitor made its own hot keys immortal. Every entry of `cargo-v1-`, `wgpu-cache-` and `pipeline-cache-host-deps-` carried a new key per source change and none could be evicted, so stale GBs sat under a budget that only fitted cold keys. Protection is now the newest entry per (prefix, ref): an older copy is one another key replaced, and it is the first thing the budget drops. `v8-artifact-pick.mjs` compared `head_repository_id !== repository_id`, which passed when the API answered without either field — undefined equals undefined, so an artifact of unknown origin read as this repository's own. Both ids must be integers now, else TN_V8_ARTIFACT_MALFORMED. Red first on all three: the missing output, the stale hot copy and the ambiguous repository id each failed their spec before the fix. 38 tests in `packages/runtime-native/tests/native-platform-workflow.test.mjs` (package config), ci-structure + ci-needs, typecheck and lint are green; lint reports no errors.
…on the lamp and 25% high on the sun
Unreal converts lumens to candela over the solid angle the light fills (LocalLightComponent.cpp:180-214): 4 pi for a point light, and 2 pi (1 - cos outerCone) for a spot, clamped at 88.9 degrees. The harness divided every light by 4 pi, so a spot in lumens read up to 33 times too dim; it now converts over the cone and fails the load when a spot in lumens carries none.
…ame scene The Unreal ID layer of shadow:wildwood, captured from the rebuilt 5.8.3 level, is committed as shadow/wildwood/id, the offline path the sameness gate reads. Against the ThreeNative frames of the same scene data the gate passes: 16 objects in 4 poses, 0 px beyond 1 px on every object, farthest edge 1 px, no pixel without an ID colour. The procedural shadow:foliage scene it replaces is retired: its registry entry, its folder and the leaf-mask spec are deleted.
…rules Phase 1b is DONE: every registered scene passes the sameness gate, and the procedural foliage scene is retired with its data. AC-2 keeps its box open: the point-light conversion is now Unreal's, and the two light scales (6.2% lamp, 25% sun) have no source rule the harness can adopt, because Unreal's own L1 EXR implies a fixed exposure of 2.03 to 2.12 that neither source path produces. Decision records why SUN_ILLUMINANCE and SKY_TO_SUN were not tuned and names the next experiment (the sun's air transmittance off L1). A third Decision places the cornell regression below the overlay: every appearance file the scene loads is byte-identical to the epic head's, so the 6.2% is in packages/.
Two lanes that pack into ~/.cache/threenative/unreal-look at once fail with TN_VISUAL_PACKAGES_MISSING; the unreal-calibration skill now says to give each lane its own TN_UNREAL_LOOK_WORK.
… bisect is measured The comment under `candela` said 3625 cd for 600 lm down a 10 degree cone. The solid angle `2 pi (1 - cos 10 deg)` gives 6285.638, so the comment now says 6286 and the spec pins both the rounded value and the exact one. The PRD records the round-3 bisect instead of the question: `three@0.185.1` in the generated project draws byte-identical cornell L1 frames, so the 0.187 upgrade is out; the 10-of-12 record is at `be61f9bc6`, an epic branch head, so the window is the epic squash merge `be61f9bc6..89ed124` and not `89ed1247f..HEAD`; the Unreal reference did not move; EON is not the uniform 6%; and the engine-vs-JS arm is refused by `TN_UNREAL_LOOK_WRONG_ENGINE`. The sun's air transmittance lead is closed with the source: Unreal applies it only while the sky atmosphere renders, and L1 sets `r.SkyAtmosphere 0`, so neither engine attenuates the sun at that layer. The absolute scene-linear ratios stay unverified: the round-2 instrument was never committed and a round-3 one disagrees with it by pi.
Delete snow's engine: "threejs" pin and its AGENTS.md sentence, drop it from the legacy pin ratchet, and give the kit contract the engine-cost bounds it now owes: one Wasm-engine run at 640x360 measured the steady-window counters, and the bound is twice the measured worst because the capture came from a private display. The starter's lifecycle replay on the desktop player (KeyR teardown and rebuild) and on the Wasm engine (React restart) passes, so no engine change was needed.
Tick the BufferAttribute disposal box with the consumer replay on both backends, and record what the template playtests actually did: 20/22 starter and 5/6 snow on the Wasm engine, 2/4 starter and 2/2 snow on the desktop player, plus three misses attributed to a stale render-chain row, virtual-display frame pacing and the desktop UI focus route. No threshold was loosened and AC-4 stays open for the judge.
…kit is pinned The engine-cost row test named runner as the pinned kit, so unpinning runner in another lane would have turned it red. It now reads the shipped declarations: every kit that declares the row writes a file, every kit that opts out writes none. A fixture kit with injected entries tests the no-row path even when no shipped kit is pinned.
The starter's look and render-chain rows expected [ambientOcclusion, traa, motionBlur, bloom, vignette], stale on both engines since the owner's 2026-10-28 decision in packages/core/src/look/quality.ts: the high tier draws 4x MSAA with no temporal resolve and no camera motion blur. Measured per tier on this tree, the rows now assert high [ambientOcclusion, ssgi, ssr, bloom, vignette, antialias], medium [ambientOcclusion, ssr, bloom, vignette, antialias] and low [ssr, bloom, vignette, antialias], with grade and grain dropped at intensity 0. Reruns: starter look on web 23/23 assertions, exit 0; starter render-chain on desktop 7/7 assertions, exit 0. The two specs that pin those rows move with them. PRD-620 also records round 2: the snow pose difference is the two engines starting the game scene at different ticks, not a camera or projection difference, and the contact-occlusion drop on the three.js arm is a correct, reported refusal.
…ed diagnosis arm TN_UNREAL_LOOK_ENGINE=threejs lets one run draw on upstream three and names every cell threejs (diagnosis) in the report and gi-capture.json. The compare refuses the row verdict, --write-stats, a lock write and a lock check for those frames (TN_UNREAL_LOOK_DIAGNOSIS_ONLY); the default still fails closed on a frame drawn on the wrong engine (PRD-646).
…l's own PNG and EXR pnpm unreal-look:ratio inverts the harness's own filmicToneMap back from a display level to the light the pixel was drawn from, and reads Unreal's reference-stats.json ev50 into the same units, so a light gap is a ratio instead of a few levels in the filmic shoulder. --selfcheck proves the convention on Unreal's frames: cornell L1 reads worst 0.74% and sunlit-room L1 4.7% between an Unreal PNG inverted and the same cell's EXR (PRD-646).
…and records the engine-split arm The ratio tool proves its convention on Unreal's own frames (0.74% cornell, 4.72% sunlit-room), reads cornell 0.953 and sunlit-room 1.232 on the owned engine against 0.955 and 1.233 on the TN_UNREAL_LOOK_ENGINE=threejs diagnosis arm, and restates 0.938, 1.249 and 2.03 to 1.6%, 1.4% and 0%.
…mp scale The threejs arm, the epic overlay, the epic loader and the core at the oldest and the newest pre-0.187 commit all draw today's frames, so the 10-of-12 record is not reproducible from the tree (the epic core cannot run under three 0.187: no getShadowMaterial in threenative:web-engine). A 1.0486 lamp scale moves cornell from 2 to 5 of 12 rows with every row within 2 levels, so the gap is one scale; no constant is tuned and the boxes stay open.
…ine pin (PRD-617) Each template binds clean on the native bundler and boots on the V8 player, so the pin and its stale deltaTime/vertexIndex comment go. LEGACY_PINNED_TEMPLATES keeps snow alone; the ratchet test passes 114/114.
…rial normalNode (PRD-617) three's MRT slot names normalView, so an authored normalNode changes lighting only. Refusing the frame cost sailing every water scenario with TN_POST_NORMAL_UNSUPPORTED. native_engine_renderer_aux_targets_normal_node aborts on the old path and passes with the target preserved.
…ned templates (PRD-617)
…-617) three 0.187 src/nodes/accessors/Normal.js:95-119 makes the fragment-stage `normalView` the value of `builder.context.setupNormal()`, and src/materials/nodes/NodeMaterial.js:952 returns `vec3(this.normalNode)` when the material authors one. The MRT normal target therefore holds the authored normal, not the geometry normal as a2750b7db claimed. `StandardPrograms::normal` is now the lit fragment's own normal, and `Renderer::normalTarget` writes it for a draw whose material authors a normalNode, through that draw's own lit fragment instead of the shared geometry-normal pass. `native_engine_renderer_aux_targets_normal_node` asserts the read-back equals the authored tilt on both masks. RED on a2750b7db: got 0 0 1 14.5, want 0 0.447214 0.894427 14.5. GREEN after, with aux_targets_one_pass, aux_targets_alpha_tested, denoise_face_normal and the lit references. Sailing's web playtests pass 8/8 on the rebuilt Wasm engine. Co-Authored-By: Claude <noreply@anthropic.com>
…617) Two crons fire, so the daily run of every Sunday shows the rotation as `skipped` and the earlier run of the same day fails it in 20 s: `pnpm test:templates` imports `@threenative/assets` through `create-threenative/src/config.ts`, and nothing built `packages/*/dist`. The job now runs the workspace-dist action (bundle-only, no cache save), the native-engine-wasm action for the web engine the local pack asserts, accepts a software adapter as golden-path does, and allows 120 minutes for eight template legs. A new spec parses the schedule crons and the job's `if:` (the condition names the weekly cron and no other), and requires both builds to precede `pnpm test:templates`. Co-Authored-By: Claude <noreply@anthropic.com>
…he engine designs Found while refreshing the native coverage record, which aborts on any failing engine test: - The C++ harness did not know the host draws `translucent` (renderer.cpp:4749 registers it as a target when the post graph reads it), so the starter and four kits failed with TN_POST_INPUT_MISSING: translucent. - The TS driver counted the contact occlusion's admission refusal (TN_AO_INDIRECT_UNSUPPORTED) as a lowering failure. PRD-635/617 make that a designed drop on both engines, and the judged racing receipt is that drop, so the sweep now names it and keeps going; any other drop still fails. Engine layer: the native test harness was stale against the engine, not the kits' look.
…er sources Measured on this tree with `pnpm --filter @threenative/runtime-native native:coverage`: 48 native contract targets, TOTAL 82.11% -> 82.14% (66579 -> 66696 instrumented, 54669 -> 54784 covered), every subsystem above its floor (engine 84.43% against 82.22%, webgpu 76.79% against 71.89%). No floor moved. PRD-628 Phase 3's spec line counts 7 disk cases and names the routing fixture's `df` stub.
…no longer carries
…amples both phases `sails-fill-with-wind` failed on hosted SwiftShader with `sailSamples` 2 and `sailMotion` 0: a step's ticks advance undrawn, so a component that samples a GPU readback gets as many samples as the adapter happens to render, and both landed inside one sail phase. On the CI-shaped five-scenario run the scenario took 27.0 s and failed. New scenario field `drawEvery` (positive safe integer, ticks) draws one frame every N ticks of each tick-advancing step, counted per step across the runner's 10-tick path cadence. It routes through the drawn-tick path `engineCost` already used (`advanceFixedStep` with `render: true` is now the same mechanism at every tick), so there is one way to draw and no second. It fails closed rather than being ignored: a wrong type, zero, a negative or a non-integer throws `invalidScenario`; a bridge advertising no `runtime.fixedStep` fails the handshake; `--live-clock` (and a scenario `clock: "wall-clock"`) fails `TN_PLAYTEST_UNSUPPORTED_ON_TARGET`; and the device and desktop lanes name it unsupported, because they own no per-step draw window. Documentation: `packages/playtest/docs/reference.md`. `sails.playtest.json` sets `drawEvery: 60` (30 drawn frames per 1,800-tick hold) with every assertion and bound unchanged, so the template tree moves and `scaffold.spec.ts` records the new sailing hash. SwiftShader, five scenarios, CI's own command: `sails` 27.0 s fail before, 73.1 s and 70.9 s pass after, `sailSamples` 36/37 and `sailMotion` 1.34/1.38, samples in both phases. RTX 2080 still passes (261 samples, `sailMotion` 1.41).
…uard The cadence only counts ticks that a counted fixed-step bridge advances, so a wall-clock run cannot honour it. The run fails with `TN_PLAYTEST_UNSUPPORTED_ON_TARGET` naming `drawEvery`; without the guard the same scenario ran on and the test failed with the diagnostic list carrying no such code (expected [Array(1)] to include it). With the cadence itself disabled the 120-tick/`drawEvery: 60` case fails as `expected [] to have a length of 2 but got +0` drawn ticks.
… fits its 2-minute budget again Its full hosted checkout of the 488 MB HEAD tree outran the job's 2-minute timeout: cancelled in develop runs 41683823791, 41686494964 and 41703997856 and in #605 run 41688813459 (both attempts); it passed in 36 s at 14:38Z. docs/ is 330 MB of the tree and no step reads it. Proved on a sparse clone (164 MB): pnpm install --frozen-lockfile exit 0, the collector control exit 2 as the job expects, production-evidence and ci-summary exit 0. Workflow specs: 952 of 953, the one red is the starter build that needs a Wasm build this checkout lacks.
jonit-dev
marked this pull request as ready for review
November 3, 2026 21:15
…utgrew 20 Develop runs 41683823791 and 41703997856 and #605 run 41688813459 (both attempts) were cancelled at 20 minutes still in actions/checkout; run 41686494964 passed in 16. The scan keeps its full history, as the job's earlier headroom decision says. Workflow specs: 32 files, 909 passed.
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
Nov 3, 2026
jonit-dev
enabled auto-merge
November 3, 2026 22:07
No box is ticked: every open criterion still needs a run this branch cannot produce. Each box now carries its measured numbers and the one run that would close it. PRD-628 - line 31 (payload reuse): run 41688813459 built `v8-android-6d7f399f…` in 1 h 45 min (18:18:44Z -> 20:03:42Z, artifact 13268070999, 27,451,905 bytes) after `No published …tgz`, `v8.artifact skipped` and `Cache not found`. Run 41718807432 restored it in 1.4 s and skipped the build. Both are `workflow_dispatch` on the same ref, so the pull-request scope stays unproven; the artifact read is verified read-only (v8-artifact-pick.mjs -> 41688813459). - line 32 (cancel keeps progress): run 41670763005 cancelled at `[1781/3519]`, saved `android-v8-source-…-41670763005-1`; run 41688813459 restored it and planned 1724 edges, not 3519. A dispatch run, so the `pull_request` arm stays unproven. - line 36 (ccache >= 80%): `test-native` 229/1076 = 21.28% (nothing restored), against 256/256, 280/280 and 312/312 = 100% in native-engine and starter-linux on the same board. - line 37 (cache table): second full warm table from 41688813459 attempt 1, 70 job logs and 181 cache lines; pnpm store, Playwright, Cargo, emsdk and third-party hit, Gradle misses. - line 55 (budget): 70 entries / 8.63 GiB at 21:19:40Z -> 86 entries / 9.28 GiB at 22:07:51Z, zero evictions; no full board completed in the window. PRD-480 AC-2 and its phase box: per-job wait p90 26.3 min (41718838521, merge group #606), 7.8 min (41718807432, dispatch #605) and 11.4 min (41569108715); median 11.4 min against 10, not met. 11 jobs per board on `tn-` runners.
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
Nov 3, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to no response for status checks
Nov 4, 2026
…ilt (PRD-628) The queue dropped #605 at its 120-minute check timeout (checks_timed_out, merge-group run 41734911462) with every finished job green: #605 keys V8 by its recipe hash, no release asset carries that hash until develop publishes one, and a merge group cannot restore the pull request's cache, so V8 built cold for over two hours. The artifact step now runs in a merge group too and trusts the pull request's own head branch (same repository only, looked up from the queue branch name) and the queue branch. The picker, the fork check and --verify still apply, and a merge group still never publishes the release asset. Red-green: ci-v8-artifact-trust.spec.ts 3 failed / 11 passed on the old action, 14 passed on the new one. Every workflow spec: 962 of 963 pass; the one failure is the starter build that needs a Wasm build in this checkout.
jonit-dev
enabled auto-merge
November 4, 2026 01:26
develop's copy of each of the seven conflicted files is byte-identical to the #606 queue commit 6103e42 that 605-sync round 1 resolved in 04f56b60c, #605 has not touched them since, and neither side changed packages/runtime-native or the templates since then, so those seven files are taken from 04f56b60c as is. Round 1 built render_database_test (123/123) and regenerated the coverage report (82.15%, every floor met) on that same content. Checks on this merge: unreal:status up to date, check:docs 3670 links, scaffold.spec 78/78, check-native-coverage + pre-commit-hook specs 8/8, pnpm typecheck exit 0.
Contributor
|
The merge queue stopped on
Fix, push, re-enqueue. |
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Nov 4, 2026
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.
What is this PR about?
Every starter kit now runs on the owned native engine. The native engine runs TSL compute. The work also lands progress on four more P0/P1 PRDs: CI caches, one-command release, runner wait time and the GI calibration scenes.
Why does it matter?
PRD-617 and PRD-620 blocked the v1 release: no template pins
engine: "threejs"any more. The other four cut CI wait and make the Unreal calibration numbers trustworthy.Done
LEGACY_PINNED_TEMPLATESis[]. Fresh Opus judges: 10 of 10 templates NEUTRAL or IMPROVEMENT against legacy (platformer waterfall fixed by a translucent target the occlusion skips; runner IMPROVEMENT, its sun's shadow box now follows the runner). Every remaining web perf miss also misses onengine: "threejs"(paired runs).Loop.sunlit-roomL1 19 → 36 of 40 rows, judge IMPROVEMENT. The remaining gap is named: the diffuse's specular-energy loss (Unreal integratedE, three pointwise Fresnel).release:ship, no hand edits; the npm upload failed in CI on the token (evidence in the PRD).Still open (not blocking this merge)
NPM_TOKENneeds publish rights; the next release proves it.Visual evidence
Judge sheets:
captures/617-final,617-final-r3,617-runner2,620-final,646/r6(local); verdicts recorded in each PRD box.