agentic game-dev stack · open-source · 2026
This page consolidates the research notes in research/ into one answer for a
build-time agent pipeline: the agent writes files, runs a headless build, verifies, ships.
Runtime/in-game LLM NPCs are out of scope. The baseline it is judged against is NEON PURGE
(this repo): a browser 2D neon arena roguelite on PixiJS 8.22.0, plain ES modules + import map,
no build step, zero external assets, seeded RNG, headless Node autotests.
Every claim carries a note filename chip. Filenames are shown as plain text on purpose — source notes are not published with this page, so only the external URLs are real links.
One pick per layer. Where a layer legitimately has two answers (a zero-dependency default plus an optional dev-only add-on), both are named and the cost of the add-on is stated.
opencode-ai 1.18.35, 2026-10-06) as the interactive harness, OpenAI Codex CLI (Apache-2.0, @openai/codex 0.162.0, 2026-10-08) for non-interactive CI runs. Spec layer: AGENTS.md + GitHub spec-kit (MIT, pushed 2026-10-08).@modelcontextprotocol/sdk 1.32.1; spec revision 2026-07-28.generateTexture + Web Audio). If polish is wanted, only the permissive offline tier: Z-Image-Turbo / FLUX.2-klein-4B (Apache-2.0 weights), ACE-Step 1.5 (MIT), TRELLIS.2-4B (MIT, needs ≥24 GB VRAM), glued with gltf-transform 4.5.1 (MIT) + sharp 0.35.5 (Apache-2.0).node --test (0 deps, Node v24.21.0 verified in this workspace) + the existing tools/check.mjs → smoke.mjs → shots.mjs chain + pixelmatch 8.0.0 (ISC, ESM) + Playwright 1.64.0 (Apache-2.0, dev-only, for input injection). Node-side pixels need no browser at all: PixiJS's own CanvasRenderer on jsdom + @napi-rs/canvas 1.0.10. Biggest flake risk: blank-canvas PNGs that still pass a file-size check.| Layer | Recommended pick | Version & date | Licence | Why this one | Evidence |
|---|---|---|---|---|---|
| Harness / orchestration | OpenCode (opencode-ai) |
1.18.35 · 2026-10-06 pushed 2026-10-09 |
MIT | Largest, fastest-moving OSS terminal harness (212,273★), LSP-aware, runs a no-build-step repo out of the box. Tradeoff: huge surface area, re-pin versions. | 01-agent-harness.md |
| Harness (CI) | OpenAI Codex CLI | 0.162.0 · 2026-10-08 | Apache-2.0 | Best-documented non-interactive mode for CI, reads AGENTS.md, 93.1 M npm downloads/month. |
01-agent-harness.md |
| Spec / convention | AGENTS.md + GitHub spec-kit | spec-kit pushed 2026-10-08 AGENTS.md 2026-09-10 |
MIT / MIT | Cheapest way to make any harness obey docs/SPEC.md and the deterministic tools/*.mjs evidence loop. |
01-agent-harness.md 03-mcp-interop.md |
| Engine runtime — 2D | PixiJS 8.22.0 (keep) + the named add-on stack in §05 | 8.22.0 · 2026-10-01 | MIT | The pin is npm latest; no editor exists to fight; and it is the only candidate empirically proven to render headless both in a GPU-free browser (WebGL2 via SwANGLE) and in pure Node (its own CanvasRenderer, no compiler, no GPU). Ranked #4/21 on the agent-target rubric (84.0%). |
07-improve-2d.md 02-engines.md |
| Engine runtime — 3D | Babylon.js (@babylonjs/core) + @babylonjs/havok |
9.30.0 · 2026-10-08 Havok 1.3.14 · 2026-07-29 |
Apache-2.0 MIT |
The only web 3D engine whose whole loop survives headless Node: NullEngine scene + Havok physics + glTF import all verified with no GPU. Batteries first-party. |
08-improve-3d.md 02-engines.md |
| Tool interop | Chrome DevTools MCP + Playwright MCP + official filesystem/memory/sequential-thinking | 1.10.1 · 2026-09-23 0.0.83 · 2026-09-28 2026.8.31 |
Apache-2.0 / Apache-2.0 / MIT–Apache-2.0 | The only MCP category that actually targets a no-build browser game, and the healthiest part of the ecosystem. Engine-editor MCP bridges are irrelevant here. | 03-mcp-interop.md |
| Assets | Keep procedural; permissive offline tier only | Z-Image-Turbo 2025-11-25 FLUX.2-klein-4B 2026-01-14 ACE-Step 1.5 2026-01-23 |
Apache-2.0 / Apache-2.0 / MIT | Shipped payload 2.52 MB and 0 assets vs +1.8 MB and a 10–33 GB dev toolchain. The procedural route wins every axis this project measures except photographic fidelity. | 04-asset-pipeline.md |
| Verification | node --test + existing tools/ + pixelmatch + Playwright (dev-only) + jsdom/@napi-rs/canvas |
Node v24.21.0 (this box) pixelmatch 8.0.0 · 2026-10-06 Playwright 1.64.0 · 2026-10-07 @napi-rs/canvas 1.0.10 · 2026-10-01 |
built-in / ISC / Apache-2.0 / MIT | The unit layer costs zero dependencies, and the Node pixel path costs no compiler. The current suite is red (15 findings) so a green run carries no signal until that is fixed. | 05-qa-harness.md 07-improve-2d.md |
| Isolation (optional) | git worktrees; microsandbox only if untrusted code appears |
microsandbox 2026-10-09 | Apache-2.0 | Worktrees work natively on Windows and are proportionate for a zero-dependency, no-build-step JS repo. E2B/Daytona are cloud/Linux-oriented and awkward locally. | 01-agent-harness.md |
PlayCanvas' engine is MIT and strong, but the Editor/cloud that owns projects, asset import and publishing is proprietary, and its terms expressly prohibit accounts created by "non-humans, 'bots' or other automated methods". Engine-only use is legal but has no headless render path. Babylon 9.30.0 replaces it.
08-improve-3d.md
Verdict of the dedicated 2D note: "Keep PixiJS 8.22.0 as the 2D renderer, and close every battery gap with a named MIT stack… plus a real verification layer." It is the only candidate empirically proven to render headless in a GPU-free browser and in pure Node, and a swap would touch 32/32 files and ~12,061 LOC for zero capability the stack does not already supply.
07-improve-2d.md
Both halves of the original proposal are judged on fetched evidence, not on taste. One is replaced; one is kept and reinforced.
| Question | What the evidence says | Verdict | Evidence |
|---|---|---|---|
| Is the engine open source? | Yes — playcanvas@2.23.1 (2026-10-07) is MIT, 2 deps, 90.1 MB unpacked / 5,089 files, 88,194 downloads last week. The editor frontend repo is also MIT. |
fine | 08-improve-3d.md §1 |
| Is the platform an agent may use? | No. playcanvas.com/terms (§4, "Your Account"): "You will not register any accounts by non-humans, 'bots' or other automated methods as such methods are expressly prohibited." §2 grants only a "revocable" personal licence to the Services; §3 takes a broad IP licence over content made publicly available. |
blocking | 08-improve-3d.md §1 |
| Is the agent-facing part the proprietary part? | Yes — "A project lives in the Editor"; asset import pipeline, prefabs, version control, publishing/hosting and the REST API are all Editor-only. The engine-standalone path is first-party documented but you supply glTF loading, bundling and hosting yourself. | inverted | 08-improve-3d.md §1 |
| Can an agent verify what it built headlessly? | NullGraphicsDevice + app.update(dt) works (verified: 30 ticks, ESM script ran), but its own agent docs say "Rendering, input and physics can only be confirmed in a browser"; asset loading needs jsdom, physics needs an externally supplied ammo.js, and readPixels is a no-op by design. |
partial | 08-improve-3d.md §2 |
| What replaces it? | Babylon.js 9.30.0 (Apache-2.0, 2026-10-08): the only one of the three web candidates whose scene graph, physics and asset loading were all booted in Node with no GPU, first-party batteries, own TypeScript types, and official graph-authoring MCP servers runnable as plain npx binaries. |
pick | 08-improve-3d.md §0, §5 |
| Where is PlayCanvas still the best answer? | Browser 3D experiences/apps: product configurators, splat scenes, embeddable viewers. @playcanvas/react 0.11.7 and @playcanvas/web-components 0.28.0 need no build step, and its docs are the best-written of the three for agents. The ToS issue rarely bites when a human ships the app. |
keep, narrowed | 08-improve-3d.md §5, §7 |
The dedicated 2D note answers this directly. Its verdict (§0): "Keep PixiJS 8.22.0 as the 2D renderer, and
close every battery gap with a named MIT stack (planck/@dimforge/rapier2d,
bitecs, @pixi/tilemap, @pixi/ui, @pixi/sound,
pixi-filters) plus a real verification layer (pixelmatch + playwright +
jsdom+@napi-rs/canvas)." 07-improve-2d.md §0
The usual claim "PixiJS v8 is WebGL2-only, so you need a browser (or a compiled gl) for headless pixels" is false. PixiJS v8 ships a real CanvasRenderer (type: RendererType.CANVAS) and its autoDetectRenderer order is webgpu | webgl | canvas; driving it with jsdom + @napi-rs/canvas produces engine-drawn pixels in plain Node with zero native build. Verified, not inferred.
07-improve-2d.md §1
The Canvas2D renderer is a reduced pipeline: it silently skips filters and masks (the repo's glowLayer BlurFilter would not render there), and WebGL-in-Node via @pixi/node + gl is compiler-gated — it failed here at node-gyp with "Could not find any Visual Studio installation to use".
07-improve-2d.md §1.2, §7
Phaser 4.2.1 has the batteries, but adopting it is a framework rewrite to its Scene/GameObject model; its LLM corpus is still overwhelmingly Phaser 3 (so an agent emits v3 API that no longer exists); Phaser.HEADLESS gives logic only, no pixels (verified); the package is 112.5 MB; and cadence is the slowest of the active web engines (last push 2026-08-21).
07-improve-2d.md §4
melonJS 20.8.0 is the only serious candidate that is both batteries-included and verified to render headlessly in pure Node — plus ~250 KB minzip, 0 deps, 22 stable releases/6 mo, agent skills and an llms.txt. The blocker is 1,652 downloads/week: effectively no LLM training corpus, so an agent guesses its API and is wrong. Its docs are best only if you accept "the agent reads the shipped types, not memory".
07-improve-2d.md §3, §4
The honest summary of the whole review: the proposal's 2D half was directionally right but under-priced — it named a renderer and stopped. The 3D half was wrong, and wrong for a reason a feature matrix cannot show: a service contract, not a licence. 07-improve-2d.md 08-improve-3d.md §7
Transcribed from 02-engines.md §2; every cell was fetched on 2026-10-09
(the note pins its live date from the Date: header of api.github.com/rate_limit).
Licences were re-read from raw LICENSE files, not taken from GitHub's SPDX detection, which the note
found wrong or ambiguous in five cases.
| Engine | Licence (exact) | Latest stable + date | Stars | Rel. 6mo | Last push | Text-only authoring? | Headless CI verification | Web export | Platforms |
|---|---|---|---|---|---|---|---|---|---|
| Godot | MIT | 4.7.2-stable · 2026-08-22 (3.6.3 same day; master 4.8.0-dev) | 118,114 | 6 | 2026-10-08 | yes .tscn/.tres/.gd plain text | yes --headless --script, --import, --export-release | WebGL2 only; no WebGPU; C# cannot export to web | W M L A i Web Cons |
| Bevy | MIT OR Apache-2.0 (dual; both files present) | 0.20.0 · 2026-10-08 | 48,749 | 3 stable (+2 rc) | 2026-10-09 | yes pure Rust, no editor | yes headless.rs, headless_renderer.rs; cargo test | WASM: WebGL2 + WebGPU | W M L Web |
| Defold | Defold License v1.0, May 2020 — custom, not OSI-approved | 1.13.2-stable · 2026-10-04 | 6,356 | 10 | 2026-10-09 | partial bob CLI exists; .go/.collection text format UNVERIFIED | yes java -jar bob.jar + dmengine_headless (OpenJDK 25) | Yes (HTML5) | W M L A i Web Cons |
| O3DE | Apache-2.0 default, MIT at your option | 2605.0 · 2026-05-27 | 9,734 | 1 | 2026-10-09 | partial heavy editor/project structure | partial / UNVERIFIED | none | W L |
| Flax | Flax Engine EULA — source-available, not OSI-libre | Update 1.12.6912 · 2026-05-18 | 7,045 | 1 | 2026-10-08 | partial editor-centric | UNVERIFIED | none official | W M L |
| Stride | MIT | 4.3.0.2507 (NuGet 2025-11-16); 4.4.0-beta9 2026-10-04; Launcher 6.0.4 2026-10-06 | 7,850 | 10 (incl. betas) | 2026-10-09 | partial C#, editor + .sln | UNVERIFIED | none official | W M L A i |
| Fyrox | MIT | 1.0.1 · 2026-03-28 | 9,577 | 0 | 2026-10-08 | partial | UNVERIFIED | none | W M L |
| Unreal Engine | Proprietary Epic EULA; source-available on request; not OSI-libre | 5.8 (Epic docs page HTTP 200 on 2026-10-09; exact date UNVERIFIED) | n/a — repo 404 | n/a | n/a | partial binary .uasset | commandlet UNVERIFIED | rare/community | W M L Cons |
| Panda3D | NOASSERTION — UNVERIFIED | SDK 1.10.16 · 2025-12-25 | 5,244 | 0 | 2026-07-28 | yes (Python) | UNVERIFIED | none | W M L |
| libGDX | Apache-2.0 | 1.14.2 · 2026-06-05 | 25,437 | 3 | 2026-10-06 | yes (Java/Kotlin) | UNVERIFIED | Yes (GWT/TeaVM) | W M L A i Web |
| Cocos Creator | MIT | 3.8.8 · 2025-12-16 | 9,851 | 0 | 2026-10-08 | partial | UNVERIFIED | Yes | W M L A i Web |
| Redot (Godot fork) | MIT | 26.3-rc.3 · 2026-10-08; Redot LTS 26.2 2026-07-01 | 6,126 | 5 | 2026-10-09 | yes Godot-compatible | yes (Godot flags) | Yes | W M L A i Web |
| raylib | Zlib | 6.0 · 2026-04-23 | 35,069 | 1 | 2026-10-09 | yes (C) | no first-party flag found | Yes (WASM) | W M L A i Web |
| LÖVE | zlib (own code; third-party components bundled) | 11.5 · 2024-04-25 | 8,808 | 0 | 2026-09-20 | yes (Lua) | no first-party flag found | via love.js (3rd-party) | W M L A i |
| MonoGame | MS-PL | 3.8.6 · 2026-10-08 | 14,500 | 8 | 2026-10-07 | yes MGCB CLI content pipeline | UNVERIFIED | no official browser target found | W M L A i Cons |
| FNA | MS-PL (stated in README; no root LICENSE on master) | 26.10 · 2026-10-01 | 3,051 | 6 | 2026-10-07 | yes (C#) | UNVERIFIED | no (web dropped) | W M L Cons |
| Macroquad | MIT OR Apache-2.0 | 0.4.16 · 2026-07-30 | 4,654 | 2 | 2026-08-18 | yes (Rust) | UNVERIFIED | WASM (miniquad) | W M L Web |
| Ebiten | Apache-2.0 | v2.10.4 · 2026-09-25 | 13,547 | 5 | 2026-10-08 | yes (Go) | CI test workflow exists; mechanism UNVERIFIED | WASM/Web (official) | W M L A i Web Cons |
| Pyxel | MIT | 2.9.9 · 2026-08-12 | 18,418 | 11 | 2026-10-08 | yes (Python ≥3.11) | UNVERIFIED | WASM/Web | W M L Web |
| LittleJS | MIT | 1.27.0 · 2026-10-09 | 4,199 | 5 | 2026-10-09 | yes (JS) | browser/jsdom; headlessMode confirmed in source, logic only | Web | Web |
| SFML | Zlib | 3.1.0 · 2026-04-23 | 12,060 | 1 | 2026-10-06 | yes (C++) | UNVERIFIED | none | W M L A i |
| Heaps | MIT | 2.1.1 · 2025-06-07 | 3,506 | 0 | 2026-10-09 | yes (Haxe) | UNVERIFIED | Yes | W M L Web Cons |
| HaxeFlixel | MIT | 6.2.0 · 2026-07-20 | 2,213 | 1 | 2026-08-23 | yes (Haxe) | UNVERIFIED | Yes | W M L Web |
| OpenFL | MIT | 9.5.2 · 2026-05-13 | 2,163 | 1 | 2026-10-01 | yes (Haxe) | UNVERIFIED | Yes | W M L Web |
| PixiJS (baseline) | MIT | 8.22.0 · 2026-10-01 ← pin is current | 48,316 | 7 stable / 101 tags | 2026-10-09 | yes no editor exists | pixels in Node via its own CanvasRenderer (verified in §05); no first-party headless tool though | Web (native) | Web |
| Three.js | MIT | 0.186.1 · 2026-09-24 (tag r186) | 116,163 | 5 | 2026-10-09 | yes | WebGPU/WebGL in Node — UNVERIFIED here, resolved empirically in §04 | Web (native) | Web |
| Babylon.js | Apache-2.0 | 9.30.0 · 2026-10-08 | 26,144 | 57 npm stable | 2026-10-09 | yes | UNVERIFIED here, verified PASS in §04 | Web (native) | Web |
| PlayCanvas | MIT (engine); Editor cloud is proprietary SaaS | engine 2.23.1 · 2026-10-07; react 0.11.7 2026-09-25; web-components 0.28.0 2026-10-04 | 16,997 | 32 stable | 2026-10-09 | yes editor optional | UNVERIFIED here, resolved in §04 | Web (native) | Web |
| Phaser | MIT | 4.2.1 · 2026-07-09 | 40,435 | 4 | 2026-08-21 | yes | logic only Phaser.HEADLESS verified → renderer = null, no pixels (§05) | Web (native) | Web |
| melonJS | MIT | 20.8.0 · 2026-10-06 | 6,405 | 22 stable | 2026-10-07 | yes | pixels in Node verified via its CanvasRenderer (§05) | WebGPU, WebGL2 or Canvas2D with auto-fallback; ~250 KB minzipped; 0 deps | Web |
| Excalibur | BSD-2-Clause | 0.32.0 · 2025-12-23 | 2,351 | 0 stable / 57 prereleases | 2026-10-05 | yes | UNVERIFIED | Web (native) | Web |
| Kaplay | MIT | 3001.0.19 · 2025-06-15 | 1,810 | 0 stable / 1 tag | 2026-10-04 | yes "game library" | UNVERIFIED | Web (native) | Web |
| Rapier | Apache-2.0 | Rust 0.36.0 · 2026-09-25; @dimforge/rapier2d 0.21.0 · 2026-09-25 | 5,831 | 10 feed / 7 crates | 2026-10-06 | yes (library) | n/a | WASM | all |
| Box2D | MIT | 3.1.1 · 2025-08-07 | 10,405 | 0 | 2026-10-09 | yes (library) | n/a | box2d-wasm 7.0.0 @ 2021-11-28 → dead; use planck.js | all |
| planck.js (Box2D port) | MIT | 1.5.0 · 2026-04-07 | 5,293 | 1 | 2026-09-22 | yes (library) | browser; explicit world.step(dt) | Web | Web |
| matter-js | MIT | 0.20.0 · 2024-06-23 (stable line stale; tags are alphas) | 18,446 | 0 stable | 2026-09-30 | yes (library) | browser/jsdom | Web | Web |
| Jolt | MIT | 5.6.0 · 2026-07-11 | 11,682 | 1 | 2026-10-08 | yes (C++) | n/a | via bindings | all |
three 78,674,917 · pixi.js 5,061,531 · phaser 1,754,552 · @babylonjs/core 1,623,073 · matter-js 1,343,879 · planck 722,224 · playcanvas 373,587 · @dimforge/rapier2d 88,746 · excalibur 39,522 · kaplay 33,800 · melonjs 7,071 // crates: bevy 7,878,259 total / 1,648,461 recent · fyrox 73,732 / 5,453 (tiny ecosystem)
The same packages on a last-week window (the basis 07-improve-2d.md used, so the numbers are not comparable to the month totals above): pixi.js 1,348,473 · phaser 479,112 · melonjs 1,652 · littlejs 3,299 · excalibur 11,558 · kaplay 9,312 · planck 204,123 · @dimforge/rapier2d 22,754 · matter-js 298,169 · bitecs 13,953 · pixi-filters 100,917 · @pixi/ui 21,143 · @pixi/sound 28,946 · @pixi/tilemap 5,468 · @napi-rs/canvas 28,876,913 · playwright 109,567,471. 07-improve-2d.md §2, §3
Install weight (npm dist.unpackedSize — a proxy for install weight and tree-shaking surface, not the runtime payload):
phaser 107.3 MiB/3,593 files (112.5 MB on the 07 probe) · playcanvas 85.9 MiB/5,089 ·
pixi.js 72.7 MiB/3,745/10 deps (76.3 MB on the 07 probe) · @babylonjs/core 68.4 MiB/10,037 ·
three 19.5 MiB/1,263/0 deps · melonjs 9.0 MiB/650/0 deps (9.46 MB on the 07 probe).
02-engines.md §2 07-improve-2d.md §3
Rubric from 02-engines.md §3: Text-only authoring [3] · Headless determinism [3] · Iteration latency [2] · 2D-action richness [2] · Performance [1] · Export breadth [1] · Maintenance evidence [2] · Docs for LLMs [1] → % of 75. Ties broken by text-only, then headless. The scores are the note author's judgement, each paired with the fetched evidence that moved it — the weights are stated so they can be re-weighted.
| # | Engine | Score | What earned the score, and what docked it |
|---|---|---|---|
| 1 | Godot 4.7.2 | 88.0 | .tscn verified plain text; --headless verified; 6 releases/6mo; 118k★; MIT. Docked: headless creates no rendering device; .import needs a pass; editor-centric ecosystem. |
| 2 | Bevy 0.20.0 | 85.3 | Zero GUI ever; first-party headless.rs and headless_renderer.rs; asset hot-reload. Docked: compile-latency tax per agent cycle; API churn hurts LLM accuracy. |
| 3 | Phaser 4.2.1 | 85.3 | Node-native, MIT, 1.75 M dl/mo, huge LLM corpus, complete 2D action set. Docked: headless unverified for v4 — later resolved by 07-improve-2d.md, which verified Phaser.HEADLESS boots logic only, no pixels; slowest push of the active web engines; Canvas/WebGL only. |
| 4 | PixiJS 8.22.0 (baseline) | 84.0 | No editor exists at all; 7 stable releases in 6 months; README: "WebGL & WebGPU Renderers"; the repo already proves text-only + headless-Node. Docked: it is a renderer — physics/UI/scene plumbing is the agent's job, a gap 07-improve-2d.md prices at 3 load-bearing packages. |
| 5 | melonJS 20.8.0 | 84.0 | WebGPU/WebGL2/Canvas2D auto-fallback, ~250 KB, zero deps, 22 stable releases/6mo; Node pixels verified in §05. Docked: 1,652 dl/week → thin corpus. |
| 6 | Ebiten v2.10.4 | 84.0 | Go: fast builds, single static binary, 5 releases/6mo, official WASM/desktop/mobile. Headless capped at 4 pending verification of the exact mechanism. |
| 7 | Babylon.js 9.30.0 | 82.7 | Apache-2.0, 57 stable npm releases in 6 months, webgpuEngine.ts present, batteries included, excellent docs — the most engine-like web 3D option, confirmed empirically in §04. |
| 8 | Three.js 0.186.1 | 81.3 | 78.7 M dl/mo, 0 runtime deps, 20 MiB package, R3F 9.8.1 + drei 10.7.9. Docked: renderer-not-engine; 2D action work is all custom; WebGL path dead in Node (§04). |
| 9 | MonoGame 3.8.6 | 77.3 | C#/MS-PL, 8 releases/6mo, CLI content pipeline (MGCB). Docked: MS-PL is OSI-approved but non-standard; no official browser target found. |
| 10 | Excalibur 0.32.0 | 76.0 | BSD-2-Clause, TS-first, pleasant API. Docked hard on cadence: 0 stable releases in 6 months, 57 prerelease tags. |
cannon.js (2023-08-04) and successor cannon-es (0.20.0 @ 2022-08-12), box2d-wasm (7.0.0 @ 2021-11-28), matter-js stable line (0.20.0 @ 2024-06-23), miniplex (2.0.0 @ 2023-07-16, no publish in ~3 years), @pixi/spine-pixi (2.1.1 @ 2024-09-24, 573 dl/wk — not recommended).EpicGames/UnrealEngine), Flax (LICENSE.md is an EULA at flaxengine.com/licensing), Defold (custom, non-OSI), MonoGame/FNA (MS-PL; FNA also inherits an LGPL-dual LzxDecoder.cs), olcPixelGameEngine ("OLC-3", GitHub reports "Other"), GDevelop (MIT core but GUI/event-sheet-centric), OpenRA/Descent3/OpenJK (GPL copyleft), and bitecs (MPL-2.0 — file-level copyleft, fine to consume, note it if you vendor it).latest disagrees with GitHub's "latest release" in both cases.This is the one layer where the proposal is wrong, and it is wrong for a reason that never appears in a feature matrix. The note's verdict (§0): "PlayCanvas is not the 3D layer for an agent-driven game-building pipeline… Choose Babylon.js 9.30.0 as the 3D engine (Apache-2.0, batteries included, NullEngine + Havok + glTF all verified working in Node with no GPU, official MCP authoring servers, ships its own TypeScript types) with Three.js 0.186.1 as the renderer-first alternative." 08-improve-3d.md
playcanvas.com/terms (last updated 2024-05-15), §4 "Your Account":
"You will not register any accounts by non-humans, 'bots' or other automated methods as such methods
are expressly prohibited." §2 grants only "a personal, worldwide, non-assignable, non-exclusive,
non-sublicenseable, and revocable license to access and use the Services", and §3 grants PlayCanvas a broad
IP licence over content you make publicly available on the Services. Text extracted live from the page.
Why this bites: the engine is MIT, the editor frontend repo is MIT, but the parts a build agent most needs — project storage, asset import/transcode, scene/prefab authoring, version control, publishing/hosting and the REST API — exist only on the proprietary cloud ("A project lives in the Editor"). The engine-standalone path is first-party documented and legal; it just means the agent must rebuild the asset pipeline, scene authoring and hosting itself.
08-improve-3d.md §1 · playcanvas.com/terms · engine/standalone docs
Agent tooling exists but is editor- or session-bound: @playcanvas/editor-mcp-server@0.7.1 (2026-09-09) needs
"an open Editor session"; third-party playcanvas-mcp@1.0.3 (2026-10-01) drives the REST API and needs
PLAYCANVAS_ACCESS_TOKEN, i.e. a real account. MIT-editor optics are the trap here: nothing is a
licence trap for the code; the trap is a service contract.
08-improve-3d.md §1, §7
The note author installed all three engines in a scratch directory (%TEMP%\gg3d, Node v24.21.0) and ran
them. Raw run outputs: research/tavily/3d-harness-results.json.
| Engine | Installable headless device | Verified result | What it does not verify |
|---|---|---|---|
| Babylon.js 9.30.0 | NullEngine (present at @babylonjs/core/Engines/nullEngine.d.ts; docs since v3.1: "does not require a WebGL-capable device"; Node needs global.XMLHttpRequest = require("xhr2").XMLHttpRequest) |
PASS NullEngine scene, 60 × scene.render() at fps 60, no GPU.PASS Havok physics headless: HavokPhysics({wasmBinary}) + PhysicsAggregate → box fell y 0.000 → −0.159.PASS glTF headless: ImportMeshAsync(DamagedHelmet.gltf) with xhr2 → 2 meshes, 14,359 verts, 6 textures.
|
Pixels — docs: "does not produce any rendering". Pixels come from a headless browser pass. |
| Three.js 0.186.1 | No scene harness at all; WebGLRenderer needs WebGL2, so headless-gl cannot help (source throws THREE.WebGLRenderer: WebGL 1 is not supported since r163 and only ever requests webgl2). gl latest stable 8.1.6 (2024-10-29); 9.0.0-rc.10 (2026-04-10) is still WebGL1-only. Installing gl here failed (node-gyp: no Visual Studio). |
DEAD in Node new WebGLRenderer() → Error creating WebGL context.PASS when shimmed WebGPURenderer with webgpu@0.6.2 (Dawn: requestAdapter()→ADAPTER OK, requestDevice()→DEVICE OK on this machine), navigator.gpu, globalThis.self = {requestAnimationFrame,…} (three's Animation.js reads typeof self) and a canvas stub that fabricates its texture from three's own device → init()+render()+submit completed (backend=WebGPUBackend). Loaders additionally need DOM shims (ProgressEvent is not defined).
|
Anything you don't shim yourself; there is no NullEngine-style facilities/logic harness. |
| PlayCanvas 2.23.1 | NullGraphicsDevice + DEVICETYPE_NULL + NullPhysicsWorld ship in the engine; the null-device source comment reads "nothing is rendered, so headless environments such as Node.js can omit the canvas". Docs: "The PlayCanvas Engine fully supports running in Node.js… runs its own unit tests using Node.js." |
PASS (logic) NullGraphicsDevice app, 30 × app.update(1/60), ESM Script.update ran.PASS (GPU) only with scaffolding createGraphicsDevice(canvas,{deviceTypes:['webgpu:bare']}) without DOM → window is not defined; with jsdom + webgpu@0.6.2 → deviceType=webgpu and app.update() ran with Render/Camera/Light systems.
|
Physics and asset-registry loading: register Ammo yourself via WasmModule.setConfig('Ammo',…) and use jsdom for XHR-based asset loading. The company's own rule for agents: "Run the app to verify. Rendering, input and physics can only be confirmed in a browser." |
Headless ranking: 1) Babylon (scene graph + animation + physics + glTF asset validation, one documented
xhr2 polyfill, no DOM) → 2) Three (no logic harness; real offscreen WebGPU rendering is now achievable but
only with hand-written self/canvas/device shims; the WebGL path is dead in Node) → 3) PlayCanvas (good logic
harness, but asset loading needs jsdom and physics needs a wasm you supply, and its own docs disclaim browser-free rendering).
For pixels, all three converge on the same answer: a headless browser pass (Playwright/Chrome) — identical
work for all three. 08-improve-3d.md §2
| PlayCanvas 2.23.1 | Babylon.js 9.30.0 (+ Lite 1.33.0) | Three.js 0.186.1 | |
|---|---|---|---|
| Licence | MIT engine + MIT editor frontend; cloud/Editor SaaS proprietary, bot accounts prohibited (playcanvas.com/terms §4) | Apache-2.0 (@babylonjs/core); @babylonjs/havok MIT; Babylon Lite Apache-2.0 | MIT, 0 runtime deps |
| Latest + date | engine 2.23.1 @ 2026-10-07; React 0.11.7 @ 2026-09-25; web-components 0.28.0 @ 2026-10-04 | core 9.30.0 @ 2026-10-08 (58 npm releases in 6 months); Lite 1.33.0 @ 2026-10-05 | 0.186.1 @ 2026-09-24 |
| Adoption (last week npm) | playcanvas 88,194 · react 6,684 · web-components 1,294 | core 350,575 · loaders 205,664 · gui 67,449 · havok 23,882 · Lite 2,305 | three 20,100,280 · R3F 5,915,275 · drei 4,180,583 · @types/three 12,885,346 |
| Package weight (unpacked) | 90.1 MB / 5,089 files / 2 deps | core 71.7 MB / 10,037 files / 0 deps; Lite 45.3 MB / 0 deps | 20.4 MB / 0 deps |
| TypeScript types | ships own (build/playcanvas.d.ts) | ships own (index.d.ts) | not bundled → @types/three@0.186.0 (DefinitelyTyped) |
| Headless logic tests | NullGraphicsDevice, app.update(dt) — verified | NullEngine — verified, incl. physics + glTF | none built-in |
| Physics | bring ammo.js | Havok (@babylonjs/havok 1.3.14, MIT) + cannon/ammo plugins; verified headless | bring @dimforge/rapier3d-compat 0.21.0 (10.4 M/week) or cannon-es (stale, 2022-08-12) |
| Animation / retarget | state-graph assets; no retarget/IK symbols in the engine d.ts | keyframe + skeletal + morph + Bones/boneIKController; official Animation Retargeting page/tool | SkeletonUtils.retargetClip() in official addons |
| GUI / UI | ElementComponent/ScreenComponent in engine; PCUI 6.1.4 for tools | @babylonjs/gui 9.30.0 + GUI-editor MCP server | none official (three-mesh-ui 6.5.4 last published 2023-03-24) |
| Navigation / AI | none in engine; third-party @recast-navigation/playcanvas 0.43.1 | Navigation/* + RecastJS plugin in core | third-party @recast-navigation/three 0.43.1 |
| WebGPU | WebGL2 default, WebGPU opt-in + auto-fallback | full WebGPU engine; cannot switch WebGPU⇆WebGL after scene creation; Lite is WebGPU-exclusive | WebGPURenderer falls back to WebGL2; TSL |
| Agent-facing docs / MCP | llms.txt "entry point for AI agents", llms-full.txt 3.03 MB, Skills, Editor MCP server (needs an open editor session) | llms.txt 4.3 KB index, sitemap 4,747 doc pages, official @babylonjs/mcp-servers@9.30.0: 7 headless graph-authoring MCP servers runnable via npx | llms.txt + docs/llms-full.txt 363 KB; community threejs-devtools-mcp@0.4.1 |
| API stability evidence | 32 stable releases/6mo plus Engine 1→2 API breaks ("Code written for Engine 1 often breaks in Engine 2") | 58 npm releases in 6 months on the 9.x line, backward-compatible | 5 r-releases/6mo, a documented migration guide each time |
| Package | Version | Date | Licence | Why |
|---|---|---|---|---|
@babylonjs/core | 9.30.0 | 2026-10-08 | Apache-2.0 | engine + NullEngine |
@babylonjs/loaders | 9.30.0 | 2026-10-08 | Apache-2.0 | glTF/OBJ/STL — headless asset validation |
@babylonjs/materials | 9.30.0 | 2026-10-08 | Apache-2.0 | sky, grid, decals |
@babylonjs/gui | 9.30.0 | 2026-10-08 | Apache-2.0 | HUD/UI without DOM |
@babylonjs/havok | 1.3.14 | 2026-07-29 | MIT | physics, verified working under NullEngine |
@babylonjs/addons | 9.30.0 | 2026-10-08 | Apache-2.0 | navigation/RecastJS plumbing |
xhr2 | latest | — | MIT | the documented Node shim for glTF loading |
@babylonjs/inspector | 9.30.0 | 2026-10-08 | Apache-2.0 | dev-only debug in the browser |
@babylonjs/mcp-servers | 9.30.0 | 2026-10-08 | Apache-2.0 | optional: graph-authoring MCP servers for the agent |
vite + vitest | current | — | MIT | browser build + unit harness |
playwright | current | — | Apache-2.0 | the only way to verify pixels headlessly |
// tools/scene.mjs - prove the loop before any graphics work (from 08-improve-3d.md §6)
// npm i @babylonjs/core@9.30.0 @babylonjs/loaders@9.30.0 @babylonjs/havok@1.3.14 xhr2
import * as B from '@babylonjs/core';
import '@babylonjs/loaders';
import HavokPhysics from '@babylonjs/havok';
import xhr2 from 'xhr2';
import fs from 'node:fs';
import assert from 'node:assert';
globalThis.XMLHttpRequest = xhr2.XMLHttpRequest; // documented Node requirement
export async function boot({ glb } = {}) {
const engine = new B.NullEngine(); // no GPU, no DOM
const scene = new B.Scene(engine);
const ground = B.MeshBuilder.CreateGround('ground', { width: 20, height: 20 }, scene);
const box = B.MeshBuilder.CreateBox('box', { size: 1 }, scene);
box.position.y = 4;
const havok = await HavokPhysics({ // pass the wasm,
wasmBinary: fs.readFileSync('node_modules/@babylonjs/havok/lib/esm/HavokPhysics.wasm') // not fetch()
});
scene.enablePhysics(new B.Vector3(0, -9.81, 0), new B.HavokPlugin(true, havok));
new B.PhysicsAggregate(box, B.PhysicsShapeType.BOX, { mass: 1 }, scene);
new B.PhysicsAggregate(ground, B.PhysicsShapeType.BOX, { mass: 0 }, scene);
if (glb) await B.ImportMeshAsync(glb, scene); // asset smoke test
return { engine, scene, box };
}
export async function step(scene, frames = 120, dt = 1 / 60) {
const zero = scene.getEngine().getDeltaTime();
for (let i = 0; i < frames; i++) scene.render(); // deterministic ticks
return zero;
}
if (import.meta.url === `file://${process.argv[1].replace(/\\/g, '/')}`) {
const { scene, box } = await boot();
const y0 = box.position.y;
await step(scene);
assert.ok(box.position.y < y0, 'gravity did not apply'); // logic assertion
console.log('OK headless: y', y0.toFixed(2), '->', box.position.y.toFixed(2));
}
Then, in order: (1) this harness + npm test; (2) a Playwright spec that boots the same scene in Chromium and
screenshots it (the pixel gate); (3) one glTF from the Khronos sample set as the asset smoke test; (4) only after that,
gameplay. Keep assets out of git or use LFS — the zero-external-asset discipline of the 2D project does not
survive 3D. 08-improve-3d.md §6
webgpu-in-Node trick depends on the runner exposing a Dawn adapter
(it worked on this machine, unproven elsewhere). Desktop-first 3D should leave the browser entirely:
Godot 4.7.2 (MIT) then Bevy 0.20.0. 08-improve-3d.md §8, §9
planck/@dimforge/rapier2d, bitecs, @pixi/tilemap,
@pixi/ui, @pixi/sound, pixi-filters) plus a real verification layer
(pixelmatch + playwright + jsdom+@napi-rs/canvas)."
The deciding evidence: PixiJS is the only candidate empirically proved to render headlessly both in a
GPU-free browser (WebGL2 via ANGLE/SwiftShader) and in pure Node (its built-in Canvas2D
CanvasRenderer on @napi-rs/canvas, no compiler). A swap to Phaser or melonJS is only better
greenfield; here it rewrites essentially 100 % of src/ for no capability this stack does
not already supply. 07-improve-2d.md §0
The common assumption — "PixiJS v8 is WebGL2-only, so headless pixels need a browser or a compiled
gl" — is false. PixiJS v8 ships a real CanvasRenderer
(type: RendererType.CANVAS), its autoDetectRenderer order is webgpu | webgl | canvas
and it will drop to Canvas2D; GlContextSystem tries getContext("webgl2") then falls back
to getContext("webgl") (v8 supports WebGL1). Driving the canvas path with jsdom +
@napi-rs/canvas produces engine-drawn pixels in plain Node with zero native build.
07-improve-2d.md §1.1 (row I), §1.2
| # | Path | How | Result |
|---|---|---|---|
| D | PixiJS 8.22.0 in headless Chrome + SwANGLE | --headless=new --disable-gpu --enable-unsafe-swiftshader --use-gl=angle --use-angle=swiftshader --virtual-time-budget=15000 --dump-dom |
PASS In-page pixel probe returned {"rendererName":"webgl","isWebGL2":true,"glVersion":"WebGL 2.0 (OpenGL ES 3.0 Chromium)","pixelsTotal":173900,"nonBg":59644,"distinctColors":3,"ok":true}; --screenshot PNG written (7,314 bytes). |
| E | PixiJS 8.22.0 in pure Node | built-in CanvasRenderer + jsdom + @napi-rs/canvas |
PASS PIXI node renderer = canvas | type = 4 | ctor = CanvasRenderer → {"nonBg":9600,"distinctColors":2} (exactly the 120×80 rect). No GPU, no compiler, no gl. |
| A | @pixi/node WebGL-in-Node |
npm i @pixi/node@8.0.0 |
FAIL Pulls native gl, which has no Node-24/win32 prebuild and fell back to node-gyp → Could not find any Visual Studio installation to use. |
| B/C | @napi-rs/canvas as a WebGL substitute / bare pixi.js in Node |
node test-node.mjs |
getContext("webgl2") THREW: webgl2 is not supported; getContext("webgl") THREW: webgl is not supported; importing pixi.js without a DOM → ReferenceError: document is not defined. |
| F | melonJS 20.8.0 in pure Node | CanvasRenderer + jsdom + @napi-rs/canvas |
PASS (Canvas2D) engine-drawn ColorLayer filled the buffer → {"renderer":"CanvasRenderer","nonBg":480000,"distinctColors":1} (all 800×600 px). Caveat: a Text renderable then threw a node-canvas drawImage type error (UNVERIFIED fix). |
| G | Phaser 4.2.1 in pure Node | new Phaser.Game({type: Phaser.HEADLESS}) |
PARTIAL import needs Element/screen globals shimmed; the game boots (HEADLESS=3 | CANVAS=1 | WEBGL=2 | AUTO=0) but renderer = null → no pixels, logic only. |
| H | LittleJS 1.27.0 | grep of littlejs.esm.js |
Ships a headlessMode that returns before setup and drawFrame() → logic harness, no pixels. |
| I | PixiJS v8 renderer inventory | grep of vendor/pixi.mjs |
autoDetectRenderer order webgpu | webgl | canvas; webgl2 → webgl fallback; the Canvas2D renderer prints CanvasRenderer: filter "X" is not supported in Canvas2D and will be skipped. |
| Candidate | Pixels in browser (software GPU) | Pixels in Node (no browser, no compiler) | Logic in Node |
|---|---|---|---|
| PixiJS 8.22.0 | yes — verified (D) | yes — verified (E) via its own CanvasRenderer; WebGL-in-Node needs gl and failed here (A) | trivial (it is a library; the ticker is yours) |
| Phaser 4.2.1 | yes (same Chrome recipe) | no first-party path; canvas renderer exists but Node boot is brittle (needs Element/screen) | yes — Phaser.HEADLESS verified (G) |
| melonJS 20.8.0 | yes (same Chrome recipe) | yes — verified (F) CanvasRenderer (engine's own AUTO falls to Canvas when no WebGL) | yes |
| LittleJS 1.27.0 | yes (4 K-line engine, Canvas2D/WebGL) | likely (it has a Canvas2D path) — UNVERIFIED | yes (headlessMode, H) |
| Excalibur 0.32.0 / Kaplay 3001.0.19 | yes (Canvas2D/WebGL) | UNVERIFIED | UNVERIFIED |
| Godot 4.7.2 (web) / Bevy 0.20.0 (WASM) / Defold 1.13.2 (HTML5) | WebGL2-only export / WebGL2+WebGPU / HTML5 export | n/a — native toolchains | --headless --script / headless.rs+headless_renderer.rs / dmengine_headless |
PixiJS is a renderer: everything below is code the agent must otherwise write and verify.
Three packages are load-bearing — planck (or rapier2d), bitecs,
and the verification pair around pixelmatch. 07-improve-2d.md §2, §6
| Gap the agent must hand-write | Package that closes it | Version (published) | Licence | Unpacked / deps / dl last-wk | Maintenance reality |
|---|---|---|---|---|---|
| Physics + collisions | planck (Box2D 2.3 port, pure JS) | 1.5.0 (2026-04-07) | MIT | 9.09 MB / 0 / 204,123 | active; engines.node ≥24; explicit world.step(dt) — a fixed-timestep-friendly engine |
| ″ performance alternative | @dimforge/rapier2d (Rust→WASM) | 0.21.0 (2026-09-25) | Apache-2.0 | 4.10 MB / 0 / 22,754 | active; async WASM init; float determinism across platforms is not guaranteed — pin it |
| ″ avoid | matter-js | 0.20.0 (2024-06-23) | MIT | 0.93 MB / 0 / 298,169 | stable line stale; repo tags are alphas |
| ECS / scene lifecycle | bitecs (data-oriented, typed arrays) | 0.4.0 (2025-12-06) | MPL-2.0 | 1.75 MB / 0 / 13,953 | active; file-level copyleft — fine to consume, note it if you vendor it |
| ″ alternative | miniplex | 2.0.0 (2023-07-16) | MIT | 0.11 MB / 3 / 7,946 | stale — no publish in ~3 years |
| Tilemaps / Tiled | @pixi/tilemap (renders; parser is yours) | 5.0.2 (2025-07-14) | MIT | 0.52 MB / 0 / 5,468 | active; peer pixi.js ≥8.5 |
| UI (in-canvas) | @pixi/ui | 2.4.1 (2026-10-01) | MIT | 2.75 MB / 1 / 21,143 | active; peer pixi.js ^8.6.2 |
| ″ DOM UI — already chosen here | none needed — #ui-root + overlay.css | — | — | 0 | DOM UI is more accessible, keyboard-reachable and directly assertable via --dump-dom |
| Audio plumbing | @pixi/sound (WebAudio + filters) | 6.0.1 (2024-07-27) | MIT | 1.04 MB / 0 / 28,946 | maintained but quiet; this repo synthesises audio, so mostly redundant |
| Filters / post-FX | pixi-filters (bloom, glow, CRT, shockwave…) | 6.1.5 (2025-11-29) | MIT | 2.69 MB / 1 / 100,917 | active; BlurFilter is already built into pixi.js |
| Input mapping / save-load | no package — Pixi events + localStorage | — | — | 0 | src/core/input.js (417 lines) and src/core/save.js (318 lines) already exist |
| Sprite / skeletal helpers | @pixi/spine-pixi | 2.1.1 (2024-09-24) | MIT | 1.47 MB / 1 / 573 | stale & near-unused — not recommended; procedural Graphics + generateTexture needs no package |
| Node-side headless pixels | jsdom + @napi-rs/canvas | 1.0.10 (2026-10-01) | MIT / MIT | prebuilt / — / 28,876,913 | prebuilt, no compiler — verified working here |
| Visual regression + input injection | pixelmatch + playwright | 8.0.0 (2026-10-06) / 1.64.0 (2026-10-07) | ISC / Apache-2.0 | tiny / 5.03 MB / 109,567,471 | from 05-qa-harness.md; pixelmatch is pure ESM and drops straight into the existing tools |
What the agent still owns after this stack (and why that is acceptable): the game simulation
(steering AI, wave budget, upgrade rarity/clash rules, projectile pooling, XP curve) — that is the actual depth of a
roguelite and no engine hands it over; Tiled/TMX parsing (@pixi/tilemap renders, it does not
parse); determinism discipline; and netcode (out of scope, single-player).
07-improve-2d.md §6
Scene/GameObject model; v4.2.1's LLM corpus is still overwhelmingly Phaser 3, so an agent emits v3 API that no longer exists; the Node HEADLESS type gives logic only (verified), so pixels still need a browser; the package is 112.5 MB; cadence is the slowest of the active web engines (last push 2026-08-21).npx skills add) and an llms.txt. Costs: 1,652 downloads/week → effectively no LLM training corpus; docs and examples assume the Tiled + browser workflow; the Node-Text interop bug.headlessMode (confirmed in source). Fine for a tiny prototype; too thin for an arena roguelite with 400+ entities, and no corpus.headless_renderer.rs gives a real image diff) but pays a Rust compile tax per agent cycle.Measured over src/** on 2026-10-09: 07-improve-2d.md §5
| Group | Files | LOC | Swap → Phaser | Swap → melonJS | Stay on PixiJS + stack |
|---|---|---|---|---|---|
src/main.js | 1 | 63 | 1/1 | 1/1 | 0 |
src/core/** | 13 | 4,213 | 13/13 | 13/13 | 0 |
src/game/** | 12 | 5,051 | 12/12 | 12/12 | 0 |
src/ui/** | 5 | 2,141 | 5/5 | 5/5 | 0 |
src/dev/fxlab.js | 1 | 593 | 1/1 | 1/1 | 0 |
| Total | 32 | 12,061 | 32/32 files, ~12,061 LOC | 32/32 files, ~12,061 LOC | ~6–10 files touched, 0 rewritten; ~600–1,200 new lines for physics/ECS adapters |
Pure-renderer-bound code that must be re-authored in any other framework's drawing model —
core/textures.js 181 + core/textures/shapes.js 410 + core/particles.js 660 +
core/postfx.js 331 + core/camera.js 311 + ui/rendering.js 492 + ui/hud.js 512 +
ui/dom-ui.js 437 + ui/dom-panels.js 266 + ui/overlay.css 434 + dev/fxlab.js 593
= ~4,627 LOC (38 %) — and that is a lower bound, because every entity's view/layer wiring
inside src/game/** is written against Pixi Container/Sprite/Graphics.
A swap would also invalidate tools/check.mjs's import-map and "no PixiJS-v7 API" checks, rewrite
docs/SPEC.md §(b)(e)(f), and replace the index.html import map.
07-improve-2d.md §5
| Not solved | Fatal? | Honest position |
|---|---|---|
| No first-party Tiled/TMX parser | No | @pixi/tilemap renders; parsing is ~200 lines or Tiled's JSON export. This repo bakes its own grid and needs none. |
| No first-party physics / ECS / netcode | No | planck + bitecs close physics/ECS; netcode is out of scope. |
| The Canvas2D Node path skips filters & masks | No for CI; yes if you expect bloom in a Node screenshot | Keep the browser SwANGLE pass as the polished-look gate (verified, D); use the Node path for fast layout/HUD assertions. |
WebGL-in-Node needs a compiled gl | No | Use the browser for the WebGL path; the Node path uses Canvas2D. Do not adopt @pixi/node in CI. |
| No GPU frame-time gate | Not solvable by any web engine headless | Already proven in 05-qa-harness.md; claim only JS/heap via Performance.getMetrics. |
| Ambient RNG draws in render code | No (already guarded) | tools/check.mjs bans Math.random(); extend the ban to src/core/** per 05-qa-harness.md. |
Why determinism survives: PixiJS is only the renderer; the simulation stays the repo's own 60 Hz
fixed-step loop over world.rng. Adding planck (explicit world.step(dt), no hidden clock)
or bitecs (entity ids and iteration order are explicit arrays, not hash-map order) strengthens
reproducibility rather than endangering it — provided RNG draws stay inside the sim and out of render.
Costs: a physics engine adds a float path (planck is deterministic per build for the same step
sequence; rapier2d's WASM floats are not guaranteed bit-identical across platforms, so pin it and keep gameplay
RNG out of it); and the Node-canvas pixel path validates layout/geometry/HUD placement, not the bloom look.
The recommended stack adds no build step and every package is either 0-dep or prebuilt, so the repo's
"no bundler, no transpiler, import map" identity survives — with a vendor/-style copy per package, exactly
as it already does for pixi.js. 07-improve-2d.md §7, §8
Keep the three existing tools as the default npm test path and add layers around them, adding at most two
dev-only dependencies. Layer order matters: each layer is cheap and fast, and a failure in an earlier layer invalidates
the later ones. Layers 1–3 take seconds; 5–7 take minutes and belong on a schedule, not on every edit.
05-qa-harness.md §3
npm test
├─ 1 node --test tools/unit/ → pure logic: math, RNG, pool, waves, upgrades [0 deps]
├─ 2 node tools/check.mjs → static/policy/DOM/import contract [0 deps]
├─ 3 node tools/smoke.mjs → boot, schema, stateHash + rngDraws determinism [0 deps]
├─ 4 node tools/input.mjs → real keyboard/mouse paths (NEW, dev-only dep)
├─ 5 node tools/shots.mjs → content assertion + stability gate + diff [+pixelmatch]
├─ 6 node tools/perf.mjs → wall-clock frame pacing, heap, long tasks [0 deps]
└─ 7 node tools/soak.mjs → peak caps, pool return, heap growth [0 deps]
engine-side pixel path (no browser, from 07-improve-2d.md)
└─ node tools/pixels.mjs → PixiJS CanvasRenderer + jsdom + @napi-rs/canvas [prebuilt]
tools/shots.mjs asserts only that the PNG
exists with non-zero size, that #fatal is still hidden and that data-game-state is not
boot/shell. A fully black canvas, a blank white page, a wrong palette, a missing glow layer or an
unrendered HUD all pass as long as Chrome wrote some bytes. shots.mjs is currently a
capture tool, not a visual-regression tool.
05-qa-harness.md §1.3
The fix needs no baseline, no decoder and no new dependency: add a ?shotcheck=1 mode that draws the
live canvas into a small 2D context, quantizes to a 16×16 grid, and writes
{nonBackgroundRatio, distinctBuckets, gridHash} into #autotest-report. --dump-dom then
yields a machine-readable assertion that the canvas is not blank, not one flat colour and not the wrong palette —
exactly the bug class that passes today. Note that the 2D note's Node path uses the same idea natively: its probes returned
{"nonBg":59644,"distinctColors":3,"ok":true} in the browser and {"nonBg":9600,"distinctColors":2}
in Node, i.e. a content assertion with no baseline at all.
05-qa-harness.md §3 Layer 4 07-improve-2d.md §1.1
| Layer | Exists? | Catches today | Missing |
|---|---|---|---|
| Static / policy | yes, strong | syntax, imports, determinism lint, DOM contract | currently red (15 findings); the layer check is a substring heuristic |
| Unit / property (logic) | no | — | everything: math, RNG, pool, waves, economy |
| Simulation determinism | partly | 4-field fingerprint (kills, wavesReached, playerLevel, upgradesTaken) | full state hash, RNG draw count, replay artifact. A stray Math.random() in src/core/** — which check 5 does not scan — would not move any of the four fields |
| Input injection | no | — | keyboard/mouse paths, upgrade selection, pause/restart. Chrome is driven with --dump-dom only |
| Visual regression | no | file exists + DOM state | baseline diff, content assertion, masking, thresholds |
| Performance | no | — (avgFrameMs/fps are virtual-clock deltas, correctly printed and never asserted) | wall-clock pacing gate, heap, long tasks |
| Soak / leak | no | — | peak-cap and heap-growth gates over minutes; the three peak counters are reported but never asserted |
--use-gl=angle --use-angle=swiftshader + --disable-gpu + --enable-unsafe-swiftshader (Chromium's documented SwANGLE recipe). The repo already passes the last two — it is on the right side of this. SwiftShader's automatic fallback is deprecated; the opt-in flag is required. The 2D harness confirmed the full recipe works here (row D).includeAA: false) before accepting either capture — the manual equivalent of Playwright's "wait until two consecutive screenshots yield the same result".rAF before capture so the compositor has promoted the WebGL back buffer into the surface a screenshot reads; this replaces setTimeout(200) with a wait exactly as long as needed. In headless Chromium the timer-query/fence path is degenerate: SwiftShader's synchronous pipeline makes clientWaitSync report signalled immediately.deviceScaleFactor: 1, scale: "css", --force-color-profile=srgb, fixed seed. Do not copy --disable-lcd-text/--font-render-hinting=none from the text-site case study — they are font-antialiasing flags and are irrelevant to a canvas.?shotcheck=1 mode above, and the Node pixel probe).threshold: 0.1 and a low-single-digit-percent maxDiffPixelRatio.page.screenshot() renders the page background where a WebGL canvas should be after setViewportSize() resizes the drawing buffer — Windows fails 5/5, macOS passes 5/5, with the GL context healthy and gl.readPixels() returning the correct colour. Chromium is the only browser where canvas capture is defensible today.EXT_disjoint_timer_query_webgl2 support is 65.52% overall, 81.21% Chrome, 0.01% Firefox, 0.23% Safari. In headless SwiftShader the fence query degenerates, and SwiftShader is a CPU rasterizer, so its frame times are not GPU frame times at all. A SwiftShader perf gate can prove "the JS didn't get slower"; it cannot prove "the game hits 60 fps on a real GPU".
Mostly no, for a canvas game. Playwright MCP's README states it "Uses Playwright's accessibility tree, not pixel-based input… operates purely on structured data". A <canvas> contributes essentially nothing to the a11y tree, so the tool most often pitched as "an agent that can use a browser" is structurally blind to the game. Chrome DevTools MCP can drive and screenshot, but no fetched doc makes a canvas/WebGL claim for it.
No — by Playwright's own admission: v1.50 — "Display of canvas content in traces is error-prone. Display is now disabled by default." v1.47 — "We've seen reports of WebGL in Webkit misbehaving on GitHub Actions macos-13."
They are not, for browser games. LMGame Bench / GamingAgent (ICLR 2026, MIT, 987★) standardises on Gymnasium + emulators and its "UI-only Interface" is literally "Coming Soon!" in the README, last pushed 2025-11-16. Voyager last pushed 2024-04-03. mineflayer and poke-env are genuinely active — but for Minecraft and Pokémon Showdown, i.e. games with a programmatic API, not a WebGL canvas. TITAN (arXiv:2509.22170) reports 95% task completion on two commercial MMORPGs but no public code repository was found: treat it as a paper, not a tool.
--json, emitting one file under .tmp/report/ with a shared envelope: {layer, ok, seed, exitCode, findings:[{code, severity, file, line, msg, reproduce}], metrics:{}, artifacts:{}}.reproduce command and the seed (node tools/shots.mjs --only=play-20s --seed=1234). That is the difference between an agent that can self-correct and one that can only report.path:line: message shape so they stay greppable and diffable.smoke.mjs conflates a missing Chrome with a failed assertion.stateHash (string), rngDraws (number), entityIdMax, frameSteps, computed over a sim world serialized sorted by entity id with floats quantized. Both parsers read the schema from docs/SPEC.md, so extending the doc extends both checks automatically. Write the seed + input log + final hash to .tmp/replay/<seed>.json.05-qa-harness.md §3
node tools/check.mjs returned FAIL, exit 1, 15 findings: four files over the 650-line cap
(src/core/audio.js 732, src/core/particles.js 713, src/dev/fxlab.js 681,
src/game/world.js 806) and eleven main.js "layer never referenced" findings; the producers
src/dev/autotest.js and src/dev/autopilot.js are specified in docs/SPEC.md §(e) but
absent, so smoke.mjs cannot pass yet. Get the suite green first — until then a green run
carries no signal. The single highest signal-per-byte change is widening the Math.random() ban from
src/game/** + src/ui/** to src/core/**.
05-qa-harness.md §1.1
src/dev/fxlab.js was being split into
src/dev/fxlab/{layout,selftests,sim}.js during the session that produced this page, which is consistent with
the 650-line-cap findings being actively worked — so re-run check.mjs rather than quoting this count.
com.unity.ai.assistant) is deprecated in favour of
its own CLI; Unreal's Unreal MCP plugin (UE 5.8) is Experimental with "many features incomplete or
missing" and binds 127.0.0.1:8000/mcp with no authentication layer; Blender's Blender Lab server ships
with the vendor's own warning that it "will execute LLM generated code in Blender without any guards" and the recommended
mitigation is "use a virtual machine". The engine with the best agent story — Defold — deliberately
declines to ship an MCP server at all, arguing it "would mostly duplicate the existing API surface".
03-mcp-interop.md §2, §Hype vs. reality
Coding-Solo/godot-mcp 5,984★ is the most-starred Godot server and
has been stale since 2026-04-16, while hi-godot/godot-ai at 2,871★ was pushed 2026-10-08.
chongdashu/unreal-mcp 2,109★ has been dead since 2025-04-22.
executeautomation/mcp-playwright ~5.7k★ is still the #1 tutorial hit and has been stale since
2025-12-13. And the canonical discovery list, punkpeye/awesome-mcp-servers (~96k★, updated daily),
is spam-saturated: greps returned blocks of 0–30-star repos with identical badge boilerplate and off-topic domains.
Use it to discover names, then verify each repo individually.
03-mcp-interop.md §4, §Hype vs. reality
2026-07-28 (previous 2025-11-25) makes MCP stateless: the
initialize/notifications/initialized handshake is removed (version and capabilities travel in
_meta on every request), Mcp-Session-Id is deleted, server/discover is mandatory,
ping/logging/setLevel/notifications/roots/list_changed are gone, every result must carry a
resultType, and SSE resumability is removed. Consequence: "an MCP server" has not been a stable
interface across the last 12 months, and any config written before ~Aug 2026 targets a superseded handshake — including
Epic's own official Unreal MCP documentation, which still describes the removed initialize handshake. Pin versions
and expect a migration. SDKs at the snapshot: TS @modelcontextprotocol/sdk 1.32.1 (2026-10-05),
Python mcp 2.3.0 (2026-10-02). MCP is an AAIF (Linux Foundation) project, founded 2025-12-09.
03-mcp-interop.md §1
| # | Server | Version · date | Licence / stars | Why, and the caveat |
|---|---|---|---|---|
| 1 | Chrome DevTools MCP | 1.10.1 · 2026-09-23 pushed 2026-10-08 | Apache-2.0 · ~53k★ | Highest-value single install: console/network visibility plus performance traces over the real PixiJS render loop; --headless supported. Caveat: usage statistics are enabled by default — opt out with --no-usage-statistics or CI. Only officially supports Chrome/Chrome for Testing. |
| 2 | Playwright MCP | 0.0.83 · 2026-09-28 pushed 2026-10-08 | Apache-2.0 · ~38k★ | Scripted interaction and gameplay regression driving. Caveat: accessibility-tree-based, not pixel-based — it sees the DOM HUD, not the arena. |
| 3 | filesystem + memory + sequential-thinking | npm 2026.8.31 | MIT–Apache-2.0 · 91k★ (repo) | Cheap local context; all three verified current and not deprecated. Install: npx -y @modelcontextprotocol/server-filesystem <dir>, …-server-memory, …-server-sequential-thinking. |
| 4 | Official git server | PyPI 2026.8.18 | MIT | Repo history/blame without shelling out: uvx mcp-server-git --repository <path>. |
| 5 | GitHub MCP server | pushed 2026-10-08 | MIT · ~33k★ | Only if the project is on GitHub (issues/PRs/Actions): docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN ghcr.io/github/github-mcp-server. |
| 6 | basic-memory (optional) | PyPI 0.23.2 | AGPL-3.0 · ~4.1k★ | Cross-session project memory — note the copyleft licence. uvx --prerelease=allow basic-memory mcp. |
| 7 | Only if a native engine is added later | Asset Library 5050 | MIT · 2,871★ | hi-godot/godot-ai over every other Godot option (one-click config for 22 clients). For Unity, prefer the official Unity CLI over any MCP. |
| 8 | skip all Unreal servers, mem0-mcp, executeautomation/mcp-playwright, the three deprecated @modelcontextprotocol/server-* packages, and every ComfyUI/image-gen server | deprecated flags: 2025-04-08 (github), 2024-12-04 (postgres), 2025-05-12 (puppeteer) | — | The official README still shows the deprecated servers; they now live in servers-archived. NEON PURGE is procedurally drawn with zero external assets by design, so image-gen servers have nothing to do here. |
Windows note (from the official servers README): wrap npx entries as
"command": "cmd", "args": ["/c", "npx", …]; uvx entries need no wrapper.
/eval), keep policy in
the adapter, and skip the MCP layer entirely. Unity is the trap case — it shipped an MCP server first and has now
deprecated it in favour of a CLI. Note also that engine MCP bridges are arbitrary code execution in your project;
several circulating entries are 0–40-star repos.
03-mcp-interop.md §Table B, §Hype vs. reality
docs/SPEC.md/docs/INTERFACES.md.SKILL.md) — anthropics/skills ~180k★, pushed 2026-10-05; the highest-momentum of the three; now reaching engines (Unity ships unity skill install). Caveat: the repo has no root licence — the README says "many skills… are open source (Apache 2.0)" while the docx/pdf/pptx/xlsx skills are explicitly source-available, so check per skill before vendoring.a2a-sdk 1.2.2 (2026-10-05), 150+ organisations. Completely orthogonal to an agent building a single game: it links agents to each other, not to engines.The verdict is unambiguous: keep the procedural, zero-asset approach as the default. Adopt generated assets only as an optional, build-time-baked, permissively-licensed polish layer — never as a runtime or build dependency. 04-asset-pipeline.md §Recommendation
| Property | NEON PURGE today | Generated-asset route |
|---|---|---|
| Shipped payload | ≈2.52 MB (2.10 MB PixiJS + 0.42 MB src), 0 assets | + a 2048² WebP atlas (~300 KB at q85) + ~24 OGG SFX (~1.5 MB) ≈ +1.8 MB → ~4.3 MB (+72%) |
| Runtime dependencies | none (no CDN, no fonts, no images) | none if assets are committed as static files |
| Build-machine toolchain | 0 MB | ~10–33 GB of model weights + PyTorch/CUDA (dev-only) |
| Determinism | exact — seeded RNG, fixed-timestep autotest; docs/SPEC.md requires identical kills/wavesReached/upgradesTaken per seed | generated assets must be frozen as committed artefacts; the generation step itself is not reproducible without pinning model + LoRA + sampler + seed + library versions |
| Licence exposure | zero | non-zero the moment any weight file with an other/NC/community licence is used |
| VRAM / latency on the build box | 0 | 2D ~13 GB (FLUX.2-klein-4B); 3D ≥24 GB (TRELLIS.2-4B); audio <4 GB (ACE-Step 1.5) |
| Category | Pick | Weight licence | Code licence | Latest · date | Requirements / signal |
|---|---|---|---|---|---|
| 2D image | Z-Image-Turbo | Apache-2.0 | Apache-2.0 | 2025-11-25 | 620,806 downloads, 5,424 likes; bf16 weights ≈33 GB download; 8 NFEs; CPU-offload supported |
| 2D image + edit | FLUX.2-klein-4B | Apache-2.0 | Apache-2.0 (flux repo, 26,009★) | 2026-01-14 | 406,006 downloads; card: "Open weights available for commercial use under the Apache 2.0 license", ~13 GB VRAM on an RTX 3090/4070 |
| 2D image | HiDream-O1-Image | MIT | MIT | 2026-05-08 | 4,719 downloads; CUDA GPU required; the undistilled variant is the heaviest |
| Sprite/pixel LoRAs | tarn59/pixel_art_style_lora_z_image_turbo, svntax-dev/pixel_spritesheet_4walk_* | Apache-2.0 | Apache-2.0 | 2025-11-30 / 2026-02-01 / 2026-03-18 | Applied on top of Z-Image-Turbo. There is no dominant purpose-built sprite-sheet foundation model — the whole category is LoRA-shaped bolt-ons |
| 3D generation | TRELLIS.2-4B | MIT | MIT (microsoft/TRELLIS, 13,779★) | 2025-12-01 | 1,894,990 downloads, 1,300 likes; designed for arbitrary topology; ≥24 GB GPU verified on A100/H100 |
| 3D (lighter) | TripoSR / InstantMesh | MIT / Apache-2.0 | MIT / Apache-2.0 | 2024-02-29 / 2024-04-10 | ~6–8 GB and ~16 GB respectively — the fully-permissive fallbacks |
| Audio (music) | ACE-Step 1.5 | MIT | Apache-2.0 | 2026-01-23 | 68,462 downloads; card: <4 GB VRAM, "Commercial-Ready", royalty-free/public-domain training data (vendor claim, third parties cannot audit provenance from the fetch) |
| Animation / rigging | UniRig + SkinTokens, or Wan2.2-Animate-14B | MIT / MIT / Apache-2.0 | MIT / MIT / Apache-2.0 | 2025-04-09 / 2026-04-20 / 2025-09-11 | UniRig needs a CUDA GPU >8 GB. Wan-Animate-2 ComfyUI repack: 537,092 downloads |
| Optimiser / glue | bpy → gltf-transform → gltfpack → toktx/Basis → sharp+oxipng → maxrects-packer | n/a (tools) | GPL-3.0 (output unencumbered) / MIT / MIT / Apache-2.0 / Apache-2.0+MIT / MIT | bpy 5.2.2 · gltf-transform 4.5.1 · gltfpack 1.3.0 · sharp 0.35.5 · maxrects-packer 2.7.3 | The whole chain is MIT/Apache-2.0 and CLI-only (except bpy, whose GPL covers the tool, not your output). Headless throughout. |
FLUX.2-dev / FLUX.1-dev / FLUX.2-klein-9B have HF cardData.license_name literally flux-non-commercial-license. Only FLUX.1-schnell, FLUX.2-klein-4B, FLUX.2-klein-base-4B and FLUX.2-small-decoder are Apache-2.0 in the family.facebook/musicgen-* family (1,911,195 downloads on medium alone) is non-commercial for the weights.EULA.txt is an End-User Licence Agreement from Igara Studio S.A. ("licensed, not sold"), and GitHub reports no SPDX licence. Use LibreSprite (GPL-2.0) instead. pngquant is GPL-3.0-or-later (dual-licensed commercially): fine as a build tool, don't link it into a proprietary binary. ComfyUI is GPL-3.0 — Copyleft applies to ComfyUI, not to the images it produces, so never ship ComfyUI itself as a component.stabilityai/* weight repos and Lightricks/LTX-Video return HTTP 401 without an authenticated HF account and often require accepting a licence plus name/email/country. An unattended agent pipeline will fail on these unless the token and gate acceptance are handled explicitly.flux Apache-2.0, generative-models MIT, audiocraft MIT, LTX-Video repo Apache-2.0) while the weights are restricted. That is why the source note carries two licence columns.Isabellaliu/RigAnything has no declared licence at all — unusable commercially. There is no sprite-sheet model; character consistency across frames is still a human-in-the-loop job, and no published consistency metric exists (that claim is explicitly qualitative). And "Commercial-Ready" is a marketing phrase — the ACE-Step licence is verifiable, its training-data provenance is not.
04-asset-pipeline.md §Hype vs. reality
The three 2D engines that 07-improve-2d.md §9 left UNVERIFIED (Excalibur 0.32.0, Kaplay, LittleJS 1.27.0) were run headlessly on 2026-10-09, so every 2D candidate in the table is now resolved: Excalibur and LittleJS render real pixels in both a GPU-free browser and plain Node, and Kaplay resolves the other way — browser pixels yes, Node impossible without a compiled WebGL. 11-headless-pixel-matrix.md
@napi-rs/canvas only, versus nine stubs and a drawImage bridge for
Excalibur). 11-headless-pixel-matrix.md §0
| Candidate | Version · published | Licence | Browser pixels (software GPU) | Node pixels (no browser, no compiler) | Node logic | Required flags / shims | Evidence |
|---|---|---|---|---|---|---|---|
| PixiJS | 8.22.0 · 2026-10-01 | MIT | yes WebGL2 via SwANGLE, nonBg 59,644, 3 colours |
yes own CanvasRenderer: nonBg 9,600, 2 colours |
n/a (library) | repo Chrome set; Node = jsdom + @napi-rs/canvas |
07-improve-2d.md rows D/E |
| melonJS | 20.8.0 · 2026-10-06 | MIT | yes same Chrome recipe | yes CanvasRenderer: nonBg 480,000 |
yes | Node: jsdom + @napi-rs/canvas (AUTO → Canvas) |
07-improve-2d.md row F · partial (a Text + drawImage error stays open) |
| Phaser | 4.2.1 · 2026-07-09 | MIT | yes same recipe (inferred, not re-run) | no renderer = null, no first-party Node path |
yes Phaser.HEADLESS |
Node: Element/screen globals shimmed; import only |
07-improve-2d.md row G |
| Excalibur | 0.32.0 · 2025-12-23 | BSD-2-Clause | yes WebGL2 via SwANGLE: nonBg 15,700, 3 colours, #ff3355:10800 + #33ffcc:4900 = exactly the scene |
yes its own Canvas 2D fallback on @napi-rs/canvas: nonBg 15,700 — geometry identical to the browser run |
yes start('test') resolves, scene initialised, 2 actors |
Browser: repo set only. Node: 9 stubs + a drawImage bridge — global addEventListener/dispatchEvent, matchMedia, requestIdleCallback, DOMMatrix, DOMPoint, ImageData, Path2D (those four from @napi-rs/canvas), ResizeObserver, IntersectionObserver, then a jsdom→napi drawImage bridge |
11-headless-pixel-matrix.md §2.2 |
| Kaplay | 3001.0.19 · 2025-06-15 | MIT | yes WebGL: nonBg 14,648, 3 colours, same scene |
no Error: WebGL not supported — WebGL-only, no Canvas2D fallback |
no throws before a scene exists; headless appears 0× in the bundle |
Node needs a compiled WebGL (gl): no Node-24/win32 prebuild, node-gyp → no Visual Studio here |
11-headless-pixel-matrix.md §2.3 |
| LittleJS | 1.27.0 · 2026-10-09 | MIT | yes WebGL2 path (glCanvas): nonBg 12,348; 2D path (setGLEnable(false)): nonBg 12,376 |
yes setHeadlessMode(true) + an explicit @napi-rs/canvas context: nonBg 16,574, 218 colours |
yes engineStep(60) → 60 updates, frame 60 |
Node: none beyond @napi-rs/canvas (“headless has no canvas, only a context passed in is drawn to”). The non-headless jsdom boot does not finish — it awaits an Image.onload that jsdom never fires |
11-headless-pixel-matrix.md §2.4 |
requestAnimationFrame callback. With --dump-dom +
--virtual-time-budget the page loads, the PNG is written with non-zero size
(7,015 / 7,345 / 31,570 B for the three engines) and the engines' first
synchronous frame lands — but a 14-frame counter never advances, across six flag variants
including the SwANGLE recipe and --headless=old. So a file-size check cannot tell a
rendered frame from a blank one, and it also cannot tell an animated game from a single static frame.
Every pixel number above was therefore read from the live canvas over CDP with real wall-clock time.
11-headless-pixel-matrix.md §3 05-qa-harness.md §1.3
One correction for anyone installing the last row: the npm package is littlejsengine —
registry.npmjs.org/littlejs returns {"error":"Not found"}.
11-headless-pixel-matrix.md §2.1
Everything below was installed, executed and measured on this machine on 2026-10-09, not selected from a comparison table. The build changed two of the conclusions this report previously carried, confirmed one, found that one layer cannot deploy where it was assumed to go, and discovered a hard platform limit that only surfaced when the deploy was attempted. The numbers are what came back from the runs.
cf CLI (1.0.0-beta.13) cannot deploy Pages — it errors that legacy Pages is unsupported and redirects to npx wrangler pages deploy. Hard limit discovered by trying: 25 MiB per file.@babylonjs/core ships ESM only (no UMD/IIFE bundle), so it needs a one-shot esbuild vendoring step; the Havok .wasm (2,094,563 B) is inlined as base64 so the page keeps zero external subresources.meta/muse-image/text-to-image, $0.01/image) through the genmedia CLI. Usable for textures, backdrops and UI; not usable for sprites — it never produced a flat sprite and cannot output alpha at all.meshy make 30 credits → a real textured GLB; remesh 5 credits → 28,788 tris. Meshy output is not shippable as-is — 4K maps blow past the 25 MiB ceiling.server.on('upgrade') and net.Server, neither of which Workers provides. On Cloudflare the only real path is Containers (Workers Paid, per-10 ms billing).| Layer | What was actually run | Measured result | Status | Evidence |
|---|---|---|---|---|
| 3D runtime | Babylon NullEngine + Havok WASM, 240 fixed steps; then real headless Chrome over CDP |
240 steps in 34 ms (7,059 steps/s); settles at step 185; min y 0.3538 m, never through the floor; no NaN/Infinity; repeat delta 0 m | CONFIRMED | publish/stack3d/VERIFY.md |
| 3D pixels | Headless Chrome, SwANGLE, real wall-clock, in-page getImageData readback |
1256×565 canvas, 161,897 non-background px (22.5%), 455 distinct colours; two snapshots 1,409 ms apart differ by +22 frames, +87 steps, +1.45 s | CONFIRMED | VERIFY.md |
| Determinism | Same seed, two full page self-test passes; then a second seed | Seed 20261009 twice → byte-identical digest f816f570, identical layout hash; seed 20261010 → different digest, as expected |
CONFIRMED | VERIFY.md |
| Slice acceptance | node publish/stack3d/verify.mjs |
139/139 checks, exit 0, two consecutive runs; GLB path exercised | CONFIRMED | VERIFY.md |
| 3D asset | meshy make → remesh → gltf-transform (scratch dir, no runtime decoder) |
47,797,152 B / 1,136,438 tris → 28,788 tris → 2,104,760 B (2.01 MiB, −94.04%); brotli transfer 1,624,967 B | CONFIRMED | 16-asset-optimization.md |
| 2D assets | genmedia run meta/muse-image/text-to-image ×12 |
9 assets shipped, SHA-256 and PNG-CRC verified; $0.12 spent; zero alpha channels; no seed parameter exists | CONFIRMED | 12-muse-assets.md |
| Multiplayer | In-process Colyseus 0.18 room, two real clients, server-authoritative 60 Hz step | 31/31 checks, exit 0, five consecutive runs; server ↔ client fields match exactly at a shared tick; clamp exercised (max |v| = 235.000000) | CONFIRMED | 14-colyseus-mp.md |
| Colyseus on CF | Docs + source review across Pages, Workers, Durable Objects, Containers | Workers lacks net.Server and the HTTP upgrade event; no Colyseus DO transport exists (open issue #851, milestone 1.0); Containers works but is Paid-only |
CONFIRMED (negative) | 14-colyseus-mp.md |
| Live deploy | wrangler pages deploy → project neon-stack3d + CNAME |
21 files uploaded; https://stack3d.publish.games/ serves 200 | CONFIRMED | this machine, 2026-10-09 |
1. The repo's headless-Chrome recipe cannot detect a render loop at all, and one test of ours would have
passed vacuously because of it. Appendix A §3 proves that the existing
--dump-dom --virtual-time-budget flag set runs zero requestAnimationFrame
callbacks (six flag variants tested), while still writing a non-zero-byte PNG — each engine draws one
synchronous first frame. The 3D slice's first pixel test was written against that recipe, and the raw-GL control
experiment showed why it was worthless: the readback returned the clear colour only (0 non-background
pixels, mean luma 17.155 = exact background) because Babylon skips meshes whose shader is still compiling.
It was rebuilt to drive Chrome over CDP with real wall-clock time and to assert that pixels came
from a frame after the burst. Any agent-authored visual test must use a real pixel readback over a ticking
clock; a PNG size check is not evidence.
11-headless-pixel-matrix.md §3 · VERIFY.md
2. A hosted image model cannot replace procedural sprites, and that is a property of the model, not a
prompting failure. Muse obeyed hex colours exactly (a requested #808080 came back
128,128,128) and framing, but across 12 generations it never once produced a flat sprite — 20,098 to 285,038
distinct colours — and every file was colour type 2, i.e. no alpha channel is possible. The
requested seamless floor did not tile (border deviation 149.1 vs 2.8 interior), and the endpoint exposes no
seed, so runs are not reproducible. Keep sprites procedural; use hosted generation for textures,
backdrops and UI only.
12-muse-assets.md
3. Generative 3D output is not deployable without a downscale step, and the platform limit is what proves
it. The Pages deploy failed before uploading anything: Pages only supports files up to 25 MiB in
size — assets/model.glb is 33.7 MiB. Note the trap: decimating the mesh is the intuitive fix and
it is the wrong one — remesh cut geometry by 24× (1,136,438 → 28,788 tris) and the file fell
only 26%, because it re-encoded the textures to PNG at 4K and made them 130% larger. 96% of the
remaining bytes were textures. Downscaling the three maps to 1024² JPEG is what took it to 2.01 MiB.
Measure the byte split before optimising.
15-meshy-lod.md · 16-asset-optimization.md
4. A low correlation was a broken measurement, not a broken asset — and the negative control is what proved it. The first analysis measured base-colour agreement with the original at r = 0.102 and honestly flagged possible corruption. Comparing whole images in atlas space is meaningless when the two maps are not aligned there. The tell was the control: deliberately mismatched atlases also collapse to r = 0.0946. Re-measured properly at surface points by mapping vertices back through their UVs, the shipped base colour agrees with the original at r = 0.9266, and with the LOD's own agreement at 0.9339. Every fidelity claim in this report should carry a negative control for this reason. 16-asset-optimization.md
5. Tool ergonomics are where an agent pipeline actually loses time. Three verified examples: the
genmedia CLI silently drops the --flag=value equals form, so an identical prompt
returned 2240×1120 instead of 2048×1152 (and silently cost a PNG-as-WebP); @babylonjs/core
has no UMD build, so a no-build-step repo needs a deliberate one-shot vendor step rather than a CDN; and the
cf CLI's OAuth token expires hourly, which is why an early wrangler call failed with
9109/10000 while cf auth whoami succeeded minutes earlier — and
/user/tokens/verify rejects OAuth bearer tokens outright, so it is a red herring when debugging.
12-muse-assets.md · this build
No human or model has visually inspected any generated asset — the image-understanding tool was unavailable for the whole session (HTTP 429, weekly usage limit), so every art-quality statement here is numeric. The 3D slice ran on SwiftShader only (no hardware GPU), so its absolute frame rates are not a user-facing benchmark. Determinism is proven on this machine and between the page and Node (0 m difference over 240 steps), but not across OS or architecture. Colyseus on Cloudflare Containers is INFERRED from first-party documentation and an example, never executed. Nothing here was tested under load, and no long-run leak or stability test was performed. 12- · 13- · 14- · 15- · 16-*.md, VERIFY.md
Each claim above is traceable to one of sixteen research notes. The notes are not published alongside this page, so they are shown as filenames in text; the external URLs below are the real links (all fetched/observed 2026-10-09).
| Note (plain text, not a link) | Live date proof used by the note | Covers |
|---|---|---|
| research/01-agent-harness.md | api.github.com Date header → Fri, 09 Oct 2026 08:19:03 GMT | harnesses, tiers, dead projects, licence traps, Windows sandboxing, AGENTS.md / MCP / A2A adoption |
| research/02-engines.md | api.github.com/rate_limit → Fri, 09 Oct 2026 08:18:45 GMT | master engine table, rubric ranking, per-scenario picks, dead/licence traps |
| research/03-mcp-interop.md | api.github.com → 08:18:37 GMT + local clock | MCP spec state, server table, engine integration surface, registries, hype-vs-reality |
| research/04-asset-pipeline.md | api.github.com → 08:18:52 GMT (re-checked 08:25:52) | asset generation, code vs weight licences, optimiser chain, traps, the recommendation |
| research/05-qa-harness.md | api.github.com → 08:18:50 GMT | tool inventory, the 7-layer harness, anti-flake rules, what is not automatable |
| research/07-improve-2d.md | registry.npmjs.org / api.npmjs.org / jsDelivr, plus a harness the author ran in %TEMP%\gg2d-harness | the 2D verdict, the Node-canvas pixel finding, the add-on stack, migration cost, determinism fit |
| research/08-improve-3d.md | registry.npmjs.org/-/ping → 08:31:56 GMT | PlayCanvas ToS finding, empirical headless harness for all three engines, package set |
| research/tavily/2d-harness-results.json | raw run output, 2026-10-09 | rows D/E/A/B-C/F/G/H/I in §05 |
| research/tavily/3d-harness-results.json | raw Node run output, 2026-10-09 | the empirical headless results in §04 |
| research/10-workers-static-assets.md | developers.cloudflare.com docs, 2026-10-09 | static-assets-only Workers, the deploy path this repo actually uses |
| research/11-headless-pixel-matrix.md | a harness the author ran on this machine (Windows 11, Node v24.21.0, Chrome 155.0.8059.40); versions from registry.npmjs.org | closes appendix A: Excalibur / Kaplay / LittleJS headless pixels and logic, and the finding that the repo's --dump-dom flag set never ticks requestAnimationFrame |
| research/12-muse-assets.md | genmedia pricing / fal.ai OpenAPI, plus 12 real generations, 2026-10-09 | the 2D asset verdict: Meta Muse Image, $0.01/image, no alpha, no seed, the --flag=value CLI bug |
| research/13-meshy-3d.md | meshy CLI live balance + price table, 2026-10-09 | the 3D asset verdict: 30 credits for a real textured GLB, the byte's-worth validation of the binary |
| research/14-colyseus-mp.md | docs.colyseus.io, developers.cloudflare.com, raw source, 2026-10-09 | the multiplayer verdict: why Colyseus cannot run on Pages/Workers, the Durable Objects transport gap (#851), the Containers path |
| research/15-meshy-lod.md | meshy CLI live balance + price table, 2026-10-09 | the geometry-vs-texture byte breakdown that showed decimation is the wrong fix |
| research/16-asset-optimization.md | @gltf-transform/* 4.5.1 from registry.npmjs.org, 2026-10-09 | the −94.04% downscale, the 25 MiB Pages ceiling, and the superseded r = 0.102 anomaly |
| publish/stack3d/VERIFY.md | generated by publish/stack3d/verify.mjs on this machine, 2026-10-09 | the 3D slice's own acceptance evidence: 139/139 checks, Stage 1 physics, Stage 2 CDP pixels, determinism digests |
| research/tavily/improve-stack-pro.json | status completed, 45 sources, 14,540-char report | cross-check only (see below), not a primary source |
registry.npmjs.org/pixi.js ·
registry.npmjs.org/planck ·
registry.npmjs.org/@dimforge/rapier2d ·
registry.npmjs.org/bitecs ·
registry.npmjs.org/@napi-rs/canvas ·
registry.npmjs.org/@pixi/ui ·
registry.npmjs.org/pixi-filters ·
registry.npmjs.org/melonjs ·
registry.npmjs.org/@babylonjs/core ·
registry.npmjs.org/playcanvas ·
registry.npmjs.org/three ·
api.npmjs.org/downloads (playcanvas) ·
api.npmjs.org/downloads (@babylonjs/core) ·
api.npmjs.org/downloads (pixi.js, month)
playcanvas.com/terms ·
PlayCanvas: engine standalone ·
developer.playcanvas.com/llms.txt ·
playcanvas/editor README ·
playcanvas null-graphics-device source
doc.babylonjs.com — server-side / NullEngine ·
Babylon WebGPU ·
Babylon MCP servers ·
@babylonjs/core 9.30.0 NullEngine d.ts
three 0.186.1 WebGLRenderer source ·
gl 8.1.6 README (WebGL 1.0.3 only) ·
threejs.org/docs/llms.txt
Godot releases.atom ·
Godot command-line tutorial ·
Godot web export (WebGL2 only) ·
crates.io bevy ·
Defold bob manual ·
Defold AI-agents position
registry.npmjs.org/pixelmatch ·
registry.npmjs.org/vitest ·
Spector.js ·
fast-check model-based testing ·
registry.npmjs.org/@modelcontextprotocol/sdk ·
pypi.org/pypi/mcp ·
official MCP registry ·
registry about (preview)
chrome-devtools-mcp ·
playwright-mcp README (a11y-tree, not pixels) ·
Playwright release notes ·
Playwright snapshots ·
Playwright issue #42885 (Windows WebKit blank canvas)
Chromium SwiftShader doc ·
Chrome headless CLI (virtual time) ·
Web3D Survey: timer query support ·
Spector.js README
Z-Image-Turbo ·
FLUX.2-klein-4B ·
Qwen-Image 2.1 licence (non-commercial) ·
Hunyuan3D-2.1 licence (EU/UK/KR excluded) ·
Stability AI Community Licence ($1M gate) ·
ACE-Step 1.5 ·
TRELLIS.2-4B ·
UniRig ·
ComfyUI (GPL-3.0) ·
Aseprite EULA
AAIF formation (2025-12-09) ·
Unity CLI replaces its MCP server ·
Unreal MCP plugin (Experimental) ·
Blender Lab MCP server ·
AGENTS.md ·
A2A ·
anthropics/skills
Every version, date, licence, star count and download count in the engine table; the Godot web-export limits; PlayCanvas engine MIT + editor-frontend MIT + the playcanvas.com/terms bot prohibition and revocable personal licence; Babylon's NullEngine docs, xhr2 requirement and the nullEngine.d.ts path in 9.30.0; three's "WebGL 1 is not supported since r163"; gl's WebGL 1.0.3 README; every harness result in §04 and §05 (the note authors ran these themselves); MCP spec 2026-07-28 and its breaking changes; the three official engine MCP servers and their statuses; npm deprecated flags for the archived official servers; Playwright's canvas-in-trace and WebKit notes; toHaveScreenshot semantics and defaults; Chromium's SwiftShader deprecation and SwANGLE recipe; --virtual-time-budget semantics; the timer-query support percentages; the licence text for every asset model named in §08; and the repo inventory itself (all three tools read in full, node tools/check.mjs run → FAIL/15 findings).
"No retarget symbols in the PlayCanvas d.ts" and "no retarget file in the Babylon core listing" mean absent rather than named differently. Babylon Lite's 19×/3.6× medians are vendor figures. The claim that shots.mjs currently passes a blank canvas is a strong inference from reading its assertions, not an observation (neither smoke.mjs nor shots.mjs was executed). --force-color-profile=srgb / --force-device-scale-factor=1 come from a capture-sync guide plus Playwright's same-environment warning. The ~4,627-LOC figure is a lower bound on renderer-bound rewrite effort, and "no HEADLESS symbol in the Excalibur/Kaplay dist" means absent rather than named differently (shipped JS was grepped, not the whole source tree). Star counts marked "~" are shields.io-rounded; "maintained?" verdicts and all rubric scores are judgement.
EpicGames/UnrealEngine returns 404, www.unrealengine.com returns 403, and the 5.9 release-notes URL returned HTTP 200 with empty content. Only "Epic's docs host a UE 5.8 release-notes page" is verified.--write-movie headless frame capture — whether the movie writer can render without a display is an inference; test it first or use a virtual display..go/.collection text format — raw path probes 404'd (wrong paths, not proof of absence). The CLI build path is verified.headlessMode flag is confirmed), and the melonJS-Text node-canvas drawImage error (row F) is unresolved.WebGPUEngine in Node — untested; the docs recommend starting from NullEngine. Whether webgpu@0.6.2 yields a working adapter on GPU-less CI runners is unproven — it produced a real adapter on one machine.planck bit-identical across OS/arch on CI runners — unverified; rapier2d's WASM float maths is explicitly not guaranteed bit-identical across platforms.gl/headless-gl on a machine that has MSVC — the install failure here is friction evidence, not proof of impossibility (and the WebGL1 verdict means it would not help WebGLRenderer anyway).NullGraphicsDevice (readPixels is a no-op by design) and Godot's --write-movie path.daytonaio/daytona was archived (2026-07-24) could not be located.badlogic/pi-mono and Hmbown/CodeWhale returned no match.Isabellaliu/RigAnything — no licence declared in HF metadata; commercial status unknown. No published sprite-sheet-consistency metric exists, and generated-3D topology was not benchmarked locally.research/tavily/improve-stack-pro.json (status completed, 45 sources) was used as a
cross-check, not a primary source. Where it overlaps, it agrees on the shape of the answer: a text-format,
headless-CI-able engine plus an external tool bridge, and it independently singles out Defold's "OpenAPI-described
HTTP API, Bob CLI and Automation Bridge with an explicit privilege taxonomy" as unusually agent-friendly — the same finding
as 03-mcp-interop.md §Table B. Where it differs and was not followed: its executive
recommendation is Godot-first, then Defold, then Bevy, and it does not evaluate the browser renderer layer at all
(no PixiJS / Babylon / PlayCanvas headless comparison), so it cannot adjudicate the 2D or 3D browser question this page answers.
The repo's own notes also carry a provenance warning: the sibling Tavily-based QA survey
(research/tavily/06-qa-playtesting.log) is empty — it contains only
{"error": "This request exceeds your plan's set usage limit…"} — so §06 of this page stands on
05-qa-harness.md alone, with no independent second source.