Skip to the content
Docs

Proofs

On this page8

A proof is an automatic snapshot of what an attacker knew about their victim at the moment they acted. Aim, damage, kill, cuff, uncuff, rob, search, escort, kidnap, and anything a script logs itself (custom).

Every proof stores both numbers:

  1. What the actor's character actually remembered at that moment.
  2. What they could have reached right now — worked out from the scene, even when a live result exists.

That second number is what turns "I recognised him" into something checkable.

Master switches

KeyDefaultAllowedWhat it does
Config.Proof.Enabledtruetrue / falseThe whole feature.
Discordtruetrue / falseAlso send proofs to the Proofs webhook.
LogJobstruetrue / falseWrite the actor's job, so admins can tell police from robbers.
EstimateNowtruetrue / falseWhether the cue breakdown of "could reach right now" is stored with the proof. Both numbers themselves are always stored.

What triggers a proof

KeyDefaultAllowedWhat it does
AimMinMs750msHow long you must aim at someone before it counts.
AimCooldown60secondsBetween two aim proofs for the same pair.
AimGapMs500msGap that breaks one aim into two.
AimMaxDistance150.0metresFurthest an aim proof is taken.
AimVehicleOccupantstruetrue / falseAiming at a car counts for the players inside, up to four.
AimIgnoreWeaponsunarmed, flashlight, petrol can, extinguisher, ball, snowball, parachute, digiscanner, hazard can, fertilizer can, metal detectorweapon namesWeapons that never produce an aim proof.
DamageCooldown10secondsBetween two damage proofs for the same pair.
DamageMaxDistance300.0metresFurthest a damage proof is taken.
ClientDamageWaitMs1000msHow long to wait for the client side of a damage event.
KillWindowMs3000msHow long a kill may be matched to the last damage.
ActionAttemptWindowMs3000msHow long a cuff or rob attempt has to be confirmed.
ActionMaxDistance5.0metresFurthest an action proof is taken.
RobGatherSeconds5secondsHow long a robbery is gathered into one proof.

Per-action cooldowns

lua
Cooldowns = { cuff = 10, uncuff = 10, rob = 30, search = 30,
              escort = 30, kidnap = 30, custom = 0 },

Seconds between two proofs of that action for the same pair. custom = 0 means anything logged through the LogAction export has no per-pair wait — only the limits below apply.

Reading actions from other scripts

KeyDefaultAllowedWhat it does
CuffStateKeys{ 'ishandcuffed', 'isHandcuffed', 'isCuffed', 'cuffed', 'cuffs' }state-bag keysPlayer state keys other scripts use for "handcuffed".
OxInventoryHookstruetrue / falseUse ox_inventory hooks to see robberies (openInventory, swapItems).
InferActorRadius2.5metresWhen no script named an actor, the nearest facing player is logged as likely.

Limits

KeyDefaultAllowedWhat it does
MaxPerActorPerMinute60countMost proofs one actor can produce per minute.
ExportCallsPerMinute120countLogAction calls per minute, per calling resource.
IncidentGapSeconds120secondsGap that separates one incident from the next in the timeline.
VerifyOnBootRows1000rowsHow many of the newest proof rows are checked for tampering at every start.

Discord

KeyDefaultAllowedWhat it does
DiscordCausesall trueper-cause true / falseWhich causes are sent: aim, damage, kill, cuff, uncuff, rob, search, escort, kidnap, custom.
DiscordOnlyBelowfalsetrue / falsetrue = only proofs below the act line go to Discord.
DiscordSkipOnDutyJobs{}e.g. { 'police' }Jobs whose on-duty actions skip Discord. The database still logs them.
DiscordBatchSeconds5secondsHow long proofs are batched before sending.

Webhook setup: Discord Webhooks.

Tamper-evident log

The proof log is tamper-evident: if someone edits or deletes a row in the database by hand, the check finds it. Run the check at any time with:

Code
recognition:verifyproofs [fromId]

It checks the proof log and the admin log and prints the result. A check value is also posted to the Proofs webhook every ServerConfig.Discord.AnchorEveryRows proofs (default 100) and at shutdown, so you have an outside copy to compare with.

Lab ID

A fingerprint or DNA match from your evidence script produces a separate Lab ID record for the investigator — not a memory, and not a recognition.

KeyDefaultAllowedWhat it does
Config.Lab.Enabledtruetrue / false
Seconds604800 (7 days)secondsHow long a Lab ID lasts.
RequireCorrectLinktruetrue / falseGrant only when the police link points at the real owner. A wrong link grants nothing.
Recipients'officer''officer' · 'onduty' · 'officer_mdt' · 'none'Who gets it. none = admin log only.
RepeatWindowSeconds600secondsThe same lab event again within this time only refreshes the timer.
PerTargetPerDay3countMost lab IDs one officer can get on the same person per day.

Trust checks

Strictest for evidence data that comes from a player's own game.

KeyDefaultAllowedWhat it does
ClientTrust.RequireJobtruetrue / falseMust have a Config.Jobs.Police job.
ClientTrust.RequireDutytruetrue / falseMust be on duty.
ClientTrust.Zones{}{ { coords = vector3(x,y,z), radius = 10.0 }, … }Lab rooms. Empty = anywhere.
ClientTrust.RatePerHour10countMost lab IDs one officer can get per hour.
ClientTrust.CountsForActfalsetrue / falseWhether a lab ID from player-side evidence also counts for "may act" and for proofs. false = stored and shown to admins only, unless the server can confirm it.

Any evidence script can grant one without an adapter:

lua
exports['5code-recognition']:GrantLabIdentification(officerSrc, targetCharKey, opts)

Add your resource to ServerConfig.Api.TrustedResources first. See Police, MDT & Evidence.

Permissions

Report.* and Lab.* both need the extra edit level on top of settings to be changed from the in-game panel (Config.Admin.SettingPerms).