Files
TheriapolisV3/theriapolis-tile-generation-handoff.md
T
Christopher Wiebe b451f83174 Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:40:51 -07:00

296 lines
13 KiB
Markdown
Raw 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.
# Theriapolis — Tile Generation Handoff
This is the working knowledge accumulated while populating
`Content/Gfx/tactical/` with art generated through the Pixellab MCP
service. Read this whole file before generating any new tiles — there are
several non-obvious failure modes and the ones we already burned credits on
have specific fixes documented below.
---
## 1. Where tiles live
```
Content/Gfx/tactical/
surface/ ← edge-to-edge ground textures, fully opaque
grass_0.png grass_1.png grass_2.png (user manual)
tallgrass_0.png tallgrass_1.png (user manual)
dirt_0.png dirt_1.png (user manual)
gravel_0.png gravel_1.png (user manual)
sand_0.png sand_1.png sand_2.png (Pixellab)
snow_0.png snow_1.png snow_2.png (Pixellab)
rock_0.png rock_1.png rock_2.png (Pixellab — see §6 known issues)
marsh_0.png marsh_1.png marsh_2.png (Pixellab — third reroll)
mud_0.png mud_1.png mud_2.png (Pixellab)
cobble_0.png cobble_1.png cobble_2.png (Pixellab)
floor_0.png floor_1.png floor_2.png (Pixellab)
wall_0.png wall_1.png wall_2.png (Pixellab)
shallowwater_0.png … _2.png (Pixellab)
deepwater_0.png … _2.png (Pixellab)
troddendirt_0.png … _2.png (Pixellab — see §3.5)
deco/ ← single objects with transparent background
tree.png bush.png snag.png (Pixellab — forest set)
flower.png crop.png reed.png (Pixellab)
rock.png boulder.png (Pixellab)
```
Filenames are **lowercased enum names** matching `TacticalSurface` and
`TacticalDeco` in [`TacticalTile.cs`](Theriapolis.Core/Tactical/TacticalTile.cs).
The `_N` suffix is a variant index — the renderer picks one per cell from a
deterministic per-tile `Variant` byte. If only `<name>.png` exists (no suffix),
every cell uses it. The atlas auto-loads anything in these folders; missing
files fall back to procedurally-generated colored squares from
[`TacticalAtlas.cs`](Theriapolis.Game/Rendering/TacticalAtlas.cs).
**Provenance matters.** The user manually placed grass/tallgrass/dirt/gravel
to set the visual style baseline. **Do not overwrite those.** Anything the
user produced in the Pixellab web UI also takes precedence over MCP-generated
tiles. When in doubt, ask before saving over an existing file.
---
## 2. The Pixellab MCP setup
The MCP server `pixellab` provides programmatic access to PixelLab AI's
generators. Tools we use:
- `mcp__pixellab__create_tiles_pro` — surface tiles. Always returns 16
variations regardless of what you describe. Async (~30 s without
`style_images`, ~7-8 min *with* style_images).
- `mcp__pixellab__get_tiles_pro` — poll status + retrieve URLs.
- `mcp__pixellab__create_map_object` — single decoration sprite with
transparent background. Async (~30-90 s).
- `mcp__pixellab__get_map_object` — poll/retrieve.
Each call returns a job ID immediately; the work happens server-side.
Use `ScheduleWakeup` with the job ID(s) in the prompt to come back when
the work is likely done — don't poll in a tight loop.
**MCP scope quirk.** When you run the Pixellab CLI installer, it adds the
server entry to whichever directory you were `cd`-ed into at the time. If the
project Claude Code sees doesn't have the entry, the tools won't appear. Fix
by editing `~/.claude.json` directly — find the project entry under
`projects[<your-project-path>].mcpServers` and ensure `pixellab` is in there.
A full Claude Code restart is required for changes to take effect.
**Auth.** The bearer token in the config is reusable indefinitely until
rotated. Don't leak it — rotate any token that gets pasted in chat or read
into a tool result.
---
## 3. The generation workflow
### 3.1 Surface tiles — use `style_images` mode, ALWAYS
Without `style_images`, `create_tiles_pro` bakes a 13 pixel **dark border
frame** into every tile (the algorithm literally "draws tile shape outlines
and has AI fill them with pixel art"). The border is sometimes pure black,
sometimes a dark grey/purple, and the existing
`TacticalAtlas.StripBorderPixels` only catches some of it. Don't rely on the
strip; just bypass the bordering algorithm by providing a clean reference.
The reference image has to be a **clean, edge-to-edge, fully-opaque 32×32
PNG with no border itself**. The user's `grass_0.png` works perfectly. Encode
it base64 and pass it as `style_images`.
### 3.2 Avoid the baked-in shadow gradient
`create_tiles_pro` defaults to `tile_view: "low top-down"` which adds ~30%
pseudo-3D depth. The AI renders that depth as a **dark shadow on the bottom
and right edges of every tile** (interior brightness drops by 5090 points
on those edges). The border detector misses this because the shadow color
isn't black-uniform — it's just darker than the interior.
Tiled together, every tile shows a diagonal grid of dark seams. Always set:
```jsonc
"tile_view": "top-down", // not "low top-down"
"tile_depth_ratio": 0, // force flat
"style_options": { "color_palette": false, "outline": false, "detail": true, "shading": false }
```
`shading: false` in style_options also helps suppress the depth shadow.
### 3.3 Force opaque coverage in the prompt
If you say "no plants no rocks no transitions" the AI sometimes interprets
that as "make most of the tile transparent and put a small clump of content
in the middle". This produced a `marsh-v2` batch where every tile was 3080%
transparent. Fix by explicitly demanding full opacity:
> "A fully opaque solid 32x32 ground tile … every single pixel must be
> opaque earth, no transparency anywhere, no holes…"
### 3.4 Decoration tiles — use `create_map_object`, not `create_tiles_pro`
Decorations are individual props (trees, bushes, etc.) with **transparent
backgrounds**. They composite over surface tiles. `create_map_object` is
designed for this — it returns a single PNG with proper transparency.
Standard params:
```jsonc
{ "width": 32, "height": 32,
"view": "high top-down",
"outline": "single color outline",
"shading": "medium shading",
"detail": "medium detail" }
```
### 3.5 Avoid description traps that bake in directional features
For `troddendirt` we said "wheel ruts" in the description. The AI obliged
by drawing very prominent vertical stripes that read as wood planks when
tiled. Subtle features → safe. Specific features → problematic, because
the AI baked them centered/directional and they don't tile.
Rule of thumb: if you want the texture to *feel* like a road but tile
seamlessly in any orientation, leave directional language out of the prompt
and let the natural variation in the 16 tiles give you variety.
---
## 4. The `tile-analyze` command
Run it on any folder of 32×32 PNGs:
```bash
dotnet run --project Theriapolis.Tools -- tile-analyze --dir /tmp/pixellab-<surface> --sheet /tmp/<surface>-sheet.png
```
Output is a per-tile table + an optional 4×-upscaled labeled contact sheet.
Each label shows:
- **`brd:N`** — number of edges (04) where ≥80% of perimeter pixels are
dark-uniform "border" pixels (max channel ≤ 95, max - min ≤ 25). Should
be 0 for a clean tile.
- **`op:NN%`** — fraction of pixels with α ≥ 128. Should be 100% for a
surface tile, 3070% for a decoration sprite.
- **`sh t/b/l/r`** — interior brightness minus edge brightness, per side.
Positive = edge is darker than interior. **Anything > 30 is a baked-in
shadow gradient** that will show as a grid of dark seams when tiled.
This is the single most useful number for triage; the border detector
misses shadows.
Labels are GREEN if the tile passes everything, ORANGE if it fails any
check. The full implementation lives in
[`TileAnalyze.cs`](Theriapolis.Tools/Commands/TileAnalyze.cs); the detector
methods (`CountBorderEdges`, `CountOpaqueFraction`, `ShadowScores`) are
public so you can call them directly from any future Tools command.
### Spot-check existing saved tiles
Same command works on `Content/Gfx/tactical/surface/`:
```bash
dotnet run --project Theriapolis.Tools -- tile-analyze \
--dir Content/Gfx/tactical/surface
```
Use this to sweep for the shadow issue before assuming any saved tile is
clean. The history of what's been spot-checked vs not is in §6.
---
## 5. End-to-end recipe: generating one new surface
1. **Read this whole document first.**
2. Verify Pixellab MCP is loaded (`/mcp` in Claude Code shows it).
3. Get the base64 of `Content/Gfx/tactical/surface/grass_0.png` (the
reference image). The previous session has this cached at
`/tmp/grass_0_b64.txt` if it survived.
4. Call `create_tiles_pro` with:
- `description`: include "fully opaque … every single pixel must be
opaque … edge-to-edge" language; avoid directional feature language.
- `style_images`: `[{"base64": "...", "width": 32, "height": 32}]`
- `tile_view: "top-down"`, `tile_depth_ratio: 0`,
`style_options: {color_palette:false, outline:false, detail:true, shading:false}`
5. Note the returned `tile_id`. Schedule a `ScheduleWakeup` for ~480 s.
6. On wakeup, `get_tiles_pro(tile_id)`. If still processing, reschedule
another 240 s. Once complete, download all 16 PNGs from the B2 URLs.
7. Run `tile-analyze --dir <download-dir> --sheet <out-sheet.png>`.
8. Show the contact sheet to the user along with the table. Recommend
3 picks that:
- Pass all detector checks (border 0, opaque 100%, shadow ≤ 30).
- Don't have strong directional features (wheel ruts, plank lines,
centered swirls — these tile poorly).
- Aren't visually identical to each other (variant rotation needs
real variety).
9. After user confirms, save the picks as `<surface>_0/1/2.png` in
`Content/Gfx/tactical/surface/`.
10. Rebuild Desktop (`dotnet build Theriapolis.Desktop`) so the runtime
`bin/Debug/net8.0/Gfx/` copy refreshes.
---
## 6. Known issues / deferred work
- **`rock_0.png` has a -87 shadow on the right edge.** Detected in a
spot-check but not yet rerolled. Use the §3.2 flat-shading params.
`rock_1` and `rock_2` haven't been spot-checked at all — verify before
rerolling so we know if all three need replacing.
- **`mud_0` and `cobble_0` are borderline** (shadow scores in the high
20s). Just under the 30 threshold. Acceptable for now but candidates
for a quality pass later.
- **Marsh history.** The marsh surface took three rerolls:
- v1: came back as ponds with reeds and grey borders (asked for
"reedy growth" — AI placed reeds on a smaller central pond shape).
- v2: came back as transparent decoration sprites (the prompt said
"no reeds no plants" too forcefully — AI made everything except the
moss patches transparent).
- v3 (current): correct after explicit "fully opaque every pixel" wording.
This is the canonical example of the failure modes in §3.3 + §3.5.
---
## 7. Quick reference — current generation params we know work
```python
# Surface tiles (create_tiles_pro)
{
"description": "<see §3.3 — emphasize opacity + edge-to-edge>",
"style_images": '[{"base64": "<grass_0.png base64>", "width": 32, "height": 32}]',
"style_options": '{"color_palette": false, "outline": false, "detail": true, "shading": false}',
"tile_view": "top-down",
"tile_depth_ratio": 0
}
# Decoration sprites (create_map_object)
{
"description": "<see request doc>",
"width": 32, "height": 32,
"view": "high top-down",
"outline": "single color outline",
"shading": "medium shading",
"detail": "medium detail"
}
```
These are the configurations that produced clean tiles after we worked
through the failure modes. Deviate only when you have a reason and you've
re-checked with `tile-analyze`.
---
## 8. Architecture context
The tactical render path that consumes these tiles is:
1. [`TacticalChunkGen`](Theriapolis.Core/Tactical/TacticalChunkGen.cs)
stamps `TacticalSurface` and `TacticalDeco` enum values into each
tactical tile based on biome, road polylines, settlement footprints.
2. [`TacticalAtlas`](Theriapolis.Game/Rendering/TacticalAtlas.cs)
loads PNGs from `Content/Gfx/tactical/{surface,deco}/` keyed by
lowercased enum name. Falls back to procedural color squares for
anything missing.
3. [`TacticalRenderer`](Theriapolis.Game/Rendering/TacticalRenderer.cs)
draws each tile's surface texture, then its decoration if any, in
chunk-by-chunk passes.
The art request document for human artists is
[`theriapolis-tactical-tile-art-request.md`](theriapolis-tactical-tile-art-request.md)
— it's the same content list we're satisfying with Pixellab, just framed
for a human painter. Keep both docs in sync if the surface or decoration
enums change.