feat(chat): Campaign CH slice CH6c — window opacity + transparency setting
Retail's ChatInterface::SetOpacity (0x004F3120) fades the WHOLE composited
window surface with one alpha; UiRenderContext.ApplyAlpha already gated
DrawSprite/DrawRect/DrawFill (since 1da697ec, pre-CH6) but DrawStringDat and
DrawString still passed applyAlpha:false, so text stayed sharp over a
translucent window. Both now route through the same chokepoint.
RetailWindowOpacityController (new) subscribes to a new
RetailWindowManager.WindowRegistered event and drives every registered
window's live Opacity from keyboard-focus state, applied to EVERY window
(chat, floaties, vitals, toolbar, ...) rather than retail's ChatInterface-only
scope — register row AP-190, retiring the stale AP-40 "fixed 0.75, no focus
transition" row in the same commit.
Verified retail's shipped opacity defaults from the decomp (constructor
literals, no cdb needed): the base ChatInterface ctor sets
DefaultOpacity=0.5/ActiveOpacity=1.0, kept unmodified by the four floating
windows; gmMainChatUI's own ctor overrides the main window to 1.0/1.0
(always fully opaque). acdream ships one shared global default (0.5/1.0)
rather than replicating the per-class override — also AP-190. The linking
invariant (raising default above active drags active UP; lowering active
below default drags default DOWN — never a clamp) is ported verbatim as
ChatOpacityLink in AcDream.UI.Abstractions, shared by the live controller
and the new Settings -> Chat tab's two linked opacity sliders.
Persistence: ChatSettings.DefaultOpacity/ActiveOpacity round-trip through
SettingsStore; Save pushes both through IRuntimeSettingsTargets.SetChatOpacity
into the live controller, no restart required.
Rider (CH6a/b re-review): strengthened the grip-media regression guard past
a bare SpriteFile != 0 check — ChatLayoutConformanceTests now drives each
live grip through a real UiRenderContext/TextRenderer (backed by the
in-memory RecordingGpuDevice test double) and asserts the draw call chain
actually queued sprite geometry, via a new TextRenderer.DebugSpriteSegments
test-only accessor.
Full Release suite 12,459 passed / 4 skipped / 0 failed (baseline
12,420/4/0). No subagents, no client launches (session hard constraints);
pending the next connected user gate for visual confirmation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
ccab53d9a1
commit
a819687cf0
23 changed files with 1174 additions and 38 deletions
File diff suppressed because one or more lines are too long
|
|
@ -45,11 +45,14 @@ font instead of the unwired 15px debug fallback; bare `/help` and
|
|||
`DoHelp` shape — two scroll entries in the right order, not one
|
||||
acdream-invented blob. **CH6b (floating chat windows 1-4) landed
|
||||
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) remains not started. 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, round 3's
|
||||
fixes, and a final in-client visual pass on everything fixed so far).
|
||||
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).
|
||||
|
||||
**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.
|
||||
|
|
@ -133,8 +136,9 @@ 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; research complete:
|
||||
- **CH6 — chat-window shell parity — all three sub-slices CODE-COMPLETE
|
||||
2026-08-10, pending the connected user gate (filed 2026-08-09 at user
|
||||
gate round 1; research complete:
|
||||
`docs/research/2026-08-09-chat-retail-window-shell.md`).** Three
|
||||
sub-slices:
|
||||
- **CH6a — correct main-window import + resize.** Swap the wrong
|
||||
|
|
@ -222,6 +226,40 @@ implementer per slice against a pinned contract (per
|
|||
the retail per-window option array `0x1000008C` is stored by ACE as
|
||||
an opaque byte[] it never parses, so the wire format is its own
|
||||
deferred slice.
|
||||
**CODE-COMPLETE 2026-08-10 (hard constraint: no subagents, no client
|
||||
launches).** The sprite/rect chokepoint (`UiRenderContext.ApplyAlpha`) had
|
||||
quietly existed since `1da697ec`, well before CH6 — the gap was narrower
|
||||
than the plan assumed: `DrawStringDat`/`DrawString` still passed
|
||||
`applyAlpha: false`, so text stayed sharp over a translucent window;
|
||||
CH6c routes both through `ApplyAlpha` too, matching retail's
|
||||
whole-surface `SetOpacity` fade. `RetailWindowOpacityController`
|
||||
(new, `src/AcDream.App/UI/RetailWindowOpacityController.cs`) subscribes to
|
||||
a new `RetailWindowManager.WindowRegistered` event and drives every
|
||||
registered window's live `Opacity` from keyboard-focus state — deliberately
|
||||
EVERY window (chat, floaties, vitals, toolbar, ...), not just retail's
|
||||
`ChatInterface`-scoped mechanism (register row AP-190, retiring the stale
|
||||
AP-40 "fixed 0.75, no focus transition" row in the same commit). Verified
|
||||
retail defaults from the decomp (constructor-literal, no cdb needed):
|
||||
the base `ChatInterface` ctor sets DefaultOpacity=0.5/ActiveOpacity=1.0,
|
||||
which the four floating windows keep unmodified, but `gmMainChatUI`'s own
|
||||
ctor overrides the main window to 1.0/1.0 (always fully opaque); acdream
|
||||
ships ONE shared global default (the base 0.5/1.0) rather than
|
||||
replicating the per-class override — also AP-190. The linking invariant
|
||||
(raising default above active drags active UP; lowering active below
|
||||
default drags default DOWN — never a clamp) is decomp-verified and ported
|
||||
as `ChatOpacityLink` in `AcDream.UI.Abstractions`, shared by the live
|
||||
controller and the Settings → Chat tab's two new linked sliders
|
||||
(`SettingsPanel.RenderChatTab`). Persistence: `ChatSettings.DefaultOpacity`/
|
||||
`ActiveOpacity` round-trip through `SettingsStore`; Save pushes both
|
||||
through `IRuntimeSettingsTargets.SetChatOpacity` into the live controller —
|
||||
no restart. Rider (CH6a/b re-review): strengthened the grip-media
|
||||
regression guard past a bare `SpriteFile != 0` check —
|
||||
`ChatLayoutConformanceTests` now drives each live grip through a real
|
||||
`UiRenderContext`/`TextRenderer` (backed by the in-memory
|
||||
`RecordingGpuDevice` test double) and asserts the draw call chain actually
|
||||
queued sprite geometry, via a new `TextRenderer.DebugSpriteSegments`
|
||||
test-only accessor. Full Release suite 12,459 passed / 4 skipped / 0
|
||||
failed (baseline 12,420/4/0).
|
||||
|
||||
## Gates
|
||||
|
||||
|
|
@ -249,7 +287,7 @@ implementer per slice against a pinned contract (per
|
|||
| Jump-in-air root cause (round-2 item 1, resolved) | `a5a7eb4f` | Runtime tests 1,323/0 | — | round-3 probe evidence pinpointed a missing `OnInterfaceText` wire on the production controller-commit path (`RuntimeLocalPlayerMovementState.CommitRuntimeOwnedController`); FIXED, regression test added |
|
||||
| User gate round 3 | `98de4f5a` | Debug (all projects): 12,329 passed / 4 skipped / 1 failed (pre-existing #351 Debug-only flake — reproduces identically on the pristine pre-round-3 commit, not a regression); Release (every project reachable while a live `AcDream.App.exe` client — PID 15064, must not be killed per project policy — holds its own Release binaries locked, blocking `AcDream.App`/`AcDream.App.Tests`/`AcDream.Core.Tests` specifically): `AcDream.UI.Abstractions.Tests` (the layer this round's `/help` fix lives in) 867/867, plus `Core.Net.Tests` 823/823, `Runtime.Tests` 1,323/1,323, `Content.Tests` 130/130, `Headless.Tests` 89/89, `Bake.Tests` 15/15, `Cli.Tests` 4/4 — all 0 failed | — | findings (a)-(c) fixed this commit — SpewBox flush-top + retail dat font, `/help`/`/help death` exact retail print sequence (see "User gate — round 3" below) |
|
||||
| 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 | not started | — | — | not started |
|
||||
| CH6c opacity | `PENDING-SHA` | 12,459 passed / 4 skipped / 0 failed | pending (no subagent review pass this session — implementer-only) | pending — needs the next connected round for visual confirmation (window fade on focus change, Settings slider live-apply) |
|
||||
|
||||
### CH4 closeout (2026-08-09)
|
||||
|
||||
|
|
@ -590,7 +628,7 @@ round's scope and filed as slice CH6.
|
|||
| 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. | **STILL CH6b** (not this commit) — retail's real floating windows 1–4 (`0x2100005B` ×4) and their `ToggleFloatingChatWindow1..4` keybinds are a separate slice; CH6a only fixed the shell (import/resize) of the main window. |
|
||||
| I | Resizing the chat window only works from one corner, not every corner. | **FIXED at CH6a `1fd51543`.** Root cause: the main window imported the WRONG LayoutDesc (`0x21000006`, an unrelated layout whose root/resize-bar appear nowhere in the EoR gameplay UI) instead of retail's real `0x2100006F`; every symptom (crop hacks, the dropped resize bar, the one-corner-only resize) was downstream of that. The swap + a new `LayoutImporter` case for element type 9 (`UIElement_Resizebar`, `UiResizeGrip`) + `UiRoot` grip-priority hit-testing now resize from all 4 edges and all 4 corners, while the top strip (a Type-2 Dragbar, not a grip) correctly remains a move-only affordance. |
|
||||
| J | The chat window has transparency issues / visual artifacts, and the user wants a transparency setting eventually. | **Artifacts FIXED at CH6a `1fd51543`** — the reported visual glitches were downstream of importing the wrong LayoutDesc (stray unparented siblings, the hand-cropped content width, the 9px patch); all retired with the correct import, and the two hard-coded translucent-black tints on the transcript/input are removed now that their parent panels draw their own authored background sprites. **Real opacity (the future transparency SETTING) remains CH6c** — `UiRenderContext.AlphaMod` still has no draw-path consumer. |
|
||||
| J | The chat window has transparency issues / visual artifacts, and the user wants a transparency setting eventually. | **Artifacts FIXED at CH6a `1fd51543`** — the reported visual glitches were downstream of importing the wrong LayoutDesc (stray unparented siblings, the hand-cropped content width, the 9px patch); all retired with the correct import, and the two hard-coded translucent-black tints on the transcript/input are removed now that their parent panels draw their own authored background sprites. **Real opacity + the transparency SETTING landed at CH6c** — `UiRenderContext` now applies window alpha to sprite, rect, AND text draws; `RetailWindowOpacityController` drives every window's opacity from keyboard focus; the Settings → Chat tab carries two linked sliders. Pending the next connected round for visual confirmation. |
|
||||
|
||||
Findings A–G's evidence: this commit's diff + the new/updated tests in
|
||||
`tests/AcDream.Runtime.Tests/Gameplay/PlayerMovementControllerTests.cs`,
|
||||
|
|
|
|||
|
|
@ -463,12 +463,54 @@ and application is one call on the window's own render surface —
|
|||
text — with one alpha, not a per-widget background tint.** This is the shape the
|
||||
future acdream user setting should take.
|
||||
|
||||
UNVERIFIED: retail's *shipped default values* for the two sliders. They are not
|
||||
constants in code (the only in-code fallback is 1.0f); they arrive from the
|
||||
server's `GameplayOptions` blob or from the options page's authored slider
|
||||
defaults. Cheapest resolution: one cdb breakpoint on
|
||||
`ChatInterface::SetDefaultOpacity` at login and read `st(0)` — or dump the
|
||||
slider template defaults from the options LayoutDesc (`0x2100002B`).
|
||||
**RESOLVED at Campaign CH slice CH6c (2026-08-10) by static decomp, not cdb —
|
||||
the values are constructor-literal, so no live attach was needed.** Retail's
|
||||
shipped defaults are PER WINDOW CLASS, not one constant:
|
||||
|
||||
```
|
||||
004f4550 ChatInterface::ChatInterface(this, arg2, arg3) // BASE ctor
|
||||
004f459f this->m_fDefaultOpacity = 0.5f;
|
||||
004f45a5 this->m_fCurrentOpacity = 0.5f;
|
||||
004f45ab this->m_fActiveOpacity = 1f;
|
||||
|
||||
004cd0f0 gmMainChatUI::gmMainChatUI(this, arg2, arg3) // derived, calls base first
|
||||
004cd0ff ChatInterface::ChatInterface(this, arg2, arg3);
|
||||
004cd148 this->m_fDefaultOpacity = 1f; // OVERRIDES base
|
||||
004cd14e this->m_fCurrentOpacity = 1f;
|
||||
// m_fActiveOpacity left at base's 1f
|
||||
|
||||
004ce2c0 gmFloatyChatUI::Create(arg1, arg2) // the 4 floating windows
|
||||
004ce2e0 ChatInterface::ChatInterface(eax, arg1, arg2); // NO override — keeps base 0.5/1.0
|
||||
```
|
||||
|
||||
`gmFloatyMainChatUI` (element class `0x10000050`, the concrete class actually
|
||||
instantiated for the retail main chat window — its `DynamicCast` accepts both
|
||||
`0x10000050` and `0x10000041`) calls `gmMainChatUI::gmMainChatUI` as its own
|
||||
base constructor (`0x004D22B0`) and adds no opacity override of its own, so it
|
||||
inherits `gmMainChatUI`'s 1.0/1.0.
|
||||
|
||||
**So: the main chat window is ALWAYS FULLY OPAQUE in both states (Default=1.0,
|
||||
Active=1.0) unless a saved `GameplayOptions` value overrides it via
|
||||
`UpdateFromPlayerModule`; the four floating windows default to
|
||||
Default=0.5/Active=1.0 (translucent when idle, opaque once the chat entry has
|
||||
focus).** These are IN-MEMORY CONSTRUCTED starting values for each window
|
||||
INSTANCE's fields — they get overwritten the moment `UpdateFromPlayerModule`
|
||||
successfully reads a persisted `0x10000080`/`0x10000081` value from
|
||||
`PlayerModule::InqOption` (the SAME global option for every window instance),
|
||||
which is why the two options are still correctly described as GLOBAL rather
|
||||
than per-window: only a NEVER-SAVED option (a fresh character, nothing in the
|
||||
`GameplayOptions` blob yet) lets the per-class constructed defaults show
|
||||
through, and even then only until the user's first slider drag pushes one
|
||||
shared value into every live window via `RecvNotice_GameplayOptionChanged`.
|
||||
|
||||
acdream ships ONE shared global default — the base `ChatInterface` value,
|
||||
0.5/1.0 — applied uniformly to every window including the main chat window
|
||||
(register row AP-190 in `docs/architecture/retail-divergence-register.md`),
|
||||
rather than replicating `gmMainChatUI`'s per-class 1.0/1.0 override. The
|
||||
linking invariant (active >= default, restored by dragging the OTHER value —
|
||||
verified from `SetDefaultOpacity`/`SetActiveOpacity`'s own bodies, matching
|
||||
the summary already recorded above) is ported exactly regardless of which
|
||||
default seeds it.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -635,7 +677,26 @@ per-grip bool properties `0x2A`/`0x2B`/`0x2C`/`0x2D`. Additionally,
|
|||
for `Resizebar` in `src/AcDream.App/UI` returns nothing — so authored grips
|
||||
would be imported as inert sprites today.
|
||||
|
||||
**G3 — Window opacity is completely inert.**
|
||||
**G3 — Window opacity is completely inert. CLOSED at Campaign CH slice CH6c
|
||||
(2026-08-10).** `UiRenderContext.ApplyAlpha` already gated `DrawRect`/
|
||||
`DrawFill`/`DrawSprite` before this slice (added back at `1da697ec`, well
|
||||
before CH6 — the "zero consumers" framing below described the PUBLIC
|
||||
`AlphaMod` property specifically, not the private `_alpha`/`ApplyAlpha` pair
|
||||
those three draws already used); the actual gap was narrower than originally
|
||||
scoped: (a) `DrawStringDat`/`DrawString` still passed `applyAlpha: false`, so
|
||||
TEXT stayed sharp over a translucent window against retail's whole-surface
|
||||
`SetOpacity` semantics — CH6c fixed both; (b) nothing ever SET a window's
|
||||
`Opacity` below its 1f default, since `RetailUiRuntime.MountChat` deliberately
|
||||
left it at 1f pending this slice. CH6c added
|
||||
`RetailWindowOpacityController` (`src/AcDream.App/UI/RetailWindowOpacityController.cs`),
|
||||
which drives every `RetailWindowManager`-registered window's live `Opacity`
|
||||
from keyboard-focus state and the two retail-linked Default/Active floats,
|
||||
now exposed as a Settings → Chat tab transparency slider pair
|
||||
(`SettingsPanel.RenderChatTab`). See the verified defaults + linking
|
||||
behavior above (§3) and register row AP-190. The original paragraph below is
|
||||
kept verbatim as the historical record of what CH6a/b actually shipped —
|
||||
do not re-run this investigation.
|
||||
|
||||
`RetailWindowFrame.cs:157` sets `outerFrame.Opacity`, and
|
||||
`UiElement.DrawSelfAndChildren` (`src/AcDream.App/UI/UiElement.cs:465`) and
|
||||
`DrawOverlays` (`:513`) push it onto `UiRenderContext`'s alpha stack. But
|
||||
|
|
@ -743,8 +804,16 @@ run alongside either.
|
|||
|
||||
- **Retires** any row asserting "chat window is not resizable from the top" once
|
||||
CH6a lands.
|
||||
- **New row** if CH6b keeps text out of the window alpha (retail's
|
||||
`ChatInterface::SetOpacity @0x004F3120` fades the whole surface).
|
||||
- **CLOSED at CH6c**: text now respects window alpha — `DrawStringDat`/
|
||||
`DrawString` route through `ApplyAlpha` exactly like `DrawSprite`/`DrawRect`/
|
||||
`DrawFill`, matching `ChatInterface::SetOpacity`'s whole-surface fade. No
|
||||
divergence row needed for this part.
|
||||
- **New row AP-190** (CH6c): acdream applies the two opacity options to EVERY
|
||||
`RetailWindowManager` window (chat + floaties + vitals + toolbar +
|
||||
everything else), where retail's mechanism only ever runs from
|
||||
`ChatInterface`-derived windows; and ships ONE shared default (the base
|
||||
`ChatInterface` ctor's 0.5/1.0) rather than `gmMainChatUI`'s per-class
|
||||
1.0/1.0 override for the main window specifically.
|
||||
- **New row** for local-only chat-window persistence until CH6f, since retail
|
||||
stores this server-side in `GameplayOptions`.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue