From 5eaeed80b4962fa46f192a0835a6fcc794754904 Mon Sep 17 00:00:00 2001 From: agent-bot Date: Mon, 17 Aug 2026 15:27:27 +0000 Subject: [PATCH] docs(avatar): add avatar component system and glTF attachment spec --- docs/architecture/avatar.md | 462 ++++++++++++++++++++++++++++++++++++ 1 file changed, 462 insertions(+) create mode 100644 docs/architecture/avatar.md diff --git a/docs/architecture/avatar.md b/docs/architecture/avatar.md new file mode 100644 index 0000000..19f5c01 --- /dev/null +++ b/docs/architecture/avatar.md @@ -0,0 +1,462 @@ +# Avatar Component System & glTF Attachment Spec + +> **Status:** Proposed +> **Owner:** Platform team +> **Related:** Server authority + client prediction, world chunk data format + +## 1. Overview + +This document specifies the **avatar component system** for **agp-game**, a +Godot-based multiplayer game. It defines: + +- How an avatar is decomposed into **body parts** and **attachment slots**. +- How **cosmetic attachments** (clothing, accessories, gear) mount onto the + base body. +- The **glTF/GLB format requirements** that all avatar assets must satisfy so + they can be loaded, skinned, and networked consistently. + +The goal is a **modular, data-driven avatar pipeline**: a single base body rig +plus swappable attachments, all authored in glTF and consumed by a shared Godot +runtime. This enables player customization, cosmetic monetization, and +server-authoritative validation of what a player is wearing. + +### 1.1 Design Goals + +- **Modularity:** Body parts and attachments are independent assets that can be + combined at runtime without re-exporting. +- **Consistency:** A single, well-defined skeleton and slot convention so any + attachment fits any base body. +- **Networkability:** The avatar's composition is expressed as a compact, + serializable **loadout** that the server can validate and replicate. +- **Performance:** Attachments are skinned to the same skeleton and share + materials where possible to minimize draw calls and skinning cost. +- **Tooling-friendly:** Assets are authored in standard glTF 2.0 and validated + by a build-time checker. + +--- + +## 2. Avatar Decomposition + +### 2.1 The Base Body + +Every avatar is built on a **base body** — a single glTF asset containing: + +- A **skeleton** (the canonical rig, see §3). +- A **base mesh** for the skin/body (head, torso, arms, legs). +- **Attachment points** (empty nodes) that define where accessories mount. + +The base body is the only asset that carries the skeleton. Attachments reference +the skeleton by **bone name** and are skinned to it; they never carry their own +rig. + +### 2.2 Body Parts vs. Attachments + +Two distinct concepts: + +| Concept | Definition | Example | +|----------------|-------------------------------------------------------------------------|----------------------------------| +| **Body part** | A mesh that replaces part of the base body's skin (e.g., a different head, a tail, wings). | Alternate head, cybernetic arm | +| **Attachment** | A mesh that mounts *on top of* the body without replacing it (clothing, accessories). | Shirt, hat, glasses, backpack | + +The distinction matters for **slot exclusivity** and **layering**: + +- **Body parts** are *exclusive* per slot — only one head can be equipped. +- **Attachments** are *layered* — a hat and glasses can coexist, and both sit + above the head mesh. + +--- + +## 3. Skeleton & Slot Convention + +### 3.1 Canonical Skeleton + +All avatars share a **canonical skeleton** with a fixed set of bone names. This +is the contract that lets any attachment fit any body. The skeleton follows a +humanoid convention (Godot's `Humanoid` profile / glTF VRM-style naming). + +Required bones (subset of the full rig): + +``` +Root +├── Hips +│ ├── Spine +│ │ ├── Chest +│ │ │ ├── Neck +│ │ │ │ └── Head +│ │ │ │ ├── HeadTop # hat / hair mount +│ │ │ │ ├── Jaw # face accessories +│ │ │ │ └── LeftEye / RightEye +│ │ │ ├── LeftShoulder +│ │ │ │ └── LeftUpperArm +│ │ │ │ └── LeftLowerArm +│ │ │ │ └── LeftHand +│ │ │ └── RightShoulder +│ │ │ └── RightUpperArm +│ │ │ └── RightLowerArm +│ │ │ └── RightHand +│ │ └── (Chest children for torso attachments) +│ ├── LeftUpperLeg +│ │ └── LeftLowerLeg +│ │ └── LeftFoot +│ └── RightUpperLeg +│ └── RightLowerLeg +│ └── RightFoot +``` + +**Rules:** + +- Bone names are **case-sensitive** and must match exactly. +- The skeleton is **T-pose** at rest for consistent skinning. +- Attachments must be skinned to **at least** the bones they deform; static + accessories may be parented to a single bone (e.g., a hat parented to `Head`). + +### 3.2 Attachment Slots + +Slots are the **logical mount points** for body parts and attachments. Each slot +maps to one or more bones and defines what can be equipped there. + +| Slot ID | Category | Mount bone(s) | Exclusivity | Example assets | +|----------------|--------------|--------------------|-------------|-------------------------------| +| `head` | body part | `Head` | exclusive | alternate head, face | +| `hair` | attachment | `HeadTop` | exclusive | hairstyles, hats | +| `face` | attachment | `Jaw` | exclusive | glasses, masks, beards | +| `torso` | body part | `Chest` | exclusive | alternate torso | +| `chest` | attachment | `Chest` | exclusive | shirts, armor, chest gear | +| `back` | attachment | `Chest` (back) | exclusive | backpacks, wings, capes | +| `left_arm` | body part | `LeftUpperArm` | exclusive | alternate arm | +| `right_arm` | body part | `RightUpperArm` | exclusive | alternate arm | +| `left_hand` | attachment | `LeftHand` | exclusive | gloves, weapons (held) | +| `right_hand` | attachment | `RightHand` | exclusive | gloves, weapons (held) | +| `legs` | body part | `Hips` | exclusive | alternate legs | +| `pants` | attachment | `Hips` | exclusive | pants, skirts | +| `left_foot` | attachment | `LeftFoot` | exclusive | shoes | +| `right_foot` | attachment | `RightFoot` | exclusive | shoes | +| `accessory` | attachment | `Root` (free) | multiple | belts, jewelry, floating items| + +**Slot semantics:** + +- **`exclusive`** slots allow at most one equipped item at a time. +- The **`accessory`** slot is *multi-slot* — multiple accessories may be equipped + simultaneously (bounded by a configurable limit, e.g., 8). +- **Body-part slots** (`head`, `torso`, `legs`, `left_arm`, `right_arm`) replace + the corresponding base mesh. When a body part is equipped, the base mesh for + that region is hidden. +- **Attachment slots** layer on top of the base mesh and never hide it. + +### 3.3 Slot → Bone Mapping + +Each slot declares a **primary mount bone** and an optional **secondary bone +list** for skinning. The runtime uses this to: + +1. Parent static accessories to the mount bone. +2. Skin skinned attachments to the union of the slot's bones. + +--- + +## 4. glTF/GLB Format Requirements + +All avatar assets (base bodies, body parts, attachments) are distributed as +**glTF 2.0** (`.gltf` + external buffers/textures) or **GLB** (`.glb`, binary +container). GLB is the preferred distribution format for runtime loading. + +### 4.1 Required Conventions + +| Concern | Requirement | +|--------------------|------------------------------------------------------------------------------| +| **Format** | glTF 2.0 (`.gltf` or `.glb`). GLB preferred for runtime. | +| **Coordinate system** | glTF standard: **+Y up, +Z forward, right-handed**. Godot converts to its own convention on import. | +| **Units** | Meters. Avatar height ~1.8 m at the `Root` bone. | +| **Skeleton** | Single skeleton per asset; bone names per §3.1. | +| **Skinning** | `skins` + `nodes` with `JOINTS_0` / `WEIGHTS_0` accessors. | +| **Pose** | T-pose at rest. | +| **Materials** | PBR metallic-roughness. Use `KHR_materials_unlit` for emissive/UI-only items. | +| **Textures** | Max 2048×2048; power-of-two preferred. Use `KHR_texture_basisu` (BasisU) for runtime. | +| **Animation** | Optional. If present, must not rename or remove canonical bones. | + +### 4.2 Asset Metadata (extras) + +Each asset **must** declare its slot and identity in the glTF `asset.extras` +object. This is the machine-readable contract consumed by the build pipeline and +runtime. + +```json +{ + "asset": { + "version": "2.0", + "extras": { + "agp": { + "type": "attachment", // "base_body" | "body_part" | "attachment" + "slot": "chest", // slot ID from §3.2 + "id": "shirt_bandit_01", // globally unique asset id + "display_name": "Bandit Shirt", + "version": "1.0.0", + "mount_bone": "Chest", // primary mount bone + "skin_bones": ["Chest", "Spine", "LeftUpperArm", "RightUpperArm"], + "exclusive": true, // matches slot exclusivity + "tags": ["cloth", "common"], + "author": "platform-team" + } + } + } +} +``` + +**Validation rules for `extras.agp`:** + +- `type` must be one of `base_body`, `body_part`, `attachment`. +- `slot` must be a known slot ID (§3.2). +- `mount_bone` must exist in the canonical skeleton. +- `skin_bones` must all exist in the canonical skeleton. +- `exclusive` must match the slot's declared exclusivity. +- `id` must be unique across the asset catalog. + +### 4.3 Node Naming Convention + +To make assets self-describing and mergeable, nodes follow a convention: + +- **Base body:** the mesh node is named `Body`; the skeleton root is `Root`. +- **Body part:** the mesh node is named `_Mesh` (e.g., `head_Mesh`). +- **Attachment:** the mesh node is named `_` (e.g., + `chest_shirt_bandit_01`). +- **Attachment points** (empty nodes) on the base body are named + `attach_` (e.g., `attach_head`, `attach_left_hand`). + +### 4.4 Material & Texture Requirements + +- All materials use the **metallic-roughness** PBR model. +- Textures are embedded in the GLB or referenced as external files; external + references must be relative paths within the asset package. +- **Color customization** is supported via a `baseColorFactor` tint applied at + runtime (multiplied over the albedo). Assets should use a neutral albedo + (white/gray) where tinting is intended. +- **Masked/transparent** materials must set the correct alpha mode + (`MASK` or `BLEND`) and `alphaCutoff`. + +### 4.5 Skeleton Merging at Runtime + +When an attachment is loaded, the runtime **merges** it onto the base body: + +1. Load the base body (skeleton + base meshes). +2. Load each equipped attachment. +3. For each attachment, **re-target its skin** to the base body's skeleton by + bone name (no re-export needed). +4. Parent static accessories to their `mount_bone`. +5. Hide base meshes for any equipped body-part slots. +6. Apply material tints and merge into the render tree. + +The merge is **name-based** — bone names must match exactly (§3.1). This is the +core reason for the strict naming contract. + +--- + +## 5. Loadout Model (Network & Persistence) + +The avatar's composition is serialized as a **loadout** — a compact, ordered +list of equipped asset IDs. This is what the server validates, persists, and +replicates. + +### 5.1 Loadout JSON + +```json +{ + "base_body": "base_human_male_01", + "slots": { + "head": "head_cyber_01", + "hair": "hair_mohawk_01", + "face": null, + "torso": null, + "chest": "chest_bandit_01", + "back": "backpack_01", + "left_arm": null, + "right_arm": null, + "left_hand": "glove_leather_01", + "right_hand": "glove_leather_01", + "legs": null, + "pants": "pants_cargo_01", + "left_foot": "shoe_boot_01", + "right_foot": "shoe_boot_01", + "accessory": ["belt_01", "necklace_01"] + } +} +``` + +**Rules:** + +- `base_body` is required and must reference a valid `base_body` asset. +- Each exclusive slot holds **at most one** asset id (or `null`). +- `accessory` is an array (0..N, bounded). +- The server validates that every id exists in the catalog and that slot + exclusivity is respected. + +### 5.2 Network Replication + +The loadout is replicated as part of the player's spawn/state data: + +``` +spawn_entity: + entity_id: uint32 + type: player + transform: { x, y, rot } + props: + loadout_hash: uint32 # hash of the loadout for change detection + loadout: { ... } # full loadout on spawn / on change +``` + +- The **full loadout** is sent on spawn and whenever it changes (reliable). +- A **`loadout_hash`** lets clients skip re-resolving assets if unchanged. +- The server is the **authority** on the loadout; clients send *requests* to + change equipment, and the server validates and broadcasts the result. + +### 5.3 Server-Side Validation + +The server rejects a loadout if: + +- `base_body` is missing or unknown. +- Any slot references an unknown asset id. +- An exclusive slot has more than one item. +- An asset's declared `slot` does not match the slot it is placed in. +- The accessory list exceeds the configured limit. +- The asset is not owned/unlocked by the player (for gated cosmetics). + +--- + +## 6. Godot Runtime Architecture + +### 6.1 Node Structure + +``` +Avatar (Node3D) +├── Skeleton3D # canonical skeleton (from base body) +├── BodyMeshes # base body meshes (skinned to Skeleton3D) +│ ├── head_Mesh +│ ├── torso_Mesh +│ └── ... +├── Attachments # equipped attachments +│ ├── chest_shirt_bandit_01 +│ ├── hair_mohawk_01 +│ └── ... +└── AttachmentPoints # empty nodes for dynamic mounting + ├── attach_head + ├── attach_left_hand + └── ... +``` + +### 6.2 Core Classes + +| Class | Responsibility | +|--------------------------|-------------------------------------------------------------| +| `AvatarSystem` | Loads base body + attachments, resolves loadouts, owns the avatar node. | +| `AvatarLoader` | glTF/GLB import, skeleton merge, skin re-targeting. | +| `AvatarSlotRegistry` | Static registry of slot definitions (id, bones, exclusivity).| +| `AvatarLoadout` | Serializable loadout model + validation. | +| `AvatarCatalog` | Asset catalog (id → asset metadata + resource path). | +| `AvatarVisualizer` | Builds the render tree from a loadout. | + +### 6.3 Loading Pipeline + +``` +loadout JSON + │ + ▼ +AvatarCatalog.resolve(loadout) # id → asset metadata + GLB path + │ + ▼ +AvatarLoader.load_base_body() # import GLB, extract skeleton + base meshes + │ + ▼ +for each equipped slot: + AvatarLoader.load_attachment()# import GLB, re-target skin to skeleton + │ + ▼ +AvatarVisualizer.assemble() # parent, hide replaced parts, apply tints + │ + ▼ +Avatar node ready for render +``` + +### 6.4 Performance Considerations + +- **Shared skeleton:** All meshes skin to one `Skeleton3D`, so skinning is + computed once per frame. +- **Material batching:** Attachments that share a material are merged into a + single mesh where possible (static accessories). +- **LOD:** Optional LOD variants per asset; the runtime selects based on + distance. +- **Caching:** Resolved loadouts and imported GLB resources are cached by + `AvatarCatalog` to avoid re-importing on respawn or re-join. + +--- + +## 7. Asset Pipeline & Validation + +### 7.1 Build-Time Validation + +A build-time checker validates every asset before it enters the catalog: + +1. **Format check:** valid glTF 2.0 / GLB. +2. **Metadata check:** `extras.agp` present and valid (§4.2). +3. **Skeleton check:** all `skin_bones` exist in the canonical skeleton. +4. **Slot check:** `slot` is known and `exclusive` matches. +5. **Mesh check:** meshes are skinned (or static accessories parented to a bone). +6. **Texture check:** sizes, formats, and alpha modes are valid. +7. **Uniqueness check:** `id` is unique. + +### 7.2 Catalog Manifest + +The catalog is a JSON manifest mapping asset ids to metadata and resource paths: + +```json +{ + "assets": { + "base_human_male_01": { + "type": "base_body", + "path": "res://assets/avatars/base_human_male_01.glb", + "version": "1.0.0" + }, + "chest_bandit_01": { + "type": "attachment", + "slot": "chest", + "path": "res://assets/avatars/chest_bandit_01.glb", + "version": "1.0.0" + } + } +} +``` + +--- + +## 8. Security & Anti-Cheat + +- **Server-authoritative loadouts:** Clients only *request* equipment changes; + the server validates against the catalog and the player's owned items. +- **No client asset injection:** Clients cannot send arbitrary GLB data; they + reference catalog ids only. +- **Rate limiting:** Equipment-change requests are rate-limited per peer. +- **Hash verification:** The `loadout_hash` lets the server detect tampered or + stale loadout state. + +--- + +## 9. Open Questions / Future Work + +- **Morph targets / blendshapes:** Support for facial expressions and body + morphs (e.g., height, muscle) via glTF morph targets. +- **Physics attachments:** Cloth simulation for capes/skirts (Godot cloth). +- **Dynamic tinting UI:** A color-picker that writes `baseColorFactor` tints. +- **VRM compatibility:** Consider supporting VRM (a glTF extension for + humanoid avatars) as an import source. +- **Attachment LOD streaming:** Streaming high-res attachments on demand. + +--- + +## 10. Summary + +| Concern | Decision | +|------------------------|-------------------------------------------------------| +| Format | glTF 2.0 / GLB (GLB preferred for runtime) | +| Skeleton | Single canonical humanoid skeleton, name-based merge | +| Slots | Fixed slot registry; exclusive body parts + layered attachments | +| Loadout | Compact JSON; server-authoritative and validated | +| Replication | Full loadout on spawn/change + `loadout_hash` | +| Runtime | Godot `Skeleton3D` + name-based skin re-targeting | +| Validation | Build-time asset checker + runtime catalog validation |