diff --git a/js/scenes/GameScene.js b/js/scenes/GameScene.js index 790cb61..c5eea4a 100644 --- a/js/scenes/GameScene.js +++ b/js/scenes/GameScene.js @@ -2627,7 +2627,26 @@ export class GameScene extends Phaser.Scene { // The level-up marquee's freeze (js/ui/LevelUpMarquee.js): the beat // owns the screen, so the action holds while it plays — the same // rule as the ack pause, one frame of the world standing still. - const _frozen = _commsPaused || this.levelMarquee?.active === true; + // The JUMP CLIP's freeze (GameScene._playJumpClip): the transport + // owns the screen (opaque backdrop + the gate video) for the clip's + // length, so the action holds the same way — the ship parks, the + // fight waits, the ticks stand down — and restart() lands the + // destination the moment it ends (the deadlines are time-based, so + // a project that finished mid-clip completes on the first tick + // after, exactly like the ack pause's completion path). + const _frozen = + _commsPaused || this.levelMarquee?.active === true || !!this._jumping; + // The freeze's PARKING (the ack pause's onPause is the same contract + // for its own freeze): while ANY freeze holds, the action is parked + // dead EVERY frame — the ship (target + throttle cleared, velocity + // and acceleration zeroed) and the combat actors' bodies. The Arcade + // world keeps stepping through a freeze (only the drivers — + // ship.update / combat.update — stand down), so anything that still + // injects motion (a coasting body, a collision response, a body that + // was set after the freeze began) is killed the very next frame. + // Resuming a freeze needs no work (the ship stays parked; the next + // input steers it again — the ack pause's same rule). + if (_frozen) this._parkForFreeze(); // The deck goes to STANDBY while an 'ack' comm holds (the pause // contract's face): the slots dim and stop hovering/pressing, so the // pulsing ACKNOWLEDGE button is the only live control on screen. @@ -2771,6 +2790,36 @@ export class GameScene extends Phaser.Scene { if (!_frozen) this.updateDiscovery(_time, delta); // last: sees this frame's final camera view } + /** + * Park the action for a frozen frame (the level-up beat, the jump + * clip, the ack comm's pause — all three gate on _frozen in update + * above, and all three need this): the ship dead (stop() clears its + * target + throttle and zeros velocity/acceleration), and the combat + * actors' bodies zeroed too. The Arcade world still steps while the + * drivers (ship.update / combat.update) stand down, so un-parked + * bodies would keep gliding through the beat/clip — the visible + * "the action isn't actually paused" drift. Cheap + idempotent, so it + * runs every frozen frame (a velocity set mid-freeze dies next frame); + * resuming a freeze needs no work (the next input steers again). + */ + _parkForFreeze() { + try { + this.ship?.stop?.(); + } catch { + /* the ship is optional (a scene mid-setup) — the park stands or falls with it */ + } + const c = this.combat; + if (!c) return; + for (const list of [c.enemies, c.allies]) { + for (const a of list ?? []) { + const b = a?.body; + if (!b) continue; + if (typeof b.setVelocity === 'function') b.setVelocity(0, 0); + if (typeof b.setAcceleration === 'function') b.setAcceleration(0, 0); + } + } + } + /** * The ship met the tether boundary: a small camera kick (impact), and a * console warning — throttled so a held push against the line doesn't