fertig-classic-games/docs/angrybirds-build-plan.md

144 lines
10 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# Angry Birds — build plan
Living plan doc. Survives context clears: **read this file first**, pick the next unchecked item, update the checkboxes and the Status line as work lands.
## Status
**Wave 0 complete 2026-07-30 — the gate is passed.** `node tools/verifyAngryBirds.js`**51 checks green**. A 10-box tower settles in 0.68s, sleeps, drifts <1px sideways, and tilts 6.1e-3 rad (0.35°, invisible). A 15-box pyramid settles too. The solver runs a 6-second, 41-body shot in **254ms** headless.
Next: Wave 1, the playable core.
## Decisions taken up front
| Question | Answer |
|---|---|
| Slug / category / iconFrame | `angrybirds` / `arcade-console-pc` ("Video Games") / **92** (next free after Excitebike's 91) |
| Physics | **Bespoke deterministic rigid-body solver.** Matter.js ships inside the CDN Phaser build and would work in-browser, but it is not importable in bare Node and not reproducible that forfeits the verifier, the generator's measured star thresholds, and the editor's winnability gate. |
| Visual treatment | **No CRT overlay, no scanlines, no pixel font.** It's a 2009 touch game, not an arcade cabinet. Clean cartoon look, app's normal fonts. |
| Campaign scale | **63 levels, 3 episodes of 21** Poached Eggs / Mighty Hoax / Danger Above. |
| Art | Procedural, with a `data/angrybirds-artwork.json` drop-in hook so real sprites can replace the fallbacks later with no code change. Procedural fallbacks must always work standalone. |
| Bird roster | All 8 Red, Chuck, Blue, Bomb, Matilda, Terence, Hal, Bubbles. |
| Modes | Single-player campaign only. `maxOpponents: 0`, so it skips `OpponentSelect` entirely. |
## Files
| File | Role | State |
|---|---|---|
| `src/games/angrybirds/AngryBirdsPhysics.js` | Rigid-body solver. Zero imports. | 🔨 Wave 0 |
| `src/games/angrybirds/AngryBirdsLogic.js` | Materials, damage, birds, scoring, `stepSim`. Zero imports. | Wave 1 |
| `src/games/angrybirds/AngryBirdsGame.js` | Phaser scene, key `AngryBirdsGame`. | Wave 1 |
| `src/games/angrybirds/AngryBirdsArt.js` | Procedural textures + artwork-JSON resolution. | Wave 5 |
| `src/games/angrybirds/AngryBirdsEditor.js` | Level editor, key `AngryBirdsEditor`. | Wave 3 |
| `src/games/angrybirds/AngryBirdsAuto.js` | Reference bot / winnability gate. In `src/` so the editor button and the verifier run the same gate. | Wave 3 |
| `src/games/angrybirds/tutorial.md` | Tutorial copy. | Wave 5 |
| `tools/genAngryBirds.js` | Level-bank generator; **measures** star thresholds. | Wave 3 |
| `tools/verifyAngryBirds.js` | Node harness. | 🔨 Wave 0 |
| `assets/gamedata/angrybirds/` | `levels.json` manifest + 63 level files. | Wave 4 |
| `data/angrybirds-artwork.json` | Drop-in sprite map. | Wave 5 |
Wiring touchpoints: `src/data/gamesRegistry.js`, `src/main.js` (import + scene array, both scenes), `src/scenes/GameRoomScene.js:26` (`slugDispatch`), `src/data/assetManifest.js`, `src/scenes/PreloadScene.js` (manifest JSON + `?angrybirds-editor=1` entrance).
---
## Wave 0 — the rigid-body solver — ✅ DONE 2026-07-30
- [x] Convex polygon + circle bodies, mass/inertia derived from geometry
- [x] Uniform-grid AABB broadphase
- [x] SAT narrowphase with reference-face clipping 2-point manifolds
- [x] Sequential-impulse solver with warm starting
- [x] Coulomb friction clamped against the accumulated normal impulse
- [x] **Split-impulse** position correction with penetration slop (not plain Baumgarte see finding 1)
- [x] Island-based sleeping
- [x] `cloneWorld` / `hashWorld` for determinism tests
- [x] Verifier green all four gate conditions pass
### The gate — all four passed
1. 10-box tower settles in 0.68s, every body asleep, drift 0.00px, tilt 6.1e-3 rad. A 15-box pyramid also settles.
2. Box on a 15° slope with µ=0.9 does not creep; a µ=0.2 box on 40° does slide.
3. Bit-identical replay; 1/60 frame == 4 substeps exactly; ragged frame pacing matches even pacing.
4. 12 monkey seeds × 900 substeps: no NaN, no overspeed, nothing sinks.
### Wave 0 findings
1. **Plain Baumgarte is not good enough for a 10-high stack, and the failure looks like a bug when it isn't.** Feeding position error back as bias velocity makes a contact behave like a stiff spring, so penetration is proportional to load the bottom contact of a 10-box tower carries 9 boxes and sinks ~9× deeper than the top one. Measured sag was 12.85px. Switching to **split impulse** (a separate pseudo-velocity accumulator, added into the position integration then discarded) removes a fixed fraction of the excess per substep *regardless of load*, and never leaks the correction back as bounce energy. Sag dropped to 0.46px per contact, which is exactly `SLOP` i.e. the remaining sag is the slop band we deliberately allow, not solver error. **Do not "simplify" this back to a single bias term.**
2. **Total stack sag is bounded by `SLOP × contactCount`, by design.** The verifier asserts against that budget rather than a flat pixel value. Shrinking `SLOP` tightens stacks but reintroduces resting-contact jitter 0.5px on a 40px block is the sweet spot.
3. **Warm starting must key on a stable feature id, not array position.** Contact points are matched across substeps by `(refFace << 8) | incidentVert`, with a flip bit. Matching by index scrambles impulses the moment the clip order changes and the stack sags visibly.
4. **The reference-face choice needs hysteresis.** Preferring A unless B is deeper by a relative margin (`0.1 × |sepA| + 0.01`) keeps the choice stable frame to frame, which is what keeps feature ids and therefore warm starting stable for a resting contact.
5. **Sequential impulses are Gauss-Seidel, so solve order genuinely changes the answer.** Bit-identical results across different body *creation* orders are not achievable and were never the goal; the verifier asserts the dependence stays sub-pixel (measured 0.4px). What must be exact and is is that a *given* scene replays identically.
6. **Island sleeping, not per-body sleeping.** Union-find over contacts; an island sleeps only when its slowest member has been slow for `SLEEP_TIME`. Per-body sleeping freezes half a tower while the rest still moves. Sleeping is also how the rules layer knows a shot is over, so it is not just an optimization.
7. **Anti-tunnelling is a design constraint, not a runtime check.** `MAX_SPEED × SUBSTEP_DT < MIN_HALF_EXTENT` (10px < 12px) is asserted by the verifier, which is what forced `SUBSTEP_DT` to 1/240 and `MAX_SPEED` to 2400. No block may be thinner than 24px.
8. **String keys dominated the profile.** Numeric composite keys for the grid and contact maps, plus building the contact list in already-sorted pair order instead of re-sorting it four times per substep, took a 6-second shot from 368ms to 254ms with bit-identical output.
9. **Contacts cache direct body references, so `cloneWorld` must rewire them.** A shallow spread leaves the clone's contacts pointing at the original's bodies the clone then drives the world it was supposed to leave alone. The winnability bot and shot preview both depend on this; the verifier now asserts it explicitly.
### Baselines
```
node tools/verifyAngryBirds.js -> 51 checks green
node tools/verifyAngryBirds.js --seeds=40 -> slower monkey soak, still green
```
---
## Wave 1 — playable core — ⬜ NEXT
- [ ] Materials: wood / stone / ice with distinct density, friction, restitution, hp, damage threshold
- [ ] Damage as a **health model** impulse above threshold subtracts hp; 2/3 and 1/3 crossings emit crack stages
- [ ] Pigs take damage from debris, not just direct hits
- [ ] Slingshot: drag, clamp to max draw, release
- [ ] Win/lose evaluated only once the world is settled or `MAX_SHOT_TIME` expires
- [ ] `stepSim(state, dt) → events[]` contract
- [ ] Phaser scene with sim-spacescreen-space mapping, camera pan/zoom
- [ ] Trajectory memory (dotted trail of previous shots)
---
## Wave 2 — the full roster, TNT, scoring — ⬜
- [ ] 8 birds, abilities fire on tap mid-flight
- [ ] TNT blocks detonate when damaged
- [ ] Scoring: 5,000/pig + material destruction points + 10,000 per unused bird
---
## Wave 3 — editor and generator — ⬜
- [ ] `?angrybirds-editor=1` entrance, authoring in play coordinates
- [ ] **Settle** button so levels export already at rest
- [ ] **Test Winnable** button running the same gate as the verifier
- [ ] `AngryBirdsAuto` beam search over (angle, power, ability timing)
- [ ] Blob export of `level-NNN.json` + regenerated `levels.json`
Gate is **one-directional**: if the bot wins, a human can. A bot failure is *not* proof a level is impossible.
---
## Wave 4 — the 63-level campaign — ⬜
- [ ] Poached Eggs (1-21) wood then stone; Red, Chuck, Blue
- [ ] Mighty Hoax (22-42) ice, TNT, taller structures; adds Bomb, Matilda
- [ ] Danger Above (43-63) mixed materials, elevated structures; adds Terence, Hal, Bubbles
Every level needs a **load-bearing** support whose removal collapses the structure. That, not bird variety, is what makes a shot feel clever.
---
## Wave 5 — art, audio, progress, polish — ⬜
- [ ] Procedural art + artwork-JSON drop-in
- [ ] Progress via `/puzzles/angrybirds/{progress,complete}`, per-level best/stars in `ab-best-N` / `ab-stars-N`
- [ ] Match history use `{ slug, score, opponentScores, result }`. Goo Tower gets this **wrong** (`GooTowerGame.js:879-881`); copy `RushHourGame.js:513-516` instead.
- [ ] `tutorial.md` + `hasTutorial: true`
- [ ] Paint `game-icons.png` frame 92 (row 6, col 2 44×44 at x=88, y=264)
---
## How difficulty is decided
`TUNING` in `AngryBirdsLogic.js` owns damage thresholds and material hp tune there, not in level files. Star thresholds are **measured** by `tools/genAngryBirds.js` from the bot's achieved score, never hand-authored (same discipline as Excitebike's `qualifyMs`).
## Sources for original-game behaviour
- Rovio *Angry Birds* (2009) material behaviour, bird abilities, 5,000/pig and 10,000/unused-bird scoring.
- Episode structure: Poached Eggs, Mighty Hoax, Danger Above 21 levels each.