# 8 — Punktesystem mit Bedeutung (`apps/loyalty`) **Wirkrichtung:** Bindung. Läuft als Modul im `overlay-server`. ## Warum nicht Channel Points? Twitch-Channel-Points funktionieren, haben aber zwei Grenzen: Man kann die Vergabelogik nicht anpassen (keine Boni für Verhalten, das man belohnen will), und Einlösungen sind an das Twitch-UI gebunden. Eigene Punkte laufen daneben. **Beides parallel betreiben.** Channel Points bleiben für simple Sachen, eigene Punkte für alles, was Logik braucht. `channel.channel_points_custom_reward_redemption.add` wird ohnehin schon ingested, also können beide Systeme dieselben Aktionen auslösen. ## Vergabe | Anlass | Punkte | |---|---| | pro 10 Minuten Anwesenheit | 10 | | ...davon aktiv im Chat | +5 | | erste Nachricht des Streams | 25 | | Anwesenheit in Streamminute 1–10 | +50 (belohnt pünktliches Erscheinen) | | Follow | 200 (einmalig) | | Sub / Resub | 500 | | Raid rein | 300 an den Raider | | Clip, der übernommen wird | 250 an den Auslöser | Anwesenheit kommt aus `user_streams.minutes` (Helix-Chatter-Liste alle 5 min), nicht aus dem Chat allein — sonst bekommen stille Zuschauer nie etwas. ## Shop Der Punkt der Sache ist, dass Einlösungen **wirklich etwas tun**: | Artikel | Kosten | Aktion | Freigabe | |---|---|---|---| | Streamtitel für 10 min | 5.000 | `PATCH /helix/channels`, Timer setzt zurück | ja | | Emote-Regen 30 s | 800 | Overlay-Effekt | nein | | Sound aus dem Board | 300 | Overlay + Audio | nein | | Challenge für Abdu | 3.000 | Karte im Overlay, Abdu bestätigt | ja | | Nächstes Spiel bestimmen | 10.000 | Eintrag in die Warteschlange | ja | | Facecam-Filter 60 s | 1.500 | obs-websocket Filter an | nein | `requires_approval` ist bei allem gesetzt, was den Stream nach außen verändert (Titel!) oder Abdu zu etwas verpflichtet. Titeländerung zusätzlich abgesichert: Wortfilter, Längenbegrenzung, automatische Rückkehr zum Originaltitel per Timer, und der Originaltitel wird vorher in `stream_segments` gesichert. ## Rückerstattung Wenn eine Aktion fehlschlägt (OBS nicht erreichbar, Helix-Fehler, Abdu lehnt ab), werden die Punkte automatisch zurückgebucht und der User im Chat informiert. `redemptions.status = 'refunded'`. ## Bilanz-Integrität Punkte werden nie direkt auf `users.points` geschrieben, sondern als Zeilen in einer `points_ledger`-Tabelle geführt; `users.points` ist ein Materialized-Summenfeld, das über einen Trigger aktualisiert wird. So ist jede Punktzahl nachvollziehbar und ein Fehler in der Vergabelogik reparierbar, ohne dass jemandem grundlos Punkte fehlen. ## Abnahmekriterien - Ein Zuschauer, der 30 min anwesend ist und dreimal schreibt, hat exakt die erwartete Punktzahl. - Titel-Einlösung ändert den Titel und setzt ihn nach 10 min zurück, auch wenn der Server dazwischen neu startet (Timer wird persistiert). - Fehlgeschlagene Einlösung bucht zurück. - `points_ledger`-Summe stimmt mit `users.points` überein.