Files
settled-reach/docs/architecture/character-asset-organization.md
T

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) or seg_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 suits
  • jacket -- upper body outerwear
  • shirt -- upper body inner
  • pants -- lower body
  • boots -- foot slot
  • gloves -- hand slot
  • vest -- sleeveless upper body
  • dress -- full-body single garment
  • uniform -- 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" -- use seg_torso (default), or hide entirely if "torso" is in hides
  • "upper" -- swap to seg_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 -> ponytail
  • SimpleParted -> simple_parted, SlickBack -> slick_back
  • BuzzedFemale -> 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 bone
  • goggles -> Head bone
  • mask -> Head bone
  • backpack -> spine_03 bone
  • belt -> pelvis bone
  • earring_l -> Head bone
  • earring_r -> Head bone
  • necklace -> Head bone
  • wrist_l -> lowerarm_l bone
  • wrist_r -> lowerarm_r bone
  • held_l -> hand_l bone
  • held_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.png
  • hat_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

  1. 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.

  2. Embedded textures. GLBs use embedded textures (not external references). The project setting gltf/embedded_image_handling=3 (embed uncompressed) must be set in project.godot. This was the critical finding from the 3dpipeline spike: values 0-1 fail silently, value 2 compresses, only value 3 works correctly.

  3. Y-up, -Z forward. Standard glTF orientation. Godot handles the conversion.

  4. Scale: 1 unit = 1 meter. Consistent with Godot's physics defaults and the existing environment asset scale from the 3dpipeline spike.

  5. 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.

  6. 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_color uniform. This is not yet implemented in the shader but the mask format supports it.
  • Default: hint_default_black in 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:

  1. Shaders -- copy toon.gdshader, toon_masked.gdshader, outline.gdshader from spike to client/assets/characters/shaders/. These are proven and unmodified from spike.

  2. Import settings -- gltf/embedded_image_handling=3 and the per-file import configuration patterns proven in the quaternius-aesthetic spike.

  3. Architectural patterns -- the slot system, CharacterVisualDescriptor structure, BoneAttachment3D attachment patterns, and segment visibility toggling from the spike's character_visual.gd. Move to client/scripts/rendering/character_visual.gd and update res:// paths to match the new directory structure.

  4. Skin tones -- copy T_Skin_*.png files from the spike to client/assets/characters/skin_tones/, renaming to match the convention (T_Skin_pale_cool.png -> pale_cool.png).

  5. Skeleton -- extract armature.glb from the Source tier .blends (not from spike segmentation output) to client/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)