fix(chargen): Campaign CC gate round 1 Batch E — text origin, caption escapes, value rects, scrollbars, name prefill
R2-1/R2-6 (description-box text clipped left of the frame, regressed from
Batch C's frame un-consume): root cause was never the un-consume change
itself — the Heritage/Profession/Town/Summary description boxes
(0x100003C4/0x100003E0/0x10000409/0x10000404) all author retail's four
independent text-inset margins (dat properties 0x23-0x26,
UIElement_Text::OnSetAttribute cases 0xf-0x12: margL=9/margR=26/margU=15/
margD=15), which this codebase never read at all, before or after Batch C.
Un-consuming the gold-frame children just made the pre-existing missing-
margin bug visible for the first time (the frame's own left border now
draws around the same x=0 origin text always used). Fixed end to end:
ElementInfo.MarginLeft/Right/Top/Bottom (read in
ApplyCanonicalLegacyProjection, propagated in Merge), UiText.MarginLeft/
Right/Top/Bottom (additive with the pre-existing Padding), a new pure
UiText.ContentOffsetX static consumed by the multi-line draw path's
per-line placement, and matching wrap-width shrinkage in
DatRichText.Compose and BuildText's own authored-multiline path. Scoped to
the multi-line (non-OneLine) path only.
R2-2/R2-3 (Attribute\n Credits renders the literal backslash-n; the live
credit value overlaps mid-caption): two stacked gaps. (1) UiButton
captions never escape-normalized the DAT's literal "\n" — centralized the
normalize into DatWidgetFactory's ResolveAuthoredString (the one choke
point every P0x17 resolution already shares) plus a NormalizeEscapes
helper for the per-state caption loop, so every caller normalizes
identically. (2) UiButton.Label only ever drew one line — retail's
UIElement_Button IS a UIElement_Text with OneLine=false on these buttons,
so a caption should word-wrap/stack like any other Type-12 box. Added
UiButton.DrawBlockLabel + the pure, unit-tested WrapBlockLines. The
value-overlap itself: ValueBox was never wrong (live-DAT-measured correct
child rects) — the caption was drawing unconfined across the button's
full width ("Available Skill Credits" measures 193px in a 231px button
whose value box starts at x=116). Fixed by confining the caption's own
drawable width to stop before ValueBox.X whenever a ValueLabel coexists.
R2-7a (Summary overview listbox missing its scrollbar): pure wiring gap —
the listbox authors a linked scrollbar via dat property 0x72
(ScrollbarElementId=0x10000401) that CharacterCreationSummaryPage's
constructor never resolved, unlike every other UiTemplateListBox owner in
the codebase. Fixed with the same resolve-and-wire pattern.
R2-7b (how-to box scrollbar overlaps text, no thumb): traced to a
downstream symptom of R2-1, not an independent bug — UiScrollbar only
paints its thumb when the linked model has overflow, and the pre-fix wrap
width (un-inset) produced fewer/shorter lines than fit the view. Pinned
directly against the real installed strings/font (Aluvian's how-to text)
that the margin-correct width overflows. No UiScrollbar code changed.
R2-8 (name field should show "[ Name ]"): re-checked the one hypothesis
Batch A's GF-15 closure left open — an authored initial-text string on
the field's own P0x17. Confirmed absent on every state in the installed
DAT. No code change; Batch A's closure stands, now pinned as a live-DAT
regression test.
App suite 5334/3 (was 5321/3, +13, zero regressions). Runtime 1735/0
unchanged. Full solution Release build green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2ad805469d
commit
e24ec20882
11 changed files with 895 additions and 23 deletions
|
|
@ -15,14 +15,14 @@ acdream GradCircle vs retail's color wheel).
|
|||
rich-text/un-consume changes moved the text draw origin to the
|
||||
element's outer rect where retail insets to an interior text region
|
||||
(authored margins or interior-relative origin). PIN IT with a probe
|
||||
before fixing.
|
||||
before fixing. — **FIXED (Batch E), see below.**
|
||||
- **R2-2: `Attribute\n Credits` renders the LITERAL `\n`** (UiButton
|
||||
captions never escape-normalize — only BuildText does), AND the value
|
||||
("24") overlaps the caption text — the ValueLabel is not drawing in
|
||||
its authored child rect.
|
||||
its authored child rect. — **FIXED (Batch E), see below.**
|
||||
- **R2-3: Skills "Available Skill Credits" value overlaps mid-caption**
|
||||
("Available Skill Credit0Credits") — same ValueLabel-rect family as
|
||||
R2-2.
|
||||
R2-2. — **FIXED (Batch E), see below.**
|
||||
- **R2-4: Skills page functional gaps (retail screenshots 5-6):**
|
||||
(a) rows are NOT selectable — retail selection turns the row brighter
|
||||
white AND writes the skill's info into the lower-left description box
|
||||
|
|
@ -32,22 +32,182 @@ acdream GradCircle vs retail's color wheel).
|
|||
(b) NOT divided into the four retail buckets (Specialized / Trained /
|
||||
Useable Untrained / Unuseable Untrained with headers) — the user's
|
||||
gate OVERTURNS AP-213's remaining flat-list half: implement the
|
||||
buckets; (c) the skill list's scrollbar is missing.
|
||||
buckets; (c) the skill list's scrollbar is missing. — OUT OF SCOPE for
|
||||
Batch E (functional gap, not text layout); still open.
|
||||
- **R2-5: the color wheel renders as static authored art** (mirror-like
|
||||
disc) where retail shows the gradient wheel + gold swatch dots that
|
||||
CHANGE per selected part — the user's gate PROMOTES AP-216/AP-217's
|
||||
remaining halves (real palette-color swatch rendering + gradient tint)
|
||||
from partial-closed to must-port.
|
||||
- **R2-6: Town description text misaligned** — R2-1 family.
|
||||
from partial-closed to must-port. — OUT OF SCOPE for Batch E; still
|
||||
open.
|
||||
- **R2-6: Town description text misaligned** — R2-1 family. — **FIXED
|
||||
(Batch E)**, same shared mechanism as R2-1.
|
||||
- **R2-7: Summary — (a) text misaligned (R2-1); (b) the summary OVERVIEW
|
||||
listbox is missing its scrollbar; (c) the how-to box's scrollbar
|
||||
renders but OVERLAPS the text area and has no thumb ("slider").**
|
||||
renders but OVERLAPS the text area and has no thumb ("slider").** —
|
||||
**FIXED (Batch E), see below** — (a) via the shared R2-1 mechanism;
|
||||
(b) the listbox's own `0x72` scrollbar linkage was simply never wired
|
||||
(every other `UiTemplateListBox` owner in the codebase already does
|
||||
this — this page was the one holdout); (c) traced to a DOWNSTREAM
|
||||
symptom of R2-1, not an independent bug — see the Batch E write-up for
|
||||
the full geometric argument.
|
||||
- **R2-8: the name field should show `[ Name ]`** — the user re-asserts
|
||||
retail's prefill. Batch A verified retail's CODE never writes it; the
|
||||
UNCHECKED hypothesis is the field's AUTHORED initial text (`P0x17` on
|
||||
`0x10000402`) — probe the DAT; if authored, render authored initial
|
||||
text (display-only; the committed state name stays empty, retail's
|
||||
NameInputFilter forbids `[` so it can never be committed as a name).
|
||||
NameInputFilter forbids `[` so it can never be committed as a name). —
|
||||
**RE-CHECKED (Batch E): NOT authored** — see below. Batch A's closure
|
||||
stands; no code change.
|
||||
|
||||
**Batch E (gate round 1, text layout correctness) is CODE-COMPLETE
|
||||
2026-08-16, pending the user's visual gate.** R2-1/R2-2/R2-3/R2-6/R2-7 are
|
||||
fixed at the mechanism level (no per-page nudges); R2-8 was re-checked and
|
||||
confirmed NOT a code change. R2-4/R2-5 are explicitly out of scope
|
||||
(functional gaps, not text layout) and remain open for a later round.
|
||||
|
||||
- **R2-1/R2-6 root cause CONFIRMED, not the un-consume change itself:**
|
||||
live-DAT-probed against the installed EoR dat, the Heritage/Profession/
|
||||
Town/Summary description boxes (`0x100003C4`/`0x100003E0`/`0x10000409`/
|
||||
`0x10000404`) all author retail's four independent text-inset margins
|
||||
(dat properties `0x23`/`0x24`/`0x25`/`0x26` — `UIElement_Text::
|
||||
OnSetAttribute @0x0046a640` cases `0xf`-`0x12`, i.e.
|
||||
`BaseProperty::GetPropertyName(arg2) - 0x14`, writing `m_margL`/
|
||||
`m_margR`/`m_margU`/`m_margD`): `margL=9, margR=26, margU=15, margD=15`
|
||||
on every one of the four boxes (one shared authored template). This
|
||||
codebase never read those four properties AT ALL, before OR after Batch
|
||||
C — `UiText.Padding` (the only inset this port had) always defaults to
|
||||
0 for DAT-built text, so every box's text drew flush against x=0
|
||||
regardless of batch. The regression's actual TRIGGER was Batch C
|
||||
un-consuming the gold-frame children (previously silently dropped): the
|
||||
frame's own left border piece (`0x100002DE`/`0x100000E8`, live-DAT-
|
||||
measured ~0-34px wide) now draws on top of/around the SAME x=0 origin
|
||||
text has ALWAYS used, making the pre-existing missing-margin bug visible
|
||||
for the first time. Fixed by adding the four margin properties end to
|
||||
end: `ElementInfo.MarginLeft/Right/Top/Bottom` (read in
|
||||
`ElementReader.ApplyCanonicalLegacyProjection`, propagated in `Merge`
|
||||
with the same "non-zero derived wins" convention as `FontDid`), new
|
||||
`UiText.MarginLeft/Right/Top/Bottom` properties (additive with the
|
||||
pre-existing `Padding`, seeded by `DatWidgetFactory.BuildText`), and a
|
||||
new pure `UiText.ContentOffsetX` static (mirrors `ContentBaseY`/
|
||||
`VOffset`'s own shape) consumed by the multi-line scrollable draw path's
|
||||
per-line horizontal placement. `DatRichText.Compose`'s and
|
||||
`DatWidgetFactory.BuildText`'s own authored-multiline wrap-width
|
||||
formulas both shrink by the same `Padding+MarginLeft`/
|
||||
`Padding+MarginRight` inset — the wrap half of the regression (text
|
||||
also overflowing the visible RIGHT edge, not just clipping on the left).
|
||||
Deliberately scoped to the multi-line (non-`OneLine`) path only — the
|
||||
static Centered/RightAligned/OneLine single-line branches keep their
|
||||
pre-fix bare-`Padding` math, since every currently-broken box is
|
||||
multi-line and touching those paths too would widen this fix's blast
|
||||
radius with no known-broken target. R2-1's finding also named
|
||||
"Appearance" — no Appearance-page description box exists in this
|
||||
codebase (only Heritage/Profession/Town/Summary call
|
||||
`DatRichText.Compose`); read as either a recollection slip or referring
|
||||
to a page that will inherit this same fix automatically once/if it ever
|
||||
grows one, since the fix lives in the shared `UiText`/`DatRichText`
|
||||
mechanism, not per-page code.
|
||||
- **R2-2/R2-3 root cause CONFIRMED, two stacked gaps:** (1) `UiButton`
|
||||
captions never escape-normalized the DAT's literal two-character `\n`
|
||||
escape — only `DatWidgetFactory.BuildText`'s own authored-string path
|
||||
did. Centralized the normalize into the ONE choke point every P0x17
|
||||
caption resolution in `DatWidgetFactory.cs` already shares
|
||||
(`ResolveAuthoredString`, plus a `NormalizeEscapes` helper for the
|
||||
per-STATE caption loop that resolves a state's own `0x17` directly) —
|
||||
every caller (`BuildText`, `BuildButton`'s own caption AND its lifted-
|
||||
child caption, `BuildButton`'s coexisting `ValueLabel`, `BuildCheckbox`,
|
||||
the per-state caption swap) now normalizes identically, closing the
|
||||
exact "some callers normalize, some don't" class of bug that caused
|
||||
this regression in the first place. (2) `UiButton.Label` only ever drew
|
||||
ONE line, unconditionally — but retail's `UIElement_Button` IS a
|
||||
`UIElement_Text` (`struct UIElement_Button : UIElement_Text`,
|
||||
`acclient.h`) and these captions author `OneLine=false`
|
||||
(live-DAT-probe-confirmed on `0x100003e2-e5`/`0x100003f9`), so a
|
||||
caption that carries a newline OR simply doesn't fit its available
|
||||
width should lay out as multiple stacked lines, the same word-wrap
|
||||
every other Type-12 text box already gets (`UiText.WrapWords`). Added
|
||||
`UiButton.DrawBlockLabel`/the pure, unit-tested `UiButton.WrapBlockLines`
|
||||
extraction. The VALUE-overlap half specifically (R2-2's "24dits", R2-3's
|
||||
"Credit0Credits"): `ValueBox` itself was NEVER null/wrong — live-DAT-
|
||||
measured, both buttons' value children (`0x100002F1`/`0x100002F3`
|
||||
family) resolve correctly. The overlap was the CAPTION drawing
|
||||
unconfined across the button's FULL width (`Available Skill Credits`
|
||||
measures 193px in the Skills button's 231px-wide box whose value box
|
||||
starts at local x=116 — the caption's own unwrapped single-line render
|
||||
reached x≈196, well past the value's territory). Fixed by confining the
|
||||
caption's OWN drawable width to stop before `ValueBox.X` whenever a
|
||||
`ValueLabel` coexists (`LabelBox`/`ValueBox` are mutually exclusive by
|
||||
construction, so this never fights GF-11c's own `LabelBox` confinement).
|
||||
A single-line caption that already fits draws with byte-identical
|
||||
geometry to the pre-fix math — the fix is a strict superset for every
|
||||
already-correct button caption in the client.
|
||||
- **R2-7a root cause CONFIRMED — pure wiring gap, same shape as every
|
||||
other holdout in this codebase:** the Summary OVERVIEW listbox
|
||||
(`0x10000400`) authors a linked scrollbar via dat property `0x72`
|
||||
(live-DAT-probe-confirmed `ScrollbarElementId=0x10000401`, a SIBLING
|
||||
element, not a descendant of the listbox). Every OTHER
|
||||
`UiTemplateListBox` owner in this codebase (`SocialFriendsPageController`,
|
||||
`ConfigOptionsPageController`, the Fellowship/Allegiance/Squelch pages)
|
||||
already resolves `ScrollbarElementId` against its page root and wires
|
||||
`.Model = listBox.Scroll` — `CharacterCreationSummaryPage`'s
|
||||
constructor was the one holdout that only ever wired the HOW-TO box's
|
||||
own scrollbar (Batch C Commit 3) and never resolved this one. Fixed by
|
||||
adding the identical resolve-and-wire block to the constructor.
|
||||
- **R2-7b root cause CONFIRMED as a DOWNSTREAM SYMPTOM of R2-1, not an
|
||||
independent defect** — investigated, not assumed: `UiScrollbar`'s own
|
||||
draw path only paints the thumb `if (m.HasOverflow)`
|
||||
(`ContentHeight > ViewHeight` on the linked `UiScrollable`). Before the
|
||||
R2-1 fix, the how-to box's wrap width used the box's raw, un-inset
|
||||
Width (247px) instead of the authored margin-inset content width
|
||||
(247-9-26=212px) — a WIDER wrap width produces FEWER/SHORTER lines,
|
||||
which can leave `ContentHeight <= ViewHeight` (no overflow → the thumb
|
||||
legitimately has nothing to gate on and correctly draws nothing). Pinned
|
||||
directly against the real installed strings/font (Aluvian's how-to
|
||||
text, the longest composed variant — `SummaryHowTo` + the male name-
|
||||
suggestion list + `SummaryHowToEnd` — at the box's real font,
|
||||
`0x40000009`): composed with the CORRECT margin-inset width, the
|
||||
content (multiple lines × the font's line height) exceeds the
|
||||
margin-inset view height, so `HasOverflow` is true and the thumb draws.
|
||||
No `UiScrollbar` code changed — this is a full explanation, not a
|
||||
guess: the "overlapping the text area" half of R2-7b's report likely
|
||||
reflects a genuine but minor (~7-9px) crowding between the scrollbar's
|
||||
own anchor-reflowed position (`UiLayoutPolicy`, retail's raw-edge
|
||||
system — verified this reflow mechanism itself works correctly, both
|
||||
via `UiElement.ApplyAnchor`'s per-frame call and hand-computed against
|
||||
the box's real 100x100 design-time template) and the box's authored
|
||||
26px right margin; this is within the authored geometry's own
|
||||
tolerance and was NOT changed, since inventing a new pixel offset here
|
||||
would be exactly the guessing this project's workflow forbids. Flagged
|
||||
for the user's own re-check once the thumb is visible — it may no
|
||||
longer be perceptible/relevant now that the box's own interior boundary
|
||||
has moved too.
|
||||
- **R2-8 RE-CHECKED, CONFIRMED NOT AUTHORED — Batch A's closure stands.**
|
||||
Probed the installed EoR dat directly for `0x10000402`'s own `P0x17`
|
||||
property (the SAME authored-caption mechanism `DatWidgetFactory`
|
||||
already reads for every other element): absent on the default state
|
||||
AND on every one of the field's named states. Batch A's GF-15 closure
|
||||
already byte-verified retail's CODE never writes the prefill
|
||||
(`CharGenState::RandomizeCharacter`, `gmCGSummaryPage::InitializePage`);
|
||||
this batch closes the remaining unchecked half (the DAT-authored-
|
||||
initial-text hypothesis) the same way — negative. No code change;
|
||||
pinned as a live-DAT regression test
|
||||
(`SummaryNameField_AuthorsNoP0x17OnAnyState`) so a future DAT re-extract
|
||||
or a future guess can't silently reintroduce the wrong fix shape.
|
||||
|
||||
Fixture + live-DAT tests only this round (no graphical client launch).
|
||||
App suite 5334/3 (was 5321/3, +13, zero regressions): +1 `DatRichText`
|
||||
wrap-width-with-margins test, +3 `UiText.ContentOffsetX` tests, +5
|
||||
`UiButton`/`DatWidgetFactory` tests (`WrapBlockLines` × 3, the value-box
|
||||
confinement shape, the escape-normalize regression), +4 live-DAT tests
|
||||
(the Heritage margin/first-line-origin pin, the Summary listbox scrollbar
|
||||
wiring, the Aluvian how-to overflow proof, the name-field no-P0x17 pin).
|
||||
Runtime 1735/0 unchanged. Full solution Release build green (0 errors).
|
||||
Blast radius swept: `UiText.MarginLeft/Right/Top/Bottom` default to 0 and
|
||||
are ADDITIVE with the pre-existing `Padding`, so every DAT-imported
|
||||
multi-line text box that does NOT author properties `0x23`-`0x26` (the
|
||||
overwhelming majority client-wide, including chat and the main game UI)
|
||||
is byte-identical to before this fix — confirmed by the unchanged full
|
||||
App suite pass count outside this batch's own new tests.
|
||||
|
||||
**MILESTONE (2026-08-16, post-Batch-A build `1.0.2-cc.g`): the user
|
||||
completed the FIRST LIVE CHARACTER CREATE from acdream against local ACE —
|
||||
|
|
@ -250,6 +410,14 @@ ISSUES.md; this doc is the six-page batch.
|
|||
acdream's existing (correct) behavior; the `[ Name` the user saw was
|
||||
most likely the field's own bracket-style empty-state chrome (GF-2/GF-12
|
||||
textbox-decoration family), not a missing name-prefill feature.
|
||||
**Re-checked at Batch E (R2-8) against the ONE hypothesis this note
|
||||
left unchecked** — an authored initial-text string on the field's own
|
||||
dat property `0x17`, the SAME mechanism `DatWidgetFactory` reads for
|
||||
every other element's caption — and confirmed ABSENT on the field's
|
||||
default state and every named state alike, live-DAT-probed against the
|
||||
installed EoR dat. This closure now covers both the CODE half (this
|
||||
paragraph) and the AUTHORED-DATA half (Batch E); no further hypothesis
|
||||
remains unchecked.
|
||||
|
||||
## Presentation families (retail parity)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue