Visitar URL original
refactor(games): migrate 9 legacy games to BaseGame framework by cwchanap · Pull Request #42 · cwchanap/cetus · GitHub
Skip to content

refactor(games): migrate 9 legacy games to BaseGame framework - #42

Merged
cwchanap merged 23 commits into
mainfrom
refactor/games-basegame-migration
Jul 6, 2026
Merged

cwchanap merged 23 commits into
mainfrom
refactor/games-basegame-migration

Conversation

@cwchanap

@cwchanap cwchanap commented Jul 5, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Migrates the 9 legacy-classic games onto the core framework (BaseGame + GameInitializer + ScoreManager + GameTimer + renderer subclass): Tetris, Bubble Shooter, 2048, Memory Matrix, Word Scramble, Reflex, Path Navigator, Evader, and Sudoku.
  • After this PR, 12 of 14 games are framework-native; Circuit Hacker and Satellite Sync remain on the blessed handle-based pattern.
  • Includes additional fixes discovered during migration:
    • Reflex: corrected click effects, state reset, animation, and accuracy test; removed needsRedraw guard for per-frame animation.
    • Memory Matrix: emit state change before end() on win so overlays render correctly.
    • Sudoku: increased uniqueness test timeout for full-suite CPU contention.
  • Documents the handle-based pattern in AGENTS.md and adds the scoped PR 2 plan.

Test plan

  • bun run test:run — 106 test files, 2191 tests passed
  • bun run lint — 0 errors (804 pre-existing warnings)
  • bun run format:check — clean
  • bun run build — succeeds
  • E2E tests (bun run test:e2e) — not run in this environment

Generated with Devin

Summary by CodeRabbit

  • New Features
    • Rolled out framework-based versions of 2048, Bubble Shooter, Evader, and Memory Matrix, including externalized Pixi rendering, updated gameplay/scoring, and richer UI status handling.
    • Added support for challenge-completion notifications alongside achievement awards.
  • Bug Fixes
    • Improved end-of-game behavior with more reliable score submission and safer rendering/cleanup (defensive no-ops on invalid state inputs).
  • Documentation
    • Updated architecture notes and added the next migration-phase plan for framework adoption.

cwchanap and others added 15 commits July 4, 2026 17:56
- Create SudokuGame.ts extending BaseGame (DOM-based, like Memory Matrix)
- Create SudokuRenderer.ts (DOMRenderer) for grid rendering
- Create initFramework.ts with full button/keyboard/difficulty wiring
- Create frameworkTypes.ts for BaseGame-integrated state/config/stats
- Consolidate two saveGameScore paths into single BaseGame.end() call
- Preserve Task 3.7 GameData contract: { difficulty, cellsFilled, hintsUsed }
- Preserve Task 1.7 uniqueness check (utils.ts unchanged)
- Preserve Task 1.6 stopGame behavior via BaseGame lifecycle
- Use duration: MAX_SAFE_INTEGER (like Tetris) for count-up elapsed timer
- Port tests to SudokuGame.test.ts (40) and initFramework.test.ts (16)
- Delete legacy game.ts, init.ts, and their tests
- Simplify index.astro to call initSudokuGameFramework
Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@vercel

vercel Bot commented Jul 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cetus Ready Ready Preview, Comment Jul 6, 2026 1:29am

@coderabbitai

coderabbitai Bot commented Jul 5, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cwchanap, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0c2cf745-52d9-4de6-8634-468fc209dbad

📥 Commits

Reviewing files that changed from the base of the PR and between 87a497f and a6d330d.

📒 Files selected for processing (2)
  • src/lib/games/word-scramble/WordScrambleGame.test.ts
  • src/lib/games/word-scramble/WordScrambleGame.ts
📝 Walkthrough

Walkthrough

This PR migrates 2048, Bubble Shooter, Evader, and Memory Matrix onto BaseGame, adds new framework-based game/renderer/init modules and tests, removes the legacy modules, and updates shared end-event handling for challenge updates. It also adds migration documentation and an architecture note.

Changes

Shared docs and challenge-update plumbing

Layer / File(s) Summary
Migration plan and architecture note
CLAUDE.md, docs/superpowers/plans/2026-07-01-games-refactoring-pr2.md
Adds a framework-vs-handle-based architecture note and a Phase 6 migration plan with tasks, constraints, and verification steps.
Challenge update payload flow
src/lib/games/core/*, src/lib/games/bejeweled/init.ts
Adds ChallengeUpdates to score/end-event payloads and forwards completed-challenge updates to global UI handlers.

2048 Framework Migration

Layer / File(s) Summary
2048 types and core game
src/lib/games/2048/frameworkTypes.ts, src/lib/games/2048/Game2048.ts, src/lib/games/2048/Game2048.test.ts
Defines framework state/config/stats types and adds the Game2048 class with move, merge, spawn, win, and score/reporting logic plus tests.
2048 PixiJS renderer
src/lib/games/2048/Game2048Renderer.ts, src/lib/games/2048/Game2048Renderer.test.ts
Adds the PixiJS renderer, tile animations, config mapping, and renderer tests.
2048 framework init and controls
src/lib/games/2048/initFramework.ts, src/lib/games/2048/initFramework.test.ts
Adds DOM wiring, keyboard/touch controls, render-loop orchestration, end handling, and tests for the framework bootstrapper.
Removed legacy 2048 modules
src/lib/games/2048/game.ts, src/lib/games/2048/game.test.ts, src/lib/games/2048/renderer.ts, src/lib/games/2048/renderer.test.ts, src/lib/games/2048/init.ts, src/lib/games/2048/init.test.ts
Deletes the old standalone 2048 game, renderer, init modules, and their test suites.

Bubble Shooter Framework Migration

Layer / File(s) Summary
Bubble Shooter types and core game
src/lib/games/bubble-shooter/types.ts, src/lib/games/bubble-shooter/BubbleShooterGame.ts, src/lib/games/bubble-shooter/BubbleShooterGame.test.ts
Defines framework state/config/stats types and adds the BubbleShooterGame class with projectile, matching, scoring, row insertion, and game-over logic plus tests.
Bubble Shooter PixiJS renderer
src/lib/games/bubble-shooter/BubbleShooterRenderer.ts, src/lib/games/bubble-shooter/BubbleShooterRenderer.test.ts
Adds the PixiJS renderer for the grid, aim line, and projectile, plus renderer config mapping and tests.
Bubble Shooter framework init and controls
src/lib/games/bubble-shooter/initFramework.ts, src/lib/games/bubble-shooter/initFramework.test.ts
Adds DOM previews, controls, achievement forwarding, render-loop orchestration, and init tests for the framework bootstrapper.
Removed legacy Bubble Shooter modules
src/lib/games/bubble-shooter/game.ts, src/lib/games/bubble-shooter/game.pure.test.ts, src/lib/games/bubble-shooter/game.test.ts, src/lib/games/bubble-shooter/physics.ts, src/lib/games/bubble-shooter/physics.test.ts, src/lib/games/bubble-shooter/renderer.ts, src/lib/games/bubble-shooter/renderer.test.ts, src/lib/games/bubble-shooter/init.ts, src/lib/games/bubble-shooter/init.test.ts
Deletes the old Bubble Shooter game, physics, renderer, init modules, and their test suites.

Evader Framework Migration

Layer / File(s) Summary
Evader types and core game
src/lib/games/evader/frameworkTypes.ts, src/lib/games/evader/types.ts, src/lib/games/evader/EvaderGame.ts, src/lib/games/evader/EvaderGame.test.ts
Defines framework state/config/stats types, updates shared spawn typing, and adds the EvaderGame class with countdown, movement, collision, spawn-timer, and stats logic plus tests.
Evader PixiJS renderer
src/lib/games/evader/EvaderRenderer.ts, src/lib/games/evader/EvaderRenderer.test.ts
Adds the PixiJS renderer for the board, player, and objects, plus renderer config mapping and tests.
Evader framework init and controls
src/lib/games/evader/initFramework.ts, src/lib/games/evader/initFramework.test.ts
Adds DOM wiring, keyboard controls, achievements, render-loop orchestration, and tests for the framework bootstrapper and game-over UI.
Removed legacy Evader modules
src/lib/games/evader/game.ts, src/lib/games/evader/renderer.ts, src/lib/games/evader/init.ts, and removed legacy test files
Deletes the old Evader game, renderer, and init modules and their tests.

Memory Matrix Framework Migration

Layer / File(s) Summary
Memory Matrix types and core game
src/lib/games/memory-matrix/frameworkTypes.ts, src/lib/games/memory-matrix/MemoryMatrixGame.ts, src/lib/games/memory-matrix/MemoryMatrixGame.test.ts
Defines framework state/config/stats types and adds the MemoryMatrixGame class with card flip, match resolution, scoring, win handling, and tests.
Memory Matrix DOM renderer
src/lib/games/memory-matrix/MemoryMatrixRenderer.ts, src/lib/games/memory-matrix/MemoryMatrixRenderer.test.ts
Adds the DOM renderer for the board and live stats, plus renderer config mapping and tests.
Removed legacy Memory Matrix module
src/lib/games/memory-matrix/game.ts, src/lib/games/memory-matrix/game.test.ts
Deletes the old Memory Matrix game module and its test suite.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant InitFramework
  participant Game
  participant Renderer
  participant ScoreManager
  participant Window
  InitFramework->>Renderer: initialize()
  InitFramework->>Game: create game instance
  Game->>Game: update state from input
  Game->>InitFramework: emit state change
  InitFramework->>Renderer: render(state)
  Game->>ScoreManager: end() / saveFinalScore()
  ScoreManager->>Window: challenge/achievement payloads
Loading

Possibly related PRs

  • cwchanap/cetus#10: Also replaces the earlier 2048 implementation with the new framework-based 2048 game, renderer, and init wiring.
  • cwchanap/cetus#14: Also threads challengeUpdates through shared end-event and score-saving code.
  • cwchanap/cetus#39: Also updates BaseGame.end and score-submission paths to handle stale runs and challenge-update dispatch.

Suggested labels: codex

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: migrating legacy games to the BaseGame framework.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/games-basegame-migration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 5, 2026 •

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 17

🧹 Nitpick comments (4)
src/lib/games/memory-matrix/MemoryMatrixRenderer.test.ts (1)

193-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider asserting computed background/border color, not just className.

These tests confirm the Tailwind class is present but don't verify the actual rendered background/border color, which is why the inline-style override bug in MemoryMatrixRenderer.ts (matched/flipped cards) wasn't caught. Adding a getComputedStyle/el.style.backgroundColor assertion would close that gap once the renderer fix lands.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/games/memory-matrix/MemoryMatrixRenderer.test.ts` around lines 193 -
215, The matched/flipped card tests in MemoryMatrixRenderer.test are only
checking className, so they miss inline-style overrides from
MemoryMatrixRenderer. Update the assertions in the relevant renderer test cases
to verify the actual rendered background/border styling (for example via
computed style or inline style on the card element) in addition to, or instead
of, Tailwind classes. Use the existing createRenderer, makeState, and
boardEl/cardEl setup to locate the affected checks.
src/lib/games/memory-matrix/MemoryMatrixRenderer.ts (1)

78-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No keyboard accessibility for card interaction.

Cards are only clickable via mouse/touch click listener; there's no tabIndex, role="button", keydown handler, or focus-visible styling, so keyboard-only users can't play.

Based on learnings, "Apply the sci-fi theme consistently... with proper hover/focus accessibility states."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/games/memory-matrix/MemoryMatrixRenderer.ts` around lines 78 - 82,
The card interaction in MemoryMatrixRenderer is mouse-only, so update the card
element setup to support keyboard access as well. In the render logic where the
card click listener is attached, add appropriate semantics and focusability to
the card div, wire a keydown handler in addition to the existing click handler,
and ensure the same onCardClick path is triggered from keyboard activation. Also
add focus-visible styling alongside the existing hover treatment so the card
interaction is accessible and matches the sci-fi theme consistently.

Source: Learnings

src/lib/games/evader/initFramework.ts (1)

164-197: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Unbounded deltaTime in the render loop.

deltaTime is derived from real elapsed wall-clock time with no cap. If the tab is backgrounded/throttled or a long pause occurs, the next frame's deltaTime could be very large, causing a single oversized update() step (player/objects jumping large distances, skipped collisions).

⏱️ Proposed fix
         const renderLoop = () => {
             const now = Date.now()
-            const deltaTime = (now - lastUpdateTime) / 1000
+            const deltaTime = Math.min((now - lastUpdateTime) / 1000, 0.1)
             lastUpdateTime = now
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/games/evader/initFramework.ts` around lines 164 - 197, The render
loop in initFramework’s startRenderLoop currently passes an unbounded deltaTime
into game.update, which can spike after tab throttling or long pauses. Clamp or
cap the computed deltaTime before calling game.update so the physics step stays
stable, and keep the change localized to the renderLoop logic using the existing
lastUpdateTime and game.update flow.
src/lib/games/evader/EvaderRenderer.ts (1)

108-126: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Player rendering breaks the sci-fi neon theme used elsewhere.

Coins and bombs use PIXI.FillGradient for a glowing effect, but the player renders as a flat solid-color square. Consider a matching gradient/glow for visual consistency.

✨ Proposed fix
     private renderPlayer(player: Player): void {
         if (!this.playerGraphic) {
             return
         }

         this.playerGraphic.clear()

         const playerWidth = this.evaderConfig.playerSize
         const playerHeight = this.evaderConfig.playerSize
+        const playerGradient = new PIXI.FillGradient(
+            0,
+            -playerHeight / 2,
+            0,
+            playerHeight / 2
+        )
+        playerGradient.addColorStop(0, 0x00ffcc)
+        playerGradient.addColorStop(0.5, 0x00ff88)
+        playerGradient.addColorStop(1, 0x00cc66)
         this.playerGraphic
             .rect(
                 player.x - playerWidth / 2,
                 player.y - playerHeight / 2,
                 playerWidth,
                 playerHeight
             )
-            .fill(0x00ff00) // Green for player
-            .stroke({ color: 0x00ff00, width: 2 })
+            .fill(playerGradient)
+            .stroke({ color: 0x00ffcc, width: 2 })
     }

Based on learnings, "Apply the sci-fi theme consistently, including neon/glass/holographic styling and responsive mobile-first behavior with proper hover/focus accessibility states."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/games/evader/EvaderRenderer.ts` around lines 108 - 126, The player in
EvaderRenderer.renderPlayer is still drawn as a flat green square, unlike the
neon/glow styling used for coins and bombs. Update renderPlayer to use the same
sci-fi visual treatment as the other entities, ideally by applying a
PIXI.FillGradient or similar glow effect with matching stroke styling. Keep the
existing playerGraphic clear/rect flow, but swap the solid fill for the themed
gradient so the player matches the rest of the game’s holographic look.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/games/2048/Game2048.ts`:
- Around line 208-210: The game-over branch in Game2048 currently fires
`this.end()` without handling failures, which can lead to an unhandled
rejection. Update the `canMove(this.state.board)` path in `Game2048` to await or
otherwise handle the async `end()` call with proper error handling, and make
sure any rejection from score saving or end hooks is caught and logged/processed
consistently with the rest of the class.
- Around line 185-213: `Game2048.move()` returns a stale board because
`result.board` is still the pre-spawn value from `computeMove()` while
`this.state.board` is updated after `spawnTile()`. Update `move()` so the
returned result reflects the post-spawn board, keeping `result.board` in sync
with the final `this.state.board` before `emitStateChange()` and the game-over
check, using the existing `computeMove`/`spawnTile` flow to locate the change.

In `@src/lib/games/2048/Game2048Renderer.ts`:
- Around line 111-131: Destroy the previously rendered Pixi children when
redrawing the 2048 board, because drawBoard() and drawTiles() recreate Graphics,
Container, and Text objects every render. Update Game2048Renderer so the
existing boardContainer children are removed and destroyed before new ones are
added, and make sure the same cleanup is applied to any tile containers created
in drawTiles() to prevent resource buildup during long sessions.

In `@src/lib/games/2048/initFramework.test.ts`:
- Around line 228-243: The pre-start guard test is using a second
init2048GameFramework instance while the beforeEach game is still active, so the
keydown can be handled by the wrong listener and leak animation work. In the
“does not move before the game starts” test, clean up the already-started game
from beforeEach before creating fresh, then attach the move spy to that fresh
instance and dispatch the event against the correct document listener. Use
init2048GameFramework, move, and cleanup to locate the affected test setup.

In `@src/lib/games/2048/initFramework.ts`:
- Line 193: The touch swipe sensitivity is still hardcoded in the 2048 init
flow, so callers cannot use the merged swipe threshold from customConfig. Update
the touch handling path in initFramework and setupTouchControls to read the
configured swipeThreshold from config instead of GAME_CONSTANTS.SWIPE_THRESHOLD,
and thread that value through any helper used for touch movement detection. Make
sure the logic in handleMove/setupTouchControls uses the same config source
everywhere so custom swipe tuning takes effect.
- Around line 320-322: The End button handler in initFramework currently fires
game.end() without handling failures, which can lead to unhandled promise
rejections. Update endHandler to catch errors from the async game.end() call and
route them through the existing error handling/logging path used in this flow,
keeping the handler safe when the user ends the game.

In `@src/lib/games/bubble-shooter/BubbleShooterGame.ts`:
- Around line 426-429: The row-add logic in BubbleShooterGame should skip adding
a new row when the current shot triggers an all-clear. Update the shot handling
in attachBubble() and the matching flow in checkMatches() so the all-clear state
is detected before the rowAddInterval check runs, and gate this.state.shotCount
% this.config.rowAddInterval accordingly. Use the existing BubbleShooterGame
methods attachBubble(), checkMatches(), and addNewRow() to ensure the board
stays empty after the bonus is awarded.
- Around line 435-436: The game-over branch in BubbleShooterGame should not drop
the promise returned by end(), since this can surface as unhandled rejections.
Update the checkGameOverCondition path in BubbleShooterGame to await or
otherwise handle the async end() call with explicit error handling, using the
end() method and the surrounding game-over logic as the anchor points.
- Line 153: The displayed accuracy in BubbleShooterGame is using bubblesPopped /
shotsFired, which can exceed 100% because a single shot may pop multiple
bubbles. Update the accuracy calculation in BubbleShooterGame so it is capped at
100% (or switch to a true hit-rate based on successful shots) before it is
exposed to the UI, keeping the logic localized around the
shotsFired/bubblesPopped stats.

In `@src/lib/games/bubble-shooter/initFramework.ts`:
- Around line 450-476: Update the input handling in initFramework by replacing
the canvas mouse listeners with pointer events so aiming and shooting work on
touch devices as well as mouse devices. Refactor mouseMoveHandler and
clickHandler wiring to use pointermove and pointerdown on the canvas, and ensure
the canvas disables default touch gestures (for example via touch-action
handling) so pointer input is not blocked. Keep the existing aim-angle logic and
game.shoot() behavior, just adapt the event types and canvas interaction setup
for mobile compatibility.
- Around line 345-347: The button handler in startHandler/endHandler is dropping
the async game.end() promise, so update the handler logic to await or chain the
promise and handle any rejection. Use the existing startHandler and endHandler
functions in initFramework to add proper error handling around game.end(),
matching the async safety pattern used elsewhere in the codebase.

In `@src/lib/games/evader/EvaderGame.test.ts`:
- Around line 392-408: The test in EvaderGame.test should await the async
lifecycle shutdown before resetting and restarting, since game.end() may still
be doing cleanup when the next game begins. Update the stuck-keys test to use
the same awaited pattern as the other lifecycle tests around game.end(), so the
reset()/start() sequence only runs after the previous game is fully finished.

In `@src/lib/games/evader/EvaderRenderer.test.ts`:
- Around line 162-174: The test description in EvaderRenderer.test.ts does not
match its expectation: the case named by the EvaderRenderer render-after-cleanup
scenario asserts that render is a no-op instead of throwing. Rename the test to
reflect the actual behavior exercised by initialize(), cleanup(), and render(),
so the title clearly indicates render does not throw when the app is unavailable
after cleanup.

In `@src/lib/games/evader/initFramework.ts`:
- Around line 327-352: `setupKeyboardControls` only wires keyboard events, so
Evader remains unusable on touch-only devices. Extend this input setup in
`initFramework.ts` with a touch/pointer (or mouse) movement handler that
translates directional gestures/presses into the same movement input path used
by `game.pressKey` and `game.releaseKey`. Keep the existing
`handleKeyDown`/`handleKeyUp` behavior intact, and ensure the cleanup function
removes any new listeners added for mobile-friendly controls.

In `@src/lib/games/memory-matrix/MemoryMatrixGame.test.ts`:
- Around line 130-142: Add regression coverage in MemoryMatrixGame.test for
calling reset() while two cards are face-up but before FLIP_DELAY completes,
since this is the stale-timeout crash path in MemoryMatrixGame. Extend the
existing reset test or add a new one that flips two cards, calls game.reset()
before the timeout fires, then advances timers and asserts reset() does not
throw, the state remains reset, and no delayed match/score changes occur after
FLIP_DELAY. Refer to game.start(), game.reset(), getState(), and the flip-delay
behavior in MemoryMatrixGame to keep the test targeted.

In `@src/lib/games/memory-matrix/MemoryMatrixGame.ts`:
- Line 39: `onGameReset()` in `MemoryMatrixGame` should also clear any pending
match-check timer, since `matchTimeoutId` can survive a reset and later run
against the new state. Update the reset flow to invoke `cleanup()` from
`onGameReset()` so the delayed `cardsMatch` callback is canceled before it can
access stale `flippedCards` and throw.

In `@src/lib/games/memory-matrix/MemoryMatrixRenderer.ts`:
- Around line 58-95: In createCardElement, remove the unconditional inline
backgroundColor and borderColor assignment for cards that are flipped or
matched, because it overrides the Tailwind state classes returned by
getCardClasses. Keep styling driven by the class list so matched cards use the
green theme and flipped cards use the cyan/slate theme consistently. If
card.color is still needed, apply it only in a way that does not override the
state-based classes, and verify the rendered styles in the MemoryMatrixRenderer
flow.

---

Nitpick comments:
In `@src/lib/games/evader/EvaderRenderer.ts`:
- Around line 108-126: The player in EvaderRenderer.renderPlayer is still drawn
as a flat green square, unlike the neon/glow styling used for coins and bombs.
Update renderPlayer to use the same sci-fi visual treatment as the other
entities, ideally by applying a PIXI.FillGradient or similar glow effect with
matching stroke styling. Keep the existing playerGraphic clear/rect flow, but
swap the solid fill for the themed gradient so the player matches the rest of
the game’s holographic look.

In `@src/lib/games/evader/initFramework.ts`:
- Around line 164-197: The render loop in initFramework’s startRenderLoop
currently passes an unbounded deltaTime into game.update, which can spike after
tab throttling or long pauses. Clamp or cap the computed deltaTime before
calling game.update so the physics step stays stable, and keep the change
localized to the renderLoop logic using the existing lastUpdateTime and
game.update flow.

In `@src/lib/games/memory-matrix/MemoryMatrixRenderer.test.ts`:
- Around line 193-215: The matched/flipped card tests in
MemoryMatrixRenderer.test are only checking className, so they miss inline-style
overrides from MemoryMatrixRenderer. Update the assertions in the relevant
renderer test cases to verify the actual rendered background/border styling (for
example via computed style or inline style on the card element) in addition to,
or instead of, Tailwind classes. Use the existing createRenderer, makeState, and
boardEl/cardEl setup to locate the affected checks.

In `@src/lib/games/memory-matrix/MemoryMatrixRenderer.ts`:
- Around line 78-82: The card interaction in MemoryMatrixRenderer is mouse-only,
so update the card element setup to support keyboard access as well. In the
render logic where the card click listener is attached, add appropriate
semantics and focusability to the card div, wire a keydown handler in addition
to the existing click handler, and ensure the same onCardClick path is triggered
from keyboard activation. Also add focus-visible styling alongside the existing
hover treatment so the card interaction is accessible and matches the sci-fi
theme consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a990bac3-12ec-4f5e-887f-4615a5d9b3c7

📥 Commits

Reviewing files that changed from the base of the PR and between d4e9991 and 86fb8b4.

📒 Files selected for processing (125)
  • CLAUDE.md
  • docs/superpowers/plans/2026-07-01-games-refactoring-pr2.md
  • src/lib/games/2048/Game2048.test.ts
  • src/lib/games/2048/Game2048.ts
  • src/lib/games/2048/Game2048Renderer.test.ts
  • src/lib/games/2048/Game2048Renderer.ts
  • src/lib/games/2048/frameworkTypes.ts
  • src/lib/games/2048/game.test.ts
  • src/lib/games/2048/game.ts
  • src/lib/games/2048/init.test.ts
  • src/lib/games/2048/init.ts
  • src/lib/games/2048/initFramework.test.ts
  • src/lib/games/2048/initFramework.ts
  • src/lib/games/2048/renderer.test.ts
  • src/lib/games/2048/renderer.ts
  • src/lib/games/bubble-shooter/BubbleShooterGame.test.ts
  • src/lib/games/bubble-shooter/BubbleShooterGame.ts
  • src/lib/games/bubble-shooter/BubbleShooterRenderer.test.ts
  • src/lib/games/bubble-shooter/BubbleShooterRenderer.ts
  • src/lib/games/bubble-shooter/game.pure.test.ts
  • src/lib/games/bubble-shooter/game.test.ts
  • src/lib/games/bubble-shooter/game.ts
  • src/lib/games/bubble-shooter/init.test.ts
  • src/lib/games/bubble-shooter/init.ts
  • src/lib/games/bubble-shooter/initFramework.test.ts
  • src/lib/games/bubble-shooter/initFramework.ts
  • src/lib/games/bubble-shooter/physics.test.ts
  • src/lib/games/bubble-shooter/physics.ts
  • src/lib/games/bubble-shooter/renderer.test.ts
  • src/lib/games/bubble-shooter/renderer.ts
  • src/lib/games/bubble-shooter/types.ts
  • src/lib/games/evader/EvaderGame.test.ts
  • src/lib/games/evader/EvaderGame.ts
  • src/lib/games/evader/EvaderRenderer.test.ts
  • src/lib/games/evader/EvaderRenderer.ts
  • src/lib/games/evader/frameworkTypes.ts
  • src/lib/games/evader/game.ts
  • src/lib/games/evader/init.test.ts
  • src/lib/games/evader/init.ts
  • src/lib/games/evader/initFramework.test.ts
  • src/lib/games/evader/initFramework.ts
  • src/lib/games/evader/renderer.test.ts
  • src/lib/games/evader/renderer.ts
  • src/lib/games/evader/types.ts
  • src/lib/games/memory-matrix/MemoryMatrixGame.test.ts
  • src/lib/games/memory-matrix/MemoryMatrixGame.ts
  • src/lib/games/memory-matrix/MemoryMatrixRenderer.test.ts
  • src/lib/games/memory-matrix/MemoryMatrixRenderer.ts
  • src/lib/games/memory-matrix/frameworkTypes.ts
  • src/lib/games/memory-matrix/game.test.ts
  • src/lib/games/memory-matrix/game.ts
  • src/lib/games/memory-matrix/init.test.ts
  • src/lib/games/memory-matrix/init.ts
  • src/lib/games/memory-matrix/initFramework.test.ts
  • src/lib/games/memory-matrix/initFramework.ts
  • src/lib/games/memory-matrix/renderer.test.ts
  • src/lib/games/memory-matrix/renderer.ts
  • src/lib/games/memory-matrix/test.ts
  • src/lib/games/memory-matrix/utils.ts
  • src/lib/games/path-navigator/PathNavigatorGame.test.ts
  • src/lib/games/path-navigator/PathNavigatorGame.ts
  • src/lib/games/path-navigator/PathNavigatorRenderer.test.ts
  • src/lib/games/path-navigator/PathNavigatorRenderer.ts
  • src/lib/games/path-navigator/init.test.ts
  • src/lib/games/path-navigator/init.ts
  • src/lib/games/path-navigator/initFramework.test.ts
  • src/lib/games/path-navigator/initFramework.ts
  • src/lib/games/path-navigator/renderer.test.ts
  • src/lib/games/path-navigator/renderer.ts
  • src/lib/games/path-navigator/types.ts
  • src/lib/games/reflex/ReflexGame.test.ts
  • src/lib/games/reflex/ReflexGame.ts
  • src/lib/games/reflex/ReflexRenderer.test.ts
  • src/lib/games/reflex/ReflexRenderer.ts
  • src/lib/games/reflex/frameworkTypes.ts
  • src/lib/games/reflex/game.test.ts
  • src/lib/games/reflex/game.ts
  • src/lib/games/reflex/init.test.ts
  • src/lib/games/reflex/init.ts
  • src/lib/games/reflex/initFramework.test.ts
  • src/lib/games/reflex/initFramework.ts
  • src/lib/games/reflex/renderer.test.ts
  • src/lib/games/reflex/renderer.ts
  • src/lib/games/reflex/types.ts
  • src/lib/games/sudoku/SudokuGame.test.ts
  • src/lib/games/sudoku/SudokuGame.ts
  • src/lib/games/sudoku/SudokuRenderer.ts
  • src/lib/games/sudoku/frameworkTypes.ts
  • src/lib/games/sudoku/game.test.ts
  • src/lib/games/sudoku/game.ts
  • src/lib/games/sudoku/init.test.ts
  • src/lib/games/sudoku/init.ts
  • src/lib/games/sudoku/initFramework.test.ts
  • src/lib/games/sudoku/initFramework.ts
  • src/lib/games/sudoku/types.ts
  • src/lib/games/sudoku/utils.test.ts
  • src/lib/games/tetris/TetrisGame.test.ts
  • src/lib/games/tetris/TetrisGame.ts
  • src/lib/games/tetris/TetrisRenderer.ts
  • src/lib/games/tetris/game.test.ts
  • src/lib/games/tetris/game.ts
  • src/lib/games/tetris/init.test.ts
  • src/lib/games/tetris/init.ts
  • src/lib/games/tetris/initFramework.ts
  • src/lib/games/tetris/renderer.test.ts
  • src/lib/games/tetris/renderer.ts
  • src/lib/games/tetris/types.ts
  • src/lib/games/tetris/utils.test.ts
  • src/lib/games/word-scramble/WordScrambleGame.test.ts
  • src/lib/games/word-scramble/WordScrambleGame.ts
  • src/lib/games/word-scramble/frameworkTypes.ts
  • src/lib/games/word-scramble/init.test.ts
  • src/lib/games/word-scramble/init.ts
  • src/lib/games/word-scramble/initFramework.test.ts
  • src/lib/games/word-scramble/initFramework.ts
  • src/lib/games/word-scramble/types.ts
  • src/pages/2048/index.astro
  • src/pages/bubble-shooter/index.astro
  • src/pages/evader/index.astro
  • src/pages/memory-matrix/index.astro
  • src/pages/path-navigator/index.astro
  • src/pages/reflex/index.astro
  • src/pages/sudoku/index.astro
  • src/pages/tetris/index.astro
  • src/pages/word-scramble/index.astro
💤 Files with no reviewable changes (22)
  • src/lib/games/bubble-shooter/physics.test.ts
  • src/lib/games/bubble-shooter/init.test.ts
  • src/lib/games/memory-matrix/game.ts
  • src/lib/games/memory-matrix/game.test.ts
  • src/lib/games/2048/game.test.ts
  • src/lib/games/bubble-shooter/physics.ts
  • src/lib/games/evader/game.ts
  • src/lib/games/evader/renderer.test.ts
  • src/lib/games/bubble-shooter/game.test.ts
  • src/lib/games/bubble-shooter/game.pure.test.ts
  • src/lib/games/evader/renderer.ts
  • src/lib/games/2048/renderer.ts
  • src/lib/games/2048/renderer.test.ts
  • src/lib/games/bubble-shooter/game.ts
  • src/lib/games/2048/init.ts
  • src/lib/games/2048/game.ts
  • src/lib/games/2048/init.test.ts
  • src/lib/games/evader/init.ts
  • src/lib/games/bubble-shooter/renderer.ts
  • src/lib/games/bubble-shooter/init.ts
  • src/lib/games/bubble-shooter/renderer.test.ts
  • src/lib/games/evader/init.test.ts

Comment thread src/lib/games/2048/Game2048.ts Resolved
Comment thread src/lib/games/2048/Game2048.ts Resolved
Comment thread src/lib/games/2048/Game2048Renderer.ts Resolved
Comment on lines +228 to +243
it('does not move before the game starts', async () => {
const fresh = await init2048GameFramework()
const moveSpy = vi
.spyOn(fresh!.game, 'move')
.mockReturnValue(NO_MOVE_RESULT)

document.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'ArrowUp',
bubbles: true,
})
)

expect(moveSpy).not.toHaveBeenCalled()
fresh!.cleanup()
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Clean up the started game before testing the pre-start guard.

This test creates a second initializer while the beforeEach game is already started and still listening on document, so the key event can be handled by the wrong game and leak async animation work.

🧪 Proposed fix
         it('does not move before the game starts', async () => {
-            const fresh = await init2048GameFramework()
+            result!.cleanup()
+            result = await init2048GameFramework()
             const moveSpy = vi
-                .spyOn(fresh!.game, 'move')
+                .spyOn(result!.game, 'move')
                 .mockReturnValue(NO_MOVE_RESULT)

             document.dispatchEvent(
@@
             )

             expect(moveSpy).not.toHaveBeenCalled()
-            fresh!.cleanup()
         })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('does not move before the game starts', async () => {
const fresh = await init2048GameFramework()
const moveSpy = vi
.spyOn(fresh!.game, 'move')
.mockReturnValue(NO_MOVE_RESULT)
document.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'ArrowUp',
bubbles: true,
})
)
expect(moveSpy).not.toHaveBeenCalled()
fresh!.cleanup()
})
it('does not move before the game starts', async () => {
result!.cleanup()
result = await init2048GameFramework()
const moveSpy = vi
.spyOn(result!.game, 'move')
.mockReturnValue(NO_MOVE_RESULT)
document.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'ArrowUp',
bubbles: true,
})
)
expect(moveSpy).not.toHaveBeenCalled()
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/games/2048/initFramework.test.ts` around lines 228 - 243, The
pre-start guard test is using a second init2048GameFramework instance while the
beforeEach game is still active, so the keydown can be handled by the wrong
listener and leak animation work. In the “does not move before the game starts”
test, clean up the already-started game from beforeEach before creating fresh,
then attach the move spy to that fresh instance and dispatch the event against
the correct document listener. Use init2048GameFramework, move, and cleanup to
locate the affected test setup.

Comment thread src/lib/games/2048/initFramework.ts Resolved
Comment on lines +327 to +352
function setupKeyboardControls(game: EvaderGame): () => void {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.ctrlKey || event.metaKey || event.altKey) {
return
}
if (MOVEMENT_KEYS.has(event.key) && game.getState().isActive) {
event.preventDefault()
game.pressKey(event.key)
}
}

const handleKeyUp = (event: KeyboardEvent) => {
if (MOVEMENT_KEYS.has(event.key)) {
event.preventDefault()
game.releaseKey(event.key)
}
}

document.addEventListener('keydown', handleKeyDown)
document.addEventListener('keyup', handleKeyUp)

return () => {
document.removeEventListener('keydown', handleKeyDown)
document.removeEventListener('keyup', handleKeyUp)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if any other migrated game wires touch/pointer input as a reference pattern
rg -n "touchstart|touchmove|pointerdown|PointerEvent" src/lib/games --type=ts

# Check git history for whether the pre-migration Evader init had touch handling
git log --oneline -- 'src/lib/games/evader/*' | tail -30

Repository: cwchanap/cetus

Length of output: 6461


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the Evader framework file and nearby input wiring
ast-grep outline src/lib/games/evader/initFramework.ts --view expanded

# Inspect the relevant section around keyboard setup and any other input handlers
sed -n '1,260p' src/lib/games/evader/initFramework.ts
printf '\n---\n'
sed -n '260,420p' src/lib/games/evader/initFramework.ts

# Look for any touch/pointer/mouse wiring in Evader specifically
rg -n "touchstart|touchmove|touchend|pointerdown|pointermove|pointerup|mousedown|mousemove|mouseup|wheel|click" src/lib/games/evader --type=ts

# Check whether Evader has tests or utilities suggesting alternative input handling
rg -n "pressKey|releaseKey|isActive|setupKeyboardControls|pointer|touch" src/lib/games/evader --type=ts

Repository: cwchanap/cetus

Length of output: 17517


Add touch/pointer movement controls for Evader. setupKeyboardControls only listens for keyboard events, so the game is still keyboard-only and not playable on touch-only mobile devices. Add a mobile-friendly touch/pointer or mouse path that maps into pressKey/releaseKey (or equivalent movement input). src/lib/games/evader/initFramework.ts:327-352

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/games/evader/initFramework.ts` around lines 327 - 352,
`setupKeyboardControls` only wires keyboard events, so Evader remains unusable
on touch-only devices. Extend this input setup in `initFramework.ts` with a
touch/pointer (or mouse) movement handler that translates directional
gestures/presses into the same movement input path used by `game.pressKey` and
`game.releaseKey`. Keep the existing `handleKeyDown`/`handleKeyUp` behavior
intact, and ensure the cleanup function removes any new listeners added for
mobile-friendly controls.

Source: Path instructions

MemoryMatrixConfig,
MemoryMatrixStats
> {
private matchTimeoutId: number | null = null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n=== MemoryMatrixGame.ts ===\n'
sed -n '1,280p' src/lib/games/memory-matrix/MemoryMatrixGame.ts

printf '\n=== BaseGame candidates ===\n'
fd -a 'BaseGame.ts' src || true
fd -a 'base-game.ts' src || true
fd -a '*Game.ts' src/lib/games | sed -n '1,80p'

Repository: cwchanap/cetus

Length of output: 7469


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n=== MemoryMatrixGame.ts ===\n'
sed -n '1,280p' src/lib/games/memory-matrix/MemoryMatrixGame.ts

printf '\n=== BaseGame candidates ===\n'
fd -a 'BaseGame.ts' src || true
fd -a 'base-game.ts' src || true
fd -a '*Game.ts' src/lib/games | sed -n '1,120p'

Repository: cwchanap/cetus

Length of output: 7469


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n=== MemoryMatrixGame.ts outline ===\n'
ast-grep outline src/lib/games/memory-matrix/MemoryMatrixGame.ts --view expanded || true

printf '\n=== Search for reset/cleanup in game base classes ===\n'
rg -n "onGameReset|cleanup\(|reset\(\)|matchTimeoutId|setTimeout|clearTimeout|cardsMatch" src/lib/games src/lib -g '!**/node_modules/**' || true

Repository: cwchanap/cetus

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n=== BaseGame.ts reset/cleanup ===\n'
sed -n '180,350p' src/lib/games/core/BaseGame.ts

printf '\n=== memory-matrix utils.ts cardsMatch ===\n'
sed -n '1,140p' src/lib/games/memory-matrix/utils.ts

printf '\n=== MemoryMatrixGame.test.ts relevant reset/cleanup tests ===\n'
sed -n '120,220p' src/lib/games/memory-matrix/MemoryMatrixGame.test.ts
sed -n '340,390p' src/lib/games/memory-matrix/MemoryMatrixGame.test.ts

Repository: cwchanap/cetus

Length of output: 11715


Clear the pending match timer on reset.

If reset() fires before flipDelay elapses, onGameReset() leaves matchTimeoutId alive. The stale callback then runs against the fresh post-reset state, sees flippedCards as [], and cardsMatch(card1, card2) throws when it reads card1.shape. Call cleanup() from onGameReset() so reset can’t leave a pending match check behind.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/games/memory-matrix/MemoryMatrixGame.ts` at line 39, `onGameReset()`
in `MemoryMatrixGame` should also clear any pending match-check timer, since
`matchTimeoutId` can survive a reset and later run against the new state. Update
the reset flow to invoke `cleanup()` from `onGameReset()` so the delayed
`cardsMatch` callback is canceled before it can access stale `flippedCards` and
throw.

cwchanap and others added 7 commits July 4, 2026 20:55
Add comprehensive test coverage for edge cases and guard branches across multiple BaseGame framework implementations:

- 2048: Test lifecycle no-ops, config immutability, state change callbacks, full board spawn handling, renderer error paths, animation orchestration, and intermediate frame handling
- Bubble Shooter: Test renderer initialization failures, render guards, and error cleanup paths
- Evader: Test lifecycle hooks (pause/resume), update guards, render no-ops, config immutability, spawn timer guards, non-movement key handling, pressed keys normalization, renderer error paths, render loop behavior, button handlers, and beforeunload warnings
- Memory Matrix: Test render guards for null/primitive states, missing board elements, game-time color tiers, and renderer config factory

These tests ensure robust error handling and proper behavior in edge cases across the migrated games.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- 2048: sync board state after spawn, add error handling for end(), Pixi resource cleanup, configurable swipe threshold, test cleanup
- Bubble Shooter: cap accuracy at 100%, skip row add on board clear, error handling for end()
- Evader: async end() in test, clamp deltaTime to prevent physics spikes
- Memory Matrix: cancel match timeout on reset, use Tailwind classes instead of inline styles

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Add error handling to prevent unhandled promise rejections from end() calls in BaseGame and game implementations
- Fix 2048 test flakiness by accounting for random tile spawning after moves (expect empty or newly spawned tile)
- Update Sudoku tests to match legacy behavior (difficulty bonus awarded on first placement, not game start)

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…etris

Remove internal requestAnimationFrame loops from BubbleShooter, Snake, and Tetris games. These games now use the framework render loop (via update()) for game logic, eliminating dual-RAF architecture and aligning with the BaseGame pattern.

Changes:
- BubbleShooter: Remove gameLoopId, startGameLoop, stopGameLoop, gameUpdate
- Snake: Remove gameLoopId, startGameLoop, stopGameLoop, gameUpdate
- Tetris: Remove gameLoopId, startGameLoop, stopGameLoop, gameUpdate
- Framework init files: Add game.update() calls in render loop with deltaTime
- Tests: Update to test update() directly instead of RAF callbacks

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- 2048: call reset() before start() when the previous run ended, so a new
  game starts with a fresh board and zero score instead of reusing the
  ended state.
- Sudoku: call newGame() before start() after game over to generate a fresh
  puzzle instead of replaying the solved/ended grid.
- Sudoku: capture elapsed time before BaseGame.end() stops the timer so
  getGameStats() reports the correct duration for completed runs.
- Add tests covering restart-after-game-over and elapsed-time stats.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Add ChallengeUpdates type to core/types and propagate it through
  ScoreManager, BaseGame, and GameInitializer.
- Update all BaseGame game init files to call window.showChallengeComplete
  when end events contain completed challenges.
- Include gameHistory in Reflex end stats so it is saved with the score.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…play after goal completion

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/lib/games/evader/EvaderRenderer.test.ts (1)

1-68: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Duplicated PixiJS test scaffolding across multiple files.

The pixi.js mock and makeContainer/makePosition/makeScale factories here are duplicated verbatim in BubbleShooterRenderer.test.ts, evader/initFramework.test.ts, 2048/initFramework.test.ts, and Game2048Renderer.test.ts. Any future change to the PixiJS mock surface (e.g. a new method used by a renderer) has to be replicated in every file.

Consider extracting this into a shared test helper (e.g. src/lib/games/testing/pixiMocks.ts) that all renderer/init test suites import.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/games/evader/EvaderRenderer.test.ts` around lines 1 - 68, The PixiJS
mock setup in EvaderRenderer.test is duplicated across several test suites, so
centralize the shared scaffolding into a common helper and import it here.
Extract the repeated mock factories used by the vi.mock('pixi.js') block,
including the application/container/graphics helpers, into a shared test utility
such as a pixi mock module, then update EvaderRenderer.test and the other
renderer/init tests to use that shared setup so future PixiJS API changes only
need one update.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/lib/games/evader/EvaderRenderer.test.ts`:
- Around line 1-68: The PixiJS mock setup in EvaderRenderer.test is duplicated
across several test suites, so centralize the shared scaffolding into a common
helper and import it here. Extract the repeated mock factories used by the
vi.mock('pixi.js') block, including the application/container/graphics helpers,
into a shared test utility such as a pixi mock module, then update
EvaderRenderer.test and the other renderer/init tests to use that shared setup
so future PixiJS API changes only need one update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14f25071-0131-452c-8a82-8cced4a0dd02

📥 Commits

Reviewing files that changed from the base of the PR and between 86fb8b4 and 87a497f.

📒 Files selected for processing (42)
  • src/lib/games/2048/Game2048.test.ts
  • src/lib/games/2048/Game2048.ts
  • src/lib/games/2048/Game2048Renderer.test.ts
  • src/lib/games/2048/Game2048Renderer.ts
  • src/lib/games/2048/initFramework.test.ts
  • src/lib/games/2048/initFramework.ts
  • src/lib/games/bejeweled/init.ts
  • src/lib/games/bubble-shooter/BubbleShooterGame.test.ts
  • src/lib/games/bubble-shooter/BubbleShooterGame.ts
  • src/lib/games/bubble-shooter/BubbleShooterRenderer.test.ts
  • src/lib/games/bubble-shooter/initFramework.test.ts
  • src/lib/games/bubble-shooter/initFramework.ts
  • src/lib/games/core/BaseGame.ts
  • src/lib/games/core/GameInitializer.ts
  • src/lib/games/core/ScoreManager.ts
  • src/lib/games/core/types.ts
  • src/lib/games/evader/EvaderGame.test.ts
  • src/lib/games/evader/EvaderRenderer.test.ts
  • src/lib/games/evader/initFramework.test.ts
  • src/lib/games/evader/initFramework.ts
  • src/lib/games/memory-matrix/MemoryMatrixGame.test.ts
  • src/lib/games/memory-matrix/MemoryMatrixGame.ts
  • src/lib/games/memory-matrix/MemoryMatrixRenderer.test.ts
  • src/lib/games/memory-matrix/MemoryMatrixRenderer.ts
  • src/lib/games/memory-matrix/initFramework.ts
  • src/lib/games/path-navigator/PathNavigatorGame.ts
  • src/lib/games/path-navigator/initFramework.ts
  • src/lib/games/reflex/ReflexGame.ts
  • src/lib/games/reflex/initFramework.ts
  • src/lib/games/snake/SnakeGame.test.ts
  • src/lib/games/snake/SnakeGame.ts
  • src/lib/games/snake/initFramework.test.ts
  • src/lib/games/snake/initFramework.ts
  • src/lib/games/sudoku/SudokuGame.test.ts
  • src/lib/games/sudoku/SudokuGame.ts
  • src/lib/games/sudoku/initFramework.test.ts
  • src/lib/games/sudoku/initFramework.ts
  • src/lib/games/tetris/TetrisGame.test.ts
  • src/lib/games/tetris/TetrisGame.ts
  • src/lib/games/tetris/TetrisRenderer.test.ts
  • src/lib/games/tetris/initFramework.ts
  • src/lib/games/word-scramble/initFramework.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/lib/games/evader/initFramework.ts
  • src/lib/games/2048/Game2048Renderer.ts
  • src/lib/games/2048/Game2048.ts
  • src/lib/games/bubble-shooter/BubbleShooterGame.ts
  • src/lib/games/2048/initFramework.ts
  • src/lib/games/bubble-shooter/initFramework.ts

Capture elapsed time before BaseGame.end() stops the timer so
getGameStats() reports correct timeElapsed and averageTimePerWord
for ended Word Scramble runs.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@cwchanap
cwchanap merged commit 62ce252 into main Jul 6, 2026
7 of 8 checks passed

This branch was successfully deployed

1 active deployment
Preview — a6d330d4 Deployed Jul 6, 2026 by vercel[bot]
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