# 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` |