polats Claude Opus 4.8 (1M context) commited on
Commit
6d04e7b
·
1 Parent(s): d649f36

Rebuild map bundle: Forgotten Plains cliff stair exits

Browse files

Picks up the auto-battler change adding narrow + wide stone stair exits to
every cliff edge (stairTileAt in bakeCliffs).

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

Files changed (1) hide show
  1. web/mapSandbox.js +129 -15
web/mapSandbox.js CHANGED
@@ -710,10 +710,67 @@ function centerTile([c0, r0], dNW, dNE, dSW, dSE) {
710
  if (dSW) return [c0, r0 + 4];
711
  return [c0 + 1, r0 + 4];
712
  }
713
- function bakeCliffs(cfg, { chunk, x0, y0, seed, raised, place, accept = () => true }) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
714
  for (let ty = 0; ty < chunk; ty++) for (let tx = 0; tx < chunk; tx++) {
715
  if (!accept(tx, ty)) continue;
716
  const wx = x0 + tx, wy = y0 + ty;
 
 
 
 
 
 
 
717
  if (raised(wx, wy)) {
718
  const bN = !raised(wx, wy - 1), bE = !raised(wx + 1, wy), bS = !raised(wx, wy + 1), bW = !raised(wx - 1, wy);
719
  if (bN && bW) place(cfg.LIP_TL, tx, ty);
@@ -977,8 +1034,54 @@ var FP_CLIFF = {
977
  FACE_VAR: [[], []],
978
  FACE_L: [[24, 19], [24, 20]],
979
  FACE_R: [[26, 19], [26, 20]],
980
- RATE: 4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
981
  };
 
982
  var COL_CLIFF = [150, 138, 112];
983
  var FOLIAGE = [
984
  { weight: 5, tiles: [[9, 6], [10, 6], [11, 6]] },
@@ -1260,7 +1363,15 @@ var fpConfig = (seed) => ({
1260
  sp.y = ty * TILE3;
1261
  tmp.addChild(sp);
1262
  }
1263
- bakeCliffs(FP_CLIFF, { chunk: CHUNK2, x0, y0, seed: seed2, raised: isRaisedAt, place, accept });
 
 
 
 
 
 
 
 
1264
  if (ctx.props) {
1265
  const inGrove = (wx, wy) => forestRegion[idx(wx - x0 + M, wy - y0 + M)] === 1;
1266
  const bx0 = Math.floor(x0 / FOREST_BLOCK_X), bx1 = Math.floor((x0 + CHUNK2 - 1) / FOREST_BLOCK_X);
@@ -1273,13 +1384,14 @@ var fpConfig = (seed) => ({
1273
  if (!inGrove(wx, wy)) continue;
1274
  if ((h2 >>> 8 & 65535) / 65536 >= FOREST_FILL) continue;
1275
  if (atW(wx, wy) || atS(wx, wy)) continue;
1276
- if (isRaisedAt(wx, wy)) continue;
1277
- let onFace = false;
1278
- for (let k = 1; k <= FP_CLIFF.FACE_H; k++) if (isRaisedAt(wx, wy - k)) {
1279
- onFace = true;
1280
- break;
 
 
1281
  }
1282
- if (onFace) continue;
1283
  const T = TREES[(h2 >>> 24) % TREES.length];
1284
  const flip = (h2 >>> 20 & 255) / 256 < TREE_FLIP_RATE;
1285
  const sc = TREE_SCALE_BASE * (1 + ((h2 >>> 12 & 255) / 256 - 0.5) * 2 * TREE_SCALE_JITTER);
@@ -1313,13 +1425,15 @@ var fpConfig = (seed) => ({
1313
  if (!accept(wx - x0, wy - y0)) continue;
1314
  if (!inClump(wx, wy)) continue;
1315
  if ((h2 >>> 8 & 65535) / 65536 >= STONE_CLUMP_FILL) continue;
1316
- if (atW(wx, wy) || isRaisedAt(wx, wy)) continue;
1317
- let onFace = false;
1318
- for (let k = 1; k <= FP_CLIFF.FACE_H; k++) if (isRaisedAt(wx, wy - k)) {
1319
- onFace = true;
1320
- break;
 
 
 
1321
  }
1322
- if (onFace) continue;
1323
  let pick = (h2 >>> 20) % STONE_WEIGHT, st = STONES[0];
1324
  for (const s of STONES) {
1325
  if (pick < s.weight) {
 
710
  if (dSW) return [c0, r0 + 4];
711
  return [c0 + 1, r0 + 4];
712
  }
713
+ function bakeCliffs(cfg, { chunk, x0, y0, seed, raised, place, accept = () => true, stairAt = null, stairAtV = null }) {
714
+ const ST = cfg.STAIR && (stairAt || stairAtV) ? cfg.STAIR : null;
715
+ const useNS = ST && stairAt, useWE = ST && stairAtV;
716
+ const isWide = (ax, ay, room) => room && rhash(ax, ay, ST.WIDE_SALT, seed) % 2 === 0;
717
+ const sAnchor = (x, y) => raised(x, y) && !raised(x, y + 1) && raised(x - 1, y) && raised(x + 1, y) && stairAt(x, y);
718
+ const nAnchor = (x, y) => raised(x, y) && !raised(x, y - 1) && raised(x - 1, y) && raised(x + 1, y) && stairAt(x, y);
719
+ const wAnchor = (x, y) => raised(x, y) && !raised(x - 1, y) && raised(x + 1, y) && raised(x, y - 1) && raised(x, y + 1) && !raised(x - 1, y + 1) && stairAtV(x, y);
720
+ const eAnchor = (x, y) => raised(x, y) && !raised(x + 1, y) && raised(x - 1, y) && raised(x, y - 1) && raised(x, y + 1) && !raised(x + 1, y + 1) && stairAtV(x, y);
721
+ const sRoom = (x, y) => !raised(x + 1, y + 1) && raised(x + 2, y) && !raised(x + 2, y + 1) && raised(x + 3, y);
722
+ const nRoom = (x, y) => !raised(x + 1, y - 1) && raised(x + 2, y) && !raised(x + 2, y - 1) && raised(x + 3, y);
723
+ const wRoom = (x, y) => raised(x, y + 2) && !raised(x - 1, y + 2);
724
+ const eRoom = (x, y) => raised(x, y + 2) && !raised(x + 1, y + 2);
725
+ const W_OFF = [[0, 0], [0, 1], [-1, 0], [-1, 1], [-1, 2]];
726
+ const E_OFF = [[0, 0], [0, 1], [1, 0], [1, 1], [1, 2]];
727
+ const sDepth = useNS ? Math.max(ST.S.NARROW.length, ST.S.WIDE.length) : 0;
728
+ function stairTileAt(wx, wy) {
729
+ if (useNS) {
730
+ for (let drow = 0; drow < sDepth; drow++) for (let dcol = 0; dcol <= 2; dcol++) {
731
+ const ax = wx - dcol, ay = wy - drow;
732
+ if (!sAnchor(ax, ay)) continue;
733
+ if (isWide(ax, ay, sRoom(ax, ay))) {
734
+ if (drow < ST.S.WIDE.length) return ST.S.WIDE[drow][dcol];
735
+ } else if (dcol === 0 && drow < ST.S.NARROW.length) return ST.S.NARROW[drow];
736
+ }
737
+ for (let dcol = 0; dcol <= 2; dcol++) {
738
+ if (nAnchor(wx - dcol, wy)) {
739
+ if (isWide(wx - dcol, wy, nRoom(wx - dcol, wy))) return ST.N.WIDE.LIP[dcol];
740
+ if (dcol === 0) return ST.N.NARROW.LIP;
741
+ }
742
+ if (nAnchor(wx - dcol, wy + 1)) {
743
+ if (isWide(wx - dcol, wy + 1, nRoom(wx - dcol, wy + 1))) return ST.N.WIDE.CAP[dcol];
744
+ if (dcol === 0) return ST.N.NARROW.CAP;
745
+ }
746
+ }
747
+ }
748
+ if (useWE) {
749
+ for (const [dx, dy] of W_OFF) {
750
+ const ax = wx - dx, ay = wy - dy;
751
+ if (!wAnchor(ax, ay)) continue;
752
+ const e = (isWide(ax, ay, wRoom(ax, ay)) ? ST.W.WIDE : ST.W.NARROW).find((o) => o.dx === dx && o.dy === dy);
753
+ if (e) return e.t;
754
+ }
755
+ for (const [dx, dy] of E_OFF) {
756
+ const ax = wx - dx, ay = wy - dy;
757
+ if (!eAnchor(ax, ay)) continue;
758
+ const e = (isWide(ax, ay, eRoom(ax, ay)) ? ST.E.WIDE : ST.E.NARROW).find((o) => o.dx === dx && o.dy === dy);
759
+ if (e) return e.t;
760
+ }
761
+ }
762
+ return null;
763
+ }
764
  for (let ty = 0; ty < chunk; ty++) for (let tx = 0; tx < chunk; tx++) {
765
  if (!accept(tx, ty)) continue;
766
  const wx = x0 + tx, wy = y0 + ty;
767
+ if (ST) {
768
+ const st = stairTileAt(wx, wy);
769
+ if (st) {
770
+ place(st, tx, ty);
771
+ continue;
772
+ }
773
+ }
774
  if (raised(wx, wy)) {
775
  const bN = !raised(wx, wy - 1), bE = !raised(wx + 1, wy), bS = !raised(wx, wy + 1), bW = !raised(wx - 1, wy);
776
  if (bN && bW) place(cfg.LIP_TL, tx, ty);
 
1034
  FACE_VAR: [[], []],
1035
  FACE_L: [[24, 19], [24, 20]],
1036
  FACE_R: [[26, 19], [26, 20]],
1037
+ RATE: 4,
1038
+ // Stair exits cut into straight cliff edges. Two sheet variants, one tile wide (cross at cols 2–6)
1039
+ // and two tiles wide (cross at cols 8–14); bakeCliffs picks per-exit by hash. Every tile below is
1040
+ // a real non-empty sheet cell — the crosses have transparent corners, so placing those would bleed
1041
+ // the grass base through (the bug the NARROW arms used to hit). Layout per direction:
1042
+ // • S — NARROW: one column, [lip, step, step, step(onto ground)]. WIDE: 3 columns × those 4 rows.
1043
+ // • N — LIP on the raised back edge (grass+steps) + CAP one tile north on the ground; ×3 cols wide.
1044
+ // • W/E — a descent off the side wall. Each tile carries its {dx,dy} cell-offset from the wall
1045
+ // anchor. NARROW: 3-tile L. WIDE: 2-tall entry on the wall + a 3-tall stone descent beside it.
1046
+ STAIR: {
1047
+ WIDE_SALT: 359697,
1048
+ // hash salt deciding narrow-vs-wide at each exit (~50/50)
1049
+ S: {
1050
+ NARROW: [[4, 18], [4, 19], [4, 20], [4, 20]],
1051
+ WIDE: [
1052
+ [[10, 19], [11, 19], [12, 19]],
1053
+ [[10, 20], [11, 20], [12, 20]],
1054
+ [[10, 21], [11, 21], [12, 21]],
1055
+ [[10, 21], [11, 21], [12, 21]]
1056
+ ]
1057
+ },
1058
+ N: {
1059
+ NARROW: { LIP: [4, 16], CAP: [4, 15] },
1060
+ WIDE: { LIP: [[10, 16], [11, 16], [12, 16]], CAP: [[10, 15], [11, 15], [12, 15]] }
1061
+ },
1062
+ W: {
1063
+ NARROW: [{ dx: 0, dy: 0, t: [3, 17] }, { dx: -1, dy: 0, t: [2, 17] }, { dx: -1, dy: 1, t: [2, 18] }],
1064
+ WIDE: [
1065
+ { dx: 0, dy: 0, t: [9, 17] },
1066
+ { dx: 0, dy: 1, t: [9, 18] },
1067
+ { dx: -1, dy: 0, t: [8, 17] },
1068
+ { dx: -1, dy: 1, t: [8, 18] },
1069
+ { dx: -1, dy: 2, t: [8, 19] }
1070
+ ]
1071
+ },
1072
+ E: {
1073
+ NARROW: [{ dx: 0, dy: 0, t: [5, 17] }, { dx: 1, dy: 0, t: [6, 17] }, { dx: 1, dy: 1, t: [6, 18] }],
1074
+ WIDE: [
1075
+ { dx: 0, dy: 0, t: [13, 17] },
1076
+ { dx: 0, dy: 1, t: [13, 18] },
1077
+ { dx: 1, dy: 0, t: [14, 17] },
1078
+ { dx: 1, dy: 1, t: [14, 18] },
1079
+ { dx: 1, dy: 2, t: [14, 19] }
1080
+ ]
1081
+ }
1082
+ }
1083
  };
1084
+ var STAIR_SPACING = 14;
1085
  var COL_CLIFF = [150, 138, 112];
1086
  var FOLIAGE = [
1087
  { weight: 5, tiles: [[9, 6], [10, 6], [11, 6]] },
 
1363
  sp.y = ty * TILE3;
1364
  tmp.addChild(sp);
1365
  }
1366
+ const stairAt = (wx, wy) => {
1367
+ const off = hashU32(seed2 ^ 358936, Math.floor(wy / STAIR_SPACING), 0) % STAIR_SPACING;
1368
+ return ((wx - off) % STAIR_SPACING + STAIR_SPACING) % STAIR_SPACING === 0;
1369
+ };
1370
+ const stairAtV = (wx, wy) => {
1371
+ const off = hashU32(seed2 ^ 358940, Math.floor(wx / STAIR_SPACING), 0) % STAIR_SPACING;
1372
+ return ((wy - off) % STAIR_SPACING + STAIR_SPACING) % STAIR_SPACING === 0;
1373
+ };
1374
+ bakeCliffs(FP_CLIFF, { chunk: CHUNK2, x0, y0, seed: seed2, raised: isRaisedAt, place, accept, stairAt, stairAtV });
1375
  if (ctx.props) {
1376
  const inGrove = (wx, wy) => forestRegion[idx(wx - x0 + M, wy - y0 + M)] === 1;
1377
  const bx0 = Math.floor(x0 / FOREST_BLOCK_X), bx1 = Math.floor((x0 + CHUNK2 - 1) / FOREST_BLOCK_X);
 
1384
  if (!inGrove(wx, wy)) continue;
1385
  if ((h2 >>> 8 & 65535) / 65536 >= FOREST_FILL) continue;
1386
  if (atW(wx, wy) || atS(wx, wy)) continue;
1387
+ if (!isRaisedAt(wx, wy)) {
1388
+ let onFace = false;
1389
+ for (let k = 1; k <= FP_CLIFF.FACE_H; k++) if (isRaisedAt(wx, wy - k)) {
1390
+ onFace = true;
1391
+ break;
1392
+ }
1393
+ if (onFace) continue;
1394
  }
 
1395
  const T = TREES[(h2 >>> 24) % TREES.length];
1396
  const flip = (h2 >>> 20 & 255) / 256 < TREE_FLIP_RATE;
1397
  const sc = TREE_SCALE_BASE * (1 + ((h2 >>> 12 & 255) / 256 - 0.5) * 2 * TREE_SCALE_JITTER);
 
1425
  if (!accept(wx - x0, wy - y0)) continue;
1426
  if (!inClump(wx, wy)) continue;
1427
  if ((h2 >>> 8 & 65535) / 65536 >= STONE_CLUMP_FILL) continue;
1428
+ if (atW(wx, wy)) continue;
1429
+ if (!isRaisedAt(wx, wy)) {
1430
+ let onFace = false;
1431
+ for (let k = 1; k <= FP_CLIFF.FACE_H; k++) if (isRaisedAt(wx, wy - k)) {
1432
+ onFace = true;
1433
+ break;
1434
+ }
1435
+ if (onFace) continue;
1436
  }
 
1437
  let pick = (h2 >>> 20) % STONE_WEIGHT, st = STONES[0];
1438
  for (const s of STONES) {
1439
  if (pick < s.weight) {