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:
Erik 2026-08-25 02:53:57 +02:00
parent ec50455a63
commit 996cd73675
11 changed files with 552 additions and 120 deletions

View file

@ -146,17 +146,25 @@ public sealed class RetailWindowFrameTests
/// enforced identically by <c>RetailWindowManager.ResizeTo</c> (this
/// test), the interactive drag path in <c>UiRoot</c>, and the persisted-
/// geometry restore clamp in <c>RetailWindowLayoutPersistence.Apply</c>.
/// Chat (<c>0x2100006F</c>) was the FIRST window to author real DAT
/// min/max (min 300/100, max 2000/2000 per
/// <c>CharacterPanelLiveDatTests.ChatWindowRoot_AuthorsExplicitSizeConstraints</c>);
/// this test proves an interactive-shaped <c>ResizeTo</c> call against a
/// chat-shaped mount still clamps at those authored bounds after CT6
/// wired a SECOND window (Character) onto the same mechanism — a
/// regression here would mean CT6 accidentally special-cased Character
/// instead of reusing the standard path.
///
/// <para>CT6 fix round (2026-08-25, N4 rename): this test's ORIGINAL name
/// (<c>NineSlice_ChatShapedConstraints_ClampProgrammaticResizeAtAuthoredBounds</c>)
/// overclaimed. It mounts a content-shaped root (490×100, roughly chat's
/// own default extent) with a synthetic height-only constraint source
/// (<see cref="Constraints"/> sets ONLY 0x3E/0x3C — no width bounds at
/// all) — it exercises the NineSlice wrapper's chrome-inset ARITHMETIC on
/// a content-shaped source, not chat's actual authored DAT contract. Chat
/// itself mounts with <c>Chrome = RetailWindowChrome.Imported</c> (its
/// own root already IS the complete window, chrome included — see
/// <c>RetailUiRuntime.MountChat</c>), so the real chat contract needs
/// ZERO chrome inset, the opposite of what this test's NineSlice
/// wrapping exercises. See
/// <see cref="Imported_ChatContract_ClampsAtAuthoredBoundsWithNoChromeInset"/>
/// below for the genuine chat-shaped pin (Chrome=Imported, chat's real
/// 300/100/2000/2000 four-sided constraint).</para>
/// </summary>
[Fact]
public void NineSlice_ChatShapedConstraints_ClampProgrammaticResizeAtAuthoredBounds()
public void NineSlice_ContentShapedConstraints_InsetArithmeticClampsProgrammaticResize()
{
var root = new UiRoot { Width = 1920, Height = 1080 };
var content = new UiPanel { Width = 490, Height = 100 };
@ -168,7 +176,7 @@ public sealed class RetailWindowFrameTests
NoTex,
new RetailWindowFrame.Options
{
WindowName = "chat",
WindowName = "chat-shaped",
Chrome = RetailWindowChrome.NineSlice,
Left = 10,
Top = 20,
@ -191,6 +199,59 @@ public sealed class RetailWindowFrameTests
Assert.Equal(500f, handle.Height);
}
/// <summary>
/// CT6 fix round (2026-08-25, N4): the true chat-contract pin the
/// renamed test above no longer claims to be. Chat's real LayoutDesc
/// (<c>0x2100006F</c>, root <c>0x10000600</c>) mounts with
/// <c>Chrome = RetailWindowChrome.Imported</c> — its own root already IS
/// the complete retail window, own dragbar/border/resize-grip children
/// included (<c>RetailUiRuntime.MountChat</c>) — and authors real
/// four-sided constraints, MinWidth=300, MinHeight=100, MaxWidth=2000,
/// MaxHeight=2000
/// (<c>CharacterPanelLiveDatTests.ChatWindowRoot_AuthorsExplicitSizeConstraints</c>).
/// Because Imported chrome applies NO wrapper (<c>inset = 0</c>), the
/// mounted clamp must equal those four DAT values EXACTLY — no chrome
/// inset composed on top, the same "authored value governs verbatim"
/// contract CT6's BLOCKER B1 fix restored for the Character window's
/// chrome-inclusive host source.
/// </summary>
[Fact]
public void Imported_ChatContract_ClampsAtAuthoredBoundsWithNoChromeInset()
{
var root = new UiRoot { Width = 1920, Height = 1080 };
var content = new UiPanel { Width = 410, Height = 100 };
var constraints = ChatConstraints();
RetailWindowHandle handle = RetailWindowFrame.Mount(
root,
content,
NoTex,
new RetailWindowFrame.Options
{
WindowName = "chat",
Chrome = RetailWindowChrome.Imported,
Left = 10,
Top = 440,
ResizeX = true,
ResizeY = true,
DatConstraintSource = constraints,
});
Assert.Equal(300f, handle.OuterFrame.MinWidth);
Assert.Equal(100f, handle.OuterFrame.MinHeight);
Assert.Equal(2000f, handle.OuterFrame.MaxWidth);
Assert.Equal(2000f, handle.OuterFrame.MaxHeight);
// No chrome inset: the clamp must hold at EXACTLY the authored
// 100/2000 bounds, not 110/2010 the way a NineSlice-wrapped source
// would.
Assert.True(handle.ResizeTo(handle.Width, 5f));
Assert.Equal(100f, handle.Height);
Assert.True(handle.ResizeTo(handle.Width, 50000f));
Assert.Equal(2000f, handle.Height);
}
[Fact]
public void NineSlice_CanSupplyBorderWithoutDuplicatingAuthoredCenter()
{
@ -312,4 +373,37 @@ public sealed class RetailWindowFrameTests
info.States[UiStateInfo.DirectStateId] = direct;
return info;
}
/// <summary>
/// Synthetic four-sided constraint matching chat's real authored DAT
/// values (<c>CharacterPanelLiveDatTests.ChatWindowRoot_AuthorsExplicitSizeConstraints</c>):
/// MinWidth=300, MinHeight=100, MaxWidth=2000, MaxHeight=2000.
/// </summary>
private static ElementInfo ChatConstraints()
{
var info = new ElementInfo();
var direct = new UiStateInfo { Id = UiStateInfo.DirectStateId };
direct.Properties.Values[0x3Fu] = new UiPropertyValue
{
Kind = UiPropertyKind.Integer,
IntegerValue = 300,
};
direct.Properties.Values[0x3Eu] = new UiPropertyValue
{
Kind = UiPropertyKind.Integer,
IntegerValue = 100,
};
direct.Properties.Values[0x3Du] = new UiPropertyValue
{
Kind = UiPropertyKind.Integer,
IntegerValue = 2000,
};
direct.Properties.Values[0x3Cu] = new UiPropertyValue
{
Kind = UiPropertyKind.Integer,
IntegerValue = 2000,
};
info.States[UiStateInfo.DirectStateId] = direct;
return info;
}
}