Drill: keep pass-through; restore the original loose-grain nudge, drop dust and push (t92)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+25
-81
@@ -178,7 +178,7 @@ check('a mold and what sets inside it stay safe from the drill', run(`(() => {
|
||||
for (const i of moldCellsAt(mo.kind, mo.x, mo.y).walls) if (crumbly(i)) return false;
|
||||
return true;
|
||||
})()`));
|
||||
// No gravity or kicks here: measure absorption through a fixed thickness, beyond normal reach
|
||||
// No gravity or nudges here: measure absorption through a fixed thickness, beyond normal reach
|
||||
// for the 115-grain case. All real medium rays still cross the powder before reaching basalt.
|
||||
function rubbleShield(thickness) {
|
||||
flatWorld(run, 240, 160, 150);
|
||||
@@ -206,36 +206,7 @@ check('115 grains of rubble prevent basalt breaking during sustained drilling',
|
||||
const through118 = rubbleShield(118);
|
||||
check('118 grains extinguish the ray before it can even shake basalt', through118 === 0 && !run('digp.some(d => d > 0)'), `${through118} basalt opened`);
|
||||
|
||||
// A 35-grain-deep pile against basalt: compare with gravity alone, tracking only rubble.
|
||||
function channelPile(fire) {
|
||||
flatWorld(run, 240, 160, 110);
|
||||
return run(`(() => {
|
||||
RS=12345; tick=0; lastPulseTick=-99; drillPulses=[]; player.bitTier=3;
|
||||
bot.x=45; bot.y=88; EV.length=0;
|
||||
for(let y=85;y<110;y++) {
|
||||
for(let x=55;x<90;x++) setCell(y*W+x,RSTONE,20,0);
|
||||
for(let x=90;x<150;x++) setCell(y*W+x,BASALT,20,F_NAT);
|
||||
}
|
||||
const measure=()=>{let n=0,sx=0,empty=0;for(let i=0;i<N;i++) if(mat[i]===RSTONE){n++;sx+=i%W;}
|
||||
for(let x=55;x<90;x++) if(mat[90*W+x]===EMPTY) empty++;
|
||||
return {n,cx:sx/n,empty};};
|
||||
const before=measure(); let dust=0;
|
||||
for(let t=0;t<480;t++) {
|
||||
${fire ? 'toolPulse(130,90,1,0);' : ''} simTick();
|
||||
dust+=EV.filter(e=>e.t==='debris' && e.dust).length; EV.length=0;
|
||||
}
|
||||
return {before,after:measure(),dust};
|
||||
})()`);
|
||||
}
|
||||
const channelRest=channelPile(false), channel=channelPile(true);
|
||||
check('medium pulses open most of the centre ray through a 35-grain pile within 480 ticks',
|
||||
channel.after.empty>=28 && channel.after.empty>channelRest.after.empty+15,JSON.stringify(channel));
|
||||
check('the pile streams back toward the bot rather than just bouncing on its surface',
|
||||
channel.after.cx<channel.before.cx-5 && channel.after.cx<channelRest.after.cx-5,JSON.stringify(channelRest));
|
||||
check('channel drilling retains every rubble grain',channel.after.n===channel.before.n,`${channel.before.n}->${channel.after.n}`);
|
||||
check('shaking loose grains emits dust puffs',channel.dust>0,`${channel.dust} puffs`);
|
||||
|
||||
// Absorption over a fixed distance, isolated from gravity, kicks and cell breakage.
|
||||
// Absorption over a fixed distance, isolated from gravity, nudges and cell breakage.
|
||||
function transmission(material, distance, step, tune = 0) {
|
||||
flatWorld(run, 240, 160, 150);
|
||||
return run(`(() => {
|
||||
@@ -249,7 +220,7 @@ function transmission(material, distance, step, tune = 0) {
|
||||
for (const [m, distance, factor, tune] of [
|
||||
['RSTONE',35,0.97,0], ['SAND',35,0.97,0], ['FEORE',35,0.97,0], ['COAL',35,0.97,0],
|
||||
['WATER',10,0.985**2,0], ['GRANITE',1,0.35**2,0], ['STONE',10,0.993**2,'CUORE'],
|
||||
['STONE',1,0.69,0], ['STONE',1,0.96**2,'RSTONE'], ['BELTL',1,0.25**2,0],
|
||||
['STONE',1,0.67,0], ['STONE',1,0.96**2,'RSTONE'], ['BELTL',1,0.25**2,0],
|
||||
]) {
|
||||
const energies = [0.25,0.5,1].map(step => transmission(m,distance,step,tune));
|
||||
const expected = 1.4 * factor ** distance;
|
||||
@@ -257,57 +228,30 @@ for (const [m, distance, factor, tune] of [
|
||||
energies.every(e => Math.abs(e-expected) < 0.00001), energies.map(e=>e.toFixed(5)).join(' / '));
|
||||
}
|
||||
|
||||
// A settled heap must slump further than an undrilled control, conserving all its grains.
|
||||
function shakenPile(pulse) {
|
||||
flatWorld(run,240,160,110);
|
||||
run(`RS=12345; tick=0; lastPulseTick=-99; drillPulses=[]; player.bitTier=3; bot.x=40; bot.y=88;
|
||||
for(let x=65;x<116;x++) for(let y=85+Math.abs(x-90);y<110;y++) setCell(y*W+x,RSTONE,20,0);
|
||||
for(let t=0;t<240;t++) simTick();`);
|
||||
const height = () => run(`(() => {let top=110;for(let i=0;i<110*W;i++) if(mat[i]===RSTONE) top=Math.min(top,(i/W)|0);return 110-top;})()`);
|
||||
const before=height(), grains=count(run,run('RSTONE'));
|
||||
run(`for(let t=0;t<480;t++) {${pulse?'toolPulse(90,94,1,0);':''}simTick();}`);
|
||||
return {before,after:height(),grains,left:count(run,run('RSTONE'))};
|
||||
// The original nudge visits only the wave front: back first, then down, then up.
|
||||
for (const [blocked, expectedY] of [[0,80], [1,81], [2,79], [3,80]]) {
|
||||
flatWorld(run,240,160,150);
|
||||
const got=run(`(() => {
|
||||
bot.x=0;bot.y=0;tickStamp=1;RS=12345;
|
||||
setCell(80*W+25,RSTONE,20,0);
|
||||
if (${blocked}>=1) setCell(80*W+24,STONE,20,F_NAT);
|
||||
if (${blocked}>=2) setCell(81*W+24,STONE,20,F_NAT);
|
||||
if (${blocked}>=3) setCell(79*W+24,STONE,20,F_NAT);
|
||||
const p={ox:24.5,oy:80.5,ang:0,w:1,jit:0.5,n:1,damp:PULSE_W[1].damp,
|
||||
en:new Float32Array([1.4]),s:new Float32Array(1),r:0,range:1,tune:0};
|
||||
drillPulses=[p];pulseTick();
|
||||
return mat[${expectedY}*W+${blocked===3?25:24}]===RSTONE;
|
||||
})()`);
|
||||
check(`original loose-grain nudge with ${blocked} blocked exits`,got);
|
||||
}
|
||||
const restingPile=shakenPile(false), fluidPile=shakenPile(true);
|
||||
check('pulses fluidise a settled rubble pile so its height drops',
|
||||
fluidPile.after <= fluidPile.before-3 && fluidPile.after < restingPile.after-3,
|
||||
`resting ${restingPile.before}->${restingPile.after}, shaken ${fluidPile.before}->${fluidPile.after}`);
|
||||
check('shaking conserves the pile material',fluidPile.grains===fluidPile.left,`${fluidPile.grains}->${fluidPile.left}`);
|
||||
check('repeating a seeded pile gives the same result',JSON.stringify(shakenPile(true))===JSON.stringify(fluidPile));
|
||||
|
||||
// A wave must still shake loose grains far behind its advancing front, throughout the path.
|
||||
flatWorld(run,240,160,150);
|
||||
const travelled=run(`(() => {
|
||||
RS=12345; bot.x=0;bot.y=0;tickStamp=1;EV.length=0;
|
||||
for(const x of [25,45,65]) setCell(80*W+x,RSTONE,20,0);
|
||||
const p={ox:19.5,oy:80.5,ang:0,w:1,jit:0.5,n:1,damp:PULSE_W[1].damp,
|
||||
en:new Float32Array([1.4]),s:new Float32Array([60]),r:60,range:100,tune:0};
|
||||
drillPulses=[p];pulseTick();
|
||||
return [25,45,65].filter(x=>mat[80*W+x-1]===RSTONE).length;
|
||||
})()`);
|
||||
check('the travelled ray path pushes interior grains one cell toward the bot',travelled>=2,`${travelled}/3 botward hops (probabilistic kicks)`);
|
||||
|
||||
// One artificially long ray crosses many exposed grains in a single tick, exercising the caps.
|
||||
flatWorld(run,240,160,150);
|
||||
const kicks = run(`(() => {
|
||||
RS=12345; bot.x=0;bot.y=0;tickStamp=1; EV.length=0;
|
||||
for(let x=20;x<120;x+=2) setCell(80*W+x,RSTONE,20,0);
|
||||
// Neither a covered grain nor a grain already moved this tick may be kicked.
|
||||
occ[80*W+20]=1; part[80*W+22]=P_PIPE; stamp[80*W+24]=tickStamp;
|
||||
// An open but owned destination and one stamped this tick are also off limits.
|
||||
part[80*W+25]=P_PIPE; occ[80*W+27]=1; stamp[80*W+29]=tickStamp;
|
||||
const p={ox:19.5,oy:80.5,ang:0,w:1,jit:0.5,n:8,damp:PULSE_W[1].damp,
|
||||
en:new Float32Array(8).fill(1.4),s:new Float32Array(8),r:200,range:100,tune:0};
|
||||
// Zero spread gives eight overlapping rays, testing the per-tick dust cap as well.
|
||||
const half=PULSE_W[1].half,mouth=PULSE_W[1].mouth; PULSE_W[1].half=0;PULSE_W[1].mouth=0;
|
||||
drillPulses=[p]; pulseTick(); PULSE_W[1].half=half;PULSE_W[1].mouth=mouth;
|
||||
let moved=0; for(let i=0;i<N;i++) if(mat[i]===RSTONE && stamp[i]===tickStamp) moved++;
|
||||
return {moved:moved-1,dust:EV.filter(e=>e.t==='debris').length,
|
||||
protected:mat[80*W+20]===RSTONE && mat[80*W+22]===RSTONE && mat[80*W+24]===RSTONE,
|
||||
blocked:mat[80*W+25]===EMPTY && mat[80*W+27]===EMPTY && mat[80*W+29]===EMPTY};
|
||||
})()`);
|
||||
check('kicks are bounded to three per ray and dust to four puffs per tick',kicks.moved>0 && kicks.moved<=24 && kicks.dust>0 && kicks.dust<=4,JSON.stringify(kicks));
|
||||
check('shaking respects occupied, owned and stamped cells',kicks.protected && kicks.blocked,JSON.stringify(kicks));
|
||||
check('a pulse leaves grains behind its advancing front alone',run(`(() => {
|
||||
bot.x=0;bot.y=0;tickStamp=1;RS=12345;
|
||||
setCell(80*W+25,RSTONE,20,0);
|
||||
drillPulses=[{ox:19.5,oy:80.5,ang:0,w:1,jit:0.5,n:1,damp:PULSE_W[1].damp,
|
||||
en:new Float32Array([1.4]),s:new Float32Array([60]),r:60,range:100,tune:0}];
|
||||
pulseTick();return mat[80*W+25]===RSTONE;
|
||||
})()`));
|
||||
|
||||
// Resonance ping (t50): finds natural veins within reach of the bit, under fog too, and
|
||||
// nothing else.
|
||||
|
||||
Reference in New Issue
Block a user