Appearance
Getting started
Requirements
- Bun (developed on 1.4). Bun runs the CLI, the tests and the scripts.
- A browser with WebGPU or WebGL2 to play.
kiln shotneeds a Chromium: Playwright's, or any Chrome binary named byKILN_CHROMIUM.
Kiln isn't published to npm yet. From a checkout of the Kiln repository:
bash
bun install
bun testCreate a game
kiln new copies the starter template into a new folder and writes an AGENTS.md:
bash
bun packages/kiln/src/cli/kiln.ts new ../my-game
cd ../my-game
bun installOutside the Kiln repo the game depends on your checkout through a file: path. Created inside examples/, it joins the workspace instead.
Inside the game the kiln command comes from the kiln package, so run it with bunx kiln ... or through the package scripts (bun run test, bun run describe, bun run docs). The rest of these docs write it as plain kiln.
bash
bunx kiln docs # add this game's schedule and exports to AGENTS.md
bunx kiln test # the template's scenario test
bunx vite # play it at http://localhost:5173The starter is a blue box on a floor that moves with WASD, the arrow keys or a gamepad stick.
What you got
my-game/
kiln.config.ts fixedHz, seed, mode, hotReload
index.html a canvas and boot(main)
vite.config.ts the kiln() Vite plugin
bunfig.toml preloads kiln/preload for bun test
src/main.ts default-exports () => createApp(config)...: the whole schedule
src/data.ts traits, resources and events
src/prefabs.ts spawn recipes
src/systems.ts systems, grouped by phase
scenarios/ headless tests
public/ assets, loaded by path
AGENTS.md the Kiln guide, plus a generated section for this gameThe schedule in src/main.ts is short:
ts
import { chain, createApp, CorePlugins, Fixed, Startup } from 'kiln'
import config from '../kiln.config.ts'
import { Movement, PlayerMove, SpawnLevel } from './systems.ts'
/** The game: the whole schedule, top to bottom. */
export default () =>
createApp(config)
.use(CorePlugins)
.add(Startup, SpawnLevel)
.add(Fixed, chain(PlayerMove, Movement))The entry module default-exports a function that builds the app and returns it. The browser calls it through boot(main) and starts the loop; the CLI and scenario tests call it and step the app headless. Never call .start() in main.ts.
See the schedule
bash
kiln describeapp mode=3d fixedHz=60 seed=1
plugins CorePlugin, RenderPlugin, InputPlugin, AssetsPlugin, TimerPlugin, TweenPlugin, UiPlugin, DebugPlugin
phase Startup
1. SpawnLevel
phase Input
1. PollInput | res-writes Input | res-reads ActionMap
2. UiEvents | res-reads Input,Ui
phase Fixed
1. TickTimers | writes Lifetime,Timer | res-reads Delayed
2. TickTweens | res-reads Tweens
3. PlayerMove | writes Velocity | reads IsPlayer | res-reads Input
4. Movement | writes Transform | reads Velocity | after PlayerMove
phase Update
1. SpawnModels | reads MeshRef,Model
phase Render
1. RenderSync | reads MeshRef,Transform
2. InstanceSync | reads InstanceColor,Instanced,Transform | after RenderSync
3. Draw | after RenderSync,InstanceSync
4. RenderUi
traits
...Every system is listed in run order with what it writes and reads, including the ones plugins add. Resource access (res-reads, res-writes) is what one headless step actually touched. This is the first thing to run after a change: schedule errors show here before anything else.
Run the test
scenarios/player.test.ts holds the starter's one test:
ts
import { expect, scenario } from 'kiln/test'
import { Transform } from 'kiln'
import { IsPlayer } from '../src/data.ts'
scenario('the player walks right while right is held', ({ app }) => {
const p = app.query(IsPlayer)[0]!
app.input.script([{ frame: 1, hold: 'right', frames: 60 }])
app.step(60)
expect(app.get(p, Transform)!.x).toBeCloseTo(5, 3)
})scenario builds a fresh app from src/main.ts, runs Startup, and disposes it afterwards. step(60) runs exactly 60 fixed steps with no renderer, so the result is exact: speed 5 for one second is 5 units.
Next
- A complete feature: add health and damage end to end.
- Working with agents: the loop and
AGENTS.md. - Entities and traits: the data model.