acdream/docs/research/2026-08-16-campaign-cc-gate-round1-findings.md
Erik 7d09821fdc fix(chargen): Campaign CC gate round 1 Batch B — authored selection states, label state, zoom/swatch feedback
GF-1/GF-8: UiButton now recognizes retail's custom Unselected/Selected
radio-pair (0x10000016/0x10000017), bypassing the standard Normal/
Highlight machine that never admitted those state names — .Selected now
lights the heritage/template/gender/Face-Clothes rows it was always a
no-op for.

AP-222/GF-11b: per-state label color/outline (dat 0x1B/0x21) now applies
off the REQUESTED retail state id, not the art-gated committed
ActiveState — resolves the Appearance spins' current-part highlight
(text recolors even though no Highlight art exists on either client) and
the Town caption's Normal-to-white swap.

GF-11c: UiButton.LabelBox lets a lifted caption with its own authored
rect draw there instead of the face-relative offset that's only correct
when the label is authored directly on the button (heritage/template
family, unchanged).

GF-9: wires the real nine companion overlay elements (SetColor's
SetVisible mechanism) that swatch clicks were always meant to drive,
retiring AP-215 item 1 (the swatch.Selected substitution was a permanent
no-op — swatches author no Highlight media at all).

GF-10: zoom buttons now set the retail-mirrored mutual-exclusive
Highlight/Normal pair on click; InitializePage carries no initial
SetState for either button, so both stay at "Normal" until first click.

Register: AP-222 retired (mechanism identified and ported), AP-215
narrowed (item 1 retired, item 2 unrelated and unchanged), row count
recount corrected 164 (was already one high before this batch).

App suite 5282/3 (was 5266/3), Runtime 1735/0 unchanged. Fixture + live-
DAT tests only — no graphical client launch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 11:37:09 +02:00

17 KiB

Campaign CC connected gate — round 1 findings (2026-08-16)

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).

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.
  • 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.

Presentation families (retail parity)

  • GF-2 Description textboxes broken everywhere. acdream renders the raw string with LITERAL \n escapes, one truncated line, no wrap, no scroll, no frame. Retail: framed scrollable textbox, multi-paragraph, colored section headers (green "Trained Starting Skills:" etc.), scrollbar + arrows (screenshot 1 right panel).
  • GF-3 Profession template description textbox missing (retail bottom- left panel, "LIFE CASTERS are experts…" — screenshot 3).
  • GF-4 Profession labels missing: "Attribute Credits" caption + value, per-attribute name labels (Strength…Self), Health/Stamina/Mana labels + values. Sliders and template selection themselves WORK.
  • GF-6 Appearance spin captions are numbers, not part names ("Hair Style", "Eyes", …). Known rows AP-215 (item 2 — item 1, the swatch-selection substitution, RETIRED at Batch B/GF-9)/AP-218 — the gate promotes them to must-port.
  • GF-7 Preview backdrop black on Appearance (and Summary, GF-14); retail's chargen 3D view shows a scenic backdrop. (The Heritage-page preview area shows terrain in BOTH clients — establish from the decomp what actually renders behind the model per page/view.)
  • 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 on every page (Skills/Appearance/Town/Summary called out explicitly).
  • GF-14 Summary paperdoll backdrop black (same family as GF-7); Summary textbox wrapper + scrollbar missing (GF-2 family).
  • GF-16 Hover tooltips missing on all pages (retail pops tooltips).

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.
  2. Rich text (escape decoding, wrap, scroll, frame) — one text-widget gap feeding GF-2/GF-3/GF-11a/GF-14.
  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.
  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.