Files
systemic-game-design-course/course/08-push-chains/scenarios.md
T
Lucas WintherandClaude Fable 5.2 e907f17937 test: pin ordering decisions the suite did not catch (Modules 5, 8, 9); stepped objects do not block a stack
Mutation runs on the references showed six surviving mutants: a live aura
scan, two turn-phase reorderings, a dropped final parse, and two hazard
reorderings. Each now fails a named test.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 09:23:24 +01:00

5.6 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).

test_08_stacked_you_moves_together

  • Setup: newBaba with an empty level 5 wide and 3 high. Place BABA (0,0) IS (1,0) YOU (2,0) AND (3,0) PUSH (4,0). Place two objects baba on (1,2) (the first and the second) and one object baba at (2,2) (the one ahead).
  • Action: move("right").
  • Assertions:
    • posOf(ahead) is (3,2), and posOf(first) and posOf(second) are both (2,2).
    • Every Baba moved exactly one tile. An object steps at most once per move: the Baba ahead isn't pushed a second tile by the ones behind it, and the second Baba of the stack follows the first onto its tile instead of pushing it on or being left behind.