@hypersoniclabs/helix-mcp 0.2.4 → 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,218 @@
1
+ ---
2
+ name: helix-vehicles
3
+ description: Make, convert and publish a drivable HELIX vehicle item and its add-ons from any source — a real car by name, a BeamNG or other game mod, a downloaded model, a concept image, or from scratch — so it works on the first publish. Use for any request to create, import, fix, republish or add parts to a car, kart, truck or other land vehicle.
4
+ ---
5
+
6
+ # HELIX Vehicles
7
+
8
+ A vehicle is a published **item**, not a world: a GLB that follows the part contract, plus a
9
+ `helix.vehicle-package/2` JSON with sourced physics, audio and controls, sealed into a Continuum
10
+ Package, visually verified, listed, and sold with a parts set. The rules are the **HELIX Vehicle
11
+ Contract**: `read_doc({ name: "vehicles" })`; read it once before step 3. This skill is the order
12
+ of work and the gates. Each step names its reference:
13
+ `read_skill({ name: "helix-vehicles", reference: "<name>" })`.
14
+
15
+ **The default failure** is a car that passes every structural check and is still wrong when
16
+ someone looks at it, drives it or opens its customizer. Every gate below reads an artifact, and
17
+ the last gate is your own eyes on real-runtime renders beside photos of the real car.
18
+
19
+ **Run `check_for_updates()` first.** The vehicle tools run the CLI bundled with this MCP; an old
20
+ MCP means an old QA harness.
21
+
22
+ **The gauntlet is mandatory: `read_skill({ name: "helix-gauntlet" })` now.** Steps 10, 11, 12 and 13 run
23
+ its loop: a bar of real-car photos, the fixed inspection set rendered in the real runtime (every seat
24
+ occupied, the driver steering left/centre/right, still hands), your own look at every sheet, a fresh blind
25
+ critic where you can spawn one, and no publish, version or listing without its PASS. Updating a car that
26
+ is already listed starts and ends with `check_listed_items({ itemIds: [<id>], rerun: true })`.
27
+
28
+ ## The workflow
29
+
30
+ Keep a ledger file. For each step, record the artifact that proves the step passed.
31
+
32
+ 1. **Research the real vehicle** (`physics-from-specs`). Cite dimensions, tyre sizes per axle,
33
+ mass and distribution, the torque curve, gearing, Cd and area, and the performance figures
34
+ (0–100, quarter mile, top speed, braking, lateral g; several tests each, take the median).
35
+ Collect reference photos at the fixed poses (front, side, rear, top, both 3/4 views, cockpit)
36
+ plus lamp, wheel, tail and interior details (`appearance`), and collect real recordings of the
37
+ car (idle, a steady high-rpm hold, the limiter, a cold start) as the **measuring reference**
38
+ for the audio step (`audio`). **Gate:** citations, a photo folder, a list of recordings.
39
+ 2. **Acquire the mesh.** For a **real named car, prefer a quality licensed/CC0 base mesh of that
40
+ exact car** and clean it up (`source-model`) over modelling the body from scratch — the
41
+ from-scratch body has come out crude across three rounds. In order of preference:
42
+ (1) a **licensed/CC0 base mesh** of the exact car (Sketchfab, CC0, or equivalent) — first choice
43
+ for a real named car (`source-model`); (2) a **BeamNG or other game mod** (`source-beamng`);
44
+ (3) a **downloaded or ripped model** (`source-model`); (4) **from scratch** (`source-scratch`)
45
+ **only for a fictional or no-source car** — never the default for a real named car. Images or a
46
+ concept: `source-concept`, for fictional cars. Whatever the source, **check its licence/CC0 and
47
+ set `provenance.distributable`** (already a publish gate). Never plan around an AI-generated car
48
+ body: every one has failed `render.intact`. **Gate:** the source on disk, with its inventory and
49
+ licence.
50
+ 3. **Separate into contract parts** (contract sections 1–5): few nodes, one material each; wheel
51
+ origins at the hubs, each axle's tyre at its real size; doors, lids, steering, glass and one node
52
+ per lamp function separate; +X left, +Y up, +Z forward, metres, tyres on Y = 0. Do not invent
53
+ parts the source lacks. Split every lid the real car opens (bonnet, boot, engine cover) and give
54
+ each indicator side a front AND a rear lens (`validate_vehicle` warns `PANEL_ROLE_*`, fails
55
+ `LAMP_INDICATOR_REAR_MISSING`). **Gate:** a numbers file against the real dimensions.
56
+ 4. **Build the cabin** (`cabin`): the real seats, dash, legible gauges, steering wheel, pedals and
57
+ door cards, with H-points where the real seats put the hips. **Gate:** the **interior finish
58
+ gate** (`appearance` section 4.4) at the cockpit and dash poses beside interior photos — a
59
+ legible binnacle (tach/speedo/turbo), real seat shells, a real dash and door cards, and the
60
+ controls; a flat grey slab cabin fails. Every occupant assert from `preview_vehicle` passes — posture
61
+ (`torso_angle`, `knees_up`, `head_in_wings`) and the hands over time (`hands_on_wheel_steering`,
62
+ `hands_follow_wheel`, `hands_at_nine_and_three`, `fingers_still`, `hands_still`, `seat_rig_warnings`)
63
+ — and the `seat-*`, `driver-steering` and `stillness` sheets show a person sitting properly in every
64
+ seat with the hands on the rim turning with it. Fix the seat, footwell and wheel geometry, not a
65
+ number (`cabin`). Every declared seat counts, rear rows included: run
66
+ `check_vehicle_cabin({ target: <dir> })` after every cabin edit (seconds, offline) and clear every
67
+ FAIL, then confirm with `preview_vehicle({ target: <dir> })`, which boards every declared seat
68
+ (`cabin`, "A rear row").
69
+ 5. **Materials and budgets** (contract sections 7–8, paint in `appearance`): baked PBR, KTX2 with
70
+ mips, linear `baseColorFactor`, ≤ 24 draws, ≤ 90k triangles, ≤ 48 materials, ≤ 2048 px, one
71
+ drawn GLB. **Gate:** the counted primitives, triangles and materials.
72
+ 6. **Write the host manifest** (`host-manifest`) into the final GLB's
73
+ **`asset.extras.HELIX_vehicle`**: class and every socket position. Without it the car mints as
74
+ a non-host and no add-on can ever fit it. **Gate:** re-read the final GLB: `asset.extras`
75
+ lists every socket (`validate_vehicle` refuses a GLB without it).
76
+ 7. **Write the package** from the reference (`get_vehicle_reference_package({ out })`, then
77
+ `reference-package`): replace every number with your sourced values, author
78
+ `simulation.land`, set `groundToBodyOrigin = wheelRadius + staticRideHeight`, declare every
79
+ seat, keep every specialty signal and action. **Gate:** `validate_vehicle({ vehiclePackage,
80
+ glb })` exits 0.
81
+ 8. **Simulate** (`physics-from-specs`): `simulate_vehicle({ target: <dir> })` until every target
82
+ is met within its tolerance. The platform car runs **ungoverned**: a market or gentlemen's-agreement
83
+ speed governor is not physics, so `targets.topSpeedKph` is the ungoverned figure and the torque
84
+ curve sits on the maker's declared crank figures, with losses in `drivetrainEfficiency` and drag
85
+ (`physics-from-specs`, "Top speed" and "Torque"). An AWD car follows "All-wheel drive" there. A turbo car's lag is authored into the curve. It also runs the
86
+ **keyboard launch** (full-throttle W, A/D taps, hands-off yaw probe): a FAIL means the car spins
87
+ under a keyboard player; tune it only with sourced levers, or author `simulation.land.tractionControl`
88
+ (the real system, or a declared playability aid) (`physics-from-specs`, "Drivable on a keyboard"
89
+ and "Traction control"). **Gate:** the figures beside the targets, and the keyboard-launch verdict.
90
+ 9. **Audio** (`audio`): **never generate or synthesise vehicle sound; always source real
91
+ recordings, from BeamNG mods as much as possible**, whatever the mesh source. Find the most
92
+ accurate simulator mod of that exact car (BeamNG first; Assetto Corsa/ACC, rFactor 2 or
93
+ Automobilista 2 where theirs is more accurate), **pick it by measuring** candidates against
94
+ real recordings of the car (firing-fundamental pitch track vs rpm, spectral centroid,
95
+ roughness, idle character, limiter cadence), and extract it with
96
+ `import_vehicle_audio` (`helix vehicle audio-import`). Fill a slot the best mod lacks from, in
97
+ order, another BeamNG mod of the same engine or family (then the BeamNG base game's stock
98
+ banks), another sim's mod, a real recording of the car; never another car's sound. **Every one of
99
+ the 31 runtime slots carries a sourced recording**, with its provenance; the only empty slots are
100
+ a component the real car physically lacks (`absent: "not-fitted"` plus a reason: turbo on an NA
101
+ car, reverse beep, clutch on an automatic, combustion slots on an EV) and `exhaustLadder`
102
+ `absent: "combined-with-engine"` when the mod records one combined set. Doors are required. Never rely on the platform's default sounds: the gate fails
103
+ a car that would fall back to one (`AUDIO_SLOT_UNSOURCED`). **Gate:** a slot table with each slot's source, the measurements that chose the mod,
104
+ the **rung-pitch self-check** on every rung (`audio`, "The rung-pitch self-check"), and
105
+ `check_vehicle_audio({ target: <dir> })` exits 0: every slot sourced, no
106
+ `AUDIO_SYNTHESISED`, no `AUDIO_SLOT_UNSOURCED`, no content warnings left unexplained (`audio`, "Content gates").
107
+ 10. **Preview and accept the look — the gauntlet** (`helix-gauntlet`, `appearance`, `qa`, `cabin`):
108
+ `preview_vehicle({ target: <dir> })` (occupants and the inspection set are on by default) and read
109
+ EVERY sheet under `<out>/inspection/` beside the bar photos, then the blind critic. Then run the reference-overlay gate: compare each fixed pose with
110
+ the reference photo at the same pose, blend the silhouettes and read the IoU and landmarks
111
+ against the thresholds, and check the lamp treatment, panel relief, wheels and tail. Pass
112
+ `referencePaint` (the median body colour of the reference photos) so the **daylight paint
113
+ gate** judges hue, saturation, lightness and glare as Try Now draws the car, and
114
+ `referenceWheel` (a face-on close-up of the real wheel) so the **wheel-design gate** judges
115
+ spoke count, finish and centre cap, and confirm
116
+ `vehicle.lamps.each_function_lights` passes (every lamp key lights its lens). Then run
117
+ the **concrete material gate** (`appearance` section 4): sample the render's body sRGB beside
118
+ the reference photo, confirm no specular highlight blows to white, confirm the lamp pods read
119
+ closed in daylight (body-colour flaps, bezel lenses, or a clear cover over chrome reflectors and projector bowls; never black slats or glowing panels), and clear the
120
+ **interior finish gate** (`appearance` section 4.4). Fix the cabin until nothing moves. The
121
+ first mint locks the stance and the slot board. **Gate:** a pass on every overlay and on the
122
+ material gate, the pose sheet beside the photos, the clearance and cabin numbers, and a gauntlet
123
+ PASS in the ledger (every sheet looked at and PASS, the critic's `MATCHES` on every sheet).
124
+ 11. **Publish** (`publish`): `publish_vehicle({ dir, dryRun: true })`, then for real;
125
+ `check_vehicle_host({ itemId })`; pass `visual_qa_item_hosted`;
126
+ `create_item_distribution` — **list the base car and price it** (section 5 of `publish`).
127
+ **Activate a later version only by re-running `publish_vehicle({ dir, itemId, version, reason })`
128
+ after visual QA passes; never `helix item set-version` on a car** (it moves the pin, not
129
+ `properties.vehiclePackage`, so the car keeps serving its old package).
130
+ A car left unlisted is not obtainable; the owner cannot buy a `NOT FOR SALE` car, so it is
131
+ not a shipped product. To change anything later, stage a new version of the same item.
132
+ If a publish tool answers NOT AVAILABLE, the mint is blocked: finish every gate and report it.
133
+ **Gate:** a PASS record for the exact pinned version, and a live marketplace listing with a
134
+ price.
135
+ 12. **Add-ons: required** (`addons`). Ship at least a wheel set, an exhaust, an ECU, a gearbox,
136
+ suspension, brakes and anti-roll bars, plus a spoiler where the real car has one. The
137
+ performance parts are tune-only: no model, every op a `tune` (an engine part also gets a bay mesh
138
+ when the engine is visible from outside; `addons`). Each one: `check_vehicle_addon`
139
+ (it must fit and measurably change the drive), `preview_vehicle({ target, addon: [<dir>] })`
140
+ for any part with a model, `publish_vehicle_addon`, its own visual QA, then **list and price
141
+ it** (`create_item_distribution`). Every add-on must be listed for sale, not left dry
142
+ (`--dry-run` only, pricing "to the owner") — round 7 shipped all nine add-ons `NOT LISTED`
143
+ and nothing was buyable.
144
+ **Gate:** `check_vehicle_host({ itemId })` shows every required part fitting **and for sale**
145
+ (a `distributionId` and a price) and passing the web customizer's own guard (it fails, naming
146
+ the part, when the customizer would refuse to equip one — round 8's four tune-only parts), and
147
+ the customizer shows them buyable.
148
+ 13. **Owner QA on the live item** (`qa`, `helix-gauntlet`): `preview_vehicle({ item: <id> })` on the
149
+ published car and `check_listed_items({ itemIds: [<id>], rerun: true })` (CURRENT and PASS); then buy it
150
+ with a QA account, spawn it in a hosted world with kerbs, board every seat, drive it against the
151
+ targets beside a control car, work the doors and lights at night, fit each add-on, listen.
152
+ **Gate:** live evidence per check, and the gauntlet's inspection of the LIVE car passing. Do not
153
+ report done before this.
154
+
155
+ ## Hard rules (each one prevents a failure that already shipped)
156
+
157
+ - **Accuracy wins over a gate artefact.** Never move the car off the real one's figures to satisfy
158
+ a check; prove the artefact and report it (`qa`).
159
+ - **Only a submitted or hosted visual-QA run is a gate result.** A local pass runs no critic.
160
+ - **`asset.extras.HELIX_vehicle`, nowhere else.** Scene and root `extras` are ignored at mint.
161
+ - **Never mint a second copy, and never re-run a mint to change a model.** Stage a version of the
162
+ same item. Locked fields stay byte-identical, `note` included.
163
+ - **Wheel nodes have their origin at the hub**, or wheel add-ons render at the car's centre.
164
+ - **Look at add-ons on the host from several angles, including inboard and x-ray.**
165
+ - **Check the car parked and unboarded, not only boarded.**
166
+ - **Every seat occupied, every time.** A posture, hands or fingers defect blocks the listing; it is never
167
+ "minor". The F40 went on sale with its driver lying back, hands off the wheel and fingers twitching
168
+ because an owner-proxy called "reclined, knees up" non-blocking and nobody looked at the hands.
169
+ - **A listed car is re-checked when you touch it** (`check_listed_items`): its record can predate today's gates.
170
+ - **Calibrate physics to the real car, not to a source mod.** (Audio is the opposite: the sound
171
+ comes from the most accurate sim mod, chosen by measurement.)
172
+ - **Never synthesise or generate vehicle audio, and never rely on the platform's default sounds**
173
+ (the synthetic fallback engine, the generated default sound pack). A car ships its own sourced
174
+ sound for every slot; `check_vehicle_audio` and `publish_vehicle` fail an empty or
175
+ default-falling-back slot (`AUDIO_SLOT_UNSOURCED`) and a car without sourced engine audio.
176
+ - **Vehicle add-ons are not auto-sealed by `publish_item`**: use `publish_vehicle_addon`.
177
+ - **If you re-frame the host, republish every add-on authored in the host's frame.**
178
+ - **Give the car GLB an empty `vehicle_root` over every top-level node, with the lids under it.**
179
+ Host-frame add-ons bind to it; without it the car is never presented (`validate_vehicle`
180
+ HOST_BIND_*; `host-manifest`).
181
+
182
+ ## Tools
183
+
184
+ - **Discovery:** `search_items` (`kind: "vehicle"`, `hostKind: "vehicle"`), `search_vault`
185
+ (`packageType: "vehicle" | "addon"`).
186
+ - **BeamNG import:** `bridge_detect`, `bridge_plan`, `bridge_import`.
187
+ - **Build and check (these run the CLI and return its output and exit code):**
188
+ `get_vehicle_reference_package`, `validate_vehicle`, `simulate_vehicle`, `check_vehicle_cabin`, `import_vehicle_audio`,
189
+ `check_vehicle_audio`,
190
+ `preview_vehicle`, `check_vehicle_addon`.
191
+ - **Publish:** `publish_vehicle`, `publish_vehicle_addon`,
192
+ `check_vehicle_host`, `visual_qa_item`, `visual_qa_item_hosted`, `visual_qa_item_status`,
193
+ `create_item_distribution`, `delist_item`, `check_listed_items` (re-judge listed cars against today's gates). Never hand-write a vehicle or add-on descriptor.
194
+ - **Audio:** `import_vehicle_audio` (extract a sim mod's audio with provenance), then
195
+ `check_vehicle_audio`. **Never `generate_audio` for a vehicle**, for the engine or for any
196
+ one-shot: vehicle sound is sourced, not generated.
197
+
198
+ Mesh work happens in Blender (headless) and `@gltf-transform/cli`; install them if missing
199
+ (`toktx` from KTX-Software for KTX2).
200
+
201
+ ## References
202
+
203
+ | Reference | Covers |
204
+ | --- | --- |
205
+ | `physics-from-specs` | Research, every physics field from published specs, staggered tyres, turbo lag, targets and `simulate_vehicle`. |
206
+ | `source-beamng` | BeamNG mods: the importer, the field mapping, atlas and lamp splitting, known traps. |
207
+ | `source-model` | **First choice for a real named car:** download a quality CC0/licensed base mesh of that exact car, then cleanup, splitting, pivots, scale. Also downloaded/ripped models. |
208
+ | `source-concept` | Images, concepts and fictional cars. |
209
+ | `source-scratch` | Building a car from primitives in Blender to the contract — for fictional or no-source cars, **not** the default for a real named car. |
210
+ | `host-manifest` | `asset.extras.HELIX_vehicle`: the shape and tested export snippets. |
211
+ | `cabin` | Interior parts, how the runtime seats occupants, `cabinFit`, the occupant thresholds, and the interior finish gate. |
212
+ | `appearance` | The reference-overlay gate (fixed poses, IoU + landmark pass/fail), lamp treatment and panel relief, and the concrete paint/bloom/lamp-pod gate and the **interior finish gate** (sections 4.1–4.5). |
213
+ | `audio` | The hard rule (never synthesise; always source real recordings), finding and measuring the most accurate sim mod, `import_vehicle_audio`, the slot map and per-slot fallback order, provenance, the **rung-pitch self-check**, content gates, the URL and package closure. |
214
+ | `audio-import` | The details of `import_vehicle_audio`: BeamNG's own base-game defaults per slot, the `--map` slot-map file (several sources, trims, concats, recordings, not-fitted), Assetto Corsa specifics. |
215
+ | `addons` | The required parts set, kinds of part, frames, ops from source data. |
216
+ | `publish` | The order of calls, the slot board, Try Now and versions, visual QA, selling. |
217
+ | `qa` | The render and drive checks, the full visual-QA assert list, gate artefacts. |
218
+ | `reference-package` | What to keep or change in the reference package, and the componentBuild digest. |
@@ -0,0 +1,212 @@
1
+ # Vehicle add-ons
2
+
3
+ The contract is in section 11 of `read_doc({ name: "vehicles" })`. This reference is the method.
4
+ **Plan the add-on set before you finalise the host mesh.** Every node an add-on hides must exist
5
+ on the host as its own primitive, and every add-on authored in the host's frame breaks if the host
6
+ moves later.
7
+
8
+ ## Required: the minimum parts set
9
+
10
+ **A car is not done until its customizer sells a parts set.** Buyers expect to change wheels and
11
+ tune the car; round 1's F40 shipped with "0 compatible" parts and failed the owner's check for it.
12
+ Every car ships at least:
13
+
14
+ | Part | Slot type | Kind |
15
+ | --- | --- | --- |
16
+ | A wheel set (rims and tyres), more than one where the real car had factory or period options | `vehicle.wheel@1` | geometry, socket frame |
17
+ | Exhaust | `vehicle.exhaust@1` | geometry, host frame, hides `exhaust`, **and a tune op that changes the drive** (or a sound `mount`) — see the rule below |
18
+ | ECU | `vehicle.ecu@1` | tune only |
19
+ | Gearbox | `vehicle.transmission@1` | tune only |
20
+ | Suspension (coilovers) | `vehicle.suspension@1` | tune only |
21
+ | Brakes | `vehicle.brakes@1` | tune only |
22
+ | Anti-roll bars | `vehicle.arb@1` | tune only |
23
+ | Engine / powertrain, where the real car has a swappable engine or a well-known upgrade path (turbo kit, cam, bigger displacement) | `vehicle.engine@1` | geometry (visible engine bay) and/or tune only |
24
+ | Spoiler or wing, where the real car has one or a well-known aftermarket one exists | `vehicle.spoiler@1` | geometry, host frame, bound to `boot` |
25
+
26
+ **Cover the reference car's obvious upgrades.** The two upgrades a buyer of that car expects must be
27
+ present, or the set is incomplete:
28
+
29
+ - **Engine/powertrain** — where the real car has a swappable engine or a well-known tuning path (the
30
+ F40's is the twin-turbo V8), ship a `vehicle.engine@1` part (a visible engine-bay mesh and/or a
31
+ torque/limiter tune), sourced like every other part.
32
+ - **Spoiler/wing** — where the real car has one, ship a `vehicle.spoiler@1` part even when the wing is
33
+ part of the body mesh. Author a wing-replacement add-on that **hides the body wing** (a `hide` op on
34
+ the host's `wing_*` node, granted via the host's `hostGrantExtensions`) and adds its own wing mesh plus
35
+ aero ops. "The wing is body-mesh" is not a reason to skip the slot.
36
+
37
+ **Engine add-ons: tune-only by default; a mesh only where the buyer can see the engine.** An engine
38
+ upgrade is a performance part, so its tune ops are what the buyer pays for, and a tune-only engine is
39
+ a complete product (the live 993's Guntherwerks 4.0 is tune-only). Owners do notice an "engine
40
+ upgrade" that changes nothing they can see, so give it a mesh when, and only when, the stock engine is
41
+ visible from outside: through glass or a louvred cover (mid-engined cars such as the F40, Carrera GT or
42
+ NSX), in an open bay, or under a lid the car articulates (`mesh.articulation.panels[]` role `bonnet` or
43
+ `boot`). In that case:
44
+
45
+ - model only the parts that visibly change (turbos, intercoolers, plenum, cam covers, strut brace),
46
+ in the host frame, with `bind { socket: <the lid or "engine">, … }` when it sits under a moving lid;
47
+ - `hide` the stock node it replaces (`engine` or the named bay part) so the two never overlap;
48
+ - prove it with `preview_vehicle({ target, addon: [<dir>] })` from the pose that shows the bay
49
+ (top-down through the glass, or with the lid open). This path is in the contract but has no live
50
+ example yet, so do not ship it on the strength of the contract alone.
51
+
52
+ When the engine is hidden (a front-engined car with a closed bonnet that does not open), ship it
53
+ tune-only and say so in the description ("performance part: no visible change"), rather than adding a
54
+ mesh nobody can see that costs draws.
55
+
56
+ Add more where the car has them (a body kit, an exhaust note). The live 993 sells
57
+ 13 parts across these slots. Each part's ops come from real data (below).
58
+
59
+ **An exhaust is a performance part.** A `vehicle.exhaust@1` add-on must change the drive — at least one
60
+ `tune` op that measurably moves a figure (power or weight: `drivetrain.engine.torqueCurve` /
61
+ `limiterRpm`, or `chassis.mass`) — or be sold as a sound part with a `mount { type: "sound" }`. A hide +
62
+ mesh with no tune op and no sound is refused by `check_vehicle_addon` (`ADDON_PERFORMANCE_NO_EFFECT`):
63
+ round 5 shipped a "competition triple-outlet exhaust" that changed nothing about how the car drove.
64
+
65
+ **Gate:** `check_vehicle_host({ itemId: <the car> })` lists every required slot type with at
66
+ least one fitting part **for sale**, and the customizer on the car's item page shows that many
67
+ compatible parts. Open it, fit each part, and look. It also runs the web customizer's own guard on
68
+ every fitting add-on's exact Package pin and **fails, naming the part and the customizer's message,
69
+ when the customizer would refuse to equip or save one**. "Fits" and "for sale" alone prove only
70
+ what the backend rail lists: round 8 sold four tune-only parts that the rail listed and every
71
+ older check passed while the customizer showed "could not be previewed" and disabled Save.
72
+
73
+ **A part with no `model.glb` is meshless, and the customizer equips a meshless part only when its
74
+ sealed definition is verified.** The root must be `helix.vehicle-addon-definition/1` (or the legacy
75
+ `helix.vehicle-addon/1`) with `mesh: null` and an ops array, and no op may be a `mount` of anything
76
+ but a sound. `check_vehicle_addon` runs that guard on the exact definition publish would seal
77
+ (`ADDON_CUSTOMIZER_REFUSES`; an `addon.json` `definition` override that restores a `mesh` or
78
+ changes the schema is the usual cause), and `helix vehicle addon check --item <id>` / `--host <id>`
79
+ runs it on what is already published.
80
+
81
+ The car must be an add-on host first: its GLB carries `asset.extras.HELIX_vehicle` (the
82
+ `host-manifest` reference) and its board has sockets, nodes and tunable paths.
83
+
84
+ ## Kinds of part
85
+
86
+ | Kind | Geometry | Frame | Ops | Example (live, Porsche 993) |
87
+ | --- | --- | --- | --- | --- |
88
+ | Wheel set | Rim, tyre and disc as **one mesh per wheel**, a single mesh serving all four corners, at about 9–11k triangles | `socket`, outboard `+x` | `hide rim_*` (the host's wheel nodes) | HRE 501, Guntherwerks 5-spoke, deep-dish |
89
+ | Wing / spoiler | Wing mesh in the host's coordinates | `host` with `bind { socket: "boot", root: "vehicle_root", matrix: identity }`, so it follows the lid | aero `add` downforce, `mul` drag, `add` mass | RWB wing |
90
+ | Lip, kit, bumper | Panel mesh in the host's coordinates | `host` (with `bind` if mounted on a moving panel) | aero, mass, and `hide` of the stock panel | RWB lip |
91
+ | Exhaust | Exhaust mesh in the host's coordinates | `host` | `hide exhaust` **(a replacing part must hide what it replaces)**, mass | RWB exhaust |
92
+ | Engine | Visible engine-bay mesh, or none | `host` | torque curve `add`, limiter `add`, coast `mul`, mass | Guntherwerks 4.0 |
93
+ | ECU / gearbox / coilovers / sway bars / brakes | **None** (0 draws, glyph tile) | — | tune only | Stage 3 ECU, short-ratio gearbox, race coilovers |
94
+ | Exhaust note / blow-off | None. A sound `mount` row with WAV assets of its own package | — | `mount { type: "sound", … }` | — |
95
+
96
+ ## Frames (the anchor decides where the part appears)
97
+
98
+ Declare the frame in the add-on GLB's `scene.extras.helixMeshFrame`:
99
+
100
+ - **`{ "contract": "helix.mesh-frame/1", "authoringFrame": "socket", "outboardAxis": "+x" }`**
101
+ - The mesh is metric and centred on the hub.
102
+ - Model the **left** wheel; the axle runs along X, and +X is the outboard face.
103
+ - The runtime turns it to face outward on the right side.
104
+ - The mesh anchors to the socket node's origin, which is why the host's wheel origins must be
105
+ at the hubs.
106
+ - Match the host tyre's radius: scale the add-on radially to the host's `wheelRadius`, as the
107
+ 993 set did with a factor of 1.0274.
108
+ - **`{ "authoringFrame": "host", "bind": { "socket", "root", "matrix" } }`**
109
+ - The vertices sit in the host's final model frame.
110
+ - `bind` makes the part follow a moving socket. Without it, the part sits static at the root.
111
+ - **If the host is ever re-framed (raised, re-centred or re-scaled), shift every host-frame
112
+ add-on and republish it.**
113
+ - Seat a body part on the host by measurement, not by eye. The mod's wings floated 0.09–0.29 m
114
+ above the 993's stock lid and each had to be lowered by its measured minimum gap.
115
+
116
+ ## Deriving the ops
117
+
118
+ - Ops address the v1 tuning tree: `drivetrain.engine.*`, `drivetrain.gearbox.*`,
119
+ `drivetrain.brakes.*`, `contacts.*.suspension.*`, `land.antiRollBar.*`, `chassis.aero.*` and
120
+ `chassis.mass`. On a car with `simulation.land` the engine and gearbox deltas are projected into
121
+ the v2 solver automatically. What stays inert is listed in section 11 of the contract.
122
+ - Use **ratios from source data**: the mod's jbeam option against its stock value, or a
123
+ manufacturer's tuning-kit figure against stock. Record each derivation in the package's
124
+ `addon-definition` root.
125
+ - **Limits:**
126
+ - `mul` and `add` only. `set` on a number is first-party only.
127
+ - An array op takes a scalar, so a per-gear ratio change becomes its mean.
128
+ - `contacts.*` is one wildcard over all four corners. When the front and rear ratios differ,
129
+ take the **smaller**.
130
+ - A `mul` on 0 does nothing; use `add`.
131
+ - A `mul` on a block the host lacks is refused at fit. A turbo kit on an NA car cannot ship,
132
+ because there is no `provides` verb.
133
+ - **Test the part live against stock.** A shorter gearbox made the 993 *slower* to 100 km/h
134
+ (5.49 → 5.60 s), because it added two 0.25 s shifts. Tuning `shiftTime ×0.6` and
135
+ `finalDrive ×0.92` fixed it, and both factors were chosen by sweeping live. A part whose only
136
+ effect is drag-limited, such as an ECU on a car that never reaches its limiter, shows nothing on
137
+ a 0–100 run. Say so in its description.
138
+ - **No add-on controls a speed limiter.** The platform car runs ungoverned and the live solver never applies
139
+ `physics.engine.speedLimiterKph`; the ECU slot's tune paths (`torqueCurve`, `limiterRpm`, …) have no limiter path and the
140
+ backend refuses one. An ECU's honest effects are torque and rev limit: say that, not "removes the speed limiter".
141
+ `check_vehicle_addon` measures the drive effect **ungoverned** (0–100, top speed, quarter mile), so an engine, gearbox or ECU
142
+ part shows its top-speed change even on a package that still carries a limiter.
143
+
144
+ ## Geometry hygiene (each item made a published add-on grey, black or invisible)
145
+
146
+ - **KTX2 with mips** on every texture. New add-ons without them are refused at upload.
147
+ - **Every textured primitive has `TEXCOORD_0`.** Recover lost UVs from the source; never
148
+ synthesise an unwrap onto a borrowed atlas.
149
+ - **Paint parts:**
150
+ - Give them `extras.helixVehicleRole: "paint"` on the material, and the host must have a paint
151
+ role too.
152
+ - Inheritance copies only the host's colour, roughness and metalness, never its texture. So
153
+ carry a solid base colour, not a neutral paint mask.
154
+ - **Base-game textures** that live outside a mod must be supplied. For BeamNG, pass the
155
+ `beamngCommon` input. Otherwise the part ships flat grey.
156
+ - **No metallic 1 on tyres.** Use 0 with roughness about 0.6; rims at metallic ≤ 0.6 with lifted
157
+ albedo.
158
+ - **Rim faces must be visible from inboard.** One wheel set whose far side culled the rim faces
159
+ showed only a tyre ring and failed visual QA.
160
+ - Strip BeamNG paint-slot tags from material names before lookup (`name [SECONDARY]`). Otherwise
161
+ slot 2 paints the wheel faces white.
162
+
163
+ ## The item
164
+
165
+ Each add-on is a directory: `addon.json` and `model.glb`. A tune-only part has **no**
166
+ `model.glb` and every op is a `tune`: ECU, gearbox, driveline, brakes, sway bars, coilovers or
167
+ steering, and optionally engine, intake, induction, exhaust or tyre compound.
168
+
169
+ ```json
170
+ { "title": "<part name>", "description": "<what it is>",
171
+ "fits": { "slotType": "vehicle.wheel@1",
172
+ "requires": { "sockets": ["wheel_lf","wheel_rf","wheel_lr","wheel_rr"], "nodes": ["rim_*"], "paths": [], "pointers": [], "skeleton": null },
173
+ "only": { "items": ["<host item id>"] } },
174
+ "ops": [{ "v": "hide", "nodes": ["rim_*"] }],
175
+ "provenance": { "kind": "authored|imported|generated", "source": "<source>" } }
176
+ ```
177
+
178
+ Example `fits` for a wing (`ops` is the aero and mass tunes):
179
+
180
+ ```json
181
+ { "slotType": "vehicle.spoiler@1",
182
+ "requires": { "sockets": ["boot"], "nodes": [], "paths": ["chassis.aero.downforceCoefficient","chassis.aero.dragCoefficient","chassis.mass"], "pointers": [], "skeleton": null },
183
+ "only": { "items": ["<host item id>"] } }
184
+ ```
185
+
186
+ At most 32 ops (64 for a clip-set slot). Then:
187
+
188
+ 1. `check_vehicle_addon({ dir })` until it exits 0. It proves the anchoring (a socket-frame part
189
+ centred on its mount; the host's wheel origins at its hubs), that every hide target exists and
190
+ is granted on the host's board, that the host exposes every required tunable path, and the
191
+ backend's own count of hosts the part fits, and (for a part with no model) that the web
192
+ customizer's guard accepts the exact definition publish would seal. For `tune` ops it measures the drive effect on each
193
+ host with the live runtime's physics. A part that changes nothing it governs fails, and an op
194
+ that cancels another is named (contract section 11). Size the ops so the figure moves the way
195
+ the real part moves it. Then `preview_vehicle({ target: <car dir>, addon: [<dir>] })`, or
196
+ `{ item: <published car id>, addon: [<dir>] }`, fits the unpublished part in the real runtime
197
+ and checks it lands on its anchor (each wheel on its hub), not at the car centre. Look at the
198
+ render.
199
+ 2. `publish_vehicle_addon({ dir })`. It runs the full check, uploads the part's model (none for a
200
+ tune-only part), seals its `addon` Package, makes the item from that Version (pinned, unlisted)
201
+ and reads back that each host lists it. A hide the host has not granted stops it: the host car
202
+ seals its grants (`hostGrantExtensions` in its `publish.json`, the `publish` reference).
203
+ 3. Visual QA on each host, then distribute.
204
+
205
+ ## Proof (per add-on, per host)
206
+
207
+ 1. In the customizer, stage the part alone. Capture the front 3/4, rear 3/4, side, a close-up, and
208
+ each corner for wheels.
209
+ 2. **Wheel copies must sit within ±1 cm of the four stock hub centres.** Measure it; a "seated"
210
+ flag is not proof.
211
+ 3. Drive with it fitted. Wheel add-ons must spin and steer with the hub.
212
+ 4. For performance parts, run a live A/B against stock, a median of 3, in the same world.