14 KiB
wgt01 Texture Kit Implementation Plan
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.mdline (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; usenix 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, default65,55,100),TINT(default#232830),TINTPCT(default12). -
Step 1: Write the script
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
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 montagethe original vs regraded cobble side by side, Read the PNG, compare against the concept sheet's wet-cobblestone swatch. TuneMOD/TINTdefaults in the script if it reads too green or too bright; re-run Step 2. -
Step 4: Commit
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, matchingwgt01_<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
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
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-slotMOD/TINTenv overrides and re-run Step 1 for any slot that's off. -
Step 4: Commit
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: basenamewgt01_plazafor Tasks 5–6. -
Step 1: Find a CC0 large-slab flagstone on ambientCG
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:
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
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_gutterfor Tasks 5–6. -
Step 1: Water — near-black, subtle ripple, from procedural noise (no source needed)
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
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_nwhere present, relative paths//textures/…). -
Step 1: Write
wgt01/wire_materials.py
"""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
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
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
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".