diff --git a/src/AcDream.App/UI/Layout/CharacterManagementUiController.cs b/src/AcDream.App/UI/Layout/CharacterManagementUiController.cs index 963d5e23..f42e241a 100644 --- a/src/AcDream.App/UI/Layout/CharacterManagementUiController.cs +++ b/src/AcDream.App/UI/Layout/CharacterManagementUiController.cs @@ -580,6 +580,13 @@ internal sealed class CharacterManagementUiController : IDisposable // remains authoritative and closes it on InWorld/error/reset. EnsureEnterWait(); RuntimeCommandResult result = _bindings.Enter(); + // Enter-world round (2026-08-17): once-per-click outcome line — a + // refused Enter was previously indistinguishable from a click that + // never dispatched (both silent), which cost a full connected-gate + // round to tell apart. + Console.WriteLine(result.Accepted + ? "[UI] character enter accepted" + : $"[UI] character enter rejected status={result.Status}"); if (!result.Accepted) CloseContext(ref _enterWaitContext, suppressCallback: true); InvalidateAndTick(); diff --git a/src/AcDream.App/UI/Testing/RetailUiAutomationProbe.cs b/src/AcDream.App/UI/Testing/RetailUiAutomationProbe.cs index d3ab64c1..f9a76784 100644 --- a/src/AcDream.App/UI/Testing/RetailUiAutomationProbe.cs +++ b/src/AcDream.App/UI/Testing/RetailUiAutomationProbe.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Numerics; using System.Text; using AcDream.Core.Items; @@ -147,7 +148,9 @@ public sealed class RetailUiAutomationProbe { var target = FindByDatElementId(datElementId); if (target is null) return Fail($"element 0x{datElementId:X8} not found"); - _root.OnMouseMove((int)target.CenterX, (int)target.CenterY); + // Same canvas→window conversion as ClickAt — see CanvasToWindow. + (int x, int y) = CanvasToWindow((int)target.CenterX, (int)target.CenterY); + _root.OnMouseMove(x, y); return true; } @@ -282,8 +285,30 @@ public sealed class RetailUiAutomationProbe } } + /// + /// Enter-world round (2026-08-17): element coordinates from the probe's + /// tree walk are CANVAS coordinates, but / + /// OnMouseDown/OnMouseUp take WINDOW coordinates and map + /// window→canvas internally (UiRoot.MapWindowToCanvas). The two + /// spaces are identical on every ordinary screen (no + /// ), which is why every prior probe + /// gate passed — the character-select/chargen screens are the first + /// STRETCHED canvases this apparatus drove, and there the mismatch sent + /// every synthetic click to canvas·(canvas/window), i.e. nowhere near the + /// target element. Element-derived pointer paths therefore convert + /// canvas→window here before touching the root. + /// + private (int x, int y) CanvasToWindow(int x, int y) + { + Vector2 scale = _root.CanvasScale; + return scale == Vector2.One + ? (x, y) + : ((int)MathF.Round(x * scale.X), (int)MathF.Round(y * scale.Y)); + } + private void ClickAt(int x, int y) { + (x, y) = CanvasToWindow(x, y); Advance(16); _root.OnMouseMove(x, y); _root.OnMouseDown(UiMouseButton.Left, x, y); @@ -294,6 +319,8 @@ public sealed class RetailUiAutomationProbe private void DragAt(int startX, int startY, int endX, int endY) { + (startX, startY) = CanvasToWindow(startX, startY); + (endX, endY) = CanvasToWindow(endX, endY); Advance(16); _root.OnMouseMove(startX, startY); _root.OnMouseDown(UiMouseButton.Left, startX, startY);