fix(chat): round 4 — no user-visible meta text, real /help groups, indicator buttons toggle
Item 3 (#364): every honesty marker is now gone from user-visible /help text. AllegianceOverview/HouseOverview's "[IMPLEMENTED]" tags and trailing "Subcommands NOT marked..." sentences, and Day/Log/Render/Motd's appended "NOT YET IMPLEMENTED in acdream" tails, are removed; the underlying retail text is corrected/completed against the pseudo-C's own pristine consolidated data dumps (Log and Motd had been silently truncated; Render was entirely acdream-authored and is replaced with the real retail usage string). The three PARTIAL /help group topics (channels/chatting/commands) are now COMPLETE verbatim listings: HelpStupidChannelHack's three "vtable slot" operands, previously believed undecodable, are the same pooled/mislabeled-data artifact this campaign has hit before (AP-113's precedent) — reading the function's own disassembly for the push imm32 preceding each constructor call resolves all three directly. messagetypes is now a real ported construction (IsLegalChannel's 14-id whitelist + LogTextTypeToString's name table + the exact join/wrap format) instead of an acdream summary. Register row AP-184 retired. Item 5: the main window's 1/2/3/4 indicator buttons now toggle their floating chat window on click, per the user's retail memory overruling the earlier decomp-only reading. UIElement_Button::HandleButtonClick has its own generic click-driven action dispatch (property 0x12) reaching the same DoVisibilityToggleAction the Alt+1..4 keybinds use; the button fixture confirms this half is genuinely armed, but the floating-window fixture authors no matching listener-registration property, so the generic mechanism has no proven target in the data on hand. Per CLAUDE.md, the user's retail memory is the axiom regardless: ChatWindowController.BindIndicatorClicks wires each indicator's click through the same ToggleFloatingChatWindow chokepoint the keybinds use, as explicit user-directed retail behavior. SetIndicatorOpen stays the sole writer of the Selected mirror so the visual stays consistent through the click round trip. Full reconciliation in docs/research/2026-08-09-chat-retail-window-shell.md §1.4. Campaign plan gets the round-4 findings section; items 1+2 (text-style) are under parallel research, item 4 passed, item 6 deferred to the settings track. Suite: 12,579 passed / 4 skipped / 0 failed (Release, complete solution), up from baseline 12,553/4/0 — net +26 tests, zero regressions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8b166f3ea2
commit
5b54387b8e
10 changed files with 1129 additions and 239 deletions
|
|
@ -59,13 +59,15 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
|
|||
private const uint SendId = 0x10000019u;
|
||||
private const uint MaxMinId = 0x1000046Fu;
|
||||
|
||||
// Chat-window 1-4 state-mirror indicator buttons
|
||||
// Chat-window 1-4 indicator buttons
|
||||
// (gmMainChatUI::RecvNotice_SetPanelVisibility @0x004CCD80): the button lights
|
||||
// up iff the corresponding floating chat window is visible. The mechanism is
|
||||
// one-directional (window visibility drives the button, never the reverse —
|
||||
// see SetIndicatorOpen's doc for the decomp confirmation). Campaign CH slice
|
||||
// CH6b resolves these to _indicatorButtons and wires them via SetIndicatorOpen,
|
||||
// called by RetailUiRuntime whenever a floating chat window's visibility changes.
|
||||
// up iff the corresponding floating chat window is visible. Campaign CH slice
|
||||
// CH6b resolves these to _indicatorButtons and wires the MIRROR half via
|
||||
// SetIndicatorOpen, called by RetailUiRuntime whenever a floating chat window's
|
||||
// visibility changes. Round 4 (2026-08-10) adds the CLICK half — see
|
||||
// SetIndicatorOpen's doc for the full reconciliation between the earlier
|
||||
// decomp-only reading (no per-window code case) and the user's retail memory
|
||||
// (clicking opens/closes the window) — both are correct, at different layers.
|
||||
private const uint Indicator1Id = 0x10000522u;
|
||||
private const uint Indicator2Id = 0x10000523u;
|
||||
private const uint Indicator3Id = 0x10000524u;
|
||||
|
|
@ -265,17 +267,21 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
|
|||
if (layout.FindElement(id) is { } twin)
|
||||
twin.Visible = false;
|
||||
|
||||
// ── Chat-window 1-4 indicator buttons — resolve now, wired later by
|
||||
// RetailUiRuntime via SetIndicatorOpen as each floating window's own
|
||||
// visibility changes (gmMainChatUI::RecvNotice_SetPanelVisibility
|
||||
// @0x004CCD80 — see this class's doc + Indicator1Id..Indicator4Id).
|
||||
// CH6a/b REJECT-review SHOULD-FIX 3: these carry DAT property 0x0B
|
||||
// (ToggleBehavior) = true, but retail's own click dispatch
|
||||
// (gmMainChatUI::ListenToElementMessage @0x004CDA80) has no case for
|
||||
// any of their element ids — clicking one does NOTHING in retail.
|
||||
// SuppressSelfToggle keeps SetIndicatorOpen the ONLY writer of their
|
||||
// Selected mirror; without it, a click would flip the Highlight/Normal
|
||||
// art with no underlying visibility change. ──
|
||||
// ── Chat-window 1-4 indicator buttons — resolve now; the MIRROR half
|
||||
// is wired later by RetailUiRuntime via SetIndicatorOpen as each
|
||||
// floating window's own visibility changes
|
||||
// (gmMainChatUI::RecvNotice_SetPanelVisibility @0x004CCD80 — see this
|
||||
// class's doc + Indicator1Id..Indicator4Id); the CLICK half is wired
|
||||
// by RetailUiRuntime calling BindIndicatorClicks after this method
|
||||
// returns (round 4, 2026-08-10 — see SetIndicatorOpen's doc for the
|
||||
// full reconciliation). SuppressSelfToggle stays true regardless:
|
||||
// these carry DAT property 0x0B (ToggleBehavior) = true, but the
|
||||
// button's own blind self-flip (UiButton.OnEvent's MouseUp case)
|
||||
// would race the REAL toggle's outcome — SetIndicatorOpen (called
|
||||
// synchronously inside the click, through RetailUiRuntime's
|
||||
// WindowVisibilityChanged plumbing) stays the ONE authoritative
|
||||
// writer of Selected, so a click's visual result always matches the
|
||||
// window's actual new state instead of a guessed flip. ──
|
||||
uint[] indicatorIds = { Indicator1Id, Indicator2Id, Indicator3Id, Indicator4Id };
|
||||
for (int i = 0; i < indicatorIds.Length; i++)
|
||||
{
|
||||
|
|
@ -507,22 +513,81 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
|
|||
/// state transition <c>gmMainChatUI::RecvNotice_SetPanelVisibility
|
||||
/// @0x004CCD80</c> performs: <c>State 6</c> (Highlight/"lit") when the
|
||||
/// floating window is visible, <c>State 1</c> (Normal) when it is not.
|
||||
/// This half of the mechanism is genuinely one-directional in the exact
|
||||
/// sense that <c>SetIndicatorOpen</c> is the ONLY writer of
|
||||
/// <see cref="UiButton.Selected"/> — see <see cref="BindIndicatorClicks"/>
|
||||
/// for the click half, which triggers the real toggle THROUGH this same
|
||||
/// writer rather than flipping the indicator itself.
|
||||
///
|
||||
/// <para>
|
||||
/// One-directional by design — a decomp read of
|
||||
/// <c>gmMainChatUI::ListenToElementMessage @0x004CDA80</c> (one of
|
||||
/// several functions in the 2013 binary that branch on
|
||||
/// <c>idMessage == 1</c>, i.e. "clicked" — CH6a/b REJECT-review NIT 4
|
||||
/// corrected the earlier "the ONLY function" superlative, e.g.
|
||||
/// <c>gmFloatyChatUI::ListenToElementMessage @0x004CE330</c> also
|
||||
/// branches on it for the floaty close button) shows THIS function
|
||||
/// handles exactly two element ids: <c>0x1000046f</c> (max/min) and the
|
||||
/// talk-focus menu's selection message. There is no case for
|
||||
/// <c>0x10000522</c>-<c>0x10000525</c> — clicking a chat-window
|
||||
/// indicator button does NOTHING in retail. acdream ports this exactly:
|
||||
/// these buttons have no <c>OnClick</c> (research doc §1.4 corrected —
|
||||
/// its own hedge that "it is safe to wire both" is a weaker reading than
|
||||
/// this direct decomp confirmation, and is now superseded by it).
|
||||
/// <b>Round 4 reconciliation (2026-08-10) — the user's retail memory
|
||||
/// ("clicking opens the window") overruled CH6b's decomp-only reading
|
||||
/// ("clicking does nothing"), and a deeper grep found the mechanism that
|
||||
/// makes BOTH readings correct at their own layer.</b> CH6b's citation of
|
||||
/// <c>gmMainChatUI::ListenToElementMessage @0x004CDA80</c> (no case for
|
||||
/// <c>0x10000522</c>-<c>0x10000525</c>) is STILL TRUE as a statement
|
||||
/// about that one function — but it was the wrong place to look for a
|
||||
/// button click. Retail buttons don't route clicks through their
|
||||
/// PARENT WINDOW's message handler at all: every <c>Type 1</c>
|
||||
/// (<c>UIElement_Button</c>) button has its own generic click path,
|
||||
/// <c>UIElement_Button::HandleButtonClick @0x00471E50</c>, which reads
|
||||
/// an ENUM-kind DAT property <c>0x12</c> off ITSELF; when present, it
|
||||
/// builds an <c>InputEvent</c> and routes it through
|
||||
/// <c>ICIDM</c>/<c>UIElementManager</c>'s action map, which (for a
|
||||
/// visibility action) reaches <c>UIElementManager::DoVisibilityToggleAction
|
||||
/// @0x0045B660</c> — a lookup into
|
||||
/// <c>m_elementInputActionListenerTable</c> (populated by
|
||||
/// <c>UIElementManager::RegisterElementForInputAction</c>, itself called
|
||||
/// from ONE place: <c>UIElement::Initialize</c>'s generic property switch,
|
||||
/// case for ENUM-kind property <c>0x24</c> — ANY element authoring that
|
||||
/// property registers itself as a listener for that action id) — for
|
||||
/// every registered listener, <c>DoVisibilityToggleAction</c> broadcasts
|
||||
/// element message <c>0x31</c>, which <c>UIElement::ListenToElementMessage</c>
|
||||
/// (the ultimate base-class handler every window falls through to when
|
||||
/// its own override, and <c>ChatInterface</c>'s, don't claim the
|
||||
/// message — confirmed for BOTH <c>gmMainChatUI</c> and
|
||||
/// <c>gmFloatyChatUI</c>) handles GENERICALLY: it reads the RECEIVING
|
||||
/// element's OWN enum property <c>0x58</c> and, if it equals <c>1</c>
|
||||
/// ("toggle"), calls <c>SetVisible(!currentlyVisible)</c>.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// This is a real, complete, working "generic UI action" system — the
|
||||
/// task's own hypothesis — and the fixture confirms the indicator
|
||||
/// 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
|
||||
/// 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
|
||||
/// <c>0x58</c> at all — so nothing in the shipped LayoutDesc data ever
|
||||
/// registers a floating chat window as a listener for those four action
|
||||
/// 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.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Per CLAUDE.md, the user's own retail memory is the axiom the code
|
||||
/// must match regardless of what a specific grep or fixture shows.
|
||||
/// <see cref="BindIndicatorClicks"/> makes each indicator's click drive
|
||||
/// the SAME <c>ToggleFloatingChatWindow(windowId)</c> chokepoint the
|
||||
/// <c>Alt+1..4</c> keybinds use — this is USER-DIRECTED retail behavior
|
||||
/// (the generic action-dispatch mechanism exists and is plausibly HOW
|
||||
/// retail wires it, but we cannot prove the exact target registration
|
||||
/// from the data on hand), not a re-guess of the mirror-only reading.
|
||||
/// See <c>docs/research/2026-08-09-chat-retail-window-shell.md</c> §1.4
|
||||
/// for the full writeup.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public void SetIndicatorOpen(int windowId, bool open)
|
||||
|
|
@ -533,6 +598,29 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
|
|||
open ? UiButtonStateMachine.Highlight : UiButtonStateMachine.Normal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wire each indicator button's click to <paramref name="toggleFloatingWindow"/>
|
||||
/// (RetailUiRuntime's <c>ToggleFloatingChatWindow</c>, the SAME chokepoint the
|
||||
/// <c>Alt+1..4</c> keybinds use) — round 4 (2026-08-10), see
|
||||
/// <see cref="SetIndicatorOpen"/>'s doc for the full reconciliation. Called by
|
||||
/// <c>RetailUiRuntime</c> after <see cref="Bind"/> returns, once the runtime's own
|
||||
/// toggle method is available. <see cref="UiButton.SuppressSelfToggle"/> stays true
|
||||
/// on every indicator (set in <see cref="Bind"/>) so this click-triggered toggle's
|
||||
/// outcome — not a blind self-flip — is what <see cref="SetIndicatorOpen"/> mirrors
|
||||
/// back onto <see cref="UiButton.Selected"/>, keeping the visual state consistent
|
||||
/// through the full click round trip even if the toggle is ever refused.
|
||||
/// </summary>
|
||||
public void BindIndicatorClicks(Func<int, bool> toggleFloatingWindow)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(toggleFloatingWindow);
|
||||
for (int i = 0; i < _indicatorButtons.Length; i++)
|
||||
{
|
||||
int windowId = i + 1;
|
||||
if (_indicatorButtons[i] is { } indicator)
|
||||
indicator.OnClick = () => toggleFloatingWindow(windowId);
|
||||
}
|
||||
}
|
||||
|
||||
public RetainedWindowState CaptureWindowState()
|
||||
=> new(
|
||||
Maximized: _maximized,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue