# 1v1 Games Migration Plan (Nuxt → rebreak-native)

Status: Recon abgeschlossen 2026-05-07. Read-only Analyse, kein Code-Touch.

Author scope: Migration der bestehenden Nuxt-1v1-Implementierung (TicTacToe + Memory) aus `~/mono/trucko-monorepo/apps/rebreak/` in die neue React-Native-App `~/mono/rebreak-monorepo/apps/rebreak-native/`. Letzter Schritt vor finalem Nuxt-Cutover (DiGA).

---

## 1. Status quo Nuxt-Implementierung

### 1.1 Frontend (Vue/Nuxt)

| Datei | Zweck |
|---|---|
| `~/mono/trucko-monorepo/apps/rebreak/app/pages/app/game/[challengeId].vue` (802 LOC) | Haupt-Game-Page. Lobby (Waiting), Live-Board für TicTacToe + Memory, Status, Lyra-Bubble, Tabs (History + Ranking), Rematch, Live-Share-Toggle. Subscribed Supabase-Realtime auf `rebreak.game_challenges`-Row. |
| `~/mono/trucko-monorepo/apps/rebreak/app/components/sos/GameTicTacToe.vue` | Solo-Modus mit Lyra-AI. Enthält "Gegen echten Spieler"-Button (Z. 73-76) — POST `/api/games/challenge` + Redirect. |
| `~/mono/trucko-monorepo/apps/rebreak/app/components/sos/GameMemory.vue` | Solo-Memory mit "Gegen echten Spieler"-Button (Z. 48-52) — POST `/api/games/challenge-memory` + Redirect. |
| `~/mono/trucko-monorepo/apps/rebreak/app/components/CommunityPostCard.vue` | Rendert "Challenge annehmen"-Button für Community-Posts mit `category="challenge"` (Z. 288, 469-479). |
| `~/mono/trucko-monorepo/apps/rebreak/app/stores/community.ts` | Pinia-Store, hält `challengeId` an Posts (Z. 8, 250). |

**File-Count Frontend: 5 relevante Vue-Files (1 Page + 2 Solo-Game-Components mit 1v1-Hook + 1 PostCard + 1 Store).**

### 1.2 Backend (Nuxt-Server, Nitro)

Backend liegt **nicht** in einem separaten trucko-backend-Service, sondern im selben Nuxt-Projekt unter `apps/rebreak/server/`. Endpoints:

| Endpoint | File |
|---|---|
| `POST /api/games/challenge` | `server/api/games/challenge.post.ts` (38 LOC) — TicTacToe-Challenge erzeugen + Community-Post |
| `POST /api/games/challenge-memory` | `server/api/games/challenge-memory.post.ts` (62 LOC) — Memory-Challenge erzeugen (16 Karten, shuffled) |
| `GET /api/games/challenge/[id]` | `server/api/games/challenge/[id].get.ts` (16 LOC) — Lade Challenge-State |
| `POST /api/games/challenge/[id]/accept` | `server/api/games/challenge/[id]/accept.post.ts` (35 LOC) — Gegner tritt bei, Status: OPEN → ACTIVE |
| `POST /api/games/challenge/[id]/move` | `server/api/games/challenge/[id]/move.post.ts` (109 LOC) — TicTacToe-Move; Win-Check, Score-Update, Post-Cleanup |
| `POST /api/games/challenge/[id]/memory-move` | `server/api/games/challenge/[id]/memory-move.post.ts` (152 LOC) — Memory-Move (Flip/Match/Mismatch) |
| `POST /api/games/challenge/[id]/rematch` | `server/api/games/challenge/[id]/rematch.post.ts` (64 LOC) — Neue Challenge mit Gegner pre-set, status=ACTIVE |
| `POST /api/games/challenge/[id]/live-toggle` | `server/api/games/challenge/[id]/live-toggle.post.ts` (35 LOC) — `isLive`-Flag für Spectators |
| `GET /api/games/history` | `server/api/games/history.get.ts` (44 LOC) — Spielhistorie (alle, oder vs Gegner) |
| `GET /api/games/ranking` | `server/api/games/ranking.get.ts` (15 LOC) — Top-Spieler-Liste |

**File-Count Backend: 10 Endpoints, ~570 LOC.**

### 1.3 DB-Schema

Aus `~/mono/trucko-monorepo/apps/rebreak/prisma/schema.prisma`:

- `enum GameChallengeStatus` (Z. 424): `OPEN | ACTIVE | FINISHED | CANCELLED`
- `model GameChallenge` (Z. 433-452, Tabelle `rebreak.game_challenges`): id, challengerId, challengerName, opponentId, opponentName, status, board (TEXT, default `---------`), currentTurn, winner, postId, gameType (default "tictactoe"), isLive, memoryState (Json), timestamps.
- `model GameScore` (Z. 470, Tabelle `rebreak.game_scores`): userId PK, playerName, wins, losses, draws, points (3 für Sieg, 1 für Unentschieden).
- `model GameRating` (Z. 483) und `GameHighScore` (Z. 496) — gehören zum Solo-Mode, irrelevant für 1v1, aber bereits portiert.

Migrations-SQL:
- `~/mono/trucko-monorepo/apps/rebreak/prisma/migrations/add_game_challenges.sql` — Enum, Table, Indexes, `ALTER PUBLICATION supabase_realtime ADD TABLE rebreak.game_challenges`
- `~/mono/trucko-monorepo/apps/rebreak/prisma/migrations/add_game_challenges_rls.sql` — RLS-Policies (read/insert/update für challenger + opponent via `auth.uid()`)
- Spätere Patches haben `gameType`, `isLive`, `memoryState` hinzugefügt (in den live-DB Tabelle vorhanden, kein eigenes Migration-File gefunden — Schema-Drift-Verdacht in Nuxt).

### 1.4 State-Sync-Mechanismus (1 Satz)

**Server-authoritative State in Postgres (`rebreak.game_challenges`-Row); Frontend mutiert via REST-POST und subscribed parallel auf Supabase-Realtime `postgres_changes` UPDATE-Events der eigenen Row → keine Polling, keine WebSocket-Eigenbau.**

### 1.5 Datenflussdiagramm (ASCII)

```
        Spieler A (Challenger)                       Spieler B (Opponent)
       ─────────────────────                        ─────────────────────
              │                                              │
   POST /api/games/challenge                                 │
              │                                              │
              ▼                                              │
   ┌──────────────────────┐                                  │
   │  game_challenges     │   communityPost.challengeId      │
   │  status=OPEN         │◀────────────────────────────────┐│
   │  board=---------     │                                 ││
   └──────────┬───────────┘                                 ││
              │                                              ▼
              │                                    GET /api/community/posts
              │                                    (sees challenge card)
              │                                              │
              │                  POST /api/games/challenge/[id]/accept
              │                                              │
              ▼                                              ▼
   ┌────────────────────────────────────────────────────────────┐
   │  game_challenges  status=ACTIVE  opponent_id=B             │
   └──────────────────────────┬─────────────────────────────────┘
                              │
                              │  Supabase Realtime (postgres_changes)
                              │  channel = `game:<id>:<ts>`
                              │  filter = id=eq.<challengeId>
                              ▼
              ┌──────────────────────────────┐
              │ both clients update UI       │
              └──────────┬───────────────────┘
                         │
        loop until FINISHED:
              │
   POST /api/games/challenge/[id]/move (or memory-move)
              │
              ▼
   ┌────────────────────────────────────────────────────────────┐
   │  Server validates turn + writes new board / memoryState    │
   │  on win/draw → upsert game_scores, delete community post  │
   └──────────────────────────┬─────────────────────────────────┘
                              │ Realtime UPDATE → both clients
                              ▼
              ┌──────────────────────────────┐
              │ FINISHED screen + Rematch    │
              └──────────────────────────────┘
```

---

## 2. Migration-Plan

### Phase A — Backend-Endpoints in rebreak-monorepo

**Status: BEREITS PORTIERT.** Verifiziert per `diff`:
- `~/mono/rebreak-monorepo/backend/server/api/games/challenge.post.ts` ist byte-identisch mit Nuxt.
- `~/mono/rebreak-monorepo/backend/server/api/games/challenge/[id]/move.post.ts` ist byte-identisch.
- Alle 10 Endpoints existieren bereits unter `~/mono/rebreak-monorepo/backend/server/api/games/`.

→ **Aufwand Phase A: 0 h.** Nur ein leichter Smoke-Test (curl Request mit Bearer-Token gegen den staging-Nitro) zur Bestätigung dass die Endpoints im Nitro-Prod-Build aktiv sind.

### Phase B — DB-Migrations für game_sessions

**Status: BEREITS PORTIERT.** Schema verifiziert:
- `enum GameChallengeStatus` in `~/mono/rebreak-monorepo/backend/prisma/schema.prisma` Z. 424 vorhanden.
- `model GameChallenge`, `GameScore`, `GameRating`, `GameHighScore` alle vorhanden.

**Offene Punkte (klein):**
1. SQL-Migration unter `backend/prisma/migrations/` muss verifiziert werden — sind `gameType`, `isLive`, `memoryState`-Spalten in einer eigenen Migration angelegt? Falls nein: ein konsolidiertes `add_game_challenges.sql` nachziehen.
2. RLS-Policies und `ALTER PUBLICATION supabase_realtime ADD TABLE rebreak.game_challenges` müssen am Staging-DB-Cluster bestätigt werden (gleicher DB für Nuxt + RN-Backend, also vermutlich schon aktiv).

→ **Aufwand Phase B: 1-2 h** (SQL-Audit + ggf. ein Catch-up-Migration-File).

### Phase C — RN-UI-Komponenten

**Status: KOMPLETT NEU.** RN-App hat aktuell:
- `apps/rebreak-native/components/urge/UrgeGames.tsx` (1067 LOC) — Solo-Mode für Memory/TicTacToe/Snake/Tetris.
- `apps/rebreak-native/app/games.tsx` — Standalone-Games-Page (Solo).
- KEIN Community-Komponent, KEIN Game-Page für 1v1.

**Zu erstellen:**

1. `apps/rebreak-native/app/(app)/game/[challengeId].tsx` — Pendant zu `pages/app/game/[challengeId].vue`. RN-Expo-Router-File. Ports:
   - Loading + Lobby (`OPEN`-Status, Waiting-Screen mit Cancel-Button)
   - TicTacToe-Board (3x3 Grid, X/O-Marker, WinLine-Highlight) — `Pressable`-Cells statt `<button>`
   - Memory-Board (4x4 Grid, Score-Header, Mismatch-Reveal, Progress-Bar)
   - Lyra-Bubble (Avatar + animierter Phrase-Text, optional TTS-Toggle — bereits vorhandene `lib/sosTtsQueue.ts`-Infra wiederverwendbar)
   - Status/Result-Section + Rematch-Button
   - History-Tab + Ranking-Tab

2. `apps/rebreak-native/components/games/Game1v1Board.tsx` (optional, falls zu monolithisch) — Sub-Component für Board-Rendering.

3. **1v1-Entry-Buttons in `UrgeGames.tsx`** — analog Vue, pro TicTacToe und Memory Solo-Mode einen "Gegen echten Spieler"-Button hinzu, der `POST /api/games/challenge[-memory]` callt und auf `/game/[id]` navigiert.

4. **Community-Listing-View** — Aktuell hat RN-App keine Community-Tab. Entweder:
   - **Option a:** Existing community-Page aus Nuxt nach RN portieren (separater großer Task).
   - **Option b:** Erstmal nur eine **"Open Challenges"-Liste** unter `/game/index.tsx`, die alle `OPEN`-Challenges (eigener Endpoint nötig: `GET /api/games/challenges?status=OPEN`) listet.
   - **Option c (empfohlen):** Direkter Invite-Flow per Share-Link `/game/[id]` (Deep-Link funktioniert bereits in Expo) — kein Community-Browsing nötig für DiGA-Cutover.

→ **Aufwand Phase C: 12-20 h** (1 Page mit 2 Game-Modes + Realtime + Lyra + History-Tab + Ranking-Tab + Lobby-Flow). Größte Position.

### Phase D — Realtime-Wiring

**Status: INFRA VORHANDEN.** `apps/rebreak-native/lib/supabase.ts` hat bereits `realtime`-Konfig.

**Zu tun:**
- Im neuen `[challengeId].tsx` analog zu Vue: `supabase.channel(...).on('postgres_changes', { schema: 'rebreak', table: 'game_challenges', filter: `id=eq.${id}` })`.
- React-Native-Spezifika: AppState-Listener für Reconnect bei Background → Foreground (Vue-Variante hat nur passive Reconnect bei `CHANNEL_ERROR`).
- Auth-Token via `supabase.realtime.setAuth(session.access_token)` — identisch zu Vue.

→ **Aufwand Phase D: 2-3 h** (im Rahmen Phase C).

### Phase E — Testing + Deploy

- **Manueller 2-Device-Test** (iOS-Simulator + Android-Emulator simultan): Challenge erstellen, accepten, abwechselnd Züge, Win/Draw, Rematch.
- **Disconnect-Resilience**: Airplane-Mode toggle während ACTIVE — Realtime muss reconnecten.
- **Deep-Link-Test**: `rebreaknative://game/<id>` aus geteiltem Link.
- **EAS-Preview-Build** für Tester.

→ **Aufwand Phase E: 4-6 h** (inkl. Bugfixes).

**Gesamtaufwand: ~20-30 h Net-Coding**.

---

## 3. Architektur-Empfehlung — was wir besser machen

Die Nuxt-Implementierung ist solide (server-authoritative + Supabase-Realtime ist die richtige Wahl). Drei Verbesserungen für die RN-Variante:

1. **Kein Community-Post-Coupling.** Die Nuxt-Variante erstellt für jede Challenge automatisch einen Community-Post (`category="challenge"`) und löscht ihn beim Spielende. Das verschmiert Game-Lifecycle und Community-Layer. Empfehlung: 1v1-Challenges leben in einer eigenen Tabelle / einem eigenen "Open-Challenges"-Endpoint, ohne Cross-Coupling. Macht Phase-C-Cleanup einfacher und entkoppelt RN-Cutover von Community-Migration.

2. **Optimistic UI mit Rollback.** Aktuell wartet Vue auf den POST-Response, bevor das Board updated → 100-300 ms Lag pro Move. RN-Variante: lokal sofort renderen (gleiche Validierungslogik clientseitig spiegeln) und auf Realtime-UPDATE reconcilen. Bei Validation-Error vom Server: rollback + Toast. Macht das Spiel "snappier" auf flackrigen Mobilfunk-Verbindungen.

3. **Heartbeat / Idle-Cancel.** Nuxt hat keinen Cleanup für tote `OPEN`-Challenges. RN-Variante: Cron-Job im Backend (`backend/server/api/cron/`-Pattern existiert bereits) markiert OPEN-Challenges nach 30 min als CANCELLED, und ACTIVE-Challenges ohne Move seit 10 min ebenfalls. Entlastet die DB und verhindert Geister-Posts.

4. **Bonus**: `gameType` zum Enum machen statt `String @default("tictactoe")` — kleine Schema-Hygiene.

---

## 4. Risk-Assessment

| Risk | Severity | Mitigation |
|---|---|---|
| Realtime-Latenz auf Mobilfunk | mittel | Optimistic UI (siehe oben). 100-500 ms ist für TicTacToe/Memory ok (Async-Style). |
| Anti-Cheat / Move-Spoofing | niedrig | Server-authoritative ist bereits implementiert (Server prüft Turn + überprüft Board-State). RLS-Policies erlauben Updates nur für Teilnehmer — aber Updates passieren ohnehin via Service-Role-Backend, RLS ist nur für Realtime-Read. |
| Disconnect mid-game | mittel | Aktuell: Spiel "hängt" bis Spieler zurückkommt; kein Auto-Forfeit. Risk: Spieler quittet → Gegner stuck. Mitigation: Heartbeat + Auto-Cancel nach 10 min Inaktivität (Phase E Verbesserung) + UI-"Gegner offline"-Badge. |
| Plan-Tier-Gate | niedrig | Aktuell **kein** Plan-Check in den Endpoints — alle Auth-User können challengen. Falls Pro-Only gewünscht: in `challenge.post.ts` und `challenge-memory.post.ts` ein `requirePlan('pro')` einfügen (User-Decision). |
| Schema-Drift Nuxt vs Rebreak-Monorepo | mittel | Beide Projekte teilen physisch dieselbe DB (Schema `rebreak`). Solange beide Schemas synchron sind, kein Problem. Beim Cutover: Nuxt komplett deaktivieren, sonst race conditions auf `game_challenges`. |
| Realtime-Quota-Kosten | niedrig | Supabase Realtime hat ~200 concurrent connections im Pro-Plan. Bei 1v1 = 2 Subscriber/Match → erst ab 100 parallelen Matches problematisch. Monitoring per Supabase-Dashboard. |
| Memory-Game state.cards Größe | niedrig | 16 Cards JSON-Blob ~1 KB pro UPDATE → vernachlässigbar. |
| iOS Background WebSocket | mittel | iOS killed Realtime-Channels nach ~30 s im Background. Reconnect-on-resume zwingend (AppState-Listener). |

**Top-3 Risks:** 1) Disconnect mid-game ohne Auto-Forfeit; 2) iOS Background-WebSocket-Drop; 3) Schema-Drift während Übergangsphase Nuxt+RN parallel.

---

## 5. Open Questions an User

1. **Live oder Async?**
   Aktuelle Nuxt-Implementierung ist **Live** (Realtime + Same-Session). Empfehlung für RN: **Live beibehalten**, weil Netcode steht. Async-Wordle-Style würde komplette Re-Architektur erfordern (Push-Notifications, Move-Queue) und 2-3x Aufwand bedeuten.

2. **Random-Matchmaking vs Friends-only vs Community-Post?**
   Nuxt nutzt Community-Post (jeder kann annehmen, kein Friend-Graph). Optionen für RN:
   - **a)** Community-Post-Style portieren (braucht Community-View in RN — großer separater Task).
   - **b)** Public "Open Challenges"-Liste auf `/games`-Page (klein, schnell).
   - **c)** Share-Link-Invite (`rebreaknative://game/<id>` per Native-Share-Sheet — kein Browse nötig).
   - **d)** Random-Pool: Server-Side-Matchmaking (setzt 2 OPEN-Challenges paarweise zusammen — kein UI-Touch).
   → **User muss entscheiden.** Empfehlung: c + b kombiniert für MVP.

3. **Public Leaderboard mit Win-Rate-Stats?**
   `GameScore`-Tabelle existiert + `/api/games/ranking`-Endpoint. Frage: soll Leaderboard
   - **a)** in jedes Game integriert (aktueller Nuxt-Stand: Tab im `[challengeId].vue`),
   - **b)** als globale Page `/games/leaderboard`,
   - **c)** beides?

4. **Plan-Tier-Gate?** 1v1 für Free-Tier verfügbar oder Pro-Only? (DiGA-Relevanz unklar.)

5. **Anonymität:** Sollen Gegnernamen anonymisiert sein? Aktuell wird `nickname || username || "Anonym"` benutzt. DiGA-Datenschutz?

6. **User-Quit-Verhalten:** Wenn ein Spieler die App schließt mid-game — Forfeit nach X min, oder Spiel hängt offen? Empfehlung: Auto-Cancel nach 10 min Inaktivität, kein Forfeit (= kein Punkteabzug).

---

## 6. Migration-Aufwand-Summary

| Phase | Aufwand | Status |
|---|---|---|
| A — Backend-Endpoints | 0 h | bereits portiert |
| B — DB-Migrations | 1-2 h | Schema da, SQL-Audit nötig |
| C — RN-UI Game-Page + Lobby | 12-20 h | komplett neu |
| D — Realtime-Wiring | 2-3 h | im Rahmen C |
| E — Testing + Deploy | 4-6 h | manuelle 2-Device-Tests |
| **Gesamt** | **~20-30 h** | |

Plus optional: Community-View-Migration (separater Plan) für Community-Post-Style-Matchmaking.

---

*Doc-Version 1.0 — 2026-05-07 — `~/mono/rebreak-monorepo/ops/GAMES_1V1_MIGRATION_PLAN.md`*
