Sloped belts and filters, laid by a 45° drag (t40)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 23:17:55 -04:00
co-authored by Claude Opus 5.5
parent 24eb9559f8
commit e19cb784d2
26 changed files with 531 additions and 110 deletions
+68 -14
View File
@@ -4,7 +4,24 @@
// clear). If a powder grain is blocked, the load above it stands still and the belt slips
// underneath. A filter's own material drops through it instead, unless it is already full.
const CARRY_DEPTH = 96;
// Find the next carrying face at the same height or one grain above/below it.
function conveyorStep(x, y, d) {
const nx = x + d;
if (nx < 0 || nx >= W) return 0;
if ((x >> 2) === (nx >> 2)) return beltGrade(blockP[(y >> 2) * BW + (x >> 2)]) * d;
for (let dy = -1; dy <= 1; dy++) {
const yy = y + dy;
if (yy < 0 || yy >= H) continue;
const b = (yy >> 2) * BW + (nx >> 2), c = blockP[b], i = yy * W + nx;
if ((isBelt(c) || isFilter(c)) && beltFaceY(c, nx >> 2, yy >> 2, nx) === yy &&
(mat[i] === BELTL || mat[i] === BELTR || part[i] === P_FILTER)) return dy;
}
const b = (y >> 2) * BW + (x >> 2);
return beltGrade(blockP[b]) * d;
}
function carryColumn(a, d, pass) {
if (a < 0 || mat[a] === EMPTY) return;
const sy = ((a / W) | 0) + 1, step = conveyorStep(a % W, sy, d);
let blocked = false;
for (let L = 0; L < CARRY_DEPTH; L++, a -= W) {
if (a < 0) return;
@@ -20,20 +37,30 @@ function carryColumn(a, d, pass) {
if (blocked) continue; // filters still sift above a stopped load
const x = a % W, nx = x + d;
if (nx < 0 || nx >= W) return;
const j = a + d, tj = mat[j];
const j = a + d + step * W;
if (j < 0 || j >= N) return;
const tj = mat[j];
// grains on a wet belt push through the water rather than stopping at it
const wet = PH[tj] === PH_LIQUID && !occ[j] && stamp[j] !== tickStamp && (!part[j] || part[j] === P_FILTER || part[j] === P_GOPEN);
if (!wet && !enter(m, PH_POWDER, j, d, 0)) {
if (!wet && !enter(m, PH_POWDER, j, d, step)) {
if (!pass) return;
blocked = true; continue;
}
const y = (a / W) | 0, bj = j + W;
// carried off the end with nothing under it: it leaves at belt speed and falls
if (mat[j] === EMPTY && !part[j] && bj < N && (mat[bj] === EMPTY || PH[mat[bj]] === PH_GAS) && !occ[bj] && !part[bj]) launchGrain(a, nx, y, d * 0.5, 0);
else move(a, j, x, y, nx, y);
if (mat[j] === EMPTY && !part[j] && bj < N && (mat[bj] === EMPTY || PH[mat[bj]] === PH_GAS) && !occ[bj] && !part[bj]) launchGrain(a, nx, y + step, d * 0.5, step * 0.5);
else move(a, j, x, y, nx, y + step);
stats.moved++;
}
}
// The running face grips its whole supported load, including on the ticks between steps.
function holdSlopeColumn(a, pass) {
for (let depth = 0; depth < CARRY_DEPTH && a >= 0; depth++, a -= W) {
const m = mat[a];
if (m === EMPTY || (PH[m] === PH_SOLID && !part[a])) return;
if (PH[m] === PH_POWDER && !occ[a] && !(pass && pass[m])) stamp[a] = tickStamp;
}
}
// Belts are perforated: liquid resting on one drips through to whatever open space is under it.
function beltDrain(b) {
const a = b - W, x = b % W;
@@ -53,23 +80,48 @@ function launchGrain(i, x, y, vx, vy) {
let filterR = [], filterL = [], filtersDirty = true;
function rebuildFilters() {
filtersDirty = false; filterR = []; filterL = [];
for (let b = 0; b < NB; b++) if (blockP[b] === BP_FILTER) (blockV[b] & 128 ? filterR : filterL).push(b);
for (let b = 0; b < NB; b++) if (isFilter(blockP[b])) (blockV[b] & 128 ? filterR : filterL).push(b);
// front of the line first, so a full run moves as one
filterR.sort((a, c) => (c % BW) - (a % BW)); filterL.sort((a, c) => (a % BW) - (c % BW));
}
function filterCarry(b, d) {
if (blockP[b] !== BP_FILTER) return;
const bx = b % BW, y = ((b / BW) | 0) * BS;
function filterCarry(b, d, carrying = true) {
if (!isFilter(blockP[b])) return;
const bx = b % BW, by = (b / BW) | 0, code = blockP[b], y = by * BS;
if (y < 1) return;
if (!carrying && !isSlopeBelt(code)) return;
const c = filterCfg.get(b), pass = c ? c.pass : null;
for (let k = 0; k < BS; k++) {
const x = d > 0 ? bx * BS + BS - 1 - k : bx * BS + k, top = y * W + x;
if (part[top] === P_FILTER) carryColumn(top - W, d, pass);
const x = d > 0 ? bx * BS + BS - 1 - k : bx * BS + k, top = beltFaceY(code, bx, by, x) * W + x;
if (part[top] === P_FILTER) {
if (carrying) carryColumn(top - W, d, pass);
if (isSlopeBelt(code)) holdSlopeColumn(top - W, pass);
}
}
}
// Belts and filters share one front-first order, including their flat/slope joins.
function carryBeltsAndFilters(belts, filters, d, bm, carrying) {
let k = 0, f = 0;
while (k < belts.length || f < filters.length) {
const fx = f < filters.length ? (filters[f] % BW) * BS + (d > 0 ? BS - 1 : 0) : 0;
if (f < filters.length && (k >= belts.length || (fx - belts[k] % W) * d > 0)) { filterCarry(filters[f++], d, carrying); continue; }
const b = belts[k++];
if (mat[b] !== bm || b < W) continue;
const x = b % W, y = (b / W) | 0, code = blockP[(y >> 2) * BW + (x >> 2)];
if (!carrying && !isSlopeBelt(code)) continue;
if (isSlopeBelt(code) && beltFaceY(code, x >> 2, y >> 2, x) !== y) continue;
if (slopeSealOwner(b) >= 0) continue;
const above = mat[b - W];
if (above === BELTL || above === BELTR || netSat(b) <= 0) continue;
if (carrying) {
if (PH[above] === PH_LIQUID) beltDrain(b);
carryColumn(b - W, d, null);
}
if (isSlopeBelt(code)) holdSlopeColumn(b - W, null);
}
}
// Change what a filter does; grains inside it that no longer pass are pushed out on top.
function setFilter(b, cfg) {
if (blockP[b] !== BP_FILTER) return false;
if (!isFilter(blockP[b])) return false;
const rec = filterRec(cfg);
filterCfg.set(b, rec);
const bx = b % BW, by = (b / BW) | 0;
@@ -79,11 +131,13 @@ function setFilter(b, cfg) {
}
// The run of filters in a row that touch block b and do the same thing it does
function filterRun(b) {
if (blockP[b] !== BP_FILTER) return [];
if (!isFilter(blockP[b])) return [];
const f = filterInfo(b), dir = blockV[b] & 128, by = (b / BW) | 0, out = [b];
for (const s of [-1, 1]) for (let bx = (b % BW) + s; bx >= 0 && bx < BW; bx += s) {
const c = by * BW + bx;
if (blockP[c] !== BP_FILTER || (blockV[c] & 128) !== dir || !sameFilter(filterInfo(c), f)) break;
const yy = by + beltGrade(blockP[b]) * (bx - (b % BW));
if (yy < 0 || yy >= BH) break;
const c = yy * BW + bx;
if (blockP[c] !== blockP[b] || (blockV[c] & 128) !== dir || !sameFilter(filterInfo(c), f)) break;
out.push(c);
}
return out.sort((a, c) => a - c);