-
Notifications
You must be signed in to change notification settings - Fork 1
Tutorial Run a Server
Monworks games can meet online. A server is a thin lobby: it checks who may enter, authenticates handles, shows who's here, introduces matched players — and nothing more. Battles and trades always run between the two players' own copies of the game, exactly like a direct cable link; when two machines can't reach each other directly, the server relays their traffic without ever reading it. There is no account system, no central service, no telemetry — a server is something you run, for your friends or your community.
From the toolkit checkout:
python tools/server.py # port 17226, data in user://
python tools/server.py --port 17300 --serverdata C:/srv/mygame --project build/mygameThat's the same engine binary you play with, in a headless server mode. It binds a
project — by default the one in game/, or whatever --project points at — and
prints who registers, enters, and leaves as it runs.
A connecting player is admitted only if their copy of the game is byte-identical to the server's — same game version, same engine build, same content hashes. Anyone whose data differs is refused with a message naming exactly what differed, before they can enter any session. You never configure this; it is the same handshake two directly-linked games already use.
Two consequences worth knowing:
- A public server must bind your own original (or CC0) content. Extracted Kanto content is personal-use only — a Kanto-bound server structurally admits only people who built the same extraction themselves, so it cannot serve a public audience, and must not.
- Cheated content can't get in the door. (Play-level fairness comes with replay verification on the ladder, a later milestone.)
{
"invites": ["cinnabar", "fuchsia"],
"motd": "welcome to the meadow",
"name": "meadowbrook league",
"registration": "invite",
"strike_limit": 3,
"verify": true
}-
registration—open(anyone may enroll a handle),invite(enrolling needs one of the codes ininvites), orclosed(no new handles). Anything else refuses to boot, loudly — a typo must never silently open your door. -
motd— shown to players on entry. -
verify— turn on replay verification (below). Off by default: a casual server records nothing at all. -
strike_limit— suspend a handle once it accumulates this many strikes from failed verifications (0, the default, records strikes without enforcing). - Everything is optional; no file at all means an open server named "monworks server".
With "verify": true, every match your server introduces is verified: both
games independently submit a compact replay at battle end (the seed, both teams as
exchanged, every action in order, and a fingerprint of the entire battle). When the
two submissions agree — every honest match — the result is recorded instantly, no
simulation, no cost. When they disagree, your server replays each side's story on
the very engine it runs, because the server IS the game: the version whose replay
matches its own claims stands, and the other is voided and struck. A hacked
client that sends an illegal move mid-battle fares no better: the honest player's
game refuses it on the spot ("ILLEGAL action! Battle voided.") and the cheater's own
log convicts them in the replay. Strikes live in players.json; set strike_limit
to suspend repeat offenders at the door. Replays and verdicts are plain JSON under
<serverdata>/replays/ — keep or delete them as you please.
What this buys, plainly: a cheat cannot beat an honest player on a verified server. The worst any tampering achieves is a voided match — the same as never having played — and a strike on the way out.
A verified server keeps a ladder: plain Elo over verified results (a voided
match never moves anyone), written to <serverdata>/ladder_<format>.json where
anyone can read it. Tune it in server.json — elo_k (default 32),
elo_provisional_n and elo_provisional_mult (newcomers' first matches move
double by default) — and end a season whenever you like by archiving the file.
Matches run under a format you author in <serverdata>/formats.json:
{"lc30": {"name": "LEVEL 30", "clauses": ["sleep", "freeze"],
"level_cap": 30, "species_clause": true, "party_max": 6}}Pick the active one with "format": "lc30" in server.json. A STANDARD format
(all four clauses, level 100) is built in, so a fresh server just works. Formats
enforce two kinds of rules:
- Admission — level cap, species clause, party size. Checked at the table by both games before anyone battles (the match refuses to seat, naming the rule), and again by your server when results come in — so even two colluding hacked clients can't ladder an illegal team.
-
In-battle clauses —
sleep,freeze,evasion,ohko, enforced inside the battle itself: a clause-breaking move is announced and simply fails, on both players' screens identically. The active set is cryptographically agreed by both games before turn 1 — a tampered client that disagrees about the rules can't get a match started at all.
Every format field is checked when the server boots, and a typo refuses loudly rather than quietly dropping a rule.
Registered players live in <serverdata>/players.json — a plain canonical-JSON
table of handle → token hash. There is no database and no personal data: a player
is a handle they chose plus a secret token their game stores. Deleting the file
resets enrollment (players' games re-enroll automatically on their next visit).
- A handle (2–16 letters/digits/underscore) is registered once per server and authenticated by a server-minted token the player's game keeps. It is not the in-game character name — that stays cosmetic.
- One live connection per handle. A second connection on the same handle is refused, so a leaked token can't silently displace its owner's session.
- Each save-file character on a player's machine enrolls as its own handle — a character is a person as far as the server cares.
Once their game points at your server (see Join a Server), the Cable Club attendant grows an ONLINE option: a lobby of who's open, challenges either way, and then the normal link flow — Trade Center or Colosseum — between the two players directly. If a direct connection can't form (strict NATs), the session runs through your server's content-blind relay instead; players notice nothing, and a mid-battle connection blip reconnects and resumes on its own.
Players can post monsters for trade and claim each other's listings even when the
other side is offline — your server holds only a listing's public card and tells
both parties the moment they're online together; the trade itself is always the
two players' own Trade Center ceremony, and the server never holds a monster.
Listings live in <serverdata>/board.json, expire after board_ttl_s in
server.json (default a week), cap at three per player, and come down
automatically when a trade completes.
- Default port: 17226 (UDP). Friends outside your network need it forwarded or the machine otherwise reachable; matched sessions also use the game's own link ports (17225 and a small window above it) when connecting directly.
- The server is quiet and tiny — it forwards small JSON frames and holds a lobby list. Any machine that runs the game can run it.
- Stop it with Ctrl+C. State is just the two JSON files in
<serverdata>.
The full protocol and design live in docs/engine/server.md.
Monworks: MIT code · CC0 samples · everything pictured is original content. The faithful Pokémon Red build derives from your own local pokered clone and is never distributed. Pokémon is © Nintendo/Creatures/GAME FREAK; this is an unaffiliated fan engineering project.
Play
Create
Tutorials
- Writing Text
- Battle Backdrops
- Colors and Palettes
- Display Filters and Shaders
- Widescreen and Scaling
- Quality-of-Life Knobs
- Overlays (Recipes)
- Abilities & Battle Hooks
- Modern Individuals
- Per-Individual Fields
- Field Effects
- The Modern Ruleset
- Per-Save Rulesets
- Six Stats
- The Clock
- Conditioned Bindings
- Day and Night
- Sprite Variants
- Overworld Spawns
- Language Packs
- Set-Piece Battles & Narration
- Run a Server
- Join a Server
Elsewhere