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:
@@ -0,0 +1,51 @@
|
||||
// Cleats are moving barriers, not material cells. A cleat supports a finite pocket; the bare face never holds grains.
|
||||
const CLEAT_SPACING = 8, CLEAT_HEIGHT = 6; // spacing in face cells, height in grains
|
||||
const CLEAT_REACH = Math.floor(CLEAT_HEIGHT * Math.SQRT1_2);
|
||||
const SCREEN_NUDGE_TICKS = 4; // one downhill contact step per four ticks (15 steps/s), plus gravity
|
||||
let cleatCarrying = false;
|
||||
let cleatMask = new Uint8Array(0), cleatCells = [], slopePhase = new Uint8Array(0);
|
||||
function cleatFoot(x, b, phase = slopePhase[b]) {
|
||||
const d = blockV[b] & 1 ? 1 : -1;
|
||||
return ((x - d * phase) % CLEAT_SPACING + CLEAT_SPACING) % CLEAT_SPACING === 0;
|
||||
}
|
||||
function pocketDepth(x, b) {
|
||||
const g = beltGrade(blockP[b]), d = blockV[b] & 1 ? 1 : -1;
|
||||
// A periodic pocket envelope, clipped to two vertical projections of the cleat.
|
||||
// End pockets keep moving while their cleat rounds the end of the band.
|
||||
const distance = ((g * (d * slopePhase[b] - x)) % CLEAT_SPACING + CLEAT_SPACING) % CLEAT_SPACING;
|
||||
return Math.max(0, CLEAT_REACH * 2 - distance);
|
||||
}
|
||||
function slopeCleatsTick(advance = true) {
|
||||
for (const i of cleatCells) { cleatMask[i] = 0; wakeIdx(i); }
|
||||
cleatCells.length = 0;
|
||||
// Front first, just like flat transport. Each foot belongs to exactly one face cell.
|
||||
for (const list of [beltR, beltL]) for (const i of list) {
|
||||
const x = i % W, y = (i / W) | 0, b = (y >> 2) * BW + (x >> 2), c = blockP[b];
|
||||
if (!isBelt(c) || !isSlopeBelt(c) || beltFaceY(c, x >> 2, y >> 2, x) !== y) continue;
|
||||
const d = blockV[b] & 1 ? 1 : -1, g = beltGrade(c), running = netSat(i) > 0;
|
||||
const phase = (slopePhase[b] + (advance && running && !(tick & 1) ? 1 : 0)) % CLEAT_SPACING;
|
||||
if (!cleatFoot(x, b, phase)) continue;
|
||||
for (let h = CLEAT_REACH; h >= 0; h--) {
|
||||
const xx = x + g * h, yy = y - h - 1;
|
||||
if (xx < 0 || xx >= W || yy < 0 || yy >= H) continue;
|
||||
const j = yy * W + xx;
|
||||
cleatCells.push(j);
|
||||
}
|
||||
}
|
||||
if (advance && !(tick & 1)) for (const list of [beltR, beltL]) for (const i of list) {
|
||||
const x = i % W, y = (i / W) | 0, b = (y >> 2) * BW + (x >> 2), c = blockP[b];
|
||||
if ((x & 3) === 0 && isBelt(c) && isSlopeBelt(c) && beltFaceY(c,x>>2,y>>2,x)===y && netSat(i)>0)
|
||||
slopePhase[b] = (slopePhase[b] + 1) % CLEAT_SPACING;
|
||||
}
|
||||
for (const i of cleatCells) { cleatMask[i] = 1; wakeIdx(i); }
|
||||
}
|
||||
// A screen shakes only the grains touching its mesh, never the whole column above it.
|
||||
function screenNudge(top, d, pass) {
|
||||
if (tick % SCREEN_NUDGE_TICKS) return;
|
||||
const i = top - W, m = mat[i];
|
||||
if (i < 0 || PH[m] !== PH_POWDER || stamp[i] === tickStamp || occ[i]) return;
|
||||
if (pass && pass[m] && enter(m, PH_POWDER, top, 0, 1)) return;
|
||||
const j = i + d + W;
|
||||
if (i % W + d < 0 || i % W + d >= W || j >= N) return;
|
||||
if (enter(m, PH_POWDER, j, d, 1)) move(i, j, i % W, (i / W) | 0, j % W, (j / W) | 0);
|
||||
}
|
||||
Reference in New Issue
Block a user