fix(chargen): Campaign CC gate round 1 Batch A — GF-15 input, GF-5 skills rows, GF-13 GM toggles

GF-15 (the gate blocker): the Summary name field and Finish button were
NOT structurally broken — live repro over the project's own local ACE
test server showed clicks correctly focus the field and land characters.
The real bug only surfaces after the first dialog opens: pressing Finish
empty successfully creates the NoName RetailMessageDialogView (visible,
correct 400x95 geometry) but it renders nothing and silently absorbs
every click across the whole canvas. Root cause: CharacterCreationUiController.Tick
and CharacterManagementUiController.Tick both call UiRoot.BringToFront(Root)
unconditionally every frame (needed so chargen stays above the occluded
management screen, AP-229); a dialog root is a direct sibling under the
same UiRoot, and RetailWindowManager.BringToFront is "highest ZOrder among
siblings + 1" — whichever BringToFront runs last in a frame wins.
RetailDialogFactory.Tick never re-asserted its own dialogs' z-order, so
the next frame's screen Tick buried the dialog behind the screen's opaque
backdrop while it stayed the registered Modal with exclusive input
priority. Fixed by having RetailDialogFactory.Tick re-raise every open
dialog (in open-order) each tick, matching retail's always-on-top dialog
behavior. Live-verified the complete user sequence end to end: click
field, type, press Finish empty, dialog now visibly renders, OK dismisses
cleanly, field still typable afterward. The "[ Name" prefill question is
closed as a non-bug: neither CharGenState::RandomizeCharacter nor
gmCGSummaryPage::InitializePage write text into the field in the decomp;
retail's field is genuinely empty on open, matching acdream already.

GF-5: CharacterCreationSkillsPage.RebuildRows resolved the wrong listbox
template (Templates[0], retail's own 3-child bucket-header row) and
required the root to be a UiButton (it's a plain container). Byte-traced
gmCGSkillsPage::DoSkillRecords + tagSkillRecord's copy-ctor field order
to map every child id in the real row (Templates[1]): name, level/cost
text, and the two real per-row up/down arrow buttons. Wired the arrows to
retail's own plain-click dispatch, retiring (narrowing) AP-213's
click-to-advance/double-click-retreat single-button substitution.

GF-13: dat property 0x3B (Invisible) was never read by the importer.
Elements 0x10000403/0x10000494 ("Non-Admin"/"Non-Envoy") author it true.
A blast-radius sweep found 1,083 elements client-wide author the same
flag, so this fix stays chargen-scoped only (ElementInfo.Invisible /
UiElement.AuthoredInvisible are pure data additions; only
CharacterCreationUiController acts on them, by the authored flag, not a
hardcoded id list). General importer-wide honor filed as ISSUES.md #408;
register row AP-230 records the split.

Gates: solution build green; App 5266/3 skips/0 failed; Runtime 1735/0;
full-solution run 0 failures anywhere. Register: AP-230 filed, AP-213
narrowed. ISSUES: #408 filed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-16 10:54:41 +02:00
parent 6699e0f88c
commit 1d9de5e095
11 changed files with 823 additions and 78 deletions

View file

@ -24,6 +24,53 @@ What does NOT go here:
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
## #408 — General importer-wide honor of dat property 0x3B (Invisible) is unshipped (1,083 elements client-wide)
**Status:** OPEN
**Severity:** LOW-MEDIUM (cosmetic — extra/leaked elements render where retail hides them; no gameplay/wire impact)
Found while fixing GF-13 (Campaign CC gate round 1, Batch A, 2026-08-16):
acdream's `LayoutImporter`/`DatWidgetFactory` never read dat property
`0x3B` (Invisible — `BoolBaseProperty`), which retail's
`UIElement::OnSetAttribute @0x00462d80` case 8
(`GetPropertyName()-0x33==8`) honors on EVERY element via
`SetVisible(value==0)`. The blast-radius sweep this fix's investigation
ran found **1,083 elements client-wide** author `P0x3B=true` — far
beyond the two chargen-Summary GM labels (`0x10000403`
"Non-Admin"/`0x10000494` "Non-Envoy") the user actually reported.
The fix (`fix(chargen): Campaign CC gate round 1 Batch A`) added the data
plumbing everywhere (`ElementInfo.Invisible`, read in
`ElementReader.ApplyCanonicalLegacyProjection`; `UiElement.AuthoredInvisible`,
set in `LayoutImporter.BuildWidget`) but deliberately does NOT act on it
in the shared importer path — only `CharacterCreationUiController`
(`HideAuthoredInvisibleElements`) walks its own mounted subtree and
hides what it finds, chargen-scoped only. Register row AP-230 records
the split.
Honoring the flag client-wide (setting `UiElement.Visible = false`
directly in `LayoutImporter.BuildWidget` when `info.Invisible` is true,
or an equivalent central chokepoint) is straightforward, but 1,083
elements is its own visual-regression surface: any one of them could be
an element some OTHER screen currently relies on being visible despite
authoring the flag (e.g. a state-conditional visibility toggle that
happens to leave `0x3B=true` on its default/direct state while a
controller separately manages `Visible` at runtime). This needs its own
sweep — dump the 1,083 ids grouped by owning LayoutDesc/screen, spot-check
a representative sample per screen against retail, then flip the
importer-wide switch with a dedicated visual gate — not a one-line
change folded into an unrelated fix.
Fix direction: (1) enumerate the 1,083 ids per LayoutDesc (a live-DAT
probe test, similar to `SpewBoxLayoutDumpDiagnostic`); (2) for each
distinct screen/LayoutDesc, confirm honoring the flag doesn't hide
something the runtime currently manages visibility of dynamically at that
SAME element id (would double-drive `Visible`); (3) flip the honor in
`LayoutImporter.BuildWidget` (mirroring the chargen-scoped code path
already proven live) and delete `CharacterCreationUiController`'s own
narrow `HideAuthoredInvisibleElements`/AP-230 in the same commit; (4) run
a full-client visual matrix, not just chargen.
## #407 — Windowed resolution offering starves on RDP/virtual displays (video-mode gating)
**Status:** DONE (`e601a496`, 2026-08-16 — same gate round, user-directed immediate fix)

File diff suppressed because one or more lines are too long

View file

@ -15,20 +15,90 @@ ISSUES.md; this doc is the six-page batch.
description text that would confirm is itself broken, GF-2). ALSO: the
open-roll's own rolled heritage shows NO lit dot on entry — every dot
dark in the acdream screenshot.
- **GF-5 Skills page empty.** Nothing renders except the screen-description
textbox — no skill rows, no credits display. (CC5's residual round wired
`TemplateResolver` into the SKILLS page too — yet live rows are absent.)
- **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.** Clicking a
color produces no visible change (model recolor absent). Could be a dead
dispatch or could be working-but-invisible (AP-216 authored-art swatches
+ a recolor that fails); investigate, don't guess.
- **GF-11a Town description text does not change** when switching towns.
- **GF-13 Summary shows "-Non-admin or Non-envoy" below the name** — an
acdream-only text leak; retail's summary list has no such rows.
- **GF-15 Summary name entry DEAD + Finish unpressable.** Cannot type into
the name field at all; Finish cannot be pressed. Also the field is not
prefilled with retail's `[ Name` placeholder. This blocks the entire
create flow — the gate cannot proceed past Summary.
- **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)
@ -72,8 +142,12 @@ ISSUES.md; this doc is the six-page batch.
GF-10 zoom art) — the AP-222 measured mechanism (state media authored
vs applied) across widget kinds.
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.
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