# tecs.Application The application that an entry chunk returns. The host owns the loop and calls the application to initialize, process events, iterate, and shut down. Game code registers systems and observers instead of calling a blocking `run` function: ```teal return tecs.newApplication({ window = { title = "Game", width = 1280, height = 720, }, plugin = function(world: tecs.World, app: tecs.Application) local transforms = world:newQuery({ include = {tecs.Transform2D}, }) world:addSystem({ name = "game.Move", phase = tecs.ecs.phases.Update, run = function(dt: number) for archetype, length in transforms:iter() do local column = archetype:getMut( tecs.Transform2D ) for row = 1, length do column[row].x = column[row].x + 120 * dt end end end, }) world:spawn(tecs.Transform2D(100, 100)) end, }) ``` ## Lifecycle The entry plugin receives the world and application after engine subsystem installation and before startup phases. It registers the game. The world then owns the scheduled lifecycle: | Stage | Game API | | --- | --- | | Startup | `PreStartup`, `Startup`, and `PostStartup` | | Event | `world:observe(0, tecs.platform.events.on., handler)` | | Frame | The phases driven by `world:update(dt)` | | Shutdown | `PreShutdown`, `Shutdown`, and `PostShutdown` | Every iteration runs the same eight steps in the same order, and the order is the contract: a system reads what the steps above it settled during this frame rather than one frame later. 1. The application starts the input frame and drains every platform event SDL delivered since the previous iteration. A quit request ends the iteration here. 2. `tecs.runtime.poll` settles process-wide asynchronous work: finished asset loads, child processes, dialogs, generic I/O, HTTP clients, and file changes. 3. The debug server answers, `world:update(dt)` runs the phases, and the renderer records and submits one frame. 4. The audio mixer updates, which reaps the voices it has finished with. Steps 2 and 4 run whatever else is true of the process. A decode that finished, a child that exited, a transfer that completed and a file that changed have all already happened, and a voice the mixer is done with has to be reaped either way, so a suspended run and a crashed one keep settling them. Step 7 is the one the guards hold back. A suspended application skips all of it, and a crashed one skips the world update and the rendering while still answering the debug server, so an agent that was watching gets the traceback. Event observers run inside step 1, before the world update and outside every phase. They see input state after the application folds the event in. Store event state in a component or a resource when a scheduled system has to react to it. ## Checkpoints and crashes Mobile platforms may suspend a process before another frame runs. Prepare a snapshot during an ordinary frame and stage its bytes before that deadline: ```teal local save = world:saveSnapshot().buffer app:stageCheckpoint(tostring(save)) ``` The application writes the latest staged bytes when the platform backgrounds or terminates it. The next entry plugin can call `readCheckpoint` and load the result. The application catches errors from game systems, observers, plugins, and startup phases. It records the first traceback while the host continues to serve platform events and the debug connection. The guard restores engine-owned scopes, not partially changed game state. Use `clearCrash` only as a development reload aid. Methods whose names begin with an underscore belong to the host contract. ## Module contents ### Constructors | Constructor | Description | | --- | --- | | [`newApplication`](/modules/Application/#tecs.Application.newApplication) | Builds an application. | ### Types | Type | Kind | Description | | --- | --- | --- | | [`Config`](/modules/Application/#tecs.Application.Config) | record | Caller-writable. Sets the window to open, as Options describes it. | | [`EntryPlugin`](/modules/Application/#tecs.Application.EntryPlugin) | type | Configures the world owned by an application. | ### Functions | Function | Kind | Description | | --- | --- | --- | | [`checkpointPath`](/modules/Application/#tecs.Application.checkpointPath) | Instance | Returns the checkpoint path, or nil when the config names none. | | [`clearCrash`](/modules/Application/#tecs.Application.clearCrash) | Instance | Clears a recoverable gameplay crash so development can render another frame. | | [`crashed`](/modules/Application/#tecs.Application.crashed) | Instance | Returns the gameplay traceback, or nil while the game is healthy. | | [`readCheckpoint`](/modules/Application/#tecs.Application.readCheckpoint) | Instance | Reads the bytes the last run left, or returns nil when there are none. | | [`stageCheckpoint`](/modules/Application/#tecs.Application.stageCheckpoint) | Instance | Hands the engine the bytes to write when the platform backgrounds us. | ### Values | Value | Type | Description | | --- | --- | --- | | [`audio`](/modules/Application/#tecs.Application.audio) | [`Audio`](/modules/audio/#tecs.audio.Audio) | Read-only. Exposes the audio mixer. | | [`device`](/modules/Application/#tecs.Application.device) | `Device` | Read-only. Exposes the GPU device. | | [`elapsed`](/modules/Application/#tecs.Application.elapsed) | `number` | Engine-owned. Reports seconds of simulated time. | | [`frame`](/modules/Application/#tecs.Application.frame) | `integer` | Engine-owned. Reports completed application iterations. | | [`input`](/modules/Application/#tecs.Application.input) | [`Input`](/modules/input/#tecs.input.Input) | Read-only. Exposes current input state. | | [`mcp`](/modules/Application/#tecs.Application.mcp) | [`Server`](/modules/io/mcp/#tecs.io.mcp.Server) | Read-only. Exposes the debug server when configuration requested one. | | [`quitRequested`](/modules/Application/#tecs.Application.quitRequested) | `boolean` | Caller-writable. Set to true to leave the loop at the end of the current iteration. | | [`renderer`](/modules/Application/#tecs.Application.renderer) | [`Renderer`](/modules/gfx/#tecs.gfx.Renderer) | Read-only. Exposes the renderer that extracts this world. | | [`suspended`](/modules/Application/#tecs.Application.suspended) | `boolean` | Engine-owned. Reports true while the platform has the application in the background. | | [`window`](/modules/Application/#tecs.Application.window) | [`Window`](/modules/platform/window/#tecs.platform.window.Window) | Read-only. Exposes the application window. | | [`world`](/modules/Application/#tecs.Application.world) | [`World`](/modules/ecs/#tecs.World) | Read-only. Exposes the world driven by this application. | ## Constructors ### tecs.Application.newApplication Static Builds an application. Return the result from the entry chunk. Reached as `tecs.newApplication`, at the root rather than under a module, because an application is not a subsystem: it owns the window, the device, the input, the renderer and the audio. Tecs hands it to the entry plugin beside the world. ```teal function tecs.Application.newApplication( config: Application.Config ): Application ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `config` | [`Application.Config`](/modules/Application/#tecs.Application.Config) | Tecs reads this table here and ignores later changes. This call opens nothing. | #### Returns | Type | Description | | --- | --- | | [`Application`](/modules/Application/) | The application, at rest until the host's first callback. | ## Types ### tecs.Application.Config record ```teal record tecs.Application.Config window: Window.Options debug: boolean framesInFlight: integer presentMode: string ambientLight: {number} audio: Audio.Config logFile: string logLevel: integer checkpoint: string mcpPort: integer watch: watcher.Config capacity: integer sprites: boolean meshes: Renderer.MeshOptions timestep: number fixedMaxSteps: integer fixedOverload: ecs.FixedOverload maxEntities: integer reserveRuns: boolean partialRewrites: boolean packImages: boolean shadows: Deferred.ShadowOptions debugMaxFrames: integer quitOnEscape: boolean plugin: EntryPlugin end ``` #### tecs.Application.Config.window field Caller-writable. Sets the window to open, as [`Options`](/modules/platform/window/#tecs.platform.window.Options) describes it. The application passes the table through whole. ```teal tecs.Application.Config.window: Window.Options ``` #### tecs.Application.Config.debug field Caller-writable. Marks this run as a development run. Tecs creates the device with validation layers enabled and writes the log file below by default. This setting also joins the three settings `clearCrash` looks at, beside `mcpPort` and `watch`, to decide whether resuming after a gameplay crash is something this build does at all. ```teal tecs.Application.Config.debug: boolean ``` #### tecs.Application.Config.framesInFlight field Caller-writable. Sets how many frames the CPU may run ahead of the GPU. The device defaults to two. ```teal tecs.Application.Config.framesInFlight: integer ``` #### tecs.Application.Config.presentMode field Caller-writable. Selects `"vsync"`, `"immediate"` or `"mailbox"` presentation. The device defaults to `"vsync"`. ```teal tecs.Application.Config.presentMode: string ``` #### tecs.Application.Config.ambientLight field Caller-writable. Sets the light every surface receives before any light entity contributes, as red, green and blue. Defaults to white. White rather than a dim gray, because a scene that has placed no lights is the first thing anyone builds and it should look like what it is: sprites at their own color, with lights adding on top of them. A game doing its own lighting turns this down to the level it wants the unlit parts of the scene to sit at. ```teal tecs.Application.Config.ambientLight: {number} ``` #### tecs.Application.Config.audio field Caller-writable. Configures sound output. Omitted takes the defaults, which open the platform's default device. ```teal tecs.Application.Config.audio: Audio.Config ``` #### tecs.Application.Config.logFile field Caller-writable. Sets a JSON Lines log file under the writable root. Omitted creates one when `mcpPort` or `debug` is set and otherwise keeps logging at the platform destination. ```teal tecs.Application.Config.logFile: string ``` #### tecs.Application.Config.logLevel field Caller-writable. Sets the lowest priority that reaches the log, as one of `log.TRACE`, `log.VERBOSE`, `log.DEBUG`, `log.INFO`, `log.WARN`, `log.ERROR` or `log.CRITICAL`. Omitted leaves the platform default. The level applies to every category and does not change whether `logFile` stores the accepted messages. ```teal tecs.Application.Config.logLevel: integer ``` #### tecs.Application.Config.checkpoint field Caller-writable. Sets the file name under the writable root that receives a checkpoint when the platform backgrounds or terminates the application. Omitted means no checkpoint, and `stageCheckpoint` says so. See `Application:stageCheckpoint` for what a game has to do, which is the part that matters. ```teal tecs.Application.Config.checkpoint: string ``` #### tecs.Application.Config.mcpPort field Caller-writable. Sets the MCP server port. After the server starts, the application appends `[MCP :port]` to the window title. Omitted means no server, since a game should not open a socket nobody asked for. ```teal tecs.Application.Config.mcpPort: integer ``` #### tecs.Application.Config.watch field Caller-writable. Watches loaded content files and reloads them when they change. Omitted means no watcher, on the same footing as the server above: a poll of the filesystem is not something to start because a build happened to be able to. A release refuses it. ```teal tecs.Application.Config.watch: watcher.Config ``` #### tecs.Application.Config.capacity field Caller-writable. Sets the renderer buffer capacity in instances. Defaults to 65536. An instance is a row the GPU draws: every renderable entity is one, and so is every glyph of every text, which an instance producer writes into a run of its own rather than spawning an entity per glyph. This is a ceiling rather than a hint. Rows past it are dropped rather than growing a buffer mid-frame, and `renderer.sprites.dropped` counts them. Sized independently of `maxEntities` beside it, because the two count different things: a world full of entities that carry no [`Renderable2D`](/modules/gfx/#tecs.gfx.Renderable2D) needs no instances at all, and one text entity needs one instance per character. ```teal tecs.Application.Config.capacity: integer ``` #### tecs.Application.Config.sprites field Caller-writable. Enables the 2D sprite domain unless set to false. Defaults to true. Disabling it creates no sprite image array, packet, buffers, or world queries, and makes `renderer.sprites` nil. ```teal tecs.Application.Config.sprites: boolean ``` #### tecs.Application.Config.meshes field Caller-writable. Enables and configures the 3D mesh domain. Nil, the default, loads no mesh renderer and allocates no mesh buffers. ```teal tecs.Application.Config.meshes: Renderer.MeshOptions ``` #### tecs.Application.Config.timestep field Caller-writable. Sets the seconds one fixed step covers. Must be greater than zero. Defaults to 1/60. ```teal tecs.Application.Config.timestep: number ``` #### tecs.Application.Config.fixedMaxSteps field Caller-writable. Sets the most fixed steps one update runs before the overload policy applies. Must be a positive integer. Defaults to 10. ```teal tecs.Application.Config.fixedMaxSteps: integer ``` #### tecs.Application.Config.fixedOverload field Caller-writable. Selects what becomes of catch-up that did not fit. Defaults to "drop", which bounds a frame's work and reports the loss through `world:getStats`. "accumulate" keeps every second instead. ```teal tecs.Application.Config.fixedOverload: ecs.FixedOverload ``` #### tecs.Application.Config.maxEntities field Caller-writable. Sets the world's entity slot capacity. Defaults to 2^20, and 2^22 - 1 is the ceiling the packed id format allows. Concurrent slots rather than lifetime spawns: a slot is given back when the world despawns the entity, then the next entity reuses it. The world preallocates this many slots, so it is a memory decision as much as a limit, and a game that knows its population sets it rather than paying for a million slots it will not use. ```teal tecs.Application.Config.maxEntities: integer ``` #### tecs.Application.Config.reserveRuns field Caller-writable. Gives each archetype a run with room to grow rather than packing the runs end to end. Defaults to false, which packs. Packed, a run cannot grow without shifting every run after it, so a spawn anywhere rewrites every instance in the scene. Reserved, it grows into its own slack and the archetypes around it are left alone. The cull also dispatches over the slack as well as over the rows, and that reserving needs headroom: a `capacity` sized to exactly the population leaves nothing to reserve out of and the setting does nothing. Worth it for a scene that spawns into one archetype while most of the world sits in others, which is what a game with a level and projectiles in it looks like. Worth nothing for a scene held in a single archetype, where there is no other run to leave alone. Not named for debugging, unlike `debugMaxFrames` below, because it is not a debugging setting: it is a tuning one, measured at 15.4x on a mixed-archetype spawner scene. A name with `debug` in it would warn off exactly the shipped games that should turn it on. ```teal tecs.Application.Config.reserveRuns: boolean ``` #### tecs.Application.Config.partialRewrites field Caller-writable. Rewrites the rows a spawn or a despawn wrote rather than the archetype's whole run. Defaults to false, which rewrites the run. A row moving into an archetype has every column newly written at that row, so a structural change dirties every column of the archetype it lands on and the run is resynced whole. The rest of the run did not change, and the ECS records which rows did, so this makes one spawn cost the rows it wrote rather than the archetype's population. It composes with `reserveRuns` above rather than replacing it: reserving keeps a spawn from moving the archetypes around it, and this keeps it from rewriting the one it landed in. Two things are deliberately left out of it. A value write through `archetype:getMut` names a column and not a row, so it still rewrites the run; that includes a `batchSpawn` fill callback, which declares intent on the whole column it writes new rows into. And an archetype carrying [`PreviousTransform2D`](/modules/gfx/#tecs.gfx.PreviousTransform2D) is interpolated, so its drawn positions move on every frame that falls at a new point in the fixed step whatever the world did. ```teal tecs.Application.Config.partialRewrites: boolean ``` #### tecs.Application.Config.packImages field Caller-writable. Fits many images into each layer of the renderer's image array rather than one. Defaults to false, where the ceiling on distinct images is the array's layer count and a small image costs a whole cell. On, the ceiling is the array's area instead. ```teal tecs.Application.Config.packImages: boolean ``` #### tecs.Application.Config.shadows field Caller-writable. Enables entity shadows and configures their cost. Nil, the default, means an [`Occluder2D`](/modules/gfx/#tecs.gfx.Occluder2D) or a [`DropShadow2D`](/modules/gfx/#tecs.gfx.DropShadow2D) on an entity draws the entity and casts nothing, because the targets that would hold a shadow are never built. An empty table turns them on with every default; see `Deferred.ShadowOptions` for the seven numbers. ```teal tecs.Application.Config.shadows: Deferred.ShadowOptions ``` #### tecs.Application.Config.debugMaxFrames field Caller-writable. Stops after this many iterations. This lets an automated run drive a real window to completion without a human closing it. ```teal tecs.Application.Config.debugMaxFrames: integer ``` #### tecs.Application.Config.quitOnEscape field Caller-writable. Requests a clean exit when the platform delivers an Escape key press. Defaults to false. The check is independent of gameplay input layers and remains available after a gameplay crash. ```teal tecs.Application.Config.quitOnEscape: boolean ``` #### tecs.Application.Config.plugin field Caller-writable. Sets the game plugin that receives the world and this application. Called at the end of initialization, after every engine subsystem is installed and before the startup phases run, so it can register a `Startup` system and have it run this initialization rather than the next one. One entry point rather than a list, because composing plugins is something the world already does: `world:addPlugin` takes an `tecs.Plugin` and is how the engine installs its own, so a game with several calls it from in here and needs no second mechanism. The world comes first because every plugin the world takes is `function(world)`, so this reads as that shape with one more thing and code moves between here and a delegated plugin without a silent argument swap. ```teal tecs.Application.Config.plugin: EntryPlugin ``` ### tecs.Application.EntryPlugin type Configures the world owned by an application. ```teal type tecs.Application.EntryPlugin = function(types.World, Application) ``` ## Functions ### tecs.Application:checkpointPath Instance Returns the checkpoint path, or nil when the config names none. ```teal function tecs.Application.checkpointPath(self): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Application` | | #### Returns | Type | Description | | --- | --- | | `string` | | ### tecs.Application:clearCrash Instance Clears a recoverable gameplay crash so development can render another frame. Reload the world before trusting it. The method works only when `debug`, `mcpPort` or `watch` marks the run for development. It refuses to continue after incomplete graphics recovery. ```teal function tecs.Application.clearCrash(self): boolean, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Application` | | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns whether the loop resumed. | | `string` | Returns the reason when the loop could not resume. | ### tecs.Application:crashed Instance Returns the gameplay traceback, or nil while the game is healthy. ```teal function tecs.Application.crashed(self): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Application` | | #### Returns | Type | Description | | --- | --- | | `string` | | ### tecs.Application:readCheckpoint Instance Reads the bytes the last run left, or returns nil when there are none. Read it while building the world, which is what the plugin and the startup phases are for. Nil covers every reason there is nothing to resume from: a first run, a game that never staged anything, a file the player deleted. None of those is an error, so none of them raises. ```teal function tecs.Application.readCheckpoint(self): string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Application` | | #### Returns | Type | Description | | --- | --- | | `string` | | ### tecs.Application:stageCheckpoint Instance Hands the engine the bytes to write when the platform backgrounds us. Call this from an ordinary system, on an ordinary frame, whenever the state worth keeping has changed. Nothing is written here; the bytes are held, and `_willEnterBackground` writes them at the one moment the platform gives. What this takes is bytes rather than a function that produces them, and that is the whole design rather than an inconvenience. iOS allows roughly five seconds from the backgrounding callback returning and Android rather less, and at this project's scale a world is not serializable inside any of it: the platform could kill a callback part way through four million entities through and leave nothing behind. A function would let a game postpone the serializing into exactly that callback while looking like it had prepared something. A string cannot: by the time it exists, the expensive half has already happened on a frame that could afford it. The host times the hook and says so past 250 ms, which is the check on the rest. Staging again replaces the previous bytes; there is one checkpoint, not a queue. Staging and then backgrounding twice writes once, because a write no changes only gives the platform another chance to interrupt the write for nothing. The counterpart is `readCheckpoint`, which is how the next run gets it back. Raises when the config names no `checkpoint`, because silently holding bytes that will never be written is the one failure a game would not notice. ```teal function tecs.Application.stageCheckpoint(self, bytes: string) ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Application` | | | `bytes` | `string` | | #### Returns None. ## Values ### tecs.Application.audio variable Read-only. Exposes the audio mixer. ```teal tecs.Application.audio: Audio ``` ### tecs.Application.device variable Read-only. Exposes the GPU device. ```teal tecs.Application.device: Device ``` ### tecs.Application.elapsed variable Engine-owned. Reports seconds of simulated time. The frame delta advances it so a replay reproduces the value exactly. ```teal tecs.Application.elapsed: number ``` ### tecs.Application.frame variable Engine-owned. Reports completed application iterations. ```teal tecs.Application.frame: integer ``` ### tecs.Application.input variable Read-only. Exposes current input state. ```teal tecs.Application.input: Input ``` ### tecs.Application.mcp variable Read-only. Exposes the debug server when configuration requested one. ```teal tecs.Application.mcp: mcp.Server ``` ### tecs.Application.quitRequested variable Caller-writable. Set to true to leave the loop at the end of the current iteration. ```teal tecs.Application.quitRequested: boolean ``` ### tecs.Application.renderer variable Read-only. Exposes the renderer that extracts this world. ```teal tecs.Application.renderer: Renderer ``` ### tecs.Application.suspended variable Engine-owned. Reports true while the platform has the application in the background. The loop still runs, but simulation and rendering do not. ```teal tecs.Application.suspended: boolean ``` ### tecs.Application.window variable Read-only. Exposes the application window. ```teal tecs.Application.window: Window ``` ### tecs.Application.world variable Read-only. Exposes the world driven by this application. ```teal tecs.Application.world: types.World ```