--- title: "Visual Asset Conventions" description: "Model naming, mask sidecars, D-235-keyed texture naming, and multi-tile footprint metadata for the 3D asset pipeline" type: design status: active --- # Visual Asset Conventions Single home for 3D-model and texture naming conventions (D-244 — the in-world view renders 3D objects directly; there is no per-object sprite layer). This file is the cross-link target from `.claude/skills/glb-gen/SKILL.md` and the catalog files in `docs/assets/visual/`. --- ## 1. Model naming Format: `_.glb` Examples: `table_baroque.glb`, `chair_modernist.glb`, `door_sliding.glb`. **Ruling (delegated to Araminta, art seat, batch-5 activation — T-1052): category-first, not descriptor-first.** Rationale: category-first matches the shipped, in-active-use character-asset clothing convention (`_` — `jacket_modern.glb`, `boots_modern.glb`, `pants_formal.glb`, all live under `client/assets/characters/clothing/`) and gives every future asset browser/manifest a stable alphabetical grouping by type (all tables sort together, all chairs sort together). The 3dpipeline spike's descriptor-first naming (`baroque_table.glb`, `modernist_chair.glb`, `spikes/3dpipeline/README.md`) was pre-convention scratch work, not a precedent to preserve — D-241 already treats spike naming as disposable at promotion time. **Surfaced to Jeroen for cheap veto** (lead delegation note, T-1050). `category` is the subdirectory-agnostic object type (`table`, `chair`, `door`, `lamp`, `statue`); `descriptor` distinguishes style/variant (`baroque`, `modernist`, `sliding`). Category is not required to match the `furniture`/`props` subdirectory name — a `statue` and a `table` can both be `furniture` or both be `props` depending on where they land; the manifest's `category` field, not the filename, is authoritative for that split. --- ## 2. Mask sidecars Convention: `_mask.png`, single-channel (R-only greyscale), sitting next to the GLB — per the character-asset convention (Section 5, `docs/architecture/character-asset-organization.md`). White = tintable region, black = preserve original texture (`hint_default_black`: no mask loaded = no tinting; assets without a mask render with their original Trellis/authored texture). Example: `table_baroque.glb` + `table_baroque_mask.png` (this is the existing Trellis convention already proven in the 3dpipeline spike, just re-ordered to category-first per Section 1's ruling). **Multi-region tinting.** If a single-channel mask is insufficient (an item needs more than one independently-tintable region), follow the RGBA channel-routed convention from `character-asset-organization.md` Section 11 (T-1089) — R/G/B/A → four independent tints, cross-fading correctly under bilinear filtering — rather than reinventing a greyscale-band scheme (that approach was tried for clothing and rejected; see that doc's Section 5 correction). This is the same mechanism, not a parallel one: environment props reuse the clothing pipeline's multi-region shader family rather than inventing a props-specific one. **Extracted-texture sidecar (`_Image_0.png`) — generated, must be committed.** On first Godot import of a promoted `.glb`, the scene importer externalizes the model's embedded texture to a loose file next to it, `_Image_0.png` (e.g. `table_baroque_Image_0.png`), and the compiled `.godot/imported/*.scn` references that file by **UID**, as an external dependency — this happens even with `gltf/embedded_image_handling=3` (embed uncompressed) set in `project.godot`, confirmed on Godot 4.6 (T-1204 promotion). This file is **machine-generated, never hand-edited** — if it is lost or deleted, a clean re-import (delete the stale `.glb.import` + `.godot/imported/*.scn` cache entry, then re-run `godot4 --headless --import`) regenerates it deterministically. But it **must be committed alongside the `.glb`** it belongs to: deleting it after import breaks loading with `ERROR: Can't load dependency` / `Resource file not found` at `ResourceLoader` time, and critically, **a text grep of the compiled `.scn` will not reveal this dependency** — the reference is a binary-encoded UID (`uid://...`), not a literal path string, so searching the `.scn` for the filename finds nothing. Losing this file costs a full debug cycle (re-copy the source `.glb`, clear the stale `.import`/`.scn` cache, re-import, re-verify) to rediscover what the UID reference actually pointed to — treat it as a required sidecar from the moment of promotion, not disposable import cache. --- ## 3. Texture naming (D-235 tokens) PBR surface maps for building-exterior and environment surfaces are named by the ratified **ObjectTag vocabulary** (D-235 amendment, T-995, `wiki/economics/object_tag_vocabulary.toml`), **not** the retired `renderer/README.md` `__.png` scheme. That scheme is direction-indexed for the retired 4-direction sprite render (`renderer/README.md`'s now-legacy object-sprite mode) and does not apply to PBR texture maps at all — there is no "direction" for a texture, only **map type** (basecolor / normal / roughness). Format: `_.png` Map types: `basecolor`, `normal`, `roughness`. (`metallic` and `ao` may be added if a material needs them; keep the same `_` shape.) Examples, drawn from the D-235 ratified tags: - `concrete_wall_basecolor.png`, `concrete_wall_normal.png`, `concrete_wall_roughness.png` - `pitched_roof_normal.png` - `stone_wall_basecolor.png` - `generic_wall_basecolor.png` (fallback-terminal placeholder — D-235's fallback hierarchy: every specific tag degrades to a `generic_*` parent until the specific texture ships, then upgrades in place) The full ratified axis lists (wall/roof/facade/street + the four `generic_*` fallback terminals) live in `wiki/economics/object_tag_vocabulary.toml` — this doc does not duplicate that list; it names the file-naming convention that keys off it. --- ## 4. Multi-tile footprint metadata A table or couch spans multiple sim tiles regardless of whether it's rendered as a sprite or a 3D model — this requirement survived the D-244 pivot unchanged. It is **not** expressed as a filename convention (a sprite-era assumption); it is a `manifest.json` field: ```json "footprint_tiles": [2, 1] ``` `[width, height]` in whole tiles (1 tile = 1m, D-243's voxel unit). Single-tile props omit the field or set it to `[1, 1]`. For Blender-normalized assets, this value is **authored at promotion time, not derived from mesh geometry** — normalization erases real-world scale (araminta ruling, PR #214; the full rule with the single-tile-class default lives in `docs/assets/briefs/furniture-props.md` §3). See `client/assets/models/manifest.json`'s `_fields` block for the authoritative field definition alongside the rest of the per-asset schema. --- ## 5. Deferred — door state handling (NOT authored here) **Do not port a state-frame suffix convention** (e.g. `door_open.glb` / `door_closed.glb`). An earlier draft of this ticket (2026-06-16, since superseded) proposed exactly that — baked open/closed GLB variants — which is sprite-era thinking (baked state = baked frame) carried over from the retired object-sprite model. For a 3D object, the door's open/closed/locked/sealed state (T-979/T-998's `DoorSpec`: `Open` / `Closed` / `TemporalWindow` / `Locked` / `Sealed`) is a **runtime transform/animation on one door `.glb`**, not separate baked models. The actual mechanism — `AnimationPlayer` swing vs. procedural hinge rotation vs. a door-model convention with a named "open" bone — is a **Phase-5 rendering decision** (player control + in-world rendering, per the cascade), not a Phase-4 asset-catalog concern. This doc records only the negative constraint above (no state-frame suffix files). The positive mechanism is deferred to whichever Phase-5 ticket handles door rendering — do not invent a runtime convention here. --- ## 6. Deferred — Godot TileSet resource organization (OUT OF SCOPE) A `/.tres` convention is **out of scope for this doc and this phase**, removed from the ticket that spawned this file. Reason: `client/scripts/rendering/tile_renderer.gd`'s `TileSet` is an explicitly-labeled placeholder ("programmatic TileSet with placeholder colored rectangles," D-014), and the cascade rule (`CLAUDE.md`) states the current in-world rendering code is "left as-is until Phase 5 — do not build on it or rip it out before then." Authoring a `.tres` resource-organization convention now would mean building on Phase-5-owned code from Phase 4. This section is intentionally not written; a future Phase-5 ticket owns it. --- ## Cross-References - [D-244](../../../governance/decisions/architecture.md#d-244-asset-rendering--3d-objects-in-world-2d-limited-to-textures--flat-artwork) — 3D objects in-world; 2D limited to textures + flat artwork. - [D-241](../../../governance/decisions/process.md#d-241-spike-binary-artifacts-are-local-only--gitignored-never-tracked) — spike binaries local-only; promotion = repo-canonical. - [D-235](../../../governance/decisions/architecture.md#d-235-building-exterior-visual-grammar-and-material-vocabulary) — ObjectTag vocabulary, ratified in `wiki/economics/object_tag_vocabulary.toml` (T-995). - [D-243](../../../governance/decisions/architecture.md#d-243) — spatial scale ladder (1m voxel/tile unit for `footprint_tiles`). - `docs/architecture/character-asset-organization.md` — Sections 3, 5, 11 (mask sidecar precedent, multi-region RGBA convention). - `client/assets/models/manifest.json` — the schema this doc's naming/metadata conventions feed. - `.claude/skills/glb-gen/SKILL.md` — the production pipeline that writes to `client/assets/models/`.