Grinder and Saw hopper blocks replace the Refiner and Stone cutter in the menu (t4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -220,6 +220,11 @@ above, including:
|
|||||||
lands on them and drop it below, so a wider row is a wider, faster hopper. The Refiner and Stone
|
lands on them and drop it below, so a wider row is a wider, faster hopper. The Refiner and Stone
|
||||||
cutter leave the build menu; built ones in old saves keep working. The owner wanted hoppers of
|
cutter leave the build menu; built ones in old saves keep working. The owner wanted hoppers of
|
||||||
any width.
|
any width.
|
||||||
|
As built: a block is solid and works the grains resting on its top edge; results drop out of
|
||||||
|
its bottom, and it stops when that space is full. Same speed and price per width as the old
|
||||||
|
machines (Grinder 0.25 grains/tick, $60; Saw 0.5, $90). In a vertical stack only the top block
|
||||||
|
takes input and the result passes down through the rest, so a Saw on a Grinder cuts stone and
|
||||||
|
grinds everything else. Partial brick counts are not saved.
|
||||||
- **The bag is the vacuum's control panel (t6).** Each bag row has separate pick-up and blow
|
- **The bag is the vacuum's control panel (t6).** Each bag row has separate pick-up and blow
|
||||||
marks; a chip on the tool card sets pick-up to everything, only marked, or the same as the
|
marks; a chip on the tool card sets pick-up to everything, only marked, or the same as the
|
||||||
first click; R over a grain toggles its pick-up mark. The owner found the shared ticks confusing.
|
first click; R over a grain toggles its pick-up mark. The owner found the shared ticks confusing.
|
||||||
|
|||||||
+11
-11
@@ -196,15 +196,15 @@ Deepgrain Foundry: simulation core (src/sim/). No DOM anywhere in src/sim/: plai
|
|||||||
- functions: rnd, mulberry32, hash2
|
- functions: rnd, mulberry32, hash2
|
||||||
- variables: RS, PH_EMPTY, PH_SOLID, PH_POWDER, PH_LIQUID, PH_GAS, PH_ITEM
|
- variables: RS, PH_EMPTY, PH_SOLID, PH_POWDER, PH_LIQUID, PH_GAS, PH_ITEM
|
||||||
|
|
||||||
### src/sim/materials.js (159 lines)
|
### src/sim/materials.js (161 lines)
|
||||||
Material ids, the material table (def), per-material property arrays, cell flags and part ids, and drill/tank tiers.
|
Material ids, the material table (def), per-material property arrays, cell flags and part ids, and drill/tank tiers.
|
||||||
- functions: def, metalFamily
|
- functions: def, metalFamily
|
||||||
- variables: EMPTY, GRASS, DIRT, LDIRT, SAND, GRAVEL, CLAY, STONE, GRANITE, BASALT, NMAT, MD, PH, DENS, KC, ICAP, CASTMETAL, METAL_TIER, SOLIDMETAL, F_NAT, F_CAST, F_BURN, P_NONE, P_TU, P_TD, P_TL, P_TR, P_GRATE, P_GOPEN, P_WHEEL, P_INTAKE, P_FILTER, P_PIPE, TDX, TDY, BIT_TIERS, TANK_TIERS, REACH
|
- variables: EMPTY, GRASS, DIRT, LDIRT, SAND, GRAVEL, CLAY, STONE, GRANITE, BASALT, NMAT, MD, PH, DENS, KC, ICAP, CASTMETAL, METAL_TIER, SOLIDMETAL, F_NAT, F_CAST, F_BURN, P_NONE, P_TU, P_TD, P_TL, P_TR, P_GRATE, P_GOPEN, P_WHEEL, P_INTAKE, P_FILTER, P_PIPE, TDX, TDY, BIT_TIERS, TANK_TIERS, REACH
|
||||||
|
|
||||||
### src/sim/pieces.js (139 lines)
|
### src/sim/pieces.js (143 lines)
|
||||||
Everything the player can build (PIECES): blocks, machines and molds, with costs and descriptions; filter settings; wall materials and their cost.
|
Everything the player can build (PIECES): blocks, machines and molds, with costs and descriptions; filter settings; wall materials and their cost.
|
||||||
- functions: isPipeCode, filterable, cleanFilterCfg, filterRec, filterPassAt, filterInfo, sameFilter, wallCost
|
- functions: isPipeCode, filterable, cleanFilterCfg, filterRec, filterPassAt, filterInfo, sameFilter, wallCost
|
||||||
- variables: BS, BP_WALL, BP_BELT, BP_TUBE, BP_SHAFT, BP_GRATE, BP_GATE, BP_FILTER, BP_PIPE, BP_PUMP, BP_OUTLET, filterCfg, WALL_OF, WALL_LIST, BAR_OF, GRAINS_PER_BAR, POWER_FREE, CASH, PIECES, PIECE_BY_ID, PIECE_BY_CODE, BELT_POWER, TUBE_POWER, FAN_POWER, WHEEL_K, ENGINE_K, MOLDS
|
- variables: BS, BP_WALL, BP_BELT, BP_TUBE, BP_SHAFT, BP_GRATE, BP_GATE, BP_FILTER, BP_PIPE, BP_PUMP, BP_OUTLET, BP_GRIND, BP_SAW, filterCfg, WALL_OF, WALL_LIST, BAR_OF, GRAINS_PER_BAR, POWER_FREE, CASH, PIECES, PIECE_BY_ID, PIECE_BY_CODE, BELT_POWER, TUBE_POWER, FAN_POWER, WHEEL_K, ENGINE_K, MOLDS
|
||||||
|
|
||||||
### src/sim/economy.js (152 lines)
|
### src/sim/economy.js (152 lines)
|
||||||
Money, prices, market saturation, the trade dock, buyer and builder orders.
|
Money, prices, market saturation, the trade dock, buyer and builder orders.
|
||||||
@@ -241,17 +241,17 @@ Machines: the power network, footprints, placing and removing machines.
|
|||||||
- functions: markPow, partLost, rebuildPower, netSat, evictCells, cellBuildable, machineFootprint, canPlaceMachine, addMachine, removeMachine
|
- functions: markPow, partLost, rebuildPower, netSat, evictCells, cellBuildable, machineFootprint, canPlaceMachine, addMachine, removeMachine
|
||||||
- variables: nextPowerBuild
|
- variables: nextPowerBuild
|
||||||
|
|
||||||
### src/sim/processors.js (99 lines)
|
### src/sim/processors.js (165 lines)
|
||||||
Processing machines: the Refiner, the Stone cutter and the Silo.
|
Processing machines: the Refiner, the Stone cutter and the Silo.
|
||||||
- functions: refinerSlot, refinerTick, cutterTick, siloTick, siloFull
|
- functions: refinerSlot, refinerTick, cutterTick, isProcCode, rebuildProcessors, procBottom, procSlot, procFeed, procBlocksTick, siloTick, siloFull
|
||||||
- variables: REFINE, REFINE_SAND, REFINE_RATE, CUT_PER_BRICK, CUT_RATE, SILO_CAP, SILO_RATE
|
- variables: REFINE, REFINE_SAND, REFINE_RATE, CUT_PER_BRICK, CUT_RATE, procState, procBlocks, procDirty, SILO_CAP, SILO_RATE
|
||||||
|
|
||||||
### src/sim/pipes.js (114 lines)
|
### src/sim/pipes.js (114 lines)
|
||||||
Pipes, pumps and outlets: each joined line holds one liquid as a count, not as grains.
|
Pipes, pumps and outlets: each joined line holds one liquid as a count, not as grains.
|
||||||
- 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 (166 lines)
|
### src/sim/machine-tick.js (167 lines)
|
||||||
Per-tick machine work: steam engines, fans, belts, filters, screw tubes (machinesTick).
|
Per-tick machine work: steam engines, fans, belts, filters, screw tubes (machinesTick).
|
||||||
- functions: engineIntake, fanBlow, machinesTick
|
- functions: engineIntake, fanBlow, machinesTick
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ How belts, filters and screw tubes move what is on or in them. What rests on a b
|
|||||||
- functions: carryColumn, beltDrain, launchGrain, rebuildFilters, filterCarry, setFilter, filterRun, roomFor, tubeSpread, tubeIntake
|
- functions: carryColumn, beltDrain, launchGrain, rebuildFilters, filterCarry, setFilter, filterRun, roomFor, tubeSpread, tubeIntake
|
||||||
- variables: CARRY_DEPTH, filterR, filterL, filtersDirty
|
- variables: CARRY_DEPTH, filterR, filterL, filtersDirty
|
||||||
|
|
||||||
### src/sim/blocks.js (132 lines)
|
### src/sim/blocks.js (136 lines)
|
||||||
Building blocks: placing, integrity, removing, lines and rectangles of blocks.
|
Building blocks: placing, integrity, removing, lines and rectangles of blocks.
|
||||||
- functions: blockOf, blockCellList, pieceCost, canPlaceBlock, placeBlock, blockIntegrity, removeBlock, blockLine, blockRect, blockInReach, blockIs, placeBlocks
|
- functions: blockOf, blockCellList, pieceCost, canPlaceBlock, placeBlock, blockIntegrity, removeBlock, blockLine, blockRect, blockInReach, blockIs, placeBlocks
|
||||||
|
|
||||||
@@ -343,7 +343,7 @@ Drawing (src/render/): browser only. This file: the renderer state R, colour tab
|
|||||||
Propagated coloured light from glowing and burning cells.
|
Propagated coloured light from glowing and burning cells.
|
||||||
- functions: computeLight
|
- functions: computeLight
|
||||||
|
|
||||||
### src/render/world.js (304 lines)
|
### src/render/world.js (309 lines)
|
||||||
Scale colour without changing the smooth-look class.
|
Scale colour without changing the smooth-look class.
|
||||||
- functions: fogColor, drawWorld
|
- functions: fogColor, drawWorld
|
||||||
- variables: TUBE_CLASS
|
- variables: TUBE_CLASS
|
||||||
@@ -399,7 +399,7 @@ The notebook (N): milestones, market, wallet, blueprints, controls.
|
|||||||
The tool hotbar and what is selected.
|
The tool hotbar and what is selected.
|
||||||
- functions: renderHotbar, toolId, steering, modShift, grainAt, selectTool, bagMaterials, blowList, blowAllList, blowingAll, piece
|
- functions: renderHotbar, toolId, steering, modShift, grainAt, selectTool, bagMaterials, blowList, blowAllList, blowingAll, piece
|
||||||
|
|
||||||
### src/ui/build-menu.js (102 lines)
|
### src/ui/build-menu.js (104 lines)
|
||||||
The build menu along the bottom: categories, piece tiles and their icons.
|
The build menu along the bottom: categories, piece tiles and their icons.
|
||||||
- functions: pieceIcon, catOf, selectBuildCat, cycleBuildCat, buildVariantChips, renderBuildBar, setupBuildBar
|
- functions: pieceIcon, catOf, selectBuildCat, cycleBuildCat, buildVariantChips, renderBuildBar, setupBuildBar
|
||||||
- variables: BUILD_CATS, BUILD_ORDER, PIECE_ICONS, lastBuildKey, lastBuildMenu
|
- variables: BUILD_CATS, BUILD_ORDER, PIECE_ICONS, lastBuildKey, lastBuildMenu
|
||||||
@@ -409,7 +409,7 @@ The selected tool and piece: R / Q / E, drill width, filter picker, the selectio
|
|||||||
- functions: pieceUsesWall, setDrillWidth, tuneUnderCursor, cycleItem, cycleWall, rotate, toggleBlow, pickFilterUnderCursor, toggleFilterMat, setBrush, pieceV, pieceExtra, filterChoices, matList, filterParts, filterSentence, applyFilterRun, openFilterPicker, openWallPicker, filterBlockAt, chipCost, pieceCostNow, renderSelCard, renderBag
|
- functions: pieceUsesWall, setDrillWidth, tuneUnderCursor, cycleItem, cycleWall, rotate, toggleBlow, pickFilterUnderCursor, toggleFilterMat, setBrush, pieceV, pieceExtra, filterChoices, matList, filterParts, filterSentence, applyFilterRun, openFilterPicker, openWallPicker, filterBlockAt, chipCost, pieceCostNow, renderSelCard, renderBag
|
||||||
- variables: lastBagKey
|
- variables: lastBagKey
|
||||||
|
|
||||||
### src/ui/info.js (171 lines)
|
### src/ui/info.js (175 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
|
- functions: showCard, howText, discoveryCard, reactionsOf, hoverInfo, renderInfo, drawCursorLabel
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,19 @@ 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-24: t4: Grinder and Saw hopper blocks (Claude Code, Codex)
|
||||||
|
|
||||||
|
- New block pieces `grinder`/`saw` (`BP_GRIND`/`BP_SAW`, materials `GRINDER`/`SAWB`, NMAT 97).
|
||||||
|
`procBlocksTick` (`src/sim/processors.js`) works the row above each stack top and passes the
|
||||||
|
result down the stack (`procFeed`), reusing `REFINE` and `CUT_PER_BRICK`; per-block state in
|
||||||
|
`procState`, rebuilt on place/remove/load. Refiner and Stone cutter are `hidden`; their ticks are
|
||||||
|
unchanged. Texture in `render/world.js`, hover text in `info.js`, Machines tab, notebook.
|
||||||
|
- Codex hit its output limit before finishing the tests; I fixed the test setup (a leftover brick
|
||||||
|
blocked the outlet), kept the old machine icons, stopped "blocked" flickering on idle ticks,
|
||||||
|
and added the play.py Machines-tab and drag-a-row checks.
|
||||||
|
- Tests: t_stone all pass, play.py 243 pass; t_smooth's half-resolution check fails inside
|
||||||
|
run_all at load ~9 and passes alone (known flake). Screenshots `shots/t4-hoppers{,-smooth}.png`.
|
||||||
|
|
||||||
## 2026-10-24: t25: air streaks for the Pneumatic nozzle (Claude Code, Codex)
|
## 2026-10-24: t25: air streaks for the Pneumatic nozzle (Claude Code, Codex)
|
||||||
|
|
||||||
- `drawAirStreaks` in `src/ui/previews.js`: 12 teal streaks spiral into the ring while sucking, 10
|
- `drawAirStreaks` in `src/ui/previews.js`: 12 teal streaks spiral into the ring while sucking, 10
|
||||||
|
|||||||
@@ -115,6 +115,11 @@ function drawWorld() {
|
|||||||
}
|
}
|
||||||
else if (m === GATE) { if ((x & 1) === 0 && (y & 3) !== 0) { r -= 40; g -= 24; b -= 20; } }
|
else if (m === GATE) { if ((x & 1) === 0 && (y & 3) !== 0) { r -= 40; g -= 24; b -= 20; } }
|
||||||
else if (m === CUTST) { const lx = x & 3, ly = y & 3; if (ly === 1 || ly === 3 || lx === (ly < 2 ? 3 : 1)) { r -= 34; g -= 33; b -= 30; } else if (ly === 0 || ly === 2) { r += 10; g += 10; b += 9; } }
|
else if (m === CUTST) { const lx = x & 3, ly = y & 3; if (ly === 1 || ly === 3 || lx === (ly < 2 ? 3 : 1)) { r -= 34; g -= 33; b -= 30; } else if (ly === 0 || ly === 2) { r += 10; g += 10; b += 9; } }
|
||||||
|
else if (m === GRINDER || m === SAWB) {
|
||||||
|
const s = procState.get((y >> 2) * BW + (x >> 2)), step = s && tick - s.busyT < 30 ? anim >> 2 : 0, lx = x & 3, ly = y & 3;
|
||||||
|
const tooth = m === GRINDER ? (ly === 1 || ly === 2) && ((lx + ly + step) & 1) : ly === 1 || ly === 2 && ((lx + step) & 1);
|
||||||
|
r = tooth ? 166 : 46; g = tooth ? 174 : 52; b = tooth ? 186 : 62;
|
||||||
|
}
|
||||||
else if (m === WOOD) { if ((y & 3) === 2 || ((x + (y >> 2) * 3) & 7) === 0) { r -= 26; g -= 22; b -= 16; } } // planks
|
else if (m === WOOD) { if ((y & 3) === 2 || ((x + (y >> 2) * 3) & 7) === 0) { r -= 26; g -= 22; b -= 16; } } // planks
|
||||||
else if (m === LWOOD) { if (((x * 3 + y) & 3) === 0) { r -= 30; g -= 26; b -= 18; } } // stringy fibres
|
else if (m === LWOOD) { if (((x * 3 + y) & 3) === 0) { r -= 30; g -= 26; b -= 18; } } // stringy fibres
|
||||||
else if (m === HULL) { if (((x * 7 + y * 3) & 15) === 0) { r += 20; g += 20; b += 22; } }
|
else if (m === HULL) { if (((x * 7 + y * 3) & 15) === 0) { r += 20; g += 20; b += 22; } }
|
||||||
|
|||||||
+6
-2
@@ -26,6 +26,7 @@ function placeBlock(code, v, bx, by, free, extra) {
|
|||||||
if (code === BP_WALL) setCell(i, WALL_OF[v], AMB[y], 0);
|
if (code === BP_WALL) setCell(i, WALL_OF[v], AMB[y], 0);
|
||||||
else if (code === BP_BELT) { setCell(i, v === 0 ? BELTL : BELTR, AMB[y], 0); markPow(i, true); }
|
else if (code === BP_BELT) { setCell(i, v === 0 ? BELTL : BELTR, AMB[y], 0); markPow(i, true); }
|
||||||
else if (code === BP_SHAFT) { setCell(i, SHAFT, AMB[y], 0); markPow(i, true); }
|
else if (code === BP_SHAFT) { setCell(i, SHAFT, AMB[y], 0); markPow(i, true); }
|
||||||
|
else if (isProcCode(code)) setCell(i, code === BP_GRIND ? GRINDER : SAWB, AMB[y], 0);
|
||||||
else if (code === BP_GATE) setCell(i, GATE, AMB[y], 0);
|
else if (code === BP_GATE) setCell(i, GATE, AMB[y], 0);
|
||||||
else if (code === BP_GRATE) { part[i] = P_GRATE; wakeXY(x, y); }
|
else if (code === BP_GRATE) { part[i] = P_GRATE; wakeXY(x, y); }
|
||||||
else if (code === BP_TUBE) { part[i] = [P_TU, P_TR, P_TD, P_TL][v & 3]; markPow(i, true); wakeXY(x, y); }
|
else if (code === BP_TUBE) { part[i] = [P_TU, P_TR, P_TD, P_TL][v & 3]; markPow(i, true); wakeXY(x, y); }
|
||||||
@@ -36,6 +37,7 @@ function placeBlock(code, v, bx, by, free, extra) {
|
|||||||
if (isPipeCode(code)) pipesDirty = true;
|
if (isPipeCode(code)) pipesDirty = true;
|
||||||
blockP[by * BW + bx] = code; blockV[by * BW + bx] = v;
|
blockP[by * BW + bx] = code; blockV[by * BW + bx] = v;
|
||||||
if (code === BP_FILTER) { blockV[by * BW + bx] = v & 128; filterCfg.set(by * BW + bx, filterRec(extra)); filtersDirty = true; }
|
if (code === BP_FILTER) { blockV[by * BW + bx] = v & 128; filterCfg.set(by * BW + bx, filterRec(extra)); filtersDirty = true; }
|
||||||
|
if (isProcCode(code)) procDirty = true;
|
||||||
stats.placed++;
|
stats.placed++;
|
||||||
undoNote({ t: 'block+', bx, by, code });
|
undoNote({ t: 'block+', bx, by, code });
|
||||||
return '';
|
return '';
|
||||||
@@ -51,6 +53,7 @@ function blockIntegrity(b) {
|
|||||||
if (code === BP_WALL) { if (PH[m] === PH_SOLID && !(flags[i] & F_NAT) && !PARTMAT[m]) n++; }
|
if (code === BP_WALL) { if (PH[m] === PH_SOLID && !(flags[i] & F_NAT) && !PARTMAT[m]) n++; }
|
||||||
else if (code === BP_BELT) { if (m === BELTL || m === BELTR) n++; }
|
else if (code === BP_BELT) { if (m === BELTL || m === BELTR) n++; }
|
||||||
else if (code === BP_SHAFT) { if (m === SHAFT) n++; }
|
else if (code === BP_SHAFT) { if (m === SHAFT) n++; }
|
||||||
|
else if (isProcCode(code)) { if (m === (code === BP_GRIND ? GRINDER : SAWB)) n++; }
|
||||||
else if (code === BP_GATE) { if (m === GATE || p === P_GOPEN) n++; }
|
else if (code === BP_GATE) { if (m === GATE || p === P_GOPEN) n++; }
|
||||||
else if (code === BP_GRATE) { if (p === P_GRATE) n++; }
|
else if (code === BP_GRATE) { if (p === P_GRATE) n++; }
|
||||||
else if (code === BP_TUBE) { if (p >= P_TU && p <= P_TR) n++; }
|
else if (code === BP_TUBE) { if (p >= P_TU && p <= P_TR) n++; }
|
||||||
@@ -79,14 +82,15 @@ function removeBlock(bx, by) {
|
|||||||
} else if (isPipeCode(code)) {
|
} else if (isPipeCode(code)) {
|
||||||
if (p === P_PIPE) { part[i] = 0; wakeIdx(i); n++; }
|
if (p === P_PIPE) { part[i] = 0; wakeIdx(i); n++; }
|
||||||
else if (m === PIPE) { setCell(i, EMPTY, temp[i], 0); n++; }
|
else if (m === PIPE) { setCell(i, EMPTY, temp[i], 0); n++; }
|
||||||
} else if (code === BP_BELT || code === BP_SHAFT || code === BP_GATE) {
|
} else if (code === BP_BELT || code === BP_SHAFT || code === BP_GATE || isProcCode(code)) {
|
||||||
if (m === BELTL || m === BELTR || m === SHAFT || m === GATE) { setCell(i, EMPTY, temp[i], 0); markPow(i, false); n++; }
|
if (m === BELTL || m === BELTR || m === SHAFT || m === GATE || m === GRINDER || m === SAWB) { setCell(i, EMPTY, temp[i], 0); markPow(i, false); n++; }
|
||||||
if (p === P_GOPEN) { part[i] = 0; n++; wakeIdx(i); }
|
if (p === P_GOPEN) { part[i] = 0; n++; wakeIdx(i); }
|
||||||
} else if (p) { part[i] = 0; markPow(i, false); wakeIdx(i); n++; }
|
} else if (p) { part[i] = 0; markPow(i, false); wakeIdx(i); n++; }
|
||||||
}
|
}
|
||||||
if (code === BP_WALL && metal) giveOrDrop(BAR_OF[WALL_OF[v]] || BARCU, Math.floor(metal / GRAINS_PER_BAR), bx * BS + 2, by * BS + 2);
|
if (code === BP_WALL && metal) giveOrDrop(BAR_OF[WALL_OF[v]] || BARCU, Math.floor(metal / GRAINS_PER_BAR), bx * BS + 2, by * BS + 2);
|
||||||
else if (code !== BP_WALL && n >= 8) refundAt(PIECE_BY_CODE[code].cost, 1, bx * BS + 2, by * BS + 2);
|
else if (code !== BP_WALL && n >= 8) refundAt(PIECE_BY_CODE[code].cost, 1, bx * BS + 2, by * BS + 2);
|
||||||
if (code === BP_FILTER) { filtersDirty = true; filterCfg.delete(b); }
|
if (code === BP_FILTER) { filtersDirty = true; filterCfg.delete(b); }
|
||||||
|
if (isProcCode(code)) { procDirty = true; procState.delete(b); }
|
||||||
blockP[b] = 0; blockV[b] = 0;
|
blockP[b] = 0; blockV[b] = 0;
|
||||||
if (isPipeCode(code)) {
|
if (isPipeCode(code)) {
|
||||||
pipesDirty = true;
|
pipesDirty = true;
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ function fanBlow(mm, sat) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function machinesTick() {
|
function machinesTick() {
|
||||||
|
procBlocksTick();
|
||||||
if (powerDirty && tick >= nextPowerBuild) { rebuildPower(); nextPowerBuild = tick + 6; }
|
if (powerDirty && tick >= nextPowerBuild) { rebuildPower(); nextPowerBuild = tick + 6; }
|
||||||
if (brokenMachines.length) {
|
if (brokenMachines.length) {
|
||||||
const ids = brokenMachines; brokenMachines = [];
|
const ids = brokenMachines; brokenMachines = [];
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ const EMPTY = 0, GRASS = 1, DIRT = 2, LDIRT = 3, SAND = 4, GRAVEL = 5, CLAY = 6,
|
|||||||
ZNV = 63, ZNORE = 64, ZINC = 65, MZN = 66, SCRZN = 67, BARZN = 68,
|
ZNV = 63, ZNORE = 64, ZINC = 65, MZN = 66, SCRZN = 67, BARZN = 68,
|
||||||
AGV = 69, AGORE = 70, SILVER = 71, MAG = 72, SCRAG = 73, BARAG = 74,
|
AGV = 69, AGORE = 70, SILVER = 71, MAG = 72, SCRAG = 73, BARAG = 74,
|
||||||
AUV = 75, AUORE = 76, GOLD = 77, MAU = 78, SCRAU = 79, BARAU = 80,
|
AUV = 75, AUORE = 76, GOLD = 77, MAU = 78, SCRAU = 79, BARAU = 80,
|
||||||
BRONZE = 81, MBZ = 82, SCRBZ = 83, BARBZ = 84, BRASS = 85, MBS = 86, SCRBS = 87, BARBS = 88, DOCKW = 89, MSLAG = 90, PIPE = 91, BARGL = 92, CUTST = 93, BARSB = 94;
|
BRONZE = 81, MBZ = 82, SCRBZ = 83, BARBZ = 84, BRASS = 85, MBS = 86, SCRBS = 87, BARBS = 88, DOCKW = 89, MSLAG = 90, PIPE = 91, BARGL = 92, CUTST = 93, BARSB = 94, GRINDER = 95, SAWB = 96;
|
||||||
const NMAT = 95;
|
const NMAT = 97;
|
||||||
|
|
||||||
const MD = [];
|
const MD = [];
|
||||||
function def(id, o) {
|
function def(id, o) {
|
||||||
@@ -62,6 +62,8 @@ def(BELTL, { name: 'Belt', col: [66, 68, 74], v: 4, k: 0.03, hard: 2, melt: 1085
|
|||||||
def(BELTR, { name: 'Belt', col: [66, 68, 74], v: 4, k: 0.03, hard: 2, melt: 1085, meltTo: MCU, partMat: true });
|
def(BELTR, { name: 'Belt', col: [66, 68, 74], v: 4, k: 0.03, hard: 2, melt: 1085, meltTo: MCU, partMat: true });
|
||||||
def(SHAFT, { name: 'Shaft', col: [150, 110, 64], v: 6, k: 0.005, hard: 2, ign: 340, flame: 800, flameF: 1000, fuel: 140, burnTo: ASH, ashP: 0.3, partMat: true });
|
def(SHAFT, { name: 'Shaft', col: [150, 110, 64], v: 6, k: 0.005, hard: 2, ign: 340, flame: 800, flameF: 1000, fuel: 140, burnTo: ASH, ashP: 0.3, partMat: true });
|
||||||
def(GATE, { name: 'Gate', col: [146, 74, 58], v: 6, k: 0.004, hard: 3, melt: 1750, meltTo: MAGMA, partMat: true });
|
def(GATE, { name: 'Gate', col: [146, 74, 58], v: 6, k: 0.004, hard: 3, melt: 1750, meltTo: MAGMA, partMat: true });
|
||||||
|
def(GRINDER, { name: 'Grinder', col: [64, 70, 80], v: 4, k: 0.035, hard: 6, melt: 1450, meltTo: MST, partMat: true });
|
||||||
|
def(SAWB, { name: 'Saw', col: [74, 80, 90], v: 4, k: 0.035, hard: 6, melt: 1450, meltTo: MST, partMat: true });
|
||||||
def(SPRING, { name: 'Spring', col: [92, 112, 128], v: 30, k: 0.012, hard: 9, yld: 0, desc: 'Groundwater seeps out of this rock whenever there is room next to it.' });
|
def(SPRING, { name: 'Spring', col: [92, 112, 128], v: 30, k: 0.012, hard: 9, yld: 0, desc: 'Groundwater seeps out of this rock whenever there is room next to it.' });
|
||||||
def(RSTONE, { name: 'Stone rubble', ph: PH_POWDER, col: [126, 125, 130], v: 30, dens: 1.8, k: 0.01, hard: 0, melt: 1250, meltTo: MAGMA, bag: true, desc: 'Broken stone. 16 grains make a stone block.' });
|
def(RSTONE, { name: 'Stone rubble', ph: PH_POWDER, col: [126, 125, 130], v: 30, dens: 1.8, k: 0.01, hard: 0, melt: 1250, meltTo: MAGMA, bag: true, desc: 'Broken stone. 16 grains make a stone block.' });
|
||||||
def(RGRAN, { name: 'Granite rubble', ph: PH_POWDER, col: [164, 128, 124], v: 32, dens: 1.9, k: 0.01, hard: 0, melt: 1260, meltTo: MAGMA, bag: true, desc: 'Broken granite. Makes granite blocks.' });
|
def(RGRAN, { name: 'Granite rubble', ph: PH_POWDER, col: [164, 128, 124], v: 32, dens: 1.9, k: 0.01, hard: 0, melt: 1260, meltTo: MAGMA, bag: true, desc: 'Broken granite. Makes granite blocks.' });
|
||||||
|
|||||||
+7
-3
@@ -4,7 +4,7 @@
|
|||||||
// Everything you build snaps to a grid of 4x4-grain blocks. The world itself
|
// Everything you build snaps to a grid of 4x4-grain blocks. The world itself
|
||||||
// stays grain-level; only built pieces snap.
|
// stays grain-level; only built pieces snap.
|
||||||
const BS = 4;
|
const BS = 4;
|
||||||
const BP_WALL = 1, BP_BELT = 2, BP_TUBE = 3, BP_SHAFT = 4, BP_GRATE = 5, BP_GATE = 6, BP_FILTER = 7, BP_PIPE = 8, BP_PUMP = 9, BP_OUTLET = 10;
|
const BP_WALL = 1, BP_BELT = 2, BP_TUBE = 3, BP_SHAFT = 4, BP_GRATE = 5, BP_GATE = 6, BP_FILTER = 7, BP_PIPE = 8, BP_PUMP = 9, BP_OUTLET = 10, BP_GRIND = 11, BP_SAW = 12;
|
||||||
function isPipeCode(c) { return c === BP_PIPE || c === BP_PUMP || c === BP_OUTLET; }
|
function isPipeCode(c) { return c === BP_PIPE || c === BP_PUMP || c === BP_OUTLET; }
|
||||||
// Filters: each filter block has a set of materials and a mode. In drop mode (inv 0)
|
// Filters: each filter block has a set of materials and a mode. In drop mode (inv 0)
|
||||||
// the chosen materials fall through and everything else rides along on top; in carry
|
// the chosen materials fall through and everything else rides along on top; in carry
|
||||||
@@ -57,15 +57,19 @@ const PIECES = [
|
|||||||
desc: 'Draws liquid in at the tail of its arrow (keep that side under water) and pushes it into the pipes on its other sides, about 60 grains a second while the line has room. It takes water and anything else cooler than 500°C, one liquid at a time per line. Press F to switch it on or off.' },
|
desc: 'Draws liquid in at the tail of its arrow (keep that side under water) and pushes it into the pipes on its other sides, about 60 grains a second while the line has room. It takes water and anything else cooler than 500°C, one liquid at a time per line. Press F to switch it on or off.' },
|
||||||
{ id: 'outlet', code: BP_OUTLET, name: 'Outlet', kind: 'block', cost: [[CASH, 15]], dirs: ['down', 'left', 'up', 'right'],
|
{ id: 'outlet', code: BP_OUTLET, name: 'Outlet', kind: 'block', cost: [[CASH, 15]], dirs: ['down', 'left', 'up', 'right'],
|
||||||
desc: 'Lets a pipe line\'s liquid out of the side its arrow points to, wherever there is room for it. Press F to open or close it.' },
|
desc: 'Lets a pipe line\'s liquid out of the side its arrow points to, wherever there is room for it. Press F to open or close it.' },
|
||||||
|
{ id: 'grinder', code: BP_GRIND, name: 'Grinder', kind: 'block', cost: [[CASH, 60]],
|
||||||
|
desc: 'Grinds rubble, gravel, sand and dirt resting on top into sand and the odd grain of ore. Results drop out below; keep that space clear. Wider rows grind faster. Stack under a Saw to cut stone and grind the rest.' },
|
||||||
|
{ id: 'saw', code: BP_SAW, name: 'Saw', kind: 'block', cost: [[CASH, 90]],
|
||||||
|
desc: 'Cuts stone rubble resting on top into a brick for every 8 grains. Results drop out below; keep that space clear. Wider rows cut faster. Stack over a Grinder to grind everything else.' },
|
||||||
{ id: 'wheel', name: 'Water wheel', kind: 'machine', bw: 3, bh: 3, cost: [[LWOOD, 48]], hidden: POWER_FREE,
|
{ id: 'wheel', name: 'Water wheel', kind: 'machine', bw: 3, bh: 3, cost: [[LWOOD, 48]], hidden: POWER_FREE,
|
||||||
desc: 'Makes power when liquid falls through it. More flow, more power.' },
|
desc: 'Makes power when liquid falls through it. More flow, more power.' },
|
||||||
{ id: 'fan', name: 'Fan', kind: 'machine', bw: 1, bh: 1, cost: [[CASH, 60]], dirs: ['right', 'down', 'left', 'up'],
|
{ id: 'fan', name: 'Fan', kind: 'machine', bw: 1, bh: 1, cost: [[CASH, 60]], dirs: ['right', 'down', 'left', 'up'],
|
||||||
desc: 'Blows gas and forces air into fires, making them far hotter: its stream reaches 28 grains and passes through grates but not walls. Its copper body melts at 1085°C, so keep it outside the furnace, a block clear of the wall, blowing in through a grate. Press F to switch it on or off.' },
|
desc: 'Blows gas and forces air into fires, making them far hotter: its stream reaches 28 grains and passes through grates but not walls. Its copper body melts at 1085°C, so keep it outside the furnace, a block clear of the wall, blowing in through a grate. Press F to switch it on or off.' },
|
||||||
{ id: 'cutter', name: 'Stone cutter', kind: 'machine', bw: 2, bh: 2, cost: [[CASH, 90]],
|
{ id: 'cutter', hidden: true, name: 'Stone cutter', kind: 'machine', bw: 2, bh: 2, cost: [[CASH, 90]],
|
||||||
desc: 'A saw under a hopper. Stone rubble dropped in is cut into stone bricks, one from every 8 grains, which drop out underneath. Builders buy stone bricks, and they make neat cut stone walls that stand more heat than rough stone. Anything else falls straight through.' },
|
desc: 'A saw under a hopper. Stone rubble dropped in is cut into stone bricks, one from every 8 grains, which drop out underneath. Builders buy stone bricks, and they make neat cut stone walls that stand more heat than rough stone. Anything else falls straight through.' },
|
||||||
{ id: 'silo', name: 'Silo', kind: 'machine', bw: 3, bh: 6, cost: [[CASH, 180]],
|
{ id: 'silo', name: 'Silo', kind: 'machine', bw: 3, bh: 6, cost: [[CASH, 180]],
|
||||||
desc: 'A steel bin on legs for one loose material: up to 4,000 grains go in through the hatch in its roof (drop them in from a belt or tube). Its gauge shows how full it is. Press F to open the gate in its cone and let it out underneath, about 60 grains a second; F again shuts it. The first material in decides what it holds until it is empty.' },
|
desc: 'A steel bin on legs for one loose material: up to 4,000 grains go in through the hatch in its roof (drop them in from a belt or tube). Its gauge shows how full it is. Press F to open the gate in its cone and let it out underneath, about 60 grains a second; F again shuts it. The first material in decides what it holds until it is empty.' },
|
||||||
{ id: 'refiner', name: 'Refiner', kind: 'machine', bw: 3, bh: 3, cost: [[CASH, 120]],
|
{ id: 'refiner', hidden: true, name: 'Refiner', kind: 'machine', bw: 3, bh: 3, cost: [[CASH, 120]],
|
||||||
desc: 'A crushing sieve. Drop stone, granite or basalt rubble, gravel, sand or dirt into its hopper: it grinds each grain, keeps the odd grain of ore it finds (clay, from dirt) and lets about half as much fine sand out of the chute underneath. Ore and anything else it cannot grind falls straight through. Keep the space under the chute clear, or a belt running under it.' },
|
desc: 'A crushing sieve. Drop stone, granite or basalt rubble, gravel, sand or dirt into its hopper: it grinds each grain, keeps the odd grain of ore it finds (clay, from dirt) and lets about half as much fine sand out of the chute underneath. Ore and anything else it cannot grind falls straight through. Keep the space under the chute clear, or a belt running under it.' },
|
||||||
{ id: 'engine', name: 'Steam engine', kind: 'machine', bw: 3, bh: 2, cost: [[BARCU, 10]], dirs: ['exhaust right', 'exhaust left'], hidden: POWER_FREE,
|
{ id: 'engine', name: 'Steam engine', kind: 'machine', bw: 3, bh: 2, cost: [[BARCU, 10]], dirs: ['exhaust right', 'exhaust left'], hidden: POWER_FREE,
|
||||||
desc: 'Steam rising into the intake slots on its underside drives it. Condensate drips out the exhaust side.' },
|
desc: 'Steam rising into the intake slots on its underside drives it. Condensate drips out the exhaust side.' },
|
||||||
|
|||||||
@@ -74,6 +74,72 @@ function cutterTick(mm) {
|
|||||||
jobs--; mm.busyT = tick;
|
jobs--; mm.busyT = tick;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Block hoppers share an outlet down a vertical stack; only its top spends work.
|
||||||
|
let procState = new Map(), procBlocks = [], procDirty = true;
|
||||||
|
function isProcCode(c) { return c === BP_GRIND || c === BP_SAW; }
|
||||||
|
function rebuildProcessors() {
|
||||||
|
procBlocks = [];
|
||||||
|
for (let b = 0; b < NB; b++) if (isProcCode(blockP[b])) {
|
||||||
|
procBlocks.push(b);
|
||||||
|
if (!procState.has(b)) procState.set(b, { work: 0, cut: 0, busyT: -999, blocked: false });
|
||||||
|
}
|
||||||
|
procDirty = false;
|
||||||
|
}
|
||||||
|
function procBottom(b) { while (b + BW < NB && isProcCode(blockP[b + BW])) b += BW; return b; }
|
||||||
|
function procSlot(b) {
|
||||||
|
const y = (((b / BW) | 0) + 1) * BS, x = (b % BW) * BS;
|
||||||
|
if (y >= H) return -1;
|
||||||
|
for (let k = 0; k < 4; k++) { const j = y * W + x + [1, 2, 0, 3][k]; if (mat[j] === EMPTY && !part[j] && !occ[j] && !mach[j]) return j; }
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
function procFeed(b, m, T) {
|
||||||
|
const end = procBottom(b), x = (b % BW) * BS, y = ((b / BW) | 0) * BS - 1;
|
||||||
|
for (;;) {
|
||||||
|
const s = procState.get(b); s.busyT = tick;
|
||||||
|
if (blockP[b] === BP_GRIND && PH[m] === PH_POWDER && REFINE[m]) {
|
||||||
|
const from = m; let got = 0;
|
||||||
|
for (const [chance, o] of REFINE[m]) if (rnd() < chance) { got = o; break; }
|
||||||
|
if (got) { stats.refinedOre = (stats.refinedOre || 0) + 1; discover(got, x, y, from, 'refine'); }
|
||||||
|
else if (rnd() < REFINE_SAND) got = SAND;
|
||||||
|
stats.refined = (stats.refined || 0) + 1; m = got;
|
||||||
|
if (!m) return;
|
||||||
|
} else if (blockP[b] === BP_SAW && m === RSTONE) {
|
||||||
|
if (++s.cut < CUT_PER_BRICK) return;
|
||||||
|
s.cut = 0; m = BARSB;
|
||||||
|
}
|
||||||
|
if (b === end) break;
|
||||||
|
b += BW;
|
||||||
|
}
|
||||||
|
if (m === BARSB) {
|
||||||
|
const e = spawnBar(BARSB, x, (((end / BW) | 0) + 1) * BS, (rnd() - 0.5) * 0.1, 0.05);
|
||||||
|
if (e) { e.T = T; discover(BARSB, x, y, RSTONE, 'cut'); stats.bricks = (stats.bricks || 0) + 1; }
|
||||||
|
} else setCell(procSlot(end), m, T, 0);
|
||||||
|
}
|
||||||
|
function procBlocksTick() {
|
||||||
|
if (procDirty) rebuildProcessors();
|
||||||
|
for (const b of procBlocks) {
|
||||||
|
if (b >= BW && isProcCode(blockP[b - BW])) continue;
|
||||||
|
const s = procState.get(b), end = procBottom(b), y = ((b / BW) | 0) * BS - 1, x = (b % BW) * BS;
|
||||||
|
s.work += blockP[b] === BP_GRIND ? 0.25 : 0.5;
|
||||||
|
let jobs = Math.floor(s.work); s.work -= jobs;
|
||||||
|
if (!jobs || y < 0) continue; // a tick without work keeps the last blocked state, so it doesn't flicker
|
||||||
|
for (let q = b; q <= end; q += BW) procState.get(q).blocked = false;
|
||||||
|
for (let k = 0; k < 4 && jobs > 0; k++) {
|
||||||
|
const i = y * W + x + ((k + tick) & 3), m = mat[i];
|
||||||
|
if (m === EMPTY || PH[m] === PH_GAS || PH[m] === PH_SOLID || stamp[i] === tickStamp) continue;
|
||||||
|
let brick = false;
|
||||||
|
if (m === RSTONE) for (let q = b; q <= end; q += BW) {
|
||||||
|
if (blockP[q] === BP_GRIND) break;
|
||||||
|
if (blockP[q] === BP_SAW) { brick = procState.get(q).cut + 1 >= CUT_PER_BRICK; break; }
|
||||||
|
}
|
||||||
|
if (procSlot(end) < 0 || brick && !entFreeAt(x, (((end / BW) | 0) + 1) * BS)) {
|
||||||
|
for (let q = b; q <= end; q += BW) procState.get(q).blocked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const T = temp[i]; setCell(i, EMPTY, AMB[y], 0); procFeed(b, m, T); jobs--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// ---------- Silo ----------
|
// ---------- Silo ----------
|
||||||
// One loose material, counted (not simulated grain by grain inside): grains in the roof hatch
|
// One loose material, counted (not simulated grain by grain inside): grains in the roof hatch
|
||||||
// go in, and with the gate open they come out of the bottom of the cone.
|
// go in, and with the gate open they come out of the bottom of the cone.
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ function deserializeWorld(meta, buffers) {
|
|||||||
computeAmbient(); fillNoise();
|
computeAmbient(); fillNoise();
|
||||||
mat.set(buffers.mat); life.set(buffers.life); flags.set(buffers.flags);
|
mat.set(buffers.mat); life.set(buffers.life); flags.set(buffers.flags);
|
||||||
for (let i = 0; i < N; i++) if (mat[i] === SPRING) mat[i] = STONE; // springs are gone: older worlds keep plain rock there
|
for (let i = 0; i < N; i++) if (mat[i] === SPRING) mat[i] = STONE; // springs are gone: older worlds keep plain rock there
|
||||||
blockP.set(buffers.blockP); blockV.set(buffers.blockV); filtersDirty = true;
|
blockP.set(buffers.blockP); blockV.set(buffers.blockV); filtersDirty = true; rebuildProcessors();
|
||||||
// pipes used to be solid copper blocks; now they are thin and let things past
|
// pipes used to be solid copper blocks; now they are thin and let things past
|
||||||
for (let b = 0; b < NB; b++) if (blockP[b] === BP_PIPE) for (const i of blockCellList(b % BW, (b / BW) | 0)) if (mat[i] === PIPE) { mat[i] = EMPTY; part[i] = P_PIPE; }
|
for (let b = 0; b < NB; b++) if (blockP[b] === BP_PIPE) for (const i of blockCellList(b % BW, (b / BW) | 0)) if (mat[i] === PIPE) { mat[i] = EMPTY; part[i] = P_PIPE; }
|
||||||
for (const [b, m, inv] of meta.filters || []) if (blockP[b] === BP_FILTER) filterCfg.set(b, filterRec({ m, inv }));
|
for (const [b, m, inv] of meta.filters || []) if (blockP[b] === BP_FILTER) filterCfg.set(b, filterRec({ m, inv }));
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ function alloc(w, h) {
|
|||||||
mat = new Uint8Array(N); temp = new Float32Array(N); life = new Uint8Array(N); flags = new Uint8Array(N);
|
mat = new Uint8Array(N); temp = new Float32Array(N); life = new Uint8Array(N); flags = new Uint8Array(N);
|
||||||
stamp = new Uint8Array(N); part = new Uint8Array(N); mach = new Uint16Array(N); digp = new Uint8Array(N);
|
stamp = new Uint8Array(N); part = new Uint8Array(N); mach = new Uint16Array(N); digp = new Uint8Array(N);
|
||||||
noiseB = new Uint8Array(N); bgm = new Uint8Array(N); airS = new Uint16Array(N); netId = new Uint16Array(N); isPow = new Uint8Array(N); occ = new Uint8Array(N);
|
noiseB = new Uint8Array(N); bgm = new Uint8Array(N); airS = new Uint16Array(N); netId = new Uint16Array(N); isPow = new Uint8Array(N); occ = new Uint8Array(N);
|
||||||
BW = Math.ceil(w / BS); BH = Math.ceil(h / BS); NB = BW * BH; blockP = new Uint8Array(NB); blockV = new Uint8Array(NB); filtersDirty = true; filterCfg = new Map();
|
BW = Math.ceil(w / BS); BH = Math.ceil(h / BS); NB = BW * BH; blockP = new Uint8Array(NB); blockV = new Uint8Array(NB); filtersDirty = true; filterCfg = new Map(); procDirty = true; procState.clear();
|
||||||
ents = []; flyers = []; ghostBlocks = new Map(); ghostMolds = []; treeBases = []; saplings = [];
|
ents = []; flyers = []; ghostBlocks = new Map(); ghostMolds = []; treeBases = []; saplings = [];
|
||||||
surfBase = Math.round(Math.min(h * 0.27, 175)); underD = h - surfBase; shallowD = 0; lapse = 0.36;
|
surfBase = Math.round(Math.min(h * 0.27, 175)); underD = h - surfBase; shallowD = 0; lapse = 0.36;
|
||||||
surfY = new Int16Array(w); roofY = new Int16Array(w); dirtY = new Int16Array(w); graniteY = new Int16Array(w); basaltY = new Int16Array(w); magmaY = new Int16Array(w);
|
surfY = new Int16Array(w); roofY = new Int16Array(w); dirtY = new Int16Array(w); graniteY = new Int16Array(w); basaltY = new Int16Array(w); magmaY = new Int16Array(w);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const BUILD_CATS = [
|
|||||||
['Structure', ['wall', 'grate', 'gate']],
|
['Structure', ['wall', 'grate', 'gate']],
|
||||||
['Moving', ['belt', 'filter', 'tube']],
|
['Moving', ['belt', 'filter', 'tube']],
|
||||||
['Liquids', ['pipe', 'pump', 'outlet']],
|
['Liquids', ['pipe', 'pump', 'outlet']],
|
||||||
['Machines', ['fan', 'refiner', 'cutter', 'silo']],
|
['Machines', ['fan', 'grinder', 'saw', 'silo']],
|
||||||
['Molds', ['ingotmold', 'bitmold', 'tankmold']],
|
['Molds', ['ingotmold', 'bitmold', 'tankmold']],
|
||||||
];
|
];
|
||||||
const BUILD_ORDER = BUILD_CATS.flatMap(c => c[1]);
|
const BUILD_ORDER = BUILD_CATS.flatMap(c => c[1]);
|
||||||
@@ -21,6 +21,8 @@ const PIECE_ICONS = {
|
|||||||
fan: '<circle cx="12" cy="12" r="1.8"/><path d="M12 10.2c-.4-3.6 1.8-6 4-4.8 2 1.2.4 3.8-4 4.8zM13.8 12c3.6-.4 6 1.8 4.8 4-1.2 2-3.8.4-4.8-4zM12 13.8c.4 3.6-1.8 6-4 4.8-2-1.2-.4-3.8 4-4.8zM10.2 12c-3.6.4-6-1.8-4.8-4 1.2-2 3.8-.4 4.8 4z"/>',
|
fan: '<circle cx="12" cy="12" r="1.8"/><path d="M12 10.2c-.4-3.6 1.8-6 4-4.8 2 1.2.4 3.8-4 4.8zM13.8 12c3.6-.4 6 1.8 4.8 4-1.2 2-3.8.4-4.8-4zM12 13.8c.4 3.6-1.8 6-4 4.8-2-1.2-.4-3.8 4-4.8zM10.2 12c-3.6.4-6-1.8-4.8-4 1.2-2 3.8-.4 4.8 4z"/>',
|
||||||
refiner: '<path d="M3 4h18l-5 7H8z"/><circle cx="9" cy="15.5" r="2.8"/><circle cx="15" cy="15.5" r="2.8"/><path d="M12 20v2"/>',
|
refiner: '<path d="M3 4h18l-5 7H8z"/><circle cx="9" cy="15.5" r="2.8"/><circle cx="15" cy="15.5" r="2.8"/><path d="M12 20v2"/>',
|
||||||
cutter: '<path d="M5 3h14l-3 5H8z"/><circle cx="12" cy="15" r="5"/><circle cx="12" cy="15" r="1.2"/><path d="M12 9.2v1.6M17.8 15h-1.6M12 20.8v-1.6M6.2 15h1.6"/>',
|
cutter: '<path d="M5 3h14l-3 5H8z"/><circle cx="12" cy="15" r="5"/><circle cx="12" cy="15" r="1.2"/><path d="M12 9.2v1.6M17.8 15h-1.6M12 20.8v-1.6M6.2 15h1.6"/>',
|
||||||
|
grinder: '<path d="M3 4h18l-5 7H8z"/><circle cx="9" cy="15.5" r="2.8"/><circle cx="15" cy="15.5" r="2.8"/><path d="M12 20v2"/>',
|
||||||
|
saw: '<path d="M5 3h14l-3 5H8z"/><circle cx="12" cy="15" r="5"/><circle cx="12" cy="15" r="1.2"/><path d="M12 9.2v1.6M17.8 15h-1.6M12 20.8v-1.6M6.2 15h1.6"/>',
|
||||||
silo: '<path d="M6 5.5h12v11H6z"/><path d="M6 5.5l1-2h10l1 2M6 16.5l4 3h4l4-3M7 16.5v5M17 16.5v5"/><path d="M15.5 8v6"/>',
|
silo: '<path d="M6 5.5h12v11H6z"/><path d="M6 5.5l1-2h10l1 2M6 16.5l4 3h4l4-3M7 16.5v5M17 16.5v5"/><path d="M15.5 8v6"/>',
|
||||||
ingotmold: '<path d="M3 9h18l-2 7H5z"/><path d="M9.2 9l.8 7M14.8 9l-.8 7"/>',
|
ingotmold: '<path d="M3 9h18l-2 7H5z"/><path d="M9.2 9l.8 7M14.8 9l-.8 7"/>',
|
||||||
bitmold: '<path d="M3 9h10l7 3-7 3H3z"/><path d="M6.5 9v6M9.5 9v6"/>',
|
bitmold: '<path d="M3 9h10l7 3-7 3H3z"/><path d="M6.5 9v6M9.5 9v6"/>',
|
||||||
|
|||||||
+6
-2
@@ -31,8 +31,8 @@ function howText(e) {
|
|||||||
case 'blow': return 'Air blown through molten iron burned off its extra carbon.';
|
case 'blow': return 'Air blown through molten iron burned off its extra carbon.';
|
||||||
case 'alloy': return `${F} melted into molten copper and the two mixed.`;
|
case 'alloy': return `${F} melted into molten copper and the two mixed.`;
|
||||||
case 'char': return `${F} got hot with no air to burn in, and charred.`;
|
case 'char': return `${F} got hot with no air to burn in, and charred.`;
|
||||||
case 'refine': return `Sifted out of ${f} by a Refiner.`;
|
case 'refine': return `Ground out of ${f}.`;
|
||||||
case 'cut': return `Cut from ${f} by a Stone cutter.`;
|
case 'cut': return `Cut from ${f} by a saw.`;
|
||||||
default: return '';
|
default: return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,6 +93,10 @@ function hoverInfo(ax, ay) {
|
|||||||
if (mm.type === 'silo') t2.push(mm.sn ? `holds ${mm.sn.toLocaleString('en-US')} / ${SILO_CAP.toLocaleString('en-US')} ${MD[mm.sm].name.toLowerCase()}` : `empty (holds ${SILO_CAP.toLocaleString('en-US')} grains of one material)`, mm.open ? 'gate open · F shuts it' : 'gate shut · F opens it');
|
if (mm.type === 'silo') t2.push(mm.sn ? `holds ${mm.sn.toLocaleString('en-US')} / ${SILO_CAP.toLocaleString('en-US')} ${MD[mm.sm].name.toLowerCase()}` : `empty (holds ${SILO_CAP.toLocaleString('en-US')} grains of one material)`, mm.open ? 'gate open · F shuts it' : 'gate shut · F opens it');
|
||||||
if (mm.type === 'cutter') t2.push(mm.blocked ? 'stopped: clear the space under it' : tick - (mm.busyT || -999) < 30 ? 'cutting' : 'idle: drop stone rubble in', `${(stats.bricks || 0).toLocaleString('en-US')} bricks cut`);
|
if (mm.type === 'cutter') t2.push(mm.blocked ? 'stopped: clear the space under it' : tick - (mm.busyT || -999) < 30 ? 'cutting' : 'idle: drop stone rubble in', `${(stats.bricks || 0).toLocaleString('en-US')} bricks cut`);
|
||||||
if (mm.type === 'refiner') t2.push(mm.blocked ? 'stopped: clear the space under its chute' : tick - (mm.busyT || -999) < 30 ? 'grinding' : 'idle: drop rubble, gravel, sand or dirt in', `${(stats.refined || 0).toLocaleString('en-US')} ground, ${(stats.refinedOre || 0).toLocaleString('en-US')} ore found`);
|
if (mm.type === 'refiner') t2.push(mm.blocked ? 'stopped: clear the space under its chute' : tick - (mm.busyT || -999) < 30 ? 'grinding' : 'idle: drop rubble, gravel, sand or dirt in', `${(stats.refined || 0).toLocaleString('en-US')} ground, ${(stats.refinedOre || 0).toLocaleString('en-US')} ore found`);
|
||||||
|
} else if (isProcCode(b)) {
|
||||||
|
const s = procState.get((ay >> 2) * BW + (ax >> 2)), saw = b === BP_SAW;
|
||||||
|
t1 = saw ? 'Saw' : 'Grinder';
|
||||||
|
t2.push(s && s.blocked ? 'stopped: clear the space under it' : s && tick - s.busyT < 30 ? saw ? 'cutting' : 'grinding' : saw ? 'idle: drop stone rubble on top' : 'idle: drop rubble, gravel, sand or dirt on top', saw ? `${(stats.bricks || 0).toLocaleString('en-US')} bricks cut` : `${(stats.refined || 0).toLocaleString('en-US')} ground, ${(stats.refinedOre || 0).toLocaleString('en-US')} ore found`);
|
||||||
} else if (p === P_FILTER) {
|
} else if (p === P_FILTER) {
|
||||||
const fb = ((ay / BS) | 0) * BW + ((ax / BS) | 0), pr = filterParts(filterInfo(fb)), ar = blockV[fb] & 128 ? '→' : '←';
|
const fb = ((ay / BS) | 0) * BW + ((ax / BS) | 0), pr = filterParts(filterInfo(fb)), ar = blockV[fb] & 128 ? '→' : '←';
|
||||||
t1 = 'Filter ' + ar; t2.push(`drops ${pr.drops}`, `carries ${pr.rides} ${ar}`, 'F to change · I shows all');
|
t1 = 'Filter ' + ar; t2.push(`drops ${pr.drops}`, `carries ${pr.rides} ${ar}`, 'F to change · I shows all');
|
||||||
|
|||||||
+1
-1
@@ -182,7 +182,7 @@ function controlsHtml() {
|
|||||||
const tools = [
|
const tools = [
|
||||||
['1 Resonance drill', 'Hold left click to send pulses from the bot toward the cursor: rock they shake hard enough shatters and falls as rubble, ore, coal or scrap, and trees come apart into wood chunks and acorns. Q and E set the pulse width: narrow bores a tunnel fast, wide clears a big cone slowly, medium is in between. R over a material tunes the drill to it, so only that shatters (great for pulling a vein out of the rock); R again untunes. Built blocks are never harmed. Better bits dig harder rock, faster and further.'],
|
['1 Resonance drill', 'Hold left click to send pulses from the bot toward the cursor: rock they shake hard enough shatters and falls as rubble, ore, coal or scrap, and trees come apart into wood chunks and acorns. Q and E set the pulse width: narrow bores a tunnel fast, wide clears a big cone slowly, medium is in between. R over a material tunes the drill to it, so only that shatters (great for pulling a vein out of the rock); R again untunes. Built blocks are never harmed. Better bits dig harder rock, faster and further.'],
|
||||||
['2 Pneumatic nozzle', "Left drag picks up the selection, or what you click if nothing is selected. Right click blows the selection. Click a bag name or Q/E selects one; Ctrl+click mixes. Hold Ctrl to see everything found. Clear removes the selection. Bars blow alone. Blowing out the last of a material deselects it. Shift while not flying takes everything; Shift + right click blows everything loose in the bag."],
|
['2 Pneumatic nozzle', "Left drag picks up the selection, or what you click if nothing is selected. Right click blows the selection. Click a bag name or Q/E selects one; Ctrl+click mixes. Hold Ctrl to see everything found. Clear removes the selection. Bars blow alone. Blowing out the last of a material deselects it. Shift while not flying takes everything; Shift + right click blows everything loose in the bag."],
|
||||||
['3 Build', 'Places pieces on a grid of 4×4 blocks: wall blocks, belts, tubes, filters, grates, gates, pipes, pumps and outlets, machines (fans, the Refiner) and molds. Drag for a line, hold Shift while dragging for a filled rectangle. Right click takes a piece back. Your bot flies through anything you build.'],
|
['3 Build', 'Places pieces on a grid of 4×4 blocks: wall blocks, belts, tubes, filters, grates, gates, pipes, pumps, outlets, Grinders and Saws, machines (fans and silos) and molds. Drag for a line, hold Shift while dragging for a filled rectangle. Right click takes a piece back. Your bot flies through anything you build.'],
|
||||||
['4 Igniter', 'Heats a small spot to 640°C, and its flame reaches a few grains into a pile. Enough to light coal and wood that touch air.'],
|
['4 Igniter', 'Heats a small spot to 640°C, and its flame reaches a few grains into a pile. Enough to light coal and wood that touch air.'],
|
||||||
['Copy, cut and paste', 'Hold C (copy) or X (cut) and drag a box over what you built; let go and it is picked up, ready to paste. Cut takes it down and refunds it to your bag. Click to place ghosts: your bot builds them from your bag when it is close. R flips, V pastes again, right click or Esc stops. Ctrl+Z undoes building. The notebook (B) keeps saved blueprints and a few starter designs.'],
|
['Copy, cut and paste', 'Hold C (copy) or X (cut) and drag a box over what you built; let go and it is picked up, ready to paste. Cut takes it down and refunds it to your bag. Click to place ghosts: your bot builds them from your bag when it is close. R flips, V pastes again, right click or Esc stops. Ctrl+Z undoes building. The notebook (B) keeps saved blueprints and a few starter designs.'],
|
||||||
];
|
];
|
||||||
|
|||||||
+11
-1
@@ -681,7 +681,7 @@ with sync_playwright() as p:
|
|||||||
ev(f'bot.x = {x_before}; bot.y = {y_before}; R.snapNext = true;'); settle(200)
|
ev(f'bot.x = {x_before}; bot.y = {y_before}; R.snapNext = true;'); settle(200)
|
||||||
# pipes are laid by dragging, like belts
|
# pipes are laid by dragging, like belts
|
||||||
ok = select('3', "piece().id === 'pipe'")
|
ok = select('3', "piece().id === 'pipe'")
|
||||||
check('pipes, pumps, outlets and the refiner are in the build menu', ok and all(x in ev("PIECES.filter(p => !p.hidden).map(p => p.id)") for x in ['pipe', 'pump', 'outlet', 'refiner']))
|
check('pipes, pumps, outlets and hoppers are in the build menu', ok and all(x in ev("PIECES.filter(p => !p.hidden).map(p => p.id)") for x in ['pipe', 'pump', 'outlet', 'grinder', 'saw']))
|
||||||
ev(f"for (let y = {(bby+2)*4}; y < {(bby+3)*4}; y++) for (let x = {(bbx-8)*4}; x < {(bbx+4)*4}; x++) if (!blockP[((y/4)|0)*BW + ((x/4)|0)]) setCell(y*W+x, EMPTY, 20, 0);")
|
ev(f"for (let y = {(bby+2)*4}; y < {(bby+3)*4}; y++) for (let x = {(bbx-8)*4}; x < {(bbx+4)*4}; x++) if (!blockP[((y/4)|0)*BW + ((x/4)|0)]) setCell(y*W+x, EMPTY, 20, 0);")
|
||||||
drag(lambda: blk(bbx - 6, bby + 2), lambda: blk(bbx - 3, bby + 2))
|
drag(lambda: blk(bbx - 6, bby + 2), lambda: blk(bbx - 3, bby + 2))
|
||||||
check('dragging lays a line of pipe that joins into one line', ev(f'(() => {{ let n = 0; for (let bx = {bbx} - 6; bx <= {bbx} - 3; bx++) if (blockP[({bby} + 2) * BW + bx] === BP_PIPE) n++; return n; }})()') == 4 and ev(f'pipeNetOf(({bby} + 2) * BW + {bbx} - 6).blocks.length') == 4, ev('GAME.status'))
|
check('dragging lays a line of pipe that joins into one line', ev(f'(() => {{ let n = 0; for (let bx = {bbx} - 6; bx <= {bbx} - 3; bx++) if (blockP[({bby} + 2) * BW + bx] === BP_PIPE) n++; return n; }})()') == 4 and ev(f'pipeNetOf(({bby} + 2) * BW + {bbx} - 6).blocks.length') == 4, ev('GAME.status'))
|
||||||
@@ -690,6 +690,16 @@ with sync_playwright() as p:
|
|||||||
pg.keyboard.press('Control+z'); settle(200)
|
pg.keyboard.press('Control+z'); settle(200)
|
||||||
check('and Ctrl+Z swaps the belt back', ev(f'blockP[({bby} + 1) * BW + {bbx} - 4]') == ev('BP_BELT') and not ev(f"machines.some(m => m && m.type === 'fan' && m.x === ({bbx} - 4) * 4)"), ev('GAME.status'))
|
check('and Ctrl+Z swaps the belt back', ev(f'blockP[({bby} + 1) * BW + {bbx} - 4]') == ev('BP_BELT') and not ev(f"machines.some(m => m && m.type === 'fan' && m.x === ({bbx} - 4) * 4)"), ev('GAME.status'))
|
||||||
|
|
||||||
|
# 7a. hopper blocks: the Machines tab has Grinder and Saw instead of the Refiner and Stone cutter; drag a row
|
||||||
|
check('the Machines tab has Grinder and Saw but not the Refiner or Stone cutter', ev("(() => { const ids = BUILD_CATS.find(c => c[0] === 'Machines')[1]; return ids.includes('grinder') && ids.includes('saw') && !ids.includes('refiner') && !ids.includes('cutter'); })()"))
|
||||||
|
ev('money = 1000')
|
||||||
|
ok = select('3', "piece().id === 'grinder'")
|
||||||
|
ev(f"for (let y = {(bby+3)*4}; y < {(bby+4)*4}; y++) for (let x = {(bbx-8)*4}; x < {(bbx+4)*4}; x++) if (!blockP[((y/4)|0)*BW + ((x/4)|0)]) setCell(y*W+x, EMPTY, 20, 0);")
|
||||||
|
drag(lambda: blk(bbx - 6, bby + 3), lambda: blk(bbx - 4, bby + 3))
|
||||||
|
check('dragging a row of Grinders places three blocks', ok and ev(f'(() => {{ let n = 0; for (let bx = {bbx} - 6; bx <= {bbx} - 4; bx++) if (blockP[({bby} + 3) * BW + bx] === BP_GRIND) n++; return n; }})()') == 3, ev('GAME.status'))
|
||||||
|
pg.keyboard.press('Control+z'); settle(200)
|
||||||
|
check('Ctrl+Z takes the Grinder row back', ev(f'(() => {{ for (let bx = {bbx} - 6; bx <= {bbx} - 4; bx++) if (blockP[({bby} + 3) * BW + bx]) return false; return true; }})()'))
|
||||||
|
|
||||||
# 7b. filter: choose several materials with F, place a line, replace belts with G, edit the whole line
|
# 7b. filter: choose several materials with F, place a line, replace belts with G, edit the whole line
|
||||||
ev('seen[CUORE] = 1; seen[FEORE] = 1; seen[SAND] = 1; money = 1000; GAME.filterCfg = { m: [], inv: 0 };')
|
ev('seen[CUORE] = 1; seen[FEORE] = 1; seen[SAND] = 1; money = 1000; GAME.filterCfg = { m: [], inv: 0 };')
|
||||||
ok = select('3', "piece().id === 'filter'")
|
ok = select('3', "piece().id === 'filter'")
|
||||||
|
|||||||
@@ -65,6 +65,41 @@ check('F shuts it again', count(run, run('RSTONE'), 100, 27, 120, 99) === out1);
|
|||||||
run('(() => { const s = serializeWorld(); deserializeWorld(JSON.parse(JSON.stringify(s.meta)), s.buffers); })()');
|
run('(() => { const s = serializeWorld(); deserializeWorld(JSON.parse(JSON.stringify(s.meta)), s.buffers); })()');
|
||||||
check('what is in a silo is saved', run(`${silo}.sn`) === held - out1 && run(`${silo}.sm`) === run('RSTONE'));
|
check('what is in a silo is saved', run(`${silo}.sn`) === held - out1 && run(`${silo}.sm`) === run('RSTONE'));
|
||||||
|
|
||||||
|
// ---- block hoppers: feed the top edge, clear the outlet between jobs
|
||||||
|
function hopper(code, stack = false) {
|
||||||
|
flatWorld(run, 240, 160, 120);
|
||||||
|
run(`money = 9999; stats.refined = 0; stats.bricks = 0; placeBlock(${code}, 0, 20, 18, true); ${stack ? 'placeBlock(BP_GRIND, 0, 20, 19, true);' : ''}`);
|
||||||
|
}
|
||||||
|
function feedHopper(m, n, bottom = 76) {
|
||||||
|
run(`for (const e of ents.slice()) removeEnt(e);
|
||||||
|
for (let k = 0; k < ${n}; k++) {
|
||||||
|
for (let y = ${bottom}; y < ${bottom}+2; y++) for (let x = 80; x < 84; x++) setCell(y*W+x, EMPTY, 20, 0);
|
||||||
|
setCell(71*W+80, ${m}, 60, 0); stamp[71*W+80] = 0;
|
||||||
|
for (let t = 0; t < 4; t++) { tick++; tickStamp++; procBlocksTick(); }
|
||||||
|
}`);
|
||||||
|
}
|
||||||
|
hopper('BP_SAW'); feedHopper('RSTONE', 8);
|
||||||
|
check('a Saw cuts eight stone grains into one brick below', run('stats.bricks === 1 && ents[0].iy === 76'));
|
||||||
|
feedHopper('CUORE', 1);
|
||||||
|
check('a Saw passes other grains through at their temperature', run('mat[76*W+81] === CUORE && temp[76*W+81] === 60'));
|
||||||
|
hopper('BP_SAW');
|
||||||
|
run('for (let x = 80; x < 84; x++) setCell(76*W+x, STONE, 20, 0); setCell(71*W+80, RSTONE, 20, 0); stamp[71*W+80] = 0; for (let k = 0; k < 8; k++) { tick++; tickStamp++; procBlocksTick(); }');
|
||||||
|
check('a full outlet stops a Saw before consuming stone', run('mat[71*W+80] === RSTONE && procState.get(18*BW+20).blocked'));
|
||||||
|
hopper('BP_GRIND'); feedHopper('RSTONE', 200);
|
||||||
|
check('a Grinder counts every ground grain and reduces rubble', run('stats.refined === 200') && count(run, run('RSTONE')) === 0);
|
||||||
|
hopper('BP_GRIND');
|
||||||
|
run('placeBlock(BP_GRIND, 0, 21, 18, true); for (let k = 0; k < 80; k++) { for (let x = 80; x < 88; x++) { setCell(71*W+x, GRAVEL, 20, 0); stamp[71*W+x] = 0; setCell(76*W+x, EMPTY, 20, 0); } tick++; tickStamp++; procBlocksTick(); }');
|
||||||
|
check('two Grinders work twice one block rate', run('stats.refined === 40'), run('stats.refined'));
|
||||||
|
hopper('BP_SAW', true); feedHopper('RSTONE', 8, 80);
|
||||||
|
check('a Saw over a Grinder drops its brick under the stack', run('stats.bricks === 1 && ents[0].iy === 80'));
|
||||||
|
feedHopper('GRAVEL', 40, 80);
|
||||||
|
check('gravel passes through the Saw and is ground below', run('stats.refined === 40'));
|
||||||
|
run('removeBlock(20, 18); removeBlock(20, 19);');
|
||||||
|
check('removing hoppers refunds costs, clears cells and state', run('money === 10149 && mat[72*W+80] === EMPTY && mat[76*W+80] === EMPTY && procState.size === 0'));
|
||||||
|
hopper('BP_SAW'); feedHopper('RSTONE', 3);
|
||||||
|
run('(() => { const s = serializeWorld(); deserializeWorld(s.meta, s.buffers); })(); procBlocksTick();');
|
||||||
|
check('loading rebuilds processors and drops partial brick state', run('procBlocks.length === 1 && procState.get(18*BW+20).cut === 0 && SAVE_VERSION === 2'));
|
||||||
|
|
||||||
// ---- builders' orders: bulk rubble, gravel and stone bricks at a few times a tiny price
|
// ---- builders' orders: bulk rubble, gravel and stone bricks at a few times a tiny price
|
||||||
flatWorld(run, 240, 160, 100);
|
flatWorld(run, 240, 160, 100);
|
||||||
run('seen[RSTONE] = 1; seen[GRAVEL] = 1; seen[BARSB] = 1; orders = []; ordersDone = 0;');
|
run('seen[RSTONE] = 1; seen[GRAVEL] = 1; seen[BARSB] = 1; orders = []; ordersDone = 0;');
|
||||||
|
|||||||
Reference in New Issue
Block a user