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 <noreply@anthropic.com>
2026-10-29 04:05:42 +01:00

65 lines
5.0 KiB
Markdown

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