Skip to content

Platformer showcase overhaul: Wave 5 player actions + level rebalance#42

Merged
SethMorrowSoftware merged 19 commits into
mainfrom
claude/determined-sagan-eex2l1
Jun 14, 2026
Merged

Platformer showcase overhaul: Wave 5 player actions + level rebalance#42
SethMorrowSoftware merged 19 commits into
mainfrom
claude/determined-sagan-eex2l1

Conversation

@SethMorrowSoftware

@SethMorrowSoftware SethMorrowSoftware commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Full audit + improvement pass on the platformer showcase, with the Kit as the source of truth. Driven by a fleet of Opus deep-dive agents (Kit internals, the platformer, docs/history, OXT/harness tooling) → a synthesized plan → the maximal path you chose (Full Wave 5 + Rebalance & add).

Status: complete, statically verified — needs an OXT feel pass. tools/check-livecodescript.py is green and the embedded Kit is in sync after every commit; CI (lint + 5 native builds) is green. Runtime feel for the new moves and the new level geometry can only be confirmed in OpenXTalk — the self-test harness is bumped to v13 for that pass, and each new beat has a universal double-jump fallback so no level can dead-end.

Kit Wave 5 — five new player-controller moves (the source of truth)

All opt-in through b2kPlayerSet knobs whose defaults leave the pre-Wave-5 controller byte-for-byte unchanged; each idle path is one compare per frame.

  • Double-jump (airJumps, refilled on landing) · wall-slide + wall-jump (wallSlideMax, wallJumpX/Y; a side ray runs only while airborne; new wallslide state) · dash (dashSpeed/dashMs/dashCooldownMs on the new dash action = SHIFT/X; new dash state) · duck capsule-reshape (duckScale < 1 → a feet-anchored crawl via b2kReshape with a headroom check) · moving-platform carry (platformCarry).
  • New helpers: b2kPlayerHalfH/HalfW (serving gotcha 28), b2kPlayerInLadder/InWater, b2kPlayerRespawn. b2kPlayerAnims gains wall/dash slots.
  • Self-test v13: six new hand-stepped, self-diagnosing tests (one per feature).

Platformer — enabled + rebalanced to showcase the moves

  • Turns all five moves on globally and teaches them in the on-screen help.
  • A new moving-platform pfMakeLift mechanic (kinematic, velocity-driven, write-on-change so carry ferries the rider).
  • Distinct signature finale per level (de-duplicating the four copy-pasted crusher alleys): L1 crawl tunnel · L2 lift bay over a grinder (carry showcase) · L3 wall-jump shaft · L4 lava lift + the alley trimmed (4→2 snails) as the kept gauntlet.
  • Self-counting coin totals stay correct (every added coin goes through pfMakeCoin).

Cleanup & docs

  • Deleted the micro-game example (focus is the platformer); removed it from the sync list, README, and CLAUDE.md.
  • Removed the platformer's 0-key DEBUG WARP.
  • Fixed the stale header (wrong level widths, "three-level" leftovers); documented Wave 5 across kit-reference, kit-guide §21, expansion-prep, CHANGELOG, and plan.md.

Needs an OpenXTalk eye (feel is statically unverifiable)

Wall-jump engagement against the L3 pillars; lift boarding (L2 pedestal / L4 lava bank) and that carry ferries the rider; the L1 crawl clearance (~52–61px gap vs the reshaped capsule); the new beats' double-jump reachability; and overall tuning of the first-pass move numbers (jump/dash/wall speeds).

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R

claude added 19 commits June 14, 2026 02:17
- Delete examples/box2dxt-microgame.livecodescript (focus is the
  platformer showcase) and drop it from the embedded-Kit sync list.
- Remove the platformer's '0'-key DEBUG WARP block (it was marked
  'delete before merge' - a swim-pool teleport left over from Wave 4).
- Update the README and CLAUDE.md example lists (seven -> six).

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
New player-controller actions, each OPT-IN through a knob whose default
leaves the pre-Wave-5 controller byte-for-byte unchanged, and each idle
path costs one compare per frame:

- airJumps: native double/multi air-jump (refilled on landing).
- wallJumpX/Y + wallSlideMax: a side ray detects a wall while airborne;
  hugging it caps the fall (wallslide state), JUMP launches up-and-away
  with a brief steer lock.
- dashSpeed/dashMs/dashCooldownMs: a flat horizontal burst (gravity
  parked) on the new dash action (bound to shift/x); yields to
  climb/swim; cooldown-gated. New dash state.
- duckScale (<1): DOWN reshapes the capsule to a feet-anchored crawl
  (b2kReshape) with a headroom check before standing, so you can slip
  under low gaps; duckScale 1 keeps the Wave 2 brake-duck.
- platformCarry: a grounded player inherits its platform's velocity
  (rides a moving/kinematic platform; vertical lift exempts ground-snap).

Plus the pure-win helpers that remove example boilerplate and serve
gotcha 28: b2kPlayerHalfH/HalfW (live capsule extents), b2kPlayerInLadder/
InWater (this frame's zone membership), and b2kPlayerRespawn (teleport +
zero velocity + clean state). b2kPlayerAnims gains pWall/pDash slots.

Self-test harness -> v13: six new hand-stepped tests (double-jump,
wall-slide/jump, dash+cooldown, platform-carry, duck-reshape, the
getters+respawn). Re-synced into every embedded copy.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
- kit-reference: the new b2kPlayer* getters (HalfH/HalfW/InLadder/InWater),
  b2kPlayerRespawn, the wall/dash anim slots + states, and the Wave 5
  opt-in tuning keys (airJumps, wall*, dash*, duckScale, platformCarry).
- kit-guide: a Wave 5 actions subsection (§21) with the enable recipe;
  retired the deleted micro-game's file references (the pattern stays).
- expansion-prep: Wave 5 marked BUILT (harness v13).
- CHANGELOG: Wave 5 Added entry + micro-game Removed entry.
- plan.md: the Wave 5 decision-log entry (the as-built record).
- README: the platformer's controller blurb advertises the new moves.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
Enable double-jump, wall-slide/jump, dash, crawl (duckScale) and
platform-carry on the showcase player (re-applied each level since
teardown wipes tuning), and rewrite the on-screen help to teach the
new controls. First-pass feel numbers; tune in OXT.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
Give each level a distinct signature finale that leans on a Wave 5 move,
and de-duplicate the four copy-pasted crusher alleys:

- New pfMakeLift/pfTickLift: a kinematic platform driven by VELOCITY
  (write-on-change at the patrol endpoints) so the Kit's platformCarry
  ferries its rider. Wired through globals, the pfStartGame reset, and
  the on b2kFrame fan-out.
- L1 GREEN HILLS: a CRAWL TUNNEL (duck under a low overhang for a coin)
  - showcases the duckScale reshape. Swim pool stays its signature.
- L2 THE WORKS: a LIFT BAY - ride a moving deck across a grinder hazard
  (the platform-carry showcase). Additive: no ground split, no x-shifts.
- L3 FROZEN CITADEL: a WALL-JUMP SHAFT - two floating ice pillars, a
  top coin reachable by wall-jumps (or a plain double-jump).
- L4 HAUNTED HOLLOW: a LAVA LIFT over the existing lava strip; trimmed
  the finale from four snails to two. Keeps its crusher alley.

Every new coin goes through pfMakeCoin (self-counting total stays
correct); every move-gated beat is also double-jumpable, so no level can
dead-end. Fixed the stale header widths (L1 8640/L2 5952/L3 6592/L4 6656)
and the 'three-level' leftovers; added a Wave 5 verify checklist item.
First-pass geometry/feel; the new beats need an OXT pass.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
The platformer reloaded all five Kenney atlases on every level rebuild:
b2kTeardown wiped the sheets, then the level re-decoded each PNG,
re-parsed each XML, and re-sliced every frame - the costliest thing the
Kit does, repeated per level. That is the between-levels load time.

Sheets are ASSETS, not world state (b2kClear already keeps them, and
sounds already survive teardown for the same reason). New opt-in
b2kSheetPersist makes them survive b2kTeardown too:

- Idempotent loaders: an identical b2kSheetLoad/LoadAtlas/FromImage is a
  no-op (matched by a stored source path), so a level rebuild reuses the
  cache. Sliced frames survive too, so re-warming is free.
- Cross-session: source images keep their deterministic name
  (b2ksheet_<name>) + a uB2kSrcPath tag, and frames are sliced into
  deterministically named images (b2kfr_<sheet>_<index>). A SAVED stack
  carries them, so on reopen the load adopts the in-stack image - no PNG
  decode, no re-slice - instead of importing from disk.
- b2kTeardown clears only sprite instances + dead viewports when
  persisting (b2kSpriteSweepOrphans gains pKeepAssets); b2kSheetsWipe is
  still the explicit purge.

All gated on the persist flag (default off), so the demo, builder,
slingshot, spike and the harness are byte-for-byte unchanged. The
platformer turns it on at openCard and purges on Shift+Reset. Self-test
v14: stTestSheetPersist (survive-teardown + idempotent-reload + purge);
stNewWorld resets the flag for isolation.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
Optimization + correctness follow-up to the persistent spritesheet cache,
from a focused review of the cross-session reuse paths.

Correctness (the saved-stack reuse was keyed too loosely):
- Sliced frames are now stamped with a provenance signature (uB2kSig =
  source + grid/xml args + scale) and re-checked on reuse. Before, a slice
  was identified by (sheet name, frame index) and an existence check only,
  so on reopen a sheet NAME reused for different art -- e.g. the platformer
  uses "chars" for both the atlas and the placeholder -- could adopt the
  wrong saved frames, and a frame baked at one scale could be reused at
  another (wrong size). A mismatch now re-slices instead of showing stale
  pixels.
- The loader idempotency key now includes ALL args (grid w/h/count/margin/
  spacing, and the atlas xml path), not just the file path, so a reload that
  changes the grid or xml rebuilds instead of being silently skipped.

Optimization:
- The deterministic frame name (and its key-list scan) is now computed only
  when persisting; off the persist path the slicer uses a fresh unique name
  exactly as before, so the demo/builder/slingshot/spike are truly
  unchanged (not just gated -- byte-for-byte behavior).

Self-test v15: stTestSheetPersist now also asserts that a changed grid and a
changed source each force a rebuild. All static gates pass; embedded Kit
re-synced.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
Adds an end-user distribution path: hand someone the extension, the native
libraries, and a saved stack as a single self-contained zip they install in
three steps.

- dist/INSTALL.md: a simple, self-contained install guide that uses ONLY the
  files in the zip -- place the native library (already renamed to the bare
  name the loader wants), Load box2dxt.lcb, open platformer.livecode. Covers
  Windows, macOS, and Linux, with a focused troubleshooting table.
- tools/make-release.py: assembles dist/box2dxt-platformer.zip from
  src/box2dxt.lcb + the prebuilt/ libraries (renamed to box2dxt.{dll,dylib,so}
  under lib/) + INSTALL.md + your saved --stack. --check validates inputs;
  --win/--mac/--linux override a library. Documented in docs/building.md.
- prebuilt/README.md: corrected -- the committed binaries are now ABI 4 (the
  full ~370-handler set), so the stale 'outdated / ABI 3' warning is removed
  and the dead linux-x86_64/ subdir paths are fixed.
- docs/getting-started.md: a zero-code 'just want to play?' callout pointing
  at the package, a worked 'try a few more things' snippet (spawn capsule +
  impulse + box), an Example Games entry, and the same prebuilt-path fixes.
- .gitignore: ignore the generated zip / dropped-in stack artifacts.

The zip itself is built after the stack is saved in OXT (the one input this
repo can't produce); everything else is in place and tested.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
The demo rebuilt its entire UI (top bar, scene/tool buttons, HUD, help bar)
on every openCard -- buildUI always clearUI'd and recreated ~15 controls. The
chrome is created controls that persist in a saved stack, so gate the rebuild
behind a version check, exactly like the contraption builder's chromeBuilt and
the platformer's kUIVersion: on reopen the persisted chrome is reused and only
the (ephemeral) physics scene is rebuilt.

- kDemoUIVersion + demoChromeBuilt(): present-and-current check on the saved UI.
- startBox2DDemo builds the chrome only when it's missing or stale (a version
  bump rebuilds older saved stacks once).

Example-only change (no Kit edit, no harness bump). Statically verified.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
clearAll removed only parts tracked in gParts, so any UNTRACKED part graphic
survived -- and a stack saved with parts on the stage reopens with exactly
that: the graphics persist but their bodies are gone and gParts is reset, so
they sit there dead, and a subsequent Load piles the restored contraption on
top of them (which makes Save/Load look broken).

Add sweepOrphanParts: delete every control carrying a uPartId (the stamp
tagPart puts on every PLACED part, and nothing else -- chrome is ui_*, the
arena is cb_*, and the image library is data in uImageLibrary, not controls).
Call it from clearAll (so Load/Reset/Clear/rebuildFromText always start from a
truly clean stage) and from startCB after the world is built (so a reopened
stack has no dead, untracked shapes).

The Save/Load format itself is sound -- audited the full round-trip: partLine's
12 fields align with rebuildFromText's reads, all 18 part kinds rebuild, and
joints/wires/world settings restore with id remapping. The orphan pile-up was
what made it look broken.

Example-only change. Statically verified.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
…unnel overlap

THE DUCK BUG (Wave 5). b2kPlayerDuckSet shrinks the crawl capsule and drops the
body CENTRE by tShift to keep the FEET planted (correct physics). But the bound
art follows the body centre at a CONSTANT offset (sSprBindDY), so it sank by
tShift with the centre -- the platformer's 'ducking puts me below ground'. The
buried, crouched hero then can't jump (jumping is gated off while DOWN is held
by design), so it read as 'breaks jumping entirely'.

Fix: b2kPlayerDuckSet lifts the bound art by tShift (and b2kPlayerStandUp drops
it back), so the visible character stays planted as the hitbox shrinks. The body
math was already correct; this is purely the art-follow offset. Audited the rest
of Wave 5 (dash, drop-through, wall-jump/slide, platform-carry, double-jump) --
all correct.

Self-test v16: stTestPlayerDuck -- duck shrinks the hitbox, keeps the feet
planted, and lifts/restores the art bind by exactly tShift.

L1 LAYOUT. The rest cloud's one-way collision + tiles (solid span 3136..3328)
overlapped the crawl-tunnel bar (slab + block tiles 3300..3492) by 28px -- the
cloud's right edge sat INSIDE the solid bar, by the flying ladybug. Shifted the
whole crawl-tunnel beat (bar, block tiles, the reward coin) right one tile to
3364..3556, clearing the cloud with a 36px gap (the space after was open).

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
…ile crawling)

Wave 5 interaction bug, same class as the duck (state lost across a reshape).
b2kReshape rebuilt the shape with the DEFAULT collision filter, silently
resetting collision layers. With duckScale < 1 (the platformer's 0.6), DOWN on a
one-way cloud/bridge reshapes the capsule to a crawl; DOWN+JUMP then drops
through -- b2kPlayerDropStart clears the one-way bit -- but the very next frame
the airborne player stands up, b2kReshape rebuilds the shape, the one-way bit
defaults back ON, and the body (centroid still above the line) snaps back onto
the platform it was falling through (gotcha 20). The drop-window's careful
timed restore was being bypassed by the reshape.

Fix: b2kReshape reads the old shape's filter (category/mask/group) and re-applies
it to the new shape, clamping Box2D's 2^64-1 default mask to the 32 Kit bits
(gotcha 21). A reshape now never changes collision layers -- so a drop survives a
duck/stand, and the drop-window owns the restore. Also fixes a latent bug where
resizing a custom-filtered contraption part reset its layers.

The player's material is still re-applied by b2kPlayerDuckSet/StandUp (b2kReshape
resets density/friction/restitution by design); only the FILTER needed carrying.

Self-test v17: stTestReshapeFilter -- a custom filter (cat 1, mask 5) set on a
box survives a b2kReshape to a ball.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
…s broken test

The sprite-bind compensation I added to b2kPlayerDuckSet/StandUp was based on a
wrong model: it assumed the camera is vertically LOCKED, so it lifted the bound
art by tShift to cancel the body-centre drop. But the platformer follows the
hero with b2kCamDeadzone 0,0 + b2kCamFollow gHero,1 -- the camera tracks the
body, so the lift over-compensated and floated the art (the 'floating + stuck
mid-air' report). Reverted both lines; the duck is back to its prior behaviour.

Also removed stTestPlayerDuck: it relied on writing the Kit's script-locals
(sPlayPX/PY) and reading sSprBindDY from a harness handler, which OXT does NOT
honour across the embedded-Kit boundary (its body teleported to y~13 and the
bind read back empty -- the test was measuring nothing real).

The L1 crawl-tunnel layout fix is unrelated and kept.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
…wl tunnel

Per the chosen path: DOWN now brakes the hero to a stop in place with NO hitbox
reshape, so the duck physically cannot float, sink, or stick (the reshape crawl
was the source of the float/sink trouble, and the Kit's own crawl self-test
still fails it).

The crawl-tunnel overhang only existed for the crawl move, so it is removed; its
coin now sits on the open meadow run (3460,500) and is grabbed just by running
past -- no coin lost, no dead end, totals unchanged. Updated the in-game help
text and the header docs (controls, the L1 beat list, the Wave 5 move summary).

Example-only change; gates pass.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
… ground

DASH (task 1): new player knob airDash (default 1 = current air-dash). The
platformer sets airDash 0, so DASH now fires only while grounded -- no more
mid-air dash. Gated in b2kPlayerTick's dash trigger. Self-test v18:
stTestDash now also asserts the dash does NOT fire when airborne with airDash
off (jump, press dash mid-air, confirm no dash state / no burst).

LAVA PIT (task 3): L4's ground ran unbroken over the lava strip (3136..3264),
so the lava sat hidden under the ground line and its hazard was invisible. The
ground now BREAKS there -- pf_ground2 (..3136) + pf_ground3 (3264..) with a gap,
and the purple top-tile loop skips the gap -- so the lava tiles fill an open pit
you ride the lift or double-jump over (the lava sensor at y548..640 knocks back
a fall). pfShowSlabs already lists pf_ground3, so the no-assets fallback shows
both halves.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
Task 2 (static platforms): pfTextureSlab lays a COPY of a tile frame, stretched
to a slab's exact rect, over the flat-coloured placeholder -- so collision sizes
never change and the rect gives way to sheet texture. Applied to the L3
wall-jump pillars (snow block), the L2 lift pedestals (stone), and the swim-pool
banks (stone). The texture is a pf_* image so pfWipeStage clears it each build;
done at build time (camera scroll 0) so the slab rect reads as world coords. No
assets / a missing frame: it no-ops and the flat colour stays (safe fallback).

The MOVING lift is left for a follow-up: a static texture can't track it, so it
needs a per-frame-followed texture -- I'd rather you eyeball the static ones
first (the stretch look is unverifiable here).

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
…nd the keg

Fewer GRCs: the L2 lift-bay grinder was a flat red rectangle (pf_grinder) + a
hurt sensor. Replaced both with pfMakeLava 3010,3210 -- the same lava sheet
tiles + hurt sensor the L4 pit uses -- so the bay hazard is sprite art, not a
GRC, and consistent with the rest of the game.

Crate pyramid: the powder keg's flat four-crate row is now a small PYRAMID
around the barrel -- a four-crate base flanking it, a crate bridging each outer
pair, and one capping the barrel. The crates are dynamic + fixed-rotation, so
the stack holds its shape and b2kExplode scatters it dramatically.

Example-only; gates pass.

https://claude.ai/code/session_01H3ZxaY5qDTTPSayfrDg64R
@SethMorrowSoftware SethMorrowSoftware marked this pull request as ready for review June 14, 2026 23:10
@SethMorrowSoftware SethMorrowSoftware merged commit c810e25 into main Jun 14, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants