Read the story

Architecture

Storyboards that keep each face the same.Every frame carries its score.

A filmmaker asks for a shot, and one FastAPI service pulls the character's frozen card and reference sheet from an in memory story bible and sends them to Nano Banana. A referee crops the face out of the new frame, embeds it and scores it against the sheet, and the frame is saved with that score on it.

30 s

a frame from Nano Banana, on a Vertex project shared with every team in the room

60 renders

per story at most, counted on the server so a loop bug cannot spend the budget

170 min

from the first commit at 11:06 to live on Cloud Run at 13:56

0.48

cosine between two different people's sheets on whole frames, which is why the referee crops the face first

New York --:-- simulated

A shot request
Magic Hour A filmmaker asks for a shot, and one FastAPI service pulls the character's frozen card and reference sheet from an in memory story bible and sends them to Nano Banana. A referee crops the face out of the new frame, embeds it and scores it against the sheet, and the frame is saved with that score on it. Gemini 2.5 Pro plans, Flash speaks Vertex embeddings face crops and lines Filmmaker script, asks, retakes Studio UI Board, Script, Cast tabs magic-hour FastAPI on Cloud Run Nano Banana a frame per shot Face referee crop, embed, score Story bible frozen cards, chunks Continuity Pack fixed slots, verbatim Story graph who knows what, when Frame cache on disk, demo set kept asks render shot card, sheet frame shot list, cards face crop context frame, score cards horizon In memory, one process

Hover a part to trace what it touches. Click it for its notes.

  • Built by me
  • Managed by Google
  • Outside system
  • Main data path
  • On demand
  • Credentials cross

When it breaks

What each failure does, and what it does not do.

  1. Vertex answers 429

    The lab project was shared with every team in the room, so backoff is built in: 2, 4 and 8 seconds, then the shot is marked failed.

    backend/app/consistency.py:140

  2. A face drifts

    The shot is redrawn once with a stronger instruction. If it still scores under 0.35, it is flagged on the board instead of retried again.

    backend/app/consistency.py:362

  3. A tab fails to import

    The API mounts inside a try/except, so the app still boots, serves its screens and reports the error on its health check.

    backend/app/main.py:64

What I tuned

Small choices that keep it fast and cheap.

  1. Crop before you embed

    Whole frame embeddings scored two different people's sheets at 0.48. A Gemini face box crop first leaves the identity signal.

    backend/app/consistency.py:257

  2. Thinking where it pays

    Voice Cards are compiled once on Gemini 2.5 Pro, which always reasons. Lines are spoken on Flash with thinking off, because a character does not deliberate before speaking.

    backend/app/voice.py:29

  3. Search in numpy

    About 50 chunks, so a full cosine scan in numpy, fused with token overlap by reciprocal rank so names and sluglines still match.

    backend/app/bible.py:142

Guards

Rules the code enforces, not rules people remember.

  1. A hard render cap

    60 renders per story, counted on the server before each one, so a loop bug cannot spend the budget.

    backend/app/api/board.py:38

  2. Canon needs a person

    The Archivist only proposes facts, with its reasoning attached. Nothing becomes canon until someone promotes it.

    backend/app/extract.py:7

  3. The voice bar

    A line must score 0.62 against its character's fingerprint, set between the best impostor line at 0.613 and the worst real one at 0.647.

    backend/app/voice.py:46