Game guide · source of truth
Tech

Architecture

Client, servers, shared simulation, protocol and persistence — how the pieces of Zoen fit, which repo folder owns each, and the rules that keep it fair and fast.

System diagram
Browser clientBabylon.js · WebGPU/WebGL2Mobile (PWA / Capacitor)same build, touch HUDzoen-sim (WASM)prediction = server codeZone servers (Rust)20 Hz · AOI · 1,000/channelzoen-sim (native)movement · casts · statsGateway (axum)accounts · items · admissionPostgreSQL 17durable state + receiptsBots (Rust)1,000-player load testswss snapshots/inputstokens · txloadpackages/game-data (JSON) feeds client, servers and this wiki

◆Components

ComponentTechFolder (monorepo)Responsibility
ClientC#, Unity 6 LTS + URP; native Windows/Android/iOS (Low–Ultra) + Web build (Low–Mid)apps/client-unityInput, prediction of own movement, interpolation of others, rendering, VFX, audio, HUD (UI Toolkit), patcher hand-off
Shared simRust crate (servers); in the client the same crate as a C ABI plugin if spike M0-11 passes, else a C# prediction subset; one set of golden vectorscrates/zoen-sim, crates/zoen-sim-ffi, packages/sim-golden, apps/client-unity/Assets/Zoen/Runtime/SimMovement, collision vs walkability grid, cast phases, cooldowns, resource costs, stat math
Protocolschema zoen.schema.json → codegen (Rust + C#), bit-packed binary over WebSocket (ADR-003)packages/protocol (M1-03)Message types, bit-packing, versioning, golden vectors
Zone serverRust, tokio, data-oriented ECS, rayonservices/zoneOne map channel: 20 Hz authoritative tick, AI, combat, loot, AOI snapshots
GatewayRust, axum, sqlxservices/gatewayAccounts (one-tap Google sign-in, Sign in with Apple on iOS — Accounts and sign-in), sessions, characters, inventory/economy transactions, channel admission
DatabasePostgreSQL 17services/gateway/migrationsDurable state: accounts, characters, items, receipts, rankings
BotsRustservices/botsLoad tests (1,000 players), soak, replay
Game dataJSON + testspackages/game-dataEvery rule and content number (this wiki renders it)
WikiNext.js 16 static (its Feel Lab previz uses Babylon.js, not the game engine)apps/wikiSource of truth for humans + agents

ADR-029 (2026-10-06): the client side of the shared sim is decided by spike M0-11. If the Rust crate runs as a plugin on every target, the client uses it and the C# subset is not written; otherwise the row above stands. No C# prediction code is written before the spike.

Engine-free C# core (M0-09, ADR-029): gameplay rules that stay in C# live in packages/core-cs/Zoen.Core (netstandard2.1, C# 9 like Unity 6, nullable, warnings as errors, deterministic, no dependencies) and are tested with xunit by pnpm core:test. Unity compiles the same folder as the local package com.zoen.core (Packages/manifest.json → file:../../../packages/core-cs/Zoen.Core, Zoen.Core.asmdef with noEngineReferences); the project files sit one level up and build outputs go to packages/core-cs/.artifacts/, so Unity never imports them. First module: the tier selection rules (TierRules, TierPolicy, TierRulesReader, MiniJson).

◆Data flow for one attack

  1. The player presses Q. The client sends Cast{seq, skillId, aim, targetId} and immediately plays the predicted windup (animation start + windup VFX/SFX). It does not show damage.
  2. The zone server validates it at the next tick (alive, not controlled, cooldown, mana, range, line of sight, weapon mode). If rejected, it sends CastRejected{seq, reason} and the client plays a 0.15 s fizzle.
  3. The server runs the phases from skills.json timings: windup → active → recovery. During active it sweeps the shape against the spatial hash. Each (castId, targetId, hitIndex) resolves once.
  4. On each hit the server applies damage, ailments, reaction (impact vs weight) and poise, then emits Hit{castId, target, amount, crit, reaction, element}.
  5. Clients in AOI receive the event. The target plays its reaction clip, numbers pop, and local hitstop/shake play for the attacker and target only.
  6. Death, loot, XP and quest progress are all server events. Item pickup becomes a durable gateway transaction with a receipt.

◆Non-negotiable rules

  • Server authority. Clients never send damage, positions without inputs, or loot claims.
  • Animation never applies damage. Presentation follows server events, and timing contracts keep them in sync (±1 frame).
  • Same command path for manual play, Smart Chain, Auto-Hunt, Auto-Quest and offline actors.
  • Determinism in zoen-sim. Fixed tick, integer fixed-point only (millimetres, ticks), no floats, wall-clock reads, I/O or threads inside the step (the crate is no_std), no heap allocation per tick, and an FNV-1a state hash after every tick. Contract: sim.json; golden vectors: packages/sim-golden/vectors.json (pnpm sim:golden, pnpm sim:test).
  • One owner per entity. An entity lives in exactly one zone process. Transfers between channels use explicit handoff with a lease.
  • Telegraphs are gameplay. They are replicated as events and rendered on every tier.

◆Repository layout (target)

zoen/
  apps/wiki            (exists)        apps/client-unity  (M0-04)
  packages/game-data   (exists)        packages/protocol  (exists)
  Cargo.toml           (M0-03, Cargo workspace for crates/ and services/)
  crates/zoen-sim      (M0-03)         services/zone      (M1-03: crate zoen-zone, tick + scheduler + spatial hash + AOI)
  crates/zoen-sim-tools (M0-03)        packages/sim-golden (M0-03)
  crates/zoen-data     (M1-03, game-data reader for the Rust tools and servers; netcode.json, sim.json)
  crates/zoen-sim-ffi  (M0-11 spike, ADR-029: C ABI over the step, include/zoen_sim.h; built into Unity's Plugins/ by tools/sim/build-plugin.mjs)
  packages/core-cs     (M0-09, engine-free C# core Zoen.Core + xunit tests; Unity loads Zoen.Core/ as the local package com.zoen.core)
  services/gateway     (M6)            services/bots      (M1-04)
  tools/               (exists)        logs/              (exists)

◆Environments

EnvWherePurpose
devowner's Mac: pnpm dev client, cargo run -p zone, Postgres in Dockerdaily work
benchsame Mac, bots on localhost (or a second machine)M1/M6 load gates
alphaone VPS (8 vCPU, owner-approved), Cloudflare Pages for the static clientclosed alpha (M11)

◆Versioning

  • protocolVersion is bumped on every breaking change. Gateway admission rejects mismatched clients with an update prompt.
  • dataVersion is the hash of packages/game-data/data/*.json. Servers refuse to start when it differs from the build's client data.
  • Database migrations are forward-only, and every one has a tested rollback note.

Source: zoen/docs/tech/ARCHITECTURE.md · 935 words · edit the Markdown, not this page.