Skip to content

TD Server

nd-server is the authoritative match host for Nexus Defense. It pairs an axum WebSocket router with the q::nexus_defense_server bevy/rapier2d simulation pulled from the shared q crate, so the wire schema, physics step, and gameplay logic stay in lockstep with the godot client at apps/godot/nexus-defense/.

  • Terminate the /ws WebSocket upgrade and verify the inbound JWT against the Supabase JWT secret (dev-accept fallback when unset).
  • Drive the bevy/rapier2d sim tick on a tokio multi-thread runtime.
  • Fan out per-slot FieldDelta snapshots so each connected player sees their own wave, gold, lives, and building list.
  • Apply ClientFrame input (currently PlaceBuilding) into the sim, gated by the server’s own validity checks.
apps/godot/nexus-defense/server/
├── Cargo.toml # package manifest (path-deps to ../../../../packages/rust/q)
├── Dockerfile # multi-stage chisel build (cargo-chef + sccache)
├── project.json # nx build / run / container targets
├── version.toml # ci-publish version sentinel
└── src/main.rs # tokio entry + graceful shutdown

Local build via nx:

Terminal window
nx run nd-server:build-release

Or directly through cargo (the package name stays nd-server regardless of the on-disk path):

Terminal window
cargo build --release -p nd-server

The release binary lands at dist/target/release/nd-server per the workspace target dir convention. The godot e2e fixture at apps/godot/nexus-defense/tests/helpers/nd_server_fixture.gd picks it up from there automatically.

Terminal window
nx run nd-server:container

Drops a kbve/nd-server:latest + version tag locally. CI uses the ci configuration with sccache + registry buildcache (ghcr.io/kbve/nd-server:buildcache).

The runtime image uses the same ghcr.io/kbve/chisel-ubuntu-axum:24.04.3 chisel base as axum-kbve, so the running surface is the same kernel-friendly minimal Ubuntu with jemalloc pre-staged. Default listen is 0.0.0.0:7878 (override via TD_SERVER_ADDR).

All message types live under packages/rust/q/src/proto. The client reads the same crate (with the nexus-defense feature) so adding a new event variant or input requires editing one file and rebuilding both sides — no spritesheet, manifest, or stub regen step in between.

Alpha. Currently driven entirely by smoke runs against the godot client. Kube deployment (Agones lifecycle + per-match Pod scale-out) follows once the first image lands on ghcr.io/kbve/nd-server.