@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.
- 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,166 @@
|
|
|
1
|
+
# The avatar contract: skeleton, frame, LODs and budgets
|
|
2
|
+
|
|
3
|
+
An avatar is a universal item with `kind: avatar`. It is one GLB holding a `helix-humanoid@1`
|
|
4
|
+
character, normally three LOD scenes, with optional face and dynamics manifests in scene extras. The
|
|
5
|
+
platform's shared locomotion clips drive it by bone name. A foreign rig T-poses: a raw Meshy or
|
|
6
|
+
Mixamo rig is refused at upload as "convert first".
|
|
7
|
+
|
|
8
|
+
## Skeleton: `helix-humanoid@1`
|
|
9
|
+
|
|
10
|
+
**68 canonical joints**:
|
|
11
|
+
- `root`, `pelvis`, `spine_01`–`spine_05`, `neck_01`, `neck_02`, `head`;
|
|
12
|
+
- per side: `clavicle`, `upperarm`, `lowerarm`, `hand`, three segments for each of the five fingers,
|
|
13
|
+
plus the importer-owned metacarpals;
|
|
14
|
+
- twist joints: `upperarm_twist_01/02`, `lowerarm_twist_01/02`, `thigh_twist_01`, `calf_twist_01`;
|
|
15
|
+
- legs: `thigh`, `calf`, `foot`, `ball`, all suffixed `_l` / `_r`.
|
|
16
|
+
|
|
17
|
+
**Bones each gate requires:**
|
|
18
|
+
- **Publish preflight:** `root`, `pelvis`, `spine_01`, `head`, `hand_l`, `hand_r`, `foot_l`,
|
|
19
|
+
`foot_r`.
|
|
20
|
+
- **Backend:** `pelvis`, `spine_01`/`03`/`05`, `neck_01` and `head`, plus `clavicle`, `upperarm`,
|
|
21
|
+
`lowerarm`, `hand`, `thigh`, `calf` and `foot` on both sides.
|
|
22
|
+
- **Optional:** fingers, twists, `ball`, `spine_02`/`04` and `neck_02`. A missing bone is skipped
|
|
23
|
+
silently by the clips.
|
|
24
|
+
|
|
25
|
+
**Auxiliary joints are allowed under canonical parents**: hair, tails, physics. Add them **after**
|
|
26
|
+
import, because the importer rebuilds the canonical 68. See `dynamics`.
|
|
27
|
+
|
|
28
|
+
## Frame, units and rest pose
|
|
29
|
+
|
|
30
|
+
| Property | Value |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| Units | Metres |
|
|
33
|
+
| Up | +Y |
|
|
34
|
+
| Forward | **+Z** |
|
|
35
|
+
| Character left | +X |
|
|
36
|
+
| Feet | On Y = 0 |
|
|
37
|
+
| Rest pose | The canonical **A-pose**: arms about 45° down, not a T-pose. Gate: each upper arm is bound at least 17° from straight down (Base Female 36°); the Marketplace walk adducts ~24° from the clip's A-pose, so a bind nearer the body walks into it (`avatar.bind_pose`) |
|
|
38
|
+
|
|
39
|
+
Orientation is gated at upload. The skeleton yaw and the skinned-geometry yaw are measured
|
|
40
|
+
separately, and each must face forward within **15°**.
|
|
41
|
+
|
|
42
|
+
## LODs: one GLB, one scene per level
|
|
43
|
+
|
|
44
|
+
- Scene *i* is LOD *i*, nearest first. The importer names the scenes `lod1`–`lod3`. Match on the
|
|
45
|
+
scene index, never on the name.
|
|
46
|
+
- The runtime switches at 0 / 8 / 15 m by default.
|
|
47
|
+
- **Every LOD scene must carry its own skeleton copy.** A GLB whose LOD scenes share one skeleton
|
|
48
|
+
loads with undefined bones in three.js. The importer asserts self-contained scenes. To repair a file
|
|
49
|
+
that broke this, run `helix character repair-lod-skeletons`.
|
|
50
|
+
- At runtime, lower LODs are rebound onto LOD0's skeleton by bone name. So every edit you make after
|
|
51
|
+
import, whether weights, auxiliary joints or a dynamics or face manifest, must be made **identically
|
|
52
|
+
on every LOD**.
|
|
53
|
+
- A healthy imported avatar has **3 skins and 6 meshes**: the body plus a `FaceMesh` per LOD.
|
|
54
|
+
`FaceMesh` is the head slice that the importer cuts so the first-person camera can hide it. It
|
|
55
|
+
is an index-only slice: it shares the body's vertex accessors (`POSITION`, `JOINTS_0`,
|
|
56
|
+
`WEIGHTS_0`), so edit each accessor once, not once per primitive.
|
|
57
|
+
|
|
58
|
+
## Budgets (upload gate)
|
|
59
|
+
|
|
60
|
+
Every count is summed across the **whole file**, which means **every LOD**.
|
|
61
|
+
|
|
62
|
+
| Limit | Avatar | Source |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| Triangles, all LODs together | **60,000** | `AVATAR_MAX_TRIANGLES` |
|
|
65
|
+
| Draw calls, all LODs together | **12** | `AVATAR_MAX_DRAW_CALLS`: each primitive × the nodes that instance it |
|
|
66
|
+
| File size | **10 MiB** | `AVATAR_MAX_FILE_BYTES` |
|
|
67
|
+
| Materials | **8** | `AVATAR_MAX_MATERIALS` |
|
|
68
|
+
| Texture edge | **2048 px** | |
|
|
69
|
+
|
|
70
|
+
How they bite:
|
|
71
|
+
- **Draw calls.** One material over three LODs, each with a body and a `FaceMesh`, is 6 draws. Every
|
|
72
|
+
extra material or separate mesh costs 3 draws, once per LOD. A helmet as its own mesh took Daft
|
|
73
|
+
Punk to 9.
|
|
74
|
+
- **Triangles.** Splits that shipped:
|
|
75
|
+
|
|
76
|
+
| Avatar | LOD0 | LOD1 | LOD2 |
|
|
77
|
+
| --- | ---: | ---: | ---: |
|
|
78
|
+
| Unitree G1 | 35k | 14.5k | 5k |
|
|
79
|
+
| C-3PO | 39k | 14k | 5.6k |
|
|
80
|
+
| T-800 | 47.8k | 7.8k | 3.8k |
|
|
81
|
+
| Cream Hoodie | 30k | 15k | 5k |
|
|
82
|
+
|
|
83
|
+
The importer's default is `10000,5000,1500`. Set `--lod-tris` to use the budget.
|
|
84
|
+
- **File size.** KTX2 makes a 2048 atlas affordable. A UASTC normal map plus an ETC1S base came to
|
|
85
|
+
9.75 MiB on Iron Man, just under the cap.
|
|
86
|
+
- **Materials.** `helix character import` does **not** merge materials. A many-material source
|
|
87
|
+
imports cleanly and is then refused at publish, so atlas the materials first.
|
|
88
|
+
|
|
89
|
+
Run the structure, budget and motion checks in the avatar `qa` reference before sealing the
|
|
90
|
+
Character Package Version through the Creator's approved Publications workflow.
|
|
91
|
+
|
|
92
|
+
**Preflight errors** (blocking):
|
|
93
|
+
- `triangles`, `materials`, `texture-edge`, `draw-calls`, `file-size`;
|
|
94
|
+
- `no-skin`;
|
|
95
|
+
- `skeleton` (a required joint is missing);
|
|
96
|
+
- `rig-scale` (the skeleton spans more than 10 m).
|
|
97
|
+
|
|
98
|
+
**Preflight warnings:**
|
|
99
|
+
- `no-dynamics`;
|
|
100
|
+
- `no-face`;
|
|
101
|
+
- `no-first-person-hide`.
|
|
102
|
+
|
|
103
|
+
Every publish also runs three guards on the materials:
|
|
104
|
+
- the base colour must not be a normal map;
|
|
105
|
+
- normal maps must be valid;
|
|
106
|
+
- rest frames must deviate no more than 100° from canonical.
|
|
107
|
+
|
|
108
|
+
Do not set their bypass environment variables. Fix the asset.
|
|
109
|
+
|
|
110
|
+
## Height and the short-body locomotion gate
|
|
111
|
+
|
|
112
|
+
- Body height is LOD0's bounding-box height.
|
|
113
|
+
- The visual-QA avatar route accepts drawn heights of 0.5–3.5 m, and warns when the drawn height is
|
|
114
|
+
outside 0.8–1.25× the declared height.
|
|
115
|
+
- **Under 1.35 m, locomotion is retargeted only if** (pelvis height − foot height) / 0.877 is below
|
|
116
|
+
0.65. Otherwise the human clips play unscaled and the feet sink.
|
|
117
|
+
- The 1.32 m Unitree G1 had a natural ratio of 0.74. Its feet sank 2–5.7 cm.
|
|
118
|
+
- Lowering the authored pelvis joint to 0.60 m (ratio 0.62) fixed it.
|
|
119
|
+
- Tall bodies (C-3PO at 1.67 m, Iron Man at 1.98 m) are not affected.
|
|
120
|
+
|
|
121
|
+
## Feet and toes
|
|
122
|
+
|
|
123
|
+
- The conform stage never aims the feet and authors the toes ground-flat at canonical rest. Clips
|
|
124
|
+
then keep the soles flat only if the ankle sits where the canonical foot pitch expects it.
|
|
125
|
+
- Iron Man's fix was to re-pose the source feet by **yaw only**, so the soles stayed flat.
|
|
126
|
+
- The visual-QA avatar route **warns** when the toes point more than 75° off forward. Meshy's
|
|
127
|
+
foot-joint placement typically lands about 25° off, which is inside the gate but visible in close-up.
|
|
128
|
+
Check the feet in the walk frames.
|
|
129
|
+
|
|
130
|
+
## First-person hide
|
|
131
|
+
|
|
132
|
+
- The camera hides meshes whose names contain an entry in `scene.extras.helixAvatar.firstPersonHide`
|
|
133
|
+
(up to 64 names).
|
|
134
|
+
- With no list at all, the runtime falls back to `["FaceMesh"]`.
|
|
135
|
+
- `helix character import` writes no list, and the preflight then warns `no-first-person-hide`.
|
|
136
|
+
Write `{ "firstPersonHide": ["FaceMesh"] }` yourself, plus anything else in front of the eyes.
|
|
137
|
+
- The VRM route writes the list for you.
|
|
138
|
+
- A helmet, hood or hair mesh in front of the eyes must be listed too, on **every scene**. Daft Punk
|
|
139
|
+
lists `["HelmetFaceMesh", "FaceMesh"]`.
|
|
140
|
+
- Dynamics keep running while a mesh is hidden.
|
|
141
|
+
|
|
142
|
+
## Face and dynamics manifests
|
|
143
|
+
|
|
144
|
+
| Manifest | Location | Contract |
|
|
145
|
+
| --- | --- | --- |
|
|
146
|
+
| Face | `scene.extras.helixFace` | `helix/avatar-face@1`: see the `face` reference and `read_doc({ name: "avatar-face" })` |
|
|
147
|
+
| Dynamics | `scene.extras.helixDynamics` | `helix/dynamics@1`: see `dynamics` |
|
|
148
|
+
|
|
149
|
+
Both must ride the GLB **at publish**. Nothing at runtime adds them later.
|
|
150
|
+
|
|
151
|
+
## The import receipt
|
|
152
|
+
|
|
153
|
+
`helix character import` writes `<name>.avatar-import.json` (`helix/avatar-import-receipt@1`). It
|
|
154
|
+
records:
|
|
155
|
+
- the source counts and sha256;
|
|
156
|
+
- the output sha256;
|
|
157
|
+
- LOD triangle counts;
|
|
158
|
+
- the face capability;
|
|
159
|
+
- the bone map;
|
|
160
|
+
- `conformed.restAimed` and `jointsRebuilt`;
|
|
161
|
+
- the KTX2 modes;
|
|
162
|
+
- `splitFace`.
|
|
163
|
+
|
|
164
|
+
Pass `--provenance <json>` with `sourceUrl`, `license` (SPDX) and `licenseUrl` (optionally
|
|
165
|
+
`attribution`, `author`, `model`, `version`) so the receipt pins where the source came from. Keep the
|
|
166
|
+
receipt in your ledger: it is the record of what you shipped.
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# Dynamics: physics bones for hair, cloth, tails, ears and soft tissue
|
|
2
|
+
|
|
3
|
+
Secondary motion is what makes an avatar read as a character instead of a mannequin. On HELIX it is
|
|
4
|
+
**data in the GLB**, solved by the engine. You never write a solver, and nothing about it is
|
|
5
|
+
networked: every client simulates the chains locally.
|
|
6
|
+
|
|
7
|
+
**Do this last, on the imported file.** `helix character import` rebuilds the skeleton to the 68
|
|
8
|
+
canonical joints (`transforms.conformed.jointsRebuilt: 68` in the receipt), so helper or physics
|
|
9
|
+
joints in a non-VRM source do not survive the import. Add dynamics to the imported `*.web.glb`, then
|
|
10
|
+
publish that file.
|
|
11
|
+
|
|
12
|
+
## The contract: `helix/dynamics@1`
|
|
13
|
+
|
|
14
|
+
The declaration lives in `scene.extras.helixDynamics`. Write an **identical copy on every LOD
|
|
15
|
+
scene.** A manifest on LOD0 only drops physics on the lower levels.
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"contract": "helix/dynamics@1",
|
|
20
|
+
"chains": [{
|
|
21
|
+
"id": "hair-long-l",
|
|
22
|
+
"root": "hair_long_l_1",
|
|
23
|
+
"importance": "high",
|
|
24
|
+
"stiffness": [0.6, 0.42],
|
|
25
|
+
"damping": [0.8, 0.75],
|
|
26
|
+
"inertia": [0.7, 0.65],
|
|
27
|
+
"gravity": 0.04,
|
|
28
|
+
"limit": { "kind": "cone", "maxDegrees": [18, 22] },
|
|
29
|
+
"radius": 0.012,
|
|
30
|
+
"colliders": ["head", "shoulders"],
|
|
31
|
+
"endpoint": [-0.085, 0, 0]
|
|
32
|
+
}],
|
|
33
|
+
"colliders": [
|
|
34
|
+
{ "id": "head", "bone": "head", "shape": "sphere", "offset": [0.09, 0, 0], "radius": 0.115 },
|
|
35
|
+
{ "id": "shoulders", "bone": "spine_04", "shape": "capsule", "offset": [0.04, 0, 0], "tail": [0.21, 0, 0], "radius": 0.085 }
|
|
36
|
+
],
|
|
37
|
+
"lod": { "minPixels": 14 }
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Top level
|
|
42
|
+
|
|
43
|
+
The only allowed keys are `contract`, `chains`, `colliders` and `lod`.
|
|
44
|
+
|
|
45
|
+
### Chain fields
|
|
46
|
+
|
|
47
|
+
| Field | Required | Meaning |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `id` | yes | Stable and authored, `[A-Za-z0-9_.-]{1,64}`, unique in the manifest. Never derive it from a bone path. |
|
|
50
|
+
| `root` | yes | The first **simulated** bone. Its parent is the anchor and never moves. |
|
|
51
|
+
| `importance` | yes | `critical`, `high`, `normal` or `decorative`. It ranks the chain in the frame budget. |
|
|
52
|
+
| `stiffness` | yes | 0..1, mapped to ω = 2 + 38·t² rad/s. 2 is a heavy cape; 40 is a taut wire. |
|
|
53
|
+
| `damping` | yes | 0..1. It is the damping ratio, so 1 is critical (no overshoot). |
|
|
54
|
+
| `inertia` | no, default 0 | 0..1. The share of the anchor's motion the chain carries without generating velocity. 1 is carried rigidly. This field is what stops hair flying backwards when running. |
|
|
55
|
+
| `gravity` | no, default 0 | −8..8, a multiplier on 9.81 m/s². Leave it out and nothing droops. |
|
|
56
|
+
| `gravityDir` | no, default (0, −1, 0) | A world-space vector. |
|
|
57
|
+
| `limit` | no, default none | `{ "kind": "cone", "maxDegrees": … }` or `{ "kind": "hinge", "maxDegrees": …, "axis": [x, y, z] }`, with degrees 0..180. A hinge needs `axis`. There is no polar limit. |
|
|
58
|
+
| `radius` | yes if `colliders` is non-empty | The per-joint collision radius in metres. |
|
|
59
|
+
| `colliders` | no | Up to 8 collider ids, each declared in `colliders`. |
|
|
60
|
+
| `endpoint` | in practice, yes | The tip offset in the **last joint's local frame**. Without it, the last joint has nothing to aim at and the chain ends one bone early. |
|
|
61
|
+
| `exclude` | no | Bone names to skip while walking the chain. |
|
|
62
|
+
|
|
63
|
+
**Curves.** `stiffness`, `damping`, `inertia`, `radius` and `limit.maxDegrees` each take either a
|
|
64
|
+
scalar or an array of 1–8 evenly spaced stops from root to tip. `[0.8, 0.3]` gives stiff-at-the-scalp,
|
|
65
|
+
loose-at-the-tip hair.
|
|
66
|
+
|
|
67
|
+
**How a chain is walked.** From `root`, the solver follows single bone children. **A branch point
|
|
68
|
+
ends the chain**, so give a skirt one chain per panel. It treats bones as spheres at the tail, not as
|
|
69
|
+
swept capsules.
|
|
70
|
+
|
|
71
|
+
**Do not root a chain on these:**
|
|
72
|
+
- a bone a face channel drives;
|
|
73
|
+
- a ragdoll bone;
|
|
74
|
+
- a twist-driver bone.
|
|
75
|
+
|
|
76
|
+
Two writers on one quaternion is undefined. Your own auxiliary joints are always safe.
|
|
77
|
+
|
|
78
|
+
### Collider fields
|
|
79
|
+
|
|
80
|
+
| Field | Meaning |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `id` | Required and unique. |
|
|
83
|
+
| `bone` | Required. Any bone in the skeleton, usually canonical: `head`, `spine_03`–`spine_05`, `pelvis`, `thigh_l`/`thigh_r`, `upperarm_*`. |
|
|
84
|
+
| `shape` | `sphere`, `capsule` or `plane`. |
|
|
85
|
+
| `offset` | Bone-local metres. |
|
|
86
|
+
| `radius` | Required for a sphere or capsule. |
|
|
87
|
+
| `tail` | **Required for a capsule** and **forbidden on a sphere**. It is the far end, in bone-local metres. |
|
|
88
|
+
| `normal` | A plane only. A plane takes no `radius` and no `tail`. |
|
|
89
|
+
| `inside` | Boolean. It keeps joints inside the shape instead of out of it. |
|
|
90
|
+
|
|
91
|
+
Colliders are never generated for you. Declare only the colliders a chain uses: **collision checks
|
|
92
|
+
(joints × colliders, summed over chains) are the cost that explodes.**
|
|
93
|
+
|
|
94
|
+
### Axes: every vector is in the bone's own frame
|
|
95
|
+
|
|
96
|
+
`endpoint`, `offset`, `tail` and `normal` are **bone-local** (`gravityDir` alone is world space), and on `helix-humanoid@1` bone-local is not world-aligned. These
|
|
97
|
+
are the measured bind frames:
|
|
98
|
+
|
|
99
|
+
| Bones | local +X | local +Y | local +Z |
|
|
100
|
+
| --- | --- | --- | --- |
|
|
101
|
+
| `pelvis`, `spine_01`–`spine_05`, `neck_*`, `head` | up | the character's **right** (world −X) | forward (world +Z) |
|
|
102
|
+
| Limb bones | along the bone, toward its child | | |
|
|
103
|
+
|
|
104
|
+
A new auxiliary joint takes its parent's bind orientation. So:
|
|
105
|
+
- a strand hanging straight down from `head` has `endpoint` `[-L, 0, 0]`, and about that from
|
|
106
|
+
`spine_05`, which leans a few degrees;
|
|
107
|
+
- a sphere 9 cm above the head bone is `offset` `[0.09, 0, 0]`.
|
|
108
|
+
|
|
109
|
+
**Compute every vector from LOD0's inverse bind matrices**: local = inverse(bind world of the bone) ×
|
|
110
|
+
world point. Do not type them by hand.
|
|
111
|
+
|
|
112
|
+
Earlier avatars wrote `[0, -L, 0]`. That includes the anime lane's values and the engine's own
|
|
113
|
+
reference build. On this skeleton it points the tip **sideways**, to the character's left. The strict
|
|
114
|
+
parse accepts it, so nothing warns. The values in this file are axis-corrected.
|
|
115
|
+
|
|
116
|
+
### The parse is strict, and failure is silent
|
|
117
|
+
|
|
118
|
+
The engine's reader returns `null` for the **whole** manifest when it meets any of these:
|
|
119
|
+
- an unknown key at any level;
|
|
120
|
+
- a value out of range;
|
|
121
|
+
- a duplicate id;
|
|
122
|
+
- a collider id a chain names but the manifest does not declare;
|
|
123
|
+
- `colliders` without `radius`;
|
|
124
|
+
- a hinge with no axis;
|
|
125
|
+
- a capsule with no tail, or a sphere with a tail.
|
|
126
|
+
|
|
127
|
+
When that happens the avatar loads with **no physics at all**. The only signal is one browser console
|
|
128
|
+
line:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
[dynamics] ignored a malformed helixDynamics block on: <name>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Nothing checks the manifest offline:
|
|
135
|
+
- The publish preflight only counts chains, and warns `no-dynamics` when there are none.
|
|
136
|
+
- The backend does not validate the manifest at upload.
|
|
137
|
+
|
|
138
|
+
So check it yourself before publishing with the engine's own parser (step 6 below), and watch the
|
|
139
|
+
console in the live preview.
|
|
140
|
+
|
|
141
|
+
**Caps.** If a count is over its cap, the whole manifest is dropped.
|
|
142
|
+
|
|
143
|
+
| Cap | Value |
|
|
144
|
+
| --- | --- |
|
|
145
|
+
| Chains | 64 |
|
|
146
|
+
| Joints per chain | 32 |
|
|
147
|
+
| Total joints | 192 |
|
|
148
|
+
| Colliders | 64 |
|
|
149
|
+
| Colliders per chain | 8 |
|
|
150
|
+
| Collision checks | 1024 |
|
|
151
|
+
|
|
152
|
+
Real avatars are many short chains (VRoid ears and tail: 48 chains, 128 joints, 352 checks), and
|
|
153
|
+
that is fine. The runtime enforces a scene-wide frame budget: it lowers rate (60 → 30 → 15 Hz) and
|
|
154
|
+
only then switches a chain off, ranked by `importance` and screen size.
|
|
155
|
+
|
|
156
|
+
`lod.minPixels` passes the parse but **nothing in the runtime reads it today**. Every lane writes
|
|
157
|
+
`14`; keep doing so for forward compatibility.
|
|
158
|
+
|
|
159
|
+
## Where the bones come from
|
|
160
|
+
|
|
161
|
+
| Source | What you do |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| **VRM** | `bridge_import` converts SpringBone to this contract for you. Audit the result; see `source-vrm`. |
|
|
164
|
+
| **Separate hair, ears or tail** that you can sell or swap | Make it a character **add-on** that carries its own bones and `helixDynamics`. The engine collects every manifest on the dressed character by traversal. When a chain id is duplicated, **the declaration attached later wins** and the other is dropped, so an add-on beats the body that wears it. |
|
|
165
|
+
| **Everything else**: Meshy, Dreamer, game rips, hand-made | Insert auxiliary joints and author the manifest yourself, as below. |
|
|
166
|
+
|
|
167
|
+
## Authoring chains on a non-VRM avatar
|
|
168
|
+
|
|
169
|
+
**No CLI or MCP command does this yet.** That is a known gap: `helix-creator-cli` issue #295
|
|
170
|
+
proposes `helix character dynamics apply|validate`. Until it lands, write a small
|
|
171
|
+
`@gltf-transform/core` script that follows these steps exactly. Every one of them is a failure that
|
|
172
|
+
already shipped.
|
|
173
|
+
|
|
174
|
+
1. **Choose the chains from the geometry, not from the wish list.**
|
|
175
|
+
- A strand, lock or panel must exist as its own surface, or at least as a separable band of
|
|
176
|
+
vertices.
|
|
177
|
+
- Meshy and Dreamer output is **one fused shell**: hair, hood, face and body are welded. You can
|
|
178
|
+
move the hair band, but only if the transition into the hood or neck stretches smoothly.
|
|
179
|
+
- Plan the chains you can move without tearing:
|
|
180
|
+
|
|
181
|
+
| Feature | Chains | Joints per chain |
|
|
182
|
+
| --- | --- | --- |
|
|
183
|
+
| Back hair fall | 3–11 | 2–4 |
|
|
184
|
+
| Long side locks | 1 each | 2 |
|
|
185
|
+
| Bob hair | 4 | 1 |
|
|
186
|
+
| Skirt | 1 per panel | 3 |
|
|
187
|
+
| Tail | 1 | 4–6 |
|
|
188
|
+
| Ears | 1 each | 1–2 |
|
|
189
|
+
| Soft tissue | breast or glute pair | 1 |
|
|
190
|
+
|
|
191
|
+
2. **Select the vertices per LOD.** Each LOD has its own geometry, so compute the selection on each
|
|
192
|
+
one. Two methods have worked:
|
|
193
|
+
- **Anatomical sphere-steal.** Use it when the region is spatially distinct: soft tissue, a bob
|
|
194
|
+
clear of the shoulders. Give each joint a sphere (`center`, `radius`, `maxWeight`). Take weight
|
|
195
|
+
**proportionally and only from a declared `stealFrom` set of bones**, so a wrist passing through
|
|
196
|
+
the chest at bind time donates nothing. Values that shipped are in the table below.
|
|
197
|
+
- **Mask and weight field.** Use it on a fused shell. Sphere-steal grabbed the hood on the hoodie
|
|
198
|
+
avatar. Classify hair vertices from that LOD's own base colour at each UV:
|
|
199
|
+
- dark means luminance below 0.30;
|
|
200
|
+
- exclude a face box (eyes, brows and lashes are dark too);
|
|
201
|
+
- require a height band below the crown;
|
|
202
|
+
- keep a vertex only if more than 50 % of its welded neighbours are also hair. That removes
|
|
203
|
+
stray dark texels on clothing.
|
|
204
|
+
|
|
205
|
+
Then build a smooth weight field (smoothstep between head and joint 1, and between joint 1 and
|
|
206
|
+
joint 2, with lateral hat functions between neighbouring chains). Blend it in through a
|
|
207
|
+
"dynamicness" scalar diffused 3 rings over the welded mesh graph, so the boundary stretches
|
|
208
|
+
instead of tearing. Classify from a PNG import (`--no-ktx2`) of byte-identical geometry when the
|
|
209
|
+
KTX2 colour is not readable.
|
|
210
|
+
|
|
211
|
+
3. **Insert the joints into every LOD skin with identical names.**
|
|
212
|
+
- At runtime, lower LODs are rebound onto LOD0's skeleton by bone name, and springs are solved
|
|
213
|
+
once, on LOD0's bones. Yet the file must still give each LOD scene its own skeleton copy (see
|
|
214
|
+
`contract`).
|
|
215
|
+
- Place joint heads in bind space, in metres, in the canonical frame (+Y up, +Z forward,
|
|
216
|
+
character left = +X).
|
|
217
|
+
- New joints take their parent's bind orientation.
|
|
218
|
+
- **Pick the anchor that the geometry actually rides on.**
|
|
219
|
+
- The hoodie's back hair lies on the hood, and the source had bound that region to the
|
|
220
|
+
clavicles, upper arms and `spine_03`. Hung from `head`, the arm swing dragged it.
|
|
221
|
+
- Hung from `spine_05`, it rides the torso and only lags.
|
|
222
|
+
- Hair that sits on the skull hangs from `head`.
|
|
223
|
+
- Soft tissue hangs from `spine_04` (breasts) or `pelvis` (glutes).
|
|
224
|
+
|
|
225
|
+
4. **Write the weights.**
|
|
226
|
+
- Keep at most 4 influences per vertex and renormalise to a sum of 1. When blending hair weight
|
|
227
|
+
into a vertex that already has 4 influences, keep the 4 largest after blending: the hair joints
|
|
228
|
+
displace the smallest body influences, never `head` or `spine_05`.
|
|
229
|
+
- **Rewrite `JOINTS_0` and `WEIGHTS_0` in place.** Appending new accessors leaves the old
|
|
230
|
+
interleaved bytes in the buffer and pushed one avatar over the 10 MiB cap.
|
|
231
|
+
- **Body and `FaceMesh` primitives share one `POSITION` accessor**, and usually their
|
|
232
|
+
`JOINTS_0` and `WEIGHTS_0` too. `FaceMesh` is an index-only slice of the same vertex buffer.
|
|
233
|
+
Track the accessors you have already processed. A refit applied once per primitive moved one
|
|
234
|
+
avatar's hair twice as far as intended, so that version had to be rejected.
|
|
235
|
+
|
|
236
|
+
5. **Write the manifest on every scene.**
|
|
237
|
+
- `endpoint` and collider offsets are in each bone's local frame. Compute them from LOD0's
|
|
238
|
+
inverse bind matrices and reuse them for every LOD.
|
|
239
|
+
- Chain and joint names must be identical in every LOD.
|
|
240
|
+
|
|
241
|
+
6. **Check before publishing, using the engine's own parser.**
|
|
242
|
+
- A throwaway character world gives you the engine's own module:
|
|
243
|
+
`helix init /tmp/avatar-harness --kind character`, then `npm install`, then `helix install`.
|
|
244
|
+
It is the same harness as `qa` step 3.
|
|
245
|
+
- Its `readDynamicsManifest` is the strict parser the runtime uses. It cannot tell a sideways
|
|
246
|
+
endpoint from a downward one, so check the axes yourself (see "Axes"). Run this from the harness
|
|
247
|
+
directory:
|
|
248
|
+
|
|
249
|
+
```js
|
|
250
|
+
// Offline helix/dynamics@1 check with the engine's own strict parser (from a scratch world's `helix install`).
|
|
251
|
+
import { readFileSync } from 'node:fs';
|
|
252
|
+
const { readDynamicsManifest } = await import('./public/helix_modules/humanoid-character/index.js');
|
|
253
|
+
const glb = readFileSync(process.argv[2]);
|
|
254
|
+
const json = JSON.parse(glb.subarray(20, 20 + glb.readUInt32LE(12)).toString('utf8'));
|
|
255
|
+
const scenes = json.scenes ?? [];
|
|
256
|
+
const skins = (json.skins ?? []).map((s) => new Set(s.joints.map((j) => json.nodes[j].name)));
|
|
257
|
+
const raw0 = JSON.stringify(scenes[0]?.extras?.helixDynamics);
|
|
258
|
+
let ok = scenes.length > 0;
|
|
259
|
+
scenes.forEach((scene, i) => {
|
|
260
|
+
const raw = scene.extras?.helixDynamics;
|
|
261
|
+
const m = readDynamicsManifest(raw);
|
|
262
|
+
if (!m) { ok = false; return console.log(`scene ${i} ${scene.name}: ${raw ? 'REJECTED by the strict parse' : 'no helixDynamics'}`); }
|
|
263
|
+
if (JSON.stringify(raw) !== raw0) { ok = false; console.log(`scene ${i} ${scene.name}: differs from scene 0`); }
|
|
264
|
+
const names = [...m.chains.flatMap((c) => [c.root, ...(c.exclude ?? [])]), ...(m.colliders ?? []).map((c) => c.bone)];
|
|
265
|
+
skins.forEach((joints, k) => names.filter((n) => !joints.has(n)).forEach((n) => { ok = false; console.log(`skin ${k}: missing joint ${n}`); }));
|
|
266
|
+
const checks = m.chains.reduce((sum, c) => sum + (c.colliders?.length ?? 0), 0);
|
|
267
|
+
console.log(`scene ${i} ${scene.name}: ${m.chains.length} chains, ${m.colliders?.length ?? 0} colliders, ${checks} collider refs`);
|
|
268
|
+
});
|
|
269
|
+
process.exit(ok ? 0 : 1);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
- Exit 0 means every LOD scene carries an identical manifest that the runtime accepts, and every
|
|
273
|
+
named bone exists in every skin.
|
|
274
|
+
- Also check that weights sum to 1 with at most 4 influences, and that the dry run is still inside
|
|
275
|
+
the triangle, draw and byte budgets.
|
|
276
|
+
|
|
277
|
+
7. **Measure it moving.** Run the motion probe in `qa` step 3. It drives the engine's `DynamicsLayer` on the platform's own walk and run clips, and reports the swing of every auxiliary bone. A chain that parses can still smear, so render the run frames too.
|
|
278
|
+
|
|
279
|
+
## Worked layout: long straight hair on a fused shell
|
|
280
|
+
|
|
281
|
+
This is the commonest request, and the one with the most ways to go wrong.
|
|
282
|
+
|
|
283
|
+
1. **Make a mask twin.** Import the same source a second time with the same flags plus `--no-ktx2`.
|
|
284
|
+
- The geometry, UVs and vertex order come out byte-identical. Assert that `POSITION` matches.
|
|
285
|
+
- A vertex mask computed on the twin then indexes the KTX2 file directly.
|
|
286
|
+
- The twin is about twice the size. Never publish it.
|
|
287
|
+
- Its textures are JPEG or PNG. Decode them in Node with `sharp` or `jpeg-js`.
|
|
288
|
+
2. **Find the hair band.** Classify hair vertices on every LOD (step 2 above).
|
|
289
|
+
- Histogram them in 5 cm height bands, and across x behind the head.
|
|
290
|
+
- The free hang starts where the hair leaves the skull, below the occiput.
|
|
291
|
+
- It ends at the lowest band that still holds hair.
|
|
292
|
+
3. **Lay out the chains.**
|
|
293
|
+
- Use 3–5 columns across the back fall, about 6–7 cm apart. The hoodie used 3, at x = −0.065,
|
|
294
|
+
0 and +0.065.
|
|
295
|
+
- Give each column 2 joints:
|
|
296
|
+
- joint 1 at the top of the free hang;
|
|
297
|
+
- joint 2 about halfway down;
|
|
298
|
+
- the tip at the hair's end, as `endpoint`.
|
|
299
|
+
- Place the joints just inside the hair surface, on its midline. The hoodie's joints sat at
|
|
300
|
+
y 1.47 and 1.37 with the tip at 1.26, z about −0.13 to −0.15.
|
|
301
|
+
4. **Choose the anchor.**
|
|
302
|
+
- Hang from `head` when the fall hangs free of clothing.
|
|
303
|
+
- Hang from `spine_05` when its lower part rests on a hood, collar or back.
|
|
304
|
+
5. **Front locks.** On a fused shell, the front locks sit inside `FaceMesh`, the head slice. If you
|
|
305
|
+
add no chain, they stay rigid on `head`. From the front they are the most visible hair, so if the
|
|
306
|
+
user asked for visible sway, say plainly when you leave them rigid, and why.
|
|
307
|
+
- That is the right call on a hood or high collar: they smeared there.
|
|
308
|
+
- With open shoulders, give each side one 2-joint long-lock chain, with the `head` and
|
|
309
|
+
`shoulders` colliders from the table below.
|
|
310
|
+
6. **Pick the feel, then measure it.** The hood set below is restrained on purpose: it reads 6–9°,
|
|
311
|
+
which is barely visible. If the user wants visible sway and nothing is in the way, start from the
|
|
312
|
+
long-lock values. Then:
|
|
313
|
+
- run the `qa` step 3 probe;
|
|
314
|
+
- render side and back frames while running;
|
|
315
|
+
- check nothing passes through the shoulders or back.
|
|
316
|
+
|
|
317
|
+
## Parameters that shipped, and why
|
|
318
|
+
|
|
319
|
+
| Use | stiffness | damping | inertia | gravity | limit | Notes |
|
|
320
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
321
|
+
| Back hair on a fused Meshy shell (3 chains, 2 joints each; anchor `spine_05`) | [0.78, 0.70] | [0.88, 0.85] | [0.85, 0.80] | 0.02 | cone [7, 9]° | `radius` 0.012. Collider `upper-back`: a capsule on `spine_05` spanning bind-space y 1.30–1.47 m just behind the spine, radius 0.10 (convert the two ends to `spine_05`-local `offset` and `tail`). About 8° of swing walking and 9° running. Live on Cream Hoodie Anime Girl. |
|
|
322
|
+
| Long side locks (2 joints; anchor `head`) | [0.6, 0.42] | [0.8, 0.75] | [0.7, 0.65] | 0.04 | cone [18, 22]° | `radius` 0.012. Colliders: `head` (sphere, `offset` [0.09, 0, 0], r 0.115) and `shoulders` (capsule on `spine_04`, `offset` [0.04, 0, 0], `tail` [0.21, 0, 0], r 0.085). Axis-corrected: they shipped as `[0, y, 0]`. |
|
|
323
|
+
| Bob hair (4 chains, 1 joint; anchor `head`) | 0.54 | 0.4 | 0.72 | 0.035 | cone 30° | Sphere-steal from `head`: radius 0.078, `maxWeight` 0.8. |
|
|
324
|
+
| Breasts (anchor `spine_04`) | 0.85 | 0.9 | 0.9 | 0.008 | cone 7° | Sphere radius about 0.09, `maxWeight` 0.45. `stealFrom` `spine_01`, `spine_03`, `spine_04`, `spine_05` and the clavicle on that side. Earlier values (0.52 / 0.43 / 0.62, cone 18°) were too loose. |
|
|
325
|
+
| Glutes (anchor `pelvis`) | 0.8 | 0.9 | 0.9 | 0.008 | cone 7° | Sphere radius about 0.095, `maxWeight` 0.45. `stealFrom` `pelvis`, `spine_01` and the thigh on that side. |
|
|
326
|
+
| Short ears (8 cm, 1–2 joints) | 1.0 | 0.5 | 0.97 | – | cone 22° | 13° walking, 16° turning and 22° sprinting, at every frame rate. With inertia 0.6, damping 0.17 and no limit, the ears swung 100°+. |
|
|
327
|
+
| Skirt panel (engine reference, 3 joints; anchor `pelvis`) | [0.62, 0.42] | [0.78, 0.66] | [0.72, 0.5] | 0.55 | hinge 24° about the panel's tangent | Colliders: hips and both thighs. A cone lets panels swing sideways like tentacles. Panels must overlap, because neighbours swing independently. |
|
|
328
|
+
| Ribbon hair (engine reference, 4 joints) | [0.72, 0.30] | [0.5, 0.28] | [0.55, 0.2] | 0.9 | cone [30, 55]° | Free strands with generated ribbon geometry, not hair glued to a shell. |
|
|
329
|
+
|
|
330
|
+
The rules behind those numbers:
|
|
331
|
+
|
|
332
|
+
- **Short chains want** inertia near 1, stiffness near the top of the range, damping about 0.5, and
|
|
333
|
+
a cone limit as the guarantee. A frame-rate-independent solver does not make a loose spring stiff.
|
|
334
|
+
- **Hair modelled already hanging needs almost no gravity.** Its rest pose is already the drape, and
|
|
335
|
+
real gravity pulls the tips into the back or hood. The lanes used 0.02–0.04. Free ribbons that rest
|
|
336
|
+
horizontal are the exception that wants about 0.9.
|
|
337
|
+
- **Soft tissue should lag, not swing.** Use damping near 0.9, a small cone, and a `maxWeight` of
|
|
338
|
+
0.45 or less, so the surface never detaches.
|
|
339
|
+
- **Long chains want high inertia.** On a 13 cm two-joint back-hair chain, measured with the `qa`
|
|
340
|
+
probe:
|
|
341
|
+
- inertia 0.7 / 0.65 lagged into the cone limit even walking, which reads as hair stuck out
|
|
342
|
+
behind, not swaying;
|
|
343
|
+
- 0.9 / 0.85 separated walking (about 10–25°) from running.
|
|
344
|
+
Below about 0.8, a chain on a moving body lags steadily rather than swinging.
|
|
345
|
+
- **Tune for the run, not the idle.** Every smear in the record appeared while running.
|
|
346
|
+
|
|
347
|
+
## Failure modes and fixes
|
|
348
|
+
|
|
349
|
+
| Symptom | Cause | Fix |
|
|
350
|
+
| --- | --- | --- |
|
|
351
|
+
| No motion, and the preview console shows `[dynamics] ignored a malformed helixDynamics block` | The strict parse rejected the manifest: an unknown key, colliders without radius, a missing collider id, and so on. | Fix the field. One bad field kills every chain. |
|
|
352
|
+
| No motion, no warning | The manifest is on LOD0 only, or the root bone is missing from the skin, or the chain roots on a bone that has no weighted descendants. | Write it on every scene. Check joint names in every skin. |
|
|
353
|
+
| Front locks smear into the hood or collar while running | A fused shell, with front hair resting on the collar. | **Drop front chains on hooded or high-collar avatars.** The hoodie shipped back hair only. |
|
|
354
|
+
| Hair detaches from the back and leaves a gap while walking | A rest gap between the lock and the body, plus a loose cone. | Pull the lock toward the body in the source (a one-off rest warp; 9 cm closed a 4.5–7 cm gap). Tighten the cone, and add a `shoulders` collider. The measured walking gap fell from 9.8 to 3.2 cm. |
|
|
355
|
+
| Hair dragged by arm swing | The anchor or the source weights tie hair to the arms or clavicles. | Re-anchor on the bone the hair actually rests on, and strip arm weight from the hair band. |
|
|
356
|
+
| Ears, tail or hair swing 90°+ when sprinting | Inertia too low (0.6 is what the VRM importer writes for a `center` node), and no limit. | Use inertia 0.95–1, stiffness 1.0, and a cone limit. |
|
|
357
|
+
| Chest dents or bulges with arm swing | Upper-arm weight on torso vertices (Meshy auto-rig). | Fix the weights before adding dynamics. See `source-generated`. |
|
|
358
|
+
| Goes bald, or stops moving, at distance | The lower LOD skins lack the joints or the weights. | Identical joints and weights on every LOD. |
|
|
359
|
+
| An add-on's tuning update is ignored | A duplicate chain id, so the body's copy won. | Engine 0.3.156+ makes the later-attached declaration win. Keep ids unique unless an override is intended. |
|
|
360
|
+
| Ears grafted after spawn never move | Engine ≤ 0.3.146 scanned for chains only at construction. | Fixed in 0.3.147. Check the engine version the world resolves. |
|
|
361
|
+
| Swing differs at 30 and 60 fps | Engine before 0.3.154. | Fixed by the first-order-hold solver. Confirm the version your world resolved (`^0.3` serves the promoted one); if it is older, get the fix promoted — never exact-pin helix.json. |
|
|
362
|
+
|
|
363
|
+
## Changing dynamics after publish
|
|
364
|
+
|
|
365
|
+
Package Versions are immutable. Changing any value means a new Package Version and a version move;
|
|
366
|
+
see `publish`. On a package-backed avatar, the character pin-correction route accepts only stiffness
|
|
367
|
+
and damping changes, not inertia or limit. **Get the values right before the first publish.**
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Faces: lip-sync, blink and expressions
|
|
2
|
+
|
|
3
|
+
The full contract is `read_doc({ name: "avatar-face" })`. Read it before you decide what the face
|
|
4
|
+
does. This page covers the avatar-specific decisions.
|
|
5
|
+
|
|
6
|
+
**The rule:** a face works only if `scene.extras.helixFace` (`helix/avatar-face@1`) rides the GLB
|
|
7
|
+
**at publish**. Nothing adds one later. With no face:
|
|
8
|
+
- the avatar stands in voice chat with a closed mouth;
|
|
9
|
+
- it never blinks;
|
|
10
|
+
- no error is raised. The preflight only warns `no-face`.
|
|
11
|
+
|
|
12
|
+
## What each source gets
|
|
13
|
+
|
|
14
|
+
| Source | Face |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| VRM | Automatic, through `bridge_import`: capability `vowel5`, both blinks, and the six expressions. No `jawOpen`, on purpose: binding it to the "aa" morph opened the A shape on every vowel. |
|
|
17
|
+
| GLB or FBX whose morphs use established names (`viseme_*`, `vrc.v_*`, `aa`/`ih`/`ou`/`ee`/`oh`, `jawOpen`, `blinkLeft`/`Blink_L`) | Detected by `helix character import`. Read its `face:` line. |
|
|
18
|
+
| GLB or FBX with morphs named anything else (a VRoid GLB export, a game face rig) | **Mute unless you pass `faceMap`.** The map assigns HELIX semantics to the morphs or bones the asset has. The importer fails if a declared morph or bone does not exist. |
|
|
19
|
+
| Dreamer or Meshy output, rigid robots, helmets | No morphs: `face: none`. All lanes so far shipped mute. A helmet or robot reads fine mute. A human face does not. |
|
|
20
|
+
|
|
21
|
+
## The capability ladder
|
|
22
|
+
|
|
23
|
+
| Capability | What the asset needs |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `viseme15` | All 15 visemes |
|
|
26
|
+
| `vowel5` | All 5 vowels; consonants fold into vowels at runtime |
|
|
27
|
+
| `jaw` | `jawOpen` only, as a morph or a bone (`axis` and `maxDegrees` ≤ 45) |
|
|
28
|
+
| `none` | Nothing |
|
|
29
|
+
|
|
30
|
+
Blink (`blink.left` / `blink.right`) and gaze are separate channels. With them, the runtime blinks on
|
|
31
|
+
its own, about every 3.2–5.3 s, at the full LOD rung only. Without them, the eyes are dead. **The
|
|
32
|
+
runtime never fakes a blink for an asset that has no blink targets.**
|
|
33
|
+
|
|
34
|
+
## Adding a face to a mesh that has none
|
|
35
|
+
|
|
36
|
+
The cheapest honest rung is a **jaw bone**: one channel, `jawOpen`, as a bone channel on a jaw joint
|
|
37
|
+
weighted to the lower face.
|
|
38
|
+
|
|
39
|
+
- An auxiliary jaw joint follows the same rules as a dynamics joint: identical names in every LOD
|
|
40
|
+
skin, with weights rewritten in place.
|
|
41
|
+
- **A face channel and a dynamics chain must never drive the same bone.**
|
|
42
|
+
|
|
43
|
+
Blend shapes are better, but authoring 15 visemes is a modelling job. If you skip both, say in the
|
|
44
|
+
listing that the avatar does not lip-sync.
|
|
45
|
+
|
|
46
|
+
## First-person
|
|
47
|
+
|
|
48
|
+
The importer splits the head into `FaceMesh` on every LOD, and the camera hides it by default. Anything
|
|
49
|
+
else in front of the eyes must be added to `helixAvatar.firstPersonHide` on every scene: a helmet, a
|
|
50
|
+
visor, a hood brim, front hair.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Publishing an avatar Package and shipping a new version
|
|
2
|
+
|
|
3
|
+
Finish `qa` steps 1–3 before publication. Package Versions are immutable. New
|
|
4
|
+
authored content enters HELIX through a sealed Continuum Package Version; MCP
|
|
5
|
+
does not expose loose-mesh Item creation.
|
|
6
|
+
|
|
7
|
+
## 0. Prepare the avatar
|
|
8
|
+
|
|
9
|
+
Render a thumbnail from the final GLB in a headless three.js page with
|
|
10
|
+
`GLTFLoader`, `KTX2Loader`, an environment map such as `RoomEnvironment`, and a
|
|
11
|
+
front three-quarter view that shows the whole avatar. Compare with live avatars
|
|
12
|
+
from `search_items({ kind: "avatar" })`.
|
|
13
|
+
|
|
14
|
+
## 1. Seal the Character Package Version
|
|
15
|
+
|
|
16
|
+
Create a self-contained Continuum publication descriptor for the Character
|
|
17
|
+
Package and include the final GLB and its dependent objects. Seal it with
|
|
18
|
+
`publish_continuum_package` or the Creator CLI's approved Package publication
|
|
19
|
+
workflow. Package publication does not create an Item or creator copy. MCP does
|
|
20
|
+
not define Publications request or receipt fields; use the Creator workflow's
|
|
21
|
+
response as the authority for the result.
|
|
22
|
+
|
|
23
|
+
The upcoming Creator CLI commands `helix continuum publish-glb` and
|
|
24
|
+
`helix continuum make-item` are intended to seal a GLB and optionally create an
|
|
25
|
+
Item through the approved Publications workflow. Use them only after that CLI
|
|
26
|
+
release is available; consult its help for arguments and rely on its response
|
|
27
|
+
for the result. For an already existing creator-owned Item, `publish_owned_item`
|
|
28
|
+
moves it to an exact sealed Package Version after explicit content-rating
|
|
29
|
+
attestation; it does not create an Item or an offer.
|
|
30
|
+
|
|
31
|
+
Public Vault or Marketplace Listings require owner policy or explicit human
|
|
32
|
+
confirmation. Read `read_doc({ name: "continuum" })` before making one public.
|
|
33
|
+
|
|
34
|
+
## 2. Add an Item or offer only when requested
|
|
35
|
+
|
|
36
|
+
When the avatar needs to be claimable or purchasable, use the Creator's approved
|
|
37
|
+
Publications workflow to create the optional Item and Marketplace or World offer
|
|
38
|
+
from the exact sealed Package Version. A Package or Vault Listing alone creates
|
|
39
|
+
no creator copy. Do not use a loose GLB upload to create an Item.
|
|
40
|
+
|
|
41
|
+
## 3. Visual QA
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
visual_qa_item_hosted({ itemId }) // the platform renders it
|
|
45
|
+
visual_qa_item({ itemId }) // local render with a QA sign-in
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Use `visual_qa_item_status` to follow a hosted run. The avatar remains held from
|
|
49
|
+
public visibility until its current Package Version passes. Read
|
|
50
|
+
`read_skill({ name: "helix-avatar-qa" })` before the first run and after any
|
|
51
|
+
failure. The route (`avatar@3`) covers the Try Now walk and the Marketplace
|
|
52
|
+
item page's walk seen from the sides and back, and a `not measured` assert
|
|
53
|
+
fails. It does not replace checking dynamics, face, first-person hide, and
|
|
54
|
+
motion against the control body in the `qa` reference.
|
|
55
|
+
|
|
56
|
+
## 4. Refresh the thumbnail
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
item_thumbnail({ action: "refresh", itemId, dryRun: true })
|
|
60
|
+
item_thumbnail({ action: "set", itemId, imagePath: "/abs/thumbnail.png" })
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`refresh` renders from the active Package Version. `set` uploads a moderated
|
|
64
|
+
PNG, JPEG or WebP and keeps it across version moves until a forced refresh.
|
|
65
|
+
|
|
66
|
+
## 5. Ship a corrected version
|
|
67
|
+
|
|
68
|
+
Changes to weights, dynamics, textures or manifests require a new immutable
|
|
69
|
+
Character Package Version. Build and seal the version through the Creator's
|
|
70
|
+
approved Package publication workflow, pass visual QA for that exact version,
|
|
71
|
+
then move the Item to it through the Creator's approved version workflow. Do
|
|
72
|
+
not replace the mesh through a legacy Item upload route. Re-run the live
|
|
73
|
+
close-ups and motion checks from `qa` after the move.
|
|
74
|
+
|
|
75
|
+
**A dynamics-only change is still a new version.** Tune inertia and limits before
|
|
76
|
+
sealing the first version.
|