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

View file

@ -1673,6 +1673,36 @@ public sealed class CharacterCreationLiveDatTests
Assert.Equal(116f, valueChild.X);
}
/// <summary>
/// R4-1 (re-test 3): the raw authored value-child X (116, pinned above)
/// is NOT where the value actually draws — <c>UiLayoutPolicy</c>'s
/// raw-edge reflow (the value child's own Right-tracking edge modes
/// against its base-inherited 150px <c>OriginalParentWidth</c> vs the
/// Skills-credits button's actual 231px width) shifts it to X=197,
/// landing right after the caption's own measured 193px span instead
/// of colliding mid-caption ("Available Skill0Credits"). Health's own
/// value child shares the SAME 150px OriginalParentWidth as its OWN
/// actual 150px-wide button (no divergence), so it reflows to its
/// byte-identical raw rect — proving the fix is additive, not a
/// blanket shift.
/// </summary>
[InstalledDatFact]
public void SkillsCreditsButton_ValueBoxReflowsPastCaption_HealthValueBoxUnchanged()
{
using var dats = new DatCollection(DatDirectory, DatAccessType.Read);
uint layoutId = RetailDataIdResolver.Resolve(
dats, CharacterCreationUiController.RootEnum, 5u);
ImportedLayout screen = BuildSelected(
dats, layoutId, CharacterCreationUiController.RootElementId);
UiButton skillsCredits = Assert.IsType<UiButton>(screen.FindElement(0x100003F9u));
Assert.Equal((197f, 0f, 34f, 28f), skillsCredits.ValueBox);
Assert.Equal(UiButton.LabelAlignment.Right, skillsCredits.ValueAlign);
UiButton health = Assert.IsType<UiButton>(screen.FindElement(0x100003E3u));
Assert.Equal((116f, 0f, 34f, 28f), health.ValueBox);
}
/// <summary>
/// R3-3 (re-test 2): the info-box title (<c>0x100003fb</c>) and
/// description (<c>0x100003fc</c>) panes' own AUTHORED boxes overlap —
@ -1714,6 +1744,41 @@ public sealed class CharacterCreationLiveDatTests
Assert.True(description.Y > title.Y);
}
/// <summary>
/// R4-3 (re-test 3): the description pane's own raw box (Y=460,
/// H=100 -&gt; bottom Y=560) extends PAST the bottom of the gold
/// decorative frame that visually contains both info panes
/// (<c>0x100003fa</c>, Y=430 H=110 -&gt; bottom Y=540 — the SAME
/// corner/edge sprite family GF-12 already renders,
/// <c>0x100002de-e3</c>/<c>0x100000e8</c>/<c>0xea</c>). Pins the
/// geometric mismatch itself (so a future DAT re-extract that removes
/// it is visible) — <c>CharacterCreationSkillsPageTests</c>' own fixture
/// covers the constructor's Height-clamp behavior against this exact
/// shape.
/// </summary>
[InstalledDatFact]
public void SkillsInfoBoxFrame_ShorterThanDescriptionPane()
{
using var dats = new DatCollection(DatDirectory, DatAccessType.Read);
uint layoutId = RetailDataIdResolver.Resolve(
dats, CharacterCreationUiController.RootEnum, 5u);
ElementInfo rootInfo = Assert.IsType<ElementInfo>(
LayoutImporter.ImportInfos(
dats, layoutId, CharacterCreationUiController.RootElementId));
ElementInfo frame = Assert.IsType<ElementInfo>(FindInfo(rootInfo, 0x100003FAu));
ElementInfo description = Assert.IsType<ElementInfo>(FindInfo(rootInfo, 0x100003FCu));
float frameBottom = frame.Y + frame.Height;
float paneBottom = description.Y + description.Height;
Assert.True(
frameBottom < paneBottom,
$"expected the frame's own bottom (Y={frame.Y} H={frame.Height}, bottom={frameBottom}) to sit "
+ $"ABOVE the description pane's own raw bottom (Y={description.Y} H={description.Height}, "
+ $"bottom={paneBottom}) — if it no longer does, CharacterCreationSkillsPage's Height clamp "
+ "may no longer be needed");
}
/// <summary>
/// R3-4/R3-7 (re-test 2): retail authors TWO distinct
/// <c>UIElement_Scrollbar</c> thumb shapes. Chat's own scrollbar