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>
151 lines
5.3 KiB
C#
151 lines
5.3 KiB
C#
namespace AcDream.App.UI.Layout;
|
|
|
|
/// <summary>
|
|
/// Property identifiers consumed by retail's <c>Dialog</c> and
|
|
/// <c>DialogFactory</c> implementations.
|
|
/// </summary>
|
|
public static class RetailDialogProperty
|
|
{
|
|
public const uint Priority = 0x8Du;
|
|
public const uint Type = 0x8Eu;
|
|
public const uint AcceptLabel = 0x90u;
|
|
public const uint RejectLabel = 0x91u;
|
|
public const uint ConfirmationResult = 0x92u;
|
|
public const uint TextInputAcceptLabel = 0x9Au;
|
|
public const uint TextInputRejectLabel = 0x9Bu;
|
|
public const uint TextInputResult = 0x9Cu;
|
|
/// <summary>
|
|
/// When true, <c>Dialog::SetData @ 0x00476BE0</c> sets UIElement boolean
|
|
/// attribute <c>0x40</c>. The Keystone-owned attribute name is unavailable.
|
|
/// </summary>
|
|
public const uint ElementAttribute40 = 0xACu;
|
|
public const uint QueueKey = 0xC3u;
|
|
public const uint Message = 0xC5u;
|
|
public const uint UsageObjectId = 0x1000003Du;
|
|
public const uint TrainSkillId = 0x10000040u;
|
|
public const uint TrainSkillCredits = 0x10000041u;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retail dialog types selected by property <c>0x8E</c> in
|
|
/// <c>DialogFactory::CreateDialog_ @ 0x00477AD0</c>.
|
|
/// </summary>
|
|
public enum RetailDialogType : uint
|
|
{
|
|
Confirmation = 1,
|
|
Wait = 2,
|
|
Message = 3,
|
|
TextInput = 4,
|
|
ConfirmationTextInput = 5,
|
|
Menu = 6,
|
|
ConfirmationMenu = 7,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Modern typed carrier for retail's <c>PropertyCollection</c> dialog contract.
|
|
/// The raw numeric keys remain visible because type-specific presenters and semantic
|
|
/// callbacks both extend the same collection in retail.
|
|
/// </summary>
|
|
public sealed class RetailDialogData
|
|
{
|
|
private readonly Dictionary<uint, object> _values = new();
|
|
|
|
public IReadOnlyDictionary<uint, object> Values => _values;
|
|
|
|
public RetailDialogData Set<T>(uint propertyId, T value) where T : notnull
|
|
{
|
|
_values[propertyId] = value;
|
|
return this;
|
|
}
|
|
|
|
public bool Contains(uint propertyId) => _values.ContainsKey(propertyId);
|
|
|
|
public bool TryGet<T>(uint propertyId, out T value)
|
|
{
|
|
if (_values.TryGetValue(propertyId, out object? raw) && raw is T typed)
|
|
{
|
|
value = typed;
|
|
return true;
|
|
}
|
|
|
|
value = default!;
|
|
return false;
|
|
}
|
|
|
|
public bool GetBoolean(uint propertyId, bool defaultValue = false)
|
|
=> _values.TryGetValue(propertyId, out object? raw)
|
|
? raw switch
|
|
{
|
|
bool value => value,
|
|
byte value => value != 0,
|
|
int value => value != 0,
|
|
uint value => value != 0,
|
|
_ => defaultValue,
|
|
}
|
|
: defaultValue;
|
|
|
|
public uint GetUInt32(uint propertyId, uint defaultValue = 0u)
|
|
=> _values.TryGetValue(propertyId, out object? raw)
|
|
? raw switch
|
|
{
|
|
byte value => value,
|
|
ushort value => value,
|
|
int value when value >= 0 => (uint)value,
|
|
uint value => value,
|
|
Enum value => Convert.ToUInt32(value),
|
|
_ => defaultValue,
|
|
}
|
|
: defaultValue;
|
|
|
|
public string? GetString(uint propertyId)
|
|
=> _values.TryGetValue(propertyId, out object? raw) ? raw as string : null;
|
|
|
|
public RetailDialogData Clone()
|
|
{
|
|
var clone = new RetailDialogData();
|
|
foreach ((uint propertyId, object value) in _values)
|
|
clone._values.Add(propertyId, value);
|
|
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);
|
|
}
|
|
|
|
/// <summary>Type-2 wait dialog data — text-only, no buttons. Sets element
|
|
/// attribute 0x40 like <c>OpenMapWarnDialog @ 0x00488A00</c> does.</summary>
|
|
public static RetailDialogData Wait(string message)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(message);
|
|
return new RetailDialogData()
|
|
.Set(RetailDialogProperty.Type, RetailDialogType.Wait)
|
|
.Set(RetailDialogProperty.ElementAttribute40, true)
|
|
.Set(RetailDialogProperty.Message, message);
|
|
}
|
|
|
|
public static RetailDialogData Message(string message)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(message);
|
|
return new RetailDialogData()
|
|
.Set(RetailDialogProperty.Type, RetailDialogType.Message)
|
|
.Set(RetailDialogProperty.Message, message);
|
|
}
|
|
|
|
public static RetailDialogData ConfirmationTextInput(string message)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(message);
|
|
return new RetailDialogData()
|
|
.Set(RetailDialogProperty.Type, RetailDialogType.ConfirmationTextInput)
|
|
.Set(RetailDialogProperty.ElementAttribute40, true)
|
|
.Set(RetailDialogProperty.Message, message);
|
|
}
|
|
}
|