Fewer, smaller messages: mold percentages, markers for machine heat, short status lines
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+19
-2
@@ -25,6 +25,7 @@ with sync_playwright() as p:
|
||||
pg.wait_for_function('GAME.running === true', timeout=20000)
|
||||
pg.wait_for_timeout(500)
|
||||
ev = pg.evaluate
|
||||
ev("window._maxStatus = ''; const _ss = setStatus; setStatus = function (s, k) { if (s && s.length > _maxStatus.length) _maxStatus = s; return _ss(s, k); }; 0")
|
||||
look = sys.argv[2] if len(sys.argv) > 2 else 'pixel'
|
||||
check('a new game starts fully zoomed in', ev('SET.zoom') == 12, ev('SET.zoom'))
|
||||
# Ctrl+W closes the tab; with a world open the browser must ask first
|
||||
@@ -286,6 +287,8 @@ with sync_playwright() as p:
|
||||
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("ents.filter(e => e.m === BARCU).length === 3", timeout=30000)
|
||||
check('mold label shows a percentage', ev(r'/^\d+%/.test(moldLabel(molds[0]))'))
|
||||
check('bar drops make no toast', ev("![...$('toasts').children].some(e => /dropped/i.test(e.textContent))"))
|
||||
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'))
|
||||
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])'))
|
||||
@@ -297,6 +300,19 @@ with sync_playwright() as p:
|
||||
hold(*cell_to_screen(*e0), 900)
|
||||
check('vacuum picks up bars', ev('bag[BARCU]') >= 1, ev('bag[BARCU]'))
|
||||
|
||||
check('casting events stay quiet and damage labels are red', ev("""(() => {
|
||||
const oldToast = toast, oldCard = showCard, oldSfx = playSfx, events = EV.splice(0), floaters = R.floaters;
|
||||
let popups = 0, casts = 0;
|
||||
toast = showCard = () => popups++; playSfx = s => { if (s === 'cast') casts++; };
|
||||
R.floaters = [];
|
||||
try {
|
||||
emit({ t: 'ingot_drop', bars: 1, metal: COPPER, first: true });
|
||||
emit({ t: 'ingot_blocked' }); emit({ t: 'cast_ready', kind: 'bit', metal: COPPER });
|
||||
emit({ t: 'warn', x: 12, y: 34, text: 'melted' }); drainEvents();
|
||||
return popups === 0 && casts === 1 && R.floaters.length === 1 && R.floaters[0].colour === '#ff8a72' && R.floaters[0].text === 'melted';
|
||||
} finally { toast = oldToast; showCard = oldCard; playSfx = oldSfx; R.floaters = floaters; EV.push(...events); }
|
||||
})()"""))
|
||||
|
||||
# 7. belt line by dragging, copied with a blueprint and pasted
|
||||
refill('bag[BARCU] = 60'); ev('money = 1000')
|
||||
ok = select('3', "piece().id === 'belt'")
|
||||
@@ -344,7 +360,7 @@ with sync_playwright() as p:
|
||||
ok = select('3', "piece().id === 'fan'")
|
||||
pg.mouse.move(*blk(bbx - 4, bby + 1)); settle(400)
|
||||
pg.mouse.down(); pg.mouse.up(); settle(100)
|
||||
check('a fan on a belt says to hold G', 'Hold G' in ev('GAME.status'), ev('GAME.status'))
|
||||
check('a fan on a belt says to hold G', 'hold G' in ev('GAME.status'), ev('GAME.status'))
|
||||
pg.keyboard.down('g'); settle(100); pg.mouse.move(*blk(bbx - 4, bby + 1)); settle(200)
|
||||
check('holding G shows replace mode', 'Replace · G' in ev("$('selcard').textContent"))
|
||||
pg.screenshot(path=str(out / '07g-replace-fan.png'))
|
||||
@@ -384,7 +400,7 @@ with sync_playwright() as p:
|
||||
# two belts at the end of the line: plain dragging leaves them, G-dragging swaps them for filters
|
||||
ev(f"placeBlock(BP_BELT, 1, {bbx-2}, {fy}, true); placeBlock(BP_BELT, 1, {bbx-1}, {fy}, true);")
|
||||
drag(lambda: blk(bbx - 2, fy), lambda: blk(bbx - 1, fy))
|
||||
check('dragging over built belts leaves them and says how to replace', ev(f'blockP[{fy}*BW+{bbx-2}] === BP_BELT') and 'Hold G' in ev('GAME.status'), ev('GAME.status'))
|
||||
check('dragging over built belts leaves them and says how to replace', ev(f'blockP[{fy}*BW+{bbx-2}] === BP_BELT') and 'hold G' in ev('GAME.status'), ev('GAME.status'))
|
||||
pg.keyboard.down('g'); settle(50)
|
||||
pg.mouse.move(*blk(bbx - 2, fy)); settle(300)
|
||||
pg.screenshot(path=str(out / '07c-replace-preview.png'))
|
||||
@@ -527,6 +543,7 @@ with sync_playwright() as p:
|
||||
pg2.screenshot(path=str(out / '11-deep-magma.png'))
|
||||
pg2.close()
|
||||
print('\n'.join(logs) or 'no console errors')
|
||||
check('every status line is short (≤ 90 characters)', len(ev('_maxStatus')) <= 90, ev('_maxStatus'))
|
||||
print('FAILS:', fails)
|
||||
b.close()
|
||||
sys.exit(1 if fails else 0)
|
||||
|
||||
+4
-2
@@ -68,8 +68,10 @@ check('undo clears the ghosts and takes back what was built from them', belts()
|
||||
fresh();
|
||||
run("placeMachineAt('fan', 0, 22, 20); EV.length = 0;");
|
||||
run('for (let y = 80; y < 84; y++) for (let x = 88; x < 92; x++) temp[y * W + x] = 900; for (let k = 0; k < 40; k++) { for (let y = 80; y < 84; y++) for (let x = 88; x < 92; x++) temp[y * W + x] = 900; simTick(); }');
|
||||
const warn = run("EV.filter(e => e.t === 'toast' && /fan/i.test(e.text)).map(e => e.text).join(' | ')");
|
||||
check('a fan at 900°C warns that it is about to melt', /1085/.test(warn) && fans() === 1, warn);
|
||||
check('a hot fan stores its temperature without a toast', run("machines.some(m => m && m.type === 'fan' && m.hot > 760) && !EV.some(e => e.t === 'toast')"));
|
||||
|
||||
run("EV.length = 0; removeMachine(machines.find(m => m && m.type === 'fan').id, false, true);");
|
||||
check('heat destruction emits a local warning', run("EV.some(e => e.t === 'warn' && e.text === 'Fan destroyed' && Number.isFinite(e.x) && Number.isFinite(e.y)) && !EV.some(e => e.t === 'toast')"));
|
||||
|
||||
// old saves: tiny specks of too-hard rock become the rock around them, once
|
||||
fresh();
|
||||
|
||||
+13
-2
@@ -5,6 +5,7 @@ const { load, suite, flatWorld, rect, ticks, count } = require('./harness');
|
||||
const { run } = load();
|
||||
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);'); }
|
||||
run("var moldMessages = []; const collectOriginal = collectMold; collectMold = function(mo) { const s = collectOriginal(mo); moldMessages.push(s); return s; }; 0");
|
||||
const cavCount = (k, m) => run(`molds[${k}].cavs[0].filter(i => mat[i] === ${m}).length`);
|
||||
|
||||
// automatic drops leave the short stub on the trough floor for the next firing
|
||||
@@ -20,11 +21,13 @@ check('five more grains top up the stub into another bar', run('ents.length ===
|
||||
// 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 have a warning flag', run('molds[0].blocked === true'));
|
||||
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 ingot mold changes nothing', run('ents.length === 0') && cavCount(0, run('COPPER')) === 28 && /empty themselves/.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); }');
|
||||
run('checkMolds()');
|
||||
check('dropping bars clears the warning flag', run('molds[0].blocked === false'));
|
||||
check('the next check drops bars only below it', run('ents.length === 3 && ents.every(e => e.iy === molds[0].y + molds[0].h) && !molds[0].blockedSaid'));
|
||||
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'));
|
||||
@@ -145,9 +148,9 @@ run('molds[0].cavs[0].forEach((i, k) => setCell(i, k & 1 ? COPPER : IRON, 30, F_
|
||||
check('mixed metal all drops as scrap without bars', under('SCRCU') === 14 && under('SCRFE') === 14 && run('ents.length === 0 && molds[0].state === "empty" && bagTotal === 0'));
|
||||
ingot();
|
||||
run('setCell(molds[0].cavs[0][0], SAND, 30, 0); for (let y = 92; y < 96; y++) for (let x = 120; x < 136; x++) setCell(y * W + x, STONE, 30, 0); checkMolds();');
|
||||
check('blocked junk stays in the trough', cavCount(0, run('SAND')) === 1 && under('SAND') === 0);
|
||||
check('blocked junk stays in the trough', cavCount(0, run('SAND')) === 1 && under('SAND') === 0 && run('molds[0].blocked === true'));
|
||||
run('for (let y = 92; y < 96; y++) for (let x = 120; x < 136; x++) setCell(y * W + x, EMPTY, 30, 0); checkMolds();');
|
||||
check('junk drops once there is room', cavCount(0, run('SAND')) === 0 && under('SAND') === 1);
|
||||
check('junk drops once there is room', cavCount(0, run('SAND')) === 0 && under('SAND') === 1 && run('molds[0].blocked === false'));
|
||||
ingot();
|
||||
run('setCell(molds[0].cavs[0][0], SAND, 30, 0); setCell(molds[0].cavs[0][1], MCU, 1200, 0); checkMolds();');
|
||||
check('cleaning waits for molten metal in the cavity', cavCount(0, run('SAND')) === 1 && under('SAND') === 0);
|
||||
@@ -184,4 +187,12 @@ check('metal wall refund does not count cavity metal twice', run('bag[BARCU] ===
|
||||
fresh();
|
||||
run('addMold("bit", 30, 20, RSTONE, true); molds[0].cavs[0].slice(0, 20).forEach((i, k) => setCell(i, k & 1 ? IRON : STEEL, 30, F_CAST)); removeMold(molds[0]);');
|
||||
check('compatible iron and steel return as iron bars and scrap', run('bag[BARFE] === 2 && bag[SCRFE] === 4 && bag[SCRST] === 0'));
|
||||
for (const kind of ['bit', 'tank']) for (const metal of ['COPPER', 'IRON', 'GLASS']) for (const upgraded of [false, true]) {
|
||||
fresh();
|
||||
run(`addMold("${kind}", 30, 20, RSTONE, true); player.bitTier = player.tankTier = ${upgraded ? 3 : 0}; molds[0].cavs[0].forEach(i => setCell(i, ${metal}, 30, F_CAST)); checkMolds(); collectMold(molds[0]);`);
|
||||
}
|
||||
for (const state of ['empty', 'filling']) {
|
||||
fresh(); run(`addMold("bit", 30, 20, RSTONE, true); molds[0].state = "${state}"; collectMold(molds[0]);`);
|
||||
}
|
||||
check('every collected mold message is short', run('moldMessages.every(s => s.length <= 90)'), run('moldMessages.join(" | ")'));
|
||||
done();
|
||||
|
||||
+1
-1
@@ -102,7 +102,7 @@ run('player.tankTier = 0; bag[RSTONE] = 16; bag[LDIRT] = 1500 - 16; bagTotal = 1
|
||||
run('placeBlocks(BP_WALL, RSTONE, [[30, 18]])');
|
||||
run('bag[LDIRT] = 1500; bagTotal = 1500;');
|
||||
const msg = run('removePieceAt(121, 73)');
|
||||
check('removing a block with a full bag drops its rubble in place', count(run, run('RSTONE'), 120, 72, 123, 75) === 16 && /bag is full/.test(msg), msg);
|
||||
check('removing a block with a full bag drops its rubble in place', count(run, run('RSTONE'), 120, 72, 123, 75) === 16 && /bag full/.test(msg), msg);
|
||||
run('bag[BARCU] = 20; bag[LDIRT] = 1480; bagTotal = 1500; placeBlocks(BP_BELT, 1, [[32, 18]]); bag[LDIRT] += 1; bagTotal = 1500;');
|
||||
const m0 = run('money'); run('removePieceAt(129, 73)'); ticks(run, 60);
|
||||
check('a belt removed with a full bag still refunds its price as money', run('money') === m0 + 12, `${m0} -> ${run('money')}`);
|
||||
|
||||
Reference in New Issue
Block a user