@wji.ai/cli 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,206 @@
1
+ # Stage 2 — Characters
2
+
3
+ Turn every `characters[]` entry into a reusable asset: a profile people read, an English portrait
4
+ prompt, a 16:9 character-sheet prompt, and a voice prompt — then the three assets WJI needs for a
5
+ character to hold across shots: **headshot** (icon), **sheet**, **voice** (`primaryAudio`).
6
+
7
+ ## Profile (drama language)
8
+
9
+ `oneLiner`, `profile.gender/ageRange/identity/appearance/personality(3–5 words)/temperament/
10
+ motivation/relationships/evidence`. Inferred facts are fine when marked (`(推断)`); `evidence`
11
+ holds verbatim quotes only, or stays empty. Keep the tier from the outline — if it looks wrong,
12
+ change the outline, not this stage.
13
+
14
+ ### `identityEn` — the name-free handle
15
+
16
+ One short English phrase, ≤ 80 characters, distinct per character: `the woman in her mid thirties in
17
+ headphones`, `the stocky man in his sixties with a grey crew cut`. Prompts may never carry a name,
18
+ but they constantly have to say *who* — who speaks this line, who turns — and this is the words
19
+ they use. Pick something visible and permanent (age, build, the thing they always wear), not a
20
+ plot role: the model can see a grey crew cut, it cannot see "the retired security guard". Gated:
21
+ present, English, name-free, no two characters alike.
22
+
23
+ ## The bible — `bible` and `profile.relationships` (gates `bible`, `relationships`)
24
+
25
+ A profile says what they look like; the bible says how they got here, how they act now and what
26
+ the story will make them pay. Method in `craft-story.md` §7. By tier:
27
+
28
+ - every character: `bible.function` — 主角 / 共同主角 / 反派 / 爱情线 / 导师 / 盟友 / 镜像 / 压力测试者 /
29
+ 喜剧调节 (the dramatic job, independent of the tier);
30
+ - `support` and `lead`: `hookLine` (身份 + 核心矛盾 + 外在欲望 + 风险), `want` and `need` (not the same
31
+ sentence), `misbelief`, `contradiction`, `pressureResponse`, `speech.rhythm` and `speech.silence`
32
+ (plus `catchphrase`, `forbiddenWords`, an optional `register` override);
33
+ - `lead`: also `wound` (150–250 字, the choice and what they believed after), ≥ 2 `pastChoices`,
34
+ `firstImpression` → `finalUnderstanding`, exactly three `forbidden` and three `mustFace`, and an
35
+ `arc` of ≥ 3 stages with the 高潮选择 among them, each bound to an episode.
36
+
37
+ `profile.relationships[]` becomes the 关系网: `{id, name, surface, real, power, secret, turnEpisode}`.
38
+ Every lead has at least one with `surface` + `real` + `power`, and somewhere in the cast a
39
+ relationship carries a `secret` — that is where the reversals come from.
40
+
41
+ **`bible.speech` is the source of the voice.** `voice.pace` / `videoDirection` and every line's
42
+ `delivery` are written from `rhythm`, `silence` and `forbiddenWords`; `seed script` prints them in
43
+ the `seedNote` and `render dialogue` puts them beside each line for the polish pass.
44
+
45
+ ## Image prompts (English, always)
46
+
47
+ - **No names, aliases, author or title anywhere** — image models paint what they remember of a
48
+ name. Describe the person.
49
+ - **Ethnicity + era + region explicit** (`East Asian, Han Chinese features`, `contemporary 2020s
50
+ Chinese city`, `Republican-era river town`). Left out, the model defaults to a contemporary
51
+ Western face. Both are gated.
52
+ - `image.prompt`: an expressive three-quarter bust on a neutral background, soft directional key
53
+ light, shallow depth of field. Realism comes from imperfection: uneven skin tone, visible pores,
54
+ asymmetry in eyelids and brows, stray hairs, fabric with weave and wear. Put the individual
55
+ description first and make it long — two similar-age characters written as "template + age +
56
+ jacket colour" come out as the same person (`distinct` gate, 75 % word overlap).
57
+ - `image.negativePrompt`: with `style: photoreal` (live action — the right choice for a
58
+ drama shot like a TV series) ban *drawing*: `illustration, anime, cartoon, painting`, and
59
+ **never** `photorealistic`, `film grain` or `skin texture`. With `style: realistic` ban *fakeness* (plastic skin, doll face, dead
60
+ eyes, helmet hair, flat fabric) and **never** `photorealistic` / `3d render`; with `ghibli` you
61
+ **must** ban `photorealistic`. Both gated. Always add `logo, brand name, text` — a "store
62
+ uniform" comes back wearing a real chain's badge otherwise (seen in testing), and a real brand
63
+ on a character is a problem in every shot after it.
64
+ - `image.sheet`: the model-sheet layout, written out in full every time:
65
+
66
+ ```
67
+ ┌──────────┬────────────────────────────┐
68
+ │ bust │ front side back │ ONE 16:9 canvas, pure white background
69
+ │ portrait ├────────────────────────────┤ LEFT ZONE ≈ 34 % width: head-and-shoulders bust,
70
+ │ (face │ detail · detail · detail │ both shoulders complete, flat bottom edge
71
+ │ ref) │ │ RIGHT-TOP: three full-body views, same height,
72
+ └──────────┴────────────────────────────┘ same head-to-body ratio, PROPORTIONS ARE CRITICAL
73
+ RIGHT-BOTTOM: 4–5 small detail studies;
74
+ the detail studies give way, not the figures
75
+ ```
76
+
77
+ Plus: `all views share the SAME face as the bust portrait`, `LIGHTING IN THE LEFT ZONE ONLY:
78
+ soft directional key…`, `LIGHTING IN THE RIGHT ZONES: flat even orthographic lighting`, the
79
+ style's render phrase (`STYLE_PRESETS[style].render` — `node scripts/short-drama.mjs` exports
80
+ it; copy it verbatim), `No text, no watermark`. Gated: `16:9`, render phrase, no names.
81
+
82
+ ## Voice
83
+
84
+ Six local-language fields (`timbre` `pitch` `pace` `accent` `emotion` + `referenceHint` optional)
85
+ for people, one English `voice.prompt` for a voice-design engine, and a **`voice.sample`** — the
86
+ short acted line that becomes the character's voice asset.
87
+
88
+ ### `voice.sample` — the 3–5 s reference
89
+
90
+ ```json
91
+ "sample": { "line": "這裡是海風電台。", "direction": "貼著麥克風,像只講給一個人聽" }
92
+ ```
93
+
94
+ - **`line`**: one line in the drama's language, **3–5 s at `params.speechCharsPerSecond`**
95
+ (default 2.4 — the voice model speaks far slower than the 4.5 chars/s the script uses for
96
+ reading, so that is **8–12 Chinese characters**, not twenty). Gated. No quote marks: the
97
+ engine reads them.
98
+ - **`direction`**: how *this line* is acted, in the drama's language.
99
+ - Pick a line that shows the character's default register, not a plot moment: it is a timbre
100
+ reference, and every later clip inherits it.
101
+
102
+ ### The prompt the model actually receives
103
+
104
+ `prompt <project> voice C01` composes it from the fields you already wrote — you do not write it
105
+ by hand, and it is a **parenthetical instruction followed by the line**:
106
+
107
+ ```
108
+ (三十五歲上下女;基隆地方電台深夜節目主持人;性格冷靜、自持、嘴硬、念舊;偏低的胸腔共鳴,帶一點沙;
109
+ 中低音域,起伏小;慢,句尾拖半拍;台灣口音國語,捲舌音輕;克制的溫柔,預設是平靜;
110
+ 此刻貼著麥克風,像只講給一個人聽)這裡是海風電台。
111
+ ```
112
+
113
+ In order: age + gender, identity, `profile.personality`, then `voice.timbre` / `pitch` / `pace` /
114
+ `accent` / `emotion`, then `此刻` + the sample's `direction`. `params.speechAccent` is appended
115
+ only when `voice.accent` is empty — the drama-language wording wins. Quote marks are stripped,
116
+ because inside the parenthetical they would be read aloud.
117
+
118
+ **The parenthetical is obeyed, not spoken.** Measured on Seed Audio 1.0: the same 8-character line
119
+ ran 3.16 s bare and 3.50 s behind a ~100-character description — speaking that description would
120
+ have added some forty seconds. It came back slower and lower, as the description asked. So write
121
+ the voice fields richly: personality, rhythm, where the voice sits, what it does at the end of a
122
+ phrase. That detail is free and it is the only thing separating two characters of the same age.
123
+
124
+ The English `voice.prompt` stays as the static voice identity (≤ 400 characters, a parameter
125
+ string, **no quoted lines** — gated) for engines that take a description instead of a sample.
126
+
127
+ ### `voice.videoDirection` — the clause every clip carries
128
+
129
+ One English clause, ≤ 160 characters, that goes **beside every line this character speaks in every
130
+ video prompt**:
131
+
132
+ ```
133
+ slow and close-miked, low and chest-resonant with a light rasp, falling at the end of every phrase,
134
+ Taiwanese-accented Mandarin, restrained
135
+ ```
136
+
137
+ It is `voice.prompt` compressed to what a video model can act on in one breath: pace, where the
138
+ voice sits, what it does at the end of a phrase, accent, default attitude. Without it a video model
139
+ reads every character the same way — evenly, brightly, like a newsreader — and the clip stops
140
+ matching the voice asset the same bible produced. Gated in stage 2 (present, English, ≤ 160 chars,
141
+ no names, no quoted lines) and again in stage 5: a segment prompt must carry the direction of every
142
+ character who speaks in it. For `wan`, the drama-language `timbre` / `pace` / `accent` / `emotion`
143
+ fields are used instead, since that prompt is Chinese.
144
+
145
+ ## On WJI
146
+
147
+ Dataset per character in `character/`:
148
+
149
+ ```bash
150
+ wji dataset new <character folder> "<name>" -t character -d "<oneLiner>" # bind characters.C01.wji.datasetId
151
+ node {baseDir}/scripts/short-drama.mjs render <project> character C01 > /tmp/C01.md
152
+ node {baseDir}/scripts/short-drama.mjs prompt <project> voice C01 > /tmp/C01-voice.txt
153
+ wji fs set <id> --merge content=@/tmp/C01.md --merge image_prompt=@/tmp/C01-sheet.txt \
154
+ --merge audio_prompt=@/tmp/C01-voice.txt --merge metadata.shortDrama=@/tmp/C01.json
155
+ ```
156
+
157
+ `characters` canvas, one row per character (see `canvas-recipes.md` §1):
158
+
159
+ | Node | Model | Ratio | Prompt |
160
+ | --- | --- | --- | --- |
161
+ | headshot (image, generate) | GPT Image 2.5 Sunburst (or the best image model listed) | 1:1 | `image.prompt` + `clean front-facing portrait, plain background` |
162
+ | sheet (image, generate, `--from` headshot) | same | 16:9 | `@image1 is the face to match exactly. ` + `image.sheet` |
163
+ | **voice (audio, generate, `--from` headshot)** | **`seed-audio-1.0`** | — | `prompt voice C01` — `(direction)line` |
164
+
165
+ **The voice node references the headshot.** Seed Audio 1.0 does 参考图片配音: given the face it
166
+ invents a voice that fits it, which is why the headshot must be approved first. It takes exactly
167
+ one reference, so pass only the headshot node. About 3.6 credits a sample.
168
+
169
+ Generate headshots first (a set of headshots is a batch — quote the total), **look at them**
170
+ (download and open), then sheets and voices from the approved headshots. Check a sheet for: one
171
+ face across all views, three full bodies of equal height, no squashed figures, white background.
172
+ Regenerate rather than accept a sheet with two faces — every shot inherits it. Listen to the voice
173
+ before you attach it: the age and the weight of the voice should match the face.
174
+
175
+ Attach and bind:
176
+
177
+ ```bash
178
+ wji fs set <datasetId> --add-images <headshotAsset> <sheetAsset> <voiceAsset> --icon <headshotAsset> --set primaryAudio=<voiceAsset>
179
+ node {baseDir}/scripts/short-drama.mjs bind <project> characters.C01.wji.assets.headshot <headshotAsset> # and sheet / voice, nodes.*
180
+ ```
181
+
182
+ The voice lives in **three** places on the dataset, and all three matter: `images[]` (the asset
183
+ list takes audio too — the clip is then playable from the dataset card next to the headshot and
184
+ the sheet), `primaryAudio` (which one is *the* voice), and `content.audio_prompt` (the brief that
185
+ produced it, so it can be regenerated identically). `render character` puts the same brief, the
186
+ sample line and its acting direction into the readable 声线 section of `content.content`.
187
+
188
+ **The sample is a dry voice.** Seed Audio is an audio-scene generator — asked for a line it will
189
+ happily add a room, a score and a coffee machine — so the composed prompt ends its parenthetical
190
+ with `干声,无配乐无音效`. The same model makes the segments' soundtrack beds in stage 5
191
+ (`prompt audio`), where the room, the score and the coffee machine belong.
192
+
193
+ ### What the voice asset is for
194
+
195
+ It is a **reference for later**, not a deliverable. Every ref-capable video model takes audio
196
+ references (`wan-3.0-ref` up to 5, ≤ 15 s total; `seedance-2.5-ref`; `minimax-h3-ref` up to 3), so
197
+ on an episode canvas the character's voice display node connects to the video node alongside the
198
+ sheets and becomes `@audio1…`, holding the timbre across clips the way the sheet holds the face.
199
+ Set `params.voiceSamples: false` only when no TTS model is enabled on the account — `manifest`
200
+ then stops asking for the node.
201
+
202
+ ## Gates
203
+
204
+ `profile` · `bible` · `relationships` · `char-prompts` · `voice` · `distinct`. Missing assets are
205
+ reported by `manifest`, not by `validate` — a character without a voice model available is still
206
+ a valid character.
@@ -0,0 +1,103 @@
1
+ # Stage 1 — Outline
2
+
3
+ Turn the source (novel, synopsis, or an original idea) into the structure everything else hangs
4
+ on: what stays, who is who, where the beats land, what happens each episode. **Skeleton before
5
+ episodes, sign-off between them.**
6
+
7
+ ## Inputs
8
+
9
+ 1. The source text the user pointed at — read it; never invent content from a title. If it is
10
+ long, summarise per volume in a scratch file first and keep quotes for `keep[].evidence`.
11
+ 2. The four parameters (episodes × minutes, genre, adapt mode, ratio) — already in `params`.
12
+ 3. Preferences the user stated (keep this character, keep that scene) — honour them and record
13
+ them in `outline.keep`.
14
+
15
+ ## Order of work (the order is the method)
16
+
17
+ 0. **Brief.** `outline.brief` — the six judgements of `craft-story.md` §1 (logline, audience,
18
+ the 爽点 engine, the main hook, the pressures on both sides, the emotional line, the promise).
19
+ A one-line idea is not enough to fill these; ask the user the focusing questions rather than
20
+ inventing a genre. Then `params.register` (语体) if not given at `init`.
21
+ 1. **Cut.** A short drama carries one main line and at most one sub-line. Everything else goes to
22
+ `outline.cut` with a `why`. Any adapt mode other than `原创` / `忠实` must cut something.
23
+ Write `cutNote`: what the story's endpoint becomes after the cuts.
24
+ 2. **Merge people.** Characters with the same function become one face. Then tier them:
25
+ `lead` (1–5, the protagonists and main antagonist, `arc` required), `support` (≤10, named
26
+ secondary characters, `arc` required), `functional` (≤10, a role label as the name, no arc).
27
+ Unnamed background people are not listed. Write `mergeNote`: why these are the leads.
28
+ 3. **Collect scenes.** Primary scenes are capped at `4 + ⌈episodes/10⌉` (5–15) — generated scenes
29
+ are cheap, but every extra one is another consistency asset to maintain. A scene used in only
30
+ one episode needs a `reusePlan` (which existing environment it derives from, what changes) or
31
+ becomes a `variantOf` in Stage 3.
32
+ 4. **Place the beats (爽点).** Majors first — where the big reveals land — then minors to fill.
33
+ Rules: adjacent beats ≤ `maxBeatGap` (3) episodes apart, a beat in episode 1, a beat in the
34
+ finale, at least one major, and the first major never on the last episode. `type` follows the
35
+ genre (打脸 / 揭破 / 反转 / 逆袭 / 认亲 …).
36
+ 5. **Pick the narrative props.** After the beats, not before — a prop is a prop because it carries a
37
+ beat. Test: *if it broke, got lost or was swapped, would the plot collapse?* Yes → `props[]`
38
+ with `function` and `beatIds`. No → set dressing; it becomes a scene anchor in Stage 3. At most
39
+ 8.
40
+ 6. **Structure and guideline.** `outline.structure` — golden / escalation / climax with episode
41
+ ranges (first ~20 % / ~50 % / ~30 %; `craft-story.md` §2) and a task, 爽点 plan and hook plan
42
+ each. `outline.guideline` — the 创作指引: tone, emotional intensity, dialogue style, visual
43
+ style, production limits, ≥ 3 forbidden rules, 3–5 priorities. It is the document every later
44
+ stage re-reads (`render guideline`), so write it as rules, not as a summary.
45
+
46
+ Run `validate --stage outline` now. The `episodes` gate will fail (no episodes yet) — every other
47
+ gate should pass before you show the skeleton.
48
+
49
+ ## Sign-off ⛔
50
+
51
+ Show the user, in plain language: the lines cut, the people merged, the episode of each major beat.
52
+ Wait for a yes. Change the skeleton on their feedback and validate again.
53
+
54
+ ## Episodes (batches of ≤10)
55
+
56
+ Design first, prose second. Per episode the 分集目录 fields: `function` (开局钩子 / 冲突升级 /
57
+ 身份揭露 / 情感推进 / 大反转 / 清算收束), `task` (`goal` / `obstacle` / `stake`), `change` (这一集改变了
58
+ 什么 — one sentence; "they talked" means the episode is not strong enough), `satisfaction`
59
+ (`type` from the 爽点 library + the visible `action` that does it), `hookType` (身份 / 危机 / 结果 /
60
+ 情感 / 反转 / 道具). The first three episodes are the retention window: 1 grabs (开局钩子), 2 proves
61
+ the 爽点 (its type is the brief's engine), 3 widens the line (冲突升级 / 身份揭露 / 大反转); the finale
62
+ includes 清算收束 or 大反转. Then `synopsis` (narrative prose — a quotation mark means you are writing dialogue, which
63
+ is Stage 4's job), `hook` (the question that makes them tap the next episode), `suspense` (what is
64
+ still buried), `sceneIds`, `characterIds`, `propIds`, `crowdPlan` when three or more characters
65
+ appear (how the group is staged: who is foreground, who is over-the-shoulder, or "never in one
66
+ frame — separate scenes"), and `warnings` from the fixed list (雨戏 / 肢体接触 / 人群 / 手部特写 —
67
+ the things video models get wrong; over-report).
68
+
69
+ A beat placed on an episode must actually happen in that episode's synopsis. Every character,
70
+ scene and prop must be used somewhere — `refs` will name the idle ones.
71
+
72
+ ## Sync
73
+
74
+ ```bash
75
+ node {baseDir}/scripts/short-drama.mjs render <project> outline > /tmp/outline.md
76
+ wji dataset new <story folder> "<title> 大纲" -t outline -d "<core sentence>" # once; bind wji.outlineDatasetId
77
+ wji fs set <id> --merge content=@/tmp/outline.md --merge viewMode=content \
78
+ --merge metadata.shortDrama=@/tmp/outline.json # outline block + params
79
+ node {baseDir}/scripts/short-drama.mjs render <project> guideline > /tmp/guideline.md
80
+ wji dataset new <story folder> "<title> 创作指引" -t outline -d "<logline>" # once; bind wji.guidelineDatasetId
81
+ wji fs set <id> --merge content=@/tmp/guideline.md --merge viewMode=content \
82
+ --merge metadata.shortDrama=@/tmp/guideline.json # {brief, structure, guideline, register}
83
+ ```
84
+
85
+ The 创作指引 is its own dataset because it is the thing to open first, every time — before a
86
+ character, an episode or a storyboard, print `render guideline` and read it.
87
+
88
+ Re-run the same `fs set` after every outline change; the dataset is a view of `project.json`.
89
+
90
+ ## Gates (what `validate --stage outline` checks)
91
+
92
+ `params` · `brief` · `structure` · `guideline` · `adaptation` · `tiers` · `scenes` · `props-cap` ·
93
+ `beats` · `episodes` · `episode-design` · `refs` — see the gate labels in the output; each failure
94
+ names the entity and the fix.
95
+
96
+ ## Rhythm for a short drama
97
+
98
+ A 短劇 episode is paced by its 爽点, not its minutes: plan **one beat per 30–45 s** plus the
99
+ cliff, so a 1-minute episode carries 1–2 beats and a 90-second one 2–3 (the gates only demand one
100
+ per episode and a gap ≤ 3 — the rhythm is on you). Put the hook's image inside the first three
101
+ script beats, a beat near the midpoint, and let the cliff be the last thing on screen. Adding beats
102
+ to an existing episode is fine — append new ids (`B11`, `B12`), never renumber, and list them in
103
+ that episode's `beatsClaimed`.
@@ -0,0 +1,113 @@
1
+ # Stage 4 — Script
2
+
3
+ One episode per task, batches of ≤3 episodes before moving on — the script is the layer that gets
4
+ rewritten most; small batches fail cheaply. **The script handles drama; the storyboard handles
5
+ shooting** — no shot numbers, no camera, no prompts here.
6
+
7
+ ## Start from the seed
8
+
9
+ ```bash
10
+ node {baseDir}/scripts/short-drama.mjs seed <project> script --ep 1
11
+ ```
12
+
13
+ `episodes/E01.json` now has `targetSeconds`, `hook`, `cliff`, `beatsClaimed`, `hookBeat: [1, 1]`,
14
+ the episode design copied from the outline (`function`, `task`, `change`, `hookType`),
15
+ `satisfactionBeat: null` (to be set) and a `seedNote` with the synopsis, the 爽点 to realise, the
16
+ register, the guideline's priorities and forbidden rules, every speaker's `bible.speech`, the ids
17
+ allowed in this episode, the crowd plan, the warnings, the previous episode's cliff and the beat
18
+ budget. **Before writing: `render guideline`, then the scene bibles (anchors, lighting states) and
19
+ the speakers' `render character` — the bible's `speech` is where the lines come from** (see
20
+ `dialogue-craft.md`). Delete `seedNote` when the scenes are written.
21
+
22
+ ## Hard rules (all gated)
23
+
24
+ 1. **The duration budget is the wall.** Lines cost `chars ÷ 4.5` s, action beats 2.5 s; the
25
+ episode must land within ±15 % of `targetSeconds`. 60 s ≈ 18–22 beats, 120 s ≈ 35, 180 s ≈ 50.
26
+ Over: cut action beats first. Under: add conflict, never small talk.
27
+ 2. **One line, one breath**: ≤ 35 non-whitespace characters. Split long lines with a reaction
28
+ beat in between.
29
+ 3. **Speakers are in the scene** (`characters`), or `VO` (inner voice — whose, in `delivery`).
30
+ VO is emphasis, not a crutch: two or three per episode at most.
31
+ 4. **Action beats: one thing each, narrative, common actions only, no quotation marks.** The video
32
+ model performs actions it has seen a million times — walk in, sit down, hand over, nod, turn,
33
+ grab, hold tight. Precise physical interactions (block a pole with an oar), micro-expressions
34
+ (lashes tremble), inch-level moves, metaphors — rewrite or delete. Drama comes from the
35
+ *combination and timing* of ordinary actions.
36
+ 5. **Every scene has an action beat.** Three lines with nothing happening is radio.
37
+ 6. **Cold open.** The hook's concrete image is on screen within the first 3 beats (`hookBeat`
38
+ points at it; gated) and it is *in motion* — running with the suitcase, not a still close-up of
39
+ it. Static detail close-ups belong to the second appearance.
40
+ 7. **The last beat is the cliff** — an unfinished sentence, an unexplained action, a sound with no
41
+ source. `cliff` describes the effect; the flow delivers it.
42
+ 8. **Claimed beats are performed.** Each id in `beatsClaimed` (seeded from the outline) needs a run
43
+ of consecutive beats that actually realises setup → payoff.
44
+ 9. **The 短剧 rhythm** (`rhythm`): the hook image by `hookSeconds` (15 s) of cumulative time; a
45
+ turn — new information, an obstacle, a counter, a choice — at least every `turnSeconds` (30 s),
46
+ marked `turn: true` on the beat; the 爽点 (`satisfactionBeat`) after the hook, before the last
47
+ beat, on an action beat or a line with an action beside it; never more than
48
+ `maxConsecutiveLines` (3) lines in a row; action beats at least `minActionRatio` (35 %) of the
49
+ lines. The seconds come from the same duration math as the budget.
50
+ 10. **The design is realised** (`script-design`): `function`, `task`, `change`, `hookType` equal
51
+ the outline episode's; the 爽点 type the outline promised is what `satisfactionBeat` delivers.
52
+ 11. **Filmable** (`filmable`): no 心想 / 内心 / 回忆起 / 脑海, no 像是 / 似乎 / seems to, no more than
53
+ three hand verbs in a beat (`craft-shots.md` §generability).
54
+ 12. **The register** (`dialogue-register`, advisory): lines match `params.register` — Cantonese
55
+ markers only in 粤语, no modern words in 古装, no net slang on the middle-aged, English short and
56
+ contracted with names used sparingly. It reports and never blocks; a character who breaks the
57
+ register on purpose gets `bible.speech.register`.
58
+
59
+ Scene fields: `sceneId` and `lighting` must exist upstream (a new lighting state is added to the
60
+ scene in `project.json` first, never bypassed), `characters` / `props` ⊆ the outline's ids for
61
+ this episode.
62
+
63
+ ## Editing discipline
64
+
65
+ - **Change one beat, re-read three.** Position, who holds the prop, who is present — spatial state
66
+ lives in your head while writing and vanishes while editing. Re-reading the neighbours is the
67
+ only way to get it back.
68
+ - **A picture change is a sound change** downstream — remember it when the storyboard exists.
69
+
70
+ ## Feel (not gated, decides whether it plays)
71
+
72
+ Short lines, dense action: an action or reaction every two or three lines, never four lines in a
73
+ row. Subtext goes in `delivery` ("says sit, doesn't move"), not spoken. Stage groups per the
74
+ outline's `crowdPlan`. Scene changes are free for the generator but cost the viewer — tighten one
75
+ scene rather than hopping four.
76
+
77
+ | Fault | Symptom | Fix |
78
+ | --- | --- | --- |
79
+ | radio play | lines only | insert a visual action every 2–3 lines |
80
+ | literary | actions as prose (oar blocks the pole, lashes tremble) | common actions |
81
+ | dead opening | first beat is a still close-up | give the same image in motion |
82
+ | space jump | on the path, next beat on the pier | add the crossing beat; re-read three |
83
+ | padding | greetings, tea, directions | delete; every beat advances or flips |
84
+ | speech | one person, four lines | split to the partner or break with action |
85
+ | omniscience | lines explain everything | move information into action and delivery |
86
+ | flat ending | ends calm | last beat becomes the cliff, even just a whistle |
87
+
88
+ ## Polish (`render dialogue --ep N`)
89
+
90
+ When the user asks for a polish, when the dialogue is the visible weakness, or when the register
91
+ gate keeps noting the same scene: `render dialogue --ep 1` prints the 逐句校对表 (原句 / 问题 / 修改句 /
92
+ 修改理由 / 导演/演员提示) with each speaker's speech rules in the last column. Fill it per
93
+ `dialogue-craft.md` §3, apply the changes to the flow, re-validate, and append the filled table to
94
+ the screenplay dataset under `## 对白校对`.
95
+
96
+ ## Sync
97
+
98
+ ```bash
99
+ node {baseDir}/scripts/short-drama.mjs validate <project> --stage script --ep 1
100
+ node {baseDir}/scripts/short-drama.mjs render <project> script --ep 1 > /tmp/E01-script.md # 短剧 episode format + 创作自检
101
+ wji dataset new <story folder> "E01 剧本" -t screenplay -d "<hook>" # bind --ep 1 wji.screenplayDatasetId
102
+ wji fs set <id> --merge content=@/tmp/E01-script.md --merge viewMode=content --merge metadata.shortDrama=@/tmp/E01.json
103
+ ```
104
+
105
+ ## Gates
106
+
107
+ `script-shape` · `beat-flow` · `duration` · `hook-cliff` · `beats-claimed` · `script-design` ·
108
+ `rhythm` · `filmable` · `dialogue-register` (advisory).
109
+
110
+ `render script` writes the reference's episode format — `△(from-to秒)` action lines with the
111
+ seconds derived, `角色(delivery):line`, `【爽点标记】` after the 爽点, `【钩子标记】` at the end — and
112
+ closes with the 创作自检 table, eight questions answered from the same helpers the gates use. A
113
+ green episode is 16/16; a row that says 否 names what to fix.