diff --git a/apps/cluster/src/components/Board.svelte b/apps/cluster/src/components/Board.svelte index 342aab676..76647ed7d 100644 --- a/apps/cluster/src/components/Board.svelte +++ b/apps/cluster/src/components/Board.svelte @@ -30,8 +30,22 @@ boardShow: () => (show = true), boardHide: () => (show = false), boardWithAnimateSymbols: async ({ symbolPositions }) => { + // Dedupe positions before awaiting. A board cell can appear in more + // than one win — a wild substitutes into multiple adjacent clusters, + // so each lists its position. Without dedupe, the duplicate iteration + // overwrites the prior `reelSymbol.oncomplete`, so the symbol's single + // `complete` resolves only the last await; the earlier one never + // settles and `Promise.all` hangs forever — the bonus/free-spin freeze + // (#14). + const seen = new Set(); + const uniquePositions = symbolPositions.filter((position) => { + const key = `${position.reel},${position.row}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); const getPromises = () => - symbolPositions.map(async (position) => { + uniquePositions.map(async (position) => { const reelSymbol = context.stateGame.board[position.reel].reelState.symbols[position.row]; reelSymbol.symbolState = 'win'; await waitForResolve((resolve) => (reelSymbol.oncomplete = resolve)); diff --git a/apps/cluster/src/components/TumbleBoard.svelte b/apps/cluster/src/components/TumbleBoard.svelte index 2020e475e..d86accded 100644 --- a/apps/cluster/src/components/TumbleBoard.svelte +++ b/apps/cluster/src/components/TumbleBoard.svelte @@ -82,8 +82,19 @@ context.stateGame.tumbleBoardBase = []; }, tumbleBoardExplode: async ({ explodingPositions }) => { + // Dedupe positions (same reason as boardWithAnimateSymbols in + // Board.svelte): a shared wild can appear in explodingPositions more + // than once; the duplicate overwrites `tumbleSymbol.oncomplete`, so the + // earlier await never settles and `Promise.all` hangs forever (#14). + const seen = new Set(); + const uniquePositions = explodingPositions.filter((position) => { + const key = `${position.reel},${position.row}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); const getPromises = () => - explodingPositions.map(async (position) => { + uniquePositions.map(async (position) => { const tumbleSymbol = context.stateGame.tumbleBoardBase[position.reel][position.row]; tumbleSymbol.symbolState = 'explosion'; await waitForResolve((resolve) => (tumbleSymbol.oncomplete = resolve));