3-round workshop with 7 agents (Tyre, Dudley, Stig, Hoshe, Justine, Gestalt, Ozzie) plus Qatux documenting. Produced: - 59-item prioritized test backlog (60 tickets under epic #455) - Gauntlet test world spec: 7 rooms + hub, 48 entities - Test client binary spec (tooling/test-client/) - Determinism fixes (3 patches, ~22 lines) - Server --test-mode + --port 0 design - Content cross-reference validation (9 checks) - make pre-pr pipeline (6-step) - 38 client tests prioritized - Anti-tedium features (reset plate, hub teleport, WRONG button) - Human tester walkthrough - CI pipeline design (deferred but documented) Sprint 8 scope: ~17.75 team-days across 26 tickets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
765 lines
37 KiB
Markdown
765 lines
37 KiB
Markdown
# 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<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 |
|