@hypersoniclabs/helix-cli 0.1.25-helix3.524 → 0.1.25-helix3.526

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.
Files changed (37) hide show
  1. package/dist/device.js +96 -4
  2. package/dist/device.js.map +1 -1
  3. package/dist/deviceActionValidation.js +1 -1
  4. package/dist/deviceActionValidation.js.map +1 -1
  5. package/dist/itemLod/artifact.d.ts +40 -0
  6. package/dist/itemLod/artifact.js +106 -22
  7. package/dist/itemLod/artifact.js.map +1 -1
  8. package/dist/itemLod/cardTruth.d.ts +93 -0
  9. package/dist/itemLod/cardTruth.js +203 -0
  10. package/dist/itemLod/cardTruth.js.map +1 -0
  11. package/dist/itemLod/command.d.ts +2 -0
  12. package/dist/itemLod/command.js +10 -2
  13. package/dist/itemLod/command.js.map +1 -1
  14. package/dist/itemLod/glbFixtures.d.ts +5 -0
  15. package/dist/itemLod/glbFixtures.js +3 -1
  16. package/dist/itemLod/glbFixtures.js.map +1 -1
  17. package/dist/itemLod/index.d.ts +3 -1
  18. package/dist/itemLod/index.js +8 -1
  19. package/dist/itemLod/index.js.map +1 -1
  20. package/dist/tsconfig.build.tsbuildinfo +1 -1
  21. package/docs/item-lod.md +31 -0
  22. package/package.json +1 -1
  23. package/plugins/beamng-vehicle/bin/__pycache__/beamng_abs.cpython-312.pyc +0 -0
  24. package/plugins/beamng-vehicle/bin/__pycache__/beamng_audio.cpython-312.pyc +0 -0
  25. package/plugins/beamng-vehicle/bin/__pycache__/beamng_engine.cpython-312.pyc +0 -0
  26. package/plugins/beamng-vehicle/bin/__pycache__/beamng_expression.cpython-312.pyc +0 -0
  27. package/plugins/beamng-vehicle/bin/__pycache__/beamng_jbeam.cpython-312.pyc +0 -0
  28. package/plugins/beamng-vehicle/bin/__pycache__/beamng_materials.cpython-312.pyc +0 -0
  29. package/plugins/beamng-vehicle/bin/__pycache__/beamng_stock_events.cpython-312.pyc +0 -0
  30. package/plugins/beamng-vehicle/bin/__pycache__/fmod_studio.cpython-312.pyc +0 -0
  31. package/plugins/beamng-vehicle/bin/__pycache__/fsb5.cpython-312.pyc +0 -0
  32. package/plugins/beamng-vehicle/bin/__pycache__/sim_audio.cpython-312.pyc +0 -0
  33. package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_ac.cpython-312.pyc +0 -0
  34. package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_beamng.cpython-312.pyc +0 -0
  35. package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_fixtures.cpython-312.pyc +0 -0
  36. package/plugins/beamng-vehicle/bin/__pycache__/test_beamng_engine.cpython-312.pyc +0 -0
  37. package/plugins/beamng-vehicle/bin/__pycache__/test_sim_audio.cpython-312.pyc +0 -0
package/dist/device.js CHANGED
@@ -477,8 +477,36 @@ function validateGraphSteps(steps, context, path, depth) {
477
477
  graphNode(context, step.node, stepPath);
478
478
  if (!['x', 'y', 'z'].includes(String(step.axis)))
479
479
  context.errors.push(`${stepPath}: rotateNode axis must be x, y or z`);
480
- if (!['toAngle', 'stop'].includes(String(step.mode)))
481
- context.errors.push(`${stepPath}: rotateNode mode must be toAngle or stop`);
480
+ if (!['toAngle', 'stop', 'spin', 'sweep'].includes(String(step.mode)))
481
+ context.errors.push(`${stepPath}: rotateNode mode must be toAngle, stop, spin or sweep`);
482
+ // spin turns continuously at `rpm` (an expression, so Speed can drive it); sweep swings `amplitudeDeg` either side every `periodMs`
483
+ // (device audio r7 engine). Both are presentation only.
484
+ if (step.mode === 'spin') {
485
+ if (step.rpm === undefined)
486
+ context.errors.push(`${stepPath}: rotateNode spin needs rpm`);
487
+ else
488
+ validateGraphExpr(step.rpm, context, `${stepPath}.rpm`);
489
+ }
490
+ else if (step.rpm !== undefined) {
491
+ context.errors.push(`${stepPath}: rotateNode rpm applies to mode "spin" only`);
492
+ }
493
+ if (step.mode === 'sweep') {
494
+ if (step.amplitudeDeg === undefined || step.periodMs === undefined)
495
+ context.errors.push(`${stepPath}: rotateNode sweep needs amplitudeDeg and periodMs`);
496
+ if (step.amplitudeDeg !== undefined &&
497
+ (!isFiniteNumber(step.amplitudeDeg) ||
498
+ step.amplitudeDeg < 0 ||
499
+ step.amplitudeDeg > 180))
500
+ context.errors.push(`${stepPath}: rotateNode amplitudeDeg must be a number from 0 to 180`);
501
+ if (step.periodMs !== undefined &&
502
+ (!isFiniteNumber(step.periodMs) ||
503
+ step.periodMs < 200 ||
504
+ step.periodMs > 60_000))
505
+ context.errors.push(`${stepPath}: rotateNode periodMs must be a number from 200 to 60000`);
506
+ }
507
+ else if (step.amplitudeDeg !== undefined || step.periodMs !== undefined) {
508
+ context.errors.push(`${stepPath}: rotateNode amplitudeDeg and periodMs apply to mode "sweep" only`);
509
+ }
482
510
  if (step.targetDeg !== undefined)
483
511
  validateGraphExpr(step.targetDeg, context, `${stepPath}.targetDeg`);
484
512
  if (step.durationMs !== undefined && !isFiniteNumber(step.durationMs))
@@ -1256,7 +1284,8 @@ function applySteps(steps, definition, state, budget) {
1256
1284
  }
1257
1285
  function invoke(definition, state, interaction, value) {
1258
1286
  const control = object(interaction.control);
1259
- if (control.stateKey && value !== undefined) {
1287
+ // a time control's value is a time of day, not the state value: its action writes the instant
1288
+ if (control.stateKey && control.kind !== 'time' && value !== undefined) {
1260
1289
  const key = String(control.stateKey);
1261
1290
  state[key] = coerce(object(object(definition.state)[key]), value);
1262
1291
  }
@@ -1274,6 +1303,8 @@ function exerciseValue(interaction, state) {
1274
1303
  const control = object(interaction.control);
1275
1304
  if (control.kind === 'number')
1276
1305
  return Number(control.max ?? control.min ?? 1);
1306
+ if (control.kind === 'time')
1307
+ return 7 * 3600 + 35 * 60; // seconds after the viewer's local midnight
1277
1308
  if (control.kind === 'choice') {
1278
1309
  const options = Array.isArray(control.options)
1279
1310
  ? control.options.map(object)
@@ -1486,6 +1517,41 @@ function validateDeviceProject(input, options = {}) {
1486
1517
  if (part.pivot !== undefined && part.pivot !== 'bottom') {
1487
1518
  errors.push(`${partPath}.pivot must be bottom`);
1488
1519
  }
1520
+ // `parent` (device audio r7 engine): position and rotationDeg are then in that part's frame and a step that moves the parent carries its subtree.
1521
+ if (part.parent !== undefined) {
1522
+ if (typeof part.parent !== 'string' || !part.parent.length || part.parent.length > 64) {
1523
+ errors.push(`${partPath}.parent must be the name of another part`);
1524
+ }
1525
+ else if (part.parent === name) {
1526
+ errors.push(`${partPath}.parent cannot be the part itself`);
1527
+ }
1528
+ else if (!Object.prototype.hasOwnProperty.call(parts, part.parent)) {
1529
+ errors.push(`${partPath}.parent "${part.parent}" is not a part of this model`);
1530
+ }
1531
+ else {
1532
+ let depth = 0;
1533
+ let cursor = name;
1534
+ const seen = new Set();
1535
+ while (cursor !== undefined && object(parts[cursor]).parent !== undefined) {
1536
+ if (seen.has(cursor)) {
1537
+ errors.push(`${partPath}.parent chain loops back on "${cursor}"`);
1538
+ break;
1539
+ }
1540
+ seen.add(cursor);
1541
+ const next = object(parts[cursor]).parent;
1542
+ cursor = typeof next === 'string' ? next : undefined;
1543
+ depth += 1;
1544
+ if (depth > 8) {
1545
+ errors.push(`${partPath}.parent chain is deeper than 8`);
1546
+ break;
1547
+ }
1548
+ }
1549
+ }
1550
+ }
1551
+ // `origin`: the shape-local point that sits at `position` and that the part (and its children) turns about (generalises pivot "bottom").
1552
+ if (part.origin !== undefined && !isNumberTriple(part.origin)) {
1553
+ errors.push(`${partPath}.origin must be three numbers`);
1554
+ }
1489
1555
  if (part.color !== undefined && !Number.isInteger(part.color)) {
1490
1556
  errors.push(`${partPath}.color must be an integer`);
1491
1557
  }
@@ -1666,6 +1732,32 @@ function validateDeviceProject(input, options = {}) {
1666
1732
  errors.push(`interaction ${id} disabled choice "${String(option.value)}" needs a reason`);
1667
1733
  }
1668
1734
  }
1735
+ else if (control.kind === 'time') {
1736
+ // A time of day set and shown in the VIEWER's zone (device audio r7 engine). The room keeps one absolute instant in `stateKey`; the control's
1737
+ // `value` is seconds after the viewer's local midnight and is NOT written to the state: the action turns it into the instant.
1738
+ if (!['float', 'integer'].includes(String(spec.type)))
1739
+ errors.push(`interaction ${id} time control stateKey "${key}" must be a declared integer or float state (epoch milliseconds)`);
1740
+ if (control.setStateKey !== undefined) {
1741
+ const setSpec = object(state[String(control.setStateKey)]);
1742
+ if (!['float', 'integer'].includes(String(setSpec.type)))
1743
+ errors.push(`interaction ${id} time control setStateKey "${String(control.setStateKey)}" must be a declared integer or float state (epoch milliseconds)`);
1744
+ }
1745
+ if (control.stepMinutes !== undefined &&
1746
+ (!Number.isInteger(control.stepMinutes) ||
1747
+ Number(control.stepMinutes) < 1 ||
1748
+ Number(control.stepMinutes) > 60))
1749
+ errors.push(`interaction ${id} time stepMinutes must be an integer 1..60`);
1750
+ const action = object(interaction.action);
1751
+ if (interaction.action === undefined) {
1752
+ errors.push(`interaction ${id} a time control needs an action that turns value (seconds after the viewer's local midnight) into the instant`);
1753
+ }
1754
+ else if (!Object.values(object(action.args)).some((arg) => {
1755
+ const a = object(arg);
1756
+ return a.type === 'number' && a.fill === 'viewer.localMidnightSec';
1757
+ })) {
1758
+ errors.push(`interaction ${id} a time control's action needs a number arg with fill "viewer.localMidnightSec" to turn the time of day into an instant`);
1759
+ }
1760
+ }
1669
1761
  else if (control.kind === 'text') {
1670
1762
  if (spec.type !== 'string')
1671
1763
  errors.push(`interaction ${id} text control must target string state`);
@@ -1684,7 +1776,7 @@ function validateDeviceProject(input, options = {}) {
1684
1776
  }
1685
1777
  }
1686
1778
  else if (control.kind === undefined) {
1687
- errors.push(`interaction ${id} control requires a kind (number|choice|text)`);
1779
+ errors.push(`interaction ${id} control requires a kind (number|choice|text|time)`);
1688
1780
  }
1689
1781
  else {
1690
1782
  errors.push(`interaction ${id} uses unsupported control kind "${String(control.kind)}"`);