Skip to content

Stop StoneHandler.getFoilStones() from mutating shared level data - #2009

Open
eastagiletracker wants to merge 1 commit into
curiouslearning:mainfrom
eastagiletracker:agile-board/fix-foilstones-level-data-mutation
Open

Stop StoneHandler.getFoilStones() from mutating shared level data#2009
eastagiletracker wants to merge 1 commit into
curiouslearning:mainfrom
eastagiletracker:agile-board/fix-foilstones-level-data-mutation

Conversation

@eastagiletracker

@eastagiletracker eastagiletracker commented Aug 12, 2026

Copy link
Copy Markdown

This PR proposes a fix that stops StoneHandler.getFoilStones() from mutating the shared level data it reads. We include this PR work along with a full history of your repo at https://eastagiletracker.com/projects/230. You can sign in with your GitHub ID to claim ownership of the project.

What this fixes

getFoilStones() in src/components/stone-handler/stone-handler.ts assembles the stones to display by editing this.currentPuzzleData.foilStones in place — it removes the target stones, caps the pool to eight by splicing foil stones out, pushes the targets back, then shuffles. The catch is that currentPuzzleData is a live reference into levelData.puzzles[...], so each call permanently rewrites your level data instead of computing a fresh list. That makes the method non-idempotent: a second call sees the already-mutated array, and for any puzzle with more than eight stones the spliced-out foil stones are gone for good.

It bites hardest in analytics — logPuzzleEndFirebaseEvent() in src/scenes/gameplay-scene/gameplay-flow-manager.ts calls getFoilStones() two or three times while assembling a single puzzle_completed event, so it re-shuffles and re-mutates the pool mid-event. Reproduced on main at HEAD with a focused test: for a puzzle with targetStones: ['A'] and foilStones: ['B'..'J'] (nine), a single getFoilStones() call leaves levelData.puzzles[0].foilStones as a shuffled eight-item array with B and C dropped and A injected, instead of the original nine. npx jest src/components/stone-handler/stone-handler.spec.ts shows the two added assertions failing against the current code.

The fix builds the result on a local copy (const foilStones = [...this.currentPuzzleData.foilStones]) and applies the exact same de-duplication, eight-stone cap and shuffle to that copy, so the value handed to createStones() is unchanged while your level data is left untouched; the puzzle_completed logging now reads the foils once. The full suite was run before and after the change with no new failures — npx jest reports 44 suites and 302 tests passing (the 300 that already passed, plus the two new regression tests that fail without this change).

How this was managed

We mirrored this repository onto a live board and used it to manage this fix. The story tracking the work is at https://eastagiletracker.com/projects/230/stories/100558, on the board at https://eastagiletracker.com/projects/230 — which imported this repository's issues and pull requests as 1,986 stories.

board

If you'd rather not receive contributions like this, reply no-more-prs on this pull request and we won't open any further ones on your repositories.


Lawrence W. Sinclair
CEO / East Agile
linkedin.com/in/lwsinclair/
eastagile.com

Summary by CodeRabbit

  • Bug Fixes

    • Fixed foil-stone selection so shared puzzle data remains unchanged across repeated calls.
    • Prevented foil-stone lists from being unintentionally truncated or limited incorrectly.
    • Ensured gameplay analytics consistently report the selected foil stones.
  • Tests

    • Added coverage for repeated foil-stone retrieval and puzzles containing more than eight foil stones.

getFoilStones() rewrote currentPuzzleData.foilStones in place, but that
array is a reference into the shared level data. Because it also caps the
pool to eight stones, repeated calls permanently dropped foil stones and
reshuffled the set; the puzzle_completed analytics event compounded this
by reading the foils more than once per puzzle. Build the result on a copy
so the level data is never mutated, and read the foils once when logging.
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

getFoilStones() now avoids mutating shared puzzle data, preserves its eight-stone limit, and returns consistent results for analytics tracking.

Changes

Foil stone integrity

Layer / File(s) Summary
Copy and validate foil stones
src/components/stone-handler/stone-handler.ts, src/components/stone-handler/stone-handler.spec.ts
getFoilStones() copies the foil-stone array before removing targets and trimming extras. Tests cover repeated calls and puzzles with more than eight foil stones.
Reuse foil stones in analytics
src/scenes/gameplay-scene/gameplay-flow-manager.ts
The gameplay flow caches the foil-stone result and uses it when formatting the puzzle-end analytics payload.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: janfb-codev

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Tests & Lint & Coverage ❓ Inconclusive Relevant regression tests were added, but lint and coverage cannot be verified because node_modules, executables, and coverage reports are absent. Install dependencies, run npm run lint and npm test with coverage, then provide the lint result and coverage percentage.
✅ 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 and concisely describes the fix to prevent getFoilStones() from mutating shared level data.
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

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.

Warning

⚠️ This pull request has been flagged as potential spam (promotional) by CodeRabbit slop detection and should be reviewed carefully.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
src/components/stone-handler/stone-handler.spec.ts (1)

157-166: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the returned collection as well as source immutability.

This test only checks that levelData.puzzles[0].foilStones remains unchanged. It passes even if getFoilStones() stops enforcing the eight-stone limit.

Store the result and assert that it contains eight stones and retains the target stone.

As per coding guidelines, “Relevant automated tests are added or updated for the changes.”

Proposed assertions
-      handler.getFoilStones();
+      const result = handler.getFoilStones();
+      expect(result).toHaveLength(8);
+      expect(result).toEqual(expect.arrayContaining(['A']));

       expect(levelData.puzzles[0].foilStones).toEqual(originalFoilStones);
🤖 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/components/stone-handler/stone-handler.spec.ts` around lines 157 - 166,
Update the test around StoneHandler.getFoilStones to store its returned
collection, assert that it contains exactly eight stones, and verify the target
stone is retained, while preserving the existing assertion that
levelData.puzzles[0].foilStones remains unchanged.

Source: Coding guidelines

src/scenes/gameplay-scene/gameplay-flow-manager.ts (1)

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

Add a regression test for the cached analytics value.

The added tests do not execute GameplayFlowManager.logPuzzleEndFirebaseEvent. A regression could call getFoilStones() more than once again while the current tests still pass.

Add a focused test that returns a known foil array, invokes the completion logger, verifies one getFoilStones() call, and checks that foils equals foilStones.join(',').

As per coding guidelines, “Relevant automated tests are added or updated for the changes.”

Also applies to: 507-507

🤖 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/scenes/gameplay-scene/gameplay-flow-manager.ts` at line 495, Add a
focused regression test for GameplayFlowManager.logPuzzleEndFirebaseEvent that
mocks getFoilStones() to return a known array, invokes the completion logger,
verifies getFoilStones() is called exactly once, and asserts the emitted foils
value equals foilStones.join(',').

Source: Coding guidelines

src/components/stone-handler/stone-handler.ts (1)

255-278: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Reuse shuffleArray for the final shuffle.

StoneHandler.shuffleArray already provides a Fisher-Yates shuffle and returns a copy. sort(() => Math.random() - 0.5) produces biased permutations and duplicates the shuffle implementation.

Proposed fix
-    return foilStones.sort(() => Math.random() - 0.5);
+    return this.shuffleArray(foilStones);
🤖 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/components/stone-handler/stone-handler.ts` around lines 255 - 278,
Replace the biased sort-based shuffle in the method building the final
foil-stone list with the existing StoneHandler.shuffleArray helper, preserving
the returned shuffled copy and all preceding stone-selection logic.
🤖 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/components/stone-handler/stone-handler.spec.ts`:
- Around line 157-166: Update the test around StoneHandler.getFoilStones to
store its returned collection, assert that it contains exactly eight stones, and
verify the target stone is retained, while preserving the existing assertion
that levelData.puzzles[0].foilStones remains unchanged.

In `@src/components/stone-handler/stone-handler.ts`:
- Around line 255-278: Replace the biased sort-based shuffle in the method
building the final foil-stone list with the existing StoneHandler.shuffleArray
helper, preserving the returned shuffled copy and all preceding stone-selection
logic.

In `@src/scenes/gameplay-scene/gameplay-flow-manager.ts`:
- Line 495: Add a focused regression test for
GameplayFlowManager.logPuzzleEndFirebaseEvent that mocks getFoilStones() to
return a known array, invokes the completion logger, verifies getFoilStones() is
called exactly once, and asserts the emitted foils value equals
foilStones.join(',').

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 99cfa997-cc08-4f51-a5c5-dbe51f209a5d

📥 Commits

Reviewing files that changed from the base of the PR and between 46e6378 and 4c64cfd.

📒 Files selected for processing (3)
  • src/components/stone-handler/stone-handler.spec.ts
  • src/components/stone-handler/stone-handler.ts
  • src/scenes/gameplay-scene/gameplay-flow-manager.ts

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