Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·originates fromworld 07158574-5…

vfs

Public Luau surface over the `__vfs` Internal FFI namespace — the engine's virtual filesystem.

byzero-proxy @ DESKTOP-DB3UJOJ·posted 2mo ago
What it does

vfs Module

Public Luau surface over the __vfs Internal FFI namespace — the engine's virtual filesystem.

Purpose

Wrap the raw __vfs.* FFI namespace in a typed Luau table that gets auto-injected as _G.vfs via the prelude. The standard read / write / move / remove / mkdir / list / exists / readAsync / reload / evict surface, plus trusted-only watch / unwatch for the bootstrap VM.

vfs.read yields on a byte-cache miss: the module's last step installs the vfs_async_read fallback over the synchronous read, so every VM that holds this namespace, the trusted VM included, reads a path through the same two steps and gets the same bytes for it.

Usage

-- Read / write
local src = vfs.read("@builtin/components/Camera.luau")
vfs.write("/zero/source/notes.md", body)

-- What a write landed: where the bytes went, and what the static passes
-- read in the Luau among them
local ok, report = vfs.write("/zero/source/game/Vent.component/init.luau", code)
if report.diagnostics then
    for _, d in report.diagnostics do
        print(d.severity, d.path, d.line, d.message)
    end
end
if not report.durable then
    log.warn(report.warning .. " " .. report.playShadow)
end

-- Listing
for _, e in ipairs(vfs.list("/zero/source")) do
    print(e.isDirectory and "[d] " or "    ", e.name)
end

-- Async fetch
local png = task.await(vfs.readAsync("/zero/runtime/screenshots/last.png"))

-- Reclaim RAM after processing a big binary
vfs.evict("/zero/source/textures/imported_huge.png")

-- Reload a module after editing its source on disk
if vfs.reload("@mylib/utils.helpers") then
    local m = require("@mylib/utils.helpers")  -- sees the new source
end

vfs.reload(identity) answers whether a module was cached under that name and has now been dropped. A false says the name matched nothing — either a misspelled identity or a module this VM never required — so the next require() returns whatever it would have returned anyway.

A module that caches state derived from OTHER files keeps serving the old value when those files are rewritten, since only its own source is watched. Reload it by identity to make the next require() recompute.

Exports

  • vfs.read(path, opts?) -> string?
  • vfs.write(path, content, opts?) -> (boolean, WriteReport | string)
  • vfs.move(src, dst, opts?) -> (boolean, WriteReport | string)
  • vfs.copy(src, dst) -> (boolean, WriteReport | string)
  • vfs.remove(path, opts?) -> (boolean, string?)
  • vfs.mkdir(path, opts?) -> boolean
  • vfs.list(path?) -> { VfsListEntry }
  • vfs.exists(path, opts?) -> boolean
  • vfs.readAsync(path, opts?) -> promiseId
  • vfs.reload(modulePath?) -> boolean
  • vfs.evict(path, opts?) -> boolean
  • vfs.mutationSeq() -> number
  • Trusted-only: vfs.watch(path, callback) -> number / vfs.unwatch(watcherId) -> boolean.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

global vfs global-types module vfs Virtual filesystem — read, write, list, watch. Public Luau surface over the `__vfs` Internal FFI namespace. require modules/api/engine/vfs about A write to authored `/source` WHILE PLAY IS RUNNING lands on the play shadow: the bytes are live in the session immediately, and a guarded play-exit puts the pre-play copy back unless they were kept. Where the bound world syncs the path, the write is carried to it as it lands, so a restart or crash before that exit keeps it; where nothing syncs the path, a restart ends it. `vfs.durability(path)` answers where any write's bytes went: `durable`, whether a restart keeps them in `survivesRestart`, the state in `warning`, the routes out in `playShadow`, and which of the paths you asked about the shadow holds in `shadowed`. `vfs.playShadowPaths()` lists everything the session is holding, and `vfs.playShadowAuthors()` says whose each one is. Three routes keep a write, differing in what they cost the session: vfs.promotePlayShadow(path) -- or a list of paths: THOSE -- paths onto canonical source, -- play keeps running, the rest -- of the set untouched vfs.write(path, bytes, { durable = true }) -- the write skips the -- shadow, nothing to promote tools.use("sceneAuthoring", "changes") -- then acceptChanges: reaches -- ENTITY changes too, the -- session keeps running `vfs.revertPlayShadow` takes the same path or list and does the opposite. Promote the whole SET the asset landed, not the path you passed: a path carrying an asset-type suffix names the asset, so one `.component` write shadows its entry file, its README and its metadata together, and a play-exit refuses over whichever of them are left behind. Read that set out of `vfs.playShadowPaths()`, filtered to the paths under the asset. The `core/vfs` guide has the model and what each route costs.

bumpVersion(path: string) → void

Bump a path's content-version counter so any assetType behavior that memoized a parse of that file (keyed by `content_version.get`) rebuilds on its next read — SYNCHRONOUSLY, in the same tick as the write, which the deferred asset-change `onChange` dispatch can't guarantee. Lazy-required so this module (installed early in the prelude) carries no load-order dependency on it; the module has no dependencies of its own, so the first call resolves cleanly.

argtypedescription
pathstring

bumpCopied(src: string, dst: string) → void

Bump every path a copy of `src` landed under `dst`. A leaf lands one path; a folder lands one per descendant, at the same relative position under `dst`. The walk follows the SOURCE, so the bumped set is exactly the set whose bytes the copy replaced — a file already sitting beside the destination keeps its counter, and the work is bounded by what was copied.

argtypedescription
srcstring
dststring

read(path: string, opts: VfsOpts?) → string

Read a file from the virtual filesystem. Binary-safe. Returns file contents as a string, or nil if the file is not known. Relative paths resolve under `opts.root` (default `/source/`). When the bytes aren't locally cached and the calling code can yield (`coroutine.isyieldable()`: a task, `awake`, `start`, `update`, `onEnable`, `onOwnerChanged`, `onSyncReceived`, a replicated event's handler), the read waits while the lazy fetch runs and answers the bytes. Code that cannot yield (`onDisable`, `onDestroy`, `onPropertyChanged`, a received synced-function call, a `vfs.watch` callback, a module's top level while `require` runs it, a metamethod, a `table.sort` comparator) cannot wait: a read there of bytes still on their way (a file `vfs.exists` knows, a render surface being captured) raises `VFS_READ_CANNOT_WAIT`, naming the path and where to read it.

argtypedescription
pathstringVFS path.
optsVfsOpts?`{ root = "/source/" }`.

examples

local src = vfs.read("@builtin/components/Camera.luau")

readBounded(path: string, maxBytes: number) →

Read a file from the virtual filesystem, at most `maxBytes` long. Binary-safe, and read the way `vfs.read` reads it. A file longer than `maxBytes` answers `nil, "too_large"`; where the layer holding it states its length (a file held in memory, a synced or staged file) its bytes are never read. Relative paths resolve under `/source/`. Where the calling code can yield, a file whose bytes are not cached is fetched and held to the same bound; where it cannot, a known file whose bytes have not arrived raises `VFS_READ_CANNOT_WAIT`, as `vfs.read` does. file longer than `maxBytes`.

argtypedescription
pathstringVFS path.
maxBytesnumberThe most bytes the read answers, a whole number 0 or more.

examples

local raw, why = vfs.readBounded("/zero/source/notes.json", 65536)

write(path: string, content: string, opts: VfsOpts?) →

Write content to a file. Binary-safe. Overwrites existing files by default — pass `opts.overwrite = false` to refuse to clobber. While play is running a `/source` write lands on the play shadow: it succeeds and reads back, live in the session, and is undone by a guarded play-exit unless accepted; the report's `survivesRestart` says whether a restart keeps it. Scratch under `/source/tmp/` writes through untouched. Pass `opts.durable = true` to say these bytes ARE the source: the write reaches canonical `/source` with play still running and the session still in play, hot-reloading the modules and components that read it, so the edit is observed running in the same play session with nothing left to promote. A durable write RAISES with the reason when the bytes cannot become canonical source. went, with `survivesRestart` / `warning` / `playShadow` / `shadowed` beside it when the play shadow took them, `diagnostics` carries what the static passes read in the Luau it landed, and `contentRequirements` names what the asset it landed in must still say before the world publishes. It is the answer the MCP `write_file` tool gives for the same bytes at the same path. On failure returns false + error message; a durable write raises instead of returning false.

argtypedescription
pathstringVFS path to write to.
contentstringFile content (binary-safe).
optsVfsOpts?`{ root = "/source/", overwrite = true, quiet = false, durable = false }`.

examples

vfs.write("/zero/source/notes.md", body)
local ok, report = vfs.write("/zero/source/game/Vent.component/init.luau", src)
if report.diagnostics then for _, d in report.diagnostics do print(d.severity, d.path, d.line, d.message) end end
if report.contentRequirements then for _, r in report.contentRequirements do print(r.path, r.detail) end end
if not report.durable then log.warn(report.warning .. " " .. report.playShadow) end
vfs.write("/zero/source/game/Vent.component/init.luau", src, { durable = true })

move(src: string, dst: string, opts: { quiet: boolean? }?) →

Move a file from `src` to `dst`. By default fires the destination's write side effects; pass `opts.quiet = true` to suppress them. A quiet move still returns the destination's report and still posts the resolve rule the publish gate reads, which is what `vfs.write` does with the same option: a publish takes no override, so the finding is stated at the line that carries it. While play is running a move takes the source away, so authored `/source` content that predates play is refused and the refusal RAISES with the reason; content this play session created moves and stays tracked on the play shadow. destination, so it reports what `vfs.write` reports for the same bytes at that path: where they went, and what the static passes read in the Luau among them, per landed leaf when the move took a folder. (false, errmsg) when the paths themselves refuse it. A move the running play session refuses raises with the whole reason instead.

argtypedescription
srcstringSource absolute VFS path.
dststringDestination absolute VFS path.
opts{ quiet: boolean? }?Optional `{ quiet: boolean? }`.

examples

vfs.move("/zero/source/a.luau", "/zero/source/b.luau")
local ok, report = vfs.move(mod .. "/init.luau", comp .. "/init.luau")

copy(src: string, dst: string) →

Copy a file OR directory from `src` to `dst`, `cp -r` style. A directory recurses — every descendant is replicated at the same relative path under `dst`, `.refs` sidecars included. `.meta` sidecars are minted fresh, so a copy is a distinct asset with its own identity. Both paths are absolute. The source may live in any layer (writable, library mount, builtin, runtime-generated); the destination must be a writable route. destination, so it reports what `vfs.write` reports for the same bytes at that path: where they went, and what the static passes read in the Luau among them, per landed leaf when the copy took a folder. A module that is advisory where it sat reads as a script error once it sits on a component path, and the copy is what put it there. (false, errmsg) on failure.

argtypedescription
srcstringSource absolute VFS path (file or directory).
dststringDestination absolute VFS path.

examples

vfs.copy("/zero/runtime/recordings/take1.mp4", "/zero/source/clips/take1.mp4")
local ok, report = vfs.copy(mod, "/zero/source/game/Vent.component")

remove(path: string, opts: VfsOpts?) →

Remove a file. Refuses to remove directories unless `opts.recursive = true`. Refuses protected system roots. While play is running, authored `/source` content that predates play is refused and the refusal RAISES with the reason; content this play session created is removable, a folder included. Pass `opts.durable = true` to say the removal IS a change to the source: it takes the route edit mode takes with play still running and the session still in play, the counterpart of `vfs.write(path, bytes, { durable = true })` for taking a path away rather than replacing its bytes. Every other refusal stands in either terms — a protected root, a read-only route, a plain directory without `recursive`. the removal. A removal the running play session refuses raises with the whole reason instead, so a `pcall` around the call reads it — and the lock answers ahead of whether the path is there, so a locked `/source` path raises whether or not it holds anything.

argtypedescription
pathstringVFS path to remove.
optsVfsOpts?`{ root = "/source/", recursive = false, durable = false }`.

examples

vfs.remove("/zero/source/scratch.luau")

mkdir(path: string, opts: VfsOpts?) → boolean

Create a directory. `mkdir -p` semantics — idempotent. Errors if a file already exists at the same path. While play is running an authored `/source` directory waits for the lock to lift and the refusal RAISES with the reason; writing a file under the path creates it as part of that write, and scratch under `/source/tmp/` creates as in edit mode. running play session refuses raises with the whole reason.

argtypedescription
pathstringDirectory path.
optsVfsOpts?`{ root = "/source/" }`.

examples

vfs.mkdir("/zero/source/scenes/")

list(path: string?) →

List entries in a VFS directory.

argtypedescription
pathstring?Directory path (defaults to `/zero`).

examples

for _, e in ipairs(vfs.list("/zero/source")) do print(e.name) end

isDirectory(path: string, opts: VfsOpts?) → boolean

Is ONE path a directory? Answers from reality — the writable layer's children, a resolver-served folder listing, an explicit empty-directory marker — so a loose file whose extension collides with an assetType name (`notes.json`) reads as the file it is while a real `<name>.<type>/` folder reads as a folder. Costs the same whatever the containing folder holds; use `vfs.list` when you want every entry's kind, this when you hold one path.

argtypedescription
pathstringVFS path to classify.
optsVfsOpts?`{ root = "/source/" }`.

examples

if vfs.isDirectory("/zero/source/Goblin.dynamicAsset") then print("folder asset") end

isSaveExcluded(path: string, opts: VfsOpts?) → boolean

Does this path hold content the machine keeps to itself? `/source/tmp/` is session scratch and `/source/local/` is this machine's own durable content — each directory itself included, and everything under it. Both are writable, hot-reloadable and enumerable like the rest of `/source/`; what separates them is where they stop. The engine filters them out of every world save and every peer broadcast, so they reach no world, carry no manifest row there, and a staging verb handed one refuses it by name. The match reads a whole path segment, so `/source/tmpfoo/` is ordinary content. Ask here whenever your code has to agree with what a world can hold.

argtypedescription
pathstringVFS path to classify.
optsVfsOpts?`{ root = "/source/" }`.

examples

if not vfs.isSaveExcluded(p) then table.insert(publishable, p) end

installedFrom(path: string, opts: VfsOpts?) → InstalledFrom

Where the asset holding `path` comes from, when the library carries it by reference. The library's source then holds an install record naming the asset, the world that published it and the commit its bytes are, and the engine serves the asset's files from those. `path` is the asset's own folder or anything inside it. Nil for a path whose bytes the library or the world holds itself.

argtypedescription
pathstringVFS path of the asset or of anything inside it.
optsVfsOpts?`{ root = "/source/" }`.

examples

local from = vfs.installedFrom(ref.path) if from then print(from.world, from.commit) end

movedReferenceNames(path: string?, opts: VfsOpts?) →

The world sources that spell a reference by a name its target no longer has. A source records, beside each name it uses, the guid of the asset the name reached; when that asset later sits at another path (it was moved, or the engine's library files it elsewhere), the reference keeps resolving through the guid and a read serves the current name, while the stored text keeps the old one. This lists those sources and changes nothing. Writing an entry's `currentText` back with `vfs.write(source, currentText, { verbatim = true })` brings the stored spelling up to date without changing what any reference resolves to. Only sources the world authors are considered: the engine library and `/source/deps/` keep the text they were published with. when none does.

argtypedescription
pathstring?One source to ask about; every source the world authors when absent.
optsVfsOpts?`{ root = "/source/" }`.

examples

for _, s in ipairs(vfs.movedReferenceNames()) do print(s.source, #s.names) end

exists(path: string, opts: VfsOpts?) → boolean

Is the path known to the VFS? Checks the Stage-1 metadata (`.meta` sidecar / ManifestView) — NOT "are the bytes locally cached?". Use `vfs.read(path) ~= nil` to confirm bytes are reachable.

argtypedescription
pathstringVFS path to check.
optsVfsOpts?`{ root = "/source/" }`.

examples

assert(vfs.exists("@builtin/models/Cube"))

contentAddress(path: string, opts: VfsOpts?) → string

The content address (sha-256 hex) of the bytes `vfs.read(path)` returns, for a path whose bytes the blob store holds, answered synchronously and without the bytes. Two answers that agree name the same bytes, and a write that changes them changes it. Nil for a path whose bytes are held in memory (text under the inline size threshold, a play shadow, a kept read), which a synchronous `vfs.read` already returns, and for a path nothing is stored at. Keep it beside a copy of a file you read to tell, from a caller that cannot yield, whether the copy is still what the path holds.

argtypedescription
pathstringVFS path.
optsVfsOpts?`{ root = "/source/" }`.

examples

local address = vfs.contentAddress("/zero/source/scenes/main.scene/scene.json")

readAsync(path: string, opts: VfsOpts?) → string

Asynchronous binary-safe read. Returns a promise ID that resolves to the file contents. Useful for reading render textures from the main thread without blocking.

argtypedescription
pathstringVFS path.
optsVfsOpts?`{ root = "/source/" }`.

examples

local data = task.await(vfs.readAsync("/zero/runtime/screenshots/last.png"))

reload(modulePath: string?) → boolean

Clear entries from the `require()` cache so the next `require(name)` re-runs the module's source. Pass a single module identity to drop only that entry; call with no arguments to drop every cached module of the world's own. A module of the built-in library runs once for the process — its body registers into registries that outlive it — and stands through a reload with no argument. The next `require` runs the source the module's file holds when the reload is called, whichever write brought those bytes there, a `quiet` one included. module of the world's own). that name and has now been dropped — `false` says the name matched nothing. The no-arg form returns true.

argtypedescription
modulePathstring?Module identity to reload (omit to reload every

examples

vfs.reload("@mylib/utils.helpers")

evict(path: string, opts: VfsOpts?) → boolean

Drop the in-memory bytes for `path` from the writable MemFs layer without removing the asset. Use after processing large binaries to reclaim RAM.

argtypedescription
pathstringVFS path whose bytes should be evicted.
optsVfsOpts?`{ root = "/source/" }`.

examples

vfs.evict("/zero/source/textures/imported_big.png")

memResident( ) →

List the MemFs entries that are NOT resident-by-default — the writable in-memory layer's binary blobs and its large text files (text at or above the inline-text size threshold). These are the bytes `vfs.evict` can reclaim: the ones kept in RAM rather than left to fall through to the on-disk BlobStore cache. Small text (resident by default) is omitted. The audit counterpart to `vfs.evict` and to reading with `{ keep = true }` — use it to see what encoded bytes are held in RAM, and why. content and `"large-text"` for oversized text. Empty when the VFS isn't up.

examples

for _, e in ipairs(vfs.memResident()) do print(e.path, e.bytes, e.kind) end

durability(paths: string | { string }, opts: VfsOpts?) →

Did that write save to disk, and if not how is it kept? What became of the bytes just written at `paths` — one path, or an array of them answered as ONE write. `durable` is true when they are where the call filed them, and false when the play shadow took any of them: live in this session and undone by a guarded play-exit unless kept. `durable` is present whatever the answer is, so its absence is never a reading. A non-durable answer carries `survivesRestart` (true when a restart of this engine keeps the bytes, because the bound world syncs them as they land; false when a restart ends them), `warning` (the state and what a restart does with it, for a reader scanning values rather than checking a field), `playShadow` (the routes to disk and what each costs a session other people are running in) and `shadowed` (which of the given paths the shadow has, or under `pending` would take). This is the answer, off the same shadow set and in the same words, that the `write_file` / `edit_file` / `capture` tools attach to their own results and that `asset.create` reports as its second return value. Ask it here at any other site that lands files, so every write surface states where the bytes went in one set of terms. write. A path resolves the way `vfs.write` resolves its own — absolute or `@`-rooted as it stands, a bare one under `/source/` — so the answer is about the file that write landed. A value that is not a path — an AssetRef, a record, a number, a string with nothing in it — raises rather than being answered off the empty set it reads as; a list with no entries names nothing and answers durable. the same question of a write that has NOT landed yet: where bytes written at these paths would go, read off the same play-lock verdict that will decide it. That is what a surface arranging a write it files later reports from — `av.record` opens a take here and the film is written when the take ends — because until those bytes land the shadow is holding nothing under the path and the answer would be durable at the one moment the caller can still act on it. The fields and the words are the same either way; the state names the tense it is true in. present exactly when `durable` is false.

argtypedescription
pathsstring | { string }One VFS path, or an array of paths answered together as one
optsVfsOpts?`{ root = "/source/", pending = false }`. `pending = true` asks

examples

local ok = vfs.write(path, body)
local d = vfs.durability(path)
if not d.durable then log.warn(d.warning .. " " .. d.playShadow) end
local ahead = vfs.durability("/zero/source/clips/take.mp4", { pending = true })

playShadowPaths( ) →

Every source write made during play that is not durable yet — the set to keep or discard before leaving play. Lists the `/source` paths edited during running play that the play shadow holds, each beside the pre-play copy a guarded discard puts back: the universal play shadow-copy set. These are the in-play edits persist promotes over the originals on confirm, or drops on a guarded discard. Empty outside play or when nothing was edited.

examples

for _, p in ipairs(vfs.playShadowPaths()) do print(p) end

playShadowAuthors( ) →

The caller behind each currently-shadowed `/source` path: the id the write was attributed to, the username to show for it, and the ZeroMind account it authenticated as. Several agents drive one engine at once and every one of their in-play source edits sits in the same shadow set, so this is how a review, a refusal or a verdict tells one agent's pending work from another's. `id` names the calling session where the call carried one, so two agent sessions on a machine linked to one ZeroMind account are two authors. A path written by a caller the engine could not name carries no entry, because it belongs to nobody in particular, and stays settleable by anyone.

examples

local mine = vfs.currentAuthor()
for path, who in pairs(vfs.playShadowAuthors()) do
if mine == nil or who.id ~= mine.id then print(path, "belongs to", who.name) end
end

currentAuthor( ) →

The caller this call is attributed to: the author a `/source` write made right now would be recorded under in the play shadow. `id` names the calling session where the transport attested one, so two agent sessions driving one engine read as two callers even when one linked ZeroMind account minted both their tokens. `stable` is the key the caller had before the world's roster named it, which a rename leaves where it was: the key the caller's staging area and the writer its dirty rows record are kept under. `account` is that shared account. Nil when the engine can name no caller at all, which is the case for engine-authored work and for a caller that presented neither a session nor a token. Compare its `id` against `vfs.playShadowAuthors()` to separate your own pending edits from a co-author's.

examples

local me = vfs.currentAuthor()
print(if me ~= nil then me.id else "unattributed")

pendingWrites( ) →

List the `/source` paths with a local write or removal the synced manifest has not reflected yet: everything this client still owes the server. A just-written or just-removed file appears here until its upload round-trips and the synced dirty state catches up; `world.vcsStatus` unions these so a fresh edit reads back as dirty immediately, and `world.syncStatus().owed_paths` reports the same list. Empty when fully synced.

examples

for _, p in ipairs(vfs.pendingWrites()) do print(p) end

mutationSeq( ) → number

Lifetime count of VFS mutations the engine has APPLIED — the drain's clock. A write queues its side effects (an asset's content reload, the assetType's `onChange`, a component or scene registration) and a later frame runs them; this number advances as each one completes. Read it, write, then poll for a larger value to learn the queue has moved past the point you wrote at — instead of waiting a guessed number of frames. It counts every mutation kind, so it answers about the pipeline rather than about one file; `asset.reloadSeq(ref)` is the per-asset reading.

examples

local at = vfs.mutationSeq()
vfs.write("/zero/source/tmp/note.txt", "hi")
repeat task.wait() until vfs.mutationSeq() > at

folderHashWalks( ) → number

Lifetime count of category-folder checksum walks — the part of a write's cost that grows with how much content already exists. A folder's checksum is the tree hash over the descendants that describe the asset, so deriving one reads every key under it, and a write re-derives it for each enclosing category folder whose bytes that write reaches. Content the checksum is made of therefore moves this once per enclosing folder per write; bytes that reach no checksum — a managed sidecar, a scene's autosave overlay — move it by nothing. Read it around a burst to learn what that burst cost in walks, a number that reads the same on a loaded machine and a quiet one.

examples

local at = vfs.folderHashWalks()
vfs.write("/zero/source/scenes/probe.scene/scene_dirty/entities/e1.json", "{}")
print(vfs.folderHashWalks() - at)

claimWalks( ) → number

Lifetime count of walks over every asset the world offers: the part of an enumeration's cost that grows with how many assets exist. `asset.list` reads a snapshot of every registered claim, built once for each change to what the world offers and shared by every list after it; `asset.categories` reads the category registry and walks nothing. Read it around a burst to learn what that burst cost in walks, a number that reads the same on a loaded machine and a quiet one.

examples

local at = vfs.claimWalks()
vfs.write("/zero/source/probe.module/init.luau", "return {}")
asset.list("module")
print(vfs.claimWalks() - at)

unmarkPlayShadow(path: string) → boolean

Forget a single play-shadow path after it has been promoted (saved over source) or discarded. Tracking only — never touches the bytes.

argtypedescription
pathstringVFS path to unmark.

examples

vfs.unmarkPlayShadow("/zero/source/Foo.component/init.luau")

clearPlayShadow( ) → boolean

Forget the entire play-shadow set after a bulk promote or discard. Tracking only — never touches the bytes.

examples

vfs.clearPlayShadow()

discardPlayShadow( ) →

Throw away every play-shadow edit: the discard a play-exit takes when the play session's work is not kept. Each path reverts to its pre-play copy, as vfs.revertPlayShadow does. A path whose pre-play copy cannot be put back yet (the world's blob, still being pulled; the world's manifest row, read once the manifest has taken the update it is applying; or a copy whose write back is refused) leaves the play shadow all the same and is OWED: it holds the play bytes until the copy lands and its write is taken, and the copy is put back then, unless the path has changed in the meantime. Every owed path is returned, posted as a notice and logged by name. Calling it again asks for every owed restore again, pulling a copy whose pull failed. restore has not landed. Empty when every path reverted.

examples

for _, owed in ipairs(vfs.discardPlayShadow()) do
print(owed.path, owed.reason)
end

playDiscardsOwed( ) →

The play edits a discard threw away whose pre-play copy has not been put back yet. Each of these paths holds the play session's bytes until its restore lands, which happens on its own once the copy arrives and its write is taken. A path that changes first keeps its new content and leaves this list. landed.

examples

for _, owed in ipairs(vfs.playDiscardsOwed()) do
print(owed.path, owed.reason)
end

promotePlayShadow(path: string | { string }) → string |

KEEP a source write made while play was running — the call that saves a play-mode edit onto disk and makes it durable. Promotes play-shadow edits into canonical writes: re-asserts the live overlay bytes through the full write pipeline, then unmarks each path. The bytes stay in the engine end to end, so binary content promotes exactly. It answers while play is RUNNING and leaves the mode, the clock and every shadow entry it did not name exactly where they stood. Takes ONE path, or an ARRAY of them — a single folder-asset write shadows the entry file, the README and the metadata together, so a slice is tens of paths, and naming them keeps the call to the caller's own work on an engine other sessions are running in. Every named path is attempted; one that refuses does not stop the ones after it. A promotion that cannot happen raises with the reason: the path is not shadowed, the path is a folder covering shadowed edits, the workspace is read-only, or the write-through failed. A shadow entry whose bytes are gone is dropped as promoted, so the path comes back with nothing written for it. vfs.playShadowPaths()). array went in — none of them are shadowed any more.

argtypedescription
pathstring | { string }A shadowed VFS path to promote, or an array of them (from

examples

local promoted = vfs.promotePlayShadow("/zero/source/cover.jpg")
local mine = {}
for _, p in ipairs(vfs.playShadowPaths()) do
if string.find(p, "/zero/source/mine/", 1, true) == 1 then table.insert(mine, p) end
end
local settled = vfs.promotePlayShadow(mine)

revertPlayShadow(path: string | { string }) → string |

DISCARD a source write made while play was running, keeping nothing — the opposite of promoting it. Reverts play-shadow edits: restores the pre-play copy captured at the first play-mode write (the last edit-mode state, unstaged edits included) into the live slot — or remove the file when it did not exist at that moment — then unmark the path. Hot-reload picks the original back up, so the running session actually reverts. It answers while play is RUNNING and takes ONE path or an ARRAY of them, on the same terms as vfs.promotePlayShadow. A revert that cannot happen raises with the reason. vfs.playShadowPaths()). an array went in — none of them are shadowed any more.

argtypedescription
pathstring | { string }A shadowed VFS path to revert, or an array of them (from

examples

local reverted = vfs.revertPlayShadow("/zero/source/Foo.component/init.luau")
local dropped = vfs.revertPlayShadow({ "/zero/source/a.md", "/zero/source/b.md" })

watch(path: string, callback: (string, string, string) →

Register a callback that fires when a VFS path is written or removed. Two match modes: exact, or folder/prefix (key ends with `/`, and fires for any descendant). The callback runs in the VM that registered it. Returns a watcher id for `vfs.unwatch`. `"remove"`; origin `"local"` for a change this engine made (through `vfs.*`, the shell or any other surface) and `"remote"` for one that arrived from another participant in the world.

argtypedescription
pathstringExact path, or folder path ending in `/`.
callback(string, string, string`(mutated_path, kind, origin) -> ()`, kind `"write"` or

examples

local id = vfs.watch("/zero/source/", function(path, kind, origin) print(kind, origin, path) end)

unwatch(watcherId: number) → boolean

Remove a previously registered VFS watcher.

argtypedescription
watcherIdnumberWatcher id returned by `vfs.watch`.

examples

vfs.unwatch(id)

open(path: string, opts: VfsOpts?) → PluginHandle

A reader of the file at `path`, to pass a plugin in a call. The plugin reads it by range and reaches nothing else through it; it is refused, raising the reason, where this script could not read. A reader whose file leaves its path answers the plugin that the asset is gone.

argtypedescription
pathstringThe file to read.
optsVfsOpts?`{ root = "/source/" }`.

examples

local weights = vfs.open(asset.containing(__FILE__).path .. "/weights.gguf")

openDir(path: string, opts: VfsOpts?) → PluginHandle

A reader of the folder at `path` and everything under it, to pass a plugin in a call. The plugin lists it and opens readers of its files, by paths relative to the folder that cannot leave it.

argtypedescription
pathstringThe folder to read.
optsVfsOpts?`{ root = "/source/" }`.

examples

local model = vfs.openDir(asset.containing(__FILE__).path .. "/model")

sink(path: string, opts: SinkOpts) → PluginHandle

A sink for the file at `path`, to pass a plugin in a call. The plugin writes it from empty, up to `opts.maxBytes`, and the file lands when the plugin closes the sink or drops it. Only the world's own files take a sink.

argtypedescription
pathstringThe file to write.
optsSinkOpts`{ maxBytes = <bytes> }`, required.

examples

local out = vfs.sink("/source/imported/sphere.zvol", { maxBytes = 64 * 1024 * 1024 })

dirSink(path: string, opts: DirSinkOpts) → PluginHandle

A sink for new files under the folder at `path`, subfolders included, to pass a plugin in a call. The plugin makes whatever files it Only the world's own files take a sink.

argtypedescription
pathstringThe folder to write under.
optsDirSinkOpts`{ maxFiles = <n>, maxBytes = <bytes>, extensions = { "png" }? }`.

examples

local frames = vfs.dirSink("/source/frames", { maxFiles = 240, maxBytes = 200 * 1024 * 1024, extensions = { "png" } })
⌬ Types
PluginHandle = anySinkOpts = { maxBytes: number, root: string? }DirSinkOpts = { maxFiles: number, maxBytes: number, extensions: { string }?, root: string? }VfsOpts = {VfsListEntry = { name: string, isDirectory: boolean, path: string }Durability = {WriteDiagnostic = {WriteContentRequirement = {WriteReport = {InstalledFrom = {MovedReferenceName = {MovedReferenceSource = {

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

9items
▣
module · born here
❒asset
# content_version Per-path content-version counters — a cheap, synchronous "has this file changed?" token. An assetType behavior memoizes its parse in the ref's `runtime` keyed by the counter `get(path)` returned when it parsed, then serves every later read as an integer compare instead of re-reading and re-parsing the file. ## Why it exists A `.data()` behavior that re-reads and re-parses its source on every call is pure overhead when the file hasn't changed. This module gives it a version token to gate the rebuild on: - `get(path)` when the parse ran, stored alongside the parsed value. - On every later call, compare `get(path)` to the stored value — equal means the cache is still valid (no `vfs.read`, no parse); a bump means rebuild. ## Exports - `M.get(path: string) -> number` — the path's current version counter (`0` if never written this VM). - `M.bump(path: string)` — bump `path`'s counter, invalidating every reader memoized against its previous value. Content code rarely calls this directly. ## What bumps a counter Two write surfaces feed it, so the token reflects a change no matter where it originated: - `vfs.write` / `vfs.move` / `vfs.remove` bump **synchronously**, in the same call — so a script that writes a file and reads it back in the same tick sees the new content immediately (the asset-change `onChange` dispatch fires a frame later, too late for a same-tick read). - the generic asset-change dispatcher bumps on every source write it routes, including peer-synced and engine-originated writes that never pass through the Luau `vfs.*` surface — with the engine's normalized path, the canonical form a reader keys on. ## Per-path, not a global epoch Counters are per **path**, not one global epoch. The per-frame dirty-entity writer churns scene-dirty paths every frame during play; a global epoch would let that churn invalidate every unrelated cache. Per-path isolation means only a change to the file a reader depends on rebuilds it. The counter map lives on `_G` (`__zero_content_versions`), installed once before the global table is sealed at boot, so a single map is shared across every copy of this module that a require-cache reset (`vfs.reload`) might create. A module-upvalue map would let a bumper and a reader that landed on different module copies diverge, and the reader would serve stale content. Per-VM.
▲ 0↑ born
▣
module · born here
❒asset
# vfs_async_read Pure-Luau wrapper that grafts a yielding `vfs.read` over the engine's sync `vfs.read` binding. The `vfs` API module (`modules/api/engine/vfs`) calls `M.installInto(vfs)` on itself as it loads, so every VM holding `vfs` (user VMs and the trusted VM alike) reads through it; user code never requires this module directly: it just calls `vfs.read(path)` and the wrapper handles cache misses by yielding the running coroutine until the bytes resolve. ## Exports - `M.installInto(vfs: VfsNamespace)`: replace `vfs.read` (and `vfs.readBounded`) with the yielding wrapper. No-op when the target lacks `read` and `readAsync` as functions. - `M.CANNOT_WAIT`: `"VFS_READ_CANNOT_WAIT"`, the code a read raises when it names a known file whose bytes have not arrived and the reading code cannot yield. Types: - `VfsNamespace = { read?, readAsync?, readBounded? }`: the engine's `vfs` global shape. ## Usage ```luau -- The `vfs` API module installs the wrapper onto itself as it loads: require("@builtin::modules.vfs_async_read").installInto(vfs) -- After install, every `vfs.read` call yields on cache miss: local bytes = vfs.read("/zero/source/some.asset") ``` ## Where a read can wait - On a miss the wrapper schedules `vfs.readAsync(path, opts)`, which fires `BlobProvider::fetch` under the hood, and `task.await`s the promise. The resolved value can still be `nil` if the path isn't in the manifest at all. - Waiting takes a yield, so it happens where `coroutine.isyieldable()` is true: a task, the component hooks the engine runs on a coroutine (`awake`, `start`, `update`, `fixedUpdate`, `onEnable`, `onOwnerChanged`, `onSyncReceived`), a replicated event's handlers, and a world or scene entrypoint's hooks. - Where it is false (`onDisable`, `onDestroy`, `onPropertyChanged`, a received synced-function call, a `vfs.watch` callback, a module's top level while `require` runs it, a metamethod, a `table.sort` comparator, a VM's main thread) a miss the engine's read answers `nil, "pending"` for (a file `vfs.exists` knows that is no directory and whose bytes can still arrive, a render surface being captured) raises `VFS_READ_CANNOT_WAIT: vfs.read("<path>") ...`, naming the path and the places it can be read from. Every other miss answers nil there as everywhere else. - `installInto` is idempotent in the trivial sense: calling it twice re-wraps `vfs.read` around the already-wrapped function, which is fine but pointless.
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.