using System.Diagnostics;
using System.Numerics;
using System.Security.Cryptography;
using System.Text.Json;
using Microsoft.UI.Windowing;
using Windows.Graphics;
using XFEEDToolBox.Controls;
using XFEEDToolBox.Core.Rendering;
using XFEEDToolBox.Rendering;
using XFEEDToolBox.Views;
namespace XFEEDToolBox;
public partial class App
{
private static async Task RunModelProbeAsync(string reportPath)
{
var output = Path.GetDirectoryName(Path.GetFullPath(reportPath))!; Directory.CreateDirectory(output);
void Stage(string stage) => File.AppendAllText(reportPath + ".progress", DateTimeOffset.UtcNow.ToString("o") + " " + stage + Environment.NewLine);
Stage("start");
object? rendering = null, refit = null; var models = new List<object>(); var lifecycle = new List<object>(); var resizing = new List<object>(); string? failure = null;
try
{
var executable = ViewModel.PluginGameExecutable;
await Task.Run(() =>
{
var reader = new EliteLocalModels(executable);
foreach (var model in EliteLocalModels.Reference.Models)
{
var mesh = reader.Read(model.ShipType)!;
var min = new Vector3(float.PositiveInfinity); var max = new Vector3(float.NegativeInfinity);
foreach (var v in mesh.Vertices) { min = Vector3.Min(min, v.Position); max = Vector3.Max(max, v.Position); }
models.Add(new { model.ShipType, model.ResourceName, model.ResourceHash, mesh.Triangles, mesh.Sha256,
finite = mesh.Vertices.All(v => float.IsFinite(v.Position.LengthSquared()) && float.IsFinite(v.Normal.LengthSquared())),
dimensions = new[] { max.X - min.X, max.Y - min.Y, max.Z - min.Z }, normalized = (max - min).Length() > 0 && Math.Abs(Math.Max(max.X - min.X, Math.Max(max.Y - min.Y, max.Z - min.Z)) - 2) < .001 });
}
});
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
Stage("all-models-verified");
((OverlappedPresenter)MainWindow.AppWindow.Presenter).Restore(false);
MainWindow.AppWindow.Resize(new SizeInt32(1510, 980)); MainWindow.AppWindow.Move(new(60, 60));
AppShellPage.Current!.Navigate(typeof(MainPage)); MainWindow.Activate();
await Task.Delay(500);
var main = (MainPage)AppShellPage.Current.CurrentPage!;
var preview = (ShipModelView)main.FindName("ShipSchematic");
preview.StartBringIntoView(new BringIntoViewOptions { VerticalAlignmentRatio = .5, AnimationDesired = false });
await Task.Delay(300);
await WaitForModelAsync(preview);
Stage("home-rendering");
await Task.Delay(1800);
var first = await preview.CaptureFrameAsync(); await SaveModelFrameAsync(first, Path.Combine(output, "home-model-first.png"));
var frames = preview.Frames; var clock = Stopwatch.StartNew(); await Task.Delay(3000);
var fps = (preview.Frames - frames) / clock.Elapsed.TotalSeconds; var timing = preview.MonitorTiming;
var second = await preview.CaptureFrameAsync(); await SaveModelFrameAsync(second, Path.Combine(output, "home-model-rotated.png"));
Stage("rotation-measured");
var changedPixels = first.Width == second.Width && first.Height == second.Height ? first.Pixels.Zip(second.Pixels).Count(p => p.First != p.Second) : -1;
var presenter = (OverlappedPresenter)MainWindow.AppWindow.Presenter;
Stage("minimizing"); presenter.Minimize(); Stage("minimized"); await Task.Delay(400); var before = preview.Frames; await Task.Delay(600); var paused = preview.Frames == before;
presenter.Restore(false); Stage("restored"); await Task.Delay(400);
var scroll = (ScrollViewer)main.Content;
scroll.ChangeView(null, 0, null, true); await Task.Delay(400);
var offscreenFrames = preview.Frames; await Task.Delay(600);
var pausedWhenOffscreen = preview.Frames == offscreenFrames;
preview.StartBringIntoView(new BringIntoViewOptions { VerticalAlignmentRatio = .5, AnimationDesired = false });
await Task.Delay(400);
var resumedWhenVisible = preview.Frames > offscreenFrames;
Stage("visibility-tested");
rendering = new { preview.ShipType, preview.ModelSource, preview.ModelHash, preview.Triangles, preview.Adapter, preview.GraphicsBackend, preview.FeatureEdges,
first.Width, first.Height, monitor = timing.Monitor, displayModeHz = timing.ModeHz, desktopCompositionHz = timing.RefreshHz, measuredFps = fps, preview.AverageDrawMilliseconds, preview.AverageWaitMilliseconds,
synchronized = timing.RefreshHz > 1 && Math.Abs(fps - timing.RefreshHz) / timing.RefreshHz < .12,
slowRotation = second.AngleDegrees > first.AngleDegrees + 6 && second.AngleDegrees < first.AngleDegrees + 18,
changedPixels, pixelsPresent = second.Pixels.Where((_, i) => i % 4 == 3).Count(a => a > 0), framing = MeasureHologramFrame(second), pausedWhenMinimized = paused, pausedWhenOffscreen, resumedWhenVisible, preview.RenderError };
foreach (var size in new[] { new SizeInt32(1900, 1100), new SizeInt32(2560, 1440), new SizeInt32(1510, 980) })
{
MainWindow.AppWindow.Resize(size); await Task.Delay(300);
preview.StartBringIntoView(new BringIntoViewOptions { VerticalAlignmentRatio = .5, AnimationDesired = false }); await Task.Delay(250);
var resized = await preview.CaptureFrameAsync();
resizing.Add(new { windowWidth = size.Width, windowHeight = size.Height, resized.Width, resized.Height, framing = MeasureHologramFrame(resized), preview.RenderError });
}
Stage("native-resizing-tested");
using (await preview.FreezeForXamlProbeAsync()) await CapturePageAsync(main, Path.Combine(output, "home-layout-with-model.png"));
Stage("home-captured");
MainWindow.Activate(); await Task.Delay(200);
AppShellPage.Current.Navigate(typeof(RefitPage)); await Task.Delay(400);
Stage("refit-navigated");
var stoppedWhenUnloaded = !preview.IsRendering;
var page = (RefitPage)AppShellPage.Current.CurrentPage!; var background = (ShipModelView)page.FindName("RefitShipModel");
await WaitForModelAsync(background); var initial = background.ShipType;
Stage("refit-rendering");
// A stock Sidewinder hash generated independently by the upstream EDSY V8 oracle.
ViewModel.RefitEdsyLink = "https://edsy.org/#/L=K100000H4C0S00,FBR00FBR00,,9p300A4Y00AKA00AZo00Ans00B1U00BH600BWQ00,,7Og0003w00mpU0nG0-0nF0-";
await ViewModel.ImportEdsyCommand.ExecuteAsync(null);
await Task.Delay(500); await WaitForModelAsync(background);
var image = await background.CaptureFrameAsync(); await SaveModelFrameAsync(image, Path.Combine(output, "refit-model.png"));
refit = new { initial, selected = ViewModel.RefitShipType, background = background.ShipType, background.ModelSource,
switched = background.ShipType == ViewModel.RefitShipType && background.ShipType != initial,
sourceMatchesSelected = background.ModelSource.Contains("#" + EliteLocalModels.Reference.Models.Single(m => EliteLocalModels.Normalize(m.ShipType) == EliteLocalModels.Normalize(background.ShipType)).ResourceName),
stoppedWhenUnloaded, framing = MeasureHologramFrame(image), background.GraphicsBackend, background.FeatureEdges, unmodifiedGradeEmpty = ViewModel.RefitGrade == 0 && ((XFEEDToolBox.Controls.EngineeringGradePicker)page.FindName("EngineeringGrade")).Value == -1,
ViewModel.RefitEdsyStatus, documentShip = ViewModel.RefitDocument?.ShipType, background.RenderError };
using (await background.FreezeForXamlProbeAsync()) await CapturePageAsync(page, Path.Combine(output, "refit-layout-with-model.png"));
await background.StopAsync();
Stage("refit-stopped");
for (var repeat = 0; repeat < 4; repeat++)
{
AppShellPage.Current.Navigate(typeof(MainPage)); await Task.Delay(300);
main = (MainPage)AppShellPage.Current.CurrentPage!; preview = (ShipModelView)main.FindName("ShipSchematic");
preview.StartBringIntoView(new BringIntoViewOptions { VerticalAlignmentRatio = .5, AnimationDesired = false });
await WaitForModelAsync(preview); await Task.Delay(200);
presenter.Minimize(); await Task.Delay(300); var count = preview.Frames; await Task.Delay(300);
var halted = count == preview.Frames;
presenter.Restore(false); await Task.Delay(350);
var resumed = preview.Frames > count && preview.RenderError.Length == 0;
AppShellPage.Current.Navigate(typeof(RefitPage)); await Task.Delay(400);
var oldStopped = !preview.IsRendering;
page = (RefitPage)AppShellPage.Current.CurrentPage!; background = (ShipModelView)page.FindName("RefitShipModel");
await WaitForModelAsync(background);
lifecycle.Add(new { repeat, halted, resumed, oldStopped, error = preview.RenderError, refitError = background.RenderError });
if (!halted || !resumed || !oldStopped || background.RenderError.Length > 0) throw new InvalidOperationException("模型页面切换/最小化恢复检查失败。");
Stage("lifecycle-" + repeat);
}
await background.StopAsync();
}
catch (Exception e) { failure = e.ToString(); }
await File.WriteAllTextAsync(reportPath, JsonSerializer.Serialize(new { models, rendering, refit, lifecycle, resizing, failure, gameActionsInvoked = false, gameFilesChanged = false }, new JsonSerializerOptions { WriteIndented = true }));
MainWindow.Close();
}
private static object MeasureHologramFrame(ShipModelView.RenderedFrame frame)
{
var left = frame.Width; var top = frame.Height; var right = -1; var bottom = -1; var transparent = 0; var premultiplied = true;
for (var y = 0; y < frame.Height; y++) for (var x = 0; x < frame.Width; x++)
{
var i = (y * frame.Width + x) * 4; var a = frame.Pixels[i + 3];
if (a == 0) transparent++; else { left = Math.Min(left, x); top = Math.Min(top, y); right = Math.Max(right, x); bottom = Math.Max(bottom, y); }
premultiplied &= frame.Pixels[i] <= a + 1 && frame.Pixels[i + 1] <= a + 1 && frame.Pixels[i + 2] <= a + 1;
}
return new { left, top, right, bottom, transparentPixels = transparent, transparentFraction = (double)transparent / (frame.Width * frame.Height), premultiplied,
marginOnEverySide = left > 4 && top > 4 && right < frame.Width - 5 && bottom < frame.Height - 5,
centred = Math.Abs((left + right + 1) / 2.0 - frame.Width / 2.0) < frame.Width * .12 && Math.Abs((top + bottom + 1) / 2.0 - frame.Height / 2.0) < frame.Height * .12 };
}
private static async Task WaitForModelAsync(ShipModelView view)
{
var clock = Stopwatch.StartNew();
while (view.Frames < 3 && view.RenderError.Length == 0 && clock.Elapsed < TimeSpan.FromSeconds(15)) await Task.Delay(100);
if (view.RenderError.Length > 0 || view.Frames < 3) throw new InvalidOperationException("模型未开始渲染:" + view.ShipType + " · " + view.RenderError);
}
private static async Task SaveModelFrameAsync(ShipModelView.RenderedFrame frame, string path)
{
using var output = File.Open(path, FileMode.Create);
var encoder = await Windows.Graphics.Imaging.BitmapEncoder.CreateAsync(Windows.Graphics.Imaging.BitmapEncoder.PngEncoderId, output.AsRandomAccessStream());
encoder.SetPixelData(Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8, Windows.Graphics.Imaging.BitmapAlphaMode.Premultiplied,
(uint)frame.Width, (uint)frame.Height, 96, 96, frame.Pixels);
await encoder.FlushAsync();
}
}
using System.Diagnostics;
using System.Numerics;
using System.Security.Cryptography;
using System.Text.Json;
using Microsoft.UI.Windowing;
using Windows.Graphics;
using XFEEDToolBox.Controls;
using XFEEDToolBox.Core.Rendering;
using XFEEDToolBox.Rendering;
using XFEEDToolBox.Views;
namespace XFEEDToolBox;
public partial class App
{
private static async Task RunModelProbeAsync(string reportPath)
{
var output = Path.GetDirectoryName(Path.GetFullPath(reportPath))!; Directory.CreateDirectory(output);
void Stage(string stage) => File.AppendAllText(reportPath + ".progress", DateTimeOffset.UtcNow.ToString("o") + " " + stage + Environment.NewLine);
Stage("start");
object? rendering = null, refit = null; var models = new List<object>(); var lifecycle = new List<object>(); var resizing = new List<object>(); string? failure = null;
try
{
var executable = ViewModel.PluginGameExecutable;
await Task.Run(() =>
{
var reader = new EliteLocalModels(executable);
foreach (var model in EliteLocalModels.Reference.Models)
{
var mesh = reader.Read(model.ShipType)!;
var min = new Vector3(float.PositiveInfinity); var max = new Vector3(float.NegativeInfinity);
foreach (var v in mesh.Vertices) { min = Vector3.Min(min, v.Position); max = Vector3.Max(max, v.Position); }
models.Add(new { model.ShipType, model.ResourceName, model.ResourceHash, mesh.Triangles, mesh.Sha256,
finite = mesh.Vertices.All(v => float.IsFinite(v.Position.LengthSquared()) && float.IsFinite(v.Normal.LengthSquared())),
dimensions = new[] { max.X - min.X, max.Y - min.Y, max.Z - min.Z }, normalized = (max - min).Length() > 0 && Math.Abs(Math.Max(max.X - min.X, Math.Max(max.Y - min.Y, max.Z - min.Z)) - 2) < .001 });
}
});
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
Stage("all-models-verified");
((OverlappedPresenter)MainWindow.AppWindow.Presenter).Restore(false);
MainWindow.AppWindow.Resize(new SizeInt32(1510, 980)); MainWindow.AppWindow.Move(new(60, 60));
AppShellPage.Current!.Navigate(typeof(MainPage)); MainWindow.Activate();
await Task.Delay(500);
var main = (MainPage)AppShellPage.Current.CurrentPage!;
var preview = (ShipModelView)main.FindName("ShipSchematic");
preview.StartBringIntoView(new BringIntoViewOptions { VerticalAlignmentRatio = .5, AnimationDesired = false });
await Task.Delay(300);
await WaitForModelAsync(preview);
Stage("home-rendering");
await Task.Delay(1800);
var first = await preview.CaptureFrameAsync(); await SaveModelFrameAsync(first, Path.Combine(output, "home-model-first.png"));
var frames = preview.Frames; var clock = Stopwatch.StartNew(); await Task.Delay(3000);
var fps = (preview.Frames - frames) / clock.Elapsed.TotalSeconds; var timing = preview.MonitorTiming;
var second = await preview.CaptureFrameAsync(); await SaveModelFrameAsync(second, Path.Combine(output, "home-model-rotated.png"));
Stage("rotation-measured");
var changedPixels = first.Width == second.Width && first.Height == second.Height ? first.Pixels.Zip(second.Pixels).Count(p => p.First != p.Second) : -1;
var presenter = (OverlappedPresenter)MainWindow.AppWindow.Presenter;
Stage("minimizing"); presenter.Minimize(); Stage("minimized"); await Task.Delay(400); var before = preview.Frames; await Task.Delay(600); var paused = preview.Frames == before;
presenter.Restore(false); Stage("restored"); await Task.Delay(400);
var scroll = (ScrollViewer)main.Content;
scroll.ChangeView(null, 0, null, true); await Task.Delay(400);
var offscreenFrames = preview.Frames; await Task.Delay(600);
var pausedWhenOffscreen = preview.Frames == offscreenFrames;
preview.StartBringIntoView(new BringIntoViewOptions { VerticalAlignmentRatio = .5, AnimationDesired = false });
await Task.Delay(400);
var resumedWhenVisible = preview.Frames > offscreenFrames;
Stage("visibility-tested");
rendering = new { preview.ShipType, preview.ModelSource, preview.ModelHash, preview.Triangles, preview.Adapter, preview.GraphicsBackend, preview.FeatureEdges,
first.Width, first.Height, monitor = timing.Monitor, displayModeHz = timing.ModeHz, desktopCompositionHz = timing.RefreshHz, measuredFps = fps, preview.AverageDrawMilliseconds, preview.AverageWaitMilliseconds,
synchronized = timing.RefreshHz > 1 && Math.Abs(fps - timing.RefreshHz) / timing.RefreshHz < .12,
slowRotation = second.AngleDegrees > first.AngleDegrees + 6 && second.AngleDegrees < first.AngleDegrees + 18,
changedPixels, pixelsPresent = second.Pixels.Where((_, i) => i % 4 == 3).Count(a => a > 0), framing = MeasureHologramFrame(second), pausedWhenMinimized = paused, pausedWhenOffscreen, resumedWhenVisible, preview.RenderError };
foreach (var size in new[] { new SizeInt32(1900, 1100), new SizeInt32(2560, 1440), new SizeInt32(1510, 980) })
{
MainWindow.AppWindow.Resize(size); await Task.Delay(300);
preview.StartBringIntoView(new BringIntoViewOptions { VerticalAlignmentRatio = .5, AnimationDesired = false }); await Task.Delay(250);
var resized = await preview.CaptureFrameAsync();
resizing.Add(new { windowWidth = size.Width, windowHeight = size.Height, resized.Width, resized.Height, framing = MeasureHologramFrame(resized), preview.RenderError });
}
Stage("native-resizing-tested");
using (await preview.FreezeForXamlProbeAsync()) await CapturePageAsync(main, Path.Combine(output, "home-layout-with-model.png"));
Stage("home-captured");
MainWindow.Activate(); await Task.Delay(200);
AppShellPage.Current.Navigate(typeof(RefitPage)); await Task.Delay(400);
Stage("refit-navigated");
var stoppedWhenUnloaded = !preview.IsRendering;
var page = (RefitPage)AppShellPage.Current.CurrentPage!; var background = (ShipModelView)page.FindName("RefitShipModel");
await WaitForModelAsync(background); var initial = background.ShipType;
Stage("refit-rendering");
// A stock Sidewinder hash generated independently by the upstream EDSY V8 oracle.
ViewModel.RefitEdsyLink = "https://edsy.org/#/L=K100000H4C0S00,FBR00FBR00,,9p300A4Y00AKA00AZo00Ans00B1U00BH600BWQ00,,7Og0003w00mpU0nG0-0nF0-";
await ViewModel.ImportEdsyCommand.ExecuteAsync(null);
await Task.Delay(500); await WaitForModelAsync(background);
var image = await background.CaptureFrameAsync(); await SaveModelFrameAsync(image, Path.Combine(output, "refit-model.png"));
refit = new { initial, selected = ViewModel.RefitShipType, background = background.ShipType, background.ModelSource,
switched = background.ShipType == ViewModel.RefitShipType && background.ShipType != initial,
sourceMatchesSelected = background.ModelSource.Contains("#" + EliteLocalModels.Reference.Models.Single(m => EliteLocalModels.Normalize(m.ShipType) == EliteLocalModels.Normalize(background.ShipType)).ResourceName),
stoppedWhenUnloaded, framing = MeasureHologramFrame(image), background.GraphicsBackend, background.FeatureEdges, unmodifiedGradeEmpty = ViewModel.RefitGrade == 0 && ((XFEEDToolBox.Controls.EngineeringGradePicker)page.FindName("EngineeringGrade")).Value == -1,
ViewModel.RefitEdsyStatus, documentShip = ViewModel.RefitDocument?.ShipType, background.RenderError };
using (await background.FreezeForXamlProbeAsync()) await CapturePageAsync(page, Path.Combine(output, "refit-layout-with-model.png"));
await background.StopAsync();
Stage("refit-stopped");
for (var repeat = 0; repeat < 4; repeat++)
{
AppShellPage.Current.Navigate(typeof(MainPage)); await Task.Delay(300);
main = (MainPage)AppShellPage.Current.CurrentPage!; preview = (ShipModelView)main.FindName("ShipSchematic");
preview.StartBringIntoView(new BringIntoViewOptions { VerticalAlignmentRatio = .5, AnimationDesired = false });
await WaitForModelAsync(preview); await Task.Delay(200);
presenter.Minimize(); await Task.Delay(300); var count = preview.Frames; await Task.Delay(300);
var halted = count == preview.Frames;
presenter.Restore(false); await Task.Delay(350);
var resumed = preview.Frames > count && preview.RenderError.Length == 0;
AppShellPage.Current.Navigate(typeof(RefitPage)); await Task.Delay(400);
var oldStopped = !preview.IsRendering;
page = (RefitPage)AppShellPage.Current.CurrentPage!; background = (ShipModelView)page.FindName("RefitShipModel");
await WaitForModelAsync(background);
lifecycle.Add(new { repeat, halted, resumed, oldStopped, error = preview.RenderError, refitError = background.RenderError });
if (!halted || !resumed || !oldStopped || background.RenderError.Length > 0) throw new InvalidOperationException("模型页面切换/最小化恢复检查失败。");
Stage("lifecycle-" + repeat);
}
await background.StopAsync();
}
catch (Exception e) { failure = e.ToString(); }
await File.WriteAllTextAsync(reportPath, JsonSerializer.Serialize(new { models, rendering, refit, lifecycle, resizing, failure, gameActionsInvoked = false, gameFilesChanged = false }, new JsonSerializerOptions { WriteIndented = true }));
MainWindow.Close();
}
private static object MeasureHologramFrame(ShipModelView.RenderedFrame frame)
{
var left = frame.Width; var top = frame.Height; var right = -1; var bottom = -1; var transparent = 0; var premultiplied = true;
for (var y = 0; y < frame.Height; y++) for (var x = 0; x < frame.Width; x++)
{
var i = (y * frame.Width + x) * 4; var a = frame.Pixels[i + 3];
if (a == 0) transparent++; else { left = Math.Min(left, x); top = Math.Min(top, y); right = Math.Max(right, x); bottom = Math.Max(bottom, y); }
premultiplied &= frame.Pixels[i] <= a + 1 && frame.Pixels[i + 1] <= a + 1 && frame.Pixels[i + 2] <= a + 1;
}
return new { left, top, right, bottom, transparentPixels = transparent, transparentFraction = (double)transparent / (frame.Width * frame.Height), premultiplied,
marginOnEverySide = left > 4 && top > 4 && right < frame.Width - 5 && bottom < frame.Height - 5,
centred = Math.Abs((left + right + 1) / 2.0 - frame.Width / 2.0) < frame.Width * .12 && Math.Abs((top + bottom + 1) / 2.0 - frame.Height / 2.0) < frame.Height * .12 };
}
private static async Task WaitForModelAsync(ShipModelView view)
{
var clock = Stopwatch.StartNew();
while (view.Frames < 3 && view.RenderError.Length == 0 && clock.Elapsed < TimeSpan.FromSeconds(15)) await Task.Delay(100);
if (view.RenderError.Length > 0 || view.Frames < 3) throw new InvalidOperationException("模型未开始渲染:" + view.ShipType + " · " + view.RenderError);
}
private static async Task SaveModelFrameAsync(ShipModelView.RenderedFrame frame, string path)
{
using var output = File.Open(path, FileMode.Create);
var encoder = await Windows.Graphics.Imaging.BitmapEncoder.CreateAsync(Windows.Graphics.Imaging.BitmapEncoder.PngEncoderId, output.AsRandomAccessStream());
encoder.SetPixelData(Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8, Windows.Graphics.Imaging.BitmapAlphaMode.Premultiplied,
(uint)frame.Width, (uint)frame.Height, 96, 96, frame.Pixels);
await encoder.FlushAsync();
}
}