Synchronized NPCs and traffic
San Andreas Online lets a server fill one shared world with NPCs and civilian road traffic driven by GTA:SA’s original AI. Pedestrians walk, react, fight, flee, and form groups using GTA’s own behavior. Cars choose GTA’s roads and lanes, drive themselves, and carry suitable drivers and passengers.
The server owns the real NPCs and vehicles. One eligible player simulates each native AI task, while everyone else receives the synchronized result instead of running a competing copy.
The shared ambient runtime covers civilians, native gang groups, gang combat, motorcycle carjacks, dealers, city cops, civilian couples, and autonomous civilian road traffic. Attractors, conversations, and headless simulation remain outside the completed slice.
One shared population, not one crowd per client
Section titled “One shared population, not one crowd per client”San Andreas Online does not turn GTA’s local CPopulation::AddToPopulation loop back on. Each client would otherwise create a different crowd.
Instead, the reference population runtime follows one authority path:
- Clients keep GTA’s native population models and zone state current.
- The server combines synchronized population targets with live civilian, gang, dealer, and cop counts.
- One client asks GTA for a read-only pedestrian, group, couple, or road-traffic candidate.
- Near-enough clients can veto a candidate they can already see.
- The server validates the proposal, creates the real NPCs or vehicles, and assigns one owner epoch.
- Only that owner runs GTA’s native AI. Observers receive the supported movement, animation, combat, and physical presentation.
- Handoff revokes the old generation before a new client resumes simulation.
The server remains authoritative for element lifetime, health, seats, damage admission, ownership, and cleanup.
Civilian road traffic
Section titled “Civilian road traffic”San Andreas Online can create shared traffic that drives itself on GTA’s original road network. GTA chooses a vehicle that fits the current area, a valid road and lane near the player, its direction, normal driving speed and style, and suitable skins for its driver and passengers.
The server then creates one real vehicle with all of its occupants. One player runs GTA’s DriveWander behavior, so the car follows roads, changes direction at road junctions, reacts to the world, and keeps driving without a Lua resource scripting every steering adjustment. Other players see that same vehicle and its occupants through normal synchronization.
When a new traffic car starts driving, San Andreas Online compares nearby roads with the car’s actual position, forward direction, and height. It chooses the road that best matches the vehicle, keeps one-way lane directions legal, and avoids attaching a correctly aligned car to a nearby crossing or road on another level. This reduces immediate U-turns, wrong-way starts, and bad junction choices.
The complete car and its occupants remain invisible during their short setup. GTA first settles the suspension on the road surface, confirms ground contact, installs DriveWander, and applies the road-aligned starting speed. Only then does the server reveal the unit. A candidate that cannot settle safely is recycled instead of appearing frozen or embedded in a slope.
Three proposal functions expose the native choices without creating anything:
getAmbientVehicleModelCandidateasks GTA which civilian road vehicle belongs in the current area.getAmbientVehicleSpawnCandidateasks GTA for a road lane, position, direction, speed, and driving style.getAmbientVehicleOccupantModelCandidateasks GTA for a suitable driver and passenger skins.
The reference resource creates traffic around players in shared 180-unit areas. Nearby players share the same cars instead of receiving duplicate local traffic. Its production profile targets 16 vehicles per active area and allows no more than 160 across the server. Walking population keeps GTA’s civilian, gang, cop, and dealer proportions, doubles the native zone target, then clamps populated areas to 12–20 walkers with a global cap of 240. A zero-population zone remains empty.
For a player moving quickly, both population resources look a short, bounded distance ahead—at most 45 world units—when asking for the next candidates. Road traffic can also begin replacing cars that are already falling behind. This helps the next area fill before the player arrives without permanently raising the configured density.
The pedestrian profile also reads GTA’s percentage value with its real integer return convention. The previous calling mismatch could eventually turn valid population targets into stale or invalid values and stop new walkers from being requested; the corrected read keeps the refill calculation usable.
For operators, the production resources emit a compact population snapshot every 15 seconds: desired and live counts, active player areas, lifecycle and movement state, and ped-pool pressure. A separate movement-anomaly line appears when an NPC or vehicle stops making expected progress, followed by a recovery line when movement resumes. These diagnostics help identify density, ownership, or stuck-state problems without enabling verbose per-frame logging.
Native population profiles
Section titled “Native population profiles”getAmbientPedPopulationProfile exposes GTA’s current popcycle targets instead of making a Lua resource approximate density from scratch. The profile includes civilian, gang, cop, and dealer targets plus the active zone, time slice, density multipliers, creation distance, and gang weights.
It also reports why the ambient-cop target is what it is. rawCopTarget keeps the unmodified popcycle result while copTarget applies the normal ambient-cop guards, and copSuppressionFlags names which guard fired:
| Flag | Meaning |
|---|---|
1 | The active zone is flagged no-cops |
2 | Random cops are disabled |
4 | A gang war is being fought |
8 | The streaming position is at high altitude |
worldLevel and zoneLabel complete the picture by identifying the native world level and navigation zone the profile was computed for.
getAmbientPedSpawnCandidate can request civilian, gang, dealer, or cop candidates explicitly, and every candidate reports its own worldLevel. getAmbientPedGangGroupCandidate proposes a native two-to-four-member gang placement. getAmbientPedCivilianCoupleCandidate proposes two compatible civilians on nearby pedestrian paths so they can be created together as a couple. All of these are proposal APIs: they create nothing by themselves.
The reversible zone layer also exposes setAmbientPedPopulationZoneState and resetAmbientPedPopulationZonesToBootstrap so a resource can apply campaign-style zone state without permanently mutating the process.
Population families
Section titled “Population families”Civilians
Section titled “Civilians”Civilian peds use GTA’s loaded zone models, path placement, CTaskComplexWanderStandard, avoidance, threat, damage reaction, flee/fight decisions, and the existing owner-handoff presentation paths.
Gang groups
Section titled “Gang groups”Gang population is no longer represented as unrelated single peds. The owner can acquire one of GTA’s native ambient group slots with acquirePedNativeGroup. GTA then owns leader/follower locomotion, social behavior, collective decisions, and the selected fight/flee task allocation.
The resource keeps the group server-owned and reacquires the native group only on the current syncer. releasePedNativeGroup and resource shutdown remove the local native lease. getPedNativeGroupDiagnostic exists for focused debugging, not gameplay state.
Gang combat
Section titled “Gang combat”Ambient gang members use GTA’s runtime gang weapon tables and native combat decisions. A native owner hit is authenticated before addPedNativeDamageEvent replays it on the victim’s own client. GTA then applies health, armour, hit reaction, and fall response once instead of letting the AI owner and victim both apply damage.
Motorcycle carjacks use the same boundary. GTA resolves the jack on the group owner, the server validates the target and seat state, and the real player owner installs the canonical bike-jacked task. addPedNativeBikeJackTask is the low-level victim-side bridge used by that flow.
Dealers
Section titled “Dealers”Dealer population uses GTA’s dealer quota, race/weather model choice, dealer ped type, and unarmed WanderStandard behavior. Dealers reduce their own native deficit but are kept separate from GTA’s ordinary stock-counted total-ped gate, matching the retail population rules instead of folding them into civilians.
City cops
Section titled “City cops”Ambient cops are the one family where San Andreas Online deliberately does not run the retail task.
MTA peds are physically CPlayerPed instances. GTA’s CTaskComplexWanderCop reads CCopPed-only storage in its first/next/control paths and can enter the wanted, alarm, criminal-scan, pursuit, and arrest systems. Running it on an MTA ped is unsafe, and a server-owned ambient cop that can start a real pursuit is not the intended behavior anyway.
San Andreas Online instead reuses GTA’s verified base CTaskComplexWander locomotion vtable slots, identifies the task as WANDER_TYPE_COP, and supplies a deliberately empty scanner. Path nodes, walking, pauses, road crossing, and blocked-node avoidance are preserved, while wanted level, alarms, criminal scans, pursuit, and arrest are unreachable by construction rather than by a runtime check.
A resource opts into that behavior through the new ambient-cop-safe event profile:
-- client, on the current syncerlocal token = acquirePedNativeEventProfile(ped, "ambient-cop-safe")if token then -- Under this profile setPedWander routes to the safe ambient-cop task setPedWander(ped, "walk", -1)endTwo behavior notes matter when writing against this profile:
- Under
ambient-cop-safe,setPedWanderroutes to the safe ambient-cop task instead ofWanderStandard, and it refuseswanderSensibly = false. RetailWanderCophardcodes sensible walking, so acceptingfalsewould produce a hybrid state neither the retail cop nor this profile can reach. isPedNativeAmbientCopWanderTaskreports whether the exact custom ambient-cop task is the primary task. It walks every primary slot rather than only the active leaf, so a temporary native hit or avoidance response does not make the check lie.
Cop density comes from the same profile path as the other families: copTarget, rawCopTarget, and copSuppressionFlags on the population profile, plus "cop" selection on the spawn-candidate proposal.
Civilian couples
Section titled “Civilian couples”Couples are atomic: two peds are placed into one GTA CTaskComplexBeInCouple relationship with an explicit leader, or neither is. A half-formed couple is refused rather than partially applied.
-- client, on the current syncerlocal check = validatePedNativeCouple(pedA, pedB)if check and check.compatible then local token = acquirePedNativeCouple(pedA, pedB, check.leaderIndex) if token then -- GTA now owns walk-side selection, hold-hands, look-at, and give-up distance endendvalidatePedNativeCouple is the pre-flight check. It reports whether the pair is compatible and which member GTA should lead, based on the two peds’ native walk speeds. A couple whose members walk at incompatible speeds would immediately break its own give-up distance. Passing that leaderIndex straight into acquirePedNativeCouple is the intended flow.
After the lease exists, GTA owns the pair behavior: walk-side selection and swapping, hand holding, looking at each other, and abandoning the couple past the give-up distance.
Observers use a separate lease. acquirePedNativeCouplePresentation opens a presentation channel on a non-syncing client, updatePedNativeCouplePresentation refreshes the synchronized walk-side and pairing state each frame, and releasePedNativeCouplePresentation closes it. Observers therefore present a couple walking together without running a second couple simulation.
Keep the two families distinct: the couple lease is owner-only real native AI; the presentation lease is observer-only appearance. getPedNativeCoupleDiagnostic reports which of the two is wrong when a pair looks broken, including partner reciprocity and leader-role mismatches.
Presentation and ownership
Section titled “Presentation and ownership”Only the current syncer runs the real native AI. Reusable observer channels cover:
- locomotion, rotation, and bounded spatial updates;
- ordered native task animations;
- fight/chat and weapon audiovisual presentation;
- selected avoidance, threat, damage, flee, airborne, jump, landing, and climb state;
- gang-group handoff and selected combat context;
- couple pairing and walk-side presentation through its own observer lease.
- autonomous road traffic, including its driver and optional passengers.
Normal MTA synchronization remains authoritative for element transforms, health, death, occupants, and network ownership. A resource should never treat observer-side GTA presentation as a second gameplay simulation.
Minimal resource shape
Section titled “Minimal resource shape”A production population resource normally needs only this high-level loop:
-- client: keep GTA's native population context currentupdateAmbientPedPopulationModels(localPlayer.position)
local profile = getAmbientPedPopulationProfile()local candidate = getAmbientPedSpawnCandidate(localPlayer.position, "civilian")
if profile and candidate then triggerServerEvent("population:candidate", resourceRoot, candidate)endThe server still has to validate proposals, compare live deficits, create the actual peds, assign ownership epochs, handle player departure, and remove every owned element on shutdown.
For groups, the current owner acquires the native group only after the server has assigned the complete member set.
Current limits
Section titled “Current limits”- The completed traffic slice covers civilian road cars, vans, trucks, motorcycles, BMX bicycles, and quads in world 0. Boats, aircraft, trailers, parked-car generation, mission routes, and emergency-service behavior are separate work.
- Ambient cops walk and avoid, but they never escalate. There is no wanted level, pursuit, or arrest behavior, and this is intentional rather than an unfinished stage, because the retail cop task is not safe on an MTA ped.
- The completed ambient population slice is still focused on outdoor world simulation; arbitrary interiors and every GTA population family are not implied.
- No eligible client means no native AI simulation. There is no headless GTA task runner.
- Syncer migration does not serialize every arbitrary GTA task tree. Supported families have explicit handoff or presentation state.
- Different clients can reach collision on different frames, so short local divergence can still occur before authoritative state reconverges.
The later population work is tracked by 1004257b9, 39f782fd9, 75dd85f77, 9fa50ed6a, 53478fdb3, 083927c23, 3accf283f, 5318f4360, 500d3cf7e, and d709fa696. Synchronized road traffic was added in 12e5799 and completed in b23f57d, with production ownership, scaling, and telemetry added in 9e2613e, 145d31a, 3222987, and cad43e835. The current road selection, density, hidden setup, slope settling, look-ahead refill, and population-profile fixes are tracked by 977acff28, 2fa523c3e, c9e8a0fa8, 66b919d48, 15d5c78b1, 1879a4db6, 93912d394, and b5295d493.
Two-client runs cover pedestrian density, group handoff, combat, motorcycle carjacks, dealers, cops, couples, civilian vehicle creation, native driving, passenger entry, ownership handoff, stuck recovery, destruction, and deterministic cleanup. The first 12–20/160/240 scaling change was Lua-checked without a runtime density pass. A later FreeROAM pass observed 17 newly created traffic actors settling on the ground and the local vehicle area refilling to 16/16. After the native percentage-return fix, Game SA and Client Deathmatch built and a FreeROAM connection observed valid civilian, cop, and dealer targets without the earlier target-invalid rejection. These checks do not prove saturation at both global caps on a public server.
Use Story runtime for server-authored mission actors and vehicle tasks.