ANTI-CHEAT / BETA

01 / TRUSTED MOVEMENT

Trust the transition.

A delayed player should still move. An impossible move should not. Build a movement validator that can tell the difference, then test what it gets wrong.

JavaScript familiarity25–40 min practice estimateBrowser only
01

Draw the trust boundary

A client is the program on the player’s machine. A server is the program that decides the shared game state. A modified client can invent a position or score. An authoritative server accepts requests, checks its own rules and computes the resulting state. A validator is the function that decides whether a requested change satisfies those rules.

CLIENT · CLAIMSseq: 1
dtMs: 100
dx: +0.6
“Apply my next move.”
→
SERVER · OWNSspeed: 6 m/s
position: 1 m
clock & last sequence
Validate, then update position.
A request crosses the boundary; authority stays with the server.

Our arena is a 12 m corridor. The server owns the starting position, 1 m, and the speed limit, 6 m/s. A command requests a signed displacement, dx, during dtMs milliseconds. At most 100 ms belongs in one command. For 100 ms, legal displacement is at most 6 × 100 / 1000 = 0.6 m in either direction.

A sequence number, seq, identifies a command’s order. Starting after sequence 0, this model expects 1, then 2. A duplicate must not move the player again. An early sequence 3 is rejected for retry after 2 arrives. That is a transport problem, not proof of cheating.

The threat model states what we protect and what an adversary controls. Here we protect legal movement and one application per command. The client controls seq, dtMs and dx. The test harness supplies server receipt time, at. In a real server, receipt time comes from a server clock, never from the request body.

YOUR DECISION · BOUNDARY

The client sends {dx: 0.4, score: 999}.

Your server has no validated scoring event. Which update respects this boundary?

02

Account for time, not just distance

Checking each command’s distance is necessary but insufficient. A client could submit ten legal-looking 100 ms moves at once. The server needs a time budget: movement cannot consume time faster than the server grants it.

Latency is delivery delay. Jitter is variation in that delay. A legal client may generate commands regularly while the network delivers several together. Charging each command only against the gap since the last receipt rejects that legal burst: the second command’s arrival gap is zero.

Instead, keep a time bank in server state. Add elapsed server time, cap the balance, then charge the command’s duration. The cap limits how much delayed movement can happen together. A small cap rejects longer legal delays; a large cap permits a larger catch-up burst. Neither setting reveals intent.

available = min(capacity, credit + at − lastAcceptedAt)
accept only if dtMs ≤ available
nextCredit = available − dtMs

All time values are milliseconds. Rejected commands leave the entire accepted state unchanged. On the next attempt, elapsed time is recomputed from that unchanged state; a rejection does not mint extra credit.

Worked example · two delayed commands

This is an author demonstration, separate from your attempts. Start with zero credit at time 0 and a 400 ms cap. Two 100 ms commands arrive at time 200. The first has 200 ms available and leaves 100 ms. The second has a zero arrival gap but spends the remaining 100 ms. Both moves fit the speed limit, so the position advances from 1 m to 2.2 m.

YOUR PREDICTION · TIME BANK

Four commands arrive at time 400.

Start at time 0 with zero credit. Each command needs 100 ms and requests +0.6 m. All sequences are valid. With a 200 ms cap, how many commands will be accepted?

03

Assemble your validator

Choose the checks and timing policy that run in your component. The fixed scaffold always keeps numbers finite and the player inside the corridor; it cannot safely display an infinite position. The optional schema check also requires a positive integer sequence and a duration from 1 to 100 ms. Inspect the full generated code below.

Your contract allows up to 400 ms of delayed movement together: four full 100 ms commands, or five 80 ms commands. After any idle interval, total accepted duration at the same receipt time must not exceed 400 ms. Accept ordinary movement and reversals, reject impossible displacement, repeated commands and unearned time, and keep state unchanged after rejection. A 400 ms legal burst is intentionally longer than the prediction exercise’s cap.

YOUR COMPONENT · CONFIGURATION

Read the failures before repairing

A false positive here is a legal labeled action that your validator rejects. A false negative is an invalid labeled action it accepts. These are fixture labels, not diagnoses of real people. Out-of-order deliveries are counted separately. The duplicate fixture is labelled as an invalid second application and remains in the invalid-action counts. Neither label establishes malicious intent.

The suite counts actions, not players. One noisy session can dominate those counts. A staff engineer also needs per-session outcomes, realistic held-out traces, CPU cost, observation-only rollout and an appeal path before any enforcement. This small synthetic suite supplies none of that production evidence.

If a check fails, inspect its first differing action and its time bank. Then change one policy decision and submit again. Your first attempt remains in the notebook. Passing means your component matches this published contract, not that the system is secure.

04

Practice with new values

Practise the same rule without the fixture stepper. These new values test calculation and application, not transfer to an unseen mechanism. A smaller arena allows 4 m/s and commands up to 80 ms. The server promises to accommodate three full commands delivered together, but no more than that after any idle interval. Start with zero credit at time 0.

Enter time answers as exact milliseconds. Use a decimal point for metres; distance answers allow less than 0.000000001 m of numerical error.

ADDITIONAL PRACTICE · FIRST ATTEMPT RETAINED

05

From one rule to a system

This first component separates client claims from accepted state. It omits authentication, a real network, prediction and reconciliation, collision physics, clocks across reconnects, denial of service and adversarial adaptation. A browser cannot protect secrets from its own user.

Continue in the runnable Courier Lab software core for server-owned actions, input order, owned-file checks, replay, detector evaluation, retention and recovery. This earlier browser model is optional. It uses displacement-and-duration requests and consecutive sequence numbers; Courier Lab uses direction intents, strictly newer sequences that may have gaps, and expiry measured in processed ticks. Carry across the trust-boundary reasoning rather than copying this model’s protocol. Native investigation remains outside the required core. Start the runnable Courier Lab course →

Sources and optional reading

The arena, rules, code and exercises are original. Glenn Fiedler’s networking explanation supplies background on server authority and latency; Riot’s Fog of War account is a later case study in reducing information exposed to clients. Neither source specifies this time-bank policy.

Optional book context: Nick Cano, Game Hacking (2016), for threat concepts; Dang, Gazet and Bachaalany, Practical Reverse Engineering (2014), for later machine-level work; Ligh et al., The Art of Memory Forensics (2014), for evidence limits. These are reading pointers, not copied exercises or requirements. Version-specific tools need separate validation.

Your notebook

Attempts keep original wording, configuration, results, written explanations and recorded help. They stay on this browser origin. Export before moving computers or clearing storage. Text explanations are retained for review; no keyword checker claims to grade their quality.