From 3356694d1a70b402369f9b58bfdf561841dfee7f Mon Sep 17 00:00:00 2001 From: Joao Paulo Furtado Date: Mon, 26 Oct 2026 17:12:09 -0700 Subject: [PATCH 01/26] docs(prd-750): plan the kit authoring loop, one KPI dashboard (PRD-751) and the reverse-engineer rename (PRD-735) Owner interview 2026-10-26: one iteration = in-repo kit author + cold sandbox build, retro on both transcripts; KPIs K1 cold-build cost, K2 first-build quality, K3 friction, K4 AI play grade; ranked genre list; PRD-735 lands first and renames threenative-game-refs to threenative-reverse-engineer-mcp; perf:live becomes the single pnpm dashboard; one epic PR. Co-Authored-By: Claude Opus 5.5 --- ...PRD-735-game-reference-content-at-scale.md | 89 +++++++++++ .../PRD-750-kit-authoring-loop.md | 144 ++++++++++++++++++ .../starter-kits/PRD-751-one-dashboard.md | 69 +++++++++ 3 files changed, 302 insertions(+) create mode 100644 docs/PRDs/authoring/PRD-735-game-reference-content-at-scale.md create mode 100644 docs/PRDs/starter-kits/PRD-750-kit-authoring-loop.md create mode 100644 docs/PRDs/starter-kits/PRD-751-one-dashboard.md diff --git a/docs/PRDs/authoring/PRD-735-game-reference-content-at-scale.md b/docs/PRDs/authoring/PRD-735-game-reference-content-at-scale.md new file mode 100644 index 0000000000..0634fbd52f --- /dev/null +++ b/docs/PRDs/authoring/PRD-735-game-reference-content-at-scale.md @@ -0,0 +1,89 @@ +# PRD-735 — Reverse-engineer references at scale: the repo rename, the full list, the classics and the scout pipeline + +**Status:** NOT STARTED +**Priority:** P1 — AC-0 to AC-5 are open, and the PRD-750 kit loop (P1) needs their references past its first row; today the release holds one Tier-1 game (SM64). +**Complexity:** 4 (MEDIUM) — separate data release +2, external API +1, 1–5 files +1; risk override: none. +**Owner:** João Paulo Furtado +**Depends on:** PRD-734 (done: the reference tools, the schema and the `v0.1` release) +**PR:** the PRD-750 epic PR (owner, 2026-10-26) + +## Context + +PRD-734 ships the reference tools and one Tier-1 game, Super Mario 64. This PRD fills the data repo +`threenative-game-refs` so the references cover what users actually ask for: + +- **Tier 2:** an index row for each project in + (2,359 on 2026-10-20). +- **Tier 1:** about 20 classic repos with a full map and full cards, in the order of the PRD-750 + genre list (its candidate reference per row), then Ocarina of Time for Lantern Vale and the other + classics. +- **Promotion:** saved Tier-2 cards that users contribute through `engine_save_reference` are + reviewed and merged. + +The schema, the borrow-not-copy guardrail and the pinned-SHA rule come from PRD-734 and do not +change here. + +## Solution + +- **Rename** (owner, 2026-10-26: "we should rename ... threenative-game-refs to + threenative-reverse-engineer-mcp"). The data repo moves to + `ThreeNativeHQ/threenative-reverse-engineer-mcp`. `REFERENCES_URL` and `CONTRIBUTION_REPO` in + `packages/engine-mcp/src/references.ts` follow it. The tools stay in `threenative-engine-mcp`, + and their descriptions carry "reverse engineer" so a search for those words finds them. GitHub + redirects the old release URL, so installed games keep working. +- **Import script** in the data repo. It parses the list README into Tier-2 rows (title, platform, + repo URL), gives each row a stable kebab-case id and keeps the ids of rows that already exist. +- **Scout pipeline.** A scout is a cheap agent (the `ds` harness, V4.1-Flash max) that clones one + repo at a pinned SHA, writes the map, then writes the cards for each mechanic node. It gets a + fixed brief: the schema, the guardrail and the unit rule. Each constant states its unit and its + timestep, for example "per frame at 30 Hz". The PRD-734 `validate` job checks every path, + function and line against the pinned clone. A full review then reads each card before merge, + because a wrong constant misleads every user. +- **Tier-1 order:** OoT first, then a ranked list of about 20 classics, kept in the data repo + README. Each choice must have a mature decomp and a mechanic that users ask for. +- **Contributions:** an issue made by `engine_save_reference` is validated by the same job, reviewed + and promoted to Tier 1 when its game is complete enough. + +## Acceptance Criteria + +See the phase boxes below. Each box names its proof. + +## Blocked on + +- The repo transfer to the `ThreeNativeHQ` org needs org admin rights on the owner's `gh` login — unblocked by the owner's token (try it first; AC-0 records the result). + +## Integration Ledger + +Integration: the release URL and contribution repo in `packages/engine-mcp/src/references.ts` change with the rename (AC-0). The PRD-734 tools read the new releases with no other code change. + +## Decisions + +- 2026-10-26 (owner): rename the data repo to `threenative-reverse-engineer-mcp`; the tools stay in engine-mcp with "reverse engineer" as search words, not a separate server. +- 2026-10-26 (owner): this PRD lands before the PRD-750 loop runs past its first row. +- 2026-10-24 (agent): split from PRD-734 to keep both PRDs within the 3-phase cap. Content at scale does not change the tool contract. + +## Execution Phases + +#### Phase 1: The rename and Tier 2 for the full list + +**Status:** NOT STARTED +**Files:** `packages/engine-mcp/src/references.ts`; the data repo: import script, release workflow. + +- [ ] AC-0 [local]: an installed project's MCP fetches the release from `ThreeNativeHQ/threenative-reverse-engineer-mcp`, and `engine_search_capabilities "reverse engineer"` returns the reference tools. proof: `pnpm exec vitest run packages/engine-mcp/__tests__/references.spec.ts packages/engine-mcp/__tests__/search.spec.ts` +- [ ] AC-1 [shared]: the release index holds one Tier-2 row per list project, and a second import keeps every existing id. proof: data repo `import` job, row count against the list README count +- [ ] AC-2 [local]: engine-mcp searches a Tier-2 title from the new release and returns its repo with the explore guidance. proof: `THREENATIVE_REFERENCES_URL= pnpm exec vitest run packages/engine-mcp/__tests__/references.spec.ts -t live` + +#### Phase 2: Scout pipeline and Ocarina of Time + +**Status:** NOT STARTED +**Files:** the data repo: scout brief, scout runner script. + +- [ ] AC-3 [shared]: OoT has a reviewed map and at least 10 reviewed cards at a pinned SHA, each constant with its unit and timestep, and the `validate` job passes. proof: data repo `validate` job + +#### Phase 3: The classics and the contribution path + +**Status:** NOT STARTED +**Files:** the data repo: the ranked Tier-1 list, the contribution issue template. + +- [ ] AC-4 [shared]: about 20 Tier-1 games each have a reviewed map and at least 10 reviewed cards, and the `validate` job passes. proof: data repo `validate` job, Tier-1 count in the release index +- [ ] AC-5 [shared]: one issue made by `engine_save_reference` passes `validate`, is reviewed and appears in the next release. proof: data repo issue, `validate` run and release diff diff --git a/docs/PRDs/starter-kits/PRD-750-kit-authoring-loop.md b/docs/PRDs/starter-kits/PRD-750-kit-authoring-loop.md new file mode 100644 index 0000000000..a06caaf99c --- /dev/null +++ b/docs/PRDs/starter-kits/PRD-750-kit-authoring-loop.md @@ -0,0 +1,144 @@ +# PRD-750 — Kit authoring loop: build a genre kit, cold-build a game from it, learn, repeat + +**Status:** NOT STARTED +**Priority:** P1 — "make me a game like X" is the primary workflow before the release, and no loop measures or improves it yet (all phases open) +**Complexity:** 3 (LOW) — 1–5 files +1, new module (the loop script) +2; risk override: none +**Owner:** João Paulo Furtado +**Depends on:** PRD-735 (reverse-engineer references at scale), PRD-749 (one kit base and a kit contract), PRD-751 (one dashboard) +**PR:** one epic PR for the loop and every kit it builds (owner, 2026-10-26), merged at the release + +## Context + +The owner wants the release to invest in the authoring layer: a user asks "make me a game like X", +an agent starts from the matching starter kit, and the first build is good on few tokens +(root `AGENTS.md`: "A request that names a game or genre a kit covers starts from that kit"). + +Today: + +- 16 kits ship in `packages/create-threenative/templates/`. Only three (collectathon, adventure, kart; + PRD-732/733/734) were built to the current bar, with `kit.json` `aliases` and pinned reference + citations. +- The reverse-engineer tools (`engine_search_references`, `engine_reference_detail`, + `engine_save_reference` in `packages/engine-mcp/src/references.ts`) shipped in #563, but the data + release holds one Tier-1 game (SM64). PRD-735 fills it. +- Nothing measures what a cold user agent pays to build from a kit, or what friction it hits. Each + kit lane learned alone, and the next lane did not read it. +- Harnesses exist and are reused here, not rebuilt: `pnpm sandbox` (user-like install), + `pnpm sweep:measure` (`scripts/measure-sandbox.ts`), the `build-on-sandbox` and + `score-build-experience` skills, the playtest runner and the visual judge rule. + +## Solution + +One **iteration** builds one row of the genre list below: + +```mermaid +flowchart LR + R[Reference: engine_search_references
-> pinned decomp cards] --> A[Author: in-repo agent
writes or revamps kit N on the kit base] + A --> C[Cold build: fresh agent in pnpm sandbox
prompt 'make me a game like X'] + C --> M[Measure: 4 KPIs
from transcripts + gates + judge] + M --> L[Retro row in docs/kit-loop/ledger.json
worked / failed / top 3 frictions] + L --> F[Fix: top frictions in the authoring layer
before iteration N+1] + F --> R +``` + +- **Author step.** An in-repo agent takes the genre row, reads the reference cards through the MCP, + and writes (or revamps) the kit on the PRD-749 kit base with its own short PRD in + `docs/PRDs/starter-kits/`. Mechanics borrow constants with `file:line` citations; art, names and + layouts stay original. +- **Cold build.** A fresh agent in a `pnpm sandbox` folder gets only the user prompt + ("make me a game like "). It must find the kit by alias, build, and pass the kit's + gates. It gets no repo `AGENTS.md` chain. +- **Measure.** `scripts/kit-loop.mjs` (the only new module) reads the cold-build transcript and the + gate outputs and appends one ledger row. It measures; it does not drive agents. +- **Retro.** The coordinator reads both transcripts (author and cold), writes three lists into the + ledger row (worked, failed, top 3 frictions with the transcript line), and the next iteration fixes + the top frictions first. **Fix scope** (owner, 2026-10-26): the whole authoring layer — MCP tools, + `capabilities.json`, template `AGENTS.md`, skills, scaffolder, and small shared helpers that several + kits need. New engine systems are out of scope. +- **Dashboard.** The ledger is a tab of the single `pnpm dashboard` (PRD-751). + +### KPIs (owner, 2026-10-26) + +Each one is a field of every ledger row. Iteration 1 sets the baseline; targets are relative to it. + +| KPI | Definition | Source | Target by the last iteration | +| --- | --- | --- | --- | +| K1 cold-build cost | tokens (input + output + cache writes), wall minutes and turns, from the prompt to the first build whose gates pass | cold transcript (`claude -p --output-format json` usage) | -40% tokens vs baseline | +| K2 first-build quality | share of kit playtest scenarios green on the first cold build with no human edit; `TN_FRAME_BUDGET` presented p95 within budget; visual judge verdict against the reference image | playtest exit codes, `TN_FRAME_BUDGET`, judge | 100% scenarios, budget met, judge CLOSE or better | +| K3 friction count | capability-search misses + hand-written duplicates of an installed system + wrong-API errors + retries of the same failing command | `score-build-experience` rows and the cold transcript | -50% vs baseline | +| K4 AI play grade | a fresh Opus judge plays the cold build (free play with real input, then the scenario captures) and grades feel and mechanics 1–5 against the reference cards | judge report | 4 or higher | + +**Stop rule** (owner, 2026-10-26): the loop runs without the owner and stops when the genre list is +done, or when two iterations in a row improve none of K1–K4 by 5% or more. After each kit, the +owner gets one ping with the dashboard tab and the ledger row. + +### Genre list (ranked; owner reviews it in the PR) + +Ranked by user demand, then by reference availability. "Revamp" rebuilds an existing kit on the kit +base to the PRD-732/733/734 bar; "new" adds a kit. PRD-746 already owns the shooter revamp, so it is +not a row here. The reference for each row is a candidate until PRD-735 lands its cards. + +| # | Genre (aliases) | Kind | Candidate reference | +| --- | --- | --- | --- | +| 1 | 3D platformer — calibration row (mario, sm64) | revamp collectathon | `sm64` (Tier 1 today) | +| 2 | Boomer shooter / arena FPS (doom, quake) | new | id Software GPL sources | +| 3 | Open-world third-person action (gta) | new | `re3` | +| 4 | RTS (command and conquer, red alert) | revamp rts | OpenRA | +| 5 | Metroidvania (metroid, castlevania) | new | Super Metroid disassembly | +| 6 | Arena fighter (smash) | new | SSB64 decomp | +| 7 | Voxel survival / crafting (minecraft) | new | Luanti (Minetest) | +| 8 | Arcade racer (need for speed, ridge racer) | revamp racing | to be scouted (PRD-735 Phase 3) | +| 9 | Tower defense (bloons, kingdom rush) | revamp tower-defense | to be scouted | +| 10 | Endless runner (subway surfers, temple run) | revamp runner | to be scouted | + +Row 1 runs first on purpose: its reference already exists, so it calibrates the loop and sets the +KPI baseline while PRD-735 scouts the next references. + +## Acceptance Criteria + +See the phase boxes. Each box names its proof. + +## Integration Ledger + +| Capability | Reachable consumer/trigger | Replaces / disposition | Evidence | +| --- | --- | --- | --- | +| Kit-loop ledger row | `pnpm kit:loop record ` → `scripts/kit-loop.mjs` → `docs/kit-loop/ledger.json` | new | AC-1 | +| Kit KPI tab | `pnpm dashboard` → kit-loop tab reads the ledger | new tab in the PRD-751 hub | AC-2 | +| Kit found by genre words | cold agent → `engine_search_capabilities` / `--genre ` → kit | unchanged path, measured | AC-4 | + +## Decisions + +- 2026-10-26 (owner): an iteration is an in-repo author step plus a cold sandbox build; both transcripts are inspected. +- 2026-10-26 (owner): fix scope is the whole authoring layer; new engine systems stay out. +- 2026-10-26 (owner): KPIs are K1–K4 above; the play grade comes from an AI judge, not the owner. +- 2026-10-26 (owner): a ranked mix of revamps and new kits; PRD-735 (references at scale) lands before the loop runs past row 1. +- 2026-10-26 (owner): one epic PR for the loop and every kit; it merges at the release. +- 2026-10-26 (owner): target-plus-plateau stop rule, autonomous, one ping per kit. +- 2026-10-26 (agent): the retro lives in the ledger row, not in a file per iteration, so the dashboard shows it and nothing drifts. + +## Execution Phases + +#### Phase 1: The ledger and the measure step + +**Status:** NOT STARTED +**Files:** `scripts/kit-loop.mjs` (new), `scripts/__tests__/kit-loop.spec.ts` (new), `docs/kit-loop/ledger.json` (new), `package.json` (`kit:loop`). + +- [ ] AC-1 [local]: `pnpm kit:loop record ` turns a cold-build transcript and its gate outputs into one ledger row with K1–K4, and a missing input fails closed (no zero, no pass). proof: `pnpm exec vitest run scripts/__tests__/kit-loop.spec.ts` +- [ ] AC-2 [local]: the PRD-751 dashboard shows the kit-loop tab with each KPI against its baseline and target. proof: `pnpm exec vitest run scripts/__tests__/perf-live.spec.ts -t kit-loop` + +#### Phase 2: Iteration 1 — calibration on row 1 (3D platformer) + +**Status:** NOT STARTED +**Files:** the row-1 kit PRD, the collectathon kit, the ledger row. + +- [ ] AC-3 [local]: the author step revamps the collectathon kit on the kit base, with SM64 card citations, and its own kit PRD is green. proof: the kit PRD's gates +- [ ] AC-4 [local]: a cold sandbox agent prompted "make me a game like Super Mario 64" picks the collectathon kit and reaches a gate-green build; the ledger holds the baseline row with K1–K4 and the retro lists. proof: `pnpm kit:loop record `, ledger row 1 + +#### Phase 3: Iterations 2..N to the stop rule + +**Status:** NOT STARTED +**Files:** one kit PRD per row, the authoring-layer fixes each retro names, the ledger. + +- [ ] AC-5 [local]: each later iteration lands its top retro fixes before its author step, and its ledger row names them. proof: ledger rows 2..N, the fix commits +- [ ] AC-6 [local]: the loop stops by the rule: the list is done, or two rows in a row gain under 5% on all of K1–K4. proof: the ledger, the stop line in this PRD +- [ ] AC-7 [local]: K1–K4 meet their targets at the last row, or the gap per KPI is recorded with its cause. proof: dashboard kit-loop tab, the last ledger row diff --git a/docs/PRDs/starter-kits/PRD-751-one-dashboard.md b/docs/PRDs/starter-kits/PRD-751-one-dashboard.md new file mode 100644 index 0000000000..5173947700 --- /dev/null +++ b/docs/PRDs/starter-kits/PRD-751-one-dashboard.md @@ -0,0 +1,69 @@ +# PRD-751 — One dashboard: `pnpm dashboard` holds every KPI tab + +**Status:** NOT STARTED +**Priority:** P2 — the owner cannot see what the loops do from one place: CI speed and asset perf still render separate pages, and the kit-loop KPIs have no view (all phases open) +**Complexity:** 2 (LOW) — 6–10 files +2; risk override: none +**Owner:** João Paulo Furtado +**Depends on:** None +**PR:** the PRD-750 epic PR (owner, 2026-10-26) + +## Context + +Owner ask (2026-10-26): "i also need a kpi dashboard to keep track of wtf is being done", then "there +are existing performance dashboards somewhere on codebase. maybe merge them all? kill redundant. But +just leave only one to rule them all". + +Dashboards found at `origin/pr563-followup` 369e57e5a: + +| Dashboard | Data | Disposition | +| --- | --- | --- | +| `pnpm perf:live` (`scripts/perf-live.ts`, `scripts/perf-live.html`) | live tabs: bench:engines reports, Midway (`scripts/midway-bench/dashboard.ts`), Shadows vs Unreal (`scripts/shadow-perf/dashboard.ts`) | **keep** — renamed `pnpm dashboard`, the hub | +| `docs/ci-speed/index.html`, written by `scripts/ci-speed-loop.mjs` | `docs/ci-speed/ledger.json` | fold in as a tab; stop writing the page | +| `docs/asset-perf/index.html`, written by `scripts/asset-perf-loop.mjs` | its ledger | fold in as a tab; stop writing the page | +| `.agents/skills/native-performance-loop/references/dashboard.md` | tells a campaign to generate its own HTML page | point it at a hub tab | +| `docs/PRDs/Unreal/research/unreal-visuals-and-performance-feature-map.html` | the Unreal port order | keep — a plan, not a KPI view | +| `prd-dashboard` (port 4319, its own repo) | live agent sessions across repos | out of scope — cross-repo telemetry | + +## Solution + +- Rename `perf:live` to `dashboard` in `package.json` and every doc that names it. +- Add three tabs, each a small reader like `shadow-perf/dashboard.ts` (reads one ledger, writes + nothing, fails closed: missing = `pending`, malformed = `invalid`): **CI speed**, **Asset perf**, + **Kit loop** (PRD-750 `docs/kit-loop/ledger.json`). +- `ci-speed-loop.mjs` and `asset-perf-loop.mjs` keep writing their ledgers and stop writing HTML; + delete the two `index.html` files and update their consumers (root `AGENTS.md` CI paragraph, + `scripts/__tests__/ci-efficiency.spec.ts`, the CI-speed skill, PRD-550 and PRD-729 links). +- The native-performance-loop skill reference names the hub tab instead of a per-campaign page. + +## Acceptance Criteria + +See the phase boxes. Each box names its proof. + +## Integration Ledger + +| Capability | Reachable consumer/trigger | Replaces / disposition | Evidence | +| --- | --- | --- | --- | +| One KPI hub | `pnpm dashboard` → `scripts/perf-live.ts` server → tabs | replaces `docs/ci-speed/index.html`, `docs/asset-perf/index.html` | AC-1..AC-4 | + +## Decisions + +- 2026-10-26 (owner): `perf:live` becomes the single dashboard; the static pages and the skill's per-campaign HTML go. + +## Execution Phases + +#### Phase 1: The hub and its two folded tabs + +**Status:** NOT STARTED +**Files:** `package.json`, `scripts/perf-live.ts`, `scripts/perf-live.html`, `scripts/ci-speed-loop.mjs`, `scripts/asset-perf-loop.mjs`, the two deleted `index.html` files and their consumers. + +- [ ] AC-1 [local]: `pnpm dashboard` serves the existing tabs plus CI speed and Asset perf, read from their ledgers; a missing or malformed ledger shows `pending` or `invalid`. proof: `pnpm exec vitest run scripts/__tests__/perf-live.spec.ts` +- [ ] AC-2 [local]: no tracked file writes or links a removed page. proof: `rg -n "ci-speed/index.html|asset-perf/index.html|perf:live" --glob '!docs/PRDs/done/**'` empty, `pnpm check:docs`, `pnpm exec vitest run scripts/__tests__/ci-efficiency.spec.ts` +- [ ] AC-3 [local]: the hub renders all tabs in a real browser with no console error. proof: headed screenshot per tab on the PR + +#### Phase 2: The kit-loop tab and the skill pointer + +**Status:** NOT STARTED +**Files:** `scripts/perf-live.ts` (kit-loop reader), `.agents/skills/native-performance-loop/references/dashboard.md`. + +- [ ] AC-4 [local]: the kit-loop tab plots K1–K4 per iteration against the PRD-750 baseline and targets. proof: `pnpm exec vitest run scripts/__tests__/perf-live.spec.ts -t kit-loop` +- [ ] AC-5 [local]: the native-performance-loop skill reference sends a campaign to a hub tab, not a new page. proof: `pnpm sync:agents --check`, `pnpm check:docs` From c2ab8c57977064d2eccf90071d5d748aad0c3284 Mon Sep 17 00:00:00 2001 From: Joao Paulo Furtado Date: Mon, 26 Oct 2026 17:29:18 -0700 Subject: [PATCH 02/26] PRD-751 Phase 1: pnpm dashboard is the one KPI hub with CI speed and Asset perf tabs perf:live becomes pnpm dashboard. The CI speed and Asset perf tabs render their ledgers on each load (missing = pending, malformed = invalid). The two static docs/*/index.html pages, the loops' report/serve commands and their consumers go. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/ci-speed-loop/SKILL.md | 8 +- AGENTS.md | 2 +- CLAUDE.md | 2 +- ...D-550-a-change-merges-within-30-minutes.md | 2 +- ...ssets-ship-smallest-and-preload-fastest.md | 4 +- ...eal-class-performance-in-release-cycles.md | 2 +- ...RD-610-native-engine-wins-on-real-games.md | 2 +- .../starter-kits/PRD-751-one-dashboard.md | 12 +-- docs/asset-perf/index.html | 79 ----------------- docs/ci-speed/index.html | 83 ----------------- package.json | 2 +- scripts/__tests__/asset-perf-loop.spec.ts | 4 +- scripts/__tests__/ci-efficiency.spec.ts | 6 +- scripts/__tests__/perf-live.spec.ts | 41 +++++++++ scripts/asset-perf-loop.mjs | 42 +++------ scripts/ci-change-scope.mjs | 3 +- scripts/ci-speed-loop.mjs | 14 ++- scripts/midway-bench/README.md | 2 +- scripts/midway-bench/dashboard.ts | 2 +- scripts/perf-live.ts | 88 +++++++++++++++++-- scripts/shadow-perf/dashboard.ts | 2 +- 21 files changed, 165 insertions(+), 237 deletions(-) delete mode 100644 docs/asset-perf/index.html delete mode 100644 docs/ci-speed/index.html diff --git a/.claude/skills/ci-speed-loop/SKILL.md b/.claude/skills/ci-speed-loop/SKILL.md index bd3cc1a139..56379302c0 100644 --- a/.claude/skills/ci-speed-loop/SKILL.md +++ b/.claude/skills/ci-speed-loop/SKILL.md @@ -1,6 +1,6 @@ --- name: ci-speed-loop -description: Run ThreeNative's CI speed loop — measure how long a change takes to merge, name the biggest cost from the first failing jobs and queue wait, change one thing, re-measure the same window, record the iteration and refresh the HTML dashboard. Use when asked to speed up CI or the merge queue, work PRD-550, check progress toward the 30-minute merge goal, or refresh docs/ci-speed/index.html. Not for runtime FPS (perf-loop). +description: Run ThreeNative's CI speed loop — measure how long a change takes to merge, name the biggest cost from the first failing jobs and queue wait, change one thing, re-measure the same window, record the iteration. Use when asked to speed up CI or the merge queue, work PRD-550, check progress toward the 30-minute merge goal, or read the CI speed tab of `pnpm dashboard`. Not for runtime FPS (perf-loop). --- # CI speed loop @@ -20,11 +20,11 @@ node scripts/ci-merge-latency.mjs --since 2026-10-09 # measure only, pr node scripts/ci-speed-loop.mjs record --since 2026-10-09 --label "gating board" --notes "..." [--decision keep] node scripts/ci-speed-loop.mjs show # the ledger node scripts/ci-speed-loop.mjs discard --iteration 3 --reason "queue outage, 2 PRs only" -node scripts/ci-speed-loop.mjs report # regenerate the HTML only +pnpm dashboard # the CI speed tab renders the ledger ``` -`docs/ci-speed/ledger.json` is the record. `docs/ci-speed/index.html` is generated from it and opens with -`file://`. Never edit either by hand. Commit both with the change they measure. +`docs/ci-speed/ledger.json` is the record; the CI speed tab of `pnpm dashboard` renders it on each load +(PRD-751). Do not edit it by hand. Commit it with the change it measures. ## The turn diff --git a/AGENTS.md b/AGENTS.md index 86e35c1c3a..f2f80f04e0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -81,7 +81,7 @@ pnpm native:build # opt-in; downloads deps, compiles th pnpm native:verify:desktop # 300 native frames + a non-blank screenshot ``` -**CI.** The pre-push hook runs `pnpm ci:fast` drift checks only; it proves no runtime behaviour. `pnpm ci:local --full` runs the full board; `pnpm ci:local --affected --base origin/develop --target develop` runs what CI would select (dirty or unknown inputs fall back to full). A change touching only Markdown that no fixture consumes (not `AGENTS.md`/`CLAUDE.md`), or only the generated speed-loop record (`docs/ci-speed/ledger.json`, `docs/ci-speed/index.html`, `docs/ci-speed/runs/.jobs.json`), runs no product CI job. Ordinary PRs into develop use the complete candidate diff: CI-only changes run CI contracts, exact template changes run scaffolder contracts and affected templates, and shared executable inputs earn the thin review lane (PRD-731): scope, typecheck, lint, budgets, secrets scan, the unit tests that import a changed file, `test` and `build-artifacts` for a package change, and one template smoke, about 10 minutes; draft PRs run nothing. Template instruction and Markdown changes retain their contract lanes without adding runtime kits. Merge groups run the full selected board once, name the first red job in a PR comment and cancel the rest of the run, so a change pays for one board (PRD-550, PRD-731); `ci-required` rejects a review plan anywhere but a PR into develop, and `pnpm ci:local --affected` runs the queue's board. Develop pushes, explicit full runs, unknown inputs, main qualification and scheduled audits use fresh exhaustive qualification: all shipped templates, both golden journeys and the full supported native matrix. `ci-required` independently rejects a review plan for these events; Integration is a required reusable join on the same candidate, with each selected canonical job proven by a receipt from its latest attempt in the current run and an independently checked completed-success API job; missing, skipped, cancelled, stale or unmapped receipts fail closed. So a red job on an unchanged head needs only `gh run rerun --failed`, not a full run; a fix still needs a push. Native per-leg authoritative completion and cold-path queue feasibility remain follow-ups before deferring any Android PR coverage. Template scenarios run once per selected kit; develop changes run one representative golden journey and full qualification retains both starter and platformer journeys. `ci-required` rejects missing or failed selected checks; retained contexts include `supply-chain`, `template-nonvisual`, `golden-path`, and native `desktop-parity` when applicable. Native host/platform tests run for native or shared runtime consumers, main qualification and unresolved scopes. Unsafe matrix/reusable verdict reuse stays disabled until authoritative complete coverage and per-leg routing exist. One exception (PRD-730): a merge group whose tree equals the tree of its pull request's head, which has a green full `ci.yml` run an author dispatched (a review-lane PR run never counts), reuses that run; `ci-required` re-decides it and writes a receipt, and any doubt runs the board. A new candidate runs fresh tests; only exact-run immutable build artifacts are shared. Details: `docs/PRDs/done/PRD-483-impact-driven-ci-without-repeated-template-setup.md` and `docs/PRDs/done/PRD-373-selective-ci-and-develop-promotion.md`. +**CI.** The pre-push hook runs `pnpm ci:fast` drift checks only; it proves no runtime behaviour. `pnpm ci:local --full` runs the full board; `pnpm ci:local --affected --base origin/develop --target develop` runs what CI would select (dirty or unknown inputs fall back to full). A change touching only Markdown that no fixture consumes (not `AGENTS.md`/`CLAUDE.md`), or only the generated speed-loop record (`docs/ci-speed/ledger.json`, `docs/ci-speed/runs/.jobs.json`), runs no product CI job. Ordinary PRs into develop use the complete candidate diff: CI-only changes run CI contracts, exact template changes run scaffolder contracts and affected templates, and shared executable inputs earn the thin review lane (PRD-731): scope, typecheck, lint, budgets, secrets scan, the unit tests that import a changed file, `test` and `build-artifacts` for a package change, and one template smoke, about 10 minutes; draft PRs run nothing. Template instruction and Markdown changes retain their contract lanes without adding runtime kits. Merge groups run the full selected board once, name the first red job in a PR comment and cancel the rest of the run, so a change pays for one board (PRD-550, PRD-731); `ci-required` rejects a review plan anywhere but a PR into develop, and `pnpm ci:local --affected` runs the queue's board. Develop pushes, explicit full runs, unknown inputs, main qualification and scheduled audits use fresh exhaustive qualification: all shipped templates, both golden journeys and the full supported native matrix. `ci-required` independently rejects a review plan for these events; Integration is a required reusable join on the same candidate, with each selected canonical job proven by a receipt from its latest attempt in the current run and an independently checked completed-success API job; missing, skipped, cancelled, stale or unmapped receipts fail closed. So a red job on an unchanged head needs only `gh run rerun --failed`, not a full run; a fix still needs a push. Native per-leg authoritative completion and cold-path queue feasibility remain follow-ups before deferring any Android PR coverage. Template scenarios run once per selected kit; develop changes run one representative golden journey and full qualification retains both starter and platformer journeys. `ci-required` rejects missing or failed selected checks; retained contexts include `supply-chain`, `template-nonvisual`, `golden-path`, and native `desktop-parity` when applicable. Native host/platform tests run for native or shared runtime consumers, main qualification and unresolved scopes. Unsafe matrix/reusable verdict reuse stays disabled until authoritative complete coverage and per-leg routing exist. One exception (PRD-730): a merge group whose tree equals the tree of its pull request's head, which has a green full `ci.yml` run an author dispatched (a review-lane PR run never counts), reuses that run; `ci-required` re-decides it and writes a receipt, and any doubt runs the board. A new candidate runs fresh tests; only exact-run immutable build artifacts are shared. Details: `docs/PRDs/done/PRD-483-impact-driven-ci-without-repeated-template-setup.md` and `docs/PRDs/done/PRD-373-selective-ci-and-develop-promotion.md`. Registry commands read auth from `~/.npmrc`; pass a checkout-local untracked `.npmrc` with `npm --userconfig .npmrc `. Never print either file. TypeScript 7 `strict` (the native `tsc`); `typescript` imports use the official TypeScript 6 compatibility API for AST scripts and tsup declarations, **ESM only**; relative imports end in `.js`. Versions come from the `catalog:` in `pnpm-workspace.yaml`, templates excepted. Biome formats — never hand-format. Interfaces are `I`-prefixed; classes and type aliases are not. Unit tests: `/__tests__/*.spec.ts`, vitest, node environment, DOM and GPU stubbed. diff --git a/CLAUDE.md b/CLAUDE.md index 2df6036c12..e6ba10401c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -83,7 +83,7 @@ pnpm native:build # opt-in; downloads deps, compiles th pnpm native:verify:desktop # 300 native frames + a non-blank screenshot ``` -**CI.** The pre-push hook runs `pnpm ci:fast` drift checks only; it proves no runtime behaviour. `pnpm ci:local --full` runs the full board; `pnpm ci:local --affected --base origin/develop --target develop` runs what CI would select (dirty or unknown inputs fall back to full). A change touching only Markdown that no fixture consumes (not `AGENTS.md`/`CLAUDE.md`), or only the generated speed-loop record (`docs/ci-speed/ledger.json`, `docs/ci-speed/index.html`, `docs/ci-speed/runs/.jobs.json`), runs no product CI job. Ordinary PRs into develop use the complete candidate diff: CI-only changes run CI contracts, exact template changes run scaffolder contracts and affected templates, and shared executable inputs earn the thin review lane (PRD-731): scope, typecheck, lint, budgets, secrets scan, the unit tests that import a changed file, `test` and `build-artifacts` for a package change, and one template smoke, about 10 minutes; draft PRs run nothing. Template instruction and Markdown changes retain their contract lanes without adding runtime kits. Merge groups run the full selected board once, name the first red job in a PR comment and cancel the rest of the run, so a change pays for one board (PRD-550, PRD-731); `ci-required` rejects a review plan anywhere but a PR into develop, and `pnpm ci:local --affected` runs the queue's board. Develop pushes, explicit full runs, unknown inputs, main qualification and scheduled audits use fresh exhaustive qualification: all shipped templates, both golden journeys and the full supported native matrix. `ci-required` independently rejects a review plan for these events; Integration is a required reusable join on the same candidate, with each selected canonical job proven by a receipt from its latest attempt in the current run and an independently checked completed-success API job; missing, skipped, cancelled, stale or unmapped receipts fail closed. So a red job on an unchanged head needs only `gh run rerun --failed`, not a full run; a fix still needs a push. Native per-leg authoritative completion and cold-path queue feasibility remain follow-ups before deferring any Android PR coverage. Template scenarios run once per selected kit; develop changes run one representative golden journey and full qualification retains both starter and platformer journeys. `ci-required` rejects missing or failed selected checks; retained contexts include `supply-chain`, `template-nonvisual`, `golden-path`, and native `desktop-parity` when applicable. Native host/platform tests run for native or shared runtime consumers, main qualification and unresolved scopes. Unsafe matrix/reusable verdict reuse stays disabled until authoritative complete coverage and per-leg routing exist. One exception (PRD-730): a merge group whose tree equals the tree of its pull request's head, which has a green full `ci.yml` run an author dispatched (a review-lane PR run never counts), reuses that run; `ci-required` re-decides it and writes a receipt, and any doubt runs the board. A new candidate runs fresh tests; only exact-run immutable build artifacts are shared. Details: `docs/PRDs/done/PRD-483-impact-driven-ci-without-repeated-template-setup.md` and `docs/PRDs/done/PRD-373-selective-ci-and-develop-promotion.md`. +**CI.** The pre-push hook runs `pnpm ci:fast` drift checks only; it proves no runtime behaviour. `pnpm ci:local --full` runs the full board; `pnpm ci:local --affected --base origin/develop --target develop` runs what CI would select (dirty or unknown inputs fall back to full). A change touching only Markdown that no fixture consumes (not `AGENTS.md`/`CLAUDE.md`), or only the generated speed-loop record (`docs/ci-speed/ledger.json`, `docs/ci-speed/runs/.jobs.json`), runs no product CI job. Ordinary PRs into develop use the complete candidate diff: CI-only changes run CI contracts, exact template changes run scaffolder contracts and affected templates, and shared executable inputs earn the thin review lane (PRD-731): scope, typecheck, lint, budgets, secrets scan, the unit tests that import a changed file, `test` and `build-artifacts` for a package change, and one template smoke, about 10 minutes; draft PRs run nothing. Template instruction and Markdown changes retain their contract lanes without adding runtime kits. Merge groups run the full selected board once, name the first red job in a PR comment and cancel the rest of the run, so a change pays for one board (PRD-550, PRD-731); `ci-required` rejects a review plan anywhere but a PR into develop, and `pnpm ci:local --affected` runs the queue's board. Develop pushes, explicit full runs, unknown inputs, main qualification and scheduled audits use fresh exhaustive qualification: all shipped templates, both golden journeys and the full supported native matrix. `ci-required` independently rejects a review plan for these events; Integration is a required reusable join on the same candidate, with each selected canonical job proven by a receipt from its latest attempt in the current run and an independently checked completed-success API job; missing, skipped, cancelled, stale or unmapped receipts fail closed. So a red job on an unchanged head needs only `gh run rerun --failed`, not a full run; a fix still needs a push. Native per-leg authoritative completion and cold-path queue feasibility remain follow-ups before deferring any Android PR coverage. Template scenarios run once per selected kit; develop changes run one representative golden journey and full qualification retains both starter and platformer journeys. `ci-required` rejects missing or failed selected checks; retained contexts include `supply-chain`, `template-nonvisual`, `golden-path`, and native `desktop-parity` when applicable. Native host/platform tests run for native or shared runtime consumers, main qualification and unresolved scopes. Unsafe matrix/reusable verdict reuse stays disabled until authoritative complete coverage and per-leg routing exist. One exception (PRD-730): a merge group whose tree equals the tree of its pull request's head, which has a green full `ci.yml` run an author dispatched (a review-lane PR run never counts), reuses that run; `ci-required` re-decides it and writes a receipt, and any doubt runs the board. A new candidate runs fresh tests; only exact-run immutable build artifacts are shared. Details: `docs/PRDs/done/PRD-483-impact-driven-ci-without-repeated-template-setup.md` and `docs/PRDs/done/PRD-373-selective-ci-and-develop-promotion.md`. Registry commands read auth from `~/.npmrc`; pass a checkout-local untracked `.npmrc` with `npm --userconfig .npmrc `. Never print either file. TypeScript 7 `strict` (the native `tsc`); `typescript` imports use the official TypeScript 6 compatibility API for AST scripts and tsup declarations, **ESM only**; relative imports end in `.js`. Versions come from the `catalog:` in `pnpm-workspace.yaml`, templates excepted. Biome formats — never hand-format. Interfaces are `I`-prefixed; classes and type aliases are not. Unit tests: `/__tests__/*.spec.ts`, vitest, node environment, DOM and GPU stubbed. diff --git a/docs/PRDs/CI/PRD-550-a-change-merges-within-30-minutes.md b/docs/PRDs/CI/PRD-550-a-change-merges-within-30-minutes.md index 3b949ac503..d47fd096f5 100644 --- a/docs/PRDs/CI/PRD-550-a-change-merges-within-30-minutes.md +++ b/docs/PRDs/CI/PRD-550-a-change-merges-within-30-minutes.md @@ -145,7 +145,7 @@ qualifying run. A PR can still opt in to qualifying on the merge path with a lab #### Phase 3: Capacity and the latency audit **Status:** IN PROGRESS **Files:** `scripts/ci-merge-latency.mjs` (new), `scripts/ci-speed-loop.mjs` (new), `docs/ci-speed/` (ledger and HTML dashboard), `.claude/skills/ci-speed-loop/`, `.github/workflows/ci.yml` and `.github/workflows/integration.yml` `runs-on` expressions, `scripts/__tests__/ci-structure.spec.ts`, `docs/PRDs/CI/EXECUTION-ORDER.md`. -**Implementation:** Route gating jobs to `vars.TN_RUNNER` with hosted fallback; route qualifying jobs to hosted. Write the audit script with `gh api` and no new dependency. `scripts/ci-speed-loop.mjs` records each audit window in `docs/ci-speed/ledger.json` and renders `docs/ci-speed/index.html`, a dashboard of every metric against its goal (skill: `ci-speed-loop`). +**Implementation:** Route gating jobs to `vars.TN_RUNNER` with hosted fallback; route qualifying jobs to hosted. Write the audit script with `gh api` and no new dependency. `scripts/ci-speed-loop.mjs` records each audit window in `docs/ci-speed/ledger.json` and feeds the CI speed tab of `pnpm dashboard` (PRD-751), a dashboard of every metric against its goal (skill: `ci-speed-loop`). - [x] The audit and the dashboard exist, reproduce this PRD's own table, and hold the baseline. proof: `pnpm exec vitest run scripts/__tests__/ci-speed-loop.spec.ts` 14 of 14; `node scripts/ci-merge-latency.mjs --since 2026-10-08` gives #461 104.2, #462 242.9, #457 432.7, #449 593.9, #455 626.9, #456 892.1, #465 131.9 min enqueue → merge (this PRD's table) and 8 `auto-exposure` first failures; ledger iteration #1 = baseline. **Investigation (2026-10-10):** The current hosted placement of the `template-nonvisual` template legs is intentional — the workflow comment pins them to hosted on purpose, to keep one routing variable from sending the whole burst to a single pool. No routing change is accepted pending queue and capacity proof. A rejected attempt to move the job onto `vars.TN_RUNNER` was reverted in full (`.github/workflows/ci.yml`, `scripts/__tests__/ci-structure.spec.ts`); nothing about this job's routing is changed here. **Repair (2026-10-10, balancer state):** `scripts/ci-runners.sh` `balance` caches `have_heavy`/`have_light` from its own last write, so a routing variable deleted outside the loop (author unknown; `up` documents the balancer as owner and `down` stops the balancer before it clears them) stays unset while the pool is idle and capacity routes to hosted. In scope: `scripts/ci-runners.sh` (`balance` only) and `scripts/__tests__/ci-runners.spec.ts`. Failure hypothesis: a cached `have_*` never re-reads the actual variable, so an external delete is invisible. Fix: one bounded `gh api repos/$repo/actions/variables` read per poll filtered to the two known routing names, exact expected-label compare, re-advertise while capacity stays debounced-on, skip advertising when the read fails. Live effect observed 2026-10-10T13:34:48Z: both routing variables are restored to their expected labels (`tn-local`, `tn-local-light`) with one `balance` controller running. On candidate `d6c1af145` (run 38056537726) the local build, test, integration and native jobs did run in CI, so variable restoration and job uptake are now observed, while latency acceptance stays open; the `exposure` lane measured 19.5 min in queue. That board failed cold boot: `verifySnap` timed out with `state.cutSampleFrames` expected 180 but last 89 in 60 sec, while the 20-launch `verifyColdBoot` check passed. Whether the cause is speed or the game is unproved and no assertion is loosened. The Windows failure triage is pending, so no full-green board is claimed. The local fake-`gh` harness stays the only behavioural proof of the balancer itself. diff --git a/docs/PRDs/done/assets/PRD-729-assets-ship-smallest-and-preload-fastest.md b/docs/PRDs/done/assets/PRD-729-assets-ship-smallest-and-preload-fastest.md index fe4acc5ce3..0acf748288 100644 --- a/docs/PRDs/done/assets/PRD-729-assets-ship-smallest-and-preload-fastest.md +++ b/docs/PRDs/done/assets/PRD-729-assets-ship-smallest-and-preload-fastest.md @@ -18,8 +18,8 @@ Phase 3 lever changes `packages/assets` defaults that every game inherits (+2). **Depends on:** [PRD-351](../../assets/PRD-351-compression-never-looks-worse-than-a-floor.md) (the quality floor that every lever must hold), [PRD-496](../../performance/PRD-496-the-loading-screen-is-bytes-not-files.md) (the loader fan-out and progress work; its probe method is the preload method here). -**Dashboard:** [`docs/asset-perf/index.html`](../../../asset-perf/index.html), generated from -[`docs/asset-perf/ledger.json`](../../../asset-perf/ledger.json) by `node scripts/asset-perf-loop.mjs report`. +**Dashboard:** the Asset perf tab of `pnpm dashboard` (PRD-751) renders +[`docs/asset-perf/ledger.json`](../../../asset-perf/ledger.json) on each load. ## Context diff --git a/docs/PRDs/performance/PRD-573-unreal-class-performance-in-release-cycles.md b/docs/PRDs/performance/PRD-573-unreal-class-performance-in-release-cycles.md index 5879adc8e9..2278b43af0 100644 --- a/docs/PRDs/performance/PRD-573-unreal-class-performance-in-release-cycles.md +++ b/docs/PRDs/performance/PRD-573-unreal-class-performance-in-release-cycles.md @@ -160,7 +160,7 @@ Cherry-picks of F `c1ef353ca 2007efe12 3d3695197 3b29482a6 504709d17 726df7f5c d #### Phase v1.9: The scorecard **Status:** NOT STARTED Today: `perf` has p95, p99 and hitches but no memory or host load; `bench:scoreboard` has medians only; the R-ladder runs only on legacy and Godot arms; `run-godot.ts` has no workloads, so Godot ratios exist only on the ladders; `profile:wasm-page --load` means load phases, not host load; no soak command exists. -- [x] `pnpm perf:live` serves a page that re-reads every bench report under `artifacts/engine-load-test` every 2 s and plots p50 per workload and arm, with a red ring for a >30% regression against the series best. proof: `pnpm exec vitest run scripts/__tests__/perf-live.spec.ts` (2 passed) and a browser check against the 505 recorded pilot points, 2026-10-11. Revamped 2026-10-12: cycle tabs v1 to v5, native-vs-legacy KPI tiles against the 2x target, epic progress and gate pills read live from this PRD (`--prd`), `epicOf` spec; the server watches `artifacts/engine-load-test/prd-573` only. Checked in a browser against the 18 first A/B reports. +- [x] `pnpm dashboard` serves a page that re-reads every bench report under `artifacts/engine-load-test` every 2 s and plots p50 per workload and arm, with a red ring for a >30% regression against the series best. proof: `pnpm exec vitest run scripts/__tests__/perf-live.spec.ts` (2 passed) and a browser check against the 505 recorded pilot points, 2026-10-11. Revamped 2026-10-12: cycle tabs v1 to v5, native-vs-legacy KPI tiles against the 2x target, epic progress and gate pills read live from this PRD (`--prd`), `epicOf` spec; the server watches `artifacts/engine-load-test/prd-573` only. Checked in a browser against the 18 first A/B reports. - [x] `bench:engines` and `bench:scoreboard` rows carry p95, p99, the hitch count, draw calls and pipeline compiles, and fail closed on a missing counter. proof: a red-green case in the engine-load-test report spec Result 2026-10-11: 147 engine-load-test specs green, red first (14 failures); Godot rows are exempt from `pipelineCompiles` only (Godot load test has no counter; `ponytail:` note in `report.ts`), a Godot rung with no draw call still fails. Unverified: the counter has not run in a real three/WebGPU renderer. - [ ] `perf`, `bench:engines` and `profile:wasm-page` carry `hostLoad` and the core count, and mark absolutes `invalid-absolute` above half the core count. proof: a red-green case per spec with a stubbed load Partial: `bench:engines` and `perf` stamp `hostLoad` and `invalid-absolute` (stubbed-load specs green, 4 red first for `perf`); `profile:wasm-page` is slice 7 and does not exist yet. - [ ] Memory (partial 2026-10-12, arm run reviewed by the lead: `parseProcStatusMemory` and `parseTotalPssMb` pure parsers fail closed with named errors, `--executable` reads `VmHWM`, 4 specs red then green; the `--logcat` PSS wiring and `profile:wasm-page --memory` are not exercised, no desktop or emulator run yet): `perf --executable` peak RSS (`VmHWM`), `perf --logcat` PSS (`dumpsys meminfo `), `profile:wasm-page --memory` (Wasm memory, JS heap). proof: red-green cases in `perf.spec.ts` and the profile-wasm-page spec, then one desktop run and one emulator run diff --git a/docs/PRDs/performance/PRD-610-native-engine-wins-on-real-games.md b/docs/PRDs/performance/PRD-610-native-engine-wins-on-real-games.md index 5fa1a44e08..aa4f9854b5 100644 --- a/docs/PRDs/performance/PRD-610-native-engine-wins-on-real-games.md +++ b/docs/PRDs/performance/PRD-610-native-engine-wins-on-real-games.md @@ -172,7 +172,7 @@ The boxes are under the execution phases. Phase 1 holds AC-1 and AC-2 (done). Ph | Animation off the critical path | `AnimationPlayer.update` → C++ mixer step and jobs | per-rig `mixer.update` calls and JS stride on native | AC-17 | | Engine JS in C++ | `three-native/src/audio.ts`, `core/src/ui-state.ts`, `core/src/ui-bridge.ts`, player shim `core-three.mjs`, `three-native/src/object-surface.ts` | JS pose sync, per-publish validation, per-read crossings | AC-18 | | HUD under one frame | WebKit overlay snapshot mailbox → frame blend | — | AC-5 | -| Final-tier result | `pnpm perf:live` hero | R5 hero becomes the cheap-tier row | AC-19 | +| Final-tier result | `pnpm dashboard` hero | R5 hero becomes the cheap-tier row | AC-19 | ## Decisions diff --git a/docs/PRDs/starter-kits/PRD-751-one-dashboard.md b/docs/PRDs/starter-kits/PRD-751-one-dashboard.md index 5173947700..2adf159931 100644 --- a/docs/PRDs/starter-kits/PRD-751-one-dashboard.md +++ b/docs/PRDs/starter-kits/PRD-751-one-dashboard.md @@ -1,7 +1,7 @@ # PRD-751 — One dashboard: `pnpm dashboard` holds every KPI tab -**Status:** NOT STARTED -**Priority:** P2 — the owner cannot see what the loops do from one place: CI speed and asset perf still render separate pages, and the kit-loop KPIs have no view (all phases open) +**Status:** PARTIAL — Phase 1 done +**Priority:** P2 — the kit-loop KPIs have no view yet and the native-performance-loop skill still asks for its own page (Phase 2 open) **Complexity:** 2 (LOW) — 6–10 files +2; risk override: none **Owner:** João Paulo Furtado **Depends on:** None @@ -53,12 +53,12 @@ See the phase boxes. Each box names its proof. #### Phase 1: The hub and its two folded tabs -**Status:** NOT STARTED +**Status:** DONE **Files:** `package.json`, `scripts/perf-live.ts`, `scripts/perf-live.html`, `scripts/ci-speed-loop.mjs`, `scripts/asset-perf-loop.mjs`, the two deleted `index.html` files and their consumers. -- [ ] AC-1 [local]: `pnpm dashboard` serves the existing tabs plus CI speed and Asset perf, read from their ledgers; a missing or malformed ledger shows `pending` or `invalid`. proof: `pnpm exec vitest run scripts/__tests__/perf-live.spec.ts` -- [ ] AC-2 [local]: no tracked file writes or links a removed page. proof: `rg -n "ci-speed/index.html|asset-perf/index.html|perf:live" --glob '!docs/PRDs/done/**'` empty, `pnpm check:docs`, `pnpm exec vitest run scripts/__tests__/ci-efficiency.spec.ts` -- [ ] AC-3 [local]: the hub renders all tabs in a real browser with no console error. proof: headed screenshot per tab on the PR +- [x] AC-1 [local]: `pnpm dashboard` serves the existing tabs plus CI speed and Asset perf, read from their ledgers; a missing or malformed ledger shows `pending` or `invalid`. proof: `pnpm exec vitest run scripts/__tests__/perf-live.spec.ts` — red 3 failed before `ledgerTab`/`withNav`, then 29/29 green; with the ci-speed and asset-perf specs 49/49 (2026-10-26) +- [x] AC-2 [local]: no tracked file writes or links a removed page. proof: `rg -n "ci-speed/index.html|asset-perf/index.html|perf:live" --glob '!docs/PRDs/done/**'` empty, `pnpm check:docs`, `pnpm exec vitest run scripts/__tests__/ci-efficiency.spec.ts` — rg empty apart from this PRD's own history, check:docs exit 0 (3558 links), ci-efficiency 55/55; the `report` and `serve` commands are gone (2026-10-26) +- [x] AC-3 [local]: the hub renders all tabs in a real browser with no console error. proof: headed screenshot per tab on the PR — headed Chromium under Xvfb, 5 tabs, 0 console errors, nav identical on each; images on the `prd-751-screenshots` branch (2026-10-26) #### Phase 2: The kit-loop tab and the skill pointer diff --git a/docs/asset-perf/index.html b/docs/asset-perf/index.html deleted file mode 100644 index 304dcd1e09..0000000000 --- a/docs/asset-perf/index.html +++ /dev/null @@ -1,79 +0,0 @@ - - -Asset pipeline performance -
-

Asset pipeline performance

-

Latest iteration #14 of 14: quiet measurement of the kept build (lever 6). Snapshot 2026-10-25T04:39:30.714Z. Regenerate with node scripts/asset-perf-loop.mjs report; the page is a static render of docs/asset-perf/ledger.json. Goal: the template corpus ships as few bytes as it can and preloads to GPU-ready as fast as it can, with no texture below its quality floor (PRD-729).

-

Progress toward the goal

-
- - - -
MetricBaselineNowGoalProgress
Bytes shipped for the template corpus5.37 MiB3.68 MiB≤ 3 MiB 71%open
Shipped bytes as a share of source bytes45.1%30.9%≤ 25 % 71%open
Preload to GPU-ready, localhost629 ms372 ms≤ 450 ms 100%met
Preload to GPU-ready, 40 Mbps / 20 ms1705 ms1162 ms≤ 1200 ms 100%met
-

Where the bytes go

-
KindFilesSourceShippedShipped/source
model67.04 MiB3.19 MiB45.3%
texture30.27 MiB0.27 MiB100.0%
audio64.59 MiB0.21 MiB4.7%
-
Largest shipped files, latest iteration (solid = shipped, pale = source)
shared/player-viewmodel.glb1.43 MiB
shared/mannequin-combat.glb0.81 MiB
sailing/ship.glb0.70 MiB
shared/sky.jpg0.27 MiB
shared/mannequin.glb0.25 MiB
rain/thunder.wav0.10 MiB
rain/rain-loop.wav0.05 MiB
rain/wind-loop.wav0.04 MiB
snow/wind.wav0.02 MiB
snow/crunch.wav0.01 MiB
starter/pickup.wav0.00 MiB
puzzle/native-proof.glb0.00 MiB
-

Trend per iteration

-
Bytes shipped for the template corpus (lower is better)
- - - -3 -goal ≤ 3 MiB -5 -#3: 5.37 MiB#3#5: 4.94 MiB#5#6: 4.42 MiB#6#7: 4.33 MiB#7#8: 4.33 MiB#8#9: 3.78 MiB#9#10: 3.68 MiB#10#11: 3.55 MiB#11#14: 3.68 MiB#14
Shipped bytes as a share of source bytes (lower is better)
- - - -25 -goal ≤ 25 % -45 -#3: 45.1%#3#5: 41.5%#5#6: 37.2%#6#7: 36.4%#7#8: 36.4%#8#9: 31.7%#9#10: 30.9%#10#11: 29.8%#11#14: 30.9%#14
Preload to GPU-ready, localhost (lower is better)
- - - -450 -goal ≤ 450 ms -1109 -#3: 629 ms#3#5: 830 ms#5#6: 621 ms#6#7: 684 ms#7#8: 1036 ms#8#9: 1109 ms#9#10: 569 ms#10#11: 416 ms#11#14: 372 ms#14
Preload to GPU-ready, 40 Mbps / 20 ms (lower is better)
- - - -1200 -goal ≤ 1200 ms -1892 -#3: 1705 ms#3#5: 1892 ms#5#6: 1545 ms#6#7: 1611 ms#7#8: 1838 ms#8#9: 1823 ms#9#10: 1322 ms#10#11: 1122 ms#11#14: 1162 ms#14
-

Iterations

-
#DateCommitChangeDecisionShippedShipped/sourcePreload localPreload 40 MbpsCook
#12026-10-246da4a6606default cook, what a game ships today
superseded: re-recorded as #3 with preload
arm default {}
baseline5.37 MiB45.1%pendingpending55.1 s
#22026-10-246da4a6606uncooked control, every byte as authored
arm none {"textures":"none","models":"none","audio":"none"}
control11.91 MiB100%pendingpending0.7 s
#32026-10-24ed1764ca1default cook with preload, what a game ships today
arm default {}
baseline5.37 MiB45.1%629 ms1705 ms39.5 s
#42026-10-244a7848209 dirtylever 1: meshopt FILTER (animation quaternion/exponential filters)
superseded: control used the same rebuilt code, so the pair was A/A; bytes 4.94 MiB are valid
arm default {} keeps keyframe times; packages/assets/src/passes/model.ts
provisional4.94 MiB41.5%799 ms2104 ms40.5 s
#52026-10-244a7848209 dirtylever 1: meshopt FILTER (animation quaternion/exponential filters)
arm default {} keeps keyframe times; packages/assets/src/passes/model.ts; control = baseline build 6da4a6606 | worst rotation error 0.712 deg vs 0.041 baseline, translation 0.97 mm vs 0; paired preload within noise (830 vs 798 ms, 1892 vs 1970 ms); -8.0% bytes
reject4.94 MiB41.5%830 ms1892 ms51.2 s
#62026-10-24a3ceb360d dirtylever 2: drop animation tracks that hold the rest pose
arm default {} packages/assets/src/passes/prune-rest-tracks.ts; control = baseline build 6da4a6606 | visual judge NEUTRAL (114 poses x2 runs, 35 isolated pixels over 8 levels, no connected region); mixer worst node diff <1e-5; templates gate 7/8: snow production-performance red 3 of 3 runs, snow ships no model and its cook is byte-identical to the control, not yet run on a clean develop build | visual judge NEUTRAL (114 poses x2 runs, 35 isolated pixels over 8 levels, no connected region); mixer worst node diff <1e-5; templates gate 7/8: snow production-performance red 3 of 3 runs, snow ships no model and its cook is byte-identical to the control, not yet run on a clean develop build | visual judge NEUTRAL (114 poses x2 runs, 35 isolated pixels over 8 levels, no connected region); mixer worst node diff <1e-5; templates gate 7/8: snow production-performance red 3 of 3 runs, snow ships no model and its cook is byte-identical to the control, not yet run on a clean develop build | visual judge NEUTRAL (114 poses x2 runs, 35 isolated pixels over 8 levels, no connected region); mixer worst node diff <1e-5; templates gate 7/8: snow production-performance red 3 of 3 runs, snow ships no model and its cook is byte-identical to the control, not yet run on a clean develop build
keep4.42 MiB37.2%621 ms1545 ms47.4 s
#72026-10-24a1d5b60ef dirtylever 3: drop spec-default JSON fields from the GLB (normalized false, byteOffset 0, interpolation LINEAR)
arm default {} 4.42 to 4.33 MiB, preload not worse vs paired control (local 684 vs 699 ms, 40 Mbps 1611 vs 1646 ms); 114 animated poses pixel-identical (0 differing pixels), so no judge subagent; 7 model-bearing templates pass the gate (95 scenarios, exit 0); snow has no model
keep4.33 MiB36.4%684 ms1611 ms35.6 s
#82026-10-24a97ccfe04lever 4: start Basis transcoder when the manifest lists Basis output
arm default {} Measured under host load 30-138 from other agents. The absolute 1036/1838 ms is not comparable to the quiet baseline; this lever was rejected because the transcoder loaded eagerly.
reject4.33 MiB36.4%1036 ms1838 ms40.3 s
#92026-10-24290f8f3f5 dirtylever 5: KTX2 only when it repays the Basis transcoder
arm default {} Measured under host load; the paired control on the same host was 1483/2086 ms and the candidate 1109/1823 ms. The quiet measurement of the kept build is iteration #14 (372/1162 ms). | Owner 2026-10-24 accepts +3.67 MB GPU texture memory on player-viewmodel for -0.55 MiB and faster preload; quiet rerun #14 meets both preload goals; the transcoder charge is now decided once per build (compileAssets), so a build that ships the transcoder anyway keeps KTX2 model maps.
keep3.78 MiB31.7%1109 ms1823 ms45.4 s
#102026-10-242999fbc35 dirtylever 6: JPEG rung for opaque colour maps
arm default {} Measured in a quiet window; paired control (lever 5) 619/1448 ms, candidate 569/1322 ms. Judge NEUTRAL; shooter and action-rpg gates pass on the quiet RTX.
keep3.68 MiB30.9%569 ms1322 ms30.5 s
#112026-10-24a847bd2fe dirtylever 7: standalone textures take the smallest passing JPEG (sky.jpg)
arm default {} 3.68 -> 3.55 MiB, but local preload rose in three paired runs (+16, +33, +23 ms, 4-8%) for a ~5% throttled gain; the loop rule rejects a lever that worsens the other metric.
reject3.55 MiB29.8%416 ms1122 ms34.3 s
#122026-10-24a847bd2fe dirtylever 7 rerun: standalone JPEG rung
superseded: rerun of the same rejected lever
arm default {}
provisional3.55 MiB29.8%459 ms1136 ms26.2 s
#132026-10-24a847bd2fe dirtylever 7 rerun 2: standalone JPEG rung
superseded: rerun of the same rejected lever
arm default {}
provisional3.55 MiB29.8%402 ms1115 ms25.2 s
#142026-10-24a847bd2fequiet measurement of the kept build (lever 6)
arm default {}
keep3.68 MiB30.9%372 ms1162 ms26.6 s
-
diff --git a/docs/ci-speed/index.html b/docs/ci-speed/index.html deleted file mode 100644 index 7949f06174..0000000000 --- a/docs/ci-speed/index.html +++ /dev/null @@ -1,83 +0,0 @@ - - -CI speed loop -
-

CI speed loop

-

Latest live iteration #36 of 37: current window refresh (develop since 2026-10-14). Snapshot 2026-10-17T21:56:44.620Z. Regenerate with node scripts/ci-speed-loop.mjs report, then reload; the page is a static render of docs/ci-speed/ledger.json. Goal: a change merges within 30 minutes of its last push (PRD-550).

-

Progress toward the goal

-
- - - - -
MetricBaselineNowGoalProgress
Last push to merge, median280.4 min147.8 min≤ 30 min 53%open
Last push to merge, p90838.3 min443 min≤ 45 min 50%open
Merge queue passes on the first attempt33.3%50%≥ 90 % 29%open
Merge-group board wall time, median77.8 min46 min≤ 15 min 51%open
Runner queue wait, p9041.4 min18.2 min≤ 3 min 60%open
-

Trend per iteration

-
Last push to merge, median (lower is better)
- - - -30 -goal ≤ 30 min -745 -#1: 280.4 min#1#5: 474.3 min#5#6: 475.9 min#6#7: 475.9 min#7#8: 475.9 min#8#9: 475.9 min#9#10: 475.9 min#10#11: 475.9 min#11#12: 475.9 min#12#13: 475.9 min#13#14: 475.9 min#14#15: 475.9 min#15#16: 477.4 min#16#17: 477.4 min#17#18: 744.5 min#18#19: 475.9 min#19#20: 474.3 min#20#36: 147.8 min#36
Last push to merge, p90 (lower is better)
- - - -45 -goal ≤ 45 min -1130 -#1: 838.3 min#1#5: 474.3 min#5#6: 477.1 min#6#7: 477.1 min#7#8: 477.1 min#8#9: 477.1 min#9#10: 477.1 min#10#11: 477.1 min#11#12: 477.1 min#12#13: 477.1 min#13#14: 477.1 min#14#15: 477.1 min#15#16: 1023.9 min#16#17: 1023.9 min#17#18: 1130.1 min#18#19: 1090.9 min#19#20: 1071.3 min#20#36: 443 min#36
Merge queue passes on the first attempt (higher is better)
- - - -90 -goal ≥ 90 % - -#1: 33.3%#1#5: 0%#5#6: 50%#6#7: 50%#7#8: 50%#8#9: 50%#9#10: 50%#10#11: 50%#11#12: 50%#12#13: 50%#13#14: 50%#14#15: 50%#15#16: 40%#16#17: 40%#17#18: 33.3%#18#19: 25%#19#20: 22.2%#20#36: 50%#36
Merge-group board wall time, median (lower is better)
- - - -15 -goal ≤ 15 min -119 -#1: 77.8 min#1#2: 119.1 min#2#3: 113.3 min#3#4: 113.3 min#4#5: 111.6 min#5#6: 109.9 min#6#7: 109.9 min#7#8: 109.9 min#8#9: 109.9 min#9#10: 109.9 min#10#11: 102.5 min#11#12: 95.1 min#12#13: 95.1 min#13#14: 95.1 min#14#15: 95.1 min#15#16: 95.1 min#16#17: 95.1 min#17#18: 107.9 min#18#19: 107.9 min#19#20: 95.1 min#20#36: 46 min#36
Runner queue wait, p90 (lower is better)
- - - -3 -goal ≤ 3 min -51 -#1: 41.4 min#1#2: 50.9 min#2#3: 49.9 min#3#4: 46.9 min#4#5: 47.2 min#5#6: 45 min#6#7: 42.9 min#7#8: 41.5 min#8#9: 40.7 min#9#10: 39 min#10#11: 38.9 min#11#12: 38.7 min#12#13: 38.5 min#13#14: 38.7 min#14#15: 38.6 min#15#16: 36.9 min#16#17: 41.5 min#17#18: 42.3 min#18#19: 42 min#19#20: 39.6 min#20#36: 18.2 min#36
First failing job per red merge group, latest iteration
native-platforms / Desktop web/native parity2
native-platforms / Windows desktop core1
-

Iterations

-
#DateCommitChangeDecisionWindow fromPush→merge p50Push→merge p90First attemptBoard wallQueue wait p90
#12026-10-0911cafc003baseline: exhaustive board on every merge group
Before PRD-550. 2026-10-08 to 2026-10-09 window, same period as the PRD Context.
baseline2026-10-08280.4838.333.3%77.841.4
#22026-10-0990dee0c83no change landed: the queue still runs the exhaustive board
Window since 2026-10-09. 0 PRs merged, so push->merge, p90 and first-attempt are n/a; board wall and queue wait measure the exhaustive merge-group board this change removes. Iteration #1 stays the baseline. Re-measure once 5 or more PRs merged.
provisional2026-10-09n/an/an/a119.150.9
#32026-10-09a47851026phase 1 in review
Same window as iteration 2: 0 PRs merged, 9 merge-group runs, board median 113.3 min, queue wait p90 49.9 min. Named cost: integration / velocity was the first failing job in 2 of 4 red merge groups, because its cost arm priced shared-runner load as render cost. The change is PRD-550 phase 1 (481824f3c, a47851026) on PR 476. It moves that arm to a scheduled lane and widens two compile budgets. The compile specs pass locally, 40 of 40 in 220 s. Re-measure after it merges. The PR head also shows an intermittent iOS simulator red, seen in 2 of 10 recent runs with different errors, on code this branch does not change.
provisional2026-10-09n/an/an/a113.349.9
#42026-10-096ec34f52cPR 476 green on phase 1 head
Same window: 0 PRs merged, 12 merge-group runs. PR 476 head 6ec34f52c is green: 69 checks pass and 12 skip, and merge state is CLEAN. integration / velocity passes on that head, and the iOS simulator job passes there too, so the iOS red from iteration 3 did not recur. Merge-group metrics do not move until phase 1 reaches develop. Named costs still on develop: integration / velocity and test-unit (3/4), each first in 2 red merge groups. The compile-spec budget change (a47851026) targets the test-unit timeouts. Re-measure after the merge.
provisional2026-10-09n/an/an/a113.346.9
#52026-10-093ca761875hourly refresh
One PR merged since the last tick: 458, last push to merge 474.3 min, first attempt failed at test-unit (2/4). The window now has 13 merge-group runs, board median 111.6 min, and queue wait p90 47.2 min over 885 jobs. First failing job in the red groups: integration / velocity 2, test-unit (3/4) 2, test-unit (2/4) 1, integration / capture 1, test-playtest 1. PR 476 is still open, so phase 1 is not on develop yet. Provisional: fewer than 5 PRs merged.
provisional2026-10-09474.3474.30%111.647.2
#62026-10-097789bf328hourly refresh
One new PR merged since the last tick, 470, after 4 merge-group attempts. Its first failing job was integration / velocity. The window now has 2 merged PRs, 470 and 458. Last push to merge median 475.9 min, p90 477.1 min. First attempt merge 50 percent. 16 merge-group runs, board median 109.9 min, queue wait p90 45 min over 1086 jobs. First failing job in the red groups: integration / velocity 2, test-unit (3/4) 2, integration / capture 1, template-nonvisual platformer 1. PR 476 is still open, so phase 1 is not on develop yet. Provisional: fewer than 5 PRs merged.
provisional2026-10-09475.9477.150%109.945
#72026-10-09aa13e02c1hourly refresh
No new PR merged since the last tick. The window still has 2 merged PRs, 470 and 458. It now has 18 merge-group runs, up from 16. Board median 109.9 min. Queue wait p90 42.9 min over 1227 jobs. Run success fell to 31.3 percent. First failing job in the 8 red groups: integration / velocity 4, test-unit (3/4) 2, integration / capture 1, template-nonvisual platformer 1. integration / velocity is the largest named cost, and phase 1 on PR 476 targets it. PR 476 is still open, so phase 1 is not on develop yet. Provisional: fewer than 5 PRs merged.
provisional2026-10-09475.9477.150%109.942.9
#82026-10-099f36ac3c8hourly refresh
No new PR merged since the last tick. The window still has 2 merged PRs, 470 and 458. It now has 20 merge-group runs, up from 18. Run success fell to 27.8 percent. Board median 109.9 min. Queue wait p90 41.6 min over 1376 jobs. First failing job in the red groups: integration / velocity 4, test-unit (3/4) 3, test-browser 2, integration / capture 1. test-browser is new in this list. integration / velocity is still the largest named cost, and phase 1 on PR 476 targets it. PR 476 is still open. Provisional: fewer than 5 PRs merged.
provisional2026-10-09475.9477.150%109.941.5
#92026-10-09cbd2e662ehourly refresh
No new PR merged since the last tick. The window still has 2 merged PRs, 470 and 458. It now has 24 merge-group runs, up from 20. Run success fell to 23.8 percent. Board median 109.9 min. Queue wait p90 41.2 min over 1579 jobs. First failing job in the red groups: integration / velocity 4, test-unit (3/4) 4, test-browser 2, integration / auto-exposure cold boot 1. integration / velocity and test-unit (3/4) are tied as the largest named costs. Phase 1 on PR 476 targets integration / velocity, and its compile-spec budget change targets test-unit timeouts. PR 476 is still open. Provisional: fewer than 5 PRs merged.
provisional2026-10-09475.9477.150%109.940.7
#102026-10-092e100adc4hourly refresh
No new PR merged since the last tick. The window still has 2 merged PRs, 470 and 458. It now has 26 merge-group runs, up from 24. Run success fell to 20.8 percent, the fourth straight drop. Board median 109.9 min. Queue wait p90 39 min over 1785 jobs, down from 41.2. First failing job in the red groups: test-unit (3/4) 5, integration / velocity 4, test-browser 2, integration / auto-exposure cold boot 1. test-unit (3/4) is now the largest named cost. Phase 1 on PR 476 includes a compile-spec budget change for test-unit timeouts, and it is not on develop yet. PR 476 is still open. Provisional: fewer than 5 PRs merged.
provisional2026-10-09475.9477.150%109.939
#112026-10-09f9a01bbe4hourly refresh
No new PR merged since the last tick. The window still has 2 merged PRs, 470 and 458. It now has 29 merge-group runs, up from 26. Run success is 22.2 percent, up from 20.8. Board median is 102.5 min, down from 109.9. Queue wait p90 is 38.4 min over 1993 jobs. First failing job in the 12 red groups: test-unit (3/4) 5, integration / velocity 4, test-browser 2, golden-path template platformer 1. The named costs are unchanged. A separate develop push run at b12b257f1 failed in test-native, and it is not counted in these groups. PR 476 is still open. Provisional: fewer than 5 PRs merged.
provisional2026-10-09475.9477.150%102.538.9
#122026-10-09f1e868d47hourly refresh
Catch-up tick after the 09:00 stop. No new PR merged. The window still has 2 merged PRs, 470 and 458. It now has 31 merge-group runs, up from 29. Run success is 24.1 percent, up from 22.2. Board median is 95.1 min, down from 102.5. Queue wait p90 is 38.7 min over 2118 jobs. First failing job in the 13 red groups: test-unit (3/4) 5, integration / velocity 4, test-browser 2, test-unit (1/4) 2. test-unit (1/4) is new in this list. The named costs are unchanged. PR 476 is still open. Provisional: fewer than 5 PRs merged.
provisional2026-10-09475.9477.150%95.138.7
#132026-10-091310367b6hourly refresh
No new PR merged since the last tick. The window still has 2 merged PRs, 470 and 458. It now has 33 merge-group runs, up from 31. Run success is 22.6 percent, down from 24.1. Board median is 95.1 min, unchanged. Queue wait p90 is 38.5 min over 2258 jobs. First failing job in the 14 red groups: integration / velocity 5, test-unit (3/4) 5, test-browser 2, test-unit (1/4) 2. integration / velocity rose by one. PR 476 is still open. Provisional: fewer than 5 PRs merged.
provisional2026-10-09475.9477.150%95.138.5
#142026-10-096639a5bafhourly refresh
No new PR merged since the last tick. The window still has 2 merged PRs, 470 and 458. It now has 35 merge-group runs, up from 33. Run success is 21.2 percent, down from 22.6. Board median is 95.1 min, unchanged. Queue wait p90 is 38.7 min over 2396 jobs. First failing job in the 15 red groups: integration / velocity 5, test-unit (3/4) 5, test-unit (2/4) 3, integration / auto-exposure cold boot 2. The list changed: test-unit (2/4) and auto-exposure cold boot appear, and test-browser and test-unit (1/4) drop out. The first attempt hit a transient GitHub 502, and the rerun succeeded. Develop still sits at b12b257f1, so the queue has no new merges. PR 476 is still open. Provisional: fewer than 5 PRs merged.
provisional2026-10-09475.9477.150%95.138.7
#152026-10-094c1bbd890hourly refresh
No new PR merged since the last tick. The window still has 2 merged PRs, 470 and 458. Develop still sits at b12b257f1, so the queue has no new merges. It now has 36 merge-group runs, up from 35. Run success is 20 percent, down from 21.2. Board median is 95.1 min, unchanged. Queue wait p90 is 38.6 min over 2476 jobs. First failing job in the 16 red groups: integration / velocity 5, test-unit (3/4) 5, test-browser 3, test-unit (2/4) 3. test-browser is back in the list. PR 476 is still open. Provisional: fewer than 5 PRs merged.
provisional2026-10-09475.9477.150%95.138.6
#162026-10-094d81de73chourly refresh
The window merged five PRs, which are 458, 470, 471, 473, and 468, and this matches the merged PR line that shows 5 PRs. The window held 42 merge group runs, with a run success of 19.4 percent and a first attempt merge of 40 percent. The merge group board median wall was 95.1 minutes, and the last push to merge median was 477.4 minutes with a p90 of 1023.9 minutes. The runner queue wait p90 was 36.9 minutes over 2845 jobs. The first failing job counts, largest first, were 5 for integration / velocity, 5 for test-unit (3/4), 4 for test-unit (2/4), 3 for test-browser, 2 for integration / auto-exposure, 2 for integration / auto-exposure cold boot, 2 for test-unit (1/4), and 1 for each of golden-path-template (platformer), integration / capture, native-platforms / iOS simulator runtime and no-Xcode consumer handoff, native-platforms / macOS desktop core, template-nonvisual (platformer), and test-playtest. Develop moved from b12b257f1 to 6b18d913e, and its CI run on that head was still queued when this row was written. None of these PRs carries the phase 1 change on PR 476, which is still open, so this window cannot judge that change, and the decision is inconclusive.
inconclusive2026-10-09477.41023.940%95.136.9
#172026-10-090a4ce848chourly refresh
Merged into develop since 2026-10-09 are 5 PRs, 473, 471, 470, 468 and 458. The merge-group run count is 42 and the run success percent is 18.4 with the first-attempt merge percent at 40. The board median wall is 95.1 minutes, and the last push to merge median is 477.4 minutes and p90 is 1023.9 minutes. The queue wait p90 is 40.4 minutes over 2873 jobs. The first failing job counts are 6 for integration / velocity, 5 for test-unit (2/4), 5 for test-unit (3/4), 3 for test-browser, 2 for integration / auto-exposure, 2 for integration / auto-exposure cold boot, 2 for test-unit (1/4), and 1 each for golden-path-template (platformer), integration / capture, native-platforms / iOS simulator runtime and no-Xcode consumer handoff, native-platforms / macOS desktop core, template-nonvisual (platformer) and test-playtest. Develop head is 6b18d913e and its CI run has finished with success. None of these PRs carry the phase 1 change on PR 476, which is still open, so this window cannot judge that change, and local commit 0a4ce848c adds a test only diagnostic and does not change any merge-group result, giving an inconclusive decision.
inconclusive2026-10-09477.41023.940%95.141.5
#182026-10-101652670eePR476 unmerged baseline refresh
PR476 remains unmerged. This is a fresh observation of develop, not a measured effect of PR476. Candidate CI run 38025989864 failed sailing diagnostics; ci-required correctly rejected it.
provisional2026-10-09744.51130.133.3%107.942.3
#192026-10-1077bb0ee57PR476 candidate 77bb in CI — unmerged baseline
Fresh develop baseline while PR476 remains unmerged. CI run38033595736 targets candidate77bb; verification is pending. This audit does not measure PR476 effect.
provisional2026-10-09475.91090.925%107.942
#202026-10-1077bb0ee57Current queue comparison — PR476 still unmerged
Refreshed after user requested current queue comparison and continued speed work. PR476 remains unmerged; no measured impact attribution.
provisional2026-10-09474.31071.322.2%95.139.6
#212026-10-1646c59e6b5historical reference: develop-push full board run 38824021621 (PRD-620 baseline)
Historical reference only: develop-push full-board run 38824021621 at 46c59e6b5, full wall 76m41s (4601s) from run_started_at 2026-10-15T23:30:48Z to 2026-10-16T00:47:29Z, including initial scope queue; job execution envelope 4553s is retained separately. V8 published-payload warm hit (job 116727720587, receipt check 405ms). Recipe differs in download-deps.mjs and PR508 changed workloads, so not a comparable current-candidate control; decision invalid, excluded from charts.
invalid2026-10-15n/an/an/an/an/a
#222026-10-16ad25e3cf3terminal diagnostic: develop-push full board run 38846474672 (PRD-620 cold V8 + cancelled; lint fixed)
Bounded diagnostic run terminated cancelled after lint failure and cold V8; retained failed lint + cancelled statuses honestly; later V8 payload asset available durably.
invalid2026-10-15n/an/an/an/an/a
#232026-10-16f02cc9e43hosted control: full-board workflow_dispatch run 38853722930 at f02cc9e43 (PRD-620 warm V8; cancelled after lint failure)
Hosted control only, not a qualifying sample. workflow_dispatch full-board run 38853722930 at f02cc9e43 (candidate f02cc9e43e04d13eebb3ae07710dc59e9a2d862f), run attempt 1, conclusion cancelled. V8 source payload job 116820922299 completed success with a warm published download (1708ms) matching recipe 1a2463b539e99c9fd7d87bd4bdd3ef86f2b61e42969a587f134e35f23d343607; all four recipe inputs are unchanged from run 38846474672, so the population is warm; no publisher mutation occurred. Lint job 116820830952 failed solely on Biome formatting of multiline JSON arrays in the existing ledger (fixed in this commit). The run was cancelled early, so the wall is short: overall run_started_at 2026-10-16T05:02:42Z to last completed job 2026-10-16T05:23:50Z is 1268s (21.1 min); the job envelope min started_at 2026-10-16T05:02:46Z to max completed_at 2026-10-16T05:23:50Z is 1264s, retained separately. All 72 job rows are embedded including labels, runner names, statuses, candidate and run attempt. Invalid, chart-excluded; generic merge-queue metrics null; no acceptance claim.
invalid2026-10-15n/an/an/an/an/a
#242026-10-16f8b2680a9matched hosted control: full-board workflow_dispatch run 38856119345 at f8b2680a9 (PRD-620 warm V8; all hosted, complete success incl. ci-required)
Matched hosted control, not a routed after-run sample: every job ran hosted, so no tn-local placement or routing gain is claimed. workflow_dispatch full-board run 38856119345 at f8b2680a9 (candidate f8b2680a99420ec49327fd009858c8dee07df293), run attempt 1, conclusion success. V8 source payload job 116828110559 completed success with a warm published download matching recipe 1a2463b539e99c9fd7d87bd4bdd3ef86f2b61e42969a587f134e35f23d343607 (published step 3307ms); all four recipe inputs are unchanged across ad25e3cf3/f02cc9e43/f8b2680, so the population is warm; no publisher mutation occurred. The run is full green including ci-required (job 116840273034, selection full, native matrix full, coverage receipts candidate f8b2680a99420ec49327fd009858c8dee07df293 runId 38856119345 attempt 1), which verifies AC-2's full-green criterion on this candidate. Whole wall: run_started_at 2026-10-16T05:30:20Z to last completed job 2026-10-16T06:16:24Z is 2764s (46.1 min); the job envelope min started_at 2026-10-16T05:30:24Z to max completed_at 2026-10-16T06:16:24Z is 2760s, retained separately. All 72 job rows are embedded with status, labels, runner names, created/started/completed timestamps, queue and duration, and a canonical class per row (46 HEAVY + 12 LIGHT + 14 HOSTED/skipped). The full-scope board is 72 rows from the final jobs API, not the 71 first expected. The AC-1 eligible population is the canonical HEAVY set: 30 keys / 46 instances (heavy queue p50 160.5s, p90 647.5s over all 46 including hosted overflow, not filtered by actual runner and not only the 19 new keys / 32 instances). Build/cache comparability remains unverified: native-ccache and native-third-party restore state is not measured here and differs per run key. Control only, chart-excluded; generic merge-queue metrics null; no acceptance claim.
invalid2026-10-15n/an/an/an/an/a
#252026-10-16f8b2680a9routed after-run attempt 1: full-board workflow_dispatch run 38861158440 at f8b2680a9 (PRD-620; all 46 HEAVY tn-local, cancelled after hosted iOS failure + local overload)
Routed after-run attempt 1, nonqualifying: full-board workflow_dispatch run 38861158440 at f8b2680a99420ec49327fd009858c8dee07df293 (same candidate and V8 recipe as iteration 24), run attempt 1, conclusion cancelled. All 46 canonical HEAVY instances routed tn-local (labels ['tn-local'], 0 hosted): 34 started, 12 cancelled before a slot. The run ended when the hosted iOS leg (job 116844117629, macos-15) failed with 'Expected exit 2 containing TN_PLAYTEST_BRIDGE_MISSING, got 0'; because the same-candidate control (iteration 24) passed this leg, the cause is UNVERIFIED and no stale-bundle or environment cause is asserted. Root then cancelled the run; cancellation cascaded into three post-cancellation failures that are consequences, not independent defects: integration required-receipts (116853277982), native required-coverage receipt (116853579336, missing release-reports leg) and ci-required (116853970840). The routed local V8 source payload (job 116844117556, tn-local) succeeded with a warm published download (v8.published 1777ms) on the unchanged recipe 1a2463b539e99c9fd7d87bd4bdd3ef86f2b61e42969a587f134e35f23d343607; no publisher mutated. Whole wall run_started_at 2026-10-16T06:25:53Z to the latest job completed_at 2026-10-16T07:02:06Z is 2173s (36.2 min); the job envelope min started_at to max completed_at is 2169s, retained separately; the run's terminal updated_at 2026-10-16T07:02:07Z is metadata only. Started-only eligible HEAVY queue (population 34 of 46; 12 cancelled-before-start excluded; cancelled/unstarted fake-0 queues NOT used): p50 629s, p90 1435s, max 1584s. Because the run is cancelled this is not an AC-1 sample and makes no claim. The mid-run snapshot (/tmp/prd620-after1-snapshot-*, 62 rows) is snapshot ONLY: 30 of 46 eligible HEAVY started with queue p50 568s and 20/46 >120s; 20 < 24, so the full-median-must-fail bound was not met and the eventual full median stayed UNKNOWN, not predicted. Pool: balancer logged TN_RUNNER=off @06:28:06 (heavy busy withdrawal) and TN_RUNNER_LIGHT=on @06:17:15 (LIGHT separated); the run's static tn-local assignment held despite the withdrawal. Runner API snapshot: 14 registered, heavy 5 online busy / 0 online idle (+3 offline), light 3 online idle (+3 offline); initial repo showed 6 heavy idle across unknown hosts, later 5 online busy / 0 idle, so the heavy pool is not only 3 total slots. All 72 job rows are embedded with status, labels, runner names, created/started/completed timestamps, queue/duration and a canonical class per row (46 HEAVY + 12 LIGHT + 14 HOSTED/skipped); iteration 24's rows are preserved. Build/cache comparability to the matched hosted control remains UNKNOWN: native-ccache and native-third-party restore state is not measured; only the V8 payload warm published hit is comparable. Invalid, chart-excluded; generic merge-queue metrics null; no acceptance or routing claim.
invalid2026-10-15n/an/an/an/an/a
#262026-10-16057f5626dhosted control: full-board workflow_dispatch run 38938825798 at 057f56 (PRD-620; all-hosted cancelled control after stale native contract + UI budget; not a routed after-run)
Invalid hosted control, chart-excluded. Full-board workflow_dispatch run 38938825798 at candidate 057f5626dc8598f5161193c35c9b21246fab9ebc (PR #513 ci/prd620-local-first, run attempt 1, conclusion cancelled, event workflow_dispatch, not a merge-group window). Every one of the 72 jobs ran hosted (46 canonical HEAVY instances all on ubuntu-24.04; 0 tn-local), so it is a control, not a routed after-run: no routing gain is claimed and no green is claimed. The run was cancelled after three named failures: (1) supply-chain job 117101384474 was cancelled during `Run actions/checkout@v7` while the initial fetch hung - fetch began 17:20:45Z and the runner logged `##[error]The operation was canceled.` at 17:30:44Z, about 10 min; (2) test-native job 117102568261 failed step `Run pnpm --filter @threenative/runtime-native test` because the native starter-row contract regex was stale: AssertionError at tests/starter-desktop.test.mjs:395 against the `native:starter-linux:linux-x64` admission clause in native-platforms.yml - now fixed by commit 2b204ea70 (`test(ci): match bounded native runner admission`); (3) native-platforms / Desktop web/native parity job 117104138305 failed step `Verify the web UI composites into the game's own frame` with `TN_FRAME_BUDGET_PHASE_OVER_BUDGET: the 'ui' phase is 2.36 ms at p95 over 12 frame(s), which is not under 2 ms` - cause UNKNOWN, not asserted. The remaining cancelled/failed jobs are cancellation consequences, not independent defects. Wall: job envelope max completed_at 17:55:32Z minus min started_at 17:19:59Z = 2133s (35.6 min); full board max completed_at 17:55:32Z minus run_started_at 17:19:54Z = 2138s (35.6 min), NOT updated_at 17:55:33Z. Both are retained for an invalid, nonqualifying run and make no AC-1 claim; targets stay full board <=60 min wall and eligible HEAVY queue p50 <=2 min over all 46 instances, unchanged. Eligible HEAVY queue over the 46 all-hosted instances (hosted overflow included by definition): p50 140.5s, p90 683.5s, max 741s, 25/46 >120s - recorded as a hosted-control population, not an AC-1 sample. All 72 job rows are embedded with status, labels, runner names, created/started/completed timestamps, queue/duration and a canonical class per row (46 HEAVY + 12 LIGHT + 14 HOSTED/skipped); classification is unchanged from iterations 24-25. Build/cache comparability and V8 warm/cold status are not re-measured in this cancelled control. No publisher mutated. Generic merge-queue metrics null; prs empty.
invalid2026-10-15n/an/an/an/an/a
#272026-10-162b204ea70hosted control: full-board workflow_dispatch run 38943971312 at 2b204e (PRD-620; all-hosted successful control incl. ci-required; not a routed after-run)
Valid all-hosted matched hosted control, chart-excluded (not a routed after-run). Full-board workflow_dispatch run 38943971312 at candidate 2b204ea704ba9deb567889476248bc925545d94e (PR #513 ci/prd620-local-first), run attempt 1, conclusion success, event workflow_dispatch, not a merge-group window. Every one of the 72 jobs ran hosted: all 46 canonical HEAVY instances on ubuntu-24.04, 0 tn-local, so it is a control, not a routed after-run, and claims no routing gain. Full green incl. ci-required: job 117144412209 success, step `Reject every unsuccessful or missing selected job` success; all 46 HEAVY success (69 success + 3 skipped in total; the skipped legs are the native-platforms V8 publish/payload jobs). Candidate 2b204e carries the startup REST fix 057f56 (`fix(ci): resolve runner repository through REST`, verified 17/17 in scripts/__tests__/ci-runners.spec.ts) and the native contract fix 2b204e (`test(ci): match bounded native runner admission`, verified 33/33 in packages/runtime-native/tests/starter-desktop.test.mjs), so this candidate is NOT unchanged versus the older controls (iterations 24-26). Wall: full board max completed_at 19:02:18Z minus run_started_at 18:00:09Z = 3729s (62.2 min); job envelope max completed_at minus min started_at 18:00:55Z = 3683s (61.4 min); both computed from run_started_at / min job started_at / max job completed_at, NOT the run updated_at 19:02:19Z. The full control wall exceeds 60 min; as a baseline control it makes no AC-1 claim and the target <=60 min wall applies to the routed after-runs. Eligible HEAVY queue over all 46 hosted instances (hosted overflow included by definition): p50 623.0s, p90 1147.0s, max 1277s, 41/46 >120s - a hosted-control population, not an AC-1 sample. Build/cache comparability is not claimed: native-ccache and native-third-party restore state is not measured and the V8 warm/cold hit is unverified without the step log. The four recipe input blobs at 2b204e equal those at 057f56/f8b2680, but the candidate itself changed, so no unchanged-candidate claim holds. No publisher mutated. Generic merge-queue metrics null; prs empty. All 72 job rows are embedded with status, labels, runner names, created/started/completed timestamps, queue/duration and a canonical class per row (46 HEAVY + 12 LIGHT + 14 HOSTED/skipped); classification is unchanged from iterations 24-26.
invalid2026-10-15n/an/an/an/an/a
#282026-10-162b204ea70routed after-run attempt 1: full-board run 38952177095 at 2b204e (PRD-620; force-cancelled after a git-core PPA 503 broke playwright install-deps before the native tests; nonqualifying)
Invalid routed after-run, chart-excluded. Full-board workflow_dispatch run 38952177095 at candidate 2b204ea704ba9deb567889476248bc925545d94e (PR #513 ci/prd620-local-first, run attempt 1, conclusion cancelled, event workflow_dispatch, not a merge-group window). Actual routing: 6 of the 46 canonical HEAVY instances ran tn-local (test, test-native, test-unit 1/4-4/4) and 40 ran hosted ubuntu-24.04 - the bounded admission plan admitted the 6 idle heavy slots the balancer published and overflowed the surplus to hosted, so it is a routed run with hosted overflow, not an all-local one. First real failure was not a test: test-native job 117147306782 (tn-local) failed step 8 `Run ./.github/actions/playwright-chromium` because `playwright install-deps chromium` exited 1 at 19:11:33Z fetching the git-core PPA (`.linchpin/prd620-routed-native-failure.log:1085` `E: Failed to fetch https://ppa.launchpadcontent.net/git-core/ppa/ubuntu/dists/noble/main/binary-amd64/Packages 503 Service Unavailable`, then `Failed to install browser dependencies` / `Process completed with exit code 1`). The jobs API step outcomes confirm the native work never ran: `Install Linux desktop build dependencies`, `Build the native host` (native build), `Run ./.github/actions/workspace-dist` and `Run pnpm --filter @threenative/runtime-native test` (tests) are all SKIPPED. The always-collector step 24 `Collect bounded native Linux performance evidence` then failed with TN_PROD_SCAFFOLDER_UNAVAILABLE (installed create-threenative lacks the built scaffolder); this is a cascade of the same broken job, not a missing workflow build, and its evidence upload still produced artifact 12069333011 (native-performance-evidence, 581 bytes). The prior stale native-contract fix 2b204ea70 (`test(ci): match bounded native runner admission`, 33/33) remains green and is not contradicted - the contract test was skipped here before it could run. Root first requested an ordinary cancel, then issued a force-cancel because the run did not reach a terminal state after about 10 minutes. No engine or workflow change is justified by this run; the same SHA was retried as run 38953871463 (routed after-run sample still open). Post-cancellation failures are consequences, not independent defects: integration / Verify required integration receipts 117149844258, native-platforms / Native required coverage receipt 117151003072 and ci-required 117151263607 (its own step left null/cancelled). Wall: job envelope max completed_at 19:17:15Z minus min started_at 19:04:27Z = 768s (12.8 min); full board max completed_at minus run_started_at 19:04:23Z = 772s (12.9 min), NOT the run updated_at 19:17:15Z. Both are retained for an invalid, nonqualifying run and make no AC-1 claim; targets stay full board <=60 min wall and eligible HEAVY queue p50 <=2 min over all 46 instances, for two comparable warm routed after-runs. Eligible HEAVY queue over the 32 started of 46 (14 cancelled before a slot; fake-0 queues NOT used): p50 4.0s, p90 185.7s, max 285s, 6/46 >120s - a partial, cancelled population, NOT an AC-1 acceptance sample. The run reported 71 job rows, not the expected 72: the LIGHT `run-summary` row was never created because the run was force-cancelled. All 71 raw rows are embedded with status, labels, runner names, created/started/completed timestamps, queue/duration and a canonical class per row (46 HEAVY + 11 LIGHT + 14 HOSTED/skipped). No publisher mutated. Generic merge-queue metrics null; prs empty.
invalid2026-10-15n/an/an/an/an/a
#292026-10-162b204ea70qualifying routed after-run sample 1: full-board workflow_dispatch run 38953871463 at 2b204e (PRD-620; 6 tn-local HEAVY + 40 hosted overflow, full green incl. ci-required; wall 39m05, eligible queue p50 79s)
Qualifying routed after-run sample 1 of 2, chart-excluded. Full-board workflow_dispatch run 38953871463 at candidate 2b204ea704ba9deb567889476248bc925545d94e (PR #513 ci/prd620-local-first, run attempt 1, conclusion success, event workflow_dispatch, not a merge-group window). Routing under the published runner_admission plan (scope job 117151729683 step 4 `Admit the eligible heavy instances to the idle pool` success): 6 of the 46 canonical HEAVY instances ran tn-local (test 117152538905, test-native 117153020449, test-unit 1/4 117153020796, 2/4 117153020393, 3/4 117153020398, 4/4 117153021044) and 40 ran hosted ubuntu-24.04 - the 6 idle heavy slots the balancer published, with the surplus overflowed to hosted; the 12 LIGHT joins ran tn-local-light. So the run is routed with hosted overflow, not all-local. Full green incl. ci-required (job 117166645960, tn-local-light) with all 46 canonical HEAVY successful (72 rows: 46 HEAVY + 12 LIGHT + 14 HOSTED/skipped; 69 success + 3 skipped, the skipped legs the V8 publish/payload jobs). Wall: full board max completed_at 2026-10-16T19:56:58Z minus run_started_at 2026-10-16T19:17:53Z = 2345s (39.1 min, 39m05); job envelope max completed_at minus min started_at 19:17:57Z = 2341s; NOT the run updated_at 19:56:59Z. AC-1 metrics on this sample: full board 2345s <= 60 min and eligible HEAVY queue p50 79s (p90 381s, max 520s, 20/46 >120s) <= 2 min, both over all 46 canonical instances including hosted overflow. This is sample 1 of the >=2 comparable after-runs AC-1 requires; the second sample (run 38958785956, same candidate) is underway, so no two-sample acceptance is claimed. Comparability vs the all-hosted control iteration 27 (same candidate 2b204e, same event, same workload): the Android V8 recipe is unchanged at 1a2463b539e99c9fd7d87bd4bdd3ef86f2b61e42969a587f134e35f23d343607 and warm (published step 3359ms vs control 1338ms); native cache is comparable - test-native restored native-third-party cache HIT (key native-third-party-Linux-X64-ae97fc39...) and native-ccache MISS (per-run key) on BOTH the routed run and the control - so the sample is comparable on candidate/recipe/workload/cache and the wall/queue win is a real before/after on this candidate; but only 6/46 HEAVY ran local and the two runs ran at different times, so the improvement is not attributable to local routing alone and no routing-only gain is claimed. Old invalid controls/after-runs (iterations 25, 26, 28) remain chart-excluded and are not counted. All 72 job rows embedded (46 HEAVY + 12 LIGHT + 14 HOSTED/skipped, 69 success + 3 skipped) with the exact canonical per-row classification from iterations 27-28; generic merge-queue metrics null; prs empty.
invalid2026-10-15n/an/an/an/an/a
#302026-10-162b204ea70routed after-run attempt 1: full-board run 38958785956 at 2b204e (PRD-620; scope checkout of all branches+tags exceeded the 5-minute limit and was cancelled before any product job ran; nonqualifying)
Invalid routed after-run, chart-excluded. Full-board workflow_dispatch run 38958785956 at candidate 2b204ea704ba9deb567889476248bc925545d94e (PR #513 ci/prd620-local-first, run attempt 1, conclusion cancelled, event workflow_dispatch, not a merge-group window). The run never reached any product job: the LIGHT scope job 117168283489 (Change scope, tn-local-light) hung in `Run actions/checkout@v7` fetching the full history (`git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*`) from 2026-10-16T19:58:18Z to 2026-10-16T20:03:15Z, about 4m57s, then was cancelled at the 5-minute limit (`##[error]The operation was canceled.`, .linchpin/prd620-sample2-scope-failure.log:81-86). The `Classify the complete pull-request diff` and `Admit the eligible heavy instances to the idle pool` steps were SKIPPED, so the 46 canonical HEAVY instances were never created or admitted. GitHub then created 18 downstream jobs at 20:03:19Z, all skipped; ci-required job 117170451456 failed its own `Reject every unsuccessful or missing selected jobs` step (a cancellation consequence, not an independent defect) and run-summary 117172399484 was cancelled. Wall: full board max completed_at 2026-10-16T20:08:06Z minus run_started_at 2026-10-16T19:58:09Z = 597s (9.9 min); job envelope max completed_at minus min started_at 19:58:13Z = 593s, NOT the run updated_at 2026-10-16T20:08:06Z (coincident here because run-summary completed at that instant). Both are retained and make no AC-1 claim. No eligible queue value is recorded (no instance was created/started; NOT 0). All 21 observed job rows are embedded with status, labels, runner names, created/started/completed timestamps, queue/duration and a canonical class per row; the 46 canonical HEAVY instances are expected only and are NOT fabricated. Root fix: `.github/workflows/ci.yml` scope checkout now uses `fetch-depth: ${{ github.event_name == 'workflow_dispatch' && 1 || 0 }}` (manual full runs check out depth 1; automatic PR/push/merge_group/schedule keep depth 0), verified red-green by scripts/__tests__/ci-structure.spec.ts (164/164 at 2b204e). No engine, heavy-workload recipe, workflow or scheduler change. A fresh final-candidate hosted control plus two warm routed after-runs on the identical candidate/recipe/workload will follow for comparability; the qualifier 38953871463 (iteration 29) stays the qualifying sample 1 of 2 and the two-sample AC-1 claim stays open. Generic merge-queue metrics null; prs empty.
invalid2026-10-15n/an/an/an/an/a
#312026-10-16b611829famatched hosted control: full-board workflow_dispatch run 38961509705 at b611829fa (PRD-620; all-hosted successful control incl. ci-required; not a routed after-run)
Matched hosted control on the final candidate, chart-excluded. Full-board workflow_dispatch run 38961509705 at candidate b611829fa0702ee52c48f027c1db76b4040dddb3 (PR #513 ci/prd620-local-first, run attempt 1, conclusion success, event workflow_dispatch, not a merge-group window). All 72 rows embedded (46 HEAVY + 12 LIGHT + 14 HOSTED/skipped; 69 success + 3 skipped); all 46 canonical HEAVY instances ran hosted ubuntu-24.04 (0 tn-local), so this is a control, not a routed after-run. Wall: full board 3177s (52.95 min) from run_started_at 20:20:20Z to max completed_at 21:13:17Z; job envelope 3172s from min started_at 20:20:25Z, NOT the run updated_at 21:13:18Z. Eligible HEAVY queue over all 46 hosted instances p50 490s / p90 1394s / max 1466s / 31 >120s; a hosted-control population, not an AC-1 sample. ci-required job 117198043339 succeeded; warm V8 published hit on the unchanged recipe 1a2463b539e99c9fd7d87bd4bdd3ef86f2b61e42969a587f134e35f23d343607 (published step 1688ms). Generic merge-queue metrics null, prs empty. This ticks the Phase 2 full-green box on the final candidate; the two warm routed after-run boxes stay open.
invalid2026-10-15n/an/an/an/an/a
#322026-10-16b611829fainvalid routed after-run attempt 2: full-board workflow_dispatch run 38968104847 at b611829fa (PRD-620; 6 tn-local HEAVY incl. the failing test-unit 3/4 on the Debian laptop, force-cancelled; nonqualifying)
Invalid routed after-run attempt 2, chart-excluded. Full-board workflow_dispatch run 38968104847 at candidate b611829fa0702ee52c48f027c1db76b4040dddb3 (PR #513 ci/prd620-local-first, run attempt 1, conclusion cancelled, event workflow_dispatch, not a merge-group window). 71 rows embedded (46 HEAVY + 11 LIGHT + 14 HOSTED/skipped; the 72nd run-summary row was never created because of the force-cancel). The first real failure was test-unit (3/4) job 117200083728 on runner tn-ed7c3ecba926 (Debian laptop): `Run impacted unit contracts` failed when the build.spec.ts `audio worker pool remains referenced after build completion` test hit the deliberate 60s child ceiling and resolved {code:null, timedOut:true}; the output tail shows asset-encoding lines but does not prove ongoing activity for 60s nor a root cause, so the cause is UNKNOWN with runner slowness suspected. The required-receipt jobs (integration 117204640878, native coverage 117205695871) and ci-required 117205918769 failed or were cancelled as a consequence. A desktop probe on the unchanged source reproduced neither: build exit 0 in 31s and the focused test exit 0 in 21s. No runtime/test change and no timer raised. The run was force-cancelled by @jonit-dev; a fresh retry of the same head is run 38970276044 (underway). Wall: full board 1074s from run_started_at 21:15:32Z to max completed_at 21:33:26Z; job envelope 1070s. Generic merge-queue metrics null, prs empty. No blind rerun claim and no routing or wall acceptance; the two warm routed after-run boxes stay open.
invalid2026-10-15n/an/an/an/an/a
#332026-10-16b611829farouted after-run attempt 3 on the final candidate: full-board workflow_dispatch run 38970276044 at b611829fa (PRD-620; 6 tn-local HEAVY + 40 hosted overflow, full green incl. ci-required; wall 43m44 passes but eligible queue p50 662.5s FAILS the <=2 min target)
Routed after-run attempt 3, chart-excluded, fails the AC-1 queue target. Full-board workflow_dispatch run 38970276044 at candidate b611829fa0702ee52c48f027c1db76b4040dddb3 (PR #513 ci/prd620-local-first, run attempt 1, conclusion success, event workflow_dispatch, not a merge-group window). Routing: 6 of the 46 canonical HEAVY instances ran tn-local (test 117206787669, test-native 117208785273, test-unit 1/4 117208785198, 2/4 117208785147, 3/4 117208785195, 4/4 117208785255 - the 6 idle heavy slots the balancer published) and 40 ran hosted ubuntu-24.04 overflow; the 12 LIGHT joins ran tn-local-light. Full green incl. ci-required (job 117220943975, tn-local-light) with all 46 canonical HEAVY successful (72 rows: 46 HEAVY + 12 LIGHT + 14 HOSTED/skipped; 69 success + 3 skipped, the skipped legs the V8 publish/payload jobs). Wall: full board max completed_at 2026-10-16T22:18:05Z minus run_started_at 2026-10-16T21:34:21Z = 2624s (43m44); job envelope 2620s from min started_at 21:34:25Z; NOT the run updated_at (same instant here but the definition excludes it). AC-1 metrics: full board 2624s <= 60 min PASSES, but eligible HEAVY queue p50 662.5s (p90 929s, max 1029s, 38/46 >120s) FAILS the <=2 min target over all 46 canonical instances incl. hosted overflow, so this is a real queue-target failure, not a technical invalid/cancelled run, and it is NOT counted as sample 2 of 2. Evidence points to hosted contention: the 6 local heavy slots had flat 3s queues while hosted lint queued 265s, build-artifacts 274s, performance-contracts 407s, despite no long needs chain; the prior comparable after-run at 2b204e (iteration 29) had the same 6-local/40-hosted shape with eligible queue p50 79s. Run start 21:34 overlapped the repo develop-push run 38968480858 and asset-mcp runs 38968477198/38968476890 created 21:18; no global GitHub incident and no exclusive cause is claimed, so priority and budget are unchanged. Comparability vs the matched hosted control iteration 31 (same candidate b611829fa, same event/workload): Android V8 recipe unchanged and warm (1a2463b539e99c9fd7d87bd4bdd3ef86f2b61e42969a587f134e35f23d343607, published step 2202ms vs control 1688ms); test-native native-third-party cache HIT on the primary key and native-ccache primary MISS with a restore-key prefix HIT from run 38968104847 on both the sample and the control (control prefix from 38943971312). Generic merge-queue metrics null, prs empty.
invalid2026-10-15n/an/an/an/an/a
#342026-10-17b611829farouted after-run on the final candidate, quiet window: full-board workflow_dispatch run 38975472149 at b611829fa (PRD-620; 6 tn-local HEAVY + 40 hosted overflow, full green incl. ci-required; wall 36.82 min and eligible queue p50 4s PASS)
Routed after-run, chart-excluded. Full-board workflow_dispatch run 38975472149 at candidate b611829fa0702ee52c48f027c1db76b4040dddb3 (PR #513, run attempt 1, conclusion success), started in a quiet repository window: no other repository run overlapped 2026-10-16T22:23:36Z to 2026-10-16T23:00:25Z (iteration 33's run ended 22:18:05Z; develop push 38968480858 ended 22:12:23Z). Routing: 6 of 46 canonical HEAVY ran tn-local (test 117223874109, test-native 117224272682, test-unit (4/4) 117224272732, test-unit (3/4) 117224272744, test-unit (2/4) 117224272814, test-unit (1/4) 117224273055), 40 ran hosted ubuntu-24.04 overflow; the 12 LIGHT joins ran tn-local-light. ci-required job 117234269821 succeeded; all 46 HEAVY green (72 rows, 69 success + 3 skipped). Wall: full board 2209s from run_started_at to max completed_at 2026-10-16T23:00:25Z <= 60 min PASSES; job envelope 2205s. Eligible HEAVY queue over all 46 incl. hosted overflow p50 4s / p90 258s / max 324s / 14 >120s; p50 <= 120s PASSES. Comparability vs control iteration 31 (same candidate/event/workload): Android V8 recipe 1a2463b539e99c9fd7d87bd4bdd3ef86f2b61e42969a587f134e35f23d343607 unchanged and warm (published step 1628ms); test-native native-third-party primary HIT and native-ccache restore-key prefix HIT from run 38970276044, the control's shape. Iteration 33 on this same candidate failed the queue target in a contended window, so this is the first quiet-window sample of the predeclared pair, not a pair. Generic merge-queue metrics null, prs empty.
invalid2026-10-15n/an/an/an/an/a
#352026-10-176234d68381db960de04cfb9956232e647aa30455routed after-run, quiet window, RED: run 39066107986 at 6234d6838 (develop-synced); wall 46.67 min and queue p50 5s meet the targets but the board failed, so it is not a sample
Routed after-run on the develop-synced candidate 6234d68381db960de04cfb9956232e647aa30455, chart-excluded and NOT counted. Quiet window: no other repository run overlapped 2026-10-17T20:17:52Z to 2026-10-17T21:04:32Z. 6 of 46 HEAVY ran tn-local, 40 hosted overflow. Timing would pass (full board 2800s, job envelope 2796s; eligible HEAVY queue p50 5s / p90 344s / max 428s / 15 >120s) but the board is red, so it is not an AC-1 sample. Root failures: (1) test-unit (3/4) on tn-local runner tn-f5a5d3bd4a00 (desktop pool, not the laptop): scripts/__tests__/check-capability-docs.spec.ts "should scan subpath exports" exceeded its 15 s hang guard (file 23406ms; the same test took 4976ms on tn-local in run 38975472149). Fix: the guard is 60 s; the assertions are unchanged. (2) native-platforms / Android emulator visual parity on hosted ubuntu-24.04 (not a PRD-620 routed job): conformance 79 pass / 0 fail / 29 blocked, but supplemental androidMultitouch exited 1 after its first proof passed (300 frames, screenshot captured) with "FAIL: Device playtest cleanup failed."; the same job passed on develop runs 39056576003 and 39059885969, so this is an emulator cleanup failure outside this PRD, with no cause claimed. Native required coverage receipt and ci-required failed as consequences.
invalid2026-10-15n/an/an/an/an/a
#362026-10-174dd04c76bcurrent window refresh (develop since 2026-10-14)
Fresh develop window measured 2026-10-17 after iterations 21-35 (PR #513 full-board samples, chart-excluded). Not an effect of PR #513, which is unmerged; it reflects develop changes since 2026-10-14.
provisional2026-10-14147.844350%4618.2
#372026-10-174dd04c76ba2c1f4d230ccf500981dfb0180e24d1routed after-run sample 2, quiet window: run 39070176677 at 4dd04c76b (develop-synced); full green incl. ci-required; wall 38.58 min and eligible queue p50 9.5s PASS
Routed after-run sample 2 of the predeclared quiet-window pair, chart-excluded. Run 39070176677 at 4dd04c76ba2c1f4d230ccf500981dfb0180e24d1 (PR #513, run attempt 1, success). Quiet at dispatch (repo active and queued lists empty twice 60 s apart); PR run 39072036343 started at 21:41:55Z, after every HEAVY job had started, so no HEAVY queue overlapped it. 6 of 46 HEAVY ran tn-local (test 117506068541, test-native 117506556550, test-unit (2/4) 117506556569, test-unit (1/4) 117506556579, test-unit (3/4) 117506556621, test-unit (4/4) 117506556654), 40 hosted overflow; 12 LIGHT on tn-local-light. ci-required job 117513267216 succeeded; all 46 HEAVY green (72 rows). Full board 2315s <= 60 min PASSES (job envelope 2311s). Eligible HEAVY queue over all 46 incl. hosted overflow p50 9.5s / p90 297s / max 388s / 16 >120s; p50 <= 120s PASSES. Comparability vs sample 1 (iteration 34, run 38975472149 at b611829fa): same event and full workload (72 rows, 46 HEAVY, 6 local); Android V8 recipe 1a2463b539e99c9fd7d87bd4bdd3ef86f2b61e42969a587f134e35f23d343607 unchanged and warm (published 1898ms); test-native native-third-party primary HIT and native-ccache prefix HIT (from run 39066107986), the same cache shape. b611829fa..4dd04c76b changes no runs-on, runner_admission or TN_RUNNER line: it adds develop (Dawn build job, a Windows temp path, the rain template timeout) and the 60 s capability-scan hang guard. Generic merge-queue metrics null, prs empty.
invalid2026-10-15n/an/an/an/an/a
-

Merged PRs, iteration #36 window

-
PRTitlePush to mergeEnqueue to mergeAttemptsFirst attemptFirst failing job
#516A character never shows its bind pose (PRD-623)443 min53.4 min1cancelled
#515chore(deps): pin threenative-asset-mcp 0.14.0 (earshot prompt-fit backend)2.4 minn/a0n/a
#514Reduce temporary allocations in render bundle safety checks60.4 minn/a0n/a
#512Smooth physics and camera motion between fixed ticks (PRD-619)678.6 min45.7 min1success
#511chore(assets): pin threenative-asset-mcp 0.13.0 (Unity Asset Store import)0.4 minn/a0n/a
#509fix(core): PRD-616 - warm the output pass before startup and streamed draws147.8 min205 min3failurenative-platforms / Windows desktop core
#508Use cached V8 by default on Linux ARM64228.4 min162.2 min2cancelled
#507fix(native): complete v0.1.6 qualification and verifier cleanup94.7 min48.5 min1success
#505feat(PRD-473): enable automatic world impostors and finish GPU-world acceptance149.9 min81.8 min2failurenative-platforms / Desktop web/native parity
#488feat(assets): PRD-568 cook mobile textures to GPU-ready ASTC91.3 min45.9 min1success
#486Use transient MSAA depth for renderer output passes193.2 min39.2 min1success
-
diff --git a/package.json b/package.json index 87bb2498d4..6167e2b388 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "bench:engines": "tsx scripts/engine-load-test/cli.ts", "bench:engines:report": "tsx scripts/engine-load-test/cli.ts --compare", "bench:engines:monitor": "tsx scripts/engine-load-test/monitor.ts", - "perf:live": "tsx scripts/perf-live.ts", + "dashboard": "tsx scripts/perf-live.ts", "bench:scoreboard": "sh scripts/engine-load-test/scoreboard.sh", "budgets": "tsx scripts/workspace-packages.ts && tsx scripts/check-version-pins.ts && tsx scripts/check-native-shims.ts && tsx scripts/check-core-boundary.ts && tsx scripts/check-template-conventions.ts && tsx scripts/check-capability-docs.ts --census && pnpm caps:recall && tsx scripts/check-evidence-budget.ts && tsx scripts/vfx-niagara-coverage.ts && tsx scripts/check-budgets.ts && tsx scripts/generate-ctx-surface-table.ts --check && tsx scripts/generate-capability-reference.ts --check && tsx scripts/realism-effects-docs.ts --check && tsx scripts/realism-effects-conformance.ts && tsx scripts/realism-effects-matrix.ts && tsx scripts/detect-capability-duplicates.ts examples --strict && tsx scripts/check-template-quality.ts && tsx scripts/sync-mcp-configs.ts --check", "build": "tsx scripts/build-capability-manifest.ts && tsx scripts/generate-ctx-surface-table.ts && tsx scripts/generate-capability-reference.ts && tsx scripts/realism-effects-docs.ts && pnpm -r --if-present run build", diff --git a/scripts/__tests__/asset-perf-loop.spec.ts b/scripts/__tests__/asset-perf-loop.spec.ts index 37a69c67b6..b4e762a5a0 100644 --- a/scripts/__tests__/asset-perf-loop.spec.ts +++ b/scripts/__tests__/asset-perf-loop.spec.ts @@ -68,8 +68,8 @@ describe("asset perf loop", () => { expect(html).toContain("Latest iteration #1 of 2: cooked."); expect(html).toContain("pending"); expect(html).not.toContain("#2: 99"); - expect(html).not.toContain("/state"); - expect(renderHtml(ledger, new Date(0), { serving: true })).toContain('fetch("/state"'); + expect(html).not.toContain("/api/asset-perf"); + expect(renderHtml(ledger, new Date(0), { serving: true })).toContain('fetch("/api/asset-perf"'); expect(() => appendIteration(ledger, { measurement, diff --git a/scripts/__tests__/ci-efficiency.spec.ts b/scripts/__tests__/ci-efficiency.spec.ts index 6a4e698d5a..2bbf1731ae 100644 --- a/scripts/__tests__/ci-efficiency.spec.ts +++ b/scripts/__tests__/ci-efficiency.spec.ts @@ -543,11 +543,7 @@ describe("PRD-373 fail-closed required verdict", () => { it("classifies the generated speed-loop record as prose, not a full board", () => { // Only scripts/ci-speed-loop.mjs reads these files, and no CI job runs that script. - for (const file of [ - "docs/ci-speed/ledger.json", - "docs/ci-speed/index.html", - "docs/ci-speed/runs/39070176677.jobs.json", - ]) { + for (const file of ["docs/ci-speed/ledger.json", "docs/ci-speed/runs/39070176677.jobs.json"]) { expect(prosePlan(file).selection).toBe("prose"); } }); diff --git a/scripts/__tests__/perf-live.spec.ts b/scripts/__tests__/perf-live.spec.ts index 7c33d74d6d..ba2b691810 100644 --- a/scripts/__tests__/perf-live.spec.ts +++ b/scripts/__tests__/perf-live.spec.ts @@ -11,6 +11,7 @@ import { desktopOf, epicOf, heroOf, + ledgerTab, loadRuns, phaseGapOf, pointsOf, @@ -19,6 +20,7 @@ import { staleReason, valueColor, webOf, + withNav, } from "../perf-live.js"; const pilot = "artifacts/engine-load-test/prd-449/pilots/scoreboard-tn-r3-2026-09-28.json"; @@ -774,4 +776,43 @@ describe("perf-live", () => { expect(html).toContain(label); }); }); + + describe("the hub tabs (PRD-751)", () => { + const ledgerFile = async (text: string) => { + const file = path.join(await makeTempDir("dashboard-tab"), "ledger.json"); + await writeFile(file, text); + return file; + }; + + it("puts every tab in the nav and marks the current one", async () => { + const html = withNav( + await readFile(new URL("../perf-live.html", import.meta.url), "utf8"), + "/ci-speed", + ); + for (const href of ["/", "/midway", "/shadows", "/ci-speed", "/asset-perf"]) + expect(html).toContain(`href="${href}"`); + expect(html).toContain(''); + expect(html.match(/