Skip to content

PRD-570: measure crowd animation and palette CPU costs - #487

Draft
jonit-dev wants to merge 9 commits into
developfrom
perf/prd570-skin-animation-gate
Draft

jonit-dev wants to merge 9 commits into
developfrom
perf/prd570-skin-animation-gate

Conversation

@jonit-dev

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

Copy link
Copy Markdown
Contributor

The Crowd fixture now reaches actual SkeletalMesh3D animation updates and palette writes at 32, 128 and 256 rigs, with animated, held and baked-static controls. Exact CPU spans and separate main/shadow GPU buckets feed a JSON-safe receipt latched once from a complete ready window after startup; flat startup metrics continue updating.

A single headed 32-animated functional run on NVIDIA/Turing passed all 14 checks. Exactly three URL/enter-time invariants have specific allowTrivial reasons; completed-window measurement checks remain unwaived, and an already-held measurement still fails the existing baseline guard. Functional artifacts: artifacts/prd570-functional-16f2173f9/. Runtime source: 16f2173f96f6bd269e3c8c8930196bf34557cda6; reviewed documentation: a311f995802fcb6aa47a1b9c3e7d2bb1a2c149d4.

Normal pre-push verification passed: agents (1 s), documentation (7 s), lint (12 s), drift (33 s). This bounded hook establishes no runtime or native result.

Validation recorded in the PRD: six real callback/bridge cases, TypeScript, scenario schema, Biome and documentation checks; the headed functional run used an explicit manual override equivalent to the shipped WebGPU recipe plus four approved NVIDIA/Vulkan arguments. No three-window cost series, CPU reduction, GPU savings, native parity or visual-judge result is claimed. PRD progress remains 0%, and all outcome boxes stay open.

Risks: timings include instrumentation overhead; one receipt is insufficient for the proposed three-window-per-arm cost series. A baseline already at window 2 or later must fail honestly rather than adjusting warmup to obtain a pass.

TL;DR — summary that supplies gpuMain; absent measurements stay absent in FrameBudget and publish null in the fixture. Readiness now requires an observed main sample as well as rendered-shadow samples. The focused fresh/deduplicated-main assertion failed before the field existed, then all 17 frame-budget GPU-series tests passed, including measured zero and missing-main cases. Core ESM/DTS/bundler build and publint pass. Crowd TypeScript, scenario schema and Vite build pass. Six bridge/readiness cases pass: a global GPU sample count cannot substitute for missing main samples. Main sample counts are observed independently of the 120 render frames.

0% by the PRD's own boxes: 0/3 phases (0/7 boxes), 0/2 acceptance criteria. PRD: docs/PRDs/unreal-source-borrowing/PRD-570-a-character-costs-what-the-camera-sees-of-it.md.

Phases

Phase 1: The cost is measured — 0/2

  • [local] The crowd scenario reports CPU milliseconds for AnimationPlayer.update and SkinnedBatch.write at 32, 128 and 256 rigs, with the result recorded under ## Decisions. proof: crowd-rate.playtest.json with playtest perf and span probes.
  • [local] The same scenario reports GPU milliseconds for skinned draws in the main and shadow passes, against an arm where the crowd is static. The result decides Phase 3 under ## Decisions. proof: the same playtest, both arms, TN_FRAME_BUDGET main/shadow buckets.

Phase 2: Rigs evaluate at the rate their screen size earns — 0/4

  • [local] Accumulated dt keeps clip time, fade progress and the finished event identical to rate-1 playback over 300 frames, and a forced evaluation follows any skip longer than the rate. proof: red-green cases in pnpm exec vitest run packages/core/__tests__/animation-rate.spec.ts.
  • [local] Stagger offsets spread 128 rigs so that the evaluations in any one frame stay within 1.5× of the mean. Root-motion and updateRate: 1 players never skip. proof: the same spec.
  • [local] AC-1 passes. A fresh judge subagent compares 10 m, 40 m and 120 m captures against rate 1 and finds no visible loss. proof: crowd-rate.playtest.json plus pnpm visuals:ab --before <rate1> --after <auto> --out <dir>.
  • [local] The native desktop host runs the crowd scenario with the policy on. proof: node packages/playtest/dist/runner/cli.js examples/skinned-crowd/playtests/crowd-rate.playtest.json --target desktop.

Phase 3: Skinning runs once per frame, if Phase 1 earns it — 0/1

  • [local] With the skin-once pass, the crowd's main-plus-shadow GPU time falls against Phase 1's arm, and the existing velocity-history assertions stay green. If Phase 1 shows less than 0.5 ms, the decline is recorded under ## Decisions instead. proof: crowd-rate.playtest.json perf arms and pnpm exec vitest run packages/core/__tests__/projection-skinned.spec.ts.

Acceptance criteria

  • AC-1 [local]: A 128-rig SkeletalMesh3D crowd receding to 120 m spends at least 40% less CPU in AnimationPlayer.update plus SkinnedBatch.write than the rate-1 arm, from one build. proof: new examples/skinned-crowd/playtests/crowd-rate.playtest.json through node packages/playtest/dist/runner/cli.js ... --browser-recipe webgpu with playtest perf, adapter named.
  • AC-2 [local]: The shooter's soldiers keep their gameplay behavior: the existing shooter playtests stay green with the policy on. proof: pnpm test:templates for shooter.

Still open

  • Phase 1: The cost is measured: [local] The crowd scenario reports CPU milliseconds for AnimationPlayer.update and SkinnedBatch.write at 32, 128 and 256 rigs, with the result recorded under ## Decisions. proof: crowd-rate.playtest.json with playtest perf and span probes.
  • Phase 1: The cost is measured: [local] The same scenario reports GPU milliseconds for skinned draws in the main and shadow passes, against an arm where the crowd is static. The result decides Phase 3 under ## Decisions. proof: the same playtest, both arms, TN_FRAME_BUDGET main/shadow buckets.
  • Phase 2: Rigs evaluate at the rate their screen size earns: [local] Accumulated dt keeps clip time, fade progress and the finished event identical to rate-1 playback over 300 frames, and a forced evaluation follows any skip longer than the rate. proof: red-green cases in pnpm exec vitest run packages/core/__tests__/animation-rate.spec.ts.
  • Phase 2: Rigs evaluate at the rate their screen size earns: [local] Stagger offsets spread 128 rigs so that the evaluations in any one frame stay within 1.5× of the mean. Root-motion and updateRate: 1 players never skip. proof: the same spec.
  • Phase 2: Rigs evaluate at the rate their screen size earns: [local] AC-1 passes. A fresh judge subagent compares 10 m, 40 m and 120 m captures against rate 1 and finds no visible loss. proof: crowd-rate.playtest.json plus pnpm visuals:ab --before <rate1> --after <auto> --out <dir>.
  • Phase 2: Rigs evaluate at the rate their screen size earns: [local] The native desktop host runs the crowd scenario with the policy on. proof: node packages/playtest/dist/runner/cli.js examples/skinned-crowd/playtests/crowd-rate.playtest.json --target desktop.
  • Phase 3: Skinning runs once per frame, if Phase 1 earns it: [local] With the skin-once pass, the crowd's main-plus-shadow GPU time falls against Phase 1's arm, and the existing velocity-history assertions stay green. If Phase 1 shows less than 0.5 ms, the decline is recorded under ## Decisions instead. proof: crowd-rate.playtest.json perf arms and pnpm exec vitest run packages/core/__tests__/projection-skinned.spec.ts.
  • Acceptance Criteria: AC-1 [local]: A 128-rig SkeletalMesh3D crowd receding to 120 m spends at least 40% less CPU in AnimationPlayer.update plus SkinnedBatch.write than the rate-1 arm, from one build. proof: new examples/skinned-crowd/playtests/crowd-rate.playtest.json through node packages/playtest/dist/runner/cli.js ... --browser-recipe webgpu with playtest perf, adapter named.
  • Acceptance Criteria: AC-2 [local]: The shooter's soldiers keep their gameplay behavior: the existing shooter playtests stay green with the policy on. proof: pnpm test:templates for shooter.

Generated from the PRD's checkboxes. Regenerate on each push so the PR and the PRD never disagree.

@jonit-dev jonit-dev added the prd:0% No verified implementation phase complete label Oct 10, 2026
@jonit-dev

Copy link
Copy Markdown
Contributor Author

PRD-570 bounded functional preflight, local source cd48d5ae78a66920b7299720a5c7ce86d77c64e4 (not yet pushed):

  • Doctor passed before and after. Headed run used the explicit nine browser arguments equivalent to shipped WebGPU recipe plus the four approved Vulkan extras. Actual capture adapter: nvidia / turing, RTX 2080.
  • Completed window 2: ready=true, 120 render/animation/palette frames, 120 actual main samples and 120 rendered-shadow samples, 32 animation calls/palette writes per frame. Main/shadow draws 3/1, triangles 17,667/17,664; 1280×720, DPR/scale 1.
  • Scenario exited 1 with 11 TN_PLAYTEST_ASSERTION_TRIVIAL errors: startup already satisfied held-invariant assertions. Window/readiness checks passed, but this is not a passing scenario. No assertions changed and no retry was made.
  • Raw receipt, capture, console, state, doctor logs and before/after source/bundle hashes retained in the PRD worktree at artifacts/prd570-functional-cd48d5ae7/. Hashes stayed unchanged during execution. Owned port 57170 preview was stopped and port closure verified.
  • Other GPU processes were present (26% utilization before, 22% after); no cost, savings, visual or native claim. Nine-arm cost series remains unrun and requires a separate exclusive grant. All PRD boxes remain open at 0%.

@jonit-dev

Copy link
Copy Markdown
Contributor Author

Functional recheck at local candidate 16f2173f9: PASS on headed NVIDIA/Turing. The unchanged 32-animated scenario passed all 14 resource checks (three documented startup-invariant waivers); startup measured:null became a complete immutable window 2 with 120 render/animation/palette frames and 120 main/120 rendered-shadow GPU samples. Original warmup120, 1280×720, scale1/MSAA4 and limits were retained. Doctor before/after and the exact fixture build passed; owned preview cleaned up.

This is functional proof only: other GPU jobs were present. No performance reduction, native acceptance or judged visual comparison is claimed; PRD570 remains at0%. Local source/PRD commits are held until the existing CI watcher completes. Results and artifact pointers remain in the PRD; capture data is artifacts/prd570-functional-16f2173f9/.

This PR stays open for João’s inspection when its PRD reaches100%; no merge or archival.

@jonit-dev jonit-dev added prd:25% PRD phase 1 verified and removed prd:0% No verified implementation phase complete labels Oct 11, 2026

This branch has not been deployed

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

Labels

prd:25% PRD phase 1 verified

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant