Skip to content

Getting started: your first server-side game rule

Citadel is for writing the rules of your game, not for assembling a Rust build environment. Start with a published server release: no Git checkout, Rust, Cargo, or source compilation is required.

In a few minutes you will run a local server and change the Lua game logic that it owns. Save the file and Citadel reloads it live.

You need a 64-bit Windows or Linux machine and a text editor. Download the matching Citadel server ZIP from GitHub Releases. The archive name tells you which one to choose:

Host Archive
Windows 64-bit citadel-windows-x86_64-v{version}.zip
Linux x86_64 / AMD64 citadel-linux-x86_64-musl-v{version}.zip
Linux ARM64 / AArch64 citadel-linux-aarch64-musl-v{version}.zip

Extract the ZIP somewhere you can edit. The included README has checksum verification and platform details if you need them.

Open a terminal in the extracted folder and run:

Terminal window
# Windows PowerShell
.\citadel.exe
Terminal window
# Linux
./citadel

On first run, Citadel creates data.sqlite, applies migrations, loads scripts/main.lua, and starts its local HTTP and realtime listeners. Keep this terminal open.

Open http://127.0.0.1:7350/dashboard in a browser. That is your local admin dashboard; seeing it confirms your game server is alive.

Open scripts/main.lua in your editor. It is your server-side gameplay file. It already contains lifecycle hooks, a message handler, an RPC, and a server tick. Citadel, not a player’s client, runs this code.

Find this line near the top:

citadel.log("game logic loaded", "info")

Change the message to something that names your game, then save the file:

citadel.log("Moonlit Arena rules loaded", "info")

Watch the terminal. Citadel reloads a valid Lua edit without restarting the server. If an edit has a syntax error, it keeps the previous working rules instead of taking your game down.

That is the core loop: write server gameplay → save → Citadel applies the new rules.

The shipped script includes a position-message relay so a client can share movement. The server owns the decision about what is sent. For example, its message handler receives the player’s request, tags it with the real sender, and broadcasts the accepted result:

citadel.on_message(KIND_POSITION, function(ctx, body)
local tagged = string.pack(">I8", ctx.sender) .. body
citadel.broadcast(KIND_PEER_POSITION, tagged, true)
end)

As your game grows, this is where you validate moves, reject impossible attacks, update monster health, award rewards, and send the state the clients should render. The client asks; your server game logic decides.