world
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
pathsstring | { 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, asgit add --renormalizedoes: 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.optsAddOpts(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
optsAddAllOpts(optional) — Optional{ stage: string? }.stagenames 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
tokenstring— 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
optsAssetInstallableOpts—{ guid }names the asset;refpins 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
namestring— 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>,@builtinincluded) whoserequested_ref(defaultmain) now resolves in the library's world to a commit other than the marker'sresolved_commit. Apply withworld.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
branchstring— 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
revstring— The revision to read.paths{ string }— The paths to write: files, composite asset folders or directories.optsCheckoutPathsOpts(optional) — OptionalCheckoutPathsOpts:stage = falsewrites 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
pathstring— The unmerged path, or the composite asset folder holding one.sidestring—oursortheirs.
world.checkoutSide("/source/foo.luau", "theirs")
globals/world/commit
world.commit(message: string, opts: CommitOpts?) -> string
Parameters
messagestringoptsCommitOpts(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
optsContributeOpts(optional) — OptionalContributeOpts—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 }:onlynames 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
namestring— The new branch name.fromCommitstring(optional) — Commit id to start at. Defaults toworld.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
branchstring— The branch to delete.optsDeleteBranchOpts(optional) — OptionalDeleteBranchOpts—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
namestring— What identifies the hook.specany(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
rootstring— 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
optsStageOpts(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
scopestring(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
pathstring— 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
outputPathstring— 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
pathstring(optional) — Canonical/sourcepath; 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
branchstring(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? }—sourceis the source world GUID; branches default to"main";maxBatchescaps 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
optsInstallAssetOpts—{ guid }names the root asset to install;refpins 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
optsInstallLibraryOpts— SeeInstallLibraryOpts.opts.worldis the upstream world's guid (required).opts.commitis the upstream commit_id to pin (optional; resolvesopts.reformainif omitted).opts.asis the local library name (defaults to the upstream world's slug).opts.refis 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 = falseleaves 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
optsLogOpts(optional) — Optional.opts.limitcaps the number of commits (default 50, 0 = all).opts.revnames where the history starts.opts.pathscopes 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
sourceBranchstring— The branch to merge in.optsMergeOpts(optional) — OptionalMergeOpts—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
handlenumber— The handleworld.onLoadedreturned.
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
handlenumber— The handleworld.onSavedreturned.
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
handlenumber— The handleworld.onUnloadedreturned.
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
worldGuidstring(optional) — The world the pull request lives in. Defaults to the bound world.numbernumber— 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
worldGuidstring(optional)numbernumber(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
worldGuidstring— The world the pull request lives in.numbernumber— The pull request number.strategystring(optional) —merge(default),squash, orfast_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
optsPrOpenOpts—title(required), plusdescription,sourceWorld,sourceBranch,targetWorld,targetBranchto 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
worldGuidstring(optional) — The world the pull request lives in (its target world). Defaults to the session world.numbernumber— 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
optsInstallAssetOpts—{ 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
outputPathstring— 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
optsStageOpts(optional) — Optional{ stage: string? }naming a staging area, so thecontentclass 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
branchstring(optional) — The origin branch to pull. Defaults to the session branch.optsPullOpts(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,convergedadvances its origin pointer, andmergeruns a three-wayvcs.merge3over (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 forworld.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
optsPullAssetOpts(optional) — SeePullAssetOpts.opts.guidoropts.pathis required;opts.refpins 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
commitIdstring(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.forceisgit 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 withcommitId: force moves the session branch.optsPushOpts(optional) — OptionalPushOpts:stagenames a staging area, the way every other staging verb takes it, andforceforces 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
oldNamestring— The branch to rename.newNamestring— 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
targetstring— The revision to move the head to.optsResetOpts(optional) — OptionalResetOpts—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
pathstring— Canonical/sourcepath of the conflicted record.modestring— One of merge | apply | take-local | take-backend | discard.contentstring(optional) — Finalized bytes forapplymode.
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
pathstring— The conflicted row's local VFS path.choicestring—"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
handleany(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
outputPathstring— The output's VFS path, aszm statusnames 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
revstring— 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 (
unitis its root path, asworld.checkUpdatesreports 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;fileslists each file the update adds, changes or removes,{ path, change, diff, hunks, local_edit }, wherelocal_editisnone, ormerges/conflictsfor 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 andsummarycounts of the library files that change and of this world's files whose references move with the pin (they readreferences movedin status and are committed with the marker).opts.files = trueaddsfiles(each changed library file with its diff) andmoving(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
unitstring— A library name or an install root path.optsReviewUpdateOpts(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
revstring— Any revisionworld.revParsereads.pathstring— The path to stage.optsStageOpts(optional) — OptionalStageOpts—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
labelstring(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
refany(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
refany(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
refany(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
namestring— 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
commitIdstring(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
pathstring— The path to unstage.optsStageOpts(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.optsUpdateOpts(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
namestring— The library name, with or without the leading@.refstring(optional) — A branch name or commit id of the library's world; defaults tomain.
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
optsStageOpts(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
pathstring(optional) — Canonical/sourcepath; 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
pathstring— Canonical/sourcepath of the conflicted record.modestring— One of merge | apply | take-local | take-backend | discard.contentstring(optional) — Finalized bytes forapplymode.
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
pathsstring | { 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, asgit add --renormalizedoes: 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.optsAddOpts(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
optsAddAllOpts(optional) — Optional{ stage: string? }.stagenames 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
tokenstring— 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
optsAssetInstallableOpts—{ guid }names the asset;refpins 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
namestring— 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>,@builtinincluded) whoserequested_ref(defaultmain) now resolves in the library's world to a commit other than the marker'sresolved_commit. Apply withworld.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
branchstring— 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
revstring— The revision to read.paths{ string }— The paths to write: files, composite asset folders or directories.optsCheckoutPathsOpts(optional) — OptionalCheckoutPathsOpts:stage = falsewrites 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
pathstring— The unmerged path, or the composite asset folder holding one.sidestring—oursortheirs.
world.checkoutSide("/source/foo.luau", "theirs")
typed/builtin//modules/world_vcs/world/commit
world.commit(message: string, opts: CommitOpts?) -> string
Parameters
messagestringoptsCommitOpts(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
optsContributeOpts(optional) — OptionalContributeOpts—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 }:onlynames 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
namestring— The new branch name.fromCommitstring(optional) — Commit id to start at. Defaults toworld.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
branchstring— The branch to delete.optsDeleteBranchOpts(optional) — OptionalDeleteBranchOpts—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
namestring— What identifies the hook.specany(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
rootstring— 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
optsStageOpts(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
scopestring(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
pathstring— 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
outputPathstring— 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
branchstring(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? }—sourceis the source world GUID; branches default to"main";maxBatchescaps 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
optsInstallAssetOpts—{ guid }names the root asset to install;refpins 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
optsInstallLibraryOpts— SeeInstallLibraryOpts.opts.worldis the upstream world's guid (required).opts.commitis the upstream commit_id to pin (optional; resolvesopts.reformainif omitted).opts.asis the local library name (defaults to the upstream world's slug).opts.refis 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 = falseleaves 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
optsLogOpts(optional) — Optional.opts.limitcaps the number of commits (default 50, 0 = all).opts.revnames where the history starts.opts.pathscopes 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
sourceBranchstring— The branch to merge in.optsMergeOpts(optional) — OptionalMergeOpts—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
worldGuidstring(optional) — The world the pull request lives in. Defaults to the bound world.numbernumber— 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
worldGuidstring(optional)numbernumber(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
worldGuidstring— The world the pull request lives in.numbernumber— The pull request number.strategystring(optional) —merge(default),squash, orfast_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
optsPrOpenOpts—title(required), plusdescription,sourceWorld,sourceBranch,targetWorld,targetBranchto 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
worldGuidstring(optional) — The world the pull request lives in (its target world). Defaults to the session world.numbernumber— 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
optsInstallAssetOpts—{ 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
outputPathstring— 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
optsStageOpts(optional) — Optional{ stage: string? }naming a staging area, so thecontentclass 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
branchstring(optional) — The origin branch to pull. Defaults to the session branch.optsPullOpts(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,convergedadvances its origin pointer, andmergeruns a three-wayvcs.merge3over (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 forworld.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
optsPullAssetOpts(optional) — SeePullAssetOpts.opts.guidoropts.pathis required;opts.refpins 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
commitIdstring(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.forceisgit 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 withcommitId: force moves the session branch.optsPushOpts(optional) — OptionalPushOpts:stagenames a staging area, the way every other staging verb takes it, andforceforces 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
oldNamestring— The branch to rename.newNamestring— 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
targetstring— The revision to move the head to.optsResetOpts(optional) — OptionalResetOpts—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
pathstring— The conflicted row's local VFS path.choicestring—"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
handleany(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
outputPathstring— The output's VFS path, aszm statusnames 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
revstring— 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 (
unitis its root path, asworld.checkUpdatesreports 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;fileslists each file the update adds, changes or removes,{ path, change, diff, hunks, local_edit }, wherelocal_editisnone, ormerges/conflictsfor 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 andsummarycounts of the library files that change and of this world's files whose references move with the pin (they readreferences movedin status and are committed with the marker).opts.files = trueaddsfiles(each changed library file with its diff) andmoving(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
unitstring— A library name or an install root path.optsReviewUpdateOpts(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
revstring— Any revisionworld.revParsereads.pathstring— The path to stage.optsStageOpts(optional) — OptionalStageOpts—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
labelstring(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
refany(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
refany(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
refany(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
namestring— 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
commitIdstring(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
pathstring— The path to unstage.optsStageOpts(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.optsUpdateOpts(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
namestring— The library name, with or without the leading@.refstring(optional) — A branch name or commit id of the library's world; defaults tomain.
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
optsStageOpts(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" })