Say plainly that there is no player limit, and stop one classroom hitting it

The README heading read 'Two people, two browsers', which sounds like a
cap. It is not one: the server holds no game state at all, so any number
of people play independent games and the server only ever sees a
finished score. The only real cap is four to a game, and that is a
keyboard sharing the same stand rather than anything technical.

Checking that turned up a case where it would not have been true. A
classroom, an office or a household all arrive from one address, and the
submission limit of thirty an hour was low enough that a class finishing
together would have started losing scores to a 429. Raised to 120 and
verified with a burst of 130: the first 120 land, the rest are refused.
What actually keeps rubbish off the board is the plausibility check, not
this limit.
This commit is contained in:
2026-09-08 17:58:40 -07:00
parent f2e084ef7a
commit 87474ad5ec
3 changed files with 28 additions and 10 deletions
+3 -2
View File
@@ -25,9 +25,10 @@ Everything in a request is treated as hostile. Names are forced to a printable
uppercase subset and cut to 12 characters. Every number must be an integer in
range, and a score is refused if it could not have happened: assets above
`$2.00 + $25 a day`, or glasses above 400 a day, are rejected as impossible for
the days claimed. Bodies are capped at 4 KB and submissions at 30 an hour per
the days claimed. Bodies are capped at 4 KB and submissions at 120 an hour per
address, which is why `TRUST_PROXY=1` matters behind nginx — otherwise every
request looks like it came from the proxy.
request looks like it came from the proxy and one busy classroom would lock
everyone else out.
**It cannot prove a score is real.** There are no accounts and no signing, so
anyone willing to craft a request can post a plausible score under any name.
+8 -1
View File
@@ -53,7 +53,14 @@ interface Bucket {
}
const posts = new Map<string, Bucket>()
const POST_WINDOW_MS = 60 * 60 * 1000
const POST_LIMIT = 30
/*
* Per address, per hour. Generous on purpose: a classroom, an office or a
* household all arrive from one address, and thirty was low enough that a
* class finishing a season together would have started losing scores to a
* 429. What actually keeps rubbish off the board is the plausibility check
* in validate.ts, not this - this only stops the database being hammered.
*/
const POST_LIMIT = 120
function overPostLimit(ip: string): boolean {
const now = Date.now()