Files
Cozypaw-Hospital/docs/superpowers/specs/2026-05-11-sprint-14-garden-party.md
T
Steven Wroblewski 8f0569766c docs(sprint-14): add garden party spec
GiftBox auto-reset, Balloon pop/respawn, Cake cut/reset, chair snap points.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:26:14 +02:00

153 lines
5.5 KiB
Markdown
Raw 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 14 — Garden Party Spec
## Goal
Make the GardenParty scene a fully playable sandbox room with three interactive objects
(GiftBox auto-reset, Balloon pop/respawn, Cake cut/reset) and character snap-to-chair
at the party table.
---
## Scope
**In scope:**
- `gift_box.gd`: add `RESETTING` state (auto-reset 3 s after opening)
- `balloon.gd`: new script — tap to pop, auto-respawn after 5 s
- `cake.gd`: new script — tap to cut slice, auto-reset after 3 s
- `GardenParty.tscn`: replace GiftBox3 with Cake, add Cake node structure, add chair
ColorRects under snap points, attach balloon.gd to existing Balloon nodes
- Tests: extend `test_gift_box.gd`, add `test_balloon.gd`, add `test_cake.gd`
**Out of scope:**
- New SFX keys (reuse `object_tap` for all three objects)
- Navigation changes (HomeButtonToGarden and HomeButtonReturn already wired)
- Additional characters beyond Bunny1 already in Main.tscn
---
## Object Mechanics
### GiftBox — RESETTING state
New state added to the existing `State` enum:
```
CLOSED → OPENING → RESETTING → CLOSED
```
- `_on_lid_opened()`: state → `RESETTING`, gift fades in (existing tween), inline 3 s
tween interval runs in parallel → `_start_close_lid()`
- `_start_close_lid()`: lid tweens back (fade-in alpha 0→1, slide y back to 0) →
`_on_reset_complete()`
- `_on_reset_complete()`: state → `CLOSED`
- `OPEN` state removed — `RESETTING` covers "gift visible + waiting"
- New internal methods: `_start_close_lid()`, `_on_reset_complete()`
- Constant: `RESET_DELAY: float = 3.0`
### Balloon
New file: `scripts/objects/balloon.gd`
New scene attachment: `balloon.gd` attached to existing `Balloon1` / `Balloon2` nodes in
`GardenParty.tscn`.
```
IDLE → POPPING → POPPED → RESPAWNING → IDLE
```
State transitions:
- Touch input on balloon while `IDLE``_start_pop()`
- `_start_pop()`: `AudioManager.play_sfx("object_tap")`, state → `POPPING`, scale tween to
`Vector2.ZERO` (0.15 s, EASE_IN, TRANS_BACK) → `_on_pop_complete()`
- `_on_pop_complete()`: state → `POPPED`, starts 5 s tween interval → `_start_respawn()`
- `_start_respawn()`: state → `RESPAWNING`, scale tween to `Vector2.ONE` (0.3 s, EASE_OUT,
TRANS_BACK) → `_on_respawn_complete()`
- `_on_respawn_complete()`: state → `IDLE`
Input: `_input(event)` — same touch-rect pattern as GiftBox, half-size 20 px (balloon is
small).
Constants: `POP_DURATION`, `RESPAWN_DURATION`, `RESPAWN_DELAY`.
### Cake
New file: `scripts/objects/cake.gd`
New node structure in `GardenParty.tscn` (replaces GiftBox3 at x=820, y=464):
```
Cake (Node2D, cake.gd)
├── Base (ColorRect, ~80×40 px, warm brown)
└── Slice (ColorRect, ~30×40 px, slightly offset + rotated, lighter brown)
```
```
WHOLE → CUTTING → CUT → RESETTING → WHOLE
```
State transitions:
- Touch input on Cake while `WHOLE``_start_cutting()`
- `_start_cutting()`: `AudioManager.play_sfx("object_tap")`, state → `CUTTING`,
tween `Slice.modulate.a` 1→0 (0.3 s, EASE_OUT) → `_on_cut_complete()`
- `_on_cut_complete()`: state → `CUT`, 3 s tween interval → `_start_reset()`
- `_start_reset()`: state → `RESETTING`, tween `Slice.modulate.a` 0→1 (0.3 s, EASE_IN)
`_on_reset_complete()`
- `_on_reset_complete()`: state → `WHOLE`
Input: touch rect `BUTTON_HALF_WIDTH = 40`, `BUTTON_HALF_HEIGHT = 20`.
Constants: `CUT_DURATION`, `RESET_DELAY = 3.0`, `RESET_DURATION`.
---
## Scene Changes (GardenParty.tscn)
| Change | Detail |
|---|---|
| Remove `GiftBox3` | Was at position Vector2(820, 464) |
| Add `Cake` | Node2D at Vector2(820, 464), `cake.gd`, child nodes Base + Slice |
| Attach `balloon.gd` | To existing `Balloon1` and `Balloon2` ColorRect nodes |
| Add `ChairLeft` | ColorRect ~60×20 px, brown, under `SnapTableLeft` (x=530, y=480) |
| Add `ChairRight` | ColorRect ~60×20 px, brown, under `SnapTableRight` (x=750, y=480) |
Navigation and snap-point logic unchanged.
---
## Tests
### `test_gift_box.gd` (extend existing)
| Test | Assertion |
|---|---|
| `test_state_becomes_resetting_after_lid_opened` | Call `_on_lid_opened()` → state == `State.RESETTING` |
| `test_state_becomes_closed_after_reset_complete` | Call `_on_reset_complete()` → state == `State.CLOSED` |
### `test_balloon.gd` (new, extends GutTest)
| Test | Assertion |
|---|---|
| `test_initial_state_is_idle` | `state == State.IDLE` |
| `test_start_pop_sets_popping` | Call `_start_pop()``state == State.POPPING` |
| `test_on_pop_complete_sets_popped` | Call `_on_pop_complete()``state == State.POPPED` |
| `test_on_respawn_complete_sets_idle` | Call `_on_respawn_complete()``state == State.IDLE` |
### `test_cake.gd` (new, extends GutTest)
| Test | Assertion |
|---|---|
| `test_initial_state_is_whole` | `state == State.WHOLE` |
| `test_start_cutting_sets_cutting` | Call `_start_cutting()``state == State.CUTTING` |
| `test_on_cut_complete_sets_cut` | Call `_on_cut_complete()``state == State.CUT` |
| `test_on_reset_complete_sets_whole` | Call `_on_reset_complete()``state == State.WHOLE` |
---
## Architecture Notes
- All three objects follow the same state-machine pattern as existing objects (`cradle.gd`,
`gift_box.gd`, etc.) — no new abstractions.
- Tween-based animations are not unit-tested (visual only). State transitions triggered by
`tween.finished` callbacks are tested by calling the callbacks directly.
- `AudioServer.get_driver_name() == "Dummy"` guard is already in `AudioManager.play_sfx()`
— no additional guard needed in new scripts.
- `balloon.gd` scales the Node2D itself (no child sprite node needed at placeholder stage).