Orbit Snatch — Game Design
Date: 2026-05-28
Status: Approved (Superpowers demo)
Integration: svelte-component, single-player arcade
Problem
Funday needs a simple, novel, instantly playable arcade game that demonstrates platform best practices without multiplayer/Nakama complexity.
Approaches Considered
| Approach | Hook | Pros | Cons |
|---|---|---|---|
| A. Orbit Snatch (recommended) | One-button direction flip on a gem orbit | Unique mechanic, 30s to learn, combo dopamine | Circular collision needs careful tuning |
| B. Chrono Rewind Dash | Rewind 3s every 5s | Very novel | Hard to explain, complex UX |
| C. Flappy clone variant | Tap to rise | Fast to build | Not unique; jumpduck exists |
Recommendation: Orbit Snatch — one input, orbital motion, combo streaks, escalating speed. Distinct from peng (paddle), jumpduck (side-scroll), memory (turn-based).
Core Loop
- Player orbits a central star at fixed radius.
- Tap / Space / click flips orbit direction (CW ↔ CCW).
- Gems spawn on the ring; collect by passing through them.
- Hazards (spike arcs) rotate on the ring; touch = game over.
- Combo: consecutive gem collects without flipping multiply score (×1 → ×2 → ×3… cap ×5).
- Escalation: angular speed increases every 5 gems collected.
- Session: ~30–90 seconds; high score persists to leaderboard.
Architecture
games/orbit-snatch/index/
├── funday-plugin.json
├── assets/thumbnail.svg
├── vitest.config.ts
├── package.json
└── src/
├── Game.svelte # Platform bridge (hostUpdate)
├── components/
│ └── OrbitArena.svelte # Render + rAF loop
└── lib/
├── orbitEngine.ts # Pure logic (TDD)
├── orbitEngine.test.ts
└── audio.ts # Procedural Web Audio
- No Nakama match handler —
maxPlayers: 1, client-authoritative score likepeng. - hostUpdate reports
{ state, score, combo }to play-shell HUD. - DaisyUI theme tokens (
oklch(var(--p))) for platform theme sync.
Data Flow
Input (tap/space) → flipDirection(engine)
rAF tick → stepEngine(state, dt) → collisions → spawn entities
OrbitArena → hostUpdate({ state, score, combo })
Game over → hostUpdate({ state: 'finished', score, outcome: 'lose' })
Error Handling
- Audio init wrapped in try/catch (autoplay policy).
- Game loop cancels on component destroy.
- Engine is pure — no DOM side effects in tests.
Testing Strategy
- Unit:
orbitEngine.test.ts— direction flip, gem collect, hazard hit, combo math, speed ramp. - Manifest:
npm run validate:manifestsfrom frontend. - Build boundary:
check-game-boundaries.mjspasses with relative./imports fromsrc/Game.svelte.
Success Criteria
- Playable in
/play/orbit-snatchvia native mount - One-button controls work on desktop + touch
- Combo + speed escalation feel addictive in first 60s
- All engine unit tests pass
- Manifest validates
Orbit Snatch — Implementation Plan
Date: 2026-05-28
Design: 2026-05-28-orbit-snatch-design.md
Task Breakdown
Task 1: Pure engine + TDD (no UI)
Files: src/lib/orbitEngine.ts, src/lib/orbitEngine.test.ts, vitest.config.ts, package.json
- Write failing tests for:
flipDirectiontoggles sign- Gem collection when angles align within threshold
- Hazard collision ends run
- Combo increments on no-flip streak; resets on flip
- Speed increases every
GEMS_PER_SPEED_TIERgems
- Implement engine until green.
Task 2: Platform shell
Files: funday-plugin.json, assets/thumbnail.svg, src/Game.svelte
- Manifest:
svelte-component,maxPlayers: 1, entrysrc/Game.svelte Game.sveltewireshostUpdate, mountsOrbitArena
Task 3: Arena + juice
Files: src/components/OrbitArena.svelte, src/lib/audio.ts
- SVG ring arena with player dot, gems, hazard arcs
- rAF loop calling
stepEngine - Procedural collect/hit/flip sounds (no
'noise'oscillator — use buffer) - Screen pulse on collect; shake on death
Task 4: Verification
cd games/orbit-snatch/index && npm testcd frontend && npm run validate:manifests- Spot-check
svelte-checkon Game.svelte if needed
Checkpoints
| # | Gate | Command |
|---|---|---|
| 1 | Engine tests pass | npm test in game dir |
| 2 | Manifest valid | validate:manifests |
| 3 | No boundary violations | included in frontend build check |
Out of Scope (YAGNI)
- Nakama match handler
- Multiplayer lobby
- 3D / Threlte (2D SVG sufficient for arcade)
- IndexedDB persistence (leaderboard via platform on game over)
Orbit Snatch — Fun Improvement Plan
REQUIRED SUB-SKILL: Implement task-by-task. After all tasks →
fun-verify.
Goal: Make each run teach the next better move, make earned chains feel visible, and make intentional flips reliably feel more valuable than passivity.
Baseline: Fun Stack avg 3.0/5 (dev/docs/content/games/orbit-snatch/audits/orbit-snatch-fun-audit.md)
Target: avg >= 3.5/5, zero unresolved red killers
Date: 2026-05-28
Audit: dev/docs/content/games/orbit-snatch/audits/orbit-snatch-fun-audit.md
Audit Inputs
Intent: Hard Fun + Sensory Fun, 30-90s arcade burst, solo + MP. Promise: one-button mastery panic where the player flips at the perfect moment, snatches gems, dodges spikes, and chases one more best.
Anti-goals: No grind, no unfair deaths, no confusion, no griefing or toxic MP incentives.
Baseline Fun Stack: L1-L8 = 4, 5, 3, 2, 2, 3, 2, 3. Average 3.0/5.
Core loop gaps: Feedback weak, Learning broken, Reward weak.
Top findings in scope:
| Priority | Finding | Severity | Loop Link |
|---|---|---|---|
| 12.5 | Failure does not teach; death screen lacks cause and next-action lesson | Red | Learning broken, Feedback weak |
| 8.0 | Combo/peak payoff is underexposed in observed play | Yellow | Reward weak, Feedback weak |
| 6.7 | Skill/agency is opaque; no-input run beat active flipping | Red | Agency weak, Learning broken |
Implementation order follows the audit’s practical sequence: failure lesson, combo payoff, then agency tuning.
Finding 1: Failure Does Not Teach 🔴
Loop link / killer: Learning broken; Feedback weak; player feels stupid; player may quit after failing.
Approved approach: Pending user approval. Recommended: A.
Hypothesis table
| Approach | Change | Psychology | Effort | Risk |
|---|---|---|---|---|
| A (recommended) | Track last hazard collision, highlight it on death, and show a one-line next-action lesson. | Converts “I died somehow” into “I know what hit me and what to try.” Directly fixes the broken Learning link with small UI and engine data. | Medium | Requires shared state/type change across solo and MP; must keep payload compact. |
| B | Add a short replay ghost of the final second. | Strongest teaching signal and memorable failure moment. | High | More state history, animation complexity, and multiplayer sync risk than needed for first fix. |
| C | Add only generic failure copy: “Dodge red spikes; flip earlier.” | Very small diff and almost no engine risk. | Low | May still feel vague because it does not point at the actual hazard or timing mistake. |
Tasks
Task 1.1: Add Death Cause Data
Files:
- Modify:
games/orbit-snatch/index/shared/types.ts - Modify:
games/orbit-snatch/index/shared/orbitEngine.ts - Modify:
games/orbit-snatch/index/src/lib/orbitEngine.test.ts
Feel Outcome: Competence improves because the game can name what ended the run instead of presenting an unexplained “Orbit Broken.”
Verify: Unit test proves hazard collision stores the eliminated user, hazard id or angle, and player angle.
Implementation skill: game engine TDD.
- Add a compact
lastEliminationordeathRecapfield toArenaState. - Populate it in
checkHazardswhen a hazard eliminates a player. - Assert solo hazard death exposes the recap in
orbitEngine.test.ts.
Task 1.2: Surface Death Recap
Files:
- Modify:
games/orbit-snatch/index/src/components/OrbitArena.svelte
Feel Outcome: Learning improves because the failure overlay gives cause plus next-action guidance.
Verify: Browser playtest death overlay shows a concrete lesson such as “Spike clipped you near the top-left. Flip earlier or coast past the gap.”
Implementation skill: critique + svelte-code-writer.
- Derive a short direction label from the death recap angle.
- Add a one-line cause below
Orbit Broken. - Add a one-line next-action lesson that stays concise on small screens.
Task 1.3: Highlight The Killing Hazard
Files:
- Modify:
games/orbit-snatch/index/src/components/OrbitArena.svelte
Feel Outcome: Feedback improves because the player can visually connect the death text to the arena threat.
Verify: On death, the responsible red arc visibly pulses or remains emphasized for the overlay state.
Implementation skill: animate + svelte-code-writer.
- Detect the recap hazard in
drawFrame. - Draw a brief death highlight ring or brighter arc when
phase === 'finished'. - Respect
prefersReducedMotionby using static emphasis when reduced motion is enabled.
Task 1.4: Keep MP Elimination Consistent
Files:
- Modify:
games/orbit-snatch/index/shared/types.ts - Modify:
games/orbit-snatch/index/server/match_handler.ts - Modify:
games/orbit-snatch/index/src/components/OrbitArena.svelte
Feel Outcome: Social/Hard Fun stays fair because multiplayer players receive the same teachable elimination signal without new toxic language.
Verify: MP state payload still broadcasts and local eliminated player sees the recap.
Implementation skill: funday-play-shell + game engine TDD.
- Reuse the shared recap from authoritative state broadcasts.
- Avoid adding chatty or blame-oriented copy for opponent eliminations.
- Run solo tests first, then route-level MP smoke after implementation.
Finding 2: Combo/Peak Payoff Is Underexposed 🟡
Loop link / killer: Reward weak; Feedback weak; player wins or scores but feels little peak.
Approved approach: Pending user approval. Recommended: A.
Hypothesis table
| Approach | Change | Psychology | Effort | Risk |
|---|---|---|---|---|
| A (recommended) | Add combo tier copy, stronger collect burst for combo 3+, and end-run best-chain summary. | Makes mastery visible exactly where the current loop already has combo math. Smallest diff with high sensory payoff. | Low-Medium | Needs a new stat field or local tracking for best chain. |
| B | Add rare power-up gems with screen-clearing payoff. | Creates bigger peaks and strategic variety. | Medium-High | New mechanics may distract from fixing current reward clarity. |
| C | Add only louder sounds and bigger particles. | Quick sensory lift. | Low | Risks decoration without teaching why combos matter. |
Tasks
Task 2.1: Track Best Chain
Files:
- Modify:
games/orbit-snatch/index/shared/types.ts - Modify:
games/orbit-snatch/index/shared/orbitEngine.ts - Modify:
games/orbit-snatch/index/src/lib/orbitEngine.test.ts
Feel Outcome: Meaning and Memory improve because the run can summarize the player’s best mastery moment, not just score.
Verify: Unit test proves consecutive collects update bestCombo or equivalent, and flip resets current combo without losing best chain.
Implementation skill: game engine TDD.
- Add a persisted per-player best combo field.
- Update it when
collectGemsincrements combo. - Add tests for best combo preservation after a flip.
Task 2.2: Add Combo Tier Feedback
Files:
- Modify:
games/orbit-snatch/index/src/components/OrbitArena.svelte - Modify:
games/orbit-snatch/index/src/lib/audio.ts - Modify:
games/orbit-snatch/index/src/lib/particles.ts
Feel Outcome: Sensory Fun improves because combo 3+ becomes a noticeable “I did it” moment.
Verify: Playtest shows combo tier 3+ with visible text/particle/audio escalation, without drowning out normal collects.
Implementation skill: delight + animate + svelte-code-writer.
- Add a short tier label for combo 3+ near the HUD.
- Scale particle count or speed based on combo tier.
- Make
playCollectpitch or secondary tone clearly escalate at combo 3+ and 5+.
Task 2.3: Show Run Summary Payoff
Files:
- Modify:
games/orbit-snatch/index/src/components/OrbitArena.svelte
Feel Outcome: Reward and Memory improve because the final overlay reminds the player of the best chain they created.
Verify: Death overlay includes “Best Chain xN” when N > 1, with score and best still readable.
Implementation skill: critique + svelte-code-writer.
- Add best-chain text to the finished overlay.
- Keep the overlay hierarchy: outcome, score/best, best chain, lesson, Again.
- Confirm mobile-sized arena still fits without covering the button.
Task 2.4: Preserve Reward Signal In Host Updates
Files:
- Modify:
games/orbit-snatch/index/src/components/OrbitArena.svelte
Feel Outcome: Platform HUD/dock can reflect combo state if needed, keeping reward feedback consistent outside the canvas.
Verify: hostUpdate includes the current combo and best chain after collect and finish.
Implementation skill: funday-play-shell.
- Extend
report()with best-chain data if available. - Confirm existing
score,combo,mode, andtimeLeftfields remain unchanged. - Do not add a new platform API unless current
hostUpdatecan carry the data.
Finding 3: Skill/Agency Is Opaque 🔴
Loop link / killer: Agency weak; Learning broken; no-input strategy outperformed active flipping in observed runs.
Approved approach: Pending user approval. Recommended: A.
Hypothesis table
| Approach | Change | Psychology | Effort | Risk |
|---|---|---|---|---|
| A (recommended) | Add first-run flip timing coaching, near-hazard telegraph, and a small engine test that intentional pathing can beat passivity in a seeded scenario. | Makes the one button feel learnable and proves active play can win a representative setup. | Medium | Requires careful tuning so coaching does not over-explain or make the game too easy. |
| B | Change combo design so flipping never resets combo; reward every active flip. | Makes input feel less punishing immediately. | Medium | Undercuts the original risk/reward premise: “consecutive gems without flipping multiply score.” |
| C | Delay hazards heavily in the first 10 seconds. | Reduces early frustration and gives room to learn. | Low | Could preserve the inverted strategy if doing nothing still scores too well. |
Tasks
Task 3.1: Prove Active Pathing Scenario
Files:
- Modify:
games/orbit-snatch/index/src/lib/orbitEngine.test.ts - Modify:
games/orbit-snatch/index/shared/orbitEngine.tsonly if the test exposes a tuning gap.
Feel Outcome: Agency improves because implementation is anchored to a falsifiable rule: timely flips should beat passivity in a representative opening.
Verify: A deterministic test compares no-flip vs timed-flip score or survival in a seeded setup.
Implementation skill: game engine TDD.
- Create a small deterministic scenario with one gem and one hazard.
- Simulate no-input and a timed flip.
- Assert the timed flip survives longer or scores better.
Task 3.2: Add First-Run Timing Hint
Files:
- Modify:
games/orbit-snatch/index/src/components/OrbitArena.svelte
Feel Outcome: Clarity and Competence improve because the player gets a timing model before the first punishing failure.
Verify: First practice run shows a concise hint such as “Flip to dodge red; coast through gems for chains,” then fades or hides after first score/death.
Implementation skill: critique + svelte-code-writer.
- Track whether the player has scored or died in the current run.
- Show the hint only during early play.
- Keep menu copy unchanged unless the in-run hint makes it redundant.
Task 3.3: Telegraph Near-Hazard Danger
Files:
- Modify:
games/orbit-snatch/index/src/components/OrbitArena.svelte
Feel Outcome: Responsiveness and Agency improve because the player sees when their current path is becoming dangerous before impact.
Verify: When the local player is approaching a hazard within a tight angle window, the hazard or player cue warns without guaranteeing survival.
Implementation skill: animate + svelte-code-writer.
- Compute a local near-hazard warning from existing
arena.hazardsandme.angle. - Add a subtle warning cue to
drawFrameor HUD. - Disable flashing motion under reduced-motion preference.
Task 3.4: Tune Early Passive Scoring If Needed
Files:
- Modify:
games/orbit-snatch/index/shared/orbitEngine.ts - Modify:
games/orbit-snatch/index/src/lib/orbitEngine.test.ts
Feel Outcome: Hard Fun improves because the player believes their input matters; doing nothing should not reliably outperform active learning.
Verify: After the test and playtest, no-input novice run no longer clearly beats a basic timed-flip attempt.
Implementation skill: game engine TDD.
- Only tune spawn timing, collect threshold, hazard timing, or speed if Task 3.1/playtest proves passivity still dominates.
- Keep changes minimal and measurable.
- Re-run the full game test suite after any tuning.
Backlog (Out Of Scope)
| Finding | Priority | Defer reason |
|---|---|---|
| Full multiplayer Social Fun audit | Unknown | Audit did not include a full 2+ session MP run; needs separate evidence. |
| Telemetry for death reason, time-to-first-score, flip count, combo distribution, retry count | Useful | Adds measurement depth but is not required for the first feel fix. |
| Hydration/perceived loading polish | Yellow | Blank shell resolved after hydration and is not one of the top feel blockers. |
| Full a11y/performance/responsiveness audit | Separate quality gate | Run after feel blockers are addressed so it audits the final interaction shape. |
| Replay ghost | High-value future teaching | Deferred because Approach A fixes the broken Learning link with less state and animation complexity. |
| New power-up gems | Potential peak feature | Deferred to avoid adding mechanics before current combo reward is legible. |
fun-verify checklist (pre-filled)
| Check | Baseline | Target | Method |
|---|---|---|---|
| Fun Stack avg | 3.0/5 | >= 3.5/5 | 3-persona playtest: novice no-input, rapid flips, competent rhythm |
| Competence | 2/5 | >= 3/5 | Failure overlay explains cause and next action; active player can state what to try next |
| Agency | 2/5 | >= 3/5 | Timed-flip attempt beats or clearly improves over no-input in seeded/playtest evidence |
| Feedback | Weak | Pass | Flip, hazard threat, combo tier, and death cause are visible or audible |
| Reward | Weak | Pass | Combo tier 3+ creates a visible peak; end overlay shows best chain |
| Red killers | 3 observed/risk | 0 unresolved | Killer sweep after playtest |
| Peak moment | Not visible in active runs | Stronger | Player can identify a best combo or clutch dodge moment |
Verification record (fill after fun-verify)
Date:
Result: PASS | FAIL
Stack avg: 3.0 →
Evidence:
Historical intent versus release outcome
This document serves as a historical record of the original 2026-05-28 intent. As of the 2.0.1 release, the focus is on exact geometry, local sector clarity, a 600 ms capability-gated arming delay, and a 300 ms frozen culprit frame. Certain features like broader onboarding or new game design mechanics remain unresolved hypotheses.