Scripting Reference
Lua API
Write world generators in Lua 5.4. Drop a file in the right directory — it's live on the next run.
Design Philosophy
Sandboxed, Safe, Simple
Lua generators run inside a strict sandbox. Only the scene construction API and safe Lua standard libraries are available. There is no file I/O, no network access, no process spawning.
This makes generators easy to distribute, safe to run from untrusted sources, and impossible to accidentally corrupt the host system.
io, os, debug, require, dofile, loadfile.
math, string, table, ipairs, pairs, type, tostring, tonumber, print, pcall, error.
Auto-Discovery
Zero-Config Registration
The LuaGeneratorRegistry scans generators/lua/ recursively at startup. Every .lua file found is executed to extract its M.id string. No configuration file or registration step is needed.
Subdirectory structure is for human organisation only — the registry does not enforce categories. The ID string in M.id is the only thing that matters for lookup.
Module Structure
Generator Module Format
local M = {} -- Required: unique generator ID M.id = "lua.zone.my_zone" -- Optional but recommended M.version = "0.1.0" M.category = "zone" function M.generate(ctx, scene) scene:addGround("grass") scene:setMetadata({ generator = { id = M.id, version = M.version, category = M.category, language = "lua" }, generation = { variationInput = ctx.variation } }) end return M
| Field | Required | Description |
|---|---|---|
M.id | required | Unique generator identifier. Must match the convention lua.<cat>.<name>.<variant>. |
M.version | optional | Semantic version string. Used in metadata output. Defaults to "0.0.0" if omitted. |
M.category | optional | Category tag: zone, object, building, room. For documentation and metadata. |
M.generate | required | Function accepting (ctx, scene). Called once per chunk generation request. |
Context Object
The ctx Parameter
The first parameter to M.generate is a context table containing information about the chunk being generated.
| Field | Type | Description |
|---|---|---|
ctx.variation | integer | Per-chunk seed derived from the world seed and chunk coordinates. Use for deterministic pseudo-random placement. |
ctx.chunk_x | integer | X coordinate of this chunk in the world grid (0-based). |
ctx.chunk_y | integer | Y coordinate of this chunk in the world grid (0-based). |
ctx.chunk_size_m | number | Side length of the chunk in metres (default 64). Use this instead of hardcoding 64. |
ctx.style | string | World style name (e.g. "central_europe_small_city"). Can drive material selection. |
ctx.zone | string | Zone type for this chunk (e.g. "city", "forest", "ocean"). |
ctx.region | string | Region type for this chunk (e.g. "park", "small_house_block", "crossroad"). |
ctx.parameters | table or nil | Optional parameter table for object/building generators. Caller passes this to override defaults. |
ctx.variation as your seed rather than math.random() to keep generation deterministic across platforms and runs.
Scene API
The scene Parameter
Places a ground plane covering the entire chunk at y=0. Every chunk should call this exactly once.
scene:addGround("grass") scene:addGround("asphalt") scene:addGround("sand")
Adds an axis-aligned rectangular box. The most versatile primitive — used for walls, floors, furniture, signs, and almost everything else.
| Param key | Type | Required | Description |
|---|---|---|---|
position | {x, y, z} | yes | Centre of the box in world space. y=0 is the ground plane. |
size | {w, h, d} | yes | Width (X), height (Y), depth (Z) of the box in metres. |
material | string | yes | Material name. Must exist in the MaterialRegistry. |
ry | number | optional | Rotation around the Y axis in degrees. Default 0. |
scene:addBox("wall_front", { position = {0, 1.5, 5}, size = {10, 3, 0.3}, material = "plaster_white", ry = 0 }) scene:addBox("bench_seat", { position = {20, 0.46, 15}, size = {1.55, 0.04, 0.35}, material = "wood_bench" })
Adds a vertical cylinder. Used for tree trunks, lamp poles, fountain basins, columns, pillars, and round objects.
| Param key | Type | Required | Description |
|---|---|---|---|
position | {x, y, z} | yes | Base centre of the cylinder (bottom face centre) in world space. |
radius | number | yes | Radius in metres. |
height | number | yes | Height in metres. |
material | string | yes | Material name. Must exist in the MaterialRegistry. |
scene:addCylinder("lamp_pole", { position = {15, 0, 18}, radius = 0.055, height = 4.5, material = "metal_lamp" }) scene:addCylinder("tree_trunk", { position = {10, 0, 10}, radius = 0.18, height = 4.0, material = "wood_bark_dark" })
Adds a flat horizontal plane at a specific position. Used for roads, pavements, plazas, water surfaces, and decorative overlays on the ground.
| Param key | Type | Required | Description |
|---|---|---|---|
position | {x, y, z} | yes | Top-left corner of the plane. y should be slightly above 0 (e.g. 0.01) to avoid z-fighting with the ground. |
size | {w, d} | yes | Width (X) and depth (Z) of the plane in metres. |
material | string | yes | Material name. |
-- Central plaza (8×8 m at chunk centre) scene:addPlane("plaza", { position = {28, 0.01, 28}, size = {8, 8}, material = "stone_pavement" }) -- North-south path (2 m wide, full chunk length) scene:addPlane("path_ns", { position = {31, 0.01, 0}, size = {2, 64}, material = "stone_path" })
Embeds another generator's output at a specific position within this chunk. Used to compose complex scenes from reusable object generators.
| Param key | Type | Required | Description |
|---|---|---|---|
generator | string | yes | Generator ID to instantiate (e.g. "lua.object.bench.simple"). |
position | {x, y, z} | yes | Position offset for the instance's origin. |
ry | number | optional | Y rotation in degrees. |
parameters | table | optional | Passed as ctx.parameters to the child generator. |
scene:addInstance("bench_n", { generator = "lua.object.bench.simple", position = {32, 0, 26}, ry = 0, parameters = { width = 1.8, material = "wood_bench" } }) scene:addInstance("house_1", { generator = "lua.building.simple_house.standard", position = {5, 0, 5}, ry = 90 })
Sets the metadata block of the MC3 output file. Required for MC3 validation to pass — a chunk without valid metadata will be rejected.
| Field | Required | Description |
|---|---|---|
meta.generator.id | required | Generator ID string. Use M.id. |
meta.generator.version | required | Version string. Use M.version. |
meta.generator.language | required | Always "lua" for Lua generators. |
meta.generation.variationInput | optional | The variation seed used. Aids reproducibility. |
meta.chunk.x, meta.chunk.y | optional | Chunk coordinates from ctx. Useful for debugging. |
scene:setMetadata({ generator = { id = M.id, version = M.version, category = M.category, language = "lua" }, chunk = { x = ctx.chunk_x, y = ctx.chunk_y, size_m = ctx.chunk_size_m }, generation = { variationInput = ctx.variation, zone = ctx.zone, region = ctx.region } })
Complete Example
Full Park Zone Generator
This is the actual generators/lua/zone/park.lua shipped with MeshWorld. It demonstrates all scene API methods, seeded randomness, and helper functions.
-- SPDX-License-Identifier: MIT local M = {} M.id = "lua.zone.park" M.version = "0.1.0" M.category = "zone" -- Seeded pseudo-random (no global state) local function rng(seed, i) local v = (seed * 1664525 + i * 22695477 + 1013904223) % (2^32) return v / (2^32) end local function place_bench(scene, id, x, z, ry) local w, sh = 1.6, 0.44 scene:addBox(id.."_legl", {position={x-w/2+0.1,sh/2,z}, size={0.08,sh,0.5}, material="metal_lamp", ry=ry}) scene:addBox(id.."_legr", {position={x+w/2-0.1,sh/2,z}, size={0.08,sh,0.5}, material="metal_lamp", ry=ry}) scene:addBox(id.."_seat", {position={x,sh+0.02,z}, size={w-0.05,0.04,0.35}, material="wood_bench", ry=ry}) scene:addBox(id.."_back", {position={x,sh+0.30,z-0.19}, size={w-0.05,0.35,0.04}, material="wood_bench", ry=ry}) end local function place_lamp(scene, id, x, z, h) h = h or 4.5 scene:addCylinder(id.."_base", {position={x,0,z}, radius=0.12, height=0.06, material="metal_lamp"}) scene:addCylinder(id.."_pole", {position={x,0,z}, radius=0.055, height=h, material="metal_lamp"}) scene:addBox(id.."_head", {position={x,h,z}, size={0.35,0.18,0.35}, material="metal_lamp"}) end local SPECIES = { {tr=0.18, th=4.0, cr=3.2, tm="wood_bark_dark", cm="foliage_oak" }, {tr=0.15, th=3.5, cr=2.8, tm="wood_bark_light", cm="foliage_linden" }, {tr=0.10, th=5.0, cr=2.0, tm="wood_birch", cm="foliage_birch" }, {tr=0.20, th=4.5, cr=3.5, tm="wood_bark_dark", cm="foliage_chestnut" }, } local function place_tree(scene, id, x, z, seed_i, scale) scale = scale or 1.0 local sp = SPECIES[math.fmod(seed_i, 4) + 1] local cy = sp.th * scale * 0.7 scene:addCylinder(id.."_trunk", {position={x,0,z}, radius=sp.tr*scale, height=sp.th*scale, material=sp.tm}) scene:addBox (id.."_canopy", {position={x,cy,z}, size={sp.cr*scale*2,sp.cr*scale*1.4,sp.cr*scale*2}, material=sp.cm}) scene:addBox (id.."_top", {position={x,cy+sp.cr*scale*0.6,z}, size={sp.cr*scale*1.3,sp.cr*scale*0.7,sp.cr*scale*1.3}, material=sp.cm}) end function M.generate(ctx, scene) local S = ctx.chunk_size_m local half = S / 2 local var = ctx.variation or 0 scene:addGround("grass") scene:addPlane("plaza", {position={half-4,0.01,half-4}, size={8,8}, material="stone_pavement"}) scene:addPlane("path_ns", {position={half-1,0.01,0}, size={2,S}, material="stone_path"}) scene:addPlane("path_ew", {position={0,0.01,half-1}, size={S,2}, material="stone_path"}) scene:addCylinder("fountain_basin", {position={half,0,half}, radius=1.8, height=0.4, material="stone_wall"}) scene:addCylinder("fountain_column", {position={half,0.4,half}, radius=0.2, height=1.2, material="stone_wall"}) scene:addCylinder("fountain_cap", {position={half,1.5,half}, radius=0.5, height=0.2, material="stone_pavement"}) place_bench(scene, "bench_n", half, half-6, 0) place_bench(scene, "bench_s", half, half+6, 0) place_bench(scene, "bench_w", half-6, half, 90) place_bench(scene, "bench_e", half+6, half, 90) local lamps = {{half,8},{half,S-8},{8,half},{S-8,half},{half-12,half},{half+12,half}} for i, lp in ipairs(lamps) do place_lamp(scene, "lamp_"..i, lp[1], lp[2]) end local trees = {{10,10},{18,8},{8,20},{14,18},{54,10},{46,8}, {56,20},{50,18},{10,54},{18,56},{8,46},{14,50}, {54,54},{46,56},{56,46},{50,50}} for i, tp in ipairs(trees) do place_tree(scene, "tree_"..i, tp[1], tp[2], var+i, 0.8+rng(var,i)*0.5) end scene:setMetadata({ generator = {id=M.id, version=M.version, category=M.category, language="lua"}, chunk = {x=ctx.chunk_x, y=ctx.chunk_y, size_m=S}, generation = {variationInput=var, zone=ctx.zone, region=ctx.region} }) end return M