Your GameScript owns the fun
Movement rules, hit outcomes, damage and death, cooldowns and stamina, spawns, loot, win conditions, replicated gameplay variables, and any custom message your game invents.

Players propose. Your rulebook decides. Everyone lives in the same world.
GameScript is the part of Citadel where you write what your game is. Movement, shooting, damage, loot, cooldowns, who-can-do-what — the rules live in a script you write in Lua, Python, or JavaScript, and Citadel makes that script the final word. Players can ask for anything. Only your rules decide what actually happens.
Multiplayer usually forces a bad trade:
GameScript refuses the trade. You get the easy part (write rules in a language you already like, and hot-reload them) and the safe part (the server is the authority, so cheating is off the table). That combination is Citadel’s signature move.
Your GameScript owns the fun
Movement rules, hit outcomes, damage and death, cooldowns and stamina, spawns, loot, win conditions, replicated gameplay variables, and any custom message your game invents.
Citadel owns the plumbing
Connections, authentication, matches and matchmaking, ownership and scope, size/rate limits, replay protection, persistence, and actually delivering messages to the right players.
You write the what. Citadel handles the how and the is-this-even-allowed.
Pick the language you’re happiest in. The one handler — citadel.on_input — looks
the same in all three shipped runtimes. Here it is turning away a classic speed
hack and letting everything else through:
citadel.on_input(function(event) if event.kind == "transform_input" and event.move_velocity.x > 2000 then return { decision = "reject", reason_code = 1 } -- nice try, speedster end return nil -- accept: the server integrates the move for realend)citadel.on_input((event) => { if (event.kind === "transform_input" && event.move_velocity[0] > 2000) { return { decision: "reject", reason_code: 1 }; // nice try, speedster } return undefined; // accept});import citadel
def on_input(event): if event["kind"] == "transform_input" and event["move_velocity"][0] > 2000: return {"decision": "reject", "reason_code": 1} # nice try, speedster return None # accept
citadel.on_input(on_input)Want the server to own hit detection but let you decide the damage? Fire a
citadel.rewind_query for lag-compensated hits, then choose the consequence — the
authoritative gameplay bridge reference
has the full surface.
Because the server is the authority, the usual exploits just… don’t work:
Prototype in minutes
Start in relay mode (the default): open the demo, move in one tab, watch the other tab update. No rules to write yet — just see multiplayer work.
Flip on authority when it counts
Set runtime.require_script = true and every protected action now flows
through your script. Same game, now cheat-proof.