Scripting limits
This reference lists the current developer-facing Lua, resource, API, queue and configuration limits audited in source. It distinguishes fixed capacities, compiled defaults and operator-configurable bounds. It does not treat every internal rendering/physics constant as a scripting limit or guarantee that the maximum workload will run smoothly on every machine.
- All 84 numeric server settings: default, minimum, maximum and units
- All 59 player statistic ranges
- Allowed appearance, equipment, monster and other catalog keys
Units: KiB = 1024 bytes; MiB = 1024 KiB; GiB = 1024 MiB. String limits count bytes unless a separate Unicode-codepoint limit is stated. Most position-based Lua functions use metres; server sync-distance configuration and voice range use centimetres. Times are milliseconds unless stated otherwise. Coordinates must be finite; numeric validity is not a promise that playable terrain exists at that position.
Lua VM and execution
| Limit | Client | Server | Scope / behaviour |
|---|---|---|---|
| Lua VM allocation budget | 1024 MiB | 1024 MiB default; configurable 4..4096 MiB | Per resource/VM. Allocated on demand, not reserved on resource startup. This is not a cap on total process/native allocations, textures, audio or database buffers. |
| Instruction budget | 10000000 | 10000000 default; configurable 10000..1000000000 | Reset on each protected script chunk/callback invocation. Exceeding it raises a Lua error. |
| Execution timeout | 5000 ms | 5000 ms default; configurable 10..60000 ms | Cooperative Lua-hook check, not an OS watchdog and not preemption of a blocking native API call. |
| Hook interval | 10000 instructions | 10000 default; configurable 100..1000000 | Budgets are checked in samples; execution can pass a nominal boundary before the next check. |
Server settings are lua.memoryLimitMB, lua.instructionLimit, lua.timeoutMs, lua.hookInstructionInterval. They do not propagate to the client. An existing configuration retaining older values overrides the new server defaults. The production and bundled GothicRP-test configuration templates now use the expanded Lua/DB defaults.
Lua is the sandboxed LuaJIT/Lua 5.1 environment with JIT disabled. Base, table, string and math libraries are exposed; general filesystem/OS/module-loading access is not. dofile, load, loadfile, loadstring, getfenv, setfenv, collectgarbage, newproxy, coroutine, io, os, package, debug, jit, ffi and string.dump are unavailable. Lua bytecode is rejected. Increasing capacity does not weaken sandbox, hash, ownership, path or network validation.
Timers, handlers and commands
| Capacity | Client, per resource | Server |
|---|---|---|
| Timers | 8192 (previously 256) | No additional fixed timer-count cap in the server Lua API; still subject to memory and execution cost. |
| Timer callbacks selected per tick | 256 (previously 32), oldest deadline first | No matching fixed per-tick callback-count cap. Due timers are processed by the server runtime. |
| Event handlers, total | 8192 (previously 256) | No additional fixed count cap in the registration API. |
| Handlers for one event | 512 (previously 32) | No matching fixed per-event count cap. |
| Declared remote event names | 4096 (previously 256) | No additional fixed registration-count cap. |
| Key bindings | 1024 (previously 128) | Client-only feature. |
Both timer APIs require an interval of at least 10 ms; interval and repeat-count parameters are unsigned 32-bit values. Repeat count 0 means repeat indefinitely. This is not a precision guarantee: timer dispatch follows runtime ticks, and missed intervals are not replayed in an unlimited catch-up loop. Client timers and key bindings accept at most 32 callback arguments. Cancelled client timers leave their storage slots when the tick removes cancelled entries. Client setTimer returns 0 on rejection; handler/key-binding registration returns false. Check return values.
Client selection is deadline-ordered and snapshots timer IDs before callbacks. Large batches of fast repeating timers therefore do not permanently starve later timers; callbacks may still be delayed by load. Do not use thousands of 10-ms timers as a substitute for batching work.
Server local event dispatch has a recursion-depth limit of 32; nested command execution has a depth limit of 16. Command names are nonempty and at most 64 bytes. executeCommandHandler accepts at most 32 arguments with at most 2048 bytes of converted argument text in total. These recursion guards are not capacities for how many independent events/commands a gamemode can declare.
Network events and IPC
| Field | Fixed limit |
|---|---|
| Resource name in an event envelope | 96 bytes |
| Event name | 128 bytes |
| Arguments per serialized resource event | 32 |
| One event string | 32 KiB |
| Complete serialized resource event | 64 KiB, including headers, names and all arguments |
| Client IPC payload | 64 KiB |
| IPC buffered bytes / queued frames | 256 KiB / 256 frames; separate guards |
Values transported directly are nil, boolean, integer/number and string, not Lua tables, functions or userdata. Client local event argument conversion also uses the 32-argument/32-KiB-string guards. Server-local callbacks should not be confused with serialized network messages. Lua numbers do not represent every 64-bit integer exactly: keep large opaque identifiers as strings where the API specifies strings.
A 1-MiB JSON document cannot be sent as one event string. The expanded JSON limit is for local serialization/storage; the event envelope is unchanged. For bulk application data, use bounded application-level chunks or appropriate resource/HTTP/database facilities. Validate sender permissions, payloads and request rates. Packet/IP budgets in Server configuration limits still apply to the connection as a whole.
Resource files and downloads
| Capacity | BUILD81 value | Previous value |
|---|---|---|
| One Lua source file | 128 MiB | 64 MiB |
| One resource file | 256 MiB | 64 MiB |
| All files in the client download catalog | 32 GiB | 8 GiB |
| Catalog file count | 262144 | 65536 |
| Serialized catalog document | 128 MiB | 64 MiB |
The download totals cover client/shared scripts and declared files, not server-only scripts. Server-only Lua files still have their individual Lua source-file cap. Server, launcher and client share these constants. A resource exceeding the old client limits will not work with old launchers simply because the server was updated.
Client catalog relative paths are at most 512 bytes, with at most 128 bytes per segment; file URLs are at most 2048 bytes. Resource names used across the whole scripting/network stack should fit the stricter 96-byte event-name envelope field even though filesystem-side validators allow longer names. Paths must stay inside the resource, use the accepted relative-path syntax and refer to declared files; absolute paths and traversal are not enabled by larger file budgets.
JSON
toJSON/fromJSON now allow 1 MiB, nesting depth 64 and 65536 cumulative entries across the document (previously 32 KiB / 16 / 2048). Object keys remain at most 256 bytes. Arrays must use contiguous positive indices; mixed array/object key tables, cyclic references and non-finite numbers are rejected. Failure returns false plus an error description. The byte cap applies to the serialized document, not to the full native/Lua memory consumed while processing it.
Database and password work
| Compiled default | BUILD81 value | Configuration / scope |
|---|---|---|
| DB workers | 8 | Global worker pool |
| DB connections | 16 | Per resource |
| Pending DB queries | 1024 | Per resource |
| SQL statement | 4 MiB | Per query |
| DB result | 50000 rows and 64 MiB | Per result, both guards apply |
| DB timeouts | connect 5000 / read 15000 / write 15000 ms | Connection options may reduce, but not exceed, configured timeouts; minimum 100 ms |
| Blocking dbPoll | 100 ms maximum | Configurable; negative timeout becomes non-blocking and excessive timeout is clamped |
| Password workers / queue / pending per resource | 4 / 1024 / 256 | Password hashing/verifying service |
All configurable ranges are listed in Server configuration limits. Result overflow is reported as a database error (client result-limit error 90001), not a successful partial result. Free/poll results and avoid unbounded result retention; use SQL pagination. MariaDB/MySQL can impose additional server-side limits, including its packet and connection settings, which this application does not automatically change.
Passwords for hash/verify are at most 256 bytes; encoded hashes at most 512 bytes. Default Argon2id parameters remain 65536 KiB memory, 3 iterations, parallelism 1; larger queue capacity does not weaken hash security. DB multi-statements, multi-queries, local infile and automatic reconnect are not enabled as a side effect of this update.
requestRemote
| Limit | Value |
|---|---|
| Workers / queued work / pending per resource | 8 / 256 / 32 (compiled defaults) |
| URL | 2048 bytes |
| Request body / response body | 4 MiB / 8 MiB |
| Request headers / response headers | 128 / 256 |
| Aggregate header bytes | 64 KiB |
| Header name / username / password | 128 / 512 / 512 bytes |
| Connection attempts | 1..3; default 1 |
| Connect timeout | 500..10000 ms; default 5000 |
| Total timeout | 1000..30000 ms; default 15000 |
| Redirect count | 0..3; default 2 |
Allowed methods are GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS. Only the supported HTTP/HTTPS destinations are accepted. Loopback/private-address restrictions, DNS and redirect validation stay enabled in production. Capacity or validation rejection returns an error; asynchronous transfers also report completion errors. Keep callbacks short. The larger download-catalog file limit is not the requestRemote response limit.
World entities and text
| Feature | Fixed capacity / range |
|---|---|
| Active world items | 16384 globally (previously 4096); quantity 1..1000 per spawn |
| Active NPCs | 8192 globally (previously 2048) |
| Active monsters | 4096 globally (previously 1024) |
| NPC route | 1..1024 points (previously 256); each wait 0..3600000 ms; speed 0.1..12 m/s |
| NPC scripted movement arrival radius | 0.01..5 m |
| Monster scripted movement | speedScale >0 and <=1; Lua arrivalRadius 0.01..5 m |
| Monster max health / attack damage | max health >0 and <=1000000; damage 0..100000 |
| NPC name | 3..16 bytes, without control characters |
| Monster/nameplate display text | At most 96 UTF-8 bytes and 48 codepoints |
| 3D text objects | 1024 per resource / 8192 globally (previously 256 / 2048) |
| 3D text content | Nonempty, at most 256 bytes AND 128 Unicode codepoints; no control characters/newlines |
| 3D text draw distance / scale | 1..1000 m / 0.25..5 |
| Scripted world positions | Finite coordinates within +/-1000000 m per axis where the Lua position validator is used |
Global capacities are shared across resources. Creation above a capacity is rejected; it does not expand the cap or authorize mutation of another resource's entities. More accepted entities do not mean that placing all of them in one visible area is performant. Navigation, authoritative sync distance, client streaming and actual hardware still matter.
Navigation
The compiled custom-collision capacities are 16384 blockers globally / 4096 per owner (previously 4096 / 1024). The internal settings validator permits global capacity up to 65536, with owner capacity positive and no greater than global capacity. These are not extra numeric serverconf.cfg options.
- Lua custom blocker coordinates: +/-100000 m; size/height dimensions 0.01..10000 m; polygons 3..16 vertices and must pass geometry validation.
- Navigation artifact: at most 256 MiB and 200000 baked blockers (previously 64 MiB / 50000); polygon cap 16 vertices.
- Compiled search defaults remain grid cell 50 cm, detour margin 1200 cm, maximum slope 45 degrees, maximum path distance 30000 cm (300 m), maximum search nodes 120000.
- Internal settings bounds: grid cell 5..1000 cm; detour margin 0..50000 cm; slope >=0 and <89 degrees; path distance >=grid cell and <=1000000 cm; search nodes 64..1000000.
- Navigation failure/search exhaustion is not permission to cross blockers. See NPC and monster navigation collision.
Player state, appearance and nameplates
- Player names: 3..16 bytes, accepted player-name character set. Appearance, armor, weapons, spells, skills and tattoo selection use validated catalogs, not arbitrary game asset paths; see Lua API catalogs.
- Skin RGB and display RGBA components: integers 0..255. Tattoo catalog: 12 presets. These are valid value ranges, not gamemode capacity budgets, and were not expanded.
- Inventory give/remove amount: 1..1000 per call. Server-controlled cumulative count: ammunition at most 65535, other supported items at most 100000. Ownership and item-specific rules still apply.
- Consumable grant count: 1..100; persistent consumable commit keys at most 96 bytes. This does not authorize trusting client-submitted permanent stats.
- All 59 stat ranges: Player statistic limits. Common HP/STR/DEX values cap at 100000; max-health minimum is 1. Level caps at 1000, magician level at 6; other stats have their own bounds.
- Scripted player damage amount: positive, at most 100000. Unconscious duration: 0 or 250..3600000 ms; configured health arguments must fit the relevant player-health range. Life-state checks still apply.
- Player/nameplate text: at most 96 UTF-8 bytes and 48 codepoints. Nameplate height offset is within +/-100 m.
- Observer-specific nameplates: defaults 250000 pairs globally and per resource; per-resource mutation budget 4096/s with burst 8192. Configurable ranges: Server configuration limits. These are directed observer/target overrides, not persistent database rows. Throttle bulk restore operations and handle rejected mutations.
- Nearby-player radius: 0..100000 m. Focus-query maximum distance: 0.1..1000 m; field of view: 1..180 degrees.
- Lua chat output: nonempty and at most 2048 bytes; chat RGB values are clamped to 0..255. Administrative
sayhas its separate 512-byte message cap. - Animation API uses the 27-key validated animation catalog.
startAnimblend argument range is 0..2 seconds. Catalog keys are not raw montage paths. - Account binding key: 3..24 bytes with its validated identifier syntax; serial: 32 hexadecimal characters. Kick/ban reason: nonempty, at most 160 bytes, valid text without controls. Ban duration: 0 (permanent) or at most 5256000 minutes.
Camera
Camera safety/representation ranges were not changed by this capacity update. There is no 100-metre distance-from-player restriction. Keep engine streaming and server relevance in mind; coordinate acceptance alone cannot guarantee valid/rendered terrain everywhere. See Scripted camera.
| Parameter | Limit |
|---|---|
| Camera command document | 24 KiB |
| Position/focus/path/orbit envelope | Finite coordinates within +/-1000000 m per axis |
| Path points | 1..32 |
| FOV | 15..150 degrees |
| Rotation / orbit starting angle | Absolute value <=360000 degrees |
| Orbit sweep | Absolute value <=3600 degrees |
| Orbit radius / height offset | 0.1..50 m / absolute value <=50 m |
| Command duration | At most 600000 ms |
| Each path point | Duration 1..600000 ms; hold 0..600000 ms; sum of all durations and holds <=600000 ms per cycle |
| Orbit duration | 100..600000 ms |
| Reset blend | 0..10000 ms |
| Camera state document | At most 4096 bytes |
Looped paths/orbits can repeat; the per-cycle duration bound is not a total lifetime cap. Invalid commands are rejected, not silently replaced by a different camera path. Resource ownership and cleanup still apply.
Client audio and UI
- Audio: 128 sound handles per resource / 512 globally (previously 32 / 128). Declared join-time
.oggresource files only. Stop unused handles; an accepted handle is not a promise that hundreds of simultaneously mixed sounds are cheap. - Sound volume 0..1; finite 3D coordinates +/-1000000 m; minimum distance >=0.01 m, maximum distance greater than minimum and <=10000 m. Audio paths must also satisfy the catalog's stricter relative-path bounds.
- UI texture files: at most 256 MiB encoded (previously 64 MiB); decoded width and height remain at most 8192 pixels each. This is not a VRAM allocation guarantee.
- UI attribute name: 1..64 bytes with safe-name validation; attribute value at most 16384 bytes (previously 4096). Event-handler attribute injection remains prohibited.
- GUI append-text retains at most 1000 child lines per target element (previously 200), removing older children. This is not a cap on all elements in an RML document.
- No separate fixed document-count or general
setInnerRMLbyte cap was found in the exposed UI API. Resource ownership, file limits, Lua/native memory and rendering cost still apply; this does not mean unlimited capacity.
Voice
Voice remains a separate real-time stream: 48000 Hz, 960 samples/20-ms frame, two channels; encoded frame at most 512 bytes and voice datagram at most 768 bytes. Default sender budgets are 55 frames/s and 65536 bytes/s; see Server configuration limits for ranges.
Voice range values use centimetres, including setPlayerVoiceRange/getPlayerVoiceRange. A positive finite custom range is normalized to 50 cm..configured voice.maxRange; defaults are whisper 500 cm (5 m), normal 1800 cm (18 m), shout 3500 cm (35 m), maximum 10000 cm (100 m). Voice volume is 0..2; channel ID 0..4294967295. Increasing Lua capacity does not alter voice gain, codec timing or anti-abuse limits.
Source audit and practical use
Audit date: 2026-09-12; expanded-budget 0.1.3 BUILD81, protocol 34. Changes increase capacities, not packet layouts. Main sources:
shared/resource_limits.hpp,resource_event_protocol.hpp,client_ipc_protocol.hpp,scripting/LuaJson.hpp/.cpp,CameraProtocol.hpp,PlayerProgressionCatalog.hpp,PlayerAnimationCatalog.hpp,NameplateValidation.hpp.server/src/config/ServerConfig.cpp/.hpp,server/src/scripting/ScriptRuntime.cpp,server/src/resources/ResourceManager.cpp, resource catalog/manifest, database, HTTP, voice and world implementations,server/enet_server.cpp.client-runtime/src/scripting/ClientScriptRuntime.cpp/.hpp,app/ClientRuntimeMain.cpp, catalog, audio and UI implementations.
The test suite executes the real Lua runtimes with 8192 client timers / 8193 server timers, expanded handler/remote-event/key-binding capacities, JSON boundary and overflow cases, client timer fairness and restart/cleanup. Capacity boundaries are not a full in-game performance benchmark. Use staged loading, staggered timers, asynchronous DB/HTTP, pagination and explicit cleanup even on fast computers. Always check API results and the runtime logs.