House rules
These are invariants the code relies on, with the file that holds each one.
- One renderer change at a time.
src/gpu/webgpu-renderer.ts(about 61k lines) is a shared hotspot. Work that touches it runs one package at a time; pure-module work may run in parallel. - The density function exists three times (WGSL
src/gpu/density-mc-shader.ts, JSsrc/chunking/density.ts, Rustdensity_field.rsin the physics fork, shipped prebuilt invendor/rapier3d/). Change all three together, layer order included. The lattice, hash and gate decisions are bit-exact; transcendentals may differ by a few f32 ULPs between JS and Rust by design.npm run test:densityparitypins the Rust output against a committed golden and bounds the JS/Rust delta. - Determinism. Seeded randomness goes through
hashSeed/makeRng(src/structures/lcg-rng.ts), with ONE ’:‘-delimited string key perhashSeedcall:hashSeedconcatenates its parts with no separator, so('ab','c')and('a','bc')collide. NoMath.randomin game logic. An NPC key such asnpc:{bodyId}:{townId}:{localIdx}names a slot, not a person: the roster is re-derived from the seed on every load, so data keyed on a slot must be dropped when its resident dies (forgetDeadinsrc/game/dialogue/llm/llm-memory.tsdoes this for conversation memory). The world is regenerated from the seed; the save stores what the player changed. - Saves.
SAVE_VERSIONis 4 (src/game/save/game-save.ts); purely additive fields may arrive under the current version when they migrate by being absent, as that file documents. Anything loaded from a save that reaches the UI or a prompt is validated, never shape-cast. Every quest mutation sits behind_questsMutable(). - Animation and combat.
CharacterState(src/character/character-state-machine.ts) is append-only at the tail, so existing numeric state values stay stable. Attack properties are index-aligned tables insrc/game/combat/attack-timing.ts, pinned byattackTablesAligned()intest:combat; never branch on index thresholds.GROUNDED_LOCO(src/animation/character-animator.ts) holds grounded locomotion only; air states never go in it. Every shipped character clip needs a recorded source:scripts/check-animation-provenance.mjsruns insidenpm run buildand fails on Mixamo data or an unrecorded clip. - Creature brains. In the creature steer brain (
src/gpu/webgpu-renderer.ts), new steer branches sit below thefleeUntilbranch and below_groundEscapeStuck, write behaviour, target and gait on the shared steer object, and never scan every active creature per creature. - Input and physics. While
uiHoldis set (src/character/character-input.ts), input is still polled so one-shot presses drain instead of firing after the UI closes, but the result is neutral. Rapier colliders are player-scoped: a far ray that returns nothing means “no information”, not “clear”, so distant queries use the density fallback. Spell lights never cast shadows (src/game/combat/spells.ts). - Spoken text. Anything Kokoro speaks has no digits and no em dashes; fixed menu labels go
into
PLAYER_STOCK_LINES(src/game/dialogue/deterministic-brain.ts) so they are synthesised ahead of time.src/game/dialogue/dialog-styles.tsandsrc/ui/window-sections.tshold large template literals: no backticks inside them, comments included. After a scripted regex edit, scan for stray control bytes (below 0x20). - Gates.
npx tsc --noEmit,npm run build, thetest:*suites covering what you touched, and every suite a work package lists.
Written by release:site-sync from docs/rpg-next-steps.md at v2026.09.22. Edit the engine document, not this page.