Movement checks: where a player and a vehicle go
Bastrel reads, every 1.5 seconds per player, where the character (or the vehicle it drives) is, as the server sees it, and measures what happened since the previous read. A speed hack, a teleport, a noclip menu or a boosted vehicle show up here. This page covers four codes.
Status: journal only, for now
Section titled “Status: journal only, for now”All four codes ship as unverified. FiveM does not document how closely the position and the velocity a server native returns follow a cheating client, and the velocity a server native returns was read on 22 October 2026 on a character that runs: it is always zero, so Bastrel no longer uses it. Until a test in game confirms the rules, these codes are held in observe whatever your configuration says:
- they appear in the log and in the detections table, with
would_haveempty; - no alert, no Discord message, no score, no kick, no ban;
- they never cancel anything.
They are lifted one at a time, in a later release, after a capture on a real server. You can already read the journal to learn what your own scripts trigger, and declare your teleports (below) so that the day a code goes live it is quiet on your server.
| Code | What it sees | Severity | How certain |
|---|---|---|---|
BAS-MV-001 |
A character on foot faster than movement.onFootMaxSpeed (horizontal, averaged) |
medium | clue only |
BAS-MV-002 |
A jump of position between two samples, without a teleport you declared | high | clue only |
BAS-MV-003 |
The position of a frozen character advances (noclip), or an attached character is far from what it is attached to | high | clue only |
BAS-VH-001 |
A vehicle faster than the maximum of its model or type, judged on its driver | medium | clue only |
How a finding is made
Section titled “How a finding is made”- Every distance and speed is measured by the server between two of its own reads, divided by the time it measured between them. A speed the client reports (
GetEntitySpeed, a velocity a menu can set) never enters a threshold, so a cheater cannot raise the limit that catches him. The only speeds in the thresholds are the ones in your configuration. BAS-MV-001andBAS-VH-001judge the average of the lastmovement.speedSamples(3) measured speeds of consecutive samples in continuity (a speed above its limit on average is a finding). The window must be full, so about 6 seconds of movement are needed.BAS-MV-003needsmovement.noclipSamples(3) consecutive samples.BAS-MV-002is a one-off jump: one sample is enough.- A finding is reported once per player and code every 10 seconds (
movement.reportMs), however long it lasts. BAS-MV-001uses the horizontal distance (a fall is not a speed); the jump, the noclip and the vehicle speed use the 3D distance. A move that exceeds the jump threshold is a jump, never a speed.- The global envelope:
BAS-MV-002as soon as the move over the interval exceedsmax(teleportDistance, highest configured maximum speed x vehicleMargin x elapsed time)(about 230 m/s with the defaults). It knows no state, so nothing the client sets (attached, passenger, seat, vehicle type) moves it. It is the threshold outside continuity, and in continuity until the speed window is full: the envelope of the state (see below) exceeds it only on a sample thatBAS-VH-001/BAS-MV-001judge, so a window emptied at every break never buys a wider threshold. The previous position is dropped only by a grace, a consumed declared teleport, or a gap abovemovement.maxDtMs; an unreadable position keeps it. - Continuity rules, read by the server alone (same state, same entity, consecutive samples): on foot (no vehicle, not attached),
BAS-MV-001,BAS-MV-003andBAS-MV-002; as the driver of the same vehicle, when its type is readable and has a configured maximum,BAS-VH-001andBAS-MV-002. While continuity holds and the speed window is full, the threshold ofBAS-MV-002is the envelope of that state (on footmax(teleportDistance, onFootMaxSpeed x teleportSpeedFactor x elapsed time), at the wheelmax(teleportDistance, maximum x vehicleMargin x teleportSpeedFactor x elapsed time), so 690 m for a plane over 1.5 s); before the window is full, and outside continuity, it is the smaller of that envelope and the global one. Any break of continuity (the state, the entity or the seat changes, an attachment, unreadable data) empties the windows: three clean samples are needed again. - Never judged on a speed: a passenger (an innocent passenger of a modded car is never reported), an attached ped, a vehicle whose type cannot be read or has no configured maximum (a train included). Only the global envelope bounds them.
- Known limit: a client that keeps breaking the continuity escapes
BAS-MV-001,BAS-VH-001andBAS-MV-003. A direct switch of vehicle (a single break) must recur everyspeedSamplessamples (4.5 s with the defaults); a one-sample detour through another state (attaching and detaching the ped) everyspeedSamples + 1samples;BAS-MV-003depends onnoclipSamples. Inside such a cycle every sample is bounded by the global envelopemax(teleportDistance, 230 m/s x elapsed time): about 230 m/s, and up to 300 m/s on a short gap (the 150 m floor over 500 ms). The on-foot envelope bounds only the on-foot samples in continuity; a vehicle in continuity whose window is not yet full is bounded bymin(global, envelope of the state). A 689 m jump (the 690 m envelope of a plane) that follows a slow window is bounded over three samples by theBAS-VH-001mean, so by about 230 m/s per cycle. Accepted: the codes areunverified. The cycle is not silent: Bastrel writes one log linemovement.continuity_breaksper player andmovement.reportMswindow (default 10 s), at the end of the window (or when a grace or the departure of the player closes it), once it saw at least two breaks, with the real count and the states involved; it is a log only, no detection and no score. A later release will revisit the limit with in-game data. BAS-VH-001compares the measured speed with the maximum of the model (movement.vehicleModels, over) or of the type (movement.vehicleMaxSpeed: automobile, bike, boat, heli, plane, submarine, trailer; a type that is not listed, like a train, is not checked) timesmovement.vehicleMargin(1.15). The game has no vehicle class a server can read, so these numbers are yours to adjust: the shipped ones are starting points, not measures.- Only the driver of a vehicle (the ped in seat -1) is judged on a speed, and only while the vehicle stays the same and its type has a maximum. A passenger, a vehicle with no driver, an attached ped, or a seat or attachment the server cannot read, is judged on nothing but the global envelope. That state is set by the client, so it never erases the previous position: attaching a ped for one sample, or sitting in a passenger seat for one sample, does not excuse a teleport that follows.
- A ped attached to another entity (carried, escorted) is also judged on its distance to the entity it is attached to, and
BAS-MV-003comes out of it when it stays further thanmovement.attachMaxOffset(6 m) fornoclipSamplessamples. Attaching a ped to something immobile does not excuse moving it. - Nothing else excuses a check. A frozen ped (
IsEntityPositionFrozen), a ragdoll or an attachment can be set by the client, so they are never exclusions. The one tolerance is the ragdoll ofBAS-MV-001: it is excused formovement.statusToleranceMs(5 s) in a row, then judged again. - Nothing is judged while the player loads (
graces.joinMs, 15 s) nor just after a change of routing bucket (graces.bucketChangeMs, 3 s): the same graces as the state checks, with the same QBX rule (the 15 s start at the load of the character). A player the state module does not know, or a state module that is not running, is not judged either (unknown is never “no grace”). After a grace, the first sample is only a starting point. If the QBX character selector stays open for more than 15 s after the load, the spawn that follows can be read as a jump: declare it withallowTeleport. - A gap between two samples above
movement.maxDtMs(6 s: the server stalled) or a grace restarts the measure; a ped that does not exist or a position the server cannot read keeps the previous position and empties the windows, so a jump after the gap is still judged (withinmaxDtMs). A change of ped or vehicle empties the windows too; a jump is still judged (changing ped does not excuse a teleport).
Declare your teleports
Section titled “Declare your teleports”Your scripts declare a legitimate teleport with the export, before they move the player:
exports.bastrel:allowTeleport(src, coords, 5000) -- coords: where the player lands, 5000 msA declared teleport is checked before any rule: when one is active and the player is within movement.teleportGraceRadius (25 m) of a declared destination, nothing is judged for that sample, whether the hop is above the jump threshold or not (a declared 100 m hop is neither a speed nor a noclip). A jump that lands elsewhere is still reported. A declaration is used up by the first sample that finds the player near it: it covers one teleport, not every sample until it expires, so declare each teleport. A teleport you do not declare gives BAS-MV-002 (a log line only while the code is unverified). Give a duration of at least two sampler intervals (3 s): a grace that ends before the sample sees the jump excuses nothing. The measure restarts after the jump, so the speed of the jump itself is never judged.
There is no automatic grace for a respawn or a revive. The reanimation scripts of your server (hospital, bed, ambulance) must call allowTeleport with the place where the player reappears; otherwise a BAS-MV-002 line is written (journal only today). A staff member with the bastrel.bypass.movement ACE is exempt from the whole module (BAS-MV-001, BAS-MV-002, BAS-MV-003 and BAS-VH-001): the detection is not journaled, and the use of the bypass is written in bastrel_staff_actions.
Configuration
Section titled “Configuration”All the values are in config/modules.lua, block movement; an invalid value takes its default and is warned about at startup, an absent one takes its default silently.
| Key | Default | Meaning |
|---|---|---|
reportMs |
10000 | One finding per player and code in this window (1000 to 3600000) |
speedSamples |
3 | Speeds averaged by BAS-MV-001 and BAS-VH-001 (2 to 10) |
maxDtMs |
6000 | A longer gap between two samples restarts the measure (2000 to 60000; keep it above twice sampler.intervalMs, or startup warns) |
onFootMaxSpeed |
20.0 | m/s, horizontal, on foot (a sprint is about 7) |
statusToleranceMs |
5000 | Ragdoll excused this long in a row (0 to 60000) |
teleportDistance |
150.0 | m, floor of the jump threshold |
teleportSpeedFactor |
2.0 | Margin on the configured speeds in the jump threshold (1 to 100) |
teleportHardCap |
800.0 | m, ceiling of the jump threshold, applied only while the fastest legitimate move stays under it (at least teleportDistance) |
teleportGraceRadius |
25.0 | m around a declared destination |
noclipMinMoved |
4.0 | m between two samples for the position of a frozen character to “advance” |
noclipSamples |
3 | Consecutive samples for BAS-MV-003 (2 to 10) |
attachMaxOffset |
6.0 | m a ped attached to an entity may be from it |
underMapZ |
-100.0 | BAS-MV-002 ignores the game lifting a character on foot back to the surface when it was below this height (-10000 to -20) |
underMapSamples |
2 | Consecutive samples the character must have stayed below underMapZ (1 to 10) |
underMapMaxHorizontal |
100.0 | m: the lift must end within this horizontal distance of the previous position |
vehicleMargin |
1.15 | Margin on the maximum speed of a vehicle (1 to 10) |
vehicleMaxSpeed |
per type | m/s per GetVehicleType (automobile 85, bike 70, boat 70, heli 100, plane 200, submarine 30, trailer 85) |
vehicleModels |
empty | m/s per model name, over the type: { adder = 70.0 } |
What to expect while it is unverified
Section titled “What to expect while it is unverified”BAS-MV-003sees only a noclip that freezes the character. The velocity the server reads is always zero on foot, even while running (22 October 2026), so it is not used: a character is reported only when it is frozen (IsEntityPositionFrozen) and its position advances. A running character is never reported. A noclip menu that does not freeze the character is not seen yet.- A character that falls under the map and is lifted back by the game is not a teleport.
BAS-MV-002ignores a lift of a character on foot (never a vehicle, a passenger or an attached character) that stayed belowunderMapZfor at leastunderMapSamplessamples and ends higher, withinunderMapMaxHorizontalof its previous position (200 m in 1.5 s was observed). Each lift is a break of continuity and writes onemovement.under_map_liftjournal line per window. A real teleport from the surface, or a longer sideways one, is still reported. - An admin teleport you did not declare (
/tpm) givesBAS-MV-002. A staff member with thebastrel.bypass.movementACE is exempt (the whole module, see above); for anyone else, declare the destination withexports.bastrel:allowTeleportor expect the line in the journal. - The positions come from the client through OneSync and arrive in batches: a lagging player can cover a large distance between two reads. The thresholds have margins for it,
maxDtMsrestarts the measure after a long gap, and Bastrel does not use the network statistics (they refresh every 10 s only). - A ped that dies or revives somewhere else is a jump (see above). A cutscene or a fast ride in a plane may be read as a speed until you adjust the numbers.
- A character in a vehicle whose seat or type Bastrel cannot read is not judged on a speed; it is still bounded by the global envelope (about 230 m/s).
- Honest false positive, known: the first 4 samples (6 s) after entering a plane in flight, after going from passenger to pilot, or after leaving a plane and getting back in, are judged at 345 m instead of 690 m (the plane envelope applies once the speed window is full). A lag catch-up more than 15 % above 200 m/s gives
BAS-MV-002in that window. Only planes are affected with the shipped values. It is a log line only while the code isunverified. - A free fall sampled after a late sample is a jump: at 55 m/s, a gap above 2.73 s (150 m) gives
BAS-MV-002. Rare, and only the log line while the code isunverified.
The module has no loop of its own: it runs in the shared sampler, one slice of players per tick. Per sample it makes five native calls on foot (GetPlayerPed, GetVehiclePedIsIn, GetEntityAttachedTo, DoesEntityExist, GetEntityCoords) five at the wheel and as a passenger (the attachment is replaced by GetPedInVehicleSeat), and seven for an attached ped (two more to read its parent). The type of the vehicle (and its model, only if you configured vehicleModels) is read once per vehicle, not per sample. The frozen state is read only when the position of a character on foot advanced by more than noclipMinMoved, the ragdoll only above the speed threshold. No call to another resource is made. The measure on a real server (resmon) is in the test list of the release.