Files
deepgrain-foundry/src/sim/machine-tick.js
T

167 lines
7.3 KiB
JavaScript

// Per-tick machine work: steam engines, fans, belts, filters, screw tubes (machinesTick).
function engineIntake(mm) {
for (const i of mm.intake) {
if (mat[i] === STEAM) {
const T = temp[i];
mm.energy += 1 + Math.max(0, T - 100) / 120;
mat[i] = EMPTY; life[i] = 0; temp[i] = AMB[(i / W) | 0];
wakeIdx(i);
const ex = mm.dir === 0 ? mm.x + mm.w : mm.x - 1, ey = mm.y + mm.h - 1;
if (ex >= 0 && ex < W) {
const j = ey * W + ex;
if (mat[j] === EMPTY && !part[j]) { mat[j] = WATER; temp[j] = 92; life[j] = 0; flags[j] = 0; wakeXY(ex, ey); }
}
}
}
}
function fanBlow(mm, sat) {
const dir = mm.dir; // 0 right, 1 down, 2 left, 3 up
const dx = [1, 0, -1, 0][dir], dy = [0, 1, 0, -1][dir];
const cur = curAir();
for (let lane = 0; lane < 4; lane++) {
let sx, sy;
if (dir === 0) { sx = mm.x + 4; sy = mm.y + lane; }
else if (dir === 2) { sx = mm.x - 1; sy = mm.y + lane; }
else if (dir === 1) { sx = mm.x + lane; sy = mm.y + 4; }
else { sx = mm.x + lane; sy = mm.y - 1; }
let powderDepth = 0;
const range = 28;
for (let k = 0; k < range; k++) {
const x = sx + dx * k, y = sy + dy * k;
if (x < 0 || y < 0 || x >= W || y >= H) break;
const i = y * W + x, m = mat[i], ph = PH[m];
if (ph === PH_SOLID && part[i] !== P_GRATE) break;
airS[i] = cur;
if (ph === PH_POWDER) { if (++powderDepth > 16) break; }
else if (ph === PH_GAS && k < range - 1 && rnd() < sat * 0.7 && stamp[i] !== tickStamp) {
const nx = x + dx, ny = y + dy;
if (nx >= 0 && ny >= 0 && nx < W && ny < H) {
const j = ny * W + nx;
if (mat[j] === EMPTY && passable(m, PH_GAS, j, dx, dy)) move(i, j, x, y, nx, ny);
}
}
}
wakeHeatXY(Math.min(W - 1, Math.max(0, sx + dx * 4)), Math.min(H - 1, Math.max(0, sy + dy * 4)));
}
}
function machinesTick() {
if (powerDirty && tick >= nextPowerBuild) { rebuildPower(); nextPowerBuild = tick + 6; }
if (brokenMachines.length) {
const ids = brokenMachines; brokenMachines = [];
for (const id of ids) if (machines[id]) removeMachine(id, false, true);
}
for (const mm of machines) {
if (!mm) continue;
if (mm.type === 'wheel') {
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') {
engineIntake(mm);
mm.out += (mm.energy * ENGINE_K - mm.out) * 0.03; mm.energy = 0;
mm.angle += Math.min(0.4, mm.out * 0.01);
}
}
for (let n = 1; n < nets.length; n++) {
const net = nets[n];
let s = 0; for (const p of net.prods) s += p.out;
let d = net.nb * BELT_POWER + net.nt * TUBE_POWER;
for (const f of net.fans) if (f.on) d += FAN_POWER;
net.supply = s; net.demand = d;
net.sat = POWER_FREE ? 1 : d > 0 ? Math.min(1, s / d) : 0;
}
for (const mm of machines) {
if (!mm || mm.type !== 'fan') continue;
const net = nets[mm.net];
const sat = POWER_FREE ? 1 : net ? net.sat : 0;
mm.angle += sat * 0.5 * (mm.on ? 1 : 0);
if (mm.on && sat > 0.05) fanBlow(mm, sat);
// Keep the measured heat for the marker and hover panel.
if ((tick + mm.id) % 30 === 0) {
let hotT = 0;
for (let y = mm.y; y < mm.y + 4; y++) for (let x = mm.x; x < mm.x + 4; x++) { const tt = temp[y * W + x]; if (tt > hotT) hotT = tt; }
mm.hot = Math.round(hotT);
}
}
// belts and filters move what rests on them every other tick: half a grain per tick
if ((tick & 1) === 0) {
for (let pass = 0; pass < 2; pass++) {
const list = pass ? beltR : beltL, d = pass ? 1 : -1, bm = pass ? BELTR : BELTL;
for (let k = 0; k < list.length; k++) {
const b = list[k];
if (mat[b] !== bm || b < W) continue;
const above = mat[b - W];
if (above === BELTL || above === BELTR) continue; // only the top surface carries
if (netSat(b) <= 0) continue;
if (PH[above] === PH_LIQUID) beltDrain(b);
carryColumn(b - W, d, null);
}
}
if (filtersDirty) rebuildFilters();
for (const b of filterR) filterCarry(b, 1);
for (const b of filterL) filterCarry(b, -1);
}
// tubes: every loose grain inside steps one cell along, front of the line first, on the same
// beat as belts (every other tick), so a block-wide screw carries as much as a block-deep belt
if ((tick & 1) === 0) for (let p = P_TU; p <= P_TR; p++) {
const list = tubeLists[p], dx = TDX[p], dy = TDY[p], dj = dy * W + dx, flat = dy === 0;
for (let k = 0; k < list.length; k++) {
const i = list[k];
if (part[i] !== p) continue;
const m = mat[i];
if (m === EMPTY || PH[m] !== PH_POWDER) { if (m === EMPTY || PH[m] !== PH_SOLID) tubeIntake(i, p, dx, dy); continue; }
if (stamp[i] === tickStamp) continue;
if (temp[i] > 1000 && rnd() < 0.02) {
part[i] = 0; markPow(i, false); emit({ t: 'warn', x: i % W, y: (i / W) | 0, text: 'melted' }); wakeIdx(i);
continue;
}
const s = netSat(i);
if (s <= 0 || rnd() > s) continue;
const x = i % W, y = (i / W) | 0;
// a sideways screw carries its load along the bottom: grains settle into empty lanes below
if (flat && y + 1 < H && part[i + W] === p && roomFor(i + W) && !occ[i + W]) { move(i, i + W, x, y, x, y + 1); continue; }
if (!flat) {
let own = 0, shifted = false;
const end = dy < 0 ? (y & ~3) : (y | 3);
for (let yy = y; dy < 0 ? yy >= end : yy <= end; yy += dy) if (PH[mat[yy * W + x]] === PH_POWDER) own++;
const side = ((tick >> 1) + i) & 1 ? 1 : -1;
for (let c = 0; c < 2; c++) {
const sx = c ? -side : side, xx = x + sx, j = i + sx;
if ((xx >> 2) !== (x >> 2) || part[j] !== p || !roomFor(j) || mat[j] !== EMPTY || occ[j] || stamp[j] === tickStamp) continue;
let load = 0;
for (let yy = y; dy < 0 ? yy >= end : yy <= end; yy += dy) if (PH[mat[yy * W + xx]] === PH_POWDER) load++;
// Include this grain: a strict gap prevents a sideways step being undone next beat.
if (own <= load + 1) continue;
move(i, j, x, y, xx, y); stats.moved++; shifted = true; break;
}
if (shifted) continue;
}
const nx = x + dx, ny = y + dy;
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
const j = i + dj;
if (occ[j]) continue;
const pj = part[j], tube = pj >= P_TU && pj <= P_TR;
if (tube && TDX[pj] === -dx && TDY[pj] === -dy) continue;
const t = mat[j];
if (!roomFor(j)) continue;
if (pj === P_GRATE || pj === P_INTAKE || (pj === P_FILTER && !filterPassAt(m, j))) continue;
if (!tube && !pj && t === EMPTY) {
// out of the mouth: the screw spits it clear, so the next grain is never blocked by the last
launchGrain(i, nx, ny, dy ? (rnd() - 0.5) * (dy < 0 ? 0.9 : 0.1) : dx * (0.42 + rnd() * 0.16), dy ? dy * (0.5 + rnd() * 0.2) : rnd() * 0.08);
stats.moved++; continue;
}
move(i, j, x, y, nx, ny); stats.moved++;
if (tube && pj !== p) {
// turning a corner: keep going across the new tube's width so every lane fills
tubeSpread(j, nx, ny, dx, dy);
}
}
}
}