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.
◆Components
| Component | Tech | Folder (monorepo) | Responsibility |
|---|---|---|---|
| Client | C#, Unity 6 LTS + URP; native Windows/Android/iOS (Low–Ultra) + Web build (Low–Mid) | apps/client-unity | Input, prediction of own movement, interpolation of others, rendering, VFX, audio, HUD (UI Toolkit), patcher hand-off |
| Shared sim | Rust 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 vectors | crates/zoen-sim, crates/zoen-sim-ffi, packages/sim-golden, apps/client-unity/Assets/Zoen/Runtime/Sim | Movement, collision vs walkability grid, cast phases, cooldowns, resource costs, stat math |
| Protocol | schema 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 server | Rust, tokio, data-oriented ECS, rayon | services/zone | One map channel: 20 Hz authoritative tick, AI, combat, loot, AOI snapshots |
| Gateway | Rust, axum, sqlx | services/gateway | Accounts (one-tap Google sign-in, Sign in with Apple on iOS — Accounts and sign-in), sessions, characters, inventory/economy transactions, channel admission |
| Database | PostgreSQL 17 | services/gateway/migrations | Durable state: accounts, characters, items, receipts, rankings |
| Bots | Rust | services/bots | Load tests (1,000 players), soak, replay |
| Game data | JSON + tests | packages/game-data | Every rule and content number (this wiki renders it) |
| Wiki | Next.js 16 static (its Feel Lab previz uses Babylon.js, not the game engine) | apps/wiki | Source 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
- 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. - 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. - The server runs the phases from
skills.jsontimings: windup → active → recovery. During active it sweeps the shape against the spatial hash. Each(castId, targetId, hitIndex)resolves once. - On each hit the server applies damage, ailments, reaction (impact vs weight) and poise, then emits
Hit{castId, target, amount, crit, reaction, element}. - 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.
- 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 isno_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
| Env | Where | Purpose |
|---|---|---|
| dev | owner's Mac: pnpm dev client, cargo run -p zone, Postgres in Docker | daily work |
| bench | same Mac, bots on localhost (or a second machine) | M1/M6 load gates |
| alpha | one VPS (8 vCPU, owner-approved), Cloudflare Pages for the static client | closed alpha (M11) |
◆Versioning
protocolVersionis bumped on every breaking change. Gateway admission rejects mismatched clients with an update prompt.dataVersionis the hash ofpackages/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.
