Code tour
Français · Type: tutorial · Audience: contributor · Context: engine repository · Status: ready · Applies from: 4.1.11
Half an hour with the code open: eight stops, from a tiny game to the test that proves each part. Each stop names the files to read and the test that shows them at work. Architecture is the same map seen from above.
What you will build
Not a feature: a picture of the engine in your head, made by reading real code and running the test of each stop. You need a prepared repository (Developer setup) and an editor. Every test of a stop runs alone:
# in the engine repository
npx vitest run <file>
The tour follows the code of 4.4.0; its stops date from 4.1.0 (and 4.1.1 for the signal).
1. Declare a small game
Read tests/fixtures/classics.ts: five tiny games after the classics (insults,
grog, dott, mansion, stan), each a GameDef of a few rooms and rules. A rule is { verb, a, b, if, do };
do is a list of commands (Cmd, in src/engine/core/types/content.ts).
The types of a whole game are in src/engine/core/types/game.ts. The demo,
written the same way but larger, is games/demo/.
# in the engine repository
npx vitest run tests/classics.test.ts
You see each classic mechanic played to its end by the real engine.
2. Compile and freeze it
compileGame in src/engine/core/define.ts clones the content, fills the
defaults, freezes it and gives each rule, choice, topic, listener and script step a stable id
(src/engine/core/content-ids.ts). Saves and sessions speak in those ids, so
renaming a line does not break a save.
# in the engine repository
npx vitest run tests/compile.test.ts tests/stable-ids.test.ts
3. Start a game
new Engine(game, layouts, presenter, store) then newGame()
(src/engine/core/engine.ts). In the tests the presenter is FakePresenter and the
store MemoryStore (src/engine/core/ports.ts); in the browser App
(src/engine/dom/app.ts) composes the engine with the DomPresenter, and bootGame
(src/engine/boot.ts) wires everything.
# in the engine repository
npx vitest run tests/core.test.ts -t "fixture game"
4. Run an action
Engine.act walks there, opens a session entry and calls resolve
(src/engine/core/interactions.ts): the rule findRule picks, else a kind,
else a fallback. Its commands run in exec and step
(src/engine/core/command-runtime.ts), each through its handler in
src/engine/core/command-handlers.ts; events and scripts in
src/engine/core/event-runtime.ts and
src/engine/core/script-runtime.ts.
# in the engine repository
npx vitest run tests/core-runtime.test.ts
Each runtime module is called on its own there, with the engine as its first argument.
5. Save, load and replay
saveEnvelope and parseSave (src/engine/core/save.ts) write and check a save;
migrate (src/engine/core/migrate.ts) carries an old one forward;
tests/save-v3.test.ts loads one save per release since 3.0 and plays it to the ending. The session
(src/engine/core/session-runtime.ts) records every input with a digest of
the state after it; replay (src/engine/tools/replay.ts) plays it again and
names the first entry that differs.
# in the engine repository
npx vitest run tests/replay.test.ts -t "a recorded session"
6. The same action in the solver
solve (src/engine/tools/solve/search.ts) takes a state from the
frontier; makeExpander (src/engine/tools/solve/expansion.ts) runs every
action worth trying on a real Engine, records each transition (TryRecord,
src/engine/tools/solve/model.ts) and what it read and wrote; the search merges
them and gives its verdict. What a state is made of for the search is in
src/engine/tools/solve/abstractions.ts.
# in the engine repository
npx vitest run tests/solver-contract.test.ts
npm run quality:baseline -- --check
tests/memo.test.ts is a heavy test (minutes): it runs in the nightly and the candidate, or alone with
npx vitest run tests/memo.test.ts. The behaviour of every game is frozen by tests/quality-baseline.json, which
quality:baseline -- --check compares (The quality baseline).
7. A signal from the world outside (4.1.1)
tests/fixtures/signals.ts declares three signals;
src/engine/reality/protocol.ts checks a signed one before it is read;
src/engine/reality/client.ts verifies it, hands it to Engine.receive
(src/engine/core/reality-runtime.ts: applied at most once, a session
entry), waits for the durable save, then acknowledges. The solver offers signals under a policy
(src/engine/tools/solve/scenarios.ts). The reference Bridge is
bridge/src/.
# in the engine repository
npx vitest run tests/reality-engine.test.ts tests/reality-proof.test.ts tests/bridge.test.ts
tests/reality-engine.test.ts crashes the engine at each boundary. In the browser, scripts/e2e-reality.mjs plays the
same path (npm run e2e:reality).
8. Find the test of a thing
- A command: grep its key in
tests/cmds.test.tsandtests/core.test.ts. - A layer rule:
tests/boundaries.test.ts. - The public names:
tests/api-surface.json. - A browser behaviour: the scripts
scripts/e2e*.mjs(npm run e2e,e2e:taps,e2e:pwa,e2e:a11y…), which CI runs in Chromium and WebKit (and Firefox for the PWA).
Which suite runs where, and when, is Which tests run when.
Limit ·
tour-scopeThe tour stays inside the engine. The Studio, the Studio Server and the connectors have their own entry points (src/studio/,studio-server/src/,connectors/src/), not walked here.
What you learned
- A game is data, compiled and frozen with stable ids: Architecture.
- One engine runs in the player, the tests and the solver, behind the
Presenterport. - Each part has its test, and the baseline freezes the whole: The quality baseline.
Where to go next
- The dev loop: change a command end to end.
- Import boundaries: what each folder may import.