merge(core): the enum verification campaign, onto the post-deletion tree
Brings `github/overnight/enums` (`c19680fd`) forward onto the V11 tree. The branch was cut at `b70b9832`, before the OpenGL deletion, and the two lines of work turned out to be disjoint: the enum campaign lives entirely in `AcDream.Core` and its tests, while V11 emptied `AcDream.App`. The merge is clean — no conflicting file on either side. What it carries: names for AC's seven property tables verified against two oracles, a correction to `DamageType`'s rotated bits and `ItemType`'s shifted craft ladder, the retail members the equipment and physics enums were missing, and names for `AmmoType`, `CombatUse` and `ItemUseable`. Five commits, seventeen files, +3,767 / -27 lines. Verified on the merge result rather than on the branch: Release build 0 errors, no new warning attributable to any file the branch touches, and `AcDream.Core.Tests` at 3,893 passed / 2 skipped / 3,895. The campaign's claimed +597 is exact — the `Properties` namespace alone runs 597 tests, all passing. The campaign's open decision items — whether to adopt `WeenieError` wholesale, whether the `SoundId` subset is the right cut, and the re-clone of the ACE and Chorizite references that `references/` no longer holds — are not settled here. They are carried into the morning report as questions for the user. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
commit
cd2f3feae2
17 changed files with 3767 additions and 27 deletions
|
|
@ -17,7 +17,7 @@ once Campaign V closes.
|
|||
|
||||
| Item | Existing foundation | Shape of the work |
|
||||
|---|---|---|
|
||||
| **VERIFY all enums** | `docs/research/2026-06-04-property-enum-divergence.md`: 929 values missing across 7 `Property*` enums, plus the `acdream-property-enum-sweep` skill for regeneration. | Extend the sweep beyond Property* to every wire-adjacent enum family; adopt values with ACE as oracle; conformance tests per enum. |
|
||||
| **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. |
|
||||
|
||||
|
|
|
|||
260
docs/research/2026-07-29-enum-verification-campaign.md
Normal file
260
docs/research/2026-07-29-enum-verification-campaign.md
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
# Enum verification campaign — 2026-07-29
|
||||
|
||||
Bucket B item 1 of [`docs/plans/2026-07-29-post-vulkan-work-intake.md`](../plans/2026-07-29-post-vulkan-work-intake.md).
|
||||
Successor to the (missing — see Open question 1) 2026-06-04 property-enum
|
||||
divergence note. Run unattended against base `b70b9832`.
|
||||
|
||||
**Headline: the campaign found two real value bugs and one wrong comment, all in
|
||||
enums nothing currently reads.** They were traps armed for the next person to
|
||||
write a comparison, not live defects. Everything else diffs clean.
|
||||
|
||||
---
|
||||
|
||||
## 1. The oracle set actually used
|
||||
|
||||
CLAUDE.md's reference hierarchy names six vendored repos. In this environment
|
||||
**`references/ACE`, `references/Chorizite.ACProtocol`, `references/holtburger`,
|
||||
`references/ACViewer`, `references/AC2D` and `references/DatReaderWriter` are all
|
||||
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:
|
||||
|
||||
| # | 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." |
|
||||
| 2 | `references/acclientlib/UtilityBelt.Common/Enums/Enums.cs` | 18,313-line client-side enum catalog, shipped beside a vendored `ACE.Entity` tree. Holds the seven property tables as `IntId`/`BoolId`/`FloatId`/… | Strong, **not infallible** — see §2. |
|
||||
| 3 | `references/weenies/**/*.json` | 38,985 ACE weenie exports. Every stat entry carries the numeric key *and* the enum member name in its `_comment`; enum-valued ints also name the value member after `=`. | Independent mechanical attestation, limited to values some weenie actually sets. |
|
||||
| 4 | `references/WorldBuilder/.../ACEnums.cs` | 233 lines, a handful of property members. | Spot check only. |
|
||||
|
||||
Retail does **not** name the seven property tables anywhere in `acclient.h`
|
||||
(searched as `SType*`, `Property*`, and by member name). The client receives
|
||||
property keys as opaque `u32`, so oracles 2 and 3 carry those alone.
|
||||
|
||||
**Why having more than one oracle mattered:** on `ItemType.CraftFletchingBase`
|
||||
the *catalog* is wrong (says `0x02000000`) and acdream was right (`0x01000000`,
|
||||
matching retail). Had the campaign trusted the catalog the way the mission brief
|
||||
assumed it would trust ACE, it would have introduced a bug. No single source was
|
||||
taken on faith; retail's header decided every disagreement, with the weenie
|
||||
corpus as tiebreak.
|
||||
|
||||
---
|
||||
|
||||
## 2. Bugs found and fixed
|
||||
|
||||
### 2.1 `DamageType` — four bits rotated (`f3e95a3e`)
|
||||
|
||||
acdream assigned `Nether/Mana/Health/Stamina` to `0x80/0x100/0x200/0x400`.
|
||||
Retail `DAMAGE_TYPE` (acclient.h:3788) assigns `Health/Stamina/Mana/Nether`.
|
||||
|
||||
| value | acdream (before) | retail | catalog | weenie corpus |
|
||||
|---|---|---|---|---|
|
||||
| 0x80 | Nether | **Health** | — | — |
|
||||
| 0x100 | Mana | **Stamina** | — | Stamina (n=2) |
|
||||
| 0x200 | Health | **Mana** | — | Mana (n=8) |
|
||||
| 0x400 | Stamina | **Nether** | — | Nether (n=9) |
|
||||
|
||||
`BASE_DAMAGE_TYPE = 0x10000000` was also absent. Both of acdream's live
|
||||
damage-type name tables — `CombatChatTranslator.FormatDamageType` (ported from
|
||||
holtburger) and `ItemAppraisalTextFormatter.TryDamageTypeName` — already used
|
||||
retail's order reading the raw wire `uint`, and `CombatChatTranslator` already
|
||||
knew about `base`. The enum was the only thing in the tree that was wrong, and
|
||||
nothing consumed the rotated members. **No live mislabel; latent trap removed.**
|
||||
|
||||
### 2.2 `ItemType` — shifted craft ladder + locally recomputed composites (`f3e95a3e`)
|
||||
|
||||
Retail `ITEM_TYPE` (acclient.h:3300) leaves `0x02000000` unused and puts
|
||||
`CRAFT_ALCHEMY_INTERMEDIATE` on `0x04000000`. acdream had alchemy-intermediate
|
||||
on `0x02000000` and an **invented `CraftCookingIntermediate`** squatting on
|
||||
`0x04000000`. The weenie corpus attests `0x04000000` as
|
||||
`Craft_Alchemy_Intermediate` 235 times and contains no cooking-intermediate at
|
||||
all — there is no such item type.
|
||||
|
||||
Separately the composite masks were derived from the local primitives instead of
|
||||
transcribed, which is how the ladder drifted in the first place:
|
||||
|
||||
| member | acdream (before) | retail |
|
||||
|---|---|---|
|
||||
| `Weapon` | `0x8101` (= melee\|missile\|caster) | `0x101` — melee\|missile, **no caster** |
|
||||
| `WeaponOrCaster` | `0x8101` (alias of `Weapon`) | `0x8101` |
|
||||
| `Item` | `0x830F` | `0x2DFBEF` |
|
||||
|
||||
Five retail composites acdream never had (`PortalMagicTarget`,
|
||||
`LockableMagicTarget`, `ItemEnchantableTarget`,
|
||||
`RedirectableItemEnchantmentTarget`, `VendorShopkeep`, `VendorGrocer`) came
|
||||
along. Only one site in the tree referenced any changed member — a test that
|
||||
wants a nonzero `HookItemTypes` and does not care which — so **no branch changed.**
|
||||
|
||||
### 2.3 `EquipMask` — a wrong type remark (`8ccaf72a`)
|
||||
|
||||
A remark claimed retail's `CLOTHING_LOC` composite "also sets bit 31,
|
||||
0x80000000, which is not a named INVENTORY_LOC primitive". It does not.
|
||||
`CLOTHING_LOC` is `0x080001FF`: the nine wear slots plus **bit 27**, which is the
|
||||
perfectly well-named `Cloak` slot. No `INVENTORY_LOC` member touches bit 31 at
|
||||
all — `ALL_LOC` stops at bit 30. Caught by writing the composite test.
|
||||
|
||||
---
|
||||
|
||||
## 3. Per-family end state
|
||||
|
||||
### 3.1 The seven `Property*` tables (`251dd68a`)
|
||||
|
||||
acdream had **no** `Property*` enums at all — property IDs were bare `uint`s with
|
||||
the meaning carried in prose (`private const uint EncumbranceValProperty = 5u`
|
||||
duplicated across two files, `UiEffects` as "ACE enum value 18" in a doc comment).
|
||||
All seven are now created under `AcDream.Core.Properties`.
|
||||
|
||||
| family | members adopted | attested by both oracles | single-sourced |
|
||||
|---|---|---|---|
|
||||
| `PropertyInt` | 391 | 189 | 202 |
|
||||
| `PropertyFloat` | 172 | 86 | 86 |
|
||||
| `PropertyBool` | 131 | 59 | 72 |
|
||||
| `PropertyDataId` | 62 | 41 | 21 |
|
||||
| `PropertyString` | 53 | 28 | 25 |
|
||||
| `PropertyInstanceId` | 46 | 2 | 44 |
|
||||
| `PropertyInt64` | 9 | 3 | 6 |
|
||||
| **total** | **864** | **408** | **456** |
|
||||
|
||||
Zero value conflicts between the two oracles across all seven, and the corpus
|
||||
contained no key the catalog lacked — the catalog is a strict superset of
|
||||
everything 38,985 weenies set. Three members differ in *spelling* only; acdream
|
||||
took ACE's (`ItemType`/`HookItemType`/`MerchandiseItemTypes` over the catalog's
|
||||
`ObjectType`/…), which is what the corpus emits and what acdream's own `ItemType`
|
||||
already called it.
|
||||
|
||||
### 3.2 Existing wire-adjacent enums, diffed against retail
|
||||
|
||||
Legend: **closed** = every retail member present at retail's value.
|
||||
|
||||
| acdream enum | retail enum | end state |
|
||||
|---|---|---|
|
||||
| `ItemType` | `ITEM_TYPE` | **closed** (2 fixed) — retail's `TYPE_UNDEF`/`TYPE_SELF` are both 0; acdream spells the single 0 `None` |
|
||||
| `DamageType` | `DAMAGE_TYPE` | **closed** (4 fixed, 1 added) |
|
||||
| `EquipMask` | `INVENTORY_LOC` | **closed** — 43/43, 11 composites adopted |
|
||||
| `TransientStateFlags` | `TransientState` | **closed** — `WaterContact`, `CheckEthereal` adopted |
|
||||
| `PhysicsStateFlags` | `PhysicsState` | **closed** — `ReservedUnused1/2` adopted |
|
||||
| `ObjectInfoState` | `ObjectInfoEnum` | **closed** — was already exact (`None` = `DEFAULT_OI`) |
|
||||
| `AttackHeight` | `ATTACK_HEIGHT` | **closed** — `Undef` adopted; `NUM_ATTACK_HEIGHTS` is a count, deliberately not a member |
|
||||
| `HoldKey` | `HoldKey` | **closed** — `Num_HoldKeys` is a count |
|
||||
| `AttackType` | `AttackType` | **already correct**, incl. both composites (`Unarmed` 0x19, `MultiStrike` 0x79E0) |
|
||||
| `RadarBlipShape` | `RadarBlipShape` | already exact (14/14) |
|
||||
| `RadarBehavior` | `RadarEnum` | already exact (5/5) |
|
||||
| `MovementType` | `MovementTypes::Type` | already exact (10/10) |
|
||||
| `ParticleType` | `ParticleType` | already exact (14/14) |
|
||||
| `PhysicsDescriptionFlag` | `PhysicsDesc::PhysicsDescInfo` | already exact (naming only: `AnimationFrame`/`ANIMFRAME_ID`) |
|
||||
|
||||
### 3.3 New enums for wire fields acdream parsed but never named (`3efa266a`)
|
||||
|
||||
| enum | retail source | members | why |
|
||||
|---|---|---|---|
|
||||
| `AmmoType` | acclient.h:4221 | 10 | parsed via `PublicWeenieDesc._ammo_type`; the appraisal sentence matched raw hex |
|
||||
| `CombatUse` | acclient.h:6523 | 6 | `PropertyInt.CombatUse` (51) |
|
||||
| `ItemUseable` | acclient.h:6478 | 39 | `PropertyInt.ItemUseable` (16); two 16-bit halves, ~30 named combinations |
|
||||
|
||||
`ItemAppraisalTextFormatter`'s ammunition fold now reads through `AmmoType`. Its
|
||||
crystal/chorizite → base collapse was verified correct against retail's bit
|
||||
layout before the change; behaviour is unchanged.
|
||||
|
||||
### 3.4 Verified correct, no change needed
|
||||
|
||||
- `ItemAppraisalTextFormatter` ammo fold (§3.3) against `AMMO_TYPE`.
|
||||
- `CombatChatTranslator.FormatDamageType` and
|
||||
`ItemAppraisalTextFormatter.TryDamageTypeName` against `DAMAGE_TYPE` — both
|
||||
already retail-correct, which is what exposed §2.1.
|
||||
|
||||
---
|
||||
|
||||
## 4. Open questions
|
||||
|
||||
Each is something the campaign could **not** settle from an oracle. Per
|
||||
CLAUDE.md these are recorded rather than guessed.
|
||||
|
||||
1. **The 2026-06-04 research drop is missing.** `claude-memory/MEMORY.md` indexes
|
||||
`research/2026-06-04-property-enum-divergence.md` (the "929 values across 7
|
||||
enums" ledger) and `research/2026-06-04-magic-number-audit.md`. Neither exists
|
||||
in the working tree, under any ref (`git log --all --diff-filter=A`), or in the
|
||||
memory directory — which has no `research/` subfolder at all. The 929 figure
|
||||
could not be reproduced or audited; this campaign regenerated from scratch and
|
||||
arrived at 864 property members. **Blocker:** source documents absent. Either
|
||||
they were never committed or they lived in a discarded worktree. Suggest
|
||||
fixing the MEMORY.md index entries to point at this doc.
|
||||
2. **Five of six reference repos are empty** (§1). Everything sourced here came
|
||||
from retail + the UtilityBelt catalog + the weenie corpus. **Blocker:** repos
|
||||
not cloned in this environment. Re-cloning ACE and Chorizite would let the 456
|
||||
single-sourced property members (§3.1) be promoted to two-oracle confirmed.
|
||||
3. **456 of 864 property members are single-sourced.** They are transcribed from
|
||||
the catalog, not invented, but no weenie in the corpus sets them so there is no
|
||||
independent attestation. **Blocker:** needs ACE source, or the client DAT's own
|
||||
`EnumMapper` file type (`acclientlib` has a reader for it — a genuinely retail
|
||||
oracle, and the most promising unexplored lead).
|
||||
4. **`WeenieError` — 362 unadopted status codes.** acdream has 16 members; the
|
||||
catalog's `StatusMessage` has 372, with 10 shared and zero conflicts. Retail's
|
||||
`charError` (26) is a *different* enum — character-creation errors only — so
|
||||
retail cannot arbitrate. Register row **AP-15** already tracks that acdream's
|
||||
translation table covers only ~30 common codes. **Blocker:** needs a decision
|
||||
on importing 362 server-side codes wholesale, and ideally retail's
|
||||
`string_table.bin` for the real sentences. Deliberately not done here.
|
||||
5. **`CombatMode` has no located retail counterpart.** acdream's 7 members match
|
||||
the catalog's `CombatMode` (`Magic = 8`). Retail's `CombatStyle` (25 members,
|
||||
`Magic_CombatStyle = 512`) is a different enum — weapon-style, not combat mode.
|
||||
A retail `COMBAT_MODE` was not found in `acclient.h`. **Blocker:** oracle not
|
||||
located; acdream is probably right but is single-sourced.
|
||||
6. **`PublicWeenieFlags` counterpart unidentified.** acdream's 17 members
|
||||
(`Attackable`, `Door`, `Vendor`, …) share exactly one name with retail's
|
||||
`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.
|
||||
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
|
||||
identified. Relevant to the Bucket C chatbox cleanup.
|
||||
9. **`GameEventType` (103 members) was not verified.** No counterpart found under
|
||||
a matching name in either retail or the catalog. **Blocker:** needs the wire
|
||||
catalog (Bucket B item 2) to supply the pairing.
|
||||
10. **`MaterialType` not adopted.** acdream parses it as raw `uint` from
|
||||
`PublicWeenieDesc`; the catalog has 108 members; no retail counterpart located
|
||||
in `acclient.h`. **Blocker:** single-sourced, and unused so far.
|
||||
11. **`SpellTargetType`, `ItemPrimaryUseResult`, `PositionFlags`,
|
||||
`EnchantmentMask`/`EnchantmentBucket`, `GameMessageGroup`, `InventoryRequestKind`
|
||||
are acdream-local** with no retail counterpart at the same semantics. Confirmed
|
||||
for `EnchantmentMask`/`EnchantmentBucket`: they are PlayerDescription wire
|
||||
*trailer bucket* flags (ACE's `EnchantmentMask`), not retail's
|
||||
`EnchantmentTypeEnum` — the apparent value conflict is a false pairing. The
|
||||
others were not individually run down.
|
||||
12. **Tooling limitation, for whoever re-runs this.** The extractor reads one line
|
||||
per enum member, so a member whose value spans multiple lines is truncated.
|
||||
This produced a false `AttackType.MultiStrike` conflict (read as `0x1E0`,
|
||||
actually `0x79E0`). Any automated re-run must join continuation lines before
|
||||
trusting a reported conflict.
|
||||
|
||||
---
|
||||
|
||||
## 5. Reproducing
|
||||
|
||||
Extraction and diff scripts were scratch tooling, not committed (they hardcode
|
||||
absolute paths into `references/`). The method, in order:
|
||||
|
||||
1. Parse `acclient.h` for `enum NAME {` blocks; resolve member expressions
|
||||
(hex, digit separators, references to earlier members).
|
||||
2. Parse the UtilityBelt catalog the same way.
|
||||
3. Walk `references/weenies/**/*.json`, pulling `(key, value, _comment)` from each
|
||||
`*Stats` array; the comment's left side is the property name, the right side
|
||||
(after `=`) names the value member for enum-typed ints.
|
||||
4. Normalise names across conventions (retail `SCREAMING_SNAKE` with family
|
||||
prefix/suffix ↔ acdream `PascalCase`) and diff on the normalised key, comparing
|
||||
values exactly.
|
||||
5. Any disagreement: retail decides, corpus breaks ties, and anything still
|
||||
unresolved becomes an open question above rather than a guess.
|
||||
|
||||
## 6. Commits
|
||||
|
||||
| commit | slice |
|
||||
|---|---|
|
||||
| `251dd68a` | the seven `Property*` tables (864 members, 429 tests) |
|
||||
| `f3e95a3e` | `DamageType` rotation + `ItemType` craft ladder and composites |
|
||||
| `8ccaf72a` | `EquipMask` composites, `TransientState`, `PhysicsState`, `AttackHeight` |
|
||||
| `3efa266a` | `AmmoType`, `CombatUse`, `ItemUseable` |
|
||||
Loading…
Add table
Add a link
Reference in a new issue