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>
This commit is contained in:
parent
ec50455a63
commit
996cd73675
11 changed files with 552 additions and 120 deletions
|
|
@ -641,26 +641,122 @@ root or the slot.
|
|||
`MountCharacter` now imports `ElementInfo? hostConstraint =
|
||||
LayoutImporter.ImportInfos(dats, 0x2100006Eu, 0x100005FEu)` (the same
|
||||
targeted single-root overload CT1 established for row templates) and
|
||||
passes it as `RetailWindowFrame.Options.DatConstraintSource`. Chrome
|
||||
inset (`2 * RetailChromeSprites.Border` = 10px, NineSlice) is added
|
||||
automatically by `RetailWindowFrame.ResolveConstraint`, giving the
|
||||
mounted outer frame MinWidth=MaxWidth=320, MinHeight=382, MaxHeight=1010.
|
||||
passes it as `RetailWindowFrame.Options.DatConstraintSource`.
|
||||
|
||||
**CORRECTED (CT6 fix round, BLOCKER B1, 2026-08-25):** this section
|
||||
originally claimed the NineSlice chrome inset (`2 *
|
||||
RetailChromeSprites.Border` = 10px) "is added automatically by
|
||||
`RetailWindowFrame.ResolveConstraint`, giving the mounted outer frame
|
||||
MinWidth=MaxWidth=320, MinHeight=382, MaxHeight=1010." That was WRONG.
|
||||
Host `0x100005FE` is not a bare content element our own NineSlice
|
||||
wrapper adds chrome to — per the geometry dumped above, it IS retail's
|
||||
own complete outer window frame: 5px bevel + the 300×362 content parent
|
||||
(`0x10000180`) + 5px = 310×372 exactly. Its authored 0x3C..0x3F values
|
||||
are therefore already CHROME-INCLUSIVE. Composing the wrapper's own 10px
|
||||
inset on top of an already chrome-inclusive source double-counted the
|
||||
bevel: the mounted window's clamp said MinWidth=320 while its actual
|
||||
mounted outer width was only 310 — the window opened already violating
|
||||
its own minimum, silently "fixed" at runtime only because
|
||||
`RetailWindowManager.ResizeTo`'s main-panel geometry sync forcibly
|
||||
widened it to 320 despite `ResizeX=false`, which would have produced a
|
||||
visible 15px right-bevel seam against the other eight main panels
|
||||
sharing that sync.
|
||||
|
||||
**Fix:** a new opt-out,
|
||||
`RetailWindowFrame.Options.DatConstraintSourceIsOuterFrame` (default
|
||||
`false`, preserving every other window's existing content-plus-chrome
|
||||
behavior), tells `ResolveConstraint` to apply a chrome inset of 0 when
|
||||
the DAT source is itself already the outer frame. `MountCharacter` sets
|
||||
it `true` for `hostConstraint`. The mounted outer clamps are now EXACTLY
|
||||
the host's four raw values, no composed arithmetic: **MinWidth=
|
||||
MaxWidth=310, MinHeight=372, MaxHeight=1000.** A new mount-time
|
||||
invariant in `RetailWindowFrame.Mount` (throws if the just-mounted outer
|
||||
extent falls outside its own just-computed clamp) guards against this
|
||||
class of bug recurring for any window this path mounts.
|
||||
`ResizeX=false`/`ResizableEdges=Bottom` (already correct in the
|
||||
pre-CT6 code) match the fixed-width/bottom-only-Resizebar authoring
|
||||
exactly — no change needed there.
|
||||
|
||||
### Which size governs the mount default (S4, campaign-lead ruling, 2026-08-25)
|
||||
|
||||
The "Size tension" flagged earlier in this doc (§4, "Correction to the
|
||||
plan") — `0x2100002E`'s own root authored 300×600 vs. the host slot's
|
||||
300×362 — is now resolved: **372px outer (362px content) is the number
|
||||
that governs the MOUNTED DEFAULT**, and 600 is the content's own
|
||||
authored design canvas that retail scroll-clips into the much smaller
|
||||
host slot, never the size the window actually opens at. Concretely:
|
||||
`0x2100002E`'s 300×600 root exists because its Titles page alone is
|
||||
authored 300×575 (Y=25 offset + the 25px tab bar — §3 above) — that is
|
||||
real, authored geometry, and the Titles page correctly stretches to fill
|
||||
whatever height the mounted window offers via its own `LayoutPolicy`
|
||||
(confirmed below). But retail never displays that full 600px canvas at
|
||||
once outside of the Titles tab's own internal scroll: the shared
|
||||
`gmPanelUI` host's content parent (`0x10000180`) is fixed at 300×362,
|
||||
and 372 (362 + the 10px chrome bevel) is exactly the host's own
|
||||
authored MinHeight — i.e., retail's Character/Skills window OPENS at
|
||||
its own resize floor and can only be dragged taller, never shorter.
|
||||
Pre-fix, `MountCharacter` left `Options.ContentHeight` unset, so it fell
|
||||
back to `content.Width`/`content.Height` — the raw 600px canvas — giving
|
||||
a stale 610px (600 + 10 chrome) mounted default that was never retail's
|
||||
actual opening size and was 238px taller than the true floor. Fixed by
|
||||
setting `Options.ContentHeight = 362f` explicitly (the same host
|
||||
content-parent height this section already probed and cited, not a new
|
||||
number). At the corrected 372px default, the authored page composition
|
||||
(header 112px + list 160px + divider + footer) is the true 362px design;
|
||||
the 9 attribute/vital rows (9 × 20 = 180px content) OVERFLOW the 160px
|
||||
list immediately, so the stat list's scrollbar is active from the moment
|
||||
the window opens — this is retail-correct (see §S2 below for what
|
||||
"active" means given the corrected `HideWhenDisabled` finding), not a
|
||||
regression introduced by the fix.
|
||||
|
||||
### Titles-page list reflow
|
||||
|
||||
The Titles page's own container (`0x10000539`) and its ListBox
|
||||
(`0x10000532`) both already carry a REAL authored `LayoutPolicy` in the
|
||||
committed/installed layout (live-verified: `LayoutPolicy is not null`
|
||||
for both) that correctly stretches with the mounted content's height —
|
||||
no code was needed to make the PAGE itself reflow. The only missing
|
||||
piece was the ListBox's own compatibility fallback (`Anchors =
|
||||
Left|Top|Bottom`, engaged only when `LayoutPolicy is null` — a no-op on
|
||||
the real DAT, but needed for synthetic/test layouts and matches the
|
||||
identical pattern already used for `CharacterStatController`'s
|
||||
`statList`), added in `CharacterTitlesController.Bind`.
|
||||
no code was needed to make the PAGE itself reflow.
|
||||
|
||||
**CORRECTED (CT6 fix round, S3, 2026-08-25):** this section originally
|
||||
went on to claim "the only missing piece was the ListBox's own
|
||||
compatibility fallback (`Anchors = Left|Top|Bottom`, engaged only when
|
||||
`LayoutPolicy is null` — a no-op on the real DAT, but needed for
|
||||
synthetic/test layouts...), added in `CharacterTitlesController.Bind`."
|
||||
That framing was wrong: both `0x10000532` and `0x10000539` author
|
||||
`HasOriginalParentSize=true` (the field `DatWidgetFactory` gates
|
||||
`LayoutPolicy` assignment on), confirmed both on the real installed DAT
|
||||
and the committed fixture — `LayoutImporter`/`DatWidgetFactory` ALWAYS
|
||||
assigns a real `LayoutPolicy` to these elements, so the `if
|
||||
(listBox.LayoutPolicy is null)` branch never ran anywhere, not even in
|
||||
the "synthetic/test layouts" case it was written to cover. It was dead
|
||||
code, not a harmless no-op fallback. Deleted from
|
||||
`CharacterTitlesController.Bind`; a new
|
||||
`CharacterPanelLiveDatTests.TitlesListAndPage_AuthorHasOriginalParentSize`
|
||||
pin asserts `HasOriginalParentSize` on both `0x10000532` and `0x10000539`
|
||||
to guard the deletion against future DAT drift.
|
||||
|
||||
### Correction to the scrollbar-visibility finding (S2, 2026-08-25)
|
||||
|
||||
The CT6 landing notes (in the campaign plan ledger) claimed
|
||||
`UiScrollbar`'s own `IsPresentationVisible`/`IsModelDisabled` "already
|
||||
draw the correct full-track 'disabled' thumb when content fits
|
||||
(`HideWhenDisabled` defaults false)." That had the authored default
|
||||
BACKWARDS. Both `0x1000023E` (the shared Attributes/Skills list
|
||||
scrollbar) and `0x10000533` (the Titles list's own scrollbar) author
|
||||
property `0x79` (`HideWhenDisabled`) **TRUE** in the committed fixture —
|
||||
verified directly against `tests/AcDream.App.Tests/UI/Layout/fixtures/character_2100002E.json`,
|
||||
property `"121"` (=0x79) carries `"BoolValue": true` on both elements. A
|
||||
fitting list HIDES the bar entirely (`IsPresentationVisible` false); it
|
||||
does not leave a full-track "disabled" thumb on screen. The CODE in
|
||||
`CharacterStatController.RebuildActiveList` was already correct —
|
||||
`.Visible = true` only keeps the bar bound in the tree, and
|
||||
`IsPresentationVisible` is the actual show/hide computation — only the
|
||||
description was wrong. Given the S4 correction above (mount default is
|
||||
now the compact 372px floor), the practical consequence is: the stat
|
||||
list's scrollbar is VISIBLE and interactive from the moment the window
|
||||
opens (rows overflow at the default), and DISAPPEARS once the window is
|
||||
grown enough that all rows fit — the opposite of what the uncorrected
|
||||
description implied.
|
||||
|
||||
### A latent anchor-baseline bug this slice surfaced and fixed
|
||||
|
||||
|
|
@ -687,3 +783,15 @@ by `CharacterStatControllerTests
|
|||
Every number in this section is either a live-probed authored DAT value
|
||||
or a structural wiring/anchor-capture-correctness fix — nothing here is
|
||||
inferred or approximated.
|
||||
|
||||
**CORRECTED (CT6 fix round, N5, 2026-08-25):** at initial landing, this
|
||||
sentence was not actually true — the mounted 320/382/1010 clamp WAS an
|
||||
inference (BLOCKER B1: the host's already chrome-inclusive values with a
|
||||
second, redundant chrome inset composed on top). After the B1 fix
|
||||
removes that composition, the mounted clamp is now literally the host's
|
||||
own four probed values (310/310/372/1000) with zero arithmetic applied,
|
||||
so the sentence holds for real. The S4 content-height default (362) is
|
||||
likewise not a new inferred number — it is the same host content-parent
|
||||
width/height (`0x10000180`, 300×362) this section already probed and
|
||||
cited above, applied to `Options.ContentHeight` instead of being left
|
||||
unset.
|
||||
|
|
|
|||
|
|
@ -75,48 +75,61 @@ Useful ACE console helpers: title grants come from quests/admin — check
|
|||
|
||||
Ground truth (2026-08-25 live probe against layout `0x2100006E`, host
|
||||
`0x100005FE` — `docs/research/2026-08-24-campaign-ct-dat-ground-truth.md`
|
||||
§CT6): the resize clamp is authored on the SHARED `gmPanelUI` host, not
|
||||
the character content itself. Host authors **MinWidth=MaxWidth=310**
|
||||
(fixed width — no horizontal Resizebar) and **MinHeight=372,
|
||||
MaxHeight=1000**. With the NineSlice chrome's 10px inset, the MOUNTED
|
||||
window's outer bounds are MinHeight≈382px, MaxHeight≈1010px, width
|
||||
fixed ≈320px.
|
||||
§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.
|
||||
|
||||
1. **Grab the bottom edge and drag up (shrink).** PASS: the window
|
||||
stops shrinking at its authored floor (≈382px outer / the point where
|
||||
further dragging has no visible effect) — it does NOT collapse
|
||||
arbitrarily small, and it does NOT refuse to shrink at all (the
|
||||
pre-CT6 bug). Retail comparison: drag retail's own Character/Skills
|
||||
window to its floor side-by-side; both should bottom out at
|
||||
proportionally the same point relative to their starting size.
|
||||
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.
|
||||
2. **Keep dragging down (grow).** PASS: the window keeps growing until
|
||||
its authored ceiling (≈1010px outer) — same side-by-side comparison
|
||||
its authored ceiling (1000px outer) — same side-by-side comparison
|
||||
against retail's own ceiling.
|
||||
3. **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).
|
||||
4. **Scrollbar hand-off, Attributes tab.** At the default window size
|
||||
the 9 attribute/vital rows fit without a visible scroll gap (bar
|
||||
shows retail's full-track "disabled" thumb — present, not hidden,
|
||||
per the 2026-08-24 scrollbar work's contract). Shrink the window
|
||||
until the rows overflow: the bar's thumb shrinks proportionally and
|
||||
becomes interactive (drag, click-to-page, wheel-scroll all move the
|
||||
list). PASS: this is the owner's item 2 fix — previously the
|
||||
scrollbar never appeared on Attributes at all.
|
||||
5. **Scrollbar hand-off, Skills tab.** Same shrink/overflow check —
|
||||
should already have worked pre-CT6; confirm no regression.
|
||||
6. **Scrollbar hand-off, Titles tab.** With several earned titles,
|
||||
shrink the window until the Titles list (authored 455px, inside the
|
||||
575px page) overflows its available space — the list's own scrollbar
|
||||
(`0x10000533`) takes over the same way.
|
||||
(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.
|
||||
5. **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.
|
||||
6. **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.
|
||||
7. **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.
|
||||
8. **Grow back restores.** Drag back to the original size: the lists
|
||||
return to showing all rows without scrolling (scrollbar reverts to
|
||||
the full-track disabled state) and the window returns to its
|
||||
original proportions.
|
||||
8. **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.
|
||||
9. Other windows (chat, social) still clamp at their own authored
|
||||
minimums — regression check (chat: min 300×100, max 2000×2000 per
|
||||
`CharacterPanelLiveDatTests.ChatWindowRoot_AuthorsExplicitSizeConstraints`).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue