diff --git a/src/AcDream.App/UI/Layout/CharacterCreationAppearancePage.cs b/src/AcDream.App/UI/Layout/CharacterCreationAppearancePage.cs
index 813f242f..cadde2ae 100644
--- a/src/AcDream.App/UI/Layout/CharacterCreationAppearancePage.cs
+++ b/src/AcDream.App/UI/Layout/CharacterCreationAppearancePage.cs
@@ -73,20 +73,29 @@ namespace AcDream.App.UI.Layout;
///
///
/// The real color wheel (Campaign CC gate round 1 Batch G, R2-5,
-/// register AP-216/AP-217): retail's DoColorSpots @0x0047d850 /
-/// DoGradDisk @0x0047da90 paint each swatch and the gradient disc
-/// with an ACTUAL representative color sampled from the real DAT palette
-/// data (AcDream.Core.CharGen.ChargenSwatchColorResolver ports the
+/// register AP-216/AP-217 — CLOSEOUT (Group 1) makes it visually live):
+/// retail's DoColorSpots @0x0047d850 / DoGradDisk @0x0047da90
+/// paint each swatch and the gradient disc with an ACTUAL representative
+/// color sampled from the real DAT palette data
+/// (AcDream.Core.CharGen.ChargenSwatchColorResolver ports the
/// computation — see its own doc for the two color-source shapes and the
-/// clothing PalSet lookup). /
-/// / are
-/// late-bound composition seams (same pattern as )
-/// a DAT-backed catalog wires in after construction; the
-/// children painted over each swatch/the gradient disc are this batch's
-/// rendering primitive — see that class's own doc for why it is a flat
-/// color fill (a documented approximation of retail's actual recolored-
-/// sprite blit) and the STOPPED shared-file edit that would upgrade it to
-/// a genuine texture tint.
+/// clothing PalSet lookup) via a genuine multiplicative sprite tint —
+/// retail's own SurfaceWindow::BlitAndColor(..., Blit_Multiply,
+/// color). //
+/// are late-bound composition seams (same
+/// pattern as ) a DAT-backed catalog wires in
+/// after construction (CharacterCreationUiController.AppearancePalSetSource
+/// etc., assigned once by LivePresentationComposition alongside the
+/// existing AppearancePreviewControl wiring). Each swatch
+/// () and the gradient disc (,
+/// resolved via ) now set their OWN
+/// / directly —
+/// the earlier flat-fill ChargenSwatchColorTile overlay (Batch G's
+/// documented approximation, since neither widget exposed a tint hook yet)
+/// is retired: a flat opaque rectangle drawn ON TOP of a sprite can never
+/// reproduce a multiply blend, only a genuine per-instance sprite tint can,
+/// so this closeout replaces the overlay outright rather than layering a
+/// tint UNDER it.
///
///
internal sealed class CharacterCreationAppearancePage : IDisposable
@@ -193,17 +202,13 @@ internal sealed class CharacterCreationAppearancePage : IDisposable
private readonly UiButton? _rotateCounterClockwise;
private readonly UiButton? _zoomIn;
private readonly UiButton? _zoomOut;
- private readonly UiElement? _gradCircle;
- /// R2-5: one flat-color tile per swatch, added as an EXTRA
- /// child of the swatch it decorates (see 's
- /// own doc) — null wherever the matching entry
- /// itself is null (nothing to attach to).
- private readonly ChargenSwatchColorTile?[] _swatchColorTiles = new ChargenSwatchColorTile?[SwatchIds.Length];
-
- /// R2-5: the gradient disc's own tint tile, an extra child of
- /// .
- private readonly ChargenSwatchColorTile? _gradCircleTile;
+ /// The gradient disc (0x1000030e) — Type 3 in the
+ /// authored dat, so (not the base
+ /// ) is what resolves;
+ /// typed concretely (post-closeout Group 1) so
+ /// can set directly.
+ private readonly UiDatElement? _gradCircle;
private Choice _currentChoice = Choice.Face;
private Part _currentPart = Part.Hair;
@@ -219,15 +224,13 @@ internal sealed class CharacterCreationAppearancePage : IDisposable
/// R2-5 late-bound seams (same pattern as
/// above) for the real color-wheel mechanism — null (the default)
/// leaves every swatch/the gradient disc showing ONLY its authored
- /// static art, i.e. this page's pre-Batch-G behavior, until a
- /// composition root supplies a DAT-backed
- /// AcDream.Content.CharGen.ChargenAppearanceCatalog (which
- /// already implements all three interfaces) for these three
- /// properties, mirroring how itself gets
- /// wired in from outside this class. STOPPED (Batch G): that
- /// assignment is a 3-line addition to
- /// CharacterCreationUiController.cs, outside this batch's file
- /// contract — see the batch's handoff notes.
+ /// static art, i.e. this page's pre-Batch-G behavior. Closeout Group 1
+ /// wires a DAT-backed AcDream.Content.CharGen.ChargenAppearanceCatalog
+ /// (which already implements all three interfaces) into these three
+ /// properties via CharacterCreationUiController.AppearancePalSetSource/
+ /// AppearanceClothingTableSource/AppearancePaletteColorSource,
+ /// mirroring how itself gets wired in from
+ /// outside this class.
///
internal IChargenPalSetSource? PalSetSource { get; set; }
internal IChargenClothingTableSource? ClothingTableSource { get; set; }
@@ -280,18 +283,6 @@ internal sealed class CharacterCreationAppearancePage : IDisposable
int index = i;
swatch.OnClick = () => SelectColor(index);
_swatches[i] = swatch;
-
- // R2-5: an extra CHILD tile, sized to exactly cover the
- // swatch's own face — see ChargenSwatchColorTile's own doc for
- // why this is a flat fill rather than a recolored sprite, and
- // for why ClickThrough there keeps this from ever swallowing
- // the swatch's own click.
- var tile = new ChargenSwatchColorTile
- {
- Left = 0f, Top = 0f, Width = swatch.Width, Height = swatch.Height,
- };
- swatch.AddChild(tile);
- _swatchColorTiles[i] = tile;
}
for (int i = 0; i < SwatchOverlayIds.Length; i++)
@@ -301,15 +292,7 @@ internal sealed class CharacterCreationAppearancePage : IDisposable
if (_shadeScroll is not null)
_shadeScroll.ScalarChanged = SetShadeFromScalar;
- _gradCircle = Find(pageRoot, GradCircleId);
- if (_gradCircle is not null)
- {
- _gradCircleTile = new ChargenSwatchColorTile
- {
- Left = 0f, Top = 0f, Width = _gradCircle.Width, Height = _gradCircle.Height,
- };
- _gradCircle.AddChild(_gradCircleTile);
- }
+ _gradCircle = Find(pageRoot, GradCircleId);
Viewport = Find(pageRoot, ViewportId);
@@ -777,12 +760,13 @@ internal sealed class CharacterCreationAppearancePage : IDisposable
continue;
bool visible = i < displayCount;
swatch.Visible = visible;
- if (_swatchColorTiles[i] is { } tile)
- {
- ChargenSwatchRgb? rgb = visible ? swatchColors[i] : null;
- tile.Color = rgb is { } c ? ToTintColor(c) : null;
- tile.Visible = rgb is not null;
- }
+ // Closeout Group 1: a genuine multiplicative sprite tint on the
+ // swatch's own authored spot art (UiButton.Tint), replacing the
+ // Batch G flat-fill overlay. Vector4.One (identity) reproduces
+ // the swatch's bare authored art untouched — both "no color data
+ // yet" (sources unwired) and "beyond this part's color count".
+ ChargenSwatchRgb? rgb = visible ? swatchColors[i] : null;
+ swatch.Tint = rgb is { } c ? ToTintColor(c) : Vector4.One;
}
// AP-217 (Batch C PARTIAL -> Batch G, R2-5, FULL):
@@ -798,18 +782,17 @@ internal sealed class CharacterCreationAppearancePage : IDisposable
{
bool isEyes = _currentPart == Part.Eyes;
_gradCircle.Visible = !isEyes;
- if (_gradCircleTile is { } gradTile)
- {
- int gradIndex = isEyes
- ? -1
- : colorSlot is null
- ? 0
- : (int)ColorCurrent(_currentPart, snapshot.Appearance);
- ChargenSwatchRgb? gradColor =
- gradIndex >= 0 && gradIndex < swatchColors.Length ? swatchColors[gradIndex] : null;
- gradTile.Color = gradColor is { } gc ? ToTintColor(gc) : null;
- gradTile.Visible = !isEyes && gradColor is not null;
- }
+ // Closeout Group 1: same Tint mechanism as the swatches above —
+ // the gradient disc's own authored art is multiplied by the
+ // currently-selected swatch's color instead of an overlay child.
+ int gradIndex = isEyes
+ ? -1
+ : colorSlot is null
+ ? 0
+ : (int)ColorCurrent(_currentPart, snapshot.Appearance);
+ ChargenSwatchRgb? gradColor =
+ gradIndex >= 0 && gradIndex < swatchColors.Length ? swatchColors[gradIndex] : null;
+ _gradCircle.Tint = gradColor is { } gc ? ToTintColor(gc) : Vector4.One;
}
ChargenShadeSlot? shadeSlot = ShadeSlotFor(_currentPart);
diff --git a/src/AcDream.App/UI/Layout/CharacterCreationUiController.cs b/src/AcDream.App/UI/Layout/CharacterCreationUiController.cs
index d736bf57..b728e6ee 100644
--- a/src/AcDream.App/UI/Layout/CharacterCreationUiController.cs
+++ b/src/AcDream.App/UI/Layout/CharacterCreationUiController.cs
@@ -363,6 +363,29 @@ internal sealed class CharacterCreationUiController : IDisposable
set => _appearancePage.PreviewControl = value;
}
+ /// Campaign CC gate round 1 closeout (Group 1, R2-5): the same
+ /// late-bound pattern as above,
+ /// for the real color-wheel/swatch-color mechanism's three DAT-backed
+ /// seams — see 's
+ /// own doc comment.
+ internal IChargenPalSetSource? AppearancePalSetSource
+ {
+ get => _appearancePage.PalSetSource;
+ set => _appearancePage.PalSetSource = value;
+ }
+
+ internal IChargenClothingTableSource? AppearanceClothingTableSource
+ {
+ get => _appearancePage.ClothingTableSource;
+ set => _appearancePage.ClothingTableSource = value;
+ }
+
+ internal IChargenPaletteColorSource? AppearancePaletteColorSource
+ {
+ get => _appearancePage.PaletteColorSource;
+ set => _appearancePage.PaletteColorSource = value;
+ }
+
/// Gates the Appearance preview's per-frame work on whether
/// that specific page — AND the whole chargen screen — is the one
/// currently showing. Close() only ever hides ,
diff --git a/src/AcDream.App/UI/Layout/ChargenSwatchColorTile.cs b/src/AcDream.App/UI/Layout/ChargenSwatchColorTile.cs
deleted file mode 100644
index 069ad825..00000000
--- a/src/AcDream.App/UI/Layout/ChargenSwatchColorTile.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using System.Numerics;
-
-namespace AcDream.App.UI.Layout;
-
-///
-/// Campaign CC gate round 1 Batch G (R2-5, register AP-216/AP-217): paints
-/// one flat-fill patch of a computed
-/// on top of whatever element it is attached to as a child — the
-/// Appearance page's real-color rendering primitive for the nine color
-/// swatches and the gradient disc.
-///
-///
-/// Why a flat fill, not a recolored sprite (documented approximation):
-/// retail's own mechanism (gmCGAppearancePage::DoColorSpots @0x0047d850
-/// / DoGradDisk @0x0047da90) blits an authored "spot"/gradient
-/// graphic and RECOLORS it in place
-/// (SurfaceWindow::ReplaceColor / BlitAndColor(...,
-/// Blit_Multiply, color)) — a genuine multiplicative texture tint. The
-/// retained-UI sprite pipeline this codebase already has
-/// () DOES carry a per-draw
-/// Vector4 tint parameter that could reproduce that exact multiply
-/// blend, but neither (the nine swatches' own type,
-/// sealed) nor (the gradient disc's own type)
-/// exposes a per-instance tint hook on their EXISTING sprite draw calls —
-/// adding one is a small, precisely-scoped, additive change to those two
-/// shared widget files, outside this batch's file contract (reported as a
-/// STOPPED item; see the batch's own commit message / handoff notes for the
-/// exact diff). Rather than leave the swatches/wheel colorless pending that
-/// follow-up, this class achieves the same OBSERVABLE result — "this
-/// swatch/wheel visibly reflects the real computed color" — the cheapest
-/// way the CURRENT public primitives allow:
-/// is a plain solid-color quad, so the tile reads as a flat color patch
-/// rather than a recolored dot/gradient graphic. It is added as an extra
-/// CHILD of the swatch/disc it decorates (never replacing or subclassing
-/// either sealed/shared type), so it draws strictly ON TOP
-/// (: children paint after their
-/// parent's own OnDraw) without disturbing the underlying element's
-/// own state machine, media, or click handling at all.
-///
-///
-///
-/// defaults to false on the
-/// base class, so this MUST be set true by the constructor here (not left
-/// to a caller to remember) — walks
-/// children BEFORE testing the parent, and an opaque, click-absorbing tile
-/// sitting on top of a swatch button would silently eat every click meant
-/// for it.
-///
-///
-internal sealed class ChargenSwatchColorTile : UiElement
-{
- public ChargenSwatchColorTile()
- {
- ClickThrough = true;
- Visible = false;
- }
-
- /// The color to paint, or null to draw nothing this frame
- /// ( is the authoritative on/off switch — callers
- /// should set both together, matching every other swatch-visibility
- /// site in CharacterCreationAppearancePage).
- public Vector4? Color { get; set; }
-
- protected override void OnDraw(UiRenderContext ctx)
- {
- if (Color is { } c && Width > 0f && Height > 0f)
- ctx.DrawFill(0f, 0f, Width, Height, c);
- }
-}
diff --git a/src/AcDream.App/UI/Layout/UiDatElement.cs b/src/AcDream.App/UI/Layout/UiDatElement.cs
index a132e6b1..7e70a5d6 100644
--- a/src/AcDream.App/UI/Layout/UiDatElement.cs
+++ b/src/AcDream.App/UI/Layout/UiDatElement.cs
@@ -179,6 +179,15 @@ public class UiDatElement : UiElement, IUiDatStateful
/// Label color (default white).
public Vector4 LabelColor { get; set; } = Vector4.One;
+ ///
+ /// Campaign CC gate round 1 closeout (Group 1, R2-5): per-instance
+ /// multiplicative sprite tint, threaded into both
+ /// calls this class makes (the runtime-image path and the ordinary
+ /// authored-media path) — same shape and same default-identity
+ /// no-op-for-existing-callers guarantee as .
+ ///
+ public Vector4 Tint { get; set; } = Vector4.One;
+
/// Retail LayoutDesc property 0x21 (two-pass glyph outline,
/// UIElement_Text::SetOutline @0x0046a81c). Seeded in the ctor from the
/// element's effective-default state, same as
@@ -271,7 +280,7 @@ public class UiDatElement : UiElement, IUiDatStateful
0f,
1f,
1f,
- Vector4.One);
+ Tint);
}
DrawLabel(ctx);
return;
@@ -290,7 +299,7 @@ public class UiDatElement : UiElement, IUiDatStateful
// doc). Overlay/Alphablend use the same blit (the sprite shader
// already alpha-blends). No Stretch mode exists in DrawModeType;
// whole-canvas stretching happens at UiRoot.FixedCanvasSize.
- ctx.DrawSprite(tex, 0, 0, Width, Height, 0, 0, Width / tw, Height / th, Vector4.One);
+ ctx.DrawSprite(tex, 0, 0, Width, Height, 0, 0, Width / tw, Height / th, Tint);
}
}
diff --git a/src/AcDream.App/UI/RetailUiRuntime.cs b/src/AcDream.App/UI/RetailUiRuntime.cs
index a62fb663..a6f56b47 100644
--- a/src/AcDream.App/UI/RetailUiRuntime.cs
+++ b/src/AcDream.App/UI/RetailUiRuntime.cs
@@ -680,6 +680,41 @@ public sealed class RetailUiRuntime : IDisposable
}
}
+ /// Campaign CC gate round 1 closeout (Group 1, R2-5): the same
+ /// late-bound pattern as above, for
+ /// the real color-wheel/swatch-color mechanism's three DAT-backed seams
+ /// — see 's
+ /// own doc comment.
+ internal AcDream.Core.CharGen.IChargenPalSetSource? ChargenPalSetSource
+ {
+ get => CharacterCreationController?.AppearancePalSetSource;
+ set
+ {
+ if (CharacterCreationController is { } controller)
+ controller.AppearancePalSetSource = value;
+ }
+ }
+
+ internal AcDream.Core.CharGen.IChargenClothingTableSource? ChargenClothingTableSource
+ {
+ get => CharacterCreationController?.AppearanceClothingTableSource;
+ set
+ {
+ if (CharacterCreationController is { } controller)
+ controller.AppearanceClothingTableSource = value;
+ }
+ }
+
+ internal AcDream.Core.CharGen.IChargenPaletteColorSource? ChargenPaletteColorSource
+ {
+ get => CharacterCreationController?.AppearancePaletteColorSource;
+ set
+ {
+ if (CharacterCreationController is { } controller)
+ controller.AppearancePaletteColorSource = value;
+ }
+ }
+
/// CC6b-MOUNT: whether the Appearance page (specifically) is
/// the one currently showing — false, safely, before the screen mounts.
///
diff --git a/src/AcDream.App/UI/UiButton.cs b/src/AcDream.App/UI/UiButton.cs
index e266710d..bf574e4b 100644
--- a/src/AcDream.App/UI/UiButton.cs
+++ b/src/AcDream.App/UI/UiButton.cs
@@ -153,6 +153,19 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
///
public uint? FaceFileOverride { get; set; }
+ ///
+ /// Campaign CC gate round 1 closeout (Group 1, R2-5): per-instance
+ /// multiplicative sprite tint, threaded into every
+ /// call this class makes (main face, face-segment, drag-acceptance
+ /// overlay) — retail's own SurfaceWindow::BlitAndColor(...,
+ /// Blit_Multiply, color). Default (white,
+ /// full alpha) leaves every DrawSprite call byte-identical to before
+ /// this property existed; only a caller that explicitly sets a
+ /// non-identity tint (e.g. 's
+ /// color-wheel swatches) changes what draws.
+ ///
+ public Vector4 Tint { get; set; } = Vector4.One;
+
/// Additional left inset for left-aligned labels.
public float LabelOffsetX { get; set; } = 3f;
@@ -469,7 +482,7 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
float faceWidth = FaceWidth > 0f ? FaceWidth : Width;
float faceHeight = FaceHeight > 0f ? FaceHeight : Height;
ctx.DrawSprite(tex, FaceLeft, FaceTop, faceWidth, faceHeight,
- 0, 0, faceWidth / tw, faceHeight / th, Vector4.One);
+ 0, 0, faceWidth / tw, faceHeight / th, Tint);
}
}
}
@@ -525,7 +538,7 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
{
var (tex, _, _) = _resolve(dragSprite);
if (tex != 0)
- ctx.DrawSprite(tex, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One);
+ ctx.DrawSprite(tex, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Tint);
}
}
@@ -647,7 +660,7 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
// first reflowed by its own four-edge retail layout policy.
ctx.DrawSprite(texture, rect.X0, rect.Y0, rect.Width, rect.Height,
0f, 0f, (float)rect.Width / textureWidth, (float)rect.Height / textureHeight,
- Vector4.One);
+ Tint);
}
private void AddAvailableStates(ElementInfo mediaInfo)
diff --git a/tests/AcDream.App.Tests/UI/Layout/CharacterCreationAppearancePageSwatchColorTests.cs b/tests/AcDream.App.Tests/UI/Layout/CharacterCreationAppearancePageSwatchColorTests.cs
index b3392e80..fc81db24 100644
--- a/tests/AcDream.App.Tests/UI/Layout/CharacterCreationAppearancePageSwatchColorTests.cs
+++ b/tests/AcDream.App.Tests/UI/Layout/CharacterCreationAppearancePageSwatchColorTests.cs
@@ -234,12 +234,9 @@ public sealed class CharacterCreationAppearancePageSwatchColorTests
Assert.IsType(UiElement.FindDescendant(
pageRoot, CharacterCreationAppearancePage.SwatchIds[index]));
- private static ChargenSwatchColorTile SwatchTile(UiElement pageRoot, int index) =>
- Assert.IsType(Assert.Single(Swatch(pageRoot, index).Children));
-
- private static ChargenSwatchColorTile GradTile(UiElement pageRoot) =>
- Assert.IsType(Assert.Single(
- UiElement.FindDescendant(pageRoot, CharacterCreationAppearancePage.GradCircleId)!.Children));
+ private static UiDatElement GradCircle(UiElement pageRoot) =>
+ Assert.IsType(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);
}
/// 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(
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);
}
/// 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);
}
- /// AP-217: Eyes always blanks the gradient disc's tile — no
- /// tint is ever shown for Eyes, regardless of the selected eye color.
+ /// AP-217: Eyes always hides the gradient disc — no tint is
+ /// ever shown for Eyes, regardless of the selected eye color.
[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);
}
/// 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);
}
/// Headgear's swatch resolves through the CURRENTLY EQUIPPED
/// garment's own ClothingTable — an Unset headgear style (no garment)
- /// shows no swatches at all.
+ /// shows no color (the swatch stays visible with its bare authored art,
+ /// untinted).
[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);
}
/// 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);
}
/// Before /
/// /
/// are
- /// wired (composition-root STOPPED item), every tile MUST stay
- /// invisible — the mechanism is fully inert, not a half-broken draw.
+ /// wired, every swatch/the gradient disc MUST show its bare authored
+ /// art (identity tint) — the mechanism is fully inert, not a
+ /// half-broken draw.
[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);
}
}