@hypersoniclabs/helix-cli
v0.1.27-helix3.676
Published
helix — the HELIX creator CLI: scaffold, validate, and publish Instant Worlds
Readme
@hypersoniclabs/helix-cli
helix — the HELIX creator CLI: scaffold, validate, and publish Instant Worlds to the HELIX
backend. It shares bundle validation with the server via @hypersoniclabs/helix-manifest, so "validate passed but
publish failed" can't happen for contract reasons.
Naming
This is HELIX Creator Tools (the CLI plus the MCP), for use by your own agent (Claude Code, Codex,
Cursor, ...). Dreamer is HELIX's built-in cloud agent on the website; it is a separate product.
The generation commands (helix assets ...) call the Asset Generation API, whose route paths still
start with /api/v1/dreamer/ for historical reasons. Dreamer is one client of Asset Generation and the
publish APIs; Creator Tools are another. Glossary: helix-backend-api docs/architecture/glossary.md.
Architecture boundary
All creator feature and operational logic, validation, file mutation, network behavior, and tests live here. MCP packages may expose transport schemas, resources, instructions, and thin delegation to these commands, but must not reimplement this logic.
Install
npm install -g @hypersoniclabs/helix-cliAuthentication
helix login opens your browser to the website's /cli-login page; you sign in there and the CLI
captures a client token via a one-shot loopback redirect. For CI/headless, pass a token directly:
helix login --token <token> (mint one in the Creator Hub → Settings → Client Tokens).
The token is stored in ~/.helix/credentials.json (mode 600). It's sent as
Authorization: Bearer <token> on publish calls. helix login validates it against
GET /api/v1/users/me before saving and reads the token's exp so the CLI knows when it lapses.
The backend & website default to production: the published
@hypersoniclabs/*CLI (built fromhelix3, see docs/npm-publishing.md) targetshttps://api.helixgame.comandhttps://helixgame.com. The former helix3 hosts (helix-backend-api-helix3.up.railway.app,new.helixgame.com) are the same environment, so a login saved under them keeps working without logging in again. The retired@helix3/*CLI resolves to production too;@helixdev/*targets staging. Override per run with--api-url/HELIX_API_URLand--web-url/HELIX_WEB_URL(e.g. a local/dev backend).
Commands
| Command | What it does |
|---|---|
| helix bridge detect\|inspect\|plan\|import\|validate\|doctor | Universal plugin-driven intake for assets, characters, animations, scenes, and engine projects. Produces deterministic plans, HELIX-native outputs, and checksummed provenance. See docs/bridge.md. |
| helix bridge plugins create\|test\|list\|inspect\|install\|remove | Build and manage local/community Bridge Plugins using the versioned capability and permission contract. |
| helix login [--api-url <url>] [--web-url <url>] [--token <t> [--refresh-token <r>]] [--no-browser] | Sign in via the browser (/cli-login), or --token for CI/headless. Stores the client token. --refresh-token (or HELIX_REFRESH_TOKEN) pairs a short-lived access token with its refresh token so a long command (assets resume) renews it when it expires. |
| helix logout | Remove the stored token. |
| helix whoami | Show current login status. |
| helix init <dir> [--kind character\|multiplayer\|scene] | Scaffold a complete, build-ready Vite + TypeScript world. --kind scene emits a multiplayer world whose ENVIRONMENT is a sealed HELIX Scene v2 document — the starter document is compiled offline into public/scene/mall/, pinned in public/helix.json's scene field, kept out of the bundle by public/.helixignore, and lit by public/helix.visuals.json through the scene + visual system pins. |
| helix install [--update] [--asset-pack <slug@range>] | Resolve a world's systems/abilities pins (a v0.2 or v0.3 manifest) → materialize the modules, the three import map, and helix.runtime.ts. Repeat --asset-pack to constrain specific system assets for this resolution. |
| helix validate [dir] | Validate a bundle locally — the exact rules the server enforces. |
| helix publish [dir] [--thumbnail <file>] [--upload-source] [--source-dir <path>] | Validate → resolve/create the world → upload files → finalize → set the cover image → print the play URL. --upload-source additionally sends the project's source through a separate private channel so the world can be edited on the website later — off by default. |
| helix list | List your worlds. |
| helix vehicle simulate <dir\|vehicle-package.json> [--item <id>] [--spec <file>] [--offline] [--json] | Drive the package offline with the platform's own engine instrument (the runtime ^0.3 serves today, cached by sha; offline = last cached, labelled) and judge 0-100 km/h, 0-60 mph, quarter mile, top speed, 100-0 km/h / 60-0 mph braking, skidpad g, turbo lag and the rest pose against the package targets and --spec. Exit 0 pass, 1 a figure out of tolerance, 2 could not simulate. See docs/vehicle-simulate.md. |
| helix vehicle validate <vehicle-package.json> [--glb <model.glb>] [--no-simulate] [--json] | One-second local gate for a hand-authored Universal Vehicle, run before Package publication. Checks the package against the engine's own validator (vendored, pinned to an engine commit), the engine publish gate, the backend pin requirements, groundToBodyOrigin vs wheelRadius + staticRideHeight, and the componentBuild digest; with --glb also resolves the four wheel nodes, checks each wheel node's origin is at its hub (WHEEL_PIVOT_NOT_AT_HUB), the +X-left / +Z-forward frame, the vehicle publish budgets the articulation nodes and the add-on host manifest (asset.extras.HELIX_vehicle); drives packages that declare targets (helix vehicle simulate). Exit 0 no errors, 1 errors, 2 unreadable input. See Vehicle validate. |
| helix vehicle publish <dir> --content-rating <r> [--item <id> --package-version <v> --reason <t>] [--json] | Package-first vehicle publication: validate, upload the model and samples, seal a public Vehicle Package (root pins the model; publish.json hostGrantExtensions sealed beside it), make the Item from that exact Version, read back pin, identity, audio and the host board (sockets, tunable paths). Resumable; --item stages a new Version and moves the car. Exit 0/1/2. See docs/vehicle-publish.md. |
| helix vehicle pull <item-id> <dir> [--package-version <v>] [--content-rating <r>] [--json] | Rebuild a publishable COPY of a live car from its sealed Package Version (public Continuum routes): vehicle-package.json, every audio object, one model per served rendition, publish.json (legacy rating general -> everyone) and pull.json. No ledger, so vehicle publish <dir> mints a new Item. See docs/vehicle-publish.md. |
| helix vehicle addon publish <dir> --content-rating <r> [--json] | Package-first vehicle add-on publication: the full vehicle addon check, seal a public addon Package (a tune-only part seals no model), make the Item, read back pin, backend fit and each host's add-on rail. Exit 0/1/2. |
| helix vehicle preview [<dir-or-package.json>] [--glb <model.glb>] [--addon <dir>]… [--item <id>] [--tolerance-cm <cm>] [--out <dir>] [--proxy-item <id>] [--json] | Render a vehicle that is not published yet headless in the real runtime (hosted test grounds, Try Now through a published proxy vehicle whose car is swapped for your package + GLB, served from disk; nothing uploaded or recorded) and measure it: body-origin height against the declared groundToBodyOrigin (the number that locks at first mint), wheels on their hubs, arch clearance, rolling radius vs wheelRadius, steering, doors, seats. Prints a numeric summary with PASS/FAIL per check and writes frames, a contact sheet, bundle.json and preview.json. Boards with the candidate's own seat ids. With --addon <dir> (repeatable) it previews unpublished vehicle add-ons instead: fits each to the candidate car or a published car and measures the drawn geometry in world space against its anchor (wheels within 1 cm of each hub, socket/host-framed parts at their mount, not at the car's origin; tune-only ops read back from the live tuning). Exit 0 pass, 1 fail, 2 could not measure. See docs/vehicle-preview.md. |
| helix continuum publish-glb <mesh.glb> --title <t> [--package-type asset\|character] [--offer marketplace\|world --price-lix <n> --content-rating <rating>] | Seal a Package first; optional offer makes it ownable. See Package-first GLB publication. |
| helix continuum make-item --package-id <id> --package-version <v> --manifest-cid <cid> --content-rating <rating> [--offer marketplace\|world --price-lix <n>] | Project one exact sealed Package Version into an Item, with an optional first offer. |
| helix continuum publish-owned-item <item-id> --package-id <id> --package-version <v> --manifest-cid <cid> --content-rating <rating> --accept-rating-attestation | Publish an existing personal Item from its unchanged public sealed Package pin. Offers are separate. |
| helix vehicle audio-import <mod dir\|mod .zip\|AC car dir\|.bank> --out <dir> [--sim beamng\|ac\|auto] [--vehicle <id>] [--config <file.pc>] [--map <slot-map.json>] [--beamng-root <dir>] [--cylinders N] [--base-package <vehicle-package.json>] [--json] | Takes a car's sounds from the sim mod that carries them, never synthesised: a BeamNG mod's own ladders and every sound its parts name (stock FMOD events resolved in the install's banks with --beamng-root, which also supplies what the game itself plays for unnamed slots), or an Assetto Corsa car's FMOD bank. Writes audio/*.wav (16-bit PCM, malformed headers repaired) and audio.json (the package audio block, all 26 live slots (the retired tyre/brake squeal slots are never emitted), provenance on every slot, bank sample and blend). --map merges slots from several sources. The block always carries the runtime's mix fields (gains, load mix, muffling): from --base-package to keep a car's current mix, else the mod's soundConfig, else the engine's defaults. Exit 1 when a slot stays empty. See docs/vehicle-audio-import.md. |
| helix vehicle carbon-weave <in.glb> <out.glb> [--tile-cm <cm>] [--roughness <r>] [--normal-scale <s>] [--carbon-materials <regex>\|none] [--ktx2] | Give a vehicle GLB's carbon-fibre a tiling 2x2 twill detail normal + roughness map at a real ~1.2 cm tile (UV scale per material from 3D/UV area), clearcoat roughness, non-metal. Tags source names that are exposed carbon first (default EXT_Carbon*, EXT_Plastic_shinny, EXT_painted_metal; an unmapped EXT_painted_metal rips as silver sides). Exit 1 when nothing was woven. See docs/vehicle-carbon-weave.md. |
| helix vehicle audio-check <dir\|package.json\|audio.json\|item-id> [--reference <clip>]... [--json] | Proves every sound the runtime will select resolves, is PCM WAV and is audible (pre-publish on local files, or read-back on a published item), and that no two roles share one recording (shift up = shift down, an exhaust rung = an engine rung) and the rev limiter is a short crack the runtime can retrigger. Prints each slot's provenance and FAILS synthesised or generated audio (AUDIO_SYNTHESISED), a car without a recorded engine ladder (AUDIO_ENGINE_UNSOURCED), a missing or empty slot (AUDIO_SLOT_UNSOURCED, except absent: "not-fitted" / "combined-with-engine") among the 26 live slots (the retired tyre squeal, scrub, wheelspin, skid and brake squeal slots only warn: AUDIO_SLOT_RETIRED), recordings without provenance (AUDIO_PROVENANCE_MISSING) and a not-fitted claim the physics contradicts (AUDIO_SLOT_FITMENT_MISMATCH) and an audio block without finite mix fields (AUDIO_MIX_FIELDS_MISSING: a NaN gain freezes the car in Try Now); vehicle publish refuses the same. --reference <clip> (repeatable; slot=clip for a slot) compares the idle voice's centroid and firing roughness with a real-car recording. |
| helix vehicle addon check <dir> [--json] | Add-on fit gate for local source and host compatibility, including the web customizer's meshless guard (ADDON_CUSTOMIZER_REFUSES: a part with no model.glb must be a verified tune-only/sound-only definition). |
| helix vehicle addon check --item <addOnId>... / --host <vehicleId>... [--json] | Published mode (public reads, no sign-in): reads each add-on Item's exact Package pin and runs exactly what the web customizer runs; --host checks every add-on on the car's rail. Exit 1 = the customizer would refuse a part, with its message. |
| helix item list-slots [--json] | Print the Character-Creator vocabulary a wearable declares: the cosmetic slot tags, and the genders. |
| helix item distribution create\|list\|update\|disable … | Manage Marketplace/World Claim or Buy routes without republishing the item or paying another publish fee. |
| helix item visual-qa <item-id> [--submit] [--local-critic] [--storage-state <file>] [--host <id…>] | Render the item's current package version headless in the real runtime (test-grounds world for vehicles and whole avatars, the customiser for add-ons and wearables on every declared host/base, the item viewer for props), measure it, and with --submit record the backend's verdict (numeric asserts + vision critic) against that exact version. The visual check is advisory: it records notes with fix hints and never gates listing, distributions or version moves. Only a technical failure (an unusable file, with its plain reason) or content safety blocks. See docs/visual-qa.md. |
| helix item visual-qa <item-id> --package-version <v> [--vehicle-package <file>] | Render a version the item is not pinned to yet and record advisory notes for it before (or after) the pointer moves; the move itself never waits on it. Vehicles and avatars go through Try Now (vehicles also need the move's vehicle package), add-ons and wearables through the customiser; the bundle names the candidate, and the vehicle checksum is the one the server minted for that trial. Never falls back to the current version. See docs/visual-qa-candidate-versions.md. With --submit, a PASS prints the next command (helix item set-version …) for a version not moved yet; a 403 for a non-creator says so on the first line; --status also lists the item's pending (sealed, not current) versions. |
| helix item set-version <item-id> --package-version <v> --reason <text> [--expected-current-version-id <uuid>] [--as-admin] [--json] | Move an item to a sealed Package Version of its own package (pin resolved from the public Package API; POST /api/v1/universal-items/<id>/versions). The flow for a listed item: continuum publish-package seals vN → item set-version; the visual check is advisory and never gates the move. A refusal (a technical failure or content safety) prints the server's plain reason first, then the remedy. An unknown version lists the sealed ones. A vehicle moves its car with the pin: the version's sealed root (role vehicle-definition) is verified against the manifest and sent as vehiclePackage, the same rider vehicle publish --item sends; a version without one is refused with a pointer to that resume. A version the item already had is a rollback: PUT /api/v1/universal-items/<id>/versions/current { revision, reason } (the server restores that revision's retained vehiclePackage; a pin-only revision has none and is refused). --as-admin (platform admins, for an item another account created) posts the same body to POST /api/v1/admin/universal-items/<id>/versions: same gates (visual QA included, no override), --reason recorded with you as the actor. |
| helix item thumbnail refresh <item-id> [--force] [--dry-run] [--json] | Re-render a published item's canonical thumbnail from its active Package Version with the standard server renderer (POST /api/v1/universal-items/<id>/thumbnail/regenerate, owner or admin). Prints the status (generated / current / dry-run), old -> new thumbnail URL, renderer version and the source lane + Package Version. --dry-run writes nothing; --force re-renders even when the stored thumbnail is current. Moving an item to a new Package Version now refreshes the thumbnail automatically unless you uploaded one. An older backend answers 404 and the CLI says it does not support thumbnail refresh yet. Not the world helix thumbnail set <slug> command. |
| helix item thumbnail set <item-id> <image> [--json] | Upload your own thumbnail (png/jpg/webp, up to 10 MiB, checked locally) for a published item (PUT /api/v1/universal-items/<id>/thumbnail, moderated). A creator upload stays in place across version moves until you run thumbnail refresh --force. 403 = you are not the item's owner. |
| helix item lods <packageId>[@version] \| --item <id> \| --glb <file> [--out <dir>] [--variant <id>] [--profile prop\|vehicle] [--api-url <url>] [--json] | Compile the helix.item-lod/1 Runtime Artifact for a published prop, Device or vehicle Package Version: up to two levels, LOD1 (props at most 25% of the triangles, vehicles max(5000, 10%), textures at most 512 px) and LOD2 (at most 300 triangles, textures at most 256 px), each its own GLB drawn in one primitive per render state (usually 1 or 2, via a baked material atlas) with its own textures (never larger than what they replace) and a measured maxErrorM, plus an 8-view 512 px octahedral impostor atlas. A level that does not cut half the triangles, or cannot be made strictly smaller in bytes (a vehicle LOD2 also: one over 25% of the extent from the source), is omitted. Content-addressed and deterministic; skinned input (avatars, add-ons) is refused. Local preview; the backend worker is the catalogue's source of truth. See docs/item-lod.md. |
| helix device create <dir> --id <slug> --title <t> --model <mesh.glb> --template standing-desk\|clock\|television | Scaffold a GLB-backed helix.device/1 definition, including the interactive TV/media-player template. There is intentionally no website Device editor. |
| helix device validate [dir] [--json] [--room] | Validate the definition, GLB bytes and named-node bindings, typed state/controls, capabilities, the runtime 512-step graph budget, deadline triggers, fill action args, and Home+World compatibility, including declarative character hand reach. --room also refuses state writes the room cannot run: any write reachable from an interact trigger through emitSignal handlers, delay or branch (the rule the backend applies when sealing a new publish). |
| helix device test [dir] [--mode single\|multiplayer\|both] [--json] | Exercise every interaction, persistence remount, and deterministic one-/two-client authority convergence. |
| helix world scene-elements compile <input> --output <scene> | Compile typed Scene v2 authoring, including exact Continuum Device pins, instances, and locally validated typed links. See examples/scene-elements-devices.json. |
| helix preview-video <slug> <youtube-url> | Set or clear a published world's YouTube preview. |
| helix thumbnail set <slug> <file> | Upload a thumbnail or preview image through the CLI-owned media path. |
| helix assets search\|get\|versions\|track\|install\|update | Search the typed Vault with explainable ranking; inspect/manage annotations and immutable versions; install by durable ID with verified SHA-256 receipts. Material installs default to lean runtime KTX2 maps; use --material-renditions source for source PNGs or all for both families. |
| helix assets start\|status\|generate\|resume | Start or poll generation without duplicates. Props/characters use the Asset Generation job lane; standalone assets use the shared broker and report the auto-published vaultAssetId. generate and resume block waiting for the job (see --timeout/--poll-interval below). |
| helix assets voices [--search <text>] [--category premade\|professional] [--page-size <n>] [--page-token <token>] [--json] | Discover safe text-to-speech voices through the authenticated HELIX catalog, including language, labels, preview, default/recommended selection, and pagination. |
| helix assets generate-image\|generate-material\|generate-audio\|generate-environment | Run the supported standalone generation adapters. Material is a validated PBR bundle (1K default, explicit 2K); splats declare object/environment scope. Animation remains an explicit unavailable boundary. |
| helix assets start character "<prompt>" [--title t] [--visibility v] [--idempotency-key k] [--max-lix n] | Starts a character on the server-side Asset Generation job driver (POST /api/v1/asset-generation/jobs, mode: deterministic) and returns at once; stdout is the driver job JSON (id). --visibility is sent only when you pass it; the server owns the default and every other policy (reference review, redraws, retries, visual QA wait, WARN/BLOCK, refunds). The create is retry-safe: the CLI picks one idempotency key (a UUID, or your --idempotency-key, 8-96 chars), sends the same key on every try (3 tries on a timeout, dropped connection or 5xx), and the backend returns the existing job for the same owner and key, so a timeout after the server created the job never duplicates or double charges. The key is printed on stderr and in the stdout JSON as idempotencyKey; if a run's answer was lost, re-run the same command with --idempotency-key <key>. |
| helix assets resume <id> [--timeout <ms>] [--poll-interval <ms>] (driver job) | Waits for succeeded, held, failed or handoff (default 80 min, poll 3 s), one progress line per change on stderr (stage, reference gate, scheduled retry, policy events, cost). Final stdout is the driver job JSON plus top-level publishedItemId (= result.itemId). Exit 0 for succeeded, held (the item exists but is not released; result.qa says why; helix assets refund <id> is offered) and handoff; exit 1 for failed (JSON on stdout, message with escalate/retriable on stderr) and for a client-side timeout (nothing on stdout). --visibility must match the job's (it is fixed at start) or the command exits 1. An access token that expires during the wait is renewed (see Login below: helix login --token <access> --refresh-token <refresh> or HELIX_REFRESH_TOKEN; otherwise another process's fresh helix login is picked up); a login that cannot be renewed ends the wait with helix login + helix assets resume <id>, and the job keeps running. An id the driver does not know (props, older characters) resumes through the previous loop below. helix assets generate character is start + resume. |
| helix assets act <id> approve\|revise ["guidance"], helix assets refund <id>, helix assets status <id> --pipeline driver | Decide at the open reference gate, get the LIX back for a held avatar, or read the raw driver job. |
| helix assets start-reference\|generate-reference | Start or fully drive a character job from a local four-view PNG/JPEG/WebP. The backend validates and reuses the exact sheet; it skips image generation while preserving mesh, texture, rig, LOD, thumbnail, billing, and Vault publication. generate-reference blocks waiting for the job (see --timeout/--poll-interval below). |
| helix assets generate\|generate-reference\|resume [--timeout <ms>] [--poll-interval <ms>] | These three drive an Asset Generation prop/character job to completion (concept → mesh → texture → rig → publish) and block until it finishes. --timeout bounds how long the CLI process waits before giving up (default 1200000ms / 20min, unchanged if omitted); --poll-interval sets how often it checks job status while waiting (default 2000ms). A timeout is a CLIENT-SIDE giveup, not a job failure — the job keeps running server-side. The error prints the exact helix assets resume <job-id> command to check on or continue it; on a genuinely slow stage (e.g. mesh generation), rerun with a larger --timeout, or just poll separately with helix assets status <job-id> --pipeline dreamer. |
| helix assets materials [--resolution <res>] [--no-resolve] [--human] | Discover the small built-in material palette. --resolution keeps only materials carrying that texture resolution. Each entry also carries the Scene v2 vault pin and the continuum package ref; --no-resolve skips those lookups. |
| helix assets material <id> [--resolution <res>] [--no-resolve] [--human] | Resolve one material to its immutable map URLs at the resolution you pick (default: the pack's own default), plus its vault pin and continuum ref. |
| helix assets credits\|check-loaders | Render asset credits from provenance and reject models requiring unsupported loaders. |
| helix character import <body.glb> [--map <json>] [--fit source\|canonical] [--provenance <json>] | Convert one self-contained third-party humanoid through the public helix-humanoid@1 pipeline. The command rejects external/remote resources and resource bombs before parsing, generates bounded LODs, and emits a source/license/transformation/checksum receipt beside the output. --fit canonical aligns the rest skeleton/proportions; it does not prove that Base Male-targeted garments fit a different body surface. Hands fused to the thighs (an A-pose mesher merging touching surfaces) are cut free and re-weighted per side, gated on a walk-stride stretch measure; --limb-bridges report only logs what it would change, off skips it. |
| helix character check <avatar.glb> [--out <dir>] [--json] [--no-inspection] | Judge an avatar GLB before it is published: the production Marketplace viewer plays the platform's walk, the engine's run and idle on it (nothing uploaded), the hosted gate's own avatar@3 math plus offline stage-1 and run/idle asserts judge it, and a fixed inspection set (walk at the back-swings from front, 3/4, true sides and back; run; idle turnaround; head/hands/feet) is written beside Base Female. Exit 0 pass (a floor, not a quality verdict), 1 a blocking assert failed (with measured value and next step), 2 could not measure. See docs/character-check.md. |
| helix character fit <addon.glb> --body <base.glb> -o <out.glb> (--slot-type <character.x@1>\|--hidden-regions <globs\|none>) [--clearance-mm 3] | Pose-aware poke-through fit for a packaged character add-on (every LOD): across the canonical pose set, pushes garment geometry out along the body-facing normal to a minimum clearance, smoothed so hems and silhouettes keep their shape. Gated inside the stage by the poke-through measurement: the output is the fitted mesh only when it is strictly better (exit 0), otherwise a byte-identical copy of the input (exit 3). Bridge add-on imports run the same stage automatically; use the fitted output in a new sealed Package Version. |
| helix character ear-clearance <addon.glb> --body <base.glb> (-o <out.glb>\|--check) [--target-mm 4] [--trigger-mm 1.5] [--max-push-mm 18] [--json] | Head-zone check and fix for headwear / mask / hair / eyewear: finds the base body's ears, measures the clearance to the OUTERMOST garment layer around each (horizontal ray from the head axis, per LOD; only samples the garment wraps on all four sides count, so a mask that stops in front of the ear or a cap above it is not a defect) and, when a wrapped ear has < 1.5 mm, pushes the garment smoothly outward (Gaussian field, sigma 22 mm, horizontal only, <= 18 mm total, every LOD). Kept only when no ear pokes afterwards. --check writes nothing and exits 3 when an ear pokes through. helix character fit runs it automatically for head-zone slot types (--no-ear-clearance skips it; --ear-clearance forces it); seal the corrected mesh in a new Package Version. The pose-aware fit cannot do this job on a thin head shell. |
| helix character derive-hide <addon.glb> --body <base.glb> --slot-type <character.x@1> [--threshold 98] [--ops-out ops.json] [--json] | Measures which of the slot's granted body regions the garment fully occludes (bind pose; a body vertex counts when garment is in front of it along its outward normal within 60 mm; the least-covered LOD decides) and prints the hide ops that follow, with the measured share of every region left visible. A region is hidden only at >= 98 % AND when no triangle the hide would collapse (engine rule: >= 2 corners hidden) reaches a body vertex the garment does not cover (the sawtooth under a hem or collar); partly covered regions stay visible and the pose-aware fit keeps their skin from poking through. Bridge add-on conform does this automatically when the profile names no hideRegions; include the resulting operations in the new sealed Package Version before moving the Item. |
| helix character retarget-animation <clip.fbx\|clip.glb> | Retarget a raw Mixamo/Meshy clip directly to helix-humanoid@1. FBX conversion is deterministic and runs inside the CLI; no hidden Blender/DCC pre-export is required. |
| helix character export-vrm <avatar.glb> [-o out.vrm] [--lod n] [--rest-pose auto\|always\|off] [--name ..] [--author ..] [--json] | Export a published HELIX avatar as a VRM 1.0 file: humanoid map, expressions and visemes from helixFace, spring bones and colliders from helixDynamics, lookAt, firstPerson, meta and MToon materials. A-pose arms are re-posed to the T-pose VRM requires and the mesh re-skinned. See docs/vrm-export.md. |
| helix character import-vrma <clip.vrma> [-o clip.glb] [--clip-name n] [--loop] | Import a VRM Animation (.vrma) as a helix-humanoid@1 clip GLB through the same retarget as Mixamo/Meshy clips; usable as an emote. See docs/vrm-export.md. |
| helix pack scaffold\|validate <dir> | Scaffold and validate locomotion clip sources offline. Seal an add-on Package before any Item or offer is created. |
| helix world audit\|source-audit\|perf-gate\|prove-live | Run the blocking world QA, performance, and deployed-build identity gates. |
| helix world performance-report [bundle] [--scene <scene-v2.json>] [--package <package-version.json>] [--snapshot <inspect.json>] [--perf <receipt.json>] | Analyze exact declared scene resources and existing reference-profile budgets, while keeping authored geometry/material/delivery estimates separate from supplied runtime claims. Prints actionable instancing, Palette, lighting, transfer, and visual-QA guidance; it never predicts FPS from static counts. |
| helix world scene-source inventory\|compile <helsinki\|bistro> <archive> | Verify and safely inventory a known CC BY source archive, then compile deterministic local proxy/detail GLBs and an existing-schema Scene v2 package. |
| helix world scene-source publish <world-id> <package-dir> [--publish] | Validate the Scene v2 package, refuse it if it breaks the open-world performance rules (docs/scene-perf.md; no override), and print its multipart build receipt. Execution remains dry-run-only until backend private-pin entitlement lands; --publish currently fails closed, and access control never changes CC BY rights. |
| helix scene validate <dir\|file> [--perf] [--tier desktop\|mobile\|desktop,mobile] [--out <report.json>] | Check a Scene v2 package against the shared validator and, with --perf, the 14 open-world performance rules (textures, geometry, cells, draws, budget, first-playable bytes). Exit 0 pass, 1 refused, 2 cannot be judged. See docs/scene-perf.md. |
| helix scene split <input> -o <dir> [--cell-size m] [--core-radius m] [--spawn x,y,z] [--textures ktx2\|keep] | Split a monolithic environment (one big shell GLB, or a Scene v2 document with an eager shell) into a small eager spawn core plus distance-streamed cells on a fixed, content-addressed grid. Writes a validated Scene v2 package with per-cell bounds, priority and an honest memory budget, plus a report of eager bytes before and after. Deterministic and byte-identical on a rerun; never publishes. See docs/scene-split.md. |
| helix scene retexture <split-dir> -o <dir> [--merge-psnr dB] [--uastc-rdo 2,1\|off] [--dry-run] | Shrink the textures of a split Scene bundle without moving a cell: merge textures that decode to the same picture, re-encode UASTC data and normal maps with RDO above a PSNR floor, rewrite the image uris in the GLBs that named them, reseal and validate. Nodes, stream groups, bounds, spawn and budgets are untouched. Never publishes. See docs/scene-split.md. |
| helix continuum publish-source-map\|publish-scratch\|publish-package\|publish-glb\|make-item\|publish-owned-item | Resume exact-CID object adoption/ingestion, Package lease/seal, optional explicitly authorized Listing, and WorldRelease publication through one creator-owned pipeline. |
| helix continuum receipt <job-id> | Read the sanitized durable receipt for a completed Continuum publication. |
| helix unreal init\|validate\|doctor\|package [project] | Create and gate a shared-runtime or native-game publish manifest, check optional editor MCP, and assemble a local upload-shaped package. |
| helix unreal publish <candidate> --world <slug> | Upload a cooked Unreal world to HELIX: verify the candidate, presign, upload every artifact, finalize, and promote it as the world's active build. |
| helix doctor [--project <dir>] | Print the environment + whether the @helix toolchain (CLI/MCP/SDK/manifest) is current. |
Constraining system assets during installation
To update system code while deliberately selecting a compatible asset version:
# helix.json keeps the range, e.g. humanoid-character: "^0.3"; only the asset pack is constrained
helix install --update --asset-pack [email protected]--asset-pack <slug@range> is repeatable for different asset slugs. Supported ranges
are exact versions, ^/~ ranges (partial versions are padded as for system pins),
latest, and *. Resolution selects the highest active version satisfying both
the explicit constraint and the system envelope's asset requirement. Complete exact
pins use the authoritative public version-detail endpoint, including active staged
versions. Incorrect kinds/identities, unused slugs, duplicates, invalid ranges,
missing versions, and incompatible constraints fail before installer writes.
These constraints apply to this invocation. An existing lock requires --update;
a fresh install can use the option without a lock. The installer resolves from the
catalog and writes the exact asset URL/version and unchanged envelope range into
the lock, with invocation intent recorded in top-level assetPackConstraints.
It does not reuse unverified asset metadata
from an old lock. Normal helix install then replays that generated lock as usual.
A subsequent helix install --update without the option refreshes all dependencies
from their ordinary requirements, including previously constrained assets. Repeat the
option in your update command to retain the constraint. It is not a world manifest
field or a permanent asset freeze, and cannot be combined with local first-party
installation. Generated locks, import maps and runtime modules remain installer-owned.
Unreal runtime projects
helix unreal init <project> --mode shared-runtime|native-game creates helix.unreal.json.
Shared-runtime projects ship cooked content/Blueprints against a pinned HELIX runtime; native-game
projects ship separate signed client/server builds and may use custom C++ and plugins. Both require
Unreal 5.8, the HelixSDK plugin, and HelixPlatform identity/session integration.
Networking is explicit: unreal-native, helixnet, custom, or none. Only helixnet requires
contract v21; blank Unreal projects using arbitrary GameMode/Pawn classes and native networking do
not need Lyra Experiences, Colyseus, or the HelixNet contract.
helix unreal validate is the publish gate and does not require MCP. helix unreal doctor adds the
loopback/editor-only Unreal MCP authoring checks. helix unreal package copies validated cooked or
native artifacts into a local checksummed staging directory — it is a local assembly step, not a
publish.
Publishing a cooked world
helix unreal publish <candidate> --world <slug-or-id> takes the directory the Unreal
HelixWorldPublish commandlet writes — world-build-manifest.json, its .sha256 receipt,
cook-plan.json, and artifacts/<role>/… — and gets it live in one command:
helix unreal publish Saved/HelixPublish --world my-world --max-players 32 --visibility unlistedIt verifies the candidate against the bytes on disk (every digest recomputed — a manifest is a
claim, not evidence), registers the Build and presigns each artifact, streams the uploads,
finalizes so the server re-verifies every byte by size and SHA-256 and countersigns the manifest,
then promotes the Build as the world's active release. --no-promote stops after verification;
--verify-delivery additionally checks what a client would actually download.
The idempotency key defaults to the cooked manifest's own digest, so re-running after a dropped connection resumes the same Build rather than minting a second one.
Only shared-runtime worlds publish this way. A World Build may not contain creator
executables, so native-game builds are a separate distribution path.
Working in a git worktree? Run this first
npm run check:dev-linkspackage.json links its sibling HELIX packages by relative path
(file:../helix-manifest, file:../helix-web-sdk). That is right in the canonical
layout, where the siblings sit beside this clone — but in a git worktree ../ is
the worktree parent, so the links resolve to whatever happens to be there: a checkout
parked on an older branch, or nothing at all.
The symptom is not "broken dev link". It is a branch that looks broken: tsc
reporting has no exported member … in files your change never touched, and whole
suites failing at import (could not find the dev shell in @hypersoniclabs/helix-sdk).
People have concluded a PR was broken on the strength of exactly this.
check:dev-links names which link is wrong, which imported symbols the linked sibling
does not export, and how to fix it without repointing a shared directory other
worktrees resolve through.
Publish flow
Platform system updates
helix install records platform-owned systems in public/helix.runtime.json.
Compatible ranges such as ^0.2 are externalized and resolved to an active,
immutable catalog module when a world launches, so a compatible platform fix can
reach an already-published world without creator edits or republishing. A new
runtime version reaches no world until the platform deliberately publishes it to
the catalog and it goes active there — building or merging one changes nothing on
its own, and there is no rolling "latest".
Engine systems are ranges. Declare "systems": { "humanoid-character": "^0.3", "engine-core": "^0.1", "scene": "^0.3", "visual": "^0.1" }
so a World loads the latest promoted engine at launch. An exact version such as
0.3.178 maps to the immutable catalog URL and freezes the World on that build
forever, so helix validate and helix publish refuse it. If an engine fix
is staged but not yet promoted, promote it (POST /api/v1/instant-packages/<slug>/versions/<version>/rollout,
{"rolloutPercent":100,"reason":"…"}); do not pin Worlds to it. The one escape
hatch is deliberate and audited:
helix publish dist --allow-exact-engine-pin "<why this World must stay frozen>"The reason is required (an empty one is refused), a loud warning is printed, and the
override (reason, slug, pinned versions) is appended to
~/.helix/engine-pin-overrides.jsonl (HELIX_ENGINE_PIN_OVERRIDE_LOG relocates it).
helix validate takes the same flag. A custom Vite config is never rewritten: install reports
legacy-bundled until the creator explicitly externalizes @helix/*.
helix validate, helix publish, and the publish API refuse that legacy mode
too. latest and * are not accepted publish ranges because they
can cross a compatibility boundary.
The platform runtime is on three 0.185.1. The version is a property of the
pinned system — humanoid-character@^0.3 resolves to an envelope declaring it —
and helix install bakes it into the import map immutably, so an already-published
world keeps running whatever it baked. A world behind that line still publishes:
helix validate and helix publish only warn, naming the upgrade path (re-pin
^0.3, helix install --update, rebuild, republish), and helix doctor reports it
(and a local devDep three that has drifted off the platform runtime) before you build.
That update contract is deliberately narrower than "the SDK updates itself":
| World dependency | What the built bytes contain | What can advance without republishing |
| --- | --- | --- |
| Platform systems (@helix/humanoid-character, @helix/engine-core and declared subpaths) with a compatible range | Bare imports plus a generated import-map entry | The compatible catalog target on the next fresh launch |
| Platform systems with an exact version (refused by validate/publish without --allow-exact-engine-pin "<reason>") | Bare imports plus the exact immutable catalog URL | Nothing; the pin stays byte-identical |
| Creator SDK (@hypersoniclabs/helix-sdk) | The SDK module graph bundled into the world's own JavaScript | Nothing; SDK changes require rebuilding and republishing the world |
| Installed abilities (public/helix_modules/*) | Verified ability code/assets copied into the world build and indexed by installed.json | Nothing; update/install and republish are explicit creator actions |
| Creator game code and assets | The world's own immutable build files | Nothing; the platform never rewrites a published creator bundle |
The generated import map, helix.runtime.json, and
helix_modules/installed.json are the authoritative way to inspect which side
of this boundary a particular build uses. Browser or proxy caching may serve a
previous compatible resolver response during a transient outage; the descriptor's
lastKnownGood field is provenance metadata, not an executable in-world fallback.
Without a cached resolver response, a cold resolver failure fails closed.
Native features remain individually overridable without pinning the whole
character runtime. The character system's native avatar features — carried
emotes and the radial wheel (including its Customize door), x-ray, pointing,
sitting, the avatar camera, the two camera legs, and rendering each player's
universal avatar — are constructed by the character chassis itself, so every
character World has them whether it was built on Character or on
CharacterMultiplayer, and a World published today receives ones added later on
its next fresh launch.
A World refuses what it does not want by NAME, in the character config it passes
at create time — not in helix.json, and nothing here is a publish-time concern:
config: { character: { native: { disabled: ['xray', 'pointing'] } } }Ids: emotes, emote-wheel, xray, pointing, sitting, avatar-camera,
camera-first-person, camera-third-person, universal-avatar. It is a
blocklist, never an allow-list, for the same reason this whole delivery
contract exists: an allow-list would freeze every already-published World out of
every feature added afterwards. A refusal also removes the feature's key,
controller button, touch pad and prompt; universal-avatar is the top switch and
takes emotes, the wheel, x-ray, pointing, sitting and the avatar camera with it
(the camera legs survive — a World still needs a camera); refusing BOTH camera
legs throws at create; and an unrecognised id is kept with a warning rather than
rejected, because it may belong to a newer runtime. The older switches still
work and route into the same resolver: CharacterMultiplayer.create's
emotes: false, emoteWheel: false and universalAvatars: false, and the
config keys character.emote.enabled, character.emote.wheel,
character.xray.enabled, character.universalAvatar.enabled. (localControl:
false is not one of these — it declares a spectator body the World drives its
own camera and input for.) Every other compatible native fix continues to update
normally.
This surface needs @helix/humanoid-character 0.2.54 or newer; an older
runtime ignores character.native.disabled silently, so confirm against the
resolved system's config schema rather than assuming. The full creator reference
is the character world recipe in the HELIX MCP docs
(read_doc({ name: "character-world" }), section 8g); the authoritative id list
and defaults are that config schema, readable from the installed system
manifest.
The runtime descriptor is mandatory for bundles that declare systems. It makes the delivery boundary inspectable by the CLI, MCP, backend, and fleet census; it does not execute code or silently rewrite an old minified bundle.
helix publish maps directly onto the backend's Instant World API:
checkBundlevalidateshelix.json+ files via@hypersoniclabs/helix-manifest.GET /api/v1/instant-worlds/me→ reuse the world with the manifest's slug, elsePOST /api/v1/instant-worlds.POST /api/v1/instant-worlds/:id/builds→ presigned upload tickets.- Files PUT directly to storage (6 concurrent), content-type/length bound to each ticket.
POST /api/v1/instant-worlds/:id/builds/:buildId/finalize→ byte-exact verify, activate, publish.GET /api/v1/instant-worlds/:slug→ the play URL.
helix.json's fields are the shared manifest's, not the CLI's — including the optional v0.3 scene
pin ({kind, id, revision, integrity:{sha256}}, or a continuum-scene package triple) naming the
sealed Scene document a world streams as its environment; integrity.sha256 is the authority and
id/revision are display text. Reference: docs/helix-scene-v2.md in @hypersoniclabs/helix-manifest.
.helixignore
An optional gitignore-style exclude list read at the bundle root. Matching paths are left out of
the upload and out of the 50 MB / 200-file budgets, and the file itself is never uploaded.
helix validate and helix publish print excluded N path(s) via .helixignore when it drops anything.
- Blank lines and
#comments are ignored. scene/— a trailing slash matches a directory and everything under it.scene/pin.json— a plain path, relative to the bundle root, forward slashes.*.mapand**/notes.txt—*stays inside one path segment,**/crosses segments.!keep.jsonre-includes: rules apply in order and the last match wins. As in git, nothing under an excluded directory can be re-included.
It must land at the bundle root, so a Vite world keeps it at public/.helixignore and the build copies
it into dist/. node_modules, .git, .vscode, .idea and screenshots are dropped regardless and
need no rule.
The case it exists for: a @helix/scene world keeps its dev-only Scene document and source GLBs under
public/scene/, which Vite copies verbatim. One scene/ line keeps megabytes of authoring bytes out of
every player's download.
Opt-in source upload
A published world ships only its built bundle, so there is nothing on the server to edit later.
--upload-source adds a second, separate upload that fixes that:
POST /api/v1/instant-worlds/:id/source→ a presigned PUT into a private bucket (this call also adopts the world into a project, which is the key its future workspace hangs from).helix-source.tar.gzPUT withcache-control: no-store.POST /api/v1/instant-worlds/:id/source/finalize→ the server verifies the stored byte count equals the declared one, then records the version.
- Off by default. Source is never sent unless you pass the flag. Not a prompt — an interactive confirm would break CI and agent-driven publishes.
- What goes in: the project directory, honouring
.gitignore, always excludingnode_modules,.git,distand.vite— the same exclude set the website's own workspace checkpoints use. - Which directory:
[dir]is the BUILT bundle, so the source defaults to its parent. Override with--source-dir <path>. - Never part of the build. The bundle's content-type allowlist rejects archives, raw
.tsis excluded from a bundle by design, the bundle size budget is the playable budget, and every build file lands on a public CDN path with a year-long immutable cache. Source uses its own private channel for exactly that reason. - Limit: 128 MiB compressed, checked locally before anything is uploaded.
Before upload, multiplayer worlds that install modules are checked for server realizability. A
module with a bank reference must name a bank that is published with the declared checksum (bank
modules belong to the default multiplayer block; the manifest validator refuses one inside a room
type). A module without bank is inline: its rules are in the manifest, so there is nothing to
resolve. The modules of every program (the default block and each multiplayer.roomTypes entry)
must then fit the 100,000 tick-node ceiling at the number of players that program's room holds: 24
for a standard default block, a room type's own maxPlayers up to 24, and the declared maxPlayers
up to 64 with capacityClass: "large", never fewer than 2. Fix the named module or bank when this
gate refuses a publish. For exceptional recovery only, --skip-realizability-check bypasses this
check; worlds without modules are unaffected.
The programmatic surface (publishWorld, checkBundle, whoAmI, …) is exported for other CLI
modules. Agent integrations should instruct or delegate to the helix commands above rather than
reimplementing operational behavior in a transport package.
Vault lifecycle
Use the durable Vault UUID as the handle. Search results include independent lexical, hard-filter,
measured-performance, reuse, and availability explanations; they do not collapse those signals into
a made-up score. helix assets install resolves an immutable version, follows the /download
indirection, verifies its server checksum and byte count, installs related material maps, and writes
public/helix.assets.json. Material installs keep the descriptor and default to the runtime KTX2
map family only; choose source PNGs with --material-renditions source, or both families with
--material-renditions all. Reinstalling with a different selection removes the stale opposite
family. Non-material assets keep their existing related-artifact behavior. For a reproducible agent build, pass --asset-version,
--checksum-sha256, and --size-bytes together; a partial pin or any metadata/download mismatch
fails before a provenance receipt is written.
Generation is source-agnostic at the backend:
propandcharacterstart through the Asset Generation job route (/api/v1/dreamer/*), which seals a Package before any optional Item offer.image,material,audio, andgaussian_splatstart through the shared asset route. Gaussian splats must declareobjectorenvironmentscope.animationfails locally withCAPABILITY_UNAVAILABLE; it never starts a chargeable job.
Run helix assets status <job-id> --pipeline dreamer|asset to poll an existing job. A successful
default-on generation reports vaultAssetId and vaultAutoPublish: "published"; an explicit
creator opt-out reports "disabled".
For .helix-scene.json, the open Vault catalog is not the scene-local budget. Reuse a small
material palette and instance repeated assets. The canonical ceilings are:
| Profile | Unique materials | Draw calls | Decoded texture memory | Triangles | Particles | | --- | ---: | ---: | ---: | ---: | ---: | | mobile | 48 | 300 | 256 MiB | 750,000 | 5,000 | | desktop | 128 | 1,200 | 1 GiB | 4,000,000 | 50,000 | | cinematic | 256 | 4,000 | 4 GiB | 15,000,000 | 250,000 |
These are publish-contract ceilings, not frame-rate promises; world runtime QA still applies.
The lifecycle acceptance harness intentionally has no CLI-local demo. Point it
at a built world in helix-web-demo-worlds that consumes the published SDK and
humanoid runtime:
npm run qa:vault-lifecycle -- \
--world-template /path/to/helix-web-demo-worlds/worlds/sdk-showcasePublishing is an explicit release-only action and additionally requires a new approved slug:
npm run qa:vault-lifecycle -- \
--world-template /path/to/helix-web-demo-worlds/worlds/sdk-showcase \
--publish --world-slug ch1146-vault-lifecycle-qa-<unique-suffix>The publish receipt requires one usage increment, one world association, and an idempotent second publish with no additional increment.
Audio generation
All three audio modes use the same authenticated Asset Generation route and automatically publish the
completed MP3 to Vault. The command waits up to 30 minutes so a ten-minute music request has enough
time to render, ingest, and publish. It prints the durable vaultAssetId, measured Spark charge,
provider cost, and server-stamped billingEvidence. It does not download another local copy unless
--output is supplied.
# Sound effect
helix assets generate-audio "heavy metal door slamming shut" \
--mode sound_effect --duration-seconds 3 --prompt-influence 0.4 --output door.mp3
# Music
helix assets generate-audio "hopeful orchestral exploration theme" \
--mode music --duration-seconds 180 --force-instrumental
# Text to speech
helix assets voices --search narrator --page-size 10
helix assets generate-audio --mode text_to_speech \
--text "Welcome to HELIX." --voice-id <id-from-catalog> \
--language-code en --stability 0.5 --speaker-boostUse the opaque --page-token printed by a page to continue. Voice cloning and
voice administration are deliberately not exposed.
Use a deterministic four-view sheet when you already have approved FRONT, BACK, LEFT, and RIGHT character art and do not want Asset Generation to generate another image:
helix assets generate-reference ./character-four-views.png \
"compact friendly service robot, game-ready humanoid proportions" \
--title "Service Robot" \
--target-polycount 12000The reference endpoint is character-only. The file is image-guarded, normalized, checksum-bound to the job, semantically validated, and mirrored before approval. Retries reuse those same bytes and never invoke or charge the image provider.
Material texture resolution
A platform material can ship more than one texture resolution. --resolution picks which one you
get; without it you get the pack's declared default, byte-for-byte what every earlier CLI returned.
helix assets material brick-block # the pack's default resolution
helix assets material brick-block --resolution 2k # the 2K variant's map URLs
helix assets material brick-block --resolution 2048 # same thing — numeric spelling
helix assets materials --resolution 2k --human # only materials that carry 2KAccepted values are the keys the catalog itself declares (1k, 2k, …), case-insensitively, plus
the numeric aliases 1024/2048, which map onto the variant authored at that pixel edge.
Both commands print JSON by default (agents and the MCP delegation parse it); --human prints a
readable summary instead. Either way the output states which resolution was resolved and which
ones the material offers, under resolution:
"resolution": {
"requested": "2k", "resolved": "2k", "pixels": 2048, "default": "1k",
"available": [{ "key": "1k", "pixels": 1024 }, { "key": "2k", "pixels": 2048 }],
"applicable": true
}Three rules make this safe to rely on:
- No silent fallback, ever. Asking for a resolution the material does not carry is an error that names the ones it does. You never receive a different resolution than the one you asked for.
- Old packs keep working. A catalog with no
resolutionsblock (schemaVersion: 1) is treated as having exactly one resolution, keyeddefault, derived from its existingmaps. Such a pack never claims to be "1k" — it does not say how big its textures are, so neither do we. - Procedural materials are not an error.
glassandprocedural_watercarry no texture maps; a resolution request against them is ignored and the output says so inresolution.note.
helix assets install has no resolution flag: Vault related-artifact roles are semantic
(source.albedo, runtime.ktx2.normal, sky.backdrop), never resolution-tagged, so a filter there
would filter nothing.
Pinning a platform material — the Vault pin and the Continuum ref
A catalog id such as white-marble is a pack-local name. No scene format accepts it, so both
material commands also return the two identities an author actually writes down:
"vault": { "assetId": "6d7ac215-…", "revision": 2 },
"continuum": {
"packageId": "3970d36f-9374-56f9-9be8-576bfa70fa8d",
"listingId": "33a5a6bc-…",
"version": "1.0.1",
"manifestCid": "cid:sha256:b7a3206f…"
}vault is the Scene v2 identity. A materials[] entry must resolve to a material resource
whose source is an immutable Vault pin (helix-manifest/src/scene-v2.ts:1627-1628), so the pin is
written on the resource and the material points at it:
"resources": [{
"id": "res-white-marble",
"kind": "material",
"source": { "kind": "vault", "assetId": "6d7ac215-…", "revision": 2 },
"integrity": { "sha256": "…" },
"mimeType": "application/vnd.helix.material+json"
}],
"materials": [{
"id": "mat-white-marble",
"resource": "res-white-marble",
"defaults": { "tint": "#ffffff", "repeatPerMeter": 1, "roughnessScale": 1, "metalnessScale": 1 }
}]continuum is the semantic-scene identity. A sceneDocument.materialBindings[].binding
(helix-manifest/src/continuum/material.ts:111-136) references a Package Version and never owns
maps:
{
"id": "white-marble",
"binding": {
"schema": "helix.continuum.material-binding/1",
"material": { "packageId": "3970d36f-…", "version": "1.0.1", "manifestCid": "cid:sha256:…" },
"slot": "white-marble",
"tint": [1, 1, 1, 1],
"opacity": 1,
"roughnessScale": 1, "metallicScale": 1, "normalScale": 1, "emissiveScale": 1,
"uv": { "set": 0, "offset": [0, 0], "scale": [1, 1], "rotationDeg": 0, "tiling": [1, 1] }
}
}The same { packageId, version, manifestCid } triple is what a scratch descriptor's
source.materials[].sourcePackage and every entry of compilation.dependencyClosures[] require.
How each field is resolved, and what a null means:
| Field | Route | Login |
| --- | --- | --- |
| vault | GET /api/v1/vault/assets?q=platform-material:<id>&kind=material&match=narrow | not needed — the route is public and the CLI sends no token |
| continuum.listingId / version / manifestCid | GET /api/v1/vault/catalog?packageType=material&q=<id, hyphens as spaces> | not needed — public |
continuum.packageIdis a pure function of the catalog id — no lookup, always present, stable forever:deterministicContinuumUuid("helix.material.catalog/1\0" + id). The catalog page is filtered on that derived id, never on a title, so a same-named Listing cannot become a wrong pin.vault: nullmeans the platform tag matched zero or several assets — exactly one match is the only pinnable state (the same rule the Home scene loader applies). Several also setsvaultWarning: "ambiguous: N matches".version: nullmeans no sealed public Package Version for that id on this lane.- A failed lookup never masquerades as an absence: it sets
vaultWarning/continuumWarningtolookup failed: …and leaves the rest of the catalog intact. --no-resolvemakes no request at all. Entries then carry only the derivedcontinuum.packageIdand novaultkey — absent, notnull, so "not looked up" cannot be read as "not there".- Lookups run at most 8 in flight and are cached for the life of the process.
Vehicle validate
helix vehicle validate <vehicle-package.json> [--glb <model.glb>] [--no-simulate] [--offline] [--json] needs no login.
This local gate checks the vehicle Package and optional GLB before any publication. When the package declares
targets it also drives the car with the platform's engine instrument (helix vehicle simulate; the runtime is
fetched once and cached, --offline uses the cache, --no-simulate skips the drive) and reports every figure
outside its tolerance as SIMULATED_PERFORMANCE_OUT_OF_TOLERANCE.
Every finding prints as CODE path — message plus a fix: line; --json returns
{ ok, errors, warnings, issues: [{ code, level, path, message, fix }], engineCommit }.
| Layer | Codes |
|---|---|
| Engine validator (all its errors and warnings) | ENGINE_VALIDATOR, ENGINE_VALIDATOR_CRASH |
| Publish gate + backend pin | PACKAGE_NOT_OBJECT, PACKAGE_VERSION_UNSUPPORTED, SCHEMA_VERSION_MISMATCH, REQUIRED_FIELD_MISSING, REQUIRED_BLOCK_MISSING, MESH_CLASS_ID_MISSING, MESH_PARTS_MISSING, WHEEL_VISUALS_MISSING, WHEEL_VISUALS_INVALID, NOT_DISTRIBUTABLE, MESH_SOURCE_FIXTURE, PUBLISH_GATE |
| Cross-field | GROUND_TO_BODY_ORIGIN_MISMATCH (> 5 mm; fix the ride height, the field is locked after first publish), COMPONENT_BUILD_DIGEST_MISMATCH (prints the computed digest), COMPONENT_ACTIONS_MISMATCH (prints the compiled protocol), COMPONENT_BUILD_INVALID, PANEL_SIGNAL_NO_ACTION (warn), SEAT_SOCKET_UNKNOWN (warn) |
| Drive (package targets) | SIMULATED_PERFORMANCE_OUT_OF_TOLERANCE (figure, simulated, target, error, tolerance, engine version), SIMULATED_REST_POSE_INVALID, SIMULATION_UNAVAILABLE (warn: no runtime reachable or cached) |
| --glb host manifest | HOST_MANIFEST_MISSING, HOST_MANIFEST_MISPLACED (found in scene/document/node extras; the backend reads only asset.extras.HELIX_vehicle), HOST_MANIFEST_EMPTY, HOST_MANIFEST_DIVERGENT, HOST_MANIFEST_SOCKET_INVALID, HOST_MANIFEST_SOCKET_NAME_INVALID, HOST_MANIFEST_SOCKET_NODE_MISSING. helix continuum publish-package refuses a vehicle Package descriptor whose model GLB has any of these errors before sealing. See docs/vehicle-simulate.md |
| --glb host-frame bind contract | HOST_BIND_ROOT_MISSING (no vehicle_root node), HOST_BIND_MOUNT_NODE_MISSING (an offered boot/bonnet socket with no node to mount on), HOST_BIND_ROOT_NOT_OWNER (the lid node is outside vehicle_root): a host that offers a lid socket must carry the nodes a host-frame add-on binds to, or the runtime throws while composing the car and it is never presented. vehicle addon check --host, vehicle addon check <dir> and vehicle preview --addon apply the same rule to the add-ons' own bind { socket, root }; vehicle preview refuses before launching a browser and fails at once on a [helix-vehicle] composition failed console line or a page error |
| --glb | WHEEL_NODES_MISSING, WHEEL_PIVOT_NOT_AT_HUB (origin > 2 cm from the centre of its geometry — the wheel add-on anchor lands there), WHEEL_ORIGINS_NOT_DISTINCT, WHEEL_FRAME_MIRRORED_X, WHEEL_FRAME_FRONT_NOT_PLUS_Z, WHEEL_NODE_EMPTY (warn), LENGTH_NOT_ALONG_Z (warn), PANEL_NODE_MISSING, PANEL_ROLE_MISSING_DOOR / PANEL_ROLE_NO_NODE_BONNET / PANEL_ROLE_NO_NODE_BOOT / PANEL_ROLE_UNDECLARED_BONNET / PANEL_ROLE_UNDECLARED_BOOT (warn: a four-wheeled car should articulate its doors, bonnet and boot / engine cover), the lamp-coverage gate on the lit geometry (LAMP_INDICATOR_REAR_MISSING / LAMP_INDICATOR_FRONT_MISSING / LAMP_INDICATOR_SIDE_UNLIT / LAMP_INDICATOR_NO_ROWS / LAMP_NODE_UNLIT / LAMP_REPEATER_UNLIT / LAMP_FUNCTION_NO_ROW errors, LAMP_ROW_NO_TARGET / LAMP_NO_LIGHTING_ROWS / LAMP_INDICATOR_REAR_OPTED_OUT warns; the documented opt-out for a car that really has no rear indicators is lights.noRearIndicators: "<why, with source>"), VEHICLE_BUDGET_TRIANGLES / _MATERIALS / _TEXTURE_EDGE / _DRAW_CALLS / _FILE_SIZE |
The model frame is glTF: +X is the vehicle's left, +Y up, +Z forward, metres. Wheel nodes are found by name with
the engine's own resolver (wheel_lf / wheel_rf / wheel_lr / wheel_rr and the usual aliases); all four must
resolve. The engine validator is vendored by scripts/vendor-vehicle-package-v2.mjs (see vendor/README.md).
Package-first GLB publication
helix continuum publish-glb <mesh.glb> --title <name> seals a Prop Package Version through
POST /api/v1/publications/upload. --package-type character seals a Character Package.
A Package-only publish creates no Item, Distribution or creator inventory copy. The receipt prints
the exact { packageId, version, manifestCid } pin and a retryable intent UUID.
Use --private for source resources that
