From 5b54387b8e1f6cf6581cb1c2a928f85fea0ae5a9 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 10 Aug 2026 18:46:34 +0200 Subject: [PATCH] =?UTF-8?q?fix(chat):=20round=204=20=E2=80=94=20no=20user-?= =?UTF-8?q?visible=20meta=20text,=20real=20/help=20groups,=20indicator=20b?= =?UTF-8?q?uttons=20toggle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/ISSUES.md | 60 +- .../retail-divergence-register.md | 2 +- docs/plans/2026-08-09-chat-parity-campaign.md | 50 +- .../2026-08-09-chat-retail-window-shell.md | 130 ++++- .../UI/Layout/ChatWindowController.cs | 150 ++++- src/AcDream.App/UI/RetailUiRuntime.cs | 7 + src/AcDream.App/UI/UiButton.cs | 34 +- .../Panels/Chat/RetailCommandHelpTable.cs | 533 ++++++++++++++---- .../UI/Layout/ChatLayoutConformanceTests.cs | 104 +++- .../Chat/RetailCommandHelpTableTests.cs | 298 ++++++++-- 10 files changed, 1129 insertions(+), 239 deletions(-) diff --git a/docs/ISSUES.md b/docs/ISSUES.md index 134a3913..2e6765d1 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -382,32 +382,48 @@ user gate round 3; closed in the goal-window follow-up). ## #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 retail's real listing; all 7 `ClientCommunicationSystem::HelpXxxGroup` nodes were re-extracted verbatim from the PDB-paired binary via a generalized `tools/pdb-extract/sweep_weenie_strings.py --ascii-only`. -4 of 7 (death/status/text/allegiances) are now COMPLETE verbatim listings -(`RetailCommandHelpTable.DeathGroupDetail` etc.). 3 remain PARTIAL -(`ChannelsGroupDetail`, `ChattingGroupDetail`, `CommandsGroupDetail`): -their detail text is built (in full or in part) by -`ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290`, which -constructs its output from three string fragments Binary Ninja -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. +4 of 7 (death/status/text/allegiances) were COMPLETE verbatim listings +(`RetailCommandHelpTable.DeathGroupDetail` etc.); 3 remained PARTIAL +(`ChannelsGroupDetail`, `ChattingGroupDetail`, `CommandsGroupDetail`) with +an explicit UNVERIFIED note, believed genuinely not decodable from a +static string sweep — see the CLOSED note above for why that turned out +to be wrong. **Campaign:** `docs/plans/2026-08-09-chat-parity-campaign.md` (Campaign CH, user gate round 2). diff --git a/docs/architecture/retail-divergence-register.md b/docs/architecture/retail-divergence-register.md index 368e3cc0..96945845 100644 --- a/docs/architecture/retail-divergence-register.md +++ b/docs/architecture/retail-divergence-register.md @@ -170,7 +170,7 @@ readiness/requeue adaptation. See --- -## 3. Documented approximation (AP) — 132 active rows (AP-113 RETIRED 2026-08-10 at the consolidated-review round, SHOULD-FIX 3/1 byproduct — DoLifestone's own bad-args refusal text is now byte-recovered, see its retirement note below; AP-183 and AP-186 RETIRED 2026-08-10 by issue #363's interface-text seam — see their retirement notes below; AP-190 filed 2026-08-10 at Campaign CH slice CH6c — window opacity now fades every RetailWindowManager window on retail's focus-driven Default/Active mechanism, not just ChatInterface-derived ones, and ships gmMainChatUI's 1.0/1.0 default as the ONE shared default across every registered window (fixed from the original 0.5/1.0 base-ChatInterface value, per the row's own REWORDED (2)) instead of applying it only to ChatInterface-derived windows, retiring AP-40 (the prior "opacity is fixed at 0.75, no focus transition" row) in the same commit; AP-189 filed 2026-08-10 at the CH6a/b REJECT-review rework, SHOULD-FIX 5 — acdream's ONE shared 500-entry/200-line-display-tail chat log gives every window a shallower EFFECTIVE per-window scrollback depth than retail's own per-window 10,000-line log, though the accumulate-while-closed and independent-per-window-scroll BEHAVIORS are both correctly reproduced; AP-188 filed 2026-08-10 at Campaign CH slice CH6b — a floating chat window's chat entry always sends on the Say channel because the floaty LayoutDesc authors no talk-focus menu and acdream does not (yet) share the main window's currently-selected channel across all five chat-window instances; AP-187 filed 2026-08-10 at Campaign CH slice CH6b — the four floating chat windows' text-type filters persist in local `settings.json` only (`ChatSettings.ChatWindow1..4Filter`), with no analog to retail's server-side `0x1000008C` GameplayOptions blob, so a character's floaty filter customization does not travel between acdream installs or round-trip to/from a retail client sharing the same character; AP-186 RETIRED 2026-08-10, issue #363's interface-text seam — `ChatVM` now carries an `OnInterfaceText` hook (`Action?`) the App-layer composition wires to `RuntimeCommunicationState.AddText(text, RetailLogTextType.ClientLocal)`, exactly fix shape (a) this row's own filing proposed; `ChatCommandRouter`'s two local-presentation fallbacks (`RetailCommandHelpTable.UnknownCommand` and the degenerate-prefix "Unknown command: {verb}." refusal) now call `ShowInterfaceText` and reach the SpewBox, with a null-fallback into the chat log (still tagged `ClientLocal`) for hosts that never wire the hook (headless has no `ChatVM` at all). Closes ISSUES.md #367; AP-185 filed 2026-08-10 at Campaign CH slice CH6a — the chat window's UiLocked border-art cosmetic swap is unported, see the row for detail; AP-184 filed 2026-08-09 at Campaign CH user-gate round 2, item 3 — three of the seven retail `/help` group-topic listings (channels/chatting/commands) remain PARTIAL, not fully verbatim: their detail text is built in full or in part by `ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290`, which constructs its output from three BN-mislabeled data fragments (`&ClientCommunicationSystem::\`vftable'.RecvNotice_StartBarberNotice` etc. are NOT real vtable dispatch) concatenated around a live `ChannelSystem::GetChannelName` lookup, not decodable with confidence from a static string sweep; each partial group keeps its own verbatim summary line and an explicit UNVERIFIED note instead of the fully-fabricated meta-message the user caught on `/help death` (that group, plus status/text/allegiances, are now COMPLETE verbatim listings); tracked as ISSUES.md #364. Round 2 item 2 also deletes `PortalWaitNoticeController` (the dedicated centered-overlay presentation the user reported was the wrong retail surface) and reroutes the portal-space wait-cue notice through the same `AddText`/SpewBox chokepoint every other on-screen interface-text site uses — AP-178's open SpewBox position/extent/font/colour questions now cover this notice too, since its separate controller and consts are gone; no new row was needed for the surface mismatch itself, since it was never separately registered (`PortalWaitNoticeController`'s own doc comment asserted "not a chat message" as an accepted design, not a flagged divergence). AP-150 RETIRED 2026-08-09 at Campaign CH user-gate round 1, item D (#329) — `PortalTunnelPresentation.TickRotation` now emits `"In Portal Space - Please Wait..."` unconditionally on every rotation-segment expiry, exactly matching `gmSmartBoxUI::UseTime`'s `else`-arm at 0x004D6FCD, instead of gating on `_waitCueVisible`, which only ever went true after the invented 5-second `RuntimeWorldTransitState.RetailWaitCueDelay` hold; `RetailWaitCueDelay`/`ObserveWait`/`SetWaitCue` remain as `LocalPlayerTeleportController`'s own hold-delay telemetry (`RuntimePortalSnapshot.WaitCueShown`) but no longer gate the on-screen cue, so they are not a residual of this row — closes issue #329; AP-183 RETIRED 2026-08-10, issue #363 — every named site now routes through the `ChatVM.ShowInterfaceText`/`OnInterfaceText` seam (see AP-186's retirement note) at its correct retail type: `DoStupidChannelHack` ("You must specify the text you wish to say!", newly wired — the six legacy channel verbs previously fell through `ChatInputParser.Parse`'s pure `return null` with no message at all), `DoChannelList`/`On`/`Off` ("Please specify the channel name.", reclassified), `DoAllegiance` ("Please see @help Allegiance...", reclassified), `DoHouseAvailableList` (reclassified AND corrected to retail's own "Please see @help hslist for more information on how to use this command" string, replacing the acdream-synthesized "Usage: /hslist " fallback — verified `acclient_2013_pseudo_c.txt:381481`/`1029383`), and `DoReply` ("Someone must @tell you first!", newly wired for the message-but-no-last-teller branch only — bare `/r` with no message at all is a separate retail branch, deliberately still unported). `DoSpeaker`/`DoEndurance`/`DoTitle` are untouched, confirmed still correct at `0x00`. The generic bad-args fallback (`ChatCommandRouter.Submit`'s catalog dispatch) now resolves `WeenieErrorMessages.Resolve(0x026u, null)` ("That is not a valid command.", the exact port of `DoCommand @0x0057E46D`'s `HandleFailureEvent(0x26)`) instead of synthesizing a `"Usage: {Usage}"` line — cross-checked against five decompiled handlers (`DoDie` plus the four above), all `0x1A`, confirming the uniform routing decision; AP-182 filed 2026-08-09 at Campaign CH slice CH4, corrected at the CH4 REJECT-review (nit 11) — `@title` is wired to a pure no-op (the value is neither stored nor consumed anywhere) and also omits `DoTitle`'s three local failure messages; recount at the CH3 Opus review corrected a pre-existing off-by-one; AP-181 filed 2026-08-09, Campaign CH slice CH3 — the local chat spam throttle (`IsMessageSpam`) has no acdream port. AP-178 NARROWED 2026-08-09 at the CH2 REJECT-review rework NIT 3, wording corrected at the CH2 re-review nits pass (`docs/plans/2026-08-09-chat-parity-campaign.md`, nits 1/2/6) — the original `dats.Portal` pass used an id source that was not Portal's own (`dats.Portal.GetAllIdsOfType()` is empty for this type), so it established nothing about Portal either way; extending a correctly-paired sweep to `dats.Local` FOUND the SpewBox element there; extent (`450×72`) and `MaxConcurrentItems` (`4`, not the code-default `1`) are now AUTHORED, leaving absolute screen position, colour, AND vertical content flow (now TOP-aligned, acdream's own invention pending measurement) open. AP-180 filed 2026-08-09 at the CH2 REJECT-review rework — `RuntimeCommunicationState.AddText`'s `windowId` parameter is accepted but not consumed, so retail's dual-destination echo (a `0x1A` message with a non-zero `windowId` lands in both the SpewBox and its originating chat window) is unimplemented; latent today since every production caller passes `windowId = 0`. AP-177/AP-178/AP-179 filed 2026-08-09, Campaign CH slice CH2 (interface text / SpewBox) — AP-177 records the invented 5-second SpewBox line lifetime (retail's real timeout is keystone-owned and unmeasured); AP-178's original filing recorded the invented SpewBox screen position/extent/font/colour/MaxConcurrentItems after `SpewBoxLayoutDumpDiagnostic`'s Portal-only sweep found zero elements of class 0x10000016 — see the NARROWED note above for the corrected finding; AP-179 is the OnCombatLine half of the RETIRED AP-176 split out to its own row. AP-176 RETIRED the same day — the WeenieErrorMessages full 344-row `HandleFailureEvent` port (`WeenieErrorMessages.Resolve`) replaces the single-stand-in-`LogTextType` approximation that row recorded for `ChatLog.OnWeenieError`. AP-175 filed 2026-08-09, Campaign CH slice CH1 — PopUpString renders as a chat-log line instead of retail's modal dialog; AP-39 updated the same day — chat coloring is now retail's exact 34-value `LogTextType` table, not a synthetic per-`ChatKind` approximation of it. AP-173 and AP-174 filed 2026-08-08, Campaign A slice A2 — AP-173 expresses retail's ±15 dB DirectSound pan as an OpenAL azimuth by inverting the constant-power pan law, since AL exposes no per-channel gain for a mono source; AP-174 records acdream's extra master volume knob on top of retail's three, folded into retail's single master multiply so the −50 dB cutoff and dB quantisation move with it. AP-172 and AP-171 filed 2026-08-08, #354 spell-bar drag-reorder fix — the favorite-bar reorder gesture defers its own list rebuild for the drag's duration so `UiRoot`'s drag-cancel safety net cannot destroy the in-flight cell, compensating the drop-time target index for the resulting stale sibling numbering; final positions and the wire pair are retail-exact, only the mid-drag visual reflow timing differs. AP-170 filed 2026-08-08, grand-gate finding G3 — an out-of-range vendor Use now arms on arrival instead of sending immediately, because the user's local ACE server polls for the player to actually reach use range before opening the shop panel and a too-early Use is silently lost; AP-169 filed 2026-08-08, grand-gate finding G2 — the vendor toolbar split-slider resolver falls back to the packed shop-supply-count field when the item's own `PublicWeenieDesc._stackSize` is absent, because the user's local ACE server never populates the latter for a browse-list item; AP-167/AP-168 filed 2026-08-09 at the Opus review of `92ea3977` (findings F1/F6) — Buy All's container-vs-item slot classification approximates retail's bitfield/capacity test with `ItemType.Container` [AP-168], and SellSingleItem's non-empty-container refusal branch is not ported [AP-167]; AP-164 RETIRED the same review (finding F4) — BF_RETAINED is now checked end to end; AP-162 NARROWED the same review (finding F1) — Buy All's four client-side pre-send guards are now ported, leaving only the single-item TryBuy path without one; AP-161 gains a REVIEW CORRECTIONS paragraph the same review (findings F1-F13) summarizing the rest as bug fixes to already-claimed behavior, not new divergences. AP-164/AP-165/AP-166 filed 2026-08-09 at Slice 6b/6c (staging+sell arc) — InqAcceptability's non-sellable bitfield is unmodeled [AP-164], the Buy-side stackable-removal-amount test substitutes DescStackSize for retail's _maxStackSize [AP-165], and the Buying/Selling tabs' own purse/count text plus the cross-panel pending-sell inventory highlight are unwired [AP-166]; AP-161 NARROWED the same day — the row's last vendor-specific residual (Buying/Selling tabs render but carry no data binding) CLOSES now that both tabs are fully wired (staging, drag-to-sell, InqAcceptability gating, Sell 0x0060, the X-close confirmation), leaving only the two long-standing PRE-EXISTING residuals (dropdown arrow-cap glyph, alt-currency m_last_sale simplification) plus the three new AP-164/165/166 residuals just filed; AP-162 EXTENDED the same day — the same no-client-pre-check omission now also covers the batched "Buy All" path (TryBuyAll), not just the single-item TryBuy. AP-162/AP-163 filed 2026-08-09 at Slice 6.3 (buy arc) — no client-side Buy affordability/capacity pre-check [AP-162] and the shop-item guid-collision skip-not-clobber policy [AP-163]; AP-161 NARROWED the same day — the private-selection and unwired-examine residuals CLOSE at Slice 6.1/6.2, leaving only the dropdown arrow-cap glyph and the alt-currency `m_last_sale` simplification, plus a confirmed-absent-from-retail note on double-click-to-buy. AP-161 REWRITTEN 2026-08-09 at the Slice 5.4 review (findings F1-F8) — the popup-never-rendered, wrong-quantity-price, no-auto-select, dropped-icon-layer, stale-category-on-vendor-switch, and unguarded-Apply-fanout bugs the review found are fixed (`VendorUiController.cs`, `VendorState.cs`, `GameEventWiring.cs`, `RetailUiRuntime.cs`); the row now records only the four consciously-deferred residuals it still owns (private per-panel selection vs. retail's global `ACCWeenieObject::selectedID`, the unwired shop-item examine route, the dropdown button-face arrow-cap glyph, and the alt-currency held-amount's `m_last_sale`-free simplification). AP-110's "retail-correct per-unit prices" phrasing is corrected the same day to "quantity-correct pricing" — the OLD phrase mischaracterized what retail even shows (a `GetObjectSplitSize`-quantity price, not literally one unit) independent of whether the code was buggy. AP-161 filed 2026-08-09 at Slice 5.4 (vendor browse panel) — the authored "Buying"/"Selling" tabs render and switch pages but carry no data binding, per contract decision 8's required successor to AP-110's narrowing; AP-110 NARROWED the same day — "vendor" is retired from its absent-panels list now that the "Items" browse tab is user-reachable. AP-160 filed 2026-08-07 at Slice 5.3 — the client-local vendor-panel distance watcher closes on plain 3D center distance instead of retail/ACE's cylinder-gap distance, because Runtime has no per-entity collision radius/height source outside the App-layer's Setup-cylinder resolver. AP-158 RETIRED 2026-08-06 by the #333 fix, closing #337 — the `maxReach` distance pre-filter is DELETED rather than re-centred, because retail has none: `CObjCell::find_obj_collisions` @0x0052b750 walks the cell's shadow list and calls `CPhysicsObj::FindObjCollisions` unconditionally. The row's predicted symptom was observed live at Neftet before it was fixed — a tall prop AP-156 had just placed correctly still not blocking, plus jumps sinking into the mesh and corpses falling through. Perf measured, not assumed: at the live-maximum 38 in-cell candidates 10.61 µs → 16.68 µs per resolve. AP-159 filed 2026-08-06 at the #334 fix — the INDOOR half of AP-156’s traversal residual is all that remains of it; the outdoor half is CLOSED by the `find_bbox_cell_list` port, and AP-156’s RISK COLUMN IS CORRECTED at the same commit: it recorded the residual as “extra broadphase candidates, never a missed one”, which generalised the indoor direction to the whole row and is exactly why #334 — a MISSED one, and a user-observed loss of collision on landblock-spanning formations — sat inside it unnoticed. AP-158 filed 2026-08-06 at the AP-156 fix review — the shadow broadphase's `maxReach` distance pre-filter is acdream's own invention with NO retail counterpart, and it measures from the part origin, so it can discard a genuine contact for exactly the off-centre parts AP-156 just placed correctly; issue #333. AP-156 CORRECTED at the same review: its population was understated — 172 is AP-152's DISPATCH population, not AP-156's CONTAINMENT population. AP-155 NARROWED and AP-156/AP-157 filed 2026-08-06 at the AP-152 retail-conformance review. AP-155 bundled two divergences with different code paths, populations and gates under one id; its flood half is now AP-156, **with its direction corrected**. AP-155(b) recorded the BSP flood approximation as OVER-inclusive and used that direction as the reason the residual was safe to defer; measured over the installed DAT it was UNDER-inclusive for 428 of the 530 BSP-bearing Setups (the AP-156 fix review corrected the originally-recorded '170 of 172'), because `BuildFloodSpheres` carried each physics-BSP part's root bounding-sphere RADIUS while discarding that sphere's own ORIGIN and centring it on the part origin. That is the #98/#168 class, and for 43 Setups the post-AP-152 flood was strictly smaller than the pre-AP-152 one. AP-156 records the correction and the fix — `ShadowShape.BoundsCenter`, filled from the same resolver that supplies the radius, plus the retirement of the 10-sphere clamp on a branch where retail has none — and keeps open only the sphere-vs-portal TRAVERSAL approximation. AP-157 is the previously unregistered third-branch substitution: retail floods from one `CPartArray::GetSortingSphere` where acdream floods from every Sphere shape, and acdream's cylinder flood ignores `CylHeight`. AP-152 RETIRED 2026-08-06, one day after it was filed: `ShadowShapeBuilder.FromSetup` now dispatches BSP-first instead of unioning, and `ShadowObjectRegistry.BuildFloodSpheres` now applies `calc_cross_cells`' own BSP → cylsphere → sorting-sphere order. Four statements in the row were false and are corrected in its retirement text — most importantly its predicted symptom, "catching on a doorway sill", which could not have been occurring: `Transition.BspOnlyDispatch` had already made the extra primitive inert at collision-query time since 2026-05-25. The live half was CELL MEMBERSHIP, the #98/#168 symptom class, which had no such guard. AP-153/AP-154/AP-155 filed at that retirement — retail's dispatch flag is cached once at part-array construction where acdream's gate is live [AP-153]; acdream's query-time guard takes a CLIENT-DERIVED flag off the WIRE and never derives it, an undeclared dependency on ACE reading the same DAT bit [AP-154]; and the static publication paths emit a Setup Sphere as a height-capped Cylinder while `BuildFloodSpheres` approximates retail's bounding BOX with bounding SPHERES [AP-155, whose flood-priority half is closed by the same commit]. AP-152 filed 2026-08-06 at the AP-22 retirement — the LIVE collision path emits Setup primitives and per-part physics-BSP shapes additively where retail's `CPhysicsObj::FindObjCollisions` dispatches exclusively; 172 of 5,935 installed Setups are affected, including BSP doors, so it needs its own visual gate and was deliberately not folded into the AP-22 commit; the count is unchanged because AP-22 retired in the same commit. AP-22 RETIRED 2026-08-06 — retail synthesizes no shape for a shapeless object (`CPhysicsObj::FindObjCollisions` 0x0050f050 exits at `0x0050f22f je 0x50f31b` returning the seeded OK_TS, and `CPartArray::GetRadius`/`GetHeight` are absent from its whole call set), so the invented `setup.Radius` cylinder was deleted rather than re-derived; the row's site list named one file that never contained the fallback and omitted the two that did, one of them the headless-only copy, and its "rare decorative props" risk described an unreachable branch — 0 of 5,935 installed Setups can satisfy the guard. AP-150/AP-151 filed 2026-08-06 at the #280 dual review — the wait cue's five-second arming is acdream's own and not retail's trigger [AP-150], and the reveal gate is materially stricter than retail's DAT-residency prefetch predicate on the mesh-build/GPU-upload axis [AP-151], the opposite asymmetry from AP-149; AP-149 filed 2026-08-05 at the #280 portal-prefetch fix — the reveal gate's outer ring accepts terrain-only publication where retail requires LandBlockInfo and every building EnvCell; the fix closes the reveal-window/visible-window ratio, not this residual; AP-148 filed 2026-08-05 at the C5b closeout — acdream's local-player Gate A requires the wire TELEPORT_TS to be EQUAL where retail requires only that it not be OLDER, verified by disassembly against the PDB-paired binary after two review rounds read the Binary Ninja tautology and missed it; AP-147 filed 2026-08-05 at the C5b architecture review, finding D3 — the accepted-Position delta stream's cardinality change and its torn intermediate; AP-138 amended at the same review — C5b staled its route-2 first-submit `CurrentCellId` measurement; AP-131 RETIRED 2026-08-05, C5b, closing #275 — the steady-state merge's `installPlacementFrame: true, clearParent: true` literals no longer exist; `InboundPhysicsStateController.TryApplyPosition` now computes both flags PRE-MERGE from `(disposition, hasAnimations(old))`, which is exactly `RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition`'s own `ApplyPlacementFrameBeforeRouting`/`UnparentBeforeRouting` rows (false/false on the Gate A force row, `!HasAnimations`/true on every accepted non-force route). Retail decides both writes BEFORE `MoveOrTeleport` is consulted — Gate A @0x0045400C returns @0x0045409D ahead of `unset_parent` @0x00454129 and the `HasAnims` `SetPlacementFrame` gate @0x00454137 — so the flags need no route, no player distance and no signature change. The row's predicted symptoms are gone: an animated entity's ordinary Position no longer installs a placement frame retail skips, and a ForcePosition no longer unparents. Evidence: `InboundPhysicsStateControllerTests` — `ApplyOnAnimatedEntity_NeverInstallsTheWirePlacementFrame`, `ApplyOnNonAnimatedEntity_InstallsTheWirePlacementFrame`, `ForcePositionOnParentedLocalPlayer_RetainsTheParentAttachment`, and the 12-row `MergedPrePlacementFieldsMatchTheClassifiedRouteFlags` matrix which uses the production classifier as its oracle rather than re-encoding the table; all four sabotage-verified in both directions. The row's "the legacy caller is deleted at the production cutover" framing was overtaken: the caller was CORRECTED, not deleted, and remains the only production Position wire caller; AP-145 RETIRED 2026-08-05, C5a commit 1, closing #318 — `TryPublishPlace` now publishes the local player's Place through `LocalPlayerShadowSynchronizer.SyncPose`, the same publisher ordinary per-tick movement uses, instead of a direct `LocalPlayerShadowState.Set` that never touched `PhysicsEngine.ShadowObjects`; AP-1 RETIRED 2026-08-05, C5a deletion sweep — `PhysicsEngine.Resolve`/`ResolvePlacement`/`HasCellSurface` deleted outright, zero production callers, so "production zero-delta routes remain on the legacy resolver" is now structurally false; AP-146 filed 2026-08-05, #319 fix — the local player's canonical cell is written only at login/inbound-Position/teleport, not per ordinary-movement tick as retail's SetPositionInternal does; #319's fix makes a player-parented child inherit exactly this coarseness, stale-but-equal to the parent, not a new staleness class; follow-up filed as issue #320; AP-144 filed 2026-08-05, C4 route 3 round 3 (R7) — the portal-arrival movement-event send reuses `UsePositionFromServer` (`autonomy_level != 2`) where retail's actual gate, `SendMovementEvent`, is `autonomy_level != 0`; the two agree everywhere except level 1, which no production caller can reach today; AP-142/AP-143 filed 2026-08-04, C4 route 7 — the parented-child single-field cell model (id/pointer collapse, zero-not-stale removal propagation, same-cell tick-loop subsumption) and the headless parent-realize drive's skipped holding-location validation; AP-141 filed 2026-08-04, C4 route 5, NARROWED 2026-08-04 at the round-2 delta review — the far-branch StopInterpolating clause was wrong for the adopted-body case (it is now ported there) and the row's language now distinguishes "never armed" from "never re-anchored"; CORRECTED 2026-08-04 at the round-3 delta review — the risk column's "would drag the body toward a stale anchor" claim was itself wrong (the leash anchor is write-only; `ConstraintManager::adjust_offset` only brakes, never pulls) and is retracted; every half remains test-gated only, since ACE never sends a missile UpdatePosition; AP-140 filed AND RETIRED 2026-08-04 — filed at the Bug B Opus review because the two accepted-Position routing gates read the client `Airborne` flag, i.e. walkability, where retail's free-flight predicate is CONTACT, and Bug B had just turned "in contact, not on walkable ground" from unreachable into ordinary; retired the same day by pointing both gates at `PhysicsBody.InContact`, retail's literal `transient_state & 1` test at `InterpolationManager::adjust_offset` @0x00555D52 (bit 0 = `CONTACT_TS`, acclient.h:3690), while leaving `Airborne` and all five of its `!Body.OnWalkable` writers untouched — the narrow shape the row itself pinned. A remote sliding on a steep face now interpolates as retail does instead of snapping at UpdatePosition cadence; AP-139 filed 2026-08-04, Bug B remote steep-contact slide — the interpolation-queue clear on the landing edge, carried over from the deleted hand-rolled remote landing block; AP-81 narrowed the same day by that fix, which retired its whole GRAVITY half; AP-87 annotated the same day — its predicted symptom was observed live and then fixed at the source, with the row's own thresholds and conditions deliberately unchanged; AP-138 filed 2026-08-04, C4 route 4b-2 dual Opus review, parts (1) and (2) rewritten the same day at the DELTA review — the far snap's refusable-placement residual: store_position only on the outcomes that never reached the engine, the two quiescence parks made restorable at the source, with the rollback gated on the cell it actually restores into, rather than refused by a pre-flight that structurally cannot see them, and the leash not armed through a superseded incarnation; AP-137 filed 2026-08-04, C4 route 4b-2 and rewritten the same day at that review, `teleport_hook`'s call list completed at the delta review — the acdream-only null/rejected/cell-less leftover arm, what the deleted duplicated 96 m/4 m constant pairs actually computed, and the vacuous headless satisfaction; AP-136 filed 2026-08-04, C4 route 4b-1 review, NARROWED 2026-08-04 at the C4 route 4b-2 delta review and AMENDED 2026-08-04 by the cancelled-park presentation rollback (the row's "restored visible" claim covered only the CANONICAL half; the presentation half was never rolled back, which left a parked-then-cancelled remote that stops moving invisible in the world AND absent from the radar for the rest of the session — a defect, now fixed by the `WithdrawalRestored` receipt, with the selection residual filed as AD-63) — a cancelled lost-cell park re-shows the entity where retail keeps it hidden until cell load, and the rollback's scope now covers the two placement-side quiescence parks whenever the cell it restores into is not itself quiescing — round 4 (2026-08-04) applies that same test a second time at RESTORE time, because a retained park's rollback lands a packet later; AP-135 filed 2026-08-03, C4 route 4a — the airborne no-op's retained acdream bookkeeping; the stated total was 2 rows stale before that filing and is now a literal count of this section; AP-130/AP-131/AP-132 filed 2026-08-02, continuation-executor slice; AP-5 retired 2026-07-31 at Campaign P Slice 2A — every successful `step_down` now performs retail's final `PLACEMENT_INSERT`; AP-3/AP-4 retired 2026-07-31 at Campaign P Slice 1B — `transitional_insert` and `edge_slide` now preserve retail's valid-contact early return and Branch-1-first order; AP-127 retired 2026-07-31 by #268 — the complete augmentation chain is shared by character UI and Runtime movement; AP-30 retired 2026-07-30 by the movement parity audit — retail Frame::is_equal genuinely uses the 0.0002 epsilon [byte-confirmed], so the row recorded a NON-divergence; acdream already matches; AP-129 narrowed 2026-07-30 at the P4 Opus review fix — `CanMoveInto`/`RestrictionDB::IsAllowedIn` are now ported and fed end-to-end (CreateObject HouseOwner/HouseRestrictions/Monarch tail fields + live `House_UpdateRestrictions 0x0248`, resolved through `PhysicsEngine.Objects`), retiring the original "CanMoveInto entirely unmodeled, unconditional fail-closed" gap the row described — the review was triggered by `RestrictionObjPrevalenceInspectionTests` showing 103,766 of 729,888 installed EnvCells (the whole housing estate) carry a baked `RestrictionObj`, so the unconditional fail-closed default would have locked every house for every player including its own owner; AP-10 retired 2026-07-30 at Campaign P Slice P4 — restored retail's 0.1 m dry-corner water sink-in, full suite green proving the sticky-bit no-regression argument; AP-71 retired same slice — `check_entry_restrictions` ported at the head of the indoor `FindEnvCollisions` branch, `CellPhysics.RestrictionObj` wired from the DAT-baked `EnvCell` field in both the dev and production caching paths; AP-128 filed 2026-07-30 at the P3 Opus review — PK-timer clock basis; AP-25 retired 2026-07-30 at Campaign P Slice P1 — the vitae/enchantment-aware run/jump skill chain; AP-7 retired 2026-07-30 at Campaign P Slice P2 — `calc_friction`'s threshold ported to retail's confirmed 0.25f; its still-open cos(10°)-vs-0.99999536f Sledding constant question moved to AD-55) +## 3. Documented approximation (AP) — 131 active rows (AP-184 RETIRED 2026-08-10 at Campaign CH round 4 — the three PARTIAL `/help` group topics (channels/chatting/commands) are now COMPLETE verbatim listings, `ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290` fully decoded (the "vftable slot" operands are the same pooled/mislabeled-data artifact as AP-186's own precedent, not real vtable dispatch — reading the function's own disassembly for the `push imm32` preceding each constructor call resolves them), see its retirement note below, closing ISSUES.md #364; AP-113 RETIRED 2026-08-10 at the consolidated-review round, SHOULD-FIX 3/1 byproduct — DoLifestone's own bad-args refusal text is now byte-recovered, see its retirement note below; AP-183 and AP-186 RETIRED 2026-08-10 by issue #363's interface-text seam — see their retirement notes below; AP-190 filed 2026-08-10 at Campaign CH slice CH6c — window opacity now fades every RetailWindowManager window on retail's focus-driven Default/Active mechanism, not just ChatInterface-derived ones, and ships gmMainChatUI's 1.0/1.0 default as the ONE shared default across every registered window (fixed from the original 0.5/1.0 base-ChatInterface value, per the row's own REWORDED (2)) instead of applying it only to ChatInterface-derived windows, retiring AP-40 (the prior "opacity is fixed at 0.75, no focus transition" row) in the same commit; AP-189 filed 2026-08-10 at the CH6a/b REJECT-review rework, SHOULD-FIX 5 — acdream's ONE shared 500-entry/200-line-display-tail chat log gives every window a shallower EFFECTIVE per-window scrollback depth than retail's own per-window 10,000-line log, though the accumulate-while-closed and independent-per-window-scroll BEHAVIORS are both correctly reproduced; AP-188 filed 2026-08-10 at Campaign CH slice CH6b — a floating chat window's chat entry always sends on the Say channel because the floaty LayoutDesc authors no talk-focus menu and acdream does not (yet) share the main window's currently-selected channel across all five chat-window instances; AP-187 filed 2026-08-10 at Campaign CH slice CH6b — the four floating chat windows' text-type filters persist in local `settings.json` only (`ChatSettings.ChatWindow1..4Filter`), with no analog to retail's server-side `0x1000008C` GameplayOptions blob, so a character's floaty filter customization does not travel between acdream installs or round-trip to/from a retail client sharing the same character; AP-186 RETIRED 2026-08-10, issue #363's interface-text seam — `ChatVM` now carries an `OnInterfaceText` hook (`Action?`) the App-layer composition wires to `RuntimeCommunicationState.AddText(text, RetailLogTextType.ClientLocal)`, exactly fix shape (a) this row's own filing proposed; `ChatCommandRouter`'s two local-presentation fallbacks (`RetailCommandHelpTable.UnknownCommand` and the degenerate-prefix "Unknown command: {verb}." refusal) now call `ShowInterfaceText` and reach the SpewBox, with a null-fallback into the chat log (still tagged `ClientLocal`) for hosts that never wire the hook (headless has no `ChatVM` at all). Closes ISSUES.md #367; AP-185 filed 2026-08-10 at Campaign CH slice CH6a — the chat window's UiLocked border-art cosmetic swap is unported, see the row for detail; AP-184 RETIRED 2026-08-10 at Campaign CH round 4, closing ISSUES.md #364 — filed 2026-08-09 at Campaign CH user-gate round 2, item 3, recording that three of the seven retail `/help` group-topic listings (channels/chatting/commands) remained PARTIAL because their detail text is built in full or in part by `ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290`, which the filing believed "not decodable with confidence from a static string sweep" because Binary Ninja renders its three internal string operands as dereferences of unrelated vtable slots (`&ClientCommunicationSystem::\`vftable'.RecvNotice_StartBarberNotice` etc.). That belief was WRONG — the same pooled/mislabeled-data artifact this register already documented elsewhere (AP-113's retirement note) applies here too: reading the function's own disassembly for the `push imm32` immediately preceding each `PStringBase::PStringBase` constructor call (rather than trusting BN's line-grouped rendering, which hides the true instruction order) resolves all three operands directly — `"@"` + a one-character tag sliced from a shared wide literal `U"fvpca"`/`U"mh,."` (a wide string read through a narrow `char*` truncates at the first zero high byte, the "hack" retail's own function name calls out) + `" - Sends a broadcast to your "` + `ChannelSystem::GetChannelName`'s own literal switch-table result + `".\n"`. `ChannelsGroupDetail` (entirely 6 such calls), `ChattingGroupDetail` (6 more, plus a `HelpReply@0x00577A50` Summary-branch quirk that unconditionally emits reply+pr+mr together — read directly, not assumed), and `CommandsGroupDetail` (`HelpAllGroup`, a straight-line concatenation of every other group's Detail branch plus a handful of its own short one-liners, including a CONFIRMED retail saveui/loadui duplicate) are now COMPLETE verbatim listings, matching the four (death/status/text/allegiances) the original filing already had. See `RetailCommandHelpTable`'s class remarks and `RetailCommandHelpTableTests` for the full per-line address citations. Round 2 item 2 also deletes `PortalWaitNoticeController` (the dedicated centered-overlay presentation the user reported was the wrong retail surface) and reroutes the portal-space wait-cue notice through the same `AddText`/SpewBox chokepoint every other on-screen interface-text site uses — AP-178's open SpewBox position/extent/font/colour questions now cover this notice too, since its separate controller and consts are gone; no new row was needed for the surface mismatch itself, since it was never separately registered (`PortalWaitNoticeController`'s own doc comment asserted "not a chat message" as an accepted design, not a flagged divergence). AP-150 RETIRED 2026-08-09 at Campaign CH user-gate round 1, item D (#329) — `PortalTunnelPresentation.TickRotation` now emits `"In Portal Space - Please Wait..."` unconditionally on every rotation-segment expiry, exactly matching `gmSmartBoxUI::UseTime`'s `else`-arm at 0x004D6FCD, instead of gating on `_waitCueVisible`, which only ever went true after the invented 5-second `RuntimeWorldTransitState.RetailWaitCueDelay` hold; `RetailWaitCueDelay`/`ObserveWait`/`SetWaitCue` remain as `LocalPlayerTeleportController`'s own hold-delay telemetry (`RuntimePortalSnapshot.WaitCueShown`) but no longer gate the on-screen cue, so they are not a residual of this row — closes issue #329; AP-183 RETIRED 2026-08-10, issue #363 — every named site now routes through the `ChatVM.ShowInterfaceText`/`OnInterfaceText` seam (see AP-186's retirement note) at its correct retail type: `DoStupidChannelHack` ("You must specify the text you wish to say!", newly wired — the six legacy channel verbs previously fell through `ChatInputParser.Parse`'s pure `return null` with no message at all), `DoChannelList`/`On`/`Off` ("Please specify the channel name.", reclassified), `DoAllegiance` ("Please see @help Allegiance...", reclassified), `DoHouseAvailableList` (reclassified AND corrected to retail's own "Please see @help hslist for more information on how to use this command" string, replacing the acdream-synthesized "Usage: /hslist " fallback — verified `acclient_2013_pseudo_c.txt:381481`/`1029383`), and `DoReply` ("Someone must @tell you first!", newly wired for the message-but-no-last-teller branch only — bare `/r` with no message at all is a separate retail branch, deliberately still unported). `DoSpeaker`/`DoEndurance`/`DoTitle` are untouched, confirmed still correct at `0x00`. The generic bad-args fallback (`ChatCommandRouter.Submit`'s catalog dispatch) now resolves `WeenieErrorMessages.Resolve(0x026u, null)` ("That is not a valid command.", the exact port of `DoCommand @0x0057E46D`'s `HandleFailureEvent(0x26)`) instead of synthesizing a `"Usage: {Usage}"` line — cross-checked against five decompiled handlers (`DoDie` plus the four above), all `0x1A`, confirming the uniform routing decision; AP-182 filed 2026-08-09 at Campaign CH slice CH4, corrected at the CH4 REJECT-review (nit 11) — `@title` is wired to a pure no-op (the value is neither stored nor consumed anywhere) and also omits `DoTitle`'s three local failure messages; recount at the CH3 Opus review corrected a pre-existing off-by-one; AP-181 filed 2026-08-09, Campaign CH slice CH3 — the local chat spam throttle (`IsMessageSpam`) has no acdream port. AP-178 NARROWED 2026-08-09 at the CH2 REJECT-review rework NIT 3, wording corrected at the CH2 re-review nits pass (`docs/plans/2026-08-09-chat-parity-campaign.md`, nits 1/2/6) — the original `dats.Portal` pass used an id source that was not Portal's own (`dats.Portal.GetAllIdsOfType()` is empty for this type), so it established nothing about Portal either way; extending a correctly-paired sweep to `dats.Local` FOUND the SpewBox element there; extent (`450×72`) and `MaxConcurrentItems` (`4`, not the code-default `1`) are now AUTHORED, leaving absolute screen position, colour, AND vertical content flow (now TOP-aligned, acdream's own invention pending measurement) open. AP-180 filed 2026-08-09 at the CH2 REJECT-review rework — `RuntimeCommunicationState.AddText`'s `windowId` parameter is accepted but not consumed, so retail's dual-destination echo (a `0x1A` message with a non-zero `windowId` lands in both the SpewBox and its originating chat window) is unimplemented; latent today since every production caller passes `windowId = 0`. AP-177/AP-178/AP-179 filed 2026-08-09, Campaign CH slice CH2 (interface text / SpewBox) — AP-177 records the invented 5-second SpewBox line lifetime (retail's real timeout is keystone-owned and unmeasured); AP-178's original filing recorded the invented SpewBox screen position/extent/font/colour/MaxConcurrentItems after `SpewBoxLayoutDumpDiagnostic`'s Portal-only sweep found zero elements of class 0x10000016 — see the NARROWED note above for the corrected finding; AP-179 is the OnCombatLine half of the RETIRED AP-176 split out to its own row. AP-176 RETIRED the same day — the WeenieErrorMessages full 344-row `HandleFailureEvent` port (`WeenieErrorMessages.Resolve`) replaces the single-stand-in-`LogTextType` approximation that row recorded for `ChatLog.OnWeenieError`. AP-175 filed 2026-08-09, Campaign CH slice CH1 — PopUpString renders as a chat-log line instead of retail's modal dialog; AP-39 updated the same day — chat coloring is now retail's exact 34-value `LogTextType` table, not a synthetic per-`ChatKind` approximation of it. AP-173 and AP-174 filed 2026-08-08, Campaign A slice A2 — AP-173 expresses retail's ±15 dB DirectSound pan as an OpenAL azimuth by inverting the constant-power pan law, since AL exposes no per-channel gain for a mono source; AP-174 records acdream's extra master volume knob on top of retail's three, folded into retail's single master multiply so the −50 dB cutoff and dB quantisation move with it. AP-172 and AP-171 filed 2026-08-08, #354 spell-bar drag-reorder fix — the favorite-bar reorder gesture defers its own list rebuild for the drag's duration so `UiRoot`'s drag-cancel safety net cannot destroy the in-flight cell, compensating the drop-time target index for the resulting stale sibling numbering; final positions and the wire pair are retail-exact, only the mid-drag visual reflow timing differs. AP-170 filed 2026-08-08, grand-gate finding G3 — an out-of-range vendor Use now arms on arrival instead of sending immediately, because the user's local ACE server polls for the player to actually reach use range before opening the shop panel and a too-early Use is silently lost; AP-169 filed 2026-08-08, grand-gate finding G2 — the vendor toolbar split-slider resolver falls back to the packed shop-supply-count field when the item's own `PublicWeenieDesc._stackSize` is absent, because the user's local ACE server never populates the latter for a browse-list item; AP-167/AP-168 filed 2026-08-09 at the Opus review of `92ea3977` (findings F1/F6) — Buy All's container-vs-item slot classification approximates retail's bitfield/capacity test with `ItemType.Container` [AP-168], and SellSingleItem's non-empty-container refusal branch is not ported [AP-167]; AP-164 RETIRED the same review (finding F4) — BF_RETAINED is now checked end to end; AP-162 NARROWED the same review (finding F1) — Buy All's four client-side pre-send guards are now ported, leaving only the single-item TryBuy path without one; AP-161 gains a REVIEW CORRECTIONS paragraph the same review (findings F1-F13) summarizing the rest as bug fixes to already-claimed behavior, not new divergences. AP-164/AP-165/AP-166 filed 2026-08-09 at Slice 6b/6c (staging+sell arc) — InqAcceptability's non-sellable bitfield is unmodeled [AP-164], the Buy-side stackable-removal-amount test substitutes DescStackSize for retail's _maxStackSize [AP-165], and the Buying/Selling tabs' own purse/count text plus the cross-panel pending-sell inventory highlight are unwired [AP-166]; AP-161 NARROWED the same day — the row's last vendor-specific residual (Buying/Selling tabs render but carry no data binding) CLOSES now that both tabs are fully wired (staging, drag-to-sell, InqAcceptability gating, Sell 0x0060, the X-close confirmation), leaving only the two long-standing PRE-EXISTING residuals (dropdown arrow-cap glyph, alt-currency m_last_sale simplification) plus the three new AP-164/165/166 residuals just filed; AP-162 EXTENDED the same day — the same no-client-pre-check omission now also covers the batched "Buy All" path (TryBuyAll), not just the single-item TryBuy. AP-162/AP-163 filed 2026-08-09 at Slice 6.3 (buy arc) — no client-side Buy affordability/capacity pre-check [AP-162] and the shop-item guid-collision skip-not-clobber policy [AP-163]; AP-161 NARROWED the same day — the private-selection and unwired-examine residuals CLOSE at Slice 6.1/6.2, leaving only the dropdown arrow-cap glyph and the alt-currency `m_last_sale` simplification, plus a confirmed-absent-from-retail note on double-click-to-buy. AP-161 REWRITTEN 2026-08-09 at the Slice 5.4 review (findings F1-F8) — the popup-never-rendered, wrong-quantity-price, no-auto-select, dropped-icon-layer, stale-category-on-vendor-switch, and unguarded-Apply-fanout bugs the review found are fixed (`VendorUiController.cs`, `VendorState.cs`, `GameEventWiring.cs`, `RetailUiRuntime.cs`); the row now records only the four consciously-deferred residuals it still owns (private per-panel selection vs. retail's global `ACCWeenieObject::selectedID`, the unwired shop-item examine route, the dropdown button-face arrow-cap glyph, and the alt-currency held-amount's `m_last_sale`-free simplification). AP-110's "retail-correct per-unit prices" phrasing is corrected the same day to "quantity-correct pricing" — the OLD phrase mischaracterized what retail even shows (a `GetObjectSplitSize`-quantity price, not literally one unit) independent of whether the code was buggy. AP-161 filed 2026-08-09 at Slice 5.4 (vendor browse panel) — the authored "Buying"/"Selling" tabs render and switch pages but carry no data binding, per contract decision 8's required successor to AP-110's narrowing; AP-110 NARROWED the same day — "vendor" is retired from its absent-panels list now that the "Items" browse tab is user-reachable. AP-160 filed 2026-08-07 at Slice 5.3 — the client-local vendor-panel distance watcher closes on plain 3D center distance instead of retail/ACE's cylinder-gap distance, because Runtime has no per-entity collision radius/height source outside the App-layer's Setup-cylinder resolver. AP-158 RETIRED 2026-08-06 by the #333 fix, closing #337 — the `maxReach` distance pre-filter is DELETED rather than re-centred, because retail has none: `CObjCell::find_obj_collisions` @0x0052b750 walks the cell's shadow list and calls `CPhysicsObj::FindObjCollisions` unconditionally. The row's predicted symptom was observed live at Neftet before it was fixed — a tall prop AP-156 had just placed correctly still not blocking, plus jumps sinking into the mesh and corpses falling through. Perf measured, not assumed: at the live-maximum 38 in-cell candidates 10.61 µs → 16.68 µs per resolve. AP-159 filed 2026-08-06 at the #334 fix — the INDOOR half of AP-156’s traversal residual is all that remains of it; the outdoor half is CLOSED by the `find_bbox_cell_list` port, and AP-156’s RISK COLUMN IS CORRECTED at the same commit: it recorded the residual as “extra broadphase candidates, never a missed one”, which generalised the indoor direction to the whole row and is exactly why #334 — a MISSED one, and a user-observed loss of collision on landblock-spanning formations — sat inside it unnoticed. AP-158 filed 2026-08-06 at the AP-156 fix review — the shadow broadphase's `maxReach` distance pre-filter is acdream's own invention with NO retail counterpart, and it measures from the part origin, so it can discard a genuine contact for exactly the off-centre parts AP-156 just placed correctly; issue #333. AP-156 CORRECTED at the same review: its population was understated — 172 is AP-152's DISPATCH population, not AP-156's CONTAINMENT population. AP-155 NARROWED and AP-156/AP-157 filed 2026-08-06 at the AP-152 retail-conformance review. AP-155 bundled two divergences with different code paths, populations and gates under one id; its flood half is now AP-156, **with its direction corrected**. AP-155(b) recorded the BSP flood approximation as OVER-inclusive and used that direction as the reason the residual was safe to defer; measured over the installed DAT it was UNDER-inclusive for 428 of the 530 BSP-bearing Setups (the AP-156 fix review corrected the originally-recorded '170 of 172'), because `BuildFloodSpheres` carried each physics-BSP part's root bounding-sphere RADIUS while discarding that sphere's own ORIGIN and centring it on the part origin. That is the #98/#168 class, and for 43 Setups the post-AP-152 flood was strictly smaller than the pre-AP-152 one. AP-156 records the correction and the fix — `ShadowShape.BoundsCenter`, filled from the same resolver that supplies the radius, plus the retirement of the 10-sphere clamp on a branch where retail has none — and keeps open only the sphere-vs-portal TRAVERSAL approximation. AP-157 is the previously unregistered third-branch substitution: retail floods from one `CPartArray::GetSortingSphere` where acdream floods from every Sphere shape, and acdream's cylinder flood ignores `CylHeight`. AP-152 RETIRED 2026-08-06, one day after it was filed: `ShadowShapeBuilder.FromSetup` now dispatches BSP-first instead of unioning, and `ShadowObjectRegistry.BuildFloodSpheres` now applies `calc_cross_cells`' own BSP → cylsphere → sorting-sphere order. Four statements in the row were false and are corrected in its retirement text — most importantly its predicted symptom, "catching on a doorway sill", which could not have been occurring: `Transition.BspOnlyDispatch` had already made the extra primitive inert at collision-query time since 2026-05-25. The live half was CELL MEMBERSHIP, the #98/#168 symptom class, which had no such guard. AP-153/AP-154/AP-155 filed at that retirement — retail's dispatch flag is cached once at part-array construction where acdream's gate is live [AP-153]; acdream's query-time guard takes a CLIENT-DERIVED flag off the WIRE and never derives it, an undeclared dependency on ACE reading the same DAT bit [AP-154]; and the static publication paths emit a Setup Sphere as a height-capped Cylinder while `BuildFloodSpheres` approximates retail's bounding BOX with bounding SPHERES [AP-155, whose flood-priority half is closed by the same commit]. AP-152 filed 2026-08-06 at the AP-22 retirement — the LIVE collision path emits Setup primitives and per-part physics-BSP shapes additively where retail's `CPhysicsObj::FindObjCollisions` dispatches exclusively; 172 of 5,935 installed Setups are affected, including BSP doors, so it needs its own visual gate and was deliberately not folded into the AP-22 commit; the count is unchanged because AP-22 retired in the same commit. AP-22 RETIRED 2026-08-06 — retail synthesizes no shape for a shapeless object (`CPhysicsObj::FindObjCollisions` 0x0050f050 exits at `0x0050f22f je 0x50f31b` returning the seeded OK_TS, and `CPartArray::GetRadius`/`GetHeight` are absent from its whole call set), so the invented `setup.Radius` cylinder was deleted rather than re-derived; the row's site list named one file that never contained the fallback and omitted the two that did, one of them the headless-only copy, and its "rare decorative props" risk described an unreachable branch — 0 of 5,935 installed Setups can satisfy the guard. AP-150/AP-151 filed 2026-08-06 at the #280 dual review — the wait cue's five-second arming is acdream's own and not retail's trigger [AP-150], and the reveal gate is materially stricter than retail's DAT-residency prefetch predicate on the mesh-build/GPU-upload axis [AP-151], the opposite asymmetry from AP-149; AP-149 filed 2026-08-05 at the #280 portal-prefetch fix — the reveal gate's outer ring accepts terrain-only publication where retail requires LandBlockInfo and every building EnvCell; the fix closes the reveal-window/visible-window ratio, not this residual; AP-148 filed 2026-08-05 at the C5b closeout — acdream's local-player Gate A requires the wire TELEPORT_TS to be EQUAL where retail requires only that it not be OLDER, verified by disassembly against the PDB-paired binary after two review rounds read the Binary Ninja tautology and missed it; AP-147 filed 2026-08-05 at the C5b architecture review, finding D3 — the accepted-Position delta stream's cardinality change and its torn intermediate; AP-138 amended at the same review — C5b staled its route-2 first-submit `CurrentCellId` measurement; AP-131 RETIRED 2026-08-05, C5b, closing #275 — the steady-state merge's `installPlacementFrame: true, clearParent: true` literals no longer exist; `InboundPhysicsStateController.TryApplyPosition` now computes both flags PRE-MERGE from `(disposition, hasAnimations(old))`, which is exactly `RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition`'s own `ApplyPlacementFrameBeforeRouting`/`UnparentBeforeRouting` rows (false/false on the Gate A force row, `!HasAnimations`/true on every accepted non-force route). Retail decides both writes BEFORE `MoveOrTeleport` is consulted — Gate A @0x0045400C returns @0x0045409D ahead of `unset_parent` @0x00454129 and the `HasAnims` `SetPlacementFrame` gate @0x00454137 — so the flags need no route, no player distance and no signature change. The row's predicted symptoms are gone: an animated entity's ordinary Position no longer installs a placement frame retail skips, and a ForcePosition no longer unparents. Evidence: `InboundPhysicsStateControllerTests` — `ApplyOnAnimatedEntity_NeverInstallsTheWirePlacementFrame`, `ApplyOnNonAnimatedEntity_InstallsTheWirePlacementFrame`, `ForcePositionOnParentedLocalPlayer_RetainsTheParentAttachment`, and the 12-row `MergedPrePlacementFieldsMatchTheClassifiedRouteFlags` matrix which uses the production classifier as its oracle rather than re-encoding the table; all four sabotage-verified in both directions. The row's "the legacy caller is deleted at the production cutover" framing was overtaken: the caller was CORRECTED, not deleted, and remains the only production Position wire caller; AP-145 RETIRED 2026-08-05, C5a commit 1, closing #318 — `TryPublishPlace` now publishes the local player's Place through `LocalPlayerShadowSynchronizer.SyncPose`, the same publisher ordinary per-tick movement uses, instead of a direct `LocalPlayerShadowState.Set` that never touched `PhysicsEngine.ShadowObjects`; AP-1 RETIRED 2026-08-05, C5a deletion sweep — `PhysicsEngine.Resolve`/`ResolvePlacement`/`HasCellSurface` deleted outright, zero production callers, so "production zero-delta routes remain on the legacy resolver" is now structurally false; AP-146 filed 2026-08-05, #319 fix — the local player's canonical cell is written only at login/inbound-Position/teleport, not per ordinary-movement tick as retail's SetPositionInternal does; #319's fix makes a player-parented child inherit exactly this coarseness, stale-but-equal to the parent, not a new staleness class; follow-up filed as issue #320; AP-144 filed 2026-08-05, C4 route 3 round 3 (R7) — the portal-arrival movement-event send reuses `UsePositionFromServer` (`autonomy_level != 2`) where retail's actual gate, `SendMovementEvent`, is `autonomy_level != 0`; the two agree everywhere except level 1, which no production caller can reach today; AP-142/AP-143 filed 2026-08-04, C4 route 7 — the parented-child single-field cell model (id/pointer collapse, zero-not-stale removal propagation, same-cell tick-loop subsumption) and the headless parent-realize drive's skipped holding-location validation; AP-141 filed 2026-08-04, C4 route 5, NARROWED 2026-08-04 at the round-2 delta review — the far-branch StopInterpolating clause was wrong for the adopted-body case (it is now ported there) and the row's language now distinguishes "never armed" from "never re-anchored"; CORRECTED 2026-08-04 at the round-3 delta review — the risk column's "would drag the body toward a stale anchor" claim was itself wrong (the leash anchor is write-only; `ConstraintManager::adjust_offset` only brakes, never pulls) and is retracted; every half remains test-gated only, since ACE never sends a missile UpdatePosition; AP-140 filed AND RETIRED 2026-08-04 — filed at the Bug B Opus review because the two accepted-Position routing gates read the client `Airborne` flag, i.e. walkability, where retail's free-flight predicate is CONTACT, and Bug B had just turned "in contact, not on walkable ground" from unreachable into ordinary; retired the same day by pointing both gates at `PhysicsBody.InContact`, retail's literal `transient_state & 1` test at `InterpolationManager::adjust_offset` @0x00555D52 (bit 0 = `CONTACT_TS`, acclient.h:3690), while leaving `Airborne` and all five of its `!Body.OnWalkable` writers untouched — the narrow shape the row itself pinned. A remote sliding on a steep face now interpolates as retail does instead of snapping at UpdatePosition cadence; AP-139 filed 2026-08-04, Bug B remote steep-contact slide — the interpolation-queue clear on the landing edge, carried over from the deleted hand-rolled remote landing block; AP-81 narrowed the same day by that fix, which retired its whole GRAVITY half; AP-87 annotated the same day — its predicted symptom was observed live and then fixed at the source, with the row's own thresholds and conditions deliberately unchanged; AP-138 filed 2026-08-04, C4 route 4b-2 dual Opus review, parts (1) and (2) rewritten the same day at the DELTA review — the far snap's refusable-placement residual: store_position only on the outcomes that never reached the engine, the two quiescence parks made restorable at the source, with the rollback gated on the cell it actually restores into, rather than refused by a pre-flight that structurally cannot see them, and the leash not armed through a superseded incarnation; AP-137 filed 2026-08-04, C4 route 4b-2 and rewritten the same day at that review, `teleport_hook`'s call list completed at the delta review — the acdream-only null/rejected/cell-less leftover arm, what the deleted duplicated 96 m/4 m constant pairs actually computed, and the vacuous headless satisfaction; AP-136 filed 2026-08-04, C4 route 4b-1 review, NARROWED 2026-08-04 at the C4 route 4b-2 delta review and AMENDED 2026-08-04 by the cancelled-park presentation rollback (the row's "restored visible" claim covered only the CANONICAL half; the presentation half was never rolled back, which left a parked-then-cancelled remote that stops moving invisible in the world AND absent from the radar for the rest of the session — a defect, now fixed by the `WithdrawalRestored` receipt, with the selection residual filed as AD-63) — a cancelled lost-cell park re-shows the entity where retail keeps it hidden until cell load, and the rollback's scope now covers the two placement-side quiescence parks whenever the cell it restores into is not itself quiescing — round 4 (2026-08-04) applies that same test a second time at RESTORE time, because a retained park's rollback lands a packet later; AP-135 filed 2026-08-03, C4 route 4a — the airborne no-op's retained acdream bookkeeping; the stated total was 2 rows stale before that filing and is now a literal count of this section; AP-130/AP-131/AP-132 filed 2026-08-02, continuation-executor slice; AP-5 retired 2026-07-31 at Campaign P Slice 2A — every successful `step_down` now performs retail's final `PLACEMENT_INSERT`; AP-3/AP-4 retired 2026-07-31 at Campaign P Slice 1B — `transitional_insert` and `edge_slide` now preserve retail's valid-contact early return and Branch-1-first order; AP-127 retired 2026-07-31 by #268 — the complete augmentation chain is shared by character UI and Runtime movement; AP-30 retired 2026-07-30 by the movement parity audit — retail Frame::is_equal genuinely uses the 0.0002 epsilon [byte-confirmed], so the row recorded a NON-divergence; acdream already matches; AP-129 narrowed 2026-07-30 at the P4 Opus review fix — `CanMoveInto`/`RestrictionDB::IsAllowedIn` are now ported and fed end-to-end (CreateObject HouseOwner/HouseRestrictions/Monarch tail fields + live `House_UpdateRestrictions 0x0248`, resolved through `PhysicsEngine.Objects`), retiring the original "CanMoveInto entirely unmodeled, unconditional fail-closed" gap the row described — the review was triggered by `RestrictionObjPrevalenceInspectionTests` showing 103,766 of 729,888 installed EnvCells (the whole housing estate) carry a baked `RestrictionObj`, so the unconditional fail-closed default would have locked every house for every player including its own owner; AP-10 retired 2026-07-30 at Campaign P Slice P4 — restored retail's 0.1 m dry-corner water sink-in, full suite green proving the sticky-bit no-regression argument; AP-71 retired same slice — `check_entry_restrictions` ported at the head of the indoor `FindEnvCollisions` branch, `CellPhysics.RestrictionObj` wired from the DAT-baked `EnvCell` field in both the dev and production caching paths; AP-128 filed 2026-07-30 at the P3 Opus review — PK-timer clock basis; AP-25 retired 2026-07-30 at Campaign P Slice P1 — the vitae/enchantment-aware run/jump skill chain; AP-7 retired 2026-07-30 at Campaign P Slice P2 — `calc_friction`'s threshold ported to retail's confirmed 0.25f; its still-open cos(10°)-vs-0.99999536f Sledding constant question moved to AD-55) Wave-0 UI ledger repair (2026-07-10) retired stale AP-38, resolved the AP-84 collision, restored overwritten paperdoll rows as AP-92/AP-93, and registered diff --git a/docs/plans/2026-08-09-chat-parity-campaign.md b/docs/plans/2026-08-09-chat-parity-campaign.md index f6ca1dee..22fb591e 100644 --- a/docs/plans/2026-08-09-chat-parity-campaign.md +++ b/docs/plans/2026-08-09-chat-parity-campaign.md @@ -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. **CH6c (opacity) landed CODE-COMPLETE 2026-08-10**, same constraints — 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 -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, and a final in-client visual pass on everything -fixed so far). +shell is now CODE-COMPLETE. **User gate round 4 ran 2026-08-10 and found +six findings; see "User gate — round 4" below.** Items 3 (user-visible +`/help` meta-markers leaking into live output, closing #364) and 5 (the +main window's 1/2/3/4 indicator buttons now toggle their floating window +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 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 | | CH6c opacity | `a819687c` | 12,459 passed / 4 skipped / 0 failed | APPROVE-WITH-FIXES; fixed this commit — BLOCKER (out-of-box `DefaultOpacity` 0.5→1.0), AP-190 reworded + two new decomp-verified clauses (retail's per-tick ease, retail's entry-field-specific focus predicate), NITs (`UiElement.cs` stale comment, `WindowUnregistered` detach, post-Dispose `Set*` guards, `DrawString`/outline-pass alpha tests) | pending — needs the next connected round for visual confirmation (window fade on focus change, Settings slider live-apply) | | Goal-window #363/#367 interface-text seam | `09453eca` | 12,542 passed / 4 skipped / 0 failed | implementer-only, no subagent review this session (hard constraint) | pending — needs the next connected round to confirm the SpewBox now flashes for the reclassified refusals (see "Goal-window follow-up" below) | -| Consolidated review — retail `/help` Detail extraction, seam wiring test | 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 ` 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 ` 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) @@ -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 `/help index`/`/clist`/`/on`/`/off` now show "Unknown command" via the SpewBox. + +## User gate — round 4 (2026-08-10) + +The user tested the goal-window build live and reported six findings. A +parallel read-only research agent investigated items 1+2 (text-style +presentation) concurrently with 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. diff --git a/docs/research/2026-08-09-chat-retail-window-shell.md b/docs/research/2026-08-09-chat-retail-window-shell.md index 67e1388b..c006769d 100644 --- a/docs/research/2026-08-09-chat-retail-window-shell.md +++ b/docs/research/2026-08-09-chat-retail-window-shell.md @@ -184,19 +184,123 @@ exactly two element ids: `0x1000046f` (max/min, dispatching (`idMessage == 7`, checked against `this->m_pCCS` / a `0x1000000b` attribute read). There is no case, anywhere in that function or its base-class fallback (`ChatInterface::ListenToElementMessage`, called unconditionally at the -function's tail), for `0x10000522`–`0x10000525`. **Clicking a chat-window -indicator button does NOTHING in retail — reading (a), pure indicator, is -correct; reading (b) is refuted.** The `0x24` input-action-property theory -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 -no case for these four ids. acdream ports this exactly: the four indicator -buttons (`ChatWindowController._indicatorButtons`) carry no `OnClick` at all; -`ChatWindowController.SetIndicatorOpen` is their only writer, called only from -`RetailUiRuntime.OnWindowVisibilityChanged` in response to the floating -window's own visibility changing (keybind or otherwise) — a pure one-directional -mirror, matching retail exactly. +function's tail), for `0x10000522`–`0x10000525`. **This citation is still a +true statement about `gmMainChatUI::ListenToElementMessage` — see the round-4 +correction below for why it is the wrong function to have grepped.** + +--- + +**ROUND-4 CORRECTION (2026-08-10) — the user's own retail memory ("clicking +opens/closes the window") overruled the CONCLUSION above, and re-attacking the +question with that as the starting axiom (per CLAUDE.md: the user's retail +memory is the axiom, not a hypothesis to be argued down) found the exact +mechanism the CH6b pass missed.** + +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 diff --git a/src/AcDream.App/UI/Layout/ChatWindowController.cs b/src/AcDream.App/UI/Layout/ChatWindowController.cs index 58816641..6c66b7f2 100644 --- a/src/AcDream.App/UI/Layout/ChatWindowController.cs +++ b/src/AcDream.App/UI/Layout/ChatWindowController.cs @@ -59,13 +59,15 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta private const uint SendId = 0x10000019u; 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 - // up iff the corresponding floating chat window is visible. The mechanism is - // one-directional (window visibility drives the button, never the reverse — - // see SetIndicatorOpen's doc for the decomp confirmation). Campaign CH slice - // CH6b resolves these to _indicatorButtons and wires them via SetIndicatorOpen, - // called by RetailUiRuntime whenever a floating chat window's visibility changes. + // up iff the corresponding floating chat window is visible. Campaign CH slice + // CH6b resolves these to _indicatorButtons and wires the MIRROR half via + // SetIndicatorOpen, called by RetailUiRuntime whenever a floating chat window's + // visibility changes. Round 4 (2026-08-10) adds the CLICK half — see + // 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 Indicator2Id = 0x10000523u; private const uint Indicator3Id = 0x10000524u; @@ -265,17 +267,21 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta if (layout.FindElement(id) is { } twin) twin.Visible = false; - // ── Chat-window 1-4 indicator buttons — resolve now, wired later by - // RetailUiRuntime via SetIndicatorOpen as each floating window's own - // visibility changes (gmMainChatUI::RecvNotice_SetPanelVisibility - // @0x004CCD80 — see this class's doc + Indicator1Id..Indicator4Id). - // CH6a/b REJECT-review SHOULD-FIX 3: these carry DAT property 0x0B - // (ToggleBehavior) = true, but retail's own click dispatch - // (gmMainChatUI::ListenToElementMessage @0x004CDA80) has no case for - // any of their element ids — clicking one does NOTHING in retail. - // SuppressSelfToggle keeps SetIndicatorOpen the ONLY writer of their - // Selected mirror; without it, a click would flip the Highlight/Normal - // art with no underlying visibility change. ── + // ── Chat-window 1-4 indicator buttons — resolve now; the MIRROR half + // is wired later by RetailUiRuntime via SetIndicatorOpen as each + // floating window's own visibility changes + // (gmMainChatUI::RecvNotice_SetPanelVisibility @0x004CCD80 — see this + // class's doc + Indicator1Id..Indicator4Id); the CLICK half is wired + // by RetailUiRuntime calling BindIndicatorClicks after this method + // returns (round 4, 2026-08-10 — see SetIndicatorOpen's doc for the + // full reconciliation). SuppressSelfToggle stays true regardless: + // these carry DAT property 0x0B (ToggleBehavior) = true, but the + // button's own blind self-flip (UiButton.OnEvent's MouseUp case) + // 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 }; for (int i = 0; i < indicatorIds.Length; i++) { @@ -507,22 +513,81 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta /// state transition gmMainChatUI::RecvNotice_SetPanelVisibility /// @0x004CCD80 performs: State 6 (Highlight/"lit") when the /// floating window is visible, State 1 (Normal) when it is not. + /// This half of the mechanism is genuinely one-directional in the exact + /// sense that SetIndicatorOpen is the ONLY writer of + /// — see + /// for the click half, which triggers the real toggle THROUGH this same + /// writer rather than flipping the indicator itself. /// /// - /// One-directional by design — a decomp read of - /// gmMainChatUI::ListenToElementMessage @0x004CDA80 (one of - /// several functions in the 2013 binary that branch on - /// idMessage == 1, i.e. "clicked" — CH6a/b REJECT-review NIT 4 - /// corrected the earlier "the ONLY function" superlative, e.g. - /// gmFloatyChatUI::ListenToElementMessage @0x004CE330 also - /// branches on it for the floaty close button) shows THIS function - /// handles exactly two element ids: 0x1000046f (max/min) and the - /// talk-focus menu's selection message. There is no case for - /// 0x10000522-0x10000525 — clicking a chat-window - /// indicator button does NOTHING in retail. acdream ports this exactly: - /// these buttons have no OnClick (research doc §1.4 corrected — - /// its own hedge that "it is safe to wire both" is a weaker reading than - /// this direct decomp confirmation, and is now superseded by it). + /// Round 4 reconciliation (2026-08-10) — the user's retail memory + /// ("clicking opens the window") overruled CH6b's decomp-only reading + /// ("clicking does nothing"), and a deeper grep found the mechanism that + /// makes BOTH readings correct at their own layer. CH6b's citation of + /// gmMainChatUI::ListenToElementMessage @0x004CDA80 (no case for + /// 0x10000522-0x10000525) is STILL TRUE as a statement + /// about that one function — but it was the wrong place to look for a + /// button click. Retail buttons don't route clicks through their + /// PARENT WINDOW's message handler at all: every Type 1 + /// (UIElement_Button) button has its own generic click path, + /// UIElement_Button::HandleButtonClick @0x00471E50, which reads + /// an ENUM-kind DAT property 0x12 off ITSELF; when present, it + /// builds an InputEvent and routes it through + /// ICIDM/UIElementManager's action map, which (for a + /// visibility action) reaches UIElementManager::DoVisibilityToggleAction + /// @0x0045B660 — a lookup into + /// m_elementInputActionListenerTable (populated by + /// UIElementManager::RegisterElementForInputAction, itself called + /// from ONE place: UIElement::Initialize's generic property switch, + /// case for ENUM-kind property 0x24 — ANY element authoring that + /// property registers itself as a listener for that action id) — for + /// every registered listener, DoVisibilityToggleAction broadcasts + /// element message 0x31, which UIElement::ListenToElementMessage + /// (the ultimate base-class handler every window falls through to when + /// its own override, and ChatInterface's, don't claim the + /// message — confirmed for BOTH gmMainChatUI and + /// gmFloatyChatUI) handles GENERICALLY: it reads the RECEIVING + /// element's OWN enum property 0x58 and, if it equals 1 + /// ("toggle"), calls SetVisible(!currentlyVisible). + /// + /// + /// + /// 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 0x12 as an Enum (not merely + /// a stray/mistyped property): chat_2100006f.json's four + /// indicator elements each carry it, with values + /// 0x10000514-0x10000517 in id order. But the OTHER half + /// of the wiring is where retail's own data falls short: the floating + /// chat window fixture (chat_floaty_2100005b.json) authors NO + /// Enum-kind property 0x24 anywhere (its one hit on property + /// number 36 is Integer-kind, an unrelated attribute) and NO property + /// 0x58 at all — so nothing in the shipped LayoutDesc data ever + /// registers a floating chat window as a listener for those four action + /// ids, and RegisterElementForInputAction has exactly one call + /// site in the whole binary (the property-driven one above; no class + /// anywhere calls it directly in code). DoVisibilityToggleAction + /// would find zero listeners and silently no-op. (The four action-id + /// VALUES themselves are not chat-specific either — the SAME four + /// numbers are gmFriendsUI::PostInit'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. + /// + /// + /// + /// 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. + /// makes each indicator's click drive + /// the SAME ToggleFloatingChatWindow(windowId) chokepoint the + /// Alt+1..4 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 docs/research/2026-08-09-chat-retail-window-shell.md §1.4 + /// for the full writeup. /// /// public void SetIndicatorOpen(int windowId, bool open) @@ -533,6 +598,29 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta open ? UiButtonStateMachine.Highlight : UiButtonStateMachine.Normal); } + /// + /// Wire each indicator button's click to + /// (RetailUiRuntime's ToggleFloatingChatWindow, the SAME chokepoint the + /// Alt+1..4 keybinds use) — round 4 (2026-08-10), see + /// 's doc for the full reconciliation. Called by + /// RetailUiRuntime after returns, once the runtime's own + /// toggle method is available. stays true + /// on every indicator (set in ) so this click-triggered toggle's + /// outcome — not a blind self-flip — is what mirrors + /// back onto , keeping the visual state consistent + /// through the full click round trip even if the toggle is ever refused. + /// + public void BindIndicatorClicks(Func 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() => new( Maximized: _maximized, diff --git a/src/AcDream.App/UI/RetailUiRuntime.cs b/src/AcDream.App/UI/RetailUiRuntime.cs index c9894c98..cf148324 100644 --- a/src/AcDream.App/UI/RetailUiRuntime.cs +++ b/src/AcDream.App/UI/RetailUiRuntime.cs @@ -893,6 +893,13 @@ public sealed class RetailUiRuntime : IDisposable controller.AttachWindow(handle); Host.Root.DefaultTextInput = controller.Input; _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)."); } diff --git a/src/AcDream.App/UI/UiButton.cs b/src/AcDream.App/UI/UiButton.cs index 235c4ac7..159babdf 100644 --- a/src/AcDream.App/UI/UiButton.cs +++ b/src/AcDream.App/UI/UiButton.cs @@ -113,18 +113,28 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful /// /// Opt-out for a button whose - /// state is a PURE MIRROR of external state (a producer other than the click - /// itself is the only legitimate writer — e.g. - /// mirroring a floating chat window's own visibility). CH6a/b REJECT-review - /// SHOULD-FIX 3: the chat-window 1-4 indicators (0x10000522-0x10000525) - /// carry DAT property 0x0B () = true, so - /// without this flag a click flips their Highlight/Normal art with no - /// underlying visibility change — the mirror lies until the next real - /// toggle. Retail confirms clicking these buttons does nothing - /// (gmMainChatUI::ListenToElementMessage @0x004CDA80 has no case for - /// their element ids — see 's - /// doc). Default — every OTHER toggle button (max/min, - /// checkboxes) keeps retail's normal click-toggles-itself behavior. + /// state is a PURE MIRROR of external state (a producer other than the button's + /// own blind self-flip is the sole legitimate writer — e.g. + /// mirroring a floating chat + /// window's own visibility). CH6a/b REJECT-review SHOULD-FIX 3: the chat-window + /// 1-4 indicators (0x10000522-0x10000525) carry DAT property + /// 0x0B () = true, so without this flag a + /// click flips their Highlight/Normal art with no underlying visibility change — + /// the mirror lies until the next real toggle. + /// + /// + /// Round 4 (2026-08-10): this stays set for the chat-window indicators even + /// though clicking them now DOES toggle their floating window (via + /// — see + /// 's doc for the full retail + /// mechanism this reconciles). The click drives the real toggle, and + /// SetIndicatorOpen — the SAME single writer as before — reports the + /// outcome back onto ; 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 + /// . + /// /// public bool SuppressSelfToggle { get; set; } diff --git a/src/AcDream.UI.Abstractions/Panels/Chat/RetailCommandHelpTable.cs b/src/AcDream.UI.Abstractions/Panels/Chat/RetailCommandHelpTable.cs index 00f1bf10..9c27ab27 100644 --- a/src/AcDream.UI.Abstractions/Panels/Chat/RetailCommandHelpTable.cs +++ b/src/AcDream.UI.Abstractions/Panels/Chat/RetailCommandHelpTable.cs @@ -1,4 +1,5 @@ using System.Collections.Frozen; +using AcDream.Core.Chat; namespace AcDream.UI.Abstractions.Panels.Chat; @@ -53,20 +54,125 @@ namespace AcDream.UI.Abstractions.Panels.Chat; /// caught that /help death printed an acdream META-MESSAGE ("This is /// a retail help-topic group; acdream has not yet extracted its exact /// retail listing text…") instead of retail's real listing — this was the -/// class-doc overclaim's SHARP end. All 7 HelpXxxGroup nodes are now -/// resolved: , , -/// , and -/// are COMPLETE verbatim listings; , -/// , and -/// are PARTIAL (their own summary line, plus — for chatting — 7 of 8 -/// entries) with an explicit UNVERIFIED note citing -/// ClientCommunicationSystem::HelpStupidChannelHack @0x0056f290 for -/// the un-decodable remainder, never a guess. 7 of the ~35 channel -/// one-liners (a/guild/gu, general/cg, trade/ct, lfg/clfg, roleplay/crp, -/// society/soc, olthoi/o) are now verbatim too; the rest (fellowship/ -/// monarch/patron/vassals/covassal family) route through the same -/// unresolved mechanism and remain acdream summaries. See the remarks on -/// for the full extraction method. +/// class-doc overclaim's SHARP end. All 7 HelpXxxGroup nodes were +/// resolved that round: , +/// , , and +/// were COMPLETE verbatim listings; +/// , , and +/// were left PARTIAL with an UNVERIFIED +/// note. 7 of the ~35 channel one-liners (a/guild/gu, general/cg, trade/ct, +/// lfg/clfg, roleplay/crp, society/soc, olthoi/o) were verbatim too; the +/// rest (fellowship/monarch/patron/vassals/covassal family, as standalone +/// /help f-style lookups) still route through the unresolved +/// mechanism below and remain acdream summaries — that part of the gap is +/// unchanged this round; see ByVerb and +/// RetailCommandHelpTableTests.ChannelVerb_StillAcdreamSummary_UnresolvedChannelHackFamily +/// 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.) +/// +/// +/// +/// 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. The +/// blocker was never really HelpStupidChannelHack @0x0056f290's own +/// code — the fragments Binary Ninja renders as vtable-slot dereferences +/// (&ClientCommunicationSystem::`vftable'.RecvNotice_StartBarberNotice +/// etc.) are the SAME pooled/mislabeled-data artifact this campaign has hit +/// before (AP-186's own precedent): they are DATA pointers into +/// .rdata, not real vtable dispatch, and dereferencing their actual +/// operand addresses (found by reading HelpStupidChannelHack's own +/// disassembly for the push imm32 immediately preceding each +/// constructor call, since BN's line-grouped rendering hides the true +/// instruction order) resolves cleanly: the function builds +/// "@" + tag + " - Sends a broadcast to your " + ChannelName + ".\n" +/// where tag is a single character sliced out of a shared wide +/// literal U"fvpca" (reading a WIDE string through a NARROW +/// char* naturally truncates to one character 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 swept directly, not guessed: +/// data_0x7d0478="Allegiance", plus "Co-vassals"/"Monarch"/"Patron"/ +/// "Vassals"/"Fellowship" already visible in that function's straight-line +/// decompiled body). See through +/// for the six resolved lines and +/// their per-line address citations. +/// +/// +/// With that decoded, (which is ENTIRELY +/// six HelpStupidChannelHack calls, confirmed by reading +/// HelpChannelsGroup's Detail branch directly) is now a COMPLETE +/// verbatim listing. needed one more +/// piece: its "@reply" entry does not call a per-tag summary — it calls +/// ClientCommunicationSystem::HelpReply @0x00577A50 with +/// Summary_HelpType, 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 +/// // +/// . +/// (retail's HelpAllGroup, 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 +/// 0x0057E7F0-0x0057EBA0) — 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 +/// (@say) whose own retail literal has NO trailing newline, unlike +/// every sibling line — also ported as-is; retail's own output runs +/// @say straight into @tell with no line break. Zero +/// remaining UNVERIFIED notes in any of the three former PARTIAL groups. +/// Closes ISSUES.md #364. +/// +/// +/// +/// The meta-marker sweep. Every honesty marker that had leaked into +/// USER-VISIBLE text — the "[IMPLEMENTED]"/"[IMPLEMENTED, also @x]" tags +/// littered through /, +/// the "NOT YET IMPLEMENTED in acdream" sentences appended to +/// ///, +/// 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. char const data_7dfb58[0x879] = +/// "@allegiance boot …) — 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). and 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 (data_7df7f8 +/// and data_7da300 respectively); 's previous +/// text was entirely acdream-authored ("Forwards to the client's +/// render-option surface…") and is replaced with the REAL retail usage +/// string (render_option_usage @ 0x0079B878, found by +/// grepping the pseudo-C for a literal @render string instead of +/// trusting the earlier "not decodable" note). +/// +/// +/// +/// messagetypes is now a real construction, not an acdream +/// summary. Retail's HelpMessageTypes @0x0056E5D0 really does +/// build its text at runtime — but by walking a FIXED whitelist +/// (LogTextTypeEnumMapper::IsLegalChannel @0x006AFF40, 14 ids) and a +/// FIXED per-id name table (LogTextTypeEnumMapper::LogTextTypeToString +/// @0x006AFF90, a literal switch), joined with ", " +/// (data_0x794334) and wrapped in "Squelch channels are as +/// follows:\n %s\n" (gmCCommunicationSystem::GetListofSquelchChannels +/// @0x00589EB0 / +/// gmCCommunicationSystem::ConvertSQToPString @0x00589A80's +/// arg3!=0 "enumerate every legal channel" branch — the +/// SquelchInfo 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 +/// PORTS the construction (the ordered id/name table + +/// '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. /// /// /// @@ -188,20 +294,65 @@ public static class RetailCommandHelpTable public const string PatronReply = "@pr - Sends the text to the last vassal who used @p to send you a message."; - // acclient_2013_pseudo_c.txt:1031093 (data_7de280). - public const string Day = - "@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."; + // acclient_2013_pseudo_c.txt:1031093 (char const data_7de280[0x77] = + // "@day - A…\n", 0) -- BN already fully decodes this consolidated data + // 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 - …\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 = - "@log - 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 - 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