# Tavern Lobby Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Replace the flat 2D join UI (lobby.tscn) with an atmospheric 3D tavern scene where the join UI floats as a CanvasLayer overlay, and introduce a SceneManager Autoload that will handle all future scene transitions.

**Architecture:** A new `SceneManager` Autoload owns a `CurrentScene` node in `main.tscn` and swaps scenes into it via `transition_to(scene_name)`. The tavern scene is a `Node3D` with BoxMesh geometry, warm lighting, and fog, with the existing join UI ported to a `CanvasLayer` on top. The `game_started` signal is handled exclusively by `SceneManager` (not by `tavern.gd`).

**Tech Stack:** Godot 4.6 (GDScript), ENet MultiplayerAPI, existing `NetworkManager` Autoload

**Spec:** `docs/superpowers/specs/2026-04-14-tavern-lobby-design.md`

---

## File Map

| Action | File | Responsibility |
|---|---|---|
| Create | `ruf-der-pilze/scripts/scene_manager.gd` | Scene registry, transition logic, `game_started` handler |
| Create | `ruf-der-pilze/scenes/tavern.tscn` | 3D tavern geometry + CanvasLayer UI |
| Create | `ruf-der-pilze/scripts/tavern.gd` | Join/wait UI logic (ported from lobby.gd) |
| Modify | `ruf-der-pilze/project.godot` | Register SceneManager autoload |
| Modify | `ruf-der-pilze/scenes/main.tscn` | Add CurrentScene node, remove Lobby |
| Delete | `ruf-der-pilze/scenes/lobby.tscn` | Replaced by tavern.tscn |
| Delete | `ruf-der-pilze/scripts/lobby.gd` | Replaced by tavern.gd |

---

## Task 1: SceneManager Autoload

**Files:**
- Create: `ruf-der-pilze/scripts/scene_manager.gd`
- Modify: `ruf-der-pilze/project.godot`

- [ ] **Step 1: Create `scene_manager.gd`**

```gdscript
extends Node

const SCENES := {
	"tavern": "res://scenes/tavern.tscn",
	"chamber": "res://scenes/chamber.tscn",
	"entrance_hall": "res://scenes/entrance_hall.tscn",
	"refectory": "res://scenes/refectory.tscn",
	"library": "res://scenes/library.tscn",
	"chapel": "res://scenes/chapel.tscn",
	"cloister": "res://scenes/cloister.tscn",
	"sanctum": "res://scenes/sanctum.tscn",
}

var _current_scene_node: Node = null


func _ready() -> void:
	NetworkManager.game_started.connect(_on_game_started)
	# Defer until main.tscn and its CurrentScene node are in the tree.
	# call_deferred is not sufficient here — autoloads init before the main
	# scene. Connect to root.ready (fires once, after main scene is ready).
	get_tree().root.ready.connect(_on_root_ready, CONNECT_ONE_SHOT)


func _on_root_ready() -> void:
	var args := OS.get_cmdline_args() + OS.get_cmdline_user_args()
	var is_server := OS.has_feature("dedicated_server") or "--server" in args
	if not is_server:
		transition_to("tavern")


func transition_to(scene_name: String) -> void:
	var path: String = SCENES.get(scene_name, "")
	var packed: PackedScene = load(path) if path else null
	if packed == null:
		push_error("[SceneManager] Scene not found: %s" % scene_name)
		return
	var container := get_node_or_null("/root/Main/CurrentScene")
	if container == null:
		push_error("[SceneManager] CurrentScene node not found in main.tscn")
		return
	if _current_scene_node != null:
		_current_scene_node.queue_free()
		_current_scene_node = null
	_current_scene_node = packed.instantiate()
	container.add_child(_current_scene_node)


func _on_game_started() -> void:
	print("[SceneManager] Spiel gestartet — chamber transition kommt im nächsten Feature")


func _load_for_role(scene_name: String) -> void:
	# my_id is only valid after NetworkManager._on_connected_to_server fires.
	# Do not call before game_started — my_id will be 0 before that.
	var role: String = NetworkManager.players.get(NetworkManager.my_id, {}).get("role", "player")
	# TODO next feature: load dm variant when role == "dm"
	print("[SceneManager] Role %s → %s (stub, not switching yet)" % [role, scene_name])
```

- [ ] **Step 2: Register SceneManager in `project.godot`**

In the `[autoload]` section, add after the `NetworkManager` line:
```
SceneManager="*res://scripts/scene_manager.gd"
```

Result:
```ini
[autoload]

MCPGameBridge="res://addons/godot_mcp/game_bridge/mcp_game_bridge.gd"
NetworkManager="*res://scripts/network_manager.gd"
SceneManager="*res://scripts/scene_manager.gd"
```

- [ ] **Step 3: Smoke-test server mode**

Run:
```bash
cd ruf-der-pilze && godot --headless -- --server
```

Expected output (no errors, no scene loading):
```
[Server] Gestartet auf Port 4242
```
SceneManager must NOT print anything about tavern on server. If it does, the `is_server` guard is broken.

- [ ] **Step 4: Commit**

```bash
git add ruf-der-pilze/scripts/scene_manager.gd ruf-der-pilze/project.godot
git commit -m "feat: add SceneManager autoload with scene registry"
```

---

## Task 2: Update `main.tscn`

**Files:**
- Modify: `ruf-der-pilze/scenes/main.tscn`

Current content has a `[node name="Lobby"]` child and an `ext_resource` referencing `lobby.tscn`. Both must be removed. A `CurrentScene` node is added instead.

- [ ] **Step 1: Replace `main.tscn` content**

```
[gd_scene load_steps=1 format=3]

[ext_resource type="Script" path="res://scripts/main.gd" id="1"]

[node name="Main" type="Node"]
script = ExtResource("1")

[node name="CurrentScene" type="Node" parent="."]
```

> **Do NOT commit yet** — without `tavern.tscn` the game will crash. The commit happens at the end of Task 4 once the scene file exists.

---

## Task 3: Create `tavern.gd`

**Files:**
- Create: `ruf-der-pilze/scripts/tavern.gd`

Port from `lobby.gd` with two changes:
1. All node paths updated: `$JoinPanel` → `$CanvasLayer/JoinPanel` (etc.)
2. `NetworkManager.game_started` connection and `_on_game_started()` removed (SceneManager owns that signal)

- [ ] **Step 1: Create `tavern.gd`**

```gdscript
extends Node3D

var _local_role: String = ""
var _pending_player_name: String = ""


func _ready() -> void:
	$CanvasLayer/JoinPanel/RoleOption.add_item("Spieler")
	$CanvasLayer/JoinPanel/RoleOption.add_item("DM")

	$CanvasLayer/JoinPanel/JoinButton.pressed.connect(_on_join_pressed)
	$CanvasLayer/WaitPanel/StartButton.pressed.connect(_on_start_pressed)

	NetworkManager.connected_to_server.connect(_on_connected)
	NetworkManager.connection_failed.connect(_on_connection_failed)
	NetworkManager.player_joined.connect(_on_player_joined)
	NetworkManager.player_left.connect(_on_player_left)
	NetworkManager.player_list_synced.connect(_rebuild_player_list)


func _on_join_pressed() -> void:
	var player_name: String = ($CanvasLayer/JoinPanel/NameInput as LineEdit).text.strip_edges()
	if player_name.is_empty():
		return
	_local_role = "dm" if $CanvasLayer/JoinPanel/RoleOption.selected == 1 else "player"
	_pending_player_name = player_name
	NetworkManager.join_server("127.0.0.1", 4242)


func _on_connected() -> void:
	NetworkManager.register.rpc_id(1, _pending_player_name, _local_role)
	$CanvasLayer/JoinPanel.visible = false
	$CanvasLayer/WaitPanel.visible = true
	$CanvasLayer/WaitPanel/StartButton.visible = (_local_role == "dm")


func _on_connection_failed() -> void:
	push_error("[Tavern] Verbindung fehlgeschlagen")
	_local_role = ""
	_pending_player_name = ""


func _on_player_joined(_peer_id: int, _player_name: String, _role: String) -> void:
	_rebuild_player_list()


func _on_player_left(_peer_id: int) -> void:
	_rebuild_player_list()


func _rebuild_player_list() -> void:
	$CanvasLayer/WaitPanel/PlayerList.clear()
	for p in NetworkManager.players.values():
		$CanvasLayer/WaitPanel/PlayerList.add_item("%s (%s)" % [p.name, p.role])


func _on_start_pressed() -> void:
	NetworkManager.request_start_game.rpc_id(1)
```

> **Do NOT commit yet** — commit happens together with `tavern.tscn` in Task 4.

---

## Task 4: Create `tavern.tscn`

**Files:**
- Create: `ruf-der-pilze/scenes/tavern.tscn`

3D room (12 × 8 × 4 m) with warm candlelight and fog. UI ported from `lobby.tscn` into a `CanvasLayer`.

Room geometry:
- Floor: BoxMesh 12 × 0.1 × 8, at y = −0.05
- Ceiling: BoxMesh 12 × 0.1 × 8, at y = 4.05
- Back wall: BoxMesh 12 × 4 × 0.1, at z = −4
- Left wall: BoxMesh 0.1 × 4 × 8, at x = −6
- Right wall: BoxMesh 0.1 × 4 × 8, at x = 6

Camera: position (0, 3, 6), rotated −18° on X axis (looking slightly downward into the room).

- [ ] **Step 1: Create `tavern.tscn`**

```
[gd_scene load_steps=8 format=3]

[ext_resource type="Script" path="res://scripts/tavern.gd" id="1"]

[sub_resource type="Environment" id="Environment_1"]
background_mode = 1
background_color = Color(0.102, 0.059, 0, 1)
ambient_light_color = Color(0.6, 0.35, 0.1, 1)
ambient_light_energy = 0.4
fog_enabled = true
fog_light_color = Color(0.102, 0.059, 0, 1)
fog_density = 0.05

[sub_resource type="StandardMaterial3D" id="Material_1"]
albedo_color = Color(0.169, 0.102, 0.051, 1)

[sub_resource type="BoxMesh" id="BoxMesh_floor"]
size = Vector3(12, 0.1, 8)

[sub_resource type="BoxMesh" id="BoxMesh_ceiling"]
size = Vector3(12, 0.1, 8)

[sub_resource type="BoxMesh" id="BoxMesh_back"]
size = Vector3(12, 4, 0.1)

[sub_resource type="BoxMesh" id="BoxMesh_left"]
size = Vector3(0.1, 4, 8)

[sub_resource type="BoxMesh" id="BoxMesh_right"]
size = Vector3(0.1, 4, 8)

[node name="Tavern" type="Node3D"]
script = ExtResource("1")

[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.951, 0.309, 0, -0.309, 0.951, 0, 3, 6)
fov = 70.0
current = true

[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_1")

[node name="OmniLight3D" type="OmniLight3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 0)
light_color = Color(0.91, 0.643, 0.29, 1)
light_energy = 1.2
omni_range = 10.0

[node name="Floor" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0)
mesh = SubResource("BoxMesh_floor")
surface_material_override/0 = SubResource("Material_1")

[node name="Ceiling" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4.05, 0)
mesh = SubResource("BoxMesh_ceiling")
surface_material_override/0 = SubResource("Material_1")

[node name="WallBack" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, -4)
mesh = SubResource("BoxMesh_back")
surface_material_override/0 = SubResource("Material_1")

[node name="WallLeft" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 2, 0)
mesh = SubResource("BoxMesh_left")
surface_material_override/0 = SubResource("Material_1")

[node name="WallRight" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6, 2, 0)
mesh = SubResource("BoxMesh_right")
surface_material_override/0 = SubResource("Material_1")

[node name="CanvasLayer" type="CanvasLayer" parent="."]

[node name="JoinPanel" type="VBoxContainer" parent="CanvasLayer"]
offset_left = 300.0
offset_top = 200.0
offset_right = 700.0
offset_bottom = 500.0

[node name="Title" type="Label" parent="CanvasLayer/JoinPanel"]
text = "Ruf der Pilze"

[node name="NameInput" type="LineEdit" parent="CanvasLayer/JoinPanel"]
placeholder_text = "Dein Name"

[node name="RoleOption" type="OptionButton" parent="CanvasLayer/JoinPanel"]

[node name="JoinButton" type="Button" parent="CanvasLayer/JoinPanel"]
text = "Beitreten"

[node name="WaitPanel" type="VBoxContainer" parent="CanvasLayer"]
visible = false
offset_left = 300.0
offset_top = 200.0
offset_right = 700.0
offset_bottom = 500.0

[node name="WaitLabel" type="Label" parent="CanvasLayer/WaitPanel"]
text = "Warte auf Spieler..."

[node name="PlayerList" type="ItemList" parent="CanvasLayer/WaitPanel"]
custom_minimum_size = Vector2(0, 150)

[node name="StartButton" type="Button" parent="CanvasLayer/WaitPanel"]
text = "Spiel starten"
visible = false
```

> Note: If Godot logs a `load_steps` mismatch warning on first run, open the scene in the editor once — it will correct the value automatically on save.

- [ ] **Step 2: Commit all three files together** (main.tscn + tavern.gd + tavern.tscn form one working unit)

```bash
git add ruf-der-pilze/scenes/main.tscn ruf-der-pilze/scripts/tavern.gd ruf-der-pilze/scenes/tavern.tscn
git commit -m "feat: add tavern.tscn — 3D room with CanvasLayer UI overlay"
```

---

## Task 5: End-to-End Verification

- [ ] **Step 1: Run server**

```bash
cd ruf-der-pilze && godot --headless -- --server
```

Expected: `[Server] Gestartet auf Port 4242` — no 3D scene loading, no errors.

- [ ] **Step 2: Run client**

In a second terminal:
```bash
cd ruf-der-pilze && godot
```

Expected:
- Dark brown 3D room visible in background
- Warm orange lighting from center
- Fog visible toward the back wall
- JoinPanel (Name field, Role dropdown, "Beitreten" button) floating on top

- [ ] **Step 3: Join as player**

Enter a name, select "Spieler", click "Beitreten".

Expected:
- JoinPanel disappears
- WaitPanel appears with player list
- "Spiel starten" button is NOT visible (player role)
- Server console: `[Server] Peer verbunden: ...`

- [ ] **Step 4: Join as DM**

Open a third terminal, run another client. Enter a name, select "DM", join.

Expected:
- Both clients show updated player list
- DM client sees "Spiel starten" button

- [ ] **Step 5: Start game**

DM clicks "Spiel starten".

Expected (all consoles):
```
[SceneManager] Spiel gestartet — chamber transition kommt im nächsten Feature
```
No crash. No scene change yet (that's the next feature).

- [ ] **Step 6: Delete legacy files**

```bash
git rm ruf-der-pilze/scenes/lobby.tscn ruf-der-pilze/scripts/lobby.gd
```

- [ ] **Step 7: Re-run full verification** (repeat Steps 1–5 to confirm deletion didn't break anything)

- [ ] **Step 8: Update `docs/STATUS.md`**

Mark "Tavern Lobby" as done, add "Szenen-Wechsel (Chamber)" as next:

```markdown
### ✅ Abgeschlossen
- ...
- **Tavern Lobby** — 3D tavern replaces flat lobby UI; SceneManager Autoload introduced

### ⏳ Als nächstes
- **Szenen-Wechsel nach Spielstart** — game_started → transition to chamber/DM scene
  Plan: noch zu erstellen
```

- [ ] **Step 9: Archive plan, final commit**

```bash
mv docs/plans/2026-04-14-tavern-lobby-plan.md docs/plans/archive/
git add -A
git commit -m "feat: replace lobby with 3D tavern scene and SceneManager"
```
