Uphill runs share one cleat phase; no cleats downhill; pick slope bands
A piece added to a running sloped belt kept its own cleat phase, so at the seam a rib could start just behind another and push its pocket into it, stalling the whole run with only a few grains. Every block of a run now takes the phase of the run's lower-left block. Downhill sloped belts no longer have cleats: they carry like a flat belt (90 grains/s, was 38) and draw only the sidewall. Middle click on a sloped belt's band, where it is drawn into the empty block beside it, now picks the belt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -53,16 +53,18 @@ const SM_SLOPE_DRAW = `
|
||||
float wave = 0.5 + 0.5 * sin((foot + w.x - local.x - phase) * 3.14159265);
|
||||
vec3 rubber = mix(vec3(0.20,0.22,0.25), vec3(0.25,0.27,0.31), wave);
|
||||
C = mix(C, rubber, 1.0 - smoothstep(-px, px, sd));
|
||||
// the ribs, where the simulation has them: rubber bars standing straight out of the belt,
|
||||
// the ribs, where the simulation has them: rubber bars standing straight out of an uphill belt,
|
||||
// one every ${CLEAT_SPACING} face cells, moved along by the belt's phase
|
||||
float dirS = (wallFlags & 32) != 0 ? 1.0 : -1.0;
|
||||
float rc = (w.x - local.x) + foot - (grade < 0.0 ? 1.0 : 0.0) - dirS * phase;
|
||||
float off = (mod(rc + ${(CLEAT_SPACING / 2).toFixed(1)}, ${CLEAT_SPACING.toFixed(1)}) - ${(CLEAT_SPACING / 2).toFixed(1)}) * 1.41421356;
|
||||
float ribTop = ${((2 * CLEAT_REACH + 1) * Math.SQRT1_2).toFixed(3)};
|
||||
float rd = length(vec2(off, depth - clamp(depth, 0.0, ribTop))) - 0.5;
|
||||
vec3 rib = mix(vec3(0.50,0.52,0.56), vec3(0.70,0.72,0.76), smoothstep(ribTop - 1.2, ribTop, depth));
|
||||
rib *= mix(0.78, 1.0, smoothstep(-0.5, 0.2, off * -dirS * grade));
|
||||
C = mix(C, rib, 1.0 - smoothstep(-px, px, rd));
|
||||
if (grade * dirS < 0.0) {
|
||||
float rc = (w.x - local.x) + foot - (grade < 0.0 ? 1.0 : 0.0) - dirS * phase;
|
||||
float off = (mod(rc + ${(CLEAT_SPACING / 2).toFixed(1)}, ${CLEAT_SPACING.toFixed(1)}) - ${(CLEAT_SPACING / 2).toFixed(1)}) * 1.41421356;
|
||||
float ribTop = ${((2 * CLEAT_REACH + 1) * Math.SQRT1_2).toFixed(3)};
|
||||
float rd = length(vec2(off, depth - clamp(depth, 0.0, ribTop))) - 0.5;
|
||||
vec3 rib = mix(vec3(0.50,0.52,0.56), vec3(0.70,0.72,0.76), smoothstep(ribTop - 1.2, ribTop, depth));
|
||||
rib *= mix(0.78, 1.0, smoothstep(-0.5, 0.2, off * -dirS * grade));
|
||||
C = mix(C, rib, 1.0 - smoothstep(-px, px, rd));
|
||||
}
|
||||
}
|
||||
|
||||
// Slopes use the flat belt's band, rollers and mesh in coordinates along the run.
|
||||
|
||||
@@ -106,7 +106,8 @@ function carryBeltsAndFilters(belts, filters, d, bm, carrying) {
|
||||
if (above === BELTL || above === BELTR || netSat(b) <= 0) continue;
|
||||
if (carrying) {
|
||||
if (PH[above] === PH_LIQUID) beltDrain(b);
|
||||
if (!isSlopeBelt(code)) carryColumn(b - W, d, null); // on a slope, the cleats do the carrying
|
||||
// uphill, the cleats do the carrying
|
||||
if (!isSlopeBelt(code) || !slopeUphill(code, (y >> 2) * BW + (x >> 2))) carryColumn(b - W, d, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-17
@@ -3,6 +3,7 @@
|
||||
// until a cleat stops them. Each belt step moves every cleat one face cell along the belt, and the
|
||||
// rib pushes whatever is in its way ahead of it, so the pocket in front rides along while anything
|
||||
// heaped above the rib is left to roll back. If a rib can't push its way, its whole run stalls.
|
||||
// Only uphill runs have cleats: downhill, the belt carries its load like a flat one.
|
||||
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
|
||||
@@ -26,6 +27,7 @@ function ribCells(x, y, g) {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function slopeUphill(c, b) { return beltGrade(c) * (blockV[b] & 1 ? 1 : -1) < 0; }
|
||||
// Is (x, y) a face cell of a sloped belt with grade g?
|
||||
function slopeFaceAt(x, y, g) {
|
||||
if (x < 0 || x >= W || y < 0 || y >= H) return false;
|
||||
@@ -46,15 +48,8 @@ function pushRow(p, dx, dy) {
|
||||
q = y * W + x;
|
||||
}
|
||||
const front = row[row.length - 1], m = mat[front];
|
||||
const open = j => !cleatMask[j] && (passable(m, PH_POWDER, j, dx, 0) || (PH[mat[j]] === PH_LIQUID && !occ[j] && !part[j]));
|
||||
// where a downhill row meets the flat belt at the foot, its front grain levels out onto it
|
||||
let to = q;
|
||||
if (!open(q)) {
|
||||
const fx = front % W + dx;
|
||||
if (dy <= 0 || fx < 0 || fx >= W || !open(front + dx)) return false;
|
||||
to = front + dx;
|
||||
}
|
||||
ribMove(front, to);
|
||||
if (cleatMask[q] || !(passable(m, PH_POWDER, q, dx, 0) || (PH[mat[q]] === PH_LIQUID && !occ[q] && !part[q]))) return false;
|
||||
ribMove(front, q);
|
||||
for (let k = row.length - 2; k >= 0; k--) ribMove(row[k], row[k] + dx + dy * W);
|
||||
return true;
|
||||
}
|
||||
@@ -118,14 +113,29 @@ function advanceCleat(x, y, g, d) {
|
||||
// A run of sloped belt is one belt: the block at its lower-left end names it.
|
||||
function slopeRunKey(b, memo) {
|
||||
if (memo.has(b)) return memo.get(b);
|
||||
const c = blockP[b], g = beltGrade(c), v = blockV[b] & 1;
|
||||
let bx = b % BW, by = (b / BW) | 0;
|
||||
while (bx > 0 && by - g >= 0 && by - g < BH && blockP[(by - g) * BW + bx - 1] === c && (blockV[(by - g) * BW + bx - 1] & 1) === v) { bx--; by -= g; }
|
||||
memo.set(b, by * BW + bx);
|
||||
return by * BW + bx;
|
||||
const c = blockP[b], g = beltGrade(c), v = blockV[b] & 1, path = [b];
|
||||
let bx = b % BW, by = (b / BW) | 0, key = -1;
|
||||
while (bx > 0 && by - g >= 0 && by - g < BH && blockP[(by - g) * BW + bx - 1] === c && (blockV[(by - g) * BW + bx - 1] & 1) === v) {
|
||||
bx--; by -= g;
|
||||
if (memo.has(by * BW + bx)) { key = memo.get(by * BW + bx); break; }
|
||||
path.push(by * BW + bx);
|
||||
}
|
||||
if (key < 0) key = by * BW + bx;
|
||||
for (const p of path) memo.set(p, key);
|
||||
return key;
|
||||
}
|
||||
function slopeCleatsTick(advance = true) {
|
||||
const memo = new Map(), stalled = new Set();
|
||||
// the whole run shares the phase of its lower-left block, so a piece added to a running belt
|
||||
// takes up its cleat spacing instead of starting a rib of its own at the seam
|
||||
let resynced = false;
|
||||
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) || !isBelt(c) || !isSlopeBelt(c) || beltFaceY(c, x >> 2, y >> 2, x) !== y) continue;
|
||||
const p = slopePhase[slopeRunKey(b, memo)];
|
||||
if (slopePhase[b] !== p) { slopePhase[b] = p; resynced = true; }
|
||||
}
|
||||
if (resynced) rebuildCleatMask();
|
||||
if (advance && !(tick & 1)) {
|
||||
// each run moves all its ribs or none: if one can't clear its path, the run stalls and every
|
||||
// grain its ribs moved goes back. Front of the line first, so a rib pushes into room the rib
|
||||
@@ -133,7 +143,7 @@ function slopeCleatsTick(advance = true) {
|
||||
const runs = new Map();
|
||||
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 || netSat(i) <= 0) continue;
|
||||
if (!isBelt(c) || !isSlopeBelt(c) || !slopeUphill(c, b) || beltFaceY(c, x >> 2, y >> 2, x) !== y || netSat(i) <= 0) continue;
|
||||
// lift the run's ribs off; each is put back where it moves to, and a rib leaving the top is gone
|
||||
if (cleatFoot(x, b)) for (const j of ribCells(x, y, beltGrade(c))) if (j >= 0) cleatMask[j] = 0;
|
||||
if (!cleatFoot(x, b, (slopePhase[b] + 1) % CLEAT_SPACING)) continue;
|
||||
@@ -162,7 +172,7 @@ function slopeCleatsTick(advance = true) {
|
||||
// they slide back down between steps, which on 45° is faster than the belt runs
|
||||
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], a = i - W;
|
||||
if (!isBelt(c) || !isSlopeBelt(c) || beltFaceY(c, x >> 2, y >> 2, x) !== y || netSat(i) <= 0) continue;
|
||||
if (!isBelt(c) || !isSlopeBelt(c) || !slopeUphill(c, b) || beltFaceY(c, x >> 2, y >> 2, x) !== y || netSat(i) <= 0) continue;
|
||||
if (a < 0 || PH[mat[a]] !== PH_POWDER || occ[a] || cleatMask[a] || stamp[a] === tickStamp) continue;
|
||||
const d = blockV[b] & 1 ? 1 : -1, st = conveyorStep(x, y, d), j = a + d + st * W;
|
||||
if (x + d < 0 || x + d >= W || j < 0 || cleatMask[j] || !passable(mat[a], PH_POWDER, j, d, st)) continue;
|
||||
@@ -180,7 +190,7 @@ function rebuildCleatMask() {
|
||||
cleatCells.length = 0;
|
||||
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 || !cleatFoot(x, b)) continue;
|
||||
if (!isBelt(c) || !isSlopeBelt(c) || !slopeUphill(c, b) || beltFaceY(c, x >> 2, y >> 2, x) !== y || !cleatFoot(x, b)) continue;
|
||||
for (const j of ribCells(x, y, beltGrade(c))) if (j >= 0) cleatCells.push(j);
|
||||
}
|
||||
for (const i of cleatCells) {
|
||||
|
||||
+4
-2
@@ -51,8 +51,10 @@ function doInteract() {
|
||||
function pickUnderCursor() {
|
||||
const ax = Math.floor(GAME.aim.x), ay = Math.floor(GAME.aim.y);
|
||||
if (ax < 0 || ay < 0 || ax >= W || ay >= H) return;
|
||||
const i = ay * W + ax, m = mat[i], bi = ((ay / BS) | 0) * BW + ((ax / BS) | 0);
|
||||
let pid = null;
|
||||
const i = ay * W + ax, m = mat[i];
|
||||
let bi = ((ay / BS) | 0) * BW + ((ax / BS) | 0), pid = null;
|
||||
// a sloped belt's band is drawn across its block's edge, so the belt you see can lie in the next block
|
||||
if (!blockP[bi] && !ghostBlocks.has(bi)) { const sb = slopeBeltAt(i, ax, ay, R.smoothOn); if (sb >= 0) bi = sb; }
|
||||
const ghost = ghostBlocks.get(bi);
|
||||
if (mach[i] && machines[mach[i]]) { const mm = machines[mach[i]]; pid = mm.type; if (PIECE_BY_ID[pid].dirs) GAME.pieceDir[pid] = mm.dir; }
|
||||
else if (moldAt(ax, ay)) { const mo = moldAt(ax, ay); pid = mo.kind + 'mold'; GAME.wallMat = mo.wall; }
|
||||
|
||||
Reference in New Issue
Block a user