character-consistency
Lock a character's visual identity into
agents/reference/ai-video/<project>/characters/<id>.json so every scene
reuses the exact same tokens verbatim. Downstream skills
(video-director,
pixar-storyteller,
motion-choreographer) read
this file and never paraphrase. Verified by visual regression
(pixel similarity ≥ 95%, Phase 6 Step 3).
When to use
- A multi-scene run names the same character on screen more than
once — Character Lock is mandatory before the second scene drafts.
- A character drift bug landed (face / outfit / prop changed between
scenes) — re-lock and rerun the affected scenes.
- A series, episode, or recurring ad uses the same on-screen identity.
Do NOT use when:
- One-shot scene with no recurring character — overhead is wasted.
- The "character" is an environment or set (a place, not an
entity) — use a
style.json lock pattern in the project's notes
instead. Recurring creatures, vehicles, and hero objects DO get a
real lock — pick the matching subject_class below.
Procedure
Step 0: Inspect
- Check
agents/reference/ai-video/<project>/characters/ — if a lock already
exists for this id, read it, do not redraft. Edits require an
explicit revision note (Phase 6 visual regression must rerun).
- Confirm the character will appear in ≥ 2 scenes; one-shot → skip.
Step 1: Draft identity tokens
Emit a JSON file at
agents/reference/ai-video/<project>/characters/<character-id>.json with the
following fields. Every field is mandatory; missing field → fail
the lock.
{
"id": "kebab-case-id",
"name": "Display Name",
"subject_class": "humanoid | creature | vehicle | abstract | object",
"silhouette": "one-line read of the body shape from 30m",
"palette": ["#hex1", "#hex2", "#hex3"],
"wardrobe": "garment list, materials, era",
"signature_prop": "the one object that travels with them",
"posture_default": "how they stand when not acting",
"eye_behavior": "blink rhythm, glance habit",
"face": "age band, skin tone, hair (length / color / texture), distinguishing marks",
"voice_note": "timbre + cadence for native-audio adapters; null if N/A",
"reference_frame": "scenes/<id>/frames/<n>.png or null",
"version": 1
}
Subject-class token matrix
Field NAMES are fixed (downstream consumers extract them verbatim);
their SEMANTICS shift per subject_class. Missing subject_class →
humanoid (back-compat with existing locks). The blueprint layer
stays subject-agnostic on purpose — it only consumes the rendered
SUBJECT string; class semantics live here, in the one file the
drafting agent reads.
| Field |
humanoid |
creature |
vehicle |
abstract |
object |
silhouette |
body shape from 30m |
body shape + locomotion read |
hull/body outline |
dominant form |
outline + scale cue |
wardrobe |
garments, materials, era |
integument: fur / scales / skin texture + markings |
body panels, livery, decals, wear |
motif / texture field |
surface finish, material, wear |
signature_prop |
the object that travels with them |
anatomical signature (horn, tail tuft, scar) |
hood ornament / aerial / charm |
recurring sub-form |
defining attachment or mark |
posture_default |
how they stand |
gait + resting stance |
stance / ride attitude |
motion signature |
resting pose / orientation |
eye_behavior |
blink rhythm, glance habit |
eye/ear behavior |
lighting signature (headlights, dash glow) |
pulse / emission rhythm |
highlight + reflection behavior |
face |
age band, skin, hair, marks |
head anatomy (muzzle, eyes, dentition) |
front fascia (grille, lights) |
focal form |
defining front / face side |
voice_note |
timbre + cadence |
vocalization |
engine / motion sound |
sound signature |
interaction sound |
Universal slots (id, name, palette, reference_frame,
version) keep one meaning across all classes.
Worked example — creature:
{
"id": "moor-wyrm",
"name": "Moor Wyrm",
"subject_class": "creature",
"silhouette": "low six-limbed serpentine bulk, head held below shoulder line",
"palette": ["#2e4a3f", "#c9b458", "#1a1a1a"],
"wardrobe": "moss-green plated scales, gold-flecked underbelly, mud-matted ridge fur",
"signature_prop": "broken left tusk capped with a brass ring",
"posture_default": "coiled low, weight on forelimbs, tail tip always moving",
"eye_behavior": "slow horizontal nictitating blink; ears flatten before lunges",
"face": "blunt muzzle, four-nostril ridge, amber eyes with horizontal pupils",
"voice_note": "sub-bass rumble with clicking overtones; null if scenes are scored only",
"reference_frame": null,
"version": 1
}
Step 2: Reference frame
- After the first scene renders, copy the highest-quality frame
showing the character full-face and full-body to
agents/reference/ai-video/<project>/characters/<id>.ref.png.
- Update
reference_frame in the JSON to point at it.
- Phase 6 visual regression compares every subsequent scene's
character frame against this reference (ImageMagick
compare
≥ 95% similarity).
Step 3: Validate
- JSON parses (
jq . characters/<id>.json exits 0).
- All mandatory fields present and non-empty.
subject_class (when present) is one of humanoid | creature | vehicle | abstract | object; each field reads per the matrix row
for that class — a creature lock with a garment list in wardrobe
is a drafting error, not a style choice.
- Palette has ≥ 2 and ≤ 5 hex values.
- Downstream skills cite this file by path, never paraphrase its
contents.
Output format
agents/reference/ai-video/<project>/characters/<id>.json — locked
identity tokens, schema above.
agents/reference/ai-video/<project>/characters/<id>.ref.png —
reference frame (added after first render).
agents/reference/ai-video/<project>/characters/CHANGELOG.md — one
line per revision: v<n> · YYYY-MM-DD · reason · scenes-to-rerun.
Gotcha
- A non-humanoid lock without
subject_class reads as humanoid
downstream — the lock is structurally weaker and nobody can tell.
Always set the class for non-humanoid subjects.
- The model wants to "improve" identity tokens on each scene —
this is the silent drift failure. Tokens are immutable until a
revision note bumps
version.
- Palette without a count fails downstream — adapters need a small
closed set (2–5 hex).
voice_note: null is explicit; missing the key entirely breaks
the schema validator.
- Reference frame is captured after the first successful render,
not before — bootstrap scenes have no reference and only the
JSON locks them.
- A revision (
version bumped) requires Phase 6 visual regression
to rerun against every prior scene that used the old version.
Do NOT
- Do NOT paraphrase identity tokens when drafting scene prompts —
copy verbatim or break the lock.
- Do NOT edit a locked JSON in place without bumping
version and
adding a CHANGELOG line.
- Do NOT skip the reference frame after the first render — visual
regression has nothing to compare against.
- Do NOT lock a character that appears in only one scene.
Policies
Paths, enforcement model, and the full set: the
media policy preamble.
When a character lock would identify or render a real person, consult before emitting the JSON:
likeness — real-person identity tokens require a cited likeness release.
public-figures — recognised public figures carry the harder gate (publicity rights + transformative-intent).
voice-cloning — when voice_note references a real person's voice.
disclosure — outputs carrying a real-person lock require the non-removable AI-generation disclosure downstream.
Refuse-and-surface the file path; do not silently sanitise the prompt.
1---2name: character-consistency3description: Use when a character must stay visually identical across AI video scenes — locks identity tokens (silhouette, palette, wardrobe, prop) in JSON. Triggers 'character lock', 'same character'.4---56# character-consistency78> Lock a character's visual identity into9> `agents/reference/ai-video/<project>/characters/<id>.json` so every scene10> reuses the **exact same tokens** verbatim. Downstream skills11> ([`video-director`](../video-director/SKILL.md),12> [`pixar-storyteller`](../pixar-storyteller/SKILL.md),13> [`motion-choreographer`](../motion-choreographer/SKILL.md)) read14> this file and never paraphrase. Verified by visual regression15> (pixel similarity ≥ 95%, Phase 6 Step 3).1617## When to use1819- A multi-scene run names the same character on screen more than20 once — Character Lock is mandatory before the second scene drafts.21- A character drift bug landed (face / outfit / prop changed between22 scenes) — re-lock and rerun the affected scenes.23- A series, episode, or recurring ad uses the same on-screen identity.2425Do NOT use when:2627- One-shot scene with no recurring character — overhead is wasted.28- The "character" is an environment or set (a place, not an29 entity) — use a `style.json` lock pattern in the project's notes30 instead. Recurring creatures, vehicles, and hero objects DO get a31 real lock — pick the matching `subject_class` below.3233## Procedure3435### Step 0: Inspect36371. Check `agents/reference/ai-video/<project>/characters/` — if a lock already38 exists for this id, **read it, do not redraft**. Edits require an39 explicit revision note (Phase 6 visual regression must rerun).402. Confirm the character will appear in ≥ 2 scenes; one-shot → skip.4142### Step 1: Draft identity tokens4344Emit a JSON file at45`agents/reference/ai-video/<project>/characters/<character-id>.json` with the46following fields. Every field is mandatory; missing field → fail47the lock.4849```json50{51 "id": "kebab-case-id",52 "name": "Display Name",53 "subject_class": "humanoid | creature | vehicle | abstract | object",54 "silhouette": "one-line read of the body shape from 30m",55 "palette": ["#hex1", "#hex2", "#hex3"],56 "wardrobe": "garment list, materials, era",57 "signature_prop": "the one object that travels with them",58 "posture_default": "how they stand when not acting",59 "eye_behavior": "blink rhythm, glance habit",60 "face": "age band, skin tone, hair (length / color / texture), distinguishing marks",61 "voice_note": "timbre + cadence for native-audio adapters; null if N/A",62 "reference_frame": "scenes/<id>/frames/<n>.png or null",63 "version": 164}65```6667#### Subject-class token matrix6869Field NAMES are fixed (downstream consumers extract them verbatim);70their SEMANTICS shift per `subject_class`. Missing `subject_class` →71`humanoid` (back-compat with existing locks). The blueprint layer72stays subject-agnostic on purpose — it only consumes the rendered73SUBJECT string; class semantics live here, in the one file the74drafting agent reads.7576| Field | humanoid | creature | vehicle | abstract | object |77|---|---|---|---|---|---|78| `silhouette` | body shape from 30m | body shape + locomotion read | hull/body outline | dominant form | outline + scale cue |79| `wardrobe` | garments, materials, era | integument: fur / scales / skin texture + markings | body panels, livery, decals, wear | motif / texture field | surface finish, material, wear |80| `signature_prop` | the object that travels with them | anatomical signature (horn, tail tuft, scar) | hood ornament / aerial / charm | recurring sub-form | defining attachment or mark |81| `posture_default` | how they stand | gait + resting stance | stance / ride attitude | motion signature | resting pose / orientation |82| `eye_behavior` | blink rhythm, glance habit | eye/ear behavior | lighting signature (headlights, dash glow) | pulse / emission rhythm | highlight + reflection behavior |83| `face` | age band, skin, hair, marks | head anatomy (muzzle, eyes, dentition) | front fascia (grille, lights) | focal form | defining front / face side |84| `voice_note` | timbre + cadence | vocalization | engine / motion sound | sound signature | interaction sound |8586Universal slots (`id`, `name`, `palette`, `reference_frame`,87`version`) keep one meaning across all classes.8889Worked example — `creature`:9091```json92{93 "id": "moor-wyrm",94 "name": "Moor Wyrm",95 "subject_class": "creature",96 "silhouette": "low six-limbed serpentine bulk, head held below shoulder line",97 "palette": ["#2e4a3f", "#c9b458", "#1a1a1a"],98 "wardrobe": "moss-green plated scales, gold-flecked underbelly, mud-matted ridge fur",99 "signature_prop": "broken left tusk capped with a brass ring",100 "posture_default": "coiled low, weight on forelimbs, tail tip always moving",101 "eye_behavior": "slow horizontal nictitating blink; ears flatten before lunges",102 "face": "blunt muzzle, four-nostril ridge, amber eyes with horizontal pupils",103 "voice_note": "sub-bass rumble with clicking overtones; null if scenes are scored only",104 "reference_frame": null,105 "version": 1106}107```108109### Step 2: Reference frame1101111. After the first scene renders, copy the highest-quality frame112 showing the character full-face and full-body to113 `agents/reference/ai-video/<project>/characters/<id>.ref.png`.1142. Update `reference_frame` in the JSON to point at it.1153. Phase 6 visual regression compares every subsequent scene's116 character frame against this reference (ImageMagick `compare`117 ≥ 95% similarity).118119### Step 3: Validate1201211. JSON parses (`jq . characters/<id>.json` exits 0).1222. All mandatory fields present and non-empty.1233. `subject_class` (when present) is one of `humanoid | creature |124 vehicle | abstract | object`; each field reads per the matrix row125 for that class — a creature lock with a garment list in `wardrobe`126 is a drafting error, not a style choice.1274. Palette has ≥ 2 and ≤ 5 hex values.1285. Downstream skills cite this file by path, never paraphrase its129 contents.130131## Output format1321331. **`agents/reference/ai-video/<project>/characters/<id>.json`** — locked134 identity tokens, schema above.1352. **`agents/reference/ai-video/<project>/characters/<id>.ref.png`** —136 reference frame (added after first render).1373. **`agents/reference/ai-video/<project>/characters/CHANGELOG.md`** — one138 line per revision: `v<n> · YYYY-MM-DD · reason · scenes-to-rerun`.139140## Gotcha141142- A non-humanoid lock without `subject_class` reads as humanoid143 downstream — the lock is structurally weaker and nobody can tell.144 Always set the class for non-humanoid subjects.145- The model wants to "improve" identity tokens on each scene —146 this is the silent drift failure. Tokens are immutable until a147 revision note bumps `version`.148- Palette without a count fails downstream — adapters need a small149 closed set (2–5 hex).150- `voice_note: null` is explicit; missing the key entirely breaks151 the schema validator.152- Reference frame is captured *after* the first successful render,153 not before — bootstrap scenes have no reference and only the154 JSON locks them.155- A revision (`version` bumped) requires Phase 6 visual regression156 to rerun against every prior scene that used the old version.157158## Do NOT159160- Do NOT paraphrase identity tokens when drafting scene prompts —161 copy verbatim or break the lock.162- Do NOT edit a locked JSON in place without bumping `version` and163 adding a CHANGELOG line.164- Do NOT skip the reference frame after the first render — visual165 regression has nothing to compare against.166- Do NOT lock a character that appears in only one scene.167168## Policies169170Paths, enforcement model, and the full set: the171[media policy preamble](../../../agents/settings/policies/media/README.md).172173When a character lock would identify or render a real person, consult before emitting the JSON:174175- **`likeness`** — real-person identity tokens require a cited likeness release.176- **`public-figures`** — recognised public figures carry the harder gate (publicity rights + transformative-intent).177- **`voice-cloning`** — when `voice_note` references a real person's voice.178- **`disclosure`** — outputs carrying a real-person lock require the non-removable AI-generation disclosure downstream.179180Refuse-and-surface the file path; do not silently sanitise the prompt.181