Files
Cozypaw-Hospital/docs/superpowers/plans/2026-04-17-sprint-11-13-zweites-obergeschoss.md
2026-04-17 16:28:32 +02:00

894 lines
31 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sprint 11-13: 2. Obergeschoss — 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:** Three fully interactive second-floor rooms (Ultrasound with continuous heartbeat animation, Delivery Room with mama-arrives animation, Nursery with rocking cradles) wired into Main.tscn.
**Architecture:** Floor 2 follows the same side-by-side layout — three rooms at X offsets 0, 1280, 2560 within the `Floor2` node (Floor 2 has only 3 rooms, unlike Floors 0 and 1 which have 4). Two new animated components are introduced: `UltrasoundMachine` (continuous looping heartbeat tween) and `DeliveryBed` (4-state FSM: mama slides in, baby fades in, tap again to reset). `Cradle` adds a rocking rotation animation on tap. `Main.tscn` is updated to replace the Floor 2 placeholder nodes with the real rooms.
**Tech Stack:** Godot 4.6.2, GDScript (static typing), existing `InteractiveObject` base class, placeholder ColorRect graphics.
---
## File Map
| Action | File | Responsibility |
|--------|------|----------------|
| Create | `scripts/objects/ultrasound_machine.gd` | Continuous heartbeat pulse animation (looping tween, no tap required) |
| Create | `scenes/objects/UltrasoundMachine.tscn` | Machine body + screen + pulsing HeartbeatDot Node2D |
| Create | `scripts/objects/delivery_bed.gd` | 4-state FSM: IDLE → MAMA_ARRIVING → COMPLETE → RESETTING |
| Create | `scenes/objects/DeliveryBed.tscn` | Bed frame + Mama Node2D (slides in) + Baby Node2D (fades in) |
| Create | `scripts/objects/cradle.gd` | Rocking rotation animation on tap (2-state FSM) |
| Create | `scenes/objects/Cradle.tscn` | Hanging cradle with suspension ropes and basket |
| Create | `scenes/rooms/floor2/Ultrasound.tscn` | Exam table, UltrasoundMachine, 2 interactive objects |
| Create | `scenes/rooms/floor2/DeliveryRoom.tscn` | DeliveryBed component, 2 interactive objects |
| Create | `scenes/rooms/floor2/Nursery.tscn` | Rail + 3 Cradle instances, 2 interactive objects |
| Modify | `scenes/main/Main.tscn` | Replace Floor2 placeholder with 3 rooms + nav arrows; fix elevator position |
**Room layout in Floor2 (local X positions):**
- Ultrasound: x=0 (room index 0) — camera center world (640, -1080)
- DeliveryRoom: x=1280 (room index 1) — camera center world (1920, -1080)
- Nursery: x=2560 (room index 2) — camera center world (3200, -1080)
- *(No room 3 — Floor 2 has only 3 rooms)*
**Floor2 elevator (top floor — no up button):**
- ElevatorDown2: position (1100, 540) → target_floor = 1
**NavigationArrows in Floor2 (4 total for 2 boundaries):**
| Node | X | Y | target_floor | target_room | label |
|------|---|---|---|---|---|
| NavRight0to1_F2 | 1200 | 480 | 2 | 1 | → |
| NavLeft1to0_F2 | 1340 | 480 | 2 | 0 | ← |
| NavRight1to2_F2 | 2480 | 480 | 2 | 2 | → |
| NavLeft2to1_F2 | 2600 | 480 | 2 | 1 | ← |
**UltrasoundMachine visual layout (local space, origin = machine base center):**
- MachineBody: grey ColorRect 80×160 at (-40, -160) — tall standing machine
- ScreenFrame: dark ColorRect 68×52 at (-34, -152) — 4px border around screen
- Screen: very dark ColorRect 60×44 at (-30, -148) — display area
- HeartbeatDot: Node2D at (0, -126) — center of Screen; scales up/down for pulse
- HeartbeatShape: bright green ColorRect 12×12 at (-6, -6) child of HeartbeatDot
- Probe: grey ColorRect 14×80 at (-7, -30) — ultrasound probe below arm
**DeliveryBed visual layout (local space, origin = center of bed frame):**
- BedFrame: ColorRect 260×50 at (-130, -50) — bed surface
- BedPillow: ColorRect 80×50 at (-130, -100) — head of bed
- BedLeg1: ColorRect 14×120 at (-120, 0)
- BedLeg2: ColorRect 14×120 at (106, 0)
- Mama: Node2D starts at (-600, 0), parks at (-100, 0)
- MamaBody: ColorRect 60×100 at (-30, -100) child of Mama
- MamaHead: ColorRect 50×50 at (-25, -150) child of Mama
- Baby: Node2D at (0, -80), starts invisible (modulate.a = 0.0)
- BabyBody: ColorRect 40×30 at (-20, -15) child of Baby
**Cradle visual layout (local space, origin = top attachment point, pivot for rocking):**
- SuspensionLeft: ColorRect 4×50 at (-25, 0) — rope going down-left
- SuspensionRight: ColorRect 4×50 at (21, 0) — rope going down-right
- CradleBasket: ColorRect 70×40 at (-35, 50) — cradle body
- CradleRim: ColorRect 70×8 at (-35, 46) — top rim of basket
- BabyBlanket: ColorRect 58×22 at (-29, 56) — blanket inside basket
---
## Task 1: UltrasoundMachine component
**Files:**
- Create: `scripts/objects/ultrasound_machine.gd`
- Create: `scenes/objects/UltrasoundMachine.tscn`
The heartbeat runs automatically on scene load — no tap required. The HeartbeatDot Node2D scales up then back down on a looping tween.
- [ ] **Step 1: Create ultrasound_machine.gd**
```gdscript
## UltrasoundMachine — displays a continuous heartbeat pulse on the screen.
class_name UltrasoundMachine extends Node2D
const BEAT_RISE_DURATION: float = 0.12
const BEAT_FALL_DURATION: float = 0.12
const BEAT_INTERVAL: float = 0.60
const BEAT_SCALE_PEAK: Vector2 = Vector2(1.5, 1.5)
const BEAT_SCALE_REST: Vector2 = Vector2(1.0, 1.0)
func _ready() -> void:
_start_heartbeat_loop()
func _start_heartbeat_loop() -> void:
var dot: Node2D = get_node_or_null("HeartbeatDot") as Node2D
if dot == null:
return
var tween: Tween = create_tween()
tween.set_loops()
tween.tween_property(dot, "scale", BEAT_SCALE_PEAK, BEAT_RISE_DURATION)
tween.tween_property(dot, "scale", BEAT_SCALE_REST, BEAT_FALL_DURATION)
tween.tween_interval(BEAT_INTERVAL)
```
- [ ] **Step 2: Create UltrasoundMachine.tscn**
```
[gd_scene load_steps=2 format=3 uid="uid://cozypaw_ultrasoundmachine"]
[ext_resource type="Script" path="res://scripts/objects/ultrasound_machine.gd" id="1_ultrasound"]
[node name="UltrasoundMachine" type="Node2D"]
script = ExtResource("1_ultrasound")
[node name="MachineBody" type="ColorRect" parent="."]
color = Color(0.68, 0.70, 0.76, 1)
size = Vector2(80, 160)
position = Vector2(-40, -160)
[node name="ScreenFrame" type="ColorRect" parent="."]
color = Color(0.30, 0.32, 0.36, 1)
size = Vector2(68, 52)
position = Vector2(-34, -152)
[node name="Screen" type="ColorRect" parent="."]
color = Color(0.06, 0.08, 0.12, 1)
size = Vector2(60, 44)
position = Vector2(-30, -148)
[node name="HeartbeatDot" type="Node2D" parent="."]
position = Vector2(0, -126)
[node name="HeartbeatShape" type="ColorRect" parent="HeartbeatDot"]
color = Color(0.40, 0.95, 0.60, 1)
size = Vector2(12, 12)
position = Vector2(-6, -6)
[node name="Probe" type="ColorRect" parent="."]
color = Color(0.55, 0.57, 0.62, 1)
size = Vector2(14, 80)
position = Vector2(-7, -30)
```
- [ ] **Step 3: Commit**
```bash
cd "F:/Development/_gameDev/Cozypaw-Hospital"
git add scripts/objects/ultrasound_machine.gd scenes/objects/UltrasoundMachine.tscn
git commit -m "feat(ultrasound): add UltrasoundMachine component with continuous heartbeat animation"
```
---
## Task 2: DeliveryBed component
**Files:**
- Create: `scripts/objects/delivery_bed.gd`
- Create: `scenes/objects/DeliveryBed.tscn`
Tap the bed → Mama slides in from left → Baby fades in on the bed. Tap again → Mama slides out, Baby fades out. Tapping during animation is ignored.
- [ ] **Step 1: Create delivery_bed.gd**
```gdscript
## DeliveryBed — tap to animate mama arriving and baby appearing; tap again to reset.
class_name DeliveryBed extends Node2D
enum State { IDLE, MAMA_ARRIVING, COMPLETE, RESETTING }
const MAMA_OFFSCREEN_X: float = -600.0
const MAMA_PARKED_X: float = -100.0
const ARRIVE_DURATION: float = 1.0
const BABY_FADE_DURATION: float = 0.6
const RESET_DURATION: float = 0.8
const BUTTON_HALF_WIDTH: float = 120.0
const BUTTON_HALF_HEIGHT: float = 40.0
var _state: State = State.IDLE
func _ready() -> void:
var mama: Node2D = get_node_or_null("Mama") as Node2D
if mama != null:
mama.position.x = MAMA_OFFSCREEN_X
var baby: Node2D = get_node_or_null("Baby") as Node2D
if baby != null:
baby.modulate.a = 0.0
func _input(event: InputEvent) -> void:
if _state == State.MAMA_ARRIVING or _state == State.RESETTING:
return
var screen_pos: Vector2
if event is InputEventScreenTouch and event.pressed:
screen_pos = event.position
elif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
screen_pos = event.position
else:
return
var canvas_transform: Transform2D = get_viewport().get_canvas_transform()
var world_pos: Vector2 = canvas_transform.affine_inverse() * screen_pos
var local_pos: Vector2 = to_local(world_pos)
if abs(local_pos.x) <= BUTTON_HALF_WIDTH and abs(local_pos.y) <= BUTTON_HALF_HEIGHT:
if _state == State.IDLE:
_start_arrival()
elif _state == State.COMPLETE:
_start_reset()
func _start_arrival() -> void:
_state = State.MAMA_ARRIVING
var mama: Node2D = get_node_or_null("Mama") as Node2D
if mama == null:
_state = State.IDLE
return
var tween: Tween = create_tween()
tween.set_ease(Tween.EASE_OUT)
tween.set_trans(Tween.TRANS_QUAD)
tween.tween_property(mama, "position:x", MAMA_PARKED_X, ARRIVE_DURATION)
tween.finished.connect(_on_mama_arrived)
func _on_mama_arrived() -> void:
_state = State.COMPLETE
var baby: Node2D = get_node_or_null("Baby") as Node2D
if baby == null:
return
var tween: Tween = create_tween()
tween.tween_property(baby, "modulate:a", 1.0, BABY_FADE_DURATION)
func _start_reset() -> void:
_state = State.RESETTING
var baby: Node2D = get_node_or_null("Baby") as Node2D
if baby != null:
var fade_tween: Tween = create_tween()
fade_tween.tween_property(baby, "modulate:a", 0.0, BABY_FADE_DURATION)
var mama: Node2D = get_node_or_null("Mama") as Node2D
if mama == null:
_state = State.IDLE
return
var slide_tween: Tween = create_tween()
slide_tween.set_ease(Tween.EASE_IN)
slide_tween.set_trans(Tween.TRANS_QUAD)
slide_tween.tween_property(mama, "position:x", MAMA_OFFSCREEN_X, RESET_DURATION)
slide_tween.finished.connect(func() -> void: _state = State.IDLE)
```
- [ ] **Step 2: Create DeliveryBed.tscn**
```
[gd_scene load_steps=2 format=3 uid="uid://cozypaw_deliverybed"]
[ext_resource type="Script" path="res://scripts/objects/delivery_bed.gd" id="1_deliverybed"]
[node name="DeliveryBed" type="Node2D"]
script = ExtResource("1_deliverybed")
[node name="BedFrame" type="ColorRect" parent="."]
color = Color(0.85, 0.85, 0.90, 1)
size = Vector2(260, 50)
position = Vector2(-130, -50)
[node name="BedPillow" type="ColorRect" parent="."]
color = Color(0.96, 0.96, 1.0, 1)
size = Vector2(80, 50)
position = Vector2(-130, -100)
[node name="BedLeg1" type="ColorRect" parent="."]
color = Color(0.70, 0.70, 0.76, 1)
size = Vector2(14, 120)
position = Vector2(-120, 0)
[node name="BedLeg2" type="ColorRect" parent="."]
color = Color(0.70, 0.70, 0.76, 1)
size = Vector2(14, 120)
position = Vector2(106, 0)
[node name="Mama" type="Node2D" parent="."]
position = Vector2(-600, 0)
[node name="MamaBody" type="ColorRect" parent="Mama"]
color = Color(0.92, 0.75, 0.65, 1)
size = Vector2(60, 100)
position = Vector2(-30, -100)
[node name="MamaHead" type="ColorRect" parent="Mama"]
color = Color(0.92, 0.75, 0.65, 1)
size = Vector2(50, 50)
position = Vector2(-25, -150)
[node name="Baby" type="Node2D" parent="."]
position = Vector2(0, -80)
[node name="BabyBody" type="ColorRect" parent="Baby"]
color = Color(0.96, 0.88, 0.82, 1)
size = Vector2(40, 30)
position = Vector2(-20, -15)
```
- [ ] **Step 3: Commit**
```bash
cd "F:/Development/_gameDev/Cozypaw-Hospital"
git add scripts/objects/delivery_bed.gd scenes/objects/DeliveryBed.tscn
git commit -m "feat(delivery): add DeliveryBed component with mama-arrives baby-appears animation"
```
---
## Task 3: Cradle component
**Files:**
- Create: `scripts/objects/cradle.gd`
- Create: `scenes/objects/Cradle.tscn`
Tap the cradle → it rocks (rotates ±15°). The origin is at the top attachment point, so rotation creates a natural pendulum swing. Tapping during rocking is ignored.
- [ ] **Step 1: Create cradle.gd**
```gdscript
## Cradle — rocks with a pendulum rotation when tapped.
class_name Cradle extends Node2D
enum State { IDLE, ROCKING }
const ROCK_ANGLE: float = 15.0
const ROCK_DURATION: float = 0.5
const BUTTON_HALF_SIZE: float = 80.0
var _state: State = State.IDLE
func _input(event: InputEvent) -> void:
if _state != State.IDLE:
return
var screen_pos: Vector2
if event is InputEventScreenTouch and event.pressed:
screen_pos = event.position
elif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
screen_pos = event.position
else:
return
var canvas_transform: Transform2D = get_viewport().get_canvas_transform()
var world_pos: Vector2 = canvas_transform.affine_inverse() * screen_pos
var local_pos: Vector2 = to_local(world_pos)
if abs(local_pos.x) <= BUTTON_HALF_SIZE and abs(local_pos.y) <= BUTTON_HALF_SIZE:
_start_rocking()
func _start_rocking() -> void:
_state = State.ROCKING
var tween: Tween = create_tween()
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_SINE)
tween.tween_property(self, "rotation_degrees", ROCK_ANGLE, ROCK_DURATION)
tween.tween_property(self, "rotation_degrees", -ROCK_ANGLE, ROCK_DURATION)
tween.tween_property(self, "rotation_degrees", 0.0, ROCK_DURATION * 0.5)
tween.finished.connect(func() -> void: _state = State.IDLE)
```
- [ ] **Step 2: Create Cradle.tscn**
```
[gd_scene load_steps=2 format=3 uid="uid://cozypaw_cradle"]
[ext_resource type="Script" path="res://scripts/objects/cradle.gd" id="1_cradle"]
[node name="Cradle" type="Node2D"]
script = ExtResource("1_cradle")
[node name="SuspensionLeft" type="ColorRect" parent="."]
color = Color(0.75, 0.65, 0.50, 1)
size = Vector2(4, 50)
position = Vector2(-25, 0)
[node name="SuspensionRight" type="ColorRect" parent="."]
color = Color(0.75, 0.65, 0.50, 1)
size = Vector2(4, 50)
position = Vector2(21, 0)
[node name="CradleBasket" type="ColorRect" parent="."]
color = Color(0.88, 0.82, 0.72, 1)
size = Vector2(70, 40)
position = Vector2(-35, 50)
[node name="CradleRim" type="ColorRect" parent="."]
color = Color(0.95, 0.90, 0.82, 1)
size = Vector2(70, 8)
position = Vector2(-35, 46)
[node name="BabyBlanket" type="ColorRect" parent="."]
color = Color(0.90, 0.84, 0.96, 1)
size = Vector2(58, 22)
position = Vector2(-29, 56)
```
- [ ] **Step 3: Commit**
```bash
cd "F:/Development/_gameDev/Cozypaw-Hospital"
git add scripts/objects/cradle.gd scenes/objects/Cradle.tscn
git commit -m "feat(nursery): add Cradle component with pendulum rocking animation"
```
---
## Task 4: Ultrasound room scene
**Files:**
- Create: `scenes/rooms/floor2/Ultrasound.tscn`
- [ ] **Step 1: Create Ultrasound.tscn**
```
[gd_scene load_steps=3 format=3 uid="uid://cozypaw_ultrasound"]
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
[ext_resource type="PackedScene" path="res://scenes/objects/UltrasoundMachine.tscn" id="2_ultrasound"]
[node name="Ultrasound" type="Node2D"]
[node name="Background" type="ColorRect" parent="."]
color = Color(0.92, 0.90, 0.96, 1)
size = Vector2(1280, 720)
position = Vector2(0, 0)
[node name="Floor" type="ColorRect" parent="."]
color = Color(0.80, 0.78, 0.84, 1)
size = Vector2(1280, 100)
position = Vector2(0, 620)
[node name="WallLeft" type="ColorRect" parent="."]
color = Color(0.90, 0.88, 0.94, 1)
size = Vector2(40, 620)
position = Vector2(0, 0)
[node name="WallRight" type="ColorRect" parent="."]
color = Color(0.90, 0.88, 0.94, 1)
size = Vector2(40, 620)
position = Vector2(1240, 0)
[node name="ExamTable" type="ColorRect" parent="."]
color = Color(0.84, 0.86, 0.90, 1)
size = Vector2(340, 40)
position = Vector2(300, 490)
[node name="ExamTableLeg1" type="ColorRect" parent="."]
color = Color(0.65, 0.68, 0.74, 1)
size = Vector2(16, 130)
position = Vector2(320, 530)
[node name="ExamTableLeg2" type="ColorRect" parent="."]
color = Color(0.65, 0.68, 0.74, 1)
size = Vector2(16, 130)
position = Vector2(608, 530)
[node name="CharacterSpawn" type="Marker2D" parent="."]
position = Vector2(160, 490)
[node name="UltrasoundMachine" parent="." instance=ExtResource("2_ultrasound")]
position = Vector2(780, 490)
[node name="GelBottle" parent="." instance=ExtResource("1_iobj")]
position = Vector2(920, 450)
[node name="Blanket" parent="." instance=ExtResource("1_iobj")]
position = Vector2(480, 450)
```
- [ ] **Step 2: Commit**
```bash
cd "F:/Development/_gameDev/Cozypaw-Hospital"
git add scenes/rooms/floor2/Ultrasound.tscn
git commit -m "feat(ultrasound): add Ultrasound room with exam table and UltrasoundMachine"
```
---
## Task 5: DeliveryRoom scene
**Files:**
- Create: `scenes/rooms/floor2/DeliveryRoom.tscn`
- [ ] **Step 1: Create DeliveryRoom.tscn**
```
[gd_scene load_steps=3 format=3 uid="uid://cozypaw_deliveryroom"]
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
[ext_resource type="PackedScene" path="res://scenes/objects/DeliveryBed.tscn" id="2_deliverybed"]
[node name="DeliveryRoom" type="Node2D"]
[node name="Background" type="ColorRect" parent="."]
color = Color(0.98, 0.92, 0.94, 1)
size = Vector2(1280, 720)
position = Vector2(0, 0)
[node name="Floor" type="ColorRect" parent="."]
color = Color(0.90, 0.82, 0.76, 1)
size = Vector2(1280, 100)
position = Vector2(0, 620)
[node name="WallLeft" type="ColorRect" parent="."]
color = Color(0.96, 0.90, 0.92, 1)
size = Vector2(40, 620)
position = Vector2(0, 0)
[node name="WallRight" type="ColorRect" parent="."]
color = Color(0.96, 0.90, 0.92, 1)
size = Vector2(40, 620)
position = Vector2(1240, 0)
[node name="CharacterSpawn" type="Marker2D" parent="."]
position = Vector2(160, 490)
[node name="DeliveryBed" parent="." instance=ExtResource("2_deliverybed")]
position = Vector2(540, 490)
[node name="FlowerVase" parent="." instance=ExtResource("1_iobj")]
position = Vector2(1020, 540)
[node name="BabyBlanket" parent="." instance=ExtResource("1_iobj")]
position = Vector2(880, 540)
```
- [ ] **Step 2: Commit**
```bash
cd "F:/Development/_gameDev/Cozypaw-Hospital"
git add scenes/rooms/floor2/DeliveryRoom.tscn
git commit -m "feat(delivery): add DeliveryRoom with animated DeliveryBed"
```
---
## Task 6: Nursery scene
**Files:**
- Create: `scenes/rooms/floor2/Nursery.tscn`
The CradleRail is a horizontal bar that the cradles visually hang from. The three Cradle origins (top attachment points) are positioned at y=240, just below the rail bottom edge (rail top at y=228, size.y=12 → bottom at y=240).
- [ ] **Step 1: Create Nursery.tscn**
```
[gd_scene load_steps=3 format=3 uid="uid://cozypaw_nursery"]
[ext_resource type="PackedScene" path="res://scenes/objects/InteractiveObject.tscn" id="1_iobj"]
[ext_resource type="PackedScene" path="res://scenes/objects/Cradle.tscn" id="2_cradle"]
[node name="Nursery" type="Node2D"]
[node name="Background" type="ColorRect" parent="."]
color = Color(0.96, 0.96, 0.84, 1)
size = Vector2(1280, 720)
position = Vector2(0, 0)
[node name="Floor" type="ColorRect" parent="."]
color = Color(0.88, 0.84, 0.70, 1)
size = Vector2(1280, 100)
position = Vector2(0, 620)
[node name="WallLeft" type="ColorRect" parent="."]
color = Color(0.94, 0.94, 0.82, 1)
size = Vector2(40, 620)
position = Vector2(0, 0)
[node name="WallRight" type="ColorRect" parent="."]
color = Color(0.94, 0.94, 0.82, 1)
size = Vector2(40, 620)
position = Vector2(1240, 0)
[node name="CradleRail" type="ColorRect" parent="."]
color = Color(0.75, 0.65, 0.50, 1)
size = Vector2(800, 12)
position = Vector2(200, 228)
[node name="CharacterSpawn" type="Marker2D" parent="."]
position = Vector2(160, 490)
[node name="Cradle1" parent="." instance=ExtResource("2_cradle")]
position = Vector2(340, 240)
[node name="Cradle2" parent="." instance=ExtResource("2_cradle")]
position = Vector2(600, 240)
[node name="Cradle3" parent="." instance=ExtResource("2_cradle")]
position = Vector2(860, 240)
[node name="TeddyBear" parent="." instance=ExtResource("1_iobj")]
position = Vector2(1100, 540)
[node name="MilkBottle" parent="." instance=ExtResource("1_iobj")]
position = Vector2(960, 540)
```
- [ ] **Step 2: Commit**
```bash
cd "F:/Development/_gameDev/Cozypaw-Hospital"
git add scenes/rooms/floor2/Nursery.tscn
git commit -m "feat(nursery): add Nursery room with rail and three rocking cradles"
```
---
## Task 7: Update Main.tscn — wire up Floor 2
**Files:**
- Modify: `scenes/main/Main.tscn`
**Changes from current file:**
1. `load_steps` increases from 17 → 20 (add 3 new ext_resources: Ultrasound, DeliveryRoom, Nursery)
2. Add 3 new ext_resource declarations (ids 1618)
3. In the Floor2 section: **remove** the placeholder `Background`, `FloorLabel`, `FloorRect` nodes
4. **Add** 3 room instances to Floor2 at x offsets 0, 1280, 2560 *(no room 3)*
5. **Move** ElevatorDown2 from (1200, 360) → (1100, 540) *(consistent with Floor1 pattern)*
6. **Add** 4 NavigationArrow instances for Floor2 room navigation *(only 2 boundaries, not 3)*
**load_steps arithmetic:** 15 existing ext_resources + 3 new = 18 ext_resources + 1 sub_resource + 1 scene root = 20.
- [ ] **Step 1: Read current Main.tscn**
Read `F:/Development/_gameDev/Cozypaw-Hospital/scenes/main/Main.tscn` to confirm current state before writing.
- [ ] **Step 2: Write the complete new Main.tscn**
```
[gd_scene load_steps=20 format=3 uid="uid://cozypaw_main"]
[ext_resource type="Script" path="res://scripts/main/main.gd" id="1_main"]
[ext_resource type="PackedScene" path="res://scenes/rooms/floor0/Reception.tscn" id="2_reception"]
[ext_resource type="PackedScene" path="res://scenes/characters/Character.tscn" id="3_char"]
[ext_resource type="PackedScene" path="res://scenes/ui/HUD.tscn" id="4_hud"]
[ext_resource type="PackedScene" path="res://scenes/ui/SettingsMenu.tscn" id="5_settings"]
[ext_resource type="PackedScene" path="res://scenes/objects/ElevatorButton.tscn" id="6_elevbtn"]
[ext_resource type="Script" path="res://scripts/characters/character_data.gd" id="7_chardata"]
[ext_resource type="PackedScene" path="res://scenes/rooms/floor0/GiftShop.tscn" id="8_giftshop"]
[ext_resource type="PackedScene" path="res://scenes/rooms/floor0/Restaurant.tscn" id="9_restaurant"]
[ext_resource type="PackedScene" path="res://scenes/rooms/floor0/EmergencyRoom.tscn" id="10_emergency"]
[ext_resource type="PackedScene" path="res://scenes/objects/NavigationArrow.tscn" id="11_navarrow"]
[ext_resource type="PackedScene" path="res://scenes/rooms/floor1/XRay.tscn" id="12_xray"]
[ext_resource type="PackedScene" path="res://scenes/rooms/floor1/Pharmacy.tscn" id="13_pharmacy"]
[ext_resource type="PackedScene" path="res://scenes/rooms/floor1/Lab.tscn" id="14_lab"]
[ext_resource type="PackedScene" path="res://scenes/rooms/floor1/PatientRoom.tscn" id="15_patientroom"]
[ext_resource type="PackedScene" path="res://scenes/rooms/floor2/Ultrasound.tscn" id="16_ultrasound"]
[ext_resource type="PackedScene" path="res://scenes/rooms/floor2/DeliveryRoom.tscn" id="17_delivery"]
[ext_resource type="PackedScene" path="res://scenes/rooms/floor2/Nursery.tscn" id="18_nursery"]
[sub_resource type="Resource" id="CharacterData_bunny1"]
script = ExtResource("7_chardata")
id = "bunny_01"
display_name = "Bunny"
species = 0
state = 0
current_floor = 0
position = Vector2(0, 0)
[node name="Main" type="Node2D"]
script = ExtResource("1_main")
[node name="Camera2D" type="Camera2D" parent="."]
position = Vector2(640, 360)
zoom = Vector2(1, 1)
[node name="Hospital" type="Node2D" parent="."]
[node name="Floor0" type="Node2D" parent="Hospital"]
position = Vector2(0, 0)
[node name="Reception" parent="Hospital/Floor0" instance=ExtResource("2_reception")]
position = Vector2(0, 0)
[node name="GiftShop" parent="Hospital/Floor0" instance=ExtResource("8_giftshop")]
position = Vector2(1280, 0)
[node name="Restaurant" parent="Hospital/Floor0" instance=ExtResource("9_restaurant")]
position = Vector2(2560, 0)
[node name="EmergencyRoom" parent="Hospital/Floor0" instance=ExtResource("10_emergency")]
position = Vector2(3840, 0)
[node name="ElevatorUp0" parent="Hospital/Floor0" instance=ExtResource("6_elevbtn")]
position = Vector2(1100, 160)
target_floor = 1
[node name="NavRight0to1" parent="Hospital/Floor0" instance=ExtResource("11_navarrow")]
position = Vector2(1200, 480)
target_floor = 0
target_room = 1
label_text = "→"
[node name="NavLeft1to0" parent="Hospital/Floor0" instance=ExtResource("11_navarrow")]
position = Vector2(1340, 480)
target_floor = 0
target_room = 0
label_text = "←"
[node name="NavRight1to2" parent="Hospital/Floor0" instance=ExtResource("11_navarrow")]
position = Vector2(2480, 480)
target_floor = 0
target_room = 2
label_text = "→"
[node name="NavLeft2to1" parent="Hospital/Floor0" instance=ExtResource("11_navarrow")]
position = Vector2(2600, 480)
target_floor = 0
target_room = 1
label_text = "←"
[node name="NavRight2to3" parent="Hospital/Floor0" instance=ExtResource("11_navarrow")]
position = Vector2(3760, 480)
target_floor = 0
target_room = 3
label_text = "→"
[node name="NavLeft3to2" parent="Hospital/Floor0" instance=ExtResource("11_navarrow")]
position = Vector2(3920, 480)
target_floor = 0
target_room = 2
label_text = "←"
[node name="Floor1" type="Node2D" parent="Hospital"]
position = Vector2(0, -720)
[node name="XRay" parent="Hospital/Floor1" instance=ExtResource("12_xray")]
position = Vector2(0, 0)
[node name="Pharmacy" parent="Hospital/Floor1" instance=ExtResource("13_pharmacy")]
position = Vector2(1280, 0)
[node name="Lab" parent="Hospital/Floor1" instance=ExtResource("14_lab")]
position = Vector2(2560, 0)
[node name="PatientRoom" parent="Hospital/Floor1" instance=ExtResource("15_patientroom")]
position = Vector2(3840, 0)
[node name="ElevatorDown1" parent="Hospital/Floor1" instance=ExtResource("6_elevbtn")]
position = Vector2(1100, 540)
target_floor = 0
[node name="ElevatorUp1" parent="Hospital/Floor1" instance=ExtResource("6_elevbtn")]
position = Vector2(1100, 180)
target_floor = 2
[node name="NavRight0to1_F1" parent="Hospital/Floor1" instance=ExtResource("11_navarrow")]
position = Vector2(1200, 480)
target_floor = 1
target_room = 1
label_text = "→"
[node name="NavLeft1to0_F1" parent="Hospital/Floor1" instance=ExtResource("11_navarrow")]
position = Vector2(1340, 480)
target_floor = 1
target_room = 0
label_text = "←"
[node name="NavRight1to2_F1" parent="Hospital/Floor1" instance=ExtResource("11_navarrow")]
position = Vector2(2480, 480)
target_floor = 1
target_room = 2
label_text = "→"
[node name="NavLeft2to1_F1" parent="Hospital/Floor1" instance=ExtResource("11_navarrow")]
position = Vector2(2600, 480)
target_floor = 1
target_room = 1
label_text = "←"
[node name="NavRight2to3_F1" parent="Hospital/Floor1" instance=ExtResource("11_navarrow")]
position = Vector2(3760, 480)
target_floor = 1
target_room = 3
label_text = "→"
[node name="NavLeft3to2_F1" parent="Hospital/Floor1" instance=ExtResource("11_navarrow")]
position = Vector2(3920, 480)
target_floor = 1
target_room = 2
label_text = "←"
[node name="Floor2" type="Node2D" parent="Hospital"]
position = Vector2(0, -1440)
[node name="Ultrasound" parent="Hospital/Floor2" instance=ExtResource("16_ultrasound")]
position = Vector2(0, 0)
[node name="DeliveryRoom" parent="Hospital/Floor2" instance=ExtResource("17_delivery")]
position = Vector2(1280, 0)
[node name="Nursery" parent="Hospital/Floor2" instance=ExtResource("18_nursery")]
position = Vector2(2560, 0)
[node name="ElevatorDown2" parent="Hospital/Floor2" instance=ExtResource("6_elevbtn")]
position = Vector2(1100, 540)
target_floor = 1
[node name="NavRight0to1_F2" parent="Hospital/Floor2" instance=ExtResource("11_navarrow")]
position = Vector2(1200, 480)
target_floor = 2
target_room = 1
label_text = "→"
[node name="NavLeft1to0_F2" parent="Hospital/Floor2" instance=ExtResource("11_navarrow")]
position = Vector2(1340, 480)
target_floor = 2
target_room = 0
label_text = "←"
[node name="NavRight1to2_F2" parent="Hospital/Floor2" instance=ExtResource("11_navarrow")]
position = Vector2(2480, 480)
target_floor = 2
target_room = 2
label_text = "→"
[node name="NavLeft2to1_F2" parent="Hospital/Floor2" instance=ExtResource("11_navarrow")]
position = Vector2(2600, 480)
target_floor = 2
target_room = 1
label_text = "←"
[node name="Characters" type="Node2D" parent="."]
[node name="Bunny1" parent="Characters" instance=ExtResource("3_char")]
position = Vector2(300, 400)
data = SubResource("CharacterData_bunny1")
[node name="UI" type="CanvasLayer" parent="."]
[node name="HUD" parent="UI" instance=ExtResource("4_hud")]
[node name="SettingsMenu" parent="UI" instance=ExtResource("5_settings")]
```
- [ ] **Step 3: Commit and push**
```bash
cd "F:/Development/_gameDev/Cozypaw-Hospital"
git add scenes/main/Main.tscn
git commit -m "feat(floor2): wire up all three second-floor rooms with navigation arrows"
git push origin develop
```
---
## Self-Review: Spec Coverage Check
| Sprint 11-13 requirement | Covered by |
|--------------------------|-----------|
| Ultraschall (sanfte Herzschlag-Animation) | Task 1: UltrasoundMachine with looping scale tween on HeartbeatDot; Task 4: Ultrasound room |
| Kreißsaal (Mama kommt rein, Baby ist da) | Task 2: DeliveryBed 4-state FSM (tap → mama slides in → baby fades in → tap → reset); Task 5: DeliveryRoom |
| Säuglingsstation mit Wiegen | Task 3: Cradle with pendulum rotation; Task 6: Nursery with 3 Cradle instances on a rail |
| All rooms accessible via elevator + horizontal nav | Task 7: Main.tscn with 4 nav arrows for 2 boundaries + ElevatorDown2 |
| Keine Gift-Mechaniken (product principle) | All interactive objects: GelBottle, Blanket, FlowerVase, BabyBlanket, TeddyBear, MilkBottle — all safe/positive |
**Placeholder scan:** All code blocks complete. No TBD or TODO. Node paths referenced in scripts match scenes:
- `ultrasound_machine.gd``"HeartbeatDot"` — exists as direct child of UltrasoundMachine ✓
- `delivery_bed.gd``"Mama"` (Node2D), `"Baby"` (Node2D) — both exist as direct children of DeliveryBed ✓
- `cradle.gd` → rotates `self` — no node path lookup needed ✓
**Type consistency:**
- `_on_mama_arrived()` used as `tween.finished.connect(_on_mama_arrived)` — method callable, matches function signature ✓
- `Baby.modulate:a` tweened as float — `modulate` is Color, `:a` accesses the alpha channel as float ✓
- `MAMA_OFFSCREEN_X = -600.0` used in `_ready()` and `_start_reset()` — consistent ✓
- `MAMA_PARKED_X = -100.0` used only in `_start_arrival()`
- `rotation_degrees` tweened as float (ROCK_ANGLE = 15.0) — matches `self.rotation_degrees` property type ✓
**Floor2 room count:** 3 rooms (not 4). Nav arrows: 4 total (2 boundaries × 2 directions). ElevatorDown2 only (no ElevatorUp2 — Floor 2 is the top floor). ✓
---
## Smoke Test Checklist (manual, on device after completion)
- [ ] Elevator from Floor 1 lands at Floor 2 Ultrasound room
- [ ] Heartbeat animation starts automatically on entering Ultrasound room (green dot pulses every ~0.84s)
- [ ] "→" navigates Floor 2: Ultrasound → DeliveryRoom → Nursery and back with "←"
- [ ] No "→" arrow visible in Nursery room (no room 3)
- [ ] Tapping DeliveryBed: Mama slides in from left, Baby appears
- [ ] Tapping DeliveryBed again: Mama slides out left, Baby disappears
- [ ] Tapping DeliveryBed during animation: nothing happens (state guard)
- [ ] Tapping any Cradle: cradle rocks (swings ±15°, returns to center)
- [ ] Tapping Cradle during rocking: nothing (state guard)
- [ ] All interactive objects (GelBottle, Blanket, FlowerVase, BabyBlanket, TeddyBear, MilkBottle) bounce on tap
- [ ] Elevator Down from Floor 2 → Floor 1 XRay room
- [ ] Bunny can be dragged to Floor 2