Appearance
Introduction
Kiln is a thin game runtime for TypeScript: a fixed-step loop, an ECS built on Koota, and a bridge that draws the ECS with Three.js. Its users are coding agents first and people second. Every design choice is aimed at one loop: an agent changes a few files, asks Kiln whether the change is right, and gets a precise answer without opening a browser.
What "agent-first" means here
A feature is a handful of exports. Data is a trait, behaviour is a system, and the whole schedule lives in one file, src/main.ts. Adding a feature means adding exports and one line in the schedule. There is no class hierarchy to extend and no registration scattered across files.
Names come from exports. export const Movement = system(...) is called Movement in errors, in kiln describe, in snapshots and in the generated docs. Moving code between files changes nothing. See Project layout.
Access is declared and checked. A system says which traits it writes with Mut(T). Everything else in its query is read-only, so writing it is a compile error, and in dev builds a runtime error that names the fix. The scheduler uses the same declarations to reject two systems that write one trait with no order between them. See Systems and The schedule.
Errors name the fix. Every error Kiln throws is a KilnError with a stable code and a message that reads "what happened. What to do." For example:
error write-conflict: PlayerMove and Knockback both take Mut(Velocity) in Fixed with no order. Put them in chain() or add .after().Everything runs headless. Core never imports Three.js. Tests step an exact number of fixed frames under bun test with scripted input, and the same seed plus the same input gives the same world. An agent "sees" the game as JSON (kiln run --snapshot), and only takes a screenshot (kiln shot) when the change is visual.
The CLI is built for tool calls. Short, parseable output, --json on every command, and errors printed as error <code>: <message>. See CLI.
The pieces
| Layer | Where | Imports Three.js? |
|---|---|---|
| Core: app, loop, ECS wrapper, schedule, events, states, snapshots, recordings | packages/kiln/src/core | No (enforced by a test) |
| Plugins: render, input, assets, timers, tweens, UI, camera, save, animation, physics, debug | packages/kiln/src/plugins | Some |
| Tooling: CLI, Vite plugin, Bun preload, scenario runner | packages/kiln/src/{cli,tooling,test} | No |
Community plugins, copied into a game by kiln add | packages/kiln/community | Yes |
The runtime is Bun for the CLI, tests and scripts, and Vite for the browser. Three.js is pinned to 0.186.1 and rendering uses WebGPURenderer with a WebGL2 fallback.
How it compares
If you know Bevy, Kiln will feel familiar: plugins, resources, events, phases, run_if-style conditions, states and commands all work much the same way, in TypeScript. Kiln is smaller and stricter. It has no parallel executor, and it spends that simplicity on startup validation, headless determinism and tooling an agent can call.
Next
- Getting started: create a game and run it.
- Working with agents: the loop an agent follows and the files it reads.
- Design notes: how the implementation maps to the design doc, and why.