
The Gazebo Harmonic based simulation environment for PhysiCar AI, a Physical AI education platform.
The source is installed at /opt/physicar/src/physicar-sim.
An HTTP API for track management and vehicle state queries is served under the /sim/api/ path.
Sections are ordered by how often you will reach for them: basic health first, then the
vehicle and world you drive against, the things you can change, and finally monitoring,
evaluation and world management.
| Method |
Path |
Description |
GET |
/sim/api/status |
Simulator runtime status |
GET |
/sim/api/clock |
Sim time / real time / RTF / paused |
| Method |
Path |
Description |
GET |
/sim/api/pose |
Vehicle pose (world absolute coordinates) |
POST |
/sim/api/pose |
Teleport the vehicle ({"x": 1.0, "y": 2.0, "yaw": 0.0} — omitted fields keep their current value; the pose is normalized upright at ground level, so this also rights a flipped car) |
| Method |
Path |
Description |
GET |
/sim/api/world |
One-call snapshot of the current world's immutable definition: identity (world_id/rev/display name), track geometry (route centerline+boundaries, bounds), object catalog (type/static/movable/origin/size), and whether it has an evaluation |
GET |
/sim/api/route |
Track centerline waypoints, plus inner/outer boundary lines when available |
GET |
/sim/api/bounds |
Track bounds (bounding box) |
GET |
/sim/api/objects |
World models query (name, type: object/wall/light, static, movable, origin/current pose, size) |
POST |
/sim/api/models/<name>/pose |
Move/rotate a world object ({"x": 1.0, "y": 2.0, "z": 0.1, "yaw": 0.0} — omitted fields keep their current value; rotation is yaw-only. Works for World Builder objects and traffic lights; walls and the track itself are rejected) |
| Method |
Path |
Description |
GET |
/sim/api/traffic_lights |
List the world's traffic lights and their states |
POST |
/sim/api/traffic_lights/<name> |
Change a light state ({"state": "red"} or {"state": "green"} — green→red passes through 3 s of yellow, during which commands are rejected with 409) |
| Method |
Path |
Description |
POST |
/sim/api/reset |
Put every movable object, traffic light and the vehicle back at its start pose — instant, pose-only, no world reload. The go-to reset between training episodes |
POST |
/sim/api/respawn |
Reload the whole world (~6 s) — the heavyweight reset, for when the world misbehaves |
| Method |
Path |
Description |
GET |
/sim/api/brightness |
Scene brightness factor ({"value": 1.0}) |
POST |
/sim/api/brightness |
Set scene brightness ({"value": 0.2..2.0}, 1.0 = default). Applied instantly at the display layer — the 3D viewer and the robot camera frames darken/brighten by the same factor (no world restart). One shared server-side value: every open viewer stays in sync. Persists across world switches and restarts |
GET |
/sim/api/overlay |
Current on-screen status text ({"text": ...}) |
POST |
/sim/api/overlay |
Show status text on the /sim screen ({"text": "...", "ttl": 10} — text ≤300 chars, ttl 1–3600 s; expires by itself, e.g. training progress) |
| Method |
Path |
Description |
GET |
/sim/api/state |
One-call snapshot of everything that changes in real time: world/running/switching, sim time/paused/rtf, vehicle pose, object poses, traffic lights, overlay, brightness, and the evaluation run state |
GET |
/sim/api/events |
SSE stream of named events. event: state — full status snapshot pushed on change (sim status, overlay, brightness, traffic lights with yellow_left); event: run — student-process events during an evaluation ({"phase": "start"|"log"|"exit", ...}). Consumers subscribe via addEventListener and MUST ignore unknown event names; new kinds are always added as new names, never as new snapshot keys |
| Method |
Path |
Description |
GET |
/sim/api/evaluation |
The current world's evaluation document (evaluation.json published from the World Builder: {version, config, script}), 404 when the world has none |
POST |
/sim/api/evaluation/run |
Launch the student's code for an evaluation ({"command"?, "time_limit_s"?} — defaults come from the world's evaluation config). Output streams to event: run on /sim/api/events; a wall-clock backstop kills the process if the browser runner disappears |
POST |
/sim/api/evaluation/stop |
Stop the student's process (idempotent) |
| Method |
Path |
Description |
GET |
/sim/api/worlds |
World list (includes the current world). Each item carries name, file, display (the published name, or the official world's display name — e.g. physicar_base → "PhysiCar Base"), world_id (32-hex publish id, null for built-ins whose internal name acts as their id), official, evaluation (whether it carries an evaluation), and deletable |
POST |
/sim/api/switch |
Switch world ({"world": "<name>.world"} or {"world_id": "<32-hex id>"} — the id form resolves an installed published world and returns 404 when it is not installed) |
GET |
/sim/api/worldpub |
Current world's publish coordinates (world_id/rev when it is an installed published world) plus the official-asset CDN revision (assets_rev, set only on a clean tagged checkout) and the CDN base URL — the viewer uses this to load meshes/textures from the worlds CDN |
POST |
/sim/api/worlds/install |
Install a published world from the worlds CDN ({"world_id": "<32-hex id>"}). The server downloads the world's manifest and files directly from the CDN (no browser relay), installs it under the custom_ convention, and skips the download when the same rev is already installed ("cached": true) |
The web viewer supports World Builder-style direct manipulation: click an object to
select it (white box), drag to move it, and drag the blue dot handle to rotate it.
The change is applied through the pose API when you release. Clicking a traffic
light opens its control panel (RED/GREEN) on the right side. The vehicle can be
moved the same way — its pose is normalized upright, and since odometry
(lidar + IMU) cannot observe a teleport, use Respawn afterwards if you need a
clean odometry state.
Traffic lights come from the world itself: Custom World Builder tracks may include
light models (<link name="light"> marker — the legacy signal marker is still
recognized). They are detected on world load and controlled via the panel or the
API above. Default state is green; states survive a respawn of the same world.
Runtime light placement was removed — place lights in the World Builder instead.
The simulator is a stock Gazebo Harmonic server, so the gz command-line tools work
against it directly — the shell environment already carries GZ_PARTITION=physicar,
which is what routes gz to this simulator (gz topic -l / gz service -l list the
raw transport).
Almost everything already has a better home: the robot's topics (camera, odometry,
/cmd_vel, ...) are bridged to ROS 2 — drive them with the ros2 CLI exactly as on
the real robot — and world, vehicle and display state is the HTTP API above. What
only the gz layer offers is the physics clock:
| Service |
Request → Reply |
Description |
/world/<world>/control |
gz.msgs.WorldControl → gz.msgs.Boolean |
Pause / resume the physics — the whole world freezes, sensors included |
gz service -s /world/physicar_base/control --reqtype gz.msgs.WorldControl \
--reptype gz.msgs.Boolean --timeout 3000 --req 'pause: true' # 'pause: false' resumes
While paused the /stats stream stops publishing, so /sim/api/clock keeps serving
its last snapshot — its paused flag lags; trust the frozen sim_time instead.
Copyright 2026 AICASTLE Inc. "PhysiCar" is a trademark of AICASTLE Inc.
| Component |
License |
physicar-sim (this project) |
GPL-3.0 |
| gzweb, Three.js, Lodash, Gazebo example models |
see NOTICE |
See LICENSE and NOTICE for full terms and third-party attributions.