Files
2026-07-05 01:46:27 +02:00

218 lines
13 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 — Build Status & Notes
Progress log for the **Westgate Urban Foundation MVP** tileset (prefix `twu01`).
Spec: [KICKOFF.md](KICKOFF.md). How NWN needs things shaped: [../AGENTS.md](../AGENTS.md).
_Last updated: 2026-07-04._
---
## What exists — the generator kit (`generator/`)
One source of truth (`tiles.py`) feeds four tools; the two NWN emitters need no
Blender, so they're directly testable.
| File | Role | Needs Blender? |
|---|---|---|
| `tiles.py` | **Single source of truth**: 40-tile table, 12 materials, the 12×12 test map, and all pure derivations (paint / height / walkmesh quads / `.set` corner+crosser mapping). `PREFIX=twu01`. | no |
| `build.py` | Builds `westgate_urban_foundation_mvp.blend` — 40 tile collections (`VIS_`/`WOK_`/`BLOCK_` + edge custom props), `TILE_EDGE_METADATA` text block, `PREVIEW_tile_library_grid`, `PREVIEW_12x12_test_district`. Editable preview only (quads OK — the exporters triangulate). | yes (4.0.2) |
| `make_mdl.py` | **Shipping model path.** Pure-Python `.mdl` + `.wok` + solid-colour `.tga`: faces triangulated, one `trimesh` node **per texture** (each with `bitmap`+`tverts`), embedded `aabb` walkmesh. | no |
| `make_itp.py` | `twu01palstd.itp` (GFF `ITP ` via `nwn_gff`) — the palette the toolset needs to register the set. | no (needs `nwn_gff`) |
| `make_wok.py` | Pure-Python ASCII `.wok` emitter (verts+faces+surfacemat+AABB tree); shared `aabb_node()` reused by `make_mdl`. | no |
| `make_set.py` | `twu01.set` (INI, **CRLF**) from the tile grammar; field order/values mirror stock. | no |
| `export_mdl.py` | _Alt path, not shipped._ Neverblender export — **flattens** multi-material tiles to one unloadable material, so superseded by `make_mdl.py`. | yes + addon |
`flake.nix` exposes these as apps + a dev shell (pinned **Blender 4.0.2**,
`neverwinter-nim`, `cleanmodels`).
## Pipeline (commands)
```sh
cd wgt01
nix run .#mdl -- mdl/ # .mdl + .wok + .tga (shipping models)
nix run .#set -- twu01.set # twu01.set (CRLF)
nix run .#itp -- twu01palstd.itp # palette (needs nwn_gff)
nix run .#build -- out.blend # optional: editable .blend preview
# nix run .#export -- out.blend mdl/ # alt Neverblender path (flattens materials)
```
## Verified (2026-07-04)
- **models (`make_mdl`)**: 40 `.mdl` + 40 `.wok` + 12 `.tga`. Each `.mdl` has
`newmodel`, `classification TILE`, AuroraBase name == resref, one `trimesh`
**per texture** with `bitmap`+`tverts` (tris), and the embedded `aabb`
walkmesh. E.g. `st_s` → 3 trimesh nodes (swlk/curb/wcob); `cp1` → 1 with
`bitmap twu01_wcob`. Walkmesh surfacemats: 4 (`wok_Stone`, walkable) / 7
(`wok_Nonwalk`). Matches the stock `trs02` tile structure field-for-field.
- **`.itp` (rebuilt 2026-07-04, pass 4)**: `twu01palstd.itp` round-trips through
`nwn_gff` with the **real structure** (../AGENTS.md §2.3): a **Features** folder
(STRREF 63261) with a leaf per **non-ground** tile (37), and a **Terrain** folder
(STRREF 8282) = paint `cobble` + the magic `eraser` leaf (STRREF 63291). Ground
tiles (CP1/CP2/CP3) are omitted from Features on purpose — they're the painted
fill, not click-placed. Every feature leaf is backed by a 1×1 `[GROUP#]` in the
`.set` (Name==leaf NAME, `Tile0`==that tile). Guarded by
`generator/test_namesync.py` (passes). **Pending GUI re-test.**
- **`.set` (pass 4)**: CRLF, 40 tiles, 3 terrains (`cobble`/`raised`/`canal`), 2
crossers (`ramp`/`canal_edge`). `HasHeightTransition=0` (MVP paints flat cobble
only; matches stock). **37 `[GROUP#]`, one per non-ground tile — CP1/CP2/CP3 stay
ungrouped so they remain the random terrain-fill pool** (see Root cause below).
`Version=V1.0`, stock field order. Corners/heights from each tile's edge grammar;
non-walkable tiles → `PathNode=N`.
- **install path**: `~/Documents/Neverwinter Nights/development` is a symlink to
`~/.local/share/Neverwinter Nights/development` (the toolset's real dir); one
`nix run .#dev-install` stages all 94 loose files there.
### Why the models were rebuilt (2026-07-04, pass 2)
The Neverblender export produced tiles the toolset couldn't load — **no `tverts`,
no `bitmap`, a dangling `materialname` (>16 chars, no `.mtr`)** — and it
**flattened** each multi-material tile to a single arbitrary material. NWN needs
UV'd, bitmapped, one-texture-per-node trimeshes. Fixed by generating the `.mdl`
in pure Python (`make_mdl.py`) with full control, plus the required
`twu01palstd.itp`. Symptom that surfaced it: blank tileset name + "could not
create area" in the New Area wizard.
## Testing in the toolset — no `.hak` needed
The toolset reads loose files from **`~/Documents/Neverwinter Nights/development/`**
(confirmed present). One command does build → export → `.set` → stage:
```sh
cd wgt01
nix run .#dev-install # -> build/ (inspectable) + installs 40 .mdl/.wok + twu01.set into development/
```
It replaces only the `twu01*` files in `development/` (leaves everything else
alone) and keeps the intermediate outputs in `./build/` for inspection. Then in
the toolset: create a small area using the `twu01` tileset, place tiles, walk-test
pathing/heights. (Packing a `.hak` with `nwn_erf` is deferred until the set is
validated.)
## First-pass caveats — tune before shipping
- **PathNode** is a placeholder (`A` walkable / `N` non-walkable) — verify creature
routing per tile shape in the toolset; real pathnode letters matter.
- **Textures are flat solid-colour `.tga`s** (one per material, KICKOFF "geometry
not art"). They double as an in-toolset grammar read-out; swap in real art
later without touching geometry.
- **No minimaps** — `ImageMap2D` refs exist in the `.set` but the `.tga`s don't
(skipped by decision; Westgate doesn't use them).
- **Substitutions**: `RA_C` = straight-ramp geometry; `STA` = smooth ramp WOK
proxy over stepped visual; `SW_C/T/X/E` share the straight-sidewalk visual.
- `MAT_void_black` is defined but currently unpainted (11/12 materials realised).
- Corner terrain is a heuristic from edges — hand-tune adjacency in the toolset.
## Git
Branch **`wgt01-tileset-kit`** (`main` still unborn — `git branch -f main
wgt01-tileset-kit` to promote). Commits:
- `60af184` — build guide + 40-tile generator kit
- `1284f15` — real `.mdl`/`.wok` export via Neverblender
## Diagnosis 2026-07-04 — palette names show but nothing paints
Tiles now carry names, but they're **not selectable/paintable**, and the standard
**Terrain** section (paint terrains + eraser, Raise/Lower) is missing. Root cause,
confirmed against the working `wut01palstd.itp` + `wut01.set` (see ../AGENTS.md
§2.3 for the full model):
- The toolset has **two placement systems** and our `.itp` uses neither correctly.
Ground is laid by **Paint Terrain** (the toolset corner-matches and auto-picks
the model); the palette only **click-places `[GROUP#]`-backed prefabs**. It never
places raw tile models.
- Our `.itp` is **one flat folder of 40 raw tile resrefs**, no Terrain folder, no
`eraser`, and the `.set` has **`[GROUPS] Count=0`**. So: none of the 40 entries is
a feature/group → unplaceable; and with no Terrain folder there's no paint/eraser
UI. `wut01` (123 tiles) lists only its **21 groups** in the palette and paints the
rest as terrain; its `PRIMARY/SECONDARY RULES` are `Count=0`, so rules aren't the
blocker.
**The real palette (verified):** `MAIN` = 3 category structs `{ID, STRREF folder
label, LIST}`; leaves `{NAME, RESREF}`. Folder 8282 = **Terrain** (leaf RESREF =
terrain/crosser *name*, plus a magic `eraser` leaf STRREF 63291); 63261 =
**Features** (1×1, RESREF = first-tile resref); 63262 = **Groups** (N×M).
## Root cause 2026-07-04 — New Area crash (pass 3 regression, fixed pass 4)
Creating a new area threw `Access violation ... Write of address 00000040` in
`nwtoolset.exe`. **Root cause: an empty terrain-fill pool.** A grouped tile is
excluded from the toolset's random fill; pass 3 put *all 40* tiles in 1×1 groups,
so `Default=cobble` had **no** tile to fill with → null deref. Verified against
stock `tcn01` (City Exterior): of its 320 cobble-cornered tiles, 314 are group
members and only **6** (non-group `a20_*`/`o01`) form the real fill pool.
**Fix (pass 4):** ground tiles `CP1/CP2/CP3` (`tiles.GROUND_CODES`) are left
ungrouped → they are the interchangeable cobble the area spawns with and the
eraser resets to. The other 37 tiles keep their `[GROUP#]` (palette features).
Also set `HasHeightTransition=0` (matches stock; we don't paint height
transitions this pass). `test_namesync.py::test_ground_tiles_stay_in_fill_pool`
is the regression guard. This also fixes the earlier "random different tiles at
different elevations" fill — only CP1/CP2/CP3 now auto-fill.
## Pass 4 result (2026-07-04, confirmed in toolset)
✅ New Area succeeds, spawns plain cobble. ✅ Features click-place (the `[flat]`
prefabs). ✅ Eraser resets to cobble. ❌ **Canal and Raise/Lower are inert** — as
predicted by the ponytail note. Root cause (now fully understood, see AGENTS.md
§2.2/§2.3):
1. Canal & raised tiles are `[GROUP#]` features → excluded from the paint pool, so
the brush has nothing to auto-lay.
2. Only `cobble` is exposed in the `.itp` Terrain folder → no canal brush.
3. `HasHeightTransition=0` → Raise/Lower greyed out.
4. `[PRIMARY RULES] Count=0` → the brush can't reconcile a 2nd terrain / a height
step against neighbours.
5. `raised` is modelled as a *terrain* (`TERRAINS`), but in NWN it's a **height on
cobble** — the model is wrong.
## Pass 5a done (2026-07-04) — CANAL is now paintable
Staged via `nix run .#dev-install`: 51 tile placements from 41 models, 33 feature
groups, matrix complete, `namesync OK`. What changed vs pass 4:
- `tiles.py`: split spec→placement (`placements()`, rotate one model into 4
orientations); `is_paint`/`is_transition`; added diagonal-pinch tile `CE_D`;
fixed `CE_I` corners (its geometry is 1 canal corner, edges wrongly read 3);
`canal_paint_matrix()` completeness helper.
- `make_set.py`: emits rotated `[TILE#]` placements, groups only NON-paint tiles,
blanks crossers on paint tiles (corner-match only), **hard-fails if the 16-pattern
cobble/canal matrix is incomplete** (caught the CE_I bug).
- `make_itp.py`: Terrain folder = `cobble` + `canal` + eraser; Features = non-paint.
- `HasHeightTransition` still 0 (raise/lower = pass 5b).
**Re-test:** (a) is there a **canal** brush in Terrain, and does painting it lay
black-water tiles? (b) do canal/land **edges auto-appear** (straight, both corners,
diagonal) without seams or gaps? (c) does the **eraser** still reset to cobble, and
New Area still spawn cobble (no regression)? (d) do the click-place features (streets
etc.) still place? Report back — then 5b adds raise/lower on the same machinery.
## Pass 5b plan — raise/lower (next) + original full plan below
Reference: stock `tcn01` (513 tiles → 58 corner-tuples; 76 primary rules;
`HasHeightTransition=1`). Our geometry-only version needs ~1 tile per tuple.
- **Model change in `tiles.py`:** split *tile spec* (a model) from *placement* (a
`[TILE#]` = model + `Orientation` + rotated corners/edges). One transition model
→ up to 4 placements (0/90/180/270). Drop `raised` from `TERRAINS`
`TERRAINS=["cobble","canal"]`; raised = `cobble` corner at `Height=1`.
- **Ungroup the terrain tiles:** canal fill, canal-edge (straight/outer/inner),
ramp, retaining-edge (straight/outer/inner) leave `[GROUPS]` and join the paint
pool. Streets/sidewalks/bridges/drains **stay** grouped features.
- **Corner matrix:** generate the full cobble/canal patterns and the full
cobble:0/cobble:1 patterns (via orientation) so the brush never requests a missing
tile. Keep canal and raised in **separate** matrices (a tile is never
canal-corner + raised-corner) to bound the count.
- **`[PRIMARY RULES]`:** skip (stay `Count=0`). Verified not required — `wut01`
paints 6 terrains with `Count=0`. They only polish cascading transitions; add
later if seams need it.
- **`[GENERAL]`:** `HasHeightTransition=1`.
- **`.itp` Terrain folder:** `cobble`, `canal`, + `eraser`.
- **Guard:** a completeness self-check that fails generation if any corner pattern
the brush can request lacks a tile (so an incomplete matrix can't ship a crash).
2. Walk-test → tune PathNodes, crossers, corner terrains; add a tileset TLK if
we later want localized palette names (local `NAME` strings suffice for now).
3. **Textures (art pass, separate note).** Source assets live in
`/home/vicky/Blender/` (e.g. `textures/`, `PBR_Procampur/`, `td_floor/`,
`new_floors/`, `pd textures main lib/`). Map each `MAT_*` in `tiles.TEXTURES`
to a chosen texture, replacing the flat solid-colour `.tga`s. **Downscale to
≤2k** — NWN handles 4k textures poorly (perf/artifacts). Geometry is
unaffected; only the `.tga`/material path changes.
4. Pack `.hak` (`nwn_erf`) once validated.