feat(ui): port retail jump power bar

Import gmFloatyPowerBarUI LayoutDesc 0x21000072, teach the meter factory its stateful single-image shape, and project the movement-owned retail jump charge through a focused retained controller. Preserve authored resize constraints and state-managed visibility, with named-decomp pseudocode plus controller, movement, and production-DAT conformance coverage.

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-13 10:22:03 +02:00
parent 27619b2328
commit db03b4bda8
18 changed files with 3045 additions and 10 deletions

View file

@ -313,6 +313,37 @@ public class DatWidgetFactoryTests
Assert.Equal(0u, m.FrontRight);
}
[Fact]
public void BuildMeter_StatefulSingleImageShape_MapsNumericFillStatesAndHidesDirectRange()
{
const uint TrackFile = 0x06004D0Bu;
const uint JumpState = 0x10000042u;
const uint JumpFile = 0x06001354u;
const uint RangeFile = 0x0600715Eu;
var meter = new ElementInfo { Type = 7, Id = 0x10000034u, Width = 600, Height = 15 };
meter.StateMedia[""] = (TrackFile, 1);
var fills = new ElementInfo { Type = 3, Id = 2u, ReadOrder = 2 };
fills.States[JumpState] = new UiStateInfo { Id = JumpState, Name = "JumpMode" };
fills.StateMedia["JumpMode"] = (JumpFile, 1);
meter.Children.Add(fills);
var range = new ElementInfo { Type = 3, Id = 0x100005EEu, ReadOrder = 1 };
range.StateMedia[""] = (RangeFile, 1);
meter.Children.Add(range);
var result = Assert.IsType<UiMeter>(DatWidgetFactory.Create(meter, NoTex, null));
Assert.Equal(TrackFile, result.BackTile);
Assert.Equal(0u, result.FrontTile);
Assert.True(result.TrySetRetailState(JumpState));
Assert.Equal(JumpState, result.ActiveRetailStateId);
Assert.Equal(JumpFile, result.FrontTile);
Assert.NotEqual(RangeFile, result.BackTile);
Assert.NotEqual(RangeFile, result.FrontTile);
}
// ── Test 6: Meter slice extraction (the important one) ───────────────────
/// <summary>

View file

@ -95,6 +95,12 @@ public static class FixtureLoader
public static ElementInfo LoadCombatInfos()
=> LoadInfos("combat_21000073.json");
public static ImportedLayout LoadPowerbar()
=> LayoutImporter.Build(LoadPowerbarInfos(), _ => (0u, 0, 0), null);
public static ElementInfo LoadPowerbarInfos()
=> LoadInfos("powerbar_21000072.json");
// ── Shared loader ────────────────────────────────────────────────────────
private static AcDream.App.UI.Layout.ElementInfo LoadInfos(string fileName)

View file

@ -0,0 +1,73 @@
using AcDream.App.Input;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
namespace AcDream.App.Tests.UI.Layout;
public sealed class JumpPowerbarControllerTests
{
private const uint JumpFill = 0x06001354u;
[Fact]
public void MovementCharge_ShowsUpdatesAndHidesAuthoredJumpMeter()
{
JumpChargeSnapshot state = default;
ImportedLayout layout = BuildLayout();
var visibility = new List<bool>();
using var controller = JumpPowerbarController.Bind(
layout, () => state, visibility.Add)!;
var meter = Assert.IsType<UiMeter>(layout.FindElement(JumpPowerbarController.MeterId));
Assert.Equal([false], visibility);
Assert.Equal(0f, meter.Fill());
Assert.Equal(JumpPowerbarController.JumpModeState, meter.ActiveRetailStateId);
Assert.Equal(JumpFill, meter.FrontTile);
state = new JumpChargeSnapshot(true, 0.35f);
controller.Tick();
Assert.Equal([false, true], visibility);
Assert.Equal(0.35f, meter.Fill());
state = new JumpChargeSnapshot(true, 1.4f);
controller.Tick();
Assert.Equal([false, true], visibility);
Assert.Equal(1f, meter.Fill());
state = default;
controller.Tick();
Assert.Equal([false, true, false], visibility);
Assert.Equal(0f, meter.Fill());
}
[Fact]
public void Bind_RejectsLayoutWithoutJumpModeMeter()
{
var root = new UiPanel();
var meter = new UiMeter { ElementId = JumpPowerbarController.MeterId };
var layout = new ImportedLayout(root, new Dictionary<uint, UiElement>
{
[JumpPowerbarController.MeterId] = meter,
});
Assert.Null(JumpPowerbarController.Bind(layout, () => default, _ => { }));
}
private static ImportedLayout BuildLayout()
{
var root = new UiPanel { Width = 610, Height = 25 };
var meter = new UiMeter
{
ElementId = JumpPowerbarController.MeterId,
Left = 5,
Top = 5,
Width = 600,
Height = 15,
};
meter.ConfigureStateFill(JumpPowerbarController.JumpModeState, JumpFill);
root.AddChild(meter);
return new ImportedLayout(root, new Dictionary<uint, UiElement>
{
[JumpPowerbarController.MeterId] = meter,
});
}
}

View file

@ -0,0 +1,33 @@
using AcDream.App.UI;
using AcDream.App.UI.Layout;
namespace AcDream.App.Tests.UI.Layout;
public sealed class JumpPowerbarLayoutConformanceTests
{
[Fact]
public void RetailFixture_UsesExactFloatyGeometryTrackAndJumpFill()
{
ElementInfo info = FixtureLoader.LoadPowerbarInfos();
ImportedLayout layout = LayoutImporter.Build(info, _ => (0u, 0, 0), null);
var meter = Assert.IsType<UiMeter>(
layout.FindElement(JumpPowerbarController.MeterId));
Assert.Equal((610f, 25f), (layout.Root.Width, layout.Root.Height));
Assert.Equal((5f, 5f, 600f, 15f),
(meter.Left, meter.Top, meter.Width, meter.Height));
Assert.Equal(0x06004D0Bu, meter.BackTile);
Assert.True(meter.TrySetRetailState(JumpPowerbarController.JumpModeState));
Assert.Equal(0x06001354u, meter.FrontTile);
Assert.Equal((0f, 0f, 300f, 15f),
UiMeter.ComputeFillRect(0.5f, meter.Width, meter.Height));
Assert.True(info.TryGetEffectiveInteger(0x3Fu, out int minWidth));
Assert.True(info.TryGetEffectiveInteger(0x3Du, out int maxWidth));
Assert.True(info.TryGetEffectiveInteger(0x3Eu, out int minHeight));
Assert.True(info.TryGetEffectiveInteger(0x3Cu, out int maxHeight));
Assert.Equal((50, 810, 25, 25),
(minWidth, maxWidth, minHeight, maxHeight));
}
}

View file

@ -21,6 +21,7 @@ public sealed class RetailLayoutFixtureGenerator
(0x21000024u, "paperdoll_21000024.json"),
(0x2100002Eu, "character_2100002E.json"),
(0x2100006Cu, "vitals_2100006C.json"),
(0x21000072u, "powerbar_21000072.json"),
(0x21000073u, "combat_21000073.json"),
(0x21000074u, "radar_21000074.json"),
};

File diff suppressed because it is too large Load diff