Technical reference

Complete Locus Package v2 authoring

On this page
  1. Start with a working complete package
  2. Archive layout
  3. locusplace.json
  4. Envelope field reference
  5. Package-v2 metadata inside the ZIP
  6. View: destination.json
  7. Room: space.json
  8. Experience: experience.json
  9. Optional capabilities and translated labels
  10. Required provenance
  11. Security and resource budgets
  12. Files and URL acquisition
  13. Package validation and examples

Status: canonical publication, storage, composition and feature-complete import contract. Locus 1.2.1 and later accept this archive from Files or a public HTTPS URL for both View and Room imports. The older flat format in locus-asset-format.md remains a compatibility input.

A .zip or .locusplace file is one ordinary ZIP archive containing one or more Package-v2 Destinations (product name: Views), Spaces (Rooms), and Experiences. It is the secure publication and integrity envelope used by Locus itself and by feature-complete portable packages. The ZIP root contains locusplace.json and catalog/ directly; do not put both inside another parent directory.

Validation is based on archive bytes and manifests, never the filename extension. A complete package preserves its packageID, content IDs, contentVersion update stream and supported Experiences; importing the same content hash is an idempotent no-op. The maintained packer and validator produce and inspect this exact format.

This envelope carries pure data, not executable extensions. For one Locus binary, every runtime-consumed Package-v2 capability has the same content semantics whether the archive is bundled, downloaded from the Library or imported from Files/URL. The origin may change entitlement, custom-import allowance, update authority, storage, removal and UI labels, but never enables a different Room/View schema or renderer feature. Scripts, plugins, dynamic libraries and executable members are outside the format. All origins retain the same path confinement, integrity, resource-budget, structural-decode, reference-closure and minimum-version checks.

Use this guide for the complete envelope and baseline manifests. The field reference documents the optional View and Room capabilities, their assets, ranges, and minimum app versions. The envelope schema and provenance schema describe JSON shape; the packer and validator also check file bytes and cross-field rules. A JSON Schema alone cannot validate an archive or prove that it will feel comfortable on Apple Vision Pro.

Start with a working complete package

Clone or download the public tools repository. Run these commands from its root on macOS, with Python 3.9 or later and an Xcode installation that provides xcrun usdchecker and xcrun usdcat. Basic validation uses the Python standard library, /usr/bin/sips for full image decoding, and Xcode’s usdchecker and usdcat for models. Advanced cloud, water reflection/contact, and sun-free-sky checks also need NumPy and Pillow: python3 -m pip install numpy pillow.

python3 examples/locusplace/build_examples.py /absolute/path/to/locusplace-examples
python3 tools/validate_locusplace.py --current-app-version 1.4.4 /absolute/path/to/locusplace-examples/view-only.locusplace
python3 tools/validate_locusplace.py --current-app-version 1.4.4 /absolute/path/to/locusplace-examples/room-only.locusplace
python3 tools/validate_locusplace.py --current-app-version 1.4.4 /absolute/path/to/locusplace-examples/combined.locusplace

The target directory must be absent or empty. These tiny synthetic examples exercise the format; the gradient and cube are not production-quality scenery or a finished Room. Their source and generated content are Apache-2.0 licensed. See the example instructions.

--current-app-version is the Locus version you are validating for; it does not upgrade the installed app or grant support for a capability. The tools conservatively default to 1.0.0; pass the intended version explicitly. Complete imports require Locus 1.2.1 or later, and newer fields require their own version floor. The Room ground and midground controls require a supporting 1.4.4-or-later build; this guide does not assert that such a build is currently on the App Store.

For your own package, unzip an example into an editable folder or create the layout below. Replace its assets and provenance, use your own stable IDs, and pack to an output outside that folder:

python3 tools/pack_locusplace.py --current-app-version 1.4.4 MyEditablePlace MyPlace.zip
python3 tools/validate_locusplace.py --current-app-version 1.4.4 --json MyPlace.zip

The packer recomputes files and contentHash, validates the archive, and only then publishes it. It refuses to overwrite an existing output. It leaves the editable source untouched. Import the result through Places → Import → Import a View or Import a Room → Files, choosing an entrypoint whose content kind is included. A combined package installs its contents together. A direct public HTTPS ZIP URL works too. Check each authored seat, effect, light and sound on Apple Vision Pro before sharing a finished place.

Archive layout

example.locusplace
├── locusplace.json
└── catalog/
    ├── destinations/<destination-id>/
    │   ├── destination.json
    │   ├── provenance.json
    │   └── ...
    ├── spaces/<space-id>/
    │   ├── space.json
    │   ├── provenance.json
    │   └── ...
    └── experiences/<experience-id>/
        └── experience.json

Every non-directory member other than the root locusplace.json must be declared in files. Every declared file must exist. The archive may contain only the three known catalog collections, and every file must be inside a package ID declared by contents. Package-v2 manifests remain forward-compatible with unknown fields, but unknown envelope fields, unsupported format versions, collections and content modes fail closed. Provenance follows its published schema.

A View-only or Room-only archive installs reusable catalog content. Once the catalog resolves, Locus derives its default pairings: each View with Your Room, and each View with every Room that has a destination opening. An explicit Experience replaces only its named pair and may refer to content in the same archive or to a stable ID already present in the built-in/imported catalog.

locusplace.json

{
  "formatVersion": 1,
  "packageID": "place.example-combined",
  "contentVersion": "1.0.0",
  "minimumAppVersion": "1.0.0",
  "contents": {
    "destinationIDs": ["destination.example-view"],
    "spaceIDs": ["space.example-room"],
    "experienceIDs": ["experience.example-combined"]
  },
  "files": [
    {
      "path": "catalog/destinations/destination.example-view/destination.json",
      "byteCount": 214,
      "sha256": "<64 lowercase hex characters>"
    }
  ],
  "contentHash": "<64 lowercase hex characters>"
}

packageID and all content IDs are stable ASCII identifiers of at most 128 bytes. They begin with a letter or digit and then use only letters, digits, ., _, and -. Versions have exactly three numeric components. An update with changed bytes must retain the same declared content IDs and use a greater contentVersion. Importing the same contentHash is an idempotent no-op.

contentHash is SHA-256 over every file record sorted by Unicode path. Each record contributes these UTF-8 bytes:

path NUL decimal-byteCount NUL lowercase-sha256 NUL

This hash is independent of JSON whitespace and ZIP member ordering. The validator also streams and verifies every declared file’s actual byte count, ZIP CRC, and SHA-256.

Envelope field reference

Field Required Meaning
formatVersion yes Integer 1; unknown envelope versions fail closed.
packageID yes Stable identity of this install/update stream; it is not a filename.
contentVersion yes Three numeric components such as 1.2.0; increase when bytes change under the same IDs.
minimumAppVersion yes Oldest Locus version allowed to import the archive, also three numeric components.
contents.destinationIDs yes Unique View IDs owned by the ZIP; use [] when none.
contents.spaceIDs yes Unique Room IDs owned by the ZIP; use [] when none.
contents.experienceIDs yes Unique Experience IDs owned by the ZIP; use [] when none.
files yes Complete generated inventory of every payload file, excluding locusplace.json.
files[].path yes Canonical archive-relative catalog/... path.
files[].byteCount yes Exact expanded byte count.
files[].sha256 yes SHA-256 of expanded bytes, 64 lowercase hexadecimal characters.
contentHash yes SHA-256 identity of the canonical payload inventory described above.

Package-v2 metadata inside the ZIP

The complete envelope uses formatVersion: 1; each destination.json, space.json and experience.json uses formatVersion: 2. The Room v1–v10 numbers in the flat compatibility reference belong to the separate flat space.json input. Do not change a complete space.json to version 10 to use baked light layers. In complete packages, optional capabilities are enabled by their typed fields and guarded by minimumAppVersion.

All package asset paths are relative to the directory containing their manifest, not to the ZIP root. Every reference resolves to a regular file inside that package. Base Package-v2 manifests allow forward-compatible metadata; individual capability profiles may use closed field sets. Do not rely on an unknown field being rendered. The outer envelope rejects unknown fields, and provenance must follow its published schema.

Coordinates are right-handed, meters, +Y up, and −Z forward. Quaternions are [x, y, z, w]. A transform can contain translationMeters (three finite numbers), orientationXYZW (four finite numbers with non-zero length), and scale (three finite positive numbers).

View: destination.json

{
  "formatVersion": 2,
  "id": "destination.example-view",
  "title": "Example View",
  "panorama": {
    "path": "panorama.jpg",
    "projection": "equirectangular",
    "width": 8192,
    "height": 4096,
    "initialYawDegrees": 0
  },
  "provenance": "provenance.json"
}
Field Required Meaning and validation
formatVersion yes Integer 2.
id yes Stable View ID; equals its directory and envelope ID.
title yes Non-empty user-visible View name.
panorama.path yes Package-relative final JPEG or PNG; decoded bytes, not extension alone, determine type.
panorama.projection yes Exactly equirectangular.
panorama.width, height yes Positive decoded dimensions, exactly declared, with width == 2 * height.
panorama.initialYawDegrees no Finite rotation within the canonical panorama frame; default 0.
sourceImage no Optional package-relative JPEG/PNG audit image. It is not rendered, but complete packages may retain it as typed, decoded and budgeted data. Editable/DCC sources remain outside the runtime ZIP.
thumbnail no Package-relative JPEG/PNG catalog image.
caption no Human-readable description.
generation.generator no Truthful generator or tool name.
generation.model no Optional model/workflow version.
generation.generatedAt no Optional timestamp; RFC 3339 UTC is recommended.
depthLayers[] no in Package v2; unsupported in public ZIP imports Reserved ordered depth assets. Public import rejects the field until a product runtime and byte-level validator consume its encoding.
midground.path no Runtime midground USDZ rendered in both Experience Modes; it follows the same member allowlist, structure and resource budgets for bundled, Library and imported packages. See the midground reference.
midground.transform no Optional finite transform for midground.path.
midground.terrainEntities no Unique non-empty names of ground units in midground.path; the runtime uses them to grade terrain beneath the Room footprint.
midground.water no App-rendered water declaration, including validated mask/normal-map and optional shore data described in the field reference.
sky no App-rendered procedural night sky (stars, Milky Way, optional aurora) shown through a validated 2:1 grayscale sky.mask; requires minimumAppVersion >= 1.2.4. See the sky reference.
gpuTexture, gpuTextures no Validated ASTC KTX1 sidecars available to user ZIPs and Library packages alike. The v1 panorama sidecar requires 1.2.3; the v2 source-image array requires 1.3.0. Source hashes, dimensions and mip levels must match.
water, clouds, fireflies no Runtime water (including glow), painted-cloud motion and firefly declarations; require minimumAppVersion >= 1.3.0. Their referenced assets and resource budgets follow the common Package-v2 contract.
environment.sunPath no Authored sun-free sky and optional masked solar disc; requires 1.3.0 and its authoring attestation.
celestial no Animated celestial body and referenced textures; requires 1.2.4, or the newer floor declared by its optional features (cloud-shell motion/evolution requires 1.3.0).
nearOrbit no Near-orbit profiles, texture assets and content-bound fallback proof. v1 and v2 require 1.3.0; v2 includes the tile manifest, tile images and their GPU sidecars.
splat.variants, splat.ply no in Package v2; unsupported in public ZIP imports Reserved future asset references. The splat backend has been removed, so public import rejects the entire field.
splat.metricScaleFactor no in Package v2; unsupported in public ZIP imports Positive finite scale in the reserved contract.
splat.groundPlaneOffset no in Package v2; unsupported in public ZIP imports Finite authored ground offset in the reserved contract.
splat.placement no in Package v2; unsupported in public ZIP imports Optional shared transform in the reserved contract.
environment no Visible-View brightness, optional small SDR Room lighting, and direct-sun defaults below.
audio.path no in Package v2; unsupported in public ZIP imports Reserved runtime audio file. Public import rejects the field until complete audio byte validation and playback are product-supported.
audio.loops with Package-v2 audio; unsupported in public ZIP imports Boolean controlling repetition in the reserved contract.
provenance yes for imports Package-relative provenance JSON.

Complete ZIPs accept the fields this binary actually consumes, plus explicitly retained metadata such as sourceImage, when their payload can be validated as typed and budgeted data. midground is part of the common runtime contract and requires minimumAppVersion >= 1.2.1. depthLayers, splat, and the legacy audio block remain reserved or retired without a current runtime consumer, so this binary rejects them regardless of origin instead of accepting a type-blind path. When a later binary implements one of those capabilities, it must add the field’s asset references, type and budget checks, runtime consumer and app-version floor to the common contract; it must not add a separate public-import switch.

12,288×6,144 (12K) is recommended, not required. Lower resolutions such as 8,192×4,096, 6,144×3,072, or 4,096×2,048 are valid when strictly 2:1 and declared with their real decoded dimensions. Do not upscale a smaller source merely to claim 12K: it invents no detail and wastes memory.

Panorama coordinates are fixed: u = 0.5 is yaw 0 and initial forward/world −Z; u increases eastward toward +X; the seam is u = 0/1, yaw ±180°; and the top image row is the zenith. initialYawDegrees rotates the image inside this frame and does not redefine it.

Optional View lighting:

{
  "environment": {
    "imageBasedLight": "thumbnail.jpg",
    "skyGainEV": 0,
    "exposureEV": 0,
    "horizonPitchDegrees": 0,
    "colorGrade": {"contrast": 1.15, "saturation": 1.2},
    "directSun": {"enabled": false, "illuminanceLux": 5000}
  }
}
Field Meaning
imageBasedLight Separate 2:1 JPEG/PNG used only to light Room geometry and reflections. It must not reuse panorama.path; 1024×512 is recommended and a neutral renderer fallback is used when omitted.
skyGainEV Finite EV gain applied only to the visible panorama.
exposureEV Finite EV gain applied only to IBL.
horizonPitchDegrees Finite authored horizon correction.
colorGrade.contrast Optional SDR visible-panorama contrast from 0.5 through 2.0; 1.0 is neutral.
colorGrade.saturation Optional SDR visible-panorama saturation from 0.0 through 2.0; 1.0 is neutral.
directSun.enabled Whether a separate directional light is authored.
directSun.azimuthDegrees, elevationDegrees Finite direction angles; both required when enabled.
directSun.illuminanceLux Finite non-negative illuminance.

Color grading affects only the displayed SDR panorama. It reuses that texture and does not turn the View into HDR or add highlight detail that is absent from the source. A disabled sun can still carry 5000 lux so Add Sunlight starts at the app default without changing the package’s authored off state.

visibleSkyHDR and hdrManifest are retired and rejected. An outer-package EXR cannot be used as the visible View or Room-lighting map. OpenEXR remains an allowed USDZ texture format for Room assets and is validated inside the model.

Room: space.json

{
  "formatVersion": 2,
  "id": "space.example-room",
  "title": "Example Room",
  "scene": "scene.usdz",
  "thumbnail": "thumbnail.jpg",
  "caption": "A quiet house arranged around a sunlit courtyard.",
  "previewCamera": {
    "yawDegrees": -28,
    "pitchDegrees": -10,
    "zoom": 1.0
  },
  "seatedOrigin": {
    "translationMeters": [0, 0, 0],
    "orientationXYZW": [0, 0, 0, 1]
  },
  "safeHeadVolume": {
    "centerMeters": [0, 1.2, 0],
    "sizeMeters": [1.2, 0.6, 1.2]
  },
  "destinationOpenings": [
    {
      "id": "front",
      "transform": {
        "translationMeters": [0, 1.2, -2],
        "orientationXYZW": [0, 0, 0, 1]
      },
      "widthMeters": 4,
      "heightMeters": 2.5
    }
  ],
  "collision": {"mode": "embedded"},
  "teleportCatalog": {"path": "teleport-points.json", "houseID": "example-room"},
  "quality": {
    "status": "unverified",
    "triangleCount": 0,
    "materialCount": 0,
    "entityCount": 0,
    "maxTextureDimension": 0
  },
  "provenance": "provenance.json"
}
Field Required Meaning and validation
formatVersion yes Integer 2.
id yes Stable Room ID; equals its directory and envelope ID.
title yes Non-empty user-visible Room name.
scene yes Package-relative self-contained USDZ. .blend and other authoring files are not substitutes and cannot be bundled elsewhere.
thumbnail no Package-relative decoded JPEG or PNG shown on the Room card. If declared it must exist, decode completely, and shares the archive image budgets; omit it to use the fallback glyph.
caption no Non-empty user-facing copy for About This Room, up to 2,000 UTF-8 bytes.
previewCamera.yawDegrees, pitchDegrees no Finite starting angles for the bounded 3D preview. They do not affect immersive placement.
previewCamera.zoom with camera Finite positive starting scale for the bounded 3D preview. Reset returns to this authored pose; the viewer clamps it to the supported interaction range.
seatedOrigin.translationMeters yes Three finite meters locating the authored seated-floor reference.
seatedOrigin.orientationXYZW yes Four finite normalized quaternion components; the seated frame must remain upright (no pitch or roll).
safeHeadVolume.centerMeters yes Three finite meters in Room coordinates.
safeHeadVolume.sizeMeters yes Three finite, strictly positive dimensions.
destinationOpenings yes Rectangular opening array. A standalone Room-only import needs exactly one; zero or multiple openings require an authored virtual-space Experience in the same archive.
destinationOpenings[].id per entry Unique package ID used by Experience composition.
destinationOpenings[].transform per entry Required position/quaternion pose.
destinationOpenings[].widthMeters, heightMeters per entry Finite, strictly positive dimensions.
collision.mode yes embedded or separate.
collision.path conditional Forbidden for embedded; for separate, a required package-relative self-contained USDZ validated with the same member, texture, and geometry rules as scene. Its geometry and scene geometry share the Room budget. Renamed authoring sources are not valid.
quality.status yes unverified or verified.
quality.triangleCount, materialCount, entityCount, maxTextureDimension yes Non-negative author measurements. Zero means unmeasured; actual USD is checked independently.
teleportCatalog.path yes Package-relative teleport JSON. Its selected house must contain at least one point; otherwise the Room cannot enter Virtual Space. Its 1 MiB JSON budget is enforced by field role even if the filename does not end in .json; case or Unicode filesystem aliases do not bypass it.
teleportCatalog.houseID yes ID selecting one non-empty houses array in that JSON.
spatialAdaptation.wallEntities no Unique non-empty USD entity names for walls; this metadata does not hide them.
spatialAdaptation.roofEntities no Unique non-empty names disjoint from walls.
spatialAdaptation.deskEntitiesByTeleportID no Map from declared teleport ID to non-empty authored desk entity name.
provenance yes Package-relative provenance JSON.

The USDZ is meter-scale, right-handed, +Y up, and contains supported runtime USD, texture, and audio members only. USDZ members must be stored, not compressed. The validator loads/flattens the actual stage and counts geometry; small or zero quality values cannot hide an oversized model.

Required teleport JSON (the selected house must contain at least one point):

{
  "houses": {
    "example-room": [
      {
        "id": "work-desk",
        "title": "Work desk",
        "anchorXZ": [0.5, 0.5],
        "sourceFloorOffset": 0,
        "eyeHeight": 1.15,
        "yawRadians": 0
      }
    ]
  }
}

anchorXZ is two finite normalized values in 0...1; sourceFloorOffset is finite and non-negative in authored model units; eyeHeight is positive, finite, and measured in meters; yawRadians is a finite model rotation about +Y in the world frame. Point IDs are unique and titles non-empty.

Experience: experience.json

{
  "formatVersion": 2,
  "id": "experience.example-combined",
  "title": "Example Room · Example View",
  "destinationID": "destination.example-view",
  "spaceID": "space.example-room",
  "supportedModes": ["virtual-space"],
  "composition": {
    "destinationOpeningID": "front",
    "destinationYawDegrees": 0,
    "destinationPitchDegrees": 0,
    "destinationExposureOffsetEV": 0
  },
  "compatibility": {
    "minimumViewerVersion": "1.0.0",
    "requiredCapabilities": ["world-tracking"]
  }
}
Field Required Meaning and validation
formatVersion yes Integer 2.
id yes Stable Experience ID.
title yes Non-empty user-visible tile name.
destinationID yes Stable View ID in this ZIP or already installed.
spaceID conditional Stable Room ID; required by virtual-space, normally omitted for room-portal.
supportedModes yes Non-empty unique array containing only virtual-space or room-portal.
composition.destinationOpeningID conditional Required for virtual-space; names an opening in the referenced Room.
composition.destinationYawDegrees no Finite sky alignment offset for this pair.
composition.destinationPitchDegrees no Finite sky pitch offset for this pair.
composition.destinationExposureOffsetEV no Finite relative exposure offset for this pair.
compatibility.minimumViewerVersion no Declarative minimum viewer version string.
compatibility.requiredCapabilities no Declarative capability strings.

View-only and Room-only ZIPs install reusable content and join the default composition matrix. An explicit Experience is necessary only to override one pair’s opening, orientation, exposure, title, mode, or compatibility. References may resolve within the same archive or against built-in/already-installed stable IDs; unresolved references fail the whole import.

Optional capabilities and translated labels

Add optional fields to their owning View or Room manifest, and include every referenced asset beside that manifest. The field reference covers media and masks, sound and Room music, midground and wind, water, sky, clouds, sun path, celestial bodies, near orbit, black holes, point lights, GPU texture sidecars, seats and groups, lights, animations, video surfaces, particles and baked light layers. It also defines localizations, provenanceDetail, and the Room’s viewGroundFloorOffsetMeters, viewGroundKeepOut, and viewMidgroundVisibility controls.

For example, these optional fields belong in an otherwise complete Room manifest, and require envelope minimumAppVersion: "1.4.4" or later:

{
  "viewGroundFloorOffsetMeters": 0.2,
  "viewGroundKeepOut": false,
  "viewMidgroundVisibility": "terrainOnly"
}

They keep their meaning for imported, bundled and Library content. Library capability names in the field reference belong to Library offers; do not add a made-up requiredCapabilities field to locusplace.json. The complete package import path checks its minimum app version and the typed content itself.

Required provenance

Every imported Destination and Space points to a provenance JSON document. It records the creator/agency, exactly one of a license or an all-rights-reserved statement, requested credit, modification notes, whether AI was used, and the AI provider when applicable. Source/original asset URLs are optional but must be HTTPS without credentials or fragments. Missing, oversized or malformed provenance fails the whole import. See the provenance reference.

{
  "formatVersion": 1,
  "creatorOrAgency": "Your name or studio",
  "sourcePageURL": "https://example.com/project",
  "originalAssetURL": "https://example.com/original",
  "license": {
    "identifier": "YOUR-LICENSE-ID",
    "name": "Your license name",
    "url": "https://example.com/license"
  },
  "requestedCredit": "Credit line requested by the creator",
  "modificationNotes": "Converted and packaged for Locus.",
  "aiGenerated": false
}
Field Required Meaning and constraints
formatVersion yes Integer 1.
creatorOrAgency yes Non-empty creator/studio name, at most 200 characters.
sourcePageURL no Public HTTPS project page; no credentials or fragment.
originalAssetURL no Public HTTPS source page; records origin without embedding the original file.
license.identifier conditional Non-empty identifier, at most 100 characters; required with license. Locus does not choose the asset’s license.
license.name conditional Non-empty name, at most 200 characters; required with license.
license.url conditional Public HTTPS license URL; required with license.
rights.statement conditional Non-empty rights statement, at most 1,000 characters; use with rights.url instead of license.
rights.url conditional Public HTTPS rights-statement URL; exactly one of license or rights is required.
requestedCredit yes Non-empty credit line, at most 1,000 characters.
modificationNotes yes Non-empty preparation notes, at most 2,000 characters.
aiGenerated yes Boolean.
aiProvider conditional Required and non-empty when aiGenerated is true; forbidden when false; at most 200 characters.
importSource no Reserved for a View Locus creates directly from an image: photos, files, or url. Third-party packages should omit it.

The document is limited to 64 KiB. Do not copy a starter’s provenance after replacing its assets. The repository’s license and the user’s asset license are separate declarations.

The ZIP is runtime delivery, not an authoring backup. Every payload file must be reachable from a recognized Package-v2 asset field; unreferenced files are rejected even when they appear in locusplace.json.files. .blend, .blend1, .blend2, .fbx, .obj, .mtl, .gltf, and .glb files are rejected even when declared. Keep those source files outside the ZIP. A Room ships the final USDZ; a packaged View ships the final 2:1 panorama and, optionally, one small 2:1 JPEG/PNG Room-lighting image.

Security and resource budgets

Validation examines bytes, not extensions. It rejects traversal, absolute or non-canonical paths, backslashes, control characters, case/Unicode collisions, symlinks, duplicate entries, encrypted content, undeclared files, hash mismatches, unsupported manifests, malformed images, invalid EXR textures inside USDZ, malformed USDZ structure, compressed USDZ members, and unsupported USDZ asset types. Room budgets are checked against the USD stage that the system loader actually parses; the self-reported quality block is authoring metadata and cannot reduce those counts.

The byte-level image allowlist is exact: imported panorama, sourceImage, thumbnail, and imageBasedLight raster fields are JPEG or PNG; USDZ textures may be JPEG, PNG, or OpenEXR. Other ImageIO-decodable formats are rejected rather than accepted implicitly. Outer-package 3D authoring formats are also rejected. A USDZ may contain only USD/USDA/USDC, JPEG, PNG, OpenEXR, M4A, WAV, and MP3 members.

Budget v1 limit
Download or local archive 1 GiB
ZIP entries 4,096
Root manifest 1 MiB
Any payload file 768 MiB
Total expanded payload 2 GiB
Compression ratio 200:1
Any imported JSON file 1 MiB
Provenance JSON 64 KiB
Image dimension 32,768 px
One image 150 million pixels
All View images 500 million pixels
USDZ entries 4,096
USDZ file / expanded bytes 768 MiB / 1.5 GiB
USDZ texture dimension / total pixels 16,384 px / 500 million
Loaded Room geometry 5 million triangles, 1,024 materials, 100,000 entities

Files and URL acquisition

For a View, the Imports page accepts one JPG, PNG, or HEIC from Photos, Files, or a direct public HTTPS URL. Locus normalizes an image to a Destination-only Package v2 payload and passes the transient package through the same validator and atomic installer. Files and URL also accept complete Package-v2 ZIPs for Views and Rooms, including combined packages when they contain the content kind selected by the visitor. Existing flat View/Room ZIPs remain accepted as compatibility inputs. A malformed archive that contains locusplace.json is rejected as an invalid complete package; it never falls back to the flat parser. URL imports reject credentials, fragments, localhost, .local, and non-public DNS results. A hostname is resolved once per request, every answer must be public, and the TLS socket connects to that exact numeric IP while authenticating the original hostname. Every redirect repeats this resolution-and-pinning step before a new connection, at most five redirects are followed, and both declared and streamed download sizes are bounded.

Validation and catalog resolution finish in a private same-volume staging directory. A library-wide advisory lock serializes inventory, quota checks, and commit. First installs use exclusive atomic rename; updates use an atomic directory swap. Any failure removes staging and leaves the installed package untouched.

Package validation and examples

The commands below maintain the complete Package-v2 envelope used by publication and feature-complete imports. The legacy flat tools remain documented in locus-asset-format.md.

Validate either filename without installing:

python3 tools/validate_locusplace.py --current-app-version 1.4.4 MyPlace.locusplace
python3 tools/validate_locusplace.py --current-app-version 1.4.4 MyPlace.zip
python3 tools/validate_locusplace.py --current-app-version 1.4.4 --json MyPlace.locusplace

The Python code uses the standard library, macOS ImageIO through /usr/bin/sips, and the USD tools usdchecker and usdcat (installed with Xcode/Command Line Tools). ImageIO performs a complete pixel decode after structural JPEG or PNG validation; EXR validation is limited to textures inside USDZ, and the USD tools load, flatten, and measure the actual Room stage. It exits nonzero on failure and performs archive, integrity, Package-v2, provenance, image/EXR, and USDZ checks. The visionOS importer remains authoritative for installation and cross-catalog resolution.

Pack an editable directory into an ordinary ZIP, recomputing every integrity record and refusing to publish unless this validator accepts the result:

python3 tools/pack_locusplace.py --current-app-version 1.4.4 MyEditablePlace MyPlace.zip

Generate real View-only, Room-only, and combined example archives into a temporary or ignored directory, then validate them:

python3 examples/locusplace/build_examples.py /tmp/locusplace-examples
for package in /tmp/locusplace-examples/*.locusplace; do
  python3 tools/validate_locusplace.py --current-app-version 1.4.4 "$package"
done

The examples are generated from source without downloading private assets or calling a paid service. The builder and expected layouts are documented in the example README.