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:
@@ -22,6 +22,7 @@
|
||||
"sim/machine-tick.js",
|
||||
"sim/joins.js",
|
||||
"sim/conveyors.js",
|
||||
"sim/flow.js",
|
||||
"sim/slope-conveyors.js",
|
||||
"sim/blocks.js",
|
||||
"sim/molds.js",
|
||||
|
||||
@@ -75,6 +75,40 @@ function drawJoinMarks() {
|
||||
for (let end = 0; end < 2; end++) if (nearMiss(bx, by, end, ghostBlocks.size ? ghostBlocks : null)) drawJoinMark(bx, by, end, code, vv);
|
||||
}
|
||||
}
|
||||
// Throughput (t49, info overlay): grains per second at the open outlet end of every line, and
|
||||
// an orange glow on transport blocks that are loaded but not moving, where a line backs up.
|
||||
function flowLoaded(b, code) {
|
||||
const bx = b % BW, by = (b / BW) | 0;
|
||||
let n = 0;
|
||||
for (let k = 0; k < BS; k++) {
|
||||
const x = bx * BS + k, i = code === BP_SCREW ? (by * BS + 1) * W + x : (beltFaceY(code, bx, by, x) - 1) * W + x;
|
||||
if (i >= 0 && i < N && PH[mat[i]] === PH_POWDER) n++;
|
||||
}
|
||||
return n >= 3;
|
||||
}
|
||||
function drawFlowRates() {
|
||||
const ctx = R.ctx, v = viewCells(), s = BS * R.zp;
|
||||
const bx0 = Math.max(0, Math.floor(R.camX / BS)), bx1 = Math.min(BW - 1, Math.floor((R.camX + v.w) / BS));
|
||||
const by0 = Math.max(0, Math.floor(R.camY / BS)), by1 = Math.min(BH - 1, Math.floor((R.camY + v.h) / BS));
|
||||
ctx.save();
|
||||
ctx.font = `600 ${Math.max(10, Math.min(14, s * 0.45))}px ui-monospace, monospace`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
||||
for (let by = by0; by <= by1; by++) for (let bx = bx0; bx <= bx1; bx++) {
|
||||
const b = by * BW + bx, code = blockP[b], ends = pieceEnds(code, blockV[b]);
|
||||
if (!ends.length) continue;
|
||||
const rate = flowRate[b] || 0;
|
||||
if (rate < 0.5 && flowLoaded(b, code)) {
|
||||
const p = worldToScreenPx(bx * BS, by * BS);
|
||||
ctx.fillStyle = 'rgba(255,150,60,0.32)'; ctx.fillRect(p.x, p.y, s, s);
|
||||
}
|
||||
const out = ends[1].outlet ? 1 : 0;
|
||||
if (endJoin(bx, by, out) >= 0) continue;
|
||||
const e = ends[out], c = worldToScreenPx(bx * BS + BS / 2 + e.dx * BS * 0.95, by * BS + BS / 2 + e.dy * BS * 0.95 - (e.dy ? 0 : BS * 0.9));
|
||||
const label = rate >= 10 ? `${Math.round(rate)}/s` : `${rate.toFixed(1)}/s`, tw = ctx.measureText(label).width;
|
||||
ctx.fillStyle = 'rgba(14,12,11,0.8)'; ctx.fillRect(c.x - tw / 2 - 4, c.y - 9, tw + 8, 18);
|
||||
ctx.fillStyle = rate > 0 ? '#e7dccb' : '#ef9a6a'; ctx.fillText(label, c.x, c.y + 1);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
// Outline a run of blocks: strong while it is being edited, faint when pointed at
|
||||
function drawRunOutline(run, strong) {
|
||||
if (!run || !run.length) return;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -133,6 +133,7 @@ function hoverInfo(ax, ay) {
|
||||
}
|
||||
else if (isWall(b) && PH[m] === PH_SOLID && !(flags[i] & F_NAT)) t1 = `${MD[m].name} ${isSlope(b) ? 'sloped wall' : 'block'}`;
|
||||
const jb = ((ay / BS) | 0) * BW + ((ax / BS) | 0);
|
||||
if (GAME.infoOverlay && !id && !bar && pieceEnds(blockP[jb], blockV[jb]).length) t2.push(`moving ${(flowRate[jb] || 0).toFixed(1)} grains/s`);
|
||||
for (let end = 0; end < 2 && !id && !bar; end++) {
|
||||
const miss = nearMiss(jb % BW, (jb / BW) | 0, end);
|
||||
if (miss) { t2.push(`doesn't connect: place it one block ${miss < 0 ? 'higher' : 'lower'}`); warn = warn || "doesn't connect"; break; }
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ window.addEventListener('keydown', e => {
|
||||
case 'f': doInteract(); break;
|
||||
case 'g': GAME.replace = true; hudDirty = true; break;
|
||||
case 'h': toggleLens(); break;
|
||||
case 'i': GAME.infoOverlay = !GAME.infoOverlay; playSfx('ui'); toast(GAME.infoOverlay ? 'Info overlay on: what every filter does, and which way belts and screw conveyors run. I hides it.' : 'Info overlay off.'); break;
|
||||
case 'i': GAME.infoOverlay = !GAME.infoOverlay; setFlowCounting(GAME.infoOverlay); playSfx('ui'); toast(GAME.infoOverlay ? 'Info overlay on: what every filter does, which way belts and screw conveyors run, and how many grains a second each line moves (orange: backing up). I hides it.' : 'Info overlay off.'); break;
|
||||
case 'l': bot.lamp = !bot.lamp; playSfx('ui'); break;
|
||||
case 'n': openNotebook(); break;
|
||||
case 'b': openNotebook('Blueprints'); break;
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ function frame(now) {
|
||||
drawParticles();
|
||||
const hb = GAME.mouse.in || GAME.usingPad ? filterBlockAt(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y)) : -1;
|
||||
if (GAME.editRun) drawRunOutline(GAME.editRun, true); else if (hb >= 0) drawRunOutline(filterRun(hb), false);
|
||||
if (GAME.infoOverlay) drawFlowArrows();
|
||||
if (GAME.infoOverlay) { drawFlowArrows(); drawFlowRates(); }
|
||||
drawJoinMarks();
|
||||
drawFilterTags(!!GAME.infoOverlay, GAME.editRun ? GAME.editRun[0] : hb);
|
||||
drawFloaters();
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ function controlsHtml() {
|
||||
['Dev console (marks the world)', 'Backtick (`)'], ['Fly', 'W A S D'], ['Boost while flying', 'Shift'], ['Tool modifier (while not flying)', 'Shift'], ['Use tool', 'Left mouse'], ['Second action', 'Right mouse'],
|
||||
['Tools', '1 – 4'], ['Select one to blow · piece', 'Q / E'], ['Switch build tab', 'Shift + Q / E'], ['Variant: material · direction · flip', 'R'], ['Wall: cycle shape · flip diagonal drag', 'T'], ['Wall or mold material menu (Build)', 'F'],
|
||||
['Pick up selection or what you click (Pneumatic nozzle)', 'Left drag · Shift: everything'], ['Select (Pneumatic nozzle)', 'Click a bag name · Q/E'], ['Add to selection (Pneumatic nozzle)', 'Ctrl+click a bag name'], ['Show everything found (Pneumatic nozzle)', 'Hold Ctrl in the bag'], ['Clear selection (Pneumatic nozzle)', 'Clear in the bag'], ['Blow everything loose in the bag', 'Shift + right click'], ['Nozzle or brush size', '[ ] or Shift + wheel'],
|
||||
['Replace what is built (Build)', 'Hold G'], ['Copy a box of what you built', 'Hold C, drag'], ['Cut a box (refunded, paste it back)', 'Hold X, drag'], ['Paste again', 'V'], ['Undo building', 'Ctrl + Z'], ['Blueprint library', 'B'], ['Zoom', 'Mouse wheel'], ['Interact (gates, molds, fans, filters)', 'F'], ['Thermal lens', 'H'], ['Info overlay: filters, belt and screw conveyor directions', 'I'], ['Bot lamp', 'L'],
|
||||
['Replace what is built (Build)', 'Hold G'], ['Copy a box of what you built', 'Hold C, drag'], ['Cut a box (refunded, paste it back)', 'Hold X, drag'], ['Paste again', 'V'], ['Undo building', 'Ctrl + Z'], ['Blueprint library', 'B'], ['Zoom', 'Mouse wheel'], ['Interact (gates, molds, fans, filters)', 'F'], ['Thermal lens', 'H'], ['Info overlay: filters, belt and screw conveyor directions, grains per second', 'I'], ['Bot lamp', 'L'],
|
||||
['Pick the piece under the cursor (with its direction and settings)', 'Middle mouse'], ['Notebook', 'N'], ['Clear all blueprint ghosts', 'Delete'], ['Save', 'Ctrl + S'], ['Pause · menu', 'Esc'], ['Mute', 'Shift + M'], ['Full map', 'M'], ['Minimap zoom', '+ / − on minimap'],
|
||||
];
|
||||
const pad = [
|
||||
|
||||
Reference in New Issue
Block a user