Fe₃O₄ — magnetic, grounded
The decentralized, self-hostable Rust game platform.
Bring any server. It scales to your hardware. No cloud required.
Content-addressed games, deterministic WASM sandboxing, replay-verified anti-cheat, and non-custodial crypto payments.
Part of VulOS — the open, self-hostable web OS & app suite. Runs standalone, or as an app hosted by the Vulos OS.
The sharpest claim here: every match is reproducible. The server records
one ReplayLog per match — inputs plus a state_hash per tick
(backend/magnetite-sdk/src/authority.rs:1093) — and anyone can re-run it
through verify_replay (authority.rs:1205) to get back the same hashes or a
named Divergence. That is not an aspiration: a unit test tampers with a
recorded hash and asserts verify_replay catches it
(authority.rs:1534, "tampered hash must yield Divergence at tick 3"), and an
end-to-end test drives real, varying, non-empty inputs from four players
through both a native and a WASM-sandboxed executor and asserts every tick's
hash matches between them (magnetite-e2e/tests/wasm_end_to_end.rs,
wasm_sandbox_parity_with_native) — deliberately not the empty-input version
of that test that used to pass while hiding a guest bug that discarded every
input frame. A match log is not a black box; it is a claim anyone holding it
can check.
Magnetite is a decentralized, self-hostable Rust game platform: write a game once against a deterministic authoritative-server SDK, and run it anywhere from a laptop to a fleet you own — no central cloud, no fiat custody, and no home-grown chat/voice/streaming stack standing between a player and the person hosting them.
A game is a content-addressed portable object. A node is generic compute that fills its own hardware. The chain is the wallet. Discovery is a phonebook, not an authority. Everything social — chat, voice, video, streaming — is a pluggable integration, not something we build.
Anyone runs the single magnetite node binary. Identity is a keypair.
Payments are non-custodial crypto. Comms are provided by existing
decentralized systems (Matrix/Element, Jitsi, LiveKit, Owncast/PeerTube)
through one adapter seam. The game runtime — authoritative simulation, WASM
sandbox, deterministic replay and anti-cheat — is the one thing Magnetite
owns outright, and it was decentralization-ready from day one. See
DECENTRALIZATION.md for the full redesign spec.
GitHub · Quick start · Docs · Architecture · License
magnetite is an independent VulOS product — a game platform,
not the OS itself. It runs fully standalone: the magnetite binary needs no
Vulos account, no Vulos infrastructure, and no network beyond whatever seams
you choose to enable. It can also be hosted as an app inside the Vulos OS shell,
the same way any other VulOS product is.
VulOS publishes one thing outright — the OS — free and open source (MIT OR Apache-2.0). There is no managed hosting and nothing sold or operated by Vulos; every product, magnetite included, is self-provisioned and self-paid for by whoever runs it. See vulos.org for the rest of the suite.
Nobody gives an open, Rust-native "same code, jam-to-AAA, authoritative + sandboxed + anti-cheat + one-command-deploy" primitive. That's what this is.
Write your game once against magnetite-sdk::authority::AuthoritativeGame.
The platform runs it at any scale:
| Topology | Player count | How |
|---|---|---|
SingleRoom |
up to ~16 | 1 process, broadcast-all |
Dedicated |
up to ~256 | authoritative server, interest-filtered snapshots |
Sharded |
AAA / unbounded | spatial shards + cross-shard handoff |
MatchConfig::auto(n) escalates topology by player count. Your game code is
identical across all three.
Perf numbers (debug build, single-threaded in-proc, magnetite-e2e scale bench):
| Scenario | ticks/sec | μs/tick |
|---|---|---|
| SingleRoom (4 players) | 203,116 | 4.92 |
| SingleRoom (16 players) | 185,399 | 5.39 |
| Dedicated (32 players) | 151,388 | 6.61 |
| Dedicated (64 players) | 114,215 | 8.76 |
| Dedicated (128 players) | 78,950 | 12.67 |
| Dedicated (256 players) | 50,591 | 19.77 |
A release build is ~3–5× faster. The smoke-check assertion ticks/sec ≥ 1,000
is met with large margin.
Game logic compiles to wasm32-wasip1 and runs inside a WasmExecutor with
hard guarantees:
- Fuel budget per tick (
fuel_per_step) — runaway loops cannot stall the server. - Memory cap (
max_memory_bytes) — guest cannot exhaust host RAM. - Epoch interrupt (
epoch_tick_ms × max_epochs_per_step) — wall-clock timeout per step. - No OS randomness, no wall clock —
random_getandclock_time_getreturnENOSYS. The only randomness source isStepCtx.rng(DeterministicRng, xoshiro256**), derived per tick from(MatchConfig::seed, tick)— so RNG position is implied by the tick rather than carried as hidden state a snapshot cannot capture.
Result: same (state, ordered commands, tick, seed) always produces the same result, on any host.
The guest boundary is a documented, language-agnostic contract — eight C-ABI exports and
length-prefixed JSON, nothing Rust-specific — see
site/docs/sandbox-abi.md. cargo run -p magnetite-sandbox --bin mag-conformance -- your.wasm checks any module against it.
- Clients send inputs; the server runs
AuthoritativeGame::validateto reject illegal actions, thenAuthoritativeGame::stepto advance state. Clients never send state. - The runtime records a
ReplayLog(every tick's inputs +state_hash).verify_replayre-simulates from scratch; any divergence is tamper evidence or a determinism bug. magnetite-anticheatadds composableValidators (aimbot snap, position teleport, fire-rate flood) and aTrustScoreMap(Warn → Kick → Ban escalation with decay).
Exercised end to end by magnetite-e2e (19 passing tests): WasmExecutor and
NativeExecutor produce identical state_hash on every tick under non-empty,
varying inputs from four players, a snapshot taken mid-match resumes the same
trajectory in a fresh sandboxed executor, verify_replay returns Clean,
cheating inputs are rejected and escalate the trust score, and a full-stack
WebSocket test with 3 real clients confirms convergence.
The emphasis is load-bearing. The parity test used to step both executors with an empty input list, which agrees perfectly on an idle physics loop and hid a guest bug that discarded every input frame. Parity over no inputs is not parity.
Everything provider-specific plugs in behind six traits (magnetite-seams).
The game runtime, scheduler, and payment path never name a provider type —
only the seam. Every seam ships a working, non-custodial, non-cloud default.
| Seam | Purpose | Default | Optional |
|---|---|---|---|
Identity / AuthProvider |
keypair identity, sign-a-challenge login | RawKeypairAuth (Ed25519) |
DmtapAuth (decentralized login) |
Naming |
human name ↔ raw key, display layer only | HashNaming |
DmtapNaming (name@domain ladder) |
BlobStore |
content-addressed games and assets | LocalBlobStore + HttpBlobStore |
DmtapPubBlobStore (MOTE); Iroh/BitTorrent |
Discovery |
the phonebook — never an authority | TrackerDiscovery + LanDiscovery (mDNS) |
DHT adapter |
CommsProvider |
chat / voice / video / streaming | BuiltinProvider (fallback) |
Matrix, Jitsi, LiveKit, Owncast/PeerTube |
PaymentRail |
non-custodial crypto checkout, hosting fees, wagers | MockPaymentRail (CI-safe) |
SolanaPaymentRail (real, wired into backend, never run against a validator); magnetite-stellar-rail (real, standalone, not yet wired into backend) — neither has ever settled a payment itself |
No fiat, no custody, no platform-held balances anywhere in the payment path — see docs/payments.md. No home-grown chat/voice/streaming stack a game is forced to depend on — see docs/comms.md. No central server registry to poll for capacity — see docs/hosting-a-server.md.
The landing page and docs viewer share Magnetite's own dark, near-black
"lodestone" identity — a magnetic violet→magenta accent (#7b61ff →
#ff4d9d) evoking a magnetic field, over a graphite base darker than any
other Vulos product.
Regenerate anytime with
npm run screenshotter(alias:npm run screenshots) — it serves the static site with a tiny built-in Node server, boots the app on a throwawayvitedev server withVITE_USE_MOCKS=true, and captures every surface in light and dark at retina. No backend, database or wasm build required. See docs/screenshots.md for the full gallery.
# install once
cargo install magnetite-cli
# scaffold a crate implementing AuthoritativeGame
magnetite new my-game
cd my-game
# cargo build --release --target wasm32-wasip1 → game.wasm
magnetite build
# build → WasmExecutor → SingleRoom server, ZERO backend
magnetite dev
# ws://127.0.0.1:<port> — play it right now
# bring your own box: measures its hardware, advertises
# capacity, joins the discovery mesh
magnetite serve --wasm path/to/game.wasm --advertise tracker.example.orgmagnetite dev already runs a game with zero backend, and magnetite serve
already takes an arbitrary box you own — there is no cloud account to create
and no capacity to request. See Getting started
for the full walkthrough.
There is no root Cargo.toml — this repo is 14 standalone crates linked
by path dependencies, each with its own manifest, on purpose (see the comment
at the top of magnetite-seams/Cargo.toml). A
bare cargo build --workspace at the repo root fails with "could not find
Cargo.toml"; .github/workflows/ci.yml builds and tests each crate from its
own directory via the matrix in ci/rust-crates.json,
and local development follows the same shape:
# Frontend (pre-decentralization marketplace UI, being rebuilt against the seams)
npm install
npm run dev # http://localhost:5173
# Any Rust crate — cd into it first, e.g.:
cd magnetite-runtime && cargo build && cargo test
cd magnetite-seams && cargo build --all-features && cargo test --all-features
# Every crate, the same way CI does it:
jq -c '.[]' ci/rust-crates.json | while read -r c; do
dir=$(echo "$c" | jq -r .dir); flags=$(echo "$c" | jq -r .flags)
(cd "$dir" && cargo test --locked $flags)
done
# Authoritative runtime standalone (smoke-test mode, no wasm required)
cd magnetite-runtime && cargo run --bin servePlayer keypair (Identity)
│
▼
Discovery (TrackerDiscovery + LanDiscovery) ──announce/find──► magnetite node
│ │ ├─ Capacity measurement
│ │ ├─ ShardScheduler
▼ │ ├─ AuthoritativeGame (WASM sandbox)
SessionAd (game hash, capacity, price, rooms) │ ├─ ReplayLog / verify_replay
│ └─ BlobStore (content-addressed)
│
┌───────────────────────┼───────────────────────┐
▼ ▼
PaymentRail (non-custodial) CommsProvider (pluggable)
checkout · open_channel · escrow Matrix · Jitsi/LiveKit · Owncast
Full diagram (mermaid) + the seam trait signatures: docs/architecture.md.
Full redesign spec + program backlog: DECENTRALIZATION.md.
| Crate | Role |
|---|---|
magnetite-seams |
The six seam traits + non-custodial, non-cloud default implementations |
backend/magnetite-sdk (::authority) |
Frozen traits: AuthoritativeGame, GameExecutor, NativeExecutor, Validator, ReplayLog, verify_replay, Topology, MatchConfig, DeterministicRng |
magnetite-runtime |
Authoritative game-server host: tick loop, WebSocket connection mgmt, interest-filtered delta/snapshot fan-out, ShardManager seam; magnetite-serve binary |
magnetite-sandbox |
WasmExecutor — Wasmtime host implementing GameExecutor; fuel/memory/epoch limits; WASI stubs (no clock, no rng) |
magnetite-anticheat |
Composable validators, TrustScoreMap, ReplayVerifier |
magnetite-cli |
magnetite new|build|dev|deploy|serve binary |
magnetite-web-client |
JS web client speaking ClientNet/ServerNet; prediction buffer; canvas renderer; in-browser replay playback |
magnetite-web-host |
Serves a content-addressed three.js/Godot/Unity/Bevy-web build over HTTP with an entitlement check — no tick loop, no authority, deliberately not a game host |
magnetite-solana-rail |
Real SPL-USDC-on-Solana PaymentRail; wired into backend behind --features solana / PAYMENT_RAIL=solana; never run against a validator |
magnetite-stellar-rail |
Real native-USDC-on-Stellar PaymentRail; standalone (no patala dependency); landed but not yet wired into backend's rail selector, and never run against Horizon — see docs/payments.md |
magnetite-kotva |
A narrow, unselected binding of the Identity/Naming seams to the kotva substrate — demonstrates the seams are bindable; nothing in the default build depends on it |
game-template-authoritative |
Reference game (top-down arena shooter) implementing AuthoritativeGame; canonical wasm ABI exports behind --features wasm |
game-client-bevy |
Bevy client with prediction/reconciliation (PredictionBuffer + ClientPredictor) wired to ServerNet |
magnetite-e2e |
Integration tests: convergence + verify_replay clean + anti-cheat WS rejection + wasm parity vs native + full-stack WS + scale bench |
use magnetite_sdk::{
export_game,
game::{GameLogic, GameMetadata},
input::{Action, Input},
state::{GameState, PlayerId, Snapshot},
};
struct MyGame { state: GameState }
impl GameLogic for MyGame {
fn new() -> Self { MyGame { state: GameState::default() } }
fn handle_input(&mut self, _pid: PlayerId, _input: Input) -> Action { Action::None }
fn tick(&mut self) { self.state.tick += 1; }
fn state(&self) -> &GameState { &self.state }
fn players(&self) -> Vec<PlayerId> { vec![] }
fn metadata(&self) -> GameMetadata { GameMetadata::default() }
fn snapshot(&self) -> Snapshot { Snapshot::new(self.state.tick, self.state.clone()) }
fn restore(&mut self, snap: Snapshot) { self.state = snap.state; }
}
export_game!(MyGame);For the server-authoritative path:
use magnetite_sdk::authority::{AuthoritativeGame, Topology, MatchConfig};
// Implement AuthoritativeGame, then:
let cfg = MatchConfig::auto(player_count); // SingleRoom / Dedicated / ShardedSee backend/magnetite-sdk/ and
game-templates/arcade/ for the full starter, and
game-templates/authoritative/ for the
canonical AuthoritativeGame reference implementation.
The node binary (magnetite serve / magnetite node) is configured entirely
by CLI flags — --wasm, --advertise, --cluster-peer, --handoff-addr — see
Quick start and docs/hosting-a-server.md.
There is no config file and no environment variable it reads: capacity (shard
count, player budget) is measured from the box's own cores and RAM, not set.
The pre-redesign backend and legacy React marketplace frontend (Postgres,
Redis, JWT, OAuth) still read .env — copy .env.example to
.env and fill in the values before running docker-compose.yml or
npm run dev. That surface predates the decentralized redesign; see
docs/self-hosting/index.md for what still
depends on it.
| Guide | File |
|---|---|
| Overview | docs/overview.md |
| Getting started | docs/getting-started.md |
| Roadmap (reference games) | ROADMAP.md |
| Architecture (seams + planes, mermaid diagram) | docs/architecture.md |
| Hosting a server (capacity-elastic nodes) | docs/hosting-a-server.md |
| Payments (non-custodial crypto) | docs/payments.md |
| Comms (Matrix / Jitsi / LiveKit / Owncast) | docs/comms.md |
| Screenshots | docs/screenshots.md |
| Decentralization spec + backlog | DECENTRALIZATION.md |
| MOAT Architecture | docs/MOAT-ARCHITECTURE.md |
| MOAT Scaling (topology + bench) | docs/moat/scaling.md |
| Replay & Spectator | docs/moat/replay-spectator.md |
| Developer Quickstart (SDK) | docs/for-developers/quickstart.md |
| Self-Hosting Guide | docs/self-hosting/index.md |
| Security & Sandboxing | docs/security/index.md |
Interactive docs site (static, no build step): open
site/docs.html, or the landing page.
Prerequisites: Rust 1.91+ (with wasm32-wasip1 target), Node.js 18+. backend/'s
locked dependency tree (AWS SDK crates) currently requires rustc 1.91.1 or newer
to even parse — verified by building backend with pinned toolchains; anything
below that fails before compilation starts.
rustup target add wasm32-wasip1
# No root Cargo.toml (14 standalone crates) — build/test/lint per crate, e.g.:
cd magnetite-seams && cargo build --all-features && cargo test --all-features && cargo clippy --all-features -- -D warnings
cd ../magnetite-sandbox && cargo build && cargo test && cargo clippy -- -D warnings
# ...or every crate exactly as CI does it, see ci/rust-crates.json + .github/workflows/ci.yml
npm install
npm run dev # legacy marketplace frontend (Vite, :5173)
npm run lint
npm run test:run # Vitest
npm run screenshotter # regenerate docs/screenshots/ (alias: npm run screenshots)Frozen invariant: the game runtime, scheduler, and payment path never name a provider-specific type — every provider integration goes through
magnetite-seams. See DECENTRALIZATION.md § 6.
The mark in brand/ is the source of truth. Every icon this repo
ships — favicon, PWA and app icons, the mark in the README and on the site — is
rendered from brand/logo.svg rather than redrawn, so there is one approved
drawing and no second copy to drift.
Copy it outward, never edit a derived copy, and never edit brand/ to match
something downstream.
See CONTRIBUTING.md.
MIT OR Apache-2.0 — © VulOS. Magnetite is a VulOS project; source and issues at github.com/vul-os/magnetite. Platform, SDK, and game templates are all dual-licensed.
GitHub · Issues · Decentralization spec
Magnetite is a free, open-source, self-hostable, decentralized Rust game platform.
Built as an alternative to Nakama, PlayFab, and Roblox — without the custody, without the cloud lock-in.
![]()
vulos — open by design







