Skip to content

Worlds

Each world is a directory under arena_simulation_setup/worlds/<name>/. The directory name is the canonical identifier consumed by WorldIdentifier.

Directory layout

Per-level world, one subdirectory per level named by level id. Every shipped world uses this layout, including single-level worlds (a lone 0/):

worlds/<name>/
├── <level_id>/          # one per level, e.g. 0/, 1/, 2/
│   ├── world.yaml       # LevelDescription for this level (zones:/microphones:)
│   ├── map.png          # per-level occupancy map
│   └── map.yaml
├── scenarios/
│   └── <scenario>/
│       ├── scenario.yaml     # Scenario: static/dynamic obstacles, robot goals
│       ├── scenario.json     # alternative; ScenarioView tries .yaml first
│       └── <agent_type>.yaml # optional scenario-local arena_humansim agent types
└── assets/
    └── <domain>/
        └── <asset_type>/
            └── <name>/       # world-local asset; overrides network-fetched assets

World.load() picks a mode by what's on disk: any subdirectory holding a world.yaml (other than scenarios/, assets/, map/) is treated as a level, named by that directory, and the root world.yaml is ignored. touch_world --all normalizes whatever is on disk back into this canonical per-level-subdirectory form.

Legacy single-file world (fallback)

With no level subdirectories, World.load() falls back to a root world.yaml, either a single-level zones: file (synthesized as level '0') or a multi-level file with a top-level levels: {<level_id>: {zones:, microphones:}} dict:

worlds/<name>/
├── world.yaml          # WorldDescription: zones, walls, doors, entities
├── map/
│   ├── map.png         # binary occupancy map (regenerated by touch_world)
│   ├── map.yaml        # ROS map-server metadata (resolution, origin)
│   └── map.world.yaml  # optional: Gazebo world file
├── scenarios/
└── assets/

No world shipped under arena_simulation_setup/worlds/ uses this layout.

world.yaml schema

Top-level key is zones, a list of zone objects. Each zone:

zones:
- name: central_hallway          # required; used as floor name
  description: Main Central Hallway
  material:                      # MaterialIdentifier for the floor
  - Concrete_Smooth              #   string form: [domain/]name
  - {}                           #   or [name, modifiers_dict] tuple form
  corners:                       # polygon corners of the zone floor
  - {x: 8.0, y: 0.0, z: 0.0}
  walls:                         # list of Wall entries
  - start: {x: 8.0, y: 0.0, z: 0.0}
    end:   {x: 9.25, y: 0.0, z: 0.0}
    material:                    # optional: inline material (when kind is absent)
      name: Plaster_Wall
      domain: Common
      _modifiers: null
  - start: {x: 0.0, y: 25.1, z: 0.0}
    end:   {x: 8.0, y: 25.1, z: 0.0}
    kind: patient_room_walls     # optional: WallIdentifier name; overrides material
  doors:
  - name: main_hallway_entrance
    start: {x: 9.25, y: 0.0, z: 0.0}
    end:   {x: 10.75, y: 0.0, z: 0.0}
    kind: sliding
    width: 0.1
    height: 2.0
    material:
    - Aluminum_Anodized
    - {}
  elevators: []
  schedules: []                  # list of Schedule (M2 standalone), see AUTHORING.md
  signals: []                    # list of Signal (M2 standalone), see AUTHORING.md
  sounds: []                     # list of Sound (M2 standalone), see AUTHORING.md
  lights: []                     # list of local Light fixtures, see AUTHORING.md#lights
  entities:
    static: []                   # list of Obstacle, same schema as scenario static:
    dynamic: []                  # list of DynamicObstacle, same schema as scenario dynamic:
  ceiling: true                  # true by default, false to leave the zone open
  ceiling_height: 2.0            # top height (m), derived from wall stack when absent
  ceiling_cast_shadows: false    # false by default, true to occlude light
  ceiling_lights: null           # ceiling rig {fixture, spacing, lumens, ...}, see AUTHORING.md#lights
  ceiling_material:              # MaterialIdentifier, defaults to Concrete_Smooth
  - Concrete_Smooth
  - {}
  wall_material:                 # MaterialIdentifier for auto-generated occupancy walls
  - Concrete_Smooth               #   (walls the map builder infers, not the walls: list)
  - {}

Field aliases: material/mat, ceiling_material/ceiling_mat, wall_material/wall_mat. schedules:, signals:, sounds:, lights:, ceiling_lights: and the semantics preset: mechanism (gate, pressure_plate, occupancy_cap, signal, schedule, sound, light) are documented in AUTHORING.md. The scenario static:/dynamic: schema referenced above is in scenarios/<name>/ schema.

A Wall entry with no kind field generates a plain wall segment using the material field (or the wall default material). A Wall with kind: <name> is resolved via WallIdentifier(name) at simulation time, see tree/Wall.py and shared/walls.py.

Door fields

key default meaning
name required unique within the world
start, end required opening endpoints
kind sliding sliding, sliding_top, teleport implemented. hinged is accepted but falls back to teleport at runtime with a warning
width 0.1 metres
height 2.0 metres
material door default MaterialIdentifier
activation_distance 3.0 scalar or [a, b], stored as a 2-tuple, only max() of the two drives the trigger radius
transition_time 1.0 seconds to fully open/close
hold_time 2.0 seconds held open after trigger clears
semantics [] attaches a scripted kind, see AUTHORING.md

Elevator fields

key default meaning
name required unique across every level in the world
position required cabin center
size [2.0, 2.0, 2.0] cabin [x, y, z]
door_side +x cabin face the door opens on, auto-inferred from the nearest wall (<=1 m) at startup when left at the default
material elevator default MaterialIdentifier
destination "" comma-separated <level_id>.<elevator_name> targets
activation_distance 3.0 metres
transition_time 1.0 seconds
hold_time 2.0 seconds
travel_time 3.0 seconds between levels
accept_outside_calls true whether hallway buttons can summon the cabin
recall_on null regime name. While asserted, refuses calls and holds the cabin door open
semantics [] attaches a scripted kind, see AUTHORING.md

microphones

A sibling of zones: at the level root, a list of audio listeners for the acoustics pipeline:

zones: [...]
microphones:
- zone: lobby       # required, must reference a zone in this level
  placement: ceiling # required, free-form. "ceiling" is validated against the zone's ceiling
  position: {x: 4.0, y: 2.0, z: 2.4}
  frame: map          # default map. with map, position must fall inside the zone polygon
  index: 1             # default 1, disambiguates duplicates, must be >= 1

(zone, placement, index) forms the listener id and must be unique across the whole world. See WorldMicrophone in tree/World/World.py.

lights

A sibling of zones: at the level root, the ambient lights of the level (fixture: dome or sun, with lux). Zone-local fixtures go in a zone's lights: instead. See AUTHORING.md.

Semantic annotations

Door and elevator state is intrinsic: every spawned door and elevator publishes its full state/predicate vocabulary (the kind's DISCRETE/CONTINUOUS/PREDICATES in arena_runtime _semantics.py) with no annotation required. An Elevator's fire-recall regime is a first-class recall_on: <regime> field, not a semantics annotation:

elevators:
- name: 1_elevator
  position: {x: 5.0, y: 0.0, z: 0.0}
  destination: "2.2_elevator"
  recall_on: alarm

An optional semantics: list can still be added to any doors: entry, elevators: entry, or to a zone itself (LevelDescription.Zone). On a door or elevator entry it attaches a scripted kind (gate, pressure_plate), not door/elevator state. Each item is either a preset or a primitive, parsed into a SemanticCfg (shared/semantics.py):

doors:
- name: north_fire_door
  start: {x: 4.0, y: 1.55, z: 0.0}
  end:   {x: 4.0, y: 2.45, z: 0.0}
  semantics:
  - {preset: gate, params: {authorized: [], unlock_on: alarm}}
zones:
- name: lobby
  corners: [...]
  semantics:
  - state: max_speed
    value: 1.5
  - predicate: quiet
    value: true
  - predicate: restricted
    value: false

A preset (e.g. {preset: gate}) expands to the fixed set of states and predicates that kind's runtime handler tracks. Any other keys on the preset item (e.g. distance) apply as overrides to every expanded primitive, except value, which is rejected on a multi-primitive preset: a params: key named after one of the expanded primitives becomes that primitive's value instead. Expansion happens at parse time, so SemanticCfgs never retain the preset name. Annotating a door/elevator entry with a field from that mechanism's own published vocabulary (e.g. {state: progress} on a door) is rejected at world-load time: remove the annotation, the field publishes on its own.

A primitive is {state: <name>, ...} or {predicate: <name>, ...}, with optional keys:

key default meaning
value null literal for zone annotations; omitted/null means runtime-derived for doors/elevators
binding kinematic joint is reserved and raises a parse error in v1
trigger proximity contact is reserved and raises a parse error in v1
distance -1.0 proximity radius in metres; < 0 inherits the entity's activation_distance

Zone semantics have a seed vocabulary (max_speed continuous state, quiet/restricted predicates) that is inert in v1: parsed and published, with no runtime behavior. Author-defined state/predicate names beyond the seed are accepted verbatim.

Door/Elevator omit an empty semantics: list on serialize (opt-in, consistent with their other omit-if-default fields). Zone is plain attrs and always emits semantics: [] when empty.

Presets with no door/elevator geometry of their own (signal, schedule, sound) instead attach to a standalone signals:/schedules:/sounds: entry, a zone-level sibling list to doors:/elevators:. occupancy_cap attaches directly to a zones: entry.

Annotated reference worlds

hospital_1 and reception carry zone semantics: annotations and are the reference for authoring new ones: throughput corridors/lobbies get max_speed, wards/waiting/rest rooms get quiet, staff-only or hazard rooms get restricted. Their doors carry no annotation: door state publishes on its own.

World Zones
hospital_1 max_speed: central_hallway, reception, sub_hallway. quiet: patient_ward, nurse_resting_room, waiting_area, doctors_resting_room. restricted: exam_room_1, pharmacy, operating_room, laboratory
reception restricted: destination (behind the counter). quiet: entrance (waiting chairs)

map/ directory

A preview map.png + map.yaml can be rendered with touch_world (see scripts/touch_world) for inspection. These are not required at runtime: the runtime renders its own map from world.yaml in-process, so editing world.yaml is enough. Use touch_world --all to also regenerate the canonical per-level world.yaml.

map.world.yaml is a Gazebo world file and is not produced by touch_world, it is hand-authored or produced by the generate_world pipeline.

scenarios/<name>/ schema

Resolved by World.scenario property which returns a per-world ScenarioIdentifier class (tree/World/World.py:1003). ScenarioView.load() tries scenario.yaml first, then scenario.json.

Current (structured) format:

static:
  - name: desk_1
    model: office_desk             # ObjectIdentifier
    pose: [x, y, yaw]
dynamic:
  - name: agent_1
    model: female_adult_medical_01 # PedestrianIdentifier
    agent: {agent_type: adult, desired_velocity: 1.4}
    pose: [4.0, 2.0, 0.0]          # yaw in radians
    waypoints:
    - [4.0, 2.0, 0.0]
robots:
  - start: [x, y, yaw]           # required. [x,y], [x,y,yaw], {x,y,yaw} or a zone name
    start_floor: ""              # optional, multi-level worlds
    goal_floor: ""               # optional, floor for goto phases
    phases:                      # ordered. each entry is one of:
      - {goto: [x, y, yaw]}      #   navigate to pose (same formats as start)
      - {goto: kitchen}          #   navigate into a zone, door, elevator or to a pedestrian by name
      - {gesture: wave}          #   arm gesture, "random" allowed, optional instance: <mount>
      - {reach: stow}            #   arm reach: named target, "random", or a pose (optional frame:)
      - {until: "door_1.open == true"}  # hold where the robot is until the atom holds
    conditions: []               # optional, clauses judged over this robot's whole phase list

Every phase also takes until: "<atom>" (completes once the atom holds, after arrival for a goto), hold_time: <s> (park time at a goto goal), conditions: (clauses judged over this phase only), text: (instruction for language-conditioned planners) and on_failure: continue | stop_task | abort_episode. A pose given as a bare string samples a point inside that zone with identity orientation, and a named goto is judged met by zone membership rather than by distance to the sampled point. goal: [x, y, yaw] is deprecated and rewritten to a single goto phase with a warning. Which robot model and adapter runs is not part of the scenario, see arena_robots setup. Schema: tree/World/Scenario.py.

Legacy format (deprecated) uses obstacles.static and obstacles.dynamic keys instead.

The agent: block configures the arena_humansim backend. agent_type is either a built-in type (adult, elder) or a scenario-local agent-type YAML file referenced by relative path (agent_type: ./doctor.yaml) for scripted behavior (sequences, interactions, needs) — see humansim/arena_humansim/config/agent_types/README.md and the hospital_1/scenarios/showcase scenario for a worked example.

static (Obstacle) also takes light: {lit, light_on, level} for the lights its object annotation carries (see AUTHORING.md), scale: [x, y, z] (default identity) and level_id (multi-level worlds). dynamic (DynamicObstacle) also takes velocity (m/s, default 1.0) and level_id. pos: is a deprecated alias for pose: on both, rewritten on parse. Any other key on a static/dynamic entry is kept verbatim in extra and forwarded to the sim backend rather than validated by the scenario parser, e.g. a static entry's type, capacity, interaction_radius, formation, satisfies become its humansim WorldObject info.

sounds is a list of Sound entries, same schema as a world zone's sounds: but episode-scoped (attached at reset, gone at the next one).

timeline is a list of scripted set actions fired on at/every/when triggers. See AUTHORING.md, Scenario timelines.

regions is a mapping of name to source/sink spawner consumed by the arena_humansim backend:

regions:
  entrance_source:
    type: source                  # source | sink
    polygon:                      # CCW corners {x,y[,z]}
      - {x: 9.25, y: 0.2}
      - {x: 10.75, y: 0.2}
      - {x: 10.75, y: 1.2}
      - {x: 9.25, y: 1.2}
    config:
      rate_profile:                # source only: list of {t, rate} keyframes
        - {t: 0.0, rate: 1.5}
      max_concurrent: 5             # source only, default -1 (unbounded)
      max_total: 50                 # source only, default -1 (unbounded)
      agent:                        # source only, spawn template
        agent_type: adult             # built-in name or ./relative.yaml
        desired_velocity: {min: 1.0, max: 1.5}  # or a scalar
        agent_radius: 0.35
        behavior_tree: default
        sink_affinity:
          - {sink: reception_sink, weight: 1.0}

  reception_sink:
    type: sink
    ref: lobby                    # instead of polygon:, takes the named zone's corners
    config:
      absorption_radius: 0.5        # sink only, default 0.5
      capacity: -1                  # sink only, default -1 (unbounded)

An unrecognized type is rejected when the region is spawned.

conditions is a list of episode success/failure clauses:

conditions:
- {op: eventually, p: "robot in pharmacy", text: "Reach the pharmacy."}
- {op: never, p: "robot in operating_room", text: "Stay out of the operating room."}
- {op: before, p: "robot in central_hallway", q: "robot in pharmacy", text: "Take the hallway before the pharmacy."}
- {op: never_during, p: "robot in exam_room_1", q: "exam_room_1_door/0.open == false", text: "Never be in the exam room while its door is closed."}
key meaning
op always, never, eventually, before, or never_during
p first atom, required
q second atom, required for before/never_during, rejected (parse error) with any other op
text human-readable description, optional

An atom is one of these bare forms (no env_N/ prefix): <entity>.<field> == <value> (an entity field test), <subject> in <zone> (zone membership), <subject> within <r> of <subject> (planar distance) and not <atom>. A subject is robot (the robot being judged), another robot's name or a pedestrian's name. Every robot is also an entity of kind robot with the fields phase, met, failed, dropped and violated, so robot_1.phase == 2 is a valid atom. A world entity (zone, door, elevator, schedule, sound) is named <name>/<level>, or <name> alone when no other level has one. Clauses take an optional on_failure (continue by default, stop_task, abort_episode). Conditions are judged online at the node tick and replayed offline by arena_evaluation with the same code. Schema: shared/conditions.py.

assets/ directory

World-local assets take precedence over network-fetched assets because DynamicPaths.WORLD / 'assets' is registered before NetResolver for every domain asset type (see DynamicPaths.as_resolvers, tree/init.py:77).

Layout mirrors the network structure: assets/<domain>/<asset_type>/<name>/. A world that ships a custom Wall preset places it at assets/Common/Wall/<name>/<name>.yaml.

How WorldIdentifier resolves a name

Resolvers run in order, first hit wins (tree/World/World.py, bottom of file):

  1. Each ARENA_WORLD_PATH root (colon-separated env var), if set.
  2. ASS_DIR / 'worlds', the worlds shipped in this package, existence-checked.
  3. One NetResolver per WORLD_BUCKETS provider (default arena-worlds-prod-public), fetching into $ARENA_ASSETS_DIR/<bucket>/<name>/. A world keys off its bare name, so the worlds bucket is itself the namespace.
  4. A FallbackResolver rooted at ASS_DIR / 'worlds'. It never answers reads and so can never shadow an earlier source. It only supplies a write target (resolve_write_path() / resolve_write_sync()) for programmatic world creation.

Worlds not shipped in this package are hosted in the public bucket(s) named by WORLD_BUCKETS and pulled on demand. arena asset find world <name> prints every resolver's verdict for a name (which one hit, which were shadowed). arena asset pull world <name> fetches one into the local cache. arena asset push world <name> publishes a locally-authored world after a preflight that checks every asset it references would resolve for someone else.

WorldIdentifier('hospital_1').resolve() returns a MultiLevelWorldView wrapping whichever of the above resolved first. Call world.load() to parse world.yaml into a WorldDescription.

Shipped worlds

Name Notes
airport Airport terminal layout
hospital_1 Multi-zone hospital with patient rooms, reception, hallways
hospital_2 Hospital variant
map_empty Single open zone
office_1 Office floor plan
reception Reception area