BUG-001

Reel canvas on Slots page displays previous spin's symbols instead of current result on every spin after the first

OPEN
OPEN S1 CRITICAL P1 100% repro · SlotCanvas

Description

Race condition in React state management causes the PixiJS reel canvas to animate with stale outcome data. Every spin visually shows the prior round's reel_matrix while the HUD displays correct win amounts from the current round.

Preconditions

  • User is authenticated
  • Sufficient balance for at least 2 spins
  • At least one previous spin completed (lastOutcome exists in state)

Steps to Reproduce

1

Open the slots game at pyavchik.space/slots

2

Place a bet and click SPIN

Wait for reels to stop. Note the symbols displayed on the canvas.

3

Click SPIN again — wait for reels to stop

The symbols now shown on the canvas are from step 2, not the current spin.

4

Open DevTools → Network tab → inspect the latest /spin response body

Look at outcome.reel_matrix in the JSON response.

5

Compare the canvas symbols to the API response's reel_matrix

The symbols do NOT match — the canvas shows the prior spin's matrix.

6

Navigate to Game History → click the latest round → compare the Round Detail reel grid

Round Detail shows the correct (server-side) symbols, confirming the mismatch is frontend-only.

Expected vs Actual Result

✓ Expected

Canvas reel symbols match the reel_matrix from the current spin's API response. Displayed symbols, win highlights, and payout amounts are all consistent.

✗ Actual

Canvas displays the previous spin's reel_matrix. Every spin result is visually one spin behind the server. Win amounts in the HUD are correct (from API), but displayed symbols belong to the prior round.

Console & Network Logs

C Browser Console
✓ No JavaScript errors or warnings in console. // The bug is silent — no exceptions are thrown. // The stale data is valid JS; the race condition // produces incorrect but non-crashing behavior.
Checked in Chrome DevTools → Console tab during 10 consecutive spins. Zero errors, zero warnings. This makes the bug harder to detect without comparing canvas to API response.
N Network — POST /api/v1/spin
// Response 200 OK — server returns CORRECT data { "outcome": { "reel_matrix": [ ["K", "A", "10"], ← Reel 1: server truth ["A", "10", "J"], ["A", "10", "J"], ["J", "Q", "K"], ["A", "10", "J"] ], "win": { "amount": 0.04 } } }
Canvas ignored this response and displayed the previous spin's matrix instead. The API response is correct — the bug is frontend-only.

Evidence

CANVAS — WRONG SYMBOLS Canvas showing wrong symbols with API response visible in DevTools
Canvas displays symbols from the previous spin. Network tab shows the current API response with different reel_matrix values.
ROUND DETAIL — SERVER TRUTH Round Detail page showing the correct reel matrix from server
Round Detail confirms the server generated a different reel result than what the canvas displayed.

Root Cause Analysis

The bug is a React state race condition in the spin flow. The useEffect in SlotCanvas depends on both spinning and lastOutcome. When setSpinning(true) is called, the effect fires before the API responds — using the stale outcome from the previous round.

1. User clicks SPIN
2. App calls setSpinning(true)spinning: false → true
3. useEffect([lastOutcome, spinning]) triggers because spinning changed
4. lastOutcome still holds previous spin's data STALE
5. spinThenStop(oldMatrix) starts animation with wrong symbols
6. API responds → setSpinResult(newOutcome)
7. useEffect triggers again with correct data
8. spinThenStop(newMatrix) REJECTED at line 595 — reels already animating REJECTED
9. Reels stop showing old symbols BUG
PROPOSED FIX

Clear stale outcome on spin start

Null out lastOutcome when setSpinning(true) is called. The effect sees lastOutcome === null and exits early. When the fresh API response arrives, the effect fires with correct data — no stale state, no rejected calls.

Diff — frontend/src/store.ts

  setSpinning: (v) =>
    set((s) => ({
      spinning: v,
+     // Clear stale outcome when starting a new spin so the SlotCanvas
+     // effect doesn't re-trigger spinThenStop with the previous result.
+     lastOutcome: v ? null : s.lastOutcome,
      lastWinAmount: v ? 0 : (s.pendingWinAmount ?? 0),
      pendingWinAmount: null,
    })),

Fixed flow

1. setSpinning(true)spinning: true, lastOutcome: null
2. useEffect fires → !lastOutcome is trueearly return (no stale animation)
3. API responds → setSpinResult(newOutcome)
4. useEffect fires with fresh dataspinThenStop(newMatrix) → correct symbols

Activity

O. Pyavchik created this issue — Mar 1, 2026
O. Pyavchik added root cause analysis — Mar 1, 2026
O. Pyavchik linked PR #40 — Mar 1, 2026