Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit44fd9e59bd

README.md

Package (asset type)

A package is an asset — a <Name>.package/ folder that bundles components, modules, scenes, presets, tools, and any other typed sub-assets under one identity, installed into a world as one unit. Packages are how authoring sets get distributed across worlds.

A library is the addressable root scope content resolves under (@builtin, @<name>) — a whole world mounted into this one under a namespace, via zeromind.installLib / world.installLibrary. A package is an asset that lives inside such a root, and one library holds as many packages as its author put there. A package is installed by guid the way every asset is (zeromind.installAsset).

When to use one

  • You're authoring a coherent feature suite (character controller stack, voxel engine, UI kit, animation library) and want to ship it as one install target.
  • You want a stable identity worlds can install and resolve sub-assets against (@<lib>::pkg.sub_module).
  • You want versioning + a per-package metadata layer that travels with the contents.

If you're authoring inside a single world and don't need a separate install lifecycle, you don't need a package — drop your .component / .module / etc. directly in /zero/source/. Packages are for cross-world distribution.

Where it lives

  • Source: /zero/source/.../<Name>.package/ (or, for legacy packages, a folder containing package.yaml at its root).
  • Identity: <Name> (the .package suffix collapses in identity — mirrors .module). A file at <Name>.package/<sub>/init.luau resolves under the same identity it would at <Name>/<sub>/init.luau.
  • Folder shape (container-style — open child set):
    • package.yaml: metadata (name, version, description) and the packages it depends on (dependencies). Required.
    • README.md — instance documentation. Required.
    • .metadata — agent-editable tags + free-form fields. Required.
    • Sub-asset folders: any combination of .component/, .module/, .scene/, .preset/, .tool/, .toolbox/, .material/, .shader/, etc.

The validator permits unlisted children (allow_unlisted: true) by design — packages are open containers.

How to create one

asset.create("package", "<Name>")
-- Creates: /zero/source/<Name>.package/
--   package.yaml   (metadata scaffold)
--   README.md      (instance README template)

-- `folder` places it in a subfolder of /source instead of the root:
asset.create("package", "<Name>", { folder = "systems" })

Then scaffold sub-assets inside it:

local pkg = asset.resolve("<Name>.package")
asset.create("component", "Spinner", { into = pkg })
asset.create("module", "util", { into = pkg })

package.yaml

name: inkBlocks          # optional; the folder stem when absent
version: "1.0.3"
description: "Ink and paper style blocks"
dependencies:
  - { name: starterBlocks, version: "1.0.8" }
  - styleKit             # a bare name states no version

dependencies lists the packages this package needs. Each entry's name is a reference the package records, the same kind of reference a require or an asset name in a data file makes:

  • It resolves in the author's world. Install the dependency here first (zeromind.installAsset); the entry then names the copy this world holds. A push refuses a package whose dependency resolves to nothing, naming it.
  • An install brings it along. Installing the package installs every dependency it lists, at the version this world holds when the package is pushed, under /source/deps/<owner world>/, deduplicated against what the installing world already holds. zeromind.preview lists each one before anything is written, and the install's deps lists what it brought. Uninstalling the package leaves a dependency that another installed package still uses.
  • version is checked, not chosen. A stated version has to equal the version the dependency's own package.yaml states in this world; asset.validate (and so the push) refuses the package naming both when they differ. To depend on another version, install that version here.

How it operates

  1. Registration. Writing package.yaml indexes the package with the package registry.
  2. Install. A package is read before it is installed: zm ls-tree @<guid> in the shell lists what it contains, and zm show @<guid>:<path> and zm grep <pattern> @<guid> read it, so candidates are compared by what they do. The one that fits is installed by guid, the way every asset is: zeromind_install over the engine's MCP, or tools.use("zeromind", "installAsset", <guid>) inside the engine. The install writes the package's files (and the deps its .refs record, the packages its package.yaml lists under dependencies included) into this world's source, so its sub-assets resolve here. tools.use("zeromind", "preview", <guid>) reports what an install would write before it writes anything.
  3. Sub-asset resolution. Because .package collapses in identity, sub-asset paths work whether the package folder is suffixed (<Name>.package/<sub>) or not (<Name>/<sub>).
  4. Updates. tools.use("zeromind", "updates") lists installed content with upstream changes available; re-running the install pulls them.
  5. Metadata. package.yaml::name overrides the folder stem if present (rare); version + description are surfaced in the package index.
  6. Hot reload. Edits to sub-assets hot-reload as normal; edits to package.yaml reload package metadata.

Discovery

  • asset.list("package") — every registered package.
  • asset.inspect("<name>") — sub-asset catalog, source, this type README.
  • ls /zero/source/<Name>.package lists the files the folder holds.
  • library.list() — the library assets registered in this world, including the ones packages under an installed library contribute.

Authoring conventions

  • Name packages by their feature (voxels, cameraRigs, dialogSystem), not by their owner — the host library is the @<lib>:: scope prefix.
  • Cluster cohesive sub-assets together. A "character controller" package should ship the controller component, its preset configs, its supporting modules, and a demo scene — not just the component.
  • Document the package's surface in README.md — what install installs, what each sub-asset does, how they relate. Agents reading cat /zero/source/<Name>.package/README.md get the full picture in one shot.
  • Version conservatively — breaking a package means breaking every world that installed it. Use semantic version bumps.

Common pitfalls

  • Missing package.yaml. Without it the folder isn't recognized as a package even if it has a .package/ suffix.
  • A dependency named only by a string the code builds. A package whose files reach another package only through a name assembled at runtime records no reference to it, so an install arrives without it. List that package under dependencies in package.yaml.
  • Identity collapse. Sub-asset paths skip the .package suffix. Don't include .package in require strings.

Referencing inside a package: the ~ rule

~ means this package, resolved structurally from the referring file's own location. It is the only package-specific syntax. It is rename-safe (no package name is ever written) and library-agnostic (it never names the host library, so it survives @builtin being renamed or the package being mounted elsewhere). ~.tail maps to <pkg>.package/tail from anywhere inside the package.

Referencing fromUseExample
An asset/module inside this package~.tailrequire("~.modules.myThing"), shader: "~.shaders.myThingBlend"
A different package under the same rootroot-relative identityrequire("@root::systems.otherPkg.modules.x")
A different library's public API (a deliberate external dependency)@otherlib::…require("@physics::modules.rigidbody")

Never write @builtin:: (or any host-library name) to reference your own package — that couples the package to the library it currently lives in and breaks if the library is renamed or the files move. Use ~.. It works from Luau require() and from asset-reference strings in data files (a material's mat.yaml shader: field, a component's default asset ref); the engine resolves it against the referring file's own package. asset.resolve takes a ~. name the same way, against the package of the file that calls it: asset.resolve("~.materials.myThingStandard", "material").

~. is the package-relative self-reference; @root:: is the root-relative one (the library root, or the world source tree for non-library content). The core/requiring-modules guide covers the root model in full.

A package that ships substantial functionality should include a .guide asset (a <name>.guide/guide.md folder anywhere under the package); the guides tool discovers it live, so collaborators find the system the way they find built-in guides. A package's test suite reaches the shared Test framework through its enclosing asset: local Test = asset.containing(__FILE__).modules.shared (asset.containing returns the deepest enclosing typed asset, the .testSuite, not the outer .package).

Related types

  • Any of .component, .module, .scene, .preset, .tool, .toolbox, .material, .shader, .style, .service, .importer — packages bundle these.

In the Inspector

Package: the name, version and description package.yaml declares, how many things the package holds and what they are, and Open for package.yaml. A package.yaml that does not decode is listed under Problems with the decoder's error.