Align frontend pulse contract and tasks
This commit is contained in:
78
TASKS.md
78
TASKS.md
@@ -1,112 +1,110 @@
|
|||||||
# Reactor Maintenance Tasks
|
# Reactor Maintenance Tasks
|
||||||
|
|
||||||
This backlog tracks what must change so the implementation matches `docs/design.md`, `docs/UX.md`, and `docs/CAMPAIGN.md`.
|
This backlog tracks what must change so the implementation matches `docs/design.md`, `docs/UX.md`, and `docs/CAMPAIGN.md`.
|
||||||
|
|
||||||
## Audit Snapshot
|
## Audit Snapshot
|
||||||
|
|
||||||
- Current simulation tests pass: `33/33` in `tests/ReactorMaintenance.Simulation.Tests`.
|
- Current simulation tests pass: `54/54` in `tests/ReactorMaintenance.Simulation.Tests`.
|
||||||
- Existing tests cover older behavior. They are useful regression scaffolding, but they do not prove the latest design rules.
|
- Godot has a usable UX scaffold and grid renderer; the full pulse playback, terminal-gated layer controls, and campaign content pass remain in later tasks.
|
||||||
- The simulation has a working `SimulationEngine`, network propagation, consumers, structural integrity, leaks, reactor readiness, forecasts, serialization, editor helpers, and Godot session bridge.
|
- Existing campaign data is the older placeholder set. These levels and manifest entries must be replaced by the tutorial plus six-group campaign from `docs/CAMPAIGN.md`.
|
||||||
- Godot currently has a usable UX scaffold and grid renderer, but it still exposes older interaction concepts such as a player-facing `EndTurn` action and always-available underground layer controls.
|
|
||||||
- Existing campaign data is the older three-level placeholder set: `water_restart.json`, `fuel_bleed.json`, and `black_start.json`. These levels and manifest entries must be replaced by the tutorial plus six-group campaign from `docs/CAMPAIGN.md`.
|
|
||||||
- Unrelated Godot metadata or generated `.uid` files are not part of this backlog unless a later implementation task intentionally touches them.
|
- Unrelated Godot metadata or generated `.uid` files are not part of this backlog unless a later implementation task intentionally touches them.
|
||||||
|
|
||||||
## P0 Simulation Contract
|
## P0 Simulation Contract
|
||||||
|
|
||||||
- [ ] Rename the public environment tick model from turn-based `AdvanceTurn`/`EndTurn` semantics to `Pulse` semantics.
|
- [x] Rename the public environment tick model from turn-based `AdvanceTurn`/`EndTurn` semantics to `Pulse` semantics.
|
||||||
- One accepted `LengthyAction` triggers exactly one `Pulse`.
|
- One accepted `LengthyAction` triggers exactly one `Pulse`.
|
||||||
- One `Pulse` contains a fixed balance-defined number of deterministic `Step`s.
|
- One `Pulse` contains a fixed balance-defined number of deterministic `Step`s.
|
||||||
- The fixed step count must not vary by action type, forecast result, or danger level.
|
- The fixed step count must not vary by action type, forecast result, or danger level.
|
||||||
- Keep isolated step or pulse advancement available only for tests, editor tooling, or debug use.
|
- Keep isolated step or pulse advancement available only for tests, editor tooling, or debug use.
|
||||||
- [ ] Remove player-facing wait/fast-forward behavior from normal gameplay.
|
- [x] Remove player-facing wait/fast-forward behavior from normal gameplay.
|
||||||
- `MoveRobot`, selection, inspection, and terminal layer viewing remain `QuickAction`s.
|
- `MoveRobot`, selection, inspection, and terminal layer viewing remain `QuickAction`s.
|
||||||
- `InteractProp`, `InteractLeak`, `ApplyHeatShield`, and accepted powered-prop no-op interactions are `LengthyAction`s.
|
- `InteractProp`, `InteractLeak`, `ApplyHeatShield`, and accepted powered-prop no-op interactions are `LengthyAction`s.
|
||||||
- `ActivateReactor` wins when `ReactorReadiness` is true and must not require a separate wait.
|
- `ActivateReactor` wins when `ReactorReadiness` is true and must not require a separate wait.
|
||||||
- [ ] Add `IsolationValveProp` as a first-class prop.
|
- [x] Add `IsolationValveProp` as a first-class prop.
|
||||||
- Store carrier binding and `Open`/`Closed` state.
|
- Store carrier binding and `Open`/`Closed` state.
|
||||||
- Block intentional underground propagation across the authored branch boundary.
|
- Block intentional underground propagation across the authored branch boundary.
|
||||||
- Preserve downstream starvation/readiness consequences for consumers and reactor feed.
|
- Preserve downstream starvation/readiness consequences for consumers and reactor feed.
|
||||||
- Add editor placement, validation, serialization, and rendering support.
|
- Add editor placement, validation, serialization, and rendering support.
|
||||||
- [ ] Add `SprinklerControlProp` and wall-mounted `SprinklerValve`.
|
- [x] Add `SprinklerControlProp` and wall-mounted `SprinklerValve`.
|
||||||
- A `SprinklerControlProp` links to exactly one `SprinklerValve`.
|
- A `SprinklerControlProp` links to exactly one `SprinklerValve`.
|
||||||
- The valve is wall-mounted, not directly interactive, and has exactly one outlet/access floor face.
|
- The valve is wall-mounted, not directly interactive, and has exactly one outlet/access floor face.
|
||||||
- Discharge creates `Water` only while linked control is `Enabled` and the water branch is fed.
|
- Discharge creates `Water` only while linked control is `Enabled` and the water branch is fed.
|
||||||
- Discharge applies deterministic local water pressure debt.
|
- Discharge applies deterministic local water pressure debt.
|
||||||
- Add editor placement/linking, validation, serialization, and rendering support.
|
- Add editor placement/linking, validation, serialization, and rendering support.
|
||||||
- [ ] Rework surface water into `Water`.
|
- [x] Rework surface water into `Water`.
|
||||||
- Rename code-facing concepts where practical; otherwise make names and UI text consistently mean water, not a generic hazard.
|
- Rename code-facing concepts where practical; otherwise make names and UI text consistently mean water, not a generic hazard.
|
||||||
- `water` pipe failures should inject `Water`, not a damaging liquid.
|
- `water` pipe failures should inject `Water`, not a damaging liquid.
|
||||||
- `Water` alone must not cause `UnsafeEntryLoss`.
|
- `Water` alone must not cause `UnsafeEntryLoss`.
|
||||||
- [ ] Update leak injection.
|
- [x] Update leak injection.
|
||||||
- Leaks inject only when the underground leak cell has positive amount and positive pressure/voltage after propagation.
|
- Leaks inject only when the underground leak cell has positive amount and positive pressure/voltage after propagation.
|
||||||
- Isolating a leak stops fresh injection without repairing the underlying fault.
|
- Isolating a leak stops fresh injection without repairing the underlying fault.
|
||||||
- Repair restores structural integrity and stops future injection but does not clean existing surface values.
|
- Repair restores structural integrity and stops future injection but does not clean existing surface values.
|
||||||
- [ ] Implement the approved surface interaction order.
|
- [x] Implement the approved surface interaction order.
|
||||||
- Resolve leak/sprinkler injection per `Step`.
|
- Resolve leak/sprinkler injection per `Step`.
|
||||||
- Resolve water mitigation before ignition and electrical spread.
|
- Resolve water mitigation before ignition and electrical spread.
|
||||||
- Implement `Dilute`, `Quench`, value-based `Evaporate`, wet-electric `Conduct`, and `Ignite`.
|
- Implement `Dilute`, `Quench`, value-based `Evaporate`, wet-electric `Conduct`, and `Ignite`.
|
||||||
- Preserve deterministic same-cell and adjacent-cell delta accumulation.
|
- Preserve deterministic same-cell and adjacent-cell delta accumulation.
|
||||||
- Closed powered doors and remedy blocks must gate only the interactions they explicitly block.
|
- Closed powered doors and remedy blocks must gate only the interactions they explicitly block.
|
||||||
- [ ] Implement value-based evaporation.
|
- [x] Implement value-based evaporation.
|
||||||
- Add balance values for ambient evaporation, heat-driven evaporation, and evaporation cooling.
|
- Add balance values for ambient evaporation, heat-driven evaporation, and evaporation cooling.
|
||||||
- Hot cells should evaporate `Water` faster than cold cells.
|
- Hot cells should evaporate `Water` faster than cold cells.
|
||||||
- Evaporation happens during useful action pulses; there is no campaign wait command.
|
- Evaporation happens during useful action pulses; there is no campaign wait command.
|
||||||
- [ ] Implement `Unsafe` as derived movement safety.
|
- [x] Implement `Unsafe` as derived movement safety.
|
||||||
- `Unsafe` is recalculated after authored setup and after each `Pulse`.
|
- `Unsafe` is recalculated after authored setup and after each `Pulse`.
|
||||||
- `Unsafe` is caused by unsafe `Heat`, unsafe `LeakedElectricity`, or the wet-electric unsafe rule.
|
- `Unsafe` is caused by unsafe `Heat`, unsafe `LeakedElectricity`, or the wet-electric unsafe rule.
|
||||||
- `LeakedFuel` alone and `Water` alone are not `Unsafe`.
|
- `LeakedFuel` alone and `Water` alone are not `Unsafe`.
|
||||||
- `UnsafeEntryLoss` happens only when `MoveRobot` enters an `Unsafe` destination without applicable protection.
|
- `UnsafeEntryLoss` happens only when `MoveRobot` enters an `Unsafe` destination without applicable protection.
|
||||||
- A `Pulse` must not kill a stationary robot just because the current cell becomes `Unsafe`.
|
- A `Pulse` must not kill a stationary robot just because the current cell becomes `Unsafe`.
|
||||||
- [ ] Implement powered prop behavior.
|
- [x] Implement powered prop behavior.
|
||||||
- `DoorProp` and `AllSeeingEyeTerminal` require positive local electricity amount and voltage for their interactions to take effect.
|
- `DoorProp` and `AllSeeingEyeTerminal` require positive local electricity amount and voltage for their interactions to take effect.
|
||||||
- Interacting with an unpowered `PoweredProp` is accepted as a `LengthyAction`, changes no prop state, reveals no terminal information, and still triggers one `Pulse`.
|
- Interacting with an unpowered `PoweredProp` is accepted as a `LengthyAction`, changes no prop state, reveals no terminal information, and still triggers one `Pulse`.
|
||||||
- Powered doors keep their last physical open/closed state when power is lost.
|
- Powered doors keep their last physical open/closed state when power is lost.
|
||||||
- [ ] Gate all-seeing-eye information.
|
- [x] Gate all-seeing-eye information.
|
||||||
- Underground topology, numeric underground values, and `Forecast` output are visible only while the robot is at an active and powered `AllSeeingEyeTerminal`.
|
- Underground topology, numeric underground values, and `Forecast` output are visible only while the robot is at an active and powered `AllSeeingEyeTerminal`.
|
||||||
- Terminal access is local and does not persist after the robot leaves.
|
- Terminal access is local and does not persist after the robot leaves.
|
||||||
- Forecasts are systemic simulations over copied state, never authored level prose.
|
- Forecasts are systemic simulations over copied state, never authored level prose.
|
||||||
- [ ] Update `ReactorReadiness` checks as the invariant source of victory.
|
- [x] Update `ReactorReadiness` checks as the invariant source of victory.
|
||||||
- Every network present beneath `ReactorControlProp` must have positive amount and intensity.
|
- Every network present beneath `ReactorControlProp` must have positive amount and intensity.
|
||||||
- Required per-carrier consumer counts must be `Enabled` and `Producing`.
|
- Required per-carrier consumer counts must be `Enabled` and `Producing`.
|
||||||
- Missing readiness blocks `Ready` but does not directly cause `Lost`.
|
- Missing readiness blocks `Ready` but does not directly cause `Lost`.
|
||||||
|
|
||||||
## P0 Simulation Tests
|
## P0 Simulation Tests
|
||||||
|
|
||||||
- [ ] Update existing tests so their names and assertions use `Pulse`, `Step`, `Water`, `Unsafe`, and `ReactorReadiness` terminology.
|
- [x] Update existing tests so their names and assertions use `Pulse`, `Step`, `Water`, `Unsafe`, and `ReactorReadiness` terminology.
|
||||||
- [ ] Add tests for fixed pulse length.
|
- [x] Add tests for fixed pulse length.
|
||||||
- Every accepted `LengthyAction` advances one `Pulse`.
|
- Every accepted `LengthyAction` advances one `Pulse`.
|
||||||
- Each pulse resolves the configured number of `Step`s.
|
- Each pulse resolves the configured number of `Step`s.
|
||||||
- `MoveRobot` and inspection-like calls do not resolve a pulse.
|
- `MoveRobot` and inspection-like calls do not resolve a pulse.
|
||||||
- [ ] Add tests for no normal wait/fast-forward dependency.
|
- [x] Add tests for no normal wait/fast-forward dependency.
|
||||||
- Player-facing session/action APIs should not require `EndTurn` to reach readiness after a lengthy action.
|
- Player-facing session/action APIs should not require `EndTurn` to reach readiness after a lengthy action.
|
||||||
- Debug/test-only pulse advancement, if retained, must be clearly named and excluded from campaign UI.
|
- Debug/test-only pulse advancement, if retained, must be clearly named and excluded from campaign UI.
|
||||||
- [ ] Add tests for `IsolationValveProp`.
|
- [x] Add tests for `IsolationValveProp`.
|
||||||
- Open valve allows branch propagation.
|
- Open valve allows branch propagation.
|
||||||
- Closed valve isolates damaged branches and can starve downstream consumers/reactor feed.
|
- Closed valve isolates damaged branches and can starve downstream consumers/reactor feed.
|
||||||
- Toggling a valve triggers exactly one `Pulse`.
|
- Toggling a valve triggers exactly one `Pulse`.
|
||||||
- [ ] Add tests for `SprinklerControlProp` and `SprinklerValve`.
|
- [x] Add tests for `SprinklerControlProp` and `SprinklerValve`.
|
||||||
- Valve discharge requires a linked enabled control and fed water branch.
|
- Valve discharge requires a linked enabled control and fed water branch.
|
||||||
- Direct valve interaction is invalid or unavailable.
|
- Direct valve interaction is invalid or unavailable.
|
||||||
- Discharge creates `Water` at the authored outlet and applies pressure debt.
|
- Discharge creates `Water` at the authored outlet and applies pressure debt.
|
||||||
- Disabling the linked control or isolating the sprinkler branch stops fresh discharge.
|
- Disabling the linked control or isolating the sprinkler branch stops fresh discharge.
|
||||||
- [ ] Add tests for updated leak injection.
|
- [x] Add tests for updated leak injection.
|
||||||
- Fed fuel, water, and electricity leaks inject to the correct access face.
|
- Fed fuel, water, and electricity leaks inject to the correct access face.
|
||||||
- Isolated leaks stop new surface injection.
|
- Isolated leaks stop new surface injection.
|
||||||
- Repairs restore structural integrity without cleaning existing surface values.
|
- Repairs restore structural integrity without cleaning existing surface values.
|
||||||
- [ ] Add tests for surface interactions.
|
- [x] Add tests for surface interactions.
|
||||||
- `Water` dilutes `LeakedFuel`.
|
- `Water` dilutes `LeakedFuel`.
|
||||||
- `Water` quenches `Heat`.
|
- `Water` quenches `Heat`.
|
||||||
- Evaporation removes water and cools heat according to balance values.
|
- Evaporation removes water and cools heat according to balance values.
|
||||||
- Wet cells conduct electricity faster than dry cells.
|
- Wet cells conduct electricity faster than dry cells.
|
||||||
- Fuel plus electricity or heat can ignite and create heat while consuming fuel.
|
- Fuel plus electricity or heat can ignite and create heat while consuming fuel.
|
||||||
- Closed doors block designed surface propagation paths.
|
- Closed doors block designed surface propagation paths.
|
||||||
- [ ] Add tests for `Unsafe`.
|
- [x] Add tests for `Unsafe`.
|
||||||
- Moving into unsafe heat loses without active heat protection.
|
- Moving into unsafe heat loses without active heat protection.
|
||||||
- Moving into unsafe electricity loses.
|
- Moving into unsafe electricity loses.
|
||||||
- Moving into wet-electric unsafe cells loses.
|
- Moving into wet-electric unsafe cells loses.
|
||||||
- Moving into fuel-only or sprinkler-water-only cells does not lose.
|
- Moving into fuel-only or sprinkler-water-only cells does not lose.
|
||||||
- A pulse that makes the robot's current cell unsafe does not immediately lose.
|
- A pulse that makes the robot's current cell unsafe does not immediately lose.
|
||||||
- [ ] Add tests for powered props.
|
- [x] Add tests for powered props.
|
||||||
- Powered door toggles and blocks/unblocks surface propagation.
|
- Powered door toggles and blocks/unblocks surface propagation.
|
||||||
- Unpowered door interaction changes no door state but still triggers a pulse.
|
- Unpowered door interaction changes no door state but still triggers a pulse.
|
||||||
- Powered terminal interaction enables local visibility and forecasts.
|
- Powered terminal interaction enables local visibility and forecasts.
|
||||||
@@ -122,19 +120,19 @@ This backlog tracks what must change so the implementation matches `docs/design.
|
|||||||
|
|
||||||
## P1 Editor, Schema, And Level Data
|
## P1 Editor, Schema, And Level Data
|
||||||
|
|
||||||
- [ ] Bump the level schema version when adding new prop/link/outlet state.
|
- [x] Bump the level schema version when adding new prop/link/outlet state.
|
||||||
- [ ] Update serialization round trips for new fields.
|
- [x] Update serialization round trips for new fields.
|
||||||
- `IsolationValveProp` carrier and open/closed state.
|
- `IsolationValveProp` carrier and open/closed state.
|
||||||
- `SprinklerControlProp` enabled/disabled state and linked valve id or position.
|
- `SprinklerControlProp` enabled/disabled state and linked valve id or position.
|
||||||
- `SprinklerValve` wall position, outlet/access face, linked control, and water connection.
|
- `SprinklerValve` wall position, outlet/access face, linked control, and water connection.
|
||||||
- Powered terminal active state if it becomes serialized runtime state.
|
- Powered terminal active state if it becomes serialized runtime state.
|
||||||
- [ ] Update `LevelEditor` tools.
|
- [x] Update `LevelEditor` tools.
|
||||||
- Add isolation valve placement.
|
- Add isolation valve placement.
|
||||||
- Add sprinkler control placement.
|
- Add sprinkler control placement.
|
||||||
- Add wall-mounted sprinkler valve placement and outlet/access cycling.
|
- Add wall-mounted sprinkler valve placement and outlet/access cycling.
|
||||||
- Keep electricity leak wall access cycling.
|
- Keep electricity leak wall access cycling.
|
||||||
- Prevent invalid prop placement on walls except designed wall-mounted sprinkler valves.
|
- Prevent invalid prop placement on walls except designed wall-mounted sprinkler valves.
|
||||||
- [ ] Update `LevelValidator`.
|
- [x] Update `LevelValidator`.
|
||||||
- Validate powered doors have valid geometry and local electricity.
|
- Validate powered doors have valid geometry and local electricity.
|
||||||
- Validate terminal power requirements where needed.
|
- Validate terminal power requirements where needed.
|
||||||
- Validate wall-mounted sprinkler valve geometry, outlet/access face, water connection, and exactly one linked control.
|
- Validate wall-mounted sprinkler valve geometry, outlet/access face, water connection, and exactly one linked control.
|
||||||
@@ -145,16 +143,16 @@ This backlog tracks what must change so the implementation matches `docs/design.
|
|||||||
- Update `default_campaign_manifest.json` to the final order.
|
- Update `default_campaign_manifest.json` to the final order.
|
||||||
- Remove or demote old placeholder levels so they are not presented as campaign content.
|
- Remove or demote old placeholder levels so they are not presented as campaign content.
|
||||||
- Add stable ids and short flavor text for every authored level.
|
- Add stable ids and short flavor text for every authored level.
|
||||||
- [ ] Add test/build helpers for level construction.
|
- [x] Add test/build helpers for level construction.
|
||||||
- Prefer shared builders for linear networks, forks, leaks, wall electricity faces, doors, controls, consumers, and reactors.
|
- Prefer shared builders for linear networks, forks, leaks, wall electricity faces, doors, controls, consumers, and reactors.
|
||||||
- Avoid duplicating low-level array setup across tests.
|
- Avoid duplicating low-level array setup across tests.
|
||||||
|
|
||||||
## P1 Godot UX Integration
|
## P1 Godot UX Integration
|
||||||
|
|
||||||
- [ ] Remove player-facing `End Turn` from the normal action bar.
|
- [x] Remove player-facing `End Turn` from the normal action bar.
|
||||||
- Replace it with contextual `LengthyAction` commands and `ActivateReactor` when ready.
|
- Replace it with contextual `LengthyAction` commands and `ActivateReactor` when ready.
|
||||||
- Keep any debug pulse control behind an explicit development-only path.
|
- Keep any debug pulse control behind an explicit development-only path.
|
||||||
- [ ] Update `GameSession` API names and events to match pulse semantics.
|
- [x] Update `GameSession` API names and events to match pulse semantics.
|
||||||
- Use `PulseAdvanced` or equivalent instead of `TurnAdvanced`.
|
- Use `PulseAdvanced` or equivalent instead of `TurnAdvanced`.
|
||||||
- Ensure accepted no-op powered-prop interactions still notify pulse playback.
|
- Ensure accepted no-op powered-prop interactions still notify pulse playback.
|
||||||
- Ensure refused invalid actions do not mutate state or trigger pulse playback.
|
- Ensure refused invalid actions do not mutate state or trigger pulse playback.
|
||||||
@@ -186,9 +184,9 @@ This backlog tracks what must change so the implementation matches `docs/design.
|
|||||||
|
|
||||||
## Verification Rules
|
## Verification Rules
|
||||||
|
|
||||||
- [ ] After each implementation iteration, run the focused simulation tests that cover the changed system.
|
- [x] After each implementation iteration, run the focused simulation tests that cover the changed system.
|
||||||
- [ ] Run full `dotnet test` before committing simulation or serializer changes.
|
- [x] Run full `dotnet test` before committing simulation or serializer changes.
|
||||||
- [ ] For code changes on Windows, run the repo-required CRLF normalization and cleanup steps after implementation.
|
- [x] For code changes on Windows, run the repo-required CRLF normalization and cleanup steps after implementation.
|
||||||
- [ ] For documentation-only iterations, no cleanup pass is required.
|
- [x] For documentation-only iterations, no cleanup pass is required.
|
||||||
- [ ] Keep documentation aligned whenever code changes terminology or behavior.
|
- [x] Keep documentation aligned whenever code changes terminology or behavior.
|
||||||
- [ ] Commit each completed iteration with a brief summary.
|
- [x] Commit each completed iteration with a brief summary.
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public partial class ForecastList : PanelContainer
|
|||||||
{
|
{
|
||||||
var pos = forecast.Position;
|
var pos = forecast.Position;
|
||||||
var posStr = pos != null ? $" [{pos.X},{pos.Y}]" : "";
|
var posStr = pos != null ? $" [{pos.X},{pos.Y}]" : "";
|
||||||
return $"Turn +{forecast.Turns}: {forecast.Message}{posStr}";
|
return $"Pulse +{forecast.Turns}: {forecast.Message}{posStr}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ApplyForecastColor(Label label, EForecastKind kind)
|
private static void ApplyForecastColor(Label label, EForecastKind kind)
|
||||||
|
|||||||
@@ -514,6 +514,9 @@ public partial class GridViewport : Control
|
|||||||
EPropType.Consumer => new(0.36f, 0.48f, 0.67f),
|
EPropType.Consumer => new(0.36f, 0.48f, 0.67f),
|
||||||
EPropType.Junction => new(0.56f, 0.44f, 0.70f),
|
EPropType.Junction => new(0.56f, 0.44f, 0.70f),
|
||||||
EPropType.AllSeeingEyeTerminal => new(0.33f, 0.59f, 0.61f),
|
EPropType.AllSeeingEyeTerminal => new(0.33f, 0.59f, 0.61f),
|
||||||
|
EPropType.IsolationValve => CarrierColor(prop.Carrier),
|
||||||
|
EPropType.SprinklerControl => new(0.21f, 0.59f, 0.73f),
|
||||||
|
EPropType.SprinklerValve => new(0.15f, 0.44f, 0.59f),
|
||||||
EPropType.RemedySupply => new(0.30f, 0.57f, 0.34f),
|
EPropType.RemedySupply => new(0.30f, 0.57f, 0.34f),
|
||||||
EPropType.ReactorControl => new(0.69f, 0.28f, 0.29f),
|
EPropType.ReactorControl => new(0.69f, 0.28f, 0.29f),
|
||||||
_ => Colors.Gray
|
_ => Colors.Gray
|
||||||
@@ -527,6 +530,9 @@ public partial class GridViewport : Control
|
|||||||
EPropType.Consumer => "CON",
|
EPropType.Consumer => "CON",
|
||||||
EPropType.Junction => $"J {prop.JunctionMode}",
|
EPropType.Junction => $"J {prop.JunctionMode}",
|
||||||
EPropType.AllSeeingEyeTerminal => "EYE",
|
EPropType.AllSeeingEyeTerminal => "EYE",
|
||||||
|
EPropType.IsolationValve => prop.IsOpen ? "V OPEN" : "V CLOSED",
|
||||||
|
EPropType.SprinklerControl => prop.IsEnabled ? "SPR ON" : "SPR OFF",
|
||||||
|
EPropType.SprinklerValve => "SPR",
|
||||||
EPropType.RemedySupply => RemedyShort(prop.RemedyType),
|
EPropType.RemedySupply => RemedyShort(prop.RemedyType),
|
||||||
EPropType.ReactorControl => "REACT",
|
EPropType.ReactorControl => "REACT",
|
||||||
_ => string.Empty
|
_ => string.Empty
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ public sealed class GameSession
|
|||||||
{
|
{
|
||||||
public event StateChangedHandler? LevelStateChanged;
|
public event StateChangedHandler? LevelStateChanged;
|
||||||
public event StateChangedHandler? RobotMoved;
|
public event StateChangedHandler? RobotMoved;
|
||||||
public event StateChangedHandler? TurnAdvanced;
|
public event StateChangedHandler? PulseAdvanced;
|
||||||
public event StateChangedHandler? LevelWon;
|
public event StateChangedHandler? LevelWon;
|
||||||
public event StateChangedHandler? LevelLost;
|
public event StateChangedHandler? LevelLost;
|
||||||
|
|
||||||
@@ -31,20 +31,13 @@ public sealed class GameSession
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LevelState EndTurn()
|
|
||||||
{
|
|
||||||
LevelState = m_Engine.EndTurn(LevelState);
|
|
||||||
OnTurnAdvanced();
|
|
||||||
return LevelState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool InteractProp()
|
public bool InteractProp()
|
||||||
{
|
{
|
||||||
if (LevelState.Global.LevelState is ELevelState.Lost or ELevelState.Won)
|
if (LevelState.Global.LevelState is ELevelState.Lost or ELevelState.Won)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
LevelState = m_Engine.InteractProp(LevelState);
|
LevelState = m_Engine.InteractProp(LevelState);
|
||||||
OnTurnAdvanced();
|
OnPulseAdvanced();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +47,7 @@ public sealed class GameSession
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
LevelState = m_Engine.InteractLeak(LevelState, carrier, useRemedy);
|
LevelState = m_Engine.InteractLeak(LevelState, carrier, useRemedy);
|
||||||
OnTurnAdvanced();
|
OnPulseAdvanced();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +57,7 @@ public sealed class GameSession
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
LevelState = m_Engine.ApplyHeatShield(LevelState);
|
LevelState = m_Engine.ApplyHeatShield(LevelState);
|
||||||
OnTurnAdvanced();
|
OnPulseAdvanced();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,10 +82,10 @@ public sealed class GameSession
|
|||||||
LevelStateChanged?.Invoke(this);
|
LevelStateChanged?.Invoke(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTurnAdvanced()
|
private void OnPulseAdvanced()
|
||||||
{
|
{
|
||||||
CheckOutcome();
|
CheckOutcome();
|
||||||
TurnAdvanced?.Invoke(this);
|
PulseAdvanced?.Invoke(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckOutcome()
|
private void CheckOutcome()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"Version": 3,
|
"Version": 4,
|
||||||
"Level": {
|
"Level": {
|
||||||
"Name": "Black Start",
|
"Name": "Black Start",
|
||||||
"Width": 10,
|
"Width": 10,
|
||||||
@@ -3971,4 +3971,4 @@
|
|||||||
},
|
},
|
||||||
"Forecasts": []
|
"Forecasts": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"Version": 3,
|
"Version": 4,
|
||||||
"Level": {
|
"Level": {
|
||||||
"Name": "Coolant Restart",
|
"Name": "Coolant Restart",
|
||||||
"Width": 8,
|
"Width": 8,
|
||||||
@@ -2795,4 +2795,4 @@
|
|||||||
},
|
},
|
||||||
"Forecasts": []
|
"Forecasts": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"Version": 3,
|
"Version": 4,
|
||||||
"Level": {
|
"Level": {
|
||||||
"Name": "Fuel Bleed",
|
"Name": "Fuel Bleed",
|
||||||
"Width": 8,
|
"Width": 8,
|
||||||
@@ -2808,4 +2808,4 @@
|
|||||||
},
|
},
|
||||||
"Forecasts": []
|
"Forecasts": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public partial class LevelScreen : ScreenBase
|
|||||||
UpdateUI();
|
UpdateUI();
|
||||||
m_Session.LevelStateChanged += OnLevelStateChanged;
|
m_Session.LevelStateChanged += OnLevelStateChanged;
|
||||||
m_Session.RobotMoved += OnRobotMoved;
|
m_Session.RobotMoved += OnRobotMoved;
|
||||||
m_Session.TurnAdvanced += OnTurnAdvanced;
|
m_Session.PulseAdvanced += OnPulseAdvanced;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GridViewport CreateGridViewport()
|
private GridViewport CreateGridViewport()
|
||||||
@@ -131,7 +131,6 @@ public partial class LevelScreen : ScreenBase
|
|||||||
actions.AddChild(CreateButton("Move", OnMoveAction, "Move robot to adjacent floor cell"));
|
actions.AddChild(CreateButton("Move", OnMoveAction, "Move robot to adjacent floor cell"));
|
||||||
actions.AddChild(CreateButton("Interact", OnInteractAction, "Interact with prop at robot position"));
|
actions.AddChild(CreateButton("Interact", OnInteractAction, "Interact with prop at robot position"));
|
||||||
actions.AddChild(CreateButton("Repair", OnRepairAction, "Repair leak at robot position"));
|
actions.AddChild(CreateButton("Repair", OnRepairAction, "Repair leak at robot position"));
|
||||||
actions.AddChild(CreateButton("End Turn", OnEndTurnAction));
|
|
||||||
actions.AddChild(CreateButton("Main Menu", () => m_App?.ShowMainMenu()));
|
actions.AddChild(CreateButton("Main Menu", () => m_App?.ShowMainMenu()));
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
@@ -168,11 +167,6 @@ public partial class LevelScreen : ScreenBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEndTurnAction()
|
|
||||||
{
|
|
||||||
m_Session?.EndTurn();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateUI()
|
private void UpdateUI()
|
||||||
{
|
{
|
||||||
if (m_Session is null || m_Inspector is null || m_ForecastList is null || m_InventoryStrip is null)
|
if (m_Session is null || m_Inspector is null || m_ForecastList is null || m_InventoryStrip is null)
|
||||||
@@ -228,7 +222,7 @@ public partial class LevelScreen : ScreenBase
|
|||||||
UpdateUI();
|
UpdateUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTurnAdvanced(GameSession sender)
|
private void OnPulseAdvanced(GameSession sender)
|
||||||
{
|
{
|
||||||
UpdateUI();
|
UpdateUI();
|
||||||
if (sender.LevelState.Global.LevelState is ELevelState.Lost or ELevelState.Won)
|
if (sender.LevelState.Global.LevelState is ELevelState.Lost or ELevelState.Won)
|
||||||
|
|||||||
@@ -37,18 +37,6 @@ public sealed class SimulationEngine
|
|||||||
return ResolveStep(level);
|
return ResolveStep(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("Use AdvancePulseForDebug. Player-facing wait/turn advancement is not part of normal gameplay.")]
|
|
||||||
public LevelState EndTurn(LevelState level)
|
|
||||||
{
|
|
||||||
return AdvancePulseForDebug(level);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Obsolete("Use AdvancePulseForDebug. Player-facing wait/turn advancement is not part of normal gameplay.")]
|
|
||||||
public LevelState AdvanceTurn(LevelState level)
|
|
||||||
{
|
|
||||||
return AdvancePulseForDebug(level);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyList<Forecast> Forecast(LevelState level)
|
public IReadOnlyList<Forecast> Forecast(LevelState level)
|
||||||
{
|
{
|
||||||
if (!level.HasActivePoweredTerminalAccess())
|
if (!level.HasActivePoweredTerminalAccess())
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<AppBarButton Icon="Save" Label="Save" Click="Save_Click" />
|
<AppBarButton Icon="Save" Label="Save" Click="Save_Click" />
|
||||||
<AppBarSeparator />
|
<AppBarSeparator />
|
||||||
<AppBarButton x:Name="PlayPauseButton" Icon="Play" Label="Play" Click="PlayPause_Click" />
|
<AppBarButton x:Name="PlayPauseButton" Icon="Play" Label="Play" Click="PlayPause_Click" />
|
||||||
<AppBarButton Icon="Forward" Label="End Turn" Click="EndTurn_Click" />
|
<AppBarButton Icon="Forward" Label="Debug Pulse" Click="DebugPulse_Click" />
|
||||||
<AppBarButton Label="Interact" Click="Interact_Click" />
|
<AppBarButton Label="Interact" Click="Interact_Click" />
|
||||||
<AppBarButton Label="Heat Shield" Click="HeatShield_Click" />
|
<AppBarButton Label="Heat Shield" Click="HeatShield_Click" />
|
||||||
<AppBarButton Icon="Accept" Label="Activate" Click="Activate_Click" />
|
<AppBarButton Icon="Accept" Label="Activate" Click="Activate_Click" />
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Text="Turn" Foreground="#9EA7AE" />
|
<TextBlock Text="Pulse" Foreground="#9EA7AE" />
|
||||||
<TextBlock x:Name="TurnText" FontSize="22" Foreground="#F4F1E8" />
|
<TextBlock x:Name="TurnText" FontSize="22" Foreground="#F4F1E8" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Grid.Column="1">
|
<StackPanel Grid.Column="1">
|
||||||
|
|||||||
@@ -217,9 +217,9 @@ public sealed partial class MainWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EndTurn_Click(object sender, RoutedEventArgs e)
|
private void DebugPulse_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
RunSimulationStep();
|
RunDebugPulse();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlayPause_Click(object sender, RoutedEventArgs e)
|
private void PlayPause_Click(object sender, RoutedEventArgs e)
|
||||||
@@ -232,7 +232,7 @@ public sealed partial class MainWindow
|
|||||||
|
|
||||||
private void SimulationTimer_Tick(object? sender, object e)
|
private void SimulationTimer_Tick(object? sender, object e)
|
||||||
{
|
{
|
||||||
RunSimulationStep();
|
RunDebugPulse();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartSimulationTimer()
|
private void StartSimulationTimer()
|
||||||
@@ -254,9 +254,9 @@ public sealed partial class MainWindow
|
|||||||
PlayPauseButton.Label = isPlaying ? "Pause" : "Play";
|
PlayPauseButton.Label = isPlaying ? "Pause" : "Play";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RunSimulationStep()
|
private void RunDebugPulse()
|
||||||
{
|
{
|
||||||
m_Level = m_Simulation.EndTurn(m_Level);
|
m_Level = m_Simulation.AdvancePulseForDebug(m_Level);
|
||||||
RefreshInspector();
|
RefreshInspector();
|
||||||
LevelCanvas.Invalidate();
|
LevelCanvas.Invalidate();
|
||||||
}
|
}
|
||||||
@@ -878,7 +878,7 @@ public sealed partial class MainWindow
|
|||||||
private void RefreshInspector()
|
private void RefreshInspector()
|
||||||
{
|
{
|
||||||
LevelNameText.Text = m_Level.Name;
|
LevelNameText.Text = m_Level.Name;
|
||||||
TurnText.Text = m_Level.Global.Turn.ToString(CultureInfo.InvariantCulture);
|
TurnText.Text = m_Level.Global.Pulse.ToString(CultureInfo.InvariantCulture);
|
||||||
StatusText.Text = string.IsNullOrWhiteSpace(m_EditorFeedback)
|
StatusText.Text = string.IsNullOrWhiteSpace(m_EditorFeedback)
|
||||||
? $"{m_Level.Global.LevelState}: {m_Level.Global.Status}"
|
? $"{m_Level.Global.LevelState}: {m_Level.Global.Status}"
|
||||||
: $"{m_Level.Global.LevelState}: {m_EditorFeedback}";
|
: $"{m_Level.Global.LevelState}: {m_EditorFeedback}";
|
||||||
@@ -1152,6 +1152,9 @@ public sealed partial class MainWindow
|
|||||||
EPropType.Junction => ColorHelper.FromArgb(255, 143, 111, 178),
|
EPropType.Junction => ColorHelper.FromArgb(255, 143, 111, 178),
|
||||||
EPropType.Door => ColorHelper.FromArgb(255, 187, 119, 55),
|
EPropType.Door => ColorHelper.FromArgb(255, 187, 119, 55),
|
||||||
EPropType.AllSeeingEyeTerminal => ColorHelper.FromArgb(255, 85, 151, 156),
|
EPropType.AllSeeingEyeTerminal => ColorHelper.FromArgb(255, 85, 151, 156),
|
||||||
|
EPropType.IsolationValve => CarrierColor(prop.Carrier),
|
||||||
|
EPropType.SprinklerControl => ColorHelper.FromArgb(255, 54, 150, 186),
|
||||||
|
EPropType.SprinklerValve => ColorHelper.FromArgb(255, 38, 112, 150),
|
||||||
EPropType.RemedySupply => ColorHelper.FromArgb(255, 76, 145, 86),
|
EPropType.RemedySupply => ColorHelper.FromArgb(255, 76, 145, 86),
|
||||||
EPropType.ReactorControl => ColorHelper.FromArgb(255, 177, 72, 73),
|
EPropType.ReactorControl => ColorHelper.FromArgb(255, 177, 72, 73),
|
||||||
_ => Colors.Gray
|
_ => Colors.Gray
|
||||||
@@ -1178,6 +1181,9 @@ public sealed partial class MainWindow
|
|||||||
EEditorTool.Junction => EPropType.Junction,
|
EEditorTool.Junction => EPropType.Junction,
|
||||||
EEditorTool.Door => EPropType.Door,
|
EEditorTool.Door => EPropType.Door,
|
||||||
EEditorTool.AllSeeingEyeTerminal => EPropType.AllSeeingEyeTerminal,
|
EEditorTool.AllSeeingEyeTerminal => EPropType.AllSeeingEyeTerminal,
|
||||||
|
EEditorTool.IsolationValve => EPropType.IsolationValve,
|
||||||
|
EEditorTool.SprinklerControl => EPropType.SprinklerControl,
|
||||||
|
EEditorTool.SprinklerValve => EPropType.SprinklerValve,
|
||||||
EEditorTool.RemedySupply => EPropType.RemedySupply,
|
EEditorTool.RemedySupply => EPropType.RemedySupply,
|
||||||
EEditorTool.ReactorControl => EPropType.ReactorControl,
|
EEditorTool.ReactorControl => EPropType.ReactorControl,
|
||||||
_ => EPropType.None
|
_ => EPropType.None
|
||||||
@@ -1192,6 +1198,9 @@ public sealed partial class MainWindow
|
|||||||
EPropType.Junction => "prop-junction",
|
EPropType.Junction => "prop-junction",
|
||||||
EPropType.Door => "prop-door",
|
EPropType.Door => "prop-door",
|
||||||
EPropType.AllSeeingEyeTerminal => "prop-eye-terminal",
|
EPropType.AllSeeingEyeTerminal => "prop-eye-terminal",
|
||||||
|
EPropType.IsolationValve => "prop-isolation-valve",
|
||||||
|
EPropType.SprinklerControl => "prop-sprinkler-control",
|
||||||
|
EPropType.SprinklerValve => "prop-sprinkler-valve",
|
||||||
EPropType.RemedySupply => "prop-remedy",
|
EPropType.RemedySupply => "prop-remedy",
|
||||||
EPropType.ReactorControl => "prop-reactor",
|
EPropType.ReactorControl => "prop-reactor",
|
||||||
_ => "prop"
|
_ => "prop"
|
||||||
@@ -1231,6 +1240,9 @@ public sealed partial class MainWindow
|
|||||||
EPropType.Junction => $"J {prop.JunctionMode}",
|
EPropType.Junction => $"J {prop.JunctionMode}",
|
||||||
EPropType.Door => "DOOR",
|
EPropType.Door => "DOOR",
|
||||||
EPropType.AllSeeingEyeTerminal => "EYE",
|
EPropType.AllSeeingEyeTerminal => "EYE",
|
||||||
|
EPropType.IsolationValve => prop.IsOpen ? "V OPEN" : "V CLOSED",
|
||||||
|
EPropType.SprinklerControl => prop.IsEnabled ? "SPR ON" : "SPR OFF",
|
||||||
|
EPropType.SprinklerValve => "SPR",
|
||||||
EPropType.RemedySupply => RemedyShort(prop.RemedyType),
|
EPropType.RemedySupply => RemedyShort(prop.RemedyType),
|
||||||
EPropType.ReactorControl => "REACT",
|
EPropType.ReactorControl => "REACT",
|
||||||
_ => string.Empty
|
_ => string.Empty
|
||||||
@@ -1300,10 +1312,15 @@ public sealed partial class MainWindow
|
|||||||
CarrierTool(EEditorTool.Flow, ECarrierType.Fuel, "Fuel Source"),
|
CarrierTool(EEditorTool.Flow, ECarrierType.Fuel, "Fuel Source"),
|
||||||
CarrierTool(EEditorTool.Flow, ECarrierType.Water, "Water Source"),
|
CarrierTool(EEditorTool.Flow, ECarrierType.Water, "Water Source"),
|
||||||
CarrierTool(EEditorTool.Flow, ECarrierType.Electricity, "Electric Source"),
|
CarrierTool(EEditorTool.Flow, ECarrierType.Electricity, "Electric Source"),
|
||||||
|
CarrierTool(EEditorTool.IsolationValve, ECarrierType.Fuel, "Fuel Valve"),
|
||||||
|
CarrierTool(EEditorTool.IsolationValve, ECarrierType.Water, "Water Valve"),
|
||||||
|
CarrierTool(EEditorTool.IsolationValve, ECarrierType.Electricity, "Electric Valve"),
|
||||||
Tool(EEditorTool.Consumer, "Consumer"),
|
Tool(EEditorTool.Consumer, "Consumer"),
|
||||||
Tool(EEditorTool.Junction, "Junction"),
|
Tool(EEditorTool.Junction, "Junction"),
|
||||||
Tool(EEditorTool.Door, "Door"),
|
Tool(EEditorTool.Door, "Door"),
|
||||||
Tool(EEditorTool.AllSeeingEyeTerminal, "Eye Terminal"),
|
Tool(EEditorTool.AllSeeingEyeTerminal, "Eye Terminal"),
|
||||||
|
Tool(EEditorTool.SprinklerControl, "Sprinkler Control"),
|
||||||
|
Tool(EEditorTool.SprinklerValve, "Sprinkler Valve"),
|
||||||
RemedyTool(ERemedyType.FuelNeutralizer, "Fuel Remedy"),
|
RemedyTool(ERemedyType.FuelNeutralizer, "Fuel Remedy"),
|
||||||
RemedyTool(ERemedyType.WaterNeutralizer, "Water Remedy"),
|
RemedyTool(ERemedyType.WaterNeutralizer, "Water Remedy"),
|
||||||
RemedyTool(ERemedyType.ElectricityNeutralizer, "Electric Remedy"),
|
RemedyTool(ERemedyType.ElectricityNeutralizer, "Electric Remedy"),
|
||||||
@@ -1333,6 +1350,9 @@ public sealed partial class MainWindow
|
|||||||
or EEditorTool.Junction
|
or EEditorTool.Junction
|
||||||
or EEditorTool.Door
|
or EEditorTool.Door
|
||||||
or EEditorTool.AllSeeingEyeTerminal
|
or EEditorTool.AllSeeingEyeTerminal
|
||||||
|
or EEditorTool.IsolationValve
|
||||||
|
or EEditorTool.SprinklerControl
|
||||||
|
or EEditorTool.SprinklerValve
|
||||||
or EEditorTool.RemedySupply
|
or EEditorTool.RemedySupply
|
||||||
or EEditorTool.ReactorControl
|
or EEditorTool.ReactorControl
|
||||||
or EEditorTool.SurfaceHazard
|
or EEditorTool.SurfaceHazard
|
||||||
@@ -1341,7 +1361,7 @@ public sealed partial class MainWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
var activeCarrier = LayerCarrier(m_ActiveLayer);
|
var activeCarrier = LayerCarrier(m_ActiveLayer);
|
||||||
return command.Carrier == activeCarrier && command.Tool is EEditorTool.Underground or EEditorTool.Flow or EEditorTool.Leak;
|
return command.Carrier == activeCarrier && command.Tool is EEditorTool.Underground or EEditorTool.Flow or EEditorTool.Leak or EEditorTool.IsolationValve;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshForecasts()
|
private void RefreshForecasts()
|
||||||
|
|||||||
@@ -70,6 +70,31 @@ public sealed class LevelEditorTests
|
|||||||
Assert.Equal(EConsumerServiceState.Unknown, next.GetProp(new(2, 2)).ElectricityServiceState);
|
Assert.Equal(EConsumerServiceState.Unknown, next.GetProp(new(2, 2)).ElectricityServiceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsolationValveToolPlacesCarrierBoundValve()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Valve editor", 6, 6);
|
||||||
|
|
||||||
|
var next = LevelEditor.Apply(level, new(2, 2), new() { Tool = EEditorTool.IsolationValve, Carrier = ECarrierType.Fuel });
|
||||||
|
|
||||||
|
Assert.Equal(EPropType.IsolationValve, next.GetProp(new(2, 2)).Type);
|
||||||
|
Assert.Equal(ECarrierType.Fuel, next.GetProp(new(2, 2)).Carrier);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SprinklerToolsPlaceFloorControlAndWallValve()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Sprinkler editor", 6, 6);
|
||||||
|
level = level.SetTerrain(new(2, 2), ECellTerrain.Wall);
|
||||||
|
|
||||||
|
var control = LevelEditor.Apply(level, new(1, 2), new() { Tool = EEditorTool.SprinklerControl });
|
||||||
|
var valve = LevelEditor.Apply(control, new(2, 2), new() { Tool = EEditorTool.SprinklerValve });
|
||||||
|
|
||||||
|
Assert.Equal(EPropType.SprinklerControl, valve.GetProp(new(1, 2)).Type);
|
||||||
|
Assert.Equal(EPropType.SprinklerValve, valve.GetProp(new(2, 2)).Type);
|
||||||
|
Assert.Equal(new(2, 1), valve.GetProp(new(2, 2)).OutletPosition);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ElectricityLeakUsesAuthoredWallAccessFace()
|
public void ElectricityLeakUsesAuthoredWallAccessFace()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ public sealed class SimulationEngineTests
|
|||||||
var next = m_Engine.MoveRobot(level, new(2, 1));
|
var next = m_Engine.MoveRobot(level, new(2, 1));
|
||||||
|
|
||||||
Assert.Equal(new(2, 1), next.Robot.Position);
|
Assert.Equal(new(2, 1), next.Robot.Position);
|
||||||
Assert.Equal(0, next.Global.Turn);
|
Assert.Equal(0, next.Global.Pulse);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -434,6 +434,21 @@ public sealed class SimulationEngineTests
|
|||||||
Assert.Equal(new(6, 3), loaded.Leaks[0].AccessPosition);
|
Assert.Equal(new(6, 3), loaded.Leaks[0].AccessPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LevelSerializationRoundTripsValveAndSprinklerFields()
|
||||||
|
{
|
||||||
|
var level = SprinklerLevel(EPropSwitchState.Disabled);
|
||||||
|
level = level.SetUnderground(new(3, 3), ECarrierType.Fuel, new() { State = EUndergroundState.Intact });
|
||||||
|
level = level.SetProp(new(3, 3), new() { Type = EPropType.IsolationValve, Carrier = ECarrierType.Fuel, SwitchState = EPropSwitchState.Disabled });
|
||||||
|
|
||||||
|
var loaded = LevelSerializer.Deserialize(LevelSerializer.Serialize(level));
|
||||||
|
|
||||||
|
Assert.Equal(EPropType.IsolationValve, loaded.GetProp(new(3, 3)).Type);
|
||||||
|
Assert.Equal(EPropSwitchState.Disabled, loaded.GetProp(new(3, 3)).SwitchState);
|
||||||
|
Assert.Equal(new(2, 1), loaded.GetProp(new(3, 2)).LinkedPosition);
|
||||||
|
Assert.Equal(new(2, 2), loaded.GetProp(new(2, 1)).OutletPosition);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void LevelSerializationRejectsOldSchema()
|
public void LevelSerializationRejectsOldSchema()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user