3 Commits
Author SHA1 Message Date
archvillainette 90e41e6780 agents.md 2026-07-09 09:47:39 +02:00
archvillainette 8c4736f14e textures 2026-07-08 13:37:00 +02:00
archvillainette 1fe41faa6b texture seeds 2026-07-05 09:34:07 +02:00
65 changed files with 284 additions and 262 deletions
+27 -22
View File
@@ -11,6 +11,21 @@ toolset, so geometry built here converts cleanly to a shippable `.hak`.
---
## What it is / part it serves
Shadows Over Westgate (SoW) is a Neverwinter Nights: Enhanced Edition
persistent-world project split into single-purpose repos. This repo holds the
**tileset sources** for the SoW module: the Blender generators and NWN files
(`.set`, `.mdl`, `.wok`, `.itp`) that become a tileset `.hak` (a HAK is an NWN
resource archive). The packed `.hak` is shipped through sow-assets-manifest
(https://git.westgate.pw/ShadowsOverWestgate/sow-assets-manifest), which owns
HAK publishing and CDN sync; the module in sow-module
(https://git.westgate.pw/ShadowsOverWestgate/sow-module) pins that HAK list.
Nothing here builds the module or the asset pipeline — this repo only makes
tilesets. Repo layout is in §0, the build pipeline in §6.
---
## 0. Repo layout
Each tileset lives in its own directory. One tileset = one 5-char prefix = one
@@ -19,14 +34,22 @@ Each tileset lives in its own directory. One tileset = one 5-char prefix = one
```
sow-module-tilesets/
├── AGENTS.md ← you are here (applies to all tilesets)
├── wgt01/ ← Westgate Urban Foundation MVP (streets, canals, ramps)
├── twu01/ ← Westgate Urban Foundation MVP (prefix TWU01)
│ ├── KICKOFF.md ← design brief / tile list for this tileset (the spec)
│ ├── concept-art-1.png
│ ├── STATUS.md ← current state / known issues
│ ├── flake.nix ← Blender 4.0.2 + generator entry points
── generator/ ← bpy scripts that build the .blend source
└── <future-tileset>/ ← same shape
── generator/ ← bpy scripts that build the .blend source
│ └── build/ ← tileset build output
├── wgt01/ ← art/design side: DESIGN.md, VIBE.md, textures, .blend
├── build/ ← top-level build output (mdl/, twu01.set, .blend)
├── docs/ ← plans/specs archive (do not touch)
└── <future-tileset>/ ← same shape as twu01
```
> Note: older sections below still say `wgt01/generator`, `wgt01/flake.nix`,
> `wgt01/STATUS.md`. Those files now live under `twu01/` (the NWN prefix dir);
> `wgt01/` keeps the art/design side. Read `wgt01/…` in the text as `twu01/…`.
- **wgt01** is the *urban foundation* set: ground grammar only (streets,
sidewalks, plazas, ramps, terraces, canals, bridges). Buildings/props are
placeables layered on top later — **not** part of any tileset here.
@@ -522,21 +545,3 @@ area with the tileset and walk-test. Much faster than pack→reload; pack the
_Verify specifics against the wiki before relying on them; correct this file when
you find drift._
## Agent skills
### Issue tracker
Issues live in Gitea at git.westgate.pw (`ShadowsOverWestgate/sow-module-tilesets`), managed with the `tea` CLI. Issues follow ownership — file work in the repo that owns it, not the one you happen to be standing in. See `docs/agents/issue-tracker.md`.
### Triage labels
Default label vocabulary (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`). See `docs/agents/triage-labels.md`.
### Domain docs
Single-context: `CONTEXT.md` at the repo root plus `docs/adr/`. See `docs/agents/domain.md`.
### Where work lives
Markdown here is **reference, law, or an ADR — nothing else** (`sow-codebase` ADR-0001). Live work -> wayfinder maps + Gitea issues (closeable, assignable, queryable). Settled decisions -> ADR files in `docs/adr/` (immutable, never closed, only superseded). Standing law -> `DOCTRINE.md` / `AGENTS.md` / `CONTEXT.md`. Current-state reference -> docs describing what the code does now. Everything else — plans, specs, concepts, handoffs, trackers — is process: it belongs in a Gitea issue, not a file. Harvest unfinished intent to an issue before deleting a process doc. `sow-docs` is deprecated and read-only.
@@ -1,55 +0,0 @@
# ADR-0001: Markdown in this repo is reference, law, or an ADR — nothing else
- **Status:** Accepted
- **Date:** 2026-07-24
- **Origin:** Adopted workspace-wide from `sow-codebase` ADR-0001, which records
the full context (a `docs/` tree that had grown to 434 files, ~27MB, most of
it dead process artifacts). This repo adopts the same law so the rule is
local, not a cross-repo reference.
## Context
Markdown that *claims to describe current state or a plan* rots, because
reality diverges and nobody edits the file. Markdown that claims neither — a
dated, immutable decision record, or a standing rule — does not rot.
Two different things get conflated:
- **Rationale** — why a system is shaped the way it is. Worth keeping.
- **Process artifacts** — plans, specs, concepts, handoffs, trackers. A record
of how work happened, not what is true now.
Left unchecked, every completed effort leaves its planning behind and the repo
accumulates a "historical" pile that agents and humans must route around to
find the few live docs.
## Decision
Markdown may exist in this repo only as one of three genres:
1. **Current-state reference** — describes what the code does now, kept honest
by code review touching it.
2. **Standing law**`DOCTRINE.md`, root and folder-scoped `AGENTS.md`,
`CONTEXT.md`. States rules and vocabulary, not plans.
3. **Immutable decision record** — ADRs under `docs/adr/`. Append-only; never
edited, only superseded by a later ADR that points back.
Anything else — implementation plans, design specs, concepts, handoffs,
progress trackers, scratch — is **process** and does not live in the repo. It
lives in Gitea issues and wayfinder maps, where it can be assigned, closed, and
superseded. Small tasks need no written plan at all.
Deleting a process document is not destroying history: `git log -- <path>`
recovers it. The exception is *unfinished intent* (a design never built, an
open question still wanted) — that is live, not history, and must be harvested
to a Gitea ticket before its file is deleted. A citation from a living doc or
from code must be resolved before the cited file is deleted.
## Consequences
- The documentation map lists only current truth; there is no "historical" pile
to route around.
- No agent, under any plugin or skill, writes process-markdown into the repo.
The rule is plugin-agnostic on purpose — it binds the agent, not a named tool.
- History of deleted process docs is in git; unfinished intent is in the issue
tracker; rationale is in ADRs and law. Each thing has exactly one home.
-51
View File
@@ -1,51 +0,0 @@
# Domain Docs
How the engineering skills should consume this repo's domain documentation when exploring the codebase.
## Before exploring, read these
- **`CONTEXT.md`** at the repo root, or
- **`CONTEXT-MAP.md`** at the repo root if it exists — it points at one `CONTEXT.md` per context. Read each one relevant to the topic.
- **`docs/adr/`** — read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved.
## File structure
Single-context repo (most repos):
```
/
├── CONTEXT.md
├── docs/adr/
│ ├── 0001-event-sourced-orders.md
│ └── 0002-postgres-for-write-model.md
└── src/
```
Multi-context repo (presence of `CONTEXT-MAP.md` at the root):
```
/
├── CONTEXT-MAP.md
├── docs/adr/ ← system-wide decisions
└── src/
├── ordering/
│ ├── CONTEXT.md
│ └── docs/adr/ ← context-specific decisions
└── billing/
├── CONTEXT.md
└── docs/adr/
```
## Use the glossary's vocabulary
When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids.
If the concept you need isn't in the glossary yet, that's a signal — either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`).
## Flag ADR conflicts
If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
> _Contradicts ADR-0007 (event-sourced orders) — but worth reopening because…_
-84
View File
@@ -1,84 +0,0 @@
# Issue tracker: Gitea (via tea)
Issues for this repo live in Gitea at `git.westgate.pw`, repo
`ShadowsOverWestgate/sow-module-tilesets`. Use the `tea` CLI for all operations —
`gh` does not work here. For anything `tea` lacks a subcommand for, use
`tea api <endpoint>` (Gitea's API mirrors GitHub's closely).
Authenticate with your own `tea` login (`tea login add`); never commit tokens
or tea config into this repo. Note Gitea blocks self-review, so approving a PR
needs a different account than the one that opened it.
## Where work lives
Markdown in this repo is **reference, law, or an ADR — nothing else**
(`sow-codebase` ADR-0001). Four homes, no overlap:
- **Live work** → wayfinder maps + Gitea issues. Closeable, assignable,
queryable. Never a markdown file.
- **Settled decisions** → ADR files in `docs/adr/`. Immutable, findable, never
closed, never edited — only superseded by a later ADR pointing back. When an
issue ends in a durable decision, write the ADR, then close the issue
pointing at it. Decisions spanning repos go to `sow-platform/docs/adr/`.
- **Standing law** → `DOCTRINE.md`, `AGENTS.md`, `CONTEXT.md`. Rules that are
always true and vocabulary everyone shares — not the record of one decision.
- **Current-state reference** → docs describing what the code does now, kept
honest by review touching them.
Anything else — implementation plans, design specs, concepts, handoffs,
progress trackers, scratch — is **process**. It does not live in this repo. It
lives in a Gitea issue or a wayfinder map, where it can be assigned, closed,
and superseded. Small tasks need no written plan at all.
Deleting a process doc is not destroying history — `git log -- <path>` recovers
it. But *unfinished intent* (a design never built, an open question still
wanted) is live, not history: harvest it to a Gitea issue before deleting.
`sow-docs` is deprecated and read-only. Never add to it, never send work there.
## Which repo gets the issue
Issues follow ownership. File the issue in the repo that **owns the work**
see the Repo/Owns/Produces table in the workspace root `AGENTS.md`. Standing
in one repo is not a reason to file there.
If work spans repos, file it in the repo that owns the *outcome* and reference
the others from it. A wrong-repo issue is a routing bug, not a filing
preference — move it.
## Conventions
- **Create an issue**: `tea issues create --title "..." --description "..."`
- **Read an issue**: `tea issues <number>` and
`tea api repos/ShadowsOverWestgate/sow-module-tilesets/issues/<number>/comments` for comments.
- **List issues**: `tea issues list --state open` (add `--labels ...` to filter).
- **Comment**: `tea comment <number> "..."`
- **Apply / remove labels**: `tea api --method PATCH` on the issue, or
`tea api repos/ShadowsOverWestgate/sow-module-tilesets/issues/<number>/labels` endpoints.
- **Close**: `tea issues close <number>`
`tea` infers the repo from the git remote when run inside the clone.
Gitea shares one number space across issues and PRs.
## Pull requests as a triage surface
**PRs as a request surface: no.**
## When a skill says "publish to the issue tracker"
Create a Gitea issue with `tea issues create`.
## When a skill says "fetch the relevant ticket"
Run `tea issues <number>` plus the comments API call above.
## Wayfinding operations
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.
- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `tea issues create --title "..." --description "..." --labels wayfinder:map`.
- **Child ticket**: this Gitea instance (v1.27) has no native sub-issue hierarchy, so a child issue carries `Part of #<map>` at the top of its description, and is also added to a task list in the map body. Labels: `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
- **Blocking**: Gitea's **native dependencies API** — the canonical, UI-visible representation (shows as "Depends on" / "Blocks" on the issue page). Add an edge with `tea api -X POST repos/ShadowsOverWestgate/sow-module-tilesets/issues/<child>/dependencies -f owner=ShadowsOverWestgate -f repo=sow-module-tilesets -F index=<blocker>`, where `<blocker>` is the blocker's issue **index** (its `#number` — Gitea's dependency API takes the index directly, unlike GitHub's numeric database id). Check status with `tea api repos/ShadowsOverWestgate/sow-module-tilesets/issues/<child>/dependencies` (GET) — a ticket is unblocked when every returned issue's `state` is `closed`.
- **Frontier query**: list the map's open children (`tea issues list --state open`, keep the ones whose description contains `Part of #<map>`), drop any with an open dependency (per the GET above) or an assignee; first in map order wins.
- **Claim**: `tea issues edit <n> --add-assignees <username>` — the session's first write.
- **Resolve**: `tea comment <n> "<answer>"`, then `tea issues close <n>`, then append a context pointer (gist + link) to the map's Decisions-so-far.
-15
View File
@@ -1,15 +0,0 @@
# Triage Labels
The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker (Gitea — see `issue-tracker.md` for how to apply labels with `tea`).
| Label in mattpocock/skills | Label in our tracker | Meaning |
| -------------------------- | -------------------- | ---------------------------------------- |
| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue |
| `needs-info` | `needs-info` | Waiting on reporter for more information |
| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent |
| `ready-for-human` | `ready-for-human` | Requires human implementation |
| `wontfix` | `wontfix` | Will not be actioned |
When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table.
Edit the right-hand column to match whatever vocabulary you actually use.
@@ -1,6 +1,11 @@
# wgt01 Texture Kit — Missing Slots (Phase 2) — Spec
Date: 2026-07-05 · Status: draft, not yet planned
Date: 2026-07-05 · Status: **material slots shipped 2026-07-05** (fbrick, iron,
timber, planks, brass, copper, plastr, slate, glass — see `wgt01/textures/SOURCES.md`).
Remaining: decal tail only (canvas, poster, alchst, soot variant) — deferred
until facade/placeable work shows they're needed. Deviations: glass is
diffuse-only (no `.mtr`; alpha/env deferred); slate `_h` is derived from the
diffuse (BC4 source undecodable).
## Goal
+29
View File
@@ -0,0 +1,29 @@
# wgt01/textures — sources & licenses
| file | origin | license |
|---|---|---|
| wgt01_cobble_* | PBR_Procampur/tcn01_cobb02_* | see PBR_Procampur readme |
| wgt01_worn_* | PBR_Procampur/tcn01_cobb05_* | see PBR_Procampur readme |
| wgt01_sidewlk_* | PBR_Procampur/tcn01_stone04_* | see PBR_Procampur readme |
| wgt01_wall_* | PBR_Procampur/tcn01_brick03_* | see PBR_Procampur readme |
| wgt01_bridge_* | PBR_Procampur/tcn01_stone04_* | see PBR_Procampur readme |
| wgt01_grime1_d | PBR_Procampur/tcn01_splotch2_d | see readme |
| wgt01_wtrline_d | PBR_Procampur/tcn01_watrstn02 | see readme |
| wgt01_plaza_* | ambientcg.com asset PavingStones127 (1K JPG) | CC0 |
| wgt01_water_d | authored (ImageMagick plasma); bumpyshiny via wgt01_water_d.txi (standard water template, no mtr) | ours |
| wgt01_gutter_* | PBR_Procampur/tcn01_cobb02_* (extra-dark regrade) | see readme |
| wgt01_grate_d | PBR_Procampur/tcn01_grate01.dds via nwn_crunch tga conversion | see readme |
| wgt01_timber_* | PBR_Procampur/tcn01_wood01_* | see PBR_Procampur readme |
| wgt01_planks_* | PBR_Procampur/tcn01_planks01_* (extra-dark tarred regrade: MOD=50,50,100 TINTPCT=18) | see readme |
| wgt01_iron_* | PBR_Procampur/tcn01_metl03_* (near-black regrade: MOD=55,40,100) | see readme |
| wgt01_plastr_* | PBR_Procampur/tcn01_plstr00_* | see readme |
| wgt01_slate_* | PBR_Procampur/tcn01_roof02_[DN].dds (cool slate regrade: MOD=42,40,105 TINT=#1c2436 TINTPCT=22); _h derived from diffuse luminance (BC4 source undecodable) | see readme |
| wgt01_glass_d | PBR_Procampur/tcn01_glas02_d (diffuse only; alpha/env treatment deferred to facade work) | see readme |
| wgt01_fbrick_* | ambientcg.com asset Bricks097 (1K JPG, 1024×512 source stacked 2× vertically to 1024×1024); hue-preserving grade MOD=55,75,100 TINT=#1a1512 TINTPCT=10 | CC0 |
| wgt01_dirt_* | PBR_Procampur/tcn01_dirt00_* (default regrade) | see readme |
| wgt01_grass_* | PBR_Procampur/tcn01_grass02_* (default regrade) | see readme |
| wgt01_gravel_* | pd textures main lib/Ground/pattern_199 (default regrade) | see pd lib readme |
| wgt01_road_* | pd textures main lib/Roads/pattern_50 (default regrade) | see pd lib readme |
| wgt01_brass_* | ambientcg.com asset Metal008 (1K JPG); warm grade MOD=70,85,100 TINT=#241c10 TINTPCT=8 | CC0 |
| wgt01_cobb03_* | PBR_Procampur/tcn01_cobb03_* (default regrade) | see readme |
| wgt01_copper_* | ambientcg.com asset PaintedMetal006 (1K JPG, verdigris); grade MOD=65,80,100 TINT=#0f1a18 TINTPCT=10 | CC0 |
BIN
View File
Binary file not shown.
-15
View File
@@ -1,15 +0,0 @@
# wgt01/textures — sources & licenses
| file | origin | license |
|---|---|---|
| wgt01_cobble_* | PBR_Procampur/tcn01_cobb02_* | see PBR_Procampur readme |
| wgt01_worn_* | PBR_Procampur/tcn01_cobb05_* | see PBR_Procampur readme |
| wgt01_sidewlk_* | PBR_Procampur/tcn01_stone04_* | see PBR_Procampur readme |
| wgt01_wall_* | PBR_Procampur/tcn01_brick03_* | see PBR_Procampur readme |
| wgt01_bridge_* | PBR_Procampur/tcn01_stone04_* | see PBR_Procampur readme |
| wgt01_grime1_d | PBR_Procampur/tcn01_splotch2_d | see readme |
| wgt01_wtrline_d | PBR_Procampur/tcn01_watrstn02 | see readme |
| wgt01_plaza_* | ambientcg.com asset PavingStones127 (1K JPG) | CC0 |
| wgt01_water_d | authored (ImageMagick plasma); bumpyshiny via wgt01_water_d.txi (standard water template, no mtr) | ours |
| wgt01_gutter_* | PBR_Procampur/tcn01_cobb02_* (extra-dark regrade) | see readme |
| wgt01_grate_d | PBR_Procampur/tcn01_grate01.dds via nwn_crunch tga conversion | see readme |
Binary file not shown.
@@ -1,9 +1,9 @@
renderhint NormalAndSpecMapped
// Textures
texture0 wgt01_plaza_d
texture1 wgt01_plaza_n
texture0 wgt01_brass_d
texture1 wgt01_brass_n
texture2 black
texture3 null
texture4 wgt01_plaza_h
texture4 wgt01_brass_h
texture5 null
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
renderhint NormalAndSpecMapped
// Textures
texture0 wgt01_cobb03_d
texture1 wgt01_cobb03_n
texture2 black
texture3 null
texture4 wgt01_cobb03_h
texture5 null
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
renderhint NormalAndSpecMapped
// Textures
texture0 wgt01_copper_d
texture1 wgt01_copper_n
texture2 black
texture3 null
texture4 wgt01_copper_h
texture5 null
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
renderhint NormalAndSpecMapped
// Textures
texture0 wgt01_dirt_d
texture1 wgt01_dirt_n
texture2 black
texture3 null
texture4 wgt01_dirt_h
texture5 null
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
renderhint NormalAndSpecMapped
// Textures
texture0 wgt01_fbrick_d
texture1 wgt01_fbrick_n
texture2 black
texture3 null
texture4 wgt01_fbrick_h
texture5 null
Binary file not shown.
Binary file not shown.
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
renderhint NormalAndSpecMapped
// Textures
texture0 wgt01_fence01_d
texture1 wgt01_fence01_n
texture2 black
texture3 null
texture4 wgt01_fence01_h
texture5 null
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
renderhint NormalAndSpecMapped
// Textures
texture0 wgt01_grass_d
texture1 wgt01_grass_n
texture2 black
texture3 null
texture4 wgt01_grass_h
texture5 null
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
renderhint NormalAndSpecMapped
// Textures
texture0 wgt01_iron_d
texture1 wgt01_iron_n
texture2 black
texture3 null
texture4 wgt01_iron_h
texture5 null
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
renderhint NormalAndSpecMapped
// Textures
texture0 wgt01_planks_d
texture1 wgt01_planks_n
texture2 black
texture3 null
texture4 wgt01_planks_h
texture5 null
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
renderhint NormalAndSpecMapped
// Textures
texture0 wgt01_plastr_d
texture1 wgt01_plastr_n
texture2 black
texture3 null
texture4 wgt01_plastr_h
texture5 null
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
renderhint NormalAndSpecMapped
// Textures
texture0 wgt01_slate_d
texture1 wgt01_slate_n
texture2 black
texture3 null
texture4 wgt01_slate_h
texture5 null
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
renderhint NormalAndSpecMapped
// Textures
texture0 wgt01_timber_d
texture1 wgt01_timber_n
texture2 black
texture3 null
texture4 wgt01_timber_h
texture5 null
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
+3 -2
View File
@@ -1,6 +1,7 @@
"""Create/refresh MAT_wgt01_* materials in wgt01.blend (run headless)."""
import bpy, os
SLOTS = ["cobble", "worn", "plaza", "sidewlk", "wall", "bridge", "gutter", "water"]
SLOTS = ["cobble", "worn", "plaza", "sidewlk", "wall", "bridge", "gutter", "water",
"fbrick", "iron", "timber", "planks", "brass", "copper", "plastr", "slate", "glass", "dirt", "grass", "gravel", "road"]
BASE = os.path.dirname(bpy.data.filepath)
for slot in SLOTS:
@@ -26,7 +27,7 @@ for slot in SLOTS:
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
bsdf.inputs["Roughness"].default_value = 0.35 if slot in ("cobble", "gutter", "water", "iron", "brass", "copper", "glass") else 0.6
bpy.ops.wm.save_mainfile()
print("WIRED:", ", ".join(f"MAT_wgt01_{s}" for s in SLOTS))