starlit

Limits, files and saved values

Every number a script can run into: files, memory, time, drawing, the menu, the tuning modules, what a call costs, the rules for file names, and how a script's control values are saved.

These are the numbers in the current build. A limit that says "clamped" changes your value without an error. A limit that says "error" raises one.

Files and sources

limitvalue
Size of one script256 KiB (262,144 bytes). Larger files are refused.
EncodingValid UTF-8. A leading byte order mark is skipped. A NUL byte is refused. A file that starts with byte 0x1B (a precompiled chunk) is refused.
Files shown on the page32, sorted by name without regard to case
Directory entries we look at4096
Scripts loaded at once16
File name5 to 64 characters including .lua

Memory

limitvalue
One script8 MiB
All scripts together32 MiB

Time

callinstructionswall clock
Main chunk5,000,000100 ms
events.on_paint100,0001.5 ms
ui.button callback100,0001.5 ms
events.on_unload20,0000.5 ms
Game event handler, one call50,0000.5 ms
events.on_shot, one call50,0000.5 ms
An on_change function, one call100,0001.5 ms
One call of a surface on_draw50,0000.5 ms
All scripts together, per framenot counted3 ms

Two strikes stop a script. Behaviour on overrun is described in How scripts run.

The thread that runs a script must have at least 256 KiB of stack left when we enter it. If it has less, the call is refused with not enough thread stack to run the script and counts as a strike.

Library caps

functioncap
Pattern matching200,000 steps per call, pattern up to 256 bytes, subject up to 256 KiB
string.rep, table.concatresult up to 64 KiB
table.moveup to 2^18 elements
table.sortup to 65,536 elements
Lua stack60,000 slots
Nested C calls200

Drawing

limitvalueon overflow
Commands per script and frame2000extra commands are dropped
Text per command128 bytescut at a character boundary
Text per script and frame16 KiBextra text commands are dropped
Coordinates-16384 to 16384clamped
Width and height0 or morenegative values become 0
thickness1 to 16clamped
circle radius0 to 4096clamped
Corner radius of rounded rectangles and gradients0 to 64clamped
Points of draw.polyline2 to 64error
Points of draw.polygon3 to 32error
Point numbers per script and frame2048the command is dropped
Open clips8extra draw.clip calls are dropped

Drawing in the world

limitvalueon overflow
Lines per script and frame256the line is skipped
Polylines per script and frame16the polyline is skipped
Points of draw.polyline3d2 to 64error
Polyline point numbers per script and frame3072the polyline is skipped
Boxes per script and frame64the box is skipped
Circles per script and frame32the circle is skipped
segments of draw.circle3d3 to 64, default 32clamped
Hitboxes per script and frame64the hitbox is skipped
Hitbox ids0 to 18nothing is drawn
Projected segments per script and frame1024the shape is skipped
World coordinates, sizes and radii-1e6 to 1e6clamped
Distance in front of the camera16384 unitsthe shape is cut away
Cost of a call30 instructions plus 3 for each segment

A line counts 1 segment, a polyline its points minus 1 (plus 1 when closed), a box 12, a circle its segments and a hitbox 16.

Controls

limitvalue
Interactive controls per script64
Labels per script32 (they do not count against the 64)
Groups per script12, the default group included
Label of a control48 bytes
Name of a group32 bytes
Items of a combo or multicombo1 to 16
One item32 bytes
Slider bounds and values-1,000,000,000 to 1,000,000,000
Queued presses of one button8

Interactive controls are the checkbox, slider, combo, multicombo, colour, keybind, button, input, list and fold. Display rows (label, separator, text, info and fold end) are counted as labels.

limitvalue
Order of a group-100 to 100, rounded and kept within
Tooltip64 bytes
Text line, value of an info row, text of an input64 bytes
Length limit of ui.input1 to 64, default 32
Visible rows of ui.list3 to 10, default 6
Folds nested4
Width of a control0.2 to 1 of the group, 0 for the rest of the row
on_change calls per script and frame16, within 1.5 ms
limitvalue
Tabs per script2
Tabs in all running scripts together3
Name of a tab12 bytes
Surfaces per script2
Name of a surface, title of a floating surface32 bytes
Size of a surface32 to 900 wide, 32 to 700 high
Commands in the buffer of a surface1000
Text in the buffer of a surface8 KiB
Point values in the buffer of a surface1024
Wheel steps seen by a surface-3 to 3
ui.style rounding0 to 6
ui.style row_gap2 to 8
ui.style header_spacing0 to 3

Game data

limitvalue
Players in game.players() and entity.players()64
Player name32 bytes
Velocity components-32768 to 32768
Property reads from the engine per script and frame512
Value of cvar.get-1e12 to 1e12
Coordinates of bullet_impact-1e6 to 1e6, otherwise the event is dropped
Coordinates passed to game.to_screeneach within -1e6 and 1e6, otherwise it returns nil

Events and shots

limitvalue
Event ring between the game and the client256 entries, the newest are dropped when it is full
Event queue the scripts read128 entries
Event calls per script and frame32, within 1.5 ms
Age at which an event is dropped5 seconds
Shot results kept64
Shot calls per script and frame16, within 1.5 ms
Age at which a shot result is dropped5 seconds

Resolver

limitvalue
Accepted resolver.set_yaw calls per script and frame256
Range of a hint-60 to 60 degrees, clamped
Resolution of a hint0.01 degree
Life of a hint after the last call2 seconds

Rage, settings, movement and visuals

limitvalue
rage calls per script and frame128, accepted or not
Life of a rage value after the last call500 ms
Hit chance asked for1 to 100, rounded and clamped
Minimum damage asked for1 to 110, rounded and clamped
Double precision chain cap1 to 3
Settings held by one script64
Accepted settings.override calls per script and frame256
Life of a settings override after the last call500 ms
Ids returned by one settings.list call256
Accepted movement.intent calls per script and frame4
Entries of an intent table16
ttl of an intent1 to 6 ticks, which is 16 to 96 ms
forward and side-1 to 1, clamped
visuals calls per script and frame1024
Players a script styles in one frame64
Text lines and bars per player4 lines of 24 bytes, 2 bars
Life of a visuals valuethe frame it was set in and the next one

Log

limitvalue
One line256 bytes
Rate20 lines per second, bursts of 20
Lines kept by the page64, the last 14 are shown

API costs

Every function we provide charges instructions to the running call, in addition to the instructions of your own code. This is why a loop that calls the API a lot ends earlier than a loop of the same length that does not. The costs are fixed.

functioninstructions
time.*, screen.size, game.in_game10
log.info50
events.on_* (registering)20
game.local_player100
game.players100 + 180 for each player
game.to_screen20
entity.get, entity.local_player30
entity.players100 + 60 for each player
Entity handle methods20
ent:eye_position60
ent:get40, plus 60 when the value is read from the engine
cvar.get30
Every draw function except polyline and polygon20
draw.polyline, draw.polygon20, plus 1 for every number in the points table
Creating a control or a group50 (label 40, multicombo 60)
get, set, has of a control10 (get of a multicombo: 10 plus 5 for every item)
resolver.set_yaw, resolver.clear20
resolver.get_yaw10
rage.override_double_precision, rage.override_auto_stop30
The other rage functions20
movement.intent40
movement.release10
settings.get, settings.base, settings.info15
settings.list40, plus 2 for each id returned
settings.override30
settings.clear20
settings.clear_all20, plus 2 for each setting given back
visuals.player30
v:hide, v:color, v:glow25
v:bar30
v:text, v:chams40
v:reset20
draw.line3d, draw.polyline3d, draw.box3d, draw.circle3d, draw.hitbox30, plus 3 for each segment of the shape

A full game.players() call costs 100 + 180 * 64 = 11,620 instructions, which is a little more than a tenth of the budget of on_paint. Call it once for each frame and keep the table.

File names

The page lists a file only if its name passes all of these rules. Anything else in the folder is ignored.

  • It is a regular file, not a directory, a link, a junction or a device.
  • It ends in .lua. The ending may be in any case.
  • It is 5 to 64 characters long including the ending.
  • It does not start with a dot.
  • It has none of the characters < > : " / \ | ? * and no control characters.
  • The part before .lua does not end in a dot or a space.
  • It is not a reserved device name: con, prn, aux, nul, com0 to com9, lpt0 to lpt9. Adding another extension does not help (con.x.lua is refused).

The name of the script is the file name without .lua. It is the title of the script's group box, the prefix in the log, and the first part of every error message (name:12: message).

When we load a file we open it without following links. It must be a disk file with a single hard link, its real path must be directly inside the lua folder, and its size must be 256 KiB or less. We read it once, with one byte more than its size, so a file that grows while we read it is caught.

Saved values

Controls keep the value the user gave them. The client stores the values of one script in a small file:

Documents\Starlit\lua\cfg\<file name>.cfg

For hello.lua that is cfg\hello.lua.cfg. The folder cfg is created when the first value is written.

When it is written. After a value changed, at most once a second, and again when the script stops. A failed write is tried again after a second.

When it is read. Before the script starts. Saved values are applied as each control is created, so get() in the main chunk already returns the saved value and not the default you passed.

What is saved:

controlsaved
checkboxthe state
sliderthe value
combothe selected item
multicombothe selected items
colourthe four channels
keybindthe key the user bound. The active state is not saved.
foldwhether the user left it open
listthe chosen item
label, button, separator, text, info, inputnothing

How a saved value finds its control. A control is matched by its kind, the text of its label and its position among the controls with the same kind and label. If you rename a label, or change a checkbox into a slider, the control starts from its default. Two controls with the same kind and label are matched in the order you create them.

What the file contains. A header of 8 bytes (SLC1 and a count) and 16 bytes for each saved control: its kind, its position among equals, a hash of its label and 8 bytes of value. One more record keeps the user's show in pages switch for the script. At most 1048 bytes. Text from the script is never written into it, and files written by earlier builds still load.

What the client does with a bad file. It checks the header, the size and every record, and clamps every value to the bounds of its control. A file that does not pass is ignored and the script starts from its defaults. The file is written to a temporary name and renamed, so a half-written file is never read.

The client manages this file for you. To reset a script to its defaults, unload it and delete its .cfg file.

Error messages

Most mistakes raise a Lua error with a fixed text. The text never contains data from the script that raised it. The messages start with the script name and the line, for example hello:7: bad argument #4 to 'text' (number expected).

messagecause
bad argument #N to 'f' (number expected)A value of the wrong type. The same form is used with string expected, function expected, boolean expected and table of points expected.
bad argument #N to 'f' (finite number expected)NaN or an infinite number where a finite one is needed.
unknown font (ui, bold, small, medium, header, large)The font name is not one of the six.
unknown text style (normal, shadow, outline)The style is not one of the three.
unknown mode (hold, toggle, always)The keybind mode is not one of the three.
draw is only available inside on_paintA draw function was called from another callback or from the main chunk.
invalid handleA method was called on a value that is not a handle of this script, or on a group object in place of a control and the reverse.
handle is read-onlyA field of a handle was assigned.
table is read-onlyA module table was assigned to.
too many controls (64), too many labels (32), too many groups (12)A cap on controls was reached.
too many tabs (2), too many tabs in all scripts (3), too many surfaces (2)A cap on tabs or surfaces was reached.
that tab name is takenThe name belongs to one of Starlit's tabs or to a tab of another running script.
give one of tab, page and floatingA surface was given more than one place.
unknown page (scripts, rage, legit, visuals, misc), unknown column (auto, left, right)A group or surface option has a value that does not exist.
no open fold in this group, folds nest 4 deep at mostfold_end without a fold, or a fold nested too deep.
this control has no change event, this control has no name, this control has no valueA handle method was called on a kind of control that does not have it.
unknown field in the movement intent, invalid value in the movement intent, conflicting fields in the movement intentThe table passed to movement.intent is wrong.
max must be greater than minui.slider got bounds in the wrong order.
1 to 16 items expected, items must be strings, item index expectedA combo or multicombo got a bad item list or default.
a button has no valueset was called on a button.
not a multi-select controlhas was called on something other than a multicombo.
script budget exceededA time or instruction budget ran out.
script memory limit reachedAn allocation was refused.

Messages that appear on a script's entry when it cannot start:

messagecause
file name is not allowedThe name does not pass the file name rules.
file could not be opened, file could not be readThe file is locked, gone or unreadable.
not a regular file inside the lua folderThe path is a link, a directory, a device, has several hard links or leads outside the folder.
script is larger than 256 KiBThe file is too big.
script contains a NUL byte, script is not valid UTF-8The text is not accepted.
precompiled bytecode is not acceptedThe file starts with byte 0x1B.
too many scripts are loaded (16)Sixteen scripts are already running.
script state could not be prepared, script state could not be created, could not create the script heap, out of memoryThe client could not allocate what the script needs.

previous: How scripts run · next: What scripts can do