Architecture & Judging Coverage β Midnight Mystery with Detective Shawn
This document does two jobs:
- Explains how the system is built (for technical judges and future contributors).
- Maps the build to the Build Small judging surface so the submission collects as many "ways to win" as possible.
1. System overview
main.py (FastAPI)
βββββββββββββββββββββββββΌββββββββββββββββββββββββββββ
β β β
Gradio: onboarding Gradio: game Gradio: case-closed
(custom noir form) (HUD + iframe map) (reveal + debrief)
β β β
βΌ βΌ βΌ
case_generator Phaser 3 (game.html) debrief service
(5-pass pipeline) β
postMessage / fetch β
β β β
βΌ βΌ βΌ
βββββββββ backend/routers (case Β· interrogation Β· forensic) ββββββββ
β
inference_client (HF Serverless / Ollama)
β
Qwen3.5-9B (text) FLUX.2-klein-4B (portraits)
- Three Gradio apps, one FastAPI host. Each page is its own
gr.Blocksmounted viagr.mount_gradio_app, so onboarding, the game, and the reveal are independent and each fully custom-styled. - Phaser β backend bridge. The map is a self-contained Phaser scene in an iframe. It talks to FastAPI with
fetchandpostMessageβ movement, clue discovery, and interrogation all flow over HTTP. - State. A Pydantic
CaseFileholds the entire game; an in-memory store keyed bycase_idcarries it across requests. A complete hardcodedSEED_CASEis the guaranteed fallback.
2. The generation pipeline (the centerpiece)
A single small text model is driven through five narrow passes. Splitting the work keeps a 9B model reliable: each prompt is small, each output is validated, each failure is recoverable.
| Pass | Input | Output | Guarantee |
|---|---|---|---|
| 0 Β· Roster | victim, scene, count, difficulty | cast (name, occupation, archetype, role) | exactly one culprit; fixed cast for later passes |
| 1 Β· Story Bible | roster + setting | the hidden ground truth (how/why/when, motive) | the truth is written once and never sent to suspects |
| 2 Β· Souls | story + one cast member | per-suspect "soul": voice, alibi, reveal rules, crack progression | runs in parallel (asyncio); each has a validator + fallback |
| 3 Β· Evidence | story + soul summaries + map rooms | clues anchored to real locations + soul write-backs | β₯2 solvable paths; clues land on real clue_anchors |
| 4 Β· Briefing | public fields only | police dossier lines | structurally cannot leak the culprit |
Why this wins points: it's a concrete, defensible answer to "can a small model do something hard?" β yes, if you decompose the task, validate each step, and constrain it to a real world (the map manifest). It's not prompt-and-pray.
Live systems layered on top
- Emotional state machine (
state_mutator,interrogation_pipeline): trust / anxiety / defensiveness per suspect; crack thresholds scale withlying_skill(1β10). State is rendered into natural-language instructions so the model enacts pressure instead of describing it. - Knowledge gate (
knowledge_gate): suspects can only reveal authorized facts; everything else is forbidden context. - Prompt-injection defense: known injection patterns are caught and deflected in character by archetype.
- Reactive world (
patrol, alertness): suspects patrol their zones; a stealth/alertness layer escalates suspects and can warn the culprit. - Forensics (
forensic_lab): clue analysis that can detect contradictions against interrogation transcripts. - Debrief (
debrief): the model grades the playthrough and narrates the resolution.
3. Small-model compliance (the hard requirement)
| Component | Model | Params | < 32B? |
|---|---|---|---|
| Case generation | Qwen/Qwen3.5-9B |
~9B | β |
| Interrogation / debrief / forensics | Qwen/Qwen3.5-9B |
~9B | β |
| Portraits | black-forest-labs/FLUX.2-klein |
~4B | β |
Rule check: "each model's parameter count must stay below 32B." Largest single model here is ~9B. Comfortably compliant β and the narrative of the project is precisely "look how much one small model can carry."
β οΈ Consistency fix before submitting: model names are stated inconsistently across the repo.
inference_client.py(the source of truth) defaults to Qwen3.5-9B + FLUX.2-klein-4B, butDEPLOYMENT.mdlists Qwen3-14B + Mistral-Small-24B + FLUX, and the onboarding CTA still says "Qwen-2.5-7B". Pick the real set, then make README, DEPLOYMENT.md, the onboarding CTA, and the demo VO all agree. Judges will notice a mismatch.
4. Judging-surface coverage
The field guide describes ~29 ways to win across track placements, Community Choice, sponsor prizes, the Custom-UI category, and stackable bonus badges. This project is strongest where it leans into being AI-native and visually distinct.
| Target | Why this build qualifies | Action to lock it in |
|---|---|---|
| Thousand Token Wood (track placement) | Couldn't exist without AI; the model is the game | Lead the README + video with this framing β |
| Custom UI badge | Zero default-Gradio surfaces β bespoke noir CSS, Phaser map, custom loader | Show the UI prominently in the video; tag custom-ui |
| OpenBMB / small-model sponsor | Whole pitch is "one 9B model runs a game" | State param counts everywhere; confirm sponsor tag slug |
| Community Choice | Shareable, novel hook ("name a victim, AI writes the murder") | Strong social clip; post early for votes |
| "Most badges stacked" | Genuinely spans creativity + UI + small-model + agentic | Add every honestly applicable badge tag |
| Full-package | App + demo video + social post all exist and align | Ship all three; keep them consistent |
Submission checklist (hard requirements)
- Deployed as a Gradio app in the official
build-small-hackathonHF org - Demo video showing the app working (see
DEMO_VIDEO_SCRIPT.md) - Social post created and linked from the README (see
SOCIAL_POST.md) - README YAML tags for the track + every badge being claimed
- All models verified < 32B and named consistently everywhere
- Submitted before June 15, 23:59 UTC
5. Pre-submission risk list
- Theme consistency. The game is now Detective Shawn (human noir). Sweep for leftover pet/animal copy in user-facing strings (onboarding hints reference "animals in the neighbourhood"; portrait assets are animal placeholders). Anything a judge sees must match the shipped theme.
- Model-name drift. See Β§3 warning β unify before recording the video.
- Inference reliability during judging. The seed-case fallback covers a hard outage, but verify the Space's
HF_TOKEN/provider and that portraits degrade gracefully. The demo video is your insurance β record it on a known-good run. - First-paint impression. Onboarding is the most-judged screen after the video. It's already strong; make sure fonts load on the Space (Google Fonts CDN reachable) so the noir look survives deployment.