34 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. The same mask applies to all 11 body-type variants of a clothing item because the mask regions (collar is region A, sleeves are region B) correspond to UV regions, and the UV layout is preserved through Surface Deform fitting.
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
- For multi-region recoloring: Use distinct greyscale values per region (e.g. 0.0, 0.33,
0.66, 1.0 for up to 4 independent tint regions). The shader samples the mask and routes
each region to a different
tint_coloruniform. This is not yet implemented in the shader but the mask format supports it. - Default:
hint_default_blackin the shader means "no mask loaded = no tinting." Assets without masks render with their original Trellis/authored texture.
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
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?
The current toon_masked shader supports single-region (binary mask). Multi-region requires
a shader update to sample distinct greyscale bands and route them to separate tint uniforms.
Recommendation: defer to when the first multi-region clothing item is authored. The mask PNG
format supports it already (greyscale values), so no re-export is needed later.
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.
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)