fix(m3): planet-1's summoned platform arms on arrival, not on drop - #40
Conversation
Three AI-pilot co-op playtests never once crossed planet-1's pit at x=660-880. The corrected death table pinned the cause: the platform cost ~4.9s to earn (4835-4869ms, n=5) and lived 5.0s from the moment it dropped, and the pit sits immediately after the sentry band -- so the pair had to hold a 3.0s freeze and a 5.0s platform open SIMULTANEOUSLY from two puzzles with a ~7x solve-time spread (freeze 672-676ms, n=5). No pair managed the overlap; every death clustered at the pit lip (x~659, 665), one landing 0.2s after the platform expired. The lever the report identified is the platform's earn-vs-life ratio, so that is the only thing this touches. summonPlatform() no longer schedules the fade at drop time -- it stamps lifetimeMs on the sprite and the platform holds indefinitely. The astronaut/platforms collider doubles as the "reached it" signal, calling a new armPlatform() that starts the same 5000ms (8000ms boosted) fade/destroy on FIRST CONTACT and no-ops thereafter via an `armed` sprite-data flag. Lifetime rides on the sprite rather than a scene field so a scene restart cannot strand a countdown. The freeze timer, patrol band, drop x, earn cost, PLATFORM HOLDS! re-cast branch and pit geometry are untouched, and both powers stay mandatory: the pit is still uncrossable without a platform, the band still unrunnable without a freeze. The pit becomes a coordination problem instead of a stopwatch problem -- the phone banks the platform first, then earns the freeze. Phone copy follows the behaviour; AUTONOMY.md and /verify-planet re-document the sharp edge as arm-on-arrival. BACKLOG records this plus the two secondary playtest findings (the level punishes standing still; the support seat is blind to world state) as new Open items. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TerCorG6J23DL2YCZ1nUqh
Adversarial review F1. The previous commit armed the countdown from the astronaut/platforms collider callback, which Phaser fires on any resolved contact on any face -- not on "the astronaut reached it". Two consequences, both re-introducing the stopwatch failure the change exists to remove: - Planet-1's jump is a 78px gap (ground right edge 640 -> platform spans 722-818) onto a 14px band. An undershoot clips the platform's LEFT face on the way into the pit, burning the whole bridge on a platform nobody stood on. The astronaut respawns at spawn.x needing a fresh freeze to re-cross the sentry band and cannot return in time. - Planet-3 makes it structural, not an edge case: the pit is degenerate (continuous ground) and platformDrop (730,470) puts a knee-high ledge spanning y 463-477 in the path of an astronaut whose body spans y 452-500. A horizontal bump at ground level is its ONLY possible first contact, so the flourish armed itself the moment you walked into it. armPlatform() now gates on a landing: arcade separation parks a landed astronaut's feet on the platform's top face, so body.bottom <= top + 2px identifies a landing and rejects both a side clip and an underside bonk, independent of sprite dimensions. Second prong: while a platform was armed, the PLATFORM HOLDS! branch swallowed the phone's re-cast behind a banner claiming the bridge was fine -- so a burnt platform cost the pair a whole ~4.9s TapSequence solve for nothing. The banner now fires only for an UNARMED platform (which genuinely cannot be improved on); a re-cast onto an armed one calls the new refreshPlatform(), cancelling the pending expiry and any fade in flight and returning it to the waiting state. An astronaut still standing on it re-arms next frame with a full new lifetime. Verified headlessly at ?solo=1&test=1: planet-3 sustained side-bump held the platform 8s+ (pre-fix it died at 5s); a planet-1 undershoot into the pit respawned with platformCount still 1; a re-cast 4s into the countdown extended life to 5043ms past the re-cast instead of dying at 5s; and planet-1 cleared 3/3 with 0 respawns and the platform banked 12s early. Gate: typecheck x2, 204 Vitest, build, smoke:relay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TerCorG6J23DL2YCZ1nUqh
Adversarial review F5, a consequence of the landing gate in d4f5a3c. Once a never-mounted platform persists, planet-3's placement becomes a permanent lethal pin. Planet-3's ground is continuous, so its summoned platform is a knee-high ledge ON the walking path rather than a bridge over a pit: walking right into it BLOCKS the astronaut instead of landing it. Committed geometry at the old drop x=730 -- curtain x 576..664 (planet3.ts:45-48), platform span 682..778 (96px texture), astronaut body 32x48 -- left an 18px gap for a 32px body. A blocked astronaut sat at left=650, i.e. 14px inside the curtain, and died the instant the 2500ms Phase Dash window closed. Before the landing gate the side bump armed the platform and it faded after 5s, so the trap cleared itself; after it, nothing retires an un-mounted platform (the phone's re-cast hits the !armed PLATFORM HOLDS! branch and resetAstronaut never touches the group), so it repeated on every attempt for the rest of the scene. Moving the drop 730 -> 750 is the root fix -- the constraint window is narrow but real: blocked astronaut left = (x - 48) - 32 >= curtain right 664 -> x >= 744 platform right = x + 48 <= dark-zone left 805 -> x <= 757 750 sits mid-window: blocked left 670 (6px clear of the curtain), right edge 798 (7px clear of the dark zone). The residual "un-mounted ledge persists" behaviour is now benign -- stepping on it arms and retires it, and it is 37px tall on continuous ground, so it is an obstacle, never a trap. Three colocated planet3 tests pin the clearance so it cannot regress silently (207 tests, was 204); planet-1 and planet-2 drop into open pits, where none of this applies. BACKLOG's Done note is corrected where this branch falsified it: the trigger is a landing (not first contact), the PLATFORM HOLDS! branch was reworked rather than untouched, and the drop x did move on planet-3. Verified headlessly at ?solo=1&test=1: pinned against the ledge at x=686 with phaseActive false for 9s -> 0 deaths (pre-fix: guaranteed loop). Gate: typecheck x2, 207 Vitest, build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TerCorG6J23DL2YCZ1nUqh
Adversarial review — CLEAR TO MERGE (rounds: 3)
Waived by Kyle: none — no waiver was requested or granted; both blocking findings were fixed and re-verified. Follow-ups (nice-to-have): F6 Coverage: complete — all three rounds reported complete coverage; no bound left unclosed. Two rounds of fixes came out of this: the original commit armed the countdown from the collider callback, which Phaser fires on any face, and the fix for that then made a never-mounted platform permanent — which planet-3's geometry turned lethal. Both were caught here, not in play. Zero-context reviewer + neutral judge on disputes (no disputes were raised); anchored at |
Nice-to-have findings from the adversarial review of PR #40, deliberately not fixed in-loop. Review record: ~/.claude/reviews/constellation/2026-08-13-fix-m3-platform-arm-on-arrival.md - F6: docs/AUTONOMY.md (playbook step 3 + the sharp edge) and /verify-planet's operating rules said the platform arms on "first contact". It arms on the first LANDING — a side clip does not arm it, which matters most on planet-3, where the ledge sits on continuous ground and walking into it never starts the countdown. As written it told a headless driver to wait for platformCount to reach 0 forever. - F2: /verify-planet step 3 still said to cast summon-platform "at the pit", contradicting its own rules 20 lines up. Mirrors AUTONOMY step 3. - F3: HANDOFF.md was dated 2026-07-26 and claimed two remaining Open BACKLOG items (now four); refreshed the date, standing, next move and recently-touched files. Decisions.md gains D9 for the drop-time → landing-time lifetime reversal. - F4: the phone's platform copy promised "holds 5s" while the lasting-platform talent makes it 8000ms. Dropped the number rather than threading the boosted flag through FEEDBACK; "he" → "they". - F7: documented the fourth placement constraint on planet-3's platformDrop — the hidden platform's collider is solid always, so the ledge can bump it — and recorded that clean clearance is unsatisfiable inside the 744..757 window F5 forces. Accepted, not solved. - F8: the "blocked, not landed" test asserted GROUND_SURFACE_Y > platformTop, looser than the runtime gate; it now asserts against PLATFORM_LANDING_EPSILON and gains an upper bound so a ledge raised above head height fails too. Gates: typecheck · typecheck:tests · vitest 207/207 · build. Claude-Session: https://claude.ai/code/session_01YLr4UWKBbzmSaMxi1vbkdR Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
Correction (edited). The first version of this comment claimed the run A behavioural cross-check on this PR's pit gate, from the pi fork's playtest
What the run does show, on trajectories where the platform is not a confound:
Two incidental observations, both offered as notes rather than defects:
No action requested. Full method, per-trial data and caveats: This comment is AI-generated from a scripted playtest run in the pi fork. |
The finding
Three AI-pilot co-op playtests (harness in the pi fork at
.pi/playtest/, zero-diff on this repo) never once crossed planet-1's pit at x=660–880. The corrected death table (pilot 3, 2026-08-12) pinned the cause precisely:The change
One lever, exactly the one the report recommended — the platform's life now starts on arrival, not on drop:
summonPlatform()no longer schedules the fade at drop time. It stampslifetimeMson the sprite and the platform holds indefinitely.armPlatform()that starts the same 5000ms (8000ms boosted) fade/destroy on first contact and no-ops thereafter via anarmedsprite-data flag.Deliberately untouched: the freeze timer, the patrol band, the drop x, the platform's earn cost, the
PLATFORM HOLDS!re-cast branch, and the pit geometry. No protocol, relay, dependency, or puzzle-difficulty changes.Both powers stay mandatory — the pit is still uncrossable without a platform, the band still unrunnable without a freeze. The pit becomes a coordination problem instead of a stopwatch problem: the phone banks the platform first (~4.9s), then earns the freeze (~0.7s), instead of racing them.
Verification
Full gate green:
typecheck·typecheck:tests· 204 Vitest ·build·smoke:relay.Live headless runs at
?solo=1&test=1via the pinned Playwright MCP:platformCount === 1at t = 0.3s, 3s, 6s, 9s, 12s (old code: destroyed at 5s)respawnCount1,platformCountstill 1won: true, 0 respawns,completed['planet-1'], planet-2 unlockedAlso in this PR
Docs that stated drop-time expiry are corrected (
docs/AUTONOMY.mdsharp-edges + playbook step 3,/verify-planet's re-cast note), and the phone's cast copy follows the behaviour ("bridge waits, then holds 5s once he steps on").The two secondary playtest findings are recorded, not fixed — both are real level/UX work beyond this lever, so they land as new
## Openbacklog items:🤖 Generated with Claude Code
https://claude.ai/code/session_01TerCorG6J23DL2YCZ1nUqh