acdream/tests/AcDream.App.Tests/UI/UiRootFixedCanvasTests.cs
Erik 0baebce262 fix(ui,runtime): Campaign LA gate-round-2 batch-review fixes F1,F3-F8; file #401
F1 (MUST-FIX): RetailWaitDialogView was the ONE dialog view the 0a7dc7d6
EffectiveCanvasSize sweep missed - the Entering World wait dialog (fires
on ENTER, the char screen primary action) still centered against the raw
window and landed off the visible canvas. Same three-line fix as its three
siblings; the enter-wait test now grows the window over the fixed canvas
and asserts canvas-space centering.

F3/F4: two stale assertions about the DELETED first AD-98 substitution
(the register section-2 header line and the live-DAT oracle test doc) now
describe the completed FixedCanvasSize mechanism - the C4-closeout failure
mode, caught before it cost anything.

F5: RetailDialogData.Confirmation sets ElementAttribute40 itself (retail
MakeConfirmExitDialog writes 0x8E=1, 0xAC=1, 0xC5); the manual set in
GameplayConfirmationController is gone.

F6: MapWindowToCanvas truncates instead of rounding - rounding mapped the
window far edge one past the canvas last valid coordinate, a 1px dead
hit-test band; test updated to truncation semantics + far-edge case.

F7: AD-98 records that the no-letterbox aspect claim has no decomp
citation and is confirmed by the user live gate pass 2026-08-15.

F8: the durable world-name read in StartCore is IsCurrent-gated like every
neighbouring step.

F2 filed as #401 (invert RetailUi to opt-out - product-default decision,
not a gate fix).

App 5100+6 skips, Runtime 1666, green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 12:28:06 +02:00

130 lines
4.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Numerics;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using Xunit;
namespace AcDream.App.Tests.UI;
/// <summary>
/// Campaign LA gate round 2 (register AD-98): retail renders fixed-canvas
/// pre-world screens (char select, authored 800×600) at authored size and
/// stretches the whole composed frame at presentation — its UI blitter has no
/// stretch mode. acdream's equivalent is <see cref="UiRoot.FixedCanvasSize"/>:
/// draw applies one uniform scale at the renderer's quad chokepoint, and the
/// mouse entry points apply the exact inverse so hit-testing lives in canvas
/// space. These tests pin the scale math and the inverse input mapping — the
/// half that, if wrong, makes the user click on art and hit nothing.
/// </summary>
public class UiRootFixedCanvasTests
{
[Fact]
public void CanvasScale_IsOne_WithoutFixedCanvas()
{
var root = new UiRoot { Width = 1920f, Height = 1080f };
Assert.Equal(Vector2.One, root.CanvasScale);
}
[Fact]
public void CanvasScale_IsWindowOverCanvas_WhenFixed()
{
var root = new UiRoot
{
Width = 1920f,
Height = 1080f,
FixedCanvasSize = new Vector2(800f, 600f),
};
Assert.Equal(new Vector2(2.4f, 1.8f), root.CanvasScale);
}
[Fact]
public void CanvasScale_IsOne_ForDegenerateCanvasOrWindow()
{
var zeroCanvas = new UiRoot
{
Width = 1920f,
Height = 1080f,
FixedCanvasSize = new Vector2(0f, 600f),
};
Assert.Equal(Vector2.One, zeroCanvas.CanvasScale);
var zeroWindow = new UiRoot
{
Width = 0f,
Height = 0f,
FixedCanvasSize = new Vector2(800f, 600f),
};
Assert.Equal(Vector2.One, zeroWindow.CanvasScale);
}
/// <summary>
/// The load-bearing inverse: a click at WINDOW coordinates must land on the
/// widget whose authored CANVAS rect the user visually clicked. The button
/// sits at canvas (300,400)+(120×40); at a 1920×1080 window over an 800×600
/// canvas it appears at window (720,720)-(1008,792). Clicking window
/// (860,750) — canvas (358,417) — must click it; clicking window (300,400)
/// — canvas (125,222), visually empty — must not.
/// </summary>
[Fact]
public void MouseInput_MapsWindowCoordsToCanvasSpace()
{
var root = new UiRoot
{
Width = 1920f,
Height = 1080f,
FixedCanvasSize = new Vector2(800f, 600f),
};
int clicks = 0;
var button = new UiButton(
new ElementInfo { Width = 120, Height = 40 },
_ => (0u, 0, 0))
{
Left = 300f,
Top = 400f,
Width = 120f,
Height = 40f,
OnClick = () => clicks++,
};
root.AddChild(button);
// Truncation, not rounding (batch review F6): 860/2.4 = 358.33 → 358,
// 750/1.8 = 416.67 → 416. Rounding mapped the window's far edge one
// past the canvas's last valid coordinate (1919 → 800), losing a 1px
// hit-test band at the right/bottom.
root.OnMouseDown(UiMouseButton.Left, 860, 750);
root.OnMouseUp(UiMouseButton.Left, 860, 750);
Assert.Equal(1, clicks);
Assert.Equal(358, root.MouseX);
Assert.Equal(416, root.MouseY);
// The far window edge maps INSIDE the canvas.
root.OnMouseMove(1919, 1079);
Assert.Equal(799, root.MouseX);
Assert.Equal(599, root.MouseY);
root.OnMouseDown(UiMouseButton.Left, 300, 400);
root.OnMouseUp(UiMouseButton.Left, 300, 400);
Assert.Equal(1, clicks);
}
[Fact]
public void MouseInput_IsUntouched_WithoutFixedCanvas()
{
var root = new UiRoot { Width = 1920f, Height = 1080f };
int clicks = 0;
var button = new UiButton(
new ElementInfo { Width = 120, Height = 40 },
_ => (0u, 0, 0))
{
Left = 300f,
Top = 400f,
Width = 120f,
Height = 40f,
OnClick = () => clicks++,
};
root.AddChild(button);
root.OnMouseDown(UiMouseButton.Left, 360, 420);
root.OnMouseUp(UiMouseButton.Left, 360, 420);
Assert.Equal(1, clicks);
}
}