Reliability Simulation
This commit is contained in:
@@ -82,6 +82,7 @@ public class Configuration : IPluginConfiguration
|
|||||||
private List<Macro> macros { get; set; } = new();
|
private List<Macro> macros { get; set; } = new();
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public IReadOnlyList<Macro> Macros => macros;
|
public IReadOnlyList<Macro> Macros => macros;
|
||||||
|
public int ReliabilitySimulationCount { get; set; } = 500;
|
||||||
public bool ConditionRandomness { get; set; } = true;
|
public bool ConditionRandomness { get; set; } = true;
|
||||||
public SolverConfig SimulatorSolverConfig { get; set; } = SolverConfig.SimulatorDefault;
|
public SolverConfig SimulatorSolverConfig { get; set; } = SolverConfig.SimulatorDefault;
|
||||||
public SolverConfig SynthHelperSolverConfig { get; set; } = SolverConfig.SynthHelperDefault;
|
public SolverConfig SynthHelperSolverConfig { get; set; } = SolverConfig.SynthHelperDefault;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ using System.Numerics;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using RandomSim = Craftimizer.Simulator.Simulator;
|
||||||
using Sim = Craftimizer.Simulator.SimulatorNoRandom;
|
using Sim = Craftimizer.Simulator.SimulatorNoRandom;
|
||||||
|
|
||||||
namespace Craftimizer.Windows;
|
namespace Craftimizer.Windows;
|
||||||
@@ -76,16 +77,77 @@ public sealed class MacroEditor : Window, IDisposable
|
|||||||
private List<int> HQIngredientCounts { get; set; }
|
private List<int> HQIngredientCounts { get; set; }
|
||||||
private int StartingQuality => RecipeData.CalculateStartingQuality(HQIngredientCounts);
|
private int StartingQuality => RecipeData.CalculateStartingQuality(HQIngredientCounts);
|
||||||
|
|
||||||
|
private readonly record struct SimulationReliablity
|
||||||
|
{
|
||||||
|
public record struct ParamReliability
|
||||||
|
{
|
||||||
|
public int Max { get; private set; }
|
||||||
|
public int Min { get; private set; }
|
||||||
|
public float Average { get; private set; }
|
||||||
|
|
||||||
|
public ParamReliability()
|
||||||
|
{
|
||||||
|
Min = int.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(int value)
|
||||||
|
{
|
||||||
|
Max = Math.Max(Max, value);
|
||||||
|
Min = Math.Min(Min, value);
|
||||||
|
Average += value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Finalize(int count)
|
||||||
|
{
|
||||||
|
if (count != 0)
|
||||||
|
Average /= count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly ParamReliability Progress = new();
|
||||||
|
public readonly ParamReliability Quality = new();
|
||||||
|
|
||||||
|
// Param is either collectability, quality, or hq%, depending on the recipe
|
||||||
|
public readonly ParamReliability Param = new();
|
||||||
|
|
||||||
|
public SimulationReliablity(in SimulationState startState, IEnumerable<ActionType> actions, int iterCount, RecipeData recipeData)
|
||||||
|
{
|
||||||
|
Func<SimulationState, int> getParam;
|
||||||
|
if (recipeData.Recipe.ItemResult.Value!.IsCollectable)
|
||||||
|
getParam = s => s.Collectability;
|
||||||
|
else if (recipeData.Recipe.RequiredQuality > 0)
|
||||||
|
getParam = s => s.Quality;
|
||||||
|
else if (recipeData.RecipeInfo.MaxQuality > 0)
|
||||||
|
getParam = s => s.HQPercent;
|
||||||
|
else
|
||||||
|
getParam = s => 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < iterCount; ++i)
|
||||||
|
{
|
||||||
|
var sim = new RandomSim(startState);
|
||||||
|
var (_, state, _) = sim.ExecuteMultiple(startState, actions);
|
||||||
|
Progress.Add(state.Progress);
|
||||||
|
Quality.Add(state.Quality);
|
||||||
|
Param.Add(getParam(state));
|
||||||
|
}
|
||||||
|
Progress.Finalize(iterCount);
|
||||||
|
Quality.Finalize(iterCount);
|
||||||
|
Param.Finalize(iterCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed record SimulatedActionStep
|
private sealed record SimulatedActionStep
|
||||||
{
|
{
|
||||||
public ActionType Action { get; init; }
|
public ActionType Action { get; init; }
|
||||||
// State *after* executing the action
|
// State *after* executing the action
|
||||||
public ActionResponse Response { get; set; }
|
public ActionResponse Response { get; set; }
|
||||||
public SimulationState State { get; set; }
|
public SimulationState State { get; set; }
|
||||||
|
public SimulationReliablity Reliability { get; set; }
|
||||||
};
|
};
|
||||||
private List<SimulatedActionStep> Macro { get; set; } = new();
|
private List<SimulatedActionStep> Macro { get; set; } = new();
|
||||||
private SimulationState InitialState { get; set; }
|
private SimulationState InitialState { get; set; }
|
||||||
private SimulationState State => Macro.Count > 0 ? Macro[^1].State : InitialState;
|
private SimulationState State => Macro.Count > 0 ? Macro[^1].State : InitialState;
|
||||||
|
private SimulationReliablity Reliability => Macro.Count > 0 ? Macro[^1].Reliability : new(InitialState, Array.Empty<ActionType>(), 0, RecipeData);
|
||||||
private ActionType[] DefaultActions { get; }
|
private ActionType[] DefaultActions { get; }
|
||||||
private Action<IEnumerable<ActionType>>? MacroSetter { get; set; }
|
private Action<IEnumerable<ActionType>>? MacroSetter { get; set; }
|
||||||
|
|
||||||
@@ -971,23 +1033,23 @@ public sealed class MacroEditor : Window, IDisposable
|
|||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
var datas = new List<BarData>(3)
|
var datas = new List<BarData>(3)
|
||||||
{
|
{
|
||||||
new("Durability", Colors.Durability, State.Durability, RecipeData.RecipeInfo.MaxDurability, null, null),
|
new("Durability", Colors.Durability, null, State.Durability, RecipeData.RecipeInfo.MaxDurability, null, null),
|
||||||
new("Condition", default, 0, 0, null, State.Condition)
|
new("Condition", default, null, 0, 0, null, State.Condition)
|
||||||
};
|
};
|
||||||
if (RecipeData.Recipe.ItemResult.Value!.IsCollectable)
|
if (RecipeData.Recipe.ItemResult.Value!.IsCollectable)
|
||||||
datas.Add(new("Collectability", Colors.HQ, State.Collectability, State.MaxCollectability, $"{State.Collectability}", null));
|
datas.Add(new("Collectability", Colors.HQ, Reliability.Param, State.Collectability, State.MaxCollectability, $"{State.Collectability}", null));
|
||||||
else if (RecipeData.Recipe.RequiredQuality > 0)
|
else if (RecipeData.Recipe.RequiredQuality > 0)
|
||||||
datas.Add(new("Quality %", Colors.HQ, State.Quality, RecipeData.Recipe.RequiredQuality, $"{(float)State.Quality / RecipeData.Recipe.RequiredQuality * 100:0}%", null));
|
datas.Add(new("Quality %", Colors.HQ, Reliability.Param, State.Quality, RecipeData.Recipe.RequiredQuality, $"{(float)State.Quality / RecipeData.Recipe.RequiredQuality * 100:0}%", null));
|
||||||
else if (RecipeData.RecipeInfo.MaxQuality > 0)
|
else if (RecipeData.RecipeInfo.MaxQuality > 0)
|
||||||
datas.Add(new("HQ %", Colors.HQ, State.HQPercent, 100, $"{State.HQPercent}%", null));
|
datas.Add(new("HQ %", Colors.HQ, Reliability.Param, State.HQPercent, 100, $"{State.HQPercent}%", null));
|
||||||
DrawBars(datas);
|
DrawBars(datas);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
datas = new List<BarData>(3)
|
datas = new List<BarData>(3)
|
||||||
{
|
{
|
||||||
new("Progress", Colors.Progress, State.Progress, RecipeData.RecipeInfo.MaxProgress, null, null),
|
new("Progress", Colors.Progress, Reliability.Progress, State.Progress, RecipeData.RecipeInfo.MaxProgress, null, null),
|
||||||
new("Quality", Colors.Quality, State.Quality, RecipeData.RecipeInfo.MaxQuality, null, null),
|
new("Quality", Colors.Quality, Reliability.Quality, State.Quality, RecipeData.RecipeInfo.MaxQuality, null, null),
|
||||||
new("CP", Colors.CP, State.CP, CharacterStats.CP, null, null)
|
new("CP", Colors.CP, null, State.CP, CharacterStats.CP, null, null)
|
||||||
};
|
};
|
||||||
if (RecipeData.RecipeInfo.MaxQuality <= 0)
|
if (RecipeData.RecipeInfo.MaxQuality <= 0)
|
||||||
datas.RemoveAt(1);
|
datas.RemoveAt(1);
|
||||||
@@ -1034,7 +1096,7 @@ public sealed class MacroEditor : Window, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly record struct BarData(string Name, Vector4 Color, float Value, float Max, string? Caption, Condition? Condition);
|
private readonly record struct BarData(string Name, Vector4 Color, SimulationReliablity.ParamReliability? Reliability, float Value, float Max, string? Caption, Condition? Condition);
|
||||||
private void DrawBars(IEnumerable<BarData> bars)
|
private void DrawBars(IEnumerable<BarData> bars)
|
||||||
{
|
{
|
||||||
var spacing = ImGui.GetStyle().ItemSpacing.X;
|
var spacing = ImGui.GetStyle().ItemSpacing.X;
|
||||||
@@ -1074,8 +1136,46 @@ public sealed class MacroEditor : Window, IDisposable
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using (var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, bar.Color))
|
if (bar.Reliability is { } reliability)
|
||||||
|
{
|
||||||
|
// blend rgb colors, assume alpha is always 1
|
||||||
|
static Vector4 BlendColor(Vector4 A, Vector4 B)
|
||||||
|
{
|
||||||
|
return new(
|
||||||
|
A.X * (1 - B.W) + B.X * B.W,
|
||||||
|
A.Y * (1 - B.W) + B.Y * B.W,
|
||||||
|
A.Z * (1 - B.W) + B.Z * B.W,
|
||||||
|
1);
|
||||||
|
}
|
||||||
|
var relBars = new (float Value, Vector4 Color)[]
|
||||||
|
{
|
||||||
|
(bar.Value, bar.Color),
|
||||||
|
(reliability.Average, BlendColor(bar.Color, new(.5f,.5f,.5f,.6f))),
|
||||||
|
(reliability.Min, BlendColor(bar.Color, new(0,0,0,.6f))),
|
||||||
|
(reliability.Max, BlendColor(bar.Color, new(1,1,1,.6f))),
|
||||||
|
}.DistinctBy(v => Math.Clamp(v.Value, 0, bar.Max)).OrderByDescending(v => v.Value);
|
||||||
|
var i = 0;
|
||||||
|
var pos = ImGui.GetCursorPos();
|
||||||
|
foreach (var relBar in relBars)
|
||||||
|
{
|
||||||
|
ImGui.SetCursorPos(pos);
|
||||||
|
{
|
||||||
|
using var frameColor = ImRaii.PushColor(ImGuiCol.FrameBg, Vector4.Zero, i != 0);
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, relBar.Color);
|
||||||
|
ImGui.ProgressBar(Math.Clamp(relBar.Value / bar.Max, 0, 1), new(barSize, ImGui.GetFrameHeight()), string.Empty);
|
||||||
|
}
|
||||||
|
if (i++ == 0)
|
||||||
|
{
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip($"Min: {reliability.Min}\nAvg: {reliability.Average:0.##}\nMax: {reliability.Max}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.PlotHistogram, bar.Color);
|
||||||
ImGui.ProgressBar(Math.Clamp(bar.Value / bar.Max, 0, 1), new(barSize, ImGui.GetFrameHeight()), string.Empty);
|
ImGui.ProgressBar(Math.Clamp(bar.Value / bar.Max, 0, 1), new(barSize, ImGui.GetFrameHeight()), string.Empty);
|
||||||
|
}
|
||||||
ImGui.SameLine(0, spacing);
|
ImGui.SameLine(0, spacing);
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
if (bar.Caption is { } caption)
|
if (bar.Caption is { } caption)
|
||||||
@@ -1420,6 +1520,7 @@ public sealed class MacroEditor : Window, IDisposable
|
|||||||
popupImportUrlTokenSource = null;
|
popupImportUrlTokenSource = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CalculateBestMacro()
|
private void CalculateBestMacro()
|
||||||
{
|
{
|
||||||
SolverTokenSource?.Cancel();
|
SolverTokenSource?.Cancel();
|
||||||
@@ -1485,8 +1586,11 @@ public sealed class MacroEditor : Window, IDisposable
|
|||||||
InitialState = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo, StartingQuality));
|
InitialState = new SimulationState(new(CharacterStats, RecipeData.RecipeInfo, StartingQuality));
|
||||||
var sim = new Sim(InitialState);
|
var sim = new Sim(InitialState);
|
||||||
var lastState = InitialState;
|
var lastState = InitialState;
|
||||||
foreach (var step in Macro)
|
for (var i = 0; i < Macro.Count; i++)
|
||||||
lastState = ((step.Response, step.State) = sim.Execute(lastState, step.Action)).State;
|
{
|
||||||
|
lastState = ((Macro[i].Response, Macro[i].State) = sim.Execute(lastState, Macro[i].Action)).State;
|
||||||
|
Macro[i].Reliability = new(InitialState, Macro.Take(i + 1).Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddStep(ActionType action, int index = -1, bool isSolver = false)
|
private void AddStep(ActionType action, int index = -1, bool isSolver = false)
|
||||||
@@ -1503,6 +1607,7 @@ public sealed class MacroEditor : Window, IDisposable
|
|||||||
var sim = new Sim(State);
|
var sim = new Sim(State);
|
||||||
var resp = sim.Execute(State, action);
|
var resp = sim.Execute(State, action);
|
||||||
Macro.Add(new() { Action = action, Response = resp.Response, State = resp.NewState });
|
Macro.Add(new() { Action = action, Response = resp.Response, State = resp.NewState });
|
||||||
|
Macro[^1].Reliability = new(InitialState, Macro.Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1510,9 +1615,14 @@ public sealed class MacroEditor : Window, IDisposable
|
|||||||
var sim = new Sim(state);
|
var sim = new Sim(state);
|
||||||
var resp = sim.Execute(state, action);
|
var resp = sim.Execute(state, action);
|
||||||
Macro.Insert(index, new() { Action = action, Response = resp.Response, State = resp.NewState });
|
Macro.Insert(index, new() { Action = action, Response = resp.Response, State = resp.NewState });
|
||||||
|
Macro[index].Reliability = new(InitialState, Macro.Take(index + 1).Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData);
|
||||||
|
|
||||||
state = resp.NewState;
|
state = resp.NewState;
|
||||||
for (var i = index + 1; i < Macro.Count; i++)
|
for (var i = index + 1; i < Macro.Count; i++)
|
||||||
|
{
|
||||||
state = ((Macro[i].Response, Macro[i].State) = sim.Execute(state, Macro[i].Action)).State;
|
state = ((Macro[i].Response, Macro[i].State) = sim.Execute(state, Macro[i].Action)).State;
|
||||||
|
Macro[i].Reliability = new(InitialState, Macro.Take(i + 1).Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1529,7 +1639,10 @@ public sealed class MacroEditor : Window, IDisposable
|
|||||||
var state = index == 0 ? InitialState : Macro[index - 1].State;
|
var state = index == 0 ? InitialState : Macro[index - 1].State;
|
||||||
var sim = new Sim(state);
|
var sim = new Sim(state);
|
||||||
for (var i = index; i < Macro.Count; i++)
|
for (var i = index; i < Macro.Count; i++)
|
||||||
|
{
|
||||||
state = ((Macro[i].Response, Macro[i].State) = sim.Execute(state, Macro[i].Action)).State;
|
state = ((Macro[i].Response, Macro[i].State) = sim.Execute(state, Macro[i].Action)).State;
|
||||||
|
Macro[i].Reliability = new(InitialState, Macro.Take(i + 1).Select(s => s.Action), Service.Configuration.ReliabilitySimulationCount, RecipeData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
Reference in New Issue
Block a user