@hypersoniclabs/helix-mcp 0.2.4 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +81 -11
  2. package/dist/continuumCanary.d.ts +17 -0
  3. package/dist/continuumCanary.js +17 -0
  4. package/dist/continuumCanary.js.map +1 -0
  5. package/dist/server.d.ts +14 -1
  6. package/dist/server.js +4058 -162
  7. package/dist/server.js.map +1 -1
  8. package/dist/tsconfig.build.tsbuildinfo +1 -1
  9. package/dist/vehicleTools.d.ts +86 -0
  10. package/dist/vehicleTools.js +229 -0
  11. package/dist/vehicleTools.js.map +1 -0
  12. package/docs/avatar-face.md +115 -0
  13. package/docs/bridge.md +98 -0
  14. package/docs/bring-your-world.md +117 -0
  15. package/docs/catalog.md +69 -1
  16. package/docs/character-animation.md +442 -0
  17. package/docs/character-attachments.md +166 -0
  18. package/docs/character-world.md +785 -130
  19. package/docs/continuum.md +153 -0
  20. package/docs/items.md +73 -0
  21. package/docs/lighting-world.md +667 -0
  22. package/docs/locomotion-clip-spec.md +294 -0
  23. package/docs/manifest.md +31 -6
  24. package/docs/multiplayer-logic.md +460 -17
  25. package/docs/multiplayer-templates/chrono-orchard.md +36 -22
  26. package/docs/multiplayer-templates/collect-a-thon.md +28 -38
  27. package/docs/multiplayer-templates/collections.md +24 -24
  28. package/docs/multiplayer-templates/hangout.md +124 -111
  29. package/docs/multiplayer-templates/npc-wave.md +310 -0
  30. package/docs/multiplayer-templates/obby.md +13 -16
  31. package/docs/multiplayer-templates/persistent-progress.md +218 -0
  32. package/docs/multiplayer-templates/physics-bumper.md +27 -9
  33. package/docs/multiplayer-templates/physics-football.md +22 -8
  34. package/docs/multiplayer-templates/relic-bearers.md +12 -15
  35. package/docs/multiplayer-templates/server-motion.md +16 -19
  36. package/docs/multiplayer-templates/shooter-range.md +275 -0
  37. package/docs/multiplayer-templates/team-control.md +28 -15
  38. package/docs/multiplayer-templates/turn-arena.md +31 -22
  39. package/docs/multiplayer-templates/voice-radio.md +166 -0
  40. package/docs/multiplayer-templates/wave-survival.md +7 -8
  41. package/docs/multiplayer-templates/world-shop.md +240 -0
  42. package/docs/multiplayer-world.md +222 -129
  43. package/docs/npc-world.md +623 -0
  44. package/docs/publishing.md +108 -28
  45. package/docs/purchases.md +223 -0
  46. package/docs/scene-performance.md +64 -0
  47. package/docs/screenshots.md +140 -0
  48. package/docs/sdk.md +324 -5
  49. package/docs/shooter-worlds.md +537 -0
  50. package/docs/terrain.md +173 -0
  51. package/docs/upgrades.md +324 -0
  52. package/docs/vehicles.md +727 -0
  53. package/docs/world-inspect.md +156 -0
  54. package/docs/world-look.md +241 -0
  55. package/docs/world-recipe.md +65 -6
  56. package/package.json +15 -4
  57. package/skills/README.md +91 -0
  58. package/skills/helix-assets/SKILL.md +491 -0
  59. package/skills/helix-assets/references/asset-sources.md +143 -0
  60. package/skills/helix-assets/references/vault-api.md +105 -0
  61. package/skills/helix-avatar-qa/SKILL.md +85 -0
  62. package/skills/helix-avatars/SKILL.md +206 -0
  63. package/skills/helix-avatars/references/contract.md +166 -0
  64. package/skills/helix-avatars/references/dynamics.md +367 -0
  65. package/skills/helix-avatars/references/face.md +50 -0
  66. package/skills/helix-avatars/references/publish.md +76 -0
  67. package/skills/helix-avatars/references/qa.md +251 -0
  68. package/skills/helix-avatars/references/rigging.md +88 -0
  69. package/skills/helix-avatars/references/source-generated.md +190 -0
  70. package/skills/helix-avatars/references/source-model.md +90 -0
  71. package/skills/helix-avatars/references/source-rigid.md +90 -0
  72. package/skills/helix-avatars/references/source-vrm.md +61 -0
  73. package/skills/helix-gauntlet/SKILL.md +128 -0
  74. package/skills/helix-multiplayer/SKILL.md +150 -0
  75. package/skills/helix-multiplayer/references/dsl-capability-map.md +107 -0
  76. package/skills/helix-vehicles/SKILL.md +218 -0
  77. package/skills/helix-vehicles/references/addons.md +212 -0
  78. package/skills/helix-vehicles/references/appearance.md +339 -0
  79. package/skills/helix-vehicles/references/audio-import.md +138 -0
  80. package/skills/helix-vehicles/references/audio.md +580 -0
  81. package/skills/helix-vehicles/references/cabin.md +225 -0
  82. package/skills/helix-vehicles/references/host-manifest.md +174 -0
  83. package/skills/helix-vehicles/references/physics-from-specs.md +463 -0
  84. package/skills/helix-vehicles/references/publish.md +214 -0
  85. package/skills/helix-vehicles/references/qa.md +177 -0
  86. package/skills/helix-vehicles/references/reference-package.json +3481 -0
  87. package/skills/helix-vehicles/references/reference-package.md +69 -0
  88. package/skills/helix-vehicles/references/source-beamng.md +167 -0
  89. package/skills/helix-vehicles/references/source-concept.md +38 -0
  90. package/skills/helix-vehicles/references/source-model.md +100 -0
  91. package/skills/helix-vehicles/references/source-scratch.md +60 -0
  92. package/skills/helix-world-build/SKILL.md +376 -0
  93. package/skills/helix-world-build/references/config-gates.md +104 -0
  94. package/skills/helix-world-director/SKILL.md +210 -0
  95. package/skills/helix-world-qa/SKILL.md +371 -0
  96. package/skills/helix-world-qa/references/perf-budgets.md +240 -0
  97. package/skills/helix-world-qa/references/perf-handle.md +125 -0
  98. package/skills/helix-world-qa/references/visual-scorecard.md +85 -0
@@ -0,0 +1,225 @@
1
+ # The cabin and its occupants
2
+
3
+ A buyer spends most of their time looking at the cabin: from the driver's seat, through the
4
+ windows, and with the doors open. Visual QA seats an adult avatar in every declared seat and
5
+ judges it. Round 1's F40 shipped a placeholder cabin and spent seven versions (1.0.3–1.0.9) on an
6
+ occupant model nobody had documented. Design the cabin once, against the rules below.
7
+
8
+ ## The quality bar
9
+
10
+ Judge the cabin against **interior reference photos of the real car**: the dash from the driver's
11
+ eye, the seats, the door cards, the pedal box and the steering wheel. Put `preview_vehicle`'s
12
+ `cockpit` pose beside the matching photo, as for the exterior (the `appearance` reference). A
13
+ blocky stand-in for any of these parts fails the bar, even when every assert passes.
14
+
15
+ **Match the reference's interior *treatment*, not just "not a flat slab".** The bar is the real
16
+ cabin's colour, material and style — a bare-black race interior with a black Momo wheel and red
17
+ cloth buckets, a tan-leather GT with wood, whatever the car actually has. A finished but *generic*
18
+ cabin (grey/tan plastic, plain wheel, featureless seats) fails the bar exactly as a flat slab does:
19
+ round 7's F40 passed every assert and still read as a "generic grey/tan cockpit" against the
20
+ F40's bare-black felt dash, black Momo wheel, red cloth buckets and exposed gated shifter, and the
21
+ critic scored the interior 4. The gate is: **does this cabin read as the reference's cabin?** —
22
+ the trim colour, the seat material, the wheel, the shifter and the dash finish, beside the photo.
23
+
24
+ This is a **pass/fail gate** with a reference pair, not a taste call. The **interior finish gate**
25
+ in `appearance` (section 4.4) is the concrete version — the bar (a legible binnacle with
26
+ tach/speedo/turbo, real seat shells, a real dash and door cards, the controls present, all in the
27
+ reference's colour and material) and the round-4 PASS/FAIL pair: the live 993's cabin passes
28
+ (interior 8), the round-4 F40's flat grey slab fails (interior 5). Run it at the `cockpit` and
29
+ dash poses before the first mint.
30
+
31
+ Required interior parts (static ones merge into `bodyshell`, or a separate `cabin` node):
32
+
33
+ | Part | Bar |
34
+ | --- | --- |
35
+ | Seat shells | The real seat's design and material: bucket, sports or bench shape, bolsters, headrest, upholstery or shell colour. Seat cushion and backrest are surfaces an occupant sits on, so model them where the H-point says. |
36
+ | Dash and instrument binnacle | The real dash's shape and layout: binnacle, vents, centre console. |
37
+ | Gauges | Legible dial faces at the cockpit pose: the real faces, numerals and needle positions in the atlas. A flat grey disc fails. |
38
+ | Steering wheel (`steering`) | The real wheel's design: spoke count and shape, rim thickness, boss and badge. Origin on the column axis, at the hub. |
39
+ | Pedals | Two or three pads (`pedal_throttle`, `pedal_brake`, `pedal_clutch`, or merged) where the real pedal box is. |
40
+ | Gear lever and gate | Where the real one is (`gearshift` socket or node). |
41
+ | Door cards | The inside of each door, on the door's own node, so an open door shows a finished panel. |
42
+ | Floor, footwells, roof lining, firewall, pillars | Closed surfaces. A missing panel shows the road or the sky through the cabin. |
43
+
44
+ Never fabricate a part the real car does not have; ship what the car has.
45
+
46
+ ## How the runtime seats an occupant
47
+
48
+ - **One anchor: the H-point.** A seat socket (`seat_dside_f`, `seat_pside_f`, `seat_dside_r1`, …)
49
+ is the occupant's hip joint in the model frame. Declare it in the package's `mesh.sockets` and in
50
+ the GLB's `asset.extras.HELIX_vehicle.sockets` (the `host-manifest` reference), with the same
51
+ value in both.
52
+ - **Everything is measured from the H-point**: the steering rim (`steering` socket; by default
53
+ about 0.38 m ahead, its lower edge 0.16 m above the hip so the thighs pass under), the pedals
54
+ (`pedal_throttle`, `pedal_brake`, `pedal_clutch`; by default about 0.84 m ahead and 0.26–0.32 m
55
+ below), the gearshift and the ignition.
56
+ - **The occupant is fixed, never scaled**: about 0.78 m hip to crown reclined, a 0.86 m leg. The
57
+ rig puts the pelvis on the H-point, reclines 8–32° until the measured crown clears the roof by
58
+ 40 mm, grips the rim with both hands (driver), and plants the feet on the pedals or the floor,
59
+ never closer than 0.32 m to the hip.
60
+ - **`cabinFit` moves your seats** (engine 0.3.168 and later) when the drawn cabin cannot hold that
61
+ occupant. It raycasts the bodywork you draw, then, in this order:
62
+ 1. moves the seat row **aft** until there is roof height to seat someone;
63
+ 2. **lowers** the hip until the crown clears the measured ceiling by 40 mm, never lower than
64
+ 0.12 m over the underbody. The ceiling is the roof's **inner surface** (the headliner), not
65
+ the outer skin: a thick roof panel is measured through, so a ~40 mm-thick shell does not read
66
+ its own thickness as headroom and squeeze the head against the lining (round 7's F40 read 2 mm
67
+ of real clearance where the fit had placed the crown 40 mm under the *outer* skin);
68
+ 3. moves the steering wheel and pedals with the hip;
69
+ 4. nudges the rim (kept ≥ 0.32 m from the hip, radius ≥ 0.08 m) and the pedals clear of the skin.
70
+
71
+ It does this silently in the live runtime: round 1's F40 had both seats moved 0.16 m aft and
72
+ nobody was told. A seat the shell cannot hold at all is **refused**, and nobody can sit there.
73
+ - **`preview_vehicle` shows it** (occupants are on by default). Its CABIN FIT section prints, per seat,
74
+ your authored H-point, the H-point the runtime used, the move (aft, lowered), headroom and crown
75
+ clearance, plus rim and pedal moves and the runtime's warnings. The check
76
+ `preview.occupants.cabin_fit` **fails** when the runtime moved a seat more than 30 mm from its
77
+ authored H-point, or refused it, and names the H-point the runtime used.
78
+ - **Design so that `cabinFit` moves nothing.** If it moved a seat, your cabin disagrees with your
79
+ H-point. If the runtime's H-point is right, declare it (move the seat socket everywhere it is
80
+ declared); otherwise open the cabin (roof, floor, bulkhead) so an adult fits where you authored
81
+ it. Never build the cabin
82
+ around where the runtime moved the occupant: round 1's F40 moved its bulkhead and seats to follow
83
+ a 0.16 m aft shift, and the cabin stopped matching the car.
84
+
85
+ ## The measured thresholds (visual QA, on the drawn occupant)
86
+
87
+ | Check | Threshold |
88
+ | --- | --- |
89
+ | The engine seats occupants correctly (`engine_current`) | engine ≥ 0.3.169 |
90
+ | Every declared seat boards with the real E key (`boarded`) | all seated |
91
+ | Crown to the first surface above it (`head_clearance`) | ≥ 10 mm |
92
+ | Buttocks above the cushion (`hips_on_seat`) | ≤ 0.12 m hovering, ≤ 0.02 m through the seat base |
93
+ | Soles to the floor, sill or pedal pad (`feet_on_floor`) | between 0.05 m below and 0.30 m above |
94
+ | Driver's nearest hand bone to the rim (`hands_on_wheel`) | ≤ 0.07 m, both hands |
95
+ | Pelvis to the seat the engine says it is on | ≤ 0.03 m |
96
+ | Torso angle, hip → neck base from vertical (`torso_angle`) | 5°–32° (warn outside 10°–28°). SAE J826: sedans 20–25°, sports 25–30° |
97
+ | Thigh, hip → knee against horizontal (`knees_up`) | −25° to +35° (warn above +30°); the knee height over the hip and the hip and knee angles are printed |
98
+ | Head held in wings (`head_in_wings`) | the car within 60 mm of the skull, below the crown, on **two or more** of back / left / right fails (one side, a plain restraint, passes) |
99
+ | **Penetration** (`vehicle.occupants.penetration`, required) | A connected patch of body through one cabin mesh fails when at least 3 points are more than 8 mm deep over at least 4 cm², or more than 25 mm deep over at least 1.5 cm². A hand on the rim and a sole on a pedal fail only beyond 20 mm. Hair, ears and cloth flaps are not judged. |
100
+
101
+ Warn only: `hips_sink_depth` beyond 0.10 m, `near_contact` beyond 1 mm. The critic then looks at
102
+ the occupant frames (head clearance, seat contact, no clipping, the driver's hands on the rim),
103
+ but only in a submitted or hosted visual-QA run, never in a preview.
104
+
105
+ ## Seat posture: how the occupant reads, and what to change in the GLB
106
+
107
+ The fit checks above ask whether an adult fits. The three posture checks ask whether the **drawn
108
+ avatar looks like a person sitting in the car**: round 8's F40 passed every fit check while both
109
+ occupants sat laid back with their knees up and their heads in the seat wings. They are measured on
110
+ the posed skeleton and skull (`preview_vehicle` prints each with its numbers),
111
+ so none of them is a number you can declare. You change the *geometry* around the H-point. Measured
112
+ on the live 993 (pass) against round 8's F40 (fail):
113
+
114
+ | Check | 993 | F40 | What drives it |
115
+ | --- | --- | --- | --- |
116
+ | `torso_angle` | 12° / 12° | 20.5° / 20.5° (inside the band: this one does not fail) | headroom over the H-point |
117
+ | `knees_up` | driver 3°, passenger 26° | driver 6°, **passenger 44°** (knee 0.23 m over the hip) | footwell depth and floor height under the dash |
118
+ | `head_in_wings` | 133–207 mm on every side | driver **1 / 54 / 36 mm** back / left / right | headrest and wing shape at head height |
119
+
120
+ **There is no seat-back-angle field.** The rig reclines the occupant from 8° to 32° only as far as
121
+ the headroom over the H-point needs (crown 40 mm under the lining), then spreads it over the spine.
122
+ A recline that reads too far back means the roof leaves too little height above the hip: the engine
123
+ prints "the roof allows an H-point only X m above the measured floor where a seating package wants
124
+ 0.34 m" in the CABIN FIT warnings. Fix it with geometry, not a socket: sink the seat bucket into the
125
+ floor pan (lower the cushion, keep the H-point on the cushion top), raise the roof lining over the
126
+ seat (the real car's roof stays, its inner lining and any roll-hoop trim can give), and model the
127
+ backrest leaning at the torso angle the preview prints, so the back meets the surface it is
128
+ resting on.
129
+
130
+ **Where the seat goes.** Place the `seat_*` socket (the hip joint) on the cushion's **top surface**
131
+ at the point where the pelvis rests, about 0.12–0.15 m ahead of the backrest face, centred
132
+ laterally between the bolsters, and the same value in `mesh.sockets` and the GLB
133
+ `HELIX_vehicle.sockets`. `hips_sink_depth` warns when it is more than 0.10 m under the cushion top,
134
+ which is what a socket at the seat's floor mount rather than at the cushion produces. Cushion height:
135
+ the runtime asks for an H-point 0.34 m above the floor pan under it; the 993 passes at 0.25 m, the
136
+ F40 had 0.21 m ("seated race-low with a shallow footwell": the legs lie out along a bench and the
137
+ roof forces the recline).
138
+
139
+ **`knees_up` fix (the thigh is rising to the knee).** The sole lands on the first up-facing
140
+ surface under a point at least 0.32 m ahead of the hip, so the thigh angle is set by how far below
141
+ the hip that floor is and how far ahead it starts. The passenger's footwell needs a floor pan at least
142
+ 0.25 m under the H-point (the runtime asks for 0.34 m) and a pedal-box depth near 0.26 m, open for about 0.6 m ahead of the hip; a dash, glovebox or bulkhead closer than that
143
+ shortens the shin and folds the knee up to the chest (the F40 passenger had 0.275 m of shin travel
144
+ and a knee angle of 92°; the 993's is 120°). Deepen the footwell (floor pan lower, dash and footwell
145
+ bulkhead further forward, or the H-point further aft), or fit a footrest ramp the sole can land on
146
+ lower than the toe board.
147
+
148
+ **`head_in_wings` fix.** A head against one headrest is normal; a head in a pocket is not. Keep the
149
+ inner faces of the wings at head height at least **0.15 m either side of the seat socket's x**
150
+ (a skull is about 0.17 m wide, so that leaves 60 mm), and the headrest face behind the head no
151
+ closer than the avatar's own head-back (the check reports the mesh it is against). Either lower
152
+ the wings below the ear line (the head bone's height, about 0.55 m over the cushion top) or open
153
+ them out; a separate `seat_*_headrest` mesh keeps the wing, ear and shoulder shapes
154
+ under your control. The real F40's seat is a pocketing race shell: keep its look, widen the
155
+ pocket enough that the avatar's head is not wedged in it.
156
+
157
+ Preview, read the three posture lines and the `seat-<seat>` inspection
158
+ sheets (first person, side glass, over the shoulder, windscreen), fix the geometry, and re-run. The posture checks are
159
+ measured on the QA account's avatar, so judge the angle values (they are the skeleton's) and treat
160
+ the head gaps as ±10 mm.
161
+
162
+ ## A rear row (2+2, four, five, seven seats)
163
+
164
+ Rear seats are the long pole: a four-seat build spent about 4.7 of its 8.5 hours on them because
165
+ each try was a 15-minute hosted run. Design the rear row against these numbers and iterate with
166
+ `check_vehicle_cabin` (seconds, offline), not with versions.
167
+
168
+ - **Declare every seat you model.** `seat_dside_r1` / `seat_pside_r1` (`r2` for a third row) are
169
+ hip sockets exactly like the front pair, in `mesh.sockets`, the GLB manifest **and**
170
+ `specialty.module.seatVars`. A seat the package does not declare cannot be boarded and is not judged.
171
+ - **Rear H-point against the roofline.** The roof slopes down over the back row, so the rear hip is
172
+ the one that comes out race-low. Height of the hip over the floor under the feet: a saloon or
173
+ wagon 0.28–0.34 m, a 2+2 coupe 0.20–0.26 m; the runtime asks for 0.34 m and warns below, and
174
+ `check_vehicle_cabin` warns under 0.15 m and fails under 0.08 m. Crown clearance is the roof's
175
+ inner surface over the hip minus about 0.71 m (reclined crown over hip): keep it at least 30 mm
176
+ (warn) and never under 10 mm. Gain headroom by raising the lining over the rear seat or sinking the
177
+ cushion into the floor pan, not by dropping the socket onto the floor.
178
+ - **Knee clearance to the front seat back.** The rear knee sits about 0.43 m ahead of the hip and
179
+ at about the hip's height. Put the rear hip roughly 0.75–0.90 m behind the front hip and slim the
180
+ front seat backs where the knees meet them; the knee must clear them by at least 15 mm.
181
+ - **A rear footwell under the front seats.** Rear feet are planted 0.6–0.84 m ahead of the rear hip
182
+ (the fit picks the clear reach), which is under the front seat bases. Leave at least 0.11 m of free
183
+ height over the rear floor under each front seat base (a foot is 90 mm thick, plus 20 mm), open the
184
+ floor pan there, and keep the front seat base up on rails rather than boxed to the floor.
185
+ - **Tunnel clearance for rear feet.** Each foot stands 0.11 m inboard and outboard of its hip's x
186
+ (a hip at x 0.30 puts the inboard foot at x 0.19). The tunnel or console sides, and the inner
187
+ faces of the front seat bases, must stay at least 15 mm outside that foot and the shin above it.
188
+ Narrow the tunnel skirt aft of the front seats or move the rear hips outboard.
189
+ - **The loop for a rear row:** edit the cabin, run `check_vehicle_cabin` (per seat: H-point, head,
190
+ knee, shin, foot, seat ahead, tunnel, footwell, each PASS / WARN / FAIL with millimetres; also
191
+ `validate_vehicle` with `cabin: true`), repeat until no rear row FAILs and the WARNs are
192
+ deliberate, then confirm once with `preview_vehicle({ target: <dir> })`, which boards and seats
193
+ an avatar in **every** seat, rear included. A rear seat is granted locally in the preview's page
194
+ (the stand-in room has no rear seats), so the preview is single-client; the fit, the occupant rig and
195
+ every gate are the real ones. The legs `check_vehicle_cabin` seats are the runtime's canonical
196
+ legs in clothes, so trust a FAIL, and treat a WARN of a few millimetres as the real avatar's
197
+ margin.
198
+
199
+ ## The loop
200
+
201
+ 1. Model the cabin to the reference photos, with the H-points where the real seats put the hips.
202
+ 2. `check_vehicle_cabin({ target: <dir> })` after each cabin edit: it judges every declared seat
203
+ offline in seconds. Fix every FAIL; for a rear row see the section above.
204
+ 3. `preview_vehicle({ target: <dir> })` seats an avatar in every seat (rear included), measures every
205
+ threshold above, prints the CABIN FIT moves, and writes the `seat-*`, `driver-steering` and
206
+ `stillness` inspection sheets. Open every one (`helix-gauntlet`).
207
+ **Hands.** With the wheel held at left lock, centred and right lock, both of the driver's hands
208
+ must stay ON the rim — nearest finger joint ≤ 30 mm from the rim's ring and the grip ≤ 90 mm from
209
+ the tube (`hands_on_wheel_steering`; a hand dropped to the thigh beside the bottom of the rim is
210
+ ~50 mm away and fails), one hand must turn the wheel, travelling
211
+ round with it by at least a quarter of its turn while the other holds and lets the rim slide (push-pull;
212
+ `hands_follow_wheel` — two hands that stay put while the wheel turns fail), sit at about 9 and 3 when centred (`hands_at_nine_and_three`: left
213
+ 7:30–11:30, right 0:30–4:30), and every occupant's finger joints must move ≤ 4 mm and turn ≤ 3° and hands ≤ 12 mm
214
+ over a held window (`fingers_still`, `hands_still`). The seat rig must log no `[vehicle-seat]` warning
215
+ (`seat_rig_warnings`: it logs a rim no hand can reach, and parks that hand on the thigh). A hand that cannot reach the rim rests on the
216
+ thigh, which fails: move the wheel toward the driver (the steering column's rake and reach) or the
217
+ seat forward, as the real car's dimensions allow.
218
+ 4. Fix the geometry until `cabin_fit` passes (no seat moved or refused) and every threshold
219
+ passes. Design the cabin once against these numbers; never iterate published versions against
220
+ the hosted gate.
221
+ 5. Compare the `cockpit` pose with the interior reference photos. Fix what does not match.
222
+
223
+ A car too low for an adult (some real supercars are) still keeps its real roof height. Lower the
224
+ seat and recline as the real car does. If a gate measurement is wrong for a correct car, follow
225
+ "When the gate, not the car, is wrong" in the `qa` reference.
@@ -0,0 +1,174 @@
1
+ # The host manifest: `asset.extras.HELIX_vehicle`
2
+
3
+ A vehicle GLB states what it is, and where its sockets are, in **one** object at the glTF
4
+ document's **`asset.extras.HELIX_vehicle`**. The backend reads that exact location, and only that
5
+ location, when the item is minted. From it, and from the vehicle package, it derives the car's
6
+ slot board:
7
+
8
+ - the host kind (`vehicle`);
9
+ - the sockets add-ons mount on;
10
+ - the node names add-ons may hide;
11
+ - the tunable paths performance parts may change (these come from the package's physics).
12
+
13
+ **The board is derived once, at mint.** A car minted without the manifest is published as a silent
14
+ non-host: the upload reports `verification.ok`, but the customizer shows "0 compatible", and no
15
+ wheel, wing, exhaust, ECU or brake part can ever fit it. A later Package Version does not
16
+ re-derive the board. Round 1's F40 lost its whole add-on set this way: the manifest was written to
17
+ the scene's `extras` and the root `extras`, and `asset.extras` was empty.
18
+
19
+ The runtime also reads the manifest from scene `extras` or root `extras`, which is why a manifest
20
+ in the wrong place still "works" in a local preview. Write it to `asset.extras` only. If it is in
21
+ more than one place, the runtime warns.
22
+
23
+ ## The shape
24
+
25
+ ```json
26
+ "asset": {
27
+ "version": "2.0",
28
+ "generator": "…",
29
+ "extras": {
30
+ "HELIX_vehicle": {
31
+ "version": 1,
32
+ "class": "supercar",
33
+ "mass": 1100,
34
+ "wheelRadius": 0.3236,
35
+ "sockets": {
36
+ "seat_dside_f": [0.33, 0.29, -0.10], "seat_pside_f": [-0.33, 0.29, -0.10],
37
+ "steering": [0.32, 0.64, 0.34], "engine": [0, 0.43, -1.50], "exhaust": [0, 0.30, -2.20],
38
+ "cam_cockpit": [0.33, 0.94, -0.16], "cam_chase": [0, 0.93, -6.9], "cam_hood": [0, 0.81, 1.86],
39
+ "wheel_lf": [0.80, 0.32, 1.22], "wheel_rf": [-0.80, 0.32, 1.22],
40
+ "wheel_lr": [0.80, 0.33, -1.20], "wheel_rr": [-0.80, 0.33, -1.20],
41
+ "headlight_l": [0.59, 0.50, 1.75], "headlight_r": [-0.59, 0.50, 1.75],
42
+ "taillight_l": [0.52, 0.45, -1.94], "taillight_r": [-0.52, 0.45, -1.94],
43
+ "boot": [0, 0.65, -1.63], "bonnet": [0, 0.54, 1.30]
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ - `sockets` maps each socket id to a **position in the model frame** (metres, +X left, +Y up,
51
+ +Z forward), measured off your final GLB. The numbers above are an example only.
52
+ - Declare **every seat** (`seat_*`, the H-point), the four `wheel_*` hub centres, `steering`,
53
+ `engine`, `exhaust`, the lamp sockets you have, and `boot` / `bonnet` if wings or bonnets will
54
+ bind to them. A socket the manifest omits is not on the board, so no add-on can mount there.
55
+ - Keep these positions equal to the package's `mesh.sockets`. Change both together.
56
+ - The node list on the board is read from the GLB's own nodes, not from the manifest. Every node an
57
+ add-on hides (`rim_*`, `exhaust`, a wing) must exist as a node.
58
+ - Socket and node names must match `[A-Za-z0-9_.-]{1,64}`. At most 128 are kept.
59
+
60
+ ## Every slot's bind root must exist in the GLB
61
+
62
+ A manifest socket is a position, but the runtime composes an add-on against **nodes**. A meshed
63
+ add-on authored in the host frame carries `helixMeshFrame.bind { socket, root, matrix }`, and the
64
+ runtime requires all three of these in the car's GLB before it draws anything:
65
+
66
+ - a node that resolves to `bind.socket` (a lid socket like `boot` or `bonnet` needs a node named
67
+ for it, the lid itself or an empty node on its hinge line);
68
+ - a node named `bind.root` (the host-frame root, `vehicle_root` by convention: an **empty node at
69
+ the origin that is the parent of every top-level node**);
70
+ - that root must contain the mount node.
71
+
72
+ When one is missing the runtime throws while composing the car: the car is **never presented**,
73
+ the page raises no error, and a preview hangs until its timeout. Round after round lost an hour to
74
+ this (a wing bound to an absent `vehicle_root`; a missing `boot` node). Add the empty root before
75
+ you export, and keep every lid node under it:
76
+
77
+ ```text
78
+ vehicle_root (empty, identity transform)
79
+ ├─ bodyshell
80
+ ├─ boot (origin on the hinge line)
81
+ ├─ bonnet
82
+ └─ …every other top-level node
83
+ ```
84
+
85
+ `validate_vehicle({ vehiclePackage, glb })` refuses a host that offers a `boot` or `bonnet` socket
86
+ and lacks any of the three:
87
+
88
+ - `HOST_BIND_ROOT_MISSING`: no `vehicle_root` node;
89
+ - `HOST_BIND_MOUNT_NODE_MISSING`: the offered lid socket has no node to mount on;
90
+ - `HOST_BIND_ROOT_NOT_OWNER`: the lid node is outside `vehicle_root`.
91
+
92
+ The same codes come back from `check_vehicle_host({ itemId })`, which also reads **every published
93
+ add-on on the car's rail** against the car's own GLB (the message names the node, the slot and the
94
+ add-on), from `check_vehicle_addon` against each host, and from `preview_vehicle` before it opens a
95
+ browser. A preview whose page logs `[helix-vehicle] composition failed` (or raises any page error)
96
+ before the car appears now fails at once with that message.
97
+
98
+ ## Writing it
99
+
100
+ Patch the **final** GLB, after every other tool has run. `gltf-transform` keeps `asset.extras`;
101
+ other optimisers may drop it.
102
+
103
+ **Node (no dependencies).** This rewrites the JSON chunk in place:
104
+
105
+ ```js
106
+ // node write-host-manifest.mjs model.glb manifest.json
107
+ import { readFileSync, writeFileSync } from 'node:fs';
108
+ const [, , glbPath, manifestPath] = process.argv;
109
+ const glb = readFileSync(glbPath);
110
+ const jsonLen = glb.readUInt32LE(12);
111
+ const gltf = JSON.parse(glb.subarray(20, 20 + jsonLen).toString('utf8'));
112
+ const rest = glb.subarray(20 + jsonLen); // the BIN chunk, untouched
113
+ gltf.asset.extras = { ...(gltf.asset.extras ?? {}), HELIX_vehicle: JSON.parse(readFileSync(manifestPath, 'utf8')) };
114
+ for (const holder of [gltf, ...(gltf.scenes ?? [])]) if (holder.extras) delete holder.extras.HELIX_vehicle;
115
+ let json = Buffer.from(JSON.stringify(gltf), 'utf8');
116
+ json = Buffer.concat([json, Buffer.alloc((4 - (json.length % 4)) % 4, 0x20)]);
117
+ const header = Buffer.alloc(20);
118
+ header.writeUInt32LE(0x46546c67, 0); header.writeUInt32LE(2, 4);
119
+ header.writeUInt32LE(20 + json.length + rest.length, 8);
120
+ header.writeUInt32LE(json.length, 12); header.writeUInt32LE(0x4e4f534a, 16);
121
+ writeFileSync(glbPath, Buffer.concat([header, json, rest]));
122
+ ```
123
+
124
+ **Python (inside Blender, after `bpy.ops.export_scene.gltf`).** Blender writes object and scene
125
+ custom properties to node and scene `extras`, never to `asset.extras`, so patch the exported file:
126
+
127
+ ```python
128
+ import json, struct
129
+
130
+ def write_host_manifest(glb_path, manifest):
131
+ data = open(glb_path, 'rb').read()
132
+ json_len = struct.unpack_from('<I', data, 12)[0]
133
+ gltf = json.loads(data[20:20 + json_len])
134
+ rest = data[20 + json_len:]
135
+ gltf.setdefault('asset', {}).setdefault('extras', {})['HELIX_vehicle'] = manifest
136
+ for holder in [gltf, *gltf.get('scenes', [])]:
137
+ holder.get('extras', {}).pop('HELIX_vehicle', None)
138
+ body = json.dumps(gltf, separators=(',', ':')).encode('utf8')
139
+ body += b' ' * ((4 - len(body) % 4) % 4)
140
+ header = struct.pack('<III', 0x46546C67, 2, 20 + len(body) + len(rest)) + struct.pack('<II', len(body), 0x4E4F534A)
141
+ open(glb_path, 'wb').write(header + body + rest)
142
+ ```
143
+
144
+ **gltf-transform (script).**
145
+
146
+ ```js
147
+ import { NodeIO } from '@gltf-transform/core';
148
+ import { ALL_EXTENSIONS } from '@gltf-transform/extensions';
149
+ const io = new NodeIO().registerExtensions(ALL_EXTENSIONS); // KTX2 textures need the extensions
150
+ const doc = await io.read('model.glb');
151
+ const asset = doc.getRoot().getAsset();
152
+ asset.extras = { ...(asset.extras ?? {}), HELIX_vehicle: manifest };
153
+ for (const holder of [doc.getRoot(), ...doc.getRoot().listScenes()]) {
154
+ const { HELIX_vehicle, ...rest } = holder.getExtras(); // keep it in asset.extras only
155
+ holder.setExtras(rest);
156
+ }
157
+ await io.write('model.glb', doc);
158
+ ```
159
+
160
+ ## Proving it
161
+
162
+ - `validate_vehicle({ vehiclePackage, glb })` and `publish_vehicle` refuse, before any mint:
163
+ `HOST_MANIFEST_MISSING` (none anywhere), `HOST_MANIFEST_MISPLACED` (only in scene, root or node
164
+ `extras`; the message says where), `HOST_MANIFEST_EMPTY` (no valid socket),
165
+ `HOST_MANIFEST_SOCKET_INVALID` (a value that is not a finite `[x, y, z]`),
166
+ `HOST_MANIFEST_SOCKET_NAME_INVALID` (a name the backend would drop),
167
+ `HOST_MANIFEST_SOCKET_NODE_MISSING` (a `socketNodes` entry naming no node) and
168
+ `HOST_MANIFEST_DIVERGENT` (a scene or root copy that differs from the asset copy: the runtime
169
+ would read one and the backend the other), and the bind-root codes above
170
+ (`HOST_BIND_ROOT_MISSING`, `HOST_BIND_MOUNT_NODE_MISSING`, `HOST_BIND_ROOT_NOT_OWNER`).
171
+ - After the mint, `publish_vehicle` reads the board back (`readBack.board`: host kind, sockets,
172
+ paths and grants), and `check_vehicle_host({ itemId })` must report
173
+ `hostKind vehicle` with non-zero sockets, nodes and tunable paths. If it does not, see section 2
174
+ of the `publish` reference: the board is fixed at mint.