openfrontio / openfrontio/OpenFrontIO
Moving a large warship fleet kicks you for too_much_data
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 1.4k
- Avg merge
- 17h 43m
- Merged PRs (30d)
- 310
Description
Description
move_warship sends every selected unit id in one intent, and the server caps a single intent at 2000 bytes. A large fleet selection clears that cap in one message, so you get kicked on the first click. No spam involved, and nowhere near the 5MB total budget.
The message is a well formed, schema valid intent. It is just long:
json
{"type":"intent","intent":{"type":"move_warship","unitIds":[10000,10003,10006, ... 400 ids ... ,11197],"tile":250000}}
That is 2476 bytes, and ClientMsgRateLimiter.ts#L31 returns "kick" for anything over 2000. The comment above it says intents should never be that large so the client is assumed malicious. The cap looks right to me, the client is just breaking the invariant it depends on.
To Reproduce
Build up a large warship fleet in a long game.
Press F
Left click any water tile.
Immediate disconnect. The modal reads Error: kick_reason.too_much_data.
Box select over a dense fleet does the same thing. Unit ids come from one global counter shared by every unit in the match, so they get wider as a game runs long and fewer ships fit per intent: the ceiling is 444 warships with 3 digit ids, 384 at 4 digits, 320 at 5 and 274 at 6. So it gets worse exactly when fleets get big, which fits the 400 to 450 range people have been reporting.
Expected behavior
A legitimate fleet order should go through, or be split client side into intents that fit. It should not end the session through the path meant for malicious clients.
Additional context
unitIds is unbounded in Schemas.ts#L535-L539, WarshipSelectionController.ts#L190 emits the entire selection in one event, and nothing chunks it before Transport.sendIntent. The server also measures the whole frame, so the {"type":"intent","intent":{...}} wrapper counts toward the 2000.
Smallest fix I can see is batching unitIds under the cap in onMoveWarshipEvent and sending several intents, with batches kept large enough to stay under the 10 per second limit. Bounding the array in the schema would work too.
One thing that makes this hard to recognise when it happens: kick_reason.too_much_data has no entry in resources/lang/en.json, and showErrorModal falls back to the key when a translation is missing, which is why the modal shows the raw string. The 5MB kick and the oversized intent kick also share that one reason. Happy to open those separately if you would rather keep this issue to the fleet bug.
Contributor guide
Assessment
This issue has not been assessed yet.