Add branch-aware junction flow
This commit is contained in:
13
TASKS.md
13
TASKS.md
@@ -1,4 +1,4 @@
|
|||||||
# Reactor Maintenance Rewrite Tasks
|
# Reactor Maintenance Rewrite Tasks
|
||||||
|
|
||||||
## Current State
|
## Current State
|
||||||
|
|
||||||
@@ -33,14 +33,21 @@
|
|||||||
- Attempted Win2D build on Linux with `dotnet build src/ReactorMaintenance.Win2D/ReactorMaintenance.Win2D.csproj -p:EnableWindowsTargeting=true -p:Platform=x64`; it fails at Windows `XamlCompiler.exe` with exec format error.
|
- Attempted Win2D build on Linux with `dotnet build src/ReactorMaintenance.Win2D/ReactorMaintenance.Win2D.csproj -p:EnableWindowsTargeting=true -p:Platform=x64`; it fails at Windows `XamlCompiler.exe` with exec format error.
|
||||||
- Attempted managed XAML compiler path with `-p:UseXamlCompilerExecutable=false`; it fails loading the WinUI XAML compiler task dependency under this Linux/.NET 10 setup.
|
- Attempted managed XAML compiler path with `-p:UseXamlCompilerExecutable=false`; it fails loading the WinUI XAML compiler task dependency under this Linux/.NET 10 setup.
|
||||||
- Updated `README.md` for the new design-model editor, .NET 10 target, and Linux/Windows build expectations.
|
- Updated `README.md` for the new design-model editor, .NET 10 target, and Linux/Windows build expectations.
|
||||||
|
- Committed the Win2D editor rewrite slice.
|
||||||
|
- Added branch-aware junction flow analysis shared by validation and simulation propagation.
|
||||||
|
- Junction validation now rejects malformed branch counts and ambiguous source-side branches.
|
||||||
|
- Junction propagation now applies deterministic T-junction and cross-junction ratio weights only to inferred outgoing branches.
|
||||||
|
- Added tests for T-junction ratio splits, zero-weight branches, ambiguous junction validation, and best-path flow into non-junction cells.
|
||||||
|
- Verified `dotnet test tests/ReactorMaintenance.Simulation.Tests/ReactorMaintenance.Simulation.Tests.csproj` passes: 15 passed.
|
||||||
|
- Ran `jb cleanupcode --build=False ...` and `python D:\Code\crlf.py ...` for touched C# files after the junction slice.
|
||||||
|
|
||||||
## Current Work
|
## Current Work
|
||||||
|
|
||||||
- Commit the Win2D editor rewrite slice.
|
- Await review and next task selection on `design-rewrite`.
|
||||||
|
|
||||||
## Future Work
|
## Future Work
|
||||||
|
|
||||||
1. Expand simulation fidelity where the first slice is intentionally simplified: junction branch inference, ambiguity validation, complete pair table coverage, richer rule predicates/effects, and stronger forecast proof cases.
|
1. Expand simulation fidelity where the first slice is intentionally simplified: complete pair table coverage, richer rule predicates/effects, and stronger forecast proof cases.
|
||||||
2. Add advanced editor workflows for explicit reactor binding selection, explicit door edge selection, electricity wall leak face selection, and rule event authoring.
|
2. Add advanced editor workflows for explicit reactor binding selection, explicit door edge selection, electricity wall leak face selection, and rule event authoring.
|
||||||
3. Verify and polish the Win2D app on Windows where the XAML compiler can run.
|
3. Verify and polish the Win2D app on Windows where the XAML compiler can run.
|
||||||
4. Update README and any affected docs to reflect the new schema, .NET target, editor controls, and deterministic defaults.
|
4. Update README and any affected docs to reflect the new schema, .NET target, editor controls, and deterministic defaults.
|
||||||
|
|||||||
59
src/ReactorMaintenance.Simulation/JunctionFlow.cs
Normal file
59
src/ReactorMaintenance.Simulation/JunctionFlow.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
public sealed record JunctionFlow
|
||||||
|
{
|
||||||
|
public GridPosition Position { get; init; } = new(0, 0);
|
||||||
|
public PropState Prop { get; init; } = new();
|
||||||
|
public ECarrierType Carrier { get; init; }
|
||||||
|
public IReadOnlyList<GridPosition> Branches { get; init; } = Array.Empty<GridPosition>();
|
||||||
|
public GridPosition? IncomingBranch { get; init; }
|
||||||
|
public IReadOnlyList<GridPosition> OutgoingBranches { get; init; } = Array.Empty<GridPosition>();
|
||||||
|
public IReadOnlyList<string> Errors { get; init; } = Array.Empty<string>();
|
||||||
|
public bool IsValid => Errors.Count == 0;
|
||||||
|
|
||||||
|
public float WeightFor(GridPosition outgoingBranch)
|
||||||
|
{
|
||||||
|
var index = IndexOfOutgoingBranch(outgoingBranch);
|
||||||
|
if (index < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var weights = Prop.Type == EPropType.TJunction
|
||||||
|
? TJunctionWeights(Prop.TJunctionMode)
|
||||||
|
: CrossJunctionWeights(Prop.CrossJunctionMode);
|
||||||
|
return index < weights.Length ? weights[index] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int IndexOfOutgoingBranch(GridPosition outgoingBranch)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < OutgoingBranches.Count; i++)
|
||||||
|
{
|
||||||
|
if (OutgoingBranches[i] == outgoingBranch)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float[] TJunctionWeights(ETJunctionMode mode)
|
||||||
|
{
|
||||||
|
return mode switch {
|
||||||
|
ETJunctionMode.ZeroFour => [0, 1],
|
||||||
|
ETJunctionMode.OneThree => [0.25f, 0.75f],
|
||||||
|
ETJunctionMode.TwoTwo => [0.5f, 0.5f],
|
||||||
|
ETJunctionMode.ThreeOne => [0.75f, 0.25f],
|
||||||
|
ETJunctionMode.FourZero => [1, 0],
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported T-junction mode.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float[] CrossJunctionWeights(ECrossJunctionMode mode)
|
||||||
|
{
|
||||||
|
return mode switch {
|
||||||
|
ECrossJunctionMode.ZeroThreeThree => [0, 0.5f, 0.5f],
|
||||||
|
ECrossJunctionMode.ThreeZeroThree => [0.5f, 0, 0.5f],
|
||||||
|
ECrossJunctionMode.ThreeThreeZero => [0.5f, 0.5f, 0],
|
||||||
|
ECrossJunctionMode.TwoTwoTwo => [1f / 3f, 1f / 3f, 1f / 3f],
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported cross-junction mode.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
98
src/ReactorMaintenance.Simulation/JunctionFlowAnalyzer.cs
Normal file
98
src/ReactorMaintenance.Simulation/JunctionFlowAnalyzer.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
public static class JunctionFlowAnalyzer
|
||||||
|
{
|
||||||
|
public static IReadOnlyList<JunctionFlow> Analyze(LevelState level)
|
||||||
|
{
|
||||||
|
var flows = new List<JunctionFlow>();
|
||||||
|
|
||||||
|
for (var y = 0; y < level.Height; y++)
|
||||||
|
{
|
||||||
|
for (var x = 0; x < level.Width; x++)
|
||||||
|
{
|
||||||
|
var position = new GridPosition(x, y);
|
||||||
|
var prop = level.GetProp(position);
|
||||||
|
if (prop.Type is not (EPropType.TJunction or EPropType.CrossJunction))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
flows.Add(AnalyzeJunction(level, position, prop));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return flows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JunctionFlow AnalyzeJunction(LevelState level, GridPosition position, PropState prop)
|
||||||
|
{
|
||||||
|
var errors = new List<string>();
|
||||||
|
var carriers = Enum.GetValues<ECarrierType>().Where(carrier => level.GetUnderground(position, carrier).IsPresent).ToArray();
|
||||||
|
var carrier = carriers.FirstOrDefault();
|
||||||
|
if (carriers.Length != 1)
|
||||||
|
errors.Add("Junction must regulate exactly one underground carrier.");
|
||||||
|
|
||||||
|
var branches = carriers.Length == 1
|
||||||
|
? position.Neighbors().Where(level.InBounds).Where(neighbor => level.GetUnderground(neighbor, carrier).CarriesFlow).ToArray()
|
||||||
|
: Array.Empty<GridPosition>();
|
||||||
|
|
||||||
|
var expectedBranches = prop.Type == EPropType.TJunction ? 3 : 4;
|
||||||
|
if (carriers.Length == 1 && branches.Length != expectedBranches)
|
||||||
|
errors.Add($"{prop.Type} must have exactly {expectedBranches} connected underground branches.");
|
||||||
|
|
||||||
|
var sourceBranches = carriers.Length == 1
|
||||||
|
? branches.Select(branch => new SourceBranch(branch, ShortestDistanceToSource(level, branch, position, carrier)))
|
||||||
|
.Where(branch => branch.Distance.HasValue)
|
||||||
|
.ToArray()
|
||||||
|
: Array.Empty<SourceBranch>();
|
||||||
|
|
||||||
|
GridPosition? incomingBranch = null;
|
||||||
|
if (sourceBranches.Length > 0)
|
||||||
|
{
|
||||||
|
var bestDistance = sourceBranches.Min(branch => branch.Distance!.Value);
|
||||||
|
var bestBranches = sourceBranches.Where(branch => branch.Distance == bestDistance).ToArray();
|
||||||
|
if (bestBranches.Length != 1 || sourceBranches.Length != 1)
|
||||||
|
errors.Add("Ambiguous junction flow.");
|
||||||
|
else
|
||||||
|
incomingBranch = bestBranches[0].Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
var outgoingBranches = incomingBranch is null
|
||||||
|
? Array.Empty<GridPosition>()
|
||||||
|
: branches.Where(branch => branch != incomingBranch).ToArray();
|
||||||
|
|
||||||
|
return new() {
|
||||||
|
Position = position,
|
||||||
|
Prop = prop,
|
||||||
|
Carrier = carrier,
|
||||||
|
Branches = branches,
|
||||||
|
IncomingBranch = incomingBranch,
|
||||||
|
OutgoingBranches = outgoingBranches,
|
||||||
|
Errors = errors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int? ShortestDistanceToSource(LevelState level, GridPosition start, GridPosition blocked, ECarrierType carrier)
|
||||||
|
{
|
||||||
|
var visited = new HashSet<GridPosition> { blocked, start };
|
||||||
|
var open = new Queue<(GridPosition Position, int Distance)>();
|
||||||
|
open.Enqueue((start, 0));
|
||||||
|
|
||||||
|
while (open.Count > 0)
|
||||||
|
{
|
||||||
|
var current = open.Dequeue();
|
||||||
|
if (level.GetProp(current.Position) is { Type: EPropType.Flow, Carrier: var sourceCarrier, SwitchState: EPropSwitchState.Enabled } && sourceCarrier == carrier)
|
||||||
|
return current.Distance;
|
||||||
|
|
||||||
|
foreach (var next in current.Position.Neighbors().Where(level.InBounds))
|
||||||
|
{
|
||||||
|
if (!visited.Add(next) || !level.GetUnderground(next, carrier).CarriesFlow)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
open.Enqueue((next, current.Distance + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record SourceBranch(GridPosition Position, int? Distance);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace ReactorMaintenance.Simulation;
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
public sealed class LevelValidator
|
public sealed class LevelValidator
|
||||||
{
|
{
|
||||||
@@ -113,20 +113,8 @@ public sealed class LevelValidator
|
|||||||
|
|
||||||
private static void ValidateJunctions(LevelState level, List<ValidationIssue> errors)
|
private static void ValidateJunctions(LevelState level, List<ValidationIssue> errors)
|
||||||
{
|
{
|
||||||
for (var y = 0; y < level.Height; y++)
|
foreach (var junction in JunctionFlowAnalyzer.Analyze(level))
|
||||||
{
|
errors.AddRange(junction.Errors.Select(error => new ValidationIssue(error, junction.Position)));
|
||||||
for (var x = 0; x < level.Width; x++)
|
|
||||||
{
|
|
||||||
var position = new GridPosition(x, y);
|
|
||||||
var prop = level.GetProp(position);
|
|
||||||
if (prop.Type is not (EPropType.TJunction or EPropType.CrossJunction))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var carrierCount = Enum.GetValues<ECarrierType>().Count(carrier => level.GetUnderground(position, carrier).IsPresent);
|
|
||||||
if (carrierCount != 1)
|
|
||||||
errors.Add(new("Junction must regulate exactly one underground carrier.", position));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateRuleEvents(LevelState level, List<ValidationIssue> errors)
|
private static void ValidateRuleEvents(LevelState level, List<ValidationIssue> errors)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace ReactorMaintenance.Simulation;
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
public sealed class SimulationEngine
|
public sealed class SimulationEngine
|
||||||
{
|
{
|
||||||
@@ -156,9 +156,10 @@ public sealed class SimulationEngine
|
|||||||
{
|
{
|
||||||
var layer = level.Layer(carrier).ToArray();
|
var layer = level.Layer(carrier).ToArray();
|
||||||
var sources = AllPositions(level).Where(position => level.GetProp(position) is { Type: EPropType.Flow, SwitchState: EPropSwitchState.Enabled, Carrier: var sourceCarrier } && sourceCarrier == carrier && level.GetUnderground(position, carrier).CarriesFlow).ToArray();
|
var sources = AllPositions(level).Where(position => level.GetProp(position) is { Type: EPropType.Flow, SwitchState: EPropSwitchState.Enabled, Carrier: var sourceCarrier } && sourceCarrier == carrier && level.GetUnderground(position, carrier).CarriesFlow).ToArray();
|
||||||
|
var junctions = JunctionFlowAnalyzer.Analyze(level).Where(junction => junction.IsValid && junction.Carrier == carrier).ToDictionary(junction => junction.Position);
|
||||||
|
|
||||||
foreach (var source in sources)
|
foreach (var source in sources)
|
||||||
ApplySourceFlow(level, layer, source, carrier);
|
ApplySourceFlow(level, layer, source, carrier, junctions);
|
||||||
|
|
||||||
return carrier switch {
|
return carrier switch {
|
||||||
ECarrierType.Fuel => level with { Fuel = layer },
|
ECarrierType.Fuel => level with { Fuel = layer },
|
||||||
@@ -168,7 +169,7 @@ public sealed class SimulationEngine
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ApplySourceFlow(LevelState level, UndergroundCell[] layer, GridPosition source, ECarrierType carrier)
|
private static void ApplySourceFlow(LevelState level, UndergroundCell[] layer, GridPosition source, ECarrierType carrier, IReadOnlyDictionary<GridPosition, JunctionFlow> junctions)
|
||||||
{
|
{
|
||||||
var open = new Queue<(GridPosition Position, int Distance, float AmountFactor, float IntensityFactor)>();
|
var open = new Queue<(GridPosition Position, int Distance, float AmountFactor, float IntensityFactor)>();
|
||||||
var best = new Dictionary<GridPosition, float>();
|
var best = new Dictionary<GridPosition, float>();
|
||||||
@@ -191,7 +192,7 @@ public sealed class SimulationEngine
|
|||||||
if (!level.GetUnderground(next, carrier).CarriesFlow)
|
if (!level.GetUnderground(next, carrier).CarriesFlow)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var weights = BranchWeights(level, current.Position, next);
|
var weights = BranchWeights(current.Position, next, junctions);
|
||||||
var amountFactor = current.AmountFactor * weights.Amount;
|
var amountFactor = current.AmountFactor * weights.Amount;
|
||||||
var intensityFactor = current.IntensityFactor * weights.Intensity;
|
var intensityFactor = current.IntensityFactor * weights.Intensity;
|
||||||
if (amountFactor <= 0 || intensityFactor <= 0)
|
if (amountFactor <= 0 || intensityFactor <= 0)
|
||||||
@@ -206,38 +207,12 @@ public sealed class SimulationEngine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (float Amount, float Intensity) BranchWeights(LevelState level, GridPosition from, GridPosition to)
|
private static (float Amount, float Intensity) BranchWeights(GridPosition from, GridPosition to, IReadOnlyDictionary<GridPosition, JunctionFlow> junctions)
|
||||||
{
|
{
|
||||||
var prop = level.GetProp(from);
|
if (!junctions.TryGetValue(from, out var junction))
|
||||||
return prop.Type switch {
|
return (1, 1);
|
||||||
EPropType.TJunction => TJunctionWeights(prop.TJunctionMode),
|
|
||||||
EPropType.CrossJunction => CrossJunctionWeights(prop.CrossJunctionMode),
|
|
||||||
_ => (1, 1)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (float Amount, float Intensity) TJunctionWeights(ETJunctionMode mode)
|
var weight = junction.WeightFor(to);
|
||||||
{
|
|
||||||
var weight = mode switch {
|
|
||||||
ETJunctionMode.ZeroFour => 0,
|
|
||||||
ETJunctionMode.OneThree => 0.25f,
|
|
||||||
ETJunctionMode.TwoTwo => 0.5f,
|
|
||||||
ETJunctionMode.ThreeOne => 0.75f,
|
|
||||||
ETJunctionMode.FourZero => 1,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported T-junction mode.")
|
|
||||||
};
|
|
||||||
return (weight, weight);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (float Amount, float Intensity) CrossJunctionWeights(ECrossJunctionMode mode)
|
|
||||||
{
|
|
||||||
var weight = mode switch {
|
|
||||||
ECrossJunctionMode.ZeroThreeThree => 0,
|
|
||||||
ECrossJunctionMode.ThreeZeroThree => 0.5f,
|
|
||||||
ECrossJunctionMode.ThreeThreeZero => 0.5f,
|
|
||||||
ECrossJunctionMode.TwoTwoTwo => 1f / 3f,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported cross-junction mode.")
|
|
||||||
};
|
|
||||||
return (weight, weight);
|
return (weight, weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace ReactorMaintenance.Simulation.Tests;
|
namespace ReactorMaintenance.Simulation.Tests;
|
||||||
|
|
||||||
public sealed class SimulationEngineTests
|
public sealed class SimulationEngineTests
|
||||||
{
|
{
|
||||||
@@ -86,6 +86,57 @@ public sealed class SimulationEngineTests
|
|||||||
Assert.NotEqual(ELevelState.Lost, next.Global.LevelState);
|
Assert.NotEqual(ELevelState.Lost, next.Global.LevelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TJunctionRatioSplitsFlowAcrossInferredOutgoingBranches()
|
||||||
|
{
|
||||||
|
var level = BuildTJunctionLevel(ETJunctionMode.TwoTwo);
|
||||||
|
|
||||||
|
var next = m_Engine.AdvanceTurn(level);
|
||||||
|
|
||||||
|
Assert.True(next.GetUnderground(new(2, 2), ECarrierType.Fuel).Amount > 0);
|
||||||
|
Assert.Equal(next.GetUnderground(new(2, 2), ECarrierType.Fuel).Amount, next.GetUnderground(new(3, 3), ECarrierType.Fuel).Amount);
|
||||||
|
Assert.True(next.GetUnderground(new(2, 2), ECarrierType.Fuel).Amount < next.GetUnderground(new(2, 3), ECarrierType.Fuel).Amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TJunctionZeroWeightBranchReceivesNoIntentionalOutflow()
|
||||||
|
{
|
||||||
|
var level = BuildTJunctionLevel(ETJunctionMode.ZeroFour);
|
||||||
|
|
||||||
|
var next = m_Engine.AdvanceTurn(level);
|
||||||
|
|
||||||
|
Assert.Equal(0, next.GetUnderground(new(2, 2), ECarrierType.Fuel).Amount);
|
||||||
|
Assert.True(next.GetUnderground(new(3, 3), ECarrierType.Fuel).Amount > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ValidatorRejectsAmbiguousJunctionSourceBranches()
|
||||||
|
{
|
||||||
|
var level = BuildTJunctionLevel(ETJunctionMode.TwoTwo);
|
||||||
|
level = level.SetProp(new(3, 3), new() { Type = EPropType.Flow, Carrier = ECarrierType.Fuel });
|
||||||
|
|
||||||
|
var report = new LevelValidator().Validate(level);
|
||||||
|
|
||||||
|
Assert.False(report.IsValid);
|
||||||
|
Assert.Contains(report.Errors, error => error.Message.Contains("Ambiguous junction flow", StringComparison.Ordinal));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NonJunctionCellsAcceptBestFlowFromMultipleSourcePaths()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Best path", 7, 7);
|
||||||
|
level = AddHorizontalUnderground(level, ECarrierType.Fuel, 1, 5, 3);
|
||||||
|
level = level.SetProp(new(1, 3), new() { Type = EPropType.Flow, Carrier = ECarrierType.Fuel });
|
||||||
|
level = level.SetProp(new(5, 3), new() { Type = EPropType.Flow, Carrier = ECarrierType.Fuel });
|
||||||
|
level = level.SetProp(new(3, 3), new() { Type = EPropType.Consumer, Carrier = ECarrierType.Fuel });
|
||||||
|
|
||||||
|
var report = new LevelValidator().Validate(level);
|
||||||
|
var next = m_Engine.AdvanceTurn(level);
|
||||||
|
|
||||||
|
Assert.True(report.IsValid);
|
||||||
|
Assert.Equal(EConsumerServiceState.Producing, next.GetProp(new(3, 3)).ServiceState);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RobotLosesOnUnsafeElementHazard()
|
public void RobotLosesOnUnsafeElementHazard()
|
||||||
{
|
{
|
||||||
@@ -199,5 +250,24 @@ public sealed class SimulationEngineTests
|
|||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static LevelState BuildTJunctionLevel(ETJunctionMode mode)
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("T junction", 6, 6);
|
||||||
|
level = level.SetUnderground(new(1, 3), ECarrierType.Fuel, new() { State = EUndergroundState.Intact });
|
||||||
|
level = level.SetUnderground(new(2, 3), ECarrierType.Fuel, new() { State = EUndergroundState.Intact });
|
||||||
|
level = level.SetUnderground(new(2, 2), ECarrierType.Fuel, new() { State = EUndergroundState.Intact });
|
||||||
|
level = level.SetUnderground(new(3, 3), ECarrierType.Fuel, new() { State = EUndergroundState.Intact });
|
||||||
|
level = level.SetProp(new(1, 3), new() { Type = EPropType.Flow, Carrier = ECarrierType.Fuel });
|
||||||
|
return level.SetProp(new(2, 3), new() { Type = EPropType.TJunction, Carrier = ECarrierType.Fuel, TJunctionMode = mode });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LevelState AddHorizontalUnderground(LevelState level, ECarrierType carrier, int startX, int endX, int y)
|
||||||
|
{
|
||||||
|
for (var x = startX; x <= endX; x++)
|
||||||
|
level = level.SetUnderground(new(x, y), carrier, new() { State = EUndergroundState.Intact });
|
||||||
|
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly SimulationEngine m_Engine = new();
|
private readonly SimulationEngine m_Engine = new();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user