Spaces:
Running
Running
Rebuild map bundle: Forgotten Plains cliff stair exits
Browse filesPicks 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>
- 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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))
|
| 1277 |
-
|
| 1278 |
-
|
| 1279 |
-
|
| 1280 |
-
|
|
|
|
|
|
|
| 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)
|
| 1317 |
-
|
| 1318 |
-
|
| 1319 |
-
|
| 1320 |
-
|
|
|
|
|
|
|
|
|
|
| 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) {
|