Clean release scrub: remove internal fleet references
Removes internal process references so the repo is presentable to an outside audience: dropped the internal build-retrospective doc (process log with internal terms, not useful to an outside reader), and reworded remaining internal references in code comments and module docs (agents.md cards, architecture.md, MODULES.md, index.html, package.json) to generic descriptions of what changed and when, with no behavior change -- full 47/47 suite re-run green after the scrub, every edit verified comment/doc-only via diff. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
1bcb501335
commit
dff5dd5b30
40 changed files with 55 additions and 487 deletions
432
BUILD-RECORD.md
432
BUILD-RECORD.md
|
|
@ -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.
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Isle Slingers — MODULES.md (master module list)
|
# 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.
|
submodule carries an `agents.md` describing its surface and invariants.
|
||||||
Load contract (order is load-bearing) is in `architecture.md` and encoded in
|
Load contract (order is load-bearing) is in `architecture.md` and encoded in
|
||||||
`index.html` + `test/page.test.mjs`.
|
`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 | — |
|
| helpers | wood, ice, stone, mogg, grum, king, SLING | — |
|
||||||
| data/level-N | one level object each (returns it) | 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`).
|
- **topple** (S-3): blocks carry a rotational DOF (`ang`, `angVel`, `pivoting`).
|
||||||
A resting block whose COM is past its support span pivots about the
|
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
|
support-edge corner (full continuous rotation, Q1) and either lands on the
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Isle Slingers — Architecture
|
# Isle Slingers — Architecture
|
||||||
|
|
||||||
Original architecture for a fully offline, canvas-rendered slingshot game.
|
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
|
JavaScript is a **modularized tree**: folders are modules, subfolders are
|
||||||
submodules, and every module/submodule carries its own `agents.md`.
|
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
|
The simulation is a deterministic impulse-ish engine: bodies integrate under
|
||||||
gravity at fixed 1/120 s, contacts resolve positionally, and damage routes
|
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:
|
added, and where it lives:
|
||||||
|
|
||||||
- **Boards tip and topple (S-3, Q1 = full continuous rotation).** Blocks carry
|
- **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
|
## Conventions for agents working in this tree
|
||||||
|
|
||||||
1. Change a physics rule -> edit the one submodule that owns it (see each
|
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
|
`agents.md`); re-run `npm test` (47/47) and re-check the golden differential
|
||||||
(`/opt/data/s2_golden.mjs` against `/opt/data/core.orig.js`) when touching
|
against the pre-modularization core when touching anything that predates
|
||||||
anything that predates RC-375D84 behavior.
|
this modularization/physics pass.
|
||||||
2. Never re-introduce a flat file for a unit that has a submodule folder.
|
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
|
3. Keep `index.html` script order in lockstep with `test/page.test.mjs`'s
|
||||||
`FILES` list — both encode the load contract.
|
`FILES` list — both encode the load contract.
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- RC-375D84 S-2: modularized tree. Order matters:
|
<!-- Modularized tree. Order matters:
|
||||||
physics submodules (shared scope) -> physics aggregator (ISLE_CORE)
|
physics submodules (shared scope) -> physics aggregator (ISLE_CORE)
|
||||||
-> levels helpers + data (shared scope) -> levels aggregator (ISLE_LEVELS)
|
-> levels helpers + data (shared scope) -> levels aggregator (ISLE_LEVELS)
|
||||||
-> audio -> render -> app -->
|
-> audio -> render -> app -->
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/app/ — agents.md
|
# 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.
|
Self-contained module — the page. Owns DOM, input, screens and the main loop.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/audio/ — agents.md
|
# 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 }`.
|
Self-contained module. Exports `ISLE_AUDIO = { AudioEngine }`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/levels/ — agents.md
|
# 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`.
|
The levels **module** (aggregator). Shared scope `L`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/levels/data/ — agents.md
|
# 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`).
|
Level definitions, one file per level (`level-1.js`..`level-5.js`).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- levels/data/level-1.js (submodule: level-1)
|
/* 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.
|
* Installer: returns the level object built from the shared helpers scope L.
|
||||||
*/
|
*/
|
||||||
(function (root, factory) {
|
(function (root, factory) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- levels/data/level-2.js (submodule: level-2)
|
/* 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.
|
* Installer: returns the level object built from the shared helpers scope L.
|
||||||
*/
|
*/
|
||||||
(function (root, factory) {
|
(function (root, factory) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- levels/data/level-3.js (submodule: level-3)
|
/* 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.
|
* Installer: returns the level object built from the shared helpers scope L.
|
||||||
*/
|
*/
|
||||||
(function (root, factory) {
|
(function (root, factory) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- levels/data/level-4.js (submodule: level-4)
|
/* 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.
|
* Installer: returns the level object built from the shared helpers scope L.
|
||||||
*/
|
*/
|
||||||
(function (root, factory) {
|
(function (root, factory) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- levels/data/level-5.js (submodule: level-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.
|
* Installer: returns the level object built from the shared helpers scope L.
|
||||||
*/
|
*/
|
||||||
(function (root, factory) {
|
(function (root, factory) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/levels/helpers/ — agents.md
|
# 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
|
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}.
|
records), mogg, grum, king (enemy factories) and the SLING anchor {x:260,y:745}.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- levels/helpers/index.js (submodule: helpers)
|
/* 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`;
|
* 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.
|
* js/levels/index.js loads it first, then the level-N data submodules.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- levels/index.js (module: levels -- aggregator / public API)
|
/* 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/helpers/ - block/enemy constructors + SLING anchor (shared scope L)
|
||||||
* levels/data/level-N.js - one level definition each (uses the shared scope)
|
* 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
|
* This aggregator loads the parts in order, builds the LEVELS array, and
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/physics/abilities/ — agents.md
|
# 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.
|
Installs into `P`: launch, triggerAbility.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- physics/abilities/index.js (submodule: abilities)
|
/* 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`
|
* js/core.js monolith). Installs its units into ONE shared physics scope `P`
|
||||||
* that every physics submodule sees -- the same single-scope semantics the
|
* that every physics submodule sees -- the same single-scope semantics the
|
||||||
* monolith had. Must load before js/physics/index.js (the aggregator that
|
* monolith had. Must load before js/physics/index.js (the aggregator that
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/physics/ — agents.md
|
# 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.
|
The physics **module** (aggregator). Deterministic, DOM-free.
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ The physics **module** (aggregator). Deterministic, DOM-free.
|
||||||
(`activeBird`, `trajectory`, `aimVector`, `starsFor`, `birdsRemaining`).
|
(`activeBird`, `trajectory`, `aimVector`, `starsFor`, `birdsRemaining`).
|
||||||
- Invariants: fixed-timestep consumer (tests run 1/120 s); no `Math.random()`
|
- Invariants: fixed-timestep consumer (tests run 1/120 s); no `Math.random()`
|
||||||
in gameplay paths (determinism is a test arm); no DOM access.
|
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
|
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),
|
rotation), collision/ owns velocity-aware crush + mass-weighted stacks (S-4),
|
||||||
step/ owns the landed-bird state and its integration (S-5).
|
step/ owns the landed-bird state and its integration (S-5).
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/physics/aim/ — agents.md
|
# 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.
|
Installs into `P`: aimVector, trajectory.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- physics/aim/index.js (submodule: aim)
|
/* 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`
|
* js/core.js monolith). Installs its units into ONE shared physics scope `P`
|
||||||
* that every physics submodule sees -- the same single-scope semantics the
|
* that every physics submodule sees -- the same single-scope semantics the
|
||||||
* monolith had. Must load before js/physics/index.js (the aggregator that
|
* monolith had. Must load before js/physics/index.js (the aggregator that
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/physics/camera/ — agents.md
|
# 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.
|
Installs into `P`: updatePopups, updateCameraTarget, decayCamera.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- physics/camera/index.js (submodule: camera)
|
/* 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`
|
* js/core.js monolith). Installs its units into ONE shared physics scope `P`
|
||||||
* that every physics submodule sees -- the same single-scope semantics the
|
* that every physics submodule sees -- the same single-scope semantics the
|
||||||
* monolith had. Must load before js/physics/index.js (the aggregator that
|
* monolith had. Must load before js/physics/index.js (the aggregator that
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/physics/collision/ — agents.md
|
# 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,
|
Installs into `P`: crushCircleVsBox, collideBirdWorld, collideBirdBlocks,
|
||||||
collideBirdEnemies, REST_VEL_CLAMP, resolveBoxPair.
|
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
|
- Contact resolution for circles (birds, enemies) vs axis-aligned blocks and
|
||||||
block-block pairs. `resolveBoxPair` is the box-box rest/impulse resolver;
|
block-block pairs. `resolveBoxPair` is the box-box rest/impulse resolver;
|
||||||
`crushCircleVsBox` is the fast-falling-onto-structure damage path.
|
`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
|
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
|
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
|
landing = one crush instance) and an inelastic velocity transfer so boxes do
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- physics/collision/index.js (submodule: collision)
|
/* 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`
|
* js/core.js monolith). Installs its units into ONE shared physics scope `P`
|
||||||
* that every physics submodule sees -- the same single-scope semantics the
|
* that every physics submodule sees -- the same single-scope semantics the
|
||||||
* monolith had. Must load before js/physics/index.js (the aggregator that
|
* monolith had. Must load before js/physics/index.js (the aggregator that
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
ent.vy -= ny * rel * 1.4;
|
ent.vy -= ny * rel * 1.4;
|
||||||
if (isEnemy) {
|
if (isEnemy) {
|
||||||
// crush damage: a fast box hurts what it lands on.
|
// 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
|
// 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
|
// (ny < -0.3, enemy above the box) only let SIDE plows crush: a block
|
||||||
// dropped STRAIGHT DOWN onto an enemy never registered ("falling
|
// dropped STRAIGHT DOWN onto an enemy never registered ("falling
|
||||||
|
|
@ -145,7 +145,7 @@
|
||||||
// per-target hit cooldown so one pass-through = one damage instance
|
// per-target hit cooldown so one pass-through = one damage instance
|
||||||
if (g.time - (e.lastHit || -9) > 0.12) {
|
if (g.time - (e.lastHit || -9) > 0.12) {
|
||||||
var speed = Math.hypot(b.vx, b.vy);
|
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
|
// 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
|
// killed without a perfect full-power hit. Bumped so a decent
|
||||||
// medium shot connects; the king (hp3) still survives one
|
// medium shot connects; the king (hp3) still survives one
|
||||||
|
|
@ -186,7 +186,7 @@
|
||||||
var relv2 = (c.vy - a.vy) * n2;
|
var relv2 = (c.vy - a.vy) * n2;
|
||||||
if (relv2 < -P.REST_VEL_CLAMP) {
|
if (relv2 < -P.REST_VEL_CLAMP) {
|
||||||
// real impact: inelastic common velocity.
|
// 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
|
// 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
|
// 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.
|
// 7.2-mass plank now drives the plank to ~0.76v instead of 0.5v.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/physics/constants/ — agents.md
|
# 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,
|
Installs into `P`: WORLD, VIEW, MATERIALS, BIRD_TYPES, ENEMY_TYPES, GRAVITY,
|
||||||
LAUNCH_POWER, MAX_PULL, MIN_PULL, AIR_DRAG, BIRD_BOUNCE, BIRD_GROUND_FRICTION,
|
LAUNCH_POWER, MAX_PULL, MIN_PULL, AIR_DRAG, BIRD_BOUNCE, BIRD_GROUND_FRICTION,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- physics/constants/index.js (submodule: constants)
|
/* 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`
|
* js/core.js monolith). Installs its units into ONE shared physics scope `P`
|
||||||
* that every physics submodule sees -- the same single-scope semantics the
|
* that every physics submodule sees -- the same single-scope semantics the
|
||||||
* monolith had. Must load before js/physics/index.js (the aggregator that
|
* monolith had. Must load before js/physics/index.js (the aggregator that
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
P.MATERIALS = {
|
P.MATERIALS = {
|
||||||
wood: { hp: 1300, density: 1.0, restitution: 0.18, dmgFactor: 0.9, score: 500, maxVel: 520 },
|
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 },
|
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
|
// 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
|
// cube took ~11 full-power direct hits — stone walls were functionally
|
||||||
// unbreakable, so every enemy behind one was unkillable. Now ~2-3 hits.
|
// unbreakable, so every enemy behind one was unkillable. Now ~2-3 hits.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/physics/damage/ — agents.md
|
# 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.
|
Installs into `P`: damageBlock, damageEnemy.
|
||||||
|
|
||||||
|
|
@ -9,6 +9,6 @@ Installs into `P`: damageBlock, damageEnemy.
|
||||||
here.
|
here.
|
||||||
- The balance spec (`test/balance.test.mjs`) asserts per-material break budgets
|
- 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
|
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.
|
impact damage) routes through these two functions so it inherits the spec.
|
||||||
- Consumes: MATERIALS, ENEMY_TYPES.
|
- Consumes: MATERIALS, ENEMY_TYPES.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- physics/damage/index.js (submodule: damage)
|
/* 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`
|
* js/core.js monolith). Installs its units into ONE shared physics scope `P`
|
||||||
* that every physics submodule sees -- the same single-scope semantics the
|
* that every physics submodule sees -- the same single-scope semantics the
|
||||||
* monolith had. Must load before js/physics/index.js (the aggregator that
|
* monolith had. Must load before js/physics/index.js (the aggregator that
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- physics/index.js (module: physics -- aggregator / public API)
|
/* 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
|
* module. The original js/core.js monolith is now split into submodules
|
||||||
* (physics/constants, -/state, -/aim, -/abilities, -/damage, -/step,
|
* (physics/constants, -/state, -/aim, -/abilities, -/damage, -/step,
|
||||||
* -/collision, -/rules, -/camera); each installs its units into ONE shared
|
* -/collision, -/rules, -/camera); each installs its units into ONE shared
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
# js/physics/rules/ — agents.md
|
# 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.
|
Installs into `P`: checkEndStates, birdsRemaining, starsFor.
|
||||||
|
|
||||||
- Win/lose arbitration (called from step): all enemies dead -> won, no bird
|
- 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
|
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)`
|
one is out in the world (it can still fall, smash and kill). `starsFor(g)`
|
||||||
maps score to the level's three star thresholds.
|
maps score to the level's three star thresholds.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- physics/rules/index.js (submodule: rules)
|
/* 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`
|
* js/core.js monolith). Installs its units into ONE shared physics scope `P`
|
||||||
* that every physics submodule sees -- the same single-scope semantics the
|
* that every physics submodule sees -- the same single-scope semantics the
|
||||||
* monolith had. Must load before js/physics/index.js (the aggregator that
|
* monolith had. Must load before js/physics/index.js (the aggregator that
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/physics/state/ — agents.md
|
# 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.
|
Installs into `P`: createGame, makeBird, activeBird.
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ Installs into `P`: createGame, makeBird, activeBird.
|
||||||
scorePopups, hadEnemies, camera, status, activeBird, launched, wonAt, lostAt,
|
scorePopups, hadEnemies, camera, status, activeBird, launched, wonAt, lostAt,
|
||||||
stats`) is consumed by render, app and every test — extending is OK, silently
|
stats`) is consumed by render, app and every test — extending is OK, silently
|
||||||
dropping a key is not.
|
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`
|
extended them additively: blocks carry `ang`, `angVel`, `pivoting`
|
||||||
(topple DOF) and birds gain the `landed` state (a live body under gravity
|
(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
|
while another bird is in play; see step/topple). Keep further extensions
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- physics/state/index.js (submodule: state)
|
/* 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`
|
* js/core.js monolith). Installs its units into ONE shared physics scope `P`
|
||||||
* that every physics submodule sees -- the same single-scope semantics the
|
* that every physics submodule sees -- the same single-scope semantics the
|
||||||
* monolith had. Must load before js/physics/index.js (the aggregator that
|
* monolith had. Must load before js/physics/index.js (the aggregator that
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/physics/step/ — agents.md
|
# 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.**
|
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`
|
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.
|
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
|
- `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
|
while ANOTHER bird is in play becomes `landed` — a live body integrated
|
||||||
every frame in `stepPhysics` (real gravity, no lifetime timer) that falls
|
every frame in `stepPhysics` (real gravity, no lifetime timer) that falls
|
||||||
when its support breaks, chain-damages boards, and can kill enemies. The LAST
|
when its support breaks, chain-damages boards, and can kill enemies. The LAST
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- physics/step/index.js (submodule: step)
|
/* 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`
|
* js/core.js monolith). Installs its units into ONE shared physics scope `P`
|
||||||
* that every physics submodule sees -- the same single-scope semantics the
|
* that every physics submodule sees -- the same single-scope semantics the
|
||||||
* monolith had. Must load before js/physics/index.js (the aggregator that
|
* 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
|
// gone for good (off the island or aged out) -> spent, next bird loads
|
||||||
P.spendBird(g, b);
|
P.spendBird(g, b);
|
||||||
} else if (b.settleTimer > P.SETTLE_TIME) {
|
} 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)
|
// A bird that settles while MORE birds are in play (queued or active)
|
||||||
// is NOT a corpse: it becomes a LANDED body -- real continuous
|
// is NOT a corpse: it becomes a LANDED body -- real continuous
|
||||||
// gravity, no lifetime timer. It falls if its support breaks,
|
// gravity, no lifetime timer. It falls if its support breaks,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# js/physics/topple/ — agents.md
|
# 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,
|
Installs into `P`: toppleBlocks, blockSupportSpan, beginTopple, integrateTopple,
|
||||||
landTopple, topplePathDamage, settleBack, supportStillAlive.
|
landTopple, topplePathDamage, settleBack, supportStillAlive.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* ISLE SLINGERS -- physics/topple/index.js (submodule: topple)
|
/* 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
|
* 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)
|
* 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.
|
* begins pivoting about the support-edge corner on the side the COM leans to.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ Self-contained module. Exports `ISLE_RENDERER = { Renderer, VIEW_W, VIEW_H }`.
|
||||||
scenery, popups). It never mutates `state`.
|
scenery, popups). It never mutates `state`.
|
||||||
- VIEW_W/VIEW_H (1280x720) mirror physics constants VIEW — keep them in sync;
|
- 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.
|
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
|
`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
|
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
|
alongside the `spent` corpses. The DOM-stub test only checks that drawing
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "isle-slingers",
|
"name": "isle-slingers",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"private": true,
|
"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": {
|
"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",
|
"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"
|
"start": "python3 -m http.server 8000"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue