diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 591ebc0..ccb3d8a 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -178,12 +178,13 @@ above, including: - **Fewer, smaller messages.** Molds show only a small percentage, red when blocked or spoiled and gold when a casting waits for F; details are in the hover panel. Bars dropping, blocked molds and set castings no longer pop up cards or toasts. Fan heat is a red marker on the fan; melted tubes and machines get a short red label where they were. Status lines are one short sentence. The owner found the messages too large and too many. - **World sounds are local.** Sounds from the world (water sizzling on hot metal or lava, and - any other event with a place) fade with distance from the view and pan left or right; they go - silent one screen width off-screen. The owner heard a constant snapping that followed the bot - in an older world but not in a new one. It was the sizzle: in the owner's world, water that - had found its way down to hot basalt and granite (480-800°C, about 500 grains below the - surface) boiled about 48 times a minute, and every sizzle played at full volume. - + any other event with a place) are heard around the bot, whatever the zoom: full volume within + 24 grains, fading to silent at 96, panned left or right. The owner heard a constant snapping + that followed the bot in an older world but not in a new one. It was the sizzle: in the + owner's world, water that had found its way down to hot basalt and granite (480-800°C, about + 500 grains below the surface) boiled about 48 times a minute, every one at full volume. The + first fix faded sounds with distance from the view; zoomed out they were still heard, so the + owner asked for a radius around the bot instead. - **The silo has pixel art in the pixel look**, drawn grain by grain like the rest of the world; the smooth look keeps the illustrated silo. The owner noticed it looked the same in both. diff --git a/docs/PROJECT_MAP.md b/docs/PROJECT_MAP.md index 12e2d3f..fe29b86 100644 --- a/docs/PROJECT_MAP.md +++ b/docs/PROJECT_MAP.md @@ -354,10 +354,10 @@ Smooth look: a WebGL2 pass that turns the per-grain buffers drawWorld fills (col - functions: smCompile, smoothAvailable, smUpload, smoothDraw, smoothPace - variables: SM, SM_VS, SM_FS, smBytes -### src/ui/audio.js (228 lines) +### src/ui/audio.js (230 lines) Audio: chiptune effects and generative music that changes with depth. Everything is synthesized with WebAudio, no samples. - functions: auInit, auVolumes, rateOk, tone, noiseBurst, sfxPlace, playSfx, auAmbient, mtof, zoneForDepth, scaleNote, musicScheduler -- variables: AU, SFX, ZONES +- variables: AU, SFX, SFX_NEAR, SFX_FAR, ZONES ### src/ui/state.js (67 lines) Game shell (src/ui/): browser only. This file: settings, the GAME state object, DOM helpers, toasts and the status line. diff --git a/docs/agent-log.md b/docs/agent-log.md index 532b3ed..b4aaba3 100644 --- a/docs/agent-log.md +++ b/docs/agent-log.md @@ -4,6 +4,18 @@ 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-23: world sounds are heard around the bot, not the view (Claude Code) + +Note: "the sound effect is still heard when the player's zoom level is farther out… sounds +shouldnt be heard based on the viewport, but rather in a radius from the player." + +- `src/ui/audio.js` `sfxPlace`: gain from the distance to the bot's centre (full within 24 + grains, silent at 96), pan from the horizontal offset; zoom no longer matters. +- `test/play.py`: the audio checks use points relative to the bot; a new check zooms out to 2 + and plays a visible sizzle 200 grains away: silent (it played before). `play.py` and all 11 + headless suites pass. `docs/DESIGN.md`. +- Open: nothing. + ## 2026-10-23: F on an ingot mold does nothing and says nothing (Claude Code) Note: "now that ingot molds empty themselves, F still makes a message…" diff --git a/src/ui/audio.js b/src/ui/audio.js index edfef2d..bc8199e 100644 --- a/src/ui/audio.js +++ b/src/ui/audio.js @@ -118,10 +118,12 @@ const SFX = { sale() { if (!AU.ctx) return; const t = AU.ctx.currentTime; [784, 988, 1175, 1568].forEach((f, k) => tone(f, 0.09, { wave: 'p25', vol: 0.05, at: t + k * 0.06 })); }, discover() { if (!rateOk('disc', 500)) return; const t = AU.ctx && AU.ctx.currentTime; tone(880, 0.06, { wave: 'p25', vol: 0.04, at: t }); tone(1175, 0.1, { wave: 'p25', vol: 0.04, at: t + 0.06 }); }, }; +// World sounds are heard around the bot, whatever the zoom: full within 24 grains, silent at 96 +const SFX_NEAR = 24, SFX_FAR = 96; function sfxPlace(x, y) { - if (typeof R === 'undefined' || !R.bw) return { gain: 1, pan: 0 }; - const dx = Math.max(R.vx0 - x, 0, x - R.vx0 - R.bw), dy = Math.max(R.vy0 - y, 0, y - R.vy0 - R.bh); - return { gain: Math.max(0, 1 - Math.hypot(dx, dy) / R.bw), pan: Math.max(-1, Math.min(1, (x - R.vx0 - R.bw / 2) / (R.bw / 2))) * 0.6 }; + if (typeof bot === 'undefined' || !bot.w) return { gain: 1, pan: 0 }; + const dx = x - (bot.x + bot.w / 2), dy = y - (bot.y + bot.h / 2), d = Math.hypot(dx, dy); + return { gain: Math.max(0, Math.min(1, (SFX_FAR - d) / (SFX_FAR - SFX_NEAR))), pan: Math.max(-1, Math.min(1, dx / SFX_FAR)) * 0.6 }; } function playSfx(name, arg, x, y) { if (!AU.ctx || !SFX[name]) return; diff --git a/test/play.py b/test/play.py index d1e346e..1c00853 100644 --- a/test/play.py +++ b/test/play.py @@ -26,15 +26,18 @@ with sync_playwright() as p: pg.wait_for_timeout(500) ev = pg.evaluate ev("window._maxStatus = ''; const _ss = setStatus; setStatus = function (s, k) { if (s && s.length > _maxStatus.length) _maxStatus = s; return _ss(s, k); }; 0") - # World effects use the view, and inaudible ones must not throttle nearby sounds. + # World sounds are heard around the bot at any zoom, and inaudible ones must not throttle nearby sounds. ev("auInit(); window._noiseCalls = 0; window._noiseBurst = noiseBurst; noiseBurst = function (...args) { _noiseCalls++; return _noiseBurst(...args); }; 0") + ev("window._bc = (dx, dy) => [bot.x + bot.w / 2 + dx, bot.y + bot.h / 2 + dy]; 0") try: - far = ev("(() => { const n = _noiseCalls; AU.last.sizzle = 0; const x = R.vx0 + R.bw * 3, y = R.vy0 + R.bh / 2; playSfx('sizzle', 0, x, y); return [_noiseCalls - n, sfxPlace(x, y).gain, AU.last.sizzle]; })()") - check('a sound far off-screen is silent', far == [0, 0, 0], far) - near = ev("(() => { const n = _noiseCalls; AU.last.sizzle = 0; const x = R.vx0 + R.bw / 2, y = R.vy0 + R.bh / 2; playSfx('sizzle', 0, x, y); return [_noiseCalls - n, sfxPlace(x, y).gain, AU.bus == null]; })()") - check('a sound on screen plays', near == [1, 1, True], near) - quiet = ev('sfxPlace(R.vx0 + R.bw * 1.5, R.vy0 + R.bh / 2)') - check('a sound just off-screen is quieter', 0 < quiet['gain'] < 1 and quiet['pan'] > 0, quiet) + far = ev("(() => { const n = _noiseCalls; AU.last.sizzle = 0; const [x, y] = _bc(200, 0); playSfx('sizzle', 0, x, y); return [_noiseCalls - n, sfxPlace(x, y).gain, AU.last.sizzle]; })()") + check('a sound 200 grains from the bot is silent', far == [0, 0, 0], far) + near = ev("(() => { const n = _noiseCalls; AU.last.sizzle = 0; const [x, y] = _bc(10, 0); playSfx('sizzle', 0, x, y); return [_noiseCalls - n, sfxPlace(x, y).gain, AU.bus == null]; })()") + check('a sound next to the bot plays at full volume', near == [1, 1, True], near) + quiet = ev('sfxPlace(..._bc(60, 0))') + check('a sound 60 grains to the right is quieter and panned right', 0 < quiet['gain'] < 1 and quiet['pan'] > 0, quiet) + zoomed = ev("(() => { const z = SET.zoom; SET.zoom = 2; resizeRender(); const n = _noiseCalls; AU.last.sizzle = 0; const [x, y] = _bc(200, 0); const vis = x < R.vx0 + R.bw; playSfx('sizzle', 0, x, y); SET.zoom = z; resizeRender(); return [vis, _noiseCalls - n]; })()") + check('zoomed far out, a visible sound 200 grains away stays silent', zoomed == [True, 0], zoomed) finally: ev('noiseBurst = _noiseBurst; delete window._noiseBurst; delete window._noiseCalls; 0') look = sys.argv[2] if len(sys.argv) > 2 else 'pixel'