Files
settled-reach/docs/workshops/test-architecture/gestalt-round3.md
T
jpmschweitzerandClaude Opus 4.6 563a295a90 docs(docs): add frontmatter to test-architecture workshop
Standardized YAML frontmatter on all 20 files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 00:01:23 +01:00

38 KiB
Raw Blame History

title, description, type, status, workshop, agent, round, created
title description type status workshop agent round created
Gestalt — Round 3: Final Gauntlet Map Specification Gestalt's final buildable Gauntlet map specification for sprint 8 implementation workshop archived test-architecture gestalt 3 2026-02-17

Gestalt — Round 3: Final Gauntlet Map Specification

Workshop: QA Strategy & Test Architecture Track: 1 (Test World Design) Date: 2026-02-17 Purpose: Produce a buildable map specification for Sprint 8. Dudley builds from this.


1. Gauntlet MVP Room List — Sprint 8

7 rooms + Central Hub. From the 14 rooms designed in Rounds 1-2, this is the minimum set that covers every Sprint 6-7 bug class, the top 10 invariants, and the core system interactions.

Rooms Shipping Sprint 8

# Room Why Sprint 8 Bug Coverage Invariants
0 Central Hub Connector. Required for hub-and-spoke. INV-S01 (spawn reachable)
1 Pause Chamber Bug #3 direct regression guard. Dudley's 8 pause tests need a room. Bug #3 (movement while paused) INV-T04 (pause coherence)
2 Occlusion Corridor Core information boundary testing. LOS = the game's master mechanic. INV-P02 (LOS symmetry)
3 Inventory Warehouse Pickup/drop/slot system. Vertical slice requirement. INV-S05 (no entity inside geometry)
4 Interaction Gallery Verb computation, multi-verb selection, sprint suppression.
5 Fog Theater Fog layer transitions. Distinct from Occlusion (fog = visual treatment, occlusion = LOS geometry). INV-P02 (fog+LOS interaction)
6 Dialogue Room KG state, trust tiers, contradiction detection. Content system anchor. INV-C07 (dialogue pool non-empty), INV-C03 (StableId uniqueness)
7 Crowd Plaza Entity density stress test. Doubles as determinism + tick budget test target. 15+ NPCs. Bug #1 (snapshot delivery at load), Bug #5 (overwrite at density), Bug #6 (rate mismatch) INV-T01 (deterministic replay), INV-T02 (tick budget), INV-T03 (snapshot delivery)

Crowd Plaza is the stress room. It replaces the separate Shift Change room for Sprint 8 by combining density testing, determinism verification, and tick budget measurement into one room with 15 NPCs. Running the same seed twice and comparing golden files tests INV-T01. Measuring tick time at 15+ entity density tests INV-T02.

Rooms Deferred to Sprint 9+

# Room Deferral Reason Ships When
8 Zone Gate Zone transitions not implemented Multi-map system lands
9 Eavesdrop Alcove Depends on ListeningFocus (D-071) Audio system implementation
10 Confrontation Stage Depends on confrontation vulnerability (D-070) Confrontation system implementation
11 Sprint Gauntlet Sprint suppression covered by Interaction Gallery + cross-room T1 Stretch goal for Sprint 9
12 Sound Lab Depends on sound propagation system Audio system implementation
13 Decay Observatory KG decay not yet connected to fog rendering KG decay wired to client
14 Shift Change Merged into Crowd Plaza for Sprint 8 When granular stress testing needed

Deferral justification: Every deferred room depends on a system that doesn't exist yet. The 7 MVP rooms cover every implemented system. When new systems land, new rooms get appended — the additive-only rule means Sprint 8 rooms never change.


2. Physical Map Layout — Final

Topology

Hub-and-spoke with 2 cross-cuts. All corridors are straight (orthogonal). Every room is reachable from the hub in 1-2 corridors.

                        ┌─────────────────────────────┐
                        │        FOG THEATER          │
                        │       (44×32 tiles)         │
                        │    observer: (28,16) S      │
                        └────────────┬────────────────┘
                                     │ corridor-N (6×10)
                                     │
┌──────────────────┐    ┌────────────┴────────────┐    ┌──────────────────────────┐
│ INVENTORY        │    │                         │    │  OCCLUSION CORRIDOR      │
│ WAREHOUSE        ├────┤      CENTRAL HUB        ├────┤  (42×22 tiles)           │
│ (30×28 tiles)    │    │      (24×24 tiles)      │    │  observer: (10,10) E     │
│ observer:(15,14)E│    │      spawn: (12,12)     │    │                          │
└────────┬─────────┘    └───────┬─────────────────┘    └─────────────┬────────────┘
         │                      │                                    │
    cross-cut-W (6×10)     corridor-S (6×10)                cross-cut-E (6×10)
         │                      │                                    │
┌────────┴─────────┐    ┌──────┴──────────┐              ┌──────────┴─────────────┐
│ INTERACTION      │    │ PAUSE CHAMBER   │              │   CROWD PLAZA          │
│ GALLERY          │    │ (16×16 tiles)   │              │   (32×32 tiles)        │
│ (24×20 tiles)    │    │ observer:(8,8) N│              │   observer: (16,16) W  │
│ observer:(12,10)E│    └──────┬──────────┘              │                        │
└──────────────────┘           │                         └────────────────────────┘
                          corridor-S2 (6×10)
                               │
                        ┌──────┴──────────┐
                        │ DIALOGUE ROOM   │
                        │ (28×20 tiles)   │
                        │ observer:(14,10)│
                        │ N               │
                        └─────────────────┘

Room Coordinate Table

All coordinates in sim tiles (0.5m per tile, per D-066). Room bounds include 2-tile-thick walls (1 visual tile = D-066 minimum). Interior walkable area starts 2 tiles inside each boundary.

# Room Origin (x,y) Size (w×h) Interior Observer Pos Observer Facing
0 Central Hub (38, 46) 24×24 20×20 (50, 58)
1 Fog Theater (28, 2) 44×32 40×28 (56, 18) South
2 Occlusion Corridor (74, 48) 42×22 38×18 (84, 58) East
3 Inventory Warehouse (2, 40) 30×28 26×24 (17, 54) East
4 Interaction Gallery (2, 82) 24×20 20×16 (14, 92) East
5 Pause Chamber (42, 78) 16×16 12×12 (50, 86) North
6 Dialogue Room (36, 104) 28×20 24×16 (50, 114) North
7 Crowd Plaza (80, 78) 32×32 28×28 (96, 94) West

Observer positions are the fixed player positions used for golden file snapshots. Each room has exactly one canonical observer position. Golden files are generated by: place player at observer position → wait 1 tick → serialize ObserverSnapshot → that's the golden file.

Corridor Table

Corridors are 6 sim tiles wide (3 visual tiles — player + 1 tile margin each side) and 10 sim tiles long (5 visual tiles). Walls on both sides. Zone boundary at midpoint (for D-073 zone crossfade testing when audio ships).

Corridor Origin (x,y) Size (w×h) Connects Reset Plate At
corridor-N (47, 34) 6×12 Hub ↔ Fog Theater (50, 34)
corridor-E (62, 55) 12×6 Hub ↔ Occlusion Corridor (73, 58)
corridor-W (32, 55) 6×6 Hub ↔ Inventory Warehouse (33, 58)
corridor-S (47, 70) 6×8 Hub ↔ Pause Chamber (50, 77)
corridor-S2 (47, 94) 6×10 Pause Chamber ↔ Dialogue Room (50, 103)
cross-cut-W (26, 68) 6×14 Inventory ↔ Interaction Gallery
cross-cut-E (80, 70) 6×8 Occlusion ↔ Crowd Plaza

Reset plates are at room entrances (corridor end closest to the room, not the hub). Cross-cuts do not have reset plates — they are shortcuts, not room entrances.

Overall Map Bounds

  • Width: 0 to 116 sim tiles (58 visual tiles, 29 meters)
  • Height: 0 to 124 sim tiles (62 visual tiles, 31 meters)
  • Total walkable area: ~2,400 sim tiles (estimated)
  • Total entities: ~35 (see entity tables below)

3. Entity Placement Per Room

Every entity has a fixed position, a stable spawn order (determines StableId), and a defined initial state. Entities are listed in canonical spawn order — this is the order they appear in the Gauntlet YAML/builder, and thus the order they receive StableId assignments. Changing this order breaks golden files.

Room 0: Central Hub

No test entities. The hub is a connector with directional markers.

Entity Type Position (relative) Purpose
sign_north Object (12, 2) "Fog Theater →" marker
sign_east Object (22, 12) "Occlusion Corridor →" marker
sign_south Object (12, 22) "Pause Chamber →" marker
sign_west Object (2, 12) "Inventory Warehouse →" marker

Hub spawn point: (12, 12) relative to room origin = (50, 58) absolute.

Room 1: Fog Theater

Tests D-059 (5-layer fog), D-015 (vision cone), D-060 (cognitive delay in fog), D-035 (symmetric LOS).

Layout: Three zones — open foyer (entry), narrowing corridor, enclosed back room. Fog thickens as player moves from foyer to back room.

Entity Type Rel. Position Initial State Test Purpose
npc_fog_near NPC (10, 14) Neutral, visible from observer Fog layer: Clear. Entity fully visible.
npc_fog_mid NPC (28, 14) Unknown, in light fog from observer Fog layer: Peripheral. Entity dimmed.
npc_fog_far NPC (38, 24) Unknown, behind corridor wall Fog layer: Deep fog. Entity not visible (wall blocks).
crate_fog Object (20, 10) Floor item Interaction in fog: can player interact in peripheral fog?

Wall layout: L-shaped wall at y=20, x=[24,40] creating a corridor. Back room behind the wall from x=30 to x=40, y=20 to y=28.

Golden file assertions:

  • From observer (28,16) facing South: npc_fog_near VISIBLE, npc_fog_mid VISIBLE (peripheral), npc_fog_far NOT VISIBLE (wall blocks), crate_fog VISIBLE.
  • Fog tile counts: ~120 Clear, ~80 Peripheral, ~150 Deep, remainder Unexplored.

Room 2: Occlusion Corridor

Tests D-035 (symmetric shadowcasting), D-017 (perception modes), D-015 (vision cone sectors).

Layout: Long east-west corridor with perpendicular wall segments creating visibility pockets. North alcove with hidden NPC. South alcove with partially visible NPC.

Entity Type Rel. Position Initial State Test Purpose
npc_guard_visible NPC (18, 10) Neutral Clear LOS from observer. Baseline "visible entity."
npc_hidden_wall NPC (18, 4) Unknown Behind north wall segment. NOT visible in Visual mode.
npc_peripheral NPC (6, 16) Unknown In south alcove, peripheral sector from observer facing East.
npc_far_end NPC (36, 10) Unknown Far end of corridor. Tests LOS at range.

Wall layout:

  • North wall segment: x=[14,22], y=[6,7] — blocks LOS to npc_hidden_wall from observer at (10,10).
  • South alcove walls: x=[4,8], y=[12,13] — creates peripheral pocket.

Golden file assertions:

  • From observer (10,10) facing East: npc_guard_visible VISIBLE/Forward, npc_hidden_wall NOT VISIBLE (blocked), npc_peripheral VISIBLE/Peripheral, npc_far_end VISIBLE/Forward.
  • LOS symmetry: from npc_guard_visible's position, the observer position should also be visible.

Room 3: Inventory Warehouse

Tests D-065 (9-slot inventory), pickup/drop verbs, CarriedBy component.

Layout: Open warehouse floor with 10 crates arranged in a grid. Player starts near the entrance with empty inventory.

Entity Type Rel. Position Initial State Test Purpose
crate_01 Object (4, 4) Item: "keycard" First pickup. Slot assignment.
crate_02 Object (8, 4) Item: "manifest" Second pickup.
crate_03 Object (12, 4) Item: "datapad" Third pickup.
crate_04 Object (16, 4) Item: "toolkit" Fourth pickup.
crate_05 Object (4, 10) Item: "badge" Fifth pickup.
crate_06 Object (8, 10) Item: "medkit" Sixth pickup.
crate_07 Object (12, 10) Item: "ration" Seventh pickup.
crate_08 Object (16, 10) Item: "cable" Eighth pickup.
crate_09 Object (4, 16) Item: "seal" Ninth pickup — inventory now FULL (9/9).
crate_10 Object (8, 16) Item: "chip" Tenth — Take verb offered by server but should fail/grey-out.
npc_warehouse NPC (22, 14) Neutral Interaction target (Talk verb while carrying items).

Golden file assertions:

  • From observer (15,14) facing East: all 10 crates visible, npc_warehouse visible. Inventory count 0/9.
  • After taking 9 items: inventory count 9/9, all crates still in snapshot (Take verb still offered).

Tests D-057 (vertical interaction list), D-056 (cursor states), D-058 (world menu), D-055 (sprint suppression).

Layout: Gallery corridor with one entity per ObjectType in a line, plus a multi-verb NPC at the end.

Entity Type Rel. Position Initial State Test Purpose
npc_talker NPC (4, 8) Neutral Single verb: Talk.
obj_container Object (8, 8) Container Single verb: Take.
obj_terminal Object (12, 8) Terminal Single verb: Read.
obj_door Object (16, 8) Door Single verb: Open.
npc_multiverb NPC (4, 14) Friendly Multiple verbs: Talk(1), Observe(2), ExamineNpc(3). Tests verb priority ordering.

Sprint entry zone: Cross-cut-W enters at (20, 10). Player sprinting from Inventory through cross-cut arrives here. Interaction buffer must be empty during sprint, then repopulate within 1 tick of stance change to Walk.

Golden file assertions:

  • From observer (12,10) facing East: all 5 entities visible. Interaction list shows closest entities with correct verb sets.

Room 5: Pause Chamber

Tests D-031 (pause/unpause), Bug #3 regression (movement while paused).

Layout: Minimal open room with a single NPC. No visual complexity — this room is pure state machine testing.

Entity Type Rel. Position Initial State Test Purpose
npc_pause_target NPC (6, 6) Neutral Interaction target during pause (Talk should work).

Test scenarios (Dudley's 8 tests):

  1. Movement discarded while paused
  2. Unpause accepted while paused
  3. Stance toggle allowed while paused
  4. Interact allowed while paused
  5. Multiple movements in paused batch all discarded
  6. Pause → move (discarded) → unpause → move (accepted) roundtrip
  7. SetTickRate(Half) while paused behavior
  8. Perception mode toggle during pause (passthrough)

Golden file assertions:

  • From observer (8,8) facing North: npc_pause_target visible. TickRate: Full.
  • Paused golden file: identical entity positions, TickRate: Paused.

Room 6: Dialogue Room

Tests D-041 (knowledge graph), D-028 (tagged dialogue pools), D-033 (entity color = relationship), D-062 (invisible locked options), D-063 (confrontation same box), D-064 (walk-away consequences).

Layout: Waiting room layout — NPCs spaced apart at different trust tiers. One NPC has pre-loaded contradictory KG entries.

Entity Type Rel. Position Initial State Test Purpose
npc_stranger NPC (4, 8) Unknown, Trust Tier 1 Dialogue: access-tier lines only. Color: Unknown (#4a9ebb).
npc_acquaintance NPC (12, 8) Neutral, Trust Tier 2 Dialogue: relationship-history lines available.
npc_trusted NPC (20, 8) Friendly, Trust Tier 3 Dialogue: trust-gated gossip available. Color: Friendly (#6bc9a6).
npc_contradiction NPC (12, 14) Neutral, KG: 2 contradicting facts Dialogue triggers contradiction detection. Color should shift to POI (#e8c547). Monologue fires: "Wait — they said..."

KG injection for npc_contradiction:

knowledge_entries:
  - fact_id: "test.manifests_clean"
    confidence: KnowsDetails
    source: npc_stranger
  - fact_id: "test.manifests_tampered"
    confidence: KnowsDetails
    source: npc_trusted

These contradict → Contradicted state after player learns both.

Golden file assertions:

  • From observer (14,10) facing North: all 4 NPCs visible with correct relationship colors. No active dialogue.
  • After dialogue with npc_contradiction and discovering both facts: entity color transitions to POI amber.

Room 7: Crowd Plaza

Tests entity density, D-033 (color spread at scale), D-060 (cognitive delay overlap), determinism (INV-T01), tick budget (INV-T02), snapshot delivery (INV-T03).

Layout: Large open area with 15 NPCs at varied positions and relationship states.

Entity Type Rel. Position Initial State Test Purpose
npc_crowd_01 NPC (4, 4) Friendly D-033 color: #6bc9a6
npc_crowd_02 NPC (10, 4) Hostile D-033 color: #d45d5d
npc_crowd_03 NPC (16, 4) Unknown D-033 color: #4a9ebb
npc_crowd_04 NPC (22, 4) Neutral
npc_crowd_05 NPC (28, 4) Friendly
npc_crowd_06 NPC (4, 10) Unknown
npc_crowd_07 NPC (10, 10) Neutral
npc_crowd_08 NPC (16, 10) Hostile
npc_crowd_09 NPC (22, 10) Friendly
npc_crowd_10 NPC (28, 10) Unknown
npc_crowd_11 NPC (4, 16) Neutral
npc_crowd_12 NPC (10, 16) Friendly
npc_crowd_13 NPC (16, 16) Hostile D-033: 3 hostile in room, color spread check
npc_crowd_14 NPC (22, 16) Unknown
npc_crowd_15 NPC (28, 16) Neutral

Relationship distribution: 4 Friendly, 3 Hostile, 4 Unknown, 4 Neutral. Tests D-033 color spread — all 4 relationship colors visible simultaneously.

Stress test assertions:

  • Determinism (INV-T01): Run with seed 42, capture golden file at tick 10. Run again with seed 42 and identical inputs. Golden files must be byte-identical.
  • Tick budget (INV-T02): 100 ticks with 15 NPCs. p95 tick time < 20ms (Tyre's threshold). p99 < 50ms (hard alarm).
  • Snapshot delivery (INV-T03): Every tick produces exactly one ObserverSnapshot. No drops, no duplicates, monotonically increasing tick numbers.

4. Cross-Room Transitions — Sprint 8 MVP

3 transitions. Selected for maximum bug coverage with minimum test infrastructure.

T1: Sprint Exit (Crowd Plaza → Occlusion Corridor)

Path: Crowd Plaza → cross-cut-E → Occlusion Corridor entrance

System combination: D-055 (sprint suppression) + D-035 (LOS recalculation)

Test script:

spawn_at crowd_plaza.observer
set_stance Sprint
move_toward cross-cut-E entrance
# During sprint: verify
assert snapshot.nearby_interactions == []     # buffer cleared
assert snapshot.player_stance == Sprint
# Enter Occlusion Corridor
move_to occlusion_corridor.entrance
set_stance Walk
wait 1 tick
assert snapshot.nearby_interactions.len() > 0  # verbs repopulate
assert npc_guard_visible in snapshot.entities  # LOS recomputed

Why Sprint 8: Both sprint and LOS are implemented. Tests the most common player behavior pattern. Uses cross-cut-E which physically connects these rooms.

Path: Inventory Warehouse → cross-cut-W → Interaction Gallery

System combination: D-065 (inventory limit) + D-057 (verb computation)

Test script:

spawn_at inventory_warehouse.observer
# Fill inventory to 9/9
repeat 9: interact crate_N Take
assert snapshot.inventory.count == 9
# Walk to Interaction Gallery
move_through cross-cut-W
move_to interaction_gallery.obj_container
# Verify Take verb behavior at 9/9
assert "Take" in snapshot.nearby_interactions[obj_container].verbs
# Server still offers Take — it's the CLIENT that should grey it out
# (Server doesn't filter Phase 1 verbs based on inventory state)

Why Sprint 8: Inventory and interaction are vertical slice systems. This tests the boundary between "server offers" and "client filters" — a class of information boundary bugs.

T6: Pause Anywhere (Pause Chamber → Hub → any room)

Path: Pause Chamber → corridor-S → Hub → corridor to any room → unpause

System combination: D-031 (pause) + any room's systems

Test script:

spawn_at pause_chamber.observer
send Pause
assert snapshot.tick_rate == Paused
# Teleport to Hub (or walk)
send TeleportToHub
assert player_position == hub.spawn
# Move into Fog Theater
move_to fog_theater.entrance
# Still paused — fog state should be frozen
send MoveNorth
assert player_position unchanged     # movement discarded
send Unpause
assert snapshot.tick_rate == Full
send MoveNorth
assert player_position changed       # movement now works
# Verify fog theater state is coherent (fog tiles computed correctly)
assert snapshot.visible_tiles.len() > 0

Why Sprint 8: Bug #3 regression guard. Tests that pause state persists across room transitions and teleports. Any state corruption during pause-while-transitioning would be caught.

Deferred Transitions

# Transition Deferral Reason
T2 Fog into Dialogue Cognitive delay during dialogue not yet wired
T4 Sprint into Interaction Covered by T1 + T3
T5 Confrontation to Eavesdrop Both rooms deferred
T7 Sound across Fog Sound propagation not implemented
T8 Walk-away Sprint Partially covered by T6 + Dialogue Room unit tests

5. Anti-Tedium — Sprint 8 MVP

Ships Sprint 8

Feature Priority Effort Justification
Reset Plate Essential ~1 day (Dudley) Without it, every test iteration requires server restart. Ozzie's UX flow depends on it. Already fully specified in Round 2.
Hub Teleport Essential ~0.5 day (Dudley + Stig) Saves 30+ seconds per room switch. PlayerAction::TeleportToHub + Home key binding. Already fully specified.

Deferred

Feature Priority Effort Deferral Reason
WRONG Button (F12) Sprint 9 ~1.5 days Requires ring buffer, file output system, and text renderer integration. Text renderer ships Sprint 8 but WRONG button integration is Sprint 9 scope. Test client --json provides partial coverage in the interim.
Room Timer + PBs Sprint 9 ~0.5 day Nice-to-have gamification. Zero impact on test coverage.
F3 Debug Overlay Sprint 10+ ~2 days Per Stig's recommendation: WRONG button captures same data on-demand. F3 as real-time overlay has measurable performance cost.

Reset Plate — Final Spec Confirmation

Per Round 2 consensus (Gestalt + Ozzie + Dudley + Stig):

  • Trigger: Step on ResetPlate tile + press Interact (NOT automatic)
  • Server: RoomResetTrigger component, RoomSnapshots resource (tick-0 per room), execute_room_reset system
  • Resets: Entity positions, entity KG, player KG (room refs only), fog (room tiles), room-sourced inventory items, dialogue state
  • Does NOT reset: Other rooms, player position, session/tick counter, global SimRng state
  • Debounce: 10-tick cooldown after trigger
  • Test-mode only: RoomResetTrigger entities only added with --test-mode

Hub Teleport — Final Spec Confirmation

  • Trigger: Home key → PlayerAction::TeleportToHub
  • Server: Move player to GAUNTLET.hub_spawn, clear dialogue/monologue/interaction buffer
  • Does NOT affect: Room state, inventory, game time, knowledge graph
  • Gauntlet-only: Server rejects TeleportToHub in non-Gauntlet maps

6. Remaining Questions — Answers

Q2: Per-Room Reset vs Server Restart

Answer: Both. Different test types need different reset levels.

Reset Level Mechanism When to Use
Room reset (Reset Plate) Restore room entities to tick-0 state. Player stays. Global state preserved. Manual testing iteration. Checklist retries. Cross-room transitions (reset source room, re-test).
Full restart (make test-world-headless) Kill server process, relaunch. Tick counter at 0, fresh SimRng, fresh TCP connection. Determinism replay tests (INV-T01). Layer 3 subprocess tests. Golden file generation. Any test where SimRng position matters.

Tests requiring full restart:

  • INV-T01 (deterministic replay) — SimRng position must match exactly
  • INV-T03 (snapshot delivery) — tests TCP connection lifecycle
  • Layer 3 subprocess test — spawns fresh server process by definition
  • Golden file regeneration — must produce identical output from tick 0
  • Performance benchmarks — clean process, no accumulated state

Tests where room reset is sufficient:

  • All per-room checklist items
  • All cross-room transition scenarios (T1, T3, T6)
  • Manual exploratory testing
  • Bug reproduction attempts (after capturing with WRONG button)

No separate "full reset" command needed. make test-world-headless is the full restart. Reset plates handle in-session resets. These two mechanisms cover all use cases.

Q3: Cross-Cut Count — MVP

Answer: 2 cross-cuts for Sprint 8.

Cross-Cut Connects Transition Scenario Width
cross-cut-W Inventory ↔ Interaction Gallery T3 (Full Inventory Interact) 6×14 sim tiles
cross-cut-E Occlusion ↔ Crowd Plaza T1 (Sprint Exit) 6×8 sim tiles

Justification: Each cross-cut serves a specific Sprint 8 transition scenario. Each connects rooms that test complementary systems (inventory+interaction, LOS+density). More cross-cuts add map complexity without adding test coverage for Sprint 8 systems.

Deferred cross-cuts (Sprint 9+):

Cross-Cut Connects Ships When
Sound Lab ↔ Fog Theater Sound propagation through fog Audio system implementation
Confrontation Stage ↔ Eavesdrop Alcove Emotional state transitions D-070/D-071 implementation
Sprint Gauntlet ↔ Interaction Gallery Full sprint-to-interaction chain Sprint Gauntlet room ships

Sound Lab consolidation note: When sound propagation ships, evaluate whether a "sound section" can be added to the Occlusion Corridor (as a sub-area with sound sources behind walls) rather than building a separate room. If the system is simple enough, the sub-area approach saves a room + cross-cut. If it needs dedicated space, add Sound Lab as room #8 with its own cross-cut.

Q6: Cross-Room Checklist Location

Answer: content/gauntlet/cross_room_checks.yaml at the Gauntlet root level.

Cross-room transitions don't belong in per-room checklists because they span multiple rooms and test system combinations, not individual systems.

# content/gauntlet/cross_room_checks.yaml
transitions:
  - id: t1_sprint_exit
    description: "Sprint from Crowd Plaza through cross-cut to Occlusion Corridor"
    source_room: crowd_plaza
    target_room: occlusion_corridor
    path: [crowd_plaza, cross_cut_e, occlusion_corridor]
    checks:
      - id: t1_01_buffer_cleared
        description: "Interaction buffer empty during sprint through cross-cut"
        type: auto
        condition:
          player_stance: Sprint
          expected_interactions: empty
        if_wrong: |
          Sprint suppression not clearing buffer on room transition.
          Check: D-055 interaction buffer clear in bridge/mod.rs.

      - id: t1_02_los_recomputed
        description: "LOS recomputed on entering Occlusion Corridor"
        type: auto
        condition:
          player_room: occlusion_corridor
          player_stance: Walk
          entity: npc_guard_visible
          expected: visible
        if_wrong: |
          LOS not recomputed after room transition.
          Check: compute_observer_snapshot runs after validate_movement.

  - id: t3_full_inventory_interact
    description: "Carry full inventory from Warehouse to Interaction Gallery"
    source_room: inventory_warehouse
    target_room: interaction_gallery
    path: [inventory_warehouse, cross_cut_w, interaction_gallery]
    checks:
      - id: t3_01_take_still_offered
        description: "Server still offers Take verb at 9/9 inventory"
        type: auto
        condition:
          inventory_count: 9
          entity: obj_container
          verb: Take
          expected: present
        if_wrong: |
          Server filtering Take based on inventory state.
          Phase 1 (ObjectType) verbs should not check inventory.
          Check: interaction.rs verb_computation.

  - id: t6_pause_anywhere
    description: "Pause in Pause Chamber, teleport, verify pause persists"
    source_room: pause_chamber
    target_room: fog_theater
    path: [pause_chamber, hub, fog_theater]
    checks:
      - id: t6_01_pause_survives_teleport
        description: "TickRate stays Paused after Hub teleport"
        type: auto
        condition:
          tick_rate: Paused
          player_room: hub
        if_wrong: |
          TeleportToHub resetting tick rate. Check: teleport handler
          should NOT modify TickRate.

      - id: t6_02_movement_blocked_in_new_room
        description: "Movement discarded in Fog Theater while paused"
        type: auto
        condition:
          tick_rate: Paused
          player_room: fog_theater
          movement_input: discarded
        if_wrong: |
          Pause guard not checking TickRate after room change.
          Check: process_player_input guard at input.rs:102-105.

make checklist generates both per-room and cross-room sections. The test client loads cross_room_checks.yaml when running transition scenarios.


7. Gauntlet Constants Module

For Dudley's implementation. This module is the single source of truth for room geometry, entity positions, and observer locations.

// server/src/test_world/constants.rs

use crate::simulation::movement::TilePosition;

/// A Gauntlet room definition.
pub struct GauntletRoom {
    pub name: &'static str,
    pub origin: TilePosition,         // top-left corner (includes walls)
    pub size: (i32, i32),             // (width, height) in sim tiles
    pub observer: TilePosition,       // golden file observer position
    pub observer_facing: Facing,      // direction observer faces
    pub reset_plate: Option<TilePosition>, // reset plate location (if any)
}

/// A Gauntlet entity definition.
pub struct GauntletEntity {
    pub name: &'static str,
    pub room: &'static str,
    pub position: TilePosition,       // relative to room origin
    pub kind: EntityKind,
    pub relationship: RelationshipState,
}

/// Canonical room definitions.
pub const HUB: GauntletRoom = GauntletRoom {
    name: "central_hub",
    origin: TilePosition::new(38, 46, 0),
    size: (24, 24),
    observer: TilePosition::new(50, 58, 0),
    observer_facing: Facing::North,
    reset_plate: None,
};

pub const FOG_THEATER: GauntletRoom = GauntletRoom {
    name: "fog_theater",
    origin: TilePosition::new(28, 2, 0),
    size: (44, 32),
    observer: TilePosition::new(56, 18, 0),
    observer_facing: Facing::South,
    reset_plate: Some(TilePosition::new(50, 34, 0)),
};

pub const OCCLUSION_CORRIDOR: GauntletRoom = GauntletRoom {
    name: "occlusion_corridor",
    origin: TilePosition::new(74, 48, 0),
    size: (42, 22),
    observer: TilePosition::new(84, 58, 0),
    observer_facing: Facing::East,
    reset_plate: Some(TilePosition::new(73, 58, 0)),
};

pub const INVENTORY_WAREHOUSE: GauntletRoom = GauntletRoom {
    name: "inventory_warehouse",
    origin: TilePosition::new(2, 40, 0),
    size: (30, 28),
    observer: TilePosition::new(17, 54, 0),
    observer_facing: Facing::East,
    reset_plate: Some(TilePosition::new(33, 58, 0)),
};

pub const INTERACTION_GALLERY: GauntletRoom = GauntletRoom {
    name: "interaction_gallery",
    origin: TilePosition::new(2, 82, 0),
    size: (24, 20),
    observer: TilePosition::new(14, 92, 0),
    observer_facing: Facing::East,
    reset_plate: Some(TilePosition::new(14, 82, 0)),
};

pub const PAUSE_CHAMBER: GauntletRoom = GauntletRoom {
    name: "pause_chamber",
    origin: TilePosition::new(42, 78, 0),
    size: (16, 16),
    observer: TilePosition::new(50, 86, 0),
    observer_facing: Facing::North,
    reset_plate: Some(TilePosition::new(50, 77, 0)),
};

pub const DIALOGUE_ROOM: GauntletRoom = GauntletRoom {
    name: "dialogue_room",
    origin: TilePosition::new(36, 104, 0),
    size: (28, 20),
    observer: TilePosition::new(50, 114, 0),
    observer_facing: Facing::North,
    reset_plate: Some(TilePosition::new(50, 103, 0)),
};

pub const CROWD_PLAZA: GauntletRoom = GauntletRoom {
    name: "crowd_plaza",
    origin: TilePosition::new(80, 78, 0),
    size: (32, 32),
    observer: TilePosition::new(96, 94, 0),
    observer_facing: Facing::West,
    reset_plate: Some(TilePosition::new(80, 86, 0)),
};

/// All rooms in canonical spawn order.
/// THIS ORDER DETERMINES STABLEID ASSIGNMENT.
/// Do not reorder existing entries. Append new rooms at the end.
pub const ROOMS: &[GauntletRoom] = &[
    HUB,
    FOG_THEATER,
    OCCLUSION_CORRIDOR,
    INVENTORY_WAREHOUSE,
    INTERACTION_GALLERY,
    PAUSE_CHAMBER,
    DIALOGUE_ROOM,
    CROWD_PLAZA,
];

/// Look up which room a position falls in.
pub fn room_at(pos: &TilePosition) -> Option<&'static GauntletRoom> {
    ROOMS.iter().find(|r| {
        pos.x >= r.origin.x && pos.x < r.origin.x + r.size.0
            && pos.y >= r.origin.y && pos.y < r.origin.y + r.size.1
            && pos.z == r.origin.z
    })
}

Canonical spawn order (answer to R2-OQ-09): Rooms are spawned in the order listed in ROOMS. Within each room, entities are spawned in the order listed in the room's entity table (Section 3). This order is stable — appending new rooms or entities at the end does not change existing StableId assignments. Do not reorder.


8. Room Ordering and StableId Assignment

Entities receive StableIds in spawn order. The Gauntlet's spawn order is:

  1. Hub entities (signs): StableId 1-4
  2. Fog Theater entities: StableId 5-8
  3. Occlusion Corridor entities: StableId 9-12
  4. Inventory Warehouse entities: StableId 13-23 (10 crates + 1 NPC)
  5. Interaction Gallery entities: StableId 24-28
  6. Pause Chamber entities: StableId 29
  7. Dialogue Room entities: StableId 30-33
  8. Crowd Plaza entities: StableId 34-48

Total: 48 entities. Player entity gets its own StableId assignment (typically 0 or assigned by the bridge).

Golden file implications: Golden files reference entities by StableId. If spawn order changes, all golden files break. The constants module is the single source of truth — any golden file regeneration is triggered by changes to constants.rs.


9. Sprint 8 Implementation Priorities

What Dudley should build first, based on test dependency chains:

Priority What Depends On Enables
P0 --test-mode + --port 0 Nothing Layer 3 test, test client, all Gauntlet testing
P0 Gauntlet constants module (constants.rs) Nothing All room builders, golden files, test client room detection
P1 Hub + Pause Chamber builder constants.rs Pause guard tests (Bug #3 regression), first golden file
P1 4 determinism fixes (Fix A-D) Nothing INV-T01, golden file stability
P2 Occlusion Corridor builder constants.rs LOS tests, perception mode tests
P2 Inventory Warehouse builder constants.rs Inventory tests, T3 transition
P2 Interaction Gallery builder constants.rs Verb tests, T3 transition
P2 Fog Theater builder constants.rs Fog tests
P2 Dialogue Room builder constants.rs + KG injection Dialogue tests, contradiction
P2 Crowd Plaza builder constants.rs Density tests, determinism, tick budget
P3 Reset plate system Room builders Manual testing iteration
P3 Hub teleport handler constants.rs Manual testing efficiency
P3 Corridor + cross-cut geometry Room builders Cross-room transitions

Critical path: --test-mode → constants module → Hub + Pause Chamber → first golden file. Everything else can parallelize after that.


Summary

Deliverable Status
MVP room list (7 rooms + hub) with justification Complete
Physical map layout with ASCII art Complete
Coordinate table (rooms, corridors, cross-cuts) Complete
Entity placement per room (48 entities) Complete
Observer positions for golden files Complete
3 MVP cross-room transitions with test scripts Complete
Anti-tedium MVP (Reset Plate + Hub Teleport) Confirmed
Q2 answer: room reset vs full restart Complete
Q3 answer: 2 cross-cuts for MVP Complete
Q6 answer: cross-room checks at Gauntlet root YAML Complete
Constants module specification Complete
StableId assignment order Complete
Sprint 8 implementation priorities Complete