Lifecycle, errors and budgets
A new instance
An instance is a script bound to an object. A new instance is a binding that starts for the first time; one added to a running world, or opened again after an error, is new too.
- The binding is checked: host type, contract fields, events and property values. If it does not fit, it does not start and the error is reported.
- Memory takes its initial values. The state is the
auto state, or the empty state. - In instance order, every instance runs a start transaction: first the
auto state'son enter, thenon ready. The two are one transaction; nothing in between is published, and an error undoes both. - Events the start transactions raise are delivered after every instance has started.
- Declared timers do not fire in the starting minute; their first firing is the next minute that fits.
A loaded instance
An instance opened from a save, with the same definitions, goes on where it was: memory, state,
started timers, random numbers and counters come back as they were. ready and the current
state's enter do not run again. Opening a save with changed definitions is not loading but
a migration.
When a binding is removed, its timers and subscriptions go; the fields it wrote keep their last values.
Errors
A runtime error (overflow, division by zero, a budget, a check of the game…) undoes the whole transaction. The report gives the file, line and column, the call stack, the event or timer, the world's time and the transaction's log lines; when a field write fails the game's check, it shows the line that last wrote that field.
Then that binding closes: its timers and subscriptions go, the fields keep their last accepted values, and the other bindings go on. Nothing makes up a "safe state" for it, and nothing retries it on its own: closing only the handler would break the state, and retrying would repeat a lasting error.
- In Play the world pauses at that moment, and the log writes the error with a link to its line.
- A closed binding starts again as a new instance after a migration with the fixed definition; the events it missed while closed do not come back.
- An error during a migration does not close the binding; it cancels the migration.
Budgets
Cost is logical, so the same code fails at the same point everywhere:
- every instruction is one unit; a library call costs what its schema says; every operation that makes or scans a string adds one unit per 16 bytes;
- memory is a logical size, not what is really allocated;
- there is no wall-clock limit, and time spent standing at a breakpoint does not count.
The limits are the game's settings. In Play today:
| Limit | Value |
|---|---|
| cost per transaction | 100,000 units |
| memory per transaction | 1 MB |
| distinct fields written per transaction | 16 |
| started timers per instance | 16 |
| transactions per moment | 5,000 |
Going over a limit is a runtime error, reported at the line that went over. At the limit of a moment, the transaction that goes over fails and its binding closes; the events still queued in that moment are not delivered and are reported by type and count. The next moment starts as usual.
The log limit (20 lines of 400 characters per transaction) is not an error: the rest is cut and marked.
Determinism
The same definitions, the same starting save and the same inputs (outside events and clock
steps) give the same result, so an error can be replayed. The rules that make it so are the same
on the server, in the native game and in the browser: evaluation left to right, 32-bit int
with overflow as an error, IEEE float with NaN and infinity as errors, round halves away from
zero, strings by code point, a seeded random generator per instance, and the world's clock in
place of a wall clock. The exact rules are in Determinism.