fix #416 #415: the retail button state/media machine — roster hover highlight clears; probe wait verbs bind without an artifact dir
#416 (char-select roster highlight never cleared on hover-leave): three decomp-grounded mechanisms replace the media-keyed _availableStates approximation. - UIElement_Button::UpdateState_ @0x00471CF0: the button machine commits ONLY states authored on the button's OWN ElementDesc (AccessStateDesc gate); unauthored requests no-op, preserving custom semantic states. - UIElement::SetState @0x00464E70: an unauthored state id is coerced to state 0 (the unnamed base state) and committed — ported into UiDatElement.TrySetRetailState with the base-descriptor PassToChildren cascade arm. - The SetState media rule @0x004651c0: a committed state replaces the playing media ONLY when its media array is non-empty. UiButton now keeps per-face-segment media states under that rule (segments model retail's PassToChildren children), and LayoutImporter records the raw MediaCount including the File=0 draw-nothing images the drawable filter drops — the roster bar children's base state is exactly such an image, and it is what clears the bar. The row template truth (probe, installed DAT): the row authors EMPTY Normal/rollover/Highlight descriptors with PassToChildren; the three bar children author rollover/Highlight media, NO Normal state, and a File=0 base image. An empty-media Normal_pressed still never blanks a Normal-art button (the media rule keeps the previous art — the exact behavior the old gate approximated), and the Appearance spins' property-only Highlight now genuinely commits: label recolors, arrow art lingers — the retail split AP-222 approximated with a requested-keyed label hack, now retired. Live-verified at char select: hover +alex shows the grey bar, moving off clears it, the selected row keeps its amber bar. #415 (probe wait world-* verbs dead): the filed snapshot-reset diagnosis was wrong — the automation bridge simply never bound without ACDREAM_AUTOMATION_ARTIFACT_DIR. A facts-only WorldRevealFactsAutomationRuntime now binds whenever the retained UI exists; checkpoint/screenshot verbs still require the artifact directory and now report that instead of a generic timeout. App tests 5568/3 skips, Runtime 1756/0, UI.Abstractions 926/0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7aa08045d8
commit
91c1962b0d
11 changed files with 469 additions and 86 deletions
|
|
@ -172,17 +172,24 @@ public class UiButtonTests
|
|||
[Fact]
|
||||
public void PropertyOnlyPressedState_PreservesDrawableNormalFace()
|
||||
{
|
||||
// #416 media-rule port: retail COMMITS the authored empty-media
|
||||
// Normal_pressed (UIElement::SetState @0x00464E70 commits any
|
||||
// authored state) while the FACE keeps the Normal art (the
|
||||
// @0x004651c0 media rule: an empty media array never replaces the
|
||||
// playing media) — the press must not blank the button.
|
||||
var info = ButtonInfo("Normal", "Highlight");
|
||||
info.States[UiButtonStateMachine.NormalPressed] = new UiStateInfo
|
||||
{
|
||||
Id = UiButtonStateMachine.NormalPressed,
|
||||
Name = "Normal_pressed",
|
||||
};
|
||||
var b = CreateButton(info);
|
||||
var b = CreateDrawableButton(info);
|
||||
Assert.Equal(1u, DrawnFaceFile(b)); // ButtonInfo assigns "Normal" file 1
|
||||
|
||||
b.OnEvent(new UiEvent(0, b, UiEventType.MouseDown, Data1: 5, Data2: 5));
|
||||
|
||||
Assert.Equal("Normal", b.ActiveState);
|
||||
Assert.Equal("Normal_pressed", b.ActiveState);
|
||||
Assert.Equal(1u, DrawnFaceFile(b));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -347,21 +354,27 @@ public class UiButtonTests
|
|||
}
|
||||
|
||||
/// <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.
|
||||
/// AP-222 / GF-11b, re-derived at the #416 media-rule port: the
|
||||
/// Appearance spins author a PROPERTY-ONLY Highlight StateDesc (the
|
||||
/// 0x1B/0x21 label style, no media). Retail's machine gate
|
||||
/// (UIElement_Button::UpdateState_ @0x00471CF0) admits any AUTHORED
|
||||
/// state, and UIElement::SetState @0x00464E70 then commits it — the
|
||||
/// label recolors — while the ARROW ART stays on Normal through the
|
||||
/// SetState media rule (@0x004651c0: an empty media array never
|
||||
/// replaces the playing media). Live-DAT-measured values: Normal
|
||||
/// (218,167,85), Highlight (255,221,131), outline off -> on.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void PerStateLabelStyle_AppliesEvenWhenActiveStateCannotReachIt()
|
||||
public void PerStateLabelStyle_PropertyOnlyHighlight_CommitsAndKeepsFace()
|
||||
{
|
||||
var info = ButtonInfo("Normal"); // no Highlight media at all
|
||||
var info = ButtonInfo("Normal"); // Highlight authors NO media...
|
||||
info.States[UiButtonStateMachine.Highlight] = new UiStateInfo
|
||||
{
|
||||
Id = UiButtonStateMachine.Highlight,
|
||||
Name = "Highlight", // ...but IS authored (property-only)
|
||||
};
|
||||
AddBoolProperty(info, 0x0Bu, true); // ToggleBehavior
|
||||
var b = CreateButton(info);
|
||||
var b = CreateDrawableButton(info);
|
||||
b.Label = "Hair Style";
|
||||
b.LabelColor = new System.Numerics.Vector4(1f, 1f, 1f, 1f);
|
||||
|
||||
|
|
@ -379,15 +392,16 @@ public class UiButtonTests
|
|||
|
||||
Assert.Equal(colors[UiButtonStateMachine.Normal], b.LabelColor);
|
||||
Assert.False(b.Outline);
|
||||
Assert.Equal(1u, DrawnFaceFile(b)); // "Normal" art (file 1)
|
||||
|
||||
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);
|
||||
// The authored property-only Highlight COMMITS (retail SetState) and
|
||||
// the label recolors; the face keeps the Normal art (media rule).
|
||||
Assert.Equal("Highlight", b.ActiveState);
|
||||
Assert.Equal(colors[UiButtonStateMachine.Highlight], b.LabelColor);
|
||||
Assert.True(b.Outline);
|
||||
Assert.Equal(1u, DrawnFaceFile(b));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -730,4 +744,35 @@ public class UiButtonTests
|
|||
|
||||
private static UiButton CreateButton(ElementInfo info)
|
||||
=> new(info, NoTex) { Width = info.Width, Height = info.Height };
|
||||
|
||||
// ── #416 media-rule draw harness ─────────────────────────────────────
|
||||
|
||||
private sealed class NullGpuFrameSource
|
||||
: AcDream.App.Rendering.ICurrentGpuFrameSource
|
||||
{
|
||||
public AcDream.App.Rendering.Gpu.IGpuFrame? CurrentFrame => null;
|
||||
}
|
||||
|
||||
/// <summary>Identity resolve (texture handle == file id) so the drawn
|
||||
/// face file is directly observable through the recording renderer.</summary>
|
||||
private static UiButton CreateDrawableButton(ElementInfo info)
|
||||
=> new(info, static file => (file, 8, 8))
|
||||
{
|
||||
Width = info.Width,
|
||||
Height = info.Height,
|
||||
};
|
||||
|
||||
private static uint DrawnFaceFile(UiButton button)
|
||||
{
|
||||
var device = new AcDream.App.Tests.Rendering.Gpu.RecordingGpuDevice();
|
||||
var renderer = new AcDream.App.Rendering.TextRenderer(
|
||||
device, new NullGpuFrameSource(), "unused");
|
||||
renderer.Begin(new System.Numerics.Vector2(200f, 200f));
|
||||
var ctx = new UiRenderContext(
|
||||
renderer, new System.Numerics.Vector2(200f, 200f));
|
||||
button.DrawSelfAndChildren(ctx);
|
||||
return renderer.DebugSpriteSegmentVerts.Count == 0
|
||||
? 0u
|
||||
: renderer.DebugSpriteSegmentVerts[0].Item1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue