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:
@@ -134,15 +134,25 @@ just slash-separated notes in a step (`F4/A4/C5`).
|
|||||||
Both run off one scheduler that queues notes 200 ms ahead, so the groove does
|
Both run off one scheduler that queues notes 200 ms ahead, so the groove does
|
||||||
not stutter when React re-renders. `MUSIC` and `SOUND` toggle independently.
|
not stutter when React re-renders. `MUSIC` and `SOUND` toggle independently.
|
||||||
|
|
||||||
## Two people, two browsers
|
## How many people can play
|
||||||
|
|
||||||
The game itself is a static bundle and lives entirely in the page. Two people
|
As many as you like. There is no limit, and no meaningful sense in which
|
||||||
on two machines, two browsers or two profiles play completely independent
|
players share anything while they are playing.
|
||||||
games — different seeds, different weather, different books. Nothing about a
|
|
||||||
season is shared or synchronised.
|
|
||||||
|
|
||||||
The only thing they have in common is the leaderboard they both post to at the
|
The game holds no state on the server — it is a static bundle, and a season
|
||||||
end. The skin preference is the one thing still kept in the browser.
|
lives entirely in the page. Two people, or two hundred, on any mix of
|
||||||
|
machines, browsers and profiles get completely independent games: different
|
||||||
|
seeds, different weather, different books. The server never learns a game is
|
||||||
|
happening; it only ever sees a finished score being posted at the end.
|
||||||
|
|
||||||
|
The one real cap is **four players to a game**, and that is a keyboard
|
||||||
|
limitation rather than a technical one — they are taking turns at the same
|
||||||
|
stand, hot-seat style. Nothing stops four separate people playing four
|
||||||
|
separate games at the same moment.
|
||||||
|
|
||||||
|
Everyone posts to the same leaderboard, and everyone reads the same one. That
|
||||||
|
is the only thing players have in common. The skin preference is the one thing
|
||||||
|
still kept in the browser.
|
||||||
|
|
||||||
## Running it anywhere
|
## Running it anywhere
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -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
|
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
|
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
|
`$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
|
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
|
**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.
|
anyone willing to craft a request can post a plausible score under any name.
|
||||||
|
|||||||
+8
-1
@@ -53,7 +53,14 @@ interface Bucket {
|
|||||||
}
|
}
|
||||||
const posts = new Map<string, Bucket>()
|
const posts = new Map<string, Bucket>()
|
||||||
const POST_WINDOW_MS = 60 * 60 * 1000
|
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 {
|
function overPostLimit(ip: string): boolean {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|||||||
Reference in New Issue
Block a user