Skip to content

VCMP-Lua 3.0: rebuild for safety, built-in libraries and single-file binaries - #23

Closed
rasikhq wants to merge 29 commits into
masterfrom
v2
Closed

rasikhq wants to merge 29 commits into
masterfrom
v2

Conversation

@rasikhq

@rasikhq rasikhq commented Oct 9, 2026

Copy link
Copy Markdown
Owner

VCMP-Lua 3.0 is a rebuild of the plugin. Scripts can no longer crash the server, the libraries are built in, and each platform gets a single binary with nothing else to install.

Why

The 2.x plugin had problems that a patch could not fix:

  • A client could crash the server. Client script data was copied into a fixed 4096-byte stack buffer without a size check.
  • MySQL, HTTP and threads used the Lua state from worker threads. A Lua state is single-threaded, so that use crashed (the "5–10 queries" MySQL crash).
  • Entity handles pointed into vectors or were owned by the garbage collector. Handles dangled, an unstored vehicle vanished at the next collection, and a destructor could delete another plugin's entity.
  • The build was unreproducible. It mixed copied sources, a patched submodule, ~100 MB of prebuilt libraries and premake. The Linux .so had unresolved symbols, and the Windows DLL needed extra DLLs.

What changed

  • Runtime. One owner has an explicit shutdown order, and only the main thread touches Lua. Every server callback is noexcept. Events use stable dispatch, and cancel applies to the innermost event. Timers use 64-bit time. Arguments are checked, and errors read like Lua's own.
  • Entities. The server owns entity lifetime. Handles are validated on every use ("vehicle no longer exists"). The same entity is always the same handle, so == and table keys work.
  • Batteries. These are built in and load with require:
    • LuaSQL (SQLite, PostgreSQL, MySQL/MariaDB), lua-cjson, LuaSocket, Copas, LuaFileSystem and inspect.lua;
    • a non-blocking http module over libcurl;
    • Hash (the 2.x digests plus PBKDF2, scrypt, HMAC and random bytes);
    • sql.format.
  • Config. luaconfig.lua replaces luaconfig.ini.
  • Build.
    • CMake with vcpkg (pinned) and tarballs pinned by SHA256.
    • Linux is built in a manylinux_2_28 image. Each binary exports only VcmpPluginInit and needs only system libraries, which CI checks.
  • Tests.
    • doctest and Lua unit tests, also run under ASan + UBSan.
    • A plugin host that loads the built binary.
    • Integration tests against the real VC:MP 0.4 Linux server with Postgres 17, MySQL 8.4, MariaDB 11 and a local TLS server.
    • A real-client test on Windows.
  • Docs. README, API reference (docs/api), docs/configuration.md, the migration guide (docs/MIGRATION-v3.md), docs/internals.md and examples/.
  • Release. A v* tag builds and tests both platforms and drafts a release with one zip per platform plus SHA256SUMS.

Breaking changes

All of them are listed in docs/MIGRATION-v3.md. The main ones:

  • Config file. luaconfig.ini becomes luaconfig.lua.
  • Entity lifetime. Entities live until :destroy(), not until the garbage collector runs.
  • Removed globals. MySQL, SQLite, Remote, JSON, Thread, Lanes and dbg are gone. Using one raises an error that names its replacement.
  • Async database queries will come once LuaSQL releases its non-blocking API.

Before merging

  • CI green on Linux, ASan and Windows for this branch and for the v3.0.0 tag.
  • Manual load test on a Windows VC:MP server.
  • Publish the draft v3.0.0 release.

🤖 Generated with Claude Code

rasikhq and others added 29 commits October 6, 2026 00:08
v2 starts from a fresh skeleton (plan decision 1). The v1 sources, the
copied and prebuilt libraries, premake, the Lanes submodule and the old
workflow are removed here; master keeps v1, and later phases port the
bindings from it file by file. The VC:MP SDK header moves to
third_party/vcmp/vcmp.h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 1 of V2_PLAN.md: the new layout, the build, and proof that every
dependency links into one self-contained plugin binary that loads.

- vcpkg manifest (baseline pinned, Lua 5.4.8 override) for Lua, sol2,
  spdlog, fmt, SQLite, OpenSSL, libpq, MariaDB Connector/C, curl and zlib,
  with static, release-only triplets (hidden visibility; /MT on Windows).
- Pinned tarballs (SHA256) for LuaSQL, lua-cjson, LuaSocket, LuaFileSystem,
  Copas, binaryheap, timerwheel, inspect.lua and digestpp. C modules are
  registered in package.preload; Lua modules are embedded and load in text
  mode, so require needs no file on disk.
- Minimal runtime that follows the B3 rules: one owner with a fixed shutdown
  order, noexcept callbacks, structSize checks, OpenSSL initialised first
  (no atexit handler, no openssl.cnf), panic handling that marks the runtime
  dead, and a sandboxed luaconfig.lua with field-path errors.
- tests/host: plugin_host loads the built binary with a fake server API and
  runs init, frames, shutdown and unload; 6 tests, including older servers
  and invalid configs. ci/check-binary.sh/.ps1 enforce the B8 rules.
- Pinned manylinux_2_28 build image and CI for Linux and Windows.
- MySQL gate (tests/integration): passes, so MySQL support stays.
- tests/probe and docs/internals.md: callback order on a real server.

Deviations from the plan:
- LuaFileSystem is built from its tarball instead of the vcpkg port, and
  lfs.h and lua_cjson.c are patched: both hard-code __declspec(dllexport),
  which would export luaopen_* from the Windows DLL.
- timerwheel requires "coxpcall" on every Lua version, so a small built-in
  coxpcall module returns the standard functions.
- LuaSQL 2.8.1 never sets MYSQL_READ_DEFAULT_GROUP. Its MySQL driver gets two
  patches instead: memory-safety fixes, and connection options (timeouts,
  TLS mode, CA file, and a plugin directory that cannot exist, so the
  connector never loads a client plugin from disk).

Verified locally: Linux build, 6/6 host tests, check-binary.sh, the MySQL
gate, and LuaPlugin_x64.so in a real VC:MP server. Not verified yet: the
Windows job (runs once the branch is pushed) and the probe's player events
(need a game client).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A VC:MP client joined the probe server three times: kicked, quit, and
online during ShutdownServer(). OnPlayerDisconnect fires inside KickPlayer,
the player stays valid during the event, and a player who is online at
shutdown is disconnected only after OnServerShutdown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Cache the vcpkg packages under a new key per run and save them right after
  they are built, even when a later step fails, so a fix cycle never
  rebuilds the dependencies. A run saves only when it built new packages.
- On failure, print the vcpkg build logs and upload them with the CMake and
  CTest logs. Test and binary-check steps run even if the other one fails.
- Job timeouts, and a workflow_dispatch option to run one platform only.
- MSVC: build with /EHs (the panic handler throws through Lua's C frames),
  and avoid two /W4 warnings that /WX would turn into errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows job failed in configure: vcpkg needed the old lua port tree for
the 5.4.8 override, and git tried to fetch it from the partial clone's
promisor remote ("Could not resolve host: github.com"). A full clone of
vcpkg is about 113 MB and needs no fetch while building.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 2 of V2_PLAN.md: the runtime core the bindings build on, with the
B3 safety rules, covered by unit tests against an in-memory server.

- Runtime owns the Lua state, EventBus, Scheduler, entity pools and
  FramePump, and shuts down in the B3.1 order: mark closing, release every
  Lua reference C++ holds, close the state (finalizers run while the
  subsystems still exist), destroy the subsystems. After a Lua panic the
  runtime is dead, every callback is a no-op, and the state is leaked.
- plugin/callbacks.cpp: noexcept server callbacks that tolerate a missing,
  closing or dead runtime; callbacks are written only where structSize has
  room; server functions are read through ServerApi (VCMP_LUA_API raises
  "not supported by this server version"). A shutdown that arrives during a
  Lua call runs when the call returns; Server.reload() runs at the end of
  the frame, deletes what the old runtime created, and keeps the old
  scripts running if the config or the new state fails.
- EventBus: events indexed by an enum plus custom events, stable handler
  storage, dispatch up to the size at its start, tombstones compacted only
  when no dispatch is active, Event.cancel() for the innermost dispatch.
  v1 names and arguments (Event.bind/unbind/cancel/create/trigger).
- Scheduler: 64-bit steady_clock milliseconds, timers held by id, due
  timers collected before any is called, no catch-up bursts. v1's
  Timer.create/destroy and thisTimer.
- EntityPool + EntityRef: one cached Lua handle per entity lifetime (==
  and table keys work), generations, idempotent adopt/release during the
  synchronous pool events, startup enumeration, "<kind> no longer exists".
- Every Lua value C++ stores is a main_* reference, so handlers and timers
  created in coroutines survive them.
- Config: log.file and log.daily now work.
- At server shutdown the runtime dispatches onPlayerDisconnect for players
  still online (the open decision from phase 1; docs/internals.md).
- tests/unit: doctest + FakeServer, 31 cases: handle identity and
  invalidation, adopt/release during synchronous pool events, timers across
  a simulated 72-minute clock, bind/unbind/cancel during nested dispatch,
  a kick inside a handler, handlers bound in coroutines, failing handlers,
  shutdown with live finalizers, deferred shutdown and reload, panics.
- linux-asan preset and CI job: the unit tests under ASan + UBSan.
- docs/internals.md describes the runtime; docs/MIGRATION-v2.md is started.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The phase 1 header (API 2.0) was one function and one callback short of
the server (docs/internals.md). This is SqMod's module/VCMP/vcmp21.h at
4a6bfc0, whose PluginFuncs and PluginCallbacks are exactly the sizes the
server reports. It appends GetNetworkStatistics and
OnEntityStreamingChange, adds vcmpEntityPoolPlayer, and renames
vcmpPlayerUpdateAiming to vcmpPlayerUpdateAimingDeprecated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The groundwork for porting the v1 classes.

- bindings/args.hpp: argument types for bindings (Int32, UInt8, Float,
  Boolean, String, Colour, Vec3, Opt<T>, Live<K>, Ctx) with sol2
  customization points. They convert with range checks (plan B3.9):
  integers accept integral floats, refuse fractions and out-of-range
  values instead of narrowing; booleans are only true/false; colours
  accept [-2^31, 2^32); Vec3 is a table {x, y, z} or three numbers, read
  with raw access. Live<K> is a handle of kind K whose entity still
  exists, so every method that takes it as self validates its entity.
- Errors read like luaL_argerror and carry the script position:
  "main.lua:12: bad argument #1 to 'setWeapon' (integer expected, got
  string)", "bad value for 'health' (...)" for property assignments.
  Bindings throw; an exception handler set in bindings::Register adds
  the position.
- Check(): a server call that returns vcmpErrorRequestDenied gives
  false; any other error raises "'<function>' failed: <error>".
- FakeServer: every PluginFuncs field is a recorder that logs
  "Name(args)", fills out-parameters and buffers, and returns values a
  test configures; players, entities and kicks keep their state. The
  plugin's callbacks are now FakeServer::plugin.
- tests/lua: Lua test files run by the unit tests, each in a new
  FakeServer, with helpers.lua and a fake.* table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v1 constant tables (ServerOption, DisconnectReason, BodyPart,
PlayerState, PlayerUpdate, PlayerOption, PlayerVehicle, VehicleUpdate,
VehicleOption, VehicleSpeed, PickupOption, EntityType), read-only, with
v1's names and values.

Fixed: BodyPart.rightLeg was the right arm's value; ServerOption listed
deathMessages twice. New: EntityType.player and NetworkStatistics (plugin
API 2.1). PlayerUpdate.aiming keeps its value, which the SDK now calls
deprecated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Player's v1 members and the player events, ported onto the B3/B4 core.

- Every member takes a checked handle (Live<Player>) and checked
  arguments; properties use the same handle check through
  bindings::Property.
- Fixed from v1: messages are sent with a "%s" format (v1 passed the
  text as the printf format); name is read from the server and set
  without strcpy into 24 bytes; ip/uid/uid2 check the server's result;
  vehicle = nil and spectateTarget = nil work; ammo returns the current
  weapon's ammo; getActive(true) filters; booleans are true/false.
- New members: findByName, speed, addSpeed, away, onFire, gameKeys,
  standingOnVehicle, standingOnObject, giveMoney, getWeaponAtSlot,
  getAmmoAtSlot, getNetworkStatistics; valid for every entity kind.
- Common entity members (type, getType, getID, findByID, count,
  getActive) live in entities.cpp for every kind.
- callbacks.cpp: the player events with v1's names and arguments.
  Cancellable callbacks return 0 after Event.cancel(). onPlayerWasted runs
  every handler (v1 only the first); onPlayerCommand also passes the raw
  argument text; entities first seen in an event are adopted.
- tests/lua/player.lua covers every member against the recording fake
  server; tests/unit/test_callbacks.cpp covers the events.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vehicle's v1 members and the vehicle events.

- The server owns vehicles (plan B4): Vehicle.create returns a handle and
  the vehicle stays until vehicle:destroy() or until the server or another
  plugin deletes it. v1's Vehicle(...) and Vehicle.new(...) are kept as
  aliases. In v1 the Lua GC deleted the vehicle, then deleted again with
  a reused id, and a throwing constructor left __gc deleting garbage.
- Fixed: rotation round-trips (the setter accepts the getter's
  {euler, quaternion} table); booleans are true/false; getOccupant of an
  empty seat is nil; setSpeed's add flag is optional.
- New: model, wrecked, lightsData, explode().
- ArgReader reads arguments by position for the bindings that accept
  several forms (create, setSpeed, setRotation, resetHandlingRule).
- onVehicleUpdate, onVehicleExplode and onVehicleRespawn.
- tests/lua/vehicle.lua covers every member, including ownership: a
  collected handle deletes nothing, and a vehicle deleted elsewhere is
  noticed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Object's v1 members and the object events.

- The server owns objects: Object.create (and v1's Object(...) and
  Object.new) returns a handle; the object stays until object:destroy()
  or until the server deletes it.
- Fixed: object.angle = {x, y, z} rotates to the angle (v1 rotated by
  it); rotateTo/rotateBy pick the quaternion form by the table's length
  instead of treating w = -1 as "no w"; alpha is range-checked.
- New: model and alpha properties.
- onObjectShot and onObjectTouch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pickup's v1 members and the pickup events.

- The server owns pickups: Pickup.create (and v1's Pickup(...) and
  Pickup.new) returns a handle; the pickup stays until pickup:destroy()
  or until the server deletes it.
- alpha (default 255) and automatic (default true) are optional when
  creating; alpha is range-checked.
- New: model and quantity properties.
- onPickupPickAttempt (cancellable: Event.cancel() refuses the pickup),
  onPickupPicked and onPickupRespawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Checkpoint's v1 members and the checkpoint events.

- The server owns checkpoints: Checkpoint.create (and v1's
  Checkpoint(...) and Checkpoint.new) returns a handle; the checkpoint
  stays until checkpoint:destroy() or until the server deletes it. The
  owner may be nil (every player sees it).
- Fixed: radius = r sets the radius (v1 compared r with the world id
  first); alpha = a no longer reads a destroyed temporary; colour
  channels are checked to be integers in [0, 255].
- New: owner and sphere properties.
- onCheckpointEnter and onCheckpointExit.
- TableInteger: checked integer elements of a table argument, with an
  error naming the element. Object and Vehicle use it too, instead of
  converting the element on the stack top (which reported a negative
  argument number).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v1's Bind class on the B4 entity pools.

- EntityKind::Bind: 256 slots, adopted at start-up (GetKeyBindData per
  slot) and on first sight in onPlayerKeyDown/Up. Binds raise no pool
  events, so every use asks GetKeyBindData and a bind another plugin
  removed raises "bind no longer exists".
- The server owns binds: Bind.create (and v1's Bind(...) and Bind.new)
  returns a handle; the bind stays until bind:destroy(). v1 removed it
  when the Lua object was collected.
- Bind.clearAllBinds() removes only the binds this plugin created (v1
  called RemoveAllKeyBinds). A reload removes ours too, never others'.
- tag is kept in the pool slot and dropped with the bind.
- FakeServer models key bind slots; plugin_host answers GetKeyBindData.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v1's Stream class and onClientData, rebuilt on a bounded vector (plan A0).

- Incoming client data is copied into a Stream of its own size. v1
  memcpy'd it into a fixed 4096-byte array on the stack without checking
  the client-controlled size: a remote stack overflow.
- Reads check that the whole value is present (v1 checked one byte) and
  raise instead of returning 0; writes check room for the whole value
  (v1's writeString could write one byte past the end) and raise at 4096
  bytes. A failed read or write changes nothing.
- Same byte layout as v1: numbers and floats little-endian, strings with
  a big-endian 16-bit length. Stream() works as well as Stream.new().
- send() without a player sends to everyone; new size and remaining.
- onClientData builds one Stream object shared by the handlers; a
  handler's read error is logged and the server carries on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v1's Server.cpp: the server-wide functions, properties and events.

- Server is the single instance of a usertype, so v1's static
  properties work (Server.name, Server.hour = 12); the functions keep
  their dot-call form. Server.reload() stays.
- Fixed: addClass reads the weapons from the right arguments (v1 was off
  by one) and its {x, y, z, angle} form is no longer shadowed; the IP
  ban functions accept strings; getSkinName of an unknown id returns nil
  instead of NULL as a std::string; Map.hideObject rounds negative
  coordinates correctly; wastedSettings keeps fields a table leaves out.
- names.cpp: v1's skin and weapon name lookups, unchanged except for
  memory handling and tolower on unsigned chars.
- Blip.create returns a Blip handle (blips are in the B4 pools since
  phase 2); destroy and getInfo take the handle or an id.
- Events: onPluginCommand (cancellable), onServerPerformanceReport with
  arrays instead of raw pointers, and the new onEntityStreamingChange.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Logger keeps v1's functions. setLevel sets the least severe level that
  is logged, by name or by v1's numbers 0 (debug) to 4 (critical); v1
  compared an unsigned level with >= 0 and worked backwards. New
  getLevel.
- MySQL, SQLite (and SqLite, SQLiteDatabase), Remote, JSON, Thread and
  dbg are stubs (plan B5): any use raises "<name> was removed in v2:
  <replacement> (see docs/MIGRATION-v2.md#<anchor>)". The guide gets the
  matching sections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/integration/bindings: bindings.lua creates, writes, reads back and
destroys every class against the real VC:MP 0.4 Linux server (no players
needed), reloads once, checks that the reload deleted the old runtime's
entities and binds, and shuts the server down. run.sh runs it in
ubuntu:24.04. All checks pass and the server exits with 0.

It found three server quirks (docs/internals.md):

- The server refuses a plugin that reports API 2.1 and never calls it.
  The plugin reports 2.0 again (with the 2.1 header, PLUGIN_API_MINOR is
  1); later fields stay behind the structSize checks. plugin_host checks
  the reported version.
- GetServerName and GetGameModeText return vcmpErrorBufferTooSmall even
  when the text fits. ReadText takes that error for success; its buffers
  are far larger than the server's limits.
- GetKeyBindData succeeds for all 50 slots, used or not; a free slot has
  keys 0, 0, 0. Start-up enumeration adopted every free slot and a removed
  bind still looked alive. A slot now holds a bind when a key is not 0.

FakeServer reproduces the last two.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Vehicle rotation tables and Server.wastedSettings were read with
  lua_getfield, which runs __index metamethods inside a binding (plan
  B3.3). They use RawField (lua_rawget) now; Lua tests check that no
  metamethod runs.
- FakeServer's stubs use one tag type per PluginFuncs field instead of a
  partial specialization over a member-pointer template argument, which
  older MSVC versions handle poorly; the unit tests build on Windows too.
- names.cpp silences MSVC's unreachable-code warning for v1's
  "return ...; break;" switches.
- The phase 3 sources are clang-formatted (Google style, 4-space indent,
  100 columns, as the phase 2 code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/integration/client: client.lua logs every event with its
arguments, checks the Player members automatically on the first spawn
(PASS/FAIL in the chat and the console), and has commands for vehicles,
checkpoints, pickups, objects, key binds, the camera, command arguments,
renaming, reload and kick. README.md describes the Windows server setup.
The script loads on the Linux server; it needs a real client to run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows build failed on C4702 inside sol2's headers
(function_types_stateful.hpp, stack.hpp): a backend warning raised when a
binding always throws, such as the stubs of the removed v1 globals. It is
disabled for first-party targets, which also covers v1's ported switches
in names.cpp (its local pragma is gone).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owner's run of tests/integration/client on a Windows server with a
real client passed everything except two checks, both faults of the
script, not the plugin:

- getNetworkStatistics raised "not supported by this server version":
  that server's PluginFuncs ends before the API 2.1 functions. The script
  now accepts that.
- player.weapon read 0 right after setWeapon: the server reports a
  weapon only after the client synced it. The script reads it 2 s later.

docs/internals.md records the results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- lua/prelude.lua: the module sandbox (plan B6). load, loadfile, dofile
  and require accept text only; loadfile()/dofile() never read the
  console; package.cpath is empty, package.loadlib and the C searchers
  explain why C modules cannot load from disk.
- require "http": non-blocking HTTP(S) over the libcurl multi interface,
  pumped once per frame, callbacks on the main thread after collecting the
  finished transfers. Peer and host name always verified; CA from
  http.cafile, else the Windows store, the distribution bundle or an
  embedded Mozilla bundle (cmake/deps/cacert.cmake). Pending requests are
  dropped without callbacks on reload and shutdown.
- Hash: v1's digests unchanged, plus OpenSSL hmac, pbkdf2, scrypt,
  randomBytes, toHex and a constant-time equals; also require "hash".
- require "sql": sql.format(conn, "...?...", ...) on top of conn:escape.
- The frame pump runs timers, HTTP, then one copas.step(0) and sets
  copas.running.
- Unit tests for all of the above (HTTP against a Copas server in the same
  state), extra host smoke checks, and tests/integration/smoke: the real
  VC:MP server against Postgres 17, MySQL 8.4, MariaDB 11 and a local TLS
  server (CRUD, 1,000 inserts in one frame per database, timers, entities,
  untrusted CA, custom CA via config, wrong host name, 100 concurrent
  requests, reload, clean shutdown). The MySQL gate services moved to the
  "gate" compose profile; CI runs the smoke test when the repository
  variables VCMP_SERVER_URL and VCMP_SERVER_SHA256 are set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- destroy() on a vehicle, object, pickup, checkpoint, blip or bind that
  the server refuses to delete keeps the handle alive and returns false;
  bind:destroy() now returns a boolean like the others.
- Bind.create raises when the server refuses to register the bind,
  instead of returning a handle to an unregistered slot.
- A received Stream larger than 4096 bytes no longer accepts writes (the
  free-space check wrapped around).
- Vehicle.create rejects a non-number angle in the position table.
- vehicle:repair() reports a refused step; player:playSound and
  playSound3D return the server's answer.
- Server.wastedSettings range-checks its timer fields instead of reading
  them as colours.
- LuaSQL SQLite: cursor:fetch(t, "a") on a query with no rows returned a
  table of NULLs; it now returns nil (patch, upstream candidate).

The fake server now honours fake.error() for functions it replaces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docs/api: reference for every binding and built-in module, and
  docs/configuration.md for luaconfig.lua.
- docs/MIGRATION-v2.md: installing, luaconfig.ini to luaconfig.lua,
  Lanes, and links to the reference.
- examples/: a luaconfig.lua and three scripts (commands, accounts with
  PBKDF2 hashes in SQLite, a JSON webhook over http).
  tests/integration/examples runs them in the real server.
- release.yml: a v* tag that matches the project version runs CI, packs
  one zip per platform (plugin, luaconfig.lua, LICENSE, notices) with
  SHA256SUMS, and drafts a GitHub release. ci.yml is now reusable.
- README rewritten for users.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments only: removes references to the private rebuild plan, v1 bug
history (now in docs/MIGRATION-v2.md), section banners and comments
that restate the code. docs/internals.md reads as a standalone document.
The real-client test no longer calls itself "phase 3" in game.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The repository already has releases 2.0 Beta to 2.8 Beta, so the rebuild
is 3.0.0. User-facing text calls the old plugin 2.x and the new one 3.0:
the migration guide is now docs/MIGRATION-v3.md, and the removed globals
say "removed in 3.0". removed_v1_stubs.cpp is now removed_globals.cpp.

THIRD_PARTY_LICENSES.txt holds the license texts of everything linked
into the plugin, written by ci/collect-licenses.py from a build tree.
Release zips include it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rasikhq rasikhq closed this Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant