feat(player): port retail augmentation stat chain

This commit is contained in:
Erik 2026-07-31 08:08:23 +02:00
parent 0cb60d98a0
commit 461a1fb7b4
22 changed files with 953 additions and 138 deletions

View file

@ -158,7 +158,7 @@ cos(10°) with base 0.2); the jump chain end-to-end (`GetJumpHeight`
0x006b09b0 exact incl. 1300/22.2/0.05/0.35, `InqJumpVelocity` 0x00592980
`vz=sqrt(h*19.6)`, powerbar charge 1.0 s / 0.8 s dual-wield) — the user's
"we jump too high" hypothesis is REFUTED, jump height is retail-parity
(our eff skill is 5 lower pending #268's augmentation port). Retail has no
(the former five-point effective-skill gap was closed by #268). Retail has no
Sledding auto-toggle (P2 finding re-confirmed; no `state |= 0x800000`
writer exists).
@ -175,27 +175,29 @@ alignment.
## #268 — Character panel: vitae color, buff coloring, and augmentation bonuses
**Status:** OPEN (user direction 2026-07-30, follow-up to #267)
**Status:** IMPLEMENTED 2026-07-31 — closure pends the user visual/live gate.
**Severity:** MEDIUM (presentation parity)
**Component:** retained UI / character window
**User direction:** (a) the vitae parenthetical (text + number) renders in
light blue — VERIFY the exact retail color from the decomp/DAT before
hardcoding (the chat-color cdb recipe in
`claude-memory/reference_retail_chat_colors.md` is the fallback method);
(b) when a buff-like effect is active (including AUGMENTATIONS such as
+Acdream's +5-all-skills), affected skills AND attributes render green
(buff coloring) — #267's CurrentLevel/BaseLevel split made the green/red
coloring live, but augmentation bonuses are not part of the effective
value yet, so the color never triggers for them; (c) augmentation skill
bonuses must be included in the displayed effective values.
**Resolution:** the shared Core `PlayerSkillMath` now ports
`CACQualities::InqSkill @ 0x00592660` in retail order for both the
character panel and Runtime movement: intrinsic skill, positive
`LumAugAllSkills` (0x16D), the authored +10 melee/missile/magic category
augmentation, `EnchantSkill`, then +5 Jack of All Trades (0x146) and
`2 × LumAugSkilledSpec` (0x158) for specialized skills. Live player
PropertyInt updates recompute the Runtime snapshot, so the display and
run/jump prediction cannot drift.
**Register link:** this PROMOTES AP-127 — the "two minor unmodeled retail
bonus properties" (0x146/0x158 family — the all-skills augmentation
terms feeding `CACQualities::InqRunRate`/`InqJumpVelocity` BEFORE
`EnchantSkill`) are user-visible in both the panel and potentially
movement speed. Port them into the CACQualities chain (movement) and the
panel's effective-value computation together, decomp-first.
`AttributeInfoRegion::Update @ 0x004F1910`,
`Attribute2ndInfoRegion::Update @ 0x004F19E0`, and
`SkillInfoRegion::Update @ 0x004F1AE0` now drive exact value coloring:
green/red compare the non-vitae residual against the base, so a pure vitae
penalty remains white. The selected-skill footer uses one shared inline-run
text primitive matching retail `AppendTextWithFont`; its vitae fragment uses
the authored LayoutDesc 0x2100002E / FooterTitle 0x1000024E palette index 3
(#7FFFFF), while positive/negative buff fragments use palette indices 1/2
(#00FF00/#FF0000). Attributes, secondary attributes, and skills share those
exact colors. AP-127 and TS-8 are retired by the same stat-chain package.
---
@ -11080,7 +11082,13 @@ The remaining trailer sections (options / shortcuts / hotbars / inventory / equi
**Commit:** `feat(net): #7 PlayerDescriptionParser — enchantment block walker + StatMod flow`
**Resolution:** Closed alongside #7 in the same commit. `ActiveEnchantmentRecord` extended with optional `StatModType`, `StatModKey`, `StatModValue`, `Bucket` fields. `Spellbook` got an `OnEnchantmentAdded(ActiveEnchantmentRecord)` overload that accepts the full record. `EnchantmentMath.GetMod` aggregator now consumes the StatMod data: multiplicative bucket (1) → multiplier ×= val; additive bucket (2) → additive += val; vitae bucket (8) → multiplier ×= val (applied last, matching retail `CEnchantmentRegistry::EnchantAttribute` semantics). 5 new EnchantmentMath StatMod-aware tests cover: multiplicative buffs aggregate, additive buffs sum, stat-key mismatch is filtered out, vitae applies multiplicatively, family-stacking picks the higher spell-id buff.
`ParseMagicUpdateEnchantment` (the live-update opcode 0x02C2) is **not** yet extended — it still uses the 4-field summary. That's a separate refactor; PlayerDescription's enchantment block is the load-bearing path for issue #6, and that's now flowing.
**2026-07-31 live-update closeout:** `ParseMagicUpdateEnchantment`
(0x02C2) now parses the complete record, including start time, DegradeModifier,
degrade limit, last time degraded, StatMod type/key/value, and bucket
classification. `GameEventWiring` maps that immutable wire record into the
same `ActiveEnchantmentRecord` shape as PlayerDescription. The end-to-end
test sends an actual 0x02C2 payload through dispatch and proves the resulting
StatMod changes the local player's effective skill without relogging.
---
@ -11090,7 +11098,9 @@ The remaining trailer sections (options / shortcuts / hotbars / inventory / equi
**Commit:** `feat(player): #6 fold enchantment buffs into vital max via EnchantmentMath`
**Resolution:** Ported `CEnchantmentRegistry::EnchantAttribute` (PDB `0x00594570`) as `EnchantmentMath.GetMod(IEnumerable<ActiveEnchantmentRecord>, SpellTable, statKey)` returning `(Multiplier, Additive)`. Family-stacking dedup via `SpellTable.Family` (only one buff per family bucket wins, by highest spell-id as a generation proxy). `Spellbook.GetVitalMod(statKey)` delegates. `LocalPlayerState.GetMaxApprox` reworked to apply `(unbuffed × mult) + add` with retail's min-vital clamp (`>= 5` if base ≥ 5 else `>= 1`, matches `CreatureVital::GetMaxValue` at PDB `0x0058F2DD`). Stat-key constants (`MaxHealth=1`, `MaxStamina=3`, `MaxMana=5`) verified against `docs/research/named-retail/acclient.h` line 37287-37301.
**Architecture in place; data still flat.** Until ISSUES.md #12 lands the wire-format extension that captures `StatMod (type/key/val)` on `ActiveEnchantmentRecord`, the per-enchantment modifier value isn't aggregated yet — `EnchantmentMath.GetMod` returns `Identity (1.0, 0.0)` for every stat key. Once #12 wires the data, the existing aggregator + formula light up automatically. Live `+Acdream` Stam/Mana percent will continue to read ~95% until #12 lands.
**Data path complete:** both PlayerDescription and live 0x02C2 updates carry
the full StatMod into `ActiveEnchantmentRecord`; the shared aggregator applies
it immediately to attributes, vitals, skills, movement, and retained UI.
6 new EnchantmentMathTests cover: empty list returns Identity, no-table-entries returns Identity, stat-key constants match ACE enum, Identity is `(1, 0)`, family-stacking dedup, family=0 (no-bucket) treated as separate.