Standardized YAML frontmatter on all 20 files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
38 KiB
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).
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):
- Movement discarded while paused
- Unpause accepted while paused
- Stance toggle allowed while paused
- Interact allowed while paused
- Multiple movements in paused batch all discarded
- Pause → move (discarded) → unpause → move (accepted) roundtrip
- SetTickRate(Half) while paused behavior
- 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:
RoomResetTriggercomponent,RoomSnapshotsresource (tick-0 per room),execute_room_resetsystem - 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:
RoomResetTriggerentities 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:
- Hub entities (signs): StableId 1-4
- Fog Theater entities: StableId 5-8
- Occlusion Corridor entities: StableId 9-12
- Inventory Warehouse entities: StableId 13-23 (10 crates + 1 NPC)
- Interaction Gallery entities: StableId 24-28
- Pause Chamber entities: StableId 29
- Dialogue Room entities: StableId 30-33
- 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 |