Ajoute Le Jeu de l'Agora : Le Char et Le Gardien du Silence
Build and deploy / deploy (push) Successful in 36s

Page /char (16:9, projetée) pour piloter les deux jeux physiques du
Tribunal : Le Char (2 colonnes de 3 emplacements côte à côte avec un
VS et une illustration de char vu du dessus, sélection en un clic,
duels, compteur et classement des passages) et Le Gardien du Silence
(tirage aléatoire parmi les Citoyens, minuteur avec reroll auto et
manuel). Banque de questions gérée séparément sur /char/questions
(pensée pour être pilotée depuis un téléphone pendant que /char est
projetée depuis un ordinateur).
This commit is contained in:
Valentin ROBIN
2026-08-11 18:17:45 +02:00
parent 4e22001ae0
commit f30f77a394
12 changed files with 1154 additions and 4 deletions
+17 -4
View File
@@ -6,15 +6,16 @@ Application web pour un groupe d'amis (~12 personnes) qui anime un jeu et des ol
## 1. État actuel
V1 à V6 sont livrées :
V1 à V7 sont livrées :
- **V1** : comptes, profils, leaderboard statique.
- **V2** : authentification par email réel (plus de pseudo+email interne), rôles `public`/`judge`, verrou de pseudo (modifiable une fois), section Admin.
- **V3** : attribution de points par les juges (RPC sécurisée, +/- avec confirmation explicite), journal public « le crieur », podium top 3, flèches de progression, recadrage/compression photo côté client.
- **V4** : direction artistique Grèce antique (marbre/or/mer de nuit, polices Cinzel/Cormorant Garamond/Manrope, vocabulaire Archontes/Citoyens/Agora/Crieur), header global avec chip utilisateur.
- **V5** : navigation entièrement repliée dans le menu déroulant du chip (header épuré : logo + chip seulement), page **La Roulette** (tirage au sort animé), page **Le Calendrier des Dieux** (agenda de la semaine, panthéon, date du Tribunal), boutons de validation des points unifiés (`PointsConfirmControls`) entre podium et classement.
- **V6** : premier mini-jeu compétitif, **Le Vol d'Icare** (`/icare`) — Flappy Bird grec (Icare vole entre des colonnes de temple), record personnel all-time (pas de piste par jour), touche l'écran pour battre des ailes, échec net au premier contact (score remis à zéro, comme l'original). Les scores sont figés dès que la date du Tribunal est atteinte, puis les gloires du top 3 sont attribuées **automatiquement** (tâche planifiée `pg_cron`, sans intervention d'un Archonte) — seul mécanisme de points de tout le projet qui ne passe pas par une décision de juge.
- **V7** : outils numériques pour le vrai jeu du Tribunal (soirée physique/sociale, pas un mini-jeu digital), page **Le Jeu de l'Agora** (`/char`) — pensée **format paysage 16:9** (projetée sur grand écran) plutôt que mobile-first comme le reste de l'app. **Le Char** : 2 colonnes fixes de 3 emplacements affichées côte à côte avec un « VS » et une illustration de char vu du dessus entre les deux, affectation d'un Citoyen à un emplacement vide en un clic (liste des Citoyens libres directement cliquables, pas de menu déroulant), bouton « Retirer », boutons « Colonne A/B remporte le duel » (vide la colonne gagnante après une brève animation — les vainqueurs sortent, les perdants restent), compteur du nombre de fois que chacun est monté dans le char, avec un petit classement récapitulatif en bas de page (tout le monde, y compris ceux qui n'y sont jamais monté). La banque de questions (ajout/édition/réordonnancement/bascule afficher-masquer) vit sur une page séparée réservée aux Archontes, **`/char/questions`** — pensée pour être pilotée depuis un téléphone pendant que `/char` est projetée depuis un ordinateur ; `/char` n'affiche que la question actuellement révélée, en lecture seule. **Le Gardien du Silence** : rôle tiré au sort parmi les Citoyens (jamais les Archontes), affiché de façon discrète (barre compacte, pas une grande carte), minuteur avec reroll automatique à expiration (RPC auto-protégée, filet `pg_cron`) et bouton de reroll manuel pour les Archontes. L'app n'arbitre jamais le jeu lui-même, elle affiche/synchronise un état piloté à la main.
Hors périmètre pour l'instant (voir roadmap en fin de doc) : éditions/saisons, le reste du jeu du Tribunal (ostracisme, timer du Gardien — Le Vol d'Icare est livré en V6), mode grand écran.
Hors périmètre pour l'instant (voir roadmap en fin de doc) : éditions/saisons, mode grand écran.
---
@@ -63,6 +64,14 @@ Le script est **idempotent** : toujours le ré-exécuter en entier après une mo
- `settings.icarus_points_awarded` : marqueur d'idempotence (un seul événement — l'attribution au début du Tribunal — pas une clôture quotidienne comme l'ancienne Course du Char).
- Génération des colonnes/obstacles entièrement côté client, sans graine partagée (pas de piste identique pour tout le monde à reproduire ici — chaque partie est procédurale).
### `public.chariot_questions` / `public.chariot_slots` / `public.chariot_entries` (Le Jeu de l'Agora — Le Char)
- `chariot_questions` : `id, text, position, created_at` — banque de questions ordonnée, réordonnée par swap de `position` (pas de contrainte unique dessus, deux `update` non transactionnels ; toujours trier `order by position, created_at`). RLS : lecture ouverte, écriture réservée aux juges (comme `days`/`events`). Gérée exclusivement depuis `/char/questions`, jamais depuis `/char`.
- `chariot_slots` : `slot (pk, 1 à 6), user_id nullable` — 6 emplacements fixes pré-remplis une fois pour toutes (1-3 = Colonne A, 4-6 = Colonne B), jamais insert/delete côté client, seulement des `update` pour affecter/vider un emplacement. Même RLS que `chariot_questions`.
- `chariot_entries` : `id, user_id, created_at` — journal append-only (comme `points_log`) d'une ligne à chaque affectation d'un Citoyen à un emplacement, sert uniquement à compter combien de fois chacun est monté dans le char (`count(*) group by user_id`, calculé côté client). RLS : lecture ouverte, insertion réservée aux juges, aucune mise à jour/suppression possible.
- `settings.chariot_revealed_question_id` : question actuellement affichée sur `/char`, directement modifiable par les juges (rien à protéger dans cette colonne-là) — bascule effectuée depuis `/char/questions`.
- Préfixe `chariot_*` (pas `char_*`) volontaire : sans rapport avec l'ancienne Course du Char digitale supprimée, qui utilisait déjà ce préfixe.
### Colonnes sensibles : RLS + trigger, jamais confiance au client
La RLS est **au niveau ligne** : elle ne peut pas exprimer « cette colonne seulement si tel rôle ». Le verrouillage fin des colonnes (`role`, `points`, `pseudo_locked`, pseudo figé, `previous_rank`) passe par le trigger `enforce_profile_update()` :
@@ -71,6 +80,8 @@ La RLS est **au niveau ligne** : elle ne peut pas exprimer « cette colonne seul
- `pseudo` : modifiable une fois par le propriétaire (auto-lock ensuite), ou à tout moment par un `judge`.
- `auth.uid() is null` (SQL Editor, migrations) bypass le trigger : ces accès sont déjà fiables par construction.
Même pattern sur `public.settings` depuis V7, via `enforce_settings_update()` : `gardien_holder_id`/`gardien_expires_at` sont bloqués en écriture directe pour **tout le monde**, y compris les juges — seule la RPC `reroll_gardien()` peut les modifier (flag `app.bypass_gardien_lock`), pour garantir que le Gardien est réellement tiré au sort et pas choisi à la main. `tribunal_date`, `icarus_points_awarded` et `chariot_revealed_question_id` restent, eux, directement modifiables par les juges (rien à protéger dans ces colonnes-là).
Ce pattern (RLS pour l'accès à la ligne + trigger `BEFORE UPDATE` pour l'accès aux colonnes) est la convention du projet — le reproduire pour toute nouvelle colonne sensible plutôt que d'inventer autre chose.
### RPC exposées
@@ -80,6 +91,7 @@ Ce pattern (RLS pour l'accès à la ligne + trigger `BEFORE UPDATE` pour l'accè
- `reset_rank_reference()` — authenticated, vérifie `role = 'judge'`, fige le classement courant dans `previous_rank`.
- `submit_icarus_score(p_score)` — authenticated, vérifie côté serveur si la date du Tribunal est déjà passée (scores figés : no-op silencieux plutôt qu'une erreur), n'écrase le record que s'il est strictement battu.
- `award_icarus_points_if_due()`**pas de grant à authenticated**, appelée uniquement par la tâche planifiée `pg_cron` (ou depuis le SQL Editor) : dès que la date du Tribunal est atteinte, attribue les gloires du top 3 automatiquement (`judge_id = null` dans `points_log`, affiché comme « Le Tribunal » dans Le Crieur), une seule fois (`settings.icarus_points_awarded`).
- `reroll_gardien(p_force default false)` — authenticated. Tire un nouveau Gardien parmi les Citoyens (jamais le détenteur actuel si possible) et repousse `gardien_expires_at` de 5 minutes. `p_force=true` (vérifie `role = 'judge'`) reroll immédiatement ; `p_force=false` (appelée par le minuteur côté client à expiration, et par `pg_cron` en filet) ne fait rien tant que `gardien_expires_at` n'est pas atteint — un seul `UPDATE ... WHERE` atomique (pas de `SELECT` puis `UPDATE`), pour qu'un reroll naturel avec plusieurs téléphones ouverts au même moment ne reroll qu'une seule fois.
---
@@ -99,12 +111,14 @@ Ce pattern (RLS pour l'accès à la ligne + trigger `BEFORE UPDATE` pour l'accè
| `/leaderboard` | « L'Agora » — podium top 3 + liste, Realtime, contrôles de points pour les juges |
| `/roulette` | « La Roulette » — tirage au sort animé parmi les membres (ou Citoyens uniquement) |
| `/icare` | « Le Vol d'Icare » — Flappy Bird grec, record personnel all-time, scores figés au Tribunal, points attribués automatiquement |
| `/char` | « Le Jeu de l'Agora » — format paysage (projeté) : Le Char (2 colonnes de 3, boutons de duel) et Le Gardien du Silence (tirage aléatoire, minuteur discret) |
| `/char/questions` | Banque de questions du Char (ajout/édition/réordonnancement/afficher-masquer) — juges uniquement, pilotée depuis un téléphone pendant que `/char` est projetée |
| `/calendrier` | « Le Calendrier des Dieux » — agenda de la semaine, panthéon, date du Tribunal, édition réservée aux juges |
| `/journal` | « Le Crieur » — fil live des décrets (attributions de points), lecture pour tous |
| `/profile` | Pseudo (verrouillable), photo, déconnexion |
| `/admin` | « Le Conseil des Archontes » — juges uniquement, **vérifié côté serveur** (Server Component) — gestion des membres, reset du repère de classement |
`/leaderboard`, `/profile`, `/admin`, `/journal`, `/roulette`, `/calendrier`, `/icare` sont protégées par `src/proxy.ts` (redirection `/login` si non connecté). L'accès juge-only de `/admin` n'est **pas** géré par le proxy (il n'a pas facilement le rôle) — c'est la page elle-même qui vérifie et redirige ; même principe pour les boutons d'édition du Calendrier (RLS + vérification serveur, jamais un simple masquage front).
`/leaderboard`, `/profile`, `/admin`, `/journal`, `/roulette`, `/calendrier`, `/icare`, `/char` (et donc `/char/questions`, couverte par le même préfixe) sont protégées par `src/proxy.ts` (redirection `/login` si non connecté). L'accès juge-only de `/admin` et `/char/questions` n'est **pas** géré par le proxy (il n'a pas facilement le rôle) — la page elle-même vérifie et redirige ; même principe pour les boutons d'édition du Calendrier (RLS + vérification serveur, jamais un simple masquage front).
Le header global (`components/header.tsx`) est rendu dans `layout.tsx` pour tout utilisateur connecté : logo (couronne de laurier + « Le Tribunal », jamais masqué même à 320px) à gauche, chip utilisateur (avatar, pseudo, points, jamais masqués) à droite. **Aucun lien de navigation dans le header lui-même** — le chip ouvre un menu déroulant qui contient toutes les pages (avec icônes), fermeture au clic extérieur/Échap, entrée courante surlignée.
@@ -153,6 +167,5 @@ Vocabulaire diégétique (toujours accompagné du terme fonctionnel dans le code
## 8. Roadmap (pas encore codé)
- **Éditions** : table `editions` (année, mood board) ; les points deviendront rattachables à une édition donnée. Le thème visuel (Grèce antique) reste fixe, ce n'est pas un système de re-thématisation par édition.
- **Le jeu du Tribunal (suite)** : batailles de cul sec, ostracisme, timer du Gardien. (La Roulette est livrée en V5, Le Vol d'Icare en V6.)
- **Mode grand écran** : vue leaderboard optimisée pour vidéoprojecteur.
- **Déploiement** : pas encore fait (Vercel + Supabase managé prévus, voir README).