docs: add Sprint 20 navigation integration spec and plan

This commit is contained in:
Steven Wroblewski
2026-05-10 20:50:13 +02:00
parent 1d65bf21dc
commit 43a7e6bde4
2 changed files with 446 additions and 0 deletions
@@ -0,0 +1,99 @@
# Sprint 20 — Navigation-Integration Design Spec
## Goal
Close the loop between RoomNavigator, GameState, and AudioManager. Three concrete bugs:
1. `AudioManager.DEFAULT_MUSIC_VOLUME` missing → HUD crashes on music toggle
2. `GameState.current_room` never updated during gameplay → floor music never switches
3. Camera not restored to saved room on game load
## Changes
### GameState
Add `set_current_room(room: String) -> void`:
```gdscript
func set_current_room(room: String) -> void:
current_room = room
state_changed.emit()
```
GameState already persists `current_room` in `get_save_data()` / `apply_save_data()`. No version bump needed — structure unchanged.
### RoomNavigator
Add room name lookup and call `GameState.set_current_room()` on every navigation:
```gdscript
const _ROOM_NAMES: Dictionary = {
Vector2i(0, 0): "reception",
Vector2i(0, 1): "giftshop",
Vector2i(0, 2): "restaurant",
Vector2i(0, 3): "emergency",
Vector2i(1, 0): "xray",
Vector2i(1, 1): "pharmacy",
Vector2i(1, 2): "lab",
Vector2i(1, 3): "patient_rooms",
Vector2i(2, 0): "ultrasound",
Vector2i(2, 1): "delivery_room",
Vector2i(2, 2): "nursery",
}
const HOME_ROOM_NAME: String = "garden_party"
```
`go_to_room(floor_index, room_index)` → after setting internal state, call `GameState.set_current_room(_ROOM_NAMES.get(Vector2i(floor_index, room_index), ""))`.
`go_to_home()``GameState.set_current_room(HOME_ROOM_NAME)`.
`go_to_hospital()``GameState.set_current_room(_ROOM_NAMES.get(Vector2i(_current_floor, _current_room), ""))`.
Add public `get_room_name(floor_index: int, room_index: int) -> String` — returns the room name or `""`.
Add `go_to_room_by_name(room_name: String) -> void` — reverse lookup: if `room_name == HOME_ROOM_NAME`, call `go_to_home()`; otherwise search `_ROOM_NAMES` for matching value and call `go_to_room()`.
### AudioManager
Add missing constant:
```gdscript
const DEFAULT_MUSIC_VOLUME: float = 0.6
```
No other changes — the existing `_on_game_state_changed()` observer already handles room-based music switching correctly once `GameState.set_current_room()` is wired up.
### main.gd
After `SaveManager.load_game()`, restore camera to the saved room:
```gdscript
RoomNavigator.go_to_room_by_name(GameState.current_room)
```
(Called without tween wait — camera teleports to saved position on startup, no animation.)
To suppress the cross-fade animation on startup (no music cross-fade before the initial room is set), `go_to_room_by_name` passes a flag or AudioManager's `_current_floor` is -1 so the first `play_floor_music` call is treated as initial — this already works correctly since `_current_floor` starts at -1.
## Testing
**`test/unit/test_game_state.gd`** — append:
- `test_set_current_room_updates_value` — calls `set_current_room("xray")`, asserts `current_room == "xray"`
- `test_set_current_room_emits_state_changed` — connects to `state_changed`, calls `set_current_room`, verifies signal fired
**`test/unit/test_room_navigator.gd`** — new file:
- `test_room_names_dict_has_eleven_entries`
- `test_get_room_name_floor0_room0_is_reception`
- `test_get_room_name_floor1_room2_is_lab`
- `test_get_room_name_floor2_room1_is_delivery_room`
- `test_get_room_name_unknown_returns_empty`
- `test_go_to_room_by_name_sets_home_for_garden_party` — verifies `is_at_home()` after call
- `test_go_to_room_by_name_unknown_is_noop` — no crash, no state change
**`test/unit/test_audio_manager.gd`** — append:
- `test_default_music_volume_constant_is_0_6` — asserts `AudioManager.DEFAULT_MUSIC_VOLUME == 0.6`
## Out of Scope
- Camera pan animation on game restore (instant teleport is sufficient)
- Floor 2 home button wiring (navigation_arrow for garden already exists in Main.tscn)
- Room-within-floor persistence beyond the existing `current_room` string