Architecture
Architecture
The one rule
The client never decides anything. The NUI is a view and an input device. It proposes moves; the server validates, commits and broadcasts the result.
A move the client believes in but the server refuses simply does not happen, and the interface corrects itself from the server's answer rather than from its own optimism.
The transaction engine
Every mutation — a drag, a purchase, a craft, a trade, a pickup, a death drop — goes through the engine in server/core/transaction.lua.
propose → VALIDATE → commit → ledger → broadcast
VALIDATE is where every rule lives. Weight, slots, pinning, bag nesting, evidence, licences, carry limits — all of them are checked in one place, on one code path.
Concretely, five rules were moved into VALIDATE after being found missing somewhere: evidence, bound items, pinning, carrying a placeable, and carrying a craft result. Each had a path around it before.
Versioned containers
Every container carries a version, and every commit bumps it. A move carries the version the player's screen was showing; if the container has changed since, the move is refused as stale_version and the player is handed the current contents instead of a silently different outcome. Two commits never touch one container at the same time: each locks every container it touches, in a fixed order, for the length of the commit.
This is what keeps two players moving items in a shared stash from overwriting each other.
Layout
shared/config.lua all settings
data/items.lua item definitions
data/recipes.lua crafting recipes
client/ NUI bridge, markers, targets, mirrors
server/
core/transaction.lua the engine — every mutation
types/*.lua container behaviours: stash, wear, placeable, …
api/*.lua exports and self-tests
admin.lua the admin panel's dispatcher
compat/
qb-inventory/ name proxy
ox_inventory/ name proxy
ui/ built NUI (Vue 3 + Vite + Pinia)
web/ NUI source
The framework bridge
Player data — identity, job, money, needs — is asked of aty_lib, not of QBCore or ESX: client/framework.lua on the client, Utils / modules on the server, and every other file gets the same answer whatever the server runs. Two deliberate exceptions: Qbox, which the bridge does not know by name, is asked through qbx_core directly (see Compatibility), and the qb compatibility layer and the migration tool talk to qb-core by name, because that is their job.
That is why standalone mode is not a special case with its own code path — it is one more answer the bridge can give.
The NUI
Vue 3 with Pinia, built by Vite into ui/. The same built artifact serves the game and the demo on the website; a mock layer stands in for the server when it detects it is running in a plain browser.