fix: stabilize tutorial cascade state#68
Conversation
protostatis
left a comment
There was a problem hiding this comment.
Sky's Code Review
This PR fixes several tutorial state bugs in a match-3 game: (1) prevents mutation of saved board snapshots by cloning on restore, (2) prevents chain-search probes from corrupting the live board by saving/restoring full engine state around each candidate, (3) removes a production debug global, (4) fixes a double endTurn bug in the wager controller, and (5) declares useEffect dependencies to avoid stale closures. The changes are well-targeted and the approach is sound.
Verdict: Approve
Comments
- The
window.__tutEnginedebug global removal is good hygiene — leaving debug globals in production builds can leak internal state and confuse DevTools users. - The chain-search pattern (save state → probe candidates → restore state) is a clean way to avoid polluting the live board. If this pattern recurs elsewhere in the codebase, consider extracting a
withBoardSnapshot(engine, fn)utility. - This is a frontend game PR, not infrastructure — the security/shell/Docker concerns from the review checklist don't apply here. Review focused on correctness and reliability of the game logic.
Reviewed by Sky — Unchained Sky engineering agent
Inline Comments (could not attach to lines)
dashboard-frontend/game/src/components/TutorialScreen.jsx:21 — Good fix: returning reset directly as the cleanup function is cleaner than the previous () => c.reset() wrapper, and declaring [start, reset] as deps eliminates the stale-closure risk from the empty-deps pattern.
dashboard-frontend/game/src/game/useTutorialController.js:337 — Restoring engine.score = scoreBeforeSearch after each candidate probe is important — without it, cascade evaluation during the probe would accumulate phantom points. Good catch.
dashboard-frontend/game/src/game/useTutorialController.js:332 — Tip: if initBoard() or runCascadeStep() have side effects beyond cells, _nextId, and score (e.g. internal counters, event listeners), those would also need snapshotting. Worth a quick audit if you haven't already.
dashboard-frontend/game/src/game/useWagerController.js:279 — Removing the duplicate engine.endTurn() is the right call if runCascade already calls it internally. However, if any future code path calls runCascade without a corresponding endTurn elsewhere, turns will silently desync. Consider adding a comment at the runCascade definition noting that it owns endTurn.
Summary
Verification
npm run lintnpm test(15 tests)npm run build