R2-5: retail's gmCGAppearancePage::DoColorSpots/SetSelection/DoGradDisk paint the nine color swatches and the gradient disc with a real, computed representative color (PalSet-averaged for Hair/Nose+Mouth+ Skin/Headgear/Shirt/Trousers/Footwear at fixed sample indices 0xd0/0xb0/0x520; direct-Palette for Eyes at 0x103), not the static authored art acdream showed before this batch. Ports the full palette-to-RGB pipeline: a new pure Core resolver (ChargenSwatchColorResolver + IChargenPaletteColorSource) backed by a new ChargenAppearanceCatalog.TryGetColor reading real Palette dat objects, pinned against the installed EoR dat. CharacterCreationAppearancePage recomputes all nine swatches + the gradient disc's tint on every refresh (part/color/heritage change) and paints them through a new ChargenSwatchColorTile overlay child — a flat-color-fill approximation of retail's actual recolored-sprite blit, since neither UiButton (sealed) nor UiDatElement exposes a per-instance sprite tint today. Two STOPPED items remain outside this batch's file contract before the mechanism is visually live: (1) wiring PalSetSource/ClothingTableSource/ PaletteColorSource from CharacterCreationUiController.cs (mirrors the existing PreviewControl seam); (2) a small additive Tint property on UiButton/UiDatElement for a byte-true recolor instead of the flat fill. Also ports Nose/Mouth/Skin's single non-interactive representative swatch, beyond AP-216/AP-217's original six-part scope. Register AP-216/AP-217 rewritten (not retired — the two STOPPED items keep them open). Tests: 11 new Core, 6 new Content live-DAT, 8 new App-layer fixture. App suite 5321/3 -> 5329/3, Runtime 1735/0 unchanged, zero regressions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
33 KiB
Campaign CC connected gate — round 1 findings (2026-08-16)
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.
- R2-2:
Attribute\n Creditsrenders 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. - R2-3: Skills "Available Skill Credits" value overlaps mid-caption ("Available Skill Credit0Credits") — same ValueLabel-rect family as R2-2.
- 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/FCare 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. - 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 indices0xd0/0xb0/0x520, direct-Palette for Eyes at0x103) backed by a newChargenAppearanceCatalog.TryGetColorreading 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)).CharacterCreationAppearancePagerecomputes all nine swatches + the gradient disc's tint on every refresh (part change / color change / heritage change,CharacterCreationAppearancePageSwatchColorTests), and paints them through a newChargenSwatchColorTileoverlay element. Two STOPPED items block this from being visually live, both outside Batch G's file contract: (1) the newPalSetSource/ClothingTableSource/PaletteColorSourcelate-bound seams (mirroring the existingPreviewControlpattern) 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 — neitherUiButton(sealed) norUiDatElementexposes a per-instance spriteTint, though the retained-UI sprite pipeline'sDrawSpritealready carries theVector4 tintmultiply retail's ownBlit_Multiplyneeds; 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-codedvar_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.
- 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").
- 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 (P0x17on0x10000402) — 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).
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 CHAT INPUT, 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).
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, media0x06006E35/0x06006E21, live-DAT-probe-confirmed).UiButton.AddAvailableStatesonly recognized the standard Normal/ Highlight/Ghosted name space, so_availableStatesnever admitted the custom pair and.Selectedcommitted nothing (probe-verified before the fix:Selected=trueleftActiveState=="Unselected", while the rawTrySetRetailState(0x10000017)already worked). Fixed by teachingUiButtonto detect the authored pair (HasStateMedia("Unselected") && HasStateMedia("Selected")) at construction and bypass the standard state machine for it —.Selectednow routes directly toRetailUiStateIds.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.Refreshalready setsbutton.Selected = heritageId == snapshot.HeritageIdfor 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.RebuildRowsresolvingTemplates[0](retail's own 3-child bucket-HEADER row,0x100002F4) instead ofTemplates[1](the REAL skill row,0x100002FF, live-DAT-probe-confirmed 7 children) and requiring the resolved root to be aUiButton(it's a plain container). Byte-traced againstgmCGSkillsPage::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 resolveTemplates[1], wire the real per-row up/down arrow buttons toListenToElementMessage @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/SetAppearanceIndexclick 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 existingswatch.Selected = ...highlight assignment inRefreshColorAndShadeControlswas 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 container0x100003B9, index-paired 1:1 withSwatchIds) that retail'sSetColor @0x0047DD50shows/hides viam_tColorWheel[...][0x10][iCurColor*7]->SetVisible— cross-confirmed againstgmCGAppearancePage::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'showTo + "\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 bytesu"\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 throughDatRichText.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 @0x00462d80case 8) was never read by the importer at all; elements0x10000403("Non-Admin") and0x10000494("Non-Envoy") both author ittrue(live-DAT-probe-confirmed, path0x100003CC > 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.AuthoredInvisibleare pure data additions (read/stored everywhere, acted on nowhere by the shared importer path), andCharacterCreationUiController.HideAuthoredInvisibleElementswalks 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=1alone does NOT open the chargen screen —RuntimeCharacterCreationStateonly activates viaLiveSessionController.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 NoNameRetailMessageDialogView(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()(andCharacterManagementUiController.Tick()) callUiRoot.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 sameUiRoot, andRetailWindowManager.BringToFrontis a simple "highest ZOrder among siblings + 1" — whichever sibling's ownBringToFrontcall runs LAST in a frame wins.RetailDialogFactory.Tick()never re-asserted its own open dialogs' z-order, so the VERY NEXT frame's screenTick()(which always runs before the dialog factory's ownTick()inRetailUiRuntime.Tick(double)'s per-frame sequence) silently buried the dialog behind the screen's opaque backdrop — while the dialog remained the registeredUiRoot.Modaland kept EXCLUSIVE input priority (OnMouseDown's Modal-vs-bounds gate is independent of render/z-order). Fixed by havingRetailDialogFactory.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,Modalclears), click the field again (still focusable/typable). The[ Nameprefill question is CLOSED, not a bug: byte-verified neitherCharGenState::RandomizeCharacter @0x005c6d80norgmCGSummaryPage::InitializePage @0x0047bbf0ever write text into the name field (InitializePageonly sets the input filter) — retail's field is genuinely code-empty on a freshly-rolled character, matching acdream's existing (correct) behavior; the[ Namethe 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.
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-
LineLinesProvider— fixed by routing every description box through the new sharedDatRichText.Composehelper (portsUIElement_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, matchingAppendStringInfoWithFont's font-index argument). (2) The authored gold frame (8 pieces) and linked scrollbar were silently dropped byUiText.ConsumesDatChildren— fixed by Commit 2's client-wideLayoutImportercarve-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'sm_pTextBox(0x100003e0) was never bound. Fixed:CharacterCreationProfessionPagenow binds it and composes the per-template string (ID_CharGen_CustomText/BowText/SwashText/LifeText/WarText/WayText/SoldierText,UpdateProfession @0x004821b0's per-case literal, plainSetStringInfo— one color, no palette) through the sameDatRichTexthelper. - 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.ConsumesDatChildrenused to drop entirely — pages substituted the button's own.Label, destroying the caption. Fixed by givingUiButtona coexistingValueLabel/ValueBox/ValueFont/ValueColorslot (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 asUIElement_Button— retail'sUIElement_Buttonis DynamicCast(0xc)- compatible withUIElement_Text) are now written once at construction withCharGenState::GetAttributeName @0x005C3A20's six hardcoded literals, matching retail's own singleInitializePage-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 @0x0047e8f0writes 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 SECONDCPhysicsObjfrom the current heritage's ownHeritageGroup_CG.environmentSetupIDfield (verbatim struct layout,acclient.h) and adds it to the SAME viewport'screature_mode_objectsthe player object lives in — this codebase already parsed that id asChargenHeritageOptions.EnvironmentSetupId(ChargenTableReader.cs) but never consumed it anywhere. The decompiler elides the actual field read (var_b8/eax_32, an unresolved-call artifact — seeclaude-memory/feedback_bn_decomp_field_names.md); cross-referencingacclient.h'sHeritageGroup_CGstruct (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 ownAddObjectinsertion order puts the backdrop BEFORE the player (the player's own re-AddObjecthappens later, at ~0x004ef199, after the full clothing ObjDesc composes). Fixed by extendingChargenPreviewEntityBuilderwithTryBuildBackdrop(builds a plain, unposed Setup mesh from the heritage'sEnvironmentSetupId, returning null for id 0/unset or an unresolvable Setup — matching retail's ownif (eax_32 != INVALID_DID.id)gate at 0x004eed29), givingPrivateEntityViewportRendereran optional second entity slot (SetBackdrop, reserved via abackdropRenderIdconstructor param so paperdoll/creature-appraisal — which never pass one — cannot acquire a second entity even by accident), and wiringChargenPreviewControllerto rebuild the backdrop whenever the HERITAGE changes (narrower than the existing camera-eye-reset gate, sinceenvironmentSetupIDis a pure function of heritage, never gender or appearance selection). Both Appearance and Summary get the fix from the SAMEChargenPreviewRendererfacade — no page-specific code needed, confirmed both pages call the identicalgmCG3DView::Updateon their own separategmCG3DViewinstance. Lighting was independently re-verified against the same function'sSetLightcall (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) endsZoomInButton->SetState(6)(Highlight),ZoomOutButton->SetState(1)(Normal);ZoomOut @0x0047D050mirrors.CharacterCreationAppearancePage's click handlers only ever calledPreviewControl.ZoomIn()/ZoomOut(), never touching either button's state — fixed to set the mutual-exclusive pair on every click. Re- derived the INITIAL state fromInitializePage @0x0047fdd0-0048032e:m_bZoomedIn = 0is set at construction, but NO explicit initialSetStatecall exists for either zoom button anywhere inInitializePage— 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 id0x10000409in 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.BuildButtonlifted 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 addingUiButton.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 (LabelBoxnull) 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) andUiField.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 newLayoutImporter.BuildWidgetcarve-out (mirroring the existingUiMetertext-overlay carve-out): build any child with its OWN non-emptyStateMedia, 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 CHAT INPUT (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. - 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::Updateon their owngmCG3DViewinstance). 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 ofgmCGSummaryPage::SetHowToTextinto0x10000404). - GF-16 Hover tooltips missing on all pages (retail pops tooltips).
DEFERRED to its own gate round — filed as
docs/ISSUES.md#409 with the decomp anchors (UIElement::StartTooltipAtMouse @0x00460D70,UIElementManager::StartTooltip @0x0045DE90+@0x00459700, layout DID0x21000041, properties P0x47-P0x4B, ~253 authored elements, prefsMisc_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)
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'sConsumesDatChildrenhandling dropped every dat child of aUiText/UiField, client-wide — fixed by the new media-bearing-child carve-out, Commit 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 sharedDatRichText.Composehelper (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 patternChatWindowControlleralready 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.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 (UiButtonnever 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 checkActiveStateis). See each GF's own FIXED entry above and the retired AP-222 / narrowed AP-215 register rows.Preview backdrop (GF-7/GF-14) — what gmCG3DView clears/draws.CONFIRMED, CLOSED at Batch D:gmCG3DView::Update's ownm_pbgObject/m_bgSetupIDpair, sourced from the heritage'sHeritageGroup_CG.environmentSetupIDfield — already parsed into this codebase asChargenHeritageOptions.EnvironmentSetupIdbut never consumed before this fix. See GF-7's own FIXED entry above for the full decomp citation.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 wasRetailDialogFactorynever re-asserting its open dialogs' z-order against the chargen/char-management screens' own per-tickBringToFront— 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.