The Open Bridge Files
On this page7
Every place where this script touches another resource lives in bridge/. Those
files are open: you can read them, edit them, and add your own systems
without any escrow access.
bridge/
framework_client.lua framework_server.lua hooks.lua
inventory.lua notify.lua textui.lua
progress.lua emote.lua target.lua
appearance_client.lua appearance_server.lua studio_client.lua
voice_client.lua voice_server.lua hud.lua
death_client.lua death_server.lua police_server.lua
mdt.lua evidence.lua nametag_client.lua
screenshot_client.lua screenshot_server.luaAlso open: config.lua, config_server.lua and locales/*.lua.
Everything else (client/, server/, shared/) is escrowed.
The selector pattern
Every area has one switch in config.lua:
Config.Framework = 'auto'
Config.InventorySystem = 'auto'
Config.VoiceSystem = 'auto'
Config.DeathSystem = 'auto'
-- …and so on| Value | Meaning |
|---|---|
'auto' | Look for the known resources in a fixed order, use the first one found |
'<resource folder name>' | Pin it. If that resource is missing you get one warning, and that area stays off rather than silently switching to something else (target, text UI, progress and emotes fall back to our own) |
'custom' | Your branch in the bridge file |
'none' | Off |
'native' / 'builtin' / 'standalone' | Our own implementation, where we ship one |
The resolved value is printed at boot:
[5code-recognition] Integrations: framework=qbx inventory=ox_inventory voice=pma-voice appearance=illenium-appearance tattoos=illenium-appearance death=qbx_medical police=qbx_police mdt=none evidence=none target=none textui=native hud=jg-hud screenshot=builtinAdding your own system
Every function in a bridge has the same shape:
function DeathBridge.IsPlayerDown(serverId)
local sys = DeathBridge.System
if sys == 'qbx_medical' then
-- ...
elseif sys == 'wasabi_ambulance' then
-- ...
elseif sys == 'custom' then
-- >>> YOUR CODE HERE <<<
return exports['my_ambulance']:IsDown(serverId)
else
-- none / native fallback
return false
end
endSo adding a system is three steps:
- Set the selector to
'custom'inconfig.lua. - Fill in the
'custom'branch of every function in that bridge file. - Restart.
If you would rather your system be detected automatically, add it to the auto
order in the same file instead — but a pinned 'custom' is easier to keep
across updates.
The rules a bridge must follow
- One global table per file, plus its resolved
.Systemfield. - Guard every call into another resource with a state check and
pcall. On failure, log once and return the documented neutral value (false,nil,{}). - Fetch framework objects lazily, inside their own branch. Never call
exports['qb-core']:GetCoreObject()at file scope — the file must not error on a server that does not run qb-core. - Bridges translate. They never score, never store secrets, never write our database tables.
- Observe, do not act. Never trigger a third-party event that changes the game on another player's behalf — in particular, never fire a revive event. Some anti-cheats punish the caller of such an event.
- Treat an event sent by a player's game as an attempt, never a fact. Confirm it through something the server can see. See Attempt, not fact below.
- Call exports by the resolved folder name —
exports[DeathBridge.System]— because exports are namespaced by folder andprovidedoes not alias them.
RecogCore — the only door into the core
A bridge may call only RecogCore.*, and only at runtime, inside a handler
or a thread.
Server
| Function | For |
|---|---|
RecogCore.DeathDown(src, data) | Somebody went down. data{stage='laststand'|'dead', killer?, weapon?, via} |
RecogCore.DeathRevived(src, data) | A revive. data{by?, method, verified, via} — set verified = true only when your server confirmed the revive |
RecogCore.DeathRespawned(src, data) | A respawn. data{method, verified, via} — wins over a later revive |
RecogCore.ProofAttempt(actorSrc, targetSrc, cause, meta?) | A raw attempt, confirmed later by its outcome |
RecogCore.ProofOutcome(targetSrc, cause, state, meta?) | An outcome; pairs with an attempt, or infers the actor |
RecogCore.ProofAction(actorSrc, targetSrc, cause, meta?) | A confirmed action |
RecogCore.LabIdentified(officerSrc, targetCharKey, meta) | A lab match. meta{evidenceType, source, correctLink, trust} — trust is 'server' or 'client' |
RecogCore.AppearanceSaved(src) | This player's look changed (an outfit or appearance save) |
Client
| Function | For |
|---|---|
RecogCore.StartScan(targetServerId) | Start a scan from a menu or a target option. The server checks it normally |
RecogCore.Introduce(targetServerId) | Open the introduce input |
RecogCore.AppearanceChanged(reason) | The local player's look changed — send it now |
RecogCore.DeathSignal(kind, data) | A death signal from a client-only adapter — checked on the server before it counts |
RecogCore.IsBusy() | true while scanning or while one of our menus is open |
Anything you want to do that is not on these two tables belongs in a hook or an export, not in a bridge.
Attempt, not fact
The rule that catches people out: an event sent by a player's game is a claim, not a fact.
-- WRONG: this records a cuff that may never have happened
RegisterNetEvent('police:server:CuffPlayer', function(targetSrc)
RecogCore.ProofAction(source, targetSrc, 'cuff')
end)
-- RIGHT: record an attempt, let server-visible state confirm it
RegisterNetEvent('police:server:CuffPlayer', function(targetSrc)
RecogCore.ProofAttempt(source, targetSrc, 'cuff')
end)
-- ...and elsewhere, when the server can see the cuffed state:
RecogCore.ProofOutcome(targetSrc, 'cuff', true)An attempt with no matching outcome inside
Config.Proof.ActionAttemptWindowMs (3 s) is dropped.
Updating
Bridge files are yours to edit. Keep a copy of your edits before you update, because a new version may change what a bridge file contains.
Every release that changes a bridge file lists it in the changelog, so you can diff your copy against ours and merge. Keep your own edits small and commented, and they will survive.