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

@ -455,6 +455,13 @@ public sealed class PlayerMovementController
private bool _jumpCharging;
private float _jumpExtent;
// Campaign CH user-gate round 1 (item A, #329 sibling finding): previous
// frame's raw Jump input, so an airborne jump press can be reported on
// its RISING edge only — retail's jump_is_allowed (called from
// ClientCombatSystem::DoJump @0x0056B110) refuses once per press, not
// once per frame the key is held.
private bool _prevJumpHeld;
/// <summary>
/// Current retail jump-powerbar state. Power is always zero when no jump is
/// pending and otherwise lies in [0,1].
@ -2574,6 +2581,21 @@ public sealed class PlayerMovementController
_jumpCharging = false;
_jumpExtent = 0f;
}
else if (input.Jump && !_prevJumpHeld && !_body.OnWalkable)
{
// Campaign CH user-gate round 1, item A: the whole jump block
// above only ever evaluates `input.Jump` inside
// `input.Jump && _body.OnWalkable` (charge) or `_jumpCharging`
// (fire/refuse) — pressing jump while airborne and NOT already
// charging never reached either branch, so retail's 0x24 "You
// can't jump while in the air" (jump_is_allowed via
// ClientCombatSystem::DoJump @0x0056B110) could never fire live.
// Report it exactly like the grounded refusals above, gated to
// the press EDGE only (see _prevJumpHeld) so holding space
// in-air raises exactly one report, not one per frame.
ReportJumpRefusal(WeenieError.NotGrounded);
}
_prevJumpHeld = input.Jump;
// ── 2. Run admitted complete-object quanta ────────────────────────────
// CPhysicsObj::update_object (0x00515D10) retains a remainder at or