From 0a5095f97342cec59babad57108674401ed4ed31 Mon Sep 17 00:00:00 2001 From: "protostatis.dev" Date: Mon, 20 Jul 2026 22:02:26 -0500 Subject: [PATCH] fix: stabilize tutorial cascade state --- .../game/src/components/TutorialScreen.jsx | 7 +++--- .../game/src/game/useTutorialController.js | 22 ++++++++++++++----- .../game/src/game/useWagerController.js | 2 -- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/dashboard-frontend/game/src/components/TutorialScreen.jsx b/dashboard-frontend/game/src/components/TutorialScreen.jsx index 6f616db..dd527c2 100644 --- a/dashboard-frontend/game/src/components/TutorialScreen.jsx +++ b/dashboard-frontend/game/src/components/TutorialScreen.jsx @@ -13,11 +13,12 @@ import { useTutorialController } from '../game/useTutorialController'; export default function TutorialScreen({ onDone, onSkip }) { const c = useTutorialController(); + const { start, reset } = c; useEffect(() => { - c.start(); - return () => c.reset(); - }, []); // eslint-disable-line + start(); + return reset; + }, [start, reset]); return (
diff --git a/dashboard-frontend/game/src/game/useTutorialController.js b/dashboard-frontend/game/src/game/useTutorialController.js index c6aef9d..6311cab 100644 --- a/dashboard-frontend/game/src/game/useTutorialController.js +++ b/dashboard-frontend/game/src/game/useTutorialController.js @@ -187,7 +187,7 @@ export function useTutorialController() { genRef.current += 1; // Restore board to pre-swap state from snapshot if (savedBoardRef.current) { - engine.cells = savedBoardRef.current; + engine.cells = savedBoardRef.current.map((c) => c.clone()); syncCells(engine); } stepRef.current = 1; @@ -317,10 +317,15 @@ export function useTutorialController() { if (hasChain) { setMessage('Chain! New gems fell in and matched again — free bonus points!'); } else { - // Try to find a board with a chain + // Search for a chain-producing move without leaving a probe board in play. + const boardBeforeSearch = engine.cells.map((c) => c.clone()); + const nextIdBeforeSearch = engine._nextId; + const scoreBeforeSearch = engine.score; let found = false; for (let attempt = 0; attempt < 10; attempt++) { engine.initBoard(); + const candidateBoard = engine.cells.map((c) => c.clone()); + const candidateNextId = engine._nextId; syncCells(engine); const moves = engine.getValidMoves(); for (const [a, b] of moves) { @@ -329,20 +334,28 @@ export function useTutorialController() { if (matches.length > 0) { const firstStep = engine.runCascadeStep(matches); if (firstStep.chainMatched.length > 0) { - engine.swapCells(a, b); // swap back + engine.cells = candidateBoard.map((c) => c.clone()); + engine._nextId = candidateNextId; + engine.score = scoreBeforeSearch; syncCells(engine); showHints(engine, [a, b]); setMessage('Try another swap — watch what happens after the gems pop!'); found = true; break; } - engine.swapCells(a, b); // swap back + engine.cells = candidateBoard.map((c) => c.clone()); + engine._nextId = candidateNextId; + engine.score = scoreBeforeSearch; } if (found) break; } if (found) break; } if (!found) { + engine.cells = boardBeforeSearch; + engine._nextId = nextIdBeforeSearch; + engine.score = scoreBeforeSearch; + syncCells(engine); setMessage('Sometimes falling gems match too — that\'s a chain combo!'); } } @@ -383,7 +396,6 @@ export function useTutorialController() { engine.initBoard(); } engineRef.current = engine; - if (typeof window !== 'undefined') window.__tutEngine = engine; setStep(0); stepRef.current = 0; diff --git a/dashboard-frontend/game/src/game/useWagerController.js b/dashboard-frontend/game/src/game/useWagerController.js index e7a8b45..2882071 100644 --- a/dashboard-frontend/game/src/game/useWagerController.js +++ b/dashboard-frontend/game/src/game/useWagerController.js @@ -277,7 +277,6 @@ export function useWagerController(opts = {}) { (async () => { const points = await runCascade(gen, engine, init); if (genRef.current !== gen) return; - engine.endTurn(); scoreRef.current[actorId] = (scoreRef.current[actorId] || 0) + (points || 0); const next = recordStreetMove(w, actorId, scoreRef.current); setW(next); @@ -335,7 +334,6 @@ export function useWagerController(opts = {}) { setPhase('swapping'); const points = await runCascade(gen, engine, init); if (genRef.current !== gen) return; - engine.endTurn(); scoreRef.current[actorId] = (scoreRef.current[actorId] || 0) + (points || 0); const next = recordStreetMove(w, actorId, scoreRef.current); setW(next);