diff --git a/docs/DESIGN.md b/docs/DESIGN.md index d5591b2..2e43a65 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -225,6 +225,18 @@ above, including: 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. + After the owner played it: the Refiner and Stone cutter code is gone (an old save that still has + one loads without it and refunds its price). Grinders draw as a pair of grinding wheels and + Saws as a round blade, turning while busy. The Saw also cuts wood chunks into Planks (2 build a + wood wall). Its stone output is now called a Stone block, so that only fired clay is a brick. + Items piling up under a block stay a thing to build around (a belt or an open drop), and the + Saw's text says so. + +- **Clay bricks come from a Brick press (t28, 2026-10-24).** A hopper block like the Saw: every + 8 clay lumps on top become a Green brick below, and a Green brick fires into a Brick above about + 900°C (any fire). 2 Bricks build a brick wall block; builders order them. The owner found it odd + that clay bricks (first planned as a mold) and stone bricks would be made in different ways, and + chose a press over a heated kiln block or a mold. Loose clay still fires into brick rubble. - **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 first click; R over a grain toggles its pick-up mark. The owner found the shared ticks confusing. diff --git a/docs/PROJECT_MAP.md b/docs/PROJECT_MAP.md index 5048437..fa11110 100644 --- a/docs/PROJECT_MAP.md +++ b/docs/PROJECT_MAP.md @@ -196,15 +196,15 @@ Deepgrain Foundry: simulation core (src/sim/). No DOM anywhere in src/sim/: plai - functions: rnd, mulberry32, hash2 - variables: RS, PH_EMPTY, PH_SOLID, PH_POWDER, PH_LIQUID, PH_GAS, PH_ITEM -### src/sim/materials.js (162 lines) +### src/sim/materials.js (167 lines) Material ids, the material table (def), per-material property arrays, cell flags and part ids, and drill/tank tiers. - 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, FIRETO, 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 (141 lines) 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 -- 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 +- 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, BP_PRESS, 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) Money, prices, market saturation, the trade dock, buyer and builder orders. @@ -241,7 +241,7 @@ Machines: the power network, footprints, placing and removing machines. - functions: markPow, partLost, rebuildPower, netSat, evictCells, cellBuildable, machineFootprint, canPlaceMachine, addMachine, removeMachine - variables: nextPowerBuild -### src/sim/processors.js (108 lines) +### src/sim/processors.js (113 lines) Processing: Grinder and Saw blocks, and the Silo. - functions: isProcCode, rebuildProcessors, procBottom, procSlot, procFeed, procBlocksTick, siloTick, siloFull - variables: REFINE, REFINE_SAND, CUT_PER_BRICK, SAW_CUTS, procState, procBlocks, procDirty, SILO_CAP, SILO_RATE @@ -269,7 +269,7 @@ Molds: cavities for castings; ingot troughs empty themselves below. Removal retu - functions: moldCellsAt, canPlaceMold, newMoldRecord, addMold, castMix, cavityState, troughState, clearCavity, moldPlugCells, rakeAbove, furnaceJunkHint, unplugMold, ejectIngot, checkMolds, moldAt, tally, collectMold, removeMold - variables: DROSS, grains, barWord -### src/sim/items.js (200 lines) +### src/sim/items.js (203 lines) Bars and other loose objects (ents), and the bag: adding, taking, costs and refunds. - functions: entSolidCell, entFreeAt, entMark, spawnBar, removeEnt, entTubeAt, entTubeFree, entTubeMove, entTubeEnter, entTubeTick, entTick, entsInCircle, bagAdd, bagTake, giveOrDrop, refundAt, canAfford, payCost, fmtMoney, costText, needText - variables: BAR_W, BAR_H, entCurM, droppedLast @@ -348,7 +348,7 @@ Scale colour without changing the smooth-look class. - functions: fogColor, drawWorld - variables: TUBE_CLASS -### src/render/machines.js (207 lines) +### src/render/machines.js (212 lines) Machine overlays: fans, engines, silo and the rest, drawn on top of the world. - functions: cellRect, drawHazardRims, drawMachines, drawProcBlocks, drawSilo, drawSiloPixel @@ -399,7 +399,7 @@ The notebook (N): milestones, market, wallet, blueprints, controls. The tool hotbar and what is selected. - functions: renderHotbar, toolId, steering, modShift, grainAt, selectTool, bagMaterials, blowList, blowAllList, blowingAll, piece -### src/ui/build-menu.js (102 lines) +### src/ui/build-menu.js (103 lines) The build menu along the bottom: categories, piece tiles and their icons. - functions: pieceIcon, catOf, selectBuildCat, cycleBuildCat, buildVariantChips, renderBuildBar, setupBuildBar - 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 - variables: lastBagKey -### src/ui/info.js (173 lines) +### src/ui/info.js (174 lines) Discovery cards, hover info (I) and the cursor label. - functions: showCard, howText, discoveryCard, reactionsOf, hoverInfo, renderInfo, drawCursorLabel diff --git a/docs/agent-log.md b/docs/agent-log.md index 4fa3b8d..69adf22 100644 --- a/docs/agent-log.md +++ b/docs/agent-log.md @@ -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 open or not verified. Keep each entry short; the git history has the details. +## 2026-10-24: t4 follow-ups and t28 Brick press (Claude Code, Codex) + +- `9739ca0`: Refiner and Stone cutter code removed; `save.js` skips unknown machine types on load + and refunds a refiner ($120) or cutter ($90). Their old t_pipes/t_stone checks now use blocks. +- `f5e5591`: `drawProcBlocks` (`render/machines.js`) draws Grinder wheels and the Saw blade over a + plain housing texture; turns while busy, no `rnd()`. +- `08d2d65`: `PLANK` item; the Saw cuts `LWOOD` too (`SAW_CUTS`, per-material `cut` counts). + "Stone brick" is "Stone block" in player text. Saw text explains the pile-up under it. +- t28: `BP_PRESS`/`PRESSB`, `GREENB` and `BARBR` items, `FIRETO` (items.js fires a hot green + brick into a Brick). Machines tab, hover, notebook, milestone hint. +- Tests: `run_all.py --browser` passed after each commit (t_smooth's flake rerun alone). + Screenshots `shots/t4-wheels{,-smooth}.png`, `shots/t28-press.png`. + ## 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). diff --git a/src/render/machines.js b/src/render/machines.js index 14a49ee..a0fd3af 100644 --- a/src/render/machines.js +++ b/src/render/machines.js @@ -90,7 +90,12 @@ function drawProcBlocks() { const radius = sw * (saw ? 0.38 : 0.22), teeth = saw ? 10 : 6; ctx.save(); ctx.lineWidth = Math.max(1, sw * 0.025); - for (let wheel = 0; wheel < (saw ? 1 : 2); wheel++) { + if (blockP[b] === BP_PRESS) { + const offset = busy ? (1 - Math.cos(tick * 0.3)) * sw * 0.12 : 0; + ctx.fillStyle = '#caa488'; ctx.fillRect(sx + sw * 0.27, sy + sw * 0.76, sw * 0.46, sw * 0.16); + ctx.fillStyle = '#7d8996'; ctx.fillRect(sx + sw * 0.45, sy, sw * 0.1, sw * 0.4 + offset); + ctx.fillStyle = '#b9c0c8'; ctx.fillRect(sx + sw * 0.12, sy + sw * 0.4 + offset, sw * 0.76, sw * 0.12); + } else for (let wheel = 0; wheel < (saw ? 1 : 2); wheel++) { const cx = sx + sw * (saw ? 0.5 : wheel ? 0.73 : 0.27), cy = sy + sw * 0.5; const a = wheel ? -angle : angle; ctx.fillStyle = saw ? '#b9c0c8' : '#8a8f96'; ctx.strokeStyle = '#444b54'; diff --git a/src/render/world.js b/src/render/world.js index ea7a47d..efa1e95 100644 --- a/src/render/world.js +++ b/src/render/world.js @@ -115,7 +115,7 @@ function drawWorld() { } 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 === GRINDER || m === SAWB) { + else if (m === GRINDER || m === SAWB || m === PRESSB) { const lx = x & 3, ly = y & 3, frame = lx === 0 || lx === 3 || ly === 0 || ly === 3; r = frame ? 96 : 38; g = frame ? 102 : 42; b = frame ? 112 : 50; } diff --git a/src/sim/blocks.js b/src/sim/blocks.js index e122521..4689f22 100644 --- a/src/sim/blocks.js +++ b/src/sim/blocks.js @@ -26,7 +26,7 @@ function placeBlock(code, v, bx, by, free, extra) { 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_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 (isProcCode(code)) setCell(i, code === BP_GRIND ? GRINDER : code === BP_PRESS ? PRESSB : SAWB, 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_TUBE) { part[i] = [P_TU, P_TR, P_TD, P_TL][v & 3]; markPow(i, true); wakeXY(x, y); } @@ -53,7 +53,7 @@ function blockIntegrity(b) { 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_SHAFT) { if (m === SHAFT) n++; } - else if (isProcCode(code)) { if (m === (code === BP_GRIND ? GRINDER : SAWB)) n++; } + else if (isProcCode(code)) { if (m === (code === BP_GRIND ? GRINDER : code === BP_PRESS ? PRESSB : SAWB)) 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_TUBE) { if (p >= P_TU && p <= P_TR) n++; } @@ -83,7 +83,7 @@ function removeBlock(bx, by) { if (p === P_PIPE) { part[i] = 0; wakeIdx(i); n++; } else if (m === PIPE) { setCell(i, EMPTY, temp[i], 0); n++; } } else if (code === BP_BELT || code === BP_SHAFT || code === BP_GATE || isProcCode(code)) { - if (m === BELTL || m === BELTR || m === SHAFT || m === GATE || m === GRINDER || m === SAWB) { setCell(i, EMPTY, temp[i], 0); markPow(i, false); n++; } + if (m === BELTL || m === BELTR || m === SHAFT || m === GATE || m === GRINDER || m === SAWB || m === PRESSB) { setCell(i, EMPTY, temp[i], 0); markPow(i, false); n++; } if (p === P_GOPEN) { part[i] = 0; n++; wakeIdx(i); } } else if (p) { part[i] = 0; markPow(i, false); wakeIdx(i); n++; } } diff --git a/src/sim/economy.js b/src/sim/economy.js index 33203ba..5df0809 100644 --- a/src/sim/economy.js +++ b/src/sim/economy.js @@ -10,7 +10,7 @@ const PRICE = new Float32Array(NMAT); for (const [m, v] of [ [SAND, 0.1], [LCLAY, 0.2], [LWOOD, 0.3], [ACORN, 0.5], [COAL, 0.5], [RGRAN, 0.2], [RBAS, 0.4], [CUORE, 1], [FEORE, 1.6], [SNORE, 2], [ZNORE, 2.2], [AGORE, 6], [AUORE, 14], - [CHARCOAL, 1.2], [RBRICK, 0.8], [CULLET, 1.2], [BARGL, 14], [RSTONE, 0.02], [GRAVEL, 0.03], [BARSB, 0.6], [PLANK, 2.4], + [CHARCOAL, 1.2], [RBRICK, 0.8], [CULLET, 1.2], [BARGL, 14], [RSTONE, 0.02], [GRAVEL, 0.03], [BARSB, 0.6], [PLANK, 2.4], [GREENB, 2], [BARBR, 9], [SCRCU, 3], [SCRFE, 5], [SCRST, 11], [SCRSN, 3.5], [SCRZN, 3.5], [SCRAG, 18], [SCRAU, 40], [SCRBZ, 8], [SCRBS, 7], [BARCU, 40], [BARFE, 75], [BARST, 160], [BARSN, 50], [BARZN, 50], [BARAG, 220], [BARAU, 480], [BARBZ, 110], [BARBS, 95], ]) PRICE[m] = v; @@ -73,7 +73,7 @@ const STANDING = ['Local buyers', 'County buyers', 'Regional buyers', 'National function standing() { return Math.min(STANDING.length - 1, Math.floor(ordersDone / 4)); } // Builders buy the cheap bulk stuff (rubble, gravel, sand, stone blocks, planks) in big lots at a few // times the market price, so waste rock earns a little. [what, smallest lot, largest lot] -const BUILD_GOODS = [[RSTONE, 400, 1500], [GRAVEL, 300, 1000], [SAND, 300, 900], [BARSB, 20, 80], [PLANK, 20, 80]]; +const BUILD_GOODS = [[RSTONE, 400, 1500], [GRAVEL, 300, 1000], [SAND, 300, 900], [BARSB, 20, 80], [PLANK, 20, 80], [BARBR, 20, 80]]; function makeBuildersOrder() { const cands = BUILD_GOODS.filter(([m]) => seen[m] && !orders.some(o => o.m === m)); if (!cands.length) return null; diff --git a/src/sim/items.js b/src/sim/items.js index b1ea1dc..c2d8651 100644 --- a/src/sim/items.js +++ b/src/sim/items.js @@ -136,6 +136,9 @@ function entTick() { // heat: the bar tends toward the temperature around it const T = temp[Math.min(H - 1, cy + 1) * W + Math.min(W - 1, cx + 1)]; e.T += (T - e.T) * 0.04; + if (FIRETO[e.m] && e.T >= 880 && rnd() < 0.004 + (e.T - 880) * 0.00012) { + e.m = FIRETO[e.m]; discover(e.m, e.ix, e.iy, GREENB, 'fire', 880); + } if (e.T >= MELT[e.m]) { // melts back into 8 grains of molten metal if they fit here let fits = true; diff --git a/src/sim/ledger.js b/src/sim/ledger.js index 41ea79f..5c07b0d 100644 --- a/src/sim/ledger.js +++ b/src/sim/ledger.js @@ -5,7 +5,7 @@ function checkLedger() { const mark = id => { if (!ledger[id]) { ledger[id] = tick || 1; emit({ t: 'milestone', id }); } }; if (stats.dug >= 15) mark('dig'); if (seen[FIRE]) { mark('fire'); if (!player.lens) { player.lens = true; } } - if (seen[BRICK] || seen[RBRICK]) mark('brick'); + if (seen[BRICK] || seen[RBRICK] || seen[BARBR]) mark('brick'); if (seen[CHARCOAL]) mark('charcoal'); if (stats.grown > 0) mark('grow'); if (seen[MCU]) mark('copper'); diff --git a/src/sim/materials.js b/src/sim/materials.js index 16cb6ad..221c5fb 100644 --- a/src/sim/materials.js +++ b/src/sim/materials.js @@ -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, AGV = 69, AGORE = 70, SILVER = 71, MAG = 72, SCRAG = 73, BARAG = 74, 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, GRINDER = 95, SAWB = 96, PLANK = 97; -const NMAT = 98; + 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, PLANK = 97, PRESSB = 98, GREENB = 99, BARBR = 100; +const NMAT = 101; const MD = []; function def(id, o) { @@ -64,6 +64,7 @@ def(SHAFT, { name: 'Shaft', col: [150, 110, 64], v: 6, k: 0.005, hard: 2, ign: 3 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(PRESSB, { name: 'Brick press', 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(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.' }); @@ -106,6 +107,10 @@ metalFamily({ solid: BRASS, molten: MBS, scrap: SCRBS, bar: BARBS, name: 'Brass' desc: 'Copper with zinc melted in. Golden and valuable.' }); def(BARGL, { name: 'Glass brick', ph: PH_ITEM, col: [176, 218, 228], v: 0, dens: 2.5, k: 0, hard: 0, melt: 1300, meltTo: MGL, bag: true, desc: 'Cast from molten glass in an Ingot mold: 8 grains of glass. Sells well, and 2 make a glass wall block.' }); def(PLANK, { name: 'Plank', ph: PH_ITEM, col: [204, 158, 104], v: 0, dens: 0.6, k: 0, hard: 0, melt: 9999, bag: true, desc: 'Sawn from 8 wood chunks by a Saw. Builders buy them, and 2 make a wood wall block.' }); +def(GREENB, { name: 'Green brick', ph: PH_ITEM, col: [202, 164, 136], v: 0, dens: 1.8, k: 0, hard: 0, melt: 1750, meltTo: MAGMA, bag: true, desc: 'Unfired clay pressed into shape by a Brick press. Fire it above ~900°C to make a Brick.' }); +def(BARBR, { name: 'Brick', ph: PH_ITEM, col: [174, 76, 56], v: 0, dens: 1.8, k: 0, hard: 0, melt: 1750, meltTo: MAGMA, bag: true, desc: 'Fired clay brick. Builders buy them, and 2 make a brick wall block.' }); +// An item that fires into another item when hot. +const FIRETO = new Uint8Array(NMAT); FIRETO[GREENB] = BARBR; def(CUTST, { name: 'Cut stone', col: [168, 162, 150], v: 6, k: 0.012, hard: 4, melt: 1450, meltTo: MAGMA, yld: BARSB, desc: 'Dressed stone blocks laid in courses. Neater than rough stone, and it stands 200°C more heat before it slumps.' }); def(BARSB, { name: 'Stone block', ph: PH_ITEM, col: [172, 166, 154], v: 0, dens: 2.6, k: 0, hard: 0, melt: 1450, meltTo: MAGMA, bag: true, desc: 'A block of stone cut square by a Saw, from 8 grains of rubble. Builders buy them, and 2 make a cut stone wall block.' }); def(PIPE, { name: 'Pipe', col: [176, 112, 72], v: 4, k: 0.04, cap: 1.5, hard: 4, melt: 1085, meltTo: MCU, partMat: true }); diff --git a/src/sim/pieces.js b/src/sim/pieces.js index 3abc5d5..f7acd4c 100644 --- a/src/sim/pieces.js +++ b/src/sim/pieces.js @@ -4,7 +4,7 @@ // Everything you build snaps to a grid of 4x4-grain blocks. The world itself // stays grain-level; only built pieces snap. 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, BP_GRIND = 11, BP_SAW = 12; +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, BP_PRESS = 13; 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) // the chosen materials fall through and everything else rides along on top; in carry @@ -25,8 +25,8 @@ function sameFilter(a, b) { return a.inv === b.inv && a.m.length === b.m.length const WALL_OF = new Uint8Array(NMAT); WALL_OF[LDIRT] = DIRT; WALL_OF[RSTONE] = STONE; WALL_OF[RGRAN] = GRANITE; WALL_OF[RBAS] = BASALT; WALL_OF[LCLAY] = CLAY; WALL_OF[LWOOD] = WOOD; WALL_OF[RBRICK] = BRICK; WALL_OF[CULLET] = GLASS; WALL_OF[BARCU] = COPPER; WALL_OF[BARFE] = IRON; WALL_OF[BARST] = STEEL; -WALL_OF[BARSB] = CUTST; WALL_OF[PLANK] = WOOD; WALL_OF[BARGL] = GLASS; -const WALL_LIST = [RSTONE, BARSB, PLANK, LCLAY, RBRICK, LDIRT, LWOOD, CULLET, BARGL, RGRAN, RBAS, BARCU, BARFE, BARST]; +WALL_OF[BARSB] = CUTST; WALL_OF[PLANK] = WOOD; WALL_OF[BARBR] = BRICK; WALL_OF[BARGL] = GLASS; +const WALL_LIST = [RSTONE, BARSB, PLANK, BARBR, LCLAY, RBRICK, LDIRT, LWOOD, CULLET, BARGL, RGRAN, RBAS, BARCU, BARFE, BARST]; const BAR_OF = new Uint8Array(NMAT); BAR_OF[COPPER] = BARCU; BAR_OF[IRON] = BARFE; BAR_OF[STEEL] = BARST; BAR_OF[GLASS] = BARGL; BAR_OF[CUTST] = BARSB; BAR_OF[WOOD] = PLANK; BAR_OF[TIN] = BARSN; BAR_OF[ZINC] = BARZN; BAR_OF[SILVER] = BARAG; BAR_OF[GOLD] = BARAU; BAR_OF[BRONZE] = BARBZ; BAR_OF[BRASS] = BARBS; const GRAINS_PER_BAR = 8; @@ -59,6 +59,8 @@ const PIECES = [ 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: 'press', code: BP_PRESS, name: 'Brick press', kind: 'block', cost: [[CASH, 60]], + desc: 'Presses clay lumps resting on top into a green brick for every 8. They drop out underneath; put a belt or an open drop under it. Fire green bricks in a fire hotter than about 900°C to make Bricks. Wider rows press faster.' }, { id: 'saw', code: BP_SAW, name: 'Saw', kind: 'block', cost: [[CASH, 90]], desc: 'Cuts stone rubble into stone blocks and wood chunks into planks, one from every 8 grains resting on top. They drop out underneath and pile up there: put a belt or an open drop under it to carry them away. 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, diff --git a/src/sim/processors.js b/src/sim/processors.js index 3a33442..3560b77 100644 --- a/src/sim/processors.js +++ b/src/sim/processors.js @@ -15,12 +15,12 @@ const CUT_PER_BRICK = 8; const SAW_CUTS = []; SAW_CUTS[RSTONE] = BARSB; SAW_CUTS[LWOOD] = PLANK; // 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 isProcCode(c) { return c === BP_GRIND || c === BP_SAW || c === BP_PRESS; } 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: { [RSTONE]: 0, [LWOOD]: 0 }, busyT: -999, blocked: false }); + if (!procState.has(b)) procState.set(b, { work: 0, press: 0, cut: { [RSTONE]: 0, [LWOOD]: 0 }, busyT: -999, blocked: false }); } procDirty = false; } @@ -45,15 +45,19 @@ function procFeed(b, m, T) { } else if (blockP[b] === BP_SAW && SAW_CUTS[m]) { if (++s.cut[m] < CUT_PER_BRICK) return; s.cut[m] = 0; m = SAW_CUTS[m]; + } else if (blockP[b] === BP_PRESS && m === LCLAY) { + if (++s.press < CUT_PER_BRICK) return; + s.press = 0; m = GREENB; } if (b === end) break; b += BW; } - if (m === BARSB || m === PLANK) { + if (m === BARSB || m === PLANK || m === GREENB) { const e = spawnBar(m, x, (((end / BW) | 0) + 1) * BS, (rnd() - 0.5) * 0.1, 0.05); if (e) { - e.T = T; discover(m, x, y, m === PLANK ? LWOOD : RSTONE, 'cut'); - if (m === PLANK) stats.planks = (stats.planks || 0) + 1; + e.T = T; discover(m, x, y, m === GREENB ? LCLAY : m === PLANK ? LWOOD : RSTONE, m === GREENB ? 'press' : 'cut'); + if (m === GREENB) stats.pressed = (stats.pressed || 0) + 1; + else if (m === PLANK) stats.planks = (stats.planks || 0) + 1; else stats.bricks = (stats.bricks || 0) + 1; } } else setCell(procSlot(end), m, T, 0); @@ -71,9 +75,10 @@ function procBlocksTick() { 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 (SAW_CUTS[m]) for (let q = b; q <= end; q += BW) { + if (SAW_CUTS[m] || m === LCLAY) for (let q = b; q <= end; q += BW) { if (blockP[q] === BP_GRIND && REFINE[m]) break; - if (blockP[q] === BP_SAW) { brick = procState.get(q).cut[m] + 1 >= CUT_PER_BRICK; break; } + if (blockP[q] === BP_PRESS && m === LCLAY) { brick = procState.get(q).press + 1 >= CUT_PER_BRICK; break; } + if (blockP[q] === BP_SAW && SAW_CUTS[m]) { brick = procState.get(q).cut[m] + 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; diff --git a/src/sim/progress.js b/src/sim/progress.js index 07d2744..b9e292a 100644 --- a/src/sim/progress.js +++ b/src/sim/progress.js @@ -24,7 +24,7 @@ const STARTER_DESIGNS = [ const MILESTONES = [ { id: 'dig', title: 'Break ground', msg: 'Broken material falls loose. The Pneumatic nozzle (2) picks up and blows the selection. Click or Q/E in the bag selects; Ctrl+click mixes. Hold Ctrl to see everything found. Clear returns to picking what you click. Shift: everything.', hint: 'Hold the left mouse button with the Drill (1): it sends pulses at the cursor that shake rock until it shatters. It falls as rubble, ore and coal.' }, { id: 'fire', title: 'First fire', hint: 'Pick up coal or wood chunks with the nozzle, blow a pile out with right click, then hold the Igniter (4) on it. Fuel only burns where it touches open air.', reward: 'Thermal lens unlocked. Press H to see heat.' }, - { id: 'brick', title: 'Fired brick', hint: 'Clay turns into brick after a while above about 900°C, hotter than an open wood fire gets. Blow clay lumps into a coal fire, best walled in like a furnace, and pick up the brick rubble with the Pneumatic nozzle. Brick rubble builds brick blocks.', reward: 'Brick insulates far better than stone and survives 1750°C.' }, + { id: 'brick', title: 'Fired brick', hint: 'Clay turns into brick after a while above about 900°C, hotter than an open wood fire gets. Blow clay lumps into a coal fire, best walled in like a furnace, and pick up the brick rubble with the Pneumatic nozzle. Brick rubble builds brick blocks. A Brick press makes neat green bricks to fire.', reward: 'Brick insulates far better than stone and survives 1750°C.' }, { id: 'charcoal', title: 'Charcoal', hint: 'Wood that gets hot without air turns into charcoal instead of burning. Bury a burning pile of wood chunks under dirt, or heat a sealed box of wood chunks from outside.', reward: 'A fuel you can grow: nearly as hot as coal.' }, { id: 'grow', title: 'Plant a tree', msg: 'Wood is renewable. Plant rows of acorns on dirt to farm it.', hint: 'Cutting down a tree drops acorns. Leave one on dirt or grass under open sky and it sprouts, then grows into a tree in about a minute and a half.' }, { id: 'copper', title: 'Molten copper', msg: 'Molten copper sinks to the bottom of the charge. Get it into a mold before it sets.', hint: 'Copper ore melts at 1085°C. An open fire loses too much heat, so wall coal and ore in with blocks and leave a small vent at the top for smoke. Mix ore and coal about half and half: Ctrl+click coal and copper ore in the bag to blow a mix. Right click blows an even mix.' }, diff --git a/src/ui/build-menu.js b/src/ui/build-menu.js index e04f8ce..1b20951 100644 --- a/src/ui/build-menu.js +++ b/src/ui/build-menu.js @@ -4,7 +4,7 @@ const BUILD_CATS = [ ['Structure', ['wall', 'grate', 'gate']], ['Moving', ['belt', 'filter', 'tube']], ['Liquids', ['pipe', 'pump', 'outlet']], - ['Machines', ['fan', 'grinder', 'saw', 'silo']], + ['Machines', ['fan', 'grinder', 'saw', 'press', 'silo']], ['Molds', ['ingotmold', 'bitmold', 'tankmold']], ]; const BUILD_ORDER = BUILD_CATS.flatMap(c => c[1]); @@ -20,6 +20,7 @@ const PIECE_ICONS = { outlet: '', fan: '', grinder: '', + press: '', saw: '', silo: '', ingotmold: '', diff --git a/src/ui/info.js b/src/ui/info.js index 993855e..ebb665f 100644 --- a/src/ui/info.js +++ b/src/ui/info.js @@ -32,6 +32,7 @@ function howText(e) { 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 'refine': return `Ground out of ${f}.`; + case 'press': return `Pressed from ${f} by a Brick press.`; case 'cut': return `Cut from ${f} by a saw.`; default: return ''; } @@ -92,9 +93,9 @@ function hoverInfo(ax, ay) { if (mm.type === 'fan') { t2.push(mm.on ? 'on' : 'off · press F'); if (mm.hot > 760) { t2.push(`${mm.hot}°C · melts at 1085°C`); warn = 'too hot'; } } 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'); } 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 ? saw ? 'stopped: no room underneath; a belt there carries them away' : 'stopped: clear the space under it' : s && tick - s.busyT < 30 ? saw ? 'cutting' : 'grinding' : saw ? 'idle: drop stone rubble or wood chunks on top' : 'idle: drop rubble, gravel, sand or dirt on top', saw ? `${(stats.bricks || 0).toLocaleString('en-US')} stone blocks, ${(stats.planks || 0).toLocaleString('en-US')} planks cut` : `${(stats.refined || 0).toLocaleString('en-US')} ground, ${(stats.refinedOre || 0).toLocaleString('en-US')} ore found`); + const s = procState.get((ay >> 2) * BW + (ax >> 2)), saw = b === BP_SAW, press = b === BP_PRESS; + t1 = press ? 'Brick press' : saw ? 'Saw' : 'Grinder'; + t2.push(s && s.blocked ? saw || press ? 'stopped: no room underneath; a belt there carries them away' : 'stopped: clear the space under it' : s && tick - s.busyT < 30 ? press ? 'pressing' : saw ? 'cutting' : 'grinding' : press ? 'idle: drop clay lumps on top' : saw ? 'idle: drop stone rubble or wood chunks on top' : 'idle: drop rubble, gravel, sand or dirt on top', press ? `${stats.pressed || 0} green bricks pressed` : saw ? `${(stats.bricks || 0).toLocaleString('en-US')} stone blocks, ${(stats.planks || 0).toLocaleString('en-US')} planks cut` : `${(stats.refined || 0).toLocaleString('en-US')} ground, ${(stats.refinedOre || 0).toLocaleString('en-US')} ore found`); } else if (p === P_FILTER) { 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'); diff --git a/src/ui/notebook.js b/src/ui/notebook.js index 1bab863..143095b 100644 --- a/src/ui/notebook.js +++ b/src/ui/notebook.js @@ -27,7 +27,7 @@ function renderNotebook() { renderMarketTab(body); } else if (nbTab === 'Materials') { let rows = '', unknown = 0; - const list = [DIRT, LDIRT, STONE, RSTONE, BARSB, PLANK, CLAY, LCLAY, WOOD, LWOOD, ACORN, CHARCOAL, SNOW, ICE, COALV, COAL, CUV, CUORE, FEV, FEORE, SAND, GRAVEL, GRANITE, RGRAN, BASALT, RBAS, + const list = [DIRT, LDIRT, STONE, RSTONE, BARSB, PLANK, GREENB, BARBR, CLAY, LCLAY, WOOD, LWOOD, ACORN, CHARCOAL, SNOW, ICE, COALV, COAL, CUV, CUORE, FEV, FEORE, SAND, GRAVEL, GRANITE, RGRAN, BASALT, RBAS, BRICK, RBRICK, GLASS, CULLET, COPPER, SCRCU, BARCU, IRON, SCRFE, BARFE, STEEL, SCRST, BARST, ASH, SLAG, MSLAG, WATER, STEAM, MAGMA, MCU, MFE, MST, MGL, FIRE, SMOKE, SNV, SNORE, TIN, MSN, SCRSN, BARSN, ZNV, ZNORE, ZINC, MZN, SCRZN, BARZN, AGV, AGORE, SILVER, MAG, SCRAG, BARAG, AUV, AUORE, GOLD, MAU, SCRAU, BARAU, BRONZE, MBZ, SCRBZ, BARBZ, BRASS, MBS, SCRBS, BARBS]; for (const m of list) { if (!seen[m]) { unknown++; continue; } @@ -58,7 +58,7 @@ function orderName(o) { return `${o.who ? o.who + ': ' : ''}${o.qty.toLocaleStri function orderLeft(o) { return Math.max(0, o.due - tick); } const MARKET_GROUPS = [ ['Raw', [COAL, LWOOD, ACORN, SAND, LCLAY, RGRAN, RBAS, CUORE, FEORE, SNORE, ZNORE]], - ['Bulk (builders)', [RSTONE, GRAVEL, BARSB, PLANK]], + ['Bulk (builders)', [RSTONE, GRAVEL, BARSB, PLANK, GREENB, BARBR]], ['Refined', [CHARCOAL, RBRICK, CULLET, BARGL, SCRCU, SCRFE, SCRST, SCRSN, SCRZN, BARCU, BARSN, BARZN, BARFE, BARST]], ['Alloys', [SCRBZ, SCRBS, BARBZ, BARBS]], ['Precious', [AGORE, AUORE, SCRAG, SCRAU, BARAG, BARAU]], @@ -182,7 +182,7 @@ function controlsHtml() { 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.'], ['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, 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.'], + ['3 Build', 'Places pieces on a grid of 4×4 blocks: wall blocks, belts, tubes, filters, grates, gates, pipes, pumps, outlets, Grinders, Saws and Brick presses, 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.'], ['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.'], ]; diff --git a/src/ui/state.js b/src/ui/state.js index fbe57e2..9e858cf 100644 --- a/src/ui/state.js +++ b/src/ui/state.js @@ -34,7 +34,7 @@ const ICONS = { ignite: '', blueprint: '', }; -const BAG_ORDER = [BARAU, BARAG, BARBZ, BARBS, BARST, BARFE, BARCU, BARSN, BARZN, BARGL, BARSB, PLANK, AUORE, AGORE, ZNORE, SNORE, RSTONE, LCLAY, RBRICK, LDIRT, LWOOD, ACORN, COAL, CHARCOAL, CUORE, FEORE, SAND, GRAVEL, RGRAN, RBAS, CULLET, +const BAG_ORDER = [BARAU, BARAG, BARBZ, BARBS, BARST, BARFE, BARCU, BARSN, BARZN, BARGL, BARSB, PLANK, GREENB, BARBR, AUORE, AGORE, ZNORE, SNORE, RSTONE, LCLAY, RBRICK, LDIRT, LWOOD, ACORN, COAL, CHARCOAL, CUORE, FEORE, SAND, GRAVEL, RGRAN, RBAS, CULLET, SCRCU, SCRFE, SCRST, SCRSN, SCRZN, SCRAG, SCRAU, SCRBZ, SCRBS, ASH, SLAG, SNOW, WATER, MAGMA, MCU, MFE, MST, MGL, MSN, MZN, MAG, MAU, MBZ, MBS]; const GAME = { ctrl: false, diff --git a/test/play.py b/test/play.py index f6806dd..aae0a65 100644 --- a/test/play.py +++ b/test/play.py @@ -691,7 +691,7 @@ with sync_playwright() as p: 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'); })()")) + 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('press') && !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);") diff --git a/test/t_stone.js b/test/t_stone.js index 366173d..ccf8486 100644 --- a/test/t_stone.js +++ b/test/t_stone.js @@ -74,6 +74,21 @@ function feedHopper(m, n, bottom = 76, clearItems = true) { for (let t = 0; t < 4; t++) { tick++; tickStamp++; procBlocksTick(); } }`); } +hopper('BP_PRESS'); run('stats.pressed = 0;'); feedHopper('LCLAY', 8); +check('a Brick press makes one green brick from eight clay lumps', run('stats.pressed === 1 && ents.length === 1 && ents[0].m === GREENB && ents[0].iy === 76 && seen[GREENB]')); +feedHopper('CUORE', 1); +check('a Brick press passes other grains through', run('mat[76*W+81] === CUORE && temp[76*W+81] === 60')); +check('two fired bricks build a brick wall block', run('WALL_OF[BARBR] === BRICK') && JSON.stringify(run('wallCost(BARBR, 16)')) === JSON.stringify([[run('BARBR'), 2]])); +hopper('BP_PRESS'); run('stats.pressed = 0;'); feedHopper('LCLAY', 7); +run('setCell(77*W+80, STONE, 20, 0); setCell(71*W+80, LCLAY, 20, 0); stamp[71*W+80] = 0; for (let k = 0; k < 8; k++) { tick++; tickStamp++; procBlocksTick(); }'); +check('a press waits until a whole brick fits underneath', run('mat[71*W+80] === LCLAY && procState.get(18*BW+20).press === 7 && procState.get(18*BW+20).blocked && stats.pressed === 0')); +hopper('BP_PRESS', true); feedHopper('LCLAY', 8, 80); +check('a green brick drops below a press and Grinder stack', run('ents.length === 1 && ents[0].m === GREENB && ents[0].iy === 80')); +for (const heat of [1000, 500]) { + flatWorld(run, 240, 160, 100); + run(`spawnBar(GREENB, 80, 98, 0, 0, ${heat}); for (let k = 0; k < 2000; k++) { temp.fill(${heat}); simTick(); }`); + check(`a green brick at ${heat}°C ${heat === 1000 ? 'fires' : 'stays unfired'}`, run(`ents.length === 1 && ents[0].m === ${heat === 1000 ? 'BARBR' : 'GREENB'} && occ[ents[0].iy*W+ents[0].ix] === 1`)); +} hopper('BP_SAW'); feedHopper('RSTONE', 8); check('a Saw cuts eight stone grains into one stone block below', run('stats.bricks === 1 && ents[0].iy === 76')); hopper('BP_SAW'); feedHopper('LWOOD', 8); @@ -131,7 +146,7 @@ for (const [type, refund] of [['refiner', 120], ['cutter', 90], ['unknown', 0]]) // ---- builders' orders: bulk rubble, gravel and stone blocks at a few times a tiny price flatWorld(run, 240, 160, 100); -run('seen[PLANK] = 0; seen[RSTONE] = 1; seen[GRAVEL] = 1; seen[BARSB] = 1; orders = []; ordersDone = 0;'); +run('seen[BARBR] = 0; seen[PLANK] = 0; seen[RSTONE] = 1; seen[GRAVEL] = 1; seen[BARSB] = 1; orders = []; ordersDone = 0;'); const os = JSON.parse(run('JSON.stringify(Array.from({ length: 40 }, () => makeBuildersOrder()))')); const okOrders = os.every(o => o && o.who === 'Builders' && [run('RSTONE'), run('GRAVEL'), run('SAND'), run('BARSB')].includes(o.m) && o.pay > 0 && o.pay < 400); check("builders post orders for rubble, gravel and stone blocks that pay a little", okOrders, os.slice(0, 4).map(o => `${o.qty} of ${o.m} for $${o.pay}`).join(', '));