diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 558261f..c690b94 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -140,8 +140,9 @@ above, including: short stubs stay for the next firing. F stays available for cleaning and raking. - **Screw tubes carry no more than belts:** they move every other tick. For more throughput, build more lines. -- **A heap on a belt moves as one**, up to 96 grains deep, not just its bottom layer. The owner - reports this still misbehaves (see NOTES.md). +- **A heap on a belt moves as one**, up to 96 grains deep, not just its bottom layer. If a grain is blocked, + the load above it stands still and the belt slips underneath; the top no longer slides over + a stuck bottom. Filters still let their chosen materials drop through. - **Pick** (middle mouse) selects the piece under the cursor with its direction, wall material and filter settings. G is unbound, as the owner requested. - **The build menu** is a bar of tabs and icon tiles along the bottom: Structure, Moving, diff --git a/docs/PROJECT_MAP.md b/docs/PROJECT_MAP.md index 58c8285..4f431e9 100644 --- a/docs/PROJECT_MAP.md +++ b/docs/PROJECT_MAP.md @@ -248,8 +248,8 @@ Pipes, pumps and outlets: each joined line holds one liquid as a count, not as g Per-tick machine work: steam engines, fans, belts, filters, screw tubes (machinesTick). - functions: engineIntake, fanBlow, machinesTick -### src/sim/conveyors.js (109 lines) -How belts, filters and screw tubes 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), the way a real belt carries a heap, not just its bottom layer. A filter's own material drops through it instead, unless the filter below is already full. +### src/sim/conveyors.js (114 lines) +How belts, filters and screw tubes 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: carryColumn, beltDrain, launchGrain, rebuildFilters, filterCarry, setFilter, filterRun, roomFor, tubeIntake - variables: CARRY_DEPTH, filterR, filterL, filtersDirty @@ -257,10 +257,10 @@ How belts, filters and screw tubes move what is on or in them. What rests on a b Building blocks: placing, integrity, removing, lines and rectangles of blocks. - functions: blockOf, blockCellList, pieceCost, canPlaceBlock, placeBlock, blockIntegrity, removeBlock, blockLine, blockRect, blockInReach, blockIs, placeBlocks -### src/sim/molds.js (297 lines) +### src/sim/molds.js (298 lines) Molds: cavities for castings; ingot troughs drop bars out of the bottom. - functions: moldCellsAt, canPlaceMold, newMoldRecord, addMold, castMix, cavityState, troughState, clearCavity, moldPlugCells, rakeAbove, furnaceJunkHint, unplugMold, ejectIngot, checkMolds, moldAt, knockNote, tally, collectMold, removeMold -- variables: DROSS, grains +- variables: DROSS, grains, barWord ### src/sim/items.js (134 lines) Bars and other loose objects (ents), and the bag: adding, taking, costs and refunds. @@ -467,5 +467,6 @@ The frame loop and boot. - `tools/experiments/smelter.js`: Smelter design experiments, built with the same block pieces a player uses. Layout (block rows, 4x4 grains each), on flat ground at row 25: roof with a vent ........ row g-CH-1 chamber, CH blocks tall .. charge of coal + copper ore inside grate floor ............. row g = 23 (liquids pass, powders do not) ingot mold .............. row 24, sitting on the ground side walls run from the roof down past the mold. Usage: node t_smelter.js [key=value ...] - `tools/experiments/starter.js`: Plays a starter design the way a player would: paste the blueprint, let the bot build it from the bag, blow an even ore/coal mix inside, light it, wait, knock out the castings. Usage: node tools/experiments/starter.js [design id] [ore] [coal] - `tools/experiments/worldgen.js`: World generation report: time to generate, how many cells of each material, where the bot starts, and what idle ticks and ticks after digging cost. +- `tools/scenes/belt-heap.js`: A belt feeding an upward screw: sand underneath a deep heap of copper ore. Run clear-sky.js first, e.g. python3 tools/shot.py /tmp/belt-heap.png --setup tools/scenes/clear-sky.js --js "$(cat tools/scenes/belt-heap.js)" --ticks 240 --zoom 7 - `tools/scenes/clear-sky.js`: A clear work area left of the bot's starting point (the trade dock is to its right): open sky 160 grains wide and 40 tall, over a flat stone floor. Money and a bag of building stuff to build with. Sets FLOOR (the first row of the floor), BX, BY (the leftmost block of the area that stands on the floor) and CAM (the middle of the area). - `tools/scenes/stone-uses.js`: The stone and sand machines: a half-full silo with its gate open over a belt, a stone cutter fed with rubble, and cut stone and glass brick walls. Run clear-sky.js first, e.g. python3 tools/shot.py /tmp/stone.png --setup tools/scenes/clear-sky.js --js "$(cat tools/scenes/stone-uses.js)" --ticks 240 --zoom 7 diff --git a/docs/agent-log.md b/docs/agent-log.md index 8e38baf..f30efcc 100644 --- a/docs/agent-log.md +++ b/docs/agent-log.md @@ -4,6 +4,26 @@ 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 open or not verified. Keep each entry short; the git history has the details. +## 2026-10-23: a backed-up heap on a belt no longer shears (Claude Code, with Codex) + +Note: "belts with a big pile on top only move the grains on the top of the stack…". The owner's +setup: a belt ending in a screw tube going up; material mixed into the bottom layers never moved. + +- Cause: `carryColumn` (`src/sim/conveyors.js`) skipped a blocked grain and carried on up the + column, so with the front backed up the top slid over a stuck bottom. Reproduced headlessly + first: over 4,000 ticks, 8,815 shear steps at the tube (1,415 against a wall) and only 57 of + 192 bottom sand grains left the belt. +- Fix: on a belt, the walk stops at the first blocked powder grain, so the load above stands + still. On a filter the walk goes on only so chosen materials can still drop through; nothing + above a blocked grain is carried. After: no shear, 119 of 192 sand grains out through the tube. +- `test/t_conveyors.js`: 3 new checks (no shear into an up tube, none against a wall, bottom + layer leaves through the tube). `tools/scenes/belt-heap.js` sets up the owner's case for + `tools/shot.py`; shots at 30 and 900 ticks look right. `docs/DESIGN.md`, `docs/PROJECT_MAP.md`. +- Tests: all 11 headless suites and `play.py` (109) pass. `t_smooth.py` failed its + half-resolution check once at load 6.5 and passed on the rerun. +- Open: `t_smooth.py`'s half-resolution check is flaky on a busy machine (the game drops to + pixel when frames are slow). It is worth making that test immune to the slow fallback. + ## 2026-10-23: ingots drop out of the bottom of the mold (Claude Code, with Codex) Note: "copper bars go into the furnace. also they shouldnt require the player to press F…". diff --git a/src/sim/conveyors.js b/src/sim/conveyors.js index 6c73828..5ea550c 100644 --- a/src/sim/conveyors.js +++ b/src/sim/conveyors.js @@ -1,10 +1,11 @@ // How belts, filters and screw tubes 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), the way a real belt carries a heap, not just its bottom layer. A filter's own -// material drops through it instead, unless the filter below is already full. +// 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. const CARRY_DEPTH = 96; function carryColumn(a, d, pass) { + let blocked = false; for (let L = 0; L < CARRY_DEPTH; L++, a -= W) { if (a < 0) return; const m = mat[a]; @@ -16,12 +17,16 @@ function carryColumn(a, d, pass) { const u = mat[a + W]; if (L > 0 && !pass[u] && PH[u] === PH_POWDER && stamp[a + W] !== tickStamp && !occ[a + W] && rnd() < 0.5) { const x = a % W, y = (a / W) | 0; move(a, a + W, x, y, x, y + 1); continue; } } + if (blocked) continue; // filters still sift above a stopped load const x = a % W, nx = x + d; if (nx < 0 || nx >= W) return; const j = a + d, tj = mat[j]; // grains on a wet belt push through the water rather than stopping at it const wet = PH[tj] === PH_LIQUID && !occ[j] && stamp[j] !== tickStamp && (!part[j] || part[j] === P_FILTER || part[j] === P_GOPEN); - if (!wet && !enter(m, PH_POWDER, j, d, 0)) continue; + if (!wet && !enter(m, PH_POWDER, j, d, 0)) { + if (!pass) return; + blocked = true; continue; + } const y = (a / W) | 0, bj = j + W; // carried off the 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, nx, y, d * 0.5, 0); diff --git a/test/t_conveyors.js b/test/t_conveyors.js index 9f1ba96..e717655 100644 --- a/test/t_conveyors.js +++ b/test/t_conveyors.js @@ -157,4 +157,36 @@ r = JSON.parse(run('JSON.stringify(placeBlocks(BP_WALL, RSTONE, blockLine(20, 25 check('and a wall can replace them too', r.n === 2 && run('blockP[25*BW+20]') === run('BP_WALL') && run('!filterCfg.has(25*BW+20)'), JSON.stringify(r)); check('filters are a buildable piece with a price', run('PIECE_BY_ID.filter && !PIECE_BY_ID.filter.hidden && PIECE_BY_ID.filter.cost[0][0] === CASH')); check('no trucks', run('typeof truck') === 'undefined'); +// --- a backed-up heap must not shear over its bottom layer +// Watch belt beats, not settling between beats: copper must not ride over sand that stays put. +run(`globalThis.__carry = carryColumn; + globalThis.__shear = 0; + carryColumn = function(a, d, pass) { + const bottom = a, watch = []; + const sand = mat[a] === SAND && stamp[a] !== tickStamp && !occ[a]; + if (sand) for (let i = a - W; i >= 0 && mat[i] !== EMPTY; i -= W) { + if (mat[i] === CUORE && stamp[i] !== tickStamp) watch.push(i); + } + __carry(a, d, pass); + if (sand && mat[bottom] === SAND && stamp[bottom] !== tickStamp) + for (const i of watch) if (mat[i] !== CUORE) __shear++; + };`); +for (const lift of [true, false]) { + fresh(); + run('RS = 7; __shear = 0; for (let bx = 20; bx < 32; bx++) placeBlock(BP_BELT, 1, bx, 25, true);'); + if (lift) run('for (let by = 15; by <= 25; by++) placeBlock(BP_TUBE, 0, 32, by, true);'); + else run('for (let by = 10; by <= 25; by++) placeBlock(BP_WALL, RSTONE, 32, by, true);'); + rect(run, 88, 94, 119, 99, run('SAND')); + rect(run, 88, 70, 119, 93, run('CUORE')); + ticks(run, 4000); + const shear = run('__shear'); + check(lift ? 'a heap feeding an upward screw does not shear over stuck sand' : 'a heap against a wall does not shear over stuck sand', shear === 0, `${shear} copper steps over stationary sand in 4000 ticks`); + if (lift) { + const left = count(run, run('SAND'), 80, 0, 127, 99); + const lifted = count(run, run('SAND'), 128, 0, 319, 93); + check('the bottom sand leaves the belt through the upward screw too', left < 192 / 2 && lifted > 0, `${192 - left} of 192 sand left the belt, ${lifted} above its bottom layer at the lift`); + } +} +run('carryColumn = __carry;'); + done(); diff --git a/tools/scenes/belt-heap.js b/tools/scenes/belt-heap.js new file mode 100644 index 0000000..948452a --- /dev/null +++ b/tools/scenes/belt-heap.js @@ -0,0 +1,9 @@ +// A belt feeding an upward screw: sand underneath a deep heap of copper ore. +// Run clear-sky.js first, e.g. +// python3 tools/shot.py /tmp/belt-heap.png --setup tools/scenes/clear-sky.js --js "$(cat tools/scenes/belt-heap.js)" --ticks 240 --zoom 7 +const bx = BX + 6, by = BY; +for (let x = bx; x < bx + 12; x++) placeBlock(BP_BELT, 1, x, by, true); +for (let y = by - 10; y <= by; y++) placeBlock(BP_TUBE, 0, bx + 12, y, true); +for (let y = by * 4 - 30; y < by * 4; y++) for (let x = (bx + 2) * 4; x < (bx + 10) * 4; x++) + setCell(y * W + x, y >= by * 4 - 6 ? SAND : CUORE, AMB[y], 0); +window.CAM = [(bx + 7) * 4, (by - 5) * 4];