Skip to the content
Docs

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.

Code
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.lua

Also 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:

lua
Config.Framework       = 'auto'
Config.InventorySystem = 'auto'
Config.VoiceSystem     = 'auto'
Config.DeathSystem     = 'auto'
-- …and so on
ValueMeaning
'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:

Code
[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=builtin

Adding your own system

Every function in a bridge has the same shape:

lua
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
end

So adding a system is three steps:

  1. Set the selector to 'custom' in config.lua.
  2. Fill in the 'custom' branch of every function in that bridge file.
  3. 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

  1. One global table per file, plus its resolved .System field.
  2. Guard every call into another resource with a state check and pcall. On failure, log once and return the documented neutral value (false, nil, {}).
  3. 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.
  4. Bridges translate. They never score, never store secrets, never write our database tables.
  5. 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.
  6. 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.
  7. Call exports by the resolved folder name — exports[DeathBridge.System] — because exports are namespaced by folder and provide does 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

FunctionFor
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

FunctionFor
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.

lua
-- 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.

See also