fix(chargen): Campaign CC gate round 1 re-test 3 — R4-1..R4-4

Four visual residuals from the lead's own live-client captures of
1.0.2-cc.m, all root-caused via decomp + live-DAT evidence:

- R4-1: Skills credits value overlapped mid-caption again. Root cause
  was a missing UiLayoutPolicy raw-edge reflow on UiButton's value-child
  rect (the child is base-inherited across four sibling buttons of
  differing widths, so its baked-in OriginalParentWidth diverges from
  the actual 231px-wide Skills credits button) plus an HJustify.Right
  value child mapped to Center instead of a real far-edge Right.
- R4-2: the single-sprite scrollbar thumb tiled (GL_REPEAT) instead of
  drawing once — DrawTiled was reused for a small fixed marker graphic
  whose native size is far smaller than the track-proportional thumb
  rect. New DrawThumbMarker draws exactly one native-size instance.
- R4-3: the skills info-box formula line clipped past the surrounding
  gold frame's own authored bottom edge (the pane's own raw box is 20px
  taller than the frame that visually contains it) — clamp the pane's
  Height to the frame's bottom (register AD-105, since retail's
  ShowSkillsText has no code relationship to the frame to cite).
- R4-4: the Appearance help text started mid-sentence — the box was
  never touched by its page controller, so it kept UiText's chat-style
  PreserveEndOnLayout=true default; the scroll model's wasAtEnd check is
  vacuously true on its first-ever overflow transition, pinning the
  first render to the bottom. Set PreserveEndOnLayout=false (a static
  top-oriented report, not a transcript) and wired the box's own nested
  authored scrollbar, never wired before.

App suite live-DAT env 5372/3 -> 5379/3 (+7, zero regressions). Runtime
1735/0 unchanged. Full solution 14585/4 skips/1 failure (the documented
Core.Net NakEmission full-solution-only flake, confirmed standalone-pass).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-16 19:05:23 +02:00
parent 28704db4bf
commit e6acb800cc
11 changed files with 678 additions and 12 deletions

File diff suppressed because one or more lines are too long

View file

@ -1028,3 +1028,127 @@ render retail-shaped; four residuals visible in the captures:
next to the article of clothing…") — the opening paragraph is either
scrolled off (box has no visible scrollbar) or missing from the
composition; check what retail authors/composes for that box.
**RE-TEST 3 fix batch (2026-08-16, R4-1..R4-4) is CODE-COMPLETE, pending the
user's visual gate.** All four findings root-caused and fixed via decomp +
live-DAT evidence, no invented pixel offsets. App suite live-DAT env
5372/3 → 5379/3 (+7, zero regressions); Runtime 1735/0 unchanged; full
solution 14585 tests / 4 skips / 1 failure (Core.Net
`NakEmissionTests.LossSoak_TwoPercentBidirectional_ZeroMessageLoss_LedgersConverge`,
the documented full-solution-only flake — confirmed passing standalone,
unrelated to this batch's files). No client launches.
- **R4-1 FIXED — root cause was a MISSING raw-edge reflow, not a wrap/clip
gap.** Live-DAT probe: the Skills credits value child (`0x100002f3`) is
BASE-INHERITED across four sibling buttons of DIFFERING widths —
Health/Stamina/Mana at 150px share the exact same child id/rect (local
X=116) as the wider, 231px Skills credits button — and the child's own
`OriginalParentWidth` (150, baked in wherever it was first resolved,
matching Health's own actual width) diverges from Skills credits' real
231px parent. `UiButton.ValueBox` was built from the child's RAW
(un-reflowed) rect, never running it through `UiLayoutPolicy` — the SAME
retail raw-edge system (`UIElement::UpdateForParentSizeChange
@0x00462640`) already used for every LIVE mounted `UiElement` via
`UiElement.ApplyAnchor`. The child's own edge modes (Left=2/Right=1,
live-DAT-confirmed "track the far edge as the parent grows") shift the
value box from X=116 to X=197 for Skills credits specifically — landing
immediately after the caption's own measured 193px span (ends ≈x=196)
instead of colliding mid-caption. Separately, `ValueAlign` mapped
`HJustify.Right` (raw dat 3/5, live-DAT-confirmed authored on ALL four
value children) to Center — `UIElement_Text::CalcJustification
@0x00467260`'s own `ecx_5==3||5` branch is a DISTINCT far-edge formula,
not Center's halved offset; added a `LabelAlignment.Right` case.
Health/Stamina/Mana (whose `OriginalParentWidth` already matches their
own actual width) reflow to their byte-identical raw rect — the fix is
additive, not a per-button special case.
Files: `src/AcDream.App/UI/Layout/DatWidgetFactory.cs` (`BuildButton`'s
value-child block, new `ReflowValueChildRect`), `src/AcDream.App/UI/UiButton.cs`
(`LabelAlignment.Right`, `OnDraw`'s value-draw `vx` switch). Tests:
`DatWidgetFactoryTests.BuildButton_ValueChildBaseInheritedNarrowerParent_ReflowsToWiderButton`
(+ its `..._OriginalParentMatchesActual_RectUnchanged` negative
companion); live-DAT
`CharacterCreationLiveDatTests.SkillsCreditsButton_ValueBoxReflowsPastCaption_HealthValueBoxUnchanged`
(pins the real installed DAT's `197,0,34,28` vs `116,0,34,28`).
- **R4-2 FIXED — the single-sprite-thumb fallback was TILING (UV-repeat)
a small marker graphic instead of drawing it once.** The re-test-2 fix
(R3-4/R3-7) correctly identified the thumb sprite but fed it to
`DrawTiled` (GL_REPEAT UV wrap) — for a small fixed "diamond" marker
drawn into a track-proportional thumb rect far taller than its own
native size (`UIElement_Scrollbar::UpdateLayout @0x4710d0`'s
`max(MinThumb, trackLen*ThumbRatio)` formula, unchanged/still correct
for the rect's SIZE), the texture sampler repeated the marker several
times down the track (~9 on Summary's overview bar, ~2 on Skills,
matching the live capture). New `DrawThumbMarker` draws exactly ONE
instance at the sprite's own native size, centered within the SAME
computed rect — neither tiled (the bug) nor stretched into an elongated
bar (a naive `DrawSprite` fix would have distorted the diamond shape).
The shade slider's own scalar-mode draw path (`DrawVerticalScalar`) was
never touched — it already used the correct native-size `DrawSprite`
pattern this fix now mirrors for model-mode bars.
File: `src/AcDream.App/UI/UiScrollbar.cs` (`DrawVerticalModel`/
`DrawHorizontalModel`'s fallback branch, new `DrawThumbMarker`). Test:
`UiScrollbarTests.SingleSpriteThumb_DrawsOneUntiledInstance_NotRepeatedDownTrack`
— reads back the actual emitted quad's UV V-coordinate via
`TextRenderer.DebugSpriteSegmentVerts` and asserts it never exceeds 1.0
(native); confirmed this test FAILS (V=7.875) against the pre-fix
`DrawTiled` call by temporarily reverting and re-running.
- **R4-3 FIXED — the description pane's own authored box is genuinely
taller than the decorative frame that visually contains it.** Live-DAT
geometry walk: the gold frame (`0x100003fa`, the SAME GF-12 corner/edge
sprite family as the Appearance help box) spans Y=430 H=110 (bottom
Y=540), but the description pane (`0x100003fc`) spans Y=460 H=100
(bottom Y=560) — 20px PAST the frame's own bottom border. Composition-
height simulation against every one of the 38 skills carrying detail
data (real `ChargenTableReader` descriptions + the worst-case
description+bonus+formula line count) confirmed the pane's OWN raw
100px interior comfortably fits every case (worst: 5 lines / 80px < 90px
interior) — ruling out a wrap-width or line-spacing bug. The real
mismatch is the SIBLING frame's smaller authored bottom, which the pane
was never clamped to, letting a tall composition's last line(s) draw
past the frame's own visible border into blank page space. Retail's
`ShowSkillsText @0x00481250` has no code linking the panes to the frame
(plain `SetText`, no size/clip handoff) — the frame's own authored Y+H
is the only available ground truth, not a decomp-confirmed clip
mechanism, so this is filed as register **AD-105** (a genuine
inference, flagged rather than silently assumed, same shape as R3-3's
own AD-104 scoped correction). `CharacterCreationSkillsPage`'s
constructor now clamps `_infoText.Height` to the frame's bottom edge
whenever it would otherwise be taller (additive; never grows it).
File: `src/AcDream.App/UI/Layout/CharacterCreationSkillsPage.cs`
(constructor, new `InfoBoxFrameElementId` clamp block). Tests: fixture
`CharacterCreationUiControllerTests.SkillsPage_InfoBoxDescriptionPane_HeightClampedToFrameBottom`
(the shared `BuildSkillsPage` fixture gained a deliberately-shorter
frame element); live-DAT
`CharacterCreationLiveDatTests.SkillsInfoBoxFrame_ShorterThanDescriptionPane`
(pins the 20px real-DAT mismatch itself).
- **R4-4 FIXED — two stacked gaps, the same "page never touched this
element" shape as prior holdouts.** The help box (`0x100003ab`) is a
purely DAT-authored static paragraph (no `gmCGAppearancePage` runtime
composition function exists for it, unlike Town/Summary's
`SetTownString`/`SetHowToText` — confirmed absent from the named
decomp) that `CharacterCreationAppearancePage` never referenced at all,
so it kept `UiText`'s own chat-style default
(`PreserveEndOnLayout=true`, "keep a view that is already at the end
pinned there"). Its content overflows a 292px-tall frame, and
`UiScrollable.SetExtents`'s own `wasAtEnd` check is vacuously true the
FIRST time a Scroll model transitions from its zero-initialized state
(`ContentHeight=0/ViewHeight=0/ScrollY=0``MaxScroll=0`
`AtEnd=(0>=0)=true`) to real overflowing content — with
`PreserveEndOnLayout` still true, that spuriously pins the very first
render to the BOTTOM, hiding the opening paragraphs exactly as reported
(the visible text is mid-way through the third paragraph). This is a
static instructions box, not a chat transcript — `PreserveEndOnLayout`'s
own doc already carves out exactly this shape ("top-oriented reports
such as Character Information disable it"). Also wired the box's own
NESTED authored scrollbar (property `0x72`, live-DAT-confirmed a direct
Type-11 child of the text box — the SAME nesting shape
`CharacterCreationSummaryPage.HowToScrollRelativeId` already uses) —
never wired by this page before, so a user can reach the rest of the
text even where the box's own height still doesn't fit everything.
File: `src/AcDream.App/UI/Layout/CharacterCreationAppearancePage.cs`
(constructor, new `HelpTextId`/`HelpScrollRelativeId` block). Tests:
fixture
`CharacterCreationUiControllerTests.AppearancePage_HelpText_TopOriented_AndOwnScrollbarIsWired`
(the shared `BuildAppearancePage` fixture gained the help box + its
nested scrollbar child, StateMedia-bearing so `UiText`'s own dat-
children carve-out actually builds it).