SLOW ROADS — PROGRAM
Scripts are JavaScript. They are saved locally, and never auto-start.
Start creates a fresh run. Pause keeps variables but releases held controls.
Resume continues that run. End calls onStop and releases input/camera overrides.
End a script before editing its code. Drag the sidebar's right edge to resize it.
Enable Gameplay → UI → Show console for output in the top-right corner.
The scripting api is separate from the desktop app's internal API. Scripts run
in workers; an endless loop is stopped rather than blocking the game. Do not use
while(true) to animate something: use onTick. Timers pause with the game/script.
Callbacks must finish promptly; expensive work should be split across ticks.
The game is read synchronously through api. Calls can throw useful errors for an
invalid ID, value, unavailable setting, or a road point not yet generated.
console.log/info/warn/error and api.log write to the in-game console.
UNITS AND IDs
Player ID: api.getId() returns 1. Traffic IDs are integers, unique for each spawn.
IDs become invalid after despawning. Use getAllEntities() to find current IDs.
Coordinates and distances are metres; velocity is metres/second. Numeric results
retain fractional precision. Angles are radians unless stated otherwise.
Positive steering means left; negative means right, matching the game's input.
Road distance is metres ahead of the player's current road position; negative
means behind. Lane 0 is the leftmost lane in the player's direction of travel.
On two-sided roads, lane indexes refer to one carriageway, not both together.
CALLBACKS
Declare functions with these names, or assign functions to these names:
function onStart() {} Called once after Start.
function onStop() {} Called on End (unless forcibly terminated).
function onPause() {} The script's Pause button.
function onResume() {} The script's Resume button.
function onTick(dt) {} Game update; dt is elapsed seconds.
function tick(milliseconds) {} Optional alternative, inspired by Bloxd.
function onFrame(dt) {} Also runs while the game is paused.
function onGamePause() {} Esc/game pause, distinct from script pause.
function onGameResume() {}
function onKeyDown(key) {} KeyboardEvent.code, e.g. "KeyW".
function onKeyUp(key) {}
function onMouseDown(button, pos) {} button: 0 left, 1 middle, 2 right.
function onMouseUp(button, pos) {} pos: {x,y}, viewport pixels.
function onEntitySpawn(id, type) {} type: "Player" or "Traffic".
function onEntityDespawn(id) {}
function onVehicleChanged(id, type) {}
function onWorldLoaded(style, seed) {}
function onPlayerRespawn(id) {}
function onWheelGroundedChange(id, wheelIndex, grounded) {}
Use onTick OR tick for your main update, unless you want both called. Game ticks
are delivered at about 30 Hz. No game ticks run while Esc pauses driving. onFrame
can still inspect keys and resume the game. Script Pause suspends all callbacks
except its lifecycle callbacks. Resume preserves variables. Newly started scripts
receive the current world/player through getters; world/entity callbacks announce
subsequent changes. Ordinary function declarations and // comments both work.
setTimeout(fn, milliseconds), setInterval(fn, milliseconds), clearTimeout(id), and
clearInterval(id) use managed timers that freeze while the game/script is paused.
ENTITIES AND MOTION
api.getId() → integer
api.getAllEntities() → {player: [1], traffic: [2,3,...]}
api.getEntityType(id) → "Player" | "Traffic"
api.getVehicleTypePlayer(id=1) → "Coupe" | "Bike" | "Bus" | "DriftCoupe"
api.getVehicleTypeTraffic(id) → "Model1", "Model2", ...
api.getVehicleTypes() → {player: [...names], traffic: [...names]}
api.setVehicle(id, name) → true; begins the normal vehicle reload/reset
api.despawn(id) → false for the player/missing IDs, true on successful removal
api.getPosition(id) → {x,y,z}
api.setPosition(id, x,y,z)
api.setPosition(id, [x,y,z])
api.setPosition(id, {x,y,z})
api.getVelocity(id) → {xv,yv,zv}
api.setVelocity(id, xv,yv,zv)
api.setVelocity(id, [xv,yv,zv])
api.setVelocity(id, {xv,yv,zv})
api.getDistance(id1,id2) → three-dimensional distance in metres
api.getSpeed(id) → {kph,mph}
api.setSpeed(id, {kph:100}) OR api.setSpeed(id, {mph:60})
Supply exactly one unit. Player speed is set along its current forward axis.
api.getTripDistance(id=1) → {km,miles,meters}
api.resetTripDistance(id=1)
Player velocity setters change momentum once; ordinary physics then continues.
Traffic normally follows its road AI. Direct traffic position/velocity or input
control takes over that entity until api.releaseControl(id), Pause, or End.
Traffic speed control keeps road following. It is held until released.
Teleports are excluded from trip distance. Player trip distance is tracked during
the drive; traffic trip distance is tracked while the Program runtime is active.
INPUT AND WHEELS
api.getThrottle(id=1) → analogue throttle; positive forward, negative reverse
api.getBrake(id=1) → [false,null] | [true,"brake"] | [true,"handbrake"]
api.getSteering(id=1) → -1…1
api.setSteering(id, value)
api.getBoost(id=1) → boolean
api.setBoost(id, boolean)
api.getInput(id=1) → {throttle,brake,steering,handbrake,boost}
api.setInput(id, {throttle,brake,steering,handbrake,boost})
Partial objects are allowed. Throttle/steering: -1…1. Other inputs: 0…1.
Values are held until replaced or released. The most recent script write wins.
api.releaseControl(id=1) → release this script's held controls for the entity
api.getWheelCount(id)
api.getWheelPosition(id, wheelIndex) → {x,y,z}, wheel centre in world coordinates
api.isWheelGrounded(id, wheelIndex) → boolean
api.getWheelSlip(id, wheelIndex) → 0…1
api.getWheelRPM(id, wheelIndex) → revolutions per minute
api.getWheelSteerAngle(id, wheelIndex) → steering relative to maximum, -1…1
Player wheel order: front left, front right, rear left, rear right.
Bike exposes two visible wheels: front, rear. Traffic wheel order follows its
native model: front right, front left, rear right, rear left. Traffic uses road AI,
not the player's tyre physics: its slip is zero, RPM follows model radius/speed,
and grounding uses terrain height. Wheel positions remain available at distant
levels of detail where wheel meshes are hidden.
WORLD, SETTINGS, AND ROADS
api.getVersion() → installed app version string
api.getTime() → "Dawn" | "Day" | "Dusk" | "Night"
api.getWeather() → "Clear" | "Weather" (the game's overcast/weather state)
api.getSeason() → "Spring" | "Summer" | "Autumn" | "Winter"
api.getWorldStyle() → Hills, Dirt Roads, Inland Sections, Shorelines,
Off-World, or Driftmas
api.getRoadsettings() → {curves,style}; getRoadSettings() is an alias
curves: Straight, Gentle, Normal, Winding
style: OffroadThin, OffroadWide, SingleThin, SingleWide, Autobahn1…Autobahn4
Autobahn1 denotes the ordinary paved road with one lane per direction.
api.getWorldSeed()
api.worldReload() → start a new generation with the current world options/seed
api.isPaused() → true while Esc has paused the game
api.setPaused(boolean)
api.getSetting(name)
api.getSettings() → available IDs, labels, values, types, and permitted ranges
api.setCarSetting(name, value)
api.setSetting(name, value) → also supports ordinary scene/gameplay/audio settings
Use the UI label, property name, or full ID, e.g. "Scale", "scale", or
"tuning.scale". Full IDs resolve ambiguous names. Changes use native stores and
persistence. Scale is 0.1…10; Downforce is -1…10. Driftmas season stays locked.
api.getRoadPosition(distance=0) → {x,y,z} on the road centreline
api.getRoadWidth() → full road width in metres
api.isOnRoad(id)
api.getLaneCount() → {multi:boolean,count:integer}
multi means two carriageways; count is lanes per direction.
api.getLanePosition(lane, distance=0) → {x,y,z} on that lane's centreline
api.getCurrentLaneIdx(id=1) → index, or null when off-road
Road queries read already-generated geometry; asking beyond it throws a RangeError
instead of fabricating a position. Settings and world/vehicle changes initiated
by a manually started script take effect directly. Ending a script releases
temporary controls; it does not undo settings, teleports, or world generation.
CAMERA
api.getCamera() → {mode,position,rotation,target,fov,zoom,distance,shake,look}
api.getCameraModes() → supported native camera mode names
api.getCameraMode()
api.setCameraMode(mode)
api.getCameraPosition() → world {x,y,z}
api.setCameraPosition({x,y,z}) Also accepts [x,y,z] or x,y,z.
api.getCameraRotation() → world {x,y,z}, radians, YXZ Euler order
api.setCameraRotation({x,y,z}) Also accepts [x,y,z] or x,y,z.
api.getCameraTarget() → entity ID
api.setCameraTarget(id)
api.getCameraFov() api.setCameraFov(degrees) — 1…175
api.getCameraZoom() api.setCameraZoom(zoom) — 0.1…10
api.getCameraDistance() api.setCameraDistance(metres) — 0.1…10000
api.getCameraShake() api.setCameraShake(amount) — 0…10 metres
api.shakeCamera(strength, duration) — temporary shake, duration in seconds
api.getCameraLook() → {yaw,pitch}
api.setCameraLook(yaw,pitch) Radians; pitch between -π/2 and π/2.
api.resetCamera() Releases this script's camera overrides.
Camera position, rotation, target, FOV, zoom, distance and shake overrides are
temporary and released on Pause/End. An explicit rotation takes precedence over
looking at a target. Mode changes are normal game settings and persist.
KEYBOARD, MOUSE, AND LOGGING
api.isKeyDown(key) api.isKeyPressed(key) api.isKeyReleased(key)
api.getMousePosition() → {x,y}, viewport pixels
api.isMouseDown(button=0)
api.pressKey(key) api.releaseKey(key)
api.log(...values) console.log(...values)
console.info(...values), console.warn(...values), console.error(...values)
console.clear() api.now() → wall-clock milliseconds
Use key codes such as KeyW, ArrowLeft, ShiftRight, Space, Escape. Single letters
are also accepted: "w" maps to KeyW. Pressed/released flags last for one script
frame; held keys remain down. Typing in the editor does not drive the vehicle.
Virtual keys are released when the owning script pauses, ends, or fails.
EXAMPLE — change scale once
function onStart() {
api.setCarSetting("Scale", 1.5);
api.log("Scale:", api.getSetting("Scale"));
}
EXAMPLE — camera follows a traffic vehicle
function onStart() {
const cars = api.getAllEntities().traffic;
if (cars.length) {
api.setCameraTarget(cars[0]);
api.setCameraDistance(12);
api.setCameraFov(65);
}
}
EXAMPLE — H logs speed, with no held driving controls
function onKeyDown(key) {
if (key === "KeyH") {
api.log(api.getSpeed(api.getId()).kph.toFixed(1), "km/h");
}
}