Sloped belts hold their load with moving cleats (t42); sloped filters are downhill shaking screens (t43)

The magic grip is gone: cleats every 8 face cells, 6 grains tall, carry
pockets uphill (about 71 grains/s against 90 flat) and overflow rolls
back. Sloped filters always run downhill; grains travel by gravity and
a shake, and sorting stays exact. Drawing needs another pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 19:47:59 -04:00
co-authored by Claude Opus 5.5
parent 1f8a89db6a
commit 578b55316b
28 changed files with 425 additions and 89 deletions
+27 -4
View File
@@ -42,8 +42,8 @@ function prepareSlopeBelts(x0, y0, x1, y1) {
if (c === BP_BELT || c === BP_FILTER) beltCaps[b] = flatBeltCaps(b);
if (!isSlopeBelt(c)) continue;
const face = beltFaceY(c, bx, by, bx * BS) * W + bx * BS;
slopeBeltJoins[b] = slopeBeltConnections(bx, by, c) | (netSat(face) > 0 ? 16 : 0) | (filterCfg.get(b)?.inv ? 32 : 0);
for (let yy = Math.max(0, by - 1); yy <= Math.min(BH - 1, by + 1); yy++)
slopeBeltJoins[b] = slopeBeltConnections(bx, by, c) | (netSat(face) > 0 ? 16 : 0) | (filterCfg.get(b)?.inv ? 32 : 0) | (isFilter(c) && netSat(face) > 0 && screenLoaded(b) && (tick & 2) ? 64 : 0);
for (let yy = Math.max(0, by - 2); yy <= Math.min(BH - 1, by + 1); yy++)
for (let xx = Math.max(0, bx - 1); xx <= Math.min(BW - 1, bx + 1); xx++) slopeBeltHalo[yy * BW + xx] = 1;
}
}
@@ -63,6 +63,7 @@ function slopeBeltCorner(x, y, ax, ay, origin) {
function slopeBeltShape(x, y, b, joins = slopeBeltJoins[b], code = blockP[b]) {
const grade = beltGrade(code), ox = (b % BW) * BS, oy = ((b / BW) | 0) * BS;
x -= ox; y -= oy;
if (isFilter(code) && (joins & 64)) y -= 0.45;
const offset = 1.85 * Math.SQRT2, sy = grade < 0 ? 4 : 0;
const normal = 1.85 * Math.SQRT1_2;
let ax = -grade * normal, ay = sy + normal, bx = 4 - grade * normal, by = 4 - sy + normal;
@@ -107,9 +108,9 @@ function slopeBeltColor(b, x, y, anim, br, bg, bb) {
if (sd > 1e-8) return (br | 0) | ((bg | 0) << 8) | ((bb | 0) << 16);
const c = blockP[b], joins = slopeBeltJoins[b], mesh = isFilter(c);
const dir = mesh ? (blockV[b] & 128 ? 1 : -1) : (blockV[b] ? 1 : -1);
const a = slopeAlong, d = slopeAcross, time = joins & 16 ? anim : 0;
const a = slopeAlong, d = slopeAcross, time = !mesh && joins & 16 ? anim : 0;
let r = 38, g = 41, blue = 46;
if (-sd < 0.8) {
if (-sd < (mesh ? 1.1 : 0.8)) {
const stripe = (Math.floor((a - dir * (d < 2 ? 1 : -1) * (time >> 1)) / 2) & 1), k = d < 2 ? 1 : 0.72;
r = (stripe ? 92 : 56) * k; g = (stripe ? 96 : 58) * k; blue = (stripe ? 104 : 66) * k;
} else if (mesh) {
@@ -125,3 +126,25 @@ function slopeBeltColor(b, x, y, anim, br, bg, bb) {
}
return (r | 0) | ((g | 0) << 8) | ((blue | 0) << 16);
}
function screenLoaded(b) {
const bx = b % BW, by = (b / BW) | 0, c = blockP[b];
for (let x = bx * BS; x < bx * BS + BS; x++) {
const i = (beltFaceY(c, bx, by, x) - 1) * W + x;
if (i >= 0 && (PH[mat[i]] === PH_POWDER || occ[i])) return true;
}
return false;
}
// Far sidewalls stay behind the load. The cleat raster is the simulation barrier raster.
function slopeDetailAt(i, x, y) {
if (!slopeBeltHalo[(y >> 2) * BW + (x >> 2)] && !cleatMask[i]) return 0;
if (occ[i] || mach[i] || (mat[i] !== EMPTY && PH[mat[i]] !== PH_GAS) || part[i]) return 0;
if (cleatMask[i]) return 1;
for (let by = y >> 2; by <= Math.min(BH - 1, (y + 9) >> 2); by++) {
const b = by * BW + (x >> 2), c = blockP[b];
if (!isSlopeBelt(c)) continue;
const depth = beltFaceY(c, x >> 2, by, x) - y;
if (isFilter(c) ? depth === 2 : depth >= 6 && depth <= 7 + ((x + slopePhase[b]) & 1)) return isFilter(c) ? 3 : 2;
}
return 0;
}