A headless-native simulation kernel for JavaScript/TypeScript with a Godot-style scene tree, pluggable renderers (SVG / Canvas / headless), and a built-in verification harness — so a game can be authored, tested, and proven correct entirely in Node, without ever opening a browser.
npm install hayao
MIT-licensed, TypeScript-first. Everything is one package behind a single barrel — the whole public surface (440 exports) is greppable in one API digest.
state₀ ──step(inputs₀)──▶ state₁ ──step(inputs₁)──▶ state₂ ──▶ …
Rendering, audio, and the browser are observer plugins: they watch
that function and paint what it produces, but can never change its result. All
randomness flows through a seeded world.rng; the fixed-tick clock is
injected; iteration order is stable. Hold that invariant and the hard problems
collapse:
world.step() runs in Node. Assert on state.step() is a pure transition, so a solver proves it by search.world.hash().world.snapshot() / restore(), or replay the input log. Free.A game is a data definition: dimensions, an input map, and a build()
that constructs the initial scene tree. createWorld() turns it into a live
deterministic world — used identically by the browser driver, the headless runner,
and every test.
import { defineGame, createWorld, Node, Sprite, Text } from 'hayao';
const game = defineGame({
title: 'my-game',
seed: 7,
inputMap: { left: ['ArrowLeft'], right: ['ArrowRight'] },
build(world) {
const root = new Node({ name: 'root' });
// …add Sprite, Text, Timer, Camera2D, behaviors…
return root;
},
});
const world = createWorld(game); // no browser needed
world.step(['right']); // one fixed tick: pure, deterministic
world.probe(); // structured game state for assertions
world.hash(); // canonical state hash — replay-stable
In the browser the same definition runs under runBrowser() with an SVG
or Canvas renderer; in CI it runs under runHeadless(). The
Sokoban
example is the reference for every convention.
Every example game in the repo ships a verify suite: solver-proven puzzles, bot-beaten levels, golden replay hashes. The same tools are exported for your games:
import { solve, createWorld, checkDeterministic } from 'hayao';
// Machine-prove a level is winnable (search over the pure transition)
const r = solve(myPuzzle, { level: 0, maxDepth: 80 });
console.assert(r.solvable);
// Run the same input log twice, compare every step's hash
const report = checkDeterministic(() => createWorld(game), inputLog);
console.assert(report.ok && report.divergedAt === -1);
See docs/VERIFICATION.md for the full harness: probes, replay, snapshot stability, and filmstrip rendering for judging looks from a headless SVG.
| layer | modules | role |
|---|---|---|
core/ | Rng · Clock · EventBus · World · state hash | the kernel (headless, pure) |
scene/ | Node, Node2D, Sprite, Text, Camera2D, Timer, AnimationPlayer | the state |
input/ | action map · per-step sampling · record / replay | deterministic |
physics/ procgen/ logic/ net/ persist/ | rigid-body + kinematic AABB · seeded generators · FSM / graph search · lockstep + rollback netplay · save/load | deterministic |
verify/ | probes · replay · assertDeterministic · solver | the AI-first harness |
render/ | display list → SVG | Canvas2D | Headless renderer | observer |
ui/ audio/ app/ | DOM overlays · Web Audio bus · runBrowser / runHeadless | observer shell |
core + scene + input + physics + procgen + logic + net + persist run in Node and are deterministic;
render + audio + ui + app are the browser-only observer shell. The engine
borrows Godot's authoring model — nodes, signals, scenes, input actions, tweens — but
keeps every part text, typed, greppable, and headlessly verifiable.