fix(chat): Campaign CH user-gate round 1 — jump-in-air edge, portal cue cadence, wrap/prefix/color fixes

The user tested Campaign CH's CODE-COMPLETE build live and reported ten
defects (docs/plans/2026-08-09-chat-parity-campaign.md, "User gate —
round 1"). Items A-G are fixed here; the remaining three (extra chat
windows on 1/2/3/4, resize working in only one corner, transparency/
artifacts) are out of scope for a fix and filed as slice CH6.

A. Jump-in-air refusal never fired live: the jump block only ever
   evaluated input.Jump inside the grounded-charge or already-charging
   branches. PlayerMovementController now detects the press RISING EDGE
   while airborne and reports WeenieError.NotGrounded once per press,
   leaving the grounded charge/fire path untouched.
B. ChatVM's invented "[System] " prefix is dropped — retail prints
   system text bare. [Popup] is unchanged (AP-175).
C. SpewBoxController's color is now the user-pinned exact value
   (1, 1, 0.247, 1), the same bright yellow as an incoming Tell.
   Register row AP-178 updated: color CLOSES, size/position/font stay
   open per the user's live report that they still differ.
D. Closes #329: PortalTunnelPresentation now emits the portal wait cue
   unconditionally on every rotation-segment boundary, matching
   gmSmartBoxUI::UseTime's decompiled else-arm exactly instead of gating
   on a 5-second hold local transits never reached. PortalWaitNotice
   Controller now renders it in the same pinned yellow as item C.
   Register row AP-150 retired.
E. Closes #362: new ClientCommandResponses.cs parses and renders the
   four previously-unhandled inbound GameEvents (ChannelIndex,
   ChannelList, AvailableHouses, AllegianceInfoResponse), each ported
   line-for-line from the named-retail decomp's inbound handlers.
   Register row TS-70 retired.
F. ChatWindowController.WrapText now splits on embedded '\n'/'\r\n'
   first, then word-wraps each segment independently — server text like
   /help's reply no longer collapses onto one line.
G. The chat input field's right edge no longer holds a fixed absolute
   pixel position across a window resize; Bind now upgrades it to
   retail edge-mode 1 (UiLayoutPolicy) or the AnchorEdges.Right stretch
   fallback so it tracks the window's client width instead of
   overflowing past a narrower resize.

Full Release suite: 12,247 passed / 4 skipped / 0 failed (baseline
12,221/4/0 + 26 new tests across items A, E, F, G).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-09 23:42:32 +02:00
parent d1c1368a5e
commit 47e40900f3
17 changed files with 1428 additions and 81 deletions

View file

@ -94,21 +94,6 @@ Register row: TS-69.
**Campaign:** `docs/plans/2026-08-09-chat-parity-campaign.md` (Campaign CH,
slice CH4).
## #362 — Four new CH4 outbound requests have no inbound response handler
**Status:** OPEN — filed 2026-08-09, Campaign CH slice CH4. `@index`,
`@clist`, `@hslist`, and `@allegiance info` send byte-correct retail
GameAction requests (`ClientCommandRequests.BuildIndexChannels`/
`BuildListChannel`/`BuildListAvailableHouses`/`BuildAllegianceInfoRequest`),
but their GameEvent responses (`ChannelIndex 0x0149`, `ChannelList
0x0148`, `AvailableHouses 0x0271`, `AllegianceInfoResponse 0x027C`) are
registered in `GameEventType` with no `GameEventWiring` handler — ACE's
reply is silently dropped. The request itself is correct and verifiable
on the wire; only the response rendering is missing. Register row: TS-70.
**Campaign:** `docs/plans/2026-08-09-chat-parity-campaign.md` (Campaign CH,
slice CH4).
## #363 — Chat refusal/usage call sites are typed ClientLocal 0x00 where retail types several 0x1A
**Status:** OPEN — filed 2026-08-09, CH4 REJECT-review, SHOULD-FIX 9.
@ -1949,39 +1934,6 @@ overlaps the Slice-J ownership work. If it is ever *accepted* rather than
fixed, it needs a divergence-register row; it is filed here as a defect
because the intent is to fix it.
## #329 — The portal wait cue arms five seconds late; retail emits it per tunnel rotation segment, unconditionally
**Status:** OPEN
**Severity:** LOW (cosmetic, but it is a retail divergence on every single
portal, in both directions)
**Filed:** 2026-08-06, #280 retail-conformance review, finding F2.
**Register row:** AP-150.
acdream suppresses `"In Portal Space - Please Wait..."` until the hold has run
five seconds (`RuntimeWorldTransitState.RetailWaitCueDelay`), then re-emits it
per rotation segment only while `_waitCueVisible`
(`PortalTunnelPresentation.TickRotation`).
Retail has no threshold. `gmSmartBoxUI::UseTime` @0x004D6E30 emits
`ECM_UI::SendNotice_DisplayStringInfo(0x1a, …)` in the `else` arm of the
rotation-segment-expiry test at 0x004D6FCD — i.e. every time a segment expires,
whether or not `CellManager::blocking_for_cells` is set. Segment duration is
`RandDouble(0.6, 1.8)` s, byte-decoded at 0x004D6FE6. acdream's own
`RotationDurationMin`/`Max` already match retail exactly, so only the arming is
wrong.
The unrelated 5.0 s constant at VA 0x007991B0 is
`CellManager::CheckPrefetchStatus`'s retry cadence, and was mis-attributed to
the cue by #280's commit message (retracted in the contract doc).
**Consequence:** every portal shorter than 5 s shows a silent tunnel where
retail shows the notice; every portal longer than 5 s shows it 3.2-4.4 s late.
**Fix shape:** delete the delay and emit on the same segment boundary the
rotation already computes. One-line arming change, but it is a user-visible
presentation change and wants the user's eyes before it lands — do not fold it
into an unrelated commit.
## #325 — Gate A's teleport test is narrower than retail's: a ForcePosition carrying a NEWER teleport stamp is misrouted into a full Apply
**Status:** OPEN
@ -14162,6 +14114,84 @@ outdoors at the angle that previously erased it.
# Recently closed
## #362 — [DONE 2026-08-09] Four new CH4 outbound requests have no inbound response handler
**Closed:** 2026-08-09, Campaign CH user-gate round 1, item E.
**Filed:** 2026-08-09, Campaign CH slice CH4.
**Register row:** TS-70, RETIRED in the same commit.
**Resolution:** `@index`, `@clist`, `@hslist`, and `@allegiance info` sent
byte-correct retail GameAction requests
(`ClientCommandRequests.BuildIndexChannels`/`BuildListChannel`/
`BuildListAvailableHouses`/`BuildAllegianceInfoRequest`), but their
GameEvent responses (`ChannelIndex 0x0149`, `ChannelList 0x0148`,
`AvailableHouses 0x0271`, `AllegianceInfoResponse 0x027C`) had no
`GameEventWiring` handler — ACE's reply was silently dropped. New
`ClientCommandResponses.cs` (`src/AcDream.Core.Net/Messages/`) parses all
four wire shapes (cross-checked against ACE's
`GameEventChannelIndex`/`GameEventChannelList`/`GameEventHouseAvailableHouses`/
`GameEventAllegianceInfoResponse` writers) and renders retail-shaped
`LogTextType 0x00` (Default) lines ported verbatim from the named-retail
decomp:
- ChannelIndex/ChannelList: `Handle_Communication__ChannelIndex`/`ChannelList`
@0x0057d0c0/@0x0057d230 — a header line then one line per name.
- AvailableHouses: `Handle_House__Recv_AvailableHouses` +
`DisplayListOfCoords` @0x00585d50/@0x00585c20 — the "There are N <type>
available." summary, then one indented `RadarCoordinates`-formatted
location line per landblock (skipped for apartments, which have no world
location, matching retail's `arg2 != 4` gate), then the >400-locations
truncation notice when `TotalAvailable > 0x190`.
- AllegianceInfoResponse: `Handle_Allegiance__AllegianceInfoResponseEvent`
@0x0056a1d0 — the asterisk-legend note, "Allegiance information for
&lt;name&gt;&lt;* if online&gt;:", an optional "Patron:" line, and an
optional "Vassals:" block, all reconstructed from the wire's flat
parent-tagged record list via ports of retail's own
`GetData`/`GetPatron`/`GetFirstVassal`/`GetNextVassal` walk. A player with
no allegiance record produces NO lines, matching retail's own early
return — this is not a residual bug.
17 new parser/format/routing tests
(`tests/AcDream.Core.Net.Tests/Messages/ClientCommandResponsesTests.cs`)
cover round-trips for all four shapes (including the empty-allegiance and
apartment-skip edge cases) and a `GameEventDispatcher`-level routing test
proving each reaches the `ChatLog` transcript with
`RetailLogTextType.Default`.
## #329 — [DONE 2026-08-09] The portal wait cue arms five seconds late; retail emits it per tunnel rotation segment, unconditionally
**Closed:** 2026-08-09, Campaign CH user-gate round 1, item D.
**Severity:** LOW (cosmetic, but it is a retail divergence on every single
portal, in both directions)
**Filed:** 2026-08-06, #280 retail-conformance review, finding F2.
**Register row:** AP-150, RETIRED in the same commit.
**Resolution:** `PortalTunnelPresentation.TickRotation` now writes
`"In Portal Space - Please Wait..."` directly and unconditionally in the
rotation-segment-expiry branch, on every segment boundary, matching
`gmSmartBoxUI::UseTime`'s `else` arm at 0x004D6FCD verbatim — confirmed
against `docs/research/named-retail/acclient_2013_pseudo_c.txt:219499-219525`
before coding, which shows no hold/threshold test anywhere in that branch.
acdream's own `RotationDurationMin`/`Max` (0.6-1.8 s) already matched
retail's `RandDouble` window byte-for-byte; only the arming — gating the
write on `_waitCueVisible`, which only ever went true after the invented
five-second `RuntimeWorldTransitState.RetailWaitCueDelay` hold — was wrong.
That hold/`ObserveWait`/`SetWaitCue` plumbing remains as
`LocalPlayerTeleportController`'s own telemetry
(`RuntimePortalSnapshot.WaitCueShown`) but no longer gates the on-screen
cue; a dedicated `ClearWaitCueNotice()` now hides the notice unconditionally
on Enter/Exit/Dispose so a line written by the per-segment path can never
survive past the presentation going invisible. The notice text also now
renders in the same bright yellow as an incoming Tell
(`(1, 1, 0.247, 1)`, `PortalWaitNoticeController`), per the user's live
side-by-side observation (round 1 also pinned the SpewBox to the same
colour, AP-178).
**Consequence (fixed):** every portal, regardless of duration, now shows the
notice from the first rotation segment (which expires immediately on entry
since `_rotationDuration` starts at 0), refreshed every 0.6-1.8 s for as
long as the tunnel presentation is visible — matching retail instead of
silently skipping short transits and running 3.2-4.4 s late on long ones.
## #234 — [DONE 2026-07-23] Cancelled close-range Use could strand the busy cursor
**Closed:** 2026-07-23

File diff suppressed because one or more lines are too long

View file

@ -16,9 +16,13 @@ see the CH4 closeout below; 138 of 152 registry verbs now execute
locally, 5 are deliberately deferred (issues #360/#361/#362, register
rows TS-68/TS-69/TS-70), and 9 are retail's own null-handler help-only
nodes. CH5 (this closeout sweep) is COMPLETE — plan/register/ISSUES/
CLAUDE.md/roadmap ledger flip plus the chat memory digest. Pending the
in-client user gate (colors, side channels, on-screen text, command
spot-checks per the test script).
CLAUDE.md/roadmap ledger flip plus the chat memory digest. **User gate
round 1 ran 2026-08-09 and found ten live defects; see "User gate —
round 1" below.** Items AG are fixed in this round's commit; the three
remaining findings (extra chat windows on 1/2/3/4, resize only working in
one corner, transparency/artifacts) are out of this round's scope and
filed as a new slice, CH6. Status stays CODE-COMPLETE pending the next
user gate round.
**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.
@ -102,6 +106,10 @@ implementer per slice against a pinned contract (per
commands implemented family-by-family.
- **CH5 — closeout.** Register sweep, ledger flip, ISSUES updates,
in-client test script for the user gate.
- **CH6 — chat-window shell parity (filed 2026-08-09 at user gate round
1).** Retail multi-window chat, per-window type filters + PostInit
defaults (see color research §4), all-corner resize, opacity; research
first.
## Gates
@ -121,6 +129,8 @@ implementer per slice against a pinned contract (per
| CH3 side channels | `614a1e05` | 11,964 passed / 4 skipped / 0 failed | APPROVE-WITH-FIXES; fixed `e07fba57` | pending (connected gate — see handoff below) |
| CH4 commands | `090825e7` | 12,221 passed / 4 skipped / 0 failed | REJECT; fixed `724ef2d3`; re-review APPROVE-WITH-FIXES; closed `5d247d55` | pending |
| CH5 closeout | (this commit) | — (docs/memory only, no build) | — | pending (connected gate — see test script) |
| User gate round 1 | (this commit) | 12,221 passed / 4 skipped / 0 failed (baseline; items AG fixed this commit) | — | items AG user-gate round 1 fixed; ten findings total, see "User gate — round 1" below |
| CH6 chat-window shell parity | not started | — | — | filed 2026-08-09 at user gate round 1; research first |
### CH4 closeout (2026-08-09)
@ -443,3 +453,35 @@ send+receive; Roleplay is now silent-but-correctly-refused until the user
turns it on via Settings (then works); `/a` with and without an allegiance;
`/ab`; the legacy family no longer double-prints; the TurbineChat ack
HResult line never appears on an ordinary successful send.
## User gate — round 1 (2026-08-09)
The user tested CH5's CODE-COMPLETE build live and reported ten defects.
Items AG are fixed in this same commit; the last three are out of this
round's scope and filed as slice CH6.
| # | User finding (condensed) | Disposition |
|---|---|---|
| A | Jumping while already airborne never shows retail's "You can't jump while in the air" refusal — the jump block only ever evaluates `input.Jump` inside the grounded-charge or already-charging branches. | **FIXED this SHA.** Rising-edge detection (`PlayerMovementController._prevJumpHeld`) reports `WeenieError.NotGrounded` once per press while airborne; holding the key or the grounded charge/fire path is unaffected. |
| B | Local system text shows an invented `"[System] "` prefix; retail prints it bare. | **FIXED this SHA.** `ChatVM.FormatEntry`'s `ChatKind.System` case now returns `entry.Text` unprefixed. `[Popup]` is unchanged (AP-175, a deliberate divergent marker). |
| C | The SpewBox's color doesn't match retail — the user recalls it as the same bright yellow as an incoming Tell. | **FIXED this SHA (color only).** `SpewBoxController.SpewBoxColor` is now the exact pinned value `(1, 1, 0.247, 1)` (`0x81C4C8`, same as `RetailChatColorTable.Yellow`). Size/position/font remain OPEN under register row AP-178 — the user reports all three still differ from retail; user gate round 1: differs, iterating. |
| D | The portal-space "In Portal Space - Please Wait..." text never shows, and when it does (via #329's 5-second delay) it's the wrong (white) color. | **FIXED this SHA, closes #329.** `PortalTunnelPresentation.TickRotation` now emits the notice unconditionally on every rotation-segment boundary, matching `gmSmartBoxUI::UseTime`'s decompiled `else`-arm exactly (no hold/threshold gate); `PortalWaitNoticeController` now renders it in the same pinned yellow as item C. Register row AP-150 retired. |
| E | `/hslist villas` (and the other three CH4-added request commands) is accepted server-side but produces no visible response. | **FIXED this SHA, closes #362.** New `ClientCommandResponses.cs` parses and renders `ChannelIndex`/`ChannelList`/`AvailableHouses`/`AllegianceInfoResponse`, each ported line-for-line from the named-retail decomp's inbound handlers. Register row TS-70 retired. |
| F | Multi-line server text (e.g. `/help`'s reply) doesn't split on embedded `\n` — "probably broken in many places." | **FIXED this SHA.** `ChatWindowController.WrapText` now splits on `\n`/`\r\n` first, then word-wraps each segment independently; the confirmed-correct single-line early-out is unchanged for text with no embedded newline. |
| G | The chat input line overflows the window's right edge when the window is resized. | **FIXED this SHA.** The input field's right edge no longer holds a fixed absolute pixel position across a resize (retail edge-mode 0's "frozen at current" fallback, or the `AnchorEdges` default with no `Right` bit) — `ChatWindowController.Bind` now upgrades it to retail edge-mode 1 (`UiLayoutPolicy`) or the equivalent `AnchorEdges.Right` stretch, so the right edge tracks every resize instead of only the bind-time/channel-change recompute. |
| H | Extra/duplicate chat windows appear on number keys 1/2/3/4. | **NEW SLICE CH6** — see the slice list above. Retail has real multi-window chat with per-window type filters; this needs research before a fix, not a quick patch. |
| I | Resizing the chat window only works from one corner, not every corner. | **NEW SLICE CH6** — bundled with H and J as "all-corner resize." |
| J | The chat window has transparency issues / visual artifacts, and the user wants a transparency setting eventually. | **NEW SLICE CH6** — bundled as "opacity"; the future user-facing transparency setting is noted in the slice's own scope, not promised for CH6's first cut. |
Findings AG's evidence: this commit's diff + the new/updated tests in
`tests/AcDream.Runtime.Tests/Gameplay/PlayerMovementControllerTests.cs`,
`tests/AcDream.UI.Abstractions.Tests/ChatVMTests.cs`,
`tests/AcDream.App.Tests/UI/SpewBoxControllerTests.cs` (unchanged; verified
by inspection — no test pinned the old color),
`tests/AcDream.App.Tests/UI/PortalWaitNoticeControllerTests.cs`,
`tests/AcDream.Core.Net.Tests/Messages/ClientCommandResponsesTests.cs`,
and `tests/AcDream.App.Tests/UI/Layout/ChatWindowControllerTests.cs`. Full
Release suite green (see the commit message for the exact count). Items
H/I/J need the next visual round once CH6 lands; AG still want a final
in-client eyes-on pass to confirm the fix reads correctly on screen (build
+ test green is necessary, not sufficient, for a presentation change).