Idle-Game (Vue 3 SPA + Supabase), in dem Spieler Zoo-Tiere sammeln, ausrüsten und über Minispiele Coins/Tickets verdienen. Dieser Leitfaden fasst zusammen, was ein Agent wissen muss, bevor er Code anfasst.
- Vue 3 (
<script setup>, JS — kein TypeScript), Pinia, vue-router (Hash-History), PrimeVue 4 (Aura, immer hell), Three.js (nur Minispiele/Welt), Capacitor 8 (Android), Vite. - Backend: Supabase (Projekt-ID
rkskpvbismdlsevaqoer, eu-north-1). npm run dev— Dev-Server (Preview-Browser hat eine eingeloggte Test-Session; Views direkt per Hash-Route ansteuerbar, z. B.#/world).npm test—node --testüber allesrc/*.test.js(reine Logik + SQL-Regex-Tests).npm run build— Vite-Build (muss vor Abschluss grün sein).
- Server-autoritative Wirtschaft: Alles mit Coins/Tickets/Tieren läuft über
Postgres-RPCs (
security definer set search_path = public, Grants nur fürauthenticated,revoke ... from anon, public). Der Client rechnet nie selbst: Antworten liefern absolutecoins/tickets+server_now. - RPC-Muster im Client: vor Käufen
await game.persist(), dannsupabase.rpc('name', { p_* }), danachgame.coins = Number(data.coins)undserverOffset = new Date(data.server_now).getTime() - Date.now(). - Reward-Spiegel: Belohnungsformeln existieren doppelt — SQL-Funktion
(z. B.
_parkour_reward) und Client-Vorschau (z. B.parkourReward()in der View). Bei Balance-Änderungen immer beide Seiten synchron ändern. - Migrationen: eine Datei pro Feature unter
supabase/migrations/(YYYYMMDD_feature.sql), angewendet zusätzlich via MCPapply_migration. Jede neue Tabelle: RLS an, Policies nur Select, Schreiben über RPCs. Achtung: Views nach Neuaufbau wiedersecurity_invokersetzen. - Tests: reine Logikmodule (
src/foo.js) bekommensrc/foo.test.js; Migrationen bekommensrc/fooSql.test.jsmit Regex-Prüfungen auf RLS, Revokes, search_path-Pins und Formeln (Vorlage:src/wordleSql.test.js). - Routen: flach, lazy,
meta: { auth: true }insrc/router.js; Minispiele/Features als eigene Top-Level-Route (/drift,/parkour,/wordle,/world). - Einstiege in GameView: Jedes Feature bekommt eine Quick-Action-Kachel
(
.qa-btn) und einen Full-Width-Kartenlink (Muster.parkour-link).
- Tokens in
src/styles.css:1-32:--bg #fdf2d9,--card,--accent #f4a912,--accent-2,--purple,--danger,--border,--radius: 22px,--space-1..6,--safe-top/--safe-bot(env(safe-area-inset-*)). Keine Navy-Hexcodes; immer Tokens verwenden. - Chunky Buttons:
.btn(Gold-Gradient, harter Bottom-Shadow), Varianten.secondary,.danger,.btn-ghost,.full. PrimeVue-Komponenten (Button, InputText, Select, Toast …) sind global registriert — nicht importieren. - App-Shell max. 560px breit; Fullscreen-Spiele als Teleport-Overlay
(
position: fixed; inset: 0; touch-action: none), HUD mitpadding: calc(8px + var(--safe-top)), Steuerelemente mitbottom: calc(14px + var(--safe-bot)). - i18n: de/en/ru. Neuere Feature-Views halten ein lokales
I18N-Dict +tx(key, vars)-Helfer (VorlageParkourGameView.vue); globale Keys liegen insrc/i18n.js. Deutsch immer mit echten Umlauten (ä ö ü ß). - Feedback über
useAppToast()(ok/err/info), Reload nach App-Rückkehr überuseReturnRefresh(loader), Busy-Zustände als String-RefbusyKey.
- Engine als framework-freie Klasse (Vorlagen
src/parkourEngine.js,src/worldEngine.js):constructor(canvas, callbacks),async init()mit dynamischemimport('three')(Code-Splitting),resize(w,h),dispose()mit_track()/_disposables+renderer.forceContextLoss()— kein WebGL-Leak. Pixel-Ratio ≤ 2, InstancedMesh für Wiederholtes, keine Echtzeit-Schatten (Blob-Shadows). - View-Muster: Engine in nicht-reaktiver
let-Variable,await nextTick()vor Erzeugung, Guard nachawait init(),dispose()inonUnmounted.
Spec: docs/superpowers/specs/2026-08-03-zoo-welt-design.md.
src/world.js= reine Logik (Ring-Layout der Farm-Bauplätze, Collider, Zonen, Katalog-Visual-Fallbacks,FOUNTAIN_REWARD-Spiegel vonworld_fountain_claim).src/worldEngine.js= Rendering.WorldView.vue= HUD/Realtime/RPCs.VirtualJoystick.vue= Touch-Joystick.- DB:
world_state(Position, Bauplatz ausworld_plot_seq, Kosmetik),world_items(Katalog;cost = 0⇒ alle besitzen es),world_purchases. RPCs:world_enter,world_save_pos(Clamp ±95),world_buy_item,world_equip,world_set_leash,world_fountain_claim(1×/UTC-Tag),world_players(aktive Spieler + ausgerüstete Tiere). - Realtime: Kanal
world:lobby— Presence (Key = User-ID, Payload = Erscheinungsbild) + Broadcastpos(~7 Hz nur bei Bewegung) undemote. Positionen sind Kosmetik; Realtime nie als Wirtschaftsquelle verwenden. - Supabase-JS-Falle:
supabase.rpc(...)ist lazy (thenable) — Fire-and-forget braucht.then(null, () => {}), sonst wird die Query nie ausgeführt (.catch?.()startet sie nicht).
main.jsblockt Pinch/Double-Tap global — eigene Touch-Flächen brauchentouch-action: none+ Pointer-Capture (sieheVirtualJoystick.vue).localeist ein ref aussrc/i18n.js;tx()/t()im Template aufrufen, damit Sprachwechsel re-rendert.- Countdown/Zeit immer über
Date.now() + game.serverOffset, nie lokale Uhr. - Intervalle nur bei
document.visibilityState === 'visible'ticken lassen. species_costs.enabled=false⇒ nicht in Shop-Rotation;shop_visible=false⇒ zusätzlich im Frontend versteckt (Craft-only-Spezies).
- Design-Specs:
docs/superpowers/specs/(eine pro Feature, vor der Implementierung committen — gleiche Reihenfolge auch für neue Features). - Pläne:
docs/superpowers/plans/.