diff --git a/.claude/skills/glb-gen/SKILL.md b/.claude/skills/glb-gen/SKILL.md index 5d6c46930..27a1c4e75 100644 --- a/.claude/skills/glb-gen/SKILL.md +++ b/.claude/skills/glb-gen/SKILL.md @@ -104,6 +104,18 @@ Promotion = repo-canonical (D-241): an asset entering the manifest AND in `spikes/`. Naming, mask-sidecar, and texture conventions are in `docs/assets/visual/conventions.md`. +**Promotion commits three files per tintable model, two for non-tintable.** +Copying the `.glb` (+ `_mask.png`, if the asset is tintable) into +`client/assets/models/{furniture,props}/` is not the last step — run a +headless import (`godot4 --headless --path client --import`) after copying, +which generates a third required file, `_Image_0.png`, next to the +`.glb`. Godot's scene importer externalizes the GLB's embedded texture to +this loose file and the compiled scene references it by UID even with +`gltf/embedded_image_handling=3` set (confirmed Godot 4.6, T-1204). It must be +committed with the rest — see `conventions.md` §2 for the full failure mode +(deleting it breaks `ResourceLoader` loading with a binary UID dependency +error that no text search of the compiled `.scn` will surface). + ## Post-process in Blender Normalize scale, center, generate recolor mask, adjust materials: diff --git a/client/assets/models/furniture/chair_modernist.glb b/client/assets/models/furniture/chair_modernist.glb new file mode 100644 index 000000000..13a93f5b8 Binary files /dev/null and b/client/assets/models/furniture/chair_modernist.glb differ diff --git a/client/assets/models/furniture/chair_modernist_Image_0.png b/client/assets/models/furniture/chair_modernist_Image_0.png new file mode 100644 index 000000000..65a631c62 Binary files /dev/null and b/client/assets/models/furniture/chair_modernist_Image_0.png differ diff --git a/client/assets/models/furniture/chair_modernist_mask.png b/client/assets/models/furniture/chair_modernist_mask.png new file mode 100644 index 000000000..e83c64d66 Binary files /dev/null and b/client/assets/models/furniture/chair_modernist_mask.png differ diff --git a/client/assets/models/furniture/desk_scifi.glb b/client/assets/models/furniture/desk_scifi.glb new file mode 100644 index 000000000..b43f45724 Binary files /dev/null and b/client/assets/models/furniture/desk_scifi.glb differ diff --git a/client/assets/models/furniture/desk_scifi_Image_0.png b/client/assets/models/furniture/desk_scifi_Image_0.png new file mode 100644 index 000000000..67fcde0d2 Binary files /dev/null and b/client/assets/models/furniture/desk_scifi_Image_0.png differ diff --git a/client/assets/models/furniture/desk_scifi_mask.png b/client/assets/models/furniture/desk_scifi_mask.png new file mode 100644 index 000000000..02cc956f5 Binary files /dev/null and b/client/assets/models/furniture/desk_scifi_mask.png differ diff --git a/client/assets/models/furniture/table_baroque.glb b/client/assets/models/furniture/table_baroque.glb new file mode 100644 index 000000000..eb93d966e Binary files /dev/null and b/client/assets/models/furniture/table_baroque.glb differ diff --git a/client/assets/models/furniture/table_baroque_Image_0.png b/client/assets/models/furniture/table_baroque_Image_0.png new file mode 100644 index 000000000..58d589692 Binary files /dev/null and b/client/assets/models/furniture/table_baroque_Image_0.png differ diff --git a/client/assets/models/furniture/table_baroque_mask.png b/client/assets/models/furniture/table_baroque_mask.png new file mode 100644 index 000000000..953a16546 Binary files /dev/null and b/client/assets/models/furniture/table_baroque_mask.png differ diff --git a/client/assets/models/manifest.json b/client/assets/models/manifest.json index c88b88f5a..bb98c3f0e 100644 --- a/client/assets/models/manifest.json +++ b/client/assets/models/manifest.json @@ -19,6 +19,39 @@ "mask": "furniture/table_baroque_mask.png" } }, - "furniture": {}, - "props": {} + "furniture": { + "table_baroque": { + "category": "furniture", + "source_model": "furniture/table_baroque.glb", + "subdirectory": "furniture", + "status": "planned", + "footprint_tiles": [1, 1], + "mask": "furniture/table_baroque_mask.png" + }, + "chair_modernist": { + "category": "furniture", + "source_model": "furniture/chair_modernist.glb", + "subdirectory": "furniture", + "status": "planned", + "footprint_tiles": [1, 1], + "mask": "furniture/chair_modernist_mask.png" + }, + "desk_scifi": { + "category": "furniture", + "source_model": "furniture/desk_scifi.glb", + "subdirectory": "furniture", + "status": "planned", + "footprint_tiles": [1, 1], + "mask": "furniture/desk_scifi_mask.png" + } + }, + "props": { + "lion_statue": { + "category": "props", + "source_model": "props/lion_statue.glb", + "subdirectory": "props", + "status": "planned", + "footprint_tiles": [1, 1] + } + } } diff --git a/client/assets/models/props/lion_statue.glb b/client/assets/models/props/lion_statue.glb new file mode 100644 index 000000000..ce4530802 Binary files /dev/null and b/client/assets/models/props/lion_statue.glb differ diff --git a/client/assets/models/props/lion_statue_Image_0.png b/client/assets/models/props/lion_statue_Image_0.png new file mode 100644 index 000000000..a0c52f9e3 Binary files /dev/null and b/client/assets/models/props/lion_statue_Image_0.png differ diff --git a/docs/assets/visual/README.md b/docs/assets/visual/README.md index 9fd1d3542..000e86972 100644 --- a/docs/assets/visual/README.md +++ b/docs/assets/visual/README.md @@ -7,8 +7,8 @@ status: active # Visual Asset Pipeline -Status: **Active** — catalog structure instantiated (T-1050); awaiting first -model/texture/artwork/effects production sprint. +Status: **Active** — catalog structure instantiated (T-1050); first models +promoted (T-1204); textures/artwork/effects production still pending. Per **[D-244](../../../governance/decisions/architecture.md#d-244)**: the in-world view renders **3D objects directly**. The only flattened 2D content is **textures** (PBR maps on 3D surfaces) and **flat 2D artwork** (paintings, flags, billboards, signage) applied as textures/decals on flat surfaces *within* the 3D world. **There is no per-object sprite layer** — game objects are not pre-rendered to sprites. @@ -16,7 +16,7 @@ Per **[D-244](../../../governance/decisions/architecture.md#d-244)**: the in-wor | Category | File | Count | Description | |----------|------|-------|-------------| -| Models | [models.md](models.md) | 0 | 3D objects/props (`.glb`) via Trellis (`/glb-gen`), promoted per [D-241] | +| Models | [models.md](models.md) | 4 | 3D objects/props (`.glb`) via Trellis (`/glb-gen`), promoted per [D-241] | | Characters | — | — | 3D meshes composited at runtime via `CharacterVisualDescriptor` — see [character-asset-organization.md](../../architecture/character-asset-organization.md) | | Textures | [textures.md](textures.md) | 0 | PBR surface maps (BaseColor / Normal / Roughness) for 3D models | | Artwork | [artwork.md](artwork.md) | 0 | Flat 2D art — paintings, flags, billboards, signage, screen content — applied as textures/decals (via the `/sprite-gen` 2D-artwork generator) | diff --git a/docs/assets/visual/conventions.md b/docs/assets/visual/conventions.md index 0e30b17ed..ec697a7bb 100644 --- a/docs/assets/visual/conventions.md +++ b/docs/assets/visual/conventions.md @@ -65,6 +65,27 @@ 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) diff --git a/docs/assets/visual/models.md b/docs/assets/visual/models.md index 03ef17a5e..bf8a2fda3 100644 --- a/docs/assets/visual/models.md +++ b/docs/assets/visual/models.md @@ -33,12 +33,25 @@ Naming, mask-sidecar, and multi-tile footprint conventions: | ID | Filename | Status | Category | Footprint | Notes | Sprint/Ticket | |----|----------|--------|----------|-----------|-------|---------------| +| table_baroque | `furniture/table_baroque.glb` | planned | furniture | [1, 1] | Promoted from spike (`baroque_table.glb`, renamed category-first); mask sidecar present; spike README flags the mask as "too aggressive — tints the whole model bright white", not yet re-tuned | T-1204 | +| chair_modernist | `furniture/chair_modernist.glb` | planned | furniture | [1, 1] | Promoted from spike (`modernist_chair.glb`, renamed category-first); mask sidecar present | T-1204 | +| desk_scifi | `furniture/desk_scifi.glb` | planned | furniture | [1, 1] | Promoted from spike (`scifi_desk.glb`, renamed category-first); mask sidecar present | T-1204 | +| lion_statue | `props/lion_statue.glb` | planned | props | [1, 1] | Promoted from spike as-is (no rename); "fixed" mode, non-tintable — no mask sidecar per brief §3 | T-1204 | -No models have entered production yet — this table is intentionally empty at -initial creation (T-1050). Rows are added as assets are promoted into -`client/assets/models/manifest.json`; keep this table's `Status` column in -sync with the manifest's `status` field (shared vocab: planned / -in-progress / placeholder / final, `docs/assets/README.md`). +These four rows are the first promoted assets (T-1204, `furniture-props.md` +brief's first acceptance) — the 3 in-scope spike furniture GLBs plus +`lion_statue`, already Blender-postprocessed (`mat_primary`, +roughness=1.0/specular=0.0 confirmed via Blender headless inspection) in the +spike, so no re-run of `postprocess_glb.py` was needed at promotion time. +`vw_beetle.glb` stays excluded (Q-067 car deferral). Footprint is `[1, 1]` +for all four — the spike GLBs are Blender-normalized to `target-width=1.0` +(max dimension ≤ 1m on every axis after normalization), so none of the actual +promoted meshes has real-world geometry spanning more than one tile; the +brief's `[2, 1]` table example is illustrative of the convention, not a +derived value for these specific assets. Further rows are added as new +assets are promoted/generated; keep this table's `Status` column in sync with +the manifest's `status` field (shared vocab: planned / in-progress / +placeholder / final, `docs/assets/README.md`). ## Cross-References