@helix3/helix-cli 0.1.5-helix3.33 → 0.1.6-helix3.34

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helix3/helix-cli",
3
- "version": "0.1.5-helix3.33",
3
+ "version": "0.1.6-helix3.34",
4
4
  "description": "helix — the HELIX creator CLI: scaffold, validate, and publish Instant Worlds",
5
5
  "main": "dist/lib.js",
6
6
  "types": "dist/lib.d.ts",
@@ -1,4 +1,4 @@
1
- // @helix/humanoid-character@0.2.2 — ability.json validator (frozen C3 contract).
1
+ // @helix/humanoid-character@0.2.7 — ability.json validator (frozen C3 contract).
2
2
  // Generated by emit-schemas; vendored by @hypersoniclabs/helix-cli. Do not edit; changes require ability-v2.
3
3
 
4
4
  // src/core/config/paths.ts
@@ -34,6 +34,318 @@ function nearestKey(key, candidates) {
34
34
  return best !== void 0 && bestDist <= Math.max(2, Math.floor(key.length * 0.4)) ? best : void 0;
35
35
  }
36
36
 
37
+ // src/core/input/gamepadStandard.ts
38
+ var StandardButton = {
39
+ faceDown: 0,
40
+ faceRight: 1,
41
+ faceLeft: 2,
42
+ faceUp: 3,
43
+ bumperL: 4,
44
+ bumperR: 5,
45
+ triggerL: 6,
46
+ triggerR: 7,
47
+ select: 8,
48
+ start: 9,
49
+ stickL: 10,
50
+ stickR: 11,
51
+ dpadUp: 12,
52
+ dpadDown: 13,
53
+ dpadLeft: 14,
54
+ dpadRight: 15
55
+ };
56
+ var StandardAxis = {
57
+ leftStick: [0, 1],
58
+ rightStick: [2, 3]
59
+ };
60
+ var BUTTON_NAMES = Object.keys(StandardButton);
61
+ var AXIS_NAMES = Object.keys(StandardAxis);
62
+
63
+ // src/core/input/standardActions.ts
64
+ var STANDARD_ACTIONS = [
65
+ // ---- character / gameplay ----
66
+ {
67
+ id: "move",
68
+ kind: "vec2",
69
+ domain: "character",
70
+ context: "gameplay",
71
+ bindings: { composite: { up: ["KeyW"], down: ["KeyS"], left: ["KeyA"], right: ["KeyD"] }, stick: "leftStick" },
72
+ label: "Move",
73
+ description: "Planar movement (vec2; WASD composite or left stick)",
74
+ touch: "stick"
75
+ },
76
+ {
77
+ id: "look",
78
+ kind: "vec2",
79
+ domain: "character",
80
+ context: "gameplay",
81
+ bindings: { stick: "rightStick" },
82
+ label: "Look",
83
+ description: "Camera look (pointer delta or right stick). First-person pointer lock auto-acquires on the T toggle and re-acquires on a canvas click (Esc releases); third-person and touch use unlocked drag",
84
+ touch: "drag"
85
+ },
86
+ {
87
+ id: "jump",
88
+ kind: "button",
89
+ domain: "character",
90
+ context: "gameplay",
91
+ bindings: { keys: ["Space"], pad: "faceDown" },
92
+ label: "Jump",
93
+ description: "Jump (when grounded)",
94
+ touch: "button"
95
+ },
96
+ {
97
+ id: "sprint",
98
+ kind: "button",
99
+ domain: "character",
100
+ context: "gameplay",
101
+ bindings: { keys: ["ShiftLeft", "ShiftRight"], pad: "stickL" },
102
+ label: "Sprint",
103
+ description: "Sprint-tier speed while held (touch: auto by stick magnitude)",
104
+ touch: "none"
105
+ },
106
+ {
107
+ // AltLeft is the browser-safe primary (Ctrl+W closes the tab and cannot be prevented);
108
+ // Ctrl stays bound for fullscreen/wrapped builds where the genre convention applies.
109
+ id: "walk",
110
+ kind: "button",
111
+ domain: "character",
112
+ context: "gameplay",
113
+ bindings: { keys: ["AltLeft", "ControlLeft", "ControlRight"] },
114
+ label: "Walk",
115
+ description: "Force walk-tier speed while held",
116
+ touch: "none"
117
+ },
118
+ {
119
+ id: "crouch",
120
+ kind: "toggle",
121
+ domain: "character",
122
+ context: "gameplay",
123
+ bindings: { keys: ["KeyC"], pad: "faceRight" },
124
+ label: "Crouch",
125
+ description: "Toggle crouch",
126
+ touch: "button"
127
+ },
128
+ {
129
+ id: "primary",
130
+ kind: "button",
131
+ domain: "character",
132
+ context: "gameplay",
133
+ bindings: { mouseButtons: [0], pad: "triggerR" },
134
+ label: "Primary",
135
+ description: "Primary use/fire (abilities bind this instead of minting a private fire action)",
136
+ touch: "button"
137
+ },
138
+ {
139
+ id: "secondary",
140
+ kind: "button",
141
+ domain: "character",
142
+ context: "gameplay",
143
+ bindings: { mouseButtons: [2], pad: "triggerL" },
144
+ label: "Aim / secondary",
145
+ description: "Secondary use \u2014 the character system will drive aim-down-sights from this id",
146
+ touch: "button"
147
+ },
148
+ {
149
+ id: "interact",
150
+ kind: "button",
151
+ domain: "character",
152
+ context: "gameplay",
153
+ bindings: { keys: ["KeyE"], pad: "faceLeft" },
154
+ label: "Interact",
155
+ description: "Contextual world interaction (doors, pickups, NPCs)",
156
+ touch: "button"
157
+ },
158
+ {
159
+ id: "reload",
160
+ kind: "button",
161
+ domain: "character",
162
+ context: "gameplay",
163
+ bindings: { keys: ["KeyR"], pad: "faceUp" },
164
+ label: "Reload",
165
+ description: "Reload / secondary contextual action",
166
+ touch: "button"
167
+ },
168
+ // Direct slot select is keyboard-only (digits); the gamepad cycles via equipPrev/equipNext instead.
169
+ ...Array.from({ length: 9 }, (_, i) => ({
170
+ id: `ability${i + 1}`,
171
+ kind: "button",
172
+ domain: "character",
173
+ context: "gameplay",
174
+ bindings: { keys: [`Digit${i + 1}`] },
175
+ label: `Ability ${i + 1}`,
176
+ description: `Ability/item slot ${i + 1} (direct select; pad players cycle with equipPrev/equipNext)`,
177
+ touch: "radial"
178
+ })),
179
+ {
180
+ id: "equipNext",
181
+ kind: "button",
182
+ domain: "character",
183
+ context: "gameplay",
184
+ bindings: { keys: ["BracketRight"], pad: "bumperR" },
185
+ label: "Next equip",
186
+ description: "Cycle the active ability/equip slot forward (1-9). Slot state is game-side \u2014 worlds/abilities consume this",
187
+ touch: "none"
188
+ },
189
+ {
190
+ id: "equipPrev",
191
+ kind: "button",
192
+ domain: "character",
193
+ context: "gameplay",
194
+ bindings: { keys: ["BracketLeft"], pad: "bumperL" },
195
+ label: "Previous equip",
196
+ description: "Cycle the active ability/equip slot back (1-9). Slot state is game-side \u2014 worlds/abilities consume this",
197
+ touch: "none"
198
+ },
199
+ {
200
+ id: "melee",
201
+ kind: "button",
202
+ domain: "character",
203
+ context: "gameplay",
204
+ bindings: { keys: ["KeyV"], pad: "stickR" },
205
+ label: "Melee",
206
+ description: "Melee strike. No built-in behavior \u2014 worlds/abilities consume it",
207
+ touch: "button"
208
+ },
209
+ {
210
+ id: "emote",
211
+ kind: "button",
212
+ domain: "character",
213
+ context: "gameplay",
214
+ bindings: { keys: ["KeyG"], pad: "dpadDown" },
215
+ label: "Emote",
216
+ description: "Emote/gesture. No built-in behavior yet \u2014 reserved for the future emote system",
217
+ touch: "button"
218
+ },
219
+ {
220
+ id: "zoom",
221
+ kind: "delta",
222
+ domain: "character",
223
+ context: "gameplay",
224
+ bindings: { wheel: "y", deltaRate: 1200, withModifier: { stick: "rightStick" } },
225
+ label: "Zoom",
226
+ description: "Camera zoom delta (wheel px; pinch on touch; hold modifier + right stick on pad \u2014 forward zooms in). Positive = zoom out",
227
+ touch: "pinch"
228
+ },
229
+ {
230
+ id: "modifier",
231
+ kind: "button",
232
+ domain: "character",
233
+ context: "gameplay",
234
+ bindings: { pad: "dpadRight" },
235
+ label: "Modifier",
236
+ description: "Held chord layer: while down, withModifier bindings resolve (zoom on the right stick) and their sources go quiet for their normal owners",
237
+ touch: "hold"
238
+ },
239
+ {
240
+ id: "cameraMode",
241
+ kind: "button",
242
+ domain: "character",
243
+ context: "gameplay",
244
+ bindings: { keys: ["KeyT"], pad: "dpadLeft" },
245
+ label: "Camera",
246
+ description: "Cycle the camera: first-person \u2192 third-person \u2192 shoulder right \u2192 shoulder left. allowModeToggle gates the FP\u2194TP legs, allowShoulderSwap the shoulder legs; all-gated = inert",
247
+ touch: "button"
248
+ },
249
+ // ---- world / system (worlds opt in via registerStandardActions) ----
250
+ {
251
+ // Not Escape — the SDK owns Escape (tablet overlay) and pointer-lock exit rides it at browser level.
252
+ id: "pause",
253
+ kind: "button",
254
+ domain: "world",
255
+ context: "gameplay",
256
+ bindings: { keys: ["KeyP"], pad: "start" },
257
+ label: "Pause",
258
+ description: "Open the pause/system menu",
259
+ touch: "button"
260
+ },
261
+ {
262
+ id: "inventory",
263
+ kind: "button",
264
+ domain: "world",
265
+ context: "gameplay",
266
+ bindings: { keys: ["Tab", "KeyI"], pad: "select" },
267
+ label: "Inventory",
268
+ description: "Open the inventory (worlds push the menu context)",
269
+ touch: "button"
270
+ },
271
+ {
272
+ id: "map",
273
+ kind: "button",
274
+ domain: "world",
275
+ context: "gameplay",
276
+ bindings: { keys: ["KeyM"] },
277
+ label: "Map",
278
+ description: "Toggle the map/overview",
279
+ touch: "button"
280
+ },
281
+ {
282
+ // Keyboard PTT stays SDK-side (the reserved KeyB / pttKey listener — pointer-lock focus lives in
283
+ // the iframe); this action is the pad/touch leg. attachVoice registers it and forwards to setPttDown.
284
+ id: "voicePTT",
285
+ kind: "button",
286
+ domain: "world",
287
+ context: "global",
288
+ bindings: { withModifier: { pad: "bumperR" } },
289
+ label: "Push to talk",
290
+ description: "Hold to talk in PTT voice mode (modifier + right bumper on pad; keyboard uses the SDK pttKey, default B). Global context \u2014 keeps working under menus. Auto-registered when the world attaches voice",
291
+ touch: "hold"
292
+ },
293
+ {
294
+ id: "confirm",
295
+ kind: "button",
296
+ domain: "world",
297
+ context: "menu",
298
+ bindings: { keys: ["Enter", "Space"], pad: "faceDown" },
299
+ label: "Confirm",
300
+ description: "Accept the focused menu item",
301
+ touch: "tap"
302
+ },
303
+ {
304
+ id: "cancel",
305
+ kind: "button",
306
+ domain: "world",
307
+ context: "menu",
308
+ bindings: { keys: ["Backspace"], pad: "faceRight" },
309
+ label: "Cancel",
310
+ description: "Back out of the current menu level",
311
+ touch: "button"
312
+ },
313
+ {
314
+ // Right stick: 'look' is gameplay-context so the stick is free in menus — the "look becomes
315
+ // navigate" handoff, mirroring how pointer lock releases when a menu opens.
316
+ id: "uiNavigate",
317
+ kind: "vec2",
318
+ domain: "world",
319
+ context: "menu",
320
+ bindings: { composite: { up: ["ArrowUp"], down: ["ArrowDown"], left: ["ArrowLeft"], right: ["ArrowRight"] }, stick: "rightStick" },
321
+ label: "Navigate",
322
+ description: "Move the menu focus",
323
+ touch: "swipe"
324
+ },
325
+ {
326
+ id: "tabPrev",
327
+ kind: "button",
328
+ domain: "world",
329
+ context: "menu",
330
+ bindings: { keys: ["KeyQ"], pad: "bumperL" },
331
+ label: "Previous tab",
332
+ description: "Cycle menu tabs left",
333
+ touch: "swipe"
334
+ },
335
+ {
336
+ id: "tabNext",
337
+ kind: "button",
338
+ domain: "world",
339
+ context: "menu",
340
+ bindings: { keys: ["KeyE"], pad: "bumperR" },
341
+ label: "Next tab",
342
+ description: "Cycle menu tabs right",
343
+ touch: "swipe"
344
+ }
345
+ ];
346
+ var byId = new Map(STANDARD_ACTIONS.map((a) => [a.id, a]));
347
+ var STANDARD_CHARACTER_ACTION_IDS = STANDARD_ACTIONS.filter((a) => a.domain === "character").map((a) => a.id);
348
+
37
349
  // src/bundle/abilityManifest.ts
38
350
  var ABILITY_ID_PATTERN = "^[a-z0-9](?:[a-z0-9-]{1,48})[a-z0-9]$";
39
351
  var SEMVER_PATTERN = "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-[0-9A-Za-z.-]+)?$";
@@ -243,15 +555,22 @@ function validateAbilityManifest(value) {
243
555
  issues.push({ path, message: `must be an object (got ${typeName(action)})` });
244
556
  return;
245
557
  }
246
- const actionId = checkQualifiedName(action.id, `${path}.id`, issues, id, "action");
558
+ const isStandard = typeof action.id === "string" && STANDARD_CHARACTER_ACTION_IDS.includes(action.id);
559
+ const actionId = isStandard ? action.id : checkQualifiedName(action.id, `${path}.id`, issues, id, "action");
247
560
  if (actionId !== void 0) {
248
561
  if (actionIds.includes(actionId)) issues.push({ path: `${path}.id`, message: `duplicate action id '${actionId}'` });
249
562
  actionIds.push(actionId);
250
563
  }
564
+ if (isStandard && action.kind !== void 0) {
565
+ issues.push({ path: `${path}.kind`, message: `standard action '${actionId}' takes its kind from the platform registry \u2014 omit kind` });
566
+ }
567
+ if (isStandard && action.defaultBinding !== void 0) {
568
+ issues.push({ path: `${path}.defaultBinding`, message: `standard action '${actionId}' takes its bindings from the platform registry \u2014 omit defaultBinding` });
569
+ }
251
570
  if (action.kind !== void 0 && (typeof action.kind !== "string" || !["button", "toggle", "vec2"].includes(action.kind))) {
252
571
  issues.push({ path: `${path}.kind`, message: "must be one of: button, toggle, vec2" });
253
572
  }
254
- if (action.defaultBinding !== void 0) checkBindings(action.defaultBinding, `${path}.defaultBinding`, issues);
573
+ if (!isStandard && action.defaultBinding !== void 0) checkBindings(action.defaultBinding, `${path}.defaultBinding`, issues);
255
574
  for (const key of Object.keys(action)) {
256
575
  if (!["id", "kind", "defaultBinding"].includes(key)) issues.push({ path: `${path}.${key}`, message: "unknown field (allowed: id, kind, defaultBinding)" });
257
576
  }
@@ -371,7 +690,10 @@ var ABILITY_V1_JSON_SCHEMA = {
371
690
  required: ["schemaVersion", "id", "version", "title", "system", "activation", "configSchema", "entry"],
372
691
  properties: {
373
692
  $schema: { type: "string", description: "Editor convenience \u2014 ignored by the validator." },
374
- schemaVersion: { const: 1, description: "Payload schema version. Post-freeze changes bump this (ability-v2), never edit v1." },
693
+ schemaVersion: {
694
+ const: 1,
695
+ description: "Payload schema version. Post-freeze changes bump this (ability-v2), never edit v1. One deliberate pre-public exception (2026-07-20): bare STANDARD character action ids became legal in `actions` \u2014 a backward-compatible loosening (every previously-valid manifest still validates). The freeze is absolute once the platform is public."
696
+ },
375
697
  id: {
376
698
  type: "string",
377
699
  pattern: ABILITY_ID_PATTERN,
@@ -459,7 +781,10 @@ var ABILITY_V1_JSON_SCHEMA = {
459
781
  additionalProperties: false,
460
782
  required: ["id"],
461
783
  properties: {
462
- id: { type: "string", description: "Qualified action id '<abilityId>.<action>', e.g. 'fly.toggle'." },
784
+ id: {
785
+ type: "string",
786
+ description: "Qualified action id '<abilityId>.<action>' (e.g. 'fly.toggle') \u2014 or a bare STANDARD character action id (e.g. 'primary', 'reload'): the platform registry then owns kind + bindings, so omit both fields."
787
+ },
463
788
  kind: { enum: ["button", "toggle", "vec2"], default: "button", description: "Action kind (mirrors the system input service)." },
464
789
  defaultBinding: {
465
790
  type: "object",
@@ -526,7 +851,7 @@ var ABILITY_V1_JSON_SCHEMA = {
526
851
 
527
852
  // src/core/identity.ts
528
853
  var SYSTEM_ID = "humanoid-character";
529
- var SYSTEM_VERSION = "0.2.2";
854
+ var SYSTEM_VERSION = "0.2.7";
530
855
  var SKELETON_STANDARD = "helix-humanoid@1";
531
856
  var SYSTEM_IDENTITY = { id: SYSTEM_ID, version: SYSTEM_VERSION, skeleton: SKELETON_STANDARD };
532
857
  export {