Files
deepgrain-foundry/test/t_drill.js
T
44r0n7andClaude Opus 5.5 b74a9173eb 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>
2026-10-26 18:40:05 -04:00

143 lines
11 KiB
JavaScript

// The resonance drill: pulses from the bot shake rock until it shatters. Widths trade area for
// speed, tuning shatters only one material and drains it out of the wall, built blocks are left
// alone, and the drill says why when nothing breaks.
const { load, suite, flatWorld, rect, ticks, count } = require('./harness');
const { run } = load();
const { check, done } = suite();
// a stone wall to the right of the bot, from x 124 on, rows 60..99
function wall(m = 'STONE') { flatWorld(run, 240, 160, 100); run(`for (let y = 60; y < 100; y++) for (let x = 124; x < 240; x++) setCell(y * W + x, ${m}, AMB[y], F_NAT);`); run('bot.x = 110; bot.y = 76; stats.dug = 0; drillPulses = []; player.bitTier = 0;'); }
const hold = (ax, ay, w, tune, secs) => run(`for (let k = 0; k < ${Math.round(secs * 60)}; k++) { toolPulse(${ax}, ${ay}, ${w}, ${tune || 0}); simTick(); }`);
// how deep the hole goes along the aim line, and how many grains it opened in all
const depth = () => run('(() => { let d = 0; for (let x = 124; x < 240; x++) { if (PH[mat[78 * W + x]] === PH_SOLID) break; d = x - 123; } return d; })()');
const opened = () => run('(() => { let n = 0; for (let y = 60; y < 100; y++) for (let x = 124; x < 240; x++) if (PH[mat[y * W + x]] !== PH_SOLID) n++; return n; })()');
wall(); run('bot.x = 116;'); hold(150, 78, 1, 0, 2);
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);
const med2 = { d: depth(), n: opened() };
wall2(); hold(150, 78, 0, 0, 2);
const nar = { d: depth(), n: opened() };
wall2(); hold(150, 78, 2, 0, 2);
const wid = { d: depth(), n: opened() };
check('a narrow pulse bores deeper than a wide one', nar.d > wid.d, `narrow ${nar.d}, medium ${med2.d}, wide ${wid.d} deep`);
const rowsOf = () => run('(() => { let a = 99, b = 0; for (let y = 60; y < 100; y++) for (let x = 124; x < 132; x++) if (PH[mat[y * W + x]] !== PH_SOLID) { a = Math.min(a, y); b = Math.max(b, y); } return b - a + 1; })()');
wall2(); hold(150, 78, 2, 0, 2); const wideRows = rowsOf();
wall2(); hold(150, 78, 0, 0, 2); const narRows = rowsOf();
check('the wide cone is taller at the face than the narrow one', wideRows > narRows, `wide ${wideRows} rows, narrow ${narRows}`);
// the reach depends on the width: a narrow beam reaches furthest, a wide fan only close by
run('player.bitTier = 0;');
const reaches = [0, 1, 2].map(w => run(`drillReach(${w})`));
check('a narrow pulse reaches further than a medium one, and a medium one further than a wide one', reaches[0] > reaches[1] && reaches[1] > reaches[2], reaches.join(' / '));
// following the face like a player would, narrow bores the fastest tunnel and wide opens the most rock
const follow = (w) => {
wall2(); run('for (let y = 50; y < 100; y++) for (let x = 124; x < 240; x++) setCell(y * W + x, STONE, AMB[y], F_NAT); bot.x = 116;');
for (let t = 0; t < 12; t++) run(`for (let k = 0; k < 15; k++) { toolPulse(Math.round(bot.x + 42), 78, ${w}, 0); simTick(); } for (let k = 0; k < 40; k++) { if (botOverlapSolid(bot.x + 4, bot.y)) break; bot.x += 1; }`);
return { d: depth(), n: opened() };
};
const fN = follow(0), fM = follow(1), fW = follow(2);
check('following the face, the narrow tunnel goes furthest', fN.d > fM.d * 1.3 && fM.d > fW.d, `narrow ${fN.d}, medium ${fM.d}, wide ${fW.d} deep`);
check('and the wide fan opens up the most rock', fW.n > fN.n && fW.n > fM.n, `narrow ${fN.n}, medium ${fM.n}, wide ${fW.n} grains`);
// the fan leaves the whole face of the bot, so rock right beside it is not missed
for (const w of [0, 1, 2]) {
wall(); run('for (let y = 60; y < 100; y++) for (let x = 124; x < 240; x++) setCell(y * W + x, EMPTY, AMB[y], 0); for (let y = 77; y < 81; y++) for (let x = 121; x < 124; x++) setCell(y * W + x, STONE, AMB[y], F_NAT); bot.x = 116; bot.y = 76;');
const lump = () => count(run, run('STONE'), 121, 60, 123, 81);
hold(150, 74, w, 0, 1.5);
check(`a ${['narrow', 'medium', 'wide'][w]} pulse aimed a little above a lump touching the bot still breaks it`, lump() <= 2, `${lump()} of 12 left`);
}
// tuning: copper veins in stone; tuned to copper ore only the veins break, and drain out
wall();
run('for (let s = 0; s <= 1; s += 0.01) { const x = Math.round(124 + 20 * s), y = Math.round(80 - 8 * s); for (let dy = -1; dy <= 1; dy++) for (let dx = 0; dx <= 1; dx++) setCell((y + dy) * W + x + dx, CUV, AMB[y], F_NAT); }');
run('player.bitTier = 1; bot.x = 114;');
const stone0 = count(run, run('STONE')), vein0 = count(run, run('CUV'));
hold(136, 77, 1, run('CUORE'), 3);
const stone1 = count(run, run('STONE')), vein1 = count(run, run('CUV'));
check('tuned to copper ore, the stone around the vein is untouched', stone1 === stone0, `stone ${stone0} -> ${stone1}`);
check('and the vein shatters', vein1 < vein0 * 0.6, `vein ${vein0} -> ${vein1}`);
const out = run('(() => { let n = 0; for (let y = 0; y < 100; y++) for (let x = 0; x < 124; x++) if (mat[y * W + x] === CUORE) n++; return n; })()');
check('shattered ore pours out of the wall toward the bot', out > 10, `${out} ore grains outside the wall`);
// built blocks soak pulses up
wall();
run('bag[RSTONE] = 200; bagTotal = 200; for (let by = 17; by <= 21; by++) placeBlock(BP_WALL, RSTONE, 30, by, true);'); // a built column at x 120..123
const built0 = run('(() => { let n = 0; for (let y = 68; y < 88; y++) for (let x = 120; x < 124; x++) if (mat[y * W + x] === STONE) n++; return n; })()');
hold(140, 78, 1, 0, 2);
const built1 = run('(() => { let n = 0; for (let y = 68; y < 88; y++) for (let x = 120; x < 124; x++) if (mat[y * W + x] === STONE) n++; return n; })()');
check('built blocks in the way are never harmed', built0 > 0 && built1 === built0, `${built0} -> ${built1}`);
check('and they shield what is behind them', opened() < med.n / 2, `${opened()} opened behind the wall`);
// it says why nothing breaks
wall('GRANITE');
const r = JSON.parse(run('JSON.stringify(toolPulse(140, 78, 1, 0))'));
check('granite with a stone bit says which bit it needs', /too hard/.test(r.status) && /iron bit/i.test(r.status), r.status);
hold(140, 78, 1, 0, 1);
check('and the granite stays whole', count(run, run('GRANITE')) === 116 * 40, count(run, run('GRANITE')));
wall();
run('drillPulses = []; tick += 20;');
const far = JSON.parse(run('JSON.stringify(toolPulse(200, 78, 1, 0))'));
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('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) => {
flatWorld(run, 240, 160, 100);
run('for (let y = 60; y < 100; y++) for (let x = 140; x < 240; x++) setCell(y * W + x, STONE, AMB[y], F_NAT);');
// a 3 x 3 lump joined to the wall by a line of stone grains
run(`player.bitTier = 0; for (let y = 70; y < 73; y++) for (let x = 130; x < 133; x++) setCell(y * W + x, ${m}, AMB[y], F_NAT); for (let x = 133; x < 140; x++) setCell(71 * W + x, STONE, AMB[71], F_NAT);`);
run('shatterCell(71 * W + 136, 136, 71, STONE); crumbleAround(136, 71);');
};
hanging('STONE');
check('a bit of stone cut off from the wall falls apart', count(run, run('STONE'), 128, 60, 135, 80) === 0 && count(run, run('RSTONE')) === 13, `${count(run, run('STONE'), 128, 60, 135, 80)} left, ${count(run, run('RSTONE'))} rubble`);
check('and the wall it came off stays whole', count(run, run('STONE'), 137, 60, 239, 99) === 100 * 40 + 3, count(run, run('STONE'), 137, 60, 239, 99));
hanging('FEV');
check('a cut-off lump with ore too hard for the bit in it stays whole', count(run, run('FEV')) === 9 && count(run, run('FEORE')) === 0, `${count(run, run('FEV'))} vein, ${count(run, run('FEORE'))} ore`);
// too-hard rock is never knocked loose, however small
const speckWall = (n) => {
flatWorld(run, 240, 160, 100);
run('for (let y = 60; y < 100; y++) for (let x = 124; x < 240; x++) setCell(y * W + x, STONE, AMB[y], F_NAT); player.bitTier = 0; drillPulses = [];');
// a lump of iron ore at the face, n grains across, three rows tall, sticking out into the open
run(`for (let y = 77; y < 80; y++) for (let x = 122; x < ${122 + n}; x++) setCell(y * W + x, FEV, AMB[y], F_NAT); bot.x = 112; bot.y = 76;`);
};
speckWall(2); hold(150, 78, 1, 0, 3);
check('a speck of iron ore is never broken or knocked loose by a stone bit', count(run, run('FEV')) === 6 && count(run, run('FEORE')) === 0, `${count(run, run('FEV'))} left, ${count(run, run('FEORE'))} ore`);
// world generation leaves no tiny specks of rock harder than what is round them
run('generateWorld(424242, 1024, 640)');
// (a speck only counts if it is harder than the rock round it; another sweep finds none)
const tiny = run('clearHardSpecks(false)');
check('a new world has no specks of iron, zinc or granite small enough to leave in the way', tiny === 0, `${tiny} specks`);
const fe = count(run, run('FEV'));
check('and still has plenty of iron ore', fe > 4000, fe);
// the bot slips round a lip of rock a grain or two deep instead of stopping dead
const corridor = (lip) => {
flatWorld(run, 240, 160, 100);
run(`for (let y = 50; y < 100; y++) for (let x = 100; x < 240; x++) if (y < 70 || y >= 78) setCell(y * W + x, STONE, AMB[y], F_NAT);
for (let y = 70; y < 70 + ${lip}; y++) for (let x = 140; x < 143; x++) setCell(y * W + x, STONE, AMB[y], F_NAT);
bot.x = 110; bot.y = 70; bot.vx = bot.vy = 0;`);
for (let k = 0; k < 150; k++) run('botStep(1, 0, false)');
return run('bot.x');
};
const past2 = corridor(2), past8 = corridor(8);
check('flying along a tunnel, the bot slips past a two-grain lip', past2 > 150, past2.toFixed(1));
check('but a wall across the tunnel still stops it', past8 < 140, past8.toFixed(1));
// trees in the way come apart into wood chunks
flatWorld(run, 240, 160, 100);
run('for (let y = 80; y < 100; y++) for (let x = 120; x < 122; x++) bgm[y * W + x] = WOOD; findTreeBases(); bot.x = 108; bot.y = 88; player.bitTier = 0;');
hold(121, 92, 1, 0, 3);
check('a tree trunk in the path is cut into wood chunks', count(run, run('LWOOD')) > 0, count(run, run('LWOOD')));
done();