Skip to content

Anti-cheat / server-authoritative validation new

An opt-in, server-side validation layer (design 02 §18) that runs at the authority's apply step and rejects impossible / illegitimate client submissions before they touch authoritative state: out-of-bounds or out-of-window inputs, input/RPC floods, speed/teleport hacks, writes/RPCs to non-owned objects, lag-comp rewind abuse, rapid-fire / aim-snap, and (for shared authority) a tampered per-tick state hash.

Default-off and additive

The layer is default-off: a runner with no anti-cheat config behaves byte-for-byte as before. It adds the config + the on_violation callback + the lattice_ac_* functions and changes nothing else. It is never on the wire codec / serialization hot path — it only validates already-decoded submissions, and every check is integer/bounded, so a verdict is bit-identical on Linux and Windows/wine.

lattice_ac_configure

LATTICE_API lattice_result lattice_ac_configure(lattice_runner* r, const lattice_ac_config* cfg);

Install (or replace) the runner's anti-cheat configuration. Role: has effect only on an authority (SERVER/HOST/SHARED_HOST); a client stores the config but does not gate (the authority does). With cfg->enabled == 0 (or cfg == NULL) the layer is off (the default). With cfg->enabled != 0, the authority validates each client-submitted input / RPC / event / move / fire at its apply step, fires on_violation on a rejection, and kicks at kick_threshold. Reconfigurable at any time.

Returns: LATTICE_OK, or LATTICE_ERR_INVALID_ARG for a NULL runner.

lattice_ac_config

The all-zero value DISABLES the whole layer. enabled 0 makes every gate a no-op; an individual bound left 0 / a *_on flag left 0 disables just that gate. Movement bounds are in the game's integer world units per tick.

typedef struct {
    int      enabled;                 /* 0 => the whole anti-cheat layer is off (default)            */

    /* 1. Input validation. */
    int32_t  input_min;               /* min legal per-axis input intent (when input_bounds_on)      */
    int32_t  input_max;               /* max legal per-axis input intent                             */
    int      input_bounds_on;         /* enable the per-input value-bounds check                     */
    uint32_t max_input_future;        /* reject an input tick > now + this (0 => off)                */
    uint32_t max_input_age;           /* reject an input tick < now - this (0 => off)                */
    uint32_t max_inputs_per_tick;     /* per-client input-rate cap per authority tick (0 => off)     */

    /* 2. Movement / physics validation (per owned-object position delta, per tick). */
    int64_t  max_speed_per_tick;      /* max |delta| world-units/tick (Chebyshev); 0 => off          */
    int64_t  max_accel_per_tick;      /* max change in per-tick delta vs last tick; 0 => off         */
    int64_t  max_teleport_dist;       /* hard single-tick jump cap (distance); 0 => off              */

    /* 3. RPC / event firewall: token-bucket rate limit + argument bounds. */
    uint32_t rpc_bucket_capacity;     /* max burst tokens a client may hold (0 => rate off)          */
    uint32_t rpc_refill_per_tick;     /* tokens refilled each authority tick                         */
    int32_t  rpc_arg_min;             /* min legal RPC/event integer argument (when rpc_arg_bounds_on)*/
    int32_t  rpc_arg_max;
    int      rpc_arg_bounds_on;

    /* 4. Ownership enforcement: reject an RPC/write/event to a non-owned object. */
    int      enforce_ownership;

    /* 5. Lag-comp view-tick window (anti rewind abuse). */
    uint32_t max_rewind_ticks;        /* furthest into the past a client may claim to have seen      */
    uint32_t future_view_slack;       /* tolerance for a view-tick slightly ahead of now (skew)      */

    /* 7. Heuristic detectors. */
    uint32_t min_fire_interval_ticks; /* min ticks between shots from one client (0 => off)          */
    int64_t  max_aim_delta_per_tick;  /* max plausible aim change per tick (0 => off)                */

    /* 8. Violation scoring + enforcement thresholds (accumulated severity per connection). */
    uint32_t warn_threshold;          /* 0 => never warn                                             */
    uint32_t kick_threshold;          /* 0 => never kick                                             */
} lattice_ac_config;

Example — enable a sensible baseline

lattice_ac_config ac = {0};
ac.enabled              = 1;
ac.input_bounds_on      = 1;  ac.input_min = -1;  ac.input_max = 1;
ac.max_inputs_per_tick  = 4;
ac.max_speed_per_tick   = 600;     /* world units/tick */
ac.max_teleport_dist    = 2000;
ac.rpc_bucket_capacity  = 20;  ac.rpc_refill_per_tick = 5;
ac.enforce_ownership    = 1;
ac.min_fire_interval_ticks = 6;
ac.warn_threshold       = 50;
ac.kick_threshold       = 200;
lattice_ac_configure(r, &ac);   /* authority only */

lattice_ac_score

LATTICE_API uint32_t lattice_ac_score(lattice_runner* r, uint64_t conn);

The accumulated anti-cheat violation score for conn. Role: authority. Returns: 0 if anti-cheat is off or conn is unknown. Useful for surfacing "flagged players" to a console / analytics dashboard.

Reacting — on_violation

void (*on_violation)(void* user_data, uint64_t conn, lattice_ac_violation violation,
                     uint32_t severity, int64_t detail);

Fires synchronously inside lattice_runner_tick() on the authority the tick an offending submission is rejected (or the tick the accumulated score crosses a threshold). conn is the offending peer; violation is a lattice_ac_violation; severity is the score increment this violation added; detail is a small integer context value (e.g. the offending delta or count). Only ever fires when anti-cheat is enabled. A NULL pointer (the default) means "no anti-cheat callback".

lattice_ac_violation

typedef enum {
    LATTICE_AC_NONE              = 0,
    LATTICE_AC_INPUT_BOUNDS      = 1,  /* an input value was outside its configured range            */
    LATTICE_AC_INPUT_TICK        = 2,  /* an input tick was in the future / older than the window    */
    LATTICE_AC_INPUT_RATE        = 3,  /* a client exceeded its per-tick input-rate cap (flood)      */
    LATTICE_AC_MOVE_SPEED        = 4,  /* a move exceeded max speed / accel (speedhack)              */
    LATTICE_AC_MOVE_TELEPORT     = 5,  /* a move exceeded the teleport-distance cap                  */
    LATTICE_AC_RPC_RATE          = 6,  /* an RPC/event exceeded the token-bucket rate (spam/flood)   */
    LATTICE_AC_RPC_ARG_BOUNDS    = 7,  /* an RPC/event argument was outside its configured bound      */
    LATTICE_AC_OWNERSHIP         = 8,  /* a client targeted an object it does not own / control      */
    LATTICE_AC_VIEW_TICK         = 9,  /* a claimed view-tick fell outside the RTT/interp window      */
    LATTICE_AC_FIRE_RATE         = 10, /* shots fired faster than the configured minimum interval     */
    LATTICE_AC_AIM_SNAP          = 11, /* an aim delta exceeded the max plausible per-tick turn       */
    LATTICE_AC_STATE_HASH_DESYNC = 12  /* a per-tick state hash disagreed across peers (P2P tamper)   */
} lattice_ac_violation;
static void on_violation(void* u, uint64_t conn, lattice_ac_violation v,
                         uint32_t severity, int64_t detail) {
    fprintf(stderr, "[AC] conn=%llu violation=%d sev=%u detail=%lld score=%u\n",
            (unsigned long long)conn, (int)v, severity, (long long)detail,
            lattice_ac_score(runner, conn));
    /* the core auto-kicks at kick_threshold; you can also report to analytics here */
}