Files
heros_web/docs/battle-map-terrain-atlas-pipeline.md

312 lines
19 KiB
Markdown

# Hand-Painted Tactical Map Pipeline
## Scope
All sixty-six campaign battles now use the same hand-painted tactical map pipeline. The renderer still reads the original scenario tile coordinates and battle logic; only the map texture asset changes from flat/vector map art to low-contrast raster terrain.
## Art Direction
- Units must read before the terrain at 100% desktop browser scale.
- Terrain uses muted greens, ochre roads, dark blue-green water, and compact structure markers.
- Roads, forests, rivers, camps, villages, cliffs, and forts should be recognizable by silhouette and texture, not by high saturation.
- The grid remains subtle and embedded in the paint grade, so it helps targeting without becoming the main visual layer.
## Resolution Policy
- Battles 1-25 keep the original 224px hand-painted tile target.
- Large maps starting at battle 26 use a 192px tile target through `LARGE_MAP_TILE_SIZE` in the generator.
- Very large maps can use a 176px tile target through `VERY_LARGE_MAP_TILE_SIZE` when WebP encoding or browser memory pressure becomes the limiting factor.
- Ultra large maps can use a 160px tile target through `ULTRA_LARGE_MAP_TILE_SIZE` when 176px output exceeds WebP encoder limits.
- Extreme large maps can use a 152px tile target through `EXTREME_LARGE_MAP_TILE_SIZE` when 160px output exceeds WebP encoder limits.
- Massive maps can use a 144px tile target through `MASSIVE_LARGE_MAP_TILE_SIZE` when 152px output approaches the WebP encoder edge.
- Colossal maps can use a 136px tile target through `COLOSSAL_LARGE_MAP_TILE_SIZE` when 144px output hits WebP encoding error 6.
- Gigantic maps can use a 128px tile target through `GIGANTIC_LARGE_MAP_TILE_SIZE` when 136px output would exceed the known WebP encoder comfort range.
- Immense maps can use a 120px tile target through `IMMENSE_LARGE_MAP_TILE_SIZE` when 128px output hits WebP encoding error 6.
- Vast maps can use a 112px tile target through `VAST_LARGE_MAP_TILE_SIZE` when 120px output becomes unreliable during WebP encoding.
- Re-evaluate the tile target before a generated map's longest edge approaches roughly 16,384px at 192px per tile.
- Use `--tile-size` only for controlled comparisons; commit the selected production size in the map config.
## Generator
`scripts/generate-handpaint-map-sample.py` reads terrain and unit positions from `src/game/data/scenario.ts`, then outputs a WebP map and documentation previews. Map configs can set `tile_size`; otherwise the default is 224px.
Example:
```powershell
python scripts/generate-handpaint-map-sample.py --map second --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map third --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fourth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fifth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map sixth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map seventh --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map eighth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map ninth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map tenth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map eleventh --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map twelfth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map thirteenth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fourteenth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fifteenth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map sixteenth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map seventeenth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map eighteenth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map nineteenth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map twentieth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map twenty-first --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map twenty-second --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map twenty-third --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map twenty-fourth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map twenty-fifth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map twenty-sixth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map twenty-seventh --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map twenty-eighth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map twenty-ninth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map thirtieth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map thirty-first --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map thirty-second --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map thirty-third --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map thirty-fourth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map thirty-fifth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map thirty-sixth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map thirty-seventh --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map thirty-eighth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map thirty-ninth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fortieth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map forty-first --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map forty-second --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map forty-third --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map forty-fourth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map forty-fifth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map forty-sixth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map forty-seventh --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map forty-eighth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map forty-ninth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fiftieth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fifty-first --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fifty-second --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fifty-third --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fifty-fourth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fifty-fifth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fifty-sixth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fifty-seventh --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fifty-eighth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map fifty-ninth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map sixtieth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map sixty-first --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map sixty-second --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map sixty-third --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map sixty-fourth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map sixty-fifth --webp-quality 90
python scripts/generate-handpaint-map-sample.py --map sixty-sixth --webp-quality 90
```
Comparison example:
```powershell
python scripts/generate-handpaint-map-sample.py --map thirtieth --tile-size 192 --out tmp\thirtieth-battle-map-192-test.webp --comparison tmp\thirtieth-battle-map-192-test-before-after.png --preview tmp\thirtieth-battle-map-192-test-preview.png --webp-quality 90
```
## Current Assets
- `src/assets/images/battle/first-battle-map.webp`
- `src/assets/images/battle/second-battle-map.webp`
- `src/assets/images/battle/third-battle-map.webp`
- `src/assets/images/battle/fourth-battle-map.webp`
- `src/assets/images/battle/fifth-battle-map.webp`
- `src/assets/images/battle/sixth-battle-map.webp`
- `src/assets/images/battle/seventh-battle-map.webp`
- `src/assets/images/battle/eighth-battle-map.webp`
- `src/assets/images/battle/ninth-battle-map.webp`
- `src/assets/images/battle/tenth-battle-map.webp`
- `src/assets/images/battle/eleventh-battle-map.webp`
- `src/assets/images/battle/twelfth-battle-map.webp`
- `src/assets/images/battle/thirteenth-battle-map.webp`
- `src/assets/images/battle/fourteenth-battle-map.webp`
- `src/assets/images/battle/fifteenth-battle-map.webp`
- `src/assets/images/battle/sixteenth-battle-map.webp`
- `src/assets/images/battle/seventeenth-battle-map.webp`
- `src/assets/images/battle/eighteenth-battle-map.webp`
- `src/assets/images/battle/nineteenth-battle-map.webp`
- `src/assets/images/battle/twentieth-battle-map.webp`
- `src/assets/images/battle/twenty-first-battle-map.webp`
- `src/assets/images/battle/twenty-second-battle-map.webp`
- `src/assets/images/battle/twenty-third-battle-map.webp`
- `src/assets/images/battle/twenty-fourth-battle-map.webp`
- `src/assets/images/battle/twenty-fifth-battle-map.webp`
- `src/assets/images/battle/twenty-sixth-battle-map.webp`
- `src/assets/images/battle/twenty-seventh-battle-map.webp`
- `src/assets/images/battle/twenty-eighth-battle-map.webp`
- `src/assets/images/battle/twenty-ninth-battle-map.webp`
- `src/assets/images/battle/thirtieth-battle-map.webp`
- `src/assets/images/battle/thirty-first-battle-map.webp`
- `src/assets/images/battle/thirty-second-battle-map.webp`
- `src/assets/images/battle/thirty-third-battle-map.webp`
- `src/assets/images/battle/thirty-fourth-battle-map.webp`
- `src/assets/images/battle/thirty-fifth-battle-map.webp`
- `src/assets/images/battle/thirty-sixth-battle-map.webp`
- `src/assets/images/battle/thirty-seventh-battle-map.webp`
- `src/assets/images/battle/thirty-eighth-battle-map.webp`
- `src/assets/images/battle/thirty-ninth-battle-map.webp`
- `src/assets/images/battle/fortieth-battle-map.webp`
- `src/assets/images/battle/forty-first-battle-map.webp`
- `src/assets/images/battle/forty-second-battle-map.webp`
- `src/assets/images/battle/forty-third-battle-map.webp`
- `src/assets/images/battle/forty-fourth-battle-map.webp`
- `src/assets/images/battle/forty-fifth-battle-map.webp`
- `src/assets/images/battle/forty-sixth-battle-map.webp`
- `src/assets/images/battle/forty-seventh-battle-map.webp`
- `src/assets/images/battle/forty-eighth-battle-map.webp`
- `src/assets/images/battle/forty-ninth-battle-map.webp`
- `src/assets/images/battle/fiftieth-battle-map.webp`
- `src/assets/images/battle/fifty-first-battle-map.webp`
- `src/assets/images/battle/fifty-second-battle-map.webp`
- `src/assets/images/battle/fifty-third-battle-map.webp`
- `src/assets/images/battle/fifty-fourth-battle-map.webp`
- `src/assets/images/battle/fifty-fifth-battle-map.webp`
- `src/assets/images/battle/fifty-sixth-battle-map.webp`
- `src/assets/images/battle/fifty-seventh-battle-map.webp`
- `src/assets/images/battle/fifty-eighth-battle-map.webp`
- `src/assets/images/battle/fifty-ninth-battle-map.webp`
- `src/assets/images/battle/sixtieth-battle-map.webp`
- `src/assets/images/battle/sixty-first-battle-map.webp`
- `src/assets/images/battle/sixty-second-battle-map.webp`
- `src/assets/images/battle/sixty-third-battle-map.webp`
- `src/assets/images/battle/sixty-fourth-battle-map.webp`
- `src/assets/images/battle/sixty-fifth-battle-map.webp`
- `src/assets/images/battle/sixty-sixth-battle-map.webp`
## QA Outputs
- `docs/second-battle-map-handpaint-before-after.png`
- `docs/second-battle-map-handpaint-preview.png`
- `docs/third-battle-map-handpaint-before-after.png`
- `docs/third-battle-map-handpaint-preview.png`
- `docs/fourth-battle-map-handpaint-before-after.png`
- `docs/fourth-battle-map-handpaint-preview.png`
- `docs/fifth-battle-map-handpaint-before-after.png`
- `docs/fifth-battle-map-handpaint-preview.png`
- `docs/sixth-battle-map-handpaint-before-after.png`
- `docs/sixth-battle-map-handpaint-preview.png`
- `docs/seventh-battle-map-handpaint-before-after.png`
- `docs/seventh-battle-map-handpaint-preview.png`
- `docs/eighth-battle-map-handpaint-before-after.png`
- `docs/eighth-battle-map-handpaint-preview.png`
- `docs/ninth-battle-map-handpaint-before-after.png`
- `docs/ninth-battle-map-handpaint-preview.png`
- `docs/tenth-battle-map-handpaint-before-after.png`
- `docs/tenth-battle-map-handpaint-preview.png`
- `docs/eleventh-battle-map-handpaint-before-after.png`
- `docs/eleventh-battle-map-handpaint-preview.png`
- `docs/twelfth-battle-map-handpaint-before-after.png`
- `docs/twelfth-battle-map-handpaint-preview.png`
- `docs/thirteenth-battle-map-handpaint-before-after.png`
- `docs/thirteenth-battle-map-handpaint-preview.png`
- `docs/fourteenth-battle-map-handpaint-before-after.png`
- `docs/fourteenth-battle-map-handpaint-preview.png`
- `docs/fifteenth-battle-map-handpaint-before-after.png`
- `docs/fifteenth-battle-map-handpaint-preview.png`
- `docs/sixteenth-battle-map-handpaint-before-after.png`
- `docs/sixteenth-battle-map-handpaint-preview.png`
- `docs/seventeenth-battle-map-handpaint-before-after.png`
- `docs/seventeenth-battle-map-handpaint-preview.png`
- `docs/eighteenth-battle-map-handpaint-before-after.png`
- `docs/eighteenth-battle-map-handpaint-preview.png`
- `docs/nineteenth-battle-map-handpaint-before-after.png`
- `docs/nineteenth-battle-map-handpaint-preview.png`
- `docs/twentieth-battle-map-handpaint-before-after.png`
- `docs/twentieth-battle-map-handpaint-preview.png`
- `docs/twenty-first-battle-map-handpaint-before-after.png`
- `docs/twenty-first-battle-map-handpaint-preview.png`
- `docs/twenty-second-battle-map-handpaint-before-after.png`
- `docs/twenty-second-battle-map-handpaint-preview.png`
- `docs/twenty-third-battle-map-handpaint-before-after.png`
- `docs/twenty-third-battle-map-handpaint-preview.png`
- `docs/twenty-fourth-battle-map-handpaint-before-after.png`
- `docs/twenty-fourth-battle-map-handpaint-preview.png`
- `docs/twenty-fifth-battle-map-handpaint-before-after.png`
- `docs/twenty-fifth-battle-map-handpaint-preview.png`
- `docs/twenty-sixth-battle-map-handpaint-before-after.png`
- `docs/twenty-sixth-battle-map-handpaint-preview.png`
- `docs/twenty-seventh-battle-map-handpaint-before-after.png`
- `docs/twenty-seventh-battle-map-handpaint-preview.png`
- `docs/twenty-eighth-battle-map-handpaint-before-after.png`
- `docs/twenty-eighth-battle-map-handpaint-preview.png`
- `docs/twenty-ninth-battle-map-handpaint-before-after.png`
- `docs/twenty-ninth-battle-map-handpaint-preview.png`
- `docs/thirtieth-battle-map-handpaint-before-after.png`
- `docs/thirtieth-battle-map-handpaint-preview.png`
- `docs/thirty-first-battle-map-handpaint-before-after.png`
- `docs/thirty-first-battle-map-handpaint-preview.png`
- `docs/thirty-second-battle-map-handpaint-before-after.png`
- `docs/thirty-second-battle-map-handpaint-preview.png`
- `docs/thirty-third-battle-map-handpaint-before-after.png`
- `docs/thirty-third-battle-map-handpaint-preview.png`
- `docs/thirty-fourth-battle-map-handpaint-before-after.png`
- `docs/thirty-fourth-battle-map-handpaint-preview.png`
- `docs/thirty-fifth-battle-map-handpaint-before-after.png`
- `docs/thirty-fifth-battle-map-handpaint-preview.png`
- `docs/thirty-sixth-battle-map-handpaint-before-after.png`
- `docs/thirty-sixth-battle-map-handpaint-preview.png`
- `docs/thirty-seventh-battle-map-handpaint-before-after.png`
- `docs/thirty-seventh-battle-map-handpaint-preview.png`
- `docs/thirty-eighth-battle-map-handpaint-before-after.png`
- `docs/thirty-eighth-battle-map-handpaint-preview.png`
- `docs/thirty-ninth-battle-map-handpaint-before-after.png`
- `docs/thirty-ninth-battle-map-handpaint-preview.png`
- `docs/fortieth-battle-map-handpaint-before-after.png`
- `docs/fortieth-battle-map-handpaint-preview.png`
- `docs/forty-first-battle-map-handpaint-before-after.png`
- `docs/forty-first-battle-map-handpaint-preview.png`
- `docs/forty-second-battle-map-handpaint-before-after.png`
- `docs/forty-second-battle-map-handpaint-preview.png`
- `docs/forty-third-battle-map-handpaint-before-after.png`
- `docs/forty-third-battle-map-handpaint-preview.png`
- `docs/forty-fourth-battle-map-handpaint-before-after.png`
- `docs/forty-fourth-battle-map-handpaint-preview.png`
- `docs/forty-fifth-battle-map-handpaint-before-after.png`
- `docs/forty-fifth-battle-map-handpaint-preview.png`
- `docs/forty-sixth-battle-map-handpaint-before-after.png`
- `docs/forty-sixth-battle-map-handpaint-preview.png`
- `docs/forty-seventh-battle-map-handpaint-before-after.png`
- `docs/forty-seventh-battle-map-handpaint-preview.png`
- `docs/forty-eighth-battle-map-handpaint-before-after.png`
- `docs/forty-eighth-battle-map-handpaint-preview.png`
- `docs/forty-ninth-battle-map-handpaint-before-after.png`
- `docs/forty-ninth-battle-map-handpaint-preview.png`
- `docs/fiftieth-battle-map-handpaint-before-after.png`
- `docs/fiftieth-battle-map-handpaint-preview.png`
- `docs/fifty-first-battle-map-handpaint-before-after.png`
- `docs/fifty-first-battle-map-handpaint-preview.png`
- `docs/fifty-second-battle-map-handpaint-before-after.png`
- `docs/fifty-second-battle-map-handpaint-preview.png`
- `docs/fifty-third-battle-map-handpaint-before-after.png`
- `docs/fifty-third-battle-map-handpaint-preview.png`
- `docs/fifty-fourth-battle-map-handpaint-before-after.png`
- `docs/fifty-fourth-battle-map-handpaint-preview.png`
- `docs/fifty-fifth-battle-map-handpaint-before-after.png`
- `docs/fifty-fifth-battle-map-handpaint-preview.png`
- `docs/fifty-sixth-battle-map-handpaint-before-after.png`
- `docs/fifty-sixth-battle-map-handpaint-preview.png`
- `docs/fifty-seventh-battle-map-handpaint-before-after.png`
- `docs/fifty-seventh-battle-map-handpaint-preview.png`
- `docs/fifty-eighth-battle-map-handpaint-before-after.png`
- `docs/fifty-eighth-battle-map-handpaint-preview.png`
- `docs/fifty-ninth-battle-map-handpaint-before-after.png`
- `docs/fifty-ninth-battle-map-handpaint-preview.png`
- `docs/sixtieth-battle-map-handpaint-before-after.png`
- `docs/sixtieth-battle-map-handpaint-preview.png`
- `docs/sixty-first-battle-map-handpaint-before-after.png`
- `docs/sixty-first-battle-map-handpaint-preview.png`
- `docs/sixty-second-battle-map-handpaint-before-after.png`
- `docs/sixty-second-battle-map-handpaint-preview.png`
- `docs/sixty-third-battle-map-handpaint-before-after.png`
- `docs/sixty-third-battle-map-handpaint-preview.png`
- `docs/sixty-fourth-battle-map-handpaint-before-after.png`
- `docs/sixty-fourth-battle-map-handpaint-preview.png`
- `docs/sixty-fifth-battle-map-handpaint-before-after.png`
- `docs/sixty-fifth-battle-map-handpaint-preview.png`
- `docs/sixty-sixth-battle-map-handpaint-before-after.png`
- `docs/sixty-sixth-battle-map-handpaint-preview.png`
Browser screenshots are saved per verification run after build/deploy.