Ingot molds drop bars out of the bottom on their own
Bars no longer get thrown up into the furnace, and F is no longer needed to collect them. Blocked bars wait in the trough; the short stub stays for the next firing; F is optional upkeep (clean, chip slag, rake). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -65,8 +65,11 @@ for (let f = 0; f < firings; f++) {
|
||||
} else ticks(run, 5400);
|
||||
const st = run(`JSON.stringify(molds.map(m => m.state + (m.flaw ? ':' + m.flaw : '')))`);
|
||||
if (process.env.DUMP === 'each') { console.log('-- before knock-out, firing', f + 1, st); dump(); }
|
||||
// Vacuum earlier drops, open the outlet, then do the optional F upkeep.
|
||||
let bars = run('ents.length');
|
||||
run('for (const e of ents.slice()) removeEnt(e); for (const mo of molds) if (mo.kind === "ingot") for (let y = mo.y + mo.h; y < mo.y + mo.h + 2; y++) for (let x = mo.x; x < mo.x + mo.w; x++) setCell(y * W + x, EMPTY, AMB[y], 0); checkMolds();');
|
||||
bars += run('ents.length'); totalBars += bars;
|
||||
const msgs = JSON.parse(run(`JSON.stringify(molds.map(m => collectMold(m)))`));
|
||||
const bars = run('ents.length'); totalBars += bars;
|
||||
if (run('player.bitTier') > bits) bits = run('player.bitTier');
|
||||
run('for (const e of ents.slice()) removeEnt(e);');
|
||||
const left = { ore: count(run('CUORE'), false), coal: count(run('COAL'), false), slag: count(run('SLAG'), false), ash: count(run('ASH'), false), copperOut: count(run('COPPER'), false) + count(run('SCRCU'), false) };
|
||||
|
||||
+5
-7
@@ -254,21 +254,19 @@ with sync_playwright() as p:
|
||||
check('bot can sit inside a built wall', not ev(f'botOverlapSolid({wx_ - 2}, {wy_ - 2})'))
|
||||
check('bot cannot sit inside natural rock', ev(f'botOverlapSolid({bx}, {gy + 12})'))
|
||||
|
||||
# 6. ingot mold: click to place, pour, knock out bars, vacuum them
|
||||
# 6. ingot mold: click to place, pour, bars drop below, vacuum them
|
||||
ok = select('3', "piece().id === 'ingotmold'")
|
||||
check('E cycles to the ingot mold', ok)
|
||||
spot = ev(f'''(() => {{ for (let dx = -2; dx <= 10; dx++) for (let dy = 6; dy < 14; dy++) {{ const ox = {bbx} + dx, oy = {bby} - dy; if (!canPlaceMold('ingot', ox - 1, oy) && inReach(ox * 4 + 8, oy * 4 + 2)) return [ox, oy]; }} return null; }})()''')
|
||||
spot = ev(f'''(() => {{ for (let dx = -2; dx <= 10; dx++) for (let dy = 6; dy < 14; dy++) {{ const ox = {bbx} + dx, oy = {bby} - dy; if (!canPlaceMold('ingot', ox - 1, oy) && [0, 1, 2].every(k => entFreeAt((ox - 1) * 4 + 1 + k * 4, oy * 4 + MOLDS.ingot.h)) && inReach(ox * 4 + 8, oy * 4 + 2)) return [ox, oy]; }} return null; }})()''')
|
||||
check('found a spot for the mold', spot is not None)
|
||||
mx, my = blk(spot[0], spot[1])
|
||||
pg.mouse.move(mx, my); settle(100); pg.mouse.down(); pg.mouse.up(); settle(100)
|
||||
check('mold placed by click', ev('molds.length') == 1, ev('GAME.status'))
|
||||
ev('for (const cav of molds[0].cavs) for (const i of cav) setCell(i, MCU, 1160, 0)')
|
||||
pg.wait_for_function("molds[0].state === 'ready'", timeout=30000)
|
||||
check('bars set in the mold', ev("molds[0].state === 'ready' && molds[0].ready.length === 3"), ev('[molds[0].state, molds[0].ready.length]'))
|
||||
pg.wait_for_function("ents.filter(e => e.m === BARCU).length === 3", timeout=30000)
|
||||
check('bars set and drop on their own', ev("ents.length === 3 && molds[0].state === 'short' && molds[0].ready.length === 0"), ev('[molds[0].state, ents.length]'))
|
||||
pg.screenshot(path=str(out / '05-mold-ready.png'))
|
||||
mo = ev('[molds[0].x + 8, molds[0].y + 2]')
|
||||
pg.mouse.move(*cell_to_screen(*mo)); settle(60); pg.keyboard.press('f'); settle(300)
|
||||
check('F knocks out three bars', ev('ents.length') == 3, ev('GAME.status'))
|
||||
check('three bars drop below the mold without F', ev('ents.every(e => e.m === BARCU && e.iy >= molds[0].y + molds[0].h)'), ev('ents.map(e => [e.ix, e.iy])'))
|
||||
settle(1500)
|
||||
pg.screenshot(path=str(out / '06-bars.png'))
|
||||
pg.keyboard.press('2'); settle(60)
|
||||
|
||||
+3
-4
@@ -44,15 +44,14 @@ check('stone wall line placed', JSON.parse(r1).n === 5 && run('bag[RSTONE]') ===
|
||||
check('wall cells are solid stone at grid positions', run('mat[80*W+100]') === run('STONE') && run('mat[83*W+119]') === run('STONE'));
|
||||
const rm = run("removePieceAt(101, 81)");
|
||||
check('taking a wall block back refunds its rubble', run('bag[RSTONE]') === 200 - 64, rm + ' ' + run('bag[RSTONE]'));
|
||||
// 5. ingot mold: pour molten copper, knock out bars
|
||||
// 5. ingot mold: pour molten copper, bars drop below
|
||||
run('bag[RSTONE] = 200; bagTotal = 0; for (let k=0;k<NMAT;k++) bagTotal += bag[k];');
|
||||
const mo = run("placeMoldAt('ingot', 32, 22, RSTONE)");
|
||||
check('ingot mold placed', mo === '' && run('molds.length') === 1, mo);
|
||||
run('for (const cav of molds[0].cavs) for (const i of cav) setCell(i, MCU, 1160, 0)');
|
||||
ticks(run, 900);
|
||||
check('ingot mold sets', run('molds[0].state') === 'ready' && run('molds[0].ready.length') === 3, run('molds[0].state') + ' ' + run('molds[0].ready.length'));
|
||||
const msg = run('collectMold(molds[0])');
|
||||
check('knock-out spawns 3 bars', run('ents.length') === 3, msg);
|
||||
check('ingot mold sets and drops three bars', run('ents.length === 3 && molds[0].state === "short"'), run('molds[0].state'));
|
||||
check('bars spawn below the mold', run('ents.every(e => e.m === BARCU && e.iy >= molds[0].y + molds[0].h)'));
|
||||
ticks(run, 200);
|
||||
check('bars fall and come to rest', run('ents.every(e => Math.abs(e.vy) < 0.01)'), run('JSON.stringify(ents.map(e=>[e.ix,e.iy]))'));
|
||||
check('bars count as built-up copper', run('stats.bars') === 3);
|
||||
|
||||
+35
-9
@@ -1,4 +1,4 @@
|
||||
// Molds and furnaces stay usable: knock-outs leave the mold clean, plugs over the mold are
|
||||
// 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.
|
||||
const { load, suite, flatWorld, rect, ticks, count } = require('./harness');
|
||||
@@ -7,13 +7,39 @@ const { check, done } = suite();
|
||||
function fresh() { flatWorld(run, 240, 160, 100); run('bot.x = 110; bot.y = 70; bag.fill(0); bagTotal = 0; for (const e of ents.slice()) removeEnt(e);'); }
|
||||
const cavCount = (k, m) => run(`molds[${k}].cavs[0].filter(i => mat[i] === ${m}).length`);
|
||||
|
||||
// an ingot with 27 grains of copper: 3 bars, and the 3 left over go to the bag, not back in the trough
|
||||
// automatic drops leave the short stub on the trough floor for the next firing
|
||||
fresh();
|
||||
run('bag[RSTONE] = 100; bagTotal = 100; placeMoldAt("ingot", 30, 22, RSTONE); bag[RSTONE] = 0; bagTotal = 0;');
|
||||
run('{ const c = molds[0].cavs[0].slice().sort((a, b) => b - a); for (let k = 0; k < 27; k++) { setCell(c[k], COPPER, 30, F_CAST); } } checkMolds();');
|
||||
let msg = run('collectMold(molds[0])');
|
||||
check('knocking out an ingot gives the bars', run('ents.length') === 3 && run('ents.every(e => e.m === BARCU)'), msg);
|
||||
check('and leaves the trough empty, the scrap in your bag', run('molds[0].cavs[0].every(i => mat[i] === EMPTY)') && run('bag[SCRCU]') === 3, `scrap in bag ${run('bag[SCRCU]')}`);
|
||||
run('bag[RSTONE] = 100; bagTotal = 100; placeMoldAt("ingot", 30, 22, RSTONE); bag[RSTONE] = 0; bagTotal = 0; EV.length = 0;');
|
||||
run('{ const c = molds[0].cavs[0].slice().sort((a, b) => b - a); for (let k = 0; k < 27; k++) setCell(c[k], COPPER, 30, F_CAST); } checkMolds();');
|
||||
let msg;
|
||||
check('an ingot drops three copper bars below the mold', run('ents.length === 3 && ents.every(e => e.m === BARCU && e.iy >= molds[0].y + molds[0].h && e.vy > 0 && e.T === 30)'));
|
||||
check('three grains stay on the trough floor, nothing goes to the bag', cavCount(0, run('COPPER')) === 3 && run('bagTotal === 0 && molds[0].cavs[0].filter(i => mat[i] === COPPER).every(i => (i / W | 0) === molds[0].y + molds[0].h - 2)'));
|
||||
check('ingots report drops instead of casting ready', run('EV.filter(e => e.t === "ingot_drop").length === 1 && !EV.some(e => e.t === "cast_ready") && stats.bars === 3 && stats.cast === 1'));
|
||||
run('for (const e of ents.slice()) removeEnt(e); { const c = molds[0].cavs[0].filter(i => mat[i] === EMPTY).sort((a, b) => b - a); for (let k = 0; k < 5; k++) setCell(c[k], COPPER, 30, F_CAST); } checkMolds();');
|
||||
check('five more grains top up the stub into another bar', run('ents.length === 1 && ents[0].m === BARCU && molds[0].state === "empty"') && cavCount(0, run('COPPER')) === 0);
|
||||
// blocked mouths retain their metal and warn once, including molds from old saves
|
||||
fresh();
|
||||
run('bag[RSTONE] = 100; bagTotal = 100; placeMoldAt("ingot", 30, 22, RSTONE); bag[RSTONE] = 0; bagTotal = 0; EV.length = 0; { const mo = molds[0]; for (const i of mo.cavs[0]) setCell(i, COPPER, 30, F_CAST); for (let y = mo.y + mo.h; y < mo.y + mo.h + 2; y++) for (let x = mo.x; x < mo.x + mo.w; x++) setCell(y * W + x, STONE, 30, 0); } checkMolds(); checkMolds();');
|
||||
check('blocked bars stay put and warn only once', run('ents.length === 0 && molds[0].state === "ready" && EV.filter(e => e.t === "ingot_blocked").length === 1') && cavCount(0, run('COPPER')) === 28);
|
||||
msg = run('collectMold(molds[0])');
|
||||
check('F on a blocked mold never launches bars upwards', run('ents.length === 0') && /open block under/.test(msg), msg);
|
||||
run('{ const mo = molds[0]; for (let y = mo.y + mo.h; y < mo.y + mo.h + 2; y++) for (let x = mo.x; x < mo.x + mo.w; x++) setCell(y * W + x, EMPTY, 30, 0); }');
|
||||
msg = run('collectMold(molds[0])');
|
||||
check('F on a ready mold drops bars only below it', run('ents.length === 3 && ents.every(e => e.iy === molds[0].y + molds[0].h) && !molds[0].blockedSaid') && /Dropped 3 bars/.test(msg), msg);
|
||||
run('for (const e of ents.slice()) removeEnt(e); { const mo = molds[0]; for (const i of mo.cavs[0]) setCell(i, COPPER, 30, F_CAST); for (let x = mo.x; x < mo.x + mo.w; x++) setCell((mo.y + mo.h) * W + x, STONE, 30, 0); } checkMolds();');
|
||||
check('a new blocked spell warns again', run('EV.filter(e => e.t === "ingot_blocked").length === 2'));
|
||||
run('{ const mo = molds[0]; for (let x = mo.x; x < mo.x + mo.w; x++) setCell((mo.y + mo.h) * W + x, EMPTY, 30, 0); } checkMolds();');
|
||||
check('clearing the obstruction lets the next check drop bars', run('ents.length === 3 && !molds[0].blockedSaid') && cavCount(0, run('COPPER')) === 4);
|
||||
// one free mouth drops one bar; occupied mouths keep the rest for later
|
||||
fresh();
|
||||
run('bag[RSTONE] = 100; bagTotal = 100; placeMoldAt("ingot", 30, 22, RSTONE); bag[RSTONE] = 0; bagTotal = 0; { const mo = molds[0]; for (const i of mo.cavs[0]) setCell(i, COPPER, 30, F_CAST); spawnBar(BARCU, mo.x + 1, mo.y + mo.h); for (let x = mo.x + 9; x < mo.x + 13; x++) setCell((mo.y + mo.h) * W + x, STONE, 30, 0); } checkMolds();');
|
||||
check('partial ejection uses the free middle mouth and retains two bars', run('ents.length === 2 && ents[1].ix === molds[0].x + 5 && ents[1].iy === molds[0].y + molds[0].h && molds[0].ready.length === 2') && cavCount(0, run('COPPER')) === 20);
|
||||
run('for (const e of ents.slice()) removeEnt(e); { const mo = molds[0]; for (let x = mo.x + 9; x < mo.x + 13; x++) setCell((mo.y + mo.h) * W + x, EMPTY, 30, 0); } checkMolds();');
|
||||
check('remaining bars drop when mouths open', run('ents.length === 2 && molds[0].state === "short"') && cavCount(0, run('COPPER')) === 4);
|
||||
// a full loose-object pool must not consume the casting or put bars in the bag
|
||||
fresh();
|
||||
run('bag[RSTONE] = 100; bagTotal = 100; placeMoldAt("ingot", 30, 22, RSTONE); bag[RSTONE] = 0; bagTotal = 0; for (const i of molds[0].cavs[0]) setCell(i, COPPER, 30, F_CAST); ents.length = 4000; checkMolds(); ents.length = 0;');
|
||||
check('failed spawns preserve all metal and leave the bag alone', cavCount(0, run('COPPER')) === 28 && run('bagTotal === 0 && molds[0].ready.length === 3'));
|
||||
// a plate of copper set over the trough, and drips frozen in the grate above: knocked loose too
|
||||
fresh();
|
||||
run('bag[RSTONE] = 100; bagTotal = 100; placeMoldAt("ingot", 30, 22, RSTONE); bag[RSTONE] = 0; bagTotal = 0;');
|
||||
@@ -27,9 +53,9 @@ check('the grate itself is still there', run('part[(molds[0].y - 1) * W + molds[
|
||||
fresh();
|
||||
run('bag[RSTONE] = 100; bagTotal = 100; placeMoldAt("ingot", 30, 22, RSTONE); bag[RSTONE] = 0; bagTotal = 0;');
|
||||
run('{ const c = molds[0].cavs[0].slice().sort((a, b) => b - a); for (let k = 0; k < 16; k++) setCell(c[k], COPPER, 30, F_CAST); for (let k = 16; k < 20; k++) setCell(c[k], SLAG, 30, 0); } checkMolds();');
|
||||
check('an ingot with slag on top is still ready', run('molds[0].state') === 'ready', run('molds[0].state'));
|
||||
check('slag on top does not stop automatic bars', run('ents.length === 2 && ents.every(e => e.iy >= molds[0].y + molds[0].h)') && cavCount(0, run('SLAG')) === 4 && run('bagTotal === 0'));
|
||||
msg = run('collectMold(molds[0])');
|
||||
check('the slag is chipped off and thrown away, and two bars come out', run('ents.length') === 2 && run('bag[SLAG]') === 0 && /chipped off/.test(msg) && run('molds[0].cavs[0].every(i => mat[i] === EMPTY)'), msg);
|
||||
check('optional F chips the slag off without putting it in the bag', run('bag[SLAG] === 0 && molds[0].cavs[0].every(i => mat[i] === EMPTY)') && /chipped off/.test(msg), msg);
|
||||
// a bit casting with slag in it is spoiled, but F knocks it out instead of asking you to drill
|
||||
fresh();
|
||||
run('bag[RSTONE] = 200; bagTotal = 200; placeMoldAt("bit", 30, 20, RSTONE); bag[RSTONE] = 0; bagTotal = 0;');
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ let peak = 0;
|
||||
for (let t = 0; t < 6; t++) { run('for (let k = 0; k < 600; k++) simTick();'); peak = Math.max(peak, run(`(() => { let p = 0; for (let y = ${cy0}; y <= ${cy1}; y++) for (let x = ${cx0}; x <= ${cx1}; x++) p = Math.max(p, temp[y * W + x]); return Math.round(p); })()`)); }
|
||||
const st = run('molds[0].state'), glassIn = run('molds[0].cavs.flat().filter(i => mat[i] === GLASS).length');
|
||||
check('a fan furnace melts the sand and the molten glass runs into the ingot mold', glassIn >= 8, `peak ${peak}°C, ${sand0} sand -> ${count(run, run('SAND'))}, ${glassIn} glass in the mold, state ${st}`);
|
||||
const msg = run('collectMold(molds[0])');
|
||||
check('knocking it out gives glass bricks', bars(run('BARGL')) >= 2, `${bars(run('BARGL'))} bricks: ${msg}`);
|
||||
run('{ const mo = molds[0]; for (let y = mo.y + mo.h; y < mo.y + mo.h + 2; y++) for (let x = mo.x; x < mo.x + mo.w; x++) setCell(y * W + x, EMPTY, AMB[y], 0); } checkMolds();');
|
||||
check('opening space below drops glass bricks', bars(run('BARGL')) >= 2 && run('ents.every(e => e.iy >= molds[0].y + molds[0].h)'), `${bars(run('BARGL'))} bricks`);
|
||||
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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user