Files
tokyo-wangan-racing/src/server/AntiCheat.luau
T
jesse d45f35736a fix: car now drives - collision groups, rigid hubs, pad spawn, anticheat grace
Four independent bugs kept the car immobile at the world origin:

1. Wheels collided with their own chassis. Wheel inner edge sits at 2.80
   vs body edge 3.00, and wheel top -0.20 vs body bottom -0.70. The
   overlap is intentional styling, but in one collision group the wheels
   ground against the body: motors spun ~1 rad/s against 20,000 torque.
   Wheels now use a CarWheel group that ignores CarChassis.

2. Suspension exploded on spawn. PrismaticConstraint+SpringConstraint
   between a 0.6-stud hub and the ballasted body was unstable enough to
   fling knuckles ~10,000,000 studs out, past FallenPartsDestroyHeight,
   deleting the wheels. Replaced with a rigid welded hub mount.

3. Car spawned at world origin, not the pad. MapBuilder made a SpawnPad
   part but no SpawnLocation, so characters spawned at (0,0,0) and Sit()
   dragged the car to them. Added a real SpawnLocation and the model is
   now pivoted after parenting, with the character teleported first.

4. AntiCheat snapped that legitimate spawn teleport back. An ~8,500-stud
   server move reads as a teleport hack. Added AntiCheat.grant() so
   server-initiated moves are exempt for a short grace window.

Verified in Studio: 881 studs in 6s, 496 studs/s top speed, 152 deg
steering, braking 33 -> 0 studs/s, chassis stays upright.
2026-07-31 10:04:47 -04:00

96 lines
3.0 KiB
Luau

--!strict
--[[
AntiCheat.luau (SERVER)
MVP-level movement sanity check. Not a fortress — a speed bump that catches
obvious speed/teleport hacks. Because money & car ownership are ALREADY
server-authoritative (Economy validates everything), the main client attack
surface left is movement, so we watch that.
Approach: sample each player's HRP position periodically; if they cover
ground implying a sustained speed above MaxPlausibleSpeed, snap them back
to their last valid position. Generous threshold to avoid false positives
from legit fast cars.
]]
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local GameConfig = require(ReplicatedStorage.Shared.GameConfig)
local AntiCheat = {}
type Track = { lastPos: Vector3, lastValid: CFrame }
local tracked: { [Player]: Track } = {}
local SAMPLE_INTERVAL = 0.5
-- Server-initiated teleports (car spawn onto the pad, respawn, mode resets)
-- legitimately move a player thousands of studs in one frame, which trips the
-- speed check and snaps them straight back -- dragging their car with them.
-- Callers announce those moves via AntiCheat.grant() so the next sample is
-- accepted as the new baseline instead of treated as a hack.
local graceUntil: { [Player]: number } = {}
local GRACE_SECONDS = 2
--- Exempt a player from the speed check briefly after a legitimate teleport.
function AntiCheat.grant(player: Player, seconds: number?)
graceUntil[player] = os.clock() + (seconds or GRACE_SECONDS)
end
local function hrpOf(player: Player): BasePart?
local char = player.Character
return char and char:FindFirstChild("HumanoidRootPart") :: BasePart?
end
function AntiCheat.init()
local function watch(player: Player)
player.CharacterAdded:Connect(function(char)
local hrp = char:WaitForChild("HumanoidRootPart", 10) :: BasePart?
if hrp then
tracked[player] = { lastPos = hrp.Position, lastValid = hrp.CFrame }
end
end)
end
for _, p in ipairs(Players:GetPlayers()) do
watch(p)
end
Players.PlayerAdded:Connect(watch)
Players.PlayerRemoving:Connect(function(p)
tracked[p] = nil
end)
local accum = 0
RunService.Heartbeat:Connect(function(dt)
accum += dt
if accum < SAMPLE_INTERVAL then
return
end
local interval = accum
accum = 0
for player, track in pairs(tracked) do
local hrp = hrpOf(player)
if not hrp then
continue
end
local pos = hrp.Position
-- Falling doesn't count (avoid flagging big drops). Only guard XZ plane.
local horizontal = Vector3.new(pos.X - track.lastPos.X, 0, pos.Z - track.lastPos.Z).Magnitude
local hSpeed = horizontal / interval
local inGrace = (graceUntil[player] or 0) > os.clock()
if hSpeed > GameConfig.MaxPlausibleSpeed and not inGrace then
-- Snap back: likely a speed/teleport hack.
hrp.CFrame = track.lastValid
hrp.AssemblyLinearVelocity = Vector3.zero
else
track.lastValid = hrp.CFrame
end
track.lastPos = hrp.Position
end
end)
end
return AntiCheat