From 9d1068a962709ba3938fc4f19455c4ccd03d8373 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Jun 2026 03:54:55 +0000 Subject: [PATCH] examples: polish the demo, contraption builder, and slingshot (statically verified) A safe-cosmetic polish pass over the non-platformer example stacks, applying the lessons banked from the platformer (build-once chrome, the performance playbook, the OXT gotchas). All changes are example-side (outside the embedded Kit), pass tools/check-livecodescript.py, and are flagged for an OXT feel/look pass. Demo (box2dxt-demo): - Fix a gotcha-11 landmine: hoist the nested `local tOldest` in registerDrop to the top of the handler (a block-nested local can break OXT compilation; the static checker does not catch it). - Lidar scene now fills the whole arena (spawn bounds derived from gArenaL/R instead of hardcoded literals that clustered the bodies left-of-centre). - Seed the FPS readout so the first HUD frame isn't blank. Contraption builder (box2dxt-contraption-builder): - Remove the dead `partTabs` function (a never-called near-duplicate of partTabsForInspector). - tickHud reads `the milliseconds` once per pass into a local (the "one clock read per pass" rule) instead of up to four times. Slingshot (box2dxt-slingshot): - A built-once SKY BACKDROP (sky + horizon haze + sun + two hills), persistent decor behind every level, created at the back of the chrome and left in place by sgWipeStage; bumps kSgUIVersion 2 -> 3 so older saved stacks rebuild once. - The aim-preview dots now clamp to the turf instead of burying into the ground. - Star-rated level-clear cards (from leftover ammo) and a star flourish on the win card. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01KcQmrtT7Vun4pwnkvHbhxX --- ...box2dxt-contraption-builder.livecodescript | 46 +++----------- examples/box2dxt-demo.livecodescript | 8 +-- examples/box2dxt-slingshot.livecodescript | 60 +++++++++++++++++-- 3 files changed, 67 insertions(+), 47 deletions(-) diff --git a/examples/box2dxt-contraption-builder.livecodescript b/examples/box2dxt-contraption-builder.livecodescript index 5f9085d..4c5b219 100644 --- a/examples/box2dxt-contraption-builder.livecodescript +++ b/examples/box2dxt-contraption-builder.livecodescript @@ -8884,17 +8884,19 @@ function fmtMass pN end fmtMass on tickHud + local tNow + put the milliseconds into tNow -- one clock read per pass (perf playbook) add 1 to gFrames - if gFpsTime is empty then put the milliseconds into gFpsTime - if the milliseconds - gFpsTime >= kFpsWindowMs then - put round(gFrames * 1000 / (the milliseconds - gFpsTime)) into gFps + if gFpsTime is empty then put tNow into gFpsTime + if tNow - gFpsTime >= kFpsWindowMs then + put round(gFrames * 1000 / (tNow - gFpsTime)) into gFps put 0 into gFrames - put the milliseconds into gFpsTime + put tNow into gFpsTime end if -- Refresh the HUD ~10x/sec while running so the live telemetry reads smoothly, -- without rebuilding the whole status string every single frame. - if gHudTime is empty or (the milliseconds - gHudTime) >= 100 then - put the milliseconds into gHudTime + if gHudTime is empty or (tNow - gHudTime) >= 100 then + put tNow into gHudTime updateHud if gPlaying and not gGameWon and there is a field "ui_playbannerlbl" then put "▶ PLAY — " & playElapsed() & "s get a movable part into the goal" into field "ui_playbannerlbl" @@ -11349,38 +11351,6 @@ function propGroup pKey return "special" -- launchspeed,launchdir,spinrate, laserdir, and the fan/magnet/bomb keys end propGroup --- The tabs that actually have settings for this kind (so empty tabs stay hidden). -function partTabs pKind - local tKey, tHasShape, tHasPhys, tHasCollide, tHasSpec, tOut - put false into tHasShape - put false into tHasPhys - put false into tHasCollide - put false into tHasSpec - set the itemDelimiter to comma - repeat for each item tKey in partProps(pKind) - switch propGroup(tKey) - case "shape" - put true into tHasShape - break - case "physics" - put true into tHasPhys - break - case "collide" - put true into tHasCollide - break - case "special" - put true into tHasSpec - break - end switch - end repeat - put empty into tOut - if tHasShape then put "shape" into tOut - if tHasPhys then put addItem(tOut, "physics") into tOut - if tHasCollide then put addItem(tOut, "collide") into tOut - if tHasSpec then put addItem(tOut, "special") into tOut - return tOut -end partTabs - -- Append an item to a comma list (small sibling of addKV for ; lists). function addItem pList, pItem if pList is empty then return pItem diff --git a/examples/box2dxt-demo.livecodescript b/examples/box2dxt-demo.livecodescript index 576a3cc..20448b7 100644 --- a/examples/box2dxt-demo.livecodescript +++ b/examples/box2dxt-demo.livecodescript @@ -5618,7 +5618,7 @@ end raiseUI on clearState put the milliseconds into gFpsTime -- seed the FPS window so the first reading is sane put 0 into gFrames - put empty into gFps + put "--" into gFps -- seed so the first HUD frame reads cleanly, not blank put empty into gDrops put empty into gBombs put empty into gFlash @@ -5907,13 +5907,13 @@ on buildLidar prepArena b2kSetGravity 0, 0 repeat with tI = 1 to 8 - put newBall(rnd(170, 810), rnd(170, 560), rnd(30, 72), "96,150,210") into tBody + put newBall(rnd(gArenaL + 90, gArenaR - 90), rnd(170, 560), rnd(30, 72), "96,150,210") into tBody b2kSetBounce tBody, 1 b2kSetFriction tBody, 0 b2kSetVelocity tBody, rnd(-170, 170), rnd(-170, 170) end repeat repeat with tI = 1 to 3 - put newPoly(rnd(220, 760), rnd(180, 540), rnd(28, 48), "200,160,120") into tBody + put newPoly(rnd(gArenaL + 110, gArenaR - 110), rnd(180, 540), rnd(28, 48), "200,160,120") into tBody b2kSetBounce tBody, 1 b2kSetFriction tBody, 0 b2kSetVelocity tBody, rnd(-130, 130), rnd(-130, 130) @@ -6286,9 +6286,9 @@ end sceneClick -- Keep the count of click-dropped bodies bounded so spamming stays smooth. on registerDrop pCtrl + local tOldest put pCtrl & cr after gDrops if the number of lines of gDrops > kMaxDrops then - local tOldest put line 1 of gDrops into tOldest delete line 1 of gDrops try diff --git a/examples/box2dxt-slingshot.livecodescript b/examples/box2dxt-slingshot.livecodescript index 137050a..a9c703b 100644 --- a/examples/box2dxt-slingshot.livecodescript +++ b/examples/box2dxt-slingshot.livecodescript @@ -82,7 +82,7 @@ local gHudLast, gHudNextMS, gBuilding -- invalidates a stale fade send; gSgCardFade is the ramp (0 opaque .. 100 clear). local gSgCardActive, gSgCardGen, gSgCardFade -constant kSgUIVersion = "2" -- added the transition cover (sgCard / sgCardText) +constant kSgUIVersion = "3" -- added the built-once sky backdrop (sgSky/sgSkyLow/sgSun/sgHillA/sgHillB) constant kPocketX = 170 -- the sling pocket (screen px) constant kPocketY = 444 constant kPullMax = 96 -- tether radius: full pull = full power @@ -120,6 +120,11 @@ command buildSgUI if there is a field "sgBigText" then delete field "sgBigText" if there is a graphic "sgCard" then delete graphic "sgCard" if there is a field "sgCardText" then delete field "sgCardText" + if there is a graphic "sgSky" then delete graphic "sgSky" + if there is a graphic "sgSkyLow" then delete graphic "sgSkyLow" + if there is a graphic "sgSun" then delete graphic "sgSun" + if there is a graphic "sgHillA" then delete graphic "sgHillA" + if there is a graphic "sgHillB" then delete graphic "sgHillB" set the width of this stack to 1024 set the height of this stack to 640 set the loc of this stack to the screenLoc @@ -127,6 +132,40 @@ command buildSgUI set the title of this stack to "Box2Dxt - Slingshot" catch tErr end try + -- ===== a built-once SKY BACKDROP behind every level. Created here (before the + -- rest of the chrome) so it sits at the very back; it is NOT "sg_" prefixed, so + -- sgWipeStage leaves it across level rebuilds. Pure decor (no body). foreground = + -- background on each so no outline shows. (Placement is tunable on an OXT pass.) + create graphic "sgSky" + set the style of it to "rectangle" + set the rect of it to 0, 44, 1024, 580 + set the filled of it to true + set the backgroundColor of it to "148,196,232" + set the foregroundColor of it to "148,196,232" + create graphic "sgSkyLow" + set the style of it to "rectangle" + set the rect of it to 0, 466, 1024, 580 + set the filled of it to true + set the backgroundColor of it to "200,222,236" + set the foregroundColor of it to "200,222,236" + create graphic "sgSun" + set the style of it to "oval" + set the rect of it to 838, 96, 946, 204 + set the filled of it to true + set the backgroundColor of it to "250,236,178" + set the foregroundColor of it to "250,236,178" + create graphic "sgHillA" + set the style of it to "oval" + set the rect of it to -80, 502, 520, 812 + set the filled of it to true + set the backgroundColor of it to "150,184,132" + set the foregroundColor of it to "150,184,132" + create graphic "sgHillB" + set the style of it to "oval" + set the rect of it to 528, 520, 1100, 820 + set the filled of it to true + set the backgroundColor of it to "132,168,118" + set the foregroundColor of it to "132,168,118" create field "sgTitle" set the rect of it to 20, 6, 1004, 26 set the lockText of it to true @@ -284,7 +323,7 @@ end sgBanner command sgShowWin local tMsg put "won" into gMode - put "A L L T O W E R S D O W N !" & cr & cr into tMsg + put "★ A L L T O W E R S D O W N ! ★" & cr & cr into tMsg put "final score " & gScore & cr & cr after tMsg put "C L I C K T O P L A Y A G A I N" after tMsg sgBanner tMsg @@ -680,15 +719,25 @@ end sgPopPig -- "retry" qualifies too: a slow-toppling column that pops the last pig -- AFTER the out-of-ammo banner is a buzzer-beater, not a loss. command sgLevelClear - local tBonus + local tBonus, tStars if gMode is not "play" and gMode is not "retry" then exit sgLevelClear put "clear" into gMode put gShots * 1000 into tBonus add tBonus to gScore + -- a star rating from the ammo left over (skill: the fewer shots used, the more stars) + if gShots >= 3 then + put "★ ★ ★" into tStars + else + if gShots >= 1 then + put "★ ★ ☆" into tStars + else + put "★ ☆ ☆" into tStars + end if + end if if tBonus > 0 then - sgBanner "T O W E R D O W N !" & cr & cr & "+" & tBonus & " ammo bonus" + sgBanner tStars & cr & cr & "T O W E R D O W N !" & cr & cr & "+" & tBonus & " ammo bonus" else - sgBanner "T O W E R D O W N !" + sgBanner tStars & cr & cr & "T O W E R D O W N !" end if b2kSound "clear" send "sgNextLevel" to me in 1700 milliseconds @@ -733,6 +782,7 @@ on b2kFrame put i * 0.11 into tT put round(gAimX + (kPocketX - gAimX) * kLaunchK * tT) into tMX put round(gAimY + (kPocketY - gAimY) * kLaunchK * tT + 300 * tT * tT) into tMY + if tMY > 576 then put 576 into tMY -- rest on the turf, never bury into the ground set the loc of graphic ("sg_dot" & i) to tMX & comma & tMY end repeat if gDotsOn is not true then