feat(bridge): the Oxide plugin — protocol 1 #1
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/phase-1-bridge"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
First code in this repo.
RunicGateway.csdials out to a rust-link sidecar on loopback and speaks newline-delimited JSON over it.Phase 1 of
modules/rust/PLAN.md. Lands with Rust-Link#1, Module-Rust and docs.The threading contract
The ServUO bridge's, unchanged, because the reason for it is the same on both games:
Emitis called from the main thread. It formats nothing, blocks on nothing and touches no socket — it enqueues and returns, so a wedged or absent sidecar cannot stall the game. The queue is bounded, drop-oldest.Interface.Oxide.NextTick, and touches no Unity object,BasePlayerorConVar.Settings come from Oxide's own config (org-lead decision D3), so an operator retunes the bridge the way they retune any other plugin — and so it lands inside the site-side config editor a later phase adds.
Four things the live rig corrected
None of these could have been caught by a test. A hook binds by reflection and fails silently, so a running server is the only proof.
catch, and a connection ending because the reader saw EOF leaves the writer to exit cleanly — nothing throws, so nothing was logged. Generalisable: a log in acatchonly covers the failures that throw, and an orderly peer shutdown is not one.Unloadblocked the main thread for 1.9s, which Oxide reports out loud. The reconnect backoff wasThread.SleepandUnloadjoins the link thread. Waiting on theAutoResetEventthatUnloadalready signals makes it immediate. The ServUO plugin has the same sleep and gets away with it only because ServUO does not hot-reload.SocketException.Messageis NUL-padded on Windows — around 200 NUL bytes in the middle of the sentence, from a fixed-size OS buffer. NUL is not whitespace, soTrimdoes not touch it and neither does a whitespace-only collapse. It tookod -con the log to see at all.bootIdregenerated on every PLUGIN load rather than every SERVER start. A freshGuidatInitmeantoxide.reloadannounced a brand-new boot — and the website's reconcile design hangs off that value, so every reload would have asked core to sweep its whole resource ledger for a world that never moved. It is nowProcess.StartTime: exact, identical on every read, and it changes when and only when the thing it names changes. Verified by reloading twice and watching the id hold.Diagnosing it
rg.link, from the console or over RCON, reports the link's own counters. That is what separates "the plugin is not loaded" from "the plugin cannot reach the sidecar" from "the website cannot reach the sidecar", which look identical from the site.How it was verified
Compiled and loaded on the live rig on the first try; reconnect, disconnect logging, backoff, heartbeat and the correlated reply were each exercised there. A
server.hellofrom it reached the public website API.🤖 Generated with Claude Code
https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4