From 9739ca0d1e86807ebadf532d963178e691aa7f42 Mon Sep 17 00:00:00 2001 From: 44r0n7 <44r0n7+gitea@pm.me> Date: Sat, 24 Oct 2026 16:39:27 -0400 Subject: [PATCH] Remove the Refiner and Stone cutter; old saves refund them on load (t4) Co-Authored-By: Claude Opus 5.5 --- docs/PROJECT_MAP.md | 28 ++++++++-------- src/render/machines.js | 25 +------------- src/sim/machine-tick.js | 4 --- src/sim/machines.js | 6 ---- src/sim/materials.js | 2 +- src/sim/pieces.js | 4 --- src/sim/processors.js | 72 +++-------------------------------------- src/sim/save.js | 8 ++++- src/ui/build-menu.js | 2 -- src/ui/info.js | 2 -- test/t_pipes.js | 40 ++++++++++++----------- test/t_stone.js | 29 +++++++++++------ 12 files changed, 70 insertions(+), 152 deletions(-) diff --git a/docs/PROJECT_MAP.md b/docs/PROJECT_MAP.md index 517d3bd..dfb9d99 100644 --- a/docs/PROJECT_MAP.md +++ b/docs/PROJECT_MAP.md @@ -201,7 +201,7 @@ Material ids, the material table (def), per-material property arrays, cell flags - 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 -### src/sim/pieces.js (143 lines) +### src/sim/pieces.js (139 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 @@ -236,22 +236,22 @@ Heat: conduction, melting and freezing, reactions, fire and air, steel. - functions: curAir, airflowAt, alloyMix, airy, hasO2, condPair, heatPass, heatChunk, neighborDir, react, char, quench, burningNear, burn, steelCheck - variables: POROUS, SOIL, HOTLIQ, ALLOY -### src/sim/machines.js (154 lines) +### src/sim/machines.js (148 lines) 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 (165 lines) -Processing machines: the Refiner, the Stone cutter and the Silo. -- functions: refinerSlot, refinerTick, cutterTick, isProcCode, rebuildProcessors, procBottom, procSlot, procFeed, procBlocksTick, siloTick, siloFull -- variables: REFINE, REFINE_SAND, REFINE_RATE, CUT_PER_BRICK, CUT_RATE, procState, procBlocks, procDirty, SILO_CAP, SILO_RATE +### src/sim/processors.js (103 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 ### src/sim/pipes.js (114 lines) 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 - variables: PIPE_CAP, PUMP_RATE, OUTLET_RATE, PUMP_MAX_T, SIDE_V, PUMP_V, OUTLET_V, pipeNets, pipeNetAt, pipesDirty -### src/sim/machine-tick.js (167 lines) +### src/sim/machine-tick.js (163 lines) Per-tick machine work: steam engines, fans, belts, filters, screw tubes (machinesTick). - functions: engineIntake, fanBlow, machinesTick @@ -329,7 +329,7 @@ World generation: noise, layers, ores, caves, water, trees, and clearing tiny ha - functions: makeNoise, fbm, clearHardSpecks, generateWorld - variables: SPECK_MIN -### src/sim/save.js (120 lines) +### src/sim/save.js (126 lines) Saving and loading a world (serializeWorld / deserializeWorld) and old-save migration. - functions: serializeWorld, deserializeWorld - variables: SAVE_VERSION, TOFF @@ -348,8 +348,8 @@ Scale colour without changing the smooth-look class. - functions: fogColor, drawWorld - variables: TUBE_CLASS -### src/render/machines.js (197 lines) -Machine overlays: fans, engines, refiner, cutter, silo and the rest, drawn on top of the world. +### src/render/machines.js (174 lines) +Machine overlays: fans, engines, silo and the rest, drawn on top of the world. - functions: cellRect, drawHazardRims, drawMachines, drawSilo, drawSiloPixel ### src/render/overlays.js (229 lines) @@ -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 (104 lines) +### src/ui/build-menu.js (102 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 (175 lines) +### src/ui/info.js (173 lines) Discovery cards, hover info (I) and the cursor label. - functions: showCard, howText, discoveryCard, reactionsOf, hoverInfo, renderInfo, drawCursorLabel @@ -456,9 +456,9 @@ The frame loop and boot. - `test/t_fog.js`: Fog: surface, sight, range, saves and simulation independence. - `test/t_furnace_repeat.js`: The starter furnaces keep working when fired again and again without cleaning, however the charge goes in: mixed, ore first with coal on top, or blown in through the vent. Each case plays furnace_play.js, which builds the design and fires it like a player. - `test/t_molds.js`: Molds and furnaces stay usable: ingots drop below and keep their stubs, plugs over molds are cleared, spoiled castings can be knocked out, grains never drop through a grate by teleporting, and slag lets the fire breathe. -- `test/t_pipes.js`: Pipes, pumps and outlets move liquids; the Refiner grinds waste rock for trace ore. +- `test/t_pipes.js`: Pipes, pumps and outlets move liquids; the Grinder grinds waste rock for trace ore. - `test/t_smooth.py`: The smooth look: the WebGL pass compiles, draws, falls back cleanly, and the thermal lens still works. -- `test/t_stone.js`: Uses for waste rock and sand: glass bricks from a fan furnace, the stone cutter, the silo and builders' orders. +- `test/t_stone.js`: Uses for waste rock and sand: glass bricks from a fan furnace, the Saw, the silo and builders' orders. - `test/t_weather.js`: Weather: no more springs; steam rises into clouds and rains back; showers; snow when it is freezing. - `test/t_world.js`: Trees from acorns, charcoal, brick, density, water/lava/snow/ice, full-bag refunds, drill reach, ballistic blowing, snowy mountains. - `tools/map.py`: Regenerate the file and symbol index at the end of docs/PROJECT_MAP.md. diff --git a/src/render/machines.js b/src/render/machines.js index 6c6df7b..f177ace 100644 --- a/src/render/machines.js +++ b/src/render/machines.js @@ -1,4 +1,4 @@ -// Machine overlays: fans, engines, refiner, cutter, silo and the rest, drawn on top of the world. +// Machine overlays: fans, engines, silo and the rest, drawn on top of the world. // ---------- Overlays ---------- function cellRect(x, y, w, h) { const p = worldToScreenPx(x, y); return [p.x, p.y, w * R.zp, h * R.zp]; } // Yellow and black hazard stripes on both top corners of a hopper @@ -63,29 +63,6 @@ function drawMachines() { ctx.beginPath(); ctx.moveTo(cx + dx * sw * 0.55, cy + dy * sh * 0.55); ctx.lineTo(cx + dx * sw * 0.9, cy + dy * sh * 0.9); ctx.stroke(); } else if (mm.type === 'silo') { drawSilo(ctx, mm, sx, sy, zp); - } else if (mm.type === 'cutter') { - // hazard rims on the hopper, and a toothed saw blade in the floor that spins while it cuts - const busy = tick - (mm.busyT || -99) < 12; - drawHazardRims(ctx, sx, sy, sw, zp); - const cx = sx + 4 * zp, cy = sy + 6.5 * zp, rr = zp * 1.4, a0 = busy ? R.frame * 0.35 : 0; - ctx.fillStyle = '#c9cfd4'; ctx.beginPath(); - for (let t = 0; t < 16; t++) { const a = a0 + t * Math.PI / 8, r2 = t & 1 ? rr : rr * 0.78; ctx.lineTo(cx + Math.cos(a) * r2, cy + Math.sin(a) * r2); } - ctx.closePath(); ctx.fill(); - ctx.fillStyle = '#3a4048'; ctx.beginPath(); ctx.arc(cx, cy, rr * 0.3, 0, Math.PI * 2); ctx.fill(); - ctx.fillStyle = mm.blocked ? '#c4553f' : busy ? '#e0b43c' : '#4a5058'; - ctx.fillRect(sx + 2 * zp, sy + 7.6 * zp, 4 * zp, zp * 0.4); - } else if (mm.type === 'refiner') { - // hopper rim, two toothed grinding rollers in the floor (they turn while it works), the chute - const busy = tick - (mm.busyT || -99) < 12; - drawHazardRims(ctx, sx, sy, sw, zp); - for (let k = 0; k < 2; k++) { - const cx = sx + (k ? 7.5 : 4.5) * zp, cy = sy + 11 * zp, rr = zp * 1.15, a0 = (busy ? R.frame * 0.25 : 0) * (k ? -1 : 1); - ctx.fillStyle = '#2c3036'; ctx.beginPath(); ctx.arc(cx, cy, rr, 0, Math.PI * 2); ctx.fill(); - ctx.strokeStyle = '#9aa4ae'; ctx.lineWidth = Math.max(1, zp * 0.3); - ctx.beginPath(); for (let t = 0; t < 6; t++) { const a = a0 + t * Math.PI / 3; ctx.moveTo(cx + Math.cos(a) * rr * 0.35, cy + Math.sin(a) * rr * 0.35); ctx.lineTo(cx + Math.cos(a) * rr, cy + Math.sin(a) * rr); } ctx.stroke(); - } - ctx.fillStyle = mm.blocked ? '#c4553f' : busy ? '#e0b43c' : '#4a5058'; - ctx.beginPath(); ctx.moveTo(sx + 4 * zp, sy + 12 * zp); ctx.lineTo(sx + 8 * zp, sy + 12 * zp); ctx.lineTo(sx + 7 * zp, sy + 12.8 * zp); ctx.lineTo(sx + 5 * zp, sy + 12.8 * zp); ctx.closePath(); ctx.fill(); } else if (mm.type === 'engine') { ctx.fillStyle = 'rgba(16,18,22,0.4)'; ctx.fillRect(sx + zp, sy + zp, sw - 2 * zp, sh - 3 * zp); const fx = sx + sw * (mm.dir ? 0.3 : 0.7), fy = sy + sh * 0.42, fr = sh * 0.3; diff --git a/src/sim/machine-tick.js b/src/sim/machine-tick.js index 13d5826..8abe615 100644 --- a/src/sim/machine-tick.js +++ b/src/sim/machine-tick.js @@ -57,10 +57,6 @@ function machinesTick() { const inst = mm.flow * WHEEL_K; mm.out += (inst - mm.out) * 0.04; mm.flow = 0; mm.angle += Math.min(0.35, mm.out * 0.012); - } else if (mm.type === 'refiner') { - refinerTick(mm); - } else if (mm.type === 'cutter') { - cutterTick(mm); } else if (mm.type === 'silo') { siloTick(mm); } else if (mm.type === 'engine') { diff --git a/src/sim/machines.js b/src/sim/machines.js index 3d83168..c8e6213 100644 --- a/src/sim/machines.js +++ b/src/sim/machines.js @@ -76,9 +76,6 @@ function machineFootprint(type, x, y) { } } else if (type === 'fan') { for (let dy = 0; dy < 4; dy++) for (let dx = 0; dx < 4; dx++) out.push([x + dx, y + dy, 'hull']); - } else if (type === 'cutter') { - // a hopper 4 grains wide over the saw floor; bricks drop out underneath - for (let dy = 0; dy < 8; dy++) for (let dx = 0; dx < 8; dx++) out.push([x + dx, y + dy, dx < 2 || dx >= 6 || dy >= 5 ? 'hull' : 'void']); } else if (type === 'silo') { // roof with a hatch, the body, a cone narrowing to the gate, and two legs; the gaps beside // the cone are left open @@ -92,9 +89,6 @@ function machineFootprint(type, x, y) { else k = dx >= 4 && dx <= 7 ? 'void' : dx === 3 || dx === 8 ? 'hull' : null; // the gate if (k) out.push([x + dx, y + dy, k]); } - } else if (type === 'refiner') { - // an open hopper 8 grains wide over the grinding floor; what it lets out drops from under the floor - for (let dy = 0; dy < 12; dy++) for (let dx = 0; dx < 12; dx++) out.push([x + dx, y + dy, dx < 2 || dx >= 10 || dy >= 10 ? 'hull' : 'void']); } else if (type === 'dock') { // an open-topped hopper: 2-grain walls and floor around a 20x14 bin for (let dy = 0; dy < 16; dy++) for (let dx = 0; dx < 24; dx++) out.push([x + dx, y + dy, dx < 2 || dx >= 22 || dy >= 14 ? 'hull' : 'void']); diff --git a/src/sim/materials.js b/src/sim/materials.js index 5fb72bc..af57da0 100644 --- a/src/sim/materials.js +++ b/src/sim/materials.js @@ -106,7 +106,7 @@ 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(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 Stone cutter, from 8 grains of rubble. Builders buy them, and 2 make a cut stone wall block.' }); +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(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 0303f0f..8938812 100644 --- a/src/sim/pieces.js +++ b/src/sim/pieces.js @@ -65,12 +65,8 @@ const PIECES = [ 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'], 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', 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.' }, { 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.' }, - { 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.' }, { 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.' }, { id: 'dock', name: 'Trade dock', kind: 'machine', bw: 6, bh: 4, cost: [], hidden: true, fixed: true, diff --git a/src/sim/processors.js b/src/sim/processors.js index f07cf67..437c611 100644 --- a/src/sim/processors.js +++ b/src/sim/processors.js @@ -1,8 +1,8 @@ -// Processing machines: the Refiner, the Stone cutter and the Silo. -// ---------- Refiner ---------- +// Processing: Grinder and Saw blocks, and the Silo. +// ---------- Grinding and cutting ---------- // Grinding a grain of rock, gravel, sand or dirt turns up the odd grain of ore: [chance, what], // tried in turn. If none turns up, half the time a grain of fine sand comes out, else nothing: -// the rest is ground to dust and washed away, so a refiner halves a pile of waste rock. +// the rest is ground to dust and washed away, so a Grinder halves a pile of waste rock. const REFINE = []; REFINE[RSTONE] = [[0.025, CUORE], [0.02, COAL], [0.015, FEORE], [0.006, SNORE]]; REFINE[RGRAN] = [[0.02, FEORE], [0.012, ZNORE], [0.01, CUORE], [0.002, AGORE]]; @@ -10,70 +10,8 @@ REFINE[RBAS] = [[0.03, FEORE], [0.01, ZNORE], [0.003, AUORE]]; REFINE[GRAVEL] = [[0.03, FEORE], [0.02, CUORE], [0.01, SNORE], [0.003, ZNORE]]; 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, REFINE_RATE = 0.5; // grains ground per tick -// a free cell under the refiner's chute, or -1 if the way out is blocked -function refinerSlot(mm) { - const y = mm.y + 12; - if (y >= H) return -1; - for (const dx of [5, 6, 4, 7]) { const j = y * W + mm.x + dx; if (mat[j] === EMPTY && !part[j] && !occ[j] && !mach[j]) return j; } - return -1; -} -function refinerTick(mm) { - mm.work = (mm.work || 0) + REFINE_RATE; - if (mm.work < 1) return; - let jobs = Math.floor(mm.work); mm.work -= jobs; - const y = mm.y + 9; - mm.blocked = false; - for (let k = 0; k < 8 && jobs > 0; k++) { - const x = mm.x + 2 + ((k + tick) & 7), i = y * W + x, m = mat[i]; - if (m === EMPTY || PH[m] === PH_GAS || PH[m] === PH_SOLID || stamp[i] === tickStamp) continue; - const out = refinerSlot(mm); - if (out < 0) { mm.blocked = true; break; } - jobs--; mm.busyT = tick; - const T = temp[i]; - if (PH[m] === PH_POWDER && REFINE[m]) { - setCell(i, EMPTY, AMB[y], 0); - 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, m, 'refine'); } - else if (rnd() < REFINE_SAND) got = SAND; - if (got) setCell(out, got, T, 0); - stats.refined = (stats.refined || 0) + 1; - } else { - // ore, coal, liquids: nothing to grind, so they go straight through - setCell(out, m, T, 0); setCell(i, EMPTY, AMB[y], 0); - } - } -} -// ---------- Stone cutter ---------- -const CUT_PER_BRICK = 8, CUT_RATE = 0.5; -function cutterTick(mm) { - mm.work = (mm.work || 0) + CUT_RATE; - if (mm.work < 1) return; - let jobs = Math.floor(mm.work); mm.work -= jobs; - const y = mm.y + 4; - mm.blocked = false; - for (let k = 0; k < 4 && jobs > 0; k++) { - const x = mm.x + 2 + ((k + tick) & 3), i = y * W + x, m = mat[i]; - if (m === EMPTY || PH[m] === PH_GAS || PH[m] === PH_SOLID || stamp[i] === tickStamp) continue; - if (m === RSTONE) { - if ((mm.cut || 0) + 1 >= CUT_PER_BRICK) { - // a brick is ready: it needs room under the machine - if (!entFreeAt(mm.x + 2, mm.y + 8)) { mm.blocked = true; break; } - mm.cut = 0; - const e = spawnBar(BARSB, mm.x + 2, mm.y + 8, (rnd() - 0.5) * 0.1, 0.05); - if (e) { e.T = temp[i]; discover(BARSB, x, y, RSTONE, 'cut'); stats.bricks = (stats.bricks || 0) + 1; } - } else mm.cut = (mm.cut || 0) + 1; - setCell(i, EMPTY, AMB[y], 0); - } else { - // anything else drops through, out of the bottom - const j = (mm.y + 8) * W + mm.x + 3 + (k & 1); - if (mm.y + 8 >= H || mat[j] !== EMPTY || part[j] || occ[j]) { mm.blocked = true; break; } - setCell(j, m, temp[i], 0); setCell(i, EMPTY, AMB[y], 0); - } - jobs--; mm.busyT = tick; - } -} +const REFINE_SAND = 0.5; +const CUT_PER_BRICK = 8; // 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; } diff --git a/src/sim/save.js b/src/sim/save.js index 54485fd..9ebadb8 100644 --- a/src/sim/save.js +++ b/src/sim/save.js @@ -74,7 +74,13 @@ function deserializeWorld(meta, buffers) { if (p >= P_TU && p <= P_TR) markPow(i, true); } for (let i = 0; i < N; i++) { const m = mat[i]; if (m === BELTL || m === BELTR || m === SHAFT) markPow(i, true); } - for (const m of meta.machines) { const mm = addMachine(m.type, m.bx, m.by, m.dir); mm.on = m.on !== false; if (m.type === 'silo') { mm.sm = m.sm || 0; mm.sn = m.sn || 0; mm.open = !!m.open; } } + // Old machines removed from the game are refunded. + const removedMachines = { refiner: 120, cutter: 90 }; + for (const m of meta.machines) { + if (!Object.hasOwn(PIECE_BY_ID, m.type)) { money += Object.hasOwn(removedMachines, m.type) ? removedMachines[m.type] : 0; continue; } + const mm = addMachine(m.type, m.bx, m.by, m.dir); mm.on = m.on !== false; + if (m.type === 'silo') { mm.sm = m.sm || 0; mm.sn = m.sn || 0; mm.open = !!m.open; } + } molds = []; for (const m of meta.molds) molds.push(newMoldRecord(m.kind, m.x, m.y, m.wall)); for (const [m, x, y, T] of meta.ents || []) { const e = { m, x, y, ix: x, iy: y, vx: 0, vy: 0, T, rest: 0 }; ents.push(e); entMark(e, 1); } diff --git a/src/ui/build-menu.js b/src/ui/build-menu.js index 1edd69c..e04f8ce 100644 --- a/src/ui/build-menu.js +++ b/src/ui/build-menu.js @@ -19,8 +19,6 @@ const PIECE_ICONS = { pump: '', outlet: '', fan: '', - refiner: '', - cutter: '', grinder: '', saw: '', silo: '', diff --git a/src/ui/info.js b/src/ui/info.js index 6b83285..dce00e5 100644 --- a/src/ui/info.js +++ b/src/ui/info.js @@ -91,8 +91,6 @@ function hoverInfo(ax, ay) { if (mm.type === 'wheel' || mm.type === 'engine') t2.push(`making ${mm.out.toFixed(1)} power`); 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'); - 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`); } else if (isProcCode(b)) { const s = procState.get((ay >> 2) * BW + (ax >> 2)), saw = b === BP_SAW; t1 = saw ? 'Saw' : 'Grinder'; diff --git a/test/t_pipes.js b/test/t_pipes.js index 6001dda..46e8cc1 100644 --- a/test/t_pipes.js +++ b/test/t_pipes.js @@ -1,4 +1,4 @@ -// Pipes, pumps and outlets move liquids; the Refiner grinds waste rock for trace ore. +// Pipes, pumps and outlets move liquids; the Grinder grinds waste rock for trace ore. const { load, suite, flatWorld, count } = require('./harness'); const { run } = load(); const { check, done } = suite(); @@ -73,29 +73,33 @@ check('water flows through a run of pipe', count(run, run('WATER'), 64, 70, 87, box(60, 64, 63, 70, 'SAND', 0); run('for (let k = 0; k < 300; k++) simTick();'); check('and sand dropped on a pipe falls down through it', count(run, run('SAND'), 40, 80, 87, 99) > 20, `${count(run, run('SAND'), 40, 80, 87, 99)} of 28 at the bottom`); -// ---- the Refiner -function refinerRig(feed, n) { +// ---- the Grinder +function grinderRig() { flatWorld(run, 240, 160, 100); - run(`money = 5000; bot.x = 110; bot.y = 40; stats.refined = 0; stats.refinedOre = 0; placeMachineAt('refiner', 0, 26, 15);`); // x 104..115, y 60..71; chute out at y 72 - run(`{ let k = 0; for (let y = 60; y < 70 && k < ${n}; y++) for (let x = 106; x < 114 && k < ${n}; x++, k++) setCell(y * W + x, ${feed}, AMB[y], 0); }`); + run('money = 5000; stats.refined = 0; stats.refinedOre = 0; placeBlock(BP_GRIND, 0, 26, 15, true);'); } -refinerRig('RSTONE', 80); +function grind(feed, n) { + run(`for (let k = 0; k < ${n}; k++) { + setCell(59 * W + 104, ${feed}, AMB[59], 0); stamp[59 * W + 104] = 0; + for (let t = 0; t < 4; t++) { tick++; tickStamp++; procBlocksTick(); } + for (let t = 0; t < 12; t++) simTick(); + }`); +} +grinderRig(); grind('RSTONE', 400); const ore = () => count(run, run('CUORE')) + count(run, run('COAL')) + count(run, run('FEORE')) + count(run, run('SNORE')); -// five hopper-loads, each ground before the next goes in -for (let k = 0; k < 5; k++) { run('for (let k = 0; k < 300; k++) simTick();'); if (k < 4) run('for (let y = 60; y < 70; y++) for (let x = 106; x < 114; x++) setCell(y * W + x, RSTONE, AMB[y], 0);'); } const fed = 400, sand = count(run, run('SAND')), o = ore(); -check('the Refiner grinds all the stone rubble it is fed', count(run, run('RSTONE')) === 0 && run('stats.refined') === fed, `${count(run, run('RSTONE'))} left, ${run('stats.refined')} of ${fed} ground`); +check('the Grinder grinds all the stone rubble it is fed', count(run, run('RSTONE')) === 0 && run('stats.refined') === fed, `${count(run, run('RSTONE'))} left, ${run('stats.refined')} of ${fed} ground`); check('about half as much sand comes out', sand > fed * 0.35 && sand < fed * 0.6, `${sand} sand from ${fed} rubble`); check('with the odd grain of common ore', o > 0 && o < fed * 0.15, `${o} ore grains`); // gravel, sand and dirt are refined too; ore drops straight through -refinerRig('LDIRT', 60); run('for (let k = 0; k < 600; k++) simTick();'); -check('dirt goes through the refiner too', count(run, run('LDIRT')) === 0 && run('stats.refined') === 60); -refinerRig('CUORE', 30); run('for (let k = 0; k < 400; k++) simTick();'); -check('ore it cannot grind drops straight through, all of it', count(run, run('CUORE')) === 30 && count(run, run('CUORE'), 104, 60, 115, 71) === 0, count(run, run('CUORE'), 104, 60, 115, 71)); -// a blocked chute stops it -refinerRig('RSTONE', 40); -box(100, 72, 120, 73, 'STONE'); -run('for (let k = 0; k < 200; k++) simTick();'); -check('with the space under its chute blocked it stops, and says so', count(run, run('RSTONE')) === 40 && run("machines.find(m => m && m.type === 'refiner').blocked") === true); +grinderRig(); grind('LDIRT', 60); +check('dirt goes through the Grinder too', count(run, run('LDIRT')) === 0 && run('stats.refined') === 60); +grinderRig(); grind('CUORE', 30); +check('ore it cannot grind drops straight through, all of it', count(run, run('CUORE')) === 30 && count(run, run('CUORE'), 104, 59, 107, 63) === 0, count(run, run('CUORE'), 104, 59, 107, 63)); +// a blocked outlet stops it +grinderRig(); +box(104, 64, 107, 65, 'STONE'); +run('setCell(59 * W + 104, RSTONE, AMB[59], 0); stamp[59 * W + 104] = 0; for (let k = 0; k < 8; k++) { tick++; tickStamp++; procBlocksTick(); }'); +check('with the space under its outlet blocked it stops, and says so', count(run, run('RSTONE')) === 1 && run('procState.get(15 * BW + 26).blocked') === true); done(); diff --git a/test/t_stone.js b/test/t_stone.js index f1909d3..6d60542 100644 --- a/test/t_stone.js +++ b/test/t_stone.js @@ -1,4 +1,4 @@ -// Uses for waste rock and sand: glass bricks from a fan furnace, the stone cutter, the silo and +// Uses for waste rock and sand: glass bricks from a fan furnace, the Saw, the silo and // builders' orders. const { load, suite, flatWorld, count } = require('./harness'); const { run } = load(); @@ -31,15 +31,11 @@ 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); -// ---- the stone cutter: 8 rubble to a stone brick; other grains drop through +// ---- Saw bricks ride a belt and build walls flatWorld(run, 240, 160, 100); -run("money = 9999; bot.x = 60; bot.y = 40; stats.bricks = 0; placeMachineAt('cutter', 0, 26, 18); for (let bx = 24; bx <= 34; bx++) placeBlock(BP_BELT, 1, bx, 21, true);"); // x 104..111, y 72..79; out at y 80, onto a belt -run('for (let y = 72; y < 77; y++) for (let x = 106; x < 110; x++) setCell(y * W + x, RSTONE, AMB[y], 0);'); // 20 grains -for (let k = 0; k < 4; k++) { run('for (let k = 0; k < 150; k++) simTick();'); run('for (let y = 72; y < 77; y++) for (let x = 106; x < 110; x++) if (mat[y * W + x] === EMPTY) setCell(y * W + x, RSTONE, AMB[y], 0);'); } -run('for (let k = 0; k < 300; k++) simTick();'); -const fed = run('stats.bricks') * 8 + run("machines.find(m => m && m.type === 'cutter').cut"); -check('the stone cutter turns every 8 rubble into a stone brick', bars(run('BARSB')) === run('stats.bricks') && run('stats.bricks') >= 8 && count(run, run('RSTONE'), 104, 72, 111, 79) === 0, `${bars(run('BARSB'))} bricks from ${fed} rubble`); -check('and they 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))')); +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('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);'); @@ -100,6 +96,21 @@ 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')); +// ---- removed machines in old saves are refunded and leave no hull or machine ids +flatWorld(run, 240, 160, 100); +run("money = 1000; addMachine('fan', 10, 10, 0); placeDock(160);"); +for (const [type, refund] of [['refiner', 120], ['cutter', 90], ['unknown', 0]]) { + const result = run(`(() => { + const saved = serializeWorld(), meta = JSON.parse(JSON.stringify(saved.meta)); + const before = machines.filter(Boolean).length, cash = money; + meta.machines.push({ type: '${type}', bx: 20, by: 10, dir: 0, on: true }); + saved.buffers.mat[40 * W + 80] = HULL; + deserializeWorld(meta, saved.buffers); + return [machines.filter(Boolean).length === before, money - cash, mat[40 * W + 80] === EMPTY && mach[40 * W + 80] === 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 flatWorld(run, 240, 160, 100); run('seen[RSTONE] = 1; seen[GRAVEL] = 1; seen[BARSB] = 1; orders = []; ordersDone = 0;');