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:
Erik 2026-08-16 11:37:09 +02:00
parent 1d9de5e095
commit 7d09821fdc
11 changed files with 1043 additions and 42 deletions

View file

@ -878,8 +878,31 @@ public static class DatWidgetFactory
button.FaceTop = face.Y;
button.FaceWidth = face.Width;
button.FaceHeight = face.Height;
button.LabelAlign = UiButton.LabelAlignment.Left;
button.LabelOffsetX = face.X + face.Width + 4f;
if (!ReferenceEquals(labelInfo, info))
{
// GF-11c (Campaign CC gate round 1 Batch B): a DISTINCT
// Type-12 caption was lifted (e.g. the Town page's per-
// marker name label, 0x10000409 under each town button —
// live-DAT-probe-confirmed authored rect + Center justify,
// independent of the marker face's own geometry) — honor
// ITS OWN authored rect/justify instead of the face-
// relative offset below, which is only correct when the
// label text is authored DIRECTLY on the button itself,
// immediately beside a single-purpose face segment (the
// heritage/template/Face-Clothes sub-tab row family —
// still handled by the else-branch two lines down, since
// ReferenceEquals(labelInfo, info) is true there).
button.LabelBox = (labelInfo.X, labelInfo.Y, labelInfo.Width, labelInfo.Height);
button.LabelAlign = labelInfo.HJustify == HJustify.Left
? UiButton.LabelAlignment.Left
: UiButton.LabelAlignment.Center;
}
else
{
button.LabelAlign = UiButton.LabelAlignment.Left;
button.LabelOffsetX = face.X + face.Width + 4f;
}
}
else if (labelInfo.HJustify == HJustify.Left)
{
@ -901,6 +924,15 @@ public static class DatWidgetFactory
button.LabelOffsetX = labelInfo.X;
}
// AP-222 / GF-11b (Campaign CC gate round 1 Batch B): per-state label
// color/outline (dat properties 0x1B/0x21 authored PER STATE on the
// label-bearing element — the Appearance spins' own states, or the
// Town caption child's states) — additive, only non-null when the
// authored dat genuinely carries more than one distinct value.
button.SetPerStateLabelStyle(
ElementReader.BuildPerStateColorMap(labelInfo, 0x1Bu),
ElementReader.BuildPerStateBoolMap(labelInfo, 0x21u));
return button;
}