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:
+15
-9
@@ -252,7 +252,7 @@ Cell helpers (setCell, move, enter) and the falling-sand movement of powders, li
|
|||||||
- functions: setCell, swapCells, move, enter, enterFree, powder, passable, liquid, gas, wakeBotPowder, simMove
|
- functions: setCell, swapCells, move, enter, enterFree, powder, passable, liquid, gas, wakeBotPowder, simMove
|
||||||
- variables: SORTP, SORTP_FUEL, LSTEP, LFILM, CORNER_SEAL
|
- variables: SORTP, SORTP_FUEL, LSTEP, LFILM, CORNER_SEAL
|
||||||
|
|
||||||
### src/sim/heat.js (265 lines)
|
### src/sim/heat.js (266 lines)
|
||||||
Heat: conduction, melting and freezing, reactions, fire and air, steel.
|
Heat: conduction, melting and freezing, reactions, fire and air, steel.
|
||||||
- functions: curAir, airflowAt, alloyMix, airy, hasO2, condPair, heatPass, heatChunk, neighborDir, react, char, quench, burningNear, burn, steelCheck
|
- functions: curAir, airflowAt, alloyMix, airy, hasO2, condPair, heatPass, heatChunk, neighborDir, react, char, quench, burningNear, burn, steelCheck
|
||||||
- variables: POROUS, SOIL, HOTLIQ, REACT_MIN, ALLOY
|
- variables: POROUS, SOIL, HOTLIQ, REACT_MIN, ALLOY
|
||||||
@@ -277,7 +277,7 @@ Pipes, pumps and outlets: each joined line holds one liquid as a count, not as g
|
|||||||
- functions: sideOf, pipeClosedSide, pipeJoins, rebuildPipes, pipeNetOf, pipeTakeShare, faceCell, pumpDraw, outletPour, pipesTick
|
- functions: sideOf, pipeClosedSide, pipeJoins, rebuildPipes, pipeNetOf, pipeTakeShare, faceCell, pumpDraw, outletPour, pipesTick
|
||||||
- variables: PIPE_CAP, PUMP_RATE, OUTLET_RATE, PUMP_MAX_T, SIDE_V, PUMP_V, OUTLET_V, pipeNets, pipeNetAt, pipesDirty
|
- variables: PIPE_CAP, PUMP_RATE, OUTLET_RATE, PUMP_MAX_T, SIDE_V, PUMP_V, OUTLET_V, pipeNets, pipeNetAt, pipesDirty
|
||||||
|
|
||||||
### src/sim/machine-tick.js (156 lines)
|
### src/sim/machine-tick.js (157 lines)
|
||||||
Per-tick machine work: steam engines, fans, belts, filters, screw conveyors (machinesTick).
|
Per-tick machine work: steam engines, fans, belts, filters, screw conveyors (machinesTick).
|
||||||
- functions: engineIntake, fanBlow, machinesTick
|
- functions: engineIntake, fanBlow, machinesTick
|
||||||
|
|
||||||
@@ -286,12 +286,17 @@ Travel-directed transport ends. Precomputed probes; world/plan queries allocate
|
|||||||
- functions: pieceEnds, joinCandidate, endJoin, pieceJoins, endAt, joinIsDrop, joinSnap, nearMiss
|
- functions: pieceEnds, joinCandidate, endJoin, pieceJoins, endAt, joinIsDrop, joinSnap, nearMiss
|
||||||
- variables: JOIN_ENDS, NO_PIECE
|
- variables: JOIN_ENDS, NO_PIECE
|
||||||
|
|
||||||
### src/sim/conveyors.js (211 lines)
|
### src/sim/conveyors.js (212 lines)
|
||||||
How belts, filters and screw conveyors move what is on or in them. What rests on a belt or filter rides along: the whole stack standing on the carrying surface steps one cell toward d together (belts run front of the line first, so the way ahead is clear). If a powder grain is blocked, the load above it stands still and the belt slips underneath. A filter's own material drops through it instead, unless it is already full.
|
How belts, filters and screw conveyors move what is on or in them. What rests on a belt or filter rides along: the whole stack standing on the carrying surface steps one cell toward d together (belts run front of the line first, so the way ahead is clear). If a powder grain is blocked, the load above it stands still and the belt slips underneath. A filter's own material drops through it instead, unless it is already full.
|
||||||
- functions: conveyorStep, carryColumn, beltDrain, launchGrain, rebuildFilters, filterCarry, carryBeltsAndFilters, setFilter, filterRun, roomFor, screwSpread, screwIntake, screwAccepts, straightScrewNext, diagonalScrewNext
|
- functions: conveyorStep, carryColumn, beltDrain, launchGrain, rebuildFilters, filterCarry, carryBeltsAndFilters, setFilter, filterRun, roomFor, screwSpread, screwIntake, screwAccepts, straightScrewNext, diagonalScrewNext
|
||||||
- variables: CARRY_DEPTH, filterR, filterL, filtersDirty
|
- variables: CARRY_DEPTH, filterR, filterL, filtersDirty
|
||||||
|
|
||||||
### src/sim/slope-conveyors.js (230 lines)
|
### src/sim/flow.js (26 lines)
|
||||||
|
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.
|
||||||
|
- functions: setFlowCounting, flowNote, flowCross, flowSlopeUnder, flowTick
|
||||||
|
- variables: flowOn, flowCount, flowRate
|
||||||
|
|
||||||
|
### src/sim/slope-conveyors.js (231 lines)
|
||||||
Cleats are rubber ribs standing up from a sloped belt's face, one every CLEAT_SPACING face cells. Loose grains treat them as solid (cleatMask). Nothing grips the bare face: grains slide down it 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.
|
Cleats are rubber ribs standing up from a sloped belt's face, one every CLEAT_SPACING face cells. Loose grains treat them as solid (cleatMask). Nothing grips the bare face: grains slide down it 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.
|
||||||
- functions: ribMove, cleatFoot, ribCells, slopeUphill, slopeFaceAt, pushRow, scoop, pocketRoom, advanceCleat, slopeRunKey, slopeCleatsTick, rebuildCleatMask, expelFromRib, screenNudge, wakeCleatLoad
|
- functions: ribMove, cleatFoot, ribCells, slopeUphill, slopeFaceAt, pushRow, scoop, pocketRoom, advanceCleat, slopeRunKey, slopeCleatsTick, rebuildCleatMask, expelFromRib, screenNudge, wakeCleatLoad
|
||||||
- variables: CLEAT_SPACING, CLEAT_HEIGHT, CLEAT_REACH, SCREEN_NUDGE_TICKS, cleatChunks, cleatMask, cleatCells, slopePhase, ribLog
|
- variables: CLEAT_SPACING, CLEAT_HEIGHT, CLEAT_REACH, SCREEN_NUDGE_TICKS, cleatChunks, cleatMask, cleatCells, slopePhase, ribLog
|
||||||
@@ -314,7 +319,7 @@ Bars and other loose objects (ents), and the bag: adding, taking, costs and refu
|
|||||||
Tool helpers: reach, brushes, and plain digging.
|
Tool helpers: reach, brushes, and plain digging.
|
||||||
- functions: inReach, forBrush, drillReach, inDrillReach, toolDig
|
- functions: inReach, forBrush, drillReach, inDrillReach, toolDig
|
||||||
|
|
||||||
### src/sim/drill.js (160 lines)
|
### src/sim/drill.js (161 lines)
|
||||||
The resonance drill: tuned pulses that shatter one material.
|
The resonance drill: tuned pulses that shatter one material.
|
||||||
- functions: drillOrigin, pulseRay, builtCell, toolPulse, crumbleAround, crumbly, shatterCell, pulseTick
|
- functions: drillOrigin, pulseRay, builtCell, toolPulse, crumbleAround, crumbly, shatterCell, pulseTick
|
||||||
- variables: PULSE_W, PULSE_EVERY, PULSE_SPEED, PULSE_K, drillPulses, lastPulseTick, drillWidth, THIN_BONUS, CRUMBLE_MAX, THIN_BONUS_OFF
|
- variables: PULSE_W, PULSE_EVERY, PULSE_SPEED, PULSE_K, drillPulses, lastPulseTick, drillWidth, THIN_BONUS, CRUMBLE_MAX, THIN_BONUS_OFF
|
||||||
@@ -356,7 +361,7 @@ Weather: clouds from steam, rain and snow, and the world water count.
|
|||||||
- functions: wrnd, resetWeather, rainGap, worldWater, absorbSteam, spawnRain, rainAt, weatherTick
|
- functions: wrnd, resetWeather, rainGap, worldWater, absorbSteam, spawnRain, rainAt, weatherTick
|
||||||
- variables: WBAND, WS, weather
|
- variables: WBAND, WS, weather
|
||||||
|
|
||||||
### src/sim/tick.js (28 lines)
|
### src/sim/tick.js (29 lines)
|
||||||
simTick: one simulation step, in order.
|
simTick: one simulation step, in order.
|
||||||
- functions: simTick
|
- functions: simTick
|
||||||
|
|
||||||
@@ -406,9 +411,9 @@ Scale colour without changing the smooth-look class.
|
|||||||
Machine overlays: fans, engines, silo and the rest, drawn on top of the world.
|
Machine overlays: fans, engines, silo and the rest, drawn on top of the world.
|
||||||
- functions: cellRect, drawHazardRims, drawMachines, drawProcBlocks, drawSilo, drawSiloPixel, drawSiloWindow, drawRackBlocks
|
- functions: cellRect, drawHazardRims, drawMachines, drawProcBlocks, drawSilo, drawSiloPixel, drawSiloWindow, drawRackBlocks
|
||||||
|
|
||||||
### src/render/overlays.js (251 lines)
|
### src/render/overlays.js (285 lines)
|
||||||
Other overlays: floating text, rain and snow, flow arrows, filter tags, molds, the bot, particles.
|
Other overlays: floating text, rain and snow, flow arrows, filter tags, molds, the bot, particles.
|
||||||
- functions: spawnFloater, drawRain, drawFlowArrows, drawJoinMark, drawJoinMarks, drawRunOutline, drawFilterTags, drawFloaters, moldLabel, drawMolds, drawBot, spawnDebris, spawnSpark, spawnSuck, drawParticles
|
- functions: spawnFloater, drawRain, drawFlowArrows, drawJoinMark, drawJoinMarks, flowLoaded, drawFlowRates, drawRunOutline, drawFilterTags, drawFloaters, moldLabel, drawMolds, drawBot, spawnDebris, spawnSpark, spawnSuck, drawParticles
|
||||||
|
|
||||||
### src/render/smooth.js (99 lines)
|
### src/render/smooth.js (99 lines)
|
||||||
Smooth look: WebGL setup, texture uploads, drawing and adaptive resolution.
|
Smooth look: WebGL setup, texture uploads, drawing and adaptive resolution.
|
||||||
@@ -493,7 +498,7 @@ Selection panels: current tool card and bag materials.
|
|||||||
- functions: renderSelCard, renderBag
|
- functions: renderSelCard, renderBag
|
||||||
- variables: lastBagKey
|
- variables: lastBagKey
|
||||||
|
|
||||||
### src/ui/info.js (200 lines)
|
### src/ui/info.js (201 lines)
|
||||||
Discovery cards, hover info (I) and the cursor label.
|
Discovery cards, hover info (I) and the cursor label.
|
||||||
- functions: showCard, howText, discoveryCard, reactionsOf, hoverInfo, renderInfo, drawCursorLabel, pipeContentsText
|
- functions: showCard, howText, discoveryCard, reactionsOf, hoverInfo, renderInfo, drawCursorLabel, pipeContentsText
|
||||||
|
|
||||||
@@ -560,6 +565,7 @@ The frame loop and boot.
|
|||||||
- `test/t_diag_screws.js`: Diagonal screws: sealed four-lane corner transfers, bars, joins and persistence.
|
- `test/t_diag_screws.js`: Diagonal screws: sealed four-lane corner transfers, bars, joins and persistence.
|
||||||
- `test/t_drill.js`: The resonance drill: pulses from the bot shake rock until it shatters. Widths trade area for speed, tuning shatters only one material and drains it out of the wall, built blocks are left alone, and the drill says why when nothing breaks.
|
- `test/t_drill.js`: The resonance drill: pulses from the bot shake rock until it shatters. Widths trade area for speed, tuning shatters only one material and drains it out of the wall, built blocks are left alone, and the drill says why when nothing breaks.
|
||||||
- `test/t_econ.js`: Economy: the dock (instant sale), orders, prices, the market dip, money for machines, alloys.
|
- `test/t_econ.js`: Economy: the dock (instant sale), orders, prices, the market dip, money for machines, alloys.
|
||||||
|
- `test/t_flow.js`: Throughput overlay (t49): grains per second per transport block, counted only while the overlay is on, never changing what the simulation does.
|
||||||
- `test/t_fog.js`: Fog: surface, sight, range, saves and simulation independence.
|
- `test/t_fog.js`: Fog: surface, sight, range, saves and simulation independence.
|
||||||
- `test/t_furnace_repeat.js`: The starter furnaces keep working when fired again and again without cleaning, however the charge goes in: mixed, ore first with coal on top, or blown in through the vent. Each case plays furnace_play.js, which builds the design and fires it like a player.
|
- `test/t_furnace_repeat.js`: The starter furnaces keep working when fired again and again without cleaning, however the charge goes in: mixed, ore first with coal on top, or blown in through the vent. Each case plays furnace_play.js, which builds the design and fires it like a player.
|
||||||
- `test/t_joins.js`: The oracle states the geometric rule independently of the precomputed neighbour tables.
|
- `test/t_joins.js`: The oracle states the geometric rule independently of the precomputed neighbour tables.
|
||||||
|
|||||||
@@ -4,6 +4,20 @@ What agents have done, newest first. Add an entry for every piece of work: the d
|
|||||||
note or request it was for, what changed, the files touched, the tests run, and anything left
|
note or request it was for, what changed, the files touched, the tests run, and anything left
|
||||||
open or not verified. Keep each entry short; the git history has the details.
|
open or not verified. Keep each entry short; the git history has the details.
|
||||||
|
|
||||||
|
## 2026-10-27: t49 throughput overlay (Claude Code)
|
||||||
|
|
||||||
|
- The I overlay also shows grains per second at each line's open outlet, an orange glow on
|
||||||
|
transport blocks that are loaded but not moving, and the rate in the hover. `src/sim/flow.js`
|
||||||
|
counts block-edge crossings per carrier block while `flowOn` (set by the I toggle) and folds
|
||||||
|
them into a smoothed per-second rate once a second. Hooks: `carryColumn`, `machinesTick` screws,
|
||||||
|
`screenNudge`, uphill cleat runs (only runs that commit), and the uphill face drag.
|
||||||
|
- Not counted: bars. Not built: money per minute.
|
||||||
|
- Files: `src/sim/flow.js`, `src/manifest.json`, `src/sim/conveyors.js`, `src/sim/machine-tick.js`,
|
||||||
|
`src/sim/slope-conveyors.js`, `src/sim/tick.js`, `src/render/overlays.js`, `src/ui/loop.js`,
|
||||||
|
`src/ui/input.js`, `src/ui/info.js`, `src/ui/notebook.js`, `test/t_flow.js`, `docs/PROJECT_MAP.md`.
|
||||||
|
- Tests: `test/t_flow.js` (including a check that counting changes nothing in the simulation);
|
||||||
|
`python3 test/run_all.py --browser` all passed.
|
||||||
|
|
||||||
## 2026-10-27: cooled magma visible and drillable (Claude Code)
|
## 2026-10-27: cooled magma visible and drillable (Claude Code)
|
||||||
|
|
||||||
- Owner report: bag magma in a brick box left invisible basalt that couldn't be picked up,
|
- Owner report: bag magma in a brick box left invisible basalt that couldn't be picked up,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"sim/machine-tick.js",
|
"sim/machine-tick.js",
|
||||||
"sim/joins.js",
|
"sim/joins.js",
|
||||||
"sim/conveyors.js",
|
"sim/conveyors.js",
|
||||||
|
"sim/flow.js",
|
||||||
"sim/slope-conveyors.js",
|
"sim/slope-conveyors.js",
|
||||||
"sim/blocks.js",
|
"sim/blocks.js",
|
||||||
"sim/molds.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);
|
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
|
// Outline a run of blocks: strong while it is being edited, faint when pointed at
|
||||||
function drawRunOutline(run, strong) {
|
function drawRunOutline(run, strong) {
|
||||||
if (!run || !run.length) return;
|
if (!run || !run.length) return;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function conveyorStep(x, y, d) {
|
|||||||
}
|
}
|
||||||
function carryColumn(a, d, pass) {
|
function carryColumn(a, d, pass) {
|
||||||
if (a < 0 || mat[a] === EMPTY) return;
|
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;
|
let blocked = false;
|
||||||
for (let L = 0; L < CARRY_DEPTH; L++, a -= W) {
|
for (let L = 0; L < CARRY_DEPTH; L++, a -= W) {
|
||||||
if (a < 0) return;
|
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);
|
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);
|
else move(a, j, x, y, nx, y + step);
|
||||||
stats.moved++;
|
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.
|
// 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) {
|
if (!screw && !pj && t === EMPTY) {
|
||||||
// out of the mouth: the screw spits it clear, so the next grain is never blocked by the last
|
// 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);
|
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++;
|
move(i, j, x, y, nx, ny); stats.moved++;
|
||||||
|
if (flowOn) flowCross((y >> 2) * BW + (x >> 2), i, j);
|
||||||
if (screw && pj !== p) {
|
if (screw && pj !== p) {
|
||||||
// turning a corner: keep going across the new screw's width so every lane fills
|
// turning a corner: keep going across the new screw's width so every lane fills
|
||||||
screwSpread(j, nx, ny, dx, dy);
|
screwSpread(j, nx, ny, dx, dy);
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ function slopeCleatsTick(advance = true) {
|
|||||||
ribLog.length = 0;
|
ribLog.length = 0;
|
||||||
let ok = true;
|
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]);
|
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);
|
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]); }
|
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
|
// 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);
|
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);
|
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;
|
if (pass && pass[m] && enter(m, PH_POWDER, top, 0, 1)) return;
|
||||||
const j = i + d + W;
|
const j = i + d + W;
|
||||||
if (i % W + d < 0 || i % W + d >= W || j >= N) return;
|
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.
|
// Only loose load can react to a barrier appearing or disappearing.
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ function simTick() {
|
|||||||
if (tick % 8 === 0) wakeBotPowder();
|
if (tick % 8 === 0) wakeBotPowder();
|
||||||
simMove();
|
simMove();
|
||||||
heatPass();
|
heatPass();
|
||||||
|
if (flowOn) flowTick();
|
||||||
for (let c = 0; c < NCH; c++) if (awake[c]) awake[c]--;
|
for (let c = 0; c < NCH; c++) if (awake[c]) awake[c]--;
|
||||||
if (tick % 15 === 0) checkMolds();
|
if (tick % 15 === 0) checkMolds();
|
||||||
if (tick % 30 === 0) checkLedger();
|
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'}`;
|
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);
|
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++) {
|
for (let end = 0; end < 2 && !id && !bar; end++) {
|
||||||
const miss = nearMiss(jb % BW, (jb / BW) | 0, 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; }
|
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 'f': doInteract(); break;
|
||||||
case 'g': GAME.replace = true; hudDirty = true; break;
|
case 'g': GAME.replace = true; hudDirty = true; break;
|
||||||
case 'h': toggleLens(); 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 'l': bot.lamp = !bot.lamp; playSfx('ui'); break;
|
||||||
case 'n': openNotebook(); break;
|
case 'n': openNotebook(); break;
|
||||||
case 'b': openNotebook('Blueprints'); break;
|
case 'b': openNotebook('Blueprints'); break;
|
||||||
|
|||||||
+1
-1
@@ -70,7 +70,7 @@ function frame(now) {
|
|||||||
drawParticles();
|
drawParticles();
|
||||||
const hb = GAME.mouse.in || GAME.usingPad ? filterBlockAt(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y)) : -1;
|
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.editRun) drawRunOutline(GAME.editRun, true); else if (hb >= 0) drawRunOutline(filterRun(hb), false);
|
||||||
if (GAME.infoOverlay) drawFlowArrows();
|
if (GAME.infoOverlay) { drawFlowArrows(); drawFlowRates(); }
|
||||||
drawJoinMarks();
|
drawJoinMarks();
|
||||||
drawFilterTags(!!GAME.infoOverlay, GAME.editRun ? GAME.editRun[0] : hb);
|
drawFilterTags(!!GAME.infoOverlay, GAME.editRun ? GAME.editRun[0] : hb);
|
||||||
drawFloaters();
|
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'],
|
['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'],
|
['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'],
|
['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'],
|
['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 = [
|
const pad = [
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Throughput overlay (t49): grains per second per transport block, counted only while the
|
||||||
|
// overlay is on, never changing what the simulation does.
|
||||||
|
const { load, suite, flatWorld, rect } = require('./harness');
|
||||||
|
const { run } = load();
|
||||||
|
const { check, done } = suite();
|
||||||
|
// A flat belt line fed from a sand heap, a screw line going right, and an uphill belt.
|
||||||
|
function scene() {
|
||||||
|
flatWorld(run, 240, 160, 140);
|
||||||
|
run('RS = 7; nextPowerBuild = 0; bot.x = 120; bot.y = 60;');
|
||||||
|
run('for (let bx = 10; bx < 20; bx++) placeBlock(BP_BELT, 1, bx, 30, true);');
|
||||||
|
rect(run, 40, 100, 47, 119, run('SAND'));
|
||||||
|
run('for (let bx = 30; bx < 38; bx++) placeBlock(BP_SCREW, 1, bx, 30, true);');
|
||||||
|
rect(run, 120, 110, 123, 119, run('SAND'));
|
||||||
|
run('for (let k = 0; k < 6; k++) placeBlock(BP_BELT_UP, 1, 44 + k, 30 - k, true); placeBlock(BP_BELT, 1, 43, 31, true);');
|
||||||
|
rect(run, 172, 112, 175, 123, run('SAND'));
|
||||||
|
}
|
||||||
|
const hash = () => run('(() => { let h = 0; for (let i = 0; i < N; i++) h = (h * 31 + mat[i]) | 0; return h; })()');
|
||||||
|
scene(); run('setFlowCounting(false); for (let k = 0; k < 300; k++) simTick();');
|
||||||
|
const off = hash();
|
||||||
|
scene(); run('setFlowCounting(true); for (let k = 0; k < 300; k++) simTick();');
|
||||||
|
check('counting changes nothing in the simulation', hash() === off);
|
||||||
|
const r = run('({ belt: flowRate[30 * BW + 15], beltEnd: flowRate[30 * BW + 19], screw: flowRate[30 * BW + 34], uphill: Math.max(...[0,1,2,3,4,5].map(k => flowRate[(30 - k) * BW + 44 + k])) })');
|
||||||
|
check('a loaded flat belt reports grains per second', r.belt > 0, JSON.stringify(r));
|
||||||
|
check('a loaded screw conveyor reports grains per second', r.screw > 0, JSON.stringify(r));
|
||||||
|
check('an uphill belt carrying sand reports grains per second', r.uphill > 0, JSON.stringify(r));
|
||||||
|
check('the rate is grains per second, not per tick (a flat belt moves well under 600)', r.belt < 600, JSON.stringify(r));
|
||||||
|
scene(); run('setFlowCounting(false); for (let k = 0; k < 120; k++) simTick();');
|
||||||
|
check('with the overlay off nothing is counted', run('flowCount.every(n => n === 0) && flowRate.every(n => n === 0)'));
|
||||||
|
done();
|
||||||
Reference in New Issue
Block a user