fix(chat): round 4 — no user-visible meta text, real /help groups, indicator buttons toggle
Item 3 (#364): every honesty marker is now gone from user-visible /help 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 removed; the underlying retail text is corrected/completed against the pseudo-C's own pristine consolidated data dumps (Log and Motd had been silently truncated; Render was 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: HelpStupidChannelHack'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 (IsLegalChannel's 14-id whitelist + LogTextTypeToString's name table + the exact join/wrap format) instead of an acdream summary. Register row AP-184 retired. Item 5: the main window's 1/2/3/4 indicator buttons now toggle their floating chat window on click, per the user's retail memory overruling the earlier decomp-only reading. UIElement_Button::HandleButtonClick has its own generic click-driven action dispatch (property 0x12) reaching the same DoVisibilityToggleAction the Alt+1..4 keybinds use; the button fixture confirms this half is genuinely armed, but the floating-window fixture authors no matching listener-registration property, so the generic mechanism has no proven target in the data on hand. Per CLAUDE.md, the user's retail memory is the axiom regardless: ChatWindowController.BindIndicatorClicks wires each indicator's click through the same ToggleFloatingChatWindow chokepoint the keybinds use, as explicit user-directed retail behavior. SetIndicatorOpen stays the sole writer of the Selected mirror 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. Campaign plan gets the round-4 findings section; items 1+2 (text-style) are under parallel research, item 4 passed, item 6 deferred to the settings track. Suite: 12,579 passed / 4 skipped / 0 failed (Release, complete solution), up from baseline 12,553/4/0 — net +26 tests, zero regressions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8b166f3ea2
commit
5b54387b8e
10 changed files with 1129 additions and 239 deletions
|
|
@ -382,32 +382,48 @@ user gate round 3; closed in the goal-window follow-up).
|
||||||
|
|
||||||
## #364 — Three `/help` group topics still partial: HelpStupidChannelHack unresolved
|
## #364 — Three `/help` group topics still partial: HelpStupidChannelHack unresolved
|
||||||
|
|
||||||
**Status:** OPEN — filed 2026-08-09, Campaign CH user-gate round 2, item 3.
|
**Status:** CLOSED 2026-08-10 — Campaign CH round 4. The blocker in the
|
||||||
|
original filing (below) was a wrong belief, not a real limitation:
|
||||||
|
`ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290`'s three
|
||||||
|
"vtable slot" operands are the SAME pooled/mislabeled-data artifact this
|
||||||
|
file's register entry AP-113 already documented elsewhere — real DATA
|
||||||
|
pointers into `.rdata`, not vtable dispatch. Reading the function's own
|
||||||
|
disassembly for the `push imm32` immediately preceding each
|
||||||
|
`PStringBase::PStringBase` constructor call (instead of trusting Binary
|
||||||
|
Ninja's line-grouped rendering, which hides the true instruction order)
|
||||||
|
resolves all three operands directly: the function builds
|
||||||
|
`"@" + tag + " - Sends a broadcast to your " + ChannelName + ".\n"`, where
|
||||||
|
`tag` is one character sliced out of a shared wide literal `U"fvpca"`
|
||||||
|
(reading a WIDE string through a NARROW `char*` truncates at the first
|
||||||
|
zero high byte — the "hack" the function's own retail name calls out) and
|
||||||
|
`ChannelName` comes from `ChannelSystem::GetChannelName`'s own literal
|
||||||
|
switch table (also read directly: "Allegiance", "Co-vassals", "Monarch",
|
||||||
|
"Patron", "Vassals", "Fellowship"). `ChannelsGroupDetail`,
|
||||||
|
`ChattingGroupDetail` (whose "@reply" entry also needed
|
||||||
|
`HelpReply@0x00577A50`'s Summary-branch decoded — it unconditionally
|
||||||
|
concatenates reply+pr+mr, a genuine retail quirk ported as found), and
|
||||||
|
`CommandsGroupDetail` (`HelpAllGroup`, a straight-line concatenation of
|
||||||
|
every other group's Detail branch, including a CONFIRMED retail
|
||||||
|
saveui/loadui duplicate) are now COMPLETE verbatim listings, matching the
|
||||||
|
four (death/status/text/allegiances) already complete. Register row
|
||||||
|
AP-184 RETIRED with the full citation trail. The 5 remaining
|
||||||
|
`ByVerb`-only channel one-liners (fellowship/monarch/patron/vassals/
|
||||||
|
covassal, as standalone `/help f`-style lookups rather than group-listing
|
||||||
|
entries) are UNCHANGED — their own standalone help registration was never
|
||||||
|
confirmed independently of this mechanism, so they are deliberately left
|
||||||
|
as acdream summaries rather than spliced in speculatively.
|
||||||
|
|
||||||
|
**Original filing (2026-08-09, Campaign CH user-gate round 2, item 3):**
|
||||||
The user caught `/help death` printing an acdream meta-message instead of
|
The user caught `/help death` printing an acdream meta-message instead of
|
||||||
retail's real listing; all 7 `ClientCommunicationSystem::HelpXxxGroup`
|
retail's real listing; all 7 `ClientCommunicationSystem::HelpXxxGroup`
|
||||||
nodes were re-extracted verbatim from the PDB-paired binary via a
|
nodes were re-extracted verbatim from the PDB-paired binary via a
|
||||||
generalized `tools/pdb-extract/sweep_weenie_strings.py --ascii-only`.
|
generalized `tools/pdb-extract/sweep_weenie_strings.py --ascii-only`.
|
||||||
4 of 7 (death/status/text/allegiances) are now COMPLETE verbatim listings
|
4 of 7 (death/status/text/allegiances) were COMPLETE verbatim listings
|
||||||
(`RetailCommandHelpTable.DeathGroupDetail` etc.). 3 remain PARTIAL
|
(`RetailCommandHelpTable.DeathGroupDetail` etc.); 3 remained PARTIAL
|
||||||
(`ChannelsGroupDetail`, `ChattingGroupDetail`, `CommandsGroupDetail`):
|
(`ChannelsGroupDetail`, `ChattingGroupDetail`, `CommandsGroupDetail`) with
|
||||||
their detail text is built (in full or in part) by
|
an explicit UNVERIFIED note, believed genuinely not decodable from a
|
||||||
`ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290`, which
|
static string sweep — see the CLOSED note above for why that turned out
|
||||||
constructs its output from three string fragments Binary Ninja
|
to be wrong.
|
||||||
mis-attributes to unrelated vtable slots (the classic BN pooled/
|
|
||||||
mislabeled-data artifact — `&ClientCommunicationSystem::\`vftable'.
|
|
||||||
RecvNotice_StartBarberNotice` etc. are NOT real vtable dispatch)
|
|
||||||
concatenated around a live `ChannelSystem::GetChannelName` call —
|
|
||||||
genuinely not decodable from a static string sweep. Each partial group
|
|
||||||
keeps its own summary line (fully verbatim) and an explicit UNVERIFIED
|
|
||||||
note citing the address; `chatting` additionally resolves 7 of its 8
|
|
||||||
detail entries (only the 5 HelpStupidChannelHack-delegated channel-alias
|
|
||||||
lines are missing). The same mechanism also blocks 5 of ~35 channel
|
|
||||||
one-liners in `ByVerb` (fellowship/monarch/patron/vassals/covassal
|
|
||||||
family), which stay acdream-authored summaries. Resolving
|
|
||||||
HelpStupidChannelHack requires reading the three mislabeled string
|
|
||||||
fragments directly out of `.rdata` by address (not by BN symbol name) and
|
|
||||||
confirming the channel-name substring logic — a focused follow-up, not
|
|
||||||
guessed.
|
|
||||||
|
|
||||||
**Campaign:** `docs/plans/2026-08-09-chat-parity-campaign.md` (Campaign CH,
|
**Campaign:** `docs/plans/2026-08-09-chat-parity-campaign.md` (Campaign CH,
|
||||||
user gate round 2).
|
user gate round 2).
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -48,11 +48,19 @@ CODE-COMPLETE 2026-08-10** under this session's hard constraints (no
|
||||||
subagents, no client launches) — see its ledger row and Slices bullet.
|
subagents, no client launches) — see its ledger row and Slices bullet.
|
||||||
**CH6c (opacity) landed CODE-COMPLETE 2026-08-10**, same constraints —
|
**CH6c (opacity) landed CODE-COMPLETE 2026-08-10**, same constraints —
|
||||||
see its ledger row and Slices bullet; the full three-sub-slice CH6 window
|
see its ledger row and Slices bullet; the full three-sub-slice CH6 window
|
||||||
shell is now CODE-COMPLETE. Status stays CODE-COMPLETE pending the next
|
shell is now CODE-COMPLETE. **User gate round 4 ran 2026-08-10 and found
|
||||||
user gate round (still needed for CH6a's own visual confirmation, CH6b's
|
six findings; see "User gate — round 4" below.** Items 3 (user-visible
|
||||||
keybind/mirror/filter behavior, CH6c's focus-driven fade and Settings
|
`/help` meta-markers leaking into live output, closing #364) and 5 (the
|
||||||
slider, round 3's fixes, and a final in-client visual pass on everything
|
main window's 1/2/3/4 indicator buttons now toggle their floating window
|
||||||
fixed so far).
|
on click, per the user's retail memory) are fixed in this round's commit;
|
||||||
|
item 4 passed with no action needed; items 1+2 (text-style findings) were
|
||||||
|
under parallel read-only research this session, concurrent with this
|
||||||
|
commit's item 3+5 work; item 6 is deferred to the settings track. Status
|
||||||
|
stays CODE-COMPLETE pending the next user gate round (still needed for
|
||||||
|
CH6a's own visual confirmation, CH6b's keybind/mirror/filter behavior,
|
||||||
|
CH6c's focus-driven fade and Settings slider, round 3's fixes, round 4's
|
||||||
|
item 3+5 fixes, and a final in-client visual pass on everything fixed so
|
||||||
|
far).
|
||||||
|
|
||||||
**Why now:** first track of the alpha-release program (chat is the most
|
**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.
|
visible daily surface for the friend-alpha). User-directed 2026-08-09.
|
||||||
|
|
@ -289,7 +297,8 @@ implementer per slice against a pinned contract (per
|
||||||
| 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 |
|
| 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) |
|
| 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) |
|
| 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 | this commit | 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) |
|
| 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) | this commit | 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) |
|
||||||
|
|
||||||
### CH4 closeout (2026-08-09)
|
### CH4 closeout (2026-08-09)
|
||||||
|
|
||||||
|
|
@ -1071,3 +1080,32 @@ connected round should confirm `/help die`, `/help lifestone`,
|
||||||
show retail's exact wording instead of the old acdream summaries, and that
|
show retail's exact wording instead of the old acdream summaries, and that
|
||||||
`/help index`/`/clist`/`/on`/`/off` now show "Unknown command" via the
|
`/help index`/`/clist`/`/on`/`/off` now show "Unknown command" via the
|
||||||
SpewBox.
|
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 this session's items 3+5 fix work; this
|
||||||
|
session owned all builds/commits (hard constraint: one commit).
|
||||||
|
|
||||||
|
| # | User finding (condensed) | Disposition |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | Text-style/presentation finding under the on-screen interface-text and/or `/help` surfaces. | **Research in flight** — a parallel read-only research agent investigated this concurrently with items 3+5; not fixed in this commit. Follow up in the next session once its findings land. |
|
||||||
|
| 2 | A second text-style/presentation finding, same surface family as item 1. | **Research in flight** — same parallel agent as item 1. |
|
||||||
|
| 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` = `0x10000514`-`0x10000517`. 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.
|
||||||
|
|
|
||||||
|
|
@ -184,19 +184,123 @@ exactly two element ids: `0x1000046f` (max/min, dispatching
|
||||||
(`idMessage == 7`, checked against `this->m_pCCS` / a `0x1000000b` attribute
|
(`idMessage == 7`, checked against `this->m_pCCS` / a `0x1000000b` attribute
|
||||||
read). There is no case, anywhere in that function or its base-class fallback
|
read). There is no case, anywhere in that function or its base-class fallback
|
||||||
(`ChatInterface::ListenToElementMessage`, called unconditionally at the
|
(`ChatInterface::ListenToElementMessage`, called unconditionally at the
|
||||||
function's tail), for `0x10000522`–`0x10000525`. **Clicking a chat-window
|
function's tail), for `0x10000522`–`0x10000525`. **This citation is still a
|
||||||
indicator button does NOTHING in retail — reading (a), pure indicator, is
|
true statement about `gmMainChatUI::ListenToElementMessage` — see the round-4
|
||||||
correct; reading (b) is refuted.** The `0x24` input-action-property theory
|
correction below for why it is the wrong function to have grepped.**
|
||||||
in reading (b) does not even apply to a mouse click on the button itself: that
|
|
||||||
property only wires *keyboard* dispatch (`UIElementManager::
|
---
|
||||||
DoVisibilityToggleAction` in §1.3), not a button's own `UIElement` click
|
|
||||||
message, which routes to its LISTENING PARENT — and that parent's handler has
|
**ROUND-4 CORRECTION (2026-08-10) — the user's own retail memory ("clicking
|
||||||
no case for these four ids. acdream ports this exactly: the four indicator
|
opens/closes the window") overruled the CONCLUSION above, and re-attacking the
|
||||||
buttons (`ChatWindowController._indicatorButtons`) carry no `OnClick` at all;
|
question with that as the starting axiom (per CLAUDE.md: the user's retail
|
||||||
`ChatWindowController.SetIndicatorOpen` is their only writer, called only from
|
memory is the axiom, not a hypothesis to be argued down) found the exact
|
||||||
`RetailUiRuntime.OnWindowVisibilityChanged` in response to the floating
|
mechanism the CH6b pass missed.**
|
||||||
window's own visibility changing (keybind or otherwise) — a pure one-directional
|
|
||||||
mirror, matching retail exactly.
|
The CH6b pass's mistake was scope, not accuracy: it proved
|
||||||
|
`gmMainChatUI::ListenToElementMessage` has no click case for these ids, then
|
||||||
|
reasoned "a button's click message… routes to its LISTENING PARENT" and
|
||||||
|
stopped there. **That's wrong — `UIElement_Button` (Type 1, the class every
|
||||||
|
one of these four indicators actually is) overrides its OWN click handling
|
||||||
|
and never asks its parent window first:**
|
||||||
|
|
||||||
|
```
|
||||||
|
UIElement_Button::HandleButtonClick @0x00471E50
|
||||||
|
00471e65 if (UIElement::GetAttribute_Enum(this, 0x12, &actionId)) // reads its OWN property
|
||||||
|
00471e72 if (actionId != 1)
|
||||||
|
00471e95 build an InputEvent(actionId)
|
||||||
|
00471eb2 ICIDM::GetActionMap()… dispatch through the action map
|
||||||
|
```
|
||||||
|
|
||||||
|
Property `0x12` here is the SAME kind of "input action" enum as `0x24` — not
|
||||||
|
the parent-window dispatch §1.4's original text assumed didn't apply to
|
||||||
|
clicks. This IS the generic mechanism a click uses to reach
|
||||||
|
`UIElementManager::DoVisibilityToggleAction @0x0045B660` (§1.3's own citation,
|
||||||
|
previously assumed keybind-only) → `BroadcastElementMessage(target, 0x31,
|
||||||
|
actionId, 0)` for every element registered under that action id via
|
||||||
|
`RegisterElementForInputAction` (§1.3's property-`0x24` registration —
|
||||||
|
confirmed as the ONLY call site of `RegisterElementForInputAction` in the
|
||||||
|
whole binary) → the RECEIVING element's generic base-class handler:
|
||||||
|
|
||||||
|
```
|
||||||
|
UIElement::ListenToElementMessage @0x00462340
|
||||||
|
00462447 case 8: // idMessage - 0x29 == 8, i.e. raw idMessage 0x31
|
||||||
|
0046244f GetAttribute_Enum(this, 0x58, &mode) // reads the RECEIVER's OWN property
|
||||||
|
00462459 if (mode == 1) SetVisible(!currentlyVisible) // toggle
|
||||||
|
0046245c else if (mode == 2) SetVisible(1) // force-show
|
||||||
|
0046245f else if (mode == 3) SetVisible(0) // force-hide
|
||||||
|
```
|
||||||
|
|
||||||
|
Neither `gmMainChatUI`, `gmFloatyChatUI`, nor `ChatInterface` overrides
|
||||||
|
`ListenToElementMessage` for raw idMessage `0x31` (confirmed by reading all
|
||||||
|
three switches directly — none has a case landing on it), so EVERY window
|
||||||
|
falls through to this base-class handler unconditionally. This is a complete,
|
||||||
|
generic, working "click toggles a registered listener's visibility" system —
|
||||||
|
exactly the "authored button behavior" this reconciliation task hypothesized
|
||||||
|
— and it is NOT gated on keyboard input the way the original §1.3 text
|
||||||
|
assumed; `UIElementManager::KeyPressEvent`'s call to `DoVisibilityToggleAction`
|
||||||
|
is simply ONE caller among the several that can reach it (the button's own
|
||||||
|
`HandleButtonClick` is another).
|
||||||
|
|
||||||
|
**What the authored DATA shows, checked directly against the committed
|
||||||
|
fixtures:**
|
||||||
|
|
||||||
|
- `chat_2100006f.json` — all four indicator elements (`0x10000522`-`0x10000525`)
|
||||||
|
DO author an Enum-kind property `0x12` (confirmed by `Kind: 0` = Enum in the
|
||||||
|
fixture's own property dump, matching `LayoutImporter.ConvertProperty`'s
|
||||||
|
`EnumBaseProperty → UiPropertyKind.Enum` mapping exactly), with values
|
||||||
|
`0x10000514`-`0x10000517` in element-id order. This is a real, present,
|
||||||
|
correctly-typed action id — the button-click half of the generic mechanism
|
||||||
|
is genuinely armed.
|
||||||
|
- `chat_floaty_2100005b.json` — the floating chat window's own fixture
|
||||||
|
authors **NO Enum-kind property `0x24` anywhere** (its only hit on property
|
||||||
|
number 36 decimal is `Kind: 4` = Integer, an unrelated attribute — not the
|
||||||
|
registration property) and **no property `0x58` at all**. Since
|
||||||
|
`RegisterElementForInputAction` has exactly one call site in the entire
|
||||||
|
binary (the property-`0x24` handler in `UIElement::Initialize`; no class
|
||||||
|
anywhere calls it directly from code), nothing in the shipped floating chat
|
||||||
|
window LayoutDesc ever registers it as a listener for ANY action id.
|
||||||
|
`DoVisibilityToggleAction` would look up the button's action id, find zero
|
||||||
|
registered listeners, and silently return — the click would fire a real
|
||||||
|
message with no receiver.
|
||||||
|
- The four action-id VALUES (`0x10000514`-`0x10000517`) are also not
|
||||||
|
chat-specific: the identical four numbers, in the identical order, are
|
||||||
|
`gmFriendsUI::PostInit`'s own child-element ids for its Add/Remove/Tell
|
||||||
|
buttons and friends listbox (`UIElement::GetChildRecursive(this,
|
||||||
|
0x10000514)` etc., confirmed by direct read). This is almost certainly a
|
||||||
|
coincidence of Turbine's global per-dat-file asset-id allocator (ids are
|
||||||
|
assigned client-wide, not scoped per panel), not a cross-reference —
|
||||||
|
nothing in `gmFriendsUI` registers for input actions either.
|
||||||
|
|
||||||
|
**Conclusion: the generic UI action system is real, it exists, and the
|
||||||
|
buttons genuinely arm their half of it — but the authored DATA available to
|
||||||
|
us (both committed fixtures, generated from the installed DAT) does not wire
|
||||||
|
a target for it.** This is consistent with, not a refutation of, the original
|
||||||
|
CH6b grep of `gmMainChatUI::ListenToElementMessage` — that citation was
|
||||||
|
looking in the wrong function, but its NEGATIVE RESULT (retail's window-level
|
||||||
|
message handlers never claim these clicks) still holds; the generic
|
||||||
|
mechanism, if it does connect the dots in real retail, does so entirely
|
||||||
|
below the level either grep could see. **Per CLAUDE.md, the user's retail
|
||||||
|
memory is the axiom regardless: acdream now wires each indicator's click to
|
||||||
|
toggle its floating window through the SAME `ToggleFloatingChatWindow`
|
||||||
|
chokepoint the `Alt+1..4` keybinds use
|
||||||
|
(`ChatWindowController.BindIndicatorClicks`, called by `RetailUiRuntime`
|
||||||
|
right after mounting the main chat window) — explicitly as USER-DIRECTED
|
||||||
|
retail behavior, not a claim that the generic-action-system data path has
|
||||||
|
been proven end-to-end.** `SetIndicatorOpen` stays the ONLY writer of the
|
||||||
|
indicator's `Selected` mirror (`UiButton.SuppressSelfToggle` stays `true`);
|
||||||
|
the click drives the real toggle, and the mirror reports the outcome back —
|
||||||
|
so the visual stays consistent through the round trip even though the write
|
||||||
|
is now two-way at the FEATURE level.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
acdream ports this exactly: the four indicator buttons
|
||||||
|
(`ChatWindowController._indicatorButtons`) now carry a real `OnClick`
|
||||||
|
(`ChatWindowController.BindIndicatorClicks`, round 4); the mirror half is
|
||||||
|
unchanged — `ChatWindowController.SetIndicatorOpen` is still the ONLY writer
|
||||||
|
of `Selected`, still called from `RetailUiRuntime.OnWindowVisibilityChanged`
|
||||||
|
in response to the floating window's own visibility changing, regardless of
|
||||||
|
what triggered it (click, keybind, or a restored layout).
|
||||||
|
|
||||||
### 1.5 Closing a floaty window from its own title bar
|
### 1.5 Closing a floaty window from its own title bar
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,13 +59,15 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
|
||||||
private const uint SendId = 0x10000019u;
|
private const uint SendId = 0x10000019u;
|
||||||
private const uint MaxMinId = 0x1000046Fu;
|
private const uint MaxMinId = 0x1000046Fu;
|
||||||
|
|
||||||
// Chat-window 1-4 state-mirror indicator buttons
|
// Chat-window 1-4 indicator buttons
|
||||||
// (gmMainChatUI::RecvNotice_SetPanelVisibility @0x004CCD80): the button lights
|
// (gmMainChatUI::RecvNotice_SetPanelVisibility @0x004CCD80): the button lights
|
||||||
// up iff the corresponding floating chat window is visible. The mechanism is
|
// up iff the corresponding floating chat window is visible. Campaign CH slice
|
||||||
// one-directional (window visibility drives the button, never the reverse —
|
// CH6b resolves these to _indicatorButtons and wires the MIRROR half via
|
||||||
// see SetIndicatorOpen's doc for the decomp confirmation). Campaign CH slice
|
// SetIndicatorOpen, called by RetailUiRuntime whenever a floating chat window's
|
||||||
// CH6b resolves these to _indicatorButtons and wires them via SetIndicatorOpen,
|
// visibility changes. Round 4 (2026-08-10) adds the CLICK half — see
|
||||||
// called by RetailUiRuntime whenever a floating chat window's visibility changes.
|
// SetIndicatorOpen's doc for the full reconciliation between the earlier
|
||||||
|
// decomp-only reading (no per-window code case) and the user's retail memory
|
||||||
|
// (clicking opens/closes the window) — both are correct, at different layers.
|
||||||
private const uint Indicator1Id = 0x10000522u;
|
private const uint Indicator1Id = 0x10000522u;
|
||||||
private const uint Indicator2Id = 0x10000523u;
|
private const uint Indicator2Id = 0x10000523u;
|
||||||
private const uint Indicator3Id = 0x10000524u;
|
private const uint Indicator3Id = 0x10000524u;
|
||||||
|
|
@ -265,17 +267,21 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
|
||||||
if (layout.FindElement(id) is { } twin)
|
if (layout.FindElement(id) is { } twin)
|
||||||
twin.Visible = false;
|
twin.Visible = false;
|
||||||
|
|
||||||
// ── Chat-window 1-4 indicator buttons — resolve now, wired later by
|
// ── Chat-window 1-4 indicator buttons — resolve now; the MIRROR half
|
||||||
// RetailUiRuntime via SetIndicatorOpen as each floating window's own
|
// is wired later by RetailUiRuntime via SetIndicatorOpen as each
|
||||||
// visibility changes (gmMainChatUI::RecvNotice_SetPanelVisibility
|
// floating window's own visibility changes
|
||||||
// @0x004CCD80 — see this class's doc + Indicator1Id..Indicator4Id).
|
// (gmMainChatUI::RecvNotice_SetPanelVisibility @0x004CCD80 — see this
|
||||||
// CH6a/b REJECT-review SHOULD-FIX 3: these carry DAT property 0x0B
|
// class's doc + Indicator1Id..Indicator4Id); the CLICK half is wired
|
||||||
// (ToggleBehavior) = true, but retail's own click dispatch
|
// by RetailUiRuntime calling BindIndicatorClicks after this method
|
||||||
// (gmMainChatUI::ListenToElementMessage @0x004CDA80) has no case for
|
// returns (round 4, 2026-08-10 — see SetIndicatorOpen's doc for the
|
||||||
// any of their element ids — clicking one does NOTHING in retail.
|
// full reconciliation). SuppressSelfToggle stays true regardless:
|
||||||
// SuppressSelfToggle keeps SetIndicatorOpen the ONLY writer of their
|
// these carry DAT property 0x0B (ToggleBehavior) = true, but the
|
||||||
// Selected mirror; without it, a click would flip the Highlight/Normal
|
// button's own blind self-flip (UiButton.OnEvent's MouseUp case)
|
||||||
// art with no underlying visibility change. ──
|
// would race the REAL toggle's outcome — SetIndicatorOpen (called
|
||||||
|
// synchronously inside the click, through RetailUiRuntime's
|
||||||
|
// WindowVisibilityChanged plumbing) stays the ONE authoritative
|
||||||
|
// writer of Selected, so a click's visual result always matches the
|
||||||
|
// window's actual new state instead of a guessed flip. ──
|
||||||
uint[] indicatorIds = { Indicator1Id, Indicator2Id, Indicator3Id, Indicator4Id };
|
uint[] indicatorIds = { Indicator1Id, Indicator2Id, Indicator3Id, Indicator4Id };
|
||||||
for (int i = 0; i < indicatorIds.Length; i++)
|
for (int i = 0; i < indicatorIds.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -507,22 +513,81 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
|
||||||
/// state transition <c>gmMainChatUI::RecvNotice_SetPanelVisibility
|
/// state transition <c>gmMainChatUI::RecvNotice_SetPanelVisibility
|
||||||
/// @0x004CCD80</c> performs: <c>State 6</c> (Highlight/"lit") when the
|
/// @0x004CCD80</c> performs: <c>State 6</c> (Highlight/"lit") when the
|
||||||
/// floating window is visible, <c>State 1</c> (Normal) when it is not.
|
/// floating window is visible, <c>State 1</c> (Normal) when it is not.
|
||||||
|
/// This half of the mechanism is genuinely one-directional in the exact
|
||||||
|
/// sense that <c>SetIndicatorOpen</c> is the ONLY writer of
|
||||||
|
/// <see cref="UiButton.Selected"/> — see <see cref="BindIndicatorClicks"/>
|
||||||
|
/// for the click half, which triggers the real toggle THROUGH this same
|
||||||
|
/// writer rather than flipping the indicator itself.
|
||||||
///
|
///
|
||||||
/// <para>
|
/// <para>
|
||||||
/// One-directional by design — a decomp read of
|
/// <b>Round 4 reconciliation (2026-08-10) — the user's retail memory
|
||||||
/// <c>gmMainChatUI::ListenToElementMessage @0x004CDA80</c> (one of
|
/// ("clicking opens the window") overruled CH6b's decomp-only reading
|
||||||
/// several functions in the 2013 binary that branch on
|
/// ("clicking does nothing"), and a deeper grep found the mechanism that
|
||||||
/// <c>idMessage == 1</c>, i.e. "clicked" — CH6a/b REJECT-review NIT 4
|
/// makes BOTH readings correct at their own layer.</b> CH6b's citation of
|
||||||
/// corrected the earlier "the ONLY function" superlative, e.g.
|
/// <c>gmMainChatUI::ListenToElementMessage @0x004CDA80</c> (no case for
|
||||||
/// <c>gmFloatyChatUI::ListenToElementMessage @0x004CE330</c> also
|
/// <c>0x10000522</c>-<c>0x10000525</c>) is STILL TRUE as a statement
|
||||||
/// branches on it for the floaty close button) shows THIS function
|
/// about that one function — but it was the wrong place to look for a
|
||||||
/// handles exactly two element ids: <c>0x1000046f</c> (max/min) and the
|
/// button click. Retail buttons don't route clicks through their
|
||||||
/// talk-focus menu's selection message. There is no case for
|
/// PARENT WINDOW's message handler at all: every <c>Type 1</c>
|
||||||
/// <c>0x10000522</c>-<c>0x10000525</c> — clicking a chat-window
|
/// (<c>UIElement_Button</c>) button has its own generic click path,
|
||||||
/// indicator button does NOTHING in retail. acdream ports this exactly:
|
/// <c>UIElement_Button::HandleButtonClick @0x00471E50</c>, which reads
|
||||||
/// these buttons have no <c>OnClick</c> (research doc §1.4 corrected —
|
/// an ENUM-kind DAT property <c>0x12</c> off ITSELF; when present, it
|
||||||
/// its own hedge that "it is safe to wire both" is a weaker reading than
|
/// builds an <c>InputEvent</c> and routes it through
|
||||||
/// this direct decomp confirmation, and is now superseded by it).
|
/// <c>ICIDM</c>/<c>UIElementManager</c>'s action map, which (for a
|
||||||
|
/// visibility action) reaches <c>UIElementManager::DoVisibilityToggleAction
|
||||||
|
/// @0x0045B660</c> — a lookup into
|
||||||
|
/// <c>m_elementInputActionListenerTable</c> (populated by
|
||||||
|
/// <c>UIElementManager::RegisterElementForInputAction</c>, itself called
|
||||||
|
/// from ONE place: <c>UIElement::Initialize</c>'s generic property switch,
|
||||||
|
/// case for ENUM-kind property <c>0x24</c> — ANY element authoring that
|
||||||
|
/// property registers itself as a listener for that action id) — for
|
||||||
|
/// every registered listener, <c>DoVisibilityToggleAction</c> broadcasts
|
||||||
|
/// element message <c>0x31</c>, which <c>UIElement::ListenToElementMessage</c>
|
||||||
|
/// (the ultimate base-class handler every window falls through to when
|
||||||
|
/// its own override, and <c>ChatInterface</c>'s, don't claim the
|
||||||
|
/// message — confirmed for BOTH <c>gmMainChatUI</c> and
|
||||||
|
/// <c>gmFloatyChatUI</c>) handles GENERICALLY: it reads the RECEIVING
|
||||||
|
/// element's OWN enum property <c>0x58</c> and, if it equals <c>1</c>
|
||||||
|
/// ("toggle"), calls <c>SetVisible(!currentlyVisible)</c>.
|
||||||
|
/// </para>
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// This is a real, complete, working "generic UI action" system — the
|
||||||
|
/// task's own hypothesis — and the fixture confirms the indicator
|
||||||
|
/// buttons genuinely author property <c>0x12</c> as an Enum (not merely
|
||||||
|
/// a stray/mistyped property): <c>chat_2100006f.json</c>'s four
|
||||||
|
/// indicator elements each carry it, with values
|
||||||
|
/// <c>0x10000514</c>-<c>0x10000517</c> in id order. But the OTHER half
|
||||||
|
/// of the wiring is where retail's own data falls short: the floating
|
||||||
|
/// chat window fixture (<c>chat_floaty_2100005b.json</c>) authors NO
|
||||||
|
/// Enum-kind property <c>0x24</c> anywhere (its one hit on property
|
||||||
|
/// number 36 is Integer-kind, an unrelated attribute) and NO property
|
||||||
|
/// <c>0x58</c> at all — so nothing in the shipped LayoutDesc data ever
|
||||||
|
/// registers a floating chat window as a listener for those four action
|
||||||
|
/// ids, and <c>RegisterElementForInputAction</c> has exactly one call
|
||||||
|
/// site in the whole binary (the property-driven one above; no class
|
||||||
|
/// anywhere calls it directly in code). <c>DoVisibilityToggleAction</c>
|
||||||
|
/// would find zero listeners and silently no-op. (The four action-id
|
||||||
|
/// VALUES themselves are not chat-specific either — the SAME four
|
||||||
|
/// numbers are <c>gmFriendsUI::PostInit</c>'s own Add/Remove/Tell
|
||||||
|
/// button and friends-listbox child ids, a coincidence of Turbine's
|
||||||
|
/// global asset-id allocator, not a cross-reference.) So even with the
|
||||||
|
/// generic mechanism confirmed real and armed on the button side, the
|
||||||
|
/// authored DATA available to us does not wire a target — which is
|
||||||
|
/// consistent with, not a refutation of, the original CH6b grep.
|
||||||
|
/// </para>
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// Per CLAUDE.md, the user's own retail memory is the axiom the code
|
||||||
|
/// must match regardless of what a specific grep or fixture shows.
|
||||||
|
/// <see cref="BindIndicatorClicks"/> makes each indicator's click drive
|
||||||
|
/// the SAME <c>ToggleFloatingChatWindow(windowId)</c> chokepoint the
|
||||||
|
/// <c>Alt+1..4</c> keybinds use — this is USER-DIRECTED retail behavior
|
||||||
|
/// (the generic action-dispatch mechanism exists and is plausibly HOW
|
||||||
|
/// retail wires it, but we cannot prove the exact target registration
|
||||||
|
/// from the data on hand), not a re-guess of the mirror-only reading.
|
||||||
|
/// See <c>docs/research/2026-08-09-chat-retail-window-shell.md</c> §1.4
|
||||||
|
/// for the full writeup.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetIndicatorOpen(int windowId, bool open)
|
public void SetIndicatorOpen(int windowId, bool open)
|
||||||
|
|
@ -533,6 +598,29 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
|
||||||
open ? UiButtonStateMachine.Highlight : UiButtonStateMachine.Normal);
|
open ? UiButtonStateMachine.Highlight : UiButtonStateMachine.Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wire each indicator button's click to <paramref name="toggleFloatingWindow"/>
|
||||||
|
/// (RetailUiRuntime's <c>ToggleFloatingChatWindow</c>, the SAME chokepoint the
|
||||||
|
/// <c>Alt+1..4</c> keybinds use) — round 4 (2026-08-10), see
|
||||||
|
/// <see cref="SetIndicatorOpen"/>'s doc for the full reconciliation. Called by
|
||||||
|
/// <c>RetailUiRuntime</c> after <see cref="Bind"/> returns, once the runtime's own
|
||||||
|
/// toggle method is available. <see cref="UiButton.SuppressSelfToggle"/> stays true
|
||||||
|
/// on every indicator (set in <see cref="Bind"/>) so this click-triggered toggle's
|
||||||
|
/// outcome — not a blind self-flip — is what <see cref="SetIndicatorOpen"/> mirrors
|
||||||
|
/// back onto <see cref="UiButton.Selected"/>, keeping the visual state consistent
|
||||||
|
/// through the full click round trip even if the toggle is ever refused.
|
||||||
|
/// </summary>
|
||||||
|
public void BindIndicatorClicks(Func<int, bool> toggleFloatingWindow)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(toggleFloatingWindow);
|
||||||
|
for (int i = 0; i < _indicatorButtons.Length; i++)
|
||||||
|
{
|
||||||
|
int windowId = i + 1;
|
||||||
|
if (_indicatorButtons[i] is { } indicator)
|
||||||
|
indicator.OnClick = () => toggleFloatingWindow(windowId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public RetainedWindowState CaptureWindowState()
|
public RetainedWindowState CaptureWindowState()
|
||||||
=> new(
|
=> new(
|
||||||
Maximized: _maximized,
|
Maximized: _maximized,
|
||||||
|
|
|
||||||
|
|
@ -893,6 +893,13 @@ public sealed class RetailUiRuntime : IDisposable
|
||||||
controller.AttachWindow(handle);
|
controller.AttachWindow(handle);
|
||||||
Host.Root.DefaultTextInput = controller.Input;
|
Host.Root.DefaultTextInput = controller.Input;
|
||||||
_chatWindowController = controller;
|
_chatWindowController = controller;
|
||||||
|
// Round 4 (2026-08-10): the indicator buttons' CLICK half — drives the
|
||||||
|
// same ToggleFloatingChatWindow chokepoint the Alt+1..4 keybinds use.
|
||||||
|
// See ChatWindowController.SetIndicatorOpen's doc for the full retail
|
||||||
|
// mechanism reconciliation (user-directed; the generic UIElement_Button
|
||||||
|
// action-dispatch system is real and armed on the button side, but the
|
||||||
|
// floating-window fixture authors no matching listener registration).
|
||||||
|
controller.BindIndicatorClicks(ToggleFloatingChatWindow);
|
||||||
Console.WriteLine("[D.2b] retail chat window from LayoutDesc importer (0x2100006F).");
|
Console.WriteLine("[D.2b] retail chat window from LayoutDesc importer (0x2100006F).");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -113,18 +113,28 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opt-out for a <see cref="ToggleBehavior"/> button whose <see cref="Selected"/>
|
/// Opt-out for a <see cref="ToggleBehavior"/> button whose <see cref="Selected"/>
|
||||||
/// state is a PURE MIRROR of external state (a producer other than the click
|
/// state is a PURE MIRROR of external state (a producer other than the button's
|
||||||
/// itself is the only legitimate writer — e.g. <see cref="ChatWindowController.SetIndicatorOpen"/>
|
/// own blind self-flip is the sole legitimate writer — e.g.
|
||||||
/// mirroring a floating chat window's own visibility). CH6a/b REJECT-review
|
/// <see cref="ChatWindowController.SetIndicatorOpen"/> mirroring a floating chat
|
||||||
/// SHOULD-FIX 3: the chat-window 1-4 indicators (<c>0x10000522</c>-<c>0x10000525</c>)
|
/// window's own visibility). CH6a/b REJECT-review SHOULD-FIX 3: the chat-window
|
||||||
/// carry DAT property <c>0x0B</c> (<see cref="ToggleBehavior"/>) = true, so
|
/// 1-4 indicators (<c>0x10000522</c>-<c>0x10000525</c>) carry DAT property
|
||||||
/// without this flag a click flips their Highlight/Normal art with no
|
/// <c>0x0B</c> (<see cref="ToggleBehavior"/>) = true, so without this flag a
|
||||||
/// underlying visibility change — the mirror lies until the next real
|
/// click flips their Highlight/Normal art with no underlying visibility change —
|
||||||
/// toggle. Retail confirms clicking these buttons does nothing
|
/// the mirror lies until the next real toggle.
|
||||||
/// (<c>gmMainChatUI::ListenToElementMessage @0x004CDA80</c> has no case for
|
///
|
||||||
/// their element ids — see <see cref="ChatWindowController.SetIndicatorOpen"/>'s
|
/// <para>
|
||||||
/// doc). Default <see langword="false"/> — every OTHER toggle button (max/min,
|
/// Round 4 (2026-08-10): this stays set for the chat-window indicators even
|
||||||
/// checkboxes) keeps retail's normal click-toggles-itself behavior.
|
/// though clicking them now DOES toggle their floating window (via
|
||||||
|
/// <see cref="ChatWindowController.BindIndicatorClicks"/> — see
|
||||||
|
/// <see cref="ChatWindowController.SetIndicatorOpen"/>'s doc for the full retail
|
||||||
|
/// mechanism this reconciles). The click drives the real toggle, and
|
||||||
|
/// <c>SetIndicatorOpen</c> — the SAME single writer as before — reports the
|
||||||
|
/// outcome back onto <see cref="Selected"/>; this button's own MouseUp-time
|
||||||
|
/// blind flip stays suppressed so the visual never races or diverges from the
|
||||||
|
/// window's actual state. Every OTHER toggle button (max/min, checkboxes) keeps
|
||||||
|
/// retail's normal click-toggles-itself behavior; default
|
||||||
|
/// <see langword="false"/>.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool SuppressSelfToggle { get; set; }
|
public bool SuppressSelfToggle { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Frozen;
|
using System.Collections.Frozen;
|
||||||
|
using AcDream.Core.Chat;
|
||||||
|
|
||||||
namespace AcDream.UI.Abstractions.Panels.Chat;
|
namespace AcDream.UI.Abstractions.Panels.Chat;
|
||||||
|
|
||||||
|
|
@ -53,20 +54,125 @@ namespace AcDream.UI.Abstractions.Panels.Chat;
|
||||||
/// caught that <c>/help death</c> printed an acdream META-MESSAGE ("This is
|
/// caught that <c>/help death</c> printed an acdream META-MESSAGE ("This is
|
||||||
/// a retail help-topic group; acdream has not yet extracted its exact
|
/// a retail help-topic group; acdream has not yet extracted its exact
|
||||||
/// retail listing text…") instead of retail's real listing — this was the
|
/// retail listing text…") instead of retail's real listing — this was the
|
||||||
/// class-doc overclaim's SHARP end. All 7 <c>HelpXxxGroup</c> nodes are now
|
/// class-doc overclaim's SHARP end. All 7 <c>HelpXxxGroup</c> nodes were
|
||||||
/// resolved: <see cref="DeathGroupDetail"/>, <see cref="StatusGroupDetail"/>,
|
/// resolved that round: <see cref="DeathGroupDetail"/>,
|
||||||
/// <see cref="TextGroupDetail"/>, and <see cref="AllegiancesGroupDetail"/>
|
/// <see cref="StatusGroupDetail"/>, <see cref="TextGroupDetail"/>, and
|
||||||
/// are COMPLETE verbatim listings; <see cref="ChannelsGroupDetail"/>,
|
/// <see cref="AllegiancesGroupDetail"/> were COMPLETE verbatim listings;
|
||||||
/// <see cref="ChattingGroupDetail"/>, and <see cref="CommandsGroupDetail"/>
|
/// <see cref="ChannelsGroupDetail"/>, <see cref="ChattingGroupDetail"/>, and
|
||||||
/// are PARTIAL (their own summary line, plus — for chatting — 7 of 8
|
/// <see cref="CommandsGroupDetail"/> were left PARTIAL with an UNVERIFIED
|
||||||
/// entries) with an explicit UNVERIFIED note citing
|
/// note. 7 of the ~35 channel one-liners (a/guild/gu, general/cg, trade/ct,
|
||||||
/// <c>ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290</c> for
|
/// lfg/clfg, roleplay/crp, society/soc, olthoi/o) were verbatim too; the
|
||||||
/// the un-decodable remainder, never a guess. 7 of the ~35 channel
|
/// rest (fellowship/monarch/patron/vassals/covassal family, as standalone
|
||||||
/// one-liners (a/guild/gu, general/cg, trade/ct, lfg/clfg, roleplay/crp,
|
/// <c>/help f</c>-style lookups) still route through the unresolved
|
||||||
/// society/soc, olthoi/o) are now verbatim too; the rest (fellowship/
|
/// mechanism below and remain acdream summaries — that part of the gap is
|
||||||
/// monarch/patron/vassals/covassal family) route through the same
|
/// unchanged this round; see <c>ByVerb</c> and
|
||||||
/// unresolved mechanism and remain acdream summaries. See the remarks on
|
/// <c>RetailCommandHelpTableTests.ChannelVerb_StillAcdreamSummary_UnresolvedChannelHackFamily</c>
|
||||||
/// <see cref="ChannelsGroupSummary"/> for the full extraction method.
|
/// for the record of exactly which verbs. (Those verbs' OWN standalone help
|
||||||
|
/// registration was never confirmed independently of the group-listing
|
||||||
|
/// mechanism decoded below, so they are left untouched rather than
|
||||||
|
/// speculatively upgraded.)
|
||||||
|
/// </para>
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// <b>Round 4 (2026-08-10): the 3 PARTIAL groups are now COMPLETE, and the
|
||||||
|
/// meta-notice text itself is GONE from every user-visible string.</b> The
|
||||||
|
/// blocker was never really <c>HelpStupidChannelHack @0x0056f290</c>'s own
|
||||||
|
/// code — the fragments Binary Ninja renders as vtable-slot dereferences
|
||||||
|
/// (<c>&ClientCommunicationSystem::`vftable'.RecvNotice_StartBarberNotice</c>
|
||||||
|
/// etc.) are the SAME pooled/mislabeled-data artifact this campaign has hit
|
||||||
|
/// before (AP-186's own precedent): they are DATA pointers into
|
||||||
|
/// <c>.rdata</c>, not real vtable dispatch, and dereferencing their actual
|
||||||
|
/// operand addresses (found by reading <c>HelpStupidChannelHack</c>'s own
|
||||||
|
/// disassembly for the <c>push imm32</c> immediately preceding each
|
||||||
|
/// constructor call, since BN's line-grouped rendering hides the true
|
||||||
|
/// instruction order) resolves cleanly: the function builds
|
||||||
|
/// <c>"@" + tag + " - Sends a broadcast to your " + ChannelName + ".\n"</c>
|
||||||
|
/// where <c>tag</c> is a single character sliced out of a shared wide
|
||||||
|
/// literal <c>U"fvpca"</c> (reading a WIDE string through a NARROW
|
||||||
|
/// <c>char*</c> naturally truncates to one character at the first zero high
|
||||||
|
/// byte — the "hack" the function's own retail name calls out) and
|
||||||
|
/// <c>ChannelName</c> comes from <c>ChannelSystem::GetChannelName</c>'s own
|
||||||
|
/// literal switch table (also swept directly, not guessed:
|
||||||
|
/// <c>data_0x7d0478</c>="Allegiance", plus "Co-vassals"/"Monarch"/"Patron"/
|
||||||
|
/// "Vassals"/"Fellowship" already visible in that function's straight-line
|
||||||
|
/// decompiled body). See <see cref="AllegianceBroadcastLine"/> through
|
||||||
|
/// <see cref="FellowshipBroadcastLine"/> for the six resolved lines and
|
||||||
|
/// their per-line address citations.
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// With that decoded, <see cref="ChannelsGroupDetail"/> (which is ENTIRELY
|
||||||
|
/// six <c>HelpStupidChannelHack</c> calls, confirmed by reading
|
||||||
|
/// <c>HelpChannelsGroup</c>'s Detail branch directly) is now a COMPLETE
|
||||||
|
/// verbatim listing. <see cref="ChattingGroupDetail"/> needed one more
|
||||||
|
/// piece: its "@reply" entry does not call a per-tag summary — it calls
|
||||||
|
/// <c>ClientCommunicationSystem::HelpReply @0x00577A50</c> with
|
||||||
|
/// <c>Summary_HelpType</c>, and that function (read directly, not assumed)
|
||||||
|
/// unconditionally concatenates THREE lines regardless of which tag it was
|
||||||
|
/// passed (reply, then pr, then mr) — a genuine retail quirk, ported
|
||||||
|
/// faithfully per CLAUDE.md's "do not fix the decompiled code" rule; see
|
||||||
|
/// <see cref="ReplySummaryLine"/>/<see cref="PatronReplySummaryLine"/>/
|
||||||
|
/// <see cref="MonarchReplySummaryLine"/>. <see cref="CommandsGroupDetail"/>
|
||||||
|
/// (retail's <c>HelpAllGroup</c>, the "lists all commands" topic) is a
|
||||||
|
/// straight-line concatenation of EVERY other group's Detail branch plus a
|
||||||
|
/// handful of its own short one-liners (all swept directly from
|
||||||
|
/// <c>0x0057E7F0</c>-<c>0x0057EBA0</c>) — including a CONFIRMED retail
|
||||||
|
/// duplicate (the saveui/loadui short-summary pair appears twice with no
|
||||||
|
/// conditional between the two occurrences, ported as-is) and one line
|
||||||
|
/// (<c>@say</c>) whose own retail literal has NO trailing newline, unlike
|
||||||
|
/// every sibling line — also ported as-is; retail's own output runs
|
||||||
|
/// <c>@say</c> straight into <c>@tell</c> with no line break. Zero
|
||||||
|
/// remaining UNVERIFIED notes in any of the three former PARTIAL groups.
|
||||||
|
/// Closes ISSUES.md #364.
|
||||||
|
/// </para>
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// <b>The meta-marker sweep.</b> Every honesty marker that had leaked into
|
||||||
|
/// USER-VISIBLE text — the "[IMPLEMENTED]"/"[IMPLEMENTED, also @x]" tags
|
||||||
|
/// littered through <see cref="AllegianceOverview"/>/<see cref="HouseOverview"/>,
|
||||||
|
/// the "NOT YET IMPLEMENTED in acdream" sentences appended to
|
||||||
|
/// <see cref="Day"/>/<see cref="Log"/>/<see cref="Render"/>/<see cref="Motd"/>,
|
||||||
|
/// and the "Subcommands NOT marked [IMPLEMENTED]…" trailer on both overview
|
||||||
|
/// blocks — is now GONE from the strings themselves. Per CLAUDE.md, honesty
|
||||||
|
/// markers belong in code comments and the divergence register, never in
|
||||||
|
/// text a player can read. Every one of those consts is now checked against
|
||||||
|
/// the raw retail data dump the pseudo-C file itself carries for long
|
||||||
|
/// consolidated string tables (e.g. <c>char const data_7dfb58[0x879] =
|
||||||
|
/// "@allegiance boot …</c>) — those blocks are ALREADY fully decoded by
|
||||||
|
/// Binary Ninja with no BN artifact to work around, so they were read
|
||||||
|
/// directly rather than re-swept, and confirmed the "[IMPLEMENTED]" tags
|
||||||
|
/// were 100% acdream additions with no retail counterpart (not even a
|
||||||
|
/// stray character). <see cref="Log"/> and <see cref="Motd"/> also picked
|
||||||
|
/// up MORE retail text than they had before while their meta tails were
|
||||||
|
/// being removed — the prior extractions had silently dropped extra
|
||||||
|
/// sentences/paragraphs present in the same data block (<c>data_7df7f8</c>
|
||||||
|
/// and <c>data_7da300</c> respectively); <see cref="Render"/>'s previous
|
||||||
|
/// text was entirely acdream-authored ("Forwards to the client's
|
||||||
|
/// render-option surface…") and is replaced with the REAL retail usage
|
||||||
|
/// string (<c>render_option_usage</c> @ <c>0x0079B878</c>, found by
|
||||||
|
/// grepping the pseudo-C for a literal <c>@render</c> string instead of
|
||||||
|
/// trusting the earlier "not decodable" note).
|
||||||
|
/// </para>
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// <b><c>messagetypes</c> is now a real construction, not an acdream
|
||||||
|
/// summary.</b> Retail's <c>HelpMessageTypes @0x0056E5D0</c> really does
|
||||||
|
/// build its text at runtime — but by walking a FIXED whitelist
|
||||||
|
/// (<c>LogTextTypeEnumMapper::IsLegalChannel @0x006AFF40</c>, 14 ids) and a
|
||||||
|
/// FIXED per-id name table (<c>LogTextTypeEnumMapper::LogTextTypeToString
|
||||||
|
/// @0x006AFF90</c>, a literal switch), joined with <c>", "</c>
|
||||||
|
/// (<c>data_0x794334</c>) and wrapped in <c>"Squelch channels are as
|
||||||
|
/// follows:\n %s\n"</c> (<c>gmCCommunicationSystem::GetListofSquelchChannels
|
||||||
|
/// @0x00589EB0</c> /
|
||||||
|
/// <c>gmCCommunicationSystem::ConvertSQToPString @0x00589A80</c>'s
|
||||||
|
/// <c>arg3!=0</c> "enumerate every legal channel" branch — the
|
||||||
|
/// <c>SquelchInfo</c> it iterates is a SYNTHETIC all-squelched object built
|
||||||
|
/// solely to walk every legal id, not the player's actual squelch state).
|
||||||
|
/// Since the whitelist and the name table are both fixed, the output is
|
||||||
|
/// itself a compile-time constant — but <see cref="MessageTypesDetail"/>
|
||||||
|
/// PORTS the construction (the ordered id/name table +
|
||||||
|
/// <see cref="BuildMessageTypesDetail"/>'s exact join/prefix logic) rather
|
||||||
|
/// than hand-authoring the already-known joined string, so a future
|
||||||
|
/// whitelist change is a one-line table edit instead of a re-derivation.
|
||||||
/// </para>
|
/// </para>
|
||||||
///
|
///
|
||||||
/// <para>
|
/// <para>
|
||||||
|
|
@ -188,20 +294,65 @@ public static class RetailCommandHelpTable
|
||||||
public const string PatronReply =
|
public const string PatronReply =
|
||||||
"@pr <text> - Sends the text to the last vassal who used @p to send you a message.";
|
"@pr <text> - Sends the text to the last vassal who used @p to send you a message.";
|
||||||
|
|
||||||
// acclient_2013_pseudo_c.txt:1031093 (data_7de280).
|
// acclient_2013_pseudo_c.txt:1031093 (char const data_7de280[0x77] =
|
||||||
public const string Day =
|
// "@day - A…\n", 0) -- BN already fully decodes this consolidated data
|
||||||
"@day - A toggle that lightens the outdoor landscape. Note that this command may take several seconds to take effect. NOT YET IMPLEMENTED in acdream — no sky/time-of-day override hook exists yet; the command reaches the server as literal text.";
|
// block, no vtable-slot artifact to work around; read directly rather
|
||||||
|
// than re-swept. Round 4 (2026-08-10): dropped the acdream
|
||||||
|
// "NOT YET IMPLEMENTED in acdream" tail -- honesty markers belong in
|
||||||
|
// comments/the register, never in text a player can read (CLAUDE.md).
|
||||||
|
// acdream's client-side "@day" implementation status, if any, is
|
||||||
|
// tracked in the divergence register, not here. Shared with
|
||||||
|
// StatusGroupDetail's own "@day" line -- same data block, same text.
|
||||||
|
public const string DayLine =
|
||||||
|
"@day - A toggle that lightens the outdoor landscape. Note that this command may take several seconds to take effect. \n";
|
||||||
|
|
||||||
// acclient_2013_pseudo_c.txt:1031192-1031198 (data_7df7f8/data_7dfac4).
|
public const string Day = DayLine;
|
||||||
|
|
||||||
|
// acclient_2013_pseudo_c.txt:1031192-1031198 (char const
|
||||||
|
// data_7df7f8[0x2ca] = "@log <name> - …\n" "@log AClog.txt - …\n" "@log
|
||||||
|
// - …\n", 0) -- read directly off the pseudo-C's own consolidated data
|
||||||
|
// dump. Round 4 (2026-08-10): the prior extraction had silently
|
||||||
|
// dropped the middle sentence ("Choose the file you are copying to by
|
||||||
|
// naming it in the command.") and BOTH trailing paragraphs (the
|
||||||
|
// AClog.txt example and the "stop the process" line) in addition to
|
||||||
|
// carrying the "NOT YET IMPLEMENTED" meta tail -- all three retail
|
||||||
|
// sentences are restored here, verbatim, with no acdream commentary.
|
||||||
public const string Log =
|
public const string Log =
|
||||||
"@log <name> - Echoes chat text to a logfile. All the information that appears in your chat window after you type this command will be copied into a text file. If this file already exists, it will add the additional text to the end of it. To turn off logging, simply retype @log. NOT YET IMPLEMENTED in acdream — the command reaches the server as literal text.";
|
"@log <name> - Echoes chat text to a logfile. All the information that appears in your chat window after you type this command will be copied into a text file. Choose the file you are copying to by naming it in the command. If this file already exists, it will add the additional text to the end of it. To turn off logging, simply retype @log.\n"
|
||||||
|
+ "@log AClog.txt - Echoes chat text to a log file named Aclog.txt in your Asheron's Call directory. After you use this command, all the information that appears in your chat window will be written to a file in your Asheron's Call directory named Aclog.txt.\n"
|
||||||
|
+ "@log - If you are currently copying the text in your chat window to a logfile, this command will stop the process.\n";
|
||||||
|
|
||||||
|
// Round 4 (2026-08-10): the prior text here ("Forwards to the client's
|
||||||
|
// render-option surface (retail: SmartBox::HandleRenderOption). NOT YET
|
||||||
|
// IMPLEMENTED in acdream…") was entirely acdream-authored, not retail
|
||||||
|
// text -- the class remarks previously (incorrectly) called @render's
|
||||||
|
// option list "not decodable". A direct grep for a literal "@render"
|
||||||
|
// string in the pseudo-C finds it: `char const render_option_usage[0x98]
|
||||||
|
// @ 0x0079B878 = "Usage:\n@render <option> <value>\n radius # :
|
||||||
|
// set landscape radius (between 5 and 25)\n fov # : set
|
||||||
|
// field of view (between 10 and 160)\n"` -- the real retail usage text
|
||||||
|
// shown by SmartBox::HandleRenderOption for bad/missing arguments
|
||||||
|
// (acclient_2013_pseudo_c.txt:94759/94767), swept verbatim, no
|
||||||
|
// paraphrase.
|
||||||
public const string Render =
|
public const string Render =
|
||||||
"@render [options] - Forwards to the client's render-option surface (retail: SmartBox::HandleRenderOption). NOT YET IMPLEMENTED in acdream — there is no SmartBox equivalent; the command reaches the server as literal text.";
|
"Usage:\n"
|
||||||
|
+ "@render <option> <value>\n"
|
||||||
|
+ " radius # : set landscape radius (between 5 and 25)\n"
|
||||||
|
+ " fov # : set field of view (between 10 and 160)\n";
|
||||||
|
|
||||||
// acclient_2013_pseudo_c.txt:1030670-1030676 (data_7da300/data_7da3e0).
|
// acclient_2013_pseudo_c.txt:1030670-1030676 (char const
|
||||||
|
// data_7da300[0xdb] = "@allegiance motd - …\n" "@allegiance motd set
|
||||||
|
// …\n" "@allegiance motd clear- …\n", 0) -- read directly off the
|
||||||
|
// pseudo-C's own consolidated data dump. Round 4 (2026-08-10): the
|
||||||
|
// prior extraction paraphrased this into a single acdream sentence and
|
||||||
|
// appended a "NOT YET IMPLEMENTED in acdream (TS-68)" meta tail; restored
|
||||||
|
// to retail's real 3-line shape, including the "clear-" (no space
|
||||||
|
// before the dash) typo confirmed present in the raw data -- ported as
|
||||||
|
// found per CLAUDE.md, not "fixed".
|
||||||
public const string Motd =
|
public const string Motd =
|
||||||
"@allegiance motd - Displays the message of the day for your allegiance. @allegiance motd set <text> - Sets the MOTD. Can only be used by monarchs. @allegiance motd clear - Clears the MOTD. Can only be used by monarchs. NOT YET IMPLEMENTED in acdream (TS-68) — the command reaches the server as literal text.";
|
"@allegiance motd - Displays the message of the day for your allegiance.\n"
|
||||||
|
+ "@allegiance motd set <text> - Sets the MOTD. Can only be used by monarchs.\n"
|
||||||
|
+ "@allegiance motd clear- Clears the MOTD. Can only be used by monarchs.\n";
|
||||||
|
|
||||||
// Split out of AllegianceOverview's own data block (data_0x7dfb58) at
|
// Split out of AllegianceOverview's own data block (data_0x7dfb58) at
|
||||||
// the consolidated review round (2026-08-10) so the const declaration
|
// the consolidated review round (2026-08-10) so the const declaration
|
||||||
|
|
@ -218,18 +369,30 @@ public static class RetailCommandHelpTable
|
||||||
// extraction dropped — confirmed present in the SAME data block
|
// extraction dropped — confirmed present in the SAME data block
|
||||||
// (data_0x7dfb58) this const already cites, not a new source. Restored
|
// (data_0x7dfb58) this const already cites, not a new source. Restored
|
||||||
// verbatim, including the retail leading space.
|
// verbatim, including the retail leading space.
|
||||||
|
//
|
||||||
|
// Round 4 (2026-08-10): every "[IMPLEMENTED]"/"[IMPLEMENTED, also
|
||||||
|
// @x]"/"[IMPLEMENTED as @ab]" tag AND the trailing "Subcommands NOT
|
||||||
|
// marked [IMPLEMENTED]…" sentence below were acdream additions with NO
|
||||||
|
// retail counterpart — confirmed by reading data_7dfb58's own raw
|
||||||
|
// consolidated dump (acclient_2013_pseudo_c.txt:1031210-1031230), which
|
||||||
|
// BN already fully decodes with no vtable-slot artifact to work around.
|
||||||
|
// The pristine dump's "broadcast" line ends "...Also: @ab\n" (no
|
||||||
|
// bracket) and the "hometown" line ends "...tied to one.\n" (no
|
||||||
|
// bracket, no alias mention at all) — both removed here. TS-68's
|
||||||
|
// implemented-vs-not-yet-implemented tracking lives in ISSUES.md and
|
||||||
|
// the divergence register now, not in this user-visible string.
|
||||||
public const string AllegianceOverview =
|
public const string AllegianceOverview =
|
||||||
"@allegiance - Commands to help manage your allegiance.\n"
|
"@allegiance - Commands to help manage your allegiance.\n"
|
||||||
+ "@allegiance boot [-account] <name> - Removes a character from your allegiance.\n"
|
+ "@allegiance boot [-account] <name> - Removes a character from your allegiance.\n"
|
||||||
+ "@allegiance ban <add/remove> <name> - Bans all characters on the given character's account from your allegiance (and boots them too!)\n"
|
+ "@allegiance ban <add/remove> <name> - Bans all characters on the given character's account from your allegiance (and boots them too!)\n"
|
||||||
+ "@allegiance ban list - List the characters whose accounts are banned from your allegiance.\n"
|
+ "@allegiance ban list - List the characters whose accounts are banned from your allegiance.\n"
|
||||||
+ AllegianceWarningLine
|
+ AllegianceWarningLine
|
||||||
+ "@allegiance info <name> - Requests information on a member of your allegiance. [IMPLEMENTED]\n"
|
+ "@allegiance info <name> - Requests information on a member of your allegiance.\n"
|
||||||
+ "@allegiance chat <on/off> - Turn allegiance chat on and off.\n"
|
+ "@allegiance chat <on/off> - Turn allegiance chat on and off.\n"
|
||||||
+ "@allegiance chat kick <name>[, <reason>] - Kick a player temporarily from the allegiance chat room.\n"
|
+ "@allegiance chat kick <name>[, <reason>] - Kick a player temporarily from the allegiance chat room.\n"
|
||||||
+ "@allegiance chat gag <name> - Gags a player so that they cannot see or speak in the allegiance chat room for 5 minutes.\n"
|
+ "@allegiance chat gag <name> - Gags a player so that they cannot see or speak in the allegiance chat room for 5 minutes.\n"
|
||||||
+ "@allegiance chat ungag <name> - Ungags a gagged allegiance member so that they may once again see and speak in the allegiance chat room.\n"
|
+ "@allegiance chat ungag <name> - Ungags a gagged allegiance member so that they may once again see and speak in the allegiance chat room.\n"
|
||||||
+ "@allegiance broadcast <message> - Broadcast a message to the entire allegiance. Limited to 10/day. Also: @ab [IMPLEMENTED as @ab]\n"
|
+ "@allegiance broadcast <message> - Broadcast a message to the entire allegiance. Limited to 10/day. Also: @ab\n"
|
||||||
+ "@allegiance officer <add/set> <level #> <name> - Assigns the position of officer, with the given level of permissions, to the named character.\n"
|
+ "@allegiance officer <add/set> <level #> <name> - Assigns the position of officer, with the given level of permissions, to the named character.\n"
|
||||||
+ "@allegiance officer <remove> <name> - Removed the named character as an allegiance officer.\n"
|
+ "@allegiance officer <remove> <name> - Removed the named character as an allegiance officer.\n"
|
||||||
+ "@allegiance officer clear - Clears all officer positions.\n"
|
+ "@allegiance officer clear - Clears all officer positions.\n"
|
||||||
|
|
@ -240,15 +403,35 @@ public static class RetailCommandHelpTable
|
||||||
+ "@allegiance name <set/clear> - Displays, sets, or clears the name of your allegiance.\n"
|
+ "@allegiance name <set/clear> - Displays, sets, or clears the name of your allegiance.\n"
|
||||||
+ "@allegiance lock <on/off/toggle/check> - Locks, unlocks, or displays the locked state of your allegiance.\n"
|
+ "@allegiance lock <on/off/toggle/check> - Locks, unlocks, or displays the locked state of your allegiance.\n"
|
||||||
+ "@allegiance lock bypass <clear/name> - Sets, clears, or displays a single character as an approved vassal. That character may then swear into a locked allegiance.\n"
|
+ "@allegiance lock bypass <clear/name> - Sets, clears, or displays a single character as an approved vassal. That character may then swear into a locked allegiance.\n"
|
||||||
+ "@allegiance hometown - Recalls you to your allegiance bindstone, if your allegiance has tied to one. [IMPLEMENTED, also @alh/@ah]\n"
|
+ "@allegiance hometown - Recalls you to your allegiance bindstone, if your allegiance has tied to one.\n"
|
||||||
+ "@allegiance motd - Displays or sets the message of the day for your allegiance.\n"
|
+ "@allegiance motd - Displays or sets the message of the day for your allegiance.\n";
|
||||||
+ "Subcommands NOT marked [IMPLEMENTED] are not yet locally executed (TS-68) and reach the server as literal text.";
|
|
||||||
|
|
||||||
// acclient_2013_pseudo_c.txt:1031041/1031045 (data_7dd908/data_7dd968)
|
// acclient_2013_pseudo_c.txt:1031041 (char const data_7dd908[0x5c] =
|
||||||
// plus the full per-subcommand block at 1031017-1031037 (data_7dd3d0).
|
// "@house - Commands that help you manage your house, including guest
|
||||||
|
// and storage management.\n", 0) -- the group-intro one-liner, shared
|
||||||
|
// verbatim below with CommandsGroupDetail's own "@house" fragment (same
|
||||||
|
// underlying retail string, per HelpAllGroup's own direct call to
|
||||||
|
// PStringBase::PStringBase with this exact literal).
|
||||||
|
public const string HouseOneLiner =
|
||||||
|
"@house - Commands that help you manage your house, including guest and storage management.\n";
|
||||||
|
|
||||||
|
// acclient_2013_pseudo_c.txt:1031017-1031037 (char const
|
||||||
|
// data_7dd3d0[0x534] = "@house abandon - …\n" … "@house available - See
|
||||||
|
// @hslist\n", 0) -- read directly off the pseudo-C's own consolidated
|
||||||
|
// data dump; BN already fully decodes this block with no vtable-slot
|
||||||
|
// artifact to work around. Round 4 (2026-08-10): every
|
||||||
|
// "[IMPLEMENTED]"/"[IMPLEMENTED, also @x]"/"[IMPLEMENTED, alias of
|
||||||
|
// mansion_recall]" tag AND the trailing "Subcommands NOT marked
|
||||||
|
// [IMPLEMENTED]…" sentence below were acdream additions with no retail
|
||||||
|
// counterpart. The "@house available" line's retail literal is exactly
|
||||||
|
// "@house available - See @hslist\n" — no trailing period, and none of
|
||||||
|
// the bracketed "[see @hslist, IMPLEMENTED]" text the old version
|
||||||
|
// appended. TS-68's implemented-vs-not-yet-implemented tracking lives
|
||||||
|
// in ISSUES.md and the divergence register now, not in this
|
||||||
|
// user-visible string.
|
||||||
public const string HouseOverview =
|
public const string HouseOverview =
|
||||||
"@house - Commands that help you manage your house, including guest and storage management.\n"
|
HouseOneLiner
|
||||||
+ "@house abandon - Abandons your house. [IMPLEMENTED]\n"
|
+ "@house abandon - Abandons your house.\n"
|
||||||
+ "@house boot <name> - Removes a player from your house.\n"
|
+ "@house boot <name> - Removes a player from your house.\n"
|
||||||
+ "@house boot -all - Removes everyone from your house.\n"
|
+ "@house boot -all - Removes everyone from your house.\n"
|
||||||
+ "@house guest add <name> - Adds players to your house guest list.\n"
|
+ "@house guest add <name> - Adds players to your house guest list.\n"
|
||||||
|
|
@ -257,7 +440,7 @@ public static class RetailCommandHelpTable
|
||||||
+ "@house guest remove_allegiance - Removes your allegiance from the guest list.\n"
|
+ "@house guest remove_allegiance - Removes your allegiance from the guest list.\n"
|
||||||
+ "@house guest remove_all - Removes all guests from your house guest list.\n"
|
+ "@house guest remove_all - Removes all guests from your house guest list.\n"
|
||||||
+ "@house guest list - Shows the current guest list.\n"
|
+ "@house guest list - Shows the current guest list.\n"
|
||||||
+ "@house recall - Teleports you to your house. [IMPLEMENTED, also @hor/@hr]\n"
|
+ "@house recall - Teleports you to your house.\n"
|
||||||
+ "@house storage add <name> - Gives a player permission to use your house storage.\n"
|
+ "@house storage add <name> - Gives a player permission to use your house storage.\n"
|
||||||
+ "@house storage remove <name> - Removes permission to use your house storage from a player.\n"
|
+ "@house storage remove <name> - Removes permission to use your house storage from a player.\n"
|
||||||
+ "@house storage add_allegiance - Grants storage permission to your allegiance.\n"
|
+ "@house storage add_allegiance - Grants storage permission to your allegiance.\n"
|
||||||
|
|
@ -266,10 +449,9 @@ public static class RetailCommandHelpTable
|
||||||
+ "@house open - Creates an open house.\n"
|
+ "@house open - Creates an open house.\n"
|
||||||
+ "@house close - Closes your house.\n"
|
+ "@house close - Closes your house.\n"
|
||||||
+ "@house hooks on|off - Makes the hooks in your house visible or invisible.\n"
|
+ "@house hooks on|off - Makes the hooks in your house visible or invisible.\n"
|
||||||
+ "@house mansion_recall - Teleports you to your allegiance mansion or villa. [IMPLEMENTED, also @hom/@hoa]\n"
|
+ "@house mansion_recall - Teleports you to your allegiance mansion or villa.\n"
|
||||||
+ "@house alleg_recall - Teleports you to your allegiance mansion or villa. [IMPLEMENTED, alias of mansion_recall]\n"
|
+ "@house alleg_recall - Teleports you to your allegiance mansion or villa.\n"
|
||||||
+ "@house available - See @hslist [see @hslist, IMPLEMENTED]\n"
|
+ "@house available - See @hslist\n";
|
||||||
+ "Subcommands NOT marked [IMPLEMENTED] are not yet locally executed (TS-68) and reach the server as literal text.";
|
|
||||||
|
|
||||||
// The 7 retail-registered "group index" nodes — retail registers them
|
// The 7 retail-registered "group index" nodes — retail registers them
|
||||||
// with a NULL func (like @mr/@pr); typing one bare reaches the server.
|
// with a NULL func (like @mr/@pr); typing one bare reaches the server.
|
||||||
|
|
@ -288,23 +470,17 @@ public static class RetailCommandHelpTable
|
||||||
// group's own one-line summary> }`; the exact byte extent (this
|
// group's own one-line summary> }`; the exact byte extent (this
|
||||||
// function's start VA to the next function's start VA) was read from
|
// function's start VA to the next function's start VA) was read from
|
||||||
// the pseudo-C's own function-header addresses, and every extent was
|
// the pseudo-C's own function-header addresses, and every extent was
|
||||||
// swept with --ascii-only. Four groups decode COMPLETELY (every
|
// swept with --ascii-only. Four groups decoded COMPLETELY that round
|
||||||
// subcommand summary is either an inline literal in the group function
|
// (every subcommand summary is either an inline literal in the group
|
||||||
// itself or a direct `HelpXxx(this, Summary_HelpType, "@tag", arg4)`
|
// function itself or a direct `HelpXxx(this, Summary_HelpType, "@tag",
|
||||||
// call whose OWN summary text was independently swept from HelpXxx's
|
// arg4)` call whose OWN summary text was independently swept from
|
||||||
// own extent): death, status, text, allegiances. Three groups
|
// HelpXxx's own extent): death, status, text, allegiances.
|
||||||
// (commands, channels, chatting) delegate part or all of their detail
|
//
|
||||||
// text to ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290,
|
// Round 4 (2026-08-10): the remaining three (commands, channels,
|
||||||
// which builds its output from THREE string fragments Binary Ninja
|
// chatting) are ALSO now COMPLETE — see the class remarks' "3 PARTIAL
|
||||||
// mis-attributes to unrelated vtable slots
|
// groups are now COMPLETE" paragraph for the full HelpStupidChannelHack
|
||||||
// (`ClientCommunicationSystem::\`vftable'.RecvNotice_StartBarberNotice`
|
// decode. Nothing in this file emits an UNVERIFIED note for any of the
|
||||||
// etc. — the classic BN pooled/mislabeled-data artifact, not real
|
// 7 groups any more.
|
||||||
// vtable dispatch) concatenated around a live channel-name lookup
|
|
||||||
// (`ChannelSystem::GetChannelName`) — genuinely not decodable via this
|
|
||||||
// sweep with confidence. Those three keep an honest UNVERIFIED fallback
|
|
||||||
// (still real retail text where available — each group's own one-line
|
|
||||||
// summary IS fully decoded and included) rather than inventing the
|
|
||||||
// missing portion. Tracked as ISSUES.md #364.
|
|
||||||
private const string ChannelsGroupSummary =
|
private const string ChannelsGroupSummary =
|
||||||
"@help channels - How to communicate with people in your allegiance or fellowship.";
|
"@help channels - How to communicate with people in your allegiance or fellowship.";
|
||||||
|
|
||||||
|
|
@ -318,9 +494,6 @@ public static class RetailCommandHelpTable
|
||||||
private const string CommandsGroupSummary =
|
private const string CommandsGroupSummary =
|
||||||
"@help commands - Lists all commands.";
|
"@help commands - Lists all commands.";
|
||||||
|
|
||||||
private const string GroupDetailUnverifiedSuffix =
|
|
||||||
" UNVERIFIED: the rest of this group's retail listing routes through ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290, which builds its text from BN-mislabeled data fragments around a live channel-name lookup — not decodable with confidence from a static string sweep. See docs/research/2026-08-09-chat-retail-command-registry.md §5 and ISSUES.md #364.";
|
|
||||||
|
|
||||||
// acclient_2013_pseudo_c.txt:391365-391436 (HelpDeathGroup, decomp
|
// acclient_2013_pseudo_c.txt:391365-391436 (HelpDeathGroup, decomp
|
||||||
// §5 sweep, addresses 0x57b913-0x57ba56). The Detail branch is a
|
// §5 sweep, addresses 0x57b913-0x57ba56). The Detail branch is a
|
||||||
// straight-line concatenation with NO leading group-summary line (that
|
// straight-line concatenation with NO leading group-summary line (that
|
||||||
|
|
@ -344,7 +517,7 @@ public static class RetailCommandHelpTable
|
||||||
public const string StatusGroupDetail =
|
public const string StatusGroupDetail =
|
||||||
"@age - Displays your total gameplay time.\n"
|
"@age - Displays your total gameplay time.\n"
|
||||||
+ "@birth - Displays when your character was created.\n"
|
+ "@birth - Displays when your character was created.\n"
|
||||||
+ "@day - A toggle that lightens the outdoor landscape. Note that this command may take several seconds to take effect. \n"
|
+ DayLine
|
||||||
+ "@endurance - Explains how endurance affects your character.\n"
|
+ "@endurance - Explains how endurance affects your character.\n"
|
||||||
+ "@framerate - Toggles the framerate display.\n"
|
+ "@framerate - Toggles the framerate display.\n"
|
||||||
+ "@loc - Displays your current position.\n"
|
+ "@loc - Displays your current position.\n"
|
||||||
|
|
@ -366,45 +539,132 @@ public static class RetailCommandHelpTable
|
||||||
"@allegiance - Commands to help manage your allegiance.\n"
|
"@allegiance - Commands to help manage your allegiance.\n"
|
||||||
+ "@allegiance motd - Displays or sets the message of the day for your allegiance, see @help motd for more information.\n";
|
+ "@allegiance motd - Displays or sets the message of the day for your allegiance, see @help motd for more information.\n";
|
||||||
|
|
||||||
// Partial: own summary (fully verbatim, swept) + the honest UNVERIFIED
|
// ── HelpStupidChannelHack, decoded (round 4, 2026-08-10) ────────────
|
||||||
// note for the HelpStupidChannelHack-delegated remainder. HelpChannelsGroup's
|
//
|
||||||
// Detail branch (acclient_2013_pseudo_c.txt:387166-387194) is ENTIRELY
|
// ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290 builds
|
||||||
// 6 HelpStupidChannelHack calls -- none of its own text is decodable.
|
// one line as `"@" + tag + " - Sends a broadcast to your " +
|
||||||
public const string ChannelsGroupDetail =
|
// ChannelName + ".\n"`. Read directly off the function's own
|
||||||
ChannelsGroupSummary + GroupDetailUnverifiedSuffix;
|
// disassembly (the `push imm32` immediately preceding each
|
||||||
|
// PStringBase::PStringBase constructor call, since BN's line-grouped
|
||||||
|
// pseudo-C hides the real instruction order and mislabels each pushed
|
||||||
|
// address as an unrelated vtable slot):
|
||||||
|
// - "@" @ data_0x7d0424, " - Sends a broadcast to your " @
|
||||||
|
// data_0x7d0428, trailing ".\n" @ data_0x7d0448 -- all three swept
|
||||||
|
// via tools/pdb-extract/sweep_weenie_strings.py --anchor against
|
||||||
|
// the PDB-paired C:\Users\erikn\Downloads\acclient.exe.
|
||||||
|
// - `tag` is one character sliced out of a shared wide literal
|
||||||
|
// `U"fvpca"` (plus one more, `U"mh,."`, for 'm') -- reading a WIDE
|
||||||
|
// string through a NARROW char* naturally truncates to one
|
||||||
|
// character at the first zero high byte, which is the "hack" the
|
||||||
|
// function's own retail name calls out.
|
||||||
|
// - `ChannelName` comes from `ChannelSystem::GetChannelID(tag)` (read
|
||||||
|
// directly: 'a'=0x2000000, 'c'=0x1000000, 'm'=0x4000, 'p'=0x2000,
|
||||||
|
// 'v'=0x1000, 'f'=0x800) then `ChannelSystem::GetChannelName(id)`'s
|
||||||
|
// own literal switch table (also read directly, not guessed):
|
||||||
|
// 0x2000="Patron", 0x4000="Monarch", 0x800="Fellowship",
|
||||||
|
// 0x1000="Vassals", 0x1000000="Co-vassals", and 0x2000000 resolves
|
||||||
|
// through one more mislabeled vtable slot that sweeps to
|
||||||
|
// data_0x7d0478="Allegiance".
|
||||||
|
public const string AllegianceBroadcastLine = "@a - Sends a broadcast to your Allegiance.\n";
|
||||||
|
public const string CovassalBroadcastLine = "@c - Sends a broadcast to your Co-vassals.\n";
|
||||||
|
public const string MonarchBroadcastLine = "@m - Sends a broadcast to your Monarch.\n";
|
||||||
|
public const string PatronBroadcastLine = "@p - Sends a broadcast to your Patron.\n";
|
||||||
|
public const string VassalBroadcastLine = "@v - Sends a broadcast to your Vassals.\n";
|
||||||
|
public const string FellowshipBroadcastLine = "@f - Sends a broadcast to your Fellowship.\n";
|
||||||
|
|
||||||
// Partial: HelpChattingGroup's Detail branch
|
// HelpChannelsGroup's Detail branch (acclient_2013_pseudo_c.txt:387166-
|
||||||
// (acclient_2013_pseudo_c.txt:390909-390984) resolves 7 of 8 entries
|
// 387194) is ENTIRELY 6 HelpStupidChannelHack calls, read in this exact
|
||||||
// verbatim (chat/notell/reply/retell/say/tell/afk, each an inline
|
// call order (a, c, m, p, v, f) -- no leading group-summary line (that
|
||||||
// literal or a direct HelpXxx(Summary_HelpType,...) call whose own text
|
// string is exclusive to the Summary_HelpType branch). COMPLETE
|
||||||
// was independently swept) -- only the 5 HelpStupidChannelHack calls in
|
// verbatim listing as of round 4; no UNVERIFIED remainder.
|
||||||
// the middle (channel-alias entries) are unresolved.
|
public const string ChannelsGroupDetail =
|
||||||
|
AllegianceBroadcastLine
|
||||||
|
+ CovassalBroadcastLine
|
||||||
|
+ MonarchBroadcastLine
|
||||||
|
+ PatronBroadcastLine
|
||||||
|
+ VassalBroadcastLine
|
||||||
|
+ FellowshipBroadcastLine;
|
||||||
|
|
||||||
|
// ClientCommunicationSystem::HelpReply @0x00577A50's Summary_HelpType
|
||||||
|
// branch (read directly, not assumed): it unconditionally concatenates
|
||||||
|
// THREE lines -- reply, then pr, then mr -- regardless of which tag it
|
||||||
|
// was called with. This is a genuine retail quirk (the shared handler
|
||||||
|
// ignores its own `arg3` tag parameter entirely in both branches),
|
||||||
|
// ported faithfully per CLAUDE.md's "do not fix the decompiled code"
|
||||||
|
// rule. Swept verbatim against the PDB-paired
|
||||||
|
// C:\Users\erikn\Downloads\acclient.exe, --range 0x577a50 0x577bd0.
|
||||||
|
public const string ReplySummaryLine = "@reply - Sends some text to the last person who @tell'd you.\n";
|
||||||
|
public const string PatronReplySummaryLine = "@pr - Sends some text to the last person who @p'd you.\n";
|
||||||
|
public const string MonarchReplySummaryLine = "@mr - Sends some text to the last person who @m'd you.\n";
|
||||||
|
|
||||||
|
// HelpChattingGroup's Detail branch (acclient_2013_pseudo_c.txt:390909-
|
||||||
|
// 390998) read in full, exact source order: chat, notell,
|
||||||
|
// HelpReply(Summary) [reply+pr+mr, see above], retell, say, tell, the
|
||||||
|
// SAME 6-line HelpStupidChannelHack block as ChannelsGroupDetail, afk.
|
||||||
|
// COMPLETE verbatim listing as of round 4. The "@say" line's own retail
|
||||||
|
// literal (data_0x7dadb4, swept) genuinely has NO trailing newline --
|
||||||
|
// confirmed by direct byte inspection, not a copy/paste drop -- so
|
||||||
|
// retail's own output runs "@say"'s line straight into "@tell"'s with
|
||||||
|
// no line break; ported as found.
|
||||||
public const string ChattingGroupDetail =
|
public const string ChattingGroupDetail =
|
||||||
"@chat - Sets whether or not you receive normal chat.\n"
|
"@chat - Sets whether or not you receive normal chat.\n"
|
||||||
+ "@notell - Sets whether or not you receive @tell's.\n"
|
+ "@notell - Sets whether or not you receive @tell's.\n"
|
||||||
+ "@reply - Sends some text to the last person who @tell'd you.\n"
|
+ ReplySummaryLine
|
||||||
|
+ PatronReplySummaryLine
|
||||||
|
+ MonarchReplySummaryLine
|
||||||
+ "@retell - Sends some text to the last person you @tell'd.\n"
|
+ "@retell - Sends some text to the last person you @tell'd.\n"
|
||||||
+ "@say - Says some text to everyone around you.\n"
|
+ "@say - Says some text to everyone around you."
|
||||||
+ "@tell - Sends a private message to another character.\n"
|
+ "@tell - Sends a private message to another character.\n"
|
||||||
+ "[5 more retail channel-alias one-liners not yet extracted verbatim -- ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290, UNVERIFIED; see the class remarks above.]\n"
|
+ ChannelsGroupDetail
|
||||||
+ "@afk - Set your away-from-keyboard status.\n";
|
+ "@afk - Set your away-from-keyboard status.\n";
|
||||||
|
|
||||||
// HelpAllGroup's Detail branch (acclient_2013_pseudo_c.txt:394006-394176)
|
// Short one-liners HelpAllGroup builds itself (distinct retail literals
|
||||||
// recursively calls HelpAllegiancesGroup AND HelpChattingGroup in Detail
|
// from the catalog's own longer Detail text for the same verbs --
|
||||||
// mode (not Summary), plus the same 5 HelpStupidChannelHack calls
|
// e.g. this @saveui line is NOT SaveUiDetail's text; both are genuine
|
||||||
// directly, plus HelpDeathGroup in Detail mode, plus several more direct
|
// retail strings, just from two different call sites). Swept verbatim,
|
||||||
// entries, plus a CONFIRMED retail duplicate (the saveui/loadui pair
|
// --range 0x57e7f0 0x57eba0 against the PDB-paired
|
||||||
// appears twice in the straight-line decompiled body with no
|
// C:\Users\erikn\Downloads\acclient.exe.
|
||||||
// conditional between the two occurrences -- verified at
|
public const string EmoteAndEmotesShortSummary =
|
||||||
// acclient_2013_pseudo_c.txt:394089-394137, not an acdream artifact).
|
"@emote - Performs a text emote.\n@emotes - Lists all standard emotes.\n";
|
||||||
// The nesting compounds two independent HelpStupidChannelHack gaps
|
public const string FillCompsShortSummary = "@fillcomps - Helps you buy components in bulk.\n";
|
||||||
// (its own 5 calls + HelpChattingGroup's 5) with enough structural
|
public const string SaveUiShortSummary = "@saveui <filename> - Saves the current user interface.\n";
|
||||||
// uncertainty (unconfirmed HelpType arg on the trailing @status/@text
|
public const string LoadUiShortSummary = "@loadui <filename> - Loads a previously saved user interface.\n";
|
||||||
// calls) that a full reconstruction risks presenting an inaccurate
|
public const string FriendsShortSummary = "@friends - Helps you manage your friends list.\n";
|
||||||
// listing as verbatim. Kept UNVERIFIED with its own summary (fully
|
public const string SquelchGroupShortSummary =
|
||||||
// decoded) rather than risk that.
|
"@squelch - Squelches a character or account.\n"
|
||||||
|
+ "@unsquelch - Unsquelches a squelched character or account.\n"
|
||||||
|
+ "@messagetypes - Lists all types of messages that can be squelched or filtered.\n";
|
||||||
|
|
||||||
|
// HelpAllGroup's Detail branch (acclient_2013_pseudo_c.txt:394006-394205,
|
||||||
|
// VA 0x0057E7FA-0x0057EB8E) read in full, exact source order:
|
||||||
|
// HelpAllegiancesGroup(Detail), the SAME 6-line HelpStupidChannelHack
|
||||||
|
// block, HelpChattingGroup(Detail), HelpDeathGroup(Detail), the emote
|
||||||
|
// one-liner pair, fillcomps, saveui(short), loadui(short), saveui(short)
|
||||||
|
// AGAIN, loadui(short) AGAIN, lockui, friends(short), the house
|
||||||
|
// one-liner, the squelch/unsquelch/messagetypes one-liner trio,
|
||||||
|
// HelpStatusGroup(Detail), HelpTextGroup(Detail). The saveui/loadui
|
||||||
|
// duplicate is a CONFIRMED retail duplicate -- the straight-line
|
||||||
|
// decompiled body repeats both calls with no conditional between the
|
||||||
|
// two occurrences (verified directly, not an acdream artifact) --
|
||||||
|
// ported as found. COMPLETE verbatim listing as of round 4; no
|
||||||
|
// UNVERIFIED remainder. Closes ISSUES.md #364.
|
||||||
public const string CommandsGroupDetail =
|
public const string CommandsGroupDetail =
|
||||||
CommandsGroupSummary + GroupDetailUnverifiedSuffix;
|
AllegiancesGroupDetail
|
||||||
|
+ ChannelsGroupDetail
|
||||||
|
+ ChattingGroupDetail
|
||||||
|
+ DeathGroupDetail
|
||||||
|
+ EmoteAndEmotesShortSummary
|
||||||
|
+ FillCompsShortSummary
|
||||||
|
+ SaveUiShortSummary
|
||||||
|
+ LoadUiShortSummary
|
||||||
|
+ SaveUiShortSummary
|
||||||
|
+ LoadUiShortSummary
|
||||||
|
+ LockUiDetail
|
||||||
|
+ FriendsShortSummary
|
||||||
|
+ HouseOneLiner
|
||||||
|
+ SquelchGroupShortSummary
|
||||||
|
+ StatusGroupDetail
|
||||||
|
+ TextGroupDetail;
|
||||||
|
|
||||||
// Campaign CH user-gate round 3 (2026-08-10), finding (b): DoHelp's
|
// Campaign CH user-gate round 3 (2026-08-10), finding (b): DoHelp's
|
||||||
// bare-/help "else" branch (arg2<=0, acclient_2013_pseudo_c.txt:395089-
|
// bare-/help "else" branch (arg2<=0, acclient_2013_pseudo_c.txt:395089-
|
||||||
|
|
@ -495,20 +755,28 @@ public static class RetailCommandHelpTable
|
||||||
// falling back to the catalog's invented summary.
|
// falling back to the catalog's invented summary.
|
||||||
//
|
//
|
||||||
// <c>messagetypes</c>/<c>message_types</c>/<c>msgtypes</c>/<c>msg_types</c>
|
// <c>messagetypes</c>/<c>message_types</c>/<c>msgtypes</c>/<c>msg_types</c>
|
||||||
// are the one leaf verb NOT extracted this round:
|
// were the one leaf verb not extracted at the consolidated-review round:
|
||||||
// `HelpMessageTypes @0x0056e5d0` calls
|
// `HelpMessageTypes @0x0056e5d0` calls
|
||||||
// `gmCCommunicationSystem::GetListofSquelchChannels(arg4)` to build its
|
// `gmCCommunicationSystem::GetListofSquelchChannels(arg4)` to build its
|
||||||
// text from a live enum table at runtime — there is no static string to
|
// text from a live enum table at runtime, not a static string. Round 4
|
||||||
// sweep. It keeps its acdream summary, honestly UNVERIFIED rather than
|
// (2026-08-10) ported that live construction for real instead of
|
||||||
// guessed at.
|
// leaving an acdream summary in its place — see
|
||||||
|
// <see cref="MessageTypesDetail"/> and <see cref="BuildMessageTypesDetail"/>
|
||||||
|
// below for the full trace (IsLegalChannel's fixed whitelist +
|
||||||
|
// LogTextTypeToString's fixed name table + the exact join/wrap format,
|
||||||
|
// all read directly off the decompiled bodies). Because both the
|
||||||
|
// whitelist and the name table are fixed data, the resulting text is
|
||||||
|
// itself a compile-time constant — this is "port the construction," not
|
||||||
|
// "guess the result."
|
||||||
//
|
//
|
||||||
// Coverage this round: 42 of the 47 catalog Definitions got a verbatim
|
// Coverage this round: 43 of the 47 catalog Definitions have a verbatim
|
||||||
// Detail override (<see cref="CatalogVerbDetailByVerb"/>, several
|
// Detail override (<see cref="CatalogVerbDetailByVerb"/>, several
|
||||||
// sharing one extraction per the CmdHashData note above), 4 are
|
// sharing one extraction per the CmdHashData note above, plus
|
||||||
// CONFIRMED-NULL (<see cref="CatalogVerbsWithNoRetailHelp"/>), and 1
|
// messagetypes' real construction), and 4 are CONFIRMED-NULL
|
||||||
// (messagetypes and its 3 aliases) remains an honest acdream summary —
|
// (<see cref="CatalogVerbsWithNoRetailHelp"/>) — zero remain an
|
||||||
// <c>RetailClientCommandHelpCoverageTests</c> pins these counts so a
|
// unverified acdream summary. <c>RetailCommandHelpTableTests</c> pins
|
||||||
// future extraction pass (or an accidental regression) is caught.
|
// these counts so a future extraction pass (or an accidental
|
||||||
|
// regression) is caught.
|
||||||
public const string LifestoneDetail =
|
public const string LifestoneDetail =
|
||||||
"@lifestone - Returns you to the last lifestone you used without killing you.\n";
|
"@lifestone - Returns you to the last lifestone you used without killing you.\n";
|
||||||
|
|
||||||
|
|
@ -617,6 +885,66 @@ public static class RetailCommandHelpTable
|
||||||
public const string HslistDetail =
|
public const string HslistDetail =
|
||||||
"@hslist <house type> - Lists the number and, if appropriate, positions of houses currently available for purchase. Types include: Apartment, Cottage, Villa, Mansion\n";
|
"@hslist <house type> - Lists the number and, if appropriate, positions of houses currently available for purchase. Types include: Apartment, Cottage, Villa, Mansion\n";
|
||||||
|
|
||||||
|
// ── messagetypes: a real construction, not a hand-authored string ───
|
||||||
|
//
|
||||||
|
// Retail's HelpMessageTypes @0x0056E5D0 -> GetListofSquelchChannels
|
||||||
|
// @0x00589EB0 -> ConvertSQToPString @0x00589A80 (the `arg3 != 0`
|
||||||
|
// "enumerate every legal channel" branch) chain, read directly:
|
||||||
|
// 1. Iterate i = 0..0x21 in ascending order.
|
||||||
|
// 2. Keep i only if LogTextTypeEnumMapper::IsLegalChannel(i) @
|
||||||
|
// 0x006AFF40 -- a literal switch over exactly 14 ids: 0x02, 0x03,
|
||||||
|
// 0x06, 0x07, 0x0C, 0x10, 0x11, 0x12, 0x13, 0x15, 0x16, 0x17,
|
||||||
|
// 0x18, 0x19. (The SquelchInfo the real function iterates is a
|
||||||
|
// SYNTHETIC all-squelched object built solely to walk every legal
|
||||||
|
// id -- SquelchInfo::SquelchInfo(..., 1) -- not the player's
|
||||||
|
// actual squelch state, so this is genuinely a fixed enumeration,
|
||||||
|
// not a live/variable one.)
|
||||||
|
// 3. Get each kept id's display name via
|
||||||
|
// LogTextTypeEnumMapper::LogTextTypeToString @0x006AFF90 -- also a
|
||||||
|
// literal switch, read directly (not the C# enum member spelling,
|
||||||
|
// which differs by underscores for two entries): Speech, Tell,
|
||||||
|
// Combat, Magic, Emote, Appraisal, Spellcasting, Allegiance,
|
||||||
|
// Fellowship, Combat_Enemy, Combat_Self, Recall, Craft, Salvaging.
|
||||||
|
// 4. Join with ", " (data_0x794334, swept) and wrap in
|
||||||
|
// "Squelch channels are as follows:\n %s\n"
|
||||||
|
// (data_0x7e2fb8, swept off GetListofSquelchChannels' own
|
||||||
|
// sprintf call).
|
||||||
|
// Both the whitelist and the name table are fixed data, so the joined
|
||||||
|
// result is itself a compile-time constant -- SquelchLegalChannels +
|
||||||
|
// BuildMessageTypesDetail port the ENUMERATION rather than
|
||||||
|
// hand-authoring the already-known joined string, so a future
|
||||||
|
// whitelist change is a one-line table edit.
|
||||||
|
private static readonly (RetailLogTextType Type, string Name)[] SquelchLegalChannels =
|
||||||
|
{
|
||||||
|
(RetailLogTextType.Speech, "Speech"),
|
||||||
|
(RetailLogTextType.Tell, "Tell"),
|
||||||
|
(RetailLogTextType.Combat, "Combat"),
|
||||||
|
(RetailLogTextType.Magic, "Magic"),
|
||||||
|
(RetailLogTextType.Emote, "Emote"),
|
||||||
|
(RetailLogTextType.Appraisal, "Appraisal"),
|
||||||
|
(RetailLogTextType.Spellcasting, "Spellcasting"),
|
||||||
|
(RetailLogTextType.Allegiance, "Allegiance"),
|
||||||
|
(RetailLogTextType.Fellowship, "Fellowship"),
|
||||||
|
(RetailLogTextType.CombatEnemy, "Combat_Enemy"),
|
||||||
|
(RetailLogTextType.CombatSelf, "Combat_Self"),
|
||||||
|
(RetailLogTextType.Recall, "Recall"),
|
||||||
|
(RetailLogTextType.Craft, "Craft"),
|
||||||
|
(RetailLogTextType.Salvaging, "Salvaging"),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string BuildMessageTypesDetail() =>
|
||||||
|
"Squelch channels are as follows:\n "
|
||||||
|
+ string.Join(", ", Array.ConvertAll(SquelchLegalChannels, c => c.Name))
|
||||||
|
+ "\n";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retail's real <c>/help messagetypes</c> text (and its 3 aliases),
|
||||||
|
/// ported from the live construction retail itself performs -- see the
|
||||||
|
/// remarks above <see cref="SquelchLegalChannels"/>. No longer an
|
||||||
|
/// acdream-authored summary.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string MessageTypesDetail = BuildMessageTypesDetail();
|
||||||
|
|
||||||
private static readonly FrozenDictionary<string, string> CatalogVerbDetailByVerb =
|
private static readonly FrozenDictionary<string, string> CatalogVerbDetailByVerb =
|
||||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
|
|
@ -671,10 +999,13 @@ public static class RetailCommandHelpTable
|
||||||
["unsquelch"] = SquelchDetail,
|
["unsquelch"] = SquelchDetail,
|
||||||
["filter"] = FilterDetail,
|
["filter"] = FilterDetail,
|
||||||
["unfilter"] = UnfilterDetail,
|
["unfilter"] = UnfilterDetail,
|
||||||
// messagetypes/message_types/msgtypes/msg_types intentionally
|
// Round 4 (2026-08-10): messagetypes/message_types/msgtypes/
|
||||||
// absent — HelpMessageTypes builds its text from a live enum
|
// msg_types now resolve to MessageTypesDetail, the ported live
|
||||||
// table at runtime (GetListofSquelchChannels), not a static
|
// construction — see the remarks above SquelchLegalChannels.
|
||||||
// string; see the class remarks above.
|
["messagetypes"] = MessageTypesDetail,
|
||||||
|
["message_types"] = MessageTypesDetail,
|
||||||
|
["msgtypes"] = MessageTypesDetail,
|
||||||
|
["msg_types"] = MessageTypesDetail,
|
||||||
["fillcomps"] = FillCompsDetail,
|
["fillcomps"] = FillCompsDetail,
|
||||||
["endurance"] = EnduranceDetail,
|
["endurance"] = EnduranceDetail,
|
||||||
["speaker"] = SpeakerDetail,
|
["speaker"] = SpeakerDetail,
|
||||||
|
|
|
||||||
|
|
@ -76,8 +76,16 @@ public class ChatLayoutConformanceTests
|
||||||
[InlineData(0x10000523u)]
|
[InlineData(0x10000523u)]
|
||||||
[InlineData(0x10000524u)]
|
[InlineData(0x10000524u)]
|
||||||
[InlineData(0x10000525u)]
|
[InlineData(0x10000525u)]
|
||||||
public void MountedChatWindow_IndicatorButtons_ImportVisibleAndInert(uint indicatorId)
|
public void MountedChatWindow_IndicatorButtons_BindAloneLeavesClickUnwiredButSelfFlipSuppressed(
|
||||||
|
uint indicatorId)
|
||||||
{
|
{
|
||||||
|
// Bind() alone (no BindIndicatorClicks call) leaves OnClick unwired —
|
||||||
|
// the click half is a separate opt-in step RetailUiRuntime performs
|
||||||
|
// once its own ToggleFloatingChatWindow method is available (round 4,
|
||||||
|
// 2026-08-10 — see ChatWindowController.SetIndicatorOpen's doc for the
|
||||||
|
// full retail mechanism reconciliation). This test only proves the
|
||||||
|
// Bind-alone state; MountedChatWindow_IndicatorButtons_ClickTogglesWindow
|
||||||
|
// below exercises the full click round trip through BindIndicatorClicks.
|
||||||
var infos = FixtureLoader.LoadChatInfos();
|
var infos = FixtureLoader.LoadChatInfos();
|
||||||
var layout = LayoutImporter.Build(infos, NoTex, null);
|
var layout = LayoutImporter.Build(infos, NoTex, null);
|
||||||
var controller = ChatWindowController.Bind(
|
var controller = ChatWindowController.Bind(
|
||||||
|
|
@ -87,21 +95,18 @@ public class ChatLayoutConformanceTests
|
||||||
UiElement? indicator = layout.FindElement(indicatorId);
|
UiElement? indicator = layout.FindElement(indicatorId);
|
||||||
Assert.NotNull(indicator);
|
Assert.NotNull(indicator);
|
||||||
Assert.True(indicator!.Visible);
|
Assert.True(indicator!.Visible);
|
||||||
// Inert: CH6a does not wire a click handler (CH6b's job — the
|
|
||||||
// one-directional visibility mirror has no button-press behavior of
|
|
||||||
// its own in retail either, per the research doc §1.4). UiButton
|
|
||||||
// structurally always reports HandlesClick=true (it can receive the
|
|
||||||
// event), but with no OnClick/OnClickAt bound, a click is a no-op.
|
|
||||||
var button = Assert.IsType<UiButton>(indicator);
|
var button = Assert.IsType<UiButton>(indicator);
|
||||||
Assert.Null(button.OnClick);
|
Assert.Null(button.OnClick);
|
||||||
Assert.Null(button.OnClickAt);
|
Assert.Null(button.OnClickAt);
|
||||||
|
|
||||||
// CH6a/b REJECT-review SHOULD-FIX 3: OnClick==null is the wrong level
|
// CH6a/b REJECT-review SHOULD-FIX 3: OnClick==null is the wrong level
|
||||||
// to prove "inert" — the fixture's own 0x0B (ToggleBehavior) = true
|
// to prove "self-flip suppressed" — the fixture's own 0x0B
|
||||||
// means a plain press/release would still flip UiButton's internal
|
// (ToggleBehavior) = true means a plain press/release would still
|
||||||
// Selected mirror even with no OnClick bound. SuppressSelfToggle
|
// flip UiButton's internal Selected mirror even with no OnClick
|
||||||
// (set by ChatWindowController.Bind) is the actual guard; a press
|
// bound. SuppressSelfToggle (set by ChatWindowController.Bind) is
|
||||||
// and release must leave Selected unchanged.
|
// the actual guard, and it stays true even after round 4 wires a
|
||||||
|
// real click handler — see SetIndicatorOpen's doc for why the blind
|
||||||
|
// self-flip must stay suppressed regardless.
|
||||||
Assert.True(button.SuppressSelfToggle);
|
Assert.True(button.SuppressSelfToggle);
|
||||||
bool before = button.Selected;
|
bool before = button.Selected;
|
||||||
button.OnEvent(new UiEvent(0, button, UiEventType.MouseDown, Data1: 2, Data2: 2));
|
button.OnEvent(new UiEvent(0, button, UiEventType.MouseDown, Data1: 2, Data2: 2));
|
||||||
|
|
@ -109,6 +114,83 @@ public class ChatLayoutConformanceTests
|
||||||
Assert.Equal(before, button.Selected);
|
Assert.Equal(before, button.Selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(0x10000522u, 1)]
|
||||||
|
[InlineData(0x10000523u, 2)]
|
||||||
|
[InlineData(0x10000524u, 3)]
|
||||||
|
[InlineData(0x10000525u, 4)]
|
||||||
|
public void MountedChatWindow_IndicatorButtons_ClickTogglesWindow_ThroughBindIndicatorClicks(
|
||||||
|
uint indicatorId, int expectedWindowId)
|
||||||
|
{
|
||||||
|
// Round 4 (2026-08-10): clicking an indicator now toggles its floating
|
||||||
|
// chat window through the SAME chokepoint the Alt+1..4 keybinds use
|
||||||
|
// (user-directed retail behavior — see SetIndicatorOpen's doc for the
|
||||||
|
// full mechanism reconciliation: the generic UIElement_Button
|
||||||
|
// action-dispatch system is real and armed on the button side, but
|
||||||
|
// the shipped floating-window fixture authors no matching listener
|
||||||
|
// registration, so this wiring cannot be proven purely from the DAT).
|
||||||
|
var infos = FixtureLoader.LoadChatInfos();
|
||||||
|
var layout = LayoutImporter.Build(infos, NoTex, null);
|
||||||
|
var controller = ChatWindowController.Bind(
|
||||||
|
infos, layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, new ChatWindowState(), null, null, NoTex);
|
||||||
|
Assert.NotNull(controller);
|
||||||
|
|
||||||
|
var toggled = new List<int>();
|
||||||
|
controller!.BindIndicatorClicks(windowId =>
|
||||||
|
{
|
||||||
|
toggled.Add(windowId);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
UiElement? indicator = layout.FindElement(indicatorId);
|
||||||
|
var button = Assert.IsType<UiButton>(indicator);
|
||||||
|
Assert.NotNull(button.OnClick);
|
||||||
|
|
||||||
|
button.OnEvent(new UiEvent(0, button, UiEventType.MouseDown, Data1: 2, Data2: 2));
|
||||||
|
button.OnEvent(new UiEvent(0, button, UiEventType.MouseUp, Data1: 2, Data2: 2));
|
||||||
|
button.OnEvent(new UiEvent(0, button, UiEventType.Click, Data1: 2, Data2: 2));
|
||||||
|
|
||||||
|
Assert.Equal(new[] { expectedWindowId }, toggled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MountedChatWindow_IndicatorButtons_ClickRoundTrip_KeepsMirrorConsistent()
|
||||||
|
{
|
||||||
|
// The full round trip: click -> BindIndicatorClicks' delegate calls the
|
||||||
|
// real toggle -> the caller (standing in for RetailUiRuntime's
|
||||||
|
// WindowVisibilityChanged plumbing) calls SetIndicatorOpen with the
|
||||||
|
// window's NEW state -> Selected matches that new state, not a blind
|
||||||
|
// self-flip. SuppressSelfToggle staying true is what makes this
|
||||||
|
// authoritative-writer invariant hold even though the button now has
|
||||||
|
// a real OnClick.
|
||||||
|
var infos = FixtureLoader.LoadChatInfos();
|
||||||
|
var layout = LayoutImporter.Build(infos, NoTex, null);
|
||||||
|
var controller = ChatWindowController.Bind(
|
||||||
|
infos, layout, new ChatVM(new ChatLog()), () => NullCommandBus.Instance, new ChatWindowState(), null, null, NoTex);
|
||||||
|
Assert.NotNull(controller);
|
||||||
|
|
||||||
|
bool windowOpen = false;
|
||||||
|
controller!.BindIndicatorClicks(windowId =>
|
||||||
|
{
|
||||||
|
windowOpen = !windowOpen;
|
||||||
|
controller.SetIndicatorOpen(windowId, windowOpen);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
var button = Assert.IsType<UiButton>(layout.FindElement(0x10000522u));
|
||||||
|
Assert.False(button.Selected);
|
||||||
|
|
||||||
|
button.OnEvent(new UiEvent(0, button, UiEventType.MouseDown, Data1: 2, Data2: 2));
|
||||||
|
button.OnEvent(new UiEvent(0, button, UiEventType.MouseUp, Data1: 2, Data2: 2));
|
||||||
|
button.OnEvent(new UiEvent(0, button, UiEventType.Click, Data1: 2, Data2: 2));
|
||||||
|
Assert.True(button.Selected);
|
||||||
|
|
||||||
|
button.OnEvent(new UiEvent(0, button, UiEventType.MouseDown, Data1: 2, Data2: 2));
|
||||||
|
button.OnEvent(new UiEvent(0, button, UiEventType.MouseUp, Data1: 2, Data2: 2));
|
||||||
|
button.OnEvent(new UiEvent(0, button, UiEventType.Click, Data1: 2, Data2: 2));
|
||||||
|
Assert.False(button.Selected);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(0x10000693u)]
|
[InlineData(0x10000693u)]
|
||||||
[InlineData(0x10000694u)]
|
[InlineData(0x10000694u)]
|
||||||
|
|
|
||||||
|
|
@ -79,54 +79,100 @@ public sealed class RetailCommandHelpTableTests
|
||||||
RetailCommandHelpTable.AllegiancesGroupDetail);
|
RetailCommandHelpTable.AllegiancesGroupDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
// ── Round 4 (2026-08-10): the 3 PARTIAL groups are now COMPLETE ─────
|
||||||
[InlineData("channels")]
|
// Closes ISSUES.md #364. No UNVERIFIED note, no meta-fabrication, ever.
|
||||||
[InlineData("commands")]
|
|
||||||
public void UnresolvedGroups_ContainVerbatimSummaryPlusHonestUnverifiedNote_NoMetaFabrication(
|
[Fact]
|
||||||
string verb)
|
public void ChannelsGroup_IsCompleteVerbatimListing_SixBroadcastLines()
|
||||||
{
|
{
|
||||||
Assert.True(RetailCommandHelpTable.TryGetHelpText(verb, out string text));
|
// ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290,
|
||||||
// The old sentinel this round retired -- must never come back.
|
// decoded: "@" + tag + " - Sends a broadcast to your " +
|
||||||
Assert.DoesNotContain("has not yet extracted", text);
|
// ChannelName + ".\n", read in HelpChannelsGroup's own call order
|
||||||
Assert.Contains("UNVERIFIED", text);
|
// (a, c, m, p, v, f).
|
||||||
Assert.Contains("HelpStupidChannelHack @0x0056f290", text);
|
Assert.Equal(
|
||||||
|
"@a - Sends a broadcast to your Allegiance.\n"
|
||||||
|
+ "@c - Sends a broadcast to your Co-vassals.\n"
|
||||||
|
+ "@m - Sends a broadcast to your Monarch.\n"
|
||||||
|
+ "@p - Sends a broadcast to your Patron.\n"
|
||||||
|
+ "@v - Sends a broadcast to your Vassals.\n"
|
||||||
|
+ "@f - Sends a broadcast to your Fellowship.\n",
|
||||||
|
RetailCommandHelpTable.ChannelsGroupDetail);
|
||||||
|
|
||||||
|
Assert.True(RetailCommandHelpTable.TryGetHelpText("channels", out string viaLookup));
|
||||||
|
Assert.Equal(RetailCommandHelpTable.ChannelsGroupDetail, viaLookup);
|
||||||
|
Assert.DoesNotContain("UNVERIFIED", viaLookup);
|
||||||
|
Assert.DoesNotContain("not yet extracted", viaLookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ChannelsGroup_SummaryLineIsVerbatim()
|
public void ChattingGroup_IsCompleteVerbatimListing_ThirteenLines()
|
||||||
{
|
{
|
||||||
Assert.True(RetailCommandHelpTable.TryGetHelpText("channels", out string text));
|
// HelpChattingGroup's Detail branch, exact source order: chat,
|
||||||
Assert.StartsWith(
|
// notell, HelpReply(Summary) [reply+pr+mr], retell, say, tell, the
|
||||||
"@help channels - How to communicate with people in your allegiance or fellowship.",
|
// same 6-line broadcast block as ChannelsGroupDetail, afk. The
|
||||||
text);
|
// "@say" line's own retail literal has NO trailing newline
|
||||||
|
// (confirmed by direct byte sweep) -- retail's own output runs it
|
||||||
|
// straight into "@tell"'s line with no line break; ported as found.
|
||||||
|
Assert.Equal(
|
||||||
|
"@chat - Sets whether or not you receive normal chat.\n"
|
||||||
|
+ "@notell - Sets whether or not you receive @tell's.\n"
|
||||||
|
+ "@reply - Sends some text to the last person who @tell'd you.\n"
|
||||||
|
+ "@pr - Sends some text to the last person who @p'd you.\n"
|
||||||
|
+ "@mr - Sends some text to the last person who @m'd you.\n"
|
||||||
|
+ "@retell - Sends some text to the last person you @tell'd.\n"
|
||||||
|
+ "@say - Says some text to everyone around you."
|
||||||
|
+ "@tell - Sends a private message to another character.\n"
|
||||||
|
+ "@a - Sends a broadcast to your Allegiance.\n"
|
||||||
|
+ "@c - Sends a broadcast to your Co-vassals.\n"
|
||||||
|
+ "@m - Sends a broadcast to your Monarch.\n"
|
||||||
|
+ "@p - Sends a broadcast to your Patron.\n"
|
||||||
|
+ "@v - Sends a broadcast to your Vassals.\n"
|
||||||
|
+ "@f - Sends a broadcast to your Fellowship.\n"
|
||||||
|
+ "@afk - Set your away-from-keyboard status.\n",
|
||||||
|
RetailCommandHelpTable.ChattingGroupDetail);
|
||||||
|
|
||||||
|
Assert.True(RetailCommandHelpTable.TryGetHelpText("chatting", out string viaLookup));
|
||||||
|
Assert.Equal(RetailCommandHelpTable.ChattingGroupDetail, viaLookup);
|
||||||
|
Assert.DoesNotContain("UNVERIFIED", viaLookup);
|
||||||
|
Assert.DoesNotContain("not yet extracted verbatim", viaLookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ChattingGroup_SevenOfEightEntriesAreVerbatim_FifthEntryMarkedUnverified()
|
public void CommandsGroup_IsCompleteVerbatimListing_ConcatenatesEveryOtherGroup()
|
||||||
{
|
{
|
||||||
Assert.True(RetailCommandHelpTable.TryGetHelpText("chatting", out string text));
|
// HelpAllGroup's Detail branch: HelpAllegiancesGroup(Detail), the
|
||||||
Assert.Contains(
|
// same 6-line broadcast block, HelpChattingGroup(Detail),
|
||||||
"@chat - Sets whether or not you receive normal chat.\n",
|
// HelpDeathGroup(Detail), emote+emotes, fillcomps, saveui(short),
|
||||||
text);
|
// loadui(short), saveui(short) AGAIN, loadui(short) AGAIN
|
||||||
Assert.Contains(
|
// (a CONFIRMED retail duplicate), lockui, friends(short), the
|
||||||
"@notell - Sets whether or not you receive @tell's.\n",
|
// house one-liner, squelch+unsquelch+messagetypes, then
|
||||||
text);
|
// HelpStatusGroup(Detail), HelpTextGroup(Detail).
|
||||||
Assert.Contains(
|
string expected =
|
||||||
"@reply - Sends some text to the last person who @tell'd you.\n",
|
RetailCommandHelpTable.AllegiancesGroupDetail
|
||||||
text);
|
+ RetailCommandHelpTable.ChannelsGroupDetail
|
||||||
Assert.Contains(
|
+ RetailCommandHelpTable.ChattingGroupDetail
|
||||||
"@retell - Sends some text to the last person you @tell'd.\n",
|
+ RetailCommandHelpTable.DeathGroupDetail
|
||||||
text);
|
+ "@emote - Performs a text emote.\n@emotes - Lists all standard emotes.\n"
|
||||||
Assert.Contains(
|
+ "@fillcomps - Helps you buy components in bulk.\n"
|
||||||
"@say - Says some text to everyone around you.\n",
|
+ "@saveui <filename> - Saves the current user interface.\n"
|
||||||
text);
|
+ "@loadui <filename> - Loads a previously saved user interface.\n"
|
||||||
Assert.Contains(
|
+ "@saveui <filename> - Saves the current user interface.\n"
|
||||||
"@tell - Sends a private message to another character.\n",
|
+ "@loadui <filename> - Loads a previously saved user interface.\n"
|
||||||
text);
|
+ "@lockui - Toggles the locked state of the UI layout.\n"
|
||||||
Assert.Contains(
|
+ "@friends - Helps you manage your friends list.\n"
|
||||||
"@afk - Set your away-from-keyboard status.\n",
|
+ "@house - Commands that help you manage your house, including guest and storage management.\n"
|
||||||
text);
|
+ "@squelch - Squelches a character or account.\n"
|
||||||
Assert.Contains("HelpStupidChannelHack @0x0056f290", text);
|
+ "@unsquelch - Unsquelches a squelched character or account.\n"
|
||||||
|
+ "@messagetypes - Lists all types of messages that can be squelched or filtered.\n"
|
||||||
|
+ RetailCommandHelpTable.StatusGroupDetail
|
||||||
|
+ RetailCommandHelpTable.TextGroupDetail;
|
||||||
|
|
||||||
|
Assert.Equal(RetailCommandHelpTable.CommandsGroupDetail, expected);
|
||||||
|
|
||||||
|
Assert.True(RetailCommandHelpTable.TryGetHelpText("commands", out string viaLookup));
|
||||||
|
Assert.Equal(RetailCommandHelpTable.CommandsGroupDetail, viaLookup);
|
||||||
|
Assert.DoesNotContain("UNVERIFIED", viaLookup);
|
||||||
|
Assert.DoesNotContain("not yet extracted", viaLookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Campaign CH user-gate round 2, item 3: 7 of the ~35 channel
|
// Campaign CH user-gate round 2, item 3: 7 of the ~35 channel
|
||||||
|
|
@ -425,10 +471,178 @@ public sealed class RetailCommandHelpTableTests
|
||||||
// count separately here since they are DISTINCT Definitions with
|
// count separately here since they are DISTINCT Definitions with
|
||||||
// their own summary text, even though several share ONE extracted
|
// their own summary text, even though several share ONE extracted
|
||||||
// Detail text -- see the CmdHashData note in RetailCommandHelpTable's
|
// Detail text -- see the CmdHashData note in RetailCommandHelpTable's
|
||||||
// class remarks).
|
// class remarks). Round 4 (2026-08-10): messagetypes moved from
|
||||||
Assert.Equal(42, extractedCount);
|
// unverified to extracted (its real live-construction is now
|
||||||
|
// ported -- see RetailCommandHelpTable.MessageTypesDetail) --
|
||||||
|
// 43/4/0, zero remaining unverified leaf verbs.
|
||||||
|
Assert.Equal(43, extractedCount);
|
||||||
Assert.Equal(4, confirmedNullCount);
|
Assert.Equal(4, confirmedNullCount);
|
||||||
Assert.Equal(1, unverifiedCount);
|
Assert.Equal(0, unverifiedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Round 4 (2026-08-10): no user-visible meta-markers, anywhere ────
|
||||||
|
// Honesty markers ("IMPLEMENTED", "NOT YET IMPLEMENTED", "UNVERIFIED",
|
||||||
|
// "has not yet extracted") belong in code comments/docs/the divergence
|
||||||
|
// register per CLAUDE.md -- never in a string a player can read.
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> AllUserVisibleStrings()
|
||||||
|
{
|
||||||
|
yield return new object[] { nameof(RetailCommandHelpTable.Day), RetailCommandHelpTable.Day };
|
||||||
|
yield return new object[] { nameof(RetailCommandHelpTable.Log), RetailCommandHelpTable.Log };
|
||||||
|
yield return new object[] { nameof(RetailCommandHelpTable.Render), RetailCommandHelpTable.Render };
|
||||||
|
yield return new object[] { nameof(RetailCommandHelpTable.Motd), RetailCommandHelpTable.Motd };
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
nameof(RetailCommandHelpTable.AllegianceOverview), RetailCommandHelpTable.AllegianceOverview,
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
nameof(RetailCommandHelpTable.HouseOverview), RetailCommandHelpTable.HouseOverview,
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
nameof(RetailCommandHelpTable.ChannelsGroupDetail), RetailCommandHelpTable.ChannelsGroupDetail,
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
nameof(RetailCommandHelpTable.ChattingGroupDetail), RetailCommandHelpTable.ChattingGroupDetail,
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
nameof(RetailCommandHelpTable.CommandsGroupDetail), RetailCommandHelpTable.CommandsGroupDetail,
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
nameof(RetailCommandHelpTable.MessageTypesDetail), RetailCommandHelpTable.MessageTypesDetail,
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
nameof(RetailCommandHelpTable.AvailableHelpListing), RetailCommandHelpTable.AvailableHelpListing,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(AllUserVisibleStrings))]
|
||||||
|
public void UserVisibleString_CarriesNoHonestyMarker(string _, string text)
|
||||||
|
{
|
||||||
|
Assert.DoesNotContain("IMPLEMENTED", text);
|
||||||
|
Assert.DoesNotContain("UNVERIFIED", text);
|
||||||
|
Assert.DoesNotContain("has not yet extracted", text);
|
||||||
|
Assert.DoesNotContain("acdream has not", text);
|
||||||
|
Assert.DoesNotContain("not yet extracted", text);
|
||||||
|
Assert.DoesNotContain("NOT YET", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AllegianceOverview_NoBracketAnnotations_MatchesPristineRetailData()
|
||||||
|
{
|
||||||
|
// Read directly off data_7dfb58's own consolidated dump
|
||||||
|
// (acclient_2013_pseudo_c.txt:1031210-1031230) -- BN already fully
|
||||||
|
// decodes this block, no vtable-slot artifact to work around.
|
||||||
|
Assert.Contains("Also: @ab\n", RetailCommandHelpTable.AllegianceOverview);
|
||||||
|
Assert.DoesNotContain("[IMPLEMENTED", RetailCommandHelpTable.AllegianceOverview);
|
||||||
|
Assert.Contains(
|
||||||
|
"@allegiance hometown - Recalls you to your allegiance bindstone, if your allegiance has tied to one.\n",
|
||||||
|
RetailCommandHelpTable.AllegianceOverview);
|
||||||
|
Assert.DoesNotContain("Subcommands NOT marked", RetailCommandHelpTable.AllegianceOverview);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HouseOverview_NoBracketAnnotations_MatchesPristineRetailData()
|
||||||
|
{
|
||||||
|
// Read directly off data_7dd3d0's own consolidated dump
|
||||||
|
// (acclient_2013_pseudo_c.txt:1031017-1031037).
|
||||||
|
Assert.Contains(
|
||||||
|
"@house available - See @hslist\n",
|
||||||
|
RetailCommandHelpTable.HouseOverview);
|
||||||
|
Assert.Contains(
|
||||||
|
"@house recall - Teleports you to your house.\n",
|
||||||
|
RetailCommandHelpTable.HouseOverview);
|
||||||
|
Assert.DoesNotContain("[IMPLEMENTED", RetailCommandHelpTable.HouseOverview);
|
||||||
|
Assert.DoesNotContain("Subcommands NOT marked", RetailCommandHelpTable.HouseOverview);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Log_IsCompleteThreeParagraphRetailText()
|
||||||
|
{
|
||||||
|
// char const data_7df7f8[0x2ca] -- the prior extraction had
|
||||||
|
// silently dropped the middle sentence and both trailing
|
||||||
|
// paragraphs in addition to carrying the meta tail.
|
||||||
|
Assert.Equal(
|
||||||
|
"@log <name> - Echoes chat text to a logfile. All the information that appears in your chat window after you type this command will be copied into a text file. Choose the file you are copying to by naming it in the command. If this file already exists, it will add the additional text to the end of it. To turn off logging, simply retype @log.\n"
|
||||||
|
+ "@log AClog.txt - Echoes chat text to a log file named Aclog.txt in your Asheron's Call directory. After you use this command, all the information that appears in your chat window will be written to a file in your Asheron's Call directory named Aclog.txt.\n"
|
||||||
|
+ "@log - If you are currently copying the text in your chat window to a logfile, this command will stop the process.\n",
|
||||||
|
RetailCommandHelpTable.Log);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Render_IsRealRetailUsageText_NotAcdreamDescription()
|
||||||
|
{
|
||||||
|
// char const render_option_usage[0x98] @ 0x0079B878 -- the prior
|
||||||
|
// text ("Forwards to the client's render-option surface…") was
|
||||||
|
// entirely acdream-authored; this is the real retail literal.
|
||||||
|
Assert.Equal(
|
||||||
|
"Usage:\n"
|
||||||
|
+ "@render <option> <value>\n"
|
||||||
|
+ " radius # : set landscape radius (between 5 and 25)\n"
|
||||||
|
+ " fov # : set field of view (between 10 and 160)\n",
|
||||||
|
RetailCommandHelpTable.Render);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Motd_IsCompleteThreeLineRetailText_PreservesRetailTypo()
|
||||||
|
{
|
||||||
|
// char const data_7da300[0xdb] -- retail's own "clear-" (no space
|
||||||
|
// before the dash) is byte-confirmed, not an acdream typo.
|
||||||
|
Assert.Equal(
|
||||||
|
"@allegiance motd - Displays the message of the day for your allegiance.\n"
|
||||||
|
+ "@allegiance motd set <text> - Sets the MOTD. Can only be used by monarchs.\n"
|
||||||
|
+ "@allegiance motd clear- Clears the MOTD. Can only be used by monarchs.\n",
|
||||||
|
RetailCommandHelpTable.Motd);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MessageTypesDetail_IsRealConstructionFromLegalChannelWhitelist()
|
||||||
|
{
|
||||||
|
// LogTextTypeEnumMapper::IsLegalChannel @0x006AFF40 (14-id
|
||||||
|
// whitelist) + LogTextTypeEnumMapper::LogTextTypeToString
|
||||||
|
// @0x006AFF90 (per-id name) + the ", " join (data_0x794334) +
|
||||||
|
// GetListofSquelchChannels' own "Squelch channels are as
|
||||||
|
// follows:\n %s\n" wrap (data_0x7e2fb8), all swept/read directly.
|
||||||
|
Assert.Equal(
|
||||||
|
"Squelch channels are as follows:\n"
|
||||||
|
+ " Speech, Tell, Combat, Magic, Emote, Appraisal, Spellcasting, Allegiance, Fellowship, Combat_Enemy, Combat_Self, Recall, Craft, Salvaging\n",
|
||||||
|
RetailCommandHelpTable.MessageTypesDetail);
|
||||||
|
|
||||||
|
Assert.True(RetailCommandHelpTable.TryGetCatalogVerbDetailText(
|
||||||
|
"messagetypes", out string messagetypes));
|
||||||
|
Assert.Equal(RetailCommandHelpTable.MessageTypesDetail, messagetypes);
|
||||||
|
|
||||||
|
foreach (string alias in new[] { "message_types", "msgtypes", "msg_types" })
|
||||||
|
{
|
||||||
|
Assert.True(RetailCommandHelpTable.TryGetCatalogVerbDetailText(alias, out string aliasText));
|
||||||
|
Assert.Equal(RetailCommandHelpTable.MessageTypesDetail, aliasText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HelpMessageTypes_ThroughRouter_PrintsRetailsExactDetailText()
|
||||||
|
{
|
||||||
|
var log = new AcDream.Core.Chat.ChatLog();
|
||||||
|
var vm = new ChatVM(log, displayLimit: 50);
|
||||||
|
var bus = new RecordingCommandBus();
|
||||||
|
|
||||||
|
var outcome = ChatCommandRouter.Submit(
|
||||||
|
"/help messagetypes", vm, bus, ChatChannelKind.Say);
|
||||||
|
|
||||||
|
Assert.Equal(SubmitOutcome.ClientHandled, outcome);
|
||||||
|
var entries = log.Snapshot();
|
||||||
|
Assert.Equal(2, entries.Length);
|
||||||
|
Assert.Equal(RetailCommandHelpTable.HelpPrefixNote, entries[0].Text);
|
||||||
|
Assert.Equal(
|
||||||
|
RetailCommandHelpTable.ForMoreInformationPrefix
|
||||||
|
+ RetailCommandHelpTable.MessageTypesDetail,
|
||||||
|
entries[1].Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class RecordingCommandBus : ICommandBus
|
private sealed class RecordingCommandBus : ICommandBus
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue