What each part declares
- A world marks the places a task might use. Each one is a port with a type, pointing at something real in the model.
- A robot points at a robot the framework already knows, with its gripper and a working controller.
- A task is ordinary Python packaged with its own props. It asks for roles, such as “somewhere to start” and “a destination”, instead of naming places in one particular world.
A port is a promise about a real place in the model, not a label on a bounding box.
The recipe connects them
ballot.toml (recipe)
Several robots in one recipe
A recipe can place more than one robot, each as a seat with its own spawn port. The task then says which seats do each step. That’s how co-op scenarios are built: see Create a scenario.ballot.toml (recipe with seats)
Swapping a part
Each swap is prepared and checked again, because a combination can fail even when every part works on its own. A robot might not reach the destination, or a gripper might not fit the object.
When a combination can’t work
Ballot refuses a combination that’s missing something real, before any pilot joins. For example, a pressing task in a world without a button:Publishing a part
Each part lives in its own folder with its ownballot.toml, and goes through the same prepare step as a complete simulation:
--visibility project makes the part available to recipes in your Ballot project. Publishing checks that the part is complete and consistent. It can’t promise the part works with every other part, which is why each recipe is checked on its own.
Packaging parts
Reusable parts are ordinary Python packages:- Give each package a distinctive import name, such as
alice_workcell, so two parts never both providetasksorutils. - Keep assets inside the package and load them relative to your code.
- Declare dependencies in
pyproject.tomlas usual.