Expand rule event coverage

This commit is contained in:
2026-05-10 17:38:43 +02:00
parent cb28eee1dd
commit a0b10423ac
5 changed files with 308 additions and 13 deletions

View File

@@ -170,6 +170,141 @@ public sealed class SimulationEngineTests
Assert.Contains(forecasts, forecast => forecast.Kind == EForecastKind.TerminalLoss);
}
[Fact]
public void RuleEventCanTriggerFromNetworkBand()
{
var level = LevelState.Create("Network rule", 6, 6);
level = AddLine(level, ECarrierType.Fuel, new(2, 2), new(3, 2));
level = level.SetProp(new(2, 2), new() { Type = EPropType.Flow, Carrier = ECarrierType.Fuel }) with {
RuleEvents = [
new RuleEventState {
Phase = ERuleEventPhase.EndOfTurn,
Predicates = [new RulePredicate { Kind = ERulePredicateKind.NetworkBandAt, Position = new(3, 2), Carrier = ECarrierType.Fuel, NetworkValue = ENetworkValueKind.Amount, Band = EBand.Critical }],
Effects = [new RuleEffect { Kind = ERuleEffectKind.EmitWarning, Message = "fuel pressure high" }]
}
]
};
var next = m_Engine.AdvanceTurn(level);
Assert.Contains("fuel pressure high", next.Global.Warnings);
}
[Fact]
public void RuleEventCanTriggerFromReactorReadiness()
{
var level = BuildReadyLevel() with {
RuleEvents = [
new RuleEventState {
Phase = ERuleEventPhase.EndOfTurn,
Predicates = [new RulePredicate { Kind = ERulePredicateKind.ReactorReadyIs, ReactorId = 1, BoolValue = true }],
Effects = [new RuleEffect { Kind = ERuleEffectKind.EmitWarning, Message = "reactor ready rule" }]
}
]
};
var next = m_Engine.AdvanceTurn(level);
Assert.Contains("reactor ready rule", next.Global.Warnings);
}
[Fact]
public void RuleEventCanTriggerFromRobotInventory()
{
var level = LevelState.Create("Inventory rule", 6, 6) with {
Robot = new() { Position = new(1, 1), FuelNeutralizers = 1 },
RuleEvents = [
new RuleEventState {
Phase = ERuleEventPhase.StartOfSimulation,
Predicates = [new RulePredicate { Kind = ERulePredicateKind.RobotInventoryAtLeast, Remedy = ERemedyType.FuelNeutralizer, InventoryCount = 1 }],
Effects = [new RuleEffect { Kind = ERuleEffectKind.EmitWarning, Message = "fuel kit detected" }]
}
]
};
var next = m_Engine.AdvanceTurn(level);
Assert.Contains("fuel kit detected", next.Global.Warnings);
}
[Fact]
public void RuleEventCanRemoveHazardsHeatAndInventory()
{
var level = LevelState.Create("Remove rule", 6, 6);
level = level.SetSurface(new(2, 2), new() { Fuel = 5, Heat = 5 }) with {
Robot = new() { Position = new(1, 1), FuelNeutralizers = 2 },
Doors = [
new DoorState { A = new(2, 2), B = new(1, 2), State = EDoorState.Closed },
new DoorState { A = new(2, 2), B = new(3, 2), State = EDoorState.Closed },
new DoorState { A = new(2, 2), B = new(2, 1), State = EDoorState.Closed },
new DoorState { A = new(2, 2), B = new(2, 3), State = EDoorState.Closed }
],
RuleEvents = [
new RuleEventState {
Phase = ERuleEventPhase.StartOfSimulation,
Predicates = [new RulePredicate { Kind = ERulePredicateKind.TurnAtLeast, Turn = 0 }],
Effects = [
new RuleEffect { Kind = ERuleEffectKind.RemoveSurfaceHazard, Position = new(2, 2), Carrier = ECarrierType.Fuel, Amount = 2 },
new RuleEffect { Kind = ERuleEffectKind.RemoveHeat, Position = new(2, 2), Amount = 3 },
new RuleEffect { Kind = ERuleEffectKind.RemoveInventory, Remedy = ERemedyType.FuelNeutralizer, Amount = 1 }
]
}
]
};
var next = m_Engine.AdvanceTurn(level);
Assert.Equal(3, next.GetSurface(new(2, 2)).Fuel);
Assert.Equal(2, next.GetSurface(new(2, 2)).Heat);
Assert.Equal(1, next.Robot.FuelNeutralizers);
}
[Fact]
public void RuleEventStartLeakUsesAuthoredElectricityAccessFace()
{
var level = LevelState.Create("Electricity leak rule", 6, 6);
level = level.SetTerrain(new(2, 2), ECellTerrain.Wall);
level = level.SetUnderground(new(2, 2), ECarrierType.Electricity, new() { State = EUndergroundState.Intact }) with {
RuleEvents = [
new RuleEventState {
Phase = ERuleEventPhase.StartOfSimulation,
Predicates = [new RulePredicate { Kind = ERulePredicateKind.TurnAtLeast, Turn = 0 }],
Effects = [new RuleEffect { Kind = ERuleEffectKind.StartLeak, Position = new(2, 2), AccessPosition = new(2, 3), Carrier = ECarrierType.Electricity }]
}
]
};
var next = m_Engine.AdvanceTurn(level);
Assert.Single(next.Leaks);
Assert.Equal(new(2, 3), next.Leaks[0].AccessPosition);
Assert.True(next.GetSurface(new(2, 3)).Electricity > 0);
}
[Fact]
public void ValidatorRejectsInvalidRuleTargets()
{
var level = LevelState.Create("Invalid rules", 6, 6);
level = level.SetTerrain(new(2, 2), ECellTerrain.Wall) with {
RuleEvents = [
new RuleEventState {
Predicates = [new RulePredicate { Kind = ERulePredicateKind.PropStateAt, Position = new(1, 1) }],
Effects = [
new RuleEffect { Kind = ERuleEffectKind.AddSurfaceHazard, Position = new(2, 2), Carrier = ECarrierType.Fuel, Amount = 1 },
new RuleEffect { Kind = ERuleEffectKind.RepairNetworkCell, Position = new(3, 3), Carrier = ECarrierType.Coolant }
]
}
]
};
var report = new LevelValidator().Validate(level);
Assert.False(report.IsValid);
Assert.Contains(report.Errors, error => error.Message.Contains("Rule prop predicate", StringComparison.Ordinal));
Assert.Contains(report.Errors, error => error.Message.Contains("Rule surface effect", StringComparison.Ordinal));
Assert.Contains(report.Errors, error => error.Message.Contains("Rule network effect", StringComparison.Ordinal));
}
[Fact]
public void ValidatorRejectsWallHazardsAndInvalidReactorBinding()
{
@@ -270,4 +405,4 @@ public sealed class SimulationEngineTests
}
private readonly SimulationEngine m_Engine = new();
}
}