Repository navigation
API Multiblocks
Lua API / Multiblocks
Declare block patterns with a controller, or discover controllerless structures from their ordinary member blocks.
Managed controllerless structures can activate automatically or when a configured item is used. Definitions without a controller or discovery remain available for pure checks.
Formation preserves member blocks, metadata, tile data, inventories and appearances.
| Function | Behavior |
|---|---|
betamoon.multiblocks:add(definition) |
Declare during initialization/reload; returns a definition reference. |
betamoon.multiblocks:get(key) |
Reference, or nil if absent. |
betamoon.multiblocks:getRequired(key) |
Reference; raises an error if absent. |
definition:getKey() |
Canonical namespaced key, with content type multiblock. |
References expire when their definition is replaced or its script disappears. A failed declaration publication
retains the previous working definition. Reacquire references after a successful reload.
| Field | Meaning |
|---|---|
key |
Required namespaced content key. |
discovery |
Optional controllerless discovery; excludes controller. |
activation |
Optional activation policy; requires discovery, defaults to automatic. |
canActivate, onActivated, onDeactivated
|
Optional activation callbacks. |
controller |
Optional resolved block reference. Each block can bind one automatic definition. |
facing |
Fixed north, east, south or west. Required for controllers without declared horizontal facing state. |
layers |
Dense list of layers, each a dense list of equal-width row strings. |
cells |
Alternative sparse list of {x, y, z, symbol} offsets. Excludes layers and repeatLayers. |
palette |
Symbol-to-rule table. Ordinary rules use resolved block references. |
mirror |
Default false. Allow mirrored checks; automatic matching tries unmirrored first at each repeat size. |
exclusive |
Default false. Reject sharing required non-air cells with another formed structure. |
constraints |
Optional count or allSame constraints, at most 64. |
captures |
Named selections of symbols for getMatches(name). Names cannot shadow symbols. |
repeatLayers |
Optional bounded repeated layer section; see below. |
data |
Optional structured data schema, without entity references. |
onFormed, onInvalidated, onSuspended, onResumed
|
Optional functions taking a multiblock context. |
Layers run bottom to top. Rows run front to back; characters run left to right. Authored front is north,
right is east, and back is south. Exactly one @ anchors the pattern at the check/controller coordinates.
The anchor need not be centered. With a controller, @ checks that block automatically. Standalone
definitions require palette["@"].
. requires actual air (block ID zero). ? ignores the position completely, including chunk availability
and membership. Other symbols must be single printable ASCII characters with palette rules. Spaces,
undefined symbols, ragged rows and duplicate anchors are declaration errors, identified by layer/row/column.
Horizontal rotation transforms local offsets clockwise: north (x,y,z), east (-z,y,x), south
(-x,y,-z), west (z,y,-x). Mirroring reverses local X before rotation. Automatic controllers derive
orientation from their declared horizontal facing state unless a fixed facing is supplied.
Sparse offsets are integers; @ must be at (0,0,0). Positions cannot repeat, even for ignored cells.
Their bounding dimensions have the same limits as layers. Sparse issue layer/row/column values are the
authored Y/Z/X offsets rather than string indices.
| Rule | Matches |
|---|---|
| Resolved block reference | That block identity, any metadata. |
{block = reference} |
Same identity rule. |
{block = reference, damage = integer} |
Literal metadata 0–15. |
{block = reference, state = fields} |
Declared semantic states; horizontal facing values rotate/mirror with the pattern. |
{air = true} |
Actual air. |
{anyOf = rules} |
Boolean OR of 1–32 rules. |
{allOf = rules} |
Boolean AND of 1–32 rules. |
{notRule = rule} |
Negation at a loaded, readable position. |
Use exactly one main rule form. damage and state require block; together they both must match.
Raw metadata stays literal. References are resolved at declaration time. Unknown fields, cyclic rules,
more than 16 levels of nesting, or more than 256 nodes in a rule are rejected.
A count constraint has cells (a string selecting symbols), rule, optional min (default 0),
max (default 32768), and label (default the selected symbols). Each selected coordinate counts once
when its rule passes. Constraints all must pass; counts do not alter the meaning of Boolean rules.
Alternatively, use cells, optional label, and allSame = "block", "metadata" or "state:<field>".
Selected coordinates must have identical values; a missing named state fails the constraint.
allSame cannot combine with rule, min or max.
captures maps names to symbol-selection strings. getMatches returns coordinates selected by a symbol
or capture only while the structure is verified formed. Ignored cells cannot be selected.
There are at most 64 capture names, each 1–64 characters, and selections contain 1–94 symbols.
repeatLayers = {first = integer, last = integer, min = integer, max = integer} repeats one contiguous
inclusive, 1-based Y-layer section. Counts are 0–32, with min <= max. The anchor must lie outside it.
All expanded dimensions and required-cell counts must stay within limits.
Matching prefers larger repeat counts. At each size it tries the authored orientation before its mirror
when mirroring is enabled. An unavailable preferred candidate stops the search; it is never assumed to
have failed so a smaller structure can form. Explicit checks can select a single options.repeats count.
ctx.world:checkMultiblock(definition, x, y, z, options?) returns a detached report without forming a
structure, creating an instance, changing blocks, or loading chunks. Coordinates are separate integers.
Options are facing, mirror (default false), detailed (default true), and repeats. A standalone check
requires options.facing unless its definition has fixed facing. A managed definition can derive facing
from a loaded anchor. Explicit mirror checks require mirror = true in the definition.
Report fields are key, anchor, facing, mirror, status, reason, checked, required, repeats,
width, height, depth, truncated, issues, and constraints.
-
statusiscomplete,incompleteorunavailable. Unloaded cells never count as air. -
Reasons include
complete,cell_mismatch,constraint_mismatch,out_of_bounds,chunk_unavailable,diagnostic_budget,exclusive_conflictandcontroller_removed. Pending checks usechecking. -
Issues contain coordinates,
layer,row,column,symbol,expected, andreason. Readablemismatches also include
blockId,metadata, and declaredstatewhere available. -
Constraint entries contain
label,found,min,max, andpassed. -
Issue lists are capped at 64;
truncatedflags omitted issues or exhausted diagnostic work. Countsdescribe the reported candidate and completed work, not a promised total of all failures.
Remote-world checks are observations only. Automatic formation and gameplay callbacks target authoritative
singleplayer worlds. Remote diagnostics inspect available chunks; absent terrain remains unavailable.
ctx.world:getMultiblock(x, y, z) returns a scoped controller instance or nil. Block and tile contexts
at managed controllers also expose optional ctx.multiblock. These handles expire with the callback,
world, controller replacement or definition reload.
| Instance method/field | Behavior |
|---|---|
isFormed() |
Cached verified usability; immediately false after a relevant change or chunk unload. |
getStatus() |
Detached lifecycle/report snapshot. |
getMatches(symbolOrCapture) |
Detached coordinate list when formed; otherwise nil, reason. Unknown names raise an error. |
requestCheck() |
Mutable callbacks only; queue a coalesced check. |
data:get(name) / data:set(name, value) / data:snapshot()
|
Persistent declared values, using structured-data validation. |
Lifecycle status is checking, formed, unformed, suspended or removed. getStatus() additionally
includes checkStatus (last query result), isFormed, persistent identity, optional callbackError,
optional dataError and optional conflictingController coordinates. Last report fields may be stale
while checking; use isFormed() to gate machine work. Reading status does not perform a synchronous scan.
| Callback | Trigger |
|---|---|
onFormed(ctx) |
New or confirmed-unformed controller verifies complete. |
onInvalidated(ctx) |
Previously usable geometry fails, or its controller is removed. |
onSuspended(ctx) |
Previously usable geometry cannot be verified because required chunks are unavailable. |
onResumed(ctx) |
Suspended/saved previously usable geometry verifies complete again. |
Contexts contain x, y, z, key, declaring-script owner, reason, detached report, scoped world, multiblock, and
data. Removal notifications describe the old instance; its data can be read but cannot be written.
An explicit world lookup may observe a replacement controller as a distinct instance.
Rapid valid-to-valid replacements coalesce without invalidated/formed spam. Callbacks run at a world
tick boundary before world services. A callback altering its own structure immediately loses formed
usability and is checked at a later boundary. Lua failures are reported separately from physical validity.
Identity and declared custom data persist independently of controller tile inventories/data. Rejoining
revalidates live geometry; saved formation never makes a structure usable by itself. Chunk unload preserves
identity; observed controller removal/replacement creates a new one. Successful reload expires handles,
revalidates geometry and uses resumption for previously usable structures. Missing definitions leave
saved records dormant. Texture/model refresh does not re-form structures or reset gameplay data.
Lifecycle notifications are not crash-safe exactly-once reward transactions. Keep saved idempotence
markers for rewards. Formation itself performs no block conversion, inventory aggregation or model changes.
Automatic recognition requires the Java agent and logical world ticks. It follows world pause behavior.
Patterns are at most 32 per axis and required world Y is 0–127. Script limits are 512 definitions per
owner, 4096 globally; at most one repeat section is supported.
Setting in config/betamoon.cfg
|
Default | Range |
|---|---|---|
multiblocks.maxRequiredCells |
4096 | 1–32768 per expanded candidate |
multiblocks.visitsPerTick |
65536 | 256–1048576 shared discovery/matching work |
multiblocks.maxInstances |
4096 | 1–65535 per active world |
multiblocks.maxInstancesPerOwner |
1024 | 1–65535 per script/world |
Checks may span ticks under load; pending geometry stays unusable. Explicit diagnostics have a separate
262144-visit budget. Instance-cap exhaustion produces a warning and background discovery retries.
Overlaps are allowed by default. Under exclusivity an existing claim is retained, new completed candidates
use stable anchor ordering, and conflicting controllers become eligible when the claim is released.
Changes made by other mods may be detected with a delay when those mods bypass ordinary block updates.
Background checks revisit loaded structures; recovery time increases with loaded terrain, controller counts,
and lower configured processing limits. Ordinary block updates through Lua or normal gameplay invalidate usability immediately.
Omit controller, supply palette["@"], and add discovery. @ is a mathematical reference point;
it can require exactly the same block as every other frame cell. Players need no special block or facing.
Equivalent symmetric placements share one instance with a stable canonical reference and facing.
| Field | Meaning |
|---|---|
discovery.mode |
"automatic" (default) scans loaded chunks and reacts to member edits; "manual" requires item activation and creates instances only through explicit item activation. |
discovery.cells |
Seed symbols or a capture name. Required for automatic discovery; rules must identify finite non-air block sets. Air-only or unrestricted negative seeds are rejected. |
discovery.facings |
One to four distinct allowed horizontal directions. Defaults to fixed facing, or all four. |
Existing completed structures are discovered when chunks load. Discovery is budgeted and may take several ticks. Required empty cells are watched too. Missing chunks suspend existing structures; discovery never loads them. Manual-mode instances receive the same validation, persistence, and cleanup after creation.
-- Fields inside a multiblock declaration; frame is a registered block reference:
layers = { { "@FF" }, { "FIF" }, { "FFF" } },
palette = { ["@"] = frame, F = frame, I = { air = true } },
captures = { frame = "@F", interior = "I" },
discovery = { cells = "frame" },
activation = { mode = "automatic" }Formation and activation are separate. A complete item-activated structure is formed while waiting for its item.
instance:isActive() also requires verified formation; it is false immediately after a relevant edit or unload.
Controller-based instances use formation as their processing gate and do not use these activation policies.
| Field | Meaning |
|---|---|
activation.mode |
"automatic" (default) or "item". |
activation.item |
Required resolved item reference for item mode. |
activation.damage |
Optional exact held-item damage, 0–32767. |
activation.target.cells |
Required symbols/capture accepting item use. |
activation.target.position |
"clicked" (default) uses the clicked member; "adjacent" uses the position next to its clicked face. |
activation.cost.damage |
Positive durability cost for a damageable item. |
activation.cost.consume |
Positive stack count, at most 64. Cannot combine with durability cost. Omit cost for a reusable item. |
activation.fill |
Optional owned output, described below. |
activation = {
mode = "item", item = wand,
target = { cells = "frame", position = "clicked" }
}Built-in item activation runs after item-use-first and ordinary block interactions, before normal item use. A successful activation handles the click and applies its cost once. Clicking an already-active instance costs nothing. A normal mismatch or rejected eligibility check passes through to ordinary item use. Ambiguous matches, relevant unavailable chunks, exhausted work budgets, conflicts, or pending cleanup handle the click without a cost so native fallback cannot perform a different destructive action. Manual discovery mode disables built-in item routing.
fill = { cells = "interior", block = surface, facingState = "facing" }fill is a field of activation. cells selects symbols/capture; block is a resolved non-air block
that differs from the original selected cells. Owned fills do not replace or create tile entities;
activation fails before item cost if a selected original cell contains one. Optional facingState names an enum containing exactly north,
east, south, and west on that output block. The engine binds the matched orientation to it.
The instance accepts only its own recorded output in place of the original requirements. Other structures cannot adopt it by block ID. Deactivation restores unchanged owned cells and preserves external replacements. Cleanup waits for unavailable chunks and blocks reactivation while pending. Breaking an owned panel deactivates item-mode structures even when the replacement is air. Automatic structures may activate again when their geometry is valid.
canActivate(ctx) returns a boolean using a read-only context. False or a callback error denies activation.
It receives the proposed instance and report, with optional player, item snapshot, clicked position, target
position and named face for item use. These extra fields are absent during automatic activation.
onFormed precedes onActivated. onActivated(ctx) observes a committed activation; callback failures are
reported without repeating effects or charging the item again. onDeactivated(ctx) includes a reason, such
as manual, output_changed, or a structural mismatch. On confirmed invalidation,
deactivation occurs before onInvalidated. Unload/rejoin uses suspension/resumption without replaying activation.
Incompatible definition replacement or removal cleans owned output without invoking callbacks from the unloaded definition.
| Function | Behavior |
|---|---|
instance:deactivate() |
Controllerless only. Preserve valid geometry, clean owned output and inhibit automatic reactivation. |
instance:activate() |
Explicitly reactivate automatic controllerless instances after validation; item mode returns item_required. |
ctx:activateMultiblock(definition, x, y, z, options?) |
Item context only. Validate an exact placement and the current actor/item before activating. |
These return {activated, handled, reason, instance?}; the optional instance handle is scoped to the callback.
activated is true only for a newly committed activation. Deactivation returns deactivated or already_inactive,
with no automatic refund. Repeated deactivation does not repeat callbacks. Manual inhibition survives rechecks and
rejoin; breaking and rebuilding a confirmed retired structure creates a new identity with normal activation rules.
Exact activation options: facing, mirror, repeats, plus optional clicked = {x,y,z} and face for a
ray-traced onUse target. Named faces are preferred; indexes 0–5 are also accepted. Omitted placement options use
the first allowed facing, no reflection, and the longest declared repeat variant. Use found-match values when available.
The target must still belong to the declaration's activation capture. Additional Lua checks cannot bypass item costs,
structural validation, claims or limits. A handled manual activation suppresses ordinary native fallback for that item use.
Status adds isActive, activationSaved (including suspended intent), and manuallyInhibited.
Calls on controller-based instances return controller_managed.
Pure search returning {matches, complete, reason, truncated}. Matches are detached reports with canonical
anchor, facing, mirror, repeats, world bounds, and named captures of positions.
| Option | Meaning |
|---|---|
bounds |
Required inclusive {min={x,y,z}, max={x,y,z}} bounds for candidate reference positions; volume 1–32768. |
near |
Optional {x,y,z,cells} requiring that world position to belong to selected symbols/capture. |
containedIn |
Optional inclusive bounds that must contain every required pattern cell. |
facings |
Optional subset of the definition's allowed orientations. Equivalent placements return their canonical facing. |
mirror |
Default definition mirror setting; true searches both reflections, false only unmirrored. |
repeats |
Optional explicit declared count; omitted searches declared sizes. |
maxMatches |
Default 64; 1–256. |
Search never creates managed instances. complete is false on search_budget_exceeded, result_limit, or
relevant chunk_unavailable; partial matches remain available. Require completeness before deciding uniqueness.
An ordinary finished search with zero matches returns complete = true.
local result = ctx.world:findMultiblocks(structure, {
near = { x = ctx.position.x, y = ctx.position.y, z = ctx.position.z, cells = "frame" },
bounds = { min = { x = 0, y = 0, z = 0 }, max = { x = 15, y = 127, z = 15 } }
})
if result.complete and #result.matches == 1 then
local match = result.matches[1]
local outcome = ctx:activateMultiblock(structure, match.anchor.x, match.anchor.y, match.anchor.z, {
facing = match.facing, mirror = match.mirror, repeats = match.repeats
})
endThe example belongs in an item block-use callback. For ordinary onUse, obtain ctx:rayTrace() first and pass
its position and face as activation options. Pure reports can be retained, but activation always checks the live world.
Returns known callback-scoped memberships in stable identity order, including required air and owned output cells.
options.definition optionally filters by a definition reference. Returns an empty list when none are known;
it does not trigger discovery or assume a single owner. Existing getMultiblock(x,y,z) remains controller lookup.
Pure checks/searches share 65536 work units per callback; partial diagnostic results remain explicit. Seed
transformations are limited to 65536 cells across variants/orientations. Managed activation requires
requiredCells * (8 + 4 * constraintCount) within the smaller of 65536 and half the configured per-tick visit
budget; declarations exceeding it explain how to reduce work or raise that budget. These limits bound validation
and owned effects; repeated search calls do not reset the callback allowance. Recognition and pending cleanup are
incremental. Structures remain unusable until a complete current verification succeeds.
Advanced 06: Visual Portal uses a 4x5 frame made entirely from Visual Portal Frame blocks (243), with optional corners and a 2x3 opening. No special controller position or placement direction is needed. Use flint and steel against an inner frame face to activate its owned, animated surface (244). Breaking a frame block or panel extinguishes it; repair and activate again. Cleanup preserves player replacements. Passing through never teleports.
Advanced 12: Charcoal Kiln uses 24 brick blocks and an outward-facing controller. Layers run bottom to top; rows run front to back:
| Layer | Front | Middle | Back |
|---|---|---|---|
| Bottom | BBB |
BBB |
BBB |
| Middle | B@B |
B.B |
BBB |
| Top | BBB |
B.B |
BBB |
B is brick, . is air, and @ is the controller. The machine gates existing recipe processing with
ctx.multiblock:isFormed(). Every vanilla log variant yields charcoal without fuel. Its two-slot GUI
uses a vanilla flame at the furnace arrow position and a conditional warning tooltip. Full output pauses
work; changed input or a failed structure resets progress. Tile inventory/data remain with the controller.
- Installation
- Install scripts
- Getting started
- VS Code and LuaLS
- Examples
- Reload and compatibility
- Troubleshooting
- Changelog
Scripts and shared values
- Script lifecycle
- Packaged Lua mods
- Modules
- References and stacks
- Queries
- Overrides
- Minecraft constants
- Chat and positions
- Structured data
- Script configuration
- Particles
Blocks and items
- Multiblocks
- Blocks
- Block state and placement
- Block shapes and rendering
- Block callbacks
- Block ticks and redstone
- Items
- Fluids
- Item callbacks
- Tools and armor
- Materials
- Textures
Entities
- Entities
- Entity access and movement
- Entity lifecycle and data
- Entity health and loot
- Entity inventories and relationships
- Projectiles and pickups
- Entity presentation and sounds
- Living entities and AI
- Entity parts and sensors
Assets and audio
- Assets
- Models and animations
- Built-in Minecraft models
- Model appearances
- Blockbench workflow
- Audio
- Texture-pack overrides
Events and world actions
World generation
World systems
Inventories and screens
Recipes