fix(ui): talk button keeps authored 46x17; white authored captions + font for talk/Send

Owner report (2026-08-24, post gate-pass): the chat channel button was
bigger than retail and both its caption and the Send caption were warm
gold instead of retail's near-white.

Size: retail never resizes the talk button — HandleSelection
@0x004cd540 only swaps the caption string; the authored 46x17 element
stands, and the authored SHORT captions ('Gen', 'Fell', ...) fit it —
that is why retail abbreviates. Our content-widening reflow (grow the
button to its label, shift the input) was a compensation for the
now-retired invented long captions, measured with the wrong font on
top. Deleted; the authored row layout stands.

Color + font: the button caption child (0x10000015) and the Send
button (0x10000019) both author pure white text with their OWN FontDid
0x40000002 (live-DAT probed) — different from the transcript font,
which is the other half of why 'Chat' fits 46px. UiMenu gains a
ButtonDatFont for the caption (popup rows keep the menu font);
the controller reads both elements' authored FontColor/FontDid instead
of the invented (1,.92,.72) constants.

The old widening pin is rewritten to the retail contract; a new
conformance test pins authored width, white captions, and the authored
font DID being requested for both buttons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-24 20:43:46 +02:00
parent c12a95b6e8
commit 67e963524b
4 changed files with 106 additions and 33 deletions

View file

@ -62,6 +62,7 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
private const uint TrackId = 0x10000012u;
private const uint InputBarId = 0x10000013u;
private const uint MenuId = 0x10000014u;
private const uint MenuLabelId = 0x10000015u; // button caption child: white, FontDid 0x40000002
private const uint InputId = 0x10000016u; // Type-12 Text + Editable 0x16 → UiField
private const uint SendId = 0x10000019u;
private const uint MaxMinId = 0x1000046Fu;
@ -301,7 +302,8 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
BitmapFont? debugFont,
Func<uint, (uint tex, int w, int h)> resolve,
Func<string?>? selectedTargetName = null,
Func<string, string?>? chatStrings = null)
Func<string, string?>? chatStrings = null,
Func<uint, UiDatFont?>? resolveFont = null)
{
ArgumentNullException.ThrowIfNull(windowFilters);
@ -478,6 +480,19 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
if (layout.FindElement(MenuId) is UiMenu menu)
{
menu.DatFont = datFont; menu.Font = debugFont; menu.SpriteResolve = resolve;
// The authored label child (0x10000015) carries the button
// caption's OWN color and font: pure white, FontDid 0x40000002
// (live-DAT probed 2026-08-24) — not the transcript font and not
// the invented warm gold. Falls back to the previous defaults
// when the fixture lacks the child (hermetic tests).
if (FindInfo(rootInfo, MenuLabelId) is { } labelInfo)
{
if (labelInfo.FontColor is { } authoredColor)
menu.TextColor = authoredColor;
if (labelInfo.FontDid != 0u
&& resolveFont?.Invoke(labelInfo.FontDid) is { } buttonFont)
menu.ButtonDatFont = buttonFont;
}
menu.NormalSprite = MenuNormal; menu.PressedSprite = MenuPressed;
menu.PopupBgSprite = MenuPopupBg;
menu.ItemNormalSprite = MenuItemRow; menu.ItemHighlightSprite = MenuItemSelected;
@ -559,33 +574,28 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
if (layout.FindElement(SendId) is UiButton sendEl)
{
sendEl.OnClick = () => c.Input.Submit();
// The Send sprite is a blank gold button — retail draws the caption as text.
// The Send sprite is a blank gold button — retail draws the caption
// as text with the element's OWN authored style: pure white,
// FontDid 0x40000002 (live-DAT probed 2026-08-24; the previous
// warm gold was invented).
sendEl.Label = "Send";
sendEl.LabelFont = datFont;
sendEl.LabelColor = new Vector4(1f, 0.92f, 0.72f, 1f);
ElementInfo? sendInfo = FindInfo(rootInfo, SendId);
sendEl.LabelFont =
(sendInfo?.FontDid is { } sendFontDid and not 0u
? resolveFont?.Invoke(sendFontDid)
: null) ?? datFont;
sendEl.LabelColor = sendInfo?.FontColor ?? new Vector4(1f, 1f, 1f, 1f);
}
// ── Size the channel button to its label + reflow the input field ─
// Retail's talk-focus button autosizes to the selected channel name; the input
// field then fills the gap from the button's right edge to the Send button. The
// dat authors the button at a fixed 46px (too narrow for "Chat" once the LED +
// arrow are accounted for), so widen it to its content and shift the input.
// Recompute on every channel change (the button grows/shrinks with the label).
if (c.Menu is not null)
{
float inputRight = c.Input.Left + c.Input.Width; // == Send button's left edge
void ReflowInputRow()
{
c.Menu.Width = System.MathF.Round(c.Menu.NaturalButtonWidth());
c.Menu.ResetAnchorCapture();
c.Input.Left = c.Menu.Left + c.Menu.Width;
c.Input.Width = System.MathF.Max(40f, inputRight - c.Input.Left);
c.Input.ResetAnchorCapture();
}
var onSelect = c.Menu.OnSelect;
c.Menu.OnSelect = p => { onSelect?.Invoke(p); ReflowInputRow(); };
ReflowInputRow();
}
// 2026-08-24: the previous content-widening reflow here (grow the
// button to its label, shift the input) was a compensation for the
// invented LONG captions ("General"). Retail keeps the authored
// 46x17 button — the authored SHORT captions ('Gen', font
// 0x40000002) fit it, which is exactly why retail abbreviates
// (gmMainChatUI::HandleSelection @0x004cd540 never resizes the
// element). The authored row layout stands untouched.
// ── Max/min toggle — gmMainChatUI::HandleMaximizeButton ──
// The dat already authors max/min (368,5,16,16) just left of the scrollbar