Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Configuration

The simulation can be configured entirely in code or loaded from a RON config file. Both paths produce the same SimConfig under the hood, so everything the builder can do, a config file can express, and vice versa.

Programmatic configuration

The SimulationBuilder provides a fluent API. Here is a more complete example that customizes everything:

use elevator_core::prelude::*;
use elevator_core::config::ElevatorConfig;
use elevator_core::stop::StopId;
use elevator_core::dispatch::etd::EtdDispatch;

fn main() -> Result<(), SimError> {
    let sim = SimulationBuilder::new()
        .stop(StopId(0), "Ground", 0.0)
        .stop(StopId(1), "Sky Lobby", 50.0)
        .stop(StopId(2), "Observation Deck", 100.0)
        .elevator(ElevatorConfig {
            id: elevator_core::config::ElevatorConfigId(0),
            name: "Express A".into(),
            max_speed: 5.0.into(),
            acceleration: 2.0.into(),
            deceleration: 3.0.into(),
            weight_capacity: 1200.0.into(),
            starting_stop: StopId(0),
            door_open_ticks: 60,
            door_transition_ticks: 15,
            ..Default::default()
        })
        .building_name("Skyline Tower")
        .ticks_per_second(60.0)
        .dispatch(EtdDispatch::new())
        .build()?;

    Ok(())
}

Override any ElevatorConfig field with struct-update syntax – ElevatorConfig { max_speed: 4.0, ..Default::default() }.

ElevatorConfig fields

FieldTypeDescriptionDefault
idElevatorConfigIdUnique numeric ID within the config (mapped to EntityId at runtime)
nameStringHuman-readable name for UIs and logs
max_speedSpeedMaximum travel speed (distance units/second)2.0
accelerationAccelAcceleration rate1.5
decelerationAccelDeceleration rate2.0
weight_capacityWeightMaximum total rider weight800.0
starting_stopStopIdWhere this elevator starts
door_open_ticksu32Ticks doors stay fully open10
door_transition_ticksu32Ticks for a door open/close transition5
restricted_stopsVec<StopId>Stops this elevator cannot serve[]
service_modeOption<ServiceMode>Initial service modeNone (Normal)
inspection_speed_factorf64Speed multiplier in Inspection mode0.25

RON config files

For data-driven workflows, define your building in a RON file and load it at runtime. RON (Rusty Object Notation) is a human-readable format that maps directly to Rust structs.

Here is the default.ron included in the repository:

SimConfig(
    building: BuildingConfig(
        name: "Demo Tower",
        stops: [
            StopConfig(id: StopId(0), name: "Ground", position: 0.0),
            StopConfig(id: StopId(1), name: "Floor 2", position: 4.0),
            StopConfig(id: StopId(2), name: "Floor 3", position: 7.5),
            StopConfig(id: StopId(3), name: "Floor 4", position: 11.0),
            StopConfig(id: StopId(4), name: "Roof", position: 15.0),
        ],
    ),
    elevators: [
        ElevatorConfig(
            id: 0,
            name: "Main",
            max_speed: 2.0,
            acceleration: 1.5,
            deceleration: 2.0,
            weight_capacity: 800.0,
            starting_stop: StopId(0),
            door_open_ticks: 60,
            door_transition_ticks: 15,
        ),
    ],
    simulation: SimulationParams(
        ticks_per_second: 60.0,
    ),
    passenger_spawning: PassengerSpawnConfig(
        mean_interval_ticks: 120,
        weight_range: (50.0, 100.0),
    ),
)

Loading a RON file

Add ron to your dependencies (cargo add ron) since elevator-core re-uses it for config parsing but doesn’t re-export the deserializer:

use elevator_core::prelude::*;
use elevator_core::config::SimConfig;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ron_str = std::fs::read_to_string("assets/config/default.ron")?;
    let config: SimConfig = ron::from_str(&ron_str)?;

    let sim = SimulationBuilder::from_config(config).build()?;

    println!("Loaded simulation with {} tick rate",
             sim.dt().recip() as u32);
    Ok(())
}

SimulationBuilder::from_config accepts a deserialized SimConfig and still lets you chain builder methods on top – for example, to override the dispatch strategy or register extensions.

Config sections

BuildingConfig

Defines the building layout. The stops list must have at least one entry, and each StopId must be unique. Stop positions are arbitrary f64 values along the shaft axis – they do not need to be uniformly spaced.

SimulationParams

Controls simulation timing. The tick rate determines dt (time delta per tick): dt = 1.0 / ticks_per_second. Higher values produce smoother motion but require more computation. 60 is a good default.

PassengerSpawnConfig

Advisory parameters for traffic generators. The core library does not spawn passengers automatically – these values are consumed by game code or the optional traffic feature:

FieldMeaning
mean_interval_ticksAverage ticks between passenger spawns (Poisson distribution)
weight_range(min, max) for uniformly distributed rider weight

Long shafts and arbitrary distances

Stops carry arbitrary f64 positions, so the same engine drives a five-floor office and a kilometres-deep mine. The example below treats one position unit as one metre – the engine doesn’t enforce a unit, so any internally-consistent convention works.

A deep mine shaft is a useful stress test: real production hoists at sites like Mponeng run skips at 16-18 m/s over 2-3 km of vertical travel, with cage-loading dwell times measured in tens of seconds rather than the few seconds an office cab needs.

SimConfig(
    building: BuildingConfig(
        name: "Mine Shaft 1",
        stops: [
            StopConfig(id: StopId(0), name: "Surface",   position:     0.0),
            StopConfig(id: StopId(1), name: "Mid-level", position: -1200.0),
            StopConfig(id: StopId(2), name: "Bottom",    position: -2400.0),
        ],
    ),
    elevators: [
        ElevatorConfig(
            id: 0,
            name: "Cage A",
            max_speed: 18.0,           // ~m/s, deep-shaft hoist class.
            acceleration: 1.5,
            deceleration: 2.5,
            weight_capacity: 12000.0,  // miners + skip ore + tools.
            starting_stop: StopId(0),
            door_open_ticks: 120,      // crew loading takes time.
            door_transition_ticks: 30,
        ),
    ],
    // ...
)

The stops sit 1,200 metres apart (under the metre convention chosen above), positions are negative (down from the surface datum), and the doors hold open six times longer than an office default. The same simulation engine handles both a five-story office and a 2.4 km mine shaft – the only thing that changes is the config.

The repository also bundles assets/config/space_elevator.ron – two stops separated by 1,000 distance units (interpret the unit however you like) – as a stress test for very high max_speed values and very long door cycles.

Validation

Config is validated at construction time (in SimulationBuilder::build()). Invalid configs produce a SimError::InvalidConfig with a descriptive message. Validation checks include:

  • At least one stop
  • No duplicate StopId values
  • At least one elevator
  • All physics parameters positive
  • Each elevator’s starting_stop references an existing stop
  • Tick rate is positive

Next steps