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:
Erik 2026-08-10 18:46:34 +02:00
parent 8b166f3ea2
commit 5b54387b8e
10 changed files with 1129 additions and 239 deletions

View file

@ -76,8 +76,16 @@ public class ChatLayoutConformanceTests
[InlineData(0x10000523u)]
[InlineData(0x10000524u)]
[InlineData(0x10000525u)]
public void MountedChatWindow_IndicatorButtons_ImportVisibleAndInert(uint indicatorId)
public void MountedChatWindow_IndicatorButtons_BindAloneLeavesClickUnwiredButSelfFlipSuppressed(
uint indicatorId)
{
// Bind() alone (no BindIndicatorClicks call) leaves OnClick unwired —
// the click half is a separate opt-in step RetailUiRuntime performs
// once its own ToggleFloatingChatWindow method is available (round 4,
// 2026-08-10 — see ChatWindowController.SetIndicatorOpen's doc for the
// full retail mechanism reconciliation). This test only proves the
// Bind-alone state; MountedChatWindow_IndicatorButtons_ClickTogglesWindow
// below exercises the full click round trip through BindIndicatorClicks.
var infos = FixtureLoader.LoadChatInfos();
var layout = LayoutImporter.Build(infos, NoTex, null);
var controller = ChatWindowController.Bind(
@ -87,21 +95,18 @@ public class ChatLayoutConformanceTests
UiElement? indicator = layout.FindElement(indicatorId);
Assert.NotNull(indicator);
Assert.True(indicator!.Visible);
// Inert: CH6a does not wire a click handler (CH6b's job — the
// one-directional visibility mirror has no button-press behavior of
// its own in retail either, per the research doc §1.4). UiButton
// structurally always reports HandlesClick=true (it can receive the
// event), but with no OnClick/OnClickAt bound, a click is a no-op.
var button = Assert.IsType<UiButton>(indicator);
Assert.Null(button.OnClick);
Assert.Null(button.OnClickAt);
// CH6a/b REJECT-review SHOULD-FIX 3: OnClick==null is the wrong level
// to prove "inert" — the fixture's own 0x0B (ToggleBehavior) = true
// means a plain press/release would still flip UiButton's internal
// Selected mirror even with no OnClick bound. SuppressSelfToggle
// (set by ChatWindowController.Bind) is the actual guard; a press
// and release must leave Selected unchanged.
// to prove "self-flip suppressed" — the fixture's own 0x0B
// (ToggleBehavior) = true means a plain press/release would still
// flip UiButton's internal Selected mirror even with no OnClick
// bound. SuppressSelfToggle (set by ChatWindowController.Bind) is
// the actual guard, and it stays true even after round 4 wires a
// real click handler — see SetIndicatorOpen's doc for why the blind
// self-flip must stay suppressed regardless.
Assert.True(button.SuppressSelfToggle);
bool before = button.Selected;
button.OnEvent(new UiEvent(0, button, UiEventType.MouseDown, Data1: 2, Data2: 2));
@ -109,6 +114,83 @@ public class ChatLayoutConformanceTests
Assert.Equal(before, button.Selected);
}
[Theory]
[InlineData(0x10000522u, 1)]
[InlineData(0x10000523u, 2)]
[InlineData(0x10000524u, 3)]
[InlineData(0x10000525u, 4)]
public void MountedChatWindow_IndicatorButtons_ClickTogglesWindow_ThroughBindIndicatorClicks(
uint indicatorId, int expectedWindowId)
{
// Round 4 (2026-08-10): clicking an indicator now toggles its floating
// chat window through the SAME chokepoint the Alt+1..4 keybinds use
// (user-directed retail behavior — see SetIndicatorOpen's doc for the
// full mechanism reconciliation: the generic UIElement_Button
// action-dispatch system is real and armed on the button side, but
// the shipped floating-window fixture authors no matching listener
// registration, so this wiring cannot be proven purely from the DAT).
var infos = FixtureLoader.LoadChatInfos();
var layout = LayoutImporter.Build(infos, NoTex, null);
var controller = ChatWindowController.Bind(
infos, layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, new ChatWindowState(), null, null, NoTex);
Assert.NotNull(controller);
var toggled = new List<int>();
controller!.BindIndicatorClicks(windowId =>
{
toggled.Add(windowId);
return true;
});
UiElement? indicator = layout.FindElement(indicatorId);
var button = Assert.IsType<UiButton>(indicator);
Assert.NotNull(button.OnClick);
button.OnEvent(new UiEvent(0, button, UiEventType.MouseDown, Data1: 2, Data2: 2));
button.OnEvent(new UiEvent(0, button, UiEventType.MouseUp, Data1: 2, Data2: 2));
button.OnEvent(new UiEvent(0, button, UiEventType.Click, Data1: 2, Data2: 2));
Assert.Equal(new[] { expectedWindowId }, toggled);
}
[Fact]
public void MountedChatWindow_IndicatorButtons_ClickRoundTrip_KeepsMirrorConsistent()
{
// The full round trip: click -> BindIndicatorClicks' delegate calls the
// real toggle -> the caller (standing in for RetailUiRuntime's
// WindowVisibilityChanged plumbing) calls SetIndicatorOpen with the
// window's NEW state -> Selected matches that new state, not a blind
// self-flip. SuppressSelfToggle staying true is what makes this
// authoritative-writer invariant hold even though the button now has
// a real OnClick.
var infos = FixtureLoader.LoadChatInfos();
var layout = LayoutImporter.Build(infos, NoTex, null);
var controller = ChatWindowController.Bind(
infos, layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, new ChatWindowState(), null, null, NoTex);
Assert.NotNull(controller);
bool windowOpen = false;
controller!.BindIndicatorClicks(windowId =>
{
windowOpen = !windowOpen;
controller.SetIndicatorOpen(windowId, windowOpen);
return true;
});
var button = Assert.IsType<UiButton>(layout.FindElement(0x10000522u));
Assert.False(button.Selected);
button.OnEvent(new UiEvent(0, button, UiEventType.MouseDown, Data1: 2, Data2: 2));
button.OnEvent(new UiEvent(0, button, UiEventType.MouseUp, Data1: 2, Data2: 2));
button.OnEvent(new UiEvent(0, button, UiEventType.Click, Data1: 2, Data2: 2));
Assert.True(button.Selected);
button.OnEvent(new UiEvent(0, button, UiEventType.MouseDown, Data1: 2, Data2: 2));
button.OnEvent(new UiEvent(0, button, UiEventType.MouseUp, Data1: 2, Data2: 2));
button.OnEvent(new UiEvent(0, button, UiEventType.Click, Data1: 2, Data2: 2));
Assert.False(button.Selected);
}
[Theory]
[InlineData(0x10000693u)]
[InlineData(0x10000694u)]