A complete simulation bundles everything together. Building from parts separates it into three pieces, so the same task can run in a different world, or the same world can host a different robot, without copying code. A recipe can hold one robot, or several robots with a seat for each pilot. Composition happens inside one framework, using that framework’s own way of building scenes. It doesn’t convert parts between engines.

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)
Or start one from the command line:
Then prepare, preview and publish as usual.

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:
Ballot won’t invent a button, and it won’t loosen the task’s rules to get a pass.

Publishing a part

Each part lives in its own folder with its own ballot.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 provide tasks or utils.
  • Keep assets inside the package and load them relative to your code.
  • Declare dependencies in pyproject.toml as usual.
All the parts in a recipe run in one Python environment. If two parts need different versions of the same library, preparation names both parts and their requirements, and suggests compatible versions.

Sharing parts

Publishing a world, robot or task for other developers, with credit to its author, is planned. Parts already record their author and version, so credit travels with every episode made from them.