
Jeu d'interrogatoire policier basé sur la voix. Mistral Large 3 + Voxtral STT + ElevenLabs TTS. Conçu pour le Mistral Worldwide Hackathon 2026.

Un jeu d'interrogatoire de détective basé sur la voix. Vous êtes un détective. Mistral est un suspect qui ment. Votre travail consiste à découvrir le mensonge rien qu'en posant des questions.
Chaque affaire est générée de manière procédurale — un crime en col blanc unique, un suspect, une histoire de couverture et un mensonge caché. Le suspect est interprété par Mistral Large, doté d'une histoire complète et chargé de la défendre sous pression contradictoire. Aucune affaire n'est identique.
Construit pour le Mistral Worldwide Hackathon 2026 (Volet en ligne).
"Mistral peut raisonner. Je l'ai fait mentir. Votre travail est de le prendre sur le fait."
npm install
cp .env.example .env # Remplissez vos clés API
npm run dev # http://localhost:3000
MISTRAL_API_KEY= # Requis — génération d'affaires, interrogatoire, évaluation, STT
ELEVENLABS_API_KEY= # Requis — TTS pour la voix du suspect
ELEVENLABS_VOICE_ID= # Facultatif — ID de voix ElevenLabs spécifique
NEXT_PUBLIC_SUPABASE_URL= # Requis — URL du projet Supabase
NEXT_PUBLIC_SUPABASE_ANON_KEY= # Requis — clé anonyme Supabase (protégée par RLS)
SUPABASE_SERVICE_ROLE_KEY= # Facultatif — endpoint d'exportation admin
EXPORT_SECRET= # Facultatif — authentification d'exportation admin
Les clés API peuvent également être configurées dans la page Paramètres à l'exécution — l'application est livrée sans clé et les joueurs apportent leurs propres clés.
npm run dev # Serveur de développement
npm run build # Construction pour la production
npm run start # Serveur de production
npm run lint # ESLint
Un mode minuteur illimité est également disponible — en difficulté difficile/expert, le suspect fera appel à un avocat après 4 échanges consécutifs à stress élevé.
Les indices se déverrouillent à des seuils de stress répartis de 1 à 9, conditionnés par :
Le bouton Accuser ne se déverrouille qu'après avoir collecté tous les indices requis.
score = timeScore x difficultyMultiplier x efficiencyBonus x hintPenalty x accusationPenalty
timeScore = 1000 x sqrt(max(0, 1 - elapsed / (parTime x 2)))
multiplier = easy 1.0x, medium 1.5x, hard 2.0x, expert 2.5x
efficiencyBonus = 1.0-1.5x (fewer questions than par = bonus)
hintPenalty = 0.85^hintsUsed
accusePenalty = max(0, 1 - wrongAccusations x 0.1)
Le score est calculé côté serveur à partir de statistiques figées au moment de la victoire — les valeurs du client sont ignorées.
app/
├── page.tsx # Écran titre
├── cases/ # Sélection d'affaires — lieux + difficulté
├── game/
│ ├── page.tsx # Orchestrateur principal du jeu
│ ├── hooks/ # useVoiceRecorder, useTTS, useGameTimer, useSfx, useSettings
│ ├── components/ # Dock, TopBar, CaseFile, SuspectZone, BriefingScreen, dialogs
│ ├── win/ # Écran de victoire + InitialsEntry
│ └── lose/ # Écran de défaite
├── leaderboard/ # Top 20, animation de nouvelle entrée
├── settings/ # Configuration des clés API, exportation, préférences
├── help/ # Comment jouer, score, à propos
├── about/ # Informations sur le créateur
└── api/
├── generate-case/ # GET — génération d'affaire par Mistral
├── interrogate/ # POST — question du joueur → réponse du suspect
├── accuse/ # POST — évaluation de l'accusation (juge séparé)
├── evaluate/ # POST — analyse victoire/défaite
├── hint/ # POST — indices basés sur des seuils de stress obscurcis
├── tts/ # POST — ElevenLabs TTS
├── transcribe/ # POST — Voxtral STT
├── leaderboard/ # GET/POST — classement Supabase
├── patterns/ # GET/POST — modèles d'apprentissage inter-sessions
└── export/ # GET — exportation admin JSONL
src/lib/
├── mistral/ # Client Mistral, interrogatoire, évaluation, génération d'affaires, embeddings
├── game-session.ts # Stockage de session côté serveur, jetons de victoire, exportation
├── game-state.ts # Définitions de types
├── scoring.ts # Calcul du score + classements de détective
├── sanitize.ts # Validation des entrées, détection d'injection
├── db.ts # Client Supabase
└── rate-limit.ts # Limitation de débit par IP
Le jeu confronte une IA antagoniste aux entrées du joueur — la sécurité est une fonctionnalité centrale, pas une réflexion après coup.
Assainissement des entrées : plus de 30 motifs d'injection regex, normalisation Unicode (NFKD), suppression des caractères de largeur nulle, détection de payloads base64, blocage du charabia/non-anglais.
Analyse des sorties : les réponses de l'IA sont vérifiées par rapport aux secrets de l'affaire avec correspondance floue (seuil de 40 %, filtrage des mots vides). Les réponses divulguées sont remplacées par une esquive.
Isolement du juge : l'évaluation de l'accusation utilise un appel Mistral séparé avec une invite système dédiée, des jetons de délimitation aléatoires, des marqueurs d'injection supprimés. Les appels du juge n'utilisent jamais les clés API fournies par le joueur.
Application du stress : limitation monotone côté serveur — le stress ne fait qu'augmenter, max +1 par tour. Les seuils d'indices sont appliqués côté serveur. L'IA ne peut pas faire monter ou descendre le stress pour contourner les mécanismes du jeu.
Jetons de victoire : 128 bits aléatoires, comparaison à temps constant, à usage unique, TTL de 30 minutes. Score calculé à partir d'un instantané des statistiques côté serveur — les valeurs rapportées par le client sont ignorées.
Sécurité des sessions : IDs aléatoires de 192 bits, verrous mutex, TTL de 1 heure, maximum 5000 sessions avec éviction LRU. Mode minuteur verrouillé à la création.
Validation TTS : nécessite une session active. Le texte doit correspondre à un message récent de l'assistant. Empêche les abus de proxy TTS gratuit.
Défense contre l'empoisonnement RAG : les tactiques apprises entre sessions sont filtrées via la détection d'injection et l'assainissement avant inclusion dans l'invite.
Les parties terminées sont persistées dans Supabase pour analyse et amélioration du modèle :
GET /api/export?limit=100&offset=0&outcome=win&difficulty=hard avec en-tête Authorization: Bearer <EXPORT_SECRET>EXPORT_SECRET=xxx npx tsx scripts/export-data.ts --output=data/export.jsonlEsthétique de détective noir. Sombre, minimaliste, typographique. JetBrains Mono. Arrière-plans pixel art et portraits de suspects. Pas de bulles de dialogue — le dialogue apparaît sous forme de texte stylisé dans la scène.
Créez une table leaderboard :
CREATE TABLE leaderboard (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
player_name text NOT NULL,
case_number text,
case_setting text,
suspect_name text,
time_remaining int,
stress_level int,
clues_found int,
hints_used int,
accusations_used int,
detective_rating text,
score int NOT NULL,
created_at timestamptz DEFAULT now()
);
ALTER TABLE leaderboard ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public read" ON leaderboard FOR SELECT USING (true);
CREATE POLICY "Public insert" ON leaderboard FOR INSERT WITH CHECK (true);
Table game_exports facultative pour les données d'entraînement :
CREATE TABLE game_exports (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
session_id text UNIQUE NOT NULL,
case_data jsonb NOT NULL,
conversation jsonb NOT NULL,
outcome text NOT NULL,
difficulty text NOT NULL,
setting text,
stats jsonb NOT NULL,
accusation_text text,
accusation_correct boolean,
created_at timestamptz DEFAULT now()
);
ALTER TABLE game_exports ENABLE ROW LEVEL SECURITY;
CREATE POLICY "anon_insert" ON game_exports FOR INSERT TO anon WITH CHECK (true);
CREATE POLICY "no_public_read" ON game_exports FOR SELECT TO anon USING (false);
Table interrogation_patterns facultative pour l'apprentissage inter-sessions :
CREATE TABLE interrogation_patterns (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
session_id text UNIQUE NOT NULL,
setting text,
difficulty text NOT NULL,
outcome text NOT NULL,
questions jsonb,
effective_questions jsonb,
max_stress int,
clues_found int,
time_elapsed int,
embedding vector(1024),
created_at timestamptz DEFAULT now()
);
ALTER TABLE interrogation_patterns ENABLE ROW LEVEL SECURITY;
CREATE POLICY "anon_insert" ON interrogation_patterns FOR INSERT TO anon WITH CHECK (true);
CREATE POLICY "anon_read" ON interrogation_patterns FOR SELECT TO anon USING (true);
Mistral Worldwide Hackathon 2026 — Volet en ligne, catégorie "Tout est permis".
Critères de jugement : Créativité/originalité (le plus important), potentiel futur, implémentation technique, qualité de la présentation.
Prix spéciaux : Best Mistral Vibe (AirPods), Best Voice AI (abonnement ElevenLabs 6 mois).
Sponsors : Mistral, ElevenLabs, NVIDIA, AWS, Hugging Face, Jump Trading, Weights & Biases, Giant, Raise, Tilde Research, White Circle.
La plupart des projets d'IA dans les hackathons sont des assistants ou des chatbots. Celui-ci renverse la situation — l'IA ne vous aide pas, elle vous ment. Le joueur est en position d'adversaire par rapport au modèle, pas de collaborateur. Cela crée une interaction fondamentalement différente : vous cherchez des incohérences, vous mettez la pression, vous lisez des signaux comportementaux au lieu de demander de l'aide.
Le jeu génère également des données d'entraînement. Chaque session terminée — questions posées, progression du stress, tactiques qui ont fonctionné ou non — est exportable au format JSONL. Cela crée une boucle de rétroaction : jouez au jeu, exportez les données, affinez le modèle, rendez-le plus difficile à démasquer.
Jason Poindexter — Participation solo, volet en ligne depuis Barcelone.
| Niveau | Indices | Minuteur | Comportement du suspect |
|---|
| Facile | 2 | 5 min | Mensonge évident, nerveux |
| Moyen | 3 | 7 min | Mensonge attrapable, calme |
| Difficile | 4 | 9 min | Mensonge subtil, expert en esquive |
| Expert | 5 | 10 min | Mensonge profondément enfoui, manipulateur |