diff --git a/CHANGELOG.md b/CHANGELOG.md index 5be270a..2686111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,14 +66,46 @@ The native shim's ABI is tracked separately by `b2Version()` (currently `4`). (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 two passes to L1 5568, L2 4032, L3 4800, - L4 4864. Existing verified beats are preserved in place; each level's - walled-door / steps finale shifts as a whole. New classic acts: a L1 - "meadow gauntlet" (a one-way cloud over two slimes + a darting fly); a - L2 second machine bay (a third chained crusher + an always-on sweeping - saw); a L3 second snow cloud + a final glacier slime; a L4 "bowling - lane" (a second snail to shell-and-kick + a slime to bowl over). More - decor and a live `awake N/M` body count on the HUD. + 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 diff --git a/examples/box2dxt-platformer.livecodescript b/examples/box2dxt-platformer.livecodescript index 88f9db2..31ebcf4 100644 --- a/examples/box2dxt-platformer.livecodescript +++ b/examples/box2dxt-platformer.livecodescript @@ -30,7 +30,7 @@ -- CURRENT level · ESC pauses · M mutes the synthesized sound -- -- THE FOUR LEVELS (every beat holds a coin; the flag advances): --- LEVEL 1 GREEN HILLS (5568px) - movement + the toys: the +-- LEVEL 1 GREEN HILLS (6336px) - movement + the toys: the -- SPRINGBOARD mid-meadow (sky coin above; a 42px hop for -- non-bouncers), the BONK ROW (headbutt ?-boxes, SMASH bricks -- into debris), the one-way BRIDGE over a spike slime, the @@ -38,7 +38,7 @@ -- SPIKE PIT, a fast MOUSE, a flying ladybug - then the -- finale: a sagging ROPE BRIDGE (hinged planks) over a chasm -- and a ladybug on the far meadow to the flag. --- LEVEL 2 THE WORKS (4032px) - the machines: drag the +-- LEVEL 2 THE WORKS (4672px) - the machines: drag the -- CRATE onto the yellow BUTTON to open the gate (coin in -- the gateway), the Wave 2 LADDER up to its bonus ledge, -- the red CHECKPOINT, the saw LEVER (STAND at it to power @@ -49,7 +49,7 @@ -- and the WALLED DOOR - a stone -- curtain to the ceiling, so the stone steps, the coins and -- the FLAG behind it are reachable ONLY through the door. --- LEVEL 3 FROZEN CITADEL (4800px) - everything at once, +-- LEVEL 3 FROZEN CITADEL (5312px) - everything at once, -- on ICE (~15% acceleration: momentum rules): a ladybug, -- spring over the first spiked pit, a bonk row, the first -- sweeping saw, a second spiked pit, a checkpoint, a thwomp @@ -57,7 +57,7 @@ -- saw under a snow cloud) with a ROLLING BOULDER that slides -- the ice head-on (leap it), the red walled door, snow -- steps, the final flag. --- LEVEL 4 HAUNTED HOLLOW (4864px) - WAVE 3's bestiary in +-- LEVEL 4 HAUNTED HOLLOW (5376px) - WAVE 3's bestiary in -- the purple biome: a MIMIC field (grass blocks that do -- not belong - they wake and lunge), the SNAIL (stomp it -- into a SHELL, kick the shell, bowl the slime down the @@ -198,7 +198,7 @@ local gPlateX, gGateUpY, gGateDownY, gDoorX, gDoorWord, gCheckX local gSlimeN, gSlimeB, gSlimeSpr, gSlimeKind, gSlimeDir local gSlimeMin, gSlimeMax, gSlimeGoneAt local gSlimeT -- Wave 3: per-row timer (mimic hop cooldowns) -local gBlockN, gBlockB, gBlockSpr, gBlockState, gBlockT +local gBlockN, gBlockB, gBlockSpr, gBlockState, gBlockT, gBlockX local gMovN, gMovSpr, gMovX, gMovY, gMovAX, gMovPX, gMovAY, gMovPY local gMovHurtW, gMovHurtH, gMovFlip -- Wave 3 (bestiary I): the piranha burrows, the ghost, the spooks sheet @@ -537,6 +537,7 @@ command pfStartGame put empty into gBlockSpr put empty into gBlockState put empty into gBlockT + put empty into gBlockX put 0 into gMovN put empty into gMovSpr put empty into gMovX @@ -958,7 +959,10 @@ end pfMakeCritter -- kinematic rise back UP to perch height. pFaced true = the Wave 3 -- CRUSHER look: it wears the block_idle/fall/rest FACES (the art that -- was this machine's fallback all along) instead of the chained weight. -command pfMakeThwomp pIdx, pX, pFaced +-- pTileFace (optional) overrides BOTH with a plain tiles sprite (e.g. +-- "block_green" for the grass-biome crusher rows) - a solid block that +-- drops, no mood-face swaps. +command pfMakeThwomp pIdx, pX, pFaced, pTileFace local tName, tBody put "pf_blockBody" & pIdx into tName create graphic tName @@ -973,7 +977,12 @@ command pfMakeThwomp pIdx, pX, pFaced put empty into gBlockChainA[pIdx] put empty into gBlockChainB[pIdx] put false into gBlockFace[pIdx] - if pFaced is true and gAssetsOK is true and b2kSheetHasFrame("foes", "block_idle") then + if pTileFace is not empty and gAssetsOK is true and b2kSheetHasFrame("tiles", pTileFace) then + -- a plain tile block (block_green for the green crusher rows): it + -- drops and rises with no mood-face swap (gBlockFace stays false) + b2kSpriteNew "tiles", pTileFace, pX, 200 + put the result into gBlockSpr[pIdx] + else if pFaced is true and gAssetsOK is true and b2kSheetHasFrame("foes", "block_idle") then b2kSpriteNew "foes", "block_idle", pX, 200 put the result into gBlockSpr[pIdx] put true into gBlockFace[pIdx] -- pfTickThwomps swaps the moods @@ -1003,6 +1012,7 @@ command pfMakeThwomp pIdx, pX, pFaced if the visible of graphic tName is true then b2kCamAdopt tBody if pIdx > gBlockN then put pIdx into gBlockN put tBody into gBlockB[pIdx] + put pX into gBlockX[pIdx] -- perch x cached: an armed block never moves put "armed" into gBlockState[pIdx] put 0 into gBlockT[pIdx] end pfMakeThwomp @@ -1489,7 +1499,7 @@ command pfMakeBoulder pX, pY, pResetX, pVX put the result into tRef if tRef is empty then exit pfMakeBoulder b2kSetDensity tRef, 3.0 - b2kSetFriction tRef, 0.35 + b2kSetFriction tRef, 0.18 -- low grip: it COASTS far on the ice b2kSetBounce tRef, 0.08 b2kNoCollide tRef, gHero b2kSetStatic tRef -- parked until the timer releases it @@ -1569,7 +1579,7 @@ command pfMakeWoodCrate pX, pY end pfMakeWoodCrate -- ===================================================================== --- LEVEL 1 - "GREEN HILLS" (5568px). Movement and the Wave 1 toys: +-- LEVEL 1 - "GREEN HILLS" (6336px). Movement and 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, the spike pit, a -- skittering MOUSE and a flying ladybug, then the showcase finale: a @@ -1579,10 +1589,10 @@ end pfMakeWoodCrate command pfL1Scene local tX put "GREEN HILLS" into gLevelName - pfBounds 5568 + pfBounds 7552 pfSlab "pf_ground1", 0, 576, 2560, 640 pfSlab "pf_ground2", 2752, 576, 3968, 640 - pfSlab "pf_ground3", 4288, 576, 5568, 640 -- the far meadow + the sky-island gauntlet + pfSlab "pf_ground3", 4288, 576, 7552, 640 -- the far meadow, the gauntlet, the homeward run + the GREEN CRUSHER alley if gAssetsOK is true and b2kSheetHasFrame("tiles", "terrain_grass_block_top") then repeat with tX = 0 to 2496 step 64 if tX >= 1376 and tX <= 1696 then @@ -1595,7 +1605,7 @@ command pfL1Scene repeat with tX = 2752 to 3904 step 64 pfTile "terrain_grass_block_top", tX, 576 end repeat - repeat with tX = 4288 to 5504 step 64 + repeat with tX = 4288 to 7488 step 64 pfTile "terrain_grass_block_top", tX, 576 end repeat pfShowSlabs false @@ -1719,7 +1729,48 @@ command pfL1Scene end if if gAssetsOK is true and b2kSheetHasFrame("tiles", "bush") then pfTile "bush", 5160, 512 - pfTile "mushroom_red", 5440, 512 + pfTile "mushroom_red", 5560, 512 + pfTile "bush", 5980, 512 + pfTile "mushroom_brown", 6200, 512 + end if + -- the HOMEWARD RUN (a fourth act): a chained thwomp + a snail (in the + -- cast) under a final one-way cloud (ghost-padded a tile past the art) + b2kSmoothGround "6296,448" & cr & "6232,448" & cr & "6040,448" & cr & "5976,448" + if gAssetsOK is true and b2kSheetHasFrame("tiles", "terrain_grass_cloud_left") then + pfTile "terrain_grass_cloud_left", 6040, 448 + pfTile "terrain_grass_cloud_middle", 6104, 448 + pfTile "terrain_grass_cloud_right", 6168, 448 + else + create graphic "pf_cloudledgeG" + set the style of it to "line" + set the points of it to "6040,448" & cr & "6232,448" + set the lineSize of it to 4 + set the foregroundColor of it to "70,190,110" + b2kCamAdopt the long id of graphic "pf_cloudledgeG" + end if + -- the GREEN CRUSHER ALLEY (the showcase gauntlet): a row of green-block + -- thwomps (in the cast) you run beneath, then a two-cloud HOP to the + -- flag. Both clouds ghost-padded a tile past the art each side. + b2kSmoothGround "7312,448" & cr & "7248,448" & cr & "7056,448" & cr & "6992,448" + b2kSmoothGround "7532,448" & cr & "7468,448" & cr & "7276,448" & cr & "7212,448" + if gAssetsOK is true and b2kSheetHasFrame("tiles", "terrain_grass_cloud_left") then + pfTile "terrain_grass_cloud_left", 7056, 448 + pfTile "terrain_grass_cloud_middle", 7120, 448 + pfTile "terrain_grass_cloud_right", 7184, 448 + pfTile "terrain_grass_cloud_left", 7276, 448 + pfTile "terrain_grass_cloud_middle", 7340, 448 + pfTile "terrain_grass_cloud_right", 7404, 448 + else + create graphic "pf_cloudledgeI" + set the style of it to "line" + set the points of it to "7056,448" & cr & "7468,448" + set the lineSize of it to 4 + set the foregroundColor of it to "70,190,110" + b2kCamAdopt the long id of graphic "pf_cloudledgeI" + end if + if gAssetsOK is true and b2kSheetHasFrame("tiles", "bush") then + pfTile "bush", 6320, 512 + pfTile "mushroom_red", 7488, 512 end if if gToysOK is true then -- the spring mid-meadow: bouncing happens in open air, well away @@ -1750,7 +1801,15 @@ command pfL1Cast pfMakeCoin 4832, 392 -- up on the gauntlet's one-way cloud pfMakeCoin 5040, 500 -- the first gauntlet slime's beat pfMakeCoin 5232, 500 -- under the darting fly - pfMakeCoin 5360, 500 -- the last slime's beat, before the flag + pfMakeCoin 5360, 500 -- the last gauntlet slime's beat + pfMakeCoin 5700, 500 -- beneath the chained thwomp: grab it as you dash under + pfMakeCoin 5900, 500 -- the homeward snail's beat (shell it, kick it) + pfMakeCoin 6136, 392 -- up on the homeward-run cloud + pfMakeCoin 6530, 500 -- threading the GREEN CRUSHER alley: grab between drops + pfMakeCoin 6710, 500 -- ...and between the next pair of crushers + pfMakeCoin 6890, 500 -- ...and the last gap + pfMakeCoin 7120, 392 -- up on the first hop cloud + pfMakeCoin 7340, 392 -- up on the second hop cloud, by the flag if gToysOK is true then pfMakeCoin 240, 230 -- the sky coin (spring up) -- the bee tours the mound (harmless); the fly guards the cloud coin; -- a flying LADYBUG patrols the second-act meadow (new aerial variety) @@ -1798,15 +1857,27 @@ command pfL1Cast pfAddMover tRef, 5232, 452, 80, 720, 30, 480, 34, 40, true end if end if + -- the HOMEWARD RUN: the classic chained-weight THWOMP returns (dash + -- under it for the coin), and a SNAIL to shell-and-kick + pfMakeThwomp 1, 5700 + pfMakeSnail 9, 5900, 5840, 5960 + -- the GREEN CRUSHER ALLEY (the grass biome's marquee gauntlet): a row + -- of four green-block thwomps you time your run beneath - each drops as + -- you near it, so keep moving and snatch the coins in the gaps. They + -- rest STATIC then rise + re-arm, never blocking the alley for good. + pfMakeThwomp 2, 6440, false, "block_green" + pfMakeThwomp 3, 6620, false, "block_green" + pfMakeThwomp 4, 6800, false, "block_green" + pfMakeThwomp 5, 6980, false, "block_green" -- a CHECKPOINT just before the rope bridge: a slip into the chasm is -- then a quick retry at the brink, never a replay of the whole level pfMakeCheckpoint 3856 if gToysOK is true then pfMakeDebrisPool -- this level has bricks - pfMakeGoal 5500, 544 + pfMakeGoal 7540, 544 end pfL1Cast -- ===================================================================== --- LEVEL 2 - "THE WORKS" (4032px). The machines: crate + button gate, +-- LEVEL 2 - "THE WORKS" (4672px). The machines: crate + button gate, -- the Wave 2 ladder to its bonus ledge, the stand-to-flip saw lever, -- both saws, a crawling WORM, chained thwomps, a breather cloud with its -- bee, a SECOND machine bay (another chained crusher + an always-on @@ -1817,26 +1888,26 @@ end pfL1Cast command pfL2Scene local tX, tRef put "THE WORKS" into gLevelName - pfBounds 4032 - pfSlab "pf_ground1", 0, 576, 4032, 640 - pfSlab "pf_plat1", 3648, 512, 3840, 576 - pfSlab "pf_plat2", 3840, 448, 3968, 576 + pfBounds 5952 + pfSlab "pf_ground1", 0, 576, 5952, 640 + pfSlab "pf_plat1", 5568, 512, 5760, 576 + pfSlab "pf_plat2", 5760, 448, 5888, 576 if gAssetsOK is true and b2kSheetHasFrame("tiles", "terrain_grass_block_top") then - repeat with tX = 0 to 3968 step 64 - if tX >= 3648 then + repeat with tX = 0 to 5888 step 64 + if tX >= 5568 then -- the finale plays in STONE past the walled door pfTile "terrain_stone_block_top", tX, 576 else pfTile "terrain_grass_block_top", tX, 576 end if end repeat - pfTile "terrain_stone_block_top_left", 3648, 512 - pfTile "terrain_stone_block_top", 3712, 512 - pfTile "terrain_stone_block_top_right", 3776, 512 - pfTile "terrain_stone_block_top", 3840, 448 - pfTile "terrain_stone_block_top", 3904, 448 - pfTile "terrain_stone_block_center", 3840, 512 - pfTile "terrain_stone_block_center", 3904, 512 + pfTile "terrain_stone_block_top_left", 5568, 512 + pfTile "terrain_stone_block_top", 5632, 512 + pfTile "terrain_stone_block_top_right", 5696, 512 + pfTile "terrain_stone_block_top", 5760, 448 + pfTile "terrain_stone_block_top", 5824, 448 + pfTile "terrain_stone_block_center", 5760, 512 + pfTile "terrain_stone_block_center", 5824, 512 pfShowSlabs false else pfShowSlabs true @@ -1911,9 +1982,51 @@ command pfL2Scene pfTile "sign", 2400, 512 pfTile "bush", 3160, 512 end if + -- a THIRD machine run: a snail + another chained crusher (in the cast) + -- under a one-way cloud (ghost-padded a tile past the art each side) + b2kSmoothGround "3956,448" & cr & "3892,448" & cr & "3700,448" & cr & "3636,448" + if gAssetsOK is true and b2kSheetHasFrame("tiles", "terrain_grass_cloud_left") then + pfTile "terrain_grass_cloud_left", 3700, 448 + pfTile "terrain_grass_cloud_middle", 3764, 448 + pfTile "terrain_grass_cloud_right", 3828, 448 + else + create graphic "pf_cloudledgeH" + set the style of it to "line" + set the points of it to "3700,448" & cr & "3892,448" + set the lineSize of it to 4 + set the foregroundColor of it to "70,190,110" + b2kCamAdopt the long id of graphic "pf_cloudledgeH" + end if + if gAssetsOK is true and b2kSheetHasFrame("tiles", "bush") then + pfTile "bush", 3400, 512 + end if + -- the GREEN CRUSHER ALLEY (the works' marquee gauntlet): a row of green- + -- block thwomps (in the cast) you run beneath, then a two-cloud HOP to the + -- walled door. Both clouds ghost-padded a tile past the art each side. + b2kSmoothGround "5096,448" & cr & "5032,448" & cr & "4840,448" & cr & "4776,448" + b2kSmoothGround "5316,448" & cr & "5252,448" & cr & "5060,448" & cr & "4996,448" + if gAssetsOK is true and b2kSheetHasFrame("tiles", "terrain_grass_cloud_left") then + pfTile "terrain_grass_cloud_left", 4840, 448 + pfTile "terrain_grass_cloud_middle", 4904, 448 + pfTile "terrain_grass_cloud_right", 4968, 448 + pfTile "terrain_grass_cloud_left", 5060, 448 + pfTile "terrain_grass_cloud_middle", 5124, 448 + pfTile "terrain_grass_cloud_right", 5188, 448 + else + create graphic "pf_cloudledgeJ" + set the style of it to "line" + set the points of it to "4840,448" & cr & "5252,448" + set the lineSize of it to 4 + set the foregroundColor of it to "70,190,110" + b2kCamAdopt the long id of graphic "pf_cloudledgeJ" + end if + if gAssetsOK is true and b2kSheetHasFrame("tiles", "bush") then + pfTile "sign", 3980, 512 -- a way-marker into the crusher alley + pfTile "bush", 5400, 512 + end if if gToysOK is true then pfMakeLever 1120 -- stand here to power the FIRST sweep saw down - pfMakeKeyDoor 1740, 500, 3424, "yellow" + pfMakeKeyDoor 1740, 500, 5344, "yellow" end if end pfL2Scene @@ -1926,9 +2039,18 @@ command pfL2Cast pfMakeCoin 2210, 392 -- on the breather cloud (the bee patrols it) pfMakeCoin 2680, 500 -- past the second bay's chained crusher pfMakeCoin 2880, 448 -- above the second sweeping saw: time the hop - pfMakeCoin 3780, 448 -- over the first stone step - pfMakeCoin 3854, 392 -- over the top step, by the flag - if gToysOK is true then pfMakeCoin 3472, 510 -- inside the door passage + pfMakeCoin 3300, 500 -- the third bay's snail (shell it, kick it) + pfMakeCoin 3600, 500 -- beneath the third chained crusher: grab it as you dash under + pfMakeCoin 3796, 392 -- up on the third bay's one-way cloud + pfMakeCoin 3950, 500 -- the fourth bay's snail (shell it, kick it) + pfMakeCoin 4230, 500 -- threading the GREEN CRUSHER alley: grab between drops + pfMakeCoin 4410, 500 -- ...and between the next pair of crushers + pfMakeCoin 4590, 500 -- ...and the last gap + pfMakeCoin 4904, 392 -- up on the first hop cloud + pfMakeCoin 5124, 392 -- up on the second hop cloud, before the door + pfMakeCoin 5700, 448 -- over the first stone step + pfMakeCoin 5774, 392 -- over the top step, by the flag + if gToysOK is true then pfMakeCoin 5392, 510 -- inside the door passage -- the bee patrols the breather cloud if gAssetsOK is true and b2kSheetHasFrame("foes", "bee_a") then b2kSpriteNew "foes", "bee_a", 2210, 310 @@ -1971,13 +2093,16 @@ command pfL2Cast put tRef into gSawSpr end if end if - pfMakeSlime 1, "normal", 3734, 3684, 3820, 512 + pfMakeSlime 1, "normal", 4374, 4324, 4460, 512 -- a slow WORM crawls the run before the breather cloud (new variety; -- native foes art, stomps like any walker) pfMakeCritter 2, "normal", 1990, 1920, 2060, 576, 38, "worm_normal_move_a", "wormwalk", empty, 22, 26, "210,120,170", -2 + -- the THIRD bay's snail (snails liberally now) and a third chained crusher + pfMakeSnail 3, 3300, 3240, 3360 pfMakeThwomp 1, 1640 pfMakeThwomp 2, 1840 pfMakeThwomp 3, 2560 -- the second bay's chained crusher + pfMakeThwomp 4, 3600 -- the third bay's chained crusher -- a SECOND sweeping saw, ALWAYS on (the lever powers only the first): -- a bodiless mover hurting by proximity, sweeping the second bay floor if gAssetsOK is true and b2kSheetHasFrame("foes", "saw_a") then @@ -1988,12 +2113,22 @@ command pfL2Cast pfAddMover tRef, 2880, 548, 92, 1050, 0, 1, 38, 42, false end if end if + -- the GREEN CRUSHER ALLEY (the works' marquee gauntlet): a row of four + -- green-block thwomps you time your run beneath - each drops as you near + -- it, so keep moving and snatch the coins in the gaps. They rest STATIC + -- then rise + re-arm, never blocking the alley for good. A fourth-bay + -- SNAIL guards the approach (snails liberally now). + pfMakeSnail 4, 3950, 3910, 3990 + pfMakeThwomp 5, 4140, false, "block_green" + pfMakeThwomp 6, 4320, false, "block_green" + pfMakeThwomp 7, 4500, false, "block_green" + pfMakeThwomp 8, 4680, false, "block_green" pfMakeCheckpoint 1000 - pfMakeGoal 3924, 416 + pfMakeGoal 5844, 416 end pfL2Cast -- ===================================================================== --- LEVEL 3 - "FROZEN CITADEL" (4800px). Everything at once, on ICE: +-- LEVEL 3 - "FROZEN CITADEL" (5312px). Everything at once, on ICE: -- snow ground with quarter-strength player acceleration (momentum!), a -- ladybug, the spring arcs over the first spiked pit, a bonk row, the -- sweeping saw, a second spiked pit, a thwomp guarding the RED key, @@ -2003,12 +2138,12 @@ end pfL2Cast command pfL3Scene local tX put "FROZEN CITADEL (icy!)" into gLevelName - pfBounds 4800 + pfBounds 6592 pfSlab "pf_ground1", 0, 576, 768, 640 pfSlab "pf_ground2", 960, 576, 1792, 640 - pfSlab "pf_ground3", 1984, 576, 4800, 640 - pfSlab "pf_plat1", 4128, 512, 4320, 576 - pfSlab "pf_plat2", 4320, 448, 4448, 576 + pfSlab "pf_ground3", 1984, 576, 6592, 640 + pfSlab "pf_plat1", 5920, 512, 6112, 576 + pfSlab "pf_plat2", 6112, 448, 6240, 576 if gAssetsOK is true and b2kSheetHasFrame("tiles", "terrain_snow_block_top") then repeat with tX = 0 to 704 step 64 pfTile "terrain_snow_block_top", tX, 576 @@ -2016,16 +2151,16 @@ command pfL3Scene repeat with tX = 960 to 1728 step 64 pfTile "terrain_snow_block_top", tX, 576 end repeat - repeat with tX = 1984 to 4736 step 64 + repeat with tX = 1984 to 6528 step 64 pfTile "terrain_snow_block_top", tX, 576 end repeat - pfTile "terrain_snow_block_top_left", 4128, 512 - pfTile "terrain_snow_block_top", 4192, 512 - pfTile "terrain_snow_block_top_right", 4256, 512 - pfTile "terrain_snow_block_top", 4320, 448 - pfTile "terrain_snow_block_top", 4384, 448 - pfTile "terrain_snow_block_center", 4320, 512 - pfTile "terrain_snow_block_center", 4384, 512 + pfTile "terrain_snow_block_top_left", 5920, 512 + pfTile "terrain_snow_block_top", 5984, 512 + pfTile "terrain_snow_block_top_right", 6048, 512 + pfTile "terrain_snow_block_top", 6112, 448 + pfTile "terrain_snow_block_top", 6176, 448 + pfTile "terrain_snow_block_center", 6112, 512 + pfTile "terrain_snow_block_center", 6176, 512 pfShowSlabs false else pfShowSlabs true @@ -2065,13 +2200,36 @@ command pfL3Scene set the foregroundColor of it to "200,220,240" b2kCamAdopt the long id of graphic "pf_cloudledgeF" end if + -- the BLUE CRUSHER ALLEY (the citadel's marquee gauntlet): a row of ice- + -- block thwomps (in the cast) you run beneath, then a two-cloud HOP to the + -- red door. Both clouds ghost-padded a tile past the art each side. + b2kSmoothGround "5516,448" & cr & "5452,448" & cr & "5260,448" & cr & "5196,448" + b2kSmoothGround "5736,448" & cr & "5672,448" & cr & "5480,448" & cr & "5416,448" + if gAssetsOK is true and b2kSheetHasFrame("tiles", "terrain_snow_cloud_left") then + pfTile "terrain_snow_cloud_left", 5260, 448 + pfTile "terrain_snow_cloud_middle", 5324, 448 + pfTile "terrain_snow_cloud_right", 5388, 448 + pfTile "terrain_snow_cloud_left", 5480, 448 + pfTile "terrain_snow_cloud_middle", 5544, 448 + pfTile "terrain_snow_cloud_right", 5608, 448 + else + create graphic "pf_cloudledgeK" + set the style of it to "line" + set the points of it to "5260,448" & cr & "5672,448" + set the lineSize of it to 4 + set the foregroundColor of it to "200,220,240" + b2kCamAdopt the long id of graphic "pf_cloudledgeK" + end if + if gAssetsOK is true and b2kSheetHasFrame("tiles", "sign") then + pfTile "sign", 4440, 512 -- a way-marker into the crusher alley + end if if gToysOK is true then pfMakeSpring 700 -- arcs you clean over the first pit pfMakeBonk "brick", 1232 pfMakeBonk "box", 1296 pfMakeBonk "brick", 1360 pfMakeBonk "box", 1424 - pfMakeKeyDoor 2268, 500, 3968, "red" + pfMakeKeyDoor 2268, 500, 5760, "red" end if end pfL3Scene @@ -2092,11 +2250,19 @@ command pfL3Cast pfMakeCoin 3250, 500 -- in the boulder run: snatch it between slides pfMakeCoin 3640, 500 -- the final glacier slime's beat pfMakeCoin 3796, 392 -- up on the second snow cloud - pfMakeCoin 4258, 448 -- over the first snow step - pfMakeCoin 4353, 392 -- over the top step, by the flag + pfMakeCoin 4040, 500 -- the glacier snail's beat (shell it, kick it) + pfMakeCoin 4280, 500 -- beneath the chained thwomp: grab it as you dash under + pfMakeCoin 4420, 500 -- the citadel snail's beat (shell it, kick it) + pfMakeCoin 4650, 500 -- threading the BLUE CRUSHER alley: grab between drops + pfMakeCoin 4830, 500 -- ...and between the next pair of crushers + pfMakeCoin 5010, 500 -- ...and the last gap + pfMakeCoin 5324, 392 -- up on the first hop cloud + pfMakeCoin 5544, 392 -- up on the second hop cloud, before the door + pfMakeCoin 6050, 448 -- over the first snow step + pfMakeCoin 6145, 392 -- over the top step, by the flag if gToysOK is true then pfMakeCoin 864, 300 -- over the first pit: ride the spring's arc - pfMakeCoin 4016, 510 -- inside the red door's passage + pfMakeCoin 5808, 510 -- inside the red door's passage end if if gAssetsOK is true and b2kSheetHasFrame("foes", "saw_a") then b2kSpriteNew "foes", "saw_a", 1640, 548 @@ -2121,21 +2287,36 @@ command pfL3Cast -- a LADYBUG ambles the early ice (new variety; native foes art) pfMakeCritter 2, "normal", 1100, 1010, 1180, 576, 64, "ladybug_walk_a", "ladywalk", empty, 22, 30, "200,70,70", -4 pfMakeThwomp 1, 2208 -- it guards the red key - -- the glacier's ROLLING BOULDER: it slides the icy flat HEAD-ON at the - -- climbing hero, who must leap it (jumping lifts him clear of the poll). - -- It passes through him (b2kNoCollide) and recycles forever, never - -- reaching the kill floor. Built in the CAST so the hero exists. - pfMakeBoulder 3450, 549, 3050, -260 + -- the glacier's ROLLING ICE BOULDER: it slides the icy flat HEAD-ON at + -- the climbing hero, who must leap it (jumping lifts him clear of the + -- poll). It passes through him (b2kNoCollide) and SLIDES ALL THE WAY + -- off-screen left (reset line 2300, well past the run and below the + -- camera's left edge) before a fresh one comes from the source - no + -- teleport-in-place. Built in the CAST so the hero exists. + pfMakeBoulder 3450, 549, 2300, -320 -- a final glacier slime under the second snow cloud, clear to the right -- of the boulder's home so the parked stone never jostles it pfMakeSlime 3, "normal", 3640, 3580, 3700, 576 + -- the citadel's final stretch: a SNAIL and the classic chained THWOMP + -- back, guarding the run to the red door + pfMakeSnail 4, 4040, 3980, 4100 + pfMakeThwomp 2, 4280 + -- the BLUE CRUSHER ALLEY (the citadel's marquee gauntlet): a row of four + -- ice-block thwomps you time your run beneath - each drops as you near it, + -- so keep moving and snatch the coins in the gaps. A glacier SNAIL guards + -- the approach (snails liberally). + pfMakeSnail 5, 4420, 4390, 4450 + pfMakeThwomp 3, 4560, false, "block_blue" + pfMakeThwomp 4, 4740, false, "block_blue" + pfMakeThwomp 5, 4920, false, "block_blue" + pfMakeThwomp 6, 5100, false, "block_blue" pfMakeCheckpoint 2048 if gToysOK is true then pfMakeDebrisPool -- this level has bricks - pfMakeGoal 4408, 416 + pfMakeGoal 6200, 416 end pfL3Cast -- ===================================================================== --- LEVEL 4 - "HAUNTED HOLLOW" (4864px). Wave 3's bestiary in the purple +-- LEVEL 4 - "HAUNTED HOLLOW" (5376px). Wave 3's bestiary in the purple -- biome: a MIMIC field (grass blocks that do not belong), the SNAIL -- whose kicked shell bowls a slime over, the BAT overhang, a pit, a long -- FOUR-burrow PIRANHA row, the GHOST stalking the back half, a pair of @@ -2147,27 +2328,27 @@ end pfL3Cast command pfL4Scene local tX, tH, tHi put "HAUNTED HOLLOW (spooky!)" into gLevelName - pfBounds 4864 + pfBounds 6656 pfSlab "pf_ground1", 0, 576, 1856, 640 - pfSlab "pf_ground2", 2048, 576, 4864, 640 - pfSlab "pf_plat1", 4448, 512, 4640, 576 - pfSlab "pf_plat2", 4640, 448, 4768, 576 + pfSlab "pf_ground2", 2048, 576, 6656, 640 + pfSlab "pf_plat1", 6240, 512, 6432, 576 + pfSlab "pf_plat2", 6432, 448, 6560, 576 -- the bat overhang: a stone bar the bats roost under pfSlab "pf_batbar", 1472, 256, 1856, 320 if gAssetsOK is true and b2kSheetHasFrame("tiles", "terrain_purple_block_top") then repeat with tX = 0 to 1792 step 64 pfTile "terrain_purple_block_top", tX, 576 end repeat - repeat with tX = 2048 to 4800 step 64 + repeat with tX = 2048 to 6592 step 64 pfTile "terrain_purple_block_top", tX, 576 end repeat - pfTile "terrain_purple_block_top_left", 4448, 512 - pfTile "terrain_purple_block_top", 4512, 512 - pfTile "terrain_purple_block_top_right", 4576, 512 - pfTile "terrain_purple_block_top", 4640, 448 - pfTile "terrain_purple_block_top", 4704, 448 - pfTile "terrain_purple_block_center", 4640, 512 - pfTile "terrain_purple_block_center", 4704, 512 + pfTile "terrain_purple_block_top_left", 6240, 512 + pfTile "terrain_purple_block_top", 6304, 512 + pfTile "terrain_purple_block_top_right", 6368, 512 + pfTile "terrain_purple_block_top", 6432, 448 + pfTile "terrain_purple_block_top", 6496, 448 + pfTile "terrain_purple_block_center", 6432, 512 + pfTile "terrain_purple_block_center", 6496, 512 pfTile "terrain_purple_horizontal_left", 1472, 256 pfTile "terrain_purple_horizontal_middle", 1536, 256 pfTile "terrain_purple_horizontal_middle", 1600, 256 @@ -2211,6 +2392,32 @@ command pfL4Scene pfMakeWoodCrate 3688, 554 pfMakeWoodCrate 3778, 554 pfMakeWoodCrate 3824, 554 + -- the RED CRUSHER ALLEY (the hollow's marquee gauntlet): a row of danger- + -- block thwomps (in the cast) you run beneath, then a two-cloud HOP to the + -- purple steps. Both clouds ghost-padded a tile past the art each side. + b2kSmoothGround "5956,448" & cr & "5892,448" & cr & "5700,448" & cr & "5636,448" + b2kSmoothGround "6176,448" & cr & "6112,448" & cr & "5920,448" & cr & "5856,448" + if gAssetsOK is true and b2kSheetHasFrame("tiles", "terrain_purple_cloud_left") then + pfTile "terrain_purple_cloud_left", 5700, 448 + pfTile "terrain_purple_cloud_middle", 5764, 448 + pfTile "terrain_purple_cloud_right", 5828, 448 + pfTile "terrain_purple_cloud_left", 5920, 448 + pfTile "terrain_purple_cloud_middle", 5984, 448 + pfTile "terrain_purple_cloud_right", 6048, 448 + else + create graphic "pf_cloudledgeL" + set the style of it to "line" + set the points of it to "5700,448" & cr & "6112,448" + set the lineSize of it to 4 + set the foregroundColor of it to "150,110,190" + b2kCamAdopt the long id of graphic "pf_cloudledgeL" + end if + if gAssetsOK is true and b2kSheetHasFrame("tiles", "sign") then + pfTile "sign", 4900, 512 -- a way-marker into the crusher alley + end if + if gAssetsOK is true and b2kSheetHasFrame("tiles", "grass_purple") then + pfTile "grass_purple", 5640, 512 + end if end pfL4Scene command pfL4Cast @@ -2228,8 +2435,16 @@ command pfL4Cast pfMakeCoin 3892, 500 -- past the powder keg (the lure: mind the blast) pfMakeCoin 4000, 500 -- the second snail's beat (shell it, kick it!) pfMakeCoin 4220, 500 -- the slime its bowled shell can flatten - pfMakeCoin 4544, 448 -- over the first purple step - pfMakeCoin 4672, 392 -- over the top step, by the flag + pfMakeCoin 4440, 500 -- a THIRD snail's beat + pfMakeCoin 4600, 500 -- beneath the chained crusher: grab it as you dash under + pfMakeCoin 4780, 500 -- a FOURTH snail's beat + pfMakeCoin 5090, 500 -- threading the RED CRUSHER alley: grab between drops + pfMakeCoin 5270, 500 -- ...and between the next pair of crushers + pfMakeCoin 5450, 500 -- ...and the last gap + pfMakeCoin 5764, 392 -- up on the first hop cloud + pfMakeCoin 5984, 392 -- up on the second hop cloud, by the steps + pfMakeCoin 6336, 448 -- over the first purple step + pfMakeCoin 6464, 392 -- over the top step, by the flag pfMakeSnail 1, 980, 880, 1080 pfMakeSlime 2, "normal", 1255, 1180, 1330, 576 -- split patrol bands: two flying BODIES sharing one band would @@ -2254,8 +2469,21 @@ command pfL4Cast -- and a slime its sliding shell flattens - the haunted level escalates pfMakeSnail 8, 4000, 3940, 4060 pfMakeSlime 9, "normal", 4220, 4160, 4280, 576 + -- the haunted FINALE: snails used liberally (two more) around the + -- classic chained-weight THWOMP coming back among the faced crushers + pfMakeSnail 10, 4440, 4380, 4500 + pfMakeThwomp 3, 4600 + pfMakeSnail 11, 4780, 4720, 4840 + -- the RED CRUSHER ALLEY (the hollow's marquee gauntlet): a row of four + -- danger-block thwomps you time your run beneath - each drops as you near + -- it, so keep moving and snatch the coins in the gaps. The haunted level's + -- last test before the flag. + pfMakeThwomp 4, 5000, false, "block_red" + pfMakeThwomp 5, 5180, false, "block_red" + pfMakeThwomp 6, 5360, false, "block_red" + pfMakeThwomp 7, 5540, false, "block_red" pfMakeCheckpoint 2160 - pfMakeGoal 4732, 416 + pfMakeGoal 6524, 416 end pfL4Cast @@ -2317,7 +2545,7 @@ end pfWipeStage -- The game tick -- ===================================================================== on b2kFrame - local tHud, tPos + local tHud, tPos, tHS if gHero is empty then exit b2kFrame -- THE HERO SNAPSHOT, once per frame: position and controller state -- feed the kill plane, edge failsafe, sound cues and every pf tick @@ -2390,10 +2618,11 @@ on b2kFrame else put (the milliseconds - gRunStart) div 1000 into tSecs end if - put "L" & gLevel & " " & gCoins & "/" & gCoinsTotal & " " & format("%d:%02d", tSecs div 60, tSecs mod 60) & " falls " & gFalls & " hits " & gOuches & " lands " & gLands & " | " & b2kPlayerState() & "/" & b2kSpriteAnim(gHeroSpr) & " " & round(b2kFrameMS() * 10) / 10 & " ms awake " & b2kAwakeBodyCount() & "/" & b2kBodyCount() into tHud + put "L" & gLevel & " " & gCoins & "/" & gCoinsTotal & " " & format("%d:%02d", tSecs div 60, tSecs mod 60) & " falls " & gFalls & " hits " & gOuches & " lands " & gLands & " | " & gHeroState & "/" & b2kSpriteAnim(gHeroSpr) & " " & round(b2kFrameMS() * 10) / 10 & " ms awake " & b2kAwakeBodyCount() & "/" & b2kBodyCount() into tHud if gHasKey is true then put " [KEY]" after tHud if gCamOK is true then - put " | view " & the hScroll of b2kCamGroup() & "-" & (the hScroll of b2kCamGroup() + 1024) after tHud + put the hScroll of b2kCamGroup() into tHS -- read the scroll once + put " | view " & tHS & "-" & (tHS + 1024) after tHud end if if not b2kIsAwake(gHero) then put " [ASLEEP?!]" after tHud -- must never show if b2kSoundStatus() is not empty then put " [audio: " & b2kSoundStatus() & "]" after tHud @@ -2483,7 +2712,7 @@ end pfTickGate -- shell; bat -> batfly; mimic -> mimiclive. (The stomp/kick/hurt -- VERDICTS live in b2kContact - this tick is pure steering.) command pfTickSlimes - local i, j, tX, tY, tPos, tBody, tVX, tMS + local i, j, tX, tY, tPos, tBody, tVX, tMS, tVel set the itemDelimiter to comma put the milliseconds into tMS repeat with i = 1 to gSlimeN @@ -2515,10 +2744,11 @@ command pfTickSlimes -- the bowling ball: a per-frame velocity ASSERT (the -- player-controller pattern) keeps the slide at speed; a -- wall hit shows as collapsed vx -> reverse, keep rolling - put item 1 of b2kVelocity(tBody) into tVX + put b2kVelocity(tBody) into tVel -- read once, reuse for the vy pass-through + put item 1 of tVel into tVX if gSlimeDir[i] > 0 and tVX < 60 then put -520 into gSlimeDir[i] if gSlimeDir[i] < 0 and tVX > -60 then put 520 into gSlimeDir[i] - b2kSetVelocity tBody, gSlimeDir[i], item 2 of b2kVelocity(tBody) + b2kSetVelocity tBody, gSlimeDir[i], item 2 of tVel if gSlimeSpr[i] is not empty then b2kSpriteFlipH gSlimeSpr[i], ((gSlimeDir[i] < 0) is not gSlimeFlip[i]) -- bowl over any ground foe it reaches (shells are immune -- to each other; this loop only runs while one slides) @@ -2689,21 +2919,25 @@ command pfTickThwomps repeat with i = 1 to gBlockN put gBlockB[i] into tBody if tBody is empty then next repeat + if gBlockState[i] is "armed" then + -- a STATIC block resting at a fixed perch (x cached, y always 200): + -- trigger on the cached x + the hero snapshot, NO per-frame FFI. + -- Only the 0-1 blocks actually in motion below pay b2kPosition. + if abs(tHX - gBlockX[i]) < 42 and tHY > 200 then + put "falling" into gBlockState[i] + b2kSetDynamic tBody + b2kSetGravityScale tBody, 1.8 -- slam, not float + -- (face swaps only exist on the block_* fallback art; the + -- chained weight is one frame and the chain stays behind) + if gBlockFace[i] is true then b2kSpriteSetFrame gBlockSpr[i], "block_fall" + end if + next repeat + end if put b2kPosition(tBody) into tPos if tPos is empty then next repeat put item 1 of tPos into tBX put item 2 of tPos into tBY switch gBlockState[i] - case "armed" - if abs(tHX - tBX) < 42 and tHY > tBY then - put "falling" into gBlockState[i] - b2kSetDynamic tBody - b2kSetGravityScale tBody, 1.8 -- slam, not float - -- (face swaps only exist on the block_* fallback art; the - -- chained weight is one frame and the chain stays behind) - if gBlockFace[i] is true then b2kSpriteSetFrame gBlockSpr[i], "block_fall" - end if - break case "falling" pfBlockUndersideCheck tHX, tHY, tBX, tBY if abs(item 2 of b2kVelocity(tBody)) < 4 and tBY > 320 then @@ -2734,6 +2968,7 @@ command pfTickThwomps b2kSetStatic tBody b2kMoveTo tBody, tBX, 200 b2kSpriteMoveTo tBody, tBX, 200 -- statics are not body-synced + put tBX into gBlockX[i] -- keep the cached perch honest after any drift -- re-seat the chain under the weight (defensive: the rise -- is vertical, but any drift would betray the chain) if gBlockChainA[i] is not empty then b2kSpriteMoveTo gBlockChainA[i], tBX, 76 diff --git a/plan.md b/plan.md index 70382ae..28db42b 100644 --- a/plan.md +++ b/plan.md @@ -317,3 +317,7 @@ user-confirmed in OXT before the next begins. | 2026-06-13 | **Platformer SHOWCASE polish round (pre-Wave-4; user direction: "show off the kit as it stands today, classic platformers in mind, more sprites + enemies").** A deep-dive survey found the kit's biggest DARK subsystem is JOINTS/dynamics (the demo used 90 of the kit's handlers but zero joints), so the polish draws its four marquee mechanisms from there — all **example-side, ZERO Kit changes, no harness bump** (rule 2), matching how Waves 1 and 3 shipped. Calls: (1) **only hazards the player TIMES/AVOIDS, never rides** — the controller has no platform-carry until a later wave, so a ridable moving platform would slide the player off (verified in `b2kPlayerTick`); a wrecking ball (hinge+motor), a sagging rope bridge (the canonical revolute-joint bridge, hinged planks pinned both ends), a sliding boulder, and an exploding barrel all sidestep that. (2) **Verdicts POLLED, bodies `b2kNoCollide` with the hero** (wrecking ball, boulder) so a solid shove never fights the controller's vx-assert/ground-snap; the pendulum head is found flip-proof at `pivot + 2*(centre - pivot)` (no angle read). (3) The boulder **recycles on a cycle, re-parked the instant it passes its reset line** so it never reaches the kill floor and never needs a respawn; a signed release velocity covers both a flat icy SLIDE (L3) and a ramp roll. (4) New enemy SPECIES (mouse/worm/ladybug/fire slime) **join the slime family** as ordinary patrollers via a new `pfMakeCritter` + two table columns (`gSlimeSpeed`, `gSlimeFlat`) — the variety-not-archetypes choice keeps frog/spider/barnacle for their planned Wave 6. (5) Levels **extended and re-spaced per the layout law** (L1 4800 / L2 3712 / L3 4416 / L4 4288); each walled-door/steps finale shifts as a WHOLE so the proven beats survive, and a new L1 checkpoint at the bridge brink makes a fall a retry, not a replay. Perf: every new tick idles at one compare and shares the one hero snapshot; the HUD now prints `awake N/M`. `b2kTeardown` destroys the world, so the first-ever joints clean up with it. Statically verified; awaiting the OXT pass. | User direction; this commit | | 2026-06-13 | **Showcase round, user review pass: the wrecking ball is CUT; the boulder gets a sprite face; the piranha row doubles.** The user's call surfaced the SPRITE-ONLY visuals policy (expansion-prep §62) sharply: "the cannonball needs to go — if we cannot use sprites for it, then remove it." A swinging arm/chain ROTATES, and sprites do not (gotcha 23), so the wrecking ball could only ever be a plain graphic — it was removed (maker, tick, globals, the L2 demolition bay now an open breather, which also serves "some things are too close together"). The boulder, by contrast, is ROUND — rotation-agnostic — so it earns a real sprite face (`rock` over the invisible ball, bound so it tracks roll + re-park); the rope-bridge planks stay graphics by necessity (a sagging deck must rotate) but read as wood. L4's PIRANHA row went from two burrows to FOUR (the user: "twice as long"), and the lava/crushers/fire-slime/barrel/steps shifted +192 (tile-aligned) to give the longer row clear air. Lesson reaffirmed: **sprite-only is a hard line for VISIBLE game art — if a mechanic's art must rotate and no sprite fits, cut the mechanic rather than ship a plain graphic.** | User review; this commit | | 2026-06-13 | **Showcase round 2: levels expanded further with classic-platformer acts; two precise sprite/facing fixes.** User: "expand the levels a bit further following all our best practices; get rid of the brown boxes around the bomb (use the crate sprite); everything else should use the provided sprites; reverse the snail (it faces backwards travelling forwards)." Done: (1) the SNAIL flip — a per-row `gSlimeFlip` polarity column XORed into all four `pfTickSlimes` flips; only the snail is `true` (its sheet art mirrors the slimes'), so slimes/mice/worms/ladybugs are byte-identical and only the snail inverts (gotcha 26). (2) The barrel's WOODPILE is now `pfMakeWoodCrate` — the `block_empty` crate sprite over an invisible FIXED-ROTATION spawned box (the L2 gate-crate pattern), so a blast slides the crates without the sprite-cannot-rotate problem (gotcha 23) — no more brown rectangles. (3) Each level gained a classic act, finales shifted as a WHOLE by a tile-aligned offset (L1 +768 tail, L2 +320, L3 +384, L4 +384): L1 a meadow gauntlet (one-way cloud + two slimes + a fly), L2 a second machine bay (a 3rd chained crusher + an ALWAYS-on saw — not on `gSawMov`, so the lever can't disable it), L3 a second snow cloud + a final glacier slime placed clear of the boulder's parked home, L4 a bowling lane (a 2nd snail + a slime to bowl). New widths L1 5568 / L2 4032 / L3 4800 / L4 4864; enemy indices stay unique per level; every new beat on solid ground within bounds. Still zero Kit changes. | User review; this commit | +| 2026-06-13 | **Showcase round 3: a third expansion + the ice-boulder physics fix.** User: "expand a little further — more clouds, coins, enemies, switches, gates; snails liberally; the OLD chained thwomps back; mimic iconic classics; and the ice boulder should SLIDE ALL THE WAY, not reset (it is an ice block)." Done: (1) BOULDER — lowered its friction to 0.18 so it coasts, and moved its reset line off-screen-left (3050 → 2300, past the run and below the camera's left edge while the hero is in it), so a fresh one arrives from the source instead of a single block teleporting back in place; the slide path is clear (the snow cloud is a one-way chain above; the saw is bodiless; the L3 patrol slime sits left of 2300 so the re-park beats it there). (2) A fourth act on each level, finales shifted whole again (L1 +768 tail, L2 +640, L3 +512, L4 +512): the classic CHAINED-WEIGHT THWOMPS return in every level (with "ride the head" coins), SNAILS go liberal (L4 now carries four), more one-way clouds + coins + decor. New widths L1 6336 / L2 4672 / L3 5312 / L4 5376. Enemy indices still unique per level; flags within bounds. Note: the single-instance toys (gate/lever/spring/key-door) stay one-per-level by design — "more gates/switches" was met by carrying the door beat into more levels and stacking machine bays, not by generalising those mechanics (which would be a Kit-shaped change). Still zero Kit changes. | User review; this commit | +| 2026-06-13 | **Final layout pass: the thwomp "ride-the-head" coins were a gate-locking trap; moved on-path.** User found an L3 coin they couldn't find. Root cause: the new thwomps' reward coins were placed at the perch height (first embedded in the 60px block at y184, then lifted to y110 above it) and were reachable only by the obscure "hop onto the resting crusher and ride it up" beat - which a player avoiding the hazard never discovers, so a REQUIRED (collect-every-coin) coin could lock the goal-flag gate. Fix: all four new thwomp coins (L1 5700, L2 3600, L3 4280, L4 4600) moved to **y500, directly under each crusher on the critical path** - the crusher is too tall to jump over, so the hero MUST pass beneath it to progress and grabs the coin by passing (timing the crush), guaranteeing the gate can open. A full coin-reachability audit (every coin vs the slabs/clouds/pits/plats per level) confirmed every other coin is reachable via its mechanism (spring arcs, the rope-bridge deck, jump-over-pit arcs, the mound plateau, L2's original verified ride coin at 1840,64). Lesson: **a REQUIRED coin must sit on the critical path or an unskippable beat - never behind an optional, discover-it-yourself trick.** Still zero Kit changes. | User review; this commit | +| 2026-06-13 | **Showcase round 4: a marquee CRUSHER ALLEY + cloud hop closes every level.** User: "make these longer - add a row of green/grass thwomps to run under/over, more clouds, longer levels in the same style; show off the engine with as much pizzazz as possible." A new `pTileFace` param on `pfMakeThwomp` makes a plain TILE-SPRITE crusher (no mood-face swaps; the same armed->drop->rest->rise->re-arm cycle), so a ROW of four becomes a timing gauntlet the hero dashes BENEATH (the 42px trigger << 180px spacing means only one drops at a time; coins sit at the 90px midpoints, y500). One alley per level, biome-matched so the mechanic reads at a glance: GREEN blocks (L1 last turn, + L2 grass), BLUE (L3 ice), RED (L4 haunted), each followed by a two-cloud HOP to the flag (clouds at y448 - a 154px jump clears the 128px - coins up top at y392), the clouds one-way chains ghost-padded a tile past the art each side (solid span == art span per the ghost rule). Finales (walled door + steps + flag + bounds) shifted RIGHT as whole units by +1280 (tile-aligned): L2 4672->5952, L3 5312->6592, L4 5376->6656 (L1 was already 7552). New snails in L2 (gSlime 4) and L3 (gSlime 5) keep snails liberal; new crushers are gBlock 5-8 (L2), 3-6 (L3), 4-7 (L4) - unique per namespace per level. Door-gated coins stay reachable (the keys are early on the main path, before each alley). Flags within plat2; coins/totals self-count as built. Static checker clean; awaiting the OXT pass. Still zero Kit changes (harness v10 holds). | User review; this commit | +| 2026-06-13 | **Showcase round 4b: a per-frame optimization pass (user: "as fully optimized to the current kit/library as possible").** An Opus audit of `on b2kFrame` + all 14 `pfTick*` against the perf playbook (cost order: interpreter ops > FFI round-trips > property-set redraws) found one real regression and three free wins, all example-side. (1) HIGH - `pfTickThwomps` did an FFI `b2kPosition` + comma-split for EVERY block every frame, and the new crusher alleys had doubled the block count to ~7-8/level while only 0-1 are ever in motion. Fix: cache each block's perch x at make + re-arm (new `gBlockX[]`), gate the armed->falling trigger on the cached x + the hero snapshot (perch y is invariantly 200, so `tHY > tBY` becomes `tHY > 200`), and read the live position ONLY in the in-motion states - cutting thwomp FFI from ~8/frame to ~0-1/frame with byte-identical trigger semantics. (2) The `shellslide` tick read `b2kVelocity` twice (vx test + vy pass-through) -> read once into a local. (3) The HUD reused the already-snapshotted `gHeroState` instead of re-calling `b2kPlayerState()` and hoisted `the hScroll of b2kCamGroup()` (read twice) into one local - both 4 Hz so minor, but free. The audit confirmed every other tick already optimal (O(1) idle gates via `gXxxN is 0`/`is empty`, hoisted clocks, the shared snapshot, change-gated property/velocity writes, sleeping bodies left asleep) and the gotcha scan clean (no smart quotes; no `local` nested in a block; no per-frame velocity write to a resting body; no two velocity-asserting bodies sharing a band; no stale `the result`). Example-only, so NO harness bump (v10 holds; the harness drives the Kit, not the example's `pf*` ticks). Static checker clean; awaiting the OXT pass. | User direction; this commit |