Joins follow the travel direction: the next piece goes right after the end (t67 phase 2b)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+4
-16
@@ -185,22 +185,10 @@ 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;
|
||||
const q = part[next];
|
||||
if (!(SCREW_DX[q] && SCREW_DY[q])) return next;
|
||||
const join = endJoin(x >> 2, y >> 2, dx + dy > 0 ? 1 : 0);
|
||||
return join >= 0 && (join >> 1) === (yy >> 2) * BW + (xx >> 2) ? next : -1;
|
||||
}
|
||||
// Four lanes cross a block in four beats; only a mutual geometric join can receive them.
|
||||
function diagonalScrewNext(i, p) {
|
||||
|
||||
+18
-8
@@ -93,13 +93,6 @@ function entScrewTick(e, p) {
|
||||
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);
|
||||
@@ -149,6 +142,23 @@ function entDiagonalScrewTick(e, p) {
|
||||
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++; }
|
||||
}
|
||||
// A same-row flat belt drops its bar into a joined screw, just as grains fall off its face.
|
||||
function entBeltScrewEnter(e) {
|
||||
if (e.ix & 3) return false;
|
||||
const y = e.iy + BAR_H;
|
||||
if (y >= H || (y & 3)) return false;
|
||||
const b = (y >> 2) * BW + (e.ix >> 2), c = blockP[b], v = blockV[b];
|
||||
if (c !== BP_BELT && c !== BP_FILTER) return false;
|
||||
if (c === BP_FILTER && filterPassAt(e.m, y * W + e.ix)) return false;
|
||||
if (netSat(y * W + e.ix) <= 0) return false;
|
||||
const d = c === BP_FILTER ? (v & 128 ? 1 : -1) : (v & 1 ? 1 : -1);
|
||||
const join = endJoin(b % BW, (b / BW) | 0, d > 0 ? 1 : 0);
|
||||
if (join < 0 || blockP[join >> 1] !== BP_SCREW) return false;
|
||||
e.x = e.ix; e.y = e.iy; e.vx = e.vy = 0;
|
||||
const nb = join >> 1;
|
||||
if (!(tick & 1) && entScrewMove(e, nb % BW * BS, ((nb / BW) | 0) * BS, true, true)) stats.moved++;
|
||||
return true;
|
||||
}
|
||||
// Bars stay level; their highest contact follows the face one grain per belt beat.
|
||||
function entSlopeBeltTick(e) {
|
||||
const bottom = e.iy + BAR_H;
|
||||
@@ -188,7 +198,7 @@ function entTick() {
|
||||
let ix = e.ix; const iy = e.iy;
|
||||
const screw = entScrewAt(ix, iy);
|
||||
if (screw) entScrewTick(e, screw);
|
||||
else if (!entSlopeBeltTick(e)) {
|
||||
else if (!entBeltScrewEnter(e) && !entSlopeBeltTick(e)) {
|
||||
// what is underneath?
|
||||
let belt = 0, bsat = 0, transport = false, support = iy + BAR_H >= H;
|
||||
if (!support) {
|
||||
|
||||
+41
-39
@@ -1,46 +1,48 @@
|
||||
// Geometric transport ends. Tables are built once; querying a world or plan allocates nothing.
|
||||
// Ends are left/right, except vertical screws (up/down). Travel direction is irrelevant.
|
||||
const JOIN_ENDS = [[], ...[
|
||||
['screw-horizontal', 1, 0], ['screw-vertical', 0, 1],
|
||||
['screw-diagonal', 1, -1], ['screw-diagonal', 1, 1],
|
||||
['belt-flat', 1, 0], ['belt-sloped', 1, -1], ['belt-sloped', 1, 1]
|
||||
].map(([kind, dx, dy]) => [-1, 1].map(s => ({
|
||||
dx: s * dx, dy: s * dy, diagonal: !!(dx && dy),
|
||||
high: !!(dx && dy && s * dy < 0), low: !!(dx && dy && s * dy > 0), kind,
|
||||
targets: []
|
||||
})))];
|
||||
function pieceEnds(code, v) {
|
||||
if (code === BP_SCREW) {
|
||||
if (v < 0 || v > 7) return JOIN_ENDS[0];
|
||||
return JOIN_ENDS[v < 4 ? (v & 1 ? 1 : 2) : (SCREW_DIRS[v][0] * SCREW_DIRS[v][1] < 0 ? 3 : 4)];
|
||||
}
|
||||
if (code === BP_BELT || code === BP_FILTER) return JOIN_ENDS[5];
|
||||
return isSlopeBelt(code) ? JOIN_ENDS[beltGrade(code) < 0 ? 6 : 7] : JOIN_ENDS[0];
|
||||
// Travel-directed transport ends. Precomputed probes; world/plan queries allocate nothing.
|
||||
// End indices stay geometric: left/right, or up/down for vertical screws.
|
||||
const JOIN_ENDS = [[]];
|
||||
for (const [kind, tx, ty] of [
|
||||
...SCREW_DIRS.map(([x,y]) => [x && y ? 'screw-diagonal' : x ? 'screw-horizontal' : 'screw-vertical',x,y]),
|
||||
['belt-flat',-1,0], ['belt-flat',1,0],
|
||||
['belt-sloped',-1,1], ['belt-sloped',1,-1],
|
||||
['belt-sloped',-1,-1], ['belt-sloped',1,1]
|
||||
]) {
|
||||
const outlet = (tx || ty) > 0 ? 1 : 0;
|
||||
JOIN_ENDS.push([0,1].map(e => ({
|
||||
dx: (e ? 1 : -1) * Math.abs(tx), dy: (e ? 1 : -1) * (tx ? ty * tx : 1),
|
||||
diagonal: !!(tx && ty), kind, tx, ty, outlet: e === outlet, targets: []
|
||||
})));
|
||||
}
|
||||
// Compile the diagonal-end rule and its inverse into bounded neighbour probes.
|
||||
for (const ends of JOIN_ENDS) for (const end of ends) {
|
||||
function pieceEnds(code, v) {
|
||||
if (code === BP_SCREW) return JOIN_ENDS[v >= 0 && v < 8 ? v + 1 : 0];
|
||||
if (!isBelt(code) && !isFilter(code)) return JOIN_ENDS[0];
|
||||
const d = isFilter(code) ? (v & 128 ? 1 : 0) : (v & 1);
|
||||
return JOIN_ENDS[9 + d + (beltGrade(code) < 0 ? 2 : beltGrade(code) > 0 ? 4 : 0)];
|
||||
}
|
||||
for (const ends of JOIN_ENDS) for (let e = 0; e < ends.length; e++) {
|
||||
const a = ends[e];
|
||||
if (!a.outlet) continue;
|
||||
for (const others of JOIN_ENDS) for (let n = 0; n < others.length; n++) {
|
||||
const other = others[n], screw = end.kind.startsWith('screw'), otherScrew = other.kind.startsWith('screw');
|
||||
if (screw !== otherScrew && (end.kind === 'screw-vertical' || other.kind === 'screw-vertical' ||
|
||||
(end.diagonal && other.diagonal))) continue;
|
||||
let dx, dy;
|
||||
if (end.diagonal && other.diagonal) {
|
||||
if (screw !== otherScrew || end.dx !== -other.dx || end.dy !== -other.dy) continue;
|
||||
dx = end.dx; dy = end.dy;
|
||||
} else if (end.diagonal) {
|
||||
if (other.dx ? other.dx !== -end.dx : other.dy !== -end.dy) continue;
|
||||
dx = end.dx; dy = other.dx && end.high ? 0 : end.dy;
|
||||
} else if (other.diagonal) {
|
||||
if (end.dx ? end.dx !== -other.dx : end.dy !== -other.dy) continue;
|
||||
dx = -other.dx; dy = end.dx && other.high ? 0 : -other.dy;
|
||||
} else {
|
||||
if (end.dx !== -other.dx || end.dy !== -other.dy) continue;
|
||||
dx = end.dx; dy = end.dy;
|
||||
const b = others[n], screw = a.kind.startsWith('screw'), otherScrew = b.kind.startsWith('screw');
|
||||
if (b.outlet || a.tx * b.tx + a.ty * b.ty <= 0) continue;
|
||||
if (screw !== otherScrew && (a.kind === 'screw-vertical' || b.kind === 'screw-vertical' ||
|
||||
(a.diagonal && b.diagonal))) continue;
|
||||
if (a.diagonal && b.diagonal && (a.tx !== b.tx || a.ty !== b.ty)) continue;
|
||||
const dy = !screw && a.ty < 0 ? 0 : a.ty;
|
||||
const offsets = [[a.tx, dy]];
|
||||
// Grains on a flat belt below an uphill low end already reach its carrying face.
|
||||
if (!screw && !otherScrew && !a.ty && b.ty < 0) offsets.push([a.tx, -1]);
|
||||
for (const [dx, yy] of offsets) {
|
||||
a.targets.push({ dx, dy: yy, ends: others, end: n });
|
||||
b.targets.push({ dx: -dx, dy: -yy, ends, end: e });
|
||||
}
|
||||
end.targets.push({ dx, dy, ends: others, end: n });
|
||||
}
|
||||
// Prefer a continuing diagonal run to an elbow when both are present.
|
||||
end.targets.sort((a, b) => Number(b.ends[b.end].diagonal) - Number(a.ends[a.end].diagonal));
|
||||
}
|
||||
for (const ends of JOIN_ENDS) for (const end of ends) {
|
||||
const sameShape = t => t.ends[t.end].kind === end.kind &&
|
||||
t.ends[t.end].tx === end.tx && t.ends[t.end].ty === end.ty;
|
||||
end.targets.sort((a,b) => end.outlet ? Math.abs(a.dy - end.ty) - Math.abs(b.dy - end.ty) :
|
||||
Number(sameShape(b)) - Number(sameShape(a)) || a.dy - b.dy || a.dx - b.dx);
|
||||
}
|
||||
// Result: neighbour block index * 2 + its end index; -1 means an open end.
|
||||
function joinCandidate(bx, by, endIndex, planned) {
|
||||
|
||||
Reference in New Issue
Block a user