Drill: visible dust; pulses push loose material back and open a channel (t92)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 18:32:31 -04:00
co-authored by Claude Opus 5.5
parent 729378770a
commit dadf62a28d
8 changed files with 151 additions and 22 deletions
+16 -2
View File
@@ -331,8 +331,13 @@ function drawBot() {
ctx.beginPath(); ctx.moveTo(cx - bot.face * zp, sy + zp * 0.6 + bob); ctx.lineTo(cx - bot.face * zp * 1.6, sy - zp * 0.6 + bob); ctx.stroke();
ctx.restore();
}
function spawnDebris(x, y, m) {
function spawnDebris(x, y, m, dust = false) {
if (!SET.particles || R.particles.length > 400) return;
if (dust) {
R.particles.push({ x: x + 0.5, y: y + 0.5, vx: (Math.random() - 0.5) * 0.12,
vy: -0.06 - Math.random() * 0.06, life: 36, r: 205, g: 190, b: 165, dust: true });
return;
}
for (let k = 0; k < 2; k++) R.particles.push({ x: x + Math.random(), y: y + Math.random(), vx: (Math.random() - 0.5) * 0.5, vy: -Math.random() * 0.5, life: 20 + Math.random() * 20, r: COLR[m], g: COLG[m], b: COLB[m] });
}
function spawnSpark(x, y) {
@@ -358,11 +363,20 @@ function drawParticles() {
p.x += dx / d * sp; p.y += dy / d * sp; p.life--;
if (p.life <= 0) { ps.splice(k, 1); continue; }
} else {
p.x += p.vx; p.y += p.vy; p.vy += 0.04; p.life--;
p.x += p.vx; p.y += p.vy; p.vy += p.dust ? 0 : 0.04; p.life--;
if (p.life <= 0) { ps.splice(k, 1); continue; }
}
if (SET.fog && fogSeen(p.x, p.y) < 64) continue;
const s = worldToScreenPx(p.x, p.y);
if (p.dust) {
const radius = zp * (1 + (36 - p.life) * 0.035);
const puff = ctx.createRadialGradient(s.x, s.y, 0, s.x, s.y, radius);
puff.addColorStop(0, `rgba(${p.r},${p.g},${p.b},${0.32 * p.life / 36})`);
puff.addColorStop(1, `rgba(${p.r},${p.g},${p.b},0)`);
ctx.fillStyle = puff;
ctx.fillRect(s.x - radius, s.y - radius, radius * 2, radius * 2);
continue;
}
ctx.fillStyle = `rgba(${p.r | 0},${p.g | 0},${p.b | 0},${Math.min(1, p.life / 10)})`;
const sz = p.spark ? Math.max(1, zp * 0.5) : p.suck ? zp : Math.max(1, zp * 0.7);
ctx.fillRect(s.x, s.y, sz, sz);
+32 -17
View File
@@ -115,7 +115,38 @@ function pulseTick(step = 0.5) {
let e = p.en[k];
if (e < 0.04) continue;
const ray = pulseRay(p.ox, p.oy, p.ang, Wd, -1 + 2 * (k + p.jit) / p.n), ux = ray.ux, uy = ray.uy;
let sd = p.s[k], kicks = 0;
// Keep shaking the travelled path while the wave advances, including the pile interior.
let shake = Wd.en, kicks = 0;
for (let d = 0.5; d < lim && shake >= 0.04 && kicks < 3; d++) {
const x = Math.floor(ray.x + ux * d), y = Math.floor(ray.y + uy * d);
if (x < 0 || y < 0 || x >= W || y >= H) break;
if (x >= bx0 && x <= bx1 && y >= by0 && y <= by1) continue;
const i = y * W + x, m = mat[i], ph = PH[m];
if (ph === PH_SOLID) break;
if (ph === PH_LIQUID) { shake *= 0.970225; continue; }
if (ph !== PH_POWDER) continue;
shake *= 0.97;
if (stamp[i] !== tickStamp && !occ[i] && !part[i] && rnd() < 0.55 * shake) {
// Lead with the reverse ray; blocked grains can lift or spill to either side.
const backX = Math.abs(ux) > 0.38 ? -Math.sign(ux) : 0;
const backY = Math.abs(uy) > 0.38 ? -Math.sign(uy) : 0;
const side = rnd() < 0.5 ? -1 : 1;
for (let hop = 0; hop < 5; hop++) {
const mx = hop < 2 ? backX : hop < 4 ? side : -side;
const my = hop === 0 ? backY : hop === 1 ? -1 : -(hop & 1);
const nx = x + mx, ny = y + my;
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
const j = ny * W + nx;
if (mat[j] !== EMPTY && PH[mat[j]] !== PH_GAS) continue;
if (occ[j] || part[j] || stamp[j] === tickStamp || !enter(m, ph, j, mx, my)) continue;
if (my && CORNER_SEAL && PH[mat[i + mx]] === PH_SOLID && PH[mat[i + my * W]] === PH_SOLID) continue;
move(i, j, x, y, nx, ny); kicks++;
if (dust < 4) { emit({ t: 'debris', x, y, m, dust: true }); dust++; }
break;
}
}
}
let sd = p.s[k];
while (sd < lim && e >= 0.04) {
sd += step;
const x = Math.floor(ray.x + ux * sd), y = Math.floor(ray.y + uy * sd);
@@ -130,22 +161,6 @@ function pulseTick(step = 0.5) {
if (ph === PH_LIQUID) { e *= liquid; continue; }
if (ph === PH_POWDER) {
e *= loose;
// At most three one-cell hops per ray/tick; gravity settles the shaken grains.
if (kicks < 3 && stamp[i] !== tickStamp && !occ[i] && !part[i] && rnd() < 1.1 * e * step) {
const side = rnd() < 0.5 ? -1 : 1;
for (let hop = 0; hop < 4; hop++) {
const mx = hop < 2 ? side : -side, my = -(hop & 1);
const nx = x + mx, ny = y + my;
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
const j = ny * W + nx;
if (mat[j] !== EMPTY && PH[mat[j]] !== PH_GAS) continue;
if (occ[j] || part[j] || stamp[j] === tickStamp || !enter(m, ph, j, mx, my)) continue;
if (my && CORNER_SEAL && PH[mat[i + mx]] === PH_SOLID && PH[mat[i - W]] === PH_SOLID) continue;
move(i, j, x, y, nx, ny); kicks++;
if (dust < 4) { emit({ t: 'debris', x, y, m }); dust++; }
break;
}
}
continue;
}
if (builtCell(i, x, y)) { e *= built; continue; }
+1 -1
View File
@@ -17,7 +17,7 @@ function drainEvents() {
showCard({ kind: e.kind === 'bit' ? 'Drill bit upgraded' : 'Bag upgraded', title: e.kind === 'bit' ? BIT_TIERS[e.tier].name : TANK_TIERS[e.tier].name, accent: 'milestone',
body: [e.kind === 'bit' ? `Digs hardness ${BIT_TIERS[e.tier].hard} and below.` : `Holds ${TANK_TIERS[e.tier].cap.toLocaleString('en-US')} grains.`] });
hudDirty = true;
} else if (e.t === 'debris') spawnDebris(e.x, e.y, e.m);
} else if (e.t === 'debris') spawnDebris(e.x, e.y, e.m, e.dust);
else if (e.t === 'suck') spawnSuck(e.x, e.y, e.m);
else if (e.t === 'warn') spawnFloater(e.x, e.y, e.text, '#ff8a72');
else if (e.t === 'sold') spawnFloater(e.x, e.y, '+' + fmtMoney(e.v));