Ping: 10 s recharge with a panel chip, natural ore colours with a rim, glints (t75)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 17:12:09 -04:00
co-authored by Claude Opus 5.5
parent a671dcb59a
commit 29eee51e35
4 changed files with 39 additions and 16 deletions
+8
View File
@@ -4,6 +4,14 @@ What agents have done, newest first. Add an entry for every piece of work: the d
note or request it was for, what changed, the files touched, the tests run, and anything left note or request it was for, what changed, the files touched, the tests run, and anything left
open or not verified. Keep each entry short; the git history has the details. open or not verified. Keep each entry short; the git history has the details.
## 2026-10-27: t75 ping recharge, natural ore colour, glints (Claude Code)
- 10 s recharge (`PING_RECHARGE`, `pingWait`) with a drill panel chip. `pingImage` draws veins in
their lit colour (base + per-grain noise) with a lighter rim; the ring is dimmer; up to 60 glints
per ping flash as the ring passes. Files: `src/ui/actions.js`, `src/ui/selection-panels.js`,
`src/render/overlays.js`. Tests: full suite with --browser passed. The ring and glints were not
caught in a screenshot (they finish before the frame is taken).
## 2026-10-27: bag counted things its list didn't show (Claude Code) ## 2026-10-27: bag counted things its list didn't show (Claude Code)
- Owner's exported save: bag total 1,522 but the list showed nothing for 1,000 basalt (from the - Owner's exported save: bag total 1,522 but the list showed nothing for 1,000 basalt (from the
+25 -12
View File
@@ -109,21 +109,25 @@ function drawFlowRates() {
} }
ctx.restore(); ctx.restore();
} }
// The resonance ping's echo: a ring spreading from the bot, the veins it found lighting up inside // The resonance ping's echo: a faint ring spreading from the bot, the veins it found showing in
// it, then fading once it has gone out. The veins are drawn once into a small image, so each // their own lit colour inside it (with a slightly lighter rim so dark seams read against fog),
// frame is one clipped drawImage however many there are. Drawn over fog, so unexplored rock shows. // a brief sparkle as the ring passes them, then a fade. The veins are drawn once into a small
const PING_SPEED = 160, PING_FADE = 4500; // image, so each frame is one clipped drawImage plus a few dozen sparkles.
const PING_SPEED = 160, PING_FADE = 4500, PING_GLINTS = 60, GLINT_MS = 450;
function pingImage(p) { function pingImage(p) {
const size = p.r * 2 + 1, c = document.createElement('canvas'); c.width = c.height = size; const size = p.r * 2 + 1, c = document.createElement('canvas'); c.width = c.height = size;
const g = c.getContext('2d'), img = g.createImageData(size, size), d = img.data, ox = Math.floor(p.x - p.r), oy = Math.floor(p.y - p.r); const g = c.getContext('2d'), img = g.createImageData(size, size), d = img.data, ox = Math.floor(p.x - p.r), oy = Math.floor(p.y - p.r);
const lit = (i, k, lift) => { const m = mat[i], v = (noiseB[i] - 128) * CVAR[m] / 128 + lift; d[k] = COLR[m] + v; d[k + 1] = COLG[m] + v; d[k + 2] = COLB[m] + v; d[k + 3] = 255; };
for (const i of p.cells) { const x = i % W - ox, y = ((i / W) | 0) - oy; if (x >= 0 && y >= 0 && x < size && y < size) lit(i, (y * size + x) * 4, 0); }
for (const i of p.cells) { for (const i of p.cells) {
const x = i % W - ox, y = ((i / W) | 0) - oy; const x = i % W - ox, y = ((i / W) | 0) - oy, k = (y * size + x) * 4;
if (x < 0 || y < 0 || x >= size || y >= size) continue; if (x <= 0 || y <= 0 || x >= size - 1 || y >= size - 1) continue;
const k = (y * size + x) * 4, m = mat[i]; if (!d[k - 1] || !d[k + 7] || !d[k - size * 4 + 3] || !d[k + size * 4 + 3]) lit(i, k, 45); // the rim
d[k] = Math.min(255, COLR[m] + 90); d[k + 1] = Math.min(255, COLG[m] + 90); d[k + 2] = Math.min(255, COLB[m] + 90); d[k + 3] = 220;
} }
g.putImageData(img, 0, 0); g.putImageData(img, 0, 0);
return { c, ox, oy }; const step = Math.max(1, Math.ceil(p.cells.length / PING_GLINTS));
const glints = p.cells.filter((_, k) => k % step === 0).map(i => ({ x: i % W + 0.5, y: ((i / W) | 0) + 0.5, d: Math.hypot(i % W - p.x, ((i / W) | 0) - p.y), m: mat[i] }));
return { c, ox, oy, glints };
} }
function drawPing() { function drawPing() {
const p = GAME.ping; const p = GAME.ping;
@@ -138,12 +142,21 @@ function drawPing() {
ctx.imageSmoothingEnabled = false; ctx.imageSmoothingEnabled = false;
ctx.drawImage(p.img.c, o.x, o.y, p.img.c.width * zp, p.img.c.height * zp); ctx.drawImage(p.img.c, o.x, o.y, p.img.c.width * zp, p.img.c.height * zp);
ctx.restore(); ctx.restore();
if (front < p.r) {
ctx.save(); ctx.save();
ctx.strokeStyle = `rgba(160,225,255,${0.85 * (1 - front / p.r)})`; ctx.lineWidth = Math.max(2, zp * 0.8); if (front < p.r) {
ctx.strokeStyle = `rgba(160,225,255,${0.35 * (1 - front / p.r)})`; ctx.lineWidth = Math.max(1, zp * 0.4);
ctx.beginPath(); ctx.arc(c.x, c.y, front * zp, 0, Math.PI * 2); ctx.stroke(); ctx.beginPath(); ctx.arc(c.x, c.y, front * zp, 0, Math.PI * 2); ctx.stroke();
ctx.restore();
} }
// a four-point glint where the ring has just passed, growing then shrinking
for (const gl of p.img.glints) {
const t = (ms - gl.d / PING_SPEED * 1000) / GLINT_MS;
if (t < 0 || t > 1) continue;
const s = worldToScreenPx(gl.x, gl.y), k = Math.sin(t * Math.PI) * zp * 2.2, m = gl.m;
ctx.strokeStyle = `rgba(${Math.min(255, COLR[m] + 140)},${Math.min(255, COLG[m] + 140)},${Math.min(255, COLB[m] + 140)},${0.9 * Math.sin(t * Math.PI)})`;
ctx.lineWidth = Math.max(1, zp * 0.35);
ctx.beginPath(); ctx.moveTo(s.x - k, s.y); ctx.lineTo(s.x + k, s.y); ctx.moveTo(s.x, s.y - k); ctx.lineTo(s.x, s.y + k); ctx.stroke();
}
ctx.restore();
} }
// Outline a run of blocks: strong while it is being edited, faint when pointed at // Outline a run of blocks: strong while it is being edited, faint when pointed at
function drawRunOutline(run, strong) { function drawRunOutline(run, strong) {
+5 -3
View File
@@ -202,11 +202,13 @@ function secondaryPress(shift) {
} }
// Right click with the drill: a resonance ping. The echo is drawn by drawPing (overlays.js). // Right click with the drill: a resonance ping. The echo is drawn by drawPing (overlays.js).
const PING_RECHARGE = 180; const PING_RECHARGE = 600;
// Seconds until the ping is ready again (0: ready).
function pingWait() { return GAME.pingTick !== undefined && tick >= GAME.pingTick ? Math.max(0, Math.ceil((PING_RECHARGE - (tick - GAME.pingTick)) / 60)) : 0; }
function firePing() { function firePing() {
if (GAME.ping && tick >= GAME.ping.tick && tick - GAME.ping.tick < PING_RECHARGE) { setStatus('The ping is still ringing; try again in a moment.'); playSfx('error'); return; } if (pingWait()) { setStatus(`The ping is recharging: ${pingWait()} s.`); playSfx('error'); return; }
const p = resonancePing(bot.x + bot.w / 2, bot.y + bot.h / 2); const p = resonancePing(bot.x + bot.w / 2, bot.y + bot.h / 2);
GAME.ping = Object.assign(p, { tick, t0: performance.now() }); GAME.ping = Object.assign(p, { t0: performance.now() }); GAME.pingTick = tick;
playSfx('pulse', 2); playSfx('pulse', 2);
const names = p.found.map(m => MD[m].name.toLowerCase()); const names = p.found.map(m => MD[m].name.toLowerCase());
setStatus(names.length ? `Ping: ${names.slice(0, 3).join(', ')}${names.length > 3 ? ' and more' : ''} nearby.` : `Ping: no veins within ${Math.round(p.r / BS)} blocks.`, 'info'); setStatus(names.length ? `Ping: ${names.slice(0, 3).join(', ')}${names.length > 3 ? ' and more' : ''} nearby.` : `Ping: no veins within ${Math.round(p.r / BS)} blocks.`, 'info');
+1 -1
View File
@@ -19,7 +19,7 @@ function renderSelCard() {
const b = BIT_TIERS[player.bitTier]; const b = BIT_TIERS[player.bitTier];
const pw = PULSE_W[GAME.drillW], tn = GAME.drillTune; const pw = PULSE_W[GAME.drillW], tn = GAME.drillTune;
h = `<div class="row cycle"><kbd>Q</kbd><span class="name grow">Resonance drill</span><span class="chip hot">${pw.name} pulse</span><kbd>E</kbd></div> h = `<div class="row cycle"><kbd>Q</kbd><span class="name grow">Resonance drill</span><span class="chip hot">${pw.name} pulse</span><kbd>E</kbd></div>
<div class="row" style="flex-wrap:wrap;gap:4px"><span class="chip">${esc(b.name.toLowerCase())}</span><span class="chip">hardness ≤ ${b.hard}</span><span class="chip">reach ${drillReach(GAME.drillW)}</span>${tn ? `<span class="chip ok"><span class="sw" style="display:inline-block;vertical-align:-2px;width:9px;height:9px;margin-right:4px;background:${rgbOf(tn)}"></span>tuned: ${esc(MD[tn].name.toLowerCase())} · R</span>` : '<span class="chip">untuned · R tunes</span>'}</div> <div class="row" style="flex-wrap:wrap;gap:4px"><span class="chip">${esc(b.name.toLowerCase())}</span><span class="chip">hardness ≤ ${b.hard}</span><span class="chip">reach ${drillReach(GAME.drillW)}</span>${pingWait() ? `<span class="chip">ping ${pingWait()}s</span>` : '<span class="chip ok">ping ready</span>'}${tn ? `<span class="chip ok"><span class="sw" style="display:inline-block;vertical-align:-2px;width:9px;height:9px;margin-right:4px;background:${rgbOf(tn)}"></span>tuned: ${esc(MD[tn].name.toLowerCase())} · R</span>` : '<span class="chip">untuned · R tunes</span>'}</div>
<div class="sub">Hold to send pulses at the cursor: rock they shake hard enough shatters and falls. <kbd>Q</kbd>/<kbd>E</kbd> width: narrow bores a long tunnel fast, wide clears a big face up close. <kbd>R</kbd> over a material tunes to it, so only that shatters. Right click pings: veins nearby light up, even unexplored. Built blocks are never harmed.</div>`; <div class="sub">Hold to send pulses at the cursor: rock they shake hard enough shatters and falls. <kbd>Q</kbd>/<kbd>E</kbd> width: narrow bores a long tunnel fast, wide clears a big face up close. <kbd>R</kbd> over a material tunes to it, so only that shatters. Right click pings: veins nearby light up, even unexplored. Built blocks are never harmed.</div>`;
} else if (t === 'build') { } else if (t === 'build') {
// the build tool in general; the piece itself is described in the menu's hover popup // the build tool in general; the piece itself is described in the menu's hover popup