@hypersoniclabs/helix-cli 0.1.25-helix3.518 → 0.1.25-helix3.520
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 +1 -1
- package/dist/device.d.ts +16 -4
- package/dist/device.js +63 -2
- package/dist/device.js.map +1 -1
- package/dist/deviceActionValidation.js +33 -0
- package/dist/deviceActionValidation.js.map +1 -1
- package/dist/deviceGraphChecks.d.ts +63 -0
- package/dist/deviceGraphChecks.js +298 -0
- package/dist/deviceGraphChecks.js.map +1 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/itemLod/artifact.d.ts +8 -2
- package/dist/itemLod/artifact.js +3 -2
- package/dist/itemLod/artifact.js.map +1 -1
- package/dist/itemLod/index.d.ts +3 -0
- package/dist/itemLod/index.js +3 -1
- package/dist/itemLod/index.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_abs.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_audio.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_engine.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_expression.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_jbeam.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_materials.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_stock_events.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/fmod_studio.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/fsb5.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/sim_audio.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_ac.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_beamng.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_fixtures.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/test_beamng_engine.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/test_sim_audio.cpython-312.pyc +0 -0
package/README.md
CHANGED
|
@@ -78,7 +78,7 @@ The token is stored in `~/.helix/credentials.json` (mode `600`). It's sent as
|
|
|
78
78
|
| `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. |
|
|
79
79
|
| `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](docs/item-lod.md). |
|
|
80
80
|
| `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. |
|
|
81
|
-
| `helix device validate [dir] [--json]` | Validate the definition, GLB bytes and named-node bindings, typed state/controls, capabilities, and Home+World compatibility, including [declarative character hand reach](docs/device-hand-reach.md). |
|
|
81
|
+
| `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](docs/device-hand-reach.md). `--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). |
|
|
82
82
|
| `helix device test [dir] [--mode single\|multiplayer\|both] [--json]` | Exercise every interaction, persistence remount, and deterministic one-/two-client authority convergence. |
|
|
83
83
|
| `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`](examples/scene-elements-devices.json). |
|
|
84
84
|
| `helix preview-video <slug> <youtube-url>` | Set or clear a published world's YouTube preview. |
|
package/dist/device.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare const DEVICE_INTERACTION_VERB_LIST: string;
|
|
|
23
23
|
* fail `helix device validate` while the client loaded it happily. Add
|
|
24
24
|
* capabilities to both lists, never to one.
|
|
25
25
|
*/
|
|
26
|
-
export declare const DEVICE_CAPABILITIES: readonly ["model.glb", "presentation.nodeMotion", "clock.serverTime", "state.typed", "state.durable", "room.authority", "audio.playback", "presentation.light", "presentation.vfx", "character.posture", "character.animate", "character.hand", "signals.local", "screen.render", "screen.input", "app.web.local", "app.web.remote", "app.web.panel", "app.image.remote", "app.image.owner", "app.canvas", "device.ports", "physics.solid"];
|
|
26
|
+
export declare const DEVICE_CAPABILITIES: readonly ["model.glb", "presentation.nodeMotion", "clock.serverTime", "clock.deadline", "state.typed", "state.durable", "room.authority", "audio.playback", "presentation.light", "presentation.vfx", "character.posture", "character.animate", "character.hand", "signals.local", "screen.render", "screen.input", "app.web.local", "app.web.remote", "app.web.panel", "app.image.remote", "app.image.owner", "app.canvas", "device.ports", "physics.solid"];
|
|
27
27
|
/**
|
|
28
28
|
* The closed component vocabulary.
|
|
29
29
|
*
|
|
@@ -56,8 +56,12 @@ export declare const DEVICE_GRAPH_STEP_KINDS: readonly ["setState", "toggleState
|
|
|
56
56
|
export declare const DEVICE_HAND_SOCKETS: readonly ["hand_r.grip", "hand_l.grip"];
|
|
57
57
|
/** The same vocabulary rendered as one readable list for error messages. */
|
|
58
58
|
export declare const DEVICE_HAND_SOCKET_LIST: string;
|
|
59
|
-
/**
|
|
60
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The closed trigger vocabulary (`validate.ts:224-243`), plus `deadline`: an
|
|
61
|
+
* epoch-ms instant held in numeric state that fires once when the clock reaches
|
|
62
|
+
* it. Its `do` is the server-evaluable subset (see `validateDeadlineTrigger`).
|
|
63
|
+
*/
|
|
64
|
+
export declare const DEVICE_TRIGGER_KINDS: readonly ["init", "interact", "stateChanged", "signal", "playerNear", "deadline"];
|
|
61
65
|
/** The closed host vocabulary for `compatibility.hosts` (`validate.ts:555-560`). */
|
|
62
66
|
export declare const DEVICE_HOSTS: readonly ["home", "world"];
|
|
63
67
|
/** The same vocabulary rendered as one readable list for error messages. */
|
|
@@ -88,7 +92,15 @@ export interface DeviceTestReport {
|
|
|
88
92
|
errors: string[];
|
|
89
93
|
}
|
|
90
94
|
export declare function readDeviceGlbNodeNames(file: string): string[];
|
|
91
|
-
export
|
|
95
|
+
export interface DeviceValidateOptions {
|
|
96
|
+
/**
|
|
97
|
+
* Also apply the room (server) authority rule: refuse state writes reachable
|
|
98
|
+
* from an `interact` trigger through `emitSignal` handlers, `delay`, or
|
|
99
|
+
* `branch`. The backend applies the same rule when sealing a new publish.
|
|
100
|
+
*/
|
|
101
|
+
room?: boolean;
|
|
102
|
+
}
|
|
103
|
+
export declare function validateDeviceProject(input: string, options?: DeviceValidateOptions): DeviceValidationReport;
|
|
92
104
|
export declare function testDeviceProject(input: string, mode: 'single' | 'multiplayer'): DeviceTestReport;
|
|
93
105
|
export declare function createDeviceProject(input: {
|
|
94
106
|
directory: string;
|
package/dist/device.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.createDeviceProject = createDeviceProject;
|
|
|
8
8
|
const node_fs_1 = require("node:fs");
|
|
9
9
|
const node_path_1 = require("node:path");
|
|
10
10
|
const deviceActionValidation_1 = require("./deviceActionValidation");
|
|
11
|
+
const deviceGraphChecks_1 = require("./deviceGraphChecks");
|
|
11
12
|
var deviceActionValidation_2 = require("./deviceActionValidation");
|
|
12
13
|
Object.defineProperty(exports, "deviceActionAstBudgetError", { enumerable: true, get: function () { return deviceActionValidation_2.deviceActionAstBudgetError; } });
|
|
13
14
|
exports.DEVICE_FILENAME = 'device.json';
|
|
@@ -61,6 +62,7 @@ exports.DEVICE_CAPABILITIES = [
|
|
|
61
62
|
'model.glb',
|
|
62
63
|
'presentation.nodeMotion',
|
|
63
64
|
'clock.serverTime',
|
|
65
|
+
'clock.deadline',
|
|
64
66
|
'state.typed',
|
|
65
67
|
'state.durable',
|
|
66
68
|
'room.authority',
|
|
@@ -142,13 +144,18 @@ exports.DEVICE_GRAPH_STEP_KINDS = [
|
|
|
142
144
|
exports.DEVICE_HAND_SOCKETS = ['hand_r.grip', 'hand_l.grip'];
|
|
143
145
|
/** The same vocabulary rendered as one readable list for error messages. */
|
|
144
146
|
exports.DEVICE_HAND_SOCKET_LIST = exports.DEVICE_HAND_SOCKETS.join('|');
|
|
145
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* The closed trigger vocabulary (`validate.ts:224-243`), plus `deadline`: an
|
|
149
|
+
* epoch-ms instant held in numeric state that fires once when the clock reaches
|
|
150
|
+
* it. Its `do` is the server-evaluable subset (see `validateDeadlineTrigger`).
|
|
151
|
+
*/
|
|
146
152
|
exports.DEVICE_TRIGGER_KINDS = [
|
|
147
153
|
'init',
|
|
148
154
|
'interact',
|
|
149
155
|
'stateChanged',
|
|
150
156
|
'signal',
|
|
151
157
|
'playerNear',
|
|
158
|
+
'deadline',
|
|
152
159
|
];
|
|
153
160
|
/** The closed host vocabulary for `compatibility.hosts` (`validate.ts:555-560`). */
|
|
154
161
|
exports.DEVICE_HOSTS = ['home', 'world'];
|
|
@@ -353,6 +360,39 @@ function validateGraphCondition(raw, context, path) {
|
|
|
353
360
|
validateGraphExpr(node.right, context, `${path}.right`);
|
|
354
361
|
}
|
|
355
362
|
const DEVICE_GRAPH_MAX_BRANCH_DEPTH = 8;
|
|
363
|
+
/**
|
|
364
|
+
* The interaction `display` hint: `{ kind: 'time', stateKey }` renders an
|
|
365
|
+
* epoch-millisecond state value in the VIEWER's local time ("rings 7:00 AM"),
|
|
366
|
+
* and "Off" when it is 0 or absent. The hint is presentation only.
|
|
367
|
+
*/
|
|
368
|
+
function validateInteractionDisplay(raw, id, state, errors) {
|
|
369
|
+
const path = `interaction ${id} display`;
|
|
370
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
371
|
+
errors.push(`${path} must be an object`);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const display = object(raw);
|
|
375
|
+
if (display.kind !== 'time') {
|
|
376
|
+
errors.push(`${path}.kind must be "time"`);
|
|
377
|
+
}
|
|
378
|
+
for (const key of Object.keys(display)) {
|
|
379
|
+
if (key !== 'kind' && key !== 'stateKey') {
|
|
380
|
+
errors.push(`${path}: unknown field "${key}"`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (!isNonEmptyName(display.stateKey)) {
|
|
384
|
+
errors.push(`${path}.stateKey must be a non-empty state key`);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
const spec = object(state[String(display.stateKey)]);
|
|
388
|
+
if (state[String(display.stateKey)] === undefined) {
|
|
389
|
+
errors.push(`${path}.stateKey "${String(display.stateKey)}" is not declared in state`);
|
|
390
|
+
}
|
|
391
|
+
else if (spec.type !== 'float' && spec.type !== 'integer') {
|
|
392
|
+
errors.push(`${path}.stateKey "${String(display.stateKey)}" must be a float or integer state holding epoch milliseconds`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
356
396
|
/**
|
|
357
397
|
* Validate one behavior-graph step list, recursing into every nested list.
|
|
358
398
|
*
|
|
@@ -1307,7 +1347,7 @@ function deviceWebPanelInteractionError(definition, interaction) {
|
|
|
1307
1347
|
}
|
|
1308
1348
|
return null;
|
|
1309
1349
|
}
|
|
1310
|
-
function validateDeviceProject(input) {
|
|
1350
|
+
function validateDeviceProject(input, options = {}) {
|
|
1311
1351
|
const target = readDefinitionPath(input);
|
|
1312
1352
|
const errors = [];
|
|
1313
1353
|
const warnings = [];
|
|
@@ -1537,6 +1577,9 @@ function validateDeviceProject(input) {
|
|
|
1537
1577
|
errors.push(`interaction ${id} requires a label`);
|
|
1538
1578
|
else if (interaction.label.length > 40)
|
|
1539
1579
|
errors.push(`interaction ${id} label must be 40 characters or fewer`);
|
|
1580
|
+
if (interaction.display !== undefined) {
|
|
1581
|
+
validateInteractionDisplay(interaction.display, id, state, errors);
|
|
1582
|
+
}
|
|
1540
1583
|
if (interaction.maxDistanceM !== undefined) {
|
|
1541
1584
|
if (!isFiniteNumber(interaction.maxDistanceM) ||
|
|
1542
1585
|
interaction.maxDistanceM <= 0 ||
|
|
@@ -2242,6 +2285,8 @@ function validateDeviceProject(input) {
|
|
|
2242
2285
|
errors,
|
|
2243
2286
|
usage: graphUsage,
|
|
2244
2287
|
};
|
|
2288
|
+
let deadlineTriggerUsed = false;
|
|
2289
|
+
const deadlineKeys = new Set();
|
|
2245
2290
|
triggers.forEach((raw, index) => {
|
|
2246
2291
|
const triggerPath = `graph.triggers.${index}`;
|
|
2247
2292
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
@@ -2265,6 +2310,15 @@ function validateDeviceProject(input) {
|
|
|
2265
2310
|
if (!isNonEmptyName(trigger.key))
|
|
2266
2311
|
errors.push(`${triggerPath}: stateChanged requires a non-empty key`);
|
|
2267
2312
|
}
|
|
2313
|
+
else if (on === 'deadline') {
|
|
2314
|
+
if (typeof trigger.key === 'string' && deadlineKeys.has(trigger.key)) {
|
|
2315
|
+
errors.push(`${triggerPath}: deadline key "${trigger.key}" is declared by more than one deadline trigger`);
|
|
2316
|
+
}
|
|
2317
|
+
if (typeof trigger.key === 'string')
|
|
2318
|
+
deadlineKeys.add(trigger.key);
|
|
2319
|
+
deadlineTriggerUsed = true;
|
|
2320
|
+
errors.push(...(0, deviceGraphChecks_1.deviceDeadlineTriggerErrors)(trigger, triggerPath, state));
|
|
2321
|
+
}
|
|
2268
2322
|
else if (on === 'signal') {
|
|
2269
2323
|
if (!isNonEmptyName(trigger.name))
|
|
2270
2324
|
errors.push(`${triggerPath}: signal requires a non-empty name`);
|
|
@@ -2309,6 +2363,12 @@ function validateDeviceProject(input) {
|
|
|
2309
2363
|
}
|
|
2310
2364
|
}
|
|
2311
2365
|
});
|
|
2366
|
+
// The engine charges one counter for every step of every trigger, nested
|
|
2367
|
+
// steps included (`PortableDeviceSupport.unsupportedStep`); the per-action AST
|
|
2368
|
+
// budget above does not see the graph at all.
|
|
2369
|
+
errors.push(...(0, deviceGraphChecks_1.deviceGraphBudgetErrors)(triggers));
|
|
2370
|
+
if (options.room)
|
|
2371
|
+
errors.push(...(0, deviceGraphChecks_1.deviceRoomAuthorityErrors)(definition));
|
|
2312
2372
|
// `compatibility` itself is optional on the host, and its `hosts` array is a
|
|
2313
2373
|
// `min(1)` enum — NOT "both home and world". Demanding both here rejected a
|
|
2314
2374
|
// world-only Device the host loads happily.
|
|
@@ -2366,6 +2426,7 @@ function validateDeviceProject(input) {
|
|
|
2366
2426
|
Object.values(state).some((spec) => object(spec).persist === true),
|
|
2367
2427
|
],
|
|
2368
2428
|
['presentation.nodeMotion', graphUsage.nodeMotion],
|
|
2429
|
+
['clock.deadline', deadlineTriggerUsed],
|
|
2369
2430
|
[
|
|
2370
2431
|
'clock.serverTime',
|
|
2371
2432
|
components.some((component) => component.type === 'clockHands'),
|