docs(architecture): add character asset organization proposal

Directory structure, naming conventions, segmentation spec, and import
pipeline for the 11 body type / 21 segment character system. Covers
Quaternius Source tier mapping, CharacterVisualDescriptor contract,
Surface Deform batch clothing pipeline, and coverage.json format.
Built from spike findings with all meshes starting fresh from Source
.blend files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 23:06:38 +01:00
co-authored by Claude Opus 4.6
parent 496953f69c
commit 61eafbb552
@@ -0,0 +1,747 @@
# 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 21 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 21 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/
eyebrows_regular.glb # Source tier eyebrows
eyebrows_female.glb
eyebrows_teen.glb
eyebrows_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 21 segment GLBs (17 base
+ 2 swappable torso variants + 2 face detail). Putting them in flat `bodies/` with prefixes
(`thin_m_seg_head.glb`) would create 231 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 (the 17 base + 2 swappable torso variants + 2 face detail)
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 | |
| Torso (full) | `seg_torso` | Spine, Spine1, Spine2 | Default; swappable |
| Torso (upper only) | `seg_torso_upper` | Spine1, Spine2 | For low-cut shirts, tank tops |
| Upper arm left | `seg_arm_upper_l` | LeftArm (shoulder to elbow) | |
| Upper arm right | `seg_arm_upper_r` | RightArm | |
| Lower arm left | `seg_arm_lower_l` | LeftForeArm | |
| Lower arm right | `seg_arm_lower_r` | RightForeArm | |
| Hand left | `seg_hand_l` | LeftHand + finger bones | |
| Hand right | `seg_hand_r` | RightHand + finger bones | |
| Upper leg left | `seg_leg_upper_l` | LeftUpLeg (hip to knee) | |
| Upper leg right | `seg_leg_upper_r` | RightUpLeg (hip to knee) | |
| Lower leg left | `seg_leg_lower_l` | LeftLeg (knee to ankle) | |
| Lower leg right | `seg_leg_lower_r` | RightLeg (knee to ankle) | |
| Foot left | `seg_foot_l` | LeftFoot + toe bones + ankle overlap | Extends past ankle bone; 1-ring vertex overlap with lower leg segment |
| Foot right | `seg_foot_r` | RightFoot + toe bones + 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: 17 base segments + 2 swappable torso variants + 2 face detail = **21 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:
```json
{
"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:
```json
{
"hides": [],
"torso_variant": "upper"
}
```
Example for cargo shorts:
```json
{
"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`, `necklace`, `held_l`, `held_r`.
Each slot maps to a specific bone for BoneAttachment3D:
- `hat` -> Head bone
- `goggles` -> Head bone
- `mask` -> Head bone
- `backpack` -> Spine2 bone
- `belt` -> Hips bone
- `earring` -> Head bone
- `held_l` -> LeftHand bone
- `held_r` -> RightHand bone
Examples: `hat_hardhat`, `goggles_welding`, `backpack_small`, `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** | 21 GLBs x 11 types = 231 | 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 (17 base + 2 swappable torso + 2 face detail = 21 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 | 21 segs | 231 | ~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 17+ 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)