docs: Campaign CT rescoped to complete chat parity, system + GUI
The user set the goal: complete retail parity for the chat system AND the chat
GUI, not just the green clickable name that started the review. Plan restated
around that bar, with a definition of done — every retail behaviour either
implemented or carrying a divergence-register row, every user-visible surface
covered by a test, and the digest/ISSUES describing reality.
Slices regrouped into A (the tagged-text capability, a strict chain where
nothing is visible until A4), B (system behaviours), C (GUI), D (hygiene), plus
the research still owed before specific slices and what is deliberately out of
scope.
Four items joined the plan that the original six lanes did not own, because
they fell between lanes:
- the /r, /t, /tell text-replacement macro (the commands work; retail's
VISIBLE expansion to "@tell {LastTeller}, " does not exist)
- FilterLanguage, which is a decorative toggle: we store it, ship the bit and
show it in Options, and never actually filter anything
- the plain-text session chat log retail writes and we do not
- the option-gated timestamp prefix
Also corrects the CH3 command-registry research note. Its "acdream status"
columns are from before slice CH4 and list 13 verbs as MISSING that have all
since been added — cg, soc, o, co-vassals, fellows, group, party, vassal, ab,
guild, ct, clfg, crp — and its DIVERGENT row for /g is likewise stale: acdream
maps /g to Fellowship, matching retail, confirmed against the live client
today. The retail side of that document is still the authority; only the
columns describing us were wrong. They misled this session's investigation,
which is exactly why the banner says to verify against ChatInputParser.cs.
Nothing implemented.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
663129c340
commit
ba6c82af93
2 changed files with 125 additions and 93 deletions
|
|
@ -1,126 +1,143 @@
|
|||
# Campaign CT — chat text tags and chat-window parity
|
||||
# Campaign CT — complete chat parity (system + GUI)
|
||||
|
||||
**Status:** PROPOSED (2026-08-21). Not started.
|
||||
|
||||
Retail renders a speaker's name inside a chat line in green, and clicking it
|
||||
opens a tell to that person. acdream renders flat, uniformly coloured, inert
|
||||
lines. Six parallel research lanes established why, and the answer is not a
|
||||
chat bug — it is a **missing capability in the text stack**.
|
||||
**Goal, set by the user 2026-08-21: complete retail parity for the chat
|
||||
system AND the chat GUI.** Not "fix the green name" — that was the symptom
|
||||
that started the review. The bar is that a retail player sitting down in front
|
||||
of acdream's chat window finds nothing missing and nothing behaving
|
||||
differently.
|
||||
|
||||
Campaign CH (2026-08-09, closed user-accepted) landed colours, side channels,
|
||||
the 152-verb command registry, the window shell and verbatim `/help`. CT is
|
||||
the pass that closes what CH did not reach.
|
||||
|
||||
Research notes (all 2026-08-21): `chat-texttag-model.md`,
|
||||
`chat-tagged-name-composition.md`, `chat-tag-click-dispatch.md`,
|
||||
`retail-chat-window-ui.md`, `acdream-text-stack-audit.md`,
|
||||
`acdream-chat-ui-audit.md`.
|
||||
|
||||
## The mechanism, proven
|
||||
## Definition of done
|
||||
|
||||
1. The CLIENT composes the markup. `Handle_Communication__HearSpeech
|
||||
@0x005712A0` / `HearDirectSpeech @0x005715A0` sprintf a literal tag into the
|
||||
plain chat line, of the shape
|
||||
`<Tell:IIDString:{iid}:{name}>{name}<\Tell> says, "{text}"` — the closing
|
||||
marker is a literal backslash. Only senders whose GUID is in AC1's player
|
||||
range `0x50000001..0x6FFFFFFF` are tagged at all.
|
||||
1. Every retail chat behaviour is either implemented, or has a divergence-
|
||||
register row saying why not.
|
||||
2. Every user-visible chat surface has a test that would catch its regression.
|
||||
3. The chat digest and `docs/ISSUES.md` describe reality (both are stale today).
|
||||
|
||||
2. `UIElement_Text::InqGlyphs @0x00468EA0` recognises the brackets while
|
||||
appending and calls `TextTagFactory::MakeTag @0x00478480`. Any bracketed
|
||||
text that fails to parse closes the open tag — `MakeTag` requires a `:` to
|
||||
succeed, which is exactly what makes the bare closer a closer.
|
||||
## What the review established
|
||||
|
||||
3. Tags attach **per glyph**. There is no run or span object anywhere: a
|
||||
"tagged run" is emergent, re-derived by walking neighbouring glyphs whose
|
||||
`m_tag` pointers are equal.
|
||||
### The green clickable name is a TEXT-STACK gap, not a chat gap
|
||||
|
||||
4. Colour: a glyph takes the TAG colour (property `0x1D`) only when a tag is
|
||||
open AND its `m_type == 0x10000001`; otherwise the ordinary line colour
|
||||
(`0x1B`). Both are DAT-authored arrays on the element.
|
||||
The client sprintfs literal markup into the line —
|
||||
`<Tell:IIDString:{iid}:{name}>{name}<\Tell> says, "{text}"`, closing marker a
|
||||
literal backslash — and `UIElement_Text::InqGlyphs @0x00468EA0` parses the
|
||||
brackets while appending, calling `TextTagFactory::MakeTag @0x00478480`. Tags
|
||||
attach **per glyph**; a "run" is emergent (adjacent glyphs with equal tag
|
||||
pointers). A glyph takes the tag colour (property `0x1D`) only when a tag is
|
||||
open AND its `m_type == 0x10000001`, else the line colour (`0x1B`). Only
|
||||
senders with a GUID in `0x50000001..0x6FFFFFFF` are tagged.
|
||||
|
||||
5. **Measured** out of the installed dats (`LayoutDump --colors`), chat
|
||||
`0x2100006F`, transcript `0x10000011`:
|
||||
Colour **measured** from the installed dats (`LayoutDump --colors`), chat
|
||||
`0x2100006F` / transcript `0x10000011`: `P0x1B` = RGB(204,204,204),
|
||||
`P0x1D` = **RGB(0,178,0)**. The tag colour is per-ELEMENT and authored, while
|
||||
the line colour on that same element comes from the runtime chat table —
|
||||
filing "tag green" into the LogTextType table would put it in the wrong place.
|
||||
|
||||
P0x1B (line) [0x00] R=204 G=204 B=204
|
||||
P0x1D (tag) [0x00] R= 0 G=178 B= 0 <- the green
|
||||
Click: `UIElement_Text::MouseUp @0x004694F0` → `DeterminePositionFromXY
|
||||
@0x004688F0` → `GlyphList::InqGlyph @0x00473430` → virtual `HandleClick` at
|
||||
tag-vtable `+0x14` → `gmMainChatUI::RecvNotice_TextTag_IIDStringClick
|
||||
@0x004CCE10` → `ChatInterface::StartTell @0x004F41F0`, which writes
|
||||
`"@tell {Name}, "`, takes focus, and shows the entry bar. Clicking a name
|
||||
always opens a TELL — fellowship, allegiance, patron/vassal and named-channel
|
||||
lines all embed the same markup. No hover effect.
|
||||
|
||||
6. Click: `UIElement_Text::MouseUp @0x004694F0` → `DeterminePositionFromXY
|
||||
@0x004688F0` (xy → glyph index) → `GlyphList::InqGlyph @0x00473430` → a
|
||||
virtual `HandleClick` at tag-vtable `+0x14` → `SendNotice_TextTag_*Click` →
|
||||
`gmMainChatUI::RecvNotice_TextTag_IIDStringClick @0x004CCE10` →
|
||||
`ChatInterface::StartTell @0x004F41F0`, which writes `"@tell {Name}, "` into
|
||||
the entry, takes keyboard focus, and shows the entry bar.
|
||||
### Our side is closer than feared
|
||||
|
||||
Two facts that shape the port:
|
||||
`UiText` **already** draws multi-coloured runs (`TextRun`/`RunsProvider`, used
|
||||
by the character stat panel); it is gated to `OneLine == true`. The draw path
|
||||
needs no renderer work — arbitrary pen X, substring measurement — and
|
||||
`UiText.HitChar` already resolves a click to (line, column). The blocker is
|
||||
that `ChatVM.RecentLinesDetailed()` drops `Sender`/`SenderGuid` one step before
|
||||
the renderer, though `ChatEntry` carries them the whole way.
|
||||
|
||||
- **The tag colour is per-ELEMENT and authored**, while the line colour on the
|
||||
same element comes from the runtime chat table. Filing "tag green" into the
|
||||
LogTextType colour table would put it in the wrong place.
|
||||
- **Clicking a name always opens a TELL**, everywhere. Fellowship, allegiance,
|
||||
patron/vassal and named-channel lines all embed the same markup. Dispatch is
|
||||
generic over four tag shapes, but only `IIDString` has a listener in this
|
||||
build.
|
||||
### The command registry is already at parity
|
||||
|
||||
Retail applies no hover effect to a tag, and the colour is a static per-glyph
|
||||
bake at append time — not a render-time lookup.
|
||||
|
||||
## What we already have
|
||||
|
||||
The audit found more than expected. `UiText` **already** has a
|
||||
`TextRun`/`RunsProvider` path that draws several differently-coloured runs on
|
||||
one line (used today by the character stat panel) — it is simply gated to
|
||||
`OneLine == true`, and the chat transcript is multi-line. The draw path needs
|
||||
no renderer work at all: it already accepts an arbitrary pen X and can measure
|
||||
substrings. `UiText.HitChar` already resolves a click to (line, column).
|
||||
|
||||
So the gap is narrower than "build a text tag system":
|
||||
|
||||
- the multi-line path cannot carry runs, and
|
||||
- sender identity is **destroyed before it reaches the renderer**: `ChatEntry`
|
||||
keeps `Sender`/`SenderGuid` all the way through `ChatLog`, and
|
||||
`ChatVM.RecentLinesDetailed()` builds a `FormattedLine` that drops both.
|
||||
All 13 verbs the CH3 research note lists as MISSING were closed by CH4 and
|
||||
verified present 2026-08-21 (`cg`, `soc`, `o`, `co-vassals`, `fellows`,
|
||||
`group`, `party`, `vassal`, `ab`, `guild`, `ct`, `clfg`, `crp`). `/g` correctly
|
||||
resolves to Fellowship, confirmed against the live retail client. That note's
|
||||
"acdream status" columns are stale and now carry a correction banner.
|
||||
|
||||
## Slices
|
||||
|
||||
**CT1 — runs on the multi-line text path.** Extend the existing `TextRun`
|
||||
model to multi-line elements; per-line run lists; additive, so `Line` keeps
|
||||
working and the ~50 files using it are untouched. No behaviour change.
|
||||
### Group A — the tagged-text capability (strict chain, A1→A5)
|
||||
|
||||
**CT2 — markup parse.** Parse the tag markup into runs carrying a tag payload,
|
||||
including retail's rule that an unparseable bracket closes the open tag. Pure
|
||||
and unit-testable, no UI.
|
||||
Nothing is user-visible until A4.
|
||||
|
||||
**CT3 — stop flattening, and emit the markup.** Carry sender name + guid
|
||||
through `ChatVM` into spans, and compose retail's markup in the speech handlers
|
||||
behind the player-GUID-range gate. This is the slice that makes the name a
|
||||
distinct run at all.
|
||||
- **CT-A1** Multi-line text elements carry coloured runs. Additive; the ~50
|
||||
files using `Line` are untouched. No behaviour change.
|
||||
- **CT-A2** Parse the tag markup into runs with a tag payload, including
|
||||
retail's rule that an unparseable bracket closes the open tag. Pure, unit-
|
||||
testable, no UI.
|
||||
- **CT-A3** Stop flattening: carry sender name + guid through `ChatVM` into
|
||||
spans, and compose retail's markup in the speech handlers behind the
|
||||
player-GUID-range gate.
|
||||
- **CT-A4** Apply the authored `0x1D` tag colour when a tag is open and its
|
||||
type matches. **Names turn green.**
|
||||
- **CT-A5** Sub-line hit-testing and `StartTell`. **Names become clickable.**
|
||||
|
||||
**CT4 — tag colour.** Read the authored `0x1D` array per element and apply it
|
||||
when a tag is open and its type matches. Uses CT1's runs.
|
||||
### Group B — chat SYSTEM behaviours
|
||||
|
||||
**CT5 — click to tell.** Sub-line hit-testing (`HitChar` → run → tag) and
|
||||
`StartTell` behaviour: write `"@tell {Name}, "`, focus the entry, show the
|
||||
entry bar. Dispatch keyed generically by tag type, with only `IIDString` wired.
|
||||
- **CT-B1** Bound the transcript: 10,000 chars, trim to ~7,500 preferring a
|
||||
newline boundary (`TruncateChatLog @0x004F4290`). Today it grows for the life
|
||||
of the session — a slow leak, not only a fidelity gap.
|
||||
- **CT-B2** Text-replacement macros: typing `/r `, `/t `, `/tell ` rewrites the
|
||||
input to `@tell {LastTeller}, ` on the space keypress
|
||||
(`HandleTextReplacements @0x004F50D0`). The commands already work; the
|
||||
visible expansion does not exist.
|
||||
- **CT-B3** `FilterLanguage`: retail runs chat text through a taboo table and
|
||||
substitutes (`PlayerModule::FilterLanguage` + `TabooTableAdaptor::
|
||||
CheckCensorsW` inside `AddTextToScroll`). We store the option, ship the bit,
|
||||
show it in Options — and never filter. A decorative toggle.
|
||||
- **CT-B4** The plain-text session chat log (`ClientSystem::s_pLogFile`). We
|
||||
write none. Path and rotation are UNKNOWN — needs research or a live check.
|
||||
|
||||
**CT6 — chat window behaviours.** Bound the transcript (10,000 chars, trim to
|
||||
~7,500 preferring a newline boundary); split auto-scroll from the unread
|
||||
indicator (`0x1000048C` — retail samples "was at bottom" BEFORE the line
|
||||
lands); the option-gated timestamp prefix. Unbounded scrollback is also a slow
|
||||
leak for the life of a session, not only a fidelity gap.
|
||||
### Group C — chat GUI
|
||||
|
||||
**CT7 — tail.** The Escape-in-chat-input no-op the audit found (no `Escape`
|
||||
case in `UiField`, and a focused field also suppresses the input dispatcher's
|
||||
fallback, so nothing happens at all); delete the dead ImGui-era `ChatPanel`;
|
||||
reconcile the stale digest/ISSUES rows (#358, #362, #363, #367, #372, #379,
|
||||
#380, #382 are DONE in code but still listed open).
|
||||
- **CT-C1** Auto-scroll vs unread: retail samples "was at bottom" BEFORE the
|
||||
line lands; if you had scrolled up it leaves you there and lights the unread
|
||||
indicator (`0x1000048C`), which scrolls to bottom and clears on click.
|
||||
- **CT-C2** Escape in the chat input is a complete no-op — `UiField` has no
|
||||
`Escape` case, and a focused field also suppresses the input dispatcher's
|
||||
fallback, so there is no clear, no defocus and no hotkey passthrough.
|
||||
- **CT-C3** Option-gated timestamp prefix (`%#H:%M:%S `, colour index `0x0C`,
|
||||
grey), gated on `PlayerModule::DisplayTimeStamps()`.
|
||||
- **CT-C4** Input-bar editing parity: clipboard and selection paths
|
||||
(Ctrl+C/X/V, shift-selection) work but are untested; `ToggleMaximize` and the
|
||||
floating-window Close button have zero coverage.
|
||||
|
||||
### Group D — hygiene
|
||||
|
||||
- **CT-D1** Delete the dead ImGui-era `ChatPanel` (never constructed since
|
||||
Campaign V deleted `AcDream.UI.ImGui`), and its three test files, which
|
||||
currently make the real input surface look better covered than it is.
|
||||
- **CT-D2** Reconcile the chat digest and `docs/ISSUES.md`: #358, #362, #363,
|
||||
#367, #372, #379, #380, #382 are DONE in code but still listed open. #359,
|
||||
#360, #361, #366 remain genuinely open.
|
||||
|
||||
## Research still owed before the affected slices
|
||||
|
||||
- The tag-type roster behind `m_type == 0x10000001` — only "Tell" is
|
||||
confirmed; the full set lives in the DAT `EnumMapper` category `0x18`.
|
||||
Blocks nothing in Group A, but decides whether other tag shapes exist.
|
||||
- Whether retail's transcript supports text selection distinctly from the
|
||||
entry field (blocks CT-C4's scope).
|
||||
- The chat log file's path and rotation (blocks CT-B4).
|
||||
- Whether a chat-specific sound cue exists — a grep came back empty, which is
|
||||
weak evidence, not proof of absence.
|
||||
|
||||
## Deliberately NOT in scope
|
||||
|
||||
Item links and the other three tag shapes (`DID`, `IID`, `IIDEnum`). They have
|
||||
no listener in the retail build we target, so porting them would be inventing
|
||||
behaviour. CT5's dispatch is generic, so they cost nothing to add later.
|
||||
|
||||
## Known-unknowns carried
|
||||
|
||||
- The symbolic name behind tag type `0x10000001` (only "Tell" is confirmed);
|
||||
the full roster lives in the DAT `EnumMapper` category `0x18`.
|
||||
- Whether the retail transcript supports text selection distinctly from the
|
||||
entry field.
|
||||
- The chat log file's path and rotation (`ClientSystem::s_pLogFile`) — retail
|
||||
writes a plain-text session log a port would miss entirely.
|
||||
behaviour. CT-A5's dispatch is generic, so they cost nothing to add later.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,20 @@
|
|||
# Retail client slash-command registry — complete enumeration + acdream audit
|
||||
|
||||
> **CORRECTION 2026-08-21 — the "acdream status" columns below are STALE.**
|
||||
> They record the state on 2026-08-09, BEFORE slice CH4 landed. Every verb this
|
||||
> document calls MISSING has since been added and verified present in
|
||||
> `ChatInputParser.ChannelVerbs`: `cg`, `soc`, `o`, `co-vassals`, `covassal`,
|
||||
> `c`, `fellows`, `group`, `party`, `vassal`, `ab`, `guild`, `gu`, `ct`,
|
||||
> `clfg`, `crp`. The DIVERGENT row for `g` was also corrected — acdream now
|
||||
> maps `/g` to Fellowship, matching retail, confirmed against the live retail
|
||||
> client on 2026-08-21.
|
||||
>
|
||||
> The RETAIL side of this document (the registered verbs, handler addresses and
|
||||
> channel ids) remains accurate and is still the authority. Only the columns
|
||||
> describing what acdream does are out of date; verify against
|
||||
> `ChatInputParser.cs` before trusting them.
|
||||
|
||||
|
||||
Date: 2026-08-09
|
||||
Status: RESEARCH ONLY. No production code was changed.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue