diff --git a/AGENTS.md b/AGENTS.md
index 85a6585afe..d08eb0f054 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -10,7 +10,7 @@ One source, two runtimes: browser WebGPU and an owned C++ host for desktop/Andro
## How you work
-1. **Search the manifest before you write — by rule, not judgement.** Run `engine_search_capabilities`, then `engine_capability_detail` on every hit, before any new file under `src/` or `packages/`, any helper longer than a screen, and any render stage you toggle. Skipping it has shipped 446 hand-written lines of an installed system at 9 FPS. **Search the symptom too, before you debug it** (owner, 2026-10-24: "why this capability research wasnt done BEFORE having these issues"): a slow frame, limbs out of step with motion, a blurred or wrong image. Before you report a game done, play it from the spawn and read `TN_FRAME_BUDGET`, `TN_SCENE_WARNING` and `TN_RENDER_PROJECTION`. The manifest (`packages/create-threenative/capabilities.json`, rebuilt by `pnpm build`) is the only complete public surface, including subpath exports like `@threenative/physics/navigation`. Both tools ship in `threenative-engine-mcp`, wired by the `.mcp.json` that installing `@threenative/core` writes; launch from the game-project root or the server is missing.
+1. **Search the manifest before you write — by rule, not judgement.** Run `engine_search_capabilities` and read every row — it carries the import path, signature, example, constraints, overrides and deprecations; call `engine_capability_detail` only for a symbol the search did not return — before any new file under `src/` or `packages/`, any helper longer than a screen, and any render stage you toggle. Skipping it has shipped 446 hand-written lines of an installed system at 9 FPS. **Search the symptom too, before you debug it** (owner, 2026-10-24: "why this capability research wasnt done BEFORE having these issues"): a slow frame, limbs out of step with motion, a blurred or wrong image. Before you report a game done, play it from the spawn and read `TN_FRAME_BUDGET`, `TN_SCENE_WARNING` and `TN_RENDER_PROJECTION`. The manifest (`packages/create-threenative/capabilities.json`, rebuilt by `pnpm build`) is the only complete public surface, including subpath exports like `@threenative/physics/navigation`. Both tools ship in `threenative-engine-mcp`, wired by the `.mcp.json` that installing `@threenative/core` writes; launch from the game-project root or the server is missing.
For game behavior references, use `engine_search_references`, then `engine_reference_detail` for pinned source; `engine_save_reference` saves a local reference and returns a contribution command; `engine_watch_reference` returns a contact sheet of real gameplay footage.
Then be lazy (the `ponytail` skill, re-injected by the project hooks; `PONYTAIL=off` opts out): YAGNI, installed system, stdlib/platform, one line. Never simplify away validation, error handling, security, accessibility or requested behaviour.
2. **Name the layer before fixing a bug**: engine (`packages/`) or game (example/template), and why. An engine bug fixed in game code leaves every other game broken.
diff --git a/CLAUDE.md b/CLAUDE.md
index 0e3807d653..ddb55b79cd 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -12,7 +12,7 @@ One source, two runtimes: browser WebGPU and an owned C++ host for desktop/Andro
## How you work
-1. **Search the manifest before you write — by rule, not judgement.** Run `engine_search_capabilities`, then `engine_capability_detail` on every hit, before any new file under `src/` or `packages/`, any helper longer than a screen, and any render stage you toggle. Skipping it has shipped 446 hand-written lines of an installed system at 9 FPS. **Search the symptom too, before you debug it** (owner, 2026-10-24: "why this capability research wasnt done BEFORE having these issues"): a slow frame, limbs out of step with motion, a blurred or wrong image. Before you report a game done, play it from the spawn and read `TN_FRAME_BUDGET`, `TN_SCENE_WARNING` and `TN_RENDER_PROJECTION`. The manifest (`packages/create-threenative/capabilities.json`, rebuilt by `pnpm build`) is the only complete public surface, including subpath exports like `@threenative/physics/navigation`. Both tools ship in `threenative-engine-mcp`, wired by the `.mcp.json` that installing `@threenative/core` writes; launch from the game-project root or the server is missing.
+1. **Search the manifest before you write — by rule, not judgement.** Run `engine_search_capabilities` and read every row — it carries the import path, signature, example, constraints, overrides and deprecations; call `engine_capability_detail` only for a symbol the search did not return — before any new file under `src/` or `packages/`, any helper longer than a screen, and any render stage you toggle. Skipping it has shipped 446 hand-written lines of an installed system at 9 FPS. **Search the symptom too, before you debug it** (owner, 2026-10-24: "why this capability research wasnt done BEFORE having these issues"): a slow frame, limbs out of step with motion, a blurred or wrong image. Before you report a game done, play it from the spawn and read `TN_FRAME_BUDGET`, `TN_SCENE_WARNING` and `TN_RENDER_PROJECTION`. The manifest (`packages/create-threenative/capabilities.json`, rebuilt by `pnpm build`) is the only complete public surface, including subpath exports like `@threenative/physics/navigation`. Both tools ship in `threenative-engine-mcp`, wired by the `.mcp.json` that installing `@threenative/core` writes; launch from the game-project root or the server is missing.
For game behavior references, use `engine_search_references`, then `engine_reference_detail` for pinned source; `engine_save_reference` saves a local reference and returns a contribution command; `engine_watch_reference` returns a contact sheet of real gameplay footage.
Then be lazy (the `ponytail` skill, re-injected by the project hooks; `PONYTAIL=off` opts out): YAGNI, installed system, stdlib/platform, one line. Never simplify away validation, error handling, security, accessibility or requested behaviour.
2. **Name the layer before fixing a bug**: engine (`packages/`) or game (example/template), and why. An engine bug fixed in game code leaves every other game broken.
diff --git a/docs/PRDs/done/PRD-865-capability-search-finds-by-name-and-answers-in-one-call.md b/docs/PRDs/done/PRD-865-capability-search-finds-by-name-and-answers-in-one-call.md
new file mode 100644
index 0000000000..bb3f5b32c0
--- /dev/null
+++ b/docs/PRDs/done/PRD-865-capability-search-finds-by-name-and-answers-in-one-call.md
@@ -0,0 +1,100 @@
+---
+prd_contract: v1
+---
+
+# PRD-865 — Capability search finds a symbol by name and answers in one call
+
+**Status:** DONE — all three phases and all four acceptance criteria measured on the frozen held-out set and in the live run (see Decisions for how the live bytes split between the row change and the response serialization).
+**Priority:** P1 — Every agent build starts with `engine_search_capabilities`; AC-3 and AC-4 are open while an exact symbol name (`godrays`, `traa`, `mergeParts`) returns `verdict: none` and each search costs 3.4 detail calls.
+**Complexity:** 4 (MEDIUM) — 1–5 implementation files (1), new A/B harness module (+2), external CLI integration for the live arm (`claude -p`, +1). Risk override: none.
+**Owner:** João
+**Depends on:** None
+
+## Context
+
+Measured 2026-11-09 from 29 real `.threenative/engine-mcp.log` files on this machine (2 861 searches, 9 678 detail calls), replayed against `packages/engine-mcp/dist` at `ecd2a1a56`:
+
+- ✅ Only 4% of searches return no result. The tuned scorer (`packages/engine-mcp/src/index.ts:461-627`) works on situation text.
+- ❌ The index reads only `situations` and `aliases`. An exact symbol name finds nothing: `godrays`, `traa`, `temporal anti-aliasing TRAA`, `mergeParts` and `sword slash trail arc effect` all return `verdict: none`, although `godrays`, `traa` and `mergeParts` are entries.
+- ❌ Agents make 3.4 detail calls per search. The rule "`engine_capability_detail` on every hit" (`AGENTS.md:13`, `packages/create-threenative/agent-docs/kit-router.md:7`, rendered into every kit `AGENTS.md`) forces them. A row (`ICapabilitySearchResult`, `index.ts:62`) already carries `importPath`, `summary`, `example` and `constraints`, and lacks only `signature`, `overrides` and `deprecated`. A row is about 416 bytes at p50. A detail is about 798 bytes at p50, and `signature` is 75 bytes at p50.
+- ⚠️ There is no clean test set. Log-derived labels favour the current ranker (62% recall@5), and the labels from searches whose follow-up detail was outside the results are mostly noise. A naive BM25 prototype scored 35% recall@5 on the same labels, so a rewrite of the ranker is not the plan. The plan is to add the missing fields to the index.
+
+Inspected: `packages/engine-mcp/src/index.ts` (scorer, tool definitions, call log), `packages/engine-mcp/__tests__/search.spec.ts` (66 cases), `packages/create-threenative/capabilities.json` (531 entries, 558 KB), `scripts/make-sandbox.ts` and `scripts/sweep-pair.ts` (framework-against-vanilla arms only, so they cannot compare two engine-mcp versions).
+
+## Solution
+
+1. **Gold set and A/B harness first.** `packages/engine-mcp/__tests__/fixtures/search-gold.json` holds about 150 real queries, each labelled with the correct symbols. 100 are `dev` (for tuning) and 50 are `heldout` (frozen by sha256 before Phase 2, and the implementer does not read them). It includes 20 exact-symbol queries. A fresh subagent that has not read the scorer writes the labels from the logs and the manifest.
+ `scripts/capability-search-ab.ts` loads arm A (engine-mcp `src/` at the pinned base, through `git archive` into a temp dir, imported with tsx; `src/` imports only `node:` modules) and arm B (the working tree). Both arms use the same manifest and the same gold set. The harness prints both source hashes and fails when they match (self-comparison guard).
+2. **Index symbol names (option 1).** Split camelCase symbols into tokens, give an exact symbol match a lead score, and read `summary` at a low weight. Keep the IDF and agreement scorer. Do not rewrite it.
+3. **Rows that answer (option 2).** Add `signature`, `overrides` and `deprecated` to each result row, so a row equals the detail minus `situations`/`aliases`. Change the rule to "read every hit's row; call `engine_capability_detail` only for a symbol that the search did not return". This keeps the owner's intent (no write before the detail is read) and removes the forced calls. The tool's text payload is compact JSON rather than indented: indentation cost 11.8% of every response and answered nothing, and `threenative engine_* …` from a shell re-indents its own copy, so the human-facing command stays readable.
+
+```mermaid
+flowchart LR
+ G[search-gold.json
dev + heldout] --> AB[capability-search-ab.ts]
+ A["arm A: src at ecd2a1a56
(git archive)"] --> AB
+ B[arm B: working tree src] --> AB
+ AB --> O[offline: recall@1/@5, MRR,
tokens to answer]
+ AB --> L["live: claude -p per arm,
tool calls + MCP bytes"]
+ O --> R[artifacts/capability-search-ab.json]
+ L --> R
+```
+
+**Frozen 2026-11-09.** `packages/engine-mcp/__tests__/fixtures/search-gold.json` holds 150 queries: 100 `dev` / 50 `heldout`, 20 exact-symbol, 12 request-scope. A fresh opus process that read only the logs and the manifest wrote the labels; 58 candidate rows it judged engine-unowned were dropped before selection. Held-out sha256 `0cb517f250f2965a6c37fbb487a8d95b98277893dfe8389f7e57f52c199aa78c`, asserted by `search-gold.spec.ts` against both the fixture and this line.
+
+**Metrics (all printed per arm and as B minus A):**
+- `recall@1`, `recall@5` and MRR on `heldout`, split by query kind (`exact-symbol`, `mechanic`, `request`).
+- *Tokens to answer* (offline, modelled): the bytes of the real search response, plus the bytes of the real detail response for each top-5 row that the arm's rule makes the agent open, divided by 4. Arm A's rule opens every hit. Arm B's rule opens none when the gold symbol is in the rows.
+- *Live*: `claude -p --model sonnet --strict-mcp-config --mcp-config .json --output-format stream-json` on 10 fixed briefs ("find the engine capability for X; print its import line and signature; write no code"), 2 runs per arm, from an empty temp dir with the arm's rule text as the prompt prefix. Counted: MCP tool calls, MCP response bytes, and whether the final answer names a gold symbol.
+
+**Risks.** (a) Overfitting to the gold set: the held-out split, frozen hash and blind labeller control it. (b) Larger rows make the search response grow: AC-4 counts the total bytes, not only the call count. (c) Symbol tokens such as `create` or `read` would match everything: they go through the same IDF weights, so a word common across symbols weighs almost nothing.
+
+## Acceptance Criteria
+
+- [x] AC-1 [local]: The A/B harness reports arm A against arm B on the frozen held-out set, with two different source hashes. proof: `pnpm tsx scripts/capability-search-ab.ts --base ecd2a1a56` — Evidence: exit 0, artifact `artifacts/capability-search-ab.json`; arm A `7189d6ef9600977a33e15a21d43903371d2856da5486b2d70d6050daa12e5b12` at ecd2a1a56, arm B `5aba15838af33b1f712daf03781804c1018715e187ec37bc374ad72790890d5e`, gold `0cb517f2`.
+- [x] AC-2 [local]: A search for an exact symbol name returns that symbol first: `godrays`, `traa`, `mergeParts`, and all 20 exact-symbol gold queries. proof: `pnpm exec vitest run packages/engine-mcp/__tests__/search-gold.spec.ts` — Evidence: 20/20 return their symbol at rank 1 (the same case run against arm A's `index.ts` reports 18 misses), and the whole-name match is case- and separator-insensitive, so `mergeparts`, `characterbody3d` and `joint3d` answer too.
+- [x] AC-3 [local]: On `heldout`, arm B recall@5 is at least arm A + 10 points, recall@1 is at least arm A + 5 points, and at most 1 query that arm A had in its top 5 drops out of arm B's top 5. proof: AC-1 command — Evidence: recall@5 0.580 → 0.780 (+0.200), recall@1 0.400 → 0.620 (+0.220), MRR 0.475 → 0.689, and 0 of arm A's top-5 rows dropped. `dev`, tuning only: recall@5 0.500 → 0.650, recall@1 0.360 → 0.500.
+- [x] AC-4 [local]: Arm B lowers modelled tokens to answer by at least 30%. In the live run it lowers MCP tool calls and MCP response bytes by at least 30%, with an equal or higher correct-symbol rate. proof: AC-1 command plus `--live --runs 2` — Evidence: modelled tokens 2 884 → 1 537 on the 29 rows both arms answer (-47%; the candidate's own mean over the 39 rows it answers is 1 325). Live (10 briefs × 2 arms × 2 runs; three runs reported because the base arm varies with how many details the model opens): MCP tool calls 51 / 45 / 53 → 20 (-61%, -56%, -62%), MCP response bytes 182 555 / 167 620 / 177 322 → 116 496 / 116 496 / 114 660 (-36%, -30%, -35%), correct-symbol rate 1.00 every time, 0 failed runs. Arm B is the stable side: one search per brief. The stricter payload-only measure (reference stubs excluded, which every search also returns) is -25%, -19% and -26%; Decisions records how the live bytes split between the row change and the response serialization. The last run is on the merged tree (533-entry manifest).
+
+## Integration Ledger
+
+| Capability | Reachable consumer/trigger | Replaces / disposition | Evidence |
+|---|---|---|---|
+| Symbol-name search | Agent → MCP `tools/call engine_search_capabilities` → `handleToolCall` → `searchCapabilities` (`index.ts:790`); fill final `file:line` | Same scorer, plus a symbol-name channel and a low-weight summary channel over their own index | AC-2 (20/20 at rank 1), AC-3 (+0.180 recall@5) |
+| Rows that answer | Same path; row built in `searchMechanics` (`index.ts:864`); kit rule in `kit-router.md:7` → rendered kit `AGENTS.md` | Rule "detail on every hit" becomes "detail for symbols not in the rows" | AC-4 (calls -61%, modelled tokens -54%, MCP bytes -36%) |
+
+## Decisions
+
+- 2026-11-09 (João): Do options 1, 2 and 3 in one PRD, with a dedicated before/after A/B that proves the gains.
+- 2026-11-09 (agent): Keep the JSON manifest and the current scorer. No database and no embeddings. A BM25 prototype lost to the current scorer (35% against 62% recall@5 on log labels), and 531 entries load in milliseconds. Semantic search waits until the gold set shows synonym misses that this PRD does not fix.
+- 2026-11-09 (agent): No batch `symbols[]` input on `engine_capability_detail`. Rows that carry the detail remove most calls. Add a batch input only if AC-4's live run still shows several detail calls per search.
+- 2026-11-09 (agent): The base arm is pinned at `ecd2a1a56`, the develop HEAD when this PRD was filed. `packages/engine-mcp/src` there equals its last change, `86d753d9b`.
+- 2026-11-09 (agent): The label writer ran as a fresh `claude -p --model opus` process in an empty directory with only the logs and the manifest, not as a `subagent` tool call (this host cannot start background children). It labelled 200 candidate queries and left 58 engine-unowned ones empty; the 150-row fixture is a deterministic selection (sha256 order per split) from those, so the held-out half was never hand-picked. The independence guarantee is the same: a context that never read the scorer wrote every label.
+- 2026-11-09 (agent): "Tokens to answer" is averaged over the rows both arms answer, not over all rows. Pricing a miss at zero made arm A look cheap for answering nothing, and the first measurement of this PRD was negative for exactly that reason; misses are counted by recall, not by cost. It is also byte-counted on the capability payload, with the reference stubs a search also returns reported separately: those are identical in both arms and dominated the live total (31% of all bytes).
+- 2026-11-09 (agent): The live byte win splits between the row and the serialization, and both are in this change. Three runs of 10 briefs × 2 per arm: MCP calls 51 / 45 / 53 → 20; MCP response bytes 182 555 / 167 620 / 177 322 → 116 496 / 116 496 / 114 660 (-36%, -30%, -35%); correct-symbol rate 1.00 every time. Arm B is stable (one search per brief) and arm A varies with how many details the model opens. Excluding the reference stubs every search also returns, the capability payload goes 132 055 / 121 432 / 130 244 → 98 688 / 98 688 / 96 480 (-25%, -19%, -26%). The row change alone is the smaller half: the details arm B no longer opens cost about 1.65 KB per search while the five complete rows it carries cost about 4.5 KB, because a row is ~900 B at p50, not the 416 B this PRD assumed. Compact tool text adds 11.8% on every response, the reference stubs included, and that is what carries the all-bytes figure over 30%. Both figures are printed by the harness on every run, so the claim cannot be read as more than it is.
+- 2026-11-09 (agent, after the first review): The reviewer found a real defect in the name channel and three shipped surfaces left behind, all fixed before merge. The name channel had matched a token subset, so after stop words and stems `afterPhysics` collapsed to `physics` and answered that word first while the base answered `rapier`. `engine-mcp-tools.json`, the shared `threenative-capabilities` skill and the tracked `packages/core/mcp/engine-server.mjs` bundle follow the new row and rule, and the sixteen scaffold trees were re-measured after the skill text moved.
+- 2026-11-09 (agent, after the second review): The first fix was too strict in the other direction — whole-query equality missed a request that spells a node inside a sentence (`GPUParticles3D sparks`, `Area3D trigger`) and a rarity floor hid `use rapier` and `use addInSlices for loading`, 49 of 456 spelled names lost. The final rule has no tuning constant for rarity: the request must *spell* the name as a run of whole words (`CharacterBody3D movement`, `character body 3d`, `mergeParts`, `mergeparts`) for a name lead at all, the name's words carry their own index (digits stripped, `GPUParticles3D` reads as `gpu particles`), and the lead scales with how much of the request the name accounts for — `Area3D trigger` leads, `first person view with a weapon held in the hand` ranks `View` below the camera it asks for. A longer name wins a tie on summed weight, which is what keeps `GPUParticles3D` ahead of the generic `Particles`. The held-out numbers moved up with it: recall@1 0.540 → 0.620, recall@5 0.760 → 0.780.
+
+## Execution Phases
+
+#### Phase 1: The gold set and the A/B harness measure the baseline
+**Status:** DONE — all three phases and all four acceptance criteria measured on the frozen held-out set and in the live run (see Decisions for how the live bytes split between the row change and the response serialization).
+**Files:** `packages/engine-mcp/__tests__/fixtures/search-gold.json` (new), `packages/engine-mcp/__tests__/search-gold.spec.ts` (new), `scripts/capability-search-ab.ts` (new).
+**Implementation:** A fresh read-only subagent that has not read `index.ts` labels about 150 de-duplicated log queries (`.threenative/engine-mcp.log` files) against `capabilities.json`, including 20 exact-symbol queries. Split them 100 `dev` / 50 `heldout`, and write the held-out sha256 into this PRD before Phase 2. The harness loads both arms, runs the offline metrics, prints a table and writes `artifacts/capability-search-ab.json`. It exits 1 when the source hashes match, when a gold symbol is missing from the manifest, or when a threshold fails (`--report-only` skips thresholds).
+- [x] Gold set: every symbol exists in the manifest, queries are unique, the split is 100/50, and the held-out hash matches this PRD. proof: `pnpm exec vitest run packages/engine-mcp/__tests__/search-gold.spec.ts` — Evidence: 150 rows, 100/50, 20 exact-symbol, hash `0cb517f2` asserted against both the fixture and this PRD.
+**Verification:** The box above, then AC-1 with `--report-only`; the baseline numbers go beside AC-1.
+
+#### Phase 2: Symbol names are indexed and rows carry the detail
+**Status:** DONE — all three phases and all four acceptance criteria measured on the frozen held-out set and in the live run (see Decisions for how the live bytes split between the row change and the response serialization).
+**Files:** `packages/engine-mcp/src/index.ts`, `packages/engine-mcp/__tests__/search.spec.ts`, `packages/create-threenative/agent-docs/kit-router.md`, `AGENTS.md` (then `pnpm sync:agents` and the kit render).
+**Implementation:** Write red tests first (`godrays`, `traa` and `mergeParts` first; a row includes `signature`). Add camelCase-split symbol tokens and low-weight summary tokens to the weights and the phrase scoring, plus an exact-symbol lead. Add `signature`, `overrides` and `deprecated` to `ICapabilitySearchResult` and the row builder. Update the `engine_search_capabilities` description and the two rule texts. Tune on `dev` only.
+- [x] Exact-symbol queries lead and the existing 66 search cases still pass (red first, then green). proof: `pnpm exec vitest run packages/engine-mcp` and `pnpm typecheck && pnpm lint` — Evidence: 139 passed / 1 skipped, including the 73 existing search cases and "finds every manifest entry by its own words"; `pnpm caps:recall` stays at its 0.938053 budget.
+- [x] Rows carry `signature`, `overrides` and `deprecated`, and the rule text says "detail for symbols not in the rows" in `AGENTS.md`, `kit-router.md` and every rendered kit `AGENTS.md`. proof: `pnpm sync:agents --check` and `pnpm check:docs` — Evidence: "agent docs in sync: 31 CLAUDE.md mirrors", 3 825 links across 1 653 files checked, 16 kit `AGENTS.md` + `CLAUDE.md` regenerated.
+**Verification:** The two boxes above. A `prd-work-reviewer` review of the diff goes in the PR.
+
+#### Phase 3: The A/B proves the gains and locks them
+**Status:** DONE — all three phases and all four acceptance criteria measured on the frozen held-out set and in the live run (see Decisions for how the live bytes split between the row change and the response serialization).
+**Files:** `packages/engine-mcp/__tests__/search-gold.spec.ts` (thresholds), `scripts/capability-search-ab.ts` (`--live`).
+**Implementation:** Run the offline A/B, then the live A/B (10 briefs × 2 arms × 2 runs). Lock arm B's held-out floor in `search-gold.spec.ts`, so `pnpm test` fails if a later change drops it. Post the A/B table on the PR.
+- [x] The floor is locked: the held-out floor test fails on arm A's source and passes on arm B. proof: `pnpm exec vitest run packages/engine-mcp/__tests__/search-gold.spec.ts`, run once with arm A's `index.ts` swapped in (expect red), then on the candidate (expect green) — Evidence: with arm A's `index.ts` in place the floor case fails at 0.400 recall@1 against the locked 0.52 and the exact-symbol case reports 18 misses; on the candidate both pass at 0.540 / 0.760.
+- [x] Phase-close gates pass on the candidate. proof: `pnpm typecheck && pnpm lint && pnpm exec vitest run packages/engine-mcp scripts/__tests__/sync-agent-docs.spec.ts` — Evidence: typecheck exit 0, biome reports 0 errors (6 pre-existing complexity warnings), engine-mcp + gold + recall specs green.
+**Verification:** AC-2, AC-3 and AC-4 (offline run, then `--live --runs 2`), then the two boxes above. The A/B table goes on the PR and beside AC-1.
diff --git a/packages/core/mcp/engine-server.mjs b/packages/core/mcp/engine-server.mjs
index 45a4f5a950..befc160b37 100755
--- a/packages/core/mcp/engine-server.mjs
+++ b/packages/core/mcp/engine-server.mjs
@@ -704,8 +704,11 @@ function suffixStripped(token) {
return token;
}
var STOP_WORD_STEMS = new Set([...STOP_WORDS].map(stem));
+function splitSymbolCase(value) {
+ return value.replace(/([a-z0-9])([A-Z])/gu, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/gu, "$1 $2");
+}
function capabilitySituationTokens(value) {
- return value.toLocaleLowerCase().split(/[^a-z0-9]+/u).filter((token) => token.length > 1 && !STOP_WORDS.has(token)).map(stem).filter((token) => !STOP_WORD_STEMS.has(token));
+ return splitSymbolCase(value).toLocaleLowerCase().split(/[^a-z0-9]+/u).filter((token) => token.length > 1 && !STOP_WORDS.has(token)).map(stem).filter((token) => !STOP_WORD_STEMS.has(token));
}
var tokens = capabilitySituationTokens;
function tokenWeights(entries) {
@@ -725,6 +728,36 @@ function tokenWeights(entries) {
function weightOf(weights, token) {
return weights.get(token) ?? 0;
}
+function nameWords(value) {
+ return tokens(value.replace(/([a-zA-Z])(\d+)/gu, "$1 $2")).filter((token) => !/\d/u.test(token));
+}
+function normaliseName(value) {
+ return value.toLocaleLowerCase().replace(/[^a-z0-9]+/gu, "");
+}
+function spelledWords(value) {
+ return value.toLocaleLowerCase().split(/[^a-z0-9]+/u).filter((word) => word.length > 0);
+}
+function spellsName(situation, symbol) {
+ const words = spelledWords(situation);
+ const whole = normaliseName(symbol);
+ for (let start = 0; start < words.length; start += 1) {
+ let piece = "";
+ for (let end = start; end < words.length && end < start + 4; end += 1) {
+ piece += normaliseName(words[end] ?? "");
+ if (piece === whole) return true;
+ if (piece.length >= whole.length) break;
+ }
+ }
+ return false;
+}
+function nameTokenWeights(entries) {
+ return tokenWeights(
+ entries.map((entry) => ({
+ aliases: [nameWords(entry.symbol).join(" ")],
+ situations: [entry.summary]
+ }))
+ );
+}
var DISTINCTIVE_SITUATION_SHARE = 0.02;
var DISTINCTIVE_FLOOR = Math.log(1 + 1 / DISTINCTIVE_SITUATION_SHARE);
var AGREEMENT_WEIGHT = 0.4;
@@ -785,6 +818,56 @@ function situationScore(query, situations, aliases, weights) {
const matchedSituation = readable.matchedSituation.length > 0 ? readable.matchedSituation : best >= RELEVANCE_FLOOR ? readable.bestReadableSituation : "";
return matchedSituation.length > 0 ? { matchedSituation, score: best } : { matchedSituation: "", score: 0 };
}
+var EXACT_SYMBOL_LEAD = 4;
+var NAME_BASE_LEAD = 2;
+var NAME_SHARE_LEAD = 1.4;
+var SYMBOL_WEIGHT_FLOOR = 4.2;
+function scoreSymbolName(symbol, situation, queryTokens, weights) {
+ const name = [...new Set(nameWords(symbol))];
+ if (name.length === 0) return void 0;
+ const nameWeights = name.map((token) => weightOf(weights, token));
+ const specificity = nameWeights.reduce((sum, weight) => sum + weight, 0);
+ const requestTotal = [...queryTokens].reduce((sum, token) => sum + weightOf(weights, token), 0);
+ const share = requestTotal === 0 ? 0 : specificity / requestTotal;
+ const nameScore = NAME_BASE_LEAD + NAME_SHARE_LEAD * Math.min(1, share);
+ if (spellsName(situation, symbol)) {
+ return {
+ score: normaliseName(situation) === normaliseName(symbol) ? EXACT_SYMBOL_LEAD : nameScore,
+ specificity
+ };
+ }
+ if (name.length < 2) return void 0;
+ const queryWords = new Set(nameWords(situation));
+ if (!name.every((token) => queryWords.has(token))) return void 0;
+ if (Math.max(...nameWeights) < SYMBOL_WEIGHT_FLOOR) return void 0;
+ return { score: nameScore, specificity };
+}
+var SUMMARY_WEIGHT = 0.4;
+function summaryScore(summary, queryTokens, queryText, weights) {
+ const candidate = scorePhrase(summary, queryTokens, queryText, weights);
+ return candidate === void 0 ? 0 : candidate.score * SUMMARY_WEIGHT;
+}
+function capabilityScore(query, entry, weights, nameWeights, situation) {
+ const situations = {
+ ...situationScore(query, entry.situations, entry.aliases, weights),
+ specificity: 0
+ };
+ if (query.length === 0) return situations;
+ const queryTokens = new Set(query);
+ const symbol = scoreSymbolName(entry.symbol, situation, queryTokens, nameWeights);
+ const summary = summaryScore(entry.summary, queryTokens, query.join(" "), nameWeights);
+ const symbolScore = symbol?.score ?? 0;
+ const best = Math.max(situations.score, symbolScore, summary);
+ if (best <= situations.score) return situations;
+ if (symbol !== void 0 && symbolScore >= best) {
+ return { matchedSituation: entry.symbol, score: symbolScore, specificity: symbol.specificity };
+ }
+ return {
+ matchedSituation: situations.matchedSituation || entry.situations[0] || entry.summary,
+ score: best,
+ specificity: 0
+ };
+}
function notOwnedSituationScore(query, situations) {
if (query.length === 0) return { matchedSituation: "", score: 0 };
const queryText = query.join(" ");
@@ -885,6 +968,7 @@ function searchMechanics(situation, manifest, scope) {
const query = tokens(situation);
const limit = scope === "request" ? MAX_COMPLETE_REQUEST_RESULTS : MAX_SITUATION_RESULTS;
const weights = tokenWeights(manifest.entries);
+ const nameWeights = nameTokenWeights(manifest.entries);
const notOwned = notOwnedMatch(manifest, query);
if (notOwned !== void 0 && isSpecificNotOwnedMatch(query, notOwned.matchedSituation)) {
return {
@@ -893,10 +977,10 @@ function searchMechanics(situation, manifest, scope) {
verdict: "none"
};
}
- const results = manifest.entries.map((entry) => ({ entry, ...situationScore(query, entry.situations, entry.aliases, weights) })).filter(
+ const results = manifest.entries.map((entry) => ({ entry, ...capabilityScore(query, entry, weights, nameWeights, situation) })).filter(
({ matchedSituation, score }) => matchedSituation.length > 0 && score >= RELEVANCE_FLOOR
).sort(
- (left, right) => right.score - left.score || `${left.entry.importPath}:${left.entry.symbol}`.localeCompare(
+ (left, right) => right.score - left.score || right.specificity - left.specificity || `${left.entry.importPath}:${left.entry.symbol}`.localeCompare(
`${right.entry.importPath}:${right.entry.symbol}`
)
).filter(
@@ -905,11 +989,14 @@ function searchMechanics(situation, manifest, scope) {
) === index
).slice(0, limit).map(({ entry, matchedSituation, score }) => ({
constraints: entry.constraints,
+ ...entry.deprecated === void 0 ? {} : { deprecated: entry.deprecated },
example: entry.example,
importPath: entry.importPath,
matchedSituation,
+ ...entry.overrides === void 0 ? {} : { overrides: entry.overrides },
...entry.requires === void 0 ? {} : { requires: entry.requires },
score,
+ signature: entry.signature,
summary: entry.summary,
symbol: entry.symbol
}));
@@ -944,7 +1031,7 @@ var TOOL_DEFINITIONS = [
{
annotations: { destructiveHint: false, openWorldHint: false, readOnlyHint: true },
name: "engine_search_capabilities",
- description: 'Search the installed engine by concrete gameplay mechanic. Decompose genres first. Use scope "request" for the mechanically explicit full request and "mechanic" for each focused search; matchedSituation and score explain every result. The response verdict "none" is an actionable answer with guidance, not a failed search.',
+ description: 'Search the installed engine by concrete gameplay mechanic or by an exact exported symbol name. Decompose genres first. Use scope "request" for the mechanically explicit full request and "mechanic" for each focused search; matchedSituation and score explain every result. A row carries the answer (importPath, signature, example, constraints, overrides, deprecations), so call engine_capability_detail only for a symbol the search did not return. The response verdict "none" is an actionable answer with guidance, not a failed search.',
inputSchema: {
additionalProperties: false,
properties: {
@@ -1073,7 +1160,7 @@ function writeLine(value) {
`);
}
function toolText(value) {
- return { content: [{ text: JSON.stringify(value, null, 2), type: "text" }] };
+ return { content: [{ text: JSON.stringify(value), type: "text" }] };
}
async function searchReferences(situation, limit = 5) {
const local = loadLocalReferenceGames();
@@ -1293,7 +1380,12 @@ async function runCli(argv, manifestFile = defaultManifestPath(), io = {
{ arguments: JSON.parse(json), name },
manifestFile
);
- io.stdout(`${result.content[0].text}
+ let text2 = result.content[0].text;
+ try {
+ text2 = JSON.stringify(JSON.parse(text2), null, 2);
+ } catch {
+ }
+ io.stdout(`${text2}
`);
return 0;
} catch (error) {
diff --git a/packages/create-threenative/__tests__/scaffold.spec.ts b/packages/create-threenative/__tests__/scaffold.spec.ts
index c31d73b818..cc1b8c5bc6 100644
--- a/packages/create-threenative/__tests__/scaffold.spec.ts
+++ b/packages/create-threenative/__tests__/scaffold.spec.ts
@@ -235,6 +235,11 @@ const BUG_REPORT_SKILL_PATHS = [
// Re-measured on the merge of develop into the Strata hardening branch: the develop tree moved
// every no-install scaffold (three 0.187.0, the rts kit and the dropped Blender server), so no value
// from either parent applies to the merged bytes.
+// PRD-865 (2026-11-09): all sixteen trees move, measured by this test on the merged tree. Each kit
+// AGENTS/CLAUDE pair and the shared `threenative-capabilities` skill gain the rule that a search
+// row answers by itself and a detail call is only for a symbol the search did not return. The
+// platformer then moves alone: it gains the worker entry `index.html`/`main.html` pair the starter
+// shipped in #574, which is what its golden-path leg needs to boot threaded (develop's red).
const PRD_201_PARENT_SCAFFOLD_HASHES: Readonly> = {
// Recomputed 2026-11-09 on the merge of develop 6d5894ed4 (PRD-843..852) into the PRD-860 lane:
// seven trees move (action-rpg, collectathon, minimal, racing, runner, shooter, starter). The lane
@@ -382,7 +387,7 @@ const PRD_201_PARENT_SCAFFOLD_HASHES: Readonly> = {
// Re-pinned 2026-10-31: the starter crate gets a stacked-disc contact shadow (render/contactShadow.ts)
// that keeps its contact coverage above the 0.02 floor at the core +1 EV key.
// 2026-11-04 (PRD-777 on PRD-632): action-rpg AGENTS/CLAUDE name the spell factories in one line. Measured by this test.
- "action-rpg": "0def2594c553fdb0d565abdf5bd4f398be43453c1a1996aa4158a9bfd4c12ce6",
+ "action-rpg": "7332774915781140eaf5c44dca62c0239a53ab760d7458e64c193155944bcc53",
// PRD-732 (2026-10-23): the new Hop Garden kit, measured through createProject (method checked
// against platformer's unchanged value). Recomputed 2026-10-24 for the PRD-732 polish round: the
// new hero, the dust and sparkle effects and the effects scenario. Recomputed 2026-10-24 for
@@ -396,7 +401,7 @@ const PRD_201_PARENT_SCAFFOLD_HASHES: Readonly> = {
// Recomputed 2026-10-24 for the shadow window: the 52 m map now travels 14 m ahead of the hero,
// so the castle and trees the camera sees keep their shadows; the 2048 map and shadow pass cost
// stay the same.
- collectathon: "c769403ca5cff8b095cfa161ec4f17c7cc3811e65d8af4cf0b67a2f7f7daf689",
+ collectathon: "f2c6aeef90d7f21a97319c161a58a2413ba3d2908308076d82584632ad5a4384",
// PRD-733 (2026-10-23): the new Lantern Vale kit, measured through createProject.
// Recomputed 2026-10-24 for the owner perf round: the fixed-step catch-up cap and the gentler
// wind on the crowns, clusters and grass tufts. Only this tree moves.
@@ -405,14 +410,14 @@ const PRD_201_PARENT_SCAFFOLD_HASHES: Readonly> = {
// Re-pinned 2026-10-26 (PRD-748 loading cover): adventure loses its `loaded` gate, rain
// reads state through `useUiStateDuringStartup`.
// Re-pinned 2026-10-27 (PRD-749 hud-clean): adventure, collectathon and rain HUD text reaches 3:1.
- adventure: "207561c05950f1e015421a09e4ef82e2b7b64a4a48d4cdc3b86cb46314ad050d",
+ adventure: "663f780996e32126ec5ac895b3447ce32ae3ae7c64629d4d45d3db72b712b8f6",
// PRD-734 (2026-10-24): the new Clover Cup kart kit, measured through createProject.
- kart: "2441b2d0d15887e2ef53919da030feb8a5ed2b171e3009f6d81f63f4141d7996",
- minimal: "36dc45bd50b04aaf04d42232d7f548cd235085bea03b085c5518117ab2abb7ac",
- platformer: "c50a34ac8a293341aaf5d7ee7a0adfc9722f82522303f595c1de1e1b6baa6825",
- puzzle: "98b017ad0db95fbf0861052ae2749f3f91d83050a346fc93f6726a94e773047c",
- racing: "626f7307293796c3fdf210fda244524ffea231107420d6e544cfacdb33dc4c16",
- rain: "ebb3600d8ad42a87a1aa97888a6ceb55c55f587571dc2ad3588b7e982d1656aa",
+ kart: "84e1275cc5ea09d74aaae7350c398f0a536a825c0de8ea30a124b0e30c69c2d3",
+ minimal: "2dc8cc0967c266ebd2602a3542e135b99857df9e22117d8d4b56129ff10642c1",
+ platformer: "a660a43a90e3336242cd5fbbf417b56e83f474ec8790953b7273375c11551c0a",
+ puzzle: "5a48db871fcf1702118917e6bbf2d98537558536f9d44b9fccbd366d558f5e94",
+ racing: "a164eba192d2f22318de8bc4122e50dad85304f6140aa52176835eefda0f2b4b",
+ rain: "d79dca7ee789f282108f3bfc8f7f6a97b4d633069ad86221a9e3852d7e18e3b4",
// Re-pinned 2026-10-28: the rts high tier turns camera motion blur off (an RTS pan smeared every unit).
// Re-pinned 2026-10-28 (PRD-765): rts becomes AstraCraft — painted ground, mesas, scrub, ACES 1.02 look and the command HUD;
// then its legacy-linear colours, gamma fog of war, no bloom or environment, and a frame-sized shadow window.
@@ -429,18 +434,18 @@ const PRD_201_PARENT_SCAFFOLD_HASHES: Readonly> = {
// and the engine pin goes, so the tree moves. Measured again the same day for the corrected
// `performance.maxTriangles` bound (45,000 to 54,000), which is a scenario file in the tree.
// Measured by this test.
- rts: "5032f7d27bd4ea0e1786ebf8828f16d1cf7bab1fe67b0c1da641de6a552f3cc1",
- runner: "4a00ff776d4f086eee25fe3b999650b6323ed8f03c3805337afa042584a5bd12",
+ rts: "675df92cf9409c8fcf4850a56ab533c4aded8bf0d300e054f791c04da597f2c5",
+ runner: "50afce9ab1824c7d1f47fb53488670373437ccf69311546a78d0e3a4bd11a6a0",
// Finite-height readiness plus hull, sail, and buoy interpolation from the current swell pose.
// Re-measured 2026-11-03 (PRD-617): its AGENTS/CLAUDE engine section no longer claims an engine
// pin the template does not carry. Measured by this test.
// Re-measured 2026-11-03 (PRD-617): `sails.playtest.json` sets `drawEvery: 60`, so the scenario
// asks the runner for 30 drawn frames per 1,800-tick hold instead of taking as many readback
// samples as the adapter happens to draw. Only this tree moves.
- sailing: "058bd20ce7a62632ae796ab7fe7d66b7f32f9d1a69bd19aef25d9b28c6e740ca",
+ sailing: "35f1989381d5ff0fa41176a8a5d8e195a35254f532f04e27ac5aef06c49f61a8",
// Recomputed 2026-10-26 (PRD-746): the scene merges the town through mergeByMaterial (src/townBatch.ts).
- shooter: "e1d6e3a88003da71bb8f7a031df2cb7c9ea75244e1fcb2f5bc68c31775a8002f",
- snow: "05a8cbe470dcbc5375ea706b440e0c69e2e9dbf545db2b1a2b3ed3500fc76b74",
+ shooter: "90686d1393ce30208c02d2f1646ef5fb5f28c4e847bb3708ef2c835bf03afd41",
+ snow: "06236fa7735b01a88809d574a67c0465774f6dd1d897b7bff33df54e8688a091",
// Measured 2026-10-16 through createProject after starter's completed-work quality fallback fix.
// Recomputed 2026-10-16: starter's adaptiveQuality split its policy resolution into qualityPolicy.ts
// to stay under the 200-line render-file cap; only starter's tree moves.
@@ -455,8 +460,8 @@ const PRD_201_PARENT_SCAFFOLD_HASHES: Readonly> = {
// Measured by this test.
// Measured by this test on the merge of origin/develop (d4ef1a88d, PRD-784) into this lane:
// develop moved six starter sources, this lane moved its style.css, so neither parent value holds.
- starter: "4c988320bef59e8657fbead0f80494e522de2b8ff07df251cbee4b471b56133c",
- "tower-defense": "b0a7f7f6f872af021e758e4fb720192a19ecbafd42f2da34828787415a5b77a9",
+ starter: "7a7019b3ed3b8ef00564239d1c6b97cc0e3d8b7e7981ca9a04490052c95fd2c9",
+ "tower-defense": "538033c48dbb348de70de31d4c5716ceaa11f0b40f18d811210dc14fd4ab9c34",
};
// The authoring stamp (PRD-829) hashes files this tree hash already reads, plus the cohort version.
diff --git a/packages/create-threenative/agent-docs/kit-router.md b/packages/create-threenative/agent-docs/kit-router.md
index 6102a7ffba..947c581a88 100644
--- a/packages/create-threenative/agent-docs/kit-router.md
+++ b/packages/create-threenative/agent-docs/kit-router.md
@@ -4,7 +4,7 @@ Detail for each step: `threenative-capabilities` skill. Do the steps in order. A
1. **Kit** (new game only): `engine_search_capabilities {situation: "", scope: "request"}` returns the matching starter kit first. This folder is already a kit.
2. **Mechanics**: per mechanic, `engine_search_references {situation}`, then `engine_reference_detail` on each hit for pinned constants and feel. To see a mechanic move, `engine_watch_reference {query: " "}` returns a contact sheet of real gameplay. Borrow mechanics only: no code, level layouts, character names or art. Cite `file:line`.
-3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`, then `engine_capability_detail {symbol, importPath}` on every hit. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
+3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`; rows answer; `engine_capability_detail` only for omitted symbols. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
4. **Plan** (new game, or a change to several systems): `prd-creator` with the kit, references and built-ins. With a user, wait for explicit approval, then implement. With no user (`claude -p`, a script), record the plan and assumptions, then continue.
5. **Assets**: `asset_search_sources`, the source's search tool, then `asset_download_file`; record source and licence in `CREDITS.md`. Nothing fits: `sculpt_plan`. A rigged GLB: `animation_inspect` (`threenative-assets` skill).
6. **Build**: gameplay in `src/`, look in `src/render/` (`threenative-context` and `threenative-ui` skills). For parallel work, give each subagent one area and its files, then integrate.
diff --git a/packages/create-threenative/agent-files/.agents/skills/threenative-capabilities/SKILL.md b/packages/create-threenative/agent-files/.agents/skills/threenative-capabilities/SKILL.md
index 7e5a45deaa..5c057f7a4a 100644
--- a/packages/create-threenative/agent-files/.agents/skills/threenative-capabilities/SKILL.md
+++ b/packages/create-threenative/agent-files/.agents/skills/threenative-capabilities/SKILL.md
@@ -50,8 +50,10 @@ prompt (`TN_DISCOVERY_GATE=off` opts out).
mechanic from pinned `file:line`, never from a video. No network or no tools: record
`none: gameplay frames unavailable`.
3. **Built-ins** — engine_capabilities built-in discovery, served by the same MCP: call
- `engine_search_capabilities` with `scope: "mechanic"` per mechanic, system or symptom; inspect
- hits with `engine_capability_detail`. Record installed capabilities and binding constraints. A
+ `engine_search_capabilities` with `scope: "mechanic"` per mechanic, system or symptom, and read
+ every row — it carries the import path, signature, example, constraints, overrides and
+ deprecations, so call `engine_capability_detail` only for a symbol the search did not return.
+ Record installed capabilities and binding constraints. A
`verdict: "none"` means game-owned behavior in `src/`; do not rephrase until something matches.
These role names add no servers.
4. **Plan** — hand **Kit**, **Mechanics** and **References** to `prd-creator`. Plan the core loop
@@ -92,8 +94,9 @@ prompt (`TN_DISCOVERY_GATE=off` opts out).
A change to an existing game skips step 1 and whole-game planning; do not invoke `prd-creator` just
to restart the game plan. For each change, search before you write:
-- Step 3 for every change: `engine_search_capabilities` with `scope: "mechanic"`, then
- `engine_capability_detail` on each hit, so installed systems are reused.
+- Step 3 for every change: `engine_search_capabilities` with `scope: "mechanic"`, then read every
+ row — `engine_capability_detail` only for a symbol the search did not return — so installed
+ systems are reused.
- Step 2 for each gameplay mechanic the change adds or alters: `engine_search_references`, then
`engine_reference_detail`; use `engine_watch_reference` when the motion is the question.
- Steps 5-8 for the change, using the existing game, plan and context: apply step 5 to the
diff --git a/packages/create-threenative/engine-mcp-tools.json b/packages/create-threenative/engine-mcp-tools.json
index 02c8e946ef..c34e0f19ec 100644
--- a/packages/create-threenative/engine-mcp-tools.json
+++ b/packages/create-threenative/engine-mcp-tools.json
@@ -110,7 +110,7 @@
}
},
"descriptions": {
- "engine_search_capabilities": "Search the installed engine by concrete gameplay mechanic. Decompose genres first. Use scope \"request\" for the mechanically explicit full request and \"mechanic\" for each focused search; matchedSituation and score explain every result. The response verdict \"none\" is an actionable answer with guidance, not a failed search.",
+ "engine_search_capabilities": "Search the installed engine by concrete gameplay mechanic or by an exact exported symbol name. Decompose genres first. Use scope \"request\" for the mechanically explicit full request and \"mechanic\" for each focused search; matchedSituation and score explain every result. A row carries the answer (importPath, signature, example, constraints, overrides, deprecations), so call engine_capability_detail only for a symbol the search did not return. The response verdict \"none\" is an actionable answer with guidance, not a failed search.",
"engine_capability_detail": "Inspect one engine capability's import, signature, example, constraints, and overrides. When several packages export the symbol, every match is returned under `matches`.",
"engine_search_references": "Reverse engineer classic games: search mechanic cards and repository map nodes from their decomps. Tier-2 titles link to repos to explore and save. References describe feel only; borrow algorithms, cite pinned sources, copy no code or art.",
"engine_reference_detail": "Reverse engineer a game: read its pinned map or mechanic card. A node includes files, key functions, dependencies and child nodes; a card includes constants and sources.",
diff --git a/packages/create-threenative/templates/action-rpg/AGENTS.md b/packages/create-threenative/templates/action-rpg/AGENTS.md
index 6e457e460f..5d52fba048 100644
--- a/packages/create-threenative/templates/action-rpg/AGENTS.md
+++ b/packages/create-threenative/templates/action-rpg/AGENTS.md
@@ -11,7 +11,7 @@ Detail for each step: `threenative-capabilities` skill. Do the steps in order. A
1. **Kit** (new game only): `engine_search_capabilities {situation: "", scope: "request"}` returns the matching starter kit first. This folder is already a kit.
2. **Mechanics**: per mechanic, `engine_search_references {situation}`, then `engine_reference_detail` on each hit for pinned constants and feel. To see a mechanic move, `engine_watch_reference {query: " "}` returns a contact sheet of real gameplay. Borrow mechanics only: no code, level layouts, character names or art. Cite `file:line`.
-3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`, then `engine_capability_detail {symbol, importPath}` on every hit. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
+3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`; rows answer; `engine_capability_detail` only for omitted symbols. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
4. **Plan** (new game, or a change to several systems): `prd-creator` with the kit, references and built-ins. With a user, wait for explicit approval, then implement. With no user (`claude -p`, a script), record the plan and assumptions, then continue.
5. **Assets**: `asset_search_sources`, the source's search tool, then `asset_download_file`; record source and licence in `CREDITS.md`. Nothing fits: `sculpt_plan`. A rigged GLB: `animation_inspect` (`threenative-assets` skill).
6. **Build**: gameplay in `src/`, look in `src/render/` (`threenative-context` and `threenative-ui` skills). For parallel work, give each subagent one area and its files, then integrate.
diff --git a/packages/create-threenative/templates/action-rpg/CLAUDE.md b/packages/create-threenative/templates/action-rpg/CLAUDE.md
index fef751b14d..2ceb1de1f4 100644
--- a/packages/create-threenative/templates/action-rpg/CLAUDE.md
+++ b/packages/create-threenative/templates/action-rpg/CLAUDE.md
@@ -13,7 +13,7 @@ Detail for each step: `threenative-capabilities` skill. Do the steps in order. A
1. **Kit** (new game only): `engine_search_capabilities {situation: "", scope: "request"}` returns the matching starter kit first. This folder is already a kit.
2. **Mechanics**: per mechanic, `engine_search_references {situation}`, then `engine_reference_detail` on each hit for pinned constants and feel. To see a mechanic move, `engine_watch_reference {query: " "}` returns a contact sheet of real gameplay. Borrow mechanics only: no code, level layouts, character names or art. Cite `file:line`.
-3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`, then `engine_capability_detail {symbol, importPath}` on every hit. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
+3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`; rows answer; `engine_capability_detail` only for omitted symbols. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
4. **Plan** (new game, or a change to several systems): `prd-creator` with the kit, references and built-ins. With a user, wait for explicit approval, then implement. With no user (`claude -p`, a script), record the plan and assumptions, then continue.
5. **Assets**: `asset_search_sources`, the source's search tool, then `asset_download_file`; record source and licence in `CREDITS.md`. Nothing fits: `sculpt_plan`. A rigged GLB: `animation_inspect` (`threenative-assets` skill).
6. **Build**: gameplay in `src/`, look in `src/render/` (`threenative-context` and `threenative-ui` skills). For parallel work, give each subagent one area and its files, then integrate.
diff --git a/packages/create-threenative/templates/adventure/AGENTS.md b/packages/create-threenative/templates/adventure/AGENTS.md
index 596eb618fc..0ad6d5e134 100644
--- a/packages/create-threenative/templates/adventure/AGENTS.md
+++ b/packages/create-threenative/templates/adventure/AGENTS.md
@@ -11,7 +11,7 @@ Detail for each step: `threenative-capabilities` skill. Do the steps in order. A
1. **Kit** (new game only): `engine_search_capabilities {situation: "", scope: "request"}` returns the matching starter kit first. This folder is already a kit.
2. **Mechanics**: per mechanic, `engine_search_references {situation}`, then `engine_reference_detail` on each hit for pinned constants and feel. To see a mechanic move, `engine_watch_reference {query: " "}` returns a contact sheet of real gameplay. Borrow mechanics only: no code, level layouts, character names or art. Cite `file:line`.
-3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`, then `engine_capability_detail {symbol, importPath}` on every hit. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
+3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`; rows answer; `engine_capability_detail` only for omitted symbols. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
4. **Plan** (new game, or a change to several systems): `prd-creator` with the kit, references and built-ins. With a user, wait for explicit approval, then implement. With no user (`claude -p`, a script), record the plan and assumptions, then continue.
5. **Assets**: `asset_search_sources`, the source's search tool, then `asset_download_file`; record source and licence in `CREDITS.md`. Nothing fits: `sculpt_plan`. A rigged GLB: `animation_inspect` (`threenative-assets` skill).
6. **Build**: gameplay in `src/`, look in `src/render/` (`threenative-context` and `threenative-ui` skills). For parallel work, give each subagent one area and its files, then integrate.
diff --git a/packages/create-threenative/templates/adventure/CLAUDE.md b/packages/create-threenative/templates/adventure/CLAUDE.md
index c555f3409f..4adabb2ec3 100644
--- a/packages/create-threenative/templates/adventure/CLAUDE.md
+++ b/packages/create-threenative/templates/adventure/CLAUDE.md
@@ -13,7 +13,7 @@ Detail for each step: `threenative-capabilities` skill. Do the steps in order. A
1. **Kit** (new game only): `engine_search_capabilities {situation: "", scope: "request"}` returns the matching starter kit first. This folder is already a kit.
2. **Mechanics**: per mechanic, `engine_search_references {situation}`, then `engine_reference_detail` on each hit for pinned constants and feel. To see a mechanic move, `engine_watch_reference {query: " "}` returns a contact sheet of real gameplay. Borrow mechanics only: no code, level layouts, character names or art. Cite `file:line`.
-3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`, then `engine_capability_detail {symbol, importPath}` on every hit. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
+3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`; rows answer; `engine_capability_detail` only for omitted symbols. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
4. **Plan** (new game, or a change to several systems): `prd-creator` with the kit, references and built-ins. With a user, wait for explicit approval, then implement. With no user (`claude -p`, a script), record the plan and assumptions, then continue.
5. **Assets**: `asset_search_sources`, the source's search tool, then `asset_download_file`; record source and licence in `CREDITS.md`. Nothing fits: `sculpt_plan`. A rigged GLB: `animation_inspect` (`threenative-assets` skill).
6. **Build**: gameplay in `src/`, look in `src/render/` (`threenative-context` and `threenative-ui` skills). For parallel work, give each subagent one area and its files, then integrate.
diff --git a/packages/create-threenative/templates/collectathon/AGENTS.md b/packages/create-threenative/templates/collectathon/AGENTS.md
index 02e450f004..c3dc79eb80 100644
--- a/packages/create-threenative/templates/collectathon/AGENTS.md
+++ b/packages/create-threenative/templates/collectathon/AGENTS.md
@@ -11,7 +11,7 @@ Detail for each step: `threenative-capabilities` skill. Do the steps in order. A
1. **Kit** (new game only): `engine_search_capabilities {situation: "", scope: "request"}` returns the matching starter kit first. This folder is already a kit.
2. **Mechanics**: per mechanic, `engine_search_references {situation}`, then `engine_reference_detail` on each hit for pinned constants and feel. To see a mechanic move, `engine_watch_reference {query: " "}` returns a contact sheet of real gameplay. Borrow mechanics only: no code, level layouts, character names or art. Cite `file:line`.
-3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`, then `engine_capability_detail {symbol, importPath}` on every hit. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
+3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`; rows answer; `engine_capability_detail` only for omitted symbols. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
4. **Plan** (new game, or a change to several systems): `prd-creator` with the kit, references and built-ins. With a user, wait for explicit approval, then implement. With no user (`claude -p`, a script), record the plan and assumptions, then continue.
5. **Assets**: `asset_search_sources`, the source's search tool, then `asset_download_file`; record source and licence in `CREDITS.md`. Nothing fits: `sculpt_plan`. A rigged GLB: `animation_inspect` (`threenative-assets` skill).
6. **Build**: gameplay in `src/`, look in `src/render/` (`threenative-context` and `threenative-ui` skills). For parallel work, give each subagent one area and its files, then integrate.
diff --git a/packages/create-threenative/templates/collectathon/CLAUDE.md b/packages/create-threenative/templates/collectathon/CLAUDE.md
index 709e48abff..83d052c11e 100644
--- a/packages/create-threenative/templates/collectathon/CLAUDE.md
+++ b/packages/create-threenative/templates/collectathon/CLAUDE.md
@@ -13,7 +13,7 @@ Detail for each step: `threenative-capabilities` skill. Do the steps in order. A
1. **Kit** (new game only): `engine_search_capabilities {situation: "", scope: "request"}` returns the matching starter kit first. This folder is already a kit.
2. **Mechanics**: per mechanic, `engine_search_references {situation}`, then `engine_reference_detail` on each hit for pinned constants and feel. To see a mechanic move, `engine_watch_reference {query: " "}` returns a contact sheet of real gameplay. Borrow mechanics only: no code, level layouts, character names or art. Cite `file:line`.
-3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`, then `engine_capability_detail {symbol, importPath}` on every hit. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
+3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`; rows answer; `engine_capability_detail` only for omitted symbols. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
4. **Plan** (new game, or a change to several systems): `prd-creator` with the kit, references and built-ins. With a user, wait for explicit approval, then implement. With no user (`claude -p`, a script), record the plan and assumptions, then continue.
5. **Assets**: `asset_search_sources`, the source's search tool, then `asset_download_file`; record source and licence in `CREDITS.md`. Nothing fits: `sculpt_plan`. A rigged GLB: `animation_inspect` (`threenative-assets` skill).
6. **Build**: gameplay in `src/`, look in `src/render/` (`threenative-context` and `threenative-ui` skills). For parallel work, give each subagent one area and its files, then integrate.
diff --git a/packages/create-threenative/templates/kart/AGENTS.md b/packages/create-threenative/templates/kart/AGENTS.md
index 028ccc7905..ea80809e06 100644
--- a/packages/create-threenative/templates/kart/AGENTS.md
+++ b/packages/create-threenative/templates/kart/AGENTS.md
@@ -11,7 +11,7 @@ Detail for each step: `threenative-capabilities` skill. Do the steps in order. A
1. **Kit** (new game only): `engine_search_capabilities {situation: "", scope: "request"}` returns the matching starter kit first. This folder is already a kit.
2. **Mechanics**: per mechanic, `engine_search_references {situation}`, then `engine_reference_detail` on each hit for pinned constants and feel. To see a mechanic move, `engine_watch_reference {query: " "}` returns a contact sheet of real gameplay. Borrow mechanics only: no code, level layouts, character names or art. Cite `file:line`.
-3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`, then `engine_capability_detail {symbol, importPath}` on every hit. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
+3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`; rows answer; `engine_capability_detail` only for omitted symbols. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
4. **Plan** (new game, or a change to several systems): `prd-creator` with the kit, references and built-ins. With a user, wait for explicit approval, then implement. With no user (`claude -p`, a script), record the plan and assumptions, then continue.
5. **Assets**: `asset_search_sources`, the source's search tool, then `asset_download_file`; record source and licence in `CREDITS.md`. Nothing fits: `sculpt_plan`. A rigged GLB: `animation_inspect` (`threenative-assets` skill).
6. **Build**: gameplay in `src/`, look in `src/render/` (`threenative-context` and `threenative-ui` skills). For parallel work, give each subagent one area and its files, then integrate.
diff --git a/packages/create-threenative/templates/kart/CLAUDE.md b/packages/create-threenative/templates/kart/CLAUDE.md
index 7167fc0a34..80ae25752d 100644
--- a/packages/create-threenative/templates/kart/CLAUDE.md
+++ b/packages/create-threenative/templates/kart/CLAUDE.md
@@ -13,7 +13,7 @@ Detail for each step: `threenative-capabilities` skill. Do the steps in order. A
1. **Kit** (new game only): `engine_search_capabilities {situation: "", scope: "request"}` returns the matching starter kit first. This folder is already a kit.
2. **Mechanics**: per mechanic, `engine_search_references {situation}`, then `engine_reference_detail` on each hit for pinned constants and feel. To see a mechanic move, `engine_watch_reference {query: " "}` returns a contact sheet of real gameplay. Borrow mechanics only: no code, level layouts, character names or art. Cite `file:line`.
-3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`, then `engine_capability_detail {symbol, importPath}` on every hit. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
+3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`; rows answer; `engine_capability_detail` only for omitted symbols. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
4. **Plan** (new game, or a change to several systems): `prd-creator` with the kit, references and built-ins. With a user, wait for explicit approval, then implement. With no user (`claude -p`, a script), record the plan and assumptions, then continue.
5. **Assets**: `asset_search_sources`, the source's search tool, then `asset_download_file`; record source and licence in `CREDITS.md`. Nothing fits: `sculpt_plan`. A rigged GLB: `animation_inspect` (`threenative-assets` skill).
6. **Build**: gameplay in `src/`, look in `src/render/` (`threenative-context` and `threenative-ui` skills). For parallel work, give each subagent one area and its files, then integrate.
diff --git a/packages/create-threenative/templates/minimal/AGENTS.md b/packages/create-threenative/templates/minimal/AGENTS.md
index 9248b4aa7b..fa8c7a70a6 100644
--- a/packages/create-threenative/templates/minimal/AGENTS.md
+++ b/packages/create-threenative/templates/minimal/AGENTS.md
@@ -11,7 +11,7 @@ Detail for each step: `threenative-capabilities` skill. Do the steps in order. A
1. **Kit** (new game only): `engine_search_capabilities {situation: "", scope: "request"}` returns the matching starter kit first. This folder is already a kit.
2. **Mechanics**: per mechanic, `engine_search_references {situation}`, then `engine_reference_detail` on each hit for pinned constants and feel. To see a mechanic move, `engine_watch_reference {query: " "}` returns a contact sheet of real gameplay. Borrow mechanics only: no code, level layouts, character names or art. Cite `file:line`.
-3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`, then `engine_capability_detail {symbol, importPath}` on every hit. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
+3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`; rows answer; `engine_capability_detail` only for omitted symbols. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
4. **Plan** (new game, or a change to several systems): `prd-creator` with the kit, references and built-ins. With a user, wait for explicit approval, then implement. With no user (`claude -p`, a script), record the plan and assumptions, then continue.
5. **Assets**: `asset_search_sources`, the source's search tool, then `asset_download_file`; record source and licence in `CREDITS.md`. Nothing fits: `sculpt_plan`. A rigged GLB: `animation_inspect` (`threenative-assets` skill).
6. **Build**: gameplay in `src/`, look in `src/render/` (`threenative-context` and `threenative-ui` skills). For parallel work, give each subagent one area and its files, then integrate.
diff --git a/packages/create-threenative/templates/minimal/CLAUDE.md b/packages/create-threenative/templates/minimal/CLAUDE.md
index 4d5b73163b..e182ba923f 100644
--- a/packages/create-threenative/templates/minimal/CLAUDE.md
+++ b/packages/create-threenative/templates/minimal/CLAUDE.md
@@ -13,7 +13,7 @@ Detail for each step: `threenative-capabilities` skill. Do the steps in order. A
1. **Kit** (new game only): `engine_search_capabilities {situation: "", scope: "request"}` returns the matching starter kit first. This folder is already a kit.
2. **Mechanics**: per mechanic, `engine_search_references {situation}`, then `engine_reference_detail` on each hit for pinned constants and feel. To see a mechanic move, `engine_watch_reference {query: " "}` returns a contact sheet of real gameplay. Borrow mechanics only: no code, level layouts, character names or art. Cite `file:line`.
-3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`, then `engine_capability_detail {symbol, importPath}` on every hit. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
+3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`; rows answer; `engine_capability_detail` only for omitted symbols. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
4. **Plan** (new game, or a change to several systems): `prd-creator` with the kit, references and built-ins. With a user, wait for explicit approval, then implement. With no user (`claude -p`, a script), record the plan and assumptions, then continue.
5. **Assets**: `asset_search_sources`, the source's search tool, then `asset_download_file`; record source and licence in `CREDITS.md`. Nothing fits: `sculpt_plan`. A rigged GLB: `animation_inspect` (`threenative-assets` skill).
6. **Build**: gameplay in `src/`, look in `src/render/` (`threenative-context` and `threenative-ui` skills). For parallel work, give each subagent one area and its files, then integrate.
diff --git a/packages/create-threenative/templates/platformer/AGENTS.md b/packages/create-threenative/templates/platformer/AGENTS.md
index a507616a63..31c067a928 100644
--- a/packages/create-threenative/templates/platformer/AGENTS.md
+++ b/packages/create-threenative/templates/platformer/AGENTS.md
@@ -11,7 +11,7 @@ Detail for each step: `threenative-capabilities` skill. Do the steps in order. A
1. **Kit** (new game only): `engine_search_capabilities {situation: "", scope: "request"}` returns the matching starter kit first. This folder is already a kit.
2. **Mechanics**: per mechanic, `engine_search_references {situation}`, then `engine_reference_detail` on each hit for pinned constants and feel. To see a mechanic move, `engine_watch_reference {query: " "}` returns a contact sheet of real gameplay. Borrow mechanics only: no code, level layouts, character names or art. Cite `file:line`.
-3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`, then `engine_capability_detail {symbol, importPath}` on every hit. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
+3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`; rows answer; `engine_capability_detail` only for omitted symbols. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
4. **Plan** (new game, or a change to several systems): `prd-creator` with the kit, references and built-ins. With a user, wait for explicit approval, then implement. With no user (`claude -p`, a script), record the plan and assumptions, then continue.
5. **Assets**: `asset_search_sources`, the source's search tool, then `asset_download_file`; record source and licence in `CREDITS.md`. Nothing fits: `sculpt_plan`. A rigged GLB: `animation_inspect` (`threenative-assets` skill).
6. **Build**: gameplay in `src/`, look in `src/render/` (`threenative-context` and `threenative-ui` skills). For parallel work, give each subagent one area and its files, then integrate.
@@ -37,7 +37,7 @@ A change to an existing game repeats steps 2-3 for that change, then steps 5-8.
## This kit
-Source in `src/`: `entities/`, `game.ts`, `level/`, `main.ts`, `playtest-events.ts`, `render/`, `scenes/`, `state.ts`, `ui/`.
+Source in `src/`: `entities/`, `game.ts`, `level/`, `main.ts`, `playtest-events.ts`, `render/`, `scenes/`, `state.ts`, `ui/`, `worker-page.ts`, `worker.ts`.
18 scenarios in `playtests/`; `playtests/survives.playtest.json` is the smoke proof.
Exposure policy: `src/render/exposure.ts` (`node_modules/create-threenative/agent-docs/references/auto-exposure.md`).
Kit notes: `node_modules/create-threenative/agent-docs/references/kit-platformer.md`.
diff --git a/packages/create-threenative/templates/platformer/CLAUDE.md b/packages/create-threenative/templates/platformer/CLAUDE.md
index 0eedfb4c08..4fc2b6d682 100644
--- a/packages/create-threenative/templates/platformer/CLAUDE.md
+++ b/packages/create-threenative/templates/platformer/CLAUDE.md
@@ -13,7 +13,7 @@ Detail for each step: `threenative-capabilities` skill. Do the steps in order. A
1. **Kit** (new game only): `engine_search_capabilities {situation: "", scope: "request"}` returns the matching starter kit first. This folder is already a kit.
2. **Mechanics**: per mechanic, `engine_search_references {situation}`, then `engine_reference_detail` on each hit for pinned constants and feel. To see a mechanic move, `engine_watch_reference {query: " "}` returns a contact sheet of real gameplay. Borrow mechanics only: no code, level layouts, character names or art. Cite `file:line`.
-3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`, then `engine_capability_detail {symbol, importPath}` on every hit. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
+3. **Built-ins**: per mechanic, system or symptom, `engine_search_capabilities {situation, scope: "mechanic"}`; rows answer; `engine_capability_detail` only for omitted symbols. Constraints are binding. `verdict: "none"` means the game writes it in `src/`.
4. **Plan** (new game, or a change to several systems): `prd-creator` with the kit, references and built-ins. With a user, wait for explicit approval, then implement. With no user (`claude -p`, a script), record the plan and assumptions, then continue.
5. **Assets**: `asset_search_sources`, the source's search tool, then `asset_download_file`; record source and licence in `CREDITS.md`. Nothing fits: `sculpt_plan`. A rigged GLB: `animation_inspect` (`threenative-assets` skill).
6. **Build**: gameplay in `src/`, look in `src/render/` (`threenative-context` and `threenative-ui` skills). For parallel work, give each subagent one area and its files, then integrate.
@@ -39,7 +39,7 @@ A change to an existing game repeats steps 2-3 for that change, then steps 5-8.
## This kit
-Source in `src/`: `entities/`, `game.ts`, `level/`, `main.ts`, `playtest-events.ts`, `render/`, `scenes/`, `state.ts`, `ui/`.
+Source in `src/`: `entities/`, `game.ts`, `level/`, `main.ts`, `playtest-events.ts`, `render/`, `scenes/`, `state.ts`, `ui/`, `worker-page.ts`, `worker.ts`.
18 scenarios in `playtests/`; `playtests/survives.playtest.json` is the smoke proof.
Exposure policy: `src/render/exposure.ts` (`node_modules/create-threenative/agent-docs/references/auto-exposure.md`).
Kit notes: `node_modules/create-threenative/agent-docs/references/kit-platformer.md`.
diff --git a/packages/create-threenative/templates/platformer/index.html b/packages/create-threenative/templates/platformer/index.html
index f59c7647a1..4cfc3edbf5 100644
--- a/packages/create-threenative/templates/platformer/index.html
+++ b/packages/create-threenative/templates/platformer/index.html
@@ -7,6 +7,6 @@
-
+