feat(audio): Campaign A slice A3 — the server sound channel (0xF750)
acdream never parsed retail's Sound event, so every server-driven cue was silent: melee hits and wounds, wield/unwield, pickup/drop, lockpicking, lifestone bind, spell resist, trap triggers, item mana depletion. SoundEvent parses the 16-byte message (guid, SoundType, f32 volume) whose layout three oracles agree on: retail CM_Physics::DispatchSB_SoundEvent @0x006AC760 reading buf+4/+8/+0xC, ACE's GameMessageSound at declared length 16, and holtburger's PlaySoundData. Playback reuses EntityEffectController's existing per-guid queue rather than adding a second one, because retail routes sounds through the SAME CObjectMaint blob queue as F754/F755: an event for a guid the client does not know yet is parked and drained by HandleCreateObject, so a creature that spawns and immediately grunts still grunts. Dropping it — the obvious alternative — would silently lose the cue. Sound joins Direct and Typed as a third PendingEffect kind so one readiness edge releases the whole mixed stream in order. AudioHookSink.PlayServerSound reproduces two decoded asymmetries with the animation-hook path: the sound plays at the WIRE volume and the SoundTable entry's volume is ignored (the hook path does the opposite), while the entry's probability still gates it and its priority still drives eviction. An object with no SoundTable plays nothing, matching CPhysicsObj::play_sound @0x0050F460's early return. The no-window host parses and discards, exactly as it does for F754/F755 — sound is presentation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
e42b99482e
commit
8bc458fb88
13 changed files with 420 additions and 24 deletions
|
|
@ -33,15 +33,22 @@ Retail is a **2D pan+gain engine**, not 3D audio. Every gameplay buffer is
|
|||
created with `m_3D = 0`; the DirectSound 3D listener the client sets up is
|
||||
dead code. Spatialization is CPU-side per voice at play time:
|
||||
|
||||
- **Gain** (`SoundManager::GetAttenuation @ 0x00550AD0` region; byte-decoded):
|
||||
`g = dist < 5m ? vol : 25·vol/dist²`, clamped to 1.0, multiplied by ONE
|
||||
master knob (`effect_sound_volume` or `ambient_sound_volume`), then
|
||||
`db = ceil(20·log10 g)` with a hard floor at −50 dB — below the floor the
|
||||
voice is **not allocated at all**. Audible radius ≈ 94 m at vol 1.0.
|
||||
- **Pan**: `pan_dB = −15·sin(Δheading listener→source)`, saturated at
|
||||
±15 dB, forced to 0 inside 5 m. No front/back, no elevation.
|
||||
- **Listener** = the player physics object's position/heading
|
||||
(`SoundManager::SetPlayerPosition`), NOT the camera.
|
||||
- **Gain** (`SoundManager::GetAttenuation @ 0x00550020`; byte-decoded):
|
||||
`g = dist < 5m ? vol : 25·vol/dist²`, clamped to 1.0, then multiplied by ONE
|
||||
master knob (`effect_sound_volume` or `ambient_sound_volume`) — clamp
|
||||
first, multiply second — then `db = ceil(20·log10 g)` with a hard floor at
|
||||
−50 dB, below which the voice is **not allocated at all**. Audible radius
|
||||
≈ 94.2 m at vol 1.0.
|
||||
- **Pan**: `pan_dB = (int)(−15·sin(Δbearing))`, truncating toward zero,
|
||||
saturated at ±15 dB, forced to 0 when `(int)distance < 5`. No front/back,
|
||||
no elevation.
|
||||
- **Listener** = `SmartBox::viewer` — the **collided third-person camera**
|
||||
Position, refreshed once per rendered frame (`SmartBox::set_viewer` @
|
||||
`0x00452D36`, `SmartBox::update_viewer` @ `0x00453CE0`), falling back to the
|
||||
player's own position when the camera sweep fails. Only its origin and
|
||||
`Frame::get_heading` are read. (An earlier draft of this plan said the
|
||||
listener is the player and called acdream's camera listener a defect —
|
||||
wrong, and corrected at A2. Do not re-"fix" it.)
|
||||
- **Voice pool**: allocator is `SoundManager::PlaySoundInternal @ 0x0054FEC0`.
|
||||
Eviction compares the DAT-authored **float priority** (0..1); equal
|
||||
priority never evicts. (`FUN_00550AD0` cited in our code is a hash-table
|
||||
|
|
@ -297,7 +304,7 @@ global kill switch.
|
|||
|---|---|---|---|
|
||||
| A1 | **COMPLETE** 2026-08-08 | `c69b3bde` | 42 Core audio tests; full Release suite 11,563 passed / 4 skipped / 0 failed. Closes #355. |
|
||||
| A2 | **COMPLETE** 2026-08-08 | `6d0156cb` | 118 Core audio tests (mixer + voice pool + cookbook); full Release suite 11,639 passed / 4 skipped / 0 failed. Opus review run and applied — 2 HIGH (pan-law saturation, stale `FUN_00550ad0` header), 5 MEDIUM (untested clamp order / pan truncation / voice pool, dead `PlayingGain`, duplicated heading helper), 5 LOW. Retires AP-28; files AP-173, AP-174, TS-64, TS-65. **Owed: user listening gate.** |
|
||||
| A3 | — | — | — |
|
||||
| A3 | **COMPLETE** 2026-08-08 | `3fae0c7d` | 14 wire-conformance tests + 5 controller tests; full Release suite 11,658 passed / 4 skipped / 0 failed. **Owed: connected gate** (melee hit / pickup / lifestone audible against ACE). |
|
||||
| A4 | — | — | — |
|
||||
| A5 | — | — | — |
|
||||
| A6 | — | — | — |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue