diff --git a/AGENTS.md b/AGENTS.md index d2ddad09f0..959f2f806b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,7 +46,7 @@ One source, two runtimes: browser WebGPU and an owned C++ host for desktop/Andro 2. **Kill switch.** An abstraction costing more code than plain Three.js is deleted; `scripts/count-loc.ts` scores every repetition. 3. **Never own the look.** Geometry, material, colour, texture, curve and timing come from the game; it must be able to change the appearance completely without editing package code. `GPUParticles3D` is the shape. 4. **Borrow vocabulary**: Godot for nodes (the only node source), Three.js for rendering, Rapier for physics, Tailwind for UI, in camelCase. **A package exists only when it carries a dependency the others must not inherit.** -5. **Closed, outranking rule 1**: an IR, a scene format, an editor, a preset/genre system, a code-first ECS, a bespoke CLI vocabulary. **Web-only is unfinished**: a helper admitted for being unportable ships native proof (a conformance case or a `--target` playtest) in the same commit, and no result claims a platform it did not run on. Contract: `packages/runtime-native/AGENTS.md`. +5. **Closed, outranking rule 1**: an authored IR, a second renderer, a scene format, an editor, a preset/genre system, a code-first ECS, a bespoke CLI vocabulary. **Web-only is unfinished**: a helper admitted for being unportable ships native proof (a conformance case or a `--target` playtest) in the same commit, and no result claims a platform it did not run on. Contract: `packages/runtime-native/AGENTS.md`. **One exception, opt-in**: the owned C++ engine (renderer plus internal shader IR) behind the Three.js API; legacy stays the default until it proves itself (`docs/architecture/NATIVE-ENGINE-DECISION.md`). ## Commands diff --git a/CLAUDE.md b/CLAUDE.md index 7afe3b15ba..237b2733cc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,7 +48,7 @@ One source, two runtimes: browser WebGPU and an owned C++ host for desktop/Andro 2. **Kill switch.** An abstraction costing more code than plain Three.js is deleted; `scripts/count-loc.ts` scores every repetition. 3. **Never own the look.** Geometry, material, colour, texture, curve and timing come from the game; it must be able to change the appearance completely without editing package code. `GPUParticles3D` is the shape. 4. **Borrow vocabulary**: Godot for nodes (the only node source), Three.js for rendering, Rapier for physics, Tailwind for UI, in camelCase. **A package exists only when it carries a dependency the others must not inherit.** -5. **Closed, outranking rule 1**: an IR, a scene format, an editor, a preset/genre system, a code-first ECS, a bespoke CLI vocabulary. **Web-only is unfinished**: a helper admitted for being unportable ships native proof (a conformance case or a `--target` playtest) in the same commit, and no result claims a platform it did not run on. Contract: `packages/runtime-native/AGENTS.md`. +5. **Closed, outranking rule 1**: an authored IR, a second renderer, a scene format, an editor, a preset/genre system, a code-first ECS, a bespoke CLI vocabulary. **Web-only is unfinished**: a helper admitted for being unportable ships native proof (a conformance case or a `--target` playtest) in the same commit, and no result claims a platform it did not run on. Contract: `packages/runtime-native/AGENTS.md`. **One exception, opt-in**: the owned C++ engine (renderer plus internal shader IR) behind the Three.js API; legacy stays the default until it proves itself (`docs/architecture/NATIVE-ENGINE-DECISION.md`). ## Commands diff --git a/docs/architecture/CHARTER-HISTORY.md b/docs/architecture/CHARTER-HISTORY.md index ae93935977..8d11d34e59 100644 --- a/docs/architecture/CHARTER-HISTORY.md +++ b/docs/architecture/CHARTER-HISTORY.md @@ -14,3 +14,4 @@ Charter itself can state the current rule without carrying its own diff history. | 2026-09-02 | all | Condensed. Rationale that had been restated across sections is stated once; open work and known limitations move to [`CURRENT-CHALLENGES.md`](../CURRENT-CHALLENGES.md). No rule changed. | | 2026-09-05 | §1 | Added the out-of-the-box principle: ordinary games work without configuration pain, and configuration remains an escape hatch rather than a prerequisite. | | 2026-09-30 (PRDs 466–468) | §2, §9a, §11.5 | Owner-approved optional Strata terrain-authoring addon, editable terrain documents and terrain-only authoring companions; baked runtime data and game-owned appearance, with no general game editor or runtime recipe/scene format. | +| 2026-10-12 (N00) | §2, §6b, §7, §11.1 | Owner-accepted native engine: one owned C++ engine (renderer plus internal shader IR) behind the Three.js API, opt-in per game, with the legacy host as the default until it proves itself. The closed list now bans an *authored* IR; any other renderer stays closed. Record: [`NATIVE-ENGINE-DECISION.md`](NATIVE-ENGINE-DECISION.md). | diff --git a/docs/architecture/CHARTER.md b/docs/architecture/CHARTER.md index 3843b09f81..06766b4128 100644 --- a/docs/architecture/CHARTER.md +++ b/docs/architecture/CHARTER.md @@ -96,8 +96,8 @@ thesis: each spends the training-data advantage to buy a surface the model then | Not building | Why | |---|---| -| An IR, a compiler, a serialized scene format | Your game is TypeScript. The compiler was 25,898 LOC and bought nothing a model can't do with a `.ts` file. | -| A second *renderer* | 32% of v1's commits went to a runtime no benchmark ever measured. §7's host is the bounded exception: it runs Three.js, it does not replace it. The moment the host starts drawing, it has become v1. | +| An authored IR, a compiler of game code, a serialized scene format | Your game is TypeScript. The compiler was 25,898 LOC and bought nothing a model can't do with a `.ts` file. The opt-in native engine's shader IR is internal: no game or author writes it. | +| A second *renderer* | 32% of v1's commits went to a runtime no benchmark ever measured. §7's host runs Three.js, it does not replace it. **One exception (owner, 2026-10-12):** ThreeNative owns one C++ engine, renderer plus internal shader IR, behind the Three.js API. A game opts in; the legacy host stays the default until measured benchmarks prove the engine ([`NATIVE-ENGINE-DECISION.md`](NATIVE-ENGINE-DECISION.md)). Any renderer beyond that one engine has become v1. | | A JSON/structured-source ECS | Measured at **14x vanilla Three.js** on greenfield work, and scored lower on playability and visuals. | | An editor | Not in v1. The paid editor is a separate product in a separate repository. | | A bespoke CLI vocabulary | 178 command forms, a 2,477-word root help. Models are worst at discovering novel API surfaces. | @@ -395,7 +395,7 @@ and a bitmap font against the browser's whole CSS, SVG and font stack. the same Tailwind or plain CSS run with no WebView: the game's own JS realm reconciles React into an element tree held by an in-process, third-party CSS engine (Stylo, Taffy and Parley through Blitz), and the result is painted into a CPU surface that the compositor draws over the frame. This -is the one exception to "no second renderer": the engine owns a UI-only element tree and a UI-only +is also an exception to "no second renderer": the engine owns a UI-only element tree and a UI-only surface. It never renders the 3D game, exposes no scene or style IR to game code, does not replace Three.js, and imposes no look — the stylesheet is the game's own, from the same Vite and Tailwind build the web renderer uses. It claims only what its fixtures prove; unsupported CSS is not @@ -461,8 +461,11 @@ Mystral is absorbed as the owned `packages/runtime-native/` fork. Its C++ and pl in one workspace package; Dawn, V8, QuickJS, SDL3, wgpu-native and other dependency trees stay downloaded into an untracked `third_party/`. -**The runtime is a host, not a renderer.** It must not own Three's renderer, fork Three.js, or -replace the JavaScript `GLTFLoader`. Three.js compatibility must equal the workspace catalog. +**The default runtime is a host, not a renderer.** It must not own Three's renderer, fork Three.js, or +replace the JavaScript `GLTFLoader`. Three.js compatibility must equal the workspace catalog. The +opt-in native engine of [`NATIVE-ENGINE-DECISION.md`](NATIVE-ENGINE-DECISION.md) is the one +exception: it implements the Three.js API in C++, a game selects it, and it changes nothing for a +game that does not. Native physics is compiled into the runtime and exposed through a coarse, host-neutral typed-array ABI, with bulk `step`/`readVisibleTransforms` crossings rather than per-object frame calls. The @@ -690,7 +693,7 @@ fifteen lines. **Four things this does not loosen:** -1. **§2's closed list outranks both questions.** An IR, a scene format, an editor, a +1. **§2's closed list outranks both questions.** An authored IR, a scene format, an editor, a preset/genre/recipe system, a code-first ECS and a bespoke CLI vocabulary stay closed however cleanly they pass. 2. **§11.2 applies retroactively to everything admitted here.** Generous on the way in is only safe diff --git a/docs/architecture/NATIVE-ENGINE-DECISION.md b/docs/architecture/NATIVE-ENGINE-DECISION.md new file mode 100644 index 0000000000..06b3962097 --- /dev/null +++ b/docs/architecture/NATIVE-ENGINE-DECISION.md @@ -0,0 +1,176 @@ +# Native engine — architecture decision record (N00) + +**Status:** binding. The owner took the decision on 2026-10-04 (interview) and accepted the agent +calls below the same day. On 2026-10-12 the owner accepted the charter amendment: ThreeNative owns +one C++ engine (renderer plus internal shader IR) behind the Three.js API, **opt-in per game**, and +the legacy host stays the default until the engine proves itself. + +**What it changes today:** the rule text of [`CHARTER.md`](CHARTER.md) §2, §7 and §11.1 and of root +`AGENTS.md` rule 5 admits the engine. [`NATIVE-RUNTIME.md`](NATIVE-RUNTIME.md) and +[`packages/runtime-native/AGENTS.md`](../../packages/runtime-native/AGENTS.md) keep describing the +shipped host. They change in the commit that ships the first native-engine artifact, because primary +docs name only shipped things. + +**Work package:** N00 of the native-engine batch (source PRD-497). The engine reaches `develop` in +small slices. Each slice is inert or opt-in until the next one lands. + +--- + +## 1. What is decided + +ThreeNative adds an owned engine underneath the Three.js API, in C++. Every engine system of that +engine runs in native code on every target; game code keeps its JavaScript VM. A game selects the +engine; a game that does not select it runs on the legacy host exactly as before. The compatibility +denominator is measured from what games actually import, not asserted as a percentage. + +```mermaid +flowchart LR + api["Three.js API
what games write"] --> legacy["Legacy host + upstream three
default"] + api --> engine["One C++ engine
opt-in per game"] + engine --> v8["V8 game runtime
desktop and Android"] + engine --> wasm["Browser Wasm port
browser JS game runtime"] +``` + +## 2. The charter rules this amends, in plain words + +The charter outranks every `AGENTS.md`, so the reversal is quoted here rather than paraphrased. + +- **"A second *renderer*."** The charter's closed list of things that killed v1 names a second + renderer: the moment the host starts drawing instead of running Three.js, it has become v1. + **Amended:** ThreeNative owns one C++ engine behind the Three.js API, opt-in per game. The row's + objection — 32% of v1's commits went to a runtime no benchmark ever measured — is answered by + measurement instead of by a ban: the CP1 checkpoint (PRD-534) can stop the program, and the + engine becomes the default only on measured evidence. Any renderer beyond that one engine stays + closed. +- **"The runtime is a host, not a renderer."** The charter's platform section says the runtime must + not own Three's renderer, fork Three.js, or replace the JavaScript `GLTFLoader`. **Amended:** the + rule stays for the default host. The opt-in engine *is* the renderer, and native glTF replaces the + JavaScript loader inside it. +- **"An IR, a compiler, a serialized scene format."** The ban is kept exactly where it was aimed: no + authored IR, no scene format, and no compiler of game code as a **required authoring step**. The + engine's shader IR is internal and never authored. + +Everything else in the charter stands, including the kill switch, "never own the look", and the +rule that the supported API is the measured denominator. + +## 3. The decisions + +Owner decisions, João, 2026-10-04 (interview): + +1. **The engine runs in C++.** Every system of the native engine runs in C++: traversal, + transforms, animation, batching, materials and TSL, visibility, streaming and rendering. No JS + implementation of an engine system ships inside the native engine on any target. Gate E is + mandatory. +2. **Speed first; game code stays on a JS VM.** Game code runs on a JS VM (V8 on desktop and + Android, the browser's own engine on web) through generated bindings. The V8 game runtime + adapter (PRD-531, N18) is the shipping game runtime. Compiling game code to native is not part + of this decision. +3. **An early perf checkpoint can stop the program.** CP1 (PRD-534) measures the native engine + against current ThreeNative after N06 + N09. With no CPU win in the engine hot paths, N11–N15 do + not start until the owner re-plans. +4. **One engine everywhere.** The web runs the same C++ core in Wasm (PRD-532, N19, is mandatory). + The core is Wasm-safe from day one: single-thread fallback, retained views that survive memory + growth, no blocking waits. + +Calls made from the code by the agent, accepted by the owner ("go with what you think will be +best"), 2026-10-04: + +5. **Charter amendment, in plain words:** "a second renderer" and "the runtime is a host, not a + renderer" admit one owned C++ engine behind the Three.js API. The closed list keeps banning an + authored IR, a scene format and a compiler of game code *as a required authoring step*. The + shader IR is internal and never authored. +6. **The game API stays vanilla Three.js.** The charter's training-data thesis is the reason. The + supported surface is the measured denominator (§7), published in + `packages/create-threenative/capabilities.json`. An unsupported symbol fails at build, or at + scene preparation, with a named diagnostic. A game that selected the engine gets no silent + fallback to the legacy host. +7. **`ctx.renderer.raw` survives as the compatible `WebGPURenderer`.** Templates read only public + renderer fields through it (`toneMapping`, `toneMappingExposure`, `shadowMap`, and setup passed + to `setupLighting`), so it returns the engine's Three-compatible renderer object. + Renderer-private fields and the raw GPU device are unsupported and named as such. +8. **Bindings are generated from one catalog for several VMs:** V8 first (N18), browser JS over + Wasm next (N19), JSC when iOS returns. iOS is out of the first program but not designed out. +9. **Binding crossing cost:** the per-object API stays Three-compatible. Bulk typed-array paths, + shaped like the existing physics ABI, are added only where CP1 shows that crossings cost real + frame time. +10. **The legacy JS-owned engine stays the default until the native engine proves itself.** + Promotion to default (PRD-533, N20) needs the owner's call on measured evidence. The legacy + engine is deleted (PRD-535, N21) only one release after that promotion. + +Fixed by the proposal: C++20 for the engine, Dawn and wgpu-native retained, no upstream Three.js +bundle inside the native engine. + +11. **The web build: Wasm engine, JavaScript game code (owner decision, 2026-10-05).** On the web + the C++ engine compiles to WebAssembly with Emscripten and renders through the browser's WebGPU + (PRD-532, N19); the game's TypeScript stays ordinary JavaScript run by the browser's JIT. A page + starts from JavaScript, and Wasm reaches WebGPU, input and the DOM through JS glue, so the + engine replaces only the three.js engine code in JS. Rules: + - **The GPU-side design comes first:** the engine owns scene state in Wasm memory; static draws + are recorded once as render bundles; culled instances draw indirectly; uniforms share one + buffer with dynamic offsets; the renderer runs in a Web Worker on an `OffscreenCanvas`; + animation, culling and decode use Wasm threads over `SharedArrayBuffer` where the host serves + cross-origin isolation (COOP/COEP). + - **The game-to-engine API is bulk by default:** typed-array views into Wasm memory and dirty + flags, not per-property calls every frame. Per-object calls stay available for authoring and + tooling. + +## 4. The gate + +**E — native engine.** A native application creates a scene, runs engine systems and renders with +no dependency on V8, QuickJS, JavaScriptCore, Hermes, an engine JavaScript bundle or a WebView. A +C++ test driver can establish it. **Mandatory**, per decision 1. + +Gate E is proved from the build dependency graph, linker and symbol inspection, the packaged +resource inventory and runtime module inspection together — a filename scan for `.js` proves +nothing, since a binary can embed a VM. + +## 5. Definitions + +| Term | Means | +| --- | --- | +| **Native engine** | The C++ implementation of scene state, traversal, transforms, animation, materials and TSL, visibility, streaming and rendering, behind the Three.js API. One engine, every platform, including web in Wasm. | +| **Legacy host** | The shipped default: the game and upstream Three.js run on a JS VM, and the host maps WebGPU calls onto Dawn or wgpu. | +| **Game runtime** | Whatever executes the game's own code: V8 on desktop and Android, the browser's own engine on web, JSC when iOS returns. | +| **Native-engine artifact** | A game build that selected the engine: the engine runs in C++ and the game code runs on a VM. | +| **WebView-UI labelling** | A WebView UI carries its own browser runtime, so a native-engine artifact shipping one is labelled as such. | + +Gate E's "no JavaScript engine" is scoped to the engine: no JavaScript engine executes an engine +system. Build tools, binding generation, asset cooking and test drivers may use Node.js or +TypeScript. + +**Non-goals.** No new Vulkan/Metal/D3D12 backend, no universal ECMAScript compiler, no mandatory ECS +programming model, no replacement physics engine, no complete browser/DOM implementation, no editor +rewrite. No iOS qualification in the first program. No Unreal-level visuals from changing the +implementation language. + +## 6. Configuration dimensions and rejected combinations + +Three dimensions are configured separately: **engine implementation** × **game runtime** × **UI**. +Contradictory combinations are rejected at packaging time, not tolerated at runtime. + +| Engine | Game runtime | UI | Artifact | +| --- | --- | --- | --- | +| Legacy host | VM | WebView or native | the default | +| Native C++ | VM (V8, browser JS, JSC) | WebView or native | **native-engine artifact**, opt-in | +| Native C++ | VM | WebView | allowed, labelled per §5 | + +Two rules follow. **There is no automatic fallback** from a native-engine artifact to the legacy +host; the legacy host is a separately selected build, preserved until N21. And a target that claims +native-engine independence must be inspected and proved as one, not assumed from a build flag. + +## 7. The pinned reference + +**`three@0.185.1` plus the ThreeNative patch +[`packages/core/patches/three@0.185.1.patch`](../../packages/core/patches/three@0.185.1.patch) is +the behavioural oracle.** Every compatibility claim — object semantics, numerics, animation, +materials, shader packages — is measured against that exact pair, resolved through the workspace +`catalog:` in [`pnpm-workspace.yaml`](../../pnpm-workspace.yaml). + +**Upgrading it is a deliberate batch, never drift.** TSL's API changes between three releases with no +deprecation cycle, so a bump re-opens the measured denominator, the reference outputs and the +baseline it was measured against; it is its own change with its own evidence, and never rides along +with engine work. + +The compatibility denominator lands with the API catalog slice: every module reachable from +`packages/core/src` is classified, and every `three*` symbol that templates and examples import +becomes a row of the catalog.