# 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
