Skip to content

Configuration ​

Server settings live in wrangler.jsonc, per environment. Games and builds live in games/<id>/game.json. Nothing is configured at runtime: change a file, run the tests, deploy.

Variables ​

Set in vars (top level for local dev, env.staging.vars and env.production.vars for deploys). Values are strings.

VariableDefaultWhat it does
VERIFY_INSTRUCTIONS1000000Lua instructions the verifier may spend per request, about 9 ms of CPU. Unverified input queues and drains in later requests and alarms. Must be an integer of at least 10000. Raise it only after measuring CPU in staging.
MAX_RUNS_PER_DAY750 (staging 500)New ranked runs admitted per UTC day across all games. Runs in progress always continue. Set it from measured rows written per run and the share of the account's free quota you can spend. 0 pauses ranked play (capacity). A missing or malformed value fails every ranked start with 500 internal.
MAX_RUNS_PER_ADDRESS75 (staging 50)Share of MAX_RUNS_PER_DAY one network address (IPv4 address or IPv6 /64) may start per UTC day, whatever the number of guests. Stored as a keyed hash (ADDRESS_KEY), pruned after a day. Raise it if many legitimate players share one address. Requests without a cf-connecting-ip header (a self-hosted wrangler dev reached directly) all share one address, and off Cloudflare a client can set that header itself. At least 1; a missing or malformed value fails every ranked start with 500 internal.
ALLOWED_ORIGINSemptyComma-separated browser origins allowed to call the API. Empty allows none; native clients send no origin.

Secrets ​

SecretWhat it does
ADMIN_TOKENBearer token for /v1/admin/*. At least 32 characters; admin routes are disabled without it. Set with wrangler secret put ADMIN_TOKEN --env <env>; locally in .dev.vars.
ADDRESS_KEYHMAC key for the per-address run share; the stored address is HMAC-SHA256 of the UTC day and the address. At least 32 characters. Ranked run starts fail while it is missing. Set with wrangler secret put ADDRESS_KEY --env <env>; locally in .dev.vars.

Bindings ​

The names are fixed; the code reads them.

BindingKindPurpose
PLAYERSDurable Object, class PlayerSession, SQLite storageOne object per game and player: credential, runs, verifier, achievements, publication outbox.
DBD1Players, accepted results, personal bests, disabled rulesets, daily admissions.
IP_LIMITERRate limit, 300 per 60 sEvery route, per client address (IPv6 per /64).
GUEST_LIMITERRate limit, 5 per 60 sGuest creation, per address.
API_LIMITERRate limit, 30 per 60 sRun starts, per player, after the credential check.

Each environment's rate limits need their own namespace_id, an integer unique in your account. Change limit and period to tune them; the period must be 10 or 60 seconds.

Cron ​

triggers.crons runs the daily cleanup (17 3 * * *, UTC): it prunes accepted results older than 90 days, 500 at a time, retaining personal bests and the top 100 named entries per game/board/country/runtime/provider tuple. Operations lists everything kept and for how long.

Fixed limits ​

These are constants in src/session.ts and src/index.ts, not settings. Changing them changes what counts as legal play, so they are part of the anti-cheat contract.

LimitValue
Tick rate60 Hz
Ticks per input chunk120 max, 60 expected
Late input window90 ticks (1.5 s)
Input ahead of real time6 ticks + 0.1%, at most 60
Pause15 min each, 60 min per run
Run lifetime24 h
Leaderboard page25 rows, pages 0-3
Leaderboard cache30 s
Raw input retention7 days after the run ends

Games and builds ​

games/<id>/game.json registers a game:

json
{
  "id": "sneak",
  "name": "Sneak",
  "source": "Y:/projs/walangstudio/sneak",
  "entry": "script.ranked",
  "boards": { "classic": ["7411b66ea8266106"] },
  "builds": [
    { "id": "sneak-web-direct-0.2.0", "ruleset": "7411b66ea8266106", "runtime": "web", "channel": "direct" }
  ]
}
FieldMeaning
idLowercase letters, digits and _, up to 32.
sourcePath to the game checkout the packer reads. Override with SCOREOSIS_SRC_<ID>.
entryThe ranked module, as require names it.
boardsBoard name to the rulesets ranked on it. Rulesets with comparable scores share a board; a new board starts fresh.
builds[].id<game>-<runtime>-<channel>-<version>. The registry refuses any other shape.
builds[].runtimeweb, windows, android, ios, macos, linux. Leaderboards rank each runtime separately.
builds[].channelitch_io, google_play, steam, direct, unknown.

Rulesets are packed with node tools/pack-game.mjs <id>, which writes games/<id>/rulesets/<ruleset>.json and regenerates games/rulesets.generated.ts. Never edit either by hand. See Adding a game.

Game client settings ​

The Lua client library takes the server URL and build id from the game. The Defold games read them from game.project (or a --settings override at build time):

ini
[scoreosis]
url = https://scores.example.com
channel = direct

An empty url turns ranked play off; the game plays locally. The build id is assembled from the game id, the platform, channel and project.version, and must match a registered build.

MIT licensed