763 lines
50 KiB
Markdown
763 lines
50 KiB
Markdown
# Campaign CC connected gate — round 1 findings (2026-08-16)
|
||
|
||
## RE-TEST 2 (build `1.0.2-cc.k`, post-E/F/G/closeout) — findings R3-1..R3-9
|
||
|
||
Heritage PASSES. Remaining, with retail side-by-side screenshots:
|
||
|
||
- **R3-1 Profession: "Coordination" wraps to two lines** on the attribute
|
||
label — retail fits one line. Likely the Batch E block-label wrap (or a
|
||
wrong/too-large font on the runtime-written `0x100002ED` labels).
|
||
Probe the authored font+rect: if the authored font fits, our font
|
||
resolution is wrong; if not, retail doesn't word-wrap captions and the
|
||
auto-wrap is the bug.
|
||
- **R3-2 Skills: "Available Skill Credits" wraps after "Available"**,
|
||
hiding behind adjacent graphics — retail is ONE line. Same family as
|
||
R3-1 (the Batch E caption-width confinement + wrap).
|
||
- **R3-3 Skills info box: the title line ("Item Enchantment (10)")
|
||
overlaps the description text** — description must start on the line
|
||
below the title.
|
||
- **R3-4 SCROLLBAR THUMB MISSING EVERYWHERE (shared mechanism):** skills
|
||
list scrollbar and summary scrollbars show track+arrows but no thumb
|
||
(retail: the red/gold diamond); the shade slider on the color disc
|
||
works but shows no indicator (retail: the small handle across the
|
||
disc). One probe: what authors the thumb (a child? state media on the
|
||
scrollbar?) and why our UiScrollbar never draws it on these chargen
|
||
scrollbars while (per OP-era gates) other scrollbars show thumbs.
|
||
- **R3-5 Appearance color wheel render targets:** (a) we tint the RING
|
||
art — retail fills the small circle INSIDE the ring (the spot); (b)
|
||
beyond-count swatches: retail SHOWS them as blocked/dark circles — we
|
||
hide them; (c) the shade indicator on the disc (see R3-4).
|
||
- **R3-6 Eyes: retail shows a graphic icon in the disc center** (the
|
||
authored eye plug art) and its swatches still show colored rings;
|
||
acdream shows an empty ring and dark swatches. Re-derive DoGradDisk's
|
||
Eyes branch + the eye swatch rendering.
|
||
- **R3-7 Summary scrollbar thumbs** — R3-4 family.
|
||
- **R3-8 `[ Name ]` (user re-asserts, third time):** P0x17 and state
|
||
text are probed-absent; retail code writes none. UNCHECKED: the
|
||
field-widget-specific properties — dump EVERY authored property id +
|
||
value on `0x10000402` raw, and check `UIElement_TextInput::OnSetAttribute`'s
|
||
full case list for prompt/default-text properties beyond 0x17. If that
|
||
is also empty, STOP and request a live retail screenshot of the field
|
||
before any further work.
|
||
|
||
## ROUND 1 RE-TEST (build `1.0.2-cc.i`, post-Batches B/C/D) — findings R2-1..R2-8
|
||
|
||
User's second visual pass with retail side-by-side screenshots (heritage
|
||
description, profession template text, "Attribute\n Credits" overlap,
|
||
skills credits overlap, retail skill-info box, retail Skills page, the
|
||
acdream GradCircle vs retail's color wheel).
|
||
|
||
- **R2-1 (COMMON, regression from Batch C): description-box text
|
||
misaligned LEFT, clipping outside the frame** on Heritage, Profession,
|
||
Appearance, Town, Summary — first characters cut ("rained Starting
|
||
Skills", "OW HUNTERS", "ump, Loyalty"). Pre-Batch-C (cc.e screenshots)
|
||
the text started INSIDE the box. One shared cause suspected: the
|
||
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. — **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. — **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. — **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
|
||
("Loyalty (5) / Affects experience given to your patron... / Training
|
||
Bonus +5 / Formula :") — the info panes `0x100003FB/FC` are still
|
||
never written (investigation root-1c, missed by Batch C's scope);
|
||
(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. — 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. **CODE-COMPLETE at Batch G (2026-08-16),
|
||
register AP-216/AP-217 rewritten (not retired — see their own rows):**
|
||
the retail mechanism (`gmCGAppearancePage::DoColorSpots @0x0047d850` /
|
||
`SetSelection @0x0047e260` / `DoGradDisk @0x0047da90`) is fully re-derived
|
||
and ported — a new pure Core resolver
|
||
(`AcDream.Core.CharGen.ChargenSwatchColorResolver`) computes each of the
|
||
nine swatches' representative RGB (PalSet-averaged for Hair/Nose+Mouth+
|
||
Skin/Headgear/Shirt/Trousers/Footwear at retail's own fixed sample
|
||
indices `0xd0`/`0xb0`/`0x520`, direct-Palette for Eyes at `0x103`) backed
|
||
by a new `ChargenAppearanceCatalog.TryGetColor` reading real Palette dat
|
||
objects, pinned against the installed EoR dat
|
||
(`ChargenAppearanceCatalogColorTests` — e.g. Aluvian male's shared skin
|
||
PalSet measures a plausible flesh-tone RGB(182,148,118)).
|
||
`CharacterCreationAppearancePage` recomputes all nine swatches + the
|
||
gradient disc's tint on every refresh (part change / color change /
|
||
heritage change, `CharacterCreationAppearancePageSwatchColorTests`), and
|
||
paints them through a new `ChargenSwatchColorTile` overlay element.
|
||
**Two STOPPED items block this from being visually live**, both outside
|
||
Batch G's file contract: (1) the new `PalSetSource`/`ClothingTableSource`/
|
||
`PaletteColorSource` late-bound seams (mirroring the existing
|
||
`PreviewControl` pattern) are never assigned by the composition root
|
||
(`CharacterCreationUiController.cs`) — until wired, the mechanism stays
|
||
fully inert, matching PRE-Batch-G behavior exactly; (2) the rendering
|
||
primitive is a flat-color-fill approximation of retail's actual
|
||
recolored-sprite blit — neither `UiButton` (sealed) nor `UiDatElement`
|
||
exposes a per-instance sprite `Tint`, though the retained-UI sprite
|
||
pipeline's `DrawSprite` already carries the `Vector4 tint` multiply
|
||
retail's own `Blit_Multiply` needs; adding that property is a small,
|
||
precisely-specified addition to those two shared widget files for the
|
||
lead to sequence. Nose/Mouth/Skin (retail's own non-interactive single
|
||
representative swatch, `SetSelection`'s hard-coded `var_1e0 = 1`) is ALSO
|
||
ported, beyond AP-216/AP-217's original six-part scope. Tests: 11 new
|
||
Core (`ChargenSwatchColorResolverTests`), 6 new Content live-DAT
|
||
(`ChargenAppearanceCatalogColorTests`), 8 new App-layer fixture
|
||
(`CharacterCreationAppearancePageSwatchColorTests`) — App suite
|
||
5321/3 -> 5329/3, Runtime 1735/0 unchanged, zero regressions.
|
||
- **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").** —
|
||
**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). —
|
||
**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 —
|
||
launcher → character select → Create → six pages → name → Finish → real
|
||
character created. USER-CONFIRMED: "Yes i could now create a char." The
|
||
create flow's core path is live; the round continues for visual parity
|
||
(Batches B-D) and the remaining script checks (rejection dialogs,
|
||
log-straight-in confirmation, credit/randomize/exit warnings).**
|
||
|
||
**Batch B (selection state media + label state) is CODE-COMPLETE
|
||
2026-08-16, pending the user's visual gate.** GF-1, GF-8, GF-9, GF-10,
|
||
GF-11b, and GF-11c are fixed — see each entry's own FIXED note below.
|
||
Fixture + live-DAT tests only this round (no graphical client launch);
|
||
App suite 5282/3 (was 5266/3), Runtime 1735/0 unchanged. Register:
|
||
AP-222 RETIRED, AP-215 NARROWED (item 1 retired, item 2 stays open).
|
||
|
||
**Batch C (text/frame/label fidelity, the largest visual batch) is
|
||
CODE-COMPLETE 2026-08-16, pending the user's visual gate.** GF-2, GF-3,
|
||
GF-4, GF-6, GF-11a, GF-12, and GF-14's text half are fixed — see each
|
||
entry's own FIXED note below. Three commits: (1) chargen-scoped rich
|
||
text + labels + backdrops (new shared `DatRichText` composer; `UiButton`
|
||
gains a coexisting `ValueLabel` slot; Heritage/Profession backdrop
|
||
`SetState` cascades; AP-216/AP-217 partially closed — the "beyond count"/
|
||
"Eyes-blank" halves ship, the palette-to-RGB "actual color"/"gradient
|
||
tint" halves stay open, judged disproportionate to add alongside this
|
||
batch's ~10 other fixes), (2) a CLIENT-WIDE `LayoutImporter` fix
|
||
un-consuming media-bearing dat children on `UiText`/`UiField` (37 distinct
|
||
(layout, element) pairs across 15 layouts, independently re-derived —
|
||
includes MAIN GAME UI and the chat transcript, closing the build half of pre-filed
|
||
issue #366), (3) the Summary how-to text
|
||
(`gmCGSummaryPage::SetHowToText`) plus the scrollbar-to-text-scroll
|
||
linkage Commit 2 left unbound. Fixture + live-DAT tests only (no
|
||
graphical client launch); App suite 5307/3 (was 5282/3, +25 tests, one
|
||
pre-existing full-suite-only allocation flake unrelated to this batch —
|
||
passes in isolation and in the full Release run), Runtime 1735/0
|
||
unchanged. Register: AP-215/AP-216/AP-217 rewritten, AP-218 retired,
|
||
AD-103 retired. **FLAG FOR THE LEAD: chat and the main game UI both
|
||
render new dat children (gold frames, an unseen-text indicator) for the
|
||
first time this batch — the user's own visual check of both is owed
|
||
before considering Commit 2 closed; automated coverage cannot catch a
|
||
purely visual placement regression.**
|
||
|
||
**Batch D (chargen 3D preview backdrop) is CODE-COMPLETE 2026-08-16, pending
|
||
the user's visual gate.** GF-7 and GF-14 are fixed — see each entry's own
|
||
FIXED note below. GF-16 (client-wide tooltips) was investigated in the same
|
||
root-cause pass but is explicitly out of this batch's scope — deferred as
|
||
`docs/ISSUES.md` #409 with its own decomp anchors. Fixture + live-DAT tests
|
||
only this round (no graphical client launch); App suite went from 5307/3 to
|
||
5321/3 (+14, zero regressions), Runtime 1735/0 unchanged. Both Launcher test
|
||
projects (this being the first build of the merged tree carrying the #406
|
||
launcher merge) pass at their own baselines: Launcher.Core.Tests 337/0,
|
||
Launcher.Tests 67/0. Blast radius: `PrivateEntityViewportRenderer` (shared
|
||
with paperdoll and creature-appraisal) gained an OPTIONAL second entity
|
||
slot reserved via a `backdropRenderId` constructor parameter — paperdoll
|
||
and creature-appraisal never pass one, so their draw stays single-entity
|
||
by construction (`SetBackdrop` throws if called without a reserved slot,
|
||
and the entity-list-assembly helper `BuildDrawEntities` degrades to
|
||
exactly the main entity whenever no backdrop is configured/set).
|
||
|
||
**Batch F (Skills page completion — R2-4 + review F1/F2) is CODE-COMPLETE
|
||
2026-08-16, pending the user's visual gate.** Four of R2-4's five
|
||
sub-items are fixed; R2-4b (the four-bucket sorted model) is NOT — see the
|
||
AP-213 register row for the exact missing data channel this batch's
|
||
investigation pinned down (`SkillBase.MinLevel`, confirmed present in the
|
||
installed dat but not threaded through `ChargenOptions`/
|
||
`CharacterCreationRuntimeBindings`). **Fixed:** R2-4a (row selection — a
|
||
row click, or an arrow click matching retail's own post-Increase/
|
||
DecreaseSkillLevel re-select, highlights the row and writes the info
|
||
panes' TITLE — name + score — and a level-gated bonus line; the
|
||
description/formula halves stay unported for the SAME missing-data reason
|
||
as R2-4b, documented on `CharacterCreationSkillsPage.RefreshInfoBox`'s own
|
||
doc rather than a new register row since no file outside the page's own
|
||
scope was needed to identify it); R2-4c (the listbox's own authored
|
||
scrollbar link, live-DAT-CONFIRMED at `0x100003F8` — exactly this batch's
|
||
own "+1 from the listbox" hypothesis — wired to the listbox's `Scroll`
|
||
model, the ordinary page-level linkage every other `UiTemplateListBox`
|
||
owner uses); review F1 (the Untrained-down/Specialized-up literal `"0"`
|
||
cost text the prior port rendered blank, and the exact per-branch 999-blank
|
||
gate — up-cost only, never down-cost); review F2 (the
|
||
`pSkillUpButton`/`pSkillDownButton` Ghosted/Enabled state pair, gated on
|
||
credits and a re-derived `bUntrainable`/`bUnspecializable` — the row's own
|
||
effective cost being nonzero — using cost data the page already resolves,
|
||
no new channel needed). Fixture + one live-DAT test this round (no
|
||
graphical client launch); App suite live-DAT env went from 5321/3 to
|
||
5328/3 (+7, zero regressions — one pre-existing baseline flake, the
|
||
streaming "injected dungeon enqueue failure" test, is a known standalone-
|
||
pass-only flake unrelated to this batch and did not reproduce on the full
|
||
post-fix run), Runtime 1735/0 unchanged.
|
||
|
||
User ran the six-page chargen flow live (build `1.0.2-cc.e`, RDP session,
|
||
windowed). Screenshots: retail Heritage, acdream Heritage, retail
|
||
Profession. The user's side-by-side retail reports are AXIOMS
|
||
(`feedback_retail_oracle_no_whack_a_mole`). Pre-page findings #405 (fixed
|
||
`344d88bf`), #406 (open), #407 (fixed `e601a496`) are recorded in
|
||
ISSUES.md; this doc is the six-page batch.
|
||
|
||
## Functional (blocking or behavior-dead)
|
||
|
||
- **GF-1 Heritage selection dead/unmarked — FIXED (Campaign CC gate round
|
||
1, Batch B).** Root cause: retail authors a custom radio-selection state
|
||
pair (`RetailUiStateIds.Unselected`/`Selected`, `0x10000016`/
|
||
`0x10000017`) on the heritage row (property-only state descriptors, no
|
||
media) with the actual art on a single stateful CHILD (the dot,
|
||
`0x100003C0`, media `0x06006E35`/`0x06006E21`, live-DAT-probe-confirmed).
|
||
`UiButton.AddAvailableStates` only recognized the standard Normal/
|
||
Highlight/Ghosted name space, so `_availableStates` never admitted the
|
||
custom pair and `.Selected` committed nothing (probe-verified before the
|
||
fix: `Selected=true` left `ActiveState=="Unselected"`, while the raw
|
||
`TrySetRetailState(0x10000017)` already worked). Fixed by teaching
|
||
`UiButton` to detect the authored pair (`HasStateMedia("Unselected") &&
|
||
HasStateMedia("Selected")`) at construction and bypass the standard
|
||
state machine for it — `.Selected` now routes directly to
|
||
`RetailUiStateIds.Selected`/`Unselected`, additive and gated on the
|
||
pair's presence, so every OTHER button's Normal/Highlight path is
|
||
byte-identical. The SAME fix also lights the Profession template icon
|
||
(`0x100003D9`), the Appearance Face/Clothes sub-tabs (GF-8, below), and
|
||
the gender buttons (whose media lives directly on the button, not a
|
||
child — the OTHER shape this fix covers). The open-roll's own
|
||
no-lit-dot-on-entry symptom shares this same root: `CharacterCreationHeritagePage.Refresh`
|
||
already sets `button.Selected = heritageId == snapshot.HeritageId` for
|
||
every row on every refresh (including the first one after open), so the
|
||
same `.Selected`-was-a-no-op bug silently ate the initial roll's own dot
|
||
too — this fix closes both halves of GF-1 with the same change.
|
||
- **GF-5 Skills page empty — FIXED (Campaign CC gate round
|
||
1, Batch A).** Root cause was `CharacterCreationSkillsPage.RebuildRows`
|
||
resolving `Templates[0]` (retail's own 3-child bucket-HEADER row,
|
||
`0x100002F4`) instead of `Templates[1]` (the REAL skill row,
|
||
`0x100002FF`, live-DAT-probe-confirmed 7 children) and requiring the
|
||
resolved root to be a `UiButton` (it's a plain container). Byte-traced
|
||
against `gmCGSkillsPage::DoSkillRecords @0x004817e0` +
|
||
`tagSkillRecord`'s copy-constructor field order to map every child id:
|
||
name (`0x10000301`), `pSkillLevelText` (`0x10000302`), `pUpCostText`
|
||
(`0x10000303`), `pSkillUpButton` (`0x10000304`), `pSkillDownButton`
|
||
(`0x10000305`), `pDownCostText` (`0x10000306`). Fixed to resolve
|
||
`Templates[1]`, wire the real per-row up/down arrow buttons to
|
||
`ListenToElementMessage @0x004814c0`'s own plain-click dispatch
|
||
(`IncreaseSkillLevel`/`DecreaseSkillLevel`), retiring AP-213's click-to-
|
||
advance/double-click-retreat single-button substitution (narrowed, not
|
||
fully retired — the flat-list-vs-four-bucket half stays). The credits-
|
||
caption clobber (`SkillsPage.cs:81-82`, now different line numbers) is
|
||
UNCHANGED — Batch C's scope.
|
||
- **GF-9 Appearance color swatches do nothing observable — FIXED (Campaign
|
||
CC gate round 1, Batch B).** Root cause confirmed as working-but-
|
||
invisible, not a dead dispatch: the `SelectColor`/`SetAppearanceIndex`
|
||
click path was already intact end to end (unchanged by this fix). The
|
||
swatch buttons themselves author ONLY an unnamed DirectState sprite —
|
||
live-DAT-probe-confirmed NO Normal/Highlight media at all — so the
|
||
existing `swatch.Selected = ...` highlight assignment in
|
||
`RefreshColorAndShadeControls` was a permanent no-op; nothing could ever
|
||
have shown a click's effect. Retail's REAL feedback mechanism is nine
|
||
separate companion overlay elements (`0x10000318`-`0x10000320`,
|
||
`CharacterCreationAppearancePage.SwatchOverlayIds`, live-DAT-confirmed
|
||
siblings of the swatches under the color-wheel container `0x100003B9`,
|
||
index-paired 1:1 with `SwatchIds`) that retail's `SetColor @0x0047DD50`
|
||
shows/hides via `m_tColorWheel[...][0x10][iCurColor*7]->SetVisible` —
|
||
cross-confirmed against `gmCGAppearancePage::InitializePage`'s own
|
||
swatch/overlay id-pair table (`@0x004800ff-00480164`). Fixed by wiring
|
||
exactly one overlay visible per part, tracking the current part's
|
||
selected color index; retires AP-215's swatch-selection substitution
|
||
(item 1 — the icon-vs-ordinal item 2 stays open).
|
||
- **GF-11a Town description text does not change when switching towns —
|
||
FIXED (Batch C, Commit 1).** The composed string
|
||
(`gmCGTownPage::SetTownString @0x0047c1f0`'s `howTo + "\n\n" + townText +
|
||
"\n"` — this ONE composition site is where retail's OWN code, not the
|
||
authored DAT string content, inserts the separator, confirmed via the
|
||
compiled format literal's raw bytes `u"\n\n%s\n"`) was already
|
||
byte-correct; the real gap was rendering it as a single un-wrapped line,
|
||
so the town-specific SUFFIX rendered past the clipped viewport —
|
||
switching towns changed the underlying string but not what was visibly
|
||
on screen. Fixed by routing through `DatRichText.Compose` (same fix
|
||
family as GF-2).
|
||
- **GF-13 Summary shows "-Non-admin or Non-envoy" below the name — FIXED
|
||
(Campaign CC gate round 1, Batch A) — this commit.** Root cause: dat
|
||
property `0x3B` (Invisible — `UIElement::OnSetAttribute @0x00462d80`
|
||
case 8) was never read by the importer at all; elements `0x10000403`
|
||
("Non-Admin") and `0x10000494` ("Non-Envoy") both author it `true`
|
||
(live-DAT-probe-confirmed, path `0x100003CC > 0x100003D0 > 0x100003D6 >
|
||
{0x10000403,0x10000494}`). Blast-radius sweep found **1,083 elements
|
||
client-wide** author the same flag — a blanket importer-wide honor is
|
||
its own visual gate, filed as ISSUES.md #408. This fix is CHARGEN-SCOPED
|
||
ONLY: `ElementInfo.Invisible`/`UiElement.AuthoredInvisible` are pure
|
||
data additions (read/stored everywhere, acted on nowhere by the shared
|
||
importer path), and `CharacterCreationUiController.HideAuthoredInvisibleElements`
|
||
walks its own mounted subtree once at construction and hides whatever
|
||
the dat itself marked hidden — by the authored flag, not a hardcoded id
|
||
list. Register AP-230 records the scoped-vs-general split.
|
||
- **GF-15 Summary name entry DEAD + Finish unpressable — FIXED
|
||
(Campaign CC gate round 1, Batch A) — this commit, LIVE-VERIFIED end to
|
||
end.** The live-repro investigation (offline `ACDREAM_OPEN_CHARGEN=1`
|
||
alone does NOT open the chargen screen — `RuntimeCharacterCreationState`
|
||
only activates via `LiveSessionController.StartAsync`'s authenticated-
|
||
connect path, `LiveSessionController.cs:791`; the repro required a real
|
||
connect to the project's own local ACE test server) showed the FIRST
|
||
click into the name field correctly focuses it and typing correctly
|
||
lands characters — the modal/pick/focus mechanics the earlier static
|
||
investigation examined were never broken. The REAL cause only surfaces
|
||
after the FIRST dialog opens: pressing Finish with an empty name
|
||
successfully creates the NoName `RetailMessageDialogView`
|
||
(`visible=true`, live-DAT-probe-confirmed nonzero popup/message/button
|
||
geometry — 400x95 popup, correctly centered) but it renders NOTHING and
|
||
silently absorbs every subsequent click across the WHOLE canvas,
|
||
including clicks aimed at the name field or Finish button underneath.
|
||
Root cause: `CharacterCreationUiController.Tick()` (and
|
||
`CharacterManagementUiController.Tick()`) call `UiRoot.BringToFront(Root)`
|
||
UNCONDITIONALLY every frame while their screen is open (needed so
|
||
chargen stays above the occluded character-management screen
|
||
underneath, register AP-229); a dialog's root is a direct sibling of
|
||
those screen roots under the same `UiRoot`, and
|
||
`RetailWindowManager.BringToFront` is a simple "highest ZOrder among
|
||
siblings + 1" — whichever sibling's own `BringToFront` call runs LAST in
|
||
a frame wins. `RetailDialogFactory.Tick()` never re-asserted its own
|
||
open dialogs' z-order, so the VERY NEXT frame's screen `Tick()` (which
|
||
always runs before the dialog factory's own `Tick()` in
|
||
`RetailUiRuntime.Tick(double)`'s per-frame sequence) silently buried the
|
||
dialog behind the screen's opaque backdrop — while the dialog remained
|
||
the registered `UiRoot.Modal` and kept EXCLUSIVE input priority
|
||
(`OnMouseDown`'s Modal-vs-bounds gate is independent of render/z-order).
|
||
Fixed by having `RetailDialogFactory.Tick()` re-raise every open dialog
|
||
(in `_openOrder`, so the most recently opened stays topmost) every tick,
|
||
matching retail's real always-on-top dialog behavior. Live-verified the
|
||
COMPLETE user sequence after the fix: click name field (focuses), type
|
||
(lands), press Finish empty (NoName dialog now VISIBLY renders: "You
|
||
must enter a name for this character!"), click OK (dismisses cleanly,
|
||
`Modal` clears), click the field again (still focusable/typable). The
|
||
`[ Name` prefill question is CLOSED, not a bug: byte-verified neither
|
||
`CharGenState::RandomizeCharacter @0x005c6d80` nor
|
||
`gmCGSummaryPage::InitializePage @0x0047bbf0` ever write text into the
|
||
name field (`InitializePage` only sets the input filter) — retail's
|
||
field is genuinely code-empty on a freshly-rolled character, matching
|
||
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)
|
||
|
||
- **GF-2 Description textboxes broken everywhere — FIXED (Campaign CC gate
|
||
round 1, Batch C, Commit 1 + Commit 2).** Root cause was TWO stacked
|
||
gaps, both closed: (1) the Heritage/Town/Profession description pages
|
||
bypassed escape-normalize + word-wrap entirely, assigning a raw
|
||
unwrapped single-`Line` `LinesProvider` — fixed by routing every
|
||
description box through the new shared `DatRichText.Compose` helper
|
||
(ports `UIElement_Text::SetStringInfoWithFont`/`AppendStringInfoWithFont`'s
|
||
composition model: escape-normalize, per-segment word-wrap, per-segment
|
||
palette color — Heritage's own header/body segments now render in
|
||
retail's own green/white, matching `AppendStringInfoWithFont`'s font-index
|
||
argument). (2) The authored gold frame (8 pieces) and linked scrollbar
|
||
were silently dropped by `UiText.ConsumesDatChildren` — fixed by
|
||
Commit 2's client-wide `LayoutImporter` carve-out (see GF-12). Both
|
||
halves are pinned by live-DAT tests (`CharacterCreationLiveDatTests`,
|
||
`LayoutImporterMediaBearingChildSweepTests`) and unit tests
|
||
(`DatRichTextTests`).
|
||
- **GF-3 Profession template description textbox missing — FIXED (Batch C,
|
||
Commit 1).** `gmCGProfessionPage::InitializePage @0x00483068`'s
|
||
`m_pTextBox` (`0x100003e0`) was never bound. Fixed:
|
||
`CharacterCreationProfessionPage` now binds it and composes the
|
||
per-template string (`ID_CharGen_CustomText`/`BowText`/`SwashText`/
|
||
`LifeText`/`WarText`/`WayText`/`SoldierText`, `UpdateProfession
|
||
@0x004821b0`'s per-case literal, plain `SetStringInfo` — one color, no
|
||
palette) through the same `DatRichText` helper.
|
||
- **GF-4 Profession labels missing — FIXED (Batch C, Commit 1).** Two
|
||
distinct mechanisms, both closed: (a) the four display buttons
|
||
(avail/health/stamina/mana credits) author their caption directly as
|
||
their own P0x17 AND carry a separate media-less Type-12 value child that
|
||
`UiButton.ConsumesDatChildren` used to drop entirely — pages substituted
|
||
the button's own `.Label`, destroying the caption. Fixed by giving
|
||
`UiButton` a coexisting `ValueLabel`/`ValueBox`/`ValueFont`/`ValueColor`
|
||
slot (`DatWidgetFactory.BuildButton`, gated on the button's own P0x17
|
||
caption existing), so the caption and the live value now render
|
||
independently — the same fix also closes the Skills page's credits
|
||
badge. (b) The six slider name labels (`0x100002ed`, live-DAT-measured
|
||
as `UIElement_Button` — retail's `UIElement_Button` is DynamicCast(0xc)-
|
||
compatible with `UIElement_Text`) are now written once at construction
|
||
with `CharGenState::GetAttributeName @0x005C3A20`'s six hardcoded
|
||
literals, matching retail's own single `InitializePage`-time write
|
||
(never re-written on refresh, same as retail).
|
||
- **GF-6 Appearance spin captions are numbers — FIXED (Batch C, Commit 1),
|
||
retiring AP-218.** `gmCGAppearancePage::Update @0x0047e8f0` writes a
|
||
heritage-flavored STATIC caption to the Hair/Eyes/Skin spins only
|
||
(normal / `GearText_*` / `OlthoiText_*` variants) — never an index, and
|
||
never touches the other six spins' own DAT-authored caption at all.
|
||
Removed the prior 1-based-ordinal/gear-name substitution outright.
|
||
AP-215's own icon-thumbnail item (the four icon-only spins still show no
|
||
per-choice icon art — a DIFFERENT, still-open gap) is rewritten, not
|
||
retired — see that row.
|
||
- **GF-7 Preview backdrop black on Appearance (and Summary, GF-14) — FIXED
|
||
(Campaign CC gate round 1, Batch D).** Root cause: retail's
|
||
`gmCG3DView::Update @0x004EE9D0` (~0x004eecd3-0x004eed44) constructs a
|
||
SECOND `CPhysicsObj` from the current heritage's own
|
||
`HeritageGroup_CG.environmentSetupID` field (verbatim struct layout,
|
||
`acclient.h`) and adds it to the SAME viewport's `creature_mode_objects`
|
||
the player object lives in — this codebase already parsed that id as
|
||
`ChargenHeritageOptions.EnvironmentSetupId` (`ChargenTableReader.cs`)
|
||
but never consumed it anywhere. The decompiler elides the actual field
|
||
read (`var_b8`/`eax_32`, an unresolved-call artifact — see
|
||
`claude-memory/feedback_bn_decomp_field_names.md`); cross-referencing
|
||
`acclient.h`'s `HeritageGroup_CG` struct (environmentSetupID sits right
|
||
after setupID) confirmed what the elided value is. The backdrop object
|
||
gets NO explicit position/orientation/scale — `CPhysicsObj::makeObject`
|
||
(0x004eed2f) leaves it at the scene origin with identity orientation,
|
||
same as the player object's own default placement, and retail's own
|
||
`AddObject` insertion order puts the backdrop BEFORE the player (the
|
||
player's own re-`AddObject` happens later, at ~0x004ef199, after the
|
||
full clothing ObjDesc composes). Fixed by extending
|
||
`ChargenPreviewEntityBuilder` with `TryBuildBackdrop` (builds a plain,
|
||
unposed Setup mesh from the heritage's `EnvironmentSetupId`, returning
|
||
null for id 0/unset or an unresolvable Setup — matching retail's own
|
||
`if (eax_32 != INVALID_DID.id)` gate at 0x004eed29), giving
|
||
`PrivateEntityViewportRenderer` an optional second entity slot
|
||
(`SetBackdrop`, reserved via a `backdropRenderId` constructor param so
|
||
paperdoll/creature-appraisal — which never pass one — cannot acquire a
|
||
second entity even by accident), and wiring `ChargenPreviewController`
|
||
to rebuild the backdrop whenever the HERITAGE changes (narrower than the
|
||
existing camera-eye-reset gate, since `environmentSetupID` is a pure
|
||
function of heritage, never gender or appearance selection). Both
|
||
Appearance and Summary get the fix from the SAME `ChargenPreviewRenderer`
|
||
facade — no page-specific code needed, confirmed both pages call the
|
||
identical `gmCG3DView::Update` on their own separate `gmCG3DView`
|
||
instance. Lighting was independently re-verified against the same
|
||
function's `SetLight` call (`DISTANT_LIGHT, 2.0, (0.3, 1.9, 0.65)`
|
||
direction, default white color) and found to ALREADY match byte-for-byte
|
||
what CC6a shipped — no lighting change was needed.
|
||
- **GF-8 Appearance Face/Clothes sub-tab selection unmarked — FIXED
|
||
(Campaign CC gate round 1, Batch B).** Same root and same fix as GF-1:
|
||
the Face (`0x100003A9`)/Clothes (`0x100003AA`) sub-tab buttons author
|
||
the identical custom Unselected/Selected radio-pair shape (media on a
|
||
stateful icon child, `0x100002E9`, live-DAT-probe-confirmed) — not the
|
||
AP-222 family as originally suspected (AP-222 turned out to be a
|
||
DIFFERENT mechanism, the per-state label color/outline gap fixed
|
||
alongside GF-11b below). `UiButton`'s custom-selection-pair bypass
|
||
fixes both in one change.
|
||
- **GF-10 Zoom buttons show identical art — FIXED (Campaign CC gate round
|
||
1, Batch B).** Pure wiring gap, not a widget mechanism problem — both
|
||
zoom buttons already author a standard Normal/Highlight(/rollover) pair
|
||
(live-DAT-probe-confirmed). `gmCGAppearancePage::ZoomIn @0x0047CF00`
|
||
(`@0x0047d005/0x0047d00f`) ends `ZoomInButton->SetState(6)` (Highlight),
|
||
`ZoomOutButton->SetState(1)` (Normal); `ZoomOut @0x0047D050` mirrors.
|
||
`CharacterCreationAppearancePage`'s click handlers only ever called
|
||
`PreviewControl.ZoomIn()/ZoomOut()`, never touching either button's
|
||
state — fixed to set the mutual-exclusive pair on every click. Re-
|
||
derived the INITIAL state from `InitializePage @0x0047fdd0-0048032e`:
|
||
`m_bZoomedIn = 0` is set at construction, but NO explicit initial
|
||
`SetState` call exists for either zoom button anywhere in
|
||
`InitializePage` — both start at their DAT-authored "Normal" default
|
||
until the first real zoom click; this port does not force an initial
|
||
Highlight either.
|
||
- **GF-11b Town selected marker does not turn white — FIXED (Campaign CC
|
||
gate round 1, Batch B).** The marker PIN art itself already swapped
|
||
correctly (the town button's own Normal/Highlight state machine was
|
||
never broken — its marker child, `0x1000040C`, authors real Highlight
|
||
media). What was missing: retail ALSO recolors the town NAME caption
|
||
(a lifted Type-12 child, id collides with the page-level description
|
||
panel's own id `0x10000409` in the installed dat — two distinct
|
||
elements in two distinct subtrees, harmless for the per-button lift)
|
||
from gold (218,167,85) to white (255,255,255) on selection, live-DAT-
|
||
measured. `DatWidgetFactory.BuildButton` lifted the caption's font
|
||
COLOR once at build time with no per-state override. Same root and fix
|
||
as AP-222 (below): per-state label color/outline, applied off the
|
||
REQUESTED retail state id.
|
||
- **GF-11c Town names misaligned on the map — FIXED (Campaign CC gate
|
||
round 1, Batch B).** The per-button caption's own authored rect
|
||
(`(0,4,100,37)`, Center-justified, live-DAT-measured) was being
|
||
discarded in favor of a Left-aligned offset computed from the marker
|
||
FACE's rect (`face.X + face.Width + 4`) — correct for the heritage/
|
||
template/Face-Clothes row family (label authored DIRECTLY on the
|
||
button, beside a single-purpose face segment) but wrong here, where a
|
||
DISTINCT Type-12 caption child was lifted with its own independent
|
||
geometry. Fixed by adding `UiButton.LabelBox`: when a distinct lifted
|
||
caption carries its own rect, the label draws within THAT box using
|
||
its own authored justify instead of the face-relative offset; every
|
||
other button (`LabelBox` null) keeps the EXACT prior draw math.
|
||
- **GF-12 Missing authored gold frames around boxes — FIXED (Batch C,
|
||
Commit 2).** Root cause: `UiText.ConsumesDatChildren` (true unless a
|
||
state authors PassToChildren) and `UiField.ConsumesDatChildren`
|
||
(unconditionally true) dropped EVERY dat child at import time,
|
||
including the eight gold-frame pieces (`0x100002DE..E3`,
|
||
`0x100000E8/EA`) every description/report box authors. Fixed by a new
|
||
`LayoutImporter.BuildWidget` carve-out (mirroring the existing `UiMeter`
|
||
text-overlay carve-out): build any child with its OWN non-empty
|
||
`StateMedia`, leaving purely structural/property-only children dropped
|
||
as before. Independently re-derived blast radius: **37 distinct
|
||
(layout, element) pairs across 15 layouts** (see the commit message for
|
||
the full enumeration), including MAIN GAME UI (`0x21000005/0x1000059A`)
|
||
and the chat transcript (`0x2100006F/0x10000011` — closing the BUILD half of
|
||
pre-filed issue #366's own "fix shape" recommendation, which proposed
|
||
this EXACT carve-out). Full App suite (5304 tests): zero regressions.
|
||
**The user's own visual check of chat + the main game UI is still owed**
|
||
— automated coverage cannot catch a purely visual placement regression.
|
||
**Closeout Group 3 (F5/F6, 2026-08-16):** one of this carve-out's
|
||
media-bearing children — the chat new-text indicator, `0x1000048C`,
|
||
live-DAT-confirmed authored `Invisible=true` on every layout it appears
|
||
in (`0x21000005`/`0x21000006`/`0x2100005B`/`0x2100006F`) — was building as
|
||
a visible phantom element retail never shows. Fixed with a NARROW honor
|
||
scoped to exactly this carve-out (`LayoutImporter.BuildWidget`'s
|
||
`UiText or UiField` branch sets a built child's `Visible = false` when its
|
||
own `AuthoredInvisible` flag is set), not the general #408 client-wide
|
||
honor. Verified in both directions: the invisible chat indicator now
|
||
builds hidden, and the eight chargen/main-game-UI gold-frame pieces
|
||
(`0x100002DE-E3`/`0x100000E8`/`0x100000EA`) do NOT author `Invisible` and
|
||
stay visible — confirmed both by a live-DAT sweep
|
||
(`MediaBearingChildSweep_EnumeratesWhichAffectedChildrenAuthorInvisible`)
|
||
and a fixture regression test.
|
||
- **GF-14 Summary paperdoll backdrop black — FIXED (Campaign CC gate round
|
||
1, Batch D, same fix as GF-7 above — both pages call the identical
|
||
`gmCG3DView::Update` on their own `gmCG3DView` instance).** **Summary
|
||
textbox wrapper + scrollbar — FIXED (Batch C, Commit 2 for the
|
||
frame/build half, Commit 3 for the scrollbar LINK and the how-to text's
|
||
own content — see the Suspected-shared-roots entry and Commit 3's own
|
||
composition of `gmCGSummaryPage::SetHowToText` into `0x10000404`).**
|
||
- **GF-16 Hover tooltips missing on all pages** (retail pops tooltips).
|
||
DEFERRED to its own gate round — filed as
|
||
[`docs/ISSUES.md` #409](../ISSUES.md) with the decomp anchors
|
||
(`UIElement::StartTooltipAtMouse @0x00460D70`,
|
||
`UIElementManager::StartTooltip @0x0045DE90` + `@0x00459700`, layout DID
|
||
`0x21000041`, properties P0x47-P0x4B, ~253 authored elements, prefs
|
||
`Misc_TooltipEnable`/`Misc_TooltipDelay`) the Batch D investigation
|
||
surfaced. Out of Batch D's scope: it is a CLIENT-WIDE mechanism, not the
|
||
chargen 3D preview backdrop Batch D actually fixed (GF-7/GF-14 above).
|
||
|
||
## Suspected shared roots (to be CONFIRMED by the investigation, not assumed)
|
||
|
||
1. ~~Missing frames/labels/statics across every page (GF-3, GF-4 labels,
|
||
GF-12) — one importer/mount-level gap OR retail writes them at runtime;
|
||
decide per element from the authored DAT + decomp.~~ CONFIRMED, CLOSED
|
||
at Batch C: TWO distinct mechanisms, both a single shared fix each. (a)
|
||
GF-3/GF-4's labels were runtime-composition gaps (unbound description
|
||
textbox; a value-write clobbering a caption) — fixed per-page, Commit 1.
|
||
(b) GF-12's frames were the importer-level gap the investigation
|
||
suspected: `LayoutImporter.BuildWidget`'s `ConsumesDatChildren` handling
|
||
dropped every dat child of a `UiText`/`UiField`, client-wide — fixed by
|
||
the new media-bearing-child carve-out, Commit 2.
|
||
2. ~~Rich text (escape decoding, wrap, scroll, frame) — one text-widget gap
|
||
feeding GF-2/GF-3/GF-11a/GF-14.~~ CONFIRMED, CLOSED at Batch C: the new
|
||
shared `DatRichText.Compose` helper (escape-normalize + per-segment
|
||
word-wrap + per-segment palette color, Commit 1) plus the Commit-2 frame/
|
||
scrollbar un-consume plus Commit-3's scrollbar-to-text-scroll linkage
|
||
(`UiScrollbar.Model = text.Scroll`, the same pattern
|
||
`ChatWindowController` already used) together close the WHOLE family —
|
||
GF-2/GF-3/GF-11a/GF-14's text half are all FIXED; GF-14's backdrop half
|
||
(GF-7 family) is unrelated and stays open.
|
||
3. ~~Selection state media (GF-1 dot, GF-8 sub-tabs, GF-11b white marker,
|
||
GF-10 zoom art) — the AP-222 measured mechanism (state media authored
|
||
vs applied) across widget kinds.~~ CLOSED, split into TWO distinct
|
||
mechanisms, both fixed at Batch B: (a) GF-1/GF-8 share a genuinely
|
||
UNRECOGNIZED custom state-name pair (`UiButton` never admitted
|
||
"Unselected"/"Selected" into its available-states set at all); GF-10
|
||
was pure wiring (the standard Normal/Highlight pair was never even
|
||
requested). (b) GF-11b turned out NOT to be a state-media gap — the
|
||
marker's own media swap already worked; the actual gap was AP-222's
|
||
real mechanism, per-state LABEL COLOR/OUTLINE (a property commit
|
||
distinct from the art/media commit, and NOT gated by the same art-
|
||
availability check `ActiveState` is). See each GF's own FIXED entry
|
||
above and the retired AP-222 / narrowed AP-215 register rows.
|
||
4. ~~Preview backdrop (GF-7/GF-14) — what gmCG3DView clears/draws.~~
|
||
CONFIRMED, CLOSED at Batch D: `gmCG3DView::Update`'s own
|
||
`m_pbgObject`/`m_bgSetupID` pair, sourced from the heritage's
|
||
`HeritageGroup_CG.environmentSetupID` field — already parsed into this
|
||
codebase as `ChargenHeritageOptions.EnvironmentSetupId` but never
|
||
consumed before this fix. See GF-7's own FIXED entry above for the full
|
||
decomp citation.
|
||
5. ~~Input routing on Summary (GF-15) — focus/typing path on the stacked
|
||
chargen screen.~~ CLOSED: focus/typing routing was never broken (live-
|
||
verified); the real cause was `RetailDialogFactory` never re-asserting
|
||
its open dialogs' z-order against the chargen/char-management screens'
|
||
own per-tick `BringToFront` — see GF-15's own entry above. Batch A
|
||
fixed it.
|
||
|
||
## Process
|
||
|
||
Root-cause investigation FIRST (report-only, static + live-DAT probe
|
||
tests, NO client launches while the user's client may be running), then
|
||
batched fix rounds per family with Opus review, one republish per batch.
|