- fix: base-appearance matched pair lost its concept image + metadata on reload; visual save wrote base concept art to the record root; one save path for base and forms now - fix: extraction no longer lets the LLM author image_prompt (source of avatar/concept drift); avatar-only generation uses the deterministic prompt and flags an existing pair - fix: prompt writes and Physical/Clothing edits go to the active form; review endpoint checks each form's own pair; fingerprint parity with the browser incl. visual_* overrides - feat: appearance forms panel (add/rename/delete/show, per-form stale flag, generate one/all) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3.4 KiB
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 overridesvisual_appearance,visual_species,visual_costume). Both prompts are built by_character_visual_factsinroutes/conversation.py; the active form is overlaid first (_character_form_sheet, which never lets a form change its ownid/name). image_prompt(portrait) andconcept_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 writeimage_prompt(it drifted from the concept sheet); the prompt is generated right after extraction bycsAutoGenerateExternalPrompts, 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_promptand 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.