Throughput in the info overlay: grains per second and backing-up glow (t49)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 16:21:48 -04:00
co-authored by Claude Opus 5.5
parent 54e7756ead
commit 9fc1b92080
14 changed files with 131 additions and 16 deletions
+2 -1
View File
@@ -21,7 +21,7 @@ function conveyorStep(x, y, d) {
}
function carryColumn(a, d, pass) {
if (a < 0 || mat[a] === EMPTY) return;
const sy = ((a / W) | 0) + 1, step = conveyorStep(a % W, sy, d);
const sy = ((a / W) | 0) + 1, step = conveyorStep(a % W, sy, d), carrier = (sy >> 2) * BW + ((a % W) >> 2);
let blocked = false;
for (let L = 0; L < CARRY_DEPTH; L++, a -= W) {
if (a < 0) return;
@@ -51,6 +51,7 @@ function carryColumn(a, d, pass) {
if (mat[j] === EMPTY && !part[j] && bj < N && (mat[bj] === EMPTY || PH[mat[bj]] === PH_GAS) && !occ[bj] && !part[bj]) launchGrain(a, nx, y + step, d * 0.5, step * 0.5);
else move(a, j, x, y, nx, y + step);
stats.moved++;
if (flowOn && (x >> 2) !== (nx >> 2)) flowNote(carrier);
}
}
// Belts are perforated: liquid resting on one drips through to whatever open space is under it.
+26
View File
@@ -0,0 +1,26 @@
// Throughput for the info overlay (t49): how many grains each belt, filter and screw conveyor
// block hands on, per second of game time. Counting runs only while the overlay is on, so
// normal play pays one flag check per carried grain. Bars are not counted.
let flowOn = false, flowCount = new Uint16Array(0), flowRate = new Float32Array(0);
function setFlowCounting(on) {
flowOn = on;
if (flowCount.length !== NB) { flowCount = new Uint16Array(NB); flowRate = new Float32Array(NB); }
else if (!on) { flowCount.fill(0); flowRate.fill(0); }
}
// A grain left carrier block b across one of its edges.
function flowNote(b) { if (b >= 0 && b < flowCount.length && flowCount[b] < 65535) flowCount[b]++; }
// Count a move from cell a to cell j if it crosses into another column of blocks or row of blocks.
function flowCross(b, a, j) { if (((a % W) >> 2) !== ((j % W) >> 2) || ((a / W) >> 2) !== ((j / W) >> 2)) flowNote(b); }
// The sloped belt block carrying a grain at cell a: the slope under it in the same block column.
function flowSlopeUnder(a) {
const bx = (a % W) >> 2;
for (let by = ((a / W) | 0) >> 2, k = 0; k < 3 && by < BH; k++, by++) if (isSlopeBelt(blockP[by * BW + bx])) return by * BW + bx;
return -1;
}
// Once a second, fold the count into a smoothed rate.
function flowTick() {
if (flowCount.length !== NB) setFlowCounting(flowOn);
if (tick % 60) return;
for (let b = 0; b < NB; b++) { if (flowCount[b] || flowRate[b]) { flowRate[b] = flowRate[b] * 0.5 + flowCount[b] * 0.5; if (flowRate[b] < 0.05) flowRate[b] = 0; } }
flowCount.fill(0);
}
+2 -1
View File
@@ -144,9 +144,10 @@ function machinesTick() {
if (!screw && !pj && t === EMPTY) {
// out of the mouth: the screw spits it clear, so the next grain is never blocked by the last
launchGrain(i, nx, ny, dy && !dx ? (rnd() - 0.5) * (dy < 0 ? 0.9 : 0.1) : dx * (0.42 + rnd() * 0.16), dy ? dy * (0.5 + rnd() * 0.2) : rnd() * 0.08);
stats.moved++; continue;
stats.moved++; if (flowOn) flowCross((y >> 2) * BW + (x >> 2), i, j); continue;
}
move(i, j, x, y, nx, ny); stats.moved++;
if (flowOn) flowCross((y >> 2) * BW + (x >> 2), i, j);
if (screw && pj !== p) {
// turning a corner: keep going across the new screw's width so every lane fills
screwSpread(j, nx, ny, dx, dy);
+3 -2
View File
@@ -155,7 +155,7 @@ function slopeCleatsTick(advance = true) {
ribLog.length = 0;
let ok = true;
for (let k = 0; k < feet.length && ok; k += 4) ok = advanceCleat(feet[k], feet[k + 1], feet[k + 2], feet[k + 3]);
if (ok) continue;
if (ok) { if (flowOn) for (let k = 0; k < ribLog.length; k += 2) flowCross(flowSlopeUnder(ribLog[k]), ribLog[k], ribLog[k + 1]); continue; }
stalled.add(key);
for (let k = ribLog.length - 2; k >= 0; k -= 2) { swapCells(ribLog[k], ribLog[k + 1]); wakeIdx(ribLog[k]); wakeIdx(ribLog[k + 1]); }
}
@@ -180,6 +180,7 @@ function slopeCleatsTick(advance = true) {
// dragged off an open end with nothing under it: it leaves at belt speed and falls
if (mat[j] === EMPTY && !part[j] && bj < N && (mat[bj] === EMPTY || PH[mat[bj]] === PH_GAS) && !occ[bj] && !part[bj]) launchGrain(a, x + d, y - 1 + st, d * 0.5, st * 0.5);
else move(a, j, x, y - 1, x + d, y - 1 + st);
if (flowOn) flowCross(b, a, j);
}
}
}
@@ -218,7 +219,7 @@ function screenNudge(top, d, pass) {
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);
if (enter(m, PH_POWDER, j, d, 1)) { move(i, j, i % W, (i / W) | 0, j % W, (j / W) | 0); if (flowOn) flowCross(((top / W) >> 2) * BW + ((top % W) >> 2), i, j); }
}
// Only loose load can react to a barrier appearing or disappearing.
+1
View File
@@ -14,6 +14,7 @@ function simTick() {
if (tick % 8 === 0) wakeBotPowder();
simMove();
heatPass();
if (flowOn) flowTick();
for (let c = 0; c < NCH; c++) if (awake[c]) awake[c]--;
if (tick % 15 === 0) checkMolds();
if (tick % 30 === 0) checkLedger();