@f0rbit/forge
v0.5.0
Published
Functional, composition-first TypeScript game engine with PIXI v8 renderer adapter, deterministic ECS, action-stream replays, and corpus-backed persistence.
Readme
@f0rbit/forge
A small, functional, composition-first TypeScript game engine published as a single npm package with subpath exports. PIXI v8 is consumed strictly as a renderer + asset loader at the edge (@f0rbit/forge/pixi, peer-dep on pixi.js); everything else (ECS, scheduling, input, time, RNG, replay, persistence, debug, palette) is renderer-agnostic and runs headless under bun test. Determinism is a hard guarantee, not an aspiration.
Documentation: https://f0rbit.github.io/forge/ LLM-readable reference: https://f0rbit.github.io/forge/llms.txt
For the design doc see PLAN.md; for release notes see CHANGELOG.md.
Install
bun add @f0rbit/forge
bun add pixi.js # only if you use the @f0rbit/forge/pixi subpathSubpaths
import { /* engine core */ } from "@f0rbit/forge";
import { presets } from "@f0rbit/forge/presets";
import { /* debug types */ } from "@f0rbit/forge/debug";
import { engine_store } from "@f0rbit/forge/storage";
import { boot, sprite_c } from "@f0rbit/forge/pixi";Quick start
import { component, pos_c } from "@f0rbit/forge";
import { boot, sprite_c } from "@f0rbit/forge/pixi";
import { presets } from "@f0rbit/forge/presets";
const player_c = component<true>("player");
const r = await boot({
mount: "#root",
window: { width: globalThis.innerWidth, height: globalThis.innerHeight },
camera: { design: { width: 320, height: 180 }, mode: "letterbox" },
bindings: presets.movement2d,
});
if (!r.ok) throw new Error(`boot failed: ${r.error.kind}`);
const app = r.value;
app.world.spawn(
[pos_c, { x: 160, y: 90 }],
[player_c, true],
[sprite_c, { texture: "__default__", frame: "__default_0__", anchor: { x: 0.5, y: 0.5 } }],
);
app.schedule.add("update", (w, ctx) => {
const [dx, dy] = ctx.input.vector("move.x", "move.y");
for (const [, p] of w.query([pos_c, player_c] as const)) {
p.x += dx * 60 * ctx.time.fixed_dt;
p.y += dy * 60 * ctx.time.fixed_dt;
}
}, "player.move");
app.start();WASD/arrows/left stick move the player; F1 toggles debug; backtick (`) opens the command palette.
For a full end-to-end consumer (replay tests, level setup, persistence), see the coin-collector repo.
Build & test
bun install
bun run build
bun test
bun run check:determinismDesign philosophy
- Records of functions over private state — no classes, no inheritance.
- Single-word, lowercase API:
world.spawn,time.advance,input.pressed("jump"). - Deterministic by construction. Browser non-determinism is quarantined to
src/pixi/. Result<T, E>everywhere (@f0rbit/corpus). Never throw, never try/catch outside foreign-call boundaries.- Test in-memory first; PIXI is a thin adapter, not a dependency of the kernel.
@f0rbit/forge is v0.x — APIs may break between minor versions until v1.0.
