Files
link/sidecar
colby b46005b333 Sidecar: shard link (Rust)
First cut of the Rust sidecar, in sidecar/. It is the TCP listener the shard
dials out to; that asymmetry is what keeps the game unreachable from the website.

shard.rs: serve() binds 127.0.0.1:7788 and accepts shard connections in a loop,
re-accepting on disconnect. Each connection splits read/write: the reader parses
newline-JSON into ShardEvent { kind, value } and forwards over an mpsc; the writer
drains a command mpsc. ShardHandle::send posts to whichever shard is connected and
drops with a warning if none is -- a website query during an outage should fail
fast, not queue; live events that must survive an outage are buffered by the shard.

main.rs wires it up, logs events by kind, and runs a 15s heartbeat ping to
exercise the command path. Later phases fan events out to a WebSocket broadcaster
and SQLite, and turn REST calls into shard commands.

Verified against the live shard: the sidecar received the shard's server.hello
(parsed, fields intact), round-tripped its heartbeat ping -> pong, and after a
sidecar restart the shard reconnected on its own and re-sent hello. Tokio + serde;
axum/sqlx/tungstenite come with the WS and REST phases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:58:04 -05:00
..
2026-07-10 15:58:04 -05:00
2026-07-10 15:58:04 -05:00
2026-07-10 15:58:04 -05:00
2026-07-10 15:58:04 -05:00
2026-07-10 15:58:04 -05:00

uo-link sidecar

The Rust half of the bridge. It terminates the loopback link to the ServUO shard and (as it grows) exposes WebSocket + REST to the website.

website ──WS (live feed) / REST (queries)──►  sidecar  ──loopback TCP 127.0.0.1:7788──►  shard
                                              (this)      newline-JSON, bidirectional

The sidecar is the TCP listener; the shard dials out to it. That is what keeps the game unreachable from the website — the game exposes no port of its own. See ../docs/PLAN.md §2.

Run

cargo run                 # info logging
RUST_LOG=debug cargo run  # see every event, incl. pong heartbeats

Binds 127.0.0.1:7788 and waits for the shard to connect. Boot the shard (or it will reconnect on its own) and watch server.hello arrive.

Status

Piece State
Shard link (shard.rs) done — accepts the shard, reads events, sends commands, re-accepts on disconnect. Verified against the live shard: received server.hello, round-tripped a pingpong, and reconnected after a sidecar restart.
WebSocket feed (website ← live events) not started
REST queries (char profile, roster, vendor snapshot, link submit) not started
SQLite persistence (event history, economy, cached profiles, link map) not started

Design

  • shard.rsserve() binds the listener and accepts shard connections in a loop. Each connection splits into read/write halves: the read half parses newline-JSON into ShardEvent { kind, value } and forwards them; the write half drains an mpsc of command lines. ShardHandle::send posts a command to whichever shard is currently connected, and drops with a warning if none is — a website query during a shard outage should fail fast and retry, not queue behind a reconnect. Live events that must survive an outage are buffered by the shard, not here.
  • main.rs — wires it together and, for now, logs events by kind. Later phases fan ShardEvents out to the WebSocket broadcaster and SQLite, and turn REST calls into shard commands via ShardHandle.

Wire protocol

Every line is one JSON object with t (epoch ms) and kind. The shard→sidecar events and sidecar→shard commands are catalogued in ../docs/PLAN.md (§5 data catalog, §7 protocol) and were all validated end-to-end while building the plugin. Notable inbound commands the sidecar will issue: char.request, account.roster, vendor.snapshot, link.confirm, towncrier.add/remove, ping.