@hypersoniclabs/helix-mcp 0.2.4 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -11
- package/dist/continuumCanary.d.ts +17 -0
- package/dist/continuumCanary.js +17 -0
- package/dist/continuumCanary.js.map +1 -0
- package/dist/server.d.ts +14 -1
- package/dist/server.js +4058 -162
- package/dist/server.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/vehicleTools.d.ts +86 -0
- package/dist/vehicleTools.js +229 -0
- package/dist/vehicleTools.js.map +1 -0
- package/docs/avatar-face.md +115 -0
- package/docs/bridge.md +98 -0
- package/docs/bring-your-world.md +117 -0
- package/docs/catalog.md +69 -1
- package/docs/character-animation.md +442 -0
- package/docs/character-attachments.md +166 -0
- package/docs/character-world.md +785 -130
- package/docs/continuum.md +153 -0
- package/docs/items.md +73 -0
- package/docs/lighting-world.md +667 -0
- package/docs/locomotion-clip-spec.md +294 -0
- package/docs/manifest.md +31 -6
- package/docs/multiplayer-logic.md +460 -17
- package/docs/multiplayer-templates/chrono-orchard.md +36 -22
- package/docs/multiplayer-templates/collect-a-thon.md +28 -38
- package/docs/multiplayer-templates/collections.md +24 -24
- package/docs/multiplayer-templates/hangout.md +124 -111
- package/docs/multiplayer-templates/npc-wave.md +310 -0
- package/docs/multiplayer-templates/obby.md +13 -16
- package/docs/multiplayer-templates/persistent-progress.md +218 -0
- package/docs/multiplayer-templates/physics-bumper.md +27 -9
- package/docs/multiplayer-templates/physics-football.md +22 -8
- package/docs/multiplayer-templates/relic-bearers.md +12 -15
- package/docs/multiplayer-templates/server-motion.md +16 -19
- package/docs/multiplayer-templates/shooter-range.md +275 -0
- package/docs/multiplayer-templates/team-control.md +28 -15
- package/docs/multiplayer-templates/turn-arena.md +31 -22
- package/docs/multiplayer-templates/voice-radio.md +166 -0
- package/docs/multiplayer-templates/wave-survival.md +7 -8
- package/docs/multiplayer-templates/world-shop.md +240 -0
- package/docs/multiplayer-world.md +222 -129
- package/docs/npc-world.md +623 -0
- package/docs/publishing.md +108 -28
- package/docs/purchases.md +223 -0
- package/docs/scene-performance.md +64 -0
- package/docs/screenshots.md +140 -0
- package/docs/sdk.md +324 -5
- package/docs/shooter-worlds.md +537 -0
- package/docs/terrain.md +173 -0
- package/docs/upgrades.md +324 -0
- package/docs/vehicles.md +727 -0
- package/docs/world-inspect.md +156 -0
- package/docs/world-look.md +241 -0
- package/docs/world-recipe.md +65 -6
- package/package.json +15 -4
- package/skills/README.md +91 -0
- package/skills/helix-assets/SKILL.md +491 -0
- package/skills/helix-assets/references/asset-sources.md +143 -0
- package/skills/helix-assets/references/vault-api.md +105 -0
- package/skills/helix-avatar-qa/SKILL.md +85 -0
- package/skills/helix-avatars/SKILL.md +206 -0
- package/skills/helix-avatars/references/contract.md +166 -0
- package/skills/helix-avatars/references/dynamics.md +367 -0
- package/skills/helix-avatars/references/face.md +50 -0
- package/skills/helix-avatars/references/publish.md +76 -0
- package/skills/helix-avatars/references/qa.md +251 -0
- package/skills/helix-avatars/references/rigging.md +88 -0
- package/skills/helix-avatars/references/source-generated.md +190 -0
- package/skills/helix-avatars/references/source-model.md +90 -0
- package/skills/helix-avatars/references/source-rigid.md +90 -0
- package/skills/helix-avatars/references/source-vrm.md +61 -0
- package/skills/helix-gauntlet/SKILL.md +128 -0
- package/skills/helix-multiplayer/SKILL.md +150 -0
- package/skills/helix-multiplayer/references/dsl-capability-map.md +107 -0
- package/skills/helix-vehicles/SKILL.md +218 -0
- package/skills/helix-vehicles/references/addons.md +212 -0
- package/skills/helix-vehicles/references/appearance.md +339 -0
- package/skills/helix-vehicles/references/audio-import.md +138 -0
- package/skills/helix-vehicles/references/audio.md +580 -0
- package/skills/helix-vehicles/references/cabin.md +225 -0
- package/skills/helix-vehicles/references/host-manifest.md +174 -0
- package/skills/helix-vehicles/references/physics-from-specs.md +463 -0
- package/skills/helix-vehicles/references/publish.md +214 -0
- package/skills/helix-vehicles/references/qa.md +177 -0
- package/skills/helix-vehicles/references/reference-package.json +3481 -0
- package/skills/helix-vehicles/references/reference-package.md +69 -0
- package/skills/helix-vehicles/references/source-beamng.md +167 -0
- package/skills/helix-vehicles/references/source-concept.md +38 -0
- package/skills/helix-vehicles/references/source-model.md +100 -0
- package/skills/helix-vehicles/references/source-scratch.md +60 -0
- package/skills/helix-world-build/SKILL.md +376 -0
- package/skills/helix-world-build/references/config-gates.md +104 -0
- package/skills/helix-world-director/SKILL.md +210 -0
- package/skills/helix-world-qa/SKILL.md +371 -0
- package/skills/helix-world-qa/references/perf-budgets.md +240 -0
- package/skills/helix-world-qa/references/perf-handle.md +125 -0
- package/skills/helix-world-qa/references/visual-scorecard.md +85 -0
|
@@ -0,0 +1,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.
|