{"id":"awakesl/tether","name":"tether","scope":"awakesl","platform":"roblox","description":"Many Roblox servers as one world. A game declares its kinds; Tether routes, buffers and arbitrates; the relay never reads a payload.","version":"0.2.0","latest":"0.2.0","versions":["0.2.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"352db63ad3c9f7c45419fd9f68f2b9a257aaefb792e80f7e50ed7598f2bf897c","likes":0,"downloads":0,"install":"forest install awakesl/tether","url":"https://forest.dev/p/roblox/awakesl/tether","files":"https://api.forest.dev/ai/package/roblox/awakesl/tether/files","readme":"# Tether\r\n\r\nReal-time state between every server of your game, so they play as one world.\r\n\r\n- **Streaming:** each server posts and receives about 8 times a second, and every value arrives\r\n  with its age.\r\n- **Interest:** put things under keys (a chunk, a room, a party). A server only gets what's under\r\n  the keys it wants.\r\n- **Fleet-wide ids:** mint ids that never collide across servers.\r\n- **Ownership:** send a message to whichever server owns a thing, like damage to whoever owns the\r\n  target.\r\n- **Contested things:** drop items nobody owns. When two servers grab one, exactly one wins.\r\n- **Late joiners:** a server that starts watching a key gets the newest state under it.\r\n- **Smooth playback:** a playout buffer on the receiving side, with posts aligned to the relay's\r\n  tick across servers.\r\n- **Small packets:** you declare each message's fields (`u8` to `u32`, bit flags, quantised\r\n  floats), and the relay never reads your payloads.\r\n\r\nTether has no built-in idea of position, damage or players. You declare every kind of message.\r\n\r\n## Example\r\n\r\n```lua\r\nlocal Tether = require(ReplicatedStorage.Packages.Tether)\r\n\r\nlocal net = Tether.connect({\r\n    url = \"https://your-relay.example/publish\",\r\n    identity = HttpService:GenerateGUID(false),\r\n    key = HttpService:GetSecret(\"tether\"),\r\n    kinds = {\r\n        motion = {\r\n            id = 1,\r\n            routing = \"key\",          -- to every server watching where it is\r\n            collapsible = true,       -- a newer position replaces an unsent older one\r\n            retention = \"replace\",    -- late joiners get the newest\r\n            fields = {\r\n                { \"x\", \"u16\", scale = 4, offset = -8192 },\r\n                { \"z\", \"u16\", scale = 4, offset = -8192 },\r\n            },\r\n        },\r\n    },\r\n})\r\n\r\nRunService.Heartbeat:Connect(function(dt)\r\n    net:step(dt)\r\nend)\r\nrepeat task.wait() until net:ready()\r\n\r\nlocal id = net:mint()\r\nnet:place(id, \"12:7\")\r\nnet:want({ \"12:7\", \"12:8\" })\r\nnet:send(\"motion\", id, { x = 100.5, z = -250.25 })\r\n\r\nnet:on(\"motion\", function(subject, values, ageMs)\r\n    -- another server's thing moved\r\nend)\r\n```\r\n\r\n## Setup\r\n\r\n1. Add it with Wally:\r\n\r\n   ```toml\r\n   Tether = \"awakesl/tether@0.2.0\"\r\n   ```\r\n\r\n2. Run the relay where your servers can reach it over HTTPS:\r\n\r\n   ```\r\n   cd relay\r\n   cargo build --release\r\n   TETHER_KEY=your-secret target/release/tether-relay\r\n   ```\r\n\r\n   To test from Studio, `scripts\\dev` starts it behind a free cloudflared tunnel.\r\n\r\n3. Turn on HTTP requests for the game, and add the key as a Secret named `tether`.\r\n\r\n## Docs\r\n\r\n[docs/Guide.md](docs/Guide.md) covers declaring kinds and field types, routing, ownership and\r\nclaims, the tick, and every relay setting.\r\n\r\n## Repo\r\n\r\n    src/       the Luau library\r\n    relay/     the relay, in Rust\r\n    test/      the specs, as they run in a place\r\n    scripts/   the dev launcher and headless rigs\r\n    docs/      the guide\r\n\r\nMIT licence.\r\n","readmeTruncated":false}