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')));