@genex-ai/cli-demo 1.36.5 → 1.36.6-dev.862
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/dist/{blender-mcp-QVEGHE5L.js → blender-mcp-WSURPZET.js} +2 -2
- package/dist/{blender-serve-66OHC4ML.js → blender-serve-EH2MOW3U.js} +1 -1
- package/dist/{chunk-4BBTPRJW.js → chunk-EABIVAGT.js} +2 -2
- package/dist/{chunk-QI7FIYBY.js → chunk-K4EHXOZK.js} +1 -1
- package/dist/index.js +1301 -576
- package/package.json +1 -1
- package/templates/controllers/character/aim-cue.ts +2 -1
- package/templates/controllers/character/follow-camera.ts +4 -3
- package/templates/skills/genex-ai-video/SKILL.md +8 -3
- package/templates/skills/genex-game-director/SKILL.md +2 -1
- package/templates/skills/genex-getting-started/SKILL.md +2 -2
- package/templates/skills/genex-llm-in-games/SKILL.md +219 -73
- package/templates/skills/genex-llm-in-games/references/pricing.md +126 -85
- package/templates/skills/genex-monetization/SKILL.md +26 -0
- package/templates/skills/genex-threejs-camera-direction/SKILL.md +3 -3
- package/templates/skills/genex-threejs-character-controller/references/wiring.md +2 -2
- package/templates/skills/genex-threejs-embed-auth/SKILL.md +158 -36
- package/templates/skills/genex-threejs-embed-auth/references/shells.md +207 -0
- package/templates/skills/genex-threejs-game-ui/SKILL.md +22 -14
- package/templates/skills/genex-threejs-multiplayer/SKILL.md +46 -8
- package/templates/skills/genex-threejs-touch-controls/SKILL.md +63 -1
- package/templates/skills/genex-tool-llm/SKILL.md +11 -9
- package/templates/skills/genex-updates/SKILL.md +1 -1
|
@@ -401,13 +401,39 @@ and friends join it; the lobby is persistent (rounds replay, nobody is evicted):
|
|
|
401
401
|
|
|
402
402
|
```ts
|
|
403
403
|
import { createPrivate, joinPrivate } from "@genex-ai/multiplayer";
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
404
|
+
import { share, getEntry } from "@genex-ai/embed-sdk"; // 0.32.0+
|
|
405
|
+
const opts = { urls, room: slug, auth: () => getColyseusAuth() };
|
|
406
|
+
|
|
407
|
+
// host
|
|
408
|
+
const lobby = await createPrivate<MyState>(opts); // live NOW
|
|
409
|
+
showCode(lobby.code); // always show the code too: the way in when a link will not do
|
|
410
|
+
inviteBtn.onclick = async () => { // share() starts inside the click, before any await
|
|
411
|
+
const { status, url } = await share({ text: "Join my lobby", params: { lobby: lobby.code } });
|
|
412
|
+
if (status === "copied") toast("Invite link copied");
|
|
413
|
+
if (status === "unavailable") showLink(url); // no share sheet, no clipboard: show the link
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// friend: ONE way in, for the code from a link and for a code typed by hand
|
|
417
|
+
async function enter(code: string) {
|
|
418
|
+
if (!/^[\w-]{1,32}$/.test(code)) return null; // a link is text anyone can write: take a code, nothing else
|
|
419
|
+
try { return await joinPrivate<MyState>(code, opts); }
|
|
420
|
+
catch { showNotice("That lobby is no longer open"); return null; }
|
|
421
|
+
}
|
|
422
|
+
// at boot, after waitForPlayer(): did they arrive on an invite link?
|
|
423
|
+
const invited = getEntry().params.get("lobby");
|
|
424
|
+
const joined = invited ? await enter(invited) : null; // null → the menu, with its "Enter code" field
|
|
408
425
|
// same handle API as matchmake(): lobby.session, lobby.matchmaking, eliminated()/score()/finish(), cancel()
|
|
409
426
|
```
|
|
410
427
|
|
|
428
|
+
`share()` builds the link itself — the game's page on Genex plus `?lobby=<code>`, or the
|
|
429
|
+
address the game is on where that is a preview address or a creator's own domain — and
|
|
430
|
+
`getEntry()` hands the parameter back to whoever opens it. **Never build the invite from
|
|
431
|
+
`location.href`**: on the game's page that is the frame's bare address, not the page a friend
|
|
432
|
+
should land on. `lobby` is this example's name; any name works except the ones Genex keeps
|
|
433
|
+
(`$genex-threejs-embed-auth`, `references/shells.md`). `joinPrivate` REJECTS when the code leads
|
|
434
|
+
to no lobby this player can reach — it closed, or its host is on another region's relay — so
|
|
435
|
+
catch it and show the menu, and keep the typed code as the second way in.
|
|
436
|
+
|
|
411
437
|
## Connect
|
|
412
438
|
|
|
413
439
|
Pick your own per-player state shape (any JSON). `room` is the **project slug**
|
|
@@ -674,10 +700,22 @@ fights (many writers). A ball on `objects` glides and has one owner. That's the
|
|
|
674
700
|
mesh — everyone uploads to everyone, and reliability, not bandwidth, breaks first). The
|
|
675
701
|
room still holds 64 players; the 7th `join()` is refused, so handle `voice:rejected`
|
|
676
702
|
instead of retrying. `join()` MUST run inside a click/tap — browsers gate both the mic
|
|
677
|
-
prompt and audio playback on a user gesture — and it REJECTS when the
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
703
|
+
prompt and audio playback on a user gesture — and it REJECTS when the microphone is
|
|
704
|
+
refused, which is an ordinary outcome to show, not an error to swallow. **Two refusals are
|
|
705
|
+
not the player's.** On its Genex page a game gets the microphone only when its BUILD
|
|
706
|
+
declared it: `npx genex preview` / `npx genex publish` declare it when the game's own
|
|
707
|
+
source calls `.voice.join()` with nothing or with options written at the call, and print
|
|
708
|
+
what the build is given — read that line. `voice.join(opts)` with the options in a
|
|
709
|
+
variable is not seen: declare the microphone in `package.json` then. When the
|
|
710
|
+
page withheld it, `join()` rejects with an error whose `name` is `VoiceNotDelegatedError`
|
|
711
|
+
(SDK 0.16.2+, Chromium browsers; elsewhere it arrives as the browser's own error): show
|
|
712
|
+
"Voice chat is not available here", never "allow your microphone" — the player was never
|
|
713
|
+
asked. And the Genex app refuses the microphone for every game: hide the voice button when
|
|
714
|
+
`getShell()` from `@genex-ai/embed-sdk` is `"app"`. The declaration rule, and the
|
|
715
|
+
`package.json` line for a call the CLI cannot see, are in `$genex-threejs-embed-auth`
|
|
716
|
+
(`references/shells.md`).
|
|
717
|
+
For a 3D game use `npx genex controller voice` (proximity audio + push-to-talk); it joins
|
|
718
|
+
with `{ spatial: true }` and drives a `THREE.PositionalAudio` per speaker. **TURN is automatic
|
|
681
719
|
— do not pass `iceServers`**: `join()` fetches short-lived relay credentials from the
|
|
682
720
|
platform before opening any peer connection, which is what lets the ~15% of players behind
|
|
683
721
|
symmetric NAT or carrier CGNAT be heard at all (in a mesh they fail every leg, so they
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: genex-threejs-touch-controls
|
|
3
|
-
description: Make a Genex Three.js game playable on phones — vendored touch primitives via `npx genex controller touch` (floating/static joystick, virtual buttons, drag zone, rotate-device overlay) plus per-genre wiring recipes (movement, camera look, taps, paddles, action buttons, touch pause). Use when wiring default touch controls into a new game, whenever someone asks to make a game playable on mobile/phone/tablet, or before shipping a game whose input is keyboard/mouse-only.
|
|
3
|
+
description: Make a Genex Three.js game playable on phones — vendored touch primitives via `npx genex controller touch` (floating/static joystick, virtual buttons, drag zone, rotate-device overlay) plus per-genre wiring recipes (movement, camera look, taps, paddles, action buttons, touch pause, tilt). Use when wiring default touch controls into a new game, whenever someone asks to make a game playable on mobile/phone/tablet, or before shipping a game whose input is keyboard/mouse-only.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Genex Three.js Touch Controls
|
|
@@ -116,6 +116,68 @@ If the game is merely suboptimal sideways, skip the overlay and just resize.
|
|
|
116
116
|
The web cannot force an orientation (fullscreen-only lock, none on iOS Safari)
|
|
117
117
|
— the overlay asks; it never traps.
|
|
118
118
|
|
|
119
|
+
## Tilt controls
|
|
120
|
+
|
|
121
|
+
Steering by tilting the phone is an extra on top of the stick, never a
|
|
122
|
+
replacement for it: the sensor is the one input a phone can silently not have.
|
|
123
|
+
Four rules, all in the block below.
|
|
124
|
+
|
|
125
|
+
1. **Ask inside the Start tap.** An iPhone hands a page no motion events until
|
|
126
|
+
the player allows them, and it asks only when `requestPermission()` is
|
|
127
|
+
called from a tap inside the game. Called at boot it is refused without a
|
|
128
|
+
prompt.
|
|
129
|
+
2. **A refusal, or silence, is "no sensor".** A browser can answer "granted"
|
|
130
|
+
and then send nothing, and a desktop can send events full of `null`. Trust
|
|
131
|
+
tilt only after a real reading has arrived.
|
|
132
|
+
3. **Keep the joystick.** It stays on screen and wins whenever it is touched.
|
|
133
|
+
4. **Hold the screen awake.** Tilt play has no touches, so the phone dims and
|
|
134
|
+
locks mid-game unless the game asks it not to.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { keepAwake } from "@genex-ai/embed-sdk"; // 0.32.0+
|
|
138
|
+
|
|
139
|
+
const tilt = { x: 0, y: 0, live: false }; // x/y in [-1, 1], same sense as joy.x / joy.y
|
|
140
|
+
const TILT_FULL = 25; // degrees of lean for full deflection
|
|
141
|
+
let rest: { x: number; y: number } | null = null; // how the phone was held at the first reading
|
|
142
|
+
|
|
143
|
+
type MotionGate = { requestPermission?: () => Promise<"granted" | "denied"> };
|
|
144
|
+
|
|
145
|
+
// Call from the Start/Play tap handler, before any await in that handler.
|
|
146
|
+
async function enableTilt(): Promise<void> {
|
|
147
|
+
void keepAwake(true); // released with keepAwake(false) on the menu / pause
|
|
148
|
+
const gate = (window as unknown as { DeviceOrientationEvent?: MotionGate }).DeviceOrientationEvent;
|
|
149
|
+
if (!gate) return; // no sensor API: the joystick is the control
|
|
150
|
+
try {
|
|
151
|
+
if (typeof gate.requestPermission === "function" && (await gate.requestPermission()) !== "granted") return;
|
|
152
|
+
} catch { return; } // refused, or not inside a tap
|
|
153
|
+
window.addEventListener("deviceorientation", (e) => {
|
|
154
|
+
if (e.beta == null || e.gamma == null) return; // a desktop fires events with nulls
|
|
155
|
+
// The reading is in the DEVICE's axes; turn it into the screen's.
|
|
156
|
+
const a = screen.orientation?.angle ?? 0;
|
|
157
|
+
const x = a === 90 ? e.beta : a === 270 ? -e.beta : a === 180 ? -e.gamma : e.gamma;
|
|
158
|
+
const y = a === 90 ? -e.gamma : a === 270 ? e.gamma : a === 180 ? -e.beta : e.beta;
|
|
159
|
+
rest ??= { x, y }; // "level" is however the player was holding it
|
|
160
|
+
const unit = (v: number) => Math.max(-1, Math.min(1, v / TILT_FULL));
|
|
161
|
+
tilt.x = unit(x - rest.x);
|
|
162
|
+
tilt.y = -unit(y - rest.y); // leaning the top edge away from you = up, like the stick
|
|
163
|
+
tilt.live = true;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// each frame, where the stick is read:
|
|
168
|
+
const steer = joy.x !== 0 ? joy.x : tilt.live ? tilt.x : 0;
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Clear `rest` when the device rotates or the player taps a "recentre" button,
|
|
172
|
+
so level is re-taken from the next reading. **Check the directions on a real
|
|
173
|
+
phone, in the orientation the game is played in** — a desktop browser has no
|
|
174
|
+
sensor, so nothing in a local test exercises this. Lean-right must steer
|
|
175
|
+
screen-right (the screen-direction contract in
|
|
176
|
+
`$genex-threejs-camera-direction`); a flipped feel is a sign to fix in the
|
|
177
|
+
mapping above, never a device quirk. What each Genex shell does with the
|
|
178
|
+
sensors and the wake lock is in `$genex-threejs-embed-auth`
|
|
179
|
+
(`references/shells.md`).
|
|
180
|
+
|
|
119
181
|
## Match the game's style
|
|
120
182
|
|
|
121
183
|
The touch controls are UI like any other — restyle them from the same style
|
|
@@ -13,11 +13,12 @@ model has to run for the player.
|
|
|
13
13
|
## What the platform does
|
|
14
14
|
|
|
15
15
|
A game hosted on Genex can call a language model from inside the running game
|
|
16
|
-
and get back text or JSON. **The player pays and the player approves**:
|
|
17
|
-
from their Genex
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
and get back text or JSON. **The player pays and the player approves**: credits
|
|
17
|
+
from their Genex account, billed as each call is used and never past the
|
|
18
|
+
per-call ceiling the game declares, or their own Claude / ChatGPT subscription,
|
|
19
|
+
chosen on an approval sheet Genex draws that the game cannot render, skin or
|
|
20
|
+
bypass. Either one call at a time, or one standing budget the player approves
|
|
21
|
+
once and the game then spends against without another popup. The game holds no provider
|
|
21
22
|
key, sees no credential, and never talks to a model vendor.
|
|
22
23
|
|
|
23
24
|
That is the whole reason this is a platform feature and not something you wire
|
|
@@ -40,9 +41,9 @@ Any of these means a model at PLAY time, however it is phrased:
|
|
|
40
41
|
Say this and stop:
|
|
41
42
|
|
|
42
43
|
> A model running while people play is built into the Genex platform — the
|
|
43
|
-
> player pays, with Genex
|
|
44
|
-
> approves it on a Genex sheet; your game just calls
|
|
45
|
-
> that way?
|
|
44
|
+
> player pays, with their Genex credits or their own Claude/ChatGPT
|
|
45
|
+
> subscription, and approves it on a Genex sheet; your game just calls
|
|
46
|
+
> `generate()`. Want it that way?
|
|
46
47
|
|
|
47
48
|
Wait for the answer. Do not start building either version first, and do not
|
|
48
49
|
expand the offer into a pitch — one line, one question.
|
|
@@ -64,7 +65,8 @@ expand the offer into a pitch — one line, one question.
|
|
|
64
65
|
ask before you do if you have not already.
|
|
65
66
|
3. Load `$genex-llm-in-games` — it arrives with the conversion and owns the
|
|
66
67
|
build: the SDK surface, one-time calls versus a standing budget, measuring
|
|
67
|
-
the
|
|
68
|
+
the per-call ceiling before the game declares it, and honest handling of
|
|
69
|
+
every refusal.
|
|
68
70
|
|
|
69
71
|
**The game has to be a static browser build.** A hosted game is files served
|
|
70
72
|
from the edge; there is no server of yours inside it. So the familiar pattern —
|
|
@@ -38,7 +38,7 @@ update, so update immediately.)
|
|
|
38
38
|
Run exactly the command the nudge printed, from the game project root:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
npm i -D @genex-ai/cli-demo@
|
|
41
|
+
npm i -D @genex-ai/cli-demo@dev # the genex CLI (a dev dependency)
|
|
42
42
|
npm i @genex-ai/embed-sdk@latest # identity/saves SDK (ships inside the game)
|
|
43
43
|
npm i @genex-ai/multiplayer@latest # multiplayer SDK (only if the game uses it)
|
|
44
44
|
```
|