@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.
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,90 @@
1
+ # Source: rigid bodies (robots, CAD and URDF, scanned or segmented kits, armour)
2
+
3
+ Lanes:
4
+ - **Unitree G1**: a URDF with CAD STL meshes.
5
+ - **C-3PO**: a geometry-only scan in 242 welded parts, ripped from a web viewer.
6
+ - **T-800**: hard-surface plates on a skinned source.
7
+
8
+ A rigid avatar uses no dynamics. Its quality comes from the pivots, the pose and the materials.
9
+
10
+ ## Bind 1:1 to bones
11
+
12
+ Every part gets weight 1.0 on **exactly one** `helix-humanoid@1` bone:
13
+
14
+ | Parts | Bone |
15
+ | --- | --- |
16
+ | Pelvis shell | `pelvis` |
17
+ | Torso and chest plates | `spine_05` |
18
+ | Head | `head` |
19
+ | Shoulder bell and upper arm | `upperarm_*` |
20
+ | Forearm | `lowerarm_*` |
21
+ | Hand and fingers | `hand_*`, unless you build finger joints |
22
+ | Thigh, knee drum and shin, foot | `thigh_*`, `calf_*`, `foot_*` |
23
+
24
+ - **Put the joints at the mechanical pivots.** On a URDF, these are the joint origins.
25
+ - Author the bones with canonical names and pass an identity `--map`.
26
+ - Exceptions:
27
+ - **Pistons** that span two bones are split along the rod, and each half binds to its own bone, so
28
+ they telescope.
29
+ - **Cables, wires and hoses** keep a two-bone blend: pelvis to `spine_05` by height, for example.
30
+
31
+ **Segmenting a welded scan.** Assign each connected component to the nearest bone capsule, using the
32
+ source's own joint positions. Keep an override file for components the heuristic gets wrong
33
+ (drop / forearm / piston), and read `JOINTS_0` back per LOD afterwards.
34
+
35
+ ## Pose to the canonical A-pose
36
+
37
+ Source poses vary: URDF zero pose, hands on hips, a T-pose. Re-pose **per segment**:
38
+ 1. Map each segment's measured frame onto its canonical frame. For a limb, use the bone direction plus
39
+ the hinge plane. For a hand, the finger direction plus the palm normal. For a leg, the leg
40
+ direction plus the foot heading.
41
+ 2. Find the pelvis, torso and head from a mirror-plane symmetry search.
42
+
43
+ The importer's rest aim then comes out as identity, and the shared clips play as designed. Unitree G1
44
+ solved its rest pose by optimisation: arms rotated +8° and thighs splayed 3.1° to match the canonical
45
+ A-pose.
46
+
47
+ ## Size, and the short-body gate
48
+
49
+ - Scale to the real height. Do not stretch to human height: Unitree G1 is 1.32 m.
50
+ - **Under 1.35 m, locomotion retargets only if (pelvis − foot) / 0.877 < 0.65.** Measure that ratio
51
+ before you publish.
52
+ - G1's natural ratio was 0.74, so the clips ran unscaled and the feet sank 2–5.7 cm.
53
+ - Placing the pelvis joint 7.5 cm lower, at 0.60 m (ratio 0.62), fixed it.
54
+ - The mesh did not move. Only the pivot did.
55
+
56
+ ## Materials without textures
57
+
58
+ A CAD or scan source with no textures gets **one palette atlas material**. Rules map faces to
59
+ colours, with dark variants in occluded crevices:
60
+ - C-3PO's palette was 4 × 4 at 256 px;
61
+ - Unitree G1's atlas was 256 px, holding base, metal/rough and emissive.
62
+
63
+ The cost is one material: 6 draws across three LODs, with a few MB of file.
64
+
65
+ Do not attempt a unique-UV normal bake on thousands of small parts. C-3PO's attempt produced about
66
+ 8,000 islands at 9–31 % coverage and noisy normals. Transfer normals from the high-poly mesh onto the
67
+ decimated one instead.
68
+
69
+ ## Geometry
70
+
71
+ - Weld and smooth normals at about 40°.
72
+ - Ray-test and cull hidden faces outside the moving limbs (about 16k on C-3PO).
73
+ - Decimate per segment toward a shared triangle total.
74
+ - Replace brackets that decimate badly with convex hulls.
75
+
76
+ Budgets that shipped:
77
+
78
+ | Avatar | Triangles | Draws | File |
79
+ | --- | --- | --- | --- |
80
+ | Unitree G1 | 34,998 / 14,531 / 4,944 | 6 | 4.49 MB |
81
+ | C-3PO | 38,999 / 13,993 / 5,646 | 6 | 3.39 MB |
82
+
83
+ ## Taste
84
+
85
+ - A 5 % head scale-up read as stocky.
86
+ - Pale cream eyes and pale gold read as washed out.
87
+ - Judge against real photographs, using the same viewer for each candidate. For Unitree G1, the
88
+ URDF's own render served as the baseline.
89
+
90
+ The critic plateaued at about 6/10 on these lanes. Say which exit you took.
@@ -0,0 +1,61 @@
1
+ # Source: a VRM (`.vrm`, VRoid and VRM 0.x / 1.0)
2
+
3
+ VRM is the only source that arrives with a humanoid map, spring bones and a face. **Never hand-roll a
4
+ VRM conversion.** The bridge does it in-process:
5
+
6
+ ```
7
+ bridge_import({ source: "/abs/avatar.vrm", type: "character", canonical: "/abs/base.web.glb", output: "/abs/out-vrm" })
8
+ # CLI: helix bridge import avatar.vrm --type character --canonical base.web.glb -o out-vrm
9
+ ```
10
+
11
+ `canonical` is **required, with no default**. It is the base body `*.web.glb` that the canonical
12
+ skeleton is fitted from: the stock `body-f-default.web.glb` or `body-m-default.web.glb` from a
13
+ throwaway character world's asset pack (`qa` step 3). The base you fit to is part of the result, so
14
+ record which one you used.
15
+
16
+ The output is `<slug>.glb` plus `vrm-conversion-receipt.json`. There is no `*.avatar-import.json` for
17
+ this route.
18
+
19
+ ## What it does
20
+
21
+ | Area | Result |
22
+ | --- | --- |
23
+ | Skeleton | Read from the file's own humanoid map, handling the thumb-name difference between VRM 0.x and 1.0. It is flipped to +Z forward and conformed to `helix-humanoid@1`. Spring-bone joints survive as auxiliary joints. |
24
+ | Dynamics | SpringBone becomes `helix/dynamics@1`, with real unit conversion, on every scene. A chain survives only if its root survived the conform **and** something in its subtree is weighted. Set `VRM_DEBUG_CHAINS=1` to print kept and dropped chains. |
25
+ | Colliders | Used colliders are kept and renamed onto canonical bones. VRM 1.0 sphere and capsule shapes import. VRM 0.x is sphere only. |
26
+ | Face | Expressions become `helix/avatar-face@1`, capability `vowel5`, with both blinks and the expression presets. **No `jawOpen` is declared**, on purpose. Undeclared blend shapes are pruned; a VRoid face drops from 57 shapes to 13. |
27
+ | First-person hide | Written automatically: meshes with at least 80 % head weight. |
28
+ | Budget | **A single scene: no LOD chain.** It decimates to 15,000 triangles, textures to 512 px, and atlases to 8 materials or fewer. |
29
+
30
+ ## Audit the conversion before you publish
31
+
32
+ 1. **Read the receipt's warnings**: dropped collider shapes, clamped stiffness or damping, guessed
33
+ importance.
34
+ 2. **Check every `center` chain.**
35
+ - VRoid writes a `center` on most spring groups by default. The CLI approximates it as
36
+ `inertia: 0.6`.
37
+ - On short chains (ears, bangs, accessories), 0.6 lets them swing 90°–120° at a sprint.
38
+ - Retune them to the short-chain values in `dynamics`: inertia about 0.97, stiffness 1.0, damping
39
+ 0.5, a 22° cone.
40
+ 3. **Per-joint variation is lost.** Only each spring's first joint values are used. Where the source
41
+ tapered stiffness along a strand, write a curve (`[root, tip]`).
42
+ 4. **Heavily dragged chains all clamp to critical damping** and stop looking different from each
43
+ other. If the hair reads as one stiff mass, lower damping on the long strands.
44
+ 5. **Look at it running** (see `qa`). VRM tuning assumed three-vrm's frame-rate-dependent integrator,
45
+ and HELIX's solver is exact. The same numbers can read differently.
46
+
47
+ Edit only the `helixDynamics` JSON in place, on every scene. The catears lane's approach was to
48
+ rewrite just the GLB JSON chunk and copy the binary chunk verbatim. Never re-import to change tuning.
49
+
50
+ ## Limits of this route
51
+
52
+ - One LOD at 15k triangles. That is far under the 60k budget, but it means no near-field detail.
53
+ - A VRoid model exported as **GLB** instead of VRM loses all of this. It imports through
54
+ `import_character` with `face: none`, unless you pass a `faceMap`, and with no dynamics.
55
+ - Get the `.vrm` if one exists.
56
+ - If not, write the `faceMap` (`read_doc({ name: "avatar-face" })`) and author dynamics per
57
+ `dynamics`.
58
+ - Cutting a **wearable** from a VRM (`keepMaterials`) uses the same converter at a smaller budget
59
+ (8k triangles, 4 materials). Chains with no garment geometry under them are dropped.
60
+ - A published package-backed VRM base cannot have its mesh replaced. The pin-correction route admits
61
+ stiffness and damping changes only. Tune before the first publish.
@@ -0,0 +1,128 @@
1
+ ---
2
+ name: helix-gauntlet
3
+ description: MANDATORY before any HELIX item is published, given a new version, listed, repriced into sale or reported done — vehicles, add-ons, avatars, wearables, props and worlds. Pick an external bar (photos and specs of the real thing), render the candidate in the real runtime from a fixed inspection set, look at every render against the bar, get a fresh blind critic's verdict where you can spawn subagents, fix and re-render, and ship only on a pass. Also for re-checking an item that is already listed when the gates change or you update it.
4
+ ---
5
+
6
+ # HELIX Gauntlet
7
+
8
+ Every gate in the other skills measures something. This one makes you **look**, against something real.
9
+
10
+ **Why it exists.** A Ferrari F40 passed every measured gate, a submitted visual-QA run and an owner-proxy
11
+ ACCEPT, and went on sale. Its owner then opened it and found the driver lying almost flat, both hands off
12
+ the steering wheel, the fingers twitching, and the passenger folded with the knees at the chest. The
13
+ proxy had written "reclined, knees up" in its notes and called it non-blocking; nobody looked at the
14
+ hands. A Porsche 993 shipped with its wheels tucked into the arches because QA read numbers and never
15
+ looked at the car. Both defects were obvious in one picture. Neither picture was looked at against the
16
+ real car.
17
+
18
+ ## The rule
19
+
20
+ - **No publish, new version, listing or "done" without a PASS from this loop.** Not your belief that it
21
+ passes: the artifacts below, written to the ledger.
22
+ - **A defect you can see is a FAIL even when every number passes.** There is no "minor" for posture,
23
+ hands, fingers, clipping, floating, missing or wrong-coloured parts. "Reclined", "knees up", "hands off
24
+ the rim", "fingers moving" are FAIL reasons, never notes.
25
+ - **Every executable assert is blocking.** A tool exit 1 is a FAIL; fix it before you spend a critic.
26
+ - **Only the real runtime counts.** Judge renders from the HELIX runtime (`preview_vehicle`, visual QA),
27
+ never Blender viewport shots, a GLB viewer, or the source code.
28
+
29
+ ## The loop
30
+
31
+ Keep `gauntlet/` beside the item's source: `bar/`, `round-1/`, `round-2/`…, and `LEDGER.md`.
32
+
33
+ 1. **Pick the bar, once.** The bar is the real thing: reference photos of the exact car / character /
34
+ object at the inspection poses (front, both 3/4 views, both sides, rear, top, cockpit and interior,
35
+ wheel, lamp and detail close-ups) plus its published specs. Save them in `gauntlet/bar/` with
36
+ `INDEX.md` naming each photo's pose and source. A fictional item's bar is its concept art plus a named
37
+ real product of the same kind ("the Ferrari F40's cabin" for a fictional supercar's cabin). Never a
38
+ rubric you wrote. Freeze it: every round is judged against the same photos.
39
+ 2. **Render the fixed inspection set in the real runtime** (the set per kind is below). Same cameras every
40
+ round — the tool owns them; never hand-pick flattering angles.
41
+ 3. **Gate on the executable asserts first.** Every blocking assert the tool prints must pass. Fix
42
+ failures now; a critic on a car that fails its asserts is wasted.
43
+ 4. **Look — yourself — at every sheet.** Open each sheet the tool lists, in order, beside the matching
44
+ bar photo. Each sheet carries a LOOK question; answer it in `LEDGER.md` per sheet: `PASS` or `FAIL`
45
+ plus every defect you see. Do not skip a sheet because its numbers passed.
46
+ 5. **Get a blind verdict.** If your client can spawn subagents, spawn a FRESH one that never saw the build
47
+ and give it only: the sheets at the size the tool wrote them (copied into a neutral folder, renamed
48
+ `sheet-01.jpg`…, nothing in a name or path that says which item, round or author), the bar photos,
49
+ and the critic brief below. A sheet the critic says it cannot judge (too dark, hidden, too small) is
50
+ a FAIL of the inspection, not a pass: fix the render, not the verdict. If you
51
+ cannot spawn one, re-read only the brief and the images and judge again from cold, and say in the
52
+ report that the critic was not independent.
53
+ 6. **Fix every defect the asserts, your look and the critic name; re-render; re-judge.** Keep the best
54
+ render set (the ratchet): a round replaces it only when it wins on every sheet it touches.
55
+ **Two rounds per approach, then diagnose**: name each remaining defect tunable or structural, and for a
56
+ structural one ask what the bar has that this approach cannot produce, and whether you can reuse the
57
+ bar's own machinery (a real licensed mesh of the car, the platform's seat rig and H-points, the
58
+ engine's own fold) instead of imitating it. Change approach; do not tune a third round.
59
+ 7. **Exit, and say which exit.** `PASS`: every blocking assert passes, every sheet PASS in your look, and
60
+ the critic picks `MATCHES` on every sheet with an overall score ≥ 8. Anything else is not a pass: a
61
+ round or budget limit is a **brake** — do not publish or list; report the item blocked with its top
62
+ unresolved defect and the sheet that shows it.
63
+
64
+ ## Updating or re-checking a listed item
65
+
66
+ A listed item is never re-judged when a gate is added, so a car on sale can be running on a record that
67
+ predates the gate that would fail it. **Before and after you touch a listed car, run
68
+ `check_listed_items({ itemIds: [<id>], rerun: true })`.** STALE (a current gate never ran) or FAIL means
69
+ the car is on sale broken: run this loop on it, stage a new version of the SAME item, pass hosted visual
70
+ QA on that version, and until then delist it (`delist_item`). With no ids it re-checks every listed
71
+ vehicle.
72
+
73
+ ## The vehicle inspection set (fixed; `preview_vehicle` renders all of it)
74
+
75
+ `preview_vehicle({ target: <dir> })` for a candidate, `preview_vehicle({ item: <id> })` for a published
76
+ car. Occupants and the inspection set are ON by default; `occupants: false` / `inspection: false` are for
77
+ a quick iteration only and are never a gate result. It writes `<out>/inspection/index.json` and these
78
+ sheets (≈1280 px JPEGs, one LOOK question each):
79
+
80
+ | Sheet | What is in it |
81
+ | --- | --- |
82
+ | `exterior-day` | 8 angles: front, front-left 3/4, left, rear-left 3/4, rear, rear-right 3/4, right, front-right 3/4 |
83
+ | `exterior-night` | the same 8 at night, low beam on |
84
+ | `lamps-night` | every lamp function lit: high beam, fog, brake, reverse, each indicator front and rear, hazards |
85
+ | `wheels` | each wheel close-up, low and square-on |
86
+ | `panels-open` | every door and lid at full open |
87
+ | `seat-<seat>` | **every seat occupied**, each from first person, through the side glass, over the shoulder, through the windscreen |
88
+ | `seat-<seat>-detail` | the same occupant: cutaway, head to roof, hips, feet, door open |
89
+ | `driver-steering` | the driver steering left / centre / right: hands view and first person |
90
+ | `stillness` | three frames 0.3 s apart of every occupant's hands |
91
+ | add-ons | every add-on fitted: `preview_vehicle({ target or item, addon: [<dir>] })` per part, its contact sheet |
92
+
93
+ What a PASS looks like, per occupant: torso at a normal driving recline (not lying back), thighs about
94
+ level (knees not at the chest), head clear of the roof and not wedged in the seat wings, feet on the
95
+ pedals or floor, nothing through the bodywork; the driver's hands **on the rim at about 9 and 3 and
96
+ turning with it** at both locks; fingers and hands identical across the stillness frames (any twitch or
97
+ "grasping" is a FAIL). The blocking asserts behind those: `vehicle.occupants.torso_angle`, `knees_up`,
98
+ `head_in_wings`, `head_clearance`, `hips_on_seat`, `feet_on_floor`, `penetration`,
99
+ `hands_on_wheel_steering`, `hands_follow_wheel`, `hands_at_nine_and_three`, `fingers_still`,
100
+ `hands_still`, `seat_rig_warnings` (the seat rig logged a rim out of reach and parked a hand in the lap), and `preview.inspection.complete` (a sheet with a frame missing is not an inspection).
101
+ When an occupant assert fails, fix the cabin (seat H-point, cushion, footwell, wheel position and reach:
102
+ `read_skill({ name: "helix-vehicles", reference: "cabin" })`), never the threshold.
103
+
104
+ ## Other kinds
105
+
106
+ The loop is the same; the inspection set comes from the kind's own tools.
107
+
108
+ - **Avatars** (`helix-avatars`, `helix-avatar-qa`): bar = the reference sheet or photos of the character
109
+ at front, side, back and 3/4; inspection = the visual-QA turnaround, the walk frames and the
110
+ Marketplace preview walk, on both base bodies where relevant.
111
+ - **Wearables, props and other items** (`helix-assets`): bar = product photos at matched angles;
112
+ inspection = the `visual_qa_item` frames (fitted on the body or placed in the scene).
113
+ - **Worlds** (`helix-world-qa`): bar = the named reference art or a comparable published world;
114
+ inspection = the fixed-camera screenshots and the mobile frames from the QA pass, plus frame time.
115
+
116
+ ## Critic brief (give it verbatim with the sheets and the bar photos)
117
+
118
+ > You are judging renders of a product against photos of the real thing. You did not make it and you do
119
+ > not know who did. For EACH sheet, in order: (1) first write `MATCHES` or `DOES NOT MATCH` the reference
120
+ > — decide before you write anything else; (2) list every visible defect: wrong shape or proportion,
121
+ > missing or extra parts, wrong colour or material, anything floating, clipping, see-through, black or
122
+ > glowing where it should not; for any seated person, posture (reclined, knees up, head jammed against
123
+ > the roof or wedged in the seat wings), hands not on the steering wheel or not turning with it, fingers
124
+ > that change between frames, feet not on the floor or pedals; (3) score the sheet 0–10, where any
125
+ > posture, hands or fingers defect caps the score at 3. Then give an overall score 0–10 and the three
126
+ > defects that matter most, each with the sheet that shows it. Be harsh: you are protecting a buyer.
127
+
128
+ Record the critic's picks, defects and scores per round in `LEDGER.md`, and keep its raw answer.
@@ -0,0 +1,150 @@
1
+ ---
2
+ name: helix-multiplayer
3
+ description: Design HELIX multiplayer against what the declarative rule DSL can actually express — and recognise early when a mechanic falls outside it, before it is half-built. Use for any shared-space or shared-game world, for converting a single-player world to multiplayer, and whenever a proposed mechanic involves hit detection, pathfinding, hidden information, persistence or physics.
4
+ ---
5
+
6
+ # HELIX Multiplayer
7
+
8
+ Multiplayer on HELIX is **declarative and server-authoritative**. You declare state and
9
+ `when`/`if`/`then` rules as data in `helix.json`; a platform-owned room interprets them at a
10
+ fixed 20 Hz. **Your world ships no server code.** There is no server file to write, and no
11
+ place to put one.
12
+
13
+ That is a genuine constraint, not a temporary gap. This skill exists so you find the edge
14
+ of the DSL during design, not after you have built three-quarters of a mechanic it cannot
15
+ express.
16
+
17
+ ## Read the contract, do not reconstruct it
18
+
19
+ `read_doc("multiplayer-world")` is the hub. `read_doc("multiplayer-logic")` is the full
20
+ grammar — 17 rule events, 25 effects, the expression language, the caps. `list_templates`
21
+ + `read_template` give you complete worked worlds. **Every verb, op and event you write is
22
+ validated at publish; one that is not in the grammar is rejected with a did-you-mean.**
23
+ Do not author the block from memory.
24
+
25
+ This skill covers what those docs deliberately do not: the boundary, and what to do at it.
26
+
27
+ ## The capability boundary
28
+
29
+ ### Expressible today
30
+ Shared numbers/strings/booleans/vec3/refs/lists/counterMaps · per-player and per-room state
31
+ · zones (enter / exit / inside, static or entity-attached) · timers, keyed or room-scoped ·
32
+ a phase state machine with late-join policy · server-authoritative entity motion
33
+ (`linear`/`orbit`/`seek`/`waypoints`) · client-hosted entities with ownership transfer ·
34
+ networked physics (dynamic rigid bodies, authority-transfer or dual-sim) · turn order and
35
+ elimination · teams · server→client broadcasts and client→server validated actions ·
36
+ proximity or global voice with channels · `playerContact` at a radius — the tag primitive.
37
+
38
+ ### Not expressible — and the DSL will not tell you nicely
39
+ | You want | Why it cannot be declared | What to do instead |
40
+ | --- | --- | --- |
41
+ | "in front of me", a facing cone, a direction vector | There is **no vec3 arithmetic.** Vec3 exists only as a literal, `distance`, and `randomPoint`. No dot, cross, normalise, add or subtract. | Reduce to a distance test (`playerContact`, a zone), or resolve client-side and send a declared `action` the rules validate. |
42
+ | Raycast, hitscan, line of sight, cover | No raycast primitive exists at any layer of the DSL. | Client detects, sends `action`; the rule validates distance + cooldown + phase before applying. Accepts that the client is trusted about *aim*, never about *outcome*. |
43
+ | Navmesh pathfinding, obstacle avoidance, "decide" AI | `seek` is a straight line. Navigating *around* things is not in the grammar. | `authority: "owner"` entity, simulated client-side. Spoofable by design — never let it write another player's state. |
44
+ | Progress that survives the session — currency, inventory, unlocks | Room state is per-instance and dies with the room. | `Helix.dataStore` (get/set/delete/list). **No multi-key transaction** — design so no two keys must move together. |
45
+ | Fog of war, hidden roles, a private hand, secret objectives | **Every value in room state is broadcast to every client.** A "hidden" playerVar is readable in devtools by every player in the room. There is no per-player-private state. | Redesign to open information, or keep the secret client-side and accept it is unenforceable. Do not ship a social-deduction game on this and call the secret secure. |
46
+ | Server-authoritative hit resolution | Follows from no raycast + no vec3 math. The server cannot independently confirm a hit. | The `claimHit` pattern: a declared action carrying the target ref, validated in the rule for distance, cooldown and phase, then writing through `{ref: action.args.target, var: "health"}`. Cheat-*resistant*, not cheat-proof. Say which you have. |
47
+
48
+ Full construct-by-construct map with the escape hatch for each:
49
+ `references/dsl-capability-map.md`.
50
+
51
+ ## The three escape hatches, in order of preference
52
+
53
+ 1. **A declared `action`.** Client sends intent, the server validates args and applies the
54
+ consequence in a rule. **This is the cheat-resistant path and the correct default for
55
+ anything that affects another player.**
56
+ 2. **An `authority: "owner"` entity.** A client simulates and uploads; the server
57
+ sanity-checks `maxSpeed` and relays. The escape hatch for motion the server cannot
58
+ compute. Spoofable — never use it as an authority over other players.
59
+ 3. **Redesign the mechanic.** Usually the right answer for hidden information and for
60
+ precise hit resolution. Cheaper than a half-working version of a game the platform
61
+ cannot host.
62
+
63
+ ## The cross-player write firewall — a publish-time error, not a runtime one
64
+
65
+ Publish **statically blocks** owner-entity logic from any cross-player write **or read**:
66
+ no `set`/`add`/`teleport`/`respawn`/`destroyEntity`/collection mutation/ownership
67
+ verb/keyed timer/`broadcast` aimed at another member, and no dereferencing a client-supplied
68
+ ref to *read* another member's var. This fails the publish, not a code review. Design around
69
+ it up front.
70
+
71
+ ## The #1 conversion bug — client-side teleport
72
+
73
+ A client that moves its own body across the map trips the room's movement gate. The gate
74
+ holds the seat's last plausible position and **never self-heals**: you keep playing locally,
75
+ and every other player sees you frozen or vanished at the old spot. **Solo testing cannot
76
+ surface this.**
77
+
78
+ Every kill-plane, checkpoint, round reset, portal, out-of-bounds handler and "play again"
79
+ flow becomes a server `respawn`/`teleport` effect. Round resets are one rule
80
+ (`forEachPlayer` → `respawn`), never a broadcast each client answers by teleporting itself
81
+ — that is one copy of the bug per player.
82
+
83
+ **Artifact check.** Every local transform write must be inside a single-player branch:
84
+
85
+ ```bash
86
+ grep -nE "\.(teleport|respawn)\(" src/*.ts src/**/*.ts
87
+ ```
88
+
89
+ Each hit is either inside `if (!mp.room) { … }` or it is a bug. `source-audit.mjs` runs this
90
+ and fails on an unguarded hit in a world whose manifest declares `multiplayer`.
91
+
92
+ ## Verify with two clients — a delta assertion, not a final-state check
93
+
94
+ Authority bugs are invisible solo, and a final-state check passes on a disconnected client.
95
+ Assert the **change**, in the *other* window:
96
+
97
+ ```js
98
+ // A and B are two browser contexts on the built preview, both joined.
99
+ const before = await B.evaluate(() => window.__helixMpProbe.remote(0).position.z);
100
+ await A.evaluate(() => window.__helixMpProbe.requestRespawn()); // triggers the SERVER rule
101
+ await expect.poll(() => B.evaluate(() => window.__helixMpProbe.remote(0).position.z))
102
+ .toBeGreaterThan(before + 0.5); // B saw A move. Magnitude, not truthiness.
103
+ ```
104
+
105
+ Expose `window.__helixMpProbe` from world code behind a query param, the same pattern
106
+ `world.ready()` and `screenshots.register()` use — a no-op during normal play. Without a
107
+ probe you are asserting on pixels, which cannot distinguish "moved" from "repainted".
108
+
109
+ **The gate:** the artifact is the test's exit code plus both screenshots, written to
110
+ `qa/mp-two-client/`. A passing description of a two-window test is not the test.
111
+
112
+ ## Voice is on by default
113
+
114
+ Every multiplayer world ships voice unless it has a reason not to. `scaffold_world` already
115
+ declares `voice.proximity` and writes the fail-soft join. A deliberately silent world opts
116
+ out by removing **both** the permission and the block.
117
+
118
+ ```ts
119
+ if (mp.room) {
120
+ try { if (await Helix.voice.join()) mp.attachVoice(Helix.voice, manifest.multiplayer.voice); }
121
+ catch (err) { console.info('voice unavailable:', err); } // never blocks play
122
+ }
123
+ ```
124
+
125
+ Tuning lives in the manifest `multiplayer.voice` block, not in code. Declare
126
+ `"spatial": true` when *presence* is the product — social spaces, horror, hide-and-seek,
127
+ stealth. Skip it for announcer-shaped or competitive-callout voice. Channels are
128
+ **exclusive**: a player is in the ambient space or in exactly one channel. Mic behavior
129
+ (mode, device, volumes, mutes) belongs to the player via the platform tablet — **a world
130
+ never renders mic UI.**
131
+
132
+ ## Rules that bite
133
+
134
+ - **It must work solo.** Guests, standalone and failed joins all still play. The facade
135
+ sets up your local player unconditionally and layers multiplayer on only when a join
136
+ succeeds. Gate any "needs 2 players" logic explicitly.
137
+ - **Read state through the typed accessors, fresh each frame** — `room.vars.num('x')`,
138
+ `room.me.str('team')`, `room.player(id)`, `room.phase()`. Colyseus maps are mutated in
139
+ place: copy values you cache, never alias. Guard against the pre-first-patch empty state.
140
+ - **Skip your own `sessionId`** when spawning remotes.
141
+ - **Mirror `maxSpeed`** between the DSL and the `EntityScene` options — the reconcile clamp
142
+ reads the client copy.
143
+ - **`maxReplicas` is a real budget** (each replica is a full character); keep it equal to
144
+ `maxPlayers`. 2–8 is the sweet spot. `uploadHz: 20` caps `maxPlayers` at 12; the platform
145
+ caps concurrent players per room at 24 regardless.
146
+ - **Never call `room.uploadEntity` directly** — register through `EntityScene`.
147
+ - **Gestures replicate by default.** Trigger them from input handlers, never from
148
+ replicated-state observers, or every client fires its own copy.
149
+ - **The per-tick evaluation budget is static.** A too-large `forEach` fan-out or nested
150
+ `aggregate`/`nearest*` fails publish before it ever runs. Simplify or spread across ticks.
@@ -0,0 +1,107 @@
1
+ # The DSL capability map
2
+
3
+ A design-time lookup: can the declarative rule DSL express this, and if not, which escape
4
+ hatch does it take. The authoritative grammar is `read_doc("multiplayer-logic")` — this file
5
+ is the judgment layer on top of it.
6
+
7
+ Read the middle column as the *mechanism*, not as a rule of thumb. An agent that knows why
8
+ a thing is impossible handles the case this table does not list.
9
+
10
+ ## Expression language — what you can compute in an `if`
11
+
12
+ | Available | Notes |
13
+ | --- | --- |
14
+ | `+ - * /` | **numbers only** |
15
+ | `== != < <= >= >` | `==`/`!=` on same-typed scalars (not vec3); ordering ops require numbers |
16
+ | `and` / `or` / `not` | |
17
+ | `distance(a, b)` | the **only** vec3 operation that produces a number |
18
+ | `randomPoint(min, max)` | the only op that produces a vec3 |
19
+ | `playerCount` · `random` · `now` · `timeInState` | leaf generators |
20
+ | `aggregate` (`count`/`sum`/`min`/`max`/`avg`/`argmax`/`argmin`) over players, a zone, or an entity kind | with an optional `where` filter |
21
+ | `timerRemaining` | |
22
+ | `listLength` · `listAt` · `count` · `listCount` · `listIndexOf` | collections |
23
+ | `controlledBy` · `hostLoad` · `sameRef` | ownership |
24
+
25
+ **Not available at any depth:** vec3 addition/subtraction, dot, cross, normalise, length of
26
+ a difference, angle between, min/max of two scalars, string concatenation, modulo, sqrt,
27
+ trigonometry, nested collections, free-form JSON.
28
+
29
+ The consequence that catches people: **you cannot compute a direction.** Every mechanic
30
+ phrased as "in front of", "facing", "behind", "within a cone", "line of sight" reduces to a
31
+ distance test or leaves the DSL entirely.
32
+
33
+ ## Mechanic → verdict
34
+
35
+ | Mechanic | Verdict | Route |
36
+ | --- | --- | --- |
37
+ | Score, lives, teams, rounds | ✅ | `roomVars` / `playerVars` + rules |
38
+ | Pickups, collectibles | ✅ | `spawnEntity` (server authority) + zone or `playerContact` |
39
+ | Capture points, king of the hill | ✅ | zone `zoneInside` + `aggregate` over `zone:<id>` |
40
+ | Checkpoints, obby, race | ✅ | ordered zones + a per-player checkpoint var + `respawn` |
41
+ | Patrolling NPC, moving platform, homing pickup | ✅ | entity `motion: waypoints`/`seek`/`orbit`, `authority: "server"` |
42
+ | Timed waves, day cycle, crop growth | ✅ | timers + `{op:"now"}` + phases |
43
+ | Turn-based / board game | ✅ | `states` + `advanceTurn` + `eliminate` |
44
+ | Cards, hand, inventory (in-session) | ✅ | `list` of flat records + `forEachInList` |
45
+ | Ball, puck, bumper cars | ✅ | `physics` on an `authority:"owner"` kind; `claimOnContact` for one contested body, `dualSimOnContact` for player-driven bodies |
46
+ | Tag / touch-based elimination | ✅ | `playerContact` at a radius |
47
+ | Walkie-talkies, private calls, team radio | ✅ | `multiplayer.voice.channels` + `Helix.voice.setChannel` |
48
+ | Enemy that paths around a wall | ❌ no pathfinding | `authority:"owner"` entity, client-simulated |
49
+ | Shooting with server-confirmed hits | ❌ no raycast, no vec3 math | declared `action` + rule-side distance/cooldown/phase validation. Cheat-**resistant**. |
50
+ | Melee arc / cone attack | ❌ no facing math | `playerContact` radius, or a client-detected `action` |
51
+ | Fog of war, hidden roles, secret hand | ❌ all state is broadcast | redesign to open information, or keep it client-side and say plainly that it is unenforceable |
52
+ | Saved currency, progression, unlocks | ❌ room state is per-instance | `Helix.dataStore` — **no multi-key transaction** |
53
+ | Persistent world edits between sessions | ❌ | `Helix.dataStore`, reloaded on join |
54
+ | Anti-cheat on client-simulated entities | ❌ by design | the server validates `maxSpeed` plausibility and nothing more |
55
+
56
+ ## Why "hidden information" is the hardest no
57
+
58
+ Room state syncs to every connected client. A `playerVars` entry named `role` with value
59
+ `"traitor"` arrives in every player's browser and is one devtools expand away. There is no
60
+ per-recipient filtering in the broadcast path and no private channel for state.
61
+
62
+ `broadcast` with `to: <ref>` or `to: {team: "..."}` targets a **message**, not state — so a
63
+ one-shot secret *reveal* can be directed. That is enough for "you are the traitor, told
64
+ once at round start" **only if** nothing in synced state ever encodes it afterwards. The
65
+ moment a rule writes the role into a var to make a later rule work, it is public. Design
66
+ accordingly, and never describe such a world as having secure hidden roles.
67
+
68
+ ## Entity authority — declare `motion` first
69
+
70
+ - `authority: "server"` (default) — deterministic, cheat-proof, never freezes. Reach for it
71
+ first. Covers straight-line `seek`, `waypoints`, `orbit`, `linear`, `static`.
72
+ - `authority: "owner"` — a client simulates and uploads; requires `maxSpeed`. The escape
73
+ hatch, spoofable by design.
74
+ - `shared: true` + `ownerLifecycle: "hostMigrate"` — game-owned, host re-elected on leave.
75
+ This is how a swarm spreads its simulation across clients.
76
+
77
+ Physics cross-field rules that publish enforces: a `physics` block **requires**
78
+ `authority: "owner"`; `claimOnContact` ⇒ `transferPolicy: "takeover"`; `dualSimOnContact` ⇒
79
+ `transferPolicy: "fixed"`; `collidesWith` names declared kinds. Cap: 8 physics kinds.
80
+
81
+ ## Caps — publish rejects an over-cap config with a precise message
82
+
83
+ `roomVars`/`playerVars` 384 · `rules` 768 · effects per `then` 16 · `if` depth 8 / 64 nodes ·
84
+ entity kinds 32 · entities per kind 256 · entity vars 64 · zones 256 · timers 192 · phases 64 ·
85
+ events 256 · actions 256 · list `maxLen` 256 · counterMap keys 64 · record fields 8 · string
86
+ `maxLen` 1024 · physics kinds 8 · enum values 64 · cascade depth 16 · `tickNodeBudget`
87
+ 100 000 (computed statically from rules × loop fan-out × cascade depth — the declaration caps
88
+ were raised, this budget was NOT, and it binds long before any declaration ceiling).
89
+
90
+ Nearing a cap is a design signal, not a tuning problem: a mechanic that wants hundreds of
91
+ rules probably wants a phase machine, a collection, or a `forEach` instead.
92
+
93
+ ## Ordering semantics you will trip over
94
+
95
+ Rules evaluate in **declared order**; effects within a `then` apply in order; later rules
96
+ see earlier rules' writes within the same tick. Effects that fire secondary events
97
+ (`transitionTo`, `spawnEntity`, `destroyEntity`, ownership verbs) queue and drain
98
+ breadth-first **after** the primary rules. `varReached` is edge-checked once at the **end**
99
+ of the rule phase and is not part of the cascade. Publish rejects cascades that could cycle.
100
+
101
+ ## Zone box sizing — the slip that costs an afternoon
102
+
103
+ A `zones[]` box takes `size` as **full dimensions**. A `physics.shape` box takes
104
+ `halfExtents`. They look identical and differ by 2×. `inspect_world` reads the declared
105
+ zones from the bundle and checks each against real geometry — the only way to catch a kill
106
+ band the pit floor misses or a checkpoint volume floating in empty space, because zones
107
+ never render.