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>
This commit is contained in:
parent
36c14902a8
commit
0baebce262
10 changed files with 87 additions and 19 deletions
|
|
@ -24,6 +24,32 @@ What does NOT go here:
|
|||
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
|
||||
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
|
||||
|
||||
## #401 — RetailUi should default ON (opt-out), not per-path forced
|
||||
|
||||
**Status:** OPEN (product-default decision)
|
||||
**Severity:** MEDIUM (recurrence risk)
|
||||
**Filed:** 2026-08-15 (Campaign LA gate-round-2 batch review, F2)
|
||||
**Component:** `src/AcDream.App/RuntimeOptions.cs`
|
||||
|
||||
`RetailUi` still parses opt-IN from `ACDREAM_RETAIL_UI` (default false), and
|
||||
`6e1c0967` forces it true on exactly one call site (the session-config
|
||||
launch path). Any other product entry point — including CLAUDE.md's
|
||||
documented plain `dotnet run` dev launch — still boots world rendering with
|
||||
zero interface, the same trap one caller later. The ImGui frontend is gone
|
||||
(Campaign V), so `RetailUi == false` means "no UI at all"; the review
|
||||
confirmed nothing legitimately needs that in a product or test path.
|
||||
|
||||
**Fix direction:** invert the flag — retail UI on by default,
|
||||
`ACDREAM_RETAIL_UI=0` as the dev opt-OUT — and delete the per-path forcing
|
||||
in `RuntimeOptions.FromSessionConfig`. Sweep launch scripts/docs
|
||||
(CLAUDE.md's launch command, test-script env listings) for stale
|
||||
`ACDREAM_RETAIL_UI=1` mentions in the same change. Also pin the currently
|
||||
untested "explicit `ACDREAM_RETAIL_UI=0` alongside a session config is
|
||||
ignored" behavior — or make the inversion moot it.
|
||||
|
||||
**Acceptance:** every launch path shows the retail UI unless explicitly
|
||||
opted out; the forcing is gone; docs updated.
|
||||
|
||||
## #400 — Character select: Credits button is ghosted; retail opens gmCreditsUI
|
||||
|
||||
**Status:** OPEN (post-LA polish)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -58,8 +58,9 @@ public sealed class GameplayConfirmationController : IDisposable
|
|||
? request.Message + " Continue?"
|
||||
: _composeMessage?.Invoke(request.Type, request.Message)
|
||||
?? request.Message;
|
||||
var data = RetailDialogData.Confirmation(message)
|
||||
.Set(RetailDialogProperty.ElementAttribute40, true);
|
||||
// ElementAttribute40 now comes from RetailDialogData.Confirmation
|
||||
// itself (batch review F5 — retail's confirmation builders all set it).
|
||||
var data = RetailDialogData.Confirmation(message);
|
||||
_dialogContext = _dialogs.MakeDialog(data);
|
||||
return _dialogContext != 0u;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,11 +108,16 @@ public sealed class RetailDialogData
|
|||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>Type-1 confirmation data. Sets element attribute 0x40 — retail's
|
||||
/// own confirmation builders do (e.g. <c>MakeConfirmExitDialog @0x004ed250</c>
|
||||
/// writes 0x8E=1, 0xAC=1, 0xC5=message), same as the Wait/TextInput factories
|
||||
/// below (gate-round-2 batch review F5).</summary>
|
||||
public static RetailDialogData Confirmation(string message)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(message);
|
||||
return new RetailDialogData()
|
||||
.Set(RetailDialogProperty.Type, RetailDialogType.Confirmation)
|
||||
.Set(RetailDialogProperty.ElementAttribute40, true)
|
||||
.Set(RetailDialogProperty.Message, message);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,10 +95,15 @@ internal sealed class RetailWaitDialogView : IRetailDialogView
|
|||
|
||||
private void SizeAndCenter()
|
||||
{
|
||||
// Center against the layout space (fixed canvas while a pre-world
|
||||
// screen is active) — gate-round-2 batch review F1: this was the ONE
|
||||
// dialog view the 0a7dc7d6 sweep missed, and it fires on ENTER (the
|
||||
// char screen's primary action), centering off the visible canvas.
|
||||
var space = _host.EffectiveCanvasSize;
|
||||
Root.Left = 0f;
|
||||
Root.Top = 0f;
|
||||
Root.Width = _host.Width;
|
||||
Root.Height = _host.Height;
|
||||
Root.Width = space.X;
|
||||
Root.Height = space.Y;
|
||||
_popup.Left = MathF.Round((Root.Width - _popup.Width) * 0.5f);
|
||||
_popup.Top = MathF.Round((Root.Height - _popup.Height) * 0.5f);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,10 +65,14 @@ public sealed class UiRoot : UiElement
|
|||
|
||||
private (int x, int y) MapWindowToCanvas(int x, int y)
|
||||
{
|
||||
// Truncate, not round (batch review F6): rounding maps the window's
|
||||
// last column/row one past the canvas's last valid coordinate
|
||||
// (1919/2.4 → 800, past 799), creating a 1px dead band at the far
|
||||
// right/bottom edge. Truncation maps 0..1919 onto 0..799 exactly.
|
||||
Vector2 scale = CanvasScale;
|
||||
return scale == Vector2.One
|
||||
? (x, y)
|
||||
: ((int)MathF.Round(x / scale.X), (int)MathF.Round(y / scale.Y));
|
||||
: ((int)(x / scale.X), (int)(y / scale.Y));
|
||||
}
|
||||
|
||||
// ── Device-level state ───────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -768,7 +768,11 @@ public sealed class LiveSessionController
|
|||
// ServerNameReceived subscription exists — so the durable field is
|
||||
// read here exactly like the roster above (gate-round-2 live fix:
|
||||
// the event-only wiring left the World box empty against ACE).
|
||||
if (_operations.GetServerInfo(session) is { } serverInfo)
|
||||
// IsCurrent-gated like every neighbouring step (batch review F8):
|
||||
// a generation flip here must not write the old server's name
|
||||
// into the new generation's state.
|
||||
if (IsCurrent(scope, generation)
|
||||
&& _operations.GetServerInfo(session) is { } serverInfo)
|
||||
CharacterSelectionState.ApplyWorldName(serverInfo.WorldName);
|
||||
|
||||
// Campaign LA slice LA2: the probe short-circuit lands here —
|
||||
|
|
|
|||
|
|
@ -261,14 +261,15 @@ public sealed class CharacterManagementLiveDatTests
|
|||
/// <c>UIElement::UpdateForParentSizeChange</c>, acclient 0x00462640). Retail's
|
||||
/// own edge-anchor resize mechanism therefore NEVER touches this element's size;
|
||||
/// it stays a fixed 800x600 rect in retail's own widget tree. This is the pivot
|
||||
/// fact behind <see cref="AcDream.App.UI.Layout.UiDatElement.StretchOwnBackgroundToFill"/>:
|
||||
/// since the dat itself asks for no resize, whatever makes the char-select scene
|
||||
/// fill an arbitrary window resolution in retail (background AND buttons AND
|
||||
/// listbox together) cannot be a per-element anchor/draw-mode difference — it has
|
||||
/// to be an out-of-band presentation-time scale of the whole fixed-size frame.
|
||||
/// acdream instead resizes the MOUNTED root itself (CharacterManagementUiController's
|
||||
/// constructor) to reach the same visual fill, which is why the background needs
|
||||
/// its own explicit stretch flag rather than an authored draw-mode bit.
|
||||
/// fact behind the completed AD-98 substitution
|
||||
/// (<see cref="AcDream.App.UI.UiRoot.FixedCanvasSize"/>): since the dat itself
|
||||
/// asks for no resize, whatever makes the char-select scene fill an arbitrary
|
||||
/// window resolution in retail (background AND buttons AND listbox together)
|
||||
/// cannot be a per-element anchor/draw-mode difference — it has to be an
|
||||
/// out-of-band presentation-time scale of the whole fixed-size frame. acdream
|
||||
/// therefore keeps the mounted root at its authored 800x600 extent and
|
||||
/// stretches the ENTIRE canvas — widgets, glyphs, art — as one unit at the
|
||||
/// renderer's quad chokepoint, with inverse mouse mapping.
|
||||
/// </summary>
|
||||
[InstalledDatFact]
|
||||
public void RootAuthorsNoEdgeAnchors_RetailNeverResizesItSelf()
|
||||
|
|
|
|||
|
|
@ -511,6 +511,13 @@ public sealed class CharacterManagementUiControllerTests
|
|||
Assert.Equal(0u, controller.OperationWaitContext);
|
||||
Assert.False(environment.Dialogs.IsOpen);
|
||||
|
||||
// Gate-round-2 batch review F1: the wait dialog must center in CANVAS
|
||||
// space like every other dialog view — it fires on ENTER, the
|
||||
// screen's primary action, and was the one view the EffectiveCanvasSize
|
||||
// sweep missed. The window is bigger than the fixed canvas here.
|
||||
environment.Host.Width = 1920f;
|
||||
environment.Host.Height = 1080f;
|
||||
|
||||
controller.Rows[0].OnClick!();
|
||||
environment.Button(CharacterManagementUiController.EnterElementId).OnClick!();
|
||||
Assert.Equal(1, environment.Runtime.EnterCalls);
|
||||
|
|
@ -518,8 +525,14 @@ public sealed class CharacterManagementUiControllerTests
|
|||
RuntimeCharacterSelectionLifecycle.EnteringWorld,
|
||||
environment.Runtime.View.Snapshot.Lifecycle);
|
||||
Assert.NotEqual(0u, controller.EnterWaitContext);
|
||||
Assert.Equal("Entering World", Message(
|
||||
environment.LastDialog(RetailDialogType.Wait)));
|
||||
ImportedLayout enterWait = environment.LastDialog(RetailDialogType.Wait);
|
||||
Assert.Equal("Entering World", Message(enterWait));
|
||||
Assert.Equal(800f, enterWait.Root.Width);
|
||||
Assert.Equal(600f, enterWait.Root.Height);
|
||||
UiElement enterWaitPopup = Assert.Single(
|
||||
enterWait.Root.Children,
|
||||
static child => child.Visible && child.Width > 0f);
|
||||
Assert.InRange(enterWaitPopup.Left + enterWaitPopup.Width * 0.5f, 399f, 401f);
|
||||
|
||||
environment.Runtime.SetError("That character is unavailable.");
|
||||
controller.Tick();
|
||||
|
|
|
|||
|
|
@ -86,11 +86,20 @@ public class UiRootFixedCanvasTests
|
|||
};
|
||||
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(417, root.MouseY);
|
||||
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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue