OPX//77
Migration

Release notes

Last updated on

What changed in opx_infinity and opx_lib, newest first, starting with the replacement of the opx77_* resources in September 2026.

Newest first. The version is the one in each resource's open77.lua.

opx_infinity — Unreleased

Merged into main after 0.2.0; the manifest still says 0.2.0. Planned for the next 0.2.x.

Fuel (PR #124)

What players see nowPages
Vehicles burn fuel while the engine runs; at 0 the engine is cut and will not stay started. The vehicle dial shows a FUEL line. AVs take no fuel.fuel
Pumps at surveyed stations: E or the eye opens a menu, pay cash or bank, a cancelable bar fills the car; a cut-short bar bills what went in.At a pump
A Fuel can (petrolcan) bought and refilled at a pump and poured anywhere.The jerry can
An owned car keeps its tank through a garage.vehicles
A Gas Station pin (drop_point) per open station.blips
UpgradingDo
No station is open out of the box. The five shipped Night City stations are placeholders, disabled at boot.Survey them with /opx.fuel.capture and paste the lines into config/fuel.lua. Until then only a can or staff fills a tank.
New configconfig/fuel.lua (new module); config/blips.lua category fuel; config/hud.lua VEHICLE.FUEL. Missing keys take their defaults.
New itempetrolcan.
Databaseopx77_vehicles.fuel (FLOAT NULL) is added on boot by OPX.Schema.AddColumns; the database user needs ALTER. Existing cars come out at INITIAL_PERCENT.
New ACLcommand.opx.fuel.set, command.opx.fuel.capture, command.opx.fuel.stations — grant command.opx.fuel.*.
New exportsGetFuel, GetFuelState (read), SetFuel (write: add your resource to SERVER.EXPORTS.WRITERS). A resource reading Entity(vehicle).state.fuel the ox way gets litres, not a percentage.
New eventopx:on:fuel:changed. Net events opx:net:fuel:*.
open77_fuelKeep it if you like: with STAND_DOWN = true this module stops burning while it runs. See open77_fuel.

opx_infinity — PRs #96–#101 (2026-10-05)

Merged into main and shipped in the manifest version 0.2.0 (PR #123). One table per theme. Before updating a running server or a resource that calls OPX, read Upgrading from PR #95. (#98, a WebUI polish pass, is not covered here.)

Four rules the server keeps (PR #101)

What players see nowBeforePages
Hunger and thirst fall on the server's clock and go up only by eating or drinking, staff (/opx.needs.set) or a server export. A modified client can no longer stay fed.The client decayed its own needs and pushed them; the server stored whatever came.needs
Global chat needs a loaded character: on the character screen a line answers you speak once your character is in the city. Staff (STAFF_PERMISSION) still can.Any admitted connection spoke to everyone.chat
An outfit share code is typed inside a shop's fitting room and the pieces are billed at that shop's prices on Save.A code dressed you for free, anywhere.shops
A give asks the receiver: an Accept / Refuse card with the item, the count and where the giver stands, never a name. 15 s to answer.The stack moved on the giver's press alone.Give and drop

Player flows (PR #96)

ChangePages
Bandage, Bounce Back and MaxDoc heal 15 %, 40 %, 75 % of maximum health (HEALING); refused at full health and while down; nothing is used up unless the heal goes through.Medical items
Withdrawing notes shows one toast; notes handed over are told to both players as money. Drawing a weapon closes the inventory. The give list refreshes every time a slot card opens.inventory
A shop uniform is billed on Save, not when picked; Cancel costs nothing. The fitting room shows the bill before Save (On Save: 1,050 €$). A long browse no longer ends in "Your outfit was not saved." (kept alive up to 30 min).shops
Contacts: DELETE on each row (two presses). A full list (64) refuses new contacts instead of dropping the oldest; offline contacts show greyed. X is calling you. / Calling X... toasts are gone.calls
Garage toasts name the car (Brought out Archer Hella · AA111AA., Recalled … here.). On foot, the garage door opens the list (Open your garage).garages
Getting into a car with a crate puts it down at your feet; it no longer flies back to its point.hauling
The dealership says You bought X first, then where the car waits.dealership
The spawn menu waits for a slow name form or fitting room (up to 30 min) instead of placing the player under it.spawn
Job, grade and spawn-place names are translated (character.job.<job>, character.grade.<job>.<level>, spawn.place.<id>); English text unchanged.character, spawn

Edge cases and robustness (PRs #97, #100)

ChangePages
Two staff door writes at once run one after the other; a wait over 5 s answers doorlock.error.busy. A numeric door code of 2^63 or more is refused.doorlock
Two players fetching at one garage get two exits (a bay is promised while the car spawns).garages
Crafting and hauling refunds and pay go to the character, not the connection: leaving or switching character mid-action loses nothing and pays nobody else.crafting, hauling
Money balances stop at ±(2^53−1) (money.tooLarge); a staff rename can no longer break saves (name column cut to 64).character
Names refuse invisible and direction-changing characters (character.nameInvisible); accents and other alphabets still pass.character
Broken UTF-8 in chat, contact names and labels is mended to ? instead of breaking saves.OPX.Text.Clean
A chat config missing MAX_LENGTH / RATE_MS / READY_MS takes the defaults instead of silencing chat.chat
Dealership deposits and crafting placements are idempotent: a lost answer never pays twice or refunds an order that exists.dealership, crafting
Stashes opened by players are put away when idle; a lift finishes its trip when a rider leaves; two vehicle registrations for one character cannot pass the cap (vehicle.registering).inventory, elevators, vehicles

Hostile net events (PR #99)

ChangePages
A downed player cannot craft, buy or sell at a dealer, use a garage, a shop, a fitting room, a car key or a stash (error.incapacitated and module codes). One check for all: OPX.Life.Down.Error codes
A contact offer out of reach answers only tooFar: it can no longer probe whether a stranger is down, loading or on a call.calls
opx:net:vehicles:spawn / store removed: no car out from anywhere, none put away from across the map.vehicles events
Refusals name back only a key the server knows; shop outfit ids must be whole numbers; the inventory spends its rate window before reading the verb; clothing:ask has a 1 s floor.garages, inventory

Upgrading from PR #95

WhatDo
Needs are server-owned (API change). The client needs contract lost SetNeeds and AddNeeds; opx:net:needs:push and opx:net:needs:pushed are removed. The client export GetNeeds is read only.Move needs from your server side: exports GetNeeds, AddNeeds, SetNeeds (add your resource to SERVER.EXPORTS.WRITERS for the two writes; all three must be awaited). Inside opx_infinity, the new server contract.
New server event opx:on:needs:changed (playerId, { citizenId, values, changed, source }).Listen there instead of a client hook. opx:net:needs:values now carries a third reason argument.
Removed net events opx:net:vehicles:spawn, opx:net:vehicles:store.A client that fired them gets nothing. Use a garage, or vehicles.Spawn / SetVehicleState on the server.
Gives need consent. A player give is an offer; opx:on:inventory:items fires only when the receiver accepts. New net events giveOffer, giveAnswer, giveWithdrawn; new codes give_pending, give_busy, give_gone, give_changed.A script that watched for a give must wait for the event. AddItem and staff gives ask nobody.
Chat needs a character. opx:net:chat:say from a connection with no loaded character is refused chat.notLoaded.Give staff accounts STAFF_PERMISSION. Server code keeps using the chat contract or BroadcastChat.
Share codes are redeemed only in a shop's fitting room and billed (shops.redeemAtShop, shops.redeemInRoom).Tell players; nothing to configure.
Downed refusals. Dealership, garages and vehicle keys answer error.incapacitated; crafting crafting.downed; shops shops.downed; gunsmith gunsmith.dead; inventory OpenStash (and the export) dead.Handle the new codes if you branch on them.
New configconfig/inventory.lua HEALING; config/chat.lua STAFF_PERMISSION. Missing keys take their defaults.
Removed configconfig/needs.lua PUSH_MS, PUSH_DELTA. Delete them.
New ACLcommand.opx.needs.set for /opx.needs.set.
New codesmoney.tooLarge (from AddMoney / RemoveMoney), character.nameInvisible, vehicle.registering (from AddVehicle), doorlock.error.busy (door write exports), calls.error.contactsFull, inventory.error.full_health, error.incapacitated.
Changed behaviourMedical items heal and are refused at full health; uniforms are billed on Save; a full contact list refuses instead of trimming; the garage door on foot opens the list; a crate is put down when its carrier gets in a car; a player-opened stash can be unloaded when idle; a lift no longer returns to floor 0 when a rider leaves.
Removed locale keyscalls.placed, calls.ringing, garages.prompt.driveIn. garages.broughtOut / storedAway take {vehicle} instead of {plate}. An override of them must follow.
New in lib/ and coreOPX.Life.Down, OPX.Storage.EnsureColumn, OPX.Schema.AddColumns. Ask OPX.Life.Down at any door a player could press from the floor.
Contractsappearance client PriceWardrobe, server PricedRoom and the bill option of AllowClothingSave; needs server contract.
Other new net eventsopx:net:appearance:roomAlive, opx:net:calls:forget (page channel calls:forget), opx:net:spawn:waiting. The garages, crafting, dealership and gunsmith answers echo the key only when the server knows it.
Databaseopx77_crafting_orders.token is added on boot by OPX.Schema.AddColumns; if it cannot be added, boot is degraded (schema failed). The database user needs ALTER.
Framework docs moved (PRs #121, #122)The framework README is now a front page; its manual is docs/MANUAL.md and the workflow CONTRIBUTING.md. New: docs/IN_GAME_CHECKLIST.md, docs/PLATFORM_REQUESTS.md.

opx_infinity — PRs #88–#95 (2026-10-04 to 2026-10-05)

Merged into main; the manifest version is still 0.1.2. One table per theme. Before updating a running server or a resource that calls OPX, read Upgrading from PR #87.

Never a name to a stranger (PRs #91, #93)

In roleplay a name is learnt by meeting someone. The data below is removed at the server, not hidden on the page. Overview: Privacy.

What players see nowBeforePages
No name over anyone's head: every remote plate is hidden.The character name (and before that the account gamertag) over every player.character client
Chat lines signed Player #12 / Joueur #12.The character name, else the gamertag, else part of the account id, to the whole server.chat
A call names a person only if they are in your contacts; otherwise Unknown caller. "X joined / hung up / declined" follows the same rule.A third person on a call learnt every name; a contact offer told its sender the other's name.calls
The give list reads To your left · 1.2 m with #12.#12 (PR #90 briefly showed the character name).inventory
A dealership offer reads You are being offered a {model}; the seller's list Sell to the person in front of you (#12).The seller's and buyer's account names.dealership
A duo emote invitation reads The person next to you invites you: {emote}.The asker's name, and the other's name in every notice.animations
The eye's Show server ID row copies the server id only.Identifiers: server id and citizen id.target
Staff still see who is who on their name tags.The tags read the public state bag.admin
Under the hoodPages
The player state bag carries no key (citizenId, name, charInfo, job, gang, username, life are cleared once). GetPlayerState / GetPlayerName / GetPlayerIdentity stay on the client contract and answer empty.State bag, Writing a resource
Probes closed: a give checks reach first (too_far for any id out of reach); a dealership offer always goes out and only the buyer learns they cannot pay; an id outside the showroom always answers buyerNotInZone.inventory codes, dealership codes
The doorlock state push is { id, state }: no by. The staff tag list now carries character and citizenId.doorlock events, admin events

Keys: one press, one owner (PRs #90, #92, #95)

ChangePages
E (garages, dealership, fitting rooms, teleports, lifts, doors): when two spots are in reach, the nearer one acts and only its key-strip row is drawn. A lockpick under way holds E. With the list open, E closes it.One press, one owner
X: a cancelable bar > ringing call (decline) > outgoing call (withdraw) > carried crate > own emote > call in progress (hang up). Cancelling an eat during a call no longer hangs up; X with a crate during a call only drops the crate.progress, calls
Y answers a ringing call and the hotbar peek stays silent; otherwise Y peeks.calls
Escape closes only the top layer (menu, form, panel, door panel, chat, call hologram); inside a submenu it goes back one level.The WebUI page, menu
H, Y and X ignore a press while chat or a form holds the keyboard. E during a bar or while down no longer toasts anywhere but at a teleport or lift. The crate-drop hint names the key the player rebound.calls, hauling
Ranks are config: KEY_PRIORITY. Only mappings bound to the same physical key contend.Core configuration

Menus, eye and toasts (PRs #88, #90, #95)

ChangePages
Every staff and player row names its action (Spawn a vehicle, Give or take money, Lock door / Unlock door, Lock vehicle / Unlock vehicle, Take elevator: {place}, Open fitting room everywhere). The confirmation's second row repeats the action. Saved staff spots and outfits are deleted only after a confirmation.admin, target
Menu rows take confirm = true or { title, yes, no }: a two-row level whose first row keeps things as they are.Confirm rows
A danger eye row asks for a second click (Confirm: …) unless it sets confirm = false; any row can opt in. This includes rows other resources add with AddTarget.target row, AddTarget
One toast per refusal: no raw codes on screen, no second toast after OPX.Refuse. OPX.Refuse takes params.OPX.Refuse
One toast kind per situation: too fast, rate limited and busy are warnings; too far, not allowed, no permission are errors (OPX.Result.Kind).OPX.Result.Kind
Money reads 4,500 €$ / 4 500 €$, €$ (bank) / €$ (banque) everywhere (OPX.Locale.Money). The HUD's money captions are translated.OPX.Locale.Money
The garage list reads Villefort Cortes · 8XK2L0P, the same name as the key (OPX.Vehicle.DisplayName).garages
French catalogues fixed (accents, vous); every key exists in both languages; no raw key flashes before the catalogue arrives.Locales
The gunsmith's Use workbench / Open armoury stock rows work (they did nothing).gunsmith

Player flows (PRs #88, #90, #94)

ChangePages
Spawn: a Where I left off card for a character with a stored position. "Spawned at …" appears after the move; a refused move says so.spawn
Inventory: Give and Drop on a stack ask how many; refusals say the real reason once; the receiver reads only +2 Water; four hotbar presses inside one bite eat one item; the trunk panel's Close works; Escape closes the innermost dialog first.Give and drop
Calls: a ring that dies updates the caller and files a missed call on both sides; a disconnect while ringing is said; a contact offer says sent / declined / not answered; going down closes the hologram.calls
Chat: a second line within 800 ms says it was not sent.chat
Garages: sitting in someone else's car at the entry says Only your own vehicles go in here; one toast naming the garage. Dealership: a failed hand-over says the car waits in the garage; a zero commission is worded.garages, dealership
Hauling: the truck leaving mid-load ends the load; a refused pickup frees the hands at once; the drop hint appears after the lift.hauling
Money notes: You took 1,000,000 €$ out as notes. / You put 4,500 €$ in notes back in your wallet.inventory
Shops: You cannot afford that: {total} needed; a paid uniform says Paid {total} at {shop}; a queued outfit save is not dropped. Crafting: going off duty closes the bench.shops, crafting
Join: the fitting room's timer waits while the name form is up; a short name reads Each name takes between 2 and 32 letters. /opx.delete <id> needs confirm. Doorlock: a pick that opens and breaks says both.character, doorlock

Reach and pace (PRs #92, #95)

ChangePages
The server allows REACH_SLACK (1 m) beyond the drawn radius at garages, dealers, fitting rooms, teleports, lifts, shops, crafting and gunsmith benches and vehicle keys: a press at the edge of a marker is no longer refused.OPX.Spots.ServerReach
Lifts scan every 500 ms (SCAN_MS, was 2000); teleports allow 6 requests per window (REQUESTS_PER_WINDOW, was 4). Lockpicking uses doorlock's REQUEST_MS. A downed rider reads Not while you are down.elevators, teleports
Character code fallbacks match the config (autosave 300 s, heading report 5000 ms, 3 slots, row ceiling 60).character

Creator surface (PR #89)

ChangePages
New server exports: reads (GetPlayers, GetJobs, GetGangs, GetItem(s), GetInventory, CanCarryItem, vehicles and keys), runtime items (RegisterItem), use handlers, OpenStash, Notify, server-judged progress bars, crafting benches.Server exports
New client exports: eye rows (AddTarget…), key-strip groups (ShowPrompt…), panels, crafting bench, local reads.Client exports
New public events, e.g. opx:on:inventory:items, opx:on:progress:finished.Events

Upgrading from PR #87

WhatDo
Doorlock contract codes renamed (API change). CreateDoor / EditDoor / RemoveDoor and the doorlock contract's Create / Edit / Remove answer doorlock.error.<code> instead of doorlock.<code> (e.g. doorlock.save_failed → doorlock.error.save_failed, doorlock.bad_door → doorlock.error.bad_door).Update any res.error == 'doorlock.…' comparison. doorlock.unknownDoor, doorlock.badState, doorlock.noDoor, doorlock.notStaff are unchanged. See CreateDoor.
doorlock.bad_<field> → doorlock.error.bad_field on an edit with a wrongly typed field; the field is in res.detail.Branch on bad_field and read detail.
Eye danger rows confirm by default (API behaviour). A row added with AddTarget and danger = true now needs two clicks.Add confirm = false to keep one click.
Player state bag is empty. Open77.state.player(id) no longer has citizenId, name, charInfo, job, gang, username, life.Use the client export GetPlayerData (own player) or the server exports (others). Never relay a name to a stranger.
Net payloads changed (module code and anything listening to them).opx:net:animations:invite lost its name argument: it is now (inviteId, what, inviteMs); opx:net:animations:notice params carry no name; opx:net:dealership:offered has no seller; opx:net:doorlock:state has no by; opx:net:calls:state names are per viewer (Unknown caller); opx:net:inventory:nearby rows are { id, distance, side? }; opx:net:chat:message author is Player #<id>; opx:net:spawn:close comes twice (second with result, placed); opx:net:spawn:offer has resume; opx:net:runtime:notify has params and a warning kind.
Toast kinds. OPX.Refuse, OPX.NotifyLocale and OPX.Toast.Locale turn error/warning into the situation's kind (OPX.Result.Kind).Nothing, unless you relied on "too fast" being red.
OPX.Refuse(source, code, operation, icon, params)Optional 5th argument. Remove a NotifyLocale that followed a Refuse only to carry params.
New configconfig/client.lua KEY_PRIORITY; config/shared.lua REACH_SLACK. Missing keys take their defaults.
Changed defaultselevators.SCAN_MS 2000 → 500; teleports.REQUESTS_PER_WINDOW 4 → 6. A copied config keeps the old value: update it.
Removed configcharacter.ORIGINS (nothing read it). Delete the block.
Removed locale keys26 unused keys (#90), e.g. target.hint, target.back, inventory.title, inventory.ui.slots, calls.incoming.*, calls.live.*, inventory.notify.received; chat.author.unknown → chat.author.player; target.identify.* reworded (no {citizen}). An override of one of them is now unused.
New in lib/OPX.Locale.Money, OPX.Vehicle.DisplayName, OPX.Result.Kind, OPX.Spots.ServerReach, the key contest (OPX.Spots.Key.Owns, Contend, Shows, Layer, TopLayer…). A module sharing E or X should enter the contest.
progress.State()Adds cancelable, cancelKey, cancelledAtMs.
/opx.delete <id>Now /opx.delete <id> confirm.

opx_infinity — PRs #58–#87 (2026-10-02 to 2026-10-04)

Merged into main; the manifest version is still 0.1.2. One table per group of PRs, newest first. Before updating a running server, read Upgrading from PR #57.

Animations with everyone (PR #87)

ChangePages
The emote picker (F3, /e) offers every platform animation: 104 on op77.123, in 12 families (Sitting, Dance and music, Work, On the move…). Animations a newer build adds appear at the next start. One-shot gestures (wave, shrug…) play once for their real length; the picker shows 16 per page and has a search.animations, catalogue
Emotes with a nearby player: carry or be carried, escort, hand something over, look after someone, two shortcuts, or any two animations, one each. From the picker's With a nearby player or /e with <move|name> [name].With a nearby player
The nearest player within 3 m is invited and answers from a menu or with /e accept / /e decline; an invitation expires after 15 s, the distance is checked again on accept, and the stop key ends the pair for both. Invitations count against RATE_LIMIT.animations
New config PLATFORM and SHARED; DISABLED also takes platform profile ids and paired-move ids. New events opx:net:animations:duo, invite, uninvite, reply, notice; offer now arrives in parts.Configuration, Events

Door locks (PRs #70, #80, #86)

ChangePages
New module doorlock, a port of ox_doorlock: doors open by job, gang, grade, key item, character or code. Press E at the door or use the eye's Lock door / Unlock door. Doors can relock themselves and play a sound.doorlock
A pickable door takes a lockpick: one progress bar per step, the rolls are made by the server. The lockpick must still be in the bag, and the player not down, when the attempt ends.At a door
Staff panel: /opx.doorlock [closest], the F9 World → Manage door locks row or the eye's Manage door. A searchable, paged table of doors; settings in tabs (General, Characters, Groups, Items, Lockpick, Sound); Pick in world, Give me a key, copy and apply settings.Staff panel
Admins are asked for the key or code like anyone else. The bypass is the plain right opx.doorlock.bypass and only works with STAFF_BYPASS = true (off by default). Staff holding command.opx.doorlock.save see the door's code in the panel.Who may turn a door
Two backends: networked (the platform's open77_doors, refused to everyone server-side) and local (each client holds its own copy — weaker).Backends
Doors saved by the first version carry over at boot; keys already cut keep opening their door.Migration
Creator exports GetDoor, GetDoorFromName, GetAllDoors, SetDoorState, SetDoorLocked, CreateDoor, EditDoor, RemoveDoor, and the public server event opx:on:doorlock:changed.Server exports → Doors, Events

Staff tools (PRs #67, #79, #82, #83)

ChangePages
Staff menu lists are paged at 20 rows and redraw on their own thread; screens built over a whole catalogue no longer close the menu.admin
New raw rights opx.admin.immune (staff commands refuse you, target_protected) and opx.admin.override (act on an immune player, audited). command.* grants do not include them.Raw rights
Grants are narrower: bag commands on a weapon item also need the matching weapon.* command; observe without self.noclip ends after 2 minutes; a typed vehicle id must be within VEHICLES.REACH (100 m) unless you hold opx.admin.vehicle.anywhere; player.health 0 needs the kill grant (kill_not_granted).What a grant does not imply
One heavy request per operator at a time (busy); one announcement per 10 s server-wide (ANNOUNCE.COOLDOWN_MS). Bag views, character lists and finds are audited.admin
A prompt shown to -1 is refused instead of reaching everyone; the pause key asks a dismiss = 'ask' panel before closing it; the menu spec takes yield for long lists.prompts, panel, menu

Vehicles, places and world (PRs #69, #78, #81, #85)

ChangePages
Holocalls go to contacts only; offering your contact again to the same player waits 30 s. A 3-way leave says who hung up.calls
Dealership: the offer screen closes when the offer is withdrawn (new opx:net:dealership:withdrawn); a bought car is handed over beside the dealer if the spot is taken, or waits in your garage. Refunds and commissions reach the right character, even offline.dealership
Garages refuse to store a car with passengers (garages.passengers). Cars out at a server stop keep their condition.garages, vehicles
Hauling only sells from a truck you own or hold a key to (new server contract vehiclekeys.Holds).hauling, vehiclekeys
Lifts: E away from a lift says nothing; lifts no longer go dead after 10 minutes and are reclaimed after a restart; a downed player cannot ride. Teleport pads no longer stick on "already on your way". weather.SetTime is validated.elevators, teleports, weather
Creators: one marker, key and scan helper for every spot module, OPX.Spots.* (lib/client/spots.lua).Spots on the client

Inventory and economy (PRs #65, #68, #74, #76)

ChangePages
The inventory key just opens the bag; it never picks up a pile you stand next to.inventory
Every droppable item shows a pile prop the platform hosts (MODEL aliases; a weapon on the ground is a decorative prop).Pile models
Bags, trunks and stashes with more than 12 stacks save again. An item in use cannot be moved, split, sorted or used twice (in_use); moving a drawn weapon puts it away first (holstering). You only see piles in your own instance (new opx:net:inventory:bucket).inventory, Events
A money hook can no longer let one balance pay twice; crafting queues cannot exceed their cap; shop doors and the gunsmith chest have cooldowns; lockpicks are re-checked when an attempt ends.character, crafting, gunsmith

Appearance and body (PRs #64, #66, #75, #77)

ChangePages
Joining no longer drops your face or leaves you in the default look: the face goes on once the platform has reset the body. A character switch or a resource restart no longer leaves you invisible to others.Joining the world
Fitting-room purchases are billed by the server before the look is saved: if you cannot pay, your old clothes come back; if the save fails, you are refunded. Saved outfits and share codes grant one save of exactly that look.shops, appearance server
Your look is sent to others at most every 2 s, and an unchanged look is not sent again. Quitting while down no longer gives a free revive.appearance events, downed

Client budget (PRs #63, #84, #85)

ChangePages
Callbacks that ran past the client's per-resume instruction budget were split: the staff search no longer closes the menu, F9 no longer needs two presses. Joining, blips, the fitting room, panels, chat commands and garage lists are lighter.Two runtimes
For module authors: keep handlers cheap and yield in loops; the test suite has a budget meter.Writing a module, Contributing

Core and WebUI audit (PRs #71, #72, #73)

ChangePages
Server toasts appear at the configured position (NOTIFY_POSITION, now top_right); a long toast is cut (title 96 bytes, message 384) instead of refused.OPX.Notify
A refused gate release is retried, so players are not left on the loading screen. On client stop, keyboard and cursor are released first (OPX.UI.ReleaseAllFocus); Modules.Stop yields between modules.core
Both export halves go through one shared gate, OPX.Export; export audit lines are kept per calling resource.core shared
The WebUI bundle is 12% lighter; payload hardening (records(), own()) and focus fixes (hologram, an unclosable menu, keys meant for a view opened over the menu).The WebUI page

Loading screen (PR #62)

ChangePages
New module loading (config/loading.lua): the HUD and other views step aside during the game's own loading screen, and loads in play (teleport, lift, respawn) get the OPX cover with progress. The join never shows the cover. Needs an updated client and the screen.read permission.loading

Exports, eye and menu fixes (PRs #58–#61)

ChangePages
Creator exports are published on hosts where exports is a callable table (Open77). Before, the boot log said the host had no exports and nothing was published.For creators
The target eye: the row under the pointer steps out like the menu's chosen row, and every row of a column appears together.target
A menu reopened by a form's Enter no longer fires the row under the cursor: a late press waits for its release.menu

Upgrading from PR #57

Most of this is drop-in. Check these:

WhatDo
SHARED.NOTIFY_POSITION = 'top-right'Now spelled 'top_right'; the hyphen is still accepted.
config/doorlock.lua from PR #70Rewritten in PR #80: LOCKPICK.ITEM → LOCKPICK.ITEMS (list), LOCKPICK.DEFAULT_DIFFICULTY → LOCKPICK.DEFAULT, LOCKPICK.BREAK_CHANCE → { SUCCESS, FAIL }, SOUNDS → { LIST, DEFAULT = { LOCK, UNLOCK } }; in DOORS, X/Y/Z → COORDS, LOCKED → STATE, DIFFICULTY → LOCKPICK_DIFFICULTY. Saved doors carry over. See Migration from the first table.
Staff opening every doorSTAFF_BYPASS is now false and the right is opx.doorlock.bypass (was command.opx.doorlock.bypass). Grant it and set STAFF_BYPASS = true to keep the old behaviour.
Staff immunityopx.admin.immune, opx.admin.override and opx.admin.vehicle.anywhere are raw rights: add them to acl.jsonc explicitly.
New configconfig/loading.lua; admin: PLACEMENT.OBSERVE_MS, ANNOUNCE.COOLDOWN_MS, VEHICLES.REACH, LINKS.DOORLOCK; animations: PLATFORM, SHARED. Missing keys take their defaults.
Removed for module codeOPX.UI.Serve (use OPX.UI.On plus OPX.UI.Answer), OPX.Surface.HasFocus, the local event opx:on:chat:submitted, the net events opx:net:shops:bill and opx:net:shops:restore; admin codes bad_amount, bad_type, vetoed, doors_unavailable.
Changed signaturesappearance.OpenWardrobe(playerId, options) and AllowClothingSave(playerId, owner, options) take options; weather.SetTime returns a Result; opx:net:animations:offer arrives in parts.

opx_infinity — documentation audit fixes (PR #57, 2026-10-02)

Merged into main; the manifest version is still 0.1.2.

ChangePages
Elevators are ridden with the key opx.elevators.use (E by default, KEY; DEFAULT = false declares none, leaving OpenPanel the only way in). A prompts row names the key within USE_RADIUS of a lift; prompts is now optional.elevators
Progress bars marked cancelable can be cancelled with CANCEL_KEY (opx.progress.cancel, X by default; DEFAULT = false = no player cancel). The page channel progress:cancel is gone; progress:show carries { label, durationMs, cancelable, cancelKey }.progress
Weather commands go through OPX.Command.Register (suggested, aliasable); answers are toasts (OPX.CommandNotice) or read-backs (OPX.CommandResult). opx:net:weather:notice and the legacy chat:ready / chat:addSuggestions handling are gone.weather
Command read-backs (opx:net:runtime:commandResult, OPX.CommandResult) carry kind ('info'|'error') instead of type, so a refused read-back draws in the error colour.Runtime events
Blips: one pin per garage location, at its menu point (the entry door is not pinned). Garage and dealer pins come from config (GARAGES, SPOTS) plus legacy rows adopted at boot, own bucket only.blips
An ammo slot holds at most the ammo item's AMMO.MAX (rounds per box), never above MAX_STACK.inventory
Dealership: server Accept(buyer, token, yes, destKey?) and client Decide(yes, garage?) take the buyer's garage (nil = vehicles default; unknown = dealership.noSuchGarage). A failed company deposit goes to the new table opx77_company_pending and is paid in by a sweep (boot, then every 60 s), never twice. opx:on:dealership:sold carries garage and pending.dealership
Inventory contract: new server HolsterWeapon(playerId) (errors bad_argument, not_armed). /opx.admin.weapon.holster works through it and no longer always refuses.inventory, admin
OPX.Storage.EnsureIndex and OPX.Schema.AddIndexes add an index to a table that predates it. idx_opx77_characters_seen is now added automatically: no manual ALTER any more.Storage
Config removed: SERVER.AUTOSAVE_MS, SERVER.SAMPLE_MS, the provider key of the MODULES comment in config/shared.lua (only enabled is read), the menu's STATUS_MS.Core configuration
Downed SUSPENDERS default is { admin = true } (opx77_admin removed).downed

opx_infinity — creator API round 2, keybinds, short paths (PRs #53–#56)

ChangePages
New server exports SetJob, SetGang, RemoveJob, RemoveGang, SetDuty, GetMetadata, SetMetadata (per-resource ext.<resource>.<key>, limits in SERVER.EXPORTS.METADATA), IsDown, Revive. New client exports Subscribe/Unsubscribe for a fixed list of client events. New codes export.badValue, export.tooLarge, export.notSubscribable.Server exports, Client exports
One host constant OPX.Host.KEYBINDS_CHANGED = 'open77:keybinds:changed' for every module. Four modules used to listen on onKeybindsChanged, which nothing raises, so they never redrew a key after a rebind.Runtime events
Clothing pictures are named by a 10-digit hash of their slug; every shipped path stays at 47 characters or less (a test enforces it), because one over-long path fails the whole resource on a client. The calls view registers its state handler before wiring the page.appearance
Notes on open77_validate false positives; the admin model commands answer models_unavailable because players.setModel is in no published build.Troubleshooting

opx_infinity — creator API, wardrobe pictures, calls (PRs #50–#52)

Merged into main after the 2026-10-02 changes below; the manifest version is still 0.1.2.

ChangePages
Creator API. Other resources can call curated server exports and client exports and listen to public server events. Who may call is SERVER.EXPORTS.READ/WRITERS (writers empty by default) and CLIENT.EXPORTS.CALLERS. New codes export.callerDenied, export.badArgument, export.booting, export.mustAwait. New core function OPX.Publish.For creators, core config
New contract functions: character.AddMoneyOffline, character.PublicView; inventory AddToStash, RemoveFromStash, CountInStash, RemoveWhere (codes stash_namespace, stash_cap); chat server contract Send, Broadcast; vehiclekeys.Revoke, RevokeAll; vehicles.SetState (codes vehicle.impounded, vehicle.busy, vehicle.badState).character, inventory, chat, vehiclekeys, vehicles
New hooks job:beforeSet, gang:beforeSet (codes job.vetoed, gang.vetoed) and money:beforeAddOffline. The internal job and gang events are now also raised when a membership is removed.character
Calls: a Cancel button and the opx:net:calls:withdraw event take back an outgoing call without touching a live one; leaving a call withdraws a pending join invite. The unused dismiss/repop card and CARD_DWELL_S are gone.calls
Fitting room: garment names and pictures (garments-*.lua, web/images/clothing/, tools/generate-garments.mjs); the panel tiles spec gains optional labels and images; categories read Top, Jacket, Pants, Shoes.appearance, panel

opx_infinity — main after 0.1.2 (2026-10-02)

Merged into main; the manifest version is still 0.1.2.

ChangePages
New module vehiclekeys: keys are inventory items (vehicle_key, metadata plate, label) that lock and unlock a vehicle from the target eye. Garages' bring-out and dealership purchases hand out a key; staff get /opx.admin.vehicle.key.vehiclekeys, garages, dealership, admin
Inventory contract: CountWhere, TrunkLocked, AddToTrunk, RemoveFromTrunk, CountInTrunk; new refusal codes locked and hands_full.inventory
Hauling reworked into a crate job sold to drop-off NPCs (new IsCarrying, DROP and SELLERS events, hauling_crate item, per-site models and NPCs). The HAUL_PAY_PER_CRATE tunable is now applied. New manifest permission world.npcs.hauling
Hauling: an unpaid sale puts crates back anywhere they fit, and records the rest for a refund (not_paid_lost, audit event hauling.crateLost); load and sell also check the routing bucket (wrong_bucket). Vehicle keys accept any printable ASCII plate of 1–16 characters, matched exactly, and a raising lock native answers lockRefused.hauling, vehiclekeys
HUD: contract ApplyVanilla; the shipped config hides every vanilla HUD component except the minimap.hud
Calls: one call screen (the holo); hanging up also withdraws an outgoing call that was not answered yet.calls

opx_infinity 0.1.2 (September 2026)

The first release of the single resource. It replaces twenty-one opx77_* resources (sixteen of them were documented on this site).

ChangeRead
One resource, opx_infinity, with a core runtime and modules under modules/<id>/.Overview
Modules talk through in-process contracts (OPX.Api); no Open77 exports are published.Modules and contracts
Event names moved to opx:net:, opx:on:, opx:in:.Events and channels
Commands moved from opx77.* to opx.*, with optional short aliases.Core configuration
One WebUI page for every screen.The WebUI page
Garages, dealers and showroom cars are written in config; legacy tables are adopted at boot.garages, dealership
Modules that had no page on the old site: spawn, entry, theme, diagnostics, downed, target, progress, panel, calls, blips, shops, crafting, gunsmith, hauling, teleports, garages, dealership, clothing, vehicles.Modules

opx_lib 0.4.0 (September 2026)

The client library opx_infinity depends on. Delivered as files, loaded with require('@opx_lib'), client only. See opx_lib.

On this page