diff --git a/docs/agent-log.md b/docs/agent-log.md index 1abebe4..bef5858 100644 --- a/docs/agent-log.md +++ b/docs/agent-log.md @@ -8,8 +8,9 @@ open or not verified. Keep each entry short; the git history has the details. - 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 + per ping flash as the ring passes. Follow-up: veins fade by distance (lit 1.2 s, fade 0.8 s, + drawn in 6 alpha bands) and `drawPingRecharge` shows an arc around the bot with the drill. Files: `src/ui/actions.js`, `src/ui/selection-panels.js`, + `src/render/overlays.js`, `src/ui/loop.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) diff --git a/src/render/overlays.js b/src/render/overlays.js index 0576fba..58f271f 100644 --- a/src/render/overlays.js +++ b/src/render/overlays.js @@ -111,9 +111,10 @@ function drawFlowRates() { } // 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; +// a brief sparkle as the ring passes them; each vein fades a fixed time after the ring reached +// it, so the fade follows the ring outward. The veins are drawn once into a small image; each +// frame draws it in a few distance bands (fading ones at partial alpha) plus a few dozen sparkles. +const PING_SPEED = 160, PING_HOLD = 1200, PING_FADE = 800, PING_BANDS = 6, 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); @@ -133,15 +134,23 @@ function drawPing() { const p = GAME.ping; if (!p) return; const ms = performance.now() - p.t0, front = ms / 1000 * PING_SPEED, done = p.r / PING_SPEED * 1000; - if (ms > done + PING_FADE) { GAME.ping = null; return; } + if (ms > done + PING_HOLD + PING_FADE) { GAME.ping = null; return; } if (!p.img) p.img = pingImage(p); const ctx = R.ctx, zp = R.zp, c = worldToScreenPx(p.x, p.y), o = worldToScreenPx(p.img.ox, p.img.oy); - ctx.save(); - ctx.beginPath(); ctx.arc(c.x, c.y, Math.min(front, p.r + 1) * zp, 0, Math.PI * 2); ctx.clip(); - ctx.globalAlpha = ms < done ? 1 : Math.max(0, 1 - (ms - done) / PING_FADE); - ctx.imageSmoothingEnabled = false; - ctx.drawImage(p.img.c, o.x, o.y, p.img.c.width * zp, p.img.c.height * zp); - ctx.restore(); + // inside `gone` the veins have faded out; from `full` out to the ring they are fully lit + const outer = Math.min(front, p.r + 1), full = (ms - PING_HOLD) / 1000 * PING_SPEED, gone = (ms - PING_HOLD - PING_FADE) / 1000 * PING_SPEED; + const band = (r0, r1, a) => { + if (r1 <= Math.max(0, r0) || a <= 0) return; + ctx.save(); ctx.beginPath(); ctx.arc(c.x, c.y, r1 * zp, 0, Math.PI * 2); + if (r0 > 0) ctx.arc(c.x, c.y, r0 * zp, 0, Math.PI * 2, true); + ctx.clip(); ctx.globalAlpha = a; ctx.imageSmoothingEnabled = false; + ctx.drawImage(p.img.c, o.x, o.y, p.img.c.width * zp, p.img.c.height * zp); ctx.restore(); + }; + band(Math.max(full, 0), outer, 1); + for (let k = 0; k < PING_BANDS; k++) { + const r0 = gone + (full - gone) * k / PING_BANDS, r1 = gone + (full - gone) * (k + 1) / PING_BANDS; + band(r0, Math.min(r1, outer), (k + 0.5) / PING_BANDS); + } ctx.save(); if (front < p.r) { ctx.strokeStyle = `rgba(160,225,255,${0.35 * (1 - front / p.r)})`; ctx.lineWidth = Math.max(1, zp * 0.4); @@ -158,6 +167,17 @@ function drawPing() { } ctx.restore(); } +// While the ping recharges, a thin arc around the bot fills up (only with the drill in hand). +function drawPingRecharge() { + if (toolId() !== 'drill' || GAME.pingTick === undefined || tick < GAME.pingTick) return; + const k = (tick - GAME.pingTick) / PING_RECHARGE; + if (k >= 1) return; + const ctx = R.ctx, c = worldToScreenPx(bot.x + bot.w / 2, bot.y + bot.h / 2), r = Math.max(bot.w, bot.h) * R.zp * 0.9; + ctx.save(); ctx.lineWidth = Math.max(2, R.zp * 0.5); ctx.lineCap = 'round'; + ctx.strokeStyle = 'rgba(160,225,255,0.18)'; ctx.beginPath(); ctx.arc(c.x, c.y, r, 0, Math.PI * 2); ctx.stroke(); + ctx.strokeStyle = 'rgba(160,225,255,0.7)'; ctx.beginPath(); ctx.arc(c.x, c.y, r, -Math.PI / 2, -Math.PI / 2 + k * Math.PI * 2); ctx.stroke(); + ctx.restore(); +} // Outline a run of blocks: strong while it is being edited, faint when pointed at function drawRunOutline(run, strong) { if (!run || !run.length) return; diff --git a/src/ui/loop.js b/src/ui/loop.js index fe98ccb..a0e846b 100644 --- a/src/ui/loop.js +++ b/src/ui/loop.js @@ -74,6 +74,7 @@ function frame(now) { if (GAME.infoOverlay) { drawFlowArrows(); drawFlowRates(); } drawJoinMarks(); drawPing(); + drawPingRecharge(); drawFilterTags(!!GAME.infoOverlay, GAME.editRun ? GAME.editRun[0] : hb); drawFloaters(); drawRain();