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
|
|
@ -249,6 +249,171 @@ public class UiButtonTests
|
|||
Assert.Equal(2, clicks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GF-1/GF-8 (Campaign CC gate round 1 Batch B): the "gender button"
|
||||
/// shape — retail's custom Unselected/Selected radio-pair media authored
|
||||
/// DIRECTLY on the button's own StateMedia (no separate face-segment
|
||||
/// child), live-DAT-measured on 0x100003A7/0x100003A8 (Female/Male).
|
||||
/// Before this fix, .Selected committed nothing: the standard
|
||||
/// AddAvailableStates loop never recognized the "Unselected"/"Selected"
|
||||
/// names, so _availableStates was empty and UpdateVisualState's
|
||||
/// RequestedState (which only ever returns Normal/Highlight/Ghosted ids)
|
||||
/// could never match anyway.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CustomSelectionPair_MediaDirectlyOnButton_SelectedTogglesActiveState()
|
||||
{
|
||||
var info = ButtonInfo("Unselected", "Selected");
|
||||
var b = CreateButton(info);
|
||||
|
||||
Assert.Equal("Unselected", b.ActiveState);
|
||||
|
||||
b.Selected = true;
|
||||
Assert.Equal("Selected", b.ActiveState);
|
||||
|
||||
b.Selected = false;
|
||||
Assert.Equal("Unselected", b.ActiveState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "heritage/template/sub-tab row" shape — the parent authors the
|
||||
/// Unselected/Selected state DESCRIPTORS (property bag only, no media),
|
||||
/// and a single stateful child (the radio dot / icon) carries the
|
||||
/// actual per-state art, matching <c>FindStatefulFaceChildren</c>'s
|
||||
/// name-overlap detection. Live-DAT-measured on the Heritage row
|
||||
/// (0x100003BF, dot child 0x100003C0) and the Profession template row
|
||||
/// (0x100003D9, icon child 0x100002E9).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CustomSelectionPair_MediaOnFaceChild_SelectedTogglesActiveState()
|
||||
{
|
||||
var info = new ElementInfo { Type = 1, Width = 305, Height = 32 };
|
||||
info.States[UiButtonStateMachine.NormalPressed] = new UiStateInfo
|
||||
{
|
||||
Id = UiButtonStateMachine.NormalPressed,
|
||||
Name = "Normal_pressed",
|
||||
};
|
||||
info.States[RetailUiStateIds.Unselected] = new UiStateInfo
|
||||
{
|
||||
Id = RetailUiStateIds.Unselected,
|
||||
Name = "Unselected",
|
||||
};
|
||||
info.States[RetailUiStateIds.Selected] = new UiStateInfo
|
||||
{
|
||||
Id = RetailUiStateIds.Selected,
|
||||
Name = "Selected",
|
||||
};
|
||||
info.DefaultStateName = "Unselected";
|
||||
|
||||
var dot = new ElementInfo { Type = 3, Width = 32, Height = 32 };
|
||||
dot.StateMedia["Unselected"] = (0x06006E35u, 1);
|
||||
dot.StateMedia["Selected"] = (0x06006E21u, 1);
|
||||
info.Children.Add(dot);
|
||||
|
||||
// Face-child discovery (FindStatefulFaceChildren) is DatWidgetFactory's
|
||||
// job, not UiButton's own constructor — go through the real factory
|
||||
// path so this fixture matches production exactly (raw CreateButton
|
||||
// below bypasses that discovery entirely).
|
||||
var b = Assert.IsType<UiButton>(DatWidgetFactory.Create(info, NoTex, null));
|
||||
|
||||
Assert.Equal("Unselected", b.ActiveState);
|
||||
|
||||
b.Selected = true;
|
||||
Assert.Equal("Selected", b.ActiveState);
|
||||
Assert.Equal(RetailUiStateIds.Selected, b.ActiveRetailStateId);
|
||||
|
||||
b.Selected = false;
|
||||
Assert.Equal("Unselected", b.ActiveState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regression pin: a STANDARD ToggleBehavior button (no Unselected/
|
||||
/// Selected states authored at all — the overwhelming majority of
|
||||
/// buttons, including every pre-existing ToggleBehavior consumer) keeps
|
||||
/// behaving exactly as before the custom-pair bypass was added.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CustomSelectionPair_Absent_StandardToggleBehaviorUnchanged()
|
||||
{
|
||||
var info = ButtonInfo("Normal", "Highlight");
|
||||
AddBoolProperty(info, 0x0Bu, true);
|
||||
var b = CreateButton(info);
|
||||
|
||||
b.Selected = true;
|
||||
Assert.Equal("Highlight", b.ActiveState);
|
||||
|
||||
b.Selected = false;
|
||||
Assert.Equal("Normal", b.ActiveState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AP-222 / GF-11b (Campaign CC gate round 1 Batch B): per-state label
|
||||
/// color/outline reacts to the REQUESTED retail state id even when the
|
||||
/// standard art-availability gate never lets ActiveState reach it — the
|
||||
/// Appearance spins' exact shape (their arrow face segments carry no
|
||||
/// Highlight media at all, so ActiveState is permanently stuck at
|
||||
/// "Normal", but the label text must still recolor). Live-DAT-measured
|
||||
/// values: Normal (218,167,85), Highlight (255,221,131), outline
|
||||
/// off -> on.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void PerStateLabelStyle_AppliesEvenWhenActiveStateCannotReachIt()
|
||||
{
|
||||
var info = ButtonInfo("Normal"); // no Highlight media at all
|
||||
AddBoolProperty(info, 0x0Bu, true); // ToggleBehavior
|
||||
var b = CreateButton(info);
|
||||
b.Label = "Hair Style";
|
||||
b.LabelColor = new System.Numerics.Vector4(1f, 1f, 1f, 1f);
|
||||
|
||||
var colors = new Dictionary<uint, System.Numerics.Vector4>
|
||||
{
|
||||
[UiButtonStateMachine.Normal] = new(218f / 255f, 167f / 255f, 85f / 255f, 1f),
|
||||
[UiButtonStateMachine.Highlight] = new(255f / 255f, 221f / 255f, 131f / 255f, 1f),
|
||||
};
|
||||
var outlines = new Dictionary<uint, bool>
|
||||
{
|
||||
[UiButtonStateMachine.Normal] = false,
|
||||
[UiButtonStateMachine.Highlight] = true,
|
||||
};
|
||||
b.SetPerStateLabelStyle(colors, outlines);
|
||||
|
||||
Assert.Equal(colors[UiButtonStateMachine.Normal], b.LabelColor);
|
||||
Assert.False(b.Outline);
|
||||
|
||||
b.Selected = true;
|
||||
|
||||
// The art stays "Normal" (no Highlight media exists to commit to) —
|
||||
// this is the exact AP-222 no-op the standard gate always produced —
|
||||
// but the label color/outline must still reach the Highlight values.
|
||||
Assert.Equal("Normal", b.ActiveState);
|
||||
Assert.Equal(colors[UiButtonStateMachine.Highlight], b.LabelColor);
|
||||
Assert.True(b.Outline);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regression pin: a button with NO per-state color map (null, the
|
||||
/// overwhelming majority — every existing external post-construction
|
||||
/// LabelColor assignment such as ChatWindowController's Send caption or
|
||||
/// PaperdollController's Slots label) never has its LabelColor touched
|
||||
/// by a state change.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void PerStateLabelStyle_Absent_ExternalLabelColorAssignmentSurvivesStateChanges()
|
||||
{
|
||||
var info = ButtonInfo("Normal", "Highlight");
|
||||
AddBoolProperty(info, 0x0Bu, true);
|
||||
var b = CreateButton(info);
|
||||
var externalColor = new System.Numerics.Vector4(1f, 0.92f, 0.72f, 1f);
|
||||
b.LabelColor = externalColor;
|
||||
|
||||
b.Selected = true;
|
||||
Assert.Equal("Highlight", b.ActiveState);
|
||||
Assert.Equal(externalColor, b.LabelColor);
|
||||
|
||||
b.Selected = false;
|
||||
Assert.Equal(externalColor, b.LabelColor);
|
||||
}
|
||||
|
||||
private static UiButton ButtonWithStates(params string[] states)
|
||||
{
|
||||
var info = ButtonInfo(states);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue