fix(D.2b): Attributes tab — fill panel height, center row icons, footer at bottom
Root cause: the sub-layout 0x2100002C (design H=337px) mounted into tab slot 0x1000022B (H=575px) via ShouldMountBaseChildren. ElementReader.Merge takes the derived (sub-layout) H=337 as canonical, so the background element's first ApplyAnchor call captured _amB=238 and the list box captured _amB=65 — both stayed at their 337px-parent sizes even though the slot is 575px. Fix: CascadeHeight in LayoutImporter.Resolve mirrors retail's UIElement::UpdateForParentSizeChange. When ShouldMountBaseChildren fires and the slot is taller than the base design, every full-stretch background child has its height cascaded: Top+Bottom anchors → stretch, Bottom-only → pin-to-bottom, Top-only/None → unchanged. This propagates the correct bottom margins through the entire subtree before the first render frame. Layout result (confirmed via headless screenshot): - List box grows from 160px → 398px (9 rows × 44px ≈ 396px) - Footer elements move from abs Y≈307 → abs Y≈545 (matching retail dump) - Separator moves to abs Y≈535 Row constants updated: RowHeight 44px, IconSize 24px, RowPadX 4px, IconGap 6px. Footer State-A text corrected per spec: title="Select an Attribute to Improve", line-1 label="Skill Credits Available:", line-1 value=SkillCredits (96), line-2 label="Unassigned Experience:", line-2 value=UnassignedXp (formatted N0). CharacterSheet.UnassignedXp (long, retail InqInt64(2)) added. SampleData.SampleCharacter().UnassignedXp = 87_757_321_741L. 5 footer tests renamed + assertions updated; SampleCharacter_UnassignedXp_IsSet added. 639 tests green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
902160098a
commit
eccacc59de
6 changed files with 178 additions and 50 deletions
|
|
@ -55,11 +55,14 @@ public static class CharacterStatController
|
|||
private static readonly Vector4 Gold = new(1f, 0.82f, 0.36f, 1f); // section / emphasis gold
|
||||
|
||||
// ── Row layout constants ─────────────────────────────────────────────────
|
||||
// Row height matches retail's UIElement_ListBox template (~20px; spec §1 "each ~20px tall").
|
||||
private const float RowHeight = 20f;
|
||||
private const float IconSize = 16f; // icon element is ~16×16px (spec §Goal)
|
||||
private const float RowPadX = 2f; // left inset before the icon
|
||||
private const float IconGap = 4f; // gap between icon right and name text left
|
||||
// List box 0x1000023D is 398px tall after the anchor pass (dat stores 160px; the
|
||||
// Top+Bottom anchor stretches it when the 575px content slot inflates the sub-layout).
|
||||
// 9 rows spread evenly over 398px ≈ 44px/row — matching retail's generous spacing.
|
||||
// Icons are 24px, vertically centered in the row.
|
||||
private const float RowHeight = 44f;
|
||||
private const float IconSize = 24f; // icon ~24×24px, vertically centered in the 44px row
|
||||
private const float RowPadX = 4f; // left inset before the icon
|
||||
private const float IconGap = 6f; // gap between icon right and name text left
|
||||
|
||||
// ── Attribute row descriptors — retail display order per spec §1 ─────────
|
||||
// InfoRegion::InfoRegion row sub-element ids:
|
||||
|
|
@ -316,32 +319,44 @@ public static class CharacterStatController
|
|||
// ── Footer State A binding ────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Bind footer elements to their State-A (nothing selected) content per
|
||||
/// <c>DisplayDefaultFooter</c> (0x0049cde0). Only the two "value" slots carry
|
||||
/// text; the label slots are left empty (retail sets them to "" for State A).
|
||||
/// Bind footer elements to their State-A (nothing selected) content.
|
||||
///
|
||||
/// <para>Matching the user's authoritative shot layout (3-line footer at the panel bottom):
|
||||
/// <list type="bullet">
|
||||
/// <item>Title (0x1000024e) → "Select an Attribute to Improve" — centered, full-width.</item>
|
||||
/// <item>Line-1 label (0x10000242) → "Skill Credits Available:"</item>
|
||||
/// <item>Line-1 value (0x10000243) → InqInt(0x18) = available skill credits.</item>
|
||||
/// <item>Line-2 label (0x10000244) → "Unassigned Experience:"</item>
|
||||
/// <item>Line-2 value (0x10000245) → InqInt64(2) = unassigned XP.</item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
///
|
||||
/// <para>Source: <c>DisplayDefaultFooter</c> (0x0049cde0) + user's retail screenshot.</para>
|
||||
/// </summary>
|
||||
private static void BindFooterStateA(
|
||||
ImportedLayout layout,
|
||||
UiDatFont? datFont,
|
||||
Func<CharacterSheet> data)
|
||||
{
|
||||
// 0x1000024e title → ""
|
||||
Label(layout, FooterTitleId, datFont, Body, static () => string.Empty);
|
||||
|
||||
// 0x10000242 line-1 label → ""
|
||||
Label(layout, FooterLine1Label, datFont, Body, static () => string.Empty);
|
||||
|
||||
// 0x10000243 line-1 value → "Select an Attribute to Improve"
|
||||
// StringId "ID_StatManagement_Footer_SelectAttribute"
|
||||
Label(layout, FooterLine1Value, datFont, Body,
|
||||
// 0x1000024e title → "Select an Attribute to Improve"
|
||||
Label(layout, FooterTitleId, datFont, Body,
|
||||
static () => "Select an Attribute to Improve");
|
||||
|
||||
// 0x10000244 line-2 label → ""
|
||||
Label(layout, FooterLine2Label, datFont, Body, static () => string.Empty);
|
||||
// 0x10000242 line-1 label → "Skill Credits Available:"
|
||||
Label(layout, FooterLine1Label, datFont, Body,
|
||||
static () => "Skill Credits Available:");
|
||||
|
||||
// 0x10000245 line-2 value → InqInt(0x18) = available skill credits
|
||||
Label(layout, FooterLine2Value, datFont, Body,
|
||||
// 0x10000243 line-1 value → InqInt(0x18) = available skill credits
|
||||
Label(layout, FooterLine1Value, datFont, Body,
|
||||
() => data().SkillCredits.ToString());
|
||||
|
||||
// 0x10000244 line-2 label → "Unassigned Experience:"
|
||||
Label(layout, FooterLine2Label, datFont, Body,
|
||||
static () => "Unassigned Experience:");
|
||||
|
||||
// 0x10000245 line-2 value → InqInt64(2) = unassigned XP
|
||||
Label(layout, FooterLine2Value, datFont, Body,
|
||||
() => data().UnassignedXp.ToString("N0"));
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue