@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,339 @@
1
+ # Appearance acceptance (before the first mint)
2
+
3
+ The car must look like the real one **on the platform**, at the poses people will see it from.
4
+ An earlier car judged its own renders in local Blender and still shipped wrong: on the platform its
5
+ paint read washed-out, its nose had flat panels where the reference has recessed lamps, and its
6
+ wheels were generic. None of that shows up in a number.
7
+
8
+ Run this before the first mint and after every mesh or material change. It reads
9
+ `preview_vehicle`'s renders of your unpublished car in the real HELIX runtime, never a DCC
10
+ viewport.
11
+
12
+ ## 1. Collect the reference set (research step)
13
+
14
+ Gather photos of the real car, in the same configuration you are building, at these poses:
15
+
16
+ | Pose | What to match |
17
+ | --- | --- |
18
+ | `front` | Eye height, straight on: lamp shapes, grille and intakes, track, stance. |
19
+ | `side` | Both sides if they differ: wheelbase, overhangs, roof line, glass line, ride height. |
20
+ | `rear` | Lamps, vents, exhausts, wing, track. |
21
+ | `top` | Plan view, or a manufacturer blueprint: the body's plan shape and the glasshouse. |
22
+ | `front34`, `rear34` | The views buyers see first: surfaces, shut lines, lamp depth. |
23
+ | `cockpit` | The driver's view: dash, gauges, steering wheel (the `cabin` reference). |
24
+
25
+ Add **detail photos**: each lamp pod lit and unlit, a wheel square-on (spoke count and design),
26
+ the tail, the door cards and the seats. Prefer manufacturer press photos and side-on blueprints.
27
+
28
+ ## 2. Render the same poses
29
+
30
+ `preview_vehicle({ target: <dir> })` always writes the fixed appearance poses `pose.front`,
31
+ `pose.side`, `pose.rear`, `pose.top`, `pose.front34`, `pose.rear34` and `pose.cockpit` under
32
+ `<out>/poses/`, with `poses.jpg` as a sheet. Crop each reference photo to the same framing.
33
+
34
+ ## 3. The reference-overlay gate (run it before the first publish)
35
+
36
+ This is a **pass/fail gate**, not a suggestion. For each pose produce `overlay_<pose>.png` — the
37
+ candidate render and the reference photo aligned and blended at 50 % — record the number, and run
38
+ it before the first publish and after every mesh or material change. Also put each render beside
39
+ its photo and write down every difference in proportion, shape, detail and colour.
40
+
41
+ ### The checklist (run it blind, in order, for `side`, `front` and `top`)
42
+
43
+ 1. **Align on a datum.** Scale and shift the reference photo so the shared datum spans the same
44
+ pixels as the render: the wheelbase (front and rear wheel centres) for `side` and `top`; the
45
+ track (outer tyre faces) and the ground line for `front`. Never align on a bounding box.
46
+ 2. **Cut both silhouettes** against the background (threshold the render's alpha; cut the photo's
47
+ body out of its background).
48
+ 3. **Blend at 50 %** into `overlay_<pose>.png`, for example `magick render.png photo.png -compose
49
+ dissolve -define compose:args=50 -composite overlay.png`.
50
+ 4. **Measure the silhouette as intersection over union (IoU)** of the two cut-outs, and measure the
51
+ **landmarks**: every wheel centre, the roof apex, the lamp-pod centres, the tail edge.
52
+ 5. **Record both numbers** per pose in the ledger.
53
+ 6. **Pass or fail** against the thresholds; a fail is a modelling error to fix before minting — do
54
+ not proceed.
55
+
56
+ | Pose | Aligned on | IoU | Landmarks |
57
+ | --- | --- | --- | --- |
58
+ | `side` | wheelbase | ≥ 0.92 | each wheel centre within 1.5 % of image width; roof apex within 1 % |
59
+ | `front` | track + ground line | ≥ 0.90 | track within 1.5 %; lamp-pod centres within 1 % |
60
+ | `top` | wheelbase + centreline | ≥ 0.90 | body centreline within 1 %; plan width within 2 % |
61
+
62
+ ### What a pass and a fail look like (reproduce this each round)
63
+
64
+ Read the blend: on a **pass** the two silhouettes share one edge — a single crisp line with at most
65
+ a hairline double — and the landmarks sit on top of one another. On a **fail** a band of a single
66
+ colour runs along an edge (one silhouette proud of the other) or a whole feature is ghosted (a
67
+ second roof line, a second arch).
68
+
69
+ ```
70
+ PASS overlay_side.png IoU 0.94 wheel centres 0/2 px off — mint
71
+ FAIL overlay_side.png IoU 0.83 rear arch 14 px proud; roof line 9 px high — fix, do not mint
72
+ ```
73
+
74
+ A wheel-centre offset, or a band you can see, fails at any IoU. The numbers confirm what your eyes
75
+ already found; they never replace looking.
76
+
77
+ **Detail checks** (each is a defect that has shipped):
78
+
79
+ - **Lamp treatment — match the reference, generically.** If the reference has **recessed** lamps —
80
+ pop-up headlamps, lamp pods set into the body, covers or flaps over them — model exactly that:
81
+ the pod recessed below the surface, the lens shape, the bezel, the number of elements, lit and
82
+ unlit, and **body-colour covers/flaps where the reference shows them** (closed or open), painted
83
+ the body colour, never left white, clear or a default material. If the reference has exposed
84
+ round lamps, fixed units or a light bar, model that instead. A flat, flush lamp panel where the
85
+ reference has a recess (or the reverse) is a fail. **The rule: match the reference's lamp
86
+ treatment, panel relief and paint.**
87
+ - **Panel relief and shut lines.** Where the reference has raised or recessed panels, vents,
88
+ louvers, scoops, flaps or covers, the model carries the same relief in the same place, and any
89
+ body-colour panel (a flap, a cover, a louver) is painted the body colour. Where the panel gaps
90
+ run; tinted, clear or louvred glass.
91
+ - **Wheels**: the design, spoke count, dish, centre-lock or lug pattern, and the tyre sidewall
92
+ height for each axle (`physics-from-specs`, "Staggered tyres").
93
+ - **Tail**: the lamp count and shape, the vents, the wing and its supports, the exhaust tips.
94
+
95
+ ## 4. Paint, materials, lamps and interior (an executable recipe — run it, do not eyeball it)
96
+
97
+ The silhouette overlay alone is not enough. Round 3 passed every overlay (side IoU 0.952) and still
98
+ shipped flat salmon paint, a hard white bloom on the nose, roof and wheel hub, two solid white
99
+ glowing lamp panels, and a grey placeholder dash. IoU cannot see any of that. This section can, and
100
+ each item below is a pass/fail with a named failure and a check.
101
+
102
+ ### 4.1 Paint — sample the code, convert to linear, then verify on the render
103
+
104
+ 1. **Sample the paint code from the reference photos — do not name it.** Pick body pixels from a
105
+ lit face, a shadowed face and a top surface, average the unshaded ones, and record the sampled
106
+ **sRGB** value (0–255 per channel) in the ledger.
107
+ 2. **Convert to linear.** glTF `baseColorFactor` is **linear**, not sRGB. Writing a photo's sRGB
108
+ value straight into it makes every colour lighter and paler: a red reads salmon. Convert each
109
+ channel first:
110
+ `linear = c ≤ 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ^ 2.4` (c in 0–1). A base-colour
111
+ texture must be tagged sRGB (KTX2 `--assign_oetf srgb`); data maps stay linear.
112
+ **The conversion is necessary, not sufficient.** A photo's pixel is a *lit* colour; the Try Now
113
+ daylight (strong sun plus the test grounds' stage lights, tone-mapped) is much hotter than a
114
+ photo's exposure, so the correct linear value of the photo colour still renders pastel (see
115
+ "The daylight paint gate" below, where the round-8 F40 did the conversion right and still
116
+ shipped salmon).
117
+ 3. **Clear-coat as a single layer.** `KHR_materials_clearcoat` is forbidden (the vehicle contract,
118
+ section 2). Reproduce the clear-coated look with a single layer: `roughness` **0.25–0.35,
119
+ UNIFORM — no roughness texture noise on the body** and `metallic` per the table below (a plain
120
+ `metallic` 0 gives white glints that wash a saturated colour in the daylight gate). Lumpy
121
+ reflections come from faceted or uneven geometry, not from the material: use weighted normals and enough panel resolution, and
122
+ never a noisy normal map on paint. Top surfaces show the sky as a soft reflection, and the
123
+ colour survives on the sides.
124
+ 4. **Verify on the platform render.** Sample the same body area in the `preview_vehicle` render and
125
+ compare it to the reference photo **at the same pose, under the same lighting**. Put the two
126
+ crops side by side and **write down both sampled sRGB values** — that plus the side-by-side is
127
+ the evidence; no ΔE formula is required.
128
+ - **The named failure — a "salmon" red.** A saturated red that renders flat, pale and
129
+ unsaturated (salmon) means a **wrong linear conversion** (the sRGB value written straight into
130
+ `baseColorFactor`) or a **wrong base colour** (sampled in shadow or from the wrong paint).
131
+ Fix the conversion maths first, then re-sample the unshaded body. If the conversion is
132
+ already right and the daylight gate still fails, the albedo is too hot for the runtime's light:
133
+ darken it by the amount the gate's measurement calls for (below), never by eye.
134
+ - **Pass:** the render's lit body face reads as the reference photo's lit body face.
135
+
136
+ **The daylight paint gate (run it, don't eyeball it).** Pass the sampled reference colour to the
137
+ preview: `preview_vehicle({ target: <dir>, referencePaint: "#rrggbb" })` (the CLI is
138
+ `helix vehicle preview --reference-paint "#rrggbb"`). Sample the reference as the **median of the
139
+ body-paint pixels across several reference photos** (strongly coloured, not a highlight, not a
140
+ shadow), not one photo's brightest face. The preview then renders the **body paint alone** (every
141
+ other mesh hidden) from the six exterior pose cameras in the real test-grounds world with its sun,
142
+ environment, exposure, tone mapping **and local lights** untouched (the appearance poses switch the
143
+ local lights off, so they cannot show the bonnet hotspot Try Now shows), and judges those pixels
144
+ against the reference. Round 8's F40 passed the old hue-only check (8 degrees off) and shipped
145
+ salmon with a white bonnet: hue is the weakest of the signals, so it is now one of four.
146
+
147
+ | Assert | Chromatic reference (saturation >= 0.2) | Neutral reference (white, silver, grey, black) |
148
+ | --- | --- | --- |
149
+ | `vehicle.paint.hue` | circular hue within 12 degrees (`paintTolerance`) | not judged: hue is undefined |
150
+ | `vehicle.paint.saturation` | rendered median >= 0.75 x the reference's | rendered <= reference + 0.15 (no tint) |
151
+ | `vehicle.paint.lightness` | rendered value >= 0.55 x the reference's (not maroon) | within 0.25 of the reference's (a white or silver may not exceed it) |
152
+ | `vehicle.paint.glare` | mean share of paint pixels washed toward white <= 30% | dark and mid neutrals only: share of near-white pixels <= 30% (white is allowed to be white) |
153
+
154
+ The numbers are medians over the six views. "Washed" is a bright pixel (value >= 0.8) whose
155
+ saturation fell below half the reference's. A body that is correct in the shaded views but blown on
156
+ the bonnet and roof fails `glare`; the stage light's own hotspot on a *correct* paint stays under the
157
+ limit (the round-8 F40 with corrected paint measured 14-18%; as published, 42%).
158
+
159
+ **How to FIX a fail: change the paint material, never the exposure, the tone mapping or an emissive
160
+ hack.** The failing message prints the material's real values and the target. In order:
161
+
162
+ 1. **Base colour, in linear.** `baseColorFactor` is the *linear* value (the message prints the
163
+ reference's linear value; a converted sRGB is `c <= 0.04045 ? c/12.92 : ((c+0.055)/1.055)^2.4`).
164
+ The runtime's daylight is hot, so author roughly **0.4-0.6x of the reference's linear value**,
165
+ not the value itself. The round-8 F40 had `0.445, 0.010, 0.007` for a `#bf141c` reference (linear
166
+ `0.521, 0.007, 0.012`): correct conversion, yet 0.85x of it rendered salmon. With a base-colour
167
+ texture, scale the factor (a `baseColorFactor` multiplies the texture) rather than repainting.
168
+ 2. **Metallic for a solid colour too.** At `metallic` 0 a highlight is the dielectric's white
169
+ (specular F0 0.04), so every glint is white and the tone mapper pushes the red toward pink. At
170
+ `metallic` **0.5-0.7** the highlight carries the paint's own colour. Do not go to 1: this world's
171
+ environment light is weak, so full metal renders near-black on the sides (value < 0.4).
172
+ 3. **Roughness about 0.3.** Lower than 0.2 makes a small, intense hotspot; a rougher lobe (0.45+)
173
+ spreads white wash across the whole panel and measured *worse* on a non-metal.
174
+ 4. **Specular / IOR.** `KHR_materials_ior` and `KHR_materials_specular` are honoured (the runtime
175
+ loads a `MeshPhysicalMaterial`); `ior` 1.2-1.3 with `specularFactor` 0.5 cut the dielectric
176
+ glint. Alone they did **not** cure the washed bonnet (the diffuse light is the cause), so use
177
+ them with 1-2, never instead of them. `KHR_materials_clearcoat` stays forbidden.
178
+ 5. **Re-run the gate; stop at pass.** Proven on the round-8 F40 (only the paint material changed):
179
+ `baseColorFactor 0.30, 0.0065, 0.005` + `metallic 0.7` + `roughness 0.3`, and
180
+ `0.22, 0.005, 0.0035` + `metallic 0.5` + `roughness 0.3` + `ior 1.3`, both PASS all four asserts
181
+ (saturation 0.75-0.77 vs 0.61 as published) and read as a proper red in the Try Now frame;
182
+ `0.16, 0.004, 0.003` + `metallic 0.9` passes saturation but renders a maroon (value 0.42).
183
+
184
+ A car with no material named `*paint*` (name every body panel's material `<node>__vehicle-paint-solid`
185
+ or `-metallic`, contract section 2), or with fewer than 3000 paint pixels over the six views, **fails
186
+ as unjudgeable** instead of passing silently. Record the reference colour, the photos it came from
187
+ and the four results in the ledger — a body-colour pass/fail, not a linter.
188
+
189
+ | Paint | `metallic` | `roughness` | Base colour |
190
+ | --- | --- | --- | --- |
191
+ | Solid gloss, such as a racing red | 0.5–0.7 (a plain 0 renders white glints; it passes only with a much darker base) | 0.25–0.35 uniform | The paint code's colour, converted to linear, **times 0.4–0.6** (the daylight gate decides) |
192
+ | Metallic or pearl | 0.6–0.9 | 0.25–0.40 | The flake colour, a little lighter than the face colour |
193
+ | Matte or satin wrap | 0 | 0.55–0.75 | As solid |
194
+ | Carbon or Kevlar weave | 0 | 0.30–0.45 | The weave texture, sRGB, with a fine normal map |
195
+
196
+ ### 4.2b Wheel design — spoke count, finish and centre cap against a real close-up
197
+
198
+ The geometry gates prove the wheels sit on the hubs and spin; they do not know **what the wheel
199
+ looks like**. A car whose wheel has the wrong spoke count, finish or no cap passes every one of
200
+ them and loses the buyer at a glance. Take one **face-on close-up of the real wheel** (a
201
+ `wheel-closeup` photo: the whole rim in frame, shot as square-on as you can find) and pass it:
202
+
203
+ `preview_vehicle({ target: <dir>, referenceWheel: { image: "<photo>", spokeCount?, finish?, centreCap?, crop? } })`
204
+ (the CLI is `helix vehicle preview --reference-wheel <photo> [--reference-wheel-spokes n]
205
+ [--reference-wheel-finish silver|chrome|gunmetal|black|bronze|gold|coloured]
206
+ [--reference-wheel-cap yes|no] [--reference-wheel-crop cx,cy,r]`).
207
+
208
+ The preview renders the candidate's front-left wheel square-on (`wheelface/candidate.jpg`, the
209
+ tyre centred) and reads the same three things from both images: the **spoke count** (the lowest
210
+ harmonic family of the rim face's brightness around the hub), the **finish** (the lit rim colour,
211
+ classed), and whether there is a **centre cap** (a hub disc carrying a badge or logo; a plain boss with lug holes is not a cap). Look at
212
+ `wheelface/candidate.jpg` beside the photo; the numbers are in `preview.json` (`facts.wheelDesign`).
213
+
214
+ | Assert | Fails when |
215
+ | --- | --- |
216
+ | `vehicle.wheels.design.spokes` | the candidate reads as a different spoke count (blocking) |
217
+ | `vehicle.wheels.design.finish` | a different finish class (blocking); a neighbouring one (silver / chrome, silver / gunmetal, bronze / silver, bronze / gold) warns |
218
+ | `vehicle.wheels.design.centre_cap` | the reference has a cap and the candidate's hub is open or plain, or the reverse (blocking) |
219
+
220
+ - **Declare what you know.** Values you pass (`spokeCount`, `finish`, `centreCap`) override what is read
221
+ from the photo: do it whenever the photo is oblique, tight or the rim is two-tone. Count by the
222
+ repeats the eye sees (a twin-spoke wheel is read as its number of spoke pairs).
223
+ - **`crop`** tells the gate where the wheel is in the photo: centre x of the width, centre y of the
224
+ height, and the **tyre's outer** radius as a fraction of the shorter side (default `0.5,0.5,0.45`).
225
+ If the photo cuts the tyre off, take the rim's outer radius divided by 0.72.
226
+ - A solid dish or a mesh has no spoke pattern: the gate then says it did not judge the count (a
227
+ warning, not a pass). Read the render yourself.
228
+ - Build the wheel face from the **reference's design**, not a generic spoke fan: its spoke count, the
229
+ spoke width relative to the gaps, a separate centre-cap disc with the badge, and the finish as a
230
+ metallic material (not a flat grey).
231
+
232
+ ### 4.2 Bloom — no white blob on the body
233
+
234
+ A specular highlight must **not blow to pure white** on the nose, the roof or a wheel hub. On a real
235
+ car those surfaces show the sky as a soft, *coloured* reflection; a clipped white blob is a
236
+ materials/lighting error, not a highlight.
237
+
238
+ - **Find the brightest body pixels in the render.** If they clip to pure white (255) where the
239
+ reference photo still shows colour, zero any errant emissive first. For the body paint itself the
240
+ fix is the material, in the daylight paint gate's order (4.1: lower linear base colour, `metallic`
241
+ 0.5–0.7, `roughness` about 0.3) — never the light, the exposure or the tone mapping, and not a
242
+ rougher lobe alone (0.45+ on a non-metal spread the white wash and measured worse).
243
+ - **Flag errant emissive.** A glowing white orb at the **steering-wheel hub**, or any surface
244
+ glowing that the reference does not show lit, is an **emissive material mis-set, not a light**.
245
+ Set its emissive to black (or the correct lamp material) before minting. Round 3's cabin shipped
246
+ exactly this — a bright white orb at the wheel hub.
247
+
248
+ ### 4.3 Lamp pods — recessed, never a flat emissive panel
249
+
250
+ When the reference has pop-up or recessed lamps, author a **recessed pod = a body-colour housing
251
+ plus a distinct lens**, never a flat emissive panel. Round 3 shipped two solid white glowing panels
252
+ where the F40 has closed body-colour pop-up flaps.
253
+
254
+ - **Lamps off in daylight must read as closed body-colour flaps** (a pop-up car) or **chrome-bezel
255
+ lenses** (a 993-style round lamp) — **never a glowing panel.** Model the flap in the body colour,
256
+ with the lens and bezel behind it.
257
+ - **Fixed clear-lens projector or reflector lamps** (the common modern case: a faired-in headlamp
258
+ with a clear cover) are a third shape. Off in daylight they read as **a clear cover over a
259
+ chrome or silver reflector, with the projector bowls and a bezel visible behind the lens** —
260
+ light, glinting and detailed, **never black slats or a dark slit** (a black lamp reads as a hole).
261
+ Model the cover as its own glass lens node, the reflector and each projector bowl as bright
262
+ metal parts behind it, and the bezel as a rim. Judge it **off, in daylight, against the
263
+ reference photo's lamp (`headlight-detail`)**: same number of lenses, same light interior, same
264
+ outline.
265
+ - Each lamp function is its own node (contract section 2), and each is **emissive on command only**
266
+ — bright when low/high/fog is commanded, dark otherwise.
267
+ - A flat, flush emissive panel where the reference has a recess (or the reverse) is a fail.
268
+ - Which of the three shapes is it? Look at the reference lamp **unlit**: a body-colour flap over
269
+ it is a pop-up; a chrome ring round a round lens is a bezel lamp; a clear cover over visible
270
+ reflector and bowls is a projector lamp. Do not carry the shape over from another car.
271
+
272
+ ### 4.4 The interior finish gate (the interior's own pass/fail gate)
273
+
274
+ The exterior material gate above has no interior twin, and round 4 shipped a car whose cabin was
275
+ the least finished surface on it — a grey slab dash, bare tunnel and door cards, and a glowing orb
276
+ at the wheel hub — with the exterior cleared. The cabin is the surface a buyer looks at every time
277
+ they sit in the car, so it gets its own gate, run at the `cockpit` and dash poses beside interior
278
+ photos of the real car (`cabin`).
279
+
280
+ **The bar.** In the `preview_vehicle` render, all of these must be true:
281
+
282
+ - **The reference's interior *treatment* — colour, material and style — not a generic cockpit.**
283
+ The cabin must read as *this car's* interior, not "a finished interior": the F40's bare-black
284
+ felt dash, black Momo wheel, red cloth buckets and exposed gated shifter; a tan-leather GT's tan
285
+ and wood; whatever the reference photos show. A clean grey/tan cockpit that is merely "finished"
286
+ fails — round 7's F40 passed every structural assert yet the critic read it as a "generic
287
+ grey/tan cockpit" and scored the interior 4 against the real bare-black cabin.
288
+ - **A legible instrument binnacle.** The tachometer and speedometer read as dials with **numerals
289
+ and needles** at the render resolution, and every gauge the real car has is present — the
290
+ **turbo boost gauge** on a turbo car, oil/water/volt where the car has them. Two or three dials is
291
+ not "a blank panel with a couple of dials"; the faces and needles must read.
292
+ - **Real seat shells**, not featureless grey planes: the real seat's shape and material (bucket,
293
+ bolsters, headrest, upholstery colour), with cushion and backrest where the H-point says (`cabin`).
294
+ - **A real dash and door cards**: the dash's shape and layout (binnacle, vents, centre console) and
295
+ a finished inside face for each door, not a flat plane.
296
+ - **The controls present and legible**: pedals (throttle/brake/clutch pads), the shifter or gate
297
+ plate, and the steering wheel — the F40's gate plate and alloy pedals are part of the car.
298
+ - **No flat grey placeholder and no glowing orb at the wheel hub** (the §4.2 emissive defect). A
299
+ bare plane where the real car has a finished surface fails even when every assert passes.
300
+
301
+ **Run it, do not eyeball it:** put the `cockpit` pose and the dash render beside the matching
302
+ interior reference photos, count what is present against the bar, and record the pass/fail in the
303
+ ledger. The reference pair is in §4.5.
304
+
305
+ ### 4.5 The pass/fail reference pair (reuse these poses every round)
306
+
307
+ **Materials, paint and lamps:**
308
+
309
+ | | Evidence | What it teaches |
310
+ | --- | --- | --- |
311
+ | **FAIL** | `/Users/jack/Developer/helix-workspace/asset-lab/vehicle-pipeline/rounds/f40-r3/owner-proxy/B-front.png` (blown white lamp panels) and the critic's `/Users/Shared/vehicle-blind-runs/_packets/f40-r3/pairs/stock--front__A.jpg` | Two solid white glowing panels where the F40 has closed body-colour pop-up flaps; the whole front washes out around the lamps. |
312
+ | **FAIL** | `/Users/jack/Developer/helix-workspace/asset-lab/vehicle-pipeline/rounds/f40-r3/owner-proxy/B-top-down.png` ("pale-pink wash") | Over-exposed paint plus a white bloom over the engine cover — the paint never reads as Rosso Corsa. |
313
+ | **FAIL** | `/Users/jack/Developer/helix-workspace/asset-lab/vehicle-pipeline/rounds/f40-r3/owner-proxy/B-front-3q.png` (bloom on nose) | A clipped specular highlight on the nose. |
314
+ | **FAIL** | the critic's `/Users/Shared/vehicle-blind-runs/_packets/f40-r3/pairs/stock--dash__A.jpg` | Flat grey dash, no cluster, a light orb at the wheel hub. |
315
+ | **PASS (the bar)** | the live 993's materials, described in `/Users/jack/Developer/helix-workspace/asset-lab/vehicle-pipeline/rounds/f40-r3/critic-verdict.md` (`better_product` = B) | Clear-coat reflections that read as paint, correct round lamps in chrome bezels, a fully modelled five-dial cluster and centre console — the finish the candidate must reach. |
316
+
317
+ **Interior (round 4 — the finish gate of §4.4):**
318
+
319
+ | | Evidence | What it teaches |
320
+ | --- | --- | --- |
321
+ | **FAIL** | the F40 round-4 cabin (interior 5): `/Users/Shared/vehicle-blind-runs/_packets/f40-r4/pairs/stock--cockpit__A.jpg` and `stock--dash__A.jpg` | The dash face, tunnel and door cards read as bare flat planes, no seat shells read, and the wheel-hub badge glows as an orb — a slab cabin the critic called the least finished surface on the car. |
322
+ | **FAIL** | the F40 round-7 cabin (interior 4): `/Users/Shared/vehicle-blind-runs/_packets/f40-r7/pairs/stock--cockpit__A.jpg` and `stock--dash__A.jpg` | A *finished but generic* cockpit — light tan/beige dash top, carpet and door trim, a plain grey three-spoke wheel, thin generic instruments — against the F40's bare-black felt cabin, black Momo wheel, red cloth buckets and gated shifter. "Not a slab" is not the bar; matching the reference's treatment is. |
323
+ | **PASS (the bar)** | the live 993's cabin (interior 8): `/Users/Shared/vehicle-blind-runs/_packets/f40-r4/pairs/stock--cockpit__B.jpg` and `stock--dash__B.jpg` (item `280e49b0-39e9-40cd-9d41-4cc4d9ef7ab5`) | A complete five-gauge cluster with numerals and needles, a centre console with vents and controls, a door pull, a finished wheel and shifter — the interior finish the candidate must reach. |
324
+
325
+ Also: tyres at metallic 0 and roughness about 0.6; rims at metallic ≤ 0.6 with the albedo lifted
326
+ to 0.55–0.6 (metallic 1 renders black without a rich environment); glass blended, with no
327
+ transmission.
328
+
329
+ ## 5. Gate
330
+
331
+ The gate is concrete and pass/fail. Record in your ledger, per pose: the render, the reference
332
+ photo, the `overlay_<pose>.png`, its IoU and landmark numbers with the pass/fail, the detail checks
333
+ (lamp treatment, panel relief, wheels, tail), **the daylight paint gate (`referencePaint`: hue, saturation, lightness and glare pass/fail)**, **the brightest
334
+ body-pixel check (no highlight blows to white)**, **the wheel-design gate (§4.2b: spoke count, finish and centre cap against a face-on close-up)**, **the lamp-pod check (closed body-colour flaps, bezel lenses or a clear cover over visible reflector and bowls in daylight, never black slats or a glowing panel)** and **the interior finish gate (§4.4: a legible
335
+ binnacle with tach/speedo/turbo, real seat shells, a real dash and door cards, the controls; no flat
336
+ grey placeholder, no wheel-hub orb)**. **Mint only when every overlay passes
337
+ its thresholds, the paint matches the reference when sampled, no body highlight clips to white, the
338
+ lamps read closed in daylight, the interior clears the gate, and a person seeing the pose sheet
339
+ beside the photos would name the car at once and find no part that reads wrong.**
@@ -0,0 +1,138 @@
1
+ # Vehicle audio import: base-game defaults, slot maps, Assetto Corsa
2
+
3
+ The companion of the `audio` reference (read that first: the hard rule, picking the most accurate
4
+ mod, the slot table, provenance, the gates). This covers the details of
5
+ `import_vehicle_audio` / `helix vehicle audio-import` that the car lanes keep needing.
6
+
7
+ ## Options, decode tools and the mix fields
8
+
9
+ - `config` picks the car configuration whose `soundConfig` is wanted (a trim can use a
10
+ different engine blend); `vehicle` the BeamNG vehicle id inside a mod that holds several.
11
+ - `beamngRoot` (or `HELIX_BEAMNG_ROOT`) is the BeamNG.drive install: its `art_sound.zip` FMOD
12
+ banks resolve every stock event a mod names, `vehicles/common.zip` the common parts a vehicle
13
+ selects (the soundscape horn), its stock blends fill a mod that uses a stock engine sound, and
14
+ slots no part names get what the game itself plays (table below). For a non-BeamNG car it
15
+ fills the slots that car lacks with BeamNG's generic stock recordings.
16
+ - `cylinders` is the **recorded** engine's cylinder count (the ladder pitch model). It is
17
+ derived from the BeamNG engine part when the part says; it is **required for an Assetto Corsa
18
+ car and for any BeamNG mod whose count would only be a guess** (the import tells you).
19
+ - `prefer exterior|interior` picks the microphone set when a mod has both; `acNativeRungs` keeps
20
+ only Assetto Corsa rungs that need no varispeed; `modUrl` records where the mod was fetched.
21
+ - FMOD decoding uses `vgmstream-cli` (`HELIX_VGMSTREAM` points at it; needed for Vorbis and
22
+ FADPCM banks) and `ffmpeg` (`HELIX_FFMPEG`) for OGG/FLAC. Without `vgmstream-cli`, PCM banks
23
+ decode byte for byte and Vorbis banks via `pip install fsb5`; anything else is refused with an
24
+ install message.
25
+ - `basePackage` (`--base-package <vehicle-package.json | dir | audio.json>`): an existing package's
26
+ audio block, so re-importing a **published** car keeps its mix. Use it whenever you re-import a car
27
+ that already exists.
28
+ - **The audio block always carries the five mix fields the runtime reads**: `engineGainDb`,
29
+ `exhaustGainDb`, `minLoadMix`, `maxLoadMix`, `muffling`. Each comes from, in order: the `--map` top
30
+ level; `--base-package`; the BeamNG mod's selected `soundConfig` (`mainGain`, `minLoadMix`,
31
+ `maxLoadMix`, `intakeMuffling`) and `soundConfigExhaust.mainGain`; else the engine's own BeamNG
32
+ defaults (-8 dB, -5 dB, 0.15, 1, 0.25). `audio-import.json` says where each came from (`mix`).
33
+ Missing or non-finite gains, or a mix/muffling outside 0..1, fail as `AUDIO_MIX_FIELDS_MISSING`:
34
+ the runtime feeds them to Web Audio, and a NaN gain throws in the frame loop (a car shipped
35
+ without them froze in Try Now).
36
+ - `publish_vehicle` uploads and seals the assets of **every** slot, consumed or not (the runtime
37
+ closure must be complete); `audio-check` verifies each unconsumed one loads without judging it as a
38
+ played sound.
39
+
40
+ ## Electric cars
41
+
42
+ An EV is detected **from the physics** (`physics.engine.cylinders` 0, an electric motor declaration,
43
+ or a motor ladder's provenance), never from `audio.drivetrain: "electric"`: that switches the
44
+ runtime to its synthesised motor voice and ignores recorded ladders, so it fails as
45
+ `AUDIO_SYNTHESISED`.
46
+
47
+ - The motor is still a **recorded engine ladder** (`AUDIO_ENGINE_UNSOURCED` otherwise). BeamNG's
48
+ electric motors name a stock blend with no rung samples (`ElectricMotor_02` ->
49
+ `event:>Engine>Bands>Real>electric_eng_02`, one loop re-pitched by an `RPM` automation);
50
+ `import_vehicle_audio` builds the ladder from that stock event (rungs at 1,000 rpm then every
51
+ 2,000 rpm to the motor's `maxRPM`, each the recording at the rate the automation plays it there,
52
+ recorded as `fmod-pitch-automation-varispeed`). A map can name it: `"engineLadder": { "event":
53
+ "event:/Engine/Bands/Real/electric_eng_02" }`. No cylinder count is asked for.
54
+ - `not-fitted` is allowed for every combustion slot (`exhaustLadder`, `idle`, `startup`, `shutdown`,
55
+ `clutch`, `turboSpool`, `blowOff`, `intake`, `revLimiter`, `backfire`) and, on a single-speed
56
+ drive, `shiftUp` / `shiftDown`. **`transmissionWhine` stays required**: the reduction gear whines.
57
+ - The firing-fundamental pitch gate does not apply to a motor ladder, and rungs the source itself
58
+ re-pitched from one take are not called copies.
59
+
60
+ ## What BeamNG itself plays when no part names a sound
61
+
62
+ With `beamngRoot`, `import_vehicle_audio` fills a slot no mod part names with what the game's own
63
+ Lua plays (evidence is the game's code). These are the stock events it takes, all real base-game
64
+ recordings:
65
+
66
+ | Slot | Stock event | Slot | Stock event |
67
+ | --- | --- | --- | --- |
68
+ | `startup` | `Engine/Starter/Old_V2` | `tyreRollSlow` / `tyreRollFast` | `Surfaces/roll_rigid_v2` |
69
+ | `backfire` | `Vehicle/Afterfire/01_Single_EQ1` | `impactLight`/`Medium`/`Heavy` | `Destruction/Vehicle/vehicle_part_impact` |
70
+ | `transmissionWhine` | `Vehicle/Transmission/helical_01/twine_in` | `suspension` | `Vehicle/Suspension/car_modn_med_01/spring_compress_01` |
71
+ | `shiftUp` / `shiftDown` | `Vehicle/Interior/Gearshift/manual_modern_01_in` / `_out` | `doorOpen` / `doorClose` | `Vehicle/Latches/Door/modern_03_open` / `_close` |
72
+ | `brakeSqueal` | `Vehicle/Failures/failure_brakes_normal` | `handbrake` | `Vehicle/Interior/Handbrake_Ratchet/Ratchet_01_Ratchet` |
73
+ | `tyreScrub` / `tyreSpin` / `skid` | `Surfaces/skid_rigid_v2` | `indicatorTick` | `Vehicle/Interior/Indicator/AU3_Click` |
74
+ | `turboSpool` / `blowOff` (turbo cars) | `Vehicle/Forced_Induction/Turbo_01/turbo_spin` / `turbo_bov` | | |
75
+
76
+ **`shutdown`, `clutch`, `intake` and `revLimiter` have no base-game default**: BeamNG plays nothing
77
+ for them unless a part names one, so they stay empty (and the gate fails) until a `--map` source
78
+ provides them: another mod of the same engine, another sim's event, or a real recording. A stock
79
+ default is a real recording but a generic one: prefer the car's own mod event where it has one, and
80
+ the provenance says `baseGameDefault`.
81
+
82
+ ## `--map`: one car from several sources
83
+
84
+ ```json
85
+ {
86
+ "sources": {
87
+ "ac": { "path": "/…/assettocorsa/content/cars/ferrari_f40", "sim": "ac" },
88
+ "euan": { "path": "../mods/f40-mod.zip", "sim": "beamng", "vehicle": "f40", "config": "LM (EU).pc", "modUrl": "https://…" },
89
+ "bng": { "path": "/…/BeamNG.drive", "sim": "beamng-base" },
90
+ "rec": { "path": "../reference-sound", "sim": "recording", "mod": "cold start, field recording", "modUrl": "https://youtu.be/…" }
91
+ },
92
+ "slots": {
93
+ "engineLadder": { "source": "euan", "blend": "art/sound/blends/f40engine.sfxBlend2D.json" },
94
+ "startup": { "source": "euan", "sample": "F40_LM_Start" },
95
+ "blowOff": { "source": "euan", "sample": "bovf40", "trim": { "startS": 0, "endS": 0.9 } },
96
+ "doorClose": { "source": "ac", "sample": "door_close" },
97
+ "shutdown": { "event": "event:>Engine>Shutoff>v8flat_1986_eng", "sample": "<one exact stock take>" },
98
+ "intake": { "source": "rec", "file": "intake-1.mp3" },
99
+ "horn": { "concat": [ { "event": "event:>Vehicle>Electrics>Horns>BM1_V1", "sample": "<start>" },
100
+ { "event": "event:>Vehicle>Electrics>Horns>BM1_V1", "sample": "<loop>", "repeat": 9 },
101
+ { "event": "event:>Vehicle>Electrics>Horns>BM1_V1", "sample": "<end>" } ] },
102
+ "reverseBeep": { "absent": "not-fitted", "reason": "the real car has no reverse beeper" }
103
+ },
104
+ "ladder": { "rpm": { "F40_ex_on_mid": 5700 }, "prefer": "exterior", "nativeRungs": false },
105
+ "cylinders": 8, "strokes": 4, "crankLayout": "flat-plane", "bankId": "ferrari-f40", "modUrl": "https://…"
106
+ }
107
+ ```
108
+
109
+ - The positional `source` of the command is `main`; a slot the map does not name comes from `main`
110
+ (then, with `beamngRoot`, the base game's default). Paths are relative to the map file.
111
+ - On a BeamNG mod, `sample` / `field` is a jbeam field, the value it names, or a recording the mod
112
+ ships (a path, or a bare stem such as `F40_LM_Start`; extension-less jbeam references resolve the
113
+ same way). On an Assetto Corsa source `sample` is the FMOD subsound name (`event` narrows it).
114
+ - `event` names a BeamNG stock FMOD event (`event:>A>B>C`, `event:/A/B/C` or `A/B/C`); `sample`
115
+ then picks one take of it exactly. A stock event with no `source` resolves in the `beamngRoot`
116
+ install.
117
+ - `blend` (on a ladder slot, or `ladder.blend` / `ladder.engineBlend` / `ladder.exhaustBlend`)
118
+ picks a blend the mod ships that no selected part names (an export whose jbeam names a stale
119
+ `sampleName`).
120
+ - `trim { startS, endS }` keeps a window of a longer real recording; `concat` plays pieces in
121
+ order (`repeat` per piece), e.g. a stock horn's start + loop + end. Both are recorded in
122
+ `provenance.transforms` and `pieces`.
123
+ - `sim: "recording"` sources take real recordings of the car (`file`) with `kind: "recording"`.
124
+ - `absent: "not-fitted"` + `reason` (or `notFitted: "<reason>"`) declares a part the real car
125
+ lacks; `empty: "<reason>"` leaves a slot empty on purpose, and the gate still fails it.
126
+
127
+ ## Assetto Corsa / ACC
128
+
129
+ `content/cars/<car>/sfx/<car>.bank` (FMOD Studio 1.x) with `content/sfx/GUIDs.txt` (a bank
130
+ without GUIDs maps by sample name). `engine_ext` (preferred) or `engine_int` gives the ladder
131
+ (combined with the exhaust, so `exhaustLadder` is `combined-with-engine`); `backfire`, `door`
132
+ (open and close by sample name), `gear` (shiftUp / shiftDown), `horn`, `limiter`, `skid`,
133
+ `transmission`, `turbo` (spool and blow-off), `wheel` map to their slots. The ladder comes from
134
+ the event's `rpms` sheet. Kunos spreads several takes recorded at one rpm across the rev range by
135
+ auto-pitch; a HELIX rung is one recording per rpm, so a rung is either the recording at its own
136
+ reference rpm, or the recording as FMOD plays it at its plateau anchor (a recorded varispeed,
137
+ `fmod-autopitch-varispeed`). `acNativeRungs` keeps only unchanged rungs. `cylinders` is required.
138
+