@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
package/docs/terrain.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Terrain — the ground a scene world stands on
|
|
2
|
+
|
|
3
|
+
**Terrain is a level feature of a scene world.** A scene world does not place its environment in `main.ts`: it points
|
|
4
|
+
at a sealed HELIX Scene document, and the platform streams that document in as the world. Terrain is the ground
|
|
5
|
+
inside such a document — a grid of heightfield tiles the platform streams and textures for you (a tile is 256 cells
|
|
6
|
+
square: 256 m at the default 1 m sample spacing, 1 km at 4 m), so a world can be a kilometre of walkable outdoors
|
|
7
|
+
without a single hand-modelled mesh. The player walks on it, collides with it, and the runtime lights it like any
|
|
8
|
+
other geometry.
|
|
9
|
+
|
|
10
|
+
**You do not model it — you author it as data.** Either GENERATE a landform from one of six built-in presets (a
|
|
11
|
+
seed, a preset and a size; `terrain_generate`), or IMPORT a heightmap you already have — a real-world elevation
|
|
12
|
+
tile, a sculpted export — and paint it with the same presets (`terrain_import`; it authors at 0.5, 1, 2, 4 or 8 m
|
|
13
|
+
per sample — `outputMetresPerSample`, default 1 — a source posting coarser than that grid is upsampled with a
|
|
14
|
+
slope-continuous cubic, and the ground should be shifted so its lowest point sits near 0 m, with `heightRange` on a
|
|
15
|
+
png16/r16 source or `heightOffset` on an f32 one in absolute metres, where the sky, the height fog and the precision
|
|
16
|
+
of the world all expect it). Both write the same thing:
|
|
17
|
+
a **terrain source folder** holding the per-tile heights, the painted surface weights, and a `terrain.json` that
|
|
18
|
+
describes them. The folder also carries the always-resident coarse layer — the far picture past the streamed tiles —
|
|
19
|
+
with its own surface weights and baked normal map at 2 m per texel or coarser (that is the floor; a large grid or a
|
|
20
|
+
coarse sample spacing widens it). Generation is deterministic — the same preset, seed and size always produce the
|
|
21
|
+
same bytes — so a re-run is free, a diff is real, and two agents scaffolding the same world get the same ground.
|
|
22
|
+
|
|
23
|
+
**Reach for it only when the world genuinely wants open ground.** Terrain belongs to `kind: "scene"` worlds; an
|
|
24
|
+
interior, an arena, a diorama or any bounded space is better and cheaper as ordinary geometry, and a character or
|
|
25
|
+
multiplayer world you build yourself has no document to put terrain in. It is also not editable at runtime: the
|
|
26
|
+
ground is authored once, published with the world, and identical for every player. If you want a small patch of
|
|
27
|
+
outdoors under a building, place a mesh — terrain earns its cost at hundreds of metres and up.
|
|
28
|
+
|
|
29
|
+
## The recipe
|
|
30
|
+
|
|
31
|
+
**1. Scaffold it with the world.** The terrain flag belongs to the scene scaffold, so the ground and the document
|
|
32
|
+
that streams it are written together and are already consistent:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
scaffold_world({ directory, title, slug, kind: "scene", terrain: "alpine" })
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
That returns `helix init <dir> --kind scene --terrain alpine`. Running it writes the usual scene project plus a
|
|
39
|
+
generated **4 x 4-tile (1 km x 1 km)** landform under `public/scene/mall/terrain/`, paints it from the preset, lifts
|
|
40
|
+
the starter mall onto the flat plateau at the spawn, and declares the terrain in the sealed document. The scaffolded
|
|
41
|
+
`public/.helixignore` already covers `terrain/` — those bytes are a dev override, not bundle payload.
|
|
42
|
+
|
|
43
|
+
Adding ground to a scene world you already have is the same generator without the scaffold:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
terrain_generate({ out: "<world>/public/scene/<folder>/terrain", preset: "meadow", tiles: "8x8", seed: 12 })
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
then declare it in the document (a `terrain` element in the `scene-elements` source names the folder).
|
|
50
|
+
|
|
51
|
+
**2. Inspect before anything else.** `terrain_inspect({ dir })` reports what the folder would actually ship: extent,
|
|
52
|
+
tile count, delivered bytes split into heights and surface weights, the height range, and every warning — an
|
|
53
|
+
over-cap tile, an unbaked folder, a grid past the cap, and which surface roles the paint had to FOLD together on
|
|
54
|
+
which tiles. The bytes count the coarse layer beside the tiles, its surface weights and baked normal map included —
|
|
55
|
+
those two are painted at 2 m per texel or coarser, the far picture past the streamed tiles. None of that is visible
|
|
56
|
+
in a screenshot and none of it is reported at publish time.
|
|
57
|
+
|
|
58
|
+
**3. Adjust, then re-inspect.** Three dials, cheapest first: a different `seed` for another world of the same
|
|
59
|
+
character; a different `preset` for another character entirely; a different size (`tiles` or `sizeKm`) for another
|
|
60
|
+
scale. To change only the SURFACES — which materials go where, how big they tile, how sharply they blend — copy a
|
|
61
|
+
preset to a JSON file, edit it, and re-paint the existing landform in place, leaving the heights untouched:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
helix terrain bake <dir> --preset ./my-preset.json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**4. Validate.** `npm run build`, then `validate_world` on `dist/` and `helix verify-subpath dist`, as for any world.
|
|
68
|
+
A terrain world must stay on dynamic lighting (below), and this is the step that says so out loud: `validate_world`
|
|
69
|
+
(and `helix validate <dist>`) reports a VALID bundle and then warns when that bundle is a terrain world whose lighting
|
|
70
|
+
contract declares `"baked"` — read past the ✔.
|
|
71
|
+
|
|
72
|
+
**5. Publish.** Terrain rides with the DOCUMENT, never in the world bundle — that is what `.helixignore` is for. So
|
|
73
|
+
the scene document (terrain included) publishes first and the world second, exactly as for any scene world.
|
|
74
|
+
|
|
75
|
+
## The presets
|
|
76
|
+
|
|
77
|
+
A preset is two things at once: the **landform** it generates, and the **surfaces** it paints onto it. Surfaces are
|
|
78
|
+
assigned by ROLE, computed from the shape of the ground itself — `base` is the ordinary flat ground, `slope` the
|
|
79
|
+
steep faces, `peak` the high ground, `low` the bottoms and basins, `cavity` the concave creases where water and
|
|
80
|
+
debris would collect, and `accent` a scattered second surface that breaks up uniformity.
|
|
81
|
+
|
|
82
|
+
| Preset | The landform | base | slope | peak | low | cavity | accent |
|
|
83
|
+
|---|---|---|---|---|---|---|---|
|
|
84
|
+
| `meadow` | gentle rolling grassland, a soft rise and a hollow (up to ~80 m) | grass | rock | — | mud | dry dirt | leafy grass |
|
|
85
|
+
| `alpine` | three massifs and a valley between them; the tallest terrain (up to ~900 m) | grass | granite | snow | gravel | dry dirt | — |
|
|
86
|
+
| `desert` | wind-aligned dunes with two mesas standing out of them (up to ~140 m) | sand | sandstone | limestone | dry dirt | gravel | pebble |
|
|
87
|
+
| `coast` | a shoreline along one edge climbing inland, with a headland (−14 to ~90 m) | grass | rock | — | sand | mud | leafy grass |
|
|
88
|
+
| `forest` | two wooded ridges around a sheltered glen (up to ~160 m) | leafy grass | rock | — | mud | dry dirt | gravel |
|
|
89
|
+
| `volcanic` | a crater cone with spatter mounds around it (up to ~600 m) | basalt | rock | gravel | slate stone | dry dirt | pebble |
|
|
90
|
+
|
|
91
|
+
Two things follow from the table. A preset may declare **six** roles but each tile ships **four**, so on tiles where
|
|
92
|
+
a role carries almost no weight the paint folds it into a kept role — normal, and reported by `terrain_inspect` so
|
|
93
|
+
you can tell "folded a role that was never visible there" from "lost the snow line". And `coast` is the one preset
|
|
94
|
+
with ground below zero: it floods to sea level by default, and `seaLevel` on the other presets floods them the same
|
|
95
|
+
way.
|
|
96
|
+
|
|
97
|
+
## Lighting
|
|
98
|
+
|
|
99
|
+
**A terrain world is a dynamic-GI world, always.** Its `public/helix.visuals.json` stays on `mode: "dynamic"` with
|
|
100
|
+
sky fallback on and no pinned probe box: the lighting runtime scrolls its probe volume with the camera, which is the
|
|
101
|
+
only thing that covers a kilometre of ground. **There is no baked mode for terrain** — do not call
|
|
102
|
+
`bake_world_lighting` on one, and do not hand-edit the contract to `"baked"` — `validate_world` warns when a terrain
|
|
103
|
+
world declares it. One consequence is worth knowing before you frame a shot: the sun shadow is a camera-following box
|
|
104
|
+
about 200 m across, so near ground casts and receives real sun shadows and distant ridges do not.
|
|
105
|
+
|
|
106
|
+
## The caps
|
|
107
|
+
|
|
108
|
+
| | |
|
|
109
|
+
|---|---|
|
|
110
|
+
| tile | 257 samples square (256 cells): 256 m at 1 m per sample, 1 km at 4 m |
|
|
111
|
+
| sample spacing | 0.5, 1, 2, 4 or 8 m — `terrain_generate` always writes 1 m, `terrain_import` picks it (`outputMetresPerSample`) |
|
|
112
|
+
| default size | 4 x 4 tiles = 1 km x 1 km at 1 m per sample (~2.4 MiB) |
|
|
113
|
+
| maximum size | 16 x 16 tiles = 4 km x 4 km at 1 m per sample (~39 MiB, ~25 s to generate) |
|
|
114
|
+
| surfaces | 6 roles per preset, 4 per tile |
|
|
115
|
+
| sizing | pass `tiles` (`"8x8"`) or `sizeKm`, never both — `sizeKm` rounds to whole tiles |
|
|
116
|
+
|
|
117
|
+
Bigger is not better: bytes are delivered to the player, and 4 km of empty ground plays worse than 1 km of ground
|
|
118
|
+
with something on it. Start at the scaffold default and grow it only when the world runs out of room.
|
|
119
|
+
|
|
120
|
+
## The two mistakes to avoid
|
|
121
|
+
|
|
122
|
+
1. **Baking the lighting.** Terrain worlds are the case the baked path was never built for — the bake volume cannot
|
|
123
|
+
cover open ground, and a world that ships `"baked"` terrain lights wrongly rather than loudly. Leave the mode
|
|
124
|
+
dynamic. (See above; this is also why `bake_world_lighting` is not part of the recipe here, though it remains the
|
|
125
|
+
right call for a bounded interior in a world that has one.) A baked terrain world is still a VALID bundle, so
|
|
126
|
+
`validate_world` prints its `⚠` AFTER the ✔ line — a pass is not a clean bill of health here.
|
|
127
|
+
2. **Publishing before running `terrain_inspect`.** Folded roles, an unbaked folder that would ship untextured, and
|
|
128
|
+
an over-cap grid are all silent everywhere else — the world builds, validates and renders. Inspect the folder
|
|
129
|
+
every time you generate, import or re-paint it, and read the warnings before you publish.
|
|
130
|
+
|
|
131
|
+
## Commands and tools
|
|
132
|
+
|
|
133
|
+
| Do this | MCP | CLI ({{CLI_PKG}}) |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| scaffold a scene world with ground | `scaffold_world({ kind: "scene", terrain })` | `helix init <dir> --kind scene --terrain <preset>` |
|
|
136
|
+
| generate a landform | `terrain_generate` | `helix terrain generate --out <dir> --preset <id>` |
|
|
137
|
+
| import a heightmap | `terrain_import` | `helix terrain import <file> --out <dir> --preset <id>` |
|
|
138
|
+
| re-paint an existing landform | — | `helix terrain bake <dir> --preset <id-or-path>` |
|
|
139
|
+
| report what would ship | `terrain_inspect` | `helix terrain inspect <dir> [--json]` |
|
|
140
|
+
|
|
141
|
+
## Trees, grass and rocks
|
|
142
|
+
|
|
143
|
+
Use placements_bake with a helix.placements-source/1 JSON file and a prepared helix.placements-assets/1 catalog.
|
|
144
|
+
The catalog must pin renderable GLB LODs, external KTX2 textures, small/full texture families, root-normalized wind gradients,
|
|
145
|
+
explicit simple colliders and optional static GI proxies. Use content whose rights permit your intended distribution.
|
|
146
|
+
The evaluation assets in the maintainer capstone are not a public catalog dependency.
|
|
147
|
+
|
|
148
|
+
A minimal source (paths relative to this JSON file):
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"schema": "helix.placements-source/1",
|
|
153
|
+
"terrain": "terrain",
|
|
154
|
+
"catalog": "prepared/catalog.json",
|
|
155
|
+
"layers": [
|
|
156
|
+
{ "id": "trees", "prototype": "douglas-fir", "seed": 42, "spacing": 16, "minSpacing": 12 },
|
|
157
|
+
{ "id": "grass", "prototype": "meadow-grass", "seed": 9, "spacing": 2, "minSpacing": 1,
|
|
158
|
+
"exclude": [[[-12, -12], [12, -12], [12, 12], [-12, 12]]] }
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Run placements_inspect on the output. In Scene Elements, add placements: { source: "placements" } beside terrain;
|
|
164
|
+
buildings may be an empty array. Set runtimePolicy.memoryBudgetMiB deliberately for textured terrain plus vegetation
|
|
165
|
+
(the old default is 64 MiB), then inspect the actual runtime reservations and budget rejections.
|
|
166
|
+
Continuum scratch descriptions use a root { id: "vegetation", type: "placements", source: "<baked-folder>" }.
|
|
167
|
+
Publication keeps the exact binary closure. Objects above 1 MiB use verified staged binary ingestion, capped at 32 MiB per object.
|
|
168
|
+
|
|
169
|
+
This requires an installed scene runtime with helix.scene-placements/1 and its module-worker sidecar.
|
|
170
|
+
The optional scene/visual attachment adds weather wind; static scene rendering works without the visual peer.
|
|
171
|
+
Trees/rocks keep stable placement and collision across tiers. Grass uses nested density subsets and may disappear on minimal.
|
|
172
|
+
The separate native Home renderer does not support this capability and must reject such a scene explicitly.
|
|
173
|
+
Do not advertise native Home support, a released package version, or mobile performance based on the local engine capstone.
|
package/docs/upgrades.md
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# HELIX Instant — World Upgrades
|
|
2
|
+
|
|
3
|
+
This is the migration ledger for **existing worlds**: change-specific steps for bringing an older world up to
|
|
4
|
+
current platform capabilities. You normally arrive here because `check_for_updates({ projectDir })` reported a
|
|
5
|
+
finding — its result already inlines the steps for exactly the entries that apply to your world, so read this full
|
|
6
|
+
document only when you want the surrounding context.
|
|
7
|
+
|
|
8
|
+
**The policy, always:** upgrades are the user's call. Report findings, suggest the fix, and stop — never edit pins,
|
|
9
|
+
run installs, or migrate world code until the user says yes. In-range staleness (`locked 0.2.9 → catalog 0.2.11
|
|
10
|
+
within the pin`) is the one low-ceremony case: a single re-run of `install_world_packages` plus a rebuild, still
|
|
11
|
+
suggested first.
|
|
12
|
+
|
|
13
|
+
**How to read entries:** each entry is **cumulative** — it takes a world from *any* older state to current in one
|
|
14
|
+
pass. Never chain multiple entries for the same system; the newest applicable entry is the whole path.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Engine-core split — humanoid-character 0.2.11 / engine-core 0.1.0
|
|
19
|
+
|
|
20
|
+
**Applies if:** the world's `helix.json` has no `engine-core` entry in `systems` (typical for worlds published
|
|
21
|
+
before engine-core existed), or `helix.lock.json` resolves `humanoid-character` below `0.2.11`.
|
|
22
|
+
|
|
23
|
+
**What changed:** the screenshot helper and the in-world photo camera moved out of `@helix/humanoid-character` into
|
|
24
|
+
their own system, `@helix/engine-core`. A world without it cannot declare screenshot shots, cannot be captured by
|
|
25
|
+
`capture_world_screenshot` (blind mode excepted), and cannot offer the photo camera.
|
|
26
|
+
|
|
27
|
+
**Steps (safe from any older state):**
|
|
28
|
+
|
|
29
|
+
1. In `helix.json`, make the systems block include both pins (keep anything else already there):
|
|
30
|
+
```json
|
|
31
|
+
"systems": { "humanoid-character": "^0.2.11", "engine-core": "^0.1.0" }
|
|
32
|
+
```
|
|
33
|
+
2. Run `install_world_packages` (terminal: `helix install`) — it resolves both pins and lays the code into
|
|
34
|
+
`public/helix_modules/`.
|
|
35
|
+
3. If `vite.config.ts` predates engine-core, add these aliases — subpath entries **must precede** any root entry
|
|
36
|
+
(the aliaser substitutes prefixes in order; a root-only alias mangles subpath imports into broken paths):
|
|
37
|
+
```ts
|
|
38
|
+
'@helix/engine-core/screenshot': fileURLToPath(new URL('./public/helix_modules/engine-core/screenshot/index.js', import.meta.url)),
|
|
39
|
+
'@helix/engine-core/world-camera': fileURLToPath(new URL('./public/helix_modules/engine-core/world-camera/index.js', import.meta.url)),
|
|
40
|
+
'@helix/engine-core': fileURLToPath(new URL('./public/helix_modules/engine-core/index.js', import.meta.url)),
|
|
41
|
+
```
|
|
42
|
+
If the project typechecks, mirror them in tsconfig `paths` — the exact block is in `read_doc({ name: "screenshots" })`.
|
|
43
|
+
4. To enable capture, add the declared shot + ready call to world code — the three lines in
|
|
44
|
+
`read_doc({ name: "screenshots" })` section 1.
|
|
45
|
+
5. Rebuild (`npm run build`), check the bundle with `validate_world`, capture with `capture_world_screenshot`, and
|
|
46
|
+
republish the world when the user is ready.
|
|
47
|
+
|
|
48
|
+
**Nothing else changes:** existing `@helix/humanoid-character` imports keep working — 0.2.11 removed only the two
|
|
49
|
+
moved subpaths, which no published world ever successfully imported.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Hosted Rapier runtime — humanoid-character 0.2.35
|
|
54
|
+
|
|
55
|
+
**Applies if:** the world's `package.json` lists `@dimforge/rapier3d-compat` under `dependencies` (the pre-0.2.35
|
|
56
|
+
scaffold shape), or its vite config lacks `'@dimforge/rapier3d-compat'` in `build.rollupOptions.external`.
|
|
57
|
+
|
|
58
|
+
**What changed:** the platform now hosts Rapier the way it hosts three. `helix install` against
|
|
59
|
+
humanoid-character 0.2.35+ writes an import-map entry for the bare specifier `@dimforge/rapier3d-compat` pointing at
|
|
60
|
+
`runtime/rapier/<ver>/rapier.es.js` on the platform CDN, and that module streams its WASM from a sibling URL. A world
|
|
61
|
+
that externalizes rapier drops ~2 MB from its bundle and lets warm reloads skip the WASM compile. A world that keeps
|
|
62
|
+
bundling rapier continues to work — it just ships the duplicate copy and recompiles it on every load.
|
|
63
|
+
|
|
64
|
+
**Steps (safe from any older state; do them together — externalizing without the new import map leaves the bare
|
|
65
|
+
import unresolvable in a served bundle):**
|
|
66
|
+
|
|
67
|
+
1. In `helix.json`, raise the pin: `"humanoid-character": "^0.2.35"` (keep everything else).
|
|
68
|
+
2. Run `install_world_packages` (terminal: `helix install`) — the lock re-resolves and the `index.html` import map
|
|
69
|
+
gains the `@dimforge/rapier3d-compat` entry. The pin raise is what forces re-resolution; a bare re-install of an
|
|
70
|
+
unchanged pin replays the old lock and adds nothing.
|
|
71
|
+
3. In `package.json`, move `@dimforge/rapier3d-compat` from `dependencies` to `devDependencies` — types and
|
|
72
|
+
`helix dev` still resolve the npm copy (Vite dev ignores build externals, so local dev is unchanged).
|
|
73
|
+
4. In the vite config, add `'@dimforge/rapier3d-compat'` to `build.rollupOptions.external`, beside `'three'`.
|
|
74
|
+
5. Rebuild (`npm run build`) — the ~2 MB rapier chunk disappears from `dist/`. Then `validate_world` and republish
|
|
75
|
+
when the user is ready. In a played world the network panel shows `runtime/rapier/<ver>/rapier.es.js` plus
|
|
76
|
+
`rapier_wasm3d_bg.wasm`, one fetch each.
|
|
77
|
+
|
|
78
|
+
**Nothing else changes:** world code and imports (`RapierBody`, physics config, ragdoll) are untouched — hosting only
|
|
79
|
+
changes where the module's bytes come from. Worlds without a rapier dependency (no physics) have nothing to do.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Weapon definition pins — the arsenal moves without you
|
|
84
|
+
|
|
85
|
+
**Applies if:** `check_for_updates` reported a weapon definition pinned below its current published version, or
|
|
86
|
+
its two config copies disagreeing. Any gun world qualifies eventually: `multiplayer.weaponItems` pins are
|
|
87
|
+
IMMUTABLE `assetId@version` references. Publishing a newer definition version never moves a consuming world.
|
|
88
|
+
|
|
89
|
+
**What changed (the waves so far):** `@2` embedded each weapon's ejected shell casing in the prop; `@3` added
|
|
90
|
+
`damage.headMultiplier` (2.0 on every hitscan category in the official arsenal). A world using the
|
|
91
|
+
`weaponDamage` `part` slot gets NO headshot bonus until it pins `@3` or newer — the multiplier lives in the
|
|
92
|
+
definition, absent = ×1, silently.
|
|
93
|
+
|
|
94
|
+
**Steps:**
|
|
95
|
+
|
|
96
|
+
1. `item-def inspect <assetId>` per pinned definition — the resolve answers the current version and the
|
|
97
|
+
numbers behind it (`check_for_updates` already names which pins are behind).
|
|
98
|
+
2. Edit the version in BOTH config copies — the `multiplayer` block in `helix.json` AND
|
|
99
|
+
`public/multiplayer.json`. They must stay identical: the room fetches one, the publish validator reads the
|
|
100
|
+
other, and a pin present in only one publishes clean then resolves to nothing at runtime.
|
|
101
|
+
3. Grep both configs for the OLD pin string: any `itemDamage` op naming it must move too — a pin named by
|
|
102
|
+
`itemDamage` but absent from `weaponItems` fails publish validation.
|
|
103
|
+
4. Rebuild (`npm run build`), `validate_world`, republish when the user is ready.
|
|
104
|
+
|
|
105
|
+
**Nothing else changes:** the engine consumes whatever the definition serves — no code edit, no re-install,
|
|
106
|
+
no system pin movement.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Installed Vault assets & interactables — the catalog moves without you
|
|
111
|
+
|
|
112
|
+
**Applies if:** `check_for_updates` reported an installed Vault asset behind its current Vault version
|
|
113
|
+
(`public/helix.assets.json`), or an installed interactable whose Marketplace reference moved
|
|
114
|
+
(`helix_interactables/installed.json`).
|
|
115
|
+
|
|
116
|
+
**The policy first — item updates are OPTIONAL and consented separately from system updates.** A user who asked
|
|
117
|
+
to "update the world" or "update systems" has NOT asked to update items: the installed bytes are already in the
|
|
118
|
+
bundle and keep working exactly as published, so nothing is broken by staying put. Ask the user specifically,
|
|
119
|
+
per item, before re-installing or re-adding anything — and skip the ask entirely unless they showed interest.
|
|
120
|
+
|
|
121
|
+
**What "behind" means here:** Vault artifact versions are immutable, so installing wrote an exact version into
|
|
122
|
+
the world and the Vault re-versioning since then moved nothing locally. A stale row is an offer, not a defect.
|
|
123
|
+
`unresolved` usually means the receipt was written against another backend (dev-stack ids checked on live) — the
|
|
124
|
+
bundled copy still works; only re-installs need the original backend.
|
|
125
|
+
|
|
126
|
+
**Steps (only for items the user said yes to):**
|
|
127
|
+
|
|
128
|
+
- **Vault asset:** `install_asset({ assetId })` (terminal: `helix assets install <assetId>`) — re-downloads the
|
|
129
|
+
current version, verifies checksums, rewrites the receipt entry. Then rebuild and republish when the user is ready.
|
|
130
|
+
- **Interactable:** `helix update-interactable <slug-or-item-id>` (terminal) updates every installed instance in
|
|
131
|
+
place: it subtracts the old bundle's composed rules and declarations, re-composes the new version under the SAME
|
|
132
|
+
namespace and placement (world-authored references to `itx…_` names keep working), moves `installed.json` and the
|
|
133
|
+
generated glue, and swaps the vendored `helix_interactables/` directory. If a composed rule was hand-edited since
|
|
134
|
+
install, the update aborts and names it rather than guessing — restore the rule (or remove the instance by hand)
|
|
135
|
+
and retry. Never adopt by re-running `helix add-interactable` on its own: that installs a SECOND instance whose
|
|
136
|
+
rules run alongside the old ones.
|
|
137
|
+
- **checksum-drift** (an interactable's Marketplace version unchanged but its bundle bytes differ): treat as
|
|
138
|
+
suspect — surface it to the user and do not re-add until the registry story is understood.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Platform runtime externalization — publish now rejects bundled system delivery
|
|
143
|
+
|
|
144
|
+
**Applies if:** validate or publish fails with any of `platform systems are declared but helix.runtime.json is
|
|
145
|
+
missing`, `helix.runtime.json is legacy-bundled`, `is not a safe platform runtime range`, `is missing from the built
|
|
146
|
+
HTML import map`, or the publish server's `the built JavaScript never imports @helix/…` /
|
|
147
|
+
`the platform runtime resolver cannot currently serve @helix/<slug>@<range>`. Enforced today on the **helix3 lane
|
|
148
|
+
only** (CLI 0.1.13-helix3.126+, manifest 0.3.19-helix3.52+, and the helix3 backend); the production and staging
|
|
149
|
+
scopes do not enforce it yet. Unlike item updates this is **not optional** on an enforcing lane — publish stays
|
|
150
|
+
blocked until the world migrates — but the suggest-first policy still holds: report the findings and the steps, and
|
|
151
|
+
apply them only when the user agrees.
|
|
152
|
+
|
|
153
|
+
**What changed:** platform systems (`@helix/humanoid-character`, `@helix/engine-core`, and every declared system)
|
|
154
|
+
must be delivered through the platform import map, never bundled into the world's JavaScript. Three declarations are
|
|
155
|
+
cross-checked and must agree: the `systems` block in `helix.json`, `public/helix.runtime.json` (generated by
|
|
156
|
+
`helix install`), and the import map in the built entry HTML. The publish server additionally verifies that the built
|
|
157
|
+
JavaScript still contains bare `@helix/…` imports (an import map nothing imports is inert) and that every pinned
|
|
158
|
+
range actually resolves on the runtime resolver — so a world can pass local validation and still be rejected at
|
|
159
|
+
publish. The server is authoritative.
|
|
160
|
+
|
|
161
|
+
**Steps:**
|
|
162
|
+
|
|
163
|
+
1. `helix.json` — every `systems` value must be a `^`/`~` range (never an exact version: `validate_world`/`publish_world`
|
|
164
|
+
refuse it — to ship an unpromoted engine fix, promote it, do not pin), AND must resolve to a published
|
|
165
|
+
version. The current scaffold pins `"humanoid-character": "^0.3", "engine-core": "^0.1.2"`; `^0.1` or `^0.4` for
|
|
166
|
+
humanoid-character resolve to nothing and fail at the server.
|
|
167
|
+
2. `vite.config.ts` — the build must externalize the platform packages; the generated form is this line:
|
|
168
|
+
```ts
|
|
169
|
+
rollupOptions: { external: (id) => id === 'three' || id === '@dimforge/rapier3d-compat' || id.startsWith('@helix/') },
|
|
170
|
+
```
|
|
171
|
+
Current CLIs recognize ANY formatting that keeps the `.startsWith('@helix/')` external predicate (single- or
|
|
172
|
+
double-quoted), so reformatting the object — wrapping it, adding an `output` key — is safe. CLIs at
|
|
173
|
+
0.1.13-helix3.135 and below matched this line as a LITERAL SUBSTRING: on those, any reformatting makes the next
|
|
174
|
+
`helix install` classify the world `legacy-bundled` and the next publish fails — keep it byte-identical there.
|
|
175
|
+
3. `index.html` — the marker block must exist verbatim:
|
|
176
|
+
```html
|
|
177
|
+
<!-- helix:three:start -->
|
|
178
|
+
<script type="importmap">
|
|
179
|
+
{ "imports": { "three": "" } }
|
|
180
|
+
</script>
|
|
181
|
+
<!-- helix:three:end -->
|
|
182
|
+
```
|
|
183
|
+
`helix install` rewrites everything between the markers, including the `@helix/*` entries. Never hand-write an
|
|
184
|
+
`@helix/*` import-map entry — a stray one is its own validation error.
|
|
185
|
+
4. `package.json` — `three`, `@types/three` and `@dimforge/rapier3d-compat` belong under `devDependencies`; the
|
|
186
|
+
bundle must not carry them.
|
|
187
|
+
5. Run `install_world_packages` with `update: true` (terminal: `helix install --update`) and READ the printed
|
|
188
|
+
`platform runtime delivery -> compatible` line. If it prints `legacy-bundled`, step 2 did not take — fix the vite
|
|
189
|
+
line first.
|
|
190
|
+
6. `npm run build`, then confirm `dist/helix.runtime.json` exists and the dist JS still contains bare `@helix/…`
|
|
191
|
+
import specifiers.
|
|
192
|
+
7. `validate_world` (terminal: `helix validate`), then republish when the user is ready.
|
|
193
|
+
|
|
194
|
+
**What install automates:** it always (re)writes `public/helix.runtime.json` and the import map between the markers.
|
|
195
|
+
It rewrites `vite.config.ts` ONLY when that file still contains the byte-exact legacy generated line
|
|
196
|
+
`rollupOptions: { external: ['three', '@dimforge/rapier3d-compat'] },`. It never edits `package.json`, the ranges in
|
|
197
|
+
`helix.json`, or an `index.html` that is missing the markers.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## three r185 + humanoid-character 0.3 — the platform runtime moved to three 0.185.1
|
|
202
|
+
|
|
203
|
+
**Applies if:** `src/helix.runtime.ts` says a `THREE_VERSION` below `0.185.1` (a world installed before this wave
|
|
204
|
+
says `0.172.0`), or `helix.json` pins `humanoid-character` on the `^0.2` line — `check_for_updates` reports either.
|
|
205
|
+
|
|
206
|
+
**What changed:** the platform runtime moved to **three 0.185.1 (r185)**, hosted per world through the import map
|
|
207
|
+
exactly as before, and `humanoid-character` **0.3.0** is the line built against it. `helix install` bakes whatever
|
|
208
|
+
three the resolved system declares, so the system pin is what chooses the version: `^0.2` keeps giving you three
|
|
209
|
+
0.172, `^0.3` gives 0.185.1. **A world behind that line still publishes** — `helix validate` / `helix publish`
|
|
210
|
+
only warn, naming these steps — so this entry, like the others, is an upgrade to suggest rather than a blocker.
|
|
211
|
+
Nothing that is already published moves: bundles and the hosted runtime URLs are immutable, so live worlds keep
|
|
212
|
+
running on the three they baked, forever. New scaffolds start on the 0.3 line and three 0.185.1 already.
|
|
213
|
+
|
|
214
|
+
**Steps (safe from any older state):**
|
|
215
|
+
|
|
216
|
+
1. In `helix.json`, raise the pin: `"humanoid-character": "^0.3"` (keep everything else). This supersedes the lower
|
|
217
|
+
`^0.2.x` pins named by the older entries above — those are the versions where each change landed, not a ceiling,
|
|
218
|
+
and 0.3 carries all of them.
|
|
219
|
+
2. Check the abilities pinned beside it with `get_package_manifest({ slug })`: an ability declares the system range
|
|
220
|
+
it was built against and refuses to load at runtime outside it, so a world moving to the 0.3 system line needs
|
|
221
|
+
ability versions that declare it.
|
|
222
|
+
3. Run `install_world_packages` with `update: true` (terminal: `helix install --update`) — it re-resolves the pin,
|
|
223
|
+
rewrites `helix.lock.json`, the `index.html` import map, `public/helix.runtime.json` and `src/helix.runtime.ts`
|
|
224
|
+
(`THREE_VERSION` becomes `0.185.1` and `TRANSCODER_PATH` moves to `runtime/basis/three-0.185.1/`). The pin raise
|
|
225
|
+
is what forces re-resolution; a bare re-install of an unchanged pin replays the old lock.
|
|
226
|
+
4. In `package.json`, move the `three` and `@types/three` devDependencies to `^0.185.1`. Do not skip this: the three
|
|
227
|
+
CORE loads from the import map, but Vite bundles the `three/examples/jsm/...` addons out of your local copy and
|
|
228
|
+
`helix dev` runs that copy too — a stale devDep ships r172 addons against an r185 core and makes local dev
|
|
229
|
+
disagree with the published build.
|
|
230
|
+
5. Delete any hardcoded `runtime/three/<ver>` or `runtime/basis/three-<ver>` URL in world code and read
|
|
231
|
+
`src/helix.runtime.ts` instead (`THREE_VERSION`, `THREE_MODULE_URL`, `TRANSCODER_PATH`) — those are rewritten by
|
|
232
|
+
install, a hand-written copy is not.
|
|
233
|
+
6. `npm run build`, then `validate_world`, then republish when the user is ready. Confirm `dist/helix.runtime.json`
|
|
234
|
+
and the built import map name `0.185.1`.
|
|
235
|
+
|
|
236
|
+
**Expect a slight look shift, and accept it.** three's physically-based materials changed between r172 and r185
|
|
237
|
+
(energy conservation on rough/metallic surfaces landed in r181): the same scene reads a little brighter, mostly on
|
|
238
|
+
rough materials. It is the new correct result, not a regression — re-check exposure and tone mapping after the
|
|
239
|
+
rebuild rather than chasing individual materials back to their old values. Tell the user to expect it before you
|
|
240
|
+
republish a world they know well.
|
|
241
|
+
|
|
242
|
+
**Nothing else changes:** world code and imports are untouched — the character, ability, physics and SDK APIs are
|
|
243
|
+
the same. Worlds that pin no system have no hosted three to move and are outside the gate.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## The visual runtime — platform-owned lighting, and the publish-time bake
|
|
248
|
+
|
|
249
|
+
**Applies if:** the world's `src/` never calls `createVisualRuntime` and its `helix.json` has no `visual` entry in
|
|
250
|
+
`systems` — true of every world scaffolded before the visual system published, since the scaffold wrote a hand-built
|
|
251
|
+
`WebGLRenderer` plus a `HemisphereLight`/`DirectionalLight` pair.
|
|
252
|
+
|
|
253
|
+
**What changed:** lighting is platform-owned by default. The `visual` system's `createVisualRuntime` owns the
|
|
254
|
+
renderer, sky, sun, shadow rig, tone mapping (AgX + a calibrated colour grade), AO, fog, auto-exposure, probe GI and
|
|
255
|
+
the time-of-day clock; the world declares HOW it is lit in `public/helix.visuals.json`. Bounded, static-geometry
|
|
256
|
+
worlds then ship **baked** lighting: `bake_world_lighting` computes the light field once at publish and it imports
|
|
257
|
+
in milliseconds at boot instead of converging live.
|
|
258
|
+
|
|
259
|
+
**The policy — this upgrade is OPTIONAL, twice over.** Nothing is broken by staying put: a hand-lit world keeps
|
|
260
|
+
rendering exactly as published. And a world with its own deliberate visual identity — a custom renderer or post
|
|
261
|
+
chain, a stylized look — is a legitimate **opt-out** and should NOT migrate. Suggest it for worlds that want the
|
|
262
|
+
platform look (and the baked boot) — expect a look shift when the calibrated pipeline replaces the two hand lights,
|
|
263
|
+
and tell the user to expect it before republishing a world they know well.
|
|
264
|
+
|
|
265
|
+
**Steps (the full conversion is `read_doc({ name: "lighting-world" })` — this is the shape):**
|
|
266
|
+
|
|
267
|
+
1. In `helix.json`, add the pin: `"visual": "^0.1"` (keep everything else); run `install_world_packages` with `update: true`.
|
|
268
|
+
2. Author `public/helix.visuals.json` (`version: 1`, `mode: "dynamic"`, your `timeOfDay`/`grade`) — the FILE,
|
|
269
|
+
fetched at boot, never an inlined const (the bake patches the file).
|
|
270
|
+
3. In `src/main.ts`, delete the renderer-construction block AND both hand-placed lights; construct the runtime from
|
|
271
|
+
a canvas, hand `visuals.renderer` to whatever used the old renderer (the character facade takes it), replace
|
|
272
|
+
`renderer.render(...)` with `visuals.tick(dt)`, and wire `visuals.setSize` + `visuals.catchUp()`.
|
|
273
|
+
4. `npm run build`, then — bounded static world — `bake_world_lighting`, then `validate_world`, and republish when
|
|
274
|
+
the user is ready. Re-bake whenever geometry or lights change; there is no staleness detector. **A bounded
|
|
275
|
+
INTERIOR authors `probes: { center, size }` in `helix.visuals.json` before baking**: the default bake volume is
|
|
276
|
+
sized for open ground, and an unfitted one lands most of its probe layers outside the room.
|
|
277
|
+
|
|
278
|
+
**Nothing else changes:** character, ability, physics and SDK APIs are untouched — only who owns the renderer moves.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## World look — the material library, texture gates, and the asset-truth pass
|
|
283
|
+
|
|
284
|
+
**Applies if:** `validate_world` reports texture warnings (mip-less KTX2, or a glTF sampler with a non-mipmap
|
|
285
|
+
`minFilter`); or world code hand-wires pack textures — sets `texture.repeat` arithmetic itself, passes scalar
|
|
286
|
+
`roughness`/`metalness` overrides that drop the maps, binds an albedo without its ORM triple; or the world imports
|
|
287
|
+
third-party GLBs and shows the classic defect signatures (dark oddly-tinted surfaces where GI seems to do nothing,
|
|
288
|
+
shimmer no quality setting fixes, wrong-scale props against the avatar). `check_for_updates` does not detect this
|
|
289
|
+
entry yet — the signals above are yours to spot, mostly from `validate_world` output and captures.
|
|
290
|
+
|
|
291
|
+
**What changed:** the craft this entry applies is `read_doc({ name: "world-look" })`. Concretely: the visual
|
|
292
|
+
system now ships a material library (`loadMaterialLibrary` → `apply(mesh, id)`) that derives per-axis repeat from
|
|
293
|
+
each material's calibrated physical size and binds the full ORM — replacing exactly the hand wiring that older
|
|
294
|
+
worlds got subtly wrong; the platform pack's tile sizes were re-calibrated, so hand-derived repeat values from the
|
|
295
|
+
old catalog data render mis-sized; and mip-less KTX2 is now rejected at Vault upload while **world bundles only
|
|
296
|
+
warn** — plus Vault assets published before the gate are grandfathered, so an older world can legally keep
|
|
297
|
+
shimmering until its assets are re-published through the pipeline.
|
|
298
|
+
|
|
299
|
+
**The policy — OPTIONAL, look-only.** Nothing functional breaks by staying put, and every step changes how the
|
|
300
|
+
world reads: capture before/after and let the user judge. A stylized world whose look is deliberate is not a
|
|
301
|
+
defect — this entry is for worlds that want to read better, not a compliance pass.
|
|
302
|
+
|
|
303
|
+
**Steps (each independent — apply what the world needs):**
|
|
304
|
+
|
|
305
|
+
1. **Texture hygiene:** run `validate_world` and read the texture warnings. Hand-encoded or mip-less KTX2 in the
|
|
306
|
+
bundle: replace it by feeding the pipeline source images (PNG/JPG/WebP) instead of shipping your own encode. A
|
|
307
|
+
non-mipmap `minFilter` in a glTF sampler: remove it (it silently disables mips AND anisotropy). A shimmering
|
|
308
|
+
Vault asset that predates the mip gate: the immutable asset is grandfathered. Do not attempt a direct
|
|
309
|
+
Vault byte upload/version move; when the supported Package import path is available, re-author and publish
|
|
310
|
+
a corrected Package, then install its pinned content.
|
|
311
|
+
2. **Material consumption:** if the world hand-wires pack textures, either adopt the library (`loadMaterialLibrary`
|
|
312
|
+
→ `apply` — it needs the `visual` system, so this pairs naturally with the visual-runtime entry above) or fix
|
|
313
|
+
the hand wiring by the world-look rules: per-axis repeat from world size, scalars as multipliers with maps kept
|
|
314
|
+
bound, the whole ORM triple.
|
|
315
|
+
3. **Asset truth:** run the world-look import checklist over the world's third-party GLBs — `metallicFactor`
|
|
316
|
+
present, roughness varying, normal maps with a real blue channel, alpha modes per material, scale proven against
|
|
317
|
+
a known-size object, stray GLB lights stripped.
|
|
318
|
+
4. Rebuild, `validate_world`, capture before/after with `capture_world_screenshot`, and republish when the user is
|
|
319
|
+
ready.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
*Retired entries (whose "applies if" can no longer match any supported world) move out of this document so it stays
|
|
324
|
+
small; this ledger holds only live migrations.*
|