# 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 1–3 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 50–90 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 30–80%
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 (0–4) 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, 30–70% 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.
