diff --git a/open-robotics/src/main/java/com/openrobotics/controllers/SetupController.java b/open-robotics/src/main/java/com/openrobotics/controllers/SetupController.java index e3c5aca..68e8226 100644 --- a/open-robotics/src/main/java/com/openrobotics/controllers/SetupController.java +++ b/open-robotics/src/main/java/com/openrobotics/controllers/SetupController.java @@ -796,8 +796,9 @@ private SimulationEngine buildEngineFromSetup() { CoordinationPolicy policy = buildCoordinationPolicy(); Dispatcher dispatcher = new Dispatcher(); - // Seed the dispatcher with the initial rack-to-station workload. - if (maxTasks > 0 && map != null) { + + // Seed the dispatcher with the initial rack-to-station workload if not in manual mode + if (!isManualMode && maxTasks > 0 && map != null) { generateFixedTasks(map, seed, maxTasks, dispatcher); } diff --git a/open-robotics/src/main/java/com/openrobotics/controllers/SimulationController.java b/open-robotics/src/main/java/com/openrobotics/controllers/SimulationController.java index 002109f..b45b8e1 100644 --- a/open-robotics/src/main/java/com/openrobotics/controllers/SimulationController.java +++ b/open-robotics/src/main/java/com/openrobotics/controllers/SimulationController.java @@ -1100,7 +1100,7 @@ private void onViewportMousePressed(MouseEvent e) { } } else { cancelDropoffPicking(); - log("Dropoff assignment cancelled."); + log("Dropoff assignment cancelled. Please select a drop off station"); } return; } @@ -1432,7 +1432,7 @@ private void showPropertiesFor(MapEntity entity) { xSpinner.setPrefWidth(60); ySpinner.setPrefWidth(60); xSpinner.valueProperty().addListener((obs, oldVal, newVal) -> { - if (newVal == null || oldVal == null) return; + if (newVal == null || oldVal == null || newVal == (int) entity.getPosition().getX()) return; if (guardEditor("move")) { xSpinner.getValueFactory().setValue(oldVal); return; } int targetX = newVal; int targetY = (int) entity.getPosition().getY(); @@ -1449,7 +1449,7 @@ private void showPropertiesFor(MapEntity entity) { } }); ySpinner.valueProperty().addListener((obs, oldVal, newVal) -> { - if (newVal == null || oldVal == null) return; + if (newVal == null || oldVal == null || newVal == (int) entity.getPosition().getY()) return; if (guardEditor("move")) { ySpinner.getValueFactory().setValue(oldVal); return; } int targetX = (int) entity.getPosition().getX(); int targetY = newVal; @@ -1545,20 +1545,18 @@ private void showPropertiesFor(MapEntity entity) { }); batterySpinner.valueProperty().addListener((obs, oldVal, newVal) -> { - if (newVal != null && oldVal != null && !newVal.equals(oldVal)) { - if (guardEditor("change battery")) { - // Revert spinner if editor is locked + if (newVal == null || oldVal == null || newVal.floatValue() == robot.getBattery()) return; + if (guardEditor("change battery")) { + // Revert spinner if editor is locked Platform.runLater(() -> batterySpinner.getValueFactory().setValue(oldVal)); - return; - } - + return; + } float newBat = newVal.floatValue(); float oldBat = oldVal.floatValue(); robot.setBattery(newBat); - drawViewport(); - pushAction(new BatteryChangeAction(robot, oldBat, newBat)); - } + drawViewport(); + pushAction(new BatteryChangeAction(robot, oldBat, newBat)); }); batteryBox.getChildren().addAll(new Label("Battery:"), batterySpinner); @@ -1577,30 +1575,54 @@ private void showPropertiesFor(MapEntity entity) { if (globalManual) { HBox boxCountBox = new HBox(8); boxCountBox.setAlignment(javafx.geometry.Pos.CENTER_LEFT); + Spinner boxCountSpinner = new Spinner<>( - new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 99, rack.getBoxCount())); + new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 99, rack.getBoxCount())); boxCountSpinner.setPrefWidth(80); boxCountSpinner.setEditable(true); + + // Restrict input to positive integers only (no decimals, no negatives) + boxCountSpinner.getEditor().setTextFormatter(new TextFormatter<>(change -> { + if (change.getControlNewText().matches("\\d*")) { + return change; + } + return null; // Reject non-digit characters + })); + + // Force spinner to commit text value when the user clicks away + boxCountSpinner.getEditor().focusedProperty().addListener((obs, wasFocused, isFocused) -> { + if (!isFocused) { + boxCountSpinner.increment(0); + } + }); + boxCountSpinner.valueProperty().addListener((obs, oldV, newV) -> { - if (newV == null || oldV == null || newV.equals(oldV)) return; + if (newV == null || oldV == null || newV.equals(rack.getBoxCount())) return; + if (guardEditor("change box count")) { - boxCountSpinner.getValueFactory().setValue(oldV); + // Revert spinner if editor is locked + javafx.application.Platform.runLater(() -> boxCountSpinner.getValueFactory().setValue(oldV)); return; } + rack.setBoxCount(newV); persistEditorChanges(); }); - boxCountBox.getChildren().addAll(new Label("Number of boxes:"), boxCountSpinner); + + boxCountBox.getChildren().addAll(new Label("Number of tasks:"), boxCountSpinner); propertiesPanel.getChildren().add(boxCountBox); HBox manualBox = new HBox(8); manualBox.setAlignment(javafx.geometry.Pos.CENTER_LEFT); CheckBox manualCheck = new CheckBox("Manual dropoff assignment"); manualCheck.setSelected(rack.isManualDropoffAssignment()); + VBox dropoffArrayBox = new VBox(4); dropoffArrayBox.setVisible(rack.isManualDropoffAssignment()); dropoffArrayBox.setManaged(rack.isManualDropoffAssignment()); + manualCheck.selectedProperty().addListener((obs, oldV, newV) -> { + if (newV == rack.isManualDropoffAssignment()) return; if (guardEditor("toggle manual assignment")) { manualCheck.setSelected(oldV); return; @@ -1611,6 +1633,7 @@ private void showPropertiesFor(MapEntity entity) { renderRackDropoffArray(rack, dropoffArrayBox); persistEditorChanges(); }); + manualBox.getChildren().add(manualCheck); propertiesPanel.getChildren().add(manualBox); @@ -1945,15 +1968,18 @@ private void onPlay() { } else { generated = com.openrobotics.task.TaskGenerator.generateAutomaticTasks( engine.getMap(), engine.getMaxTasks(), engine.getSeed()); + + // No task assignments could be generated due to invalid map configuration + if (generated.isEmpty()) { + log("\u26a0 No tasks could be generated."); + engine.setSimulationError(SimulationError.INVALID_MAP_CONFIGURATION); + handleSimulationFailure(); + return; + } } if (!generated.isEmpty()) { engine.getDispatcher().addTasks(generated); log("Auto-generated " + generated.size() + " tasks from map racks and delivery stations."); - } else { - log("\u26a0 No tasks could be generated."); - engine.setSimulationError(SimulationError.INVALID_MAP_CONFIGURATION); - handleSimulationFailure(); - return; } } diff --git a/open-robotics/src/main/java/com/openrobotics/map/entities/environment/Rack.java b/open-robotics/src/main/java/com/openrobotics/map/entities/environment/Rack.java index aa7532a..b0612a9 100644 --- a/open-robotics/src/main/java/com/openrobotics/map/entities/environment/Rack.java +++ b/open-robotics/src/main/java/com/openrobotics/map/entities/environment/Rack.java @@ -21,7 +21,7 @@ public class Rack extends MapEntity { */ public Rack(String name, Vector2D position) { super(name, position); - this.boxCount = 1; + this.boxCount = 0; this.validDropoffIds = new ArrayList<>(); } @@ -34,7 +34,7 @@ public Rack(String name, Vector2D position) { */ public Rack(UUID id, String name, Vector2D position) { super(id, name, position); - this.boxCount = 1; + this.boxCount = 0; this.validDropoffIds = new ArrayList<>(); } @@ -47,7 +47,7 @@ public Rack(UUID id, String name, Vector2D position) { */ public void setBoxCount(int boxCount) { // a rack always has at least 1 box; 0 would mean nothing to pick up - this.boxCount = Math.max(1, boxCount); + this.boxCount = Math.max(0, boxCount); } public List getValidDropoffIds() { return validDropoffIds; } diff --git a/open-robotics/src/main/java/com/openrobotics/simulationcore/Dispatcher.java b/open-robotics/src/main/java/com/openrobotics/simulationcore/Dispatcher.java index 27449ab..6bf5522 100644 --- a/open-robotics/src/main/java/com/openrobotics/simulationcore/Dispatcher.java +++ b/open-robotics/src/main/java/com/openrobotics/simulationcore/Dispatcher.java @@ -76,7 +76,6 @@ public int assignTasks(Robot[] robots) { return 0; // there are no tasks available, no assignments are made } - int currentTick = AppState.getEngine().getTickCounter(); // getting the current simulation tick from global app state int assignmentCount = 0; for (Robot robot : robots) { diff --git a/open-robotics/src/main/java/com/openrobotics/simulationcore/SimulationEngine.java b/open-robotics/src/main/java/com/openrobotics/simulationcore/SimulationEngine.java index 82c205d..0bc29f4 100644 --- a/open-robotics/src/main/java/com/openrobotics/simulationcore/SimulationEngine.java +++ b/open-robotics/src/main/java/com/openrobotics/simulationcore/SimulationEngine.java @@ -484,14 +484,27 @@ public boolean tick() { return false; } + // guard against ticking when no tasks were ever added to the dispatcher + if (dispatcher.getTotalTasksAdded() == 0) { + this.running = false; + + // differentiate between no tasks because of manual assignment (user didn't add any) vs. automatic generation (generation failed) + if (manualTaskAssignment) { + simulationError = SimulationError.NO_TASKS_ASSIGNED; + } else { + simulationError = SimulationError.NO_TASKS_GENERATED; + } + + return false; + } + if (tickCounter >= maxTicks) { this.running = false; return false; } - // "No configured tasks" is treated as sandbox mode: ticks still run. - // Only short-circuit when a workload was actually configured and is now finished. - if (dispatcher.getTotalTasksAdded() > 0 && workloadComplete()) { + // Check if workload complete before ticking + if (workloadComplete()) { this.running = false; SimulationRunRecordBuilder recordBuilder = new SimulationRunRecordBuilder(this); diff --git a/open-robotics/src/main/java/com/openrobotics/simulationcore/SimulationError.java b/open-robotics/src/main/java/com/openrobotics/simulationcore/SimulationError.java index cb164d3..7e82501 100644 --- a/open-robotics/src/main/java/com/openrobotics/simulationcore/SimulationError.java +++ b/open-robotics/src/main/java/com/openrobotics/simulationcore/SimulationError.java @@ -5,7 +5,9 @@ public enum SimulationError { NONE("No errors detected. Simulation is running smoothly."), ALL_ROBOTS_DEAD("All robots have depleted their batteries. Simulation cannot continue.\nConsider adding more charging stations to the map."), NO_ROBOTS_SPAWNED("No robots were spawned in the simulation. Simulation cannot run.\nPlease add robots to the simulation"), - INVALID_MAP_CONFIGURATION("The map configuration is invalid. Simulation cannot run.\nEnsure the map has at least one rack and one delivery station."); + INVALID_MAP_CONFIGURATION("The map configuration is invalid. Simulation cannot run.\nEnsure the map has at least one rack and one delivery station."), + NO_TASKS_ASSIGNED("No tasks were added to the simulation. Simulation cannot run.\nPlease manually add tasks to the simulation by configuring racks on the map."), + NO_TASKS_GENERATED("No tasks were generated for the simulation. Simulation cannot continue.\nThere was likely an error with automatic task generation."); private final String message; diff --git a/open-robotics/src/test/java/com/openrobotics/controllers/SimulationControllerTest.java b/open-robotics/src/test/java/com/openrobotics/controllers/SimulationControllerTest.java index a66fd15..b2f12d5 100644 --- a/open-robotics/src/test/java/com/openrobotics/controllers/SimulationControllerTest.java +++ b/open-robotics/src/test/java/com/openrobotics/controllers/SimulationControllerTest.java @@ -336,18 +336,18 @@ void sync_task_positions_updates_pickup_and_dropoff_references() { assertTrue(outliner().getItems().stream().anyMatch(item -> item.contains("(2, 1) → (4, 1)"))); } - /** Verifies single-step frame advances simulation tick/progress/status and logs. */ + /** Verifies single-step frame fails to advance simulation tick/progress/status and logs. */ @Test - void next_frame_with_engine_advances_tick_status_progress_and_console() { + void next_frame_with_engine_does_not_advance_tick_status_progress_and_console() { loadScreenWith(emptyEngine(), null, 30, 30); Label simStatus = installOptionalStatusLabel(); invokeOnFx("onNextFrame", new Class[0]); - assertEquals("STEPPING", simStatus.getText()); - assertEquals("TICK 1", tickLabel().getText()); - assertEquals(0.001, progressBar().getProgress(), 0.0001); - assertTrue(consoleText().contains("Step → TICK 1")); + assertEquals("FAILURE", simStatus.getText()); + assertEquals("TICK 0", tickLabel().getText()); + assertEquals(0.0, progressBar().getProgress(), 0.0001); + assertTrue(consoleText().contains("Step → TICK 0")); } /** Verifies completion path updates status/progress without incrementing tick counter. */ @@ -471,7 +471,7 @@ void sidebar_and_console_toggles_collapse_and_restore_split_panes() { assertEquals(0.83, consoleSplit.getDividerPositions()[0], 0.08); } - /** Verifies restart with valid engine resets run state and rotates run identifier. */ + /** Verifies restart with invalid engine resets run state and rotates run identifier. */ @Test void restart_with_engine_resets_tick_progress_status_and_updates_run_id() { SimulationEngine engine = emptyEngine(); @@ -480,7 +480,7 @@ void restart_with_engine_resets_tick_progress_status_and_updates_run_id() { UUID originalRunId = engine.getRunId(); invokeOnFx("onNextFrame", new Class[0]); - assertEquals("TICK 1", tickLabel().getText()); + assertEquals("TICK 0", tickLabel().getText()); fireButtonByText("↺"); diff --git a/open-robotics/src/test/java/com/openrobotics/integration/simulation/ReservationKIntegrationTest.java b/open-robotics/src/test/java/com/openrobotics/integration/simulation/ReservationKIntegrationTest.java index 161f1b6..9a46d8d 100644 --- a/open-robotics/src/test/java/com/openrobotics/integration/simulation/ReservationKIntegrationTest.java +++ b/open-robotics/src/test/java/com/openrobotics/integration/simulation/ReservationKIntegrationTest.java @@ -1,6 +1,9 @@ package com.openrobotics.integration.simulation; +import com.openrobotics.AppState; import com.openrobotics.integration.SimulationIntegrationTestSupport; +import com.openrobotics.logging.Logger; +import com.openrobotics.logging.LoggerMode; import com.openrobotics.map.Map; import com.openrobotics.robot.Robot; import com.openrobotics.robot.RobotState; @@ -51,13 +54,20 @@ void reservationKMakesTrailingRobotWaitForLeadRobotsWindow() { map.addEntity(leadRobot); map.addEntity(trailingRobot); + Dispatcher dispatcher = new Dispatcher(); + dispatcher.addTask(trailingTask); // adding task so ticking doesn't fail + SimulationEngine engine = new SimulationEngine( map, new Robot[]{leadRobot, trailingRobot}, - new Dispatcher(), + dispatcher, new ReservationKPolicy(2) ); + // Setting global state for logging + AppState.setEngine(engine); + Logger.setMode(LoggerMode.NO_OP); + engine.tick(); assertEquals(pos(2, 0), leadRobot.getPosition()); assertEquals(pos(0, 0), trailingRobot.getPosition()); diff --git a/open-robotics/src/test/java/com/openrobotics/integration/simulation/SimulationIdleLoopIntegrationTest.java b/open-robotics/src/test/java/com/openrobotics/integration/simulation/SimulationIdleLoopIntegrationTest.java deleted file mode 100644 index dbf5fe0..0000000 --- a/open-robotics/src/test/java/com/openrobotics/integration/simulation/SimulationIdleLoopIntegrationTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.openrobotics.integration.simulation; - -import com.openrobotics.integration.SimulationIntegrationTestSupport; -import com.openrobotics.map.Map; -import com.openrobotics.robot.Robot; -import com.openrobotics.robot.RobotState; -import com.openrobotics.simulationcore.CoordinationPolicy; -import com.openrobotics.simulationcore.Dispatcher; -import com.openrobotics.simulationcore.SimulationEngine; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Integration test covering idle-loop behavior when no tasks are configured. - * - *

This suite verifies simulation ticks still advance while robots remain stationary, available, - * and task-free in the absence of dispatcher work.

- */ -public class SimulationIdleLoopIntegrationTest extends SimulationIntegrationTestSupport { - - /** - * Verifies that with an empty dispatcher the robot stays idle at its start position while tick - * counters and idle metrics continue to advance. - */ - @Test - void noConfiguredTasksKeepsSimulationTickingAndRobotIdle() { - Map map = new Map(3, 3); - Robot robot = greedyRobot("IdleBot", 1, 1, 99L); - map.addEntity(robot); - - Dispatcher dispatcher = new Dispatcher(); - SimulationEngine engine = new SimulationEngine( - map, - new Robot[]{robot}, - dispatcher, - CoordinationPolicy.noOp() - ); - - runTicks(engine, 3); - - assertEquals(pos(1, 1), robot.getPosition()); - assertEquals(RobotState.IDLE, robot.getState()); - assertNull(robot.getCurrentTask()); - assertTrue(robot.isAvailable()); - assertEquals(3, robot.getTotalIdleTicks()); - assertEquals(0, dispatcher.getTotalTasksAdded()); - assertEquals(3, engine.getTickCounter()); - } -} diff --git a/open-robotics/src/test/java/com/openrobotics/integration/simulation/TrafficRulesIntegrationTest.java b/open-robotics/src/test/java/com/openrobotics/integration/simulation/TrafficRulesIntegrationTest.java index 2a8e9b5..d61ae5c 100644 --- a/open-robotics/src/test/java/com/openrobotics/integration/simulation/TrafficRulesIntegrationTest.java +++ b/open-robotics/src/test/java/com/openrobotics/integration/simulation/TrafficRulesIntegrationTest.java @@ -97,10 +97,13 @@ void trafficRulesKeepsIntersectionExclusiveAcrossTicks() { map.getTile(2, 1) )); + Dispatcher dispatcher = new Dispatcher(); + dispatcher.addTask(taskA); // adding task so ticking doesn't fail + SimulationEngine engine = new SimulationEngine( map, new Robot[]{robotA, robotB}, - new Dispatcher(), + dispatcher, policy ); diff --git a/open-robotics/src/test/java/com/openrobotics/map/entities/environment/RackTest.java b/open-robotics/src/test/java/com/openrobotics/map/entities/environment/RackTest.java index a26730d..77c4979 100644 --- a/open-robotics/src/test/java/com/openrobotics/map/entities/environment/RackTest.java +++ b/open-robotics/src/test/java/com/openrobotics/map/entities/environment/RackTest.java @@ -53,14 +53,14 @@ void togglingManualOffPreservesValidDropoffIds() { } /** - * Verifies box count setter clamps invalid low values to minimum supported count of one. + * Verifies box count setter clamps invalid low values to minimum supported count of zero. */ @Test - void boxCountSetterEnforcesMinimumOfOne() { + void boxCountSetterEnforcesMinimumOfZero() { Rack rack = new Rack("r1", new Vector2D(0, 0)); rack.setBoxCount(0); - assertEquals(1, rack.getBoxCount()); + assertEquals(0, rack.getBoxCount()); rack.setBoxCount(-5); - assertEquals(1, rack.getBoxCount()); + assertEquals(0, rack.getBoxCount()); } } diff --git a/open-robotics/src/test/java/com/openrobotics/simulationcore/ReservationKPolicyTest.java b/open-robotics/src/test/java/com/openrobotics/simulationcore/ReservationKPolicyTest.java index f544951..f5d869d 100644 --- a/open-robotics/src/test/java/com/openrobotics/simulationcore/ReservationKPolicyTest.java +++ b/open-robotics/src/test/java/com/openrobotics/simulationcore/ReservationKPolicyTest.java @@ -1,5 +1,8 @@ package com.openrobotics.simulationcore; +import com.openrobotics.AppState; +import com.openrobotics.logging.Logger; +import com.openrobotics.logging.LoggerMode; import com.openrobotics.map.Map; import com.openrobotics.map.Tile; import com.openrobotics.map.Vector2D; @@ -34,6 +37,13 @@ public void setUp() { policy = new ReservationKPolicy(1); } + /** + * Creates a position vector with integer tile coordinates. + */ + protected Vector2D pos(int x, int y) { + return new Vector2D(x, y); + } + /** * Creates a robot fixture at origin with a generated UUID. */ @@ -567,13 +577,21 @@ void simulationEngineAppliesReservationPolicyBeforeCollisionResolution() { map.addEntity(robotA); map.addEntity(robotB); + // Creating dispatcher and loading it with a dummy task + Dispatcher dispatcher = new Dispatcher(); + Task dummyTask = new Task(1, pos(0, 1), pos(3, 1), 1); + dispatcher.addTask(dummyTask); + SimulationEngine engine = new SimulationEngine( map, new Robot[] { robotA, robotB }, - new Dispatcher(), + dispatcher, new ReservationKPolicy(3) ); + AppState.setEngine(engine); + Logger.setMode(LoggerMode.NO_OP); + engine.tick(); // Without ReservationK being applied in the engine, robot B would move to 3,0. diff --git a/open-robotics/src/test/java/com/openrobotics/simulationcore/SimulationEngineTest.java b/open-robotics/src/test/java/com/openrobotics/simulationcore/SimulationEngineTest.java index e7f8fa3..b2b1d98 100644 --- a/open-robotics/src/test/java/com/openrobotics/simulationcore/SimulationEngineTest.java +++ b/open-robotics/src/test/java/com/openrobotics/simulationcore/SimulationEngineTest.java @@ -232,20 +232,21 @@ public void testTickIncrementsCounter() { } /** - * With no configured tasks, the engine treats this as sandbox mode: - * {@code tick()} still advances time and leaves {@code running = false}. - * At least one robot is required for tick() to proceed past the no-robots guard. + * When no tasks have ever been added to the dispatcher, {@code tick()} exits + * early without advancing the counter, sets {@code running} to {@code false}, + * and records a {@link SimulationError#NO_TASKS_GENERATED} error. + * At least one robot is required to reach the no-tasks guard. */ @Test - public void testTickStopsWhenWorkloadComplete() { + public void testTickStopsWhenNoTasksAdded() { Robot r = makeRobot("R1", 5, 5); SimulationEngine eng = buildEngine(new Robot[]{ r }); - int before = eng.getTickCounter(); eng.tick(); - assertEquals(before + 1, eng.getTickCounter(), - "tick() should increment in no-workload sandbox mode"); + assertEquals(0, eng.getTickCounter(), + "tick() should not increment when no tasks have been added"); assertFalse(eng.getIsRunning()); + assertEquals(SimulationError.NO_TASKS_GENERATED, eng.getSimulationError()); } /** @@ -277,6 +278,8 @@ public void testTickBlocksMoveIntoDeadRobotTile() { */ @Test public void testTickBlocksSameDirectionFollowThrough() { + dispatcher.addTask(makeTask(1, 0, 0, 9, 9)); + ScriptedRobot leader = new ScriptedRobot("Leader", new Vector2D(1, 0)); leader.setState(RobotState.MOVING); leader.setNextMove(new MoveIntention(map.getTile(1, 0), map.getTile(2, 0), leader)); @@ -302,6 +305,8 @@ public void testTickBlocksSameDirectionFollowThrough() { */ @Test public void testTickAllowsOverlapOnDeliveryStation() { + dispatcher.addTask(makeTask(1, 0, 0, 9, 9)); + map.addEntity(new DeliveryStation("Delivery", new Vector2D(1, 0))); ScriptedRobot staying = new ScriptedRobot("Staying", new Vector2D(1, 0)); @@ -329,6 +334,8 @@ public void testTickAllowsOverlapOnDeliveryStation() { */ @Test public void testTickAllowsOverlapOnChargingStation() { + dispatcher.addTask(makeTask(1, 0, 0, 9, 9)); + map.addEntity(new ChargingStation("Charging", new Vector2D(1, 0))); ScriptedRobot staying = new ScriptedRobot("Staying", new Vector2D(1, 0)); @@ -544,6 +551,8 @@ public void testEngineHaltsNaturallyAfterSingleTaskCompletion() { */ @Test public void testTickDoesNotCommitIllegalMoveIntentions() { + dispatcher.addTask(makeTask(1, 0, 0, 9, 9)); + ScriptedRobot robot = new ScriptedRobot("Scripted", new Vector2D(0, 0)); robot.setNextMove(new MoveIntention(map.getTile(0, 0), map.getTile(2, 0), robot));