Log inGet started

world

Updated October 20, 2026

The world namespace: 168 functions.

globals/world/add

world.add(paths: string | { string }, opts: AddOpts?) -> AddReport

Stage one path's manifest row, or each of a list of paths, expanding to the full asset family if a path lives inside a composite asset. Idempotent at (stage, manifest_row). A list stages every row its paths select once, so naming several files of one asset stages that asset's family once, as git add a b c does. Pass { force = true } to bypass the .zmignore / .gitignore gate — same intent as git add -f. Without force, attempts to stage an ignored path (or a path whose .refs points at an ignored dep) error. { stage = "<name>" } stages into the named area, shared with whoever else names it, instead of the caller's own, so a commit naming that area freezes these paths and leaves every other area's staged. Staging is advisory, and a path is staged in one area at a time. Naming a path another staging area holds takes it from that area, whichever account opened it and whether or not its session is open, and the report names the area, its account and whether that session is working now in released. A take from an area of another account warns, naming that account, the area and its session; a take from another agent or engine of the caller's own account does not. A staged path a caller of another account is writing now is named in written and warned about the same way. A staged source that references an asset whose current version no commit holds needs that version committed with it. The asset is staged beside the source, taken from any other area holding it, and named in carried. A .scene stages as its files on disk hold it, in edit and in play alike; live entity edits nobody saved stay in the session, and a notice names them with the calls that keep or drop them. An installed asset is staged by its root path: every row of the install (its files, its dependencies under /zero/source/deps/) is staged with it, as world.vcsStatus().installs lists it. A path that is one of the other rows of an install raises, naming the install and its root. A file world.vcsStatus lists with the reason references moved stages like any change: its row carries its live reference table and its bytes are the ones the branch head already holds.

Parameters

  • paths string | { string } — The path to stage, or a non-empty list of paths. Each must be a non-empty string; a gate refusing any of them refuses the call before anything is staged. { renormalize = true } first restates every folder asset's row at or beneath the named paths from the files beneath it, as git add --renormalize does: a folder whose stated version is not the folder hash of its files is staged at the hash its files hold, and a folder that already states it is left out.
  • opts AddOpts (optional) — Optional { force: boolean?, renormalize: boolean?, stage: string? }. Defaults to { force = false } on the calling caller's own staging area.

Returns AddReport — What the call staged: staged lists the rows the paths name (the asset family of a path inside an asset; otherwise the path and every changed row beneath it, deletions included, so a deleted asset folder stages the deletion of each of its files), leaving out with a warning naming it each path no row at or beneath which holds a change, carried the referenced assets staged beside them because no commit holds their current version, released each path taken from another staging area (stage, its account, same_account, its session and whether that session is live), and written each staged path a caller of another account is writing now (by, account). Each carried row, and each released and written row of another account, is also written to the caller as a warning.

world.add("/source/foo.luau")
world.add({ "/source/a.luau", "/source/b.luau" })
world.add("/source/scene_dirty/entities/42.json", { force = true })
world.add("/source/fauna.module", { stage = "fauna" })
world.add("/source/fauna.module", { renormalize = true })

globals/world/add_all

world.add_all(opts: AddAllOpts?) -> ({ string }, AddAllReport)

Stage every dirty manifest row, whoever wrote it, skipping any path that matches .zmignore / .gitignore. Paths that match an ignore pattern are skipped; world.add(path, { force = true }) is the explicit way to override the gate for an individual path. Rows still flagged conflicted by world.pullAsset are held back too: resolve them (edit + world.add(path), or world.resolvePullConflict) and re-run. Staging is advisory. The working tree is one per branch and staging areas are not, and a path is staged in one area at a time: a path another staging area holds is taken from that area, whichever account opened it and whether or not its session is open, and named in released with the area, its account, its session and whether that session is live. A row another caller is writing is staged like any other. The calling account's own agents and engines never warn each other: their areas and their writes are the account's own. A take from another account's area warns once per area, naming the account, the area and its session, and saying that account is working on the paths now when its session is open; a staged path a caller of another account is writing now warns once per caller and is named in written. Each staged path brings its asset family. A file world.vcsStatus lists with the reason references moved is staged too. The call reads the working tree a fixed number of times and stages and releases in batches, so its cost follows the size of the dirty set. A scene stages as its files on disk hold it, in edit and in play alike: live entity edits nobody saved stay in the session, and a notice names each scene holding some, with the calls that write them into scene.json (sceneAuthoring acceptChanges, layers.active:save()) or drop them.

Parameters

  • opts AddAllOpts (optional) — Optional { stage: string? }. stage names the staging area to stage into; omitted, the call stages into the calling caller's own area.

Returns ({ string }, AddAllReport) — The paths that were held back: the conflicted ones. Empty when none were. The second value reports the call in full: staged lists the paths it added to the staging area, held lists every held-back path with its reason ("conflicted"), released lists each path taken from another staging area (stage, account, same_account, session, live), and written each staged path a caller of another account is writing now (by, account).

world.add_all()
world.add_all({ stage = "fauna" })
local held, report = world.add_all()

globals/world/affirm

world.affirm(token: string)

Consume an XXX-XXX-XXX-style affirmation token returned by a destructive op surface (e.g. vfs.remove). The destruction commits atomically with the pending-row delete; the token is one-shot. Errors verbatim on expiry / wrong-user.

Parameters

  • token string — The affirmation token.
world.affirm("ABC-DEF-GHI")

globals/world/args

world.args -> any

Returns any

globals/world/assetInstallable

world.assetInstallable(opts: AssetInstallableOpts) -> AssetInstallableReport

Report whether one published asset can be installed on its own, without raising. world.previewInstall plans a real install and raises when the closure will not resolve; this answers the prior question — does this guid name something ZeroMind will hand over by itself — as a verdict a caller can branch on. Content that ships inside a larger library (a module inside a package, a material inside a system) carries its own published identity, so the answer is per asset rather than per library. Reads only.

Parameters

  • opts AssetInstallableOpts — { guid } names the asset; ref pins a commit-id instead of the latest.

Returns AssetInstallableReport — An AssetInstallableReport — installable, a verdict, a one-line detail, and the closure's nodes / tree_children / deps shape when one resolved.

world.assetInstallable({ guid = asset.guid("@builtin::materials.neon") })

globals/world/avatar_default_edit

world.avatar_default_edit -> any

Returns any

globals/world/avatar_default_play

world.avatar_default_play -> any

Returns any

globals/world/awaitOutgoingSync

world.awaitOutgoingSync()

Wait until every /source write this session made has reached the branch it was written against. Raises naming the paths that did not land. add / commit / push / checkout / merge / pull already wait on their own; call this before rebinding after a burst of writes, which world.checkout and world.swap refuse over.

Returns Nothing. Raises when a write did not land.

world.awaitOutgoingSync() ; world.checkout("main")

globals/world/beforeCommit

world.beforeCommit(name: string, fn: ((any) -> ())?)

Open-or-resume a staging area, set the message, and materialise the commit. Commits ONLY what's already staged via world.add / world.add_all — git semantics, not git commit -a. The reducer auto-deletes the stage row on success so a subsequent world.commit opens a fresh one.

Where derive hooks compute outputs for the staged content, the commit is sealed under its id first and lands once every required output is computed. The id this returns can name a commit that has not landed yet: world.vcsStatus().sealed lists the sealed commits with what each waits on, and world.unseal returns one to the staging area. When an older sealed commit holds a refusal, this commit queues behind it and the call says so.

{ stage = "<name>" } materialises one of the caller's own staging areas, so the commit carries the paths staged under that name and leaves every other caller's staged. When a commit from another caller has landed since the area was opened, this brings the area onto the branch head there is now and commits it there.

A staged scene is frozen as staged. Live entity edits its overlay holds that nobody saved stay in the session, and a notice names them once the commit lands.

Pre-flight .zmignore refs gate: every staged source's aggregated deps (via asset.deps, which recurses composite asset folders) are checked against the live ignore set. If any dep target's path is currently ignored AND the dep target is not itself in the stage, the commit is refused. This mirrors the closure invariant — a commit whose deps can't resolve cleanly shouldn't land. Force-staging the dep alongside (world.add(dep, { force = true })) makes the ignored dep satisfy the gate. Run fn at the start of every world.commit, before the commit reads what is staged — the moment to write content that belongs in the commit being made, and stage it. fn receives { stage }, the staging area the commit materialises (nil for the caller's own), to pass to world.add. Registering a name again replaces what it held, and fn = nil removes it. A hook that raises refuses the commit, naming the hook.

Parameters

  • name string — What identifies the hook.
  • fn ((any) -> ()) (optional) — The hook, or nil to remove it.

Returns The newly-allocated commit id (ULID string).

local id = world.commit("feat: ship widget")
local id = world.commit("fauna: the swallow colony", { stage = "fauna" })
world.beforeCommit("procgen.diskcache", function(ctx) ... end)

globals/world/branches

world.branches() -> { { branch: string, commit_id: string, current: boolean } }

Every branch this world has, with the commit each one's head names and which one this session is on — git branch --list. Sorted by name. A branch exists for everyone in the world; which one you are on is yours alone, so current is true for at most one row here and says nothing about where anybody else is.

Returns { { branch: string, commit_id: string, current: boolean } } — Array of { branch, commit_id, current }.

for _, b in ipairs(world.branches()) do print(b.branch, b.commit_id) end

globals/world/camera_default_edit

world.camera_default_edit -> any

Returns any

globals/world/camera_default_play

world.camera_default_play -> any

Returns any

globals/world/checkUpdates

world.checkUpdates() -> ({ UpdateReport }, { UpdateSkip })

Discover which units of this world have an update available. Read-only — makes no local mutation and no VCS write. A unit is one of:

  • a library: a marker (/source/libs/@<name>, @builtin included) whose requested_ref (default main) now resolves in the library's world to a commit other than the marker's resolved_commit. Apply with world.updateLibrary(name).
  • an installed asset: an install root (the asset that was installed; its reference table marks it) whose recorded version differs from the newest version of that asset at its origin, one lookup per root. A dependency of an install is never compared or reported: only the root is. Apply with world.update(<root path>) (zeromind.update), which installs the root's new closure with each dependency at the version the new root records.

Installed assets whose origin world is a library's world are part of that library's unit. A pulled row that no table marks as an install root (an install older than the marks, content forked from another world) is not reported.

Returns ({ UpdateReport }, { UpdateSkip }) — Two values: an array of UpdateReport (libraries first), each { kind = "library", library, from, to } or { kind = "asset", root_path, root_guid, display_name, from, to }, empty when everything is current; and an array of { name, reason } for the units whose origin could not be reached, which never fail the whole call.

local reports, skipped = world.checkUpdates()

globals/world/checkout

world.checkout(branch: string) -> string

Switch this session to another branch — git checkout <branch>. The branch must already exist (create one with world.createBranch). The tree is replaced by the branch's own content. Which branch this session is on is this session's alone; the branch itself is shared, so others may be on the one you move onto. Your uncommitted changes and staged entries come with you, as git keeps them across a switch: each lands on branch as it read before. When the switch would replace a path you changed with a different version, the call refuses with git's message naming the paths and nothing moves — commit or stash them first. Another caller's uncommitted changes stay on the branch they were made on. The switch is all or nothing: a write of yours still on its way to the branch you leave is waited for, and one that does not arrive refuses the switch before anything moves; a switch refused after your changes moved puts them back, and names each path that does not read as it did. Returns only once the branch's content has landed, so world.head, world.log, world.commit and the VFS all target the new branch immediately afterwards.

Parameters

  • branch string — The branch to switch to.

Returns string — The branch now checked out.

world.checkout("feature")

globals/world/checkoutPaths

world.checkoutPaths(rev: string, paths: { string }, opts: CheckoutPathsOpts?) -> { string }

Write paths as a revision holds them, and stage them: git checkout <rev> -- <path>.... rev is any revision world.revParse reads. Each path comes back as the asset the revision records: its asset guid, its bytes and its kind, so what refers to it by guid keeps resolving, and restoring it again gives the same asset again. A folder, a composite asset's or a plain directory, brings back every file the revision holds beneath it, so a deleted component, module or package comes back whole; a file beneath it the revision does not hold stays. A file whose folder asset was deleted brings the folder back with it. Binary files come back by their content. A path whose version is the branch head's reads clean afterwards and leaves the staging area; any other is staged. The paths that name nothing the revision holds are refused together, naming each, and nothing is written. Returns once the working copy holds every written path as the revision records it.

Parameters

  • rev string — The revision to read.
  • paths { string } — The paths to write: files, composite asset folders or directories.
  • opts CheckoutPathsOpts (optional) — Optional CheckoutPathsOpts: stage = false writes the working tree and leaves the staging area as it was (git restore --source <rev> -- <path>).

Returns { string } — The paths written, as the revision names them.

world.checkoutPaths("HEAD~1", { "/source/foo.luau" })
world.checkoutPaths("HEAD", { "/source/Doom.component", "/source/notes.md" })
world.checkoutPaths("HEAD~2", { "/source/levels" }, { stage = false })

globals/world/checkoutSide

world.checkoutSide(path: string, side: string)

Take one side of an unmerged path — git checkout --ours|--theirs <path>. The path holds the side's whole content afterwards; a merge conflict's side is the session branch (ours) or the branch merged in (theirs), an installed asset's update conflict's side is your local version (ours) or the upstream one (theirs). Record the resolution with world.add and commit.

A merge conflict's side is that branch's committed version: its bytes, its asset guid, its kind and its references, so what refers to the asset by guid keeps resolving. Text, binary and composite assets alike: a composite asset (a .scene, .package, .component … folder) is taken whole — name its folder and the folder and every file in it come from that side. A side that deleted the path leaves it a pending delete. Taking a side again replaces the earlier choice.

Parameters

  • path string — The unmerged path, or the composite asset folder holding one.
  • side string — ours or theirs.
world.checkoutSide("/source/foo.luau", "theirs")

globals/world/commit

world.commit(message: string, opts: CommitOpts?) -> string

Parameters

  • message string
  • opts CommitOpts (optional)

Returns string

globals/world/conflicts

world.conflicts() -> { ConflictEntry }

List every locally-pulled row currently flagged conflicted — the findable surface world.pullAsset leaves behind on an unresolved merge. Read-only.

Returns { ConflictEntry } — An array of ConflictEntry, one per conflicted row. Empty when nothing is conflicted.

local list = world.conflicts()

globals/world/connectedUsers

world.connectedUsers -> any

Returns any

globals/world/contentRequirements

world.contentRequirements(scope: { string }?) -> { ContentRequirement }

List the world's unmet content requirements: user-authored assets whose type-declared content constraints are not yet satisfied (an empty README, a .metadata with no description or tags — the empty-skeleton state a fresh create emits for the author to fill, and a description the type asks for that still starts with the [[describe]] placeholder, which describes = true marks on the row; world.push asks for a description only of the assets the push changes). The same walk world.push gates on, over the whole world: the push refuses over the rows whose asset the calling staging area holds or committed, and publishes the rest with the shortfall recorded on the asset that carries it. world.publishBlockers reports the caller's own share as the content class beside the other two. Empty list = every checked asset meets its type's contract. A write into an asset reports the rows its type's type.yaml requirements give for that asset as it lands: contentRequirements on the vfs.write report and on the MCP write_file / edit_file result.

Parameters

  • scope { string } (optional) — Asset paths to restrict the check to — pass a status read's dirty + staged paths to check only content that would actually publish (a per-file path matches its containing asset; each path resolves directly, with no world enumeration). Omit for the full-world walk the push gate performs.

Returns { ContentRequirement } — Array of ContentRequirement rows — { asset, typeName, detail, path, describes? }, where path is the asset the row is about.

for _, r in ipairs(world.contentRequirements()) do print(r.asset, r.detail) end

globals/world/contribute

world.contribute(opts: ContributeOpts?) -> { ContributeOutcome }

Send improvements to installed content back upstream — git subtree push ending in a pull request. For each targeted origin world: the diverging subtree is remapped to the origin's canonical paths, three-way merged against the origin's CURRENT content (regions the origin also changed become local conflicts to resolve first), pushed as a contrib/<id> branch in the origin world, and opened as a pull request there. With merge (the default) the pull request is merged immediately when authorized — a refusal leaves it open and reported, never a failure. After a merge, the local fork re-pulls so its origin pins advance and the asset no longer reads as ahead.

Parameters

  • opts ContributeOpts (optional) — Optional ContributeOpts — targets (origin world guids; default all ahead), roots (install roots; default every installed asset ahead), merge (default true), title, description, dryRun.

Returns { ContributeOutcome } — Array of ContributeOutcome, one per targeted origin.

local r = world.contribute({})

globals/world/convertInstalls

world.convertInstalls(opts: { only: string?, dryRun: boolean? }?) -> { { root_path: string, record: string, rows_removed: number, overrides_kept: number, files_added: number, completion: string? } }

Bring installs that stand as pulled rows (an earlier engine landed each file as a row of the world) into one record row each, the form world.installAsset writes now. The record is built from the rows themselves, written, and once this engine has materialized the install from it the unchanged rows are taken out of the world; a file changed since the install stays a row of the world. Nothing is committed: rows that were committed leave as deletions in the next commit. Returns one entry per install, and with dryRun writes nothing. Once the rows are taken out the record is stated again from the published version it names, so an install the old engine cut short holds every file of the version: files_added counts the files that added, and completion says why the record stayed as the rows stated it (the published version unreachable, or an asset of it that does not load as its type). The files stay where they stand and the call yields between its steps, so it never holds a frame: a run that outlasts the execute budget continues as a task.

Parameters

  • opts { only: string?, dryRun: boolean? } (optional) — { only?: string, dryRun?: boolean }: only names one install by its root path.

Returns { { root_path: string, record: string, rows_removed: number, overrides_kept: number, files_added: number, completion: string? } } — { { root_path, record, rows_removed, overrides_kept, files_added, completion? } }.

world.convertInstalls({ dryRun = true })

globals/world/createBranch

world.createBranch(name: string, fromCommit: string?, opts: { force: boolean? }?)

Create a branch — git branch <name> [<start>]. The branch starts at fromCommit (defaults to the session branch's HEAD) and gets its own working tree, materialized from that commit. The session stays on its current branch; move onto it with world.checkout("<branch>") (git checkout). With { force = true } (git branch -f) an existing branch is pointed at fromCommit and its working tree rewritten to it, in one transaction; that refuses only when the branch's working tree holds uncommitted changes, which the rewrite would lose.

Parameters

  • name string — The new branch name.
  • fromCommit string (optional) — Commit id to start at. Defaults to world.head().
  • opts { force: boolean? } (optional) — Optional { force: boolean? }.
world.createBranch("feature")
world.createBranch("feature", world.revParse("HEAD~1"), { force = true })

globals/world/deleteBranch

world.deleteBranch(branch: string, opts: DeleteBranchOpts?)

Delete a branch — git branch -d|-D <name>. Drops the branch and the working tree it owns; its commits are left alone, since deleting a branch is dropping the name and the tree under it, not rewriting history. Without opts.force (-d) it refuses a branch whose commits the session's branch does not hold, and one whose working tree holds uncommitted changes, naming them, since those go with the tree; { force = true } (-D) deletes it regardless. Refuses the branch this session is on (check out another first) and the world's last branch.

Parameters

  • branch string — The branch to delete.
  • opts DeleteBranchOpts (optional) — Optional DeleteBranchOpts — force.
world.deleteBranch("feature")
world.deleteBranch("experiment", { force = true })

globals/world/derive

world.derive(name: string, spec: any?)

Register a computation whose output lands in the commit with the content it is computed from. The hook (scope = "asset") computes output inside each staged asset matches accepts, from the asset as staged. Its fingerprint and compute are entries of module, run on a worker. Staging starts the work, and a required output that is not computed holds the commit that carries it: world.commit seals the commit and returns its id at once, and the commit lands with the outputs once they are computed. When every required output is computed already, world.commit returns after the commit landed. spec = nil removes it.

Parameters

  • name string — What identifies the hook.
  • spec any (optional) — The hook, or nil to remove it.
world.derive("thumbs", { scope = "asset", output = "thumb.png", matches = function(ref) return ref.type == "texture" end, module = "@root::thumbs", fingerprint = "fingerprint", compute = "compute" })

globals/world/diff

world.diff(...: string) -> any

Mirror git diff's CLI arg shape. Returns per-file diffs by default; pass --stat for summary stats, --name-only for just paths. Positional commit ids drive the two sources; --staged pivots to staged-vs-HEAD. ---separated args scope the diff to specific paths. --stage=<name> reads the named staging area in place of the calling caller's own.

Parameters

  • ... string — Variadic string args: flags, commit ids, --, path filters.

Returns any — Array of DiffFile tables (or string-list for --name-only).

local files = world.diff()
local files = world.diff("--staged")
local files = world.diff("abc", "def")
local names = world.diff("--name-only")
local files = world.diff("--staged", "--stage=fauna")

globals/world/diffUpstream

world.diffUpstream(root: string) -> { UpdateFile }

What your copy of an installed asset changed against the version it was installed from — the diff zm diff <root> upstream prints. One UpdateFile per file under root whose content differs from its origin version, with the unified diff from the origin version to yours for a text file (diff and hunks are nil for a binary one). Empty when the installed asset holds no change of yours.

Parameters

  • root string — The installed asset's root path (or any path inside it).

Returns { UpdateFile } — Array of UpdateFile, sorted by path.

for _, f in ipairs(world.diffUpstream("/source/Doom.component")) do print(f.diff) end

globals/world/discard

world.discard(opts: StageOpts?)

Drop a staging area without committing. Live manifest dirty flags are preserved so the user can re-stage later. No-op if the area doesn't exist.

Parameters

  • opts StageOpts (optional) — Optional { stage: string? } naming the staging area to drop. Omitted, the call drops the calling caller's own area.
world.discard()
world.discard({ stage = "fauna" })

globals/world/discardAll

world.discardAll(scope: string?) -> DiscardAllResult

Discard every change under a path in one operation — the whole working tree by default. Every file edited since its commit goes back to the committed version, every deletion comes back, and every file no commit holds, an uncommitted install included, leaves the tree. Each goes to trash first, where world.restore(<handle>) brings it back. The paths a staging area holds stay, a staged file edited since it was staged goes back to its staged copy, and a change another account is making and a dependency another install records stay as well: they are named in kept. One backend operation takes the whole scope back, and the call returns once the working copy reads as the committed content.

Parameters

  • scope string (optional) — The VFS path to discard beneath; omitted, every path.

Returns DiscardAllResult — { discarded, paths, kept } — how many rows went back, which, and which rows under the scope stayed because another account or another install holds them.

world.discardAll()
world.discardAll("/source/hud")

globals/world/discardFile

world.discardFile(path: string)

Discard a path's unstaged working edits, taking it back to what it was staged or committed as — the git restore <path> shape. A folder takes every file beneath it back with it, and a deleted path comes back as its committed version. The stage is the baseline when the path is staged, the last commit when it is not, and where it is neither there is nothing to come back to, so the path goes away. Staging is left exactly as it was; world.unstage is the verb that changes it. One shot: edited bytes that revert to a committed version are snapshotted to trash first, so that case is recoverable via world.restore(<handle>); a deletion taken back throws nothing away and leaves no trash entry. Returns once the working copy reads as the reverted content: text reads back as committed, and binary bytes are served at the committed version on the next read. Errors when nothing at or beneath the path is dirty (nothing to discard). An installed asset is discarded by its root path, which puts the whole install back at the version the branch head holds as one unit: its rows leave the staging area and then return to their committed version, or go away when no commit holds them. A dependency another install records too stays. Another row of an install raises, naming the install and its root. A file whose only difference from the head is its reference table (listed with the reason references moved) raises too, naming what moved its references and the undo: moving the library pin back (world.updateLibrary, or discarding the pin marker) or reverting the moved asset.

Parameters

  • path string — The VFS path whose unstaged edits to discard.
world.discardFile("/source/foo.luau")
world.discardFile("/source/Doom.component")

globals/world/dropDerived

world.dropDerived(outputPath: string, opts: { stage: string? }?) -> number

Leave the derived output at outputPath out of the commit. Only an output whose hook is best-effort can be dropped; a required one is refused, naming the other ways out (retry, provide, unseal).

Parameters

  • outputPath string — The output's VFS path.
  • opts { stage: string? } (optional) — Optional { stage: string? } naming the staging area.

Returns number — How many reservations were dropped.

world.dropDerived("/zero/source/gold.material/extra.json")

globals/world/dropOwedWrites

world.dropOwedWrites(path: string?) -> { string }

Give up the changes this session still owes the world at path and beneath it: the writes and removals vfs.pendingWrites() lists there (a write the world never took included), the ones world.syncStatus() names as conflicts or losses, and every stored record of them on this machine, so no later boot re-applies one. Each path then reads as the world's row holds it: the row's content, or nothing where the world has no row. Returns the paths given up. The bytes of a given-up change are not kept.

Parameters

  • path string (optional) — Canonical /source path; defaults to the whole source tree.

Returns { string } — The paths given up, sorted.

world.dropOwedWrites()
world.dropOwedWrites("/zero/source/hud")

globals/world/fetch

world.fetch(branch: string?) -> FetchResult

Update the origin/<branch> remote-tracking ref — git fetch. Mirrors the world's ZeroMind branch head into the local commit history (no working-tree change) and reports how the session branch relates to it: behind origin commits to pull, ahead local commits to push, diverged when both. A stale installed pin or out-of-band ZeroMind change shows up here as behind — reconcile with world.pull().

Parameters

  • branch string (optional) — Remote branch to fetch. Defaults to the session branch.

Returns FetchResult — A FetchResult table.

local f = world.fetch()

globals/world/forkLive

world.forkLive(opts: { source: string, sourceBranch: string?, maxBatches: number? }) -> number

Seed THIS (empty) world's live content from another world by copying its whole manifest as clean Pulled rows — the in-engine half of "fork a world". Provenance is preserved: each row points at the content's ORIGINAL owner (a fork of a fork-of-A still points at A), so the fork never claims to have authored what it pulled. The copy runs server-side in bounded batches (idempotent + resumable), looping until the source is fully mirrored. Pair with world.add_all() + world.commit() + world.push() to publish the fork.

Parameters

  • opts { source: string, sourceBranch: string?, maxBatches: number? } — { source, sourceBranch?, maxBatches? } — source is the source world GUID; branches default to "main"; maxBatches caps the batch loop (default 60 ⇒ up to ~120k entries).

Returns number — The number of pulled (dirty) rows now staged-pending on the fork.

world.forkLive({ source = "e89aa92e-4c1f-460e-acd8-73859dd3a346" })

globals/world/forkStatus

world.forkStatus() -> { ForkStatus }

Per-asset "ahead of origin" report — the fork analogue of git status against an upstream. Every installed (pulled) row whose content diverges from its pinned origin is listed, partitioned by the TRUE origin world it was pulled from (nested dependencies carry the world that authored them, not the intermediary they arrived through). This is information for judgment: decide whether a change belongs upstream, then world.contribute.

Returns { ForkStatus } — Array of ForkStatus partitions.

for _, f in ipairs(world.forkStatus()) do print(f.origin_world, #f.entries) end

globals/world/head

world.head() -> string?

Return the current branch HEAD commit id, or nil if the branch has no commits yet.

Returns string? — The commit id string, or nil.

local id = world.head()

globals/world/installAsset

world.installAsset(opts: InstallAssetOpts) -> InstallAssetResult

Install a published asset into this world, pulling the asset and every dependency it closes over and writing them into the source tree. The world holds ONE row for the install, its record <dest>.pull, which lists every file of the version and every dependency placed beside it; every engine that holds the row materializes the install from it, so a file of the install is a row of the world only once it is changed. Returns once this engine has materialized the install and it runs. Installing an asset where the world already holds it is safe to repeat: rows at this version are left as they are, and rows of an earlier version are moved to this one. The root lands at at, else where the world already installed it, else at /source/<name>; a copy of it the world holds at this version anywhere else (another install's dependency under /source/deps/) moves there, so the world holds the asset once and everything that depends on it resolves to the moved copy. A path holding another asset, or local edits to this one, refuses the install. An install that does not materialize takes its record out again, so the world holds nothing of it and the same install can simply be run again.

Parameters

  • opts InstallAssetOpts — { guid } names the root asset to install; ref pins a specific commit-id instead of the latest.

Returns InstallAssetResult — { assets_written, blobs_downloaded, root_guid, root_path, root_version, deps, pending, problems, outcome, moved_from, record, files, timings }. record is the path of the install's row, files the number of files and folders it states, timings the seconds each step took. outcome is installed, updated or already_present; moved_from names the copy the root moved from, nil when it moved from nowhere. blobs_downloaded counts the blobs the install fetched (bytes already cached cost none); pending lists the written paths still unreadable when the wait for them stalled, and is empty when every one reads back; problems names each reference site in the installed content that resolved to nothing in the world it was published from, and each file a folder version the install wrote holds at its version that ZeroMind gave no entry for, and is empty when every site resolves and every written folder holds all of its files. A folder version lands holding every file its version states: a file the closure leaves out of it is read from that version of the folder.

local r = world.installAsset({ guid = assetGuid })

globals/world/installLibrary

world.installLibrary(opts: InstallLibraryOpts) -> InstallLibraryResult

Declarative cross-world dependency. Writes a single marker file at /source/libs/@<name> whose body is the zero/world-import/v1 JSON. The next commit ships it as one regular manifest entry; ZM's import-derivation pass at finalize-time decodes the marker and stamps the new commit's imports[]. Unmodified library content never ships in the importing world's tree: the engine fetches the pinned commit from ZeroMind and mounts it as @<name>::<path>. The call returns once the library is mounted (require("@<name>::…") resolves and library.list() lists it), and raises naming why when it does not mount, leaving the marker as it was before the call. A marker already there whose library is mounted is refused (move it with world.updateLibrary); one whose library never mounted is replaced, so running the install again repairs it.

Parameters

  • opts InstallLibraryOpts — See InstallLibraryOpts. opts.world is the upstream world's guid (required). opts.commit is the upstream commit_id to pin (optional; resolves opts.ref or main if omitted). opts.as is the local library name (defaults to the upstream world's slug). opts.ref is the human-meaningful ref recorded in the marker.

Returns InstallLibraryResult summarising the install.

world.installLibrary({ world = "guid", as = "combat" })

globals/world/installedAssets

world.installedAssets() -> { InstalledAsset }

Every asset this world carries from ZeroMind, keyed by the published guid it was pulled from. The read that answers "what is actually in this world" by identity rather than by path — an installed asset's local name can be chosen by the installer, so a path is not the thing to check a pull against.

Returns { InstalledAsset } — An array of InstalledAsset sorted by local path, one per pulled row carrying a published guid.

for _, a in ipairs(world.installedAssets()) do print(a.asset_guid, a.path) end

globals/world/installedLibraries

world.installedLibraries() -> { LibraryMarker }

The libraries this world has installed: one row per library marker under /zero/source/libs, @builtin among them, sorted by name.

Returns { LibraryMarker } — { name, world_guid, requested_ref?, resolved_commit } per library: its name with the @, the world it comes from, the ref that was asked for and the commit it is pinned at.

for _, lib in ipairs(world.installedLibraries()) do print(lib.name, lib.resolved_commit) end

globals/world/installs

world.installs(opts: { rows: boolean? }?) -> { InstallInfo }

The installs this world holds, one entry each, whichever form each is stored in: record (one row, every engine materializes the files from it) or rows (an earlier engine landed one row per file, and world.convertInstalls brings it into one record). An asset installed at more than one place names the other places in also_at.

Parameters

  • opts { rows: boolean? } (optional) — { rows: boolean? }: rows = false leaves the row installs out, which reads no row of the world.

Returns { InstallInfo } — An array of InstallInfo sorted by path.

for _, i in ipairs(world.installs()) do print(i.root_path, i.form, i.files) end

globals/world/list

world.list() -> { WorldEntry }

List every world the authenticated user has access to. Calls the spacetime list_my_worlds procedure which wraps ZeroMind's GET /v1/me/worlds. Flattens each entry to one record per world with the role promoted to a top-level field.

Returns { WorldEntry } — Array of WorldEntry records.

local worlds = world.list()

globals/world/loading_screen

world.loading_screen -> any

Returns any

globals/world/log

world.log(opts: LogOpts?) -> { CommitRow }

The history of the session branch, newest first — git log: every commit reachable from HEAD through both parent edges, so a merged branch's commits and a pulled commit appear along with the commits recorded on this branch. opts.rev starts the history at another revision (git log <rev>). Pass opts.path to get the per-path history (git log -- <path>): only the commits that touched that file, newest-first.

Parameters

  • opts LogOpts (optional) — Optional. opts.limit caps the number of commits (default 50, 0 = all). opts.rev names where the history starts. opts.path scopes the log to one file.

Returns { CommitRow } — Array of CommitRow tables.

local commits = world.log({ limit = 20 })
local merged = world.log({ rev = "feature" })
local touched = world.log({ path = "/source/foo.luau" })

globals/world/merge

world.merge(sourceBranch: string, opts: MergeOpts?) -> MergeResult

Merge another branch into the session branch — git merge <source>. The merge runs locally in the world's SpacetimeDB clone and is abortable with world.mergeAbort; nothing reaches ZeroMind until the result is pushed. When the session branch's head is in the source's history the head fast-forwards onto it (unless opts.noFf); otherwise a two-parent merge commit lands (opts.ffOnly refuses that). Local changes follow git's rule: one on a path the merge does not rewrite stays in the working tree, and one on a path it does rewrite refuses the merge with git's message naming the paths. A scene's unsaved live edits are a local change to its scene.json: a fast-forward refuses over a scene whose scene.json it rewrites, naming the scene and how to save or discard the edits, and a merge commit refuses over any scene holding them. A staged entry refuses a merge commit. Conflicts → git-style markers are projected into each conflicting text file, the cleanly-merged remainder is applied as working-tree changes, and world.vcsStatus().unmerged lists what needs attention: resolve each path (edit out the markers, or take one side whole), then world.add + world.commit — that commit records the merge (second parent = the source head) and clears the unmerged set.

Parameters

  • sourceBranch string — The branch to merge in.
  • opts MergeOpts (optional) — Optional MergeOpts — ffOnly, noFf.

Returns MergeResult — A MergeResult — status is fast_forward or clean (with commit), conflicts (with conflicts), or up_to_date.

local r = world.merge("feature")
local r = world.merge("feature", { noFf = true })

globals/world/mergeAbort

world.mergeAbort()

Abort the in-progress merge — git merge --abort. Clears the unmerged set and restores the working tree to the pre-merge state (the target head's committed content; the branch head never moved during a conflicted merge). Errors when no merge is in progress.

world.mergeAbort()

globals/world/offLoaded

world.offLoaded(handle: number) -> boolean

Stop a callback registered with world.onLoaded from running.

Parameters

  • handle number — The handle world.onLoaded returned.

Returns boolean — true when the handle matched a registered callback.

world.offLoaded(h)

globals/world/offSaved

world.offSaved(handle: number) -> boolean

Stop a callback registered with world.onSaved from running.

Parameters

  • handle number — The handle world.onSaved returned.

Returns boolean — true when the handle matched a registered callback.

world.offSaved(h)

globals/world/offUnloaded

world.offUnloaded(handle: number) -> boolean

Stop a callback registered with world.onUnloaded from running.

Parameters

  • handle number — The handle world.onUnloaded returned.

Returns boolean — true when the handle matched a registered callback.

world.offUnloaded(h)

globals/world/onLoaded

world.onLoaded(cb: (...any) -> ()) -> number

Register a callback to run after a world finishes loading.

Parameters

  • cb (...any) -> () — Called when the event fires, with whatever the event supplies.

Returns number — Handle for world.offLoaded.

local h = world.onLoaded(function() log.info("loaded") end)

globals/world/onSaved

world.onSaved(cb: (...any) -> ()) -> number

Register a callback to run after a world is saved.

Parameters

  • cb (...any) -> () — Called when the event fires, with whatever the event supplies.

Returns number — Handle for world.offSaved.

local h = world.onSaved(function() log.info("saved") end)

globals/world/onUnloaded

world.onUnloaded(cb: (...any) -> ()) -> number

Register a callback to run after a world is unloaded.

Parameters

  • cb (...any) -> () — Called when the event fires, with whatever the event supplies.

Returns number — Handle for world.offUnloaded.

local h = world.onUnloaded(function() log.info("unloaded") end)

globals/world/orientation

world.orientation -> string

Returns string

globals/world/prConflicts

world.prConflicts(worldGuid: string?, number: number) -> any

Read a pull request's conflicts — what stands between it and a merge. Returns the mergeability verdict, the merge base, both heads, and one entry per conflicting path. A conflicting TEXT path carries marked_text: the same <<<<<<< / ======= / >>>>>>> rendering a merge leaves in the working tree, with the source and target sides laid against their common ancestor. Resolve a path by writing the settled bytes back to it and committing on the source branch; the pull request re-analyses on the next read. A binary path carries the two sides' hashes and no text — pick a side. A mergeable pull request returns an empty conflict list.

Parameters

  • worldGuid string (optional) — The world the pull request lives in. Defaults to the bound world.
  • number number — The pull request number.

Returns any — Decoded ZeroMind conflicts response.

local c = world.prConflicts(nil, 3)
for _, m in ipairs(world.prConflicts(originGuid, 3).markers) do print(m.path, m.marked_text) end

globals/world/prList

world.prList(worldGuid: string?, number: number?) -> any

Parameters

  • worldGuid string (optional)
  • number number (optional)

Returns any

globals/world/prMerge

world.prMerge(worldGuid: string, number: number, strategy: string?) -> any

Merge a pull request — the agent-side merge button.

Parameters

  • worldGuid string — The world the pull request lives in.
  • number number — The pull request number.
  • strategy string (optional) — merge (default), squash, or fast_forward.

Returns any — Decoded ZeroMind merge response.

world.prMerge(originGuid, 3)

globals/world/prOpen

world.prOpen(opts: PrOpenOpts) -> any

List a world's pull requests, or fetch one. Open a pull request — gh pr create. Proposes the work on one (world, branch) pair to another. Defaults make the common cases one argument: from a fork, the target is the world it was forked from, so world.prOpen({ title = "..." }) proposes your work upstream. In an ordinary world the target is the same world, so you get a branch → main pull request. The PR lives in — and is numbered by — the world it targets, exactly as a forge numbers pull requests on the upstream repository. That is also where world.prList finds it.

Parameters

  • opts PrOpenOpts — title (required), plus description, sourceWorld, sourceBranch, targetWorld, targetBranch to address any leg explicitly.

Returns any — Decoded ZeroMind response. The decoded ZeroMind pull request.

local prs = world.prList()
world.prOpen({ title = "fix the door hinge" })
world.prOpen({ title = "port the fix", targetWorld = otherGuid })

globals/world/prView

world.prView(worldGuid: string?, number: number) -> any

Read one pull request in full — gh pr view. Returns the record plus a LIVE re-analysis against the current branch heads: mergeability (clean / conflicts / fast_forwardable / up_to_date / unrelated), conflict_count, and diff — every path the request adds, modifies or deletes with its checksums. Read this before merging: it is what tells you WHAT the request changes.

Parameters

  • worldGuid string (optional) — The world the pull request lives in (its target world). Defaults to the session world.
  • number number — The pull request number.

Returns any — The decoded pull request view.

world.prView(nil, 1)

globals/world/previewInstall

world.previewInstall(opts: InstallAssetOpts) -> PreviewResult

Preview what installing an asset WOULD write, without writing anything. Fetches + decodes the closure and plans placement (the same helpers world.installAsset uses), returning a flat node list plus rollup totals. A truncated closure is reported (not raised) so a caller can surface it and block import.

Parameters

  • opts InstallAssetOpts — { guid, at?, ref? } — same shape as installAsset.

Returns PreviewResult — nodes + totals + a truncated flag.

world.previewInstall({ guid = "..." })

globals/world/provideDerived

world.provideDerived(outputPath: string, opts: { stage: string? }?) -> number

Take the file at outputPath as the derived output: the file you wrote is staged as it stands, and the reservation is settled done with the fingerprint authored, which no recomputation replaces.

Parameters

  • outputPath string — The output's VFS path; the file must exist.
  • opts { stage: string? } (optional) — Optional { stage: string? } naming the staging area.

Returns number — How many reservations the file filled.

world.provideDerived("/zero/source/gold.material/preview.png")

globals/world/publishBlockers

world.publishBlockers(opts: StageOpts?) -> { PublishBlockerClass }

List every reason world.push would refuse to publish this world, as one entry per blocker class: script errors in user content (script), assets that don't meet their type's content requirements (content), computed asset references a component or scene entrypoint reaches (staticPin), and the conditions the publish gate refuses on: a component or scene entrypoint reaching a dynamic resolve over require/tools.use (gameplayPath), a reference pinned to an asset the world no longer holds (references), a reference of the world's own file recorded before the world pinned the library it names (pinPending, which the commit that takes the file clears by deriving its table at the pin), and an error-severity problem recorded on published content (problems). Each class carries the same title the refusal prints, one items entry per offending subject (an asset identity, or a <path>:<line> site), and the single remedy covering that class. This is the account world.push composes its refusal from, so it names the same blockers with no push attempted — and in full, where a refusal bounds how many of a class it prints. zm status prints this list. One more class blocks the next commit rather than the publish, and carries blocks = "commit": staleReferences, the files the caller's staged set holds or leaves out whose references do not resolve in it (a file whose references moved with a staged change and is left out, or a staged file stating a version the staged set leaves out). It is the class world.commit refuses with; world.push never reads it, since a push publishes what is committed. Empty list = the caller commits and publishes.

Parameters

  • opts StageOpts (optional) — Optional { stage: string? } naming a staging area, so the content class reports the assets that area answers for. Omitted, the read acts on the calling caller's own area.

Returns { PublishBlockerClass } — Array of PublishBlockerClass entries, empty when nothing blocks.

for _, c in ipairs(world.publishBlockers()) do
for _, i in ipairs(c.items) do print(c.kind, i.subject, i.detail) end
end

globals/world/pull

world.pull(branch: string?, opts: PullOpts?) -> PullResult

git pull: fetch origin/<branch> (default the session branch) and merge it into the session branch. When the session branch is strictly behind, its head fast-forwards to the fetched commit; when both sides moved, a two-parent merge commit lands with the same conflict/marker/resolve flow as world.merge (resolve the unmerged paths, then world.add + world.commit; abortable with world.mergeAbort); when the fetched commit is already in the session branch's history nothing changes. opts.ffOnly refuses anything but a fast-forward; opts.noFf always makes a merge commit. Local changes follow git's rule, as world.merge's do, unsaved scene edits included. A branch origin does not hold raises.

Parameters

  • branch string (optional) — The origin branch to pull. Defaults to the session branch.
  • opts PullOpts (optional) — { ffOnly?, noFf? }.

Returns PullResult — A PullResult table: status is up_to_date, fast_forward (commit the new head), clean (commit the merge commit) or conflicts (conflicts).

local r = world.pull()
local r = world.pull("feature")
local r = world.pull("main", { ffOnly = true })

globals/world/pullAsset

world.pullAsset(opts: PullAssetOpts?) -> PullAssetResult

Pull upstream changes into a previously-installed asset. Re-resolves the root's transitive closure at opts.ref (default latest) and lands only what differs from what the world holds (M.__updateRowPlan):

  • A file the world never edited (its bytes equal the version it installed) lands whole when its version, its reference table or a stale conflict flag differs from the closure's: bytes and table are written to the pulled row in one step, text and binary alike, and the write reaches source whatever the engine's mode.
  • A file the world edited is reconciled from (row.origin_checksum, row.checksum, entry.checksum) (M.__reconcileDecision): noop (upstream unmoved) leaves it alone, converged advances its origin pointer, and merge runs a three-way vcs.merge3 over (base, local, theirs): a clean result is written and the origin pointer advances, a conflicted result is written WITH markers and the row is flagged conflicted (base + theirs checksums recorded for world.resolvePullConflict). A binary file the world edited that upstream changed too is flagged conflicted with the structured base/theirs checksums (no marker write).
  • A folder moves to the closure's version and takes its table.
  • A row flagged conflicted that holds the bytes it installed carries no local content: it does not block the pull, and the pull clears it.

Closure drift: an entry the original install never landed is pulled fresh (added). A locally-pulled row nested under the root's own directory whose origin entry disappeared from the closure is removed when it holds the bytes it installed (pruned), or flagged conflicted when it carries edits.

Parameters

  • opts PullAssetOpts (optional) — See PullAssetOpts. opts.guid or opts.path is required; opts.ref pins the re-resolve to a specific upstream commit (defaults to latest finalized).

Returns PullAssetResult summarising what merged, conflicted, was pruned, and was newly added, the version of the root it landed (root_version), and the reference sites in the pulled content that resolved to nothing where it was published (problems).

world.pullAsset({ guid = "..." })

globals/world/push

world.push(commitId: string?, opts: PushOpts?) -> (string? | PushQueued, string)

Publish the current branch to ZeroMind. The no-argument form is a git merge --squash push: EVERY unpushed commit on the branch collapses into a SINGLE ZeroMind commit (latest content per path, parented on the branch's current remote HEAD). Because only the merged final state's bytes are uploaded, a superseded or lost intermediate-commit blob can never break the push — this is what makes a churn-heavy world publishable. The local commit history is preserved as the editing journal; on success every squashed commit shares the one remote commit id. The explicit commitId form still pushes that single commit verbatim via publish_commit (advanced / chain-replay use; its parent must already be on the remote).

Parameters

  • commitId string (optional) — Optional. A single commit to push verbatim. Omit for the squash push of the whole unpushed stack (the normal path). A push only moves the remote branch forward, as git's does: a remote head no local commit holds raises git's ! [rejected] <branch> -> <branch> (fetch first), and one the local head does not descend from raises (non-fast-forward); world.pull() first. opts.force is git push --force: a branch whose head is already published moves the remote branch there regardless (a Maintainer's call). A branch with commits not yet published is refused with force, nothing sent (pull first), and so is force with commitId: force moves the session branch.
  • opts PushOpts (optional) — Optional PushOpts: stage names a staging area, the way every other staging verb takes it, and force forces the move. The pre-push content gate refuses over the assets that area holds or committed; an asset another area answers for publishes with its shortfall recorded on it and is named to the caller with the area that holds it. Omitted, the call acts on the calling caller's own area, so the gate refuses over what that caller staged and committed and names the rest. What an area holds is read from the staged rows a status call reports and from the world's history, where every commit records the area it came through, so the answer is the same from every engine and every session bound to the world. An area named after a caller holds what it committed while that caller's session is open; an area a caller named holds it until another area commits inside the asset.

Returns (string? | PushQueued, string) — Two values: the ZeroMind-allocated commit id, and the verdict. "published" with the new commit id when this call published; "already-published" when ZeroMind already carries what this call would have published — the state a push asks for, so it returns rather than raising. That verdict carries the commit's existing ZeroMind id when the publish names one (the single-commit form), and a nil id when it names none (the whole-stack form, which reports a chain). A squash whose merged final state carries dep.unresolved problems takes the slow path inside the same call: the engine re-resolves each pending literal against its live asset index and submits the resolutions to the publish procedure, which completes the push. A reference literal that still resolves to nothing is published with the asset holding it and stays a problem recorded on that asset, while a dep pin the squash severed raises instead; either way the engine names each one with its path, line, reference and reason. A publish ZeroMind refuses raises naming the condition and the command that clears it — a branch that moved under this push names world.pull().

local zmId = world.push()
local zmId, verdict = world.push()
local queue, verdict = world.push() -- verdict "queued" while commits are sealed
world.push("01HABC...")

globals/world/reload

world.reload() -> { started: boolean?, guid: string?, branch: string?, error: string? }

Run the bound world again from the top, the way a fresh boot into it would: the world unloads, its content is taken again, its entrypoint and startup scene run. The load is asynchronous; the answer says whether it started. An engine with no world bound answers { error }.

Returns { started: boolean?, guid: string?, branch: string?, error: string? } — { started = true, guid, branch } or { error = string }.

world.reload()

globals/world/remoteBranches

world.remoteBranches() -> { { branch: string, commit_id: string, origin_head: string } }

Origin's branches as the last fetch of each recorded them — git branch -r. One row per branch of this world that world.fetch has fetched: branch is origin/<name>, commit_id the local commit that names origin's head, origin_head that head's ZeroMind id.

Returns { { branch: string, commit_id: string, origin_head: string } } — Array of { branch, commit_id, origin_head }, sorted by branch.

for _, r in ipairs(world.remoteBranches()) do print(r.branch) end

globals/world/renameBranch

world.renameBranch(oldName: string, newName: string, opts: { force: boolean? }?)

Rename a branch — git branch -m <old> <new>. The branch's head and history, and your uncommitted changes on it, go to newName; when oldName is the branch this session is on, the session moves with it. Another caller's uncommitted changes on oldName keep that branch in place beside the renamed one.

Parameters

  • oldName string — The branch to rename.
  • newName string — Its new name.
  • opts { force: boolean? } (optional)
world.renameBranch("feature", "feature-2")

globals/world/reset

world.reset(target: string, opts: ResetOpts?) -> ResetResult

Move the session branch's head to target — git reset [--soft|--mixed|--hard] <commit>. target is any revision world.revParse reads (a commit id, HEAD~1, a branch, ...). opts.mode: soft keeps the staged entries and the working tree and stages every path whose committed version the move changed; mixed (the default) empties your staging area and keeps the working tree, so a path the move changed reads as a local change; hard empties your staging area and brings the working tree to the new head: every path the move changed, every path your account changed, and every file of your account's that no commit holds (an uncommitted install and a scene's autosave overlay included), each to trash first. A write this session owes the world and the world is not taking is given up (world.dropOwedWrites). The local changes of another account stay and are listed in kept. A target equal to HEAD moves nothing, so world.reset("HEAD") unstages everything and world.reset("HEAD", { mode = "hard" }) discards your local changes. Non-destructive for history: every commit the head leaves behind becomes a trash entry that re-attaches it (in chain order).

Parameters

  • target string — The revision to move the head to.
  • opts ResetOpts (optional) — Optional ResetOpts — mode.

Returns ResetResult — A ResetResult — the new head, its subject line, and the commits left behind.

world.reset("HEAD~1")
world.reset("01HABC...", { mode = "hard" })

globals/world/resolveConflict

world.resolveConflict(path: string, mode: string, content: string?) -> ResolveResult

Resolve one /source record this session owes the world, one path at a time: a conflicted one, or one whose write or removal the server refused or the queue gave up on (unsaved_writes in world.syncStatus()), which waiting never carries and which holds a world.swap or world.checkout away from the world until it is settled here. mode is merge | apply | take-local | take-backend | discard. merge returns { merged, clean } and mutates nothing — a clean merge can be finalized with apply, and a conflicted one carries <<<<<<< / ======= / >>>>>>> markers to edit first. apply writes the finalized content to /source; take-local writes the retained local bytes; take-backend / discard keep the backend head. For a removal (deleted in world.syncStatus().conflicts), take-local removes the path again and merge errors, since a removal holds no content to merge. Errors when the record is absent, a merge has no common ancestor or hits binary content, or a write fails.

Parameters

  • path string — Canonical /source path of the conflicted record.
  • mode string — One of merge | apply | take-local | take-backend | discard.
  • content string (optional) — Finalized bytes for apply mode.

Returns ResolveResult

world.resolveConflict("/zero/source/foo.luau", "take-local")
local r = world.resolveConflict(p, "merge"); if r.clean then world.resolveConflict(p, "apply", r.merged) end

globals/world/resolvePullConflict

world.resolvePullConflict(path: string, choice: string)

Resolve a conflicted row. A TEXT conflict is one where the file currently contains conflict markers (world.pullAsset writes markers only for a text three-way merge that didn't resolve cleanly); a BINARY/composite conflict has no markers — the local bytes were left untouched.

choice = "theirs" fetches clean upstream text by content hash (conflict_theirs_blob_sha256 — path/rename-independent, since blobs are content-addressed) and overwrites the local file with it before clearing the flag. It errors, refusing to guess, when the row records no theirs blob (a binary/composite conflict — a text blob read can't address theirs for those; keep "ours" or re-install the asset instead).

choice = "ours" keeps the local side. When the file carries conflict markers, the local side is reconstructed from them (the marker writers put ours first, so dropping each block's base and theirs sections restores your bytes exactly) and written back; a marker-free file is kept as-is. Either way the flag clears.

Either way, staging the resolved file (world.add) is the normal git-add path once this returns — this call only clears the manifest-level flag and (for "theirs") the file content.

Parameters

  • path string — The conflicted row's local VFS path.
  • choice string — "ours" or "theirs".
world.resolvePullConflict("/source/combat/rules.luau", "theirs")

globals/world/restore

world.restore(handle: any?)

Restore one trash entry by row_id. Errors verbatim on handler-not-yet-implemented / world-mismatch.

Parameters

  • handle any (optional) — The trash row id. May be a number or a numeric string.
world.restore(42)

globals/world/retryDerived

world.retryDerived(outputPath: string, opts: { stage: string? }?) -> number

Compute the derived output at outputPath again, on the staging area or on a sealed commit that reserves it. A reservation that failed, was dropped or is done is reserved anew and computed from the inputs as they stand; a sealed commit waiting on it lands when it is done.

Parameters

  • outputPath string — The output's VFS path, as zm status names it.
  • opts { stage: string? } (optional) — Optional { stage: string? } naming the staging area.

Returns number — How many reservations were computed again.

world.retryDerived("/zero/source/gold.material/preview.png")

globals/world/revParse

world.revParse(rev: string) -> string

The commit a revision names — git rev-parse <rev>. A revision is HEAD, a branch, origin/<branch> (as the last fetch recorded it), a commit id or a unique prefix of one, each optionally followed by ~N (N first parents back), ^ (the first parent) or ^2 (a merge commit's second parent). Raises git's message for a revision that names nothing, HEAD on a branch with no commit included.

Parameters

  • rev string — The revision.

Returns string — The commit id.

local id = world.revParse("HEAD~1")

globals/world/reviewUpdate

world.reviewUpdate(unit: string, opts: ReviewUpdateOpts?) -> UpdateReview

Review what updating one unit changes, writing nothing: the update itself, never a preview of the asset. Answers the versions the unit moves from (what the world holds) and to (what its origin holds at opts.ref, default the ref world.checkUpdates compares it with), then commits: the commits of the origin world in between, newest first, each { id, message, author, date } (more is true past 200), then the files. modified names up front each file the world holds uncommitted changes to (a dirty or staged row) that the update touches, { path, outcome } with outcome merges or conflicts.

  • An installed asset (unit is its root path, as world.checkUpdates reports it; a path inside the install stands for its root): the commits that change the install's files, back to the one that moved the root from the version the world holds; files lists each file the update adds, changes or removes, { path, change, diff, hunks, local_edit }, where local_edit is none, or merges / conflicts for a file the world edited since it was installed.
  • A library (@builtin, @combat, with or without the @): the commits from the pinned commit to the new one and summary counts of the library files that change and of this world's files whose references move with the pin (they read references moved in status and are committed with the marker). opts.files = true adds files (each changed library file with its diff) and moving (those paths).

opts.commits = false reads no commits, and opts.files = false for an install leaves out files and its diffs: { commits = false, files = false } is the check before an update, answering modified and the counts alone. When a version of the install's root cannot be read the commit list stops there, more is true and commits_error says why.

Parameters

  • unit string — A library name or an install root path.
  • opts ReviewUpdateOpts (optional) — { ref?, files?, commits? }.

Returns UpdateReview: { kind, unit, from, to, commits, more, commits_error?, files?, moving?, modified, summary }; modified is one { path, outcome = "merges" | "conflicts" } per file holding uncommitted changes of its own that the update touches; summary counts added, changed, removed, modified, and merges / conflicts for an install or moving for a library.

local r = world.reviewUpdate("@builtin")
local r = world.reviewUpdate("/zero/source/Fire.module")
local r = world.reviewUpdate("/zero/source/Fire.module", { commits = false, files = false })

globals/world/show

world.show(...: string) -> any

Mirror git show's CLI arg shape. Default returns commit metadata + full diff vs parent. world.show("commit:/path") returns just the bytes. Flags: --stat, --name-only.

Parameters

  • ... string — Variadic string args: commit id, optional path, optional flags.

Returns any — Either a ShowResult table or a string (for commit:/path).

local r = world.show("abc123")
local r = world.show("--stat", "abc123")
local bytes = world.show("abc123:/foo.luau")

globals/world/stageVersion

world.stageVersion(rev: string, path: string, opts: StageOpts?) -> boolean

Make the staging area's entry for path the version revision rev holds — git reset <rev> -- <path> (zm restore --staged --source <rev>). When that version is the one the branch head holds (both lacking the path included), the path is unstaged, as the index then matches HEAD there; otherwise a revision that lacks the path stages its deletion. The working tree is untouched.

Parameters

  • rev string — Any revision world.revParse reads.
  • path string — The path to stage.
  • opts StageOpts (optional) — Optional StageOpts — stage, the staging area.

Returns boolean — true when the staging area holds an entry for the path afterwards, false when the path ended unstaged.

world.stageVersion("HEAD~1", "/source/foo.luau")

globals/world/startup_scene

world.startup_scene -> any

Returns any

globals/world/stash

world.stash(label: string?) -> StashPushResult

Stash the caller's uncommitted changes — git stash push. The caller's dirty + staged state on the session branch is saved into a stash row (label names it), and each stashed path goes back to its committed version, unstaged. Binary and large content is saved by its stored blob, so applying the stash brings it back byte-identical. A path no commit holds keeps its working content in the tree, as git leaves an untracked file, and is listed under kept. A path that cannot be taken back raises, naming it; the stash is saved either way.

Parameters

  • label string (optional) — Optional. Free-form text label for the stash.

Returns StashPushResult — A StashPushResult.

local r = world.stash("wip widget refactor")

globals/world/stashApply

world.stashApply(ref: any?) -> StashApplyResult

Apply a stash and keep it — git stash apply [<stash>]. Author-only. ref is a row id, stash@{n}, or nil for the caller's newest stash. Text content is written back and staged entries are staged again; content recorded by checksum alone is listed under skipped. Refuses, writing nothing, when a path the stash restores holds a different local change.

Parameters

  • ref any (optional) — Optional stash reference.

Returns StashApplyResult — A StashApplyResult.

local r = world.stashApply()

globals/world/stashDrop

world.stashDrop(ref: any?) -> number

Drop a stash — git stash drop [<stash>]. Author-only. ref is a row id, stash@{n}, or nil for the caller's newest stash. The dropped stash goes to trash, where world.restore brings it back under a new row id.

Parameters

  • ref any (optional) — Optional stash reference.

Returns number — The row id of the dropped stash.

world.stashDrop()

globals/world/stashPop

world.stashPop(ref: any?) -> StashApplyResult

Apply a stash and drop it — git stash pop [<stash>]. Author-only. ref is a row id, stash@{n}, or nil for the caller's newest stash. Applies as world.stashApply does; a refused apply keeps the stash. The dropped stash goes to trash.

Parameters

  • ref any (optional) — Optional stash reference.

Returns StashApplyResult — A StashApplyResult.

local r = world.stashPop()

globals/world/stashes

world.stashes() -> { StashRow }

List every stash row in the world. Anyone with read access sees every stash; the per-row author_hex makes it clear which entries the caller can pop / drop themselves.

Returns { StashRow } — Array of StashRow tables.

local rows = world.stashes()

globals/world/status

world.status -> any

Returns any

globals/world/status_text

world.status_text -> any

Returns any

globals/world/syncStatus

world.syncStatus() -> SyncStatus

Read the durable-sync status: { subscribed, content_synced, content_sync_held, world_entry_held, outgoing_synced, uploads_outstanding, uploads_in_flight, progress, pending_writes, unrecorded_writes, owed_paths, unsaved_writes, held_uploads, uploads_abandoned, conflicts, binding }. Content sync runs in two directions and each has its own answer: content_synced is incoming, true once the world's content has been applied to this client, and it says nothing about this session's own changes. outgoing_synced is outgoing, true once every /source change this session made has reached the world: nothing queued, uploading, held for another attempt, unreflected by the manifest, kept in a durable record or a conflict, or refused. Read outgoing_synced to know whether your work is saved; uploads_outstanding counts the upload jobs it still waits on, and uploads_in_flight the ones among them a flush has taken and is carrying to the world. Both booleans read false with no world subscribed. content_sync_held is true while vfs.holdContentSync(true) holds the content sync, which keeps content_synced false until released. world_entry_held is true while world.holdEntry(true) holds the world entry: a world load whose content is ready waits before the world's entrypoint runs and its startup scene loads, with binding.step reading entryHeld, until it is released. owed_paths lists the /source paths this session wrote or removed that the server has not taken yet, the same list vfs.pendingWrites() returns. conflicts maps each conflicted /source path to { base_sha, local_sha, backend_sha, isBinary, deleted }; deleted is true when the author's side is a removal of the path. unsaved_writes lists the /source paths this session wrote or removed whose change the server never took, and uploads_abandoned counts the uploads the queue stopped carrying — read those two to tell a queue working through a backlog from one that gave content up, which pending_writes alone reads the same for. held_uploads lists the owed changes whose upload failed and is being tried again, as { path, reason }: a stage or commit of other content goes ahead past them, one naming such a path waits on it and reports the reason, and removing the path ends the hold. unrecorded_writes lists the owed paths whose durable record this machine could not store, which a restart loses; pending_writes counts the owed records it did store. binding names which world holds /source — bound, unbound, binding, session_only, or unclassified before the boot has decided — and, when a load attempt is on record, which attempt is running, the step it is held at and for how long, what the last one answered, and whether the load stopped. Read binding to tell a world that is still coming from one that was never asked for: subscribed answers false for both. Synchronous.

Returns SyncStatus

local s = world.syncStatus(); print(s.outgoing_synced, s.uploads_outstanding)
local s = world.syncStatus(); print(s.pending_writes)
local s = world.syncStatus(); for _, p in ipairs(s.unsaved_writes) do print(p) end
local s = world.syncStatus(); if s.binding.awaiting_world then print(s.binding.state, s.binding.attempt) end

globals/world/trash

world.trash() -> { TrashRow }

List trash entries for the world. Anyone with read access to the world can list trash — recovery is a shared safety net, not a privacy boundary. The handle (row_id) feeds back into world.restore.

Returns { TrashRow } — Array of TrashRow tables.

local rows = world.trash()

globals/world/trashEmpty

world.trashEmpty() -> number

Permanently delete the trash entries your own operations put there — zm trash empty — in one transaction. Entries other callers' operations trashed stay restorable. An emptied entry cannot be restored.

Returns number — The number of entries deleted.

local n = world.trashEmpty()

globals/world/uninstallLibrary

world.uninstallLibrary(name: string) -> string

Delete the library marker file. name accepts "@combat" or "combat" (the leading @ is the convention carried by the on-disk path).

Parameters

  • name string — The library name, with or without the leading @.

Returns string — The marker path that was removed.

world.uninstallLibrary("@combat")

globals/world/unseal

world.unseal(commitId: string?) -> string

Return a sealed commit to the staging area it was sealed from, with its message kept, so its paths can be changed and committed again. A commit world.commit sealed lands by itself once its required derived outputs are computed; this is the way out of one that cannot land.

Parameters

  • commitId string (optional) — The sealed commit's id. Omitted, the oldest sealed commit holding a failure, else the newest.

Returns string — The id of the commit returned.

world.unseal()

globals/world/unstage

world.unstage(path: string, opts: StageOpts?) -> UnstageReport

Remove a path from the staging area. Live manifest dirty state is untouched. A path is staged in one area at a time: a path the calling area does not hold that another staging area does is taken out of that area, whichever account opened it, and named in the report; a take from an area of another account warns, naming that account, the area and its session. An installed asset is unstaged by its root path, which takes every row of the install out of the area; another row of an install raises, naming the install and its root.

Parameters

  • path string — The path to unstage.
  • opts StageOpts (optional) — Optional { stage: string? } naming the staging area the path was staged into. Omitted, the call acts on the calling caller's own area.

Returns UnstageReport — { released }: each path taken out of another staging area (stage, account, same_account, session, live). Empty when the calling area held the path.

world.unstage("/source/foo.luau")
world.unstage("/source/foo.luau", { stage = "fauna" })

globals/world/update

world.update(units: (string | { string })?, opts: UpdateOpts?) -> UpdateResult

Apply updates. With no units, every unit world.checkUpdates lists; a name or a list of names (library names and install root paths, mixed) updates those. A library moves its pin (world.updateLibrary), an installed asset pulls its new closure (world.pullAsset), one unit at a time: a unit that fails is reported in its entry and the rest still run, and an install whose local edits conflict lists the conflicted paths. Nothing is staged, committed or pushed: the updated files wait in world.status(), to_commit lists every path the updates changed or moved, and warnings names each file holding uncommitted changes that an update merged into or conflicted.

Parameters

  • units (string | { string }) (optional) — Nil, a unit name, or a list of names.
  • opts UpdateOpts (optional) — { ref? }: a branch name or commit id every unit is updated to.

Returns UpdateResult — { results, to_commit, skipped, warnings, note }: results holds one { unit, kind, from, to, ok, error?, merged?, conflicted?, added?, pruned?, moving? } per unit, skipped the units the check could not read, warnings one { unit, path, outcome = "merged" | "conflicted" } per modified file an update touched, and note says nothing was committed.

local r = world.update()
local r = world.update("@combat")
local r = world.update({ "@builtin", "/zero/source/Fire.module" })

globals/world/updateLibrary

world.updateLibrary(name: string, ref: string?) -> UpdateLibraryResult

Move an installed library to the newest commit of a ref. Resolves ref (default main) in the world the library's marker at /source/libs/@<name> names; when that is the commit the marker already holds nothing is written, even when requested_ref differs; an imported library the engine does not hold at that commit is mounted and waited for, and a mount that fails raises naming why. Otherwise the marker is rewritten with the new commit (other marker fields are kept), the call waits until the engine holds the library at that commit (@builtin: its pin table is read; another library: its content is mounted), and the reference tables of the world's own sources that name the library are derived again. Each source whose table moved is listed by world.vcsStatus with the reason references moved and is committed with the marker: a commit holding the marker and leaving one of them out is refused, naming each. Discarding the marker puts the pin and its tables back. Works for @builtin and for every library world.installLibrary wrote. A failed wait puts the old marker back, waits for the library at the old commit and raises, saying where it ended. One move per library runs at a time, and none runs while play is running or in a runtime-profile session.

Parameters

  • name string — The library name, with or without the leading @.
  • ref string (optional) — A branch name or commit id of the library's world; defaults to main.

Returns UpdateLibraryResult — { name, from, to, changed, rederived }: the commits the pin moved between, whether it moved, and the source paths whose reference table was derived again, each one to commit with the marker.

local r = world.updateLibrary("@builtin")

globals/world/vcsStatus

world.vcsStatus(opts: StageOpts?) -> StatusResult

Return the working-tree VCS status: dirty paths, staged entries, ignored paths, untracked paths, and any unmerged ones. A file whose bytes are the ones the branch head holds and whose reference table is not (a library pin moved, or an asset it references was renamed, moved or got new bytes) is listed like any change, with reason = "references moved": in staged when the area holds it, in dirty otherwise. world.add / world.add_all stage it and it is committed with the change that moved it. A table that differs only in the install record the engine writes (an installed asset marked as the root of its install) is listed the same way with reason = "install recorded". An untracked path is one with no committed version behind it, and it appears in dirty as well — git add . picks up new files too. Named vcsStatus (not status) because world.status() is the runtime-snapshot accessor owned by world_status.module; the source-control surface keeps its own VCS-specific name so the two never shadow each other. Each dirty[i].dirtied_by is the identity the row was written under, which every session of one account shares; dirtied_by_caller is the stable caller key of the call that wrote it, dirtied_by_name the name that caller goes by on the world's roster now, and writer says what the row is to the calling caller: "you", "other" (a caller whose session is open), "ended" (a caller whose session has ended) or "unattributed". dirty_since_micros is the microsecond timestamp of the first write of the current dirty run. dirtied_by_account is the account the row's writer acts for and same_account whether that is the calling caller's account. local_identity is this connection's identity and local_caller the calling caller's own stable key, local_name the name it goes by now, local_account the account it acts for. claimed_by_other_stages names the paths some other staging area holds and who holds each: the area, its owner_account, whether that is the caller's (same_account), the session holding it and whether that session is open now (live). Staging is advisory: a staging call that stages one of them takes it out of that area. writer_accounts_refusal is set when the world's backend module predates the record of writers' accounts. stage names the area staged is the set of, so a caller reads which name reaches its own staged rows.

Parameters

  • opts StageOpts (optional) — Optional { stage: string? } naming the staging area whose staged set to report. Omitted, the read reports the calling caller's own area. The dirty, ignored and untracked sets are the world's working tree and read the same whichever area is named.

Returns StatusResult — A StatusResult table.

local s = world.vcsStatus()
local s = world.vcsStatus({ stage = "fauna" })

typed/builtin//modules/world_sync/world/dropOwedWrites

world.dropOwedWrites(path: string?) -> { string }

Give up the changes this session still owes the world at path and beneath it: the writes and removals vfs.pendingWrites() lists there (a write the world never took included), the ones world.syncStatus() names as conflicts or losses, and every stored record of them on this machine, so no later boot re-applies one. Each path then reads as the world's row holds it: the row's content, or nothing where the world has no row. Returns the paths given up. The bytes of a given-up change are not kept.

Parameters

  • path string (optional) — Canonical /source path; defaults to the whole source tree.

Returns { string } — The paths given up, sorted.

world.dropOwedWrites()
world.dropOwedWrites("/zero/source/hud")

typed/builtin//modules/world_sync/world/resolveConflict

world.resolveConflict(path: string, mode: string, content: string?) -> ResolveResult

Resolve one /source record this session owes the world, one path at a time: a conflicted one, or one whose write or removal the server refused or the queue gave up on (unsaved_writes in world.syncStatus()), which waiting never carries and which holds a world.swap or world.checkout away from the world until it is settled here. mode is merge | apply | take-local | take-backend | discard. merge returns { merged, clean } and mutates nothing — a clean merge can be finalized with apply, and a conflicted one carries <<<<<<< / ======= / >>>>>>> markers to edit first. apply writes the finalized content to /source; take-local writes the retained local bytes; take-backend / discard keep the backend head. For a removal (deleted in world.syncStatus().conflicts), take-local removes the path again and merge errors, since a removal holds no content to merge. Errors when the record is absent, a merge has no common ancestor or hits binary content, or a write fails.

Parameters

  • path string — Canonical /source path of the conflicted record.
  • mode string — One of merge | apply | take-local | take-backend | discard.
  • content string (optional) — Finalized bytes for apply mode.

Returns ResolveResult

world.resolveConflict("/zero/source/foo.luau", "take-local")
local r = world.resolveConflict(p, "merge"); if r.clean then world.resolveConflict(p, "apply", r.merged) end

typed/builtin//modules/world_sync/world/syncStatus

world.syncStatus() -> SyncStatus

Read the durable-sync status: { subscribed, content_synced, content_sync_held, world_entry_held, outgoing_synced, uploads_outstanding, uploads_in_flight, progress, pending_writes, unrecorded_writes, owed_paths, unsaved_writes, held_uploads, uploads_abandoned, conflicts, binding }. Content sync runs in two directions and each has its own answer: content_synced is incoming, true once the world's content has been applied to this client, and it says nothing about this session's own changes. outgoing_synced is outgoing, true once every /source change this session made has reached the world: nothing queued, uploading, held for another attempt, unreflected by the manifest, kept in a durable record or a conflict, or refused. Read outgoing_synced to know whether your work is saved; uploads_outstanding counts the upload jobs it still waits on, and uploads_in_flight the ones among them a flush has taken and is carrying to the world. Both booleans read false with no world subscribed. content_sync_held is true while vfs.holdContentSync(true) holds the content sync, which keeps content_synced false until released. world_entry_held is true while world.holdEntry(true) holds the world entry: a world load whose content is ready waits before the world's entrypoint runs and its startup scene loads, with binding.step reading entryHeld, until it is released. owed_paths lists the /source paths this session wrote or removed that the server has not taken yet, the same list vfs.pendingWrites() returns. conflicts maps each conflicted /source path to { base_sha, local_sha, backend_sha, isBinary, deleted }; deleted is true when the author's side is a removal of the path. unsaved_writes lists the /source paths this session wrote or removed whose change the server never took, and uploads_abandoned counts the uploads the queue stopped carrying — read those two to tell a queue working through a backlog from one that gave content up, which pending_writes alone reads the same for. held_uploads lists the owed changes whose upload failed and is being tried again, as { path, reason }: a stage or commit of other content goes ahead past them, one naming such a path waits on it and reports the reason, and removing the path ends the hold. unrecorded_writes lists the owed paths whose durable record this machine could not store, which a restart loses; pending_writes counts the owed records it did store. binding names which world holds /source — bound, unbound, binding, session_only, or unclassified before the boot has decided — and, when a load attempt is on record, which attempt is running, the step it is held at and for how long, what the last one answered, and whether the load stopped. Read binding to tell a world that is still coming from one that was never asked for: subscribed answers false for both. Synchronous.

Returns SyncStatus

local s = world.syncStatus(); print(s.outgoing_synced, s.uploads_outstanding)
local s = world.syncStatus(); print(s.pending_writes)
local s = world.syncStatus(); for _, p in ipairs(s.unsaved_writes) do print(p) end
local s = world.syncStatus(); if s.binding.awaiting_world then print(s.binding.state, s.binding.attempt) end

typed/builtin//modules/world_vcs/world/add

world.add(paths: string | { string }, opts: AddOpts?) -> AddReport

Stage one path's manifest row, or each of a list of paths, expanding to the full asset family if a path lives inside a composite asset. Idempotent at (stage, manifest_row). A list stages every row its paths select once, so naming several files of one asset stages that asset's family once, as git add a b c does. Pass { force = true } to bypass the .zmignore / .gitignore gate — same intent as git add -f. Without force, attempts to stage an ignored path (or a path whose .refs points at an ignored dep) error. { stage = "<name>" } stages into the named area, shared with whoever else names it, instead of the caller's own, so a commit naming that area freezes these paths and leaves every other area's staged. Staging is advisory, and a path is staged in one area at a time. Naming a path another staging area holds takes it from that area, whichever account opened it and whether or not its session is open, and the report names the area, its account and whether that session is working now in released. A take from an area of another account warns, naming that account, the area and its session; a take from another agent or engine of the caller's own account does not. A staged path a caller of another account is writing now is named in written and warned about the same way. A staged source that references an asset whose current version no commit holds needs that version committed with it. The asset is staged beside the source, taken from any other area holding it, and named in carried. A .scene stages as its files on disk hold it, in edit and in play alike; live entity edits nobody saved stay in the session, and a notice names them with the calls that keep or drop them. An installed asset is staged by its root path: every row of the install (its files, its dependencies under /zero/source/deps/) is staged with it, as world.vcsStatus().installs lists it. A path that is one of the other rows of an install raises, naming the install and its root. A file world.vcsStatus lists with the reason references moved stages like any change: its row carries its live reference table and its bytes are the ones the branch head already holds.

Parameters

  • paths string | { string } — The path to stage, or a non-empty list of paths. Each must be a non-empty string; a gate refusing any of them refuses the call before anything is staged. { renormalize = true } first restates every folder asset's row at or beneath the named paths from the files beneath it, as git add --renormalize does: a folder whose stated version is not the folder hash of its files is staged at the hash its files hold, and a folder that already states it is left out.
  • opts AddOpts (optional) — Optional { force: boolean?, renormalize: boolean?, stage: string? }. Defaults to { force = false } on the calling caller's own staging area.

Returns AddReport — What the call staged: staged lists the rows the paths name (the asset family of a path inside an asset; otherwise the path and every changed row beneath it, deletions included, so a deleted asset folder stages the deletion of each of its files), leaving out with a warning naming it each path no row at or beneath which holds a change, carried the referenced assets staged beside them because no commit holds their current version, released each path taken from another staging area (stage, its account, same_account, its session and whether that session is live), and written each staged path a caller of another account is writing now (by, account). Each carried row, and each released and written row of another account, is also written to the caller as a warning.

world.add("/source/foo.luau")
world.add({ "/source/a.luau", "/source/b.luau" })
world.add("/source/scene_dirty/entities/42.json", { force = true })
world.add("/source/fauna.module", { stage = "fauna" })
world.add("/source/fauna.module", { renormalize = true })

typed/builtin//modules/world_vcs/world/add_all

world.add_all(opts: AddAllOpts?) -> ({ string }, AddAllReport)

Stage every dirty manifest row, whoever wrote it, skipping any path that matches .zmignore / .gitignore. Paths that match an ignore pattern are skipped; world.add(path, { force = true }) is the explicit way to override the gate for an individual path. Rows still flagged conflicted by world.pullAsset are held back too: resolve them (edit + world.add(path), or world.resolvePullConflict) and re-run. Staging is advisory. The working tree is one per branch and staging areas are not, and a path is staged in one area at a time: a path another staging area holds is taken from that area, whichever account opened it and whether or not its session is open, and named in released with the area, its account, its session and whether that session is live. A row another caller is writing is staged like any other. The calling account's own agents and engines never warn each other: their areas and their writes are the account's own. A take from another account's area warns once per area, naming the account, the area and its session, and saying that account is working on the paths now when its session is open; a staged path a caller of another account is writing now warns once per caller and is named in written. Each staged path brings its asset family. A file world.vcsStatus lists with the reason references moved is staged too. The call reads the working tree a fixed number of times and stages and releases in batches, so its cost follows the size of the dirty set. A scene stages as its files on disk hold it, in edit and in play alike: live entity edits nobody saved stay in the session, and a notice names each scene holding some, with the calls that write them into scene.json (sceneAuthoring acceptChanges, layers.active:save()) or drop them.

Parameters

  • opts AddAllOpts (optional) — Optional { stage: string? }. stage names the staging area to stage into; omitted, the call stages into the calling caller's own area.

Returns ({ string }, AddAllReport) — The paths that were held back: the conflicted ones. Empty when none were. The second value reports the call in full: staged lists the paths it added to the staging area, held lists every held-back path with its reason ("conflicted"), released lists each path taken from another staging area (stage, account, same_account, session, live), and written each staged path a caller of another account is writing now (by, account).

world.add_all()
world.add_all({ stage = "fauna" })
local held, report = world.add_all()

typed/builtin//modules/world_vcs/world/affirm

world.affirm(token: string)

Consume an XXX-XXX-XXX-style affirmation token returned by a destructive op surface (e.g. vfs.remove). The destruction commits atomically with the pending-row delete; the token is one-shot. Errors verbatim on expiry / wrong-user.

Parameters

  • token string — The affirmation token.
world.affirm("ABC-DEF-GHI")

typed/builtin//modules/world_vcs/world/assetInstallable

world.assetInstallable(opts: AssetInstallableOpts) -> AssetInstallableReport

Report whether one published asset can be installed on its own, without raising. world.previewInstall plans a real install and raises when the closure will not resolve; this answers the prior question — does this guid name something ZeroMind will hand over by itself — as a verdict a caller can branch on. Content that ships inside a larger library (a module inside a package, a material inside a system) carries its own published identity, so the answer is per asset rather than per library. Reads only.

Parameters

  • opts AssetInstallableOpts — { guid } names the asset; ref pins a commit-id instead of the latest.

Returns AssetInstallableReport — An AssetInstallableReport — installable, a verdict, a one-line detail, and the closure's nodes / tree_children / deps shape when one resolved.

world.assetInstallable({ guid = asset.guid("@builtin::materials.neon") })

typed/builtin//modules/world_vcs/world/awaitOutgoingSync

world.awaitOutgoingSync()

Wait until every /source write this session made has reached the branch it was written against. Raises naming the paths that did not land. add / commit / push / checkout / merge / pull already wait on their own; call this before rebinding after a burst of writes, which world.checkout and world.swap refuse over.

Returns Nothing. Raises when a write did not land.

world.awaitOutgoingSync() ; world.checkout("main")

typed/builtin//modules/world_vcs/world/beforeCommit

world.beforeCommit(name: string, fn: ((any) -> ())?)

Open-or-resume a staging area, set the message, and materialise the commit. Commits ONLY what's already staged via world.add / world.add_all — git semantics, not git commit -a. The reducer auto-deletes the stage row on success so a subsequent world.commit opens a fresh one.

Where derive hooks compute outputs for the staged content, the commit is sealed under its id first and lands once every required output is computed. The id this returns can name a commit that has not landed yet: world.vcsStatus().sealed lists the sealed commits with what each waits on, and world.unseal returns one to the staging area. When an older sealed commit holds a refusal, this commit queues behind it and the call says so.

{ stage = "<name>" } materialises one of the caller's own staging areas, so the commit carries the paths staged under that name and leaves every other caller's staged. When a commit from another caller has landed since the area was opened, this brings the area onto the branch head there is now and commits it there.

A staged scene is frozen as staged. Live entity edits its overlay holds that nobody saved stay in the session, and a notice names them once the commit lands.

Pre-flight .zmignore refs gate: every staged source's aggregated deps (via asset.deps, which recurses composite asset folders) are checked against the live ignore set. If any dep target's path is currently ignored AND the dep target is not itself in the stage, the commit is refused. This mirrors the closure invariant — a commit whose deps can't resolve cleanly shouldn't land. Force-staging the dep alongside (world.add(dep, { force = true })) makes the ignored dep satisfy the gate. Run fn at the start of every world.commit, before the commit reads what is staged — the moment to write content that belongs in the commit being made, and stage it. fn receives { stage }, the staging area the commit materialises (nil for the caller's own), to pass to world.add. Registering a name again replaces what it held, and fn = nil removes it. A hook that raises refuses the commit, naming the hook.

Parameters

  • name string — What identifies the hook.
  • fn ((any) -> ()) (optional) — The hook, or nil to remove it.

Returns The newly-allocated commit id (ULID string).

local id = world.commit("feat: ship widget")
local id = world.commit("fauna: the swallow colony", { stage = "fauna" })
world.beforeCommit("procgen.diskcache", function(ctx) ... end)

typed/builtin//modules/world_vcs/world/branches

world.branches() -> { { branch: string, commit_id: string, current: boolean } }

Every branch this world has, with the commit each one's head names and which one this session is on — git branch --list. Sorted by name. A branch exists for everyone in the world; which one you are on is yours alone, so current is true for at most one row here and says nothing about where anybody else is.

Returns { { branch: string, commit_id: string, current: boolean } } — Array of { branch, commit_id, current }.

for _, b in ipairs(world.branches()) do print(b.branch, b.commit_id) end

typed/builtin//modules/world_vcs/world/checkUpdates

world.checkUpdates() -> ({ UpdateReport }, { UpdateSkip })

Discover which units of this world have an update available. Read-only — makes no local mutation and no VCS write. A unit is one of:

  • a library: a marker (/source/libs/@<name>, @builtin included) whose requested_ref (default main) now resolves in the library's world to a commit other than the marker's resolved_commit. Apply with world.updateLibrary(name).
  • an installed asset: an install root (the asset that was installed; its reference table marks it) whose recorded version differs from the newest version of that asset at its origin, one lookup per root. A dependency of an install is never compared or reported: only the root is. Apply with world.update(<root path>) (zeromind.update), which installs the root's new closure with each dependency at the version the new root records.

Installed assets whose origin world is a library's world are part of that library's unit. A pulled row that no table marks as an install root (an install older than the marks, content forked from another world) is not reported.

Returns ({ UpdateReport }, { UpdateSkip }) — Two values: an array of UpdateReport (libraries first), each { kind = "library", library, from, to } or { kind = "asset", root_path, root_guid, display_name, from, to }, empty when everything is current; and an array of { name, reason } for the units whose origin could not be reached, which never fail the whole call.

local reports, skipped = world.checkUpdates()

typed/builtin//modules/world_vcs/world/checkout

world.checkout(branch: string) -> string

Switch this session to another branch — git checkout <branch>. The branch must already exist (create one with world.createBranch). The tree is replaced by the branch's own content. Which branch this session is on is this session's alone; the branch itself is shared, so others may be on the one you move onto. Your uncommitted changes and staged entries come with you, as git keeps them across a switch: each lands on branch as it read before. When the switch would replace a path you changed with a different version, the call refuses with git's message naming the paths and nothing moves — commit or stash them first. Another caller's uncommitted changes stay on the branch they were made on. The switch is all or nothing: a write of yours still on its way to the branch you leave is waited for, and one that does not arrive refuses the switch before anything moves; a switch refused after your changes moved puts them back, and names each path that does not read as it did. Returns only once the branch's content has landed, so world.head, world.log, world.commit and the VFS all target the new branch immediately afterwards.

Parameters

  • branch string — The branch to switch to.

Returns string — The branch now checked out.

world.checkout("feature")

typed/builtin//modules/world_vcs/world/checkoutPaths

world.checkoutPaths(rev: string, paths: { string }, opts: CheckoutPathsOpts?) -> { string }

Write paths as a revision holds them, and stage them: git checkout <rev> -- <path>.... rev is any revision world.revParse reads. Each path comes back as the asset the revision records: its asset guid, its bytes and its kind, so what refers to it by guid keeps resolving, and restoring it again gives the same asset again. A folder, a composite asset's or a plain directory, brings back every file the revision holds beneath it, so a deleted component, module or package comes back whole; a file beneath it the revision does not hold stays. A file whose folder asset was deleted brings the folder back with it. Binary files come back by their content. A path whose version is the branch head's reads clean afterwards and leaves the staging area; any other is staged. The paths that name nothing the revision holds are refused together, naming each, and nothing is written. Returns once the working copy holds every written path as the revision records it.

Parameters

  • rev string — The revision to read.
  • paths { string } — The paths to write: files, composite asset folders or directories.
  • opts CheckoutPathsOpts (optional) — Optional CheckoutPathsOpts: stage = false writes the working tree and leaves the staging area as it was (git restore --source <rev> -- <path>).

Returns { string } — The paths written, as the revision names them.

world.checkoutPaths("HEAD~1", { "/source/foo.luau" })
world.checkoutPaths("HEAD", { "/source/Doom.component", "/source/notes.md" })
world.checkoutPaths("HEAD~2", { "/source/levels" }, { stage = false })

typed/builtin//modules/world_vcs/world/checkoutSide

world.checkoutSide(path: string, side: string)

Take one side of an unmerged path — git checkout --ours|--theirs <path>. The path holds the side's whole content afterwards; a merge conflict's side is the session branch (ours) or the branch merged in (theirs), an installed asset's update conflict's side is your local version (ours) or the upstream one (theirs). Record the resolution with world.add and commit.

A merge conflict's side is that branch's committed version: its bytes, its asset guid, its kind and its references, so what refers to the asset by guid keeps resolving. Text, binary and composite assets alike: a composite asset (a .scene, .package, .component … folder) is taken whole — name its folder and the folder and every file in it come from that side. A side that deleted the path leaves it a pending delete. Taking a side again replaces the earlier choice.

Parameters

  • path string — The unmerged path, or the composite asset folder holding one.
  • side string — ours or theirs.
world.checkoutSide("/source/foo.luau", "theirs")

typed/builtin//modules/world_vcs/world/commit

world.commit(message: string, opts: CommitOpts?) -> string

Parameters

  • message string
  • opts CommitOpts (optional)

Returns string

typed/builtin//modules/world_vcs/world/conflicts

world.conflicts() -> { ConflictEntry }

List every locally-pulled row currently flagged conflicted — the findable surface world.pullAsset leaves behind on an unresolved merge. Read-only.

Returns { ConflictEntry } — An array of ConflictEntry, one per conflicted row. Empty when nothing is conflicted.

local list = world.conflicts()

typed/builtin//modules/world_vcs/world/contentRequirements

world.contentRequirements(scope: { string }?) -> { ContentRequirement }

List the world's unmet content requirements: user-authored assets whose type-declared content constraints are not yet satisfied (an empty README, a .metadata with no description or tags — the empty-skeleton state a fresh create emits for the author to fill, and a description the type asks for that still starts with the [[describe]] placeholder, which describes = true marks on the row; world.push asks for a description only of the assets the push changes). The same walk world.push gates on, over the whole world: the push refuses over the rows whose asset the calling staging area holds or committed, and publishes the rest with the shortfall recorded on the asset that carries it. world.publishBlockers reports the caller's own share as the content class beside the other two. Empty list = every checked asset meets its type's contract. A write into an asset reports the rows its type's type.yaml requirements give for that asset as it lands: contentRequirements on the vfs.write report and on the MCP write_file / edit_file result.

Parameters

  • scope { string } (optional) — Asset paths to restrict the check to — pass a status read's dirty + staged paths to check only content that would actually publish (a per-file path matches its containing asset; each path resolves directly, with no world enumeration). Omit for the full-world walk the push gate performs.

Returns { ContentRequirement } — Array of ContentRequirement rows — { asset, typeName, detail, path, describes? }, where path is the asset the row is about.

for _, r in ipairs(world.contentRequirements()) do print(r.asset, r.detail) end

typed/builtin//modules/world_vcs/world/contribute

world.contribute(opts: ContributeOpts?) -> { ContributeOutcome }

Send improvements to installed content back upstream — git subtree push ending in a pull request. For each targeted origin world: the diverging subtree is remapped to the origin's canonical paths, three-way merged against the origin's CURRENT content (regions the origin also changed become local conflicts to resolve first), pushed as a contrib/<id> branch in the origin world, and opened as a pull request there. With merge (the default) the pull request is merged immediately when authorized — a refusal leaves it open and reported, never a failure. After a merge, the local fork re-pulls so its origin pins advance and the asset no longer reads as ahead.

Parameters

  • opts ContributeOpts (optional) — Optional ContributeOpts — targets (origin world guids; default all ahead), roots (install roots; default every installed asset ahead), merge (default true), title, description, dryRun.

Returns { ContributeOutcome } — Array of ContributeOutcome, one per targeted origin.

local r = world.contribute({})

typed/builtin//modules/world_vcs/world/convertInstalls

world.convertInstalls(opts: { only: string?, dryRun: boolean? }?) -> { { root_path: string, record: string, rows_removed: number, overrides_kept: number, files_added: number, completion: string? } }

Bring installs that stand as pulled rows (an earlier engine landed each file as a row of the world) into one record row each, the form world.installAsset writes now. The record is built from the rows themselves, written, and once this engine has materialized the install from it the unchanged rows are taken out of the world; a file changed since the install stays a row of the world. Nothing is committed: rows that were committed leave as deletions in the next commit. Returns one entry per install, and with dryRun writes nothing. Once the rows are taken out the record is stated again from the published version it names, so an install the old engine cut short holds every file of the version: files_added counts the files that added, and completion says why the record stayed as the rows stated it (the published version unreachable, or an asset of it that does not load as its type). The files stay where they stand and the call yields between its steps, so it never holds a frame: a run that outlasts the execute budget continues as a task.

Parameters

  • opts { only: string?, dryRun: boolean? } (optional) — { only?: string, dryRun?: boolean }: only names one install by its root path.

Returns { { root_path: string, record: string, rows_removed: number, overrides_kept: number, files_added: number, completion: string? } } — { { root_path, record, rows_removed, overrides_kept, files_added, completion? } }.

world.convertInstalls({ dryRun = true })

typed/builtin//modules/world_vcs/world/createBranch

world.createBranch(name: string, fromCommit: string?, opts: { force: boolean? }?)

Create a branch — git branch <name> [<start>]. The branch starts at fromCommit (defaults to the session branch's HEAD) and gets its own working tree, materialized from that commit. The session stays on its current branch; move onto it with world.checkout("<branch>") (git checkout). With { force = true } (git branch -f) an existing branch is pointed at fromCommit and its working tree rewritten to it, in one transaction; that refuses only when the branch's working tree holds uncommitted changes, which the rewrite would lose.

Parameters

  • name string — The new branch name.
  • fromCommit string (optional) — Commit id to start at. Defaults to world.head().
  • opts { force: boolean? } (optional) — Optional { force: boolean? }.
world.createBranch("feature")
world.createBranch("feature", world.revParse("HEAD~1"), { force = true })

typed/builtin//modules/world_vcs/world/deleteBranch

world.deleteBranch(branch: string, opts: DeleteBranchOpts?)

Delete a branch — git branch -d|-D <name>. Drops the branch and the working tree it owns; its commits are left alone, since deleting a branch is dropping the name and the tree under it, not rewriting history. Without opts.force (-d) it refuses a branch whose commits the session's branch does not hold, and one whose working tree holds uncommitted changes, naming them, since those go with the tree; { force = true } (-D) deletes it regardless. Refuses the branch this session is on (check out another first) and the world's last branch.

Parameters

  • branch string — The branch to delete.
  • opts DeleteBranchOpts (optional) — Optional DeleteBranchOpts — force.
world.deleteBranch("feature")
world.deleteBranch("experiment", { force = true })

typed/builtin//modules/world_vcs/world/derive

world.derive(name: string, spec: any?)

Register a computation whose output lands in the commit with the content it is computed from. The hook (scope = "asset") computes output inside each staged asset matches accepts, from the asset as staged. Its fingerprint and compute are entries of module, run on a worker. Staging starts the work, and a required output that is not computed holds the commit that carries it: world.commit seals the commit and returns its id at once, and the commit lands with the outputs once they are computed. When every required output is computed already, world.commit returns after the commit landed. spec = nil removes it.

Parameters

  • name string — What identifies the hook.
  • spec any (optional) — The hook, or nil to remove it.
world.derive("thumbs", { scope = "asset", output = "thumb.png", matches = function(ref) return ref.type == "texture" end, module = "@root::thumbs", fingerprint = "fingerprint", compute = "compute" })

typed/builtin//modules/world_vcs/world/diff

world.diff(...: string) -> any

Mirror git diff's CLI arg shape. Returns per-file diffs by default; pass --stat for summary stats, --name-only for just paths. Positional commit ids drive the two sources; --staged pivots to staged-vs-HEAD. ---separated args scope the diff to specific paths. --stage=<name> reads the named staging area in place of the calling caller's own.

Parameters

  • ... string — Variadic string args: flags, commit ids, --, path filters.

Returns any — Array of DiffFile tables (or string-list for --name-only).

local files = world.diff()
local files = world.diff("--staged")
local files = world.diff("abc", "def")
local names = world.diff("--name-only")
local files = world.diff("--staged", "--stage=fauna")

typed/builtin//modules/world_vcs/world/diffUpstream

world.diffUpstream(root: string) -> { UpdateFile }

What your copy of an installed asset changed against the version it was installed from — the diff zm diff <root> upstream prints. One UpdateFile per file under root whose content differs from its origin version, with the unified diff from the origin version to yours for a text file (diff and hunks are nil for a binary one). Empty when the installed asset holds no change of yours.

Parameters

  • root string — The installed asset's root path (or any path inside it).

Returns { UpdateFile } — Array of UpdateFile, sorted by path.

for _, f in ipairs(world.diffUpstream("/source/Doom.component")) do print(f.diff) end

typed/builtin//modules/world_vcs/world/discard

world.discard(opts: StageOpts?)

Drop a staging area without committing. Live manifest dirty flags are preserved so the user can re-stage later. No-op if the area doesn't exist.

Parameters

  • opts StageOpts (optional) — Optional { stage: string? } naming the staging area to drop. Omitted, the call drops the calling caller's own area.
world.discard()
world.discard({ stage = "fauna" })

typed/builtin//modules/world_vcs/world/discardAll

world.discardAll(scope: string?) -> DiscardAllResult

Discard every change under a path in one operation — the whole working tree by default. Every file edited since its commit goes back to the committed version, every deletion comes back, and every file no commit holds, an uncommitted install included, leaves the tree. Each goes to trash first, where world.restore(<handle>) brings it back. The paths a staging area holds stay, a staged file edited since it was staged goes back to its staged copy, and a change another account is making and a dependency another install records stay as well: they are named in kept. One backend operation takes the whole scope back, and the call returns once the working copy reads as the committed content.

Parameters

  • scope string (optional) — The VFS path to discard beneath; omitted, every path.

Returns DiscardAllResult — { discarded, paths, kept } — how many rows went back, which, and which rows under the scope stayed because another account or another install holds them.

world.discardAll()
world.discardAll("/source/hud")

typed/builtin//modules/world_vcs/world/discardFile

world.discardFile(path: string)

Discard a path's unstaged working edits, taking it back to what it was staged or committed as — the git restore <path> shape. A folder takes every file beneath it back with it, and a deleted path comes back as its committed version. The stage is the baseline when the path is staged, the last commit when it is not, and where it is neither there is nothing to come back to, so the path goes away. Staging is left exactly as it was; world.unstage is the verb that changes it. One shot: edited bytes that revert to a committed version are snapshotted to trash first, so that case is recoverable via world.restore(<handle>); a deletion taken back throws nothing away and leaves no trash entry. Returns once the working copy reads as the reverted content: text reads back as committed, and binary bytes are served at the committed version on the next read. Errors when nothing at or beneath the path is dirty (nothing to discard). An installed asset is discarded by its root path, which puts the whole install back at the version the branch head holds as one unit: its rows leave the staging area and then return to their committed version, or go away when no commit holds them. A dependency another install records too stays. Another row of an install raises, naming the install and its root. A file whose only difference from the head is its reference table (listed with the reason references moved) raises too, naming what moved its references and the undo: moving the library pin back (world.updateLibrary, or discarding the pin marker) or reverting the moved asset.

Parameters

  • path string — The VFS path whose unstaged edits to discard.
world.discardFile("/source/foo.luau")
world.discardFile("/source/Doom.component")

typed/builtin//modules/world_vcs/world/dropDerived

world.dropDerived(outputPath: string, opts: { stage: string? }?) -> number

Leave the derived output at outputPath out of the commit. Only an output whose hook is best-effort can be dropped; a required one is refused, naming the other ways out (retry, provide, unseal).

Parameters

  • outputPath string — The output's VFS path.
  • opts { stage: string? } (optional) — Optional { stage: string? } naming the staging area.

Returns number — How many reservations were dropped.

world.dropDerived("/zero/source/gold.material/extra.json")

typed/builtin//modules/world_vcs/world/fetch

world.fetch(branch: string?) -> FetchResult

Update the origin/<branch> remote-tracking ref — git fetch. Mirrors the world's ZeroMind branch head into the local commit history (no working-tree change) and reports how the session branch relates to it: behind origin commits to pull, ahead local commits to push, diverged when both. A stale installed pin or out-of-band ZeroMind change shows up here as behind — reconcile with world.pull().

Parameters

  • branch string (optional) — Remote branch to fetch. Defaults to the session branch.

Returns FetchResult — A FetchResult table.

local f = world.fetch()

typed/builtin//modules/world_vcs/world/forkLive

world.forkLive(opts: { source: string, sourceBranch: string?, maxBatches: number? }) -> number

Seed THIS (empty) world's live content from another world by copying its whole manifest as clean Pulled rows — the in-engine half of "fork a world". Provenance is preserved: each row points at the content's ORIGINAL owner (a fork of a fork-of-A still points at A), so the fork never claims to have authored what it pulled. The copy runs server-side in bounded batches (idempotent + resumable), looping until the source is fully mirrored. Pair with world.add_all() + world.commit() + world.push() to publish the fork.

Parameters

  • opts { source: string, sourceBranch: string?, maxBatches: number? } — { source, sourceBranch?, maxBatches? } — source is the source world GUID; branches default to "main"; maxBatches caps the batch loop (default 60 ⇒ up to ~120k entries).

Returns number — The number of pulled (dirty) rows now staged-pending on the fork.

world.forkLive({ source = "e89aa92e-4c1f-460e-acd8-73859dd3a346" })

typed/builtin//modules/world_vcs/world/forkStatus

world.forkStatus() -> { ForkStatus }

Per-asset "ahead of origin" report — the fork analogue of git status against an upstream. Every installed (pulled) row whose content diverges from its pinned origin is listed, partitioned by the TRUE origin world it was pulled from (nested dependencies carry the world that authored them, not the intermediary they arrived through). This is information for judgment: decide whether a change belongs upstream, then world.contribute.

Returns { ForkStatus } — Array of ForkStatus partitions.

for _, f in ipairs(world.forkStatus()) do print(f.origin_world, #f.entries) end

typed/builtin//modules/world_vcs/world/head

world.head() -> string?

Return the current branch HEAD commit id, or nil if the branch has no commits yet.

Returns string? — The commit id string, or nil.

local id = world.head()

typed/builtin//modules/world_vcs/world/installAsset

world.installAsset(opts: InstallAssetOpts) -> InstallAssetResult

Install a published asset into this world, pulling the asset and every dependency it closes over and writing them into the source tree. The world holds ONE row for the install, its record <dest>.pull, which lists every file of the version and every dependency placed beside it; every engine that holds the row materializes the install from it, so a file of the install is a row of the world only once it is changed. Returns once this engine has materialized the install and it runs. Installing an asset where the world already holds it is safe to repeat: rows at this version are left as they are, and rows of an earlier version are moved to this one. The root lands at at, else where the world already installed it, else at /source/<name>; a copy of it the world holds at this version anywhere else (another install's dependency under /source/deps/) moves there, so the world holds the asset once and everything that depends on it resolves to the moved copy. A path holding another asset, or local edits to this one, refuses the install. An install that does not materialize takes its record out again, so the world holds nothing of it and the same install can simply be run again.

Parameters

  • opts InstallAssetOpts — { guid } names the root asset to install; ref pins a specific commit-id instead of the latest.

Returns InstallAssetResult — { assets_written, blobs_downloaded, root_guid, root_path, root_version, deps, pending, problems, outcome, moved_from, record, files, timings }. record is the path of the install's row, files the number of files and folders it states, timings the seconds each step took. outcome is installed, updated or already_present; moved_from names the copy the root moved from, nil when it moved from nowhere. blobs_downloaded counts the blobs the install fetched (bytes already cached cost none); pending lists the written paths still unreadable when the wait for them stalled, and is empty when every one reads back; problems names each reference site in the installed content that resolved to nothing in the world it was published from, and each file a folder version the install wrote holds at its version that ZeroMind gave no entry for, and is empty when every site resolves and every written folder holds all of its files. A folder version lands holding every file its version states: a file the closure leaves out of it is read from that version of the folder.

local r = world.installAsset({ guid = assetGuid })

typed/builtin//modules/world_vcs/world/installLibrary

world.installLibrary(opts: InstallLibraryOpts) -> InstallLibraryResult

Declarative cross-world dependency. Writes a single marker file at /source/libs/@<name> whose body is the zero/world-import/v1 JSON. The next commit ships it as one regular manifest entry; ZM's import-derivation pass at finalize-time decodes the marker and stamps the new commit's imports[]. Unmodified library content never ships in the importing world's tree: the engine fetches the pinned commit from ZeroMind and mounts it as @<name>::<path>. The call returns once the library is mounted (require("@<name>::…") resolves and library.list() lists it), and raises naming why when it does not mount, leaving the marker as it was before the call. A marker already there whose library is mounted is refused (move it with world.updateLibrary); one whose library never mounted is replaced, so running the install again repairs it.

Parameters

  • opts InstallLibraryOpts — See InstallLibraryOpts. opts.world is the upstream world's guid (required). opts.commit is the upstream commit_id to pin (optional; resolves opts.ref or main if omitted). opts.as is the local library name (defaults to the upstream world's slug). opts.ref is the human-meaningful ref recorded in the marker.

Returns InstallLibraryResult summarising the install.

world.installLibrary({ world = "guid", as = "combat" })

typed/builtin//modules/world_vcs/world/installedAssets

world.installedAssets() -> { InstalledAsset }

Every asset this world carries from ZeroMind, keyed by the published guid it was pulled from. The read that answers "what is actually in this world" by identity rather than by path — an installed asset's local name can be chosen by the installer, so a path is not the thing to check a pull against.

Returns { InstalledAsset } — An array of InstalledAsset sorted by local path, one per pulled row carrying a published guid.

for _, a in ipairs(world.installedAssets()) do print(a.asset_guid, a.path) end

typed/builtin//modules/world_vcs/world/installedLibraries

world.installedLibraries() -> { LibraryMarker }

The libraries this world has installed: one row per library marker under /zero/source/libs, @builtin among them, sorted by name.

Returns { LibraryMarker } — { name, world_guid, requested_ref?, resolved_commit } per library: its name with the @, the world it comes from, the ref that was asked for and the commit it is pinned at.

for _, lib in ipairs(world.installedLibraries()) do print(lib.name, lib.resolved_commit) end

typed/builtin//modules/world_vcs/world/installs

world.installs(opts: { rows: boolean? }?) -> { InstallInfo }

The installs this world holds, one entry each, whichever form each is stored in: record (one row, every engine materializes the files from it) or rows (an earlier engine landed one row per file, and world.convertInstalls brings it into one record). An asset installed at more than one place names the other places in also_at.

Parameters

  • opts { rows: boolean? } (optional) — { rows: boolean? }: rows = false leaves the row installs out, which reads no row of the world.

Returns { InstallInfo } — An array of InstallInfo sorted by path.

for _, i in ipairs(world.installs()) do print(i.root_path, i.form, i.files) end

typed/builtin//modules/world_vcs/world/list

world.list() -> { WorldEntry }

List every world the authenticated user has access to. Calls the spacetime list_my_worlds procedure which wraps ZeroMind's GET /v1/me/worlds. Flattens each entry to one record per world with the role promoted to a top-level field.

Returns { WorldEntry } — Array of WorldEntry records.

local worlds = world.list()

typed/builtin//modules/world_vcs/world/log

world.log(opts: LogOpts?) -> { CommitRow }

The history of the session branch, newest first — git log: every commit reachable from HEAD through both parent edges, so a merged branch's commits and a pulled commit appear along with the commits recorded on this branch. opts.rev starts the history at another revision (git log <rev>). Pass opts.path to get the per-path history (git log -- <path>): only the commits that touched that file, newest-first.

Parameters

  • opts LogOpts (optional) — Optional. opts.limit caps the number of commits (default 50, 0 = all). opts.rev names where the history starts. opts.path scopes the log to one file.

Returns { CommitRow } — Array of CommitRow tables.

local commits = world.log({ limit = 20 })
local merged = world.log({ rev = "feature" })
local touched = world.log({ path = "/source/foo.luau" })

typed/builtin//modules/world_vcs/world/merge

world.merge(sourceBranch: string, opts: MergeOpts?) -> MergeResult

Merge another branch into the session branch — git merge <source>. The merge runs locally in the world's SpacetimeDB clone and is abortable with world.mergeAbort; nothing reaches ZeroMind until the result is pushed. When the session branch's head is in the source's history the head fast-forwards onto it (unless opts.noFf); otherwise a two-parent merge commit lands (opts.ffOnly refuses that). Local changes follow git's rule: one on a path the merge does not rewrite stays in the working tree, and one on a path it does rewrite refuses the merge with git's message naming the paths. A scene's unsaved live edits are a local change to its scene.json: a fast-forward refuses over a scene whose scene.json it rewrites, naming the scene and how to save or discard the edits, and a merge commit refuses over any scene holding them. A staged entry refuses a merge commit. Conflicts → git-style markers are projected into each conflicting text file, the cleanly-merged remainder is applied as working-tree changes, and world.vcsStatus().unmerged lists what needs attention: resolve each path (edit out the markers, or take one side whole), then world.add + world.commit — that commit records the merge (second parent = the source head) and clears the unmerged set.

Parameters

  • sourceBranch string — The branch to merge in.
  • opts MergeOpts (optional) — Optional MergeOpts — ffOnly, noFf.

Returns MergeResult — A MergeResult — status is fast_forward or clean (with commit), conflicts (with conflicts), or up_to_date.

local r = world.merge("feature")
local r = world.merge("feature", { noFf = true })

typed/builtin//modules/world_vcs/world/mergeAbort

world.mergeAbort()

Abort the in-progress merge — git merge --abort. Clears the unmerged set and restores the working tree to the pre-merge state (the target head's committed content; the branch head never moved during a conflicted merge). Errors when no merge is in progress.

world.mergeAbort()

typed/builtin//modules/world_vcs/world/prConflicts

world.prConflicts(worldGuid: string?, number: number) -> any

Read a pull request's conflicts — what stands between it and a merge. Returns the mergeability verdict, the merge base, both heads, and one entry per conflicting path. A conflicting TEXT path carries marked_text: the same <<<<<<< / ======= / >>>>>>> rendering a merge leaves in the working tree, with the source and target sides laid against their common ancestor. Resolve a path by writing the settled bytes back to it and committing on the source branch; the pull request re-analyses on the next read. A binary path carries the two sides' hashes and no text — pick a side. A mergeable pull request returns an empty conflict list.

Parameters

  • worldGuid string (optional) — The world the pull request lives in. Defaults to the bound world.
  • number number — The pull request number.

Returns any — Decoded ZeroMind conflicts response.

local c = world.prConflicts(nil, 3)
for _, m in ipairs(world.prConflicts(originGuid, 3).markers) do print(m.path, m.marked_text) end

typed/builtin//modules/world_vcs/world/prList

world.prList(worldGuid: string?, number: number?) -> any

Parameters

  • worldGuid string (optional)
  • number number (optional)

Returns any

typed/builtin//modules/world_vcs/world/prMerge

world.prMerge(worldGuid: string, number: number, strategy: string?) -> any

Merge a pull request — the agent-side merge button.

Parameters

  • worldGuid string — The world the pull request lives in.
  • number number — The pull request number.
  • strategy string (optional) — merge (default), squash, or fast_forward.

Returns any — Decoded ZeroMind merge response.

world.prMerge(originGuid, 3)

typed/builtin//modules/world_vcs/world/prOpen

world.prOpen(opts: PrOpenOpts) -> any

List a world's pull requests, or fetch one. Open a pull request — gh pr create. Proposes the work on one (world, branch) pair to another. Defaults make the common cases one argument: from a fork, the target is the world it was forked from, so world.prOpen({ title = "..." }) proposes your work upstream. In an ordinary world the target is the same world, so you get a branch → main pull request. The PR lives in — and is numbered by — the world it targets, exactly as a forge numbers pull requests on the upstream repository. That is also where world.prList finds it.

Parameters

  • opts PrOpenOpts — title (required), plus description, sourceWorld, sourceBranch, targetWorld, targetBranch to address any leg explicitly.

Returns any — Decoded ZeroMind response. The decoded ZeroMind pull request.

local prs = world.prList()
world.prOpen({ title = "fix the door hinge" })
world.prOpen({ title = "port the fix", targetWorld = otherGuid })

typed/builtin//modules/world_vcs/world/prView

world.prView(worldGuid: string?, number: number) -> any

Read one pull request in full — gh pr view. Returns the record plus a LIVE re-analysis against the current branch heads: mergeability (clean / conflicts / fast_forwardable / up_to_date / unrelated), conflict_count, and diff — every path the request adds, modifies or deletes with its checksums. Read this before merging: it is what tells you WHAT the request changes.

Parameters

  • worldGuid string (optional) — The world the pull request lives in (its target world). Defaults to the session world.
  • number number — The pull request number.

Returns any — The decoded pull request view.

world.prView(nil, 1)

typed/builtin//modules/world_vcs/world/previewInstall

world.previewInstall(opts: InstallAssetOpts) -> PreviewResult

Preview what installing an asset WOULD write, without writing anything. Fetches + decodes the closure and plans placement (the same helpers world.installAsset uses), returning a flat node list plus rollup totals. A truncated closure is reported (not raised) so a caller can surface it and block import.

Parameters

  • opts InstallAssetOpts — { guid, at?, ref? } — same shape as installAsset.

Returns PreviewResult — nodes + totals + a truncated flag.

world.previewInstall({ guid = "..." })

typed/builtin//modules/world_vcs/world/provideDerived

world.provideDerived(outputPath: string, opts: { stage: string? }?) -> number

Take the file at outputPath as the derived output: the file you wrote is staged as it stands, and the reservation is settled done with the fingerprint authored, which no recomputation replaces.

Parameters

  • outputPath string — The output's VFS path; the file must exist.
  • opts { stage: string? } (optional) — Optional { stage: string? } naming the staging area.

Returns number — How many reservations the file filled.

world.provideDerived("/zero/source/gold.material/preview.png")

typed/builtin//modules/world_vcs/world/publishBlockers

world.publishBlockers(opts: StageOpts?) -> { PublishBlockerClass }

List every reason world.push would refuse to publish this world, as one entry per blocker class: script errors in user content (script), assets that don't meet their type's content requirements (content), computed asset references a component or scene entrypoint reaches (staticPin), and the conditions the publish gate refuses on: a component or scene entrypoint reaching a dynamic resolve over require/tools.use (gameplayPath), a reference pinned to an asset the world no longer holds (references), a reference of the world's own file recorded before the world pinned the library it names (pinPending, which the commit that takes the file clears by deriving its table at the pin), and an error-severity problem recorded on published content (problems). Each class carries the same title the refusal prints, one items entry per offending subject (an asset identity, or a <path>:<line> site), and the single remedy covering that class. This is the account world.push composes its refusal from, so it names the same blockers with no push attempted — and in full, where a refusal bounds how many of a class it prints. zm status prints this list. One more class blocks the next commit rather than the publish, and carries blocks = "commit": staleReferences, the files the caller's staged set holds or leaves out whose references do not resolve in it (a file whose references moved with a staged change and is left out, or a staged file stating a version the staged set leaves out). It is the class world.commit refuses with; world.push never reads it, since a push publishes what is committed. Empty list = the caller commits and publishes.

Parameters

  • opts StageOpts (optional) — Optional { stage: string? } naming a staging area, so the content class reports the assets that area answers for. Omitted, the read acts on the calling caller's own area.

Returns { PublishBlockerClass } — Array of PublishBlockerClass entries, empty when nothing blocks.

for _, c in ipairs(world.publishBlockers()) do
for _, i in ipairs(c.items) do print(c.kind, i.subject, i.detail) end
end

typed/builtin//modules/world_vcs/world/pull

world.pull(branch: string?, opts: PullOpts?) -> PullResult

git pull: fetch origin/<branch> (default the session branch) and merge it into the session branch. When the session branch is strictly behind, its head fast-forwards to the fetched commit; when both sides moved, a two-parent merge commit lands with the same conflict/marker/resolve flow as world.merge (resolve the unmerged paths, then world.add + world.commit; abortable with world.mergeAbort); when the fetched commit is already in the session branch's history nothing changes. opts.ffOnly refuses anything but a fast-forward; opts.noFf always makes a merge commit. Local changes follow git's rule, as world.merge's do, unsaved scene edits included. A branch origin does not hold raises.

Parameters

  • branch string (optional) — The origin branch to pull. Defaults to the session branch.
  • opts PullOpts (optional) — { ffOnly?, noFf? }.

Returns PullResult — A PullResult table: status is up_to_date, fast_forward (commit the new head), clean (commit the merge commit) or conflicts (conflicts).

local r = world.pull()
local r = world.pull("feature")
local r = world.pull("main", { ffOnly = true })

typed/builtin//modules/world_vcs/world/pullAsset

world.pullAsset(opts: PullAssetOpts?) -> PullAssetResult

Pull upstream changes into a previously-installed asset. Re-resolves the root's transitive closure at opts.ref (default latest) and lands only what differs from what the world holds (M.__updateRowPlan):

  • A file the world never edited (its bytes equal the version it installed) lands whole when its version, its reference table or a stale conflict flag differs from the closure's: bytes and table are written to the pulled row in one step, text and binary alike, and the write reaches source whatever the engine's mode.
  • A file the world edited is reconciled from (row.origin_checksum, row.checksum, entry.checksum) (M.__reconcileDecision): noop (upstream unmoved) leaves it alone, converged advances its origin pointer, and merge runs a three-way vcs.merge3 over (base, local, theirs): a clean result is written and the origin pointer advances, a conflicted result is written WITH markers and the row is flagged conflicted (base + theirs checksums recorded for world.resolvePullConflict). A binary file the world edited that upstream changed too is flagged conflicted with the structured base/theirs checksums (no marker write).
  • A folder moves to the closure's version and takes its table.
  • A row flagged conflicted that holds the bytes it installed carries no local content: it does not block the pull, and the pull clears it.

Closure drift: an entry the original install never landed is pulled fresh (added). A locally-pulled row nested under the root's own directory whose origin entry disappeared from the closure is removed when it holds the bytes it installed (pruned), or flagged conflicted when it carries edits.

Parameters

  • opts PullAssetOpts (optional) — See PullAssetOpts. opts.guid or opts.path is required; opts.ref pins the re-resolve to a specific upstream commit (defaults to latest finalized).

Returns PullAssetResult summarising what merged, conflicted, was pruned, and was newly added, the version of the root it landed (root_version), and the reference sites in the pulled content that resolved to nothing where it was published (problems).

world.pullAsset({ guid = "..." })

typed/builtin//modules/world_vcs/world/push

world.push(commitId: string?, opts: PushOpts?) -> (string? | PushQueued, string)

Publish the current branch to ZeroMind. The no-argument form is a git merge --squash push: EVERY unpushed commit on the branch collapses into a SINGLE ZeroMind commit (latest content per path, parented on the branch's current remote HEAD). Because only the merged final state's bytes are uploaded, a superseded or lost intermediate-commit blob can never break the push — this is what makes a churn-heavy world publishable. The local commit history is preserved as the editing journal; on success every squashed commit shares the one remote commit id. The explicit commitId form still pushes that single commit verbatim via publish_commit (advanced / chain-replay use; its parent must already be on the remote).

Parameters

  • commitId string (optional) — Optional. A single commit to push verbatim. Omit for the squash push of the whole unpushed stack (the normal path). A push only moves the remote branch forward, as git's does: a remote head no local commit holds raises git's ! [rejected] <branch> -> <branch> (fetch first), and one the local head does not descend from raises (non-fast-forward); world.pull() first. opts.force is git push --force: a branch whose head is already published moves the remote branch there regardless (a Maintainer's call). A branch with commits not yet published is refused with force, nothing sent (pull first), and so is force with commitId: force moves the session branch.
  • opts PushOpts (optional) — Optional PushOpts: stage names a staging area, the way every other staging verb takes it, and force forces the move. The pre-push content gate refuses over the assets that area holds or committed; an asset another area answers for publishes with its shortfall recorded on it and is named to the caller with the area that holds it. Omitted, the call acts on the calling caller's own area, so the gate refuses over what that caller staged and committed and names the rest. What an area holds is read from the staged rows a status call reports and from the world's history, where every commit records the area it came through, so the answer is the same from every engine and every session bound to the world. An area named after a caller holds what it committed while that caller's session is open; an area a caller named holds it until another area commits inside the asset.

Returns (string? | PushQueued, string) — Two values: the ZeroMind-allocated commit id, and the verdict. "published" with the new commit id when this call published; "already-published" when ZeroMind already carries what this call would have published — the state a push asks for, so it returns rather than raising. That verdict carries the commit's existing ZeroMind id when the publish names one (the single-commit form), and a nil id when it names none (the whole-stack form, which reports a chain). A squash whose merged final state carries dep.unresolved problems takes the slow path inside the same call: the engine re-resolves each pending literal against its live asset index and submits the resolutions to the publish procedure, which completes the push. A reference literal that still resolves to nothing is published with the asset holding it and stays a problem recorded on that asset, while a dep pin the squash severed raises instead; either way the engine names each one with its path, line, reference and reason. A publish ZeroMind refuses raises naming the condition and the command that clears it — a branch that moved under this push names world.pull().

local zmId = world.push()
local zmId, verdict = world.push()
local queue, verdict = world.push() -- verdict "queued" while commits are sealed
world.push("01HABC...")

typed/builtin//modules/world_vcs/world/remoteBranches

world.remoteBranches() -> { { branch: string, commit_id: string, origin_head: string } }

Origin's branches as the last fetch of each recorded them — git branch -r. One row per branch of this world that world.fetch has fetched: branch is origin/<name>, commit_id the local commit that names origin's head, origin_head that head's ZeroMind id.

Returns { { branch: string, commit_id: string, origin_head: string } } — Array of { branch, commit_id, origin_head }, sorted by branch.

for _, r in ipairs(world.remoteBranches()) do print(r.branch) end

typed/builtin//modules/world_vcs/world/renameBranch

world.renameBranch(oldName: string, newName: string, opts: { force: boolean? }?)

Rename a branch — git branch -m <old> <new>. The branch's head and history, and your uncommitted changes on it, go to newName; when oldName is the branch this session is on, the session moves with it. Another caller's uncommitted changes on oldName keep that branch in place beside the renamed one.

Parameters

  • oldName string — The branch to rename.
  • newName string — Its new name.
  • opts { force: boolean? } (optional)
world.renameBranch("feature", "feature-2")

typed/builtin//modules/world_vcs/world/reset

world.reset(target: string, opts: ResetOpts?) -> ResetResult

Move the session branch's head to target — git reset [--soft|--mixed|--hard] <commit>. target is any revision world.revParse reads (a commit id, HEAD~1, a branch, ...). opts.mode: soft keeps the staged entries and the working tree and stages every path whose committed version the move changed; mixed (the default) empties your staging area and keeps the working tree, so a path the move changed reads as a local change; hard empties your staging area and brings the working tree to the new head: every path the move changed, every path your account changed, and every file of your account's that no commit holds (an uncommitted install and a scene's autosave overlay included), each to trash first. A write this session owes the world and the world is not taking is given up (world.dropOwedWrites). The local changes of another account stay and are listed in kept. A target equal to HEAD moves nothing, so world.reset("HEAD") unstages everything and world.reset("HEAD", { mode = "hard" }) discards your local changes. Non-destructive for history: every commit the head leaves behind becomes a trash entry that re-attaches it (in chain order).

Parameters

  • target string — The revision to move the head to.
  • opts ResetOpts (optional) — Optional ResetOpts — mode.

Returns ResetResult — A ResetResult — the new head, its subject line, and the commits left behind.

world.reset("HEAD~1")
world.reset("01HABC...", { mode = "hard" })

typed/builtin//modules/world_vcs/world/resolvePullConflict

world.resolvePullConflict(path: string, choice: string)

Resolve a conflicted row. A TEXT conflict is one where the file currently contains conflict markers (world.pullAsset writes markers only for a text three-way merge that didn't resolve cleanly); a BINARY/composite conflict has no markers — the local bytes were left untouched.

choice = "theirs" fetches clean upstream text by content hash (conflict_theirs_blob_sha256 — path/rename-independent, since blobs are content-addressed) and overwrites the local file with it before clearing the flag. It errors, refusing to guess, when the row records no theirs blob (a binary/composite conflict — a text blob read can't address theirs for those; keep "ours" or re-install the asset instead).

choice = "ours" keeps the local side. When the file carries conflict markers, the local side is reconstructed from them (the marker writers put ours first, so dropping each block's base and theirs sections restores your bytes exactly) and written back; a marker-free file is kept as-is. Either way the flag clears.

Either way, staging the resolved file (world.add) is the normal git-add path once this returns — this call only clears the manifest-level flag and (for "theirs") the file content.

Parameters

  • path string — The conflicted row's local VFS path.
  • choice string — "ours" or "theirs".
world.resolvePullConflict("/source/combat/rules.luau", "theirs")

typed/builtin//modules/world_vcs/world/restore

world.restore(handle: any?)

Restore one trash entry by row_id. Errors verbatim on handler-not-yet-implemented / world-mismatch.

Parameters

  • handle any (optional) — The trash row id. May be a number or a numeric string.
world.restore(42)

typed/builtin//modules/world_vcs/world/retryDerived

world.retryDerived(outputPath: string, opts: { stage: string? }?) -> number

Compute the derived output at outputPath again, on the staging area or on a sealed commit that reserves it. A reservation that failed, was dropped or is done is reserved anew and computed from the inputs as they stand; a sealed commit waiting on it lands when it is done.

Parameters

  • outputPath string — The output's VFS path, as zm status names it.
  • opts { stage: string? } (optional) — Optional { stage: string? } naming the staging area.

Returns number — How many reservations were computed again.

world.retryDerived("/zero/source/gold.material/preview.png")

typed/builtin//modules/world_vcs/world/revParse

world.revParse(rev: string) -> string

The commit a revision names — git rev-parse <rev>. A revision is HEAD, a branch, origin/<branch> (as the last fetch recorded it), a commit id or a unique prefix of one, each optionally followed by ~N (N first parents back), ^ (the first parent) or ^2 (a merge commit's second parent). Raises git's message for a revision that names nothing, HEAD on a branch with no commit included.

Parameters

  • rev string — The revision.

Returns string — The commit id.

local id = world.revParse("HEAD~1")

typed/builtin//modules/world_vcs/world/reviewUpdate

world.reviewUpdate(unit: string, opts: ReviewUpdateOpts?) -> UpdateReview

Review what updating one unit changes, writing nothing: the update itself, never a preview of the asset. Answers the versions the unit moves from (what the world holds) and to (what its origin holds at opts.ref, default the ref world.checkUpdates compares it with), then commits: the commits of the origin world in between, newest first, each { id, message, author, date } (more is true past 200), then the files. modified names up front each file the world holds uncommitted changes to (a dirty or staged row) that the update touches, { path, outcome } with outcome merges or conflicts.

  • An installed asset (unit is its root path, as world.checkUpdates reports it; a path inside the install stands for its root): the commits that change the install's files, back to the one that moved the root from the version the world holds; files lists each file the update adds, changes or removes, { path, change, diff, hunks, local_edit }, where local_edit is none, or merges / conflicts for a file the world edited since it was installed.
  • A library (@builtin, @combat, with or without the @): the commits from the pinned commit to the new one and summary counts of the library files that change and of this world's files whose references move with the pin (they read references moved in status and are committed with the marker). opts.files = true adds files (each changed library file with its diff) and moving (those paths).

opts.commits = false reads no commits, and opts.files = false for an install leaves out files and its diffs: { commits = false, files = false } is the check before an update, answering modified and the counts alone. When a version of the install's root cannot be read the commit list stops there, more is true and commits_error says why.

Parameters

  • unit string — A library name or an install root path.
  • opts ReviewUpdateOpts (optional) — { ref?, files?, commits? }.

Returns UpdateReview: { kind, unit, from, to, commits, more, commits_error?, files?, moving?, modified, summary }; modified is one { path, outcome = "merges" | "conflicts" } per file holding uncommitted changes of its own that the update touches; summary counts added, changed, removed, modified, and merges / conflicts for an install or moving for a library.

local r = world.reviewUpdate("@builtin")
local r = world.reviewUpdate("/zero/source/Fire.module")
local r = world.reviewUpdate("/zero/source/Fire.module", { commits = false, files = false })

typed/builtin//modules/world_vcs/world/show

world.show(...: string) -> any

Mirror git show's CLI arg shape. Default returns commit metadata + full diff vs parent. world.show("commit:/path") returns just the bytes. Flags: --stat, --name-only.

Parameters

  • ... string — Variadic string args: commit id, optional path, optional flags.

Returns any — Either a ShowResult table or a string (for commit:/path).

local r = world.show("abc123")
local r = world.show("--stat", "abc123")
local bytes = world.show("abc123:/foo.luau")

typed/builtin//modules/world_vcs/world/stageVersion

world.stageVersion(rev: string, path: string, opts: StageOpts?) -> boolean

Make the staging area's entry for path the version revision rev holds — git reset <rev> -- <path> (zm restore --staged --source <rev>). When that version is the one the branch head holds (both lacking the path included), the path is unstaged, as the index then matches HEAD there; otherwise a revision that lacks the path stages its deletion. The working tree is untouched.

Parameters

  • rev string — Any revision world.revParse reads.
  • path string — The path to stage.
  • opts StageOpts (optional) — Optional StageOpts — stage, the staging area.

Returns boolean — true when the staging area holds an entry for the path afterwards, false when the path ended unstaged.

world.stageVersion("HEAD~1", "/source/foo.luau")

typed/builtin//modules/world_vcs/world/stash

world.stash(label: string?) -> StashPushResult

Stash the caller's uncommitted changes — git stash push. The caller's dirty + staged state on the session branch is saved into a stash row (label names it), and each stashed path goes back to its committed version, unstaged. Binary and large content is saved by its stored blob, so applying the stash brings it back byte-identical. A path no commit holds keeps its working content in the tree, as git leaves an untracked file, and is listed under kept. A path that cannot be taken back raises, naming it; the stash is saved either way.

Parameters

  • label string (optional) — Optional. Free-form text label for the stash.

Returns StashPushResult — A StashPushResult.

local r = world.stash("wip widget refactor")

typed/builtin//modules/world_vcs/world/stashApply

world.stashApply(ref: any?) -> StashApplyResult

Apply a stash and keep it — git stash apply [<stash>]. Author-only. ref is a row id, stash@{n}, or nil for the caller's newest stash. Text content is written back and staged entries are staged again; content recorded by checksum alone is listed under skipped. Refuses, writing nothing, when a path the stash restores holds a different local change.

Parameters

  • ref any (optional) — Optional stash reference.

Returns StashApplyResult — A StashApplyResult.

local r = world.stashApply()

typed/builtin//modules/world_vcs/world/stashDrop

world.stashDrop(ref: any?) -> number

Drop a stash — git stash drop [<stash>]. Author-only. ref is a row id, stash@{n}, or nil for the caller's newest stash. The dropped stash goes to trash, where world.restore brings it back under a new row id.

Parameters

  • ref any (optional) — Optional stash reference.

Returns number — The row id of the dropped stash.

world.stashDrop()

typed/builtin//modules/world_vcs/world/stashPop

world.stashPop(ref: any?) -> StashApplyResult

Apply a stash and drop it — git stash pop [<stash>]. Author-only. ref is a row id, stash@{n}, or nil for the caller's newest stash. Applies as world.stashApply does; a refused apply keeps the stash. The dropped stash goes to trash.

Parameters

  • ref any (optional) — Optional stash reference.

Returns StashApplyResult — A StashApplyResult.

local r = world.stashPop()

typed/builtin//modules/world_vcs/world/stashes

world.stashes() -> { StashRow }

List every stash row in the world. Anyone with read access sees every stash; the per-row author_hex makes it clear which entries the caller can pop / drop themselves.

Returns { StashRow } — Array of StashRow tables.

local rows = world.stashes()

typed/builtin//modules/world_vcs/world/trash

world.trash() -> { TrashRow }

List trash entries for the world. Anyone with read access to the world can list trash — recovery is a shared safety net, not a privacy boundary. The handle (row_id) feeds back into world.restore.

Returns { TrashRow } — Array of TrashRow tables.

local rows = world.trash()

typed/builtin//modules/world_vcs/world/trashEmpty

world.trashEmpty() -> number

Permanently delete the trash entries your own operations put there — zm trash empty — in one transaction. Entries other callers' operations trashed stay restorable. An emptied entry cannot be restored.

Returns number — The number of entries deleted.

local n = world.trashEmpty()

typed/builtin//modules/world_vcs/world/uninstallLibrary

world.uninstallLibrary(name: string) -> string

Delete the library marker file. name accepts "@combat" or "combat" (the leading @ is the convention carried by the on-disk path).

Parameters

  • name string — The library name, with or without the leading @.

Returns string — The marker path that was removed.

world.uninstallLibrary("@combat")

typed/builtin//modules/world_vcs/world/unseal

world.unseal(commitId: string?) -> string

Return a sealed commit to the staging area it was sealed from, with its message kept, so its paths can be changed and committed again. A commit world.commit sealed lands by itself once its required derived outputs are computed; this is the way out of one that cannot land.

Parameters

  • commitId string (optional) — The sealed commit's id. Omitted, the oldest sealed commit holding a failure, else the newest.

Returns string — The id of the commit returned.

world.unseal()

typed/builtin//modules/world_vcs/world/unstage

world.unstage(path: string, opts: StageOpts?) -> UnstageReport

Remove a path from the staging area. Live manifest dirty state is untouched. A path is staged in one area at a time: a path the calling area does not hold that another staging area does is taken out of that area, whichever account opened it, and named in the report; a take from an area of another account warns, naming that account, the area and its session. An installed asset is unstaged by its root path, which takes every row of the install out of the area; another row of an install raises, naming the install and its root.

Parameters

  • path string — The path to unstage.
  • opts StageOpts (optional) — Optional { stage: string? } naming the staging area the path was staged into. Omitted, the call acts on the calling caller's own area.

Returns UnstageReport — { released }: each path taken out of another staging area (stage, account, same_account, session, live). Empty when the calling area held the path.

world.unstage("/source/foo.luau")
world.unstage("/source/foo.luau", { stage = "fauna" })

typed/builtin//modules/world_vcs/world/update

world.update(units: (string | { string })?, opts: UpdateOpts?) -> UpdateResult

Apply updates. With no units, every unit world.checkUpdates lists; a name or a list of names (library names and install root paths, mixed) updates those. A library moves its pin (world.updateLibrary), an installed asset pulls its new closure (world.pullAsset), one unit at a time: a unit that fails is reported in its entry and the rest still run, and an install whose local edits conflict lists the conflicted paths. Nothing is staged, committed or pushed: the updated files wait in world.status(), to_commit lists every path the updates changed or moved, and warnings names each file holding uncommitted changes that an update merged into or conflicted.

Parameters

  • units (string | { string }) (optional) — Nil, a unit name, or a list of names.
  • opts UpdateOpts (optional) — { ref? }: a branch name or commit id every unit is updated to.

Returns UpdateResult — { results, to_commit, skipped, warnings, note }: results holds one { unit, kind, from, to, ok, error?, merged?, conflicted?, added?, pruned?, moving? } per unit, skipped the units the check could not read, warnings one { unit, path, outcome = "merged" | "conflicted" } per modified file an update touched, and note says nothing was committed.

local r = world.update()
local r = world.update("@combat")
local r = world.update({ "@builtin", "/zero/source/Fire.module" })

typed/builtin//modules/world_vcs/world/updateLibrary

world.updateLibrary(name: string, ref: string?) -> UpdateLibraryResult

Move an installed library to the newest commit of a ref. Resolves ref (default main) in the world the library's marker at /source/libs/@<name> names; when that is the commit the marker already holds nothing is written, even when requested_ref differs; an imported library the engine does not hold at that commit is mounted and waited for, and a mount that fails raises naming why. Otherwise the marker is rewritten with the new commit (other marker fields are kept), the call waits until the engine holds the library at that commit (@builtin: its pin table is read; another library: its content is mounted), and the reference tables of the world's own sources that name the library are derived again. Each source whose table moved is listed by world.vcsStatus with the reason references moved and is committed with the marker: a commit holding the marker and leaving one of them out is refused, naming each. Discarding the marker puts the pin and its tables back. Works for @builtin and for every library world.installLibrary wrote. A failed wait puts the old marker back, waits for the library at the old commit and raises, saying where it ended. One move per library runs at a time, and none runs while play is running or in a runtime-profile session.

Parameters

  • name string — The library name, with or without the leading @.
  • ref string (optional) — A branch name or commit id of the library's world; defaults to main.

Returns UpdateLibraryResult — { name, from, to, changed, rederived }: the commits the pin moved between, whether it moved, and the source paths whose reference table was derived again, each one to commit with the marker.

local r = world.updateLibrary("@builtin")

typed/builtin//modules/world_vcs/world/vcsStatus

world.vcsStatus(opts: StageOpts?) -> StatusResult

Return the working-tree VCS status: dirty paths, staged entries, ignored paths, untracked paths, and any unmerged ones. A file whose bytes are the ones the branch head holds and whose reference table is not (a library pin moved, or an asset it references was renamed, moved or got new bytes) is listed like any change, with reason = "references moved": in staged when the area holds it, in dirty otherwise. world.add / world.add_all stage it and it is committed with the change that moved it. A table that differs only in the install record the engine writes (an installed asset marked as the root of its install) is listed the same way with reason = "install recorded". An untracked path is one with no committed version behind it, and it appears in dirty as well — git add . picks up new files too. Named vcsStatus (not status) because world.status() is the runtime-snapshot accessor owned by world_status.module; the source-control surface keeps its own VCS-specific name so the two never shadow each other. Each dirty[i].dirtied_by is the identity the row was written under, which every session of one account shares; dirtied_by_caller is the stable caller key of the call that wrote it, dirtied_by_name the name that caller goes by on the world's roster now, and writer says what the row is to the calling caller: "you", "other" (a caller whose session is open), "ended" (a caller whose session has ended) or "unattributed". dirty_since_micros is the microsecond timestamp of the first write of the current dirty run. dirtied_by_account is the account the row's writer acts for and same_account whether that is the calling caller's account. local_identity is this connection's identity and local_caller the calling caller's own stable key, local_name the name it goes by now, local_account the account it acts for. claimed_by_other_stages names the paths some other staging area holds and who holds each: the area, its owner_account, whether that is the caller's (same_account), the session holding it and whether that session is open now (live). Staging is advisory: a staging call that stages one of them takes it out of that area. writer_accounts_refusal is set when the world's backend module predates the record of writers' accounts. stage names the area staged is the set of, so a caller reads which name reaches its own staged rows.

Parameters

  • opts StageOpts (optional) — Optional { stage: string? } naming the staging area whose staged set to report. Omitted, the read reports the calling caller's own area. The dirty, ignored and untracked sets are the world's working tree and read the same whichever area is named.

Returns StatusResult — A StatusResult table.

local s = world.vcsStatus()
local s = world.vcsStatus({ stage = "fauna" })
  • api
  • reference