Repository navigation
feat(arcade): restore white faithful Snake - #174
Conversation
Bring back the player-approved white Snake from b7558a8 and drop the levels, speed tiers, and sprite-sheet board. Keep the current overlay chrome, other cabinets, and server-authoritative newBest scoring path intact.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 182de1b399
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (onFood) { | ||
| score += FOOD_SCORE; | ||
| speed = speedForScore(score, state.difficulty); | ||
| // Milestone replaces the plain eat blip on multiples of 100. | ||
| events.push(score % 100 === 0 ? "milestone" : "eat"); | ||
| } | ||
| if (onBonus && state.bonus) { | ||
| score += state.bonus.value; |
There was a problem hiding this comment.
Version Snake scores before changing their scale
On deployments with existing Snake scores, changing ordinary food from 1 point to 10 and adding bonuses worth up to 100 mixes incompatible values in the unchanged arcade_scores rows keyed by game = 'snake'. Because the backend preserves only each user's maximum and this commit provides no migration, reset, or versioned game ID, new runs will dominate and overwrite historical personal bests and leaderboard entries even when the underlying performance is not better.
AGENTS.md reference: AGENTS.md:L74-L74
Useful? React with 👍 / 👎.
| // The faithful white renderer has no screen-shake layer. Keep accepting the | ||
| // live shared prop so the overlay can pass one contract to every cabinet. | ||
| void shake; |
There was a problem hiding this comment.
Honor the shake and reduced-motion settings
When a player disables the header's screen-shake toggle or enables reduced motion, Snake still shakes on every death: this discards the live shake prop, while renderSnake unconditionally applies shakeOffset during the death effect. Gate that effect on both settings, or remove it as the comment claims, so the accessibility and explicit opt-out controls actually take effect.
Useful? React with 👍 / 👎.
| const heading = renderHeading(st.dir, st.dirQueue); | ||
| stateRef.current = snakeInput(st, dir); | ||
| // A real turn commits on the very next frame instead of waiting out the | ||
| // rest of the tick — that input-to-movement gap read as control lag. | ||
| if (st.alive && dir !== heading && dir !== OPPOSITE[heading]) { | ||
| accMsRef.current = Math.max( | ||
| accMsRef.current, | ||
| 1000 / Math.max(0.5, st.speed) |
There was a problem hiding this comment.
Move instant-turn scheduling into the reducer
When the player quickly presses an illegal reversal and then a valid perpendicular direction before the next frame—for example, Left then Up while moving right—this renderer-side legality check marks Up as an immediate turn and forces a tick, but snakeTick consumes only the queued Left, discards it, and moves right; Up then waits another full interval. That creates a dangerous straight-ahead step despite the advertised instant steering, and results from re-deriving turn rules outside the pure Snake module.
AGENTS.md reference: AGENTS.md:L71-L71
Useful? React with 👍 / 👎.
| const size = Math.min( | ||
| cell * 0.7, | ||
| Math.max(cell * 0.2, (bonus.size / DESIGN_CELL) * cell * 0.45) | ||
| ); |
There was a problem hiding this comment.
Keep the bonus orb inside its collision cell
At full lifetime, bonus.size is 30 and this calculation produces a radius of 0.675 * cell, so the orb's diameter spans 1.35 cells despite the comment and the reducer treating only bonus.cell as edible. A snake in an adjacent cell can therefore visibly overlap a fresh bonus without collecting it; cap the radius below half a cell or treat the authored size as a diameter.
Useful? React with 👍 / 👎.
| const free = freeCells(width, height, occupied); | ||
| if (free.length === 0) { | ||
| const head = occupied[0] ?? { x: 0, y: 0 }; | ||
| return { food: head, rngSeed: seed }; |
There was a problem hiding this comment.
Avoid hiding food in the snake near a full board
When the snake has two cells left, an active bonus occupies one, and the head eats normal food in the other, foodOccupied leaves no free cell even though the run is not won; this fallback then places the replacement food on the snake's head. If the bonus expires before being collected, the only normal food remains hidden inside the moving body for potentially almost its full length, leaving no visible collectible despite a free board cell. Handle the bonus-owned final cell explicitly or respawn food when that bonus expires.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ee30bc50ab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| UPDATE arcade_scores | ||
| SET score = score * 10, | ||
| updated_at = now() |
There was a problem hiding this comment.
Coordinate the score rescale with the frontend rollout
If Vercel deploys the new 10-point client before the independent backend deployment reaches its migrate step, any Snake score submitted during that window is already in the new scale and this unconditional update multiplies it again, leaving a permanently inflated personal best and leaderboard row. I checked .github/workflows/frontend-ci.yml and .github/workflows/backend-deploy.yml: they have no cross-workflow ordering, while deploy/deploy-remote.sh runs migrations only during the backend rollout. Make the migration distinguish legacy rows or version the game/score format so concurrently submitted new-scale scores cannot be rescaled.
AGENTS.md reference: AGENTS.md:L31-L32
Useful? React with 👍 / 👎.
| stateRef.current = initSnake(seed, { | ||
| difficulty: d, | ||
| width: stage.cols, | ||
| height: stage.rows, | ||
| }); |
There was a problem hiding this comment.
Use one board size for globally ranked Snake runs
When players start Snake in differently sized surfaces—for example, a narrow embedded layout versus fullscreen—these viewport-derived dimensions change both the lethal boundaries and the win threshold (body.length >= width * height). A larger surface therefore provides substantially more maneuvering room and a much higher attainable food score, yet every run is submitted under the same game = "snake" leaderboard. Keep scored runs on fixed dimensions or separate scores by board configuration so display size does not determine leaderboard potential.
AGENTS.md reference: AGENTS.md:L74-L74
Useful? React with 👍 / 👎.
| -- existing leaderboard remains comparable to the new scoring unit. | ||
| UPDATE arcade_scores | ||
| SET score = score * 10, | ||
| updated_at = now() |
There was a problem hiding this comment.
Preserve leaderboard tie timestamps during rescaling
When historical players have equal Snake scores, this assignment replaces all of their achievement times with the same transaction timestamp. getArcadeLeaderboard uses ORDER BY a.score DESC, a.updated_at ASC, so the migration erases the existing tie-break order and makes which tied player appears at the top-10 cutoff dependent on unspecified row ordering. Scaling the numeric score should leave updated_at unchanged.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d584c13187
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| export type ArcadeScoreGame = (typeof ARCADE_SCORE_GAMES)[number]; | ||
|
|
||
| export const SCORE_GAME_BY_ARCADE_GAME: Readonly<Record<ArcadeGame, ArcadeScoreGame>> = { | ||
| snake: "snake-v2", |
There was a problem hiding this comment.
Stage the Snake score namespace before switching clients
When the independently deployed Vercel frontend reaches production before the backend, every Snake leaderboard request and score submission uses snake-v2, while the previous backend only accepts the old ARCADE_GAMES values and therefore returns 404/400; completed runs during that window are never retried, so their scores are permanently lost. I checked .github/workflows/frontend-ci.yml and .github/workflows/backend-deploy.yml, and there is no cross-workflow ordering. Fresh evidence in this head is the unconditional snake → snake-v2 mapping; deploy backend acceptance first or retain a client fallback until it is live.
AGENTS.md reference: AGENTS.md:L31-L32
Useful? React with 👍 / 👎.
| phaseRef.current === "play" && | ||
| !pausedRef.current && | ||
| s.alive && | ||
| !s.won && | ||
| !fxRef.current.animating; |
There was a problem hiding this comment.
Disable Snake's decorative animations for reduced motion
When the effective reduced-motion preference is enabled, this gate still allows every movement to use interpolateSnakeBody; renderSnake also advances the food pulse unconditionally. Although this head now gates the death shake, these newly introduced tween/pulse paths remain active, contrary to the consumer contract in frontend/src/game/reducedMotion.ts:13-15 to skip tweens and pulses while preserving state transitions. Include !reducedMotion in the interpolation/pulse decisions so the live preference suppresses all of Snake's decorative canvas motion.
Useful? React with 👍 / 👎.
| import { hash } from "./util"; | ||
|
|
||
| /** ~50 frames at 60 Hz — the original's MAX_GAME_OVER_FRAMES pacing. */ | ||
| export const GAME_OVER_DURATION_MS = 830; |
There was a problem hiding this comment.
Let the Snake death effect finish before pausing it
On every Snake death, this effect is configured to run for 830 ms, but ArcadeOverlay switches from finishing to over after DEATH_FREEZE_MS (450 ms), at which point it passes paused={true} and stops the renderer loop. The effect therefore never reaches settled; it freezes at roughly 54% progress, still within the shake phase that ends at 60%, behind the game-over card. Align the overlay hold with this duration or keep advancing the terminal effect until it settles.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 09f39f00c3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| easy: { initialSpeed: 5, speedIncrement: 0.5, bonusFrequency: 150 }, | ||
| normal: { initialSpeed: 7, speedIncrement: 1, bonusFrequency: 180 }, | ||
| hard: { initialSpeed: 10, speedIncrement: 1.5, bonusFrequency: 220 }, |
There was a problem hiding this comment.
Separate scores for Snake difficulty modes
When players choose different difficulties, runs submitted to the single snake-v2 leaderboard do not use equivalent scoring rules: Easy starts rolling for bonus food after 150 movement steps while Hard waits 220, so the same survival path can receive different bonus opportunities and scores. Use a common bonus cadence for ranked runs or include difficulty in the score namespace so the leaderboard does not compare incompatible runs.
AGENTS.md reference: AGENTS.md:L74-L74
Useful? React with 👍 / 👎.
| const flash = flashOpacity(fx); | ||
| if (flash > 0.001) { | ||
| ctx.fillStyle = `rgba(255, 0, 0, ${flash})`; | ||
| ctx.fillRect(0, 0, boardW, boardH); |
There was a problem hiding this comment.
Suppress the death flash under reduced motion
When effective reduced motion is enabled and Snake ends, this still renders flashOpacity, which alternates a full-board red overlay roughly every 66 ms during the death effect. Fresh evidence after the earlier reduced-motion fix is that motionEnabled now gates interpolation and food animation but is not consulted by this flash path, so users who opted out still receive a rapid pulse on every death.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 70787e58a5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| /** Let terminal particles/shake read before the game-over card covers them. */ | ||
| const DEATH_FREEZE_MS = 450; | ||
| /** Let Snake's full 830ms terminal FX settle before the game-over card covers it. */ | ||
| export const DEATH_FREEZE_MS = 830; |
There was a problem hiding this comment.
Scope the longer terminal hold to Snake
When Flappy or Merge Drop ends, this overlay-wide constant now keeps the game-over card hidden for 830 ms instead of the previous 450 ms, even though the longer duration was introduced only for Snake's death effect. Flappy already waits for its crash/tumble to settle before calling onGameOver in FlappyGame.tsx, so this adds another 380 ms of an inert terminal screen after every failure; select the hold duration per game rather than applying Snake's timing to every cabinet.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eefba19512
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| easy: { initialSpeed: 5, speedIncrement: 0.5, bonusFrequency: BONUS_FREQUENCY }, | ||
| normal: { initialSpeed: 7, speedIncrement: 1, bonusFrequency: BONUS_FREQUENCY }, | ||
| hard: { initialSpeed: 10, speedIncrement: 1.5, bonusFrequency: BONUS_FREQUENCY }, |
There was a problem hiding this comment.
Keep bonus scoring comparable across Snake difficulties
When two players follow the same path to a bonus, Easy advances at 5 ticks/s while Hard starts at 10 ticks/s, but the bonus value decays over a fixed 10,000 ms of wall time; Hard therefore reaches the same bonus sooner and receives more points even though every mode submits to snake-v2. Fresh evidence after the earlier cadence fix is that unifying bonusFrequency does not unify this wall-clock decay, so use tick-based decay or separate score namespaces for the modes.
AGENTS.md reference: AGENTS.md:L74-L74
Useful? React with 👍 / 👎.
|
|
||
| /** Terminal presentation hold per cabinet; Snake's death FX needs the longer hold. */ | ||
| export const TERMINAL_HOLD_MS: Readonly<Record<ArcadeGame, number>> = { | ||
| snake: 830, |
There was a problem hiding this comment.
Skip Snake's terminal hold under reduced motion
When the effective reduced-motion preference is enabled, Snake suppresses its shake, flash, pulse, and interpolation but this unconditional 830 ms timer still delays the game-over card for an effect the user opted out of. That conflicts with the consumer contract in frontend/src/game/reducedMotion.ts:13-15 to drop decorative motion without delaying the state transition; derive this hold from the effective preference so the terminal card appears immediately in reduced-motion mode.
Useful? React with 👍 / 👎.
|
@codex review |
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Why
The player-approved white faithful Snake was overwritten when banked PR #167 merged its levels and dark-sprite rework. This restores only that Snake experience.
What
b7558a8(including fix(arcade): make snake board edges readable and crisp #170 edge polish).newBestcelebration, and shared shake prop contract.Test plan
npx vitest run src/game/arcade/snake.test.ts src/game/arcade/feedback.test.ts src/ui/arcade/snake/interp.test.ts src/ui/arcade/ArcadeOverlay.test.tsx src/ui/arcade/ArcadeMotion.test.tsx src/ui/settings.test.ts— 7 files, 112 tests passed.npx tsc -bfromfrontend/passed.Out of scope
No Flappy, Merge-drop, board tables, backend, shared REST/schema, map, deploy, or CI workflow changes.