Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions docs/architecture/CHARTER-HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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). |
15 changes: 9 additions & 6 deletions docs/architecture/CHARTER.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
176 changes: 176 additions & 0 deletions docs/architecture/NATIVE-ENGINE-DECISION.md
Original file line number Diff line number Diff line change
@@ -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<br/>what games write"] --> legacy["Legacy host + upstream three<br/>default"]
api --> engine["One C++ engine<br/>opt-in per game"]
engine --> v8["V8 game runtime<br/>desktop and Android"]
engine --> wasm["Browser Wasm port<br/>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.
Loading