Commit graph

4 commits

Author SHA1 Message Date
Erik
025108a8aa fix(CT-GF1): review fix round — literal DrawHere clip shape, empty-clip cull, popup input routing
Applies all 11 items from the Opus dual-lens review of 989f6652 (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>
2026-08-25 07:11:42 +02:00
Erik
996cd73675 fix(CT6): fix round — chrome-inclusive host clamp (BLOCKER B1) + 372px mount default
Opus dual-lens review of CT6 (ec50455a) found 1 blocker, 4 should-fix, 5
notes. All applied:

BLOCKER B1 — the shared gmPanelUI host (0x100005FE) IS retail's own
outer window frame, not a content element: its authored 310/372/310/1000
already include the 5px bevel on every side. RetailWindowFrame.Mount was
adding the NineSlice wrapper's OWN 10px chrome inset on top of that
already-chrome-inclusive source, clamping MinWidth to 320 while the
window's actual mounted outer width stayed 310 — silently below its own
minimum until RetailWindowManager.ResizeTo forcibly widened it despite
ResizeX=false. Fixed with a new
RetailWindowFrame.Options.DatConstraintSourceIsOuterFrame opt-out
(chrome inset = 0 for constraint resolution only, value stays
DAT-sourced); MountCharacter sets it true. Mounted clamp is now exactly
the host's four raw values: width fixed 310, height 372..1000. Added a
mount-time invariant (throws if the mounted outer extent falls outside
its own just-computed clamp) that would have caught this at the first
test run.

S4 (campaign-lead ruling) — the window must MOUNT at retail's authored
default, outer 372 (content 362, matching the host's own content parent
0x10000180), not 0x2100002E's own 300x600 content-authoring canvas
(which produced a stale 610px default pre-fix: 600 + 10 chrome inset).
372 is exactly the host's own authored MinHeight — retail opens at its
resize floor and can only be dragged taller. MountCharacter now sets
ContentHeight=362f explicitly. At this default the 9 attribute/vital
rows (180px) overflow the 160px list immediately — retail-correct, not
a regression.

S2 — 0x1000023E and 0x10000533 both author property 0x79
(HideWhenDisabled) TRUE (fixture-verified: BoolValue=true on both). A
fitting list HIDES the scrollbar entirely; it does not draw a full-track
"disabled" thumb. The code was already correct; four wrong descriptions
(plan ledger, CharacterStatController comment, CT7 script, test comment)
are corrected, plus a new IsPresentationVisible assertion pair in the
resize test.

S3 — CharacterTitlesController's `if (listBox.LayoutPolicy is null)`
Anchors fallback was unreachable on both the real DAT and the fixture
(0x10000532/0x10000539 both author HasOriginalParentSize=true, so
LayoutPolicy is always assigned). Deleted; added an InstalledDatFact pin
guarding the deletion against DAT drift.

N4 — renamed NineSlice_ChatShapedConstraints_... to
NineSlice_ContentShapedConstraints_InsetArithmeticClampsProgrammaticResize
(it tested inset arithmetic on a content-shaped source, not chat's real
contract) and added a true chat-contract pin mounting Chrome=Imported
with chat's real 300/100/2000/2000 constraints, asserting no inset
applies.

N5 — corrected the "nothing inferred, no register row" sentences in the
ground-truth doc and plan ledger: they were false pre-fix (the mounted
clamp WAS an inferred double-counted composition); true now that B1
removes the composition.

CT7 script §4 rewritten with exact clamps (no "≈"), the corrected
default-overflow scrollbar behavior, and an absolute starting-height
statement.

Verified: full hermetic solution suite green (15,441 tests, Release,
Lane exclusions per the release gate), InstalledDat lane green across
the whole solution (414 tests, ACDREAM_RUN_INSTALLED_DAT_TESTS=1,
Status!=KnownFailure) including two new pins
(TitlesListAndPage_AuthorHasOriginalParentSize,
Imported_ChatContract_ClampsAtAuthoredBoundsWithNoChromeInset).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 02:53:57 +02:00
Erik
ec50455a63 feat(CT6): character window Y-resize clamped at retail's authored host minimum + shrink-and-scroll list contract
CT6 (Campaign CT slice 6): the resize clamp source is the SHARED
gmPanelUI host (0x100005FE in LayoutDesc 0x2100006E), not 0x2100002E's
own root and not the Character/Skills slot 0x1000018E either — live
probe confirmed the host authors MinWidth=MaxWidth=310 (fixed — no
horizontal Resizebar authored), MinHeight=372, MaxHeight=1000, and that
the bottom Resizebar (0x10000660) and top Dragbar (0x1000065C) are
direct children of the host, not the content parent. Decomp chain:
UIElement_Resizebar::StartMouseResizing @0x0046B7E0 calls
UIElement::StartResizing(this->GetParent(), ...), stashing drag state on
that parent; UIElement::MouseResizeElement @0x00461130 then reads
GetAttribute_Int(this, 0x3C..0x3F) off that same element every
mouse-move.

RetailUiRuntime.MountCharacter now imports the host element and passes
it as RetailWindowFrame.Options.DatConstraintSource, matching the
existing MountSideVitals pattern.

CharacterStatController.RebuildActiveList now wraps BOTH the Attributes
and Skills tabs' rows in the same UiScrollablePanel viewport (previously
only Skills got one; Attributes rows had no clipping/scrolling and the
shared scrollbar was force-hidden — owner report item 2). The shared
scrollbar is now always bound + visible; UiScrollbar's own
IsPresentationVisible/IsModelDisabled already draw the correct
full-track "disabled" thumb when content fits. This surfaced and fixed
a real #372/#412-class anchor-baseline bug: the viewport's
Left|Top|Bottom anchor was capturing its baseline margins lazily on its
own first ApplyAnchor call, which happens AFTER the ListBox has already
grown from its raw DAT height to its mounted height, permanently
capping the viewport short on every later resize. Fixed with an eager
CaptureCurrentAnchorBaseline() call, mirroring UiTemplateListBox
.Viewport's own lazy getter.

CharacterTitlesController.Bind gained the same defensive
Anchors = Left|Top|Bottom fallback for the Titles ListBox that
CharacterStatController already had (a no-op on the real DAT — both the
Titles page and its ListBox already carry a real authored LayoutPolicy
that stretches correctly).

Standardization audit: UiElement.MinWidth/MinHeight/MaxWidth/MaxHeight,
set once at RetailWindowFrame.Mount, are the ONLY clamp fields — read
identically by interactive drag, RetailWindowManager.ResizeTo, and
RetailWindowLayoutPersistence's restore clamp. No gaps found; no
register row (every number is a live-probed authored DAT value or a
structural correctness fix, nothing inferred).

Tests: CharacterStatControllerTests
.CharacterWindow_ResizesYWithinAuthoredHostClamp_AndReflowsListAndScrollbar,
CharacterTitlesControllerTests
.TitlesList_ReflowsWithWindowResize_AndScrollbarOverflowFlips,
RetailWindowFrameTests
.NineSlice_ChatShapedConstraints_ClampProgrammaticResizeAtAuthoredBounds
(shared-mechanism regression pin), CharacterPanelLiveDatTests
.PanelHost_AuthorsFixedWidthAndBottomOnlyResizeContract (InstalledDat
pin). Existing attribute-row tests updated from list.Children to
Descendants(list) for the new nested-viewport shape (the pattern skill
rows already needed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 02:21:56 +02:00
Erik
657b84c297 docs(CT): CT7 gate script draft — §1-§3/§5 final, §4 pending CT6
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 01:17:35 +02:00