Maturity: Draft. Consolidated and cross-checked for vanilla 2.31 female and male resources, ArchiveXL 1.27.3 source, one decoded 2.31 save and the implemented resolver's run on the reference installation; consolidated category by category on 30 September 2026 in the file-chain map, which also lists the source contradictions and the resolver's ranked gaps. Not yet runtime-tested in the areas marked hypothesis; the creator checks run in session 4 are in in-game results. Evidence grades follow the knowledge rules: source engine/framework/tool source, resource extracted game or mod resources, wiki Modding Docs text or image, runtime running game, hypothesis not yet established.
This page answers four questions for XF Studio agents:
- How does a character-creator (CC) choice become geometry and materials? (render any V in the Studio viewport)
- What options exist, and how are they identified? (expose every CC option as a Studio control)
- How does a save store the choices? (later write-back and shareable CC presets)
- How do mods add options? (our own eye-makeup selector via ArchiveXL/CCXL)
Material and shader internals (.mt/.remt templates, skin, hair, eye and decal shaders) are covered in materials and shaders; this page stops at "which material instance and which inputs".
Evidence snapshot
| Source | Version | What it established |
|---|---|---|
| Installed game resources | 2.31 (resource GameVersion 2310). female_, male_ and their _ twins; representative .app, player .ent and .charcustpreset files | Option lists, groups, links, .app → .ent → component wiring. Extracted with WolvenKit CLI 8.17.4 (unbundle, convert serialize) into ignored research/. SHA-256 of the four .inkcharcustomization resources are in the option inventory. |
| Vanilla CCO evidence | 2026-09-25 | Extraction steps, resource hashes and the detailed observations behind sections 2, 3, 6 and 7. |
| CC option inventory | xfs/, generated from the above | Machine-readable, asset-free: every vanilla group, option, choice name, .app path, link family, preset and version-migration entry for both body genders; EP1 files as diffs. |
| ArchiveXL source | 1.27.3, commit 5474e34d | CCXL merge, fixes, scopes, dynamic appearances (merge boundary). |
| WolvenKit source | commit 11720772 | Save node reader and writer, resource types (gameuiCharacterCustomizationPreset, …UiPreset, CC controller components). |
| Reference save | 2.31, save version 269, preset version 12 | One real female V: groups, resolved appearances, morphs (save import). |
| Modding Docs clone | be2f44ee | Documented relationships and editor screenshots (wiki chain map); CCXL guides. |
| XF Studio character resolver | 2026-09-25, reference installation (MO2 route) | Resolves the reference save and PRC option 12 from data; reproduces the hand-traced chains and records every unproven precedence rule. Validation |
| Head CC render evidence | 2026-09-25, resolver at afb474e on four synthetic creator states (direct route) plus the reference save; one MO2 profile scanned for skin overrides | Material templates and key parameters of every head option, the skin type/tone split, decoded skin tint arithmetic, and how complexion mods take effect (head CC rendering). |
| Installed game scripts, TweakDB and texts | 2.31 final.redscripts decompiled with redscript-cli 0.5.31; tweakdb_; English onscreens.json (base and EP1); ArchiveXL 1.27.3 Localization source; 2026-09-26 | How the creator lists, orders, groups, labels and colours options, and where each part lives (presentation); the Studio's creator catalogue on the reference installation (CC controls backlog). |
| File-chain map | 2026-09-30: the vanilla resources re-read, ArchiveXL's bundled per-category files and ResourcePatch source, the decompiled creator scripts, the Studio catalogue of the reference installation (26 September) and a read-only scan of its 1,052 .xl files; the wiki clone at a54b0873 (no CC page changed since be2f44ee) | Every category's chain in two tables, 13 resolved or narrowed contradictions, the resolver's ranked gaps |
| Legacy xf-omega generator | commit 28c822ea (2025-08-10), design notes in the legacy sx-cp2077 docs at b139ccab (2026-04-18) | The predecessor female eye-makeup CCXL generator. The reference save still contains three of its selections, so the game accepted its resources. Reference only: its lessons are summarised in legacy lessons. |
1. The chain at a glance
Arrows
- .archive.xl arrow to Sex-specific .inkcharcustomization (ArchiveXL merges)
- Sex-specific .inkcharcustomization arrow to SwitcherInfo choice
- SwitcherInfo choice arrow to AppearanceInfo choice
- SwitcherInfo choice arrow to MorphInfo choice
- Sex-specific .inkcharcustomization arrow to AppearanceInfo choice
- Sex-specific .inkcharcustomization arrow to MorphInfo choice
- AppearanceInfo choice arrow to .app appearance
- MorphInfo choice arrow to MorphTargetManager
- .app appearance arrow to inline components[]
- .app appearance arrow to partsValues: component .ent
- .app appearance arrow to partsOverrides:
- inline components[] arrow to Component
- partsValues: component .ent arrow to Component
- partsOverrides: arrow to Component
- Component arrow to .morphtarget (morphResource)
- Component arrow to .mesh (mesh)
- .morphtarget arrow to .mesh
- .mesh arrow to meshAppearance → chunkMaterials
- meshAppearance → chunkMaterials arrow to materialEntries → local instance
- materialEntries → local instance arrow to .mi chain → .mt / .remt
- MorphTargetManager dotted arrow to Component
Boxes
- .archive.xl
- customizations: female / male
- Sex-specific .inkcharcustomization
- female_cco(_ep1) / male_cco(_ep1); head · body · arms: options + groups
- SwitcherInfo choice
- activates other named options
- AppearanceInfo choice
- .app resource + definition name
- MorphInfo choice
- region + target name
- .app appearance
- (name = CC definition)
- MorphTargetManager
- applies (target, region) to every; morph component that has it
- partsOverrides:
- componentName → meshAppearance, chunkMask
- Component
- MorphTargetSkinnedMesh · SkinnedMesh · GarmentSkinnedMesh
- .morphtarget
- baseMesh + named deltas
- meshAppearance → chunkMaterials
- chunkMask hides submeshes
- materialEntries → local instance
- or external .mi
- .mi chain → .mt / .remt
- + .xbm, .hp, .mlsetup, .sp
Mermaid source
flowchart TD
XL[".archive.xl<br/>customizations: female / male"] -->|ArchiveXL merges| CCO
CCO["Sex-specific .inkcharcustomization<br/>female_cco(_ep1) / male_cco(_ep1)<br/>head · body · arms: options + groups"]
CCO --> SW["SwitcherInfo choice<br/>activates other named options"]
SW --> AO
SW --> MO
CCO --> AO["AppearanceInfo choice<br/>.app resource + definition name"]
CCO --> MO["MorphInfo choice<br/>region + target name"]
AO --> APP[".app appearance<br/>(name = CC definition)"]
MO --> MTM["MorphTargetManager<br/>applies (target, region) to every<br/>morph component that has it"]
APP --> INL["inline components[]"]
APP --> PV["partsValues: component .ent"]
APP --> PO["partsOverrides:<br/>componentName → meshAppearance, chunkMask"]
INL --> COMP["Component<br/>MorphTargetSkinnedMesh · SkinnedMesh · GarmentSkinnedMesh"]
PV --> COMP
PO --> COMP
COMP -->|morphResource| MT[".morphtarget<br/>baseMesh + named deltas"]
COMP -->|mesh| MESH[".mesh"]
MT --> MESH
MESH --> MA["meshAppearance → chunkMaterials<br/>chunkMask hides submeshes"]
MA --> ME["materialEntries → local instance<br/>or external .mi"]
ME --> MI[".mi chain → .mt / .remt<br/>+ .xbm, .hp, .mlsetup, .sp"]
MTM -.-> COMP
Read the diagram top-down. The three option types branch differently: an appearance option names an .app and one of its definitions; a morph option names a morph target that is applied by (target, region) across components; a switcher only activates other options. An .app appearance can list components inline and/or pull them from component .ent files; both are overridden by name. Rendered and inspected: see diagram review.
Naming conventions resource
Resource names are systematic, which makes a resolver and a human reviewer much faster. These meanings are inferred from consistent use across the extracted resources, not from CDPR documentation.
| Token | Meaning | Examples |
|---|---|---|
pwa / pma | player woman / man, average build | h0_, player_ |
wa / ma | woman / man (shared or NPC body) | t0_ |
h0_ | head skin mesh | h0_ |
he_ | eyes mesh (eyeballs and vanilla lashes as separate chunks) | he_ |
heb_ | eyebrows | heb_ |
hel_ | eyelash appearance resource (it re-uses the he_ mesh) | hel_ |
ht_ | teeth and mouth interior | ht_ |
hx_ | head decals and overlays: makeup, freckles/blush, pimples, tattoos, scars, face cyberware, personal-link decal | hx_ |
hh_ / hb_ | head hair / facial hair (beards) | hh_, hb_ |
i0_ / i1_ | intimate items (nipples, genitals, pubic hair) / head items (earrings) | i0_, i1_ |
t0_ / tx_ | torso and full body / body tattoos | t0_, tx_ |
a0_ | arms, hands, nails, arm cyberware | a0_ |
l0_ / n0_ | legs and feet / FPP neck | l0_, n0_ |
_ | a .mesh (the "component" mesh) | h0_ |
_ | a .morphtarget | he_ |
NN_ | skin tone: number, ethnicity family, name | 01_, 05_; variant 01_ |
2. The .inkcharcustomization resource
Root type gameuiCharacterCustomizationInfoResource resource source. There is one per body gender, and the game ships two pairs:
| Resource | Head / body / arms options | Head / body / arms groups |
|---|---|---|
base\ | 405 / 73 / 32 | 10 / 7 / 18 |
base\ | 483 / 52 / 23 | 11 / 5 / 13 |
ep1\ | same option names as base; 34 head, 2 body option diffs | same names; reordered groups, two extra arms group entries |
ep1\ | same option names; 15 head, 1 body option diffs | TPP/TPP_photomode gain eyelash_ and pimples |
The EP1 twin is the one in use when Phantom Liberty is installed. The reference save's tpp_ hash 14034739559546167190 equals FNV-1a64 of ep1\, a path only the _ resources reference resource. ArchiveXL's bundled fixes target all four paths [source: bundle/]. A resolver must therefore pick the pair that matches the installation, not assume the base\ path. The diffs are small (skin-colour definition metadata with unchanged names, a few index and link fixes, male lash/pimple group membership, the EP1 face rig) and recorded in the inventory.
Root fields
| Field | Content | Merged by ArchiveXL? |
|---|---|---|
headCustomizationOptions, bodyCustomizationOptions, armsCustomizationOptions | Handles to options (below) | Yes |
headGroups, bodyGroups, armsGroups | gameuiOptionsGroup {name, options: CName[]} | Yes, existing group names only |
perspectiveInfo | {name, fpp, tpp} group pairs for body and arms (e.g. holstered_ → holstered_ / _) | No |
uiPresets | three named .charcustpreset resources: corpo, nomad, street | No |
excludedFromRandomize | option names (genitals, pubic hair, etc.) | No |
version | 12 (matches the save's preset version 12) | No |
versionUpdateInfo | per-version option migrations for older saves, e.g. version 7 activates hair_ (definition default) wherever hair_ is active, without replacing it | No |
Merge behaviour: source ArchiveXL Extension.cpp lines 254–332; see the merge boundary.
Option types resource source
All options share name, index, defaultIndex, localizedName (a localisation key such as UI-CharacterCreation-eyes), uiSlot, link, linkController, editTags, hidden, enabled, randomizeCategory, censorFlag, censorFlagAction, onDeactivateActions.
| Type | Extra fields | Choice list | What a choice points at |
|---|---|---|---|
gameuiAppearanceInfo | resource (.app, soft), useThumbnails | definitions[]: gameuiIndexedAppearanceDefinition {name, index, localizedName, color, icon (TweakDBID, e.g. OptionsIcons.BrownLiquorice), tags, actions, randomizationInfo} | The .app appearance whose name equals definition.name. An empty resource plus definitions is a colour-only controller (skin_). |
gameuiSwitcherInfo | uiSlots[], switchVisibility | options[]: gameuiSwitcherOption {index, localizedName, names[], actions, tags} | Other option names to activate. Only the chosen choice's names are active; the switcher's other targets are inactive even when enabled (see slots and switchers). One choice can activate several options at once, e.g. hairstyle N activates hair_ and hair_. |
gameuiMorphInfo | none | morphNames[]: {index, localizedName, morphName}; None means no morph | A morph target name. Region = option name. |
Identity, and why indices are not identity
- Option identity:
namewithin the head/body/arms list. Names are stable across base/EP1 resource. - Choice identity:
(option, definition name)for appearances,(region, morphName)for morphs,localizedNamefor switcher choices. ArchiveXL merges choices on exactly these keys [source:Extension.cpp465–557]. - Indices are regenerated. ArchiveXL rewrites every choice index after merging [source:
RegenerateIndexes, lines 605–637], and vanilla itself contains duplicate option indices (makeupEyes_and09 _are both 460) resource. Never persist an index as identity. The Studio should match saves by10 (app hash, definition)and(region, target)as the resolver contract already requires.
Links: how one colour follows several options resource
link is a free-form key shared by a family of options. Options with linkController = 1 are the ones the UI drives; followers with linkController = 0 (usually hidden) receive the same choice index. Evidence: every member of a family has the same number of definitions, and the vanilla UI presets store equal values across the family.
| Link key | Controller(s) | Followers | Choices |
|---|---|---|---|
skin color | skin_ (empty resource, 12 colour definitions) | 97 female / 72 male options: all skin_, head/neck proxies, face cyberware, facial tattoos, body, arms (every cyberware and holster variant), nipples, genitals, feet | 12 skin tones |
hairstyle / hairstyle color | hairstyle, hairstyle_ / every hair_ | FPP hair shadows | 51 styles / 35 colours |
eyebrows color | each eyebrows_ | – | 35 colours |
beard color (male) | each beard_ | – | 35 colours |
makeupEyes color, makeupLips color, makeupCheeks color, makeupPimples color, piercings color | each style option | – | 14, 1–14, 4 or 16, 6, 16 |
makeupLips type | makeupLips, makeupLips_, makeupLips_ | – | 38 lip styles per finish |
nails_, nails_ | nails_, nails_ | nails_, launcher/mantis nail variants, nails_ | 54 colours, 2 lengths |
breast_, genitals_, nipples type, body_, genitals hairstyle type/color | the visible option(s) | FPP twins or the alternative anatomy options | – |
The wiki's CC options page (manavortex and redacted-c01, 2025) states that link syncs a selection across options wiki; its example, that hair colour also sets the beard, does not hold for 2.31: hair colours use hairstyle color and beard colours beard color, so nothing links the two resource (map contradiction C3). The vanilla UI preset ui_ stores skin_ and the value 10 for body_, nipples_, genitals_, every arm option and neck; nails_ resource. This is the strongest offline evidence for the link semantics; the exact propagation code is native and unread [hypothesis for edge cases, e.g. followers with a different choice count].
EP1 anomaly resource: in female_, eye-makeup styles 21–36 carry the link key LocKey#43269 instead of makeupEyes color, so under the link rule a colour chosen on styles 1–20 would not carry over to 21–36 (test ask 8).
Consequence for the Studio: choosing a skin tone is one control, but the renderer must apply that index to every linked option that is active, including tattoos and face cyberware whose colour follows skin tone.
Switcher link members: hairstyle and hairstyle_ are both link controllers of hairstyle, but list their styles in different orders (they differ at 20 positions on the reference installation) resource. The Studio's character context carries a switcher member's choice as the choice that activates the same options, not the same position, and leaves a member without such a choice at its own (CORE-61). In game, a face cyberware that swaps the row keeps the same hair, at the same position for style 5 runtime (session 4); a style at one of the 20 differing positions would separate the two rules.
How the Studio stores and applies links (character context, src/): only the choice a person set is stored, one per link family (the latest wins); every other member, controllers and followers, is derived when the V is prepared (CORE-50, CORE-51). Resetting a member resets the family to the V's own. A portable preset therefore holds one hair colour, not every hairstyle's colour.
Slots, switchers and visibility resource
- A switcher's
uiSlotsname the slots whose content it swaps.makeupLips_(slottype makeupLips_) →type makeupLips/_/glossy _switchers (slotmatte makeupLips) → 38 appearance options each (slotmakeupLips_). This is a three-level tree: finish → style → colour.color - A switcher owns its targets. An option that any switcher choice names is active only while an active switcher's current choice names it. Its own
enabledflag does not keep it active beside another choice. Most targets areenabled = 0, but each switcher's default target isenabled = 1(skin_,type_ 01 eyebrows_,color1 hair_,color1 cyberware_,00 hairstyleundercyberware), and the vanilla UI presets store it inactive whenever another choice is chosen. Targets of an inactive switcher are inactive too. An option that no switcher names is active whenenabled. Evidence: all six vanilla UI presets, 1,736 target checks with no exception, and the reference save; the native code is unread (evidence). This matches the wiki's rule that auiSlotholds one active option at a time wiki. - "Off" is what the creator labels Off. The creator shows a choice's
localizedNameand nothing else (see Labels below), so a choice is Off exactly when its label is the game's Off text (Common-Off, "OFF" in English) [source + resource]. In 2.31 that label sits on 17 feminine rows' choices (eye, lip, cheek and blemish makeup, scars, tattoos, piercings, pubic hair, body scars, and the legacy XF Eye Artistry layers on the reference installation). Adding nothing is not Off: the first nipples choice activatesnipples_, an option with no01 .appbecause those nipples are in the skin's albedo, and reads01; every face morph'sNoneis the base shape (01); a breast size'sNoneisDefault; the bald hairstyle is51resource. The other way round, the choice labelled Off on nipples (nipples_), genitals and face cyberware adds an appearance resource. What is true of adding nothing: those choices are a04 Nonedefinition (on an option with no.app, such aspiercings_,00 makeupEyes_,00 hair_, or as definition 0 of one with ancolor_ none .app, such asscars), a switcher choice whose options add nothing, or a morph'sNone; they name no appearance, and the save stores no entry for them. hidden = 1options never get their own UI row (proxies, FPP twins, colour followers).switchVisibilityandactionsexist on switchers and choices; vanilla actions are rare.switchVisibilitydoes not decide target deactivation:skin_andtype cyberwarehave it at 0 and still deactivate their defaults resource. Its meaning is unknown. source ArchiveXL does not copyswitchVisibility,hidden,enabledorlinkControlleronto an existing option during a merge, only onto a wholly new option.editTags=NewGame,HairDresser,Ripperdoc: which editing context may change the option (new game, mirror/hairdresser, ripperdoc) resource. Body skin colour isNewGame/Ripperdoc; makeup and hair addHairDresser. Which context applies is the menu's, not the caller's wish: a creator opened from the pause menu's scenario switch reportsNewGameand makes everyNewGamerow editable even when asked forHairDresserruntime (below).
Groups: who consumes a choice resource
Groups do not define the UI; they tell each consumer which active options belong to it. The same option can sit in several groups (e.g. eyes_ is in TPP, TPP_ and character_).
| Group (head) | Contents | Consumer |
|---|---|---|
TPP, TPP_ | head skin, eyes/eye colour, lashes, brows, teeth, facial tattoos, scars, pimples, morph regions, face rig | The third-person head (the head is an item in AttachmentSlots.TppHead [source: ArchiveXL PuppetState/]); photo-mode puppet. Consumer wiring hypothesis. |
face | face cyberware, piercings, eye/lip/cheek makeup | gameuiCharacterCustomizationFaceController {groupName: face} on the player entities resource. CCXL makeup, including the legacy selectors in the reference save, lands here. An XF Eye Artistry build whose option sat only in character_ rendered in the creator but not in photo mode; vanilla makeupEyes_ sits in both groups [runtime, 25 September (finish board results): consistent with this consumer, not a trace of it]. |
hairs, FPP_ | hair style/colour; FPP shadow-only hair. Multi-part hairstyles put each colourable part here as its own option (multi-part hairstyles); on the reference installation hairs holds only hair_ and mch_ options | gameuiCharacterCustomizationHairstyleController {groupName: hairs} |
beards (male only) | beard options | gameuiCharacterCustomizationBeardController |
FPP, TPP_, FPP_ | FPP neck; low-detail head/neck proxies | FPP body; LOD/shadow proxies hypothesis |
character_ | nearly every head option | The creator preview puppet. The save duplicates choices here. |
finalSceneBruises | finalSceneBruises (quest q307 beaten-up face) | gameuiCharacterCustomizationBrokenNoseController {finalSceneGroup} type exists source; quest-driven |
Body groups: FPP_/TPP_ (perspective pair), character_, genitals and breast (consumed by gameuiCharacterCustomizationGenitalsController {bottomBodyGroupName: genitals, upperBodyGroupName: breast}), lifted_/flat_ (…FeetController). Arms groups are holster/cyberware states (holstered_, holstered_, holstered_, holstered_, holstered_, their unholstered_, personal_) consumed via perspectiveInfo, plus nails (…NailsController) and …ArmCyberwareController. Female arms groups are split _/_; male groups are not resource.
Presentation: order, rows, sections, labels and swatches
How the creator presents the options, and where the Studio's catalogue (src/) takes each part from. Sources: the installed 2.31 game scripts (r6\, SHA-256 2119046f…ee86, no redscript backup beside it, decompiled with redscript-cli 0.5.31 decompile -f into a private scratch folder; files cyberpunk/, characterCreationBodyMorphListItem.script, characterCreationPunkRandomizerMenu.script and orphans.script), the compiled TweakDB (r6\, SHA-256 89c7ee67…14c5; tweakdb.bin 918f0acf…13f9 holds the same category list) and the English text resources.
| Question | Answer | Grade |
|---|---|---|
| One list or pages? | One list. InitializeList takes GetUnitedOptions(true, true, true) (head, body and arms together) and creates a row for each option that is isEditable && isActive && !isCensored. The creator has no section headers. | source |
| Order | The options' index ("determines order of switchers in Character Creator"; two top-level options with the same index show only the first) [wiki: icxrus's switcher theory and the CCXL overview]. Vanilla head indices run 5 (skin tone) to 700, arms 800–820, body 1010 and up. The two creators number differently: teeth and eye makeup are 310 and 450 in female_ but 540 and 550 in male_, where 300–380 hold the beard options (311 is beard_), so a mod option needs its index chosen per creator (XF Eye Artistry uses 311 and 541) resource. The sort itself is native and unread. | wiki + resource + [hypothesis: native sort by index] |
| Rows | A row is found again by uiSlot (UpdateOption compares lookupOption.info.uiSlot with the row's option): when a switcher changes its target, the new option takes the old one's row. So options sharing a slot are one row. | source |
| Which options get a row | isEditable and isActive are native. In the data, every hidden option and every link follower (a link key with linkController = 0: the neck, FPP twins, the skin types' tone) lacks a label; the Studio shows neither. editTags must include the editing context (NewGame, HairDresser, Ripperdoc; the menu calls ApplyEditTag). | source + [hypothesis for followers] |
| Selector or colour grid | useThumbnails on an appearance option spawns the colour picker (a grid, six per row); anything else a stepper. | source |
| Sections | The creator's randomizer lists categories from TweakDB CharacterRandomization.CharacterRandomizationCategories.list: CharacterRandomizationCategoryUI records whose categoryName is a text key and whose categoryType.enumName equals the options' randomizeCategory. 2.31 order: Skin, Hair, Eyes, Eyebrows, Face, FacialHair, Scars, Tattoos, FaceModification, Makeup, Nails, Body (UI-CharacterCreation-skin to -body). The Studio uses this as its section order and titles. A slot's Off placeholder may carry the default Body (masculine beard_), so a row is filed under the category most of its options name. | resource + [source: InitializeRandomizerLocksList] |
| Labels | Options and choices pass localizedName straight to the text widget (SetText(label, option.info.localizedName)), so a key shows its text and anything else shows as written (the choice numbers 01, 02). LocKey#<n> is a primary key; UI-CharacterCreation-eyes and Common-Off are secondary keys. | source |
| Where texts live | base\ in archive/ (60,303 English entries) and ep1\ in archive/ (10,276; no key shared with base). Each is a JsonResource holding localizationPersistenceOnScreenEntries {primaryKey, secondaryKey, femaleVariant, maleVariant}. onscreens_ beside each is byte-identical in 2.31. No creator text has a male variant. | resource |
| Player's language | %LOCALAPPDATA%\, group /, option OnScreen (e.g. en-us). | resource |
| Mod texts | ArchiveXL localization.onscreens in any .xl (a map of language to a path or list; the first language is the fallback; a bare path or list means English; extend appends to another file's unit). Per unit in load order: the player's language merges first, then (unless it is the fallback) the fallback language, which only adds keys that are missing; a unit without files for the language merges its fallback's, adding only (OnLoadTexts). Every entry is identified by its primary key alone: a mod entry with an existing primary key replaces that whole entry, secondary key included, and one with a new primary key but an existing secondary key replaces nothing by ID. An entry without a primary key is added twice, under FNV-1a 32 of its secondary key (keeping the key) and under FNV-1a 64 of it (without) (MergeTextResource, MergeTextEntry). The Studio's text table keys entries the same way (src/, PIPE-47); which of two entries holding the same secondary key the native lookup answers is not read (the Studio answers with the one merged last) hypothesis. The reference installation declares 924 English files in 735 archives. A language whose lang_ isn't installed (no mounted archive holds its texts) falls back to English, with the gap texts-language-missing; texts that are there but couldn't be read keep the language and say so instead (texts-transient, texts-need-wolvenkit). | source |
| Swatches | The swatch tints its background with the choice's color and draws its icon, a UIIcon record, from atlasResourcePath (an .inkatlas) and atlasPartName (SetTintColor in the list item). Icons added by TweakXL mods are not in the compiled blob: CCXL mods declare them in r6/ YAML, usually one $instances template per mod (OptionsIcons.<mod>_ of type gamedataUIIcon_). On the reference installation the compiled blob and the installed YAML together resolve every eye colour's icon, across 11 atlases (1 vanilla, 10 from mods; 551 parts used by colour rows). The vanilla creator atlas keeps its parts in slot 0, with none at the top. | source + resource |
| Camera per option | GetSlotName picks the creator camera by option name and slot (UI_, UI_, UI_, UI_, UI_, UI_, UI_, UI_, else a head or body preview). This is the one grouping the game keeps only in UI code; the Studio doesn't use it yet. | source |
The TweakDB blob is read with WolvenKit's layout (flats by type, (TweakDBID, index) keys). A record's field ID is the CRC-32 of the record name continued over .<field>, so fields of records known only by ID (the category list's entries) are addressable without their names (src/). Every count in the blob is bounded by the bytes that could hold it before anything is allocated (PIPE-49).
In the Studio. The host builds each body gender's catalogue once per installation and sends the Character panel a compact projection (src/, xfs/, about 319 KB for the reference installation's 943 options and 131,856 choices): sections, rows, options (label, type, choice count, Off and default keys, link, the switchers that turn it on), with mods and coverage notes in tables by index and each mod's maker group (the base game; XF Studio's own mods, known by the xfs_ prefix of their creator resource; the author Vortex records as author or uploader; else the mod's own name, since MO2's meta.ini records no author [observed: 1,000 meta.ini files on the reference instance]), so a row can group its choices by who made them; an option's choices come in pages of 240 when its row opens, and a search over every choice runs on the host. A switcher choice is identified by the options it activates as well as its name (the creator picks by position, so two same-named choices stay apart; CORE-70). An option's Off is the choice the creator labels Off (labelledOff: the game's Off text, or "Off" written out), never a choice inferred from adding nothing (off, kept only as structure: a save stores no entry for it).
Swatches in the Studio (src/, src/): every colour row's choices get a swatch derived from the resource that wins for them, the way the preview draws it: a hair or eyelash profile's baked root-to-tip row (hair shading §3) as five colours, the iris ring's base colour, the skin's toned albedo, a decal's colour times its texture's coverage-weighted colour (brows through their gradient map). Choices that follow one link share a swatch per choice key, answered by one member's chain (a hair colour across 522 hairstyles); a colour-only controller (the skin tone) is answered by a drawn follower at the same position, as the link applies it. On the reference installation 2,231 swatches cover 127,648 of the 129,064 choices in colour rows; the rest are multilayered eyes (which have icons) and a few chains that resolve nothing. Hair-tone packs that replace vanilla profiles at their paths make every vanilla hair, brow and lash swatch a mod's colour: a colour whose profile, gradient, colour texture or colour-setting material instance comes from a mod other than the choice's own supplier is marked replaced (24,888 choices), and the panel shows its derived colour instead of the icon, which would show the replaced colour. Icons come from the compiled TweakDB overlaid with the installed TweakXL YAML (src/, src/). What the preview draws of each option is the preview side's projection (catalogueCoverage, src/), never stored in the catalogue (CORE-60); a conditional option settles from the parts the shown V's record draws. A head option whose resource names no category (the enum's default, Body) is filed with the rows around its position instead (UI-60). A base creator resource that a mod archive replaces names that mod as the source of its options and choices (PIPE-46).
3. From .app to pixels
.app definitions resource wiki
Every vanilla CC .app holds both body genders' appearances, distinguished by name (…pwa…/…pma…, female_/male_, w_/m_) and visualTags (Female/Male). The CC definition name equals the appearance name. Typical definition:
partsValues[]: one or more component.entfiles, e.g.…\.appearances\ entity\ face_ decals\ hx_ 000_ pwa_ _ basehead_ makeup_ eyes_ 01.ent partsOverrides[].componentsOverrides[]:{componentName, meshAppearance, chunkMask}applied to that.ent's components. The chunk mask only hides: it is ANDed with the component's own, so an override can't show a chunk the component hides [source: ArchiveXLComponentState::AddHidingChunkMaskOverride] [wiki: the.apppage, "You can't un-hide something via partsOverrides"]. A mod's copy of an.appcan therefore mask a creator part out entirely (body animation §4).components[]: inline components. In vanilla CC.apps they mirror thepartsValuescomponents with the overrides applied, but inline components can also be the only source of geometry (PRC and the legacy generator rely on this; see section 6). The wiki chain map coverspartsValues/partsOverridesscreenshots and ArchiveXL's empty-partResourcehandling.
Three variant mechanisms appear, often combined:
| Mechanism | Used by | Evidence |
|---|---|---|
| meshAppearance selects material set | skin tones (01_), eye colours (gradient_), hair/brow/lash colours (blonde_, eyelashes_), makeup colours (black_), lip finish (black_ glossy, black_ matte), piercing metals | resource |
| chunkMask selects submeshes | scars (one scars_ appearance; each scar is a different mask), eyes vs vanilla lashes (same he_ mesh: eyes mask …551614 hides chunk 0, lashes mask …551609 hides chunks 1–2), earrings, body/feet | resource; the he_ mesh has three chunks (12,393 / 668 / 152 vertices) per native-eye intake |
| Different morphtarget / mesh | makeup styles (makeup_), tattoo designs, hairstyles, beards, cyberware variants | resource |
Makeup and decal .app overrides also name the head skin component MorphTargetSkinnedMesh7243, which the decal .ent does not own; its purpose is unknown [hypothesis: harmless carry-over from a shared authoring template].
Components resource
| Component type | Used for | Deformation |
|---|---|---|
entMorphTargetSkinnedMeshComponent (morphResource) | head, eyes, brows, teeth, all head decals (makeup, tattoos, scars, cyberware), earrings, female body/breast, genitals, beards | Skinned and receives CC morph targets |
entSkinnedMeshComponent (mesh) | hair, hair shadow meshes, FPP neck, seam-fix body pieces | Skinned only |
entGarmentSkinnedMeshComponent (mesh) | arms, nails, nipples (FPP), feet, body scars/tattoos (male), censorship underpants | Skinned; participates in garment/visual-tag hiding ([ArchiveXL VisualTags.xl]) |
entAnimatedComponent | face rig graph (…paperdoll_), hair dangle physics, genital dangle | Animation |
entMorphTargetManagerComponent on the player entity resource is the likely applier of the region/target pairs to all morph components hypothesis.
Morph targets resource wiki
.morphtarget=baseMeshreference plus an embedded geometry blob andtargets[], each{name, regionName}, e.g.{h091, eyes}resource. WolvenKit's GLB export names the shape keys<name>_(<region> h091_), which is what the Studio's assets use; the head/plate master carries 105 female targets (5 regions × 21).eyes - Female morph options offer
Noneplus 21 targets per region (h011…h211for eyes,h0N2nose,h0N3mouth,h0N4jaw,h0N5ear); male offers 20 (…h201) resource. The last digit encodes the region. - Every component that follows the face carries matching targets, but only for the regions it needs: head and brows carry all 105; eyes and lashes the 21
eyestargets; a vanilla earring placeholder the 21eartargets; a nose ring the 21nosetargets; a PRC linked-mesh ring all 105 resource. The pair(target, region)is the key, and the save stores both. A component without a region's targets simply does not follow that slider [hypothesis; consistent with every inspected resource]. - The eyes follow the eye shape through their own morph resource. The installed eye entity
he_binds000_ pwa_ _ basehead.ent he_(21000_ pwa_ _ morphs.morphtarget eyestargets,baseMesh=he_), separate from the head's000_ pwa_ c_ _ basehead.mesh h0_; both carry the pair000_ pwa_ _ morphs.morphtarget {h091, eyes}and the other 20, and the 34 joint matrices the twoh091targets share are identical resource (experiment 015 trace). A shape moves the eyeball, lash and wetness chunks by up to about 4 mm. XF Studio's derived preview applies each eye-shape choice to every mesh carrying that(target, region)pair (src/); with the eyes left static, 18 of the 21 shapes left the eyeball showing through 35–354 of the 680 lid/socket rim vertices; with the eye targets, 14 shapes are within one vertex of the neutral assembly's 12, the worst is 41 (face-morphs.ts h121), andh181/h191rise from 3/7 to 16/19 (offline, derived 2.31 assets, nearest-vertex signed-gap proxy) [resource; game rendering not compared]. - Resource order is not option order. The
eyesoption'smorphNames(None,h011…h211) follow the morph resources' target order, but the vanilla femalenoseoption listsh112afterh162resource. Choice identity is the(region, target)pair; a selector must take its order and labels from the merged CCO, never from a mesh. The creator's choice labels are two-digit positions (localizedName01=None,10=h091) resource. - The morphtarget guide (no page author evidenced) says the engine activates targets by name and region, face regions blend together while chest size is exclusive, and mods cannot add new activation names, only reuse existing ones wiki. The creator side can grow: ArchiveXL appends a morph choice whose
localizedNameis new (Extension.cpp504–528) source; whether the engine then applies a target name that only a mod's morph resources carry is untested hypothesis, and no installed mod adds morph choices. - Facial rigs. Each head
.appuses a face-rig component; the game ships 22 female and 21 male per-shape skeleton rigs, and the NPV rig guide (saltypigloaf, 2025) reports that vanilla V always uses rig 000 whatever the sliders, a known cause of eye clipping that community rig-fix mods address wiki. The Studio's saved-head morph binds should account for this (brow idle gap, experiments 012/015). - Exporting a morph target with its rig. WolvenKit resolves a
.morphtarget'sbaseMeshitself: CLIuncookof the morph target from the game archives (orexport … -gp <game>of an extracted copy) writes a bone-bound GLB with the base mesh's skin (254 joints, two four-influence sets for the female head) and every target as a shape key. No custom exporter is needed. CLI 8.17.4 reproduces experiment 012's bound head byte for byte; 9.0.1 gives identical positions, UVs, influences and morph deltas but gives joint nodes rest rotations with matching inverse binds, so rest deformation is identical resource (experiment 013). The export keeps native vertex and triangle order, so the eye plate recipe's native face IDs select the same rows in the GLB resource. - Body morphs:
breast(female:t0_/ none /000_ wa_ base_ _ full_ breast_ small _),big nails_/l nails_(long nails),r penis_/base penis_size. There is no vanilla body-shape slider beyond these.circumcised
Meshes and materials
meshAppearance → chunkMaterials names → materialEntries (local buffer or external .mi) → .mi chain → template. Resolved examples: skin → skin.mt with a four-level .mi chain (saved skin chain); teeth → teeth_ → skin.mt with its own skin profile; hair → hair.mt + .hp profile (hair profile); brows → mesh_-family instance with a gradient (brow audit); eye makeup → makeup_ → makeup_ → mesh_ on one shared mesh for all 36 styles; lipstick → mesh_ or mesh_ depending on the style; cheeks, pimples, scars, facial tattoos, face cyberware and male stubble → mesh_; piercings → multilayered.mt; the eye mesh's third chunk (wetness) → eye_ resource. The per-option templates, key parameters and chunk roles are tabulated in head CC rendering §1. Shader behaviour: materials and shaders.
4. Every CC detail, and what drives it
The file-chain map carries the same categories with their save groups, ArchiveXL support, installed CCXL extensions and the Studio's status. Female and male names differ only where stated. "Opt" is the option type (A = appearance, S = switcher, M = morph). All counts are vanilla 2.31 resource; per-option detail is in the inventory. The wiki's CCXL overview has a matching table of vanilla uiSlot names and option types wiki. Its character-creator cheat sheet (last updated January 2025) lists fewer choices than the 2.31 resources (e.g. eye makeup 1–20 against 36 styles, lip styles 1–20 against 38, face cyberware 1–8 against 16); the resource counts are current, but which choices the UI actually shows is test ask 9.
| Detail | Options (type) | .app / resources | Variant mechanism | Notes |
|---|---|---|---|---|
| Voice tone | None: row 0 of the creator list is a separate voice-over switcher widget, not a creator option [source: characterCreationBodyMorphMenu.script UpdateVoiceOverWidget, OnVoiceOverSwitched] | None | – | Sets the state's IsBrainGenderMale and plays a sample (TriggerVoiceToneSample); picking a body gender sets both genders alike (characterCreationGenderBackstoryMenu.script) source. Saved as isBrainGenderMale (§7) |
| Skin tone | skin_ (A, colour-only, 12 tones, controller of skin color) | none of its own; drives every linked option | Linked index → each follower's meshAppearance. On the head the tone is only the material chain's TintColor/TintScale (multiply, or overlay when the scale is negative); the albedo comes from the skin type (head CC rendering §2) | Tones: 01_, 01_, 02_, 02_, 03_, 03_, _, _, 04_, 04_, 05_, 06_; same for both genders |
| Skin type (complexion) | skin_ (S, 5) → skin_ (A, 12 defs each) | h0_, …_ → h0_ → h0_ → h0_ | Different .app per type; the head mesh has 60 local materials, one per (tone, type), whose Albedo is the type's h0_ | Chain to textures: saved skin chain |
| Facial morphs (eyes, nose, mouth, jaw, ears) | eyes, nose, mouth, jaw, ear (M, 21/20 targets + None) | head and all face-following morphtargets | Named morph target per region | No separate cheek/brow sliders in vanilla |
| Eye colour | eyes_ (A, 71) | he_ → he_ (21 eye targets) | meshAppearance (e.g. gradient_), chunk 0 hidden | ArchiveXL remaps this app to archive_ and adds @eyes material names source; mod example: modded eye resolution |
| Eyelashes | eyelash_ (A, 35 colours) | hel_ re-uses the eye component he_ | meshAppearance eyelashes_ with chunks 1–2 hidden | No lash style option in vanilla; CCXL lash styles need a core mod that adds an Eyelashes switcher (eyelashes_ on the reference installation), per icxrus's CCXL eyelashes guide wiki (head details) |
| Eyebrows | eyebrows (S, 14 choices: 13 styles plus eyebrows_, which has no resource) → eyebrows_ (A, 35 colours each, link eyebrows color) | heb_ → face_ → heb_ (105 targets) | One .app per style, one shared mesh; style = texture set, colour by meshAppearance (<colour>_) | ArchiveXL remaps to per-sex archive_ source |
| Hair style + colour | hairstyle (S, 51) → hair_ (A, 35 colours, tags e.g. brown_) + hair_ (A, hidden shadow); hairstyle_ twin when face cyberware changes the scalp | hh_ → entity\ → hair .mesh (e.g. hh_) + shadow mesh + dangle animgraph | meshAppearance = colour name → material with .hp profile | Hair colour tags are appended to TweakDB ItemFactory.HairColors.hairColors by ArchiveXL for CCXL colours source; the reference save's tags are ash_ and Short: the chosen colour definition's tag and the chosen hairstyle switcher choice's length tag. Every non-bald vanilla hairstyle choice carries one of Long, Short, Buzz or Dreads resource |
| Beard (male) | beard (S, Off + 12) → 13 part switchers beard0…12 (S, slot beard_, 1–7 parts each, 57 in all) → 57 beard_ (A, slot beard_, 35 colours) | facial_ → beard morphtarget + beard_ | Two-level switcher; colour by meshAppearance with @beard dynamic names [source: PlayerCustomizationBeardFix.xl] | Group beards |
| Eye makeup | makeupEyes (S, Off + 36) → makeupEyes_ (A, 14 colours) | makeup_ → face_ → hx_ | Style = resource; colour = meshAppearance (black_) | Group face. The XF Studio product target |
| Lipstick | makeupLips_ (S: none / regular / glossy / matte) → makeupLips, _, _ (S, 38 styles) → 114 style options (A, 1–14 colours) | makeup_ → one hx_ | Finish = .app suffix and meshAppearance suffix _/_; style/colour by meshAppearance | Named NPC looks (nicola, trauma, mox, …) are single-colour styles; regular-finish styles 30–38 have no edit tags |
| Cheeks / freckles / blush | makeupCheeks (S, Off + 24) → makeupCheeks_ (A: 1–4 freckles, 4 colours; 5–24 blush makeup_, 16 colours) | makeup_ → hx_ | Style = resource; colour = meshAppearance | Group face. The game also ships a makeup_ folder with a differing hx_ that no vanilla creator resource names resource |
| Pimples / blemishes | makeupPimples (S) → makeupPimples_ (A, 6) | pimples\ | meshAppearance + chunkMask | Group TPP |
| Facial tattoos | facial_ (S, Off + 15) → facial_ (A, 12 tones, link skin color); legacy hidden tattoo (A, 7) | tattoos\ → entity\ → tattoo morphtarget | Design = resource; colour follows skin tone (meshAppearance 01_) | |
| Face scars | scars (A, Off + 13) | hx_ → hx_ | Same meshAppearance, different chunkMask per scar | |
| Face cyberware | cyberware (S, Off + 16) → cyberware_ (A, 12 tones) | hx_ (386 appearances) → entity\ | Design = component; tone = linked meshAppearance | Switcher choice also swaps hairstyle ↔ hairstyle_. Since patch 2.2, options 8–16 are appearances inside other decal meshes (e.g. makeup_ carries cyberware_), per the head cheat sheet wiki. In 2.31 the resolver also finds this for lower-numbered choices (option 03 draws the freckle mesh's cyberware_), and the appearances exist in the player meshes, contrary to the sheet's warning resource |
| Piercings | piercings (S, Off + 14 female, Off + 16 male) → piercings_ (A, 16 metals/colours) | piercings\ → up to three entity\ + morphtargets | chunkMask per style over four shared piece banks, meshAppearance per colour | Piercings and jewellery |
| Teeth | teeth (A, 5: default, silver, gold, …) | ht_ → ht_ → ht_ | meshAppearance | Mouth shape is the mouth morph, not this option |
| Neck / proxies / face rig | neck, tpp_, fpp_ (hidden, skin color), tpp_, …_ | n0_, proxy\, face_ (animgraph + facial anims) | – | Not user-visible; required for a full in-game assembly |
| Body skin | body_, fpp_ (+ _) (A, 12, hidden, skin color) | t0_ → female t0_; male t0_ | Tone by meshAppearance; chunkMask hides regions | |
| Breast size (female) | breast (M: small / none / big) | t0_ targets | Morph | Group breast, genitals controller |
| Nipples | nipples (S) → nipples_ female, _ male (A, 12 tones) + FPP twins | i0_, i0_ | Tone link | |
| Genitals and pubic hair | genitals (S, 4 combinations of genitals_ + penis_ size morph + *_ switcher) | i0_ (88 appearances, both anatomies), i0_ | Tone link; size morphs; hairstyle colour link | Available to both body genders; excluded from randomise; censorship options (underpants, *_) |
| Body tattoos / scars | body_ (S, Off + 7) + FPP twin; body_ (S, 3 choices over 5 options) | t0_, t0_, scars_ | Tone link; scars by chunkMask | Female body tattoos are morph-skinned; male ones garment meshes |
| Feet | lifted_, flat_ (A, hidden, tone) | l0_, l0_ | Tone link, chunkMask | Chosen by footwear (feet controller), not by the player |
| Nails | nails_/nails_ (M, long/short) + nails_ (A, 54: plain, …_ designs) | a0_, a0_ | Morph length; colour/design by meshAppearance | Arms part; nails group |
| Arms and arm cyberware | ~25 hidden arm options per holster/weapon state (A, 12 tones) | a0_ and arms\ | Tone link; state chosen by equipped cyberware via perspectiveInfo groups | Not a CC choice beyond tone |
5. How ArchiveXL/CCXL extends the lists
Details and line references: CCXL merge boundary and catalogue probe. Summary source ArchiveXL 1.27.3:
- Registration. An
.xllistscustomizations: {female: [...], male: [...]}. ArchiveXL prefetches each resource and merges it into the loaded base male or female resource (MergeCustomEntries). There is no male/female sharing: a mod that supports both body genders registers two resources (e.g.*_andpwa *_).pma - Fixes first.
FixCustomizationOptionsappliesresource.fix.pathsto base options before any mod merge: e.g. vanillahe_→000_ _ basehead.app archive_,xl\ …\ he_ 000_ pwa_ _ basehead.app heb_and000_ _ basehead_ NN.app hel_likewise. Each remap registers an app override so saved descriptors using the old path still resolve.000_ _ basehead.app - Groups. Group entries are appended only to existing groups with the same name; a new group name is ignored.
- Named options merge into the same-named, same-typed option (definitions by
name, morphs and switcher choices bylocalizedName), otherwise the whole option is appended. A new option may carry any metadata; a merge into an existing option does not change its top-level metadata. A merged choice whose key already exists replaces the existing choice (targetChoice = sourceChoice, lines 473, 517, 544), so an addition is additive only when its keys are new. Vanilla switcher choices are often labelled by position (01,02, …): a mod choice labelled15in the piercings row would overwrite masculine V's vanilla style 15 (jewellery §5). - Anonymous overlays (no
name) target existing options byuiSlotand/orlink, with*suffix wildcards, in a second pass. Source note: the link-wildcard flag is computed from the slot string (Extension.cpp:379readssourceSlotStr), so a link-only wildcard does not appear to take effect [source reading; not runtime-tested]. - App overrides. When a merged definition's option names a different
.appthan the target option, ArchiveXL records(target app, definition) → (mod app, definition)and rewrites appearance descriptors atOnGetAppearances/OnChangeAppearance(s). This is why a saved choice can carry the vanilla-option-associated app hash yet render from a mod app (see the Unique Eyes example in the catalogue probe). - Dynamic customization appearances.
FixCustomizationAppearanceclones a template appearance inside theplayer_scope for a missing requested name; the suffix aftercustomization.app _becomes the mesh appearance. Combined with mesh_ names/contextfixes (@eyes,@lashes,@beard,@cap,@dread), one template can serve many colours. The scopes and extensions guide (manavortex) illustrates the makeup variant: an@contextmaterial namingMakeupBaseMaterial, an@makeupmaterial pointing at*…\, and one small colour{material}.mi .miper colour; it warns that several styles in one mesh block later colour extension wiki. - Indices are regenerated; hair-colour tags go to the TweakDB flat.
- Older saves. A new option that is enabled, not hidden and absent from the loaded state is added to it (appearance and morph options) or activated (switchers) at its default (
OnInitAppOption,OnInitSwitcherOption, lines 97–143), so a save made before a CCXL mod was installed shows the new row at its default choice. - Removal. ArchiveXL can remove every custom entry it registered (
RemoveCustomEntries) source; whether the game calls this on reload is runtime behaviour hypothesis. What the game draws for a saved choice whose mod is gone is unread; icxrus's eyelash guide tells players to select the vanilla lashes before uninstalling a lash mod, because the lashes otherwise disappear [wiki; community runtime, not ours].
Which categories ArchiveXL itself supports [source: bundle/]. Only five categories have a bundled fix, scope and colour patch: eyes (@eyes), lashes (@lashes), brows (@brows), hair (@long, @short, @cap, …) and the masculine beard (@beard). They alone are in the player_ scope, so only their mod options get the dynamic appearance cloning of rule 7. A CCXL option for makeup, piercings, tattoos, scars, cyberware, teeth, nails or the body must define every appearance its definitions name; the mesh side's @context / *{material} expansion still works for any mesh (mod loading §5). The body-tattoo guide's CCXL route is an unnamed switcher with only link: body_, which the overlay pass adds to both the third- and first-person tattoo switchers, since they share that link [wiki: ccxl_] source.
Entity patches: always-on parts outside the creator [source: ArchiveXL ResourcePatch/ 252–345, 740–857]. A resource.patch whose source is an .ent merges into the target entity template its appearances (replacing same-named ones), resolved dependencies and visual tags, and, when the entity or a part .ent is built, adds the patch entity's components. The wiki's facial-cyberware guide (mana vortex) uses this to put a custom decal on every player entity (third person, photo mode, cutscene, reflection, EP1): always drawn, never a creator choice and absent from the save wiki. On the reference installation three mods patch .ent files, among them a face-rig fix that patches every feminine and masculine player entity resource. The Studio does not apply .ent patches yet (R6b).
XF Studio's single eye-makeup selector (one switcher with Off and complete-look choices in the face group) follows this model; see experiment 005 and the ArchiveXL strategy.
Hair-colour additions
Every CCXL hair-colour pack on the reference installation (six, 150 colours) follows the wiki's hair-profiles template (study):
- Anonymous slot overlays [source + resource]. The pack's creator resources hold only unnamed appearance options with
uiSlothair_,color eyebrows_,color eyelash_(andcolor beard_in the masculine resource), each listing the colours. ArchiveXL merges every mod's named options before any overlay (color MergeCustomEntries), so the colours reach all 73 feminine and 78 masculine hairstyle colour options, cyberware twins and CCXL hairstyles included, whatever the load order. Each overlay appends the same list to every matching option, which keeps thehairstyle colorlink index aligned for styles that start from the 35 vanilla colours. Exact-slot overlays also reachhair_(the bald choice) and femininecolor_ none eyebrows_(brows Off); what those rows then show is untested.color0 - Definition name → mesh appearance source.
FixCustomizationAppearancestrips a leadingNN_(two digits and an underscore), takes what follows the last_, or else uses the name whole (_ 38_→ash_ brown ash_;brown sigh4_unchanged). A pack'smch_ blondeice patch.mesh, patched into the player hair, hat-hair, brow, lash and beard scopes, adds that empty appearance with ten@familytemplates whose paths expand{material}to the pack's.hpand cap gradient (mod loading §5). ArchiveXL's own fix gives 295 vanilla meshes the matching template families. - Tags [source + wiki]. For
hair_targets the choice'scolor tagsgo to TweakDBItemFactory.HairColors.hairColors; garment dynamic appearances substitute the chosen colour's tag as{hair_, so hair under hats needs the tag to equal the mesh appearance name. One installed pack leaves all 21 tags at the template placeholder.color} - Brows, lashes and beard get the same names in their own rows, with no engine link to the hair row. Brows draw the cap gradient's last texel; lashes draw the profile's tip (eyebrows §3, hair reference §9).
- Rendering never consults the creator list: a saved
(app, definition)is cloned from the requested name alone, so a colour whose definition is gone but whose patch appearance and files remain should still render [hypothesis; the study's checklist item 9].
Multi-part hairstyles (Multicolored Hair)
The Multicolored Hair framework (Nexus 21613, 1.1) makes multicoloured hair from geometry: a hairstyle is cut into up to three meshes, each an ordinary CCXL dynamic hair with its own colour row (study).
- Its own switcher [source + resource]. The vanilla
hairstyleswitcher'suiSlotsare fixed athair_andcolor hair_, and a merge never changes an existing option's top-level fields (§5 rule 4), so extra colour rows need a new switcher. The framework'scolor_ fpp mch_(linkhair_ switcher mch_feminine,hairstyle mch_masculine) owns slotshairstyle_ pma mch_andhair_ part_ 01…03 mch_. Its Off choice names four resource-less placeholder options.hair_ fpp - Hairstyles join by link [source + resource]. An MCH hairstyle ships an anonymous switcher with only
link: mch_. The overlay pass (§5 rule 5) appends its choice to the framework's switcher. The choice names one named appearance option per part, each a link controller ofhairstyle hairstyle_, plus an FPP shadow option. Switcher choices merge bycolor_ N localizedName, so two MCH hairstyles with the same name replace each other. - Parts render like any CCXL hair resource. Each part has its own
.appwith one template appearance, in theplayer_scope, and its own mesh withwa_ hair.app @context/@long/@capentries, in theplayer_scope. Vanilla and pack colours therefore reach every part mesh through the existing patch meshes. A colour appears in a part's creator row only when its pack also overlayswa_ hair.mesh mch_. A pack that doesn't needs a creator-only patch listing its definitions on those slots, which is what the framework's compatibility patch for redacted-c01's pack is.hair_ part_ 01…03 - Consumer resource. Part options sit in
hairsandcharacter_, so the hairstyle controller draws them beside, and independently of, the vanilla hairstyle. The framework's instructions set the vanilla hairstyle to 51 while MCH is on. The Studio draws every active option incustomization hairsas hair by this consumer rule (no framework slot names), so each part draws with its own colour.
6. Case study: why PRC piercings work in game
eagul's PRC Fully Modular Jewellery Framework predates CCXL and uses no .xl, .inkcharcustomization, TweakXL record, script or ArchiveXL feature [resource: archive listing in the PRC inventory]. It works purely by replacing files that vanilla already reads. Once the Studio interprets archives the way the game does, PRC support is a side effect, not an adapter.
Arrows
- Vanilla CCO option arrow to i0_000__earring_14.app
- i0_000__earring_14.app arrow to Kept vanilla part:
- i0_000__earring_14.app arrow to 128 inline components fpm1…fpm128 (mpm for male)
- 128 inline components fpm1…fpm128 (mpm for male) arrow to fpmN.morphtarget placeholder (unfilled slot)
- 128 inline components fpm1…fpm128 (mpm for male) arrow to fpmN.morphtarget from an item archive (filled slot)
- fpmN.morphtarget from an item archive arrow to baseMesh: vanilla earring mesh or linked .mesh
- baseMesh: vanilla earring mesh or linked .mesh arrow to chunkMaterials → .mi → multilayered.mt / .mlsetup
Boxes
- Vanilla CCO option
- female piercings_12 · male piercings_14; resource = i0_000__earring_14.app, 16 colours
- i0_000__earring_14.app
- same depot path, replaced by the PRC framework archive; (mod archive beats base archive)
- Kept vanilla part:
- i1_000_pwa_earring__basehead_04.ent; + override, chunk mask …610
- 128 inline components fpm1…fpm128 (mpm for male)
- all with the definition's one meshAppearance
- fpmN.morphtarget placeholder
- zero render chunks → draws nothing
- fpmN.morphtarget from an item archive
- same depot path, sorts before PRC_z_999 → wins
- baseMesh: vanilla earring mesh or linked .mesh
- has appearances silver, gold, … wood
Mermaid source
flowchart TD
CCO["Vanilla CCO option<br/>female piercings_12 · male piercings_14<br/>resource = i0_000__earring_14.app, 16 colours"] --> APP
APP["i0_000__earring_14.app<br/>same depot path, replaced by the PRC framework archive<br/>(mod archive beats base archive)"]
APP --> PART["Kept vanilla part:<br/>i1_000_pwa_earring__basehead_04.ent<br/>+ override, chunk mask …610"]
APP --> BANK["128 inline components fpm1…fpm128 (mpm for male)<br/>all with the definition's one meshAppearance"]
BANK -->|unfilled slot| PH["fpmN.morphtarget placeholder<br/>zero render chunks → draws nothing"]
BANK -->|filled slot| ITEM["fpmN.morphtarget from an item archive<br/>same depot path, sorts before PRC_z_999 → wins"]
ITEM --> LM["baseMesh: vanilla earring mesh or linked .mesh<br/>has appearances silver, gold, … wood"]
LM --> MAT["chunkMaterials → .mi → multilayered.mt / .mlsetup"]
| Step | Mechanism | Grade |
|---|---|---|
| 1. Option | Vanilla female piercings_ and male piercings_ both point at i0_ with 16 colour definitions. The author's "option 12 for female, 14 for male" is simply this vanilla mapping (the female list skips earring_/13). | resource |
2. .app replacement | PRC_ contains the same depot path base\. A mod archive's copy of a path wins over the base game's. | Tool sources (WolvenKit's resolver and ArchiveXL's archive groups put mods first) plus the mod working for users; native engine rule not read [source + community runtime] |
| 3. Kept vanilla geometry | Each of the 32 appearances keeps vanilla style 12's partsValues part and override, with chunk mask changed from …612 to …610. Selecting option 12 shows that vanilla piece plus the bank. | resource |
| 4. Slot bank | Each appearance adds 128 entMorphTargetSkinnedMeshComponents directly to components[] (fpm1…fpm128 female, mpm… male), each referencing eagul\. Inline components are part of the appearance just like partsValues components. | resource [wiki: .app guide, components/partsValues] |
| 5. Empty slots | The placeholders are byte-identical morph targets (based on vanilla i1_, 21 ear targets) whose embedded geometry has zero render chunks, so an unfilled slot renders nothing. | resource (SHA-256 417b1ff3… for fpm1, fpm9, fpm50 placeholders) |
| 6. Filling a slot | Item archives such as PRC_ ship the same depot path …\ with real geometry. They win because PRC_ sorts before PRC_ under the default first-alphabetical-wins archive order. | [source: MO2 Cyberpunk guide and plugin] [hypothesis for the native engine order] |
| 7. Shared colour | Every one of the 128 components in a definition uses the same meshAppearance (silver, gold, plastic_, cooper, …, wood). Each item's base mesh must define mesh appearances with those names; its chunkMaterials then choose per-submesh materials (the nose stud's second chunk is always default_ → base\ → silver .mlsetup). Hence one colour choice for all installed items. | resource (PRC catalogue audit) |
| 8. Following the face | Item morph targets carry region targets matching the CC morph names (a nose ring the 21 nose targets, a linked-mesh ring all 105), so face sliders move them like vanilla piercings. | resource + [hypothesis: manager applies by (target, region)] |
| 9. Save | The save stores (FNV-1a64 of earring_ in the face group, identical to vanilla option 12. Uninstalling PRC reverts to vanilla style 12 with no broken reference. | [resource: save structure] [hypothesis: behaviour after uninstall] |
Limits that follow from the design: one colour for every item; slot numbers collide between packs (three septum packs all use fpm17); a pack's slot is only visible if its archive wins; vanilla style 12 cannot be used on its own while PRC is installed.
What the Studio needs from this: nothing PRC-specific. The generic rules in section 8 — archive precedence, inline plus partsValues components, zero-chunk geometry, mesh-appearance lookup on each component's own base mesh, and resolving baseMesh through the same mount set — produce the PRC result, and the implemented resolver does so on the reference installation (validation).
7. How a save stores CC choices
Full format and reader: save import. Summary:
- Node
CharacetrCustomization_(engine spelling):Appearances dataExists, an unknownu32, presetversion(12),isMale(body gender),isBrainGenderMale(voice), then head, arms and body group lists,perspectiveInfoandtags[source: WolvenKitCharacterCustomizationAppearancesParser.cs]. - Each group is
{name, appearances[{resourceHash u64, definition, name (option name), censorFlag, censorFlagAction}], morphs[{region, target, censorFlag, censorFlagAction}]}. resourceHash= FNV-1a64 of the lower-case.appdepot path. Verified for teeth, lip, cheek, neck and EP1 face-rig entries of the reference save resource.- The save stores resolved output, not UI state. It lists, per consumer group, the active appearance options with their chosen definition, and the non-
Nonemorphs. It does not store switcher choices, link indices or colour-only controllers (skin_never appears; its choice is visible only as the definition ofcolor skin_,type_ 05 body_, arms and so on). The same choice is repeated in every group that contains the option: in the reference save, eye colour appears incolor TPP,TPP_andphotomode character_.customization - The node mirrors the creator resource [resource: two 2.31 saves against the installed feminine resources]: its head, arms and body group lists are the resource's
headGroups,armsGroupsandbodyGroupsin the same order, empty groups included; its perspective list is the resource'sperspectiveInfo; its preset version is the resource'sversion(12); vanilla options appear within each group in the resource's option order. Censor fields copy the option's own (Censor_= 1;Nudity Activate= 0,Deactivate= 1), so body entries carry non-zero values and both the censored and uncensored body variants are stored. The two tags are the chosen hair colour's and hairstyle's tags (colour name and length). Re-encoding a decoded node reproduces it byte for byte. - By contrast, the vanilla UI preset
.charcustpreset(gameuiCharacterCustomizationUiPreset) stores the index state:{optionName, isActive, value}for every option,value = 0xFFFFFFFFwhen unset, plusisMaleVOresource source. WolvenKit also registers.ccstateas agameuiCharacterCustomizationPresetresource, the same type as the save node [source:FileTypeHelper.cs].
Writing back: what is known
| Known | Unknown |
|---|---|
WolvenKit has a byte-level writer for this node and a whole-save writer (CyberpunkSaveWriter) source. The layout is simple length-prefixed strings and integers. | Whether the game rebuilds the editable CC state (for the mirror/ripperdoc) from the stored appearances, or trusts them as-is. A write that changes only one group could render inconsistently between TPP, photo mode, FPP and the creator. |
| The Studio reader decodes the node completely with zero trailing bytes; no Studio writer exists. | Whether version differences trigger versionUpdateInfo migration on load. |
| Consistency rules can be derived offline: for a chosen UI state, every group containing an active option must list it with the same definition, and linked followers must use the linked index. | How censor flags, Unknown1, isBrainGenderMale and tags (hair colour and length) are validated. |
| Removing a mod leaves unresolvable hashes; ArchiveXL maps some old paths through app overrides. | How the game treats an entry whose app hash or definition no longer exists (drop, default, or corrupt). |
The safe order is: generate the complete group set from an option state using the effective CCO, write to a copy, re-read with both the Studio reader and WolvenKit, then verify in game. The container side is now measured: the node is root-level, childless and sits in the last compressed chunk of both saves, and sav.dat has no checksum; the save write-back design sets out a minimal-diff writer, its integrity gates and an alternative that applies the look in game through the bridge. CC presets for sharing should store the UI state (option names + choice identities, like .charcustpreset but by name rather than index) and derive the save representation, not the reverse.
8. Generic resolver specification
Principle: the Studio interprets game files the way the game and its core frameworks do. Vanilla piercings, PRC, CCXL packs, hair-colour packs and future mods must all resolve from data through the same rules; there are no mod-specific adapters. Where a rule is not yet proven, the resolver reports the uncertainty instead of guessing.
Status: phase 1 implemented in projects/ (character-resolver.ts and the pure modules it uses; host adapter resolver-host.ts; CLI tools/). It reproduces the hand-traced eyes, hair, brows, lashes, skin and PRC chains of the reference installation from data alone; see the resolver validation. Archive precedence and ArchiveXL semantics are consolidated in mod loading. The Studio preview consumes it for the skin, face details, brows, lashes, hair, eyes and piercings (the shown V's resolved components, planned by character-detail-plan.ts and exported into the character render record; head CC rendering P0); Build uses its archive layer for the eye plate's head.
Inputs
- Installation view: base game
archive/,pc/ content archive/(if present),pc/ ep1 archive/, the ArchiveXL bundle directory, plus the mod manager's virtual view (MO2 profile with overwrite, or manual files). Vortex needs no adapter for the files, since it hard-links them into the game folder (Vortex §5); REDmod is a later adapter. Source discovery enumerates the files;pc/ mod resolver-host.tsreads RDAR indexes and reads resources natively first (archive formats), falling back to WolvenKit CLI per resource with an ignored JSON cache (mod loading §6). - Framework declarations: every visible
.xlin ArchiveXL's load order (customizations,resource.scope/fix/patch/copy/link). TweakXL records are presentation only (R11) and not read yet. - Character input: a decoded save node (
inputFromSave) or a UI option state (descriptorsFromUiState: option → definition, morph or switcher choice). The character context (src/) holds such a state, built from the default V, a save (recovering switcher and link-controller choices the save doesn't store) or ancharacter-context.ts xfs/preset.cc-preset-1
Rules, in application order
| # | Rule | Implemented from | Status and grade |
|---|---|---|---|
| R1 | Archive precedence. MO2 virtual path first (overwrite, then the first modlist.txt row); then the mod group (visible archive/, else first-alphabetical), the ArchiveXL bundle group, EP1, content. Every candidate is kept. | Mod loading §1–2 | Implemented (archive-precedence.ts). Tool-source rules source; the native engine order is unread, so every mod-over-base, base-internal and collation-sensitive decision carries an ambiguity record |
| R2 | CCO selection. Body gender picks the male or female CCO; the _ twin when EP1 archives are mounted. | Save face-rig hash (section 2) | Implemented (ccoPath). resource; confirm with a male EP1 save |
| R3 | ArchiveXL resource metadata: scopes (transitive), fixes (paths, mesh names, context), copies and links (rejected over existing paths), patches of meshes, morph targets and .app definitions. | ArchiveXL ResourceMeta, ResourceLink, ResourcePatch | Implemented (archivexl-config.ts, resource-graph.ts) source. .ent patches (R6b) and !exclude tags not yet |
| R4 | CCO merge: fix base option paths (registering app overrides), then per custom resource: groups, unnamed expansion, named options, a second pass for anonymous uiSlot/link overlays; regenerate indices; collect hair-colour tags. | ArchiveXL Customization/ 254–637 | Implemented (cco-model.ts), including the link-wildcard quirk source |
| R5 | State → descriptors. The Studio's character context feeds it the whole creator state: the base (the default V, or the state recovered from a save) with the choices a person set on top; from a save, the save's own descriptors are kept for every option the choices neither reach nor change (deriveCharacter). From a UI state: roots are the enabled options that no switcher names. An active switcher activates only the options its current choice names, so a switcher target is active only through a switcher, whatever its enabled flag. Propagate link indices from appearance controllers to followers. Emit (app, definition) and (region, target) per group, skipping an Off choice (a definition whose name is None). From a save: the stored descriptors. | Slots and switchers; vanilla UI presets; reference save; a new-game save made to test it [runtime, 25 September] | Implemented. Switcher ownership and Off choices resource: the output matches the active appearance options and definitions of all six vanilla UI presets. Link propagation [resource-inferred]; a follower with fewer choices keeps its default and records link-index-out-of-range hypothesis. Activation of a switcher link controller's followers (tattoo under facial_) is not modelled hypothesis |
| R6 | Descriptor → appearance. Apply app overrides; load the winning .app; pick the appearance by name; if absent and in the player_ scope, build ArchiveXL's dynamic appearance. | ArchiveXL ApplyAppOverride, FixCustomizationAppearance | Implemented source |
| R6b | Entity patches: a patch .ent's appearances, dependencies, visual tags and components merged into the player entities (always-on parts) and into part .ent files. | ArchiveXL ResourcePatch/ 252–345, 740–857; wiki facial cyberware guide; ArchiveXL PlayerBaseScope.xl | Not implemented source. Consumers on the reference installation: a face-rig fix on every player entity, a body-type tag mod (§5); ranked gap 3 in the file-chain map |
| R7 | Appearance → components. Inline components (compiled package first) ∪ components of each existing partsValues .ent (missing parts dropped, as ArchiveXL does); partsOverrides by component name; in the customization scope, ArchiveXL's first-override mesh appearance on the appearance's own mesh components. | wiki .app guide; ArchiveXL FixCustomizationComponents, Garment OnResolveDefinition | Implemented. Same-named inline and part components are treated as one; several same-named components in one appearance are all kept and flagged hypothesis |
| R8 | Component → geometry. Morph targets after patches (a copied or patched blob counts), then baseMesh through the same mount set; zero render chunks draw nothing; the chunk mask selects visible chunks. | PRC placeholders; head details; PRC preview | Implemented (provenance, chunk counts, each chunk's LOD mask, and drawnFrom: the resource whose blob draws, a patch or copy source when ArchiveXL supplies it). The preview exports that resource to GLB from its winning archive resource |
| R9 | Morphs. Apply each (target, region) to every morph component whose targets[] contain it. | Save + morph resources | Implemented as a report of which components each pair reaches; manager semantics hypothesis |
| R10 | Materials. Mesh appearance (with patches, names fixes and expansion) → chunk material → target entry, patch-mesh entry or @template → instance chain to .mt/.remt, with @context parameters and *{attr} path expansion; effective parameters nearest-first. | ArchiveXL Mesh extension; materials and shaders | Implemented to the template and resource parameters; shader semantics are not interpreted source |
| R11 | Presentation: texts, sections, icons. | Presentation | Implemented for the game's texts (keyed as ArchiveXL merges them), ArchiveXL text declarations and the compiled TweakDB (src/, src/, src/) [source + resource], and shown in the Character panel (src/). Mod icons declared in TweakXL YAML are overlaid on the compiled TweakDB (src/) source |
| R12 | Full body: visual-tag hiding, garment offsets, feet/genital controllers. | ArchiveXL VisualTags.xl | Partly implemented for worn clothing: .xl tag rules and entity-wide partsOverrides onto the body and arms, and the feet group from footwear (worn clothing §6); garment offsets and garment support deferred |
Output: a renderable description
ResolvedCharacter (character-resolver.ts, schema xfs/) lists one entry per unique saved or derived (option, definition) with the groups that use it, the requested and effective .app (with any override), who provided the choice (base CCO or which custom resource), the appearance status (defined, dynamic with its template, or missing) and its components. Each component carries its origin (inline or part), final mesh appearance and chunk mask, geometry provenance (.morphtarget, .mesh, render-chunk count, visible chunks, drawsNothing, patch sources), morph regions and the saved morphs that reach it, and per visible chunk the material route, instance chain, template and effective parameters. Every resource reference is a Provenance: depot path and hash, winning archive and provider, losing alternatives, the rule and its grade, copy/link hops and the extracted-byte SHA-256. Character-level ambiguities and gaps list everything unproven or unresolved. Reports stay local because they name installed mods.
Everything is data: a vanilla piercing, a PRC slot and a CCXL earring produce the same shape, differing only in provenance.
Retiring the PRC-specific code
Done 26 September 2026. With PRC installed, resolving vanilla piercing option 12 yields the framework's .app, its 128-slot bank (placeholders drawing nothing, filled slots from their item archives by alphabetical order), the kept vanilla part with the framework's chunk mask, and the stud's default_ → base\ material (validation). The preview now draws exactly that: piercings are a slot of the character record, selected by the creator slot piercings_, each resolved drawing component with its own chunk mask, through the layered adapter (head CC rendering). What the old code did and what replaced it:
| Removed | Replaced by |
|---|---|
tools/, the xfs/ manifest | The resolver's provenance and the generic exporter's content-addressed geometry, keyed by depot hash and winning archive |
tools/ and xfs/ | The Piercings row of the Character panel: the creator catalogue's switcher choices and colours on this installation (the record lists no choices since xfs/) |
aggregatePrcStyle and prc_ in the retired piercing-preview.ts | The resolved appearance's own component list with its shared meshAppearance |
Single-slot prc_ styles | Nothing yet: per-component visibility from the resolved list is a possible later control |
PRC notes in the preview panel, prcError/prcAvailable in the status | The same per-slot outcome and plain line as every other detail |
With PRC installed, the "Piercing 12" choice is the PRC bank, as in game; there is no separate PRC style. Trying a style in the viewport asks the host to resolve the V with that creator choice in place of its own.
9. Male and female, NPCs and later full-body work
- Body gender = entity. The player is
player_orwa_ * player_(TPP, FPP, photo mode, cutscene, reflection variants, plus EP1 copies; list in ArchiveXLma_ * PlayerBaseScope.xl) source. The save'sisMalecorresponds to the CCO and entity choice;isBrainGenderMale(the presets'isMaleVO) is independent: the voice, set by the creator's voice-over switcher row and by the body-gender screen (§4, Voice tone) source. - Separate option catalogues. Male has beards (a 13-choice
beardswitcher over 13 per-style part switchers and 57 colour options), 20 instead of 21 face targets per region, no breast morph, a two-choice nipple switcher, no split_/tpp _arms groups, and different bodyfpp .appchoices (t0_, male body is not morph-skinned) resource. Genital choices are available to both.000_ pma_ base_ _ full.mesh - Shared
.appfiles. Most CC.appfiles contain both genders' appearances; a resolver keys on the definition name, never on the file alone. - NPC reuse. Vanilla NPC and photo-mode entities reuse the same head bases (
PhotoModeScope.xllists NPC photo-mode puppets such as Judy and Panam alongsideplayer_) source.wa_ photomode h0_carries named NPC tattoos (000_ tattoo.app tygerclaw,valentino) and lip styles carry NPC names, showing the same resources dress NPCs resource. Converting a V into an NPC ("NPV") bakes the chosen options into a standalone.app: the NPV guide (manavortex, crediting NoraLee for the process) reads the CC numbers in game, keeps only the used head parts, applies the matching shape keys to every head part in Blender and re-imports them; its app structure screenshot is an illustrated map from CC categories to components (h0_,head he_,eyes ht_,teeth heb_,eyebrows hx_withmakeup_ eyes black_,07 hx_withmakeup_ lips_ 01 peach_,04_ 03 i1_, tattoo, cyberware, hair, body) [wiki, editor illustration]. The Studio's renderer is effectively a live NPV: the same parts, but morphs applied at runtime instead of baked.earring - Full-body customisation later. The preview draws the vanilla body since 26 September (body rendering). Body coverage in the creator is limited to skin tone, breast size, nipples, genitals, body tattoos/scars, nails and feet. There is no vanilla body-shape morph. Body mods (e.g. replacer bodies) change
t0_/a0_/l0_meshes and rely on garment refitters; they are out of scope for the head-focused Studio but the same chain applies. The wiki's garment-support page gives the component-prefix layering scores (s00,l010,a020,t030,h040, …hh90) that decide which garment deforms over which body part wiki.
10. Studio rendering readiness
Source: audit of projects/ on 2026-09-25, rows updated as later steps landed. Since 26 September the Character panel (src/) lists every creator option, so any detail the preview draws can be changed there as well as read from a save. The per-option templates each gap needs and the ranked render plan are in head CC rendering. "Selectable" means a Studio control exists; "Save" means it renders only when an imported save matches.
| Detail | Needed to render | Path today | Status | Gap |
|---|---|---|---|---|
| Head mesh + facial morphs | head .morphtarget/.mesh, 105 targets | Derived from the user's installed game with all 105 targets and the eye plate cut from the same rows (src/ over src/), loaded through one typed render record (src/); this is the only source of the preview head | Save (5 regions) + selectable eye shape (22 choices, listed from the head's eyes targets; the derived eyes follow it) | Nose, mouth, jaw and ear shapes selectable in the Character panel; male head absent; vanilla base-game head only, not the effective mod winner |
| Skin tone / type | 5 type .apps × 12 tones → skin.mt chain (albedo, packed normal, roughness R/B, microdetail, skin profile) | Resolved skin chain of the shown V through the skin adapter (src/, P1: winning albedo including complexion mods, tint, detail and microdetail, secondary albedo, skin profile) (head CC rendering) | Save, default V or any creator choice | Screen-space SSS not ported (a per-channel wrap stands in) |
| Eye colour | he_ mesh appearance → eye .mi → textures | Resolved eye component of the shown V (creator slot eyes_) through the eyeball and wetness-shell adapters (src/): colour sampled as the game does, the iris ramp baked from the resolved gradient stops, the refracted iris under the cornea and the Eye-class light on both normals, layered designs through the layered adapter (eye rendering §6.6) | Save or default V | Creator selector; heterochromia (rank 6) |
| Eyebrows + colour | one heb_ morph/mesh for every style, a texture set per style, decal material + gradient (eyebrows) | Resolved component of the shown V through the double-diffuse adapter (src/, src/) | Save or default V + visibility | The brow's normal and roughness writes; colour accuracy unverified in game |
| Eyelashes + colour | vanilla: he_ chunk 0 + eyelashes_; CCXL: own mesh | Resolved component with its .hp through the hair.mt adapter | Save or default V + visibility | Colour accuracy unverified in game |
| Hair + colour | hair .app → mesh(es) + .hp + cap | Resolved components (strands and cap, highest LOD) through the hair.mt and gradient-recolour adapters (src/) | Save or default V + visibility | Physics (dangle physics); shadow-only parts are skipped by render mask. Hair is every active option the hairs group lists, whatever its slot, so multi-part hairstyles draw part by part, and a hairstyle's accessories (earrings on metal_) draw with it |
| XF Studio eye makeup | recipe → plate | src/ | Selectable (authoring) | Game parity of finishes |
| Vanilla eye/lip/cheek makeup, pimples | hx_ morphtarget per style + decal .mi | Resolved face-detail components through the decal family (src/; head CC rendering rank 2); CCXL makeup options the same way | Save or default V | Draw order within a priority; in-game comparison |
| Facial tattoos, face scars, face cyberware | hx_ morphtargets, chunk masks, tone link | Same path (tone from the saved definition; scars' and cyberware's normal writes included) | Save or default V | Decal templates other than the three drawn are reported, not drawn |
| Piercings | i1_ earring morphtargets + chunk masks + multilayered .mi | Resolved components through the layered adapter (src/); any installed framework by the same rules | Save or default V, with any creator choice set in the Character panel (either body) | The colour-mask and contrast mappings (materials open question 10) |
| Teeth | ht_ morph/mesh; teeth_ (skin.mt) or a finish's multilayered.mt | Resolved component of the shown V (creator slot teeth) through its template's adapter: skin with the teeth's own profile, or layered (head CC rendering §1) | Save or default V, with any creator choice set | No mouth occlusion; not compared with the game |
| Beard (male) | hb_ morphtargets + beard .mi | The stubble decal through the face-decal family; the hair cards are not drawn | Stubble | The cards (a beard slot on the hair adapter; masculine V plan phase 3) |
| Body, nails, arms, feet, body tattoos and scars | t0_/a0_/l0_/tx_/i0_ resources in the third-person body groups | Resolved components of the shown V (the body slot) through the skin, layered and decal adapters, with the body's own shapes and the game's underwear cover (body rendering) | Save, default V or any creator choice (either body) | Genitals and nipples stay under the cover unless the viewer opts in to the game's uncensored mode (body rendering §3); first-person parts and arm cyberware states aren't drawn |
| Idle/face rig | face rig animgraph + facial anims | vanilla CC idle clip (src/) | Selectable (motion panel), female | The masculine idle (his head holds still); wrinkle maps |
| Male V | pma variants of all the above | His own core head (head, eyes on chunk 2, a preview-only plate cut), prepared on first use; every detail above through the same resolver and adapters (masculine V plan) | Default V, save or preset | His idle and blink; the beard cards; an audited plate and a male: selector for XF Eye Artistry |
Common prerequisites: wiring the implemented generic resolver into the preview (done: the skin, face details, brows, lashes, hair, eyes and piercings go through it and the generic exporter into the character render record); one morph-target mesh path for all hx_ decals (makeup, pimples, tattoos, scars and face cyberware share morph-skinned decal meshes over the head, so one geometry path plus the decal material family covers them; done in rank 2); and the skin shader adapter (done in P1). The derived head.glb reads the base game's content archives only; a resolver-driven head would load the winning h0_, which matters because installed mods (for example a facial-rig fix) can replace it (saved skin chain).
Lessons from the legacy generator
From the legacy xf-omega eye-makeup generator (female only, reference only) and its design notes:
- It registered one switcher per layer in
character_andcustomization face, with an Off option and colour lists linked through alinkstring; its variant options wereenabled = 0behind the switcher, mirroring vanilla. Vanilla uses"<option> color"link keys with a space; the legacy code usedcolour, which still works because the key is only a shared string. - It placed a morph-skinned component directly in each
.appappearance, instead of vanilla'spartsValues.entpluspartsOverrides. The game accepted this (so does PRC, section 6); the vanilla structure is still preferable for ArchiveXL's customization fallback, which operates onpartsOverrides. - Its option indices collided across layers. ArchiveXL regenerates choice indices but not option indices; keep option indices unique and never rely on them for identity.
- A 4 layers × 20 designs × 49 colours × 4 finishes matrix (15,680 appearances) made the mod slow to open in WolvenKit (measured structure). XF Studio compiles only authored complete looks.
- UI icon atlases imported as
TEXG_looked washed out in game; they must beGeneric_ Color TEXG_.Generic_ UI
Diagram review
| Date | Tool | Observations |
|---|---|---|
| 2026-09-25 | Mermaid CLI 11.17.0 (npx, headless Chrome); both diagrams rendered to temporary PNGs at 800 px and 1600 px and inspected at both sizes | First render of the chain diagram drew switchers as a self-loop whose label collided with the morph label; it was redrawn with separate choice nodes and an explicit inline-components branch. All labels, arrow directions, the dotted morph-manager edge and the PRC filled/unfilled branches are legible at 800 px. Renders contain only documentation text and were not committed. |
Open questions
- How exactly does link propagation handle a follower whose choice count differs from the controller (e.g.
tpp_has 16 definitions against 12 tones; the corpo preset stores 0 for it)?head_ proxy - Which consumer applies the
TPP,TPP_,photomode FPP, proxy andcharacter_groups? The CC controller components covercustomization hairs,face,beards,nails,genitals/breastand feet; the head item is unconfirmed. For photo mode,character_alone is not enough for a face-decal option (runtime, 25 September, finish board results); whethercustomization facealone is enough is being tested. - On load, does the game rebuild option state from the save's appearance lists (needed for the mirror to show the right selection), and what happens to entries whose hash or definition is missing? The only evidence so far is community advice that a removed lash mod's lashes disappear (§5, rule 10); it matters for every rebuilt XF Eye Artistry collection.
- Does
entMorphTargetManagerComponentsilently skip components without a target, or does it fail? (Affects CCXL accessories lacking a region.) - Is the link-wildcard branch in ArchiveXL's anonymous overlay intentionally tied to the slot suffix?
- Why do decal
.appoverrides name the head componentMorphTargetSkinnedMesh7243? - How does the runtime use
censorFlag/censorFlagAction? The save copies each option's values (non-zero on the body's nudity-dependent entries, §7), and stores both censored and uncensored variants. - Does ArchiveXL remove and re-merge custom entries on every load, so that uninstalled options disappear cleanly?
- What is the native engine rule for a depot path present in both a base-game and a mod archive, and for bundle order? Tool sources (WolvenKit, ArchiveXL) put mods first; PRC depends on it, but the native lookup has not been read or measured.
- Are eye-makeup styles 21–36 in the EP1 CCO deliberately on a separate link key, and are all resource-listed choices visible in the UI?
- How does the engine treat several components with the same name in one appearance (the reference installation's teeth
.appfrom a morph-additions mod has 16)? The preview draws the first drawable one hypothesis; in that mod's case the other 15 carry LOD mask 0 on their only chunk, so the question does not change what is drawn there resource. - The EP1 CCO and the reference save use
i0_, which no installed000_ pwa_ base_ _ vagina_ _ 01_ ca_ pale_ 00_ warm_ ivory i0_defines. What does the engine fall back to?000_ base_ _ genitals.app - Does the engine load resources that WolvenKit 8.17.4 and 9.0.1 reject as an older layout (a replacement feet
.appwithcastShadowsstored asBool)? - Does the native
GetUnitedOptionssort by optionindexalone, and what makes an optionisEditable? The Studio leaves out hidden options and link followers;holstered_(feminine arms, no label) is neither and still gets a row.data - Which TweakDB blob does the game load with Phantom Liberty (
tweakdb_is assumed), and does the text lookup pickep1.bin maleVariantfor a masculine V?
In-game results (session 4)
From the creator on 28 September 2026 (game 2.31, the test profile, feminine V; experiment 029) runtime:
- Visible choice counts match the resources, not the wiki. With Off counted: eye makeup 37, lip styles 38 per finish, cheeks 25, face cyberware 17, facial tattoos 16, piercings 15, teeth 5. That is the resources' 36/38/24/16/15 plus Off. The wiki cheat sheet's 20/20/14/8/11 is outdated. With the installed packs: eyebrows 70, hairstyles 283 (285 cyberware variants), eye colours 254. Cheek choice 1 is a freckle pattern, not a blush.
- Link propagation. With
skin_3, switchingcolor skin_1 → 5 keeps tone 3, and the body keeps its colour within 3 % (upper chest; the creator's swatch is identical). The creator's framing doesn't show the hands, so they are unchecked.type - Lip finish tree.
makeupLips_is 0 Off, 1 Default, 2 Glossy, 3 Matte. The style position carries across finishes: style 8 stayed at 8 through Default → Glossy → Matte → Off → Glossy, each finish's own style row taking the position. Colour carry-over is untested: the colour row can't be set from the lips page (the bridge answered thattype makeupLips_"can't be changed on this screen").08 - Linked hairstyle. Hairstyle 5 (label 06); face cyberware 1 switches the row to
hairstyle_at the same index 5 (label 06), and the hair looks the same. Cyberware 2–4 don't switch the row. So the Studio's rule holds for this style. Whether position 5 is one of the 20 where the two lists differ wasn't checked, so the test doesn't yet tell "same options" from "same position" (§2, switcher link members).cyberware - Hairstyle switcher order. The row's index follows the option list's order, installed packs included: 90 Grace Bob V4, 96 Kala Messy Pixie, 100 Viessa Bun, 217 Valby Curtain Bob, 220 Viv Loose Waves; the reference V's 142 is "LONG PAK - #011" (
lm097_).hair - Piercing colour order (the same for each style): 0 silver, 1 gold, 2 pearl, 3 copper, 4 red, 5 pink, 6 black, 7 blue, 8 mixed, 9 mixed 2, 10 neon teal, 11 pink metallic, 12 rainbow, 13 rose gold, 14 steel, 15 wood.
- Teeth choices 0–4: base, silver, gold, copper (labelled "cooper") and pink.
In-game results (sessions 5 and 6)
From the creator opened by the XF bridge on 29 September 2026 (game 2.31, the test profile, feminine V; experiment 032) runtime:
- The creator opens anywhere through the pause menu, and the game decides the edit context. Opened from the street or a megabuilding corridor, away from any mirror, by switching the pause menu's scenario to the mirror's (Character Customization Anywhere's route, with CCA itself disabled), the creator was fully drawn and editable. Asked for the mirror's
HairDressertag or forNewGame, the menu reportedNewGameboth times and listed the same 860 editable options of 1,209, skin tone, skin type and face shape included, whichHairDresserdoesn't allow. SoeditTagsfiltering (§2) follows the menu's own mode, and this route always gets the new-game set (runtime access §5). - The lip colour row is per finish and style. With the glossy finish and style 8 chosen, the colour row in use is
makeupLips_(14 colours), in the slotglossy_ 08 makeupLips_;color makeupLips_isn't in use and can't be set. Setting the slot changed the colour (red → violet). Session 4's refusal came from naming a row not in use (experiment 029 guessed08 makeupLips_); the colour carry-over across finishes (ask 2) can now run through the slot.09 - Hairstyle labels resolve to the same indices as session 4: 217 VALBY CURTAIN BOB, 142 LONG PAK - #011.
In-game test asks
Each is a small check for one prepared session; record game, ArchiveXL, TweakXL and Codeware versions and whether Phantom Liberty is installed. Asks 1, 2, 9 and 11 ran in session 4 (above); what remains of them is noted in each.
- Link propagation (hands still open). In the creator, pick skin tone 3, then switch skin type 1 → 5 and toggle face tattoo 6. Do skin type, tattoo, neck and hands all keep tone 3? Session 4 confirmed the body; the hands need a framing that shows them.
- Lip finish tree (colour still open). Choose lipstick style 8 colour 6, then switch finish regular → glossy → matte → none → glossy. Session 4 showed that the style position carries; set the colour through the slot
makeupLips_(the row in use is the finish's own, e.g.color makeupLips_; sessions 5 and 6, above) to test whether it carries too.glossy_ 08 - Save representation. Save once with a vanilla-only look (no CCXL), change only eye makeup colour at a mirror, save again. Hand both saves over for diffing: we expect exactly the
faceandcharacter_entries to change.customization - Mirror reconstruction. Load a save, open the mirror and confirm every slider shows the saved choice (not index 0). This tests whether option state is rebuilt from the save.
- Missing-mod behaviour. With a throwaway save using one CCXL eye colour, disable that mod, load, note what the eyes show, open the mirror, then re-enable and load the original save. (Do not overwrite the original.)
- Photo mode. In photo mode, check that makeup, piercings and brows match TPP. This tests the
TPP_versusphotomode facegroup split. - PRC as vanilla option 12. With the PRC framework and its two enabled item packs, choose Piercings 12 silver, then gold. Expected: the vanilla style-12 piece plus the nose stud and both nose rings appear together and all change colour together (the stud's small second part stays silver). Then move the nose slider: the rings should follow. This confirms archive precedence, the zero-chunk placeholders and the shared
meshAppearancein one look. - EP1 eye-makeup colour carry-over. Choose eye makeup style 5 with colour teal, then switch to style 25. Does teal carry over? The EP1 CCO gives styles 21–36 a different link key, so we expect it may not.
Visible choice counts.Answered in session 4: the creator shows the resources' counts plus Off (above).- Creator rows. On the body page of a new game, list every row in order (feminine and masculine), and note whether any unlabelled row appears. The Studio's catalogue expects the options'
indexorder and no rows for link followers. - Linked hairstyle switchers (one style checked). In the creator, pick hairstyle 5 without face cyberware, then choose a face cyberware that swaps the hairstyle row to the cyberware variant. Is it still the same hair (the Studio expects the style that turns on the same hair option), or the style at the same position? Screenshot both. Session 4 checked style 5 (same hair, same position); repeat with a style at a position where the two lists differ.
- Makeup Off on a legacy build. On the reference character (wearing an older XF Eye Artistry build), set each of its layer rows and the vanilla lipstick and blush rows to Off at a mirror. Does nothing of them remain drawn? The Studio's Hide my V's own makeup does exactly this.
- Multi-part hair. With the Multicolored Hair Core and an MCH hairstyle, set the vanilla hairstyle to 51 and give two parts different colours. Are both drawn with their own colour, and does the MultiColor Hair Off row show a colour grid? Then put on a beanie and note what hair shows under it (hair colour study checklist items 6, 14 and 15).
Related pages
Head CC rendering · Mod loading · Resolver validation · Materials and shaders · Wiki file-chain map · CCXL merge boundary · Catalogue probe · Source resolution · Save import · CC controls and presets backlog · Body rendering · Worn clothing · Piercings and jewellery · Archive and resource formats · Vortex