Plan the v0.6.0 graphics pass and add Blender-authored NPC assets

This commit is contained in:
zwitschi committed 2026-11-01 12:02:27 +01:00
1 parent a27e8cb74c
commit b24d0aeb61
33 files changed
+1005 -2

No files matched your search

@@ -0,0 +1,85 @@
# 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).