feat(core): adopt retail's SoundType catalog; retire AC2D

SoundId was not a subset of retail's table, the way its comment claimed.
It was an invention: 23 acdream-local names on acdream-local values, and
the values were wrong in the way that matters. FootstepDefault = 0x02 is
retail's Random. SwingSword = 0x10 is retail's Death2. Death = 0x60 is
retail's Explode. Anyone who reached for one of those names to compare
against a wire or dat value would have got a different sound.

Nothing referenced any of them by name -- grep for `SoundId.` across src
and tests returns nothing -- so this was a trap rather than a live defect,
the same shape the enum campaign found in DamageType. All 22 invented names
are deleted and retail's 205 replace them.

Three oracles agree exactly, on every name and every value: retail
acclient.h:4569 enum SoundType, ACE's Sound, and DatReaderWriter's Sound.
The third matters most. AudioHookSink already resolves SoundTable lookups
through DatReaderWriter.Enums.Sound, so that is the enum acdream actually
reads at runtime; our catalog now agrees with the values already flowing
through the dat path, and a conformance test pins the two so they cannot
drift apart.

On the "206 sounds" figure: retail's block holds 207 entries, being 205
sounds followed by NUM_SOUND_TYPES = 0xCD and FORCE_SoundType_32_BIT. The
first is a count and the second a width pin. Counting the former is where
206 came from. Neither is a member here, matching how the campaign treated
NUM_ATTACK_HEIGHTS and Num_HoldKeys -- a count is not a value the wire can
carry.

Behaviour is unchanged and could not be otherwise: the enum had no
consumers. IAudioEngine's three SoundId overloads are no-op stubs and the
live path takes wave ids and DatReaderWriter values.

The user's separate report that sound is "not working that good" is a
triggering, selection and attenuation question rather than a catalog one,
and is filed as its own Bucket B row in the post-Vulkan intake.

Also in this commit, by user decision: AC2D is retired as a reference. Its
clone and directory are gone and it must not be re-cloned. Everything we
took from it still stands and is written down -- the FSplitNESW terrain
split constants, the 0xF61C movement packet layout, the finding that a
client need not compute terrain Z itself -- so CLAUDE.md's reference list,
its hierarchy table, and the architecture doc's protocol row now point at
docs/research/2026-04-12-movement-deep-dive.md rather than erasing the
history. The reference count drops from six to five.

Core tests 3907 passed / 2 skipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-29 07:38:56 +02:00
parent f27ad9ee43
commit 39c1737bda
7 changed files with 601 additions and 58 deletions

View file

@ -1135,7 +1135,7 @@ For acdream-specific code (renderer, plugin API, streaming):
| Animation | `docs/research/named-retail/` + ACE Animation/ | — |
| Terrain | ACME ClientReference.cs | named retail / older decompiled chunks |
| Rendering | WorldBuilder (Silk.NET; its GL calls now read as RHI intent) | ACViewer |
| Protocol | holtburger | AC2D |
| Protocol | holtburger | `docs/research/named-retail/` (AC2D was the secondary until 2026-07-29 — retired reference; historical analysis remains in `docs/research/2026-04-12-movement-deep-dive.md`) |
| Server behavior | ACE | — |
---

View file

@ -20,6 +20,7 @@ once Campaign V closes.
| **VERIFY all enums****DONE 2026-07-29**, see [`docs/research/2026-07-29-enum-verification-campaign.md`](../research/2026-07-29-enum-verification-campaign.md) | The 2026-06-04 divergence note turned out to be missing from the repo entirely (open question 1 in the successor), so the sweep was regenerated from scratch. | Seven `Property*` tables created (864 members); `DamageType` and `ItemType` value bugs fixed; equipment/physics/item families closed against retail. Twelve open questions remain, headed by five empty `references/` repos. |
| **Verify networking stack** | `docs/research/2026-06-04-wire-message-catalog.md`: 256 opcodes catalogued, 114 parsed. | Audit parsed-vs-stub-vs-missing against the catalog; prioritize by what live servers actually send (Coldeve session logs are now a real corpus); holtburger remains the client-side oracle. |
| **Physics review — retail landing bounce et al.** | The named-retail decomp workflow; carried debt #116 (slide response), TS-50/51/53 in the divergence register. | A focused fidelity pass: start with the user's observed case — long-jump landing should bounce/recoil as retail does (grep `named-retail` for the landing/impact path in `MovementManager`/`SetMotion` land states before writing anything). Each fix follows grep→decompile→pseudocode→port→conformance. |
| **Audio fidelity review** — user-sourced, 2026-07-29 | User reports sound "not working that good" generally. `AudioHookSink` + `OpenAlAudioEngine` are the current path; `SoundId` now carries retail's full 205-entry `SoundType` table (adopted 2026-07-29), so the catalog is no longer the gap. | Scope: sound *triggering*, *selection*, and *attenuation* versus retail — which hooks fire, how a SoundTable entry is chosen among its probability-weighted alternatives, and the falloff/pan curve. Separate from the `SoundId` catalog adoption, which was naming only. Oracles: named-retail's sound path, ACE, and the DAT SoundTable. |
## C. New feature bodies — milestone candidates, no existing spec

View file

@ -19,6 +19,10 @@ empty directories** (`references/*` is gitignored; only WorldBuilder is checked
in). The campaign therefore re-anchored on the oracles that *are* present, which
is no loss of rigour — CLAUDE.md ranks the first of them above ACE anyway:
> This paragraph describes the environment the campaign *ran in*, later the same
> day. It is no longer current: §7 records the restore, and AC2D was retired, so
> CLAUDE.md now names five vendored repos rather than six.
| # | Oracle | What it is | Weight |
|---|---|---|---|
| 1 | `docs/research/named-retail/acclient.h` | The Sept 2013 EoR retail header, verbatim. 348 parseable `enum` blocks. | **Decides.** CLAUDE.md: "beats every other reference for what the real client does." |
@ -201,11 +205,8 @@ CLAUDE.md these are recorded rather than guessed.
`PublicWeenieDescPackHeader` (34), which is the *pack-header* bitfield — a
different thing. `PublicWeenieDesc::BitfieldIndex` (31 members) is the likely
real counterpart. **Blocker:** pairing unconfirmed; not diffed.
7. **`SoundId` is a curated 23-member local subset** of retail's `SoundType`
(206), with acdream-local names (`FootstepGrass`, `BuffApplied`) that do not
correspond 1:1 to retail members. **Blocker:** needs a decision — adopt
retail's table wholesale, or record the subset as an intentional divergence
with a register row. Neither was done.
7. ~~**`SoundId` is a curated 23-member local subset**~~ **RESOLVED 2026-07-29**
by user decision: retail's table adopted wholesale. See §8.2.
8. **`ChatType` / `BlobType` / `DispatchType`** (TurbineChat) share no members
with retail's `eChatTypes` (26) or `ChatTypeEnum` (12); they describe chat
*channels*, not text-display types. **Blocker:** correct counterpart not
@ -261,11 +262,19 @@ absolute paths into `references/`). The method, in order:
## 7. The reference tree, restored (2026-07-29)
Open question 2 is closed. All six vendored repos were re-cloned from their
upstreams into the main checkout at
`C:\Users\erikn\source\repos\acdream\references\`, which every worktree shares.
`references/*` is gitignored (only `WorldBuilder` is checked in as a gitlink), so
none of this enters our history.
Open question 2 is closed. The vendored repos were re-cloned from their upstreams
into the main checkout at `C:\Users\erikn\source\repos\acdream\references\`, which
every worktree shares. `references/*` is gitignored (only `WorldBuilder` is
checked in as a gitlink), so none of this enters our history.
**AC2D was cloned, then dropped by user decision the same day and deleted along
with its directory.** It is a retired reference: do not re-clone it. What we took
from it — the `FSplitNESW` terrain split constants, the `0xF61C` movement packet
layout, and the finding that a client need not compute terrain Z itself — is
already written down in `docs/research/2026-04-12-movement-deep-dive.md` and the
`docs/research/retail-ui/` dat-id work, and those citations stand. CLAUDE.md's
reference list and hierarchy table, and the architecture doc's protocol row, were
updated to say so rather than to erase the history.
Each candidate URL was cross-checked against the description in CLAUDE.md and
`memory/reference_repos.md` before cloning, and the clone was verified to contain
@ -277,7 +286,7 @@ the files those descriptions name.
| ACViewer | `https://github.com/ACEmulator/ACViewer.git` | `c5c54fb3` | 97 MB | cited by URL in `docs/research/2026-07-13-retail-projectile-vfx-pseudocode.md`; also the remote of `repos/ACViewer`. Cloned `--recurse-submodules`, so its `ACE.DatLoader` submodule (`382edb23`) is present as the docs describe |
| Chorizite.ACProtocol | `https://github.com/Chorizite/Chorizite.ACProtocol` | `ff7dffd6` | 4.5 MB | Chorizite org + exact repo name; contains the `Chorizite.ACProtocol` + `.SourceGen` projects CLAUDE.md describes as XML-generated |
| holtburger | `https://github.com/merklejerk/holtburger` | `a7e806cb` | 25 MB | named by URL in `docs/research/2026-05-10-holtburger-network-stack-study.md`; also the remote of `repos/holtburger` |
| AC2D | `https://github.com/deregtd/AC2D` | `3451f0d2` | 17 MB | no URL was recorded anywhere in the repo, so the candidate was verified by content: it is C++, and it holds `cNetwork.cpp`, `cInterface.cpp`, `cPictureBox.h`/`cStaticText.h`/`cEditBox.h`, and `FSplitNESW` in `Landblocks.cpp` — every file the docs attribute to AC2D |
| ~~AC2D~~ | ~~`https://github.com/deregtd/AC2D`~~ | ~~`3451f0d2`~~ | — | **Retired 2026-07-29, deleted.** No URL was recorded anywhere in the repo, so the candidate had been verified by content: C++, holding `cNetwork.cpp`, `cInterface.cpp`, `cPictureBox.h`/`cStaticText.h`/`cEditBox.h`, and `FSplitNESW` in `Landblocks.cpp` — every file the docs attributed to AC2D. Identification was sound; the reference is simply no longer kept |
| DatReaderWriter | `https://github.com/Chorizite/DatReaderWriter` | `c5359870` | 4.2 MB | cited by URL in `docs/research/2026-04-26-datreaderwriter-reference.md`. Not on the restore list, but it was empty for the same reason and is unambiguously identified |
---
@ -329,3 +338,50 @@ naming a code does not make it render. The only site touched is
The enum also moved out of `MotionInterpreter.cs` into its own file at the same
namespace; at 372 members it no longer belongs inside a physics class file.
### 8.2 `SoundId` — retail's full `SoundType` catalog
Open question 7, closed by user decision. This one was not a subset; it was an
invention. The 23 members were acdream-local names on acdream-local values, and
the values were wrong in the way that matters: the old `FootstepDefault = 0x02`
is retail's `Random`, `SwingSword = 0x10` is retail's `Death2`, `Death = 0x60` is
retail's `Explode`. The header comment called it "a sparse subset of retail
enums", which it never was.
Nothing referenced any of the 23 by name — `grep` for `SoundId.` across `src/`
and `tests/` returns nothing — so this was a trap rather than a live defect,
exactly the shape the campaign found in `DamageType` (§2.1). All 22 invented
names are deleted; retail's 205 replace them.
Three oracles agree exactly, on every name and every value:
| oracle | members | agreement |
|---|---|---|
| retail `acclient.h:4569 enum SoundType` | 205 named sounds | decides |
| ACE `ACE.Entity.Enum.Sound` | 205 | identical names and values to retail |
| DatReaderWriter `DatReaderWriter.Enums.Sound` | 205 | identical names and values to retail |
The third is the interesting one. `AudioHookSink` already resolves SoundTable
lookups through `DatReaderWriter.Enums.Sound`, so that enum is what acdream
actually reads at runtime — the adoption makes our own catalog agree with the
values already flowing through the dat path, and a conformance test pins the two
together so they cannot drift.
**On "206".** Retail's block holds 207 entries: 205 sounds, then
`NUM_SOUND_TYPES = 0xCD` and `FORCE_SoundType_32_BIT`. The first is a count and
the second a width pin. Counting `NUM_SOUND_TYPES` is where the 206 figure in
open question 7 came from. Neither is a member here, matching how §3.2 treated
`NUM_ATTACK_HEIGHTS` and `Num_HoldKeys` — a count is not a value the wire can
carry.
**Behaviour: unchanged**, for the same reason as §8.1 and more strongly — the
enum had no consumers at all. `IAudioEngine`'s three `SoundId` overloads
(`PlayUi`, `Play3D`, `StartAmbient`) are no-op stubs; the live audio path takes
wave ids and `DatReaderWriter.Enums.Sound` values, not this type.
The enum moved out of `AudioModel.cs` into its own file at the same namespace.
**Follow-up filed.** The user reports sound "not working that good" generally.
That is a triggering/selection/attenuation question, not a catalog question, and
is now a Bucket B row in
[`docs/plans/2026-07-29-post-vulkan-work-intake.md`](../plans/2026-07-29-post-vulkan-work-intake.md).