Remove the Refiner and Stone cutter; old saves refund them on load (t4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+14
-14
@@ -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.
|
||||
|
||||
+1
-24
@@ -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;
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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,
|
||||
|
||||
+5
-67
@@ -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; }
|
||||
|
||||
+7
-1
@@ -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); }
|
||||
|
||||
@@ -19,8 +19,6 @@ const PIECE_ICONS = {
|
||||
pump: '<circle cx="11" cy="14" r="6"/><path d="M11 14l3.5-2.5M11 14l-1 4.2M11 14l-4-1.2"/><path d="M15.5 9.5L20 5M16 5h4v4"/>',
|
||||
outlet: '<path d="M2 7h12a4 4 0 0 1 4 4v3"/><path d="M2 11h10a2 2 0 0 1 2 2v1"/><path d="M15 18v1.5M18 20v1.5M12 20v1.5"/>',
|
||||
fan: '<circle cx="12" cy="12" r="1.8"/><path d="M12 10.2c-.4-3.6 1.8-6 4-4.8 2 1.2.4 3.8-4 4.8zM13.8 12c3.6-.4 6 1.8 4.8 4-1.2 2-3.8.4-4.8-4zM12 13.8c.4 3.6-1.8 6-4 4.8-2-1.2-.4-3.8 4-4.8zM10.2 12c-3.6.4-6-1.8-4.8-4 1.2-2 3.8-.4 4.8 4z"/>',
|
||||
refiner: '<path d="M3 4h18l-5 7H8z"/><circle cx="9" cy="15.5" r="2.8"/><circle cx="15" cy="15.5" r="2.8"/><path d="M12 20v2"/>',
|
||||
cutter: '<path d="M5 3h14l-3 5H8z"/><circle cx="12" cy="15" r="5"/><circle cx="12" cy="15" r="1.2"/><path d="M12 9.2v1.6M17.8 15h-1.6M12 20.8v-1.6M6.2 15h1.6"/>',
|
||||
grinder: '<path d="M3 4h18l-5 7H8z"/><circle cx="9" cy="15.5" r="2.8"/><circle cx="15" cy="15.5" r="2.8"/><path d="M12 20v2"/>',
|
||||
saw: '<path d="M5 3h14l-3 5H8z"/><circle cx="12" cy="15" r="5"/><circle cx="12" cy="15" r="1.2"/><path d="M12 9.2v1.6M17.8 15h-1.6M12 20.8v-1.6M6.2 15h1.6"/>',
|
||||
silo: '<path d="M6 5.5h12v11H6z"/><path d="M6 5.5l1-2h10l1 2M6 16.5l4 3h4l4-3M7 16.5v5M17 16.5v5"/><path d="M15.5 8v6"/>',
|
||||
|
||||
@@ -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';
|
||||
|
||||
+22
-18
@@ -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();
|
||||
|
||||
+20
-9
@@ -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;');
|
||||
|
||||
Reference in New Issue
Block a user