From 7e026683710b5233960f533aa512c7457949e41e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 22:30:23 +0000 Subject: [PATCH] platformer: finish Phase E - the goo serpent (L6) + the serpent generalized Completes asset-expansion Phase E (snakes & the slither beat): - Generalize the L4 lava serpent into a reusable pfMakeSerpent / pfTickSerpent (params: pL, pR, pSurfY, pPeakY, pFrame, pAnim), driving both the snakeLava and snakeSlime skins from one code path. Single- instance (one serpent per level, reset on each build); gLSerpLavaY -> gLSerpSurfY. L4 now calls pfMakeSerpent ... "snakeLava.png","snakelava". - New slime-pool beat on L6 (CAVERN DEPTHS): PIT2's 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 (y460) over the centre. Time the double-jump for when it has sunk; a mistimed leap or a slip into the goo is a recoverable knockback. Serpent created before the goo so the goo draws over its submerged body. Self-gates on gSpooksOK (a plain double-jump gap without the spooks sheet). - Two more plain snake placements for variety: a meadow snake on L1 and a sand snake on L5, each on a verified-clear ground stretch (self-reverses at its band ends). - Tie up the loose end: document the forward-constant-reference trap as CLAUDE.md gotcha 29 (OXT leaves a constant referenced before its declaration unresolved - it silently zeroes the expression; that was the L4 serpent's "never rose" bug). Confirmed in OXT. - audit-platformer.py tracks pfMakeSlimePool as a lava-like hazard. Static gates clean; audit-platformer 0 findings across 7 levels. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01X92APxUJUMue3bAzXiX9V2 --- CHANGELOG.md | 20 ++- CLAUDE.md | 10 ++ docs/asset-expansion-plan.md | 33 +++-- examples/box2dxt-platformer.livecodescript | 153 ++++++++++++++------- tools/audit-platformer.py | 2 + 5 files changed, 149 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab0b01c..4b5ae54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,19 @@ The native shim's ABI is tracked separately by `b2Version()` (currently `4`). ### Added +- **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 @@ -17,9 +30,10 @@ The native shim's ABI is tracked separately by `b2Version()` (currently `4`). 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). - New `pfMakeLavaSerpent` / `pfTickLavaSerpent`: 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 + `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 diff --git a/CLAUDE.md b/CLAUDE.md index 24f10d6..fd95a0e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -250,6 +250,16 @@ OXT's compiler is **stricter than LiveCode's**. These are the recurring footguns real half-height (a build-time global), never a hardcoded constant. The platformer's brick-smash gap (round 7) was exactly this: an 88px capsule over a ~76px visible character. +29. **A `constant` must be declared BEFORE its first use, lexically.** OXT resolves + `constant` names by their position in the file, so a handler that references a + constant declared *later* gets an UNRESOLVED name: it compiles with no error, + then evaluates to nothing at runtime, silently zeroing the expression. The L4 + lava serpent shipped broken this way — `pfTickLavaSerpent` used `sin(kPI * tP)` + while `kPI` was declared ~900 lines below, so the term collapsed to 0 every + frame and the serpent never rose (it stayed in its "submerged, hidden" branch + forever). Fix: declare the constant above its first use, or inline the literal + value. Distinct from gotcha 6 (constant *values* must be literals) — this is + about the *order* of declaration vs use. (Confirmed in OXT.) ## The single-threaded performance playbook diff --git a/docs/asset-expansion-plan.md b/docs/asset-expansion-plan.md index 6ea9b45..3bf47d0 100644 --- a/docs/asset-expansion-plan.md +++ b/docs/asset-expansion-plan.md @@ -226,30 +226,35 @@ needs an OXT eye. `spooks`, via `pfMakeCritter`'s new optional sheet param, `gSpooksOK`-gated) and the **ring worm** (`worm_ring`, native foes). Phase D essentially complete. -### Phase E — Snakes & the slither biome beat (M) — BEGUN +### Phase E — Snakes & the slither biome beat (M) — DONE (pending OXT) - **Assets:** `snake(.png)/_walk/_hit/_dead`, `snakeLava*`, `snakeSlime*`. - **New movement type:** **slither** — a ground crawler that hugs the floor and reverses at edges/walls (slime-family kind `snake`, animated `_walk`). The LOW `snake.png` is the crawler; the TALL rearing `snakeLava`/`snakeSlime` art is the - rising **lava serpent** (a separate bodiless mover, not a floor crawler). -- Woven into L4 and the new biomes (no dedicated level needed). + rising **serpent** (a separate bodiless mover, not a floor crawler). +- Woven into L4 (lava), L6 (goo), and across the biomes (no dedicated level needed). - **Done (pending OXT):** - The `snake` kind + slither tick (floor-probe edge/wall reversal) + `pfMakeSnake pIdx,pX,pMinX,pMaxX,pTopY` — the plain low crawler, `gSpooksOK`- - gated, via `pfMakeCritter`'s sheet param. Deployed: L3's ice platform (turns at - the spike pit) and L4's lava-pit approach (turns at the entry pit + lava lip). - - **The "serpent pit" beat (`pfMakeLavaSerpent`/`pfTickLavaSerpent`):** L4's old - collapsing bridge is removed; the lava pit is widened to a 512px chasm crossed - in two hops over a middle stepping-stone, and a rearing `snakeLava` serpent - rises out / arcs across / sinks back in on a sine path, peaking at the stone - (a proximity-poll knockback, the saw rule). Created under the lava tiles so its - submerged body is occluded. + gated, via `pfMakeCritter`'s sheet param. Deployed: L1 meadow, L3 ice, L4 + lava-approach, L5 desert — the slither type across four biomes. + - **The serpent (`pfMakeSerpent`/`pfTickSerpent`, generic):** a rearing snake + that rises out of a hazard pool, arcs across on a sine path and sinks back in, + peaking high so it contests the crossing (a proximity-poll knockback, the saw + rule). Created under the pool surface so its submerged body is occluded. + Single-instance (one per level). Two homes: + - **L4 lava** (`snakeLava`): the old collapsing bridge is gone; the pit is a + 512px chasm crossed in two hops over a middle stepping-stone the serpent + peaks at. + - **L6 goo** (`snakeSlime`): PIT2's spike chasm becomes a toxic **goo pool** + (`pfMakeSlimePool`, the slime-biome twin of `pfMakeLava`); the serpent peaks + at jump-arc height, so you time the double-jump for when it has sunk. - **Sprite grounding fix:** the spook skins fill their frames edge-to-edge (no transparent padding, measured), so their bind offset is the plain geometric `pFullH/2 - frameH*0.9/2`, not the FOES soft-bottom sink that floated them ~9px. -- **TODO:** a `snakeSlime` home (a slime-pool beat) and more snake placements; - optionally teach `audit-platformer.py` the `pfMakeSnake`/`pfMakeLavaSerpent` - makers (currently ignored — harmless, the pit isn't gap-checked). +- **TODO (optional):** more snake placements if wanted; `audit-platformer.py` now + tracks `pfMakeSlimePool` as a hazard but still ignores `pfMakeSnake`/`pfMakeSerpent` + (harmless — the pools aren't gap-checked and the snakes self-reverse). ### Phase F — Collectibles & a health model (M) - **Assets:** `coin_bronze/silver(_side)`, `star`, `heart`, `hud_heart(_half/ diff --git a/examples/box2dxt-platformer.livecodescript b/examples/box2dxt-platformer.livecodescript index 66fd416..2d9c6b2 100644 --- a/examples/box2dxt-platformer.livecodescript +++ b/examples/box2dxt-platformer.livecodescript @@ -262,7 +262,7 @@ -- hidden by the lava tiles), its head reaches the stepping-stone deck at the -- peak, each hop is clearable by a running/double jump, and the mid-stone -- coin is grabbable between rises; tune gLSerpPer (the crossing period) in --- pfMakeLavaSerpent to taste. +-- pfMakeSerpent to taste. (L6 reuses the same serpent as a GOO-pool snake.) -- 18. WAVE 6 BESTIARY II (frog / barnacle / spider - all example-side, no Kit -- change, no harness bump). FROG (L1 far meadow, ~x4660): a native-art -- HOPPER that crouches then LEAPS at you when near; STOMP it from above (it @@ -443,14 +443,14 @@ local gBarrelN, gBarrelB, gBarrelSpr, gBarrelX, gBarrelY, gBarrelState, gBarrelT -- gLiftVX caches the last-written vx so the tick never re-sets an unchanged -- velocity (gotcha 17). Indexed 1..gLiftN. local gLiftN, gLift, gLiftMin, gLiftMax, gLiftSpeed, gLiftVX --- Phase E: the LAVA SERPENT (L4). A bodiless rearing-snake sprite that rises out --- of the lava pit, arcs across it and sinks back in, peaking at the middle --- stepping-stone so it CONTESTS the platform (gotcha 16: a proximity-poll hurt, --- the saw rule - you TIME it, you never stomp it). gLSerpL/R = the lava lips it --- travels between; gLSerpLavaY = the surface it emerges from; gLSerpPeakY = the --- head's height at the centre peak; gLSerpVis caches its shown/hidden state so the --- visibility flip is write-on-change. (It replaced the L4 collapsing bridge.) -local gLSerpSpr, gLSerpL, gLSerpR, gLSerpLavaY, gLSerpPeakY, gLSerpPer +-- Phase E: the rearing SERPENT (L4 lava, L6 goo). A bodiless snake sprite that +-- rises out of a hazard pool, arcs across it and sinks back in, peaking high so it +-- CONTESTS the crossing (gotcha 16: a proximity-poll hurt, the saw rule - you TIME +-- it, you never stomp it). gLSerpL/R = the pool lips it travels between; +-- gLSerpSurfY = the surface it emerges from; gLSerpPeakY = the head's height at the +-- centre peak; gLSerpVis caches its shown/hidden state so the visibility flip is +-- write-on-change. Single-instance (one serpent per level), reset on each build. +local gLSerpSpr, gLSerpL, gLSerpR, gLSerpSurfY, gLSerpPeakY, gLSerpPer local gLSerpHalfH, gLSerpVis -- Wave 6 (bestiary II), all example-side: the FROG joins the slime family -- (gFrogAir, indexed by slime row, tracks its mid-hop pose so the crouch frame @@ -1060,7 +1060,7 @@ command pfStartGame put empty into gBarrelY put empty into gBarrelState put empty into gBarrelT - -- the lava serpent starts empty (only L4 builds one) + -- the serpent starts empty (only the L4 lava / L6 goo pools build one) put empty into gLSerpSpr put false into gLSerpVis -- the Wave 5 moving-platform lifts start the run empty/idle @@ -1583,8 +1583,8 @@ end pfMakeCritter -- gets its own tick: it crawls the floor and AUTO-REVERSES at a pit edge or a -- wall (pfTickSlimes "snake" case), so you place it without hand-tuning a band - -- pMinX/pMaxX are just the outer safety bound. This is the LOW (snake.png) --- crawler; the taller snakeLava/snakeSlime art rears up and is the rising LAVA --- SERPENT (pfMakeLavaSerpent). Spook-sheet art, so it SELF-GATES on gSpooksOK. +-- crawler; the taller snakeLava/snakeSlime art rears up and is the rising +-- SERPENT (pfMakeSerpent). Spook-sheet art, so it SELF-GATES on gSpooksOK. command pfMakeSnake pIdx, pX, pMinX, pMaxX, pTopY if gSpooksOK is not true then exit pfMakeSnake pfMakeCritter pIdx, "snake", pX, pMinX, pMaxX, pTopY, 46, "snake.png", "snakewalk", "snake_dead.png", 28, 24, "150,170,90", 3, "spooks" @@ -2050,6 +2050,38 @@ command pfMakeLava pL, pR, pSensorTop b2kAddSensor tRef, "box" end pfMakeLava +-- Phase E: a TOXIC GOO POOL - the slime-biome twin of pfMakeLava and the home of +-- the snakeSlime SERPENT (L6's cavern). A green goo rect filling the pit (the y576 +-- row, so it occludes a serpent's submerged lower body exactly as the lava tiles +-- do) over a knockback hurt sensor, plus a bright surface line. Like the lava, +-- wading in knocks back (pfOuch via uPfHazardFlag) - it is not a respawn pit. A +-- plain rect (the tiles sheet has no goo frame), so no gAssetsOK branch is needed. +-- pSensorTop defaults to y596 (a hero on the bank is clear, a slip into the goo +-- burns). Create the serpent BEFORE this so the goo draws OVER its lower body. +command pfMakeSlimePool pL, pR, pSensorTop + local tRef + if pSensorTop is empty then put 596 into pSensorTop + create graphic ("pf_goo" & pL) + set the style of it to "rectangle" + set the rect of it to pL, 576, pR, 640 + set the filled of it to true + set the backgroundColor of it to "96,176,72" -- toxic green goo + b2kCamAdopt the long id of graphic ("pf_goo" & pL) + create graphic ("pf_gooSurf" & pL) + set the style of it to "line" + set the points of it to pL & comma & 578 & cr & pR & comma & 578 + set the lineSize of it to 3 + set the foregroundColor of it to "168,228,128" -- a brighter goo surface line + b2kCamAdopt the long id of graphic ("pf_gooSurf" & pL) + create graphic ("pf_goosens" & pL) + set the style of it to "rectangle" + set the rect of it to pL + 6, pSensorTop, pR - 6, 640 + set the visible of it to false + put the long id of graphic ("pf_goosens" & pL) into tRef + set the uPfHazardFlag of tRef to true + b2kAddSensor tRef, "box" +end pfMakeSlimePool + -- Wave 4: a SWIM basin. Registers the Kit swim zone (the controller polls -- it -- buoyant gravity, capped sink, UP/DOWN swim, SPACE strokes up) and -- draws the water as a blue rect with a bright surface line. The caller @@ -2472,34 +2504,36 @@ command pfMakeBridge pL, pR, pY, pPlanks if tPrev is not empty then b2kHinge tPrev, empty, pR, pY -- the far post end pfMakeBridge --- Phase E: the LAVA SERPENT (L4, replacing the old collapsing bridge). A tall --- rearing snake (snakeLava art) that rises out of the lava pit, arcs ACROSS it --- and sinks back in, peaking at the middle stepping-stone so it CONTESTS the --- platform. Bodiless: a pure sprite that pfTickLavaSerpent drives along a sine --- arc (rise at the lips, peak over the centre) and hides while submerged - the --- lava tiles occlude its lower body, so it must be created BEFORE pfMakeLava. --- Unkillable, like the saws (gotcha 16: a proximity-poll knockback, never a --- stomp). Optional art (enemies.png / gSpooksOK): absent, the maker no-ops and --- the pit is simply a two-hop lava gap (the crossing never depends on it). --- pL/pR are the lava lips; pLavaY the surface; pPeakY the head's height at peak. -command pfMakeLavaSerpent pL, pR, pLavaY, pPeakY +-- Phase E: a rearing SERPENT - a tall snake that rises out of a hazard pool +-- (LAVA on L4, toxic GOO on L6), arcs ACROSS it on a sine path and sinks back +-- in, peaking high so it CONTESTS the crossing (the L4 stepping-stone, the L6 +-- jump arc). Bodiless: a pure sprite that pfTickSerpent drives along the arc +-- (rise at the lips, peak over the centre) and hides while submerged - the pool's +-- surface (lava tiles / goo rect) occludes its lower body, so it must be created +-- BEFORE the pool. Unkillable, like the saws (gotcha 16: a proximity-poll +-- knockback, never a stomp). pFrame/pAnim pick the skin (snakeLava / snakeSlime), +-- both spook-sheet art, so it SELF-GATES on gSpooksOK - absent, the maker no-ops +-- and the pit is simply a hazard gap (the crossing never depends on the serpent). +-- pL/pR are the pool lips; pSurfY the surface it emerges from; pPeakY the head's +-- height at the centre peak. +command pfMakeSerpent pL, pR, pSurfY, pPeakY, pFrame, pAnim local tRef - if gSpooksOK is not true then exit pfMakeLavaSerpent - if not b2kSheetHasFrame("spooks", "snakeLava.png") then exit pfMakeLavaSerpent - b2kSpriteNew "spooks", "snakeLava.png", (pL + pR) / 2, pLavaY + if gSpooksOK is not true then exit pfMakeSerpent + if not b2kSheetHasFrame("spooks", pFrame) then exit pfMakeSerpent + b2kSpriteNew "spooks", pFrame, (pL + pR) / 2, pSurfY put the result into tRef - if tRef is empty then exit pfMakeLavaSerpent - b2kSpritePlay tRef, "snakelava" + if tRef is empty then exit pfMakeSerpent + b2kSpritePlay tRef, pAnim set the visible of tRef to false -- starts submerged (the tick shows it as it rises) put tRef into gLSerpSpr put pL into gLSerpL put pR into gLSerpR - put pLavaY into gLSerpLavaY + put pSurfY into gLSerpSurfY put pPeakY into gLSerpPeakY put 4200 into gLSerpPer -- one full lip-to-lip crossing; slow enough to read + time - put 66 into gLSerpHalfH -- snakeLava is 147px * the 0.9 spooks scale ~= 132 tall; head at the sprite TOP, so half ~= 66 + put 66 into gLSerpHalfH -- snakeLava/Slime is 147px * the 0.9 spooks scale ~= 132 tall; head at the sprite TOP, so half ~= 66 put false into gLSerpVis -end pfMakeLavaSerpent +end pfMakeSerpent -- A ROLLING BOULDER: a dynamic ball released across the ground (a flat -- icy SLIDE on level 3, signed pVX) toward the hero, who must outrun or @@ -2932,6 +2966,10 @@ command pfL1Cast end if pfMakeSlime 1, "spike", 880, 760, 1000, 576 pfMakeSlime 2, "normal", 1130, 1080, 1180, 576 + -- Phase E: a SNAKE slithers the open meadow stretch past the slimes (auto- + -- reverses at its band ends; clear of the pit at 2560). The showcase level gets + -- the slither type too. Optional spooks art - absent, the stretch is just open. + pfMakeSnake 11, 2050, 1700, 2300, 576 if gSpooksOK is true then -- Phase D: a GREEN slime variant (optional spooks art; a normal slime without it) pfMakeCritter 3, "normal", 2876, 2816, 2936, 576, 52, "slimeGreen.png", "slimegreen", "slimeGreen_squashed.png", 22, 36, "120,200,90", 4, "spooks" else @@ -3619,7 +3657,7 @@ command pfL4Scene -- to the stepping-stone deck (y490) at its peak over the pit centre (x3200), -- CONTESTING the stone: time your two hops between its rises. Bodiless, the -- saw rule (gotcha 16) - a recoverable proximity knockback, never a stomp. - pfMakeLavaSerpent 2944, 3456, 596, 490 + pfMakeSerpent 2944, 3456, 596, 490, "snakeLava.png", "snakelava" -- the WIDE lava pit (2944..3456, 512px - a real widening from the old 192px -- bridge strip) with a low hurt-top (y596) so a hero ON the banks / the -- stepping-stone is clear, but a slip INTO the lava burns. Crossed in TWO @@ -3930,6 +3968,9 @@ command pfL5Cast -- ACT 1 cast pfMakeSlime 1, "normal", 680, 620, 760, 576 pfMakeFrog 2, 1500, 1420, 1620, 576 + -- Phase E: a SAND SNAKE slithers the open dune stretch before the snail (auto- + -- reverses at its band ends; clear of the snail at 2940). Optional spooks art. + pfMakeSnake 8, 2550, 2350, 2850, 576 pfMakeSnail 3, 3000, 2940, 3060 pfMakeBarnacle 1, 3300, 544 -- ACT 2 cast: a desert FLY, a BOULDER rolling the dune, a FIRE slime, a 2nd frog @@ -4089,8 +4130,16 @@ command pfL6Scene -- mechanic (Phase B, slice 3). Grid-aligned, on solid ground2. pfMakeConveyor 2176, 2368, -1 -- ===== ACT 2/3 terrain (the asset-expansion LENGTH pass) ===== - -- PIT2 - a WIDER spiked chasm (256px = four spikes) past the crusher gallery. - pfMakeSpikes 3520, 3776 + -- PIT2 - the GOO PIT (Phase E): a 256px toxic-goo chasm past the crusher + -- gallery where a rearing snakeSlime SERPENT rises out of the goo, arcs across + -- and sinks back in. The serpent is created BEFORE pfMakeSlimePool so the goo + -- draws over its submerged body (it reads as rising OUT of the goo). It peaks at + -- y460 over the centre (x3648) - so time your double-jump for when it has sunk; + -- a mistimed leap is a knockback (pfOuch) to the near bank, and a slip into the + -- goo burns the same. Self-gates on gSpooksOK: absent the spooks sheet the goo + -- pit is just a plain double-jump gap (still completable). + pfMakeSerpent 3520, 3776, 596, 460, "snakeSlime.png", "snakeslime" + pfMakeSlimePool 3520, 3776, 596 -- a one-way DIRT CLOUD high route on G3 (solid span 4416..4608), and a -- second on G4 (solid 5568..5760) - both ghost-padded a tile each side. b2kSmoothGround "4672,448" & cr & "4608,448" & cr & "4416,448" & cr & "4352,448" @@ -4177,7 +4226,7 @@ command pfL6Cast pfMakeCoin 2880, 440 -- under the BATS (duck the swoop) pfMakeCoin 3088, 500 -- into the crusher gallery pfMakeCoin 3304, 500 -- threading the two crushers: grab it between drops - pfMakeCoin 3648, 452 -- mid-air over PIT2: leap the wider chasm + pfMakeCoin 3648, 452 -- mid-air over the GOO PIT: grab it as you leap, timed for when the serpent has sunk -- ACT 3 coins (the deep run + the descent to the flag) pfMakeCoin 3968, 470 -- under the dropping SPIDER (grab it as you dodge) pfMakeCoin 4180, 500 -- the ghost-stalked run @@ -4499,7 +4548,7 @@ on b2kFrame pfTickBoulder pfTickLift pfTickConveyor -- Phase B: belts carry the grounded hero - pfTickLavaSerpent -- Phase E: the L4 lava serpent's rise-cross-sink arc + pfTickSerpent -- Phase E: the L4 lava / L6 goo serpent's rise-cross-sink arc pfTickBarrel pfTickParallax -- Wave 8: drift the biome backdrop for depth -- HUD at 4 Hz, not 60: the ms readout changes every frame, so an @@ -5489,30 +5538,30 @@ command pfTickLift end repeat end pfTickLift --- The lava serpent's arc (Phase E): a sine EMERGENCE (0 at the lips, 1 over the --- middle platform) drives both how far it has risen and where along the pit it --- sits, so one parameter traces a rise-cross-sink path lip to lip; it loops, and --- the reset lands while submerged (hidden), so the teleport back to the left lip --- never shows. The lava tiles (the y576 row) occlude its lower body as it rises; --- we ALSO hide it outright while its head is below the surface so the no-tile --- fallback never shows a snake floating in the gap. Proximity HURT (the saw --- rule, gotcha 16) fires only while emerged - a recoverable knockback, never a --- respawn. O(1) out when no serpent exists (the non-L4 case). -command pfTickLavaSerpent +-- The serpent's arc (Phase E): a sine EMERGENCE (0 at the lips, 1 over the centre) +-- drives both how far it has risen and where along the pool it sits, so one +-- parameter traces a rise-cross-sink path lip to lip; it loops, and the reset +-- lands while submerged (hidden), so the teleport back to the left lip never +-- shows. The pool surface (the y576 row) occludes its lower body as it rises; we +-- ALSO hide it outright while its head is below the surface so the no-tile +-- fallback never shows a snake floating in the gap. Proximity HURT (the saw rule, +-- gotcha 16) fires only while emerged - a recoverable knockback, never a respawn. +-- O(1) out when no serpent exists (every non-serpent level). +command pfTickSerpent local tP, tEm, tX, tHeadY, tCy, tMS - if gLSerpSpr is empty then exit pfTickLavaSerpent + if gLSerpSpr is empty then exit pfTickSerpent put the milliseconds into tMS put (tMS mod gLSerpPer) / gLSerpPer into tP put sin(3.14159265 * tP) into tEm -- 0 at the lips, 1 at the centre peak (literal pi: kPI is declared later in the script, and a forward constant ref leaves it unresolved on OXT - which pinned tEm at 0 and kept the serpent submerged forever) put gLSerpL + (gLSerpR - gLSerpL) * tP into tX - put gLSerpLavaY - (gLSerpLavaY - gLSerpPeakY) * tEm into tHeadY - if tHeadY >= gLSerpLavaY - 6 then - -- submerged (head at/below the lava line): hide it; the loop reset lands here + put gLSerpSurfY - (gLSerpSurfY - gLSerpPeakY) * tEm into tHeadY + if tHeadY >= gLSerpSurfY - 6 then + -- submerged (head at/below the pool surface): hide it; the loop reset lands here if gLSerpVis then set the visible of gLSerpSpr to false put false into gLSerpVis end if - exit pfTickLavaSerpent + exit pfTickSerpent end if if not gLSerpVis then set the visible of gLSerpSpr to true @@ -5523,7 +5572,7 @@ command pfTickLavaSerpent -- proximity HURT (the saw rule): the head's strike zone. Mistime the crossing -- and the rearing head bounces you back to the near bank (pfOuch), not a kill. if gHurtLock is not true and abs(gHeroPX - tX) < 34 and abs(gHeroPY - tHeadY) < 84 then pfOuch tX -end pfTickLavaSerpent +end pfTickSerpent -- The powder keg: idle until the hero is near, then the fuse lights -- (bomb_active); a beat later b2kExplode scatters the woodpile, knocks the diff --git a/tools/audit-platformer.py b/tools/audit-platformer.py index a581b1a..abddd1c 100644 --- a/tools/audit-platformer.py +++ b/tools/audit-platformer.py @@ -103,6 +103,8 @@ def parse(): v = nums(s); L.spikes.append((v[0], v[1])); continue if s.startswith("pfMakeLava"): v = nums(s); L.lava.append((v[0], v[1])); continue + if s.startswith("pfMakeSlimePool"): # Phase E goo pool = a lava-like surface hazard + v = nums(s); L.lava.append((v[0], v[1])); continue if s.startswith("pfMakeWater"): v = nums(s); L.water.append((v[0], v[1], v[2], v[3])); continue if s.startswith("pfMakeCoin"):