11 KiB
Scene 7: prove Barricelli was an inventor
Status: implemented and verified on scene-7-evidence-goal
Demo: GUPI Demo 1 — The Barricelli Files
Outcome
Scene 7 teaches one idea: a screenshot found during an OSINT search can become source evidence.
The player opens a minimal OSINT board, reads “Demonstrate OSINT skill: prove Nils Aall Barricelli was an inventor,” finds the relevant Google Patents result, and pastes or uploads one screenshot. The board creates a document, extracts its text, recognizes the source, and clears the level. A URL and written report are not required.
paste screenshot -> document appears -> text is read -> source is verified
-> Scene 7 complete -> continue to Scene 8
Product decisions
- One suitable Google Patents screenshot is sufficient.
- Clipboard paste and file upload use the same server path.
- The image and OCR remain a real source document on the player's board.
- A known-source fuzzy match is the fast, deterministic victory path.
- A small semantic judge is a fallback for other credible sources and for distinguishing Nils from his father. It cannot overrule the trusted path.
- Evidence about Barricelli's father may award an optional discovery but does not clear the assignment unless it also supports the claim about Nils.
- The boarding-house fire belongs to the later age/rescue assignment, not Scene 7.
- Scene 7 records
scene7.nils_inventor_proved. Scene 8 owns the ceremony and awardsbarricelli_luggage. - Inconclusive evaluation never deletes or penalizes uploaded evidence.
Shared identifiers
| Purpose | Key |
|---|---|
| Goal | barricelli.inventor-proof |
| Scene 7 completion | scene7.nils_inventor_proved |
| Optional father discovery | scene7.father_inventor_discovered |
| Scene 8 reward | barricelli_luggage |
All identifiers and content are template data. There must be no Barricelli conditional in React or server business logic.
Existing foundation
025_level_document_flags.sql: clonable document gates and level flags.026_achievements.sql: playthrough achievements.027_evidence_text_matching.sql: asset OCR, board match rules/anchors, level-owned evaluations, and flag provenance.028_level_goals.sql: board-owned goals and normalized flag requirements.029_semantic_evidence_judging.sql: clonable semantic rules, level-owned evaluations, and semantic flag provenance.030_evidence_match_source_metadata.sql: author-only canonical source metadata.server/ocr.ts: plain-text extraction and Tesseract.server/evidenceMatching.ts: OCR-tolerant fuzzy passage matching.POST /api/levels/:id/documents: persistent upload plus OCR and matching.- The story runtime already tracks
current_node_idandcurrent_level_id.
Architecture contract
Recognition and completion are separate
Recognition answers what a document supports. A goal answers whether the level's
authored requirements have been satisfied. level_goals and
level_goal_flag_requirements clone with a template. Goal completion is derived
from level flags; there is no second mutable completion boolean.
Play mode receives a goal's key, title, instructions, completion copy, status, and completion time. IDs, enabled state, required flags, target text, and judging prompts remain author-only.
Known-source fast path
The Scene 7 template owns an evidence_match_rule with distinctive text visible
in the real Google Patents result: a combination of patent number/title, inventor
name, and invention language. A name alone is too generic. When enough anchors
match, the existing matcher awards scene7.nils_inventor_proved in the upload
transaction and the goal becomes complete immediately.
Reference OCR, thresholds, source metadata, and copy live in the manifest/database, not TypeScript constants. The expected text is never returned to play mode.
Semantic fallback
A provider-neutral EvidenceJudge receives only allowlisted goal data and OCR
text. It returns strictly validated structured data:
type EvidenceVerdict = {
subject: 'target' | 'related' | 'ambiguous' | 'neither'
supportsClaim: boolean
evidenceExcerpt: string
confidence: number
}
OCR is untrusted quoted material. The judge prompt explicitly ignores instructions inside it. Provider/model, timeout, input limit, and confidence threshold are environment configuration. Semantic configuration clones with the board; evaluation history belongs to the level/document/extraction and records evaluator version, provider/model, verdict, excerpt, confidence, timestamps, and sanitized failure state.
| Verdict | Mutation |
|---|---|
| Target + assertion supported at threshold | authored success flag (scene7.nils_inventor_proved) |
| Related subject only + assertion supported | authored related flag (scene7.father_inventor_discovered) |
| Ambiguous, unsupported, or below threshold | none |
| Provider failure | none; retryable |
The known-source pass avoids an LLM call. After a deterministic miss, the client automatically calls an idempotent semantic endpoint. This keeps document upload durable even if an external provider times out, without requiring a demo job queue.
Story progression
Advancing from a level is server-authoritative. The server verifies the JWT user, active playthrough/current level, and enabled goal state. It promotes the Scene 7 completion fact idempotently and only then follows the level terminal. The browser cannot grant its own achievement.
The player must see the verification result before navigation. Success exposes a single Continue action to Scene 8. Scene 6 only wires into the Scene 7 level node; Scene 8 owns the luggage reward.
Work packages
S7-A — Goal model and cloning
- Add normalized board goals and flag requirements in migration
028. - Clone goals during freeze, instantiation, and reset.
- Derive pending/complete state from level flags.
- Hide goal requirements from play payloads.
- Return newly completed goals from document upload.
- Add admin CRUD repository/API contracts.
- Test migration, cloning, deterministic completion, and isolation.
- Clear level flags/reveal state when resetting from a template.
S7-B — Barricelli content and trusted recognition
- Add goals and evidence-match rules to the normal mystery manifest importer.
- Create the Scene 7 template with its assignment and no solution-bearing starting document.
- Add a reproducible screenshot-paste acceptance path that runs through local OCR.
- Author three distinctive patent anchors.
- Tune against the target and unrelated/father-only negative fixtures.
- Award
scene7.nils_inventor_provedand require it for the level goal. - Store canonical source metadata for administrators; keep player URL optional.
S7-C — Semantic judge
- Add provider-neutral interface and strict verdict validation.
- Add clonable semantic rule configuration and level-owned evaluations.
- Add semantic evaluation provenance to level flags.
- Add provider/model/timeout/input/confidence environment configuration.
- Send OCR text rather than raw image bytes.
- Add ownership-checked, idempotent judge endpoint.
- Route target and father verdicts to their authored flags.
- Make disabled provider, timeout, quota, and malformed output safe/retryable.
- Do not log evidence text or secrets.
S7-D — Story bridge
- Require completed enabled goals before a playthrough can leave a level.
- Verify the current playthrough belongs to the JWT user and owns the level.
- Promote required completion facts exactly once.
- Return a useful pending-goal error rather than advancing early.
- Return to the generic story runtime after success; the Scene 6/8 graph owner wires the actual neighboring nodes.
- Keep the arbitrary achievement-grant route development-only.
S7-E — Board experience
- Present the active goal prominently on Scene 7.
- Preserve clipboard paste, drag/drop, and file picker equivalence.
- Show source import/OCR and semantic checking stages.
- Highlight the accepted document and show SOURCE VERIFIED — NILS AALL BARRICELLI: INVENTOR.
- Show Continue only after server-confirmed completion.
- Acknowledge father-only discovery while asking for evidence about Nils.
- Keep inconclusive evidence and offer neutral guidance.
- Respect reduced-motion and mobile layouts.
S7-F — Verification
- Add a legally safe derived OCR fixture.
- Test line breaks, punctuation, cropping, name hyphenation, unrelated patent, father-only text, empty OCR, and prompt-injection-like text.
- Contract-test with a fake semantic provider; CI never calls a paid model.
- Integration-test target upload -> document -> flag -> completed goal.
- Test duplicate evaluation, provider failure/retry, two-user isolation, and story progression before/after completion.
- Browser-test clipboard image paste through OCR and verification. Continue is covered by the generic story-gate integration until the Scene 8 node lands.
- Run migrations on an empty database and one currently at
027. - Run unit, integration, build, and Docker smoke tests.
API shape
type LevelGoal = {
key: string
title: string
instructions: string
completionMessage: string
status: 'pending' | 'complete'
completedAt?: string
newlyCompleted: boolean
}
type DocumentAnalysis = {
extractionStatus: 'succeeded' | 'unsupported' | 'failed'
matchedFlags: string[]
awardedFlags: string[]
goals: LevelGoal[]
}
newlyCompleted is response-local: a reload returns complete with
newlyCompleted: false.
Security and cost limits
- Require identity and level ownership on player mutations.
- Limit upload bytes, OCR/model characters, output tokens, duration, and retries.
- Never expose reference anchors or judge instructions to play mode.
- Treat filenames, MIME declarations, OCR, and model output as untrusted.
- Validate model output and confidence before mutating flags.
- Persist enough provenance to explain completion without storing unnecessary raw provider payloads.
Out of scope
- Scenes 1–6, Scene 8's ceremony/3D model, and Scene 9's fire mystery.
- Knowledge graph, Case Report, claims, red-thread reasoning, or multi-document synthesis.
- Web crawling, URL fetching, or requiring a URL for victory.
- Custom model training or the general story graph
llm_gate. - Deleting irrelevant evidence.
Merge guidance
Prefer new modules and narrow glue commits. High-conflict files are
server/index.ts, server/narrativeRepository.ts, src/App.tsx, src/main.tsx,
and src/play.tsx; one integrator should own their final changes.
Suggested order: S7-A schema -> S7-B deterministic content -> S7-C judge -> S7-D story bridge -> S7-E UI -> S7-F hardening. Reserve migration numbers before parallel schema work and never renumber an already-shared migration silently.
Definition of done
From a fresh playthrough, the player reaches Scene 7 and pastes one accepted
Google Patents screenshot. One source document appears; MinIO asset, OCR, match
evaluation, and scene7.nils_inventor_proved are persisted; the goal completes;
the UI explains what was proved and offers Continue; and the story enters Scene 8.
Reload and duplicate evaluation are idempotent, another player's level is
unaffected, no URL was required, and barricelli_luggage is not awarded until
Scene 8.