DEMIENGINEDOCUMENTATION Everything is free
Lua API reference · Create

Return shapes, units, error handling, and require style.

API conventions

Every engine service is a plain Lua table you import with require. This page explains how those tables are shaped, how results come back, how failures are reported, and which units the numbers use — read it once before diving into the individual APIs.

What a service module is

A service module is a small table of functions grouped by job (entities, transforms, audio, and so on). You import it into a local and call through that local. There are no engine globals: if you forget the require, the name is simply nil and the call fails loudly instead of silently doing something else.

local Entity = require("demi.entity")
local Transform3D = require("demi.transform3d")

local id = Entity.find("skyforge_player")
local x, y, z = Transform3D.get_position(id)

require("demi.entity") returns the entity service table. Entity.find("skyforge_player") looks up an entity by ID or name and returns its stable ID string, or nil when missing. Transform3D.get_position(id) returns three local-position scalars, or three nil values when unavailable. The local names (Entity, Transform3D) are yours to choose; the "demi.…" strings are the contract.

Vector annotations

When an API documents a vector type it uses the names Vec2 and Vec3. Those are annotations for readers and editor tooling, not Lua classes: a Vec3 is just three numbers. How you hold them depends on the call:

  • Transform getters and setters pass coordinates as separate scalar arguments and returns: local x, y, z = Transform3D.get_position(id).
  • Vectors embedded inside tables (spawn options, hit results, color-style fields) are fixed-length numeric arrays: { x, y } or { x, y, z }. Lua arrays start at index 1, so read them as v[1], v[2], v[3].
local crate = Entity.spawn("skyforge_crate", {
    position = { 4, 2, 0 },   -- Vec3 as a 3-number array
    velocity = { 1, 0, 0 },
})

local px, py, pz = Transform3D.get_position(crate)  -- three scalars

Entity.spawn(id, options) takes the new stable ID and an options table. Its position and velocity shorthands are dense numeric arrays of length two or three; their dimensions must agree when both are supplied. It returns true when creation was queued, or false plus an error string on failure. The entity is created at the next creation step, not synchronously. The transform getter returns separate scalars.

How results come back

ShapeWhere it is usedHow to read it
local x, y, z = … Transform position, rotation, scale One scalar per coordinate. If the entity has no transform, each coordinate is nil — check one of them before doing arithmetic.
{ x, y } / { x, y, z } Dense coordinate inputs, such as spawn position/velocity and authored sizes Numeric arrays indexed from 1. Follow the particular function's input contract.
{x = …, y = …, z = …} Named vectors returned inside ray, contact and other result tables Read fields such as hit.point.x. Do not assume these outputs use the dense-array shape required by some inputs.
value, nil or nil, err Data.load, Data.parse_yaml Check the first result. Success is value, nil. Failure is nil plus an error table (shape below).
nil Missing entity, unknown HUD node, missed raycast A single nil means “nothing found”. Guard with if hit then … end before reading fields.
{} (empty table) Bulk queries, children lists, path results from NavigationGrid An empty array means “the query ran and found nothing”, not an error. Iterate with ipairs.

Two pathfinding calls illustrate the split between nil and empty tables. Grid.path returns a path table on success and a single nil (plus a diagnostic you can log) when no path exists. nav:path always returns a table of waypoints — empty when nothing was found — and a diagnostic. Treat nil as “no result object”, and an empty table as “a result with zero items”.

local Grid = require("demi.grid")
local nav = require("demi.navigation").default_grid()

local gridPath = Grid.path(0, 0, 5, 5)
-- gridPath: array of {x, y} waypoints, or nil when unreachable (a diagnostic code is returned)

local navPath = nav:path(0, 0, 5, 5)
-- navPath: always a table of waypoints (possibly empty) plus a diagnostic

Grid.path(start_x, start_y, goal_x, goal_y) takes four tile coordinates and returns either an array of waypoints or nil plus a diagnostic code. nav:path(start_x, start_y, goal_x, goal_y, diagonal?) returns a (possibly empty) waypoint array and a diagnostic string you can pass to Debug.log.

Error shapes

Data-loading and YAML-parsing failures use nil, DataError. This is not a universal error convention: many entity/network operations return booleans, while HTTP request creation returns a handle or nil, error_string. Poll an HTTP handle's response for transport/HTTP errors. Use each service's documented return shape. A data error looks like:

local Data = require("demi.data")

local value, err = Data.load("asset://data/skyforge_enemies")
if err then
    -- err.code    : short machine string, e.g. "not_found" or "schema_mismatch"
    -- err.message : human-readable sentence
    -- err.path    : JSON pointer into the document, or the URI
end

Data.load(uri) returns value, nil on success. On failure it returns nil and an error table with three string fields: code (stable, safe to branch on), message (safe to show or log), and path (where in the document the problem is, as a JSON pointer such as /stats/0/hp, or the requested URI when the whole document is missing).

JSON null

JSON has a real null value that is different from “field missing”. Lua only has nil, which also means “table key absent”, so DemiEngine exposes a sentinel instead:

local Data = require("demi.data")

local value = Data.load("asset://data/skyforge_enemies")
print(Data.kind(value.nickname))   -- "null" when the field is JSON null
print(Data.is_null(value.nickname)) -- true

local written = { nickname = Data.null }

Data.null is the single sentinel value for JSON null — a field of the Data table, not a function. Data.kind(value) returns "array", "object", or "null", and nil for scalars (numbers, strings, booleans) or missing values. Data.is_null(value) returns true only for the sentinel. Assign Data.null when you want to write a real null back out.

Units

  • Transform2D rotation and Transform3D Euler rotation: radians.
  • Angular velocity on rigidbodies: radians per second.
  • Camera field of view: degrees.
  • All Time values (delta_time, fixed_time, time): seconds.
  • HUD positions and sizes: canvas units from the HUD document canvas_size, not raw screen pixels.

Module map

Group services by the job they do. Names with dots are single modules — require("demi.physics.rigidbody3d") is one require for one table.

ModuleUse it for
demi.applicationWindow mode, FPS cap, mouse capture and visibility, safe area, DPI, UI scale, orientation, keyboard, clipboard, focus/suspend state, user and cache paths, permissions
demi.entityFinding, creating, spawning, cloning, destroying entities; components; queries; parenting — see Entity API
demi.scene / demi.prefabScene prepare/activate and prefab instances — see Scene & prefab API
demi.inputAction queries, contexts, rebinding, touch, gamepads
demi.transform2d / demi.transform3dPosition, rotation, scale; 3D directions and look_at — see Transform API
demi.sprite2d / demi.tilemap2dSprite draw state and animation; tile reads and overrides
demi.animationState machine parameters, clips, layers, root motion — see State machines
demi.camera3dScreen rays and world/screen conversion
demi.hudUI nodes, text, variables, tweens, virtualization — see HUD API
demi.eventsSubscribing and emitting events — see Events & timers
demi.data / demi.saveGame data documents and save slots — see Data & save API
demi.assetsAsync load/unload of asset:// resources — see Assets, time & timers
demi.time / demi.timerFrame time, pause, time scale; delays and repeats
demi.debug / demi.profileOn-screen debug lines; scoped performance timers — see Math & debug
demi.text / demi.regexGrapheme-safe text helpers; ECMAScript pattern matching
demi.grid / demi.navigationIsometric placement; 2D pathfinding
demi.video / demi.cutsceneVideo playback and cutscene sequencing
demi.testE2E helpers — see E2E tests
demi.networkLow-level transport — see Sessions & contracts
Namespaced moduleUse it for
demi.physics.rigidbody2d / demi.physics.rigidbody3dVelocities, forces, impulses, sleep, kinematic movement
demi.physics.query2d / demi.physics.query3dRaycasts, shape casts, overlap checks
demi.physics.character_controller3dCharacter velocity, jump, grounded state
demi.physics.destruction3dImpacts, part damage, checkpoints
demi.network.sessionHost-authoritative multiplayer sessions
demi.network.httpAsync HTTP/HTTPS calls — see HTTP & TLS
demi.network.tls.client / demi.network.tls.serverVerified TLS sockets
demi.network.cryptoRandom tokens, password hashing, constant-time compare
demi.audio / demi.audio.sourceBuses, snapshots, one-shots; entity-attached playback — see Audio mixing
demi.math.vector2 / demi.math.vector3 / demi.math.scalar / demi.math.randomVector math, easing, seeded randomness
demi.mesh.procedural / demi.mesh.deformationProcedural meshes; deformation
demi.voxel.worldChunked voxel sections and meshes

Next steps

  • Script lifecycle — how a Lua module becomes a running instance.
  • Entity API — finding and spawning the objects a Skyforge script drives.
  • Data schemas — declaring the shape of loaded game data.