T-1095 fixed: template heads now receive the body's skin tone (same treatment as seg_head) — _load_head passes tone.lit with a full-white fallback mask through _attach_to_bone; without a mask sidecar the tint was silently ignored, so untinted templates read grey against mid/dark tones. Verified by lookbook render (head_001 on child, tone 6 — face matches arms). Docs: the T-1088 locomotion design document committed to docs/design/ (the sandbox code's 'design §' references now resolve in-repo); character-asset- organization gains §11 Wardrobe Pipeline and sheds three stale claims (the never-built indexed-greyscale mask plan, single-mask-per-asset for clothing, the resolved multi-region open question); CHANGELOG carries the sidequest entry; D-250 amended with the shipped cutaway-modes verdict (mode 2 default, user-decided live). Tickets: T-1088, T-1089, T-1095 closed; T-1096 filed (S9 live feel-tuning, needs the user). The Fable-5 cascade exception is closed — the cascade resumes at Phase 4 (T-750). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
40 KiB
Character Asset Organization Proposal
Author: TYRE Date: 2026-03-19 Status: Proposal (pending review) Inputs: Spike 3dpipeline, Spike quaternius-aesthetic (VERDICT.md), Quaternius Source tier, D-159, D-160, D-161, D-162, D-163, D-164
Scope
This document proposes the directory structure, naming conventions, shared/per-variant boundaries, and import pipeline for character assets in the production client. It covers everything from skeleton to accessories. It does NOT cover environment assets (furniture, props) -- those follow the existing Trellis pipeline conventions proven in the 3dpipeline spike.
1. Quaternius Source Tier Mapping
The Quaternius Source tier has been purchased. Source .blend files are available at
docs/assets/downloads/Universal Base Characters[Source]/. Five of the 11 body types have
direct Source .blend files; six are forks requiring Blender modification.
All body meshes start fresh from Source .blend files. No mesh work from the spikes carries over -- the spike's automated segmentation output is discarded. Only shaders, import settings, and architectural patterns (slot system, CharacterVisualDescriptor, BoneAttachment3D) carry forward from spikes.
| Body type key | Quaternius source | Work needed |
|---|---|---|
average_m |
Regular_Male_FullBody.blend | Fork + segment |
average_f |
Regular_Female_FullBody.blend | Fork + segment |
muscular_m |
Superhero_Male_FullBody.blend | Segment (usable as-is) |
muscular_f |
Superhero_Female_FullBody.blend | Segment (as-is) |
teen_m |
Teen_Male_FullBody.blend | Segment (as-is or minor tweaks) |
teen_f |
Teen_Female_FullBody.blend | Segment (as-is or minor tweaks) |
thin_m |
Regular_Male as base | Fork + slim down + segment |
thin_f |
Regular_Female as base | Fork + slim down + segment |
heavy_m |
Regular_Male as base | Fork + widen + segment |
heavy_f |
Regular_Female as base | Fork + widen + segment |
child |
Teen as base | Fork to gender-neutral + scale down + segment |
Key decisions:
- Superhero = muscular. Rejected as default body, but correct for the muscular end of the spectrum. Use Superhero .blends as-is for muscular body type.
- Teen is its own body type, not a child stand-in. Direct Source .blends available.
- Child is gender-neutral, forked from Teen and scaled down.
- 5 direct / 6 forked -- NOT hand-authored from scratch.
Source tier hairstyles
Source tier includes ~18 hairstyle .blend files with "Rigged to Head Bone" variants ready for BoneAttachment3D:
Female: Bob, Buns, BuzzedFemale, Long, LongDreads, Ponytail_2 (+ Teen variants) Male: Balding, Beard, Buzzed, Dreads, Mohawk, Moustache, MuttonChops, Ponytail, SimpleParted, SlickBack (+ Teen variants) Eyebrows: Female, Regular, Teen, Thick (+ Teen variants)
Hair is mostly available from Source tier, not authored from scratch.
Source tier head templates (OnlyHead .blends)
Source tier provides 4 standalone head .blend files -- the starter set for the curated head template library:
- Regular_Female_OnlyHead.blend
- Regular_Male_OnlyHead.blend
- Teen_Female_OnlyHead.blend
- Teen_Male_OnlyHead.blend
These are exported as head_001.glb through head_004.glb in the template library. More
heads are added over time via the offline Trellis pipeline (see Section 5).
Animation libraries
Two Universal Animation Libraries are available (both Standard tier):
- UAL1 -- core animation set (idle, walk, run, etc.)
- UAL2 -- extended animation set, includes Female Mannequin .blend
Both share the same skeleton hierarchy and can be combined into the production animation library GLBs.
2. Directory Structure
client/assets/characters/
skeleton/
armature.glb # 65-bone skeleton, no mesh -- shared by ALL body types
animations/
ual_standard.glb # UAL1 -- core animations, shared by all body types
ual_extended.glb # UAL2 -- extended animations + female mannequin
ual_combat.glb # (future) additional animation libraries
bodies/
thin_m/ # One directory per body type (11 total)
seg_head.glb
seg_neck.glb
seg_torso.glb # Full torso (default)
seg_torso_upper.glb # Upper chest only (for low-cut/tank top clothing)
seg_arm_upper_l.glb
seg_arm_upper_r.glb
seg_arm_lower_l.glb
seg_arm_lower_r.glb
seg_hand_l.glb
seg_hand_r.glb
seg_leg_upper_l.glb # Hip to knee (left)
seg_leg_upper_r.glb # Hip to knee (right)
seg_leg_lower_l.glb # Knee to ankle (left)
seg_leg_lower_r.glb # Knee to ankle (right)
seg_foot_l.glb # Extends up past ankle bone
seg_foot_r.glb # Extends up past ankle bone
seg_eyes.glb
seg_eyebrows.glb
thin_f/
... # Same 18 segment files
average_m/
...
average_f/
...
muscular_m/
...
muscular_f/
...
teen_m/
...
teen_f/
...
heavy_m/
...
heavy_f/
...
child/ # Gender-neutral, forked from Teen
... # Same 18 segments but different proportions
skin_tones/
pale_cool.png # Skin tone texture variants
pale_warm.png # Shared across all body types --
light_olive.png # the toon shader tints these,
medium_golden.png # so one set of textures works
olive_warm.png # for all 11 body types
medium_brown.png
deep_brown.png
very_deep_warm.png
very_deep_cool.png
heads/
templates/ # Curated head template library (all heads live here)
head_001.glb # Numeric IDs -- heads are unique per character
head_001_mask.png # Recolor mask sidecar
head_002.glb
head_002_mask.png
...
hair/
bob.glb # Source tier hairstyle (female)
bob_mask.png # Recolor mask -- hair color via tint_color
buns.glb
buns_mask.png
buzzed.glb # Source tier (male)
buzzed_mask.png
buzzed_female.glb # Source tier (female)
buzzed_female_mask.png
long.glb
long_mask.png
long_dreads.glb
long_dreads_mask.png
ponytail.glb
ponytail_mask.png
ponytail_f.glb # Ponytail_2 (female variant)
ponytail_f_mask.png
balding.glb
balding_mask.png
dreads.glb
dreads_mask.png
mohawk.glb
mohawk_mask.png
simple_parted.glb
simple_parted_mask.png
slick_back.glb
slick_back_mask.png
bald.glb # Placeholder or empty -- explicit "no hair"
...
facial_hair/
beard.glb # Source tier facial hair
beard_mask.png
moustache.glb
moustache_mask.png
mutton_chops.glb
mutton_chops_mask.png
...
eyebrows/
regular.glb # Source tier eyebrows (short names — no prefix)
female.glb
teen.glb
thick.glb
...
clothing/
<item_id>/ # One directory per clothing item
reference.glb # Authored on average_m (reference body)
reference_mask.png # Recolor mask (shared across all variants)
coverage.json # Segment coverage + torso variant selection
thin_m.glb # Surface Deform fitted variant
thin_f.glb
average_m.glb # Copy of reference.glb
average_f.glb
muscular_m.glb
muscular_f.glb
teen_m.glb
teen_f.glb
heavy_m.glb
heavy_f.glb
child.glb
coveralls_basic/ # Example: a basic work coverall
reference.glb
reference_mask.png
coverage.json
thin_m.glb
thin_f.glb
average_m.glb
...
jacket_utility/
...
pants_cargo/
...
accessories/
hat_hardhat.glb # Single-mesh rigid items
hat_hardhat_mask.png # Recolor mask sidecar
goggles_welding.glb
goggles_welding_mask.png
backpack_small.glb
backpack_small_mask.png
...
shaders/
toon.gdshader # Flat color toon (body segments)
toon_masked.gdshader # Texture + recolor mask + toon shadow
outline.gdshader # Inverted hull outline pass
Total: ~10 top-level directories under characters/. Flat where possible, one level of
nesting where multiplicity demands it (bodies, clothing). Facial hair and eyebrows are
split from hair into their own directories because they are separate equip slots.
Why this structure
Bodies are directories, not flat files. Each body type produces 18 segment GLBs (14 limb/body
segments + 2 torso variants + 2 face detail). Putting them in flat bodies/ with prefixes
(thin_m_seg_head.glb) would create 198 files in one directory. Directories per body type
keep segment files scannable and let the compositor construct paths programmatically:
bodies/{body_type}/seg_{segment}.glb.
Clothing items are directories. Each item has 11 body-type variants plus the reference
mesh, the recolor mask, and coverage.json. That is 14 files per item. A flat structure would
be unreadable by the time you have 20 items. One directory per item keeps variant lookup
trivial: clothing/{item_id}/{body_type}.glb.
Skin tones are shared, not per-body-type. The toon shader applies skin tone via texture tinting. The same base skin tone texture works across all 11 body types because the shader controls the color, not the texture. One set of 9 textures, not 99.
Heads are a curated template library. All heads live in heads/templates/ -- there is no
distinction between Quaternius OnlyHead .blends and Trellis-generated heads. They are all
equal templates. The starter set is 4 Quaternius OnlyHead .blends (Regular_Female,
Regular_Male, Teen_Female, Teen_Male). More are added over time via the offline Trellis
pipeline: generate candidates, curate the good ones, land them in templates/. Trellis head
generation is an offline asset production tool, not a runtime or worldgen process.
3. Naming Conventions
Body Types (the 11-value enum)
D-159 defines: Thin, Average, Muscular, Heavy, Teen (x2 genders) + Child.
| D-159 Name | File key | Description |
|---|---|---|
| Thin Male | thin_m |
Ectomorph, narrow shoulders, long limbs (forked from Regular) |
| Thin Female | thin_f |
Ectomorph, narrow frame (forked from Regular) |
| Average Male | average_m |
Regular proportions (canonical/default, reference body for clothing) |
| Average Female | average_f |
Regular proportions |
| Muscular Male | muscular_m |
Broad shoulders, thick limbs (from Superhero .blend) |
| Muscular Female | muscular_f |
Broad shoulders, thick limbs (from Superhero .blend) |
| Teen Male | teen_m |
Adolescent proportions (from Teen .blend) |
| Teen Female | teen_f |
Adolescent proportions (from Teen .blend) |
| Heavy Male | heavy_m |
Wide torso, thick midsection (forked from Regular) |
| Heavy Female | heavy_f |
Wide torso, thick midsection (forked from Regular) |
| Child | child |
Gender-neutral, forked from Teen, scaled down |
These keys are used everywhere: directory names, GLB filenames, enum values in code. Lowercase,
underscore-separated, no abbreviations beyond m/f. The GDScript and Rust enum should use
ThinM, ThinF, AverageM, AverageF, MuscularM, MuscularF, TeenM, TeenF,
HeavyM, HeavyF, Child.
Body Segments (14 limb/body segments + 2 torso variants + 2 face detail = 18 total)
From the spike validation and D-160. Fine-grained segmentation is required for per-limb addressability: amputation (hide limb, show stump cap), prosthetics (replace limb with alternative mesh), injury overlays (per-segment visual state), and clothing coverage (hide segments under clothing).
These are the segment suffixes in filenames:
| Segment | File suffix | Bones covered | Notes |
|---|---|---|---|
| Head | seg_head |
Head | |
| Neck | seg_neck |
neck_01 | |
| Torso (full) | seg_torso |
spine_01, spine_02, spine_03 | Default; swappable |
| Torso (upper only) | seg_torso_upper |
spine_02, spine_03 | For low-cut shirts, tank tops |
| Upper arm left | seg_arm_upper_l |
upperarm_l (shoulder to elbow) | |
| Upper arm right | seg_arm_upper_r |
upperarm_r | |
| Lower arm left | seg_arm_lower_l |
lowerarm_l | |
| Lower arm right | seg_arm_lower_r |
lowerarm_r | |
| Hand left | seg_hand_l |
hand_l + finger bones | |
| Hand right | seg_hand_r |
hand_r + finger bones | |
| Upper leg left | seg_leg_upper_l |
thigh_l (hip to knee) | |
| Upper leg right | seg_leg_upper_r |
thigh_r (hip to knee) | |
| Lower leg left | seg_leg_lower_l |
calf_l (knee to ankle) | |
| Lower leg right | seg_leg_lower_r |
calf_r (knee to ankle) | |
| Foot left | seg_foot_l |
foot_l + ball_l/ball_leaf_l + ankle overlap | Extends past ankle bone; 1-ring vertex overlap with lower leg segment |
| Foot right | seg_foot_r |
foot_r + ball_r/ball_leaf_r + ankle overlap | Extends past ankle bone; 1-ring vertex overlap with lower leg segment |
| Eyes | seg_eyes |
Attached to Head bone (small mesh) | |
| Eyebrows | seg_eyebrows |
Attached to Head bone (small mesh) |
Swappable variants (torso only): The torso has TWO mesh variants. The compositor loads one or the other based on clothing coverage.json:
- Torso:
seg_torso(full, default) orseg_torso_upper(upper chest only). Full-coverage garments hide seg_torso entirely. Partial-coverage garments (low-cut shirts, tank tops) swap to seg_torso_upper, exposing lower chest/abdomen.
Legs use per-leg L/R segments split at the knee, matching the arm pattern. This provides all needed granularity without swappable variants:
- Shorts: clothing hides upper leg segments, lower legs show skin.
- Amputation: hide one leg's segments individually.
- Prosthetics: replace one leg's segments individually.
Foot segments extend past the ankle bone. Boots and shoes have geometry to cover the ankle region. Bare feet look natural with no visible seam. A 1-ring vertex overlap at the boundary with the lower leg segment above ensures smooth deformation at the joint.
The seg_ prefix is load-bearing: it distinguishes segment GLBs from the armature.glb in
the same directory and makes glob patterns simple (seg_*.glb).
Total per body type: 14 limb/body segments + 2 torso variants (full and upper) + 2 face detail = 18 GLB files.
Clothing Items
Format: <category>_<descriptor>
Categories:
coveralls-- full-body work suitsjacket-- upper body outerwearshirt-- upper body innerpants-- lower bodyboots-- foot slotgloves-- hand slotvest-- sleeveless upper bodydress-- full-body single garmentuniform-- occupation-specific full outfit
Examples: coveralls_basic, jacket_utility, pants_cargo, shirt_henley, boots_work,
uniform_medic, dress_formal.
The category prefix enables filtering in the character editor UI (show all jackets) and in the clothing catalogue data.
Clothing Coverage (coverage.json)
Each clothing item directory contains a coverage.json sidecar that tells the compositor
which body segments to hide and which torso variant to use:
{
"hides": ["torso", "arm_upper_l", "arm_upper_r", "leg_upper_l", "leg_upper_r", "leg_lower_l", "leg_lower_r"],
"torso_variant": "full"
}
The hides array uses segment names without the seg_ prefix. The compositor hides the
corresponding seg_{name} mesh when loading. Leg segments are individually addressable
(upper/lower, left/right), so clothing specifies exactly which leg segments to hide.
Torso variant values:
"full"-- useseg_torso(default), or hide entirely if"torso"is inhides"upper"-- swap toseg_torso_upper, exposing lower chest/abdomen
Example for a tank top:
{
"hides": [],
"torso_variant": "upper"
}
Example for cargo shorts:
{
"hides": ["leg_upper_l", "leg_upper_r"],
"torso_variant": "full"
}
Hair
Format: <visual_descriptor>.glb
Source tier hairstyles are renamed from their Quaternius names to visual descriptors:
Bob->bob,Buns->buns,Long->long,Ponytail->ponytailSimpleParted->simple_parted,SlickBack->slick_backBuzzedFemale->buzzed_female,LongDreads->long_dreads
No category prefix needed -- hair is a single slot. The descriptor should be visually unambiguous at a glance.
Accessories
Format: <slot>_<descriptor>.glb
Slots: hat, goggles, mask, backpack, belt, earring_l, earring_r, necklace, wrist_l, wrist_r, held_l, held_r.
Each slot maps to a specific bone for BoneAttachment3D:
hat-> Head bonegoggles-> Head bonemask-> Head bonebackpack-> spine_03 bonebelt-> pelvis boneearring_l-> Head boneearring_r-> Head bonenecklace-> Head bonewrist_l-> lowerarm_l bonewrist_r-> lowerarm_r boneheld_l-> hand_l boneheld_r-> hand_r bone
Examples: hat_hardhat, goggles_welding, backpack_small, earring_l_stud, wrist_l_watch, held_r_wrench.
Recolor Masks
Convention: <asset_name>_mask.png as a sidecar file next to the GLB.
baroque_table.glb+baroque_table_mask.png(existing Trellis convention from 3dpipeline spike)bob.glb+bob_mask.pnghat_hardhat.glb+hat_hardhat_mask.png- For clothing, the mask lives in the item directory:
coveralls_basic/reference_mask.png
Masks are per-asset, not per-variant, for hair/facial hair/eyebrows/accessories/heads -- one mask applies across all 11 body-type variants because the mask is UV-space and the UV layout is preserved through fitting.
Superseded for clothing (T-1089, see Section 11): offset-shell garments now author a mask
per body type (<body_type>_mask.png, that body's own UV0 layout), falling back to
reference_mask.png only for the Surface-Deform-fit route (suit, uniform, Fantasy-derived
cuts), which still shares one mask across variants as described above.
Head Templates
Format: head_NNN.glb with zero-padded 3-digit numeric ID.
The starter set is 4 Quaternius OnlyHead .blends (Regular_Female, Regular_Male, Teen_Female,
Teen_Male). Additional templates are produced offline via the Trellis pipeline: generate
candidates, curate the best, export to GLB, and add to templates/. All heads are equal
members of the template library regardless of origin.
Heads are unique per character in the fiction, so descriptive names are wrong -- these are
catalogue entries. The number is an index into the head template library. The compositor
always resolves head_id to characters/heads/templates/{head_id}.glb -- a single path,
no branching.
4. Shared vs Per-Variant Breakdown
| Asset | Shared or per-variant | Count | Notes |
|---|---|---|---|
| Skeleton (armature.glb) | Shared | 1 | All body types, all clothing, all animations reference this one skeleton |
| Animation libraries | Shared | 2+ GLBs | UAL1 (core) + UAL2 (extended + female mannequin); animations work on all body types via shared skeleton |
| Shaders (toon, toon_masked, outline) | Shared | 3 files | One copy in characters/shaders/ |
| Skin tone textures | Shared | 9 PNGs | Toon shader tints these -- same textures for all body types |
| Body segments | Per body type | 18 GLBs x 11 types = 198 | Each body type has its own segments (incl. swappable torso variants) |
| Clothing items | Per body type | 11 GLBs + 1 reference + 1 mask + 1 coverage per item | Authored once on reference, batch-fitted to 11 |
| Recolor masks (clothing) | Shared per item | 1 PNG per item | UV layout preserved through Surface Deform |
| Coverage metadata | Shared per item | 1 JSON per item | Declares which segments to hide and torso variant selection |
| Heads (templates) | Shared library | N GLBs + N masks | Curated set shipped with game; starter set of 4 Quaternius OnlyHead .blends, expanded via offline Trellis pipeline |
| Hair | Shared | 1 GLB + 1 mask per style | Same mesh for all body types (BoneAttachment3D to Head bone); ~18 from Source tier |
| Facial hair | Shared | 1 GLB + 1 mask per style | BoneAttachment3D to Head bone; beard, moustache, mutton chops from Source tier |
| Eyebrows | Shared | 1 GLB per style | BoneAttachment3D to Head bone; 4 styles from Source tier |
| Accessories | Shared | 1 GLB + 1 mask per item | Rigid bone attachment, body type irrelevant |
The important asymmetry
Hair, facial hair, eyebrows, and accessories are body-type-agnostic. They attach to a single bone and do not deform with the body. This is the architectural win of BoneAttachment3D: one mesh works for all 11 body types with zero fitting work. The Source tier hairstyles arrive pre-rigged to the Head bone, making them nearly production-ready.
Clothing is the expensive axis. Every clothing item requires 11 fitted variants. The Surface Deform batch pipeline amortizes this, but the disk cost is real: 20 clothing items = 220 GLBs + 20 masks + 20 reference meshes + 20 coverage JSONs = 280 files. This is manageable but worth tracking. At 50 items it is 700 files -- still fine for Godot's import system but a reason to keep the per-item directory structure clean.
5. Import Pipeline
Source .blend to production flow
Quaternius Source .blend files (docs/assets/downloads/Universal Base Characters[Source]/)
|
v
Blender: fork/modify body shape (for thin, heavy, child -- 6 of 11 types)
|-- OR: use as-is (average, muscular, teen -- 5 of 11 types)
|
v
Blender: fine-grained segmentation (14 limb/body segments + 2 torso variants + 2 face detail = 18 GLBs)
|
v
Export GLB (per segment / per clothing variant / per accessory)
|
v
Recolor mask generation (Blender script or manual in image editor)
|
v
Place files in client/assets/characters/<appropriate directory>
|
v
Godot auto-import (gltf/embedded_image_handling=3 in project.godot)
|
v
Runtime: CharacterVisual.gd loads by convention paths
Offline Head Production (Trellis)
Trellis is used as an offline asset production tool to expand the head template library
beyond the 4 Quaternius starter heads. The workflow is: generate candidate heads in batch,
review and curate for quality and diversity, export accepted heads as GLB + mask PNG, and
place them in client/assets/characters/heads/templates/ with the next sequential ID. This
is a manual curation step, not an automated pipeline -- quality control matters more than
volume.
GLB Export Conventions
-
One GLB per segment. Each body segment is a separate GLB containing a single skinned MeshInstance3D and the skeleton it references. The skeleton must have the same bone names and hierarchy as
armature.glb. -
Embedded textures. GLBs use embedded textures (not external references). The project setting
gltf/embedded_image_handling=3(embed uncompressed) must be set inproject.godot. This was the critical finding from the 3dpipeline spike: values 0-1 fail silently, value 2 compresses, only value 3 works correctly. -
Y-up, -Z forward. Standard glTF orientation. Godot handles the conversion.
-
Scale: 1 unit = 1 meter. Consistent with Godot's physics defaults and the existing environment asset scale from the 3dpipeline spike.
-
No animation data in segment/clothing/accessory GLBs. Animations are in the separate animation library GLBs only. Exporting animations into every segment would bloat file size for no gain.
-
Foot segments include ankle overlap. The foot segment mesh extends 1 ring of vertices past the ankle bone boundary into leg territory. This overlap ensures smooth deformation and eliminates visible seams at the ankle. Boots and shoes have their own geometry covering this region.
Mask Sidecar Convention
For every GLB that supports recoloring, a _mask.png sidecar sits next to it:
- Format: PNG, same UV layout as the GLB's texture
- Channels: Greyscale (R channel only). White = tintable region, black = preserve original
- Default:
hint_default_blackin the shader means "no mask loaded = no tinting." Assets without masks render with their original Trellis/authored texture.
Multi-region resolved differently than proposed here (T-1089, see Section 11). The
distinct-greyscale-band idea above (0.0/0.33/0.66/1.0 in one channel) was never built --
indexed greyscale bilinear-filters badly at region boundaries (a filtered sample between two
index levels reads as a wrong third region, not a blend). Clothing multi-region instead
channel-routes an RGBA mask (R/G/B/A -> 4 independent tints, toon_garment.gdshader),
so bilinear filtering cross-fades neighboring regions correctly. This is clothing-only; the
single-channel binary convention above is unchanged for hair/facial hair/eyebrows/accessories.
Surface Deform Batch Output
The Blender batch pipeline takes one reference clothing mesh and produces 11 fitted variants. The output on disk looks like:
clothing/coveralls_basic/
reference.glb # Input: authored on average_m
reference_mask.png # Input: recolor mask
coverage.json # Input: segment coverage metadata
thin_m.glb # Output: fitted variant
thin_f.glb # Output: fitted variant
average_m.glb # Output: copy of reference
average_f.glb # Output: fitted variant
muscular_m.glb # Output: fitted variant
muscular_f.glb # Output: fitted variant
teen_m.glb # Output: fitted variant
teen_f.glb # Output: fitted variant
heavy_m.glb # Output: fitted variant
heavy_f.glb # Output: fitted variant
child.glb # Output: fitted variant
The batch script lives in tooling/blender/ (not in the client tree). It takes arguments:
- Input: path to reference GLB
- Input: path to the 11 body type meshes (for Surface Deform targets)
- Output: directory to write fitted variants
The reference mesh is authored on average_m because Average is the canonical/default body
type (D-159). The average_m.glb variant in the output is a direct copy of the reference
(convention: copy, not symlink -- see Section 9).
Godot Import Settings
Required in project.godot (already set from spikes):
[gltf]
embedded_image_handling=3
No per-file .import overrides are needed if this project-wide setting is correct. All
character GLBs use the same import configuration.
6. CharacterVisualDescriptor (server-to-client interface)
The server sends a descriptor per character. The client compositor uses it to assemble the visual. This is the data contract between simulation and rendering:
CharacterVisualDescriptor:
body_type: BodyType # enum: thin_m, thin_f, average_m, average_f, muscular_m,
# muscular_f, teen_m, teen_f, heavy_m, heavy_f, child
head_id: String # template ID ("head_042") -- resolves to templates/ library
hair_id: String # hair style key ("bob", "ponytail", "bald")
hair_tint: Color # hair color via shader tint
facial_hair_id: String # facial hair key ("beard", "moustache", "") -- empty = none
facial_hair_tint: Color # facial hair color via shader tint
eyebrow_id: String # eyebrow style key ("regular", "thick", "female", "teen")
eyebrow_tint: Color # eyebrow color via shader tint
skin_tone: int # index into the 9-tone palette
clothing_slots: Dictionary # slot -> item_id (e.g. "torso" -> "coveralls_basic")
clothing_tints: Dictionary # item_id -> Array[Color] for multi-region recolor
clothing_logos: Dictionary # item_id -> brand id (logo_capable items only; T-1089, Section 11)
accessory_slots: Dictionary # slot -> item_id (e.g. "hat" -> "hat_hardhat")
accessory_tints: Dictionary # item_id -> Color
The compositor resolves paths from this descriptor:
- Body:
characters/bodies/{body_type}/seg_{segment}.glb - Body (torso variant):
characters/bodies/{body_type}/seg_torso_upper.glb(when coverage.json requests it) - Head:
characters/heads/templates/{head_id}.glb - Hair:
characters/hair/{hair_id}.glb - Facial hair:
characters/facial_hair/{facial_hair_id}.glb - Eyebrows:
characters/eyebrows/{eyebrow_id}.glb - Clothing:
characters/clothing/{item_id}/{body_type}.glb - Accessories:
characters/accessories/{accessory_id}.glb
Every path is deterministic from the descriptor. No lookup tables, no indirection. The
directory structure IS the lookup table. The only additional data the compositor reads at
runtime is coverage.json per equipped clothing item, which determines segment visibility
and torso variant selection.
7. Disk Budget Estimate
| Category | Count | Files per | Total files | Size estimate |
|---|---|---|---|---|
| Skeleton | 1 | 1 | 1 | ~10 KB |
| Animations | 3 (UAL1 + UAL2 + future) | 1 | 3 | ~4 MB |
| Body segments | 11 types | 18 segs | 198 | ~2.2 MB |
| Skin tones | 1 set | 9 | 9 | ~500 KB |
| Head templates | 50 (v0.2) | 2 (GLB+mask) | 100 | ~5 MB |
| Hair styles | 18 (v0.2, Source tier) | 2 (GLB+mask) | 36 | ~1.8 MB |
| Facial hair | 3 (v0.2, Source tier) | 2 (GLB+mask) | 6 | ~300 KB |
| Eyebrows | 4 (v0.2, Source tier) | 1 | 4 | ~50 KB |
| Clothing items | 20 (v0.2) | 14 (11 variants+ref+mask+coverage) | 280 | ~22 MB |
| Accessories | 15 (v0.2) | 2 (GLB+mask) | 30 | ~1.5 MB |
| Shaders | 3 | 1 | 3 | ~5 KB |
| Total (v0.2) | ~703 | ~37.4 MB |
This is well within Godot's comfort zone. The increase from the earlier estimate comes from
the per-leg L/R split at the knee (4 leg segments + 2 feet = 6 leg-related files per body
type vs the old 2 combined + 2 feet = 4), swappable torso variants, coverage.json per
clothing item, and the facial hair / eyebrow split. The import cache
(.godot/imported/) will be larger due to Basis Universal compression and resource
conversion, but that is gitignored and rebuilt on each machine.
8. Migration Path from Spikes
All body meshes start fresh from the Quaternius Source tier .blend files. No mesh work from the spikes carries over -- the spike's automated segmentation output is discarded.
What carries forward from spikes:
-
Shaders -- copy
toon.gdshader,toon_masked.gdshader,outline.gdshaderfrom spike toclient/assets/characters/shaders/. These are proven and unmodified from spike. -
Import settings --
gltf/embedded_image_handling=3and the per-file import configuration patterns proven in the quaternius-aesthetic spike. -
Architectural patterns -- the slot system, CharacterVisualDescriptor structure, BoneAttachment3D attachment patterns, and segment visibility toggling from the spike's
character_visual.gd. Move toclient/scripts/rendering/character_visual.gdand updateres://paths to match the new directory structure. -
Skin tones -- copy
T_Skin_*.pngfiles from the spike toclient/assets/characters/skin_tones/, renaming to match the convention (T_Skin_pale_cool.png->pale_cool.png). -
Skeleton -- extract
armature.glbfrom the Source tier .blends (not from spike segmentation output) toclient/assets/characters/skeleton/armature.glb.
What does NOT carry forward:
- Body segment meshes -- all 11 body types are segmented fresh from Source .blends.
- Fitted clothing variants -- re-fitted against the new body meshes.
- Spike hairstyles -- Source tier hairstyles replace any spike hair work.
9. Resolved Conventions
Copy, not symlink. The average_m.glb in a clothing directory is a copy of
reference.glb, not a symlink. Symlinks are fragile across platforms (Windows). A copy adds
~50KB per clothing item but is reliable. The batch script produces it automatically.
10. Open Questions for Review
Q: Multi-region recolor masks -- implement now or defer? RESOLVED (T-1089, Section 11).
Deferred until the first multi-region clothing item (the t-shirt) needed it, then implemented
as an RGBA channel-routed mask + toon_garment.gdshader -- not the greyscale-band shader
update this note anticipated (see the corrections in Sections 3 and 5 above). toon_masked
is retained for single-region assets (hair, accessories).
Q: Teen clothing variants -- separate catalogue or shared with adult? Teen body types use the same clothing system (Surface Deform fitting) but teen proportions may make some adult garments look wrong. Options: (a) all clothing items get teen variants automatically, accept minor visual issues; (b) tag clothing items as adult-only or all-ages in coverage.json; (c) separate teen-specific clothing items. Recommendation: (a) for v0.2, revisit if visual quality is insufficient.
11. Wardrobe Pipeline (T-1089)
The clothing engine that shipped the first real catalogue (2026-07-06/07). This section supersedes the single-region greyscale mask model assumed in Sections 3/5/10 above for clothing specifically -- hair/facial hair/eyebrows/accessories/heads keep the single-channel convention as originally written.
Authoring: offset-shell from our own body segments
The preferred authoring route is offset-shell
(tooling/garment-fit/blender_author_offset_shell.py, one per-garment companion script per
item, e.g. blender_author_hoodie.py, blender_author_boots.py): extract the body segment(s)
a garment covers, cut neckline/hem/sleeve boundaries via bone-plane thresholds, offset the
surface outward along vertex normals, solidify for real cloth thickness. Because the shell
is body topology, bone weights are inherited by construction -- no Surface Deform, no re-rig.
PER-BODY mode (--per-body) is now the standard for offset-shell garments, not
single-reference-then-fit. Q-060 evidence: single-reference Surface-Deform fitting degraded
with girth divergence (muscular_m hit an 859px worst clip at 24mm standoff on the
reference-authored t-shirt); authoring per body guarantees clearance + exact weights by
construction and lets the offset drop back to the ~12mm ideal. Cut/mask thresholds are
derived from each body's own bone landmarks using the proportional ratios that reproduce the
hand-calibrated reference constants -- recomputed per body, not copied.
Shared geometry practices proven across garments: boundary weld (weld_boundaries --
merges coincident segment-boundary verts so the offset can't open cracks) and open-rim
flattening (flatten_open_rims -- pulls the jagged segment-splitter boundary rings onto
clean planes before the offset runs; first required by jeans and the track jacket).
convex_toe_box (shared helper, used by boots/sneakers/shoes_formal) reshapes the
forefoot into one smooth per-slice ellipse cap so individual-toe geometry doesn't poke the
shoe shell -- residual toe hints are accepted for first release (see Catalogue below).
Fitting: Surface-Deform batch route
tooling/garment-fit/blender_batch_fit_skinned.py remains the route for garments sharing one
reference mesh + mask across all 11 bodies -- Fantasy-pack-derived cuts and hand-authored
pieces on a single UV layout (the suit, the uniform). SD-bind to each target body, bake the
deformed rest shape, transfer + normalize weights, retarget the armature, export with
export_skins=True (the predecessor script's export_skins=False output could not
animate on the shared skeleton -- why it was superseded, not merely extended).
Runtime contract
coverage.jsonper clothing item:hides(segment names to hide),torso_variant(full/upper), and the newmulti_regionflag (routes the item throughtoon_garment.gdshaderinstead of the single-tinttoon_masked.gdshader).- Per-body masks:
<body_type>_mask.pngsits beside each body's fitted GLB; the compositor prefers it and falls back toreference_mask.pngfor the Surface-Deform-fit route. See the mask-convention correction in Section 3. toon_garment.gdshader(client/assets/characters/shaders/): channel-routes FOUR tints through an RGBA region mask (R/G/B/A ->tint_0..tint_3), channel-blended rather than indexed-greyscale (see the correction in Section 5). A logo decal samples a dedicated UV2 (TEXCOORD_1) chest channel, composited after region tinting (brand colors stay faithful) but before the toon shadow (the logo darkens with its fabric).CharacterVisualDescriptor.clothing_logos:item_id -> brand id, per-character logo choice (Section 6), alongsideclothing_tints(item_id -> Array[Color], one per region).- Manifest clothing schema (
client/assets/characters/manifest.json,clothingkey):slot,style(modern/formal/utility/... -- the D-251 style-tag seam),logo_capable, and for multi-region itemsregions(named R/G/B/A) +default_tints. outfits.json: named complete looks (presets) composing manifest items with per-item tints/logos -- 12 presets (casual/business/sport/beach/worker/sleep) for NPC dressing and shop display.
QA: chromakey acceptance gate
tooling/garment-qa (run-garment-qa <config>) is the acceptance gate every garment in this
pipeline runs before it ships: a two-pass depth-proximity chromakey renders the same frozen
frame twice -- once keying claimed-covered body segments magenta, once with the garment
nudged a few cm toward the camera and re-keyed cyan. A body-key pixel that flips from magenta
to cyan is a genuine clip (clip_through_pixels, gates on connected-component size); a pixel
that never flips despite being exposed is informational (exposed_skin_pixels -- collars,
sleeveless armholes). Wave 1/2 garments were accepted at <=150px worst clip component.
client/tools/garment_preview/lookbook_scene renders the full catalogue for visual review --
the pass that caught the missing-albedo and manifest-nesting findings fixed in e562418d0.
Current catalogue
24 entries: 21 modern (tops, bottoms, feet, full-body, swim -- wave 1 + wave 2 + the fill-in-the-blanks addendum) + 3 legacy peasant pieces (pre-dating this pipeline, still on the Surface-Deform-fit route). All 24 fitted across all 11 body types.
Known artifact classes, accepted for first release: sleeve/opening-hem epsilon blobs
(offset-shell boundary artifacts, offset-insensitive); residual toe hints on closed footwear
after convex_toe_box ("I still see some toes, but this will do" -- user review, 2026-07-07).
Further smoothing is polish backlog, not open scope.
Decisions Referenced
- D-159: 5 adult body types x 2 genders + 1 child = 11 base meshes (updated: added Teen)
- D-160: Body meshes segmented into 18 bone-group regions with swappable torso variants and per-leg L/R split
- D-161: Head is always a separate mesh on the Head bone
- D-162: Clothing is pre-baked per body type via Surface Deform
- D-163: Trellis generates heads via BoneAttachment3D (offline asset production, curated into template library)
- D-164: Fork Quaternius skeleton, replace all body meshes (Source tier .blends as starting point)
- D-251: Character asset route reconfirmed 2026-07-06 -- Quaternius rig stays, wardrobe is in-house (offset-shell + Surface-Deform routes, Section 11), UAL1/UAL2 animation tiers purchased