@hypersoniclabs/helix-mcp 0.2.5 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -11
- package/dist/continuumCanary.d.ts +17 -0
- package/dist/continuumCanary.js +17 -0
- package/dist/continuumCanary.js.map +1 -0
- package/dist/server.d.ts +14 -1
- package/dist/server.js +4058 -162
- package/dist/server.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/vehicleTools.d.ts +86 -0
- package/dist/vehicleTools.js +229 -0
- package/dist/vehicleTools.js.map +1 -0
- package/docs/avatar-face.md +115 -0
- package/docs/bridge.md +98 -0
- package/docs/bring-your-world.md +117 -0
- package/docs/catalog.md +69 -1
- package/docs/character-animation.md +442 -0
- package/docs/character-attachments.md +166 -0
- package/docs/character-world.md +785 -130
- package/docs/continuum.md +153 -0
- package/docs/items.md +73 -0
- package/docs/lighting-world.md +667 -0
- package/docs/locomotion-clip-spec.md +294 -0
- package/docs/manifest.md +31 -6
- package/docs/multiplayer-logic.md +460 -17
- package/docs/multiplayer-templates/chrono-orchard.md +36 -22
- package/docs/multiplayer-templates/collect-a-thon.md +28 -38
- package/docs/multiplayer-templates/collections.md +24 -24
- package/docs/multiplayer-templates/hangout.md +124 -111
- package/docs/multiplayer-templates/npc-wave.md +310 -0
- package/docs/multiplayer-templates/obby.md +13 -16
- package/docs/multiplayer-templates/persistent-progress.md +218 -0
- package/docs/multiplayer-templates/physics-bumper.md +27 -9
- package/docs/multiplayer-templates/physics-football.md +22 -8
- package/docs/multiplayer-templates/relic-bearers.md +12 -15
- package/docs/multiplayer-templates/server-motion.md +16 -19
- package/docs/multiplayer-templates/shooter-range.md +275 -0
- package/docs/multiplayer-templates/team-control.md +28 -15
- package/docs/multiplayer-templates/turn-arena.md +31 -22
- package/docs/multiplayer-templates/voice-radio.md +166 -0
- package/docs/multiplayer-templates/wave-survival.md +7 -8
- package/docs/multiplayer-templates/world-shop.md +240 -0
- package/docs/multiplayer-world.md +222 -129
- package/docs/npc-world.md +623 -0
- package/docs/publishing.md +108 -28
- package/docs/purchases.md +223 -0
- package/docs/scene-performance.md +64 -0
- package/docs/screenshots.md +140 -0
- package/docs/sdk.md +324 -5
- package/docs/shooter-worlds.md +537 -0
- package/docs/terrain.md +173 -0
- package/docs/upgrades.md +324 -0
- package/docs/vehicles.md +727 -0
- package/docs/world-inspect.md +156 -0
- package/docs/world-look.md +241 -0
- package/docs/world-recipe.md +65 -6
- package/package.json +15 -4
- package/skills/README.md +91 -0
- package/skills/helix-assets/SKILL.md +491 -0
- package/skills/helix-assets/references/asset-sources.md +143 -0
- package/skills/helix-assets/references/vault-api.md +105 -0
- package/skills/helix-avatar-qa/SKILL.md +85 -0
- package/skills/helix-avatars/SKILL.md +206 -0
- package/skills/helix-avatars/references/contract.md +166 -0
- package/skills/helix-avatars/references/dynamics.md +367 -0
- package/skills/helix-avatars/references/face.md +50 -0
- package/skills/helix-avatars/references/publish.md +76 -0
- package/skills/helix-avatars/references/qa.md +251 -0
- package/skills/helix-avatars/references/rigging.md +88 -0
- package/skills/helix-avatars/references/source-generated.md +190 -0
- package/skills/helix-avatars/references/source-model.md +90 -0
- package/skills/helix-avatars/references/source-rigid.md +90 -0
- package/skills/helix-avatars/references/source-vrm.md +61 -0
- package/skills/helix-gauntlet/SKILL.md +128 -0
- package/skills/helix-multiplayer/SKILL.md +150 -0
- package/skills/helix-multiplayer/references/dsl-capability-map.md +107 -0
- package/skills/helix-vehicles/SKILL.md +218 -0
- package/skills/helix-vehicles/references/addons.md +212 -0
- package/skills/helix-vehicles/references/appearance.md +339 -0
- package/skills/helix-vehicles/references/audio-import.md +138 -0
- package/skills/helix-vehicles/references/audio.md +580 -0
- package/skills/helix-vehicles/references/cabin.md +225 -0
- package/skills/helix-vehicles/references/host-manifest.md +174 -0
- package/skills/helix-vehicles/references/physics-from-specs.md +463 -0
- package/skills/helix-vehicles/references/publish.md +214 -0
- package/skills/helix-vehicles/references/qa.md +177 -0
- package/skills/helix-vehicles/references/reference-package.json +3481 -0
- package/skills/helix-vehicles/references/reference-package.md +69 -0
- package/skills/helix-vehicles/references/source-beamng.md +167 -0
- package/skills/helix-vehicles/references/source-concept.md +38 -0
- package/skills/helix-vehicles/references/source-model.md +100 -0
- package/skills/helix-vehicles/references/source-scratch.md +60 -0
- package/skills/helix-world-build/SKILL.md +376 -0
- package/skills/helix-world-build/references/config-gates.md +104 -0
- package/skills/helix-world-director/SKILL.md +210 -0
- package/skills/helix-world-qa/SKILL.md +371 -0
- package/skills/helix-world-qa/references/perf-budgets.md +240 -0
- package/skills/helix-world-qa/references/perf-handle.md +125 -0
- package/skills/helix-world-qa/references/visual-scorecard.md +85 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# HELIX Instant — World Inspection (layout as data)
|
|
2
|
+
|
|
3
|
+
Screenshots answer "does this read as a plaza"; they measurably cannot answer "is the bench 0.4 m in the
|
|
4
|
+
air" — distances and depth read off an image are unreliable on exactly the axis placement lives on.
|
|
5
|
+
`inspect_world` gives you the world's geometry as data instead: one headless boot returns a serialized
|
|
6
|
+
snapshot of the scene — world positions, extents, oriented boxes, visibility, physics colliders — and every
|
|
7
|
+
placement question is answered from that snapshot with numbers.
|
|
8
|
+
|
|
9
|
+
**The loop: measure with `inspect_world` until the numbers are right, then `capture_world_screenshot` once
|
|
10
|
+
before handing to a human.** Do not answer placement questions by looking harder at a render.
|
|
11
|
+
|
|
12
|
+
## 1. The one-line integration
|
|
13
|
+
|
|
14
|
+
One import and one call, a cheap no-op during normal play — it only activates when the page is loaded with
|
|
15
|
+
`?helixInspect=1`, which `inspect_world` (and the `helix world inspect` CLI command) do for you:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { world } from '@helix/engine-core/inspect';
|
|
19
|
+
// ...at the END of your world's async boot, after the scene is fully built:
|
|
20
|
+
world.ready({ scene, body, spawn: SPAWN });
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`scaffold_world` already wires this in `src/main.ts`. `scene` is required; `body` (your `RapierBody`) enables
|
|
24
|
+
the collider report — without it the snapshot still covers the render side and says so honestly
|
|
25
|
+
(`sources.colliders: "absent"`); `spawn` is optional context. Timing is forgiving: the snapshot walks the
|
|
26
|
+
live scene when taken, so registering early still captures everything built afterwards.
|
|
27
|
+
|
|
28
|
+
Needs `engine-core >= 0.1.2` (and `humanoid-character >= 0.2.13` for the collider report).
|
|
29
|
+
|
|
30
|
+
## 2. What a report contains
|
|
31
|
+
|
|
32
|
+
- **anomalies** (the default section) — placement findings, each with the measured numbers, the method that
|
|
33
|
+
produced it, and a confidence: floating / sunk geometry (vs the character's 0.35 m step height),
|
|
34
|
+
intersecting geometry (true oriented-box overlap, not loose bounding boxes), collider-vs-visual divergence
|
|
35
|
+
— including the classic half-extent/full-size slip, where a collider is exactly 2× its visual because full
|
|
36
|
+
sizes were passed where half-extents belong — orphan colliders (invisible walls) and un-collided visuals,
|
|
37
|
+
mirrored or degenerate transforms, and stray far-away coordinates (a `1e6` typo).
|
|
38
|
+
- **roster** — the grouped object inventory: 60 identical trees collapse to one row with a count, size,
|
|
39
|
+
triangle count, and spread. Ask for it when you need to know what is actually in the scene and where.
|
|
40
|
+
- **colliders** — the physics roster: every collider with shape, extents, and position. The only way to SEE
|
|
41
|
+
collision geometry — colliders never render, so a screenshot cannot debug an invisible wall.
|
|
42
|
+
- **zones** — the declared `multiplayer.json` trigger volumes, each with its extents, the geometry it touches,
|
|
43
|
+
and its **rest surfaces** (surfaces a standing player could occupy while inside it). Zones are doubly
|
|
44
|
+
invisible — they never render AND they are usually off-camera — so this is the only check that catches a
|
|
45
|
+
kill band the pit floor misses, a checkpoint volume floating in empty space, or a zone centered on a stray
|
|
46
|
+
coordinate. Zone findings (`zone-empty`, `zone-fly-through`, `zone-out-of-bounds`, spawn-inside-zone) join
|
|
47
|
+
the anomalies section automatically whenever the bundle carries a `multiplayer.json`. The same
|
|
48
|
+
intent rule applies: a tall abyss band under a floorless arena is a design pattern, not a bug — the report
|
|
49
|
+
says which reading applies.
|
|
50
|
+
|
|
51
|
+
Request sections via `report: ["anomalies", "roster", "colliders"]`; `json: true` returns the raw
|
|
52
|
+
`{ snapshot, analysis }` for programmatic follow-up on exact numbers.
|
|
53
|
+
|
|
54
|
+
## 3. Findings are measurements, not verdicts
|
|
55
|
+
|
|
56
|
+
Every finding is a true statement about geometry — whether it is a PROBLEM depends on what you meant.
|
|
57
|
+
Perfectly good worlds keep warnings on purpose:
|
|
58
|
+
|
|
59
|
+
- a collectible gem hovering 1.2 m above the ground → an intended float, not a floating bug;
|
|
60
|
+
- a tilted ramp whose base dips below the ground plane → an intended embed, not sunk;
|
|
61
|
+
- arena walls overlapping at their ends → corner joints, standard construction (uniform-depth overlaps are
|
|
62
|
+
collapsed into one summary line for exactly this reason).
|
|
63
|
+
|
|
64
|
+
**The rule: a warn that matches what you meant needs no fix; a warn that surprises you is the bug.** Never
|
|
65
|
+
edit geometry just to silence a finding — that trades real design for a quiet report. What the checks are
|
|
66
|
+
FOR is the surprise: the bench you believed was grounded but floats, the platform whose collider is twice
|
|
67
|
+
its size, the wall segment left at the origin.
|
|
68
|
+
|
|
69
|
+
Two mechanisms keep a dense hand-built world readable instead of drowning the one real defect:
|
|
70
|
+
|
|
71
|
+
- **Run collapse (automatic).** Three or more same-parent siblings with the same finding against the same
|
|
72
|
+
support and uniform gaps fold into one line — `33 children of Group#12 floating 1.75–1.96 m above
|
|
73
|
+
Mesh#38 … adjudicate as one`. A member that breaks the run's gap band keeps its own line, because in a
|
|
74
|
+
hanging run the bulb 2 m below its siblings IS the finding. Composite-model internals collapse per model
|
|
75
|
+
the same way.
|
|
76
|
+
- **The baseline (your adjudication, made durable).** After checking the findings against intent, pass
|
|
77
|
+
`writeBaseline: true` once — every finding is accepted into `inspect.baseline.json` beside the project.
|
|
78
|
+
From then on inspect reports only what CHANGED: new findings, plus accepted entries that no longer occur
|
|
79
|
+
(prune those with a fresh `writeBaseline`). Entries key on rule + world-space anchor — never on paths —
|
|
80
|
+
so they survive rebuilds. Suggest the write to the user before doing it: accepting findings is a
|
|
81
|
+
judgement, not a reflex. `showAccepted: true` renders the hidden ones when you need the full picture.
|
|
82
|
+
|
|
83
|
+
Object paths in reports are **canonicalized** — auto-named `Type#N` segments are renumbered in content
|
|
84
|
+
order (by each subtree's centroid), not scene-traversal order, so the same object keeps the same path
|
|
85
|
+
across runs even when async loads shift traversal. Coordinates remain the strongest cross-run handle.
|
|
86
|
+
|
|
87
|
+
## 4. What this cannot tell you
|
|
88
|
+
|
|
89
|
+
Whether the scene READS as intended: materials, lighting mood, color, animation, art direction, atmosphere.
|
|
90
|
+
That is the closing `capture_world_screenshot` check. The snapshot also reads bounding boxes, not exact
|
|
91
|
+
surfaces — non-box shapes are over-approximated, and each finding's stated method and confidence say so.
|
|
92
|
+
|
|
93
|
+
## 5. Checking ONE object — the focus lens
|
|
94
|
+
|
|
95
|
+
After placing or moving a single object, you do not need the whole report — focus the lens on the spot you
|
|
96
|
+
just wrote into the code:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
inspect_world({ directory, focusNear: [10, 0.5, 3] })
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
You get back exactly what is at that coordinate: each matching object's geometry/color, exact position, size,
|
|
103
|
+
world AABB, its FACING — yaw plus the world direction its +Z forward points, and horizontal
|
|
104
|
+
`TOWARD / AWAY from / side-on to` relations against the nearest objects (floors are skipped — you stand on
|
|
105
|
+
them, you do not face them). A chair rotated 180° from its table reads `faces: AWAY from table (0.8 m)` —
|
|
106
|
+
orientation defects are invisible in a position/AABB listing and easy to miss in a screenshot. Also reported:
|
|
107
|
+
whether any collider touches it ("walk-through unless intended" when none does), and only the
|
|
108
|
+
findings that involve it — with the world's total finding count stated so nothing is silently hidden. If
|
|
109
|
+
nothing matches, the three nearest objects are listed with distances, which answers both failure modes at once
|
|
110
|
+
(the object landed somewhere else vs it never made it into the scene). Analysis always runs whole-world —
|
|
111
|
+
correctness is relational — the lens only scopes the report.
|
|
112
|
+
|
|
113
|
+
No naming is required: you know the coordinates because you wrote them. Unnamed meshes report as `Mesh#N`;
|
|
114
|
+
imported GLB assets keep their authoring-tool node names for free. Optionally set `mesh.name = 'bench'` on a
|
|
115
|
+
few landmark objects if you want findings and `focus: "bench"` to read by name — but do not name everything;
|
|
116
|
+
it is authoring noise the tools do not need.
|
|
117
|
+
|
|
118
|
+
## 6. Sizing geometry BEFORE you place it — `world_metrics`
|
|
119
|
+
|
|
120
|
+
`world_metrics({ projectDir })` returns the traversal/movement reference from the world's own installed
|
|
121
|
+
character system: capsule height/radius, the 0.35 m step height (taller blocks; shorter auto-steps), the 50°
|
|
122
|
+
walkable slope limit, movement speeds, and the derived jump envelope — air time and the maximum flat-ground
|
|
123
|
+
gap at run and sprint speed — plus corridor and ceiling clearances. Read it instead of guessing whether a gap
|
|
124
|
+
is jumpable or a doorway fits; values are the system defaults a world starts from (worlds may override via
|
|
125
|
+
config). The CLI pair is `helix world metrics -C <projectDir>`.
|
|
126
|
+
|
|
127
|
+
## 7. Comparing before and after an edit
|
|
128
|
+
|
|
129
|
+
`inspect_world({ json: true })` returns the raw snapshot; the CLI pair `helix world inspect <dist> --out
|
|
130
|
+
a.json` … `helix world diff a.json b.json` reports what moved (per-axis deltas), what appeared/disappeared,
|
|
131
|
+
and which findings appeared or cleared — the regression check for a layout edit.
|
|
132
|
+
|
|
133
|
+
## 8. Timing and determinism
|
|
134
|
+
|
|
135
|
+
The snapshot is taken after a settle delay (default 750 ms past world-ready). A world whose props animate
|
|
136
|
+
into place may need more: pass `settleMs`. Objects that animate forever (a spinning pickup) snapshot at
|
|
137
|
+
whatever pose they hold that frame — expect their numbers to differ between runs.
|
|
138
|
+
|
|
139
|
+
## 9. Adding the helper to an existing world
|
|
140
|
+
|
|
141
|
+
Almost every world scaffolded before the inspect helper existed is missing the `world.ready` call — and a
|
|
142
|
+
fix-up session on such a world cannot measure anything until it lands. `check_for_updates({ projectDir })`
|
|
143
|
+
reports the wiring state up front ("engine-core is pinned but src never calls world.ready"), which is cheaper
|
|
144
|
+
than discovering it through an inspect timeout. The upgrade is small and additive:
|
|
145
|
+
|
|
146
|
+
1. `"engine-core": "^0.1.2"` in `public/helix.json` → `systems` (alongside `humanoid-character`), then
|
|
147
|
+
`install_world_packages` (or the CLI's `helix install`).
|
|
148
|
+
2. If `vite.config.ts` predates the alias, add this line to `resolve.alias` BEFORE the root
|
|
149
|
+
`@helix/engine-core` entry — subpath aliases must precede the root alias, or rollup's prefix
|
|
150
|
+
substitution mangles the import into an ENOENT:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
'@helix/engine-core/inspect': fileURLToPath(new URL('./public/helix_modules/engine-core/inspect/index.js', import.meta.url)),
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
3. The two lines from §1 in `src/main.ts`, then rebuild (`npm run build`) and `inspect_world` as usual.
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# The look of a world — from raw assets to good
|
|
2
|
+
|
|
3
|
+
The QA gates can tell you a world fails to look right (`materials.default-plastic`, `composition.primitive-dominant`,
|
|
4
|
+
`lighting.unmotivated`). This page is how to make it look right: the process the platform maintainers converged on
|
|
5
|
+
while calibrating real production scenes (Amazon's Bistro, Crytek and Intel Sponza) against the visual runtime — and
|
|
6
|
+
the mistakes they made first, so you do not repeat them. Every number below was measured on those scenes.
|
|
7
|
+
|
|
8
|
+
Territory: lighting itself — the runtime, `helix.visuals.json`, baked vs dynamic, quality tiers — is
|
|
9
|
+
`read_doc({ name: "lighting-world" })`. Where assets COME FROM (the sourcing ladder, budgets, loaders) is the
|
|
10
|
+
`helix-assets` skill; the six build non-negotiables are `helix-world-build`; what blocks a ship is `helix-world-qa`.
|
|
11
|
+
This page owns what sits between: **asset truth, material consumption, texture hygiene, scene assembly, composition,
|
|
12
|
+
and judging your own captures.** The platform doctrine is *measure first, look last* — this page is the LOOK half.
|
|
13
|
+
|
|
14
|
+
## Fix defects before taste
|
|
15
|
+
|
|
16
|
+
Raw assets are usually **broken**, not merely unstyled. Bistro — a professionally authored, industry-standard
|
|
17
|
+
showcase scene — shipped to the calibration bench with the wrong scale, all-metal materials, dead normal maps and
|
|
18
|
+
mip-less textures. Styling on top of defects teaches you wrong numbers everywhere downstream: you crank a light to
|
|
19
|
+
fight metalness, then crank the grade to fight the light. The maintainers' rule, verbatim: **a knob that does nothing
|
|
20
|
+
or behaves nonlinearly is a defect signal, not a tuning challenge.** Find the defect; do not compensate.
|
|
21
|
+
|
|
22
|
+
So the work has a fixed order, and taste comes last:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
1. ASSET TRUTH — the import checklist below, per asset, before anything is styled
|
|
26
|
+
2. GEOMETRY + SUN — measure the space, then aim the light at it
|
|
27
|
+
3. MATERIALS — consumed correctly (per-axis repeat, multiplier semantics, whole ORM)
|
|
28
|
+
4. GRADE, LAST — and inside the grade: power (contrast) first, saturation second, slope last
|
|
29
|
+
5. VERIFY — captures from more than one camera; on a cycling world, more than one hour
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Iterate with one knob at a time, and re-look after each change. And the meta-rule that has paid for itself more than
|
|
33
|
+
any other: **measure whether a knob reaches the frame — do not trust a comment, including your own.** Take a capture,
|
|
34
|
+
change the value, take another; if nothing moved, you found a defect (or the knob is not yours — see the
|
|
35
|
+
lighting-world DO/DON'T table).
|
|
36
|
+
|
|
37
|
+
## Asset truth — the import checklist
|
|
38
|
+
|
|
39
|
+
Run this on every imported GLB or third-party asset before styling. Each row is a defect that shipped in a
|
|
40
|
+
professional scene and survived until someone measured.
|
|
41
|
+
|
|
42
|
+
1. **Scale, proven — never assumed.** The platform is metric and the avatar is a real ~1.8 m human; shadow bias, fog
|
|
43
|
+
density and the probe volume are all metre-derived, so a mis-scaled world silently degrades all of them. Bistro's
|
|
44
|
+
exterior shipped **1.6× oversized** — proven by a shared liquor-bottle mesh measuring 0.580 m in one sub-scene and
|
|
45
|
+
0.364 m in another. Prove scale with a known-size object: a bottle, a table (~0.75 m), a doorway (~2.1 m).
|
|
46
|
+
2. **`metallicFactor` present.** glTF's default for a MISSING `metallicFactor` is **1.0** — full metal, no diffuse
|
|
47
|
+
term at all. All 132 of Bistro's materials omitted it: cobbles, plaster, foliage and fabric arrived as mirrors
|
|
48
|
+
with their base colour re-read as specular tint, and — because diffuse GI lights only diffuse surfaces — the
|
|
49
|
+
probe field lit **literally nothing**: the frame rendered byte-identical with the GI removed. If an imported
|
|
50
|
+
world looks dark, oddly tinted, and GI seems to do nothing, check metalness FIRST. Set `metalness: 0` on
|
|
51
|
+
everything that is not actually metal.
|
|
52
|
+
3. **Roughness varies.** One global roughness constant across a whole asset (Bistro: 0.5527864 on all 132 materials)
|
|
53
|
+
is a conversion artifact, not authoring. Expect a roughness map or per-material values.
|
|
54
|
+
4. **Normal maps carry a real blue channel.** Bistro's normal maps were two-channel with blue zeroed — the decoded
|
|
55
|
+
normal pointed INTO the surface, so up-facing geometry refused sun. Symptom: surface detail lit from the wrong
|
|
56
|
+
direction, or normal maps that seem to darken. The cheap convention test (DirectX vs OpenGL green): flip
|
|
57
|
+
`material.normalScale.y` and see whether the detail snaps right.
|
|
58
|
+
5. **Colorspaces are per texture.** Albedo/emissive = sRGB; normal, ORM, roughness, anything data = linear. A linear
|
|
59
|
+
albedo reads washed out; an sRGB normal map reads lumpy. Platform materials arrive correctly tagged; hand-wired
|
|
60
|
+
textures are where this goes wrong.
|
|
61
|
+
6. **Alpha mode is a per-material decision.** Near-binary foliage alpha → `alphaTest` (MASK); liquids and glass stay
|
|
62
|
+
BLEND — Bistro's wine and beer forced to cutout render as garbage. And do not assume foliage means alpha at all:
|
|
63
|
+
Intel's ivy is opaque modelled geometry with no alpha channel. Blended foliage on thousands of triangles will
|
|
64
|
+
sort-artifact; that is what MASK is for.
|
|
65
|
+
7. **No stray lights, no double-counting.** Imported GLBs carry lights you did not ask for (Intel's Sponza ships 24)
|
|
66
|
+
— strip them on load. Never hide one with `visible = false`: a glTF light node may PARENT its fixture's meshes,
|
|
67
|
+
and an invisible node's children stop rendering with it. Remove the light from the scene or layer-mask it. And
|
|
68
|
+
never represent one light twice — an emissive mesh is not a light source (it glows, it does not illuminate), and
|
|
69
|
+
a hand-placed "sun" on a runtime-lit world double-lights the whole frame.
|
|
70
|
+
|
|
71
|
+
## Geometry first, then the sun
|
|
72
|
+
|
|
73
|
+
Aim the light at the space you actually built — measured, not eyeballed. A courtyard, atrium, alley or roofed street
|
|
74
|
+
has an **elevation floor**: a sun below it never reaches the ground, and the transition is a cliff, not a slider.
|
|
75
|
+
Sponza's courtyard measures 16 × 5.6 m at 14.4 m deep — a well; direct sun reaches its pavement only above ~61°
|
|
76
|
+
elevation, and the first auto-derived sun left **0.14 % of the frame lit**, with the whole scene running on blue sky
|
|
77
|
+
ambient. That is the "flat, dim, bluish" signature: not a grade problem, an aim problem.
|
|
78
|
+
|
|
79
|
+
On a runtime-lit world you aim with two fields in `helix.visuals.json`: `noonAzimuthDeg` (the one knob that aligns
|
|
80
|
+
the sun path to YOUR geometry — set it first) and `hours`. Sanity-check against the shadows you want: pick the
|
|
81
|
+
façade the sun should rake, verify the interior you care about actually receives light at your chosen hour, and
|
|
82
|
+
re-check at a second hour if the world cycles. If an interior must read sunlit, the opening it receives sun through
|
|
83
|
+
is part of your geometry design, not something the grade can fake later. And if the aim is right but a bounded
|
|
84
|
+
interior still reads washed and flat, the defect is the sun-to-ambient RATIO, not the aim and not the grade — the
|
|
85
|
+
fix is the transport knobs, in lighting-world's "Calibrating a high-contrast interior" recipe. The same recipe
|
|
86
|
+
covers the opposite complaint: an interior that reads too DARK once its bake is honest is short of BOUNCES, not of
|
|
87
|
+
exposure — `probes.bounces` 2–3 and `sky.irradianceScale` are the dials, and `exposure.key` / `exposure.postGain`
|
|
88
|
+
develop the result afterwards.
|
|
89
|
+
|
|
90
|
+
Two scene-side facts complete the picture:
|
|
91
|
+
|
|
92
|
+
- **Scene-side dynamic range is half the job.** The tone mapper cannot manufacture contrast the render does not
|
|
93
|
+
contain: a scene where grass, cobble and concrete all sit near the same albedo, with no deep shade anywhere,
|
|
94
|
+
measured ~1 stop of usable range against Bistro's 3.5–4. Vary your albedos and build real occluded volumes —
|
|
95
|
+
doorways, overhangs, interiors — or the world will read flat under any grade.
|
|
96
|
+
- **Sealed interiors.** A single-sided or paper-thin wall lets sky light leak into shaded space (the bake samples
|
|
97
|
+
through backfaces): the signature is a blue-white wash on surfaces that should be in shadow. Give interior shells
|
|
98
|
+
thickness or double-sided walls, keep large geometry off exact probe-grid planes, and expect thin cards and
|
|
99
|
+
single-quad walls to need deliberate shadow treatment (`material.shadowSide`, or `castShadow = false` on a shell
|
|
100
|
+
that would otherwise fill the shadow map with its own far face). An interior that will BAKE also has to fit the
|
|
101
|
+
probe volume to its room (`probes: { center, size }` in `helix.visuals.json`): placement and extent are authorable,
|
|
102
|
+
the probe budget is not — which is why a very large map, spreading the same probes thinner, ships `dynamic`
|
|
103
|
+
instead. The numbers, and the ceiling-wash defect this prevents, are in `read_doc({ name: "lighting-world" })`.
|
|
104
|
+
|
|
105
|
+
## Platform materials — consuming them correctly
|
|
106
|
+
|
|
107
|
+
A real HELIX agent once built a world on the platform material pack and it still looked wrong. The audit found the
|
|
108
|
+
textures were fine — the agent had re-implemented a material library by hand, got ~80 % right, and the subtle 20 %
|
|
109
|
+
ruined it: tiling written as if UVs were metres, scalar overrides that silently discarded the roughness/metalness
|
|
110
|
+
maps, the AO channel never bound. Every rule below exists because of that world or one like it.
|
|
111
|
+
|
|
112
|
+
**The taught path** — the visual system ships a material library that gets all of it right by construction.
|
|
113
|
+
Setup, once per world: add `"visual": "^0.1"` to the `systems` block in `helix.json`, then run
|
|
114
|
+
`install_world_packages` with `update: true` (terminal: `helix install --update`) — a newly added pin is refused
|
|
115
|
+
until the lock is re-resolved.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { loadMaterialLibrary } from '@helix/visual';
|
|
119
|
+
|
|
120
|
+
// assetBaseUrl = the pack base: `assetBaseUrl` in use_material's output (`helix assets material <id>`).
|
|
121
|
+
const materials = await loadMaterialLibrary(assetBaseUrl, { renderer });
|
|
122
|
+
|
|
123
|
+
// A 20 x 12 m floor: apply() measures the mesh (and its world scale) and derives per-axis repeat
|
|
124
|
+
// from the material's physical tile size — no repeat arithmetic in world code, ever.
|
|
125
|
+
// Boxes, planes, spheres, cylinders and capsules are measured exactly.
|
|
126
|
+
const floor = new THREE.Mesh(new THREE.PlaneGeometry(20, 12));
|
|
127
|
+
materials.apply(floor, 'cobblestone');
|
|
128
|
+
|
|
129
|
+
// Imported geometry with unknown UVs: state the metres its 0..1 UV span covers. Without `size`, apply()
|
|
130
|
+
// can only estimate from the bounding box, which stretches anything whose UVs are not laid out that way.
|
|
131
|
+
materials.apply(importedWall, 'painted-wood', { size: [8, 3] });
|
|
132
|
+
|
|
133
|
+
// Hand the library's readiness to the visual runtime (the assetsReady option in the boot snippet)
|
|
134
|
+
// so no lighting is computed from half-loaded textures.
|
|
135
|
+
await materials.ready();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Worlds not on the visual system load the same catalog through the SDK — `Helix.materials.load(assetBaseUrl)`, then
|
|
139
|
+
`library.resolve(id)` for absolute map URLs and authored defaults — and wire the maps themselves. If you do that,
|
|
140
|
+
these are the rules the hand-rolled library broke:
|
|
141
|
+
|
|
142
|
+
| DON'T | DO | Why |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| `texture.repeat.set(s, s)` with s derived from "tiles per metre" | derive repeat PER AXIS from world size: `repeat = faceMetres / tileMetres` for each axis | stock geometry UVs span 0..1 across the whole face — they are not metres. The square set() smeared ~45 % of ONE tile across a 34 m slab, and ignores face aspect, stretching every non-square face |
|
|
145
|
+
| pass `roughness: 0.8` and drop the map (or null it "to be safe") | keep the maps bound; scalars are three.js MULTIPLIERS over them — with real maps bound, set the scalars to 1 | nulling the map throws the authored data away; and a catalog value authored as an absolute double-attenuates when multiplied into the map |
|
|
146
|
+
| bind only the albedo | bind the whole ORM triple — one texture into `aoMap`, `roughnessMap` AND `metalnessMap` | the pack's ORM is R=occlusion, G=roughness, B=metalness; binding one channel wastes the other two (the r185 rule "aoMap needs uv2" is dead — every map samples through `.channel`) |
|
|
147
|
+
| grab Vault `kind=texture` results as world textures | the material pack is the FIRST rung (`list_materials` → `use_material`) | Vault loose textures are flat single images, several with baked-in lighting — a diffuse photo applied as `map` on a default material is exactly "supposed to be PBR but does not look PBR" |
|
|
148
|
+
| request 2K everywhere | 2K on hero surfaces the camera gets close to; the default elsewhere | resolution is a budget; distant and background surfaces cannot show the difference |
|
|
149
|
+
|
|
150
|
+
The pack's authored values (`roughness`, `metalness`, `normalScale`, the physical tile size, glass/water parameters)
|
|
151
|
+
are calibrated as a set — trust them first, and tune only after the checklist above is clean.
|
|
152
|
+
|
|
153
|
+
## Texture hygiene
|
|
154
|
+
|
|
155
|
+
The single highest-leverage image-quality fact on the platform: **mips + anisotropy measured +5.06 dB / +0.153 SSIM
|
|
156
|
+
— about three times the benefit of the entire anti-aliasing ladder.** Shimmer, moiré and texture crawl are texture
|
|
157
|
+
defects; no AA setting fixes them. Check these before ever blaming the quality tier:
|
|
158
|
+
|
|
159
|
+
- **Mips must live inside the KTX2 container.** three cannot generate mipmaps for compressed textures — a mip-less
|
|
160
|
+
KTX2 shimmers at grazing angles and reads full-resolution at any distance, forever. 622 of Bistro's 637 textures
|
|
161
|
+
shipped mip-less. Vault uploads of mip-less KTX2 are now rejected at publish (a hard error), but **world bundles
|
|
162
|
+
only WARN** at `helix validate` — a shimmering file ships legally, so read your warnings.
|
|
163
|
+
- **Never hand-encode KTX2 into a bundle.** The platform importers pass the mip flag, cap sizes and pad correctly;
|
|
164
|
+
hand-encoded files are how mip-less textures happen. Feed the pipeline source images (PNG/JPG/WebP) instead.
|
|
165
|
+
- **Never a non-mipmap `minFilter`.** With `minFilter: LinearFilter` the driver ignores anisotropy entirely —
|
|
166
|
+
anisotropy 1, 8 and 16 render byte-identical. Inside a glTF the sampler wins over the loader, so an exporter can
|
|
167
|
+
commit this silently; `helix validate` warns on it.
|
|
168
|
+
- **Alpha-tested foliage needs coverage-preserving alpha mips**, or leaves thin out and vanish with distance —
|
|
169
|
+
a separate defect from cutout edge-crawl, and `alphaToCoverage` does not fix it.
|
|
170
|
+
- **Old Vault assets predating the mip gate still shimmer** — they are grandfathered, not fixed. Re-publishing the
|
|
171
|
+
asset through the pipeline is the fix.
|
|
172
|
+
|
|
173
|
+
On runtime-lit worlds, anisotropy itself is the runtime's job (set per quality tier at construction) — your job is
|
|
174
|
+
not to break it with the two DON'Ts above.
|
|
175
|
+
|
|
176
|
+
## Composition — building a frame worth capturing
|
|
177
|
+
|
|
178
|
+
This is the territory the QA scorecard grades (rows 3–5) and nothing else teaches:
|
|
179
|
+
|
|
180
|
+
- **Layer the space: foreground, midground, background.** A flat plane with objects scattered at one distance reads
|
|
181
|
+
as a diorama. Give the camera something near, something at play distance, and something far (a skyline, a wall
|
|
182
|
+
with depth cues, terrain) — and include **scale cues** the player reads instinctively: doorways, railings,
|
|
183
|
+
furniture, lamps, all at real metric sizes against the ~1.8 m avatar.
|
|
184
|
+
- **Repeated scenery ships instanced — mesh count is the ceiling, not triangles.** Every draw call costs
|
|
185
|
+
roughly constant main-thread dispatch (~15 µs, measured) however small the mesh: the platform's ≤300-call
|
|
186
|
+
budget is already ~4.5 ms of a 16.7 ms frame, and every extra hundred calls is another ~1.5 ms gone before
|
|
187
|
+
a pixel shades. An agent-built world shipped its skyline as ~1,200 individual meshes — every lit tower
|
|
188
|
+
window and water glint its own object — for only 72k triangles and 19.7 ms of draw dispatch: **45 fps
|
|
189
|
+
facing the windows, 60 looking away.** So anything you place in a loop (windows, glints, star fields,
|
|
190
|
+
foliage cards, crowds of repeated props) is ONE `InstancedMesh` per material family, per-instance colour
|
|
191
|
+
for variety — the same frame, a handful of draws. When frames are slow, **count draw calls first** (the
|
|
192
|
+
`?perf=1` handle and `helix world perf-gate` both report them) and remedy in order: instance repeated
|
|
193
|
+
families, merge static same-material geometry, and only then reach for resolution or quality levers. The
|
|
194
|
+
cost only shows when the camera FACES the population, so captures pointed elsewhere read clean — which is
|
|
195
|
+
why this is authored up front, not discovered at QA.
|
|
196
|
+
- **Material ROLES, not per-object one-offs.** Good scenes reuse a small palette by role — one floor family, one
|
|
197
|
+
wall family, a trim material on edges and openings, an accent — with wear where hands and feet actually go. A
|
|
198
|
+
world where every object carries its own unrelated material reads as noise; the scorecard's "cohesive material
|
|
199
|
+
language" is this, and reuse is also what keeps the texture budget honest.
|
|
200
|
+
- **Contrast is authored.** Deep shade, bright openings, albedo variation between families (see "scene-side dynamic
|
|
201
|
+
range" above). If every surface is mid-grey, no lighting or grading rescues it.
|
|
202
|
+
- **Practical lights follow the budget patterns** — the emissive-mesh-plus-real-light pairing, the layer-masked
|
|
203
|
+
night rig, and the eight-light ceiling are in the `helix-world-build` skill and the lighting-world night note;
|
|
204
|
+
composition-wise the rule is that light placement is motivated by fixtures the player can see.
|
|
205
|
+
|
|
206
|
+
## Judging your own captures
|
|
207
|
+
|
|
208
|
+
`capture_world_screenshot` is the closing check — after the numbers (placement via `inspect_world`, traversal via
|
|
209
|
+
`world_metrics`) are already right. When you look, know what a capture can and cannot answer, and iterate honestly:
|
|
210
|
+
|
|
211
|
+
- **One well-chosen angle beats a burst.** Frame the world at active-play framing (what a player actually sees),
|
|
212
|
+
not a turntable beauty angle. Check the mobile viewport too — composition that reads at desktop can collapse at
|
|
213
|
+
390 px wide.
|
|
214
|
+
- **Change one knob, capture before/after, compare.** A look opinion formed across two captures with three changes
|
|
215
|
+
between them attributes improvement to the wrong knob.
|
|
216
|
+
- **What to look for**, in order: Is anything obviously BROKEN (black surfaces = missing maps or all-metal; lit
|
|
217
|
+
interior walls that should be dark = leaks; wrong-scale props against the avatar)? Does the frame have depth
|
|
218
|
+
layers and deep shade? Do materials read as their substance at play distance? Does light direction match the
|
|
219
|
+
fixtures and sky the player can see? Only then: mood, grade, art direction.
|
|
220
|
+
- **References have limited authority.** A photograph settles *relative* falloff and material behaviour, never
|
|
221
|
+
absolute brightness (your exposure is auto-derived — bias or pin it through lighting-world's `exposure` block,
|
|
222
|
+
not by matching a photo's levels); an engine-demo screenshot settles sun side and gross framing
|
|
223
|
+
and is often a LOW quality bar, not an aspiration; a render made without GI answers framing only — never brightness
|
|
224
|
+
or colour. Do not tune your world to match a reference's property that the reference cannot testify about.
|
|
225
|
+
|
|
226
|
+
## The hard boundary — no fake light transport
|
|
227
|
+
|
|
228
|
+
The platform's ground rule, and it is craft, not just a gate: **light in a HELIX world is real transport.** No fill
|
|
229
|
+
lights standing in for GI — the runtime provides real indirect light (sky, probes, bounces); a hand-placed fill
|
|
230
|
+
"because the corner is dark" fights it and reads wrong the moment the sun moves. (The sanctioned dials for the
|
|
231
|
+
GLOBAL balance are the transport knobs — `sun.intensityScale`, `sky.irradianceScale`, `environment.intensity`,
|
|
232
|
+
`shadows.opacity` and, for indirect light specifically, `probes.bounces` in lighting-world — plus `exposure.key` /
|
|
233
|
+
`exposure.postGain` to develop the frame. Never an extra light: a second bounce is something the bake can compute,
|
|
234
|
+
and a fill light is a guess at it.) No fog, darkness, bloom or particle
|
|
235
|
+
soup hiding missing geometry — the scorecard fails it on sight, and it always reads as what it is. If a corner is
|
|
236
|
+
too dark, the fixes are real: an opening that admits light, a practical fixture with a real light, a lighter albedo,
|
|
237
|
+
or accepting that some corners are dark because that is what makes the lit parts read.
|
|
238
|
+
|
|
239
|
+
Where to go deeper: `read_doc({ name: "lighting-world" })` for everything the light rig itself does;
|
|
240
|
+
`list_materials` / `use_material` for the palette; the `helix-assets` skill for sourcing anything you do not have;
|
|
241
|
+
the `helix-world-qa` skill for proving the result.
|
package/docs/world-recipe.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# HELIX Instant — World Project Recipe
|
|
2
2
|
|
|
3
|
+
After the first build or Scene import, call `analyze_scene_performance` before visual polish. It returns estimated reference-budget utilization and concrete fixes while leaving unknown runtime costs unknown. Repeat it with the final Scene, Package, inspection snapshot, and performance receipt; `read_doc({ name: "scene-performance" })` defines every counter and the matched fullscreen visual QA.
|
|
4
|
+
|
|
3
5
|
Follow this recipe exactly to generate a **publishable HELIX Instant world**. A world is a static web app (Vite-built) that runs sandboxed in the HELIX portal and talks to the platform only through `@hypersoniclabs/helix-sdk`.
|
|
4
6
|
|
|
5
7
|
## Project layout
|
|
@@ -27,11 +29,11 @@ my-world/
|
|
|
27
29
|
"build": "vite build"
|
|
28
30
|
},
|
|
29
31
|
"dependencies": {
|
|
30
|
-
"@hypersoniclabs/helix-sdk": "{{SDK_DEP_SPEC}}"
|
|
31
|
-
"three": "^0.172.0"
|
|
32
|
+
"@hypersoniclabs/helix-sdk": "{{SDK_DEP_SPEC}}"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
|
-
"@types/three": "^0.
|
|
35
|
+
"@types/three": "^0.185.1",
|
|
36
|
+
"three": "^0.185.1",
|
|
35
37
|
"typescript": "~5.7.3",
|
|
36
38
|
"vite": "^6.0.0"
|
|
37
39
|
}
|
|
@@ -40,6 +42,8 @@ my-world/
|
|
|
40
42
|
|
|
41
43
|
Three.js is the usual choice but any web stack works — the only hard requirements are the manifest, the SDK, and a static `dist/` output.
|
|
42
44
|
|
|
45
|
+
`three` belongs under `devDependencies`, not `dependencies`: nothing installs a world's npm tree at runtime. Either Vite bundles three into `dist/` (a bare scene like this one) or the platform hosts it and `helix install` import-maps the bare specifier (any world that pins a system — see the character recipe). Keep it on the platform's line, **0.185.1**: a world that pins a system and targets an older three still publishes, but `helix validate`/`publish` warn and name the upgrade path.
|
|
46
|
+
|
|
43
47
|
## vite.config.ts — `base: './'` is REQUIRED
|
|
44
48
|
|
|
45
49
|
```ts
|
|
@@ -52,6 +56,13 @@ export default defineConfig({
|
|
|
52
56
|
});
|
|
53
57
|
```
|
|
54
58
|
|
|
59
|
+
**And never write a root-absolute URL for anything inside your own bundle.** `base: './'` only fixes the
|
|
60
|
+
URLs *Vite* emits. Any path you write by hand — `fetch('/data.json')`, `new Audio('/theme.mp3')`,
|
|
61
|
+
`loader.load('/models/ship.glb')` — still resolves against the SITE root, which is not where a published
|
|
62
|
+
world lives (`…/instant-worlds/<id>/<build>/`). It 404s and usually throws, while working perfectly in
|
|
63
|
+
local preview. Resolve against the document instead: `new URL('models/ship.glb', document.baseURI).href`.
|
|
64
|
+
`verify-subpath` (below) is what proves you got them all.
|
|
65
|
+
|
|
55
66
|
## public/helix.json — the manifest
|
|
56
67
|
|
|
57
68
|
```json
|
|
@@ -91,6 +102,10 @@ Rules that commonly bite:
|
|
|
91
102
|
</html>
|
|
92
103
|
```
|
|
93
104
|
|
|
105
|
+
**On-screen UI (HUD).** The player shell overlays a chrome bar across the **top-center** of every world (~top 56px:
|
|
106
|
+
Exit / Save Progress / helixOS — user-hideable, but shown by default). Any HUD / DOM overlay you add must stay OUT of
|
|
107
|
+
that strip: anchor it to a corner (top-left/right) or the bottom, never top-center, or it renders behind the chrome.
|
|
108
|
+
|
|
94
109
|
## src/main.ts — SDK integration pattern
|
|
95
110
|
|
|
96
111
|
```ts
|
|
@@ -129,9 +144,53 @@ npm install
|
|
|
129
144
|
npm run build # → dist/ (contains helix.json, index.html, assets/)
|
|
130
145
|
```
|
|
131
146
|
|
|
132
|
-
Then
|
|
147
|
+
Then, in order:
|
|
133
148
|
1. `validate_world` with the **absolute path to `dist/`** — fix every reported problem.
|
|
134
|
-
2. `
|
|
135
|
-
|
|
149
|
+
2. `npx -y {{CLI_PKG}}@latest verify-subpath dist` — **REQUIRED.** A published world is served from
|
|
150
|
+
`…/instant-worlds/<id>/<build>/`, never the origin root, but `npm run preview`, `validate_world` and
|
|
151
|
+
`inspect_world` all serve `dist/` from a root. So they all pass a bundle whose root-absolute URL 404s
|
|
152
|
+
and throws in production. This gate serves the same bytes from a nested path and fails on a throw, a
|
|
153
|
+
404, or a blank render — it is the only local check that sees the difference.
|
|
154
|
+
3. `whoami` — confirm a creator login exists (if not, the human must run `helix login`).
|
|
155
|
+
4. `publish_world` with the same dist path — returns the public play URL.
|
|
136
156
|
|
|
137
157
|
Budget: ≤ 200 files, ≤ 25 MB per file, ≤ 50 MB total. Allowed types include html/js/css/json/wasm, images (png/jpg/webp/svg/ktx2), models (glb/gltf/bin), audio (mp3/ogg/wav), fonts.
|
|
158
|
+
|
|
159
|
+
## Verify placement by measurement — see the world-inspect doc
|
|
160
|
+
|
|
161
|
+
To check WHERE things actually landed, do not squint at screenshots — `inspect_world` on `dist/` returns the
|
|
162
|
+
scene as data (positions, extents, placement findings), and `focusNear: [x, y, z]` checks just the object you
|
|
163
|
+
placed at that coordinate. Works on a bare scene too (no character system needed — the collider report simply
|
|
164
|
+
says it has nothing to read). Full workflow:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
read_doc("world-inspect")
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## In development: scene-streamed worlds — opt-in, only when the creator asks for one
|
|
171
|
+
|
|
172
|
+
The `scene` scaffold kind is a platform feature still in development. Do NOT pick it for "a scene", "a bare scene" or
|
|
173
|
+
"a world with no character" — those are the plain recipe above, or a `character` / `multiplayer` scaffold whose
|
|
174
|
+
geometry you place yourself. Use it ONLY when the creator explicitly asks for a world whose environment is a streamed HELIX Scene
|
|
175
|
+
v2 document, or names a Scene document / Scene Package to stream — and say so in your plan before scaffolding.
|
|
176
|
+
|
|
177
|
+
A **scene world** is the opposite trade from the recipe above: instead of placing geometry in `main.ts`, the world's
|
|
178
|
+
environment is a sealed **HELIX Scene v2** document that the platform's `scene` system streams and the `visual` system
|
|
179
|
+
lights. `scaffold_world({ kind: "scene" })` — `helix init <dir> --kind scene` — writes one: a starter document
|
|
180
|
+
compiled offline into `public/scene/mall/` next to its `pin.json`, that same pin in `public/helix.json`'s `scene`
|
|
181
|
+
field, a `public/helix.visuals.json` lighting contract, a `public/.helixignore` that keeps `scene/` out of the
|
|
182
|
+
uploaded bundle, and a `src/main.ts` that boots in the one order that works: document → visual runtime → scene world
|
|
183
|
+
→ character facade. There is no floor, no props and no lights in that file — geometry, materials, authored lights,
|
|
184
|
+
spawn and collision all come from the document. Underneath it is a multiplayer world (presence + proximity voice), so
|
|
185
|
+
the multiplayer recipe still applies on top.
|
|
186
|
+
|
|
187
|
+
Two things differ at publish time: `install_world_packages` must resolve the extra `scene` and `visual` system pins,
|
|
188
|
+
and a published world resolves its environment from the `scene` pin against the backend — so the **document has to be
|
|
189
|
+
published before the world** (`helix world scene-source publish`). Locally, `?scene=<folder>` picks between the
|
|
190
|
+
documents under `public/scene/`.
|
|
191
|
+
|
|
192
|
+
**Terrain** — open ground for a scene world — is the one environment piece you author as DATA rather than place:
|
|
193
|
+
`scaffold_world({ kind: "scene", terrain: "alpine" })` generates a 1 km x 1 km heightfield into the scaffolded
|
|
194
|
+
document, and `terrain_generate` / `terrain_import` / `terrain_inspect` author, adopt and measure it afterwards. It
|
|
195
|
+
is a scene-world feature only, it stays on dynamic lighting (never bake a terrain world), and the folder must be
|
|
196
|
+
inspected before publishing. The whole recipe, the six presets and the caps: `read_doc({ name: "terrain" })`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypersoniclabs/helix-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "HELIX Instant MCP server — gives AI coding agents the platform contract: world recipes, manifest schema, validation, catalog discovery, and publishing.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/server.js",
|
|
@@ -9,15 +9,26 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
-
"docs"
|
|
12
|
+
"docs",
|
|
13
|
+
"scaffold",
|
|
14
|
+
"skills"
|
|
13
15
|
],
|
|
14
16
|
"scripts": {
|
|
15
17
|
"build": "tsc -p tsconfig.build.json",
|
|
16
18
|
"lint": "eslint \"src/**/*.ts\"",
|
|
17
|
-
"test": "node scripts/smoke.mjs"
|
|
19
|
+
"test": "npm run build && node scripts/smoke.mjs && node scripts/legacy-publish-negative-smoke.mjs && node scripts/continuum-canary-smoke.mjs && node scripts/continuum-pull-smoke.mjs && node scripts/item-distribution-smoke.mjs && node scripts/item-thumbnail-smoke.mjs && node scripts/builds-smoke.mjs && node scripts/looks-smoke.mjs && node scripts/device-smoke.mjs && node scripts/engine-pin-smoke.mjs && node scripts/publish-source-smoke.mjs && node scripts/codex-smoke.mjs --schema-only",
|
|
20
|
+
"test:codex": "npm run build && node scripts/smoke.mjs && node scripts/publish-source-smoke.mjs && node scripts/codex-smoke.mjs",
|
|
21
|
+
"test:package": "npm run build && node scripts/package-smoke.mjs",
|
|
22
|
+
"verify": "npm run test:codex && node scripts/package-smoke.mjs",
|
|
23
|
+
"e2e": "node scripts/e2e-inspect.mjs",
|
|
24
|
+
"test:vault-live": "node scripts/test-vault-live.mjs",
|
|
25
|
+
"test:looks-live": "npm run build && node scripts/looks-live-smoke.mjs"
|
|
26
|
+
},
|
|
27
|
+
"helix": {
|
|
28
|
+
"minCliVersion": "0.1.23-helix3.433"
|
|
18
29
|
},
|
|
19
30
|
"dependencies": {
|
|
20
|
-
"@hypersoniclabs/helix-cli": "^0.1.
|
|
31
|
+
"@hypersoniclabs/helix-cli": "^0.1.23",
|
|
21
32
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
22
33
|
"zod": "^3.24.0"
|
|
23
34
|
},
|