This page is the wire contract between Studio and any client. It covers the connection, the full topic namespace, the two encryption layers, and the packet formats a client must parse. Read it before writing a custom terminal.

All examples use <GAME> for the session name configured under Client in Studio, and <TEAM> for a team’s numeric ID.


Connection

  • Default broker: mqtt.zendir.io, or your own host. See Running Your Own MQTT Broker.
  • Port: 1883 for native TCP clients. The browser Operator tool uses MQTT over WebSockets on 8883.
  • Authentication: none at the broker level in the default model.
  • QoS: all traffic is QoS 0. The retain flag is never set, so subscribe before the simulation starts.
  • Client ID: arbitrary. There is no required naming convention.
  • Handshake: none. There is no login packet or team-join message. Team credentials are shared out of band.

Topic namespace

Every topic is rooted at Zendir/SpaceRange/<GAME>/. Directions below are from the client’s point of view. Studio’s direction is the mirror image. All topics are QoS 0 and non-retained.

TopicClient doesPayloadEncryption
Zendir/SpaceRange/<GAME>/SessionsubscribeJSON clock and run statenone
Zendir/SpaceRange/<GAME>/InfosubscribeJSON game metadata and scoresnone
Zendir/SpaceRange/<GAME>/<TEAM>/UplinkpublishJSON command envelopeXOR (team password)
Zendir/SpaceRange/<GAME>/<TEAM>/Downlinksubscribebinary frameXOR (team password) then Caesar (team key)
Zendir/SpaceRange/<GAME>/<TEAM>/RequestpublishJSON request envelopeXOR (team password)
Zendir/SpaceRange/<GAME>/<TEAM>/ResponsesubscribeJSON response and pushesXOR (team password)
Zendir/SpaceRange/<GAME>/Admin/RequestpublishJSON request envelopeXOR (admin password)
Zendir/SpaceRange/<GAME>/Admin/ResponsesubscribeJSON response and pushesXOR (admin password)

A single-team terminal subscribes to Session, Info, its team Downlink, and its team Response, and publishes to its team Uplink and Request.

NOTE

The Downlink topic is the only one with two encryption layers. Everything else that is encrypted uses XOR alone. Session and Info are never encrypted.

Session

Studio publishes Session to all clients about three times a second. It is plain JSON:

{
  "timestamp": "2026-02-14T00:10:00Z",
  "time": 600.0,
  "utc": "2026-02-14T00:10:00Z",
  "instance": "a1b2c3d4",
  "state": "running"
}
  • time is seconds since simulation start and matches the SimulationTime in telemetry.
  • state is one of running, standby, paused, or ended.
  • instance changes whenever the scenario resets. When it changes, clear every cache and re-run discovery.

Info

Studio publishes Info only when the game roster or a score changes. It carries the game name and the current team list with scores.

Team traffic on Uplink, Request, and Response is JSON XOR-encrypted with the team password. Response is multiplexed: besides replies, it carries unsolicited pushes such as event_triggered, objective_completed, and score_updated. Always dispatch by the message type, not by the order replies arrive.

Admin

Admin/Request and Admin/Response mirror the team request topics but are XOR-encrypted with the separate admin password. They expose cross-team control and history.


Encryption

Space Range stacks two length-preserving ciphers. Neither changes the message length.

MessageLayers
Uplink, Request, Response, AdminXOR only
DownlinkXOR on the outside, Caesar on the inside
Session, Infonone

XOR transport layer

  • Key: the team’s six-character password (or the admin password), UTF-8 encoded.
  • Algorithm: cyclic byte-wise XOR. For each byte, out[i] = data[i] XOR key[i mod keyLength].
  • Properties: the same operation encrypts and decrypts. There is no initialization vector and no padding.
  • Coverage: the entire MQTT payload of the topic.

Caesar RF layer

  • Key: an integer from 0 to 255, per team. Read it with the get_telemetry request; it changes if the team rotates keys.
  • Algorithm: additive shift modulo 256. Encrypt with c[i] = (p[i] + key) mod 256; decrypt with p[i] = (c[i] - key) mod 256.
  • Coverage: only the inner body of a downlink frame, meaning the bytes after the five-byte header. It does not cover the header, and it is not applied to uplink.

Order of operations

To decode a downlink message:

  1. Peel the XOR layer with the team password.
  2. Read the five-byte frame header (format byte plus team ID).
  3. Peel the Caesar layer from the bytes after the header, using the team key.
  4. Parse the inner body according to the format byte.

To encode an uplink message:

  1. Serialize the command envelope to JSON and UTF-8 bytes.
  2. XOR-encrypt with the team password.
  3. Publish to the team Uplink topic. There is no Caesar layer on uplink.

Packet formats

Endianness

LayerEndianness
Downlink frame header (team ID)little-endian
CCSDS primary and secondary headerbig-endian
CCSDS user-data fieldsbig-endian
Uplink intercept headerlittle-endian
Charge-coupled device metadata headerlittle-endian

After the XOR layer is removed, a downlink message is:

[ Format (1 byte) | TeamID (4 bytes, little-endian) | Caesar-encoded body ]

The format byte selects how to parse the body after the Caesar layer is removed:

ValueMeaningBody
0Noneempty sentinel, discard
1Messagea CCSDS space packet
2Media50-byte name header then file bytes, usually a JPEG
3Uplink intercept32-byte header then captured on-air bytes
4Detector frame50-byte name header, 20-byte metadata header, then 16-bit samples

CCSDS space packet (format 1)

A space packet is a six-byte primary header, a 24-byte secondary header by default, then variable user data.

Primary header, three 16-bit big-endian words:

  • Word 0: version (3 bits), type (1 bit), secondary-header flag (1 bit), then the 11-bit APID.
  • Word 1: sequence flags (2 bits) and a 14-bit per-APID sequence count.
  • Word 2: packet data length minus one. Add one for the byte count after the primary header.

Secondary header (default 24 bytes, big-endian): a one-byte P-field, a four-byte coarse time, a two-byte fine time, an eight-character ASCII spacecraft ID, a one-byte packet subtype, and an eight-byte float64 SimulationTime that matches the Session clock.

NOTE

A scenario can customize the headers. The authoritative field layout for every packet is the schema returned by the get_packet_schemas request. Cache it once per instance.

APID catalog

Telemetry packets are grouped by subsystem:

RangeAPIDs
System100 Ping, 101 Schedule Report, 102 Configuration Report
Power200 Battery, 201 Power Source, 202 Power Node, 203 Power Monitor
Sensors300 Magnetometer, 301 GPS, 302 EM Sensor, 303 CCD, 304 Gyroscope, 305 Laser Range Finder, 306 Radar
Attitude400 Computer, 401 Reaction Wheels, 402 Dynamics, 403 Thruster, 404 Formation Flying, 405 Fuel Tank, 406 Fuel Flow
Telemetry500 Receiver, 501 Transmitter, 502 Jammer, 503 Storage

User-data encoding

Every packet encodes fields the same way: a Bool is one byte; an Int is a 32-bit big-endian two’s-complement integer; a Float is a 32-bit big-endian IEEE-754 value; a String is a 16-bit big-endian length prefix then UTF-8 bytes; a Vector is a sequence of 64-bit big-endian floats in X, Y, Z, W order.

Ping (APID 100)

The Ping is the heartbeat packet. Its fields, in order, are State (string), Station (string), Memory (float, 0 to 1), Battery (float, 0 to 1), Commands (a JSON-string array of recently executed commands), and UplinkInterceptDataBytes (int). State is one of NOMINAL, LOW, SAFE, TRANSMIT, FULL STORAGE, or REBOOTING.

Schedule Report (APID 101) and Configuration Report (APID 102)

The Schedule Report replies to get_schedule and lists queued commands. The Configuration Report replies to get_configuration, and is also queued automatically after power_bus, fuel_bus, guidance, camera, capture, and any docking change. Its single Data field is a JSON object with scope-gated keys power_bus, fuel_bus, computer, camera, and docking.


Internal topics

Some topics are implementation details and are not part of the client contract. Do not build against them:

  • Zendir/SpaceRange/<GAME>/<FREQUENCY>/Telemetry, the simulated radio medium between spacecraft and ground stations.
  • Zendir/SpaceImage/..., the optional external image-server integration.

Next, apply all of this in Building a Custom Operator Terminal.