MISSION-STORY-001 — Multi-Rover Mission
Story
As an operator,
I want to deploy multiple rovers in a single mission,
so that I can navigate all of them with one invocation and receive all their final positions.
Architecture Reference: 05-building-blocks.md — MissionController application layer; 01-introduction.md — FR-4; 02-constraints.md — DC-4
Scenarios
SCENARIO 1: Each rover processes its own commands independently
Scenario ID: MISSION-STORY-001-S1
GIVEN
WHEN
THEN
SCENARIO 2: Rover 2 is unaffected by Rover 1’s final state
Scenario ID: MISSION-STORY-001-S2
GIVEN
WHEN
THEN
SCENARIO 3: Output contains one line per rover in deployment order
Scenario ID: MISSION-STORY-001-S3
GIVEN
WHEN
THEN
SCENARIO 4: Full kata two-rover example
Scenario ID: MISSION-STORY-001-S4
GIVEN
Plateau 5 5, Rover 1 at (1, 2, N) with commands LMLMLMLMM, Rover 2 at (3, 3, E) with commands MMRMMRMRRM
WHEN
THEN
Backend Sub-Story
Story ID: MISSION-BE-001.1
As a developer I want a MissionController that orchestrates sequential rover execution so that the application layer coordinates missions without knowing domain internals.
Architecture Reference: 05-building-blocks.md — MissionController; 04-solution-strategy.md — Hexagonal architecture; 02-constraints.md — DC-4 sequential processing
Scenarios:
SCENARIO 1: MissionController does not import from adapters
Scenario ID: MISSION-BE-001.1-S1
GIVEN
WHEN
THEN
Application layer — mars_rover/application/mission_controller.py
from mars_rover.domain.rover import Rover
from mars_rover.domain.commands import TurnLeft, TurnRight, MoveForward
from mars_rover.domain.plateau import Plateau
from typing import Sequence
class MissionController:
def __init__(self, plateau: Plateau) -> None:
self._plateau = plateau
def run(self, missions: Sequence[tuple[Rover, str]]) -> list[Rover]:
command_map = {
"L": TurnLeft(),
"R": TurnRight(),
"M": MoveForward(self._plateau),
}
for rover, command_string in missions:
for ch in command_string:
rover.execute(command_map[ch])
return [rover for rover, _ in missions]
Design notes:
MissionController owns the command-string → command-object mapping; InputParser (CLI-STORY-001) produces raw strings, keeping parsing out of the application layer
MoveForward is instantiated once per plateau and reused across all rovers — safe because it holds no per-rover state
Sequential processing is explicit and documented (DC-4); no thread safety required
Note: The return type list[Rover] is the initial implementation. NAV-STORY-003 (obstacle detection) changes it to list[tuple[Rover, bool]] to carry the obstacle-stopped flag. Implement this version first, then extend in NAV-STORY-003.
Unit tests — tests/application/test_mission_controller.py
from mars_rover.application.mission_controller import MissionController
from mars_rover.domain.heading import Heading
from mars_rover.domain.plateau import Plateau
from mars_rover.domain.rover import Rover
def test_single_rover_kata_example_1():
plateau = Plateau(5, 5)
rover = Rover(1, 2, Heading.N)
controller = MissionController(plateau)
results = controller.run([(rover, "LMLMLMLMM")])
assert results[0].x == 1 and results[0].y == 3 and results[0].heading == Heading.N
def test_single_rover_kata_example_2():
plateau = Plateau(5, 5)
rover = Rover(3, 3, Heading.E)
controller = MissionController(plateau)
results = controller.run([(rover, "MMRMMRMRRM")])
assert results[0].x == 5 and results[0].y == 1 and results[0].heading == Heading.E
def test_full_kata_two_rovers():
plateau = Plateau(5, 5)
missions = [
(Rover(1, 2, Heading.N), "LMLMLMLMM"),
(Rover(3, 3, Heading.E), "MMRMMRMRRM"),
]
controller = MissionController(plateau)
results = controller.run(missions)
assert results[0].x == 1 and results[0].y == 3 and results[0].heading == Heading.N
assert results[1].x == 5 and results[1].y == 1 and results[1].heading == Heading.E
def test_rovers_are_independent():
plateau = Plateau(5, 5)
rover1 = Rover(5, 5, Heading.N)
rover2 = Rover(0, 0, Heading.E)
controller = MissionController(plateau)
results = controller.run([(rover1, ""), (rover2, "M")])
assert results[1].x == 1 and results[1].y == 0
def test_empty_command_string_leaves_rover_unchanged():
plateau = Plateau(5, 5)
rover = Rover(2, 3, Heading.W)
controller = MissionController(plateau)
results = controller.run([(rover, "")])
assert results[0].x == 2 and results[0].y == 3 and results[0].heading == Heading.W
Frontend Sub-Story
Story ID: MISSION-FE-001.1
As an operator I want to provide multiple rover blocks in a single stdin input so that I can run a full multi-rover mission with one command.
Architecture Reference: 03-context.md — Operator → System interface
Scenarios:
Infrastructure Sub-Story
Story ID: MISSION-INFRA-001.1
As a developer I want multi-rover mission functionality to be containerized and testable so that parallel rover processing works consistently across environments.
Architecture Reference: 07-deployment.md — deployment topology; 04-solution-strategy.md — Hexagonal architecture
SCENARIO 1: Container processes multiple rovers sequentially and outputs results in order
Scenario ID: MISSION-INFRA-001.1-S1
GIVEN
WHEN
THEN
Each rover is processed sequentially using the mission controller
Final positions are output in deployment order
All rovers complete their missions independently
Container exits with code 0 on successful completion
SCENARIO 2: Container handles rover failures gracefully in multi-rover missions
Scenario ID: MISSION-INFRA-001.1-S2
GIVEN
WHEN
THEN
Failed rover is handled appropriately (obstacle prefix, error logging)
Other rovers continue processing normally
Mission completes with all rover results
Container exits with appropriate status code
SCENARIO 3: Dockerfile builds with mission controller dependencies
Scenario ID: MISSION-INFRA-001.1-S3
GIVEN
WHEN
THEN
The build includes mission controller code and dependencies
Multi-rover processing logic is available in the container
The container can coordinate multiple rover missions
Build completes without errors
SCENARIO 4: Test suite validates multi-rover functionality inside container
Scenario ID: MISSION-INFRA-001.1-S4
GIVEN
WHEN
THEN
All multi-rover mission tests run inside the container
Tests validate rover independence and sequential processing
pytest discovers and executes all mission-related tests
Container exits with code 0 on test success