fix: social panel completion batch (user gate 2026-08-13, "fix all")

One user-ordered batch across the FA social panel + world selection.
Every root cause was probe-proven before the fix (new
ProbeSocialClickRouting in SocialPanelLiveMountProbeTests - production
window mount + real UiRoot hit-tests + a synthetic click):

1. STUCK CHECKBOXES (fellowship x4, allegiance x1, "always checked /
   can't change any options"): the authored checkboxes carry DAT
   ToggleBehavior, so UiButton SELF-FLIPS Selected at MouseUp - the old
   handlers read the flipped value and wrote the ORIGINAL back, snapping
   every click to where it started (the probe recorded (id, oldValue)).
   Fix: SuppressSelfToggle (the CH6a/b mirror discipline) + derive the
   next value from the STORE; the per-tick seeding mirrors it back.
2. UNCLICKABLE ROSTER ROWS ("only get the move window cursor"): the row
   name text is display-text ClickThrough=true, which the hit-test walk
   skips regardless of HandlesClick - the wired OnClick was unreachable.
   Fix: UiText.OnClick assignment now clears ClickThrough (central,
   documented); the stats text gains the same select handler so most of
   the row's width selects the fellow.
3. TRUNCATED EMPTY-STATE ("You do not belong... To create MISSING"):
   the authored string resolves COMPLETE (three sentences) but embedded
   '\n's rendered as one clipped line. DatWidgetFactory now splits
   authored strings into one Line per newline, with the provider still
   re-reading DefaultColor live (the state-color contract - caught by
   BuildText_AuthoredLineTracksStateFontColor).
4. FELLOW NAMES WHITE (user-directed): the AD-82 invented leader-gold +
   selection-blue tints are deleted; names always white (register row
   narrowed).
5. ALLEGIANCE HEADER LABELS: bare "0"/"0" -> "Followers: N" / "Rank: [N]"
   (user-specified format; the full retail StringInfo composition stays
   AD-85's gap), monarch block matching.
6. FRIENDS/SQUELCH LIVE (AD-79 mostly retired): Add friend (name box ->
   0x0018, retail clears the box - Request_AddFriend @0x0048D240),
   Remove (row-click selection -> 0x0017), Appear Offline (CharacterOption
   0x27 via the immediate 0x0005 auto-save, ACE pushes FriendStatusChanged
   to your friend-of list), Squelch Character/Account add-by-name
   (0x0058 guid0/type AllChannels + 0x0059) and Remove for the selected
   row. The wire beneath (builders, WorldSession sends, Runtime commands,
   parsers) existed end-to-end since J4.1/FA1 - this is panel wiring only
   (docs/research/2026-08-13-social-wire-completion.md, committed here).
   Send Tell stays inert (not in the order; AD-79's remainder).
7. WORLD SELF-SELECTION ("clicking my own char should select myself"):
   retail has NO self-exclusion (CPhysicsPart::Draw @0x0050D823 arms
   every physobj; RecvNotice_SmartBoxObjectFound @0x004E5BAE selects
   unconditionally) - the includeSelf gate was an unregistered
   divergence, now removed on both the left-click and right-click paths.

Element roles were probe-measured, never guessed (Add 0x10000514 /
Remove 0x10000515 / Send Tell 0x10000516 / Appear Offline 0x1000052C /
name field 0x1000051B; Squelch: field 0x10000540, Remove 0x10000547,
Squelch Character 0x1000054B, Squelch Account 0x1000054C).

Register: AD-79 mostly retired, AD-82 narrowed. Known remainder, filed
not hidden: the fellowship page's authored 600px content vs the 362px
viewport leaves Dismiss/Assign-Leader below the fold until the window is
resized taller (probe-measured; candidate follow-up).

Tests: Checkbox_Click fact rewritten to the mirror contract (both
directions), monarch-followers label updated, includeSelf expectation
updated, probe extended (click routing, synthetic click, action-widget
role dump). App suite 4,976/3 skips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-13 19:30:51 +02:00
parent ec2a7b0cce
commit 72ceddce2e
15 changed files with 1018 additions and 65 deletions

View file

@ -58,11 +58,11 @@ namespace AcDream.App.UI.Layout;
/// <b>Leader marker.</b> Lane A's row-template inventory names no dedicated
/// "this fellow is the leader" element (the 8-child row is name / stats /
/// three meter+text pairs, nothing else) — retail may simply not mark
/// leadership in the row at all. Lacking a decompiled anchor either way,
/// this controller tints the leader's name text a distinct gold
/// (<see cref="LeaderNameColor"/>) as a minimal, clearly-adaptive visual
/// cue rather than inventing a DAT mechanism that was not found — register
/// row AD-82.
/// leadership in the row at all. The earlier gold leader tint and blue
/// selection tint this controller invented for that gap were RETIRED at the
/// 2026-08-13 gate by user direction: names render white, always
/// (<see cref="MemberNameColor"/>), and selection feedback is the in-game
/// selection itself — register row AD-82 updated accordingly.
/// </para>
///
/// <para>
@ -126,18 +126,12 @@ public sealed class SocialFellowshipPageController
/// <c>_currNum &gt;= 9</c> (lane B §7.1).</summary>
private const int MaxFellowshipSize = 9;
private static readonly Vector4 LeaderNameColor = new(1f, 0.84f, 0f, 1f);
/// <summary>2026-08-13 gate (user-directed): fellow names are WHITE,
/// always — the earlier AD-82 leader-gold and selection-blue tints are
/// retired (no decompiled anchor ever existed for either); the in-game
/// selection ring is the selection feedback.</summary>
private static readonly Vector4 MemberNameColor = Vector4.One;
/// <summary>Fix-round MUST-FIX 4 — the panel-local "this row is selected"
/// visual cue (see the class doc's world&#8594;panel selection note).
/// Same disposition as <see cref="LeaderNameColor"/>: no decompiled
/// anchor for a per-row selection sprite/marker was found, so this is a
/// minimal, clearly-adaptive tint rather than an invented DAT mechanism
/// — register row AD-82. Takes priority over <see cref="LeaderNameColor"/>
/// when a row is both the leader and the current selection.</summary>
private static readonly Vector4 SelectedNameColor = new(0.45f, 0.85f, 1f, 1f);
/// <summary>Cached per <see cref="RefreshFellowshipName"/> — only
/// reassigned when the fellowship name actually changes (fix-round SF-1,
/// the same zero-allocation-while-idle discipline
@ -455,10 +449,19 @@ public sealed class SocialFellowshipPageController
if (tooltip is not null)
checkbox.TooltipText = tooltip;
// 2026-08-13 gate fix ("I can't change any options"): these authored
// checkboxes carry DAT ToggleBehavior, so the button SELF-FLIPS
// Selected at MouseUp — the old `!checkbox.Selected` here then read
// the ALREADY-FLIPPED value and wrote the ORIGINAL back, visually
// snapping every click to where it started (probe-proven:
// ProbeSocialClickRouting recorded (id, oldValue)). Same CH6a/b
// mirror discipline as the chat indicators: suppress the blind
// self-flip, derive the next value from the STORE, and let the
// per-tick seeding mirror the outcome back onto Selected.
checkbox.SuppressSelfToggle = true;
checkbox.OnClick = () =>
{
bool next = !checkbox.Selected;
checkbox.Selected = next;
bool next = !_bindings.CurrentCharacterOption(id);
_bindings.SetCharacterOption(id, next);
};
}
@ -725,6 +728,15 @@ public sealed class SocialFellowshipPageController
uint guid = member.Guid;
nameText.OnClick = () => SelectFellow(guid);
}
// 2026-08-13 gate fix ("I only get the move window cursor"):
// give the STATS text the same click target so most of the row's
// width selects the fellow (UiText.OnClick now clears the
// display-text ClickThrough default — see UiText.OnClick's doc).
if (widgets.Stats is { } statsText)
{
uint guid = member.Guid;
statsText.OnClick = () => SelectFellow(guid);
}
}
foreach (RuntimeFellowMemberSnapshot member in members)
@ -738,13 +750,11 @@ public sealed class SocialFellowshipPageController
if (widgets.Name is { } nameText)
{
string name = member.Name;
// MUST-FIX 4 (fix round): selection takes priority over the
// leader tint when a row is both — it's the more immediate,
// user-driven state (see SelectedNameColor's own doc).
Vector4 color = _selectedFellowGuid == member.Guid
? SelectedNameColor
: snapshot.LeaderGuid == member.Guid ? LeaderNameColor : MemberNameColor;
nameText.LinesProvider = () => [new UiText.Line(name, color)];
// 2026-08-13 gate (user-directed): fellow names render WHITE,
// always — the AD-82 invented leader/selection tints are retired;
// selection feedback is the in-game selection itself
// (SelectFellow drives the world selection ring).
nameText.LinesProvider = () => [new UiText.Line(name, MemberNameColor)];
}
if (widgets.Stats is { } statsText)