Blocker 1: an unrecognized "@allegiance <sub>" subcommand escaped TryMatchAllegiance (which only claimed "info"/"hometown") and fell through the unregistered-tag channel fallback, broadcasting the raw subcommand text to the Allegiance chat channel (0x02000000). Retail's own DoAllegiance never reaches DoChannelCommand for an unrecognized subcommand — it claims the whole verb and prints its own client-local refusal. TryMatchAllegiance now claims "allegiance"/"all" unconditionally and shows retail's "Please see @help Allegiance..." text; ChatCommandRouter also gained a blanket RetailClientCommandCatalog.KnownVerbs ownership guard in TryDispatchChannelFallback as defense in depth. Blocker 2: "@house abandon" sent 0x021F immediately with no confirmation. Retail runs a real two-stage dialog before Event_AbandonHouse(); ported both verbatim strings and chained two ShowConfirmation calls. Should-fixes: a bare unregistered tag with no text now passes through silently instead of showing a refusal that belongs to a different retail function; @join/@leave update RuntimeCharacterOptionsState locally (new SetOptionBit) before the wire push so the Turbine membership gate stops refusing a just-joined room; @permit accepts multi-word names; @clist/ @on/@off validate shape only and raise WeenieError 0x422 for an unknown tag; @mr/@pr help text is now the verbatim retail strings; corrected issue #360, register row TS-68, the campaign doc's B.7 note, and a stale RetailChannelTagTable comment; filed issue #363 + register row AP-183 for the deferred error-typing debt. Nits: fixed TryMatchHouse's stale doc comment, the AP-182/@title "stores the value" comments (the binding is a no-op), IsUnregisteredFallbackTag's olthoi false-positive, added /g and /rp binding-level conformance pins, made @index ignore extra arguments, and noted the six removed invented verbs in ISSUES.md. Suite: 12,216 passed / 4 skipped / 0 failed (Release), up from CH4's 12,190/4/0 — net +26 tests, no removals. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
410 lines
24 KiB
Markdown
410 lines
24 KiB
Markdown
# Campaign CH — chat & interface-text retail parity
|
||
|
||
**Status:** ACTIVE 2026-08-09 — CH1 complete (implementation `172c6f9a` +
|
||
review fixes `34d8a3c0`); CH2 CODE-COMPLETE and CLOSED pending the user
|
||
gate (`77c8296e`, REJECT-reviewed at
|
||
`docs/research/2026-08-09-ch2-review-findings.md`, reworked `e0e78883`,
|
||
re-reviewed APPROVE-WITH-FIXES with nits applied this commit) — the sole
|
||
outstanding item is the in-client user gate (jump-in-air / jump-loaded
|
||
refusals showing on-screen, not in chat). CH3 (side channels)
|
||
CODE-COMPLETE, pending the connected user gate — see the CH3 row below and
|
||
`docs/research/2026-08-09-chat-side-channels-vs-ace.md`. CH4 (command
|
||
registry completion) CODE-COMPLETE — see the CH4 closeout below; 138 of
|
||
152 registry verbs now execute locally, 5 are deliberately deferred
|
||
(issues #360/#361/#362, register rows TS-68/TS-69/TS-70), and 9 are
|
||
retail's own null-handler help-only nodes. Pending the in-client user
|
||
gate (command spot-checks per the campaign's overall gate list).
|
||
|
||
**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.
|
||
|
||
## 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,190 passed / 4 skipped / 0 failed | REJECT; fixed `<sha>` | pending |
|
||
| CH5 closeout | — | — | — | — |
|
||
|
||
### 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).
|
||
|
||
### 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.
|