fix(chargen): Campaign CC gate round 1 closeout — Group 1: real color wheel wiring
Lands Batch G's two STOPPED items, making the real palette-color swatch wheel visually live instead of inert: - UiButton and UiDatElement gain a per-instance Tint property threaded into every existing DrawSprite call (defaults to Vector4.One, so every pre-existing button/element is byte-identical unless a caller sets a non-identity tint). - CharacterCreationAppearancePage now sets Tint directly on each color swatch button and the GradCircle element, replacing the Batch G flat-fill ChargenSwatchColorTile overlay outright — an opaque rectangle drawn on top can never reproduce retail's actual SurfaceWindow::BlitAndColor(..., Blit_Multiply, color) multiply blend, only a genuine per-instance sprite tint can, so the overlay approach is deleted rather than layered under the new mechanism. - CharacterCreationUiController and RetailUiRuntime grow pass-through properties (AppearancePalSetSource/AppearanceClothingTableSource/ AppearancePaletteColorSource) mirroring the existing PreviewControl seam, so LivePresentationComposition can wire a DAT-backed ChargenAppearanceCatalog into the Appearance page (wiring itself lands with the Group 3 commit, since it shares a file with an unrelated F16 fix). Register: AP-216/AP-217 RETIRED (161 -> now further reduced in later commits) — both rows' remaining gaps are closed, not merely narrowed. CharacterCreationAppearancePageSwatchColorTests updated for the new Tint-based assertions (two pre-existing assertions were carried over incorrectly from the old overlay-visibility model and are corrected). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1f6365d3e5
commit
e1d7d09599
7 changed files with 193 additions and 192 deletions
|
|
@ -234,12 +234,9 @@ public sealed class CharacterCreationAppearancePageSwatchColorTests
|
|||
Assert.IsType<UiButton>(UiElement.FindDescendant(
|
||||
pageRoot, CharacterCreationAppearancePage.SwatchIds[index]));
|
||||
|
||||
private static ChargenSwatchColorTile SwatchTile(UiElement pageRoot, int index) =>
|
||||
Assert.IsType<ChargenSwatchColorTile>(Assert.Single(Swatch(pageRoot, index).Children));
|
||||
|
||||
private static ChargenSwatchColorTile GradTile(UiElement pageRoot) =>
|
||||
Assert.IsType<ChargenSwatchColorTile>(Assert.Single(
|
||||
UiElement.FindDescendant(pageRoot, CharacterCreationAppearancePage.GradCircleId)!.Children));
|
||||
private static UiDatElement GradCircle(UiElement pageRoot) =>
|
||||
Assert.IsType<UiDatElement>(UiElement.FindDescendant(
|
||||
pageRoot, CharacterCreationAppearancePage.GradCircleId));
|
||||
|
||||
private static Vector4 ToVector4(ChargenSwatchRgb rgb) => new(rgb.R / 255f, rgb.G / 255f, rgb.B / 255f, 1f);
|
||||
|
||||
|
|
@ -251,13 +248,13 @@ public sealed class CharacterCreationAppearancePageSwatchColorTests
|
|||
|
||||
page.Refresh(view, view.Snapshot);
|
||||
|
||||
Assert.Equal(ToVector4(HairColorA), SwatchTile(root, 0).Color);
|
||||
Assert.True(SwatchTile(root, 0).Visible);
|
||||
Assert.Equal(ToVector4(HairColorB), SwatchTile(root, 1).Color);
|
||||
Assert.True(SwatchTile(root, 1).Visible);
|
||||
// Only two hair colors exist — swatch 2 must be blank.
|
||||
Assert.Null(SwatchTile(root, 2).Color);
|
||||
Assert.False(SwatchTile(root, 2).Visible);
|
||||
Assert.Equal(ToVector4(HairColorA), Swatch(root, 0).Tint);
|
||||
Assert.True(Swatch(root, 0).Visible);
|
||||
Assert.Equal(ToVector4(HairColorB), Swatch(root, 1).Tint);
|
||||
Assert.True(Swatch(root, 1).Visible);
|
||||
// Only two hair colors exist — swatch 2 must be hidden and untinted.
|
||||
Assert.Equal(Vector4.One, Swatch(root, 2).Tint);
|
||||
Assert.False(Swatch(root, 2).Visible);
|
||||
}
|
||||
|
||||
/// <summary>Part change (Hair -> Eyes via the spin's own select-zone
|
||||
|
|
@ -269,14 +266,14 @@ public sealed class CharacterCreationAppearancePageSwatchColorTests
|
|||
var (pal, clothing, colors) = MakeSources();
|
||||
(CharacterCreationAppearancePage page, FakeView view, UiElement root) = BuildPage(pal, clothing, colors);
|
||||
page.Refresh(view, view.Snapshot);
|
||||
Assert.Equal(ToVector4(HairColorA), SwatchTile(root, 0).Color);
|
||||
Assert.Equal(ToVector4(HairColorA), Swatch(root, 0).Tint);
|
||||
|
||||
UiButton eyesSpin = Assert.IsType<UiButton>(
|
||||
UiElement.FindDescendant(root, CharacterCreationAppearancePage.EyesSpinId));
|
||||
eyesSpin.OnClickAt!(180, 10); // select zone — switches _currentPart, no index change.
|
||||
|
||||
Assert.Equal(ToVector4(EyeColorA), SwatchTile(root, 0).Color);
|
||||
Assert.Equal(ToVector4(EyeColorB), SwatchTile(root, 1).Color);
|
||||
Assert.Equal(ToVector4(EyeColorA), Swatch(root, 0).Tint);
|
||||
Assert.Equal(ToVector4(EyeColorB), Swatch(root, 1).Tint);
|
||||
}
|
||||
|
||||
/// <summary>Color change (clicking a different swatch) must retint the
|
||||
|
|
@ -287,9 +284,12 @@ public sealed class CharacterCreationAppearancePageSwatchColorTests
|
|||
var (pal, clothing, colors) = MakeSources();
|
||||
(CharacterCreationAppearancePage page, FakeView view, UiElement root) = BuildPage(pal, clothing, colors);
|
||||
page.Refresh(view, view.Snapshot);
|
||||
// No color selected yet (Unset) — no tint.
|
||||
Assert.Null(GradTile(root).Color);
|
||||
Assert.False(GradTile(root).Visible);
|
||||
// No color selected yet (Unset) — no tint, but the disc's own base
|
||||
// art is still shown (Visible tracks !isEyes only, independent of
|
||||
// whether a real color has been resolved — the disc is never
|
||||
// hidden pending a tint, only Eyes hides it at all).
|
||||
Assert.Equal(Vector4.One, GradCircle(root).Tint);
|
||||
Assert.True(GradCircle(root).Visible);
|
||||
|
||||
Swatch(root, 0).OnClick!();
|
||||
view.Snapshot = view.Snapshot with
|
||||
|
|
@ -297,8 +297,8 @@ public sealed class CharacterCreationAppearancePageSwatchColorTests
|
|||
Appearance = view.Snapshot.Appearance with { HairColor = 0u },
|
||||
};
|
||||
page.Refresh(view, view.Snapshot);
|
||||
Assert.Equal(ToVector4(HairColorA), GradTile(root).Color);
|
||||
Assert.True(GradTile(root).Visible);
|
||||
Assert.Equal(ToVector4(HairColorA), GradCircle(root).Tint);
|
||||
Assert.True(GradCircle(root).Visible);
|
||||
|
||||
Swatch(root, 1).OnClick!();
|
||||
view.Snapshot = view.Snapshot with
|
||||
|
|
@ -306,13 +306,13 @@ public sealed class CharacterCreationAppearancePageSwatchColorTests
|
|||
Appearance = view.Snapshot.Appearance with { HairColor = 1u },
|
||||
};
|
||||
page.Refresh(view, view.Snapshot);
|
||||
Assert.Equal(ToVector4(HairColorB), GradTile(root).Color);
|
||||
Assert.Equal(ToVector4(HairColorB), GradCircle(root).Tint);
|
||||
}
|
||||
|
||||
/// <summary>AP-217: Eyes always blanks the gradient disc's tile — no
|
||||
/// tint is ever shown for Eyes, regardless of the selected eye color.</summary>
|
||||
/// <summary>AP-217: Eyes always hides the gradient disc — no tint is
|
||||
/// ever shown for Eyes, regardless of the selected eye color.</summary>
|
||||
[Fact]
|
||||
public void EyesPart_GradientDiscTileStaysBlank()
|
||||
public void EyesPart_GradientDiscStaysHiddenAndUntinted()
|
||||
{
|
||||
var (pal, clothing, colors) = MakeSources();
|
||||
(CharacterCreationAppearancePage page, FakeView view, UiElement root) = BuildPage(pal, clothing, colors);
|
||||
|
|
@ -326,11 +326,11 @@ public sealed class CharacterCreationAppearancePageSwatchColorTests
|
|||
UiElement.FindDescendant(root, CharacterCreationAppearancePage.EyesSpinId));
|
||||
eyesSpin.OnClickAt!(180, 10);
|
||||
|
||||
Assert.False(GradTile(root).Visible);
|
||||
Assert.Null(GradTile(root).Color);
|
||||
Assert.False(GradCircle(root).Visible);
|
||||
Assert.Equal(Vector4.One, GradCircle(root).Tint);
|
||||
// The swatches themselves still show real eye colors — only the
|
||||
// disc blanks.
|
||||
Assert.Equal(ToVector4(EyeColorA), SwatchTile(root, 0).Color);
|
||||
// disc hides.
|
||||
Assert.Equal(ToVector4(EyeColorA), Swatch(root, 0).Tint);
|
||||
}
|
||||
|
||||
/// <summary>Nose/Mouth/Skin (colorSlot == null): retail still shows
|
||||
|
|
@ -348,19 +348,20 @@ public sealed class CharacterCreationAppearancePageSwatchColorTests
|
|||
UiElement.FindDescendant(root, CharacterCreationAppearancePage.SkinSpinId));
|
||||
skinSpin.OnClickAt!(10, 10); // skin has no arrow zones — every click selects it.
|
||||
|
||||
Assert.Equal(ToVector4(SkinColor), SwatchTile(root, 0).Color);
|
||||
Assert.True(SwatchTile(root, 0).Visible);
|
||||
Assert.Null(SwatchTile(root, 1).Color);
|
||||
Assert.False(SwatchTile(root, 1).Visible);
|
||||
Assert.Equal(ToVector4(SkinColor), GradTile(root).Color);
|
||||
Assert.True(GradTile(root).Visible);
|
||||
Assert.Equal(ToVector4(SkinColor), Swatch(root, 0).Tint);
|
||||
Assert.True(Swatch(root, 0).Visible);
|
||||
Assert.Equal(Vector4.One, Swatch(root, 1).Tint);
|
||||
Assert.False(Swatch(root, 1).Visible);
|
||||
Assert.Equal(ToVector4(SkinColor), GradCircle(root).Tint);
|
||||
Assert.True(GradCircle(root).Visible);
|
||||
}
|
||||
|
||||
/// <summary>Headgear's swatch resolves through the CURRENTLY EQUIPPED
|
||||
/// garment's own ClothingTable — an Unset headgear style (no garment)
|
||||
/// shows no swatches at all.</summary>
|
||||
/// shows no color (the swatch stays visible with its bare authored art,
|
||||
/// untinted).</summary>
|
||||
[Fact]
|
||||
public void HeadgearPart_ResolvesThroughTheEquippedGarment_UnsetShowsNoSwatches()
|
||||
public void HeadgearPart_ResolvesThroughTheEquippedGarment_UnsetShowsNoColor()
|
||||
{
|
||||
var (pal, clothing, colors) = MakeSources();
|
||||
(CharacterCreationAppearancePage page, FakeView view, UiElement root) = BuildPage(pal, clothing, colors);
|
||||
|
|
@ -374,16 +375,21 @@ public sealed class CharacterCreationAppearancePageSwatchColorTests
|
|||
UiElement.FindDescendant(root, CharacterCreationAppearancePage.HeadgearSpinId));
|
||||
headgearSpin.OnClickAt!(180, 10);
|
||||
|
||||
Assert.Equal(ToVector4(HeadgearColorA), SwatchTile(root, 0).Color);
|
||||
Assert.Equal(ToVector4(HeadgearColorA), Swatch(root, 0).Tint);
|
||||
|
||||
// Now un-equip (Unset) and refresh again — no garment, no colors.
|
||||
// The swatch's own Visible stays true (the "beyond count" gate is
|
||||
// the gender's fixed ClothingColors list length, independent of
|
||||
// which garment is equipped — Batch C's already-shipped half); only
|
||||
// the TINT reverts to identity, showing the swatch's bare authored
|
||||
// art with no color.
|
||||
view.Snapshot = view.Snapshot with
|
||||
{
|
||||
Appearance = view.Snapshot.Appearance with { HeadgearStyle = RuntimeCharacterCreationAppearance.Unset },
|
||||
};
|
||||
page.Refresh(view, view.Snapshot);
|
||||
Assert.Null(SwatchTile(root, 0).Color);
|
||||
Assert.False(SwatchTile(root, 0).Visible);
|
||||
Assert.Equal(Vector4.One, Swatch(root, 0).Tint);
|
||||
Assert.True(Swatch(root, 0).Visible);
|
||||
}
|
||||
|
||||
/// <summary>Heritage/gender change must re-source the color computation
|
||||
|
|
@ -395,7 +401,7 @@ public sealed class CharacterCreationAppearancePageSwatchColorTests
|
|||
var (pal, clothing, colors) = MakeSources();
|
||||
(CharacterCreationAppearancePage page, FakeView view, UiElement root) = BuildPage(pal, clothing, colors);
|
||||
page.Refresh(view, view.Snapshot);
|
||||
Assert.Equal(ToVector4(HairColorA), SwatchTile(root, 0).Color);
|
||||
Assert.Equal(ToVector4(HairColorA), Swatch(root, 0).Tint);
|
||||
|
||||
// A second heritage with a DIFFERENT hair-color list.
|
||||
const uint otherHairPalSetId = 0x0F00_00AAu;
|
||||
|
|
@ -409,16 +415,17 @@ public sealed class CharacterCreationAppearancePageSwatchColorTests
|
|||
|
||||
page.Refresh(otherView, otherView.Snapshot);
|
||||
|
||||
Assert.Equal(ToVector4(otherHairColor), SwatchTile(root, 0).Color);
|
||||
Assert.Equal(ToVector4(otherHairColor), Swatch(root, 0).Tint);
|
||||
}
|
||||
|
||||
/// <summary>Before <see cref="CharacterCreationAppearancePage.PalSetSource"/>/
|
||||
/// <see cref="CharacterCreationAppearancePage.ClothingTableSource"/>/
|
||||
/// <see cref="CharacterCreationAppearancePage.PaletteColorSource"/> are
|
||||
/// wired (composition-root STOPPED item), every tile MUST stay
|
||||
/// invisible — the mechanism is fully inert, not a half-broken draw.</summary>
|
||||
/// wired, every swatch/the gradient disc MUST show its bare authored
|
||||
/// art (identity tint) — the mechanism is fully inert, not a
|
||||
/// half-broken draw.</summary>
|
||||
[Fact]
|
||||
public void UnwiredSources_LeaveEveryTileInvisible()
|
||||
public void UnwiredSources_LeaveEverySwatchUntinted()
|
||||
{
|
||||
var view = new FakeView(MakeOptions(HeritageId, MakeGender()), HeritageId);
|
||||
var bindings = new CharacterCreationRuntimeBindings(
|
||||
|
|
@ -440,8 +447,8 @@ public sealed class CharacterCreationAppearancePageSwatchColorTests
|
|||
|
||||
page.Refresh(view, view.Snapshot);
|
||||
|
||||
Assert.False(GradTile(root).Visible);
|
||||
Assert.Equal(Vector4.One, GradCircle(root).Tint);
|
||||
for (int i = 0; i < CharacterCreationAppearancePage.SwatchIds.Length; i++)
|
||||
Assert.False(SwatchTile(root, i).Visible);
|
||||
Assert.Equal(Vector4.One, Swatch(root, i).Tint);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue