Files
sow-module-tilesets/docs/superpowers/plans/2026-07-05-wgt01-texture-kit.md
2026-07-05 01:51:50 +02:00

286 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# wgt01 Texture Kit Implementation Plan
> **STATUS: EXECUTED & MERGED (2026-07-05).** Historical record. As-shipped
> deviations are in `../specs/2026-07-05-wgt01-texture-kit-design.md` ("As
> shipped"); remaining slots in `../specs/2026-07-05-wgt01-texture-kit-gaps.md`.
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build the 8-slot dark-gothic PBR texture kit for the wgt01 tileset (`wgt01/textures/`), regraded to the concept-art palette, with provenance notes and materials wired into `wgt01.blend`.
**Architecture:** Copy the best `tcn01` PBR maps out of `/home/vicky/Blender/PBR_Procampur` into a `wgt01_` namespace, run every diffuse through one shared ImageMagick regrade script, pull CC0 sets from ambientCG only for the plaza (and optionally sidewalk) slots, author the water map, then load everything into `wgt01.blend` as `MAT_wgt01_*` materials via headless Blender.
**Tech Stack:** bash + ImageMagick (`magick`), `curl` (ambientCG API), Blender 4.0.2 headless (via `nix develop ../twu01``$BLENDER_BIN`), NWN `.mtr` text files.
## Global Constraints
- All shipped names ≤ 16 chars, lowercase, `wgt01_` prefix (spec: resref limit).
- Kit lives in `wgt01/textures/`; never modify originals under `/home/vicky/Blender`.
- Every diffuse goes through `wgt01/regrade.sh` — no hand-edited one-off images.
- Height/normal maps copy through untouched.
- Every shipped file gets a `SOURCES.md` line (origin path or CC0 URL + license).
- TGA only in the kit (convert any DDS); NWN also reads TGA directly.
- Working branch: `wgt01-tileset-kit`. NixOS: no ad-hoc global installs; use `nix shell nixpkgs#imagemagick -c` / the twu01 dev shell.
- This is an asset pipeline, not TDD code: each task's "test" is a runnable visual or scripted check, and the plan's self-checks (`assert`-style bash) stand in for unit tests.
---
### Task 1: Kit scaffold + regrade script
**Files:**
- Create: `wgt01/textures/` (dir), `wgt01/regrade.sh`, `wgt01/textures/SOURCES.md` (header only)
**Interfaces:**
- Produces: `regrade.sh <in> <out.tga>` — darkens ~35%, desaturates, cool-shifts any diffuse. All later tasks call it. Env overrides: `MOD` (modulate triple, default `65,55,100`), `TINT` (default `#232830`), `TINTPCT` (default `12`).
- [ ] **Step 1: Write the script**
```bash
mkdir -p wgt01/textures
cat > wgt01/regrade.sh <<'EOF'
#!/usr/bin/env bash
# Uniform Westgate regrade: darken, desaturate, cool tint.
# Usage: regrade.sh <in> <out.tga> (env: MOD, TINT, TINTPCT to override per-slot)
set -euo pipefail
MOD="${MOD:-65,55,100}"; TINT="${TINT:-#232830}"; TINTPCT="${TINTPCT:-12}"
magick "$1" -modulate "$MOD" -fill "$TINT" -colorize "${TINTPCT}%" -level '0%,95%' "$2"
EOF
chmod +x wgt01/regrade.sh
printf '# wgt01/textures — sources & licenses\n\n| file | origin | license |\n|---|---|---|\n' > wgt01/textures/SOURCES.md
```
- [ ] **Step 2: Self-check — regraded output is darker and less saturated**
```bash
nix shell nixpkgs#imagemagick -c bash -c '
./wgt01/regrade.sh /home/vicky/Blender/PBR_Procampur/tcn01_cobb02_d.tga /tmp/rg_test.tga
b_in=$(magick /home/vicky/Blender/PBR_Procampur/tcn01_cobb02_d.tga -colorspace Gray -format "%[fx:mean]" info:)
b_out=$(magick /tmp/rg_test.tga -colorspace Gray -format "%[fx:mean]" info:)
awk -v a="$b_in" -v b="$b_out" "BEGIN{ if (b < a*0.75) print \"OK darker: \" a \" -> \" b; else { print \"FAIL\"; exit 1 } }"'
```
Expected: `OK darker: <in> -> <out>` with out ≲ 0.75×in.
- [ ] **Step 3: Eyeball it**`magick montage` the original vs regraded cobble side by side, Read the PNG, compare against the concept sheet's wet-cobblestone swatch. Tune `MOD`/`TINT` defaults in the script if it reads too green or too bright; re-run Step 2.
- [ ] **Step 4: Commit**
```bash
git add wgt01/regrade.sh wgt01/textures/SOURCES.md
git commit -m "wgt01: texture kit scaffold + shared regrade script"
```
---
### Task 2: Procampur slot extraction (slots 1,2,4,5,6 + grime decals)
**Files:**
- Create: `wgt01/textures/wgt01_{cobble,worn,sidewlk,wall,bridge}_{d,n,h}.tga`, matching `wgt01_<slot>.mtr`, `wgt01_grime1_d.tga`, `wgt01_wtrline_d.tga`
- Modify: `wgt01/textures/SOURCES.md`
**Interfaces:**
- Consumes: `wgt01/regrade.sh` (Task 1).
- Produces: slot basenames `wgt01_cobble`, `wgt01_worn`, `wgt01_sidewlk`, `wgt01_wall`, `wgt01_bridge` (each `_d/_n/_h` + `.mtr`) — Tasks 56 reference exactly these.
- [ ] **Step 1: Extract, regrade diffuses, copy n/h, emit .mtr**
```bash
cd wgt01 && P=/home/vicky/Blender/PBR_Procampur
declare -A SRC=( [cobble]=tcn01_cobb02 [worn]=tcn01_cobb05 [sidewlk]=tcn01_stone04 [wall]=tcn01_brick03 [bridge]=tcn01_stone04 )
nix shell nixpkgs#imagemagick -c bash -c '
set -euo pipefail; P=/home/vicky/Blender/PBR_Procampur
for slot in cobble worn sidewlk wall bridge; do
case $slot in cobble) s=tcn01_cobb02;; worn) s=tcn01_cobb05;; sidewlk) s=tcn01_stone04;; wall) s=tcn01_brick03;; bridge) s=tcn01_stone04;; esac
./regrade.sh "$P/${s}_d.tga" "textures/wgt01_${slot}_d.tga"
cp "$P/${s}_n.tga" "textures/wgt01_${slot}_n.tga"
cp "$P/${s}_h.tga" "textures/wgt01_${slot}_h.tga"
printf "renderhint NormalAndSpecMapped\r\n\r\n// Textures\r\ntexture0 wgt01_%s_d\r\ntexture1 wgt01_%s_n\r\ntexture2 black\r\ntexture3 null\r\ntexture4 wgt01_%s_h\r\ntexture5 null\r\n" "$slot" "$slot" "$slot" > "textures/wgt01_${slot}.mtr"
printf "| wgt01_%s_* | PBR_Procampur/%s_* | see PBR_Procampur readme |\n" "$slot" "$s" >> textures/SOURCES.md
done
# grime decals: pass through (alpha overlays, already neutral)
magick "$P/tcn01_splotch2_d.tga" textures/wgt01_grime1_d.tga
magick "$P/tcn01_watrstn02.tga" textures/wgt01_wtrline_d.tga
printf "| wgt01_grime1_d | PBR_Procampur/tcn01_splotch2_d | see readme |\n| wgt01_wtrline_d | PBR_Procampur/tcn01_watrstn02 | see readme |\n" >> textures/SOURCES.md'
```
Note: `bridge` starts as a duplicate of `stone04` with a darker override (`MOD=58,50,100 ./regrade.sh …`) so it separates visually from sidewalk; if it still reads identical at Task 6, the internet arch-stone fallback from Task 3's API search replaces it.
- [ ] **Step 2: Check — every slot complete, names ≤16**
```bash
cd wgt01/textures && for f in wgt01_{cobble,worn,sidewlk,wall,bridge}{_d.tga,_n.tga,_h.tga,.mtr}; do
[ -f "$f" ] || { echo "MISSING $f"; exit 1; }
b="${f%.*}"; [ ${#b} -le 16 ] || { echo "TOO LONG $b"; exit 1; }
done && echo OK
```
Expected: `OK`.
- [ ] **Step 3: Contact sheet** — montage all `wgt01_*_d.tga`, Read it, compare with concept-art material swatches. Adjust per-slot `MOD`/`TINT` env overrides and re-run Step 1 for any slot that's off.
- [ ] **Step 4: Commit**
```bash
git add wgt01/textures && git commit -m "wgt01: Procampur-derived slots (cobble/worn/sidewalk/wall/bridge) + grime decals"
```
---
### Task 3: Internet pull — plaza slabs (slot 3)
**Files:**
- Create: `wgt01/textures/wgt01_plaza_{d,n,h}.tga`, `wgt01/textures/wgt01_plaza.mtr`
- Modify: `wgt01/textures/SOURCES.md`
**Interfaces:**
- Consumes: `regrade.sh`. Produces: basename `wgt01_plaza` for Tasks 56.
- [ ] **Step 1: Find a CC0 large-slab flagstone on ambientCG**
```bash
curl -s 'https://ambientcg.com/api/v2/full_json?q=paving+stones&type=Material&limit=50' \
| nix shell nixpkgs#jq -c jq -r '.foundAssets[] | [.assetId, .displayName] | @tsv' | head -30
```
Pick a large/irregular slab asset (PavingStones-family; prefer big flat slates over small cobbles — this is the plaza). Download the 1K JPG zip:
```bash
cd /tmp/claude-*/*/scratchpad 2>/dev/null || cd "$SCRATCHPAD"
curl -Lo plaza.zip 'https://ambientcg.com/get?file=<AssetID>_1K-JPG.zip' && nix shell nixpkgs#unzip -c unzip -o plaza.zip -d plaza/
ls plaza/ # expect <AssetID>_1K_Color.jpg, _NormalGL.jpg, _Displacement.jpg
```
- [ ] **Step 2: Convert + regrade into the kit**
```bash
cd <repo>/wgt01 && S=<scratchpad>/plaza A=<AssetID>
nix shell nixpkgs#imagemagick -c bash -c '
./regrade.sh "'$S'/'$A'_1K_Color.jpg" textures/wgt01_plaza_d.tga
magick "'$S'/'$A'_1K_NormalGL.jpg" textures/wgt01_plaza_n.tga
magick "'$S'/'$A'_1K_Displacement.jpg" -colorspace Gray textures/wgt01_plaza_h.tga'
printf "renderhint NormalAndSpecMapped\r\n\r\n// Textures\r\ntexture0 wgt01_plaza_d\r\ntexture1 wgt01_plaza_n\r\ntexture2 black\r\ntexture3 null\r\ntexture4 wgt01_plaza_h\r\ntexture5 null\r\n" > textures/wgt01_plaza.mtr
printf "| wgt01_plaza_* | ambientcg.com asset %s (1K JPG) | CC0 |\n" "$A" >> textures/SOURCES.md
```
- [ ] **Step 3: Check** — same completeness/length loop as Task 2 Step 2 for `wgt01_plaza`; montage plaza next to cobble/sidewalk and Read: plaza must read as *larger, flatter slabs* than both, in the same palette.
- [ ] **Step 4: Commit**`git add wgt01/textures && git commit -m "wgt01: plaza slab slot from ambientCG (CC0)"`
---
### Task 4: Authored slots — canal water (8) + gutter/drain (7)
**Files:**
- Create: `wgt01/textures/wgt01_water_d.tga`, `wgt01_water.mtr`, `wgt01_gutter_{d,n,h}.tga`, `wgt01_gutter.mtr`
- Modify: `wgt01/textures/SOURCES.md`
**Interfaces:**
- Produces: basenames `wgt01_water`, `wgt01_gutter` for Tasks 56.
- [ ] **Step 1: Water — near-black, subtle ripple, from procedural noise (no source needed)**
```bash
cd wgt01 && nix shell nixpkgs#imagemagick -c bash -c '
magick -size 512x512 plasma:gray20-gray35 -blur 0x6 -modulate 40,20,100 \
-fill "#0a0e14" -colorize 70% -level "0%,60%" textures/wgt01_water_d.tga'
printf "renderhint NormalAndSpecMapped\r\n\r\n// Textures\r\ntexture0 wgt01_water_d\r\ntexture1 null\r\ntexture2 black\r\ntexture3 null\r\ntexture4 null\r\ntexture5 null\r\n" > textures/wgt01_water.mtr
printf "| wgt01_water_d | authored (ImageMagick plasma) | ours |\n" >> textures/SOURCES.md
```
(EnvMap-driven sheen comes from the `.set` later — out of scope here.)
- [ ] **Step 2: Gutter — extra-dark stone channel from the cobble source**
```bash
cd wgt01 && nix shell nixpkgs#imagemagick -c bash -c '
P=/home/vicky/Blender/PBR_Procampur
MOD=45,40,100 TINT="#14181f" TINTPCT=20 ./regrade.sh "$P/tcn01_cobb02_d.tga" textures/wgt01_gutter_d.tga
cp "$P/tcn01_cobb02_n.tga" textures/wgt01_gutter_n.tga
cp "$P/tcn01_cobb02_h.tga" textures/wgt01_gutter_h.tga'
printf "renderhint NormalAndSpecMapped\r\n\r\n// Textures\r\ntexture0 wgt01_gutter_d\r\ntexture1 wgt01_gutter_n\r\ntexture2 black\r\ntexture3 null\r\ntexture4 wgt01_gutter_h\r\ntexture5 null\r\n" > textures/wgt01_gutter.mtr
printf "| wgt01_gutter_* | PBR_Procampur/tcn01_cobb02_* (extra-dark regrade) | see readme |\n" >> textures/SOURCES.md
```
Try converting `$P/tcn01_grate01.dds` for an iron grate decal (`magick` failed on its header earlier; try `nix shell nixpkgs#directx-shader-compiler`? No — try `texconv` alt: `nix shell nixpkgs#imagemagick -c magick dds:...` or Gimp headless). If unreadable, skip: the drain grate becomes modeled geometry with `wgt01_gutter`, note it in SOURCES.md — do not block on it.
- [ ] **Step 3: Check + commit** — completeness loop for `wgt01_water`, `wgt01_gutter`; montage; `git add wgt01/textures && git commit -m "wgt01: authored water + gutter slots"`.
---
### Task 5: Wire materials into wgt01.blend
**Files:**
- Create: `wgt01/wire_materials.py`
- Modify: `wgt01/wgt01.blend` (headless save)
**Interfaces:**
- Consumes: all 8 slot basenames from Tasks 24.
- Produces: Blender materials named `MAT_wgt01_<slot>` (Principled BSDF, image texture node on `_d`, normal-map node on `_n` where present, relative paths `//textures/…`).
- [ ] **Step 1: Write `wgt01/wire_materials.py`**
```python
"""Create/refresh MAT_wgt01_* materials in wgt01.blend (run headless)."""
import bpy, os
SLOTS = ["cobble", "worn", "plaza", "sidewlk", "wall", "bridge", "gutter", "water"]
BASE = os.path.dirname(bpy.data.filepath)
for slot in SLOTS:
name = f"MAT_wgt01_{slot}"
mat = bpy.data.materials.get(name) or bpy.data.materials.new(name)
mat.use_nodes = True
nt = mat.node_tree; nt.nodes.clear()
out = nt.nodes.new("ShaderNodeOutputMaterial")
bsdf = nt.nodes.new("ShaderNodeBsdfPrincipled")
nt.links.new(bsdf.outputs["BSDF"], out.inputs["Surface"])
d = os.path.join(BASE, "textures", f"wgt01_{slot}_d.tga")
img = bpy.data.images.load(d, check_existing=True)
img.filepath = f"//textures/wgt01_{slot}_d.tga"
tex = nt.nodes.new("ShaderNodeTexImage"); tex.image = img
nt.links.new(tex.outputs["Color"], bsdf.inputs["Base Color"])
n = os.path.join(BASE, "textures", f"wgt01_{slot}_n.tga")
if os.path.exists(n):
nimg = bpy.data.images.load(n, check_existing=True)
nimg.filepath = f"//textures/wgt01_{slot}_n.tga"; nimg.colorspace_settings.name = "Non-Color"
ntex = nt.nodes.new("ShaderNodeTexImage"); ntex.image = nimg
nmap = nt.nodes.new("ShaderNodeNormalMap")
nt.links.new(ntex.outputs["Color"], nmap.inputs["Color"])
nt.links.new(nmap.outputs["Normal"], bsdf.inputs["Normal"])
# wet look: low roughness on ground slots
bsdf.inputs["Roughness"].default_value = 0.35 if slot in ("cobble", "gutter", "water") else 0.6
bpy.ops.wm.save_mainfile()
print("WIRED:", ", ".join(f"MAT_wgt01_{s}" for s in SLOTS))
```
- [ ] **Step 2: Run headless and verify**
```bash
cd wgt01 && nix develop ../twu01 -c "$BLENDER_BIN" --background wgt01.blend --python wire_materials.py 2>&1 | grep WIRED
```
(If `$BLENDER_BIN` is unset outside the dev shell, run `nix develop ../twu01` first — it exports it.) Expected: `WIRED: MAT_wgt01_cobble, …` and `wgt01.blend` mtime updated.
- [ ] **Step 3: Verify images resolve** — re-open headless: `--python-expr 'import bpy; missing=[i.name for i in bpy.data.images if not i.has_data]; print("MISSING:", missing); assert not missing'`. Expected: `MISSING: []`.
- [ ] **Step 4: Commit**`git add wgt01/wire_materials.py wgt01/wgt01.blend && git commit -m "wgt01: wire MAT_wgt01_* materials into blend"`.
---
### Task 6: Final verification — palette contact sheet
**Files:**
- Create: `wgt01/textures/contact-sheet.png` (committed as the kit's visual record)
- [ ] **Step 1: Build the sheet**
```bash
cd wgt01 && nix shell nixpkgs#imagemagick -c bash -c '
magick montage -label "%f" -background "#111" -fill "#ccc" textures/wgt01_*_d.tga \
-tile 4x -geometry 256x256+6+6 textures/contact-sheet.png'
```
- [ ] **Step 2: Read the sheet next to `concept-art-1.png`'s Material Language column.** Acceptance: all 8 slots present, one shared dark/cool palette, plaza ≠ sidewalk ≠ cobble at a glance, bridge ≠ sidewalk, water near-black. Fix any failing slot by adjusting its regrade override and re-running that task's Step 1.
- [ ] **Step 3: SOURCES.md audit**
```bash
cd wgt01/textures && for f in wgt01_*; do grep -q "${f%%_[dnhs].tga}" SOURCES.md || grep -q "${f%.mtr}" SOURCES.md || { echo "UNSOURCED $f"; exit 1; }; done && echo OK
```
- [ ] **Step 4: Commit**`git add wgt01/textures/contact-sheet.png && git commit -m "wgt01: texture kit contact sheet"`.