acdream/docs/research/2026-07-17-retail-vitae-character-info-pseudocode.md
Erik 82789eea88 fix(ui): restore effect detail scrolling
Reflow selected Helpful/Harmful spell descriptions through the retained retail text shaper, bind their authored 0x10000127 information scrollbar independently from the list scrollbar, and allow display-only text to consume wheel scrolling without becoming selectable.

Format Vitae recovery experience through retail's grouped integer presentation. Release build succeeds and all 5,832 tests pass with five intentional skips.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-17 13:28:18 +02:00

4.6 KiB

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

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.

Every integer above is inserted with StringInfo::AddVariable_Int. Its LInt_StringInfoData::ToString @ 0x0042F7E0 calls NumToString with grouping enabled, so recovery XP is rendered with the client language's thousands separator (for English, 61,000,000, never 61000000).

Character Information

gmCharacterInfoUI::Update @ 0x004BA790 clears m_pMainText, appends six sections, and inserts one empty StringInfo between each section:

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

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

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.