polats Claude Opus 4.8 (1M context) commited on
Commit
7db83b9
·
1 Parent(s): c8f2ab9

Cities pill: rebuild bundle + carry Medieval City & Mountain Stronghold assets (LFS)

Browse files

Picks up the new Cities sandbox pill (two skins, decoded clickable catalogues)
and curates each pack's runtime sheets + premade-exterior layers into web/assets.
City-pack PNGs are LFS/Xet-tracked per HF's binary-storage policy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files changed (33) hide show
  1. .gitattributes +2 -0
  2. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_a-night_effect.png +3 -0
  3. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_b-pennant_flags.png +3 -0
  4. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_c-shadows.png +3 -0
  5. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_d-props_on_city_walls.png +3 -0
  6. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_e-city_walls.png +3 -0
  7. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_f-chimneys.png +3 -0
  8. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_g-turrets_and_annexes.png +3 -0
  9. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_h-roof.png +3 -0
  10. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_i-props.png +3 -0
  11. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_j-stairs_and_terraces_2.png +3 -0
  12. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_k-walls_2.png +3 -0
  13. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_l-stairs_and_terraces_1.png +3 -0
  14. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_m-walls_1.png +3 -0
  15. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_n-fences.png +3 -0
  16. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_o-floor.png +3 -0
  17. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Props/Props.png +3 -0
  18. web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Tileset/Tileset.png +3 -0
  19. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_a-shadows.png +3 -0
  20. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_b-human.png +3 -0
  21. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_c-stairs.png +3 -0
  22. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_d-doors_windows.png +3 -0
  23. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_e-snow_2.png +3 -0
  24. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_f-battlements_and_rooftops.png +3 -0
  25. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_g-bridges.png +3 -0
  26. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_h-props.png +3 -0
  27. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_i-structures_built_into_cliffs.png +3 -0
  28. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_j-snow_1.png +3 -0
  29. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_k-jagged_cliffs.png +3 -0
  30. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_l-bg.png +3 -0
  31. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Props/Props.png +3 -0
  32. web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Tileset/Tileset.png +3 -0
  33. web/mapSandbox.js +338 -65
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/**/*.png filter=lfs diff=lfs merge=lfs -text
37
+ web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/**/*.png filter=lfs diff=lfs merge=lfs -text
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_a-night_effect.png ADDED

Git LFS Details

  • SHA256: 8e234b2a71f3128ad8e66c4a754ad0653741ee756c6e791e2e4125b8b7e45299
  • Pointer size: 130 Bytes
  • Size of remote file: 12.8 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_b-pennant_flags.png ADDED

Git LFS Details

  • SHA256: 25a19e4e9e4bbf5d1ba9be9d8ad6cccea56021a8cccc836e5213bda24dabf016
  • Pointer size: 129 Bytes
  • Size of remote file: 3.71 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_c-shadows.png ADDED

Git LFS Details

  • SHA256: 4b6e97bab84568151093e83b9028c26503901f9c77a1f4073e4f016524db7e5b
  • Pointer size: 129 Bytes
  • Size of remote file: 9.13 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_d-props_on_city_walls.png ADDED

Git LFS Details

  • SHA256: 3246b16f3b8ade7f211c66b33b9f2e5587ce709958c51e200a44a8c3b9998aa2
  • Pointer size: 129 Bytes
  • Size of remote file: 3.11 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_e-city_walls.png ADDED

Git LFS Details

  • SHA256: 3a9286a26b70d7c1a1737c2b6d88b9aafc67c7d08680cc27e21423454f01cfe6
  • Pointer size: 130 Bytes
  • Size of remote file: 21.8 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_f-chimneys.png ADDED

Git LFS Details

  • SHA256: 6f0e9bcf79df6ec05afcdf0f851db080e222cfdb5a2693591cf6cd81cb2ab7ae
  • Pointer size: 129 Bytes
  • Size of remote file: 5.27 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_g-turrets_and_annexes.png ADDED

Git LFS Details

  • SHA256: bdf70962b07f20b808c5e9facec8b8b35e827d94449ec263598e13d75c7bdc92
  • Pointer size: 129 Bytes
  • Size of remote file: 9.63 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_h-roof.png ADDED

Git LFS Details

  • SHA256: c23514cc5dec5a02754ecaec66cdc296a327ec3c27386eac1ba549fc8d9ea28a
  • Pointer size: 130 Bytes
  • Size of remote file: 17.8 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_i-props.png ADDED

Git LFS Details

  • SHA256: c1c450da0707de916199a22da2df92e6de3847cb1899c25d42a0b7e18d87e0f4
  • Pointer size: 130 Bytes
  • Size of remote file: 13 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_j-stairs_and_terraces_2.png ADDED

Git LFS Details

  • SHA256: 3b35ce1625582e324fbff711ce263a2db0ee230565ebce2db5d91c5e94752d27
  • Pointer size: 129 Bytes
  • Size of remote file: 2.09 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_k-walls_2.png ADDED

Git LFS Details

  • SHA256: 2187134b1caddf38d952eeeb1bbf2ddf31afd7779911f4d3541b76b928c147c0
  • Pointer size: 130 Bytes
  • Size of remote file: 17.6 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_l-stairs_and_terraces_1.png ADDED

Git LFS Details

  • SHA256: 8b561fc9f47d07a7837c2d6cdfcf618e2d93d1051ee57720e6718b67a7dfcce7
  • Pointer size: 129 Bytes
  • Size of remote file: 4.25 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_m-walls_1.png ADDED

Git LFS Details

  • SHA256: 69a3ec10970cc150caf0f004a3d68433a5610825e66a4ab47bd5f145b047d312
  • Pointer size: 130 Bytes
  • Size of remote file: 19.5 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_n-fences.png ADDED

Git LFS Details

  • SHA256: b042a499276eff0a3d3df59d827fd977e6dec8b5cd85fce63bd58fb8fe66e555
  • Pointer size: 129 Bytes
  • Size of remote file: 6.2 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Premade/Premade_Exterior/Separate_Layers/Premade_Exterior_o-floor.png ADDED

Git LFS Details

  • SHA256: 6348d685a7c42e6575c0a212e7823841ef5bdb37574953e3a9bcebca440b7dfe
  • Pointer size: 130 Bytes
  • Size of remote file: 62.6 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Props/Props.png ADDED

Git LFS Details

  • SHA256: 33072daa9460eda7a144026d104dabfa3e9daa77c2b89b0e40293834a84d9ad2
  • Pointer size: 129 Bytes
  • Size of remote file: 9.82 kB
web/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets/Tileset/Tileset.png ADDED

Git LFS Details

  • SHA256: 15c1c281929da324b095f20b2c19492a34007f94d9fffd5afce023615decddb5
  • Pointer size: 130 Bytes
  • Size of remote file: 94.6 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_a-shadows.png ADDED

Git LFS Details

  • SHA256: 8b1984d7db71ae65f682af5af193e30079602b7d1676041d9abc8d49ab4599dd
  • Pointer size: 129 Bytes
  • Size of remote file: 5.57 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_b-human.png ADDED

Git LFS Details

  • SHA256: bc178350565d961cc0f9aca6ad11ecb28cf74bbcaf56a8ec556ab0e164468481
  • Pointer size: 129 Bytes
  • Size of remote file: 1.62 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_c-stairs.png ADDED

Git LFS Details

  • SHA256: 247f79edee6ca5a964fe4cfb87ae12ad2c5e06e0d76e4068073e1b9f41fe023b
  • Pointer size: 129 Bytes
  • Size of remote file: 2.05 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_d-doors_windows.png ADDED

Git LFS Details

  • SHA256: e2bc5a10c5f50fb8b34098a9cc6bbd6a2504c170855e7796e42b3028b8d5be2e
  • Pointer size: 129 Bytes
  • Size of remote file: 4.93 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_e-snow_2.png ADDED

Git LFS Details

  • SHA256: c7504b8c1b0b2beab6e3b039bba84d31485e2c855a84ca578acc1bc431cb7b71
  • Pointer size: 129 Bytes
  • Size of remote file: 2.98 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_f-battlements_and_rooftops.png ADDED

Git LFS Details

  • SHA256: d9d7540790a8985af333acf0769ec509c22177dd01d1cb2ff60dc31e3e8ed44e
  • Pointer size: 130 Bytes
  • Size of remote file: 10.9 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_g-bridges.png ADDED

Git LFS Details

  • SHA256: 3689ad3bc0a5042370d5cb9b3a1d75acde1da1300ffde5082589dcb700bfd27c
  • Pointer size: 129 Bytes
  • Size of remote file: 3.52 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_h-props.png ADDED

Git LFS Details

  • SHA256: f7b1a08f6510c1a7a5147bf67428165f8fa28eeca4085b2f18c269c770708630
  • Pointer size: 130 Bytes
  • Size of remote file: 17.1 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_i-structures_built_into_cliffs.png ADDED

Git LFS Details

  • SHA256: a2b323516504278b9b962984a28b20859497302d57968e080b2973189cbfe0a7
  • Pointer size: 130 Bytes
  • Size of remote file: 19.2 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_j-snow_1.png ADDED

Git LFS Details

  • SHA256: f2e0f9896174b8dc857b3e8f542af174c1610b788055bbc1d9e8533b927133c5
  • Pointer size: 129 Bytes
  • Size of remote file: 5.48 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_k-jagged_cliffs.png ADDED

Git LFS Details

  • SHA256: e208bcdc37ded4c788006399ac062126291adc9b8c32b1c85ac0ebe50f08dd4b
  • Pointer size: 130 Bytes
  • Size of remote file: 76.4 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Premade/Premade_Exterior/_Separate_Layers/Premade_Exterior_l-bg.png ADDED

Git LFS Details

  • SHA256: f2949846e6342e888ad7ce5e6e8d5873cae70466f53963d32725a5a2afc0d696
  • Pointer size: 129 Bytes
  • Size of remote file: 2.47 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Props/Props.png ADDED

Git LFS Details

  • SHA256: 75bb4744fe651ad07c6ec820d0914c0422fd4cedd746f8e2f860dc822d1b44b8
  • Pointer size: 130 Bytes
  • Size of remote file: 11.5 kB
web/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets/Tileset/Tileset.png ADDED

Git LFS Details

  • SHA256: 08cbff905e6ebc8ef0d847307a289f355a1509fc2d875974b78915ce3ee410e1
  • Pointer size: 131 Bytes
  • Size of remote file: 178 kB
web/mapSandbox.js CHANGED
@@ -1,9 +1,9 @@
1
  // ../auto-battler/src/render/chunkedMap.js
2
  function createChunkedMap(pixi, host, config) {
3
  const { Application, Assets, Sprite, Container, Texture, Rectangle, RenderTexture } = pixi;
4
- const TILE8 = config.tile ?? 8;
5
- const CHUNK7 = config.chunk ?? 32;
6
- const CHUNKPX = CHUNK7 * TILE8;
7
  const BG = config.background ?? "#69636f";
8
  const Z_DEFAULT = config.zoomDefault ?? 1;
9
  const Z_MIN = config.zoomMin ?? 1 / 32;
@@ -17,10 +17,10 @@ function createChunkedMap(pixi, host, config) {
17
  const DETAIL_BUDGET = config.detailBudget ?? 1;
18
  const MACRO_BUDGET = config.macroBudget ?? 8;
19
  const bounds = config.bounds ? {
20
- x0: config.bounds.x0 * TILE8,
21
- y0: config.bounds.y0 * TILE8,
22
- x1: config.bounds.x1 * TILE8,
23
- y1: config.bounds.y1 * TILE8,
24
  tx0: config.bounds.x0,
25
  ty0: config.bounds.y0,
26
  tx1: config.bounds.x1,
@@ -47,12 +47,12 @@ function createChunkedMap(pixi, host, config) {
47
  const pinch = { on: false, dist: 0 };
48
  const handlers = {};
49
  const emit = () => listeners.forEach((fn) => fn(getSnapshot()));
50
- const getSnapshot = () => ({ seed, zoom, cx: Math.round(camera.x / TILE8), cy: Math.round(camera.y / TILE8), chunks: chunks.size });
51
  function tex(source, c, r) {
52
  const k = source.uid + ":" + c + "," + r;
53
  let t = texCache.get(k);
54
  if (!t) {
55
- t = new Texture({ source, frame: new Rectangle(c * TILE8, r * TILE8, TILE8, TILE8) });
56
  texCache.set(k, t);
57
  }
58
  return t;
@@ -67,16 +67,16 @@ function createChunkedMap(pixi, host, config) {
67
  return t;
68
  }
69
  function makeChunk(cx, cy) {
70
- const x0 = cx * CHUNK7, y0 = cy * CHUNK7;
71
  const tmp = new Container();
72
  const add = (source, c, r, tx, ty) => {
73
  const sp = new Sprite(tex(source, c, r));
74
- sp.x = tx * TILE8;
75
- sp.y = ty * TILE8;
76
  tmp.addChild(sp);
77
  return sp;
78
  };
79
- const res = config.bake({ cx, cy, x0, y0, seed, chunk: CHUNK7, tile: TILE8, ctx, tmp, app, Sprite, Container, Texture, Rectangle, tex, texFrame, add }) || {};
80
  const rt = RenderTexture.create({ width: CHUNKPX, height: CHUNKPX, autoGenerateMipmaps: true, scaleMode: "nearest" });
81
  rt.source.minFilter = "linear";
82
  rt.source.mipmapFilter = "linear";
@@ -84,12 +84,12 @@ function createChunkedMap(pixi, host, config) {
84
  rt.source.updateMipmaps();
85
  tmp.destroy({ children: true });
86
  const sprite = new Sprite(rt);
87
- sprite.x = x0 * TILE8;
88
- sprite.y = y0 * TILE8;
89
  return { sprite, rt, meta: res.meta ?? null, live: res.live ?? null };
90
  }
91
  function chooseMacroLevel(z) {
92
- return Math.max(0, Math.min(MACRO_LEVEL_MAX, Math.round(Math.log2(MACRO_TEXEL_PX / (TILE8 * z)))));
93
  }
94
  function makeMacroChunk(L, mcx, mcy) {
95
  const step = 1 << L, t0x = mcx * MCHUNK * step, t0y = mcy * MCHUNK * step;
@@ -110,9 +110,9 @@ function createChunkedMap(pixi, host, config) {
110
  const t = Texture.from(cv);
111
  t.source.scaleMode = "linear";
112
  const sprite = new Sprite(t);
113
- sprite.x = t0x * TILE8;
114
- sprite.y = t0y * TILE8;
115
- sprite.width = sprite.height = MCHUNK * step * TILE8;
116
  return { sprite, tex: t };
117
  }
118
  function coverZoom() {
@@ -169,10 +169,10 @@ function createChunkedMap(pixi, host, config) {
169
  let c0 = Math.floor((camera.x - halfW) / CHUNKPX) - 1, c1 = Math.floor((camera.x + halfW) / CHUNKPX) + 1;
170
  let r0 = Math.floor((camera.y - halfH) / CHUNKPX) - 1, r1 = Math.floor((camera.y + halfH) / CHUNKPX) + 1;
171
  if (bounds) {
172
- c0 = Math.max(c0, Math.floor(bounds.tx0 / CHUNK7));
173
- c1 = Math.min(c1, Math.floor((bounds.tx1 - 1) / CHUNK7));
174
- r0 = Math.max(r0, Math.floor(bounds.ty0 / CHUNK7));
175
- r1 = Math.min(r1, Math.floor((bounds.ty1 - 1) / CHUNK7));
176
  }
177
  for (const [key, ch] of chunks) {
178
  const [cx, cy] = key.split(",").map(Number);
@@ -199,7 +199,7 @@ function createChunkedMap(pixi, host, config) {
199
  return missing.length > DETAIL_BUDGET;
200
  }
201
  function reconcileMacro(L) {
202
- const px = MCHUNK * (1 << L) * TILE8;
203
  const halfW = app.screen.width / 2 / zoom, halfH = app.screen.height / 2 / zoom;
204
  let c0 = Math.floor((camera.x - halfW) / px) - 1, c1 = Math.floor((camera.x + halfW) / px) + 1;
205
  let r0 = Math.floor((camera.y - halfH) / px) - 1, r1 = Math.floor((camera.y + halfH) / px) + 1;
@@ -403,7 +403,7 @@ function createChunkedMap(pixi, host, config) {
403
  if (cameraDirty || genPending) {
404
  reconcile();
405
  cameraDirty = false;
406
- const tk2 = Math.round(camera.x / TILE8) + "," + Math.round(camera.y / TILE8) + "," + zoom.toFixed(3);
407
  if (tk2 !== lastEmitTile) {
408
  lastEmitTile = tk2;
409
  emit();
@@ -464,7 +464,7 @@ function createChunkedMap(pixi, host, config) {
464
  }
465
  function tileIndexAt(wx, wy) {
466
  if (!config.tileIndexAt) return null;
467
- const cx = Math.floor(wx / CHUNK7), cy = Math.floor(wy / CHUNK7);
468
  const ch = chunks.get(cx + "," + cy);
469
  if (!ch) return null;
470
  return config.tileIndexAt(wx, wy, ch.meta);
@@ -523,7 +523,7 @@ function createChunkedMap(pixi, host, config) {
523
  ctx = null;
524
  texCache.clear();
525
  }
526
- return { ready, regenerate, destroy, onChange, getSnapshot, getCamera, tileIndexAt, biomeAt: biomeAt2, getBounds, zoomBy, flyTo, setEnabled, onTick, getEntityLayer, getApp, screenToWorld, worldToScreen, tile: TILE8 };
527
  }
528
 
529
  // ../auto-battler/src/engine/rng.js
@@ -3313,6 +3313,239 @@ function createDesolateDesertMap(pixi, host, opts = {}) {
3313
  return createChunkedMap(pixi, host, { ...desolateDesertConfig(opts.seed ?? 1), keyboardPan: opts.keyboardPan });
3314
  }
3315
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3316
  // ../auto-battler/src/render/sceneViewer.js
3317
  function createSceneViewer(pixi, host, { layers, dir, sceneW, sceneH, background = "#15121b", pulse = null }) {
3318
  const { Application, Assets, Sprite, Container } = pixi;
@@ -3651,10 +3884,10 @@ function createTileViewer(pixi, host, cfg) {
3651
  root = new Container();
3652
  app.stage.addChild(root);
3653
  const skin = skins[skinKey];
3654
- const TILE8 = skin.tile || 8;
3655
- const res = render3({ app, root, skin, seed, TILE: TILE8, tex, sources, Sprite, Graphics, Container }) || {};
3656
  lastBounds = res.bounds || null;
3657
- lastTile = res.tile ?? TILE8;
3658
  lastSnapshot = res.snapshot || {};
3659
  if (res.tick) {
3660
  tick = res.tick;
@@ -3728,19 +3961,19 @@ function createInteriorViewer(pixi, host, opts = {}) {
3728
  fit
3729
  });
3730
  }
3731
- function render({ root, skin, seed, TILE: TILE8, tex, sources, Sprite, Graphics }) {
3732
  const src = sources.get(skin.url);
3733
  const { W, H, floor, rooms, bounds } = generateRooms(seed);
3734
  const isF = (x, y) => x >= 0 && y >= 0 && x < W && y < H && floor[y * W + x] === 1;
3735
  const addS = (source, coord, x, y) => {
3736
- const sp = new Sprite(tex(source, coord[0], coord[1], TILE8));
3737
- sp.x = x * TILE8;
3738
- sp.y = y * TILE8;
3739
  root.addChild(sp);
3740
  return sp;
3741
  };
3742
  const add = (coord, x, y) => addS(src, coord, x, y);
3743
- const c = { skin, TILE: TILE8, W, H, rooms, seed, isF, add, addS, root, sources, Graphics };
3744
  drawWalls(c);
3745
  drawFloor(c);
3746
  drawShadows(c);
@@ -3750,18 +3983,18 @@ function render({ root, skin, seed, TILE: TILE8, tex, sources, Sprite, Graphics
3750
  const tick = torch.flames.length && torch.flameSrc ? (ticker) => {
3751
  animPhase += 0.14 * ticker.deltaTime;
3752
  const fi = Math.floor(animPhase) % torch.flameFrames;
3753
- for (const fl of torch.flames) fl.sp.texture = tex(torch.flameSrc, fl.col0 + torch.flameStride * fi, fl.row, TILE8);
3754
  if (torch.glowG) torch.glowG.alpha = 0.78 + 0.22 * Math.sin(animPhase * 0.9);
3755
  } : null;
3756
- return { bounds, tile: TILE8, snapshot: { rooms: rooms.length }, tick };
3757
  }
3758
- function fit(app, root, b, TILE8) {
3759
  const sw = app.screen.width, sh = app.screen.height;
3760
- const cw = (b.x1 - b.x0 + 3) * TILE8, ch = (b.y1 - b.y0 + 4) * TILE8;
3761
  const s = Math.max(1, Math.floor(Math.min(sw / cw, sh / ch)));
3762
  root.scale.set(s);
3763
- root.x = Math.round((sw - cw * s) / 2 - (b.x0 - 1) * TILE8 * s);
3764
- root.y = Math.round((sh - ch * s) / 2 - (b.y0 - 2) * TILE8 * s);
3765
  }
3766
  function drawWalls(c) {
3767
  if (c.skin.wall && !c.skin.walls) drawBackWall(c);
@@ -3869,15 +4102,15 @@ function drawFloor(c) {
3869
  }
3870
  }
3871
  function drawShadows(c) {
3872
- const { W, H, TILE: TILE8, isF, root, Graphics } = c;
3873
  const shade = new Graphics();
3874
  for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
3875
  if (!isF(x, y)) continue;
3876
  if (!isF(x, y - 1)) {
3877
- shade.rect(x * TILE8, y * TILE8, TILE8, TILE8).fill({ color: 0, alpha: 0.3 });
3878
- if (isF(x, y + 1)) shade.rect(x * TILE8, (y + 1) * TILE8, TILE8, TILE8).fill({ color: 0, alpha: 0.12 });
3879
  } else if (!isF(x - 1, y) || !isF(x + 1, y)) {
3880
- shade.rect(x * TILE8, y * TILE8, TILE8, TILE8).fill({ color: 0, alpha: 0.12 });
3881
  }
3882
  }
3883
  root.addChild(shade);
@@ -3924,7 +4157,7 @@ function drawProps(c) {
3924
  }
3925
  }
3926
  function drawTorches(c) {
3927
- const { skin, W, H, TILE: TILE8, isF, sources, root, Graphics, addS } = c;
3928
  const torch = skin.torch, torchSrc = torch && sources.get(torch.url);
3929
  if (!(torch && torchSrc)) return { flames: [], glowG: null, flameSrc: null, flameStride: 1, flameFrames: 1 };
3930
  const flames = [];
@@ -3933,8 +4166,8 @@ function drawTorches(c) {
3933
  for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
3934
  if (!isF(x, y) || isF(x, y - 1)) continue;
3935
  if ((x * 7 + y * 13) % 5 !== 0) continue;
3936
- const cx = (x + f.w / 2) * TILE8, cy = (y - 1) * TILE8;
3937
- for (const [r, a] of [[3.2, 0.05], [2.1, 0.08], [1.2, 0.13]]) glow.circle(cx, cy, r * TILE8).fill({ color: torch.glow, alpha: a });
3938
  spots.push([x, y]);
3939
  }
3940
  root.addChild(glow);
@@ -3975,7 +4208,7 @@ function createTowerViewer(pixi, host, opts = {}) {
3975
  fit: fit2
3976
  });
3977
  }
3978
- function render2({ root, skin, seed, TILE: TILE8, tex, sources, Sprite, Graphics }) {
3979
  const src = sources.get(skin.url);
3980
  const shaftW = skin.crown ? skin.crown.shaftW : (skin.roof?.w ?? 7) - 2;
3981
  const winH = skin.window?.h ?? 2, doorH = skin.door?.h ?? 2;
@@ -3992,14 +4225,14 @@ function render2({ root, skin, seed, TILE: TILE8, tex, sources, Sprite, Graphics
3992
  const corn = spec.roof === "none" ? 0 : 1;
3993
  const dy = -(h2 + PLINTH - 1);
3994
  const add = (coord, x, y) => {
3995
- const sp = new Sprite(tex(src, coord[0], coord[1], TILE8));
3996
- sp.x = (x + ox) * TILE8;
3997
- sp.y = (y + dy) * TILE8;
3998
  root.addChild(sp);
3999
  return sp;
4000
  };
4001
  const shadow = new Graphics();
4002
- shadow.ellipse((ox + w / 2) * TILE8, TILE8 - TILE8 * 0.3, wb / 2 * TILE8 + TILE8 * 0.5, TILE8 * 0.7).fill({ color: 0, alpha: 0.22 });
4003
  root.addChild(shadow);
4004
  for (let y = 0; y < h2; y++) for (let x = 0; x < w; x++) {
4005
  const top = y === 0, bot = y === h2 - 1, left = x === 0, right = x === w - 1;
@@ -4061,19 +4294,34 @@ function render2({ root, skin, seed, TILE: TILE8, tex, sources, Sprite, Graphics
4061
  ox += w + GAP;
4062
  }
4063
  const bounds = { x0: Math.min(x0, 0), y0, x1: Math.max(x1, ox - GAP), y1: 2 };
4064
- return { bounds, tile: TILE8, snapshot: { w: spec0?.w ?? 0, h: spec0?.h ?? 0, roof: spec0?.roof } };
4065
  }
4066
- function fit2(app, root, b, TILE8) {
4067
  const sw = app.screen.width, sh = app.screen.height;
4068
- const cw = (b.x1 - b.x0) * TILE8, ch = (b.y1 - b.y0) * TILE8;
4069
- const pad = 4 * TILE8;
4070
  const s = Math.max(1, Math.floor(Math.min(sw / (cw + pad), sh / (ch + pad))));
4071
  root.scale.set(s);
4072
- root.x = Math.round((sw - cw * s) / 2 - b.x0 * TILE8 * s);
4073
- root.y = Math.round((sh - ch * s) / 2 - b.y0 * TILE8 * s);
4074
  }
4075
 
4076
  // ../auto-battler/src/render/mapConfigs.js
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4077
  var ASSET = "/assets/minifantasy";
4078
  var ORC4 = `${ASSET}/Minifantasy_Orc_Kingdom_v1.0/Minifantasy_Orc_Kingdom_Assets`;
4079
  var NECRO4 = `${ASSET}/Minifantasy_Necropolis_v1.0/Minifantasy_Necropolis_Assets`;
@@ -4251,6 +4499,15 @@ var MAP_ASSET_URLS = [.../* @__PURE__ */ new Set([
4251
  ...NECRO_SHEETS.map((s) => s.url),
4252
  ...ORC_SHEETS.map((s) => s.url),
4253
  ...FP_SHEETS.map((s) => s.url),
 
 
 
 
 
 
 
 
 
4254
  // Premade-scene reference layers.
4255
  ...sceneUrls(NECRO_EXT_DIR, NECRO_LAYERS),
4256
  ...sceneUrls(ORC_LAYER_DIR, ORC_LAYERS),
@@ -4277,7 +4534,7 @@ function h(tag, attrs, ...kids) {
4277
  var randomSeed = () => Math.floor(Date.now() % 1e6 + Math.random() * 1e6) >>> 0;
4278
  var infiniteMeta = (s) => s?.zoom != null ? `\u221E \xB7 seed ${s.seed} \xB7 (${s.cx}, ${s.cy}) \xB7 ${s.zoom.toFixed(2)}\xD7 \xB7 drag / WASD / scroll` : "loading\u2026";
4279
  function makeGridOverlay() {
4280
- const TILE8 = 8;
4281
  const cv = h("canvas", { class: "necro-grid-overlay" });
4282
  cv.style.display = "none";
4283
  let raf = 0, ctrl = null;
@@ -4290,12 +4547,12 @@ function makeGridOverlay() {
4290
  if (cv.height !== H) cv.height = H;
4291
  const ctx = cv.getContext("2d");
4292
  ctx.clearRect(0, 0, W, H);
4293
- const z = cam.zoom, cell = TILE8 * z;
4294
  if (cell >= 6) {
4295
- const sx = (wx) => (wx * TILE8 - cam.x) * z + W / 2;
4296
- const sy = (wy) => (wy * TILE8 - cam.y) * z + H / 2;
4297
- const x0 = Math.floor((cam.x - W / 2 / z) / TILE8), x1 = Math.ceil((cam.x + W / 2 / z) / TILE8);
4298
- const y0 = Math.floor((cam.y - H / 2 / z) / TILE8), y1 = Math.ceil((cam.y + H / 2 / z) / TILE8);
4299
  ctx.strokeStyle = "rgba(255,70,70,0.45)";
4300
  ctx.lineWidth = 1;
4301
  ctx.beginPath();
@@ -4724,6 +4981,7 @@ var GEN_MODES = [{ key: "generated", label: "Generated" }, { key: "tilesheet", l
4724
  var SKIN_MODES = [{ key: "generated", label: "Generated" }, { key: "reference", label: "Reference" }, { key: "tilesheet", label: "Tilesheet" }];
4725
  var skinList = (skins) => Object.entries(skins).map(([key, s]) => ({ key, label: s.label }));
4726
  var sceneFromRef = (pixi, host, ref) => createSceneViewer(pixi, host, { layers: ref.layers, dir: ref.dir, sceneW: ref.w, sceneH: ref.h, background: ref.bg, pulse: ref.pulse });
 
4727
  var PAGES = {
4728
  world: (pixi, body, opts) => makePage(pixi, body, {
4729
  title: "World Map",
@@ -4796,6 +5054,20 @@ var PAGES = {
4796
  sheets: () => DD_SHEETS,
4797
  generatedMeta: infiniteMeta
4798
  }),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4799
  interiors: (pixi, body) => makePage(pixi, body, {
4800
  title: "Interiors",
4801
  modes: SKIN_MODES,
@@ -4821,6 +5093,7 @@ var NAV = [
4821
  ["gameworld", "Game World"],
4822
  ["world", "World Map"],
4823
  { group: "biome", label: "Biome", pages: [["plains", "Forgotten Plains"], ["desert", "Desolate Desert"], ["orc", "Orc Kingdom"], ["necropolis", "Necropolis"]] },
 
4824
  ["interiors", "Interiors"],
4825
  ["towers", "Towers"]
4826
  ];
 
1
  // ../auto-battler/src/render/chunkedMap.js
2
  function createChunkedMap(pixi, host, config) {
3
  const { Application, Assets, Sprite, Container, Texture, Rectangle, RenderTexture } = pixi;
4
+ const TILE10 = config.tile ?? 8;
5
+ const CHUNK9 = config.chunk ?? 32;
6
+ const CHUNKPX = CHUNK9 * TILE10;
7
  const BG = config.background ?? "#69636f";
8
  const Z_DEFAULT = config.zoomDefault ?? 1;
9
  const Z_MIN = config.zoomMin ?? 1 / 32;
 
17
  const DETAIL_BUDGET = config.detailBudget ?? 1;
18
  const MACRO_BUDGET = config.macroBudget ?? 8;
19
  const bounds = config.bounds ? {
20
+ x0: config.bounds.x0 * TILE10,
21
+ y0: config.bounds.y0 * TILE10,
22
+ x1: config.bounds.x1 * TILE10,
23
+ y1: config.bounds.y1 * TILE10,
24
  tx0: config.bounds.x0,
25
  ty0: config.bounds.y0,
26
  tx1: config.bounds.x1,
 
47
  const pinch = { on: false, dist: 0 };
48
  const handlers = {};
49
  const emit = () => listeners.forEach((fn) => fn(getSnapshot()));
50
+ const getSnapshot = () => ({ seed, zoom, cx: Math.round(camera.x / TILE10), cy: Math.round(camera.y / TILE10), chunks: chunks.size });
51
  function tex(source, c, r) {
52
  const k = source.uid + ":" + c + "," + r;
53
  let t = texCache.get(k);
54
  if (!t) {
55
+ t = new Texture({ source, frame: new Rectangle(c * TILE10, r * TILE10, TILE10, TILE10) });
56
  texCache.set(k, t);
57
  }
58
  return t;
 
67
  return t;
68
  }
69
  function makeChunk(cx, cy) {
70
+ const x0 = cx * CHUNK9, y0 = cy * CHUNK9;
71
  const tmp = new Container();
72
  const add = (source, c, r, tx, ty) => {
73
  const sp = new Sprite(tex(source, c, r));
74
+ sp.x = tx * TILE10;
75
+ sp.y = ty * TILE10;
76
  tmp.addChild(sp);
77
  return sp;
78
  };
79
+ const res = config.bake({ cx, cy, x0, y0, seed, chunk: CHUNK9, tile: TILE10, ctx, tmp, app, Sprite, Container, Texture, Rectangle, tex, texFrame, add }) || {};
80
  const rt = RenderTexture.create({ width: CHUNKPX, height: CHUNKPX, autoGenerateMipmaps: true, scaleMode: "nearest" });
81
  rt.source.minFilter = "linear";
82
  rt.source.mipmapFilter = "linear";
 
84
  rt.source.updateMipmaps();
85
  tmp.destroy({ children: true });
86
  const sprite = new Sprite(rt);
87
+ sprite.x = x0 * TILE10;
88
+ sprite.y = y0 * TILE10;
89
  return { sprite, rt, meta: res.meta ?? null, live: res.live ?? null };
90
  }
91
  function chooseMacroLevel(z) {
92
+ return Math.max(0, Math.min(MACRO_LEVEL_MAX, Math.round(Math.log2(MACRO_TEXEL_PX / (TILE10 * z)))));
93
  }
94
  function makeMacroChunk(L, mcx, mcy) {
95
  const step = 1 << L, t0x = mcx * MCHUNK * step, t0y = mcy * MCHUNK * step;
 
110
  const t = Texture.from(cv);
111
  t.source.scaleMode = "linear";
112
  const sprite = new Sprite(t);
113
+ sprite.x = t0x * TILE10;
114
+ sprite.y = t0y * TILE10;
115
+ sprite.width = sprite.height = MCHUNK * step * TILE10;
116
  return { sprite, tex: t };
117
  }
118
  function coverZoom() {
 
169
  let c0 = Math.floor((camera.x - halfW) / CHUNKPX) - 1, c1 = Math.floor((camera.x + halfW) / CHUNKPX) + 1;
170
  let r0 = Math.floor((camera.y - halfH) / CHUNKPX) - 1, r1 = Math.floor((camera.y + halfH) / CHUNKPX) + 1;
171
  if (bounds) {
172
+ c0 = Math.max(c0, Math.floor(bounds.tx0 / CHUNK9));
173
+ c1 = Math.min(c1, Math.floor((bounds.tx1 - 1) / CHUNK9));
174
+ r0 = Math.max(r0, Math.floor(bounds.ty0 / CHUNK9));
175
+ r1 = Math.min(r1, Math.floor((bounds.ty1 - 1) / CHUNK9));
176
  }
177
  for (const [key, ch] of chunks) {
178
  const [cx, cy] = key.split(",").map(Number);
 
199
  return missing.length > DETAIL_BUDGET;
200
  }
201
  function reconcileMacro(L) {
202
+ const px = MCHUNK * (1 << L) * TILE10;
203
  const halfW = app.screen.width / 2 / zoom, halfH = app.screen.height / 2 / zoom;
204
  let c0 = Math.floor((camera.x - halfW) / px) - 1, c1 = Math.floor((camera.x + halfW) / px) + 1;
205
  let r0 = Math.floor((camera.y - halfH) / px) - 1, r1 = Math.floor((camera.y + halfH) / px) + 1;
 
403
  if (cameraDirty || genPending) {
404
  reconcile();
405
  cameraDirty = false;
406
+ const tk2 = Math.round(camera.x / TILE10) + "," + Math.round(camera.y / TILE10) + "," + zoom.toFixed(3);
407
  if (tk2 !== lastEmitTile) {
408
  lastEmitTile = tk2;
409
  emit();
 
464
  }
465
  function tileIndexAt(wx, wy) {
466
  if (!config.tileIndexAt) return null;
467
+ const cx = Math.floor(wx / CHUNK9), cy = Math.floor(wy / CHUNK9);
468
  const ch = chunks.get(cx + "," + cy);
469
  if (!ch) return null;
470
  return config.tileIndexAt(wx, wy, ch.meta);
 
523
  ctx = null;
524
  texCache.clear();
525
  }
526
+ return { ready, regenerate, destroy, onChange, getSnapshot, getCamera, tileIndexAt, biomeAt: biomeAt2, getBounds, zoomBy, flyTo, setEnabled, onTick, getEntityLayer, getApp, screenToWorld, worldToScreen, tile: TILE10 };
527
  }
528
 
529
  // ../auto-battler/src/engine/rng.js
 
3313
  return createChunkedMap(pixi, host, { ...desolateDesertConfig(opts.seed ?? 1), keyboardPan: opts.keyboardPan });
3314
  }
3315
 
3316
+ // ../auto-battler/src/render/medievalCity.js
3317
+ var MC = "/assets/minifantasy/Minifantasy_Medieval_City_v1.1/Minifantasy_Medieval_City_Assets";
3318
+ var MC_TILES_URL = `${MC}/Tileset/Tileset.png`;
3319
+ var MC_TILES_SHADOWS_URL = `${MC}/Tileset/Shadows.png`;
3320
+ var MC_PROPS_URL = `${MC}/Props/Props.png`;
3321
+ var MC_PROPS_SHADOWS_URL = `${MC}/Props/Shadows.png`;
3322
+ var MC_MOCKUP_URL = `${MC}/Mockup_Exterior_1.png`;
3323
+ var MC_PREMADE_DIR = `${MC}/Premade/Premade_Exterior/Separate_Layers`;
3324
+ var TILE8 = 8;
3325
+ var CHUNK7 = 32;
3326
+ var MC_REF = {
3327
+ dir: MC_PREMADE_DIR,
3328
+ w: 680,
3329
+ h: 552,
3330
+ bg: "#1a1714",
3331
+ layers: [
3332
+ ["Exterior_o-floor", "Floor", "ground"],
3333
+ ["Exterior_n-fences", "Fences", "structure"],
3334
+ ["Exterior_m-walls_1", "Walls 1", "structure"],
3335
+ ["Exterior_l-stairs_and_terraces_1", "Stairs & terraces 1", "structure"],
3336
+ ["Exterior_k-walls_2", "Walls 2", "structure"],
3337
+ ["Exterior_j-stairs_and_terraces_2", "Stairs & terraces 2", "structure"],
3338
+ ["Exterior_i-props", "Props", "scatter"],
3339
+ ["Exterior_h-roof", "Roof", "structure"],
3340
+ ["Exterior_g-turrets_and_annexes", "Turrets & annexes", "structure"],
3341
+ ["Exterior_f-chimneys", "Chimneys", "structure"],
3342
+ ["Exterior_e-city_walls", "City walls", "structure"],
3343
+ ["Exterior_d-props_on_city_walls", "Props on city walls", "scatter"],
3344
+ ["Exterior_c-shadows", "Shadows", "lighting"],
3345
+ ["Exterior_b-pennant_flags", "Pennant flags", "scatter"],
3346
+ ["Exterior_a-night_effect", "Night effect", "lighting"]
3347
+ ]
3348
+ };
3349
+ var DIRT_BASE2 = [4, 132];
3350
+ var DIRT_VARS4 = [[6, 132], [8, 132], [10, 132], [6, 134], [8, 134], [10, 134], [6, 136], [8, 136]];
3351
+ var DIRT_RATE2 = 6;
3352
+ var COL_DIRT4 = [151, 116, 94];
3353
+ var MC_MAP_ASSETS = [MC_TILES_URL];
3354
+ var medievalCityConfig = (seed) => ({
3355
+ seed,
3356
+ tile: TILE8,
3357
+ chunk: CHUNK7,
3358
+ background: "#3a2e25",
3359
+ async load({ Assets }) {
3360
+ const ctx = { tiles: null };
3361
+ const t = await Assets.load(MC_TILES_URL);
3362
+ t.source.scaleMode = "nearest";
3363
+ ctx.tiles = t.source;
3364
+ return ctx;
3365
+ },
3366
+ bake({ x0, y0, seed: seed2, ctx, add, accept = () => true }) {
3367
+ for (let ty = 0; ty < CHUNK7; ty++) for (let tx = 0; tx < CHUNK7; tx++) {
3368
+ if (!accept(tx, ty)) continue;
3369
+ const wx = x0 + tx, wy = y0 + ty;
3370
+ add(ctx.tiles, ...sparse(DIRT_BASE2, DIRT_VARS4, wx, wy, 0, DIRT_RATE2, seed2), tx, ty);
3371
+ }
3372
+ return {};
3373
+ },
3374
+ macroColor() {
3375
+ return COL_DIRT4;
3376
+ }
3377
+ });
3378
+ function createMedievalCityMap(pixi, host, opts = {}) {
3379
+ return createChunkedMap(pixi, host, { ...medievalCityConfig(opts.seed ?? 1), keyboardPan: opts.keyboardPan });
3380
+ }
3381
+ var MC_TILE_CATALOG = [
3382
+ // ── Ground (band at rows ~130-141) ──────────────────────────────────────────────────────────
3383
+ {
3384
+ name: "Outdoor floor (dirt base + speckle)",
3385
+ frame: [4, 140, 8, 9],
3386
+ tiles: [DIRT_BASE2, ...DIRT_VARS4],
3387
+ used: true,
3388
+ note: "dirt base [4,132] (100% plain) + speckle variants on the even col/row grid (cols 4-11), via sparse(). Laid by the Generated ground"
3389
+ },
3390
+ {
3391
+ name: "Cobble road (tan)",
3392
+ frame: [14, 142, 3, 11],
3393
+ tiles: [[14, 132]],
3394
+ used: false,
3395
+ note: "tan cobblestone road \u2014 FP-format autotile block; a grass-on-dirt block sits just below it"
3396
+ },
3397
+ { name: "Flagstone road (grey)", frame: [18, 142, 3, 13], tiles: [[18, 130]], used: false, note: "grey stone-slab road \u2014 autotile block" },
3398
+ { name: "Brick road (red)", frame: [22, 142, 3, 13], tiles: [[22, 130]], used: false, note: "red brick road \u2014 autotile block" },
3399
+ { name: "Smooth pavement", frame: [26, 136, 3, 5], tiles: [[26, 132]], used: false, note: "flat tan paving (plaza/courtyard)" },
3400
+ { name: "Garden / planting bed", frame: [30, 136, 3, 7], tiles: [[30, 130]], used: false, note: "grass patch + tilled-earth bed" },
3401
+ { name: "Hedge fence (green)", frame: [38, 137, 5, 7], tiles: [[38, 131]], used: false, note: "9-slice green hedge enclosure" },
3402
+ { name: "Picket fence", frame: [45, 140, 5, 11], tiles: [[45, 130]], used: false, note: "light picket fence \u2014 9-slice + gate" },
3403
+ { name: "Wooden fence / gate", frame: [52, 138, 5, 8], tiles: [[52, 131]], used: false, note: "wooden fence/gate frame" },
3404
+ // ── Grates / arches (rows ~120-127) ─────────────────────────────────────────────────────────
3405
+ { name: "Stone arch (gate/bridge)", frame: [12, 122, 5, 3], tiles: [[12, 120]], used: false, note: "open stone archway" },
3406
+ { name: "Iron grate / portcullis", frame: [12, 126, 5, 3], tiles: [[12, 124]], used: false, note: "barred iron gate (sewer/gate)" },
3407
+ // ── Buildings — premade facades + components (3 colour sets: red cols 1-37 / green 39-75 / purple 77-113)
3408
+ {
3409
+ name: "Building facades (\xD73 colours)",
3410
+ frame: [1, 32, 37, 30],
3411
+ tiles: [[1, 3]],
3412
+ used: false,
3413
+ note: "PREMADE full house facades + corner/conical towers \u2014 drop-in buildings. Red cols 1-37, green 39-75, purple 77-113"
3414
+ },
3415
+ {
3416
+ name: "Roof pieces (\xD73 colours)",
3417
+ frame: [1, 60, 37, 24],
3418
+ tiles: [[1, 37]],
3419
+ used: false,
3420
+ note: "ridge / slope / eave / cap / gable \u2014 the roof overlay for assembled buildings; same 3 colour columns"
3421
+ },
3422
+ {
3423
+ name: "Walls A \u2014 light stone (9-slice + door + windows)",
3424
+ frame: [20, 85, 13, 15],
3425
+ tiles: [[20, 71]],
3426
+ used: false,
3427
+ note: "wall 9-slice with baked door + window openings (light tan stone), rows 67-90"
3428
+ },
3429
+ {
3430
+ name: "Walls B \u2014 grey stone (9-slice + door + windows)",
3431
+ frame: [20, 112, 16, 19],
3432
+ tiles: [[20, 94]],
3433
+ used: false,
3434
+ note: "second wall material (grey stone), rows 94-118"
3435
+ },
3436
+ { name: "Doors", frame: [58, 102, 8, 5], tiles: [[58, 98]], used: false, note: "wooden double / arched / portcullis doors" },
3437
+ { name: "Arched windows (glass)", frame: [34, 77, 2, 6], tiles: [[34, 72]], used: false, note: "stained-glass arched windows" }
3438
+ // TODO: split the wall sets into individual 9-slice anchors (BLOCK/FILL/door/window cells) when the
3439
+ // building generator needs them; and enumerate the per-colour roof sub-pieces. No water tiles in this pack.
3440
+ ];
3441
+ var MC_PROP_CATALOG = [
3442
+ { name: "Crates & sacks", frame: [2, 8, 5, 3], tiles: [[2, 8]], used: false, note: "wooden crates + sack" },
3443
+ { name: "Market produce / small goods", frame: [8, 9, 10, 5], tiles: [[8, 9]], used: false, note: "small stall goods (food/wares)" },
3444
+ { name: "Barrel + lamp (stacked)", frame: [22, 9, 1, 7], tiles: [[22, 9]], used: false, note: "1-wide barrel + lantern" },
3445
+ { name: "Market stalls + well", frame: [24, 9, 19, 10], tiles: [[24, 9]], used: false, note: "awning shop stalls + a well/fountain" },
3446
+ { name: "Cloth / meat stalls", frame: [44, 9, 7, 4], tiles: [[44, 9]], used: false, note: "covered market tables" },
3447
+ { name: "Wooden beam", frame: [1, 17, 3, 3], tiles: [[1, 17]], used: false, note: "plank/beam" },
3448
+ { name: "Benches", frame: [3, 19, 2, 6], tiles: [[3, 19]], used: false, note: "wooden benches" },
3449
+ { name: "Post / plank", frame: [6, 17, 1, 3], tiles: [[6, 17]], used: false, note: "tall post" },
3450
+ { name: "Lamp posts", frame: [8, 20, 11, 9], tiles: [[8, 20]], used: false, note: "street lamp posts / torches" },
3451
+ { name: "Furniture & barrels (mixed)", frame: [22, 23, 26, 13], tiles: [[22, 23]], used: false, note: "barrels, tables, signs, cart wheel \u2014 general street/yard clutter" },
3452
+ { name: "Barrels (stacked)", frame: [49, 18, 1, 6], tiles: [[49, 18]], used: false, note: "two stacked barrels" },
3453
+ { name: "Bed / cart", frame: [51, 15, 2, 2], tiles: [[51, 15]], used: false, note: "small bed or hand-cart" }
3454
+ ];
3455
+
3456
+ // ../auto-battler/src/render/mountainStronghold.js
3457
+ var MS = "/assets/minifantasy/Minifantasy_Mountain_Stronghold_v1.0/Minifantasy_Mountain_Stronghold_Assets";
3458
+ var MS_TILES_URL = `${MS}/Tileset/Tileset.png`;
3459
+ var MS_PROPS_URL = `${MS}/Props/Props.png`;
3460
+ var MS_MOCKUP_URL = `${MS}/Mockup_Exterior.png`;
3461
+ var MS_PREMADE_DIR = `${MS}/Premade/Premade_Exterior/_Separate_Layers`;
3462
+ var TILE9 = 8;
3463
+ var CHUNK8 = 32;
3464
+ var MS_REF = {
3465
+ dir: MS_PREMADE_DIR,
3466
+ w: 488,
3467
+ h: 592,
3468
+ bg: "#14171c",
3469
+ layers: [
3470
+ ["Exterior_l-bg", "Background", "ground"],
3471
+ ["Exterior_k-jagged_cliffs", "Jagged cliffs", "ground"],
3472
+ ["Exterior_j-snow_1", "Snow 1", "ground"],
3473
+ ["Exterior_i-structures_built_into_cliffs", "Structures in cliffs", "structure"],
3474
+ ["Exterior_h-props", "Props", "scatter"],
3475
+ ["Exterior_g-bridges", "Bridges", "structure"],
3476
+ ["Exterior_f-battlements_and_rooftops", "Battlements & rooftops", "structure"],
3477
+ ["Exterior_e-snow_2", "Snow 2", "ground"],
3478
+ ["Exterior_d-doors_windows", "Doors & windows", "structure"],
3479
+ ["Exterior_c-stairs", "Stairs", "structure"],
3480
+ ["Exterior_b-human", "Characters", "scatter"],
3481
+ ["Exterior_a-shadows", "Shadows", "lighting"]
3482
+ ]
3483
+ };
3484
+ var FLOOR_BASE = [39, 85];
3485
+ var FLOOR_VARS = [[40, 85], [39, 86], [40, 86], [39, 87], [40, 87]];
3486
+ var FLOOR_RATE = 5;
3487
+ var COL_STONE3 = [144, 148, 137];
3488
+ var MS_MAP_ASSETS = [MS_TILES_URL];
3489
+ var mountainStrongholdConfig = (seed) => ({
3490
+ seed,
3491
+ tile: TILE9,
3492
+ chunk: CHUNK8,
3493
+ background: "#2c2f2c",
3494
+ async load({ Assets }) {
3495
+ const ctx = { tiles: null };
3496
+ const t = await Assets.load(MS_TILES_URL);
3497
+ t.source.scaleMode = "nearest";
3498
+ ctx.tiles = t.source;
3499
+ return ctx;
3500
+ },
3501
+ bake({ x0, y0, seed: seed2, ctx, add, accept = () => true }) {
3502
+ for (let ty = 0; ty < CHUNK8; ty++) for (let tx = 0; tx < CHUNK8; tx++) {
3503
+ if (!accept(tx, ty)) continue;
3504
+ const wx = x0 + tx, wy = y0 + ty;
3505
+ add(ctx.tiles, ...sparse(FLOOR_BASE, FLOOR_VARS, wx, wy, 0, FLOOR_RATE, seed2), tx, ty);
3506
+ }
3507
+ return {};
3508
+ },
3509
+ macroColor() {
3510
+ return COL_STONE3;
3511
+ }
3512
+ });
3513
+ function createMountainStrongholdMap(pixi, host, opts = {}) {
3514
+ return createChunkedMap(pixi, host, { ...mountainStrongholdConfig(opts.seed ?? 1), keyboardPan: opts.keyboardPan });
3515
+ }
3516
+ var MS_TILE_CATALOG = [
3517
+ // ── Floor band (rows 85-108) ─────────────────────────────────────────────────────────────────
3518
+ {
3519
+ name: "Stone floor (grey + shades)",
3520
+ frame: [38, 107, 3, 23],
3521
+ tiles: [FLOOR_BASE, ...FLOOR_VARS],
3522
+ used: true,
3523
+ note: "grey floor base [39,85] + variants, via sparse() (Generated ground). Also green-grey [42,85] & brown [46,85] shades"
3524
+ },
3525
+ { name: "Flagstone courtyard", frame: [101, 107, 19, 23], tiles: [[101, 85]], used: false, note: "large paved courtyard / autotile area" },
3526
+ { name: "Hanging banners (red)", frame: [123, 107, 5, 16], tiles: [[123, 92]], used: false, note: "cloth banners draped on walls" },
3527
+ // ── Walls / structures (snowy rows 0-42; plain rows 43-84) ───────────────────────────────────
3528
+ { name: "Cliff cave / tunnel mouth", frame: [1, 28, 13, 27], tiles: [[1, 2]], used: false, note: "dark opening bored into the cliff (snowy; plain [1,45])" },
3529
+ { name: "Fortress wall (9-slice + windows)", frame: [34, 24, 13, 24], tiles: [[34, 1]], used: false, note: "stone wall block with baked windows (snowy; plain [34,44])" },
3530
+ { name: "Wall with arched door", frame: [61, 22, 9, 20], tiles: [[61, 3]], used: false, note: "wall section with an arched doorway" },
3531
+ { name: "Wall edges / pilasters", frame: [28, 25, 2, 15], tiles: [[28, 11]], used: false, note: "vertical wall-edge / pilaster strips" },
3532
+ { name: "Corner tower", frame: [156, 35, 11, 30], tiles: [[156, 6]], used: false, note: "round corner tower (snowy; plain [156,49])" },
3533
+ { name: "Curtain wall (tall)", frame: [170, 36, 7, 36], tiles: [[170, 1]], used: false, note: "tall curtain-wall section" },
3534
+ { name: "Great gate arch", frame: [180, 36, 23, 37], tiles: [[180, 0]], used: false, note: "large gatehouse archway (snowy; plain [180,43])" },
3535
+ { name: "Bridge / aqueduct arch", frame: [102, 27, 24, 23], tiles: [[102, 5]], used: false, note: "arched stone bridge / aqueduct span" },
3536
+ { name: "Battlements (crenellations)", frame: [148, 35, 7, 9], tiles: [[148, 27]], used: false, note: "wall-top merlons + crenellations" },
3537
+ { name: "Arched gateway opening", frame: [16, 67, 7, 12], tiles: [[16, 56]], used: false, note: "open arched gateway (plain set)" }
3538
+ // TODO: split wall blocks into 9-slice cells for the building generator (Phase 3); stairs/snow detail.
3539
+ ];
3540
+ var MS_PROP_CATALOG = [
3541
+ { name: "Crates & stone braziers", frame: [1, 16, 7, 15], tiles: [[1, 16]], used: false, note: "wooden crates + stone fire braziers/stands" },
3542
+ { name: "Wooden table / cart", frame: [9, 10, 3, 9], tiles: [[9, 10]], used: false, note: "table or hand-cart" },
3543
+ { name: "Banners, racks & barrels", frame: [13, 17, 20, 17], tiles: [[13, 17]], used: false, note: "hanging banners, weapon racks, barrels, bones \u2014 mixed hall clutter" },
3544
+ { name: "Rubble / small rocks", frame: [3, 33, 1, 13], tiles: [[3, 33]], used: false, note: "thin rubble columns (also at [9] and [23])" },
3545
+ { name: "Snow-capped boulders", frame: [14, 33, 7, 14], tiles: [[14, 33]], used: false, note: "large rocks with snow" },
3546
+ { name: "Icy boulders", frame: [26, 33, 7, 14], tiles: [[26, 33]], used: false, note: "ice-glazed boulders" }
3547
+ ];
3548
+
3549
  // ../auto-battler/src/render/sceneViewer.js
3550
  function createSceneViewer(pixi, host, { layers, dir, sceneW, sceneH, background = "#15121b", pulse = null }) {
3551
  const { Application, Assets, Sprite, Container } = pixi;
 
3884
  root = new Container();
3885
  app.stage.addChild(root);
3886
  const skin = skins[skinKey];
3887
+ const TILE10 = skin.tile || 8;
3888
+ const res = render3({ app, root, skin, seed, TILE: TILE10, tex, sources, Sprite, Graphics, Container }) || {};
3889
  lastBounds = res.bounds || null;
3890
+ lastTile = res.tile ?? TILE10;
3891
  lastSnapshot = res.snapshot || {};
3892
  if (res.tick) {
3893
  tick = res.tick;
 
3961
  fit
3962
  });
3963
  }
3964
+ function render({ root, skin, seed, TILE: TILE10, tex, sources, Sprite, Graphics }) {
3965
  const src = sources.get(skin.url);
3966
  const { W, H, floor, rooms, bounds } = generateRooms(seed);
3967
  const isF = (x, y) => x >= 0 && y >= 0 && x < W && y < H && floor[y * W + x] === 1;
3968
  const addS = (source, coord, x, y) => {
3969
+ const sp = new Sprite(tex(source, coord[0], coord[1], TILE10));
3970
+ sp.x = x * TILE10;
3971
+ sp.y = y * TILE10;
3972
  root.addChild(sp);
3973
  return sp;
3974
  };
3975
  const add = (coord, x, y) => addS(src, coord, x, y);
3976
+ const c = { skin, TILE: TILE10, W, H, rooms, seed, isF, add, addS, root, sources, Graphics };
3977
  drawWalls(c);
3978
  drawFloor(c);
3979
  drawShadows(c);
 
3983
  const tick = torch.flames.length && torch.flameSrc ? (ticker) => {
3984
  animPhase += 0.14 * ticker.deltaTime;
3985
  const fi = Math.floor(animPhase) % torch.flameFrames;
3986
+ for (const fl of torch.flames) fl.sp.texture = tex(torch.flameSrc, fl.col0 + torch.flameStride * fi, fl.row, TILE10);
3987
  if (torch.glowG) torch.glowG.alpha = 0.78 + 0.22 * Math.sin(animPhase * 0.9);
3988
  } : null;
3989
+ return { bounds, tile: TILE10, snapshot: { rooms: rooms.length }, tick };
3990
  }
3991
+ function fit(app, root, b, TILE10) {
3992
  const sw = app.screen.width, sh = app.screen.height;
3993
+ const cw = (b.x1 - b.x0 + 3) * TILE10, ch = (b.y1 - b.y0 + 4) * TILE10;
3994
  const s = Math.max(1, Math.floor(Math.min(sw / cw, sh / ch)));
3995
  root.scale.set(s);
3996
+ root.x = Math.round((sw - cw * s) / 2 - (b.x0 - 1) * TILE10 * s);
3997
+ root.y = Math.round((sh - ch * s) / 2 - (b.y0 - 2) * TILE10 * s);
3998
  }
3999
  function drawWalls(c) {
4000
  if (c.skin.wall && !c.skin.walls) drawBackWall(c);
 
4102
  }
4103
  }
4104
  function drawShadows(c) {
4105
+ const { W, H, TILE: TILE10, isF, root, Graphics } = c;
4106
  const shade = new Graphics();
4107
  for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
4108
  if (!isF(x, y)) continue;
4109
  if (!isF(x, y - 1)) {
4110
+ shade.rect(x * TILE10, y * TILE10, TILE10, TILE10).fill({ color: 0, alpha: 0.3 });
4111
+ if (isF(x, y + 1)) shade.rect(x * TILE10, (y + 1) * TILE10, TILE10, TILE10).fill({ color: 0, alpha: 0.12 });
4112
  } else if (!isF(x - 1, y) || !isF(x + 1, y)) {
4113
+ shade.rect(x * TILE10, y * TILE10, TILE10, TILE10).fill({ color: 0, alpha: 0.12 });
4114
  }
4115
  }
4116
  root.addChild(shade);
 
4157
  }
4158
  }
4159
  function drawTorches(c) {
4160
+ const { skin, W, H, TILE: TILE10, isF, sources, root, Graphics, addS } = c;
4161
  const torch = skin.torch, torchSrc = torch && sources.get(torch.url);
4162
  if (!(torch && torchSrc)) return { flames: [], glowG: null, flameSrc: null, flameStride: 1, flameFrames: 1 };
4163
  const flames = [];
 
4166
  for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
4167
  if (!isF(x, y) || isF(x, y - 1)) continue;
4168
  if ((x * 7 + y * 13) % 5 !== 0) continue;
4169
+ const cx = (x + f.w / 2) * TILE10, cy = (y - 1) * TILE10;
4170
+ for (const [r, a] of [[3.2, 0.05], [2.1, 0.08], [1.2, 0.13]]) glow.circle(cx, cy, r * TILE10).fill({ color: torch.glow, alpha: a });
4171
  spots.push([x, y]);
4172
  }
4173
  root.addChild(glow);
 
4208
  fit: fit2
4209
  });
4210
  }
4211
+ function render2({ root, skin, seed, TILE: TILE10, tex, sources, Sprite, Graphics }) {
4212
  const src = sources.get(skin.url);
4213
  const shaftW = skin.crown ? skin.crown.shaftW : (skin.roof?.w ?? 7) - 2;
4214
  const winH = skin.window?.h ?? 2, doorH = skin.door?.h ?? 2;
 
4225
  const corn = spec.roof === "none" ? 0 : 1;
4226
  const dy = -(h2 + PLINTH - 1);
4227
  const add = (coord, x, y) => {
4228
+ const sp = new Sprite(tex(src, coord[0], coord[1], TILE10));
4229
+ sp.x = (x + ox) * TILE10;
4230
+ sp.y = (y + dy) * TILE10;
4231
  root.addChild(sp);
4232
  return sp;
4233
  };
4234
  const shadow = new Graphics();
4235
+ shadow.ellipse((ox + w / 2) * TILE10, TILE10 - TILE10 * 0.3, wb / 2 * TILE10 + TILE10 * 0.5, TILE10 * 0.7).fill({ color: 0, alpha: 0.22 });
4236
  root.addChild(shadow);
4237
  for (let y = 0; y < h2; y++) for (let x = 0; x < w; x++) {
4238
  const top = y === 0, bot = y === h2 - 1, left = x === 0, right = x === w - 1;
 
4294
  ox += w + GAP;
4295
  }
4296
  const bounds = { x0: Math.min(x0, 0), y0, x1: Math.max(x1, ox - GAP), y1: 2 };
4297
+ return { bounds, tile: TILE10, snapshot: { w: spec0?.w ?? 0, h: spec0?.h ?? 0, roof: spec0?.roof } };
4298
  }
4299
+ function fit2(app, root, b, TILE10) {
4300
  const sw = app.screen.width, sh = app.screen.height;
4301
+ const cw = (b.x1 - b.x0) * TILE10, ch = (b.y1 - b.y0) * TILE10;
4302
+ const pad = 4 * TILE10;
4303
  const s = Math.max(1, Math.floor(Math.min(sw / (cw + pad), sh / (ch + pad))));
4304
  root.scale.set(s);
4305
+ root.x = Math.round((sw - cw * s) / 2 - b.x0 * TILE10 * s);
4306
+ root.y = Math.round((sh - ch * s) / 2 - b.y0 * TILE10 * s);
4307
  }
4308
 
4309
  // ../auto-battler/src/render/mapConfigs.js
4310
+ var CITY_SKINS = {
4311
+ medieval: { label: "Medieval City", url: MC_TILES_URL },
4312
+ mountain: { label: "Mountain Stronghold", url: MS_TILES_URL }
4313
+ };
4314
+ var CITY_REF = { medieval: MC_REF, mountain: MS_REF };
4315
+ var citySheets = (skin) => ({
4316
+ medieval: [
4317
+ { key: "tiles", label: "Tiles", url: MC_TILES_URL, catalog: MC_TILE_CATALOG },
4318
+ { key: "props", label: "Props", url: MC_PROPS_URL, catalog: MC_PROP_CATALOG }
4319
+ ],
4320
+ mountain: [
4321
+ { key: "tiles", label: "Tiles", url: MS_TILES_URL, catalog: MS_TILE_CATALOG },
4322
+ { key: "props", label: "Props", url: MS_PROPS_URL, catalog: MS_PROP_CATALOG }
4323
+ ]
4324
+ })[skin] || [];
4325
  var ASSET = "/assets/minifantasy";
4326
  var ORC4 = `${ASSET}/Minifantasy_Orc_Kingdom_v1.0/Minifantasy_Orc_Kingdom_Assets`;
4327
  var NECRO4 = `${ASSET}/Minifantasy_Necropolis_v1.0/Minifantasy_Necropolis_Assets`;
 
4499
  ...NECRO_SHEETS.map((s) => s.url),
4500
  ...ORC_SHEETS.map((s) => s.url),
4501
  ...FP_SHEETS.map((s) => s.url),
4502
+ // Cities: each skin's generated-ground sheet, clickable Tiles/Props, and premade reference layers.
4503
+ ...MC_MAP_ASSETS,
4504
+ MC_TILES_URL,
4505
+ MC_PROPS_URL,
4506
+ ...sceneUrls(MC_REF.dir, MC_REF.layers),
4507
+ ...MS_MAP_ASSETS,
4508
+ MS_TILES_URL,
4509
+ MS_PROPS_URL,
4510
+ ...sceneUrls(MS_REF.dir, MS_REF.layers),
4511
  // Premade-scene reference layers.
4512
  ...sceneUrls(NECRO_EXT_DIR, NECRO_LAYERS),
4513
  ...sceneUrls(ORC_LAYER_DIR, ORC_LAYERS),
 
4534
  var randomSeed = () => Math.floor(Date.now() % 1e6 + Math.random() * 1e6) >>> 0;
4535
  var infiniteMeta = (s) => s?.zoom != null ? `\u221E \xB7 seed ${s.seed} \xB7 (${s.cx}, ${s.cy}) \xB7 ${s.zoom.toFixed(2)}\xD7 \xB7 drag / WASD / scroll` : "loading\u2026";
4536
  function makeGridOverlay() {
4537
+ const TILE10 = 8;
4538
  const cv = h("canvas", { class: "necro-grid-overlay" });
4539
  cv.style.display = "none";
4540
  let raf = 0, ctrl = null;
 
4547
  if (cv.height !== H) cv.height = H;
4548
  const ctx = cv.getContext("2d");
4549
  ctx.clearRect(0, 0, W, H);
4550
+ const z = cam.zoom, cell = TILE10 * z;
4551
  if (cell >= 6) {
4552
+ const sx = (wx) => (wx * TILE10 - cam.x) * z + W / 2;
4553
+ const sy = (wy) => (wy * TILE10 - cam.y) * z + H / 2;
4554
+ const x0 = Math.floor((cam.x - W / 2 / z) / TILE10), x1 = Math.ceil((cam.x + W / 2 / z) / TILE10);
4555
+ const y0 = Math.floor((cam.y - H / 2 / z) / TILE10), y1 = Math.ceil((cam.y + H / 2 / z) / TILE10);
4556
  ctx.strokeStyle = "rgba(255,70,70,0.45)";
4557
  ctx.lineWidth = 1;
4558
  ctx.beginPath();
 
4981
  var SKIN_MODES = [{ key: "generated", label: "Generated" }, { key: "reference", label: "Reference" }, { key: "tilesheet", label: "Tilesheet" }];
4982
  var skinList = (skins) => Object.entries(skins).map(([key, s]) => ({ key, label: s.label }));
4983
  var sceneFromRef = (pixi, host, ref) => createSceneViewer(pixi, host, { layers: ref.layers, dir: ref.dir, sceneW: ref.w, sceneH: ref.h, background: ref.bg, pulse: ref.pulse });
4984
+ var CITY_MAPS = { medieval: createMedievalCityMap, mountain: createMountainStrongholdMap };
4985
  var PAGES = {
4986
  world: (pixi, body, opts) => makePage(pixi, body, {
4987
  title: "World Map",
 
5054
  sheets: () => DD_SHEETS,
5055
  generatedMeta: infiniteMeta
5056
  }),
5057
+ // Cities — same skin-based shape as Interiors/Towers (one pill, a style picker), with the standard
5058
+ // Generated / Reference / Tilesheet sub-pills. Reference = the pack's layer-by-layer premade;
5059
+ // Tilesheet = clickable Tiles/Props catalogue; Generated = the outdoor-floor ground (buildings TBD).
5060
+ cities: (pixi, body) => makePage(pixi, body, {
5061
+ title: "Cities",
5062
+ subtitle: () => "assembled \xB7 layered",
5063
+ modes: SKIN_MODES,
5064
+ skins: skinList(CITY_SKINS),
5065
+ grid: true,
5066
+ makeGenerated: (p, host, seed, skin) => CITY_MAPS[skin](p, host, { seed }),
5067
+ makeScene: (p, host, skin) => sceneFromRef(p, host, CITY_REF[skin]),
5068
+ sheets: (skin) => citySheets(skin),
5069
+ generatedMeta: (snap, label2) => snap ? `ground \xB7 ${label2} \xB7 seed ${snap.seed}` : "loading\u2026"
5070
+ }),
5071
  interiors: (pixi, body) => makePage(pixi, body, {
5072
  title: "Interiors",
5073
  modes: SKIN_MODES,
 
5093
  ["gameworld", "Game World"],
5094
  ["world", "World Map"],
5095
  { group: "biome", label: "Biome", pages: [["plains", "Forgotten Plains"], ["desert", "Desolate Desert"], ["orc", "Orc Kingdom"], ["necropolis", "Necropolis"]] },
5096
+ ["cities", "Cities"],
5097
  ["interiors", "Interiors"],
5098
  ["towers", "Towers"]
5099
  ];