Files
systemic-game-design-course/course/09-turn-pipeline/scenarios.md
T
Lucas WintherandClaude Opus 5.5 1f3359cfe2 feat: module 09 turn pipeline
Module 9 lesson, scenarios, tests (8, both tracks), reference engines
(TURN phase list, conversion, content-registered hazards, win/lost),
WEAK Expansion Drop, harness.md M9 semantics, research notes (WEAK page,
baba-is-auto Game::MovePlayer, X IS NOT X placement reading).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 04:28:17 +01:00

7.1 KiB

Module 9 scenarios: The turn pipeline

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

The level format, place, rules(), activeRules(), hasProperty, posOf, kindsAt 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 to 8. This module adds three harness functions:

  • step(dir) (Rust: step(Dir::Right)) plays one whole turn, in the course's simplified order: Movement → Parse → Conversion → Parse → Hazards → Win, and a last parse so that rules() matches the board. Movement is Module 8's move without its reparse.
  • won() is true once a turn's win check has found a YOU object that is WIN, or that shares its tile with a WIN object on the same float level. It stays true.
  • lost() is true when no object is YOU.

What the turn does, in this module:

  • Conversion. Every object named by a rule X IS Y, where Y is a noun, becomes a Y where it stands: same id, same tile. X IS X blocks it. An object named by X IS NOT X is deleted. All objects are converted at once, from the board as it was after movement.
  • Hazards, each against the objects that share a tile on the same float level (both FLOAT or both not), and each destroying everything it finds at once before the next runs: SINK destroys every object it shares a tile with, and itself; HOT destroys MELT objects (an object that is both destroys itself); DEFEAT destroys YOU objects (an object that is both destroys itself).
  • Win runs after every hazard, so a YOU object destroyed by DEFEAT can't win.
  • Every phase uses the rules from the parse before it.

Words used: the nouns BABA, ROCK, FLAG, LAVA, KEY, SKULL, WATER; the properties YOU, WIN, PUSH, HOT, MELT, SINK, DEFEAT, FLOAT; the operators IS, AND, NOT; the base rule TEXT IS PUSH.

test_09_lava_saved_by_reparse

  • Setup: newBaba with an empty level 6 wide and 5 high. Place BABA (0,0) IS (1,0) YOU (2,0) and LAVA (0,1) IS (1,1) HOT (2,1). Place BABA (4,2) IS (4,3) MELT (4,4), reading down column 4. Place an object lava at (4,4), under the word MELT, and an object baba at (3,4).
  • Assertions 1: hasProperty(baba, "MELT") is true.
  • Action: step("right").
  • Assertions 2:
    • posOf(baba) is (4,4), on the lava, and posOf(meltText) is (5,4): Baba pushed MELT out of its sentence as it stepped.
    • posOf(lava) is (4,4), and kindsAt(4,4) is ["baba", "lava"]: Baba survived. The rules were reparsed after movement, so when the hazards ran, BABA IS MELT no longer existed.
    • rules() doesn't contain "BABA IS MELT", and lost() is false.
    • That Baba survives is inferred from the wiki's order of operations, not stated by a source (see the lesson).

test_09_mid_turn_conversion

  • Setup: newBaba with an empty level 5 wide and 5 high. Place BABA (0,0) IS (1,0) YOU (2,0) and BABA (0,2) IS (1,2), with (2,2) empty. Place a text text:ROCK at (2,3). Place an object baba at (2,4) (the pusher) and an object baba at (4,4) (the other Baba).
  • Action: step("up").
  • Assertions:
    • posOf(rockText) is (2,2): the pusher pushed it up, and BABA IS ROCK now reads along row 2. rules() contains "BABA IS ROCK".
    • posOf(pusher) is (2,3) and posOf(other) is (4,3): both moved first, then converted where they stood.
    • kindsAt(2,3) is ["rock"] and kindsAt(4,3) is ["rock"]: the rule the move completed converted both Babas in the same turn.
    • kindsAt(2,4) and kindsAt(4,4) are [].

test_09_identity_lock_blocks_conversion

  • Setup: newBaba with an empty level 5 wide and 5 high. Place BABA (0,0) IS (1,0) YOU (2,0), BABA (0,1) IS (1,1) BABA (2,1), BABA (0,2) IS (1,2) ROCK (2,2) and KEY (0,3) IS (1,3) ROCK (2,3). Place an object baba at (0,4) and an object key at (3,4).
  • Action: step("right").
  • Assertions:
    • posOf(baba) is (1,4) and kindsAt(1,4) is ["baba"]: BABA IS BABA blocked BABA IS ROCK.
    • kindsAt(3,4) is ["rock"] and posOf(key) is (3,4): KEY IS ROCK converted the key, in place, with the same id.
    • activeRules() doesn't contain "BABA IS ROCK"; it contains "BABA IS BABA" and "KEY IS ROCK".

test_09_self_negation_deletes

  • 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) NOT (2,1) KEY (3,1) and KEY (0,2) IS (1,2) ROCK (2,2). Place an object baba at (0,4) and an object key at (3,4).
  • Assertions 1: posOf(key) is (3,4): placing text doesn't delete anything; the turn does.
  • Action: step("right").
  • Assertions 2:
    • posOf(key) is null: KEY IS NOT KEY deleted the key in the conversion step.
    • kindsAt(3,4) is ["rock"]: KEY IS NOT KEY doesn't lock the key, so KEY IS ROCK still applies, and a rock appears where the key was, as a new object.
    • posOf(baba) is (1,4).

test_09_float_separates_hazards

  • Setup: newBaba with an empty level 4 wide and 6 high. Place BABA (0,0) IS (1,0) YOU (2,0), KEY (0,1) IS (1,1) YOU (2,1), SKULL (0,2) IS (1,2) DEFEAT (2,2) and BABA (0,3) IS (1,3) FLOAT (2,3). Place an object baba at (0,4) and an object skull at (1,4); an object key at (0,5) and an object skull at (1,5).
  • Action: step("right").
  • Assertions:
    • posOf(baba) is (1,4) and kindsAt(1,4) is ["baba", "skull"]: Baba is FLOAT and the skull isn't, so they are on different float levels and DEFEAT doesn't reach Baba.
    • posOf(key) is null and kindsAt(1,5) is ["skull"]: the key is on the skull's float level and is destroyed.
    • lost() is false: Baba is still YOU.

test_09_sink_destroys_both

  • Setup: newBaba with an empty level 5 wide and 5 high. Place BABA (0,0) IS (1,0) YOU (2,0), ROCK (0,1) IS (1,1) PUSH (2,1) and WATER (0,2) IS (1,2) SINK (2,2). Place an object baba at (0,4), an object rock at (1,4) and an object water at (2,4).
  • Action: step("right").
  • Assertions:
    • posOf(rock) and posOf(water) are both null: the rock was pushed onto the water, and SINK destroyed both.
    • kindsAt(2,4) is [].
    • posOf(baba) is (1,4): Baba didn't share the water's tile and survives.

test_09_you_and_win

  • Setup: newBaba with an empty level 4 wide and 4 high. Place BABA (0,0) IS (1,0) YOU (2,0) and BABA (0,1) IS (1,1) WIN (2,1). Place an object baba at (0,3).
  • Assertions 1: won() is false: the win is checked by the turn, not when text is placed.
  • Action: step("right").
  • Assertions 2: won() is true: an object that is both YOU and WIN wins. lost() is false.

test_09_defeat_beats_win

  • Setup: newBaba with an empty level 5 wide and 4 high. Place BABA (0,0) IS (1,0) YOU (2,0) and FLAG (0,1) IS (1,1) WIN (2,1) AND (3,1) DEFEAT (4,1). Place an object baba at (0,3) and an object flag at (1,3).
  • Action: step("right").
  • Assertions:
    • posOf(baba) is null: Baba stepped onto the flag, which is DEFEAT.
    • won() is false: DEFEAT runs before the win check, so no YOU object is left to win.
    • lost() is true, and posOf(flag) is (1,3).