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>
This commit is contained in:
parent
4cbbdaf4bf
commit
ec50455a63
10 changed files with 741 additions and 74 deletions
|
|
@ -399,8 +399,75 @@ enforcement verified as the STANDARD path for every registered window
|
|||
(one shared mechanism in `RetailWindowFrame`/`RetailWindowManager`,
|
||||
no per-window special cases).
|
||||
|
||||
**CT6 landing notes (2026-08-25, implementation).** Live probe (dumped
|
||||
+ deleted, pattern preserved by the new
|
||||
`CharacterPanelLiveDatTests.PanelHost_AuthorsFixedWidthAndBottomOnlyResizeContract`
|
||||
pin) confirmed the research lead's hypothesis exactly: the shared
|
||||
`gmPanelUI` host `0x100005FE` (LayoutDesc `0x2100006E`) authors
|
||||
MinWidth=MaxWidth=310 (fixed — no horizontal Resizebar), MinHeight=372,
|
||||
MaxHeight=1000; its bottom Resizebar (`0x10000660`) and top Dragbar
|
||||
(`0x1000065C`) are DIRECT CHILDREN of the host, not the content parent
|
||||
— matching `UIElement_Resizebar::StartMouseResizing @0x0046B7E0`'s
|
||||
`GetParent()` call and `UIElement::MouseResizeElement @0x00461130`'s
|
||||
`GetAttribute_Int(this, 0x3C..0x3F)` reads off that same parent. The
|
||||
Character/Skills slot `0x1000018E` itself authors no constraints of its
|
||||
own (confirmed, same pin). `RetailUiRuntime.MountCharacter` now imports
|
||||
that host element and passes it as `DatConstraintSource`; the mounted
|
||||
outer frame clamps at MinWidth=MaxWidth≈320, MinHeight≈382,
|
||||
MaxHeight≈1010 after the NineSlice chrome inset. Full derivation +
|
||||
decomp anchors: `docs/research/2026-08-24-campaign-ct-dat-ground-truth.md`
|
||||
§CT6.
|
||||
`CharacterStatController.RebuildActiveList` now wraps BOTH the
|
||||
Attributes and Skills tabs' rows in the same `UiScrollablePanel`
|
||||
viewport (previously only Skills got one; Attributes rows were added
|
||||
directly to the ListBox with no clipping/scrolling and the shared
|
||||
scrollbar was force-hidden — the owner's 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 (`HideWhenDisabled`
|
||||
defaults false), so no per-tab visibility toggle is needed any more.
|
||||
This surfaced and fixed a real, previously-unexercised `#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 (160px) to its mounted height — measuring a
|
||||
bogus non-zero margin that permanently capped the viewport short on
|
||||
every later resize. Fixed with an eager
|
||||
`viewport.CaptureCurrentAnchorBaseline()` call right after
|
||||
`AddChild`, mirroring the identical fix already shipped in
|
||||
`UiTemplateListBox.Viewport`'s own lazy getter. `CharacterTitlesController.Bind`
|
||||
gained the same defensive `Anchors = Left|Top|Bottom` fallback for the
|
||||
Titles ListBox (`0x10000532`) that `CharacterStatController` already
|
||||
had for its own list — live-verified as a no-op on the real DAT (both
|
||||
the Titles page container and its ListBox already carry a real authored
|
||||
`LayoutPolicy` that stretches correctly on its own), but matching the
|
||||
established pattern for synthetic/test layouts.
|
||||
STANDARDIZATION AUDIT (no gaps found, no follow-up filed): `UiElement
|
||||
.MinWidth/MinHeight/MaxWidth/MaxHeight`, set once at
|
||||
`RetailWindowFrame.Mount` from `Options.DatConstraintSource`/explicit
|
||||
overrides, are the ONLY clamp fields — read identically by the
|
||||
interactive drag path (`UiRoot`'s resize handling), the programmatic
|
||||
path (`RetailWindowManager.ResizeTo`, which both `RetailPanelUiController`'s
|
||||
main-panel geometry sync and this slice's tests exercise), and the
|
||||
persisted-geometry restore clamp (`RetailWindowLayoutPersistence.Apply`).
|
||||
`RetailWindowFrame.Mount` remains the single production mount path (no
|
||||
window bypasses it). New regression pin:
|
||||
`RetailWindowFrameTests.NineSlice_ChatShapedConstraints_ClampProgrammaticResizeAtAuthoredBounds`
|
||||
proves the same mechanism still clamps chat-shaped constraints after
|
||||
Character was wired onto it. Tests: `CharacterStatControllerTests
|
||||
.CharacterWindow_ResizesYWithinAuthoredHostClamp_AndReflowsListAndScrollbar`
|
||||
(window-level: clamp at authored min/max, list shrink, scrollbar
|
||||
overflow flip, footer stays bottom-docked, grow-back restore) and
|
||||
`CharacterTitlesControllerTests.TitlesList_ReflowsWithWindowResize_AndScrollbarOverflowFlips`
|
||||
(same contract for the Titles list) plus the pre-existing 126+22-test
|
||||
suites, all updated where the new nested-viewport DOM shape required it
|
||||
(`Descendants(list)` instead of `list.Children` — the shape Skills rows
|
||||
already needed). No register row: every number is either a live-probed
|
||||
authored DAT value or a structural anchor-capture-correctness fix,
|
||||
nothing inferred.
|
||||
|
||||
**CT7 — Connected gate.** Test script
|
||||
(`docs/research/2026-08-24-campaign-ct-test-script.md`), owner drive:
|
||||
(`docs/research/2026-08-25-campaign-ct-test-script.md`), owner drive:
|
||||
titles round trip against ACE (earn/set/display), header lines vs
|
||||
retail side-by-side, resize behavior, row alignment screenshots.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue