fix(ui): #267 character panel reflects vitae/buffed skills and attributes
Retail CACQualities::EnchantAttribute (0x00594570), EnchantAttribute2nd (0x00594670, already ported for #6), and EnchantSkill (0x005947b0) are the three enchantment-composition functions the Character window's Attributes and Skills tabs depend on. Primary attributes never reference the vitae singleton in retail (only Attribute2nd/Skill do) — confirmed directly from the decompiled function bodies, not assumed. EnchantmentMath.GetMod gains requiredType/includeVitae parameters (default to the prior behavior) so a numeric StatMod key collision across domains (e.g. key=1 is both Strength and MaxHealth) can't leak a buff into the wrong computation. Spellbook.GetAttributeMod/GetSkillMod and LocalPlayerState.GetEffectiveAttribute/GetEffectiveSkill/ GetSkillVitaeModifier wire the retail chain through to the panel. CharacterSheetProvider now reports the effective value as the main number and CharacterSkill.CurrentLevel is no longer an alias of BaseLevel (this also activates the previously-dead SkillValueColor buffed/debuffed row coloring). CharacterStatController's footer-title parenthetical is cited from gmAttributeUI::DisplaySelectionFooter_Attribute (0x0049d280) and gmSkillUI::DisplaySelectionFooter_Trained (0x0049b860) + SkillInfoRegion::GetVitaeModifier (0x004f0fa0): skills show up to two segments (vitae's own contribution, then the buff-only residual), while vitae-immune attributes show at most one; no parenthetical when the delta is zero. The panel now refreshes on Spellbook.EnchantmentsChanged, not only raw property/attribute updates. Core goldens cover the user-reported 33% vitae example (303->203, "(-100)" exactly), buff+vitae composition, and the attribute vitae-immunity finding. Provider/controller tests cover the full row-click -> footer-title path and live refresh. Full solution suite passes with zero failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f6275f4501
commit
cf2605fa4a
11 changed files with 1089 additions and 53 deletions
|
|
@ -92,6 +92,105 @@ Copy this block when adding a new issue:
|
|||
|
||||
---
|
||||
|
||||
## #267 — Vitae does not update the character panel's skills/attributes display
|
||||
|
||||
**Status:** DONE (implementation) — 2026-07-30; visual confirmation with the user pending
|
||||
**Severity:** MEDIUM
|
||||
**Filed:** 2026-07-30
|
||||
**Component:** retained UI / character window / vitae
|
||||
|
||||
**Symptom (user report):** with 5% vitae active, the skills and attributes
|
||||
values in the character window did not change; retail shows the CURRENT
|
||||
(vitae-reduced) value and, on click/detail, the current level with the
|
||||
vitae reduction in parentheses (e.g. "(-100)"). The P1 movement chain
|
||||
consumes vitae correctly (EnchantSkill for run/jump); the character
|
||||
window presentation did not.
|
||||
|
||||
**Scoping (2026-07-30):** confirmed — `CharacterSheetProvider`/
|
||||
`CharacterStatController` contained zero vitae/enchantment references; the
|
||||
panel rendered base property values only. `LocalPlayerState.AttributeSnapshot.Current`
|
||||
(Ranks+Start) and `SkillSnapshot.CurrentLevel` (an alias of `BaseLevel`) never
|
||||
consulted the wired `Spellbook`.
|
||||
|
||||
**Root cause / resolution:** Ported retail's three enchantment-composition
|
||||
functions from the named decomp:
|
||||
`CEnchantmentRegistry::EnchantAttribute` (0x00594570 — primary attributes;
|
||||
mult/add buff lists only, floor at 1/10 by base<10, truncate),
|
||||
`CEnchantmentRegistry::EnchantAttribute2nd` (0x00594670 — vitals, already
|
||||
ported for issue #6), and `CEnchantmentRegistry::EnchantSkill` (0x005947b0 —
|
||||
skills; vitae singleton applied first, then mult/add lists, zero-floor at
|
||||
<0.5, truncate). **Primary attributes are vitae-immune in retail** —
|
||||
`EnchantAttribute` never references the `_vitae` singleton, unlike its
|
||||
Attribute2nd/Skill siblings; this is confirmed directly from the decompiled
|
||||
function bodies, not assumed.
|
||||
`EnchantmentMath.GetMod` gained a `requiredType` (`EnchantmentTypeFlag`:
|
||||
Attribute=1, SecondAtt=2, Skill=0x10, matching ACE's
|
||||
`EnchantmentTypeFlags`) and `includeVitae` parameter so a numeric key
|
||||
collision across domains (e.g. key=1 is both Strength and MaxHealth) can't
|
||||
leak a buff across domains; both default to the prior no-filter/vitae-always
|
||||
behavior so the existing vitals path (#6) is untouched.
|
||||
`Spellbook.GetAttributeMod`/`GetSkillMod` are new cached accessors mirroring
|
||||
`GetVitalMod`. `LocalPlayerState.GetEffectiveAttribute`/`GetEffectiveSkill`/
|
||||
`GetSkillVitaeModifier` compose the final int, falling back to the
|
||||
unenchanted base when no spellbook is wired.
|
||||
`CharacterSheetProvider.BuildSheet()` now reports the effective (buffed)
|
||||
value as the panel's main number for all 6 attributes and every skill's
|
||||
`CharacterSkill.CurrentLevel` (previously an alias of `BaseLevel` — this also
|
||||
activates the pre-existing but previously-dead
|
||||
`CharacterStatController.SkillValueColor` buffed/debuffed row coloring).
|
||||
`CharacterSheet.AttributeBaseValues` and `CharacterSkill.VitaeModifier` carry
|
||||
the extra state the footer-title delta needs.
|
||||
|
||||
The click/detail parenthetical format is cited from
|
||||
`gmAttributeUI::DisplaySelectionFooter_Attribute` (0x0049d280, format
|
||||
`"%s: %d"` + `" (%s%d)"`) and `gmSkillUI::DisplaySelectionFooter_Trained`
|
||||
(0x0049b860, plus `SkillInfoRegion::GetVitaeModifier` 0x004f0fa0 for the
|
||||
isolated vitae segment `" (%d)"`). Retail skills show up to TWO
|
||||
parentheticals — vitae's own contribution (always ≤0, e.g. "(-100)") and the
|
||||
buff-only residual excluding vitae — while attributes (vitae-immune) show at
|
||||
most one. No parenthetical appears when the relevant delta is zero; an
|
||||
increase gets an explicit "+" the natural `%d` doesn't provide, a decrease
|
||||
uses the natural minus sign. Retail colors each segment independently via
|
||||
`AppendTextWithFont` state 1/2/3; the current `UiText` footer-title element is
|
||||
`OneLine` (single color, single run), so segments share the title's white
|
||||
color rather than retail's per-run tint — a presentation simplification, not
|
||||
a value/format deviation.
|
||||
|
||||
Live refresh: `CharacterSheetProvider`'s existing `SubscribeChanged` binding
|
||||
now also subscribes to `LocalPlayerState.Spellbook.EnchantmentsChanged` (a
|
||||
newly-exposed read-only property mirroring the private reference
|
||||
`LocalPlayerState` already held for issue #6), so the panel repaints when
|
||||
vitae is applied/removed or a buff is cast/expires, not only on raw
|
||||
property/attribute updates.
|
||||
|
||||
**Files:** `src/AcDream.Core/Spells/EnchantmentMath.cs`;
|
||||
`src/AcDream.Core/Spells/Spellbook.cs`;
|
||||
`src/AcDream.Core/Player/LocalPlayerState.cs`;
|
||||
`src/AcDream.App/UI/Layout/CharacterSheet.cs`;
|
||||
`src/AcDream.App/UI/Layout/CharacterSheetProvider.cs`;
|
||||
`src/AcDream.App/UI/Layout/CharacterStatController.cs`.
|
||||
|
||||
**Research:** `docs/research/named-retail/acclient_2013_pseudo_c.txt` lines
|
||||
416110 (`EnchantAttribute`), 416169 (`EnchantAttribute2nd`), 416240
|
||||
(`EnchantSkill`), 167258 (`gmSkillUI::DisplaySelectionFooter_Trained`),
|
||||
168660 (`gmAttributeUI::DisplaySelectionFooter_Attribute`), 244033
|
||||
(`SkillInfoRegion::GetVitaeModifier`);
|
||||
`references/ACE/Source/ACE.Entity/Enum/EnchantmentTypeFlags.cs`.
|
||||
|
||||
**Acceptance:** Core golden tests cover 33% vitae on a known skill base
|
||||
(303→203, matching the user's exact "(-100)" example), buff+vitae
|
||||
composition, zero-delta no-parenthetical, and the attribute vitae-immunity
|
||||
finding. `CharacterStatControllerTests` covers the full row-click →
|
||||
footer-title text through `CharacterStatController.Bind` for both domains.
|
||||
`CharacterSheetProviderTests` covers `BuildSheet()` effective-value output
|
||||
and `SubscribeChanged` firing (and rebuilding to the new value) on
|
||||
`EnchantmentsChanged`. `dotnet build AcDream.slnx -c Release` and the
|
||||
complete solution test suite pass with zero failures. **Final closure pends
|
||||
the user's live visual check** (5%/33% vitae against a live ACE session) per
|
||||
the standard M4 UI-accuracy gate.
|
||||
|
||||
---
|
||||
|
||||
## #247 — Loot ordering and local dropped-item projection regressed
|
||||
|
||||
**Status:** DONE — 2026-07-26; connected loot/drop/selection gate passed
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue