fix(ui): round-5 review polish — S1 block outline pass, S2 non-UiText outline paths, S3 citation fix
Collects the post-gate polish left uncommitted by the killed round-5 agent (S1/S3 + review fixes N1/N3/N4) and completes the missing S2 half: - S1: UiText multi-line transcript + colored-run label now submit EVERY line/run's outline pass before ANY fill pass, matching retail's UIElement_Text::DrawSelf @0x00467aa0 whole-block walk. DrawStringDatPass is exposed for block-level batching; single lines keep DrawStringDat. - S2 (completed this commit): authored outline 0x21/0x22 now reaches every text-bearing widget — UiButton, UiDatElement, UiField, UiMeter, UiMenu, UiCatalogSlot — seeded from the element's effective-default state exactly like UiText (BuildButton lifts the label-bearing Text child's authored value first, same chain as the label color). Per-STATE outline switching (dialog/character/combat buttons author 0x21 in state 0x3 only) is NOT ported — filed as register row AP-192 in this commit. - S3: ChatWindowController reconciliation comment corrects the misread indicator action ids 0x10000514-17 -> 0x10000114-17 and re-attributes the id-coincidence to the pagination widget's m_prevButton/m_nextButton, not gmFriendsUI; register + window-shell research doc corrected to match. - N1: LayoutImporter's duplicate per-state any-state-first-wins 0x21 read is deleted — ElementReader.ApplyCanonicalLegacyProjection's DirectState-then- effective-default resolution is the single source (the duplicate would have lit state-0x3-only outlines permanently once S2 widened consumption). - N3: the outline pass tints with the outline color's OWN alpha, not the fill's (retail tints m_curOutlineColor and m_curTextColor independently). - N4: the outline-inflated glyph SOURCE rect is clamped to the atlas bounds with matching dest shrink, porting CreateCharRectPair @0x00441480's edge behavior — edge glyphs crop instead of sampling a neighbour's texels. Full Release suite: 12,610 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ed0dbff90a
commit
aa6635aebf
14 changed files with 258 additions and 66 deletions
|
|
@ -557,8 +557,9 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
|
|||
/// buttons genuinely author property <c>0x12</c> as an Enum (not merely
|
||||
/// a stray/mistyped property): <c>chat_2100006f.json</c>'s four
|
||||
/// indicator elements each carry it, with values
|
||||
/// <c>0x10000514</c>-<c>0x10000517</c> in id order. But the OTHER half
|
||||
/// of the wiring is where retail's own data falls short: the floating
|
||||
/// <c>0x10000114</c>-<c>0x10000117</c> in id order (an earlier pass here
|
||||
/// misread these as <c>0x10000514</c>-<c>0x10000517</c>). But the OTHER
|
||||
/// half of the wiring is where retail's own data falls short: the floating
|
||||
/// chat window fixture (<c>chat_floaty_2100005b.json</c>) authors NO
|
||||
/// Enum-kind property <c>0x24</c> anywhere (its one hit on property
|
||||
/// number 36 is Integer-kind, an unrelated attribute) and NO property
|
||||
|
|
@ -567,14 +568,19 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
|
|||
/// ids, and <c>RegisterElementForInputAction</c> has exactly one call
|
||||
/// site in the whole binary (the property-driven one above; no class
|
||||
/// anywhere calls it directly in code). <c>DoVisibilityToggleAction</c>
|
||||
/// would find zero listeners and silently no-op. (The four action-id
|
||||
/// VALUES themselves are not chat-specific either — the SAME four
|
||||
/// numbers are <c>gmFriendsUI::PostInit</c>'s own Add/Remove/Tell
|
||||
/// button and friends-listbox child ids, a coincidence of Turbine's
|
||||
/// global asset-id allocator, not a cross-reference.) So even with the
|
||||
/// generic mechanism confirmed real and armed on the button side, the
|
||||
/// authored DATA available to us does not wire a target — which is
|
||||
/// consistent with, not a refutation of, the original CH6b grep.
|
||||
/// would find zero listeners and silently no-op. (Two of the four action-id
|
||||
/// VALUES are not chat-specific either — <c>0x10000114</c>/<c>0x10000115</c>
|
||||
/// are <c>m_prevButton</c>/<c>m_nextButton</c> child ids for an unrelated
|
||||
/// pagination widget elsewhere in the decomp
|
||||
/// (<c>acclient_2013_pseudo_c.txt:194343-194344</c>), a coincidence of
|
||||
/// Turbine's global asset-id allocator, not a cross-reference — an
|
||||
/// earlier pass here misattributed this coincidence to
|
||||
/// <c>gmFriendsUI::PostInit</c>'s Add/Remove/Tell child ids, which match
|
||||
/// only the wrong, misread values above and are not actually involved.)
|
||||
/// So even with the generic mechanism confirmed real and armed on the
|
||||
/// button side, the authored DATA available to us does not wire a
|
||||
/// target — which is consistent with, not a refutation of, the original
|
||||
/// CH6b grep.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
|
|
|
|||
|
|
@ -389,7 +389,11 @@ public static class DatWidgetFactory
|
|||
ElementId = info.Id,
|
||||
SpriteResolve = resolve,
|
||||
DatFont = datFont,
|
||||
// Outline 0x21 from the meter element (round-5 review S2).
|
||||
Outline = info.Outline,
|
||||
};
|
||||
if (info.OutlineColor.HasValue)
|
||||
m.OutlineColor = info.OutlineColor.Value;
|
||||
|
||||
// The two 3-slice containers are Type-3 children of the meter element.
|
||||
// ReadOrder determines draw order: the back track has a LOWER ReadOrder
|
||||
|
|
@ -574,11 +578,15 @@ public static class DatWidgetFactory
|
|||
OneLine = oneLine,
|
||||
Centered = info.HJustify == HJustify.Center,
|
||||
RightAligned = info.HJustify == HJustify.Right,
|
||||
// Outline 0x21 from the field element (round-5 review S2).
|
||||
Outline = info.Outline,
|
||||
};
|
||||
if (info.TryGetEffectiveInteger(0x1Eu, out int maxCharacters))
|
||||
field.MaxCharacters = maxCharacters;
|
||||
if (info.FontColor.HasValue)
|
||||
field.TextColor = info.FontColor.Value;
|
||||
if (info.OutlineColor.HasValue)
|
||||
field.OutlineColor = info.OutlineColor.Value;
|
||||
return field;
|
||||
}
|
||||
|
||||
|
|
@ -683,7 +691,13 @@ public static class DatWidgetFactory
|
|||
LabelFont = labelFont,
|
||||
LabelColor = labelInfo.FontColor ?? info.FontColor
|
||||
?? System.Numerics.Vector4.One,
|
||||
// Outline 0x21 / OutlineColor 0x22 follow the same lift chain as the label
|
||||
// and its color: the label-bearing Text child's authored value first, the
|
||||
// button's own second (round-5 review S2).
|
||||
Outline = labelInfo.Outline || info.Outline,
|
||||
};
|
||||
if ((labelInfo.OutlineColor ?? info.OutlineColor) is { } buttonOutlineColor)
|
||||
button.OutlineColor = buttonOutlineColor;
|
||||
|
||||
if (face is not null)
|
||||
{
|
||||
|
|
@ -724,7 +738,11 @@ public static class DatWidgetFactory
|
|||
: elementFont,
|
||||
LabelColor = info.FontColor ?? System.Numerics.Vector4.One,
|
||||
LabelAlign = UiButton.LabelAlignment.Left,
|
||||
// Outline 0x21 from the checkbox element itself (round-5 review S2).
|
||||
Outline = info.Outline,
|
||||
};
|
||||
if (info.OutlineColor.HasValue)
|
||||
button.OutlineColor = info.OutlineColor.Value;
|
||||
|
||||
if (indicator is not null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -561,14 +561,15 @@ public static class LayoutImporter
|
|||
}
|
||||
|
||||
// Outline (0x21): BoolBaseProperty. Retail SetOutline @0x0046a81c / m_bitField &
|
||||
// 0x10. Only update while still at the default (false); derived-wins handled in
|
||||
// ElementReader.Merge — same "only update if still at default" pattern as HJustify.
|
||||
if (!info.Outline
|
||||
&& sd.Properties.TryGetValue(0x21u, out var outlineRaw)
|
||||
&& outlineRaw is BoolBaseProperty outlineBool)
|
||||
{
|
||||
info.Outline = outlineBool.Value;
|
||||
}
|
||||
// 0x10. NOT read here — reading it per-state as each StateDesc is visited is an
|
||||
// any-state "first wins" scan that can pick up a NON-effective state's property
|
||||
// (e.g. a Pressed-only override) ahead of the state retail would actually use.
|
||||
// ElementReader.ApplyCanonicalLegacyProjection (called once per element, right
|
||||
// after every state is read) is the single correct source: it resolves 0x21
|
||||
// through TryGetEffectiveProperty's DirectState-then-effective-default-state rule,
|
||||
// exactly like FontDid/HJustify/VJustify/FontColor already do. Round-5 review N1:
|
||||
// this duplicate early read was masked while Outline only reached UiText; S2's
|
||||
// widening to six more text-bearing widgets un-masks a state mismatch here.
|
||||
|
||||
// OutlineColor (0x22): ColorBaseProperty. Retail m_curOutlineColor, ctor default
|
||||
// RGBAColor_Black. Only read when not already set — same pattern as FontColor.
|
||||
|
|
|
|||
|
|
@ -118,6 +118,12 @@ public sealed class UiDatElement : UiElement, IUiDatStateful
|
|||
else if (info.StateMedia.ContainsKey("Normal"))
|
||||
ActiveState = "Normal";
|
||||
// else ActiveState stays "" (DirectState)
|
||||
|
||||
// Outline 0x21 / OutlineColor 0x22 from the effective-default state, mirroring
|
||||
// DatWidgetFactory.BuildText's seed of UiText (round-5 review S2).
|
||||
Outline = info.Outline;
|
||||
if (info.OutlineColor.HasValue)
|
||||
OutlineColor = info.OutlineColor.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -158,6 +164,16 @@ public sealed class UiDatElement : UiElement, IUiDatStateful
|
|||
/// <summary>Label color (default white).</summary>
|
||||
public Vector4 LabelColor { get; set; } = Vector4.One;
|
||||
|
||||
/// <summary>Retail LayoutDesc property <c>0x21</c> (two-pass glyph outline,
|
||||
/// <c>UIElement_Text::SetOutline @0x0046a81c</c>). Seeded in the ctor from the
|
||||
/// element's effective-default state, same as <see cref="UiText.Outline"/>
|
||||
/// (round-5 review S2 — per-STATE switching is AP-192).</summary>
|
||||
public bool Outline { get; set; }
|
||||
|
||||
/// <summary>Retail LayoutDesc property <c>0x22</c> (<c>m_curOutlineColor</c>,
|
||||
/// ctor default black). Only meaningful when <see cref="Outline"/> is true.</summary>
|
||||
public Vector4 OutlineColor { get; set; } = UiRenderContext.DefaultOutlineColor;
|
||||
|
||||
/// <summary>
|
||||
/// Controls only this element's authored media. Descendants still draw.
|
||||
/// Layered retail composites use this to place a template's chrome behind a
|
||||
|
|
@ -220,7 +236,7 @@ public sealed class UiDatElement : UiElement, IUiDatStateful
|
|||
{
|
||||
float tx = (Width - lf.MeasureWidth(label)) * 0.5f;
|
||||
float ty = (Height - lf.LineHeight) * 0.5f;
|
||||
ctx.DrawStringDat(lf, label, tx, ty, LabelColor);
|
||||
ctx.DrawStringDat(lf, label, tx, ty, LabelColor, Outline, OutlineColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue