Files
systemic-game-design-course/course/08-push-chains/scenarios.md
T
Lucas WintherandClaude Opus 5.5 bd80cefbef feat: module 08 push chains
Movement planned in full, then committed: a Planner builds a plan
(moving and destroyed ids) by running content's movement responses
(OPEN/SHUT, PUSH, STOP) for every step, rolling back failed branches.
Edge of level blocks; YOU objects move furthest-ahead first; rules are
reparsed once after movement. Harness: move/move_dir(Dir), posOf,
kindsAt. Expansion Drop: PULL as one word plus one content response.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 04:05:42 +01:00

5.0 KiB

Module 8 scenarios: Push chains (validate, then commit)

Each heading is a test name. The Rust test lives in tests/rust/test_08.rs and the TypeScript test in tests/ts/test_08.test.ts; both state exactly what is written here.

The level format, place, rules(), activeRules(), hasProperty and the way a test writes a sentence (BABA (0,0) IS (1,0) YOU (2,0) is three text objects) are those of Modules 6 and 7. This module adds three harness functions:

  • move(dir) (Rust: move_dir(Dir::Right)) moves every object that is YOU one tile in dir, "up", "down", "left" or "right", with everything it pushes, and then reparses the rules. Nothing else happens: no conversion, no win, no hazard. That is the turn of Module 9.
  • posOf(id) is the object's tile [x, y] (Rust: Some((x, y))), or null (Rust: None) once the object has been destroyed.
  • kindsAt(x, y) is the kinds of the objects on a tile, sorted, written as they are placed: "baba" for a thing, "text:ROCK" for text. An empty tile gives [].

What moving means, in this module:

  • An object that is PUSH on the tile a mover enters is pushed one tile the same way, and may push the next one: a push chain. If any object in the chain can't move, nothing in the chain moves, the YOU object included.
  • An object that is STOP, and isn't pushed out of the way, blocks the move.
  • The edge of the level blocks like STOP: no object leaves the level by moving.
  • When an OPEN object moves onto a tile with a SHUT object (or the reverse), both are destroyed, and the SHUT object doesn't block. A pushed object counts as moving.
  • Every rule a move uses is the rule in force before the move. The rules are reparsed once, after all movement.

Words used: the nouns BABA, ROCK, WALL, KEY, DOOR; the properties YOU, PUSH, STOP, OPEN, SHUT; the operators IS, AND; the base rule TEXT IS PUSH.

test_08_chain_moves_together

  • Setup: newBaba with an empty level 6 wide and 4 high. Place BABA (0,0) IS (1,0) YOU (2,0) and ROCK (0,1) IS (1,1) PUSH (2,1). Place an object baba at (0,3) and three objects rock at (1,3), (2,3) and (3,3).
  • Action: move("right").
  • Assertions:
    • posOf(baba) is (1,3).
    • posOf of the three rocks, in the order placed, is (2,3), (3,3) and (4,3): the whole chain shifted one tile.
    • kindsAt(0,3) is [] and kindsAt(4,3) is ["rock"].

test_08_chain_into_stop_nothing_moves

  • Setup: newBaba with an empty level 6 wide and 5 high. Place BABA (0,0) IS (1,0) YOU (2,0), ROCK (0,1) IS (1,1) PUSH (2,1) and WALL (0,2) IS (1,2) STOP (2,2). Place an object baba at (0,4), three objects rock at (1,4), (2,4) and (3,4), and an object wall at (4,4). The tile (5,4), beyond the wall, is empty.
  • Action: move("right").
  • Assertions:
    • posOf(baba) is still (0,4).
    • The rocks are still at (1,4), (2,4) and (3,4), and the wall at (4,4).
    • The chain is checked before anything moves: the first rocks don't move because the last one can't, and Baba doesn't move because the chain can't.

test_08_open_shut_during_push

  • Setup: newBaba with an empty level 5 wide and 5 high. Place BABA (0,0) IS (1,0) YOU (2,0), KEY (0,1) IS (1,1) PUSH (2,1) AND (3,1) OPEN (4,1) and DOOR (0,2) IS (1,2) STOP (2,2) AND (3,2) SHUT (4,2). Place an object baba at (0,4), an object key at (1,4) and an object door at (2,4).
  • Action: move("right").
  • Assertions:
    • posOf(key) and posOf(door) are both null: the pushed key is OPEN, the door is SHUT, and both are destroyed during the move. The door's STOP doesn't block the key.
    • posOf(baba) is (1,4): the push succeeded, so Baba moves one tile, into the key's former tile. It does not reach the door's tile.
    • kindsAt(2,4) is [] and kindsAt(1,4) is ["baba"].
    • Baba's landing tile is inferred from the sources, not stated by them (see the lesson).

test_08_text_pushed

  • Setup: newBaba with an empty level 4 wide and 5 high. Place BABA (0,0) IS (1,0) YOU (2,0). Place an object baba at (0,2) and a text text:ROCK at (1,2). Place a text text:IS at (2,3) and a text text:PUSH at (2,4).
  • Assertions 1: rules() is exactly ["BABA IS YOU", "TEXT IS PUSH"].
  • Action: move("right").
  • Assertions 2:
    • posOf(baba) is (1,2) and posOf(rockText) is (2,2): text is pushed by the base rule TEXT IS PUSH, with no rule written for it.
    • rules() is exactly ["BABA IS YOU", "ROCK IS PUSH", "TEXT IS PUSH"]: the pushed ROCK now reads down the column as ROCK IS PUSH, and move reparsed the rules.

test_08_level_edge_is_stop

  • Setup: newBaba with an empty level 3 wide and 3 high. Place BABA (0,0) IS (1,0) YOU (2,0). Place an object baba at (1,2) and a text text:FLAG at (2,2), on the right edge.
  • Action 1: move("right").
  • Assertions 1: posOf(baba) is (1,2) and posOf(flagText) is (2,2). The text would have to leave the level, so the chain can't move.
  • Action 2: move("down").
  • Assertions 2: posOf(baba) is still (1,2): Baba is on the bottom edge. Nothing was destroyed: posOf(flagText) is still (2,2).