diff --git a/docs/PROJECT_MAP.md b/docs/PROJECT_MAP.md index ad7e1c5..ab59aef 100644 --- a/docs/PROJECT_MAP.md +++ b/docs/PROJECT_MAP.md @@ -320,10 +320,10 @@ Tool helpers: reach, brushes, and plain digging. - functions: setBuildRange, inBuildRange, inReach, forBrush, drillReach, inDrillReach, toolDig - variables: BUILD_MAX, buildBox -### src/sim/drill.js (161 lines) +### src/sim/drill.js (177 lines) The resonance drill: tuned pulses that shatter one material. -- functions: drillOrigin, pulseRay, builtCell, toolPulse, crumbleAround, crumbly, shatterCell, pulseTick -- variables: PULSE_W, PULSE_EVERY, PULSE_SPEED, PULSE_K, drillPulses, lastPulseTick, drillWidth, THIN_BONUS, CRUMBLE_MAX, THIN_BONUS_OFF +- functions: drillOrigin, pulseRay, builtCell, toolPulse, crumbleAround, crumbly, shatterCell, pulseTick, pingRadius, resonancePing +- variables: PULSE_W, PULSE_EVERY, PULSE_SPEED, PULSE_K, drillPulses, lastPulseTick, drillWidth, THIN_BONUS, CRUMBLE_MAX, THIN_BONUS_OFF, PING_VEINS ### src/sim/nature.js (168 lines) Trees (background layer), acorns, saplings, and waking freezing water. @@ -412,9 +412,10 @@ Scale colour without changing the smooth-look class. Machine overlays: fans, engines, silo and the rest, drawn on top of the world. - functions: cellRect, drawHazardRims, drawMachines, drawProcBlocks, drawSilo, drawSiloPixel, drawSiloWindow, drawRackBlocks -### src/render/overlays.js (285 lines) +### src/render/overlays.js (321 lines) Other overlays: floating text, rain and snow, flow arrows, filter tags, molds, the bot, particles. -- functions: spawnFloater, drawRain, drawFlowArrows, drawJoinMark, drawJoinMarks, flowLoaded, drawFlowRates, drawRunOutline, drawFilterTags, drawFloaters, moldLabel, drawMolds, drawBot, spawnDebris, spawnSpark, spawnSuck, drawParticles +- functions: spawnFloater, drawRain, drawFlowArrows, drawJoinMark, drawJoinMarks, flowLoaded, drawFlowRates, pingImage, drawPing, drawRunOutline, drawFilterTags, drawFloaters, moldLabel, drawMolds, drawBot, spawnDebris, spawnSpark, spawnSuck, drawParticles +- variables: PING_SPEED, PING_FADE ### src/render/smooth.js (99 lines) Smooth look: WebGL setup, texture uploads, drawing and adaptive resolution. @@ -512,10 +513,10 @@ Keyboard and mouse listeners. - functions: isTyping - variables: canvas -### src/ui/actions.js (243 lines) +### src/ui/actions.js (254 lines) What input does: overlays, F, pick, copy / cut / paste, undo, building and tool use each frame. Which overlays are on, shown as a coloured frame round the screen and a named badge -- functions: updateOverlayUI, toggleLens, doInteract, pickUnderCursor, aimBlock, stampOrigin, selModeHeld, startPaste, stopSelectAndPaste, plural, piecesIn, doUndo, endRmbRecord, dragDirFor, blockLabel, primaryPress, primaryRelease, secondaryPress, toolTick -- variables: ovShown +- functions: updateOverlayUI, toggleLens, doInteract, pickUnderCursor, aimBlock, stampOrigin, selModeHeld, startPaste, stopSelectAndPaste, plural, piecesIn, doUndo, endRmbRecord, dragDirFor, blockLabel, primaryPress, primaryRelease, secondaryPress, firePing, toolTick +- variables: ovShown, PING_RECHARGE ### src/ui/gamepad.js (60 lines) Gamepad input. @@ -545,7 +546,7 @@ Shared world colours, minimap and full map. - functions: resetMap, mapArea, updateMap, drawMap, mapFit, drawFullMap, openMap, zoomMap, zoomMinimap - variables: MAP_S, MAP -### src/ui/loop.js (137 lines) +### src/ui/loop.js (138 lines) The frame loop and boot. - functions: scanVisible, frame, boot - variables: lastT, acc, lastRender, frameMs, simMs, fpsSm, ambientT diff --git a/docs/agent-log.md b/docs/agent-log.md index 3faa6f7..6f35053 100644 --- a/docs/agent-log.md +++ b/docs/agent-log.md @@ -4,6 +4,16 @@ 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: t50 resonance ping (Claude Code) + +- Right click with the drill: `firePing` (`src/ui/actions.js`) calls `resonancePing` (`src/sim/drill.js`), + which lists natural vein cells (coal seam and the six ore veins) within 3x the bit's reach. + `drawPing` (`src/render/overlays.js`) draws an echo ring and the veins inside it over fog, from + a one-time offscreen image (one clipped drawImage per frame), fading over 4.5 s. 3 s recharge. +- Files: `src/sim/drill.js`, `src/ui/actions.js`, `src/render/overlays.js`, `src/ui/loop.js`, + `src/ui/notebook.js`, `src/ui/selection-panels.js`, `test/t_drill.js`, `docs/PROJECT_MAP.md`. +- Tests: 4 checks in `test/t_drill.js`; `python3 test/run_all.py --browser` all passed. + ## 2026-10-27: t64 build range about the screen; out-of-range replace ghosts (Claude Code) - `setBuildRange`/`inBuildRange` in `src/sim/tools.js`: the UI sets the view box each frame diff --git a/src/render/overlays.js b/src/render/overlays.js index a5f2833..76796ff 100644 --- a/src/render/overlays.js +++ b/src/render/overlays.js @@ -109,6 +109,42 @@ 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; +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); + 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; + } + g.putImageData(img, 0, 0); + return { c, ox, oy }; +} +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 (!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(); + 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.beginPath(); ctx.arc(c.x, c.y, front * zp, 0, 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/sim/drill.js b/src/sim/drill.js index b521711..3b421b1 100644 --- a/src/sim/drill.js +++ b/src/sim/drill.js @@ -159,3 +159,19 @@ function pulseTick() { } drillPulses = drillPulses.filter(p => p.r < p.range + 3); } +// Resonance ping (t50): an echo from the bot that finds ore veins in the rock around it, under +// fog too. Reach grows with the bit. Only reads the world; the UI draws the echo. +const PING_VEINS = new Uint8Array(NMAT); for (const m of [COALV, CUV, FEV, SNV, ZNV, AGV, AUV]) PING_VEINS[m] = 1; +function pingRadius() { return BIT_TIERS[player.bitTier].reach * 3; } +// Natural vein cells within r of (cx, cy), and the materials found. +function resonancePing(cx, cy, r = pingRadius()) { + const cells = [], found = new Set(), r2 = r * r; + const x0 = Math.max(0, Math.floor(cx - r)), x1 = Math.min(W - 1, Math.ceil(cx + r)); + const y0 = Math.max(0, Math.floor(cy - r)), y1 = Math.min(H - 1, Math.ceil(cy + r)); + for (let y = y0; y <= y1; y++) for (let x = x0; x <= x1; x++) { + const dx = x - cx, dy = y - cy, i = y * W + x, m = mat[i]; + if (dx * dx + dy * dy > r2 || !PING_VEINS[m] || !(flags[i] & F_NAT)) continue; + cells.push(i); found.add(m); + } + return { cells, found: [...found], r, x: cx, y: cy }; +} diff --git a/src/ui/actions.js b/src/ui/actions.js index c4c7ace..ea5b9b6 100644 --- a/src/ui/actions.js +++ b/src/ui/actions.js @@ -197,9 +197,20 @@ function secondaryPress(shift) { if (GAME.selDrag) { GAME.selDrag = null; hudDirty = true; GAME.rmbConsumed = true; } else if (GAME.paste) { GAME.paste = false; hudDirty = true; playSfx('ui'); GAME.rmbConsumed = true; } else if (t === 'build' && GAME.drag) { GAME.drag = null; GAME.dragDir = -1; GAME.rmbConsumed = true; } + else if (t === 'drill') { firePing(); GAME.rmbConsumed = true; } else { GAME.rmbConsumed = false; if (t === 'build') { endRmbRecord(); GAME.rmbRec = { label: '', ops: [] }; } } } +// Right click with the drill: a resonance ping. The echo is drawn by drawPing (overlays.js). +const PING_RECHARGE = 180; +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; } + const p = resonancePing(bot.x + bot.w / 2, bot.y + bot.h / 2); + GAME.ping = Object.assign(p, { tick, t0: performance.now() }); + 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'); +} // continuous actions, once per simulation tick function toolTick() { const t = toolId(), ax = Math.floor(GAME.aim.x), ay = Math.floor(GAME.aim.y); diff --git a/src/ui/loop.js b/src/ui/loop.js index f4c8c69..fe98ccb 100644 --- a/src/ui/loop.js +++ b/src/ui/loop.js @@ -73,6 +73,7 @@ function frame(now) { if (GAME.editRun) drawRunOutline(GAME.editRun, true); else if (hb >= 0) drawRunOutline(filterRun(hb), false); if (GAME.infoOverlay) { drawFlowArrows(); drawFlowRates(); } drawJoinMarks(); + drawPing(); drawFilterTags(!!GAME.infoOverlay, GAME.editRun ? GAME.editRun[0] : hb); drawFloaters(); drawRain(); diff --git a/src/ui/notebook.js b/src/ui/notebook.js index da3305a..acc1e9d 100644 --- a/src/ui/notebook.js +++ b/src/ui/notebook.js @@ -57,7 +57,7 @@ function renderNotebook() { } function controlsHtml() { const kb = [ - ['Dev console (marks the world)', 'Backtick (`)'], ['Fly', 'W A S D'], ['Boost while flying', 'Shift'], ['Tool modifier (while not flying)', 'Shift'], ['Use tool', 'Left mouse'], ['Second action', 'Right mouse'], + ['Dev console (marks the world)', 'Backtick (`)'], ['Fly', 'W A S D'], ['Boost while flying', 'Shift'], ['Tool modifier (while not flying)', 'Shift'], ['Use tool', 'Left mouse'], ['Second action', 'Right mouse'], ['Resonance ping: light up nearby veins (Drill)', 'Right mouse'], ['Tools', '1 – 4'], ['Select one to blow · piece', 'Q / E'], ['Switch build tab', 'Shift + Q / E'], ['Variant: material · direction · flip', 'R'], ['Wall: cycle shape · flip diagonal drag', 'T'], ['Wall or mold material menu (Build)', 'F'], ['Pick up selection or what you click (Pneumatic nozzle)', 'Left drag · Shift: everything'], ['Select (Pneumatic nozzle)', 'Click a bag name · Q/E'], ['Add to selection (Pneumatic nozzle)', 'Ctrl+click a bag name'], ['Show everything found (Pneumatic nozzle)', 'Hold Ctrl in the bag'], ['Clear selection (Pneumatic nozzle)', 'Clear in the bag'], ['Blow everything loose in the bag', 'Shift + right click'], ['Nozzle or brush size', '[ ] or Shift + wheel'], ['Replace what is built (Build)', 'Hold G'], ['Copy a box of what you built', 'Hold C, drag'], ['Cut a box (refunded, paste it back)', 'Hold X, drag'], ['Paste again', 'V'], ['Undo building', 'Ctrl + Z'], ['Blueprint library', 'B'], ['Zoom', 'Mouse wheel'], ['Interact (gates, molds, fans, filters)', 'F'], ['Thermal lens', 'H'], ['Info overlay: filters, belt and screw conveyor directions, grains per second', 'I'], ['Bot lamp', 'L'], diff --git a/src/ui/selection-panels.js b/src/ui/selection-panels.js index 1aef2d6..0b22eba 100644 --- a/src/ui/selection-panels.js +++ b/src/ui/selection-panels.js @@ -20,7 +20,7 @@ function renderSelCard() { const pw = PULSE_W[GAME.drillW], tn = GAME.drillTune; h = `