--- title: "Gestalt — Round 3: Final Gauntlet Map Specification" description: "Gestalt's final buildable Gauntlet map specification for sprint 8 implementation" type: workshop status: archived workshop: test-architecture agent: "gestalt" round: 3 created: 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). ### Room 4: Interaction Gallery 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. ### T3: Full Inventory Interact (Inventory → Interaction Gallery) **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. ```yaml # 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. ```rust // 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, // 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 |