diff --git a/CHANGELOG.md b/CHANGELOG.md index ab01474..cf3db0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,1961 +8,192 @@ The native shim's ABI is tracked separately by `b2Version()` (currently `4`). ## [Unreleased] -### Added - -- **Slingshot: a transition COVER between levels (carrying the platformer's - transition-card lesson across).** A review of the other demo stacks found the - slingshot had the same "visible build seam" the platformer just fixed - and worse: - every level change ran `b2kClear`/`b2kTeardown`/`sgWipeStage` *on screen* before - `lock screen`, and each transition path *hid* its translucent banner overlay right - before tearing down, so the player watched the old tower dismantle against a bare - card. Added a dedicated **opaque cover** (`sgCard` + `sgCardText`, built once in - `buildSgUI`, `kSgUIVersion` 1 -> 2) raised above everything: `sgCardShow` covers - with a "LEVEL n / 3" title *before* the teardown (in all four transition callers - - retry, next-level, win-replay, the 1-3 level jumps), the rebuild paints behind it, - and `sgCardReveal` holds a beat then fades it out via a generation-guarded - `send`-driven `blendLevel` ramp - the slingshot twin of the platformer's - `pfCardShow`/`pfCardReveal`, minus the illustrated art (a plain tinted cover is - enough). Kept separate from the translucent `sgShade` banner so neither juggles the - other's blendLevel; the initial `openCard` build is left to the menu (a - `gSgCardActive` gate). Example-side only; gates clean. **Needs an OXT pass** to - confirm the cover masks the rebuild and to tune the hold/fade timing. The same - review also **removed the dead `newStaticBar` factory** from the demo (defined, - never called) and confirmed the other stacks need no change: the contraption builder - is already playbook-clean, and spike-gamekit is intentional throwaway scaffolding. - -- **Platformer: ILLUSTRATED biome cards (real game art on the transition card).** The - level cards are no longer a solid tint + sparse text: each is now a little composed - scene built from the already-loaded atlases (zero new asset files). Behind the title - text sits the level's **real Kenney biome backdrop** (L1-L5; the dark cavern/keep - keep their tint) under a dimming scrim, and a **cast row of real sprites** - the - chosen hero + a biome-representative foe + a coin + the goal flag - stands on a - ground band, previewing what's in that world. Implementation notes: - - Art is cloned onto the CARD via the proven `pfTextureSlab` throwaway-sprite path - (`pfCardArtImage`), so it is camera-free chrome regardless of the live camera; the - clones are resized directly (no `b2kSheetScale` juggling, so the game's sheet - scales are never touched). - - Every piece is **independently optional** - a missing sheet or a failed slice just - drops that layer, and the always-present opaque `pfCardShade` tint base still - guarantees the cover hides the build. Worst case is "needs tuning", never a broken - cover. - - The whole stack (tint base + art layers + title text, tracked in `gCardArt`) fades - out together; each layer carries its resting blend in `uCardBaseBlend` so the - scrim's dimming holds through the ramp instead of flashing opaque. - Biome backdrops/foes, the cast layout, scrim/ground tints, and the ground line are - first-pass and tunable in OXT. Example-side only (no Kit touch). Static gates + - audit clean. **Needs an OXT pass** to confirm the slice-clone art renders/positions - on the engine and to tune the composition. -- **Platformer: TRANSITION CARD + boot TITLE screen + recomposed WIN card (the - polish pass's headline - `docs/platformer-polish-plan.md` §2 / §2.4).** The single - biggest "demo-not-game" tell is gone: a full-screen opaque overlay now COVERS - every level teardown + rebuild, so the player never sees the old level clear or the - new one assemble. One card-level control (`pfCardShade` + `pfCardText`, built once - in `buildPfUI`, `kPfUIVersion` 10 -> 11, raised above the camera viewport like the - pause overlay so it never scrolls) does three jobs: - - **Cover -> reveal.** `pfStartGame` raises the opaque biome card (`pfCardCover`) - BEFORE the teardown trio (`b2kClear` / `b2kTeardown` / `pfWipeStage`), so the - whole build paints behind it; after `b2kStart` it holds a beat then FADES out - (`pfCardReveal` -> a `send`-driven `blendLevel` ramp, generation-guarded so a - fresh cover invalidates a stale fade). The fade is decoupled from the frame loop - on purpose - it never touches the intro code a past pan rework broke (gotcha 11). - Every caller gets it for free: level start, level -> level advance, R-restart, the - dev picker, and the title's SPACE-start. Death / respawn keeps its light - camera-shake (no full card, by design). - - **A boot TITLE screen.** The demo no longer drops straight into L1: it opens on a - "B O X 2 D X T" card with a **live hero preview** (the chosen skin's idle frame - - a camera-off Kit sprite is a plain card control) and the **1-5 hero chooser** moved - HERE, where picking can't accidentally restart a level. SPACE / RETURN starts a - fresh run. The in-level 1-5 binding (and `pfPickHero`) is retired accordingly; the - pause overlay's hero line now points at the title. - - **A recomposed WIN card.** The final-run summary is restyled in the level-card - visual language (a deeper, more central panel) and now names the **chosen hero** - alongside time / falls / gems / stars / coin-score / the flawless callout. - - **Bug fix carried along.** A single `pfResetRun` now zeroes EVERY whole-run bank; - the dev picker and "Play again" previously left the coin-score + star banks stale, - inflating a second run's win screen. - Biome card tints / flavours + the hold-and-fade timings are first-pass and tunable - in OXT. Example-side only - no Kit touch, so no harness bump and the embedded Kit - stays in sync. Static gates clean, audit 0 findings. **Needs an OXT pass** to - confirm the cover fully masks the build, the fade feel, the title hero preview, and - the card composition. -- **Platformer: CHARACTER SELECT + a hero portrait (asset-expansion Phase G).** The - hero is no longer locked to the beige skin: press **1-5** to pick - **beige / green / pink / purple / yellow** (`gHeroSkin`). The skin is one word the - hero anim defs and the creation frame interpolate, so picking re-skins the whole - character - idle/walk/jump/duck/climb/hurt/win - on a clean rebuild (the - controller suppresses redundant anim replays, so a rebuild is the reliable swap; - `pfLoadSheets` already runs per build). The choice **persists across levels and - restarts**. A **hero PORTRAIT** (`hud_player_`) anchors the bottom-left - status corner with the heart row shifted to its right, rebuilt each level so it - always shows the current hero. The splash and the ESC pause overlay gained the - 1-5 hint (`kPfUIVersion` bumped). All 4 alternate skins + the beige original carry - the full 8-frame anim set. Example-side only; static gates clean, audit 0 findings. - Needs an OXT pass to confirm each skin animates cleanly. -- **Platformer: COLLECTIBLES - coin tiers + a hidden star per level (asset-expansion - Phase F, completing it).** - - **Coin tiers.** Coins are now bronze/silver/gold worth **1/2/3** toward a bonus - SCORE (`gScore`/`gScoreTotal`), auto-assigned by height (higher = harder to - reach = worth more; `pfMakeCoin` takes an optional explicit tier). The tier - sets the art (`coin_bronze/silver/gold`), its own spin anim, and the fallback - colour. The flag still gates on the coin **COUNT** (unchanged - "collect all to - gild the flag"); the weighted score is a pure bonus, banked across the run and - shown on the win screen. - - **A hidden STAR per level.** One `star` challenge pickup per level - (`pfMakeStar`), a distinct rarer collectible banked to the win screen with its - own dim->lit HUD slot (top-right). It NEVER gates the flag, so it sits on the - level's hardest route without locking progress. **Every star is placed ON a - proven-reachable standing surface** - the highest cloud-hop peak (L1), a - ladder-reached bonus ledge (L2), a snow/sand/dirt bonus cloud (L3/L5/L6), the - mid-lava stepping-stone you cross on (L4, grabbed between serpent rises), and a - mid-climb keep ledge (L7) - never a bare mid-air apex. The audit gained a star - check (in-bounds, ON a surface ~56px below it, clear of coins/gems/goal) and - **fixed a latent cloud-parser bug** (the `2` in `b2kSmoothGround` was read as a - coordinate, corrupting every parsed cloud span - harmless until the star - surface check relied on it). - - Example-side only; static gates clean, audit 0 findings across 7 levels. Needs - an OXT pass to confirm each star reads as reachable and the coin tier colours - look right. -- **Platformer: HEALTH - a forgiving five-heart buffer (asset-expansion Phase F).** - The hero starts each level with **5 full hearts** (`kHearts`), drawn as a heart - row (`hud_heart`/`hud_heart_empty`) low along the bottom-left of the art HUD. A - contact hit (`pfOuch`, the single knockback chokepoint) spends ONE pip; the - existing mercy window between hits means at most one pip per hit, so the buffer - can't drain in a single frame of overlap. Emptying the row makes that hit - LETHAL - it hands off to the respawn (`pfHurt`), which returns the hero to the - checkpoint and REFILLS the buffer (falls and the kill-plane refill it too), so - the hero never hard-fails on a contact, it just loses ground. `pfUpdateHearts` - redraws the row on change only (the 4 Hz / write-on-change rule); the debug - overlay gained an `HP n/5` field. Hearts reset to full each level. Example-side - only; static gates clean, audit 0 findings. -- **Platformer: the GOO SERPENT in a slime-pool beat + the serpent generalized - (asset-expansion Phase E, continued).** L6's PIT2 spike chasm becomes a **toxic - GOO POOL** (new `pfMakeSlimePool` — a green goo rect over a knockback hurt - sensor, the slime-biome twin of `pfMakeLava`) where a rearing **`snakeSlime` - serpent** rises out of the goo, arcs across and sinks back in, peaking at - jump-arc height so you time your double-jump for when it has sunk (a mistimed - leap or a slip into the goo is a recoverable knockback). The L4 lava serpent's - maker is generalized to `pfMakeSerpent pL,pR,pSurfY,pPeakY,pFrame,pAnim` (tick - `pfTickSerpent`), driving both the lava (`snakeLava`) and goo (`snakeSlime`) - skins from one code path (single-instance — one serpent per level, reset on each - build). Plus two more plain **snake** placements for variety: a meadow snake on - L1 and a sand snake on L5. Self-gates on `gSpooksOK`; static gates clean, audit - 0 findings. -- **Platformer: the LAVA SERPENT + a widened L4 lava pit (asset-expansion Phase - E).** L4's old collapsing bridge (which never read right) is **gone**; in its - place the lava pit is widened to a **512px chasm** (2944..3456, up from a 192px - strip) crossed in **two ~160px hops** over a middle **stepping-stone**, and a - rearing **LAVA SERPENT** (`snakeLava` art, bodiless) rises OUT of the lava, arcs - ACROSS it and sinks back in on a sine path, **peaking at the stepping-stone** so - it contests the very rock you must land on (time your hops between its rises). - `pfMakeSerpent` / `pfTickSerpent` (the generic serpent maker/tick): a pure - sprite created BEFORE the lava tiles so the y576 tile row occludes its submerged - lower body (it reads as rising from / sinking into the lava), with a visibility - toggle backing the - no-tile fallback; the head's strike zone is a proximity-poll knockback (the saw - rule, gotcha 16), never a stomp. The collapsing-bridge maker/tick/globals were - removed wholesale. Self-gates on `gSpooksOK` - absent the spooks sheet the pit is - simply a two-hop lava gap (always completable). -- **Platformer: SNAKES - the slither movement type (asset-expansion Phase E).** A - new slime-family kind `snake` with its own tick: it crawls the floor and - AUTO-REVERSES at a pit edge or wall (a single floor-probe `b2kOverlap` - ahead-and-below the body per frame), so you place it without hand-tuning a patrol - band - `pMinX/pMaxX` are just the outer safety bound. Stompable on top / hurts on - a side touch like a slime (kind "snake" falls through to the classic verdict), - and the Phase-D defeat juice (dead pose, poof, fade, pop) carries it for free. - `pfMakeSnake pIdx,pX,pMinX,pMaxX,pTopY` is the LOW (`snake.png`) crawler; the - tall rearing `snakeLava`/`snakeSlime` art is the lava serpent above. Spook-sheet - `.png` art, so it self-gates on `gSpooksOK`. Deployed on L3 (turns at the spike - pit) and L4's lava-pit approach (turns at the entry pit and the lava lip). -- **Platformer: spook slime/snake sprites sit ON the ground (alignment fix).** The - new spook-sheet skins (green/blue slime, snake) had inherited the FOES bind - offset (`pDY -6/-8`), which floated them ~9px: the foes art has soft, sparse - bottom rows that want a deep frame-sink, but the spook frames fill edge-to-edge - (zero transparent padding, measured against `enemies.png`), so feet-on-ground is - just `pFullH/2 - frameH*0.9/2`. Re-derived per frame (snake `pDY 3`, slimes `4`); - needs an OXT pass to confirm to the pixel (art alignment is statically - unverifiable). Example-side only; static gates clean, audit 0 findings. -- **Platformer: defeat-animation juice (asset-expansion Phase D).** Pure polish, - no new mechanics, on the one shared slime-family defeat path (so it lifts every - level at once). A stomped foe now **fades out** over its ~700ms linger - (`blendLevel` 0 -> ~96) instead of blinking off, and the spook foes show a - proper **dead** pose rather than a hit-flash: the bat `bat_hit.png` -> - `bat_dead.png`, the mimic `grassBlock_hit.png` -> `grassBlock_dead.png`. The - slimes/snail/block-slime already played their `_flat`/`_shell`/`_rest` squash - poses, and the telegraphing foes already idle on rest frames (`barnacle_*_rest`, - `block_rest`, `bat_hang`). The fade window is short and only a stomped foe or - two ever fade at once. - - **A stomp DUST-POOF** (more juice): four little pale motes arc out of a - squashed foe (`pfMakeDustPool`/`pfDustBurst`/`pfTickDust`, the pooled-debris - pattern - eight bodies parked off-world, flung on the stomp, re-parked ~0.5s - later, so nothing is created mid-game). The motes are `b2kSpawnBall` bodies - (no new art) born in the camera group, so they scroll with the world. Built - every level. - Example-side only; static gates clean, audit 0 findings. - - **A defeat POP + second SKINS** (more juice): the squashed art now also POPS - up on an ease-out arc (~50px) as it fades; and the bestiary gains variants - - a **green** and **blue** slime (the `spooks` `.png` skins, via `pfMakeCritter` - which now takes an optional sheet; `gSpooksOK`-gated, a normal slime without - it) reskin two L1 slimes, and the **ring worm** (`worm_ring_*`, native foes) - reskins L2's worm. `tools/audit-platformer.py` learned that two same-index - makers are a conditional reskin (`if gSpooksOK ... else ...`), not two foes. -- **Platformer: MULTI-KEY doors + a two-key puzzle in L2 (asset-expansion Phase C, - slice 3 - part 1).** `pfMakeKeyDoor` was single-instance (one door per level via - shared globals); it now appends to a per-door table, so a level can plant - several coloured key+door pairs. The hero carries a SET of keys (`gKeysHeld`, - shown in the HUD as `[KEYS yellow,blue]`); each key tags itself `uPfKeyWord` = - its colour, rides along the shoulder (fanned out when you hold several), and is - CONSUMED when it opens its own colour of door (`pfTickDoors` loops every door). - Single-door levels (L3's red door) are unchanged. **L2 "The Works"** now ends on - a **two-key lockgate** - its stone VAULT finale was extended (~5,884 -> ~6,312px) - into a proper corridor so the yellow gate, a stretch, the blue gate and the steps - are each their own beat (the first cut had the blue door embedded in the goal - step) - and gains coins to **25** (was 19). - Example-side only; gates clean, audit 0 findings. -- **Platformer: latching SWITCH-GATES + a switch puzzle in L2 (Phase C slice 3 - - part 2).** `pfMakeSwitchGate pSwitchX, pColour, pGateL` plants a floor/ledge - switch (`switch_`) and the coloured kinematic gate (`block_`) - it opens. Unlike `pfMakeGate`'s momentary pressure pad, it **latches**: step on - the switch once and the gate rises out of the way for good (`pfTickSwitchGates` - polls only-moving-bodies, then `gSwDone` idles it in one compare/frame). L2 gets - a **green switch-gate** - the gate bars the crusher alley until you climb to the - third-bay cloud and press the switch up there, a "find the switch" beat (both - the keys and the switch are `gToysOK`-gated, so a missing tiles sheet degrades to - an open run). Example-side only; gates clean, audit 0 findings. Slice 3 done. -- **Platformer: LEVEL 7 "STONE KEEP" expanded to a blade-and-warden gauntlet (the - length + variety pass, vertical edition).** The climbing tower roughly doubles - in height (1,280 -> ~2,300px, **8 -> 16 ledges**) and goes from 8 coins / 2 - spinners to **~22 coins / 2 bonus gems** and a real bestiary - all chosen to - work in a *vertical* shaft (bats/ghosts/snails/mimics hardcode a horizontal - ground-y and were ruled out): **five sweeping spinners** guarding the climb gaps - (each verified standing-safe on both adjacent ledges), a **stationary - half-blade** (`pHalf`) on the final ledge, and three **wardens** on the wide - "fight" ledges - a `pfMakeSlime` to stomp, a `pfMakeFrog`, and a spike-slime to - dodge (the makers that take an explicit surface-y, so they ride the one-way - ledges). `pfDressWall` now tiles the taller shaft. Example-side only; the - geometry audit skips vertical levels, so the spinner safety + ledge spacing were - hand-verified (script gates clean, L1-L6 still 0 findings). **Needs the OXT - pass** (the vertical-only checks: walkers resting on one-way ledges, the - spike-slime leap, blade timing). -- **Platformer: LEVEL 5 "SCORCHED DUNES" expanded to a full three-act level (the - length + variety pass, second of the audit).** From 4,200px / 12 coins / 3 foes - to **~6,400px and 24 coins**, keeping Act 1 verbatim and appending two desert - acts across three thorn pits: **Act 2** a darting `fly` (mover), a second dune a - sand `pfMakeBoulder` rolls down at you (re-parks at 4160 so the landing bank - stays safe), a high sand-cloud route, a fire `pfMakeCritter` (no stomp) and a - second `pfMakeFrog`; **Act 3** a treasure `pfMakeMimic`, a second fly, a final - cloud route and a homeward snail to the summit. Three checkpoints (now using - the fixed multi-checkpoint path). Example-side only; geometry audit clean (it - caught a summit coin overlapping the flag - fixed), 0 findings across 7 levels. -- **Platformer: multi-checkpoint activation fix.** `pfMakeCheckpoint` tracked the - flag + its x in single globals that each call overwrote, and the sensor handler - banked that one x behind a one-time guard - so L6's three checkpoints left all - but the last dead (the first never lit, a later one stood pre-raised). Each flag - now carries its own respawn x (`uPfCheckX`) and a one-shot guard - (`uPfCheckDone`); touching a flag activates THAT flag and only ever advances the - respawn point. Single-checkpoint levels (L1-L4) are unchanged. -- **Platformer: LEVEL 6 "CAVERN DEPTHS" expanded to a full three-act level (the - length + variety pass).** The thinnest level (2,976px, 9 coins, 3 foes) grows - to **~6,400px and 24 coins** with a rich cave bestiary, keeping all of Act 1 - verbatim and appending two acts of proven geometry: **Act 2** a CRUSHER GALLERY - - two `pfMakeBat` roosting on a dirt overhang (split bands, gotcha 18), two - faced `pfMakeThwomp` crushers, a wider spike chasm (PIT2); **Act 3** the DEEP - RUN - a dropping `pfMakeSpider`, a stalking `pfMakeGhost`, a floor - `pfMakeBarnacle`, a `pfMakePlant`, a third spiked chasm a `pfMakeLift` ferries - (PIT3, still double-jumpable), a rightward speed `pfMakeConveyor` + a second - block slime, a fire `pfMakeCritter`, a treasure `pfMakeMimic` and a finale - crusher to the flag. Three checkpoints. The spook-sheet foes (bat/spider/ghost/ - mimic/plant) are `gSpooksOK`-gated, so a missing `enemies.png` degrades them - silently and the level stays completable. First of the level-length audit pass - (L1 ~8.5k/29 coins is the reference; L5 and L7 follow). Example-side only; - geometry audit clean (`audit-platformer.py` caught a goal-past-the-ground edge, - a coin/decor crowding the flag and a checkpoint on the pit lip - all fixed). -- **Platformer: the SPINNER - a spinning-blade hazard (asset-expansion Phase C, - slice 2).** The previously-unused `spinner*` art (the `spooks` / `enemies.png` - sheet) becomes the keep's hazard: `pfMakeSpinner pX, pY, pAmpX, pPerX, pHalf` - registers a bodiless sprite that spins via ANIMATION (not body rotation, - gotcha 23) and sweeps a horizontal sine path, hurting by plain proximity like - the saw - an UNKILLABLE "saw-rule" hazard you TIME, never stomp (knockback via - `pfOuch` in `pfTickMovers`, never a respawn). Built on the existing mover - table (no new per-frame tick). `pHalf` selects the wall-mounted half-blade - (`spinnerHalf`) for slice 3's puzzle wing. L7 deploys two full blades, each - sweeping the shaft across a climb gap (lower L1->L2, upper L4->L5) and placed - STANDING-SAFE on both adjacent ledges (the hero waits on the lower ledge and - times the jump). Optional art (gated on `gSpooksOK`): no `enemies.png` = the - maker no-ops and those spots are simply safe (the climb never depends on a - hazard). Example-side only (no Kit change, no harness bump); static gates - clean. **The blade timing needs the OXT feel-pass** (tune `pPerX` if frantic). -- **Platformer: a LEVEL PICKER (dev/test convenience).** A top-right option-menu - dropdown jumps straight to any level (`menuPick` -> `pfJumpToLevel`, a fresh - run-state rebuild), so levels already approved need not be replayed after every - update. Works from play, pause, or the win screen; the menu tracks the current - level. Chrome change, so `kPfUIVersion` bumps to "8" (older pasted stacks - rebuild their UI once). Not part of the normal coin-gated progression; the - title field shrinks to make room. Example-side only. -- **Platformer: LEVEL 7 "STONE KEEP" - a VERTICAL climbing tower (asset-expansion - Phase C, slice 1).** A seventh level on the previously-unused `terrain_stone_*` - set, built as **a tall tower you CLIMB, not a level you cross** - the camera - **scrolls UP** as the hero jumps from one one-way stone ledge to the next, to - the flag atop the keep. The first level in the game that scrolls vertically. - (Two earlier passes - a stone-skinned L6 clone, then a single-screen fortress - - were redesigned after OXT feedback; the user asked for a true vertical level.) - - **New gated vertical-camera mode**, all parameterized so L1-L6 are byte-for- - byte unchanged: `pfBoundsV` (a tall world, walls + ceiling, a kill plane far - below) sets `gCamTopY`/`gCamBotY`/`gKillPlaneY`; the per-frame camera follows - the hero's Y clamped to those (horizontal levels keep `gCamTopY=gCamBotY=320`, - pinning Y as before) and centres X when the world is no wider than the - viewport; the hero spawns at `gRespawnX`/`gRespawnY` (L1-L6 still 120/480). - - `pfMakeLedge` builds the one-way stone climb platforms (`b2kSmoothGround` + - `stone_cloud`); a zig-zag of 8 ledges, 8 coins, a summit gem, contained walls - (a fall drops you to a lower ledge, no respawn). - - **Centred + enclosed (two OXT rounds).** The play column (64..960) is - narrower than the 1024 viewport, so `pfBoundsV` now sets the camera's X - bounds to a viewport-wide range CENTRED on the column (was left-aligning the - tower), and `pfDressWall` fills the leftover margin EACH side with solid - stone (`terrain_stone_block_center`, matching the floor) so the shaft reads - as an enclosed keep edge-to-edge - no dead backdrop at ground level. The - side-wall colliders that were invisible are now the visible walls. - The win moves to L7 (`gLevel >= 7`); L6's flag now ADVANCES. Example-side only - (no Kit change, no harness bump). `tools/audit-platformer.py` learned to skip a - vertical level (the y=576 model doesn't apply); L1-L6 still 0 findings. - Statically verified; **the vertical camera scroll needs the OXT pass** (verify - item 21) - it is the one thing untested on the engine. -- **Platformer: the CONVEYOR BELT - a carried surface (asset-expansion Phase B, - slice 3).** The previously-unused `conveyor` tile becomes a polled surface - zone (`pfMakeConveyor pL, pR, pDir`) that adds a steady vx to the GROUNDED hero - on top of his own walking, so you can power against it; jumping over it is - unaffected, and a hero on a higher platform at the same x is excluded. No body - - the level's ground slab still owns the collision. Built example-side per the - plan (no Kit "surface velocity" feature). The `conveyor` art is a single frame - (no scroll animation), flipped to face the push direction. Debuts in L6 as a - leftward treadmill before the block slime. New `pfTickConveyor` in the frame - fan-out (one compare when no belts exist); `tools/audit-platformer.py` gains a - conveyor parse branch + an over-solid-ground check (a belt may not run over a - pit). This completes asset-expansion **Phase B**. Example-side only (no Kit - change, no harness bump); audit clears all 6 levels. Statically verified; - needs an OXT pass. -- **Platformer: the BLOCK SLIME - a hopping cube (asset-expansion Phase B, - slice 2).** The previously-unused `slime_block_*` foes art becomes a new - slime-family kind `block`: a cube that arcs back and forth across its band in - hops (the `_jump` frame in the air, a `walk_a/b` squish idle when settled), - reversing at the band edges and sleeping between hops. Stompable on the - classic path (a stomp squashes it to `slime_block_rest`, a side touch knocks - back) - no new contact case. Debuts in L6 "Cavern Depths" (it replaces a plain - slime, so the cavern's signature foe is the hopper). New `pfMakeBlockSlime` - maker + a `case "block"` in `pfTickSlimes`; `tools/audit-platformer.py` gains - its parse branch and clears L6 (still 0 findings). Example-side only (no Kit - change, no harness bump). Statically verified; needs an OXT pass. -- **Platformer: LEVEL 6 "CAVERN DEPTHS" - the DIRT biome (asset-expansion - Phase B, slice 1).** A sixth level built on the previously-unused - `terrain_dirt_*` tile set (block tops, `block_center` mass under the mound, - carved `block_top_left/right` cliff corners on the goal steps AND the spike-pit - lips, `ramp_long_a/b` dirt ramps, a one-way `cloud_left/middle/right` dirt - platform): a WALL-JUMP SHAFT of floating dirt columns (slot coin, the L3 - ice-shaft recipe in dirt), a spike GAP to leap (checkpoint past it), a - DIRT-RAMP mound, a high one-way-cloud bonus route, two slimes + a snail, a - bonus GEM above the shaft, and carved dirt steps to the flag. The win moves to - L6 (`gLevel >= 6`); L5's flag now ADVANCES. **OXT round 1 polish (atmosphere):** - a built **dark cave backdrop** (`pfBuildCaveBackdrop` - two card graphics, not - the flat/blank `background_solid_dirt` frame), flickering **wall torches** - (`torch_on_a/b`, new `pfMakeTorch`), hanging **chains + stalactites** - (`terrain_dirt_vertical_bottom`), and ground decor (a rock, cave mushrooms, a - bush) - placed clear of every beat. Example-side only (no Kit change, no - harness bump); `tools/audit-platformer.py` auto-discovers and clears L6 (9 - coins, 3 walkers, 0 findings). Statically verified; needs an OXT pass (see - header verify item 20). Phase B's headline mechanics - the block slime and the - conveyor belt - land in slices 2-3. -- **Platformer: FISH in the swim pool.** The L1 hilltop pond now has two fish - (blue + yellow, native `foes` art that was unused) swimming at different depths - and periods — bodiless proximity hazards (recoverable knockback) you time your - dive between while grabbing the underwater coins. -- **Platformer: GEM bonus pickups.** A distinct collectible (`gem_*` tiles art, - previously unused) that does **not** gate the flag — a skill-reward tally shown - in the HUD and the win screen. One gem per level in a hard-to-reach spot (above - the mound, atop the ladder ledge, the wall-jump-shaft top, high over the piranha - row, above the dune crest). New synthesized "gem" chime. -- **Platformer: a PARALLAX biome backdrop.** Each level gets its own scene that - drifts behind the foreground at 0.3× for depth — green hills (L1/L2), a pale - wintry hills (L3), purple mushrooms (L4), and desert dunes (L5) — tiled and - wrapped across three card panels (gated so a still camera costs one compare). - The Kenney bg scenes are fully opaque, so this is a single drifting layer (a - fade layer behind would be hidden); true multi-layer parallax would need - transparent layer art (noted in the expansion plan). - -### Fixed - -- **Platformer: flying movers (bee/fly/fish/ladybug) no longer face backwards.** - The bodiless sine-path movers (`pfAddMover`/`pfTickMovers`) flipped to face - travel with `cos(t/P) < 0` (flip when moving LEFT), which is only correct for - right-facing art. But the foes-sheet flyer frames are drawn facing LEFT (like - the snail - gotcha 26, facing polarity is statically unverifiable), so every - bee, fly, fish and flying ladybug pointed the wrong way along its path. Flipped - the test to `cos(t/P) > 0` (mirror when moving RIGHT) - the same polarity - correction the snail row already carries via `gSlimeFlip`. Symmetric movers - (saw/spinner, `gMovFlip` false) are untouched. Example-side only; static gates - clean, audit 0 findings. Needs an OXT pass to confirm the facing. - -- **Platformer: GROUND ladybugs + frogs no longer face backwards either.** The - follow-on to the flying-mover fix above, on the *other* facing path: the - slime-family walkers flip via `gSlimeFlip` (the snail's left-facing correction). - The frog and the ground ladybug art also face LEFT, but their makers set - `gSlimeFlip` false (right-facing). `pfMakeFrog` now sets it true; `pfMakeCritter` - gained an optional `pFaceFlip` arg (the ground-ladybug calls pass true) so the - ladybug inverts WITHOUT touching the mice / worms / fire-slimes / slime skins - that share the maker and already face correctly. Example-side only; gates clean. - -- **Platformer: spike pits now align FLUSH with the pit edges (all levels).** - Found over two OXT rounds: - - *Horizontal (the real misalignment).* `pfTile` treats its `(x,y)` as the - tile's TOP-LEFT (it centres at `x+32`, exactly like all the ground tiling), - but the spike row was tiled over the range `pL+32 .. pR-32`, shifting every - spike 32px RIGHT - a bare 32px gap at the left pit edge and the last spike - OVERHANGING onto the ground on the right. The row now tiles `pL .. pR-64` - (top-lefts), landing flush from `pL` to `pR` in every pit. - - *Vertical.* The spike pixels are the bottom 34px of the 64px cell, so the old - y576 sank the tips to y606 (30px below the edge); the tile now sits at y546 so - the tips meet the ground line. - Affects every spike pit (L1/L3/L5/L6); the hurt sensor and pit geometry are - unchanged. The no-art fallback strip matches. -- **Platformer (OXT round 6): five polish fixes for a solid state.** - - *Parallax seam.* Adjacent 640px backdrop panels met exactly edge-to-edge, so - sub-pixel rounding leaked a 1px white hairline. Panels are now widened 4px and - recentred (a ~2px overlap each side, opaque-over-opaque), and the drift is - rounded to whole pixels; the 640 wrap modulus is unchanged so the wrap stays - seamless. - - *L5 spike pit.* `pfMakeSpikes` tiled the row from `pL` (burying the first - spike's left half under the slab and leaving a bare strip at the right edge — - it read as "the spikes don't fit"). It now centres the row at `pL+32..pR-32`, - flush to both ground edges — fixes every pit at once. - - *Slime stuck to the crusher (L3).* The red-key crusher's slime patrolled to - `2148`; its right edge (`2172`) all but touched the block body (`2178`, a 6px - gap) and the velocity-asserting slime pinned against it. Right limit pulled in - to `2118` (36px clearance). - - *Coin obscured by the chain (L2).* The "ride the thwomp up" coin sat at - `1840,64` — dead in the chain's column (chain art spans y44..172 at x1840), so - it drew behind the chain. Moved to `1912,96`, clear of the chain, still - reachable from the rising weight. - - *Sign overlapping the checkpoint (L2).* A decor sign at `2400` (centre 2432) - overlapped the checkpoint flag at `2450`. Moved to `2300` (~86px clear). - - *Bonus, caught by the extended audit:* L2's slime#1 was patrolling **inside** - the green crusher alley (`4324..4460`), its sweep overlapping crushers 6 & 7 - (0px) — the same stick risk. Crusher alleys are walker-free by convention, so - it was relocated to the open stretch past the 2nd-bay crusher (a real 100px - patrol, 86px clear), and a decor bush nudged out of its new sweep. - - The geometry audit (`tools/audit-platformer.py`) gained four checks that flag - this whole class statically: spike-pit fill (64px-multiple width), walker - sweep vs thwomp body proximity, coin-in-a-chain-column, and decor vs the - checkpoint/goal flag. -- **Platformer: the L5 thorn pit fits its spikes and the final coin is off the - flag.** The pit was widened 192px → 256px (3 → 4 spikes, `ground2` starts at - 2256, over-pit coin re-centred to 2128) for a proper spiked chasm, and the - summit coin moved off the goal flag (`4180` → `4100,420`; it overlapped the - flag at `4200`). The audit gained a coin/gem-vs-goal-flag overlap check. -- **Platformer (OXT round 4): the collapsing-bridge planks now actually DROP.** - They recoloured (the "shaking" tint) but never fell — a fresh `b2kAddBox` body - needs gravity *asserted* to drop (the working thwomp does `b2kSetGravityScale - 1.8`, commented "slam, not float"); the plank skipped that step, so it floated - in place. It now asserts gravity on the drop, like the thwomp. -- **Platformer: the L1 swim basin has a KILL FLOOR under the water.** The solid - pond floor is replaced by a kill sensor just below the water line - (`uPfKillFlag` → `pfHurt`), so sinking out the bottom respawns instead of - resting on the floor forever — the swim now has stakes. The deepest underwater - coin is raised (`588` → `576`) for a safe dive margin above the kill line, and - the layout audit learned about water zones so underwater coins don't false-flag. - -- **Platformer (OXT round 3): the ROOT cause of the drifting coins and flags — - cast sprites were born scroll-shifted.** The camera `b2kCamGoto` ran *before* - the hero and the cast, so every create-once cast sprite (coins, flags, - checkpoints, the barnacle) was created while the view was already scrolled and - landed offset on the user's engine — pond coins pushed off-centre, flag and - checkpoint POLES drifting to step edges or hovering "unattached". Bound bodies - (hero, slimes) re-sync every frame, so they were always fine; static pickups - never re-sync. Fix: build the WHOLE world (scenery, hero AND cast) at scroll 0, - then `b2kCamGoto` exactly **once, after the cast** — matching how the - correctly-placed terrain was always built. The flag plant drops back to 8px - (it had been bumped to 16 to fight the now-removed offset), and the L1 pond's - underwater coins are grouped more centrally (`7880/7960/8040`). - -- **Platformer (OXT round 2): the L3 wall-jump shaft reads as a real beat now.** - The two pillars were a stretched thin slab; rebuilt as crisp 64px ICE-BLOCK - columns framing a clear slot, and the slot coin is now built in the SCENE - *with* the columns (it was in the cast, created after the camera scroll, so it - could drift off-centre relative to the scene-built columns) — it sits - dead-centre between them to show off the wall-jump. -- **Platformer: removed the L5 spider + its floating sand overhang.** A - ceiling-crawler needs a ceiling; a sand bar hovering in the open desert sky - read as a glitch. The spider stays showcased in L4's cave biome (the overhang - is a real ceiling fragment there); the 3552 coin remains as a run-home pickup. - L5's bestiary is now frog + barnacle (+ slime/snail). -- **Platformer: the flag/checkpoint plant was raised 8px → 16px** after an OXT - pass showed flags still hovering at 8px. `kFlagPlantPx` remains the single - tunable knob. - -- **Platformer: the L4 COLLAPSING BRIDGE was crammed into a 128px lava slot - between two crushers — rebuilt with room to read.** The mechanic's logic was - sound all along (the stand-to-crumble detection window, the static→dynamic - plank flip, and the render-sync — `b2kSetType` clears `sStatic`, wakes the - body and forces a full sync — all check out); the *space* was the problem: - three ~42px planks pinched between faced crushers at `3040` and `3360`. The - lava pit is now **192px** (grid-aligned `3136..3328`), the bridge is **four - ~48px planks**, and the **far-bank crusher is retired** so the planks get - clear air (the single entrance crusher still gates the approach). The ~190px - strip stays double-jumpable (the L2 lift bay's ~200px reach — no dead-end) and - the mid-lava coin re-centres to `3232`. Downstream beats (fire slime, powder - keg, bowling lane) are untouched — no cascade. - -- **Platformer: flags/checkpoints that read as "floating" on the user's engine - are now PLANTED.** Every flag/checkpoint frame seats its pole base on the floor - line in geometry (re-verified: the 64px frames have *zero* footroom and are - centre-anchored at `surface − 32`), but they hovered a few px on the user's - engine — a scrolled-group vertical quirk at sprite-create time, the same class - of engine difference the camera code already works around. A single tunable - constant `kFlagPlantPx` (8px) now sinks every goal/checkpoint base slightly - *into* its surface so it reads as rooted; bump it if a flag still floats in an - OXT pass. (Statically verified — needs an OXT confirmation.) - -- **Platformer: scenery no longer spawns on top of enemies or coins.** A new - geometric layout audit (`tools/audit-platformer.py`, advisory) found decor - sitting on beats: L1 mushrooms/bush on the meadow-slime / 2nd-act-slime / - mouse / frog spawns (nudged into the clear inter-enemy gaps), an L2 bush on the - barnacle's telegraph (`3400`→`3488`), and an L4 grass tuft that both hid the - `4000` coin and sat on the bowling-lane snail (removed). Also fixed an L2 slime - spawned 64px in the air (`topY 512`→`576`, it dropped on build). The audit now - passes clean across all five levels (grounding, bounds, walk-offs, overlaps). - -- **Platformer: three coins sat *on* a wall — lifted clear (a measured-alpha - audit of every coin/flag in all four levels).** The visible coin disc is a - 38×40 sprite-alpha box centred in its 64px cell (not the whole cell), so most - coins float clear at head/jump height; the audit found only three that bit a - solid: the L2 lift-pedestal coin (`3300`→`3350`, off the pedestal corner) and - the L2/L3 door-passage coins (`5392`→`5440`, `5808`→`5856`, which z-ordered - on top of the *closed* door tile). Flags verified seated on their surfaces. +Nothing yet. -- **Kit: commands called with function syntax never worked in OXT.** OXT - cannot invoke a *command* handler with function syntax (`get b2kChain(...)` - throws "can't find function"), which the Game Kit Phase 0 spike caught on - real hardware. Repaired everywhere: - - `b2kSmoothGround` (returned `b2kChain(...)`) — the alias **always threw**; - it now calls `b2kChain` as a statement and returns `the result`. - - `b2kLayerBits` (used `b2kDefineLayer(...)`) — **named** collision layers - via `b2kSetCategory`/`b2kSetMask` always threw; numeric layer lists were - unaffected. - - Contraption builder: the **servo** joint (`put b2kMotorTo(...) into tJ`) - never created its motor joint; it now uses statement + `the result` like - the neighbouring joint cases. - - Docs taught the broken pattern: the 60-second starts (README, - kit-reference, getting-started, Kit header comment) and ~25 snippets in - the kit guide now use `b2kSpawnBox ...` / `put the result into tRef`. -- **Kit: an invalid colour name no longer aborts a spawn.** `b2kSpawnBox` / - `b2kSpawnBall` / `b2kSpawnCapsule` ignored-or-threw inconsistently on bad - colours (CSS-only names like `teal` are not LC/X11 colour names and threw - mid-spawn, leaving an orphaned control with no body). The colour set is now - tolerated like every other bad input: the spawn proceeds with the default - colour. -- **Docs: `b2AddSegment` / `b2kWall` segments are two-sided.** The references - claimed segments were one-sided; the Phase 0 spike disproved it (a body is - blocked from below on either winding). Both docs now say so and point - one-way platform builders at chains (chain segments are the one-sided - primitive). -- **Platformer: levels now hug their CONTENT, so the hero can no longer walk - off into dead ground at either end.** `pfBounds` built its thick slabs, edge - wall segments and camera clamp at the raw WORLD width, leaving walkable - emptiness before each spawn (hero at x 120 vs a wall at x 0) and after each - flag (up to ~390px past L3's). It now takes the content edges (just past the - spawn, just past the flag); the per-frame edge failsafe follows, and the side - slabs grew taller so no Wave-5 double-/wall-jump rounds their corners. - Example-only. - -### Removed - -- **The micro-game example (`box2dxt-microgame.livecodescript`) was - retired.** The repo concentrates its game work on the platformer showcase; - the "build a whole game" pattern it demonstrated is preserved in - kit-guide §20. Dropped from the embedded-Kit sync list and the example - lists in the README and CLAUDE.md. +## [0.3.0] - 2026-06-22 ### Added -- **Platformer Wave 7 (biomes): a new desert Level 5, "Scorched Dunes."** The - platformer's first *fifth* level, in the fully-tiled **sand biome** — a sand - **dune** (ramp slope), a **thorn pit** to leap, a **two-cloud hop**, and the - Wave 6 bestiary (frog, barnacle, spider) given **room** (one of each, well - apart), then sand steps to the flag. Laid out **layout-first**: every - placement was geometrically audited (coin/flag/enemy overlaps + 250px-plus - spacing per beat) *before* coding. The win now spans five levels. -- **Platformer Wave 6 (bestiary II): frog, barnacle, and spider — woven into - L1/L2/L4, all example-side (no Kit change, no harness bump).** Three new - enemy archetypes, each built on an existing Kit pattern: - - **Frog** (L1 far meadow) — a grounded *hopper* that crouches then leaps - toward you when you're in range. Joins the slime family as a new `"frog"` - kind, so it's **stompable for free** (the classic-slime contact path); its - side touch knocks back. Bounded to its band so it never springs into a pit. - - **Barnacle** (L2 machine-works) — a stationary clam (the piranha's timed - cycle held still): it lurks shut, **opens as a telegraph**, snaps a brief - hurt-window, then cools down. Unkillable (the saw rule) — time it or jump it. - - **Spider** (L4 haunted hollow) — a bodiless ceiling-crawler under a small - overhang that **drops as you pass under** and climbs back. Family-C art, so - **optional**: a missing spooks sheet omits it *and* its overhang, and the - level still completes (the optional-art rule). Plus a `bite` sound cue. -- **Kit: a persistent spritesheet cache (`b2kSheetPersist`) — load atlases - once, not per level (statically verified + harness v14).** Opt-in (default - off, so every other example and the harness are byte-for-byte unchanged). - When on, loaded sheets are assets that **survive `b2kTeardown`** (like - synthesized sounds): a level rebuild reuses them instead of re-decoding - each PNG, re-parsing each XML, and re-slicing every frame — the costliest - work the Kit does, previously repeated on every level transition. An - identical `b2kSheetLoad`/`LoadAtlas`/`FromImage` becomes a no-op, sliced - frames survive, and because source/frame images are named deterministically - (`b2ksheet_` / `b2kfr__`, tagged with the file path) a - **saved stack** carries the cache — on reopen the load adopts the in-stack - images, skipping the disk import entirely. `b2kSheetsWipe` stays the - explicit purge. The **platformer** turns it on at `openCard` (Shift+Reset - purges) to cut its between-levels load time. -- **Wave 5 (player actions II) — five new player-controller moves - (statically verified + harness v13).** All **opt-in** through - `b2kPlayerSet` knobs whose defaults leave the pre-Wave-5 controller - byte-for-byte unchanged, and each idle path costs one compare per frame: - - **Double-jump** (`airJumps` — extra mid-air jumps, refilled on landing). - - **Wall-slide + wall-jump** (`wallSlideMax` caps the fall while pressing - into a wall; `wallJumpX`/`wallJumpY` launch up and away with a brief - steer-lock; a side ray runs only while airborne). New `wallslide` state. - - **Dash** (`dashSpeed`/`dashMs`/`dashCooldownMs` on the new `dash` action, - bound to SHIFT/X — a flat horizontal burst with gravity parked; yields to - climb/swim). New `dash` state. - - **Duck capsule-reshape** (`duckScale < 1` turns the Wave 2 brake-duck into - a feet-anchored **crawl** via `b2kReshape`, with a headroom check before - standing — so the hero slips under low gaps). - - **Moving-platform carry** (`platformCarry 1` — a grounded player inherits - the velocity of the moving kinematic body it rides; a vertical lift's - carry is exempt from the ground-snap). - - New helpers: `b2kPlayerHalfH()`/`b2kPlayerHalfW()` (live capsule extents, - serving gotcha 28), `b2kPlayerInLadder()`/`b2kPlayerInWater()` (this - frame's zone membership), and `b2kPlayerRespawn x,y` (teleport + zero - velocity + clean state). `b2kPlayerAnims` gains `wall`/`dash` slots. - - The **platformer showcase** turns them all on and leans each beat on one - (double-jump throughout, a wall-jump shaft, a dash gap, the L2 lift bay; - DOWN stays the Wave 2 brake-duck, not the crawl-reshape). Self-test **v13** - adds six hand-stepped tests. -- **Wave 4 (liquids) — SWIM, the Kit's first new player-action since - Wave 2 (statically verified + harness v12; user play-tested in the - platformer).** A new `b2kPlayerAddWater x1,y1,x2,y2` registers a polled - water zone (world state, wiped by `b2kClear`, exactly like the ladder - zones). While the player's centre is submerged the controller SWIMS: - gravity drops to `swimGravity` so you sink slowly, the sink caps at - `swimMaxFall` (far below the air terminal), UP/DOWN swim at `swimSpeed`, - and a JUMP press is a REPEATABLE upward STROKE (`swimJump`) with no ground - gate. A new `swim` state plus a `b2kPlayerAnims` swim slot (a 9th arg, - falling back to the fall pose, so three-arg calls still work) drive the - art. Swim is mutually exclusive with the climb (the tick starts only - one); leaving the zone, a hurt, or teardown restores the saved gravity - scale exactly once. The swim path costs ONE compare per frame when no - water zones exist. Two Opus correctness reviews found no blockers. - - **The platformer's L1 GREEN HILLS gains a HILLTOP POOL** — the swim - showcase, in the level the game is actually play-tested in. A RAISED - swim basin between two earth banks past the crusher alley: the 640-tall - world clamps the camera at its bottom, so a swim pool is a basin held - at the surface, never a sub-ground pit. Hop in, dive for three - underwater coins (the gate needs them), then stroke up + hold-forward - to HOP out the far bank to the flag. New `pfMakeWater` helper; per the - playtest the water was made heavier (`swimGravity` 0.6, `swimMaxFall` - 200, a trimmed `swimJump` 300 — `swimJump` alone sets the escape, so it - is the lever for "harder to climb out"). - - **Fixed a pre-existing brick head-bump gap** the pool work surfaced: - the hero's 88px capsule was taller than its ~76px visible character - (128px frame headroom at a 0.75 down-scale), so heads "missed" bricks - by the difference even though the smash fired. The hitbox now matches - the art (feet-aligned, bind offset derived) and the bonk window reads - the real half-height instead of a hardcoded 44 (gotcha 28). - - **Harness v12** adds three swim tests: `stTestSwim` (dive / buoyant cap - / repeatable stroke / swim-up / gravity-restored-on-exit, every value - printed), `stTestSwimGrounded` (swim while resting on a submerged - floor — the pool-floor case), and `stTestSwimClear` (the level-rebuild - path: `b2kClear` must wipe the zone, or the next level's player is born - swimming in mid-air where the old pool was). - - The micro-game also gained an L3 "THE DEEP" swim level (data verbs - `water` + a `fish` pit-dweller), but it shows a white-world build issue - in its own example code and is set aside pending a focused pass — the - Kit swim itself is sound (it runs clean in the platformer). - - **The COLLAPSING BRIDGE — Wave 4's last named mechanic — is now built.** - New `pfMakeCollapseBridge`/`pfTickCollapse`: static planks crumble a beat - after the grounded hero stands on one (CREAK then SMASH, dropping the plank - and its rider into the lava), re-park static before the kill floor can - destroy them, and the whole bridge re-forms once the hero retreats to the - near bank. It crosses L4's lava pit in place of the lava lift (the gap - already existed; a fall is the recoverable pfOuch; a running double-jump - still clears the strip; platform-carry stays showcased by the L2 lift bay). - Example-only — no Kit change. -- **Platformer SHOWCASE polish round (statically verified; awaiting the - OXT pass).** A pre-Wave-4 pass over the platformer to make it a - polished demo of the kit *as it stands today* — longer, better-spaced - levels, classic mechanics drawn from the kit's biggest UNUSED subsystem - (JOINTS + dynamics), and four new enemy species for variety. - **All example-side: zero Kit changes, so harness v10 stays the - baseline** (rule 2 is conditional on Kit edits). - - **Three sprite-faithful mechanisms, all polled HAZARDS the player - times or avoids (never rides — so none needs the platform-carry a - later wave adds):** a sagging **ROPE BRIDGE** (L1 — six dynamic planks - hinged end-to-end and pinned to the world at both posts, the canonical - Box2D bridge, with a new checkpoint at the brink); a **ROLLING - BOULDER** (L3 — a sprite-faced dynamic ball that slides the icy flat - head-on, recycled on a cycle so it never reaches the kill floor, - judged by a poll since it passes through the hero); an **EXPLOSIVE - BARREL** (L4 — a fuse-then-`b2kExplode` powder keg that scatters a - woodpile of loose crates). A swinging wrecking ball was prototyped and - **cut**: a rotating arm cannot be sprite art (gotcha 23), and the kit - is sprite-only for game visuals — so it was removed rather than left - as a plain graphic. - - **Four new enemy species** on the existing slime FAMILY (native 64px - foes art, a new `pfMakeCritter` maker + per-row speed/squash-frame - columns): a fast **mouse**, a slow **worm**, a **ladybug** (+ a - flying one via the mover table), and a **fire slime** by the L4 lava - (spike-type — hurts from every side). L4's PIRANHA row is now twice as - long (four burrows on staggered timers). - - **Longer, re-spaced levels** (the layout law: widen before squeezing - a beat in), grown across several passes to L1 7552, L2 5952, L3 6592, - L4 6656. Existing verified beats are preserved in place; each level's - walled-door / steps finale shifts as a whole. Classic acts stacked on: - L1 a "meadow gauntlet" + a "homeward run" (chained thwomp + snail under - a cloud); L2 second and third machine bays (chained crushers + an - always-on saw + a snail); L3 a second snow cloud + a glacier slime, - snail and thwomp; L4 a "bowling lane" plus a snail-heavy finale. - **Snails are now used liberally** (L4 carries four), the **classic - chained-weight thwomps are back** in every level alongside L4's faced - crushers, and there are more clouds, coins, and decor throughout. A - live `awake N/M` body count on the HUD. - - **A marquee CRUSHER ALLEY + cloud hop closes every level** (the latest - pass: "show off the engine with as much pizzazz as possible"). Each - level's homeward stretch is now a row of FOUR tile-block thwomps the - hero times a dash BENEATH, biome-matched so the mechanic reads at a - glance — **green** blocks (L1, L2 grass), **blue** (L3 ice), **red** - (L4 haunted) — followed by a two-cloud HOP to the flag with coins up - top. Powered by a new `pTileFace` parameter on `pfMakeThwomp` (a plain - tile sprite, no mood-face swaps; the same drop/rest/rise/re-arm cycle, - so the alley never blocks the path for good). The four levels each grew - ~1280px for the new gauntlets; the walled-door/steps finales shifted as - whole units; coins and their totals self-count as the level builds. - - **A per-frame optimization pass** against the kit's performance - playbook (FFI round-trips are the second-biggest per-frame cost). The - new crusher rows exposed that `pfTickThwomps` read each block's - position over the FFI *every frame even while ARMED* — a static body - resting at a fixed perch that cannot move until triggered — so it now - caches each perch x (`gBlockX`) and gates the armed→falling trigger on - that + the shared hero snapshot, paying `b2kPosition` only for the 0–1 - blocks actually in motion (≈8 → ≈1 FFI/frame, identical trigger). Plus - the sliding-shell tick reads its velocity once (not twice) and the HUD - reuses the snapshotted player state and a single camera-scroll read. An - Opus audit confirmed every other per-frame tick was already at the - playbook's standard (O(1) idle gates, hoisted clocks, shared snapshot, - change-gated writes, sleep-friendly). - - **The L3 ice boulder slides ALL THE WAY** in its direction now (per - the user: "it is an ice block/boulder") - lower friction so it coasts - far, and its reset line moved off-screen-left (past the run, below the - camera edge) so a fresh one comes from the source rather than the old - one teleporting back in place. - - **User review fixes:** the SNAIL faced backwards relative to travel - (gotcha 26 — its sheet art is mirrored vs the slimes'); a per-row - `gSlimeFlip` polarity column inverts only its flip. The barrel's - woodpile is now real CRATES (the `block_empty` sprite over invisible - fixed-rotation boxes, the L2 gate-crate pattern — a blast slides them - without the sprite-cannot-rotate problem) instead of brown rectangles. -- **Wave 3 — bestiary I + HAUNTED HOLLOW (statically verified; awaiting - the OXT pass).** Six enemy archetypes and a FOURTH platformer level, - all example-side: **zero Kit changes, so harness v10 stays the - baseline** (rule 2). Design in `docs/expansion-prep.md` §10. - - **Snail (kickable shell):** joins the slime family as a kind chain - `snail -> shell -> shellslide <-> shell` — a stomp shells it, any - touch of the parked shell kicks it sliding 520px/s away from the - hero, the slide is a per-frame velocity assert that REVERSES off - walls (collapsed-vx poll) and bowls over every ground foe it - reaches; stomp the slide to park it; its sides knock back. - - **Bat:** roosts static under an overhang (`bat_hang`), drops when - the hero nears, then flies as a gravity-scale-0 body — patrol vx + - a proportional swoop to head height + sine bob. One stomp. - - **Mimic:** a `grassBlock` sitting dead still in the PURPLE biome - (wrong on purpose — the tell); wakes within ~90px and lunges in - short hops on a cooldown. One stomp. - - **Piranha burrows:** bodiless sprites rising from drawn mouth - holes on a down/rise/bite/sink cycle with the classic mercy (never - rises while the hero stands over the mouth). Unkillable. - - **Ghost:** bodiless, drifts through terrain toward the hero at - ~80px/s ONLY while he faces away (`b2kPlayerFacing` poll); eye - contact freezes it in the shy pose. The level's pressure. - - **Faced crushers:** `pfMakeThwomp` gains a `pFaced` flag that puts - the `block_idle/fall/rest` mood art (which was already the - machine's fallback) on L4's thwomps on purpose. - - **L4 "HAUNTED HOLLOW"** (3712px, 10 coins): purple biome, the - mimic field, the snail+slime bowling lane, the bat bar, a pit, two - burrows, the ghost over the back half, a faced-crusher pair around - a new **lava strip** hazard (`pfMakeLava`: knockback, never - respawn), purple steps, the flag. Win moves to `gLevel >= 4`; all - copy says FOUR. Spook art (`enemies.png`, the Family C sheet) - loads as sheet `spooks` at `b2kSheetScale 0.9` per the mixed-grids - law and is OPTIONAL — without it the four spook makers skip - silently and L4 still completes. -- **Wave 2 closed (user-verified 2026-06-13; harness v10 all-pass) + - the level SPACING pass.** The one OXT note from the wave: the new - beats cramped the layouts. Every level stretched so each interactive - beat gets ~100px of clear air — now a layout law: - - **L1** 3712→3968px: the cloud steps no longer start at the mound's - foot (+128px gap), the spike pit and second act shift out, the - fence/bush decor moves off the bonk row. - - **L2** 2816→3072px: the Wave 2 ladder/ledge becomes its own beat - past the gate (the checkpoint used to stand INSIDE the ladder - tiles and the lever 6px off them); checkpoint → 1000, lever → - 1120, saws → 1240/1430, thwomps → 1640/1840, wall → 2464. - - **L3** 3520→3776px: the bonk-row → saw → pit corridor was - wall-to-wall (gaps of 0–18px); the saw now has 88/56px of air and - everything from the checkpoint out shifts +128. - - **Micro-game L2** 1536→1664px: the exit pillar + ladder move past - the sweeper's reach (its far end used to graze the ladder base). -- **New example: the SLINGSHOT** (`examples/box2dxt-slingshot.livecodescript`) - — an angry-birds-style tower-knockdown game, and the example that shows - the PHYSICS CORE carrying a whole game by itself (the platformer and - micro-game showcase the game modules; this one uses no player, no - camera, no sprites, and deliberately **no contact events at all**). - Drag the red ball out of the slingshot pocket (a tether-clamped pull - with live rubber bands and a ballistic aim preview — the dots plot the - same `x + v·t + a·t²/2` the world then integrates), release to fire; - three towers of columns/planks/crates (plus stone blocks on level 3) - topple for real, and the green pigs pop on a **speed poll** — the - light body always inherits the impact momentum, so the poll can't be - outrun by the solver (the doctrine's answer to post-impact zero - reads). Shot budget per level, 50 a block + 500 a pig + 1000 per - leftover shot, out-of-ammo retry with score rollback, win screen. - Zero assets: all visuals are colored graphics (which is also why — - spawned box graphics rotate with their bodies; sprites don't), all - sounds synthesized. Hot-path discipline: ammo/bands/dots/pop-rings - are pooled at build, block polls only run during a post-shot carnage - window, HUD at 4 Hz. Registered in the embedded-Kit sync tool. -- **Wave 2 — player actions I (statically verified; awaiting the OXT - pass).** Four controller abilities land in the Kit, asserted by - harness **v10** (four new tests, ~20 assertions) and consumed by both - games: - - **Drop-through:** every `b2kChain`/`b2kSmoothGround` chain now - carries a reserved one-way collision category (bit 2³¹, nameable as - the `oneway` layer; `b2kDefineLayer` stops at 2³⁰ — 31 user layers — - and `b2kSetMask` ORs the bit in automatically so custom-masked - bodies still stand on terrain). DOWN+JUMP while standing on a chain - masks the bit off the player for `dropMs` (~260 ms); the probe - ignores one-way ground during the window (no phantom re-ground), - and the mask restores only once the capsule has CLEARED the deck it - dropped through (a straddling restore would snap it back on top — - chain contacts are one-sided, judged by the centroid), with a 4× - hard deadline. On solid ground DOWN+JUMP just ducks, and the press - is eaten. **No ABI change was needed** — `b2lc_chain_create` - already honors the pending shape-def filter (§9's open question, - resolved). - - **Ladder climb:** `b2kPlayerAddLadder x1,y1,x2,y2` registers polled - ZONES (flat numeric arrays, zero physics objects; world state — - `b2kClear` wipes them). In-zone UP (or DOWN while airborne) enters - `climb`: gravity scale parks at 0 (the body's own scale is saved - and restored), y runs at `climbSpeed` off the moveY axis (0 = - hang), x at half speed; JUMP exits with a normal jump; climbing - down onto ground steps off. The ground-snap is climb-exempt (rising - off a grounded ladder base is real motion). - - **Duck:** DOWN while grounded brakes to a stop at the normal decel - and shows the new `duck` state/anim. No hitbox change this wave - (capsule reshape is Wave 5). - - **Hurt-knockback standard:** `b2kPlayerHurt [fromX]` — an away-pop - (`hurtPopX`/`hurtPopY`, riding the jump flag so the ground-snap - can't swallow it), the `hurt` state, input suppressed until - `hurtMs` or the first landing after half of it (whichever is - LATER), then an `invulnMs` mercy window during which repeat hurts - no-op and `b2kPlayerHurtIs()` answers true. An explicit - `b2kPlayerControl` call cancels a knockback in flight (respawn - flows take over cleanly, no mercy granted). - - **Kit surface:** `b2kPlayerAnims` grows optional `duck`/`climb`/ - `hurt` slots (old five-argument calls unchanged; sensible pose - fallbacks); new tuning keys `dropMs`, `climbSpeed`, `hurtPopX/Y`, - `hurtMs`, `invulnMs`; `b2kPlayerState()` adds `duck`/`climb`/ - `hurt`. All new tick paths idle at one compare per frame, knobs - cached at set-time, and the probe's one-way classification rides - the body handle `b2kRayHit` already fetched — zero added FFI in - the steady state. - - **Platformer:** the knockback-vs-respawn SPLIT — slime sides, saw - brushes, spike tips, thwomp undersides and movers now knock back - (`pfOuch`, HUD counts "hits"); only pits/kill-plane falls respawn. - The beige hero ducks and climbs with REAL frames (the default - characters sheet has `duck`/`climb_a/b` — the design doc guessed - wrong). L2 gains a LADDER up to a bonus ledge above the gate (one - new coin, 9 total); the L1 bridge/clouds drop through as designed. - The knockback pose is a LOOPING `hurtpose` twin of `hit` — a - non-looping pose would fire `b2kSpriteOnFinish` → the respawn - mid-knockback (both games gate their `*HurtDone` on the respawn - lock for the same reason). - - **Micro-game:** the same split (sweeper/spikes knock back; falls - respawn; HUD + win screen count hits), a zero-asset `ladder` verb - (drawn rails + rungs) up the now-taller L2 exit pillar, and - OPTIONAL **alien skins**: when the platformer's remembered - Spritesheets folder is present (the game itself never prompts — - zero-asset stays zero-asset), the menu offers keys 1–6 (classic + - five alien colours from the one `aliens.png` atlas, scaled 0.7); - aliens duck/climb/hurt with real frames. - - **Hardening found en route:** `b2kChain`/`b2kAddChain` parsed - points without setting `itemDelimiter` (gotcha 5 — latent breakage - under a tab delimiter); both now set it. `b2kRayHit` stashes the - hit body handle it already fetched (`sRayBodyH`). -- **Wave 1 closed (user-verified 2026-06-12); Wave 2 designed.** The - three-level platformer is the wave's verified exit. Wave 2 — player - actions I (drop-through, ladder climb, duck, the hurt-knockback - standard, optional alien skins in the micro-game) — is fully - designed in `docs/expansion-prep.md` §9, including the Kit surface, - tuning keys, harness v10 assertions, art inventory, and the open - chain-filter ABI question to resolve first. -- **The platformer is now a THREE-LEVEL game** (user direction: "we - need three distinct levels for this demo to show off all features"). - Touching a level's gold flag banners "LEVEL N CLEAR!" and builds the - next level outside the physics frame (the micro-game's deferred- - rebuild lesson); level 3's flag is the win, with TOTAL time and - falls banked across clears. R restarts only the current level; Play - again is a fresh run. The world build split into per-level builder - pairs (`pfLScene` before the hero — scenery must never cover the - actors — and `pfLCast` after), with the shared machines factored - into parameterized makers (`pfMakeGate`, `pfMakeKeyDoor` with a - colour, `pfMakeSpikes`, `pfMakeGoal`, `pfMakeCheckpoint`, - `pfBounds`). Coin totals **count themselves** as each level builds, - so totals can never drift from the layout (and the no-asset - fallback's smaller totals fall out for free): - - **Level 1 — GREEN HILLS** (2880px): movement + the Wave 1 toys — - springboard + sky coin, the bonk row, the one-way bridge over the - spike slime, the slope mound, two one-way clouds, bee/fly movers, - the spike pit. 9 coins. - - **Level 2 — THE WORKS** (2208px): the machines — crate onto the - button gate, checkpoint, the stand-to-flip saw lever, both saws, - chained thwomps, the yellow key and the walled door gating stone - steps and the flag. 6 coins. - - **Level 3 — FROZEN CITADEL** (2880px): everything at once on ICE - (quarter-strength `accel`/`airAccel` — momentum rules), snow - biome, spring over a spiked pit, bonk row, sweeping saw, second - pit, a thwomp guarding the RED key, the red walled door, snow - steps. 10 coins. - - **Boundary hardening** (user report: walking past the world's - edges): every level's box is built by one `pfBounds` helper — - thick side slabs PLUS two-sided wall segments at the exact edges - PLUS the ceiling, kill floor, and the camera clamped to the same - box. Boundary bugs now have exactly one home. - - **Final pass**: brick debris became a six-slot POOL built at level - build (parked static off-world; a smash only moves and flings the - chunks, a tick re-parks them before the kill floor) — mid-game - control creation under accelerated rendering was the remaining - hitch, and the frozen frame was why chunks "never showed up". The - spring returned to mid-meadow (the corner bounce hugged the window - edge). Hot path: ONE hero position/state snapshot per frame feeds - the kill plane, edge failsafe, sound cues and all the pf ticks - (~8 FFI round-trips per frame replaced by one), and the bonk - rising-test now reads the controller's jump state instead of a - per-frame velocity fetch. - - **Polish round** (user: "much better shape now"): every level - lengthened with a second act — GREEN HILLS to 3712px/12 coins (two - more slimes + a rest cloud), THE WORKS to 2816px/8 (a breather - cloud with its own bee before the wall), FROZEN CITADEL to - 3520px/10 (the glacier run: a second always-on sweeping saw under - a snow cloud). The left-edge escape died twice over: the L1 spring - host now sits FLUSH against the wall (the 14px slot between them - was a solver-squeeze ejector) and the boundary slabs grew to 256px - thick, plus a per-frame edge failsafe rides the kill-plane check. - Brick debris no longer hitches or "appears offscreen": the - `brick_brown` icon is PRE-WARMED at build (lazy slicing cost - ~250ms × 3 at first smash — physics outran the freeze, so the - chunks seemed to pop in far away). Ice strengthened to ~15% - accel/brake (a full-speed stop slides ~300px). - - **The scroll-shifted-build regression, found and fixed** ("level 1 - is fundamentally broken"): the restructure briefly called - `b2kCamGoto` before any camera bounds existed; an unbounded goto - scrolls the empty viewport (centring x=120 means scroll −392), and - the entire level then builds INTO a scrolled group — art lands - ~400px from its physics (gotcha #12, again). Order law now in the - code: build at scroll 0, set bounds (`pfBounds`), only THEN goto. - Stale per-level machine refs (the gate, plate, door, checkpoint) - are also reset on every rebuild so no tick ever chases a deleted - control from the previous level. +- **Slingshot: a transition COVER between levels (carrying the platformer's transition-card lesson across).** +- **Platformer: ILLUSTRATED biome cards (real game art on the transition card).** +- **Platformer: TRANSITION CARD + boot TITLE screen + recomposed WIN card (the polish pass's headline - `docs/platformer-polish-plan.md` §2 / §2.4).** +- **Platformer: CHARACTER SELECT + a hero portrait (asset-expansion Phase G).** +- **Platformer: COLLECTIBLES - coin tiers + a hidden star per level (asset-expansion Phase F, completing it).** +- **Platformer: HEALTH - a forgiving five-heart buffer (asset-expansion Phase F).** +- **Platformer: the GOO SERPENT in a slime-pool beat + the serpent generalized (asset-expansion Phase E, continued).** +- **Platformer: the LAVA SERPENT + a widened L4 lava pit (asset-expansion Phase E).** +- **Platformer: SNAKES - the slither movement type (asset-expansion Phase E).** +- **Platformer: spook slime/snake sprites sit ON the ground (alignment fix).** +- **Platformer: defeat-animation juice (asset-expansion Phase D).** +- **Platformer: a stomp DUST-POOF** — four little pale motes arc out of a squashed foe. +- **Platformer: a defeat POP + second SKINS** — the squashed art pops up on an ease-out arc as it fades, and the bestiary gains green/blue slime variants and a ring worm. +- **Platformer: MULTI-KEY doors + a two-key puzzle in L2 (asset-expansion Phase C, slice 3 - part 1).** +- **Platformer: latching SWITCH-GATES + a switch puzzle in L2 (Phase C slice 3 - part 2).** +- **Platformer: LEVEL 7 "STONE KEEP" expanded to a blade-and-warden gauntlet (the length + variety pass, vertical edition).** +- **Platformer: LEVEL 5 "SCORCHED DUNES" expanded to a full three-act level (the length + variety pass, second of the audit).** +- **Platformer: multi-checkpoint activation fix.** +- **Platformer: LEVEL 6 "CAVERN DEPTHS" expanded to a full three-act level (the length + variety pass).** +- **Platformer: the SPINNER - a spinning-blade hazard (asset-expansion Phase C, slice 2).** +- **Platformer: a LEVEL PICKER (dev/test convenience).** +- **Platformer: LEVEL 7 "STONE KEEP" - a VERTICAL climbing tower (asset-expansion Phase C, slice 1).** +- **Platformer: the CONVEYOR BELT - a carried surface (asset-expansion Phase B, slice 3).** +- **Platformer: the BLOCK SLIME - a hopping cube (asset-expansion Phase B, slice 2).** +- **Platformer: LEVEL 6 "CAVERN DEPTHS" - the DIRT biome (asset-expansion Phase B, slice 1).** +- **Platformer: FISH in the swim pool.** +- **Platformer: GEM bonus pickups.** +- **Platformer: a PARALLAX biome backdrop.** +- **Platformer Wave 7 (biomes): a new desert Level 5, "Scorched Dunes."** +- **Platformer Wave 6 (bestiary II): frog, barnacle, and spider — woven into L1/L2/L4, all example-side (no Kit change, no harness bump).** +- **Kit: a persistent spritesheet cache (`b2kSheetPersist`) — load atlases once, not per level (statically verified + harness v14).** +- **Wave 5 (player actions II) — five new player-controller moves (statically verified + harness v13).** +- **Wave 4 (liquids) — SWIM, the Kit's first new player-action since Wave 2 (statically verified + harness v12; user play-tested in the platformer).** +- **The platformer's L1 GREEN HILLS gains a HILLTOP POOL** — the swim showcase, in the level the game is actually play-tested in. +- **The COLLAPSING BRIDGE — Wave 4's last named mechanic — is now built.** +- **Platformer SHOWCASE polish round (statically verified; awaiting the OXT pass).** +- **Wave 3 — bestiary I + HAUNTED HOLLOW (statically verified; awaiting the OXT pass).** +- **Wave 2 closed (user-verified 2026-06-13; harness v10 all-pass) + the level SPACING pass.** +- **New example: the SLINGSHOT** (`examples/box2dxt-slingshot.livecodescript`) — an angry-birds-style tower-knockdown game. +- **Wave 2 — player actions I (statically verified; awaiting the OXT pass).** +- **Wave 1 closed (user-verified 2026-06-12); Wave 2 designed.** +- **The platformer is now a THREE-LEVEL game** (user direction: "we need three distinct levels for this demo to show off all features"). - **Wave 1 — the iconic-feel base: the platformer learns the classics.** - Springboards, ?-boxes, breakable bricks, a saw power lever and a - key-and-lock door land in the platformer — entirely on existing Kit - mechanisms and the already-shipped 64px tiles atlas. **Zero Kit - changes** (so no harness bump: the Kit's contract is untouched) and - zero new asset dependencies; the no-asset fallback level stays frozen - at its original 12 coins. - - **Springboard** (`spring`/`spring_out`): feet in its band relaunch - every 420ms — you cannot stand on a springboard — reaching a new - sky coin ~320px up. The boost is `b2kPlayerJump 620`, the canonical - use of the API jump (a raw upward set-velocity on a grounded player - is ground-snapped away as solver rebound). - - **The bonk row** (brick, ?-box, brick, ?-box over the meadow): - headbutts are judged by POLLED geometry plus a 160ms rising window, - because the solver has already zeroed the upward velocity on the - very frame the head meets the tile (the stomp lesson, applied - upward — no contact events on statics anywhere in Wave 1). ?-boxes - pay one coin each (pop animation, then the `block_empty` face); - bricks shatter into three `brick_brown` chunks (32×24, measured) - riding invisible spawned balls — the kill floor destroys them and - `b2kFell` sweeps the bound art. Debris no-collides with the hero. - - **Button art on the plate**: the polled pressure plate now wears - `switch_yellow` / `switch_yellow_pressed` faces in step with its - polled state (the coloured-rect fallback survives, frozen). - - **The saw lever** (`lever_left/right`): STANDING at it (grounded, - near-zero vx — running past never flips it; a leave-the-band latch - re-arms it) powers the sweeping saw down: spin stopped, ghosted, - hurt box off. Standing again powers it back up. - - **Key + the WALLED door — the finale's mandatory gate.** A key - floats in thwomp alley (one-shot sensor pickup, per doctrine), - rides the hero's shoulder as a bound sprite and shows `[KEY]` on - the HUD. The gate itself is a **stone wall from the ceiling to the - ground with a locked two-tile door at its foot**: the steps, two - step coins, a coin inside the passage, and **the flag itself** sit - behind it, so the win provably requires the door (three of the - fifteen coins are back there) and the flag can never end the game - with content unseen. With the key, the door opens FOR GOOD on - approach (~20px before face contact; the lock tile dissolves into - the wall; the passage body is `b2kMoveTo`-parked off-world first, - then `b2kDisable`d in its own try); keyless, it buzzes and banners - where the key is. Respawns walk back through. - (The OXT saga, each round a recorded lesson: a slime guarded the - unlock threshold — relocated; a flush-against-the-step column made - the open door a jump shaft — moved to the flat; door art created - after the hero drew over him — scenery builds first now; the - disable-then-park ordering let a swallowed throw kill the park — - park runs first now, and harness v9's `stTestDoorClears` asserts - both clearing paths; and finally the free-standing 192px column - was simply BYPASSABLE — the thwomp-ride arc sailed over it, which - is why "the door never works" survived every physics fix. A gate - must be structural: wall to the ceiling, prizes behind it.) - - **Re-skinned, sprite-only**: thwomps are now chained weights - (`weight` + `chain` tiles; the chain stays at the perch while the - weight falls) — and a weight on a chain is **not the player's to - move**: it rests STATIC (unpushable; `b2kGrab` refuses statics by - design) and `mouseDown` filters the brief dynamic fall out of the - grab, so only the crate drags. The old drag-a-thwomp coin became - the doorway crown coin. The crate itself wears the empty-box face - (`block_empty`, the same box a headbutted ?-box turns into) over an - invisible fixed-rotation host. The spike pit grew real `spikes` - tips (placed from measured alpha: tips at y 606, base flush with - the stage floor), and the level crosses a biome seam at the locked - door into a STONE finale. - - **Win-state clarity** (user report: "the flag win state is - confusing"): every coin lands in one `pfGainCoin`; the LAST one - turns the goal flag **GOLD** (`flag_yellow` wave) with a banner + - chime — "ready to win" is visible from across the level — and - touching the flag short of the total now **buzzes** and banners - exactly how many coins remain (throttled; the HUD line repeats - it). The red checkpoint flag can no longer be confused with the - goal. - - **Layout audit** (jump math, v=430/g=600 → 154px apex; spring 620 - → 320px): all 15 coins verified reachable, every beat passable. - One fix fell out: the springboard squatted on the only path out of - the spawn — it now hugs the left wall behind the spawn, a - discovery toy that blocks nothing. - - Fifteen coins now (sky coin + two box coins); six new synthesized - cues (spring, smash, key, unlock, lever, deny) — still zero asset - files for audio. - **Wave 0: the asset pack catalogued; the content roadmap expanded.** - The uploaded Kenney platformer family (~900 frames, three compatible - sets) is fully inventoried in `docs/expansion-prep.md`: six 70px - terrain biomes with slope and one-way tiles, buttons/levers/ - springboards/keys+locks/doors/ladders/liquids/ropes/?-boxes, a - 25-species enemy sheet (bat, ghost, piranha, mimics, crushers…), - p1–p3 players plus five alien skins **with climb and swim frames**, - and a hearts-and-digits HUD set. Decisions recorded: the 70px grid is - primary; **sprite-only visuals from here on** (plain LC graphics only - as invisible physics hosts; existing no-asset fallbacks frozen); the - roadmap now spans ten player actions, a fourteen-archetype bestiary, - seven biomes (snow = ice friction), the full switch family, and eight - waves aimed squarely at mimicking the iconic platformers. -- **`b2kOverlapMoving` — the presence poll done right (the plate's last - stand).** The polled plate read *pressed from frame one, forever*: - its pad region sits on the floor, and `b2kOverlap` asks the - **broadphase**, whose boxes are fattened (~0.1 m ≈ a few px at game - scale) — so the ground slab itself permanently overlapped the region. - New `b2kOverlapMoving` runs the same query with **static bodies - filtered out**, which is what a plate means ("is some *thing* on - me?"); dynamic and kinematic bodies count, sleeping ones still - register. The platformer's plate uses it; both kit docs warn about - the fat-AABB margin; and the self-test (v8) closes its own blind spot - — the old presence test only asserted positives, never "empty over - bare floor", which is exactly where the fattening bites. +- **`b2kOverlapMoving` — the presence poll done right (the plate's last stand).** - **Documentation sweep + the expansion plan (the green-board close).** - With the self-test at v7/all-pass and both games user-verified, every - doc now states the as-built truth: the game-engine spec is marked - **implemented** (deltas live in plan.md's decision log), plan.md flips - Phase 3 / audio / scenes / the micro-game exit to *shipped and - user-verified*, the Kit reference documents the player's **feel - guarantees** (sim-time windows, dead landings, hysteresis, - ground-snap + the `b2kPlayerJump` rule) and `b2kSetVelocity`'s - wake-on-write, README points new platforms at the self-test first, - and CLAUDE.md codifies the campaign's doctrine as gotchas #14–16 - (contact-target wiring, the chain ghost rule, windows/polls over - instantaneous reads) plus the harness workflow (assert + bump - `kStHarnessV` with every Kit change). New: **`docs/expansion-prep.md`** - — the intake plan for the upcoming asset dump and the content phase: - delivery format, the measure-before-placing rule, a player-actions - roadmap (drop-through, wall-jump, dash, double-jump, platform carry — - each with tuning keys and a self-test sketch), an enemy archetype - roadmap mapped to the known Kenney frames, the rules of engagement, - and a six-wave order ending in builder cross-pollination. -- **The harness doubles its reach (engine-surprise insurance).** Six new - suites, ~40 new assertions, aimed squarely at "the LC engine OXT - inherited is full of surprises": **engine contracts** (fractional - `mod`, byte packing, base64, temp-file I/O, imageData/alphaData - strides, `word 2 to -1` chunk semantics, `wholeMatches lineOffset`, - long-id re-resolution after a relayer, custom-property boolean - round-trips, `playLoudness` readback — when an engine update or new - platform breaks one, the report names it in plain text); - **materials + joints** (a 0.8-restitution ball measurably rebounds, - density drives mass, a motorised hinge spins, a rope holds a dangling - ball at length); **filtering** (no-collide pairs pass through; a - ghost-layer ball ignores a solid-layer platform — regression for the - named-layers path that once always threw); **queries** (controlAt / - contains, overlapCircle, multi-ray nearest-first); **sheet extras** - (per-sheet scaling sizes sprites, margin/spacing grids count - correctly, frame-size-0 + `b2kSheetAddFrame` manual regions slice, - flips swap to a real mirrored frame image and back); **player slopes - + limits** (climbs a 22° chain ramp without wall-stick — also guards - the ground-snap's slope exemption — maxFall clamps a long dive, and - the kill floor never takes the player). The micro-game's temporary - `ev` HUD diagnostic is retired now that it plays clean. +- **The harness doubles its reach (engine-surprise insurance).** - **The micro-game's actual bug: it never set `b2kContactTarget`.** - Sensor and contact *messages* dispatch to the contact target; the - micro-game only set the frame target, so every coin/spike/door event - fired into the void on every build — solids, player, camera and - visuals all worked, which made it look like "collision" breakage. - One line fixes it. The self-test had a matching blind spot — its - sensor test used the polling accessors, which need no target — so the - events test now also asserts the **message path** delivers - (`on b2kSensorEnter` / `on b2kContact` received exactly once / - at-least-once via a registered contact target). -- **Self-test round 4: ground-snap + wake-on-SetVelocity.** The - instrumented land test measured the truth: after a hard landing the - contact solver's push-out launches a real ~7px hop (24 frames of - airtime, down-leg at ~61 px/s) — too long for hysteresis to mask, and - with restitution already zero. The controller now **ground-snaps**: - grounded on *flat* ground (probe normal |x| < 0.1 — slopes exempt, - uphill running is real upward motion), drifting upward, without - having jumped ⇒ upward velocity zeroed at the source. External - boosts must use `b2kPlayerJump` (the platformer's stomp bounce now - does — the jump flag exempts it from the snap). Separately, - **`b2kSetVelocity` now wakes the body**: raw SetVelocity does not - (which is why `b2kPush` always called SetAwake), so a parked, - *sleeping* kinematic gate given one velocity write stayed frozen — - a gate that randomly refuses to open read as "the plate is flaky - again". The platformer's stomp gate also gained a recent-airborne - window (250 ms) so a slime sinking under the landing hero can't - outrun the state reads. -- **Self-test round 3: landing hysteresis.** Zeroing restitution wasn't - the whole story — the suite still counted two land ticks per jump: - the contact solver's push-out can blip the 4px ground probe off for a - tick around an impact, which the state machine read as a micro-fall - and a second landing. The controller now has the genre-standard cure: - a touchdown counts as `land` only after a real airborne stretch (3+ - ticks), and a single ungrounded blip doesn't even show as airborne - (no one-frame jump-anim flicker on rough ground or ramp seams) — - unless it's the controller's own jump, which still reads `jump` on - its launch frame. `b2kPlayerOnGround()` stays raw and truthful; only - the state classification gained hysteresis. -- **Self-test round 2: the player landed springy.** The harness's - "land fired exactly once (got 2)" caught that controller-created - capsules kept `b2kAddCapsule`'s default **0.2 restitution** — every - landing rebounded ~13px (a second airborne arc: double land ticks, - double land sounds, a faint trampoline feel). `b2kPlayerAttach` now - zeroes bounce on bodies it creates; explicit bounces (the slime - stomp) are unaffected. The camera test's final assert also stopped - using a pre-`b2kCamOff` long id (group paths go stale on ungroup — - the test now re-resolves by name). -- **First self-test run: two real Kit bugs found and fixed.** The - harness's first hardware run (35 passes) exposed: (1) **the player's - coyote/buffer timers ran on wall-clock time** — on a slow machine the - 90/110 ms windows silently shrink to fewer frames than designed; they - now run on a **sim-time clock** (summed frame ms — identical under - the live loop, frame-coherent everywhere, deterministic under - hand-stepping); (2) **`b2kSpriteAnim`/`b2kSpriteFrame`/ - `b2kSpriteFlipped` threw on a removed control** instead of honouring - the Kit's stale-ref tolerance — they now return empty/false. Three - failures were the harness's own arithmetic (jump arcs at scale 40 - take ~138 frames; tests now run full arcs, settle between phases, and - isolate each test so one throw cannot abort the suite). -- **The Kit self-test harness** — - `examples/box2dxt-selftest.livecodescript`: one click in OXT, ~30 - seconds, a PASS/FAIL report. There is no headless OXT, so this stack - is the project's runtime safety net: it drives the REAL Kit - deterministically (worlds started paused and advanced by - `b2kStepOnce`; the keyboard replaced by the new - **`b2kInputInject`**/`b2kInputInjectOff` — scripted keys with exact - frame timing, also useful for replays) and asserts the behaviours the - games depend on. Every test encodes a lesson learned on real - hardware: fixed-step determinism, frame-exact events (one enter, one - exit, no duplicates at rest), the chain ghost rule, one-way chains, - presence polling incl. **sleeping** bodies, `b2kKillFloor` + - `b2kFell`, the player feel contract (run accel, grounded probe, - tap-vs-held jump, the **coyote** and **buffer** windows, land firing - exactly once), input edges, sprites, tones, camera adopt/goto/off, - and teardown hygiene. Run it after any Kit change and on every new - platform before trusting the games. The micro-game's level build is - now also **error-proof**: the whole build runs guarded so a mid-build - error can never leave the screen locked — it lands, named, in the HUD - for verbatim reporting. -- **The pressure plate is polled, not counted.** Exact events unmasked - what enter/exit *counting* had been hiding: Box2D's sensor begin/end - around settling and sleeping bodies is precisely the edge a pressure - plate lives on (the old duplicate-enter bug had been inflating the - count, accidentally holding the gate open). The plate is now a pure - graphic whose pressed state is **polled** with `b2kOverlap` each - frame — stateless, so it cannot drift, and the broadphase includes - sleeping bodies, so a crate that settles and sleeps keeps holding it — - plus a 200 ms release debounce so micro-bounces don't flap the gate. - Doctrine added to the sensor docs: **enter/exit for one-shots; polling - for presence.** With this, no gameplay state in the demo depends on - balanced event counting: everything is one-shot, guarded, geometric, - or polled. -- **Frame-exact physics events (the "flaky collisions" root cause) + - `b2kKillFloor`.** Box2D only exposes the **last step's** begin/end - contact and sensor events, but a frame can run two fixed steps (under - load) or zero (timer jitter) — so the old once-per-frame snapshot - *lost* the first step's coin touches and stomps on heavy frames and - *re-dispatched* the previous step's events on empty ones (a pressure - plate could double-count). The Kit now **harvests events after every - fixed step into per-frame buffers**; the `b2kContact`/`b2kSensorEnter` - messages and all polling accessors read the same complete, - duplicate-free frame view. New **`b2kKillFloor screenY`**: any moving - Kit body whose centre falls below the line is destroyed instead of - falling forever (crates thrown into pits, enemies knocked off the - level), with a **`b2kFell ctrl`** message to the frame target first so - games clean up bound art and table slots — the player's body is - exempt. Hooked into the body-sync loop, so it costs nothing extra (a - falling body is by definition a moving one). The platformer arms it at - 820px, cleans up fallen slimes/thwomps in `on b2kFell`, and replaces - its thin level-edge segments with **thick boundary slabs** (a capsule - driven into a thin segment can jitter or creep; a 48px box stops it - flush). -- **Hot-path performance pass (engine-limitation aware).** The frame - budget on a single interpreted thread goes to interpreter ops, FFI - round-trips and property-set redraws, so all three got leaner: - **sprite tick** now maintains a lazy *live list* (bound and/or playing - sprites) and walks only that — a tile-heavy level's ~100 inert sprites - cost zero per-frame work, with membership re-derived only when a - bind/play/stop/remove actually changes it; **input** resolves friendly - key names to keycode lists once at bind time (`b2kBindAction`/ - `b2kBindAxis`), so the per-frame action/axis queries are pure set - scans; the **player tick** reads its nine tuning knobs and probe - geometry from values baked at `b2kPlayerSet`/attach time and talks to - the body through its raw handle (no per-frame ref lookups or "x,y" - string round-trips — same math, gotcha-9 flip preserved). Example - side: both games **throttle their HUD to 4 Hz** — the ms readout - changed every frame, which forced a field relayout+redraw every frame, - the single biggest avoidable cost — and the platformer's gate writes - its kinematic velocity only when the target flips. The kit guide's - performance section now documents the playbook (throttle HUDs, write - on change, one clock read per handler, build heavy things once). -- **The micro-game (Game Kit Phase 5 exit artifact):** - `examples/box2dxt-microgame.livecodescript` — a COMPLETE game in one - pasteable file: start screen → two levels → win screen, with nothing - to install beyond the extension (the hero sheet is embedded base64, - every sound is `b2kToneMake`d). It is the "copy this to start your own - game" example and the companion to the kit guide's new **"Building a - whole game"** chapter (§20). What it adds over the platformer - showcase: the **one-call player** (`b2kPlayerMake` — the green-field - path the platformer's adopt-flow doesn't exercise), **levels as - data** (each level is a dozen lines of `verb args` text — `slab`, - `ledge`, `coin`, `spike`, `sweep`, `door`… — interpreted by a ~100 - line `mgBuild`; the `ledge` verb ghost-pads its chain automatically), - and a **game-state machine** (menu/play/won) gated by - `b2kPlayerControl`, so the world runs live behind the menus. The - coins-unlock-the-door rule, sweeper hazards, kill-plane respawns and - the win screen's time/falls stats are all sensor + frame-hook - patterns, no new Kit surface. This is also the plan's scenes/levels - design probe: the level format lives at example level first; - promotion to `b2kScene*` API gets decided from how it holds up. -- **Kit Sound module (Game Kit Phase 5 begins).** Named sounds over - **audioClips** — the one LC sound path with no external media-layer - dependency — with `b2kSoundLoad` (import a WAV/AIFF/AU file) and - **`b2kToneMake`**, a pure-script synthesizer (8-bit mono WAV at - 22050 Hz, square or sine, a comma list of note frequencies with a - per-note decay) so self-contained examples ship SFX with **zero asset - files**. `b2kSound`/`b2kSoundLoop`/`b2kSoundStop` play (one clip at a - time — the classic LC model, documented not fought), `b2kSoundMute` is - a preference that survives teardown, `b2kSoundVolume` wraps the - engine-global `playLoudness`, and failures degrade to silence, never - errors (first failing play trips a dead-flag; `b2kSoundStatus()` says - why). Sounds **survive `b2kTeardown`** — clips are tiny and - deterministic, and resets must stay snappy; `b2kSoundsWipe` purges - them (it also sweeps `b2ksnd_` clips a dead session left behind, and - stable names mean re-making replaces rather than accumulates). The - platformer gains eight synthesized cues — jump, land (off the player's - one-tick land state), coin, stomp, hurt, checkpoint, gate, win — plus - an M-key mute and HUD audio diagnostics. -- **Sheets beyond the Kenney format: custom grids and hand-named - regions.** `b2kSheetLoad`/`b2kSheetFromImage` take optional `margin` - (outer border px) and `spacing` (gutter between cells px) for grid - sheets that aren't edge-to-edge, and a frame size of **0 registers the - source with no grid at all** — then **`b2kSheetAddFrame sheet, frame, - x, y, w, h`** names each region yourself: the no-XML path for packed - sheets in any layout (any sizes/positions; redefining a name re-bakes - its slice; works on top of grids and atlases too). - **`b2kSheetFrameNames(sheet)`** lists every frame key of a loaded - sheet — introspect an atlas you didn't make. -- **The platformer is now a collect-them-ALL puzzle platformer** on a - rebuilt, half-again-longer level (3072 → 4608 px) with a traditional - left-to-right arc: meadow → one-way bridge over a **spike slime** (the - unstompable kind — hurts from every side) → the slope mound → two - cloud steps guarded by the fly → spike pit with a mid-air coin → the - crate/plate/gate puzzle (a coin locked **inside the gateway**) → - checkpoint → a saw plus a **sweeping saw** (a bodiless sine-path mover, - like the fly) → **thwomp alley** — dodge the first, ride the second - one's head to a sky coin, and **mouse-DRAG a resting thwomp** under - the lone high coin to ride up from there (rest extended to 2.6 s so - dragging is feasible; it rises from wherever you park it, then snaps - home) → slime fields → step platforms → flag. **Twelve coins, every - one verified reachable** (jump arc ≈ 154 px apex at the demo tuning), - most of them a small puzzle; all 12 + flag wins. Enemies/traps/movers - are now indexed tables (`pfMakeSlime`/`pfMakeThwomp`/`pfAddMover`), so - the level adds foes in one line each. The **mound's ramp art is fixed** - by measuring the actual Kenney tiles (the short ramps are a 45° pair, - drawn descending left-to-right; the long ramps are the 26.6° pair that - matches the chain) — ascent shows them mirrored, and the ground row - beneath the mound switches to dirt-centre tiles so the hill reads as - one mass. The first OXT runs then surfaced **the chain ghost rule**: - Box2D collides an open chain's N points as only **N−3 segments** (the - first and last are ghost anchors), which the original level had - silently respected by always running chains one tile past the art — - the rebuild didn't, so the bridge's outer planks, both cloud edges and - *both mound ramps* were intangible. All four chains are now - ghost-padded (the mound grew to six points so its ramps, the actual - slope test, really collide), and the rule is documented in `b2kChain`, - the kit reference and the guide. Two more OXT findings fixed: **slime - stomps are judged by the player controller's land/fall state**, not by - post-impact velocity (contacts dispatch after the solver has already - absorbed the impact, so a clean stomp read as ~0 velocity and hurt the - hero), and **thwomps re-arm wherever they rose** instead of - teleporting back to their perch (the snap-home read as - vanish-and-reappear, and it undid the drag-to-reposition puzzle). - Optimization pass: the sprite tick now skips inert sprites (no bind, - no animation — about a hundred static tiles) before doing any work, - sounds persisting across teardown removes the ~quarter-second tone - re-synthesis from every reset, and the mover tick reads the clock - once per pass. -- **Kit Player module (Game Kit Phase 3 — the headline feature).** A - complete platformer character controller for one keyboard player: - `b2kPlayerMake` (capsule body host + bound sprite + controller + input - in one call) or `b2kPlayerAttach` (adopt an existing control/sprite — - a capsule body is added if missing, with fixed rotation, sleep - disabled and low friction). The per-frame `b2kPlayerTick` (loop order: - input → player → sprites → camera) reads axis `moveX` / action `jump`, - accelerates vx toward `axis × moveSpeed` (`accel`/`airAccel`), probes - the ground with three short downward rays gated by a surface-normal - slope test (`maxSlopeDeg` — walkable slope vs wall is one cosine - compare; the probe is suppressed while the controller's own jump is - still rising, so one-way chains can't phantom-ground a jump-through), - and runs the state machine (`idle`/`run`/`jump`/`fall` + a one-tick - `land` for dust/sound hooks). Genre-standard jump feel is built in: - **coyote time** (`coyoteMs`), **jump buffering** (`bufferMs`), - **release jump-cut** (`jumpCut` — tap = hop, hold = full) and a - terminal fall speed (`maxFall`). `b2kPlayerAnims` maps states to sheet - animations (optional held land flourish; facing auto-flips); - `b2kPlayerSet/Get` expose the nine tuning knobs (kept across - `b2kClear` like input bindings, wiped by `b2kTeardown`); - `b2kPlayerJump` gives springs/powerups the same launch without the - grounded gate; `b2kPlayerControl false` turns the controller - observe-only for cutscenes/hit-poses/knockback; plus - `b2kPlayerOnGround/State/Facing`, `b2kPlayer`, `b2kPlayerSprite`, - `b2kPlayerRemove`. New general body setting: **`b2kSetSleepEnabled`** - (per-body sleep permission over raw `b2EnableSleep`; the controller - forbids sleep on its body). The platformer example's entire hand-rolled - movement layer — axis-to-velocity tick, jump-press/release handling and - the two-ray ground probe (~53 lines) — is replaced by four declarative - calls (`b2kPlayerAttach` + `b2kPlayerAnims` + two `b2kPlayerSet`s), - and the level gains a **walkable slope mound** (26.6° Kenney ramp - tiles over a one-sided chain, with a plain-polygon fallback), a 7th - coin on its plateau, and a HUD readout of the controller state, land - count and sleep anomaly for the OXT feel checklist. -- **Kit Camera module (Game Kit Phase 4, pulled forward).** Scrolling - levels on the viewport-group mechanism the Phase 0 spike benchmarked: - `b2kCamOn/Off`, follow with lerp + deadzone (`b2kCamFollow`, - `b2kCamDeadzone`), level bounds, `b2kCamGoto`/`b2kCamPos`, - `b2kCamShake`, and camera-aware mouse mapping (`b2kCamMouseX/Y` — the - Kit's own grab-drag target now routes through it). While the camera is - on, spawned bodies and sprites are created inside the viewport - automatically and keep world-pixel locs, so physics math is untouched; - `b2kCamAdopt` moves hand-made controls in; `b2kTeardown` dissolves the - viewport and the orphan sweep ungroups (never deletes) a dead - session's viewport so adopted controls survive. The platformer example - is now a complete 3072px scrolling level: far-parallax backdrop, - spike pit, a pressure-plate + crate puzzle driving a kinematic gate - (sensor enter/exit counting), a patrolling stompable slime - (contact-event stomp vs hurt), camera shake on every hit, and the - goal flag — with plain-graphic fallbacks for every piece of art. -- **The platformer demo (release candidate).** The example grew into a - complete showcase: intro splash, centre-locked scrolling camera, a - two-cloud one-way route guarding a bonus coin, hazard fly, spike pit, - crate-on-pressure-plate gate puzzle, mid-level checkpoint flag, saw, a - riding Thwomp (underside kills, head is a platform, slow kinematic - rise), two stompable patrol slimes, six coins with pickup pops, and an - all-coins-plus-flag win dialogue with run time, fall count and a - physics-confetti burst. R restarts and ESC pauses (raw key events, so - they work while paused); every art piece has a plain-graphic fallback. -- **Camera hardening (from the demo's OXT runs).** `b2kCamOn` self-tests - that the scroll property pans the group and probes the engine's - grouped-loc coordinate model (`b2kCamStatus`, `b2kCamLocSemantics`); - scroll-adjusted ("visual") engines get every in-viewport write - compensated automatically (`b2kCamShiftX/Y`, `b2kSpriteMoveTo`). The - follow carries a mathematical never-offscreen guarantee plus a - real-scroll failsafe; the bounds stretch the viewport's invisible - anchor so the engine can never clamp the scroll short; teardown wipes - sprites BEFORE dissolving the viewport (their stored ids embed the - group path), and sprite removal survives stale references. -- **Kit Sprite module (Game Kit Phase 2).** Spritesheet animation on the - icon-button backend the Phase 0 benchmarks selected: sheets register - frame regions of one hidden source image — a uniform grid - (`b2kSheetLoad`), an existing stack image (`b2kSheetFromImage`), or a - packed **TextureAtlas XML** with named regions (`b2kSheetLoadAtlas`, - the Kenney format shipped in `Spritesheets/`). Regions are sliced into - shared per-frame images lazily; a sprite is a transparent button whose - icon is the current frame. Named animations (`b2kAnimDef` with ranges - or frame names), `b2kSpritePlay/Stop/SetFrame/FPS`, lazy mirrored - facing (`b2kSpriteFlipH`), end-of-animation messages - (`b2kSpriteOnFinish`), body-following sprites (`b2kSpriteBind` — give - an invisible control the body, bind the art to it), an animated-GIF - backend (`b2kSpriteFromGIF`), and lifecycle integration (`b2kClear` - removes sprites, `b2kTeardown` also frees sheets). The platformer - example now plays an atlas-driven animated hero with spinning coin - pickups, a patrolling bee, and a saw hazard that chains - hit-pose-then-respawn through `b2kSpriteOnFinish`. **Per-sheet display - scaling** (`b2kSheetScale` — engine-resampled at slice time, any frame - size at any sprite size; `b2kSheetFrameSize` for layout math), and an - **orphan sweep** on every teardown (script state resets when a stack - reopens but controls persist — previously a ghost sprite could linger - frozen on its last frame). The platformer example is now a polished - scene: hills backdrop panels (256px art engine-scaled to 640), grass - tile terrain over invisible physics slabs, bridge-plank one-way ledge, - a 0.75-scaled hero, and a waving goal flag with a LEVEL COMPLETE - state. -- **Kit Input module (Game Kit Phase 1).** Poll-based keyboard input for - games: `b2kInputOn` arms a once-per-frame `keysDown` sample (taken inside - the loop's screen lock, so `on b2kFrame` always sees the current frame's - state), diffed against the previous frame for exact pressed/released - edges with zero auto-repeat or focus-path artifacts. Friendly key names - (letters match both shifted/unshifted keysyms), named **actions** - (`b2kBindAction "jump", "space,up,w"` + `b2kActionIsDown/Pressed/ - Released`), **axes** (`b2kAxis("moveX")` → -1/0/+1, both directions held - = 0), `b2kKeyIsDown/Pressed/Released`, `b2kKeysHeld()`, and - `b2kFrameMS()` (the frame's real elapsed ms, for animation timing). -- **Paced simulation loop.** `b2kStep` now schedules the next tick - `in max(1, 16 − elapsed)` ms instead of a flat 16 ms after the frame's - work — the flat delay made the real rate "timer cadence plus frame - cost" (~50 fps under load in the Phase 0 spike measurements). -- **`examples/box2dxt-platformer.livecodescript`** — Game Kit Phase 1's - playable example: a fixed-rotation capsule driven by the Input module - (axis run, edge-triggered jump with release-cut variable height, ray - grounded-check) on flat ground, steps, and a jump-through one-way chain - ledge. Registered in `tools/sync-embedded-kit.py`. -- **Keyboard support in the contraption builder.** **Delete**/**Backspace** - removes the selected part or joint (Build mode, with full joint/wire - cleanup); **Escape** walks outward — closes an open overlay, then cancels a - half-made joint or wire, then clears the selection; **arrow keys** nudge the - selected part 1 px (Shift: 10 px) with the same group-follow, arena clamping - and body re-seat as a mouse drag. -- **Generation-tagged handles (C shim).** Every handle now packs an 11-bit - generation above its 20-bit table slot, bumped each time the slot is freed. - A stale handle therefore stays a harmless no-op even after its slot is - recycled by a new object — previously a stale handle could silently alias - whatever was created next in that slot. First-generation handle values are - unchanged (1, 2, 3, …) and the ABI signatures are untouched; treat handles - as opaque positive ints. The smoke test now proves a recycled slot rejects - its old handle. -- **ABI fail-fast in the LCB binding.** `b2NewWorld` / `b2NewThreadedWorld` - check `b2lc_abi_version()` and throw a clear, catchable error when the - loaded native library doesn't match the binding (ABI `4`), instead of - crashing on whichever later call hit the skew first. -- **A root `README.md`** — project overview, quick start, doc map, and build - instructions on the repository landing page. - +- **Self-test round 4: ground-snap + wake-on-SetVelocity.** +- **Self-test round 3: landing hysteresis.** +- **Self-test round 2: the player landed springy.** +- **First self-test run: two real Kit bugs found and fixed.** +- **The Kit self-test harness** — `examples/box2dxt-selftest.livecodescript`: one click in OXT, ~30 seconds, a PASS/FAIL report. +- **The pressure plate is polled, not counted.** +- **Frame-exact physics events (the "flaky collisions" root cause) + `b2kKillFloor`.** +- **Hot-path performance pass (engine-limitation aware).** +- **The micro-game (Game Kit Phase 5 exit artifact):** `examples/box2dxt-microgame.livecodescript` — a COMPLETE game in one pasteable file. +- **Kit Sound module (Game Kit Phase 5 begins).** +- **Sheets beyond the Kenney format: custom grids and hand-named regions.** +- **The platformer is now a collect-them-ALL puzzle platformer** on a rebuilt, half-again-longer level. +- **Kit Player module (Game Kit Phase 3 — the headline feature).** +- **Kit Camera module (Game Kit Phase 4, pulled forward).** +- **The platformer demo (release candidate).** +- **Camera hardening (from the demo's OXT runs).** +- **Kit Sprite module (Game Kit Phase 2).** +- **Kit Input module (Game Kit Phase 1).** +- **Paced simulation loop.** +- **`examples/box2dxt-platformer.livecodescript`** — Game Kit Phase 1's playable example. +- **Keyboard support in the contraption builder.** +- **Generation-tagged handles (C shim).** +- **ABI fail-fast in the LCB binding.** +- **A root `README.md`** — project overview, quick start, doc map, and build instructions on the repository landing page. - **Showcase pass — every Kit capability is now reachable from the builder.** - Several engine features the demo never surfaced are now exposed (raising the - count of directly-used `b2k…` handlers from 79 to 98; the rest are alternate - spellings of features already shown, e.g. `b2kSpawnBall` vs `b2kAddBall`): - - **Laser (SPECIAL).** A body-less emitter that casts a ray each frame and - draws its beam to the first thing it strikes — walls count — via `b2kRayHit` - / `b2kRayHitX/Y` / `b2kRayDist`. The beam is live in Build and Run, follows - its emitter when dragged, and its angle cycles in 45° steps; select it during - Run to read the live hit distance. - - **Thruster (SPECIAL).** A self-propelled rocket: an ignition pop (`b2kPush`) - on Run, then steady force along its own facing (`b2kForce`) with optional - spin (`b2kTorque`). Lock its rotation for a straight climb or let it tumble. - - **Shift-click to kick.** Shift-clicking a body during Run gives it a sharp, - mass-aware impulse and a tumble (`b2kImpulse` / `b2kAngularImpulse`). - - **Live telemetry.** While running, the status bar shows solver step time and - awake-body count (`b2kProfile`, `b2kAwakeBodyCount`) plus a live readout of - the selected part (speed/mass/angle) or joint (`b2kHingeAngle` / - `b2kRopeLength` / `b2kSliderPos`). - - **Bomb blast preview.** Selecting a bomb reports how many parts sit inside - its radius right now (`b2kOverlapCircle`), and a bomb can switch between the - shape-aware native blast and the old velocity-based feel (`b2kExplodeLegacy`). - - **World · Advanced panel.** Solver toggles the Feel presets don't cover — - continuous collision (`b2kEnableContinuous`), sleeping (`b2kEnableSleeping`), - warm-starting (`b2kEnableWarmStarting`) and a sub-step quality cycle - (`b2kSetSubsteps`) — applied on Run and saved with the layout. - -- **Per-part collision filter (contraption builder).** Beyond the quick - *Collision layer*, every solid part now has a **Collision filter** that opens a - popup of eight channels in two rows — which channels the part *is on* - (category) and which it *collides with* (mask). Two parts touch only when each - is on a channel the other collides with, so you can make whole sets of parts - ignore each other while still landing on the ground. Built on the Kit's - `b2kSetCategory` / `b2kSetMask`, re-applied after any reshape, and saved/loaded. -- **Per-part sensor toggle (contraption builder).** Any solid part (box, image, - ball, capsule, polygon) can be flipped into a **trigger zone** from its Collide - tab — it turns translucent, stops blocking, and fires the same enter/exit - signal as the dedicated Sensor. The Kit gained `b2kSetSensor` so the flag can - be toggled on an existing part (the shape is rebuilt, keeping the sensor state). - Saved and loaded. -- **Categorised Examples gallery.** The Examples menu is now a tidy two-column - gallery grouped into **Launchers / Machines / Chain Reactions / Toys & Tests** - with section headers, instead of one long scrolling list — room to keep adding - machines and far easier to scan. (UI version bumped so it rebuilds.) -- **Enable/disable parts.** Every dynamic part has an **In simulation: yes/no** - toggle (Collide tab) that pulls it out of / back into the live world - (`b2kDisable` / `b2kEnable`); saved and loaded. -- **Comprehensive part inspector.** More of box2dxt is now editable per object: - a body can be **kinematic** (a moving platform — set a launch velocity and it - drifts, unaffected by gravity or collisions) as well as static or dynamic; - every dynamic part exposes a **sleep threshold**; **imported images** can pick - their collision shape (box / ball / capsule, which survives resize and - save/load); and previously bare inspectors gained options — *Draw* ground gets - a colour and *Anchors* get bounce and grip. -- **Three more example machines (contraption builder).** **Trebuchet** (a - counterweight arm that whips round and flings a ball), **Crane** (a motorised - jib with a load hung on a rope) and **Wrecking Ball** (a roped weight shoved - into a brick wall) — each one click from the Examples menu, showcasing the - hinge-motor, weld and rope joints. -- **Cannon example recipe (contraption builder).** A new one-click machine in the - Examples menu: a static carriage + barrel and a heavy ball that fires across the - field on Run (via the launch system) into a crate stack. It doubles as a demo - of **collision layers** — the ball and barrel share a layer so the ball slips - clear of the barrel as it launches. -- **Draw-your-own terrain (contraption builder).** A new TERRAIN tool (**Draw**, - the ✎ glyph): press and drag across the play field to sketch a ground contour — - bumps, valleys, ramps, anything — and on release it becomes a smooth one-sided - chain (`b2kAddChain`, open) that parts roll along without catching on seams. It - saves and loads as its own point list and can be dragged to reposition. -- **World feel presets (contraption builder).** A new BUILD OPTIONS cycle button - beside Gravity tunes how the whole world responds — **Default**, **Bouncy**, - **Floaty** (soft, speed-limited) and **Snappy** (stiff, responsive) — via the - Kit's restitution-threshold, contact-tuning and max-speed setters. Re-applied - each Run and saved/loaded with the layout. -- **Servo joint (contraption builder).** A new joint (the Kit's motor joint, - `b2kMotorTo`) that drives part A to hold its position and angle relative to - part B, yielding under load and springing back rather than locking rigidly like - a weld — for self-righting parts, soft platforms and return-to-home arms. Saves - and loads like any joint. -- **Sensor trigger zones (contraption builder).** A new SPECIAL part: a - non-solid zone that parts pass straight through, but the instant a dynamic body - *enters* it, it fires the same signal as a pressure plate — set off bombs and - flip every motor. Perfect for tripwires and gates; it fires once per arrival - (occupancy-tracked) so passing traffic doesn't machine-gun the motors. Built on - the Kit's sensor events, and resizable — the Kit's `b2kReshape` now keeps a - reshaped shape a sensor. -- **Collision layers (contraption builder).** Every solid part now has a - *Collision layer* setting on its Collide tab: 0 hits everything (the default), - while parts sharing a layer 1–8 pass through each other but still collide with - the ground and parts on other layers — handy for overlapping mechanisms or - letting a sub-assembly move without snagging on itself. Saves and loads with - the part. -- **Smooth, rolling hills (contraption builder + Kit).** The Hill terrain tool now - builds a smooth chain that follows its outline instead of a single convex - polygon, so a fast ball or wheel rides over it without catching on seams — and - the bump count scales with width (a narrow hill is a dome, a wide one rolls). - Backed by a new Kit helper, **`b2kAddChain pControl, pPoints [, pLoop]`**, which - attaches a *control-tracked* smooth chain — unlike the world-only `b2kChain` it - selects, drags, resizes, deletes and saves like any static part. -- **Full Box2D v3.1.0 live-object API.** The binding now exposes essentially the - whole engine surface a script needs — ~240 new shim functions, each with a - `b2…` extension wrapper and, where it helps, a friendly `b2k…` Kit helper: - - **Sensors.** Non-solid trigger zones via a new **shape-def builder** - (`b2ShapeDefSensor`, `b2ShapeDefFilter`, `b2ShapeDefEnable*Events`, - `b2ShapeDefMaterialId` — one-shot options applied to the next shape). Sensor - overlaps arrive as `b2World_GetSensorEvents` (`b2SensorsUpdate` + accessors). - The Kit adds `b2kAddSensor` and `on b2kSensorEnter` / `on b2kSensorExit`. - - **Collision filtering.** `b2SetShapeFilter` + category/mask/group getters - (32 layers). The Kit adds a named-layer registry: `b2kDefineLayer`, - `b2kSetCategory`, `b2kSetMask`, `b2kSetCollisionGroup`, `b2kNoCollide`. - - **Chains.** Smooth multi-segment terrain (`b2CreateChain` + builder, - materials, segment enumeration; new chain handle table). Kit: `b2kChain`, - `b2kSmoothGround`. - - **More joints.** The **motor** joint (`b2MotorJoint`, drive a body to an - offset pose) and the **filter** joint (`b2FilterJoint`, disable a single - pair's collision), the generic joint surface (type, bodies, anchors, - constraint force/torque, collide-connected, wake), and the **complete - per-joint get/set** surface for all six joint types (springs, limits, - motors, readouts). Kit: `b2kMotorTo`. - - **World queries.** Overlap (AABB / point / circle / shape), ray-cast-**all** - (sorted), and shape-cast, surfaced through one shared result buffer - (`b2QueryCount`/`b2QueryBody`/…). Kit: `b2kOverlap`, `b2kOverlapCircle`, - `b2kRayHitAll`. - - **Events.** Contact **hit** events (impact point/normal/speed), **sensor** - events, and bulk **body-move** events (`b2BodiesUpdate` — an efficient way to - read every moved transform in one call). - - **World tuning / info.** Restitution & hit-event thresholds, contact/joint - tuning, max linear speed, warm-starting/speculative toggles, gravity getter, - `b2WorldExplode` (native blast), and profile/counters. Kit passthroughs plus - `b2kProfile`; `b2kExplode` is now native (old behaviour kept as - `b2kExplodeLegacy`). - - **Body extras.** World/local point & vector transforms, velocity-at-point, - force/impulse **at a point**, full mass-data get/set + `ApplyMassFromShapes`, - rotational inertia, local centre, kinematic target transform, sleep-enable, - per-body event flags, AABB, and shape/joint enumeration. - - **Shape extras.** Runtime geometry get/set for every shape type, material & - event-flag get/set, per-shape ray cast, AABB, closest point, mass data, and - sensor-overlap polling. -- **Kit completeness pass.** The Kit (`b2k…`) now covers the full core `b2…` - surface — every capability the binding exposes has a friendly - pixel/degree/screen-coordinate helper. New handlers: - - **Read state:** `b2kPosition(ctrl)` (body centre as `x,y` screen pixels — - the position partner of `b2kVelocity`), `b2kWorldCenter(ctrl)` (centre of - mass), `b2kGravityScale(ctrl)` and `b2kDamping(ctrl)` (getters for the - existing setters), and `b2kControlContains(ctrl, x, y)` (a rotation-aware - point-in-shape test for a single control). - - **Act:** `b2kAngularImpulse(ctrl, imp)` — a one-shot, mass-aware angular - kick, the rotational partner of `b2kImpulse`. - - **Contacts (polling):** `b2kContactCount()` / `b2kContactA(i)` / - `b2kContactB(i)` and the `b2kEndContact…` equivalents, so a `b2kFrame` - handler can read each frame's touch pairs without registering a contact - target. - - **Loader:** `b2kVersion()` returns the native ABI version for a Kit-only - "extension loaded and in sync" check. -- **Multiply tool.** Alongside Duplicate, a new **Multiply** tool asks how many - copies you want (1–50) and drops them in a tidy grid — each an independent - copy of the part you clicked (same size, colour, material and settings). Great - for crates, dominoes and brick walls. -- **Drop-in vehicles.** A new **VEHICLES** palette section adds a **Car** you - place like any other object: a chassis on two sprung, motorised wheels that - drives off when you press Run. Its three parts share a group tag, so dragging - any one of them moves the whole car as a unit (and the grouping survives - save/load/reset). The "Motor Cart" recipe now builds this same improved - vehicle. -- **Scrolling tool palette.** The left palette is now a single vertically - scrolling group, so the full tool list (SHAPES · TERRAIN · SPECIAL · VEHICLES - · JOINTS) fits a normal-height window — the panel scrolls instead of the - window growing. The stack is back to its original 760 px height. -- **Ramp facing.** Ramps now have a **Facing** setting (high on the left or - right) so you can build slopes in both horizontal directions; it mirrors the - outline and saves/loads with the piece. - -- **Terrain objects in the Contraption Builder.** A new **TERRAIN** palette - section adds three pieces of static scenery the dynamic parts rest on, roll - down and bump into: **Platform** (a solid ledge), **Ramp** (a wedge for - rolling and sliding) and **Hill** (a rounded mound). Each is a static polygon - body, so it never falls and the Kit's sync skips it. They size, rotate (the - angle is baked into the outline so a static body tilts without a redraw it - never gets), recolour, take an adjustable grip (friction), drag, duplicate - and save/load like any other part — and joints can pin to them, so you can - hinge a part to a platform or hang a bridge from a ledge. -- **"Terrain Run" example recipe** — a ball rolls down a ramp, over a hill and - onto a platform, showing the new pieces in one click. -- **Continuous integration** (`.github/workflows/build.yml`). Every push and - pull request builds the native library and runs the C smoke test on Linux, - macOS and Windows; publishing a GitHub Release (any tag name) additionally - builds and attaches the per-OS binaries to it — the canonical artifacts - `prebuilt/README.md` points to. A manual run (Actions ▸ build ▸ Run workflow) - can attach binaries to an existing release after the fact. -- **Single-source Kit + drift check** (`tools/sync-embedded-kit.py`). The - example stacks stay self-contained but now embed a *generated* copy of - `src/box2dxt-kit.livecodescript` between sentinel comments, kept in sync by - the tool. CI (`--check`) fails if a copy drifts, so the canonical Kit is the - one place to edit. - +- **Per-part collision filter (contraption builder).** +- **Per-part sensor toggle (contraption builder).** +- **Categorised Examples gallery.** +- **Enable/disable parts.** +- **Comprehensive part inspector.** +- **Three more example machines (contraption builder).** +- **Cannon example recipe (contraption builder).** +- **Draw-your-own terrain (contraption builder).** +- **World feel presets (contraption builder).** +- **Servo joint (contraption builder).** +- **Sensor trigger zones (contraption builder).** +- **Collision layers (contraption builder).** +- **Smooth, rolling hills (contraption builder + Kit).** +- **Full Box2D v3.1.0 live-object API.** +- **Kit completeness pass.** +- **Multiply tool.** +- **Drop-in vehicles.** +- **Scrolling tool palette.** +- **Ramp facing.** +- **Terrain objects in the Contraption Builder.** +- **"Terrain Run" example recipe** — a ball rolls down a ramp, over a hill and onto a platform, showing the new pieces in one click. +- **Continuous integration** (`.github/workflows/build.yml`). +- **Single-source Kit + drift check** (`tools/sync-embedded-kit.py`). - **Contraption Builder overhaul — a polished, beginner-friendly sandbox.** - `examples/box2dxt-contraption-builder.livecodescript` was redesigned for people - who know nothing about 2D physics: - - **New five-region layout** (dark theme): a top action bar, a grouped left tool - palette (SHAPES · SPECIAL · JOINTS with section headers and glyphs), the play - stage in the centre, a right **inspector** panel, and a bottom status bar. The - physics arena and its walls are inset to the centre stage. - - **Plain-language guidance everywhere:** a tooltip on every control, an - inspector that explains the hovered/selected tool in everyday words, a - dismissible first-run onboarding overlay, and a Recipes menu of ready-made - examples. - - **Deeply customizable parts.** A prominent "PART SETTINGS" panel lets you tune - a selected part live with +/− controls: **size** (re-fits the collision shape - in place), **colour**, bounciness, weight, **grip (friction)**, **gravity / - float**, **spin-lock**, **fixed ↔ free movement**, plus per-object specials - (fan direction/strength/zone size, magnet pull/push/strength/reach, bomb blast - radius/power and fuse). Everything round-trips through the save file. - - **Duplicate tool.** Click any part to drop an identical copy — same size, - colour, material and special settings — then drag it into place (it auto-selects - and switches to the Drag tool). - - **Reset.** A one-click "Reset" returns every part to exactly where it was the - moment you last pressed Run, so you can replay a contraption again and again. - - **Clearer Build vs Run.** The top accent turns amber and the subtitle reads - "RUNNING" while the simulation is live (green/neutral while building), and - picking any build tool while running drops you straight back into Build mode. - - **New objects, all built from existing Kit primitives:** **helium balloons** - (rise via negative gravity scale; lift parts by a rope; pop in a blast), - **bombs** (detonate on a hard hit, a pressure-plate signal, or an optional - Run-fuse — using `b2kExplode` + a shock ring), **pressure plates** (sense - resting weight via contact events and fire a signal that detonates bombs and - toggles all motors), and bonus **fan/wind zones** and **magnets** (per-frame - `b2kForce` fields). - - **Recipes & stress test:** Balloon Lift, Plate → Bomb Chain, Fan Tower, Magnet - Catch, the motor cart and pendulum samples, plus a Stress Test that spawns - batches of bodies to probe the frame-rate limit (live fps/body-count HUD). - - **Save format `CB2`** round-trips the new objects and their properties, and - still loads old `CB1` files. Plus internal clean-ups (table-driven button - highlighting, named constants in place of magic numbers, and hardened - image-import / load error handling). -- **Rebuilt the demo on top of the Kit.** `examples/box2dxt-demo.livecodescript` - is now a polished, self-building showcase written entirely with `b2k…` calls — - a professional header, scene tabs, a shape palette, a status HUD, and six - scenes (Playground, Pyramid, Cradle, Bridge, Vehicle, Lidar). It is - **self-contained** (bundles a copy of the Kit) so it runs from a single paste - into a stack script, and doubles as a worked example of the Kit. -- **Kit per-frame hook:** `b2kFrameTarget obj` delivers an `on b2kFrame` message - once per simulated frame, for app logic, motors, input, and custom drawing. +- **Rebuilt the demo on top of the Kit.** +- **Kit per-frame hook:** `b2kFrameTarget obj` delivers an `on b2kFrame` message once per simulated frame, for app logic, motors, input, and custom drawing. - **Kit getter:** `b2kBodyCount()` returns how many bodies the Kit is tracking. -- **Kit additions:** `b2kWall x1, y1, x2, y2` builds a static collision segment - between two screen points (custom walls, ramps, ledges, floors), so static - geometry no longer has to drop to the world-space `b2…` API; `b2kBodyType(ctrl)` - reads a body's type back as a word (`static` / `kinematic` / `dynamic`), - mirroring `b2kSetType`. Both are documented in the cheat sheet and Kit reference. -- **Kit shape resizing:** `b2kReshape ctrl, "box"|"ball"|"capsule"|"poly"` - re-fits a body's collision shape to the control's current size *in place on the - same body*, so attached joints survive and the body's mass is recomputed. Powers - the Contraption Builder's live Size control. -- **Much broader Kit coverage of the engine.** New `b2k…` helpers wrap nearly - all of the remaining low-level `b2…` API, in pixels/degrees: - - **Shapes:** `b2kAddCapsule` / `b2kSpawnCapsule` (pill shape; graphics render - as a rounded outline and rotate, images rotate via `the angle`). - - **Joints:** `b2kSlider` (prismatic) with `b2kSliderMotor` / `b2kSliderLimit` - / `b2kSliderPos`; `b2kWheel` with `b2kWheelMotor` / `b2kWheelSpring`; - `b2kHingeLimit` / `b2kHingeAngle`; `b2kRopeRange` / `b2kRopeLength` / - `b2kSpring`; `b2kWeldSpring`. - - **Forces & body control:** `b2kForce` (continuous force), `b2kSpinBy` (add to - spin), `b2kSetType` / `b2kSetStatic` / `b2kSetDynamic` / `b2kSetKinematic` - (runtime body type), `b2kEnable` / `b2kDisable`. - - **Sensing:** `on b2kEndContact` message (collisions ending); ray-hit details - `b2kRayHitNormalX/Y` and `b2kRayDist`; `b2kMass`. - - **World:** `b2kEnableSleeping` / `b2kEnableContinuous`. -- **Rotating image support in the Kit.** Dynamic **image** controls attached - with `b2kAddBox`/`b2kAddBall` now follow both the body's position *and* its - rotation (via `the angle`); previously only graphics rotated. Buttons, fields, - and other non-rotatable controls keep their rotation locked so the simulation - stays consistent with the upright render. +- **Kit additions:** `b2kWall x1, y1, x2, y2` builds a static collision segment between two screen points. +- **Kit shape resizing:** `b2kReshape ctrl, "box"|"ball"|"capsule"|"poly"` re-fits a body's collision shape to the control's current size in place on the same body. +- **Much broader Kit coverage of the engine.** +- **Rotating image support in the Kit.** - `LICENSE` (MIT, with the bundled Box2D MIT notice). - `CONTRIBUTING.md` and `CODE_OF_CONDUCT.md`. -- New documentation set under `docs/`: getting-started, building, api-reference, - kit-reference, and architecture. +- New documentation set under `docs/`: getting-started, building, api-reference, kit-reference, and architecture. - This changelog. ### Changed -- **Input validation across the whole shim surface.** Joint creation and every - double-taking joint/world/body setter now rejects non-finite values (and - mirrors Box2D's own ordering/sign/range requirements: revolute limits clamp - to ±0.95π, limit pairs must be ordered, hertz/damping/max-force non-negative, - prismatic/wheel axes non-zero, capsules need distinct centers, distance-joint - length clamps to Box2D's linear slop). NaN/Inf from script can no longer - poison the solver in Release builds. -- **Collision filter bits widened and made safe.** Category/mask values are - range-checked before conversion (negative or oversized doubles were undefined - behaviour in C) and now pass through 53 usable bits (a double's exact-integer - range) instead of silently truncating to 32. -- **Kit hardening: a missing body is never a script error.** Every `b2k…` - wrapper that targets a control's body, a joint handle, or the world now - no-ops cleanly when there is none (unregistered control, removed part, call - before `b2kSetup`). Previously ~35 handlers passed empty into an LCB - `Integer` parameter, which raises an execution error. Getters return the - same defaults a stale handle produces (`0` / `"0,0"` / `false`). - -- **Sturdier bridges and more natural chains (contraption builder).** Bridge - planks now hinge through a flex limit (a new internal `deckpin` joint) so a - loaded deck sags under weight but can never fold back on itself, and chain - spans use finer, lighter links for a smoother, more rope-like drape. Both still - save, load and delete like any other span. -- **Examples now embed the current Kit.** The demo and contraption builder - previously carried hand-copied, drifted snapshots of the Kit; they now embed - the canonical `src/box2dxt-kit.livecodescript` verbatim (regenerated by - `tools/sync-embedded-kit.py`). Their own GUI code is unchanged. One visible - consequence: both pick up the native, shape-aware `b2kExplode` (the old - velocity-based feel is still available as `b2kExplodeLegacy`). -- **ABI bumped 2 → 3** (`b2Version()` now returns `3`). The change is purely - additive — every existing `b2…` handler keeps its signature — but the prebuilt - libraries must be rebuilt (CI regenerates them on release). **Note:** collision - filter category/mask bits are exposed as 32-bit (32 layers), not Box2D's full - 64-bit, because xTalk numbers carry 32 unsigned bits cleanly. Pre-solve / custom - callbacks and Box2D's standalone math/geometry helpers remain intentionally - unwrapped (no safe mid-step FFI callback into xTalk; the math operates on raw - structs xTalk already handles). -- **Slicker tool palette.** Section headers are brighter and sit over a hairline - rule; tool buttons are left-aligned, light up on hover, and keep their accent - when selected — a more polished, professional left sidebar. -- **Contraption joints skip redrawing settled markers.** While running, the - builder's per-frame `renderJoints` now skips any joint whose tracked bodies are - all asleep — the Kit only repositions awake bodies, so an asleep joint's marker - is already in place. Build-mode dragging still redraws every marker. The win - grows with the joint count on a settled machine. -- **Less redundant work in hot paths.** The Kit's material setters - (`b2kSetBounce` / `b2kSetFriction` / `b2kSetDensity`) now resolve the control's - long id once instead of twice, matching the collision-filter setters; and the - demo's lidar scene reads each ray-hit accessor once per ray rather than twice - (48 rays a frame), trimming per-frame overhead with no behaviour change. - -- **Vehicle is now a coin-run.** Rolling segment terrain with a matching filled - silhouette, gold coins to grab as you drive, spinning wheel spokes, a stronger - motor, and a live "Coins N / total · Distance m" readout. -- **Light gamification.** The HUD shows a per-scene objective; the Pyramid tracks - a live "Scattered N / total" demolition count. The pyramid is a bit smaller - (7 rows) for a faster load. -- **Smoother rendering.** The Kit's loop now does the whole frame (body sync + - contact/frame events + app drawing) inside a single `lock screen`, and - `b2kSync` skips bodies that are asleep — so busy scenes stay smooth and settled - scenes cost almost nothing. The demo opens as a proper, centred, non-resizable - titled window. New `b2kAwakeCount()` getter. -- **Demo polish & Lidar fix.** The Lidar only scans while the cursor is inside - the play-field (rays no longer spill over — and through — the toolbar), with a - glowing emitter; the demo keeps its chrome layered above every scene control so - the UI is always clickable, adds a framed play-field panel, and shows live - body/awake counts in the HUD. -- **Rebranded from LiveCode to OpenXTalk (OXT) / xTalk.** The project is now - *Box2Dxt*, designed in and for OpenXTalk while remaining compatible with - LiveCode 9.6.3+. - - The loadable library and extension are renamed `box2dlc` → **`box2dxt`** - (`libbox2dxt.so` / `libbox2dxt.dylib` / `box2dxt.dll`); foreign-binding - strings are now `c:box2dxt>…`. - - The exported C ABI symbols keep the historical `b2lc_` prefix, so existing - compiled binaries remain binding-compatible (no ABI change; `b2Version()` - stays `2`). -- **Reorganized the repository** for a public release: source in `src/`, the - demo in `examples/`, guides in `docs/`, tests in `tests/`, binaries in - `prebuilt/`. -- Renamed `box2d-helper.livecodescript` → `src/box2dxt-kit.livecodescript` and - `box2d-demo.livecodescript` → `examples/box2dxt-demo.livecodescript`. +- **Input validation across the whole shim surface.** +- **Collision filter bits widened and made safe.** +- **Kit hardening: a missing body is never a script error.** +- **Sturdier bridges and more natural chains (contraption builder).** +- **Examples now embed the current Kit.** +- **ABI bumped 2 → 3** (`b2Version()` now returns `3`). +- **Slicker tool palette.** +- **Contraption joints skip redrawing settled markers.** +- **Less redundant work in hot paths.** +- **Vehicle is now a coin-run.** +- **Light gamification.** +- **Smoother rendering.** +- **Demo polish & Lidar fix.** +- **Rebranded from LiveCode to OpenXTalk (OXT) / xTalk.** +- **Reorganized the repository** for a public release: source in `src/`, the demo in `examples/`, guides in `docs/`, tests in `tests/`, native libraries bundled in the extension at `src/code/`. +- Renamed `box2d-helper.livecodescript` → `src/box2dxt-kit.livecodescript` and `box2d-demo.livecodescript` → `examples/box2dxt-demo.livecodescript`. - Renamed the test build option `BOX2DLC_BUILD_TESTS` → `BOX2DXT_BUILD_TESTS`. ### Fixed -- **Placed parts now show up immediately, not "only after I press Run".** Two - independent causes, the first one the engine-room bug (Kit-level, so the - demo and user code are healed too): - - **The Kit never drew a freshly attached body while the loop was stopped.** - `b2kAddBox`/`b2kAddCapsule` switch a spawned graphic's style to `polygon` - and leave its points empty until the first body sync — but the sync fast - path draws only bodies reported by Box2D **move events**, and a world that - isn't stepping emits none. So a box, capsule or thruster placed in Build - mode stayed a point-less (invisible) polygon until Run produced the first - step. The attach handlers now draw the body the moment it's created, and - the public `b2kSync` (the "loop is stopped, redraw by hand" entry point) - full-scans instead of relying on move events; the running loop keeps the - fast path. Balls, polygons and terrain were never affected — their - graphics are complete at creation — which is why the bug hit "often" - rather than always. - - **Per-frame overlays now composite on their own GPU layer (contraption - builder).** Joint markers, signal wires, the freehand sketch line, - thruster flames and shock rings are re-pointed constantly while running, - but lived in the compositor's cached static scene; they're now dynamic - layers like the moving bodies they follow. +- **Platformer: flying movers (bee/fly/fish/ladybug) no longer face backwards.** +- **Platformer: GROUND ladybugs + frogs no longer face backwards either.** +- **Platformer: spike pits now align FLUSH with the pit edges (all levels).** +- **Platformer (OXT round 6): five polish fixes for a solid state.** +- **Platformer: the L5 thorn pit fits its spikes and the final coin is off the flag.** +- **Platformer (OXT round 4): the collapsing-bridge planks now actually DROP.** +- **Platformer: the L1 swim basin has a KILL FLOOR under the water.** +- **Platformer (OXT round 3): the ROOT cause of the drifting coins and flags — cast sprites were born scroll-shifted.** +- **Platformer (OXT round 2): the L3 wall-jump shaft reads as a real beat now.** +- **Platformer: removed the L5 spider + its floating sand overhang.** +- **Platformer: the flag/checkpoint plant was raised 8px → 16px** after an OXT pass showed flags still hovering at 8px. +- **Platformer: the L4 COLLAPSING BRIDGE was crammed into a 128px lava slot between two crushers — rebuilt with room to read.** +- **Platformer: flags/checkpoints that read as "floating" on the user's engine are now PLANTED.** +- **Platformer: scenery no longer spawns on top of enemies or coins.** +- **Platformer: three coins sat *on* a wall — lifted clear (a measured-alpha audit of every coin/flag in all four levels).** +- **Kit: commands called with function syntax never worked in OXT.** +- **Kit: an invalid colour name no longer aborts a spawn.** +- **Docs: `b2AddSegment` / `b2kWall` segments are two-sided.** +- **Platformer: levels now hug their CONTENT, so the hero can no longer walk off into dead ground at either end.** +- **Placed parts now show up immediately, not "only after I press Run".** - **Popups no longer glitch over a running sim (contraption builder).** - Opening Recipes / Filter / World / Images while running left dozens of - GPU-layered parts repainting underneath the popup — parts and joint markers - bled through patchy chrome. A popup now freezes the view beneath it: the - sim pauses (only if it was running) and the stack composites the classic, - cache-free way while the popup is up; both are restored exactly as found on - close. This also means a contraption can't change while the user is reading - a menu. -- **Inspector text no longer overlaps the settings rows (contraption - builder).** The hint/preview line shares screen space with the settings - rows by design (visibility-toggled), but two paths showed both at once — - the "Simple mode hides some expert settings" notice and the live previews - for bomb/laser/fan/magnet/goal drew straight over the first rows. - `setSettingsHint` now parks itself below the last visible row (and drops - the text when a full tab leaves no room). Also: eleven tool descriptions - were longer than the description box and clipped mid-sentence — trimmed to - fit — and a long part name no longer overflows the inspector title. -- **Parts can no longer be parked overlapping the chrome (contraption - builder).** Placement, duplicate/multiply copies, build-mode drags and arrow - nudges keep the part's whole rect inside the arena (previously only its - centre was clamped, so a wide platform could hang halfway over the palette - or under the ground bar). The laser keeps its emitter-based dragging. +- **Inspector text no longer overlaps the settings rows (contraption builder).** +- **Parts can no longer be parked overlapping the chrome (contraption builder).** - **Signal wires no longer churn the renderer (contraption builder).** - `drawWires` reuses its marker graphics and re-points them instead of - deleting and recreating every wire on every build-mode redraw. -- **Geometry getters no longer leak the previous shape's values.** The - circle/capsule/segment read-back stash zeroes on a failed update (stale - handle or wrong shape type), so `b2ShapeCircleRadius()` & co. report `0` - instead of stale data. -- **An aborted `b2kAddSensor` / `b2kReshape` no longer leaks one-shot shapedef - flags.** Bail paths now call `b2ShapeDefReset`, so a failed attach can't - silently turn the next spawned shape into a sensor. -- **`b2kPruneDeadRefs` now clears every parallel table** (shape, render, - verts, radius, image-angle, static, spawned, sensor) like `b2kRemove` does, - so a control deleted behind the Kit's back leaves no stale state. -- **`b2kDefineLayer` refuses a 33rd layer** instead of overflowing xTalk's - 32-bit `bitOr` into a collide-with-nothing category. -- Stale ABI-`3` references in the demo header, getting-started, kit-reference, - and changelog preamble now read `4`. - -- **Every inspector setting is reachable again.** As the part inspector grew, the - Physics tab had more settings than the panel can show at once, so the last few - (Collision layer, Sleep, In-simulation, Sensor) were silently cut off. The - settings are re-balanced across the tabs — Physics keeps the material/dynamics - settings, a renamed **Collide** tab gathers the collision and simulation-state - settings, and the launch settings move to **Special** — so nothing is hidden. -- **Collision settings survive a reshape.** Changing an image's collision shape, - toggling the sensor flag, or resizing a part rebuilds its shape with a fresh - (default) filter; the part's collision layer and channel filter are now - re-applied afterwards instead of being quietly dropped. -- **Drawn ground now collides and is clearly visible.** A freehand *Draw* piece - is now a filled, closed ground mass (the drawn surface down to the floor) with - its chain wound so the solid side faces up — bodies rest on it instead of - falling through, and it reads as solid ground rather than a thin line. The same - chain-winding fix applies to the smooth **Hill**. -- **Drawn terrain saves where you last dragged it.** A freehand *Draw* terrain - piece is now serialized from its current points, so repositioning it before a - save no longer snaps it back to where it was first drawn when you load. -- **Tidied the smooth-hill outline locals.** Its working variables used bare - names (including `env`); they're now `t`-prefixed like the rest of the file, - removing any chance of clashing with an xTalk reserved word. +- **Geometry getters no longer leak the previous shape's values.** +- **An aborted `b2kAddSensor` / `b2kReshape` no longer leaks one-shot shapedef flags.** +- **`b2kPruneDeadRefs` now clears every parallel table** (shape, render, verts, radius, image-angle, static, spawned, sensor) like `b2kRemove` does. +- **`b2kDefineLayer` refuses a 33rd layer** instead of overflowing xTalk's 32-bit `bitOr` into a collide-with-nothing category. +- Stale ABI-`3` references in the demo header, getting-started, kit-reference, and changelog preamble now read `4`. +- **Every inspector setting is reachable again.** +- **Collision settings survive a reshape.** +- **Drawn ground now collides and is clearly visible.** +- **Drawn terrain saves where you last dragged it.** +- **Tidied the smooth-hill outline locals.** - **Kit collision-layer handlers no longer trip an OpenXTalk reserved word.** - The `pLayers` parameter of `b2kLayerBits` / `b2kSetCategory` / `b2kSetMask` is - (case-insensitively) the reserved word `players`, which stopped the script - from compiling in OpenXTalk once the examples embedded the full Kit. Renamed to - `pLayerList`; callers are unaffected (the parameter name is internal). - **Contraption motors no longer stay dead after a pressure plate + rebuild.** - Joints persist across Build↔Run, so a pressure plate that switched motors off - during a run left them off when you returned to Build and pressed Run again - (and the joint inspector could still read "Motor: on"). Entering Run now - re-applies each joint's designed motor state from its marker, so a Run always - starts from the layout you built. - **`b2kReshape` no longer leaves a body shapeless — and keeps it sensor-aware.** - Reshaping destroyed the old collision shape *before* building the new one, so a - graphic whose outline collapsed to fewer than three points (a degenerate - polygon) lost its shape entirely, leaving a body with nothing to collide with. - The swap is now atomic: the new shape is built first and the original is kept - untouched if the new one is rejected. Like the attach helpers, reshaping also - re-enables sensor events, so a reshaped body stays detectable by sensors. -- **Demo's first FPS reading is no longer bogus.** `clearState` now seeds the - frame counter and timestamp, so the HUD's opening frames-per-second value is - meaningful instead of a one-off near-zero spike right after a (re)compile. -- **Bridge & Chain spans ignore Bouncy mode.** Building a span while the - **Bouncy** build option was on made every plank/link springy, so bridges and - chains jittered and never settled. Span segments are now always built - non-bouncy, regardless of the toggle, so they hang and sag predictably. -- **Duplicating a rotated part keeps its size.** Duplicate copied the graphic's - *bounding box* (which inflates as a part rotates) as the new part's size, so a - rotated box, polygon, plank or terrain piece grew each time it was copied. It - now copies the stored design size instead. +- **Demo's first FPS reading is no longer bogus.** +- **Bridge & Chain spans ignore Bouncy mode.** +- **Duplicating a rotated part keeps its size.** - **Kit no longer leaks image-angle cache entries across teardowns.** - `b2kResetTables` now also clears the `sImgAngle` table, so repeatedly tearing - a world down and rebuilding it (e.g. switching demo scene tabs) stops - accumulating dead per-control entries. -- **No more divide-by-zero after editing the demo script.** Recompiling a stack - script wipes its script-locals, so `sScale` was empty until `startBox2DDemo` - ran — a click could hit `b2kToWorldX` first. The Kit's world↔screen helpers - now return safely before setup, and the demo starts itself on the first click. -- **Playground see-saw now tilts.** Its fulcrum was a *static body* that the - plank jammed against; it's now a decorative graphic, so the centre-hinged - plank pivots freely. -- **`b2kRemove` now deletes kit-spawned controls**, so removing a body never - leaves a dead graphic behind (the demo's bombs now vanish when they explode, - and dropped shapes are cleaned up at the cap). -- **Newton's cradle no longer explodes on launch.** The demo created each ball, - hinged it, then teleported the end ball — which violated the fresh joint. It - now spawns the end ball already lifted, with a tiny gap and moderate - restitution. +- **No more divide-by-zero after editing the demo script.** +- **Playground see-saw now tilts.** +- **`b2kRemove` now deletes kit-spawned controls**, so removing a body never leaves a dead graphic behind. +- **Newton's cradle no longer explodes on launch.** + +### Removed + +- **The micro-game example (`box2dxt-microgame.livecodescript`) was retired.** ## [0.2.0] @@ -1974,6 +205,3 @@ The native shim's ABI is tracked separately by `b2Version()` (currently `4`). and contact events. - Cross-platform CMake build (fetches and pins Box2D v3.1.0), runtime smoke test, and CI that builds and releases native libraries for Linux, macOS, and Windows. - -[Unreleased]: https://github.com/SethMorrowSoftware/Box2Dxt/compare/v0.2.0...HEAD -[0.2.0]: https://github.com/SethMorrowSoftware/Box2Dxt/releases/tag/v0.2.0 diff --git a/CLAUDE.md b/CLAUDE.md index a0e25ab..9ce941f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,14 +27,15 @@ Box2D v3.1.0 (fetched by CMake) The shim compiles into one shared library (`libbox2dxt.{so,dylib,dll}`) that **ships bundled INSIDE the extension** under `src/code/-/box2dxt.{so,dll,dylib}` (bare token, no `lib` prefix; platform-ids `x86_64-linux` / `x86-linux` / `x86_64-win32` / `x86-win32` / - `universal-mac`, architecture FIRST, Windows `-win32` for both bitnesses). `tools/package-extension.py` - lays the `prebuilt/` binaries into that tree; installing the packaged extension makes the engine + `universal-mac`, architecture FIRST, Windows `-win32` for both bitnesses). Those libraries are + **committed** (built and tested by CI, attached to each Release); `tools/package-extension.py` + refreshes that tree from a newer build. Installing the packaged extension makes the engine resolve the `c:box2dxt>` bindings via `the revLibraryMapping` automatically — **no loose library, no rename, no sudo/`/usr/lib`/`LD_LIBRARY_PATH`** (see `docs/building.md`). - **LCB binding** (`src/box2dxt.lcb`, `library org.openxtalk.box2dxt`): declares `foreign handler` bindings to the shared library and public `b2PascalCase` handlers callable from xTalk. This API speaks **metres and radians**; body type codes are `0=static, 1=kinematic, 2=dynamic`. -- **The Kit** (`src/box2dxt-kit.livecodescript`): a pure-xTalk convenience layer (312 `b2k*` +- **The Kit** (`src/box2dxt-kit.livecodescript`): a pure-xTalk convenience layer (313 `b2k*` handlers incl. the game modules: input, sprites, player controller, camera) that speaks **screen pixels and degrees**, binds bodies to LiveCode controls, and runs the animation loop. This is what the examples and most users actually call. @@ -42,11 +43,12 @@ Box2D v3.1.0 (fetched by CMake) Docs live in `docs/` (`architecture.md`, `building.md`, `getting-started.md`, `api-reference.md`, `kit-guide.md`, `kit-reference.md`, `asset-expansion-plan.md`, and `platformer-polish-plan.md` — the forward-looking plan now that feature dev is frozen; the superseded pre-implementation -`game-engine-spec.md` + `expansion-prep.md` are under `docs/archive/`). Prebuilt per-platform -binaries are in `prebuilt/` — the SOURCE `tools/package-extension.py` lays into the extension's -`src/code/-/` tree; the install is the packaged extension, not a loose drop-in (a -loose `box2dxt.{so,dll,dylib}` beside a saved stack is only the dev/fallback path, mapped at runtime -by the Kit's `b2kEnsureNativeLib`). The **Game Kit** (input/sprites/player/camera/sound modules) is +`game-engine-spec.md` + `expansion-prep.md` are under `docs/archive/`). The per-platform native +binaries are **committed inside the extension** at `src/code/-/` (built and tested by +CI, attached to each Release); `tools/package-extension.py` refreshes that tree from a newer build. +The install is the packaged extension, not a loose drop-in (a loose `box2dxt.{so,dll,dylib}` beside a +saved stack — copied from `src/code/-/` — is only the dev/fallback path, mapped at +runtime by the Kit's `b2kEnsureNativeLib`). The **Game Kit** (input/sprites/player/camera/sound modules) is implemented and user-verified; content **Waves 0-7 are built** (Wave 8, builder cross-pollination, is the only remaining roadmap item); `plan.md`'s decision log is the as-built record. Six examples: demo, contraption builder, **spike-gamekit** (the Phase-0 Game Kit harness), **platformer** (the flagship diff --git a/docs/architecture.md b/docs/architecture.md index db63bb3..e84096f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -35,7 +35,8 @@ How the pieces fit, why the shim exists, and how to extend the binding. in `src/code/-/box2dxt.{so,dll,dylib}` (bare token, no `lib` prefix; platform-ids `x86_64-linux`, `x86-linux`, `x86_64-win32`, `x86-win32`, `universal-mac` — architecture first, Windows `-win32` for both bitnesses). - `tools/package-extension.py` populates that tree from `prebuilt/`. + Those libraries are committed (built and tested by CI, and attached to each + Release); `tools/package-extension.py` refreshes the tree from a newer build. - **`src/box2dxt.lcb`** is the xTalk Builder (LCB) extension. It declares `private foreign handler` bindings to the `b2lc_*` symbols (`binds to "c:box2dxt>b2lc_…!cdecl"`) and wraps each in a friendly public @@ -125,8 +126,9 @@ Exposing more of Box2D is mechanical. To add a handler: handles like the rest). 3. **Bump `LC_ABI_VERSION`** in the shim if the exported ABI changed. 4. **Rebuild** the native library (see [building.md](building.md)), re-run - `tools/package-extension.py` to refresh the bundled `src/code/-/` - copy, then re-Package (or **Test**) the extension so the new library loads. (For + `tools/package-extension.py --linux64 build/libbox2dxt.so` (or the flag for your + platform) to refresh the bundled `src/code/-/` copy, then + re-Package (or **Test**) the extension so the new library loads. (For quick iteration, dropping the rebuilt `box2dxt.{so,dll,dylib}` beside a saved stack picks it up via `b2kEnsureNativeLib` without repackaging.) diff --git a/docs/archive/expansion-prep.md b/docs/archive/expansion-prep.md index 70a00dd..69a0978 100644 --- a/docs/archive/expansion-prep.md +++ b/docs/archive/expansion-prep.md @@ -26,7 +26,7 @@ that keep the expansion as reliable as the engine underneath it. | Wave 7 | **COMPLETE — statically verified, merged** (the desert biome: **L5 SCORCHED DUNES** — the platformer's fifth level, sand/desert). | | Wave 8 | **NOT STARTED** — builder cross-pollination (animated sprite parts + the player-as-a-part in the Contraption Builder); the only remaining roadmap item. | | Next | Wave 8: the builder cross-pollination. | -| Companions | [plan.md](../plan.md) (history/decision log) · [game-engine-spec.md](game-engine-spec.md) (module design) | +| Companions | [plan.md](../../plan.md) (history/decision log) · [game-engine-spec.md](game-engine-spec.md) (module design) | --- diff --git a/docs/archive/game-engine-spec.md b/docs/archive/game-engine-spec.md index 1366371..d2c5225 100644 --- a/docs/archive/game-engine-spec.md +++ b/docs/archive/game-engine-spec.md @@ -11,10 +11,10 @@ | | | |---|---| -| Status | **Implemented and user-verified on Win32 (2026-06-11)** — all four modules + audio shipped; as-built deltas live in [plan.md](../plan.md)'s decision log; runtime behaviour is asserted by `examples/box2dxt-selftest.livecodescript` (harness v22, ~180 checks across 37 test handlers, having grown with the content waves). macOS/Linux passes still open (risk R1). | +| Status | **Implemented and user-verified on Win32 (2026-06-11)** — all four modules + audio shipped; as-built deltas live in [plan.md](../../plan.md)'s decision log; runtime behaviour is asserted by `examples/box2dxt-selftest.livecodescript` (harness v22, ~180 checks across 37 test handlers, having grown with the content waves). macOS/Linux passes still open (risk R1). | | Scope | New `b2k…` modules in the Kit: **Input**, **Sprites**, **Player**, **Camera** | | Native impact | **None.** No C-shim or LCB changes; ABI stays at 4 | -| Companion | [plan.md](../plan.md) — the phased implementation plan | +| Companion | [plan.md](../../plan.md) — the phased implementation plan | The end goal: make Box2Dxt + xTalk a credible **2D game engine** — you should be able to build a small platformer (an animated character running and jumping @@ -505,7 +505,7 @@ not the card, when the camera is on). ## 11. Out of scope (for now) -Deliberately not in this spec, sketched as later phases in [plan.md](../plan.md): +Deliberately not in this spec, sketched as later phases in [plan.md](../../plan.md): - **Tilemaps / level editor** — `b2kChain` terrain + IDE-placed controls + `b2kCamAdopt` cover small levels; a tile pipeline is its own design. diff --git a/docs/asset-expansion-plan.md b/docs/asset-expansion-plan.md index c957b77..fa93f99 100644 --- a/docs/asset-expansion-plan.md +++ b/docs/asset-expansion-plan.md @@ -2,11 +2,11 @@ > **STATUS (frozen): Phases A–G shipped — the demo grew from 5 to SEVEN polished > levels.** Forward feature development is now **stopped** for a polish pass (see -> [`platformer-polish-plan.md`](platformer-polish-plan.md)). The remaining phases -> below (**H** Clocktown — *attempted and rolled back*, **I** alien-swim, **J** -> stretch) are **not being pursued**; they're kept as an as-built record and a -> "if we ever resume" appendix. The canonical history is -> [`../CHANGELOG.md`](../CHANGELOG.md) + [`../plan.md`](../plan.md). +> [`platformer-polish-plan.md`](platformer-polish-plan.md)). The once-planned +> Phases **H** (Clocktown — *attempted and rolled back*), **I** (alien-swim) and +> **J** (stretch) are **not being pursued** and have been dropped from this plan; +> the assets they would have used are still catalogued in §1. The canonical +> history is [`../CHANGELOG.md`](../CHANGELOG.md) + [`../plan.md`](../plan.md). The platformer demo now ships **seven polished levels** (it shipped five when this roadmap was written). This document was the roadmap that grew it — an audit of @@ -323,29 +323,14 @@ needs an OXT eye. (the plain portraits read cleaner at HUD scale). *OXT to confirm:* each skin animates cleanly across idle/walk/jump/duck/climb. -### Phase H — The Village biome → **Level 8 "CLOCKTOWN"** (L) -- **Assets:** the whole **`spritesheet.xml`** city set (house walls/roofs in 3 - palettes, awnings, chimneys, `clock`, fences, doors, shop signs, windows). -- A **town** level — rooftops, awnings to bounce/clamber, a clocktower set-piece. - A distinct art style (needs its own atlas load + a `gCityOK` capability gate). -- *Heaviest art-integration phase; its own backdrop (`background_solid_sky`/clouds).* - -### Phase I — The Alien world & a swim hero (M–L) -- **Assets:** `aliens.xml` / `alien*.xml` (alien heroes **with swim frames**), - `items_sheet` water props. -- The Kenney `character_*` hero has **no swim frames** (the Kit falls back to - idle/jump in water). An **alien hero** has `_swim1/2` — ideal for a **water-world** - level (extended swim, currents via Phase-B conveyors underwater, the new fish). - Pairs naturally with the Phase-G character select (pick an alien for the water world). - -### Phase J — Stretch / quality (varies) -- **True multi-layer parallax:** needs *transparent* overlay art (cloud/fog bands). - Source or author a transparent layer; then layer it over the Phase-A biome scene - at a different drift rate. (`items_sheet` `cloud1/2/3` may serve.) -- **Hi-DPI pass:** the `-double` 2× sheets for crisp art on high-density displays. -- **Effects:** `items_sheet` `particle*` (brick debris), `springboardUp/Down` - (animated springboard), `weightChained` (alt thwomp art). -- **`enemies_sheet` blocker/poker** as a distinct foe set if a theme wants them. +### Phases H–J — not pursued + +The Village/**Clocktown** biome (H, the `spritesheet.xml` city set — *attempted and +rolled back*), the **alien swim-world** (I, the `aliens.xml` heroes with swim +frames), and the **stretch/quality** items (J — true multi-layer parallax, the +`-double` hi-DPI sheets, `items_sheet` particles/springboards) were scoped but are +**not being pursued** now that feature development is frozen. The assets each would +have used remain catalogued in §1 if work ever resumes. --- @@ -360,10 +345,9 @@ needs an OXT eye. | 5 | Scorched Dunes | sand / desert | (built) + gem | dune, thorn pit, bestiary II | | **6** | **Cavern Depths** | **dirt / dirt** | dirt biome, conveyor, torches, block slime | conveyor descent + dark shaft | | **7** | **Stone Keep** | **stone / cave** | stone biome, multi-key/switch, spinners | lock-and-key puzzle wing | -| **8** | **Clocktown** | **city / sky** | the buildings set | rooftop run + clocktower | -| **9** | **Tidal Caves** | **stone+water** | alien swim hero, fish, currents | extended swim world | -(6–9 are the new content; ordering/naming is a starting point.) +(6–7 are the shipped new content; the once-planned 8 "Clocktown" / 9 "Tidal Caves" +were not pursued — see Phases H–J above.) --- @@ -398,22 +382,13 @@ needs an OXT eye. --- -## 6. Asset-coverage checklist (definition of done) - -Track usage per sheet; "done" = used or a one-line documented reason it isn't. - -- [ ] `backgrounds` — biome scenes used (Phase A partial); solids/clouds for new biomes (B/H). -- [ ] `tiles` terrain — dirt (B) + stone (C) biomes; corner/edge/ramp/overhang pieces across biomes. -- [ ] `tiles` items — **coin tiers + star (F, SHIPPED)**; **heart (F, SHIPPED — `hud_heart*` HUD row)**; torches + conveyor + planks (B/C); multi-colour locks/switches (C); `flag_green`. -- [ ] `tiles` HUD strip — art HUD (F), **removing the LiveCode top/bottom fields**. -- [ ] `foes` — block slime, worm ring, rest poses (B/D). -- [ ] `spooks` — snakes (E), spinners (C), squash/dead states everywhere (D), alt fish. -- [ ] `characters` — **4 skins + portraits via character select (G, SHIPPED — 1-5 to pick; `hud_player_*` avatar)**. -- [ ] `spritesheet.xml` city — Clocktown (H). -- [ ] `aliens.xml` — swim hero / Tidal Caves (I). -- [ ] `items_sheet` — particles, springboardUp/Down, weightChained (J). -- [ ] `enemies_sheet` blocker/poker — themed foe set (J, optional). -- [ ] `-double` sheets — hi-DPI pass (J, optional). +## 6. Asset-coverage (as-built) + +Phases A–G shipped; the per-phase notes above and [`../CHANGELOG.md`](../CHANGELOG.md) +are the as-built coverage record. The original aspirational per-sheet checklist has +been dropped now that feature development is frozen — much of it shipped (coin tiers, +star, the heart HUD, character select, the dirt/stone biomes, snakes, spinners, +switch puzzles), and the remainder was tied to the unpursued Phases H–J. --- diff --git a/docs/building.md b/docs/building.md index b7af50c..c95454a 100644 --- a/docs/building.md +++ b/docs/building.md @@ -1,8 +1,9 @@ # Building Box2Dxt from source You only need to build if you want a fresh native library (or are porting to a -new platform/architecture). Most users can skip this and grab a -[prebuilt library](../prebuilt/) instead. +new platform/architecture). Most users can skip this entirely — the per-platform +libraries are committed inside the extension (`src/code/-/`) and +attached to each [Release](../../releases). - [Prerequisites](#prerequisites) - [Build](#build) @@ -72,12 +73,14 @@ The build produces a single shared library: You don't deploy this file by hand. Box2Dxt ships as a LiveCode/OpenXTalk **extension with the native library bundled inside it**, under -`src/code/-/`. `tools/package-extension.py` copies the built -library (or the committed `prebuilt/` one) to its bare name in that tree: +`src/code/-/`. Those libraries are **committed** (built and tested +by CI, and attached to each Release), so a fresh clone is already a ready-to-build +extension. `tools/package-extension.py` refreshes that tree when you have a newer +build — point each flag at the matching library: ```sh -python3 tools/package-extension.py # populate src/code// -python3 tools/package-extension.py --check # validate inputs only +python3 tools/package-extension.py --check # list/validate the committed tree +python3 tools/package-extension.py --linux64 build/libbox2dxt.so # refresh one target ``` | Platform-id (`-`) | Bundled file | @@ -102,16 +105,16 @@ and OpenXTalk (incl. OXT Lite) — **no library download, no renaming, no sudo, When you build a **standalone**, the Standalone Builder bundles the matching `code/` library automatically. For quick dev without packaging, you can instead -drop a single `box2dxt.{so,dll,dylib}` (bare name) next to your **saved** stack; -the Kit's `b2kEnsureNativeLib` (called from `b2kSetup`) points the engine at it -via `the revLibraryMapping` — see [prebuilt/README.md](../prebuilt/README.md). +copy the matching `src/code/-/box2dxt.{so,dll,dylib}` (already the +bare name) next to your **saved** stack; the Kit's `b2kEnsureNativeLib` (called +from `b2kSetup`) points the engine at it via `the revLibraryMapping`, so even on +Linux no `/usr/lib`, `sudo`, or `LD_LIBRARY_PATH` is needed. ## Packaging a distribution zip -The supported install is the packaged extension: run -`python3 tools/package-extension.py` to populate `src/code/-/` -(above), then **Package** `src/box2dxt.lcb` into `box2dxt.lce` in OXT's Extension -Builder. Shipping the `.lce` (or the source tree with its `code/` folder) gives +The supported install is the packaged extension: the `src/code/-/` +libraries are already committed (above), so just **Package** `src/box2dxt.lcb` into +`box2dxt.lce` in OXT's Extension Builder. Shipping the `.lce` (or the source tree with its `code/` folder) gives the recipient a one-step install with the right native library bundled in — no loose libraries to place. @@ -130,7 +133,7 @@ python3 tools/make-release.py --stack /path/to/NewPlateformerDemo.oxtstack ``` It copies `src/box2dxt.lcb` together with its whole `src/code/` tree into -`extension/` (run `tools/package-extension.py` first to populate it), copies +`extension/` (committed in the repo; `tools/package-extension.py --check` validates it), copies `box2d_lc.c` / `box2dxt-kit.livecodescript` into `source/` for reference, copies the platformer's `Spritesheets/` art into `spritesheets/`, adds `dist/INSTALL.md`, and drops your saved stack at the root — producing: @@ -158,7 +161,7 @@ first-run prompt at `spritesheets/`. - **AVX2 / SIMD.** Box2D assumes **AVX2** on x64 by default. If your binary must run on older CPUs, configure Box2D with `-DBOX2D_DISABLE_SIMD=ON` (slower) or - an SSE2 build. The committed `prebuilt/libbox2dxt-linux-x86_64.so` binary is + an SSE2 build. The committed `src/code/x86_64-linux/box2dxt.so` binary is built with SIMD disabled so it runs anywhere. ```sh diff --git a/docs/getting-started.md b/docs/getting-started.md index 61e6629..21c4a48 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -36,9 +36,10 @@ Windows, macOS, and Linux: The engine then loads the correct library for your platform automatically — **no `/usr/lib`, no `sudo`, no `LD_LIBRARY_PATH`, no renaming.** -> The committed `prebuilt/` binaries are the *source* of those bundled libraries; -> `python3 tools/package-extension.py` lays them into `src/code/` (already done in -> the repo). Prefer building the library yourself? See [building.md](building.md). +> The per-platform libraries under `src/code/-/` are committed +> (built and tested by CI, and attached to each [Release](../../releases)), so +> this is already done in the repo. Prefer building the library yourself? See +> [building.md](building.md). ## 2. Load it while developing diff --git a/docs/kit-guide.md b/docs/kit-guide.md index 8521384..a940de8 100644 --- a/docs/kit-guide.md +++ b/docs/kit-guide.md @@ -32,8 +32,9 @@ and degrees** the whole way. 18. [Dropping to the core `b2…` API](#18-dropping-to-the-core-b2-api) 19. [A complete worked example: a little car](#19-a-complete-worked-example-a-little-car) 20. [Building a whole game (the micro-game pattern)](#20-building-a-whole-game-the-micro-game-pattern) -21. [xTalk gotchas worth knowing](#21-xtalk-gotchas-worth-knowing) -22. [Complete API index](#22-complete-api-index) +21. [Player actions: duck, drop-through, ladders, knockback, swim](#21-player-actions-duck-drop-through-ladders-knockback-swim) +22. [xTalk gotchas worth knowing](#22-xtalk-gotchas-worth-knowing) +23. [Complete API index](#23-complete-api-index) --- @@ -1328,7 +1329,7 @@ Optional arguments are in `[…]`. ### Player (the platformer controller) `b2kPlayerMake x,y,w,h [,sheet]` · `b2kPlayerAttach ctrl` · -`b2kPlayerAnims idle,run,jump [,fall] [,land] [,duck] [,climb] [,hurt] [,swim]` · +`b2kPlayerAnims idle,run,jump [,fall] [,land] [,duck] [,climb] [,hurt] [,swim] [,wall] [,dash]` · `b2kPlayerSet key,value` · `b2kPlayerGet(key)` `[f]` · `b2kPlayerOnGround()` `[f]` · `b2kPlayerState()` `[f]` · `b2kPlayerFacing()` `[f]` · `b2kPlayerJump [speed]` · `b2kPlayerControl flag` · diff --git a/docs/kit-reference.md b/docs/kit-reference.md index dad8f06..0e0f632 100644 --- a/docs/kit-reference.md +++ b/docs/kit-reference.md @@ -23,7 +23,17 @@ stack and `start using` it). It requires the `box2dxt` extension loaded - [Act on bodies](#act-on-bodies) - [Read state](#read-state) - [Joints](#joints) +- [Input (keyboard)](#input-keyboard) +- [Sprites & animation](#sprites--animation) +- [Player (the platformer controller)](#player-the-platformer-controller) +- [Camera (scrolling levels)](#camera-scrolling-levels) +- [Sound (SFX)](#sound-sfx--with-or-without-asset-files) - [Drag & events](#drag--events) +- [Sensors (trigger zones)](#sensors-trigger-zones) +- [Collision filtering (named layers)](#collision-filtering-named-layers) +- [Chains & terrain](#chains--terrain) +- [Region & ray queries](#region--ray-queries) +- [Motors & tuning](#motors--tuning) - [Tips](#tips) --- @@ -558,6 +568,7 @@ is in the other's mask (and no shared negative group forbids it). | Handler | Purpose | |---------|---------| | `b2kDefineLayer name` → bit | Define/fetch a named layer. | +| `b2kLayerBits(list)` → bits | Turn a comma/space layer-name list into a raw category/mask bitmask. | | `b2kSetCategory ctrl, layers` | Set which layer(s) a control *is* (comma/space list of names or numbers). | | `b2kSetMask ctrl, layers` | Set which layer(s) it collides *with*. The reserved `oneway` bit is **included automatically** — a custom-masked body still stands on `b2kChain` terrain (object rules shouldn't silently mean "fall through the ground"). To genuinely pass through chains, use the raw `b2SetShapeFilter`. | | `b2kSetCollisionGroup ctrl, n` | Negative = never collide with same group; positive = always. | diff --git a/docs/platformer-polish-plan.md b/docs/platformer-polish-plan.md index 722c66c..2814414 100644 --- a/docs/platformer-polish-plan.md +++ b/docs/platformer-polish-plan.md @@ -225,8 +225,8 @@ adjust on an OXT pass, level by level: - **Audit/gates green** after every edit; keep `tools/audit-platformer.py` clean (extend it for any new maker — e.g. the transition overlay needs no geometry, so no audit change). -- **Packaging.** Confirm `tools/package-extension.py` still produces a clean - installable extension and the dist stack runs from a fresh machine. +- **Packaging.** Confirm `tools/package-extension.py --check` reports a complete + `src/code/` tree and the dist stack runs from a fresh machine. --- diff --git a/examples/box2dxt-spike-gamekit.livecodescript b/examples/box2dxt-spike-gamekit.livecodescript index 116e4cd..c491025 100644 --- a/examples/box2dxt-spike-gamekit.livecodescript +++ b/examples/box2dxt-spike-gamekit.livecodescript @@ -1,7 +1,7 @@ -- ===================================================================== -- box2dxt-spike-gamekit.livecodescript · Game Kit Phase 0 runtime spike -- --- PURPOSE: answer the "(verify in OXT)" questions in docs/game-engine-spec.md +-- PURPOSE: answer the "(verify in OXT)" questions in docs/archive/game-engine-spec.md -- on real OXT hardware BEFORE any Game Kit module is built. See plan.md -- Phase 0 for the S0-S10 test table this implements. -- diff --git a/prebuilt/README.md b/prebuilt/README.md deleted file mode 100644 index c0523d4..0000000 --- a/prebuilt/README.md +++ /dev/null @@ -1,86 +0,0 @@ -# Prebuilt `box2dxt` libraries - -Per-platform native libraries built from the source in this repo, so you can run -Box2Dxt without a C toolchain. These files are the **source the packaged -extension is built from** — `tools/package-extension.py` lays them into the -extension's `src/code/-/` tree (see below), and that tree is what -ships and installs. They are *not* meant to be dropped onto a search path by hand -(that old workaround is now just a dev/fallback note at the bottom). - -| Platform | File | -|----------|------| -| Windows x64 | `box2dxt-windows-x64.dll` | -| Windows x86 (32-bit) | `box2dxt-windows-x86.dll` | -| macOS (universal: Intel + Apple Silicon) | `libbox2dxt-macos-universal.dylib` | -| Linux x86-64 | `libbox2dxt-linux-x86_64.so` | -| Linux i686 (32-bit) | `libbox2dxt-linux-i686.so` | - -These report **ABI 4** — what the current Kit and examples need. Confirm after -installing with `put b2Version()` (it should return `4`). - -## Install: the packaged extension (the supported method) - -Box2Dxt installs as a LiveCode/OpenXTalk **extension with the native library -bundled inside it**. `tools/package-extension.py` copies each file above to its -bare name under the extension's `code/` tree: - -| Platform-id (`-`) | Bundled file | -|-----------------------------------|--------------| -| `x86_64-linux` | `src/code/x86_64-linux/box2dxt.so` | -| `x86-linux` | `src/code/x86-linux/box2dxt.so` | -| `x86_64-win32` | `src/code/x86_64-win32/box2dxt.dll` | -| `x86-win32` | `src/code/x86-win32/box2dxt.dll` | -| `universal-mac` | `src/code/universal-mac/box2dxt.dylib` | - -The architecture comes **first** (`x86_64-linux`, not `linux-x86_64`); Windows -uses `-win32` for both bitnesses; the file is the bare token `box2dxt.` -(no `lib` prefix — it must equal the `c:box2dxt>` binding name). Regenerate the -tree from these binaries with: - -```sh -python3 tools/package-extension.py # populate src/code// -python3 tools/package-extension.py --check # validate inputs only -``` - -`src/box2dxt.lcb` + its `src/code/` tree is then the ready-to-build extension: -open `src/box2dxt.lcb` in OXT's **Extension Builder** and **Package** to produce -`box2dxt.lce` (the `code/` libraries are rolled in), then install it via the -Extension Manager — or hit **Test** to compile and load it in place. Installing -the extension makes the engine load the right library for the running platform -automatically. **No separate library download, no renaming, no sudo, no -`/usr/lib`, no `LD_LIBRARY_PATH`** — the same on Windows, macOS and Linux, on -LiveCode Community 9.6.3 and OpenXTalk (including OXT Lite). See -[docs/building.md](../docs/building.md#packaging-a-distribution-zip). - -## Dev / fallback: a loose library beside your stack - -For quick iteration without packaging, you can drop a single library here next to -your **saved** stack under its bare name (no `lib` prefix, no platform suffix): - -| Platform | Drop in as | -|----------|------------| -| Windows | `box2dxt.dll` | -| macOS | `box2dxt.dylib` | -| Linux | `box2dxt.so` | - -The committed Linux file is `libbox2dxt-linux-x86_64.so`, so for this path rename -it to `box2dxt.so`. The Kit's `b2kEnsureNativeLib` (called from `b2kSetup`) then -points the engine at that file via `the revLibraryMapping["box2dxt"]` — so no -`/usr/lib`, no `sudo`, no `LD_LIBRARY_PATH` is needed even on Linux, where the -dynamic loader otherwise wouldn't search the stack's folder. (Raw `b2*` callers -that don't use the Kit can set the same mapping themselves before their first -`b2` call.) Standalones don't need this at all — the Standalone Builder bundles -the correct `code/` library automatically. - -## Portability & freshness - -- The macOS file is a **universal** binary (Intel + Apple Silicon). For - older-CPU (no-AVX2) or SSE2 builds, see the SIMD notes in - [docs/building.md](../docs/building.md#platform--cpu-notes). -- These committed files are convenience artifacts and can lag behind - `src/box2d_lc.c`. When in doubt, confirm `put b2Version()` matches the ABI - your `box2dxt.lcb` expects, build from source (two `cmake` commands — see - [docs/building.md](../docs/building.md)), or grab the matching tagged - **[Release](../../releases)**, whose per-platform binaries are built and - tested on native runners by the - [`build` workflow](../.github/workflows/build.yml). diff --git a/prebuilt/box2dxt-windows-x64.dll b/prebuilt/box2dxt-windows-x64.dll deleted file mode 100644 index 32e8361..0000000 Binary files a/prebuilt/box2dxt-windows-x64.dll and /dev/null differ diff --git a/prebuilt/box2dxt-windows-x86.dll b/prebuilt/box2dxt-windows-x86.dll deleted file mode 100644 index 70449be..0000000 Binary files a/prebuilt/box2dxt-windows-x86.dll and /dev/null differ diff --git a/prebuilt/libbox2dxt-linux-i686.so b/prebuilt/libbox2dxt-linux-i686.so deleted file mode 100644 index c95cdd7..0000000 Binary files a/prebuilt/libbox2dxt-linux-i686.so and /dev/null differ diff --git a/prebuilt/libbox2dxt-linux-x86_64.so b/prebuilt/libbox2dxt-linux-x86_64.so deleted file mode 100644 index f1813a8..0000000 Binary files a/prebuilt/libbox2dxt-linux-x86_64.so and /dev/null differ diff --git a/prebuilt/libbox2dxt-macos-universal.dylib b/prebuilt/libbox2dxt-macos-universal.dylib deleted file mode 100644 index eaa5a1c..0000000 Binary files a/prebuilt/libbox2dxt-macos-universal.dylib and /dev/null differ diff --git a/tools/make-release.py b/tools/make-release.py index 4cdca0e..1680103 100755 --- a/tools/make-release.py +++ b/tools/make-release.py @@ -13,8 +13,8 @@ The native library ships INSIDE the extension (the code/ tree), so the recipient installs one extension and the engine loads the right per-platform library automatically -- no loose .so/.dll/.dylib, no rename, no sudo, no /usr/lib. The -extension/ folder comes straight from src/ (src/box2dxt.lcb + src/code/, which -tools/package-extension.py populates from prebuilt/). If you have already built +extension/ folder comes straight from src/ (src/box2dxt.lcb + src/code/, which is +committed; tools/package-extension.py refreshes it from a newer build). If you have already built a .lce in OXT's Extension Builder, pass it with --lce to drop it in too, so testers can install in one click instead of Packaging it themselves. diff --git a/tools/package-extension.py b/tools/package-extension.py index c2ba896..f2dab37 100644 --- a/tools/package-extension.py +++ b/tools/package-extension.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Populate code// beside the extension source with the native libs. +"""Refresh code// beside the extension source with the native libs. This is the intended, cross-platform way to ship native code with a LiveCode Builder extension: the per-platform shared library lives INSIDE the extension, @@ -10,19 +10,27 @@ /usr/lib, no sudo, no LD_LIBRARY_PATH, no "next to the stack". Same on Linux, macOS and Windows, on LiveCode 9.6.3 and OpenXTalk. -Because the libs live next to src/box2dxt.lcb, that folder IS the ready-to-build -extension: open src/box2dxt.lcb in OXT's Extension Builder and Package -> .lce. +Those per-platform libraries are COMMITTED under src/code/ (built and tested by +CI -- see .github/workflows/build.yml -- and attached to each GitHub Release), so +a fresh clone of this repo is already a ready-to-build extension: open +src/box2dxt.lcb in OXT's Extension Builder and Package -> .lce. - python3 tools/package-extension.py # populate src/code// - python3 tools/package-extension.py --check # validate inputs only +Use this script only to REFRESH that tree when you have newer binaries. Point each +-- flag at the matching freshly-built (build/libbox2dxt.*) or +Release-downloaded library and it copies them into src/code// under +the bare binding name. A platform with no flag is left untouched. -Libs come from prebuilt/ by default; override with --linux64 / --linux32 / ---win64 / --win32 / --mac. + python3 tools/package-extension.py --check # list the committed tree + python3 tools/package-extension.py --linux64 build/libbox2dxt.so + python3 tools/package-extension.py --win64 ~/dl/box2dxt-windows-x64.dll \ + --mac ~/dl/libbox2dxt-macos-universal.dylib PLATFORM-ID FORMAT: -, verified against the LiveCode/OXT engine + IDE source (the IDE's code-folder matcher filters on `the processor` first, then the platform). The architecture comes FIRST -- e.g. x86_64-linux, -NOT linux-x86_64. Windows uses the -win32 suffix for both bitnesses. +NOT linux-x86_64. Windows uses the -win32 suffix for both bitnesses. The file is +the bare token box2dxt.{so,dll,dylib} (no "lib" prefix; it must equal the +c:box2dxt> binding name). """ import argparse @@ -32,74 +40,87 @@ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -# The extension source; its folder is where the code/ tree is written. +# The extension source; its folder is where the code/ tree lives. LCB_SRC = "src/box2dxt.lcb" -# code// <- default committed binary in prebuilt/. -# is "-" (arch FIRST) -- this is what the engine/ -# IDE actually match. The bare name must equal the binding token "box2dxt" -# (the engine maps c:box2dxt> to box2dxt.{so,dll,dylib} -- no "lib" prefix). +# platform-id ("-", arch FIRST) -> (override-flag attr, bare name). +# The bare name must equal the binding token "box2dxt" (the engine maps +# c:box2dxt> to box2dxt.{so,dll,dylib} -- no "lib" prefix). CODE_LAYOUT = { - "x86_64-linux": ("prebuilt/libbox2dxt-linux-x86_64.so", "box2dxt.so"), - "x86-linux": ("prebuilt/libbox2dxt-linux-i686.so", "box2dxt.so"), - "x86_64-win32": ("prebuilt/box2dxt-windows-x64.dll", "box2dxt.dll"), - "x86-win32": ("prebuilt/box2dxt-windows-x86.dll", "box2dxt.dll"), - "universal-mac": ("prebuilt/libbox2dxt-macos-universal.dylib", "box2dxt.dylib"), + "x86_64-linux": ("linux64", "box2dxt.so"), + "x86-linux": ("linux32", "box2dxt.so"), + "x86_64-win32": ("win64", "box2dxt.dll"), + "x86-win32": ("win32", "box2dxt.dll"), + "universal-mac": ("mac", "box2dxt.dylib"), } def main(): - ap = argparse.ArgumentParser(description="Populate code// beside the extension source.") - ap.add_argument("--linux64", help="override the 64-bit Linux .so") - ap.add_argument("--linux32", help="override the 32-bit Linux .so") - ap.add_argument("--win64", help="override the 64-bit Windows .dll") - ap.add_argument("--win32", help="override the 32-bit Windows .dll") - ap.add_argument("--mac", help="override the macOS .dylib") - ap.add_argument("--check", action="store_true", help="validate inputs without writing") + ap = argparse.ArgumentParser(description="Refresh src/code// from per-platform native libraries.") + ap.add_argument("--linux64", help="source .so for code/x86_64-linux/") + ap.add_argument("--linux32", help="source .so for code/x86-linux/") + ap.add_argument("--win64", help="source .dll for code/x86_64-win32/") + ap.add_argument("--win32", help="source .dll for code/x86-win32/") + ap.add_argument("--mac", help="source .dylib for code/universal-mac/") + ap.add_argument("--check", action="store_true", help="list/validate the committed src/code/ tree and exit") args = ap.parse_args() - overrides = { - "x86_64-linux": args.linux64, - "x86-linux": args.linux32, - "x86_64-win32": args.win64, - "x86-win32": args.win32, - "universal-mac": args.mac, - } - code_root = os.path.join(ROOT, os.path.dirname(LCB_SRC), "code") - plan = [] # (dest_abspath, src_abspath) - problems = [] + rel = lambda p: os.path.relpath(p, ROOT) + if not os.path.isfile(os.path.join(ROOT, LCB_SRC)): - problems.append(f"missing extension source: {LCB_SRC}") + print(f"missing extension source: {LCB_SRC}", file=sys.stderr) + return 1 + + # --check: report the committed tree; non-zero if any platform slot is empty + # (an extension missing a slot won't load on that target). + if args.check: + print("Committed extension tree (src/code//):") + missing = [] + for platform_id, (_, bare) in CODE_LAYOUT.items(): + dest = os.path.join(code_root, platform_id, bare) + if os.path.isfile(dest): + print(f" {rel(dest)} ({os.path.getsize(dest)} bytes)") + else: + missing.append(f"src/code/{platform_id}/{bare}") + if missing: + print("MISSING -- the extension will not load on these targets:", file=sys.stderr) + for m in missing: + print(f" - {m}", file=sys.stderr) + return 1 + return 0 - for platform_id, (default_src, bare) in CODE_LAYOUT.items(): - src = overrides[platform_id] or default_src + # Copy each provided source into its committed slot under the bare name. + plan = [] # (dest_abspath, src_abspath) + problems = [] + for platform_id, (attr, bare) in CODE_LAYOUT.items(): + src = getattr(args, attr) + if not src: + continue src_abs = src if os.path.isabs(src) else os.path.join(ROOT, src) if os.path.isfile(src_abs): plan.append((os.path.join(code_root, platform_id, bare), src_abs)) else: - problems.append(f"missing library for code/{platform_id}/{bare}: {src}") + problems.append(f"--{attr}: not a file: {src}") + if not plan and not problems: + print("Nothing to do. Pass --linux64/--linux32/--win64/--win32/--mac to refresh a", file=sys.stderr) + print("platform, or --check to validate the committed tree. (src/code/ is committed,", file=sys.stderr) + print("so a fresh clone is already build-ready.)", file=sys.stderr) + return 1 if problems: - print("Cannot package -- inputs missing:", file=sys.stderr) + print("Cannot refresh -- inputs missing:", file=sys.stderr) for p in problems: print(f" - {p}", file=sys.stderr) return 1 - rel = lambda p: os.path.relpath(p, ROOT) - if args.check: - print("All inputs present. Would write:") - for dest, _ in plan: - print(f" {rel(dest)}") - return 0 - for dest_abs, src_abs in plan: os.makedirs(os.path.dirname(dest_abs), exist_ok=True) shutil.copy2(src_abs, dest_abs) print(f" + {rel(dest_abs)}") print() - print("src/box2dxt.lcb + src/code/ is now the ready-to-build extension.") + print("src/box2dxt.lcb + src/code/ is the ready-to-build extension.") print("Open src/box2dxt.lcb in OXT's Extension Builder and Package -> .lce.") return 0