A live object is a proxy for something in the game: reading one of its fields reads the game at that moment, and assigning a field writes the value back. The cars, the players, the props, the Nemesis actors and the race are live objects. There are no get_ and set_ functions anywhere in the API.

Reading and writing#

local car = ss.vehicle                    -- the local player's car (nil outside a race)
print(car.speed, car.speed_kmh, car.race.lap)

car.position.y = car.position.y + 3       -- read the game, write it back: the car lifts 3 m
car.rotation.yaw = 90                     -- degrees
car.velocity = car.forward * 40           -- m/s: the physics velocity is writable too
car:impulse(car.up * car.mass * 5)        -- an action with nothing to read back is a method

ss.vehicles[2].position = car.position + car.forward * 40   -- another car, 40 m ahead of ours
ss.race.splits = 8                        -- the race is an object in the same style

The reference tables mark each field as read-only or not. Anything that performs an action rather than holding a value is a method, called with a colon: car:impulse(vec), prop:mem(0x9c, "int").

Groups#

Live objects come in groups, and every group works the same way. group[i] indexes from 0 (the index the race events carry as player), group.count is the number of objects, group.all() returns a plain table for ipairs, and group.each() returns an iterator for a for loop.

TypeFieldDescription
vehicle ss.vehicles The cars, one per player. ss.vehicle is the local player's. See ss.vehicles.
player ss.players The players: position, lap, score, timers. ss.player is the local one. See ss.players.
prop ss.props The level's movable props, with find and near searches. See ss.props.
powerplay ss.powerplays The level's Power Plays, which a script can fire, reset and switch off. See ss.powerplays.
actor ss.nemesis Survival's trucks and the attack modes' cars. See ss.nemesis.
routine ss.routines The mod's own running routines. See Routines.
for car in ss.vehicles.each() do
  ss.log("%d: %s at %.0f km/h", car.player, car.state, car.speed_kmh)
end
local props = ss.props.all()              -- a plain table, for ipairs or #
local first = ss.players[0]               -- player 0, not necessarily the leader

Live vectors#

A field that is a vector (position, rotation, velocity, angular_velocity) is live as well. car.position.y = 5 reads the transform, replaces that one component and applies it. Assigning the whole field accepts anything vector-like: { x =, y =, z = }, an ss.vec3, { 1, 2, 3 }, another object's live vector, or { pitch =, yaw =, roll = } for a rotation.

The operators (+, -, * number, / number) return plain copies that do not write through, which is what makes car.position + car.forward * 40 a point and car.position = that a move. :copy() returns a plain snapshot on purpose, for a value that must not change while a loop runs over it. ss.vec3 lists the methods.

Validity#

A live object belongs to the race it was created in. Every object has a valid field, which is true while the object is in the running race. An object kept across the end of a race reads valid == false, returns nil for every other field, and raises an error when written to. When the race ends (race_end), the groups are emptied before the event's handlers run, so a handler sees no cars and no props rather than freed ones. It's recommended to fetch objects fresh on race_start rather than keep them from the previous race.

Cost#

Every field read is one call into the hook, a few loads and a unit conversion. That is cheap enough for a handler that reads a handful of values a frame, and expensive enough that a loop over many fields of many objects should not read them one by one. A player's snapshot is a plain table of all its fields from one call, which is useful for such loops. Measured in a race with 867 props: ss.props.near(point, 400) takes 0.2 ms, a sweep of every prop reading state 0.5 ms, ss.props.find("bollard") 0.1 ms, and moving a compound prop 0.1 ms. An ss.params path resolves on every access by walking the tree, which takes a fraction of a millisecond. It's recommended to keep a value that is read more than once per frame in a local.

Errors#

Assigning a read-only field (car.speed = 0, player.position = 1) results in an error, as does writing to an object whose valid is false. An error inside a handler is caught and logged with a traceback, and the other handlers still run; see How to: Your first mod.