Skip to content

refactor(untangle): one owner per value — PRD-832 lands, PRD-833..842 follow - #632

Open
jonit-dev wants to merge 149 commits into
developfrom
refactor/untangle-832-842
Open

jonit-dev wants to merge 149 commits into
developfrom
refactor/untangle-832-842

Conversation

@jonit-dev

@jonit-dev jonit-dev commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Status

The untangle batch (report): one owner per
value, so a change lands in one place and drift goes red. One PR for the batch, as the owner asked.

PRD Label State
PRD-832, 833, 835, 836, 837, 838 prd:100% DONE, in docs/PRDs/done/
PRD-834 cross-language contracts prd:100% DONE — opcodes, markers, config keys and hook names come from one manifest each; render folds into renderer with TN_GAME_RENDER_KEY_RENAMED
PRD-839 the CI contract is a model prd:100% DONE — AC-2 measured on run 42533915867: 25 of 25 job names identical to the last comparable run
PRD-841 game.ts stops being the hub prd:100% DONE — boot/loop phases take narrow interfaces, every acquisition pushes its disposer, the UI bridge left the hub
PRD-842 native tests check behaviour prd:50% OPEN — six of seven iOS launch checks pass on macOS; web-ui-missing-bundle does not log TN_UI_BUNDLE_MISSING
PRD-840 platform difference is a value prd:75% OPEN — phases 1-3 landed; conformance unchanged before/after and the web judge is NEUTRAL, but the native capture and the paired bench are not closed

What this head adds over the last push

  • One renderer config key. defineGame takes renderer only; the portable block that was
    render merged into it, and the retired key throws TN_GAME_RENDER_KEY_RENAMED naming the field
    mapping. The 16 kits, the examples and the guides migrate; the scaffold is re-pinned.
  • PRD-841's remaining phases (boot proxy, disposer stack, UI bridge) merged in.
  • PRD-840's RenderFeatures, one flags.h, one CMake renderer link line and the
    renderer.cpp split
    merged in.
  • origin/develop merged (PRD-859, the threaded-engine startup fixes, the asset MCP pin), with
    the scaffold re-pinned.

Found and fixed here, not asked for

  • The desktop build of every kit that imports @threenative/core/render/environment was broken.
    PRD-833 moved the module to src/render-environment.ts, but the native player bundles source and
    read the last path segment as the file name, so it asked for src/environment.ts and the build
    died with Cannot read file. coreSourcePath now resolves the subpath through the package's own
    exports map, and tests/core-subpath-source.test.mjs walks every declared subpath. Before the
    fix, the starter's pnpm build:desktop failed; after it, both capture arms build.
  • The shooter kit set two renderer keys after the fold; it is one object now, with the kit's
    antialias default where the config leaves it unset.
  • frame-op-stream.js's generated OP_* names are exempt from the naming rule (they come from
    frame-ops.json), and native-config-keys.json is formatted, so pnpm lint is 0 errors.

Verification run for this push (local, on this head)

  • pnpm typecheck → 0 errors.
  • pnpm lint → 0 errors (2138 pre-existing warnings).
  • npx vitest run packages/core/__tests__ → 4626 passed, 4 skipped.
  • npx vitest run packages/playtest/__tests__ → 1974 passed.
  • pnpm native:build → exit 0; pnpm wasm:build → exit 0.
  • pnpm native:verify:desktop → exit 0: desktop core gate 300 frames at 1280x720, native contract lane passed: 56 of 56 targets, audio/loading/physics/UI-frame proofs and the three host vitest
    files (23 passed).
  • pnpm scaffold:pin --check → current; pnpm ci:fast → agents, docs, lint, drift pass;
    pnpm check:docs → 3813 links across 1637 files.
  • PRD-840: pnpm parity on both arms — web 154/154 cases the same status, desktop 152/154 with one
    timing-bound swap each way; the visual judge (fresh read-only reviewer, two runs per arm,
    BEFORE/AFTER burned in) returned NEUTRAL on all five criteria, cross-arm RMSE 0.000598 against
    a 0.000563 same-arm noise.

Not run to green, and why

  • PRD-840 AC-3 (paired bench) is blocked, not measured. The host ran other agents' benchmarks at
    load 25-50 on 24 cores; bench:engines recorded host load: 25.97 on 24 cores, absolutes invalid-absolute, so a frame-time A/B here is not evidence. An arm run did start and draw on the
    Wasm engine at the real adapter, so what is missing is a quiet host (or CI's benchmark job).
  • PRD-840 AC-2's native half is blocked. The shipped desktop verifier stops with
    TN_NATIVE_STARTER_SMOKE: runner failed: (empty stderr) on both arms after pnpm build:desktop,
    and a direct playtest --target desktop run reaches the engine player
    (TN_PLAYER_FIRST_PRESENT: runtime v8) but reports frames: 0 and no screenshot. The web half is
    judged NEUTRAL; the renderer split is exercised by that same C++ renderer compiled for web.
  • PRD-842 AC-2 is red on one check. The release-proof run built the simulator app and passed
    six of seven launch checks; the seventh (web-ui-missing-bundle) did not log
    TN_UI_BUNDLE_MISSING. Objective-C++ and xcrun are macOS-only, so the fix needs that lane.
  • test-unit (1/1) is red with no failing test, twice. On heads d674d4f00 and 8bebd2f92
    the review lane's "Run impacted unit contracts" step printed passing specs, then its log ends
    3.5 minutes before the step fails; the full test job on the same heads is green. The impact set
    is nearly the whole suite because this PR touches 596 files, and the earlier run showed gh: server error from a spec shelling out to the API. This needs the CI-harness owners: either the
    lane's timeout/output path or the spec that speaks to gh.
  • pnpm visuals still cannot exit 0 on any branch: the model scores in
    docs/verification/visuals/scores.json miss 10 kits and hold five below the floor. Owner input.

…, tone curve and core version

PRD-832 phases 1 and 2. scripts/lib/repo-inventory.mjs derives the kits, the
brief genres, the workspace packages and the Node floor from disk, so the
recall no longer rejects the puzzle, rts and runner kits it had never heard of.
A drift spec fails on any render source that ships byte-identical in every kit
that carries it and is not restamped: it named nine copies, not the seven the
report counted, and all nine are now shared and restamped to no-op.

debug.ts owns readLaunchFlag/readLaunchValue, replacing fourteen readers and
three truthiness rules. WorldEnvironment delegates its tone curve to
applyRendererToneMapping (the preset already made filmic the default), and the
core version now has one source: the generated version.ts.
…from one manifest

PRD-832 phase 3. api/web-frame-fields.json is the field table; generation writes
the TypeScript constant, the C++ enum and the facade's stage tables from it.
web_host.cpp switches on the enum names, and an id the manifest does not declare
prints TN_WEB_FRAME_UNKNOWN_FIELD once and answers NaN rather than a zero a
caller would read as a real measurement. A spec pins the three copies and digests
the table, so a renumbered field goes red.
The drift guard grouped copies by hash, so a listed file one kit edited dropped
out of the check; it now compares every shipped copy with the canonical one.
The facade's debugRequested and core's readLaunchFlag follow one rule, held
together by a parity spec, and bundlesAsked hears every naming seam again. The
web frame table pins the published (name, id) pairs instead of a digest that
could not tell an append from a renumber, and the generated header refuses an
unknown id before a renderer exists. Reverted the spell tier reader (a verbatim
upstream port), the invented TN_SHADOW_* environment seams, and a comment that
ignored the Unreal-look harness's own tone map copy.
…order

visual-gate hands repo-inventory the repository root, not the templates folder
(its spec was red); the spell tier reader is byte-identical to the base commit
again; the example's package.json gains one line instead of a re-sort; the
parity cases carry the answer the rule gives, so two copies agreeing on a wrong
answer still fail; and assertSupportedNodeVersion reads the floor from
engines.node, where a range shorter than the running version is not 'below'.
@jonit-dev jonit-dev added the prd:75% PRD phases in, acceptance open label Nov 7, 2026
… rewritten by a tool

PRD-833 phase 3. One hash per kit said a tree moved without saying why, so a
shared edit re-pinned sixteen rows and needed a paragraph of history to read.
Each kit now carries a 'shared' hash (agent-files, template-assets, core's
tables, the shared render sources and the generated AGENTS/CLAUDE routers) and
a 'kit' hash (the kit's own files); `pnpm scaffold:pin` rewrites both and names
which side moved. The classification is derived in __tests__/scaffold-tree.ts,
not listed, so a new synthesized file lands in the right bucket.

STARTER_PATHS, MINIMAL_RENDER_PATHS and PLATFORMER_PATHS are deleted: the two
hashes catch a missing or changed file and the allowlist catches a new kind of
file, which is the whole contract those lists carried.
@jonit-dev jonit-dev added prd:25% PRD phase 1 verified and removed prd:75% PRD phases in, acceptance open labels Nov 7, 2026
…ce, in core

PRD-833 phase 1. The seven render files every kit copied byte-for-byte
(environment measure, sample, cache and provenance, the backlight lane and its
controls) move to @threenative/core/render/environment, together with the
admission gate they already depended on: that gate was the one cross-boundary
import, twelve kits held their own copy of it, and it is the WebGL-fallback
predicate the phase also asked to unify. 84 copies are deleted and each kit's
quality.ts is a three-line re-export. Every moved export carries the
@Situation tags a public capability needs.

The charter checks were extended rather than bypassed: constraints.spec.ts
admits the folder with a tighter assertion that it originates no material,
light, colour or shader (only the sampling quad's material and the node twin of
the game's own), and check-core-boundary admits the mechanism names with the
same proof. looks.spec.ts and the RTS cost loader learned the one subpath.
@jonit-dev jonit-dev added prd:50% PRD half the phases verified and removed prd:25% PRD phase 1 verified labels Nov 7, 2026
PRD-833 phase 2, box 2. template-assets/patches/manifest.json lists each patch
once with its repository source; the prepack step (scripts/sync-patches.mjs)
copies exactly those into the tarball and prunes the rest, copyFrameworkPatches
resolves a kit's patchedDependencies through it, and framework-patches.spec.ts
holds every kit to it. The filename was spelled in src, in the prepack
one-liner and in sixteen kit manifests; a three-version bump needed all of them
edited by hand.
A single-line pin was longer than the formatter's width, so biome wrapped it
and the tool's reader stopped recognising its own table, which made every
commit alternate between a re-pin and a format. Pins are one hash per line now,
with the key quoted only when it is not a plain identifier, and the reader
accepts both forms so a formatter pass cannot break it.
@jonit-dev jonit-dev added prd:75% PRD phases in, acceptance open and removed prd:50% PRD half the phases verified labels Nov 7, 2026
md5sum over all sixteen kits names four vite variants, three tsconfig variants
and four main.ts variants, and the diff of each: minimal drops React and
Tailwind, starter and shooter add a second worker entry, the rain group differs
only by a missing comment, and adventure's Tab preventer is the one genuine
per-kit statement. The next attempt starts from that table instead of a survey.
PRD-838 phase 2, the dead-script half (the box said five; the report was
wrong about one). asset-pose-capture, realistic-ocean-budget,
realistic-ocean-capture and verify-fog-exposure are referenced by nothing but
the untangle report. release-native-local.mjs stays: release-native-local.ts
imports it at runtime because it ships untranspiled, and its own spec covers it.

The scaffold pin tool registers its temp-dir cleanup in the guard the repo keeps
for that, and the orphan-gate audit (which gate is superseded, which produces
cited evidence) is written into the PRD box that still owns the decision.
@jonit-dev jonit-dev added prd:25% PRD phase 1 verified and removed prd:75% PRD phases in, acceptance open labels Nov 7, 2026
check-capability-examples is a library the engine-mcp spec imports, so pnpm test
runs it; arm-census, asset-parity and playtest-pair are imported by their own
specs inside the root vitest include and produce reports the docs cite. The
suite is the gate, and pnpm budgets is for invariants rather than for writing
evidence, so the box that asked to wire or delete them is deleted by decision.
PRD-837 phase 2, box 1. Four subsystems asked the facade's own info table
whether the owned engine was behind it by spelling `"engine" in info\': game
startup (which decides the velocity and matrix-world passes), WorldEnvironment
twice (temporal AA and the SSGI cache fallback) and the bundle recorder. A
rename in the engine's info table would have left all four reporting the wrong
backend with nothing failing.

rendererFacts() is the one reader, and it ships only the two fields its callers
need: webglFallback already has an owner in the environment gate, and software
has no caller.
# Conflicts:
#	packages/runtime-native/tests/embedded-js.mjs
#	packages/runtime-native/tests/scheduler-yield.test.mjs
…(PRD-834 phase 3 box 1)

defineGame takes one `renderer` key: the project's portable config (`config.renderer`
from threenative.config.ts) beside the live renderer options. The retired `render` key
throws TN_GAME_RENDER_KEY_RENAMED and names the field mapping, so a game that still
passes it cannot silently lose its scale, sampling or Android overrides.

The 16 kit templates, the in-repo examples, the fixtures and the guides migrate to the
one key; the scaffold hashes are re-pinned. Core's own tests prove the throw at
defineGame time and exercise the merged key through the scale, antialias and projection
resolvers.
…gh its exports map (PRD-840)

`@threenative/core/render/environment` became a subpath when PRD-833 moved the module to
`src/render-environment.ts`. The native player bundles source and read the last path
segment as the file name, so every kit that imports that subpath failed its desktop build
with `Cannot read file: …/@threenative/core/src/environment.ts`.

coreSourcePath reads the package's own exports map: the published `./dist/<name>.js` is
written from `src/<name>.ts`. core-subpath-source.test.mjs walks every declared subpath
and fails on one that resolves to a file that is not shipped.
…the generated frame-op names, the config-key manifest

The shooter kit set both `renderer: config.renderer` and `renderer: { antialias: true }` after
the fold; it is one object now, with the kit's antialias default where the config leaves it unset.
`frame-op-stream.js` declares the 35 generated `OP_*` ids, so the naming rule does not apply to
that file (the names come from `frame-ops.json`), and `native-config-keys.json` is formatted.

pnpm lint: 0 errors, 2138 pre-existing warnings.
@jonit-dev jonit-dev added the release-proof Run the native-release proof matrix on this PR label Nov 8, 2026
# Conflicts:
#	docs/verification/native-coverage-2026-08-28.md
#	packages/create-threenative/__tests__/scaffold.spec.ts
… seven launch checks pass, web-ui-missing-bundle does not refuse
…n CTest

verify-native-contracts.mjs lists three invocations for
threenative-shim-bootstrap-test (default, --worker-rollback, --callback-retention) and the
coverage lane builds its execution list from the CTest inventory, so it failed with
'CTest registered 1 invocation(s), expected 3' and the native-coverage report could not be
regenerated — which is the red the budgets job reports as a stale digest.
…w contract targets

PRD-842 added shim-bootstrap, webgpu-wrapper-bag, image-decode-teardown, buffer-map-lifetime and
platform-policy as contract targets without claiming them in build-matrix.json, so
`pnpm budgets` fails on each one; the coverage report itself was stale for the same merge, and
regenerating it needed the two extra shim-bootstrap CTest registrations committed just before.
`pnpm budgets` → exit 0.
@jonit-dev jonit-dev added prd:75% PRD phases in, acceptance open and removed prd:50% PRD half the phases verified labels Nov 9, 2026
…n unknown double cast

`pnpm quality` counts a new `as unknown as` as a fatal suppression finding, and the review lane's
budgets job now reaches that step. The hook is still reached the same way; the carrier type says
what the cast means. `pnpm quality` → exit 0.
…e host tests register their temp dirs

Two unit contracts went red on the merge candidate and nowhere else:

- ablation-flags-never-ship: PRD-859's TN_PLAYER_PRIORITY was read with std::getenv
  in engine/player/run.cpp, around the flags.h list PRD-840 put there. Declared
  Read::Live beside PlayerVsync and read through flags::read.
- temp-dir-guard: embedded-script-enum, host-cli and host-window called mkdtempSync
  directly. They now use test-support/temp-dir.ts, which registers the cleanup the
  guard asks for.
build-ios-simulator only uploaded artifacts/ios after the verify step succeeded, so a
failed launch threw away the log that says why: variant-web-ui.log is written by the
throw itself. The evidence upload now also runs when an earlier step failed, and warns
instead of failing when an early failure left nothing to upload.
The desktop capture verifier assembled its failure from stderr, which is an empty
string rather than null when the runner says nothing, so every failed capture read
'runner failed: (empty stderr)' with no status and no pointer to
artifacts/native/starter-desktop.log.

PRD-840 AC-2 records the native half as still uncaptured; PRD-842 records the second
build-ios-simulator run's real first failure (the web-ui variant's main-frame
navigation neither finishing nor failing) and the evidence upload that now survives a
failed step.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

prd:75% PRD phases in, acceptance open release-proof Run the native-release proof matrix on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant