tecs.application
The host-neutral application lifecycle.
The Rust host owns polling and passes one sealed event batch and elapsed delta to iterate. This module owns only game lifecycle: plugin installation, startup, world updates, crash containment, suspension, quit requests, and shutdown. Windowing, rendering, audio, and platform event conversion are Rust service boundaries rather than application fields.
Each application owns an asset loader. Config.mcpPort opts into one loopback MCP listener, opened before plugin installation so startup failures remain inspectable. Only one application per runtime may own that listener, because the tool registry names one world. Nil opens no listener; zero chooses a port.
A host calls poll outside iterate, once per host turn, even while gameplay is suspended or a lifecycle operation is parked. tecs.host does this and parks asynchronous startup as well as updates. A custom host must follow the same ordering. Diagnostics remain available during a parked operation; tools that could touch its in-progress world report a retryable refusal.
Driving frames from outside#
A test harness or debugger drives an application deterministically with three calls, which the MCP send_event and clock_* tools reach. queueEvent queues a platform event for the next frame that runs, delivered after the host's batch for that turn exactly as a platform event is, so per-frame edges such as keyPressed survive the frame's beginFrame. pause holds the simulation: an iteration that has no granted frame runs no system, folds no input, leaves frame and elapsed unchanged, and keeps the host's events for the frame that eventually runs, honoring only a quit. The host keeps rendering the held world, so a screenshot requested while paused shows it. step grants frames that run while paused, each with the fixed delta it names rather than the host's measured one, so a paused application advances by exactly the granted frames and simulated time.
app:pause()
app:queueEvent(tecs.platform.events.key(true, "Space", " ", " ", 0, false, 0))
app:step(1, 1 / 60)
app:iterate(0.5, nil) -- runs one frame with dt 1/60 and sees Space pressed
app:iterate(0.5, nil) -- holds: no frame, no timeShutdown first unwinds a parked host operation, then attempts world shutdown, stops the loader, and closes the debug listener. Later application cleanup still runs if world shutdown raises. Resident asset handles retain their existing release contract: game shutdown systems release the holds they own. Repeated application shutdown does nothing.
Module contents
Types
| Type | Kind | Description |
|---|---|---|
Application | record | A host-driven application and its world. |
Config | type | Settings copied by newApplication. |
HostEvent | type | A platform event already translated by the Rust host. |
Plugin | type | Installs a game's entities, resources, observers, and systems before startup. |
State | type | The lifecycle state visible to host diagnostics. |
Functions
| Function | Kind | Description |
|---|---|---|
newApplication | function | Builds an inert application without opening platform services. |
Values
| Value | Kind | Description |
|---|---|---|
DEFAULT_IDENTITY | variable | Read-only. |
Types#
Applicationrecord#
record Application is mcpbindings.Application ...A host-driven application and its world.
Members
| Name | Kind | Description |
|---|---|---|
world | field | |
assets | field | Read-only. |
debugServer | field | Read-only. |
input | field | |
window | field | |
quitRequested | field | |
suspended | field | |
elapsed | field | |
frame | field | |
paused | field | Read-only. |
state | field | |
plugin | field | |
onEvent | field | |
bindDebug | method | |
mcpPort | field | |
debug | field | |
debugMaxFrames | field | |
failure | field | |
shutdownDone | field | |
steps | field | |
stepDt | field | |
held | field | |
injected | field | |
poll | method | Polls optional debugging once per host turn, outside the world update scope. |
init | method | |
iterate | method | |
requestQuit | method | |
setSuspended | method | |
crashed | method | |
clearCrash | method | |
shutdown | method | |
getState | method | |
queueEvent | method | Queues a platform event for the next frame that runs. |
pause | method | Holds the simulation from the next iteration on. |
resume | method | Lets the simulation run on the host's measured delta again. |
step | method | Grants frames that run while paused, one per host iteration. |
pendingSteps | method | Returns the frames step granted that have not started. |
stepDelta | method | Returns the fixed delta granted frames run with. |
pendingEvents | method | Returns how many events wait for the next frame that runs. |
assetsfield#
assets: assets.AssetsRead-only. Holds this application's loader, which shuts down with the application.
debugServerfield#
debugServer: mcp.Server?Read-only. Holds the optional loopback debug listener after initialization.
quitRequestedfield#
quitRequested: booleansuspendedfield#
suspended: booleanelapsedfield#
elapsed: numberframefield#
frame: integerpausedfield#
paused: booleanRead-only. Reports whether pause holds the simulation. Change it through pause and resume.
onEventfield#
onEvent: (function(event: platformevents.Event, exclusive app: Application): nil)?pollmethod#
poll: function(exclusive self: Application, parked: boolean?): booleanPolls optional debugging once per host turn, outside the world update scope.
Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Application | the application to serve. |
parked | boolean? | whether its lifecycle coroutine remains suspended. |
Returns
| Type | Description |
|---|---|
boolean | whether one debug request was answered. |
initmethod#
init: function(exclusive self: Application): booleanArguments
| Name | Type | Description |
|---|---|---|
exclusive self | Application |
Returns
| Type | Description |
|---|---|
boolean |
iteratemethod#
iterate: function(exclusive self: Application, dt: number, events: {platformevents.Event}?): booleanArguments
| Name | Type | Description |
|---|---|---|
exclusive self | Application | |
dt | number | |
events | {platformevents.Event}? |
Returns
| Type | Description |
|---|---|
boolean |
requestQuitmethod#
requestQuit: function(exclusive self: Application): nilArguments
| Name | Type | Description |
|---|---|---|
exclusive self | Application |
Returns
| Type | Description |
|---|---|
nil |
setSuspendedmethod#
setSuspended: function(exclusive self: Application, suspended: boolean): nilArguments
| Name | Type | Description |
|---|---|---|
exclusive self | Application | |
suspended | boolean |
Returns
| Type | Description |
|---|---|
nil |
crashedmethod#
crashed: function(borrows self: Application): string?Arguments
| Name | Type | Description |
|---|---|---|
borrows self | Application |
Returns
| Type | Description |
|---|---|
string? |
clearCrashmethod#
clearCrash: function(exclusive self: Application): (boolean, string?)Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Application |
Returns
| Type | Description |
|---|---|
boolean | |
string? |
shutdownmethod#
shutdown: function(exclusive self: Application): booleanArguments
| Name | Type | Description |
|---|---|---|
exclusive self | Application |
Returns
| Type | Description |
|---|---|
boolean |
getStatemethod#
getState: function(borrows self: Application): StateArguments
| Name | Type | Description |
|---|---|---|
borrows self | Application |
Returns
| Type | Description |
|---|---|
State |
queueEventmethod#
queueEvent: function(exclusive self: Application, event: platformevents.Event): nilQueues a platform event for the next frame that runs.
The event reaches window state, input state, the world's observers and Config.onEvent after that frame's beginFrame, behind the host's own batch for the same turn, so per-frame edges such as keyPressed read true for exactly that frame. While paused the event waits for the next frame step or resume lets run.
Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Application | the application to deliver to |
event | platformevents.Event | the event, delivered as if the platform had sent it |
Returns
| Type | Description |
|---|---|
nil |
pausemethod#
pause: function(exclusive self: Application): nilHolds the simulation from the next iteration on.
A held iteration runs no system and folds no input, and leaves frame and elapsed unchanged. Host events wait for the next frame that runs, up to the latest 1024, except that a quit or close request still stops the application. The host keeps rendering the held world. Pausing again does nothing, and frames already granted by step are kept.
Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Application | the application to hold |
Returns
| Type | Description |
|---|---|
nil |
resumemethod#
resume: function(exclusive self: Application): nilLets the simulation run on the host's measured delta again.
Discards any frames step granted and has not run yet. Resuming a running application does nothing.
Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Application | the application to release |
Returns
| Type | Description |
|---|---|
nil |
stepmethod#
step: function(exclusive self: Application, frames: integer, dt: number): nilGrants frames that run while paused, one per host iteration.
Each granted frame passes dt to the world in place of the host's delta, so a script of steps reproduces the same simulated time on every run and every host. The delta applies to every granted frame not yet run, including ones an earlier call granted.
Arguments
| Name | Type | Description |
|---|---|---|
exclusive self | Application | the paused application |
frames | integer | how many more frames to run, at least one |
dt | number | the fixed delta of each granted frame, in seconds |
Returns
| Type | Description |
|---|---|
nil |
Raises
| Type | Condition |
|---|---|
string | when the application is not paused |
string | when |
pendingStepsmethod#
pendingSteps: function(borrows self: Application): integerReturns the frames step granted that have not started.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | Application | the application to inspect |
Returns
| Type | Description |
|---|---|
integer | the count, zero while running or once every granted frame ran |
stepDeltamethod#
stepDelta: function(borrows self: Application): numberReturns the fixed delta granted frames run with.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | Application | the application to inspect |
Returns
| Type | Description |
|---|---|
number | the delta in seconds the last |
pendingEventsmethod#
pendingEvents: function(borrows self: Application): integerReturns how many events wait for the next frame that runs.
Arguments
| Name | Type | Description |
|---|---|---|
borrows self | Application | the application to inspect |
Returns
| Type | Description |
|---|---|
integer | the count of queued and held events |
Configtype#
type Config = {
world: ecs.WorldConfig?,
window: platformwindow.Options?,
plugin: Plugin?,
onEvent: (function(event: platformevents.Event, exclusive app: Application): nil)?,
--- Caller-writable. Enables loopback debugging on this port; nil disables it and
--- zero chooses a free port.
mcpPort: integer?,
debug: boolean?,
debugMaxFrames: integer?
}Settings copied by newApplication.
HostEventtype#
type HostEvent = Quit
| WindowCloseRequested
| WindowResized
| WindowScaleChanged
| WindowFocusGained
| WindowFocusLost
| KeyDown
| KeyUp
| MouseMotion
| MouseDown
| MouseUp
| MouseWheel
| TextInput
| FingerDown
| FingerUp
| FingerMotion
| FingerCanceled
| GamepadAdded
| GamepadRemoved
| GamepadButtonDown
| GamepadButtonUp
| GamepadAxisA platform event already translated by the Rust host.
Plugintype#
type Plugin = function(exclusive app: Application): nilInstalls a game's entities, resources, observers, and systems before startup.
Statetype#
type State = "created" | "running" | "stopped"The lifecycle state visible to host diagnostics.
Functions#
newApplicationfunction#
function newApplication(config: Config?): ApplicationBuilds an inert application without opening platform services.
Arguments
| Name | Type | Description |
|---|---|---|
config | Config? | the copied world and lifecycle settings |
Returns
| Type | Description |
|---|---|
Application | an application waiting for |
Raises
| Type | Condition |
|---|---|
string | when the debug frame limit or MCP port is invalid |
Values#
DEFAULT_IDENTITYvariable#
const DEFAULT_IDENTITYRead-only. Names the organization and the application newApplication selects when a game named neither.