@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,294 @@
|
|
|
1
|
+
# Locomotion pack — per-clip authoring specification
|
|
2
|
+
|
|
3
|
+
*Authored 2026-08-26 against the engine's `locomotion-packs` worktree: `LocomotionAbility.ts`, `loadLocomotionPack.ts`, `AnimGraph.ts`, `ClipRegistry.ts`, the
|
|
4
|
+
character-pipeline `clips` stage, and the shipped platform set (`asset-packs/humanoid-character/anims/clip-meta.json`). Audience: a creator or an
|
|
5
|
+
animation-generation AI producing a pack without reading engine source. Companion docs: `add-on-system.md` (the carrier), `Docs/Plans/locomotion-packs-build-plan.md`
|
|
6
|
+
(the decisions this spec encodes). Numbers marked **publish** are enforced by the publish gate, which ships later than the runtime described here.*
|
|
7
|
+
|
|
8
|
+
## 1. What a locomotion pack is
|
|
9
|
+
|
|
10
|
+
A locomotion pack is a set of replacement animation clips for the humanoid character's built-in movement — a female walk, a zombie shamble, a soldier patrol. It
|
|
11
|
+
ships as a character add-on in the `anim.locomotion` slot (`character.locomotion@1`, archetype `clip-set`), and it is **data, not code**: each clip is a GLB
|
|
12
|
+
animation on the canonical `helix-humanoid` skeleton plus one metadata entry. The scope is **gait-only**: the nine core locomotion clips, the directional strafe
|
|
13
|
+
rings, and the turn-in-place steps. Deaths, get-ups, sit/lie poses and hit reactions stay platform-owned and are not part of the pack vocabulary. The governing
|
|
14
|
+
rule of the slot: a pack may change how a walk *looks*; it may never change how far that walk *gets you*. Movement speed belongs to the world's config
|
|
15
|
+
(`walkSpeed`/`runSpeed`/`sprintSpeed`); the pack's `strideSpeed` values are *measurements* of the authored motion, used for blending and anti-foot-slide, and the
|
|
16
|
+
runtime clamps them into a band around the platform reference.
|
|
17
|
+
|
|
18
|
+
**Partial packs are valid products, down to a single clip.** A pack that contains only a styled `locomotion.walk` is legitimate. At load, the pack is merged over
|
|
19
|
+
the platform default set *per clip name*: a pack clip replaces the platform clip of the same name; every name the pack does not supply falls back to the platform
|
|
20
|
+
clip. There is no completeness roster — the publish gate requires only that the pack contains at least one valid clip (**publish**), and validates the clips that
|
|
21
|
+
are present.
|
|
22
|
+
|
|
23
|
+
## 2. The pack format
|
|
24
|
+
|
|
25
|
+
### 2.1 Directory shape
|
|
26
|
+
|
|
27
|
+
A pack is served from a base URL in the platform asset-pack shape:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
<pack-base>/
|
|
31
|
+
anims/
|
|
32
|
+
clip-meta.json # array of clip metadata entries
|
|
33
|
+
locomotion.walk.glb # one GLB per clip; file name = the meta entry's full name + ".glb"
|
|
34
|
+
locomotion.run.glb
|
|
35
|
+
...
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The loader fetches `anims/clip-meta.json`, then `anims/<name>.glb` for each surviving entry. The GLB **file name must equal the entry's `name` field exactly**,
|
|
39
|
+
including the `locomotion.` prefix.
|
|
40
|
+
|
|
41
|
+
### 2.2 `clip-meta.json` schema
|
|
42
|
+
|
|
43
|
+
The file is a **JSON array** of entries. If it is unreadable or not an array, the whole pack is rejected (the platform set stands in). Per entry:
|
|
44
|
+
|
|
45
|
+
| Field | Type | Required | Semantics | When wrong |
|
|
46
|
+
|---|---|---|---|---|
|
|
47
|
+
| `name` | string | yes | Full namespaced clip name, `locomotion.<clip>`, from the vocabulary in §3. | Not a string → entry dropped with a warning. A string *outside* the `locomotion.` namespace → skipped silently (another system's clips may ride in the same file). |
|
|
48
|
+
| `loop` | boolean | yes | `true` = the clip cycles (gait loops, idles, fall); `false` = one-shot (jumpStart, land, turn steps). | Not a boolean → entry dropped with a warning. |
|
|
49
|
+
| `duration` | number | yes | Clip length in seconds. Must be finite and > 0. Must match the GLB animation's actual length. | Zero, negative, non-finite or non-numeric → entry dropped with a warning. |
|
|
50
|
+
| `strideSpeed` | number \| null | yes | Measured XZ ground speed of the source motion, m/s (§4). `null` for non-travelling clips. | Any other type → entry dropped with a warning. Out-of-band values are clamped at runtime (§4) and rejected at publish (**publish**). |
|
|
51
|
+
| `direction` | number | ring clips only | Signed travel direction in degrees, gait-forward = 0, positive = the character's left (§3.2). | Missing on a ring sibling → the clip loads but never joins the ring (it is registered and unused). |
|
|
52
|
+
| `hadRootMotion` | boolean | no | Pipeline provenance flag (the source carried root motion that was stripped). Ignored by the runtime loader. | — |
|
|
53
|
+
|
|
54
|
+
A dropped entry is a per-clip event: the rest of the pack still loads, and the dropped name falls back to the platform clip.
|
|
55
|
+
|
|
56
|
+
### 2.3 GLB requirements
|
|
57
|
+
|
|
58
|
+
- **One animation per file.** The loader takes `animations[0]`; any further animations are ignored. A GLB with no animation drops that clip with a warning.
|
|
59
|
+
- The animation's internal name is irrelevant — the loader renames it to the meta `name`. The file name and the meta entry are the contract.
|
|
60
|
+
- **Clip-only content**: the GLB needs only the node hierarchy and the animation. Meshes, skins, materials and textures are dead weight (the platform pipeline
|
|
61
|
+
strips them with `toClipOnly`).
|
|
62
|
+
- **Tracks target canonical bone names** (§5). One rotation (quaternion) track per animated bone. Only `pelvis` (and `root`) may carry translation — bone-scale
|
|
63
|
+
tracks and non-pelvis translation tracks are meaningless on the rigid skinned skeleton and are stripped by the pipeline.
|
|
64
|
+
- **Clips must arrive in-place.** The runtime loader performs no root-motion processing: a travelling gait must have its ground travel already removed (linear XZ
|
|
65
|
+
drift subtracted, or the whole root channel dropped) with the measured speed recorded in `strideSpeed`. A clip that still travels will slide the mesh away from
|
|
66
|
+
the character's capsule every cycle.
|
|
67
|
+
- Units: metres and seconds, glTF Y-up. Keyframes may be resampled/quantized (the platform pipeline resamples at 1e-4 tolerance and quantizes rotation outputs to
|
|
68
|
+
normalized int16); this is an optimisation, not a requirement.
|
|
69
|
+
|
|
70
|
+
The character-pipeline `clips` command produces all of the above from raw exported takes — including the in-place conversion, the `strideSpeed`/`direction`
|
|
71
|
+
measurement and `clip-meta.json` itself — and is the recommended lane.
|
|
72
|
+
|
|
73
|
+
## 3. The clip vocabulary
|
|
74
|
+
|
|
75
|
+
### 3.1 The core nine
|
|
76
|
+
|
|
77
|
+
These are the names the locomotion ability requires of the *merged* set (the platform always supplies them, so a pack may replace any subset). Platform reference
|
|
78
|
+
values, from the shipped set:
|
|
79
|
+
|
|
80
|
+
| Name | Loop | Platform duration (s) | Platform strideSpeed (m/s) | Runtime band (×0.5–1.5) |
|
|
81
|
+
|---|---|---|---|---|
|
|
82
|
+
| `locomotion.idle` | loop | 10 | null | — |
|
|
83
|
+
| `locomotion.walk` | loop | 4.0 | 2.049 | 1.02 – 3.07 |
|
|
84
|
+
| `locomotion.run` | loop | 2.5 | 5.096 | 2.55 – 7.64 |
|
|
85
|
+
| `locomotion.sprint` | loop | 2.0 | 7.111 | 3.56 – 10.67 |
|
|
86
|
+
| `locomotion.crouchIdle` | loop | 10 | null | — |
|
|
87
|
+
| `locomotion.crouchWalk` | loop | 3.7 | 2.303 | 1.15 – 3.45 |
|
|
88
|
+
| `locomotion.jumpStart` | one-shot | 2.338 | null | — |
|
|
89
|
+
| `locomotion.fall` | loop | 3.333 | null | — |
|
|
90
|
+
| `locomotion.land` | one-shot | 2.262 | null | — |
|
|
91
|
+
|
|
92
|
+
Per clip:
|
|
93
|
+
|
|
94
|
+
- **`idle`** — standing at rest. Loop, seamless. Sits at speed 0 in the locomotion blend; it is on-screen more than any other clip, so subtle repetition is the
|
|
95
|
+
quality bar. The platform idle is a 10 s cycle; long idles are fine.
|
|
96
|
+
- **`walk`** — the walk-tier gait, straight ahead. Loop, seamless, left/right footfalls balanced. Its `strideSpeed` is the walk blend threshold: author the style
|
|
97
|
+
at a tempo whose natural travel is near 2 m/s (§4).
|
|
98
|
+
- **`run`** — the default movement gait. Same rules; natural travel near 5 m/s.
|
|
99
|
+
- **`sprint`** — the top gait. Forward only: no sideways or backward sprint clips exist anywhere in the vocabulary (sprint input outside a ±30° forward cone
|
|
100
|
+
soft-caps to run). Natural travel near 7 m/s.
|
|
101
|
+
- **`crouchIdle`** — crouched at rest. Loop. The platform crouch idle holds the pelvis at roughly 0.43 m (standing is ~0.92 m).
|
|
102
|
+
- **`crouchWalk`** — crouched movement. Loop; the only crouched gait tier.
|
|
103
|
+
- **`jumpStart`** — launch. One-shot. Exits are physics-driven, not completion-driven (the state leaves for `airborne` as soon as vertical velocity turns
|
|
104
|
+
negative), so a long recovery tail is tolerated — but the *front* must be tight: jumps are physics-instant, and the platform pipeline trims the standing
|
|
105
|
+
anticipation to ~0.2 s before the root leaves the ground. Author with minimal wind-up.
|
|
106
|
+
- **`fall`** — airborne loop, plays while descending. Seamless.
|
|
107
|
+
- **`land`** — touchdown recovery. One-shot; must *start* at the touchdown frame (the platform pipeline trims the mid-descent lead). Movement interrupts it
|
|
108
|
+
(speed > 0.5 m/s blends straight back to the gait blend), so a long settle tail is safe.
|
|
109
|
+
|
|
110
|
+
All nine are non-travelling **except `walk`, `run`, `sprint`, `crouchWalk`**: those four must carry a numeric `strideSpeed`; the other five must carry `null`
|
|
111
|
+
(§4 — the runtime forces mismatches back to the platform's travel classification).
|
|
112
|
+
|
|
113
|
+
How the gaits blend: the grounded state is a blend space over speed with each gait positioned at its own `strideSpeed` — idle at 0, then walk, run, sprint. As the
|
|
114
|
+
character's actual speed moves through that ladder the graph cross-fades the clips and scales playback rate toward `speed / blendedThreshold`, clamped to
|
|
115
|
+
0.75–1.25×, phase-locking the non-dominant loops to the dominant one. That is why `strideSpeed` must be a real measurement: it is both the clip's position on the
|
|
116
|
+
speed axis and the anchor of the anti-foot-slide rate correction.
|
|
117
|
+
|
|
118
|
+
### 3.2 The directional rings (strafe sets)
|
|
119
|
+
|
|
120
|
+
Three gaits may carry a directional ring: **`walk`**, **`run`**, **`crouchWalk`**. The bare name is the forward clip (direction 0 by definition); siblings append
|
|
121
|
+
a direction suffix to the gait name. The full naming grid, with the platform set's values:
|
|
122
|
+
|
|
123
|
+
| Suffix | Direction (deg) | Meaning | walk (m/s) | run (m/s) | crouchWalk (m/s) |
|
|
124
|
+
|---|---|---|---|---|---|
|
|
125
|
+
| *(none)* | 0 | forward | 2.049 | 5.096 | 2.303 |
|
|
126
|
+
| `FL` | 45 | forward-left | 2.049 | 5.096 | 2.304 |
|
|
127
|
+
| `FR` | −45 | forward-right | 2.049 | 5.096 | 2.304 |
|
|
128
|
+
| `L` | 90 | left strafe | 1.844 | 3.565 | 2.049 |
|
|
129
|
+
| `R` | −90 | right strafe | 1.844 | 3.566 | 2.048 |
|
|
130
|
+
| `BL` | 135 | back-left | 1.537 | 3.059 | 1.859 |
|
|
131
|
+
| `BR` | −135 | back-right | 1.537 | 3.059 | 1.844 |
|
|
132
|
+
| `B` | 180 | backpedal | 1.537 | 3.059 | 1.843 |
|
|
133
|
+
|
|
134
|
+
(e.g. `locomotion.walkFL`, `locomotion.runB`, `locomotion.crouchWalkR`.)
|
|
135
|
+
|
|
136
|
+
**Direction semantics.** `direction` is the signed angle of the clip's source ground travel *relative to the same gait's own forward clip*, in degrees; positive
|
|
137
|
+
values are the character's **left**. It is measured, not asserted: the pipeline computes `heading − forwardHeading` from the exported root trajectories (so no
|
|
138
|
+
absolute world axis matters), rounded to 0.1°. Author to the exact ±45/±90/±135/180 grid; the fixed vocabulary above is what the publish gate accepts
|
|
139
|
+
(**publish**).
|
|
140
|
+
|
|
141
|
+
**Ring assembly rules** (what the runtime actually does):
|
|
142
|
+
|
|
143
|
+
- A sibling joins the ring only if it has **both** a numeric `strideSpeed` and a `direction`. The forward clip must itself have a numeric `strideSpeed`, or the
|
|
144
|
+
whole ring for that gait is skipped.
|
|
145
|
+
- A ring only *forms* once it has sideways coverage — at least one member with |direction| > 60°. Forward plus the two 45° diagonals alone stay one-dimensional.
|
|
146
|
+
- The standing 2D blend space activates only when **both** the walk ring and the run ring exist; the crouch ring activates the crouched 2D space on its own.
|
|
147
|
+
Without the rings the graph degrades to the 1D forward-only blend — a pack with no ring clips is perfectly valid.
|
|
148
|
+
- A **partial ring** is legal: the blend space and the per-direction speed lookup interpolate between the nearest authored directions (with wrap-around), so a
|
|
149
|
+
missing `BL` is covered by blending `L` and `B`. Coverage is graceful, not gated.
|
|
150
|
+
- Ring members' strides do gameplay-visible work in camera-facing (strafe) mode: the movement tier is scaled by the ring's `direction / forward` speed ratio, so a
|
|
151
|
+
backpedal authored slower than forward genuinely moves slower. The ratio is relative — the world's `runSpeed` config keeps authority over the forward speed.
|
|
152
|
+
- Each ring member's stride is banded against **its own platform counterpart** (e.g. `walkB` against 1.537 m/s), not against the forward clip.
|
|
153
|
+
|
|
154
|
+
Coherence note: because the merge is per clip name, replacing only a gait's forward clip leaves the platform's strafe siblings blending against your styled
|
|
155
|
+
forward. That is functional but can look mismatched — a styled gait is best shipped either forward-only (accepting platform strafes) or as the full ring.
|
|
156
|
+
|
|
157
|
+
### 3.3 Turn-in-place
|
|
158
|
+
|
|
159
|
+
Idle step-turn clips, one set per stance:
|
|
160
|
+
|
|
161
|
+
| Standing | Crouched | Loop | Platform duration (s) | strideSpeed |
|
|
162
|
+
|---|---|---|---|---|
|
|
163
|
+
| `locomotion.turnL90` | `locomotion.crouchTurnL90` | one-shot | 2.0 / 2.5 | null |
|
|
164
|
+
| `locomotion.turnR90` | `locomotion.crouchTurnR90` | one-shot | 2.0 / 2.5 | null |
|
|
165
|
+
| `locomotion.turnL180` | `locomotion.crouchTurnL180` | one-shot | 2.167 / 2.667 | null |
|
|
166
|
+
| `locomotion.turnR180` | `locomotion.crouchTurnR180` | one-shot | 2.167 / 2.667 | null |
|
|
167
|
+
|
|
168
|
+
- **One-shot rule: `loop` must be `false`.** The graph plays these as one-shots and exits on the finished event; a looping turn clip never finishes.
|
|
169
|
+
- L/R is the turn direction, 90/180 the step size. The runtime triggers a turn once the camera-to-body angle exceeds `turnInPlaceMinDeg` (default 45°) and picks
|
|
170
|
+
the 180° clip when the arc exceeds 135°, else the 90° clip; the four internal codes are 1 = L90, 2 = R90, 3 = L180, 4 = R180. The 90° steps also serve as the
|
|
171
|
+
cosmetic foot-shuffle while aiming and on network replicas.
|
|
172
|
+
- **The clips must carry no net root/pelvis yaw** — they are in-place stepping *poses*. The controller rotates the body itself, by the actual arc to the camera,
|
|
173
|
+
easing along the clip's own motion-energy curve (cumulative angular motion across all rotation tracks). Author a readable anticipation → step → settle profile:
|
|
174
|
+
the body rotates when the skeleton visibly moves, and holds through the anticipation and settle.
|
|
175
|
+
- Playback rate is retuned live from config (`turnInPlaceSpeed`, default 1.6; the aim shuffle uses `aimStepSpeed`, default 2.5) — author at natural speed.
|
|
176
|
+
- Any missing turn clip simply never fires; an absent set leaves turn-in-place inert for that stance. Partial sets are fine (e.g. 90s only).
|
|
177
|
+
|
|
178
|
+
### 3.4 Out of scope for packs
|
|
179
|
+
|
|
180
|
+
`locomotion.death*`, `locomotion.getup*`, `locomotion.sit*`, `locomotion.lie`, `locomotion.hit*` exist in the platform set but are **not** part of the pack
|
|
181
|
+
vocabulary (gait-only scope). The publish gate refuses them in a pack (**publish**); they interact with ragdoll and seating machinery that has its own gates.
|
|
182
|
+
|
|
183
|
+
## 4. `strideSpeed` — measurement, bands, and what the runtime does
|
|
184
|
+
|
|
185
|
+
**Definition.** `strideSpeed` is the horizontal (XZ) ground speed of the clip's *source* motion in metres per second: the net XZ travel of the root trajectory
|
|
186
|
+
from first to last frame, divided by the clip duration — measured **before** the clip is made in-place. Speeds at or below 0.05 m/s classify the clip as
|
|
187
|
+
non-travelling: `strideSpeed` is `null`. The character-pipeline `clips` stage measures this automatically (exactly, via the exported source root curves, when
|
|
188
|
+
`root-curves.json` is present; otherwise from the root/pelvis translation track's endpoint drift) and rounds to three decimals. Do not eyeball it and do not tune
|
|
189
|
+
it — it is a measurement the blend space and foot-slide correction depend on.
|
|
190
|
+
|
|
191
|
+
**The rules the runtime enforces at load** (collect-all; issues are logged, the pack still plays):
|
|
192
|
+
|
|
193
|
+
1. **Travel classification is fixed per name.** A clip that is non-travelling in the platform set (`idle`, `crouchIdle`, `jumpStart`, `fall`, `land`, all turn
|
|
194
|
+
clips) must have `strideSpeed: null` — a number is forced back to `null`. A clip that travels in the platform set (`walk`, `run`, `sprint`, `crouchWalk` and
|
|
195
|
+
all ring members) must have a number — `null` is replaced with the platform reference value.
|
|
196
|
+
2. **The band.** A travelling clip's stride may sit within **0.5× to 1.5× of the platform clip it replaces** (`PACK_STRIDE_BAND`): walk 1.02–3.07, run 2.55–7.64,
|
|
197
|
+
sprint 3.56–10.67, crouchWalk 1.15–3.45 m/s; ring members take their own counterpart's band. A pack-only name with no platform counterpart is bounded
|
|
198
|
+
absolutely to **0.1–15 m/s** (`PACK_STRIDE_ABSOLUTE`).
|
|
199
|
+
3. **Monotonicity.** Over the merged effective set, `walk < run < sprint` must hold (crouchWalk is outside the ladder; ring members are checked by their own
|
|
200
|
+
counterpart band instead). A violation is reported as an issue.
|
|
201
|
+
|
|
202
|
+
**Out-of-band values are clamped, not rejected.** The runtime's job is to keep foot-sliding bounded, not to police content: a genuinely styled walk can measure
|
|
203
|
+
outside the band, and it is clamped to the band edge with a logged issue. The publish gate is the strict layer — it rejects out-of-band strides and monotonicity
|
|
204
|
+
violations with a clear error (**publish**).
|
|
205
|
+
|
|
206
|
+
**Why the band matters to your style.** The character's actual travel speed is world config; the clip only decides how the motion *looks* at that speed. The
|
|
207
|
+
playback-rate correction can stretch a clip by at most ×0.75–1.25, and the band caps total stride distortion at about ×2 — beyond that, a too-slow style plays
|
|
208
|
+
visibly sped-up (and, past the rate clamp, foot-slides) rather than the world slowing down for it. Measured examples from the style-pack spike: a strut at
|
|
209
|
+
1.405 m/s or a power-walk at 2.242 m/s sit comfortably in the walk band; a creeping walk at 0.593 m/s or a stroll at 1.016 m/s sit at or below the walk floor and
|
|
210
|
+
play noticeably accelerated. **Match the style's natural tempo to the slot**: a walk-slot style should travel around 1–3 m/s at authoring time, a run-slot style
|
|
211
|
+
around 2.5–7.6 m/s. Styles slower than the band do not belong on the walk slot.
|
|
212
|
+
|
|
213
|
+
## 5. The skeleton
|
|
214
|
+
|
|
215
|
+
Clips must target the canonical **`helix-humanoid@1`** skeleton — the rest skeleton of the platform body (`body-m-default.web.glb`), snapshotted in the engine at
|
|
216
|
+
`systems/humanoid-character/src/animation/gestures/canonicalSkeleton.ts` (and mirrored in `helix-creator-cli/src/character/canonicalSkeletonData.ts`). Root bone:
|
|
217
|
+
`root`. The 68 skin joints:
|
|
218
|
+
|
|
219
|
+
- **Core:** `root`, `pelvis`, `spine_01`–`spine_05`, `neck_01`, `neck_02`, `head`
|
|
220
|
+
- **Arms (×2, `_l`/`_r`):** `clavicle`, `upperarm`, `lowerarm`, `hand`, `upperarm_twist_01/02`, `lowerarm_twist_01/02`
|
|
221
|
+
- **Fingers (×2):** `thumb_01–03`, `index_01–03`, `middle_01–03`, `ring_01–03`, `pinky_01–03`
|
|
222
|
+
- **Legs (×2):** `thigh`, `calf`, `foot`, `ball`, `thigh_twist_01`, `calf_twist_01`
|
|
223
|
+
|
|
224
|
+
The node hierarchy additionally carries per-finger metacarpal helper bones (`index/middle/ring/pinky_metacarpal_l/r`) between hand and finger chains. A clip need
|
|
225
|
+
not animate every bone — untracked bones hold their rest transform.
|
|
226
|
+
|
|
227
|
+
**Rest-pose conventions.** Author against the canonical rest pose exactly as snapshotted (the character-pipeline conform stage retargets foreign sources onto it).
|
|
228
|
+
Units are metres, Y-up. The standing pelvis sits at ~0.92 m; the platform crouch idle holds it at ~0.43 m — useful scale anchors when checking a retarget.
|
|
229
|
+
Rotation tracks carry the motion; only `pelvis` translates (the gait bob), and every conformed avatar body re-renders the same joint angles, which is what lets
|
|
230
|
+
one clip set serve every avatar.
|
|
231
|
+
|
|
232
|
+
**What breaks on a foreign rig.** The clip registry carries a bone-standard check (`ClipRegistry: '<name>' targets bones not in the skeleton standard`), but
|
|
233
|
+
today's runtime never arms it — a track targeting a bone the skeleton does not have (e.g. `mixamorigHips`, an unstripped UE source bone) is instead silently
|
|
234
|
+
dropped at binding time, so the failure mode is a clip that plays partially or not at all, with only console binding warnings to show for it. Do not rely on a
|
|
235
|
+
loud error: retarget onto the canonical skeleton and strip non-canonical bones before shipping. The publish gate enforces the skeleton contract properly
|
|
236
|
+
(**publish**).
|
|
237
|
+
|
|
238
|
+
## 6. Validation and failure behaviour
|
|
239
|
+
|
|
240
|
+
Runtime (the loader and merge), in order:
|
|
241
|
+
|
|
242
|
+
| Condition | Behaviour |
|
|
243
|
+
|---|---|
|
|
244
|
+
| `clip-meta.json` unreadable, or not a JSON array | **Whole pack rejected** (throw, caught by the pack cache): warned once, cached as failed, the platform set stands in. |
|
|
245
|
+
| Meta entry: name not a string; `loop` not boolean; `duration` not a positive finite number; `strideSpeed` not number-or-null | **Entry dropped** with a warning; that name falls back to the platform clip. |
|
|
246
|
+
| Meta entry named outside `locomotion.` | Skipped silently (permitted cohabitation). |
|
|
247
|
+
| Clip GLB fails to fetch/parse, or contains no animation | **Clip dropped** with a warning; per-name fallback to the platform clip. |
|
|
248
|
+
| `strideSpeed` out of band / wrong travel classification | **Clamped/corrected** with a logged issue (§4); the clip still plays. |
|
|
249
|
+
| `walk < run < sprint` violated in the merged set | Issue logged; no correction. |
|
|
250
|
+
| A track targets a non-canonical bone | **Throws at clip registration** — the character build fails (§5). |
|
|
251
|
+
| Ring sibling missing `direction` or `strideSpeed` | Excluded from the ring; registered but unused. |
|
|
252
|
+
| Turn/ring/optional clip absent | Feature degrades gracefully (1D blend, inert turn code) — never an error. |
|
|
253
|
+
|
|
254
|
+
The pack is loaded once per distinct base URL per page and shared across every character wearing it; validation runs per merge, against the platform set of the
|
|
255
|
+
character it is merged into.
|
|
256
|
+
|
|
257
|
+
The **publish gate** (ships later — every row here is **publish**): at least one valid clip; names restricted to the gait-only vocabulary of §3; per-clip
|
|
258
|
+
constraints on the clips present (stride bands *rejected* rather than clamped, null-stride classification, the fixed `direction` vocabulary, monotonicity among
|
|
259
|
+
the gaits the pack actually contains); the canonical-skeleton bone check; clips-only asset verification and size caps. No completeness roster.
|
|
260
|
+
|
|
261
|
+
## 7. Worked example — a minimal single-clip "styled walk" pack
|
|
262
|
+
|
|
263
|
+
A strut-style walk (measured stride 1.405 m/s — inside the walk band 1.02–3.07) replacing only `locomotion.walk`. Every other clip falls back to the platform set,
|
|
264
|
+
and the standing blend stays 1D exactly as it was.
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
strut-walk-pack/
|
|
268
|
+
anims/
|
|
269
|
+
clip-meta.json
|
|
270
|
+
locomotion.walk.glb
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`anims/clip-meta.json`:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
[
|
|
277
|
+
{
|
|
278
|
+
"name": "locomotion.walk",
|
|
279
|
+
"loop": true,
|
|
280
|
+
"duration": 4.0,
|
|
281
|
+
"strideSpeed": 1.405,
|
|
282
|
+
"hadRootMotion": true,
|
|
283
|
+
"direction": 0
|
|
284
|
+
}
|
|
285
|
+
]
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Field notes: `duration` shown is illustrative — it must equal your GLB animation's actual length in seconds (the pipeline writes the measured value);
|
|
289
|
+
`strideSpeed` is the *measured* pre-in-place travel of the strut source, not a chosen number; `direction: 0` is optional on a forward gait clip (forward is
|
|
290
|
+
direction 0 by definition) but harmless and matches the platform set's own entries; `hadRootMotion` is optional provenance. The GLB itself contains exactly one
|
|
291
|
+
animation, in-place, rotation tracks plus a pelvis translation track, targeting canonical bone names.
|
|
292
|
+
|
|
293
|
+
What the player sees: at walk tier the strut plays, rate-corrected toward the world's `walkSpeed` (2.0 m/s default → ×1.42 requested, clamped to the ×1.25
|
|
294
|
+
ceiling — close enough to read cleanly); run, sprint, crouch, jumps and turns remain the platform motion.
|
package/docs/manifest.md
CHANGED
|
@@ -19,27 +19,51 @@ Required fields (all versions): `helixVersion`, `title`, `slug`, `entry`. Unknow
|
|
|
19
19
|
| `supportsMobile` | `boolean` | `false` | Whether the world is playable on mobile browsers. Surfaces as a compatibility tag. |
|
|
20
20
|
| `requiresAuth` | `boolean` | `false` | When true, players must log in before playing — the shell's login overlay offers no guest option, and the world page shows an 'Account required' badge. Leave false (default) for instant guest play. NOTE: the `multiplayer` permission forces this to true (login is mandatory for multiplayer). |
|
|
21
21
|
| `contentRating` | `"unrated"` / `"everyone"` / `"teen"` / `"mature"` | `"unrated"` | Self-declared content rating. Curated review may override. 'unrated' worlds may be restricted from public listing. |
|
|
22
|
+
| `scene` | object, discriminated on `kind` — **v0.3 only** | absent | OPTIONAL and IN DEVELOPMENT — a world carries it only when the creator explicitly asked for a streamed-scene world (`scaffold_world` kind `scene`); never add it to an ordinary world. The sealed Scene this world streams as its environment. A world that builds its scene in code omits it (no schema default, so the stored manifest stays byte-identical). Scene v2 document: `{"kind":"scene-v2","id":"scene.example.mall","revision":"v3","integrity":{"sha256":"<64 lower-case hex>"}}`. Continuum Scene Package: `{"kind":"continuum-scene","packageId":"<uuid>","version":"<sealed version>","manifestCid":"cid:sha256:<64 hex>"}`. **Resolve on the hash** — `integrity.sha256` is authoritative (published documents are content-addressed at `scene-v2/<sha256>/`) and `manifestCid` is its Continuum equivalent; `id`/`revision`/`packageId`/`version` are advisory DISPLAY text, and `revision` is free author text two unrelated documents may share. The manifest package does no I/O — it checks shape and formats only; the backend verifies at publish that the pinned document (or sealed Package Version) exists, and rejects the field outright below v0.3. |
|
|
22
23
|
|
|
23
24
|
## Permissions per version
|
|
24
25
|
|
|
25
26
|
- **v0.1 / v0.2:** `auth.profile` (read the player's id, username, display name).
|
|
26
|
-
- **v0.3:** `auth.profile`, `multiplayer` (join the world's shared room), `voice.room` / `voice.proximity` (
|
|
27
|
-
- Cross-field rules the validator enforces: `maxPlayers > 1` ⇒ `multiplayer` permission; `voice.*` ⇒ `multiplayer` permission; the `multiplayer` permission ⇒ `requiresAuth: true`; a `multiplayer` config block (state/entities/rules) ⇒ the `multiplayer` permission.
|
|
27
|
+
- **v0.3:** `auth.profile`, `multiplayer` (join the world's shared room), `voice.room` / `voice.proximity` (voice chat — room-wide vs distance-attenuated ambient render; see the `multiplayer.voice` block below), `camera.capture` (save a photo taken with the in-engine world camera to the player's gallery).
|
|
28
|
+
- Cross-field rules the validator enforces: `maxPlayers > 1` ⇒ `multiplayer` permission; `voice.*` ⇒ `multiplayer` permission; the `multiplayer` permission ⇒ `requiresAuth: true`; a `multiplayer` config block (state/entities/rules) ⇒ the `multiplayer` permission; a `multiplayer.voice` block ⇒ a `voice.*` permission.
|
|
28
29
|
|
|
29
30
|
## v0.2+ — `systems` and `abilities` (catalog pins)
|
|
30
31
|
|
|
31
32
|
Maps of `slug -> semver range`, resolved against the platform catalog by `helix install` (which locks exact versions into `helix.lock.json` and copies code into `public/helix_modules/`):
|
|
32
33
|
|
|
33
34
|
```json
|
|
34
|
-
"systems": { "humanoid-character": "^0.
|
|
35
|
-
"abilities": { "fly": "^0.
|
|
35
|
+
"systems": { "humanoid-character": "^0.3" },
|
|
36
|
+
"abilities": { "fly": "^0.3", "swim": "^0.3" }
|
|
36
37
|
```
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
**Systems are always ranges** (`^0.3`, `^0.1`) — never an exact `x.y.z`. Worlds and Homes load the latest *promoted* engine at launch; an exact version freezes a World on one build forever, so `validate_world` / `publish_world` refuse it. To ship an engine fix, promote it (`POST /api/v1/instant-packages/<slug>/versions/<version>/rollout`) — do not pin a World to get an unpromoted fix.
|
|
40
|
+
|
|
41
|
+
Pin each ability at the version `get_package_manifest` reports for the system line you pinned — an ability bundle declares the system range it was built against and refuses to load at runtime outside it. The system pin also decides the hosted three version (`^0.3` → three 0.185.1); a publish targeting an older three still goes through, with a warning. Full walkthrough: the `character-world` recipe (`get_started { kind: "character" }`).
|
|
39
42
|
|
|
40
43
|
## v0.3 — the `multiplayer` block
|
|
41
44
|
|
|
42
|
-
The declarative multiplayer config (synced state, server-side `when/if/then` rules, entities, zones, timers, phases, `uploadHz
|
|
45
|
+
The declarative multiplayer config (synced state, server-side `when/if/then` rules, entities, zones, timers, phases, `uploadHz`, persistent playerVars, world counters, leaderboards, achievement awards). It is validated at publish and interpreted by the platform's generic room — no server code ships with the world. The grammar is large; author it from the `multiplayer-world` recipe (`get_started { kind: "multiplayer" }`) and the `multiplayer-logic` reference (`read_doc { name: "multiplayer-logic" }`), never from memory.
|
|
46
|
+
|
|
47
|
+
### `multiplayer.voice` — voice-chat render config
|
|
48
|
+
|
|
49
|
+
Optional; requires a `voice.*` permission. **Client-render knobs only** — the room server never reads this block; the engine applies it (`mp.attachVoice(Helix.voice, manifest.multiplayer.voice)`).
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
"multiplayer": {
|
|
53
|
+
"voice": {
|
|
54
|
+
"mode": "proximity",
|
|
55
|
+
"refDistance": 4,
|
|
56
|
+
"maxDistance": 20,
|
|
57
|
+
"spatial": true,
|
|
58
|
+
"channels": { "freq-1": {}, "squad": { "render": "proximity" } }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- `mode` (`"proximity"` default / `"global"`): how the AMBIENT space renders — distance-attenuated (pairs with the `voice.proximity` permission) or flat room-wide (pairs with `voice.room`). A mode/permission mismatch warns at publish.
|
|
64
|
+
- `refDistance` (default 4) / `maxDistance` (default 20): proximity falloff in meters — full volume within `refDistance`, silent beyond `maxDistance`, equal-power fade between. `refDistance` must be less than `maxDistance` (publish error).
|
|
65
|
+
- `spatial` (default `false`): **directional** ambient voice — on headphones a speaker is heard from where they stand (HRTF panning). Direction ONLY: loudness still follows `mode` + falloff, so `"mode": "global"` + `"spatial": true` is valid (direction without falloff). Channel voices never spatialize (radio/phone semantics — always center-panned). Degrades to non-spatial on clients without the audio hook; no extra permission needed, no bandwidth or server cost. Declare it when presence/immersion is the point — realistic social spaces, horror, hide-and-seek/stealth, hangout-with-friends worlds; skip it for announcer-style or competitive-callout voice (decision guide: the multiplayer hub's voice section).
|
|
66
|
+
- `channels`: declared channel ids (lowercase, hyphens, ≤ 32 chars, max 32 channels) → `{ "render": "proximity" | "global" }` (default `"global"` — flat radio/phone semantics). Channel membership is EXCLUSIVE and runtime-set via `Helix.voice.setChannel(id)`; **undeclared ids are allowed at runtime** (dynamic ids like a per-call channel) and render `"global"`. Declare a channel only to opt it into proximity render or to document it.
|
|
43
67
|
|
|
44
68
|
## Bundle rules (enforced at validate AND publish)
|
|
45
69
|
|
|
@@ -49,3 +73,4 @@ The declarative multiplayer config (synced state, server-side `when/if/then` rul
|
|
|
49
73
|
- `entry` must exist in the bundle and end in `.html`
|
|
50
74
|
- The manifest file itself must be at the bundle root as `helix.json`
|
|
51
75
|
- v0.2/v0.3: HELIX assets must stream from the CDN — a clip/texture under `helix_modules/` is rejected (only installed CODE lives there)
|
|
76
|
+
- An optional `.helixignore` at the bundle root (gitignore grammar: `#` comments, `scene/` directory rules, `*.map` / `**/name` globs, `!` re-inclusion) drops matching paths from the upload and from the size/file budgets before any of these rules run; it is never uploaded itself. A Vite world keeps it at `public/.helixignore` so the build copies it to the root. Use it for dev-only payload a build copies verbatim — a Scene document and its source GLBs under `public/scene/`. `validate`/`publish` report `excluded N path(s) via .helixignore`.
|