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>
This commit is contained in:
Steven Wroblewski
2026-05-11 15:26:14 +02:00
parent 52ebb78862
commit 8f0569766c
@@ -0,0 +1,152 @@
# 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).