Files
settled-reach/docs/design/character-visuals-spec.md
jpmschweitzerandClaude Opus 4.6 6dee6b73f0 docs(design): add character visuals spec and compositor API from workshop
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>
2026-03-17 23:34:16 +01:00

15 KiB
Raw Permalink Blame History

title, description, type, status, sprint, ticket, created
title description type status sprint ticket created
Character Visuals Specification Complete spec for character rendering: layer stack, color regions, body/face/hair types, direction system, outline, LOD spec active 28 684 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 (~3264px 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: 23)

Sprint 28 ships with 23 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 perbody-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 3264px. 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.