elevator-core 7.0.1

Engine-agnostic elevator simulation library with pluggable dispatch strategies
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
//! # elevator-core
//!
//! Engine-agnostic, tick-based elevator simulation library for Rust.
//!
//! This crate provides the building blocks for modeling vertical transportation
//! systems — from a 3-story office building to an orbital space elevator.
//! Stops sit at arbitrary positions rather than uniform floors, and the
//! simulation is driven by a deterministic 8-phase tick loop.
//!
//! ## Key capabilities
//!
//! - **Pluggable dispatch** — four built-in algorithms ([`dispatch::scan::ScanDispatch`],
//!   [`dispatch::look::LookDispatch`], [`dispatch::nearest_car::NearestCarDispatch`],
//!   [`dispatch::etd::EtdDispatch`]) plus the [`dispatch::DispatchStrategy`] trait
//!   for custom implementations.
//! - **Trapezoidal motion profiles** — realistic acceleration, cruise, and
//!   deceleration computed per-tick.
//! - **Extension components** — attach arbitrary `Serialize + DeserializeOwned`
//!   data to any entity via [`world::World::insert_ext`] without modifying the
//!   library.
//! - **Lifecycle hooks** — inject logic before or after any of the eight
//!   simulation phases. See [`hooks::Phase`].
//! - **Metrics and events** — query aggregate wait/ride times through
//!   [`metrics::Metrics`] and react to fine-grained tick events via
//!   [`events::Event`].
//! - **Snapshot save/load** — capture and restore full simulation state with
//!   [`snapshot::WorldSnapshot`].
//! - **Zero `unsafe` code** — enforced by `#![forbid(unsafe_code)]`.
//!
//! ## Quick start
//!
//! ```rust
//! use elevator_core::prelude::*;
//! use elevator_core::stop::StopConfig;
//!
//! let mut sim = SimulationBuilder::demo()
//!     .stops(vec![
//!         StopConfig { id: StopId(0), name: "Ground".into(), position: 0.0 },
//!         StopConfig { id: StopId(1), name: "Floor 2".into(), position: 4.0 },
//!         StopConfig { id: StopId(2), name: "Floor 3".into(), position: 8.0 },
//!     ])
//!     .build()
//!     .unwrap();
//!
//! sim.spawn_rider_by_stop_id(StopId(0), StopId(2), 75.0).unwrap();
//!
//! for _ in 0..1000 {
//!     sim.step();
//! }
//!
//! assert!(sim.metrics().total_delivered() > 0);
//! ```
//!
//! ## Crate layout
//!
//! | Module | Purpose |
//! |--------|---------|
//! | [`builder`] | Fluent [`SimulationBuilder`](builder::SimulationBuilder) API |
//! | [`sim`] | Top-level [`Simulation`](sim::Simulation) runner and tick loop |
//! | [`dispatch`] | Dispatch strategies and the [`DispatchStrategy`](dispatch::DispatchStrategy) trait |
//! | [`world`] | ECS-style [`World`](world::World) with typed component storage |
//! | [`components`] | Entity data types: [`Rider`](components::Rider), [`Elevator`](components::Elevator), [`Stop`](components::Stop), [`Line`](components::Line), [`Route`](components::Route), [`Patience`](components::Patience), [`Preferences`](components::Preferences), [`AccessControl`](components::AccessControl), [`DestinationQueue`](components::DestinationQueue), [`ServiceMode`](components::ServiceMode), [`Orientation`](components::Orientation), [`Position`](components::Position), [`Velocity`](components::Velocity), [`FloorPosition`](components::FloorPosition) |
//! | [`config`] | RON-deserializable [`SimConfig`](config::SimConfig), [`GroupConfig`](config::GroupConfig), [`LineConfig`](config::LineConfig) |
//! | [`events`] | [`Event`](events::Event) variants and the [`EventBus`](events::EventBus) |
//! | [`metrics`] | Aggregate [`Metrics`](metrics::Metrics) (wait time, throughput, etc.) |
//! | [`hooks`] | Lifecycle hook registration by [`Phase`](hooks::Phase) |
//! | [`query`] | Entity query builder for filtering by component composition |
//! | [`systems`] | Per-phase tick logic (dispatch, movement, doors, loading, ...) |
//! | [`snapshot`] | [`WorldSnapshot`](snapshot::WorldSnapshot) save/restore with custom-strategy factory |
//! | [`scenario`] | Deterministic scenario replay from recorded event streams |
//! | [`topology`] | Lazy-rebuilt connectivity graph for cross-line routing |
//! | [`traffic`] | [`TrafficSource`](traffic::TrafficSource) trait + `PoissonSource` (feature-gated) |
//! | [`tagged_metrics`] | Per-tag metric accumulators for zone/line/priority breakdowns |
//! | [`movement`] | Trapezoidal velocity-profile primitives ([`braking_distance`](movement::braking_distance), [`tick_movement`](movement::tick_movement)) |
//! | [`door`] | Door finite-state machine ([`DoorState`](door::DoorState)) |
//! | [`time`] | Tick-to-wall-clock conversion ([`TimeAdapter`](time::TimeAdapter)) |
//! | `energy` | Simplified per-elevator energy modeling (gated behind the `energy` feature) |
//! | [`stop`] | [`StopId`](stop::StopId) and [`StopConfig`](stop::StopConfig) |
//! | [`entity`] | Opaque [`EntityId`](entity::EntityId) runtime identity |
//! | [`ids`] | Config-level typed identifiers ([`GroupId`](ids::GroupId), etc.) |
//! | [`error`] | [`SimError`](error::SimError), [`RejectionReason`](error::RejectionReason), [`RejectionContext`](error::RejectionContext) |
//!
//! ## Architecture overview
//!
//! ### 8-phase tick loop
//!
//! Each call to [`Simulation::step()`](sim::Simulation::step) runs these
//! phases in order:
//!
//! 1. **`AdvanceTransient`** — transitions `Boarding→Riding`, `Exiting→Arrived`,
//!    teleports walkers.
//! 2. **Dispatch** — builds a [`DispatchManifest`](dispatch::DispatchManifest)
//!    and calls each group's [`DispatchStrategy`](dispatch::DispatchStrategy).
//! 3. **Reposition** — optional phase; moves idle elevators via
//!    [`RepositionStrategy`](dispatch::RepositionStrategy) for better coverage.
//! 4. **`AdvanceQueue`** — reconciles each elevator's phase/target with the
//!    front of its [`DestinationQueue`](components::DestinationQueue), so
//!    imperative pushes from game code take effect before movement.
//! 5. **Movement** — applies trapezoidal velocity profiles, detects stop arrivals
//!    and emits [`PassingFloor`](events::Event::PassingFloor) events.
//! 6. **Doors** — ticks the [`DoorState`](door::DoorState) FSM per elevator.
//! 7. **Loading** — boards/exits riders with capacity and preference checks.
//! 8. **Metrics** — aggregates wait/ride times into [`Metrics`](metrics::Metrics)
//!    and per-tag accumulators.
//!
//! For full per-phase semantics (events emitted, edge cases, design rationale),
//! see [`ARCHITECTURE.md`][arch] §3. This crate-level summary is the short
//! form; `ARCHITECTURE.md` is canonical.
//!
//! [arch]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/ARCHITECTURE.md
//!
//! ### Component relationships
//!
//! ```text
//! Group ──contains──▶ Line ──has──▶ Elevator ──carries──▶ Rider
//!   │                  │              │                      │
//!   └── DispatchStrategy              └── Position           └── Route (optional)
//!        RepositionStrategy               Velocity               Patience
//!                      │                  DoorState               Preferences
//!                      └── Stop (served stops along the shaft)
//! ```
//!
//! ### Rider lifecycle
//!
//! Riders progress through phases managed by the simulation:
//!
//! ```text
//! Waiting → Boarding → Riding → Exiting → Arrived
//!    ↑         (1 tick)           (1 tick)     │
//!    │                                         ├── settle_rider() → Resident
//!    │                                         │                       │
//!    │                                         └── despawn_rider()     │
//!    │                                                                 │
//!    └──────── reroute_rider() ────────────────────────────────────────┘
//!
//! Waiting ──(patience exceeded)──→ Abandoned ──→ settle/despawn
//! ```
//!
//! - **`Arrived`** / **`Abandoned`**: terminal states; consumer must explicitly
//!   settle or despawn the rider.
//! - **`Resident`**: parked at a stop, invisible to dispatch and loading.
//!   Query with [`Simulation::residents_at()`](sim::Simulation::residents_at).
//! - **Population queries**: O(1) via maintained reverse index —
//!   [`residents_at`](sim::Simulation::residents_at),
//!   [`waiting_at`](sim::Simulation::waiting_at),
//!   [`abandoned_at`](sim::Simulation::abandoned_at).
//!
//! ### Extension storage
//!
//! Games attach custom data to any entity without modifying the library:
//!
//! ```rust,ignore
//! // Attach a VIP flag to a rider.
//! world.insert_ext(rider_id, VipTag { priority: 1 }, "vip_tag");
//!
//! // Query it alongside built-in components.
//! for (id, rider, vip) in world.query::<(EntityId, &Rider, &Ext<VipTag>)>().iter() {
//!     // ...
//! }
//! ```
//!
//! Extensions participate in snapshots via `serialize_extensions()` /
//! `register_ext::<T>(name)` + `load_extensions()`.
//!
//! ### Snapshot lifecycle
//!
//! 1. Capture: `sim.snapshot()` → [`WorldSnapshot`](snapshot::WorldSnapshot)
//! 2. Serialize: serde (RON, JSON, bincode, etc.)
//! 3. Deserialize + restore: `snapshot.restore(factory)` → new `Simulation`
//! 4. Re-register extensions: `world.register_ext::<T>(name)` per type
//! 5. Load extension data: `sim.load_extensions()`
//!
//! For the common case (save-to-disk, load-from-disk), skip the format choice
//! and use [`Simulation::snapshot_bytes`](sim::Simulation::snapshot_bytes) /
//! [`Simulation::restore_bytes`](sim::Simulation::restore_bytes). The byte
//! blob is postcard-encoded and carries a magic prefix plus the crate version:
//! restoring bytes from a different `elevator-core` version returns
//! [`SimError::SnapshotVersion`](error::SimError::SnapshotVersion) instead of
//! silently producing a garbled sim. Determinism is bit-exact across builds
//! of the same crate version, which makes snapshots viable as rollback-netcode
//! checkpoints or deterministic replay fixtures.
//!
//! ### Performance
//!
//! | Operation | Complexity |
//! |-----------|-----------|
//! | Entity iteration | O(n) via `SlotMap` secondary maps |
//! | Stop-passing detection | O(log n) via `SortedStops` binary search |
//! | Dispatch manifest build | O(riders) per group |
//! | Population queries | O(1) via `RiderIndex` reverse index |
//! | Topology graph queries | O(V+E) BFS, lazy rebuild |
//!
//! ## Runtime upgrades
//!
//! Elevator kinematic and door-timing parameters can be mutated at runtime
//! via the `Simulation::set_*` setters — handy for RPG-style upgrade systems
//! or scripted events that boost speed, capacity, or door behavior mid-game.
//!
//! Each setter validates its input, mutates the underlying component, and
//! emits an [`Event::ElevatorUpgraded`](events::Event::ElevatorUpgraded) so
//! game code can react (score popups, SFX, UI). Velocity is preserved when
//! kinematic parameters change — the integrator picks up the new values on
//! the next tick without jerk. Door-timing changes apply to the next door
//! cycle and never retroactively retime an in-progress transition.
//!
//! See [`examples/runtime_upgrades.rs`][rte] for an end-to-end demonstration
//! that doubles a car's `max_speed` mid-run and prints the throughput delta.
//!
//! [rte]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/examples/runtime_upgrades.rs
//!
//! ## Door control
//!
//! Games that want to drive elevator doors directly — e.g. the player
//! pressing "open" or "close" on a cab panel in a first-person game, or an
//! RPG where the player *is* the elevator — use the manual door-control
//! API on [`Simulation`](sim::Simulation):
//!
//! - [`request_door_open`](sim::Simulation::request_door_open)
//! - [`request_door_close`](sim::Simulation::request_door_close)
//! - [`hold_door_open`](sim::Simulation::hold_door_open) (cumulative)
//! - [`cancel_door_hold`](sim::Simulation::cancel_door_hold)
//!
//! Each call is either applied immediately (if the car is in a matching
//! door-FSM state) or queued on the elevator's
//! [`door_command_queue`](components::Elevator::door_command_queue) and
//! re-tried every tick until it can be applied. The only hard errors are
//! "not an elevator" / "elevator disabled" and (for `hold_door_open`) a
//! zero-tick argument — the rest return `Ok(())` and let the engine pick
//! the right moment. A [`DoorCommand`](door::DoorCommand) can be:
//!
//! - `Open` — reverses a closing door; no-op if already open or opening;
//!   queues while the car is moving.
//! - `Close` — forces an early close from `Loading`. Waits one tick if a
//!   rider is mid-boarding/exiting (safe-close).
//! - `HoldOpen { ticks }` — adds to the remaining open dwell; two calls
//!   of 30 ticks stack to 60. Queues if doors aren't open yet.
//! - `CancelHold` — clamps any accumulated hold back to the base dwell.
//!
//! Every command emits
//! [`Event::DoorCommandQueued`](events::Event::DoorCommandQueued) when
//! submitted and
//! [`Event::DoorCommandApplied`](events::Event::DoorCommandApplied) when
//! it actually takes effect — useful for driving UI feedback (button
//! flashes, SFX) without polling the elevator every tick.
//!
//! See [`examples/door_commands.rs`][dex] for a runnable demo.
//!
//! [dex]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/examples/door_commands.rs
//!
//! ## Sub-tick position interpolation
//!
//! Games that render at a higher framerate than the simulation ticks (e.g.
//! a 60 Hz sim driving a 144 Hz camera, or a first-person game where the
//! player is parented to an elevator car) need a smooth position between
//! ticks. [`Simulation::position_at`](sim::Simulation::position_at) lerps
//! between the snapshot taken at the start of the current tick and the
//! post-tick position, using an `alpha` accumulator clamped to `[0.0, 1.0]`:
//!
//! ```text
//! // typical fixed-timestep render loop
//! accumulator += frame_dt;
//! while accumulator >= sim.dt() {
//!     sim.step();
//!     accumulator -= sim.dt();
//! }
//! let alpha = accumulator / sim.dt();
//! let y = sim.position_at(car, alpha).unwrap();
//! ```
//!
//! The previous-position snapshot is refreshed automatically at the start
//! of every [`step`](sim::Simulation::step). [`Simulation::velocity`] is
//! a convenience that returns the raw `f64` along the shaft axis (signed:
//! +up, -down) for camera tilt, motion blur, or cabin-sway effects.
//!
//! See [`examples/fp_player_rider.rs`][fpe] for a runnable demo.
//!
//! [fpe]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/examples/fp_player_rider.rs
//!
//! ## Manual-drive mode
//!
//! Games where the player *is* the elevator — driving the car with a
//! velocity stick, slamming an emergency brake, stopping between floors
//! — use [`ServiceMode::Manual`](components::ServiceMode::Manual). Manual
//! elevators are skipped by the automatic dispatch and repositioning
//! phases; the consumer drives movement via:
//!
//! - [`Simulation::set_target_velocity`](sim::Simulation::set_target_velocity) — signed target speed (+up, -down), clamped to the car's `max_speed`.
//! - [`Simulation::emergency_stop`](sim::Simulation::emergency_stop) — commands an immediate deceleration to zero.
//!
//! Physics still apply: velocity ramps toward the target using the car's
//! `acceleration` / `deceleration` caps, and positions update at the same
//! rate as normal elevators. Manual elevators can come to rest at any
//! position — they are not required to align with a configured stop.
//! Door behaviour is governed by the [manual door-control API](#door-control);
//! nothing opens or closes automatically. Leaving `Manual` clears any
//! pending velocity command.
//!
//! Each command emits
//! [`Event::ManualVelocityCommanded`](events::Event::ManualVelocityCommanded)
//! with the clamped target, or `None` for an emergency stop.
//!
//! See [`examples/manual_driver.rs`][mde] for a runnable demo.
//!
//! [mde]: https://github.com/andymai/elevator-core/blob/main/crates/elevator-core/examples/manual_driver.rs
//!
//! ## ETA queries
//!
//! Hall-call dispatch UIs, scheduling overlays, and "press to call" panels
//! all need the same answer: *how long until this car shows up?* Two methods
//! on [`Simulation`](sim::Simulation) compute it from the elevator's queued
//! destinations, current kinematic state, and configured door dwell:
//!
//! - [`Simulation::eta`](sim::Simulation::eta) — seconds until a specific
//!   elevator reaches a specific stop, or `None` if the stop isn't on its
//!   route or the car is in a dispatch-excluded service mode.
//! - [`Simulation::best_eta`](sim::Simulation::best_eta) — winner across all
//!   eligible elevators, optionally filtered by indicator-lamp direction
//!   ("which up-going car arrives first?").
//!
//! Both walk the queue in service order, summing closed-form trapezoidal
//! travel time per leg plus the configured door cycle at every intermediate
//! stop. The closed-form solver lives in [`eta::travel_time`] and tracks the
//! per-tick integrator in [`movement::tick_movement`] to within a tick or
//! two — close enough for UI countdowns; not a substitute for actually
//! simulating to compare two dispatch policies.
//!
//! For narrative guides, tutorials, and architecture walkthroughs, see the
//! [mdBook documentation](https://andymai.github.io/elevator-core/).

#![forbid(unsafe_code)]
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]

/// Entity-component data types for the simulation.
pub mod components;
/// Entity identity and allocation.
pub mod entity;
/// Simulation error types.
pub mod error;
/// Typed identifiers for groups and other sim concepts.
pub mod ids;
/// ECS-style query builder for iterating entities by component composition.
pub mod query;
/// Tick-loop system phases (dispatch, reposition, movement, doors, loading, metrics).
pub mod systems;
/// Central entity/component storage.
pub mod world;

/// Fluent builder for constructing a Simulation programmatically.
pub mod builder;
/// Building and elevator configuration (RON deserialization).
pub mod config;
/// Pluggable dispatch strategies (SCAN, LOOK, nearest-car, ETD).
pub mod dispatch;
/// Door finite-state machine.
pub mod door;
/// Simplified energy modeling for elevators.
#[cfg(feature = "energy")]
pub mod energy;
/// ETA estimation for queued elevators (closed-form trapezoidal travel time).
pub mod eta;
/// Simulation event bus and event types.
pub mod events;
/// Lifecycle hooks for injecting logic before/after simulation phases.
pub mod hooks;
/// Aggregate simulation metrics.
pub mod metrics;
/// Trapezoidal velocity-profile movement math.
///
/// Exposes [`braking_distance`](movement::braking_distance) for consumers
/// writing opportunistic dispatch strategies that need the kinematic answer
/// without constructing a `Simulation`.
pub mod movement;
/// Phase-partitioned reverse index for rider population queries.
mod rider_index;
/// Scenario replay from recorded event streams.
pub mod scenario;
/// Top-level simulation runner.
pub mod sim;
/// World snapshot for save/load.
pub mod snapshot;
/// Stop configuration helpers.
pub mod stop;
/// Tag-based per-entity metrics.
pub mod tagged_metrics;
/// Tick-to-wall-clock time conversion.
pub mod time;
/// Topology graph for cross-line connectivity queries.
pub mod topology;
/// Traffic generation (arrival patterns).
#[cfg(feature = "traffic")]
pub mod traffic;

/// Register multiple extension types for snapshot deserialization in one call.
///
/// Eliminates the manual `register_ext` ceremony after snapshot restore.
///
/// # Example
///
/// ```
/// use elevator_core::prelude::*;
/// use elevator_core::register_extensions;
/// use serde::{Deserialize, Serialize};
///
/// #[derive(Clone, Debug, Serialize, Deserialize)]
/// struct VipTag { level: u32 }
///
/// #[derive(Clone, Debug, Serialize, Deserialize)]
/// struct Priority { rank: u8 }
///
/// let mut sim = SimulationBuilder::demo().build().unwrap();
/// register_extensions!(sim.world_mut(), VipTag => "vip_tag", Priority => "priority");
/// ```
#[macro_export]
macro_rules! register_extensions {
    ($world:expr, $($ty:ty => $name:expr),+ $(,)?) => {
        $( $world.register_ext::<$ty>($name); )+
    };
}

/// Common imports for consumers of this library.
///
/// `use elevator_core::prelude::*;` pulls in the types you need for the vast
/// majority of simulations — building a sim, stepping it, spawning riders,
/// reading events and metrics, and writing custom dispatch strategies.
///
/// # Contents
///
/// - **Builder & simulation:** [`SimulationBuilder`](crate::builder::SimulationBuilder),
///   [`Simulation`](crate::sim::Simulation),
///   [`RiderBuilder`](crate::sim::RiderBuilder)
/// - **Components:** [`Rider`](crate::components::Rider),
///   [`RiderPhase`](crate::components::RiderPhase),
///   [`Elevator`](crate::components::Elevator),
///   [`ElevatorPhase`](crate::components::ElevatorPhase),
///   [`Stop`](crate::components::Stop), [`Line`](crate::components::Line),
///   [`Position`](crate::components::Position),
///   [`Velocity`](crate::components::Velocity),
///   [`FloorPosition`](crate::components::FloorPosition),
///   [`Route`](crate::components::Route),
///   [`Patience`](crate::components::Patience),
///   [`Preferences`](crate::components::Preferences),
///   [`AccessControl`](crate::components::AccessControl),
///   [`Orientation`](crate::components::Orientation),
///   [`ServiceMode`](crate::components::ServiceMode)
/// - **Config:** [`SimConfig`](crate::config::SimConfig),
///   [`GroupConfig`](crate::config::GroupConfig),
///   [`LineConfig`](crate::config::LineConfig)
/// - **Dispatch:** [`DispatchStrategy`](crate::dispatch::DispatchStrategy),
///   [`RepositionStrategy`](crate::dispatch::RepositionStrategy), plus the
///   built-in reposition strategies
///   [`NearestIdle`](crate::dispatch::reposition::NearestIdle),
///   [`ReturnToLobby`](crate::dispatch::reposition::ReturnToLobby),
///   [`SpreadEvenly`](crate::dispatch::reposition::SpreadEvenly),
///   [`DemandWeighted`](crate::dispatch::reposition::DemandWeighted)
/// - **Identity:** [`EntityId`](crate::entity::EntityId),
///   [`StopId`](crate::stop::StopId), [`GroupId`](crate::ids::GroupId)
/// - **Errors & events:** [`SimError`](crate::error::SimError),
///   [`RejectionReason`](crate::error::RejectionReason),
///   [`RejectionContext`](crate::error::RejectionContext),
///   [`Event`](crate::events::Event),
///   [`EventBus`](crate::events::EventBus)
/// - **Misc:** [`Metrics`](crate::metrics::Metrics),
///   [`TimeAdapter`](crate::time::TimeAdapter)
///
/// # Not included (import explicitly)
///
/// - Concrete dispatch implementations: `dispatch::scan::ScanDispatch`,
///   `dispatch::look::LookDispatch`, `dispatch::nearest_car::NearestCarDispatch`,
///   `dispatch::etd::EtdDispatch`
/// - `ElevatorConfig` and `StopConfig` from [`crate::config`]
/// - Traffic generation types from [`crate::traffic`] (feature-gated)
/// - Snapshot types from [`crate::snapshot`]
/// - The [`World`](crate::world::World) type (accessed via `sim.world()`,
///   but required as a parameter when implementing custom dispatch)
pub mod prelude {
    pub use crate::builder::SimulationBuilder;
    pub use crate::components::{
        AccessControl, DestinationQueue, Direction, Elevator, ElevatorPhase, FloorPosition, Line,
        Orientation, Patience, Position, Preferences, Rider, RiderPhase, Route, ServiceMode, Stop,
        Velocity,
    };
    pub use crate::config::{GroupConfig, LineConfig, SimConfig};
    pub use crate::dispatch::reposition::{
        DemandWeighted, NearestIdle, ReturnToLobby, SpreadEvenly,
    };
    pub use crate::dispatch::{
        AssignedCar, DestinationDispatch, DispatchStrategy, RepositionStrategy,
    };
    pub use crate::entity::EntityId;
    pub use crate::error::{RejectionContext, RejectionReason, SimError};
    pub use crate::events::{Event, EventBus, EventCategory};
    pub use crate::ids::GroupId;
    pub use crate::metrics::Metrics;
    pub use crate::sim::{RiderBuilder, Simulation};
    pub use crate::stop::StopId;
    pub use crate::time::TimeAdapter;
}

#[cfg(test)]
mod tests;