acdream/docs/research/2026-08-10-ch6ab-review-findings.md
Erik 8b554d4de4 docs: CH6a+CH6b review REJECT findings — invisible borders, wrong window-id model
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 12:32:09 +02:00

129 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 25; `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 25) in `ChatWindowState`, or
keep 04 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 (0x100005220x10000525
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.