4 realtime registry and wire contract
forgejo-actions edited this page 2026-07-13 19:45:35 +00:00

Realtime Registry And Wire Contract

What This Contract Is

The realtime registry is the source of truth for Aether's client/server wire protocol. It ties packet names, stream message ids, datagram kinds, capability gates, delivery metadata, validation coverage, rollout state, and generated documentation into one contract. The exhaustive Realtime Protocol Registry is a generated wiki reference; this page explains how to use it when changing or debugging the protocol.

Registry edits start in crates/common-proto/protocol/realtime_registry.json. The generated Rust facade in crates/common-proto/src/realtime_registry.rs exposes lookup helpers for stream dispatch, datagram dispatch, delivery metadata, capabilities, packet budgets, bootstrap sections, and generated docs.

Streams And Datagrams

Reliable streams carry ordered protocol messages in protobuf envelopes with a numeric message_id. Use stream messages for handshake, heartbeats, chat, handoff and transfer control, gameplay action requests and results, bootstrap manifests, durable snapshots, and anything that cannot be safely lost. Stream rows declare direction, stream class, payload type, max-byte budget, capability, compression class, rollout, deprecation state, and coverage anchors.

The stream classes are control, gameplay, and background. Control traffic is for handshakes, heartbeats, disconnects, receipts, and transfer control. Gameplay traffic is for latency-sensitive authoritative game actions and corrections. Background traffic is for snapshots, chat, inventory, currency, quests, overlays, and other state that should not block control flow.

Datagrams carry oneof packet bodies instead of numeric stream ids. Use datagrams only for high-frequency or freshness-first state where the newest valid packet can replace older packets. Current client datagrams are client_move_input, client_gameplay_state, and client_equipment_state. Current server datagrams are server_state_snapshot, server_movement_correction, and server_equipment_state.

Never put durable side effects on a datagram-only path. A datagram receiver must tolerate loss, duplication, stale arrival, and reordering. If a feature needs reliable completion, pair the datagram with a stream acknowledgement, repair path, or durable stream message.

Delivery Guarantees

Delivery contracts are centralized in crates/common-proto/src/realtime_wire/delivery.rs. The contract names the primary path, durability, acknowledgement rule, recovery rule, stream class, queue lane, overflow behavior, fairness throttle, and reconnect replay policy used by runtime queues.

Reliable stream delivery is ordered within the chosen stream class and is the default for durable or recoverable protocol state. The registry currently models delivery paths as reliable stream, datagram, and negotiated movement. Durability classes include durable, recoverable, freshness-first, and ephemeral.

Movement input is negotiated movement: it may use the datagram path, is recoverable, and is acknowledged by server movement correction. Stale or duplicate movement is expected to be ignored by the reducer.

Server state snapshots are freshness-first. They can use datagram delivery, are acknowledged by client_snapshot_receipt, and recover through targeted repair before falling back to a full resync.

Gameplay action request/result traffic is durable stream traffic keyed by gameplay request id. A replay should return the same authoritative result instead of applying the action twice.

Message IDs

Stream message_id values are stable wire ids. Current client stream ids are 1, 2, and 4 through 9; current server stream ids are 101 through 124. Id 3 is intentionally unused in the current generated registry.

Do not reuse a stream id for changed semantics. When a payload shape or behavior is not backward compatible, add a new registry row, gate it behind capability or rollout policy, and deprecate the old row after all supported peers have migrated.

Packet capture and validation should treat stream ids and datagram names differently. Stream captures include message_id; datagram captures identify the packet by datagram kind and packet name.

Capabilities

Capabilities describe negotiated protocol features, not UI preferences. The current required/default capability set is:

  • clock_sync_v1
  • continuous_replication_v1
  • equipment_state_datagram_v1
  • gameplay_state_datagram_v1
  • interest_buckets_v1
  • movement_datagram_v1
  • resume_snapshot_v1
  • typed_disconnects_v1

All are currently default-enabled, required, stable, and current. A sender must not rely on a capability-gated packet unless the peer's negotiated capability set includes that capability. New capabilities should start as explicit registry rows, be wired into handshake negotiation, and only become required when every supported client and server build can honor them.

Validators

Packet validation lives in crates/common-proto/src/packet_validation.rs. Validation failures currently classify as packet_too_large or malformed_packet.

Stream envelope validators check that the message id is known, the direction matches the peer, the frame and per-message budgets are respected, and wire enums decode to supported values. Decoded client validators enforce field limits such as session token length, chat message and target lengths, trigger tag counts, gameplay action list size, equipped item counts, action timing bounds, and snapshot repair receipt rules.

Datagram validators enforce datagram frame and payload budgets, oneof kind support, and body-specific constraints. Snapshot repair receipts cap missing snapshot ids at 64, require ids newer than the acknowledged snapshot, and require strictly increasing ids.

When adding a registry row, update or generate all related coverage: codec dispatch, replay fixture, delivery contract, validation entry, and docs anchor. A registry row without one of those bindings is not ready for rollout.

Rollout And Deprecation

The current rollout policy supports current only. There are no default allowed channels, blocked channels, or deprecation windows.

Most stream and datagram rows are stable and permanent. server_spawn and server_despawn remain stable with legacy_presence_shadow deprecation. server_farming_tile_deltas is experimental/current.

Roll out protocol changes in this order:

  1. Add the new registry row, capability, validation, delivery contract, codec binding, and replay fixture.
  2. Ship receive-side support before send-side use.
  3. Gate send-side use behind negotiated capabilities, build channels, or rollout metadata.
  4. Keep old ids and packet names until every supported peer has migrated.
  5. Mark deprecation in the registry before removing a row.

Wire compatibility is permanent inside a supported protocol generation unless the protocol major changes and all peers upgrade together.

Packet Capture

Set AETHER_PACKET_DEBUG_CAPTURE_DIR to enable development packet capture. When unset, capture is a no-op. Capture writes JSONL files named by executable and process id under the configured directory.

Capture records include timestamp, process id, executable, direction, transport, packet name, stream message id when applicable, protocol/features when available, and payload length. Captures do not store raw payload bytes.

Use captures to answer questions like:

  • Is the peer sending an unknown stream id or datagram name?
  • Did the packet exceed the generated byte budget?
  • Did stream direction, capability negotiation, or rollout policy reject the packet?
  • Did a datagram arrive but get dropped as stale or duplicate?

Packet debug captures are development artifacts. Keep them under artifacts/ or another ignored path and do not commit them.

Regeneration

After editing the registry source, regenerate the derived files:

cargo run -p aether-common-proto --bin aether-realtime-registry -- --write

Use --check in gates to verify generated Rust, generated tests, and the wiki registry reference are current. The generator prints markdown when run without --check or --write; --check and --write are mutually exclusive.

Before merging a protocol change, review the generated docs diff and run the common-proto tests that cover realtime_registry, realtime_wire, client_datagrams, delivery, and packet validation. If the generated docs changed unexpectedly, treat the registry row as the source of the drift and fix it there.

Keep this page narrative and use the Realtime Protocol Registry for packet-by-packet lookup.