Ingot molds empty themselves (no F); removing a mold returns its set metal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 22:53:38 -04:00
co-authored by Claude Opus 5.5
parent 9addc43be1
commit 43eb4e23fd
13 changed files with 146 additions and 43 deletions
+1 -1
View File
@@ -153,7 +153,7 @@ function drawMolds(hoverMold) {
ctx.strokeRect(sx - zp * 0.5, sy - zp * 0.5, sw + zp, sh + zp);
ctx.setLineDash([]);
if (ready || flawed || mo === hoverMold || mo.state === 'filling' || mo.state === 'short') {
const label = ready ? `${mo.kind === 'ingot' ? (mo.ready.length === 1 ? 'Bar' : `${mo.ready.length} bars`) : mo.kind === 'bit' ? 'Bit' : 'Tank'} set · F` : flawed ? 'Flawed casting' : mo.state === 'short' ? 'Needs more metal' : mo.state === 'filling' ? `Filling ${Math.round(mo.fill * 100)}%` : MOLDS[mo.kind].name;
const label = ready ? `${mo.kind === 'ingot' ? (mo.ready.length === 1 ? 'Bar' : `${mo.ready.length} bars`) : mo.kind === 'bit' ? 'Bit' : 'Tank'} set${mo.kind === 'ingot' ? '' : ' · F'}` : flawed ? 'Flawed casting' : mo.state === 'short' ? 'Needs more metal' : mo.state === 'filling' ? `Filling ${Math.round(mo.fill * 100)}%` : MOLDS[mo.kind].name;
ctx.fillStyle = 'rgba(14,12,11,0.75)';
const tw = ctx.measureText(label).width + zp * 2;
ctx.fillRect(sx + sw / 2 - tw / 2, sy - zp * 5.2, tw, zp * 4.2);
+1 -1
View File
@@ -73,7 +73,7 @@ function interactAt(x, y) {
const g = toggleGateAt(x, y);
if (g) { emit({ t: 'sfx', s: 'gate' }); return g === 'open' ? 'Gate opened.' : 'Gate closed.'; }
const mo = moldAt(x, y);
if (mo) { const r = collectMold(mo); if (!r.startsWith('New') && !r.startsWith('Knocked')) emit({ t: 'sfx', s: 'ui' }); return r; }
if (mo) { const r = collectMold(mo); if (mo.kind !== 'ingot' && !r.startsWith('New') && !r.startsWith('Knocked')) emit({ t: 'sfx', s: 'ui' }); return r; }
const pb = ((y / BS) | 0) * BW + ((x / BS) | 0);
if (blockP[pb] === BP_PUMP || blockP[pb] === BP_OUTLET) {
blockV[pb] ^= 4; emit({ t: 'sfx', s: 'ui' });
+46 -16
View File
@@ -1,4 +1,4 @@
// Molds: cavities for castings; ingot troughs drop bars out of the bottom.
// Molds: cavities for castings; ingot troughs empty themselves below. Removal returns set metal.
// ---------- Molds ----------
function moldCellsAt(kind, x, y) {
const md = MOLDS[kind]; const walls = [];
@@ -34,7 +34,7 @@ function addMold(kind, bx, by, wallLoose, free) {
return mo;
}
// Slag and ash that reach a mold float on the metal. They are chipped off and thrown
// away when the casting is knocked out, so they never spoil it unless metal set over them.
// away automatically in ingot troughs, or when other castings are knocked out.
const DROSS = new Uint8Array(NMAT); DROSS[SLAG] = 1; DROSS[MSLAG] = 1; DROSS[ASH] = 1;
// Two metals in one casting: iron with some steel in it (air got at it) casts as plain iron;
// anything else is a mix that spoils it. Returns the metal the casting counts as, or -1.
@@ -68,7 +68,7 @@ function cavityState(cav) {
return { st, fill: (solid + liq) / n, metal, flaw };
}
// The ingot trough: ready as soon as at least one bar's worth has set and nothing is still molten.
// Bars drop automatically; the short stub stays on the floor. F cleans out slag and grit.
// Bars, junk and mixed scrap drop automatically; slag is discarded. Short stubs stay on the floor.
function troughState(mo) {
let solid = 0, liq = 0, junk = 0, metal = -1, mixed = false;
for (const i of mo.cavs[0]) {
@@ -79,8 +79,8 @@ function troughState(mo) {
}
const bars = mixed ? 0 : Math.floor(solid / GRAINS_PER_BAR), n = mo.cavs[0].length;
const st = liq ? 'filling' : bars ? 'ready' : mixed ? 'flawed' : solid ? 'short' : junk ? 'flawed' : 'empty';
const flaw = st === 'flawed' ? (mixed ? 'Two different metals mixed in the ingot. Press F to knock it out as scrap.' : 'Only junk in the trough. Press F to knock it out.')
: st === 'short' ? `Only ${solid} grains set. A bar takes ${GRAINS_PER_BAR}. Fire again to top it up, or press F to knock it out as scrap.` : '';
const flaw = st === 'flawed' ? (mixed ? 'Two metals mixed. It drops out below as scrap.' : 'Junk in the trough. It drops out below when there is room.')
: st === 'short' ? `Only ${solid} grains set. A bar takes ${GRAINS_PER_BAR}; the next firing tops it up.` : '';
return { st, bars, fill: (solid + liq) / n, metal, flaw };
}
// Empties cells: metal goes to your bag (or beside you if the bag is full) as scrap, other loose
@@ -119,7 +119,7 @@ function moldPlugCells(mo) {
}
return out;
}
// Knocking a mold out also rakes the furnace over it: slag and ash lying on the grate and in
// Ingot ejection or knocking a casting out also rakes the furnace over it: slag and ash lying on the grate and in
// the charge above (up to the furnace roof) are cleared away, so the next charge sits on a
// clean grate. Unburnt coal and ore stay for the next firing. Returns how many grains it raked.
function rakeAbove(mo) {
@@ -184,6 +184,32 @@ function ejectIngot(mo) {
r = troughState(mo);
} else if (!free && !mo.blockedSaid) { mo.blockedSaid = true; emit({ t: 'ingot_blocked', mold: mo.id, x, y }); }
}
let molten = mo.cavs[0].some(i => CASTMETAL[mat[i]]), pushed = 0;
for (let yy = Math.max(0, mo.y - 8); yy < mo.y + mo.h && !molten; yy++) for (let xx = mo.x; xx < mo.x + mo.w; xx++) if (CASTMETAL[mat[yy * W + xx]]) { molten = true; break; }
if (!molten) {
const mixed = r.st === 'flawed' && r.metal >= 0;
const plug = moldPlugCells(mo), cavity = mo.cavs[0];
let slot = 0;
for (const i of cavity.concat(plug)) {
const m = mat[i];
if (m === EMPTY || PH[m] === PH_GAS || CASTMETAL[m] || (SOLIDMETAL[m] && !mixed && !plug.includes(i))) continue;
if (!DROSS[m]) {
const loose = SOLIDMETAL[m] ? (YLD[m] || SCRCU) : PH[m] === PH_SOLID ? YLD[m] : m;
if (!loose) continue;
let placed = false;
for (; slot < mo.w * Math.min(4, H - y); slot++) {
const dest = (y + ((slot / mo.w) | 0)) * W + mo.x + slot % mo.w;
if ((mat[dest] === EMPTY || PH[mat[dest]] === PH_GAS) && !occ[dest] && !part[dest]) {
setCell(dest, loose, temp[i], 0); slot++; placed = true; break;
}
}
if (!placed) continue;
}
setCell(i, EMPTY, AMB[(i / W) | 0], 0); pushed++;
}
}
if (n || pushed) rakeAbove(mo);
r = troughState(mo);
if (r.st === 'empty') mo.blockedSaid = false;
mo.ready = []; if (r.st === 'ready') for (let k = 0; k < r.bars; k++) mo.ready.push(k);
mo.state = r.st; mo.fill = r.fill; mo.metal = r.metal; mo.flaw = r.flaw;
@@ -231,6 +257,7 @@ function tally(got) {
return { scrap, other, dross: got.dross || 0 };
}
function collectMold(mo) {
if (mo.kind === 'ingot') return 'Ingot molds empty themselves.';
if (mo.state === 'filling') return 'The metal has not fully set yet.';
if (mo.state === 'empty') {
const g = tally(clearCavity(mo.cavs.flat())), u = unplugMold(mo);
@@ -240,16 +267,6 @@ function collectMold(mo) {
return 'Knocked the mold clean.' + knockNote(scrap, g.other, g.dross + u.chipped, u.raked);
}
let msg = '';
if (mo.kind === 'ingot' && mo.state === 'ready') {
const n = ejectIngot(mo);
return n ? `Dropped ${n} ${barWord(mo.metal, n)} out of the bottom of the mold.` : 'Bars drop out of the bottom of the mold. Leave one open block under it, with a belt below that to carry them off.';
}
if (mo.kind === 'ingot') {
const u = unplugMold(mo), g = tally(clearCavity(mo.cavs[0]));
mo.state = 'empty'; mo.ready = []; mo.blockedSaid = false;
emit({ t: 'sfx', s: 'ui' });
return 'Knocked the trough clean.' + knockNote(g.scrap + u.metal, g.other, g.dross + u.chipped, u.raked) + furnaceJunkHint(mo);
}
if (mo.state === 'short' && !(mo.knockAsk != null && tick - mo.knockAsk >= 0 && tick - mo.knockAsk < 300)) {
// an unfinished casting can still be topped up, so the first press only says so
mo.knockAsk = tick;
@@ -283,6 +300,19 @@ function collectMold(mo) {
}
function removeMold(mo) {
undoNote({ t: 'mold-', kind: mo.kind, bx: mo.x / BS, by: mo.y / BS, wall: mo.wall });
const cavity = mo.cavs.flat(), set = cavity.filter(i => SOLIDMETAL[mat[i]]);
let cast = -1, mixed = false;
for (const i of set) { if (cast < 0) cast = mat[i]; else { const c = castMix(cast, mat[i]); if (c < 0) mixed = true; else cast = c; } }
const cx = mo.x + mo.w / 2, cy = mo.y + mo.h / 2;
const bars = mixed ? 0 : Math.floor(set.length / GRAINS_PER_BAR);
const scrap = new Map();
for (let k = 0; k < set.length; k++) {
const i = set[k], m = mixed ? mat[i] : cast;
if (k >= bars * GRAINS_PER_BAR) { const loose = YLD[m] || SCRCU; scrap.set(loose, (scrap.get(loose) || 0) + 1); }
setCell(i, EMPTY, AMB[(i / W) | 0], 0);
}
if (bars) giveOrDrop(BAR_OF[cast], bars, cx, cy);
for (const [m, n] of scrap) giveOrDrop(m, n, cx, cy);
const { walls } = moldCellsAt(mo.kind, mo.x, mo.y);
let metal = 0;
for (const i of walls) {
+1 -1
View File
@@ -72,7 +72,7 @@ const PIECES = [
{ id: 'dock', name: 'Trade dock', kind: 'machine', bw: 6, bh: 4, cost: [], hidden: true, fixed: true,
desc: 'Owned by the claim office. Anything that drops through the grate in its floor is sold on the spot. Build walls around it to funnel more in.' },
{ id: 'ingotmold', name: 'Ingot mold', kind: 'mold', mold: 'ingot', variants: 'wall',
desc: 'A long trough. Pour molten metal in: bars of 8 grains drop out of the bottom when set, up to 3. Leave one open block below it; a belt under that carries them off. F cleans the trough, chips off slag and rakes the furnace grate.' },
desc: 'A long trough. Pour molten metal in: bars of 8 grains drop out of the bottom when set, up to 3. Leave one open block below it; a belt under that carries them off. Ingot molds empty themselves: bars, junk and scrap drop below, slag is thrown away and the grate above is raked. Removing any mold returns its set metal to your bag: whole bars as bars, the rest as scrap.' },
{ id: 'bitmold', name: 'Bit mold', kind: 'mold', mold: 'bit', variants: 'wall',
desc: 'A drill-bit-shaped cavity. Cast a harder metal in it to upgrade your drill.' },
{ id: 'tankmold', name: 'Tank mold', kind: 'mold', mold: 'tank', variants: 'wall',
+2 -2
View File
@@ -15,7 +15,7 @@ function smelterBlueprint(kind, count, ch, wall) {
}
const STARTER_DESIGNS = [
{ id: 'smelter1', name: 'Copper smelter', bp: () => smelterBlueprint('ingot', 1, 3, RSTONE),
desc: 'Stone furnace over an ingot mold. Charge it with about 50 copper ore and 50 coal, mixed (or the ore first with the coal on top), and light the coal. Makes 3 bars a firing. Bars drop out of the bottom of the mold onto whatever is under it; leave one open block under it, and a belt below that carries them off. Leftover copper stays for the next firing. F on the mold is optional: it cleans the trough, chips off slag and rakes the furnace grate.' },
desc: 'Stone furnace over an ingot mold. Charge it with about 50 copper ore and 50 coal, mixed (or the ore first with the coal on top), and light the coal. Makes 3 bars a firing. Bars drop out of the bottom of the mold onto whatever is under it; leave one open block under it, and a belt below that carries them off. Leftover copper stays for the next firing. Ingot molds empty themselves: bars, junk and scrap drop below, slag is thrown away and the grate above is raked. Removing any mold returns its set metal to your bag: whole bars as bars, the rest as scrap.' },
{ id: 'bitfurnace', name: 'Bit casting furnace', bp: () => smelterBlueprint('bit', 1, 3, RSTONE),
desc: 'Stone furnace over a bit mold. Charge it with about 70 copper ore and 70 coal, mixed (or the coal on top), and light the coal. Press F once the bit has set. For iron, build it from brick and blow air in with a fan.' },
];
@@ -28,7 +28,7 @@ const MILESTONES = [
{ id: 'charcoal', title: 'Charcoal', hint: 'Wood that gets hot without air turns into charcoal instead of burning. Bury a burning pile of wood chunks under dirt, or heat a sealed box of wood chunks from outside.', reward: 'A fuel you can grow: nearly as hot as coal.' },
{ id: 'grow', title: 'Plant a tree', msg: 'Wood is renewable. Plant rows of acorns on dirt to farm it.', hint: 'Cutting down a tree drops acorns. Leave one on dirt or grass under open sky and it sprouts, then grows into a tree in about a minute and a half.' },
{ id: 'copper', title: 'Molten copper', msg: 'Molten copper sinks to the bottom of the charge. Get it into a mold before it sets.', hint: 'Copper ore melts at 1085°C. An open fire loses too much heat, so wall coal and ore in with blocks and leave a small vent at the top for smoke. Mix ore and coal about half and half: tick both in your bag and pick Ticked mix with Q/E, and right click blows an even mix.' },
{ id: 'bars', title: 'Copper bars', msg: 'Bars sell for far more than the ore in them, and make metal wall blocks.', hint: 'Build an Ingot mold (Build, 3) right under the furnace with a floor of clay Grates between them: molten copper drips through, while the charge and the thick slag stay up. When the bars set, they drop out of the bottom of the mold onto whatever is under it; a belt carries them off. Leave space below, or vacuum up the bars. F on the mold is optional: it cleans the trough, chips off slag and rakes the furnace grate. Copper that sets in the furnace melts again next firing.' },
{ id: 'bars', title: 'Copper bars', msg: 'Bars sell for far more than the ore in them, and make metal wall blocks.', hint: 'Build an Ingot mold (Build, 3) right under the furnace with a floor of clay Grates between them: molten copper drips through, while the charge and the thick slag stay up. When the bars set, they drop out of the bottom of the mold onto whatever is under it; a belt carries them off. Leave space below, or vacuum up the bars. Ingot molds empty themselves: bars, junk and scrap drop below, slag is thrown away and the grate above is raked. Removing any mold returns its set metal to your bag: whole bars as bars, the rest as scrap. Copper that sets in the furnace melts again next firing.' },
{ id: 'bit1', title: 'Copper drill bit', hint: 'Cast copper in a Bit mold. Building the furnace right on top works. A Grate over the funnel keeps the charge out. Let it set, then press F.', reward: 'Copper bit. You can now dig iron ore.' },
{ id: 'auto', title: 'Moving parts', msg: 'Machines can now haul for you. Feed furnaces automatically, and lift ore out of your mines.', hint: 'Belts carry grains and bars resting on them. Screw tubes push loose material along (never liquids); stacked pointing up they lift it out of a mine. A Filter lets one material fall through and carries the rest along, so you can sort ore from rubble. Move 300 grains with machines.' },
{ id: 'glass', title: 'Glass', hint: 'Sand melts near 1300°C. Still air will not get a fire that hot: point a Fan into the fuel. Molten glass runs through a grate like metal, so cast it in an Ingot mold for glass bricks, which sell well.', reward: 'Flask unlocked. The Vacuum can suck up and blow out liquids.' },
+1 -1
View File
@@ -123,7 +123,7 @@ function hoverInfo(ax, ay) {
const mo = moldAt(ax, ay);
if (mo) {
t1 = MOLDS[mo.kind].name;
t2 = [mo.state === 'ready' ? (mo.kind === 'ingot' ? `${mo.ready.length} ${mo.ready.length === 1 ? 'bar' : 'bars'} set · leave space below` : `${MD[mo.metal].name} casting set · press F`) : mo.state === 'flawed' || mo.state === 'short' ? mo.flaw : mo.state === 'filling' ? `filling ${Math.round(mo.fill * 100)}%` : 'empty'];
t2 = [mo.state === 'ready' ? (mo.kind === 'ingot' ? `${mo.ready.length} ${mo.ready.length === 1 ? 'bar' : 'bars'} set · drop when there is room below` : `${MD[mo.metal].name} casting set · press F`) : mo.state === 'flawed' || mo.state === 'short' ? mo.flaw : mo.state === 'filling' ? `filling ${Math.round(mo.fill * 100)}%` : 'empty'];
if (player.lens) t2.push(fmtT(temp[i]));
}
const reachOk = toolId() === 'drill' ? inDrillReach(ax, ay, GAME.drillW) : inReach(ax, ay);
+1 -1
View File
@@ -194,7 +194,7 @@ function controlsHtml() {
2. With the Vacuum, click on coal to suck up only coal, then the copper ore, rubble and clay you want.<br>
3. Build (3) a chamber of stone wall blocks with a one-block vent in the roof, a floor of clay Grates, and an Ingot mold right under the grates. The notebook's Blueprints tab has this as a starter design.<br>
4. Tick coal and copper ore in your bag and pick Ticked mix (Q/E, or click it at the bottom of the bag). Fly inside (the bot passes through what you build) and right click to blow an even mix onto the grates: about 50 of each. (Ore first and coal on top works too; coal buried under ore will not light.) Light a coal grain on top with the Igniter.<br>
5. When the ingot sets, bars drop out of the bottom of the mold onto whatever is under it. Leave one open block under it: a belt below that carries them off, or vacuum them up. Leftover metal stays for the next firing. F on the mold is optional: it cleans the trough, chips off slag and rakes the furnace grate. Scrap goes to your bag: throw it in with the next charge.<br>
5. When the ingot sets, bars drop out of the bottom of the mold onto whatever is under it. Leave one open block under it: a belt below that carries them off, or vacuum them up. Leftover metal stays for the next firing. Ingot molds empty themselves: bars, junk and scrap drop below, slag is thrown away and the grate above is raked. Removing any mold returns its set metal to your bag: whole bars as bars, the rest as scrap.<br>
6. Blow the bars into the trade dock: they sell the moment they drop through its grate, and bars pay far more than ore. Watch the Market page for orders that pay double. Money buys belts, screw tubes and fans; pay down the claim there too.<br>
7. From there: deeper ores (tin, zinc, silver, gold), alloys, fans for hotter fires, iron and steel.</p>
`;