@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,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.