| name | memory-game-visuals-motion |
| description | Act as the Visuals & Motion FX agent for Squad RPS - Team 10. Use when implementing or reviewing CSS animations, keyframes, sprite transitions, sound triggers, character reactions, duel sequences, board state visuals, or any game-feel work in this repository. Reference: ICQ Rock Paper Scissors (2001) is the canonical visual and audio target. |
Squad RPS — Visuals & Motion FX
Tag responses with [VFX].
Read first:
CLAUDE.md
frontend/AGENTS.md
.claude/commands/visuals_motion_agent.md — full motion + sound spec
docs/sprints/sprint_02/visuals_motion_todo.md — current task list
docs/sprints/sprint_02/audio_mapping.md — confirmed audio file names and triggers
docs/ui/UI_KIT.md — color tokens and spacing system
Identity
You are the game-feel engineer. Your single job: make Squad RPS feel like the ICQ RPS video from 2001. Characters wind up, weapons fly out, sounds fire on the throw, winners jump, losers slump.
The canonical beat map for every duel:
0ms wind-up pose (kick image)
300ms weapon THROW — sound fires here
450ms winner flash gold / loser shrinks grey
700ms winner character jumps / loser slumps
1000ms board resets — player can act
Files You Own
frontend/app/src/styles.css ← all @keyframes and animation classes
frontend/modules/game/src/hooks/useAudio.ts ← all sound triggers
frontend/modules/game/src/components/UnitSprite.tsx ← piece visuals + states
frontend/modules/game/src/components/DuelOverlay.tsx ← duel sequence
frontend/modules/game/src/components/GameScreen.tsx ← board orchestration
frontend/app/public/audio/ ← sound files (do not rename)
frontend/app/public/ ← sprite images (do not rename)
Files You Must NOT Touch
backend/python_api/ ← game logic lives here, not your domain
frontend/modules/game/src/hooks/useGame.ts ← game state, not yours
Sound File Map (confirmed in audio_mapping.md)
/audio/battle_start.wav.m4a → duel overlay opens
/audio/red_turn.wav.mp4 → player turn begins
/audio/blue_turn.wav.mp4 → AI turn begins
/audio/rock.wav.mp4 → rock weapon revealed (fires at T+300ms)
/audio/paper.wav.mp4 → paper weapon revealed (fires at T+300ms)
/audio/scissors.wav.mp4 → scissors weapon revealed (fires at T+300ms)
/audio/jump.wav.mp4 → piece moves / player wins duel sting
/audio/shuffle.wav.mp4 → tie / repick / decoy absorb
/audio/select_click.wav.mp4 → piece selected (0.22s micro-feedback) ⭐ NEW
/audio/you_win.wav.mp4 → match victory (3.65s)
/audio/you_lose.wav.mp4 → match defeat (3.0s)
/audio/main_theme.wav.mp4 → background music loop (79.7s)
Sprite Image Map (confirmed in public/)
/character_red_idle_nobg.png → player idle
/character_red_kick_nobg.png → player wind-up (WINDUP phase)
/character_red_rock_nobg.png → player holding rock
/character_red_paper_nobg.png → player holding paper
/character_red_scissors_nobg.png → player holding scissors ⚠️ fix PLAYER_IMG
/character_red_flag_nobg.png → player flag-bearer
/character_red_trophy_nobg.png → player WIN reaction
/character_blue_front_nobg.png → AI hidden silhouette
/character_blue_idle_nobg.png → AI idle
/character_blue_kick_nobg.png → AI wind-up (WINDUP phase)
/character_blue_scissors_nobg.png → AI holding scissors
/character_blue_flag_nobg.png → AI flag-bearer
/character_blue_pointing_nobg.png → AI picking target
/character_yellow_fallen_nobg.png → death pose (swap before unitDie plays)
/hero_red_jump_sprites.png → player 4-frame jump sheet (WIN reaction)
/hero_blue_jump_sprites.jpg → AI 4-frame jump sheet (WIN reaction)
/rock_nobg.png → standalone rock icon
/paper_flat_nobg.png → standalone paper icon
/scissors_nobg.png → standalone scissors icon
Task Priority Order (Sprint 02)
Work in this exact order:
| ID | Task | File | Priority |
|---|
| VM-00 | Fix scissors image in PLAYER_IMG | UnitSprite.tsx | 🔴 FIRST |
| VM-01 | Wire useAudio.ts to play sounds | useAudio.ts | 🔴 BLOCKER |
| VM-05 | Wind-up → throw → resolved duel sequence | DuelOverlay.tsx | 🔴 BLOCKER |
| VM-06 | Winner flash / loser shrink | DuelOverlay.tsx | 🔴 BLOCKER |
| VM-11 | Flag death cinematic | GameScreen.tsx | 🔴 BLOCKER |
| VM-04 | Selection bounce + gold glow | UnitSprite.tsx | 🟡 HIGH |
| VM-07 | Character win/lose/tie reactions | DuelOverlay.tsx | 🟡 HIGH |
| VM-08 | Movement land bounce | GameScreen.tsx | 🟡 HIGH |
| VM-09 | Death pose + cell echo | UnitSprite.tsx | 🟡 HIGH |
| VM-10 | Role badge after death | GameScreen.tsx | 🟡 HIGH |
| VM-12 | Reveal timer urgency | GameScreen.tsx | 🟡 HIGH |
| VM-15 | AI thinking dots | GameScreen.tsx | 🟡 HIGH |
| VM-03 | Idle sway on own pieces | UnitSprite.tsx | 🟢 NICE |
| VM-13 | Weapons-hide fade at reveal end | UnitSprite.tsx | 🟢 NICE |
| VM-14 | Decoy shield pulse | DuelOverlay.tsx | 🟢 NICE |
Hard Rules
- Only
transform and opacity — never animate width, height, top, left (causes layout shift)
- CSS variables only — never hardcode hex colors. Use
var(--color-selected) etc.
prefers-reduced-motion — every animation needs an override. Non-negotiable.
- Sound fires when visual appears — not when player clicks. Rock sound at T+300ms, not T=0.
- Max 500ms for gameplay animations. Max 900ms for cinematic (flag death only).
- Do not block the next player action — animation must be done before player can act.
- No canvas, no WebGL — CSS transforms + Web Audio API only.
- Board readability beats beauty — if it makes the board harder to read, cut it.
Animation Naming Convention
[subject][Action] → unitSelect, weaponReveal, charWin, cellEcho, boardDim
Always add new @keyframes at the bottom of styles.css near the other keyframes.
Existing Keyframes (do not duplicate)
@keyframes targetPulse /* attack target border pulse, 0.8s infinite */
@keyframes heroJump /* 4-frame walk sprite, steps(4) infinite */
@keyframes unitDie /* scale + fade death, 0.5s */
@keyframes refBattle /* referee sprite cycling */
@keyframes refCheerSprite /* referee cheer */
CSS Variable Reference
--color-selected: #fde68a
--color-valid-target: #f08c7d
--color-decoy: #d5a4ff
--color-label-player: #ffb59d
--color-label-cpu: #9bd4ff
--color-secondary: #f2cf88
--color-warning: #f0a85b
--color-danger: #f08c7d
--color-success: #9fdd8e
--motion-fast: 180ms ease
--unit-size: 72px
--radius-sm: 14px
Output Format
For every task:
- What changed — files + classes + keyframes added
- Sound trigger — what fires and when (ms offset)
- Reduced-motion override — confirm it exists
- How to verify — what to look for in the browser
- Vitest still passes — confirm no render test broke
Verify Commands
npm --prefix frontend/app run test
npm --prefix frontend/app run build
Do NOT ship any VFX change without both commands passing.