feat(ui): Campaign CT slice CT5 — attribute/skill row geometry + selection media
Aligns the hand-built attribute/skill rows in CharacterStatController with the authored shared row template 0x10000248 (LayoutDesc 0x21000045, InfoRegion::InfoRegion @0x004F1450 template index 0 — the same template gmAttributeUI and gmSkillUI both instantiate): - Row geometry replaced with AUTHORED PIXEL VALUES instead of derived fractions: icon flush left 20x20 (was 16x16 at X=4, vertically centered), name column X=25 W=150 fixed (was RowPadX+IconSize+IconGap offset with a width*0.60 fraction), value column X=175 W=100 right-justified (its right edge sits 7px short of the row's 282px right edge — the authored gutter the owner reported). Row width itself now clamps to the authored 282px template width (RowContentWidth) rather than the ListBox's raw 300px container width. Attribute-row height fixed at 20px (was 22px, no dat basis); SkillRowHeight folded into the same RowHeight constant since both row kinds share H=20. - RowHighlightSprite corrected from 0x06001397 to 0x06000F93 — CT1's ground-truth research sealed the verdict that gmAttributeUI:: UpdateSelection @0x0049DEE0 (SetState(6) -> InfoRegion::SetState @0x004F0EE0) swaps the row's Highlight-state media (0x06000F93), a full-row background swap. 0x06001397 belongs to a different mechanism entirely (the spellbook row's UIElement_UIItem::SetSelectedState overlay child) and SpellbookRowStyle.cs is untouched. - UiClickablePanel.UseSelectionBars/SelectionBarHeight retired outright (UiPanel.cs): they existed only to emulate 0x06001397's dark-bars art; the correct retail rendering is the full-panel sprite stretch the base UiPanel.OnDraw already performs, so the override is dead code once the correct sprite is used. No consumer existed outside CharacterStatController. - Per-attribute/per-vital icon DIDs now resolve through the live DBObj::GetDIDByEnum chain (RetailDataIdResolver.Resolve, AP-235's unification seam) when a resolver is supplied — RetailUiRuntime. MountCharacter wires one under the shared DatLock — falling back to the hardcoded AttrRows/VitalRows column otherwise (tests, no dat). gmAttributeUI::PostInit @0x0049DB70 read verbatim: attributes resolve via category 0x10000002 (statId order 1,2,4,3,5,6, matching AttrRows' authored display order exactly); vitals via category 0x10000003. Live-DAT-verified: every hardcoded fallback value already matched the resolved DID byte-exact (new InstalledDat pin AttributeAndVitalIconDids_MatchTheRetailEnumMapperChain). - RetailAppraisalNameResolver.ResolveHeritage's independent re-implementation of the 2/5/13 heritage overrides deleted; it now delegates straight to CharacterIdentityText.HeritageGroupDisplayName (which already bakes in the same overrides) — one owner, byte-identical behavior. AP-235's register row updated to reflect the single-owner fix (the underlying hardcoded-vs-live-DAT mechanism divergence itself stays open — out of CT5's scope). Hand-built-vs-template ruling: rows stay HAND-BUILT rather than converting to UiTemplateListBox instantiation. The hand-built path hits every authored number byte-exact (proven by the CT1 InstalledDat pin AttributeRowTemplate_IconIsFlushLeftTwentyPixels_NameAndValueAreFixedColumns), while conversion would touch ~15 call sites (raise-button affordability, footer State A/B, per-row tooltip, section bucketing, live-refresh, selection-highlight) for a geometry-only slice — smaller-risk path per the task's own judgment-call guidance. Tests: CharacterStatControllerTests' sprite/UseSelectionBars assertions corrected to the authored geometry; new InstalledDat pin for the icon-DID chain. Full hermetic solution suite green (App/Core/Runtime/Headless/ Launcher/Content/etc., 0 failures) and the full InstalledDat lane green (203 App.Tests pins, TowerAscentReplayTests' known Status=KnownFailure case excluded per the acceptance filter). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
01e44a7018
commit
f532f28c5b
7 changed files with 266 additions and 125 deletions
|
|
@ -747,8 +747,12 @@ public class CharacterStatControllerTests
|
|||
[Fact]
|
||||
public void RowClick_WithSpriteResolve_SelectedRowHasHighlightSprite()
|
||||
{
|
||||
// When spriteResolve is provided, the selected row must use sprite 0x06001397
|
||||
// (retail Button-state-6 dark bar) instead of the translucent gold BackgroundColor.
|
||||
// CT5 SEALED VERDICT (docs/research/2026-08-24-campaign-ct-dat-ground-truth.md
|
||||
// §2): the row template's Highlight-state media is 0x06000F93
|
||||
// (gmAttributeUI::UpdateSelection @0x0049DEE0 SetState(6) ->
|
||||
// InfoRegion::SetState @0x004F0EE0 on template 0x10000248) — NOT
|
||||
// 0x06001397, which belongs to the spellbook row's separate
|
||||
// selected-overlay mechanism (SpellbookRowStyle.cs, untouched here).
|
||||
var list = new UiPanel();
|
||||
var layout = Fake((CharacterStatController.ListBoxId, list));
|
||||
|
||||
|
|
@ -761,7 +765,7 @@ public class CharacterStatControllerTests
|
|||
var rows = list.Children.OfType<UiClickablePanel>().ToList();
|
||||
rows[2].OnClick!();
|
||||
|
||||
Assert.Equal(0x06001397u, rows[2].BackgroundSprite); // selected → sprite
|
||||
Assert.Equal(0x06000F93u, rows[2].BackgroundSprite); // selected → sprite
|
||||
Assert.Equal(0f, rows[2].BackgroundColor.W); // no tint
|
||||
Assert.Equal(0u, rows[0].BackgroundSprite); // others cleared
|
||||
Assert.Equal(0u, rows[1].BackgroundSprite);
|
||||
|
|
@ -1110,7 +1114,6 @@ public class CharacterStatControllerTests
|
|||
{
|
||||
Assert.Equal(Vector4.Zero, row.BackgroundColor);
|
||||
Assert.Equal(0u, row.BackgroundSprite);
|
||||
Assert.True(row.UseSelectionBars);
|
||||
});
|
||||
var rowNames = rows
|
||||
.Select(r => r.Children.OfType<UiText>().ToList()[1].LinesProvider()[0].Text)
|
||||
|
|
@ -1326,11 +1329,15 @@ public class CharacterStatControllerTests
|
|||
Assert.Equal((11_100_000L).ToString("N0"), l1Value.LinesProvider()[0].Text);
|
||||
Assert.Equal("Unassigned Experience:", l2Label.LinesProvider()[0].Text);
|
||||
Assert.Equal((87_757_321_741L).ToString("N0"), l2Value.LinesProvider()[0].Text);
|
||||
Assert.Equal(0x06001397u, rows[1].BackgroundSprite);
|
||||
Assert.True(rows[1].UseSelectionBars);
|
||||
// CT5: RowHighlightSprite corrected to 0x06000F93 (see the sealed
|
||||
// verdict on RowClick_WithSpriteResolve_SelectedRowHasHighlightSprite
|
||||
// above); UseSelectionBars was retired outright (retail's actual
|
||||
// Highlight state is a full-row background swap, which the
|
||||
// inherited UiPanel.OnDraw already renders — no bars-only mode
|
||||
// needed).
|
||||
Assert.Equal(0x06000F93u, rows[1].BackgroundSprite);
|
||||
Assert.Equal(Vector4.Zero, rows[1].BackgroundColor);
|
||||
Assert.Equal(0u, rows[0].BackgroundSprite);
|
||||
Assert.True(rows[0].UseSelectionBars);
|
||||
Assert.Equal(Vector4.Zero, rows[0].BackgroundColor);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue