@hypersoniclabs/helix-mcp 0.2.5 → 0.2.12

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 (98) hide show
  1. package/README.md +81 -11
  2. package/dist/continuumCanary.d.ts +17 -0
  3. package/dist/continuumCanary.js +17 -0
  4. package/dist/continuumCanary.js.map +1 -0
  5. package/dist/server.d.ts +14 -1
  6. package/dist/server.js +4058 -162
  7. package/dist/server.js.map +1 -1
  8. package/dist/tsconfig.build.tsbuildinfo +1 -1
  9. package/dist/vehicleTools.d.ts +86 -0
  10. package/dist/vehicleTools.js +229 -0
  11. package/dist/vehicleTools.js.map +1 -0
  12. package/docs/avatar-face.md +115 -0
  13. package/docs/bridge.md +98 -0
  14. package/docs/bring-your-world.md +117 -0
  15. package/docs/catalog.md +69 -1
  16. package/docs/character-animation.md +442 -0
  17. package/docs/character-attachments.md +166 -0
  18. package/docs/character-world.md +785 -130
  19. package/docs/continuum.md +153 -0
  20. package/docs/items.md +73 -0
  21. package/docs/lighting-world.md +667 -0
  22. package/docs/locomotion-clip-spec.md +294 -0
  23. package/docs/manifest.md +31 -6
  24. package/docs/multiplayer-logic.md +460 -17
  25. package/docs/multiplayer-templates/chrono-orchard.md +36 -22
  26. package/docs/multiplayer-templates/collect-a-thon.md +28 -38
  27. package/docs/multiplayer-templates/collections.md +24 -24
  28. package/docs/multiplayer-templates/hangout.md +124 -111
  29. package/docs/multiplayer-templates/npc-wave.md +310 -0
  30. package/docs/multiplayer-templates/obby.md +13 -16
  31. package/docs/multiplayer-templates/persistent-progress.md +218 -0
  32. package/docs/multiplayer-templates/physics-bumper.md +27 -9
  33. package/docs/multiplayer-templates/physics-football.md +22 -8
  34. package/docs/multiplayer-templates/relic-bearers.md +12 -15
  35. package/docs/multiplayer-templates/server-motion.md +16 -19
  36. package/docs/multiplayer-templates/shooter-range.md +275 -0
  37. package/docs/multiplayer-templates/team-control.md +28 -15
  38. package/docs/multiplayer-templates/turn-arena.md +31 -22
  39. package/docs/multiplayer-templates/voice-radio.md +166 -0
  40. package/docs/multiplayer-templates/wave-survival.md +7 -8
  41. package/docs/multiplayer-templates/world-shop.md +240 -0
  42. package/docs/multiplayer-world.md +222 -129
  43. package/docs/npc-world.md +623 -0
  44. package/docs/publishing.md +108 -28
  45. package/docs/purchases.md +223 -0
  46. package/docs/scene-performance.md +64 -0
  47. package/docs/screenshots.md +140 -0
  48. package/docs/sdk.md +324 -5
  49. package/docs/shooter-worlds.md +537 -0
  50. package/docs/terrain.md +173 -0
  51. package/docs/upgrades.md +324 -0
  52. package/docs/vehicles.md +727 -0
  53. package/docs/world-inspect.md +156 -0
  54. package/docs/world-look.md +241 -0
  55. package/docs/world-recipe.md +65 -6
  56. package/package.json +15 -4
  57. package/skills/README.md +91 -0
  58. package/skills/helix-assets/SKILL.md +491 -0
  59. package/skills/helix-assets/references/asset-sources.md +143 -0
  60. package/skills/helix-assets/references/vault-api.md +105 -0
  61. package/skills/helix-avatar-qa/SKILL.md +85 -0
  62. package/skills/helix-avatars/SKILL.md +206 -0
  63. package/skills/helix-avatars/references/contract.md +166 -0
  64. package/skills/helix-avatars/references/dynamics.md +367 -0
  65. package/skills/helix-avatars/references/face.md +50 -0
  66. package/skills/helix-avatars/references/publish.md +76 -0
  67. package/skills/helix-avatars/references/qa.md +251 -0
  68. package/skills/helix-avatars/references/rigging.md +88 -0
  69. package/skills/helix-avatars/references/source-generated.md +190 -0
  70. package/skills/helix-avatars/references/source-model.md +90 -0
  71. package/skills/helix-avatars/references/source-rigid.md +90 -0
  72. package/skills/helix-avatars/references/source-vrm.md +61 -0
  73. package/skills/helix-gauntlet/SKILL.md +128 -0
  74. package/skills/helix-multiplayer/SKILL.md +150 -0
  75. package/skills/helix-multiplayer/references/dsl-capability-map.md +107 -0
  76. package/skills/helix-vehicles/SKILL.md +218 -0
  77. package/skills/helix-vehicles/references/addons.md +212 -0
  78. package/skills/helix-vehicles/references/appearance.md +339 -0
  79. package/skills/helix-vehicles/references/audio-import.md +138 -0
  80. package/skills/helix-vehicles/references/audio.md +580 -0
  81. package/skills/helix-vehicles/references/cabin.md +225 -0
  82. package/skills/helix-vehicles/references/host-manifest.md +174 -0
  83. package/skills/helix-vehicles/references/physics-from-specs.md +463 -0
  84. package/skills/helix-vehicles/references/publish.md +214 -0
  85. package/skills/helix-vehicles/references/qa.md +177 -0
  86. package/skills/helix-vehicles/references/reference-package.json +3481 -0
  87. package/skills/helix-vehicles/references/reference-package.md +69 -0
  88. package/skills/helix-vehicles/references/source-beamng.md +167 -0
  89. package/skills/helix-vehicles/references/source-concept.md +38 -0
  90. package/skills/helix-vehicles/references/source-model.md +100 -0
  91. package/skills/helix-vehicles/references/source-scratch.md +60 -0
  92. package/skills/helix-world-build/SKILL.md +376 -0
  93. package/skills/helix-world-build/references/config-gates.md +104 -0
  94. package/skills/helix-world-director/SKILL.md +210 -0
  95. package/skills/helix-world-qa/SKILL.md +371 -0
  96. package/skills/helix-world-qa/references/perf-budgets.md +240 -0
  97. package/skills/helix-world-qa/references/perf-handle.md +125 -0
  98. package/skills/helix-world-qa/references/visual-scorecard.md +85 -0
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MINIMUM_VEHICLE_PARTS = exports.VEHICLE_AUDIO_RULE = exports.VEHICLE_OUTPUT_LIMIT = void 0;
4
+ exports.reportVehicleRun = reportVehicleRun;
5
+ exports.displayArg = displayArg;
6
+ exports.displayCommand = displayCommand;
7
+ exports.audioImportArgs = audioImportArgs;
8
+ exports.vehicleUnavailableText = vehicleUnavailableText;
9
+ exports.summariseReferencePackage = summariseReferencePackage;
10
+ exports.summariseVehicleHost = summariseVehicleHost;
11
+ exports.summariseCustomizerGuard = summariseCustomizerGuard;
12
+ // Pure helpers behind the vehicle tools in server.ts: how an executed `helix vehicle …` run is reported back, what
13
+ // the reference-package copy tool says about the file it wrote, and how a host's slot board + add-on rail are
14
+ // summarised. No I/O beyond the log file a long run spills into, so smoke tests can drive them directly.
15
+ //
16
+ // Why the vehicle tools EXECUTE rather than hand back a command (round-1 F40 forensics §14): a DELEGATED
17
+ // `validate_vehicle` sent the agent hunting for the CLI inside the MCP's npm cache, after which it used the raw CLI
18
+ // for everything — skipping the MCP wrappers and pinning a stale QA harness. Running the bundled CLI here keeps an
19
+ // agent on the MCP path, and the exit code is the gate result.
20
+ const node_fs_1 = require("node:fs");
21
+ const node_os_1 = require("node:os");
22
+ const node_path_1 = require("node:path");
23
+ /** Above this many characters a run's output is cut to its head and tail; the whole of it goes to a log file. */
24
+ exports.VEHICLE_OUTPUT_LIMIT = 30_000;
25
+ const HEAD_CHARS = 6_000;
26
+ function spill(label, body, logDir) {
27
+ try {
28
+ const dir = logDir ?? (0, node_path_1.join)((0, node_os_1.tmpdir)(), 'helix-mcp-vehicle');
29
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
30
+ const file = (0, node_path_1.join)(dir, `${label.replace(/[^a-z0-9]+/gi, '-').toLowerCase()}-${Date.now()}.log`);
31
+ (0, node_fs_1.writeFileSync)(file, body);
32
+ return file;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ /**
39
+ * Turn an executed CLI run into the tool result. Exit 0 is a pass; any other exit is returned as `isError` with the
40
+ * full output (never a throw), because the output IS the list of things to fix.
41
+ */
42
+ function reportVehicleRun(label, run, options = {}) {
43
+ const limit = options.limit ?? exports.VEHICLE_OUTPUT_LIMIT;
44
+ const minutes = (run.elapsedMs / 60_000).toFixed(1);
45
+ let status;
46
+ if (run.spawnError)
47
+ status = `${label}: COULD NOT RUN (${run.spawnError})`;
48
+ else if (run.timedOut)
49
+ status = `${label}: TIMED OUT after ${minutes} min — the CLI was stopped; nothing after that point happened. Re-run the same call (vehicle publish steps resume from their ledger).`;
50
+ else if (run.exitCode === 0)
51
+ status = `${label}: exit 0 (${options.exitMeaning?.[0] ?? 'pass'}) in ${minutes} min`;
52
+ else {
53
+ const meaning = run.exitCode === null ? undefined : options.exitMeaning?.[run.exitCode];
54
+ status = `${label}: FAILED — exit ${run.exitCode}${meaning ? ` (${meaning})` : ''} in ${minutes} min. The output below says what to fix.`;
55
+ }
56
+ const sections = [run.stdout.trim(), run.stderr.trim() ? `--- stderr ---\n${run.stderr.trim()}` : ''].filter(Boolean);
57
+ let body = sections.join('\n\n') || '(no output)';
58
+ if (body.length > limit) {
59
+ const file = spill(label, `$ ${run.command}\n${status}\n\n${body}\n`, options.logDir);
60
+ const tail = limit - HEAD_CHARS;
61
+ body = `${body.slice(0, HEAD_CHARS)}\n\n… ${body.length - limit} characters omitted${file ? ` — the full output is in ${file}` : ''} …\n\n${body.slice(-tail)}`;
62
+ }
63
+ const ok = !run.spawnError && !run.timedOut && run.exitCode === 0;
64
+ const text = [`$ ${run.command}`, status, '', body, ok && options.next ? `\nNext: ${options.next}` : ''].filter((s) => s !== '').join('\n');
65
+ return { text, isError: !ok };
66
+ }
67
+ /** Shell-quote one argument for display (the run itself never goes through a shell). */
68
+ function displayArg(value) {
69
+ return /^[A-Za-z0-9_./:=@%+-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
70
+ }
71
+ function displayCommand(args) {
72
+ return ['helix', ...args.map(displayArg)].join(' ');
73
+ }
74
+ // ---- vehicle audio: the hard rule, and the audio-import call ---------------------------------------------------
75
+ /** The standing rule every vehicle-audio tool description and route note repeats, so no agent reaches it second-hand. */
76
+ exports.VEHICLE_AUDIO_RULE = 'HARD RULE: NEVER generate or synthesise vehicle sound (no resynthesis, no generate_audio, not even for a slot nothing provides, and never rely on the platform\'s default sounds: the synthesised fallback engine, the generated default sound pack, runtime noise); ALWAYS source real recordings, from BeamNG mods as much as possible, and a car ships a sourced recording for EVERY one of the 31 runtime slots (the only empty slots are a component the real car physically lacks, declared consumed:false, absent:"not-fitted" plus a reason, and exhaustLadder absent:"combined-with-engine" beside a sourced engine ladder; doors are required). Audio step one, whatever the mesh source: find the most accurate simulator mod of that exact car (BeamNG first; Assetto Corsa/ACC, rFactor 2, Automobilista 2 where theirs is more accurate), choose it by MEASURING candidates against real recordings of the car (firing-fundamental pitch track vs rpm, spectral centroid, roughness, idle character, limiter cadence), and extract it with import_vehicle_audio.';
77
+ /**
78
+ * The argv for `helix vehicle audio-import <source> --out <dir>` (sim-audio-cli lane's interface). `beamngRoot`
79
+ * (the BeamNG.drive install that holds the stock FMOD banks and blends a mod refers to by name) is `--beamng-root`;
80
+ * the rest map one to one onto the flags documented in helix-creator-cli docs/vehicle-audio-import.md.
81
+ */
82
+ function audioImportArgs(a, resolvePath) {
83
+ return [
84
+ 'vehicle', 'audio-import', resolvePath(a.source),
85
+ '--out', resolvePath(a.out),
86
+ ...(a.sim ? ['--sim', a.sim] : []),
87
+ ...(a.vehicle ? ['--vehicle', a.vehicle] : []),
88
+ ...(a.config ? ['--config', a.config.includes('/') ? resolvePath(a.config) : a.config] : []),
89
+ ...(a.map ? ['--map', resolvePath(a.map)] : []),
90
+ ...(a.beamngRoot ? ['--beamng-root', resolvePath(a.beamngRoot)] : []),
91
+ ...(a.cylinders ? ['--cylinders', String(a.cylinders)] : []),
92
+ ...(a.prefer ? ['--prefer', a.prefer] : []),
93
+ ...(a.acNativeRungs ? ['--ac-native-rungs'] : []),
94
+ ...(a.modUrl ? ['--mod-url', a.modUrl] : []),
95
+ ...(a.basePackage ? ['--base-package', resolvePath(a.basePackage)] : []),
96
+ ...(a.json ? ['--json'] : []),
97
+ ];
98
+ }
99
+ /** What the tools answer when the bundled CLI lacks a vehicle command, so a stale CLI never reads as a crash. */
100
+ function vehicleUnavailableText(kind, path, tool) {
101
+ const command = `helix ${path.join(' ')}`;
102
+ if (kind === 'audio-import') {
103
+ return [
104
+ `${tool}: NOT AVAILABLE in the CLI bundled with this MCP — \`${command}\` does not exist in it.`,
105
+ 'Nothing was done. Run check_for_updates: a newer MCP bundles a CLI that has it.',
106
+ `${exports.VEHICLE_AUDIO_RULE}`,
107
+ 'Until then the rule still holds. Do NOT synthesise, generate or resynthesise the sound to fill the gap, and do not omit audio.bank or a slot to fall back on a platform default; check_vehicle_audio and publish_vehicle fail an empty or default-falling-back slot (AUDIO_SLOT_UNSOURCED) and a car without sourced engine audio. Extract the mod by hand (BeamNG art/sound blends + .flac rungs, Assetto Corsa .bank via vgmstream-cli), record each slot\'s provenance { kind, sim, mod, modUrl, sourceFile, sourceSha256, extractor } yourself, and report the import as blocked on the CLI.',
108
+ ].join('\n');
109
+ }
110
+ return [
111
+ `${tool}: NOT AVAILABLE in the CLI bundled with this MCP — \`${command}\` does not exist in it.`,
112
+ 'Minting a new vehicle or vehicle add-on item is unavailable during the Package-first publication cutover. Nothing was done.',
113
+ 'What still works: every offline gate (validate_vehicle, simulate_vehicle, check_vehicle_audio, preview_vehicle, check_vehicle_addon), check_vehicle_host, visual QA, distributions on existing items, and a NEW VERSION of an existing car: seal it staged with publish_continuum_package, pass visual_qa_item on that version, then activate it by re-running `helix vehicle publish <dir> --item <itemId> --package-version <v> --reason "<why>"` once the bundled CLI has it (the resume moves the pin and properties.vehiclePackage together). NEVER `helix item set-version` for a vehicle: it moves the Continuum pin but not properties.vehiclePackage, so the car keeps serving its old package (until the CLI says otherwise) (read_skill({ name: "helix-vehicles", reference: "publish" }), "A new version of an existing car").',
114
+ 'Finish every gate, keep the directory ready, and REPORT the mint as blocked. Do not improvise it with raw API calls, an older CLI, or publish_item. Run check_for_updates: a newer MCP may bundle a CLI that has the command again.',
115
+ ].join('\n');
116
+ }
117
+ const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
118
+ /** One line per top-level block: its size and what it holds, so the agent can open the file block by block. */
119
+ function summariseReferencePackage(pkg) {
120
+ if (!isRecord(pkg))
121
+ return ['(not a JSON object)'];
122
+ const lines = [];
123
+ for (const [key, value] of Object.entries(pkg)) {
124
+ const chars = JSON.stringify(value)?.length ?? 0;
125
+ let what = '';
126
+ if (Array.isArray(value))
127
+ what = `${value.length} entries`;
128
+ else if (isRecord(value)) {
129
+ const keys = Object.keys(value);
130
+ what = `${keys.length} keys: ${keys.slice(0, 12).join(', ')}${keys.length > 12 ? ', …' : ''}`;
131
+ }
132
+ else
133
+ what = JSON.stringify(value);
134
+ if (key === 'audio' && isRecord(value)) {
135
+ const slots = Array.isArray(value.slots) ? value.slots.length : 0;
136
+ const assets = isRecord(value.bank) && Array.isArray(value.bank.assets) ? value.bank.assets.length : 0;
137
+ what += ` (${slots} slots, ${assets} bank samples)`;
138
+ }
139
+ lines.push(`- ${key} (${chars.toLocaleString('en-GB')} chars): ${what}`);
140
+ }
141
+ return lines;
142
+ }
143
+ // ---- vehicle host board + add-on rail --------------------------------------------------------------------------
144
+ /**
145
+ * The parts set a car must ship before it is done (helix-vehicles step "Add-ons"). Slot types are the backend's
146
+ * `helix/vehicle-base@2` pack ids. `spoiler` is required only when the real car has a wing or aftermarket ones exist,
147
+ * so it is reported, never counted as missing.
148
+ */
149
+ exports.MINIMUM_VEHICLE_PARTS = [
150
+ { slotType: 'vehicle.wheel@1', part: 'wheel set (rims + tyres)', required: true },
151
+ { slotType: 'vehicle.exhaust@1', part: 'exhaust', required: true },
152
+ { slotType: 'vehicle.ecu@1', part: 'ECU', required: true },
153
+ { slotType: 'vehicle.transmission@1', part: 'gearbox', required: true },
154
+ { slotType: 'vehicle.suspension@1', part: 'suspension (coilovers)', required: true },
155
+ { slotType: 'vehicle.brakes@1', part: 'brakes', required: true },
156
+ { slotType: 'vehicle.arb@1', part: 'anti-roll bars', required: true },
157
+ { slotType: 'vehicle.engine@1', part: 'engine / powertrain (where the real car has a swappable engine or a well-known upgrade)', required: false },
158
+ { slotType: 'vehicle.spoiler@1', part: 'spoiler / wing (where the real car has one or a well-known aftermarket one exists)', required: false },
159
+ ];
160
+ function summariseVehicleHost(itemId, board, rail) {
161
+ const lines = [];
162
+ const b = isRecord(board) ? board : {};
163
+ const surface = isRecord(b.surface) ? b.surface : {};
164
+ const count = (k) => (Array.isArray(surface[k]) ? surface[k].length : 0);
165
+ const sockets = count('sockets');
166
+ const nodes = count('nodes');
167
+ const paths = count('paths');
168
+ const hostKind = typeof b.hostKind === 'string' ? b.hostKind : null;
169
+ const grants = isRecord(b.hostGrantExtensions) ? b.hostGrantExtensions : {};
170
+ const problems = [];
171
+ lines.push(`Vehicle host ${itemId}`);
172
+ lines.push(`- slot board: hostKind ${hostKind ?? 'none'}, use ${String(b.use ?? '—')}, ${Number(b.slotCount ?? 0)} slots`);
173
+ lines.push(`- measured surface: ${sockets} sockets, ${nodes} nodes, ${paths} tunable paths, ${count('materialSlots')} material slots`);
174
+ lines.push(`- hide grants: ${Object.keys(grants).length ? JSON.stringify(grants) : 'none (only the standard *_stock_* globs)'}`);
175
+ const repair = 'The board is derived ONCE, at mint, from the GLB\'s asset.extras.HELIX_vehicle, the vehicle package and its hostGrantExtensions; a version move does not rebuild it. Today the repair is a fresh car minted with the manifest (read_skill helix-vehicles reference "publish", section 2): delist this one and report it.';
176
+ if (hostKind !== 'vehicle')
177
+ problems.push(`the item is not a vehicle host: its GLB carried no asset.extras.HELIX_vehicle at upload (read_skill helix-vehicles reference "host-manifest"). ${repair}`);
178
+ if (hostKind === 'vehicle' && sockets === 0)
179
+ problems.push(`the board has no sockets, so wheel, wing and exhaust add-ons fit nothing: asset.extras.HELIX_vehicle.sockets was empty. ${repair}`);
180
+ if (hostKind === 'vehicle' && nodes === 0)
181
+ problems.push('the board has no nodes, so no add-on can hide a stock part.');
182
+ if (hostKind === 'vehicle' && paths === 0)
183
+ problems.push(`the board has no tunable paths, so no performance part (ECU, gearbox, brakes, suspension, anti-roll bars) can fit. ${repair}`);
184
+ const r = isRecord(rail) ? rail : {};
185
+ const items = Array.isArray(r.items) ? r.items.filter(isRecord) : [];
186
+ const total = typeof r.total === 'number' ? r.total : items.length;
187
+ lines.push(`- add-ons that fit it (the customizer's list): ${total}${r.truncated ? ' (truncated)' : ''}`);
188
+ const bySlot = new Map();
189
+ for (const item of items) {
190
+ const slotType = typeof item.slotType === 'string' ? item.slotType : 'unknown';
191
+ bySlot.set(slotType, [...(bySlot.get(slotType) ?? []), item]);
192
+ }
193
+ for (const [slotType, rows] of [...bySlot.entries()].sort()) {
194
+ lines.push(` - ${slotType}: ${rows.map((i) => `${String(i.title)}${i.distributionId ? ` (${String(i.priceLix ?? '?')} LIX)` : ' (not for sale)'}`).join('; ')}`);
195
+ }
196
+ lines.push('', 'Minimum parts set:');
197
+ const missing = [];
198
+ for (const want of exports.MINIMUM_VEHICLE_PARTS) {
199
+ const rows = bySlot.get(want.slotType) ?? [];
200
+ const sold = rows.filter((i) => i.distributionId).length;
201
+ const mark = rows.length ? (sold ? '✔' : '◐') : want.required ? '✖' : '·';
202
+ lines.push(` ${mark} ${want.part} — ${want.slotType}: ${rows.length} fitting, ${sold} for sale`);
203
+ if (want.required && sold === 0)
204
+ missing.push(want.part);
205
+ }
206
+ if (missing.length)
207
+ problems.push(`the minimum parts set is not complete and for sale: ${missing.join(', ')}.`);
208
+ if (problems.length)
209
+ lines.push('', 'Not done:', ...problems.map((p) => `- ${p}`));
210
+ else
211
+ lines.push('', 'The host board is complete and every required part fits and is for sale. Now open the customizer and look at each part fitted.');
212
+ return { text: lines.join('\n'), ok: problems.length === 0 };
213
+ }
214
+ /**
215
+ * The customizer-guard half of `check_vehicle_host`: the CLI's `checkHostAddOns` verdicts (every add-on on the car's rail
216
+ * judged by the web customizer's own guard) as a section of the report. Any refused add-on, or a rail truncated before
217
+ * every add-on could be checked, fails the host check; the message names each add-on and quotes the customizer's text.
218
+ */
219
+ function summariseCustomizerGuard(formatted, verdicts, truncated) {
220
+ const refused = verdicts.filter((v) => !v.ok);
221
+ const lines = ['Web customizer guard (the same check the customizer runs when a buyer stages each part):', ...formatted.split('\n').map((l) => ` ${l}`)];
222
+ if (refused.length) {
223
+ lines.push('', 'Not done:', ...refused.map((v) => `- add-on ${v.itemId} "${v.title ?? '?'}" would be refused by the web customizer ("${v.error}"): it cannot be equipped or saved there, so it is not a shipped part. Republish it as a new Package Version the customizer accepts (read_skill helix-vehicles reference "addons").`));
224
+ }
225
+ if (truncated)
226
+ lines.push('', 'Not done:', '- the add-on rail was truncated, so not every add-on could be checked against the customizer guard.');
227
+ return { text: lines.join('\n'), ok: refused.length === 0 && !truncated };
228
+ }
229
+ //# sourceMappingURL=vehicleTools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vehicleTools.js","sourceRoot":"","sources":["../src/vehicleTools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ # Avatar faces — lip-sync, blink and expressions
2
+
3
+ A HELIX character speaks with its mouth only if the GLB it was published from carries a
4
+ `helix/avatar-face@1` manifest. Nothing at runtime, in the world, or in the shell can add one later.
5
+ An avatar that arrives without it is **permanently mute**: it will stand in a voice chat with a closed
6
+ mouth and never blink, and no error is raised anywhere.
7
+
8
+ That is the whole trap this document exists for. Everything else here is detail.
9
+
10
+ ## What actually drives the mouth
11
+
12
+ Face V1 is a **receiver-local presentation layer**. No facial weight, microphone sample or derived
13
+ biometric is ever sent through HELIX multiplayer state:
14
+
15
+ - Each client analyses the remote voice audio **it already plays** and solves visemes locally.
16
+ - The local player's own avatar is driven from their **own microphone analyser** — nobody is
17
+ subscribed to their own track, and that mouth is visible in third person, in mirrors and through
18
+ the in-world camera.
19
+ - Both lanes are gated by publish-mute × the sensitivity gate, so a muted player does not mouth
20
+ silently at everyone.
21
+
22
+ **No world authoring is required.** `mp.attachVoice(Helix.voice, ...)` wires replica faces, the local
23
+ face, the screen-space face LOD and pad push-to-talk in one call. Do not build a face system, do not
24
+ network viseme weights, and do not drive morph targets from world code.
25
+
26
+ ## The capability ladder
27
+
28
+ `helix character import` prints a `face:` line and the avatar preflight reports the same capability.
29
+ It degrades in this order, and each rung is honest about what the asset can do:
30
+
31
+ | capability | what the asset carries | what a speaker looks like |
32
+ |---|---|---|
33
+ | `viseme15` | the 15 Oculus visemes | full lip-sync |
34
+ | `vowel5` | the five VRM/VRoid vowels | lip-sync folded onto five shapes (the runtime does the fold — a 15-viseme solver drives a 5-vowel asset) |
35
+ | `jaw` | a `jawOpen` morph or jaw bone | the jaw opens with speech amplitude |
36
+ | `none` | nothing | **closed mouth, forever** |
37
+
38
+ Blink channels (`blink.left` / `blink.right`) and gaze channels are separate from the ladder. With
39
+ them the runtime blinks and idles the eyes on its own; without them the eyes are dead. V1 never
40
+ fabricates blink motion for an asset that has no explicit targets.
41
+
42
+ Screen-space LOD: full facial work above 96 projected avatar pixels, jaw-only from 32, nothing below.
43
+
44
+ ## Getting a face onto an imported character
45
+
46
+ ### VRM (`.vrm`) — automatic, use the Bridge
47
+
48
+ `bridge_import` routes a `.vrm` through the `helix.character.vrm.import` capability, which carries the
49
+ five vowels, both blinks and the six expression presets across on its own. It runs in-process on
50
+ read/write permissions alone and needs no reviewed plan key. **Never hand-roll a VRM conversion.**
51
+
52
+ ### A GLB/FBX biped — `faceMap`, or it is probably mute
53
+
54
+ `import_character` (and `helix character import`) detects only **exact, established** morph names —
55
+ `viseme_*`, VRChat's `vrc.v_*`, plain `aa/ih/ou/ee/oh`, `jawOpen`/`mouthOpen`, `blinkLeft`/`Blink_L`
56
+ and their obvious spellings. It deliberately does not guess from arbitrary names, so a rig whose
57
+ shapes are called anything else — including a **VRoid model exported as GLB rather than VRM**, whose
58
+ shapes are `Fcl_MTH_A`, `Fcl_EYE_Close_L`, … — imports with `face: none` unless you say otherwise.
59
+
60
+ So: after any character import, **read the `face:` line**. If it says `none` and the source model has
61
+ blend shapes, inspect their names and pass `faceMap` — a JSON file mapping HELIX semantics onto the
62
+ morphs the asset actually has:
63
+
64
+ ```json
65
+ {
66
+ "contract": "helix/avatar-face@1",
67
+ "channels": {
68
+ "vowel.aa": { "kind": "morph", "name": "Fcl_MTH_A", "scale": 1 },
69
+ "vowel.ih": { "kind": "morph", "name": "Fcl_MTH_I", "scale": 1 },
70
+ "vowel.ou": { "kind": "morph", "name": "Fcl_MTH_U", "scale": 1 },
71
+ "vowel.ee": { "kind": "morph", "name": "Fcl_MTH_E", "scale": 1 },
72
+ "vowel.oh": { "kind": "morph", "name": "Fcl_MTH_O", "scale": 1 },
73
+ "blink.left": { "kind": "morph", "name": "Fcl_EYE_Close_L", "scale": 1 },
74
+ "blink.right":{ "kind": "morph", "name": "Fcl_EYE_Close_R", "scale": 1 }
75
+ },
76
+ "lod": { "fullMinPixels": 96, "jawMinPixels": 32 }
77
+ }
78
+ ```
79
+
80
+ A `jawOpen` channel may instead be a **bone**: `{ "kind": "bone", "name": "FACIAL_C_Jaw", "axis": "y",
81
+ "maxDegrees": 18 }`. The importer verifies every declared morph or bone exists after conversion and
82
+ fails loudly if one does not, so a face map cannot silently name nothing.
83
+
84
+ A stylised avatar with no mouth shapes at all is legitimately `none` — say so plainly rather than
85
+ inventing channels.
86
+
87
+ ### MetaHuman / Unreal
88
+
89
+ Supply the source through Bridge with `type: "character"` and the face as the named `face` input, and
90
+ the head/eye material path is preserved. A MetaHuman legacy head can carry 800+ blend shapes; the
91
+ importer drops every delta the manifest cannot drive, so declaring the channels is also what keeps the
92
+ web asset small.
93
+
94
+ ## Add-ons do not carry faces
95
+
96
+ `helix.character.addon.import` packages a modular wearable (`character.<slot>@1`) against a validated
97
+ base avatar. A hat, a jacket or a pair of ears is not a face and never declares face channels — the
98
+ face belongs to the base body the add-on is worn on. If the base is `none`, installing add-ons does
99
+ not change that.
100
+
101
+ ## Honest limits, as of this writing
102
+
103
+ - **Face V1 is a `0.3`-line runtime feature.** A world published on the older `^0.2` engine line
104
+ resolves to `0.2.x` and has no face runtime at all. Re-publishing on the current line is the only
105
+ way such a world gains lip-sync.
106
+ - **The stock Base Female body ships with no face channels.** Base Male carries the full 21-channel
107
+ `viseme15` set; the female face pack currently has no morph targets and no facial rig, so she is
108
+ `none` — mute and unblinking. Do not tell a human that "the default avatars lip-sync"; only the male
109
+ one does.
110
+ - The portable browser solver reaches 8 of the 15 viseme shapes from its acoustic features
111
+ (`sil, pp, ff, th, kk, ss, rr, aa`). The remaining shapes exist in the asset and are reachable by a
112
+ better solver, not by this one.
113
+ - Expressions (`happy`/`angry`/`sad`/`relaxed`/`surprised`/`neutral`) ride the same manifest as a
114
+ separate `expressions` block and a separate driver. They are carried by the VRM path and are not
115
+ driven by speech.
package/docs/bridge.md ADDED
@@ -0,0 +1,98 @@
1
+ # HELIX Bridge — agent workflow
2
+
3
+ Bridge is the one path for turning an existing external asset, character, animation, scene, mod export, or engine project into HELIX content. Do not invent a one-off conversion shell script before asking Bridge what is installed.
4
+
5
+ ## Default loop
6
+
7
+ 1. `bridge_detect` fingerprints the source.
8
+ 2. `bridge_inspect` returns the structural evidence and viable goals.
9
+ 3. `bridge_plan` resolves the capability graph without writing or executing anything.
10
+ 4. If the plan has blockers, use `bridge_plugins_list` and `bridge_plugin_inspect`; install a plugin only when the requested source path is available and its declared permissions match the task.
11
+ 5. For any command-backed capability, pass the reviewed plan's `deterministicKey` back as `expectedPlanKey`, approve every exact permission in `requiredPermissions`, and call `bridge_import`. An in-process built-in-only route with no command needs no additional acknowledgement.
12
+ 6. Read every warning and output digest, then call `bridge_validate` on the artifact or provenance record.
13
+ 7. Publish through the existing HELIX package, item, scene, or world path. Bridge conversion is not publication authority.
14
+
15
+ For the common case, `bridge_import` performs the first three steps internally. Use the explicit detect/inspect/plan sequence when the source is unfamiliar, an engine project, or a community plugin would run.
16
+
17
+ ## What Bridge can actually convert
18
+
19
+ `bridge_plugins_list` and `bridge_doctor` are the authority — read them before planning, because a
20
+ plugin can be installed or absent on this machine. The HELIX Verified set the CLI ships with is:
21
+
22
+ | plugin | capability | accepts → produces |
23
+ |---|---|---|
24
+ | `helix.formats` | `helix.asset.copy` | glb / gltf / fbx / obj / usd / usdz / vrm / image / audio → a checksummed package input (preserved, not converted) |
25
+ | `helix.characters` | `helix.character.import` | glb / fbx biped → `helix-humanoid@1` character (retarget, conform, LODs, KTX2) |
26
+ | `helix.characters` | `helix.character.vrm.import` | **`.vrm` → `helix-humanoid@1`**, carrying SpringBone physics, the five vowel visemes, both blinks and the expression presets |
27
+ | `helix.characters` | `helix.animation.retarget` | glb / fbx clip → `helix-humanoid@1` animation |
28
+ | `helix.characters` | `helix.character.addon.import` | glb / fbx part + a published host → a modular add-on and its runtime `character.<slot>@1` contract |
29
+ | `helix.metahuman` | `metahuman.face.materials` | a MetaHuman face → a character-face package with the eyes preserved |
30
+ | `helix.scenes` | `helix.scene.package` | glb / fbx / obj → a deterministic HELIX Scene package |
31
+ | `helix.engines` | `unity.project.extract`, `godot.project.extract` | a Unity or Godot **project** → extracted assets |
32
+ | `helix.bridge.unreal` | `unreal.project.extract` | an Unreal **project** → FBX assets (launches Unreal Editor) |
33
+
34
+ Shipped alongside, but **not** HELIX Verified: `hypersonic.beamng-vehicle`
35
+ (`beamng.vehicle.import`, trust `local-untrusted`) converts a BeamNG vehicle directory to a glb. It
36
+ spawns Blender, so it requires `spawn-process`, `blender` and `unrestricted-host-execution` plus a
37
+ reviewed plan key and a named `palette` input — approve it deliberately, never as a formality.
38
+
39
+ **The VRM route is in-process** — a VRM is a glTF and the whole conversion is TypeScript over
40
+ `gltf-transform` — so `helix bridge import avatar.vrm` runs on read-source/write-output alone and
41
+ needs no reviewed plan key. A **VRChat Unity avatar is the opposite case** and belongs to a
42
+ Unity-permissioned extraction, not this capability; some creators ship a `.vrm` alongside, and when
43
+ they do, the VRM capability handles it and the Unity adapter is not needed at all.
44
+
45
+ **There is no bridge for most game formats.** Cities: Skylines, Source, Bethesda archives, and the
46
+ rest have no capability today. When nothing accepts the source, say so plainly — do not hand-roll a
47
+ converter, and do not route the source through an unrelated capability because it is the only one
48
+ that would accept the file extension.
49
+
50
+ ## Faces
51
+
52
+ A character imported through any of these routes is **mute unless a `helix/avatar-face@1` manifest
53
+ rides its GLB**, and nothing downstream can add one later. The VRM capability carries a face across on
54
+ its own. `helix.character.import` detects only established morph names, so a rig named anything else —
55
+ including a VRoid model exported as GLB rather than VRM — needs an explicit face map. An add-on never
56
+ carries a face; the face belongs to the base body. Read `read_doc({ name: "avatar-face" })` before
57
+ importing any character a human will speak through.
58
+
59
+ ## Safety and permissions
60
+
61
+ Plugin installation and plugin execution are separate decisions. Installed plugins are discoverable, but every command-backed capability—including a packaged HELIX Verified adapter—runs only when `allowPermissions` contains every permission in the selected plan.
62
+
63
+ Never add permission names merely to make a plan green. Report the plugin id, capability, requested permissions, and why they are needed. A plan may be inspected safely without permission grants.
64
+
65
+ Command-backed execution is bound to the reviewed plan. `bridge_import` rejects a missing or changed `expectedPlanKey` before reserving output, snapshotting content, or starting a process. If the source, plugin digest, route, target, tool identity, or inputs change, call `bridge_plan` again and review the new key.
66
+
67
+ Pass the same `headTexture`, `eyeTexture`, `canonical`, `boneMap`, and named `inputs` to both `bridge_plan` and `bridge_import`. Bridge fingerprints them before planning, so changing any auxiliary file or a safe FBX/glTF sidecar changes the reviewed key.
68
+
69
+ Bridge executes plugins without a shell, bounds runtime and stdout, requires artifacts to remain inside the output directory, and re-hashes outputs itself. Command-backed capabilities must request `unrestricted-host-execution` in addition to process and filesystem permissions. That name is deliberate: until an isolated runner ships, approval grants the adapter the authority of a local process rather than pretending the narrower declarations are an OS sandbox. Prefer HELIX Verified capabilities when they satisfy the plan, while still approving the exact permissions they request.
70
+
71
+ ## MetaHuman and Unreal character material path
72
+
73
+ The official MetaHuman face capability carries the eye-material safeguard migrated from `helix-web-engine-new` PR #99:
74
+
75
+ - provide the baked head texture with `headTexture`;
76
+ - omit `eyeTexture` to use the CLI-packaged canonical bake, or supply an explicit override;
77
+ - Bridge refuses textureless `MI_EyeL_Baked` / `MI_EyeR_Baked` materials;
78
+ - validate a combined base avatar with `bridge_validate({ baseAvatar: true })` before publication.
79
+
80
+ ## Official Unreal and modular-character path
81
+
82
+ The bundled CLI packages `helix.bridge.unreal`, a HELIX Verified, command-backed Unreal skeletal-mesh adapter. Supply an Unreal asset or asset-set profile as the named `unrealAsset` input. Use `type: "extract"` to retain every FBX from a batch, then run each retained asset through its character or add-on import. Because the adapter launches Unreal Editor, its plan requires a reviewed plan key plus the exact filesystem, process, host-execution, and `unreal-editor` permissions reported by `bridge_plan`.
83
+
84
+ Use `type: "addon"` with named `host` and `addon` inputs to package a modular wearable against a validated base avatar. The add-on profile carries the runtime `character.<slot>@1` contract used by the customizer. Split body and face exports can use `type: "character"` with the face supplied as the named `face` input; Bridge preserves the shared rig, LODs, morphs, and material roles in the composed package.
85
+
86
+ ## Tool boundary
87
+
88
+ Bridge implementation lives in `{{CLI_PKG}}`. These MCP tools either execute that bundled CLI or return its output. Do not port converter logic into the MCP server.
89
+
90
+ The canonical schemas are:
91
+
92
+ - `helix.bridge-detection/1`
93
+ - `helix.bridge-plan/1`
94
+ - `helix.bridge-plugin/1`
95
+ - `helix.bridge-plugin-request/1`
96
+ - `helix.bridge-provenance/1`
97
+
98
+ Use `bridge_doctor` to see in-process plugins, the packaged official Unreal adapter, and optional engine adapters. Engine projects can always be detected and planned; execution requires an eligible extraction capability and every permission reported by that plan.
@@ -0,0 +1,117 @@
1
+ # Bring your world to HELIX — adopt the platform in an existing three.js project
2
+
3
+ Follow this when a game **already exists outside HELIX** — a plain three.js/vite project, a game-jam build, a
4
+ port from another platform — and you want it published as a HELIX Instant world. You do NOT rewrite it into a
5
+ scaffold shape: adoption is **staged**, each stage ships value on its own, and your scene/loop/code stay yours
6
+ throughout. (Converting a world that is already HELIX-shaped from single-player to multiplayer? Skip to the
7
+ hub's conversion section: `get_started({ kind: "multiplayer" })`.)
8
+
9
+ **The three stages:**
10
+
11
+ 1. **Become a world** — your game runs on the platform as-is (own character/controller, single-player).
12
+ 2. **Adopt the character** — swap your controller for the `humanoid-character` system (avatars, abilities).
13
+ 3. **Go multiplayer** — one facade call + the manifest opt-in, then the authority audit.
14
+
15
+ Stop at any stage; each is a publishable world.
16
+
17
+ ## Stage 1 — Become a world
18
+
19
+ A HELIX world is a **static bundle** (an `index.html` entry + assets) plus a **manifest** (`public/helix.json`).
20
+ If your project builds to a static `dist/` with vite (or any bundler), it qualifies already.
21
+
22
+ 1. **Start with `scaffold_world`** to get the CLI command and recipe for your EXISTING project directory. The CLI does not overwrite by
23
+ default, so it only adds what you're missing:
24
+ - `public/helix.json` — the manifest (title/slug/entry/permissions). Start `kind: "character"` even if you
25
+ keep your own controller; trim what doesn't apply.
26
+ - `index.html` — if you have one, keep yours and copy over the TWO platform pieces from the scaffolded
27
+ reference: the `helix:three` import-map markers (only if you adopt the character in stage 2) and the
28
+ `#loading` overlay + `src/loading.ts` (optional but recommended — count-based progress off
29
+ `THREE.DefaultLoadingManager`, which your existing loaders already feed).
30
+ 2. **`Helix.init()` first.** Add `"@hypersoniclabs/helix-sdk": "{{SDK_DEP_SPEC}}"` to dependencies and call
31
+ `await Helix.init()` before your game boots. Not embedded → `{ embedded: false }` and your game runs exactly
32
+ as before (the standalone path must keep working — the golden rule).
33
+ 3. **Check the platform rules that bite existing games:**
34
+ - **HUD placement**: the player shell overlays chrome across the top-center (~top 56px: Exit / Save /
35
+ helixOS). Move any top-center UI to a corner or the bottom.
36
+ - **Build shape**: `base: './'` in vite config (relative asset URLs); no server-side anything — the bundle
37
+ is static.
38
+ - **NO ROOT-ABSOLUTE URLs into your own bundle.** This is the one that bites ported games hardest: a
39
+ published world is served from `…/instant-worlds/<id>/<build>/`, so every `'/assets/x.glb'`,
40
+ `fetch('/data.json')` or `new Audio('/theme.mp3')` your game already has resolves against the SITE
41
+ root, 404s, and usually throws. Rewrite each one against the document —
42
+ `new URL('assets/x.glb', document.baseURI).href` — before you publish.
43
+ - **Big assets**: they bundle as-is at this stage; that's fine. (Character assets in stage 2 stream from
44
+ the platform CDN instead.)
45
+ 4. **Build + publish**: `npm run build` → `validate_world` on `dist/` (fix every problem) →
46
+ **`helix verify-subpath dist`** (REQUIRED — serves the bundle from a nested path and catches exactly the
47
+ root-absolute 404s above; local preview serves from the origin root and cannot see them) → `whoami` →
48
+ `publish_world`. Your game is now a HELIX world.
49
+
50
+ ## Stage 2 — Adopt the character (required for multiplayer presence)
51
+
52
+ Swap your hand-rolled controller for the platform character: physics-driven chassis, locomotion + animation,
53
+ first/third-person cameras, ability host, and every player's **universal avatar**.
54
+
55
+ 1. Pin it in the manifest: `"systems": { "humanoid-character": "^0.3" }`, then **`install_world_packages`**
56
+ (resolves the pin into `public/helix_modules/`, rewrites the import map, generates `src/helix.runtime.ts`).
57
+ Add the vite alias + tsconfig path from the character recipe §3–4.
58
+ 2. **Externalize Three, Rapier, and platform systems** (`build.rollupOptions.external: (id) =>
59
+ id === 'three' || id === '@dimforge/rapier3d-compat' || id.startsWith('@helix/')`, plus the import-map
60
+ markers): the platform hosts the single shared Three; two copies break `instanceof` and
61
+ silently corrupt rendering. The character system imports `@dimforge/rapier3d-compat` by bare specifier, and
62
+ `helix install` maps it to the platform-hosted physics engine instead of baking ~2 MB into your bundle.
63
+ Your code keeps importing both normally — only the resolution changes. `three` moves to devDependencies
64
+ and onto the platform's line (`^0.185.1`, what `^0.3` of the system hosts — an older devDep bundles
65
+ mismatched jsm addons against it); `@dimforge/rapier3d-compat` goes there too (types + `helix dev`), never
66
+ to dependencies. Compatible
67
+ `@helix/*` systems update safely on fresh launch; validation and publish refuse a system-bearing build
68
+ that leaves them bundled.
69
+ 3. Replace your controller with the facade in single-player mode — your scene, camera, and loop stay:
70
+
71
+ ```ts
72
+ import { CharacterMultiplayer, RapierBody } from '@helix/humanoid-character';
73
+ import { SYSTEM_ASSET_BASE, TRANSCODER_PATH } from './helix.runtime';
74
+
75
+ const body = await RapierBody.create({ position: SPAWN });
76
+ // Rebuild your walkable surfaces as static colliders on this body (box/capsule approximations are fine):
77
+ body.addStaticCuboid({ x: 24, y: 0.5, z: 24 }, { x: 0, y: -0.5, z: 0 });
78
+
79
+ const mp = await CharacterMultiplayer.create({
80
+ helix: Helix, renderer, scene, camera, body,
81
+ assetBase: SYSTEM_ASSET_BASE, transcoderPath: TRANSCODER_PATH, spawn: SPAWN,
82
+ join: false, // stage 2: character only — flip to multiplayer in stage 3
83
+ });
84
+ // your loop: mp.update(dt) each frame, before your game logic.
85
+ ```
86
+
87
+ Map your old controller's feel onto the character **config** (camera mode/pitch, movement axis, speeds —
88
+ character recipe §8a; top-down and side-on are config, not code). Your old NON-movement key handling
89
+ (attack, dash, interact, menus — any raw `addEventListener('keydown', …)`) moves onto the input router:
90
+ create ONE `InputService`, pass it as `input` to the create call, and re-home each verb with
91
+ `input.registerAction` / the standard ids (character recipe §8c) — that's what makes them gamepad-ready,
92
+ rebindable, and menu-context aware. Delete your controller code last, once the feel matches.
93
+ 4. Rebuild, `validate_world`, republish. Players now walk your world as their avatar.
94
+
95
+ ## Stage 3 — Go multiplayer
96
+
97
+ 1. Manifest: `helixVersion: "0.3"`, `maxPlayers > 1`, add the `"multiplayer"` permission and
98
+ `"multiplayer": { "authoritative": true }`.
99
+ 2. Delete `join: false` — the facade now joins when embedded, and brings replicas, nameplates, and the
100
+ reconciler with it. That's presence done.
101
+ 3. **The authority audit** — the step that makes or breaks a conversion. Your existing game code is full of
102
+ self-authority multiplayer must not keep (client-side teleports, local score state, local round resets).
103
+ Work through the hub's conversion sequence steps 3–7 (`get_started({ kind: "multiplayer" })`): every
104
+ position write becomes a server `respawn`/`teleport` rule; shared values move into declared `state` read via
105
+ the typed accessors; **verify with two browser windows**.
106
+ 4. Game logic (zones, timers, entities, scores) is declared DATA in the manifest — `read_doc({ name:
107
+ "multiplayer-logic" })` + the closest template from the hub's index.
108
+
109
+ ## The rules that matter (adoption)
110
+
111
+ - **Each stage must leave the standalone path working** — `Helix.init()` returning `embedded: false` is your
112
+ game exactly as it was.
113
+ - **Never two copies of three** after stage 2 — externalize + import map, or rendering corrupts silently.
114
+ `@dimforge/rapier3d-compat` is external and import-mapped for the same reason: one hosted instance, never bundled.
115
+ - **Your geometry/visuals/loop are never the platform's business.** The platform owns identity (login, avatars),
116
+ the character system, and the multiplayer room; everything else stays your code.
117
+ - **Publish early** — stage 1 is a real release; feedback on the platform beats a big-bang conversion.