Applies all 11 items from the Opus dual-lens review of989f6652(0 blockers, 7 SHOULD-FIX, 4 NOTE): - S2: UiElement.DrawSelfAndChildren now pushes the ambient clip right after PushAlpha and wraps OnDraw + the children walk + OnDrawAfterChildren in ONE block — the literal UIRegion::DrawHere @0x0069FA30 shape, which clips an element's OWN DrawSelf too, not just its children (UIElement_Text::DrawSelf @0x00467AA0 locks glyph blits to its own clipped surface rect; UIRegion::DrawSelf @0x0069F1A0 blits per clip rect). Deleted the two now-redundant ad-hoc self-clips this supersedes: UiText.DrawText and UiField.DrawMultiLine both pushed their own (0,0,Width,Height) — exactly what the new ambient clip already provides one level up. Kept UiButton.DrawBlockLabel's clip: it clips to LabelBox/ValueBox, an authored INNER sub-rect that can be smaller than and offset from the button's own full rect — a genuine narrower viewport, not a redundant duplicate. - S3: deleted UiItemList's `ClipsChildren => CellWidth > 0f` override — correct under the old opt-in-false default, inverted under the new default-true (an unconfigured list would stop clipping instead of clipping like everything else). - S4: pinned the escaped-popup input path end to end. New UiAncestorClipTests test mounts a menu inside a short window on a real UiRoot, opens it, and proves a click in the escaped popup region reaches the menu through UiRoot.PopupHit (a plain top-down walk is proven to reject the same point first). UiRoot.WantsMouse now also checks PopupHit — it previously only checked Captured/ HitTestTopDown, so a game action could fire underneath an open dropdown's escaped region. OnMouseDown/OnScroll already routed through PopupHit first (#374); unchanged. - S5: strengthened the Titles-divider regression test's positive half. The old assertion only checked SOME quad's Y fell in a band — vacuously true given other same-band content. Now asserts the divider's exact rect (X and Y), then diffs against the same rect with the divider hidden (Visible=false) to prove the quad was actually attributable to it. - S1: added UiWindowDrawCaptureSweepTests — Character/Chat/Vendor/ Options mounted through their real production Bind entry points with a non-zero sprite resolver, drawn via RecordingGpuDevice, asserting a per-window vertex floor (~40-45% of this session's observed baseline: Character 588, Chat 162, Vendor 54, Options 240) plus one key sprite id read LIVE off the bound controller/element (never hardcoded). Character's key sprite (RetailChromeSprites. TopEdge) specifically exercises OnDrawAfterChildren, the exact path S2's caution note flagged. Inventory/Paperdoll/social/map-house skipped — no single fixture-driven top-level Bind entry point. - S6: added the CT-GF1 subsection to the campaign plan's ledger (989f6652+ this fix round; CT7 re-gate still owed). - S7: UiRenderContext.PushClipUnbounded now resets to the CANVAS rect (0,0,ScreenSize), not null — retail's own popup region is SCREEN-clipped (UIElement_Menu::MakePopup spawns a top-level region bounded by the screen), not truly unbounded. AD-113 amended. - N1: UiRoot overrides ClipsChildren => false — the root's own region IS the screen (the viewport already scissors it), so this is a safety net against a momentarily zero-sized root silently blanking the whole UI tree under the new ancestor-clip default. - N2: added the empty-clip subtree cull (retail's var_24 gate @0x0069FB8E) to DrawSelfAndChildren only — DrawOverlays is a wholly separate traversal untouched by this change. New test proves a menu inside a fully-clipped (zero-width) window still draws its open popup via the overlay pass while the main pass draws nothing. - N3: CT7 script §5 now names the collapsed-toolbar check and the four highest-overflow windows (combat/vitals bar, Options bottom-button row, map/house page, floaty chat) as explicit eyeball items for the re-gate. - N4: verification below covers both the working tree and the clean committed tree. Decomp anchors: UIRegion::DrawHere @0x0069FA30 (var_24 gate @0x0069FB8E); UIElement_Text::DrawSelf @0x00467AA0 (self-clip); UIRegion::DrawSelf @0x0069F1A0; UIElement_Menu::MakePopup (screen- clipped popup region). Verification (both runs green, --filter "Lane!=InstalledDat& Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Timing& Lane!=Windows&Lane!=Linux&Lane!=SystemFont&Purpose!=Diagnostic& Status!=KnownFailure"): full Release solution build green; working tree 14,900+ tests across every project (one LandblockPresentation PipelineTests flake reproduced ONLY under full-solution parallel load, passes standalone and on rerun — unrelated to this change, streaming domain); InstalledDat lane green (ACDREAM_RUN_INSTALLED_DAT _TESTS=1, Status!=KnownFailure, 205+34+3+172 App/Content/Bake/Core tests). Clean committed tree (git stash push -u the uncommitted owner probe + docs files, rerun, stash pop) reported in the session summary. src/AcDream.App/UI/UiRoot.cs carries an unrelated, pre-existing uncommitted owner probe (ACDREAM_PROBE_UI_HOVER) — staged selectively (git add -p) so only this commit's own two hunks (ClipsChildren override, WantsMouse) landed; the probe hunk is untouched and stays uncommitted, same as before this fix round. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9.6 KiB
Campaign CT — connected gate script (CT7, user-driven)
Purpose: live verification of the character-panel parity campaign
(CT1–CT6) against ACE. Launch: the normal connected launch
(ACDREAM_RETAIL_UI=1, live ACE at 127.0.0.1:9000,
ACDREAM_PAK_PATH=<worktree>\artifacts\owner-gate\acdream-v5.pak).
Open the character panel (F9 / toolbar). Retail side-by-side comparison
is the oracle for every visual item.
Useful ACE console helpers: title grants come from quests/admin — check
@acecommands for a title-grant command; @grantxp for levels.
1. Header identity block (CT4) — Attributes AND Skills tabs
- Name line: the plain character name (rankless characters — the allegiance rank prefix is a registered deferral, AP-109).
- Heritage line: " " — e.g. "Female Aluvian War Mage" when a display title is set; just "Female Aluvian" when none. PASS: matches retail's composition and spacing exactly; a title beginning with "The" shows unmangled.
- PK line: "Non-Player Killer" (or "Player Killer" / "Player Killer Lite" on a PK/PKL character) in PURE WHITE. PASS: correct text + color on BOTH the Attributes and Skills tabs.
- Level number: pale gold with outline (authored color — compare against retail's level display side-by-side; the owner reported ours was previously off).
- Luminance pair: on a sub-200 character, NO luminance caption or value renders. (A level ≥ 200 character with MaximumLuminance shows "Luminance:" and " / " — verify only if such a character is available.)
- Live update: set a display title (see §2) — the heritage line updates the moment the server confirms, with no relog and no panel re-open.
2. Titles tab (CT3)
- Click the Titles tab. PASS: the page shows "Current Display Title:" + the current title (or "Unknown" only when the server's title id fails to resolve — normally a real title or the authored empty state), the "All Available Titles:" list, and the "Set as Display Title" button.
- List content: every earned title, alphabetically sorted, readable rows. With few titles the scrollbar shows retail's full-track thumb; with many (if available) the thumb sizes proportionally and scrolls.
- Ghost rule: with NOTHING selected the button is ghosted. Select the title that IS the current display title — button stays ghosted. Select a DIFFERENT title — button un-ghosts.
- Set round trip: click Set as Display Title. PASS: the display title text updates on the server's confirmation, the selection CLEARS (row highlight goes dark — retail behavior), the button re-ghosts, and the §1 heritage line updates live.
- Row selection visual: the selected row highlights with retail's row highlight art (full-row background swap), not a synthesized bar.
3. Attribute/skill rows (CT5)
- Icon alignment: row icons sit flush left (20x20 at the row's left edge), matching retail — the previous inset/smaller icons are gone. Compare a few rows side-by-side against retail.
- Value gutter: the numbers column ends with a visible margin before the panel border (the scrollbar band) — retail's 7px gutter.
- Row height: rows are retail-height (slightly tighter than before); section headers (Trained/Untrained/Unusable) unchanged.
- Selection highlight: clicking a row highlights with the retail full-row art; the spellbook's selection visuals are UNCHANGED (regression check — open the spellbook and select a spell).
- Raise buttons / tooltips / footer: regression sweep — raise ×1 and ×10 still work with correct ghosting, skill tooltips still show formula + description wrapped correctly, the footer numbers update.
4. Resize + scrollbar (CT6)
Ground truth (2026-08-25 live probe against layout 0x2100006E, host
0x100005FE — docs/research/2026-08-24-campaign-ct-dat-ground-truth.md
§CT6, corrected by the CT6 fix round's BLOCKER B1): the resize clamp is
authored on the SHARED gmPanelUI host, not the character content
itself, and the host IS retail's own outer window frame — its authored
values are chrome-INCLUSIVE, not a content size our own chrome adds on
top of. Host authors MinWidth=MaxWidth=310 (fixed width — no
horizontal Resizebar) and MinHeight=372, MaxHeight=1000. The
MOUNTED window's outer bounds are EXACTLY those same numbers: width
fixed 310px, floor 372px, ceiling 1000px — no inset is
added on top (RetailWindowFrame.Options.DatConstraintSourceIsOuterFrame
now tells the mount path this source already includes the bevel).
Starting height: the window OPENS at retail's authored default,
372px — its own resize floor. It cannot open any shorter; it can
only be dragged taller.
- Grab the bottom edge and drag up (shrink). PASS: the window stops shrinking at its authored floor (372px outer / the point where further dragging has no visible effect) — since the window already OPENS at that floor, this step should show no shrink at all (there is no room below the default to shrink into). It does NOT collapse arbitrarily small. Retail comparison: drag retail's own Character/ Skills window down from its own default; it should likewise refuse to shrink further immediately.
- Keep dragging down (grow). PASS: the window keeps growing until its authored ceiling (1000px outer) — same side-by-side comparison against retail's own ceiling.
- Left/right edges do not resize. Only the bottom edge (and top Dragbar for moving, not resizing) responds — matches retail's fixed-width authoring (no horizontal Resizebar).
- Scrollbar hand-off, Attributes tab. At the default window size (372px) the 9 attribute/vital rows (180px content) OVERFLOW the 160px list — the scrollbar is ACTIVE (visible + interactive) IMMEDIATELY on open, not after shrinking. PASS: this is the owner's item 2 fix — previously the scrollbar never appeared on Attributes at all. Grow the window until the rows fit without scrolling: the bar DISAPPEARS entirely (0x1000023E authors 0x79 hide-when-disabled TRUE — a fitting list hides the bar, it does not leave a full-track "disabled" thumb visible). Shrink back down and the bar reappears.
- Scrollbar hand-off, Skills tab. Same immediate-overflow-at- default check (a longer skills list only makes the overflow more obvious); grow until it fits and confirm the bar disappears the same way.
- Scrollbar hand-off, Titles tab. With several earned titles, the
Titles list (authored 455px, inside the 575px page) is scroll-clipped
into the same 372px-default window and its own scrollbar
(
0x10000533, also hide-when-disabled — fixture-verified) takes over the same way: active when titles overflow, hidden when the window is grown enough that they all fit. - Footer stays bottom-docked. While shrinking/growing on the Attributes/Skills tabs, the footer (raise buttons / selected-stat info) stays pinned to the bottom edge — it does not float mid-window or get clipped early.
- Grow back restores. Drag back down to the original default (372px): the lists return to their default OVERFLOWING state (scrollbar reactivates — this is the default, not "all rows fit") and the window returns to its original proportions.
- Other windows (chat, social) still clamp at their own authored
minimums — regression check (chat: min 300×100, max 2000×2000 per
CharacterPanelLiveDatTests.ChatWindowRoot_AuthorsExplicitSizeConstraints).
5. Regression sweep (5 minutes)
- Attributes/Skills tab switching unaffected; CA5 behaviors intact (raise round trips, live run-speed update on Quickness).
- Logout/login: titles and display title persist; the header matches PlayerDescription's values.
- Chat window: the CH-round fixes hold (input rails on focus, "Gen" caption, button flick, no vibrating text while dragging).
- CT-GF1 fix round (client-wide ancestor clip) — eyeball items. The new
default clips every element to its own box by default; these are the
windows most likely to show a silent over-clip (content trimmed that
should be visible) if the port has an edge case the automated suite
didn't catch:
- Collapsed toolbar: collapse the combat/spell toolbar to its narrow strip and back — confirm nothing inside it (icons, the collapse grip) gets cut off or fails to reappear on expand.
- Combat/vitals bar: at its default size, confirm the health/ stamina/mana bars and their numeric overlays render in full, not trimmed at an edge.
- Options panel bottom-button row (Gameplay tab): confirm all seven buttons (Exit to Character Selection, Configure Keyboard, In-Game Help, Urgent Assistance, Report Abuse, mouse-turning checkbox, Exit Game) render completely, none clipped at the panel's bottom edge.
- Map/house page: confirm the map image and player/house icons render in full across the page's own scroll/zoom range, not clipped at the viewport edge.
- Floaty chat (a detached floating chat window, Alt+1..4): confirm
the transcript and input row render in full at both a small and a
resized-larger window size — the same class of symptom CT-GF1's own
ChatLayoutConformanceTestsregression-pinned for the main chat window's input row.
Report back
Per section: PASS/FAIL plus anything odd. The three answers that matter most:
- §2.4 — does the set-title round trip clear the selection and update the header live?
- §3.1/§3.2 — do icons and the value gutter now match retail side-by-side?
- §1.3 — is the PK line present, white, and correct on both tabs?