The blessed renderer-free entry: every mwg module that loads in bare Node, in one
import, with no DOM or WebGL globals.
A game rule that never touches a renderer should never have to discover, by accident,
which subpaths load without one. Importing this entry is the declaration: if it loads,
the game logic is headless; if a rule needs anything outside it, that dependency is
renderer-bound and belongs in presentation, not in the rule.
Membership is the set REFERENCE.md documents as renderer-free, minus three-d
(renderer-free of Pixi but needing Babylon at import, an optional peer a plain
npm test run has no reason to have installed), plus the two renderer-free asset
halves (assetPaths, assetBinary - path resolution and byte caching with no
renderer, beside the Pixi-backed assets loader). Deliberately excluded: the Lua
runtimes (mwl/pyroeis, ai/lua), which carry their own load contracts, and every
two-d/* module (Pixi by documentation, enforced by test).
threads rides along (simulation's async rollouts and ai training spawn through
it, and it loads with no renderer - its worker host is a call-time concern, not an
import-time one, handled inside threads.spawn itself).
tests/headless-smoke.test.ts is the proof: it imports this entry in plain Node
with no DOM/WebGL globals, walks the reachable import graph for a renderer leak,
and runs a seeded scenario through it twice for the same result.
// deterministic rules with no renderer: a seeded stream plus one headless scenario constrandom = newcore.Generator(7); constresult = simulation.runScenario({ state: { value:0 }, commands: [1, 2, 3], random, step: (state, command, rng) => ({ state: { value:state.value + command + rng.int(2) }, events: [], status:'ready'asconst, }), });
// every other member rides the same entry: content, rules, saves, and the two // renderer-free asset halves (path resolution and byte caching with no loader) consttables = mwl.MWL_SCHEMA_10; constpresent = assetPaths.has('tiles/grass.png'); constcached = assetBinary.isBinaryLoaded('sfx/hit.mp3'); constcheck = { actors, ai, audio, battle, board, i18n, roguelike, rpg, testing, world }; voidresult; voidtables; voidpresent; voidcached; voidcheck;
The blessed renderer-free entry: every
mwgmodule that loads in bare Node, in one import, with no DOM or WebGL globals.A game rule that never touches a renderer should never have to discover, by accident, which subpaths load without one. Importing this entry is the declaration: if it loads, the game logic is headless; if a rule needs anything outside it, that dependency is renderer-bound and belongs in presentation, not in the rule.
Membership is the set
REFERENCE.mddocuments as renderer-free, minusthree-d(renderer-free of Pixi but needing Babylon at import, an optional peer a plainnpm testrun has no reason to have installed), plus the two renderer-free asset halves (assetPaths,assetBinary- path resolution and byte caching with no renderer, beside the Pixi-backedassetsloader). Deliberately excluded: the Lua runtimes (mwl/pyroeis,ai/lua), which carry their own load contracts, and everytwo-d/*module (Pixi by documentation, enforced by test).threadsrides along (simulation's async rollouts andaitraining spawn through it, and it loads with no renderer - its worker host is a call-time concern, not an import-time one, handled insidethreads.spawnitself).tests/headless-smoke.test.tsis the proof: it imports this entry in plain Node with no DOM/WebGL globals, walks the reachable import graph for a renderer leak, and runs a seeded scenario through it twice for the same result.Example