Skip to main content

Developer Documentation

This page covers building Playarea from source and the network protocol the simulator speaks. For installing the prebuilt package and a quick tour, see Getting Started.

Build

The simulator is a C++ app built with xmake:

xmake build

Dependencies are resolved automatically: threepp comes from Conan Center (conan::threepp in xmake.lua); the rest come from xmake's own package repo (xrepo). On Linux you also need the system X11/GL/audio dev headers (GLFW/OpenGL and OpenAL build from source there).

Network Interface

The robot splits control and feedback across two transports so each uses the right tool:

  • Control over TCP (default port 9000, --tcp_port) — reliable delivery, because a dropped command is bad. The protocol is plain ASCII, so you can drive it by hand with nc localhost 9000.
  • Feedback over UDP — compact binary sensor frames, because for a fast telemetry loop a fresh-but-lost frame beats one stuck behind TCP retransmits (head-of-line blocking).

Feedback is split into three independent channels a client subscribes to one at a time, each streamed to a UDP port of the client's choosing (so every channel can live on its own socket):

ChannelFrame magicContents
odomODM1Wheel-odometry pose estimate, at --tcp_feedback_hz (100 Hz).
scanSCN1Fake 360° planar laser scan, at --scan_hz (10 Hz).
cameraCAM1Head-camera POV image (RGB888, chunked), at --camera_hz (15 Hz).

Commands (client → server, TCP):

CommandEffect
cmd <v> <w> [head]Set linear (m/s), angular (rad/s) and optional head (rad/s).
stopZero every command.
sub <channel> <udp_port>Stream a channel (odom|scan|camera) to this client's address on the given UDP port.
unsub <channel>Stop that channel's stream.

UDP frames (server → client, all little-endian):

odom magic "ODM1" | uint32 seq | float64 t[s] | 5× float32: x y theta v w
(struct <4sId5f>, 36 bytes)

scan magic "SCN1" | uint32 seq | float64 t[s] |
float32 angle_min, angle_increment, range_min, range_max | uint32 count |
count× float32 ranges[m] (struct <4sId4fI> + <Nf>)

camera magic "CAM1" | uint16 w,h | uint32 size,offset |
uint16 chunk_index, chunk_count | RGB888 payload (struct <4sIHHIIHH> + bytes)
— one image split across chunks; reassemble by seq using offset.

Each frame's sequence number lets a client detect dropped or reordered frames; the timestamp is seconds since server start. Every subscription is tied to its TCP connection and stops automatically when that connection closes.

All values are SI: x/y in metres, theta heading in radians (about the z axis, wrapped to [-π, π]), v in m/s, w in rad/s. Scan ranges are metres in the sensor frame (a beam at range_max means "no return"). Kinematics and odometry are integrated on a dedicated fixed-rate thread (--control_hz, default 200 Hz) decoupled from the render frame rate, so the control/feedback loop stays fast and jitter-free. The laser scanner raycasts the same obstacle footprints the robot collides with, and the head camera is an extra offscreen render of the robot's point of view — captured only while a client is subscribed.

Python client

A Python client wrapping this protocol ships in the playarea package — from playarea import Robot (client.py). The uv-based demos in examples/00-simple/ use it to drive the robot in a circle and print odometry, plot the laser scanner RViz-style, and view the head-camera stream live:

cd examples/00-simple && uv run main.py # or: uv run scan.py / uv run camera.py

Map format & coordinate conventions

The simulator loads a ROS-style occupancy grid (dark pixels are walls, white is free space) plus a map.yaml metadata file (resolution, origin, thresholds). The default world (11.09 × 11.09 m at 0.022178 m/px, 500 × 500 px) matches the sim's built-in map. Point the simulator at a YAML pair:

playarea --map_yaml /path/to/map.yaml

The .yaml's image: path is resolved relative to the yaml file, so keep the pair together. Pass --map_yaml "" to ignore the yaml and drive the map from --map_image + the individual --map_* flags instead.

  • World origin (0, 0) is the centre of the map — the exported yaml origin is the bottom-left corner [-w/2, -h/2, 0], which places it centred.
  • x points east (right), y points north (up), matching the simulator.
  • PNG row 0 is the north edge; the sim loads with flipY = true so image row 0 becomes the bottom (south) row of the world.

Design and export these maps in the browser with the Map Editor.