@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.
- package/README.md +81 -11
- package/dist/continuumCanary.d.ts +17 -0
- package/dist/continuumCanary.js +17 -0
- package/dist/continuumCanary.js.map +1 -0
- package/dist/server.d.ts +14 -1
- package/dist/server.js +4058 -162
- package/dist/server.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/vehicleTools.d.ts +86 -0
- package/dist/vehicleTools.js +229 -0
- package/dist/vehicleTools.js.map +1 -0
- package/docs/avatar-face.md +115 -0
- package/docs/bridge.md +98 -0
- package/docs/bring-your-world.md +117 -0
- package/docs/catalog.md +69 -1
- package/docs/character-animation.md +442 -0
- package/docs/character-attachments.md +166 -0
- package/docs/character-world.md +785 -130
- package/docs/continuum.md +153 -0
- package/docs/items.md +73 -0
- package/docs/lighting-world.md +667 -0
- package/docs/locomotion-clip-spec.md +294 -0
- package/docs/manifest.md +31 -6
- package/docs/multiplayer-logic.md +460 -17
- package/docs/multiplayer-templates/chrono-orchard.md +36 -22
- package/docs/multiplayer-templates/collect-a-thon.md +28 -38
- package/docs/multiplayer-templates/collections.md +24 -24
- package/docs/multiplayer-templates/hangout.md +124 -111
- package/docs/multiplayer-templates/npc-wave.md +310 -0
- package/docs/multiplayer-templates/obby.md +13 -16
- package/docs/multiplayer-templates/persistent-progress.md +218 -0
- package/docs/multiplayer-templates/physics-bumper.md +27 -9
- package/docs/multiplayer-templates/physics-football.md +22 -8
- package/docs/multiplayer-templates/relic-bearers.md +12 -15
- package/docs/multiplayer-templates/server-motion.md +16 -19
- package/docs/multiplayer-templates/shooter-range.md +275 -0
- package/docs/multiplayer-templates/team-control.md +28 -15
- package/docs/multiplayer-templates/turn-arena.md +31 -22
- package/docs/multiplayer-templates/voice-radio.md +166 -0
- package/docs/multiplayer-templates/wave-survival.md +7 -8
- package/docs/multiplayer-templates/world-shop.md +240 -0
- package/docs/multiplayer-world.md +222 -129
- package/docs/npc-world.md +623 -0
- package/docs/publishing.md +108 -28
- package/docs/purchases.md +223 -0
- package/docs/scene-performance.md +64 -0
- package/docs/screenshots.md +140 -0
- package/docs/sdk.md +324 -5
- package/docs/shooter-worlds.md +537 -0
- package/docs/terrain.md +173 -0
- package/docs/upgrades.md +324 -0
- package/docs/vehicles.md +727 -0
- package/docs/world-inspect.md +156 -0
- package/docs/world-look.md +241 -0
- package/docs/world-recipe.md +65 -6
- package/package.json +15 -4
- package/skills/README.md +91 -0
- package/skills/helix-assets/SKILL.md +491 -0
- package/skills/helix-assets/references/asset-sources.md +143 -0
- package/skills/helix-assets/references/vault-api.md +105 -0
- package/skills/helix-avatar-qa/SKILL.md +85 -0
- package/skills/helix-avatars/SKILL.md +206 -0
- package/skills/helix-avatars/references/contract.md +166 -0
- package/skills/helix-avatars/references/dynamics.md +367 -0
- package/skills/helix-avatars/references/face.md +50 -0
- package/skills/helix-avatars/references/publish.md +76 -0
- package/skills/helix-avatars/references/qa.md +251 -0
- package/skills/helix-avatars/references/rigging.md +88 -0
- package/skills/helix-avatars/references/source-generated.md +190 -0
- package/skills/helix-avatars/references/source-model.md +90 -0
- package/skills/helix-avatars/references/source-rigid.md +90 -0
- package/skills/helix-avatars/references/source-vrm.md +61 -0
- package/skills/helix-gauntlet/SKILL.md +128 -0
- package/skills/helix-multiplayer/SKILL.md +150 -0
- package/skills/helix-multiplayer/references/dsl-capability-map.md +107 -0
- package/skills/helix-vehicles/SKILL.md +218 -0
- package/skills/helix-vehicles/references/addons.md +212 -0
- package/skills/helix-vehicles/references/appearance.md +339 -0
- package/skills/helix-vehicles/references/audio-import.md +138 -0
- package/skills/helix-vehicles/references/audio.md +580 -0
- package/skills/helix-vehicles/references/cabin.md +225 -0
- package/skills/helix-vehicles/references/host-manifest.md +174 -0
- package/skills/helix-vehicles/references/physics-from-specs.md +463 -0
- package/skills/helix-vehicles/references/publish.md +214 -0
- package/skills/helix-vehicles/references/qa.md +177 -0
- package/skills/helix-vehicles/references/reference-package.json +3481 -0
- package/skills/helix-vehicles/references/reference-package.md +69 -0
- package/skills/helix-vehicles/references/source-beamng.md +167 -0
- package/skills/helix-vehicles/references/source-concept.md +38 -0
- package/skills/helix-vehicles/references/source-model.md +100 -0
- package/skills/helix-vehicles/references/source-scratch.md +60 -0
- package/skills/helix-world-build/SKILL.md +376 -0
- package/skills/helix-world-build/references/config-gates.md +104 -0
- package/skills/helix-world-director/SKILL.md +210 -0
- package/skills/helix-world-qa/SKILL.md +371 -0
- package/skills/helix-world-qa/references/perf-budgets.md +240 -0
- package/skills/helix-world-qa/references/perf-handle.md +125 -0
- package/skills/helix-world-qa/references/visual-scorecard.md +85 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Publishing a vehicle and its add-ons
|
|
2
|
+
|
|
3
|
+
Publication is **Package-first**: each tool seals a public Continuum Package, then makes the item
|
|
4
|
+
from that exact sealed Version, and reads everything back. Every step that writes goes to the live
|
|
5
|
+
platform. Packages and versions cannot be deleted, and a mistake costs a version number, so finish
|
|
6
|
+
every offline gate first.
|
|
7
|
+
|
|
8
|
+
## 0. Preflight (offline)
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
validate_vehicle({ vehiclePackage: "/abs/car/vehicle-package.json", glb: "/abs/car/model.glb" }) # exit 0
|
|
12
|
+
simulate_vehicle({ target: "/abs/car" }) # every target met within tolerance
|
|
13
|
+
import_vehicle_audio({ source: "<sim mod>", out: "/abs/car", sim: "beamng" }) # the car's sound, from the most accurate sim mod, with provenance
|
|
14
|
+
check_vehicle_audio({ target: "/abs/car" }) # every sample resolves, is PCM WAV, is audible, is SOURCED (provenance printed)
|
|
15
|
+
preview_vehicle({ target: "/abs/car" }) # the real runtime: stance, cabin, poses, every seat occupied, the inspection sheets (helix-gauntlet)
|
|
16
|
+
check_vehicle_addon({ dir: "/abs/addons/<part>" }) # per add-on (against a published host)
|
|
17
|
+
publish_vehicle({ dir: "/abs/car", dryRun: true }) # all of the car's gates, in the publish order
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**A dry run also runs the seal's own refusals**, nothing uploaded: `provenance.source` over 256
|
|
21
|
+
characters, a missing or invalid `contentRating` (a new item needs one of `everyone`, `teen`,
|
|
22
|
+
`mature`, `adult`), title/description/tag limits, and the Package Version shape and closure the
|
|
23
|
+
real publish builds. It names each one (`SEAL_*`), so a dry run that passes will not be refused at
|
|
24
|
+
the seal. Keep `provenance.source` to a short citation; put the long credit in `description`.
|
|
25
|
+
|
|
26
|
+
The car's directory holds:
|
|
27
|
+
|
|
28
|
+
- `vehicle-package.json`;
|
|
29
|
+
- `model.glb` (or name it with `glb`), carrying **`asset.extras.HELIX_vehicle`** with every socket
|
|
30
|
+
(the `host-manifest` reference);
|
|
31
|
+
- the WAVs beside it or in `audio/`;
|
|
32
|
+
- `publish.json`:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{ "title": "…", "description": "…", "tags": ["…"], "version": "1.0.0", "contentRating": "everyone",
|
|
36
|
+
"provenance": { "kind": "authored", "source": "<where the mesh and data came from>" },
|
|
37
|
+
"hostGrantExtensions": { "whl.wheel": { "hide": ["rim_*"] }, "pwr.exhaust": { "hide": ["exhaust"] } } }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`provenance.kind` is `authored`, `imported` or `generated`. **`hostGrantExtensions`** lets your
|
|
41
|
+
add-ons hide your real nodes: the standard board lets them hide only `*_stock_*` globs, so a
|
|
42
|
+
wheel set hiding `rim_*` or an exhaust hiding `exhaust` fits no host until the car grants it.
|
|
43
|
+
Each pattern must match a node of your GLB. List every hide your planned add-ons need.
|
|
44
|
+
|
|
45
|
+
**If `publish_vehicle` or `publish_vehicle_addon` answers NOT AVAILABLE**, the CLI bundled with
|
|
46
|
+
this MCP has no vehicle mint: nothing was done. Run `check_for_updates`; if no newer MCP has it,
|
|
47
|
+
finish every gate, keep the directory ready, and **report the mint as blocked**. Never improvise it
|
|
48
|
+
(raw API calls, an older CLI, `publish_item`).
|
|
49
|
+
|
|
50
|
+
## 1. Publish the car
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
publish_vehicle({ dir: "/abs/car" })
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
In order, it:
|
|
57
|
+
|
|
58
|
+
1. validates the package and GLB exactly as `validate_vehicle` does (host manifest and the drive
|
|
59
|
+
against `targets` included), and checks every local WAV, **refusing synthesised or generated
|
|
60
|
+
audio (`AUDIO_SYNTHESISED`) and a car with no sourced engine audio (`AUDIO_ENGINE_UNSOURCED`)**.
|
|
61
|
+
Any error stops it; nothing is uploaded;
|
|
62
|
+
2. uploads the GLB and every WAV as public objects, and uses a content URL only once it serves the
|
|
63
|
+
exact bytes;
|
|
64
|
+
3. builds the root: the package with `mesh.ref: "$self"`, the drawn `mesh.visual` rendition pinned
|
|
65
|
+
to the uploaded GLB (what Try Now, the customizer and visual QA draw), every audio row pinned,
|
|
66
|
+
and your `hostGrantExtensions` (`.helix-publish/vehicle-package.json` is the finished package);
|
|
67
|
+
4. seals a public `vehicle` Package Version (contract `helix.vehicle/2`);
|
|
68
|
+
5. makes the item from that exact Version. It opens no offer;
|
|
69
|
+
6. reads back the pin, the package identity, the bound and pinned mesh, every audio URL (fetched
|
|
70
|
+
and decoded), and the slot board (`readBack.board`: host kind, sockets, paths, grants).
|
|
71
|
+
|
|
72
|
+
Exit 0 means published **and** verified. A failure after the seal names the step: fix it and
|
|
73
|
+
re-run the same call; it resumes from its ledger in `<dir>/.helix-publish/`. **Never publish a
|
|
74
|
+
second copy**: round 1's F40 re-ran a mint to change its model and left a duplicate item behind.
|
|
75
|
+
**Ship one drawn GLB**: the seal sums draws and triangles over the closure.
|
|
76
|
+
|
|
77
|
+
## 2. What the first mint fixes for good
|
|
78
|
+
|
|
79
|
+
- `groundToBodyOrigin` and the other identity fields (contract section 12).
|
|
80
|
+
- **The slot board**: host kind, sockets, node names, tunable paths and hide grants, derived once
|
|
81
|
+
from the GLB's `asset.extras.HELIX_vehicle`, the package's physics and your
|
|
82
|
+
`hostGrantExtensions`. **A version move does not rebuild it.** New sockets or grants need a fresh
|
|
83
|
+
car today, so plan the add-on set and its grants before the first mint.
|
|
84
|
+
|
|
85
|
+
Then `check_vehicle_host({ itemId })` must show `hostKind vehicle` with non-zero sockets, nodes and
|
|
86
|
+
paths, and your grants.
|
|
87
|
+
|
|
88
|
+
## 3. A new version of an existing car
|
|
89
|
+
|
|
90
|
+
**Changing a minted car is always a new Package Version of the same item.**
|
|
91
|
+
|
|
92
|
+
1. **Pause sales first** if the live version is broken: `disable_item_distribution`.
|
|
93
|
+
2. **Edit the directory.** Start from `.helix-publish/vehicle-package.json`. **Copy every locked
|
|
94
|
+
field byte for byte**: the backend compares each locked `Sourced` value as a whole object,
|
|
95
|
+
`value`, `unit`, `sources`, `derivedFrom` **and `note`** (round 1's F40 edited only a note and
|
|
96
|
+
the move was refused with a 409). A changed model is fine: it is uploaded and pinned as in a
|
|
97
|
+
first publish.
|
|
98
|
+
3. `publish_vehicle({ dir, itemId, version: "<next patch>", reason: "<1–500 chars>" })` seals a
|
|
99
|
+
**staged** version into the car's own Package and moves the item to it. On a listed car the
|
|
100
|
+
move waits for visual QA: it stops at `staged-awaiting-visual-qa` and prints the two steps,
|
|
101
|
+
`visual_qa_item({ itemId, packageVersion, vehiclePackage: "<dir>/.helix-publish/vehicle-package.json" })`
|
|
102
|
+
with a QA sign-in, then the same `publish_vehicle` call again, which resumes at the move
|
|
103
|
+
(`helix vehicle publish <dir> --item <id> --package-version <v> --reason ...`: pin and vehicle
|
|
104
|
+
package move together). **Never activate a car version with `helix item set-version`**: it moves
|
|
105
|
+
the pin but not `properties.vehiclePackage`, so the car keeps serving its old package.
|
|
106
|
+
4. Check every surface that draws it: hosted world, customizer, Try Now and thumbnail.
|
|
107
|
+
|
|
108
|
+
What a new version must keep is in contract section 12: identity fields identical; parts,
|
|
109
|
+
sockets, renditions, panels, seats, actions and signals only grow. **If you re-frame the host,
|
|
110
|
+
re-publish every host-frame add-on against the new frame**, and run visual QA with the add-ons
|
|
111
|
+
fitted on it.
|
|
112
|
+
|
|
113
|
+
## 4. Visual QA (required before selling)
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
visual_qa_item_hosted({ itemId }) # the platform renders the current version; no sign-in needed
|
|
117
|
+
visual_qa_item_status({ itemId }) # read the verdict + reasons
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The verdict binds to the exact item, Package Version and manifest CID. On a FAIL, read every
|
|
121
|
+
reason, open the contact sheet, fix the cause, publish a new version (section 3) and re-run.
|
|
122
|
+
Hosted runs are capped per day, so do not spin on it. **Never request an admin override.** The
|
|
123
|
+
full list of required asserts and critic checks is in the `qa` reference. Only a submitted or
|
|
124
|
+
hosted verdict counts; a local run is not a gate result. Read the numeric asserts and the frames
|
|
125
|
+
before changing anything, and **never distort the car to satisfy a gate artefact** ("When the
|
|
126
|
+
gate, not the car, is wrong" in `qa`).
|
|
127
|
+
|
|
128
|
+
A freshly sealed model is public at once but the CDN the runtime draws from can lag it by minutes.
|
|
129
|
+
The hosted runner now waits for the CDN to serve the pinned model before it renders (up to about 10
|
|
130
|
+
minutes) and hands the attempt back when it never does, so a lagging CDN no longer burns the three
|
|
131
|
+
attempts; a failure reading "never presented" or "no fetched asset URL contains <sha>" is not the
|
|
132
|
+
car's fault, so do not change the model for it. A job that did end `error` is re-run with
|
|
133
|
+
`visual_qa_item_hosted({ itemId, retry: true })`, which **requeues** it as a fresh job (attempts
|
|
134
|
+
start again; it waits out the server's cooldown, about 10 minutes after the last run, instead of
|
|
135
|
+
failing). Requeue only after a failure that is not the car's, or after a fix has been published as
|
|
136
|
+
a new version.
|
|
137
|
+
|
|
138
|
+
## 5. Sell
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
create_item_distribution({ itemId, channel: "marketplace", priceLix: <n> })
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Run it with `dryRun` first, then for real. The server enforces a minimum price. **MCP cannot
|
|
145
|
+
grant public-listing authority**; a Vault listing for the Package needs `--confirm-public-listing`
|
|
146
|
+
on a human's CLI run.
|
|
147
|
+
|
|
148
|
+
**The car is not finished until it is listed and priced.** A `NOT CURRENTLY LISTED / NOT FOR
|
|
149
|
+
SALE` car is not obtainable — the owner literally cannot buy it, so the customizer's base is
|
|
150
|
+
`NOT SOLD` and the whole vehicle is a demo, not a product. Round 7 shipped exactly that: the
|
|
151
|
+
builder left the base and all nine add-ons dry and handed pricing "to the owner". **Never leave
|
|
152
|
+
the listing or the price to the owner; create the distribution yourself as the last step of
|
|
153
|
+
publishing.** A dry run of `create_item_distribution` is only ever a preview of the call that
|
|
154
|
+
must follow.
|
|
155
|
+
|
|
156
|
+
## 6. Add-ons, per part
|
|
157
|
+
|
|
158
|
+
Each add-on is a directory: `addon.json` and `model.glb`. Tune-only parts (ECU, gearbox,
|
|
159
|
+
driveline, suspension, brakes, anti-roll bars, steering) and clip-set slots ship no model.
|
|
160
|
+
|
|
161
|
+
1. `check_vehicle_addon({ dir })`: the contract, the anchoring, the host's hubs, the host's hide
|
|
162
|
+
grants and tunable paths, the backend's own fit count, and the drive effect of every `tune` op.
|
|
163
|
+
It must say it fits every host and does something.
|
|
164
|
+
2. `preview_vehicle({ item: <host id>, addon: [<dir>] })` for any part with a model: it fits the
|
|
165
|
+
unpublished part in the real runtime and checks it lands on its anchor.
|
|
166
|
+
3. `publish_vehicle_addon({ dir })`: the full check, then it uploads the model, seals a public
|
|
167
|
+
`addon` Package (contract `helix.addon/1`), makes the item from it (pinned, unlisted, no
|
|
168
|
+
offer), and reads back that every host lists it as a fit and on its add-on rail. A hide the
|
|
169
|
+
host has not granted stops it (`HOST_GRANT_MISSING`): the host car must seal the grant.
|
|
170
|
+
A dry run (`dryRun: true`) also runs the seal's refusals (`provenance.source` over 256
|
|
171
|
+
characters, missing `contentRating`, title and tag limits, Package shape), so run it before
|
|
172
|
+
the real call.
|
|
173
|
+
4. `visual_qa_item_hosted({ itemId: <add-on id> })` renders it fitted on every host. Open the
|
|
174
|
+
contact sheet and look at each close-up and the x-ray.
|
|
175
|
+
**Changing a published part:** `publish_vehicle_addon({ dir, itemId: <add-on id>, version:
|
|
176
|
+
"<next patch>", reason: "<1–500 chars>" })` seals a **staged** new Version of that part's own
|
|
177
|
+
Package and moves the *same* item to it, with the same checks and read-back as the first
|
|
178
|
+
publish (owners, offers and the item id are unchanged). Run from the directory that first
|
|
179
|
+
published the part, `version` alone is enough. A part's slot cannot change; a version name that
|
|
180
|
+
is already sealed with different bytes is refused (`VERSION_CLASH`), so take the next number.
|
|
181
|
+
A listed meshed part is held by visual QA: the call stages the version and says so; run
|
|
182
|
+
`visual_qa_item({ itemId, packageVersion: "<v>", submit: true })` (or hosted), then repeat the
|
|
183
|
+
same call to finish the move. Never hand-build a descriptor.
|
|
184
|
+
5. **List and price it** with `create_item_distribution` — the same hard requirement as the base
|
|
185
|
+
(section 5). A published-but-unlisted add-on is `NOT LISTED` on the car's page and the owner
|
|
186
|
+
cannot buy it; the customizer shows it "ALL COMPATIBLE" yet unobtainable. Round 7 left all nine
|
|
187
|
+
add-ons dry (`--dry-run` only) and handed pricing to the owner — that is a defect, not a
|
|
188
|
+
handoff. The final distribution step lists every add-on; a dry run is only a preview of the
|
|
189
|
+
real call.
|
|
190
|
+
6. `check_vehicle_host({ itemId })` must show **every required part fitting and for sale** — each
|
|
191
|
+
minimum-set slot with a `distributionId` and a price, not `(not for sale)` — and the
|
|
192
|
+
"Web customizer guard" section must pass for every add-on: it runs the customizer's own check on
|
|
193
|
+
each part's exact Package pin, so a part the rail lists but the customizer refuses to equip
|
|
194
|
+
fails the host check with the customizer's message.
|
|
195
|
+
|
|
196
|
+
## If the mint is unavailable: sealing a version by hand
|
|
197
|
+
|
|
198
|
+
Only when `publish_vehicle` (or, for a part, `publish_vehicle_addon`) answers NOT AVAILABLE and
|
|
199
|
+
something that **already exists** must change:
|
|
200
|
+
write a staged Package Version descriptor, seal it with `publish_continuum_package({ input })`, pass
|
|
201
|
+
`visual_qa_item` on that version, then activate it by re-running
|
|
202
|
+
`helix vehicle publish <dir> --item <itemId> --package-version <v> --reason "<why>"` (`publish_vehicle` with
|
|
203
|
+
`itemId`, `version`, `reason`) once the bundled CLI has it: the resume moves the Continuum pin and
|
|
204
|
+
`properties.vehiclePackage` **together**. **Never `helix item set-version` for a vehicle**: it moves
|
|
205
|
+
the pin but not `properties.vehiclePackage`, so the car keeps serving its old package (the 993 and
|
|
206
|
+
the R34 both hit this). The CLI lane is fixing `set-version`; until the CLI says so, do not use it
|
|
207
|
+
on a car. Copy the shape of the
|
|
208
|
+
car's last `.helix-publish/descriptor-<v>.json`: `"stage": true`; the root is the package as
|
|
209
|
+
canonical JSON (keys sorted, no whitespace, UTF-8, `mesh.ref` the item id) with role
|
|
210
|
+
`vehicle-definition`; the GLB is the `target-balanced` variant with role
|
|
211
|
+
`vehicle.model.web.medium`; each WAV is a `vehicle-audio` variant; every `cid` is
|
|
212
|
+
`cid:sha256:<sha256 of the exact bytes>`; `expectedClosure` counts the objects and sums their
|
|
213
|
+
bytes. Point `mesh.visual`'s derived `artifactRef` and each audio row's `url` at
|
|
214
|
+
`<origin>/content/sha256/<first two hex>/<sha>`, on the origin the existing rows use.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Vehicle QA: what to look at, what to measure
|
|
2
|
+
|
|
3
|
+
**The rule:** nothing ships until you have looked at the rendered result in a real HELIX runtime.
|
|
4
|
+
That covers the base car, each add-on fitted on it, every opening and every seat. Measure
|
|
5
|
+
positions against known landmarks; a platform self-report is not evidence.
|
|
6
|
+
|
|
7
|
+
On the 993, `parts-seated: 1, parts-offvehicle: 0` was reported while all four wheel copies sat at
|
|
8
|
+
(0, 0, 0). Every boarded check passed while the parked tyres were 8–10 cm into the road.
|
|
9
|
+
|
|
10
|
+
## Before publishing: render your GLB
|
|
11
|
+
|
|
12
|
+
This is your only look before the first mint, and **the first mint locks `groundToBodyOrigin`**.
|
|
13
|
+
So do it properly. `preview_vehicle({ target: <dir> })` loads the unpublished directory into the real HELIX
|
|
14
|
+
runtime (headless), boards and settles it, and prints ride height per wheel against
|
|
15
|
+
`groundToBodyOrigin`, wheel-to-hub offsets, arch clearance and the measured rolling radius, with a
|
|
16
|
+
contact sheet. It seats an avatar in every seat by default and measures the cabin the way the gate will
|
|
17
|
+
(the `cabin` reference), and writes the gauntlet inspection sheets under `<out>/inspection/` (read the
|
|
18
|
+
`helix-gauntlet` skill: every sheet, beside the reference photos, before any publish). It always writes the fixed
|
|
19
|
+
appearance poses (`front`, `side`, `rear`, `top`, `front34`, `rear34`, `cockpit`) under
|
|
20
|
+
`<out>/poses/` and `poses.jpg`; put them beside your reference photos (the `appearance`
|
|
21
|
+
reference). Fix, re-run, and compare its frames with the reference photos at matched angles:
|
|
22
|
+
|
|
23
|
+
- front, rear, both sides, and both 3/4 views, at eye height;
|
|
24
|
+
- a close-up of each wheel, plus one from **inboard or x-ray**, because a culled far-side rim
|
|
25
|
+
shows only a tyre ring;
|
|
26
|
+
- all four arches from the 3/4 views and from below;
|
|
27
|
+
- the cabin from the driver's eye point;
|
|
28
|
+
- each door and lid at its declared open angle;
|
|
29
|
+
- the lamps lit and unlit, at night.
|
|
30
|
+
|
|
31
|
+
Measure these, and fix anything outside the threshold:
|
|
32
|
+
|
|
33
|
+
| Check | Threshold |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Overall length, width, height, wheelbase, track | Within 2 % of the real car |
|
|
36
|
+
| Wheel node origin to its tyre-cylinder centre | ≤ 2 mm |
|
|
37
|
+
| Tyre bottoms at rest | Y = 0 ± 1 cm |
|
|
38
|
+
| Tyre to arch, liner and bumper at rest, 0.07 m bump, full lock, lock + bump | ≥ 10 mm (the 993 achieved 14 mm) |
|
|
39
|
+
| Lowest body point | ≥ 5 cm above the road |
|
|
40
|
+
| Seat H-point to roof lining, upright adult (crown about 0.83 m above the hip) | ≥ 10 mm |
|
|
41
|
+
| Driver's hand to rim | ≤ 7 cm |
|
|
42
|
+
| Feet | On the floor, not through it |
|
|
43
|
+
|
|
44
|
+
**Camber.** Fit the tread cylinder's axis against the hub axis. Never use a PCA of the rim, which
|
|
45
|
+
fakes 2–3° on a straight wheel.
|
|
46
|
+
|
|
47
|
+
## The platform gate (visual QA)
|
|
48
|
+
|
|
49
|
+
**Only the backend's verdict is a gate result.** `visual_qa_item` (with a QA sign-in; it submits
|
|
50
|
+
by default) and `visual_qa_item_hosted` record it: the backend re-derives pass or fail from the
|
|
51
|
+
measured asserts and runs the vision critic. A local run (`submit: false`, or `helix item
|
|
52
|
+
visual-qa` without `--submit`) measures but never runs the critic, so its "PASS" is not a gate
|
|
53
|
+
result: round 1's F40 read five local passes as progress
|
|
54
|
+
while every submitted run failed on the critic. Each submitted or hosted run is binding and capped
|
|
55
|
+
per day, so use local runs and `preview_vehicle` to iterate, and submit when they are clean.
|
|
56
|
+
|
|
57
|
+
A run **passes** only when there is no harness error, every required assert is present at
|
|
58
|
+
severity `block` and passes, no other `block` assert fails, and the vision critic answers `pass` to
|
|
59
|
+
every check. A missing required assert ("not measured") and a critic `cannot_judge` both fail;
|
|
60
|
+
`warn` never blocks. Required measured asserts:
|
|
61
|
+
|
|
62
|
+
| Assert | Pass condition |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `vehicle.wheels.at_hubs` | each drawn wheel within 3 cm of its hub |
|
|
65
|
+
| `vehicle.wheels.arch_clearance` | no body inside the tyre volume; lowest body point ≥ 5 cm |
|
|
66
|
+
| `vehicle.wheels.spin_with_speed` | rolling ratio 0.8–1.25 per wheel, against the radius that wheel rolls on (its axle's `tyreByAxle` radius, so staggered tyres are judged correctly) |
|
|
67
|
+
| `vehicle.steering.wheel_rotates` | front wheels ≥ 10°, steering wheel ≥ 30° |
|
|
68
|
+
| `vehicle.articulation.reaches_limits` | each panel within 5° of its declared travel |
|
|
69
|
+
| `vehicle.seats.addressable` | every declared seat can be boarded |
|
|
70
|
+
| `vehicle.occupants.penetration` | every occupant run: no body surface more than 8 mm through the cabin over ≥ 4 cm², or more than 25 mm over ≥ 1.5 cm² (the `cabin` reference) |
|
|
71
|
+
| `frame.non_empty` | no flat or empty frame |
|
|
72
|
+
| `scene.no_nan_or_invisible` | no NaN transform, no invisible drawn part |
|
|
73
|
+
| `render.textured` | materials resolve; no missing texture or placeholder grey |
|
|
74
|
+
| `scale.sane_for_category` | 1.2–30 m for a vehicle |
|
|
75
|
+
|
|
76
|
+
- **Blocking whenever present** (always on an occupant run): `vehicle.occupants.engine_current`,
|
|
77
|
+
`boarded`, `head_clearance` (crown ≥ 10 mm under the lining), `hips_on_seat` (≤ 120 mm over the
|
|
78
|
+
cushion, ≤ 20 mm through the base), `feet_on_floor` (−50 to +300 mm) and `hands_on_wheel`
|
|
79
|
+
(driver, ≤ 70 mm).
|
|
80
|
+
- **Blocking whenever present:** `vehicle.lamps.each_function_lights`. The run presses the lamp
|
|
81
|
+
keys as the seated driver (low beam, fog, high beam, each indicator, hazards, reverse, handbrake)
|
|
82
|
+
and reads the emissive of every mesh your `componentBuild` lamp rows resolve to. A lens that
|
|
83
|
+
stays dark fails naming it. An indicator must blink and, per side and on hazards, light geometry
|
|
84
|
+
that reaches both the front and the rear quarter of the car; tail lamps are read while reversing
|
|
85
|
+
with the low beam on, so the brake lens cannot stand in for them. A car that cannot be put in
|
|
86
|
+
reverse (a manual gearbox) reports reverse as not measured. The reads are in
|
|
87
|
+
`bundle.json` `facts.lamps`. It is absent when the package has no lamp rows.
|
|
88
|
+
- **Brake lamp lit at a standstill is expected, not a defect.** A seated driver with no input gets
|
|
89
|
+
the platform's auto-hold (the brake is held, the lamp follows it, as on a car with Auto Hold);
|
|
90
|
+
an unoccupied parked car's lamp is off, and the lamp drops when the car is driven or reversed.
|
|
91
|
+
Do not file it or strip the brake rows. (Platform nit: it also stays lit with the ignition off
|
|
92
|
+
and the driver still seated.)
|
|
93
|
+
- **Warn only:** `vehicle.wheels.radius_matches_physics`, `vehicle.occupants.hips_sink_depth`,
|
|
94
|
+
`vehicle.occupants.near_contact`, `render.flat_shaded`.
|
|
95
|
+
- **Every other declared rendition** (low, medium, …) must pass `at_hubs`, `arch_clearance`,
|
|
96
|
+
`articulation`, `occupants.penetration`, `no_nan`, `textured` and `scale` again.
|
|
97
|
+
- **The vision critic**, every check must pass: `item.matches_title`, `frame.nonempty` (no frame
|
|
98
|
+
shows only the environment), `render.textured`, `render.intact`, `scale.plausible`,
|
|
99
|
+
`vehicle.wheels.seated`, `vehicle.wheels.count`, `vehicle.ride_height`, `vehicle.symmetry`,
|
|
100
|
+
`vehicle.cabin`, `vehicle.articulation`, `vehicle.steering` and `vehicle.motion`. With occupant
|
|
101
|
+
frames it also checks `vehicle.occupant.head_clearance`, `seat_contact`, `no_clipping` (counted
|
|
102
|
+
only when two failing votes name the same seat, body part and cabin part) and `hands_on_wheel`.
|
|
103
|
+
- The listing gate (Marketplace and Vault listings, distributions, version moves) reads only the
|
|
104
|
+
server's recorded run for that exact Package Version.
|
|
105
|
+
|
|
106
|
+
Also: the gate passes vacuously on a car with no declared panels, never plays audio, and never
|
|
107
|
+
compares physics with the real car. It sweeps only the panels the package DECLARES, so a car whose
|
|
108
|
+
bonnet and boot are fixed passes it; `validate_vehicle` is what warns about the missing lids
|
|
109
|
+
(`PANEL_ROLE_*`), so read its warnings, do not ship them unread. A passing preview with
|
|
110
|
+
`PANEL_ROLE_NO_NODE_*` warnings means the car opens only what it declares, not what the real car
|
|
111
|
+
opens. `preview_vehicle` and a local `--local-critic` dry run never
|
|
112
|
+
produce a verdict either.
|
|
113
|
+
|
|
114
|
+
### When the gate, not the car, is wrong
|
|
115
|
+
|
|
116
|
+
**Accuracy wins.** If a gate failure comes from the harness (a camera inside a wall, a probe that
|
|
117
|
+
misreads your mesh, an assert that cannot represent a real feature such as staggered tyres), do
|
|
118
|
+
not change the car to satisfy it:
|
|
119
|
+
|
|
120
|
+
1. Prove it: open the frame and the numbers, and show the failure would occur on a correct car.
|
|
121
|
+
2. Record it in your ledger with the verdict id, the frame and the measurement.
|
|
122
|
+
3. Report it as a platform defect, with that evidence, in your final report.
|
|
123
|
+
4. Keep the car true to the real one: real dimensions, ride height and tyre sizes.
|
|
124
|
+
|
|
125
|
+
Round 1's F40 raised its body 36 mm, off the real car's figures, to lift an occupant camera above
|
|
126
|
+
a test wall. It still failed, and the car got less accurate. (That camera is fixed: the drive now
|
|
127
|
+
keeps clear of the walls and the occupant cameras clip scenery between lens and car.) Never request
|
|
128
|
+
an admin override. After a harness fix lands, re-queue the **same** version with
|
|
129
|
+
`visual_qa_item_hosted({ itemId, retry: true })`, and read the verdict and its frames
|
|
130
|
+
(`visual_qa_item_status`, the contact sheet) before changing anything.
|
|
131
|
+
|
|
132
|
+
## After publishing: the live car
|
|
133
|
+
|
|
134
|
+
1. **Platform visual QA.** Use `visual_qa_item_hosted`. It must pass for the car and for every
|
|
135
|
+
add-on on every host. Read the reasons and **open the contact sheet**.
|
|
136
|
+
2. **Owner pass on the live item.** Use a fresh QA account, as a buyer would:
|
|
137
|
+
1. Find the car in the Marketplace and buy it through the UI.
|
|
138
|
+
2. Spawn it in a hosted world **with kerbs** as well as in a flat test world.
|
|
139
|
+
3. **Parked:** walk around it. Are the tyres on the road, is it at the right ride height, and
|
|
140
|
+
are there no gaps?
|
|
141
|
+
4. **Driver:** board, take the first-person and chase views, and check that the hands are on
|
|
142
|
+
the wheel, the head clears the roof, there is no black band (headrest back faces), and the
|
|
143
|
+
steering wheel turns with the steering.
|
|
144
|
+
5. **Passenger:** board every declared seat from a second account.
|
|
145
|
+
6. **Drive:**
|
|
146
|
+
- wheels spin with speed and steer with input;
|
|
147
|
+
- 0–100 in a median of 3 runs, beside a control car in the same world, against `targets`;
|
|
148
|
+
- discard any sample after a wheel leaves the ground;
|
|
149
|
+
- it climbs a 15 cm kerb.
|
|
150
|
+
7. **Controls:** doors and lids open (O, P, `-`, `=`), headlights, indicators and hazards
|
|
151
|
+
work, and brake and reverse lamps light at night.
|
|
152
|
+
8. **Listen:** ignition, idle, a rev sweep under load, shifts, the limiter, the horn, and tyres,
|
|
153
|
+
beside a real recording of the car; read each slot's provenance from `check_vehicle_audio`.
|
|
154
|
+
9. **Customizer:** fit each add-on, save, and check that the wheel copies sit within ±1 cm of
|
|
155
|
+
the stock hubs. Then drive with each part. For performance parts, run a live A/B against
|
|
156
|
+
stock.
|
|
157
|
+
3. **Judge against the reference, blind where you can.** Put matched before and after views next
|
|
158
|
+
to real photos, pick the better one before scoring, and judge the whole set. A single view
|
|
159
|
+
misleads: the 993's final build won stance and arches but still lost on front 3/4 ride height
|
|
160
|
+
and on the steering-wheel rake.
|
|
161
|
+
|
|
162
|
+
## What numbers missed and eyes caught (the 993 and Subaru record)
|
|
163
|
+
|
|
164
|
+
- Four wheel add-ons stacked at the car's centre, while the flags said seated.
|
|
165
|
+
- A sunk body with tyres flattened into the arches: arch gaps of −8.5 and −14.1 cm.
|
|
166
|
+
- Torn, jagged arch liners after raising the body. A matte-black liner tub per wheel fixed it.
|
|
167
|
+
- A far-side wheel that showed only a tyre ring, because its rim faces were culled.
|
|
168
|
+
- Black tyres, from glTF's default metallic 1.
|
|
169
|
+
- An exhaust tip through the bumper, with the stock muffler still visible.
|
|
170
|
+
- Flat grey add-ons: a neutral paint mask, missing UVs, or base-game textures that were never
|
|
171
|
+
supplied.
|
|
172
|
+
- Heads 11 cm into the roof, a first-person black band, and feet through the floor and arch.
|
|
173
|
+
- Parked tyres 8–10 cm into the road while every boarded check passed.
|
|
174
|
+
- 26 of 26 lamp scenarios passing numerically on a car nobody had looked at.
|
|
175
|
+
- Indicators that flashed at the front only (the rear lens was painted into the body), and a bonnet,
|
|
176
|
+
engine cover and boot that never opened: every node, row and panel the package declared was
|
|
177
|
+
valid; only the hazard key and the bonnet key, pressed by someone looking at the car, showed it.
|