From 29eee51e3522331f476f114570bb3473fc7b2735 Mon Sep 17 00:00:00 2001 From: 44r0n7 <44r0n7+gitea@pm.me> Date: Tue, 27 Oct 2026 17:12:09 -0400 Subject: [PATCH] Ping: 10 s recharge with a panel chip, natural ore colours with a rim, glints (t75) Co-Authored-By: Claude Opus 5.5 --- docs/agent-log.md | 8 ++++++++ src/render/overlays.js | 37 +++++++++++++++++++++++++------------ src/ui/actions.js | 8 +++++--- src/ui/selection-panels.js | 2 +- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/docs/agent-log.md b/docs/agent-log.md index b645207..1abebe4 100644 --- a/docs/agent-log.md +++ b/docs/agent-log.md @@ -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 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) - Owner's exported save: bag total 1,522 but the list showed nothing for 1,000 basalt (from the diff --git a/src/render/overlays.js b/src/render/overlays.js index 76796ff..0576fba 100644 --- a/src/render/overlays.js +++ b/src/render/overlays.js @@ -109,21 +109,25 @@ function drawFlowRates() { } ctx.restore(); } -// The resonance ping's echo: a ring spreading from the bot, the veins it found lighting up inside -// it, then fading once it has gone out. The veins are drawn once into a small image, so each -// frame is one clipped drawImage however many there are. Drawn over fog, so unexplored rock shows. -const PING_SPEED = 160, PING_FADE = 4500; +// The resonance ping's echo: a faint ring spreading from the bot, the veins it found showing in +// their own lit colour inside it (with a slightly lighter rim so dark seams read against fog), +// a brief sparkle as the ring passes them, then a fade. The veins are drawn once into a small +// 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) { 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 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) { - const x = i % W - ox, y = ((i / W) | 0) - oy; - if (x < 0 || y < 0 || x >= size || y >= size) continue; - const k = (y * size + x) * 4, m = mat[i]; - 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; + const x = i % W - ox, y = ((i / W) | 0) - oy, k = (y * size + x) * 4; + if (x <= 0 || y <= 0 || x >= size - 1 || y >= size - 1) continue; + if (!d[k - 1] || !d[k + 7] || !d[k - size * 4 + 3] || !d[k + size * 4 + 3]) lit(i, k, 45); // the rim } 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() { const p = GAME.ping; @@ -138,12 +142,21 @@ function drawPing() { ctx.imageSmoothingEnabled = false; ctx.drawImage(p.img.c, o.x, o.y, p.img.c.width * zp, p.img.c.height * zp); ctx.restore(); + ctx.save(); if (front < p.r) { - ctx.save(); - ctx.strokeStyle = `rgba(160,225,255,${0.85 * (1 - front / p.r)})`; ctx.lineWidth = Math.max(2, zp * 0.8); + 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.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 function drawRunOutline(run, strong) { diff --git a/src/ui/actions.js b/src/ui/actions.js index ea5b9b6..96f60af 100644 --- a/src/ui/actions.js +++ b/src/ui/actions.js @@ -202,11 +202,13 @@ function secondaryPress(shift) { } // 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() { - 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); - GAME.ping = Object.assign(p, { tick, t0: performance.now() }); + GAME.ping = Object.assign(p, { t0: performance.now() }); GAME.pingTick = tick; playSfx('pulse', 2); 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'); diff --git a/src/ui/selection-panels.js b/src/ui/selection-panels.js index 0b22eba..2c7f3b0 100644 --- a/src/ui/selection-panels.js +++ b/src/ui/selection-panels.js @@ -19,7 +19,7 @@ function renderSelCard() { const b = BIT_TIERS[player.bitTier]; const pw = PULSE_W[GAME.drillW], tn = GAME.drillTune; h = `
QResonance drill${pw.name} pulseE
-
${esc(b.name.toLowerCase())}hardness ≤ ${b.hard}reach ${drillReach(GAME.drillW)}${tn ? `tuned: ${esc(MD[tn].name.toLowerCase())} · R` : 'untuned · R tunes'}
+
${esc(b.name.toLowerCase())}hardness ≤ ${b.hard}reach ${drillReach(GAME.drillW)}${pingWait() ? `ping ${pingWait()}s` : 'ping ready'}${tn ? `tuned: ${esc(MD[tn].name.toLowerCase())} · R` : 'untuned · R tunes'}
Hold to send pulses at the cursor: rock they shake hard enough shatters and falls. Q/E width: narrow bores a long tunnel fast, wide clears a big face up close. R over a material tunes to it, so only that shatters. Right click pings: veins nearby light up, even unexplored. Built blocks are never harmed.
`; } else if (t === 'build') { // the build tool in general; the piece itself is described in the menu's hover popup