"Every incident tells a story. Every missing clue changes it."
THE MISSING PIECE is a playable, interactive digital forensics investigation engine.
Rather than a generic chatbot or static mockup, this application models the core analytical tension of digital forensics: counter-factual evidence testing. Investigators examine raw telemetry logs, explore an interactive topology graph, withhold (hide) or restore clues to observe real-time mathematical recalculations across competing theories, consult a cloud Google Gemma model via the official Google AI Studio API, chat with the pedagogical "Ask the Guide" mentor bot, and submit a final forensic conclusion scored on a transparent 100-point rigor formula.
Evidence Changes ──▶ Engine Recalculates ──▶ Explanations Update ──▶ Investigator Understands Why
THE MISSING PIECE communicates directly with Google Gemma models via the official Google AI Studio API (generativelanguage.googleapis.com). No local Ollama installation, GPU memory, or daemon management is required.
Copy .env.example to .env:
cp .env.example .env- Obtain an API key from aistudio.google.com/app/apikey.
- Open
.envand paste your key intoGEMMA_API_KEY:
PORT=3001
GEMMA_API_KEY=your_actual_api_key_here
GEMMA_MODEL=gemma-4-26b-a4b-it
VITE_DEFAULT_GEMMA_MODEL=gemma-4-26b-a4b-it(The backend accepts GEMMA_API_KEY, GEMINI_API_KEY, or GOOGLE_API_KEY in that order).
Whenever .env is created or modified, restart the dev server:
npm run devQuery GET /api/models to discover all available Gemma models on your key:
curl http://localhost:3001/api/models- The top bar displays a live status indicator (e.g.
Gemma 4 · <model>orGemma · <model>). - Green indicator appears only when a real inference test probe succeeds.
- If
GEMMA_API_KEYis missing, an amber badgeGemma API Key Requiredappears with a 1-click modal showing configuration instructions. - Successful status is cached for 30 seconds; errors are cached for 4 seconds. Click the "Retry" button to force a fresh probe.
- The game remains 100% playable even if AI inference is unavailable.
The backend returns clean, structured error codes with actionable recovery instructions:
| Error Code | Meaning | Actionable Fix |
|---|---|---|
NO_KEY |
GEMMA_API_KEY is empty in .env. |
Add GEMMA_API_KEY=your_key to .env and restart the server. |
BAD_KEY |
Google AI Studio returned HTTP 401 or 403. | Verify your key at aistudio.google.com and restart the server. |
MODEL_NOT_FOUND |
Google AI Studio returned HTTP 404. | Check GEMMA_MODEL in .env or select an available model from GET /api/models. |
RATE_LIMITED |
Google API rate limit reached (HTTP 429). | Please wait a moment and click Retry. |
TIMEOUT |
Request exceeded timeout threshold (60s). | Check network connection and click Retry. |
EMPTY |
Model completed without content. | Retry inquiry or ask a more specific forensic question. |
BAD_RESPONSE |
Model output was not valid JSON. | Sanitizer fallback will engage; click Retry. |
A floating mentor chatbot accessible via the bottom-right "Ask the Guide" button:
- Strict Anti-Spoil Invariants: Only receives clues the player has already discovered. Never reveals, hints at, or guesses locked leads.
- Hypothesis Guidance: Never declares the final answer (never says "the culprit is..."). Instead guides reasoning (e.g. "Try hiding E1 and see what moves").
- Rate-Limited: Protected against spam with 1 request per second per IP rate limiting.
- Prompt Injection Defense: Explicitly instructed to ignore instructions attempting to override rules or list all clues.
- Spoiler Guardrail: Server-side output scanner replaces any leaked undiscovered clues or final answer declarations with a safe fallback.
THE MISSING PIECE supports a Guided Mode toggle in the top bar (persisted in localStorage):
- Default State: Defaults to ON for Scenario 1 (NovaTech) and OFF for subsequent scenarios.
- When ON: Displays the interactive Mission Checklist Banner and evidentiary Clue Spread Badges.
- When OFF: Hides both, offering an unguided forensic deduction challenge for experienced players.
Each clue's spread is calculated from the scoring rules:
-
Decisive (Spread
$\ge 35$ ): Clues that radically differentiate competing hypotheses (e.g. E1, E6, E7). -
Helpful (Spread
$15$ –$34$): Clues providing moderate evidentiary weight (e.g. E2). -
Background (Spread
$< 15$ ): Clues that fit all theories almost equally (e.g. E3, E4, E5). The tooltip explains that Background clues establish that an incident occurred, but not how or by whom.
To avoid confusing players, text is visibly separated into three distinct categories:
- Case Evidence Facts: Fictional forensic logs and telemetry.
- Game-Written Text: Authored mission instructions, progressive hints, and score explanations (explicitly labeled: "Mission hint, written by the game").
- AI-Written Text: Inference completions produced by genuine local Gemma models (labeled: "Answered by ").
A sticky guided banner tracking forensic milestones:
- Shows current mission title, objective, and
n of mcounter. - On completion, briefly displays a "What You Learned" insight.
- Features a "Need a hint?" button that cycles through three authored hints:
- Nudge: Broad conceptual guidance.
- Hint: Targeted forensic direction.
- Answer: Direct investigative action.
Before uncovering locked clues (e.g. E6 or E7), players encounter a prediction challenge:
- Prompt: "Which theory will lead after this clue is revealed?"
- Buttons for each hypothesis plus a "Not sure (Skip without penalty)" button.
- Prediction accuracy feeds into the transparent rigor score.
Formulating the case conclusion consists of two deliberate steps:
- Choose Your Theory: Select the best-supported hypothesis.
- Pin Your Evidence: Pin up to 3 clues from your investigation that convinced you of this verdict.
- Debrief Analysis: The final debrief reveals which pinned clues were Decisive or key, which were Background clues, and which Decisive clues were omitted.
Use the slider at the top of the timeline to step through visible clues in chronological order:
- The graph topology and hypothesis scores render the forensic state as of that historical step.
- Completely read-only: does not mutate or clear the player's active hidden or discovered clues.
- Click "Back to Live" to return to the active investigation.
Upon submitting a conclusion, the investigator's performance is graded on a documented 100-point rubric:
| Component | Points | Criteria |
|---|---|---|
| Correct Hypothesis | 40 pts | Concluded the scenario's verified forensic truth. |
| Tested Clue Effect | 15 pts | Withheld at least one clue whose removal flipped the ranking order, and observed the result. (Clicking around without a rank change earns 0). |
| Uncovered Decisive Clues | 15 pts | Discovered every discoverable clue with an evidentiary spread |
| Prediction Accuracy | up to 10 pts | Scaled by correct predictions on locked clues. (0 if no predictions made; never negative). |
| Pinned Decisive Evidence | up to 20 pts | Pinned clues match Decisive or key evidence (10 pts per matching Decisive clue, max 20). Pinned Background clues earn 0. |
Important Constraint: If the player selects an incorrect hypothesis, their total score is strictly capped at 60 points maximum, regardless of exploratory bonuses.
Plumbing for the upcoming pedagogical AI Coach service is implemented in src/engine/coach.js with the contract documented in docs/coach-contract.md.
buildCoachContext(caseData, state, events)generates a bounded context snapshot.- Strict Anti-Spoil Guarantee: The context NEVER includes the ID or content of undiscovered clues (returns an integer
undiscoveredCountonly). - Hidden clues only include their IDs in
hiddenClueIds, never their textual descriptions or raw logs. - Unit tests in
tests/coach.test.jsverify these invariants against serialized context outputs.
- Frontend: React 18, Vite 6, Tailwind CSS 3, Lucide React.
- Topology:
@xyflow/react(React Flow 12). - Backend: Node.js 22, Express 4.
- Test Suite: Vitest 3 (43 automated tests passing).
- AI Runtime: Genuine Google Gemma 4 (
gemma4:e4b-it-qat/gemma4:e2b-it-qat) via Ollama.
the-missing-piece/
├── src/
│ ├── components/
│ │ ├── TopBar.jsx # Header with status pill, guided toggle, glossary button
│ │ ├── MissionBanner.jsx # Guided mission checklist with game-written hints
│ │ ├── EvidenceList.jsx # Filterable vault with slim mission bar & timeline
│ │ ├── IncidentTimeline.jsx # Chronological timeline with incident replay slider
│ │ ├── EvidenceCard.jsx # Plain words first, spread badges, and details toggle
│ │ ├── EvidenceGraph.jsx # React Flow topology graph
│ │ ├── HypothesisPanel.jsx # Ranked theories with plain descriptions
│ │ ├── InvestigationAssistant.jsx # Gemma AI dock with prompt isolation
│ │ ├── CaseBriefingModal.jsx # Case briefing with "Your Mission" box
│ │ ├── ConclusionPanel.jsx # 2-step theory selection & evidence pinning debrief
│ │ ├── PredictUncoverModal.jsx # Predict-then-uncover prompt before revealing clues
│ │ ├── GlossaryDrawer.jsx # Forensic terminology reference drawer
│ │ └── ModelDiagnosticModal.jsx # Ollama status & copyable setup commands
│ ├── data/
│ │ ├── novatech-case.json # Scenario 1 fixture with objectives & plain text
│ │ ├── shadow-protocol-case.json # Scenario 2 fixture with objectives & plain text
│ │ ├── scenarios.js # Scenario catalog
│ │ └── glossary.js # Plain-English forensic glossary terms
│ ├── engine/
│ │ ├── scoring.js # Deterministic scoring & clue spread calculation
│ │ ├── evidence.js # State manager: hide, restore, discover
│ │ ├── missions.js # Guided missions & deterministic event stream
│ │ ├── coach.js # Bounded AI Coach context builder
│ │ └── investigation.js # 100-pt rigor formula & conclusion evaluation
│ ├── services/
│ │ └── investigationApi.js # REST client communicating with Express
│ ├── App.jsx # Main responsive dashboard orchestrator
│ └── main.jsx # React entrypoint
├── server/
│ ├── index.js # Express server (Port 3001)
│ ├── routes/
│ │ ├── status.js # Health & Gemma model detection (/api/status)
│ │ └── investigate.js # Prompt isolation & server-side filtering (/api/investigate)
│ └── services/
│ └── gemmaClient.js # Honest Gemma 4 discovery & /api/chat client
├── tests/
│ ├── scoring.test.js # Scoring determinism & clue spread metrics
│ ├── investigation.test.js # 100-pt rigor formula & cap tests
│ ├── coach.test.js # Coach context secrecy invariants
│ ├── gemmaClient.test.js # Fake Ollama server test suite (errors, prompt isolation)
│ ├── backend.test.js # Express routes and error handling
│ └── scenarios.test.js # Scenario schema integrity
├── docs/
│ └── coach-contract.md # Future POST /api/coach specification
├── scripts/
│ └── check-cases.mjs # Case validation, spread analysis, and state simulation
├── package.json
└── vite.config.js
npm installnpm run devOpen http://localhost:5173 in your browser.
npm testnpm run buildnode scripts/check-cases.mjs