From 150c2c9716dc0ca24f6c9d2412906afba51e9c3a Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Thu, 28 Nov 2024 02:38:01 -0800 Subject: [PATCH] Plugin: Add Raphael --- Craftimizer/Craftimizer.csproj | 2 + Craftimizer/ImGuiUtils.cs | 20 +- Craftimizer/Plugin.cs | 8 + Craftimizer/Utils/DynamicBars.cs | 16 +- Craftimizer/Utils/SimulatedMacro.cs | 46 ++++- Craftimizer/Windows/MacroEditor.cs | 8 +- Craftimizer/Windows/RecipeNote.cs | 3 +- Craftimizer/Windows/Settings.cs | 281 ++++++++++++++++------------ Craftimizer/Windows/SynthHelper.cs | 1 + Craftimizer/packages.lock.json | 15 +- 10 files changed, 265 insertions(+), 135 deletions(-) diff --git a/Craftimizer/Craftimizer.csproj b/Craftimizer/Craftimizer.csproj index 60294fd..a554c5a 100644 --- a/Craftimizer/Craftimizer.csproj +++ b/Craftimizer/Craftimizer.csproj @@ -13,7 +13,9 @@ enable true false + win-x64 false + false true diff --git a/Craftimizer/ImGuiUtils.cs b/Craftimizer/ImGuiUtils.cs index 0211d54..e2ba89b 100644 --- a/Craftimizer/ImGuiUtils.cs +++ b/Craftimizer/ImGuiUtils.cs @@ -226,7 +226,25 @@ internal static class ImGuiUtils public static void ArcProgress(float value, float radius, float ratio, uint backgroundColor, uint filledColor) { - Arc(MathF.PI / 2, MathF.PI / 2 - MathF.Tau * Math.Clamp(value, 0, 1), radius, ratio, backgroundColor, filledColor); + float startAngle, endAngle; + + // https://github.com/ocornut/imgui/commit/c895e987adf746a997b655c64a6a8916c549ff6f#diff-d750e175eb584ba76bc560b8e54cf113ccbb31dd33f75078c1588925e197a3afR1304-R1310 + if (value < 0) + { + const float ArcSize = 0.15f; + startAngle = -value % 1; + endAngle = startAngle + ArcSize; + + startAngle = float.Lerp(MathF.PI / 2, -3 * MathF.PI / 2, startAngle); + endAngle = float.Lerp(MathF.PI / 2, -3 * MathF.PI / 2, endAngle); + } + else + { + startAngle = MathF.PI / 2; + endAngle = MathF.PI / 2 - MathF.Tau * Math.Clamp(value, 0, 1); + } + + Arc(startAngle, endAngle, radius, ratio, backgroundColor, filledColor); } public sealed class ViolinData diff --git a/Craftimizer/Plugin.cs b/Craftimizer/Plugin.cs index 178333c..72acc0a 100644 --- a/Craftimizer/Plugin.cs +++ b/Craftimizer/Plugin.cs @@ -151,6 +151,14 @@ public sealed class Plugin : IDalamudPlugin ClipboardWindow = new(macros); } + public IActiveNotification DisplaySolverWarning(string text) => + DisplayNotification(new() + { + Content = text, + Title = "Solver Warning", + Type = NotificationType.Warning + }); + public IActiveNotification DisplayNotification(Notification notification) { var ret = Service.NotificationManager.AddNotification(notification); diff --git a/Craftimizer/Utils/DynamicBars.cs b/Craftimizer/Utils/DynamicBars.cs index 7df26d8..a77e4fe 100644 --- a/Craftimizer/Utils/DynamicBars.cs +++ b/Craftimizer/Utils/DynamicBars.cs @@ -191,9 +191,11 @@ internal static class DynamicBars var progressWidth = availSpace.Value - percentWidth - spacing; var progressColors = Colors.GetSolverProgressColors(solver.ProgressStage); + fraction = Math.Clamp(fraction, 0, 1); + using (ImRaii.PushColor(ImGuiCol.FrameBg, progressColors.Background)) using (ImRaii.PushColor(ImGuiCol.PlotHistogram, progressColors.Foreground)) - ImGui.ProgressBar(Math.Clamp(fraction, 0, 1), new(progressWidth, ImGui.GetFrameHeight()), string.Empty); + ImGui.ProgressBar(solver.IsIndeterminate ? (float)-ImGui.GetTime() : fraction, new(progressWidth, ImGui.GetFrameHeight()), string.Empty); if (ImGui.IsItemHovered()) DrawProgressBarTooltip(solver); ImGui.SameLine(0, spacing); @@ -203,9 +205,15 @@ internal static class DynamicBars public static void DrawProgressBarTooltip(Solver.Solver solver) { - var tooltip = $"Solver Progress: {solver.ProgressValue:N0} / {solver.ProgressMax:N0}"; - if (solver.ProgressValue > solver.ProgressMax) - tooltip += $"\n\nThis is taking longer than expected. Check to see if your gear stats are good and the solver settings are adequate."; + string tooltip; + if (solver.IsIndeterminate) + tooltip = "Initializing Solver"; + else + { + tooltip = $"Solver Progress: {solver.ProgressValue:N0} / {solver.ProgressMax:N0}"; + if (solver.ProgressValue > solver.ProgressMax) + tooltip += $"\n\nThis is taking longer than expected. Check to see if your gear stats are good and the solver settings are adequate."; + } ImGuiUtils.TooltipWrapped(tooltip); } } diff --git a/Craftimizer/Utils/SimulatedMacro.cs b/Craftimizer/Utils/SimulatedMacro.cs index cb9753f..edbb111 100644 --- a/Craftimizer/Utils/SimulatedMacro.cs +++ b/Craftimizer/Utils/SimulatedMacro.cs @@ -1,6 +1,7 @@ using Craftimizer.Plugin; using Craftimizer.Simulator; using Craftimizer.Simulator.Actions; +using DotNext.Collections.Generic; using System; using System.Collections.Generic; using System.Linq; @@ -95,6 +96,7 @@ internal sealed class SimulatedMacro private sealed record Step { public ActionType Action { get; } + public bool IsEphemeral { get; } // State *after* executing the action public ActionResponse Response { get; private set; } public SimulationState State { get; private set; } @@ -107,9 +109,10 @@ internal sealed class SimulatedMacro } // Call recalculate after this please! - public Step(ActionType action) + public Step(ActionType action, bool isEphemeral = false) { Action = action; + IsEphemeral = isEphemeral; } public SimulationState Recalculate(Sim sim, in SimulationState lastState) @@ -125,6 +128,7 @@ internal sealed class SimulatedMacro }; private List Macro { get; set; } = []; + private SimulationState initialState; public SimulationState InitialState { @@ -140,6 +144,7 @@ internal sealed class SimulatedMacro } private object QueueLock { get; } = new(); private List QueuedSteps { get; set; } = []; + private List QueuedEphemeralSteps { get; set; } = []; public SimulationState FirstState => Macro.Count > 0 ? Macro[0].State : InitialState; public SimulationState State => Macro.Count > 0 ? Macro[^1].State : InitialState; @@ -158,7 +163,7 @@ internal sealed class SimulatedMacro public Reliablity GetReliability(RecipeData recipeData, Index? idx = null) => Macro.Count > 0 ? - Macro[idx ?? ^1].GetReliability(InitialState, Macro.Select(m => m.Action), recipeData) : + Macro[idx ?? ^1].GetReliability(InitialState, Actions.ToArray(), recipeData) : new(InitialState, Array.Empty(), 0, recipeData); private void TryRecalculateFrom(int index) @@ -213,15 +218,41 @@ internal sealed class SimulatedMacro TryRecalculateFrom(Math.Min(fromIdx, toIdx)); } + public void RemoveEphemeral() + { + for (var i = Macro.Count - 1; i >= 0; --i) + { + if (Macro[i].IsEphemeral) + Macro.RemoveAt(i); + } + } + public int Enqueue(ActionType action, int? maxSize = null) { lock (QueueLock) { if (maxSize is { } size && QueuedSteps.Count + Macro.Count >= size) return size; - + + QueuedEphemeralSteps.Clear(); QueuedSteps.Add(new(action)); - return QueuedSteps.Count + Macro.Count; + return Macro.Count + QueuedSteps.Count; + } + } + + public int EnqueueEphemeral(IEnumerable actions, int? maxSize = null) + { + lock (QueueLock) + { + QueuedEphemeralSteps.Clear(); + foreach (var action in actions) + { + if (maxSize is { } size && QueuedSteps.Count + QueuedEphemeralSteps.Count + Macro.Count >= size) + return size; + + QueuedEphemeralSteps.Add(new(action, true)); + } + return Macro.Count + QueuedSteps.Count + QueuedEphemeralSteps.Count; } } @@ -230,6 +261,7 @@ internal sealed class SimulatedMacro lock (QueueLock) { QueuedSteps.Clear(); + QueuedEphemeralSteps.Clear(); } } @@ -237,11 +269,15 @@ internal sealed class SimulatedMacro { lock (QueueLock) { - if (QueuedSteps.Count > 0) + if (QueuedSteps.Count > 0 || QueuedEphemeralSteps.Count > 0) { + RemoveEphemeral(); var startIdx = Macro.Count; + Macro.AddRange(QueuedSteps); + Macro.AddRange(QueuedEphemeralSteps); QueuedSteps.Clear(); + QueuedEphemeralSteps.Clear(); TryRecalculateFrom(startIdx); } } diff --git a/Craftimizer/Windows/MacroEditor.cs b/Craftimizer/Windows/MacroEditor.cs index 6b5a390..1348407 100644 --- a/Craftimizer/Windows/MacroEditor.cs +++ b/Craftimizer/Windows/MacroEditor.cs @@ -1182,7 +1182,7 @@ public sealed class MacroEditor : Window, IDisposable var actionBase = action.Base(); var failedAction = response != ActionResponse.UsedAction; using var id = ImRaii.PushId(i); - if (ImGui.ImageButton(action.GetIcon(RecipeData!.ClassJob).ImGuiHandle, new(imageSize), default, Vector2.One, 0, default, failedAction ? new(1, 1, 1, ImGui.GetStyle().DisabledAlpha) : Vector4.One)) + if (ImGui.ImageButton(action.GetIcon(RecipeData!.ClassJob).ImGuiHandle, new(imageSize), default, Vector2.One, 0, default, failedAction ? new(1, 1, 1, ImGui.GetStyle().DisabledAlpha) : Vector4.One) && !SolverRunning) RemoveStep(i); if (response is ActionResponse.ActionNotUnlocked || ( @@ -1550,10 +1550,14 @@ public sealed class MacroEditor : Window, IDisposable var solver = new Solver.Solver(config, state) { Token = token }; solver.OnLog += Log.Debug; + solver.OnWarn += t => Service.Plugin.DisplaySolverWarning(t); solver.OnNewAction += a => Macro.Enqueue(a); + solver.OnSuggestSolution += a => Macro.EnqueueEphemeral(a.Actions); SolverObject = solver; solver.Start(); - _ = solver.GetTask().GetAwaiter().GetResult(); + var t = solver.GetTask(); + _ = t.ContinueWith(_ => Macro.RemoveEphemeral()); + _ = t.GetAwaiter().GetResult(); token.ThrowIfCancellationRequested(); diff --git a/Craftimizer/Windows/RecipeNote.cs b/Craftimizer/Windows/RecipeNote.cs index a003025..cf66203 100644 --- a/Craftimizer/Windows/RecipeNote.cs +++ b/Craftimizer/Windows/RecipeNote.cs @@ -844,7 +844,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable { var progressColors = Colors.GetSolverProgressColors(solver.ProgressStage); ImGuiUtils.ArcProgress( - fraction, + solver.IsIndeterminate ? (float)-ImGui.GetTime() : fraction, windowHeight / 2f + 2, .5f, ImGui.ColorConvertFloat4ToU32(progressColors.Background), @@ -1187,6 +1187,7 @@ public sealed unsafe class RecipeNote : Window, IDisposable var solver = new Solver.Solver(config, state) { Token = token }; solver.OnLog += Log.Debug; + solver.OnWarn += t => Service.Plugin.DisplaySolverWarning(t); BestMacroSolver = solver; solver.Start(); var solution = solver.GetTask().GetAwaiter().GetResult(); diff --git a/Craftimizer/Windows/Settings.cs b/Craftimizer/Windows/Settings.cs index 35eb6d0..c674aee 100644 --- a/Craftimizer/Windows/Settings.cs +++ b/Craftimizer/Windows/Settings.cs @@ -149,6 +149,7 @@ public sealed class Settings : Window, IDisposable SolverAlgorithm.Stepwise => "Stepwise", SolverAlgorithm.StepwiseForked => "Stepwise Forked", SolverAlgorithm.StepwiseGenetic => "Stepwise Genetic", + SolverAlgorithm.Raphael => "Optimal (Slow)", _ => "Unknown", }; @@ -160,9 +161,12 @@ public sealed class Settings : Window, IDisposable SolverAlgorithm.Stepwise => "Run through all iterations and pick the next best step, " + "and repeat using previous steps as a starting point", SolverAlgorithm.StepwiseForked => "Stepwise, but using multiple solvers simultaneously", - SolverAlgorithm.StepwiseGenetic => "Stepwise Forked, but the top N next best steps are " + + SolverAlgorithm.StepwiseGenetic => "Stepwise Forked, but the top N next best steps are " + "selected from the solvers, and each one is equally " + "used as a starting point", + SolverAlgorithm.Raphael => "Finds the best solution, every time. This solver has " + + "very different options compared to the rest, as it " + + "is designed using an entirely different algorithm.", _ => "Unknown" }; @@ -527,118 +531,140 @@ public sealed class Settings : Window, IDisposable ref isDirty ); - DrawOption( - "Target Iterations", - "The total number of iterations to run per crafting step. " + - "Higher values require more computational power. Higher values " + - "also may decrease variance, so other values should be tweaked " + - "as necessary to get a more favorable outcome.", - config.Iterations, - 1000, - 1000000, - v => config = config with { Iterations = v }, - ref isDirty - ); - - DrawOption( - "Max Iterations", - "The solver may go about the target iteration value if the craft " + - "is sufficiently difficult, and it wasn't able to find any way to " + - "complete it yet. In rare cases, the solver might go on for a very " + - "long time. This maximum is here to prevent the solver from stealing " + - "all your RAM.", - config.MaxIterations, - config.Iterations, - 5000000, - v => config = config with { MaxIterations = v }, - ref isDirty - ); - - DrawOption( - "Max Step Count", - "The maximum number of crafting steps; this is generally the only " + - "setting you should change, and it should be set to around 5 steps " + - "more than what you'd expect. If this value is too low, the solver " + - "won't learn much per iteration; too high and it will waste time " + - "on useless extra steps.", - config.MaxStepCount, - 1, - 100, - v => config = config with { MaxStepCount = v }, - ref isDirty - ); - - DrawOption( - "Exploration Constant", - "A constant that decides how often the solver will explore new, " + - "possibly good paths. If this value is too high, " + - "moves will mostly be decided at random.", - config.ExplorationConstant, - 0, - 10, - v => config = config with { ExplorationConstant = v }, - ref isDirty - ); - - DrawOption( - "Score Weighting Constant", - "A constant ranging from 0 to 1 that configures how the solver " + - "scores and picks paths to travel to next. A value of 0 means " + - "actions will be chosen based on their average outcome, whereas " + - "1 uses their best outcome achieved so far.", - config.MaxScoreWeightingConstant, - 0, - 1, - v => config = config with { MaxScoreWeightingConstant = v }, - ref isDirty - ); - - using (var d = ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseGenetic))) + if (config.Algorithm != SolverAlgorithm.Raphael) + { DrawOption( - "Max Core Count", - "The number of cores to use when solving. You should use as many " + - "as you can. If it's too high, it will have an effect on your gameplay " + - $"experience. A good estimate would be 1 or 2 cores less than your " + - $"system (FYI, you have {Environment.ProcessorCount} cores), but make sure to accomodate " + - $"for any other tasks you have in the background, if you have any.\n" + - "(Only used in the Forked and Genetic algorithms)", - config.MaxThreadCount, - 1, - Environment.ProcessorCount, - v => config = config with { MaxThreadCount = v }, + "Target Iterations", + "The total number of iterations to run per crafting step. " + + "Higher values require more computational power. Higher values " + + "also may decrease variance, so other values should be tweaked " + + "as necessary to get a more favorable outcome.", + config.Iterations, + 1000, + 1000000, + v => config = config with { Iterations = v }, ref isDirty ); - using (var d = ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseGenetic))) DrawOption( - "Fork Count", - "Split the number of iterations across different solvers. In general, " + - "you should increase this value to at least the number of cores in " + - $"your system (FYI, you have {Environment.ProcessorCount} cores) to attain the most speedup. " + - "The higher the number, the more chance you have of finding a " + - "better local maximum; this concept similar but not equivalent " + - "to the exploration constant.\n" + - "(Only used in the Forked and Genetic algorithms)", - config.ForkCount, - 1, - 500, - v => config = config with { ForkCount = v }, + "Max Iterations", + "The solver may go about the target iteration value if the craft " + + "is sufficiently difficult, and it wasn't able to find any way to " + + "complete it yet. In rare cases, the solver might go on for a very " + + "long time. This maximum is here to prevent the solver from stealing " + + "all your RAM.", + config.MaxIterations, + config.Iterations, + 5000000, + v => config = config with { MaxIterations = v }, ref isDirty ); - using (var d = ImRaii.Disabled(config.Algorithm is not SolverAlgorithm.StepwiseGenetic)) DrawOption( - "Elitist Action Count", - "On every craft step, pick this many top solutions and use them as " + - "the input for the next craft step. For best results, use Fork Count / 2 " + - "and add about 1 or 2 more if needed.\n" + - "(Only used in the Stepwise Genetic algorithm)", - config.FurcatedActionCount, + "Max Step Count", + "The maximum number of crafting steps; this is generally the only " + + "setting you should change, and it should be set to around 5 steps " + + "more than what you'd expect. If this value is too low, the solver " + + "won't learn much per iteration; too high and it will waste time " + + "on useless extra steps.", + config.MaxStepCount, 1, - 500, - v => config = config with { FurcatedActionCount = v }, + 100, + v => config = config with { MaxStepCount = v }, ref isDirty ); + + DrawOption( + "Exploration Constant", + "A constant that decides how often the solver will explore new, " + + "possibly good paths. If this value is too high, " + + "moves will mostly be decided at random.", + config.ExplorationConstant, + 0, + 10, + v => config = config with { ExplorationConstant = v }, + ref isDirty + ); + + DrawOption( + "Score Weighting Constant", + "A constant ranging from 0 to 1 that configures how the solver " + + "scores and picks paths to travel to next. A value of 0 means " + + "actions will be chosen based on their average outcome, whereas " + + "1 uses their best outcome achieved so far.", + config.MaxScoreWeightingConstant, + 0, + 1, + v => config = config with { MaxScoreWeightingConstant = v }, + ref isDirty + ); + + using (ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseGenetic))) + DrawOption( + "Max Core Count", + "The number of cores to use when solving. You should use as many " + + "as you can. If it's too high, it will have an effect on your gameplay " + + $"experience. A good estimate would be 1 or 2 cores less than your " + + $"system (FYI, you have {Environment.ProcessorCount} cores), but make sure to accomodate " + + $"for any other tasks you have in the background, if you have any.\n" + + "(Only used in the Forked and Genetic algorithms)", + config.MaxThreadCount, + 1, + Environment.ProcessorCount, + v => config = config with { MaxThreadCount = v }, + ref isDirty + ); + + using (ImRaii.Disabled(config.Algorithm is not (SolverAlgorithm.OneshotForked or SolverAlgorithm.StepwiseForked or SolverAlgorithm.StepwiseGenetic))) + DrawOption( + "Fork Count", + "Split the number of iterations across different solvers. In general, " + + "you should increase this value to at least the number of cores in " + + $"your system (FYI, you have {Environment.ProcessorCount} cores) to attain the most speedup. " + + "The higher the number, the more chance you have of finding a " + + "better local maximum; this concept similar but not equivalent " + + "to the exploration constant.\n" + + "(Only used in the Forked and Genetic algorithms)", + config.ForkCount, + 1, + 500, + v => config = config with { ForkCount = v }, + ref isDirty + ); + + using (ImRaii.Disabled(config.Algorithm is not SolverAlgorithm.StepwiseGenetic)) + DrawOption( + "Elitist Action Count", + "On every craft step, pick this many top solutions and use them as " + + "the input for the next craft step. For best results, use Fork Count / 2 " + + "and add about 1 or 2 more if needed.\n" + + "(Only used in the Stepwise Genetic algorithm)", + config.FurcatedActionCount, + 1, + 500, + v => config = config with { FurcatedActionCount = v }, + ref isDirty + ); + } + else + { + DrawOption( + "Ensure 100% Reliability", + "Find a rotation that can reach the target quality no matter " + + "how unlucky the random conditions are.", + config.Adversarial, + v => config = config with { Adversarial = v }, + ref isDirty + ); + DrawOption( + "Backload Progress", + "Find a rotation that only uses Progress-increasing actions " + + "at the end of the rotation. May speed up solve times.", + config.BackloadProgress, + v => config = config with { BackloadProgress = v }, + ref isDirty + ); + } } using (var panel = ImRaii2.GroupPanel("Action Pool", -1, out var poolWidth)) @@ -658,27 +684,40 @@ public sealed class Settings : Window, IDisposable using (var panel = ImRaii2.GroupPanel("Advanced", -1, out _)) { - DrawOption( - "Max Rollout Step Count", - "The maximum number of crafting steps every iteration can consider. " + - "Decreasing this value can have unintended side effects. Only change " + - "this value if you absolutely know what you're doing.", - config.MaxRolloutStepCount, - 1, - 50, - v => config = config with { MaxRolloutStepCount = v }, - ref isDirty - ); + if (config.Algorithm != SolverAlgorithm.Raphael) + { + DrawOption( + "Max Rollout Step Count", + "The maximum number of crafting steps every iteration can consider. " + + "Decreasing this value can have unintended side effects. Only change " + + "this value if you absolutely know what you're doing.", + config.MaxRolloutStepCount, + 1, + 50, + v => config = config with { MaxRolloutStepCount = v }, + ref isDirty + ); - DrawOption( - "Strict Actions", - "When finding the next possible actions to execute, use a heuristic " + - "to restrict which actions to attempt taking. This results in a much " + - "better macro at the cost of not finding an extremely creative one.", - config.StrictActions, - v => config = config with { StrictActions = v }, - ref isDirty - ); + DrawOption( + "Strict Actions", + "When finding the next possible actions to execute, use a heuristic " + + "to restrict which actions to attempt taking. This results in a much " + + "better macro at the cost of not finding an extremely creative one.", + config.StrictActions, + v => config = config with { StrictActions = v }, + ref isDirty + ); + } + else + { + DrawOption( + "Unsound Branch Pruning", + "TBD", + config.UnsoundBranchPruning, + v => config = config with { UnsoundBranchPruning = v }, + ref isDirty + ); + } } using (var panel = ImRaii2.GroupPanel("Score Weights (Advanced)", -1, out _)) diff --git a/Craftimizer/Windows/SynthHelper.cs b/Craftimizer/Windows/SynthHelper.cs index 433aae4..960738c 100644 --- a/Craftimizer/Windows/SynthHelper.cs +++ b/Craftimizer/Windows/SynthHelper.cs @@ -611,6 +611,7 @@ public sealed unsafe class SynthHelper : Window, IDisposable var solver = new Solver.Solver(config, state) { Token = token }; solver.OnLog += Log.Debug; + solver.OnWarn += t => Service.Plugin.DisplaySolverWarning(t); solver.OnNewAction += EnqueueAction; SolverObject = solver; solver.Start(); diff --git a/Craftimizer/packages.lock.json b/Craftimizer/packages.lock.json index a260f5f..604c3c4 100644 --- a/Craftimizer/packages.lock.json +++ b/Craftimizer/packages.lock.json @@ -28,6 +28,11 @@ "System.IO.Hashing": "8.0.0" } }, + "Raphael.Net": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "J/D1pw2MT52hENysYs7SA++PBT4sHwUcPnTzjb9/lLigNJazob4v7WbBe2enKpfDAI6hO8al7p2Qlt2258fk8g==" + }, "System.IO.Hashing": { "type": "Transitive", "resolved": "8.0.0", @@ -40,9 +45,17 @@ "type": "Project", "dependencies": { "Craftimizer.Simulator": "[1.0.0, )", - "DotNext": "[5.14.0, )" + "DotNext": "[5.14.0, )", + "Raphael.Net": "[1.0.0, )" } } + }, + "net8.0-windows7.0/win-x64": { + "Raphael.Net": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "J/D1pw2MT52hENysYs7SA++PBT4sHwUcPnTzjb9/lLigNJazob4v7WbBe2enKpfDAI6hO8al7p2Qlt2258fk8g==" + } } } } \ No newline at end of file