Replace battle maps 51 and 52 with hand-painted assets

This commit is contained in:
2026-07-09 21:29:54 +09:00
parent 03ec1ca86f
commit 0fd87dcb98
11 changed files with 33 additions and 157 deletions

View File

@@ -2,7 +2,7 @@
## Scope
The first fifty 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.
The first fifty-two 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
@@ -18,6 +18,7 @@ The first fifty campaign battles now use the same hand-painted tactical map pipe
- 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.
- 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.
@@ -77,6 +78,8 @@ python scripts/generate-handpaint-map-sample.py --map forty-seventh --webp-quali
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
```
Comparison example:
@@ -137,6 +140,8 @@ python scripts/generate-handpaint-map-sample.py --map thirtieth --tile-size 192
- `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`
## QA Outputs
@@ -238,5 +243,9 @@ python scripts/generate-handpaint-map-sample.py --map thirtieth --tile-size 192
- `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`
Browser screenshots are saved per verification run after build/deploy.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

View File

@@ -20,6 +20,7 @@ LARGE_MAP_TILE_SIZE = 192
VERY_LARGE_MAP_TILE_SIZE = 176
ULTRA_LARGE_MAP_TILE_SIZE = 160
EXTREME_LARGE_MAP_TILE_SIZE = 152
MASSIVE_LARGE_MAP_TILE_SIZE = 144
TILE = DEFAULT_TILE_SIZE
RNG = random.Random(240704)
@@ -499,6 +500,26 @@ MAP_CONFIGS = {
"seed": 241194,
"tile_size": EXTREME_LARGE_MAP_TILE_SIZE,
},
"fifty-first": {
"slug": "fifty-first-battle-map",
"terrain_export": "fiftyFirstBattleMap",
"units_export": "fiftyFirstBattleUnits",
"ally_positions_export": "fiftyFirstBattleAllyPositions",
"out": BATTLE_IMAGE_DIR / "fifty-first-battle-map.webp",
"before": BATTLE_IMAGE_DIR / "fifty-first-battle-map.svg",
"seed": 241204,
"tile_size": MASSIVE_LARGE_MAP_TILE_SIZE,
},
"fifty-second": {
"slug": "fifty-second-battle-map",
"terrain_export": "fiftySecondBattleMap",
"units_export": "fiftySecondBattleUnits",
"ally_positions_export": "fiftySecondBattleAllyPositions",
"out": BATTLE_IMAGE_DIR / "fifty-second-battle-map.webp",
"before": BATTLE_IMAGE_DIR / "fifty-second-battle-map.svg",
"seed": 241214,
"tile_size": MASSIVE_LARGE_MAP_TILE_SIZE,
},
}

View File

@@ -1,77 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="5100" height="4200" viewBox="0 0 10200 8400" role="img" aria-label="Nanman fourth capture battlefield">
<defs>
<linearGradient id="ground" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#958657"/>
<stop offset=".48" stop-color="#766c45"/>
<stop offset="1" stop-color="#3e4f2d"/>
</linearGradient>
<radialGradient id="marsh" cx=".52" cy=".56" r=".7">
<stop offset="0" stop-color="#59764f"/>
<stop offset=".54" stop-color="#334d37"/>
<stop offset="1" stop-color="#172d2d"/>
</radialGradient>
<filter id="softNoise">
<feTurbulence type="fractalNoise" baseFrequency=".018" numOctaves="4" seed="51"/>
<feColorMatrix type="saturate" values=".45"/>
<feBlend mode="multiply" in2="SourceGraphic"/>
</filter>
<pattern id="reed" width="220" height="220" patternUnits="userSpaceOnUse">
<path d="M42 218c30-90 42-130 22-214M106 220c-6-82 12-142 58-202M176 218c-38-72-28-128 8-206" stroke="#283c24" stroke-width="18" stroke-linecap="round" opacity=".35"/>
</pattern>
<pattern id="ridge" width="340" height="280" patternUnits="userSpaceOnUse">
<path d="M0 210c80-110 154-164 252-176 46-6 78-24 112-50" fill="none" stroke="#2b241a" stroke-width="34" opacity=".42"/>
<path d="M16 226c68-80 140-118 226-122" fill="none" stroke="#b08a4b" stroke-width="18" opacity=".3"/>
</pattern>
</defs>
<rect width="10200" height="8400" fill="url(#ground)"/>
<rect width="10200" height="8400" fill="#000" opacity=".06" filter="url(#softNoise)"/>
<path d="M0 7600C1120 7280 1680 7750 2760 7390c870-288 1084-990 2030-920 970 70 1230 760 2290 590 1070-172 1550-1040 3120-742v2082H0z" fill="url(#marsh)"/>
<path d="M6280 0c-310 1020-240 1990 60 2800 350 948 470 1658 116 2560-248 612-140 1220 390 3040h-720c-606-1432-764-2442-430-3242 304-730 202-1320-130-2202-318-840-410-1848-104-2956z" fill="#233f44" opacity=".92"/>
<path d="M6520 0c-224 980-94 1928 218 2760 322 860 396 1574 90 2312-258 622-170 1308 336 3328h-278c-436-1636-574-2608-240-3374 304-700 214-1348-102-2198-320-858-452-1770-224-2828z" fill="#6b9da3" opacity=".5"/>
<path d="M0 0h3380v3200H0zM3220 0h3860v2480H3220zM7080 0h3120v3480H7080zM8520 2900h1680v2460H8520z" fill="url(#ridge)" opacity=".82"/>
<path d="M180 4860c1620-920 3560-680 5260-720 1730-42 3040-440 4580-1220v720c-1550 734-2900 1120-4580 1176-1648 54-3460-92-5020 744z" fill="#3d5f2b" opacity=".72"/>
<path d="M920 6550c1160-540 2520-550 4020-410 1260 118 2420 310 3950-300v690c-1490 512-2720 354-3970 240-1560-142-2730-118-4000 458z" fill="#496a2d" opacity=".7"/>
<path d="M960 6580C2380 6040 3970 6060 5480 5920c1390-128 2530-420 3740-1110" fill="none" stroke="#c2a05a" stroke-width="280" opacity=".82"/>
<path d="M2820 5600c760-720 1600-1080 2540-1080h3600" fill="none" stroke="#b89552" stroke-width="260" opacity=".8"/>
<path d="M4320 7040c160-1260 20-2420 420-3500" fill="none" stroke="#ad8c4e" stroke-width="240" opacity=".78"/>
<path d="M7780 4740c-470 580-496 1170-80 1760 420 596 310 1110-330 1620" fill="none" stroke="#b18d4a" stroke-width="220" opacity=".75"/>
<g fill="url(#reed)" opacity=".7">
<rect x="5880" y="2480" width="920" height="4980"/>
<rect x="430" y="6680" width="2680" height="1560"/>
<rect x="7480" y="5840" width="1620" height="1550"/>
</g>
<g fill="#274d2e" opacity=".78">
<ellipse cx="1920" cy="5160" rx="920" ry="560"/>
<ellipse cx="3520" cy="4820" rx="940" ry="510"/>
<ellipse cx="5720" cy="4040" rx="1050" ry="560"/>
<ellipse cx="8120" cy="3680" rx="1040" ry="570"/>
<ellipse cx="8540" cy="5940" rx="900" ry="500"/>
</g>
<g fill="#a06928" opacity=".86">
<rect x="2460" y="5220" width="760" height="520" rx="40"/>
<rect x="3900" y="4300" width="840" height="540" rx="40"/>
<rect x="7360" y="4860" width="880" height="560" rx="40"/>
<rect x="8060" y="3220" width="860" height="520" rx="40"/>
</g>
<g fill="#d1b76c" stroke="#49331c" stroke-width="42">
<path d="M2760 5480l300-230 300 230v280h-600z"/>
<path d="M4200 4560l320-250 320 250v300h-640z"/>
<path d="M7660 5120l330-260 330 260v330h-660z"/>
<path d="M8360 3480l300-240 300 240v300h-600z"/>
</g>
<g fill="#523527" stroke="#d19042" stroke-width="50">
<rect x="5280" y="3270" width="840" height="680" rx="48"/>
<rect x="6740" y="5060" width="900" height="700" rx="48"/>
<rect x="8620" y="4240" width="940" height="720" rx="48"/>
</g>
<g fill="#e08b31" opacity=".78">
<circle cx="5700" cy="3600" r="130"/>
<circle cx="7140" cy="5420" r="120"/>
<circle cx="9020" cy="4580" r="140"/>
</g>
<g fill="none" stroke="#1b1f16" stroke-width="34" opacity=".32">
<path d="M0 4800c1840-940 4000-770 5920-830 1430-44 2770-420 4280-1210"/>
<path d="M600 6460c1360-490 2680-450 4180-330 1380 110 2660 360 4360-330"/>
<path d="M6500 0c-360 1360 116 2820 420 3980 230 880-180 1660-20 2620"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 MiB

View File

@@ -1,77 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="5200" height="4300" viewBox="0 0 10400 8600" role="img" aria-label="Nanman fifth capture battlefield">
<defs>
<linearGradient id="ground" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#8d7f52"/>
<stop offset=".48" stop-color="#6f6843"/>
<stop offset="1" stop-color="#34482c"/>
</linearGradient>
<radialGradient id="deepJungle" cx=".52" cy=".55" r=".72">
<stop offset="0" stop-color="#476d3e"/>
<stop offset=".58" stop-color="#28472f"/>
<stop offset="1" stop-color="#16272c"/>
</radialGradient>
<filter id="softNoise">
<feTurbulence type="fractalNoise" baseFrequency=".017" numOctaves="4" seed="52"/>
<feColorMatrix type="saturate" values=".42"/>
<feBlend mode="multiply" in2="SourceGraphic"/>
</filter>
<pattern id="bamboo" width="260" height="240" patternUnits="userSpaceOnUse">
<path d="M40 240c30-100 20-154 74-234M126 240c-12-90 26-148 76-226M214 240c-40-84-18-142 10-232" stroke="#23371e" stroke-width="18" stroke-linecap="round" opacity=".38"/>
</pattern>
<pattern id="stone" width="360" height="300" patternUnits="userSpaceOnUse">
<path d="M0 230c70-112 160-176 270-190 52-6 80-22 116-48" fill="none" stroke="#261f18" stroke-width="32" opacity=".4"/>
<path d="M22 238c72-74 142-112 238-120" fill="none" stroke="#ae8950" stroke-width="16" opacity=".28"/>
</pattern>
</defs>
<rect width="10400" height="8600" fill="url(#ground)"/>
<rect width="10400" height="8600" fill="#000" opacity=".07" filter="url(#softNoise)"/>
<path d="M0 8020c1160-360 1720 160 2860-230 920-314 1130-1080 2140-980 1060 104 1290 820 2420 604 1110-210 1580-1120 2980-852v2038H0z" fill="url(#deepJungle)" opacity=".96"/>
<path d="M6680 0c-400 980-358 1920-22 2830 356 964 380 1660-26 2480-340 686-272 1360 300 3290h-760c-624-1510-724-2528-320-3360 360-744 304-1330-40-2240-326-862-430-1780-112-3000z" fill="#203f43" opacity=".92"/>
<path d="M6920 0c-270 940-132 1840 190 2690 326 864 330 1512-28 2248-292 604-188 1350 374 3662h-286c-470-1740-590-2730-224-3480 330-678 282-1254-36-2110-330-890-464-1740-206-3010z" fill="#6ea1a2" opacity=".46"/>
<path d="M0 0h3480v3380H0zM3330 0h3980v2580H3330zM7280 0h3120v3600H7280zM8700 3100h1700v2620H8700z" fill="url(#stone)" opacity=".82"/>
<path d="M260 5040c1640-920 3600-700 5320-730 1760-30 3120-460 4640-1300v730c-1520 800-2940 1190-4660 1230-1660 38-3520-88-5000 772z" fill="#3b5d2a" opacity=".74"/>
<path d="M1160 6840c1200-590 2560-590 4080-440 1330 132 2500 330 4100-360v720c-1550 588-2800 400-4110 270-1600-160-2820-126-4070 510z" fill="#496d30" opacity=".72"/>
<path d="M1040 6790c1480-590 3080-540 4620-710 1460-160 2680-500 3860-1280" fill="none" stroke="#c2a15d" stroke-width="270" opacity=".84"/>
<path d="M3020 5850c820-760 1720-1140 2660-1120h3740" fill="none" stroke="#ba9654" stroke-width="250" opacity=".82"/>
<path d="M4540 7220c150-1260 10-2500 450-3640" fill="none" stroke="#b18e50" stroke-width="230" opacity=".78"/>
<path d="M8200 4840c-570 620-620 1260-142 1920 406 560 250 1080-420 1560" fill="none" stroke="#b18d4a" stroke-width="220" opacity=".75"/>
<g fill="url(#bamboo)" opacity=".72">
<rect x="5960" y="2480" width="1080" height="5260"/>
<rect x="420" y="6860" width="2880" height="1600"/>
<rect x="7700" y="6000" width="1760" height="1600"/>
</g>
<g fill="#24492d" opacity=".8">
<ellipse cx="2100" cy="5320" rx="980" ry="570"/>
<ellipse cx="3720" cy="4960" rx="1020" ry="540"/>
<ellipse cx="5900" cy="4140" rx="1120" ry="600"/>
<ellipse cx="8340" cy="3760" rx="1080" ry="590"/>
<ellipse cx="8780" cy="6100" rx="960" ry="520"/>
</g>
<g fill="#98652c" opacity=".88">
<rect x="2600" y="5420" width="780" height="540" rx="42"/>
<rect x="4100" y="4480" width="860" height="560" rx="42"/>
<rect x="7500" y="5000" width="900" height="580" rx="42"/>
<rect x="8380" y="3500" width="900" height="540" rx="42"/>
</g>
<g fill="#d2ba73" stroke="#49331c" stroke-width="42">
<path d="M2900 5700l310-242 318 242v294h-628z"/>
<path d="M4420 4750l330-260 334 260v310h-664z"/>
<path d="M7820 5280l342-270 344 270v340h-686z"/>
<path d="M8690 3780l318-250 318 250v306h-636z"/>
</g>
<g fill="#52352a" stroke="#d38d3f" stroke-width="48">
<rect x="5480" y="3380" width="860" height="700" rx="52"/>
<rect x="6960" y="5200" width="940" height="720" rx="52"/>
<rect x="8840" y="4400" width="960" height="740" rx="52"/>
</g>
<g fill="#e49333" opacity=".75">
<circle cx="5900" cy="3730" r="132"/>
<circle cx="7380" cy="5560" r="122"/>
<circle cx="9270" cy="4760" r="144"/>
</g>
<g fill="none" stroke="#171d15" stroke-width="34" opacity=".34">
<path d="M0 5000c1860-980 4080-820 6040-870 1460-38 2860-470 4360-1280"/>
<path d="M700 6680c1420-530 2760-500 4300-350 1420 138 2740 370 4500-360"/>
<path d="M6820 0c-420 1380 92 2860 420 4040 260 940-210 1710-12 2700"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 MiB

View File

@@ -2,8 +2,8 @@ import eighthBattleMapUrl from '../../assets/images/battle/eighth-battle-map.web
import eighteenthBattleMapUrl from '../../assets/images/battle/eighteenth-battle-map.webp';
import eleventhBattleMapUrl from '../../assets/images/battle/eleventh-battle-map.webp';
import fiftiethBattleMapUrl from '../../assets/images/battle/fiftieth-battle-map.webp';
import fiftyFirstBattleMapUrl from '../../assets/images/battle/fifty-first-battle-map.svg';
import fiftySecondBattleMapUrl from '../../assets/images/battle/fifty-second-battle-map.svg';
import fiftyFirstBattleMapUrl from '../../assets/images/battle/fifty-first-battle-map.webp';
import fiftySecondBattleMapUrl from '../../assets/images/battle/fifty-second-battle-map.webp';
import fiftyThirdBattleMapUrl from '../../assets/images/battle/fifty-third-battle-map.svg';
import fiftyFourthBattleMapUrl from '../../assets/images/battle/fifty-fourth-battle-map.svg';
import fiftyFifthBattleMapUrl from '../../assets/images/battle/fifty-fifth-battle-map.svg';