clone

a browser grid puzzle game where you push words around to rewrite the rules of each level

main
miau-is-true / docs / design-backbone.md
design-backbone.md 15.0K source

Dynamic-rule Sokoban engine: design backbone

This document is a vocabulary and execution contract for a grid puzzle engine that combines Sokoban-style pushing with Baba Is You-style rules that are physical, editable objects. It separates observations about the reference games from recommendations for this engine so that a mod author can tell which behavior is compatibility-oriented and which behavior is ours to choose.

Design thesis

The core puzzle loop is: inspect a board, form or break a rule, observe the new affordances, then make a deliberately ordered sequence of movement and pushes. The board contains both the objects affected by rules and the words that define the rules. A rule is therefore state, level geometry, and puzzle clue at the same time.

This is a direct synthesis of two proven constraints:

  • Sokoban's single verb is constrained pushing. The player can walk and push but cannot pull, so a push can permanently remove future options. The creator Hiroyuki Imabayashi describes the 1981 prototype as a puzzle in which a bad box placement can make further progress impossible (creator interview).
  • Baba Is You makes the rules themselves pushable blocks. Teikari's GDC slides describe the basic rule as object + verb + quality, with rules being re-detected after words move (GDC talk PDF, pp. 8–14).

Recommendation: make every turn replayable and undoable from the beginning. This preserves Sokoban's meaningful irreversible pushes while allowing players to explore a dynamic rule system without paying a restart tax.

Vocabulary inventory

The engine should distinguish surface words (what a module places and the player reads) from semantic capabilities (what the simulation executes). This avoids making the parser and renderer depend on JavaScript class names.

Tier 0: board invariants

These are the smallest shared concepts and should be stable in the public mod API.

Term Meaning Required invariant
cell One integer grid coordinate A cell can hold zero or more entities and terrain layers.
entity A runtime instance with identity and position Identity survives movement; two entities may share a cell.
kind Whether an entity is a physical object or pushable word Rule matching uses an object's noun or a word's text, never an instance ID.
noun Semantic type of a physical object, such as MIAU, WALL, or ROCK Rules target nouns, so every object of the same noun gains the same derived properties.
layer Terrain, actor, item, text, or effect ordering Rendering order must not silently change collision order.
direction Facing or movement vector Cardinal movement is the baseline; diagonal support is an explicit extension.
occupancy Entities currently in a cell Never encode occupancy as a single sprite slot: stacked words and objects are valid.
property A capability or tag attached by active rules Properties are recomputed from the current rule set, not permanently mutated.
goal A predicate over state that ends or advances a level A goal may be an object property (WIN) or a mod-defined predicate.
turn One accepted player intent plus all resulting resolution Undo restores the complete pre-turn snapshot, including rules and effects.

The stacked-word invariant is evidence-based: Teikari says the shipped system had to be rewritten to account for words sharing a cell and to test possible permutations (Game Developer interview, lines 68–71).

Tier 1: nouns, operators, and properties

The initial built-in lexicon should be small enough to teach in a few levels. Words are data records, not reserved identifiers; a mod can add synonyms or a new word that maps to an existing semantic capability.

Nouns (things that can be targeted):

YOU's subject kinds such as BABA and FENN; board kinds such as WALL, ROCK, FLAG, WATER, LAVA, GRASS; and meta kinds TEXT, LEVEL, ALL, EMPTY, WORD. ALL and EMPTY are selectors rather than ordinary spawnable kinds and need explicit scope rules.

Core relation verbs:

IS assigns properties or transforms one kind into another; HAS emits or creates a kind when the subject is destroyed; MAKE is an optional explicit spawn relation. Start with IS and HAS; keep MAKE out until a concrete level needs its semantics.

Core properties / capabilities:

YOU makes every matching entity player-controlled; PUSH allows an entity to be pushed; PULL makes it drag an adjacent entity when it moves; STOP blocks movement; OPEN and SHUT form a paired interaction; SINK consumes both participants; WIN satisfies the level goal on contact; DEFEAT resets or removes the controlled entity; HOT and MELT form a paired interaction; FLOAT chooses a collision layer; MOVE supplies autonomous movement; SHIFT transfers entities on contact.

Connectives and modifiers:

AND bundles adjacent nouns or properties; NOT negates a term; ON, NEAR, FACING, and LONELY constrain a rule by spatial context. A parser must retain the exact token sequence and source coordinates so diagnostics and editor highlighting can explain why a rule is active.

These categories follow Teikari's documented progression: three-part rules, then AND and ON, then parameterized conditionals such as NEAR and FACING, then LONELY, NOT, letters, and stacked rules (GDC talk PDF, pp. 15–21).

Tier 2: derived systems

These are useful after the core loop is stable:

  • Rule text: words have their own kind and can be targeted by TEXT, transformed, destroyed, or made controllable.
  • Plural and wildcard selectors: ALL IS YOU expands to all eligible kinds; expansion must be a deterministic snapshot for the turn.
  • Conditional predicates: a rule can query the subject's neighbors, supporting cell, facing vector, or count. Predicates should be pure queries.
  • Creation and destruction: HAS, SINK, DEFEAT, MELT, and OPEN / SHUT are effect relationships, not movement properties.
  • Meta-board entities: the level map, level-select map, or UI may become simulation entities, but only in an opt-in extension tier.

Teikari's implementation uses multiple passes: find candidate straight-line sentences, filter word-type order, validate syntax, convert to rule records, then apply expansion and disabling. It accepts only left-to-right and top-to-bottom straight lines and discards lines shorter than three words (GDC talk PDF, pp. 10–14). That is a good default grammar because it is predictable to read and easy to debug.

Turn pipeline and precedence

The following order is the recommended engine contract. It makes each phase observable for debugging and deterministic for mods.

  1. Accept intent. Read one cardinal input (up, down, left, right), wait, undo, or reset. Reject input while a level transition is being committed.
  2. Snapshot. Save the entire state, including entity identities, positions, directions, text stacks, active rules, RNG seed, and pending effects.
  3. Resolve movement intent. Determine controlled entities from the active YOU rules. For each requested move, compute the push chain in a stable order. A blocked chain fails atomically; a legal chain moves as one action.
  4. Apply movement and pushes. Move entities from back to front. A push cannot pull an entity unless PULL independently creates that behavior. Resolve simultaneous controlled entities with a documented ordering key (for example entity ID), then expose collision groups to mods.
  5. Re-scan rules if relevant state changed. If any text moved, was created, destroyed, transformed, or stacked, parse the board again. The reference implementation tracks whether words changed and only rewrites rules when needed (Game Developer interview, lines 46–52).
  6. Compile semantic rules. Validate syntax, expand AND / ALL, apply negation and disablement, and replace the active rule table atomically. Contradictions must be deterministic: a negative rule such as BABA IS NOT YOU suppresses the positive BABA IS YOU rule, as in the reference game.
  7. Resolve interactions. Run paired effects (OPEN/SHUT, SINK, DEFEAT, MELT) using a fixed priority list. Recommended initial order: structural destruction and transformations, then defeat, then win. A mod can register a priority but cannot create a cycle.
  8. Run autonomous behavior. Apply MOVE, SHIFT, and similar behaviors as a new deterministic substep, then re-run rule and interaction phases if they changed text or occupancy. Cap cascading substeps and report a cycle.
  9. Evaluate goals. Evaluate WIN and mod goals against the settled state. A surviving object with both YOU and WIN satisfies the goal by itself; a YOU object and a distinct WIN object satisfy it when they share a cell. Evaluate this after SINK and DEFEAT removals. If both win and defeat occur in a turn, use the explicit goal policy (the recommended default is defeat before win unless the level opts into “reaching win ends immediately”). This self-win behavior is supported by Hempuli's patch note for EMPTY IS YOU AND WIN AND DEFEAT (official patch notes).
  10. Commit presentation. Emit a replay event list, sound/effect cues, and text highlighting. Rendering and particles must not mutate simulation state.

This pipeline is an engine recommendation inferred from the source's multi-pass rule detection and from Sokoban's need to reason about pushes as irreversible state changes. It is not a claim that the commercial games use this exact sequence.

Puzzle pedagogy

Use a vocabulary curriculum, not a feature dump. Teach one new noun or property in a low-pressure board; immediately give a second level where the same word combines with an older word; then make the player manipulate the word tile itself. Keep the number of initially active rules low. Teikari says levels were cut when the intended interaction could be bypassed, required too many supporting rules, or became overwhelming with many active rules (Game Developer interview, lines 59–64).

Preserve a visible escape route from difficulty spikes. Teikari grouped areas by introduced words and used “Extra” markers to signal unusually difficult levels, while acknowledging that sequential numbering worked against returning later (same interview, lines 82–87). For this engine, a mod should be able to mark levels as required, optional, or challenge, and the campaign should unlock around a hard puzzle.

Sokoban pedagogy depends on the player learning which pushes are unsafe. A crate in a non-goal corner is a simple deadlock; more complex deadlocks arise from wall corridors, frozen crate groups, and goal ordering. Deadlock research exists because these states make Sokoban difficult even for non-optimal solving (Cazenave, “Towards deadlock free Sokoban”). Make deadlocks legible through undo, restart, and optional post-failure hints; do not silently auto-solve or remove every consequence.

For a design tool, validate levels with at least: reachability of controlled entities, a goal witness or solver result, detection of static dead squares, and a report of active rules at level start. Unique-solution or minimum-push claims are optional metadata, not a requirement for a fun level.

CommonJS mod boundary

The first playable release accepts trusted local CommonJS modules containing a manifest, levels, noun vocabulary, and an emoji palette. Its concrete shape and limits are in the modding guide. A later engine milestone should add pure hooks through a narrow, versioned contract such as:

module.exports = {
  manifest: { id: 'garden-01', name: 'Garden', version: '2.0.0' },
  vocabulary: { nouns: [], verbs: [], properties: [] },
  levels: [{ id: 'intro', width: 9, height: 7, cells: [] }],
  systems: {
    onTurnStart(ctx) {},
    onCollision(ctx) {},
    onGoalCheck(ctx) { return false }
  }
}

The host owns parsing, snapshots, input, rendering, replay, and persistence. Mods may register words, level data, serializers, pure predicates, and named effect handlers. They should not access DOM nodes, mutate the active rule table in place, replace the scheduler, use wall-clock time, or depend on object iteration order. The host passes a capability-scoped context with read-only queries and queued commands (spawn, destroy, setDirection, emitEffect).

This boundary is a recommendation for deterministic, shareable content. It is also a practical security boundary: CommonJS is code, so untrusted mods need a worker or restricted loader with explicit resource and API limits.

Explicit non-goals and open decisions

The first engine should not promise full commercial Baba compatibility, natural language parsing, diagonal movement, continuous time, physics simulation, network multiplayer, arbitrary UI-as-level semantics, or unrestricted code execution in mods. Those features multiply ambiguity before the basic turn contract is proven.

Open decisions to settle with executable examples: whether entities can occupy multiple layers in one cell; whether YOU entities move simultaneously or in ID order; whether win precedes defeat; whether a failed push still consumes a turn; how ALL, EMPTY, and TEXT select targets; whether rule scan uses nearest valid sentence or all overlapping valid sentences; and whether effects are allowed to trigger recursive rule scans. Each decision should become a small fixture level and a replay test before the mod API is frozen.

The GDC talk explicitly cautions that a perfectly robust parser would require removing some desirable complexity; Teikari kept unstable but fitting elements and limited player access to the worst cases, while treating IF as not worth the implementation pain at that stage (GDC talk PDF, pp. 19–21). That is a useful product constraint: keep the grammar deliberately small and make quirky extensions opt-in and testable.

Sources