Public: anyone can read this; the crew can change it.

forked from trevato/cs

A sound says which side it came from: the ear has a heading, and the engine pans what it hears #217

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`src/engine/sound.ts` grows `panOf(source, ear, yaw)` beside `attenuate`: the component of the direction to a sound along the ear's right — `right = (sin yaw, −cos yaw, 0)`, the same degrees `angleVectors` takes — divided by the distance, or by `HEAD` (64 Source units, about a stride) when that is larger. That gives −1 hard left, +1 hard right, and 0 dead ahead, dead behind, straight overhead or o…

shipped production is serving it deploy ae07c2d

opened this

src/engine/sound.ts grows panOf(source, ear, yaw) beside attenuate: the component of the direction to a sound along the ear's right — right = (sin yaw, −cos yaw, 0), the same degrees angleVectors takes — divided by the distance, or by HEAD (64 Source units, about a stride) when that is larger. That gives −1 hard left, +1 hard right, and 0 dead ahead, dead behind, straight overhead or on top of the ear, with the near-head softening falling out of the same division rather than needing a branch: a body walking through you slides to centre and out the other side instead of flipping speakers in one frame.

Burst and Tone gain an optional pan. A voice that carries one ends on a StereoPannerNode into the destination instead of the destination itself; the value is clamped to −1..1 with NaN and infinities landing at centre before any node sees it. A voice with no pan builds no panner and is wired byte-for-byte as before — which is what the range does today — and a context with no createStereoPanner falls back to the destination rather than throwing, so the three hand-written fakes and the oldest Safari all play the sound centred.

The shooter's shot and play now take the ear's heading as well as its position and hand the pan they compute to every voice of every recipe; src/client/main.ts passes ears.yaw. Your own boots are passed no pan at all, and your own gun is fired from the ear that hears it, so it works out centred without a special case.

Front and back are deliberately not in the answer: two speakers cannot say the difference, and the only honest way to would be a per-voice head model this game does not want.

Worth a look: the panOf table and the no-panner path in src/engine/sound.test.ts; the recorder tests in src/client/audio.test.ts (a step 300 units left of a north-facing ear is −1, right is +1, ahead is 0, and every node of every named noise carries the same pan); and src/client/main.test.ts, where the stage helper now records the heading handed to the voice, so a frame that played from one player's eye with another's yaw would fail. src/range/stage.test-helper.ts and src/range/audio.test.ts grew createStereoPanner on their doubles — not because the range pans, but because a double that answers less than a browser is how the thrown frame hides. The committed bundles are rebuilt and all three stay under their ceilings. No dependencies added; bun run test is 972 passing.

🤖 Generated with Claude Code

Closes #209

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs