Skip to content

Roadmap

This is where SAGL is and where it's going. Items under the same heading are roughly in order. Edit this file as plans change; decisions are recorded with their reasons so they don't get re-litigated.

Principles

  • The server decides. Clients simulate, and the server has the final say on anything that matters: damage, deaths, spawns, time, skins, and later inventory and money. Servers should get more control than SA-MP gives them: they can see every shot and hit, and veto or change damage before it applies.
  • Gamemodes are Node.js. Server logic is ordinary Node/TypeScript with npm modules, built on @sagl/server. The C++ core does the networking and calls gamemode hooks synchronously, so a hook can veto a hit.
  • Clients run no arbitrary server code. Server-supplied client logic, such as UI scripts, runs in a sandbox that can only touch its own UI and the server channel.
  • Patch carefully. Every memory patch checks the original bytes first. Engine addresses are checked against gta-reversed and a disassembly of gta_sa.exe 1.0 US.

Done

Area What exists
Foundation Cross-compiled 32-bit client (clang-cl + xwin) built on plugin-sdk. Linux server. Dev Wine prefixes and run scripts. GitLab CI packages the client
Empty world No intros, menu, single-player scripts, peds or traffic. Our own player spawn. Watchdog and crash logging
Networking ENet with protobuf (nanopb) messages. Handshake, reconnect takeover, fast timeouts
Players Remote players are real player peds driven by synced input (context switching). Smooth on-foot movement and animations, interpolation, drift correction
Combat Server-authoritative damage: the attacker reports hits, the server validates them, and every client applies the verdict. Death and spawn detection, kill credit
Vehicles Server-created vehicles (createVehicle). The driver's client simulates and syncs its car; others place it from that sync, interpolated. Passengers (seats 1-8); remote players can't be dragged out. Enter/exit events
Car radio Everyone in a car hears the same thing: one occupant's game picks the songs and reports station, queue and position; the others play that. The station is the server's and stays with the car: any occupant can retune (the gamemode can refuse), vehicle.radio sets it (also when creating it); to fix one, set it and refuse changes. Station names can be hidden per player. Untuned cars get the game's own station for that model. The radio keeps playing (in step) while alt-tabbed
Zones and garages Server-side zones (spheres, boxes) with enter/leave/move events from synced positions. The game's garages run no logic of their own: the server opens and closes their doors, and each has a zone, so Pay 'n' Spray (in the freeroam example), mod shops and garages are gamemode features. Server-owned money
Virtual worlds Players and vehicles each have a world (default 0); players only see their own world plus worlds it views, one way (a safehouse world viewing world 0 sees the street, not the reverse). The server sends each client only what it may see. Only same-world players can hit each other or get in each other's vehicles (vehicles seen from another world are locked). Vehicles move between worlds with their occupants. Zones can be per world
Ambient population (phase 4) server.population / world.population: peds, traffic, density, and switches for gangs, police, ambulance and fire. The game's own population code runs on each player's game (only when the area is under budget) and what it makes is proposed (AmbientSpawn), checked (switches, budgets per area, rate, position) and created by the server for everyone, simulated by the proposer; populationSpawn can refuse. Ambient peds and vehicles are removed when no player is near (bodies after a minute, wrecks after 30 s). Nothing appears or goes in anyone's view: spawns another player could see (in front of their camera within 120 m for peds, 250 m for vehicles, or within 15 m) are refused, removals wait until nobody looks, and clients fade ambient peds and vehicles in and out. The vanilla world setup (gang territories, zone population types and races, relationships) is extracted from main.scm (scripts/gen-world-setup.py, sagl/world_setup.hpp) and applied by clients; gangs off empties the territories
Peds (phase 3) Peds in vehicles: ped.putInVehicle()/removeFromVehicle(), seats shared with players (a seat has one or the other). A wandering driver drives around the roads with the game's own driving AI in its owner's game, which syncs the car (the server's vehicle state follows it; copies place it as they do a remote player's car). Peds get in and out as their AI decides (fleeing, fighting back), and copies follow. Jacking a ped: the player's game claims the ped (PedClaim, within 15 m) so its drag-out and the ped's reaction (it may drag you back out) run there. Peds leaving everyone's range stop until a player comes near, who takes them over
Peds (phase 2) Copies show what the owner's ped is doing beyond moving: hands up, ducking, cowering, aiming, fighting, and at whom (the game's own tasks), and knockdowns (the same fall, then getting up). Peds react as the game's own peds of their kind do (their ped stats' decision maker, not the mission one script-created peds get): civilians flee or cower, gang members duck and shoot back. A ped's shots are checked (the weapon the server gave it, fire rate, from where it is) and fired by everyone's copy; its hits on players and peds are validated like players' (an accepted shot per gun hit) and go through playerDamage/pedDamage with the ped as attacker; deaths credit killer peds
Streaming Each player's game only gets what's near: server peds within 200 m (out beyond 230), vehicles within 300 m (340), players within 350 m (400; their sync is relayed only then). A ped's owner and a vehicle's occupants always have it. Rechecked every half second and on world changes, replacing the world-visibility diff
Peds (phase 1) server.createPed(): peds on foot, standing or wandering. The nearest player in their world simulates them with the game's own AI (owner); everyone else's game shows a copy that only follows the owner's sync. Ownership moves as players move or leave. Hits on peds go through the server like hits on players (pedDamage); the owner's game applies them, so its AI reacts and the copies follow; pedDeath credits the killer. Owner reports are checked (speed, healing, weapons, coming back to life); refused ones are dropped, repeated ones cost the owner the ped (pedViolation)
Vehicle state Horn and siren are synced with the driver's sync. Visible damage (doors, bonnet, boot, panels, lights, tyres) is reported by the game simulating the car and shown on every copy, including to players who arrive later. Damage and health only get worse: the server keeps the worst of what it knew and what is reported, so a client that repairs its car itself (a trainer) is corrected and nobody else sees it; only the server repairs (vehicle.repair()). Getting in and out is played on everyone's screen with the game's own tasks (walking to the door, opening it, dragging a ped out, getting out), for players (VehicleEntry) and peds; their sync only warps them if that hasn't happened in a few seconds. Vehicles nobody drives are simulated by one player's game (the driver who just got out, else the nearest player), which reports them while they move; everyone else's copy follows, and is put back if it drifts. Those reports are checked: a vehicle can't move further than it could have or speed up impossibly, and a refused report puts the vehicle back on the reporter's screen; repeated ones hand the vehicle to someone else
Player traits Stats are the server's (setStat, Stat): playing never changes them, and every client plays each player with that player's own stats (stamina, weapon skills, max health...). Infinite sprint per player. Walk and run like the skin or like CJ (skinAnimations, per player or server-wide). Players speak with their skin's voice. No single-player stat popups ("Respect +")
Weapons The server owns every player's weapons and ammo (giveWeapon, setAmmo, removeWeapon, resetWeapons, weapon); clients keep the game to that inventory (a trainer's weapon is taken away, ammo never above the server's count). Every round fired is reported and checked: owned weapon, ammo left, not faster than the weapon fires. playerShoot can cancel a shot (no one sees it, its hits don't count) and playerWeaponViolation reports cheating. A gun hit needs an accepted shot (one hit per round, shotgun pellets more). Other players see a remote's weapon in hand and their accepted shots. Drive-bys: a player leaning out of a car is seen leaning out, aiming where they aim. Grenades, molotovs, satchels and rockets: everyone's game flies the same projectile from where it left the thrower; the thrower's game reports where it went off, which the server matches to something they threw (playerExplosion can cancel it), and only then do explosion and fire hits on others count. Satchels go off with their owner's detonator only; a player's projectiles die with them. Vehicles blow up for everyone at once, when the server says (vehicleDeath, vehicle.explode()), credited to whoever wrecked them
World environment Time and weather per virtual world: worlds follow world 0's until given their own (server.world(10).weather = ...), and can be set back to follow it. Weather is synced (it used to cycle separately on each client)
Cull zones Server-defined areas with the effects of the game's own cull and audio zones: interior music/ambience (Ammu-Nation, Binco, gym, ...), attributes (no rain, camera close-in, fewer peds, ...) and mirrors, optionally per world. Client side they go in after the map's own; the few free audio slots go to the nearest audio zones
World Server-controlled time of day. Server-assigned skins. Server messages, teleport, set health and armour
Scripting @sagl/server: Server and Player API, typed events, damage veto/modify, operator console, example freeroam gamemode
Engine fixes CdStreamSync lost-wakeup deadlock fix: loading hung intermittently under Wine. The multi-adapter video-mode dialog is patched out
Launcher sagl.exe injects sagl/sagl.dll into a suspended gta_sa.exe; gta_sa.exe alone stays single-player. Server and name on the command line. Intro skip that doesn't depend on load timing
Display [video] resolution, windowed mode (title bar, cursor released when unfocused) and borderless (any resolution, scaled to the monitor with the aspect ratio kept), DPI-aware

In progress: UI milestone

The in-game UI uses RmlUi, an HTML/CSS-like UI library. It's skinnable, light enough for a 32-bit game, and doesn't run arbitrary JS. ImGui stays as the developer overlay. CEF is dropped in favour of server UI packages (see below).

  1. Foundation: RmlUi and FreeType, a D3D9 renderer, and an input router (our own cursor, keyboard/text, gamepad navigation, blocking game input while UI has focus). Done; tested on Windows and Wine. RCSS box-shadow/filters aren't supported yet: the renderer has no layer support.
  2. Built-in components: chat (/commands as their own server event), notifications, message box, input box, menu. Async Node API: await player.ui.input(...).
  3. Replacement pause menu: the game keeps running underneath. Done: Esc/Start opens it. Its pages are Players (pushed by the server while paused) and Settings, which covers audio, display, HUD/radar and mouse through GTA's own preferences (saved to gta_sa.set) plus window mode and resolution (saved to sagl.ini, applied on restart). There is also Quit. Pause and focus state reach the server (player.paused, player.focused, playerPause) and other players, shown on name tags. Not done: Disconnect/reconnect, and key bindings (waits for input actions).
  4. Input actions: server.input.define("name", { default: { keyboard, gamepad } }). Players rebind them per server under Settings → Controls.

Planned

Gameplay sync

  • Weapons, continued:
  • fires and burning cars: each game simulates its own copy of a fire (molotovs, burning wrecks), so how long it burns and what catches can differ; only the damage to players is the server's. Sync fires, and an empty vehicle's health
  • rocket launchers fired by players in the game (tested with bots only), and heat-seeker locks
  • tear gas only makes a cloud (its choking isn't synced)
  • syncing the lock-on target (each client currently picks its own)
  • reload sync, and the aim pose of remote players without a fire button
  • per-weapon firing limits taken from the game's weapon data rather than a fixed table; server-side line-of-sight checks
  • Knockdown and death state: sync "on the ground" and "dead" from the victim's own client, so every screen agrees. Today each client plays its own reaction to the same verdict, and they can differ.
  • Vehicles (M5), continued:
  • respawning wrecks (they blow up for everyone; gamemodes destroy and recreate them)
  • setting a vehicle's health and damage from the gamemode (today only full repair)
  • movement checks for drivers' own cars (today only empty vehicles' reports are checked for teleports)
  • tested: bikes (and their passengers), quads, monster trucks, hovercraft, planes (on the ground and gliding), helicopters, boats, trailers. Planes' control surfaces, rotors and propellers, landing gear and smoke trails are synced, and boarding a boat from the water plays for others. RC vehicles: player.remoteControl() (any empty vehicle), synced as an empty vehicle the controller simulates. Trains aren't supported yet. Vehicle weapons (Rhino and RC Tiger cannons, Hunter/Hydra/Seasparrow guns and missiles, RC Baron gun, water cannons) aren't synced yet
  • Peds, continued (see the agreed plan: owner-simulated, GTA V style):
  • gang config for gamemodes: change territories and relationships (today a new game's, or none)
  • emergency responses: ambulances to the injured, fire trucks to fires, police responding to crime (wanted levels are off today). The switches exist and gate ambient cops, medics and firemen; dispatch itself is next
  • gang wars and gang groups (members walking together, following a leader); gang members recruiting
  • peds that leave everyone's range: scripted ones stop where they are until a player comes near (today); ambient ones (phase 4) will be removed by the server as the game removes its own far-away population; an option for scripted ones to be removed or sent back
  • streaming peds in and out by distance (today everyone in a world gets every ped in it), and never handing peds to a headless client such as sagl-bot
  • Camera control: gamemodes direct a player's camera: fixed positions, looking at a point, ped or vehicle, smooth moves and turns between shots (cutscene style, the game's own camera interpolation), a freecam, and handing back to the normal camera behind the player. Something like player.camera.moveTo()/lookAt()/interpolate()/freecam/reset()
  • Limits: remote players share one ped group (the game has 8), so many players can be near each other (tested with 12); other pools (140 peds, 110 vehicles) are kept in check by streaming and the population budgets.
  • Anti-cheat considerations: server-side plausibility checks on movement, weapons and damage.

Session and identity

  • Staged join:
  • connect
  • server loading screen
  • optional login (server UI)
  • Ready

Only after Ready does the server announce the player and start sync. - Unique player ID plus display name: the server decides whether to honour a nickname, kick duplicates, or force or let players pick a name. This should support both servers with accounts (username/password in game) and servers without. Reconnect takeover should then match on the ID instead of name plus IP.

Server scripting API

  • Weapons: give and take, ammo, a playerShot event.
  • Vehicles: spawn, destroy, colours, mods, occupants, events.
  • Chat and commands, once UI phase 2 is done.
  • Objects in worlds: server-created objects (createObject(model, position, rotation, { world })) sent only to players who can see that world, so a player's safehouse world can have its own furniture or interior. Per-world time and weather.
  • Prefabs: (also cull zones, garages and audio zones, in the same IPL-like format) named sets of objects (and object removals, e.g. to clear a lot or open a wall) defined once, shipped in the server's asset pack (an IPL-like file), and loaded or unloaded client-side when the server says, per world, instead of streaming every object on connect. For custom interiors, street furniture and extra buildings. Clients would stream them in by distance like the game's own map.
  • Docs and typed API reference for @sagl/server.

UI, after the current milestone

  • Client scripts (done): gamemodes send JavaScript (server.clientScripts) that runs in a QuickJS sandbox in players' games, and talk to it on named JSON channels both ways (server.channel(name), player.emit; sagl.on/sagl.emit in scripts). Script UI (documents, pause-menu pages, the message box) and the map are done, and client files (a server directory downloaded on join, cached by SHA-256, only changes fetched). Next: more read-only game state, request/response.
  • Server UI packages: RML, RCSS, images and fonts, delivered with server assets, plus sandboxed client scripts (QuickJS, so servers use TypeScript on both sides). The sandbox has no file, network, OS or memory access, and limits on CPU and memory.
  • Client-side world scripting: the same client scripts can create things locally that only that player sees: vehicles (e.g. a garage showroom of the player's cars, locked and immovable, without spawning real server vehicles), cull zones, objects. The client modules for these (vehicles, cull zones) already take local calls as well as server messages.
  • Plugin messaging channel: named events both ways (done: client scripts and channels, see below), plus request/response (to do). For example, a phone's messaging app loads its inbox with await server.request("messages:list"), and the server handles it with server.ui.handle("messages:list", async (player, args) => db.query(...)).
  • Server-defined layouts: an in-game phone, shops, login screens. Per-document input modes: cursor, keyboard or gamepad navigation, text capture, and "can still walk".
  • Pause menu customisation: server pages, scoreboard columns and theming. Resume, Settings, Disconnect and Quit always stay.
  • Server themes, as RCSS overriding the default theme.

HUD

  1. Show or hide native HUD pieces per player: radar, health, armour, breath, money, weapon and ammo, wanted level, clock, area and vehicle names.
  2. Move native pieces with a per-player layout table, by replacing CHud::DrawPlayerInfo with our own port. For example, move health down a line.
  3. Native-styled server text: drawn with the game's own font renderer in HUD styles (money, clock), anchored to native elements. For example, a bank balance line under money.
  4. Custom HUDs: RmlUi documents plus client scripts that read live game state, for example a GTA V–style square minimap. Map tiles come from the game's radar textures or server images, and blips come from the server. The game's texture fonts (Pricedown, the menu and subtitle fonts, Gothic) are available to RmlUi as font families, read from the player's fonts.txd.

Custom assets

  • Downloads: servers offer a URL to a ZIP of assets (models, textures, player skins, vehicles, UI packages, themes). The client downloads it on join, during the loading stage, and caches it per server.
  • Runtime injection: loading models and textures at runtime (model info slots, TXD/DFF loading), so servers can add map objects, peds and vehicles without players modding their game.

Infrastructure

  • Launcher: a server browser, plus a list of other ASI mods found in the game folder, with warnings for known-incompatible ones (SA-MP). Explicit SilentPatch compatibility.
  • Server packaging: a CI job for @sagl/server (prebuilt native addon), plus a Docker image for k3s with UDP exposed via hostPort or NodePort.
  • Proxy: a BungeeCord-style proxy that routes players to back-end servers by the hostname they connected to. Servers already support it: the Hello carries the address as typed and the player's forwarded IP, and trusted-proxy mode exists. The proxy itself would also need to forward real pings and handle moving players between servers.
  • Testing: more headless bot scenarios (vehicles, weapons, UI messages); smoke tests in CI.
  • Docs: protocol reference, client architecture (context switching, damage gates, patches), gamemode guide.

Known issues

  • Knockdown and death can look different on different screens (see Gameplay sync).
  • Lock-on targeting with right mouse picks its target per client.
  • Damage needs a server round trip before anyone sees a reaction; there's no local prediction yet.
  • Building CJ's clothed model may do a short synchronous load; watch for hangs there.
  • CJ's clothed model (model 0) is shared by the local player and any remote CJ. Remote players now spawn straight in their own skin, because switching the local player and a remote away from CJ close together corrupted memory (crashes creating the next vehicle). A server that gives several players CJ may still hit this; CJ for remote players needs its own model slot.
  • Two clients starting at once under Wine occasionally hang drawing the loading screen (inside wined3d).
  • Car radio: at some track changes a passenger can be a few seconds off, or on another track, until the next correction (within about 6-18 s). The game itself occasionally stalls a radio stream briefly. Under Wine the game isn't muted while alt-tabbed (muting its audio session seemed to stall the radio there).
  • Stopping players jacking a remote player's car relies on the game honouring "can't be dragged out"; check it with a real F press.
  • The world streams in around a newly spawned player, so they briefly stand in empty space.
  • Windowed mode: no resizing yet. Changing window mode or resolution needs a restart.
  • Quitting with other players around found the local ped holding a real-time shadow that belonged to no one, which crashed CGame::Shutdown. Such shadows are detached before shutdown (logged as "detached N stale shadow(s)"). The cause, probably in player context switching, isn't found yet.