V5.0 MICRO-DRAMA / VERTICAL SERIES

หนึ่งความลับ
อีกตอนที่ต้องดูต่อ

วางซีรีส์ละครสั้นแนวตั้งด้วย Drama Beat Engine คุมเสียงตัวละคร และต่อเรื่องจาก Cliffhanger ของแต่ละตอน

03คลิปตัวอย่างจริง
01เอกสารอ้างอิง
FREEรับ License Key
ภาพตัวอย่าง A9 STUDIO จากโปรเจกต์
0014-Micro-Drama / ภาพตัวอย่างจากสตูดิโอ
YOUR STUDIO PASS

เริ่มสร้างงานของคุณ

กดตรงนี้ เพื่อไป ที่ Google flow ↗
NO-CODE PROMPT BUILDER

มีไอเดียก็เริ่มได้
ให้เครื่องมือช่วยเรียบเรียง

เลือก Trope แนวภาพ ภาษา และไอเดีย เครื่องมือช่วยวางพล็อต ตัวละคร และ Storyboard ก่อนเลือกภาพและผลิตทีละตอนใน Google Flow

  1. กดรับ License Key
  2. คัดลอก Key หรือข้อความที่ต้องการ
  3. เปิดเครื่องมือ Google Flow เพื่อวางใช้งาน

หน้านี้เป็นจุดรับ Key และคลังอ้างอิง การสร้างภาพและวิดีโอทำในเครื่องมือ Google Flow

FROM THE STUDIO

ดูจังหวะ ค่อยต่อยอดเป็นงานของคุณ

เลือกคลิป ปรับความเร็ว หรือขยายภาพเพื่อดูรายละเอียดของผลงานตัวอย่าง

PROMPT & WORKFLOW LIBRARY

เลือกแนวทางที่อยากสร้าง

รวบรวมข้อความจากเอกสารจริง ทั้ง Prompt, Template และค่าพรีเซ็ต โดยระบุชนิดและไฟล์อ้างอิงแต่ละรายการ

Face-slap revengepreset

Preset ต้นฉบับ · ค่าตั้งต้นและคำสั่งที่เอกสารระบุ · 0014-lakon-v5-en.md

| ⭐ Face-slap revenge | tr1 a2 b5 c12 d9:16 e1 f1 g1 h1 i1 k1 l=drama m1 n2 o5 p1 q2 r2 t5 u2 mu4 |

Source prompt fragments:
present-day Bangkok, Thailand: condos, BTS skytrain, street food, traffic, luxury hotels and offices
balanced drama with moments of humor
happy ending, the protagonist fully vindicated
the first line is an insult, a shock or a confession
dramatic push-ins on reactions, the micro-drama look
tension, arguments and mild peril, no blood, no gore
Hidden CEOpreset

Preset ต้นฉบับ · ค่าตั้งต้นและคำสั่งที่เอกสารระบุ · 0014-lakon-v5-en.md

| Hidden CEO | tr2 a2 b5 c12 d9:16 e1 f1 g1 h1 i1 k1 l=drama m1 n2 o5 p1 q2 r1 t5 u2 mu4 |

Source prompt fragments:
present-day Bangkok, Thailand: condos, BTS skytrain, street food, traffic, luxury hotels and offices
balanced drama with moments of humor
happy ending, the protagonist fully vindicated
open in the middle of the action
dramatic push-ins on reactions, the micro-drama look
tension, arguments and mild peril, no blood, no gore
Mother-in-law warpreset

Preset ต้นฉบับ · ค่าตั้งต้นและคำสั่งที่เอกสารระบุ · 0014-lakon-v5-en.md

| Mother-in-law war | tr4 a2 b5 c12 d9:16 e1 f1 g1 h3 i1 k1 l=familydrama m2 n3 o1 p1 q2 r2 t5 u2 mu1 |

Source prompt fragments:
present-day Thai provincial town, shophouses, markets, temples
dark, tense mood, low-key lighting
clear satisfying resolution
the first line is an insult, a shock or a confession
dramatic push-ins on reactions, the micro-drama look
tension, arguments and mild peril, no blood, no gore
Rebirth revengepreset

Preset ต้นฉบับ · ค่าตั้งต้นและคำสั่งที่เอกสารระบุ · 0014-lakon-v5-en.md

| Rebirth revenge | tr5 a2 b5 c12 d9:16 e1 f1 g1 h1 i3 k1 l=drama m1 n3 o5 p1 q2 r4 t5 u2 mu1 |

Source prompt fragments:
present-day Bangkok, Thailand: condos, BTS skytrain, street food, traffic, luxury hotels and offices
dark, tense mood, low-key lighting
happy ending, the protagonist fully vindicated
open on a glimpse of the climax (or the death, for rebirth), then go back
dramatic push-ins on reactions, the micro-drama look
tension, arguments and mild peril, no blood, no gore
Contract marriagepreset

Preset ต้นฉบับ · ค่าตั้งต้นและคำสั่งที่เอกสารระบุ · 0014-lakon-v5-en.md

| Contract marriage | tr6 a2 b5 c12 d9:16 e1 f1 g1 h5 i1 k=k_pastel_pink_improved l=romance m1 n7 o5 p1 q2 r2 t5 u1 mu3 |

Source prompt fragments:
present-day Bangkok, Thailand: condos, BTS skytrain, street food, traffic, luxury hotels and offices
sweet romantic mood, soft glances, warm light
happy ending, the protagonist fully vindicated
the first line is an insult, a shock or a confession
dramatic push-ins on reactions, the micro-drama look
family-safe, no violence or crude language (soft impact locked on)
Thai ghost lovepreset

Preset ต้นฉบับ · ค่าตั้งต้นและคำสั่งที่เอกสารระบุ · 0014-lakon-v5-en.md

| Thai ghost love | tr8 a2 b5 c12 d9:16 e1 f1 g2 h7 i3 k=k_cinematic_movie l=supernatural m3 n5 o3 p2 q2 r7 t5 u2 mu6 |

Source prompt fragments:
rural Isan village in northeast Thailand, rice fields, wooden stilt houses
eerie Thai folk-tale atmosphere, mist and lanterns
bittersweet ending
quiet ordinary moment with one wrong detail
dramatic push-ins on reactions, the micro-drama look
tension, arguments and mild peril, no blood, no gore
Office war comedypreset

Preset ต้นฉบับ · ค่าตั้งต้นและคำสั่งที่เอกสารระบุ · 0014-lakon-v5-en.md

| Office war comedy | tr9 a2 b5 c12 d9:16 e1 f1 g1 h6 i1 k1 l=comedy m1 n1 o5 p1 q2 r1 t4 u1 mu5 |

Source prompt fragments:
present-day Bangkok, Thailand: condos, BTS skytrain, street food, traffic, luxury hotels and offices
light, playful mood, bright colors
happy ending, the protagonist fully vindicated
open in the middle of the action
static locked frames, action plays inside the frame
family-safe, no violence or crude language (soft impact locked on)
Inheritance warpreset

Preset ต้นฉบับ · ค่าตั้งต้นและคำสั่งที่เอกสารระบุ · 0014-lakon-v5-en.md

| Inheritance war | tr11 a2 b5 c12 d9:16 e1 f1 g1 h3 i1 k1 l=familydrama m1 n3 o2 p1 q2 r3 t5 u2 mu1 |

Source prompt fragments:
present-day Bangkok, Thailand: condos, BTS skytrain, street food, traffic, luxury hotels and offices
dark, tense mood, low-key lighting
final twist that reframes the story
open on an unanswered visual question
dramatic push-ins on reactions, the micro-drama look
tension, arguments and mild peril, no blood, no gore
Cat-head dramapreset

Preset ต้นฉบับ · ค่าตั้งต้นและคำสั่งที่เอกสารระบุ · 0014-lakon-v5-en.md

| Cat-head drama | tr1 a2 b5 c12 d9:16 e1 f1 g1 h6 i1 k14 l=comedy m1 n6 o5 p1 q2 r2 t4 u1 mu5 |

Source prompt fragments:
present-day Bangkok, Thailand: condos, BTS skytrain, street food, traffic, luxury hotels and offices
absurd over-the-top comedy
happy ending, the protagonist fully vindicated
the first line is an insult, a shock or a confession
static locked frames, action plays inside the frame
family-safe, no violence or crude language (soft impact locked on)
Single short filmpreset

Preset ต้นฉบับ · ค่าตั้งต้นและคำสั่งที่เอกสารระบุ · 0014-lakon-v5-en.md

| Single short film | tr0 a1 c12 d9:16 e1 f1 g1 h1 i1 k1 l=drama m1 n2 o1 p2 q2 r1 t1 u2 ns=three_act mu1 |

Source prompt fragments:
present-day Bangkok, Thailand: condos, BTS skytrain, street food, traffic, luxury hotels and offices
balanced drama with moments of humor
clear satisfying resolution
open in the middle of the action
smooth dolly and slider moves, stable framing
tension, arguments and mild peril, no blood, no gore
10. AI processingtemplate

ข้อความต้นฉบับ · 0014-lakon-v5-en.md

FORMAT: {a} · EP {n} of {N} · arc beat: {arcBeat} · this EP ends with: {CLIFF of type cl | END with ending o}.
TROPE: {tr promptEn}. OPENING HOOK (EP1 shot 1): {r}. CAMERA LANGUAGE: {t}. CONTENT LEVEL: {u}. DIALOGUE TONE: {h}. ALWAYS AVOID: {w[]}.
SOFT IMPACT: {si ? 'never show contact; show the moment before or after (slap = raised hand, then the face turned aside)' : 'contact may be implied by a fast cut; no blood'}.
LANGUAGE: write all dialogue, inner voice, narration, names and titles in {f}; dialect {g}; never mix languages in one line.
CONTEXT: everyday details, food, vehicles and places match {m}; people are {e} unless the style requires otherwise (cat-head, fruit).
10.1b Arc, secrets and relationships (text model, JSON only)template

ข้อความต้นฉบับ · 0014-lakon-v5-en.md

You are the head writer of a vertical micro-drama series for Thai audiences. Write the arc of "{title}" in {N} episodes.
Synopsis: {synopsis}. Trope: {tr promptEn}. Arc beats per EP (fixed): {arcPlan}. Ending of the final EP: {o}.
Each EP: a 2–3 sentence outline in {f}, the cliff type for EPs 1..N-1 from {cl labels}, and the exact cliff line in {f} (one complete sentence, never cut mid-word).
List the secrets that drive the series (English), who knows each at the start, and in which EP and to whom each is revealed. Every secret is revealed exactly once; the biggest one in the final EP.
List the key relationships (English).
Reply: {"episodes": [{"ep": 1, "arcBeat": "…", "outline": "…", "cliffType": "cl1", "cliffLine": "…"}],
        "secrets": [{"id": "s1", "text": "…", "knownBy": ["c1"], "revealEp": 3, "revealedTo": ["c2"]}],
        "relationships": [{"a": "c1", "relation": "…", "b": "c2", "note": "…"}]}
10.2b Voice lock (text model)template

ข้อความต้นฉบับ · 0014-lakon-v5-en.md

Write one fixed voice description for {name} ({gender}, about {age}, {role}, {dramaRole}). Voice chip: {cv}. Accent: {g}. Dialogue tone: {h}.
One English line, max 30 words, in this order: name, ethnicity and gender, age, pitch, accent, voice texture, speaking habit. No story details. It must be usable word for word in every clip.
Reply: {"voiceLock": "Mali: Thai woman, about 25, medium-high pitch, Central Thai accent, soft clear voice, polite with quick soft sentence endings"}
10.5 Storyboard (system instruction, keep buildStoryboardSystemPrompt and extend)template

ข้อความต้นฉบับ · 0014-lakon-v5-en.md

You are the showrunner and screenwriter of A9 STUDIO, writing a vertical micro-drama. Write shots {from}–{to} of {total} of EP {n}/{N} of "{title}".
Synopsis: {synopsis}. Arc beat of this EP: {arcBeat}. EP outline: {outline}. This EP ends with: {cliff type + cliff line | END + ending o}.
ROLE PLAN (fixed, one shot each, in order): {plan.map(s => `${s.n}. ${s.role} · line type ${s.lineType} · motion template: ${ROLE_MOTION[s.role]} · role SFX: ${s.sfx}`)}
Roles: HOOK = conflict already happening in the first 2 seconds · SETUP = who is who, who wants what · PRESSURE = the antagonist pushes, the audience gets angry · TURN = the game flips (identity, evidence, a call, a newcomer) · PAYOFF = the satisfying moment, the loser's face · CLIFF = one complete revealing sentence, cut on the reaction · END = calm final image.
EP1 shot 1 follows the opening hook {r}. EP2+ shot 1 continues EP {n-1}'s last shot (the same moment, a new angle): "{previous cliff line}" — {previous last 3 shots summary}. No greeting, no recap narration.
LINES: exactly one speaker per shot. Line types: "dialogue" (speaker on screen, lips move), "inner_voice" (the speaker's thought, lips closed), "narration" (off-screen narrator), "silent" (no line). Never more than 2 silent shots in a row. Max syllables: 12 for 4 s, 20 for 6 s, 28 for 8 s, 36 for 10 s; HOOK lines max 70 %. Count syllables carefully and return "syllables". Lines in {f} ({g}), tone {h}.
SECRETS LEDGER (state at the start of this EP): {ledger}. A character may only act on a secret they know. Reveal only the secrets planned for this EP, each once. Return per shot "usesSecrets" and "revealsSecrets" (ids).
SOFT IMPACT: {rule}. Never describe the moment of contact in visualDescription.
For each shot set continuity: "continue", "new_angle", "new_location", "time_jump", "flashback", or "from_last_cliff" (EP2+ shot 1 only).
Use only these characters and locations (ids). The protagonist and the antagonist appear in this EP. Product "{product}" appears naturally in {target} shots.
visualDescription in English, concrete (who, where, action, expression), no text overlays. motion = the role's motion template with the braces filled, English. sfx = concrete, English, from the role SFX; never describe music.
Also return "flags": {"conflictBy2s": true|false, "continuesCliff": true|false, "directImpact": [shot ids]} and "musicCues": [{"from": "0:00", "to": "0:12", "cue": "…"}] for the whole EP in the mood {mu}, following: duck under lines, sting at each TURN, silence 0.5 s before the main PAYOFF line then swell, heartbeat into the CLIFF and hard stop.
{Chinese drama / Thai period requirement block when k2 / k3}
Previous shots summary: {summary}. Return ONLY JSON.
10.5b Automatic fixtemplate

ข้อความต้นฉบับ · 0014-lakon-v5-en.md

Fix only these problems in the EP {n} storyboard JSON below and return the full JSON in the same format. Keep every other shot unchanged; keep the role plan, speakers, line types and syllable limits.
Problems: {list, e.g. "shot 1: no conflict in the first 2 seconds", "shot 7: Mali uses secret s2 before she learns it", "shot 9: slap shown at contact", "shot 4: 27 syllables, limit 20"}
Storyboard: {json}
10.7 Still prompt and image checktemplate

ข้อความต้นฉบับ · 0014-lakon-v5-en.md

You check still options for shot {n} of EP {ep} of a vertical micro-drama. Image P = the previous locked still (for "continue") or EP {ep-1}'s last locked still (for "from_last_cliff"). Images C1… = character portraits. Image L = the location. Images 1–3 = candidates.
Expected: {visualDescription}. Role: {role}. In frame: {cast names}. Speaker: {speaker or none} ({scriptType}). Place: {location}. Relation: {continuity}. Emotion: {emotion}. Style: {k label}. Soft impact: {on|off}.
For each candidate set false when:
singleFrame — collage, grid, split screen, panels, character sheet, comic page, border or inset;
characterMatch — a face, hair or outfit differs from its portrait (outfit only when the wardrobe rule keeps it);
placeMatch — clearly a different place than image L;
continuous — only for "continue" and "from_last_cliff": camera, light, place or wardrobe differ from image P (for "from_last_cliff" the angle may differ);
textFree — readable text, subtitles, watermark or unplaced logo;
anatomy — distorted hands, extra fingers or warped faces;
speakerVisible — only for "dialogue": the speaker's face and mouth are not clearly visible;
noImpact — only when soft impact is on: a hand, object or water touching a person at the moment of contact.
Rank the remaining candidates by: role clarity · emotion · continuity with image P · beauty.
Reply: {"pick": 1|2|3|0, "reason": "max 12 words", "singleFrame": [..3], "characterMatch": [..3], "placeMatch": [..3], "continuous": [..3], "textFree": [..3], "anatomy": [..3], "speakerVisible": [..3], "noImpact": [..3]}  (0 = none valid → redraw)
10.11 Video prompt (MUST — this key order, every clip)prompt

โครงสร้าง JSON ต้นฉบับ · 0014-lakon-v5-en.md

{
  "voice_lock": { "Mali": "Mali: Thai woman, about 25, medium-high pitch, Central Thai accent, soft clear voice, polite with quick soft sentence endings" },
  "audio": {
    "type": "lip_sync_dialogue | inner_voice | voiceover_narration | none",
    "spoken_language": "Thai (Central Thai accent)",
    "speaker": "Mali",
    "line": "{dialogueText}",
    "timing": "speech starts at 0.3s and ends before {d - 0.7}s",
    "delivery": "{sd} emotion, {vb} acting energy",
    "others": "everyone else in the shot keeps their lips closed and does not speak"
  },
  "scene_action": "{visualDescription}",
  "motion": "{motion}",
  "camera_movement": "{va}",
  "acting_energy": "{vb}",
  "ending": "{vd}",
  "emotion": "{sd}",
  "sfx": "{sfx}, {vc[]}",
  "music": "NO MUSIC",
  "setting": "{m}",
  "aspect_ratio": "{d}",
  "characters": [{ "name": "…", "acting": "speaking naturally | listening with subtle emotional reaction" }],
  "render_rules": "keep characters, wardrobe and place identical to the start frame; no subtitles, no text, no logos; {soft impact rule}"
}
10.12 ✨ AI suggest (extension scenes)template

ข้อความต้นฉบับ · 0014-lakon-v5-en.md

You see the last frame of a micro-drama clip (EP {ep}). Suggest 3 short follow-up shots that keep the characters, wardrobe and place exactly as they are and make the moment more satisfying.
Each has one speaker (or silent), a line in {f} of max {limit} syllables, and respects soft impact {si}.
Allowed option codes: xa, xb, xc, xd (multi), xe (4|6|8|10), xf. Labels: {xa–xf labels}.
Reply: [{"title": "max 5 words", "text": "what happens next, max 25 words", "speakerId": "c1|null", "scriptType": "dialogue|inner_voice|narration|silent", "line": "…", "syllables": 14, "codes": {"xa": "xa1", "xb": "xb1", "xc": "xc1", "xd": ["xd1"], "xe": 6, "xf": "xf1"}}]
11. Save / load & series bibleprompt

โครงสร้าง JSON ต้นฉบับ · 0014-lakon-v5-en.md

{
  "a9Series": true, "version": 3,
  "title": "…", "trope": "tr1", "narrativeStructureId": null, "customStyle": null,
  "selections": { "…": "…" },
  "arc": [{ "ep": 1, "arcBeat": "…", "outline": "…", "cliffType": "cl1", "cliffLine": "…", "status": "done" }],
  "characters": [{ "id": "c1", "name": "…", "role": "…", "dramaRole": "protagonist", "gender": "…", "age": 25, "description": "…", "voice": "cv3", "voiceLock": "…", "imageBase64": "…" }],
  "narrator": { "voice": "j1", "voiceLock": "…" },
  "locations": [{ "id": "loc1", "name": "…", "description": "…", "singleBase64": "…" }],
  "relationships": [{ "a": "c1", "relation": "…", "b": "c2", "note": "…" }],
  "secrets": [{ "id": "s1", "text": "…", "knownBy": ["c1"], "revealEp": 3, "revealedTo": ["c2"], "revealed": false }],
  "lastCliff": { "ep": 2, "shotId": 12, "line": "…", "stillBase64": "…" }
}
LIVE VARIABLE REMIX

ลองเปลี่ยนตัวแปร เห็นข้อความทันที

ใช้ Template จากเอกสารต้นฉบับ ช่องที่แสดงตรงกับตัวแปรในรายการที่เลือก

ORIGINAL DOCUMENTATION

อ่านคู่มือต้นฉบับ

อ่านรายละเอียดคู่มือต้นฉบับได้ด้านล่าง

0014-lakon-v5-en.md

A9 STUDIO V5 — Vertical Micro-Drama — Tool Spec

Base app: the "Lakon-v4" short-film / lakorn generator (App.tsx, constants.ts, types.ts, components/SetupStep.tsx, CastStep.tsx, StoryboardStep.tsx, StillsStep.tsx, VideoStep.tsx, CinemaStep.tsx, Primitives.tsx, services/promptService.ts, poolScheduler.ts, ffmpegService.ts). Built on the V4 spec (0007-lakon-v4-en.md). 6 steps: Setup → Cast → Storyboard → Stills → Video → Cinema Room. Keep its flow, data model (StoryState, Character, Location, Shot, PlotOption, PoolJob), Flow SDK calls, NARRATIVE_STRUCTURES (45), VIDEO_STYLES (46), GENRES (20), prompt builders, safe-prompt repair, series hand-off and FFmpeg merge. V5 changes: 1. Vertical micro-drama series is the main format — 5 episodes × 12 shots × 6 s (72 s per EP), 9:16. Every EP opens with a hook and ends on a cliffhanger. Single short film stays as the second format. 2. Drama Beat Engine (1.1–1.5) — 7 roles (HOOK · SETUP · PRESSURE · TURN · PAYOFF · CLIFF · END) drive shot size, camera, emotion, line length, shot ending and SFX. Per-EP shot templates (first · middle · final) and a series arc. 3. 12 micro-drama tropes are the main entry (4.2); the 45 narrative structures move to "Advanced" and only steer the arc content. 4. Voice lock per character and narrator, one speaker per shot, Thai syllable limits, 4 line types (Dialogue · Inner voice · Narrator · Silent). No music inside clips; a Music cue sheet per EP. 5. Soft impact (slaps, splashes, throws shown before / after the contact) on by default; content level default u2 Teen. 6. Series bible with relationships, a Secrets ledger and the last cliff; plan all EPs first, produce one EP at a time (EP tabs). 7. Retention check (8 rules) after every storyboard draft (6.2). 8. Kept from V4 unchanged: click feedback (2.1), regenerate controls (2.2), ✨ Start with AI (4.0, extended), keyframe rules and continuity by image reference (7.0, 7.0b), sequential pick flow (7.1), generation pool (7.2), video chips and + Add scene (8.0, 8.1), Cinema Room, intro and brand, bug fixes (13).

Option format: code ⭐ = default · UI label — prompt fragment (sent to the model, never shown as UI text). Where a stored value already exists in types.ts / constants.ts, keep that stored value; only the UI label changes. Every section marked MUST is required; the checklist (14) lists them.


0. Language, context & brand rules (MUST)

  1. All UI text is English, exactly as written in this spec (labels, toasts, errors, placeholders, progress messages, file names). No translation layer.
  2. Prompts to image/video models are English.
  3. Plot, dialogue, inner voice, narration, character names, captions and the cover title are written in the selected output language f (default Thai). All other AI text shown in the UI (analysis, reasons, suggestions, secrets ledger) is English.
  4. Thai context: default ethnicity Thai, default setting Thailand. Every people prompt contains the ethnicity e; every location and shot prompt contains the setting m (e.g. in Bangkok, Thailand).
  5. Spoken voice (V5): every spoken line is sent with the Voice lock of its speaker word for word (5.1) and the language and dialect (f, g), e.g. "spoken_language": "Thai (Central Thai accent)". One speaker per shot. Lines are limited by syllables: 12 (4 s) · 20 (6 s) · 28 (8 s) · 36 (10 s); speech window 0.3 s → duration − 0.7 s.
  6. No music inside clips — every video prompt contains "music": "NO MUSIC". Music is added in editing from the Music cue sheet (9).
  7. Soft impact (1.5) is on by default: no hit, slap, punch, kick or weapon contact is ever shown in a still or a clip.
  8. No text, subtitles, logos or watermarks inside generated images and clips (the AI poster title and a placed product's own label are the only exceptions).
  9. Brand: app name "A9 STUDIO", version label "V5.0 Micro-Drama", component A9StudioApp. Logo: "A9 STUDIO logo (to be provided)" — no external logo URL. Copyright lines "Copyright of A9 STUDIO" · "Facebook: A9 STUDIO". Project file A9_STUDIO_V5_{title}_{yyyymmdd}.json. Series flag a9Series: true (on import also accept fileFlag and the legacy lakonSeries flag). No other brand name in UI, prompts, placeholders, file names or JSON keys.
  10. Click feedback is not optional (do not skip): every work button uses <ActionButton> and every card uses <CardStatus> (2.1). A build without this is incomplete.
  11. File names: titles are sanitized with /[^\p{L}\p{N}_-]+/gu → "_" (keeps Thai titles readable); empty → Untitled. Fixed parts of file names are English.

1. Concept

An app that turns a trope pick or a one-line idea into a vertical micro-drama series (or a single short film). The user clicks buttons to pick a trope, format, style, language, cast, mood and camera language; the AI writes the whole series arc and a secrets ledger, casts characters with reference portraits and locked voices, scouts locations, then — one episode at a time — writes a 12-shot storyboard where every shot has a drama role (hook, pressure, turn, payoff, cliffhanger), draws 3 still options per shot that the user picks one by one, animates each locked still with the speaker's locked voice, and merges the episode into one vertical clip with a ready-to-post caption and poster. Episode n+1 opens exactly on episode n's cliffhanger.

  • Title "A9 STUDIO" · subtitle "V5.0 Micro-Drama"
  • Key features: ✨ Start with AI (idea → series arc → cast → voices → EP1 storyboard in one click) · 12 micro-drama tropes · Drama Beat Engine — every shot's camera, emotion and ending follow its role · Cliffhanger every episode · One voice per character — locked across every clip and episode · Secrets ledger — nobody knows what they should not · Soft impact — slaps and splashes that pass the video model · click-only setup · 47 visual styles + your own custom style · pick-your-still storyboard with continuity · 4-job pool with live countdown · + Add scene from the last frame · in-browser episode merge · caption + AI poster.

1.1 Drama Beat Engine — 7 roles (MUST)

Every shot has one role. The role sets the defaults of the shot; the user can override any of them per shot (1.4).

Role Purpose Camera angle sa Camera move va Emotion sd Line Shot ending vd Acting vb SFX default
HOOK something is already happening in the first 2 s Extreme close-up (alt Close-up) va9 Fast push-in Shock short shock line, max 70 % of the syllable limit vd7 Hard cut on the peak vb5 Intense a sharp impact sound or a sudden gasp
SETUP who is who, who wants what Two-shot (alt Medium) va11 Slow dolly Calm (alt Tension) normal vd1 Natural cut vb1 Natural soft room tone
PRESSURE the antagonist pushes, the audience gets angry Low angle on the antagonist (alt High angle on the victim) va2 Slow push-in Tension (alt Sadness) the antagonist speaks vd2 Hold on the victim's face 1 s vb3 Heightened lakorn low murmurs of onlookers
TURN the game flips: identity, evidence, a call Insert detail on the first TURN shot of the EP, Close-up after va10 Rack focus Shock the protagonist or a newcomer speaks vd4 Freeze-like hold vb5 Intense a deep bass hit
PAYOFF the satisfying moment the audience waited for Close-up on the loser (alt Low angle on the winner) va2 Slow push-in Triumph (alt Shock) short cool line, or Silent vd2 Hold on the reaction 1 s vb3 Heightened lakorn onlookers gasp
CLIFF make them watch the next EP Extreme close-up (or a face half revealed) va9 Fast push-in Shock (alt Fear) one complete revealing sentence, never cut mid-word vd7 Hard cut on the peak vb5 Intense a single heartbeat thump
END last shot of the final EP or of a single film same framing as EP1 shot 1 when o8, else Wide va3 Pull-out reveal from ending o optional final line vd8 Final hold 1.5 s vb6 Tender gentle closing ambience

Motion templates (English, {…} filled by the AI per shot in the motion field, 10.5):

export const ROLE_MOTION = {
  HOOK:     'Fast push-in on {subject} within the first second; the action is already happening: {action}.',
  SETUP:    'Slow dolly across {place}; {who} {action}; natural small movements.',
  PRESSURE: 'Slow push-in on {antagonist} towering over {victim}; {victim} shrinks back; onlookers turn to watch.',
  TURN:     'Rack focus from {foreground} to {detail}; everyone freezes for a beat.',
  PAYOFF:   "Slow push-in on {loser}'s face as the truth lands; {winner} stands calm and still.",
  CLIFF:    'Fast push-in on {subject} as {reveal}; cut at the peak of the reaction.',
  END:      'Slow pull-out from {subject} revealing {place}; everything settles; hold still for the last 1.5 seconds.',
};
  • Duration: every role uses the shot duration vt (default 6 s); the syllable limit follows the actual duration.
  • Still prompt hint per role: HOOK "mid-action, the conflict visible at a glance" · SETUP "clear relationship between the people in frame" · PRESSURE "power imbalance: the antagonist higher and closer, the victim smaller" · TURN "the key object or the newcomer in sharp focus" · PAYOFF "the loser's face as the truth lands" · CLIFF "the reveal at its peak, face or object filling the frame" · END "calm, wide, settled".

1.2 Shot templates per episode (MUST — 12 shots)

H = HOOK · S = SETUP · P = PRESSURE · T = TURN · PO = PAYOFF · C = CLIFF · E = END

Template Used for Shots 1 → 12
first EP1 of a series H · S · S · P · P · P · T · PO · S · P · T · C
middle EP2 … EP(N−1) H · S · P · P · P · T · PO · PO · S · P · T · C
final last EP of a series H · S · P · P · T · T · PO · PO · PO · PO · S · E
single single short film (a1) H · S · S · P · P · T · P · P · T · PO · PO · E

Scaling to c shots (buildEpisodePlan, 12): - More than 12: insert extra P shots before the first T, then extra PO shots after the last PO, alternating P, PO, P, PO… - Fewer than 12: remove S shots from the last to the first (keep the first S), then P shots (keep one), then the extra T and PO (keep one of each). - Never remove the H shot or the last shot (C or E). Minimum = 5 shots (H · P · T · PO · C/E). Below 5: warning "Too short for a micro-drama episode — at least 5 shots" and the storyboard button is disabled.

1.3 Series arc (MUST)

The plot step writes one outline per EP from this arc; the trope (4.2) fills the concrete events.

EP (of 5) Arc beat Default cliff cl
EP1 Humiliation + a hint of hidden power cl1 Identity about to be revealed
EP2 Pressure grows + the first small payoff cl2 The wrong person walks in
EP3 Midpoint big turn — part of the truth comes out cl5 Evidence appears
EP4 The villain strikes back — all seems lost cl3 Threat
EP5 Full reveal + big payoff + ending o (END, no cliff)
  • Other EP counts: EP1 and the final EP stay. The middle EPs keep this order: "Midpoint big turn" in the middle, "All seems lost" right before the final EP (when there are ≥ 4 EPs); remaining middle EPs alternate "Pressure EP" (pressure grows, small payoff) and "Payoff EP" (a clear win, then a bigger threat). 2 EPs = EP1 + final. 3 EPs = EP1 + Midpoint + final.
  • Advanced structure (ns, 4.3): when a structure is picked, its beats are spread over the EPs in order and replace the arc text above; the shot templates (1.2) and the roles never change.
  • Single film (a1): no arc, one single template; the structure ns (⭐three_act) maps its beats over the shots as in V4.

1.4 Overrides per shot (MUST)

  • Every Beat-Engine field of a shot (sa, va, vb, vd, sd, line type sf, SFX, motion) shows a 📌 dot when the user changed it by hand.
  • Changing a shot's role (se) re-applies the new role's defaults to every field without 📌; pinned fields keep their value.
  • ↺ Reset to role (per shot) clears every 📌 and re-applies the role defaults. Fields already used by a locked still or a clip mark it Options changed · ↻ (2.2).

1.5 Soft impact (MUST — on by default)

Toggle si · Soft impact (⭐on; forced on and locked for u1, u4, u5). When on, every physical clash is shown before or after the contact, never the contact. The storyboard prompt (10.5), still prompt (10.7) and video prompt (10.11) apply SOFT_IMPACT_MAP (12); the Retention check flags any shot whose visualDescription matches IMPACT_WORDS (12).

Story says Shown as
slap the hand raised mid-air (shot n) → the face already turned aside, cheek touched, hand lowering (shot n+1)
punch / hit the fist pulled back → the person stumbling back, hand on the jaw
water splash water already leaving the glass, drops in the air → the wet face, hair dripping
throw an object papers landing and scattering across the table, never hitting a person
push / shove the victim stumbling back, the other's hand still extended
knife / gun never shown; threat by words, eyes and a step closer
  • Soft impact off (u2, u3 only): contact may be implied with a fast cut, still no blood, no weapon contact.

2. Button UI rules

  • Single = radio chips. Multi = toggle chips ✓. Numbers = number chips. On/off = toggle switch. Tropes, styles, genres and narrative structures = cards (keep).
  • No <select> anywhere (ethnicity, language, dialect, tone, audio mode, narrator, per-shot location, camera, model, duration, voice, role, line type — all chips).
  • Group header (keep ChipGroup): code · group name · current value + ✨ AI pick · ↺ Default · + Custom (where free text makes sense: e, cb, sb, ib, cl). The source defines these handlers but never passes them — wire them on every group.
  • Setup buttons: 🎲 Random matching set (coherent sets only: never k3 Ayutthaya with m1 modern Bangkok, never k14 cat-head with u4 kids-only realism, never tr8 ghost love with n1 light & fun) · Presets (4.1).
  • Dimmed + tooltip when unavailable: dialect chips of the other language · durations not allowed by the model ("Veo 3.1 Fast: 8 s only") · j when i1 and no shot uses Narrator · b when a1 · si off when u1 / u4 / u5 ("Required for this content level") · EP tabs not yet open ("Lock every still of EP {n−1} first").
  • Two-click confirm (keep TwoClickButton): destructive buttons change label to "Confirm …?" for 4 s.
  • Group codes: Setup a–w + tr (trope) + ns (structure) + si (soft impact) + mu (music for cue sheet) + cl (cliff per EP) · Engines za–zb · Cast ca–cf + per-character cv · Storyboard sa–sf · Stills ia–id · Video va–vd (presentation) + vm model · vt duration · vi ingredients · Extension xa–xf · Cinema pa–pb.

2.1 Click feedback — implementation contract (MUST — do not skip, do not simplify)

<ActionButton> — every button in the table below (no plain <button> for these; replaces the ad-hoc isXxx spinners of the source):

type BtnState = 'idle' | 'pressed' | 'queued' | 'busy' | 'done' | 'failed';
// pressed: set synchronously in onClick BEFORE any await (darker fill + scale 0.97), within 100 ms
// queued : spinner + "Queued · starts in {eta} s"   (disabled, small ✕ cancel)
// busy   : spinner + busyLabel + elapsed "0:12"      (disabled, small ✕ cancel)
// done   : green "✓ Done" 1.5 s → idle · failed: red "⚠ Failed · ↻ Retry" (tooltip = error)
// state follows the job in the pool (7.2), never a setTimeout guess. Never icon-only; aria-busy while queued/busy.

<CardStatus> — on every character, location, storyboard, still and video card:

type CardState = 'idle'|'waiting'|'queued'|'generating'|'checking'|'locked'|'rendering'|'done'|'failed'|'cancelled';
// top-right chip, always visible: waiting (grey, no spinner) "Waiting for shot 2 pick" · queued (blue) "Queued #2 · starts in 4 s"
// generating (amber) "Generating 2/3 · 0:18" · checking "Checking images…" · rendering "Rendering video · 0:41 / 3:00"
// locked/done (green) "Locked #2" / "Done" · failed (red) "Failed · Retry"
// while working: glowing border + thin progress bar; each option slot: shimmer + spinner + "#2 generating… 0:09";
// a finished slot appears at once (do not wait for all 3)

Buttons that MUST use it (busy label in brackets):

Step Buttons
Header / pool Save project ("Saving…" → "Saved (2.4 MB)") · Load project ("Opening…") · Load series · 🧹 Clear stuck · ⏹ Stop all ("Stopping…") · Resume
Setup ✨ Start with AI (stepper label, e.g. "Writing the series arc… 0:14") · trope cards ("Writing plots…") · genre cards · Re-roll plots · Use this plot · Create storyboard from this plot · ✨ Suggest 3 directions · ✨ AI pick · 🎲 Random… · preset chips (pressed + done) · ✨ Rewrite arc · secrets ledger ✨ Fill from plot
Cast 🎲 Re-roll cast ("Designing cast…") · Generate all images ("Queued 6 · 2/8 done") · card Create image / Redraw ("Drawing…") · Upload photo · Match the grid · Analyze · ✨ Write voice lock ("Writing…")
Storyboard Draft EP {n} storyboard / Re-draft ("Writing shots 6/12…") · Generate remaining scenes (N) · quick-fix chips ("Rewriting…") · retention ✨ Fix / ✨ Fix all ("Fixing…") · ↺ Reset to role · ✨ Soften impact
Stills Generate options / ↻ Generate 3 new ("Generating 0/3…") · slot ↻ · ✨ Suggest best ("Checking…") · ✓ Lock shot … & generate shot … · ↻ Update from previous · ⟲ Reset shot · slot ✕ Cancel / ↻ Retry / ⚡ Force run · refine chips · click an image to lock ("✓ Locked" within 100 ms)
Video Generate clip / ↻ Re-render ("Rendering… 0:41") · Render all clips of EP {n} ("Rendering 2/12…") · Make safe prompt ("Rewriting…") · + Add scene · ✨ AI suggest · ✨ Write motion prompt · Upload clip · ▶ Play / ⏹ Stop · ▶ Play EP in order
Cinema 🎬 Download EP {n} ("Merging 3/12… 42 %") · Cancel merge · Download all files (one by one) ("Downloading 5/17…") · 💾 Save series · Open EP n+1 · ✨ Write caption · 🎬 Make poster (AI) · Download cover (PNG) · Copy music cues · Record

Reference badge (MUST): every still option slot shows thumbnails of its references + Edited from: Shot 3 · #2 (relation sc1), From EP {n−1} cliff (relation sc6) or Refs: Location · Shot 3 · Mali · Somchai (other relations). A still request whose required reference is missing (locked previous still for sc1, locked last still of the previous EP for sc6, character portrait of a cast member in the shot) is not sent; the slot shows ⚠ Reference missing · ↻.

Acceptance test (must pass before delivery): ① click Generate options on shot 1 → button darker within 100 ms, then spinner "Generating 0/3…"; card chip amber; 3 slots shimmer ② clicking again while busy does nothing (no second job) ③ slots 2 and 3 count down "Starting in N s…" (7.2) ④ each image appears at once, chip counts "2/3" ⑤ a forced error shows "⚠ failed · ↻" and chip "Failed · Retry" ⑥ Generate clip shows "Rendering… 0:01" counting up, chip "Rendering video · 0:01 / 3:00", glowing border, then "✓ Done" + ▶ Play ⑦ every button in the table behaves the same ⑧ change shot 6's role from PRESSURE to PAYOFF → angle, camera move, emotion, ending, acting, SFX and motion switch to PAYOFF defaults at once; a field pinned before (📌) keeps its value; ↺ Reset to role clears the pin.

2.2 Regenerate controls (MUST)

  • ↻ Generate 3 new replaces the 3 options (the locked still stays locked until a new pick); with no options yet it reads Generate options. Slot ↻ regenerates one slot. ✎ Edit shot jumps to the storyboard card (visual action, line, relation, role).
  • "Visual action" is never empty; if empty, generate buttons are disabled ("Add the visual action first").
  • When shot n is regenerated or re-locked, shots n+1… made from the old still (and their clips) get the yellow badge Ref changed + ↻ Update from previous. Re-locking the last shot of EP n marks shot 1 of EP n+1 (sc6). Editing a character portrait marks every still with that character Ref changed.
  • Editing a character's Voice lock (or the narrator's) marks every rendered clip where that voice speaks Voice changed · ↻ Re-render.

3. Shell, intro & header

  • Intro screen (keep from V4): title "A9 STUDIO", subtitle "MICRO-DRAMA — vertical series made with AI, end-to-end"; boxes "Important warning" (uses Flow credits; AI output may need regeneration; quality depends on plot, cast and settings), "Terms of use" (no copying, reselling or redistributing without written permission from A9 STUDIO; users own their works, not the tool), "Usage tips" (make one episode at a time; soften wording if blocked; pick stills carefully, they drive every clip; keep each character's voice lock unchanged across episodes), copyright box; button Accept and enter → (remembered in local storage). Optional access-code screen ("Enter access code", Sign in, "Wrong code, try again"); the code is configured by the owner and is not part of this spec.
  • Header (keep): A9 logo + "A9 STUDIO" + version chip "V5.0 Micro-Drama" · current title · EP switcher EP1 · EP2 · … · EPN (current EP highlighted; ✓ when its stills are all locked; 🔒 when not open yet) · pool indicator (7.2) · Resume when paused · Save project · Load project · Load series.
  • Progress bar (keep calculateProgressPercent, per current EP: arc 15 · all portraits + voice locks 25 · storyboard 20 · locked stills 20 · clips 20) "EP {n} progress N% · Series {m}/{N} EPs done" · note "Project files do not include clips; clips stay in your Flow library".
  • Step tabs: 1 Setup · 2 Cast & Locations · 3 Storyboard · 4 Stills · 5 Video · 6 Cinema Room (tabs after Setup disabled until an arc / synopsis exists; Stills needs a storyboard of the current EP; Video needs ≥ 1 locked still of the current EP). Storyboard, Stills, Video and Cinema work on the EP chosen in the header.
  • Pool pause shows an in-app banner (not alert()): "Queue paused: {reason}" + Resume.
  • Media restore overlay after loading a project: "Restoring reference images (i/N)" for characters, locations, products and locked stills of every EP; failures count; uploads through the pool (kind upload) with 3 tries and 5 s wait on rate limit.

3.1 Episode production flow (MUST)

  • Plan all, produce one at a time: Setup writes the arc (one outline per EP) and the secrets ledger for the whole series. Cast and locations are shared by every EP.
  • EP gate: EP n's storyboard can be drafted only when every still of EP n−1 is locked (its shot 1 needs EP n−1's last locked still, sc6). Clips of EP n−1 can still be rendering.
  • Changing EP n−1 after EP n exists: EP n's shot 1 gets Ref changed; changing EP n−1's storyboard asks "EP {n} was written after this episode. Re-check EP {n} with the Retention check?" Re-check · Later.
  • Credit estimate card shows "EP {n}: ≈ N credits" and "Whole series: ≈ N credits".

4. Step 1 — Setup

Header "Series setup" · Reset settings (two-click, applies the ⭐ preset) · Load series (continue EP).

4.0 ✨ Start with AI — one-click start (MUST — the most prominent button of the app)

Look: full-width button at the top of Setup, above the presets — min 56 px high, gradient amber accent, icon ✨, label ✨ Start with AI, sub-label "Idea → series arc → cast → voices → EP1 storyboard, in one click". Uses <ActionButton> (2.1). A small text field next to it: "One-line idea (optional)".

One click runs, in order (each step after the previous one succeeded; every request through the pool 7.2): 1. Prepare — if no option was touched, apply the ⭐ preset (with its trope tr, genre l). Existing work is kept. 2. Plot & arc — synopsis already set → skip to the arc · one-line idea typed → expand it into title + synopsis following tr (10.1, 1 plot) · else generate plots for tr and use plot 1 (others stay visible). Then write the arc (one outline + cliff per EP) and the secrets ledger (10.1b). 3. Cast & locations — design them (10.2), unless characters exist; every character gets a Voice lock (10.2b). 4. Portraits & location images — queue every missing one (10.3, 10.4). 5. EP1 storyboard — draft it (10.5) when all portraits are done, then the Retention check (6.2) with its one automatic fix. 6. Open the Stills step on EP1 and start shot 1's 3 options, toast "EP1 storyboard ready — pick a still for shot 1".

Progress panel: vertical stepper Prepare → Plot → Arc & secrets → Cast → Voices → Portraits (5/7) → EP1 storyboard (6/12) → Retention check → Stills with ⏳ waiting · spinner + elapsed · ✓ · ⏭ skipped · ⚠ failed; live thumbnails for portraits. ⏹ Cancel (stops after the running request; finished work kept) and when paused ↻ Retry step · Fix it myself (closes the panel, jumps to the step). - If shots or stills already exist: confirm "This replaces the arc, storyboards, stills and clips. Save your project first?" Start over · Cancel. - The run never locks a still for the user (7.1), never drafts EP2+ and never renders video.

4.1 Section "One-click presets"

Values use codes; d stores the ratio, c and b numbers, tr the trope, ns the structure id, l the genre id. Every preset also sets za1 zb2 w1-4 si=on.

Preset (UI) Values
⭐ Face-slap revenge tr1 a2 b5 c12 d9:16 e1 f1 g1 h1 i1 k1 l=drama m1 n2 o5 p1 q2 r2 t5 u2 mu4
Hidden CEO tr2 a2 b5 c12 d9:16 e1 f1 g1 h1 i1 k1 l=drama m1 n2 o5 p1 q2 r1 t5 u2 mu4
Mother-in-law war tr4 a2 b5 c12 d9:16 e1 f1 g1 h3 i1 k1 l=familydrama m2 n3 o1 p1 q2 r2 t5 u2 mu1
Rebirth revenge tr5 a2 b5 c12 d9:16 e1 f1 g1 h1 i3 k1 l=drama m1 n3 o5 p1 q2 r4 t5 u2 mu1
Contract marriage tr6 a2 b5 c12 d9:16 e1 f1 g1 h5 i1 k=k_pastel_pink_improved l=romance m1 n7 o5 p1 q2 r2 t5 u1 mu3
Thai ghost love tr8 a2 b5 c12 d9:16 e1 f1 g2 h7 i3 k=k_cinematic_movie l=supernatural m3 n5 o3 p2 q2 r7 t5 u2 mu6
Office war comedy tr9 a2 b5 c12 d9:16 e1 f1 g1 h6 i1 k1 l=comedy m1 n1 o5 p1 q2 r1 t4 u1 mu5
Inheritance war tr11 a2 b5 c12 d9:16 e1 f1 g1 h3 i1 k1 l=familydrama m1 n3 o2 p1 q2 r3 t5 u2 mu1
Cat-head drama tr1 a2 b5 c12 d9:16 e1 f1 g1 h6 i1 k14 l=comedy m1 n6 o5 p1 q2 r2 t4 u1 mu5
Single short film tr0 a1 c12 d9:16 e1 f1 g1 h1 i1 k1 l=drama m1 n2 o1 p2 q2 r1 t1 u2 ns=three_act mu1

Preset = codes + idea starter (MUST): a preset also fills an empty "One-line idea" with its sample ("A cleaner humiliated at a hotel gala is the owner's daughter" · "The delivery rider they mocked is the new company owner" · "A new bride proves her mother-in-law framed her" · "Betrayed and killed on her wedding day, she wakes up one year earlier" · "A florist signs a one-year marriage contract with a cold heir" · "A village girl falls for the spirit guarding an old teak house" · "An intern takes down the boss who steals everyone's work" · "At the will reading, the maid's son is named the heir" · "A cat-faced cleaner turns out to own the whole office tower" · "One night, one secret, one choice"). If the user typed an idea: confirm "Replace your idea with the preset sample?" Replace · Keep mine. A preset changing k after cast exists uses the style-change confirm (4.7).

4.2 Section "Trope" — tr (cards, ⭐tr1) — the main entry (MUST)

Card grid (icon, label, logline, mini role strip of EP1, "Default cliffs" chips). Click = select; Write plots generates plots for it (10.1). Each trope's promptEn holds the logline, archetypes and the key P / T / PO events that the arc and storyboards must use.

Code Trope (UI) Logline · protagonist vs antagonist Pressure (P) Turn (T) Payoff (PO) Default l · n
tr1⭐ Face-slap revenge someone looked down on hides who they really are · humble protagonist vs arrogant snob public humiliation at an event the hidden status is revealed the snob loses face in front of everyone drama · n2
tr2 Hidden CEO / heir the "poor" one is the boss · disguised heir vs rude manager mocked for being poor staff bow to the protagonist the rude one is fired on the spot drama · n2
tr3 Cinderella a poor woman and a rich man · heroine vs his family his family blocks her her talent or past is revealed her grand entrance at the gala romance · n7
tr4 Mother-in-law war a new bride against a scheming mother-in-law the mother-in-law sets traps proof of the mother-in-law's scheme the husband stands by his wife familydrama · n3
tr5 Rebirth revenge betrayed to death, wakes up in the past · reborn heroine vs betrayers the old betrayal starts again she uses what she knows from the future the betrayers fall into their own trap drama · n3
tr6 Contract marriage a fake marriage turns real · bride vs the ex or the family the ex or the family pressures the contract is found a real confession of love romance · n7
tr7 Betrayal & comeback partner and best friend betray · fallen heroine vs the pair betrayed and thrown out returns transformed takes everything back drama · n3
tr8 Ghost / past-life love love across lifetimes · living lover vs whoever keeps them apart a bound spirit, a family against it past-life memories return the spirit is freed or they stay together supernatural · n5
tr9 Office war an underdog at work · intern vs toxic boss the boss steals credit and bullies proof surfaces promotion, the boss exposed comedy · n1
tr10 Swapped at birth the real child returns · real heir vs the fake one the real child is despised DNA result the real child takes their place at home familydrama · n3
tr11 Inheritance war a family fights over the will · hidden heir vs greedy relatives the scramble for the inheritance a secret heir appears the will reading flips everything familydrama · n3
tr12 The ex returns the one who left comes back · successful heroine vs the ex the ex who dumped her returns she is now the successful one she turns the ex down with grace romance · n2
tr0 No trope (use a structure) free plot; uses ns from Advanced — — — —

Trope promptEn example (tr1): Micro-drama trope "face-slap revenge": a humble protagonist secretly holds high status; an arrogant antagonist humiliates them in public; the hidden status is revealed step by step; every episode gives the audience at least one satisfying moment where the antagonist loses face; the final episode is a full public reveal.

4.3 Section "Advanced — Narrative structure" — ns (collapsed, cards, ⭐none for series, three_act for single film)

Collapsed panel "Advanced: narrative structure (optional)" with the note "For series, a structure only shapes the arc across episodes; every episode still follows Hook → Pressure → Turn → Payoff → Cliff." Keep the V4 panel: category filter chips All (45) · Classic & Hollywood · Drama & relationships · Documentary & process · Action & strategy · Mystery & thriller · Comedy & chaos · Structure & timeline + search field + 3-column card grid (category chip, name, one-line description, N beats + Show structure / Hide) + badge "Selected structure: …" + ✕ Clear. Stored as narrativeStructureId (keep ids). English text (replace the Thai thaiLabel, description, beats):

Classic & Hollywood - three_act Three-Act Structure — setup, confrontation, resolution. Beats: setup and inciting incident · rising conflict and midpoint · climax and resolution - heros_journey Hero's Journey — the hero leaves the familiar world, faces trials and a crisis, returns changed. Beats: leaving the ordinary world · trials and the great crisis · return with a new self - freytags_pyramid Freytag's Pyramid — tension climbs to a peak, then falls to the ending. Beats: exposition · rising action · climax · falling action · denouement - save_the_cat Save the Cat! — beat sheet from opening image to final image. Beats: opening image and catalyst · fun and games · midpoint and all-is-lost · finale - snowflake_method Snowflake Method — one core sentence grown into characters, events and scenes. Beats: one-sentence core · three disasters · scene-by-scene detail - kishotenketsu Kishotenketsu — introduction, development, unexpected turn, reconciliation. Beats: Ki (introduction) · Sho (development) · Ten (twist of perspective) · Ketsu (harmonious ending)

Drama & relationships - rivals_collaboration Rivals → Collaboration → Acceptance → Care — clash, forced teamwork, respect, quiet care. Beats: opening clash of words · need to work together · respect for each other's strengths · lingering quiet care - relationship_structure Relationship Structure — a bond forms, deepens, is tested, ends or heals. Beats: first meeting · closeness and trust · test or crack · forgiveness or farewell - rivalry_structure Rivalry Structure — two sides chase the same prize to a decisive showdown. Beats: declaration of rivalry · move and counter-move · decisive duel - rags_to_riches Rags to Riches — from hardship and contempt to hard-won success. Beats: hard life and humiliation · the big chance and relentless effort · success and pride - riches_to_rags Riches to Rags — from comfort and power to losing everything. Beats: comfort and carelessness · sudden failure or betrayal · loss and facing the truth - tragedy_structure Tragedy Structure — a flaw or fate leads to downfall. Beats: great goal with an inner flaw · irreversible decision · unavoidable downfall - transformation_structure Transformation Structure — an inner change of mind, behaviour or identity. Beats: old troubled mindset · crisis that shakes beliefs · rebirth with a new view - fish_out_of_water Fish Out of Water — thrown into an unfamiliar world, struggling to adapt. Beats: arrival in a strange place · misunderstandings and effort · adaptation and new value - stranger_comes_to_town Stranger Comes to Town — an outsider disturbs a calm community. Beats: calm community · arrival of a mysterious stranger · secrets exposed, irreversible change

Documentary & process - doc_subverting_assumptions Documentary: Question → Subverting Assumptions → Deeper Discovery — Beats: intriguing question · old belief overturned · mechanism and facts · conclusion and new view - doc_following_evidence Documentary: Following the Evidence — Beats: puzzle to investigate · competing hypotheses · testing the evidence · truth by evidence - doc_process_observation Documentary: Process Observation — Beats: delicate materials and steps · care in the making · the piece takes shape · deeper understanding - discovery_structure Discovery Structure — a hidden place, object or truth is uncovered step by step. Beats: first clue · following the trail · great secret revealed

Action & strategy - military_strategy Military Strategy: Conflict → Ambush → Luring from Base → Two-Sided Takeaway — Beats: deception and enemy expectation · trap and hidden unit · luring the main force out · camp taken, double victory - quest_structure Quest Structure — Beats: receiving the mission · trials and enemies on the way · reaching the goal - survival_structure Survival Structure — Beats: sudden danger · struggle with scarce resources · survival through will - chase_structure Chase Structure — Beats: chase begins · near capture · breaking-point confrontation - race_against_time Race Against Time — Beats: countdown starts · delaying obstacles · last-second decision - countdown_structure Countdown Structure — Beats: clear deadline · tension rising with time · resolution at zero - monster_in_the_house Monster in the House — Beats: space sealed · danger creeps closer · last stand in a tight space

Mystery & thriller - mystery_reveal Mystery → Reveal — Beats: intriguing case · misdirection and clues · full truth revealed - twist_ending Twist Ending — Beats: plausible path · small hidden anomaly · twist that flips everything - reversal Reversal — Beats: one side controls everything · unexpected turning point · complete reversal - false_victory False Victory — Beats: smooth celebration · ignored warning sign · it is not over - false_defeat False Defeat — Beats: deepest despair · game-changing discovery · counterattack and comeback - setup_payoff Setup → Payoff — Beats: small item planted · story moves on · it returns as the key - problem_solution Problem → Solution — Beats: key obstacle · experiments and failures · the real answer

Comedy & chaos - comedy_structure Comedy Structure — Beats: small misunderstanding · excuses that make it worse · truth comes out, laughter - slapstick_structure Slapstick Structure — Beats: ordinary activity · physical mishap spreads · total chaos - contrast_structure Contrast — Beats: extreme difference · clash of incompatible views · humour or insight from the difference - escalation_structure Escalation — Beats: tiny problem · overreaction spirals · full-scale mayhem - chain_reaction Chain Reaction — Beats: first domino falls · unstoppable consequences · spectacular finale

Structure & timeline - in_medias_res In Medias Res — Beats: open mid-crisis · flash back to how it began · catch up and move on - circular_structure Circular Structure — Beats: signature opening image · journey through events · same image, new meaning - loop_structure Loop Structure — Beats: the loop restarts · perception slowly changes · breaking or accepting the loop - nonlinear_structure Nonlinear Structure — Beats: intercut time periods · past memory explains the present · timelines converge - parallel_structure Parallel Structure — Beats: intercut two threads · thematic link · convergence at the decisive point - episodic_structure Episodic Structure — Beats: the immediate event · its resolution · on to the next event - montage_structure Montage Structure — Beats: unskilled beginning · effort and change over time · mastery

4.4 Section "Models"

  • za · Image model (⭐za1): za1⭐ 🍌 Nano Banana Pro "Highest quality, most consistent characters" · za2 🍌 Nano Banana 2.1 "Realistic, sharp, cinematic" · za3 🍌 Nano Banana 2 Lite "Fast, for heavy generation"
  • zb · Video model (⭐zb2) — chip shows "N credits/clip · max N s · note" (keep VIDEO_MODEL_CONFIGS, English notes): zb1 Omni 1.1 Flash 360p 7 · 10 s · "cheapest, for tests" · zb2⭐ Omni 1.1 Flash 720p 15 · 10 s · "sharp, recommended" · zb3 Veo 3.1 Lite 5 · 8 s · "fast and good value" · zb4 Veo 3.1 Fast 10 · 8 s · "fast queue" · zb5 Veo 3.1 Quality 100 · 8 s · "best cinema quality" · zb6 Veo 3.1 Lite free (slow queue) 0 · 8 s
  • Credit estimate card: "EP {n}: ≈ N credits · ≈ N s" and "Whole series: ≈ N credits"; after shots exist it counts only shots without clips, using per-shot vm / vt. Stills note: "3 still options per shot = 3 image generations".

4.5 Section "Format"

  • a · Story format (⭐a2): a2⭐ Micro-drama series — vertical episodes, each opens with a hook and ends on a cliffhanger · a1 Single short film — one complete story with a clear ending (template single, 1.2)
  • b · Episodes (series only, ⭐5): chips 2 3 4 5 6 7 8 9 10 12 15 20 · note "Each EP has {c} shots ≈ {c × vt} s; AI writes the whole arc first, you produce one EP at a time"
  • c · Shots per EP / film (⭐12): chips 5 6 8 10 12 14 16 18 20 24 30 · live info "12 shots × 6 s = 72 s · roles: 1 H · 2 S · 4 P · 2 T · 2 PO · 1 C" (from buildEpisodePlan, 1.2)
  • d · Aspect ratio (⭐9:16) — 9:16 Vertical (TikTok / Reels / Shorts)⭐ · 16:9 Landscape (YouTube / TV) · 1:1 Square (feed) · 4:5 Feed portrait (IG / FB) · 3:4 Portrait · 4:3 Classic · 21:9 Ultra-wide cinematic (tooltip "Video falls back to 16:9 or 9:16" for ratios the video model does not support). Used by every still, location single shot and clip (portraits stay 1:1).

4.6 Section "Cast, language & voice"

  • e · Character ethnicity (⭐e1): e1⭐ Thai · e2 Korean · e3 Japanese · e4 Chinese · e5 Southeast Asian · e6 Western · e7 Mixed Eurasian · e8 Indian · e9 Custom (text field "e.g. Scandinavian, Thai-European")
  • f · Output language (⭐f1): f1⭐ Thai · f2 English (switching sets g to g1 / g5)
  • g · Dialect / accent (filtered by f): Thai g1⭐ Central Thai · g2 Isan · g3 Northern (Kham Mueang) · g4 Southern · English g5⭐ American · g6 British · g7 Australian · g8 Indian English
  • h · Dialogue tone (⭐h1)
  • h1⭐ Normal — natural everyday conversational tone
  • h2 Rough street talk — raw street talk, harsh informal pronouns, intense delivery (keep the register-override rule: overrides polite particles)
  • h3 Polite formal — polite formal speech with polite particles (sweet-poison politeness for schemers)
  • h4 Royal / period speech — classical court and period speech (auto with k3 / m6)
  • h5 Cute & playful — cute playful teasing tone
  • h6 Witty sarcastic — quick witty sarcastic comebacks
  • h7 Poetic & lyrical — lyrical, rhythmic, folk-tale phrasing
  • h8 Teen slang — current teen slang, short bursts
  • i · Audio mode (⭐i1) — sets the default line type sf of every shot: i1⭐ Characters talk — Dialogue in every shot, Silent allowed for reactions · i2 Narrator — Narrator in every shot, characters act silently · i3 Mixed — Narrator on the first and last shot of the film or EP, Dialogue in between. Inner voice can be used in any mode.
  • j · Narrator voice (when any shot uses Narrator, ⭐j1; filter chips Male · Female) — promptEn = the narrator's Voice lock text (editable, 5.1): j1⭐ Deep documentary (M) — Thai male narrator, about 45, deep warm baritone, Central Thai accent, slow steady documentary cadence, close-mic studio sound · j2 Young bright (M) — Thai male narrator, about 25, bright clear tenor, Central Thai accent, friendly lively pace · j3 Old storyteller (M) — elderly Thai male narrator, about 70, gentle low raspy voice, folk storytelling cadence · j4 Dark trailer (M) — Thai male narrator, about 40, deep gravelly intense movie-trailer voice, short dramatic pauses · j5 Soft warm (F) — Thai female narrator, about 40, soft warm mid-pitched voice, soothing pace · j6 Young bright (F) — Thai female narrator, about 25, bright cheerful energetic voice · j7 Grandma storyteller (F) — elderly Thai female narrator, about 70, gentle kind bedtime-story voice · j8 Low mystery (F) — Thai female narrator, about 35, low hushed mysterious voice, slow pace. For f2 "Thai" becomes nothing and "Central Thai accent" becomes the g accent.

4.7 Section "Visual style" — k (cards, ⭐k1)

Keep the source panel: filter chips All (48) · 🌸 Pastel pink & editorial (19) · 🎬 Cinematic & film · 🎨 Painting & line art · 🕹️ 3D & pop culture · ✏️ Custom + search field + card grid (label, 3-line prompt preview, pink highlight for pink styles) + badge "Current style: …". Keep every id and promptText from VIDEO_STYLES; only labels change (English, no brand names in labels). Two styles are new (k_photorealistic, k_custom, 12):

  • Pastel pink & editorial: k_pastel_pink_improved Pastel Pink · k_korean_soft_beauty Korean Soft Beauty · k_luxury_pink_editorial Luxury Pink Editorial · k_dreamy_romantic Dreamy Romantic · k_natural_iphone_snapshot Natural Phone Snapshot · k_korean_soft_blush_portrait Korean Soft Blush Portrait · k_korean_creamy_close_up Korean Creamy Close-Up · k_soft_emotional_portrait Soft Emotional Portrait · k_minimal_refined_portrait Minimal Refined Portrait · k_premium_pastel_beauty_ad Premium Pastel Beauty Ad · k_minimal_dusty_pink_ads Minimal Dusty Pink Ad · k_authentic_iphone_candid Authentic Phone Candid · k_handheld_everyday_realism Handheld Everyday Realism · k_japanese_airy_editorial Japanese Airy Editorial · k_sakura_lifestyle_film Sakura Lifestyle Film · k_korean_dewy_skincare Korean Dewy Skincare · k_hydrated_skin_ad_premium Hydrated Skin Ad · k_dusty_rose_luxury_fashion Dusty Rose Luxury Fashion · k_high_end_soft_glamour High-End Soft Glamour
  • Cinematic & film: k1⭐ Realistic cinematic 35mm · k_photorealistic Photorealistic (new) · k2 Chinese costume drama · k3 Thai period (Ayutthaya) · k_cinematic_movie Hollywood Cinematic · k_doc_realism Documentary Realism
  • Painting & line art: k_surreal_dream Surreal Dream · k_oil_painting Oil Painting · k_minimal_flat Minimal Flat · k_ghibli_style Warm Hand-Painted Anime · k_manga_bw Manga Black & White · k_newspaper_strip Newspaper Strip · k_storybook_watercolor Storybook Watercolor · k_cutout_cartoon Paper Cutout Cartoon · k_humorous_cartoon Humorous Cartoon · k_stickman_brown_paper Stickman on Brown Paper · k_modern_minimal_stickman Modern Minimal Stickman · k_chalkboard_sketch Chalkboard Sketch · k_watercolor_wash Watercolor Wash · k_technical_blueprint Technical Blueprint · k_paper_craft_stop_motion Paper-Craft Stop-Motion
  • 3D & pop culture: k_clay_toy Clay Toy Stop-Motion · k_low_poly_3d Low Poly 3D · k_minecraft_voxel Cinematic Voxel World · k_lego_brick Brick Toy Cinematic · k_voxel_diorama Voxel Diorama · k_pixar_3d Cute 3D Animation · k_neon_night Neon Night · k_vibrant_pop_art Vibrant Pop Art · k4 Fruit lakorn · k14 Cat-head drama
  • Custom: k_custom Custom style (new) — opens the field "Describe your style (English)" (placeholder "e.g. soft film photo, warm afternoon light, muted greens, grainy 35mm"); the text is stored in customStyle and replaces promptText in portrait, location, still, video and poster prompts (stylePrompt(), 12). Empty text → ✨ Start with AI, Generate all images, Draft EP {n} storyboard and every draw button are disabled with the tooltip "Describe your custom style first". ✨ Write from a photo (optional) uploads one image and asks the text model for a one-line style description (style only, no people or places). Portrait branches: k_custom uses the realistic branch.

Changing k after portraits or stills exist needs a second click within 6 s ("Existing characters and images won't match the new style; click again to confirm") and then marks them Ref changed.

4.8 Section "Story feel" (appended to plot, storyboard, still and video prompts)

m · Setting & era (single, ⭐m1) - m1⭐ Modern Bangkok — present-day Bangkok, Thailand: condos, BTS skytrain, street food, traffic, luxury hotels and offices - m2 Thai provincial town — present-day Thai provincial town, shophouses, markets, temples - m3 Isan village — rural Isan village in northeast Thailand, rice fields, wooden stilt houses - m4 Northern hills — misty northern Thailand hills, Lanna wooden houses, pine forest - m5 Southern seaside — southern Thailand seaside, longtail boats, limestone cliffs - m6 Ayutthaya era — Ayutthaya-era Siam, teak houses, palaces, temples (auto with k3) - m7 Retro 80s–90s Thailand — 1980s–1990s Thailand, old buses, CRT TVs, retro signage without readable text - m8 Near-future Thailand — near-future Bangkok, clean tech, neon accents - m9 Fantasy world — invented fantasy world inspired by Thai folklore - m10 Seoul / Tokyo city — present-day Seoul or Tokyo streets, cafes and apartments (for e2/e3 casts)

n · Mood (single, ⭐n2) - n1 Light & fun — light, playful mood, bright colors - n2⭐ Balanced — balanced drama with moments of humor - n3 Dark & tense — dark, tense mood, low-key lighting - n4 Heartfelt — warm, emotional, family and love - n5 Eerie folk — eerie Thai folk-tale atmosphere, mist and lanterns - n6 Absurd comedy — absurd over-the-top comedy - n7 Sweet romance — sweet romantic mood, soft glances, warm light - n8 Epic & grand — epic grand scale, sweeping emotion

o · Ending (single, ⭐o5 for series, o1 for single film; used by the final EP's END shot — every other EP ends on a cliff) - o1 Clear ending — clear satisfying resolution - o2 Twist ending — final twist that reframes the story - o3 Bittersweet — bittersweet ending - o4 Moral lesson — ending with a gentle moral, folk-tale style - o5⭐ Happy ending — happy ending, the protagonist fully vindicated - o6 Season cliffhanger — the final EP ends on a cliff for a next season (END becomes CLIFF) - o7 Open ending — open ending that invites the audience to imagine - o8 Full circle — final image mirrors EP1's opening image with new meaning

p · Pacing (single, ⭐p1 for series): p1⭐ Fast — fast pacing, short lines, quick changes · p2 Balanced — balanced pacing · p3 Slow burn — slow-burn, lingering emotional beats · p4 Montage-heavy — many quick wordless montage shots between key lines

q · Plot options to generate (⭐q2): q1 3 · q2⭐ 5 · q3 8 (series always 3)

r · Opening hook of EP1 (single, ⭐r2) — shot 1 must show conflict within 2 s (EP2+ open on the previous cliff, 6.3) - r1 Cold open in action — open in the middle of the action - r2⭐ Shocking first line — the first line is an insult, a shock or a confession - r3 Mystery question — open on an unanswered visual question - r4 Flash-forward — open on a glimpse of the climax (or the death, for rebirth), then go back - r5 Striking visual — open on one striking image of the conflict - r6 POV "you" — first-person point of view, the audience is the character - r7 Calm before the storm — quiet ordinary moment with one wrong detail - r8 Narrator teaser — narrator line that promises a story

t · Camera language (single, ⭐t5 for series) — fallback motion for every role (va1) - t1 Cinematic steady — smooth dolly and slider moves, stable framing - t2 Handheld documentary — subtle handheld documentary camera - t3 Phone vertical vlog — smartphone vertical vlog feel, close and casual - t4 Locked frames (comedy timing) — static locked frames, action plays inside the frame - t5⭐ Lakorn push-ins — dramatic push-ins on reactions, the micro-drama look - t6 Aerial + close-ups — aerial establishing shots then intimate close-ups - t7 Anime dynamic — dynamic anime-style angles and speed lines feel - t8 Long-take feel — slow continuous moves, few cuts

u · Content level (single, ⭐u2) - u1 Family-safe — family-safe, no violence or crude language (soft impact locked on) - u2⭐ Teen — tension, arguments and mild peril, no blood, no gore - u3 Mature tension — mature themes and strong tension, no explicit violence, no nudity - u4 Kids — gentle, friendly, nothing scary (soft impact locked on) - u5 Brand-safe ad — strict brand-safe content for advertising (soft impact locked on)

si · Soft impact (toggle, ⭐on) — 1.5.

w · Always avoid (multi, ⭐w1 w2 w3 w4) - w1⭐ No text in frames — no text, subtitles, watermarks or signs with readable text - w2⭐ No warped hands or faces — no distorted hands, extra fingers or warped faces - w3⭐ No celebrity likeness — original fictional faces, never a celebrity - w4⭐ No collage or split screen — single full-frame image, no panels, no split screen - w5 No unplaced logos — no brand logos except the placed product - w6 No gore or weapon close-ups — no gore, no weapon close-ups - w7 No modern objects in period — no phones, plastic or modern cars in period stories - w8 Nobody looks at camera — characters never look into the lens unless POV

mu · Music for editing (single, ⭐mu1) — never sent to the video model; used only for the Music cue sheet (9): mu1⭐ Tense strings — tense pulsing strings · mu2 Sad piano — slow sad piano · mu3 Romantic — warm romantic strings and guitar · mu4 Revenge beat — dark trap beat with stabs · mu5 Comedy pizzicato — bouncy comedy pizzicato · mu6 Horror drone — low horror drone and bells · mu7 Thai traditional — ranat and khim · mu8 No music — cue sheet lists silence only

4.9 Section "Plot & series arc"

  • Write your own: Title (optional) + Synopsis (placeholder "Type your synopsis… AI uses it to write the arc, design the cast and write the storyboards") + Write the arc from this synopsis (10.1b).
  • Or pick a genre — l (20 cards; icon, color, label, one-line description; click = generate plots for the trope tr in this genre): Horror / ghost — Thai ghosts, haunted spirits, dark legends · Comedy — gags, jokes, chaotic situations · Romance — love, sweetness, warmth · Rom-com — bickering couple, secret crush · Action — fights, stunts, chases · Thriller — suspense, mind games, sharp twists · Mystery — cases, clues, hidden truth · Drama — family conflict, tears · Sci-fi — future Bangkok, cyberpunk, high tech · Fantasy — Himmapan forest, spells, supernatural power · Adventure — jungle trek, treasure hunt · Crime — mafia, police, heist, betrayal · Supernatural — amulets, black magic, karma · Psychological — mind games, hallucinations · Feel-good — healing, smiles, hometown life · Family drama — inheritance war, family secrets · Period — Ayutthaya, Rattanakosin, love across lifetimes · Survival — stranded on an island or in a jungle · Superhero — everyday hero with powers · War — battlefields, heroes, sacrifice (keep ids, icons, colors).
  • Plot list: cards with title + synopsis + "Arc: N EPs" · Re-roll plots · Use this plot (sets title, synopsis, writes the arc and the secrets ledger, opens the arc panel).
  • Arc panel (series): one row per EP — EP n · arc beat chip (1.3) · outline (editable, in f) · cl · Cliff type chips (AI pre-selects, 4.10) · cliff line (editable, in f) · status (🔒 / storyboard / stills N/12 / clips N/12) · ✨ Rewrite arc (whole arc, keeps EPs already produced).
  • Secrets ledger panel (series, MUST): table "Secret (English) · Known by (cast chips) · Revealed in EP (chips) · Revealed to (cast chips) · Status (hidden / revealed in EP n)" + + Add secret · ✨ Fill from plot (10.1b) · ✕ per row. Used by the storyboard prompt and the Retention check (6.2); updated after every storyboard draft (revealsSecrets).
  • Relationships panel (series): rows "A · relation · B · note" (e.g. "Mali · secretly the daughter of · hotel owner"), filled by 10.1b, editable.
  • Continue-series panel (keep from V4, for a series file from another project): "Previous episode (reference)" collapsible recap · "New episode direction" textarea · ✨ Suggest 3 directions (new character / old secret / twist; prompt 10.9) · Create storyboard from this direction · Exit continuation mode. Locked settings (style, ethnicity, language, aspect, voice locks) show 🔒 "Locked to match previous episode".
  • Banner "Settings changed after the arc was written" + Edit settings · Keep.

4.10 Cliff types — cl (per EP, single, AI pre-selects from 1.3)

  • cl1 Identity about to be revealed — someone is one second from learning who the protagonist really is
  • cl2 The wrong person walks in — the person who must not see this walks in and sees it
  • cl3 Threat — a clear threat to someone the protagonist loves
  • cl4 Ultimatum — "choose now": a deadline or a forced choice
  • cl5 Evidence appears — a photo, a document, a recording surfaces
  • cl6 Back from the dead — someone believed dead or gone appears
  • cl7 Half confession — a complete sentence that admits something but withholds the answer ("I know who your real mother is.")
  • Rule (MUST): the cliff line is always a complete sentence; the cut happens on the reaction, never mid-word.

5. Step 2 — Cast & Locations

Header "Cast & location design" · "N characters · N locations" · 🎲 Re-roll cast (two-click if portraits exist) · Add character · Add location · Generate all images.

  • ca · Cast size (⭐ca1): ca1⭐ Auto (3–8, scaled to shots and EPs) · ca2 2 · ca3 3 · ca4 5 · ca5 7 · ca6 9
  • cb · Cast look theme (⭐cb2 for series; fills the editable "Look theme prompt" field customCastTheme)
  • cb1 Natural cinematic — high-end cinematic character photo, natural skin texture, calm expression, 50mm, soft diffused light, muted earthy grade
  • cb2⭐ Lakorn glam — glamorous Thai TV drama look, flattering beauty light, polished styling
  • cb3 Gritty real — gritty realistic look, harsh daylight, imperfect skin, everyday clothes
  • cb4 Soft romance — soft romantic look, pastel tones, gentle backlight
  • cb5 Period elegance — elegant period styling, warm candle and lantern light
  • cb6 Street style — modern Thai street fashion, vivid urban colors
  • cb7 K-beauty soft — soft Korean beauty look, milky skin, minimal makeup, airy light (pairs with pastel styles)
  • cb8 Fantasy costume — ornate fantasy costumes, jewelry and layered fabrics
  • cc · Portrait type (⭐cc1): cc1⭐ Reference sheet — close-up face left + full-body turnaround right on white (portrait only; never copied into stills, 7.0) · cc2 Single full-body — single full-body portrait (used automatically for k3) · cc3 Head and shoulders — clean head-and-shoulders portrait on a neutral background
  • cd · Locations (⭐cd1): cd1⭐ Auto (2–5, across all EPs) · cd2 None · cd3 2 · cd4 4 · cd5 6
  • cf · Wardrobe continuity (⭐cf2 for series): cf1 Same outfit all film — every character wears the portrait outfit in every shot · cf2⭐ Changes by day — outfit changes only on a time jump (sc4) or a new EP set on another day · cf3 Per-scene costume — the storyboard may describe a new outfit per scene
  • Character card: portrait (lightbox) · role badge (Protagonist · Antagonist · Ally · Love interest · Family · Extra) · editable name, role, gender, age, description · cv · Voice chips (per character): cv1 Warm low · cv2 Bright young · cv3 Soft gentle · cv4 Husky · cv5 Deep authoritative · cv6 Playful high · cv7 Elderly · cv8 Child — AI pre-selects from role, age and gender · Voice lock box (5.1) · buttons Create image / Redraw · Upload photo · Analyze (fills description from an uploaded photo, 10.8) · ✕ Remove (two-click) · note "Use only images you own or are licensed to use".
  • Location card: grid image (2×2 angles: wide exterior, entrance, interior, reverse) + "Image actually used" (single shot in d aspect) · editable name, description · Create image / Redraw (grid first, then the single shot from the grid) · Upload · Match the grid (redraw the single shot from the grid) · ✕ Remove.
  • Product placement (keep; toggle "Attach product to film"): up to 5 images (first = "Main") · Product name (placeholder "e.g. SPF50 sunscreen") · Short description (placeholder "color, size, highlights") · licence note.
  • ce · Product frequency (⭐ce2): ce1 Subtle (2–3 shots per EP) · ce2⭐ Normal (3–5 shots per EP) · ce3 Hero (5–7 shots, close-up hero shot included)
  • Image-limit banner (keep): "Image generation limit reached. Switch the image model to continue." + Use Nano Banana 2.1 · Use Nano Banana 2 Lite · Close; pool setting fe (chips on the pool panel 7.2, ⭐fe1): fe1⭐ Pause and ask · fe2 Auto-switch to Nano Banana 2.1 · fe3 Auto-switch to Nano Banana 2 Lite.
  • Footer: Back · Next: Draft EP1 storyboard (enabled when every character has a portrait and a voice lock; tooltip lists missing ones).
  • Portrait prompt keeps the five style branches: cat-head (realistic feline face, breed per character), Chinese drama (wuxia names, silk robes), Ayutthaya (period names and ranks, sabai, chong kraben, full body), fruit lakorn (fruit-styled hair and accessories), realistic — plus ethnicity e, setting m, look theme cb (except k3), and "original fictional face, never a celebrity".

5.1 Voice lock (MUST)

  • Every character and the narrator has one Voice lock: a fixed English sentence sent word for word in every clip where that voice speaks (Dialogue or Inner voice for a character; Narrator for j).
  • Format (written by the AI from gender, age, role, cv, g, h, 10.2b): {Name}: Thai woman, about 25, medium-high pitch, Central Thai accent, soft clear voice, polite with quick soft sentence endings.
  • Box on the card: read-only text + ✎ Edit (pins it 📌) + ✨ Write voice lock (rewrites from the chips; disabled while pinned unless confirmed) + ↺ From voice chip.
  • Changing it marks every rendered clip where that voice speaks Voice changed · ↻ Re-render (2.2).
  • Saved in the project and in the series file; a loaded series keeps voice locks 🔒 (unlock with two-click "Unlock voices — new clips may sound different").
  • Video prompts carry only the speaker's Voice lock (10.11); nobody else in the shot speaks.

6. Step 3 — Storyboard

Header "Storyboard — EP {n} ({c} shots)" · EP tabs (3.1) · Draft EP {n} storyboard / Re-draft (two-click) · Generate remaining scenes (N) · role strip (12 colored blocks: H red · S grey · P orange · T purple · PO gold · C pink · E green; click = jump) · arc beat of this EP + cliff type cl · warnings "Characters with no shot yet: …" · "Shots without a line: …" · "Product shots: n / target" · Retention panel (6.2).

  • Shot card: "Shot #N of M" + role chip + beat label · "Visual action" textarea · cast chips (toggle) · location chips (No location + each location) · Product toggle (when placement is on) · speaker chips (one speaker; dimmed for Silent) · line field ("Dialogue (speaker)", "Inner voice (speaker)", "Narration" or empty for Silent) with syllable counter 18 / 20 (red above the limit + Shorter line) · "Motion" (role template filled) · "SFX" · ↺ Reset to role · groups:
  • se · Role (single, from the template; 1.1): HOOK · SETUP · PRESSURE · TURN · PAYOFF · CLIFF · END. Changing it re-applies role defaults (1.4).
  • sf · Line type (single, default from i and the role): Dialogue · Inner voice · Narrator · Silent
  • sa · Camera angle (keep 11, default from role): AI choice · Wide · Medium · Close-up · Extreme close-up · Over the shoulder · Two-shot · Low angle · High angle · POV · Insert detail
  • sb · Quick fix (buttons → text model, 10.6): More dramatic · Funnier · Shorter lines · Softer wording (policy-safe) · ✨ Soften impact · Add product naturally · More emotional pause · Stronger hook · Stronger cliff · Rewrite scene · + Custom. Every rewrite keeps the role, the speaker and the syllable limit.
  • sc · Shot relation (single, AI pre-fills continuity; drives the still references 7.0b)
    • sc1 Continue — same moment and place as the previous shot; the still is an edit of the previous locked still
    • sc2 New angle, same place — same place and moment, new camera angle; previous still = wardrobe, light and props reference
    • sc3 New location — another place; location image first, previous still only for wardrobe and look
    • sc4 Time jump — later time or day; wardrobe may change (cf2), light changes
    • sc5 Flashback / dream — memory or dream, softer desaturated grade
    • sc6 From last EP's cliff — shot 1 of EP2+ only: the same moment as EP {n−1}'s last shot, seen from a new angle (6.3)
  • sd · Emotion (single, default from role): Calm · Tension · Shock · Joy · Sadness · Romance · Funny · Fear · Triumph — sent to still (expressions) and video (acting)
  • Footer: Back · Retry generation (when a batch failed) · Next: Pick stills.
  • Generation logic (keep from V4): shots written in batches of 10 (each batch gets the previous shots' summary), cast coverage (every main character appears in the series; every EP has the protagonist and the antagonist), names limited to the cast list, series context (arc + this EP's outline + previous EP's last 3 shots + secrets ledger), 4 retries with backoff, empty-line repair (2 rounds; Silent shots exempt), cast/location id repair (unknown ids mapped to the closest or dropped), 3600-char trimming of descriptions. Insert the Chinese-drama and Thai-period requirement blocks (exist in promptService.ts). The role plan (buildEpisodePlan) is sent as a fixed list; the AI writes one shot per planned role.

6.1 Syllable counting (MUST)

Limit by shot duration: 12 (4 s) · 20 (6 s) · 28 (8 s) · 36 (10 s) — about 4 syllables per second inside the speech window 0.3 s → duration − 0.7 s. HOOK lines: max 70 % of the limit. The counter shows max(shot.syllables from the AI, estimateSyllables(line)) (12). Replaces the V4 word counter ("ideal 26–32 words per 10 s").

6.2 Retention check (MUST — after every draft and every storyboard edit of the EP)

# Rule How it is checked
1 Shot 1 shows conflict within 2 s; no greeting, no recap narration AI flag conflictBy2s + HOOK_BANNED patterns (12) on shot 1's line
2 EP2+: shot 1 continues EP n−1's cliff relation is sc6 + AI flag continuesCliff
3 The EP has ≥ 1 PAYOFF and ends with CLIFF (series, not final) or END (final EP / single film) code: role list
4 No more than 2 Silent shots in a row code
5 One speaker per shot; a Dialogue / Inner voice speaker is in the shot's cast code: speakerId ∈ castIds (Narrator exempt)
6 Every line within its syllable limit code: 6.1
7 Matches the secrets ledger: nobody acts on a secret they do not know yet; a secret is never revealed twice code: usesSecrets / revealsSecrets per shot vs ledger state at that shot
8 No direct impact when soft impact is on code: IMPACT_WORDS on visualDescription + AI flag directImpact
  • After a draft, failing rules go back to the AI once automatically with the problem list (10.5b). Still failing → the panel lists each problem with ✨ Fix (one shot) and ✨ Fix all; work is never blocked.
  • Panel when all pass: "Retention check ✓ 8 / 8".
  • Extension scenes (8.1) are not checked.

6.3 Opening of EP2+ (MUST)

  • Shot 1 of EP n (n ≥ 2) has role HOOK, relation sc6, and shows the same moment as EP n−1's last shot from a new angle, continuing the cliff line (the answer, the reaction, or the next blow).
  • No recap narration, no "previously" line, no greeting. The "Previously on…" text goes into the post caption (10.13).
  • The last locked still of EP n−1 is reference [1] of shot 1 (7.0b).

7. Step 4 — Stills (Sequential pick flow)

Header "Pick a still for every shot — EP {n}" · counter "Locked N / M" · guide line "Next: pick a still for shot {n}" · role strip · pool panel (7.2). When all are locked: toast "EP {n}: all {M} stills locked — EP {n+1} storyboard is open" (series).

7.0 Keyframe image rules (MUST)

  1. One still = one single photograph / frame filling the image in the exact d aspect. Never a collage, grid, split screen, panels, comic page, character sheet, turnaround, border, inset or before/after.
  2. One moment only: the still shows the start of the shot's action. The words storyboard, panel, sheet, turnaround, grid, sequence, collage, comic page, multiple views are stripped from still prompts (FORBIDDEN_IMAGE_WORDS).
  3. Every still prompt ends with SINGLE_FRAME_SUFFIX (12). Portrait sheets (cc1) and location grids are references only; the prompt says "use reference images for identity and place only, do not copy their layout".
  4. Characters keep face, hair and wardrobe of their portrait (cf rule); every still names who is in frame, where, the role hint (1.1) and the expression from sd.
  5. Speaker framing: Dialogue → the speaker's face and mouth clearly visible · Inner voice / Narrator / Silent → nobody is mid-speech (mouths closed or neutral).
  6. Soft impact (1.5): the still shows the moment before or after contact.
  7. Image check (MUST) — a check job runs on every new still (prompt 10.7). A failing option is marked, redrawn once automatically and can never be locked: ✗ Collage · ✗ Wrong character · ✗ Wrong place · ✗ Not continuous (sc1 / sc6) · ✗ Text in image · ✗ Warped hands/face · ✗ Speaker not visible (Dialogue shots) · ✗ Impact shown (soft impact on).

7.0b Continuity by image reference (MUST)

Relation Reference order (max 10) Prompt
sc1 Continue [1] previous locked still (edit target) · [2] cast portraits in frame · [3] product EDIT_PROMPT(change, castLine) (12): keep camera, light, place, wardrobe; change only the action / expression / framing listed
sc2 New angle [1] location single · [2] previous locked still (labelled "wardrobe, light and props reference") · [3] cast · [4] product still JSON prompt (10.7a) + "same place and moment as reference 2, new angle {sa}"
sc3 New location [1] location single · [2] cast · [3] product · [4] previous still (wardrobe only, unless sc4) still JSON prompt
sc4 Time jump [1] location · [2] cast · [3] product still JSON prompt + new light; wardrobe per cf
sc5 Flashback [1] location · [2] cast still JSON prompt + flashback grade
sc6 From last EP's cliff [1] last locked still of EP n−1 ("same moment reference") · [2] location single · [3] cast · [4] product still JSON prompt + "the same moment as reference 1, seen from a new angle {sa}; same wardrobe, light and place"

The 3 options of a shot = 3 seeds of the same prompt and references. Overflow references are saved in cutCharIds and shown "Not used: …".

7.1 Sequential pick flow (MUST — no full automation)

There is no ⚡ Full automation (remove the button, ⏹ Stop automation, the "Full Automation Settings" panel and StoryState.automation). The user picks every still, shot by shot, EP by EP. 1. Gate: only shot 1 can start (EP2+: also needs every still of EP n−1 locked). Shot n's Generate options is locked (🔒, "Pick a still in shot {n−1} first"); its slots show grey "Waiting for shot {n−1} pick" (never "Generating"). 2. Pick = lock + auto-continue: clicking an option locks it (green border ✓ Locked, writes stillMediaId / stillBase64) and starts shot n+1's 3 options. Toggle Auto-generate next shot after pick (⭐ on). 3. Re-picking in a shot that already has a next shot never overwrites: the next card gets Ref changed + ↻ Update from previous. 4. When all are locked: toast (above) + Next: Create video highlighted. 5. ✨ Suggest best marks one option Suggested (reason as tooltip); it never locks. 6. Unstuck shortcut: if shot n−1 has finished options but no lock, card n shows "Shot {n−1} has stills but no pick yet" + ✓ Lock shot {n−1} #{k} & generate shot {n} (k = Suggested, else first valid). 7. Toggle Use first finished still if no pick (auto-fallback, ⭐ off): shot n starts from shot n−1's first valid option; card shows Ref: shot {n−1} #{k} (auto); a later different pick → Ref changed. 8. Clips are made only in the Video step.

Shot card: 3 option slots (in d aspect, object-fit: contain) + locked still · badges "#N", role chip, relation chip, line type, "Uploaded", "Product" · Restore previous still · groups: - ia · Ingredients (multi, max 10): Location · Previous shot (on by default, forced on for sc1; for sc6 = EP n−1's last still) · each cast member in the shot · Product - ib · Refine (buttons that append to the prompt and regenerate the 3 options; + free text): Closer · Wider · Brighter · Moodier · Fix hands/faces · Match previous shot · Fix wardrobe · Remove any text · Show the speaker's face · Before the impact - ic · Light override (⭐ic1): ic1⭐ Follow style · ic2 Golden hour · ic3 Soft window light · ic4 Night practical lamps · ic5 Rainy moody · ic6 Neon night · ic7 Harsh noon · ic8 Candle / lantern - id · Options per shot (global chip on the header, ⭐id1): id1⭐ 3 options · id2 2 options · id3 1 option (cheapest; still needs a click to lock) - Edit prompt (JSON editor, counter "/4000", gold at 3200, red above 3900) · Upload manually (Flow library or device ≤ 10 MB, locks directly) · ⟲ Reset shot. - Footer: Back · Next: Create video (enabled when ≥ 1 still is locked; warning "N shots have no still").

7.2 Generation pool (MUST — replaces poolScheduler.ts behaviour, keeps its class)

  • MAX_CONCURRENT = 4 requests app-wide (text, portraits, locations, stills, checks, videos, uploads, captions, posters). Every button adds jobs to this one queue; nothing bypasses it (the source plot, cast, storyboard and quick-fix calls currently bypass it — route them through it).
  • Start gap: two requests never start at the same moment; each starts ≥ START_GAP_MS after the previous start. Pool-panel chips Gap 2 s⭐ · 3 s · 4 s (4 s if the API rate-limits). Do not wait for the previous request to finish: 3 options = send #1 → +gap #2 → +gap #3 while #1 still runs.
  • Live countdown: countdownTimer (250 ms) while jobs wait; nextInSeconds = ceil((lastStart + START_GAP_MS − now) / 1000); waiting slots show Starting in {nextInSeconds + position × gap} s…, or Waiting for a free slot, or Waiting for shot {n−1} pick; buttons Queued · starts in N s; running slots Generating… 0:12 / 0:45. Header: Running 2/4 · Next request in 3 s · Queued 1 · Done 9 · Failed 0 + expandable list of running jobs ("EP2 · Shot 4 · still #2", "EP1 · Shot 2 · clip").
  • Ready rules (read live state through getState(), never a closure copy): portrait / location grid = always · location single = its grid done · storyboard batch(k) = batch(k−1) done · still(ep, n) = shot n−1 locked (shot 1: storyboard + portraits of its cast done, and for EP2+ every still of EP n−1 locked; auto-fallback: one valid option) · check = its still done · video(ep, n) = shot n locked · upload = always. Priority: text > upload > portrait/location > lowest EP, lowest shot; same shot: check → still → video.
  • Timeouts: TIMEOUT_MS = { text: 60000, image: 45000, check: 30000, upload: 30000, video: 180000 }. Past the limit: slot frees at once, ⏱ Timed out, re-queued once. Clip cards also show the timer "m:ss / 3:00" (gold > 90 s "Longer than usual", red > 150 s "May have been rejected" + ✕ Cancel).
  • Slot controls (always clickable on Queued / Starting / Generating): ✕ Cancel (frees the slot; late results ignored by job token) · ↻ Retry · ⚡ Force run (no gap, no priority; uses the next free slot or cancels the oldest job past half its timeout).
  • ⟲ Reset shot · 🧹 Clear stuck (cancel jobs past timeout and queued jobs whose dependency can no longer be met) · ⏹ Stop all (no new starts; running jobs finish and are kept).
  • Keep the source rules: credit / quota error → pause the whole pool "Not enough Flow credits. Generation paused." · circuit breaker: 5 consecutive failures → pause "Many failures in a row; queue paused to save credits" (network errors do not count) · duplicate job ids ignored · rate limit waits (stills 30 s, video 45 s, max 3) · media-not-ready retry (3 s).
const MAX_CONCURRENT = 4; let START_GAP_MS = 2000; let lastStart = 0; let timer: number | undefined;
const TIMEOUT_MS = { text: 60000, image: 45000, check: 30000, upload: 30000, video: 180000 };
type Job = PoolJob & { kind: 'text'|'portrait'|'location'|'still'|'check'|'video'|'upload'; ep?: number; token?: string; force?: boolean };
function pump() {
  if (paused || timer || running.size >= MAX_CONCURRENT) return;
  const forced = queue.find(j => j.force && j.ready()); if (forced) { forced.force = false; start(forced); return pump(); }
  const job = queue.filter(j => j.ready()).sort(byPriority)[0]; if (!job) return;
  const wait = lastStart + START_GAP_MS - Date.now();
  if (wait > 0) { timer = setTimeout(() => { timer = undefined; pump(); }, wait); return; }
  start(job); pump();                                    // schedules the next start (+gap), does not wait for the result
}
function start(job: Job) {
  lastStart = Date.now(); queue.splice(queue.indexOf(job), 1); running.add(job.id);
  const token = (job.token = crypto.randomUUID());
  const limit = TIMEOUT_MS[job.kind === 'portrait' || job.kind === 'location' || job.kind === 'still' ? 'image' : job.kind];
  const timeout = new Promise<never>((_, rej) => setTimeout(() => rej('timeout'), limit));
  Promise.race([job.run(), timeout])
    .then(() => { if (job.token === token) { consecutiveFailures = 0; done++; } })
    .catch(err => { if (job.token !== token) return; handleJobError(job, err); })   // credit pause · breaker · re-queue once
    .finally(() => { if (job.token === token) { running.delete(job.id); syncCountdown(); pump(); } });
}
function cancelJob(job: Job) { job.token = undefined; running.delete(job.id); const i = queue.indexOf(job); if (i >= 0) queue.splice(i, 1); markCancelled(job); syncCountdown(); pump(); }
function retryJob(job: Job) { cancelJob(job); job.tries = 0; queue.unshift(job); pump(); }
function forceRun(job: Job) { if (!job.ready()) return; if (running.size >= MAX_CONCURRENT) { const old = oldestPastHalfTimeout(); if (old) cancelJob(old); else { job.force = true; return; } } start(job); }
const nextInSeconds = () => Math.max(0, Math.ceil((lastStart + START_GAP_MS - Date.now()) / 1000));
const startsInSeconds = (position: number) => nextInSeconds() + position * (START_GAP_MS / 1000);
function syncCountdown() { /* setInterval(renderCountdown, 250) while jobs wait; clearInterval when empty or stopped */ }
function onPick(ep: number, n: number, option: 1|2|3) {
  lock(ep, n, option);
  const shots = getState().episodes[ep - 1].shots;
  if (n < shots.length) { if (hasOptions(ep, n + 1)) markRefChanged(ep, n + 1); else if (autoNext) enqueueStillOptions(ep, n + 1); }
  else { toast(`EP ${ep}: all ${shots.length} stills locked`); openEpisode(ep + 1); if (hasOptions(ep + 1, 1)) markRefChanged(ep + 1, 1); }
}

8. Step 5 — Video

Header "Production stage — EP {n}" · status "Rendering…" / "Queue paused" · "Main video model: … (N credits)" · Render all clips of EP {n} (only shots with a locked still and no clip) · ⏹ Stop all · progress "N / M clips" · ▶ Play EP in order (plays every clip of the EP back to back — voice and continuity check).

8.0 Video presentation options (MUST — on every video card)

Every video card shows these chips directly under the start frame and above "Motion & line", always visible (never in a menu). A row "Apply to all shots" + ↺ Default sits above the first card (cards changed by hand show 📌). Defaults come from the shot's role (1.1), then t for va1. Every chip change rewrites the clip prompt at once (if edited by hand: "Rewrite the prompt from the new options?" Rewrite · Keep my text). A rendered clip with changed options shows Options changed · ↻ Re-render. - va · Camera move: va1 From camera language t · va2 Slow push-in · va3 Pull-out reveal · va4 Gentle handheld · va5 Slow pan · va6 Orbit around the subject · va7 Locked tripod · va8 Tracking alongside · va9 Fast push-in · va10 Rack focus · va11 Slow dolly - vb · Acting energy: vb1 Natural · vb2 Subtle and still · vb3 Heightened lakorn · vb4 Comic big reactions · vb5 Intense and tense · vb6 Tender and soft - vc · Ambience (multi, mixed with the shot's SFX): vc1⭐ Ambient room tone · vc2 City traffic · vc3 Rain · vc4 Night insects · vc5 Wind and leaves · vc6 Market crowd · vc7 Temple bells · vc8 Soft footsteps · vc9 Silence before the line (no music option: clips never carry music, 0.6) - vd · Shot ending: vd1 Natural cut · vd2 Hold on the face 1 s · vd3 Look toward the next shot · vd4 Freeze-like hold · vd5 Fade-out feel · vd6 Match-cut pose for the next shot · vd7 Hard cut on the peak · vd8 Final hold 1.5 s

  • Shot card: start frame = locked still (d aspect, contain) · lazy player · timer · badges (model, duration, relation, role, line type) · speaker + voice lock preview (first 60 characters) · error box with ↻ Retry · Make safe prompt (10.10, then auto-retry once) · Revert to original prompt.
  • vi · Ingredients (multi; limit Omni 7 / Veo 3, counter "n/limit"): Start image (always) · cast (speaker first) · Location · Product.
  • vm · Video model (per shot): Use main setting⭐ · Omni 360p (7) · Omni 720p (15) · Veo Lite free (0) · Veo Lite (5) · Veo Fast (10) · Veo Quality (100).
  • vt · Duration (per shot, chips 4 s · 6 s · 8 s · 10 s, dimmed when not allowed; ⭐6 s where the model allows it): Omni 4 / 6⭐ / 8 / 10 · Veo Quality 4 / 6⭐ / 8 · other Veo 8 only. Changing it updates the syllable limit (6.1).
  • Batch model rule (chip row next to Render all clips): Use per-shot setting⭐ · Force Veo 3.1 Lite · Force free slow queue.
  • Row: Up · Down · Delete (two-click) · Insert scene before / Insert scene after (asks the role first; AI writes one shot between, 10.11) · Upload clip (≤ 100 MB) · Regenerate (two-click) · Edit prompt · Edit line (syllable counter).
  • ▶ Play / ⏹ Stop toggle on every card with a clip: plays inline, loops; Stop rewinds; one card at a time; no clip → dimmed ("No video yet").
  • Error: "Video generation failed. Check the settings and try again."
  • Footer: Back · Go to Cinema Room (enabled with ≥ 1 clip; warning "N shots have no clip").

8.1 Extension scenes — + Add scene (MUST — append only, endless)

  • Wide dashed + Add scene inside the last video card of the current EP only; every new card has its own. Append only; only the newest extension has ✕ Remove scene. Numbered N+1…, role from xa, included in the player, merge and downloads of that EP. An extension after a CLIFF shot asks "This EP ends on a cliff. Add the scene after it anyway?" Add · Cancel.
  • Start frame (locked): ⭐ last frame of the previous rendered clip (seek MP4 to duration − 0.04 s, draw to canvas, PNG, upload through the pool; <video crossOrigin="anonymous">) → fallback: the previous shot's locked still → else "Render clip {n−1} first, or upload its last frame" + Render clip {n−1} · Upload frame. Label Start: last frame of clip {n−1}; re-rendered clip n−1 → Ref changed + ↻ Use new last frame.
  • Role of an extension from xa: xa1 → SETUP · xa2 → PAYOFF · xa3 / xa6 → END · xa4 → CLIFF · xa5 → SETUP · xa7 → SETUP · xa8 → SETUP. Voice lock, one speaker, syllable limit, soft impact and NO MUSIC apply as in 8.0.

xa · Scene type (⭐xa1): xa1⭐ Continue the action · xa2 Reaction shot · xa3 Epilogue moment · xa4 After-credits tease · xa5 Product hero shot (placement on) · xa6 Narrator outro · xa7 Establishing view of the place · xa8 Custom xb · Camera (⭐from role): xb1 Slow push-in · xb2 Pull-out reveal · xb3 Slow pan · xb4 Orbit · xb5 Rise up (crane) · xb6 Handheld follow · xb7 Locked · xb8 Tracking walk xc · Emotion (⭐xc1): xc1⭐ Same as previous shot · xc2 Relief · xc3 Joy · xc4 Sad · xc5 Shock · xc6 Romance · xc7 Funny · xc8 Triumph xd · What happens (multi): xd1 Character smiles · xd2 Turns away · xd3 Says one last line · xd4 Walks out of frame · xd5 Hug · xd6 Looks at the product · xd7 Light changes to evening · xd8 Rain starts xe · Duration (⭐6 s where allowed): 4 s · 6 s · 8 s · 10 s (same model limits as vt) xf · End frame (⭐xf1): xf1⭐ None (animate from the start frame) · xf2 AI keyframe (3 edits of the start frame, pick 1; the clip runs start → end)

  • Fields "What happens next" + speaker chips + line type sf + "Line (spoken in {f})" with syllable counter · ✨ AI suggest (3 ideas, 10.12; click fills xa–xf + text) · ✨ Write motion prompt · Generate clip · ↻ Re-render · ▶ Play / ⏹ Stop · Download.
  • Rule: characters, wardrobe and place stay exactly as in the start frame; areas a camera move reveals continue the same location (location notes).

9. Step 6 — Cinema Room

  • Header "Cinema Room — EP {n} / {N}" (+ "Series finale" on the last EP) · EP tabs · 💾 Save series → {title}-series.json (series bible, 11) · Open EP n+1 (switches to EP n+1's Storyboard when its gate is open) · 🎬 Download EP {n} · Download all files (one by one).
  • Player (keep): sequential playback of the EP's clips in order, skip missing, play/pause, prev/next, shot dots (colored by role), seek, volume, fullscreen, caption line (line of the current shot, labelled "Inner voice" / "Narrator" when not Dialogue), aspect from d (contain).
  • pa · Audio source (⭐pa1): pa1⭐ Clip audio · pa2 Recorded voiceover (only when recordings exist).
  • Record voiceover (keep, collapsible): per shot Record (8 s mic, countdown) · Play · Delete.
  • Download EP (FFmpeg.wasm, keep the worker service): normalize every clip first (scale + pad to the d size, 30 fps, H.264 yuv420p, AAC 48 kHz; add a silent track to clips without audio), then concat demuxer; progress "Merging 3/12… 42 %" from onProgress; Cancel merge; warn above 1500 MB; file {title}_EP{nn}.mp4 (single film: {title}_FullMovie.mp4). pb · Merge audio (⭐pb1): pb1⭐ Clip audio · pb2 Mix in recorded voiceovers.
  • Music cue sheet (MUST, per EP): read-only list from musicCues (10.5) in the mood mu + Copy music cues. Cue rules: duck under every line · sting on the first frame of each TURN · drop to silence 0.5 s before the main PAYOFF line, then swell · heartbeat build into the CLIFF and a hard stop on the cut · resolve on END. mu8 → "No music".
  • Download all files (one by one), 700 ms apart: {title}_EP{nn}_00_storyboard.txt (English headers, lines in f, role per shot), {title}_EP{nn}_dialogue_sheet.txt (per shot: speaker, line type, voice lock, line, speech window, syllables — for re-recording a failed voice), {title}_EP{nn}_music_cues.txt, {title}_EP{nn}_NN_clip.mp4, {title}_EP{nn}_NN_still.png, voiceovers {title}_EP{nn}_NN_voice.webm.
  • Ready to post panel (keep): ✨ Write caption (title ≤ 8 words, caption with hook + emoji + 4–6 hashtags + "EP n/N", and for EP2+ a one-line "Previously:" recap; mood; in f, 10.13) · title and caption fields with Copy · cover canvas (d size, mood fonts and colors, title ≤ 3 lines, "EP n" badge) · thumbnail picker (any locked still; ⭐ the CLIFF still) · 🎬 Make poster (AI) (Nano Banana Pro first, up to 5 cast refs, title text in f in the lower third, "EP.n") · Download cover (PNG) → {title}_EP{nn}_cover.png.
  • Footer: 🗑️ Start new project (two-click).

10. AI processing

Keep every source prompt and JSON schema; system instructions English; JSON keys English. Every prompt includes buildContextRules (setting, mood, ending, pacing, ethnicity, language, dialect, no text, trope or narrative structure) plus these lines:

FORMAT: {a} · EP {n} of {N} · arc beat: {arcBeat} · this EP ends with: {CLIFF of type cl | END with ending o}.
TROPE: {tr promptEn}. OPENING HOOK (EP1 shot 1): {r}. CAMERA LANGUAGE: {t}. CONTENT LEVEL: {u}. DIALOGUE TONE: {h}. ALWAYS AVOID: {w[]}.
SOFT IMPACT: {si ? 'never show contact; show the moment before or after (slap = raised hand, then the face turned aside)' : 'contact may be implied by a fast cut; no blood'}.
LANGUAGE: write all dialogue, inner voice, narration, names and titles in {f}; dialect {g}; never mix languages in one line.
CONTEXT: everyday details, food, vehicles and places match {m}; people are {e} unless the style requires otherwise (cat-head, fruit).

10.1 Plots

Keep generatePlots. Output [{ "id", "title", "synopsis" (3–4 sentences, follows the trope {tr} or the structure {ns}), "episodeOutlines"? }] in f. One-line idea (4.0): same schema, 1 plot, "expand this idea: {idea}".

10.1b Arc, secrets and relationships (text model, JSON only)

You are the head writer of a vertical micro-drama series for Thai audiences. Write the arc of "{title}" in {N} episodes.
Synopsis: {synopsis}. Trope: {tr promptEn}. Arc beats per EP (fixed): {arcPlan}. Ending of the final EP: {o}.
Each EP: a 2–3 sentence outline in {f}, the cliff type for EPs 1..N-1 from {cl labels}, and the exact cliff line in {f} (one complete sentence, never cut mid-word).
List the secrets that drive the series (English), who knows each at the start, and in which EP and to whom each is revealed. Every secret is revealed exactly once; the biggest one in the final EP.
List the key relationships (English).
Reply: {"episodes": [{"ep": 1, "arcBeat": "…", "outline": "…", "cliffType": "cl1", "cliffLine": "…"}],
        "secrets": [{"id": "s1", "text": "…", "knownBy": ["c1"], "revealEp": 3, "revealedTo": ["c2"]}],
        "relationships": [{"a": "c1", "relation": "…", "b": "c2", "note": "…"}]}

Character ids are filled after 10.2; until then the AI uses role names, mapped to ids when the cast exists.

10.2 Cast & locations

Keep autoDesignCastAndLocations; size from ca / cd. Output { "characters": [{ "id", "name", "role", "dramaRole": "protagonist|antagonist|ally|love_interest|family|extra", "description", "voice": "cv1…cv8", "gender", "age", "voiceLock" }], "locations": [{ "id", "name", "description" }] } — names in f, descriptions and voice locks English. Every trope needs at least one protagonist and one antagonist.

10.2b Voice lock (text model)

Write one fixed voice description for {name} ({gender}, about {age}, {role}, {dramaRole}). Voice chip: {cv}. Accent: {g}. Dialogue tone: {h}.
One English line, max 30 words, in this order: name, ethnicity and gender, age, pitch, accent, voice texture, speaking habit. No story details. It must be usable word for word in every clip.
Reply: {"voiceLock": "Mali: Thai woman, about 25, medium-high pitch, Central Thai accent, soft clear voice, polite with quick soft sentence endings"}

10.3 Portrait

Keep buildCharacterPortraitPrompt + the five style branches (5) + cc framing + cb / customCastTheme; 1:1.

10.4 Location

Grid (2×2 angles, empty set, no people) then single wide master shot in d aspect from the grid (Match the grid).

10.5 Storyboard (system instruction, keep buildStoryboardSystemPrompt and extend)

You are the showrunner and screenwriter of A9 STUDIO, writing a vertical micro-drama. Write shots {from}–{to} of {total} of EP {n}/{N} of "{title}".
Synopsis: {synopsis}. Arc beat of this EP: {arcBeat}. EP outline: {outline}. This EP ends with: {cliff type + cliff line | END + ending o}.
ROLE PLAN (fixed, one shot each, in order): {plan.map(s => `${s.n}. ${s.role} · line type ${s.lineType} · motion template: ${ROLE_MOTION[s.role]} · role SFX: ${s.sfx}`)}
Roles: HOOK = conflict already happening in the first 2 seconds · SETUP = who is who, who wants what · PRESSURE = the antagonist pushes, the audience gets angry · TURN = the game flips (identity, evidence, a call, a newcomer) · PAYOFF = the satisfying moment, the loser's face · CLIFF = one complete revealing sentence, cut on the reaction · END = calm final image.
EP1 shot 1 follows the opening hook {r}. EP2+ shot 1 continues EP {n-1}'s last shot (the same moment, a new angle): "{previous cliff line}" — {previous last 3 shots summary}. No greeting, no recap narration.
LINES: exactly one speaker per shot. Line types: "dialogue" (speaker on screen, lips move), "inner_voice" (the speaker's thought, lips closed), "narration" (off-screen narrator), "silent" (no line). Never more than 2 silent shots in a row. Max syllables: 12 for 4 s, 20 for 6 s, 28 for 8 s, 36 for 10 s; HOOK lines max 70 %. Count syllables carefully and return "syllables". Lines in {f} ({g}), tone {h}.
SECRETS LEDGER (state at the start of this EP): {ledger}. A character may only act on a secret they know. Reveal only the secrets planned for this EP, each once. Return per shot "usesSecrets" and "revealsSecrets" (ids).
SOFT IMPACT: {rule}. Never describe the moment of contact in visualDescription.
For each shot set continuity: "continue", "new_angle", "new_location", "time_jump", "flashback", or "from_last_cliff" (EP2+ shot 1 only).
Use only these characters and locations (ids). The protagonist and the antagonist appear in this EP. Product "{product}" appears naturally in {target} shots.
visualDescription in English, concrete (who, where, action, expression), no text overlays. motion = the role's motion template with the braces filled, English. sfx = concrete, English, from the role SFX; never describe music.
Also return "flags": {"conflictBy2s": true|false, "continuesCliff": true|false, "directImpact": [shot ids]} and "musicCues": [{"from": "0:00", "to": "0:12", "cue": "…"}] for the whole EP in the mood {mu}, following: duck under lines, sting at each TURN, silence 0.5 s before the main PAYOFF line then swell, heartbeat into the CLIFF and hard stop.
{Chinese drama / Thai period requirement block when k2 / k3}
Previous shots summary: {summary}. Return ONLY JSON.

Output: { "shots": [{ "id", "role", "beat", "visualDescription", "castIds", "locationId", "scriptType": "dialogue|inner_voice|narration|silent", "speakerId", "dialogueText", "syllables", "cameraAngle", "continuity", "emotion", "motion", "sfx", "hasProduct", "usesSecrets", "revealsSecrets" }], "flags": {…}, "musicCues": […] } (role → se, scriptType → sf, continuity → sc, emotion → sd).

Validation: shot count and roles equal the plan · one speaker per shot · Silent shots have an empty line, others non-empty · ids repaired (6) · then the Retention check (6.2).

10.5b Automatic fix

Fix only these problems in the EP {n} storyboard JSON below and return the full JSON in the same format. Keep every other shot unchanged; keep the role plan, speakers, line types and syllable limits.
Problems: {list, e.g. "shot 1: no conflict in the first 2 seconds", "shot 7: Mali uses secret s2 before she learns it", "shot 9: slap shown at contact", "shot 4: 27 syllables, limit 20"}
Storyboard: {json}

✨ Fix (one shot) sends the same prompt with one problem and one shot. ✨ Soften impact (quick fix) applies SOFT_IMPACT_MAP to one shot and may split it into two shots (before / after) when the user confirms "Split into 2 shots?".

10.6 Quick fix

Keep applyQuickFix; add role, scriptType, syllables, cameraAngle, continuity, emotion, motion, sfx to the returned JSON; all through the pool.

10.7 Still prompt and image check

a) Keep buildStillJsonPrompt and add: aspect_ratio: {d}, role_hint: {1.1}, continuity: {sc}, emotion: {sd}, speaker_framing: {7.0 rule 5}, soft_impact: {si rule}, light: {ic}, wardrobe_rule: {cf}, product_lock (when the shot has the product: exact shape, color and label of the product images), reference_use: "references are for identity, place and product only; do not copy sheet or grid layouts", then SINGLE_FRAME_SUFFIX. For sc1 the prompt is EDIT_PROMPT (12) instead. b) Image check (text model with images, JSON only):

You check still options for shot {n} of EP {ep} of a vertical micro-drama. Image P = the previous locked still (for "continue") or EP {ep-1}'s last locked still (for "from_last_cliff"). Images C1… = character portraits. Image L = the location. Images 1–3 = candidates.
Expected: {visualDescription}. Role: {role}. In frame: {cast names}. Speaker: {speaker or none} ({scriptType}). Place: {location}. Relation: {continuity}. Emotion: {emotion}. Style: {k label}. Soft impact: {on|off}.
For each candidate set false when:
singleFrame — collage, grid, split screen, panels, character sheet, comic page, border or inset;
characterMatch — a face, hair or outfit differs from its portrait (outfit only when the wardrobe rule keeps it);
placeMatch — clearly a different place than image L;
continuous — only for "continue" and "from_last_cliff": camera, light, place or wardrobe differ from image P (for "from_last_cliff" the angle may differ);
textFree — readable text, subtitles, watermark or unplaced logo;
anatomy — distorted hands, extra fingers or warped faces;
speakerVisible — only for "dialogue": the speaker's face and mouth are not clearly visible;
noImpact — only when soft impact is on: a hand, object or water touching a person at the moment of contact.
Rank the remaining candidates by: role clarity · emotion · continuity with image P · beauty.
Reply: {"pick": 1|2|3|0, "reason": "max 12 words", "singleFrame": [..3], "characterMatch": [..3], "placeMatch": [..3], "continuous": [..3], "textFree": [..3], "anatomy": [..3], "speakerVisible": [..3], "noImpact": [..3]}  (0 = none valid → redraw)

10.8 Analyze (uploaded portrait / location / still)

English, 2–3 sentences: who or what, age, face, hair, outfit, colors, light; no names of real people. Fills the description field.

10.9 Episode directions

✨ Suggest 3 directions → [{ "title", "direction" }] in f: one new character, one old secret, one twist — continuing the recap and the secrets ledger.

10.10 Safe prompt

Keep SAFE_PROMPT_INSTRUCTION (family-safe rewrite, keep JSON structure, ids and the original-language lines, soften only flagged words, ≤ 3200 chars) and add "apply SOFT_IMPACT_MAP to any physical clash". Applied automatically once after a policy rejection, then one retry; still rejected → needsEdit badge "Needs edit".

10.11 Video prompt (MUST — this key order, every clip)

Keep buildVideoJsonPrompt; rebuild its JSON in this order:

{
  "voice_lock": { "Mali": "Mali: Thai woman, about 25, medium-high pitch, Central Thai accent, soft clear voice, polite with quick soft sentence endings" },
  "audio": {
    "type": "lip_sync_dialogue | inner_voice | voiceover_narration | none",
    "spoken_language": "Thai (Central Thai accent)",
    "speaker": "Mali",
    "line": "{dialogueText}",
    "timing": "speech starts at 0.3s and ends before {d - 0.7}s",
    "delivery": "{sd} emotion, {vb} acting energy",
    "others": "everyone else in the shot keeps their lips closed and does not speak"
  },
  "scene_action": "{visualDescription}",
  "motion": "{motion}",
  "camera_movement": "{va}",
  "acting_energy": "{vb}",
  "ending": "{vd}",
  "emotion": "{sd}",
  "sfx": "{sfx}, {vc[]}",
  "music": "NO MUSIC",
  "setting": "{m}",
  "aspect_ratio": "{d}",
  "characters": [{ "name": "…", "acting": "speaking naturally | listening with subtle emotional reaction" }],
  "render_rules": "keep characters, wardrobe and place identical to the start frame; no subtitles, no text, no logos; {soft impact rule}"
}
  • inner_voice: voice_lock = the speaker's; audio.others becomes "{speaker} keeps their lips closed; the line is heard as their inner thought in their own voice; nobody speaks".
  • voiceover_narration: voice_lock = { "Narrator": j voice lock }; audio.others = "nobody on screen speaks".
  • none (Silent): no voice_lock, audio = { "type": "none", "others": "nobody speaks" }.
  • Insert-scene prompt: write one shot with the chosen role that bridges shot n and n+1, same JSON as 10.5.

10.12 ✨ AI suggest (extension scenes)

You see the last frame of a micro-drama clip (EP {ep}). Suggest 3 short follow-up shots that keep the characters, wardrobe and place exactly as they are and make the moment more satisfying.
Each has one speaker (or silent), a line in {f} of max {limit} syllables, and respects soft impact {si}.
Allowed option codes: xa, xb, xc, xd (multi), xe (4|6|8|10), xf. Labels: {xa–xf labels}.
Reply: [{"title": "max 5 words", "text": "what happens next, max 25 words", "speakerId": "c1|null", "scriptType": "dialogue|inner_voice|narration|silent", "line": "…", "syllables": 14, "codes": {"xa": "xa1", "xb": "xb1", "xc": "xc1", "xd": ["xd1"], "xe": 6, "xf": "xf1"}}]

10.13 Caption and poster

Keep V4 prompts: caption JSON { "title", "caption", "hashtags", "mood", "previously" } in f (previously = one-line recap of EP n−1 for EP2+, empty for EP1); poster = image model with up to 5 cast refs + title text in f + "EP.n" (the only allowed text).


11. Save / load & series bible

  • Save project → A9_STUDIO_V5_{title}_{yyyymmdd}.json (fileFlag: "a9Series", version "5.0", savedAt; no videoBase64; still options other than the locked one are dropped) — shows "Saved (x.x MB)".
  • Load project (two-click if work exists) → merge with defaults (selections merged key by key); V4 files: drop automation, move shots into episodes[0], set a = a1 when totalEpisodes is 1, write missing voice locks with 10.2b; restore media ids for characters, locations, products and locked stills of every EP through the pool; jump to the furthest EP and step with data.
  • Series bible — 💾 Save series → {title}-series.json:
{
  "a9Series": true, "version": 3,
  "title": "…", "trope": "tr1", "narrativeStructureId": null, "customStyle": null,
  "selections": { "…": "…" },
  "arc": [{ "ep": 1, "arcBeat": "…", "outline": "…", "cliffType": "cl1", "cliffLine": "…", "status": "done" }],
  "characters": [{ "id": "c1", "name": "…", "role": "…", "dramaRole": "protagonist", "gender": "…", "age": 25, "description": "…", "voice": "cv3", "voiceLock": "…", "imageBase64": "…" }],
  "narrator": { "voice": "j1", "voiceLock": "…" },
  "locations": [{ "id": "loc1", "name": "…", "description": "…", "singleBase64": "…" }],
  "relationships": [{ "a": "c1", "relation": "…", "b": "c2", "note": "…" }],
  "secrets": [{ "id": "s1", "text": "…", "knownBy": ["c1"], "revealEp": 3, "revealedTo": ["c2"], "revealed": false }],
  "lastCliff": { "ep": 2, "shotId": 12, "line": "…", "stillBase64": "…" }
}
  • Load series accepts a full project or a series file (a9Series, fileFlag or legacy lakonSeries), re-uploads portraits, locations and the last cliff still, keeps voice locks 🔒, opens the next EP (lastCliff.ep + 1) with its arc row, or the continuation panel (4.9) when the arc is finished.

12. Constants for developers

export type Role = 'HOOK'|'SETUP'|'PRESSURE'|'TURN'|'PAYOFF'|'CLIFF'|'END';
export type LineType = 'dialogue'|'inner_voice'|'narration'|'silent';
export interface OptionItem { id: string; label: string; promptEn?: string; value?: string; credits?: number; duration?: number; desc?: string; icon?: string; disabled?: boolean; filterBy?: string }
export interface OptionGroup { code: string; step: StepId; label: string; select: 'single'|'multi'|'number'|'toggle'; default: string|string[]|number|boolean; options?: OptionItem[]; filterBy?: string }
export interface RoleSlot { n: number; role: Role; lineType: LineType; sfx: string; }

export const EP_TEMPLATES: Record<'first'|'middle'|'final'|'single', Role[]> = {
  first:  ['HOOK','SETUP','SETUP','PRESSURE','PRESSURE','PRESSURE','TURN','PAYOFF','SETUP','PRESSURE','TURN','CLIFF'],
  middle: ['HOOK','SETUP','PRESSURE','PRESSURE','PRESSURE','TURN','PAYOFF','PAYOFF','SETUP','PRESSURE','TURN','CLIFF'],
  final:  ['HOOK','SETUP','PRESSURE','PRESSURE','TURN','TURN','PAYOFF','PAYOFF','PAYOFF','PAYOFF','SETUP','END'],
  single: ['HOOK','SETUP','SETUP','PRESSURE','PRESSURE','TURN','PRESSURE','PRESSURE','TURN','PAYOFF','PAYOFF','END'],
};
export const ROLE_DEFAULTS: Record<Role, { sa: string; va: string; sd: string; vd: string; vb: string; sfx: string; lineMax: number }> = {
  HOOK:     { sa: 'Extreme close-up', va: 'va9',  sd: 'Shock',   vd: 'vd7', vb: 'vb5', sfx: 'a sharp impact sound or a sudden gasp', lineMax: 0.7 },
  SETUP:    { sa: 'Two-shot',         va: 'va11', sd: 'Calm',    vd: 'vd1', vb: 'vb1', sfx: 'soft room tone',                        lineMax: 1 },
  PRESSURE: { sa: 'Low angle',        va: 'va2',  sd: 'Tension', vd: 'vd2', vb: 'vb3', sfx: 'low murmurs of onlookers',              lineMax: 1 },
  TURN:     { sa: 'Insert detail',    va: 'va10', sd: 'Shock',   vd: 'vd4', vb: 'vb5', sfx: 'a deep bass hit',                       lineMax: 1 },
  PAYOFF:   { sa: 'Close-up',         va: 'va2',  sd: 'Triumph', vd: 'vd2', vb: 'vb3', sfx: 'onlookers gasp',                        lineMax: 0.7 },
  CLIFF:    { sa: 'Extreme close-up', va: 'va9',  sd: 'Shock',   vd: 'vd7', vb: 'vb5', sfx: 'a single heartbeat thump',              lineMax: 1 },
  END:      { sa: 'Wide',             va: 'va3',  sd: 'Calm',    vd: 'vd8', vb: 'vb6', sfx: 'gentle closing ambience',               lineMax: 1 },
};
// ROLE_MOTION: see 1.1. TURN: the first TURN shot of an EP uses 'Insert detail', later ones 'Close-up'. END: sa = EP1 shot 1 framing when o8.

export function buildEpisodePlan(ep: number, totalEps: number, shots: number, format: 'a1'|'a2', i: 'i1'|'i2'|'i3', ending: string): RoleSlot[] {
  const key = format === 'a1' ? 'single' : ep === 1 && totalEps > 1 ? 'first' : ep === totalEps ? 'final' : 'middle';
  const roles = [...EP_TEMPLATES[key]];
  if (format === 'a2' && ep === totalEps && ending === 'o6') roles[roles.length - 1] = 'CLIFF';   // season cliffhanger
  const count = (r: Role) => roles.filter(x => x === r).length;
  for (let k = 0; roles.length < shots; k++) {                            // grow: P before the first T, PO after the last PO
    if (k % 2 === 0) roles.splice(roles.indexOf('TURN'), 0, 'PRESSURE');
    else roles.splice(roles.lastIndexOf('PAYOFF') + 1, 0, 'PAYOFF');
  }
  const shrink: [Role, number][] = [['SETUP', 1], ['PRESSURE', 1], ['TURN', 1], ['PAYOFF', 1]];
  for (const [r, keep] of shrink)                                          // shrink: S (keep 1), P (keep 1), extra T / PO
    for (let x = roles.length - 2; x > 0 && roles.length > shots; x--) if (roles[x] === r && count(r) > keep) roles.splice(x, 1);
  return roles.map((role, idx) => ({
    n: idx + 1, role, sfx: ROLE_DEFAULTS[role].sfx,
    lineType: i === 'i2' || (i === 'i3' && (idx === 0 || idx === roles.length - 1)) ? 'narration' : 'dialogue',
  }));
}
export const MIN_SHOTS = 5;

export const SYLLABLE_LIMIT: Record<4|6|8|10, number> = { 4: 12, 6: 20, 8: 28, 10: 36 };
const seg = new Intl.Segmenter('th', { granularity: 'word' });
export function estimateSyllables(text: string): number {                 // rough: Thai ~2 consonants per syllable
  let n = 0;
  for (const { segment, isWordLike } of seg.segment(text)) {
    if (!isWordLike) continue;
    const thai = segment.match(/[\u0E01-\u0E2E]/g)?.length ?? 0;
    n += thai ? Math.max(1, Math.round(thai / 2)) : (segment.match(/[aeiouy]+/gi)?.length ?? 1);
  }
  return n;
}
export const HOOK_BANNED: RegExp[] = [
  /\u0E2A\u0E27\u0E31\u0E2A\u0E14\u0E35/,                         // "sawasdee" (hello)
  /\u0E15\u0E2D\u0E19\u0E17\u0E35\u0E48\u0E41\u0E25\u0E49\u0E27/,  // "ton tee laew" (previously / last episode)
  /\b(hello|welcome|previously|last episode|subscribe)\b/i,
]; // matches are warnings, never blocks
export const IMPACT_WORDS = /\b(slap|slaps|slapped|slapping|punch|punches|punched|hit|hits|kick|kicks|kicked|stab|stabs|stabbed|shoot|shoots|shot at|strangle|choke|beat up|smash(es|ed)? (into|onto) (his|her|their) (face|head))\b/i;
export const SOFT_IMPACT_MAP: Record<string, string> = {
  slap:  'the hand raised mid-air before contact; next shot: the face already turned aside, hand on the cheek',
  punch: 'the fist pulled back before contact; next shot: the person stumbling back, hand on the jaw',
  splash:'water already leaving the glass, drops in the air; next shot: the wet face, hair dripping',
  throw: 'papers or objects landing and scattering on the table, never hitting a person',
  push:  'the victim stumbling back, the other hand still extended',
  weapon:'no weapon shown; threat through words, eyes and a step closer',
};
export const VOICE_LOCK_LINE = (name: string, lock: string) => ({ [name]: lock });

export const OPTION_GROUPS: OptionGroup[] = [
  { code: 'tr', step: 'setup', label: 'Trope', select: 'single', default: 'tr1', options: [ /* tr0–tr12, 4.2 */ ]},
  { code: 'a', step: 'setup', label: 'Story format', select: 'single', default: 'a2', options: [ /* a1 single film, a2 micro-drama series */ ]},
  { code: 'b', step: 'setup', label: 'Episodes', select: 'number', default: 5 },
  { code: 'c', step: 'setup', label: 'Shots per EP', select: 'number', default: 12 },
  { code: 'u', step: 'setup', label: 'Content level', select: 'single', default: 'u2', options: [ /* u1–u5 */ ]},
  { code: 'si', step: 'setup', label: 'Soft impact', select: 'toggle', default: true },
  { code: 'mu', step: 'setup', label: 'Music for editing', select: 'single', default: 'mu1', options: [ /* mu1–mu8 */ ]},
  { code: 'cl', step: 'setup', label: 'Cliff type', select: 'single', default: 'cl1', options: [ /* cl1–cl7, per EP */ ]},
  { code: 'se', step: 'storyboard', label: 'Role', select: 'single', default: 'HOOK' },
  { code: 'sf', step: 'storyboard', label: 'Line type', select: 'single', default: 'dialogue' },
  // a–w (o, p, r, t, cb, cf defaults changed for series), ns (Advanced), za–zb, ca–cf, cv, ce, sa–sd, ia–id, va–vd, vm, vt, vi, xa–xf, pa–pb, fe (pool)
];
export const NARRATIVE_STRUCTURES: NarrativeStructureItem[]; // keep ids; English name, category, description, beats (4.3)
export const TROPES: { id: string; label: string; promptEn: string; genre: string; mood: string; pressure: string; turn: string; payoff: string }[]; // 4.2
export const PRESETS: Preset[]; // 10 presets of 4.1, values include tr, l and (single film) narrativeStructureId
export const BRAND = { name: 'A9 STUDIO', version: 'V5.0 Micro-Drama', fileFlag: 'a9Series', copyright: 'Copyright of A9 STUDIO', facebook: 'Facebook: A9 STUDIO' };
// added to VIDEO_STYLES (category 'cinematic' and 'custom'):
// { id: 'k_photorealistic', label: 'Photorealistic', promptText: 'Hyper-realistic photorealistic photography, extreme detail, 8k, ultra-hd, cinematic lighting, sharp focus, lifelike textures, professional camera shot, realistic skin and materials, stunning clarity' }
// { id: 'k_custom', label: 'Custom style', promptText: 'CUSTOM_STYLE' }
export const CUSTOM_STYLE = 'CUSTOM_STYLE';
export const stylePrompt = (k: string, customStyle?: string) =>
  k === 'k_custom' ? (customStyle?.trim() || '') : (VIDEO_STYLES.find(s => s.id === k)?.promptText || 'Cinematic 35mm film');   // replaces every styleObj?.promptText lookup in promptService.ts
export const SINGLE_FRAME_SUFFIX = 'single continuous full-frame image, one moment, one camera, edge-to-edge, no collage, no grid, no split screen, no panels, no character sheet, no comic page, no borders, no inset, no text, no subtitles, no watermark';
export const FORBIDDEN_IMAGE_WORDS = ['storyboard','panel','panels','sheet','turnaround','grid','collage','sequence','comic page','multiple views','split screen'];
export const EDIT_PROMPT = (change: string, cast: string) =>
  `Edit reference image 1. Keep the exact same place, background, light, color grade, wardrobe and faces identical. ` +
  `This is a minimal edit: change only what is listed, everything else stays as in image 1. Change only: ${change}. ${cast}. ${SINGLE_FRAME_SUFFIX}`;
export const TIMEOUT_MS = { text: 60000, image: 45000, check: 30000, upload: 30000, video: 180000 };
export const MAX_POOL_CONCURRENT = 4; export const START_GAP_MS_OPTIONS = [2000, 3000, 4000];

StoryState changes: customStyle?: string (used when k = k_custom; saved in the project and the series bible) · shots → episodes: { ep: number; arcBeat: string; outline: string; cliffType: string; cliffLine: string; shots: Shot[]; musicCues?: {from, to, cue}[]; retention?: {rule: number; shotId?: number; problem: string}[] }[] (the current EP's shots are episodes[currentEpisode − 1].shots) · trope: string · secrets: {id, text, knownBy: string[], revealEp: number, revealedTo: string[], revealed: boolean}[] · relationships: {a, relation, b, note}[] · narratorVoiceLock: string · remove automation. Character additions: gender, age, dramaRole, voice, voiceLock, voiceLockPinned?. Shot additions: role: Role, scriptType: LineType (extends 'dialogue'|'narration'), syllables?, motion, sfx, continuity (sc1–sc6), emotion, usesSecrets?: string[], revealsSecrets?: string[], pinned?: string[], options?: {mediaId, base64, check?}[], lockedOption?, pickReason?, stillState, videoState, chips?: {va, vb, vc, vd}, refChanged?, voiceChanged?, isExtra?. <ChipGroup /> (keep) gets onAiPick, onResetDefault, showCustom wired on every group; <ActionButton> and <CardStatus> (2.1) are shared primitives next to TwoClickButton.


13. Fixes to known gaps in the V4 source

  • UI, toasts, errors and file names were Thai → English (0.1, 0.11); BRAND.version was empty → "V5.0 Micro-Drama".
  • Stills, location shots and clips were hard-coded to 16:9 → use d; d had only 2 ratios → 7.
  • ⚡ Full automation read stale state in ready() closures (jobs never became ready), drew stills without references and rendered with stale shots → removed; Sequential pick flow with live getState() (7.1, 7.2).
  • Stills had 1 option, no check, no collage guard → 3 options, image check, keyframe rules (7.0).
  • Plot, cast, storyboard and quick-fix calls bypassed the pool; buttons used setTimeout guesses for busy state → everything through the pool; <ActionButton> follows the job.
  • Pool had no start gap, no timeouts, no cancel → 7.2. Pool pause used alert() → in-app banner.
  • Storyboard was one call with no batching, repair or cast coverage → restored (6).
  • Load project restored only character images and ignored locations / stills → all media restored through the pool.
  • FFmpeg concat failed when a clip had no audio or a different size → normalize + silent track (9).
  • ChipGroup never received onAiPick / onResetDefault → wired.
  • Narrative structure text, style labels, genre text and model notes were Thai → English (4.3, 4.7, 4.9, 4.4).
  • Video prompt always said "gentle push or parallax drift" → role motion + va–vd (1.1, 8.0).
  • Characters had no fixed voice; buildVideoJsonPrompt sent only a name → Voice lock per character and narrator (5.1, 10.11).
  • buildVideoJsonPrompt allowed any speaker list and every character "speaking naturally" → one speaker, others lips closed (10.11).
  • Clips could carry generated music that changed every shot → "music": "NO MUSIC" + Music cue sheet (9).
  • The narrative structures were feature-film arcs with no hook / cliffhanger rhythm → tropes + Drama Beat Engine + per-EP templates (1, 4.2).
  • Series continuation cleared all shots and lost the previous EP → all EPs kept in one project; EP n+1 opens on EP n's cliff still (3.1, 6.3).
  • Word counts do not work for Thai → syllable limits (6.1).
  • Violent actions were often rejected by the video model → Soft impact (1.5).

14. Checklist

  • [x] FIRST: click feedback 2.1 — <ActionButton> + <CardStatus> on every listed button; acceptance test ①–⑧ passes; reference badge on every still slot
  • [x] Brand A9 STUDIO everywhere; version "V5.0 Micro-Drama"; no logo URL; access code not in spec (0.9)
  • [x] English UI and file names; story text in f with dialect g; Thai people and places by default (0)
  • [x] Drama Beat Engine: 7 roles with defaults for angle, camera move, emotion, line length, ending, acting, SFX and motion template (1.1)
  • [x] EP templates first / middle / final / single + scaling rules + minimum 5 shots (1.2)
  • [x] Series arc for any EP count; Advanced structure only changes arc content (1.3, 4.3)
  • [x] Per-shot overrides with 📌, role change re-applies defaults, ↺ Reset to role (1.4)
  • [x] Soft impact on by default, locked for u1 / u4 / u5; map + impact words + image check (1.5)
  • [x] No <select>; every group as chips with ✨ AI pick / ↺ Default / + Custom wired (2)
  • [x] Visual style k: 46 source styles + k_photorealistic + k_custom (own text field, stylePrompt() in every prompt) (4.7, 12)
  • [x] Regenerate controls, Ref changed, Voice changed, "Visual action" never empty (2.2)
  • [x] EP switcher; plan all EPs, produce one at a time; EP n opens when EP n−1's stills are all locked (3, 3.1)
  • [x] ✨ Start with AI (4.0): idea → plot → arc & secrets → cast → voices → portraits → EP1 storyboard → retention check → Stills; never locks a still, never renders video
  • [x] 10 presets with idea starters (4.1); 12 tropes + "No trope" (4.2); 45 structures in Advanced (4.3)
  • [x] Defaults: a2 series · b5 · c12 · 9:16 · vt 6 s · u2 · si on · o5 · p1 · r2 · t5 · cb2 · cf2 (4.5–4.8, 5)
  • [x] Arc panel with cliff type cl and cliff line per EP; secrets ledger and relationships panels (4.9, 4.10)
  • [x] Voice lock per character and narrator, sent word for word, only the speaker's in each clip (5.1, 10.11)
  • [x] Storyboard: role se, line type sf (Dialogue · Inner voice · Narrator · Silent), one speaker per shot, syllable counter (6, 6.1)
  • [x] Retention check 8 rules, one automatic fix, then ✨ Fix / ✨ Fix all; never blocks (6.2)
  • [x] EP2+ shot 1 = sc6 from the last EP's cliff still, no recap narration (6.3, 7.0b)
  • [x] Keyframe rules incl. speaker framing and soft impact; image check with 8 tests (7.0)
  • [x] No ⚡ Full automation: shot n+1 unlocks only after shot n is picked; a pick auto-starts the next shot's options (7.1)
  • [x] Pool: max 4, start gap, live countdown, timeouts per kind, Cancel / Retry / Force run, Reset shot, Clear stuck, credit pause, breaker, live getState() (7.2)
  • [x] Video: presentation chips va–vd default from role on every card + Apply to all; per-shot vm / vt / vi; ▶ Play / ⏹ Stop; ▶ Play EP in order (8.0)
  • [x] Video prompt JSON in the fixed key order with voice_lock, audio.timing, "music": "NO MUSIC" (10.11)
  • [x] + Add scene on the last video card of the EP, start from the previous clip's last frame, role from xa, ✨ AI suggest (8.1)
  • [x] Cinema per EP: merge {title}_EP{nn}.mp4, Music cue sheet, dialogue sheet, caption with "Previously", poster with "EP.n" (9)
  • [x] Series bible v3 with arc, voice locks, relationships, secrets ledger and last cliff; V4 files load into episodes[0] (11)
  • [ ] Owner to provide: A9 STUDIO logo, style preview images, optional access code