- commit
- 5c49232565d1183a48881a71d5d01e5fcd778be1
- parent
- 1a04d702cea64778065807e87bad5077f4deb07d
- author
- Dami <dami.etoile@gmail.com>
| M | openspec/ | 4 | ++++ |
| M | src/ | 16 | +++++++++++++--- |
@@ -23,6 +23,10 @@ The stage SHALL support Title, INDEX, Proof, Pause, Win card, Workshop, and pend
2323 - **WHEN** the player presses Enter before the Win card has settled2424 - **THEN** the entire card reaches its final legible state; a later Enter exits to INDEX2525 26+#### Scenario: An authored proof is already won on entry27+- **WHEN** the player enters an initially won proof from INDEX while its exit animation is in progress28+- **THEN** the proof handoff completes before the Win card opens for that same proof29+2630 #### Scenario: Reduced motion uses settled scenes2731 - **WHEN** reduced motion is requested2832 - **THEN** Pause and Win controls and text are immediately visible in their final positions without waiting for choreography@@ -204,13 +204,23 @@ function scheduleInitialWin() {
204204 presentationClock.clearTimeout(winTimer);205205 if (!game?.getState().won) return;206206 const level = currentLevels()[levelNo];207+ const expectedGame = game;208+ const expectedLevelId = level?.id;207209 if (level?.packId && level?.localId) {208210 progress.markSolved(level.packId, level.localId);209211 refreshIndex(level.id);210212 }211- winTimer = presentationClock.setTimeout(() => {212- if (game?.getState().won && stage.scene === 'proof') stage.show('win', { steps: game.getState().moves, nodeId: level?.id, instant: true });213- }, 0);213+ const promote = () => {214+ if (game !== expectedGame || currentLevels()[levelNo]?.id !== expectedLevelId || !game?.getState().won) return;215+ if (stage.scene === 'proof') {216+ stage.show('win', { steps: game.getState().moves, nodeId: expectedLevelId, instant: true });217+ return;218+ }219+ // Index → proof has a 280ms scene exit. Retry after that visual handoff;220+ // the timer is presentation-clock based, so slowmo timing is preserved.221+ if (stage.scene === 'index-exiting') winTimer = presentationClock.setTimeout(promote, 280);222+ };223+ winTimer = presentationClock.setTimeout(promote, 0);214224 }215225 function paintBoardFrame(now = presentationClock.now()) { stage.setBoard(displayState(),game,activePalette(),glyphs,juice.frame(now)); }216226 function animateBoard(duration = 650) { animationDeadline = Math.max(animationDeadline,presentationClock.now()+duration); if (animationRequest) return; const tick = now => { animationRequest=0; if (stage.scene === 'proof' || stage.scene === 'win') paintBoardFrame(now); if (now < animationDeadline) animationRequest=presentationClock.requestAnimationFrame(tick); }; animationRequest=presentationClock.requestAnimationFrame(tick); }