7.4 KiB
CH6a+CH6b review findings — 1fd51543 + 22020ef2 (REJECT; rework list)
Dual-lens Opus review, 2026-08-10. The retail research held up under independent re-derivation (Type-2/Type-9 correction, filter masks, Alt+1..4, pure-mirror indicators all CONFIRMED); the rework list below is what did not.
BLOCKER 1 — CH6a: chat window loses 7 of its 8 authored border pieces
UiResizeGrip : UiElement (UiResizeGrip.cs:33) inherits the no-op
UiElement.OnDraw and is constructed WITHOUT the ElementInfo/resolve
pair (DatWidgetFactory.cs:270-278) — the seven Type-9 grips render
NOTHING. Pre-CH6a, Type 9 fell through to UiDatElement (whose class doc
names resize grips explicitly). Same commit also hides the eight _Locked
media twins (ChatWindowController.cs:239-245) and switches to
Chrome = RetailWindowChrome.Imported (RetailWindowFrame.Mount:133-142
makes outerFrame = content, no wrapper chrome). Fixture proof: all 8 border
elements carry real art (0x06006129/2A/2B/2C/2D family); the window root has
NO background image. Net: only the 400×5 top strip (Type 2 → UiDatElement)
draws — no left/right/bottom edges, no corners.
Fix: UiResizeGrip derives from UiDatElement (or carries info+resolve
and draws its active state's media), keeping ClickThrough = false;
BuildResizeGrip(info) → BuildResizeGrip(info, resolve). Conformance
assertion: each of the seven grips resolves a non-zero sprite.
SHOULD-FIX 2 — CH6b: wrong window-id model asserted as decomp fact
ChatWindowState.cs:31-42 claims UpdateFromPlayerModule @0x004F3920
early-returns for the MAIN window and overloads id 0 as both "main" and
"broadcast". Decomp refutes both: PostInit @0x004F3DD0 switches on
m_eWindowID - 1 (case 0→0xFBFFFFFF, 1→0x101C, 2→0x40C00, 3→0x80000,
4→0x78000000, 7→0xFBFFFFFF); gmChatOptionsUI::InitOptions @0x0049FC60
builds five per-window filter blocks with SetUserData window ids 8
(main, default 0xFBFFFFFF @0x0049FDC9), then 2/3/4/5 (@0x0049FF4E,
:FF71, :FF93, @0x004A0000 default 0x78000000). Retail: main window is
m_eWindowID == 8; floaties are 2–5; m_eWindowID == 0 means UNAUTHORED
(ctor default — what the UpdateFromPlayerModule guard is for); wire
arg5 == 0 is a pure broadcast sentinel, never a window id.
Consequences: the MASKS are correct once re-indexed (confirmed); the main
window's filter IS user-settable in retail (first block on the options
page, with a dedicated high-dword Society child @0x0049FEFB — so 0x20
Society is opt-in on main; acdream's accept: null main view over-displays
it today); SetFilter(0,…) hard no-op (:119) blocks the future options
UI; the 0-overloading makes AP-180's m_idCurrentCommandSource routing
inexpressible. ShouldDisplay_MainWindow_ShowsEveryBroadcastRegardlessOfSeededFilter
(:137-146) pins the wrong behavior including 0x1A displaying in main.
Fix: separate the broadcast sentinel from the main-window id — either
carry retail's real ids (main 8, floaties 2–5) in ChatWindowState, or
keep 0–4 internally plus an explicit BroadcastTargetWindow constant
distinct from every id; drop the SetFilter main no-op; give the main window
a real accept predicate (default 0xFBFFFFFF, i.e. everything except 0x1A
and with the high dword zeroed so Society is opt-in) in
ChatWindowController.GetTranscriptLines; correct the class doc; re-point
the test.
SHOULD-FIX 3 — CH6b: indicator buttons self-toggle on click
UiButton.cs:438-439: _pressed && _pointerOver && Enabled && ToggleBehavior → _selected = !_selected; the fixture shows
0x10000522/0x10000523 carry DAT property 0x0B = true, so a click flips
Highlight↔Normal art with no visibility change — the mirror lies until the
next real toggle. Retail: clicking does nothing (0x10000522–0x10000525
appear ONLY in RecvNotice_SetPanelVisibility @0x004CCD80). The existing
test asserts OnClick == null (wrong level) on a synthetic fixture without
0x0B. Fix: a SuppressSelfToggle opt-out checked at UiButton.cs:438,
set in ChatWindowController.Bind with the @0x004CDA80 citation; test
presses/releases an indicator built WITH 0x0B=true, state unchanged.
SHOULD-FIX 4 — CH6b: 0x2100005B never dumped (research doc's explicit instruction)
No chat_floaty_2100005b.json fixture; FloatingChatWindowControllerTests
is fully synthetic with Type-3 stand-ins. Three untested production
assumptions: FloatingChatWindowController.cs:122 (FindElement(InputId) as UiField — if 0x10000016 isn't Type-12 with Editable 0x16, all four
windows mount non-interactive), :199 (TitleBarId is UiText — if it
imports as UiDatElement the "Chat N" title never renders), :211
(CloseButtonId is UiButton — no test at all). The dump's widget_kind
is media-presence-driven and cannot settle element types. Fix: add
0x2100005B to RetailLayoutFixtureGenerator.Layouts, commit the fixture,
conformance tests pinning the resolved widget types of 0x10000016,
0x10000019, 0x100004D9, 0x1000052A, and the border/corner element types
(also determines whether floaties resize from real grips).
SHOULD-FIX 5 — CH6b: missing register row for the shared-transcript model
Retail: per-ChatInterface m_chatLog, truncated at 10,000
(RecvNotice_DisplayFinalStringInfo @0x004F4711 → TruncateChatLog); a
closed window keeps accumulating (gmFloatyMainChatUI::SetVisible @0x004CE9B0 never unregisters the handler). acdream: one 500-entry
ChatLog with a 200-line display tail each window filters — accumulate-
while-closed and per-window scroll fall out correctly, but the EFFECTIVE
per-window scrollback depth differs (a fellowship-only window retains up to
10,000 fellowship lines in retail; here only those inside the last 200
shared lines). Fix: file the register row (AP series) describing the
depth divergence.
NITs
N1 — filter persistence saves only from /saveautoui (SaveLayout()),
while geometry/visibility auto-save; harmless today, note for CH6e.
N2 — ChatWindows.ResetToDefaults() called only in Dispose; not resetting
across reconnect is likely correct but undocumented — add the one-line
comment (deliberate, user preference survives reconnect).
N3 — research doc §1.3 still says UNVERIFIED for the modifier decode
(CH6b resolved it: MetaKeys 1=Shift, 2=Ctrl, 3=Alt→mask 0x4, 4=Win — also
fix the §1.3 "0x00000002 = shift" mislabel).
N4 — §1.4's "the ONLY function that branches on idMessage == 1" superlative
is false (gmFloatyChatUI::ListenToElementMessage @0x004CE330 does too);
the substantive claim stands; fix the wording.
N5 — ChatTranscriptRenderer.BuildLines calls back into
ChatWindowController.WrapText; move WrapText into the renderer to close
the circular dependency.
CONFIRMED-OK (do not churn)
CH6a layout geometry vs the independent 2026-06-25 dump; the Type-2/Type-9
correction; DecodeBorderLocation vs StartMouseResizing @0x0046B7E0;
deleted compensations have no survivors and each removal is DAT-justified;
cursor ids byte-exact; AP-185 accurate; the UiRoot grip/dragbar priority
change is retail-correct. CH6b masks (via the options page, independent of
PostInit); Alt+1..4 proven; pure-mirror indicators (the finding, not the
implementation); closed-window accumulation reproduced; SpewBox 0x1A
exclusion survives everywhere; ChatTranscriptRenderer behavior-identical
for main; layering clean (Core presentation-free, single canonical
instance, zero env reads, zero GameWindow changes); input pipeline
conventions followed; visibility chokepoint genuine; AP-187/AP-188/#369
accurate.