# 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 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=<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 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_<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"`.
