Collects the post-gate polish left uncommitted by the killed round-5 agent (S1/S3 + review fixes N1/N3/N4) and completes the missing S2 half: - S1: UiText multi-line transcript + colored-run label now submit EVERY line/run's outline pass before ANY fill pass, matching retail's UIElement_Text::DrawSelf @0x00467aa0 whole-block walk. DrawStringDatPass is exposed for block-level batching; single lines keep DrawStringDat. - S2 (completed this commit): authored outline 0x21/0x22 now reaches every text-bearing widget — UiButton, UiDatElement, UiField, UiMeter, UiMenu, UiCatalogSlot — seeded from the element's effective-default state exactly like UiText (BuildButton lifts the label-bearing Text child's authored value first, same chain as the label color). Per-STATE outline switching (dialog/character/combat buttons author 0x21 in state 0x3 only) is NOT ported — filed as register row AP-192 in this commit. - S3: ChatWindowController reconciliation comment corrects the misread indicator action ids 0x10000514-17 -> 0x10000114-17 and re-attributes the id-coincidence to the pagination widget's m_prevButton/m_nextButton, not gmFriendsUI; register + window-shell research doc corrected to match. - N1: LayoutImporter's duplicate per-state any-state-first-wins 0x21 read is deleted — ElementReader.ApplyCanonicalLegacyProjection's DirectState-then- effective-default resolution is the single source (the duplicate would have lit state-0x3-only outlines permanently once S2 widened consumption). - N3: the outline pass tints with the outline color's OWN alpha, not the fill's (retail tints m_curOutlineColor and m_curTextColor independently). - N4: the outline-inflated glyph SOURCE rect is clamped to the atlas bounds with matching dest shrink, porting CreateCharRectPair @0x00441480's edge behavior — edge glyphs crop instead of sampling a neighbour's texels. Full Release suite: 12,610 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1126 lines
91 KiB
Markdown
1126 lines
91 KiB
Markdown
# Campaign CH — chat & interface-text retail parity
|
||
|
||
**Status:** USER-GATE PASSED — CAMPAIGN CLOSED 2026-08-10. Five connected
|
||
gate rounds (2026-08-09/10) iterated colors, SpewBox, side channels,
|
||
commands, the window shell, and the retail text style to user acceptance
|
||
(round 5: "Good, looks good now"). Carried tail: #366 (unseen-text
|
||
indicator), #369 (floaty send channel), AP-177 (SpewBox line lifetime),
|
||
AP-190 (opacity ease), AP-191 (transcript tag colors), and the round-5
|
||
review's S1/S2/S3 follow-ups (per-block outline hoist, non-UiText outline
|
||
paths + register row, reconciliation hex citation) — queued as post-close
|
||
polish.
|
||
|
||
**Why now:** first track of the alpha-release program (chat is the most
|
||
visible daily surface for the friend-alpha). User-directed 2026-08-09.
|
||
|
||
## Scope
|
||
|
||
Four deliverables, one campaign:
|
||
|
||
1. **Exact chat colors.** The 2026-06-16 cdb session pinned the retail
|
||
`RGBAColor` constants (0x81c4a8+), but only 4 of ~13 kinds were mapped
|
||
with confidence; the type→color lookup table
|
||
(`ChatInterface::BuildChatColorLookupTable @ 0x4f31c0`) was never
|
||
decompiled. This campaign pins the COMPLETE table and conforms
|
||
`ChatWindowController.RetailChatColor` to it.
|
||
2. **Working side channels vs local ACE.** Turbine rooms (General / Trade /
|
||
LFG / Roleplay / Society) and the legacy family (/f /a /m /p /v /cv),
|
||
inbound and outbound. The 26-day-old "ACE doesn't run a TurbineChat
|
||
server" claim is under verification — the research lane reads current
|
||
ACE source rather than trusting it.
|
||
3. **Retail's on-screen interface text.** The transient yellow
|
||
top-of-viewport messages (jump-while-airborne being the canonical
|
||
example) that retail does NOT put in the chat window. Routing table
|
||
(on-screen vs chat vs both), presentation (color/duration/fade as far
|
||
as acclient-side data allows — the draw itself lives in keystone.dll),
|
||
and the client-raised local errors.
|
||
4. **Complete `/` and `@` command registry.** Every command the retail
|
||
client parses, audited against `RetailClientCommandCatalog` (the
|
||
2026-07-13 family port is the baseline, not a restart). Missing
|
||
commands implemented; non-retail verbs remain ACE server-passthrough
|
||
per the command ownership rule (2026-07-13).
|
||
|
||
**Out of scope:** retail's secondary/floating chat windows and per-window
|
||
filter masks (post-alpha polish unless a slice lands it for free); chat
|
||
color user-configurability (we ship retail defaults); TurbineChat server
|
||
emulation (ACE-side).
|
||
|
||
## Method
|
||
|
||
Per CLAUDE.md: grep `docs/research/named-retail/` first; cdb only where
|
||
static decomp is insufficient; cross-check ACE + holtburger; conformance
|
||
tests pin every table (colors, routing, command catalog); divergence
|
||
register rows land in the same commits; build + full Release suite green
|
||
per commit.
|
||
|
||
**Model split (user-directed 2026-08-09):** research on Opus; planning by
|
||
the main loop (Fable); implementation slices on Sonnet; every slice gets a
|
||
dual-lens Opus review — retail faithfulness AND architecture — before its
|
||
gate.
|
||
|
||
## Research lanes (all Opus, parallel, read-only)
|
||
|
||
| Lane | Output doc | Question |
|
||
|---|---|---|
|
||
| R1 command registry | `docs/research/2026-08-09-chat-retail-command-registry.md` | Complete retail verb/alias/handler table + acdream audit |
|
||
| R2 interface text | `docs/research/2026-08-09-chat-retail-interface-text.md` | What draws the yellow text, the routing table, client-raised errors |
|
||
| R3 color table | `docs/research/2026-08-09-chat-retail-color-table.md` | Full BuildChatColorLookupTable decompile + wire→type→color trace |
|
||
| R4 side channels | `docs/research/2026-08-09-chat-side-channels-vs-ace.md` | Per-family defect diagnosis vs current ACE source |
|
||
|
||
## Slices (provisional — finalized when research lands)
|
||
|
||
Ordering rationale: colors first (small, immediately visible, zero wire
|
||
risk), then interface text (new presentation subsystem), then side
|
||
channels (wire work, needs the connected gate), then command breadth,
|
||
then closeout. Slices touching shared chat files run serially — one
|
||
implementer per slice against a pinned contract (per
|
||
`feedback_dont_parallelize_coupled_plan_slices`).
|
||
|
||
- **CH1 — exact color table.** Conformance test pins every enum entry to
|
||
its retail RGBA; `RetailChatColor` corrected; register rows for any kind
|
||
we cannot yet receive on the wire.
|
||
- **CH2 — on-screen interface text.** New presentation owner (App layer,
|
||
retained-UI or TextRenderer HUD path per research recommendation);
|
||
routing per retail's table; client-raised local errors ported at their
|
||
retail raise sites; keystone-owned presentation unknowns get register
|
||
rows.
|
||
- **CH3 — side channels.** Fix list from R4; connected two-way gate
|
||
against local ACE (send + receive per family); self-echo semantics per
|
||
`ChatChannelInfo.IsSelfEchoChannel`.
|
||
- **CH4 — command registry completion.** Catalog conformance test pins
|
||
acdream's table against the retail registry enumeration; missing
|
||
commands implemented family-by-family.
|
||
- **CH5 — closeout.** Register sweep, ledger flip, ISSUES updates,
|
||
in-client test script for the user gate.
|
||
- **CH6 — chat-window shell parity — all three sub-slices CODE-COMPLETE
|
||
2026-08-10, pending the connected user gate (filed 2026-08-09 at user
|
||
gate round 1; research complete:
|
||
`docs/research/2026-08-09-chat-retail-window-shell.md`).** Three
|
||
sub-slices:
|
||
- **CH6a — correct main-window import + resize.** Swap the wrong
|
||
LayoutDesc `0x21000006` for retail's `0x2100006F` and delete the
|
||
downstream compensations (hand-cropped 490px ContentWidth, dropped
|
||
800px resize bar, the 9px transcript patch, orphan-sibling pruning);
|
||
teach `LayoutImporter` element type 9 (`UIElement_Resizebar`, 8
|
||
authored grips: 4 edges + 4 corners via bools 0x2A–0x2D); fix the
|
||
resize mask that excludes Top. Expected to also clear the round-1
|
||
artifact report. **User gate round 2 (2026-08-09) added two more
|
||
symptoms to fold into this same slice:** resize has no diagonal
|
||
(corner) cursor feedback — only edge cursors show, the 4 corner grips
|
||
from the 8-grip authored set above have no matching cursor affordance
|
||
yet; and the window cannot grow in the Y axis when dragging from the
|
||
bottom-right corner (a corner-grip axis-composition bug, likely the
|
||
same resize-mask gap already scoped for the Top edge above, now
|
||
confirmed to also affect corner grips specifically).
|
||
**CODE-COMPLETE `1fd51543`.** The wrong layout swap, the 8-grip
|
||
`LayoutImporter`/`UiResizeGrip` port, and both round-2 symptoms landed
|
||
together: the real DAT ground-truth (`0x2100006F` lives in
|
||
`dats.Local`, not `dats.Portal`) showed the top strip is authored as a
|
||
Type-2 Dragbar, not an 8th Resizebar grip as the research doc's
|
||
decomp-only reading assumed — `UiRoot` now gives a directly-hit grip's
|
||
own edges priority over its generic proximity heuristic, and a
|
||
directly-hit move handle the same priority over ambient proximity, so
|
||
the plain top strip moves the window while its two corners (which ARE
|
||
grips) resize it including the Y axis, matching retail exactly. The
|
||
diagonal-cursor gap needed no new cursor plumbing —
|
||
`CursorFeedbackController`/`RetailCursorCatalog` already had the exact
|
||
DAT-matching Type-9 cursor ids pinned; they just never received a
|
||
genuine diagonal edge combination before. The corner-grow gap
|
||
dissolved with the `RetailWindowChrome.Imported` mount (0x2100006F's
|
||
own border art IS the window chrome — no nine-slice wrapper, no
|
||
content crop, frame==content) using the DAT's real
|
||
minH=100/maxH=2000/minW=300/maxW=2000. Register row AP-185 files the
|
||
one accepted simplification (the `_Locked` cosmetic border-art swap on
|
||
`UiLocked` is not ported; the live grip skin shows unconditionally).
|
||
- **CH6b — floating windows 1–4.** Mount `0x2100005B` ×4 as
|
||
always-resident children per `gmGamePlayUI::SetupChildren
|
||
@0x004E9EC0` (ids 0x10000505/0x1000050E/0x1000050F/0x10000510);
|
||
keybind actions `ToggleFloatingChatWindow1..4` (retail defaults at
|
||
`retail-default.keymap.txt:150-153`) through the dispatcher; the
|
||
main window's 1/2/3/4 buttons are one-directional state mirrors
|
||
(`gmMainChatUI::RecvNotice_SetPanelVisibility @0x004CCD80`), not
|
||
the toggle; per-window PostInit filter defaults (color research §4)
|
||
with the `windowId == m_eWindowID OR (windowId==0 && TypeIsActive)`
|
||
display rule.
|
||
**CODE-COMPLETE 2026-08-10 (this commit — hard constraint: no
|
||
subagents, no client launches).** `ChatWindowState` (Runtime, borrowed
|
||
from `RuntimeCommunicationState.ChatWindows`) owns the exact
|
||
PostInit-default filters + open flags for ids 0-4 and the full
|
||
`ShouldDisplay(windowId, targetWindowId, logTextType)` predicate;
|
||
`FloatingChatWindowController` (new sibling to `ChatWindowController`,
|
||
sharing the wrap/color algorithm via the new `ChatTranscriptRenderer`)
|
||
binds all four windows, each importing its own widget tree from one
|
||
shared `0x2100005B` `ElementInfo` parse. The keymap default confirmed
|
||
**Alt+1..4** (`MetaKeys` index 3 = `0x00000004`, cross-checked against
|
||
the file's own Alt+A/D strafe and Alt+Enter/Tab/F4 rows — `KeyBindings`
|
||
already carried this binding since Phase K.1c). A direct decomp read of
|
||
`gmMainChatUI::ListenToElementMessage @0x004CDA80` (the only function
|
||
in the binary that branches on a click message) settled the
|
||
button-mirror-vs-toggle question the research doc had left as a
|
||
hedge: **the four indicator buttons carry NO click handler in
|
||
retail** — `ChatWindowController.SetIndicatorOpen` ports this as a
|
||
pure one-directional mirror, no `OnClick`. `RetailUiRuntime.
|
||
OnWindowVisibilityChanged` is the single chokepoint that both syncs
|
||
`ChatWindowState.SetOpen` and calls the indicator mirror, regardless
|
||
of what changed a window's visibility (keybind, close button, or a
|
||
restored layout). Geometry + open/visible persist for free through
|
||
the existing `RetailWindowLayoutPersistence` path once each window
|
||
registers under its own `WindowNames` entry; the filter masks get a
|
||
dedicated local `ChatSettings` round-trip (register row AP-187 — no
|
||
retail `0x1000008C` wire yet). One approximation, register row
|
||
AP-188: a floaty window's entry field always sends on `Say` (no
|
||
talk-focus menu is authored on `0x2100005B`, and whether retail's
|
||
ACTUAL send path reads a per-window or a shared globally-current
|
||
channel is unconfirmed). Full Release suite 12,392 passed / 4
|
||
skipped / 0 failed.
|
||
- **CH6c — opacity.** Implement `UiRenderContext.AlphaMod`
|
||
consumption (whole-composited-window alpha per
|
||
`ChatInterface::SetOpacity @0x004F3120`); the two GLOBAL retail
|
||
options `0x10000080` (unfocused) / `0x10000081` (focused,
|
||
active >= default); retail defaults now, the user-facing Settings
|
||
slider in the settings track. Persistence: local settings first —
|
||
the retail per-window option array `0x1000008C` is stored by ACE as
|
||
an opaque byte[] it never parses, so the wire format is its own
|
||
deferred slice.
|
||
**CODE-COMPLETE 2026-08-10 (hard constraint: no subagents, no client
|
||
launches).** The sprite/rect chokepoint (`UiRenderContext.ApplyAlpha`) had
|
||
quietly existed since `1da697ec`, well before CH6 — the gap was narrower
|
||
than the plan assumed: `DrawStringDat`/`DrawString` still passed
|
||
`applyAlpha: false`, so text stayed sharp over a translucent window;
|
||
CH6c routes both through `ApplyAlpha` too, matching retail's
|
||
whole-surface `SetOpacity` fade. `RetailWindowOpacityController`
|
||
(new, `src/AcDream.App/UI/RetailWindowOpacityController.cs`) subscribes to
|
||
a new `RetailWindowManager.WindowRegistered` event and drives every
|
||
registered window's live `Opacity` from keyboard-focus state — deliberately
|
||
EVERY window (chat, floaties, vitals, toolbar, ...), not just retail's
|
||
`ChatInterface`-scoped mechanism (register row AP-190, retiring the stale
|
||
AP-40 "fixed 0.75, no focus transition" row in the same commit). Verified
|
||
retail defaults from the decomp (constructor-literal, no cdb needed):
|
||
the base `ChatInterface` ctor sets DefaultOpacity=0.5/ActiveOpacity=1.0,
|
||
which the four floating windows keep unmodified, but `gmMainChatUI`'s own
|
||
ctor overrides the main window to 1.0/1.0 (always fully opaque); acdream
|
||
ships ONE shared global default (the base 0.5/1.0) rather than
|
||
replicating the per-class override — also AP-190. The linking invariant
|
||
(raising default above active drags active UP; lowering active below
|
||
default drags default DOWN — never a clamp) is decomp-verified and ported
|
||
as `ChatOpacityLink` in `AcDream.UI.Abstractions`, shared by the live
|
||
controller and the Settings → Chat tab's two new linked sliders
|
||
(`SettingsPanel.RenderChatTab`). Persistence: `ChatSettings.DefaultOpacity`/
|
||
`ActiveOpacity` round-trip through `SettingsStore`; Save pushes both
|
||
through `IRuntimeSettingsTargets.SetChatOpacity` into the live controller —
|
||
no restart. Rider (CH6a/b re-review): strengthened the grip-media
|
||
regression guard past a bare `SpriteFile != 0` check —
|
||
`ChatLayoutConformanceTests` now drives each live grip through a real
|
||
`UiRenderContext`/`TextRenderer` (backed by the in-memory
|
||
`RecordingGpuDevice` test double) and asserts the draw call chain actually
|
||
queued sprite geometry, via a new `TextRenderer.DebugSpriteSegments`
|
||
test-only accessor. Full Release suite 12,459 passed / 4 skipped / 0
|
||
failed (baseline 12,420/4/0).
|
||
|
||
## Gates
|
||
|
||
- Per slice: `dotnet build` green, full Release suite green, conformance
|
||
tests, register rows same-commit, Opus dual review resolved.
|
||
- Campaign: user in-client gate — colors side-by-side vs the retail
|
||
client, each side channel spoken + heard, on-screen text provoked live
|
||
(jump in air), command spot-checks. Test script delivered at CH5.
|
||
|
||
## Ledger
|
||
|
||
| Slice | Commit | Suite | Review | User gate |
|
||
|---|---|---|---|---|
|
||
| R1–R4 research | `see docs/research/2026-08-09-chat-retail-*` | — | — | — |
|
||
| CH1 colors | `172c6f9a` | 11,835 passed / 4 skipped / 0 failed | APPROVE-WITH-FIXES; fixed `34d8a3c0` | pending |
|
||
| CH2 interface text | `77c8296e`, reworked `e0e78883` | 11,916 passed / 4 skipped / 0 failed | REJECT → reworked `e0e78883` → re-review APPROVE-WITH-FIXES → nits `233c30d1` | pending |
|
||
| CH3 side channels | `614a1e05` | 11,964 passed / 4 skipped / 0 failed | APPROVE-WITH-FIXES; fixed `e07fba57` | pending (connected gate — see handoff below) |
|
||
| CH4 commands | `090825e7` | 12,221 passed / 4 skipped / 0 failed | REJECT; fixed `724ef2d3`; re-review APPROVE-WITH-FIXES; closed `5d247d55` | pending |
|
||
| CH5 closeout | `38f0c0de` | — (docs/memory only, no build) | — | pending (connected gate — see test script) |
|
||
| User gate round 1 | `47e40900` | 12,221 passed / 4 skipped / 0 failed (baseline; items A–G fixed this commit) | — | items A–G user-gate round 1 fixed; ten findings total, see "User gate — round 1" below |
|
||
| CH6a main-window layout + 8-grip resize | `1fd51543` | 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 | `c1f15825` | 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`, reworked `1aa77099` | 12,420 passed / 4 skipped / 0 failed | REJECT (docs/research/2026-08-10-ch6ab-review-findings.md, BLOCKER 1) → reworked `1aa77099` | 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 | `98de4f5a` | 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 1–4 | `22020ef2`, reworked `1aa77099` | 12,420 passed / 4 skipped / 0 failed | REJECT (docs/research/2026-08-10-ch6ab-review-findings.md) → reworked `1aa77099` — 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 | `a819687c` | 12,459 passed / 4 skipped / 0 failed | APPROVE-WITH-FIXES; fixed this commit — BLOCKER (out-of-box `DefaultOpacity` 0.5→1.0), AP-190 reworded + two new decomp-verified clauses (retail's per-tick ease, retail's entry-field-specific focus predicate), NITs (`UiElement.cs` stale comment, `WindowUnregistered` detach, post-Dispose `Set*` guards, `DrawString`/outline-pass alpha tests) | pending — needs the next connected round for visual confirmation (window fade on focus change, Settings slider live-apply) |
|
||
| Goal-window #363/#367 interface-text seam | `09453eca` | 12,542 passed / 4 skipped / 0 failed | implementer-only, no subagent review this session (hard constraint) | pending — needs the next connected round to confirm the SpewBox now flashes for the reclassified refusals (see "Goal-window follow-up" below) |
|
||
| Consolidated review — retail `/help` Detail extraction, seam wiring test | `f7a6f46b` | 12,553 passed / 4 skipped / 0 failed | APPROVE-WITH-FIXES; fixed this commit — SHOULD-FIX 1 (42 of 47 catalog leaf verbs given retail Detail_HelpType text, 4 confirmed-null, 1 honest UNVERIFIED, retiring the class doc's overclaim), SHOULD-FIX 2 (`ComposedChatViewModelWiresOnInterfaceTextToSpewBox` — the a5a7eb4f defect class had no test), SHOULD-FIX 3 (AP-113 RETIRED — Lifestone's and Marketplace's own bespoke bad-args refusal text recovered byte-exact), SHOULD-FIX 4 (register header's stale 0.5/1.0 sentence corrected), NITs (a)-(d) (`HeadlessDiagnosticWriter` instead of `Console.WriteLine`, bounded non-quiescent-pump liveness diagnostic, hydration test doc-comment contradiction, 0x26 fallback dispatches on its own `Type`) | pending — needs the next connected round to confirm `/help <verb>` now shows retail's exact wording (see "Consolidated review" below) |
|
||
| User gate round 4 — items 3+5 (no meta text, indicator click-toggle) | `5b54387b` | 12,579 passed / 4 skipped / 0 failed | implementer-only, no subagent review this session (hard constraint) | pending — needs the next connected round to confirm `/help channels`/`chatting`/`commands`/`messagetypes` show complete retail text and clicking each indicator button toggles its floating window (see "User gate — round 4" below) |
|
||
| User gate round 4 — items 1+2 (retail two-plane glyph outline, authored SpewBox/chat styles) | this commit | 12,610 passed / 4 skipped / 0 failed | implementer-only, no subagent review this session (hard constraint) | pending — needs the next connected round to confirm the SpewBox's heavy black border and the chat transcript's softer default shade (see "User gate round 4, items 1+2" below) |
|
||
|
||
### CH4 closeout (2026-08-09)
|
||
|
||
Full parser-semantics + catalog-breadth pass against
|
||
`docs/research/2026-08-09-chat-retail-command-registry.md`'s complete
|
||
130-registered + 22-fallback = 152-verb enumeration.
|
||
|
||
**A. Parser semantics** (`ChatInputParser.cs`, `RetailClientCommandCatalog.cs`,
|
||
`ChatCommandRouter.cs`):
|
||
|
||
1. `:`/`;` emote-prefix rewrite (`OnChatCommand` cases 0x0B/0x0C) — both
|
||
prefixes rewrite identically to `@emote <rest>`.
|
||
2. Verb trailing-comma trim (`DoCommand`'s right-trim) applied at every
|
||
verb-lookup site in both the catalog and the parser — `"@f, hi"` ≡
|
||
`"@f hi"`.
|
||
3. `@tell`/aliases now split the target on the FIRST COMMA, not the first
|
||
whitespace token — `"@tell Aunt Agatha, hello"` addresses "Aunt
|
||
Agatha" (previously truncated to "Aunt"). Falls back to the pre-CH4
|
||
whitespace+punctuation-strip split when no comma is present, so
|
||
existing single-word-target muscle memory still works.
|
||
4. The 22 unregistered `ChannelSystem::GetChannelID` fallback tags
|
||
(`av`, `admin`, `sentinel`, `celestialhand`, …) now broadcast for real
|
||
via a new `RetailChannelTagTable` + `SendRawChannelCmd` bypass path,
|
||
reusing the existing `BuildChatChannel` wire builder — no new opcode
|
||
needed.
|
||
|
||
**B. Binding corrections:**
|
||
|
||
5. `/g`/`/group`/`/party` → Fellowship (0x800), not General — the live
|
||
correctness bug the doc flagged as Tier-1 #1.
|
||
6. `/rp` → reply alias (confirmed by retail's own help text, "You may
|
||
also use @r or @rp"), not Roleplay. Roleplay keeps `roleplay`/`crp`;
|
||
the non-retail `/role` invention is deleted.
|
||
7. `/allegiance`/`/all` are now `RetailClientCommandCatalog`'s allegiance
|
||
MANAGEMENT command (a new `TryMatchAllegiance` dispatcher), not a
|
||
channel verb. The channel-send verbs stay `a`/`ab`/`guild`/`gu`.
|
||
**Corrected 2026-08-09 at the CH4 REJECT-review (Blocker 1): the
|
||
original implementation above only claimed ownership for the 2 ported
|
||
subcommands (`info`/`hometown`/`ho`) and let every OTHER subcommand
|
||
fall through the unregistered-tag channel-fallback path, which
|
||
broadcast the raw subcommand text to the Allegiance chat channel
|
||
(`0x02000000`) — a real chat-visible bug (`@allegiance boot Bob` sent
|
||
"boot Bob" to allegiance chat). Retail's own `DoAllegiance` claims the
|
||
ENTIRE verb unconditionally: an unrecognized subcommand prints "Please
|
||
see @help Allegiance for more information on how to use this command."
|
||
locally and never reaches `DoChannelCommand` or the server.
|
||
`TryMatchAllegiance` now matches this exactly — it always returns
|
||
ownership for `allegiance`/`all`, showing retail's refusal text for
|
||
any subcommand beyond the 2 ported ones.**
|
||
8. `/house`/`/hou` no longer swallows unrecognized subcommands with a
|
||
local usage error — `TryMatchHouse` returns no match for anything
|
||
beyond `recall`/`re`/`mansion_recall`/`alleg_recall`/`ma`/`abandon`,
|
||
letting it reach ACE.
|
||
9. `@mr`/`@pr` pinned as permanently non-executable
|
||
(`MrPr_AreNeverExecutable` test) — retail registers them with a NULL
|
||
function pointer; they must never resolve in
|
||
`RetailClientCommandCatalog` or `ChatInputParser`.
|
||
|
||
**C. New verbs implemented** (real local execution, not passthrough):
|
||
`endurance`, `speaker`, `title` (silent — AP-182, no chrome yet), `chat`,
|
||
`notell`, `join`, `leave`, `permit`, `hslist`, `index`, `clist`, `on`,
|
||
`off`, `alh`/`ah` (+ `@allegiance hometown`/`ho`), `@allegiance info`,
|
||
`@house abandon`; missing-alias sweep (`pkl`, `hou`, `message_types`,
|
||
`msgtypes`, `msg_types`, `rt`, `send`, `whisper`, `w`, `vassal`,
|
||
`covassal`, `co-vassals`, `c`, `fellows`, `group`, `party`, `guild`,
|
||
`gu`, `cg`, `ct`, `clfg`, `crp`, `soc`, `o`, `ab` (already CH3)); the
|
||
non-retail inventions `gen`, `cv`, `lookingforgroup`, `tr`, `role`, `h`
|
||
are deleted. New Core.Net wire builders: `IndexChannels`/`ListChannels`/
|
||
`AddChannel`/`RemoveChannel`/`RecallAllegianceHometown`/
|
||
`AllegianceInfoRequest`/`ListAvailableHouses`/`AddPlayerPermission`/
|
||
`RemovePlayerPermission`/`AbandonHouse` — all parameterless or
|
||
single-field payloads cross-checked against ACE's GameAction readers
|
||
(`references/ACE/Source/ACE.Server/Network/GameAction/Actions/*.cs`), not
|
||
guessed. **Deferred, filed as issues #360/#361/#362 + register rows
|
||
TS-68/TS-69/TS-70:** the ~22 remaining allegiance/house subcommands + the
|
||
standalone `@motd`, the three still-inert pure-local commands
|
||
(`day`/`log`/`render`), and the four unparsed inbound GameEvent responses
|
||
for the new outbound requests.
|
||
|
||
**D. Conformance:** `RetailCommandRegistryConformanceTests` (new,
|
||
`tests/AcDream.UI.Abstractions.Tests/Panels/Chat/`) enumerates all 152
|
||
verbs from the registry doc, transcribed and cross-checked against the
|
||
doc's own per-section counts (130 = 9+31+20+14+6+7+17+8+18 by section;
|
||
22 fallback tags; totals self-consistent). Per-verb theory test asserts
|
||
Implemented verbs resolve through exactly one of
|
||
`RetailClientCommandCatalog`/`ChatInputParser`/`RetailChannelTagTable`,
|
||
and HelpOnly/ServerPassthrough verbs resolve through NONE of them (so
|
||
they provably fall to ACE passthrough). Two reverse-direction tests
|
||
enforce the ownership rule: nothing in `RetailClientCommandCatalog.
|
||
KnownVerbs` or `ChatInputParser.KnownVerbs` may exist outside this
|
||
registry — a future invented alias fails the build immediately. Final
|
||
tally: **138 Implemented / 5 ServerPassthrough / 9 HelpOnly = 152.**
|
||
|
||
Suite: 12,190 passed / 4 skipped / 0 failed (Release), up from CH3's
|
||
11,964/4/0 — net +226 tests. (Corrected 2026-08-09 at the CH4
|
||
REJECT-review, item 8: this paragraph originally read "12,026 ... net
|
||
+62 (157 new conformance-family cases plus net test churn)"; the actual
|
||
measured CH4-landing count was 12,190, matching CLAUDE.md's Current
|
||
Suite baseline — only the raw counts are corrected here, the +62/157
|
||
breakdown was not re-derived.) One pre-existing,
|
||
environment-specific Debug-only failure
|
||
(`LandblockBuildOriginTests.FarLoad_StripsEnvCellsAndPhysicsEvenWhenEntityListIsAlreadyEmpty`)
|
||
was confirmed present on the unmodified baseline via `git stash` before
|
||
and after this slice's changes — passes in Release, unrelated to chat.
|
||
|
||
### CH4 REJECT-review fixes (2026-08-09)
|
||
|
||
Two blockers, seven should-fixes, and six nits from the CH4 review landed:
|
||
|
||
**Blockers:** (1) `@allegiance <sub>` for an unrecognized subcommand was
|
||
broadcasting the raw subcommand text to the Allegiance chat channel
|
||
(`SendRawChannelCmd(0x02000000, ...)`) because `TryDispatchChannelFallback`
|
||
only guarded on `ChatInputParser.IsKnownVerb`, and `/allegiance` had been
|
||
deleted from that parser at CH4. Fixed at both ends: `TryMatchAllegiance`
|
||
now claims ownership of `allegiance`/`all` unconditionally (matching
|
||
retail's own `DoAllegiance`, which never falls through to
|
||
`DoChannelCommand`) and shows retail's own "Please see @help Allegiance
|
||
for more information on how to use this command." refusal client-side;
|
||
`TryDispatchChannelFallback` also gained a blanket
|
||
`RetailClientCommandCatalog.KnownVerbs` ownership guard as defense in
|
||
depth for the rest of the catalog. (2) `@house abandon` sent `0x021F`
|
||
immediately with zero confirmation; retail's `DoHouse` abandon branch runs
|
||
a real two-stage dialog ("Do you really want to abandon your house? ..."
|
||
then "Are you absolutely certain you wish to abandon your house? Click
|
||
yes only if you are sure!") before `Event_AbandonHouse()`.
|
||
`ClientCommandController`'s `HouseAbandon` case now chains two
|
||
`ShowConfirmation` calls with retail's verbatim text; `AbandonHouse` only
|
||
fires after both accepts.
|
||
|
||
**Should-fixes:** a bare unregistered tag with no text (`@admin`) now
|
||
passes through to the server silently, matching retail's `DoChannelCommand`
|
||
returning 0 on `argc<=0`, instead of showing "You must specify the text
|
||
you wish to say!" (that string belongs to the registered-verb-only
|
||
`DoStupidChannelHack`); `@join`/`@leave` now update
|
||
`RuntimeCharacterOptionsState` locally (a new `SetOptionBit` method)
|
||
before the wire push, so `TurbineChatMembershipGate` stops refusing a
|
||
just-joined room without waiting on a fresh `PlayerDescription`;
|
||
`@permit add/remove` now accepts a multi-word name (`>= 2` tokens,
|
||
joins the remainder, matching retail's `JoinArgsAsName`); `@clist`/`@on`/
|
||
`@off` now validate only argument SHAPE (exactly one token) at the
|
||
catalog layer and raise `WeenieError 0x422` ("That channel doesn't
|
||
exist.") for an unresolved tag, instead of silently doing nothing;
|
||
`@mr`/`@pr`'s help text is now the verbatim retail strings from
|
||
`data_7daa08`/`data_7daa80` (previously fabricated acdream summaries),
|
||
and the class doc no longer overclaims every table entry is verbatim
|
||
(the ~35 channel one-liners are acknowledged as acdream summaries);
|
||
issues #360 and register row TS-68 corrected — `@house`'s unported
|
||
subcommands still reach ACE, but `@allegiance`'s now correctly stay
|
||
client-side; the campaign doc's own B.7 note and `RetailChannelTagTable`'s
|
||
stale "IsKnownVerb intercepts them first" comment are corrected to
|
||
describe the catalog-ownership interception path; the ledger suite counts
|
||
above are corrected from a stale 12,026 to the actual 12,190. The
|
||
error-typing debt (~10 new refusal sites at `ClientLocal 0x00` where
|
||
retail types several `0x1A`, plus `DoCommand`'s real `HandleFailureEvent
|
||
(0x26)` bad-args response) was deliberately NOT re-plumbed — filed as
|
||
issue #363 and register row AP-183, CH5-or-later.
|
||
|
||
**Nits:** `TryMatchHouse`'s doc comment no longer describes a
|
||
local-swallow path that doesn't exist in the code; AP-182 and the
|
||
`SetChatTitle`/`@title` comments across three files no longer claim the
|
||
value is "stored" (the binding is `_ => { }`, a pure no-op) and AP-182
|
||
now lists `DoTitle`'s three omitted failure messages; `RetailChannelTagTable
|
||
.IsUnregisteredFallbackTag` now excludes by TAG STRING instead of channel
|
||
ID, fixing a false-positive on `"olthoi"` (which shares an id with the
|
||
genuinely-unregistered `"ol"` but has its own registered Turbine verb);
|
||
two binding-level conformance pins (`/g`→Fellowship `0x800`, `/rp`→reply)
|
||
were added to `RetailCommandRegistryConformanceTests` so a rebind
|
||
regression fails there, not just a narrower parser test; `@index foo` is
|
||
now accepted (retail's `DoChannelIndex` ignores argc); an ISSUES.md note
|
||
records the six invented verbs (`gen`/`cv`/`lookingforgroup`/`tr`/`role`/`h`)
|
||
removed at CH4 for registry parity.
|
||
|
||
Suite: 12,216 passed / 4 skipped / 0 failed (Release), up from CH4's
|
||
12,190/4/0 — net +26 tests (new/expanded theory cases across
|
||
`ChatCommandRouterTests`, `RetailClientCommandCatalogTests`,
|
||
`RetailCommandRegistryConformanceTests`, `ClientCommandControllerTests`,
|
||
`RuntimeCharacterStateTests`, and `TurbineChatMembershipGateTests`; no
|
||
tests removed, several renamed/retargeted in place).
|
||
|
||
### CH4 re-review fixes — closed at `5d247d55` (2026-08-09)
|
||
|
||
A second, smaller review round found three more should-fixes after
|
||
`724ef2d3` landed: (1) `RetailDialogFactory.CloseDialog`'s queued branch
|
||
ran `DialogDone` — whose callback can synchronously reopen a new dialog
|
||
under the SAME queue key, exactly what the two-stage house-abandon
|
||
confirmation does — then called `OpenNextDialog`, which did an
|
||
unconditional `Dictionary.Add` on a key the reentrant dialog had already
|
||
re-occupied; retail's `HashTable::add` tolerates the duplicate, `Dictionary`
|
||
throws. `OpenNextDialog` now returns early when the queue key is already
|
||
active. (2) `@join`/`@leave` wrote the local `RuntimeCharacterOptionsState`
|
||
bit before sending, but the Settings Chat toggles reached a second binding
|
||
(`SendSingleCharacterOption`) that only sent the wire message, leaving the
|
||
Turbine membership gate stale until the next `PlayerDescription`;
|
||
`LiveSessionRuntimeFactory.CreateCommandBindings` now shares one local
|
||
function for both entrances. (3) TS-68/#360's wording was corrected again —
|
||
retail's `DoAllegiance` dispatcher table EXECUTES the nine
|
||
boot/ban/officer/title/motd/name/lock/house/chat/broadcast subcommands
|
||
locally through their own handlers; acdream shows the unrecognized-
|
||
subcommand refusal for all nine pending the #360 port. What matches
|
||
retail is the ownership rule (the verb never reaches
|
||
`DoChannelCommand`/the server), not the subcommand behavior itself — the
|
||
inaccurate "now matches this" claims were removed from both the register
|
||
row and the issue.
|
||
|
||
Suite: 12,221 passed / 4 skipped / 0 failed (Release), up from CH4's
|
||
12,216/4/0 — net +5 tests, no removals. This is the CH4 slice's final
|
||
number; the ledger row above and CLAUDE.md's Current Suite baseline both
|
||
carry it.
|
||
|
||
### CH3 closeout handoff (2026-08-09)
|
||
|
||
All nine steps of the research doc's §6 fix list landed:
|
||
|
||
1. **The false "ACE doesn't run a TurbineChat server" claim retracted** in
|
||
`docs/ISSUES.md` (#19) and `docs/plans/2026-04-11-roadmap.md` (I.6, ×2).
|
||
2. **`TurbineChatMembershipGate`** (new, `AcDream.Runtime.Gameplay`) ports
|
||
retail's `SendTurbineChat @0x0057db10` local pre-send gate — Turbine
|
||
off/room-0 → `"Turbine chat is not available."`; Hear-option off →
|
||
`0x0551 YouAreNotListeningTo_Channel` — both raised through
|
||
`RuntimeCommunicationState.AddText`. Wired into BOTH
|
||
`LiveSessionCommandRouter` (graphical) and `DirectGameRuntimeCommandAdapter`
|
||
(headless) so the two hosts can't diverge. `RuntimeCharacterState.IsOlthoiPlayer`
|
||
added (heritage-gated, not an option) for the Olthoi room.
|
||
3. **`SetSingleCharacterOption (0x0005)`** implemented end to end (codec,
|
||
`WorldSession.SendSetSingleCharacterOption`, `IRuntimeCharacterCommands.
|
||
SetSingleOption`, both adapters, `LiveSessionCommandRouter` registration)
|
||
and wired to the 5 Settings Chat toggles via `RuntimeSettingsController.
|
||
SaveChat` (publishes only the CHANGED bits) through a hoisted
|
||
`LiveSessionCommandSurface` now shared with `RuntimeSettingsTargets`.
|
||
No 6th (Allegiance) toggle was added — `ChatSettings` has never had one
|
||
and retail's own Settings UI was not confirmed to have one either; flagged
|
||
for the user rather than guessed.
|
||
4. **`ChatSettings` seeded from server truth** — `RuntimeSettingsController.
|
||
SyncChatFromServerOptions` reseeds both the persisted snapshot and any
|
||
live unsaved draft from `CharacterOptions2` whenever a fresh
|
||
PlayerDescription lands (`LiveCharacterSessionBindings.
|
||
OnCharacterOptionsChanged`, new optional hook).
|
||
5. **Self-echo double-print fixed** — `LiveSessionCommandRouter.
|
||
RouteLegacyChannel` now consults `ChatChannelInfo.Legacy(...).
|
||
IsSelfEchoChannel()`; Fellow/Vassals/Patron/Monarch/CoVassals skip the
|
||
local echo (server resends with `""` sender), AllegianceBroadcast/Say/Tell
|
||
keep it. Existing `TellAndLegacyChannel_PreserveOutboundAndEchoPolicy`
|
||
test corrected to the fixed (single-print) expectation.
|
||
6. **TurbineChat ack HResult surfaced** — `LiveSessionEventRouter.
|
||
RouteTurbineChat` now switches on `Payload.Response{HResult}`; nonzero
|
||
surfaces as a system chat line, zero (the common case) stays silent
|
||
matching retail.
|
||
7. **`/a` routes to Turbine unconditionally** — `ChatChannelKind.Allegiance`
|
||
is exhaustively dispatched to the Turbine pipeline (never falls through to
|
||
legacy); a new `ChatChannelKind.AllegianceBroadcast` + `/ab` verb owns the
|
||
legacy `0x02000000` path retail's own `@ab` verb uses.
|
||
`/allegiancebroadcast` was deliberately NOT added — the retail command
|
||
registry (`docs/research/2026-08-09-chat-retail-command-registry.md`)
|
||
only has `ab`, not that long form.
|
||
8. **Malformed builders resolved** — `SocialActions.BuildSetCharacterOptions`
|
||
(0x01A1, no caller), `BuildAddChannel`/`BuildRemoveChannel` (0x0145/0x0146,
|
||
wrong payload type, no caller) DELETED along with their entire call chain
|
||
(`WorldSession.SendSetCharacterOptions`, `IRuntimeCharacterCommands.
|
||
SetOptions1`, `SetCharacterOptionsRuntimeCmd`) — replaced by
|
||
`SetSingleCharacterOption`, the message step 3 actually needed.
|
||
9. **Register + memory** — AP-181 (no client-side chat spam throttle) and
|
||
UN-9 (an incidentally-discovered, unexplained one-byte
|
||
`CharacterOptions1.Default` mismatch vs ACE's own literal — not
|
||
investigated further, flagged for a future pass) filed in
|
||
`docs/architecture/retail-divergence-register.md`.
|
||
`claude-memory/project_chat_pipeline.md` line ~111 corrected.
|
||
|
||
**Deviations from the literal ordered list:** none structural; the two
|
||
notes above (no 6th Allegiance toggle, no `/allegiancebroadcast` verb) are
|
||
scope-narrowing decisions made against the retail command registry and the
|
||
existing `ChatSettings` shape, not skipped work.
|
||
|
||
### CH3 Opus review fixes (2026-08-09)
|
||
|
||
The review found the CH3 closeout above had three items that no longer
|
||
match the fixed code — corrections, not a rewrite of the historical
|
||
record:
|
||
|
||
- **Item 5 is now WRONG for AllegianceBroadcast.** ACE's
|
||
`GameActionChatChannel` handler iterates `player.Allegiance.Members` —
|
||
the sender IS a member, so they get their own real-name line back
|
||
through the same broadcast, same as Fellow/Vassals/Patron/Monarch/
|
||
CoVassals (a different mechanism, same self-echo consequence).
|
||
`ChatChannelInfo.Legacy.IsSelfEchoChannel()` now returns `true` for
|
||
`0x02000000` too; `RouteLegacyChannel` skips the local echo for it.
|
||
- **Item 7 is now WRONG.** `/a` is NOT unconditionally Turbine. Retail's
|
||
base binding keeps it on the legacy `AllegianceBroadcast` bitflag until
|
||
`StartupTurbineChatSystem` successfully starts Turbine chat and rebinds
|
||
it (research doc §4.3). `LiveSessionCommandRouter.RouteChat` and
|
||
`DirectGameRuntimeCommandAdapter.TrySendChannel` now special-case
|
||
`TurbineChatState.Enabled == false` to fall back to the legacy send;
|
||
`Enabled == true` with `AllegianceRoom == 0` still correctly refuses
|
||
locally ("Turbine chat is not available.").
|
||
- **Item 9's UN-9 filing was a phantom.** ACE's own
|
||
`CharacterOptions1.cs:47` OR-sum is `0x50C4A54A` (confirmed by its own
|
||
inline comment, `// 1355064650`), identical to acdream's
|
||
`PlayerDescriptionParser.cs:217` — there was never a divergence. The
|
||
wrong literal `0x50C48D4A` existed only in
|
||
`docs/research/2026-08-09-chat-side-channels-vs-ace.md`. UN-9 is deleted
|
||
from the register; AP-181 is rewritten to name BOTH of retail's omitted
|
||
pre-send checks (`IsMessageSafe` silent-drop, THEN `IsMessageSpam`) and
|
||
no longer misattributes either to `RouteLegacyChannel`.
|
||
|
||
Also fixed this review: `ChatSettings.Default` now matches ACE's real
|
||
`CharacterOptions2.Default` (Roleplay/Society start OFF, not the
|
||
previously-claimed "all on"); `TurbineChatMembershipGate` reuses
|
||
`TurbineChatDisplayNames.Resolve` instead of a second name table; the
|
||
gate-result-to-refusal-text mapping is shared via
|
||
`TurbineChatMembershipGate.ResolveRefusalText` instead of being
|
||
duplicated in both hosts; the `CharacterOptionId` enum in
|
||
`SocialActions.cs` moved below the class so its doc comment re-attaches
|
||
correctly; `docs/plans/2026-04-11-roadmap.md` line ~948 got the same
|
||
TurbineChat-server retraction already applied at line ~429; and the
|
||
register's §3 header count was corrected from a pre-existing off-by-one
|
||
(129 claimed vs 128 actual `| AP-` rows).
|
||
|
||
**What the connected gate must verify (not run this session — build+test
|
||
only per the CH3 task's hard constraint):** General/Trade/LFG round-trip
|
||
send+receive; Roleplay is now silent-but-correctly-refused until the user
|
||
turns it on via Settings (then works); `/a` with and without an allegiance;
|
||
`/ab`; the legacy family no longer double-prints; the TurbineChat ack
|
||
HResult line never appears on an ordinary successful send.
|
||
|
||
## User gate — round 1 (2026-08-09)
|
||
|
||
The user tested CH5's CODE-COMPLETE build live and reported ten defects.
|
||
Items A–G are fixed in this same commit; the last three are out of this
|
||
round's scope and filed as slice CH6.
|
||
|
||
| # | User finding (condensed) | Disposition |
|
||
|---|---|---|
|
||
| A | Jumping while already airborne never shows retail's "You can't jump while in the air" refusal — the jump block only ever evaluates `input.Jump` inside the grounded-charge or already-charging branches. | **FIXED this SHA.** Rising-edge detection (`PlayerMovementController._prevJumpHeld`) reports `WeenieError.NotGrounded` once per press while airborne; holding the key or the grounded charge/fire path is unaffected. |
|
||
| B | Local system text shows an invented `"[System] "` prefix; retail prints it bare. | **FIXED this SHA.** `ChatVM.FormatEntry`'s `ChatKind.System` case now returns `entry.Text` unprefixed. `[Popup]` is unchanged (AP-175, a deliberate divergent marker). |
|
||
| C | The SpewBox's color doesn't match retail — the user recalls it as the same bright yellow as an incoming Tell. | **FIXED this SHA (color only).** `SpewBoxController.SpewBoxColor` is now the exact pinned value `(1, 1, 0.247, 1)` (`0x81C4C8`, same as `RetailChatColorTable.Yellow`). Size/position/font remain OPEN under register row AP-178 — the user reports all three still differ from retail; user gate round 1: differs, iterating. |
|
||
| D | The portal-space "In Portal Space - Please Wait..." text never shows, and when it does (via #329's 5-second delay) it's the wrong (white) color. | **FIXED this SHA, closes #329.** `PortalTunnelPresentation.TickRotation` now emits the notice unconditionally on every rotation-segment boundary, matching `gmSmartBoxUI::UseTime`'s decompiled `else`-arm exactly (no hold/threshold gate); `PortalWaitNoticeController` now renders it in the same pinned yellow as item C. Register row AP-150 retired. |
|
||
| E | `/hslist villas` (and the other three CH4-added request commands) is accepted server-side but produces no visible response. | **FIXED this SHA, closes #362.** New `ClientCommandResponses.cs` parses and renders `ChannelIndex`/`ChannelList`/`AvailableHouses`/`AllegianceInfoResponse`, each ported line-for-line from the named-retail decomp's inbound handlers. Register row TS-70 retired. |
|
||
| F | Multi-line server text (e.g. `/help`'s reply) doesn't split on embedded `\n` — "probably broken in many places." | **FIXED this SHA.** `ChatWindowController.WrapText` now splits on `\n`/`\r\n` first, then word-wraps each segment independently; the confirmed-correct single-line early-out is unchanged for text with no embedded newline. |
|
||
| G | The chat input line overflows the window's right edge when the window is resized. | **FIXED this SHA.** The input field's right edge no longer holds a fixed absolute pixel position across a resize (retail edge-mode 0's "frozen at current" fallback, or the `AnchorEdges` default with no `Right` bit) — `ChatWindowController.Bind` now upgrades it to retail edge-mode 1 (`UiLayoutPolicy`) or the equivalent `AnchorEdges.Right` stretch, so the right edge tracks every resize instead of only the bind-time/channel-change recompute. |
|
||
| H | Extra/duplicate chat windows appear on number keys 1/2/3/4. | **STILL CH6b** (not this commit) — retail's real floating windows 1–4 (`0x2100005B` ×4) and their `ToggleFloatingChatWindow1..4` keybinds are a separate slice; CH6a only fixed the shell (import/resize) of the main window. |
|
||
| I | Resizing the chat window only works from one corner, not every corner. | **FIXED at CH6a `1fd51543`.** Root cause: the main window imported the WRONG LayoutDesc (`0x21000006`, an unrelated layout whose root/resize-bar appear nowhere in the EoR gameplay UI) instead of retail's real `0x2100006F`; every symptom (crop hacks, the dropped resize bar, the one-corner-only resize) was downstream of that. The swap + a new `LayoutImporter` case for element type 9 (`UIElement_Resizebar`, `UiResizeGrip`) + `UiRoot` grip-priority hit-testing now resize from all 4 edges and all 4 corners, while the top strip (a Type-2 Dragbar, not a grip) correctly remains a move-only affordance. |
|
||
| J | The chat window has transparency issues / visual artifacts, and the user wants a transparency setting eventually. | **Artifacts FIXED at CH6a `1fd51543`** — the reported visual glitches were downstream of importing the wrong LayoutDesc (stray unparented siblings, the hand-cropped content width, the 9px patch); all retired with the correct import, and the two hard-coded translucent-black tints on the transcript/input are removed now that their parent panels draw their own authored background sprites. **Real opacity + the transparency SETTING landed at CH6c** — `UiRenderContext` now applies window alpha to sprite, rect, AND text draws; `RetailWindowOpacityController` drives every window's opacity from keyboard focus; the Settings → Chat tab carries two linked sliders. Pending the next connected round for visual confirmation. |
|
||
|
||
Findings A–G's evidence: this commit's diff + the new/updated tests in
|
||
`tests/AcDream.Runtime.Tests/Gameplay/PlayerMovementControllerTests.cs`,
|
||
`tests/AcDream.UI.Abstractions.Tests/ChatVMTests.cs`,
|
||
`tests/AcDream.App.Tests/UI/SpewBoxControllerTests.cs` (unchanged; verified
|
||
by inspection — no test pinned the old color),
|
||
`tests/AcDream.App.Tests/UI/PortalWaitNoticeControllerTests.cs`,
|
||
`tests/AcDream.Core.Net.Tests/Messages/ClientCommandResponsesTests.cs`,
|
||
and `tests/AcDream.App.Tests/UI/Layout/ChatWindowControllerTests.cs`. Full
|
||
Release suite green (see the commit message for the exact count). Items
|
||
H/I/J need the next visual round once CH6 lands; A–G still want a final
|
||
in-client eyes-on pass to confirm the fix reads correctly on screen (build
|
||
+ test green is necessary, not sufficient, for a presentation change).
|
||
|
||
## User gate — round 2 (2026-08-09)
|
||
|
||
The user tested round 1's fixes live and reported six more findings.
|
||
|
||
| # | User finding (condensed) | Disposition |
|
||
|---|---|---|
|
||
| 1 | Jumping while already airborne is STILL silent live — round 1's press-edge branch (`PlayerMovementController._prevJumpHeld`) has a passing unit test but never visibly fires in the running client. | **NOT REPRODUCED; NO SPECULATIVE FIX SHIPPED.** Exhaustive static re-audit of the whole live path (branch logic, `_body.OnWalkable`'s single writer — the quantum-loop resolve, driven only by real physics results, never anything else — the exact-once-per-frame `Update()`/`Capture()` call site, `TakeControlFromServer`'s edge-history reset scope, mouse-look's extra `Capture()` calls) found no bug: the mechanism is provably correct BY CONSTRUCTION from source. Attempted a live headless repro (new `jump-probe` bot policy exercising the SAME typed `commands.Movement.SetIntent` surface, real ACE connect at 127.0.0.1:9000) — blocked: `probeaccount2` connects but has NO CHARACTER ("no available characters on account"), and per the task's own constraint the fallback to `testaccount` is refused because the graphical client (PID confirmed running this session) owns that account. Left TWO temporary probes behind a `PhysicsDiagnostics`-family flag (`ACDREAM_PROBE_JUMP=1`, blocked entirely in Headless by the existing multi-session static-state guard, so this is a GRAPHICAL-client-only diagnostic for the next round): `[jump]` in `ReportJumpRefusal` (prints unconditionally, even when `OnInterfaceText` is null, to separate "branch never evaluated true" from "callback dropped it"), and `[jump-tick]` (per-tick trace bracketing every frame where Jump is/was held). Next round: launch with `ACDREAM_PROBE_JUMP=1`, reproduce, and read the console trace — it will show exactly which of the three jump branches fires and what `OnWalkable`/`_prevJumpHeld` were at that instant. |
|
||
| 2 | Retail shows "In Portal Space…" at the TOP of the screen, SMALL font, tell-yellow (the SpewBox) — acdream renders a big centered white/yellow overlay instead. | **FIXED this SHA.** Verified in the decomp: `gmSmartBoxUI::UseTime`'s notice emits via `ECM_UI::SendNotice_DisplayStringInfo(0x1A, ...)`, which forwards to `AddTextToScroll(str, 0x1A, 1, 0)` — type `0x1A` is HARDCODED to the SpewBox (`docs/research/2026-08-09-chat-retail-interface-text.md` §1.1/§4.2), the same surface every other `ClientLocal` refusal (jump-in-air, etc.) already uses. `PortalWaitNoticeController` (the dedicated centered-overlay presentation) and its lease are DELETED outright; `PortalTunnelPresentation`'s round-1 per-rotation-segment emission cadence is unchanged, now writing straight into `RuntimeCommunicationState.AddText(text, RetailLogTextType.ClientLocal)` — the SpewBox's own dedupe-at-index-0 (`SpewBoxState.Tick`) collapses the per-segment repetition exactly as retail's `gmSpewBoxUI::Update` does. No conflict with the decomp — the fix and the user's report agree exactly. |
|
||
| 3 | (Round 1 item B, `[System] ` prefix) | **CONFIRMED FIXED** — no regression, no further action. |
|
||
| 4+5 | `/help` output differs from retail; `/help death` prints an acdream META-MESSAGE ("This is a retail help-topic group; acdream has not yet extracted its exact retail listing text…"); spacing/alignment is off. | **FIXED this SHA via verbatim extraction, not authorship.** `tools/pdb-extract/sweep_weenie_strings.py` generalized to decode narrow `PStringBase<char>` literals (the Help* family's shape) alongside its original UTF-16LE support, then swept every `ClientCommunicationSystem::HelpXxxGroup` function's exact byte extent (read from the pseudo-C's own function-header addresses) against the PDB-paired `C:\Users\erikn\Downloads\acclient.exe` (verified MATCH). 4 of 7 groups (death/status/text/allegiances) are now COMPLETE verbatim listings — `/help death` now prints retail's real 8-line text, byte-exact including the retail-authentic trailing space on the `@day` line. The other 3 (channels/chatting/commands) delegate part or all of their detail text to `ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290`, which builds its output from BN-mislabeled data fragments around a live channel-name lookup — genuinely not decodable with confidence; each keeps its own verbatim summary line plus an explicit UNVERIFIED note citing the address, never a fabricated meta-message. 7 of the ~35 channel one-liners are also now verbatim (a/guild/gu, general/cg, trade/ct, lfg/clfg, roleplay/crp, society/soc, olthoi/o), each including retail's own "Also: @alias" text — this is also the alignment/spacing fix, since the fabricated summaries never matched retail's exact wording. Register row AP-184 filed; ISSUES.md #364 tracks the remaining 3-group gap. `RetailCommandHelpTableTests.cs` (new) pins every complete listing and the partial/UNVERIFIED shape byte-exact. |
|
||
| 6 | Resize is still buggy: missing diagonal (corner) cursor feedback, and the window cannot grow in the Y axis when dragging from the bottom-right corner. | **FIXED at CH6a `1fd51543`.** The 4 corner grips (`0x1000069B`/`D`/`F`, `0x100006A1`) decode retail's exact per-grip `BorderLocation` bools (`0x2A`-`0x2D`) and `UiRoot` gives a directly-hit grip's own edges priority, so `CursorFeedbackController`'s existing (already retail-pinned) `KindForResize` now actually receives a diagonal edge combination when hovering a corner — no new cursor ids were invented; `RetailCursorCatalog`'s pinned Type-9 cursor ids already matched the DAT exactly. The corner-grow gap traced to the same wrong-LayoutDesc root cause as item I: mounting through the universal nine-slice wrapper + a 490px content crop against 0x21000006's own (irrelevant) constraints created an indirection prone to stale-baseline growth bugs; 0x2100006F's root IS the complete retail chrome, so CH6a mounts it with `RetailWindowChrome.Imported` (no wrapper, no crop, frame==content) using the DAT's real `minH=100/maxH=2000/minW=300/maxW=2000`, collapsing the whole class. `ChatLayoutConformanceTests.MountedChatWindow_BottomRightGrip_GrowsBothAxes_NotOnlyShrinks` exercises the exact reported gesture end-to-end. |
|
||
|
||
Evidence for items 2, 4, and 5: this commit's diff + `tests/AcDream.UI.Abstractions.Tests/Panels/Chat/RetailCommandHelpTableTests.cs` (new) + the deletion of
|
||
`tests/AcDream.App.Tests/UI/PortalWaitNoticeControllerTests.cs` (its subject
|
||
class no longer exists) + `PortalTunnelAssetTests.cs` (unchanged; verified by
|
||
inspection — its `CreateRequired` call never passed the removed
|
||
`displayNoticeLifetime` parameter, so it needed no update). Item 1's evidence
|
||
is negative: no code changed in the jump branch logic itself, only
|
||
diagnostics; the two temporary probes and the `jump-probe` headless bot
|
||
policy are the round's deliverable for item 1, pending either a successful
|
||
next-round repro or a character created on `probeaccount2`. Full Release
|
||
suite green (see the commit message for the exact count).
|
||
|
||
Item 6's evidence landed in a LATER commit, the CH6a slice itself (see the
|
||
ledger row above): `src/AcDream.App/UI/UiResizeGrip.cs` (new),
|
||
`src/AcDream.App/UI/Layout/DatWidgetFactory.cs` (Type-9 factory case),
|
||
`src/AcDream.App/UI/UiRoot.cs` (grip-priority hit-testing in `OnMouseDown` /
|
||
`HoverResizeEdges`), `src/AcDream.App/UI/Layout/ChatWindowController.cs` and
|
||
`src/AcDream.App/UI/RetailUiRuntime.cs` (the `0x2100006F` swap + `Imported`
|
||
chrome mount), plus new/updated tests in
|
||
`tests/AcDream.App.Tests/UI/Layout/DatWidgetFactoryTests.cs`,
|
||
`tests/AcDream.App.Tests/UI/UiRootInputTests.cs`,
|
||
`tests/AcDream.App.Tests/UI/CursorFeedbackControllerTests.cs`, and
|
||
`tests/AcDream.App.Tests/UI/Layout/ChatLayoutConformanceTests.cs`.
|
||
|
||
## User gate — round 3 (2026-08-10)
|
||
|
||
The user tested round 2's fixes live and reported three more findings, all
|
||
presentation. Between rounds, the main session also root-caused round 2's
|
||
open item 1 (jump-in-air silence) from live probe evidence and landed the
|
||
fix at `a5a7eb4f` — see the standalone ledger row above; not repeated here
|
||
since it needed no further work this round.
|
||
|
||
| # | User finding (condensed) | Disposition |
|
||
|---|---|---|
|
||
| (a) | The SpewBox yellow text is "still not aligned all the way to the top" and not "the correct font and size" (retail's is SMALLER than ours). | **FIXED this SHA.** Two independent sub-fixes, both best-available APPROXIMATIONS (the true retail values remain unmeasurable — `SpewBoxLayoutDumpDiagnostic` re-run this round still finds no `FontDid`/colour property on element `0x10000048` or its ListBox child): **position** — `SpewBoxController.TopOffset` moves from round 1's 60px placeholder to `0` (flush to the viewport top), per the user's explicit direction. **Font** — the controller never wired `DatFont`/`Font` at all before this round, so it silently fell through to the retained-UI host's 15px debug `BitmapFont`; it now resolves retail dat Font `0x40000025` (`MaxCharHeight=11px`) through `RetailUiRuntime.Assets.ResolveFont` (a new public accessor), the SAME memoized resolver the rest of the retained UI uses. `0x40000025` was picked by cross-referencing every currently-imported retail LayoutDesc fixture (`tests/AcDream.App.Tests/UI/Layout/fixtures/*.json`) for the smallest FontDid actually in use — it is 11px, smaller than every other font found (`0x40000000`=16px, `0x40000002`=14px, etc.), confirmed against the installed DAT via `AcDream.Cli dump-font-atlas` sweeping every populated font id `0x40000000`-`0x40000032`. It is ALSO the chat window's own smallest font (the `0x2100006F` floating-window 1/2/3/4 indicator badges use the same id) — both selection criteria the round-3 brief offered landed on the same answer, no tie to break. Register row AP-178 updated (not retired — position/font remain approximations, not resolved retail values; only vertical content flow stays fully OPEN). |
|
||
| (b) | `/help` output is "still not what retail displays". | **FIXED this SHA.** Round 2 extracted the individual STRINGS byte-exact but never traced `ClientCommunicationSystem::DoHelp @0x0057f9e0`'s complete PRINT SEQUENCE, so the bare `/help` listing was still an acdream-invented cheat sheet ("Chat: /say...", "Channels: /general...", etc. — none of it retail text). Traced this round via a byte-sweep of DoHelp's own range (`0x57f9e0`-`0x57fe7e`) plus the five Summary-branch functions it calls into, against the PDB-paired `C:\Users\erikn\Downloads\acclient.exe` (verified MATCH). Retail's real bare-`/help` output is exactly TWO scroll entries — `RetailCommandHelpTable.HelpPrefixNote` (now re-swept with its leading blank line and trailing double newline, previously dropped) then `RetailCommandHelpTable.AvailableHelpListing`, a 13-item straight-line concatenation of retail's real topic-group one-liners (allegiances/channels/chatting/death/emote/fillcomps/friends/house/squelch/status/text/commands) in DoHelp's exact source order — never one concatenated blob. `ChatCommandRouter.EmitBareHelp` now emits two `ShowSystemMessage` calls, matching. The acdream-only `RetailClientCommandCatalog.BuildHelpText()`/router `BuildHelpText()` methods that built the old fabricated listing are deleted outright (dead code once nothing calls them). |
|
||
| (c) | `/help death` is "still not formatted correctly". | **FIXED this SHA.** The CONTENT (`DeathGroupDetail`'s 8 lines) was already byte-exact from round 2 — what was still wrong was the SHAPE. DoHelp wraps EVERY successfully-resolved `/help <verb>` in the SAME two-entry shape as the bare listing: `HelpPrefixNote` as its own entry, then a SECOND entry that is `ForMoreInformationPrefix` ("For more information, type @help <command>.\n" — retail's own literal; "<command>" is NOT a substituted placeholder, confirmed by the absence of any sprintf/substitution call in the decomp) concatenated DIRECTLY onto the verb's own detail text — no blank line, no third entry, because retail's own handler call appends into the SAME string accumulator the prefix was built into. `ChatCommandRouter.EmitVerbHelp` now applies this wrap UNIFORMLY to every resolved verb (both `RetailClientCommandCatalog` and `RetailCommandHelpTable` lookups), not just death — the general fix, not a death-specific patch, per CLAUDE.md's root-cause discipline. An unresolved verb now shows retail's real fallback text, `RetailCommandHelpTable.UnknownCommand` ("Unknown command", swept verbatim) instead of the acdream-invented "No help available for '{verb}'." — this surfaced a SEPARATE finding: retail types this fallback `0x1A` (`ClientLocal`), which routes to the SpewBox exclusively, never the chat window; `ChatCommandRouter`/`ChatVM` live in `AcDream.UI.Abstractions`, a layer beneath Runtime with no SpewBox access, so the fallback still renders in the chat scroll — not a regression (it was already there, just with fabricated text), now tracked as ISSUES.md #367 / register row AP-186 instead of silently continuing unregistered. |
|
||
|
||
Evidence: this commit's diff + `tests/AcDream.UI.Abstractions.Tests/Panels/Chat/RetailCommandHelpTableTests.cs`
|
||
(new pinning tests for `HelpPrefixNote`, `ForMoreInformationPrefix`,
|
||
`UnknownCommand`, `AvailableHelpListing`, and the router-level `/help death`
|
||
two-entry shape) + `tests/AcDream.UI.Abstractions.Tests/Panels/Chat/ChatCommandRouterTests.cs`
|
||
(updated `/help` tests for the new shape) +
|
||
`tests/AcDream.UI.Abstractions.Tests/Panels/Chat/ChatPanelInputTests.cs`
|
||
(updated entry-count assertions) + `tests/AcDream.App.Tests/UI/SpewBoxControllerTests.cs`
|
||
(new flush-top / resize / font-wiring tests). Debug suite (all projects)
|
||
green — 12,329 passed / 4 skipped / 1 failed, the single failure being
|
||
issue #351 (a pre-existing, load-sensitive Debug-only flake in an
|
||
unrelated streaming test, confirmed reproducing identically on the
|
||
pristine pre-round-3 commit via `git stash`, not a regression from this
|
||
work). Release verification was possible for every project reachable
|
||
without rebuilding `AcDream.App` — a live client process (PID 15064) held
|
||
its own Release binaries locked for the whole session and was not killed
|
||
per project policy (`feedback_dont_kill_clients_before_launch`) —
|
||
including `AcDream.UI.Abstractions.Tests` (867/867, the layer both `/help`
|
||
fixes live in) and every other non-App-dependent test project, all 0
|
||
failed. `AcDream.App`/`AcDream.App.Tests`/`AcDream.Core.Tests` (which the
|
||
SpewBox fix and the SpewBox tests live in) could not be Release-verified
|
||
this session; they are green in Debug.
|
||
|
||
### Byte-sweep method for findings (b)/(c)
|
||
|
||
`ClientCommunicationSystem::DoHelp @0x0057f9e0` has two branches
|
||
(`arg2 > 0` = verb specified, `arg2 <= 0` = bare `/help`). Both ALWAYS
|
||
print via exactly two `AddTextToScroll` calls, both typed `0`
|
||
(informational) — never a single concatenated string:
|
||
|
||
1. The SAME `HelpPrefixNote` line, unconditionally, in both branches.
|
||
2. Bare: `AvailableHelpListing` — 13 items, 8 inline literals DoHelp
|
||
builds itself (allegiances/channels/chatting/death/fillcomps/friends/
|
||
house, plus the header) interleaved with 5 items DoHelp gets by calling
|
||
each group's own `Summary_HelpType` branch (`HelpEmote`/`HelpSquelch`/
|
||
`HelpStatusGroup`/`HelpTextGroup`/`HelpAllGroup` — every one of those 5
|
||
functions has the identical `if (arg2 != Summary_HelpType) {Detail}
|
||
else {"@help X - ..."}` shape `HelpEmote` makes explicit at
|
||
`acclient_2013_pseudo_c.txt:388664`). Verb-specified (verb resolves):
|
||
`ForMoreInformationPrefix` immediately followed (string concatenation,
|
||
no separator) by the verb's own Detail-branch text.
|
||
|
||
When the verb does NOT resolve, DoHelp instead prints ONE entry,
|
||
`UnknownCommand`, typed `0x1A` — the SpewBox-exclusive routing type CH
|
||
user-gate round 2 item 2 already ported for the portal-space notice.
|
||
|
||
Tool: `py tools/pdb-extract/sweep_weenie_strings.py
|
||
C:/Users/erikn/Downloads/acclient.exe --range 0x57f9e0 0x57fe7e
|
||
--ascii-only --min-len 3` (DoHelp's own range) plus the same tool against
|
||
each of the five Summary-branch functions' own ranges (`HelpEmote`
|
||
`0x578b80`-`0x578c60`, `HelpSquelch` `0x57c190`-`0x57c2d0`,
|
||
`HelpStatusGroup` `0x57c410`-`0x57c5a0`, `HelpTextGroup`
|
||
`0x57c6c0`-`0x57c860`, `HelpAllGroup` `0x57e7f0`-`0x57eba0`), each range
|
||
read from the pseudo-C's own function-header addresses. `check_exe_pdb.py`
|
||
confirmed the candidate binary MATCH before any of this.
|
||
|
||
### SpewBox font selection method for finding (a)
|
||
|
||
`AcDream.Cli dump-font-atlas <dat-dir> 0x400000XX` (already-existing
|
||
tooling, extended nowhere — used as-is) against every populated font id
|
||
from `0x40000000` to `0x40000032` (38 of 50 candidate ids populated in the
|
||
installed `client_portal.dat`), reading each `Font` DBObj's own
|
||
`MaxCharHeight`/`BaselineOffset`/glyph-table-size fields. Cross-referenced
|
||
against every `FontDid` value appearing in
|
||
`tests/AcDream.App.Tests/UI/Layout/fixtures/*.json` (24 already-imported
|
||
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).
|
||
|
||
## Goal-window follow-up — #363/#367 interface-text seam (2026-08-10)
|
||
|
||
Closed issues #363 and #367 (register rows AP-183 and AP-186, both
|
||
RETIRED) under the goal-window's hard constraints: no subagents, no
|
||
client launches, one commit.
|
||
|
||
`ChatVM` (`AcDream.UI.Abstractions`) gained an `OnInterfaceText`
|
||
(`Action<string>?`) init property and a `ShowInterfaceText(text)` method —
|
||
the seam #367's own filing proposed as fix shape (a). The App-layer
|
||
composition (`InteractionRetainedUiComposition.CreateRetainedUi`) wires it
|
||
to `RuntimeCommunicationState.AddText(text, RetailLogTextType.ClientLocal)`,
|
||
the same SpewBox chokepoint every other interface-text producer (item
|
||
examine, magic feedback, the portal wait cue) already uses. Unwired hosts
|
||
(headless has no `ChatVM` at all; plain test fixtures) fall back to the
|
||
ordinary chat log tagged `ClientLocal`, so no text is ever silently
|
||
dropped — only the SURFACE degrades, never the message.
|
||
|
||
Every site AP-183 named now routes through the seam at its correct retail
|
||
type, cross-checked against the named-retail decomp (5 handlers spot-
|
||
verified: `DoDie`, `DoChannelList`/`On`/`Off`, `DoAllegiance`,
|
||
`DoHouseAvailableList` — all `0x1A`):
|
||
|
||
- `DoStupidChannelHack` ("You must specify the text you wish to say!") —
|
||
**newly wired**, not merely reclassified: the six legacy channel verbs
|
||
(Fellowship/Allegiance/AllegianceBroadcast/Vassals/Patron/CoVassals)
|
||
previously fell through `ChatInputParser.Parse`'s pure `return null`
|
||
shape with NO message shown at all. New `ChatInputParser
|
||
.IsBareRegisteredChannelVerb` predicate (pure, no side effects) plus a
|
||
`ChatCommandRouter.Submit` check ahead of `Parse`.
|
||
- `DoChannelList`/`DoChannelOn`/`DoChannelOff` ("Please specify the
|
||
channel name.") — reclassified (was already wired via
|
||
`InvalidArgumentsText`, just typed `0x00`).
|
||
- `DoAllegiance` ("Please see @help Allegiance...") — reclassified, same
|
||
shape.
|
||
- `DoHouseAvailableList` — reclassified AND corrected: retail's own
|
||
bad-house-type text is "Please see @help hslist for more information on
|
||
how to use this command" (`acclient_2013_pseudo_c.txt:381481`/`1029383`,
|
||
`AddTextToScroll(..., 0x1a, ...)`), not the acdream-synthesized "Usage:
|
||
/hslist <house type>" line the catalog fell back to.
|
||
- `DoReply` ("Someone must @tell you first!") — **newly wired** for the
|
||
message-but-no-last-teller branch only (`gmCCommunicationSystem
|
||
::GetLastTeller() == 0`). New `ChatInputParser.IsReplyMissingLastTeller`
|
||
predicate. Bare `/r` with no message at all is retail's OWN separate
|
||
copy of the "you must specify text" string (a different call site) and
|
||
is deliberately still unported — not named by AP-183, out of scope.
|
||
- `DoSpeaker`/`DoEndurance`/`DoTitle` — untouched, confirmed still correct
|
||
at `0x00` (their text is produced by `ClientCommandController`, not
|
||
`ChatCommandRouter`).
|
||
|
||
The generic bad-args fallback is also fixed: `ChatCommandRouter.Submit`'s
|
||
catalog dispatch now resolves `WeenieErrorMessages.Resolve(0x026u, null)`
|
||
("That is not a valid command.", the exact port of `DoCommand
|
||
@0x0057E46D`'s `HandleFailureEvent(0x26)`) instead of synthesizing
|
||
`"Usage: {Usage}"` — the row already carried the `0x026` template from
|
||
CH2's `HandleFailureEvent` port, so no new retail text needed extracting.
|
||
|
||
This closes #367 too: `ChatCommandRouter`'s two other local-presentation
|
||
fallbacks (`RetailCommandHelpTable.UnknownCommand` in `EmitVerbHelp`, and
|
||
the degenerate-prefix "Unknown command: {verb}." refusal in `Submit`'s
|
||
main body) now call `ShowInterfaceText` instead of `ShowSystemMessage`.
|
||
The test-script doc (`docs/research/2026-08-09-campaign-ch-test-script.md`)
|
||
is updated to drop the "known, tracked gap" notes for both — the next
|
||
connected round should observe both fallbacks flash on the SpewBox instead
|
||
of landing in the chat window.
|
||
|
||
Tests: per-site routing pinned both ways (seam wired → reaches the
|
||
SpewBox capture; seam unwired → falls back to chat, tagged `ClientLocal`)
|
||
for every reclassified/newly-wired site in
|
||
`tests/AcDream.UI.Abstractions.Tests/Panels/Chat/ChatCommandRouterTests.cs`,
|
||
plus a Turbine-only-channel negative case and a 0x00-site-stays-in-chat
|
||
sanity check; pure-predicate coverage for `IsBareRegisteredChannelVerb`/
|
||
`IsReplyMissingLastTeller` in `ChatInputParserTests.cs`; the seam itself
|
||
in `ChatVMRetellAndProvidersTests.cs`.
|
||
|
||
Full Release suite: 12,542 passed / 4 skipped / 0 failed (baseline 12,466/4/0
|
||
at `ff2784ea`; net +76 tests, all new coverage from this follow-up, zero
|
||
regressions). No subagent review this session (hard constraint); no
|
||
connected user gate (hard constraint — no client launches). The next
|
||
connected round should confirm: bare `/g`/`/a`/etc. now flash the SpewBox
|
||
red instead of doing nothing; `/hslist badtype` and a bad-args catalog
|
||
command (e.g. `/ls now`) now flash red SpewBox text instead of showing a
|
||
green "Usage:" line in chat; `/help nonsenseverb` and a bare `/`/`@` now
|
||
flash on the SpewBox instead of appearing in the chat scroll.
|
||
|
||
## Consolidated review — goal-window round (2026-08-10)
|
||
|
||
Applied the consolidated-review findings against the `09453eca` goal
|
||
window under the same hard constraints (no subagents, no client launches,
|
||
one commit). Baseline `03404b71`, full Release suite 12,542/4/0.
|
||
|
||
**SHOULD-FIX 1 — retail `/help <verb>` Detail extraction.** Retail's
|
||
`DoHelp @0x0057F9E0` calls a resolved verb's OWN registered help callback
|
||
with `Detail_HelpType(2)`, never `Summary_HelpType(1)` —
|
||
`RetailClientCommandCatalog`'s ~45 leaf Definitions were all showing their
|
||
acdream-authored Summary-shaped `HelpText` for `/help <verb>` instead.
|
||
Located every `Help*` handler by name in `acclient_2013_pseudo_c.txt`,
|
||
swept its exact byte extent for `push imm32` string literals against the
|
||
PDB-paired `C:\Users\erikn\Downloads\acclient.exe` (verified MATCH), and
|
||
confirmed each Detail-vs-Summary branch assignment by reading the actual
|
||
decompiled if/else shape — address order and string length both proved
|
||
UNRELIABLE indicators on their own (HelpDie's Detail sits at the HIGHER
|
||
address, HelpCorpse's at the LOWER; the "longest text" heuristic held for
|
||
every simple 2/3-branch case but AFK/Emote/Squelch turned out to
|
||
CONCATENATE multiple literals within one branch instead). Found and fixed
|
||
a real tool gap along the way: `sweep_weenie_strings.py`'s stock 800-char
|
||
cap silently dropped several longer Detail branches as a bare `None`
|
||
(HelpConsent 978 chars, HelpFillComponents 1036, HelpEndurance 2459) — a
|
||
custom unbounded pass recovered them.
|
||
|
||
`InitializeCommands @0x00581970`'s registration blocks always render the
|
||
CmdHashData help-pointer 4th argument as a literal `nullptr` in Binary
|
||
Ninja's pseudo-C — a confirmed, systematic decompiler artifact (the real
|
||
pointer is stored to a throwaway local immediately before the constructor
|
||
call, which BN fails to thread through). Reading for the PRESENCE of that
|
||
typed local (vs. a plain `int32_t = 0`) resolved every ambiguous verb:
|
||
hor/hr/hom/hoa and alh/ah share their Detail text with `HelpHouse`/
|
||
`HelpAllegiance` (i.e. `HouseOverview`/`AllegianceOverview`, already
|
||
extracted); friends_add/friends_remove share `HelpFriends`;
|
||
squelch/unsquelch share `HelpSquelch`. Four verbs
|
||
(index/clist/on/off) genuinely have no help pointer — retail's own
|
||
`DoHelp` falls straight to `UnknownCommand` for these, a CONFIRMED
|
||
behavior now reproduced (`RetailCommandHelpTable.CatalogVerbsWithNoRetailHelp`),
|
||
not a guess. `messagetypes`/its 3 aliases stay an honest acdream summary —
|
||
`HelpMessageTypes` builds its text from a live enum table at runtime, not
|
||
a static string. Final coverage (measured by test, not hand-counted): 42
|
||
of 47 distinct catalog leaf Definitions verbatim-extracted, 4
|
||
confirmed-null, 1 unverified.
|
||
|
||
`ChatCommandRouter.EmitVerbHelp` now checks
|
||
`CatalogVerbsWithNoRetailHelp` → `RetailCommandHelpTable.TryGetCatalogVerbDetailText`
|
||
→ `RetailClientCommandCatalog.TryGetHelpText` (the summary fallback,
|
||
unchanged) → the pre-existing `RetailCommandHelpTable.TryGetHelpText`
|
||
(chat-alias/channel verbs, a disjoint key space). `RetailCommandHelpTable`'s
|
||
class doc, which claimed to cover only verbs the catalog "doesn't dispatch
|
||
directly," is corrected — it now overlaps the catalog's own leaf verbs by
|
||
design. A fresh full-length byte-sweep of `HelpAllegiance @0x0057ae10` (the same
|
||
function `AllegianceOverview` already cites) also turned up one
|
||
previously-dropped line (" WARNING! Officers banning...") the earlier
|
||
extraction missed — now split into `AllegianceWarningLine` and restored
|
||
in place at its correct position (between "ban list" and "info").
|
||
|
||
**SHOULD-FIX 2 — `OnInterfaceText` production wiring had no test.** The
|
||
a5a7eb4f defect class (a composed hook wired but never transferred) went
|
||
undetected because `InteractionRetainedUiCompositionTests`' `FakeFactory`
|
||
substitutes a stub `ChatVM` for every composition-order test and never
|
||
exercises `RetailInteractionRetainedUiCompositionFactory`'s real wiring.
|
||
Extracted the wiring (`InteractionRetainedUiComposition.cs`, previously
|
||
inline at the line the review cited) into its own testable
|
||
`CreateChatViewModel` method — no GPU/dat/UiHost dependency, so it is
|
||
callable directly against the existing null-heavy test `Fixture` without
|
||
a full `Compose()`. New test
|
||
`ComposedChatViewModelWiresOnInterfaceTextToSpewBox` asserts the hook is
|
||
non-null AND that a `ShowInterfaceText` call actually lands in
|
||
`RuntimeCommunicationState.SpewBox` after a `Tick`.
|
||
|
||
**SHOULD-FIX 3 — AP-113 RETIRED.** `DoLifestone`/`DoMarketplace
|
||
@0x0056FC70`/`@0x0056FCE0` print their OWN 0x1A string for bad args and
|
||
return 1 — retail never reaches the generic `HandleFailureEvent(0x26)`
|
||
fallback for either. Both literals were mis-attributed by Binary Ninja to
|
||
unrelated vtable-slot symbols (the same pooled-string artifact class this
|
||
campaign has hit before); read directly off the raw `push imm32` operand
|
||
and decoded as UTF-16LE against the PDB-paired binary: `"Please see @help
|
||
lifestone for more information on how to use this command."` and the
|
||
`marketplace` analogue. `RetailClientCommandCatalog.Lifestone`/`Marketplace`
|
||
now carry them as `InvalidArgumentsText`. AP-113 is RETIRED (its only
|
||
named verb, lifestone, now has its exact wording); Marketplace was never a
|
||
filed divergence, so its fix is a plain accuracy improvement alongside it.
|
||
`ChatCommandRouter.cs`'s bad-args comment is corrected to name both
|
||
verbs and state the 0x26 fallback's actual scope (verbs with NO bespoke
|
||
retail refusal, not every bad-args case).
|
||
|
||
**SHOULD-FIX 4 — register header sentence fixed.** `retail-divergence-
|
||
register.md`'s AP section header still said AP-190 "ships one shared
|
||
0.5/1.0 default rather than gmMainChatUI's per-class 1.0/1.0 override" —
|
||
refuted by `cc582899`, which fixed the shipped default TO 1.0/1.0 (the
|
||
row's own REWORDED (2) already said so; only the header summary hadn't
|
||
caught up). Corrected in place; the active-row count drops to 132 with
|
||
AP-113's retirement.
|
||
|
||
**NITs (a)-(d).** (a) `HeadlessStaticStateAudit`'s single-session log line
|
||
now routes through the injected `HeadlessDiagnosticWriter` (constructed
|
||
before the audit call in `HeadlessProcessHost`, not after) instead of a
|
||
bare `Console.WriteLine` that bypassed the structured stream every other
|
||
headless diagnostic uses. (b) `HeadlessSessionWorldProjection.PumpFirstEntry`
|
||
gained a bounded, diagnostic-only trip-wire: 300 consecutive non-quiescent
|
||
pumps (generous — many seconds at the host's tick cadence) emit ONE
|
||
message naming the stuck landblock; no retry, no behavior change, the gate
|
||
itself is untouched. (c) The #365 hydration test's doc comment is
|
||
corrected — it claimed reverting the `IsQuiescent` gates makes the
|
||
conductor reach "`PublicationCommitted` — a non-null dormant controller,"
|
||
which contradicts the diagnosis doc's own §8 finding (the controller is
|
||
ALREADY built AND published, `CanExecuteLiveMovement = True`, when the
|
||
assertion actually fails); the comment now also states plainly that this
|
||
bounded xunit test proves the GATE, not the #365 stall's closure — that
|
||
broader claim's evidence is the separate live-ACE run §8 records. (d)
|
||
`ChatCommandRouter`'s 0x26 fallback now resolves
|
||
`WeenieErrorMessages.Resolve(0x026u, null)` once and dispatches on its own
|
||
`Type` field instead of assuming `ShowInterfaceText`'s hardcoded
|
||
`ClientLocal` is correct for it (it is, today — this just stops relying on
|
||
that being an unstated invariant).
|
||
|
||
Full Release suite: 12,553 passed / 4 skipped / 0 failed (net +11 tests
|
||
from this round, zero regressions; one `LandblockPresentationPipelineTests`
|
||
failure observed on one parallel full-solution run reproduced as PASS in
|
||
isolation and on two subsequent full-solution reruns — pre-existing
|
||
run-order flakiness in an unrelated streaming test, not caused by this
|
||
round's changes). No subagent review this session (hard constraint); no
|
||
connected user gate (hard constraint — no client launches). The next
|
||
connected round should confirm `/help die`, `/help lifestone`,
|
||
`/help endurance`, and a handful of the other 42 newly-extracted verbs
|
||
show retail's exact wording instead of the old acdream summaries, and that
|
||
`/help index`/`/clist`/`/on`/`/off` now show "Unknown command" via the
|
||
SpewBox.
|
||
|
||
## User gate — round 4 (2026-08-10)
|
||
|
||
The user tested the goal-window build live and reported six findings. A
|
||
parallel read-only research agent investigated items 1+2 (text-style
|
||
presentation) concurrently with the session that fixed items 3+5
|
||
(`5b54387b`); that session owned its own build/commit (hard constraint:
|
||
one commit). Items 1+2 landed CODE-COMPLETE in a separate follow-up
|
||
session/commit off the research agent's findings
|
||
(`docs/research/2026-08-10-retail-ui-text-style.md`) — see the dispositions
|
||
below and the new ledger row.
|
||
|
||
| # | User finding (condensed) | Disposition |
|
||
|---|---|---|
|
||
| 1 | Both the chat-window transcript text AND the on-screen SpewBox text differ from retail in face, size, and colour shade. | **FIXED in the round-4 items-1+2 follow-up commit** (see the new ledger row below — a separate commit from items 3+5's `5b54387b`, per this session's one-parallel-agent/one-commit-per-session-slice pattern). Face/size for BOTH surfaces resolve to the actual authored fonts (chat: `0x40000000`/16px, already correct; SpewBox: `0x40000001`/18px, corrected from a round-3 heuristic). The transcript's default fill now seeds from its authored `ARGB(255,204,204,204)` instead of an unrelated color-table slot, without touching the 34-entry `LogTextType` table. See `docs/research/2026-08-10-retail-ui-text-style.md` §5. |
|
||
| 2 | Retail's SpewBox text carries a heavy black border around every glyph that acdream's does not. | **FIXED in the same round-4 items-1+2 follow-up commit as item 1.** Root cause: retail ships a SECOND ("background") glyph atlas per font, dilated 2px on every side, plus two border-pixel scalars (`NumHorizontalBorderPixels`/`NumVerticalBorderPixels`) that acdream's font reader dropped entirely (zero repo hits for `BorderPixel` before this fix) — so even the pre-existing `outline` parameter drew almost nothing once enabled. Both the missing border-pixel read AND the un-inflated background blit rect are fixed together (either alone is a no-op), `UiRenderContext.DrawStringDat` now runs retail's exact two-pass whole-string outline-then-fill model, and property 0x21/0x22 (Outline/OutlineColor) import onto every DAT-authored text element — not just the SpewBox — so this class of bug cannot recur element-by-element. The SpewBox's own line template authors outline ON with no colour (ctor black default), matching the user's screenshot. |
|
||
| 3 | User-visible meta-markers ("IMPLEMENTED", "acdream has not yet extracted…"-style notices) leaking into live `/help` output. | **FIXED this commit, closes ISSUES.md #364.** Every honesty marker is now gone from user-visible 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 all removed, with the underlying retail text corrected/completed against the pseudo-C's own pristine consolidated data dumps (`Log` and `Motd` had also been silently truncated; `Render` had been 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 — `ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290`'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 (`LogTextTypeEnumMapper::IsLegalChannel`'s 14-id whitelist + `LogTextTypeToString`'s name table + the exact join/wrap format) instead of an acdream summary. Register row AP-184 RETIRED. See `RetailCommandHelpTable`'s class remarks and `RetailCommandHelpTableTests` (51 tests, all passing) for the full citation trail. |
|
||
| 4 | (Not detailed to this session — reported as passed.) | **User-passed**, no action needed this round. |
|
||
| 5 | The main window's 1/2/3/4 indicator buttons don't open/close the floating chat windows on click — the user's retail memory says clicking should toggle them. | **FIXED this commit.** CH6b's decomp grep (`gmMainChatUI::ListenToElementMessage @0x004CDA80` has no click case for these ids) was TRUE but incomplete — it never checked `UIElement_Button`'s own generic click handler. `UIElement_Button::HandleButtonClick @0x00471E50` reads an Enum property (`0x12`) off the button itself and, if present, routes through `ICIDM`'s action map to `UIElementManager::DoVisibilityToggleAction @0x0045B660` (the SAME function the `Alt+1..4` keybinds reach), which broadcasts element message `0x31` to every element registered as a listener for that action id (via property `0x24`, read once by `UIElement::Initialize`) — the receiving element's generic `UIElement::ListenToElementMessage` base-class handler then toggles its own visibility per its OWN property `0x58`. The committed fixture (`chat_2100006f.json`) confirms the button HALF is genuinely armed: all four indicators carry a real Enum-kind property `0x12` = `0x10000114`-`0x10000117` (corrected 2026-08-10 from an earlier misread of `0x10000514`-`0x10000517`; see `docs/research/2026-08-09-chat-retail-window-shell.md` §1.4's own correction note). But the floating-window fixture (`chat_floaty_2100005b.json`) authors NO matching property `0x24`/`0x58` anywhere, so nothing in the shipped DAT registers a floating chat window as that action's listener — the generic mechanism is real and armed on the button side but has no proven target in the data available to us. Per CLAUDE.md, the user's retail memory is the axiom regardless: `ChatWindowController.BindIndicatorClicks` (new) wires each indicator's click to the SAME `ToggleFloatingChatWindow(windowId)` chokepoint the keybinds use, explicitly as USER-DIRECTED retail behavior. `SetIndicatorOpen` stays the sole writer of the `Selected` mirror (`SuppressSelfToggle` stays `true`) 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 (the `@0x004CDA80` citation stays true as a statement about that one function; the CONCLUSION is corrected). New tests in `ChatLayoutConformanceTests.cs` exercise the click round trip and confirm keybind and click drive the same chokepoint. |
|
||
| 6 | A settings-surface finding. | **Deferred to the settings track** — out of this commit's scope; no code change this round. |
|
||
|
||
Suite: 12,579 passed / 4 skipped / 0 failed (Release, complete solution —
|
||
`AcDream.slnx`), up from the goal-window baseline 12,553/4/0 — net +26
|
||
tests, zero regressions. Evidence: this commit's diff +
|
||
`tests/AcDream.UI.Abstractions.Tests/Panels/Chat/RetailCommandHelpTableTests.cs`
|
||
(51 tests) + `tests/AcDream.App.Tests/UI/Layout/ChatLayoutConformanceTests.cs`
|
||
(new/updated indicator-button tests). No subagent review this session (hard
|
||
constraint); no connected user gate for this commit's own changes (hard
|
||
constraint — no client launches) — the next connected round should confirm
|
||
`/help channels`/`/help chatting`/`/help commands`/`/help messagetypes` now
|
||
show complete retail text with no meta-notice, and that clicking each of the
|
||
four chat-window indicator buttons now opens/closes its floating window
|
||
while the button's lit/unlit state stays correct.
|
||
|
||
### User gate round 4, items 1+2 — text style follow-up (2026-08-10)
|
||
|
||
Off the parallel research agent's findings
|
||
(`docs/research/2026-08-10-retail-ui-text-style.md`): retail's outline is a
|
||
SECOND ("background") glyph atlas per font, dilated 2px on every side, plus
|
||
two border-pixel scalars acdream's font reader never read (zero repo hits
|
||
for `BorderPixel`) — so even flipping the pre-existing `outline` parameter
|
||
was a visual no-op. Landed together (either half alone is a no-op or a
|
||
regression):
|
||
|
||
1. **`UiDatFont`** carries `BorderX`/`BorderY` from the DAT's
|
||
`NumHorizontalBorderPixels`/`NumVerticalBorderPixels`
|
||
(`Font::Serialize @0x00443650`).
|
||
2. **`UiRenderContext.DrawStringDat`** inflates the background blit's source
|
||
AND destination rect by that margin, and restructures into retail's
|
||
exact two-pass model — the WHOLE STRING's outline pass, then the WHOLE
|
||
STRING's fill pass (`UIElement_Text::DrawSelf @0x00467aa0`), plus the
|
||
8-neighbour ±1px fallback for the rare 0-border font family. The stale
|
||
"LayoutDesc property 0xd" comment (actually the `OnSetAttribute`
|
||
switch-case index, not the property id) is corrected to 0x21/0x22.
|
||
3. **Property 0x21 (Outline) / 0x22 (OutlineColor) import**:
|
||
`ElementInfo.Outline`/`OutlineColor`, read in `LayoutImporter.ReadState`
|
||
and `ElementReader.ApplyCanonicalLegacyProjection`/`Merge` with the same
|
||
"derived wins" convention as `FontDid`, wired onto `UiText.Outline`/
|
||
`OutlineColor` by `DatWidgetFactory.BuildText`. Every one of the ~100
|
||
authored-outline elements across 15 layouts is correct at once, not
|
||
fixed controller-by-controller.
|
||
4. **SpewBox**: `RetailFontId` corrected from the round-3 heuristic
|
||
(`0x40000025`) to the actually-authored `0x40000001` (18px bold serif,
|
||
base style `0x10000377`), `Outline = true` set directly on the
|
||
controller's `UiText` (it is synthesized, not DAT-imported). Fill colour
|
||
stays the user-gate-round-1-pinned yellow `(1,1,0.247,1)` — font atlases
|
||
are `PFID_A8` (alpha-only), so there is no baked shading that could
|
||
explain the screenshot's gold as anything other than the outline itself
|
||
making a bright fill read warmer. Register row AP-178 updated: font,
|
||
size, outline, and position are now AUTHORED; only fill-colour
|
||
calibration and the AP-177 line-lifetime timeout remain open.
|
||
5. **Chat transcript**: default fill now seeds from the authored
|
||
`ARGB(255,204,204,204)` (style `0x10000372` property 0x1B) instead of
|
||
the color table's own unrelated index-0x00 slot — `ChatTranscriptRenderer.BuildLines`
|
||
takes the transcript's own `UiText.DefaultColor` as an explicit
|
||
parameter. The 34-entry `LogTextType` table (the per-message color
|
||
authority) is untouched — every existing CH1 conformance test
|
||
(`RetailChatColorTableTests.cs`, `ChatWindowControllerTests.cs`) stays
|
||
green unmodified. No outline on the transcript, matching retail (style
|
||
`0x10000372` authors no property 0x21 anywhere in its chain) — pinned
|
||
against the real installed DAT via a regenerated `chat_2100006f.json`
|
||
fixture, not a JSON-missing-field default.
|
||
|
||
Suite: 12,610 passed / 4 skipped / 0 failed (Release, complete solution —
|
||
`AcDream.slnx`), up from the round-4 items-3+5 baseline 12,579/4/0 — net
|
||
+31 tests, zero regressions (one `LandblockPresentationPipelineTests`
|
||
failure reproduces identically on the pristine pre-this-session baseline —
|
||
pre-existing run-order flakiness in an unrelated streaming test, confirmed
|
||
via `git stash` before touching any code; it did not reproduce at all
|
||
during this round's own full-solution runs). No subagent review this session
|
||
(hard constraint); no connected user gate for this commit's own changes
|
||
(hard constraint — no client launches) — the next connected round should
|
||
confirm the SpewBox now shows a heavy black border in the authored 18px
|
||
face, and that the chat transcript's default text shade reads slightly
|
||
softer than pure white.
|