Raise the NPC character budget to 24k and refine the prototype neck

This commit is contained in:
zwitschi committed 2026-11-05 19:37:19 +01:00
1 parent f0d4b81239
commit 79224c5acf
19 files changed
+128 -56

No files matched your search

@@ -1,6 +1,6 @@
--- ---
name: gig-line-blender-asset-contract name: gig-line-blender-asset-contract
description: Use when adding, rebuilding, auditing or fixing any Gig Line Blender-authored character, NPC base, gear, prop or venue geometry. Covers the required 13 core bones (optional extra bones allowed), five tint slots, 16,000/16,000 character budgets, gear attach space, 256² texture/AO rules, headless flatpak Blender commands, optional MCP guidance, and the inspect/audit/fit/proportion/capture gates. Do not use for pure GDScript/UI work or release publishing. description: Use when adding, rebuilding, auditing or fixing any Gig Line Blender-authored character, NPC base, gear, prop or venue geometry. Covers the required 13 core bones (optional extra bones allowed), five tint slots, 24,000/24,000 character budgets, gear attach space, 256² texture/AO rules, headless flatpak Blender commands, optional MCP guidance, and the inspect/audit/fit/proportion/capture gates. Do not use for pure GDScript/UI work or release publishing.
compatibility: Gig Line repository checkout. Godot 4.7.2 on PATH; Blender 5.2 LTS via the org.blender.Blender flatpak for build/bake/render. Audit, fit, inspect and proportion checks are stdlib-only. compatibility: Gig Line repository checkout. Godot 4.7.2 on PATH; Blender 5.2 LTS via the org.blender.Blender flatpak for build/bake/render. Audit, fit, inspect and proportion checks are stdlib-only.
metadata: metadata:
project: Gig Line project: Gig Line
@@ -51,8 +51,8 @@ and resolve the source of truth before editing assets.
| Bones | The 13 required core bones: `Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R`, `Spine`, `Neck`. Optional extra bones (fingers, face, toes) are allowed; they must not move the core rest positions or break animation proxies. | | Bones | The 13 required core bones: `Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R`, `Spine`, `Neck`. Optional extra bones (fingers, face, toes) are allowed; they must not move the core rest positions or break animation proxies. |
| Tint slots | `skin`, `cloth`, `pants`, `accessory`, `hair`. | | Tint slots | `skin`, `cloth`, `pants`, `accessory`, `hair`. |
| Orientation | Front is Blender −Y / Godot +Z; feet grounded near y = 0; metres. | | Orientation | Front is Blender −Y / Godot +Z; feet grounded near y = 0; metres. |
| Character budget | **16,000 triangles / 16,000 glTF vertices** per character (`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS`). | | Character budget | **24,000 triangles / 24,000 glTF vertices** per character (`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS`). |
| Frozen fallback | `npc_base` remains the low-poly fallback; it shares the 16,000/16,000 cap but is not required to use it. Do not silently lower the cap. | | Frozen fallback | `npc_base` remains the low-poly fallback; it shares the 24,000/24,000 cap but is not required to use it. Do not silently lower the cap. |
| Gear budget | **1,500 triangles** per gear piece; authored in attach-point space. | | Gear budget | **1,500 triangles** per gear piece; authored in attach-point space. |
| Prop budget | **4,000 triangles** per prop. | | Prop budget | **4,000 triangles** per prop. |
| Gear attach | Use the catalogued attach points; verify with `fit`. Runtime `GEAR_OFFSETS` (lanyard forward, firefighter helmet up) and `HAND_ITEM_FORWARD` are intentional, not asset-contract changes. | | Gear attach | Use the catalogued attach points; verify with `fit`. Runtime `GEAR_OFFSETS` (lanyard forward, firefighter helmet up) and `HAND_ITEM_FORWARD` are intentional, not asset-contract changes. |
@@ -167,7 +167,7 @@ At minimum:
- `npc_proportions.py` passes the stylized guide. - `npc_proportions.py` passes the stylized guide.
- `npc_pipeline.py audit` passes if the base/textures changed. - `npc_pipeline.py audit` passes if the base/textures changed.
- texel density is inside the authored/fabric bands. - texel density is inside the authored/fabric bands.
- No character exceeds 16,000/16,000; gear/prop caps hold. - No character exceeds 24,000/24,000; gear/prop caps hold.
- 24-NPC budget and Web/Compatibility path remain green. - 24-NPC budget and Web/Compatibility path remain green.
- Contract tests, headless suite, capture gates and docs check pass. - Contract tests, headless suite, capture gates and docs check pass.
- The diff contains only the intended assets, tools, tests and docs. - The diff contains only the intended assets, tools, tests and docs.
@@ -59,7 +59,7 @@ skin, cloth, pants, accessory, hair
| Asset | Limit | | Asset | Limit |
| --- | --- | | --- | --- |
| Character (library and base fallback) | 16,000 triangles / 16,000 glTF vertices | | Character (library and base fallback) | 24,000 triangles / 24,000 glTF vertices |
| Gear piece | 1,500 triangles | | Gear piece | 1,500 triangles |
| Prop | 4,000 triangles | | Prop | 4,000 triangles |
| Library prop/venue total | must stay inside the venue tests and library budget | | Library prop/venue total | must stay inside the venue tests and library budget |
+2 -2
View File
@@ -91,8 +91,8 @@ specialists the task needs:
### Project rules that override a skill ### Project rules that override a skill
1. **The project contract wins.** `npc_base` mesh node, the 13 required core bones at their rest 1. **The project contract wins.** `npc_base` mesh node, the 13 required core bones at their rest
positions, five tint slots, front on Blender −Y / Godot +Z, grounded feet, 16,000 positions, five tint slots, front on Blender −Y / Godot +Z, grounded feet, 24,000
triangles / 16,000 glTF vertices per character, 1,500 per gear, 4,000 per prop. Optional triangles / 24,000 glTF vertices per character, 1,500 per gear, 4,000 per prop. Optional
extra bones (fingers, face, toes) are allowed if they do not move the core rest positions. A skill extra bones (fingers, face, toes) are allowed if they do not move the core rest positions. A skill
may recommend another topology, naming or budget scheme; do not adopt it unless the may recommend another topology, naming or budget scheme; do not adopt it unless the
change is explicitly planned and the contract, tests and docs are updated together. change is explicitly planned and the contract, tests and docs are updated together.
+8
View File
@@ -4,6 +4,14 @@ All notable user-facing changes to Gig Line. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use
Semantic Versioning. Semantic Versioning.
## [Unreleased]
### Changed
- Raised the character mesh budget from 16,000 to 24,000 triangles and glTF
vertices so the MakeHuman head, MPFB body, fused neck and clothing variants
can fit without heavy decimation. Gear and prop budgets are unchanged.
## [0.9.8] — 2026-11-04 ## [0.9.8] — 2026-11-04
### Fixed ### Fixed
+12 -2
View File
@@ -114,11 +114,21 @@ canonical implemented-features list in `docs/development-status.md`.
`docs/asset-tooling.md#optional-interactive-blender-mcp`. The headless flatpak scripts remain authoritative; MCP is `docs/asset-tooling.md#optional-interactive-blender-mcp`. The headless flatpak scripts remain authoritative; MCP is
never a build/CI/test/runtime dependency, and anything MCP touched must pass the never a build/CI/test/runtime dependency, and anything MCP touched must pass the
CLI audit/fit gates. CLI audit/fit gates.
- **Character mesh cap (2026-11-04):** all characters use 16,000 tris / 16,000 glTF vertices - **Character mesh cap (2026-11-05):** all characters use 24,000 tris / 24,000 glTF vertices
(`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` in `tools/blender_assets.py`; `npc_pipeline.MAX_TRIANGLES`/ (`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` in `tools/blender_assets.py`; `npc_pipeline.MAX_TRIANGLES`/
`MAX_VERTICES`). The required 13 core bones stay, optional extra bones are allowed, and the `MAX_VERTICES`). The required 13 core bones stay, optional extra bones are allowed, and the
low-poly `npc_base` fallback remains the shipped fallback. Measure the 24-NPC/Web budget after low-poly `npc_base` fallback remains the shipped fallback. The raise funds the MPFB body and a
seamless neck/shoulder junction. Measure the 24-NPC/Web budget after
any geometry change; never silently lower the cap without a new decision. any geometry change; never silently lower the cap without a new decision.
- **MPFB prototype neck (2026-11-05):** a boolean cylinder (r 0.102) cuts the body neck bore; a
smooth lathe covers it. The accepted head keeps a wide flat cut brim at 1.53, so the prototype
trims the head at 1.55 (discarding only the neck stub) and matches the lathe to the smaller
elliptical opening. This removes the square collar, dark notches and wide brim. Remaining: a
thin jaw crease and a hard shirt-shoulder point in three-quarter view. Do not re-add a torus,
flat disk, collar shell or destructive neck smoothing; those were rejected.
- **MPFB prototype count (2026-11-05):** 19,747 tris / 11,475 glTF verts / 1.810 m / 14 bones /
10 materials; `inspect` clean. Body-only head/neck surgery lives in
`tmp/npc-research/build_mpfb_npc.py` (backups in `/tmp/build_mpfb_npc_94.py`).
- **Engine baseline:** Godot 4.7.2 stable (official editor `~/.local/bin/godot`). The system's - **Engine baseline:** Godot 4.7.2 stable (official editor `~/.local/bin/godot`). The system's
`godot-runner` cannot load arbitrary project paths (compiled without path overrides); use the `godot-runner` cannot load arbitrary project paths (compiled without path overrides); use the
official editor binary. official editor binary.
+16 -7
View File
@@ -18,7 +18,7 @@ Goal: replace the sphere-and-primitive face and hand construction with a local,
development pipeline that can build, inspect and compare the **head/face** and **hands** separately development pipeline that can build, inspect and compare the **head/face** and **hands** separately
before assembling them into the shipped character. Prioritise face readability at portrait before assembling them into the shipped character. Prioritise face readability at portrait
distance, then hand silhouette and gear contact. Preserve the `npc_base` contract, the 13 core-bone rig (optional extra bones allowed), distance, then hand silhouette and gear contact. Preserve the `npc_base` contract, the 13 core-bone rig (optional extra bones allowed),
five tint slots, 16,000/16,000 character cap, optional extra bones beyond the required 13, and the Compatibility/Web path. five tint slots, 24,000/24,000 character cap, optional extra bones beyond the required 13, and the Compatibility/Web path.
*Skill route: `character-artist`, `retopology`, `sculpting`, `rigging`, `uv-workflow`, *Skill route: `character-artist`, `retopology`, `sculpting`, `rigging`, `uv-workflow`,
`texture-workflow`, `materials`, `lookdev`, `asset-optimization`, `qa-review`.* `texture-workflow`, `materials`, `lookdev`, `asset-optimization`, `qa-review`.*
@@ -28,7 +28,7 @@ gitignored `.blend1`; the 12 production characters are assembled procedurally by
`tools/blender_assets.py` from `_shape_head_mesh`, `_face`, `_hair` and `_hands_and_shoes`. `tools/blender_assets.py` from `_shape_head_mesh`, `_face`, `_hair` and `_hands_and_shoes`.
The MakeHuman head is now a second head source (`build_character(head="mh")`, The MakeHuman head is now a second head source (`build_character(head="mh")`,
`build --head mh`, `npc_parts.py assemble --head mh`) and all 12 variants assemble inside the `build --head mh`, `npc_parts.py assemble --head mh`) and all 12 variants assemble inside the
16,000-triangle cap with it. The procedural body, hands and clothing remain unchanged. The 24,000-triangle cap with it. The procedural body, hands and clothing remain unchanged. The
MakeHuman body prototype and its temporary head/hand swap live under `tmp/npc-research/` and are MakeHuman body prototype and its temporary head/hand swap live under `tmp/npc-research/` and are
not the shipped path. This overhaul supplies the reusable head/face and hand parts that both the not the shipped path. This overhaul supplies the reusable head/face and hand parts that both the
procedural characters and the MakeHuman body can consume. procedural characters and the MakeHuman body can consume.
@@ -45,7 +45,7 @@ the rest of §C remains the broad review checklist.
`docs/3d-model-review.md` §Baseline inventory. `docs/3d-model-review.md` §Baseline inventory.
- [x] Define measurable acceptance: face front/three-quarter/profile at 1.5 m and 1.85 m; hands - [x] Define measurable acceptance: face front/three-quarter/profile at 1.5 m and 1.85 m; hands
open and gripping at 0.8 m; silhouette, eye/mouth readability, ear/nose/jaw planes, finger open and gripping at 0.8 m; silhouette, eye/mouth readability, ear/nose/jaw planes, finger
separation and gear contact. Keep the 16,000/16,000 cap; optional extra bones are allowed, but the required 13 core bones and rest positions must not move. separation and gear contact. Keep the 24,000/24,000 cap; optional extra bones are allowed, but the required 13 core bones and rest positions must not move.
Recorded in `docs/3d-model-review.md` §Overhaul acceptance criteria. Recorded in `docs/3d-model-review.md` §Overhaul acceptance criteria.
- [x] Confirm the identity boundary: `npc_base.blend` is the frozen fallback and must not absorb - [x] Confirm the identity boundary: `npc_base.blend` is the frozen fallback and must not absorb
prototype detail unless a separate contract decision approves it. prototype detail unless a separate contract decision approves it.
@@ -195,7 +195,7 @@ the rest of §C remains the broad review checklist.
### Full-body MPFB prototype ### Full-body MPFB prototype
Goal: assemble the accepted MakeHuman head onto a CC0 MPFB body with project hands, shoes and Goal: assemble the accepted MakeHuman head onto a CC0 MPFB body with project hands, shoes and
14-bone contract rig, inside the 16,000/16,000 character budget. Local development only until the 14-bone contract rig, inside the 24,000/24,000 character budget. Local development only until the
owner reviews the silhouette and a production integration decision is recorded. owner reviews the silhouette and a production integration decision is recorded.
- [x] Build the dev pipeline at `tmp/npc-research/build_mpfb_npc.py`: create a macro-driven MPFB - [x] Build the dev pipeline at `tmp/npc-research/build_mpfb_npc.py`: create a macro-driven MPFB
@@ -203,10 +203,19 @@ owner reviews the silhouette and a production integration decision is recorded.
apply the helper MASK, scale to 1.81 m, retarget the A-pose arms to the contract wrist apply the helper MASK, scale to 1.81 m, retarget the A-pose arms to the contract wrist
(`x=±0.27`, `z=0.78`), cut the neck at 1.505, decimate, bisect the clothing boundaries, paint (`x=±0.27`, `z=0.78`), cut the neck at 1.505, decimate, bisect the clothing boundaries, paint
the five slots, add a short neck flare, project hands, rounded shoe shells, cuffs, the the five slots, add a short neck flare, project hands, rounded shoe shells, cuffs, the
accepted head and the 14-bone armature. Current output: **15,317 tris / 13,690 glTF verts / accepted head and the 14-bone armature. Current output: **19,747 tris / 11,475 glTF verts /
13,702 seam-split verts / 1.810 m / 14 bones / 10 materials**; `inspect` clean, feet grounded. 1.810 m / 14 bones / 10 materials**; `inspect` clean, feet grounded.
The torso is inflated radially by 12% through z 0.72–1.40 (tapering out at the collar) so the The torso is inflated radially by 12% through z 0.72–1.40 (tapering out at the collar) so the
shirt reads as fabric rather than body paint. shirt reads as fabric rather than body paint.
- [x] Raise the character contract to 24,000 triangles / 24,000 glTF vertices so the MPFB body,
the accepted head and a fused neck can coexist with headroom for the 12-variant clothing.
Bumped `CHAR_MAX_TRIS`/`CHAR_MAX_VERTS`, `npc_pipeline.MAX_TRIANGLES`/`MAX_VERTICES`,
the adapt defaults, the tests, `AGENTS.md`, ADR-0016 and the project skills.
- [-] Fix the head/neck/shoulder junction so there are no gaps, square collars or hard rims.
Done: circular boolean bore, smooth lathe, accepted head trimmed at its narrow neck (1.55),
no dark notches or wide brim; prototype at 19,747 tris. Remaining: a thin jaw crease and a
hard shirt-shoulder point in three-quarter view. Next candidate: reshape the shirt shoulder
or a shaped collar that follows the neckline, not a torus or flat disk.
- [x] Replace the spiky decimated MPFB hands with the project palm/finger primitives, straighten the - [x] Replace the spiky decimated MPFB hands with the project palm/finger primitives, straighten the
splayed MPFB feet so the shoes point forward, and use rounded shoe shells + cuffs instead of splayed MPFB feet so the shoes point forward, and use rounded shoe shells + cuffs instead of
flat blocks. Use flat slot colours in the prototype because the one-shot MPFB UV unwrap does flat blocks. Use flat slot colours in the prototype because the one-shot MPFB UV unwrap does
@@ -299,7 +308,7 @@ an active track; ADR-0015 (CC0 / CC-BY with attribution) still governs every int
### Intake and adaptation ### Intake and adaptation
- [x] `adapt` path added to `tools/blender_assets.py` (`adapt --file <in> --out <out> - [x] `adapt` path added to `tools/blender_assets.py` (`adapt --file <in> --out <out>
[--target-tris 16000]`): imports the GLB, applies transforms, grounds the feet, welds seam [--target-tris 24000]`): imports the GLB, applies transforms, grounds the feet, welds seam
splits and decimates to the budget, then exports. The five tint slots + 13-core rig splits and decimates to the budget, then exports. The five tint slots + 13-core rig
(optional extra bones allowed) stay a manual step (recorded in the command doc). (optional extra bones allowed) stay a manual step (recorded in the command doc).
- [x] `audit` stays the final gate; `test_adapt_requires_blender` covers the non-Blender error - [x] `audit` stays the final gate; `test_adapt_requires_blender` covers the non-Blender error
+1 -1
View File
@@ -193,7 +193,7 @@ capture). Pass conditions:
### Contract (both, every prototype) ### Contract (both, every prototype)
- 16,000 triangles / 16,000 glTF vertices per character; gear stays at 1,500. - 24,000 triangles / 24,000 glTF vertices per character; gear stays at 1,500.
- Optional extra bones are allowed; the required 13 core bones and their rest positions must not - Optional extra bones are allowed; the required 13 core bones and their rest positions must not
move; the `npc_base` mesh node name and the five tint slots stay unchanged. move; the `npc_base` mesh node name and the five tint slots stay unchanged.
+1 -1
View File
@@ -4,7 +4,7 @@ This folder holds the project documentation, visual references and generated QA
## Current priorities ## Current priorities
1. [Blender NPC model overhaul](../TODO.md#blender-npc-model-overhaul-top-priority) — part-based head/face/hands pipeline and 16,000-budget contract. 1. [Blender NPC model overhaul](../TODO.md#blender-npc-model-overhaul-top-priority) — part-based head/face/hands pipeline and 24,000-budget contract.
2. [MakeHuman and 3D model/map improvements](../TODO.md#makehuman-character-pipeline) — body intake, asset re-evaluation and local map reconstruction. 2. [MakeHuman and 3D model/map improvements](../TODO.md#makehuman-character-pipeline) — body intake, asset re-evaluation and local map reconstruction.
3. [0.10.0 release candidate](../TODO.md#milestone-0100--3d-model-and-map-improvements-minor) — versioned release hardening after the unversioned development tracks. 3. [0.10.0 release candidate](../TODO.md#milestone-0100--3d-model-and-map-improvements-minor) — versioned release hardening after the unversioned development tracks.
+42
View File
@@ -0,0 +1,42 @@
# ADR-0016: 24,000-triangle character budget
- **Status:** Accepted
- **Date:** 2026-11-05
## Context
ADR-0015 raised the character contract to 16,000 triangles / 16,000 glTF
vertices for the MakeHuman head, hands and crowd variety. The local MPFB
full-body prototype now uses that head, project hands and shoes, and a fused
neck: it measures 19,747 triangles before any clothing variation. The 16,000 cap
leaves no room for the firefighter and hoodie gear, and it forces the body and
neck into a low decimation that produces visible facets.
## Decision
Raise the character contract to **24,000 triangles / 24,000 glTF vertices** per
character (`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS`, `npc_pipeline.MAX_TRIANGLES`/
`MAX_VERTICES`, and the `adapt` defaults). The 13 core bones, five tint slots,
front orientation, grounding and single `npc_base` skinned mesh are unchanged.
Gear stays at 1,500 triangles and props at 4,000.
## Consequences
- The MPFB body, accepted head and neck fit with headroom for the 12 clothing
variants and future jacket/hoodie detail.
- The 24-NPC and Web frame-time budgets must be re-measured before shipping any
variant built at the new cap; the extra geometry is not free.
- The procedural generator and the low-poly `npc_base` fallback remain valid;
they are not required to use the new headroom.
- MPFB stays a local development tool. Production must bake a committed source
GLB; CI and the build must not depend on MPFB.
- Historical reviews and measurements that state 16,000 remain accurate for the
date they were recorded; current contract documents state 24,000.
## Alternatives
- **Keep 16,000 and decimate the MPFB body further:** rejected because the neck
and shoulder already show facets and the clothing variants would exceed the
cap.
- **Separate caps for the MPFB prototype and the shipped characters:** rejected
because it creates a second contract and makes the rollout ambiguous.
+1
View File
@@ -19,3 +19,4 @@ Each ADR records the context, decision, consequences and alternatives for a dura
- [0013-blender-authored-npc-characters](0013-blender-authored-npc-characters.md) - [0013-blender-authored-npc-characters](0013-blender-authored-npc-characters.md)
- [0014-runtime-image-root](0014-runtime-image-root.md) - [0014-runtime-image-root](0014-runtime-image-root.md)
- [0015-npc-model-variety-sourcing](0015-npc-model-variety-sourcing.md) - [0015-npc-model-variety-sourcing](0015-npc-model-variety-sourcing.md)
- [0016-npc-character-budget-24k](0016-npc-character-budget-24k.md)
+3 -3
View File
@@ -139,7 +139,7 @@ The runtime NPC contract is intentionally narrow. It is enforced by
| Core bones | `Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R`, `Spine`, `Neck` at the frozen rest positions. Optional extra bones (fingers, face, toes) are allowed if they do not move the core rest positions or break `NpcAvatar` proxies. | | Core bones | `Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R`, `Spine`, `Neck` at the frozen rest positions. Optional extra bones (fingers, face, toes) are allowed if they do not move the core rest positions or break `NpcAvatar` proxies. |
| Tint slots | `skin`, `cloth`, `pants`, `accessory`, `hair`; fixed detail materials keep their authored colours. | | Tint slots | `skin`, `cloth`, `pants`, `accessory`, `hair`; fixed detail materials keep their authored colours. |
| Orientation | Blender −Y / Godot +Z front, feet grounded near `y = 0`, metre scale. | | Orientation | Blender −Y / Godot +Z front, feet grounded near `y = 0`, metre scale. |
| Budget | 16,000 triangles / 16,000 glTF vertices per character; 1,500 per gear piece; 4,000 per prop. The low-poly `npc_base` remains a fallback, not a second contract. | | Budget | 24,000 triangles / 24,000 glTF vertices per character; 1,500 per gear piece; 4,000 per prop. The low-poly `npc_base` remains a fallback, not a second contract. |
| Textures | 256² albedo + normal + packed ORM per slot; cloth/pants use the 4× `NPC_FABRIC_UV_SCALE` and skip per-character baked AO, while skin/hair/gear keep AO. | | Textures | 256² albedo + normal + packed ORM per slot; cloth/pants use the 4× `NPC_FABRIC_UV_SCALE` and skip per-character baked AO, while skin/hair/gear keep AO. |
| Gear attach | Authored in attach-point space: head `Rig/Head` (0, 1.62, 0), back (0, 1.10, 0), chest (0, 1.30, 0), hand (±0.27, 0.78, 0.02); grip 5 cm below the origin. `GEAR_OFFSETS` and `HAND_ITEM_FORWARD` are runtime fit corrections, not authored-geometry changes. | | Gear attach | Authored in attach-point space: head `Rig/Head` (0, 1.62, 0), back (0, 1.10, 0), chest (0, 1.30, 0), hand (±0.27, 0.78, 0.02); grip 5 cm below the origin. `GEAR_OFFSETS` and `HAND_ITEM_FORWARD` are runtime fit corrections, not authored-geometry changes. |
| Fallback chain | Library variant → `npc_base.glb` → procedural `NpcAvatar` mesh. Committed GLBs and maps must keep CI offline and Blender-free. | | Fallback chain | Library variant → `npc_base.glb` → procedural `NpcAvatar` mesh. Committed GLBs and maps must keep CI offline and Blender-free. |
@@ -217,8 +217,8 @@ GLBs and maps.
| Tool | Role | Committed artifact | Verification gate | | Tool | Role | Committed artifact | Verification gate |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `tools/npc_pipeline.py` | Builds the skinned base avatar, five-slot material maps, palette and AI-fabric preparation (`prepare`/`palette`) | `npc_base.glb`, `assets/textures/npc/*_{albedo,normal,orm}.png`, `palette.json` | `audit_glb` against the 16,000-triangle / 16,000-vertex base contract (13 core bones required; extras allowed) | | `tools/npc_pipeline.py` | Builds the skinned base avatar, five-slot material maps, palette and AI-fabric preparation (`prepare`/`palette`) | `npc_base.glb`, `assets/textures/npc/*_{albedo,normal,orm}.png`, `palette.json` | `audit_glb` against the 24,000-triangle / 24,000-vertex base contract (13 core bones required; extras allowed) |
| `tools/blender_assets.py` | Builds/renders the library: 8 characters, 18 gear pieces, 20 props, plus `manifest.json`; stdlib `audit`/`fit`/`sheet`/`list` | `assets/models/blender/{npc,npc_gear,props}/*.glb` | Character cap 16,000 tris / 16,000 verts, 1,500 per gear, 4,000 per prop; grounding, UV, degenerate-triangle, attach-fit and manifest coverage checks | | `tools/blender_assets.py` | Builds/renders the library: 8 characters, 18 gear pieces, 20 props, plus `manifest.json`; stdlib `audit`/`fit`/`sheet`/`list` | `assets/models/blender/{npc,npc_gear,props}/*.glb` | Character cap 24,000 tris / 24,000 verts, 1,500 per gear, 4,000 per prop; grounding, UV, degenerate-triangle, attach-fit and manifest coverage checks |
| `tools/bake_ao.py` | Bakes per-asset AO maps | `<asset>_ao.png` (256²) | Wired through `Materials.apply_ao()`/`SurfaceMaterials`; cloth/pants opt out (see below) | | `tools/bake_ao.py` | Bakes per-asset AO maps | `<asset>_ao.png` (256²) | Wired through `Materials.apply_ao()`/`SurfaceMaterials`; cloth/pants opt out (see below) |
| `tools/npc_proportions.py` | Measures exported GLB rest pose (height, head, shoulders, hips, torso, limbs, feet) against the stylized guide | report / JSON in `docs/npc-visual-review.md#proportion-audit` | All eight variants must stay inside the guide after a body change | | `tools/npc_proportions.py` | Measures exported GLB rest pose (height, head, shoulders, hips, torso, limbs, feet) against the stylized guide | report / JSON in `docs/npc-visual-review.md#proportion-audit` | All eight variants must stay inside the guide after a body change |
| `tools/npc_baseline.py` | Assembles neutral-baseline sheets and before/after comparisons | `docs/blender_previews/sheet_npc_baseline*.jpg` | Human review against `docs/npc-visual-review.md` | | `tools/npc_baseline.py` | Assembles neutral-baseline sheets and before/after comparisons | `docs/blender_previews/sheet_npc_baseline*.jpg` | Human review against `docs/npc-visual-review.md` |
+2 -2
View File
@@ -200,8 +200,8 @@ flatpak run org.blender.Blender --background \
--python "$PWD/tools/blender_assets.py" -- adapt \ --python "$PWD/tools/blender_assets.py" -- adapt \
--file "$PWD/tmp/npc-research/makehuman_base.glb" \ --file "$PWD/tmp/npc-research/makehuman_base.glb" \
--out "$PWD/tmp/npc-research/makehuman_adapted.glb" \ --out "$PWD/tmp/npc-research/makehuman_adapted.glb" \
--target-tris 16000 --target-tris 24000
# prints: adapted makehuman_adapted.glb: 36972 -> 16000 tris (bones=N, meshes=1) # prints: adapted makehuman_adapted.glb: 36972 -> 24000 tris (bones=N, meshes=1)
``` ```
Run `inspect` on the adapted GLB before and after, and keep the procedural Run `inspect` on the adapted GLB before and after, and keep the procedural
+2 -2
View File
@@ -141,7 +141,7 @@ render about 2.5 min.
All library variants share the [character and gear asset contract](architecture.md#character-and-gear-asset-contract): All library variants share the [character and gear asset contract](architecture.md#character-and-gear-asset-contract):
`npc_base` mesh node, required 13 core bones (optional extras allowed), five `npc_base` mesh node, required 13 core bones (optional extras allowed), five
tint slots, +Z visual front, 16,000/16,000 character budget and the same 256² tint slots, +Z visual front, 24,000/24,000 character budget and the same 256²
slot-map/AO rules. Role selection and look notes: slot-map/AO rules. Role selection and look notes:
| Character | `layer_for()` / look | Suggested roles | Preview gear | | Character | `layer_for()` / look | Suggested roles | Preview gear |
@@ -200,7 +200,7 @@ carry baked AO and the shared neutral material maps described above.
- `python3 tools/blender_assets.py audit`: container, `POSITION`/`NORMAL`/ - `python3 tools/blender_assets.py audit`: container, `POSITION`/`NORMAL`/
`TEXCOORD_0`, UVs in [0, 1], no zero-area triangles (Godot warns about them `TEXCOORD_0`, UVs in [0, 1], no zero-area triangles (Godot warns about them
during LOD generation), budgets (characters 16000/16000, gear 1500, props 4000 during LOD generation), budgets (characters 24000/24000, gear 1500, props 4000
triangles), grounding, and an editable `.blend`. For characters it also triangles), grounding, and an editable `.blend`. For characters it also
checks skinning, bones, slots, the `npc_base` mesh node and the height. checks skinning, bones, slots, the `npc_base` mesh node and the height.
- `tools/tests/test_blender_assets.py`: catalogue and contract consistency with - `tools/tests/test_blender_assets.py`: catalogue and contract consistency with
+9 -7
View File
@@ -8,7 +8,7 @@ remain references or local prototypes unless a later ADR accepts them.
The library needs variety beyond the original eight silhouettes while preserving The library needs variety beyond the original eight silhouettes while preserving
the `npc_base` contract: mesh node, 13 required core bones (optional extras the `npc_base` contract: mesh node, 13 required core bones (optional extras
allowed), five tint slots, −Y/+Z front, grounded feet and the 16,000/16,000 allowed), five tint slots, −Y/+Z front, grounded feet and the 24,000/24,000
character budget. character budget.
| Source | Licence | Result | | Source | Licence | Result |
@@ -55,14 +55,16 @@ The MakeHuman body should reuse the approved local head/hand parts rather than f
a second face implementation. a second face implementation.
The current local MPFB full-body prototype (`tmp/npc-research/build_mpfb_npc.py`) assembles the The current local MPFB full-body prototype (`tmp/npc-research/build_mpfb_npc.py`) assembles the
accepted head on an MPFB 2.0.18 body at **15,317 / 16,000 tris**, 1.810 m, 14 bones and 10 accepted head on an MPFB 2.0.18 body at **19,747 / 24,000 tris**, 1.810 m, 14 bones and 10
materials, with project hands, bevelled shoe shells with inset soles and tongues, pant cuffs, materials, with project hands, bevelled shoe shells with inset soles and tongues, pant cuffs,
cloth sleeve cuffs and flat slot colours. The torso is radially inflated by 12% through z 0.72–1.40 cloth sleeve cuffs and flat slot colours. The torso is radially inflated by 12% through z 0.72–1.40
to loosen the shirt. It is a review candidate, not a shipped asset. The neck is a radial cut to loosen the shirt. It is a review candidate, not a shipped asset. The neck is a boolean-cut
of the decimated column bridged by a four-ring, 20-segment lathe (lower rings painted cloth, circular bore bridged by a smooth lathe; the accepted head is trimmed at the narrow neck (z 1.55)
reading as a crew neck); the ankles are hidden by 0.11 m shoe shells and 0.08 m pant cuffs, and a so its wide flat cut brim is discarded, and the lathe flares to cover the bore, then tucks inside
light smoothing pass softens the closed thigh/hip region. The neck skin test is guarded to z > the jaw. This removed the square collar, dark notches and wide brim; a thin jaw crease and a hard
1.35 so it cannot paint the crotch. Remaining polish: the crotch hollow still reads as a V under shirt-shoulder point in three-quarter view remain. The ankles are hidden by 0.11 m shoe shells and
0.08 m pant cuffs, and a light smoothing pass softens the closed thigh/hip region. The neck skin
test is guarded to z > 1.35 so it cannot paint the crotch. Remaining polish: the crotch hollow still reads as a V under
the shirt hem, a partly loosened shirt and angular thigh facets; a separate loose shirt shell is the shirt hem, a partly loosened shirt and angular thigh facets; a separate loose shirt shell is
the fix. Lowering the hem to z 0.80, tilting it up at the front and a 13% hem flare were rejected the fix. Lowering the hem to z 0.80, tilting it up at the front and a 13% hem flare were rejected
as a unitard, high-cut swimwear and a peplum respectively. A quick open, flared solidified tube as a unitard, high-cut swimwear and a peplum respectively. A quick open, flared solidified tube
+2 -2
View File
@@ -71,8 +71,8 @@ The criteria below are for the stylized target, not photorealism. A change must
pass every applicable item before a library rebuild is accepted. pass every applicable item before a library rebuild is accepted.
1. **Asset contract:** `npc_base` mesh node, the required bones, five tint slots, 1. **Asset contract:** `npc_base` mesh node, the required bones, five tint slots,
front +Z, feet within 3 cm of y=0, 1.6–2.1 m height, at most 16,000 triangles front +Z, feet within 3 cm of y=0, 1.6–2.1 m height, at most 24,000 triangles
and 16,000 glTF vertices per character. The required 13 core bones stay fixed; and 24,000 glTF vertices per character. The required 13 core bones stay fixed;
optional extra bones are allowed. Gear remains at most 1,500 triangles. optional extra bones are allowed. Gear remains at most 1,500 triangles.
2. **Proportion and silhouette:** head, neck, shoulders, torso, hips, limbs and 2. **Proportion and silhouette:** head, neck, shoulders, torso, hips, limbs and
feet read as one body; front/three-quarter/profile silhouettes agree with the feet read as one body; front/three-quarter/profile silhouettes agree with the
+11 -11
View File
@@ -53,14 +53,14 @@ MANIFEST = OUT_DIR / "manifest.json"
SLOTS = ["skin", "cloth", "pants", "accessory", "hair"] SLOTS = ["skin", "cloth", "pants", "accessory", "hair"]
BONES = ["Head", "HipL", "HipR", "KneeL", "KneeR", "ShoulderL", "ShoulderR", BONES = ["Head", "HipL", "HipR", "KneeL", "KneeR", "ShoulderL", "ShoulderR",
"ElbowL", "ElbowR", "HandL", "HandR"] "ElbowL", "ElbowR", "HandL", "HandR"]
# Character cap raised to 16000/16000 on 2026-11-04 for the head/face/hand # Character cap raised to 24000/24000 on 2026-11-05 for the MPFB body, the
# overhaul and possible MakeHuman parts. The required 13 core bones stay, but # accepted MakeHuman head and a seamless neck/shoulder junction. The required
# optional extra bones (fingers, face, toes) are allowed; they must not move the # 13 core bones stay, but optional extra bones (fingers, face, toes) are
# core rest positions. The glTF vertex count is after UV-seam splits; each # allowed; they must not move the core rest positions. The glTF vertex count is
# avatar is still one skinned mesh, and `smoke_npc_budget.gd` must confirm the # after UV-seam splits; each avatar is still one skinned mesh, and the 24-NPC
# 24-NPC frame time before accepting the increase. # smoke test must confirm frame time before shipping.
CHAR_MAX_TRIS = 16000 CHAR_MAX_TRIS = 24000
CHAR_MAX_VERTS = 16000 CHAR_MAX_VERTS = 24000
GEAR_MAX_TRIS = 1500 GEAR_MAX_TRIS = 1500
PROP_MAX_TRIS = 4000 PROP_MAX_TRIS = 4000
@@ -2486,7 +2486,7 @@ def inspect_glb(path, category=None) -> dict:
return result return result
def adapt_glb(path, out_path, target_tris=16000): def adapt_glb(path, out_path, target_tris=24000):
"""Import an external GLB and adapt it toward the library contract. """Import an external GLB and adapt it toward the library contract.
Applies node transforms, grounds the feet (lowest vertex at y = 0), welds Applies node transforms, grounds the feet (lowest vertex at y = 0), welds
@@ -2892,8 +2892,8 @@ def main(argv=None):
ada.add_argument("--file", required=True, ada.add_argument("--file", required=True,
help="external GLB to adapt (absolute path; runs in Blender)") help="external GLB to adapt (absolute path; runs in Blender)")
ada.add_argument("--out", required=True, help="where to write the adapted GLB") ada.add_argument("--out", required=True, help="where to write the adapted GLB")
ada.add_argument("--target-tris", type=int, default=16000, ada.add_argument("--target-tris", type=int, default=24000,
help="decimate target (default 16000)") help="decimate target (default 24000)")
tex = sub.add_parser("texels", help="per-slot texel density of the character GLBs") tex = sub.add_parser("texels", help="per-slot texel density of the character GLBs")
tex.add_argument("--only", default="") tex.add_argument("--only", default="")
tex.add_argument("--json", action="store_true") tex.add_argument("--json", action="store_true")
+3 -3
View File
@@ -23,7 +23,7 @@ Contract (frozen in ADR-0013 and docs/blender-assets.md):
(plus Hips/Spine/Neck as parents) (plus Hips/Spine/Neck as parents)
* material slots: skin, cloth, pants, accessory, hair * material slots: skin, cloth, pants, accessory, hair
* neutral albedo + normal + packed ORM maps, albedo tinted at runtime * neutral albedo + normal + packed ORM maps, albedo tinted at runtime
* <= 16000 triangles / 16000 vertices, UVs in [0,1], feet at y ~= 0 * <= 24000 triangles / 24000 vertices, UVs in [0,1], feet at y ~= 0
* the 13 core bones are required; extra optional bones are allowed * the 13 core bones are required; extra optional bones are allowed
""" """
@@ -45,8 +45,8 @@ BONES = [
"Head", "HipL", "HipR", "KneeL", "KneeR", "Head", "HipL", "HipR", "KneeL", "KneeR",
"ShoulderL", "ShoulderR", "ElbowL", "ElbowR", "HandL", "HandR", "ShoulderL", "ShoulderR", "ElbowL", "ElbowR", "HandL", "HandR",
] ]
MAX_TRIANGLES = 16000 MAX_TRIANGLES = 24000
MAX_VERTICES = 16000 MAX_VERTICES = 24000
TEX_SIZE = 256 TEX_SIZE = 256
AI_DIR = TEX_DIR / "ai" AI_DIR = TEX_DIR / "ai"
# Base material slots that get an AI fabric albedo (`prepare` layers them over # Base material slots that get an AI fabric albedo (`prepare` layers them over
+6 -6
View File
@@ -9,10 +9,10 @@ class CatalogueTests(unittest.TestCase):
def test_contract_matches_npc_pipeline(self): def test_contract_matches_npc_pipeline(self):
self.assertEqual(ba.SLOTS, npc_pipeline.SLOTS) self.assertEqual(ba.SLOTS, npc_pipeline.SLOTS)
self.assertEqual(sorted(ba.BONES), sorted(npc_pipeline.BONES)) self.assertEqual(sorted(ba.BONES), sorted(npc_pipeline.BONES))
# The character cap was raised to 16000/16000 for the head/face/hand # The character cap was raised to 24000/24000 for the MPFB body and the head
# overhaul; the 13 core bones stay required and extra bones are optional. # overhaul; the 13 core bones stay required and extra bones are optional.
self.assertLessEqual(ba.CHAR_MAX_TRIS, 16000) self.assertLessEqual(ba.CHAR_MAX_TRIS, 24000)
self.assertLessEqual(ba.CHAR_MAX_VERTS, 16000) self.assertLessEqual(ba.CHAR_MAX_VERTS, 24000)
def test_gear_uses_known_attach_points(self): def test_gear_uses_known_attach_points(self):
for name, attach in ba.GEAR.items(): for name, attach in ba.GEAR.items():
@@ -197,7 +197,7 @@ class InspectionTests(unittest.TestCase):
def test_character_contract_failures_are_listed(self): def test_character_contract_failures_are_listed(self):
stats = self.stats(mesh_nodes=["Armature"], skinned=False, unskinned=["Armature"], stats = self.stats(mesh_nodes=["Armature"], skinned=False, unskinned=["Armature"],
materials=["cloth"], bones=["Head"], meshes_without_uv=1, materials=["cloth"], bones=["Head"], meshes_without_uv=1,
triangles=17000, split_vertices=17000, triangles=25000, split_vertices=25000,
world_bounds={"min": [-0.3, -0.4, -0.2], "max": [0.3, 1.2, 0.2]}) world_bounds={"min": [-0.3, -0.4, -0.2], "max": [0.3, 1.2, 0.2]})
issues = "\n".join(ba.analyse_inspection(stats)["issues"]) issues = "\n".join(ba.analyse_inspection(stats)["issues"])
self.assertIn("no mesh node named npc_base", issues) self.assertIn("no mesh node named npc_base", issues)
@@ -205,8 +205,8 @@ class InspectionTests(unittest.TestCase):
self.assertIn("missing material slot skin", issues) self.assertIn("missing material slot skin", issues)
self.assertIn("mesh not skinned", issues) self.assertIn("mesh not skinned", issues)
self.assertIn("mesh(es) without UVs", issues) self.assertIn("mesh(es) without UVs", issues)
self.assertIn(f"triangles 17000 > {ba.CHAR_MAX_TRIS}", issues) self.assertIn(f"triangles 25000 > {ba.CHAR_MAX_TRIS}", issues)
self.assertIn(f"glTF vertices 17000 > {ba.CHAR_MAX_VERTS}", issues) self.assertIn(f"glTF vertices 25000 > {ba.CHAR_MAX_VERTS}", issues)
self.assertIn("feet not on the ground", issues) self.assertIn("feet not on the ground", issues)
self.assertIn("unexpected height", issues) self.assertIn("unexpected height", issues)
+2 -2
View File
@@ -13,8 +13,8 @@ class NpcContractTests(unittest.TestCase):
"ShoulderL", "ShoulderR", "ElbowL", "ElbowR", "ShoulderL", "ShoulderR", "ElbowL", "ElbowR",
"HandL", "HandR"]: "HandL", "HandR"]:
self.assertIn(bone, npc_pipeline.BONES) self.assertIn(bone, npc_pipeline.BONES)
self.assertLessEqual(npc_pipeline.MAX_TRIANGLES, 16000) self.assertLessEqual(npc_pipeline.MAX_TRIANGLES, 24000)
self.assertLessEqual(npc_pipeline.MAX_VERTICES, 16000) self.assertLessEqual(npc_pipeline.MAX_VERTICES, 24000)
def test_textures_exist_for_every_slot(self): def test_textures_exist_for_every_slot(self):
for slot in npc_pipeline.SLOTS: for slot in npc_pipeline.SLOTS: