144 lines
10 KiB
Markdown
144 lines
10 KiB
Markdown
# 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-space→screen-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.
|