The remaining code-bearing findings from the round review, F4-F16 minus
the doc-only items (batched separately):
- F4: three client-wide UiButton corpus sweeps (LabelBox path — exactly
the 4 Town buttons, confined to chargen; conflicting custom-selection-
pair + standard Normal/Highlight media — zero found, no gate
tightening needed; per-state label-color map — 209 matches beyond
chargen, confirming AP-222's mechanism has always been broadly active
since it shipped generically in DatWidgetFactory).
- F5/F6: LayoutImporter's Batch C un-consumed-children carve-out now
honors a child's own AuthoredInvisible flag (a narrow honor scoped to
exactly that carve-out, not the general #408 client-wide one) — the
chat transcript's new-text indicator (0x1000048C) was building as a
visible phantom element retail never shows; verified both directions
against the gold-frame pieces, which do not author Invisible.
- F7: BoundedProcessOutputCapture.AppendLine combines the line text and
its trailing newline into one buffer and one file open/write/close
instead of two.
- F9: corrected a stale comment in RuntimeSettingsTargets — #407 split
DisplayModeCatalog's Resolutions/WindowedResolutions in two, so the
fullscreen validator's own narrower list is now DELIBERATELY different
from the Config dropdown's fuller offering, not the "must match" bug
the comment described.
- F10: documented (not changed) why the LabelBox path's default 3px
inset and the face-relative +4px gap in DatWidgetFactory.BuildButton
are deliberately different numbers — neither carries a retail
citation, and moving either to match the other would be an unfounded
guess on a button that currently works correctly.
- F11: Heritage/Profession/Summary/Town description pages now compose
DatRichText.Compose's result ONCE inside their already revision-gated
Refresh, caching the built line list instead of re-wrapping on every
draw call.
- F14: documented (not changed) why PrivateEntityViewportRenderer's
_animatedIds set carrying a reserved-but-never-drawn backdrop id is
harmless — BuildDrawEntities already excludes a null/empty backdrop
from the actual draw list, so the id is never looked up.
- F16: the Summary preview now uses its own render-id pair
(SummaryPreviewRenderId/SummaryPreviewBackdropRenderId, 0xDA11D035/
0xDA11D036) instead of sharing the Appearance page's
(0xDA11D032/0xDA11D034) — confirmed by tracing
FixedEntityTextureOwnerLease through TextureCache to
CompositeTextureArrayCache's shared owner tracker that both pages'
previews share ONE process-wide TextureCache, so sharing render ids
was a real cross-page texture-release collision (either page's own
re-dress or disposal could release the OTHER page's still-active
textures), not a theoretical one.
F3's own register bookkeeping (AP-229 addendum) and F12's register/AD
header-count corrections land in the docs-only commit alongside F15.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Commit 1/3: chargen-scoped, low blast-radius fixes.
- New DatRichText helper: escape-normalize + word-wrap + per-segment
palette color, porting UIElement_Text::SetStringInfoWithFont /
AppendStringInfoWithFont's composition model. Routes the Heritage
(GF-2), Town (GF-11a), and Profession (GF-3) description boxes
through it instead of a raw unwrapped single-Line LinesProvider.
Heritage headers use font-color palette index 1 (green), bodies
index 0 (white), matching AppendStringInfoWithFont's own font-index
argument. Town's diagnosed GF-11a root cause: a single un-wrapped
line meant the town-specific suffix rendered past the clipped
viewport, so switching towns looked like "text never changes" even
though the underlying composed string genuinely differed.
- GF-3: bind the Profession page's description textbox (0x100003e0,
gmCGProfessionPage::InitializePage @0x00483068) and compose its
per-template text (UpdateProfession @0x004821b0's CustomText/
BowText/SwashText/LifeText/WarText/WayText/SoldierText, plain
SetStringInfo — no palette).
- GF-4: UiButton gains a coexisting ValueLabel/ValueBox/ValueFont/
ValueColor slot alongside Label. Retail's chargen display buttons
(avail/health/stamina/mana credits, 0x100003e2-e5/0x100003f9)
author their caption directly on 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. DatWidgetFactory.BuildButton now surfaces
that child (gated on ReferenceEquals(labelInfo, info) — own-caption
buttons only) instead. The six Profession slider name labels
(0x100002ed, CharGenState::GetAttributeName @0x005C3A20's six
hardcoded literals) resolve as UiButton in this port (live-DAT-
measured Type 1 — retail's UIElement_Button is DynamicCast(0xc)-
compatible with UIElement_Text) and are written once at
construction, matching retail's own single InitializePage write.
- GF-6/AP-218: gmCGAppearancePage::Update writes a heritage-flavored
STATIC caption to the Hair/Eyes/Skin spins (plain / GearText_* /
OlthoiText_* variants) — never an index. Removed the prior 1-based-
ordinal/gear-name substitution entirely; the other six spins keep
their DAT-authored caption untouched, matching retail exactly.
- Root 1d: wire the Heritage (0x100003be, 13 states) and Profession
(0x100003d8, 7 states) backdrop SetState cascades
(gmCGHeritagePage::Update / gmCGProfessionPage::UpdateProfession).
- AP-216/AP-217 (partial, register updated honestly): swatches beyond
the current part's real color count now hide (DoColorSpots' blank-
blit half); the GradCircle now blanks for Eyes (DoGradDisk's blank-
plug half). The "paint with the actual represented/current color"
halves stay open — they need a PalSet/Palette-id -> RGB pipeline no
chargen page reads at runtime yet, judged disproportionate to add
alongside this batch's other ~10 fixes.
Register: AP-215 rewritten (item 2's "ordinal" framing is stale after
GF-6; restated as the icon-thumbnail gap), AP-216/AP-217 rewritten
(partially closed), AP-218 retired, AD-103 retired (the swallowed-
child Label substitution AD-103 tracked is replaced by ValueLabel's
own-geometry surfacing).
22 new tests (DatRichText unit tests, UiButton/DatWidgetFactory
ValueLabel tests, live-DAT structural pins, controller behavioral
tests) — all green. Full App suite (Release, live-DAT):
5300 passed / 1 pre-existing unrelated flake (PortalProjectionTests
allocation test, passes in isolation) / 3 skipped, up from the
baseline 5282/3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dual-lens review of CC4 (0e71d3b8) returned architectural FAIL (F1, F6)
and retail-fidelity PASS-with-reservations (F2, F3, F4), plus LOW
findings F5, F7-F12. F13 (TS-82's merge collision with campaign-cc6a) is
merge mechanics for the orchestrator, not addressed here.
F1 (HIGH, blocking): CharacterCreationUiController never released
UiRoot.FixedCanvasSize, on a FALSE premise that CharacterManagementUi-
Controller does a per-tick set (it does not — it sets once on activation
and nulls on Deactivate/Dispose). Root cause: RuntimeCharacterCreation-
State had no CompleteEnter() analogue to RuntimeCharacterSelectionState's,
so the creation view reported IsActive=true for an entire in-world
session. Added CompleteEnter(), wired at both LiveSessionController
in-world edges (StartCore, EnterHighlightedCore); made Open/Close/
Deactivate/Dispose set/null the canvas symmetrically; corrected the false
comment and ledger claim; added FixedCanvasSize test coverage.
F2 (MEDIUM-HIGH, blocking): the attribute-slider scalar mapping was not
retail's. Fixed display to value/100f (UpdateAttributeValues @
0x0048251d) and the drag inverse to truncate+clamp-low-only, no rescale
(ListenToElementMessage @ 0x004829c0, independently re-verified against
the decomp). Added tests at scalar 0.5/0.0 plus a display-direction test.
F3 (MEDIUM, blocking): ported the unported heritage-button tab-restore
arm (ListenToElementMessage @ 0x004e9450) — SHOW/HIDE id sets independently
re-derived from the decomp, including the genuine Lugian (0x100005f1)
no-restore quirk, reproduced faithfully. Wired via a new HeritagePage
click callback; added restore + quirk tests.
F4 (MEDIUM): ported SetTown's (@ 0x0047c360) separate per-town page-root
state literal (Holtburg->0x10000034 etc.), independently re-derived from
the decomp's tail-merged branches; wired via the existing
IUiDatStateful.TrySetRetailState seam; added a test.
F5 (MEDIUM): softened AD-103's unmeasured pixel-equivalence claim.
F6 (MEDIUM, blocking): DECISION — install ChargenOptions in the headless
content path (chosen over marking headless creation out-of-scope).
HeadlessSessionHost now calls InstallOptions off the shared content
lease's Dats, beside the existing InstallSpellMetadata call.
F7: AP-213 already named the label format and click/double-click
substitution explicitly on inspection — no edit needed.
F8: AP-212 now names all six DoRandom primitives with a known landing site.
F9: AD-101 retirement corrected to precede CC5's Finish un-ghosting.
F10: merged ItemAppraisalTextFormatter's duplicate <summary> block.
F11: fixed TS-82's wrong AP-211 cross-reference.
F12: cached the chargen DatStringResolver once per composition instead of
per ResolveText call.
Runtime 1713/0, App 5125/13 skips (+8 new tests), Headless 165/0, full
solution Release build green. Live-DAT probes 7/7 under
ACDREAM_PROBE_LIVE_MOUNT=1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mounts gmCharGenMainUI (enum 0x10000039, root 0x100003CC) via
CharacterCreationUiController/CharacterCreationUiMountCoordinator,
cloning CharacterManagementUiController's recipe. Master shell ports
SetProgressState @0x004e7a10 (Olthoi tab-hide + redirect) and
ListenToElementMessage @0x004e9450 (Back/Next/Finish/Help/Exit/Random
nav) verbatim, with free tab navigation over all six pages. Heritage,
Profession, Skills, and Town pages bind to CC3's
RuntimeCharacterCreationState commands; Appearance and Summary mount as
content-inert placeholders for CC6b/CC5.
Live-DAT probing (CharacterCreationLiveDatTests) found two widget-
mapping surprises the decomp's DynamicCast hints don't predict: the
Profession slider's value field imports as an editable UiField (wired
for direct numeric entry), and the avail/health/stamina/mana/credits
displays author as UIElement_Button hosts whose Type-12 value child is
swallowed by UiButton.ConsumesDatChildren — substituted with the
button's own Label. No new DatWidgetFactory widget types were needed.
Threads the installed DAT's real ChargenOptions into Runtime via the
new RuntimeCharacterCreationState.InstallOptions, called from
ContentEffectsAudioCompositionPhase.Compose (mirrors
InstallSpellMetadata's pattern); headless keeps ChargenOptions.Empty
unchanged. Wires CC3's F14 status-hook gap (ApplyCharacterCreated/
ApplyCreationFailed) to SessionStatusWriter for both graphical and
headless hosts, and adds the CharacterCreation view/command seam
through CurrentGameRuntimeAdapter and DeferredGameRuntimeStateCommands
alongside CharacterSelection's existing shape.
Register: AD-101/102/103, AP-212/213, TS-82 filed for the auto-gender-
select interim default, the omitted ToD-account gate, the button-Label
widget substitution, the Random-button approximation, the flat-listbox
Skills simplification, and the Appearance/Summary placeholders.
Runtime 1713/0 (was 1707), App 5117/13 skips (was 5101/6), Headless
165/0 unaffected, full solution Release build green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>