v0.2 — multi-vehicle shared-world runtime¶
Status: in progress (started 2026-06-05). Decision recorded in V0.2_PLAN.md.
Decision¶
One vdsim_realtime process holds N vehicles in a shared world, stepped on one
clock, so vehicles share the environment and (later) can interact. Rejected N
independent plant processes because in-engine collision needs all bodies in one
frame at the same instant with contact forces fed back inside the integration
step — async free-running UDP processes cannot do that. Collision physics itself is
a future TODO (below); the runtime is built collision-ready now.
Scope & control model (v0.2)¶
- No runtime spawn/despawn. The vehicle set is fixed at simulator-setup time (the scenario YAML, loaded before the run starts). The runtime never adds or removes vehicles mid-run. (Dynamic spawning is a later concern.)
- Manual drive targets the selected vehicle. A driver/algorithm CMD carries a
vehicle_id; the runtime applies it to exactly that vehicle. Each vehicle holds its own latched command + ZOH timeout, independent of the others. - Multiple clients on one server. The CMD socket accepts datagrams from any
source, so two (or more) GUIs/clients can connect to one runtime and each drive a
different
vehicle_id. STATE is delivered to every active client (see subscribers below). This is the racing feature: GUI A drives vehicle 0, GUI B drives vehicle 1, both see both cars, they race. No ownership lock in v0.2 (last CMD pervehicle_idwins); soft ownership can be added later if needed.
Subscribers (multi-client STATE delivery)¶
- The runtime keeps a set of subscriber endpoints and sends every vehicle's STATE to all of them each tick.
- Auto-subscribe from CMD source: when a CMD arrives from a new
(ip,port), add it as a STATE destination and refresh its last-seen time; evict after a timeout (e.g. 2 s silent). So any controlling client automatically receives state — no explicit registration needed for drivers. - A lightweight
subscribe/heartbeat (a CMD with throttle/brake/steer = 0, or a dedicated keepalive) lets pure spectators receive STATE without driving. - Backward compat: the single fixed
--state-ip/--state-portdestination stays supported as a pre-seeded subscriber (today's GUI keeps working unchanged).
Wire protocol — VDS1 v4¶
Add a vehicle_id to address vehicles on a single socket. The 24-byte header
already has a _pad uint32 right after seq; repurpose it as vehicle_id
(uint32). No header or payload size change — only kVersion 3 -> 4 and the
pad becomes a real field. CMD and STATE both flow through write_header, so both
carry vehicle_id for free; kCmdBytes/kStateBytes are unchanged.
- STATE: server emits one packet per vehicle per tick, each tagged
vehicle_id. - CMD: client tags each command with
vehicle_id; server demuxes and ZOH-holds per vehicle (per-vehiclecmd_timeout). - Single-vehicle is just N=1 with
vehicle_id = 0— existing flows keep working. - Mirror the change in
cosim/protocol.pyand bump the version check there too.
Runtime — cosim/realtime_server.cpp -> world¶
- Hold
std::vectorof per-vehicle entries:{ IVehicleDynamics, State, last Cmd, cmd_recv_time, vehicle_id, spawn pose }. - One shared environment (
IContactProvider) and one shared clock; step every vehicle each tick with the samedt. - Per tick: drain the CMD socket, demux by
vehicle_id-> per-vehicle ZOH, and add each CMD's source to the subscriber set; step each vehicle; [future] run the contact-coupling pass; emit each vehicle's STATE to every subscriber. - Config (fixed at setup, no runtime spawn): a scenario YAML listing vehicles —
each with
vehicle.yaml,tire.yaml,level, spawn{x0,y0,yaw0,vx0}, andvehicle_id. Keep the current flat-flag CLI as the N=1 shortcut.
GUI — gui/server.py¶
The data layer already keys by vehicle id (self.ports[vid], live_vid,
/api/io and telemetry take a vehicle field). Changes:
- CosimBridge connects to ONE runtime, receives the multiplexed STATE and
demuxes by vehicle_id into per-vehicle buffers; renders all vehicles.
- Control relays CMD tagged with the selected vehicle_id (the car this GUI
drives). Two GUIs pointed at the same runtime, each with a different selected
vehicle, race against each other.
- The runtime can spawn locally (single-GUI default) or be an already-running
shared server that multiple GUIs attach to (racing) — the GUI just needs the
runtime's host/cmd-port. Scene UI (WS3) grows the vehicle tree / selector.
Collision — FUTURE TODO (deferred)¶
Where it will go and what it needs, so the runtime is built to accept it: - A contact-coupling pass after per-vehicle force/derivative computation and before (or within) the integration step, so contact forces are applied in the same step — this is why the shared-world single-process design was chosen. - Needs: per-vehicle body geometry (footprint / OBB), broad-phase (AABB sweep) + narrow-phase (OBB overlap / GJK), and a contact model (penalty spring-damper or impulse/LCP) feeding equal-and-opposite forces into each body's equations. - Fidelity is its own decision when scheduled (lightweight impulse vs full rigid-body); VDSim today has only tire-road contact, so body-body contact is a sizable new module. CARLA (PhysX) remains an alternative if collision is delegated. Not in the v0.2 multi-vehicle foundation scope.
Implementation increments¶
- VDS1 v4:
vehicle_idin header (cosim_protocol.hpp+cosim/protocol.py), version 3 -> 4, round-trip test. (foundation — DONE) - Runtime world: N vehicles in
realtime_server.cpp, shared env + clock, scenario YAML config, CMD demux byvehicle_id, subscriber set (auto-subscribe from CMD source + timeout), STATE to all subscribers. - GUI: connect to runtime, demux states, control the selected
vehicle_id, render N; support attaching to an already-running shared runtime (racing). - (parallel) subsystem modules — see
V0.2_SUBSYSTEMS.md(brake / steering / drivetrain / suspension as pluggable interfaces). Independent of 2-3. - (future) collision module per the section above.
Backward compatibility¶
N=1 + vehicle_id=0 reproduces v0.1 behaviour. The protocol size is unchanged, so
only the version gate moves; old v3 peers are rejected by the version check (as
before across bumps).