Screw conveyors join by the shared rule: low ends and verticals join in the corner block (t67 phase 2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 13:30:44 -04:00
co-authored by Claude Opus 5.5
parent 198bea54da
commit fd35bf334f
14 changed files with 358 additions and 209 deletions
+2 -2
View File
@@ -44,7 +44,7 @@ function enterFree(m, ph, j, dx, dy) {
if (occ[j]) return 0;
const p = part[j];
if (p !== 0) {
if (IS_SCREW[p]) { if (ph === PH_POWDER && SCREW_DX[p] === -dx && SCREW_DY[p] === -dy) return 0; } // liquids and gases flow through freely
if (IS_SCREW[p]) { if (ph === PH_POWDER && !screwAccepts(p, dx, dy)) return 0; } // liquids and gases flow through freely
else if (p === P_GRATE) { if (ph === PH_POWDER || m === MSLAG) return 0; } // thick slag does not run through a grate
else if (p === P_INTAKE) { if (ph !== PH_GAS) return 0; }
else if (p === P_FILTER) { if (ph === PH_POWDER && !filterPassAt(m, j)) return 0; }
@@ -110,7 +110,7 @@ function passable(m, ph, j, dx, dy) {
if (occ[j]) return false;
const p = part[j];
if (p !== 0) {
if (IS_SCREW[p]) { if (ph === PH_POWDER && SCREW_DX[p] === -dx && SCREW_DY[p] === -dy) return false; }
if (IS_SCREW[p]) { if (ph === PH_POWDER && !screwAccepts(p, dx, dy)) return false; }
else if (p === P_GRATE) { if (ph === PH_POWDER || m === MSLAG) return false; }
else if (p === P_INTAKE) { if (ph !== PH_GAS) return false; }
else if (p === P_FILTER) { if (ph === PH_POWDER && !filterPassAt(m, j)) return false; }
+38 -13
View File
@@ -176,23 +176,48 @@ function screwIntake(i, p, dx, dy) {
}
}
// Four lanes cross a block in four beats; the casing carries every lane across the corner.
// Prefer the next diagonal block, then either straight join, then the open head.
// Powder may enter any screw except one pushing exactly against it.
function screwAccepts(p, dx, dy) {
return SCREW_DX[p] !== -dx || SCREW_DY[p] !== -dy;
}
// Straight mouths retain side-feeding into lifts; only diagonal transfers need a join.
function straightScrewNext(i, p) {
const dx = SCREW_DX[p], dy = SCREW_DY[p], x = i % W, y = (i / W) | 0;
const xx = x + dx, yy = y + dy, next = yy * W + xx;
if ((xx >> 2) === (x >> 2) && (yy >> 2) === (y >> 2)) return next;
const end = dx + dy > 0 ? 1 : 0;
const q = part[next], nb = (yy >> 2) * BW + (xx >> 2);
if (blockP[nb] && !(SCREW_DX[q] && SCREW_DY[q])) return next;
const join = endJoin(x >> 2, y >> 2, end);
if (join >= 0) {
const target = join >> 1, v = blockV[target];
if (blockP[target] === BP_SCREW && v >= 4) {
if (target === nb) return next;
if (!blockP[nb]) {
const lane = dx ? y & 3 : x & 3;
const tx = target % BW * BS + (join & 1 ? BS - 1 : 0);
return (((target / BW) | 0) * BS + lane) * W + tx;
}
}
}
return SCREW_DX[q] && SCREW_DY[q] ? -1 : next;
}
// Four lanes cross a block in four beats; only a mutual geometric join can receive them.
function diagonalScrewNext(i, p) {
const x = i % W, y = (i / W) | 0, dx = SCREW_DX[p], dy = SCREW_DY[p];
const xx = x + dx, bx = x & ~3, by = y & ~3;
const xx = x + dx;
if (xx < 0 || xx >= W) return -1;
if ((xx >> 2) === (x >> 2)) return i + dx;
const yy = y + dy * BS;
if (yy >= 0 && yy < H) {
const q = part[yy * W + xx];
if (q >= P_SCREW_UR && q <= P_SCREW_UL && SCREW_DX[q] === dx && SCREW_DY[q] === dy) return yy * W + xx;
const yy = y + dy * BS, join = endJoin(x >> 2, y >> 2, dx > 0 ? 1 : 0);
if (join >= 0) {
const nb = join >> 1, nx = nb % BW * BS, ny = ((nb / BW) | 0) * BS;
if (blockP[nb] === BP_BELT || blockP[nb] === BP_FILTER) return (ny - 1) * W + xx;
if (blockP[nb] === BP_SCREW) {
const v = blockV[nb];
if (v >= 4) return yy * W + xx;
if (v & 1) return (ny + (y & 3)) * W + xx;
return (ny + (dy > 0 ? 0 : 3)) * W + nx + (y & 3);
}
}
if (IS_SCREW[part[y * W + xx]]) return y * W + xx;
if (dy > 0 && yy < H && (mat[yy * W + xx] === BELTL || mat[yy * W + xx] === BELTR)) return ((yy & ~3) - 1) * W + xx;
const hy = by + (dy > 0 ? BS : -1), hx = bx + (y & 3);
if (hy >= 0 && hy < H && IS_SCREW[part[hy * W + hx]]) return hy * W + hx;
// An open mouth discharges along the diagonal, clear of the last block.
// A corner screw is a separate end, not an open discharge cell.
return yy >= 0 && yy < H && !IS_SCREW[part[yy * W + xx]] ? yy * W + xx : -1;
}
+28 -9
View File
@@ -90,16 +90,32 @@ function entScrewTick(e, p) {
if ((tick & 1) || netSat(ci) <= 0 || rnd() > netSat(ci)) return;
const dx = SCREW_DX[p], dy = SCREW_DY[p];
if (dx && dy) { entDiagonalScrewTick(e, p); return; }
const frontX = e.ix + (dx > 0 ? BAR_W - 1 : 0), frontY = e.iy + (dy > 0 ? BAR_H - 1 : 0);
const front = frontY * W + frontX, dest = straightScrewNext(front, p);
if (dest < 0) return;
if (dest !== front + dx + dy * W) {
const q = part[dest];
if (!screwAccepts(q, dx, dy)) return;
const x = (dest % W) & ~3, y = entScrewLane((dest / W) | 0, e.iy);
if (entScrewMove(e, x, y, true)) stats.moved++;
return;
}
let x = e.ix + dx, y = e.iy + dy, next = 0, ax = 0, ay = 0;
for (let k = 0; k < (dx ? BAR_H : BAR_W); k++) {
const i = entScrewEdge(e, dx, dy, k);
if (i < 0) return;
const xx = i % W, yy = (i / W) | 0;
const q = part[i];
if (IS_SCREW[q] && q !== p) { next = q; ax = xx; ay = yy; break; }
if (IS_SCREW[q] && q !== p) {
if (SCREW_DX[q] && SCREW_DY[q]) {
const join = endJoin(frontX >> 2, frontY >> 2, dx + dy > 0 ? 1 : 0);
if (join < 0 || (join >> 1) !== (yy >> 2) * BW + (xx >> 2)) return;
}
next = q; ax = xx; ay = yy; break;
}
}
if (next) {
if (SCREW_DX[next] === -dx && SCREW_DY[next] === -dy) return;
if (!screwAccepts(next, dx, dy)) return;
x = ax & ~3; y = entScrewLane(ay, e.iy);
if (entScrewMove(e, x, y, true)) stats.moved++;
return;
@@ -115,17 +131,20 @@ function entScrewTick(e, p) {
function entDiagonalScrewTick(e, p) {
if (tick & 7) return;
const dx = SCREW_DX[p], dy = SCREW_DY[p], bx = e.ix & ~3, by = e.iy & ~3, lane = e.iy & 3;
for (const [ox, oy] of [[dx, dy], [dx, 0], [0, dy]]) {
const x = bx + ox * BS, y = by + oy * BS + lane, q = entScrewAt(x, y);
if (!q || (ox && oy && (q < P_SCREW_UR || q > P_SCREW_UL || SCREW_DX[q] !== dx || SCREW_DY[q] !== dy))) continue;
const edge = (by + lane) * W + bx + (dx > 0 ? BS - 1 : 0);
const dest = diagonalScrewNext(edge, p);
if (dest < 0) return;
const x = (dest % W) & ~3;
let y = (dest / W) | 0;
const q = part[dest];
if (IS_SCREW[q]) {
if (SCREW_DX[q] * dx + SCREW_DY[q] * dy < 0) return;
y = Math.min((y & ~3) + BS - BAR_H, y);
if (entScrewMove(e, x, y, true)) stats.moved++;
return;
}
const x = bx + dx * BS;
let y = by + dy * BS + lane;
const belt = y >= 0 && y < H ? mat[y * W + x] : EMPTY;
if (dy > 0 && (belt === BELTL || belt === BELTR)) y = (y & ~3) - BAR_H;
const join = endJoin(bx >> 2, by >> 2, dx > 0 ? 1 : 0);
if (join >= 0 && (blockP[join >> 1] === BP_BELT || blockP[join >> 1] === BP_FILTER)) y -= BAR_H - 1;
if (!entScrewFree(x, y)) return;
for (let yy = y; yy < y + BAR_H; yy++) for (let xx = x; xx < x + BAR_W; xx++) if (part[yy * W + xx]) return;
if (entScrewMove(e, x, y, false, dy < 0)) { e.vx = dx * 0.5; e.vy = dy * 0.6; stats.moved++; }
+3 -3
View File
@@ -97,7 +97,7 @@ function machinesTick() {
// beat as belts (every other tick), so a block-wide screw carries as much as a block-deep belt
if ((tick & 1) === 0) for (let p = 1; p < IS_SCREW.length; p++) {
if (!IS_SCREW[p]) continue;
const list = screwLists[p], dx = SCREW_DX[p], dy = SCREW_DY[p], dj = dy * W + dx, flat = dy === 0;
const list = screwLists[p], dx = SCREW_DX[p], dy = SCREW_DY[p], flat = dy === 0;
for (let k = 0; k < list.length; k++) {
const i = list[k];
if (part[i] !== p || occ[i]) continue;
@@ -130,14 +130,14 @@ function machinesTick() {
if (shifted) continue;
}
if (x + dx < 0 || x + dx >= W || y + dy < 0 || y + dy >= H) continue;
const dest = dx && dy ? diagonalScrewNext(i, p) : i + dj;
const dest = dx && dy ? diagonalScrewNext(i, p) : straightScrewNext(i, p);
if (dest < 0) continue;
const nx = dest % W, ny = (dest / W) | 0;
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
const j = dest;
if (occ[j]) continue;
const pj = part[j], screw = IS_SCREW[pj];
if (screw && SCREW_DX[pj] === -dx && SCREW_DY[pj] === -dy) continue;
if (screw && !screwAccepts(pj, dx, dy)) continue;
const t = mat[j];
if (!roomFor(j)) continue;
if (pj === P_GRATE || pj === P_INTAKE || (pj === P_FILTER && !filterPassAt(m, j))) continue;