@helix3/helix-cli 0.1.18-helix3.347 → 0.1.18-helix3.349

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,6 +45,7 @@ The token is stored in `~/.helix/credentials.json` (mode `600`). It's sent as
45
45
  | `helix validate [dir]` | Validate a bundle locally — the exact rules the server enforces. |
46
46
  | `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**. |
47
47
  | `helix list` | List your worlds. |
48
+ | `helix vehicle validate <vehicle-package.json> [--glb <model.glb>] [--json]` | One-second local gate for a hand-authored Universal Vehicle, run before `item publish`. 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 and the articulation nodes. Exit `0` no errors, `1` errors, `2` unreadable input. See [Vehicle validate](#vehicle-validate). |
48
49
  | `helix item list-slots [--json]` | Print the Character-Creator vocabulary a wearable declares: the cosmetic slot tags, and the genders. |
49
50
  | `helix item publish <mesh.glb> --title <t> [--collectible-supply <n>] [initial distribution flags] [--dry-run\|--quote]` | Publish a Standard item by default, or one fixed-supply Collectible. The item definition is separate from acquisition routes. |
50
51
  | `helix item distribution create\|list\|update\|disable …` | Manage Marketplace/World Claim or Buy routes without republishing the item or paying another publish fee. |
@@ -545,6 +546,27 @@ How each field is resolved, and what a null means:
545
546
  and **no `vault` key** — absent, not `null`, so "not looked up" cannot be read as "not there".
546
547
  - Lookups run at most 8 in flight and are cached for the life of the process.
547
548
 
549
+ ## Vehicle validate
550
+
551
+ `helix vehicle validate <vehicle-package.json> [--glb <model.glb>] [--json]` needs no login and no network.
552
+ A vehicle that did not come through the BeamNG bridge is otherwise validated against nothing: `item publish`
553
+ returns ok and the runtime then refuses to spawn it. `helix item publish --kind vehicle` now runs the package
554
+ checks on `--vehicle-package` itself (publish, `--dry-run` and `--quote`) and refuses on any error.
555
+
556
+ Every finding prints as `CODE path — message` plus a `fix:` line; `--json` returns
557
+ `{ ok, errors, warnings, issues: [{ code, level, path, message, fix }], engineCommit }`.
558
+
559
+ | Layer | Codes |
560
+ |---|---|
561
+ | Engine validator (all its errors and warnings) | `ENGINE_VALIDATOR`, `ENGINE_VALIDATOR_CRASH` |
562
+ | 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` |
563
+ | 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) |
564
+ | `--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`, `VEHICLE_BUDGET_TRIANGLES` / `_MATERIALS` / `_TEXTURE_EDGE` / `_DRAW_CALLS` / `_FILE_SIZE` |
565
+
566
+ The model frame is glTF: +X is the vehicle's left, +Y up, +Z forward, metres. Wheel nodes are found by name with
567
+ the engine's own resolver (`wheel_lf` / `wheel_rf` / `wheel_lr` / `wheel_rr` and the usual aliases); all four must
568
+ resolve. The engine validator is vendored by `scripts/vendor-vehicle-package-v2.mjs` (see `vendor/README.md`).
569
+
548
570
  ## Item publish flow
549
571
 
550
572
  `helix item publish` creates an item definition. It defaults to **Standard**: unlimited issuance,
package/dist/index.js CHANGED
@@ -40,6 +40,7 @@ const normalMapGuard_1 = require("./character/normalMapGuard");
40
40
  const itemSetVersion_1 = require("./itemSetVersion");
41
41
  const moveUx_1 = require("./visualQa/moveUx");
42
42
  const restFrameGuard_1 = require("./character/restFrameGuard");
43
+ const validate_1 = require("./vehicle/validate");
43
44
  const commander_1 = require("commander");
44
45
  const commands_1 = require("./sceneHlod/commands");
45
46
  const placementRequest_1 = require("./placementRequest");
@@ -555,6 +556,58 @@ deviceCommand
555
556
  if (reports.some((report) => !report.ok))
556
557
  process.exitCode = 1;
557
558
  });
559
+ const vehicleCommand = program
560
+ .command('vehicle')
561
+ .description('Check a Universal Vehicle before publish');
562
+ vehicleCommand
563
+ .command('validate')
564
+ .description('Validate a vehicle package (and optionally its GLB) against the engine contract, the backend pin rules and the publish budgets. '
565
+ + 'Exit 0 = no errors, 1 = errors, 2 = unreadable input')
566
+ .argument('<vehicle-package.json>', 'the vehicle package JSON (the file given to `item publish --vehicle-package`)')
567
+ .option('--glb <model.glb>', 'also check the model: wheel nodes and pivots, frame orientation, budgets, articulation nodes')
568
+ .option('--json', 'print the machine-readable report')
569
+ .action(async (packageFile, opts) => {
570
+ let pkg;
571
+ let glb;
572
+ try {
573
+ pkg = (0, validate_1.readJsonFile)((0, node_path_1.resolve)(packageFile));
574
+ }
575
+ catch (error) {
576
+ process.stderr.write(`✖ cannot read vehicle package ${packageFile}: ${error instanceof Error ? error.message : String(error)}\n`);
577
+ process.exitCode = 2;
578
+ return;
579
+ }
580
+ if (opts.glb !== undefined) {
581
+ try {
582
+ glb = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(opts.glb));
583
+ }
584
+ catch (error) {
585
+ process.stderr.write(`✖ cannot read GLB ${opts.glb}: ${error instanceof Error ? error.message : String(error)}\n`);
586
+ process.exitCode = 2;
587
+ return;
588
+ }
589
+ }
590
+ let result;
591
+ try {
592
+ result = await (0, validate_1.validateVehicle)(pkg, glb);
593
+ }
594
+ catch (error) {
595
+ process.stderr.write(`✖ cannot validate ${opts.glb ?? packageFile}: ${error instanceof Error ? error.message : String(error)}\n`);
596
+ process.exitCode = 2;
597
+ return;
598
+ }
599
+ if (opts.json) {
600
+ console.log(JSON.stringify({ file: (0, node_path_1.resolve)(packageFile), glb: opts.glb ? (0, node_path_1.resolve)(opts.glb) : null, ...result }, null, 2));
601
+ }
602
+ else {
603
+ if (result.issues.length > 0)
604
+ console.log((0, validate_1.formatVehicleIssues)(result.issues));
605
+ console.log(`${result.ok ? '✔' : '✖'} ${result.ok ? 'Valid' : 'Invalid'} vehicle package: ${result.errors} error${result.errors === 1 ? '' : 's'}, ${result.warnings} warning${result.warnings === 1 ? '' : 's'}`
606
+ + `${glb ? ' (package + GLB)' : ' (package only — add --glb <model.glb> to check wheels, pivots and budgets)'} [engine ${result.engineCommit.slice(0, 8)}]`);
607
+ }
608
+ if (!result.ok)
609
+ process.exitCode = 1;
610
+ });
558
611
  // Validate a token against the API, then persist it. Shared by the browser
559
612
  // flow and the --token (CI/headless) path.
560
613
  async function finishLogin(apiUrl, token) {
@@ -1638,6 +1691,7 @@ item
1638
1691
  if (opts.dryRun) {
1639
1692
  try {
1640
1693
  const plan = (0, lib_1.planItemPublish)(publishOptions);
1694
+ const vehicleWarnings = await (0, validate_1.assertPublishVehiclePackage)(publishOptions);
1641
1695
  await (0, baseColorGuard_1.assertPublishBaseColor)(publishOptions, plan.meshBytes);
1642
1696
  await (0, normalMapGuard_1.assertPublishNormalMaps)(publishOptions, plan.meshBytes);
1643
1697
  await (0, restFrameGuard_1.assertPublishRestFrames)(publishOptions, plan.meshBytes);
@@ -1649,6 +1703,7 @@ item
1649
1703
  mesh: plan.mesh,
1650
1704
  thumbnail: plan.thumbnail,
1651
1705
  warnings: plan.warnings,
1706
+ ...(vehicleWarnings.length > 0 ? { vehicleWarnings } : {}),
1652
1707
  }, null, 2));
1653
1708
  return;
1654
1709
  }
@@ -1659,6 +1714,8 @@ item
1659
1714
  console.log(` payload: ${JSON.stringify(plan.payload, null, 2).replace(/\n/g, '\n ')}`);
1660
1715
  for (const warning of plan.warnings)
1661
1716
  console.log(` • ${warning}`);
1717
+ if (vehicleWarnings.length > 0)
1718
+ console.log((0, validate_1.formatVehicleIssues)(vehicleWarnings));
1662
1719
  const status = (0, lib_1.authStatus)();
1663
1720
  if (!status.loggedIn)
1664
1721
  console.log(' • not logged in — run `helix login` before the real publish');