@slatesvideo/shared 0.5.4 → 0.5.5
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 +13 -0
- package/dist/operations/index.d.ts +3 -8
- package/dist/operations/index.js +21 -38
- package/dist/prompts/character-sheet.d.ts +10 -21
- package/dist/prompts/character-sheet.js +50 -54
- package/dist/prompts/partials.generated.js +2 -2
- package/dist/prompts/prompting-tips.js +2 -2
- package/dist/prompts/reference-composer.d.ts +1 -1
- package/dist/prompts/reference-composer.js +3 -4
- package/dist/prompts/reference-rules.js +3 -3
- package/dist/skills/content.js +10 -10
- package/exports/slates-prompt-builder/generated/SKILL.md +59 -0
- package/exports/slates-prompt-builder/generated/reference-character.md +78 -0
- package/exports/slates-prompt-builder/generated/reference-content-policy.md +75 -0
- package/exports/slates-prompt-builder/generated/reference-kling.md +212 -0
- package/exports/slates-prompt-builder/generated/reference-nano-banana.md +182 -0
- package/exports/slates-prompt-builder/generated/reference-seedance.md +353 -0
- package/exports/slates-prompt-builder/generated/slates-prompt-builder-manifest.json +79 -0
- package/exports/slates-prompt-builder/generated/slates-prompt-builder.skill +0 -0
- package/package.json +7 -3
- package/skills/_partials/reference-rules-core.md +1 -1
- package/skills/_partials/reference-tips-short.md +1 -1
- package/skills/{slates-character-turnaround.md → slates-character-identity.md} +32 -22
- package/skills/slates-edit-and-iterate.md +1 -1
- package/skills/slates-one-prompt-film.md +3 -3
- package/skills/slates-prompting-flux-2-max.md +1 -1
- package/skills/slates-prompting-gpt-image-2.md +1 -1
- package/skills/slates-prompting-kling-v3.md +8 -6
- package/skills/slates-prompting-nano-banana-2.md +7 -5
- package/skills/slates-prompting-omni-flash.md +1 -1
- package/skills/slates-prompting-seedance.md +15 -9
- package/skills/slates-prompting-veo-3.md +1 -1
package/README.md
CHANGED
|
@@ -21,3 +21,16 @@ Prompting doctrine that applies to more than one model lives **once**, in `skill
|
|
|
21
21
|
`npm run build` runs `sync-partials.mjs --check`, which fails with a diff if anything inside a marker block was hand-edited, if a skill references a partial that doesn't exist, if markers are unbalanced, or if a partial is referenced by nothing. Run `npm run sync-partials` to apply changes after editing a partial.
|
|
22
22
|
|
|
23
23
|
**To change shared prompting doctrine: edit the partial.** Never edit between markers, never edit `partials.generated.ts`, and never hand-copy a shared rule into a new skill — a copy is a fork with a delay fuse. Per-model levers (a vendor's own official consistency mechanism, its caps and transport quirks) stay hand-authored, below the injected block, under a `### For <model> specifically` heading.
|
|
24
|
+
|
|
25
|
+
## Portable Prompt Builder export
|
|
26
|
+
|
|
27
|
+
The downloadable Claude `.skill` is generated at `exports/slates-prompt-builder/generated/`. Its model, character, and content-policy references come directly from the resolved production files in `skills/`; its routing table derives from `MODEL_FACTS`; the portable wrapper owns only export-specific scope and output behavior.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm run sync-prompt-builder # regenerate markdown, manifest, and deterministic .skill
|
|
31
|
+
npm run typecheck # fails if the committed export is stale
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`scripts/build-prompt-builder.mjs --check` validates generated text content (normalizing LF/CRLF), keeps the archive byte-exact, opens it to verify its exact entry set, and enforces the runtime character contract (one sheet, the canonical `IDENTITY_PLATE_HEX`, flat/shadowless light, and the three-panel builder). Never edit `exports/slates-prompt-builder/generated/` directly.
|
|
35
|
+
|
|
36
|
+
The repository verification workflow runs both typecheck and build on every pull request and push to `main`, so stale exports cannot merge through the normal GitHub path.
|
|
@@ -89,11 +89,7 @@ export declare const createCharacter: Operation<{
|
|
|
89
89
|
description?: string;
|
|
90
90
|
style?: string;
|
|
91
91
|
}>;
|
|
92
|
-
export declare const
|
|
93
|
-
characterId: string;
|
|
94
|
-
assetId: string | null;
|
|
95
|
-
}>;
|
|
96
|
-
export declare const setCharacterExpression: Operation<{
|
|
92
|
+
export declare const setCharacterIdentity: Operation<{
|
|
97
93
|
characterId: string;
|
|
98
94
|
assetId: string | null;
|
|
99
95
|
}>;
|
|
@@ -106,11 +102,10 @@ export declare const createEnvironment: Operation<{
|
|
|
106
102
|
description?: string;
|
|
107
103
|
style?: string;
|
|
108
104
|
}>;
|
|
109
|
-
export declare const
|
|
105
|
+
export declare const generateCharacterIdentity: Operation<{
|
|
110
106
|
characterId: string;
|
|
111
107
|
projectId: string;
|
|
112
108
|
baseAssetId: string;
|
|
113
|
-
sheetTypes?: ('turnaround' | 'expression')[];
|
|
114
109
|
userNotes?: string;
|
|
115
110
|
model?: 'nano-banana-2' | 'nano-banana-2-lite' | 'nano-banana-pro' | 'gpt-image-2';
|
|
116
111
|
}>;
|
|
@@ -377,7 +372,7 @@ export declare const updateEnvironment: Operation<{
|
|
|
377
372
|
name?: string;
|
|
378
373
|
description?: string;
|
|
379
374
|
style?: string;
|
|
380
|
-
|
|
375
|
+
referenceAssetId?: string | null;
|
|
381
376
|
}>;
|
|
382
377
|
export declare const deleteEnvironment: Operation<{
|
|
383
378
|
environmentId: string;
|
package/dist/operations/index.js
CHANGED
|
@@ -528,9 +528,9 @@ export const createCharacter = {
|
|
|
528
528
|
return ok(await ctx.desktop().post('/agent/characters', input));
|
|
529
529
|
},
|
|
530
530
|
};
|
|
531
|
-
export const
|
|
532
|
-
id: '
|
|
533
|
-
description: 'Bind
|
|
531
|
+
export const setCharacterIdentity = {
|
|
532
|
+
id: 'slates_set_character_identity_asset',
|
|
533
|
+
description: 'Bind one image asset as the character identity (or clear with assetId=null). The user sees the character card update live.',
|
|
534
534
|
input: z.object({
|
|
535
535
|
characterId: z.string().uuid(),
|
|
536
536
|
assetId: z.string().uuid().nullable(),
|
|
@@ -538,21 +538,7 @@ export const setCharacterTurnaround = {
|
|
|
538
538
|
async run(input, ctx) {
|
|
539
539
|
return ok(await ctx.desktop().post('/agent/characters/update', {
|
|
540
540
|
id: input.characterId,
|
|
541
|
-
data: {
|
|
542
|
-
}));
|
|
543
|
-
},
|
|
544
|
-
};
|
|
545
|
-
export const setCharacterExpression = {
|
|
546
|
-
id: 'slates_set_character_expression_asset',
|
|
547
|
-
description: 'Bind an image asset to the character\'s expression-sheet slot (or clear with assetId=null).',
|
|
548
|
-
input: z.object({
|
|
549
|
-
characterId: z.string().uuid(),
|
|
550
|
-
assetId: z.string().uuid().nullable(),
|
|
551
|
-
}),
|
|
552
|
-
async run(input, ctx) {
|
|
553
|
-
return ok(await ctx.desktop().post('/agent/characters/update', {
|
|
554
|
-
id: input.characterId,
|
|
555
|
-
data: { expressionAssetId: input.assetId },
|
|
541
|
+
data: { identityAssetId: input.assetId, expressionAssetId: null },
|
|
556
542
|
}));
|
|
557
543
|
},
|
|
558
544
|
};
|
|
@@ -578,17 +564,13 @@ export const createEnvironment = {
|
|
|
578
564
|
return ok(await ctx.desktop().post('/agent/environments', input));
|
|
579
565
|
},
|
|
580
566
|
};
|
|
581
|
-
export const
|
|
582
|
-
id: '
|
|
583
|
-
description: "Generate
|
|
567
|
+
export const generateCharacterIdentity = {
|
|
568
|
+
id: 'slates_generate_character_identity',
|
|
569
|
+
description: "Generate one character identity sheet from a base portrait asset and bind it as the character's canonical reference. Call after slates_create_character. Read slates-character-identity before calling and quote the cost from slates_estimate_generation_cost.",
|
|
584
570
|
input: z.object({
|
|
585
571
|
characterId: z.string().uuid(),
|
|
586
572
|
projectId: z.string().uuid(),
|
|
587
|
-
baseAssetId: z.string().uuid().describe('The base portrait asset the
|
|
588
|
-
sheetTypes: z
|
|
589
|
-
.array(z.enum(['turnaround', 'expression']))
|
|
590
|
-
.optional()
|
|
591
|
-
.describe("Which sheets to generate. Default ['turnaround'] — the single identity sheet. Add 'expression' only when the character needs a dedicated expression range; it costs a second reference slot on every downstream generation."),
|
|
573
|
+
baseAssetId: z.string().uuid().describe('The base portrait asset the identity is generated from.'),
|
|
592
574
|
userNotes: z.string().optional().describe('Extra instruction, e.g. "use the woman on the left".'),
|
|
593
575
|
model: z
|
|
594
576
|
.enum(['nano-banana-2', 'nano-banana-2-lite', 'nano-banana-pro', 'gpt-image-2'])
|
|
@@ -596,11 +578,10 @@ export const generateCharacterSheets = {
|
|
|
596
578
|
.describe('Image model for the sheet. Omit for the default (nano-banana-2). Exists so the layout-vs-face tradeoff can be tested with comparison gens — do not switch without a receipt.'),
|
|
597
579
|
}),
|
|
598
580
|
async run(input, ctx) {
|
|
599
|
-
return ok(await ctx.desktop().post('/agent/characters/generate-
|
|
581
|
+
return ok(await ctx.desktop().post('/agent/characters/generate-identity', {
|
|
600
582
|
characterId: input.characterId,
|
|
601
583
|
projectId: input.projectId,
|
|
602
584
|
baseAssetIds: [input.baseAssetId],
|
|
603
|
-
sheetTypes: input.sheetTypes,
|
|
604
585
|
userNotes: input.userNotes,
|
|
605
586
|
model: input.model,
|
|
606
587
|
}));
|
|
@@ -608,7 +589,7 @@ export const generateCharacterSheets = {
|
|
|
608
589
|
};
|
|
609
590
|
export const generateEnvironmentPlate = {
|
|
610
591
|
id: 'slates_generate_environment_plate',
|
|
611
|
-
description: "Generate
|
|
592
|
+
description: "Generate one clean establishing image from an optional base image and bind it as the environment's canonical reference. Call after slates_create_environment and quote the cost from slates_estimate_generation_cost.",
|
|
612
593
|
input: z.object({
|
|
613
594
|
environmentId: z.string().uuid(),
|
|
614
595
|
projectId: z.string().uuid(),
|
|
@@ -1384,7 +1365,7 @@ export const generateVideo = {
|
|
|
1384
1365
|
lastFrameAssetId: z.string().optional().describe('Ending frame (UUID or badge code). Veo and Seedance only. Pairs with firstFrameAssetId for guided transitions.'),
|
|
1385
1366
|
ingredientAssetIds: z.array(z.string()).max(9).optional().describe('Visual reference / ingredient assets (UUIDs or badge codes) for Kling Omni, Seedance, or Omni Flash. Up to 9 (Seedance), 4 (Kling), or 7 (Omni Flash, combined across all ref params).'),
|
|
1386
1367
|
characterAssetIds: z.array(z.string()).optional().describe('Character sheet assets (UUIDs or badge codes) — keeps a character consistent across the shot.'),
|
|
1387
|
-
environmentAssetIds: z.array(z.string()).optional().describe('Environment
|
|
1368
|
+
environmentAssetIds: z.array(z.string()).optional().describe('Environment reference assets (UUIDs or badge codes) — keeps a location/setting consistent across the shot.'),
|
|
1388
1369
|
styleAssetIds: z.array(z.string()).optional().describe('Style reference assets (UUIDs or badge codes) — locks the visual style of the shot.'),
|
|
1389
1370
|
videoReferenceAssetId: z.string().optional().describe('Seedance ONLY: an existing VIDEO asset (UUID or badge code) to use as a reference — edit/relocate a clip, or MOTION TRANSFER (pair with a subject in ingredientAssetIds and a prompt like "the character from image 1 performs the motion from video 1"). 2-15s. Billing switches to input+output seconds (the vref key) — pass videoReferenceSeconds so the quote is right. If the clip contains a human/AI character, pair with seedanceFace=true (the default Seedance route blocks people). Ignored by Kling/Veo.'),
|
|
1390
1371
|
videoReferenceSeconds: z.number().optional().describe('REQUIRED with videoReferenceAssetId: the reference clip\'s duration in seconds (from the asset listing). Feeds the vref cost key — a video-reference gen bills combined input+output seconds; the server re-derives this by probing the clip, so an understated value just gets corrected upward.'),
|
|
@@ -2532,7 +2513,7 @@ export const setFolderCover = {
|
|
|
2532
2513
|
};
|
|
2533
2514
|
export const updateCharacter = {
|
|
2534
2515
|
id: 'slates_update_character',
|
|
2535
|
-
description: 'Update a character\'s name, description, or style.
|
|
2516
|
+
description: 'Update a character\'s name, description, or style. Use slates_set_character_identity_asset for its canonical image.',
|
|
2536
2517
|
input: z.object({
|
|
2537
2518
|
characterId: z.string().uuid(),
|
|
2538
2519
|
name: z.string().min(1).max(120).optional(),
|
|
@@ -2556,13 +2537,13 @@ export const deleteCharacter = {
|
|
|
2556
2537
|
};
|
|
2557
2538
|
export const updateEnvironment = {
|
|
2558
2539
|
id: 'slates_update_environment',
|
|
2559
|
-
description: 'Update an environment\'s name, description, style, or bound
|
|
2540
|
+
description: 'Update an environment\'s name, description, style, or bound reference image (referenceAssetId=null clears it).',
|
|
2560
2541
|
input: z.object({
|
|
2561
2542
|
environmentId: z.string().uuid(),
|
|
2562
2543
|
name: z.string().min(1).max(120).optional(),
|
|
2563
2544
|
description: z.string().optional(),
|
|
2564
2545
|
style: z.string().max(200).optional().describe("Art style. Omit to inherit the reference's style (the default). Canonical styles: photoreal, anime, painterly, 3d-render, comic. Or pass any free-text instruction, e.g. 'turn this into a real person'."),
|
|
2565
|
-
|
|
2546
|
+
referenceAssetId: z.string().uuid().nullable().optional(),
|
|
2566
2547
|
}),
|
|
2567
2548
|
async run(input, ctx) {
|
|
2568
2549
|
return ok(await ctx.desktop().post('/agent/environments/update', {
|
|
@@ -2571,7 +2552,7 @@ export const updateEnvironment = {
|
|
|
2571
2552
|
name: input.name,
|
|
2572
2553
|
description: input.description,
|
|
2573
2554
|
style: input.style,
|
|
2574
|
-
|
|
2555
|
+
referenceAssetId: input.referenceAssetId,
|
|
2575
2556
|
},
|
|
2576
2557
|
}));
|
|
2577
2558
|
},
|
|
@@ -2728,6 +2709,9 @@ function resolveGuideTopic(topic) {
|
|
|
2728
2709
|
const t = topic.trim().toLowerCase();
|
|
2729
2710
|
if (SKILLS[t])
|
|
2730
2711
|
return t;
|
|
2712
|
+
if (t === 'slates-character-turnaround' || t === 'character-turnaround') {
|
|
2713
|
+
return 'slates-character-identity';
|
|
2714
|
+
}
|
|
2731
2715
|
if (t === 'model-selection' ||
|
|
2732
2716
|
t === 'model selection' ||
|
|
2733
2717
|
t === 'which-model' ||
|
|
@@ -2780,7 +2764,7 @@ export const getPromptingGuide = {
|
|
|
2780
2764
|
topic: z
|
|
2781
2765
|
.string()
|
|
2782
2766
|
.min(1)
|
|
2783
|
-
.describe('Guide name, model id, or style name. Guides: slates-model-selection (which model for which job — read before choosing any model), slates-cost-discipline, slates-content-policy, slates-style-prompting, slates-prompting-nano-banana-2, slates-prompting-veo-3, slates-prompting-kling-v3, slates-prompting-seedance, slates-prompting-lip-sync, slates-prompting-motion-transfer, slates-prompting-flux-2-max, slates-prompting-seedream-5-lite, slates-edit-and-iterate, slates-vision-feedback-loop, slates-character-
|
|
2767
|
+
.describe('Guide name, model id, or style name. Guides: slates-model-selection (which model for which job — read before choosing any model), slates-cost-discipline, slates-content-policy, slates-style-prompting, slates-prompting-nano-banana-2, slates-prompting-veo-3, slates-prompting-kling-v3, slates-prompting-seedance, slates-prompting-lip-sync, slates-prompting-motion-transfer, slates-prompting-flux-2-max, slates-prompting-seedream-5-lite, slates-edit-and-iterate, slates-vision-feedback-loop, slates-character-identity, slates-storyboard-from-script, slates-direct-response-ad, slates-one-prompt-film. Style names (photoreal, anime, painterly, 3d-render) resolve to slates-style-prompting.'),
|
|
2784
2768
|
}),
|
|
2785
2769
|
async run(input) {
|
|
2786
2770
|
const resolved = resolveGuideTopic(input.topic);
|
|
@@ -2814,11 +2798,10 @@ export const ALL_OPERATIONS = [
|
|
|
2814
2798
|
moveAssetsToFolder,
|
|
2815
2799
|
listCharacters,
|
|
2816
2800
|
createCharacter,
|
|
2817
|
-
|
|
2818
|
-
setCharacterExpression,
|
|
2801
|
+
setCharacterIdentity,
|
|
2819
2802
|
listEnvironments,
|
|
2820
2803
|
createEnvironment,
|
|
2821
|
-
|
|
2804
|
+
generateCharacterIdentity,
|
|
2822
2805
|
generateEnvironmentPlate,
|
|
2823
2806
|
listStoryboards,
|
|
2824
2807
|
createStoryboard,
|
|
@@ -4,33 +4,22 @@
|
|
|
4
4
|
* pixels, so it gets the resolution. The body panels exist for build,
|
|
5
5
|
* proportion, wardrobe and hair, not for the face.
|
|
6
6
|
*
|
|
7
|
-
* The
|
|
8
|
-
*
|
|
7
|
+
* The front panel is headless and the back panel KEEPS its head — that
|
|
8
|
+
* asymmetry is the whole rule. A front-facing body panel renders a ~40px face
|
|
9
|
+
* that cannot match the portrait's, so the sheet would carry two competing
|
|
10
|
+
* identities and the model averages them. A back view has no face to compete
|
|
11
|
+
* with, and it is the only panel where hair fall reads. See the header comment
|
|
12
|
+
* for the receipt and for why the phrasing must stay framing, not removal.
|
|
9
13
|
*/
|
|
10
14
|
export declare const CHARACTER_SHEET_PANELS_DESC: string;
|
|
11
15
|
/** Panel identifiers, in sheet order. */
|
|
12
16
|
export declare const BODY_POSE_LABELS: readonly ["portrait", "front", "back"];
|
|
13
17
|
/**
|
|
14
|
-
*
|
|
15
|
-
* 2026-07-21 single-sheet architecture keep regenerating correctly, and so the
|
|
16
|
-
* expression slot remains usable for a character that genuinely needs a
|
|
17
|
-
* dedicated expression range.
|
|
18
|
-
*/
|
|
19
|
-
export declare const CHARACTER_EXPRESSIONS_DESC = "neutral expression on left, genuine smile showing teeth in center, serious frown on right";
|
|
20
|
-
export declare const EXPRESSION_LABELS: readonly ["neutral", "smile", "serious"];
|
|
21
|
-
/**
|
|
22
|
-
* The character identity sheet — one asset, three panels, bound to the
|
|
23
|
-
* turnaround slot. Named `buildCharacterTurnaroundPrompt` for continuity with
|
|
24
|
-
* every existing caller and with the slot it binds to.
|
|
18
|
+
* The character identity sheet — one asset, three panels.
|
|
25
19
|
*
|
|
26
20
|
* @param userStyle optional natural-language style transform (e.g. "make her a real person")
|
|
27
21
|
*/
|
|
28
|
-
export declare function
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
* identity sheet above is the default and this slot is normally left null.
|
|
32
|
-
* Generate one only when a character needs an explicit expression range;
|
|
33
|
-
* attaching it costs a second reference slot on every generation.
|
|
34
|
-
*/
|
|
35
|
-
export declare function buildExpressionSheetPrompt(userStyle?: string | null): string;
|
|
22
|
+
export declare function buildCharacterIdentityPrompt(userStyle?: string | null): string;
|
|
23
|
+
/** @deprecated Use buildCharacterIdentityPrompt. */
|
|
24
|
+
export declare const buildCharacterTurnaroundPrompt: typeof buildCharacterIdentityPrompt;
|
|
36
25
|
//# sourceMappingURL=character-sheet.d.ts.map
|
|
@@ -2,41 +2,51 @@
|
|
|
2
2
|
//
|
|
3
3
|
// ARCHITECTURE (locked by Eric 2026-07-21): ONE identity sheet per character.
|
|
4
4
|
// A dominant off-frontal chest-up portrait carries the face; two full-body
|
|
5
|
-
// panels (front + back) carry build, wardrobe and hair. The
|
|
6
|
-
// character's
|
|
5
|
+
// panels (front + back) carry build, wardrobe and hair. The result is the
|
|
6
|
+
// character's one canonical identity reference.
|
|
7
7
|
//
|
|
8
8
|
// Why one sheet and not two — three arguments, none of which depend on a
|
|
9
9
|
// comparison generation:
|
|
10
|
-
// 1. Reference-cap economics. Every
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// Kling 3.0 takes 4 ingredients (2 characters, zero room for an
|
|
14
|
-
// environment), NB2 has 4 character slots, Seedance 9. One sheet each
|
|
15
|
-
// DOUBLES the cast you can stage on every model we route to.
|
|
16
|
-
// 2. Competing face renderings drop 6 → 2. The old pair sent three large
|
|
17
|
-
// portraits plus three postage-stamp faces (turnaround front + both
|
|
18
|
-
// profiles). The model cannot tell which rendering is authoritative and
|
|
19
|
-
// averages them; ByteDance documents the same root cause for its
|
|
20
|
-
// duplicate-character failure (ModelArk :1959) and prescribes fewer
|
|
21
|
-
// competing views. Both profile panels disappear with the shape change.
|
|
22
|
-
// 3. One generation instead of two per character — half the sheet spend, one
|
|
23
|
-
// asset to inspect and bind.
|
|
10
|
+
// 1. Reference-cap economics. Every character uses one reference slot.
|
|
11
|
+
// 2. One authoritative face avoids averaging competing renderings.
|
|
12
|
+
// 3. One generation means one asset to inspect and bind.
|
|
24
13
|
//
|
|
25
14
|
// KNOWN COST, accepted for v1: a neutral chest-up portrait carries no dental
|
|
26
15
|
// information, so a character who smiles in a shot gets invented teeth. The
|
|
27
16
|
// 2026-06-26 doctrine already holds that the user's prompt owns expression.
|
|
28
17
|
// Revisit only if a receipt shows invented teeth.
|
|
29
18
|
//
|
|
30
|
-
//
|
|
31
|
-
// "ghost mannequin" treatment
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
19
|
+
// THE FRONT PANEL IS HEADLESS (shipped 2026-07-22, receipt-gated). The face is
|
|
20
|
+
// cropped off the front body panel — the "ghost mannequin" treatment — taking
|
|
21
|
+
// competing face renderings 2 → 1. The BACK panel keeps its head: it has no
|
|
22
|
+
// face to compete with the portrait, and it is the only panel where hair fall
|
|
23
|
+
// reads. The rule is "kill every competing rendering of the FACE", not "kill
|
|
24
|
+
// every head".
|
|
35
25
|
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
26
|
+
// Two things had to be true before this shipped, and both were verified on
|
|
27
|
+
// real generations (research/model-prompting-research.md, "Head-crop receipt"):
|
|
28
|
+
// 1. It is prompt-reachable. NB2 renders a clean invisible-mannequin panel
|
|
29
|
+
// with no refusal. THIS DEPENDS ON THE PHRASING: it is framed as FRAMING
|
|
30
|
+
// ("cropped at the collarbone, head not shown, invisible-mannequin
|
|
31
|
+
// presentation"), a standard e-commerce genre with deep training data.
|
|
32
|
+
// Never phrase it as removal or decapitation.
|
|
33
|
+
// 2. The literal-reading law does NOT fire on it. This was the real risk and
|
|
34
|
+
// it is ours, not the source corpus's: `references-read-literally.md` says
|
|
35
|
+
// a baked-in property is read as a property of the SUBJECT, and this panel
|
|
36
|
+
// renders headlessness as CONTENT (empty plate above the collar), not as
|
|
37
|
+
// photographic framing. The predicted failure was a headless or
|
|
38
|
+
// neck-glitched downstream character. A Kling shot from a bound sheet came
|
|
39
|
+
// back head intact, identity holding, wardrobe held — in a MULTI-CHARACTER
|
|
40
|
+
// frame, which is the scope ByteDance's averaging failure lives in
|
|
41
|
+
// (ModelArk :1948-1994), so it is the hardest form of the test.
|
|
42
|
+
//
|
|
43
|
+
// Receipt strength: N=1 character, decisive for filterability, strong for the
|
|
44
|
+
// literal-reading risk, NOT a scored V2-vs-V3 comparison — nobody measured
|
|
45
|
+
// whether 2 → 1 improves identity hold, only that it doesn't break. HOW YOU'D
|
|
46
|
+
// KNOW THIS IS BEATEN: a downstream character generating headless,
|
|
47
|
+
// neck-glitched, or with a floating collar; or a scored run where heads-kept
|
|
48
|
+
// holds identity better. Reverting is a one-line change here — no migration,
|
|
49
|
+
// no data touched. Quadrupeds are carved out below (a horse has no collarbone).
|
|
40
50
|
//
|
|
41
51
|
// SOURCE OF TRUTH. The desktop imports these builders through
|
|
42
52
|
// `slate/src/shared/prompts/character-sheet.ts` (a thin re-export since 1.2.1)
|
|
@@ -50,22 +60,19 @@ import { renderStyleInstruction } from './style-library.js';
|
|
|
50
60
|
* pixels, so it gets the resolution. The body panels exist for build,
|
|
51
61
|
* proportion, wardrobe and hair, not for the face.
|
|
52
62
|
*
|
|
53
|
-
* The
|
|
54
|
-
*
|
|
63
|
+
* The front panel is headless and the back panel KEEPS its head — that
|
|
64
|
+
* asymmetry is the whole rule. A front-facing body panel renders a ~40px face
|
|
65
|
+
* that cannot match the portrait's, so the sheet would carry two competing
|
|
66
|
+
* identities and the model averages them. A back view has no face to compete
|
|
67
|
+
* with, and it is the only panel where hair fall reads. See the header comment
|
|
68
|
+
* for the receipt and for why the phrasing must stay framing, not removal.
|
|
55
69
|
*/
|
|
56
70
|
export const CHARACTER_SHEET_PANELS_DESC = 'a large chest-up portrait on the left at a three-quarter angle (never dead-on), ' +
|
|
57
|
-
'a full-body front view in a relaxed A-pose in the centre, ' +
|
|
58
|
-
'
|
|
71
|
+
'a full-body front view in a relaxed A-pose in the centre, framed from the collarbone down with the head not shown — ' +
|
|
72
|
+
'an invisible-mannequin presentation where the clothing holds its own shape, ' +
|
|
73
|
+
'and a full-body back view on the right with the head and hair fully visible';
|
|
59
74
|
/** Panel identifiers, in sheet order. */
|
|
60
75
|
export const BODY_POSE_LABELS = ['portrait', 'front', 'back'];
|
|
61
|
-
/**
|
|
62
|
-
* Expression-sheet close-ups — LEGACY. Kept so characters built before the
|
|
63
|
-
* 2026-07-21 single-sheet architecture keep regenerating correctly, and so the
|
|
64
|
-
* expression slot remains usable for a character that genuinely needs a
|
|
65
|
-
* dedicated expression range.
|
|
66
|
-
*/
|
|
67
|
-
export const CHARACTER_EXPRESSIONS_DESC = 'neutral expression on left, genuine smile showing teeth in center, serious frown on right';
|
|
68
|
-
export const EXPRESSION_LABELS = ['neutral', 'smile', 'serious'];
|
|
69
76
|
// The sheet's style directive: a user transform REPLACES the inherit-source
|
|
70
77
|
// instruction (so the model isn't told to both preserve the medium AND change
|
|
71
78
|
// it); otherwise inherit the source medium.
|
|
@@ -73,31 +80,20 @@ function styleDirective(userStyle) {
|
|
|
73
80
|
return renderStyleInstruction(userStyle).trim() || INHERIT_SOURCE_STYLE;
|
|
74
81
|
}
|
|
75
82
|
/**
|
|
76
|
-
* The character identity sheet — one asset, three panels
|
|
77
|
-
* turnaround slot. Named `buildCharacterTurnaroundPrompt` for continuity with
|
|
78
|
-
* every existing caller and with the slot it binds to.
|
|
83
|
+
* The character identity sheet — one asset, three panels.
|
|
79
84
|
*
|
|
80
85
|
* @param userStyle optional natural-language style transform (e.g. "make her a real person")
|
|
81
86
|
*/
|
|
82
|
-
export function
|
|
87
|
+
export function buildCharacterIdentityPrompt(userStyle) {
|
|
83
88
|
return (`A single character identity reference sheet of one character, three panels side by side on one plate: ` +
|
|
84
89
|
`${CHARACTER_SHEET_PANELS_DESC}. ` +
|
|
85
90
|
`The portrait is the largest panel and occupies roughly a quarter to a third of the sheet — it is the sole authority for the face, so render it at maximum facial detail. ` +
|
|
91
|
+
`No second rendering of the face anywhere on the sheet. ` +
|
|
86
92
|
`Neutral expression and identical appearance, wardrobe and hair across all three panels. ` +
|
|
87
93
|
`${styleDirective(userStyle)} ${IDENTITY_LIGHTING_CLAUSE} ${IDENTITY_CRAFT_CLAUSE} ` +
|
|
88
|
-
`For quadruped or non-bipedal characters, replace the A-pose with a natural standing stance and keep the same three-panel layout. ` +
|
|
94
|
+
`For quadruped or non-bipedal characters, replace the A-pose with a natural standing stance, show the whole animal including the head on both body panels, and keep the same three-panel layout. ` +
|
|
89
95
|
`No text, no labels, no captions, no panel borders.`);
|
|
90
96
|
}
|
|
91
|
-
/**
|
|
92
|
-
|
|
93
|
-
* identity sheet above is the default and this slot is normally left null.
|
|
94
|
-
* Generate one only when a character needs an explicit expression range;
|
|
95
|
-
* attaching it costs a second reference slot on every generation.
|
|
96
|
-
*/
|
|
97
|
-
export function buildExpressionSheetPrompt(userStyle) {
|
|
98
|
-
return (`Character expression reference sheet with 3 head and shoulder portraits arranged side by side horizontally: ${CHARACTER_EXPRESSIONS_DESC}. ` +
|
|
99
|
-
`Consistent character appearance across all three. ` +
|
|
100
|
-
`${styleDirective(userStyle)} ${IDENTITY_LIGHTING_CLAUSE} ${IDENTITY_CRAFT_CLAUSE} Same framing for each. ` +
|
|
101
|
-
`No text, no labels, no captions.`);
|
|
102
|
-
}
|
|
97
|
+
/** @deprecated Use buildCharacterIdentityPrompt. */
|
|
98
|
+
export const buildCharacterTurnaroundPrompt = buildCharacterIdentityPrompt;
|
|
103
99
|
//# sourceMappingURL=character-sheet.js.map
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// no longer disagree. Edit the partial, not this file, not the skills.
|
|
7
7
|
export const PARTIALS = {
|
|
8
8
|
"decision-log": "When you surface the plan, include a short **decision log** — one line per decision *you* made that the user did not specify:\n\n```\nsource phrase or declared default → what you wrote → what it resolves\n\"in a diner\" → chrome-and-vinyl booth, 3/4 on the counter → fixes the anchor so blocking is repeatable\n(no time of day) → late afternoon, low warm key → default; say the word and it changes\n(no camera) → slow push-in, single move → one move per shot; stacking increases instability\n```\n\n**Hard rule: never silently add weather, props, style, or camera movement.** If it wasn't in the brief and you added it, it goes in the log. This is the \"why did you add that?\" affordance — for an agent that writes prompts on the user's behalf and spends their credits, it is what keeps the model in assembly and the user in the director's chair.\n\n> ❌ **Do NOT turn this into a question gate.** Clarifying questions before optimizing directly fight the locked fast-path rule: *if intent is clear, generate immediately with sane defaults, don't ask questions; only ask for production intent, and batch every question into one message.* Log the decisions, then go. The log is an **output**, not an interrogation — surfaced alongside the plan, never as a separate ceremony, and never as a reason to wait.",
|
|
9
|
-
"reference-rules-core": "Identity = a few flat-lit neutral angles; one reference per role, named inline; 2-4 refs not 12; describe environments instead of feeding a grid.\n\n1. **2-4 strong references beat both extremes.** Not 1 (warps toward itself), not 12 (averages worse). Start with 2-3 focused refs — each one adds context AND another variable to balance.\n2. **One reference per ROLE, named in the prompt** — identity / style-grade / environment. The model does **not** infer a reference's role from its position in the list; the inline name carries it. Same-role competitors drift (two \"identity\" refs of different people blend into a third face). Slates composes the naming for you from your `@mentions` / `#tags` — you never hand-write role labels.\n3. **One identity sheet per character
|
|
10
|
-
"reference-tips-short": "Name each reference inline; never write role essays. Slates does this for you: `@mention` a subject or environment and it composes `Marcus (
|
|
9
|
+
"reference-rules-core": "Identity = a few flat-lit neutral angles; one reference per role, named inline; 2-4 refs not 12; describe environments instead of feeding a grid.\n\n1. **2-4 strong references beat both extremes.** Not 1 (warps toward itself), not 12 (averages worse). Start with 2-3 focused refs — each one adds context AND another variable to balance.\n2. **One reference per ROLE, named in the prompt** — identity / style-grade / environment. The model does **not** infer a reference's role from its position in the list; the inline name carries it. Same-role competitors drift (two \"identity\" refs of different people blend into a third face). Slates composes the naming for you from your `@mentions` / `#tags` — you never hand-write role labels.\n3. **One identity sheet per character, named inline.** A character's identity is a single asset (dominant portrait + body panels), so attach that one asset rather than a pile of views: **fewer competing renderings of a face is better, because the model cannot tell which one is authoritative and averages them.** Slates cites it as `Marcus (image 1)`. **Do NOT hand-write a \"Reference Image Instructions\" block or role essays** (\"use for identity, ignore the outfit, render a neutral expression\") — that drags the sheet's studio lighting and wardrobe into a scene that asked for neither. The prompt leads; the user's words own wardrobe, expression, lighting, and action.\n4. **Flat-light identity refs.** Prep identity references with flat, even, shadowless lighting on a plain neutral background. A studio-lit or scene-lit character sheet bleeds its lighting into every generation — the failure looks like the subject was green-screen-pasted in front of the location. Reference prep beats prompting here.\n5. **Environment: describe it, don't feed a grid.** Default to describing the location in words and let the model build a space that fits the shot. Reserve an environment reference for a mandatory exact-match, and then use ONE clean establishing image with natural ambient light that reads as the location's real light — never a multi-panel grid fed whole.\n6. **Grids: explore, don't input.** Use grids to explore compositions cheaply, then pick a cell. Never feed a grid back in as a reference — the cells share a split detail budget and were generated jointly, so their flaws propagate.\n7. **Reuse the same refs across every shot** in a sequence. Lock a set and keep it; swapping references mid-sequence causes drift, because the model adapts each reference to the current prompt rather than copying it.\n8. **Legible in-shot text → bake it into a still start frame, never trust text-to-video.** Have an image model render the text, then animate from that locked frame. Video models smear type.\n9. **Working from existing media — describe ONLY what changes.** The source already carries its composition, motion, timing, and performance; re-describing them fights the model. Narrate the delta. (Video lane: restyle your own clip while keeping the performance; delayed-VFX on \"video one\"; marker-object insertion; video-as-reference for a series.)\n10. **Style transforms happen in natural language.** By default the source's artistic medium and visual style are inherited. To change it, add a plain-text instruction (\"anime → real person\"). There are no preset pickers, and there is no style slider.",
|
|
10
|
+
"reference-tips-short": "Name each reference inline; never write role essays. Slates does this for you: `@mention` a subject or environment and it composes `Marcus (image 1) in the cafe (image 2)`, citing them in the exact order it sends them. One canonical identity image avoids competing facial renderings; a \"Reference Image Instructions\" block drags reference lighting into your scene. Start with 2-3 focused refs.",
|
|
11
11
|
"references-read-literally": "> **The general law: the model reads a reference literally.**\n> A reference image is not a suggestion. Whatever is baked into it — lighting, medium, texture, symmetry, competing identities — is read as a **property of the subject** and reproduced downstream. A baked rim light tints every shot made from that sheet. A sheet that looks like a 3D game render gets animated like game footage. Two competing renderings of one face get averaged into a third face.\n\nEvery reference rule below is a corollary of that one sentence, which is why \"prep the reference\" beats \"prompt around the reference\" every time:\n\n- **Flat, plain identity refs** — because scene lighting in the sheet becomes scene lighting in the output (Slates' own receipt: a studio-lit sheet produced a subject that looked green-screen-pasted in front of mountains).\n- **One authoritative rendering per subject** — because the model cannot tell which panel is the real one. ByteDance documents this failure directly: multi-view character assets \"confuse the model's character recognition, causing it to generate duplicate characters of the same appearance.\"\n- **No 3D-game-render look in a reference** — the model recognizes the render mood and inherits its motion character, so the *animation* comes out looking like game footage. This is not a taste rule; it is the same literal-reading mechanism applied to the temporal layer.\n- **Break perfect symmetry** — mirrored faces and dead-square framing read as synthetic, and the model preserves that reading rather than correcting it.\n\n**What this means in practice:** when output is wrong in a way that tracks the *subject* rather than the *scene* — the lighting is wrong the same way in every shot, the face drifts, the material looks synthetic everywhere — fix the reference, not the prompt. Prompting around a baked-in property is the expensive way to lose.",
|
|
12
12
|
"still-gate": "**A visible defect in the still is already a STOP.** Do not animate it. Fix the frame first, then move to motion — and go to motion only when the crop passes the still scan and you genuinely need movement to confirm an uncertain edge, reflection, or object.\n\nThis is a **cost** rule as much as a craft rule: a 1080p/10s premium video generation costs many multiples of an image re-roll, and video is where a defect stops being fixable. Anything wrong in the still gets worse in motion — soft geometry mushes, broken-but-plausible objects fall apart, oily textures start crawling. **Animating a known-bad frame is the single most expensive mistake in the pipeline.** Re-rolling the image is the cheap move; re-rolling the video is not.",
|
|
13
13
|
};
|
|
@@ -212,7 +212,7 @@ const OMNI_FLASH = {
|
|
|
212
212
|
[
|
|
213
213
|
{
|
|
214
214
|
heading: 'Name references inline',
|
|
215
|
-
example: 'Marcus (
|
|
215
|
+
example: 'Marcus (image 1) walks into the cafe...',
|
|
216
216
|
note: 'Up to 7 reference images merge into one list — refer to them by number in the prompt.',
|
|
217
217
|
},
|
|
218
218
|
{
|
|
@@ -325,7 +325,7 @@ const NANO_BANANA = {
|
|
|
325
325
|
},
|
|
326
326
|
{
|
|
327
327
|
heading: 'Reference images — name them, never label roles',
|
|
328
|
-
example: 'Marcus (
|
|
328
|
+
example: 'Marcus (image 1) sits across from the woman (image 2) in the cafe (image 3).',
|
|
329
329
|
note: `Up to 14 refs (10 object + 4 character — caps don't trade). ${PARTIALS['reference-tips-short']}`,
|
|
330
330
|
},
|
|
331
331
|
{
|
|
@@ -14,7 +14,7 @@ export interface ReferenceGroup {
|
|
|
14
14
|
/** Display + citation name: 'Marcus' | 'the cafe' | 'noir'. Used verbatim. */
|
|
15
15
|
name: string;
|
|
16
16
|
kind: ReferenceKind;
|
|
17
|
-
/** A group can carry several images
|
|
17
|
+
/** A group can carry several images for workflows that genuinely need them. */
|
|
18
18
|
media: ReferenceMedia[];
|
|
19
19
|
}
|
|
20
20
|
export interface ComposedReferences {
|
|
@@ -14,10 +14,9 @@
|
|
|
14
14
|
// the desktop generation + rail read from this one function, so the rail's badge
|
|
15
15
|
// numbers and the prompt's "image N" citations can never desync.
|
|
16
16
|
//
|
|
17
|
-
// Naming is the
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// face. This IS each model's own official consistency lever (NB2 "assign a
|
|
17
|
+
// Naming is the identity signal. Citing the canonical subject image inline
|
|
18
|
+
// ("Marcus (image 1)") tells the model which reference owns that entity. This
|
|
19
|
+
// is each model's own official consistency lever (NB2 "assign a
|
|
21
20
|
// distinct name", Seedance "Reference Subject_N in Image_N", Kling "reuse a fixed
|
|
22
21
|
// label verbatim"); the heavy role-essay block was the off-doctrine part.
|
|
23
22
|
// Normalize a name/token for matching: drop the sigil, lowercase, strip
|
|
@@ -30,9 +30,9 @@ export const REFERENCE_RULES = [
|
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
id: 'identity-name-as-one-entity',
|
|
33
|
-
title: 'One identity sheet per character
|
|
34
|
-
rule: 'Attach the character\'s single identity sheet (dominant portrait + body panels) rather than a pile of views — fewer competing renderings of a face is
|
|
35
|
-
why: '
|
|
33
|
+
title: 'One identity sheet per character, named inline',
|
|
34
|
+
rule: 'Attach the character\'s single identity sheet (dominant portrait + body panels) rather than a pile of views — fewer competing renderings of a face is better, because the model cannot tell which is authoritative and averages them. Cite it inline as the subject ("Marcus (image 1)"). Do not inject a role essay ("use for identity, ignore the outfit/lighting, render neutral"): the user\'s prompt owns wardrobe, expression, and lighting.',
|
|
35
|
+
why: 'Distinct inline naming is each model\'s own consistency lever — NB2 "assign a distinct name to each character/object"; Seedance "Reference <Subject_N> in <Image_N>"; Kling "reuse a fixed label verbatim". One canonical identity image removes same-role competition before it starts.',
|
|
36
36
|
grade: 'Eric-test',
|
|
37
37
|
},
|
|
38
38
|
{
|