@hypersoniclabs/helix-mcp 0.2.4 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +81 -11
  2. package/dist/continuumCanary.d.ts +17 -0
  3. package/dist/continuumCanary.js +17 -0
  4. package/dist/continuumCanary.js.map +1 -0
  5. package/dist/server.d.ts +14 -1
  6. package/dist/server.js +4058 -162
  7. package/dist/server.js.map +1 -1
  8. package/dist/tsconfig.build.tsbuildinfo +1 -1
  9. package/dist/vehicleTools.d.ts +86 -0
  10. package/dist/vehicleTools.js +229 -0
  11. package/dist/vehicleTools.js.map +1 -0
  12. package/docs/avatar-face.md +115 -0
  13. package/docs/bridge.md +98 -0
  14. package/docs/bring-your-world.md +117 -0
  15. package/docs/catalog.md +69 -1
  16. package/docs/character-animation.md +442 -0
  17. package/docs/character-attachments.md +166 -0
  18. package/docs/character-world.md +785 -130
  19. package/docs/continuum.md +153 -0
  20. package/docs/items.md +73 -0
  21. package/docs/lighting-world.md +667 -0
  22. package/docs/locomotion-clip-spec.md +294 -0
  23. package/docs/manifest.md +31 -6
  24. package/docs/multiplayer-logic.md +460 -17
  25. package/docs/multiplayer-templates/chrono-orchard.md +36 -22
  26. package/docs/multiplayer-templates/collect-a-thon.md +28 -38
  27. package/docs/multiplayer-templates/collections.md +24 -24
  28. package/docs/multiplayer-templates/hangout.md +124 -111
  29. package/docs/multiplayer-templates/npc-wave.md +310 -0
  30. package/docs/multiplayer-templates/obby.md +13 -16
  31. package/docs/multiplayer-templates/persistent-progress.md +218 -0
  32. package/docs/multiplayer-templates/physics-bumper.md +27 -9
  33. package/docs/multiplayer-templates/physics-football.md +22 -8
  34. package/docs/multiplayer-templates/relic-bearers.md +12 -15
  35. package/docs/multiplayer-templates/server-motion.md +16 -19
  36. package/docs/multiplayer-templates/shooter-range.md +275 -0
  37. package/docs/multiplayer-templates/team-control.md +28 -15
  38. package/docs/multiplayer-templates/turn-arena.md +31 -22
  39. package/docs/multiplayer-templates/voice-radio.md +166 -0
  40. package/docs/multiplayer-templates/wave-survival.md +7 -8
  41. package/docs/multiplayer-templates/world-shop.md +240 -0
  42. package/docs/multiplayer-world.md +222 -129
  43. package/docs/npc-world.md +623 -0
  44. package/docs/publishing.md +108 -28
  45. package/docs/purchases.md +223 -0
  46. package/docs/scene-performance.md +64 -0
  47. package/docs/screenshots.md +140 -0
  48. package/docs/sdk.md +324 -5
  49. package/docs/shooter-worlds.md +537 -0
  50. package/docs/terrain.md +173 -0
  51. package/docs/upgrades.md +324 -0
  52. package/docs/vehicles.md +727 -0
  53. package/docs/world-inspect.md +156 -0
  54. package/docs/world-look.md +241 -0
  55. package/docs/world-recipe.md +65 -6
  56. package/package.json +15 -4
  57. package/skills/README.md +91 -0
  58. package/skills/helix-assets/SKILL.md +491 -0
  59. package/skills/helix-assets/references/asset-sources.md +143 -0
  60. package/skills/helix-assets/references/vault-api.md +105 -0
  61. package/skills/helix-avatar-qa/SKILL.md +85 -0
  62. package/skills/helix-avatars/SKILL.md +206 -0
  63. package/skills/helix-avatars/references/contract.md +166 -0
  64. package/skills/helix-avatars/references/dynamics.md +367 -0
  65. package/skills/helix-avatars/references/face.md +50 -0
  66. package/skills/helix-avatars/references/publish.md +76 -0
  67. package/skills/helix-avatars/references/qa.md +251 -0
  68. package/skills/helix-avatars/references/rigging.md +88 -0
  69. package/skills/helix-avatars/references/source-generated.md +190 -0
  70. package/skills/helix-avatars/references/source-model.md +90 -0
  71. package/skills/helix-avatars/references/source-rigid.md +90 -0
  72. package/skills/helix-avatars/references/source-vrm.md +61 -0
  73. package/skills/helix-gauntlet/SKILL.md +128 -0
  74. package/skills/helix-multiplayer/SKILL.md +150 -0
  75. package/skills/helix-multiplayer/references/dsl-capability-map.md +107 -0
  76. package/skills/helix-vehicles/SKILL.md +218 -0
  77. package/skills/helix-vehicles/references/addons.md +212 -0
  78. package/skills/helix-vehicles/references/appearance.md +339 -0
  79. package/skills/helix-vehicles/references/audio-import.md +138 -0
  80. package/skills/helix-vehicles/references/audio.md +580 -0
  81. package/skills/helix-vehicles/references/cabin.md +225 -0
  82. package/skills/helix-vehicles/references/host-manifest.md +174 -0
  83. package/skills/helix-vehicles/references/physics-from-specs.md +463 -0
  84. package/skills/helix-vehicles/references/publish.md +214 -0
  85. package/skills/helix-vehicles/references/qa.md +177 -0
  86. package/skills/helix-vehicles/references/reference-package.json +3481 -0
  87. package/skills/helix-vehicles/references/reference-package.md +69 -0
  88. package/skills/helix-vehicles/references/source-beamng.md +167 -0
  89. package/skills/helix-vehicles/references/source-concept.md +38 -0
  90. package/skills/helix-vehicles/references/source-model.md +100 -0
  91. package/skills/helix-vehicles/references/source-scratch.md +60 -0
  92. package/skills/helix-world-build/SKILL.md +376 -0
  93. package/skills/helix-world-build/references/config-gates.md +104 -0
  94. package/skills/helix-world-director/SKILL.md +210 -0
  95. package/skills/helix-world-qa/SKILL.md +371 -0
  96. package/skills/helix-world-qa/references/perf-budgets.md +240 -0
  97. package/skills/helix-world-qa/references/perf-handle.md +125 -0
  98. package/skills/helix-world-qa/references/visual-scorecard.md +85 -0
@@ -0,0 +1,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.
@@ -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.*