diff --git a/BUILD-RECORD.md b/BUILD-RECORD.md deleted file mode 100644 index 318ad1d..0000000 --- a/BUILD-RECORD.md +++ /dev/null @@ -1,432 +0,0 @@ -# BUILD RECORD — RC-0D193E "Build Angry Birds, Report Process" - -- **RC:** RC-0D193E (tier: scout, owner: rc-ops, assigned: Hermie(s)) -- **Worker:** Hermie (HANDS worker, Hermes Agent v0.19.1, default profile) -- **Date:** 2026-08-28 (UTC) -- **Artifact root:** `/root/rc-0d193e-isle-slingers/` (this git repository) -- **Result:** shipped — playable browser game + this build record. Test - suites: **sim 15/15 PASS + page 7/7 PASS** (see §5; post-shipment - fixes: bug #11 boot crash, bugs #12-13 physics+camera, bugs #14-15 - release+direction — all found by the Commander's playtest). - ---- - -## 1. Mission and deliverables - -Dispatch asked for an "Angry Birds" build with a report on the process. -Two constraints shaped everything: - -1. **IP boundary.** No Rooster/RIAA assets, art, audio, branding, or - trademarked names may be copied. The deliverable is therefore an - *original* slingshot-physics game, **Isle Slingers**: same genre and - same core loop (drag-sling projectiles at fragile structures to defeat - cartoon enemies), 100% original procedural art (Canvas 2D) and - synthesized audio (WebAudio oscillators — no sample files). -2. **Report the process.** This document is the process record: what was - built, how, what broke, how it was verified. - -## 2. Environment baseline (telemetry) - -Captured before coding into `telemetry/collected.json` (+ `ledger.log`, -collector `telemetry/collect.py`): - -| Item | Value | -|---|---| -| Host | Debian 13 (trixie) container, kernel 6.12.33 (TrueNAS host), virtual | -| CPU / RAM | AMD Ryzen 9 9900X (24 threads), 123 GiB RAM | -| Disk | 638 GiB overlay, 46% used | -| Node | v20.19.2 (npm added post-shipment via corepack shim — see §6 notes) | -| Python | 3.11.15 | -| Git | 2.47.3 | -| Hermes | /opt/hermes @ ba2a4296 (pinned), v0.19.1, profile: default | - -## 3. Architecture - -Zero runtime dependencies. Everything is plain ES5-flavored JS in UMD -wrappers so each module loads in a browser (`window.*`) *and* in Node -(`require`/`import`) — that dual-loadability is what makes the headless -test harness possible. - -``` -index.html 86 L page shell, script include order -css/style.css 97 L layout, HUD styling -js/levels.js 158 L LEVELS[] data: 5 levels, coords = block centers -js/core.js ~640 L THE engine: pure deterministic simulation -js/renderer.js ~595 L Canvas 2D: procedural art, particles, camera pan+zoom -js/audio.js 137 L WebAudio synth (no samples): launch, hit, break, win -js/main.js 325 L glue: RAF loop, mouse+touch input, screens, HUD -test/sim.test.mjs ~410 L headless verification (see §5) -test/page.test.mjs ~200 L page-boot harness (see §5) -telemetry/ pre-build environment baseline -``` - -### Core invariants (js/core.js) - -- **Fixed timestep 120 Hz**, semi-implicit Euler with velocity-averaged - position update, per-substep air drag. No `Math.random` anywhere in the - core (verified by grep; only a doc comment mentions it). Identical - inputs produce byte-identical states (test §6.3). -- **No DOM, no audio, no timers** in the core: `createGame(levelDef) -> state`, - `step(state, dt)` mutates state, `state.events[]` is an event queue the - renderer/audio *consume* (hit, break, kill, launch, score popups). This - split is what lets the game be unit-tested headlessly in Node. -- **Materials:** wood (hp 1300, e 0.35), ice (hp 480, e 0.2, fragile vs - high impact), stone (hp 3400, e 0.15, heavy). Restitution + friction - differ per pair; blocks are AABB bodies with rotation-free "tumble" - approximation (blocks fall, stack, shatter at hp ≤ 0 with material - damage thresholds). -- **Crush model:** a fast bird can crush blocks/enemies it plows through - (relative normal speed ≥ 24 px/s AND impact impulse above threshold); - slow contact never crushes — a resting bird is harmless. -- **Impulse model (fixed in dev):** collision response uses a single - normal-only impulse `j = -(1+e)*rel_v_n * m_b/(m_b+m_B)` — an earlier - draft applied a redundant per-body impulse that made pairs fly apart - unnaturally (see §7). -- **Birds (3 kinds, each with a mid-flight ability on tap/click):** - - | bird | mass | r | ability | - |---|---|---|---| - | wren | 3.0 | 20 | fury — horizontal burst | - | zippy | 2.2 | 17 | dive — steep dive vector | - | boulder | 6.5 | 26 | quake — radial shock + damage falloff | - - Birds are "spent" when they settle (jitter-tolerant 0.9 s rest window, - anywhere in the world — perched on a beam included), age out at 12 s, - or fly off-world. A 0.12 s per-enemy hit cooldown prevents one overlap - from dealing damage every substep. -- **Win/lose:** all enemies dead → `won` (unused birds pay a 5000-point - bonus); out of birds with enemies alive → `lost`. 3-star thresholds per - level. - -### Renderer / audio / input - -- **Renderer:** camera frames the slingshot AND the full action zone - while aiming (zooming out when the level needs it — bug #13 fix), - follows the bird 1:1 in flight, + shake on big impacts; enemies/birds - are drawn procedurally (vector shapes, squash & stretch, blink, damage - faces); particles from `state.events`. -- Audio: all SFX synthesized at event time (sine/triangle/noise envelopes); - mute toggle; no network fetches, no files. -- Input: mouse drag on the sling (with dotted trajectory preview computed - by the *same* integrator the live sim uses — preview is pixel-accurate), - tap-to-launch, tap-in-flight for abilities; full touch support - (pointer events, `touch-action: none`). - -## 4. Game content - -5 levels of ordered difficulty (world 2400 px wide, ground y=900): - -| # | Level | birds | enemies | star gates | -|---|---|---|---|---| -| 1 | First Perch | wren wren zippy | 2 mogg | 12k/24k/36k | -| 2 | Crate Caves | wren zippy wren | 3 mogg | 15k/28k/42k | -| 3 | Glass Works | wren zippy boulder | 2 mogg + 1 grum + 1 mogg | 18k/34k/50k | -| 4 | Stone Keep | wren zippy boulder wren | 4 mogg + 1 grum | 22k/40k/58k | -| 5 | Mog Fortress | wren zippy boulder boulder | 5 mogg + 1 king | 28k/48k/70k | - -Enemies: mogg (hp 1), grum (hp 2, armored), king (hp 3, large, armored). -Progress (unlocked levels, best stars) persists in `localStorage`. - -## 5. Verification (the "Report Process" evidence) - -Two headless suites, zero dependencies, no browser required: - -- `node test/sim.test.mjs` — physics/rules harness. **15/15 PASS**: - 1. all JS files parse (`node --check`-equivalent) - 2. **DOM wiring: every id referenced by `js/*.js` - (`getElementById` + `querySelector('#id')`) exists in - `index.html`** — added post-shipment (bug #11); it is RED on the - shipped page and GREEN after the fix - 3. level data integrity (5 levels, ordered difficulty, no overlap - nonsense) - 4. **determinism:** two games with identical inputs run in lockstep to - identical states - 5. **stability:** 15 s of physics after a full-power 45° shot stays - finite (no NaN/explosion) - 6. **trajectory preview** tracks the real flight (empty air, < 40 px - error) - 7. **abilities:** each slinger's ability is distinct and usable - mid-flight - 8. **win/lose rules:** all-dead → `won` + unused-bird bonus; one wren - vs king → king survives, out of birds → `lost` - 9–13. **level clearability (L1–L5):** each level is cleared by a - scripted "greedy perfect archer" — a test-internal AI that *only* - sees the same public API a human sees (the same `launch` + `step` - calls the page's input triggers). For each living enemy it (a) - picks the most exposed target (fewest blocks on the line of fire), - (b) evaluates candidates in two stages — a coarse 2° full-power - sweep, then ±3° at 0.5° steps across a pull ladder - [1, 0.85, 0.7, 0.55, 0.4] around the top-3 coarse angles — running - the real physics 5 s per candidate on a clone, (c) commits kill - shots first, else the shot that lands closest to the target. - Upgraded post-playtest (bug #12 follow-up): the AI now uses - partial-power pulls (players do) and refuses to spend a bird on a - shot that damages neither the target nor the board — the old - full-power 2°-grid AI left L1's mogg at 0.06 hp for a 4th bird - that does not exist. - Result: all 5 clear — L1 3/3 birds (1★), L2 2/3, L3 3/3, L4 4/4, - L5 3/4 (2★). - 14. **fort rest (bug #12's arm):** every level sits still when - untouched — 15 s idle, max block/enemy speed < 20 px/s (sub-pixel - at 120 Hz) and < 0.5 px positional drift in the final second. RED - on the original solver (measured 34-112 px/s speeds and 21-108 px/s - drift on a level with *zero* input). - 15. **aim framing (bug #13's arm):** on the first aiming frame of - every level the camera view must contain BOTH the slingshot and the - full action zone (all live blocks + enemies). RED on the old - camera (aim view showed the sling with the fort 400-900 px - off-screen). - -- `node test/page.test.mjs` — **page-boot harness, 7/7 PASS.** Loads - `index.html` + all five `js/` modules *in browser order* against a - minimal DOM stub built from the real `id` attributes in - `index.html` (fake canvas 2D context, RAF queue, pointer events, - localStorage), then drives a full click session: PLAY → level tile → - aim & launch → 8 s of simulated play → pause/mute/keyboard. Any - null-element wiring, missing global, or boot-time throw fails here — - the class the physics suite is structurally blind to. Arms 16-17 - drive the page's OWN pointer handlers (the exact calls a player - makes) against the real camera transform: - 16. **direction (bug #15's arm):** grab the bird at the place it is - VISIBLE (inverse of the renderer transform), pull it back - (away from the fort), release — the launch velocity must point - TOWARD the fort (vx > 100). RED on 2d9c8f47. - 17. **release (bug #14's arm):** mid-flight, record the canvas - `lineTo()` calls of one drawn frame and assert no band endpoint - sits at the flying bird's position (±12px of bird.x±6, bird.y). - RED on 2d9c8f47 (the banded tracked the projectile all flight). - - -## 6. Process log — what broke, what it taught - -Iterative build order: data (levels) → deterministic core → renderer → -audio → UI → tests. Bugs found by the headless harness and fixed in order: - -1. **Redundant impulse** — early collision code applied the impulse to - *both* bodies in a way that double-counted; fixed to a single - normal-only impulse on each body from the relative normal velocity. -2. **Slow birds crushing everything** — a bird resting against a block - still triggered crush every step; fixed with a rest-contact guard - (relative normal speed < 30 px/s is contact, crush requires ≥ 24 and a - real impulse). -3. **Enemy-identity leak in crush** — `crushCircleVsBox` used the bird's - kind as the identity key; enemy blocks misclassified. Fixed with an - explicit `isEnemy` flag. -4. **`g.time` never advanced** — abilities' cooldowns could never elapse - (cooldown measured 0 s forever). Added `state.time += dt` in `step()`. -5. **Empty-enemy instant win** — `won` fired at t=0 on levels with no - enemies; win now requires `hadEnemies && launched > 0`. -6. **Range tuning** — at g=2300 px/s² max flat range (≈1293 px) fell short - of the furthest structures (≈1900 px); g=1500 gives ≈1980 px max range - and snappier flight time. Gravity is a feel knob; tuned until the far - keeps were reachable. -7. **Preview/sim divergence** — the trajectory preview integrated at a - different rate than the live sim; fixed by making the preview run the - *exact same* 2×(1/120) substepping as the live integrator (dots now - match the real flight pixel-for-pixel). -8. **Multi-hit damage** — a bird overlapping an enemy for several - substeps dealt damage each step (one "hit" killed a 3-hp king). Fixed - with a 0.12 s per-enemy hit cooldown. -9. **Birds perched on beams never spent** — the original settle check - required the bird to be near the ground; a bird resting on a structure - stayed "in flight" forever and blocked the next bird. Fixed: low speed - *anywhere* = spent, with jitter-tolerant accumulation (timer decays 4× - faster than it fills when speed spikes). -10. **AI test-harness bugs (test-side, but instructive):** inverted kill - score (`!dead ? 1000` — the AI was rewarded for *missing* the target), - and a shot-budget gate that rejected all valid "approach" shots - (negative by construction). Both fixed; the harness then cleared all 5 - levels unassisted. -11. **Page boot crash (found post-shipment, in a real browser).** - `main.js:256-257` wired `btn-replay` / `btn-menu` — ids left over from - a rename that never landed in `index.html` (the shipped buttons are - the role-specific pairs `btn-win-*` / `btn-lose-*` / `btn-pause-*`, - which were already wired). `getElementById` → null → `.addEventListener` - threw inside the setup IIFE *before* `show('start')` and - `requestAnimationFrame(frame)` ran: the canvas painted its sky once and - the page was a dead blue rectangle. The fix was deletion of the two - dead lines (every screen already had its full button set — no markup - added). **Lesson recorded:** the 12/12 physics suite was green on this - build because it imports only `core.js`/`levels.js` and cannot see the - page — a suite that cannot go red for the defect you shipped is not - evidence about that defect. Response: added the DOM-wiring arm (test - 1b — RED before the fix, listing exactly `main.js->btn-menu, - main.js->btn-replay`; GREEN after) and the page-boot harness - (`test/page.test.mjs`), which executes all five modules in browser - order against a DOM stub built from the real `index.html` ids and - plays a full click session headlessly. -12. **Inverted box-pair separation — forts self-destructed (found - post-shipment, Commander's playtest: "objects glitching like crazy, - bouncing around on their own", "unbreakable objects", "can't kill a - pig").** `resolveBoxPair` pushed *resting* blocks **toward** each - other: with `s = a.x < c.x ? -1 : 1`, `a.x -= s*ox*0.5; c.x += - s*ox*0.5` moves both bodies *into* the overlap in both sign cases - (and the y-branch is identically inverted). Every structure in every - level was pulled together from t=0: posts crossed through each - other, beams dropped through the gap, the minimum-penetration axis - flipped mid-collapse, and the pair solver flung blocks at 34-112 - px/s forever (probe, zero input: L1 drift 21.6 px/s, L3 108.7 px/s; - the beams visibly oscillated 100+ px — the "zigzag streaks" in the - screenshot are blocks drawn mid-flail). The physics suite was green - because it asserted *finiteness* and *kill outcomes* — the AI's - shots won against the chaos (collapsing blocks crushing the mogg), - so the clearability arms passed while the level was a demolition - derby. Fix: correct separation direction (push apart along the - axis), inelastic velocity handling, and a resting clamp (top takes - bottom's velocity; |v| < 30 px/s on a resting contact is zeroed) so - stacks settle to *exactly* still instead of oscillating under - gravity re-injection. The crush path also gained the same slow-rest - treatment for perched enemies. After the fix: 15 s idle → max speed - 0.0 px/s, drift 0.005 px, on every level. **The (a)/(b)/(c) - reconciliation from the sent-back report:** (a) is false — the page - harness proves the browser runs the *same* `CORE.step`/`CORE.launch` - the AI drives (the page's own frame loop was replayed headlessly: - identical camera + physics behaviour); (c) is false — same 1/120 - fixed steps on both sides; (b) is the truth — the AI won by a route - no human can reproduce, because the route *was* the collapse: - reproducible per-shot 5 s replays of a world falling apart, vs a - player aiming (with the sling off-screen, see #13) at targets - flailing at 30-100 px/s. The suite wasn't testing a different path - from the player's — the shared path was broken, and the suite had no - arm asserting the world's *health* (rest), only its finiteness. - Arms added: fort rest (test 14) — RED on the original solver - ("L1: structure still moving at idle (max 76.5 px/s)"), GREEN - after. -13. **Camera never showed sling and target together (Commander: "cannot - see the entire playing field and have to guess where to sling the - birds", sling "not visible anywhere").** Two framings, neither - sufficient: aiming → camera at (480, 740), a 1280 px view showing - the sling at x=260 but the action zone ending at x=1794-2090 — - 400-900 px off-screen right; flight → 1:1 follow of the bird, sling - off-screen left (the exact geometry of the screenshot: fort center, - sling nowhere). A 1280 px 1:1 view can never span sling (260) to - action-right (1794+), so the fix is a *zoom*, not a pan: the core - now owns `camera.zoom` — while aiming/waiting it frames - [sling-190, action-right+90] at `zoom = min(1, 1280/span)` (L1: - 0.70, L5: 0.61) with the ground at 78% down; in flight it returns to - 1:1 bird-follow; the renderer applies the zoom to the world - transform (shake stays in screen space). First frame of a level - snaps to the target (no level-start pan). Arm added: aim framing - (test 15) — RED on the old camera, GREEN after. - (Also fixed in this pass: `createGame` spawned enemies at - `e.y - r` while the level data and the integrity test treat `e.y` as - the center — every mogg dropped 24-38 px on load, one more "objects - moving on their own" moment.) -14. **Sling never lets go (Commander round 2: "sling never actually lets - go of bird when the bird is shot out").** Purely visual: - `drawSling` drew the taut rubber bands toward the active bird - whenever the bird existed — including while it was *flying* — so the - bands tracked the projectile the entire flight and the sling looked - glued to it. Fix: the taut band is drawn only when - `bird.state === 'aiming'` (pouch or mid-drag); in flight the bands - hang relaxed (the pre-existing else-branch). Arm 17 (page harness, - `lineTo` recording) is RED on 2d9c8f47, GREEN after. -15. **Birds only launched backwards (Commander round 2: "Sling shot - only shoots backwards and not in the direction of the target").** - Root cause in `main.js toWorld()`: the screen→world inverse was - `(sx - VW/2) + cam.x` — it added the camera center but never divided - by the camera zoom that the renderer applies. On the new aim-framing - camera (zoom ≈ 0.70 on L1, center x≈977) the whole visible canvas - mapped to world x ∈ [337, 1617] — *entirely to the right of the - sling at x=260*. A pull-back drag is aimed as `sling − drag`, so - every visible drag produced a leftward launch vector: the bird flew - back, off the left of the island, behind the sling. Measured on the - page by the relay: bird landed at x=230 (sling at 270, fort at - 1500+). The fix is the one missing operator: - `(sx - VW/2) / cam.zoom + cam.x` (and y). The bird now sits exactly - under the cursor, the trajectory preview (same mapping) shows the - real shot, and pull-back launches toward the fort. A `window.ISLE_PAGE` - state handle (documented test hook, unused by the page) lets the - harness assert real launch state. Arm 16 is RED on 2d9c8f47, GREEN - after (measured launch vx = +1381 px/s toward the fort on the - standard gesture). - (Test-harness note: the page harness's `pumpFrames` was passed an - undefined rAF timestamp by the new arms, which NaN'd the fixed- - timestep accumulator and silently froze the sim — fixed by - carrying the last pumped timestamp forward. A harness bug that - briefly mimicked a physics freeze; the sim suite's 15/15 confirms - the core was unaffected.) - -Process notes: - -- The headless harness caught **11 bugs pre- and post-shipment on its - own** (10 in dev + the page boot); the Commander's playtest then found - **4 more** (#12-15) that the harness *could* have caught — the missing - invariants were "the world is at rest", "the view shows the play", - "the input maps where the player sees", and "the sling lets go". - Building the core to be Node-testable first was the single - highest-value decision of the build; the standing lesson is that a - game's test suite must assert the *health* of the state (rest, - framing, input mapping, release), not just finiteness and outcomes — - a suite that only checks "no NaN and someone eventually died" is - green in a demolition derby. -- One terminal guardrail event: a repeated identical test command was - blocked once by the tool-loop guard; resolved by re-running through a - different entrypoint (`node ./test/sim.test.mjs`). -- npm was absent at build time; it was later added via a corepack shim - (`corepack prepare npm@10.8.2 --activate` + a two-line - `/usr/local/bin/npm` wrapper), so `npm test` now runs both suites in - this container. - -## 7. How to run - -``` -# play (any static server, or just open the file): -cd /root/rc-0d193e-isle-slingers -python3 -m http.server 8000 # then open http://localhost:8000/ -# or open index.html directly in a browser - -# verify (both arms): -node test/sim.test.mjs # 15/15 — physics, rules, DOM wiring, rest, framing, clearability -node test/page.test.mjs # 7/7 — page boot, click session, direction, release -node test/balance.test.mjs# 5/5 — balance spec (material break + enemy kill budgets) -# or: npm test (runs all, in order) -``` - -## 8. Known limitations (honest list) - -- No mobile device tested in-browser (touch input implemented per spec, - verified by code review only). -- Physics is an arcade approximation (AABB blocks, tumble without true - rotation) — intentionally: deterministic, stable, fast, and matches the - genre's feel. -- No save-file export/import; progress lives in `localStorage`. -- Difficulty: the greedy-AI lower bound suggests early levels are easy and - L4/L5 are the challenge peak (L5 needs both boulders used well). - -## 9. IP compliance statement - -No assets, code, names, art, audio, or branding derived from, copied from, -or resembling any third party's copyrighted slingshot game were used. All -art is procedural Canvas drawing written for this project; all audio is -synthesized at runtime; level layouts, physics constants, and enemy -designs are original. The game is named **Isle Slingers**. - -## 10. Commit - -See the repository commit SHA recorded in the RC completion payload. -Working tree at commit time contains exactly the files listed in §3. - -## 11. Addendum — RC-375D84 S-2 modularization (2026-09-28) - -The flat `js/{core,levels,audio,renderer,main}.js` files described in §3 no -longer exist. The tree was restructured (behavior-neutral, proven by a -golden-step hash — identical scripted playthroughs through the original -monolith and the split build produce byte-identical state hashes — and by -27/27 green with the old files DELETED, so the new paths are the only load -path): - -- `js/physics/` — the engine, split into 9 submodules (constants, state, - aim, abilities, damage, step, collision, rules, camera) that install into - ONE shared scope `P`; `js/physics/index.js` aggregates and exports the - unchanged 20-key `ISLE_CORE` API. -- `js/levels/` — `helpers/` (constructors + SLING) + `data/level-1..5.js` - into shared scope `L`; `index.js` exports the unchanged `{LEVELS, SLING}`. -- `js/audio/`, `js/render/`, `js/app/` — the former audio.js / renderer.js / - main.js, moved into module folders (content unchanged at S-2 time). -- Every module and submodule carries an `agents.md`; the master list is - `MODULES.md`; the runtime model and load-order contract is `architecture.md`. -- The test suites were re-pointed to the new paths (`sim.test.mjs` now walks - the whole `js/` tree; `page.test.mjs` loads the 19-script browser order). - -See `architecture.md` before touching any of these. diff --git a/MODULES.md b/MODULES.md index 4acaf4c..642d971 100644 --- a/MODULES.md +++ b/MODULES.md @@ -1,6 +1,6 @@ # Isle Slingers — MODULES.md (master module list) -RC-375D84 S-2. Folders = modules, subfolders = submodules. Every module and +the modularization pass. Folders = modules, subfolders = submodules. Every module and submodule carries an `agents.md` describing its surface and invariants. Load contract (order is load-bearing) is in `architecture.md` and encoded in `index.html` + `test/page.test.mjs`. @@ -35,7 +35,7 @@ Load contract (order is load-bearing) is in `architecture.md` and encoded in | helpers | wood, ice, stone, mogg, grum, king, SLING | — | | data/level-N | one level object each (returns it) | wood/ice/stone/mogg/grum/king, SLING | -## Physics notes (S-3/S-4/S-5, RC-375D84 — landed 2026-09-29) +## Physics notes (S-3/S-4/S-5, the physics + modularization update — landed 2026-09-29) - **topple** (S-3): blocks carry a rotational DOF (`ang`, `angVel`, `pivoting`). A resting block whose COM is past its support span pivots about the support-edge corner (full continuous rotation, Q1) and either lands on the diff --git a/architecture.md b/architecture.md index 945c195..ff58abf 100644 --- a/architecture.md +++ b/architecture.md @@ -1,7 +1,7 @@ # Isle Slingers — Architecture Original architecture for a fully offline, canvas-rendered slingshot game. -No frameworks, no build step, no external assets. As of RC-375D84 (S-2) the +No frameworks, no build step, no external assets. As of the physics + modularization update (S-2) the JavaScript is a **modularized tree**: folders are modules, subfolders are submodules, and every module/submodule carries its own `agents.md`. @@ -52,7 +52,7 @@ app/index.js : the page — input, screens, main loop The simulation is a deterministic impulse-ish engine: bodies integrate under gravity at fixed 1/120 s, contacts resolve positionally, and damage routes -through two funnels (`damageBlock`, `damageEnemy`). What each RC-375D84 step +through two funnels (`damageBlock`, `damageEnemy`). What each physics-pass step added, and where it lives: - **Boards tip and topple (S-3, Q1 = full continuous rotation).** Blocks carry @@ -145,9 +145,9 @@ same surface (every new spec was watched RED on the pre-change tree first). ## Conventions for agents working in this tree 1. Change a physics rule -> edit the one submodule that owns it (see each - `agents.md`); re-run `npm test` (27/27) and the golden-step hash - (`/opt/data/s2_golden.mjs` against `/opt/data/core.orig.js`) when touching - anything that predates RC-375D84 behavior. + `agents.md`); re-run `npm test` (47/47) and re-check the golden differential + against the pre-modularization core when touching anything that predates + this modularization/physics pass. 2. Never re-introduce a flat file for a unit that has a submodule folder. 3. Keep `index.html` script order in lockstep with `test/page.test.mjs`'s `FILES` list — both encode the load contract. diff --git a/index.html b/index.html index d587ffa..aa87bff 100644 --- a/index.html +++ b/index.html @@ -77,7 +77,7 @@ - diff --git a/js/app/agents.md b/js/app/agents.md index f1bbb8b..80d2f69 100644 --- a/js/app/agents.md +++ b/js/app/agents.md @@ -1,5 +1,5 @@ # js/app/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. Self-contained module — the page. Owns DOM, input, screens and the main loop. diff --git a/js/audio/agents.md b/js/audio/agents.md index fdc4063..91ac50c 100644 --- a/js/audio/agents.md +++ b/js/audio/agents.md @@ -1,5 +1,5 @@ # js/audio/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. Self-contained module. Exports `ISLE_AUDIO = { AudioEngine }`. diff --git a/js/levels/agents.md b/js/levels/agents.md index 71c0da2..01bc55e 100644 --- a/js/levels/agents.md +++ b/js/levels/agents.md @@ -1,5 +1,5 @@ # js/levels/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. The levels **module** (aggregator). Shared scope `L`. diff --git a/js/levels/data/agents.md b/js/levels/data/agents.md index 6d0bc2a..bd05003 100644 --- a/js/levels/data/agents.md +++ b/js/levels/data/agents.md @@ -1,5 +1,5 @@ # js/levels/data/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. Level definitions, one file per level (`level-1.js`..`level-5.js`). diff --git a/js/levels/data/level-1.js b/js/levels/data/level-1.js index 28703f6..8382707 100644 --- a/js/levels/data/level-1.js +++ b/js/levels/data/level-1.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- levels/data/level-1.js (submodule: level-1) - * RC-375D84 S-2: level 1 definition, split from the original js/levels.js. + * Modularization pass: level 1 definition, split from the original js/levels.js. * Installer: returns the level object built from the shared helpers scope L. */ (function (root, factory) { diff --git a/js/levels/data/level-2.js b/js/levels/data/level-2.js index 8d5a936..21fc5f8 100644 --- a/js/levels/data/level-2.js +++ b/js/levels/data/level-2.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- levels/data/level-2.js (submodule: level-2) - * RC-375D84 S-2: level 2 definition, split from the original js/levels.js. + * Modularization pass: level 2 definition, split from the original js/levels.js. * Installer: returns the level object built from the shared helpers scope L. */ (function (root, factory) { diff --git a/js/levels/data/level-3.js b/js/levels/data/level-3.js index 9c1bc6e..8078074 100644 --- a/js/levels/data/level-3.js +++ b/js/levels/data/level-3.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- levels/data/level-3.js (submodule: level-3) - * RC-375D84 S-2: level 3 definition, split from the original js/levels.js. + * Modularization pass: level 3 definition, split from the original js/levels.js. * Installer: returns the level object built from the shared helpers scope L. */ (function (root, factory) { diff --git a/js/levels/data/level-4.js b/js/levels/data/level-4.js index 44164bf..0997728 100644 --- a/js/levels/data/level-4.js +++ b/js/levels/data/level-4.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- levels/data/level-4.js (submodule: level-4) - * RC-375D84 S-2: level 4 definition, split from the original js/levels.js. + * Modularization pass: level 4 definition, split from the original js/levels.js. * Installer: returns the level object built from the shared helpers scope L. */ (function (root, factory) { diff --git a/js/levels/data/level-5.js b/js/levels/data/level-5.js index 36875e3..427eb5c 100644 --- a/js/levels/data/level-5.js +++ b/js/levels/data/level-5.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- levels/data/level-5.js (submodule: level-5) - * RC-375D84 S-2: level 5 definition, split from the original js/levels.js. + * Modularization pass: level 5 definition, split from the original js/levels.js. * Installer: returns the level object built from the shared helpers scope L. */ (function (root, factory) { diff --git a/js/levels/helpers/agents.md b/js/levels/helpers/agents.md index 0f2e7b9..d2acbcb 100644 --- a/js/levels/helpers/agents.md +++ b/js/levels/helpers/agents.md @@ -1,5 +1,5 @@ # js/levels/helpers/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. Installs into `L`: wood, ice, stone (block factories, return center-based records), mogg, grum, king (enemy factories) and the SLING anchor {x:260,y:745}. diff --git a/js/levels/helpers/index.js b/js/levels/helpers/index.js index f858c20..f2ea3bb 100644 --- a/js/levels/helpers/index.js +++ b/js/levels/helpers/index.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- levels/helpers/index.js (submodule: helpers) - * RC-375D84 S-2: block/enemy constructor helpers + SLING anchor, split from + * Modularization pass: block/enemy constructor helpers + SLING anchor, split from * the original js/levels.js. Installs into the shared levels scope `L`; * js/levels/index.js loads it first, then the level-N data submodules. */ diff --git a/js/levels/index.js b/js/levels/index.js index d4c02a4..a5ac868 100644 --- a/js/levels/index.js +++ b/js/levels/index.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- levels/index.js (module: levels -- aggregator / public API) - * RC-375D84 S-2 modularization. The original js/levels.js monolith is now: + * Modularization pass. The original js/levels.js monolith is now: * levels/helpers/ - block/enemy constructors + SLING anchor (shared scope L) * levels/data/level-N.js - one level definition each (uses the shared scope) * This aggregator loads the parts in order, builds the LEVELS array, and diff --git a/js/physics/abilities/agents.md b/js/physics/abilities/agents.md index 7703ea4..9762ca4 100644 --- a/js/physics/abilities/agents.md +++ b/js/physics/abilities/agents.md @@ -1,5 +1,5 @@ # js/physics/abilities/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. Installs into `P`: launch, triggerAbility. diff --git a/js/physics/abilities/index.js b/js/physics/abilities/index.js index f00562b..94101f8 100644 --- a/js/physics/abilities/index.js +++ b/js/physics/abilities/index.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- physics/abilities/index.js (submodule: abilities) - * RC-375D84 S-2 modularization (behavior-neutral split of the original + * Modularization pass (behavior-neutral split of the original * js/core.js monolith). Installs its units into ONE shared physics scope `P` * that every physics submodule sees -- the same single-scope semantics the * monolith had. Must load before js/physics/index.js (the aggregator that diff --git a/js/physics/agents.md b/js/physics/agents.md index f58ba10..e6f8806 100644 --- a/js/physics/agents.md +++ b/js/physics/agents.md @@ -1,5 +1,5 @@ # js/physics/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. The physics **module** (aggregator). Deterministic, DOM-free. @@ -17,7 +17,7 @@ The physics **module** (aggregator). Deterministic, DOM-free. (`activeBird`, `trajectory`, `aimVector`, `starsFor`, `birdsRemaining`). - Invariants: fixed-timestep consumer (tests run 1/120 s); no `Math.random()` in gameplay paths (determinism is a test arm); no DOM access. -- New physics behavior lands in the owning submodule, NOT here. RC-375D84 +- New physics behavior lands in the owning submodule, NOT here. The physics pass S-3..S-5 (landed 2026-09-29): topple/ owns board tip-over (Q1 full continuous rotation), collision/ owns velocity-aware crush + mass-weighted stacks (S-4), step/ owns the landed-bird state and its integration (S-5). diff --git a/js/physics/aim/agents.md b/js/physics/aim/agents.md index 98fb089..28776ed 100644 --- a/js/physics/aim/agents.md +++ b/js/physics/aim/agents.md @@ -1,5 +1,5 @@ # js/physics/aim/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. Installs into `P`: aimVector, trajectory. diff --git a/js/physics/aim/index.js b/js/physics/aim/index.js index 40832fa..b255d40 100644 --- a/js/physics/aim/index.js +++ b/js/physics/aim/index.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- physics/aim/index.js (submodule: aim) - * RC-375D84 S-2 modularization (behavior-neutral split of the original + * Modularization pass (behavior-neutral split of the original * js/core.js monolith). Installs its units into ONE shared physics scope `P` * that every physics submodule sees -- the same single-scope semantics the * monolith had. Must load before js/physics/index.js (the aggregator that diff --git a/js/physics/camera/agents.md b/js/physics/camera/agents.md index afefcbe..a616dd1 100644 --- a/js/physics/camera/agents.md +++ b/js/physics/camera/agents.md @@ -1,5 +1,5 @@ # js/physics/camera/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. Installs into `P`: updatePopups, updateCameraTarget, decayCamera. diff --git a/js/physics/camera/index.js b/js/physics/camera/index.js index 43fb929..09c0dbe 100644 --- a/js/physics/camera/index.js +++ b/js/physics/camera/index.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- physics/camera/index.js (submodule: camera) - * RC-375D84 S-2 modularization (behavior-neutral split of the original + * Modularization pass (behavior-neutral split of the original * js/core.js monolith). Installs its units into ONE shared physics scope `P` * that every physics submodule sees -- the same single-scope semantics the * monolith had. Must load before js/physics/index.js (the aggregator that diff --git a/js/physics/collision/agents.md b/js/physics/collision/agents.md index 22afb19..91a4134 100644 --- a/js/physics/collision/agents.md +++ b/js/physics/collision/agents.md @@ -1,5 +1,5 @@ # js/physics/collision/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. Installs into `P`: crushCircleVsBox, collideBirdWorld, collideBirdBlocks, collideBirdEnemies, REST_VEL_CLAMP, resolveBoxPair. @@ -7,7 +7,7 @@ collideBirdEnemies, REST_VEL_CLAMP, resolveBoxPair. - Contact resolution for circles (birds, enemies) vs axis-aligned blocks and block-block pairs. `resolveBoxPair` is the box-box rest/impulse resolver; `crushCircleVsBox` is the fast-falling-onto-structure damage path. -- RC-375D84 S-4 (landed 2026-09-29): `crushCircleVsBox` is velocity-direction- +- Added 2026-09-29: `crushCircleVsBox` is velocity-direction- aware (the box must be the body plowing into the enemy, `boxAlong > 30`; a resting box never crushes), carries a per-enemy crush cooldown (0.12 s — one landing = one crush instance) and an inelastic velocity transfer so boxes do diff --git a/js/physics/collision/index.js b/js/physics/collision/index.js index ad64210..a6ed022 100644 --- a/js/physics/collision/index.js +++ b/js/physics/collision/index.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- physics/collision/index.js (submodule: collision) - * RC-375D84 S-2 modularization (behavior-neutral split of the original + * Modularization pass (behavior-neutral split of the original * js/core.js monolith). Installs its units into ONE shared physics scope `P` * that every physics submodule sees -- the same single-scope semantics the * monolith had. Must load before js/physics/index.js (the aggregator that @@ -32,7 +32,7 @@ ent.vy -= ny * rel * 1.4; if (isEnemy) { // crush damage: a fast box hurts what it lands on. - // S-4 (RC-375D84): the direction gate is velocity-aware -- the BOX + // Added in the physics pass: the direction gate is velocity-aware -- the BOX // must be the body moving into the enemy (boxAlong > 30). The old gate // (ny < -0.3, enemy above the box) only let SIDE plows crush: a block // dropped STRAIGHT DOWN onto an enemy never registered ("falling @@ -145,7 +145,7 @@ // per-target hit cooldown so one pass-through = one damage instance if (g.time - (e.lastHit || -9) > 0.12) { var speed = Math.hypot(b.vx, b.vy); - // retuned 2026-09-28 (RC-0D193E, Commander bug #3): the old curve + // retuned 2026-09-28 (playtest finding): the old curve // gave ~0.9 dmg to a soft wren lob — a mogg (hp1) could not be // killed without a perfect full-power hit. Bumped so a decent // medium shot connects; the king (hp3) still survives one @@ -186,7 +186,7 @@ var relv2 = (c.vy - a.vy) * n2; if (relv2 < -P.REST_VEL_CLAMP) { // real impact: inelastic common velocity. - // S-4 (RC-375D84): mass-WEIGHTED (was a plain (a.vy+c.vy)/2), so a + // Added in the physics pass: mass-WEIGHTED (was a plain (a.vy+c.vy)/2), so a // heavy block landing on a light plank transfers its real momentum // through the stack (chain-crush). A 22.6-mass stone at v over a // 7.2-mass plank now drives the plank to ~0.76v instead of 0.5v. diff --git a/js/physics/constants/agents.md b/js/physics/constants/agents.md index 30baf80..3118ac6 100644 --- a/js/physics/constants/agents.md +++ b/js/physics/constants/agents.md @@ -1,5 +1,5 @@ # js/physics/constants/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. Installs into `P`: WORLD, VIEW, MATERIALS, BIRD_TYPES, ENEMY_TYPES, GRAVITY, LAUNCH_POWER, MAX_PULL, MIN_PULL, AIR_DRAG, BIRD_BOUNCE, BIRD_GROUND_FRICTION, diff --git a/js/physics/constants/index.js b/js/physics/constants/index.js index a619a81..3e30711 100644 --- a/js/physics/constants/index.js +++ b/js/physics/constants/index.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- physics/constants/index.js (submodule: constants) - * RC-375D84 S-2 modularization (behavior-neutral split of the original + * Modularization pass (behavior-neutral split of the original * js/core.js monolith). Installs its units into ONE shared physics scope `P` * that every physics submodule sees -- the same single-scope semantics the * monolith had. Must load before js/physics/index.js (the aggregator that @@ -19,7 +19,7 @@ P.MATERIALS = { wood: { hp: 1300, density: 1.0, restitution: 0.18, dmgFactor: 0.9, score: 500, maxVel: 520 }, ice: { hp: 480, density: 0.7, restitution: 0.05, dmgFactor: 1.6, score: 800, maxVel: 560 }, - // stone: retuned 2026-09-28 (RC-0D193E, Commander bug #3 "blocks and pig + // stone: retuned 2026-09-28 (playtest finding: "blocks and pig // bad guys too hard to kill"): at hp 3400 with dmgFactor 0.35 a 70x70 // cube took ~11 full-power direct hits — stone walls were functionally // unbreakable, so every enemy behind one was unkillable. Now ~2-3 hits. diff --git a/js/physics/damage/agents.md b/js/physics/damage/agents.md index 9778ed2..92f0d5b 100644 --- a/js/physics/damage/agents.md +++ b/js/physics/damage/agents.md @@ -1,5 +1,5 @@ # js/physics/damage/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. Installs into `P`: damageBlock, damageEnemy. @@ -9,6 +9,6 @@ Installs into `P`: damageBlock, damageEnemy. here. - The balance spec (`test/balance.test.mjs`) asserts per-material break budgets and enemy kill budgets under full-power direct hits — that spec is the - contract for everything that deals damage. S-4 (RC-375D84, falling-object + contract for everything that deals damage. Falling-object impact damage) routes through these two functions so it inherits the spec. - Consumes: MATERIALS, ENEMY_TYPES. diff --git a/js/physics/damage/index.js b/js/physics/damage/index.js index be0ba70..94675b5 100644 --- a/js/physics/damage/index.js +++ b/js/physics/damage/index.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- physics/damage/index.js (submodule: damage) - * RC-375D84 S-2 modularization (behavior-neutral split of the original + * Modularization pass (behavior-neutral split of the original * js/core.js monolith). Installs its units into ONE shared physics scope `P` * that every physics submodule sees -- the same single-scope semantics the * monolith had. Must load before js/physics/index.js (the aggregator that diff --git a/js/physics/index.js b/js/physics/index.js index fab21b0..b7f1513 100644 --- a/js/physics/index.js +++ b/js/physics/index.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- physics/index.js (module: physics -- aggregator / public API) - * RC-375D84 S-2 modularization. This is the THIN AGGREGATOR for the physics + * Modularization pass. This is the THIN AGGREGATOR for the physics * module. The original js/core.js monolith is now split into submodules * (physics/constants, -/state, -/aim, -/abilities, -/damage, -/step, * -/collision, -/rules, -/camera); each installs its units into ONE shared diff --git a/js/physics/rules/agents.md b/js/physics/rules/agents.md index 51d7996..7a66242 100644 --- a/js/physics/rules/agents.md +++ b/js/physics/rules/agents.md @@ -1,10 +1,10 @@ # js/physics/rules/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. Installs into `P`: checkEndStates, birdsRemaining, starsFor. - Win/lose arbitration (called from step): all enemies dead -> won, no bird - left in play with enemies alive -> lost. S-5 (RC-375D84, landed 2026-09-29): + left in play with enemies alive -> lost. S-5 (added 2026-09-29): a LANDED bird counts as a bird still in play — do not declare `lost` while one is out in the world (it can still fall, smash and kill). `starsFor(g)` maps score to the level's three star thresholds. diff --git a/js/physics/rules/index.js b/js/physics/rules/index.js index 8c5652a..d074922 100644 --- a/js/physics/rules/index.js +++ b/js/physics/rules/index.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- physics/rules/index.js (submodule: rules) - * RC-375D84 S-2 modularization (behavior-neutral split of the original + * Modularization pass (behavior-neutral split of the original * js/core.js monolith). Installs its units into ONE shared physics scope `P` * that every physics submodule sees -- the same single-scope semantics the * monolith had. Must load before js/physics/index.js (the aggregator that diff --git a/js/physics/state/agents.md b/js/physics/state/agents.md index c1caa9d..67f7e55 100644 --- a/js/physics/state/agents.md +++ b/js/physics/state/agents.md @@ -1,5 +1,5 @@ # js/physics/state/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. Installs into `P`: createGame, makeBird, activeBird. @@ -8,7 +8,7 @@ Installs into `P`: createGame, makeBird, activeBird. scorePopups, hadEnemies, camera, status, activeBird, launched, wonAt, lostAt, stats`) is consumed by render, app and every test — extending is OK, silently dropping a key is not. -- Block/enemy/bird records are plain objects. S-3/S-5 (RC-375D84, landed) +- Block/enemy/bird records are plain objects. S-3/S-5 (added) extended them additively: blocks carry `ang`, `angVel`, `pivoting` (topple DOF) and birds gain the `landed` state (a live body under gravity while another bird is in play; see step/topple). Keep further extensions diff --git a/js/physics/state/index.js b/js/physics/state/index.js index 69d49a2..4102510 100644 --- a/js/physics/state/index.js +++ b/js/physics/state/index.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- physics/state/index.js (submodule: state) - * RC-375D84 S-2 modularization (behavior-neutral split of the original + * Modularization pass (behavior-neutral split of the original * js/core.js monolith). Installs its units into ONE shared physics scope `P` * that every physics submodule sees -- the same single-scope semantics the * monolith had. Must load before js/physics/index.js (the aggregator that diff --git a/js/physics/step/agents.md b/js/physics/step/agents.md index edb5aef..c3f4daa 100644 --- a/js/physics/step/agents.md +++ b/js/physics/step/agents.md @@ -1,5 +1,5 @@ # js/physics/step/ — agents.md -RC-375D84 S-2. Module map: `MODULES.md`; runtime model: `architecture.md`. +Part of the modularization pass. Module map: `MODULES.md`; runtime model: `architecture.md`. Installs into `P`: step, stepPhysics, spendBird. **The physics hub.** @@ -10,7 +10,7 @@ Installs into `P`: step, stepPhysics, spendBird. **The physics hub.** BEFORE nothing — but all of its callees must exist in `P` by the time `step` runs (they do, because install order is load order). Keep it that way. - `spendBird` retires a bird that is GONE (aged out / off the island). S-5 - (RC-375D84, landed 2026-09-29) split the settle branch: a bird that settles + (added 2026-09-29) split the settle branch: a bird that settles while ANOTHER bird is in play becomes `landed` — a live body integrated every frame in `stepPhysics` (real gravity, no lifetime timer) that falls when its support breaks, chain-damages boards, and can kill enemies. The LAST diff --git a/js/physics/step/index.js b/js/physics/step/index.js index bc09f2f..ff57409 100644 --- a/js/physics/step/index.js +++ b/js/physics/step/index.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- physics/step/index.js (submodule: step) - * RC-375D84 S-2 modularization (behavior-neutral split of the original + * Modularization pass (behavior-neutral split of the original * js/core.js monolith). Installs its units into ONE shared physics scope `P` * that every physics submodule sees -- the same single-scope semantics the * monolith had. Must load before js/physics/index.js (the aggregator that @@ -61,7 +61,7 @@ // gone for good (off the island or aged out) -> spent, next bird loads P.spendBird(g, b); } else if (b.settleTimer > P.SETTLE_TIME) { - // S-5 (RC-375D84, Q2): the "another bird is already active" window. + // Landed-bird gravity behavior: the "another bird is already active" window. // A bird that settles while MORE birds are in play (queued or active) // is NOT a corpse: it becomes a LANDED body -- real continuous // gravity, no lifetime timer. It falls if its support breaks, diff --git a/js/physics/topple/agents.md b/js/physics/topple/agents.md index bdd7872..540aabf 100644 --- a/js/physics/topple/agents.md +++ b/js/physics/topple/agents.md @@ -1,5 +1,5 @@ # js/physics/topple/ — agents.md -RC-375D84 S-3 (2026-09-29). Module map: `MODULES.md`; runtime model: `architecture.md`. +Added in the physics pass (2026-09-29). Module map: `MODULES.md`; runtime model: `architecture.md`. Installs into `P`: toppleBlocks, blockSupportSpan, beginTopple, integrateTopple, landTopple, topplePathDamage, settleBack, supportStillAlive. diff --git a/js/physics/topple/index.js b/js/physics/topple/index.js index 147fe3a..466ee93 100644 --- a/js/physics/topple/index.js +++ b/js/physics/topple/index.js @@ -1,5 +1,5 @@ /* ISLE SLINGERS -- physics/topple/index.js (submodule: topple) - * RC-375D84 S-3. Boards tip and topple end-over-end (Commander Q1: FULL + * Board tip/topple physics. Boards tip and topple end-over-end (design call: FULL * CONTINUOUS ROTATION). A resting block whose centre of mass lies OUTSIDE the * x-span of whatever supports it (ground, or the top face of a block below) * begins pivoting about the support-edge corner on the side the COM leans to. diff --git a/js/render/agents.md b/js/render/agents.md index c6dc387..9333cb8 100644 --- a/js/render/agents.md +++ b/js/render/agents.md @@ -8,7 +8,7 @@ Self-contained module. Exports `ISLE_RENDERER = { Renderer, VIEW_W, VIEW_H }`. scenery, popups). It never mutates `state`. - VIEW_W/VIEW_H (1280x720) mirror physics constants VIEW — keep them in sync; the camera lives in state (set by physics/camera), render only applies it. -- S-3/S-5 (RC-375D84, landed): `drawBlock` rotates about the block COM when +- S-3/S-5 (added): `drawBlock` rotates about the block COM when `bl.ang` is set (no-op at `ang=0`, so un-tipped blocks draw identically to the pre-S-3 output), and `drawBird` renders `landed` birds as live bodies alongside the `spent` corpses. The DOM-stub test only checks that drawing diff --git a/package.json b/package.json index 3d03c45..f34eba7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "isle-slingers", "version": "3.0.0", "private": true, - "description": "Isle Slingers — an original browser slingshot physics game (RC-0D193E). No dependencies; opens directly in a browser.", + "description": "Isle Slingers — an original browser slingshot physics game. No dependencies; opens directly in a browser.", "scripts": { "test": "node test/sim.test.mjs && node test/page.test.mjs && node test/balance.test.mjs && node test/topple.test.mjs && node test/knifeedge.test.mjs && node test/fallingkill.test.mjs && node test/landedbird.test.mjs", "start": "python3 -m http.server 8000"