# ADR-0013: Blender-authored NPC characters and baked materials - **Status:** Proposed - **Date:** 2026-11-01 ## Context NPCs are the procedural capsule-and-box rig from [ADR-0010](0010-procedural-npc-presentation.md): articulated `Node3D` limbs, flat `StandardMaterial3D` colors, and sphere hair/eyes. ADR-0010 accepted this as a placeholder and recorded that a later skinned-mesh pass would replace the rig while keeping the `Head` contract. The props already moved to generated `.glb` assets ([ADR-0012](0012-surface-materials-and-generated-props.md)), but people remain the lowest-fidelity part of the scene: no UVs, no baked normal or ambient-occlusion maps, and silhouettes built from capsules. The project renders with the **GL Compatibility** backend (no SSAO/SSIL), so visual quality has to come from the assets and baked maps rather than a renderer change. CI must stay offline: the Blender step is a developer tool, not a build dependency. ## Decision 1. **Author the NPC body in Blender** and export a skinned, UV-mapped base mesh as `assets/models/generated/npc_base.glb`, driven by a new `tools/npc_pipeline.py` (`build` / `audit`) invoked through the Blender flatpak in headless mode. The `.blend` source is committed under `assets/models/npc/`. 2. **Bake material maps in Blender** — neutral albedo, normal, and a packed ORM (occlusion/roughness/metallic) map per material slot (`skin`, `cloth`, `pants`, `accessory`, `hair`). Ambient occlusion is baked into the albedo or the ORM R channel, since Compatibility has no screen-space AO. 3. **Keep a procedural fallback.** `NpcAvatar` loads the imported `PackedScene` when it exists and falls back to the current `_build_body()` rig otherwise, mirroring `WorldBuilder._add_prop_model()`. Committed assets mean CI never needs Blender, and the headless suite runs against the fallback when the import is absent. 4. **Preserve the `Head` contract and seed determinism.** The armature bones are named after the current nodes (`Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R`); `outfit_for` and friends stay pure and drive the material slots. A thin `_set_limb_rotation()` wrapper lets `_animate()` / `_idle_layer()` target either `Node3D` pivots or `Skeleton3D` bones. 5. **AI tooling supplies art direction only.** `tools/asset_pipeline.py` generates seamless clothing albedos, `tools/model_pipeline.py`'s palette helpers (`dominant_colors`, `assign_roles`) derive per-role palettes from concept sheets, and `tools/screen_designs.py judge` closes the loop. All AI outputs are committed; generation is dev-time and never a runtime or CI dependency. ## Consequences ### Positive - NPCs gain real geometry, UVs, and baked normal/AO/roughness, so they read as people at cinematic distance without a renderer switch. - Skinned meshes collapse ~33 draw calls per avatar into one, which can offset the skinning cost at `MAX_LIVE_NPCS = 16`. - The asset contract stays behind the same `.glb` + audit discipline that props already use; the procedural fallback keeps offline CI deterministic. - AO baked into the maps compensates for the absence of SSAO in Compatibility. ### Negative - A new toolchain (Blender flatpak + `npc_pipeline.py`) and its path/colour-space gotchas enter the repo; contributors must run `audit` after changing the `.blend`. - Skinning shifts cost from draw calls to CPU; the 16-NPC frame budget must be re-measured before raising the cap. - Two avatar backends (skinned + fallback) must stay behaviourally identical, or tests that assume the fallback will silently pass against one and not the other. ## Alternatives considered - **Keep the procedural rig and only add textures.** Rejected: textures on capsules do not fix the silhouette; the rig is the fidelity ceiling. - **Use Mixamo / a downloaded skinned character.** Rejected: licensing and retargeting overhead exceed the value; a low-poly base authored in-house keeps the art direction and the `Head`/bone contract under our control. - **Switch the renderer to Forward+ for SSAO/SSIL.** Rejected as a prerequisite; it would break the Web export assumptions. A desktop-only Forward+ preset remains a follow-up, not a dependency of this decision. - **A 3D-model (image-to-mesh) API instead of Blender.** Rejected for characters; output topology is not controllable enough to satisfy the bone/UV contract. It remains a possible prop-only follow-up (ADR-0012).