Silo contents at a glance (t38); drill aimed past its reach stays calm (t41)
Silos show a wide fill window in the material's colour in both looks, and the cursor label names the contents anywhere over the outline. Aiming the drill beyond its reach keeps normal colours, marks where the pulse stops, and drops the red circle and the out-of-reach toast. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1134,6 +1134,7 @@ with sync_playwright() as p:
|
||||
settle(1500)
|
||||
ev("__pad.buttons[7].pressed = false; __pad.buttons[7].value = 0;")
|
||||
settle(200)
|
||||
check('gamepad drill aim stays within its reach', ev('Math.hypot(GAME.padAim.x, GAME.padAim.y) <= drillReach(GAME.drillW)'))
|
||||
check('gamepad RT digs where the right stick aims', ev('stats.dug') > d0, f"{d0}->{ev('stats.dug')}")
|
||||
ev("__pad.buttons[5].pressed = true;"); settle(100); ev("__pad.buttons[5].pressed = false;"); settle(100)
|
||||
check('gamepad RB switches to the vacuum', ev('GAME.tool') == 1)
|
||||
@@ -1185,6 +1186,77 @@ with sync_playwright() as p:
|
||||
fp.goto(url)
|
||||
fp.evaluate('startNewWorld(4242, 512, 384)')
|
||||
fp.wait_for_function('GAME.running')
|
||||
# Silo feedback uses real pointer positions; drawing checks sample an isolated canvas.
|
||||
fp.evaluate("""() => {
|
||||
GAME.paused = true; SET.fog = false; SET.look = 'pixel'; SET.zoom = 8; SET.cursorLabel = true;
|
||||
SET.cameraLead = false; SET.cameraSmooth = 0; resizeRender();
|
||||
for (let y = 80; y < 140; y++) for (let x = 80; x < 160; x++) { setCell(y * W + x, EMPTY, 20, 0); bgm[y * W + x] = 0; }
|
||||
window.testSilo = addMachine('silo', 28, 25, 0);
|
||||
bot.x = 104; bot.y = 106; snapCamera(); $('cards').replaceChildren();
|
||||
}""")
|
||||
for amount in [0, 2000]:
|
||||
fp.evaluate(f'testSilo.sm = SAND; testSilo.sn = {amount}')
|
||||
for name, dx, dy in [('drum', 5, 8), ('hatch with snow', 5, 0), ('cone gap', 1, 22), ('leg', 0, 23), ('gate', 5, 23)]:
|
||||
fp.evaluate('setCell((testSilo.y) * W + testSilo.x + 5, SNOW, -10, 0)')
|
||||
point = fp.evaluate(f'(() => {{ const p = worldToScreenPx(testSilo.x + {dx} + 0.5, testSilo.y + {dy} + 0.5); return [p.x * innerWidth / R.canvas.width, p.y * innerWidth / R.canvas.width]; }})()')
|
||||
fp.mouse.move(*point); fp.wait_for_timeout(200)
|
||||
want = 'sand 2,000 / 4,000' if amount else 'empty'
|
||||
check(f'silo {name} hover shows {want}', fp.evaluate(f"hoverInfo(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y)).t1 === 'Silo' && hoverInfo(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y)).contents === '{want}' && $('info').textContent.includes('{('holds 2,000 / 4,000 sand' if amount else 'empty')}')"))
|
||||
check(f'silo {name} cursor label draws {want} in normal colour', fp.evaluate("""() => {
|
||||
const calls = [], ctx = R.ctx, text = ctx.fillText;
|
||||
ctx.fillText = function(t) { calls.push([t, this.fillStyle]); };
|
||||
try { drawCursorLabel(500, 300, 1); } finally { ctx.fillText = text; }
|
||||
const info = hoverInfo(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y));
|
||||
return calls.some(([t, c]) => t === info.contents && c === '#efe6d8');
|
||||
}"""))
|
||||
check('snow in silo is named snow', fp.evaluate("testSilo.sm = SNOW; hoverInfo(testSilo.x + 5, testSilo.y + 8).contents === 'snow 2,000 / 4,000'"))
|
||||
for look in ['pixel', 'smooth']:
|
||||
check(f'{look} silo fill pixels show empty, near empty, half and full at zooms 2 through 12', fp.evaluate("""pixel => {
|
||||
const canvas = document.createElement('canvas'); canvas.width = 160; canvas.height = 300;
|
||||
const ctx = canvas.getContext('2d'), mm = { x: 112, y: 100, sm: SAND };
|
||||
const at = (x, y, z) => Array.from(ctx.getImageData(Math.floor(x * z), Math.floor(y * z), 1, 1).data).slice(0, 3).join(',');
|
||||
for (let z = 2; z <= 12; z++) for (const [n, h] of [[0, 0], [1, 1], [2000, 8], [4000, 16]]) {
|
||||
mm.sn = n; drawSiloWindow(ctx, mm, 0, 0, z, pixel);
|
||||
for (let y = 3; y < 19; y++) if ((at(9.5, y + 0.5, z) !== '20,24,28') !== (y >= 19 - h)) return false;
|
||||
}
|
||||
// Every powder siloTick accepts must stand apart from dark glass, including coal.
|
||||
for (let m = 1; m < NMAT; m++) if (PH[m] === PH_POWDER) {
|
||||
mm.sm = m; mm.sn = 2000; drawSiloWindow(ctx, mm, 0, 0, 2, pixel);
|
||||
const filled = at(9.5, 18.5, 2).split(',').map(Number);
|
||||
if (Math.max(...filled.map((c, k) => Math.abs(c - [20, 24, 28][k]))) < 25) return false;
|
||||
const image = ctx.getImageData(0, 0, 24, 48).data.slice().join(',');
|
||||
drawSiloWindow(ctx, mm, 0, 0, 2, pixel);
|
||||
if (ctx.getImageData(0, 0, 24, 48).data.join(',') !== image) return false;
|
||||
}
|
||||
return true;
|
||||
}""", look == 'pixel'))
|
||||
fp.set_viewport_size({'width': 700, 'height': 800})
|
||||
check('silo contents remain available when the narrow HUD hides the info plate', fp.evaluate("getComputedStyle($('info')).display === 'none' && hoverInfo(testSilo.x + 5, testSilo.y + 8).contents === 'snow 2,000 / 4,000'"))
|
||||
fp.set_viewport_size({'width': 1280, 'height': 800})
|
||||
check('other tools still warn beyond reach', fp.evaluate("GAME.tool = 1; hoverInfo(testSilo.x + 80, testSilo.y).warn === 'out of reach'"))
|
||||
# Holding the drill with a distant pointer is ordinary aim, but hard rock still warns.
|
||||
fp.evaluate('bot.y = 130; snapCamera()')
|
||||
fp.keyboard.press('1')
|
||||
fp.mouse.move(1000, 400); fp.wait_for_timeout(100)
|
||||
fp.evaluate("$('toasts').replaceChildren(); lastPulseTick = -99; player.bitTier = 0; GAME.drillTune = 0")
|
||||
fp.evaluate('GAME.paused = false')
|
||||
fp.mouse.down()
|
||||
fp.evaluate('GAME.paused = true; for (let k = 0; k < 24; k++) { tick++; toolTick(); }')
|
||||
check('held drill beyond reach makes no toast or distance label', fp.evaluate("GAME.lmb && !inDrillReach(GAME.aim.x, GAME.aim.y, GAME.drillW) && !$('toasts').children.length && !hoverInfo(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y)).warn && !hoverInfo(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y)).t2.includes('out of reach')"))
|
||||
check('distant drill fan and crosshair stay normal with no red reach circle', fp.evaluate("""() => {
|
||||
const ctx = R.ctx, stroke = ctx.stroke, circle = drawReachCircle, colours = []; let circles = 0;
|
||||
ctx.stroke = function() { colours.push(this.strokeStyle); };
|
||||
drawReachCircle = () => circles++;
|
||||
try { drawPreviews(); } finally { ctx.stroke = stroke; drawReachCircle = circle; }
|
||||
return circles === 0 && !colours.some(c => c.includes('239, 106, 85'));
|
||||
}"""))
|
||||
fp.evaluate("""() => {
|
||||
const o = drillOrigin(), dx = GAME.aim.x + 0.5 - o.x, dy = GAME.aim.y + 0.5 - o.y, d = Math.hypot(dx, dy);
|
||||
for (let k = 6; k < 10; k++) setCell(Math.floor(o.y + dy / d * k) * W + Math.floor(o.x + dx / d * k), GRANITE, 20, F_NAT);
|
||||
tick += 20; toolTick();
|
||||
}""")
|
||||
check('held drill still toasts too hard with a distant pointer', fp.evaluate("$('toasts').textContent.includes('Granite is too hard; needs an iron bit.')"))
|
||||
fp.mouse.up()
|
||||
fp.evaluate("""() => {
|
||||
GAME.paused = true; SET.fog = true; SET.look = 'pixel'; SET.zoom = 6; SET.lighting = 'off';
|
||||
SET.cameraLead = false; SET.cameraSmooth = 0; resizeRender();
|
||||
|
||||
+7
-2
@@ -16,6 +16,11 @@ const med = { d: depth(), n: opened() };
|
||||
check('held on a stone wall, medium pulses dig a hole and drop rubble', med.d >= 6 && run('stats.dug') > 30 && count(run, run('RSTONE')) > 30, `${med.d} deep, ${med.n} opened, ${count(run, run('RSTONE'))} rubble`);
|
||||
wall(); hold(150, 78, 1, 0, 0.3);
|
||||
check('rock that is shaking but has not broken yet shows it', run('(() => { let n = 0; for (let i = 0; i < N; i++) if (digp[i] > 0 && PH[mat[i]] === PH_SOLID) n++; return n; })()') > 5);
|
||||
// A distant cursor chooses direction; the pulse still ends at its reach.
|
||||
flatWorld(run, 240, 160, 100);
|
||||
run('bot.x = 40; bot.y = 40; player.bitTier = 0; lastPulseTick = -99;');
|
||||
check('aiming past reach fires without a distance warning', run("toolPulse(220, 42, 1, 0).status") === '');
|
||||
check('a distant aim still caps the pulse at its reach', run('drillPulses[drillPulses.length - 1].range === drillReach(1)'));
|
||||
// the widths, with a copper bit so the reach is not what stops them
|
||||
const wall2 = () => { wall(); run('player.bitTier = 1;'); };
|
||||
wall2(); hold(150, 78, 1, 0, 2);
|
||||
@@ -81,10 +86,10 @@ check('and the granite stays whole', count(run, run('GRANITE')) === 116 * 40, co
|
||||
wall();
|
||||
run('drillPulses = []; tick += 20;');
|
||||
const far = JSON.parse(run('JSON.stringify(toolPulse(200, 78, 1, 0))'));
|
||||
check('aiming past the reach says how far the bit reaches', /Out of reach/.test(far.status), far.status);
|
||||
check('aiming past the reach fires without a warning', far.fired && !far.status, far.status);
|
||||
run('drillPulses = []; tick += 20;');
|
||||
const farW = JSON.parse(run('JSON.stringify(toolPulse(125, 78, 2, 0))')), nearM = JSON.parse(run('tick += 20; JSON.stringify(toolPulse(125, 78, 1, 0))'));
|
||||
check('a spot a medium pulse reaches is out of reach for a wide one, and it says so', /Out of reach/.test(farW.status) && /wide/.test(farW.status) && !nearM.status, `${farW.status} | ${nearM.status}`);
|
||||
check('aiming beyond a wide pulse also fires without a warning', farW.fired && !farW.status && nearM.fired && !nearM.status);
|
||||
|
||||
// stray bits: a lump of rock left hanging when the rock round it breaks falls apart
|
||||
const hanging = (m) => {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Stored snow keeps its identity and remains in the world's water budget.
|
||||
const { load, suite, flatWorld, ticks } = require('./harness');
|
||||
const { run } = load();
|
||||
const { check, done } = suite();
|
||||
flatWorld(run, 240, 160, 120);
|
||||
run("globalThis.snowSilo = addMachine('silo', 20, 20, 0); setCell(80 * W + 85, SNOW, -10, 0); stamp[80 * W + 85] = 0; tickStamp = 1;");
|
||||
const before = run('worldWater()');
|
||||
run('siloTick(snowSilo)');
|
||||
check('silo accepts snow and keeps its name', run('snowSilo.sm === SNOW && snowSilo.sn === 1'));
|
||||
check('snow inside still counts as water', run('worldWater()') === before);
|
||||
run('for (let y = 80; y < 104; y++) for (let x = 80; x < 92; x++) temp[y * W + x] = 100;');
|
||||
ticks(run, 180);
|
||||
check('stored snow does not melt in a warm silo', run('snowSilo.sm === SNOW && snowSilo.sn === 1'));
|
||||
run('snowSilo.open = true; siloTick(snowSilo)');
|
||||
check('opening the gate releases snow, then the silo is empty', run('snowSilo.sn === 0 && snowSilo.sm === EMPTY && mat[103 * W + 84 + (tick & 3)] === SNOW'));
|
||||
check('releasing snow preserves the water count', run('worldWater()') === before);
|
||||
done();
|
||||
Reference in New Issue
Block a user