Skip to content

API Capabilities

Piggo41 edited this page Oct 16, 2026 · 8 revisions

Capabilities

Lua API / Capabilities

Capabilities define reusable typed behavior contracts for BetaMoon tile entities. A contract declares persistent per-tile state, immutable attachment configuration, and operations. Different tile types can implement the same operations differently, so a network or neighboring machine can use them through one interface.

betamoon.capabilities:add(definition)

Registers during modInit() and returns a capability reference with key. Capability contracts and attachments are structural registrations; changes require a restart.

local energy = betamoon.capabilities:add {
    key = "mymod:capability/energy",
    state = { stored = { type = "integer", default = 0 } },
    config = { capacity = { type = "integer" } },
    operations = {
        status = {
            mode = "query",
            response = { stored = { type = "integer" } }
        },
        receive = {
            mode = "action",
            request = { amount = { type = "integer" } },
            response = { accepted = { type = "integer" } }
        }
    }
}

Field definitions

Field Meaning
key Required namespace:capability/name.
state Optional structured fields, saved separately on every attached tile.
config Optional structured fields supplied by each attachment. Fields without declared defaults are required.
operations Optional map of operation names to typed definitions.

An operation accepts optional request and response field maps. mode = "query" is the default and prevents gameplay mutation. mode = "action" permits mutation when its caller is also in a mutable callback.

Capability lookup

Method Result
betamoon.capabilities:get(key) Capability reference, or nil when absent.
betamoon.capabilities:getRequired(key) Capability reference; raises an error when absent.
betamoon.capabilities:find(query) Result list filtered by optional key and owner.
betamoon.capabilities:first(query) First match, or nil.
betamoon.capabilities:one(query) Sole match, nil for none, or an error for multiple matches.

Capability reference

A live reference exposes key, owner, and exists.

capability:override(definition)

Overrides implementations of already declared operations through changes.operations. Schemas and attachment configuration remain unchanged. An override receives the normal (ctx, request) arguments and can call ctx:base() to invoke the next lower implementation once.

local patch = energy:override {
    changes = { operations = {
        status = function(ctx, request)
            local result = ctx:base()
            result.stored = math.min(result.stored, 1000)
            return result
        end
    } }
}

Tile attachment

Add an array entry to tileEntities:add { capabilities = ... }.

capabilities = {{
    capability = energy,
    config = { capacity = 4000 },
    ports = { north = "energy", south = "energy", east = false },
    operations = {
        status = function(ctx)
            return { stored = ctx.capability.data:get("stored") }
        end,
        receive = function(ctx, request)
            local stored = ctx.capability.data:get("stored")
            local accepted = math.min(request.amount, ctx.capability.config.capacity - stored)
            ctx.capability.data:set("stored", stored + accepted)
            return { accepted = accepted }
        end
    }
}}

Every declared operation needs an implementation. ports accepts the six absolute faces and front, back, left, right on horizontally facing blocks. Values may be a port name or false to close that face. Omitted ports use the default port.

Operation callbacks receive ctx.world, integer x/y/z, a detached position, ctx.entity.data/capabilities, and:

ctx.capability field Meaning
key Exact capability key.
data Persistent local capability state with get, set, and snapshot. Query operations cannot mutate it.
config Detached immutable attachment configuration.
face Requested physical face for external access, when one was supplied.
port Attachment port name, false for a closed face, or absent when no face was selected.

Requests and responses must exactly match their schemas. A callback error, illegal query mutation, or invalid response rolls state back and disables that operation for the instance to prevent repeated corruption.

Local access

Tile callbacks expose ctx.entity.capabilities:

local value = ctx.entity.capabilities:get(energy)
local required = ctx.entity.capabilities:getRequired("mymod:capability/energy")
required.data:set("stored", 20)
local response = required:call("status", {})

Local proxies expose:

Field/method Meaning
key Exact capability key.
position Detached tile position.
face Selected physical face, when applicable.
port Resolved port name, false for a closed face, or absent without a selected face.
config Detached attachment configuration.
data Persistent local state access; only local proxies expose it.
call(operation, request) Invoke one declared operation and return its validated response table.

data:get, data:set, and data:snapshot operate on persistent capability state.

External access

ctx.world:getCapability(position, capability, face) or getCapability(x, y, z, capability, face) returns an operation-only proxy, or nil if the chunk/tile/capability/face is unavailable. It never loads a chunk.

local target = ctx.world:getCapability(ctx.x + 1, ctx.y, ctx.z, energy, "west")
if target then
    local result = target:call("receive", { amount = 10 })
end

External callers cannot read or write capability state directly. Proxies and callback contexts expire when the callback returns, and stale proxies reject calls after a tile is removed or replaced. Calls are bounded by nesting depth and a per-world tick budget.

At most 16 capability calls may be nested. Across all scripts, callbacks, and logical networks in one world, at most 4,096 capability calls may begin during one world tick. The shared counter resets when that world's time advances to the next tick. Exceeding either bound raises an error before the requested operation callback begins; it does not by itself disable that operation. Avoid all-to-all network loops, cache repeated query results inside one callback, and limit work per tick when processing large node collections.

Example: Energy network.

Clone this wiki locally