Overview
Simple 2D (slug simple-2d) is the 2D engine of the Simple family. It provides
everything a 2D browser game needs around its own code and leaves the game
itself to the model: the build writes its own simulation and its own drawing.
The runtime is the npm package @clockwyrks/simple-2d, written entirely in
TypeScript and imported by a build as an ordinary dependency. Its source is
packages/simple-2d/, and the documentation seeded into a run workspace is that
package’s own docs/ directory.
What each side owns
Section titled “What each side owns”The engine owns the frame loop, the clock that decides what each frame’s delta time is, the fit from the logical design size to the canvas, the input action registry and its bindings, the pointer mapped into the game’s logical coordinates, the audio bus, the asset loader, the debug overlay together with the frame metrics it reports, the draw-command recorder over the context the game draws through, and the debug surface the game returned beside its state, held for a caller to read back.
The game owns its simulation and its drawing, supplied as a
Game<S, D>: an initialize that builds the
state and the debug surface and returns them as [state, debug], an update
that takes the current state and a delta in seconds and returns the next state,
and a render that draws that state through a 2D context. The engine holds the
state by value and hands every reader a read-only view, so rendering cannot
change the state and nothing but a transition advances it. The declarations the
engine works from are made during initialization: action bindings, cue
definitions, the assets the state holds, the diagnostic sources the overlay
reads, and the debug surface a caller drives the build through.
Each function receives only the part of the engine it may use, so a frame’s audible and observable behavior belongs to the update and the picture belongs to the render. Collision detection, physics, and gameplay structure belong to the game, so a case that measures those measures them directly.
The sections
Section titled “The sections”| Section | Covers |
|---|---|
| APIs | The types and functions the engine exposes, as the specification its implementation satisfies. |
| Concepts | How each subsystem works and why it is shaped that way. |
| Usage | How a build is expected to write its code against the engine. |
| Examples | Complete games written against the engine, read as working reference. |
| Validators | How a test case checks a build through the engine. |
Where it fits
Section titled “Where it fits”Simple 2D suits a case whose difficulty is the simulation and the presentation. Carom-class games write their own collision response and draw their own playfield, and the engine removes the surrounding work a case never intended to measure.
A case’s validators are vitest suites that run in the same process as the build
they check. A suite imports the engine and the build’s own game module, creates
an engine over a scripted clock and a canvas it owns, and steps the game with
engine.advance. The clock, the input, the cues that played, the assets that
resolved, and the diagnostics are therefore read off engine code, and the frames
a check depends on are exactly the frames it asked for.
A case still declares the scenario its checks arrange, since setting up a
situation runs through the game’s own state: a validator poses it through
engine.apply and the build’s debug surface, and reads it back off
engine.state.
The engine also captures what a build drew. A validator arms the recorder around the stretch of a scenario its check is about and hands back the operations the build issued, which a reviewer replays beside the same scenario driven against the case’s reference implementation.