Skip to content

feat(arcade): restore white faithful Snake - #174

Merged
Anuraj-dev merged 7 commits into
mainfrom
feat/restore-white-snake
Aug 8, 2026
Merged

Anuraj-dev merged 7 commits into
mainfrom
feat/restore-white-snake

Conversation

@Anuraj-dev

Copy link
Copy Markdown
Owner

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

  • Restore the pure Snake rules, white renderer, crisp edge treatment, bonus food, interpolation, local easy/normal/hard difficulty menu, and owner bus sound events from b7558a8 (including fix(arcade): make snake board edges readable and crisp #170 edge polish).
  • Remove the levels/speed-tier UI, settings fields, sprite-sheet renderer/artifacts, and dead Snake-only near-miss feedback.
  • Keep the current overlay pause/fullscreen/quit behavior, score submission, server newBest celebration, 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 -b from frontend/ passed.
  • Touched-file ESLint passed; forbidden levels/sprite reference scan and diff checks passed.
  • Full CI suite is the merge gate.

Out of scope

No Flappy, Merge-drop, board tables, backend, shared REST/schema, map, deploy, or CI workflow changes.

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.
@vercel

vercel Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
2d-metaverse Ready Ready Preview Aug 8, 2026 10:26am

@Anuraj-dev

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment on lines +409 to +416
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment thread frontend/src/ui/arcade/SnakeGame.tsx Outdated
Comment on lines +115 to +117
// 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread frontend/src/ui/arcade/SnakeGame.tsx Outdated
Comment on lines +467 to +474
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +72 to +75
const size = Math.min(
cell * 0.7,
Math.max(cell * 0.2, (bonus.size / DESIGN_CELL) * cell * 0.45)
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread frontend/src/game/arcade/snake.ts Outdated
Comment on lines 215 to 218
const free = freeCells(width, height, occupied);
if (free.length === 0) {
const head = occupied[0] ?? { x: 0, y: 0 };
return { food: head, rngSeed: seed };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@Anuraj-dev

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment on lines +4 to +6
UPDATE arcade_scores
SET score = score * 10,
updated_at = now()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +293 to +297
stateRef.current = initSnake(seed, {
difficulty: d,
width: stage.cols,
height: stage.rows,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@Anuraj-dev

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment thread shared/src/constants.ts
export type ArcadeScoreGame = (typeof ARCADE_SCORE_GAMES)[number];

export const SCORE_GAME_BY_ARCADE_GAME: Readonly<Record<ArcadeGame, ArcadeScoreGame>> = {
snake: "snake-v2",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +172 to +176
phaseRef.current === "play" &&
!pausedRef.current &&
s.alive &&
!s.won &&
!fxRef.current.animating;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@Anuraj-dev

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment thread frontend/src/game/arcade/snake.ts Outdated
Comment on lines +65 to +67
easy: { initialSpeed: 5, speedIncrement: 0.5, bonusFrequency: 150 },
normal: { initialSpeed: 7, speedIncrement: 1, bonusFrequency: 180 },
hard: { initialSpeed: 10, speedIncrement: 1.5, bonusFrequency: 220 },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread frontend/src/ui/arcade/snake/render.ts Outdated
Comment on lines +66 to +69
const flash = flashOpacity(fx);
if (flash > 0.001) {
ctx.fillStyle = `rgba(255, 0, 0, ${flash})`;
ctx.fillRect(0, 0, boardW, boardH);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@Anuraj-dev

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@Anuraj-dev

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment on lines +69 to +71
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 },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@Anuraj-dev

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: 21e6decc3d

ℹ️ 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".

@Anuraj-dev
Anuraj-dev merged commit 3701d47 into main Aug 8, 2026
9 checks passed
@Anuraj-dev
Anuraj-dev deleted the feat/restore-white-snake branch August 8, 2026 10:54

This branch was successfully deployed

1 active and 1 inactive (outdated) deployments
Preview — 21e6decc Deployed Aug 8, 2026 by vercel[bot]
production — 09f39f00 Deployed Aug 8, 2026 by Anuraj-dev via deploy #61
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