| name | csp-lua |
| description | CSP (Custom Shaders Patch) Lua API reference for Assetto Corsa modding. Use when working with ac.*, ui.*, render.*, physics.* APIs or any CSP Lua code. |
CSP Lua API Reference Skill
Use this skill when working with CSP (Custom Shaders Patch) Lua code for Assetto Corsa.
Development Rules
NEVER use shell commands or external tools (io.popen, os.execute, etc.) to solve problems. CSP provides comprehensive APIs for most tasks.
Before implementing any functionality:
- Check
reference/lib.luain this skill folder for existing CSP APIs - Use CSP's built-in functions (e.g.,
io.scanDirinstead ofdircommand) - Only fall back to shell if absolutely no CSP API exists
Key File System APIs
From reference/lib.lua:
io.scanDir(directory, "*.csv")- List files matching a patternio.dirExists(path)- Check if directory existsio.fileExists(path)- Check if file existsio.fileSize(path)- Get file size in bytes (-1 on error)io.getAttributes(path)- Get file attributes (fileSize, creationTime, lastWriteTime, etc.)
Track Information
ac.getTrackID()
Returns the track identifier string (e.g., "ks_brands_hatch-gp", "daytona").
local trackId = ac.getTrackID()
-- Returns: "ks_brands_hatch-gp"
Note: ac.getSim().trackId does NOT exist. Use ac.getTrackID() instead.
ac.getTrackDataFilename(filename)
Returns the full path to a file in the track's data folder.
local path = ac.getTrackDataFilename('traffic.json')
ac.getSim()
Returns ac.StateSim reference with information about the simulation state.
Key fields:
sim.dt- Delta time in seconds (0 when paused, affected by replay speed)sim.isPaused- Simulation is pausedsim.isOnlineRace- True if in an online sessionsim.trackLengthM- Track length in meterssim.time- Total time in milliseconds since AC startedsim.gameTime- Total time in seconds since AC startedsim.sessionTimeLeft- Remaining session time in mssim.currentSessionIndex- 0-based session indexsim.rainIntensity- Current rain intensity (0-1)sim.roadGrip- Current track grip (0-1)sim.connectedCars- Number of connected players (online)
Spline & World Coordinates
ac.worldCoordinateToTrackProgress(vec3)- Converts world position to spline position (0-1)ac.trackProgressToWorldCoordinate(pos, linear)- Converts spline position to world positionac.getTrackSectorName(pos)- Returns the name of the sector at given spline position
Car Information
ac.getCar(index)
Returns ac.StateCar for the specified car index (0 = player).
Key fields:
car.id- Car identifier stringcar.speedKmh- Speed in km/hcar.splinePosition- Position on track (0-1)car.lapCount- Number of completed lapscar.lapTimeMs- Current lap time in millisecondscar.bestLapTimeMs- Best lap time in this session (ms)car.gas- Throttle (0-1)car.brake- Brake (0-1)car.clutch- Clutch (0-1, 1 = fully depressed)car.steer- Steering angle in degreescar.gear- Current gear (0 = N, -1 = R)car.handbrake- Handbrake (0-1)car.isLapValid- True if current lap is considered valid by ACcar.resetCounter- Increments each time car is reset (teleported)car.lastLapCutsCount- Number of cuts in the last lapcar.collisionDepth- Depth of current collision in meterscar.isInPitlane- True if in pitlane areacar.racePosition- Current position in session/race
Car Events
ac.onTrackPointCrossed(carIndex, progress, callback)- Triggered when car crosses a specific spline pointac.onCarCollision(carIndex, callback)- Triggered on collisionac.onCarJumped(carIndex, callback)- Triggered when car jumps
Hotkeys
ac.ControlButton(name, defaults)
Creates a bindable hotkey control.
local myButton = ac.ControlButton('ac-tracer/MyHotkey', {
keyboard = { key = ui.KeyIndex.T, ctrl = true },
gamepad = ac.GamepadButton.Y
})
-- Check if pressed this frame
if myButton:pressed() then ... end
-- Check if currently held
if myButton:down() then ... end
-- Render binding control in settings (UI function)
myButton:control(vec2(120, 0))
Storage
ac.storage(layout, prefix) - PREFERRED
Advanced persistent storage with default values and automatic synchronization. This is the recommended approach.
-- Define layout with defaults (at module level)
local config = ac.storage{
showTraces = true,
autoHide = false,
hideSpeed = 20,
colorOwn = rgb(0.2, 0.2, 0.2)
}
-- Access/Modify (automatically persists on assignment)
if ui.checkbox('Show traces', config.showTraces) then
config.showTraces = not config.showTraces -- Auto-saved!
end
config.hideSpeed = ui.slider('##speed', config.hideSpeed, 0, 100, 'Speed: %.0f')
Values can be: strings, numbers, booleans, vectors (vec2/vec3/vec4), colors (rgb/rgbm).
Note: Direct access ac.storage[key] = value only supports strings and requires manual serialization. Avoid this pattern - use the table-based approach instead.
SDK Examples
See working examples in other CSP apps:
apps/lua/Radar/Radar.lua- Simple config with checkboxes, sliders, colorsapps/lua/CSPDataLogger/CSPDataLogger.lua- Basic boolean/number settings
Logging & UI
ac.log(message)
Writes to CSP log.
ac.setMessage(title, description)
Shows a toast-style message in the game UI.
ac.lapTimeToString(ms, allowHours)
Utility to format milliseconds into "M:SS.ms" format.
UI & Windows
Window Types
Prefer these wrappers over ui.beginWindow / ui.endWindow as they handle crashes gracefully and provide standard styling.
ui.toolWindow(id, pos, size, noPadding, inputs, content)
Standard app window with background. Best for main app windows.
function script.windowMain(dt)
ui.toolWindow('MyAppMain', vec2(100, 100), vec2(400, 300), false, true, function()
-- Window content here
ui.text("Hello World")
end)
end
ui.transparentWindow(id, pos, size, noPadding, inputs, content)
Window with no background. Best for HUD overlays or non-intrusive elements.
function script.windowHUD(dt)
-- Transparent, pass-through inputs unless interactive
ui.transparentWindow('MyAppHUD', vec2(0, 0), ui.windowSize(), true, false, function()
ui.textColored("HUD Overlay", rgbm.colors.red)
end)
end
Layout Best Practices
Scrollable Areas (ui.beginChild)
Use child windows for scrollable content lists.
-- Reserve 30px at bottom for footer
ui.beginChild('ScrollableList', vec2(0, -30), false, ui.WindowFlags.None)
for i = 1, 100 do
ui.text("Item " .. i)
end
ui.endChild()
-- Footer (pinned to bottom due to child height reservation)
ui.button("Close", vec2(-1, 0)) -- -1 width = full width
Grouping & Layout
ui.beginGroup()/ui.endGroup(): Group items to treat them as one for hover checks or same-line layout.ui.sameLine(offset, spacing): Place next item on the same horizontal line.vec2(-1, 0): Use as size to fill remaining horizontal space.
Styling
Use ui.pushFont, ui.pushStyleVar, and ui.pushStyleColor to customize look, but ALWAYS pair with ui.pop....
ui.pushFont(ui.Font.Title)
ui.text("Title")
ui.popFont()
ui.pushStyleVar(ui.StyleVar.Alpha, 0.5)
ui.button("Dimmed Button")
ui.popStyleVar()
Settings Integration
ui.addSettings(params, callback)
Registers a settings window accessible via the taskbar context menu or settings apps.
ui.addSettings({
name = "My App Settings",
id = "MyAppSettings",
icon = ui.Icons.Settings,
size = {
default = vec2(400, 300),
min = vec2(300, 200)
}
}, function()
ui.header("General Options")
if ui.checkbox("Enable Feature", state.enabled) then
state.enabled = not state.enabled
end
ui.separator()
ui.header("Visuals")
local newVal, changed = ui.slider("Opacity", state.opacity, 0, 1, "%.2f")
if changed then state.opacity = newVal end
ui.combo("Mode", state.mode, ui.ComboFlags.None, function()
if ui.selectable("Mode A", state.mode == "A") then state.mode = "A" end
if ui.selectable("Mode B", state.mode == "B") then state.mode = "B" end
end)
ui.text("Toggle Key:")
ui.sameLine()
myBindableHotkey:control(vec2(-1, 0))
end)
Window Visibility & Management
Use ac.getAppWindows() to list all windows and check their status, and ac.accessAppWindow() to modify them.
ac.getAppWindows()
Returns an array of descriptors for all available windows.
local windows = ac.getAppWindows()
for _, w in ipairs(windows) do
ac.log(string.format("Window: %s, Visible: %s", w.name, tostring(w.visible)))
end
ac.accessAppWindow(windowName)
Returns an ac.AppWindowAccessor for the specified window name.
local window = ac.accessAppWindow("ac-tracer/corners")
if window and window:valid() then
window:setVisible(true)
end
ac.setAppWindowVisible(appID, windowFilter, visible)
Toggle windows that might be hidden or not yet initialized.
ac.setAppWindowVisible("ac-tracer", "telemetry", true)
Full API Reference
For the complete API reference with all types, enums, and functions, read the file reference/lib.lua in this skill folder (17,000+ lines of type definitions and documentation).
Scripts
scripts/logs.ps1
View CSP logs filtered for ac-tracer entries.
# Default: last 20 entries
.\.claude\skills\scripts\logs.ps1
# Last 50 entries
.\.claude\skills\scripts\logs.ps1 -l 50
# Only errors
.\.claude\skills\scripts\logs.ps1 -only ERROR
# Only warnings
.\.claude\skills\scripts\logs.ps1 -only WARN