# 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 ` — 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 (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: -> ` 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_.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 5–6 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 5–6. - [ ] **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=_1K-JPG.zip' && nix shell nixpkgs#unzip -c unzip -o plaza.zip -d plaza/ ls plaza/ # expect _1K_Color.jpg, _NormalGL.jpg, _Displacement.jpg ``` - [ ] **Step 2: Convert + regrade into the kit** ```bash cd /wgt01 && S=/plaza A= 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 5–6. - [ ] **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 2–4. - Produces: Blender materials named `MAT_wgt01_` (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"`.