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
|
|
@ -26,8 +26,10 @@ public class UiPanel : UiElement
|
|||
|
||||
/// <summary>Optional dat RenderSurface id for the panel background sprite, drawn
|
||||
/// in place of (or alongside) <see cref="BackgroundColor"/>. 0 = none.
|
||||
/// When set, the sprite is stretched to fill the panel rect.
|
||||
/// Used by the attribute-list selected-row highlight (sprite 0x06001397 = Button state 6).</summary>
|
||||
/// When set, the sprite is stretched to fill the panel rect — the same
|
||||
/// full-swap semantics retail's <c>InfoRegion::SetState</c> uses. Used by
|
||||
/// the character-panel attribute/skill row's selected-row highlight
|
||||
/// (sprite 0x06000F93, template 0x10000248's Highlight-state media).</summary>
|
||||
public uint BackgroundSprite { get; set; }
|
||||
|
||||
/// <summary>Resolves a dat RenderSurface id to (GL tex handle, pixel width, pixel height).
|
||||
|
|
@ -191,29 +193,22 @@ public class UiSimpleButton : UiPanel
|
|||
/// parent window. In acdream we wire the equivalent via this action callback instead of
|
||||
/// the retail message bus.</para>
|
||||
///
|
||||
/// <para>When <see cref="UseSelectionBars"/> is true and <see cref="UiPanel.BackgroundSprite"/>
|
||||
/// is non-zero, draws the sprite as a thin full-width bar at the TOP and BOTTOM edges of
|
||||
/// the row (not stretched to fill). This matches retail's selection highlight which shows
|
||||
/// a horizontal dark bar on both the top and bottom edge of the selected attribute row,
|
||||
/// with NO left/right end-caps. Bar height is <see cref="SelectionBarHeight"/> pixels
|
||||
/// (default 3px).</para>
|
||||
/// <para>Campaign CT slice CT5 (2026-08-25): the selected-row highlight draws through the
|
||||
/// inherited <see cref="UiPanel.OnDraw"/> full-panel <see cref="UiPanel.BackgroundSprite"/>
|
||||
/// stretch — no override here. This class previously had its own "selection bars" draw
|
||||
/// mode (a thin top/bottom-bar rendering tuned to look like sprite 0x06001397's dark
|
||||
/// bars); CT1's ground-truth research found that sprite belongs to a DIFFERENT retail
|
||||
/// mechanism entirely (the spellbook row's overlay child), and the row's actual retail
|
||||
/// Highlight state (<c>InfoRegion::SetState</c>, media 0x06000F93) is a plain full-row
|
||||
/// background SWAP — exactly what the inherited <see cref="UiPanel.OnDraw"/> already
|
||||
/// draws. The bars mode was therefore retired rather than reconfigured to a wrong sprite's
|
||||
/// geometry; no consumer outside <c>CharacterStatController</c> ever set it.</para>
|
||||
/// </summary>
|
||||
public class UiClickablePanel : UiPanel
|
||||
{
|
||||
/// <summary>Called when the user releases the left mouse button over this panel.</summary>
|
||||
public Action? OnClick { get; set; }
|
||||
|
||||
/// <summary>When true and <see cref="UiPanel.BackgroundSprite"/> is non-zero, draws
|
||||
/// the sprite as a thin horizontal bar at the top AND bottom edges of the panel,
|
||||
/// NOT as a full-height stretched fill. Matches retail's selected-row highlight
|
||||
/// (sprite 0x06001397 — 300×32 px — shown as bars, not a block fill).
|
||||
/// Default false (preserves legacy full-stretch behavior).</summary>
|
||||
public bool UseSelectionBars { get; set; }
|
||||
|
||||
/// <summary>Height in pixels of each selection bar (top and bottom). Default 3px.
|
||||
/// Ignored when <see cref="UseSelectionBars"/> is false.</summary>
|
||||
public float SelectionBarHeight { get; set; } = 3f;
|
||||
|
||||
/// <summary>Settable tooltip, surfaced through the shared
|
||||
/// <see cref="UiElement.GetTooltipText"/> hover pipeline (same pattern as
|
||||
/// <see cref="UiButton.TooltipText"/> / <see cref="UiCatalogSlot"/>). TS-85's
|
||||
|
|
@ -250,33 +245,4 @@ public class UiClickablePanel : UiPanel
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnDraw(UiRenderContext ctx)
|
||||
{
|
||||
if (UseSelectionBars && BackgroundSprite != 0 && SpriteResolve is { } sr)
|
||||
{
|
||||
// Draw the selection highlight as a thin bar at the TOP and BOTTOM of the row.
|
||||
// The sprite (0x06001397) is 300×32 px — we draw it as horizontal strips at
|
||||
// native height (SelectionBarHeight), stretched to full panel width (UV tile
|
||||
// horizontally). No left/right end-caps: u0=0, u1=Width/nativeW (UV repeat).
|
||||
var (tex, tw, th) = sr(BackgroundSprite);
|
||||
if (tex != 0 && tw > 0 && th > 0)
|
||||
{
|
||||
float barH = SelectionBarHeight;
|
||||
float uTile = tw > 0 ? Width / tw : 1f;
|
||||
// Top bar: shows the top barH px of the sprite (v = 0 → barH/th).
|
||||
float vBot = th > 0 ? barH / th : 1f;
|
||||
ctx.DrawSprite(tex, 0f, 0f, Width, barH, 0f, 0f, uTile, vBot, Vector4.One);
|
||||
// Bottom bar: shows the bottom barH px of the sprite (v = 1−barH/th → 1).
|
||||
float vTop2 = th > 0 ? 1f - barH / th : 0f;
|
||||
ctx.DrawSprite(tex, 0f, Height - barH, Width, barH, 0f, vTop2, uTile, 1f, Vector4.One);
|
||||
}
|
||||
// Selection-bar mode draws no border (rows have BorderColor=Zero by design).
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default UiPanel draw: handles BackgroundSprite, BackgroundColor, AND border.
|
||||
base.OnDraw(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue