diff --git a/docs/architecture/character-asset-organization.md b/docs/architecture/character-asset-organization.md new file mode 100644 index 000000000..7bd419bc9 --- /dev/null +++ b/docs/architecture/character-asset-organization.md @@ -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/ + / # 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: `_` + +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: `.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: `_.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: `_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/ + | + 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)