game-design
Attaches itself automatically when a prompt matches.
Build browser mini-games that feel made, not generated — game loop structure, dual input (touch + keyboard), procedural graphics, synth SFX, juice (screenshake, particles, easing), difficulty ramps, score/game-over patterns, and mobile-safe controls. Use when the user wants a game, arcade game, platformer, shooter, puzzle, tetris, snake, breakout, pong, clicker, endless runner, or any playable interactive experience.
Scaffolds
Source
Adapted from in-house — distilled from the Codebase Arcade (Debug Protocol et al.)
Instructions
The full instruction set the agent receives when this skill is active.
Game Design: Mini-Games That Feel Made
A generated game is judged in the first 10 seconds: does input respond instantly, does every action produce feedback, can I die and retry without friction? Graphics don't decide this — feedback does.
0. Structure (non-negotiable)
- Three scenes minimum: menu ("tap to start" — this gesture also unlocks audio), gameplay, game-over (score + best + instant retry).
- One loop. On scaffold-game the engine owns it (
k.onUpdate/k.loop); hand-rolled canvas games get exactly onerequestAnimationFrameloop with delta-time ((now - last) / 1000), neversetInterval. - Always clamp dt:
dt = Math.min((now - last) / 1000, 1/20). A tab switch, GC pause, or slow phone produces a giant frame gap — unclamped, entities teleport through walls and the physics explodes (spiral of death). Non- negotiable one-liner. - State is explicit:
menu | playing | gameover. No scattered booleans. - Add a ~0.5s input lockout on the game-over screen so the death-tap doesn't skip the score.
1. Input — both schemes, always
- Pointer/touch AND keyboard (arrows + WASD + space). A keyboard-only game is broken on phones; a hover-dependent game is broken everywhere.
- Movement feel: pointer-follow uses lerp (
pos = pos.lerp(target, dt * 10)), keyboard uses normalized diagonal speed (dir.unit() * SPEED * dt). - Clamp the player inside the playfield every frame.
- Mobile:
touch-action: none+overscroll-behavior: noneon the shell so the page never scrolls or rubber-bands mid-game.
2. The Juice Checklist (ship = all five)
- SFX on every interaction — pickup, hit, UI, win, lose. Synth only (WebAudio oscillators/noise) — never audio files. Keep each under 300ms.
- Screenshake on impact — small hits ~8, death ~14–16. Never constant.
- Particle burst on rewards — 8–16 small shapes, random direction, fade over ~0.4s. This replaces sprites as the energy carrier.
- Motion polish — trails behind the player, pulsing "press start"
prompts (
opacity = 0.5 + |sin(t*3)| * 0.5), fade-in/out on spawn/despawn. - Feedback < 100ms — the frame the event happens, the response happens.
3. Visual language (procedural constraints → style)
- Dark background (#0f0f1a-class), 2–4 vivid accents. Role-lock colors: reward = warm (gold), hazard = red, player = cool (cyan/green). Never blur those roles.
- Distinct silhouettes: player vs hazard must differ in shape, not just color (circle vs rotating square). Rotation on hazards reads as "danger" for free.
- HUD: big score, one corner, tabular numerals; zero chrome during play.
- No image files, no sprite URLs, no @font-face — shapes, text, and particles only. Engine/system fonts.
4. Difficulty & session shape
- Ramp within the first 20 seconds (spawn interval shrinks, speeds grow with elapsed time — cap them so late-game stays playable).
- Target run length 30–90 seconds. The retry loop is the game; make dying cheap and restarting instant.
- Fairness: hazards must be visible before they can kill (spawn off-screen moving in, or telegraph with a brief flash). Deaths the player can't see coming are why players quit.
5. Score & persistence
- Score visibly increments with feedback (blip + tiny scale-pop).
- Persist best score in localStorage wrapped in try/catch (sandboxed iframes can throw); show "best" on the menu and game-over screens.
6. Performance rules
- Destroy everything that leaves the screen (
offscreen({ destroy: true })or lifespans). Leaked entities are the #1 cause of minigame slowdown. - Cap concurrent particles (~60) and spawned entities; reuse where trivial.
- Remove dead entities in place with a reverse
for+splice— neverarr = arr.filter(...)inside the loop. The per-frame realloc churns GC and stutters low-end phones. - One canvas; if hand-rolled, cap the backing store:
Math.min(devicePixelRatio, 2)on mobile and1on desktop. "DPR-aware" ≠ "use full devicePixelRatio" — 3× overdraw is fill-rate death on cheap phones. Never stack canvases per entity.
7. Anti-patterns (each is a real failure class)
alert()/prompt()for game messages — freezes the loop, blocked in sandboxes. In-canvas/in-DOM UI only.- Loading sprites/audio from URLs — instant black screen in the preview.
- A second rAF/
setIntervalloop beside the engine's — stutter + double updates. - KAPLAY:
k.area({ shape: new k.Circle(...) })— the collision engine only supports rect/polygon areas and hard-crashes on first contact. Use plaink.area()always; its rect approximation is correct for balls/orbs. - Keyboard-only controls, hover-dependent mechanics, pointer-lock or gamepad-only schemes — preview iframes and phones break all of these.
- Unbounded spawn loops without cleanup — slow-motion death by entity leak.
- Silent games — if a playtester would ask "is sound broken?", add SFX.