67 lines
5.1 KiB
Markdown
67 lines
5.1 KiB
Markdown
# SOP — Character visual identity and appearance forms
|
|
|
|
Status: implemented. Covers how a character's avatar and concept (reference) sheet stay the same
|
|
person, and how one character holds several appearances ("forms") without splitting into several
|
|
characters or voices.
|
|
|
|
## Single source of truth
|
|
|
|
* Appearance facts come from the sheet's `physical`, `clothing`, `age_estimate`, `race_species`,
|
|
`gender`, `title`, `profession`, `archetype` (and the user overrides `visual_appearance`,
|
|
`visual_species`, `visual_costume`). Both prompts are built by
|
|
`_character_visual_facts` in `routes/conversation.py`; the active form is overlaid first
|
|
(`_character_form_sheet`, which never lets a form change its own `id`/`name`).
|
|
* `image_prompt` (portrait) and `concept_art_prompt` (reference sheet) are **deterministic** —
|
|
built server-side from those facts with fixed camera/lighting/period text. Sheet extraction no
|
|
longer asks the LLM to write `image_prompt` (it drifted from the concept sheet); the prompt is
|
|
generated right after extraction by `csAutoGenerateExternalPrompts`, and again before any
|
|
avatar-only or per-form generation. A hand-edited prompt (`*_user_edited`) is never overwritten.
|
|
* Only the two prompts describe an appearance. `voice_design_prompt` and the SillyTavern prompt
|
|
belong to the character as a whole, so they always live on the base sheet.
|
|
|
|
## Stale detection
|
|
|
|
`_character_identity_fingerprint` (server) and `_libIdentityFingerprint` (browser) hash the same
|
|
field list; a test runs the browser code in Node and compares, including list values and non-BMP
|
|
characters. The three `visual_*` overrides are appended only when set, so pairs made before they
|
|
existed keep a valid fingerprint. A pair needs review when its prompt version is not current, its
|
|
fingerprint is missing or differs, or `identity_consistent` is false. Regenerating **only** the
|
|
avatar or **only** the reference image sets `identity_consistent = false` on an existing pair,
|
|
because a lone regeneration produces a new face.
|
|
|
|
## Where the data lives
|
|
|
|
| | base appearance | a form |
|
|
|---|---|---|
|
|
| profile image | record `image` | `form.image` |
|
|
| reference image, version, fingerprint, digests, `identity_consistent` | `sheet.*` | `form.*` |
|
|
| Physical / Clothing text | `sheet.physical/clothing` | `form.physical/clothing` |
|
|
|
|
All visual results are written through one function (`_libSaveCharacterVisual`), which re-reads the
|
|
stored record, writes to the active form or the base, and saves. (It used to write the base
|
|
reference image to the record root, and the consistent-pair generator kept it in memory only, so a
|
|
base pair lost its reference image and metadata on reload.)
|
|
|
|
`GET /api/characters/visual-pair-review` checks the base pair and every form that has images, each
|
|
against its own metadata and its own fingerprint, and reports form pairs under the form name.
|
|
|
|
## Forms (max 8 per character, enforced in `core/validation.py`)
|
|
|
|
Add (starts as a copy of the base Physical/Clothing; only non-empty keys are stored because an
|
|
empty key would blank the base value when overlaid), rename (unique, ≤160 chars), delete (confirm;
|
|
the character, its voice and the base look stay), show, and generate matched images for one form or
|
|
for all forms. Generation rebuilds that form's prompts first (unless hand-edited), saves the pair on
|
|
that form, then restores whichever appearance was active. Editing Physical/Clothing while a form is
|
|
active changes only that form.
|
|
|
|
|
|
## Image prompt hygiene and the turnaround (1.34.x)
|
|
|
|
* **Setting.** Prompts describe the books as *The Dark Eye* high-fantasy medieval dress (about 900-1300 AD: wool and linen tunics, belted robes, leather, mail where the sheet names it). The earlier "early Renaissance 1420-1500" wording pulled uniforms, coats, hats and boots that read as musketeer/cowboy/wartime.
|
|
* **Wording.** Extracted costume text is rewritten at prompt time (`_period_wording`: Uniform, Weste, Hut, Sporen, Jacke, Schal … become surcoat, jerkin, hood, boots, tunic …). Stored sheets are not changed.
|
|
* **Injuries and dirt.** `_strip_injury_clauses` removes clauses about blood, wounds, bandages, burns, limping, torn clothes, soot and dirt from appearance, costume and the story moment; the rest of the sentence stays. The portrait baseline asks for clean skin and garments.
|
|
* **Variety.** Characters with armour or weapons get an individual garment cut and colour scheme (`_character_design_variation`, stable per name; colours the sheet names win) instead of one shared list.
|
|
* **The turnaround stays one T2I triptych.** Drawing front/side/rear one by one with the edit model was tried (2026-09-20) and reverted the next day: it returned three near-identical front views, mixed rendering styles between panels, cropped bodies, and copied the wireframe pose guide into the costume — exactly the failure the triptych was introduced to fix. The portrait/sheet face mismatch is still open and needs a different approach.
|
|
* **Props** get an object-only period sentence, so a tile no longer turns into a portrait of the character.
|
|
* Older saved prompts are cleaned the same way at render time. Existing image pairs are unchanged until regenerated.
|