fix #213: complete suicide and framerate presentation
Translate suicide response 0x004A as retail's successful self-kill notice. Port /framerate onto the authored SmartBox FPS element with live two-decimal FPS and DEG values, keep diagnostic window chrome independent, and synchronize command-driven settings without discarding panel drafts. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
9ea579bdd0
commit
43f7c7807c
17 changed files with 729 additions and 39 deletions
|
|
@ -107,6 +107,12 @@ public static class FixtureLoader
|
|||
public static ElementInfo LoadConfirmationDialogInfos()
|
||||
=> LoadInfos("dialogs_2100003C.json");
|
||||
|
||||
public static ImportedLayout LoadFpsDisplay()
|
||||
=> LayoutImporter.Build(LoadFpsDisplayInfos(), _ => (0u, 0, 0), null);
|
||||
|
||||
public static ElementInfo LoadFpsDisplayInfos()
|
||||
=> LoadInfos("smartbox_fps_2100000F.json");
|
||||
|
||||
// ── Shared loader ────────────────────────────────────────────────────────
|
||||
|
||||
private static AcDream.App.UI.Layout.ElementInfo LoadInfos(string fileName)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
public sealed class RetailFpsControllerTests
|
||||
{
|
||||
[Fact]
|
||||
public void AuthoredSmartBoxDisplay_FormatsUpdatesAndTogglesLikeRetail()
|
||||
{
|
||||
double fps = 59.994;
|
||||
double degrade = 0.875;
|
||||
bool visible = false;
|
||||
ImportedLayout layout = FixtureLoader.LoadFpsDisplay();
|
||||
ElementInfo info = FixtureLoader.LoadFpsDisplayInfos();
|
||||
|
||||
Assert.Equal(0x4000001Au, info.FontDid);
|
||||
Assert.Equal(128, info.Width);
|
||||
Assert.Equal(36, info.Height);
|
||||
Assert.Equal(1u, info.Left);
|
||||
Assert.Equal(1u, info.Top);
|
||||
|
||||
var controller = RetailFpsController.Bind(
|
||||
layout, () => fps, () => degrade, () => visible)!;
|
||||
|
||||
UiText display = controller.Display;
|
||||
Assert.Equal(RetailFpsController.DisplayElementId, display.ElementId);
|
||||
Assert.Equal(128f, display.Width);
|
||||
Assert.Equal(36f, display.Height);
|
||||
Assert.Equal(1f, display.Left);
|
||||
Assert.Equal(1f, display.Top);
|
||||
Assert.False(display.Visible);
|
||||
|
||||
Assert.Equal(
|
||||
["FPS: 59.99", "DEG: 0.88"],
|
||||
display.LinesProvider().Select(line => line.Text));
|
||||
|
||||
fps = 144.0;
|
||||
degrade = 1.0;
|
||||
visible = true;
|
||||
controller.Tick();
|
||||
|
||||
Assert.True(display.Visible);
|
||||
Assert.Equal(
|
||||
["FPS: 144.00", "DEG: 1.00"],
|
||||
display.LinesProvider().Select(line => line.Text));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bind_RejectsLayoutWithoutAuthoredTextElement()
|
||||
{
|
||||
var root = new UiPanel();
|
||||
var layout = new ImportedLayout(root, new Dictionary<uint, UiElement>());
|
||||
|
||||
Assert.Null(RetailFpsController.Bind(layout, () => 60, () => 1, () => true));
|
||||
}
|
||||
}
|
||||
|
|
@ -64,6 +64,25 @@ public sealed class RetailLayoutFixtureGenerator
|
|||
Path.Combine(FixtureDirectory(), $"dialogs_{dialogsLayoutDid:X8}.json"),
|
||||
dialogsJson);
|
||||
|
||||
const uint smartboxLayoutDid = 0x2100000Fu;
|
||||
var fps = LayoutImporter.ImportInfos(
|
||||
dats,
|
||||
smartboxLayoutDid,
|
||||
0x10000047u);
|
||||
Assert.NotNull(fps);
|
||||
Assert.Equal(0x4000001Au, fps!.FontDid);
|
||||
var fpsStrings = new DatStringResolver(dats);
|
||||
Assert.Equal("FPS: ", fpsStrings.Resolve(0x23000001u, 0x0DCFFF73u, 0));
|
||||
Assert.Equal("\nDEG: ", fpsStrings.Resolve(0x23000001u, 0x0DCFFF73u, 1));
|
||||
var fpsJson = JsonSerializer.Serialize(fps, new JsonSerializerOptions
|
||||
{
|
||||
IncludeFields = true,
|
||||
WriteIndented = true,
|
||||
});
|
||||
File.WriteAllText(
|
||||
Path.Combine(FixtureDirectory(), $"smartbox_fps_{smartboxLayoutDid:X8}.json"),
|
||||
fpsJson);
|
||||
|
||||
foreach (var (layoutId, fileName) in Layouts)
|
||||
{
|
||||
var info = LayoutImporter.ImportInfos(dats, layoutId);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,395 @@
|
|||
{
|
||||
"Id": 268435527,
|
||||
"Type": 12,
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Width": 128,
|
||||
"Height": 36,
|
||||
"OriginalParentWidth": 0,
|
||||
"OriginalParentHeight": 0,
|
||||
"HasOriginalParentSize": false,
|
||||
"Left": 1,
|
||||
"Top": 1,
|
||||
"Right": 2,
|
||||
"Bottom": 2,
|
||||
"ReadOrder": 15,
|
||||
"ZLevel": 0,
|
||||
"States": {
|
||||
"4294967295": {
|
||||
"Id": 4294967295,
|
||||
"Name": "",
|
||||
"PassToChildren": false,
|
||||
"IncorporationFlags": 30,
|
||||
"Image": null,
|
||||
"Cursor": null,
|
||||
"Properties": {
|
||||
"Values": {
|
||||
"33": {
|
||||
"Kind": 1,
|
||||
"MasterPropertyId": 33,
|
||||
"UnsignedValue": 0,
|
||||
"IntegerValue": 0,
|
||||
"FloatValue": 0,
|
||||
"BoolValue": true,
|
||||
"StringInfoValue": {
|
||||
"Token": 0,
|
||||
"StringId": 0,
|
||||
"TableId": 0,
|
||||
"Override": 0,
|
||||
"English": 0,
|
||||
"Comment": 0
|
||||
},
|
||||
"ColorValue": {
|
||||
"Blue": 0,
|
||||
"Green": 0,
|
||||
"Red": 0,
|
||||
"Alpha": 0
|
||||
},
|
||||
"VectorValue": {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0
|
||||
},
|
||||
"ArrayValue": [],
|
||||
"StructValue": {}
|
||||
},
|
||||
"35": {
|
||||
"Kind": 4,
|
||||
"MasterPropertyId": 35,
|
||||
"UnsignedValue": 0,
|
||||
"IntegerValue": 2,
|
||||
"FloatValue": 0,
|
||||
"BoolValue": false,
|
||||
"StringInfoValue": {
|
||||
"Token": 0,
|
||||
"StringId": 0,
|
||||
"TableId": 0,
|
||||
"Override": 0,
|
||||
"English": 0,
|
||||
"Comment": 0
|
||||
},
|
||||
"ColorValue": {
|
||||
"Blue": 0,
|
||||
"Green": 0,
|
||||
"Red": 0,
|
||||
"Alpha": 0
|
||||
},
|
||||
"VectorValue": {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0
|
||||
},
|
||||
"ArrayValue": [],
|
||||
"StructValue": {}
|
||||
},
|
||||
"81": {
|
||||
"Kind": 1,
|
||||
"MasterPropertyId": 81,
|
||||
"UnsignedValue": 0,
|
||||
"IntegerValue": 0,
|
||||
"FloatValue": 0,
|
||||
"BoolValue": true,
|
||||
"StringInfoValue": {
|
||||
"Token": 0,
|
||||
"StringId": 0,
|
||||
"TableId": 0,
|
||||
"Override": 0,
|
||||
"English": 0,
|
||||
"Comment": 0
|
||||
},
|
||||
"ColorValue": {
|
||||
"Blue": 0,
|
||||
"Green": 0,
|
||||
"Red": 0,
|
||||
"Alpha": 0
|
||||
},
|
||||
"VectorValue": {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0
|
||||
},
|
||||
"ArrayValue": [],
|
||||
"StructValue": {}
|
||||
},
|
||||
"59": {
|
||||
"Kind": 1,
|
||||
"MasterPropertyId": 59,
|
||||
"UnsignedValue": 0,
|
||||
"IntegerValue": 0,
|
||||
"FloatValue": 0,
|
||||
"BoolValue": true,
|
||||
"StringInfoValue": {
|
||||
"Token": 0,
|
||||
"StringId": 0,
|
||||
"TableId": 0,
|
||||
"Override": 0,
|
||||
"English": 0,
|
||||
"Comment": 0
|
||||
},
|
||||
"ColorValue": {
|
||||
"Blue": 0,
|
||||
"Green": 0,
|
||||
"Red": 0,
|
||||
"Alpha": 0
|
||||
},
|
||||
"VectorValue": {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0
|
||||
},
|
||||
"ArrayValue": [],
|
||||
"StructValue": {}
|
||||
},
|
||||
"37": {
|
||||
"Kind": 4,
|
||||
"MasterPropertyId": 37,
|
||||
"UnsignedValue": 0,
|
||||
"IntegerValue": 2,
|
||||
"FloatValue": 0,
|
||||
"BoolValue": false,
|
||||
"StringInfoValue": {
|
||||
"Token": 0,
|
||||
"StringId": 0,
|
||||
"TableId": 0,
|
||||
"Override": 0,
|
||||
"English": 0,
|
||||
"Comment": 0
|
||||
},
|
||||
"ColorValue": {
|
||||
"Blue": 0,
|
||||
"Green": 0,
|
||||
"Red": 0,
|
||||
"Alpha": 0
|
||||
},
|
||||
"VectorValue": {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0
|
||||
},
|
||||
"ArrayValue": [],
|
||||
"StructValue": {}
|
||||
},
|
||||
"26": {
|
||||
"Kind": 7,
|
||||
"MasterPropertyId": 26,
|
||||
"UnsignedValue": 0,
|
||||
"IntegerValue": 0,
|
||||
"FloatValue": 0,
|
||||
"BoolValue": false,
|
||||
"StringInfoValue": {
|
||||
"Token": 0,
|
||||
"StringId": 0,
|
||||
"TableId": 0,
|
||||
"Override": 0,
|
||||
"English": 0,
|
||||
"Comment": 0
|
||||
},
|
||||
"ColorValue": {
|
||||
"Blue": 0,
|
||||
"Green": 0,
|
||||
"Red": 0,
|
||||
"Alpha": 0
|
||||
},
|
||||
"VectorValue": {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0
|
||||
},
|
||||
"ArrayValue": [
|
||||
{
|
||||
"Kind": 2,
|
||||
"MasterPropertyId": 24,
|
||||
"UnsignedValue": 1073741850,
|
||||
"IntegerValue": 0,
|
||||
"FloatValue": 0,
|
||||
"BoolValue": false,
|
||||
"StringInfoValue": {
|
||||
"Token": 0,
|
||||
"StringId": 0,
|
||||
"TableId": 0,
|
||||
"Override": 0,
|
||||
"English": 0,
|
||||
"Comment": 0
|
||||
},
|
||||
"ColorValue": {
|
||||
"Blue": 0,
|
||||
"Green": 0,
|
||||
"Red": 0,
|
||||
"Alpha": 0
|
||||
},
|
||||
"VectorValue": {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0
|
||||
},
|
||||
"ArrayValue": [],
|
||||
"StructValue": {}
|
||||
}
|
||||
],
|
||||
"StructValue": {}
|
||||
},
|
||||
"27": {
|
||||
"Kind": 7,
|
||||
"MasterPropertyId": 27,
|
||||
"UnsignedValue": 0,
|
||||
"IntegerValue": 0,
|
||||
"FloatValue": 0,
|
||||
"BoolValue": false,
|
||||
"StringInfoValue": {
|
||||
"Token": 0,
|
||||
"StringId": 0,
|
||||
"TableId": 0,
|
||||
"Override": 0,
|
||||
"English": 0,
|
||||
"Comment": 0
|
||||
},
|
||||
"ColorValue": {
|
||||
"Blue": 0,
|
||||
"Green": 0,
|
||||
"Red": 0,
|
||||
"Alpha": 0
|
||||
},
|
||||
"VectorValue": {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0
|
||||
},
|
||||
"ArrayValue": [
|
||||
{
|
||||
"Kind": 6,
|
||||
"MasterPropertyId": 25,
|
||||
"UnsignedValue": 0,
|
||||
"IntegerValue": 0,
|
||||
"FloatValue": 0,
|
||||
"BoolValue": false,
|
||||
"StringInfoValue": {
|
||||
"Token": 0,
|
||||
"StringId": 0,
|
||||
"TableId": 0,
|
||||
"Override": 0,
|
||||
"English": 0,
|
||||
"Comment": 0
|
||||
},
|
||||
"ColorValue": {
|
||||
"Blue": 255,
|
||||
"Green": 255,
|
||||
"Red": 255,
|
||||
"Alpha": 255
|
||||
},
|
||||
"VectorValue": {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0
|
||||
},
|
||||
"ArrayValue": [],
|
||||
"StructValue": {}
|
||||
}
|
||||
],
|
||||
"StructValue": {}
|
||||
},
|
||||
"205": {
|
||||
"Kind": 0,
|
||||
"MasterPropertyId": 205,
|
||||
"UnsignedValue": 3,
|
||||
"IntegerValue": 0,
|
||||
"FloatValue": 0,
|
||||
"BoolValue": false,
|
||||
"StringInfoValue": {
|
||||
"Token": 0,
|
||||
"StringId": 0,
|
||||
"TableId": 0,
|
||||
"Override": 0,
|
||||
"English": 0,
|
||||
"Comment": 0
|
||||
},
|
||||
"ColorValue": {
|
||||
"Blue": 0,
|
||||
"Green": 0,
|
||||
"Red": 0,
|
||||
"Alpha": 0
|
||||
},
|
||||
"VectorValue": {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0
|
||||
},
|
||||
"ArrayValue": [],
|
||||
"StructValue": {}
|
||||
},
|
||||
"20": {
|
||||
"Kind": 0,
|
||||
"MasterPropertyId": 20,
|
||||
"UnsignedValue": 2,
|
||||
"IntegerValue": 0,
|
||||
"FloatValue": 0,
|
||||
"BoolValue": false,
|
||||
"StringInfoValue": {
|
||||
"Token": 0,
|
||||
"StringId": 0,
|
||||
"TableId": 0,
|
||||
"Override": 0,
|
||||
"English": 0,
|
||||
"Comment": 0
|
||||
},
|
||||
"ColorValue": {
|
||||
"Blue": 0,
|
||||
"Green": 0,
|
||||
"Red": 0,
|
||||
"Alpha": 0
|
||||
},
|
||||
"VectorValue": {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0
|
||||
},
|
||||
"ArrayValue": [],
|
||||
"StructValue": {}
|
||||
},
|
||||
"21": {
|
||||
"Kind": 0,
|
||||
"MasterPropertyId": 21,
|
||||
"UnsignedValue": 4,
|
||||
"IntegerValue": 0,
|
||||
"FloatValue": 0,
|
||||
"BoolValue": false,
|
||||
"StringInfoValue": {
|
||||
"Token": 0,
|
||||
"StringId": 0,
|
||||
"TableId": 0,
|
||||
"Override": 0,
|
||||
"English": 0,
|
||||
"Comment": 0
|
||||
},
|
||||
"ColorValue": {
|
||||
"Blue": 0,
|
||||
"Green": 0,
|
||||
"Red": 0,
|
||||
"Alpha": 0
|
||||
},
|
||||
"VectorValue": {
|
||||
"X": 0,
|
||||
"Y": 0,
|
||||
"Z": 0
|
||||
},
|
||||
"ArrayValue": [],
|
||||
"StructValue": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DefaultStateId": 0,
|
||||
"FontDid": 1073741850,
|
||||
"HJustify": 0,
|
||||
"VJustify": 2,
|
||||
"FontColor": {
|
||||
"X": 1,
|
||||
"Y": 1,
|
||||
"Z": 1,
|
||||
"W": 1
|
||||
},
|
||||
"StateMedia": {},
|
||||
"StateCursors": {},
|
||||
"DefaultStateName": "",
|
||||
"Children": []
|
||||
}
|
||||
|
|
@ -99,6 +99,7 @@ public sealed class WeenieErrorMessagesTests
|
|||
[Theory]
|
||||
[InlineData(0x0036u, "Action cancelled!")]
|
||||
[InlineData(0x003Du, "You charged too far!")]
|
||||
[InlineData(0x004Au, "Ack! You killed yourself!")]
|
||||
public void Format_CombatMovementErrors(uint code, string expected)
|
||||
=> Assert.Equal(expected, WeenieErrorMessages.Format(code, null));
|
||||
|
||||
|
|
|
|||
|
|
@ -17,17 +17,14 @@ public sealed class DisplaySettingsTests
|
|||
// · Resolution matches WindowOptions (1280×720 in GameWindow.Run)
|
||||
// · FieldOfView matches camera FovY (60° = π/3)
|
||||
// · VSync matches WindowOptions (false during dev)
|
||||
// · ShowFps true preserves the perf string in the title bar
|
||||
// Net effect: opening Display + Save with no edits is a visual
|
||||
// no-op (no window resize, no camera FovY change, no title
|
||||
// bar change).
|
||||
// · ShowFps false matches retail's initially-hidden SmartBox meter
|
||||
var d = DisplaySettings.Default;
|
||||
Assert.Equal("1280x720", d.Resolution);
|
||||
Assert.False(d.Fullscreen);
|
||||
Assert.False(d.VSync);
|
||||
Assert.Equal(60f, d.FieldOfView);
|
||||
Assert.Equal(1.0f, d.Gamma);
|
||||
Assert.True(d.ShowFps);
|
||||
Assert.False(d.ShowFps);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -65,7 +62,7 @@ public sealed class DisplaySettingsTests
|
|||
// Other fields untouched.
|
||||
Assert.Equal("1280x720", d.Resolution);
|
||||
Assert.False(d.VSync);
|
||||
Assert.True(d.ShowFps);
|
||||
Assert.False(d.ShowFps);
|
||||
}
|
||||
|
||||
private static int ParseWidth(string res)
|
||||
|
|
|
|||
|
|
@ -271,12 +271,31 @@ public sealed class SettingsVMTests
|
|||
public void SetDisplay_marks_unsaved_changes()
|
||||
{
|
||||
var (vm, _, _, _, _, _, _, _, _, _) = Build();
|
||||
// Default ShowFps is true → flip to false to ensure the with-
|
||||
// Default ShowFps is false → flip to true to ensure the with-
|
||||
// expression actually mutates a field.
|
||||
vm.SetDisplay(vm.DisplayDraft with { ShowFps = false });
|
||||
vm.SetDisplay(vm.DisplayDraft with { ShowFps = true });
|
||||
Assert.True(vm.HasUnsavedChanges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyExternalDisplayChange_updates_both_snapshots_and_preserves_other_drafts()
|
||||
{
|
||||
var (vm, _, _, _, _, _, _, _, _, _) = Build();
|
||||
|
||||
vm.SetDisplay(vm.DisplayDraft with { FieldOfView = 90f });
|
||||
Assert.True(vm.HasUnsavedChanges);
|
||||
|
||||
vm.ApplyExternalDisplayChange(display => display with { ShowFps = true });
|
||||
|
||||
Assert.True(vm.DisplayDraft.ShowFps);
|
||||
Assert.Equal(90f, vm.DisplayDraft.FieldOfView);
|
||||
Assert.True(vm.HasUnsavedChanges);
|
||||
|
||||
vm.Cancel();
|
||||
Assert.True(vm.DisplayDraft.ShowFps);
|
||||
Assert.Equal(DisplaySettings.Default.FieldOfView, vm.DisplayDraft.FieldOfView);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Save_invokes_display_callback_with_draft()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue