fix(chargen): Campaign CC gate round 1 Batch B — authored selection states, label state, zoom/swatch feedback
GF-1/GF-8: UiButton now recognizes retail's custom Unselected/Selected radio-pair (0x10000016/0x10000017), bypassing the standard Normal/ Highlight machine that never admitted those state names — .Selected now lights the heritage/template/gender/Face-Clothes rows it was always a no-op for. AP-222/GF-11b: per-state label color/outline (dat 0x1B/0x21) now applies off the REQUESTED retail state id, not the art-gated committed ActiveState — resolves the Appearance spins' current-part highlight (text recolors even though no Highlight art exists on either client) and the Town caption's Normal-to-white swap. GF-11c: UiButton.LabelBox lets a lifted caption with its own authored rect draw there instead of the face-relative offset that's only correct when the label is authored directly on the button (heritage/template family, unchanged). GF-9: wires the real nine companion overlay elements (SetColor's SetVisible mechanism) that swatch clicks were always meant to drive, retiring AP-215 item 1 (the swatch.Selected substitution was a permanent no-op — swatches author no Highlight media at all). GF-10: zoom buttons now set the retail-mirrored mutual-exclusive Highlight/Normal pair on click; InitializePage carries no initial SetState for either button, so both stay at "Normal" until first click. Register: AP-222 retired (mechanism identified and ported), AP-215 narrowed (item 1 retired, item 2 unrelated and unchanged), row count recount corrected 164 (was already one high before this batch). App suite 5282/3 (was 5266/3), Runtime 1735/0 unchanged. Fixture + live- DAT tests only — no graphical client launch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1d9de5e095
commit
7d09821fdc
11 changed files with 1043 additions and 42 deletions
|
|
@ -785,6 +785,52 @@ public sealed class CharacterCreationUiControllerTests
|
|||
Assert.Equal(0, environment.Runtime.AppearanceIndexCallCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GF-9 (Campaign CC gate round 1 Batch B): retail's ACTUAL swatch click
|
||||
/// feedback — exactly one companion overlay visible, tracking the
|
||||
/// current part's own selected color index (<c>SetColor</c>'s
|
||||
/// <c>m_tColorWheel[...][0x10][iCurColor*7]->SetVisible</c>). Drives
|
||||
/// the snapshot directly (the fake binding only records what a click
|
||||
/// SENDS, it doesn't feed it back) to exercise
|
||||
/// <c>RefreshColorAndShadeControls</c>'s own overlay loop end to end.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AppearanceSwatchOverlays_ExactlyOneVisible_TrackingTheCurrentPartsColorIndex()
|
||||
{
|
||||
using var environment = new EnvironmentHarness();
|
||||
environment.Controller.Open();
|
||||
SelectAluvianMale(environment);
|
||||
// Part defaults to Hair on construction — no extra click needed.
|
||||
|
||||
UiElement[] overlays = [.. CharacterCreationAppearancePage.SwatchOverlayIds
|
||||
.Select(environment.Page)];
|
||||
|
||||
// No color selected yet (Unset) -> every overlay hidden.
|
||||
Assert.All(overlays, overlay => Assert.False(overlay.Visible));
|
||||
|
||||
RuntimeCharacterCreationSnapshot snapshot = environment.Runtime.View.Snapshot;
|
||||
environment.Runtime.View.Snapshot = snapshot with
|
||||
{
|
||||
Revision = snapshot.Revision + 1,
|
||||
Appearance = snapshot.Appearance with { HairColor = 1u },
|
||||
};
|
||||
environment.Controller.Tick();
|
||||
|
||||
for (int i = 0; i < overlays.Length; i++)
|
||||
Assert.Equal(i == 1, overlays[i].Visible);
|
||||
|
||||
snapshot = environment.Runtime.View.Snapshot;
|
||||
environment.Runtime.View.Snapshot = snapshot with
|
||||
{
|
||||
Revision = snapshot.Revision + 1,
|
||||
Appearance = snapshot.Appearance with { HairColor = 2u },
|
||||
};
|
||||
environment.Controller.Tick();
|
||||
|
||||
for (int i = 0; i < overlays.Length; i++)
|
||||
Assert.Equal(i == 2, overlays[i].Visible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AppearanceShadeScroll_ScalarChanged_SetsShadeForTheCurrentPart()
|
||||
{
|
||||
|
|
@ -848,6 +894,48 @@ public sealed class CharacterCreationUiControllerTests
|
|||
environment.Button(CharacterCreationAppearancePage.RotateClockwiseId).OnClick!();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GF-10 (Campaign CC gate round 1 Batch B): ports
|
||||
/// <c>gmCGAppearancePage::ZoomIn @0x0047CF00</c>
|
||||
/// (<c>@0x0047d005/0x0047d00f</c>: ZoomInButton -> Highlight(6),
|
||||
/// ZoomOutButton -> Normal(1)) and its <c>ZoomOut</c> mirror
|
||||
/// (<c>@0x0047D050</c>, <c>@0x0047d140/0x0047d14a</c>). Both buttons
|
||||
/// start at their DAT-authored "Normal" default — re-derived from
|
||||
/// <c>InitializePage @0x0047fdd0-0048032e</c>: <c>m_bZoomedIn = 0</c> is
|
||||
/// set at construction (<c>@0x004802c3</c>) but NO explicit initial
|
||||
/// <c>SetState</c> call exists for either zoom button anywhere in
|
||||
/// <c>InitializePage</c>, so this port does not force one either.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AppearanceZoomButtons_ClickPath_TogglesMutualExclusiveHighlightPair()
|
||||
{
|
||||
using var environment = new EnvironmentHarness();
|
||||
environment.Controller.Open();
|
||||
var preview = new FakeChargenPreviewControl();
|
||||
environment.Controller.AppearancePreviewControl = preview;
|
||||
|
||||
UiButton zoomIn = environment.Button(CharacterCreationAppearancePage.ZoomInId);
|
||||
UiButton zoomOut = environment.Button(CharacterCreationAppearancePage.ZoomOutId);
|
||||
|
||||
Assert.Equal("Normal", zoomIn.ActiveState);
|
||||
Assert.Equal("Normal", zoomOut.ActiveState);
|
||||
|
||||
zoomIn.OnClick!();
|
||||
Assert.Equal("Highlight", zoomIn.ActiveState);
|
||||
Assert.Equal("Normal", zoomOut.ActiveState);
|
||||
|
||||
zoomOut.OnClick!();
|
||||
Assert.Equal("Normal", zoomIn.ActiveState);
|
||||
Assert.Equal("Highlight", zoomOut.ActiveState);
|
||||
|
||||
// Re-asserting the SAME direction is idempotent (retail's own early-
|
||||
// return branch when already zoomed in/out — this port doesn't
|
||||
// track m_bZoomedIn, but the RESULT is identical either way).
|
||||
zoomOut.OnClick!();
|
||||
Assert.Equal("Normal", zoomIn.ActiveState);
|
||||
Assert.Equal("Highlight", zoomOut.ActiveState);
|
||||
}
|
||||
|
||||
private static void SelectAluvianMale(EnvironmentHarness environment)
|
||||
{
|
||||
environment.Runtime.SelectHeritageDirect(AluvianId);
|
||||
|
|
@ -2151,6 +2239,11 @@ public sealed class CharacterCreationUiControllerTests
|
|||
|
||||
foreach (uint swatchId in CharacterCreationAppearancePage.SwatchIds)
|
||||
page.Children.Add(ButtonInfo(swatchId));
|
||||
// GF-9: the nine companion overlay elements, live-DAT-measured as
|
||||
// plain Type-3 siblings of the swatches under the color-wheel
|
||||
// container.
|
||||
foreach (uint overlayId in CharacterCreationAppearancePage.SwatchOverlayIds)
|
||||
page.Children.Add(ContainerInfo(overlayId));
|
||||
|
||||
page.Children.Add(ScrollbarInfo(CharacterCreationAppearancePage.ShadeScrollId));
|
||||
page.Children.Add(ContainerInfo(CharacterCreationAppearancePage.GradCircleId));
|
||||
|
|
@ -2166,12 +2259,27 @@ public sealed class CharacterCreationUiControllerTests
|
|||
|
||||
page.Children.Add(ButtonInfo(CharacterCreationAppearancePage.RotateClockwiseId));
|
||||
page.Children.Add(ButtonInfo(CharacterCreationAppearancePage.RotateCounterClockwiseId));
|
||||
page.Children.Add(ButtonInfo(CharacterCreationAppearancePage.ZoomInId));
|
||||
page.Children.Add(ButtonInfo(CharacterCreationAppearancePage.ZoomOutId));
|
||||
// GF-10: unlike the plain ButtonInfo() used above, the zoom buttons
|
||||
// need REAL Normal/Highlight media so AppearanceZoomButtons_
|
||||
// ClickPath_TogglesMutualExclusiveHighlightPair can observe the
|
||||
// actual mutual-exclusive state swap through TrySetRetailState —
|
||||
// live-DAT-measured shape (both start "Normal", both author
|
||||
// Highlight/rollover media).
|
||||
page.Children.Add(ZoomButtonInfo(CharacterCreationAppearancePage.ZoomInId));
|
||||
page.Children.Add(ZoomButtonInfo(CharacterCreationAppearancePage.ZoomOutId));
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
private static ElementInfo ZoomButtonInfo(uint id)
|
||||
{
|
||||
var info = new ElementInfo { Id = id, Type = 1u, Width = 81f, Height = 38f };
|
||||
info.StateMedia["Normal"] = (0x06004D55u, 1);
|
||||
info.StateMedia["Highlight"] = (0x06004D56u, 1);
|
||||
info.DefaultStateName = "Normal";
|
||||
return info;
|
||||
}
|
||||
|
||||
private static ElementInfo SpinInfo(uint id)
|
||||
{
|
||||
var spin = new ElementInfo
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue