feat(chargen): Campaign CC slice CC5 — Summary page, Finish flow, RandomizeCharacter port

Fills TS-82's Summary placeholder with a faithful port of gmCGSummaryPage
(name field with NameInputFilter + the retail commit-on-focus-lost/submit
dispatch + the >32-char ID_CharGen_NameTooLong reject-and-revert path, the
REAL three-row-template listbox confirmed against the installed EoR dat
before writing any page code, and Summary's own independent gmCG3DView
preview instance wired through a second ChargenPreviewController pair
mirroring the Appearance page's exact composition shape).

Ports CharGenState::RandomizeCharacter and its six sub-primitives into
RuntimeCharacterCreationState — not approximated: the RandInt/RollDice
semantics are independently confirmed from both the decompiled RNG bodies
and the CharGenStateVtbl union struct in acclient.h. Three consumers:
the chargen screen's open-roll (retiring AP-214's honest-blank deviation
and reproducing the Appearance page's gender-flip-on-init quirk), the
Summary page's Random button (behind the retail randomize-warning
confirm), and the Appearance page's Random button (narrowing AP-212 to
just Heritage/Profession/Town's still-approximated rolls and Skills'
still-unported RandomizeSkills).

Wires the Finish button (previously ghosted) with retail's NoName/
CreditWarning dialog pair, adds the F12 amendment's HeritageOrGenderUnset
local refusal to TryBeginFinish (register AP-223) as a defensive backstop
now that the screen-open roll normally makes it unreachable, and wires
the four ID_Character_Err_* rejection dialogs for the 0xF643 response
codes CC3 already parsed but nothing displayed.

Register: TS-82 retired, AP-214 retired, AP-212 narrowed, AP-223/224/225
filed (heritage/gender Finish refusal, Summary's two-bucket skill-list
narrowing, the 32-vs-33 name-length threshold reconciliation).

Runtime 1722/0 (was 1713), App 5240/3 skips (was 5223/3), Headless 166/0
unchanged, full solution Release build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-16 00:01:07 +02:00
parent 6114b2dda2
commit 34e3a534be
22 changed files with 2217 additions and 79 deletions

View file

@ -669,6 +669,34 @@ public sealed class RetailUiRuntime : IDisposable
internal bool IsChargenPreviewPageVisible =>
CharacterCreationController?.IsAppearancePageVisible ?? false;
/// <summary>Campaign CC slice CC5: the Summary page's OWN authored
/// viewport (<c>0x10000406</c>) — same one-shot GPU-composition
/// disposition as <see cref="ChargenPreviewViewportWidget"/> (see that
/// property's own doc comment; AP-221 covers both).</summary>
internal UiViewport? SummaryPreviewViewportWidget =>
CharacterCreationController?.SummaryViewport;
/// <summary>Campaign CC slice CC5: the Summary preview's late-bound
/// control surface. No zoom/rotate buttons bind against it (retail's
/// Summary page has none) — the composition root assigns it purely so
/// <see cref="AcDream.App.Rendering.ChargenPreviewController.Rebuild"/>
/// gets driven per-selection-change the same way the Appearance
/// preview's is.</summary>
internal AcDream.App.Rendering.IChargenPreviewControl? SummaryPreviewControl
{
get => CharacterCreationController?.SummaryPreviewControl;
set
{
if (CharacterCreationController is { } controller)
controller.SummaryPreviewControl = value;
}
}
/// <summary>Campaign CC slice CC5: whether the Summary page
/// (specifically) is the one currently showing.</summary>
internal bool IsSummaryPreviewPageVisible =>
CharacterCreationController?.IsSummaryPageVisible ?? false;
public static RetailUiRuntime Mount(RetailUiRuntimeBindings bindings)
{
ArgumentNullException.ThrowIfNull(bindings);
@ -3977,14 +4005,38 @@ public sealed class RetailUiRuntime : IDisposable
}
string? exitWarning;
string? noNameWarning;
string? creditWarning;
string? randomizeWarning;
string? nameTooLong;
lock (_bindings.Assets.DatLock)
{
exitWarning = ResolveCharacterManagementString(
strings,
stringTableId,
"ID_CharGen_ExitWarning");
noNameWarning = ResolveCharacterManagementString(
strings,
stringTableId,
"ID_CharGen_NoNameWarning");
creditWarning = ResolveCharacterManagementString(
strings,
stringTableId,
"ID_CharGen_CreditWarning");
randomizeWarning = ResolveCharacterManagementString(
strings,
stringTableId,
"ID_CharGen_RandomizeWarning");
nameTooLong = ResolveCharacterManagementString(
strings,
stringTableId,
"ID_CharGen_NameTooLong");
}
if (exitWarning is null)
if (exitWarning is null
|| noNameWarning is null
|| creditWarning is null
|| randomizeWarning is null
|| nameTooLong is null)
{
Console.WriteLine(
"[UI] character creation: required retail strings are unavailable.");
@ -4009,7 +4061,8 @@ public sealed class RetailUiRuntime : IDisposable
layoutId,
layout,
ResolveTemplate,
new CharacterCreationUiController.DialogStrings(exitWarning));
new CharacterCreationUiController.DialogStrings(
exitWarning, noNameWarning, creditWarning, randomizeWarning, nameTooLong));
}
private void MountItemCooldowns()