Guide streamer
# Streamio — Streamer & Viewers
Jeu type **Gartic Phone** intégré au stream. N joueurs écrivent une phrase, qui est passée de main en main en alternant phrase ↔ dessin, puis les "albums" sont révélés un à un avec rejouage stroke par stroke.
> Cadrage initial : voir `projet.md`.
> Cette page est la source de vérité **utilisateur** des fonctionnalités effectives.
## État d'implémentation
| Lot | Fonctionnalité | État |
|-----|----------------|------|
| 1 | Bootstrap build TS (squelette projet, 3 bundles) | ✅ |
| 2 | HTML/CSS squelettes plugins | ✅ |
| 3 | Communication app ↔ plugins (handshake, identité) | ✅ |
| 4 | Lobby (inscription tchat, paramètres, lancement) | ✅ |
| 5 | Phase écriture (tour 0 = phrase libre) | ✅ |
| 6 | Rotation aléatoire des albums (ChainBuilder + tests) | ✅ |
| 7 | Phase dessin (canvas brush + boucle write↔draw) | ✅ |
| 8 | Outils dessin (brush, gomme, pot, undo/redo) | ✅ |
| 9 | Overlay public (vue diffusée dans le stream) | ✅ |
| 10 | Reveal (navigation manuelle) | ✅ |
| 11 | Réactions tchat & emojis flottants | ✅ |
| 12 | Persistance N parties (re-replay depuis dashboard) | ✅ |
| 13 | AFK kick (2 tours d'affilée) | ✅ |
| 14 | Animation reveal stroke-par-stroke | ✅ |
| 15 | Sons UI viewer *(assets à fournir)* | ✅ |
| 16 | i18n complet FR/EN | ✅ |
## Pour le streamer
### Plugin streamer
- **Au boot / refresh** : écran « Chargement... » affiché tant que la première `STATE_UPDATE` n'est pas reçue, pour éviter qu'un click sur « Créer une partie » (vue idle par défaut) annule une partie en cours par inadvertance.
- **Vue idle** : bouton **« Créer une partie »** + historique des parties sauvegardées (cliquer pour re-rejouer).
- **Lobby** : form de configuration (joueurs max, durées écriture/dessin, nb étapes), liste des inscrits avec bouton × pour kicker, bouton **Démarrer** activé dès que la pref *Joueurs min pour démarrer* est atteinte (2 par défaut).
- **Le streamer peut jouer** : le dashboard n'est pas un client de jeu, il s'inscrit comme tout le monde (commande tchat ou bouton « Rejoindre la partie » de son plugin viewer) et joue depuis son plugin viewer. Le lobby affiche un rappel avec un **lien direct vers son plugin viewer** — le plugin tournant en iframe sandboxée, si l'ouverture d'onglet est bloquée le lien copie l'URL dans le presse-papier (« ✓ Lien copié »), et en dernier recours l'affiche en clair. Le rappel est remplacé par une confirmation une fois le streamer inscrit. L'overlay bascule alors automatiquement en mini-encart pendant les tours.
- **Pendant la partie** :
- Bandeau phase courante + numéro de tour + barre de progression du timer.
- Liste des joueurs avec pastilles ⚪ en train de jouer / 🟢 a soumis / 🔴 AFK kické.
- Boutons **Pause** (toggle) et **Forcer fin de phase** (commit immédiatement empty pour les non-soumetteurs et avance — bypass des PROBE).
- **Fermer la partie** : bouton discret en bas de chaque vue active (lobby / partie / reveal). Confirmation puis retour à l'écran d'accueil — depuis là, **« Créer une partie »** lance une nouvelle session.
- **Reveal** :
- Liste des albums (cliquables pour goto direct).
- Boutons **Précédent / Pause / Suivant** pour piloter la révélation.
- Checkbox **Avance auto** : raccourci vers la pref `reveal.autoAdvance` (modifie la pref directement → le choix persiste entre les sessions et reste synchronisé avec l'UI des préférences).
- L'overlay diffuse l'album sous forme de **fil "conversation"** : chaque step s'empile en bulle (avatar + nom de l'auteur, à gauche/droite en alternance), le dernier step animé (phrase fade-in, dessin stroke-par-stroke cap 4 s), scroll auto en bas.
### Préférences (persistantes)
**Lobby**
- *Commande d'inscription* (string) — défaut `play`
- *Joueurs min pour démarrer* (2–16) — défaut `2`
- *Joueurs max (défaut)* (2–16) — défaut `8`
- *Durée écriture (défaut)* (10–120 s) — défaut `30 s`
- *Durée dessin (défaut)* (20–300 s) — défaut `60 s`
- *Nb étapes (0 = auto)* (0–16) — défaut `0`
- *Seuil AFK (tours d'affilée)* (1–5) — défaut `2`
- *Nouvelle partie auto après reveal* (boolean) — défaut `on`. À la fin du reveal, retour automatique au lobby (les joueurs doivent se ré-inscrire). Off → reste sur Fin de partie, le streamer ferme la partie manuellement.
**Overlay**
- *Position et taille* (containerArea sur 1920×1080) — l'overlay se positionne dans la zone définie. Défaut : zone centrée 745×800. Min 745×800 (largeur min imposée directement par le SDK, hauteur min dérivée via `maxRatio = 0.93` puisque le SDK n'expose pas de `minHeight`).
- *Couleur d'accent* (color) — bordures, halos, dégradé du titre. Défaut `#5bc4ff`.
- **Mini-encart auto** : si le streamer est inscrit comme joueur, l'overlay bascule **automatiquement** en mini-encart haut-droite (libellé phase + n° de tour, compteur X/N, barre de timer) pendant les tours d'**écriture / dessin**, pour ne pas masquer son propre écran de jeu. En **lobby** et au **reveal**, l'overlay reste en grand dans la zone configurée : le lobby est l'écran de recrutement (commande d'inscription + inscrits) et le reveal le spectacle — dans les deux cas le streamer ne dessine pas.
**Reveal**
- *Durée animation dessin* (1–15 s) — défaut `4 s` (cap du re-jouage stroke-par-stroke).
- *Avancer automatiquement* (boolean) — passe au step suivant tout seul après l'affichage. Défaut `off`.
- *Réactions > Accepter toutes les emotes en réaction* (boolean) — défaut `off`. Si on, **n'importe quel emote Twitch / BTTV** (global ou de la chaîne) écrit dans le tchat compte comme une réaction, avec son image dynamiquement en compteur et en emoji flottant — pas besoin de le déclarer dans la liste. Les entrées explicites de la liste prennent la priorité (emoji custom possible pour un nom donné).
- *Réactions > Liste* (collection) — chaque entrée = { commande, emoji }. Réordonnable, ajout/suppression libre (cap 10). Défauts : `lol → 😂`, `wow → 😱`, `plus1 → 👍`. Chemin de pref : `reveal.reactions.list`.
- Le champ *emoji* accepte un caractère unicode (😂, 👍, …) **ou** le nom d'un emote Twitch / BTTV global ou de la chaîne (ex : `LUL`, `Kappa`, `peepoHappy`). Si le nom matche un emote connu, l'image est affichée à la place du texte dans le compteur et l'emoji flottant.
- La commande déclenche la réaction quand un viewer écrit `!cmd` **ou** `cmd` tout seul **ou** dans une phrase (ex : « haha LUL trop drôle » déclenche `LUL`). Matching sensible à la casse.
**Historique**
- *Parties gardées* (1–50) — défaut `10`
**Messages**
Annonces multi-canal (chat Twitch / TTS / notification écran / annonce écran / Discord) déclenchées aux moments clés. Le streamer choisit pour chaque message les canaux à activer — **tous désactivés par défaut**, l'app reste silencieuse hors-config explicite.
- *Début de partie* (`gameStart`) — déclenché à l'ouverture du lobby.
Tags : `#streamer#`, `#play_command#`, `#plugin_url#`, `#min_players#`, `#max_players#`.
- *Fin de partie* (`gameEnd`) — déclenché à la fin du reveal.
Tags : `#streamer#`, `#play_command#`.
- *Début du reveal* (`revealStart`) — déclenché à la fin de la dernière phase dessin, juste avant que le reveal démarre.
Tags : `#streamer#`, `#reactions#` (liste sérialisée `!cmd1 emoji1, !cmd2 emoji2…` depuis la pref Réactions).
D'autres annonces (changement de phase, rejoint la partie, kick AFK…) pourront être ajoutées au même endroit.
> Les valeurs *par défaut* sont chargées au moment de **« Créer une partie »** ; le streamer peut ensuite les ajuster ponctuellement dans le lobby sans modifier la pref.
> Fin de phase : à l'expiration du timer, l'app réclame son contenu en cours à chaque joueur qui n'a pas encore validé (même vide). Un joueur dont le plugin est fermé est reconnu comme tel immédiatement — il ne fait plus attendre la table.
> Limitations connues :
> - Si on change un mot-clé de commande dans les préférences en cours d'usage, l'ancien mot-clé reste réactif (le SDK Kappapps n'expose pas de désenregistrement). Modifier hors partie.
> - **Rotation des albums** : algo de shifts cycliques uniques par tour. Garantie forte : tant que `nbSteps ≤ nbJoueurs`, chaque joueur visite chaque album au plus une fois (jamais sa propre contribution). Si `nbSteps > nbJoueurs`, des répétitions deviennent inévitables (limite mathématique).
## Pour les viewers
L'inscription se fait via le tchat (commande paramétrable, par défaut `!play`) **ou** via le bouton **« Rejoindre la partie »** dans le plugin viewer (équivalent en un clic). Premier arrivé premier servi jusqu'au cap.
### Lobby
- Si pas inscrit → message « Tape la commande pour t'inscrire (!play) » + bouton **Rejoindre la partie**.
- Si inscrit → coche ✓ et liste des joueurs (ton nom en gras), bouton masqué.
### Phase écriture
- **Tour 0** : phrase libre.
- **Tours pairs ≥ 2** : *deviner* la phrase à partir d'un dessin reçu (canvas read-only au-dessus de la textarea). Si le joueur précédent n'a rien soumis, message « Le joueur précédent n'a rien soumis — devine au pif ! » à la place du canvas.
- Bouton **Valider** désactivé tant que la textarea est vide. Barre de timer qui décompte. **À l'expiration du timer**, le serveur demande à chaque viewer non-soumetteur son contenu courant via un PROBE — la textarea est envoyée telle quelle (même vide). Skip si tous prêts.
- Sons UI : tick les 5 dernières secondes, son de validation au submit.
### Phase dessin (tours impairs)
- La phrase à dessiner s'affiche au-dessus du canvas (« Dessine ce qui suit : *…* »). Si le joueur précédent n'a rien soumis, message « Aucune phrase reçue — dessine ce que tu veux ! ».
- Outils : **pinceau** (B), **gomme** (E), **pot de peinture** (G, flood fill), **ligne** (L), **rectangle** (R), **ellipse** (C), **undo / redo** (Ctrl+Z / Ctrl+Shift+Z, historique 100 strokes) et **tout effacer** — annulable comme le reste.
- Toolbar : palette de 18 couleurs party-game + sélecteur de couleur libre, 4 tailles de pinceau (touches 1–4, ou molette sur le canvas).
- Traits **lissés** (courbes quadratiques) — même rendu en direct, au re-render et au reveal.
- Un **curseur circulaire** montre la taille et la couleur réelles du pinceau avant de tracer.
- Les formes se tracent en glissant, avec aperçu en direct ; un simple clic ne produit rien.
- Bouton **Valider** désactivé tant que le canvas est vide. **À l'expiration du timer**, le serveur récupère ton dessin en cours via un PROBE (les strokes locaux). Strokes envoyés uniquement à la soumission ou à la réponse du PROBE (pas de live stream).
### Reveal
- Phase passive : tu regardes le stream, l'overlay déroule les albums.
- Tu peux réagir via le tchat : `!lol`, `!wow`, `!plus1` (ou simplement `lol`, `wow`, `plus1` — y compris au milieu d'un message) → emojis flottants 😂 😱 👍 + compteurs sauvegardés par étape. Si le streamer configure un emote Twitch / BTTV comme emoji (ex : `LUL`), l'image de l'emote est affichée.
- Une réaction par utilisateur par étape et par mot-clé (anti-spam).
### AFK
- Si tu rates **2 tours d'affilée** (timer expire sans soumission), tu es kické. La partie continue sans toi, ton statut passe à 🔴 dans le dashboard streamer.
## Overlay (vue diffusée dans le stream)
L'app principale rend un panneau central avec :
- Titre **Streamio** + libellé de phase.
- **Lobby** : message d'invitation + cartes joueur (avatar + nom).
- **Tour** : compteur soumissions (X/N) + statut des joueurs + barre de timer.
- **Reveal** : nom de l'album courant, étape N/M, contenu (phrase ou canvas animé), compteurs de réactions.
- **Emojis flottants** spawnés en bas à chaque réaction tchat pendant le reveal.
## Build
```bash
npm install # devDeps : typescript, esbuild, tsx
npm run dev # watch + rebuild auto
npm run build # build prod (minifié)
npm run typecheck # tsc --noEmit
npm test # tests unitaires (ChainBuilder)
```
Sorties générées : `script.js` (overlay), `plugin/index.js` (viewer), `streamer/index.js` (streamer dashboard) — gitignorés.
## Assets à fournir
- `assets/sounds/click.mp3` — clic UI (changement d'outil/couleur)
- `assets/sounds/valid.mp3` — soumission validée
- `assets/sounds/error.mp3` — erreur
- `assets/sounds/tick.mp3` — tick chaque seconde des 5 dernières secondes d'une phase
Si un fichier manque, le son est silencieusement désactivé (pas de blocage UI).