fix(app,runtime,headless): Campaign CC slice CC4 review-fix round — F1-F12
Dual-lens review of CC4 (0e71d3b8) returned architectural FAIL (F1, F6)
and retail-fidelity PASS-with-reservations (F2, F3, F4), plus LOW
findings F5, F7-F12. F13 (TS-82's merge collision with campaign-cc6a) is
merge mechanics for the orchestrator, not addressed here.
F1 (HIGH, blocking): CharacterCreationUiController never released
UiRoot.FixedCanvasSize, on a FALSE premise that CharacterManagementUi-
Controller does a per-tick set (it does not — it sets once on activation
and nulls on Deactivate/Dispose). Root cause: RuntimeCharacterCreation-
State had no CompleteEnter() analogue to RuntimeCharacterSelectionState's,
so the creation view reported IsActive=true for an entire in-world
session. Added CompleteEnter(), wired at both LiveSessionController
in-world edges (StartCore, EnterHighlightedCore); made Open/Close/
Deactivate/Dispose set/null the canvas symmetrically; corrected the false
comment and ledger claim; added FixedCanvasSize test coverage.
F2 (MEDIUM-HIGH, blocking): the attribute-slider scalar mapping was not
retail's. Fixed display to value/100f (UpdateAttributeValues @
0x0048251d) and the drag inverse to truncate+clamp-low-only, no rescale
(ListenToElementMessage @ 0x004829c0, independently re-verified against
the decomp). Added tests at scalar 0.5/0.0 plus a display-direction test.
F3 (MEDIUM, blocking): ported the unported heritage-button tab-restore
arm (ListenToElementMessage @ 0x004e9450) — SHOW/HIDE id sets independently
re-derived from the decomp, including the genuine Lugian (0x100005f1)
no-restore quirk, reproduced faithfully. Wired via a new HeritagePage
click callback; added restore + quirk tests.
F4 (MEDIUM): ported SetTown's (@ 0x0047c360) separate per-town page-root
state literal (Holtburg->0x10000034 etc.), independently re-derived from
the decomp's tail-merged branches; wired via the existing
IUiDatStateful.TrySetRetailState seam; added a test.
F5 (MEDIUM): softened AD-103's unmeasured pixel-equivalence claim.
F6 (MEDIUM, blocking): DECISION — install ChargenOptions in the headless
content path (chosen over marking headless creation out-of-scope).
HeadlessSessionHost now calls InstallOptions off the shared content
lease's Dats, beside the existing InstallSpellMetadata call.
F7: AP-213 already named the label format and click/double-click
substitution explicitly on inspection — no edit needed.
F8: AP-212 now names all six DoRandom primitives with a known landing site.
F9: AD-101 retirement corrected to precede CC5's Finish un-ghosting.
F10: merged ItemAppraisalTextFormatter's duplicate <summary> block.
F11: fixed TS-82's wrong AP-211 cross-reference.
F12: cached the chargen DatStringResolver once per composition instead of
per ResolveText call.
Runtime 1713/0, App 5125/13 skips (+8 new tests), Headless 165/0, full
solution Release build green. Live-DAT probes 7/7 under
ACDREAM_PROBE_LIVE_MOUNT=1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0e71d3b829
commit
ec854db045
12 changed files with 419 additions and 30 deletions
|
|
@ -147,8 +147,12 @@ internal sealed class CharacterCreationProfessionPage : IDisposable
|
|||
foreach ((ChargenAttributeId attribute, SliderWidgets widgets) in _sliders)
|
||||
{
|
||||
int value = GetAttribute(snapshot.Attributes, attribute);
|
||||
float scalar = (value - ChargenAttributeMath.AttributeMin)
|
||||
/ (float)(ChargenAttributeMath.AttributeMax - ChargenAttributeMath.AttributeMin);
|
||||
// gmCGProfessionPage::UpdateAttributeValues @ 0x0048251d:
|
||||
// SetAttribute_Float(pSlider, 0x86, value * 0.00999999978f) —
|
||||
// scalar = value/100, NOT (value-AttributeMin)/(AttributeMax-
|
||||
// AttributeMin). Review fix round F2 (2026-08-15): the earlier
|
||||
// [10,100]<->[0,1] normalization here did not match retail.
|
||||
float scalar = value / 100f;
|
||||
widgets.Slider?.SetScalarPosition(scalar);
|
||||
widgets.Value?.SetText(value.ToString(CultureInfo.InvariantCulture));
|
||||
if (widgets.Lock is { } lockButton)
|
||||
|
|
@ -211,14 +215,21 @@ internal sealed class CharacterCreationProfessionPage : IDisposable
|
|||
_bindings.SelectTemplate(templateIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// gmCGProfessionPage::ListenToElementMessage @ 0x004829c0, the
|
||||
/// scrollbar-drag case (relative id 0x100002ee, idMessage 0xa):
|
||||
/// <c>ebx = _ftol2(param*100); if (ebx < 0xa) ebx = 0xa;
|
||||
/// SetAttribValue(this, parent, ebx)</c> — truncate (not round) the
|
||||
/// scalar times 100, clamp LOW only to 10, with NO upper clamp/rescale.
|
||||
/// Review fix round F2 (2026-08-15): the earlier
|
||||
/// AttributeMin+Round(scalar*(Max-Min)) formula here did not match
|
||||
/// retail (it only happened to agree with retail at scalar=1).
|
||||
/// </summary>
|
||||
private void SetAttributeFromScalar(ChargenAttributeId attribute, float scalar)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
int value = ChargenAttributeMath.AttributeMin
|
||||
+ (int)MathF.Round(
|
||||
scalar * (ChargenAttributeMath.AttributeMax - ChargenAttributeMath.AttributeMin),
|
||||
MidpointRounding.AwayFromZero);
|
||||
int value = Math.Max(ChargenAttributeMath.AttributeMin, (int)(scalar * 100f));
|
||||
_bindings.SetAttribute(attribute, value);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue