docs(enums): the 2026-07-29 verification campaign, end to end
The ledger the campaign owed: which oracles were actually available, what each family's end state is, what got fixed and why, and - the part that matters most for whoever picks this up - the twelve things that could not be settled from an oracle and are therefore recorded as open questions rather than guessed. Two findings deserve to survive past the morning report. The first is that five of the six vendored reference repos named in CLAUDE.md are empty directories in this environment. ACE, Chorizite, holtburger, ACViewer, AC2D and DatReaderWriter contain nothing, so the campaign re-anchored on the retail header itself - which CLAUDE.md ranks above ACE anyway - with the UtilityBelt enum catalog and the 38,985-file ACE weenie corpus as cross-checks. That turned out to be the more rigorous arrangement rather than a compromise, because of the second finding: the catalog is wrong about CraftFletchingBase, where retail and acdream agree. Trusting any single source, including the one the brief nominated, would have introduced a bug. Retail's header decided every disagreement and the weenie corpus broke ties. Also recorded: the 2026-06-04 property-enum divergence note that this work was supposed to build on does not exist - not in the tree, not under any ref, not in the memory directory, which has no research/ subfolder at all. The MEMORY.md index points at both it and a magic-number audit that is equally absent. The sweep was regenerated from scratch instead, and landed on 864 property members against the missing note's remembered 929. Someone should repoint those index entries. The Bucket B intake row is marked done and points here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
3efa266a61
commit
c19680fd69
2 changed files with 261 additions and 1 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