From b76f4516b7bb04bd1a3f8eac31542c85236f4bbf Mon Sep 17 00:00:00 2001 From: 44r0n7 <44r0n7+gitea@pm.me> Date: Sat, 24 Oct 2026 16:50:35 -0400 Subject: [PATCH] Saw cuts wood chunks into Planks; stone bricks are now Stone blocks (t4) Co-Authored-By: Claude Opus 5.5 --- docs/PROJECT_MAP.md | 6 +++--- src/sim/economy.js | 6 +++--- src/sim/materials.js | 7 ++++--- src/sim/pieces.js | 8 ++++---- src/sim/processors.js | 25 +++++++++++++++---------- src/ui/info.js | 2 +- src/ui/notebook.js | 4 ++-- src/ui/state.js | 2 +- test/t_stone.js | 43 ++++++++++++++++++++++++++++++++----------- 9 files changed, 65 insertions(+), 38 deletions(-) diff --git a/docs/PROJECT_MAP.md b/docs/PROJECT_MAP.md index c4f5af7..5048437 100644 --- a/docs/PROJECT_MAP.md +++ b/docs/PROJECT_MAP.md @@ -196,7 +196,7 @@ 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 (161 lines) +### src/sim/materials.js (162 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 @@ -241,10 +241,10 @@ 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 (103 lines) +### src/sim/processors.js (108 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, procState, procBlocks, procDirty, SILO_CAP, SILO_RATE +- variables: REFINE, REFINE_SAND, CUT_PER_BRICK, SAW_CUTS, procState, procBlocks, procDirty, SILO_CAP, SILO_RATE ### src/sim/pipes.js (114 lines) Pipes, pumps and outlets: each joined line holds one liquid as a count, not as grains. diff --git a/src/sim/economy.js b/src/sim/economy.js index aedae51..33203ba 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], + [CHARCOAL, 1.2], [RBRICK, 0.8], [CULLET, 1.2], [BARGL, 14], [RSTONE, 0.02], [GRAVEL, 0.03], [BARSB, 0.6], [PLANK, 2.4], [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; @@ -71,9 +71,9 @@ let orders = [], ordersDone = 0, nextOrderT = 3600, orderSeq = 1; const ORDER_SLOTS = 3; const STANDING = ['Local buyers', 'County buyers', 'Regional buyers', 'National buyers', 'Export houses']; function standing() { return Math.min(STANDING.length - 1, Math.floor(ordersDone / 4)); } -// Builders buy the cheap bulk stuff (rubble, gravel, sand, stone bricks) in big lots at a few +// 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]]; +const BUILD_GOODS = [[RSTONE, 400, 1500], [GRAVEL, 300, 1000], [SAND, 300, 900], [BARSB, 20, 80], [PLANK, 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/materials.js b/src/sim/materials.js index af57da0..16cb6ad 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; -const NMAT = 97; + 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; const MD = []; function def(id, o) { @@ -105,8 +105,9 @@ metalFamily({ solid: BRONZE, molten: MBZ, scrap: SCRBZ, bar: BARBZ, name: 'Bronz metalFamily({ solid: BRASS, molten: MBS, scrap: SCRBS, bar: BARBS, name: 'Brass', col: [212, 174, 84], mcol: [255, 196, 104], melt: 930, dens: 8.4, k: 0.035, hardSolid: 4, 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(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 brick', 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(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 }); def(DOCKW, { name: 'Trade dock', col: [70, 82, 96], v: 6, k: 0.01, cap: 2, hard: 9, yld: 0, partMat: true, desc: 'The claim office\'s trade dock. Whatever drops through the grate in its floor is sold.' }); def(HULL, { name: 'Machine', col: [84, 92, 102], v: 6, k: 0.03, cap: 1.5, hard: 4, melt: 1085, meltTo: MCU, partMat: true }); diff --git a/src/sim/pieces.js b/src/sim/pieces.js index 8938812..3abc5d5 100644 --- a/src/sim/pieces.js +++ b/src/sim/pieces.js @@ -25,10 +25,10 @@ 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[BARGL] = GLASS; -const WALL_LIST = [RSTONE, BARSB, LCLAY, RBRICK, LDIRT, LWOOD, CULLET, BARGL, RGRAN, RBAS, BARCU, BARFE, BARST]; +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]; 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[TIN] = BARSN; BAR_OF[ZINC] = BARZN; BAR_OF[SILVER] = BARAG; BAR_OF[GOLD] = BARAU; BAR_OF[BRONZE] = BARBZ; BAR_OF[BRASS] = BARBS; +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; function wallCost(v, cells) { return [[v, PH[v] === PH_ITEM ? Math.ceil(cells / GRAINS_PER_BAR) : cells]]; } @@ -60,7 +60,7 @@ const PIECES = [ { 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.' }, + 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, 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'], diff --git a/src/sim/processors.js b/src/sim/processors.js index 437c611..3a33442 100644 --- a/src/sim/processors.js +++ b/src/sim/processors.js @@ -12,6 +12,7 @@ REFINE[SAND] = [[0.01, SNORE], [0.008, FEORE], [0.0015, AUORE]]; REFINE[LDIRT] = [[0.08, LCLAY], [0.01, COAL], [0.008, CUORE], [0.003, ACORN]]; const REFINE_SAND = 0.5; 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; } @@ -19,7 +20,7 @@ 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 }); + if (!procState.has(b)) procState.set(b, { work: 0, cut: { [RSTONE]: 0, [LWOOD]: 0 }, busyT: -999, blocked: false }); } procDirty = false; } @@ -41,16 +42,20 @@ function procFeed(b, m, T) { 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; + } 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]; } 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; } + if (m === BARSB || m === PLANK) { + 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; + else stats.bricks = (stats.bricks || 0) + 1; + } } else setCell(procSlot(end), m, T, 0); } function procBlocksTick() { @@ -66,9 +71,9 @@ 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 (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 (SAW_CUTS[m]) 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 (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/ui/info.js b/src/ui/info.js index dce00e5..993855e 100644 --- a/src/ui/info.js +++ b/src/ui/info.js @@ -94,7 +94,7 @@ function hoverInfo(ax, ay) { } 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`); + 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`); } 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 f749135..1bab863 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, 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, 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]], + ['Bulk (builders)', [RSTONE, GRAVEL, BARSB, PLANK]], ['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]], diff --git a/src/ui/state.js b/src/ui/state.js index 4394693..fbe57e2 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, 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, 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/t_stone.js b/test/t_stone.js index 6d60542..366173d 100644 --- a/test/t_stone.js +++ b/test/t_stone.js @@ -31,12 +31,12 @@ check('opening space below drops glass bricks', bars(run('BARGL')) >= 2 && run(' check('glass bricks sell for much more than the sand in them', run('PRICE[BARGL]') >= 8 * run('PRICE[SAND]') * 10, run('PRICE[BARGL]')); check('glass in a bit mold is too brittle to make a bit', run("METAL_TIER[GLASS]") === 0); -// ---- Saw bricks ride a belt and build walls +// ---- Saw stone blocks ride a belt and build walls flatWorld(run, 240, 160, 100); run("money = 9999; bot.x = 60; bot.y = 40; stats.bricks = 0; placeBlock(BP_SAW, 0, 26, 19, true); for (let bx = 24; bx <= 34; bx++) placeBlock(BP_BELT, 1, bx, 21, true);"); run('for (let k = 0; k < 100; k++) { setCell(75 * W + 104, RSTONE, AMB[75], 0); stamp[75 * W + 104] = 0; for (let t = 0; t < 4; t++) { tick++; tickStamp++; procBlocksTick(); } for (let t = 0; t < 10; t++) simTick(); } for (let k = 0; k < 300; k++) simTick();'); -check('Saw bricks ride a belt away from under it', run('ents.filter(e => e.m === BARSB && e.ix > 120).length') >= 3, run('JSON.stringify(ents.filter(e => e.m === BARSB).map(e => e.ix))')); -check('stone bricks build cut stone walls, 2 a block', run('WALL_OF[BARSB]') === run('CUTST') && JSON.stringify(run('wallCost(BARSB, 16)')) === JSON.stringify([[run('BARSB'), 2]])); +check('Saw stone blocks ride a belt away from under it', run('ents.filter(e => e.m === BARSB && e.ix > 120).length') >= 3, run('JSON.stringify(ents.filter(e => e.m === BARSB).map(e => e.ix))')); +check('stone blocks build cut stone walls, 2 a block', run('WALL_OF[BARSB]') === run('CUTST') && JSON.stringify(run('wallCost(BARSB, 16)')) === JSON.stringify([[run('BARSB'), 2]])); check('cut stone stands more heat than rough stone', run('MELT[CUTST]') > run('MELT[STONE]')); run('bag[BARSB] = 4; bagTotal = 4; placeBlock(BP_WALL, BARSB, 10, 20, false);'); const wallOk = count(run, run('CUTST')) === 16 && run('bag[BARSB]') === 2; @@ -64,10 +64,10 @@ check('what is in a silo is saved', run(`${silo}.sn`) === held - out1 && run(`${ // ---- 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);' : ''}`); + run(`money = 9999; stats.refined = 0; stats.bricks = 0; stats.planks = 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); +function feedHopper(m, n, bottom = 76, clearItems = true) { + run(`${clearItems ? "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; @@ -75,12 +75,30 @@ function feedHopper(m, n, bottom = 76) { }`); } 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')); +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); +check('a Saw cuts eight wood chunks into one plank below', run('stats.planks === 1 && ents.length === 1 && ents[0].m === PLANK && ents[0].iy === 76 && seen[PLANK]')); +check('two planks pay for a wood wall block', JSON.stringify(run('wallCost(PLANK, 16)')) === JSON.stringify([[run('PLANK'), 2]])); +run('bag[PLANK] = 4; bagTotal = 4; bot.x = 40; bot.y = 80; placeBlock(BP_WALL, PLANK, 10, 20, false);'); +check('placing a plank wall builds wood and uses two planks', count(run, run('WOOD')) === 16 && run('bag[PLANK] === 2')); +run('removeBlock(10, 20);'); +check('removing a plank wall refunds its two planks', run('bag[PLANK] === 4') && count(run, run('WOOD')) === 0); +check('cut stone is named Stone block', run('MD[BARSB].name') === 'Stone block'); +hopper('BP_SAW'); feedHopper('RSTONE', 5); feedHopper('LWOOD', 5); feedHopper('RSTONE', 3); +check('mixed feed finishes only the stone block first', run('stats.bricks === 1 && stats.planks === 0 && ents.length === 1 && ents[0].m === BARSB && procState.get(18*BW+20).cut[LWOOD] === 5')); +run('entMark(ents[0], 0); ents[0].ix = 96; ents[0].x = 96; entMark(ents[0], 1);'); +feedHopper('LWOOD', 3, 76, false); +check('mixed feed keeps separate wood and stone counts', run('stats.bricks === 1 && stats.planks === 1 && ents.length === 2 && ents.filter(e => e.m === BARSB).length === 1 && ents.filter(e => e.m === PLANK).length === 1')); 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_SAW'); feedHopper('LWOOD', 7); +run('setCell(77*W+80, STONE, 20, 0); setCell(71*W+80, LWOOD, 20, 0); stamp[71*W+80] = 0; for (let k = 0; k < 8; k++) { tick++; tickStamp++; procBlocksTick(); }'); +check('a Saw holds the eighth wood chunk until a whole plank fits', run('mat[71*W+80] === LWOOD && procState.get(18*BW+20).cut[LWOOD] === 7 && procState.get(18*BW+20).blocked && stats.planks === 0')); +hopper('BP_SAW', true); feedHopper('LWOOD', 8, 80); +check('a plank passes the Grinder and drops below the stack', run('stats.planks === 1 && stats.refined === 0 && ents[0].m === PLANK && ents[0].iy === 80')); 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'); @@ -94,7 +112,7 @@ 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')); +check('loading rebuilds processors and drops partial brick state', run('procBlocks.length === 1 && procState.get(18*BW+20).cut[RSTONE] === 0 && procState.get(18*BW+20).cut[LWOOD] === 0 && SAVE_VERSION === 2')); // ---- removed machines in old saves are refunded and leave no hull or machine ids flatWorld(run, 240, 160, 100); @@ -111,12 +129,15 @@ for (const [type, refund] of [['refiner', 120], ['cutter', 90], ['unknown', 0]]) check(`loading a saved ${type} skips it and refunds exactly $${refund}`, result[0] && result[1] === refund && result[2], result); } -// ---- builders' orders: bulk rubble, gravel and stone bricks at a few times a tiny price +// ---- builders' orders: bulk rubble, gravel and stone blocks at a few times a tiny price flatWorld(run, 240, 160, 100); -run('seen[RSTONE] = 1; seen[GRAVEL] = 1; seen[BARSB] = 1; orders = []; ordersDone = 0;'); +run('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 bricks that pay a little", okOrders, os.slice(0, 4).map(o => `${o.qty} of ${o.m} for $${o.pay}`).join(', ')); +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(', ')); +run('seen[PLANK] = 1; orders = BUILD_GOODS.filter(([m]) => m !== PLANK).map(([m]) => ({ m }));'); +check('builders buy planks in lots of 20 to 80', run('(() => { const o = makeBuildersOrder(); return o.m === PLANK && o.qty >= 20 && o.qty <= 80 && o.pay > 0 && Math.abs(PRICE[PLANK] - 2.4) < 0.00001; })()')); +run('orders = [];'); const regular = JSON.parse(run('JSON.stringify(Array.from({ length: 60 }, () => { const o = makeOrder(); return o && !o.who ? o.m : 0; }))')); check('ordinary buyers never ask for thousands of grains of rubble', !regular.includes(run('RSTONE')) && !regular.includes(run('GRAVEL')));