fix(chargen): Campaign CC CC5 review fix round — F1-F14
Opus dual-lens review of 34e3a534+a975efd1 returned architectural PASS-with-items / retail-fidelity FAIL. Every finding fixed: - F1 (BLOCKER): deleted CharacterCreationSummaryPage's dead _suppressNextFieldEvent latch. UiField.SetText never raises OnFocusLost/OnSubmit, so the latch never had anything genuine to suppress — it stayed armed until the player's own next real commit and silently ate their typed name. - F2: byte-re-derived gmCharGenMainUI::RecvNotice_ CharGenVerificationResponse @0x004e9030's jump table — Pending is an explicit switch case landing on the SAME NameDBDown label as Corrupt/DatabaseDown, and Undef/out-of-range falls through the function's own unsigned-underflow default arm to that identical label. Retail's dispatch has NO silent branch. ApplyCreationResponse now produces a real rejection for Pending/Undef instead of a silent reset; ReconcileDialogs maps them to NameDBDown. Corrects the wrong "retail swallows Pending" claim everywhere it was repeated (plan doc, Core.Net doc comment, Runtime doc comments). - F3: skill rows now use the key/value template with CharGenState::GetSkillScore @0x005C4B50 as the value (ported via the new RetailSkillFormula.CalculateChargenScore / ChargenSkillScoreResolver, wired through a new GetSkillScore binding), not template 0/name-only; bucket headers are unconditional. Writing this fix's own regression test surfaced a second, more severe bug: CharacterCreationSummaryPage never wired _list.TemplateResolver at all, so RebuildListbox has been a silent no-op since CC5 shipped — fixed by threading templateResolver through the page's constructor, matching every sibling UiTemplateListBox owner. - F4: added the missing _errorMessageDialogContext one-outstanding guard to the 0xF643 rejection dialog, matching MakeErrorMessageDialog's own guard @0x004e8cc4 and the other four sibling dialogs' shape (registered in CloseAllDialogs, suppress- callback checked). - F5: the Summary preview camera now seeds/re-derives retail's zoomed-OUT eye (byte-decoded (0,-2.5,0.95) at gmCGSummaryPage:: InitializePage ~0x0047bd14-0x0047bd44) instead of Appearance's zoomed-in default, via a new ChargenPreviewController useZoomedOutEye flag. - F6: retired AP-225 outright — re-derived the ListenToElementMessage length gate is NUL-inclusive, so MaxNameLength=32 was always byte-correct, not merely internally consistent. - F7: amended AP-221 to cover the Summary preview's duplicate one-shot-composition binding gap (CC5 duplicated the pattern instead of closing it). - F8: byte-decoded GetRandomReal @0x00563940's fmul operand at 0x007cd650 — an 8-byte double, not a 4-byte float — is EXACTLY 1.0/32767.0, not 1/32768. Added RollShadeLocked (_random.Next(32768) * (1.0/32767.0)) and switched all six shade rolls onto it. - F9: evaluated porting retail's exact empty-name-commit no-op (NUL-inclusive length==1 skips SetName entirely) and rejected it — it would fight the F1 field-sync model by spontaneously reverting an emptied field on the next unrelated revision bump. Kept the clear, documented the tradeoff, filed AP-227. - F11: filed AP-226 documenting retail's static pcProfessions/pcGender/ pcHeritage/pcTown label tables versus acdream's DAT-sourced labels, including the non-human-heritage-renders-bare-"Heritage:" retail quirk. - F12: added exclude-current determinism (count-2 lists), Random- clears-name, repeat-identical-rejection-reshows, and RebuildListbox content tests (the last one found F3's TemplateResolver bug). - F13: threaded an optional Random through GameRuntimeDependencies -> LiveSessionController -> RuntimeCharacterCreationState, matching the existing TimeProvider injection shape, closing the Slice-K determinism hazard on a bot-reachable Randomize* command family. - F14: RandomizeCharacterLocked now assigns _heritageId unconditionally before the TryGetHeritage gate, matching retail's SetHeritageGroup @0x005C67A0 (mHeritageGroup written before the DAT lookup). Gates: Runtime 1726/0 (was 1722/0), App 5242/3 skips (was 5240/3), Headless 166/0, Core.Net 993/994 (the one failure, NakEmissionTests LossSoak, is a known pre-existing flake — passes standalone), full solution Release build green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a975efd1d5
commit
0c8e1e7df1
14 changed files with 720 additions and 141 deletions
|
|
@ -26,6 +26,7 @@ using AcDream.UI.Abstractions.Input;
|
|||
using AcDream.UI.Abstractions.Panels.Chat;
|
||||
using AcDream.UI.Abstractions.Panels.Vitals;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using Silk.NET.Input;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
|
|
@ -656,6 +657,19 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
|
|||
// outside the lock matches this file's existing pattern
|
||||
// elsewhere (construct once, lock only around Resolve calls).
|
||||
var characterCreationStrings = new DatStringResolver(d.Dats);
|
||||
// CC5 review fix round F3 (2026-08-16): read the global
|
||||
// SkillTable (portal.dat 0x0E000004 — the SAME file
|
||||
// ChargenOptions.GlobalSkillCostsBySkillId's own doc comment and
|
||||
// LiveSessionRuntimeFactory.CreateCharacterBindings already read)
|
||||
// ONCE at composition time, under the DatLock DatCollection's
|
||||
// thread-safety contract requires — mirrors LiveSkillCreditResolver's
|
||||
// own constructor-time load. The resolver itself does no further
|
||||
// DAT access per call (pure SkillFormula arithmetic), so the
|
||||
// Summary page's GetSkillScore binding below needs no lock.
|
||||
SkillTable? chargenSkillTable;
|
||||
lock (d.DatLock)
|
||||
chargenSkillTable = d.Dats.Get<SkillTable>(0x0E000004u);
|
||||
var chargenSkillScoreResolver = new ChargenSkillScoreResolver(chargenSkillTable);
|
||||
var bindings = new RetailUiRuntimeBindings(
|
||||
Host: host,
|
||||
Assets: assets,
|
||||
|
|
@ -1012,6 +1026,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
|
|||
RandomizeCharacter: late.GameRuntime.CharacterCreationRandomizeCharacter,
|
||||
RandomizeAppearance: late.GameRuntime.CharacterCreationRandomizeAppearance,
|
||||
RandomizeClothing: late.GameRuntime.CharacterCreationRandomizeClothing,
|
||||
GetSkillScore: chargenSkillScoreResolver.Resolve,
|
||||
OpenOnStart: d.Options.OpenCharacterCreationOnStart)
|
||||
: null);
|
||||
RetailUiRuntime runtime = lease.Mount(
|
||||
|
|
|
|||
|
|
@ -1112,14 +1112,17 @@ internal sealed class LivePresentationCompositionPhase
|
|||
// (gmCGSummaryPage::InitializePage @0x0047bbf0, confirmed a SEPARATE
|
||||
// instance from the Appearance page's own during the CC6b-MOUNT
|
||||
// review) — same one-shot binding shape as the Appearance preview
|
||||
// immediately above (AP-221's own disposition applies here too: a
|
||||
// DAT/resource read not ready on this exact composition frame means
|
||||
// the Summary preview stays permanently unbound for the session,
|
||||
// same tracked follow-up as the Appearance preview). No zoom/rotate
|
||||
// control surface is wired — retail's Summary page has no such
|
||||
// buttons (only <c>StartAnimation</c>'s idle loop and a fixed 180°
|
||||
// heading), so this controller's ZoomIn/RotateClockwise etc. simply
|
||||
// never get called.
|
||||
// immediately above. Review fix round F7 (2026-08-16): AP-221 is now
|
||||
// AMENDED to cover this second binding explicitly (it originally
|
||||
// named CC5 as the slice that should CLOSE the gap; CC5 duplicated
|
||||
// the pattern here instead) — a DAT/resource read not ready on this
|
||||
// exact composition frame means the Summary preview stays
|
||||
// permanently unbound for the session, same tracked follow-up as
|
||||
// the Appearance preview, now under the same amended row. No
|
||||
// zoom/rotate control surface is wired — retail's Summary page has
|
||||
// no such buttons (only <c>StartAnimation</c>'s idle loop and a
|
||||
// fixed 180° heading), so this controller's ZoomIn/RotateClockwise
|
||||
// etc. simply never get called.
|
||||
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
||||
ChargenPreviewRenderer>? summaryPreviewLease = null;
|
||||
ChargenPreviewController? summaryPreviewController = null;
|
||||
|
|
@ -1162,7 +1165,12 @@ internal sealed class LivePresentationCompositionPhase
|
|||
content.AnimationLoader,
|
||||
summaryCatalog,
|
||||
summaryCatalog,
|
||||
d.DatLock);
|
||||
d.DatLock,
|
||||
// F5 (2026-08-16): the Summary preview is retail's zoomed-
|
||||
// OUT full-body framing (gmCGSummaryPage::InitializePage @
|
||||
// 0x0047bbf0), not the Appearance page's zoomed-in default —
|
||||
// see ChargenPreviewController's own ctor doc comment.
|
||||
useZoomedOutEye: true);
|
||||
interaction.RetainedUi.Runtime.SummaryPreviewControl = summaryPreviewController;
|
||||
bindings.AdoptRelease(
|
||||
"summary preview control",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue