sync: integrate blockcoined2 tutorial + unified cascade (ff4d798)#67
Conversation
Upstream changes from protostatis/blockcoined2 fix/v2-pvp-presentation: ff4d798 feat: complete tutorial animation flow and unified cascade system Sync includes: - ELI5 tutorial: swap -> match -> chain -> free play with auto-animated demo - TutorialScreen + useTutorialController (463-line tutorial engine) - Unified 7-phase cascade animation across all modes (swap -> match -> pop -> cascade -> chains -> cleanup -> endTurn) - useWagerController: runCascade refactored to 7-phase flow - Board: tutorialHighlight prop for green match-highlight - App.css: tutorial layout, step dots, instruction cards, landing CTA - App.jsx: tutorial routing + "✨ Try the Game" landing button - modes.js: TUTORIAL mode constant - SOURCE.md: bump sync commit to ff4d798 Adaptations preserved: - .jsx extensions, keyboard nav + ARIA on Board, PanicRadar shell with back links, guest-only auth, Vite build, origin allowlist, etc.
protostatis
left a comment
There was a problem hiding this comment.
Sky's Code Review
This PR integrates a 4-step ELI5 tutorial system and refactors the cascade animation into a unified 7-phase flow. The new tutorial engine, UI component, and mode constants are cleanly structured. However, there is a variable name bug in the refactored runCascade in useWagerController.js where early returns reference the old points variable instead of the new totalPoints, which would cause a ReferenceError at runtime on generation mismatch. There is also a debug leak (window.__tutEngine) in the tutorial controller and a minor stale-closure risk in the TutorialScreen useEffect.
Verdict: Changes requested
Comments
- The
useTutorialControllerhook is well-structured with generation-based cancellation to handle unmounts and race conditions. The starter board constant with documented guaranteed-swap is a nice touch. - The 7-phase cascade refactor in
useWagerController.jsis a significant improvement over the old loop structure, adding explicit match validation, sound on every chain (not just point-scoring ones), and anendTurn()call. Thepointsvariable bug is the main blocker. - The SOURCE.md sync process documentation is thorough and explicitly warns against git subtree/merge — good operational hygiene for a manual sync workflow.
Reviewed by Sky — Unchained Sky engineering agent
Inline Comments (could not attach to lines)
dashboard-frontend/game/src/game/useWagerController.js:127 — Bug: points is never declared in the new code. The early return points; statements on lines ~128, ~137, ~142, ~151, ~160 will throw a ReferenceError at runtime. Should be return totalPoints; — the refactored code accumulates into totalPoints but the early-return guard clauses still reference the old variable name.
dashboard-frontend/game/src/game/useTutorialController.js:290 — Debug leak: window.__tutEngine = engine exposes the engine globally and is never cleaned up in reset(). Remove this or clean it up in the reset callback to avoid leaking game state to the console in production.
dashboard-frontend/game/src/components/TutorialScreen.jsx:19 — The useEffect depends on c (the controller return value) but has an empty dependency array with an eslint-disable. If the controller's identity ever changes (e.g., hot module reload or parent re-mount), start() won't re-fire. Consider adding [c.start] or documenting why this is intentionally fire-once.
dashboard-frontend/game/src/game/useTutorialController.js:345 — Minor: In the step 1→2 transition, the chain-detection loop reinitializes the board up to 10 times (engine.initBoard()) without restoring the tutorial's starter board if no chain-producing move is found. The user could end up on a random board layout instead of the carefully crafted STARTER_BOARD.
protostatis
left a comment
There was a problem hiding this comment.
Sky's Code Review
This PR integrates a 4-step ELI5 tutorial system and refactors the cascade animation into a unified 7-phase flow (swap → match → pop → cascade → chains → cleanup → endTurn). The tutorial controller is well-structured with a carefully designed starter board and proper generation-based cancellation for async flows. However, the useWagerController refactor introduces a variable scoping bug (points referenced after being renamed to totalPoints) and an unconditional sound effect that previously only played on scoring moves.
Verdict: Changes requested
Comments
- The tutorial controller's generation-based cancellation pattern (genRef) is well-implemented and prevents stale async callbacks from corrupting state. The starter board design with guaranteed swap→match is clever.
- The unified 7-phase cascade in useWagerController is a significant improvement over the old loop-based approach, making the animation flow much more predictable. But the variable rename from
pointstototalPointswas incomplete — this is a real bug that will cause undefined returns. - The
modes.jsfile adds MODE constants including TUTORIAL, but TUTORIAL is only used by the tutorial controller directly — the mode system isn't wired into the tutorial flow. This is fine for now but could cause confusion if someone tries to use MODE.TUTORIAL to gate behavior. - The SOURCE.md sync process documentation is excellent — explicitly calling out 'Never use git subtree pull' and providing a clear 7-step process will save future maintainers from subtle breakage.
Reviewed by Sky — Unchained Sky engineering agent
Inline Comments (could not attach to lines)
dashboard-frontend/game/src/game/useWagerController.js:125 — The local variable points was removed and replaced with totalPoints, but early-return statements at lines 128, 131, 136, 142, 148, 154 still reference return points. These will return undefined instead of a numeric value. Replace all return points in this function with return totalPoints.
dashboard-frontend/game/src/game/useWagerController.js:141 — The old code guarded playScoreSound() with if (step.points > 0). The new code calls it unconditionally in Phase 4 before checking whether the cascade actually yields points. This means the score sound fires on every swap, including ones that produce 0-point matches (if such a state is reachable). Consider guarding with if (matched.length > 0) or deferring until points are confirmed.
dashboard-frontend/game/src/game/useWagerController.js:130 — The old code used init.matchedIndices (pre-computed by the caller). The new code re-detects matches via engine.findMatches(). Also, the swap-back branch references init.i and init.j — ensure all callers of runCascade now pass { i, j } in the init object, otherwise this will throw. This is a breaking API change if any caller omits these fields.
dashboard-frontend/game/src/game/useTutorialController.js:107 — In the Step 1→2 transition, when no chain is found, the code loops up to 10 times calling engine.initBoard() to find a board that produces a chain. This mutates the engine's board as a side effect of the loop — if no chain-producing board is found after 10 attempts, the board is left in whatever last initBoard() state it landed in, which may not match the starter board layout. Consider restoring the original board if no chain is found.
dashboard-frontend/game/src/game/useTutorialController.js:210 — In the click-during-demo handler (step 0), savedBoardRef.current is assigned directly to engine.cells without .map(c => c.clone()). Later in start(), it's stored with .clone(). The mismatch means the demo handler could share mutable Cell objects with the engine, causing subtle state corruption if the engine mutates cells in place. Use savedBoardRef.current.map(c => c.clone()) here for consistency.
dashboard-frontend/game/src/game/useTutorialController.js:408 — The useEffect runs c.start() and returns c.reset() as cleanup, but the dependency array is empty ([]). The ESLint disable comment acknowledges this, but since start and reset are stable (zero-dependency useCallback), this is safe in practice. However, the component receives onDone and onSkip as props that are never used inside the controller — they're only used in the JSX. No issue, just noting the architecture is clean.
dashboard-frontend/game/src/game/useTutorialController.js:14 — The STARTER_BOARD comment claims 'Verified: zero initial matches, 33 valid moves' but there's no runtime assertion. If the board is ever modified upstream, this could silently ship a broken tutorial. Consider adding a dev-mode assertion: if (process.env.NODE_ENV !== 'production') { engine.initBoard(); console.assert(engine.findMatches().length === 0); } or similar.
Summary
Syncs the latest commit from
protostatis/blockcoined2(fix/v2-pvp-presentationbranch) — an ELI5 tutorial system and unified 7-phase cascade animation.Upstream commit:
ff4d798—feat: complete tutorial animation flow and unified cascade systemChanges
src/game/useTutorialController.jssrc/components/TutorialScreen.jsxsrc/game/modes.jsTUTORIALmode constantsrc/game/useWagerController.jsrunCascaderefactored to 7-phase flowsrc/components/Board.jsxtutorialHighlightprop for match highlightingsrc/App.jsxsrc/App.cssSOURCE.mdff4d798What this adds
Verification
npm test— 15/15 tests passnpm run build— builds in 839ms (450 modules)Adaptations preserved
JSX extensions, keyboard navigation + ARIA on Board, PanicRadar shell with back links, guest-only auth, Vite build, origin allowlist, server hardening.