Skip to content

sync: integrate blockcoined2 tutorial + unified cascade (ff4d798)#67

Merged
protostatis merged 2 commits into
mainfrom
sync/blockcoined2-tutorial
Jul 21, 2026
Merged

sync: integrate blockcoined2 tutorial + unified cascade (ff4d798)#67
protostatis merged 2 commits into
mainfrom
sync/blockcoined2-tutorial

Conversation

@protostatis

Copy link
Copy Markdown
Owner

Summary

Syncs the latest commit from protostatis/blockcoined2 (fix/v2-pvp-presentation branch) — an ELI5 tutorial system and unified 7-phase cascade animation.

Upstream commit: ff4d798feat: complete tutorial animation flow and unified cascade system

Changes

File Type Description
src/game/useTutorialController.js New 463-line tutorial engine — swap → match → chain → free play
src/components/TutorialScreen.jsx New Tutorial UI with step dots, instructions, score
src/game/modes.js New TUTORIAL mode constant
src/game/useWagerController.js Updated runCascade refactored to 7-phase flow
src/components/Board.jsx Updated tutorialHighlight prop for match highlighting
src/App.jsx Updated Tutorial routing + "✨ Try the Game" landing button
src/App.css Updated +169 lines: tutorial layout, step dots, instruction cards
SOURCE.md Updated Bump sync commit to ff4d798

What this adds

  • Try the Game button on the landing page (before Play vs AI)
  • 4-step tutorial: auto-demo → user match → chain explanation → free play
  • Unified 7-phase cascade: swap → match → pop → cascade → chains → cleanup → endTurn (all modes)
  • Consistent match highlighting with green pulse animation

Verification

  • npm test — 15/15 tests pass
  • npm 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.

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 protostatis left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 useTutorialController hook 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.js is a significant improvement over the old loop structure, adding explicit match validation, sound on every chain (not just point-scoring ones), and an endTurn() call. The points variable 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
protostatis merged commit 30885d8 into main Jul 21, 2026

@protostatis protostatis left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 points to totalPoints was incomplete — this is a real bug that will cause undefined returns.
  • The modes.js file 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant