fix(chat): CH6a/b rework — grip media, retail window-id model, floaty fixture

Applies docs/research/2026-08-10-ch6ab-review-findings.md in full:

- BLOCKER 1: UiResizeGrip now carries its ElementInfo/resolve pair and
  draws its own authored DirectState media (a synthetic parameterless
  grip still draws nothing, preserving existing resize-drag tests).
  DatWidgetFactory.BuildResizeGrip threads resolve through. All seven
  live grips on the main chat window now resolve a non-zero sprite,
  restoring the visible borders/corners CH6a silently dropped.

- SHOULD-FIX 2: ChatWindowState gains BroadcastTargetWindow, a sentinel
  distinct from every real window id (0-4), fixing the bug where the
  main window's explicit-addressing branch coincided with the broadcast
  check (both were literal 0). SetFilter's main-window no-op is dropped
  — the main window's filter is now genuinely settable. ChatWindowController
  .Bind takes a ChatWindowState (the same canonical instance the floating
  windows already share) and GetTranscriptLines builds a real accept
  predicate instead of accept:null. Verified safe: ClientLocal (0x1A)
  never reaches ChatLog (AddText routes it to the SpewBox and returns),
  so nothing observable regresses.

- SHOULD-FIX 3: UiButton.SuppressSelfToggle stops the four chat-window
  indicator buttons (DAT property 0x0B=true, no retail click handler)
  from flipping their own Selected mirror on a stray click.

- SHOULD-FIX 4: generated and committed chat_floaty_2100005b.json from
  the real installed dats; added the permanent RetailLayoutFixtureGenerator
  entry. All three flagged FloatingChatWindowController assumptions
  (input field, title bar, close button) are confirmed correct against
  real data — no controller code changes needed. New finding: unlike the
  main window, ALL EIGHT floaty border/corner elements are live Type-9
  grips (the floaty's own title bar is its move handle), so a floaty
  window resizes from every edge and corner.

- SHOULD-FIX 5: register row AP-189 documents the shared-500-entry/
  200-line-tail vs retail's per-window 10,000-line scrollback depth gap.

- NITs 1-5: documented the filter-persistence-only-on-/saveautoui
  asymmetry and the reconnect-preserves-filters intent; corrected the
  research doc's modifier-mask mislabel and the "ONLY function" false
  superlative; moved WrapText off ChatWindowController onto
  ChatTranscriptRenderer, closing the circular dependency.

Full Release suite: 12,420 passed / 4 skipped / 0 failed (baseline
12,392/4/0 at 22020ef2; net +28 tests, zero regressions).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-10 13:09:32 +02:00
parent 56b84deeab
commit 1aa7709988
23 changed files with 6284 additions and 248 deletions

File diff suppressed because one or more lines are too long

View file

@ -245,10 +245,10 @@ implementer per slice against a pinned contract (per
| CH6a main-window layout + 8-grip resize | (this commit) | 12,317 passed / 4 skipped / 0 failed | pending (no subagent review pass this session — implementer-only) | pending — needs the next in-client round (items H/I round 1, item 6 round 2) |
| CH6b/CH6c floating windows + opacity | superseded — split below | — | — | superseded |
| User gate round 2 | (this commit) | 12,267 passed / 4 skipped / 0 failed | — | items 2/4/5 fixed this commit, item 3 confirmed-fixed, item 6 folded into CH6a's spec, item 1 NOT reproduced (see "User gate — round 2" below) |
| CH6a main-window layout + 8-grip resize | `1fd51543` | 12,317 passed / 4 skipped / 0 failed | pending | pending — landed same day as round 2 |
| CH6a main-window layout + 8-grip resize | `1fd51543`, reworked (this commit) | 12,420 passed / 4 skipped / 0 failed | REJECT (docs/research/2026-08-10-ch6ab-review-findings.md, BLOCKER 1) → reworked (this commit) | pending — needs the next in-client round to confirm the border/corner art now renders |
| Jump-in-air root cause (round-2 item 1, resolved) | `a5a7eb4f` | Runtime tests 1,323/0 | — | round-3 probe evidence pinpointed a missing `OnInterfaceText` wire on the production controller-commit path (`RuntimeLocalPlayerMovementState.CommitRuntimeOwnedController`); FIXED, regression test added |
| User gate round 3 | (this commit) | Debug (all projects): 12,329 passed / 4 skipped / 1 failed (pre-existing #351 Debug-only flake — reproduces identically on the pristine pre-round-3 commit, not a regression); Release (every project reachable while a live `AcDream.App.exe` client — PID 15064, must not be killed per project policy — holds its own Release binaries locked, blocking `AcDream.App`/`AcDream.App.Tests`/`AcDream.Core.Tests` specifically): `AcDream.UI.Abstractions.Tests` (the layer this round's `/help` fix lives in) 867/867, plus `Core.Net.Tests` 823/823, `Runtime.Tests` 1,323/1,323, `Content.Tests` 130/130, `Headless.Tests` 89/89, `Bake.Tests` 15/15, `Cli.Tests` 4/4 — all 0 failed | — | findings (a)-(c) fixed this commit — SpewBox flush-top + retail dat font, `/help`/`/help death` exact retail print sequence (see "User gate — round 3" below) |
| CH6b floating windows 14 | (this commit) | 12,392 passed / 4 skipped / 0 failed | REJECT (docs/research/2026-08-10-ch6ab-review-findings.md); rework in progress | pending — no client launches this session (hard constraint); needs the next connected round for keybind/mirror/filter visual confirmation |
| CH6b floating windows 14 | `22020ef2`, reworked (this commit) | 12,420 passed / 4 skipped / 0 failed | REJECT (docs/research/2026-08-10-ch6ab-review-findings.md) → reworked (this commit) — SHOULD-FIXES 2/3/4/5 + NITs 1-5 applied | pending — no client launches this session (hard constraint); needs the next connected round for keybind/mirror/filter visual confirmation, plus the new 0x2100005B fixture's resolved-type assumptions |
| CH6c opacity | not started | — | — | not started |
### CH4 closeout (2026-08-09)
@ -722,3 +722,98 @@ retail LayoutDesc dumps) to find the smallest font id actually CONFIRMED
in use by any retail UI import acdream has ported, rather than merely
present in the DAT. `0x40000025` (11px) was both the global minimum across
those fixtures AND the chat window's own minimum — no tie-break needed.
## CH6a/b REJECT-review rework (2026-08-10)
Applied everything in `docs/research/2026-08-10-ch6ab-review-findings.md`
under the session's hard constraints (no subagents, no client launches).
The retail research itself held up under independent re-derivation
(masks, Alt+1..4, pure-mirror indicators all CONFIRMED); the rework was
entirely against the FIX list, not the research.
- **BLOCKER 1 (CH6a — invisible borders).** `UiResizeGrip` (`src/AcDream.App/UI/UiResizeGrip.cs`)
gained a second constructor carrying its `ElementInfo`/sprite-resolve
pair and an `OnDraw` override that stamps the active DirectState sprite
exactly like `UiDatElement` — a synthetic (parameterless) grip still
draws nothing, matching every existing resize-drag unit test.
`DatWidgetFactory.BuildResizeGrip` now threads `resolve` through.
`SpriteFile` exposes the raw dat sprite id for conformance tests without
needing a live GL context; `ChatLayoutConformanceTests
.MountedChatWindow_LiveGrip_ResolvesNonZeroSprite` pins all seven live
grips against the committed `chat_2100006f.json` fixture.
- **SHOULD-FIX 2 (CH6b — window-id model).** `ChatWindowState` gained
`BroadcastTargetWindow` (a sentinel distinct from every real window id
`0`-`4`) so `ShouldDisplay`'s explicit-addressing branch can no longer
coincide with the broadcast check for the main window (id `0`).
`SetFilter`'s main-window no-op is dropped — the main window's filter is
now genuinely settable, matching retail's options-page-driven main
filter. `ChatWindowController.Bind` gained a `ChatWindowState
windowFilters` parameter (the same canonical
`RuntimeCommunicationState.ChatWindows` instance the floating windows
already read) and `GetTranscriptLines` now builds a real accept
predicate from it instead of `accept: null` — verified safe: `0x1A`
(`ClientLocal`) never reaches `ChatLog` in the first place
(`RuntimeCommunicationState.AddText` routes it to the SpewBox and
returns before `Chat.OnSystemMessage`), so excluding it from the main
filter has zero effect on WeenieError/refusal text. Every production
gameplay chat type (Speech/Tell/Social/Fellowship/Allegiance/Turbine
rooms) stays within the default filter's covered bits. Register row
AP-189 documents the resulting shallower shared-log scrollback depth
(SHOULD-FIX 5, below) as the one behavioral residual.
- **SHOULD-FIX 3 (CH6b — indicator self-toggle).** `UiButton` gained
`SuppressSelfToggle`, checked alongside `ToggleBehavior` at the
press-release toggle site; `ChatWindowController.Bind` sets it on all
four chat-window indicator buttons (which carry DAT property `0x0B` =
true but have no retail click handler). New `UiButtonTests
.SuppressSelfToggle_PressReleaseDoesNotFlipSelected` and an extended
`MountedChatWindow_IndicatorButtons_ImportVisibleAndInert` (now presses
and releases the real fixture's indicator, asserting `Selected`
unchanged) replace the "wrong level" `OnClick == null` check.
- **SHOULD-FIX 4 (CH6b — 0x2100005B never dumped).** The floaty layout
DAT dir at `%USERPROFILE%\Documents\Asheron's Call\` (the default
`ConformanceDats.ResolveDatDir()`/`RetailLayoutFixtureGenerator` path)
IS reachable in this environment — `chat_floaty_2100005b.json` (171 KB,
5,473 lines) was generated for real and committed;
`RetailLayoutFixtureGenerator.Layouts` gained the permanent entry for
future regenerations. All three flagged untested assumptions in
`FloatingChatWindowController` turned out CORRECT against the real
fixture — no controller code changed: `0x10000016` is Type 12 with
property `0x16` (Editable) = true, resolving to `UiField`; `0x100004D9`
is Type 12 with NO `0x16`, resolving to `UiText`; `0x1000052A` is Type 1,
resolving to `UiButton`. One extra finding beyond the three named
assumptions: unlike the main window (whose plain top strip is a Type-2
Dragbar move handle because the main window has no title bar), the
floaty window's move handle is its own title bar group (`0x10000529`,
Type 2) — so ALL EIGHT of the floaty's border/corner elements are live
Type-9 grips, not seven; a floaty window resizes from every edge and
corner. New `FloatingChatLayoutConformanceTests.cs` pins the resolved
widget types and extends BLOCKER 1's sprite-non-zero guard to all eight
floaty grips.
- **SHOULD-FIX 5 (register row).** AP-189 files the shared-500-entry/
200-line-tail vs. retail's per-window 10,000-line log scrollback-depth
divergence — the accumulate-while-closed and independent-per-window-
scroll BEHAVIORS both reproduce correctly; only the numeric DEPTH ceiling
differs.
- **NITs 1-5.** N1: documented the filter-persistence-only-on-`/saveautoui`
asymmetry on `RetailUiRuntime.SaveChatWindowFilters`, deferred to CH6e.
N2: documented why `ChatWindows.ResetToDefaults()` only runs in
`RuntimeCommunicationState.Dispose` (full teardown), not on reconnect —
deliberate, matches window-geometry persistence. N3: replaced the §1.3
UNVERIFIED modifier-mapping hedge and the "`0x00000002` = shift" mislabel
with `retail-default.keymap.txt`'s own `MetaKeys` legend (Shift=`0x1`,
Ctrl=`0x2`, Alt=`0x4`, Win=`0x8`) — no code change, `KeyBindings` already
had `ModifierMask.Alt` right. N4: fixed the false "ONLY function that
branches on `idMessage == 1`" superlative in §1.4 and
`ChatWindowController.SetIndicatorOpen`'s doc
(`gmFloatyChatUI::ListenToElementMessage @0x004CE330` also does); the
substantive "no case for the indicator ids" claim stands. N5: moved
`WrapText`/`WrapSingleLine` off `ChatWindowController` onto
`ChatTranscriptRenderer` (closing the circular dependency where
`BuildLines` called back into one of its own two consumers); updated the
two unrelated external callers (`IndicatorDetailText.cs`,
`ItemAppraisalReport.cs`) and moved the WrapText tests into the new
`ChatTranscriptRendererTests.cs`.
Full Release suite: 12,420 passed / 4 skipped / 0 failed (baseline
12,392/4/0 at `22020ef2`; net +28 tests, all new coverage from this
rework, zero regressions).

View file

@ -87,11 +87,30 @@ ToggleFloatingChatWindow4 [ "" [ 0 DIK_4 ] 0x00000004 ]
```
`0x00000004` is the modifier mask (the same mask the `UseQuickSlot_14..18` rows
use for `Alt`+digit, versus `0x00000002` for the shift-digit quickslot rows).
use for `Alt`+digit, versus `0x00000002` for the CTRL-digit quickslot rows —
corrected below; the original filing mislabeled `0x00000002` as "shift").
So retail's default is **Alt+1 … Alt+4**, not bare 14.
UNVERIFIED: the exact modifier bit → key mapping (4 = Alt vs 4 = Ctrl). Cheapest
resolution: read `keymap-default.txt`'s modifier legend, or one cdb breakpoint on
`UIElementManager::KeyPressEvent`. The *mechanism* below is independent of this.
RESOLVED at Campaign CH slice CH6b: `retail-default.keymap.txt`'s own
`MetaKeys` legend (not a guess — the keymap file's literal index table) reads
```
MetaKeys
[
1 [ 0 DIK_LSHIFT ]
2 [ 0 DIK_LCONTROL ]
2 [ 0 DIK_RCONTROL ]
3 [ 0 DIK_LMENU ]
3 [ 0 DIK_RALT ]
4 [ 0 DIK_LWIN ]
4 [ 0 DIK_RWIN ]
]
```
so the modifier-mask bit for MetaKeys index *N* is `1 << (N-1)`: **index 1 =
Shift → `0x1`, index 2 = Ctrl → `0x2`, index 3 = Alt → `0x4`, index 4 = Win →
`0x8`**. `0x00000004` on the `ToggleFloatingChatWindow1..4` rows is therefore
unambiguously **Alt**, cross-checked against the same file's own Alt+A/D
strafe and Alt+Enter/Tab/F4 rows (all `0x00000004`). `KeyBindings` already
carried `ModifierMask.Alt` for these four actions since Phase K.1c — no code
change was needed, only removing this hedge.
The dispatch chain is fully generic — there is no chat-specific code in it:
@ -151,10 +170,15 @@ State 6 = "on/depressed", state 1 = "normal". No handler anywhere in the binary
switches on `0x10000522..0x10000525` as a *source* of a click — `grep` over the
whole pseudo-C returns only `gmMainChatUI::RecvNotice_SetPanelVisibility`.
**RESOLVED 2026-08-10 at Campaign CH slice CH6b.** The prior UNVERIFIED
paragraph's hedge ("safe to wire both") is superseded by a direct read of
`gmMainChatUI::ListenToElementMessage @0x004CDA80` — the ONLY function in the
whole 2013 binary that branches on `idMessage == 1` ("clicked"). It handles
**RESOLVED 2026-08-10 at Campaign CH slice CH6b; wording corrected at the
CH6a/b REJECT-review (NIT 4) — the original "ONLY function in the whole 2013
binary that branches on `idMessage == 1`" superlative was false**
(`gmFloatyChatUI::ListenToElementMessage @0x004CE330`, the floaty windows'
close-button handler, also branches on `idMessage == 1`; §1.5). **The
substantive claim stands: within `gmMainChatUI::ListenToElementMessage`
specifically, there is no case for `0x10000522`-`0x10000525`.** The prior
UNVERIFIED paragraph's hedge ("safe to wire both") is superseded by a direct
read of `gmMainChatUI::ListenToElementMessage @0x004CDA80`. It handles
exactly two element ids: `0x1000046f` (max/min, dispatching
`HandleMaximizeButton`) and the talk-focus menu's selection message
(`idMessage == 7`, checked against `this->m_pCCS` / a `0x1000000b` attribute