fix(ui): match retail vitae and character info
Restore Vitae's omitted penalty paragraph, replace the invented character summary with gmCharacterInfoUI's ordered report and property meanings, preserve authored translucent body surfaces, and initialize the end-session button in its visible Normal DAT state. Release build and all 5,830 tests pass with five intentional skips. Connected visual gate pending. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
d1d603105f
commit
16c21e299c
21 changed files with 771 additions and 303 deletions
|
|
@ -72,12 +72,21 @@ Link ping RTT and Vitae
|
|||
recovery XP are live; transport packet-loss averaging and Mini Game gameplay
|
||||
remain the explicitly narrowed AP-110 residual.
|
||||
|
||||
The 2026-07-17 retail-conformance follow-up completes the visible detail data:
|
||||
Vitae now includes all three localized paragraphs; Character Information now
|
||||
emits retail's birth/playtime/deaths, resistance grades, innates,
|
||||
chess/fishing, earned mastery/augmentation, and burden report instead of an
|
||||
invented character-sheet summary. Character/Vitae authored center surfaces
|
||||
are no longer composited twice, and the end-session button enters its Normal
|
||||
DAT state before hover.
|
||||
|
||||
**Files:** `src/AcDream.App/UI/Layout/IndicatorBarController.cs`;
|
||||
`src/AcDream.App/UI/Layout/DatWidgetFactory.cs`;
|
||||
`src/AcDream.Core.Net/WorldSession.cs`; `src/AcDream.App/UI/RetailUiRuntime.cs`.
|
||||
|
||||
**Research:**
|
||||
`docs/research/2026-07-17-retail-indicator-bar-pseudocode.md`.
|
||||
`docs/research/2026-07-17-retail-vitae-character-info-pseudocode.md`.
|
||||
|
||||
**Acceptance:** All seven authored controls render their retail sprites. Link
|
||||
quality follows the 5/20/40-second thresholds and bad-link flash cadence;
|
||||
|
|
|
|||
|
|
@ -722,6 +722,10 @@ Helpful/Harmful instantiate retail's DAT icon/name/time row and selected-spell
|
|||
information presentation. The burden icon sends panel 3 (Character Information),
|
||||
not panel 11 (Attributes/Skills). Link uses retail's payload-free ping/RTT
|
||||
cadence; Vitae uses PropertyInt 129/139 and the retail XP-pool threshold formula.
|
||||
The detail conformance pass also restores Vitae's omitted penalty explanation,
|
||||
ports the exact Character Information report/property meanings, prevents
|
||||
double-compositing authored translucent panel centers, and initializes the
|
||||
end-session control in its visible Normal DAT state before hover.
|
||||
The first connected gate exposed and corrected two retained-import defects:
|
||||
spellbook tabs are authored stateful text controls (not buttons), and retail
|
||||
text starts with zero margins. Real-DAT fixtures now pin spellbook binding,
|
||||
|
|
|
|||
|
|
@ -23,10 +23,13 @@ Both Attributes + Skills tabs share a header strip filled by the base class
|
|||
`UpdatePKStatus` 0x004f00a0). The tab sub-layout `0x2100002C` nests `0x10000226` which
|
||||
chains again into `0x21000045` — the real header/list content is **two inheritance levels deep**.
|
||||
|
||||
The **text report** ("birth/age/deaths, innate attributes, augmentations, load") the earlier
|
||||
pilot guessed is a SEPARATE sub-panel: LayoutDesc `0x2100001A`, `gmCharacterInfoUI`
|
||||
(register 0x004b8c90), whose `m_pMainText` (`0x1000011d`) is created at RUNTIME. That window
|
||||
is handled by `CharacterController` (create-the-element path) — different layout, not 0x2100002E.
|
||||
The **text report** ("birth/playtime/deaths, resist grades, innate attributes,
|
||||
chess/fishing, earned augmentations, load") is a SEPARATE child panel:
|
||||
`gmCharacterInfoUI` (register `0x004b8c90`), root `0x10000183` under the
|
||||
production shared-panel LayoutDesc `0x2100006E`. Its authored
|
||||
`m_pMainText` (`0x1000011d`) and scrollbar are imported from that child tree;
|
||||
the controller only supplies the report. This is different from `0x2100002E`.
|
||||
See `2026-07-17-retail-vitae-character-info-pseudocode.md` for the exact report.
|
||||
|
||||
## Importer reality (dumped live, 2026-06-25)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
# Retail Vitae and Character Information pseudocode
|
||||
|
||||
Sources: Sept. 2013 named retail client, installed `local_English.dat`, and the
|
||||
matching v11.4186 executable. String keys are resolved from StringTable
|
||||
`0x23000001`; duration formatting is StringTable `0x23000006`.
|
||||
|
||||
## Vitae
|
||||
|
||||
`gmVitaeUI::Update @ 0x004A7400`
|
||||
|
||||
```text
|
||||
vitae = current Vitae enchantment modifier, or 1.0
|
||||
lost = 100 - trunc(vitae * 100)
|
||||
|
||||
if lost <= 0:
|
||||
mainText = ID_Vitae_Text_Full
|
||||
else:
|
||||
mainText = ID_Vitae_Text_Vitae(lost)
|
||||
mainText += ID_Vitae_Text_Skills(lost)
|
||||
remaining = VitaeCPPoolThreshold(vitae, deathLevel) - currentPool
|
||||
mainText += ID_Vitae_Text_Experience(remaining)
|
||||
```
|
||||
|
||||
The skills paragraph is not optional. It explains that health, stamina, mana,
|
||||
and skills are reduced by the same percentage and warns about penalties above
|
||||
15 percent.
|
||||
|
||||
## Character Information
|
||||
|
||||
`gmCharacterInfoUI::Update @ 0x004BA790` clears `m_pMainText`, appends six
|
||||
sections, and inserts one empty `StringInfo` between each section:
|
||||
|
||||
```text
|
||||
UpdatePlayerBirthAgeDeaths
|
||||
blank
|
||||
UpdateEnduranceInfo
|
||||
blank
|
||||
UpdateInnateAttributeInfo
|
||||
blank
|
||||
UpdateFakeSkills
|
||||
blank
|
||||
UpdateAugmentations
|
||||
blank
|
||||
UpdateLoad
|
||||
```
|
||||
|
||||
It does not prepend character name, level, race, title, current vitals, or
|
||||
invented section headings.
|
||||
|
||||
### Birth, play time, and deaths
|
||||
|
||||
`gmCharacterInfoUI::UpdatePlayerBirthAgeDeaths @ 0x004B8CB0`
|
||||
|
||||
```text
|
||||
if PropertyInt 0x62 exists:
|
||||
append ID_CharacterInfo_Birth(strftime("%c", localtime(value)))
|
||||
if PropertyInt 0x7D exists:
|
||||
append ID_CharacterInfo_Played(TimeUtils::QueryDuration(value))
|
||||
|
||||
deaths = PropertyInt 0x2B, default 0
|
||||
append the None / One / Two string for 0 / 1 / 2
|
||||
otherwise append ID_CharacterInfo_Deaths_Many(deaths)
|
||||
```
|
||||
|
||||
`TimeUtils::QueryDuration @ 0x00684170` divides seconds into 365-day years,
|
||||
30-day months, seven-day weeks, days, hours, minutes, and seconds. Zero units
|
||||
are omitted by `ID_DurationFormat`.
|
||||
|
||||
### Resistance grades
|
||||
|
||||
`gmCharacterInfoUI::UpdateEnduranceInfo @ 0x004B8EB0`
|
||||
|
||||
```text
|
||||
strength = current Strength
|
||||
endurance = current Endurance
|
||||
|
||||
resist = grade(strength + endurance,
|
||||
[200, 260, 320, 380, 440])
|
||||
regen = grade(strength + 2 * endurance,
|
||||
[200, 346, 470, 580, 690])
|
||||
|
||||
grade values in ascending order:
|
||||
None, Poor, Mediocre, Hardy, Resilient, Indomitable
|
||||
|
||||
append ID_CharacterInfo_Resists(RESIST=resist, REGEN=regen)
|
||||
```
|
||||
|
||||
The localized template uses `RESIST` for both the Natural and Drain lines and
|
||||
`REGEN` for the Regeneration Bonus line.
|
||||
|
||||
The literal grades and thresholds were verified from the matching executable
|
||||
at `0x00794028` and `0x007B1EB8..0x007B1EE8`.
|
||||
|
||||
### Innates and fake skills
|
||||
|
||||
`UpdateInnateAttributeInfo @ 0x004B87E0` appends the six innate values in the
|
||||
order Strength, Endurance, Coordination, Quickness, Focus, Self.
|
||||
|
||||
`UpdateFakeSkills @ 0x004B8930` appends PropertyInt `0xB5` as Chess Rank and
|
||||
PropertyInt `0xC0` as Fishing Skill. These are not skill-credit counters.
|
||||
|
||||
### Augmentations and load
|
||||
|
||||
`UpdateAugmentations @ 0x004B9000` emits only earned mastery, luminance, and
|
||||
augmentation lines. It emits no `Augmentations` heading and no `None` line.
|
||||
The first three mastery properties are `0x162`, `0x163`, and `0x16A`; ordinary
|
||||
augmentation counts are read from their individual PropertyInt qualities.
|
||||
|
||||
`UpdateLoad @ 0x004B8A20` computes retail encumbrance capacity from Strength
|
||||
and PropertyInt `0xE6`. Below 100 percent load it appends the not-overburdened
|
||||
sentence. At or above 100 percent it appends excess burden and the Run/Jump/
|
||||
defense reduction. When `0xE6` is positive it also appends the Seventh Mule
|
||||
augmentation sentence.
|
||||
|
||||
## Panel surface and quit button
|
||||
|
||||
The Character Information and Vitae child layouts author a full-height
|
||||
`0x06004CC2` center surface beneath their text. The Positive Effects child does
|
||||
not; it relies on its enclosing shared panel surface. When mounting those child
|
||||
layouts in acdream's standalone shared window, the outer chrome must not paint
|
||||
a second center surface over an already-authored one. Two translucent center
|
||||
layers compound into the opaque-looking result reported in-client.
|
||||
|
||||
The quit control is the ordinary button `0x100000FA` in LayoutDesc
|
||||
`0x21000071`. Its DAT default is state 1 (`Normal`) with RenderSurface
|
||||
`0x06007BB1`; state 3 is the pressed surface. `gmIndicatorsUI` does not own a
|
||||
hover-only visibility rule, so the retained button must enter Normal during
|
||||
controller initialization, before pointer input.
|
||||
Loading…
Add table
Add a link
Reference in a new issue