Round 22 workshop output: character-visuals-spec.md (color mesh regions, LOD tiers, layered composition) and compositor-api-spec.md (Node3D architecture, CharacterColors data structure, set_color API). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
293 lines
15 KiB
Markdown
293 lines
15 KiB
Markdown
---
|
||
title: "Character Visuals Specification"
|
||
description: "Complete spec for character rendering: layer stack, color regions, body/face/hair types, direction system, outline, LOD"
|
||
type: spec
|
||
status: active
|
||
sprint: 28
|
||
ticket: 684
|
||
created: 2026-03-17
|
||
---
|
||
|
||
# Character Visuals Specification
|
||
|
||
**Produced by:** Sprint 28 Character Visuals Workshop (ticket #684)
|
||
**Date:** 2026-03-17
|
||
**Art direction reference:** Invisible Inc — clean, adult, high contrast. Not a candy store.
|
||
**Downstream:** `docs/design/compositor-api-spec.md`, tickets #686–#692 (visual team), #693 (compositor)
|
||
|
||
---
|
||
|
||
## 1. Camera and Display Context
|
||
|
||
All art must be authored and tested at the confirmed gameplay camera angle:
|
||
|
||
- **Camera tilt:** 30° from vertical (60° from horizontal) — orthographic
|
||
- **Map rotation:** 45° (diamond grid)
|
||
- **Projection:** Orthographic — no perspective foreshortening
|
||
- **Reference games:** Hades, Divinity: Original Sin (not Diablo — 45° is NOT the default)
|
||
|
||
Characters are rendered as live 3D models in the Godot scene (D-149). The camera is a real `Camera3D` — perspective is not faked in art. Artwork is authored as 3D assets, not pre-rendered sprites.
|
||
|
||
**Art direction notes at this angle:**
|
||
- Front faces are significantly visible at 30° — face and clothing detail matters more than at 45°
|
||
- The top of characters' heads is less prominent — hair silhouette reads from the side, not top-down
|
||
- Wall depth and character shadow are both visible — spatial grounding is strong
|
||
- Characters must read clearly at tile scale (~32–64px display height)
|
||
|
||
---
|
||
|
||
## 2. Rendering Architecture Overview
|
||
|
||
Characters use a `CharacterCompositor` (Node3D subtree). See `docs/design/compositor-api-spec.md` for the full API.
|
||
|
||
- **Not sprites:** No pre-rendered sprite sheets. All composition is runtime in Godot.
|
||
- **Outline:** Inverted hull method — uniform dark gray (#1a1a1a) for all characters. Not a relationship indicator (D-154). See §10.
|
||
- **Relationship data:** Lives on minimap, name bubbles, and insert/perception mode overlay (D-033 amended). Not on character body in normal gameplay.
|
||
- **Player character:** Visually identical to NPCs — same models, same variety (D-153).
|
||
|
||
---
|
||
|
||
## 3. Direction System
|
||
|
||
**Server (authoritative):** 8 facing directions — N, NE, E, SE, S, SW, W, NW.
|
||
|
||
**Client Sprint 28:** 4 visual groups. The 3D model rotates to the true 8-direction angle; only the visual mesh group snaps to 4:
|
||
|
||
| Visual Group | Covers Server Facings | Mirror |
|
||
|---|---|---|
|
||
| North | N, NW | — |
|
||
| East | NE, E | — (source mesh) |
|
||
| South | SE, S | — |
|
||
| West | SW, W | Mirror of East |
|
||
|
||
The exact mapping of server facings to visual groups depends on camera orientation relative to the diamond grid — Tyre to confirm in compositor implementation. West is a horizontal mirror of East; no additional art required.
|
||
|
||
**Character editor:** Four cardinal direction buttons. Button order: **S → E → N → W**. Default on open: **South** (face-forward at 30° camera — maximum cosmetic utility, highest clothing/face visibility). No free-spin (D-155). The four buttons correspond directly to the four visual groups.
|
||
|
||
**Critical distinction — 8 facings vs 4 visual groups:**
|
||
- **8 server-side facings** = perception system input. Used by fog-of-war, vision cone direction, and all simulation logic. The server always tracks the true 8-direction facing.
|
||
- **4 visual groups** = character rendering output. What the player actually sees. Characters snap to the nearest cardinal visual group. Diagonal facings do not exist as visible character states — a character facing NE renders as the East visual group.
|
||
- Smooth rotation interpolation between directions is applied at the model root level (ModelRoot rotation), so the body physically points the correct way even while the visual mesh group is a snap.
|
||
|
||
**Post-Sprint 28:** Diagonal mesh variants (NE, NW, SE, SW as separate visual groups) can be added without protocol changes — the 8 server facings are already tracked.
|
||
|
||
---
|
||
|
||
## 4. Layer Stack
|
||
|
||
Layers are rendered bottom-to-top. Each layer is a separate mesh in the `CharacterCompositor` subtree.
|
||
|
||
| # | Layer | Notes |
|
||
|---|---|---|
|
||
| 1 | Body base | Body type variant (slim / average / stocky). Skin tone mesh regions. |
|
||
| 2 | Leg clothing | Trousers, shorts, skirt. Occludes body base from waist down. |
|
||
| 3 | Footwear | Shoes, boots. Occludes leg clothing at ankle/foot. |
|
||
| 4 | Torso clothing (back) | Rear of jacket/shirt. Behind arms. |
|
||
| 5 | Torso clothing (front) | Front of jacket/shirt. Over arms. |
|
||
| 6 | Accessories | Belt, bag, holster, jewelry. Multiple items possible. |
|
||
| 7 | Head / face | Face mesh with skin regions. Neck connects to body base. |
|
||
| 8 | Hair (back) | Hair behind the head/shoulders. |
|
||
| 9 | Hair (front) | Hair in front of face / over forehead. |
|
||
| — | Outline | Inverted hull pass (not a layer — shader effect on all meshes). |
|
||
| 10 | Scar overlays | Placed on exposed skin regions. Additive blend on skin. |
|
||
| 11 | Tattoo overlays | Placed on exposed skin regions. Multiply blend on skin. |
|
||
| 12 | Injury overlays | Applied over clothing (tears, stains) and face (bruises, cuts). |
|
||
| 13 | Expression overlay | Replaces/modifies face mesh for expression state. |
|
||
|
||
**Occlusion rule:** Each layer should include an occlusion mesh (invisible, write-only depth) matching the body shape, so underlying layers do not bleed through clothing at edges.
|
||
|
||
---
|
||
|
||
## 5. Color Mesh Regions
|
||
|
||
Each layer exposes named UV-mapped color regions that can be overridden by the compositor. Regions that are not overridden use the layer's authored default color.
|
||
|
||
### Body base
|
||
| Region | Description |
|
||
|---|---|
|
||
| `skin_primary` | Face, hands, neck, visible forearms |
|
||
| `skin_secondary` | Inner arm, sole of hand — subtle variation (may auto-derive from `skin_primary`) |
|
||
|
||
### Hair
|
||
| Region | Description |
|
||
|---|---|
|
||
| `hair_primary` | Main hair color |
|
||
| `hair_highlight` | Secondary sheen / highlight (may auto-derive from `hair_primary` as lighter tint) |
|
||
|
||
### Clothing (all clothing items)
|
||
| Region | Description |
|
||
|---|---|
|
||
| `cloth_primary` | Dominant fabric — the main color the player picks |
|
||
| `cloth_secondary` | Trim, seams, buttons, collar, cuffs |
|
||
| `cloth_accent` | Optional third zone — logo patch, lining, contrast panel. May be absent on simpler items. |
|
||
|
||
### Accessories
|
||
| Region | Description |
|
||
|---|---|
|
||
| `accessory_primary` | Main material color |
|
||
| `accessory_secondary` | Optional secondary (e.g. stitching on a bag, gem on jewelry) |
|
||
|
||
**Design note:** Not all clothing recolors uniformly. A jacket's body is `cloth_primary`; its piping is `cloth_secondary`. Per-item region definition is the artist's responsibility. Invisible Inc reference: colored bodysuits with trim lines — crisp and readable at small scale.
|
||
|
||
---
|
||
|
||
## 6. Body Types (Sprint 28: 2–3)
|
||
|
||
Sprint 28 ships with 2–3 body type variants. Minimum viable: slim + average. Stocky as stretch if time allows.
|
||
|
||
| Type | Visual description | Notes |
|
||
|---|---|---|
|
||
| Slim | Narrow shoulders, slight frame, elongated limbs | |
|
||
| Average | Medium proportions, balanced silhouette | |
|
||
| Stocky | Broad shoulders, thicker torso and limbs | Stretch goal, Sprint 28 |
|
||
|
||
**Constraints:**
|
||
- All body types share the same layer interface (`skin_primary`, `skin_secondary` region names)
|
||
- Clothing mesh layers must fit all body types without visible clipping or floating
|
||
- Clothing artists author per–body-type mesh variants OR the compositor scales the clothing mesh to the body type (implementation TBD — Tyre to specify in compositor-api-spec)
|
||
- Silhouette must differ meaningfully at 32px display height — not just subtle (otherwise pointless at tile scale)
|
||
|
||
**Combination validation:** Sprint 28 goal is to confirm that switching body types with any clothing combination produces no visual artifacts. This is the primary QA target.
|
||
|
||
---
|
||
|
||
## 7. Face Types
|
||
|
||
Face is a compositable region on the `Head/Face` layer.
|
||
|
||
**Range:** Broad variety. Asymmetric features valued — these are adults, not game-industry default faces. Invisible Inc reference for readability at small scale.
|
||
|
||
**Mesh regions on face:**
|
||
- `skin_primary` — face, forehead, cheeks, chin
|
||
- `skin_lips` — lips (may auto-derive from `skin_primary`)
|
||
|
||
**Compositable elements within the face region (each a separate overlay or mesh swap):**
|
||
- Eye shape (mesh swap — left/right eye as separate swappable meshes or UV regions)
|
||
- Nose shape (mesh swap)
|
||
- Mouth/lip shape (mesh swap)
|
||
- Eyebrow shape (mesh swap or UV overlay)
|
||
|
||
**Expression states (minimum Sprint 28):**
|
||
|
||
| State | Description | Trigger |
|
||
|---|---|---|
|
||
| `neutral` | Default resting face | No active emotional state |
|
||
| `alert` | Eyes slightly widened, posture tense | Danger or person-of-interest nearby |
|
||
| `stressed` | Furrowed brow, tight mouth | High stress value from simulation |
|
||
| `distressed` | Open distress, visible tension | Extreme stress or injury |
|
||
|
||
Expression overlay replaces or modifies the base face mesh. The expression does not affect the body posture in Sprint 28.
|
||
|
||
---
|
||
|
||
## 8. Hair Style Spec
|
||
|
||
Hair silhouette must read legibly at tile scale. Overly detailed styles will read as noise at small size — err on the side of clean, strong silhouettes.
|
||
|
||
**Minimum Sprint 28 styles:**
|
||
- Short A (close-cropped)
|
||
- Short B (textured/spiky)
|
||
- Medium A (smooth, parted)
|
||
- Medium B (wavy/voluminous)
|
||
- Long A (flowing, shoulder-length or longer)
|
||
- Bald / shaved
|
||
|
||
**Color regions:** `hair_primary`, `hair_highlight` (see §5).
|
||
|
||
**Layer split:** Every style must have a `Hair (back)` and `Hair (front)` mesh — these straddle the head/face layer so face is visible between them at certain angles.
|
||
|
||
---
|
||
|
||
## 9. Scar and Tattoo Overlays
|
||
|
||
Overlays are placed on **exposed skin regions only** (face, hands, forearms — not on covered areas).
|
||
|
||
**Placement zones:**
|
||
- Face zone
|
||
- Left arm / hand zone
|
||
- Right arm / hand zone
|
||
- Torso zone (only visible when wearing open/sleeveless clothing)
|
||
|
||
**Blending:**
|
||
- **Tattoos:** Multiply blend on `skin_primary` region — color mixes with skin tone
|
||
- **Scars:** Additive blend on `skin_primary` — lighter, raised-texture appearance
|
||
|
||
**Optional:** Not all characters have scars or tattoos. Zero overlays is valid and common.
|
||
|
||
---
|
||
|
||
## 10. Injury Overlays
|
||
|
||
Injury state represents physical damage visible on the character's appearance.
|
||
|
||
**States (minimum Sprint 28):**
|
||
|
||
| State | Clothing effect | Skin effect |
|
||
|---|---|---|
|
||
| `undamaged` | None | None |
|
||
| `light_injury` | Minor tear/dirt on torso clothing | Minor bruising on face/exposed skin |
|
||
| `heavy_injury` | Visible tears, blood staining | Heavy bruising, cuts visible |
|
||
|
||
Injury overlays are applied on top of clothing (not inside it) and on top of exposed skin. Injury tint auto-derives from the layer's base color (darker, desaturated).
|
||
|
||
---
|
||
|
||
## 11. Outline Specification
|
||
|
||
- **Method:** Inverted hull — GPU vertex extrusion, back-face-only render pass (D-150)
|
||
- **Color:** `#1e1e24` (very dark blue-grey) for all characters, always — not pure black
|
||
- **Width:** 1px at gameplay zoom (consistent regardless of zoom level — use shader-based fixed screen-space width)
|
||
- **Semantic content:** None — the outline does not encode relationship, status, or any game state
|
||
- **LOD behavior:** At Tier 2 (billboard impostor), the outline is baked into the impostor sprite. The inverted hull is disabled at Tier 2 — no separate draw call.
|
||
|
||
---
|
||
|
||
## 12. LOD Strategy
|
||
|
||
Three tiers, degraded based on GPU frame budget (not distance, not fixed count) (D-152):
|
||
|
||
| Tier | Label | Description |
|
||
|---|---|---|
|
||
| 0 | Full | All compositor layers active. Full 3D model. Inverted hull outline. |
|
||
| 1 | Simplified mesh | Reduced-poly model. Clothing layers merged into combined mesh. Outline active. |
|
||
| 2 | Billboard impostor | Flat sprite impostor with outline baked in. One draw call. |
|
||
|
||
**Degradation order:** Characters furthest from the player's character degrade first. Nearest characters stay at Tier 0 longest. Threshold is a frame budget check — when the GPU is under pressure, demote the outermost tier-eligible characters.
|
||
|
||
**LOD trigger:** Proactive on **projected character count**, not reactive on frame drop. The LOD manager anticipates load based on how many characters will be in the scene this frame and pre-demotes before GPU pressure occurs. Reactive triggering (demoting after a frame drops) produces visible hitches. Proactive triggering is invisible.
|
||
|
||
**Billboard LOD requirement (Tier 2 preservation):** At billboard tier, each impostor sprite **must visually encode two signals per character:**
|
||
1. **Body size tier** (slim / average / stocky) — silhouette must differ meaningfully at billboard scale
|
||
2. **Dominant clothing color** — the primary `cloth_primary` of the most visible clothing item must be readable
|
||
|
||
These signals must not be optimized away during impostor baking. The impostor is not a generic character silhouette — it is a per-character snapshot preserving identity.
|
||
|
||
**Pause behavior:** When the game is paused, the render budget is fully freed. All characters restore to Tier 0. The pause button is a deliberate affordance — player can inspect any character at full detail.
|
||
|
||
**Chaos feel:** In a very large crowd (400+ characters), peripheral degradation is intentional and cognitively appropriate — real crowds have visual noise at the edges. The player naturally focuses center-screen where Tier 0 characters are.
|
||
|
||
---
|
||
|
||
## 13. Art Direction Reference Summary
|
||
|
||
| Principle | Notes |
|
||
|---|---|
|
||
| Reference: Invisible Inc | Clean, sleek, adult. High contrast, strong silhouettes. Not Rimworld-chunky, not anime. |
|
||
| Scale readability | Every design decision must be validated at 32–64px. Detail that doesn't read at tile scale should be removed. |
|
||
| Uniform outlines | Dark gray for everyone. No auras, no glows, no player distinction. |
|
||
| Per-item color variety | Clothing colors are per-item, not per-faction, not per-team. Life-sim, not RTS. |
|
||
| Adult faces | Asymmetric features, variety across ages and types. Not default RPG faces. |
|
||
|
||
---
|
||
|
||
## 14. Open Questions / Flags
|
||
|
||
| Item | Status | Notes |
|
||
|---|---|---|
|
||
| Exact N/E/S/W → server facing mapping | Open | Tyre to confirm in compositor-api-spec based on camera orientation |
|
||
| Stocky body type (Sprint 28) | Conditional | Stretch goal — drop if time pressure |
|
||
| Clothing mesh per body type vs. scale | Open | Tyre to specify — separate mesh variants or compositor-level scaling |
|
||
| Torso-zone tattoo visibility | Open | Depends on clothing occlusion rules — artist + Tyre to confirm |
|
||
| Expression overlay = mesh swap or morph target | Open | Tyre to decide in compositor-api-spec |
|
||
| D-146 interaction with 3D rendering | Flag | D-146 says "tile-scale sprite with heavy zoom" for editor preview; D-149 says live 3D rendering. The editor preview may show the live 3D model at zoom scale — compatible if interpreted as "tile-scale representation" not "2D sprite". Do not reopen D-146 without explicit direction from Jeroen. |
|