{"id":"michaelvqq/rollbackhitbox","name":"rollbackhitbox","scope":"michaelvqq","platform":"roblox","description":"Server-authoritative rollback lag compensation for Roblox shooters.","version":"0.1.0","latest":"0.1.0","versions":["0.1.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"fdaebbb37e60924972d98d6fd20bb27676ad53a6004cee06926bc8460d2fd73c","likes":0,"downloads":0,"install":"forest install michaelvqq/rollbackhitbox","url":"https://forest.dev/p/roblox/michaelvqq/rollbackhitbox","files":"https://api.forest.dev/ai/package/roblox/michaelvqq/rollbackhitbox/files","readme":"# RollbackHitbox\n\nServer-authoritative rollback lag compensation for Roblox shooters. The server rewinds hitbox positions to the exact moment the client fired, then validates the shot using oriented bounding-box (OBB) math — no physics simulation, no ghost parts moved at runtime.\n\n## Installation\n\n**Roblox Studio** — Download `RollbackHitbox.rbxm` from the [latest release](https://github.com/michaelvqq/RollbackHitbox/releases/latest) and drag it into `ServerScriptService`.\n\n**Rojo** — Copy the `RollbackHitbox/` folder into your project and reference it in your `default.project.json`:\n\n```json\n\"RollbackHitbox\": {\n    \"$path\": \"path/to/RollbackHitbox\"\n}\n```\n\n## Quick Start\n\n### 1. Record snapshots every frame\n\n```lua\nlocal RollbackHitbox = require(path.to.RollbackHitbox)\nlocal RunService = game:GetService(\"RunService\")\n\nRunService.Heartbeat:Connect(function()\n    RollbackHitbox.RecordAll(workspace:GetServerTimeNow())\nend)\n```\n\n### 2. Register characters on spawn\n\n```lua\nPlayers.PlayerAdded:Connect(function(player)\n    player.CharacterAdded:Connect(function(character)\n        character:WaitForChild(\"HumanoidRootPart\")\n        character:WaitForChild(\"Head\")\n\n        RollbackHitbox.AttachHitboxes(character)\n        RollbackHitbox.RegisterCharacter(character)\n\n        character:WaitForChild(\"Humanoid\").Died:Once(function()\n            RollbackHitbox.UnregisterCharacter(character)\n            RollbackHitbox.RemoveHitboxes(character)\n        end)\n    end)\nend)\n```\n\n### 3. Validate hits on the server\n\n```lua\n-- Inside your hit RemoteEvent handler\nlocal isValid, isHeadshot, hitDistance = RollbackHitbox.ValidateHit(\n    shooter,\n    target.Character,\n    shotOrigin,\n    shotDirection,\n    gunConfig.Range,\n    gunConfig.MuzzleOffset,\n    clientTimestamp\n)\n\nif isValid then\n    local damage = isHeadshot and gunConfig.HeadDamage or gunConfig.Damage\n    target.Character.Humanoid:TakeDamage(damage)\nend\n```\n\n### 4. Send the timestamp from the client\n\n```lua\nlocal fireTimestamp = workspace:GetServerTimeNow()\n-- ... raycast, effects ...\nCombatRemotes.PlayerHit:FireServer(targetUserId, shotOrigin, shotDirection, fireTimestamp)\n```\n\n## How it works\n\n```\nCLIENT fires\n├─ Capture timestamp = workspace:GetServerTimeNow()\n├─ Local raycast for instant visual feedback\n└─ Send: origin, direction, timestamp → server\n\nSERVER receives\n├─ rewindAmount = clamp(serverNow - timestamp + buffer, 0, maxRewind)\n├─ Retrieve shooter's rewound position  → validate origin distance\n├─ Retrieve target's rewound hitboxes   → binary-search + lerp snapshot buffer\n├─ Ray-OBB intersection on rewound positions\n└─ Apply damage only if valid\n```\n\nEvery server Heartbeat (~60 Hz), each registered character's hitbox CFrames are written into a circular buffer. `GetInterpolatedSnapshot()` binary-searches that buffer and lerps between the two bracketing entries — giving sub-frame accuracy without storing every frame.\n\n## API Reference\n\n### Hitbox lifecycle\n\n| Function | Description |\n|---|---|\n| `AttachHitboxes(character: Model)` | Attach `HitboxBody` and `HitboxHead` parts; disable `CanQuery` on all other BaseParts. |\n| `RemoveHitboxes(character: Model)` | Destroy hitbox parts and clean up listeners. |\n| `GetHitboxParts(character: Model)` | Returns `{ body: BasePart, head: BasePart }?` |\n\n### Position history\n\n| Function | Description |\n|---|---|\n| `RegisterCharacter(character: Model)` | Allocate snapshot buffer. Call after `AttachHitboxes`. |\n| `UnregisterCharacter(character: Model)` | Free snapshot buffer. |\n| `RecordAll(timestamp: number)` | Record one snapshot for every registered character. Call in `RunService.Heartbeat`. |\n| `RecordSnapshot(character: Model, timestamp: number)` | Record one snapshot for a single character. |\n| `GetInterpolatedSnapshot(character: Model, targetTime: number)` | Returns `{ PartHitbox }?` — interpolated hitboxes at `targetTime`. |\n\n```lua\nexport type PartHitbox = {\n    cframe:  CFrame,   -- hitbox position at the rewound time\n    size:    Vector3,  -- hitbox dimensions\n    isHead:  boolean,  -- true = head, false = body\n}\n```\n\n### Hit validation\n\n```lua\nRollbackHitbox.ValidateHit(\n    shooter:         Player,\n    targetCharacter: Model,\n    shotOrigin:      Vector3?,\n    shotDirection:   Vector3?,\n    gunRange:        number,\n    muzzleOffset:    Vector3,\n    clientTimestamp: number?\n) → (isValid: boolean, isHeadshot: boolean, hitDistance: number?)\n```\n\n| Parameter | Notes |\n|---|---|\n| `shooter` | The firing `Player`. Used to validate shot origin against their rewound position. |\n| `targetCharacter` | The `Model` being shot. Must be registered. |\n| `shotOrigin` | World-space muzzle position at fire time (client-sent). |\n| `shotDirection` | Normalised shot direction (client-sent). |\n| `gunRange` | Max effective range in studs. |\n| `muzzleOffset` | `Vector3` from character root to muzzle tip. Widens origin tolerance. |\n| `clientTimestamp` | `workspace:GetServerTimeNow()` captured on the client at fire time. Pass `nil` to skip rewind. |\n\n**Returns:** `isValid`, `isHeadshot`, `hitDistance` (studs to hit point — use for damage falloff).\n\n## Config\n\nEdit `Config.luau` to tune the system for your game.\n\n| Constant | Default | Purpose |\n|---|---|---|\n| `MAX_REWIND_SECONDS` | `0.5` s | Hard cap on rewind. Raise for high-latency players; lower to tighten the anti-cheat window. |\n| `INTERPOLATION_BUFFER` | `0.1` s | Extra rewind to compensate for Roblox's ~20 Hz character replication delay. |\n| `MAX_ORIGIN_DISTANCE` | `12` studs | Base tolerance for shot origin vs. the shooter's rewound position. |\n| `MAX_PLAYER_SPEED` | `60` studs/s | Scales origin tolerance with latency. Set to your fastest character speed. |\n| `BUFFER_CAPACITY` | `24` | Snapshots per character (~400 ms at 60 Hz). Increase if you raise `MAX_REWIND_SECONDS`. |\n| `BODY_HITBOX_SIZE` | `(4, 4, 3)` | Body hitbox dimensions (W × H × D in studs). |\n| `HEAD_HITBOX_SIZE` | `(2.5, 1.3, 2.5)` | Head hitbox dimensions. |\n| `BODY_HITBOX_OFFSET` | `(0, -1, 0)` | Body hitbox offset from `HumanoidRootPart`. |\n| `HEAD_HITBOX_OFFSET` | `(0, 0.2, 0)` | Head hitbox offset from `Head`. |\n| `ROLLBACK_FORGIVENESS` | `(3, 3, 0)` | Extra hitbox size during the OBB test. Tune down for tighter accuracy. |\n| `DEBUG` | `false` | Spawn visible ghost boxes and ray parts per validation. |\n| `DEBUG_DURATION` | `1.5` s | How long debug visuals persist. |\n\n## Security\n\n**What this protects against**\n\n- **Hit teleportation** — origin distance check bounds the shot to within reasonable range of the shooter's rewound position.\n- **Timestamp manipulation** — `clientTimestamp` is clamped to `[0, MAX_REWIND_SECONDS]`; a client cannot claim an arbitrarily old position.\n- **Headshot spoofing** — the server independently determines headshot vs. body-shot by which hitbox the rewound ray intersects.\n\n**What this does not protect against**\n\n- Fabricated `shotDirection` — direction is trusted. Add a server-side line-of-sight raycast for stronger guarantees.\n- Aimbots — this system ensures the claimed shot is geometrically plausible, not that it was aimed by a human.\n\n## License\n\nMIT\n","readmeTruncated":false}