# Asset Contract Reference This is a working summary. The authoritative sources remain `docs/assets/blender-assets.md`, `docs/assets/asset-tooling.md`, ADR-0013, the tests in `src/test/tests/` and the constants in `tools/blender_assets.py` / `tools/npc_pipeline.py`. ## Library inventory - **Character variants (8):** `npc_casual`, `npc_jacket`, `npc_coat`, `npc_vest`, `npc_dress`, `npc_hoodie`, `npc_firefighter`, `npc_guard`. - **Frozen fallback:** `npc_base` / `assets/models/generated/npc_base.glb`. - **Gear:** 18 attach-space pieces covering head, back, chest and hand items. - **Library props:** 20 Blender-authored props plus 6 generated props. - **Venues:** procedural venues in `src/game/world_builder.gd`; Blender venue geometry follows the same budgets and collision/provenance rules. ## Skeleton and geometry The required contract skeleton has 13 core bones: ```text Head, Spine, Neck, HipL, HipR, KneeL, KneeR, ShoulderL, ShoulderR, ElbowL, ElbowR, HandL, HandR ``` Optional extra bones (fingers, face controls, toes) are allowed if they do not move these core rest positions and do not break `NpcAvatar`'s animation proxies. Requirements: - `npc_base` mesh node exists and is skinned under `Rig`. - Limb node names stay stable for `NpcAvatar` and tests. - Front is Blender −Y / Godot +Z. - Feet are grounded at approximately y = 0; no floating origin. - Units are metres; character height is approximately 1.8 m. - No zero-area/duplicate degenerate faces in the exported mesh. - Every character has UVs in [0,1] unless a planned UDIM/atlas change is explicitly accepted. ## Material slots Exactly these five tint slots: ```text skin, cloth, pants, accessory, hair ``` - Neutral albedo is authored in Blender; runtime tinting is authoritative. - Slot names are contract values; do not rename or add a sixth slot casually. - The procedural fallback (`NpcAvatar._build_body`) must keep working when the GLB is missing. ## Budgets | Asset | Limit | | --- | --- | | Character (library and base fallback) | 24,000 triangles / 24,000 glTF vertices | | Gear piece | 1,500 triangles | | Prop | 4,000 triangles | | Library prop/venue total | must stay inside the venue tests and library budget | `CHAR_MAX_TRIS`, `CHAR_MAX_VERTS`, `GEAR_MAX_TRIS`, `PROP_MAX_TRIS` and related constants live in `tools/blender_assets.py`. The budget is per character, not an average. Measure after every mesh change. Do not lower the cap to make a diff pass. ## Gear and hand items - Gear is authored in the attach-point space documented by `GEAR` / `GEAR_ATTACH` / `ATTACH_BLENDER`. - `blender_assets.py fit` measures gear boxes against the character's head band, clothing radius and back clearance. - Runtime offsets are intentional: - `NpcAvatar.GEAR_OFFSETS = {"lanyard": Vector3(0,0,0.04), "firefighter_helmet": Vector3(0,0.03,0)}` - `NpcAvatar.HAND_ITEM_FORWARD = Vector3(0,0,0.04)` - An offset is **not** an authored-asset change; do not bake it into the GLB without a separate contract decision. - Open-palm grip (no finger-curl rig) is the accepted stylized limit. ## Textures and AO - NPC maps are 256² albedo + normal + packed ORM. - `TEX_SIZE = 256` in `tools/npc_pipeline.py`. - Fabric UV scale is `Materials.NPC_FABRIC_UV_SCALE = Vector3(4,4,1)`. - Cloth/pants skip the per-character baked AO; skin/hair/gear keep it. This avoids the large blotchy magnification that appeared at 256². - Gear/props may have their own AO maps; `tools/bake_ao.py` writes `_ao.png` next to the GLB and Godot wires it into the AO channel. - Never overwrite an existing committed image; use a new `-vN` candidate and promote only after review. - Run `python3 tools/asset_pipeline.py audit ` and the relevant `npc_pipeline.py audit` before committing map changes. ## File map | Path | Role | | --- | --- | | `tools/blender_assets.py` | Catalogue, builders, audit/fit/inspect/texels/sheet. | | `tools/npc_pipeline.py` | `npc_base` builder, texture preparation and audit. | | `tools/bake_ao.py` | Blender AO bake into `_ao.png`. | | `tools/npc_proportions.py` | Exported-GLB proportion audit (no Blender). | | `tools/npc_baseline.py` | Contact sheets/comparisons (no Blender). | | `assets/models/blender/manifest.json` | Generated asset manifest. | | `assets/models/blender/**/*.glb` | Committed library assets. | | `assets/models/generated/npc_base.glb` | Frozen NPC fallback. | | `assets/textures/npc/*_{albedo,normal,orm}.png` | Committed NPC maps. | | `src/test/capture_npc_baseline.gd` | 48-frame neutral baseline. | | `src/test/capture_npc_review.gd` | 96-frame final-context review. | ## Provenance and source rules - Procedural Blender assets are project-owned. - External/CC assets need ADR-0015 clearance: CC0 preferred, CC-BY only with attribution in `CREDITS.md`; no service assets with redistribution limits. - Editable `.blend` files under `assets/models/blender_src/` are gitignored; only exported `.glb`, AO maps, manifests and committed preview sheets ship. - AI-generated source images are development inputs, not shipped assets. Keep raw generations under gitignored paths until picked, checked and credited. - Do not copy a downloaded/Street View/AI reference into a committed asset without the licence and provenance review. ## Verification invariants The asset pass is not complete unless: 1. `python3 tools/blender_assets.py audit` exits 0. 2. `python3 tools/blender_assets.py fit` exits 0 and the report is reviewed. 3. `python3 tools/npc_proportions.py report` passes the stylized guide. 4. `python3 tools/npc_pipeline.py audit` passes when the base/textures changed. 5. The 48-frame baseline and 96-frame final-context review are captured, with no blank frames. 6. The 24-NPC budget smoke is inside its frame budget. 7. The full Godot suite and Python tooling tests pass. 8. No runtime contract, renderer or dependency changed except the rebuilt assets and their generated metadata.