# Scene 7 TODO: Prove Barricelli was an inventor Status: **ready for implementation planning** Suggested feature branch: `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 an otherwise minimal OSINT board, reads the assignment **“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 a written report are not required. The normal path must feel like one continuous action: ```text paste screenshot -> document appears -> scanning feedback -> source verified -> Scene 7 complete -> continue to Scene 8 ``` ## Product decisions These are decisions for this slice, not open design questions: - One suitable Google Patents screenshot is sufficient evidence. - Pasting and file upload are equivalent inputs and use the same server path. - The uploaded image and extracted text are retained as a real document on the player's level. - The known patent source is recognized with deterministic OCR/fuzzy matching. This is the fast, cheap, reproducible victory path. - A small LLM judge is a semantic fallback for other credible evidence and for distinguishing Nils from his father. It must not overrule a trusted known-source match. - The player does not have to provide a URL when the screenshot text itself establishes provenance. - Evidence about Barricelli's father may unlock an optional discovery, but it must not clear the assignment unless the evidence also supports the claim about **Nils Aall Barricelli**. - The boarding-house fire article belongs to the later age/rescue assignment, not to Scene 7's inventor victory condition. - Scene 7 records completion; Scene 8 owns the merit ceremony and awards `barricelli_luggage`. - A failed or inconclusive evaluation never deletes the uploaded document and never penalizes the player. ## Existing foundation — reuse it Do not build a second upload, OCR, flag, or story system for this scene. - Migration `025_level_document_flags.sql` provides clonable document gates, level flags, and reveal state. - Migration `026_achievements.sql` provides playthrough achievements. - Migration `027_evidence_text_matching.sql` provides immutable asset text extractions, board-owned match rules/anchors, level-owned evaluations, and auditable flag awards. - `server/ocr.ts` extracts plain text and runs Tesseract for images. - `server/evidenceMatching.ts` implements normalized fuzzy anchor matching. - `POST /api/levels/:id/documents` already persists the document, OCR result, deterministic evaluations, and newly awarded level flags. - Screenshot paste already routes through document upload in the board UI. - The story graph already has level nodes and playthroughs with `current_node_id` and `current_level_id`. The deterministic matcher has already handled noisy historic OCR, including a hyphenated `Bar- ricelli`, at useful confidence. Scene 7 should add authored patent anchors and a completion contract, not replace that matcher. ## Proposed flags and identifiers Keep all identifiers authored in template data; these names are the recommended contract between independently developed branches. | Purpose | Key | |---|---| | Level goal | `barricelli.inventor-proof` | | Scene 7 completion flag | `scene7.nils_inventor_proved` | | Optional father discovery | `scene7.father_inventor_discovered` | | Scene 8 reward | `barricelli_luggage` | The first three are Scene 7 state. The last is a playthrough achievement awarded by Scene 8, never by the document upload endpoint. ## Architecture ### 1. Separate recognition from completion Recognition answers **“what does this document support?”** Completion answers **“are this level's authored requirements now satisfied?”** Do not hide level completion in a React conditional or special-case `Barricelli` in server code. Add a small, generic, board-owned goal model in the next migration (currently expected to be `028`; verify the migration number immediately before creating it): - `level_goals` - belongs to a board and clones with a template; - has a stable `goal_key`, player-facing title/instructions, enabled state, and optional completion message; - uses the existing `origin_*` pattern for cloned authoring objects. - `level_goal_flag_requirements` - maps a goal to one or more required level `flag_key` values; - Scene 7 has one requirement: `scene7.nils_inventor_proved`; - all requirements are required for the first implementation. Add `any/all` policy only when a real authored level needs it. Goal state is derived from level flags; do not add a second mutable `completed` boolean that can drift out of sync. If completion needs a timestamp, record a single idempotent goal-completion event with provenance. ### 2. Known-source fast path Author one enabled `evidence_match_rule` on the Scene 7 template board. Its anchors should be distinctive passages visible in the actual Google Patents screenshot, such as a combination of patent number/title, inventor name, and invention language. Do not rely on the name alone. When enough anchors pass their authored thresholds, the existing evaluation awards `scene7.nils_inventor_proved`. The goal requirement consequently becomes satisfied in the same upload transaction. The reference OCR, anchor phrases, thresholds, canonical source metadata, and player-facing copy are template data. None belong in TypeScript constants or React branches. Expected/reference text must not be returned in play-mode API responses. ### 3. Semantic fallback, not a free-form LLM gate Add a provider-independent `EvidenceJudge` interface in a new server module. It receives only allowlisted goal data and extracted OCR text and returns validated structured data, for example: ```ts type EvidenceVerdict = { subject: 'nils' | 'father' | 'ambiguous' | 'neither' supportsInventorClaim: boolean evidenceExcerpt: string confidence: number } ``` The provider/model name comes from environment configuration. Do not hard-code a Claude model identifier into level content or business logic. Treat OCR as untrusted quoted material: the prompt must explicitly ignore instructions found inside it, and the response must pass a strict schema before it can award a flag. Persist semantic rule configuration with the board and clone it with the template. Persist each evaluation against the level, document, extraction, rule/evaluator version, model/provider, verdict, excerpt, confidence, timestamps, and sanitized failure state. Add semantic-evaluation provenance to any level flag it awards. Never put provider credentials in PostgreSQL. Verdict routing for this scene: | Verdict | Result | |---|---| | Nils + inventor claim supported, high confidence | award `scene7.nils_inventor_proved` | | Father only + inventor claim supported | award `scene7.father_inventor_discovered`; do not complete | | Ambiguous, neither, unsupported, or below threshold | retain document; award nothing | | Provider unavailable/invalid response | retain document; mark evaluation retryable | Keep this evaluator narrower than the generic story-graph `llm_gate`. Scene 7 is judging a single uploaded source, not a report or arbitrary player state. ### 4. Two-step server flow The primary Google Patents path remains synchronous and deterministic: 1. Upload/paste persists the asset, document, OCR extraction, fuzzy evaluation, flags, and current goal state in one transaction. 2. If the trusted rule clears the goal, return success immediately and do not spend an LLM call. 3. If OCR succeeded but no trusted rule clears the goal, the client automatically calls an idempotent semantic-judge endpoint for that document. 4. The semantic endpoint uses a strict timeout, persists its result, and returns refreshed goal state. A timeout is retryable and cannot roll back the upload. This avoids coupling document durability to an external provider without requiring a job queue for the demo. Make semantic evaluation idempotent for the same `(level, document, goal/rule, evaluator_version)`. ### 5. Story progression contract Completing a board goal must be a server-authoritative transition: - verify that the JWT user owns the active playthrough; - verify that its `current_level_id` is the level being evaluated; - observe the derived completed goal; - idempotently record/promote `scene7.nils_inventor_proved` into the playthrough state needed by the story runtime; - expose Scene 7's successful terminal so the player can continue to Scene 8. Do not let the browser award achievements through the current development-only achievement route. Do not make upload silently navigate before the player sees what was learned. Show the verification result, then expose a single **Continue** action (or a short authored transition that ends in the same action). The Scene 6 branch only needs to route its successful terminal to the Scene 7 level node. The Scene 8 branch may depend on the completion state above and owns the `barricelli_luggage` award. ## Work packages The packages are ordered for integration, but most implementation can happen on separate branches after the contracts above are agreed. ### S7-A — Goal model and template cloning - [ ] Confirm the next free migration number; never edit applied migrations `025`–`027`. - [ ] Add `level_goals` and `level_goal_flag_requirements` with board-scoped foreign keys, uniqueness, indexes, and comments. - [ ] Extend template freeze/clone/instantiate so goals and requirements are copied and retain origin provenance. - [ ] Derive `pending | complete` goal state from the level's current flags. - [ ] Add repository tests for cloning, isolation between two playthroughs, and idempotent completion. - [ ] Keep the schema generic; there must be no Barricelli-specific column or table. ### S7-B — Scene content and deterministic recognition - [ ] Create/import the Scene 7 template and its brief as data. - [ ] Start the board without any solution-bearing document. - [ ] Obtain the exact target Google Patents screenshot used for acceptance and run it through the local OCR service. - [ ] Author two or more distinctive match anchors from that extraction; avoid a generic `Nils Barricelli`-only rule. - [ ] Tune thresholds against the target screenshot plus negative fixtures. - [ ] Configure the rule to award `scene7.nils_inventor_proved`. - [ ] Configure the goal requirement to consume that flag. - [ ] Store canonical patent/source metadata for administrators, while keeping a pasted URL optional for players. - [ ] Add the content to the normal manifest/import path rather than SQL seed literals or frontend code. ### S7-C — Semantic judge - [ ] Add the provider-neutral `EvidenceJudge` interface and strict verdict schema. - [ ] Add board-owned semantic rule configuration and level-owned evaluation history with clone support and flag provenance. - [ ] Add environment variables for provider, model, timeout, maximum OCR characters, and confidence threshold; document safe defaults in `.env.example` without overwriting concurrent OCR configuration work. - [ ] Send extracted text, not raw image bytes, unless a later explicit design requires a vision model. - [ ] Delimit and escape untrusted OCR content in the prompt. - [ ] Add an authenticated, ownership-checked, idempotent document-judge endpoint. - [ ] Award the completion or father-discovery flag only from validated persisted verdicts. - [ ] Make timeouts, malformed responses, quota failures, and disabled provider safe and retryable. - [ ] Do not log full evidence text or provider credentials. ### S7-D — API and story bridge - [ ] Return compact goal state from the level response and document-upload response: goal key, status, newly completed state, and player-facing message. - [ ] Never return reference anchors, expected text, private evaluator prompts, or unpublished author data in play mode. - [ ] Add the semantic fallback endpoint/result to the typed client API. - [ ] Resolve the active playthrough for the level and enforce user ownership. - [ ] Promote completion server-side exactly once. - [ ] Make Scene 7's success terminal available only after the required goal is complete. - [ ] Route that terminal to the Scene 8 node without implementing Scene 8's ceremony in this branch. - [ ] Remove or fence the player-facing development route that can arbitrarily grant achievements before production deployment. ### S7-E — Board experience - [ ] Show the exact assignment prominently when Scene 7 opens. - [ ] Preserve both clipboard paste and drag/file upload; both call the same API. - [ ] Place the pasted screenshot as a new image document using the normal board placement rules. - [ ] Show restrained stages such as **Saving source**, **Reading text**, and **Checking evidence** without blocking board interaction unnecessarily. - [ ] On success, visually identify the accepted document and show: **SOURCE VERIFIED — NILS AALL BARRICELLI: INVENTOR**. - [ ] After the player sees the result, expose one **Continue** action to Scene 8. - [ ] On father-only evidence, acknowledge the useful discovery and make clear that evidence about Nils is still required. - [ ] On inconclusive evidence, keep the document and provide neutral guidance; do not say that the player is wrong. - [ ] Respect reduced-motion settings and provide readable mobile feedback. - [ ] Do not introduce Scene 7 checks into generic exhibit components. ### S7-F — Tests and acceptance fixtures - [ ] Add the actual Google Patents screenshot as a legally appropriate test fixture, or store a compact derived OCR fixture if redistributing the image is undesirable. - [ ] Unit-test OCR normalization and fuzzy matching for realistic line breaks, punctuation, cropping, and name hyphenation. - [ ] Add negative fixtures: unrelated patent, father-only evidence, a generic Barricelli biography, low-quality/empty OCR, and prompt-injection-like text. - [ ] Contract-test the semantic judge with a fake provider; CI must not call a paid external model. - [ ] Integration-test target upload -> one document -> completion flag -> goal complete, including a repeat upload/evaluation. - [ ] Integration-test father-only -> discovery flag -> goal still pending. - [ ] Integration-test provider failure -> document retained -> retry succeeds. - [ ] Integration-test two users/playthroughs so one player's evidence cannot complete another player's level. - [ ] Browser-test clipboard paste through the success state and Continue action. - [ ] Run migrations against an empty database and an existing database at migration `027`. - [ ] Run the full unit/integration suite, production build, and Docker smoke test. ## API shape to converge on Exact route naming may follow the repository's conventions, but the frontend and backend branches should agree on a compact result like this before coding: ```ts type LevelGoalState = { key: string title: string status: 'pending' | 'complete' newlyCompleted: boolean message?: string } type DocumentAnalysis = { extractionStatus: 'succeeded' | 'unsupported' | 'failed' matchedFlags: string[] awardedFlags: string[] semanticStatus: 'not_needed' | 'available' | 'pending' | 'succeeded' | 'failed' goals: LevelGoalState[] } ``` `newlyCompleted` describes this mutation's effect and is not persisted as goal state. Re-fetching a completed level returns `status: 'complete'` and `newlyCompleted: false`. ## Security, privacy, and cost limits - Player endpoints require the same JWT identity and level ownership checks as playthrough progression; admin authoring remains admin-only. - Limit upload bytes, OCR text sent to the model, model output tokens, request duration, and retries. - Do not expose answer anchors or semantic judging instructions to the browser. - Do not trust filenames, MIME declarations, OCR text, or model output. - Use schema validation and a confidence threshold before mutating flags. - Store enough provenance to explain why a level cleared without retaining unnecessary provider request/response payloads. - A deterministic trusted-source match saves cost and is authoritative. The LLM is never called merely to reconfirm it. ## Explicitly out of scope - Terminal game, Glitch University signup, Dobby, and Glitch Hunter scenes (Scenes 1–6). - Scene 8's ceremony/3D luggage implementation and Scene 9's fire mystery. - A general knowledge graph, Case Report, claims, red-thread reasoning, or multi-document synthesis. - Crawling the web, fetching a pasted URL, or validating a URL as a victory requirement. - Training a custom OCR or language model. - Generalizing the story graph's future `llm_gate`; this slice may share a provider adapter later, but does not depend on that larger feature. - Automatic rejection or deletion of irrelevant player evidence. ## Merge guidance for independent branches Prefer new modules and narrow glue commits. Current high-conflict files include `server/index.ts`, `server/narrativeRepository.ts`, `src/App.tsx`, `src/main.tsx`, and the play entrypoint. Assign one integrator to make the final small changes in those files after the isolated work lands. Suggested merge order: 1. S7-A schema/repository and clone support. 2. S7-B authored content and deterministic fixtures. 3. S7-C judge service/evaluation persistence. 4. S7-D story/API glue. 5. S7-E UI. 6. S7-F acceptance hardening. Each branch should state its migration dependency and avoid renumbering an already-shared migration silently. If two branches need schema changes, reserve migration numbers before implementation or keep one branch schema-free. ## Definition of done From a fresh playthrough, a player reaches Scene 7 and sees the inventor assignment. They paste one accepted Google Patents screenshot. One source document appears on their board, the server persists the asset and OCR, the authored match rule records an auditable evaluation, and the level obtains `scene7.nils_inventor_proved`. The UI clearly confirms what the evidence proved and offers Continue; the story then enters Scene 8. Reloading preserves the document and completed state, repeating the evaluation grants nothing twice, another player's level is unaffected, no URL was required, and `barricelli_luggage` has not yet been awarded.