752 lines
34 KiB
Markdown
752 lines
34 KiB
Markdown
# 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:
|
|
|
|
```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_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)
|