@wji.ai/cli 0.1.1 → 0.3.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.
- package/README.md +10 -1
- package/dist/commands/canvas.js +91 -12
- package/dist/commands/fsnode.js +30 -1
- package/dist/commands/workspace.js +6 -2
- package/package.json +1 -1
- package/skill/short-drama/SKILL.md +322 -0
- package/skill/short-drama/examples/night-shift/episodes/E01.json +1883 -0
- package/skill/short-drama/examples/night-shift/project.json +774 -0
- package/skill/short-drama/reference/canvas-recipes.md +219 -0
- package/skill/short-drama/reference/craft-motion.md +116 -0
- package/skill/short-drama/reference/craft-shots.md +177 -0
- package/skill/short-drama/reference/craft-story.md +188 -0
- package/skill/short-drama/reference/dialogue-craft.md +120 -0
- package/skill/short-drama/reference/operations-cli.md +43 -0
- package/skill/short-drama/reference/operations-wji.md +82 -0
- package/skill/short-drama/reference/schema.md +435 -0
- package/skill/short-drama/reference/stage-art.md +57 -0
- package/skill/short-drama/reference/stage-characters.md +206 -0
- package/skill/short-drama/reference/stage-outline.md +103 -0
- package/skill/short-drama/reference/stage-script.md +113 -0
- package/skill/short-drama/reference/stage-storyboard.md +468 -0
- package/skill/short-drama/reference/workspace-layout.md +130 -0
- package/skill/short-drama/scripts/selftest.mjs +1415 -0
- package/skill/short-drama/scripts/short-drama.mjs +3387 -0
- package/skill/wji/SKILL.md +33 -15
- package/skill/wji/reference/commands.md +4 -3
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
# Project schema
|
|
2
|
+
|
|
3
|
+
A project is a directory:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
<project>/
|
|
7
|
+
├── project.json title, params, outline, characters, scenes, props, WJI bindings
|
|
8
|
+
└── episodes/
|
|
9
|
+
├── E01.json script + storyboard of episode 1 (+ its WJI bindings)
|
|
10
|
+
└── E02.json …
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`project.json` is the machine-checked source of truth; WJI datasets and canvases
|
|
14
|
+
are the published store and work area. Every WJI id the pipeline creates is
|
|
15
|
+
written back into the `wji` blocks so a run can resume. **The model fills the
|
|
16
|
+
design fields; the script (`scripts/short-drama.mjs`) fills or derives the
|
|
17
|
+
rest** — target seconds, beat timing, prompt skeletons, bindings.
|
|
18
|
+
|
|
19
|
+
All commands accept either the directory or the `project.json` path.
|
|
20
|
+
|
|
21
|
+
## project.json
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"version": 1,
|
|
26
|
+
"title": "夜班便利店",
|
|
27
|
+
"lang": "zh",
|
|
28
|
+
"params": {
|
|
29
|
+
"episodes": 3, "minutesPerEpisode": 1, "genre": "都市悬疑", "adaptMode": "原创",
|
|
30
|
+
"ratio": "9:16", "video": "h3", "videoResolution": "768P", "speechAccent": "", "style": "realistic", "register": "大陆普通话",
|
|
31
|
+
"references": "sheets", "videoDuration": "auto", "lookPhrase": "",
|
|
32
|
+
"camera": { "body": "ARRI ALEXA Mini LF", "lens": "Zeiss Supreme Prime", "aperture": "T1.5" },
|
|
33
|
+
"charsPerSecond": 4.5, "actionSeconds": 2.5, "tolerance": 0.15,
|
|
34
|
+
"maxLineChars": 35, "hookWindow": 3,
|
|
35
|
+
"minSegmentSeconds": 5, "maxSegmentSeconds": 15, "minCutSeconds": 2, "maxCutSeconds": 5, "maxOnScreen": 3,
|
|
36
|
+
"thresholds": { "maxLeads": 5, "maxSupport": 10, "maxFunctional": 10, "maxBeatGap": 3, "maxProps": 8 }
|
|
37
|
+
},
|
|
38
|
+
"source": { "kind": "original", "path": "", "note": "" },
|
|
39
|
+
"outline": { "core": "…", "brief": {}, "structure": [], "guideline": {}, "keep": [], "cut": [], "merge": [], "risks": [], "beats": [], "episodes": [] },
|
|
40
|
+
"characters": [],
|
|
41
|
+
"scenes": [],
|
|
42
|
+
"props": [],
|
|
43
|
+
"wji": { "workspaceId": "", "datasetFolders": {}, "canvases": { "episodes": {} } }
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### params
|
|
48
|
+
|
|
49
|
+
| Field | Default | Meaning |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| `episodes` | required | total episode count; `outline.episodes` must have exactly this many |
|
|
52
|
+
| `minutesPerEpisode` | required | target length; `targetSeconds = minutes × 60` |
|
|
53
|
+
| `genre` | required | decides the beat (爽点) types |
|
|
54
|
+
| `adaptMode` | `原创` | `原创` / `忠实` / `抽核` / `借壳`. Anything but `原创`/`忠实` requires a non-empty `outline.cut` |
|
|
55
|
+
| `ratio` | `9:16` | keyframe and video ratio; sheets are always 16:9 |
|
|
56
|
+
| `video` | `h3` | one of `h3` / `seedance` / `seedance2` / `seedance2fast` / `wan` — see the model table below. **It decides the segment length, so it decides how the plot is cut up.** |
|
|
57
|
+
| `videoResolution` | `720P` | passed to the video node as `--resolution`; it drives the price per second. The tiers are **per model** and so is the spelling — `validate` rejects one the model does not have (h3 has 768P and no 720P; the Seedance models are lowercase). `init` writes the nearest tier the chosen model has, in that model's spelling, so h3 projects start at `768P` |
|
|
58
|
+
| `speechAccent` | empty | e.g. `台湾口音国语` / `Taiwanese-accented Mandarin`. Any segment with dialogue must name it in its prompt (the video model speaks the lines) |
|
|
59
|
+
| `references` | `sheets` | `sheets` (**the default — no storyboard images**): the video node references the scene / character / prop sheets directly (`segmentReferences` order: environment, people, props) and the prompt describes each shot. `keyframes`: one generated image per cut, the video node references them in cut order — more images to pay for and approve, worth it when a shot has to be composed exactly. Works with every mode; H3 in sheets mode opens with an `Image k is …` legend instead of the alignment line |
|
|
60
|
+
| `humanReferences` | `true` | `false` drops character sheets from the video node's references (people are described in the prompt's Cast line instead). Any Seedance mode + `photoreal` requires it to be set explicitly: BytePlus refuses photorealistic people as reference images (`input image may contain real person`, HTTP 400) unless the account has the real-person permission enabled |
|
|
61
|
+
| `videoDuration` | `auto` | `auto` (**the default**) passes no `-d` and lets the model's `autoDuration` match the clip to the prompt; the cut seconds are then the *planned* timing. `fixed` passes `-d` = Σ cut seconds, and is what `init`/`paramsOf` fall back to on a model with no `autoDuration` parameter (today: `minimax-h3-ref`). Asking for `auto` on such a model is a gate failure, not a silent downgrade |
|
|
62
|
+
| `camera` | ARRI ALEXA Mini LF / Zeiss Supreme Prime / T1.5 | the production's camera package: `{ body, lens, aperture }`. Every cut's `frame` names it verbatim, so the whole drama looks shot on one rig. A cut may override `focal` and `aperture` |
|
|
63
|
+
| `videoReferences` | `none` | `previous` hands each segment the clip before it as `@video1` (continuity of grade, wardrobe and camera language); `none` leaves the sheets to carry it. Opt-in, because it chains the segments: regenerating one invalidates the reference of every segment after it |
|
|
64
|
+
| `maxVideoRefs` / `maxAudioRefs` | per mode | how many clip / voice references the model takes; set them lower to tighten, never higher |
|
|
65
|
+
| `lookPhraseZh` | empty | the same look phrase in the drama's language, gated in a **Chinese** prompt (`wan`) where the English `lookPhrase` would be both out of place and extra length for the content filter to judge |
|
|
66
|
+
| `speechLeadSeconds` | 0.5 | added to a cut's dialogue seconds before the fit check — the mouth opens after the cut starts; also where a line starts in the `full` soundtrack prompt |
|
|
67
|
+
| `audioTrack` | `bed` | the Seed Audio node per segment: `bed` (ambience + sfx + timed music, no voices), `full` (the bed plus every line in the connected `@Audio1..3` voices — at most three speakers), `none` |
|
|
68
|
+
| `subtitles` | false | `true` drops the subtitles-off sentence requirement from the prompts |
|
|
69
|
+
| `maxFrameChars` | 900 | frames longer than this are flagged as padding (advisory) |
|
|
70
|
+
| `lookPhrase` | empty | a lens / look phrase (e.g. `wide-aperture lens, shallow depth of field, creamy background bokeh and soft out-of-focus foreground elements`) that every segment prompt must carry verbatim |
|
|
71
|
+
| `style` | `realistic` | `photoreal` (live action), `realistic` (semi-realistic painterly) or `ghibli`; one style for the whole drama (see stage-characters) |
|
|
72
|
+
| `register` | required | the 语体 the lines are written in: `粤语` / `大陆普通话` / `台湾国语` / `港式普通话` / `古装` / `年代` / `英文美式` / `英文英式` (traditional spellings accepted). When `speechAccent` is empty it is derived from this in the prompt's language (`standard Mandarin` / `标准普通话`). A character may override it in `bible.speech.register`. The `dialogue-register` gate is **advisory** — it notes lines that break the register and never fails `validate` |
|
|
73
|
+
| `hookSeconds` | 15 | the hook image (`hookBeat`) must be on screen by this cumulative second |
|
|
74
|
+
| `turnSeconds` | 30 | the longest stretch allowed between turn points — the hook, every beat marked `turn: true`, the 爽点 and the cliff |
|
|
75
|
+
| `maxConsecutiveLines` | 3 | line beats in a row without an action beat between them |
|
|
76
|
+
| `minActionRatio` | 0.35 | action beats ÷ line beats over an episode with ≥ 5 lines (谈话+行动) |
|
|
77
|
+
| `maxLineWords` / `maxNameCallsPerScene` | 20 / 2 | English registers only: words per line, and how often another character is addressed by name in one scene (advisory) |
|
|
78
|
+
| `charsPerSecond` / `actionSeconds` | 4.5 / 2.5 | script duration math: a line takes `nonWhitespaceChars ÷ charsPerSecond` s, an action beat takes `actionSeconds` |
|
|
79
|
+
| `tolerance` | 0.15 | ±15 % for episode duration gates |
|
|
80
|
+
| `maxLineChars` | 35 | one line must be sayable in one breath |
|
|
81
|
+
| `hookWindow` | 3 | the hook's concrete image must land within the first N beats of the episode |
|
|
82
|
+
| `min` / `maxSegmentSeconds` | per mode | the duration range of one video generation, taken from `params.video` when not set. **Every ref model takes whole seconds only**, so in `fixed` mode a segment's cuts must sum to an integer. Setting them by hand cannot widen the model's own range |
|
|
83
|
+
|
|
84
|
+
| `minCutSeconds` / `maxCutSeconds` | 2 / 5 | cut length band |
|
|
85
|
+
| `maxOnScreen` | 3 | more people in one cut needs a `note` explaining the staging |
|
|
86
|
+
| `thresholds.maxPrimaryScenes` | derived | `4 + ⌈episodes/10⌉` clamped to 5–15; set explicitly to override |
|
|
87
|
+
|
|
88
|
+
**Video models.** Read off the backend model forms; `VIDEO_MODELS` in `scripts/short-drama.mjs` is
|
|
89
|
+
the single source and every gate reads it:
|
|
90
|
+
|
|
91
|
+
| `video` | model | segment | images | clips | voices | auto duration | resolutions | prompt |
|
|
92
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
93
|
+
| `h3` | `minimax-h3-ref` | 5–15 s | 9 | 3 (≤ 15 s) | 3 | ✗ | `480P` `768P` `2K` `4K` | English, keyframe-pinned |
|
|
94
|
+
| `seedance` | `seedance-2.5-ref` | 4–30 s | 30 | 10 (≤ 30 s) | 10 | ✓ | `480p` `720p` `1080p` | English, `@imageN` |
|
|
95
|
+
| `seedance2` | `seedance-2.0-ref` | 4–15 s | 9 | 3 (≤ 15 s) | 3 | ✓ | `480p` `720p` `1080p` `4k` | English, `@imageN` |
|
|
96
|
+
| `seedance2fast` | `seedance-2.0-fast-ref` | 4–15 s | 9 | 3 (≤ 15 s) | 3 | ✓ | `480p` `720p` | English, `@imageN` |
|
|
97
|
+
| `wan` | `wan-3.0-ref` | 2–30 s | 10 | 5 (≤ 15 s) | 5 | ✓ | `480P` `720P` `1080P` | Chinese prose, `@imageN`, speaks the dialogue |
|
|
98
|
+
|
|
99
|
+
The three Seedance entries share one prompt dialect (`family: 'seedance'`), so 2.0 and 2.0 Fast are
|
|
100
|
+
written exactly like 2.5 — they differ only in the ceiling (15 s, not 30) and the reference cap
|
|
101
|
+
(9, not 30)
|
|
102
|
+
|
|
103
|
+
### outline
|
|
104
|
+
|
|
105
|
+
| Field | Required | Notes |
|
|
106
|
+
| --- | --- | --- |
|
|
107
|
+
| `core` | yes | one sentence |
|
|
108
|
+
| `keep[]` | ≥1 unless `adaptMode` is `原创` | `{what, why, evidence?}` — `evidence` is a verbatim quote from the source |
|
|
109
|
+
| `cut[]` | non-empty when `adaptMode` ∉ {`原创`,`忠实`} | `{what, why}` |
|
|
110
|
+
| `merge[]`, `risks[]` | no | `{what, why}` / `{what, plan}` |
|
|
111
|
+
| `cutNote`, `mergeNote` | no | decision summaries shown to the user at sign-off |
|
|
112
|
+
| `brief` | yes | the 创作简报 — `{logline, audience, satisfactionEngine, mainHook, protagonistPressure, antagonistPressure, emotionalLine, promise}`, drama language, no quotes; `satisfactionEngine` names one of `打脸 / 复仇 / 强宠 / 身份揭露 / 智商碾压 / 情感释放` (and may say how it varies) |
|
|
113
|
+
| `structure[]` | yes | the three phases `{phase: "golden"|"escalation"|"climax", episodes: [from, to], task, satisfactionPlan, hookPlan}` in that order, tiling `1..episodes`; golden ends within the first ~20 % (and never before episode 3 when there are ≥ 6). Two or fewer episodes: golden + climax only |
|
|
114
|
+
| `guideline` | yes | the 创作指引 — `{tone, emotionalIntensity, dialogueStyle, visualStyle, productionLimits, forbidden[] (≥3), priorities[] (3–5)}`; `render guideline` publishes it as its own dataset and it is re-read before every stage |
|
|
115
|
+
| `beats[]` | ≥1 | `{id:"B01", type, weight:"major"|"minor", episode, setup, payoff}`; `episode` is the only place a beat's position lives |
|
|
116
|
+
| `episodes[]` | exactly `params.episodes` | `{ep, function[], task: {goal, obstacle, stake}, change, satisfaction: {type, action}, hookType, synopsis, hook, suspense, sceneIds[], characterIds[], propIds[], crowdPlan?, warnings[]}` — narrative prose, **no dialogue quotes** (`「」『』“”`). `function` ⊆ `开局钩子 / 冲突升级 / 身份揭露 / 情感推进 / 大反转 / 清算收束`; `satisfaction.type` ∈ the six 爽点 types and `action` is the visible action that does it; `hookType` ∈ `身份 / 危机 / 结果 / 情感 / 反转 / 道具`. Episode 1 includes 开局钩子; with ≥ 3 episodes, episode 2's 爽点 type is the brief's engine and episode 3 includes 冲突升级 / 身份揭露 / 大反转; the finale includes 清算收束 or 大反转 |
|
|
117
|
+
|
|
118
|
+
`warnings` values: `雨戏`, `肢体接触`, `人群`, `手部特写` (generation hazards).
|
|
119
|
+
|
|
120
|
+
### characters[]
|
|
121
|
+
|
|
122
|
+
Outline stage fills the first block; the characters stage fills the rest.
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"id": "C01", "name": "林小夏", "aliases": ["小夏"],
|
|
127
|
+
"tier": "lead", "role": "女主·夜班店员", "arc": "从躲着不问到主动追问", "from": ["原创"],
|
|
128
|
+
|
|
129
|
+
"oneLiner": "夜班便利店店员,眼神总在监控屏和门口之间来回。",
|
|
130
|
+
"identityEn": "the young woman in the faded navy jacket",
|
|
131
|
+
"profile": {
|
|
132
|
+
"gender": "女", "ageRange": "24 岁左右", "identity": "便利店夜班店员",
|
|
133
|
+
"appearance": "…", "personality": ["警觉", "嘴硬", "心软"], "temperament": "…",
|
|
134
|
+
"motivation": "…", "relationships": [{ "id": "C02", "name": "老陈", "surface": "夜班常客", "real": "…", "power": "…", "secret": "…", "turnEpisode": 2 }], "evidence": []
|
|
135
|
+
},
|
|
136
|
+
"image": { "prompt": "…", "negativePrompt": "…", "sheet": "…", "tags": [] },
|
|
137
|
+
"voice": { "timbre": "…", "pitch": "…", "pace": "…", "accent": "…", "emotion": "…", "prompt": "…",
|
|
138
|
+
"videoDirection": "quiet and slightly fast, clipping the ends of phrases, flat and guarded",
|
|
139
|
+
"sample": { "line": "…", "direction": "…" } },
|
|
140
|
+
"bible": {
|
|
141
|
+
"function": "主角", "hookLine": "身份 + 核心矛盾 + 外在欲望 + 风险", "want": "…", "need": "…", "misbelief": "…", "contradiction": "…",
|
|
142
|
+
"wound": "150–250 字", "pastChoices": [{ "when": "…", "pressure": "…", "choice": "…", "gained": "…", "lost": "…", "residue": "…" }],
|
|
143
|
+
"firstImpression": "…", "finalUnderstanding": "…", "pressureResponse": "…",
|
|
144
|
+
"speech": { "rhythm": "…", "catchphrase": "", "silence": "…", "forbiddenWords": [], "register": "" },
|
|
145
|
+
"forbidden": ["…", "…", "…"], "mustFace": ["…", "…", "…"],
|
|
146
|
+
"arc": [{ "stage": "开场", "episode": 1, "state": "…", "change": "…" }]
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
"wji": { "datasetId": "", "nodes": { "title": "", "headshot": "", "sheet": "", "voice": "" },
|
|
150
|
+
"assets": { "headshot": "", "sheet": "", "voice": "" } }
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
| Field | Language | Rules |
|
|
155
|
+
| --- | --- | --- |
|
|
156
|
+
| `tier` | — | `lead` (1–5, `arc` required) / `support` (≤10, `arc` required) / `functional` (≤10, `name` is a role label such as 急诊医生, `arc` optional) |
|
|
157
|
+
| `from[]` | — | change record: source character, merges, or `["原创"]` |
|
|
158
|
+
| `oneLiner`, `profile.*`, `voice.timbre…emotion` | `lang` | human-readable |
|
|
159
|
+
| `profile.evidence[]` | source | verbatim quotes, never translated |
|
|
160
|
+
| `image.prompt` | **English** | portrait prompt; **no names or aliases**; must state ethnicity and era |
|
|
161
|
+
| `image.negativePrompt` | English | style-dependent, see stage-characters |
|
|
162
|
+
| `image.sheet` | English | 16:9 three-zone sheet prompt; no names; contains the style's render phrase |
|
|
163
|
+
| `voice.prompt` | English | ≤400 characters, a static voice identity, **no quoted lines** |
|
|
164
|
+
| `voice.videoDirection` | **English** | ≤160 characters, one clause: how they speak (pace, where the voice sits, what it does at the end of a phrase, accent). It is pasted beside **every line this character says** in a video prompt, and gated there — compress `voice.prompt`, do not repeat it |
|
|
165
|
+
| `identityEn` | **English** | ≤80 characters, the name-free way a prompt refers to this person (`the woman in her mid thirties in headphones`). Distinct per character; no names. Used to attribute dialogue in `keyframes` mode and anywhere a `@imageK` label is not available |
|
|
166
|
+
| `bible` | `lang` | the 人物圣经 (gate `bible`), required by tier — **functional**: `function` (∈ `主角 / 共同主角 / 反派 / 爱情线 / 导师 / 盟友 / 镜像 / 压力测试者 / 喜剧调节`) only; **support**: also `hookLine`, `want`, `need` (≠ want), `misbelief`, `contradiction`, `pressureResponse`, `speech.rhythm`, `speech.silence`; **lead**: also `wound`, `firstImpression`, `finalUnderstanding`, `pastChoices` (≥ 2, each with when / pressure / choice / gained / lost / residue), exactly three `forbidden` and three `mustFace`, `arc` (≥ 3 stages from `开场 / 第一转折 / 中段 / 低谷 / 高潮选择 / 结局`, episodes in range, `高潮选择` present). `speech.register` overrides `params.register` for this character |
|
|
167
|
+
| `profile.relationships[]` | `lang` | `{id, name, surface, real, power, secret, turnEpisode}` (gate `relationships`): every lead has one whose `id` resolves with `surface` + `real` + `power`; at least one relationship in the cast carries a `secret`. The old `{name, relation}` shape is still read (`relation` = surface) |
|
|
168
|
+
|
|
169
|
+
### scenes[]
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"id": "S01", "name": "便利店店内", "primary": true, "reusePlan": "",
|
|
174
|
+
"summary": "设计意图…", "anchors": [{ "name": "闪烁的关东煮灯箱", "desc": "…" }],
|
|
175
|
+
"lighting": [{ "state": "凌晨冷白光", "prompt": "…" }],
|
|
176
|
+
"image": { "prompt": "…", "negativePrompt": "…", "sheet": "…", "tags": [] },
|
|
177
|
+
"variantOf": "", "changes": "",
|
|
178
|
+
"wji": { "datasetId": "", "nodes": { "sheet": "", "lighting": {} }, "assets": { "sheet": "", "lighting": {} } }
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Outline stage: `id`, `name`, `primary`, `reusePlan` (required for a scene used in
|
|
183
|
+
only one episode). Art stage: `summary` (design intent), `anchors` (3–5,
|
|
184
|
+
paintable and checkable), `lighting` (≥1, English prompts, derived from the
|
|
185
|
+
episodes it appears in), `image.*` (English, **empty scene — negative prompt
|
|
186
|
+
must ban people**), `variantOf` + `changes` for variants of a parent scene.
|
|
187
|
+
`wji.nodes.lighting` / `wji.assets.lighting` are keyed by lighting `state`.
|
|
188
|
+
|
|
189
|
+
### props[]
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"id": "P01", "name": "旧监控硬盘", "function": "全剧悬念核心:里面到底录了什么",
|
|
194
|
+
"beatIds": ["B02"], "scale": "handheld", "summary": "…",
|
|
195
|
+
"anchors": [{ "name": "…", "desc": "…" }], "states": [{ "state": "封在防静电袋里", "prompt": "…" }],
|
|
196
|
+
"relatedScenes": ["S01"], "carriedBy": ["林小夏"],
|
|
197
|
+
"image": { "prompt": "…", "negativePrompt": "…", "sheet": "…", "tags": [] },
|
|
198
|
+
"wji": { "datasetId": "", "nodes": { "sheet": "", "states": {} }, "assets": { "sheet": "", "states": {} } }
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Outline stage: `id`, `name`, `function` (what it carries in the story —
|
|
203
|
+
if you cannot fill it, it is set dressing, not a prop), `beatIds`. Art stage:
|
|
204
|
+
`scale` (`handheld` / `tabletop` / `furniture` — the matching phrase
|
|
205
|
+
`handheld scale` etc. must appear in `image.prompt` and `image.sheet`),
|
|
206
|
+
`anchors` 3–5, `states` ≥1, `image.*` (**white background, no people, negative
|
|
207
|
+
prompt bans hands**). At most `thresholds.maxProps` (8).
|
|
208
|
+
|
|
209
|
+
### wji (project level)
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"endpoint": "https://api.wji.ai", "workspaceId": "…",
|
|
214
|
+
"datasetFolders": { "story": "…", "character": "…", "location": "…", "prop": "…", "shot": "…" },
|
|
215
|
+
"canvases": { "characters": "…", "scenes": "…", "props": "…", "episodes": { "1": "…" } },
|
|
216
|
+
"outlineDatasetId": "…", "guidelineDatasetId": "…"
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## episodes/E<nn>.json
|
|
221
|
+
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"ep": 1, "targetSeconds": 60,
|
|
225
|
+
"function": ["开局钩子"], "task": { "goal": "…", "obstacle": "…", "stake": "…" }, "change": "…", "hookType": "身份",
|
|
226
|
+
"hook": "…", "cliff": "…", "beatsClaimed": ["B01"], "hookBeat": [1, 1], "satisfactionBeat": [2, 9],
|
|
227
|
+
"scenes": [
|
|
228
|
+
{ "sceneId": "S01", "lighting": "凌晨冷白光", "characters": ["C01", "C02"], "props": ["P01"],
|
|
229
|
+
"flow": [
|
|
230
|
+
{ "action": "林小夏抱着纸箱推开员工通道的门,箱子一角磕在货架上。", "turn": true },
|
|
231
|
+
{ "speaker": "C02", "line": "又是你值夜班。", "delivery": "没抬头,手里翻着报纸" }
|
|
232
|
+
] }
|
|
233
|
+
],
|
|
234
|
+
"storyboard": {
|
|
235
|
+
"segments": [
|
|
236
|
+
{ "id": "E01-01", "sceneIndex": 1,
|
|
237
|
+
"cuts": [
|
|
238
|
+
{ "beats": [1, 1], "seconds": 3, "size": "medium", "camera": "Tracking Shot",
|
|
239
|
+
"focal": "40mm",
|
|
240
|
+
"light": { "direction": "back-left", "note": "the store light rims her shoulder; the street stays unlit" },
|
|
241
|
+
"foreground": "the dripping edge of the striped awning",
|
|
242
|
+
"characters": ["C01"], "props": ["P01"], "frame": "medium shot of …, cinematic film still", "note": "" }
|
|
243
|
+
],
|
|
244
|
+
"prompt": "…",
|
|
245
|
+
"wji": { "datasetId": "", "nodes": { "keyframes": [], "video": "", "track": "", "prompt": "" },
|
|
246
|
+
"assets": { "keyframes": [], "video": "" } } }
|
|
247
|
+
]
|
|
248
|
+
},
|
|
249
|
+
"wji": { "screenplayDatasetId": "", "canvasId": "", "shotFolderId": "", "displayNodes": {} }
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Script (filled by the script stage)
|
|
254
|
+
|
|
255
|
+
| Field | Rules |
|
|
256
|
+
| --- | --- |
|
|
257
|
+
| `targetSeconds` | seeded from `params`; the episode estimate must be within `± tolerance` |
|
|
258
|
+
| `hook` / `cliff` | descriptions of the opening hook and closing suspense, not lines |
|
|
259
|
+
| `beatsClaimed[]` | outline beat ids this episode realises; must equal the beats whose `episode` is this one |
|
|
260
|
+
| `hookBeat` | `[sceneIndex, beatIndex]` (1-based) where the hook's concrete image lands — within the first `hookWindow` beats of the episode and by `hookSeconds` of cumulative time |
|
|
261
|
+
| `function` / `task` / `change` / `hookType` | seeded from the outline episode and gated equal to it (`script-design`) |
|
|
262
|
+
| `satisfactionBeat` | `[sceneIndex, beatIndex]` of the 爽点 — an action beat, or a line with an action beat beside it; after the hook, before the last beat |
|
|
263
|
+
| `flow[].turn` | `true` on a beat that brings new information, an obstacle, a counter or a choice; the `rhythm` gate measures hook → turns → 爽点 → cliff against `turnSeconds` |
|
|
264
|
+
| `scenes[].sceneId` / `lighting` / `characters` / `props` | must exist in `project.json`; `lighting` must be a state of that scene |
|
|
265
|
+
| `flow[]` | alternating **action beats** `{action}` (narrative, one thing per beat, **common actions only**, no quotes) and **line beats** `{speaker, line, delivery?}`; `speaker` ∈ scene `characters` or `VO`; `line` ≤ `maxLineChars` non-whitespace characters |
|
|
266
|
+
|
|
267
|
+
Duration: `Σ lines (chars ÷ charsPerSecond) + Σ actions × actionSeconds`.
|
|
268
|
+
|
|
269
|
+
### Storyboard (filled by the storyboard stage)
|
|
270
|
+
|
|
271
|
+
| Field | Rules |
|
|
272
|
+
| --- | --- |
|
|
273
|
+
| `segments[].id` | `E<ep>-<nn>`, sequential in file order; also the name of the shot dataset and canvas group |
|
|
274
|
+
| `sceneIndex` | 1-based index into `scenes[]`; a segment never crosses scenes |
|
|
275
|
+
| `cuts[].beats` | `[from, to]` inclusive, 1-based within that scene's `flow`; every beat of every scene is claimed exactly once, in order |
|
|
276
|
+
| `cuts[].seconds` | `minCutSeconds`–`maxCutSeconds`; ≥ the dialogue seconds of the claimed beats; `minSegmentSeconds ≤ Σ cuts ≤ maxSegmentSeconds` — **the model's range**, and a whole number in `fixed` duration mode |
|
|
277
|
+
| `size` | `extreme-wide` / `wide` / `medium` / `close` / `extreme-close`; its English phrase must be in `frame` |
|
|
278
|
+
| `camera` | one of the H3 vocabulary: `Static Shot` `Push In` `Pull Out` `Zoom In` `Zoom Out` `Pan Left` `Pan Right` `Truck Left` `Truck Right` `Tilt Up` `Tilt Down` `Pedestal Up` `Pedestal Down` `Arc Shot` `Tracking Shot` `Shake Slightly` `Shake Strongly` `POV` `Roll Clockwise` `Roll Counterclockwise` |
|
|
279
|
+
| `characters` / `props` | ⊆ the scene's; `characters.length > maxOnScreen` needs `note` |
|
|
280
|
+
| `focal` / `aperture` | optional per-cut overrides of the lens; left out, `FOCAL_BY_SIZE` picks the focal from the shot size (extreme-wide 18mm, wide 25mm, medium 40mm, close 75mm, extreme-close 100mm macro) and `params.camera.aperture` the stop |
|
|
281
|
+
| `light` | `{ direction, note }`. `direction` ∈ `frontal` `front-left` `front-right` `side-left` `side-right` `back-left` `back-right` `backlight` `top` `underlight` `ambient` — **relative to the camera, not the room**; `note` says what the key picks out, what fills the shadow side and how it changes over the cut. Both required |
|
|
282
|
+
| `foreground` | what sits between the lens and the subject, to be thrown out of focus. `""` is a deliberately clean frame; at least half the cuts of a segment must have one |
|
|
283
|
+
| `background` | optional — the third depth layer behind the subject; expected (or a `note`) on a 9:16 wide |
|
|
284
|
+
| `purpose` | `{ kind, change }` — `kind` ∈ `establish / reveal / evidence / reaction / pressure / decision / consequence / performance / transition`; `change` says what the audience knows or who holds power when the cut ends (gate `shot-purpose`) |
|
|
285
|
+
| `angle` / `height` / `axisSide` | `front / three-quarter / profile / rear-three-quarter / back` · `ground / low / eye / high / top` · `working / cross / neutral` (`cross` needs `note`) — gate `framing-axis` |
|
|
286
|
+
| `start` / `end` | boundaries `{ positions: {C01: …}, facing: {C01: screen-left\|screen-right\|camera\|away}, gaze, hands, heldProps: {C01: [P01]}, state: [] }` for every person on screen; `start` only on a segment's first cut (later cuts inherit the previous `end`); a prop changes hands only on an action beat; `state` items that exist only in `end` may not appear in `frame` — gate `continuity` |
|
|
287
|
+
| `motion` | `{ trigger, action, result }` — cue, one visible state chain, landing (or `hold`); English unless the prompt language is Chinese; no names, no inferred states, ≤ 3 hand verbs — gate `motion` |
|
|
288
|
+
| `cameraNote` | `{ motivation, trigger, path, endFraming }` on any move (`motivation` ∈ `reveal / pressure / alignment / relationship / transition / stillness`), `{ hold }` on a `Static Shot` — gate `motion` |
|
|
289
|
+
| `frame` | English prompt for the moment: composition, this moment's positions and poses, the size phrase, the style's `framePhrase` (`photorealistic cinematic film still` / `cinematic film still` / `hand-painted anime film still`), **and verbatim** the camera phrase (`shot on an ARRI ALEXA Mini LF with a Zeiss Supreme Prime 75mm at T1.5`), the light-direction phrase and the foreground; **no names** — identity comes from the sheet references |
|
|
290
|
+
| `prompt` | the video prompt, shape by `params.video` (below); gate `prompt-hygiene` (no `…`, no ids / paths / notes, subtitles off unless `params.subtitles`, H3 lips-closed clause, sheets-mode `@imageK` per shot) |
|
|
291
|
+
| `segments[].axis` | `{ between: [C01, C02], workingSide, screenLeft, screenRight }`, required when two or more people appear in the segment |
|
|
292
|
+
| `segments[].sound` | `{ ambience, sfx: [{ cut, event }], music }` — ambience identical across a scene's segments; `music` is `N/A`, an intent string, or `{ intent, cues: [{ at: <cut number \| "10.5s">, do: enter \| swell \| duck \| drop \| silence \| out, note }] }` — gate `sound`. `prompt audio E01-01` derives the segment's Seed Audio soundtrack prompt from it (timecoded `[start s:end s]` ambience / sfx / music, plus the lines with `--full`) — gate `audio-track` |
|
|
293
|
+
| `storyboard.visualPlans[]` | `{ sceneIndex, turn, alignment, pressure, progression, rhythm, landing, sound }` for the 爽点 scene and the cliff scene (seeded) — gate `visual-plan` |
|
|
294
|
+
| `props[].textPolicy` | optional `{ mode: readable \| symbolic \| blank, text }` — gate `text-policy` |
|
|
295
|
+
|
|
296
|
+
`seed storyboard` also writes a **`plan`** block — the segmentation the chosen model forces, before
|
|
297
|
+
any prose is written:
|
|
298
|
+
|
|
299
|
+
```json
|
|
300
|
+
"plan": { "model": "minimax-h3-ref", "segmentSeconds": [5, 15], "wholeSecondsOnly": true,
|
|
301
|
+
"duration": "fixed", "maxReferences": 9, "episodeSeconds": 90, "segments": 7,
|
|
302
|
+
"scenes": [ { "sceneIndex": 1, "sceneId": "S01", "seconds": 28.5,
|
|
303
|
+
"segments": 2, "secondsPerSegment": 14.25, "note": "" } ] }
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
`segments` per scene is `⌈scene seconds ÷ maxSegmentSeconds⌉` — the fewest generations the model
|
|
307
|
+
allows — and the seconds are then spread evenly so none falls under the floor; a scene too short
|
|
308
|
+
for the floor gets a `note` telling you to merge or lengthen it. Swapping `params.video` changes
|
|
309
|
+
this and nothing else about the script, which is the point: the same 90-second episode is 7
|
|
310
|
+
segments on a 15-second model and 3 on a 30-second one. Delete `plan` with `seedScenes` once the
|
|
311
|
+
segments are written.
|
|
312
|
+
|
|
313
|
+
**References on the video node.** WJI numbers connected references **per type, in connection
|
|
314
|
+
order**, and the prompt addresses them by those labels:
|
|
315
|
+
|
|
316
|
+
- `@image1…N` — the keyframes in cut order, or in `sheets` mode the environment sheet, the lighting
|
|
317
|
+
state, one sheet per person and one per prop, in that order.
|
|
318
|
+
- `@video1…` — with `params.videoReferences: "previous"`, the **clip of the segment before this
|
|
319
|
+
one**, for continuity of grade, wardrobe and camera language. Off by default. Capped by
|
|
320
|
+
`params.maxVideoRefs`, and the referenced clip may itself be no longer than the model's
|
|
321
|
+
`maxRefVideoSeconds`.
|
|
322
|
+
- `@audio1…M` — the **voice sample of every character who speaks in that segment**, in speaking
|
|
323
|
+
order. Their own series: a voice connected after three sheets is `@audio1`, not `@image4`.
|
|
324
|
+
`params.voiceSamples: false` drops them; `params.maxAudioRefs` (default: the model's) caps them.
|
|
325
|
+
|
|
326
|
+
Each label is then used wherever it applies, and all of it is gated: `@imageK` **in the shot
|
|
327
|
+
paragraph that anchors on it** (keyframe modes) or somewhere in the prompt (sheets mode), `@videoK`
|
|
328
|
+
in the sentence saying what to carry over, `@audioK` **beside every line that character speaks**.
|
|
329
|
+
`segmentReferences()` builds the list; `prompt segment` prints it in connection order — pictures,
|
|
330
|
+
then clips, then voices — which is the order the video node must be `--from`'d.
|
|
331
|
+
|
|
332
|
+
**H3 prompt** (`params.video = "h3"`), checked verbatim where stated:
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
How the reference pictures align with the target video — @image1 (from Shot 1) aligns with the 0.00-second mark of the target video; @image2 (from Shot 2) aligns with the 3.00-second mark of the target video.
|
|
336
|
+
← derived from cut seconds; single-cut segments use:
|
|
337
|
+
For the target video, at 0.00 seconds into the target video, @image1 (from [Shot 1]) is fully referenced.
|
|
338
|
+
|
|
339
|
+
integrated_multimodal_description:
|
|
340
|
+
[Shot 1] … (anchor on @image1, then action, the camera word, the lens phrase, the light direction and
|
|
341
|
+
what the light does, the foreground; then <speaker>, <voice.videoDirection>, says
|
|
342
|
+
<d>[Chinese] 台词</d>)
|
|
343
|
+
[Shot 2] At 00:03.000, … (every k ≥ 2 starts with the cumulative timestamp)
|
|
344
|
+
|
|
345
|
+
overall_soundscape: …
|
|
346
|
+
|
|
347
|
+
non_diegetic_music: … (or N/A)
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Gates: alignment line verbatim; the three field names in order; `[Shot k]`
|
|
351
|
+
present for every cut, with `At mm:ss.mmm,` equal to the cumulative seconds for
|
|
352
|
+
k ≥ 2; every claimed line verbatim inside `<d>[<Language>] …</d>`; the cut's
|
|
353
|
+
camera word inside its own `[Shot k]` paragraph; every speaker's `voice.videoDirection`
|
|
354
|
+
verbatim; no names outside `<d>`.
|
|
355
|
+
|
|
356
|
+
**Seedance prompt** (any Seedance mode — `seedance`, `seedance2`, `seedance2fast`), following `sd25-pe`:
|
|
357
|
+
|
|
358
|
+
```
|
|
359
|
+
Use @image1 through @imageN as keyframes in that order. ← only when N ≥ 2
|
|
360
|
+
Use @image1 as the first frame.
|
|
361
|
+
This first frame defines …
|
|
362
|
+
@image2 defines the second keyframe: …
|
|
363
|
+
Use @imageN as the last frame. ← only when N ≥ 2
|
|
364
|
+
…event script…, dialogue lines verbatim, sound.
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Gates: `Use @image1 as the first frame.` verbatim; with N ≥ 2 the keyframe-order
|
|
368
|
+
and last-frame sentences verbatim; no `@imageK` with K > N; every claimed line
|
|
369
|
+
verbatim; camera words present; no names; ≤ 10,000 characters.
|
|
370
|
+
|
|
371
|
+
**H3 prompt in sheets mode** (`params.video = "h3"`, `params.references = "sheets"`): the alignment
|
|
372
|
+
line and its frame anchors are replaced by one `@imagek is …` line per referenced sheet (every
|
|
373
|
+
k ≤ N mentioned, none above N, no alignment wording). The rest is unchanged: `Look:` line (style
|
|
374
|
+
`videoPhrase` + `lookPhrase`), `integrated_multimodal_description:` with `[Shot k] At mm:ss.mmm,`
|
|
375
|
+
paragraphs carrying the cut's `frame`, camera word and `<d>[Chinese] …</d>`, then
|
|
376
|
+
`overall_soundscape:` (name `speechAccent` here) and `non_diegetic_music:`. H3 has no auto
|
|
377
|
+
duration: `videoDuration` must be `fixed` (gated).
|
|
378
|
+
|
|
379
|
+
**Seedance prompt in sheets mode** (any Seedance mode, `params.references = "sheets"` — the default):
|
|
380
|
+
`@image1…N` are the **sheets** the segment references, not frames, so the keyframe anchor
|
|
381
|
+
sentences (`Use @image1 as the first frame.` …) are forbidden. The skeleton from `prompt segment`:
|
|
382
|
+
|
|
383
|
+
```
|
|
384
|
+
@image1 is the environment — … ← one line per referenced sheet, every @imageK must be mentioned
|
|
385
|
+
@image2 is the character sheet of …
|
|
386
|
+
Shot 1 (0.0–5.0 s, wide shot, Push In, shot on an ARRI ALEXA Mini LF with a Zeiss Supreme Prime 25mm at T1.5): <frame>
|
|
387
|
+
rim-lit from behind camera left, <light.note>. <foreground> sits in the immediate foreground, thrown well out of focus.
|
|
388
|
+
@image2, <voice.videoDirection>, says: 台词原文
|
|
389
|
+
Shot 2 (5.0–10.0 s, …): …
|
|
390
|
+
Look: <style videoPhrase>, <params.lookPhrase>. Maintain continuity …
|
|
391
|
+
Sound: … <params.speechAccent> …
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Gates: every `@imageK` for K ≤ N mentioned, none above N, no keyframe anchors, camera words, lines
|
|
395
|
+
verbatim, no names outside the lines (say `@image2`, or the character's `identityEn`), every
|
|
396
|
+
speaker's `voice.videoDirection` present verbatim, the style's `videoPhrase` and `lookPhrase`
|
|
397
|
+
present, English apart from dialogue.
|
|
398
|
+
|
|
399
|
+
**Wan prompt** (`params.video = "wan"`), for `wan-3.0-ref`. Wan is an Alibaba
|
|
400
|
+
model: it is prompted in Chinese, it addresses its reference images as
|
|
401
|
+
图1…图10, and it generates the **sound with the picture**, so the dialogue and
|
|
402
|
+
the accent live in the prompt and no separate voice model is needed.
|
|
403
|
+
|
|
404
|
+
```
|
|
405
|
+
参考图说明:图1=镜头1关键帧;图2=镜头2关键帧。按顺序还原每个镜头的构图、人物外形与场景。
|
|
406
|
+
|
|
407
|
+
镜头1(0.0–5.0秒,中景,推近):…描述… ← header derived from the cut
|
|
408
|
+
台词(图1中的短发女子):「…台词原文…」 ← speakers identified by 图N, never by name
|
|
409
|
+
|
|
410
|
+
镜头2(5.0–10.0秒,大特写,固定镜头):…
|
|
411
|
+
|
|
412
|
+
整体:…色调、情绪、跨镜头一致性…
|
|
413
|
+
音频:对白为<params.speechAccent>,…环境音…
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Gates: the legend line verbatim as the first line; every `镜头k(start–end秒,
|
|
417
|
+
size,camera):` header verbatim (Chinese size and camera words come from
|
|
418
|
+
`SIZES_ZH` / `CAMERAS_ZH`); a `台词` block when the segment has dialogue; every
|
|
419
|
+
claimed line verbatim; an `音频:` line naming `params.speechAccent`; no character
|
|
420
|
+
name outside the dialogue; ≤ 20,000 characters.
|
|
421
|
+
|
|
422
|
+
Node settings: `-m wan-3.0-ref -r <ratio> -d <Σ cut seconds> --resolution
|
|
423
|
+
<params.videoResolution>`. Passing `-d` sets `autoDuration=false`, which is what
|
|
424
|
+
pins the clip to the storyboard; leave it off and the model picks its own length.
|
|
425
|
+
|
|
426
|
+
### wji (episode level)
|
|
427
|
+
|
|
428
|
+
`displayNodes` maps `"<entityId>:<asset>"` (`C01:sheet`, `C01:voice`,
|
|
429
|
+
`S01:sheet`, `S01:lighting:<state>`, `P01:sheet`) to the display node on the
|
|
430
|
+
episode canvas that shows that asset — keyframe nodes reference those.
|
|
431
|
+
|
|
432
|
+
## Id formats
|
|
433
|
+
|
|
434
|
+
`C01` characters, `S01` scenes, `P01` props, `B01` beats, `E01-01` segments.
|
|
435
|
+
Ids are stable once assigned; never renumber after a dataset is bound.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Stage 3 — Art (scenes and props)
|
|
2
|
+
|
|
3
|
+
Environments and props are generated dozens of times and must look the same every time. This
|
|
4
|
+
stage's output is a **consistency plan** per asset, not a mood board.
|
|
5
|
+
|
|
6
|
+
## Scenes
|
|
7
|
+
|
|
8
|
+
| Field | What it is for |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| `summary` | design intent — what story this space tells, not a floor plan |
|
|
11
|
+
| `anchors` (3–5) | features that must appear in every generation: paintable, recognisable, checkable ("flickering oden light box", "red drawer with worn corners"; not "old atmosphere") |
|
|
12
|
+
| `lighting` (≥1) | one English prompt per lighting state the episodes actually use — derive them from the episodes the scene appears in, don't write a day/dusk/night set nobody uses |
|
|
13
|
+
| `image.prompt` | the master view, **empty scene, no people** (say so); `negativePrompt` must ban people |
|
|
14
|
+
| `image.sheet` | environment sheet: main panel (≈72 % × 70 %, master view + first lighting state) + an L of detail panels showing the anchors; `THE SPACE MUST BE IDENTICAL ACROSS ALL PANELS`, `nothing invented that is not present in the master view`; the style render phrase |
|
|
15
|
+
| `variantOf` + `changes` | a scene that is the parent with a different time / weather / foreground — cheaper and more consistent than a new environment; outline scenes with a `reusePlan` become variants |
|
|
16
|
+
|
|
17
|
+
Keep scenes distinguishable from each other — the spatial character of each must differ, not all
|
|
18
|
+
"worn and dim". Environment realism comes from used materials (chipped paint, water stains,
|
|
19
|
+
polished-by-hands wood), never from skin-detail vocabulary.
|
|
20
|
+
|
|
21
|
+
## Props (narrative props only)
|
|
22
|
+
|
|
23
|
+
Three conditions at once: **gets a close-up, spans episodes, carries plot.** A cup of tea is a
|
|
24
|
+
shot-level prompt detail; the oden warmer is a scene anchor; the hard drive is a prop.
|
|
25
|
+
|
|
26
|
+
| Field | Rule |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `scale` | `handheld` / `tabletop` / `furniture`; the phrase `handheld scale` etc. must be in `image.prompt` and `image.sheet` — models paint handheld things at furniture size |
|
|
29
|
+
| `summary` | dramatic function, not a product description |
|
|
30
|
+
| `anchors` (3–5) | details that survive an extreme close-up (a fresh scratch on the brass clasp) |
|
|
31
|
+
| `states` (≥1) | closed / open, sealed / plugged in — each is its own reference image |
|
|
32
|
+
| `image.*` | **pure white background, no people, no hands** (negative prompt bans hands — the hand holding the prop is the usual contamination), three-quarter master view; sheet = white-background main panel + detail panels + other states + a side orthographic view |
|
|
33
|
+
|
|
34
|
+
## On WJI
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
wji dataset new <location folder> "<scene name>" -t location -d "<one line>" # bind scenes.S01.wji.datasetId
|
|
38
|
+
wji dataset new <prop folder> "<prop name>" -t prop -d "<function>" # bind props.P01.wji.datasetId
|
|
39
|
+
node {baseDir}/scripts/short-drama.mjs render <project> scene S01 > /tmp/S01.md
|
|
40
|
+
wji fs set <id> --merge content=@/tmp/S01.md --merge image_prompt=@/tmp/S01-sheet.txt --merge metadata.shortDrama=@/tmp/S01.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`scenes` canvas (`canvas-recipes.md` §2) for the scenes and a separate `props` canvas (§2b) for
|
|
44
|
+
the props: one 16:9 **sheet** node per scene and per prop; per
|
|
45
|
+
extra lighting state / prop state one node `--from` the sheet (`@image1 is this exact space —
|
|
46
|
+
keep structure, materials and wear identical; change only: <state prompt>`); variants `--from` the
|
|
47
|
+
parent scene's sheet with the `changes` text. Sheets are single images (run), a whole batch of
|
|
48
|
+
them is a batch (quote the total).
|
|
49
|
+
|
|
50
|
+
Check every generated sheet before attaching: **no person or hand anywhere** (a figure in an
|
|
51
|
+
environment reference cannot be washed out later), one consistent space across panels, no melted
|
|
52
|
+
geometry. Attach: `fs set <dataset> --add-images <sheet> [<states…>] --icon <sheet>`; bind
|
|
53
|
+
`wji.nodes.sheet`, `wji.assets.sheet`, `wji.assets.lighting.<state>` / `wji.assets.states.<state>`.
|
|
54
|
+
|
|
55
|
+
## Gates
|
|
56
|
+
|
|
57
|
+
`scene-design` · `scene-prompts` · `prop-design`.
|