From 469d201c8eba490c84bbee03c8035a11cfe61096 Mon Sep 17 00:00:00 2001 From: mfsohail12 <149211499+mfsohail12@users.noreply.github.com> Date: Thu, 16 Apr 2026 14:49:08 -0400 Subject: [PATCH 1/7] fixed preset maps auto generating 10 tasks even if on manual task assignment mode --- .../java/com/openrobotics/controllers/SetupController.java | 5 +++-- .../com/openrobotics/map/entities/environment/Rack.java | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) 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 2920358..85de1e1 100644 --- a/open-robotics/src/main/java/com/openrobotics/controllers/SetupController.java +++ b/open-robotics/src/main/java/com/openrobotics/controllers/SetupController.java @@ -785,8 +785,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/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; } From 180f79bd1ec83617032a437e31cb89b4ff813e92 Mon Sep 17 00:00:00 2001 From: mfsohail12 <149211499+mfsohail12@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:19:31 -0400 Subject: [PATCH 2/7] Updated manual task assignment to configure every rack to have 0 tasks assigned by default. Updated sim engine error handling to consider cases of no task generation due to no manual assignment or failed automatic task generation. --- .../controllers/SimulationController.java | 17 ++++++++++------- .../simulationcore/SimulationEngine.java | 19 ++++++++++++++++--- .../simulationcore/SimulationError.java | 4 +++- 3 files changed, 29 insertions(+), 11 deletions(-) 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 d2fbe49..0864831 100644 --- a/open-robotics/src/main/java/com/openrobotics/controllers/SimulationController.java +++ b/open-robotics/src/main/java/com/openrobotics/controllers/SimulationController.java @@ -1554,7 +1554,7 @@ private void showPropertiesFor(MapEntity entity) { 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); boxCountSpinner.valueProperty().addListener((obs, oldV, newV) -> { @@ -1566,7 +1566,7 @@ private void showPropertiesFor(MapEntity entity) { 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); @@ -1925,15 +1925,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/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; From a1c3c775e08d57733b3c59db33e99e5351f2770f Mon Sep 17 00:00:00 2001 From: mfsohail12 <149211499+mfsohail12@users.noreply.github.com> Date: Thu, 16 Apr 2026 16:05:26 -0400 Subject: [PATCH 3/7] fixed event listeners in SimulationController.showPropertiesFor() looping on unchanged values --- .../controllers/SimulationController.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) 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 0864831..3580ae3 100644 --- a/open-robotics/src/main/java/com/openrobotics/controllers/SimulationController.java +++ b/open-robotics/src/main/java/com/openrobotics/controllers/SimulationController.java @@ -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; @@ -1527,15 +1527,15 @@ private void showPropertiesFor(MapEntity entity) { batterySpinner.setPrefWidth(90); batterySpinner.setEditable(true); batterySpinner.valueProperty().addListener((obs, oldVal, newVal) -> { - if (newVal != null && oldVal != null && !newVal.equals(oldVal)) { - if (guardEditor("change battery")) { - batterySpinner.getValueFactory().setValue(oldVal); - return; - } - robot.setBattery(newVal.floatValue()); - drawViewport(); - pushAction(new BatteryChangeAction(robot, oldVal.floatValue(), newVal.floatValue())); + if (newVal == null || oldVal == null || newVal.floatValue() == robot.getBattery()) return; + if (guardEditor("change battery")) { + batterySpinner.getValueFactory().setValue(oldVal); + return; } + robot.setBattery(newVal.floatValue()); + drawViewport(); + pushAction(new BatteryChangeAction(robot, oldVal.floatValue(), newVal.floatValue())); + }); batteryBox.getChildren().addAll(new Label("Battery:"), batterySpinner); propertiesPanel.getChildren().add(batteryBox); @@ -1558,7 +1558,7 @@ private void showPropertiesFor(MapEntity entity) { boxCountSpinner.setPrefWidth(80); boxCountSpinner.setEditable(true); boxCountSpinner.valueProperty().addListener((obs, oldV, newV) -> { - if (newV == null || oldV == null || newV.equals(oldV)) return; + if (newV == null || oldV == null || newV == rack.getBoxCount()) return; if (guardEditor("change box count")) { boxCountSpinner.getValueFactory().setValue(oldV); return; @@ -1577,6 +1577,7 @@ private void showPropertiesFor(MapEntity entity) { 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; From f98ff8096501e44593a255d4f59bdde4fb0ac4a8 Mon Sep 17 00:00:00 2001 From: mfsohail12 <149211499+mfsohail12@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:22:14 -0400 Subject: [PATCH 4/7] fixed some tests --- .../simulationcore/Dispatcher.java | 1 - .../controllers/SimulationControllerTest.java | 16 +++--- .../ReservationKIntegrationTest.java | 12 ++++- .../SimulationIdleLoopIntegrationTest.java | 52 ------------------- .../TrafficRulesIntegrationTest.java | 5 +- .../map/entities/environment/RackTest.java | 8 +-- .../ReservationKPolicyTest.java | 20 ++++++- 7 files changed, 46 insertions(+), 68 deletions(-) delete mode 100644 open-robotics/src/test/java/com/openrobotics/integration/simulation/SimulationIdleLoopIntegrationTest.java 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/test/java/com/openrobotics/controllers/SimulationControllerTest.java b/open-robotics/src/test/java/com/openrobotics/controllers/SimulationControllerTest.java index 61846de..7fd3477 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. From 8cc53bfd625158f209bc3334ca9e7808b738a421 Mon Sep 17 00:00:00 2001 From: mfsohail12 <149211499+mfsohail12@users.noreply.github.com> Date: Thu, 16 Apr 2026 19:19:18 -0400 Subject: [PATCH 5/7] fixed sim engine tests --- .../simulationcore/SimulationEngineTest.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) 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)); From 27f54292cc6dd7361d60bf368daf0cb0fbfc227c Mon Sep 17 00:00:00 2001 From: DanIsOk Date: Thu, 16 Apr 2026 19:45:03 -0400 Subject: [PATCH 6/7] fix data validation on rack amount of tasks --- .../controllers/SimulationController.java | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) 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 d79660f..52b9390 100644 --- a/open-robotics/src/main/java/com/openrobotics/controllers/SimulationController.java +++ b/open-robotics/src/main/java/com/openrobotics/controllers/SimulationController.java @@ -1551,7 +1551,7 @@ private void showPropertiesFor(MapEntity entity) { Platform.runLater(() -> batterySpinner.getValueFactory().setValue(oldVal)); return; } -float newBat = newVal.floatValue(); + float newBat = newVal.floatValue(); float oldBat = oldVal.floatValue(); robot.setBattery(newBat); @@ -1575,19 +1575,40 @@ 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(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 == rack.getBoxCount()) 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 tasks:"), boxCountSpinner); propertiesPanel.getChildren().add(boxCountBox); @@ -1595,9 +1616,11 @@ private void showPropertiesFor(MapEntity entity) { 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")) { @@ -1610,6 +1633,7 @@ private void showPropertiesFor(MapEntity entity) { renderRackDropoffArray(rack, dropoffArrayBox); persistEditorChanges(); }); + manualBox.getChildren().add(manualCheck); propertiesPanel.getChildren().add(manualBox); From 389f5e06027bd9b0aaec34d85249ac4a36a244f6 Mon Sep 17 00:00:00 2001 From: mfsohail12 <149211499+mfsohail12@users.noreply.github.com> Date: Thu, 16 Apr 2026 19:49:49 -0400 Subject: [PATCH 7/7] more meaningful dropoff assignment cancellation message --- .../java/com/openrobotics/controllers/SimulationController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 52b9390..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; }