docs: CH6a+CH6b review REJECT findings — invisible borders, wrong window-id model
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
22020ef2c4
commit
8b554d4de4
1 changed files with 129 additions and 0 deletions
129
docs/research/2026-08-10-ch6ab-review-findings.md
Normal file
129
docs/research/2026-08-10-ch6ab-review-findings.md
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
# 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.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue