Reverse Screenplay Engine — architecture (design doc)
Status: Phases 1–4 built; golden-baseline regression deferred. Phase 1 = the adaptation brain (beat sheet) + steering UI. Phase 2 = per-scene Fountain render + downloadable
.fountain. Phase 3 = the quality layer (critique panel + apply-and-improve loop + adaptation-fidelity rubric). Phase 4 = FDX export + print-to-PDF HTML + the Scene Studio contract (a rendered scene's Fountain is handed to Scene Studio as its shot-breakdown source via asourceTextOverrideongenerateSceneBreakdown, closing story→script→movie). Code:services/screenplay-engine.ts,services/screenplay-critique.ts,services/screenplay-export.ts,routes/screenplay.ts,/dashboard/admin/screenplay, migrations411_screenplay_engine.sql+412_screenplay_critique.sql. Deferred: golden-adaptation regression tests — they need a human-approved reference screenplay to diff against (lock one once the first adaptation is approved); and a true server-side PDF (the current path is browser print-to-PDF, no PDF dependency). This doc is the plan for the missing "script" layer in the story→script→movie pipeline. Today Scene Studio jumps straight from chapter prose to a shot breakdown of one strong beat; there is no real screenplay in between. This engine adapts a finished Graphene novella into an industry-standard screenplay that is both a sellable human artifact and the clean scene boundaries Scene Studio consumes. North-star framing: memoryproject_movie_studio_vision+project_reverse_screenplay_engine.
Thesis: an adaptation engine, not a formatter
A generic prose→screenplay tool has to reverse-engineer everything from raw text — where scenes start and end, what each character wants, where they are, what's subtext vs. surface. Our novella engine already emits all of that as first-class structured data. That is the entire reason this can be good:
| What adaptation needs to infer | What Graphene already has |
|---|---|
| Scene boundaries (time + place) | settings[] in story-bible.ts sensory_palette / rules; chapter_spine ("scene/camera shape guidance") |
| Beat / act position | story_episodes.dramatic_role (opening / inciting / rising / midpoint / crisis / climax / resolution) |
| What a scene must reveal | story_episodes.knowledge_layer (what the reader learns vs. what the narrator knows) — the single best driver for adaptation |
| What's at stake in a scene | story_episodes.chapter_stakes |
| How a character speaks | bible characters[].voice_brief / verbal_tics / speech_avoid / secret_knowledge |
| Who is speaking which line | character-voices.ts detectDialogue / splitProseByVoice |
| World/canon continuity | season_story_bibles, universe_characters / universe_places |
The optimization is: feed the screenplay generator the metadata, not just the prose. Everything below follows from that one move.
Honest constraints (these shape the design)
- Adaptation ≠ transcription. A 30k-word novella is not a 120-page script. It is a series of decisions: compress, externalize, merge, cut. The engine must make those decisions explicitly and surface them for a human to steer — not silently flatten prose into slug lines.
- Interiority is the core problem. Novellas live in
inner_lifeand narration. A screenplay can show only what a camera sees and a mic hears. Converting thought into action / behavior / subtext / sparing VO is the hardest pass and gets first-class treatment (below), not an afterthought. - Length is a dial, not a constant. A single
target_pagesknob per project sets the compression budget. Defaults short (~20–30pp) toward the Scene Studio short-film direction; can be turned up toward a feature (~90–120pp). - The text layer is cheap; spend the budget on craft. A whole-novella adaptation is sub-$1 in LLM tokens (Fountain is plain text). The expensive spend lives downstream in Scene Studio video. So this layer should run a generous critique loop — quality, not cost, is the gate here.
- Art-first (Odessa). The screenplay serves the story the writer already lived in. The renderer is downstream; the adaptation should preserve the voice and the throughline, not genericize it into "AI screenplay."
Output format: Fountain canonical, FDX + PDF exports
Fountain (plain-text screenplay markup) is the canonical stored form:
- It is plain text, so it drops directly into the existing
takes → critique → version-snapshot machinery (see
episode-takes.ts/episode-take-critiques.ts) — diffs cleanly, snapshots cheaply, no binary blobs. - It is free and round-trips to FDX (Final Draft) and PDF for the human / sellable / festival path.
- Per-scene Fountain bodies map 1:1 to
screenplay_scenesrows, which is exactly the granularity Scene Studio wants to consume.
FDX and PDF are exports, generated on demand from the canonical Fountain.
Pipeline — 5 adaptation passes
Novella (chapter_prose × N + full story bible + per-chapter spine/role/stakes/knowledge_layer)
→ 0. Scene re-segmentation (LLM → screenplay scenes w/ slug lines, grounded in bible settings)
→ 1. Beat sheet + budget (LLM → scene-by-scene plan against target_pages; dramatize / compress / cut / merge)
→ 2. Interiority externalize (per scene: inner_life + knowledge_layer → action / behavior / subtext / VO)
→ 3. Dialogue surface + polish (detectDialogue extract; expand summary-dialogue; ground in voice_brief)
→ 4. Fountain render (assemble scenes + title page → canonical Fountain)
→ FDX / PDF export + Scene Studio contract (scenes become canonical shot boundaries)
The passes mirror the proven chapter pipeline
(season-novel-chapter.ts
prompt-block builder), so the prompt-engineering surface is familiar.
0. Scene re-segmentation (chapters → screenplay scenes)
A screenplay scene = unity of time + place + continuous action. Chapters
don't map 1:1 (one chapter is often several scenes; a montage can span
chapters). An LLM pass re-segments the novella, emitting for each scene a
slug line (INT./EXT. · LOCATION · TIME-OF-DAY) grounded in the bible's
settings[], plus source_chapter_ids[] so every scene traces back to its
prose. Sonnet planning tier (same tier already used for spine/stakes/bible
bootstrap). Output is structured JSON, validated like the existing
JSON-envelope chapter calls.
1. Beat sheet + compression budget (the adaptation brain)
Before any screenplay prose is written, decide what to dramatize / compress /
cut / merge, against a page budget derived from target_pages (≈1 minute ≈ 1
page; budget allocated by dramatic_role weight). The beat sheet reads
dramatic_role + chapter_stakes + planted_threads (with their
payoff_target_chapter) so subplots are cut or kept coherently — a planted
thread is never dropped without also dropping its payoff. Each beat carries:
the scene's job, its knowledge_layer reveal, a page estimate, and a
keep/compress/cut/merge decision. This is the human steering point — the
sum of page estimates must land near target before generation runs.
2. Interiority externalization (per scene)
The hardest pass. For each scene, convert inner_life / narration /
knowledge_layer into something a camera can see: a choice the character
makes, a behavior, dialogue subtext, a visual, or — sparingly, flagged, and
budgeted — voice-over. The knowledge_layer field tells the pass exactly what
the audience must learn in this scene, so externalization is goal-directed
rather than decorative. A hard cap on VO density is a critique check (below).
3. Dialogue surfacing + polish
Reuse character-voices.ts
detectDialogue to pull dialogue that already exists in the prose verbatim,
then expand summary dialogue ("they argued about the money") into real
exchanges. Every line is grounded in that character's voice_brief,
verbal_tics, and speech_avoid so voices stay distinct and consistent — the
same canon the audiobook voice-casting already trusts.
4. Fountain render + assembly
Assemble per-scene Fountain bodies into one document with a title page (logline
- author + season metadata), correct scene-heading / action / character / parenthetical / dialogue / transition elements, and a final page-count estimate. Canonical Fountain is stored; FDX and PDF are exported on demand.
Quality — the part that actually matters
Because the text layer is cheap, quality is the whole game. Three mechanisms, all reusing infrastructure that already exists for chapters.
A. Screenplay critique panel (reuse the critique loop)
Extend episode-take-critiques.ts
with screenplay-specific critics. Take 1 → critics → apply findings → take 2 →
promote, exactly like chapters. Proposed critics:
| Critic | Checks |
|---|---|
structure_editor | Act breaks land near expected page marks for the target length; midpoint and climax are present and in the right place. |
scene_economy | Every scene turns — enters on one value charge, exits on the opposite. Flags inert "as-you-know" scenes. |
on_the_nose_doctor | Dialogue carries subtext; characters don't say exactly what they feel/mean; cuts exposition dumps. |
voice_distinction | Each speaker is identifiable from dialogue alone, consistent with voice_brief / verbal_tics; flags interchangeable voices. |
vo_discipline | Voice-over stays under budget and is used only where externalization genuinely fails — not as a crutch. |
white_space | Format density / action-line length / readability — the page looks like a screenplay a reader flies through. |
continuity_canon | Characters, places, and planted-thread payoffs stay consistent with the bible / universe canon. |
B. Adaptation-fidelity rubric (does it still tell the story?)
A scored evaluator pass (Sonnet) grades the screenplay against the source, not
just against screenplay craft, on a fixed rubric: throughline preserved ·
every planted thread resolved or deliberately cut · each knowledge_layer
reveal lands in some scene · protagonist's arc intact · voice/tone faithful to
the bible. Low scores route specific scenes back through passes 2–3. This is
the guardrail against a technically-correct screenplay that has quietly lost the
story.
C. Golden adaptations (regression tests)
Lock one or two reference novellas (e.g. a Turing Logs season) with a
human-approved screenplay as the golden output. On any prompt/model change,
re-run and diff the beat sheet + rubric scores so a "tuning" change can't
silently degrade adaptation quality. Mirrors the spirit of the existing audit
patterns (memory feedback_audit_script_pattern).
Non-destructive by default
Every promotion snapshots the prior version (the animatic_versions /
keyframe_versions restore pattern), and scenes are individually cached — only
edited beats regenerate, so a one-scene fix never re-rolls the whole script.
Data model (proposed — migrations 411+)
Mirror the chapter take/critique tables.
screenplay_projects (
id uuid pk,
season_id uuid, -- the Graphene novella
target_pages int, -- the compression dial (e.g. 25 short, 100 feature)
logline text,
format text, -- 'fountain' (canonical); exports tracked below
status text, -- draft → beats → scenes → critiqued → final
fountain_url text null, -- assembled canonical doc
fdx_url text null, -- export
pdf_url text null, -- export
page_estimate int null,
scene_project_link uuid null, -- contract → scene_studio (optional)
created_at, updated_at
)
screenplay_scenes (
id uuid pk,
screenplay_project_id uuid,
idx int, -- order
slug_line text, -- "INT. AUDIT HALL - NIGHT"
int_ext text, -- INT | EXT | INT/EXT
location text,
time_of_day text,
beat_role text, -- from dramatic_role (act/beat position)
source_chapter_ids uuid[], -- provenance back to prose
knowledge_reveal text, -- what this scene must reveal (from knowledge_layer)
fountain_body text, -- the scene in Fountain
page_estimate numeric,
vo_lines int, -- tracked for vo_discipline
created_at, updated_at
)
screenplay_takes ( -- mirror story_episode_takes
id uuid pk, screenplay_scene_id uuid,
fountain_body text, status text, -- draft → live → archived → rejected
created_at
)
screenplay_scene_critiques ( -- mirror episode_take_critiques
id uuid pk, screenplay_take_id uuid,
critic text, findings jsonb, decision text,
created_at
)
Beat sheet lives as a column on screenplay_projects (jsonb) or its own
screenplay_beats table if the steering UI wants per-beat rows.
LLM tiers (reuse llm.ts)
- Planning (Sonnet): scene re-segmentation, beat sheet, adaptation-fidelity rubric. Reasoning-heavy, one call each — same tier as bible bootstrap / spine.
- Generation (Haiku): per-scene Fountain prose. Cheap, parallelizable, cached per scene (~$0.01–0.04/scene).
- Promote (Opus, optional): re-roll a flagged hero scene at premium quality, same as the chapter promote tier.
Whole-novella adaptation lands well under $1 in tokens. Run the critique loop generously.
UI surface
A Reverse Screenplay admin module (its own page like Scene Studio, or a
panel on /seasons/[id]). Per
novella: pick target_pages → generate + edit the beat sheet (the steering
point) → generate scenes → run critique panel → review fidelity score →
assemble → preview Fountain / download FDX + PDF → "send to Scene Studio." Per-
scene regeneration and per-critic apply/dismiss are essential (you'll re-roll
individual scenes constantly).
Scene Studio contract (the integration win)
screenplay_scenes becomes the canonical, ordered, slug-lined scene list that
Scene Studio's generateSceneBreakdown consumes — it stops guessing "the
strongest beat" of a chapter and gets real boundaries with location, time,
speakers, and dialogue already resolved. One link field
(scene_projects.screenplay_scene_id ↔ screenplay_scenes.id) closes the
story→script→movie loop end to end.
Phasing
- Beat sheet + scene re-segmentation (passes 0–1) + steering UI. Proves the
adaptation brain and the
target_pagesdial. Cheapest, highest-leverage slice; produces a reviewable plan before any screenplay prose exists. - Per-scene generation + Fountain render (passes 2–4). Full canonical screenplay end to end; reuses the chapter prompt-block + caching pattern.
- Critique panel + fidelity rubric + golden tests. The quality layer — the reason the engine is worth building. Generous critique because text is cheap.
- FDX / PDF export + Scene Studio contract. The sellable artifact and the machine handoff; closes the pipeline.
Risks / open questions
- Interiority ceiling: how well does externalization preserve a heavily internal novella? Validate pass 2 on one interior-heavy season before committing — this is the make-or-break pass.
- Compression coherence: cutting a subplot must also cut its setups and
payoffs. The
planted_threadspayoff graph is the lever; verify the beat sheet never orphans a planted thread. - Fidelity vs. craft tension: a screenplay can score high on craft while losing the story. The rubric (Quality §B) exists precisely for this; weight it as a gate, not a footnote.
- Format fidelity on export: Fountain→FDX/PDF edge cases (dual dialogue, montages, intercuts). Pin a parser, golden-test the exports.
- Voice drift across a long script: distinct character voices over 90+ pages
is harder than over one chapter;
voice_distinctioncritic + bible grounding are the mitigations.
Brand alignment
Ships under EmberKiln (graphene.fm) as the script stage of story→script→movie — the same wedge as Scene Studio: the screenplay is born from a story the writer actually lived in and rehearsed (the Odessa fiction→real- change loop), now with a shootable script and a screen at the end of it. Keep the story and the writer's voice primary; the format serves them.