Replace battle maps 45 and 46 with hand-painted assets

This commit is contained in:
2026-07-09 20:32:09 +09:00
parent 3d858fd93d
commit 94b77239d4
11 changed files with 33 additions and 170 deletions

View File

@@ -2,7 +2,7 @@
## Scope
The first forty-four 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 forty-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
@@ -16,6 +16,7 @@ The first forty-four campaign battles now use the same hand-painted tactical map
- 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.
- 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.
@@ -69,6 +70,8 @@ python scripts/generate-handpaint-map-sample.py --map forty-first --webp-quality
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
```
Comparison example:
@@ -123,6 +126,8 @@ python scripts/generate-handpaint-map-sample.py --map thirtieth --tile-size 192
- `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`
## QA Outputs
@@ -212,5 +217,9 @@ python scripts/generate-handpaint-map-sample.py --map thirtieth --tile-size 192
- `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`
Browser screenshots are saved per verification run after build/deploy.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@@ -18,6 +18,7 @@ DOCS_DIR = ROOT / "docs"
DEFAULT_TILE_SIZE = 224
LARGE_MAP_TILE_SIZE = 192
VERY_LARGE_MAP_TILE_SIZE = 176
ULTRA_LARGE_MAP_TILE_SIZE = 160
TILE = DEFAULT_TILE_SIZE
RNG = random.Random(240704)
@@ -437,6 +438,26 @@ MAP_CONFIGS = {
"seed": 241134,
"tile_size": VERY_LARGE_MAP_TILE_SIZE,
},
"forty-fifth": {
"slug": "forty-fifth-battle-map",
"terrain_export": "fortyFifthBattleMap",
"units_export": "fortyFifthBattleUnits",
"ally_positions_export": "fortyFifthBattleAllyPositions",
"out": BATTLE_IMAGE_DIR / "forty-fifth-battle-map.webp",
"before": BATTLE_IMAGE_DIR / "forty-fifth-battle-map.svg",
"seed": 241144,
"tile_size": ULTRA_LARGE_MAP_TILE_SIZE,
},
"forty-sixth": {
"slug": "forty-sixth-battle-map",
"terrain_export": "fortySixthBattleMap",
"units_export": "fortySixthBattleUnits",
"ally_positions_export": "fortySixthBattleAllyPositions",
"out": BATTLE_IMAGE_DIR / "forty-sixth-battle-map.webp",
"before": BATTLE_IMAGE_DIR / "forty-sixth-battle-map.svg",
"seed": 241154,
"tile_size": ULTRA_LARGE_MAP_TILE_SIZE,
},
}

View File

@@ -1,73 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9000" height="7200" viewBox="0 0 9000 7200">
<defs>
<linearGradient id="ground" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#b99c64"/>
<stop offset="0.42" stop-color="#9f8d58"/>
<stop offset="1" stop-color="#6e7a4b"/>
</linearGradient>
<pattern id="grain" width="86" height="86" patternUnits="userSpaceOnUse">
<path d="M0 42h86M42 0v86" stroke="#2f3a25" stroke-width="5" opacity=".08"/>
<circle cx="18" cy="21" r="7" fill="#e0c37a" opacity=".18"/>
<circle cx="63" cy="55" r="5" fill="#314521" opacity=".16"/>
<path d="M4 76c18-12 38-12 62 0" stroke="#5d6f36" stroke-width="5" opacity=".16" fill="none"/>
</pattern>
<filter id="softShadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="22" stdDeviation="20" flood-color="#1b170f" flood-opacity=".34"/>
</filter>
</defs>
<rect width="9000" height="7200" fill="url(#ground)"/>
<rect width="9000" height="7200" fill="url(#grain)"/>
<g opacity=".92">
<path d="M0 6040c1180-250 2050-90 3120-110 900-20 1510-300 2310-210 950 105 1850 550 3570 230v1250H0z" fill="#315c73"/>
<path d="M0 6440c1260-170 2020-20 3150-60 860-30 1500-250 2240-170 990 105 1950 420 3610 210v780H0z" fill="#213f61" opacity=".74"/>
<path d="M420 6040c560 180 1140 190 1700 80M4560 5800c610 110 1220 260 1890 430M6450 6310c810 120 1530 70 2430-70" stroke="#87b8b9" stroke-width="26" opacity=".24" fill="none"/>
</g>
<g filter="url(#softShadow)">
<path d="M0 0h2360c-120 540-430 1180-900 1610C980 2050 570 2470 0 2820z" fill="#5b5f37"/>
<path d="M3240 0h2620c-110 480-390 900-790 1200-520 390-1110 520-1870 640z" fill="#5b5f37"/>
<path d="M6470 0h2530v2660c-480-110-900-340-1230-710-380-430-620-1020-1300-1950z" fill="#535c35"/>
<path d="M8400 2700h600v2910c-330-160-550-490-640-900-120-540 40-1190 40-2010z" fill="#4c5732"/>
</g>
<g opacity=".92">
<path d="M670 3650c870-300 1690-250 2510-110 1040 180 1850 130 2800-90 750-170 1500-280 2390-100" stroke="#c2aa72" stroke-width="470" stroke-linecap="round" fill="none"/>
<path d="M1260 4820c900-170 1870-90 2780 60 1100 190 2070 160 3240-180" stroke="#c2aa72" stroke-width="390" stroke-linecap="round" fill="none"/>
<path d="M2500 2850c150 760 120 1240-30 1820" stroke="#b79a60" stroke-width="280" stroke-linecap="round" fill="none"/>
<path d="M5820 2050c-130 900 80 1540 390 2290" stroke="#b79a60" stroke-width="250" stroke-linecap="round" fill="none"/>
</g>
<g opacity=".9">
<path d="M2350 1880c570-250 1360-240 2100-80 600 130 1150 320 1770 330 470 10 950-80 1460-270v780c-980 320-1800 250-2750 10-760-190-1500-330-2350-70-510 160-940 390-1340 700v-820c300-240 650-430 1110-580z" fill="#406f3a"/>
<path d="M3900 3170c890-210 1610-140 2220 150 770 370 1560 470 2540 140v1180c-670 240-1450 310-2200 90-760-220-1410-520-2310-300-540 130-1100 410-1740 760v-1290c440-360 910-590 1490-730z" fill="#38683a"/>
<path d="M360 4520c980-240 1710-90 2420 210 590 250 1170 340 1760 220v900c-770 220-1520 70-2260-240-670-280-1320-340-2280-70z" fill="#416f39"/>
<circle cx="3180" cy="2420" r="160" fill="#263f24" opacity=".55"/>
<circle cx="5050" cy="3940" r="180" fill="#263f24" opacity=".55"/>
<circle cx="7500" cy="4300" r="150" fill="#263f24" opacity=".55"/>
</g>
<g filter="url(#softShadow)">
<path d="M720 3260h1420v1620H720z" fill="#6c4b2f"/>
<path d="M820 3380h1220v1400H820z" fill="#8b693f"/>
<path d="M910 3510h1040v170" stroke="#e2c078" stroke-width="40"/>
<path d="M910 3920h1040" stroke="#4a2f1f" stroke-width="34"/>
<path d="M910 4360h1040" stroke="#4a2f1f" stroke-width="34"/>
<path d="M5160 3220h1160v960H5160z" fill="#655032"/>
<path d="M5280 3330h920v720H5280z" fill="#927243"/>
<path d="M7560 2740h980v850h-980z" fill="#60452f"/>
<path d="M7660 2850h780v620h-780z" fill="#927243"/>
</g>
<g opacity=".8">
<path d="M1400 3300l220-280 220 280-220 220zM1030 3750l190-230 190 230-190 190zM1780 4240l210-250 210 250-210 210z" fill="#d7a044"/>
<path d="M5360 3440l220-270 220 270-220 210zM5860 3740l190-230 190 230-190 190z" fill="#d7a044"/>
<path d="M7810 3010l170-210 170 210-170 170zM8140 3340l170-210 170 210-170 170z" fill="#d7a044"/>
</g>
<g opacity=".56">
<path d="M0 0h9000v7200H0z" fill="none" stroke="#20180f" stroke-width="36"/>
<path d="M0 600h9000M0 1200h9000M0 1800h9000M0 2400h9000M0 3000h9000M0 3600h9000M0 4200h9000M0 4800h9000M0 5400h9000M0 6000h9000M0 6600h9000" stroke="#2f271a" stroke-width="7" opacity=".25"/>
<path d="M600 0v7200M1200 0v7200M1800 0v7200M2400 0v7200M3000 0v7200M3600 0v7200M4200 0v7200M4800 0v7200M5400 0v7200M6000 0v7200M6600 0v7200M7200 0v7200M7800 0v7200M8400 0v7200" stroke="#2f271a" stroke-width="7" opacity=".25"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

View File

@@ -1,94 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="9200" height="7400" viewBox="0 0 9200 7400">
<defs>
<linearGradient id="ground" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#af8a57"/>
<stop offset=".36" stop-color="#8c7a4d"/>
<stop offset=".68" stop-color="#6d6f42"/>
<stop offset="1" stop-color="#3d5134"/>
</linearGradient>
<linearGradient id="river" x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stop-color="#2f617a"/>
<stop offset=".52" stop-color="#244768"/>
<stop offset="1" stop-color="#315f78"/>
</linearGradient>
<pattern id="grain" width="92" height="92" patternUnits="userSpaceOnUse">
<path d="M0 45h92M45 0v92" stroke="#241b12" stroke-width="5" opacity=".06"/>
<circle cx="18" cy="22" r="8" fill="#e4bc72" opacity=".16"/>
<circle cx="64" cy="58" r="6" fill="#2c4023" opacity=".15"/>
<path d="M6 78c22-15 43-13 70 2" stroke="#614b28" stroke-width="5" opacity=".14" fill="none"/>
</pattern>
<filter id="softShadow" x="-18%" y="-18%" width="136%" height="136%">
<feDropShadow dx="0" dy="24" stdDeviation="22" flood-color="#140d08" flood-opacity=".36"/>
</filter>
<filter id="emberGlow" x="-60%" y="-60%" width="220%" height="220%">
<feGaussianBlur stdDeviation="26" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<rect width="9200" height="7400" fill="url(#ground)"/>
<rect width="9200" height="7400" fill="url(#grain)"/>
<g opacity=".94">
<path d="M0 6240c1180-260 2100-80 3210-120 980-35 1500-340 2330-240 950 115 1920 540 3660 220v1300H0z" fill="url(#river)"/>
<path d="M0 6660c1270-180 2150-35 3260-65 920-25 1510-240 2250-150 1030 126 1990 420 3690 220v735H0z" fill="#1d3658" opacity=".76"/>
<path d="M380 6200c620 180 1180 210 1810 95M4620 5935c680 115 1250 280 2010 445M6610 6530c820 120 1510 55 2410-90" stroke="#8fc0b9" stroke-width="28" opacity=".23" fill="none"/>
</g>
<g filter="url(#softShadow)">
<path d="M0 0h2600c-150 620-500 1230-980 1640C1020 2160 560 2590 0 3020z" fill="#575630"/>
<path d="M3400 0h2780c-120 520-430 940-850 1230-520 360-1130 500-1970 640z" fill="#5b5630"/>
<path d="M6600 0h2600v2800c-520-110-960-370-1310-780-410-470-660-1090-1290-2020z" fill="#4d522f"/>
<path d="M9000 2700h200v3320c-310-200-470-560-520-1040-70-690 270-1350 320-2280z" fill="#47502e"/>
<path d="M3600 5260h450c40 660-80 1510-360 2140h-520c260-710 400-1420 430-2140z" fill="#4f4b2d"/>
</g>
<g opacity=".91">
<path d="M160 3990c1240-210 2130-70 3130-95 980-25 1690-250 2600-170 930 82 1780 300 2550 105" stroke="#c09c61" stroke-width="520" stroke-linecap="round" fill="none"/>
<path d="M1540 5220c830-135 1740-70 2710 70 1170 170 2300 120 4050-210" stroke="#b78f58" stroke-width="430" stroke-linecap="round" fill="none"/>
<path d="M3050 3160c150 780 105 1390-80 1960" stroke="#ad824e" stroke-width="300" stroke-linecap="round" fill="none"/>
<path d="M6200 2360c-110 880 110 1600 430 2380" stroke="#ad824e" stroke-width="260" stroke-linecap="round" fill="none"/>
</g>
<g opacity=".9">
<path d="M2520 1900c620-270 1480-260 2250-80 620 145 1170 350 1800 360 520 10 1010-100 1550-330v800c-1010 340-1880 260-2860 5-780-205-1540-360-2420-80-520 165-960 420-1380 760v-845c310-250 670-440 1060-590z" fill="#3e6d37"/>
<path d="M4160 3260c880-235 1650-145 2260 155 800 390 1600 490 2600 130v1220c-700 245-1500 310-2280 70-770-235-1430-545-2340-305-560 145-1140 430-1810 800v-1320c455-375 965-620 1570-750z" fill="#365f34"/>
<path d="M420 4700c960-260 1760-100 2500 205 600 250 1200 335 1810 205v930c-790 230-1570 65-2350-260-680-285-1360-355-2380-55z" fill="#3d6835"/>
<circle cx="3360" cy="2460" r="175" fill="#24381f" opacity=".55"/>
<circle cx="5270" cy="4050" r="190" fill="#24381f" opacity=".55"/>
<circle cx="7720" cy="4400" r="160" fill="#24381f" opacity=".55"/>
</g>
<g filter="url(#softShadow)">
<path d="M1080 3760h1240v1500H1080z" fill="#68462c"/>
<path d="M1210 3880h980v1260h-980z" fill="#8a653d"/>
<path d="M1280 4020h840M1280 4390h840M1280 4770h840" stroke="#e4bf76" stroke-width="38" opacity=".74"/>
<path d="M4320 3420h1320v1460H4320z" fill="#65472d"/>
<path d="M4450 3550h1060v1190H4450z" fill="#8d693f"/>
<path d="M4560 3700h820M4560 4080h820M4560 4460h820" stroke="#e3bb73" stroke-width="36" opacity=".72"/>
<path d="M6960 2650h1420v1680H6960z" fill="#5c402b"/>
<path d="M7100 2790h1140v1400H7100z" fill="#88643d"/>
<path d="M7230 2960h900M7230 3400h900M7230 3860h900" stroke="#e1ba72" stroke-width="38" opacity=".72"/>
</g>
<g filter="url(#emberGlow)" opacity=".88">
<path d="M1390 3860c95 170 50 350-85 470 250-55 370-210 380-425 120 175 110 365-40 555 300-120 430-340 345-645-60-225-250-395-600-520 105 150 105 310 0 565z" fill="#de5b2c"/>
<path d="M4790 3540c120 190 80 390-75 535 300-80 450-260 460-500 140 190 125 410-55 630 340-150 500-405 410-745-70-255-295-440-700-565 130 175 125 360-40 645z" fill="#e05a2a"/>
<path d="M7420 2800c105 160 70 330-60 450 250-70 380-230 390-430 120 170 110 360-40 540 295-120 420-340 335-630-60-210-250-375-600-490 110 145 105 300-25 560z" fill="#df6229"/>
<path d="M2460 5030c70 120 40 240-55 330 185-42 280-160 290-310 95 120 80 260-30 390 230-90 335-260 270-470-45-165-190-275-450-360 80 100 75 215-25 420z" fill="#d94f26"/>
</g>
<g opacity=".8">
<path d="M1650 4030l215-275 215 275-215 215zM1230 4550l190-230 190 230-190 190zM1980 4980l205-250 205 250-205 210z" fill="#d89c42"/>
<path d="M4680 3700l220-270 220 270-220 215zM5250 4240l190-235 190 235-190 190z" fill="#d79b41"/>
<path d="M7330 2960l195-240 195 240-195 200zM7930 3740l180-220 180 220-180 180z" fill="#d89b40"/>
</g>
<g opacity=".55">
<path d="M0 0h9200v7400H0z" fill="none" stroke="#21160e" stroke-width="36"/>
<path d="M0 600h9200M0 1200h9200M0 1800h9200M0 2400h9200M0 3000h9200M0 3600h9200M0 4200h9200M0 4800h9200M0 5400h9200M0 6000h9200M0 6600h9200M0 7200h9200" stroke="#2f2117" stroke-width="7" opacity=".25"/>
<path d="M600 0v7400M1200 0v7400M1800 0v7400M2400 0v7400M3000 0v7400M3600 0v7400M4200 0v7400M4800 0v7400M5400 0v7400M6000 0v7400M6600 0v7400M7200 0v7400M7800 0v7400M8400 0v7400M9000 0v7400" stroke="#2f2117" stroke-width="7" opacity=".25"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 MiB

View File

@@ -23,13 +23,13 @@ import fifteenthBattleMapUrl from '../../assets/images/battle/fifteenth-battle-m
import firstBattleMapUrl from '../../assets/images/battle/first-battle-map.webp';
import fortiethBattleMapUrl from '../../assets/images/battle/fortieth-battle-map.webp';
import fortyEighthBattleMapUrl from '../../assets/images/battle/forty-eighth-battle-map.svg';
import fortyFifthBattleMapUrl from '../../assets/images/battle/forty-fifth-battle-map.svg';
import fortyFifthBattleMapUrl from '../../assets/images/battle/forty-fifth-battle-map.webp';
import fortyFirstBattleMapUrl from '../../assets/images/battle/forty-first-battle-map.webp';
import fortyFourthBattleMapUrl from '../../assets/images/battle/forty-fourth-battle-map.webp';
import fortyNinthBattleMapUrl from '../../assets/images/battle/forty-ninth-battle-map.svg';
import fortySecondBattleMapUrl from '../../assets/images/battle/forty-second-battle-map.webp';
import fortySeventhBattleMapUrl from '../../assets/images/battle/forty-seventh-battle-map.svg';
import fortySixthBattleMapUrl from '../../assets/images/battle/forty-sixth-battle-map.svg';
import fortySixthBattleMapUrl from '../../assets/images/battle/forty-sixth-battle-map.webp';
import fortyThirdBattleMapUrl from '../../assets/images/battle/forty-third-battle-map.webp';
import fourteenthBattleMapUrl from '../../assets/images/battle/fourteenth-battle-map.webp';
import fourthBattleMapUrl from '../../assets/images/battle/fourth-battle-map.webp';