Commit 2/3: CLIENT-WIDE blast radius — un-consume media-bearing dat
children on UiText/UiField.
UiText.ConsumesDatChildren (true unless a state authors PassToChildren)
and UiField.ConsumesDatChildren (true, unconditional) used to drop
EVERY dat child at import time, including ones that carry their own
renderable media — retail's UIElement_Text/Field genuinely composites
those as real chrome/controls (frame pieces, linked scrollbars), not
swallowed caption/face art the way a Button's or Meter's children are.
LayoutImporter.BuildWidget gains a new carve-out (mirroring the
existing UiMeter one): when a UiText/UiField's ConsumesDatChildren is
true, build any child whose OWN StateMedia is non-empty (it carries a
real sprite/track) instead of dropping it outright. Purely structural/
property-only children (StateMedia.Count == 0) stay dropped exactly as
before — this is additive, not a relaxation of the PassToChildren gate.
Independently re-derived blast-radius sweep (walks every installed
LayoutDesc via DatCollection.GetAllIdsOfType<LayoutDesc>, new
LayoutImporterMediaBearingChildSweepTests): 37 distinct (layout,
element) pairs — 41 raw tree positions, since a handful of element ids
recur at multiple subtree positions within the same layout — across 15
layouts. Full list:
0x21000005/0x10000011 (x5 tree positions — chat-adjacent template
reused across the layout), 0x21000005/0x1000059A [MAIN GAME UI],
0x21000006/0x10000011, 0x2100000F/0x1000059A,
0x21000038/{0x100003AB,0x100003BA,0x100003C4,0x100003E0,0x100003EC,
0x100003F6,0x100003FA,0x100003FD,0x100003FF,0x10000402,0x10000404,
0x10000405,0x10000409} [character creation],
0x21000043/0x10000362,
0x21000046/0x100003C4, 0x21000047/{0x100003E0,0x100003EC},
0x21000048/{0x100003F6,0x100003FA,0x100003FD},
0x21000049/{0x100003AB,0x100003BA}, 0x2100004A/0x10000409,
0x2100004B/{0x100003FF,0x10000402,0x10000404,0x10000405},
0x2100004C/{0x100002DD,0x100002E5,0x100002E6},
0x2100005B/0x10000011, 0x21000068/0x1000059A,
0x2100006F/0x10000011 [CHAT INPUT].
(This is an independent re-derivation, not a re-statement of the
investigation's earlier "42/14" estimate — the small difference is
expected from measuring with this commit's own criteria.)
New tests: the sweep itself (pins the two flagged landmarks —
MAIN GAME UI 0x21000005/0x1000059A and CHAT INPUT 0x2100006F/
0x10000011 — plus the three chargen boxes), a build-through regression
test confirming those two landmarks' children resolve as real widgets
post-fix, and a chargen-scoped test confirming the eight gold-frame
pieces + linked scrollbar on all three description boxes now resolve
via UiElement.FindDescendant.
Full App suite (Debug and Release, live-DAT): 5304 passed / 0 failed /
3 skipped — ZERO regressions across the whole client, including every
existing chat and main-UI test. Runtime suite: 1735/0, unaffected
(this is an App-layer-only change).
FLAG FOR THE LEAD: automated coverage cannot catch a purely VISUAL
regression (a frame drawing in the wrong place, a scrollbar overlapping
text). Chat and the main game UI both got new dat children rendered for
the first time this commit — schedule the user's own visual check of
both before considering this closed, per the campaign's oracle
discipline.
The scrollbar linkage (wiring the description boxes' UiScrollbar to
actual text scrolling) is NOT done in this commit — the scrollbar
widget now BUILDS, but CharacterCreationHeritagePage/TownPage/
ProfessionPage/SummaryPage do not yet bind its ScalarChanged to
UiText.Scroll. Filed as follow-up (see report).
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>
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>
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>
The narrow re-review of fix commits 0c8e1e7d+2d4168f9 found every code fix
oracle-verified but returned NOT CLOSED on five test/doc residuals plus
three nits and a follow-up filing. All fixed:
- R1: the claimed "external-change->user-commit->SetName" regression test
for F1 (the deleted _suppressNextFieldEvent latch) never existed — the
Runtime-layer randomize test doesn't touch the page. Added
CharacterCreationUiControllerTests.SummaryNameField_RealCommitAfter
ExternalRefreshWhileUnfocused_StillReachesSetName: drives Refresh with a
revision bump + changed snapshot.Name while the field is unfocused (the
programmatic SetText path that used to arm the latch), THEN performs a
real user commit (field.SetText + field.Submit(), the actual event path),
asserting SetName receives the player's typed text.
- R2: RetailSkillFormula.CalculateChargenScore and ChargenSkillScoreResolver
had zero direct coverage (the F12(d) test substitutes skillId*10). Added
a Untrained/Trained(+5)/Specialized(+10) theory, the divisor-zero skip
path, and a six-way AttributeId theory (Str=1..Self=6) to
RetailSkillFormulaTests.cs.
- R3: RetailSkillFormula.cs's doc comment claimed "no retail-authored skill
sets MinLevel above Untrained=1" without ever reading the field — ACE's
own SkillBase.cs hedges the same field "// 1-2?". MEASURED (not assumed)
against the installed EoR dat's global SkillTable
(CharacterCreationLiveDatTests.SkillTable_MinLevelDistribution_
NeverExceedsTrained): 23 skills at MinLevel 1, 15 at MinLevel 2, zero
above 2, of 38 priced skills. ACE's hedge was right; the doc comment now
states the measured fact and leans on the structural argument (the gate
holds for Trained/Specialized under any MinLevel in {1,2}) as load-
bearing, not the unverified data claim.
- R4: filed AP-228 — the Summary/Skills skill-row KEY sources from
ItemAppraisalTextFormatter.SkillName's hardcoded English switch, where
retail's own key is DAT-sourced (SkillBase->_name via %hs,
0x0047b90f-0x0047b915) — same divergence class as AP-226 filed the same
round, reversed polarity, also present at CC4's Skills page. Softened
AP-224's "ported exactly, not simplified" claim: it only ever covered the
row's VALUE/template, never its KEY.
- R5: this commit corrects 0c8e1e7d's gate claim. "Release build zero
warnings" was false: a clean `dotnet build -c Release -t:Rebuild` shows
25 pre-existing warnings (18 in tests/AcDream.Core.Tests, 7 in
tests/AcDream.App.Tests — Composition/HostInputCameraCompositionTests.cs,
Composition/WorldRenderCompositionTests.cs,
UI/Layout/OptionsPanelLiveMountProbeTests.cs), none in any file this
campaign or its residual round touched. History is not amended; this is
the correction.
Nits: the ChargenPreviewController ctor doc now also cites
gmCGSummaryPage::Update @0x0047baa0 (the per-heritage re-derive site — 0xc
Olthoi/0xd OlthoiAcid/else — not just the one-shot InitializePage seed) as
the stronger justification for why Rebuild re-derives the zoomed-out eye
per heritage on every change. RuntimeCharacterCreationState's F2 comment
("Finish becoming a permanent no-op") reworded: the same unconditional
_verificationPending = false assignment ran pre-fix too, so Finish was
never blocked — only the response FEEDBACK vanished (no dialog, no created
character, nothing), not the request itself. Filed #404 for
ChargenSkillScoreResolver's own independent SkillTable read alongside
ChargenTableReader's (cleanup follow-up, out of this round's scope).
Ledger: CC5 flipped REVIEW-CLOSED in the campaign plan (dual-lens
architectural PASS-with-items / retail-fidelity FAIL -> F1-F14 fix round
0c8e1e7d -> narrow re-review: all code oracle-verified, residuals R1-R5
test/doc -> this commit; re-reviewer pre-authorized lead diff-check close).
This commit's own sha is recorded by a follow-up ledger-only commit,
matching 2d4168f9's own pattern.
Gates: Release build 0 errors (25 pre-existing warnings, unrelated to this
round — see R5 above); App suite 5257/3 skips (was 5242/3), 0 failed;
Runtime suite 1726/0 (unchanged); the three new/measured tests (R1, R2's
ten cases, R3) all pass individually.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fills TS-82's Summary placeholder with a faithful port of gmCGSummaryPage
(name field with NameInputFilter + the retail commit-on-focus-lost/submit
dispatch + the >32-char ID_CharGen_NameTooLong reject-and-revert path, the
REAL three-row-template listbox confirmed against the installed EoR dat
before writing any page code, and Summary's own independent gmCG3DView
preview instance wired through a second ChargenPreviewController pair
mirroring the Appearance page's exact composition shape).
Ports CharGenState::RandomizeCharacter and its six sub-primitives into
RuntimeCharacterCreationState — not approximated: the RandInt/RollDice
semantics are independently confirmed from both the decompiled RNG bodies
and the CharGenStateVtbl union struct in acclient.h. Three consumers:
the chargen screen's open-roll (retiring AP-214's honest-blank deviation
and reproducing the Appearance page's gender-flip-on-init quirk), the
Summary page's Random button (behind the retail randomize-warning
confirm), and the Appearance page's Random button (narrowing AP-212 to
just Heritage/Profession/Town's still-approximated rolls and Skills'
still-unported RandomizeSkills).
Wires the Finish button (previously ghosted) with retail's NoName/
CreditWarning dialog pair, adds the F12 amendment's HeritageOrGenderUnset
local refusal to TryBeginFinish (register AP-223) as a defensive backstop
now that the screen-open roll normally makes it unreachable, and wires
the four ID_Character_Err_* rejection dialogs for the 0xF643 response
codes CC3 already parsed but nothing displayed.
Register: TS-82 retired, AP-214 retired, AP-212 narrowed, AP-223/224/225
filed (heritage/gender Finish refusal, Summary's two-bucket skill-list
narrowing, the 32-vs-33 name-length threshold reconciliation).
Runtime 1722/0 (was 1713), App 5240/3 skips (was 5223/3), Headless 166/0
unchanged, full solution Release build green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
R1: LivePresentationComposition's chargen-preview diagnostic fired on every
ordinary launch without ACDREAM_RETAIL_UI set, since interaction.RetainedUi
is null in that configuration and there's no Appearance page to warn about.
Narrowed the else-if guard to require RetainedUi is not null too, so the
diagnostic only fires in the one configuration it actually diagnoses.
R2: filed AP-221 for the F8 one-shot-binding disposition the re-reviewer
accepted as scoped but which shipped without its own register row — the
chargen preview's GPU-side binding reads the retryable mount coordinator's
widget exactly once, so a slow-DAT frame permanently kills the preview for
the session with only R1's diagnostic as evidence.
R3: rewrote AP-217 after re-deriving from the decomp. The original row
claimed the GradCircle was an interactive click-to-hue picker with no
handler wired up. gmCGAppearancePage::ListenToElementMessage's dispatch
switch has no case for the GradCircle's offset at all — it isn't a click
target in retail either. DoGradDisk is a paint-only routine that blits the
gradient art tinted with the current color (or blanks it for Eyes)
whenever SetColor/SetSelection run. acdream's real gap is that it never
repaints the GradCircle — a cosmetic paint gap, not a dead control.
N1: tightened AP-220's "leaving Gearknight for something else" — the
decomp shows leaving Gearknight for Olthoi/OlthoiAcid takes a separate
branch that does not randomize; only leaving for a non-Olthoi heritage
does.
N2: added the requested media pin to the F2 spin-highlight live-DAT test,
then measured it against the installed EoR dat rather than assuming it
would pass. It doesn't: none of the nine spins author Highlight-state
media on either consumed arrow face segment, so
TrySetRetailState(Highlight) is a silent no-op for all of them today.
Pinned the test to the measured reality (ActiveState stays "Normal") and
filed AP-222 documenting the discovery — unresolved whether retail's own
spin art has the same gap.
Plan doc: CC6b-MOUNT ledger row updated to REVIEW-CLOSED with the full
commit chain and re-review disposition; OWED list corrected for AP-217/
AP-222.
Gates: dotnet build -c Release green. App suite 5223 passed / 3 skipped
(ACDREAM_PROBE_LIVE_MOUNT=1, ACDREAM_DAT_DIR set) — count held exactly at
baseline. Runtime suite 1713/0 — unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes every finding from the dual-lens review of 34c6fceab0 (architectural
PASS-with-items, retail-fidelity FAIL). Re-derived every decomp citation
against docs/research/named-retail/acclient_2013_pseudo_c.txt directly
rather than trusting the reviewer's transcription.
Wrap/normalize semantics (F1): CycleIndex's decrement-from-Unset landed on
0; the decomp's shared decrement tail (label_47f065/label_47f6d9, the same
switch the headgear ring was ported from) computes new=cur-1=-2 on the raw
signed int32, which wraps to count-1 — matching headgear's own ring shape.
Also ports the spin body-click normalize-and-write-back retail's cases
0xa5-0xae all share (NormalizeChoiceOnSelect), which acdream had dropped
entirely. Flips the one test that pinned the wrong expectation and adds
select-zone coverage no prior test isolated.
Heritage gate (F3): Update's Gearknight/Olthoi/OlthoiAcid branches reset
SetChoice(FACE)/SetSelection(HAIR) unconditionally, not only when Clothes
was showing — a conditional gate stranded Nose/Mouth as the current part
under a Face-tab session.
Doc corrections propagated everywhere they repeated (F4, F5, F6, plus the
plan doc's own CC6b-MOUNT ledger row for F1/F3): the gmBarberUI heading
citation conflated PostInit with InitializePage; Random's Appearance
disable was mislabeled a placeholder when it's really AP-212's unported
RandomizeAppearance/RandomizeClothing gap; the master-page doc still called
the Appearance page content-inert after this campaign made it real.
Visual substitutions widened (F2): AP-215 named only two of the Appearance
page's swatch/spin substitutions. Ports the two cheap ones directly —
current-part highlight via SetSelection's SetState(1)/SetState(6), routed
through the existing UiButtonStateMachine.Normal/Highlight ids and
IUiDatStateful.TrySetRetailState seam (installed-DAT-confirmed
ToggleBehavior=true on all nine spins); the shade scrollbar's SetVisible(0)
for Eyes vs acdream's Enabled=false. Files the other five (DoColorSpots,
the inert GradCircle, spin-caption/heritage-caption loss, the Skin-spin
MoveTo reposition, the Gearknight-boundary randomize calls) as new register
rows AP-216..AP-220 and corrects the plan doc's false claim that AP-215
already named the GradCircle.
Unlocked DAT read (F7, BLOCKER): ChargenPreviewController.Rebuild called
ChargenAppearanceFactory.TryCompose outside _datLock while the very next
line correctly locked TryBuildAnimated — CC6a's own F4 class of bug,
reintroduced at this catalog's first production call site. Wrapped in the
same lock; documented the invariant on ChargenAppearanceCatalog itself.
One-shot preview mount (F8): LivePresentationComposition reads
ChargenPreviewViewportWidget once, but its underlying mount
(CharacterCreationUiMountCoordinator) is explicitly retryable while this
GPU-resource composition pass is not — unlike PaperdollViewportWidget,
which IS eager/non-retryable, so the "mirrors Paperdoll" doc claim was
false. Retrofitting cross-frame retry here would mean restructuring this
composition's one-shot contract for every private viewport (paperdoll,
creature appraisal) and FrameRootComposition's fixed frame-group array —
out of this round's blast radius. Corrected the doc and made the failure
loud (a diagnostic log) instead of silent.
Dispose leak (F9): ChargenPreviewController.Dispose left the preview
WorldEntity referenced by the leased renderer until the renderer's own,
later disposal. Releases it on its own teardown now.
Test-quality items (F10, F11, F13): pinned the spin arrow widths
(47px, both arrows) the 174 zone boundary is derived from, plus a
controller test for the previously-uncovered select zone. Measured the
shade scrollbar's authored orientation instead of assuming it — it is
VERTICAL (33x85) — which is a real production bug: UiScrollbar only routed
scalar-mode mouse events when Horizontal was true, so the shade control
never fired in production. Added OnVerticalScalarEvent/DrawVerticalScalar
mirroring the existing horizontal scalar path. Converted
ChargenPreviewControllerTests from silent-pass [Fact] to the shared
InstalledDatFactAttribute skip-reporting pattern.
Adjudication (F12): AD-101's retirement leaves TryBeginFinish's four local
refusals (NoName/AttributeCreditsUnspent/AlreadyPending/RosterFull) with no
heritage/gender gate — currently latent since Finish stays hard-disabled
this round. Amended the campaign plan's CC5 slice scope to require BOTH a
heritage/gender refusal AND a real RandomizeCharacter port before the
connected user gate opens Finish; noted the interaction on AP-214's own
register row. No CC5 implementation in this commit.
Gates: dotnet build -c Release green across the full solution. App suite
(Release, ACDREAM_PROBE_LIVE_MOUNT=1) 5223/3 skips, Runtime suite
1713/0 — both clean across repeated runs. A full-solution run surfaced
three pre-existing, previously-documented flakes unrelated to this change
(Streaming.LandblockBuildFactoryTests/LandblockPresentationPipelineTests
#402, Core.Net.Tests.NakEmissionTests loss soak) — each confirmed passing
in isolation, consistent with their known full-suite-parallelism-timing
history; none touch any file this commit changes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The page-mount half CC6b-PRE deferred: CharacterCreationAppearancePage
(gender buttons, Face/Clothes sub-tabs, nine spin controls with retail's
decrement/increment/select-as-current-part OnClickAt zones, nine color
swatches, shade scrollbar, zoom/rotate wiring) plus ChargenPreviewController,
which bridges the ChargenPreviewRenderer/ChargenPreviewZoomController
camera-injection gap CC6a/CC6b-PRE left open and mounts as the third private
creature viewport beside paperdoll/creature-appraisal.
Color-wheel scouting (campaign risk item 4): live-DAT probe found every
color-wheel-family id resolves through existing DatWidgetFactory mappings
(Button/Scrollbar/generic fallback) — no new widget type needed.
The @140355 gender-flip-on-init oddity (risk item 5): resolved via decomp
alone — gmCharGenMainUI's own ctor calls CharGenState::RandomizeCharacter
before any page constructs, so retail's chargen screen is never actually
blank on open; the Appearance page's gender-flip code always fires against
a real, randomly-rolled gender. Filed AP-214 (acdream doesn't port
RandomizeCharacter this round, so it opens honestly blank instead) and
AP-215 (two narrow visual substitutions: swatch .Selected highlight vs
retail's separate overlay, ordinal labels vs retail's icon-only spins).
AD-101 retired: the Heritage page's auto-gender-select interim default is
deleted now that the Appearance page's real gender buttons exist. TS-82
narrowed to Summary-only.
Scope addendum: ChargenPreviewRotationController's parameterless-constructor
default changes from 0f to a new RetailDefaultHeadingDegrees=180f constant
(retail's InitializePage override, not the ctor's raw 0) — every real
gmCG3DView owner converges on 180 before its first frame, so a controller
defaulting to 0 was a trap for future consumers.
Runtime 1713/0, Core 4786/1 skip, Content 147/0, App 5220/3 skips (Release,
ACDREAM_PROBE_LIVE_MOUNT=1) — zero failures across two clean full-solution
runs; the one Core.Net.Tests NakEmissionTests flake observed on a third run
is the same pre-existing, previously-documented timing flake (zero files
under src/AcDream.Core.Net/ touched, passes 100% in isolation).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The final CC4 re-review closed R1-R4 (the fixed-canvas arbiter, headless
create-gate proof, #402 filing) and left one residual: the arbiter's
mismatch-throw makes 'both char-select screens author 800x600' a crash
premise on the exact user-gate path (ACDREAM_OPEN_CHARGEN=1 ->
char-management declares -> chargen declares on top), and only
char-management's extent was DAT-pinned. The chargen live-DAT probe now
pins the root at 800x600 the same way — measured against the installed
DAT (passes 7/7), not inferred. Ledger row flipped to REVIEW-CLOSED with
real shas.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
EOF
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>