@genex-ai/cli-demo 0.16.0 → 0.18.0

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 (36) hide show
  1. package/README.md +10 -0
  2. package/dist/index.js +299 -58
  3. package/package.json +1 -1
  4. package/templates/skills/genex-ai-model/SKILL.md +6 -1
  5. package/templates/skills/genex-ai-sfx/SKILL.md +9 -0
  6. package/templates/skills/genex-ai-skybox/SKILL.md +9 -1
  7. package/templates/skills/genex-ai-texture/SKILL.md +11 -1
  8. package/templates/skills/genex-getting-started/SKILL.md +44 -0
  9. package/templates/skills/genex-threejs-atmosphere-aerial-perspective/SKILL.md +4 -0
  10. package/templates/skills/genex-threejs-bloom/references/bloom.md +3 -1
  11. package/templates/skills/genex-threejs-camera-direction/SKILL.md +6 -0
  12. package/templates/skills/genex-threejs-camera-direction/references/camera-rigs.md +2 -2
  13. package/templates/skills/genex-threejs-character-controller/SKILL.md +6 -1
  14. package/templates/skills/genex-threejs-embed-auth/SKILL.md +9 -1
  15. package/templates/skills/genex-threejs-game-feel/SKILL.md +97 -0
  16. package/templates/skills/genex-threejs-game-ui/SKILL.md +119 -0
  17. package/templates/skills/genex-threejs-image-pipeline/references/image-pipeline.md +2 -0
  18. package/templates/skills/genex-threejs-multiplayer/SKILL.md +11 -6
  19. package/templates/skills/genex-threejs-multiplayer/references/realtime-patterns.md +16 -5
  20. package/templates/skills/genex-threejs-physics-rapier/SKILL.md +2 -1
  21. package/templates/skills/genex-threejs-physics-rapier/references/colliders-from-assets.md +1 -1
  22. package/templates/skills/genex-threejs-procedural-animation/references/procedural-motion.md +8 -8
  23. package/templates/skills/genex-threejs-screen-space-ambient-occlusion/SKILL.md +1 -1
  24. package/templates/skills/genex-threejs-screen-space-ambient-occlusion/references/ambient-occlusion.md +2 -0
  25. package/templates/skills/genex-threejs-shadow-systems/SKILL.md +6 -1
  26. package/templates/skills/genex-threejs-shadow-systems/references/shadow-systems.md +2 -0
  27. package/templates/skills/genex-threejs-skill-router/SKILL.md +23 -3
  28. package/templates/skills/genex-threejs-skill-router/references/routing-map.md +30 -13
  29. package/templates/skills/genex-threejs-spectral-ocean/SKILL.md +2 -0
  30. package/templates/skills/genex-threejs-spectral-ocean/references/spectral-ocean.md +2 -0
  31. package/templates/skills/genex-threejs-vehicle-controllers/SKILL.md +24 -0
  32. package/templates/skills/genex-threejs-vehicle-controllers/references/car.md +30 -2
  33. package/templates/skills/genex-threejs-vehicle-controllers/references/drone.md +21 -2
  34. package/templates/skills/genex-threejs-visual-validation/SKILL.md +15 -0
  35. package/templates/skills/genex-threejs-volumetric-clouds/SKILL.md +4 -0
  36. package/templates/skills/genex-threejs-water-optics/references/water-optics.md +3 -1
@@ -58,7 +58,7 @@ Use Three.js `GLTFLoader` (the game serves at the domain root
58
58
  `https://<slug>.genex.technology/`, so relative or absolute paths both work):
59
59
 
60
60
  ```ts
61
- import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
61
+ import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
62
62
 
63
63
  const loader = new GLTFLoader();
64
64
  const gltf = await loader.loadAsync("./assets/models/weathered-wooden-barrel.glb");
@@ -71,6 +71,11 @@ scene.add(model);
71
71
  To place many copies, `model.clone()` per instance. For animated GLBs, drive
72
72
  `gltf.animations` with a `THREE.AnimationMixer`.
73
73
 
74
+ **If the game has physics, give the model a collider** — a GLB added only to the
75
+ scene is a ghost: players and objects pass straight through it.
76
+ `$genex-threejs-physics-rapier` has the decision table
77
+ (`collidersFromObject(model, "hull")` is the default for props).
78
+
74
79
  ## Publish checklist (so players see the model)
75
80
 
76
81
  - Reference assets with **relative** paths (`./assets/...`); absolute
@@ -58,8 +58,17 @@ Reuse one loaded `buffer` across many plays; create a fresh `Audio`/`PositionalA
58
58
  - Relative path `./assets/sfx/...`; `base: "./"` in `vite.config.ts`; files live under
59
59
  `public/assets/` so Vite ships them; `npx genex publish` ships them to R2.
60
60
 
61
+ ## Options
62
+
63
+ - `--duration <sec>` — target length 0.5–22 (shown in Run above); omit to let
64
+ the model pick.
65
+ - `--no-wait` — enqueue and return immediately (the file won't be downloaded;
66
+ re-run without `--no-wait` to fetch it).
67
+ - `--api-url <url>` — override the API base (local dev).
68
+
61
69
  ## Troubleshooting
62
70
 
71
+ - **"Not authorized"** — run `npx @genex-ai/cli-demo@latest init` first (it writes your `GENEX_TOKEN`).
63
72
  - **No sound** — the `AudioContext` is suspended until a user gesture; trigger the
64
73
  first play from a click/keydown. Confirm the camera has an `AudioListener`.
65
74
  - **Too quiet/loud** — `sound.setVolume(0..1)`; for positional, tune
@@ -56,7 +56,8 @@ const pmrem = new THREE.PMREMGenerator(renderer);
56
56
  const envMap = pmrem.fromEquirectangular(texture).texture;
57
57
  scene.environment = envMap;
58
58
  scene.background = texture; // keep the raw texture for the visible sky
59
- texture.dispose; // (dispose the PMREM source later if you stop using it)
59
+ // Do NOT dispose `texture` here it IS the visible background. Dispose it only
60
+ // if you later stop using it as the sky.
60
61
  ```
61
62
 
62
63
  ## Publish checklist
@@ -66,8 +67,15 @@ texture.dispose; // (dispose the PMREM source later if you stop using it)
66
67
  - Generated files live under `public/assets/` so Vite ships them; `npx genex publish`
67
68
  ships that folder with the game.
68
69
 
70
+ ## Options
71
+
72
+ - `--no-wait` — enqueue and return immediately (the file won't be downloaded;
73
+ re-run without `--no-wait` to fetch it).
74
+ - `--api-url <url>` — override the API base (local dev).
75
+
69
76
  ## Troubleshooting
70
77
 
78
+ - **"Not authorized"** — run `npx @genex-ai/cli-demo@latest init` first (it writes your `GENEX_TOKEN`).
71
79
  - **Sky looks too dark/bright** — adjust `renderer.toneMappingExposure`, or scale
72
80
  `scene.environment` influence via material `envMapIntensity`.
73
81
  - **Seam/pole artifacts** — that's inherent to equirect images; keep the camera
@@ -41,8 +41,10 @@ Committed by `npx genex publish`, so it ships with your game.
41
41
  ```ts
42
42
  import * as THREE from "three";
43
43
 
44
+ // The extension varies by provider (production emits .png) — use the EXACT
45
+ // path the CLI printed when generation finished, don't assume .jpg.
44
46
  const map = await new THREE.TextureLoader().loadAsync(
45
- "./assets/textures/lush-green-grass/basecolor.jpg",
47
+ "./assets/textures/lush-green-grass/basecolor.png",
46
48
  );
47
49
  map.colorSpace = THREE.SRGBColorSpace;
48
50
  map.wrapS = map.wrapT = THREE.RepeatWrapping;
@@ -70,8 +72,16 @@ scene.add(ground);
70
72
  - Relative path `./assets/textures/...`; `base: "./"` in `vite.config.ts`; files live under
71
73
  `public/assets/` so Vite ships them; `npx genex publish` ships them to R2.
72
74
 
75
+ ## Options
76
+
77
+ - `--terrain` — seamless tiling tuned for ground/terrain (shown in Run above).
78
+ - `--no-wait` — enqueue and return immediately (the file won't be downloaded;
79
+ re-run without `--no-wait` to fetch it).
80
+ - `--api-url <url>` — override the API base (local dev).
81
+
73
82
  ## Troubleshooting
74
83
 
84
+ - **"Not authorized"** — run `npx @genex-ai/cli-demo@latest init` first (it writes your `GENEX_TOKEN`).
75
85
  - **Visible tiling seams** — use `--terrain` (tuned for seamless edges), lower the
76
86
  `repeat`, or blend two textures. Perfect seamlessness is a known v1 limitation.
77
87
  - **Colors look washed/dark** — ensure `map.colorSpace = THREE.SRGBColorSpace`.
@@ -45,6 +45,30 @@ npx genex texture "mossy cobblestone" --terrain # a tiling surface texture
45
45
  Each has a focused skill with the exact loader code — `$genex-ai-model`,
46
46
  `$genex-ai-skybox`, `$genex-ai-sfx`, `$genex-ai-texture`.
47
47
 
48
+ ## Identity & saves (every game)
49
+
50
+ Every Genex game ships with built-in player identity: sign-in, guests, per-player
51
+ saves, a shared world slot, and leaderboards — all through `@genex-ai/embed-sdk`.
52
+ Load `$genex-threejs-embed-auth` before writing boot code; it wires the one-call
53
+ setup (`initEmbed` + `waitForPlayer`) and is also where multiplayer gets its
54
+ connection auth. A game without it loses all progress on refresh.
55
+
56
+ ## Loading screens (recommendation)
57
+
58
+ Generated assets take a moment to fetch at runtime. Show a simple loading
59
+ screen or progress hint while models/textures/sounds load, and remove it when
60
+ the game is ready — players should never stare at a black screen. This is a
61
+ recommendation, not a rule: even a one-line "Loading…" overlay over the dark
62
+ page background is enough.
63
+
64
+ ## Remixing an existing game
65
+
66
+ If the project folder already contains a game (a remix or any existing
67
+ project), don't scaffold a new app over it: read the existing code first and
68
+ keep its renderer, structure, and conventions — extend, don't rebuild.
69
+ Running `genex init` in such a folder only wires identity/publishing and
70
+ refreshes the genex-owned skills; the game's own files are never touched.
71
+
48
72
  ## Publishing
49
73
 
50
74
  Before your first `genex preview`, the play URL (`https://<slug>.genex.technology/`)
@@ -66,6 +90,26 @@ If unsure, use `games` (also the server's fallback for anything unrecognized).
66
90
  Your own files were left untouched. `genex init` only adds missing files and
67
91
  refreshes the genex-owned ones.
68
92
 
93
+ ## Reconnecting an existing game (`genex link`)
94
+
95
+ Each game's connection to its live page is folder-local (`.genex/project.json`
96
+ + the `genex_key` deploy key). If that folder is gone — deleted, or the game
97
+ was built on another machine — **don't run `init` to "recover" it**: that
98
+ creates a brand-new game at a new URL. Instead, clone the game's source repo
99
+ and re-link the clone to the same live game:
100
+
101
+ ```bash
102
+ git clone <the game's repo url> my-game && cd my-game
103
+ npm install
104
+ npx @genex-ai/cli-demo@latest link <slug> # slug = the name in the play URL
105
+ ```
106
+
107
+ `link` never creates a project: it signs in if needed (the browser opens once),
108
+ registers a fresh deploy key for this folder, and rewrites the local link.
109
+ After it, `npx genex preview` / `npx genex publish` update the **same live
110
+ game** — plays, likes, and comments stay. It also fixes a folder whose
111
+ authorization went stale ("Not authorized" from `preview`/`publish`).
112
+
69
113
  ## Re-running setup
70
114
 
71
115
  Safe to run any time — genex-owned skills are refreshed to the latest version,
@@ -5,6 +5,10 @@ description: Implement sky and aerial perspective for Genex Three.js games. Use
5
5
 
6
6
  # Genex Three.js Atmosphere And Aerial Perspective
7
7
 
8
+ **Static sky at a fixed time of day → `$genex-ai-skybox` instead** (one command,
9
+ a real 360° image + lighting). This skill is for dynamic, animated, or
10
+ scale-transitioning skies (time-of-day, ground-to-space).
11
+
8
12
  Treat sky rendering and aerial perspective as two views of the same scattering model. They must share radii, density profiles, coefficients, sun direction, exposure scale, and coordinate transforms.
9
13
 
10
14
  ## Choose the implementation tier
@@ -2,6 +2,8 @@
2
2
 
3
3
  Use this reference to choose bloom ownership, signal order, selective contribution, and scene-relative emissive ranges without making bloom responsible for the underlying form.
4
4
 
5
+ > **Renderer note:** this reference assumes `WebGPURenderer` + TSL node materials. Check the project's actual renderer first — the Genex scaffold ships vanilla WebGL three.js. On WebGL, adapt the technique with standard materials / `EffectComposer` passes or pick a simpler alternative; never switch renderers mid-project.
6
+
5
7
  ## Contents
6
8
 
7
9
  - production WebGPU pipeline signal order
@@ -119,7 +121,7 @@ Composer order:
119
121
  scene -> SSAO -> volumetrics -> bloom -> lens flare -> fog/color
120
122
  ```
121
123
 
122
- This is a useful comparison, not the quality target. The threshold is very low
124
+ This is a useful comparison, not the quality target — note its fog/color stage runs AFTER bloom, deviating from the canonical order in $genex-threejs-image-pipeline (fog belongs before bloom so bright fog does not glow). The threshold is very low
123
125
  and can bloom ordinary bright surfaces. The wrapper exposes only enabled,
124
126
  strength, and threshold, while radius stays at its constructor value.
125
127
 
@@ -5,6 +5,12 @@ description: Design advanced Three.js camera systems for Genex browser games. Us
5
5
 
6
6
  # Genex Three.js Camera Direction
7
7
 
8
+ **Boundary:** the pack's default third-person camera is the bundled
9
+ `FollowCamera` (see `$genex-threejs-character-controller` — the vehicle
10
+ controllers hand off to it too). Extend and tune that first; load this skill
11
+ for rigs beyond it — top-down/side views, authored shots, cinematics, pointer
12
+ look, and camera handoffs.
13
+
8
14
  Treat the camera as an authored visual system, not a passive viewport. Compose
9
15
  the subject, establish scale, choose a stable up frame, and make every mode
10
16
  handoff explicit.
@@ -289,8 +289,8 @@ Keys are cleared on:
289
289
 
290
290
  Scene-specific constraints then run after controls:
291
291
 
292
- - NASA room clamps X/Y/Z with floor, ceiling, and wall clearance.
293
- - Mann’s Planet clamps camera Y above sampled terrain plus `0.2`.
292
+ - an interior room scene clamps X/Y/Z with floor, ceiling, and wall clearance.
293
+ - a terrain scene clamps camera Y above sampled terrain plus `0.2`.
294
294
  - cinematic scenes block movement keys while retaining their authored camera.
295
295
 
296
296
  Input control and spatial constraint are separate layers.
@@ -139,7 +139,12 @@ when done (punch-on-click is the default). Read
139
139
  wiring, the **full 46-clip catalog** with genre hints, `playOneShot` options,
140
140
  overrides, foot IK, and remote-player animation.
141
141
 
142
- ## Mobile: TouchJoystick + VirtualButton
142
+ ## Mobile: TouchJoystick + VirtualButton (wire by default)
143
+
144
+ **Wire these whenever this controller is installed — not only when asked.**
145
+ Published games get opened on phones from shared links; the bundled touch
146
+ controls are ~6 lines and invisible on desktop. (Designing phone-specific
147
+ layouts or testing mobile viewports stays ask-only.)
143
148
 
144
149
  ```ts
145
150
  import { TouchJoystick, VirtualButton } from "./controllers/character/touch-joystick.ts";
@@ -138,7 +138,11 @@ session is blocked):
138
138
  - `getLeaderboard({ board?, limit?, order? }?)` → `Promise<{ items, me }>` —
139
139
  top entries (verified display names — never trust client-side name input
140
140
  for this) + the signed-in player's own `{ rank, score }`. Works for guests
141
- too (`me: null`).
141
+ too (`me: null`). `limit` caps at 100 server-side.
142
+
143
+ Server write limits (per player, per minute): **60 player-saves, 120
144
+ world-saves, 30 score submits**. A debounced ~1/sec checkpoint never gets near
145
+ them — only a save-per-frame loop does (it surfaces as HTTP 429).
142
146
 
143
147
  From `@genex-ai/embed-sdk/sentry` (crash reporting; exactly these two):
144
148
 
@@ -298,6 +302,10 @@ concerns.
298
302
  header (401), a guest token (403 `guest_no_save`), or the token belongs to a
299
303
  different game (403). All three mean the code bypassed the SDK helpers —
300
304
  switch to `savePlayerState`/`saveWorldState`, which handle every case.
305
+ - **Saves or score submits return 429** — writing too often (per-player limits:
306
+ 60 player-saves/min, 120 world-saves/min, 30 score submits/min). Debounce to
307
+ ~1 write/sec at checkpoints and never save in the render loop; back off a
308
+ second and retry.
301
309
  - **`saveWorldState` resolves `{ conflict: true }`** — another player wrote the
302
310
  shared slot since your last read. Expected under concurrency: reload with
303
311
  `loadWorldState()`, merge, retry with the fresh `version`. If it happens
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: genex-threejs-game-feel
3
+ description: Make a Genex Three.js game feel alive — instant input response, acceleration curves, camera follow and impact shake, layered hit feedback, hitstop, readable cooldowns, difficulty ramp, and a fast fail/retry loop. Use when a game technically works but feels flat, floaty, or boring, and before calling any playable loop done.
4
+ ---
5
+
6
+ # Genex Three.js Game Feel
7
+
8
+ "Works" and "feels good" are different finish lines. This skill is the second
9
+ one: the tuning pass that turns a functioning loop into a game people replay.
10
+ Run it once the mechanics exist — feel tuning before mechanics is polish on
11
+ nothing.
12
+
13
+ ## The loop test (run this first)
14
+
15
+ A playable loop has four parts. If any is missing, fix that before tuning:
16
+
17
+ 1. **Verb** — something the player does (move, jump, shoot, grab).
18
+ 2. **Objective** — something to chase (score, survive, reach, beat).
19
+ 3. **Feedback** — success and damage are unmistakable the moment they happen.
20
+ 4. **Fail / retry** — the game can be lost, and restarting is instant.
21
+
22
+ A game that can't be lost — or takes more than a second to retry — gets boring
23
+ no matter how good it looks.
24
+
25
+ ## Input: respond this frame
26
+
27
+ - Act on input the **same frame** it arrives. Animation follows the action; it
28
+ never gates it (the character moves now, the run cycle catches up).
29
+ - Add forgiveness windows: buffer a jump pressed ~100 ms early, allow a jump
30
+ ~100 ms after walking off a ledge. Players read forgiveness as "tight
31
+ controls", not as cheating. (The bundled `CharacterController` ships with
32
+ NO input buffer or coyote time — add these in `character-controller.ts`
33
+ itself, or apply them only to hand-rolled movement.)
34
+ - If an action can't fire (cooldown, no ammo), say so instantly — a click, a
35
+ dimmed icon — silence reads as broken input.
36
+
37
+ ## Movement: snappy beats realistic
38
+
39
+ - Reach max speed fast; stop even faster (deceleration stronger than
40
+ acceleration). Long ease-in reads as "floaty" — the most common complaint.
41
+ - Tune with live numbers, not rebuilds: keep the constants (accel, decel, max
42
+ speed, jump impulse) in one place and tweak while replaying the same ten
43
+ seconds.
44
+ - On-foot and vehicle movement ship pre-tuned in
45
+ `$genex-threejs-character-controller` and
46
+ `$genex-threejs-vehicle-controllers` — retune their exposed knobs before
47
+ writing new movement math.
48
+
49
+ ## Camera: the invisible half of feel
50
+
51
+ - Smooth follow with **lookahead** — bias the camera toward where the player
52
+ is going, never behind the action hiding the next decision.
53
+ - FOV kick on speed/boost (a few degrees, eased) sells acceleration better
54
+ than particles.
55
+ - Impact shake: short (< 0.2 s), small, decaying, and always **caused** —
56
+ shake per event, never ambient. Rigs and handoffs live in
57
+ `$genex-threejs-camera-direction`.
58
+ - Feel work (shake, FOV kick, hitstop triggers) runs in the **render phase,
59
+ after `physics.step(...)`** — never inside `onBeforeStep` (that's fixed-step
60
+ physics territory; see the physics skill's ordering contract).
61
+
62
+ ## Impact: layer the feedback
63
+
64
+ Every meaningful event the player caused should be **seen and heard**. For big
65
+ hits, layer two or three of:
66
+
67
+ - a flash or scale pop on the thing that was hit;
68
+ - a particle burst — `$genex-threejs-procedural-vfx`;
69
+ - a sound — generate real ones with `npx genex sfx` (`$genex-ai-sfx`);
70
+ - **hitstop**: freeze the simulation 30–80 ms on heavy impacts (skip rendering
71
+ pauses — just hold the physics/game clock). Longer than ~100 ms feels like lag.
72
+ With the bundled physics pack this is built in — `physics.paused = true` /
73
+ `physics.timeScale = 0.2` (see the physics skill's pause/slow-mo section) plus
74
+ `anims.setPaused(true)` / `anims.setTimeScale(0.2)` for the character's
75
+ animations. Don't hand-roll a second clock.
76
+
77
+ One layer per small event, three for the biggest — uniform intensity flattens
78
+ everything back out.
79
+
80
+ ## Pacing and fail/retry
81
+
82
+ - Show cooldowns and charges visibly (a refilling ring beats a hidden timer).
83
+ - Ramp difficulty measurably (speed, spawn rate, tighter windows) so the
84
+ player feels progress; reward near-misses where the design allows.
85
+ - Death should teach: show what killed you, keep the score/best visible, and
86
+ restart on ONE key without a page reload. The faster the retry, the more
87
+ "one more try" the game becomes.
88
+
89
+ ## Failure modes to catch
90
+
91
+ - A mechanic fires with zero feedback — it might as well not have happened.
92
+ - The camera hides what the player needs to react to next.
93
+ - The game cannot be lost, or losing dumps you to a dead screen.
94
+ - Restart requires reloading the page (kills the retry loop — and reloads
95
+ re-run auth and asset loading).
96
+ - Shake/flash spam with no cause — feedback inflation reads as noise.
97
+ - Feel constants scattered through the code where nobody dares touch them.
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: genex-threejs-game-ui
3
+ description: Design the 2D interface of a Genex Three.js game — HUD, menus, pause/win/lose screens, score and health displays, overlays, loading screens, and UI state flow. Use whenever the game needs on-screen text, meters, buttons, or menus, or when the interface reads as a generic dashboard, covers the action, or shifts around as numbers change.
4
+ ---
5
+
6
+ # Genex Three.js Game UI
7
+
8
+ The interface is part of the game, not chrome around it. A strong HUD tells the
9
+ player what to protect, what to chase, and what just happened — without ever
10
+ stealing attention from the scene. This skill covers the 2D layer of a vanilla
11
+ Three.js game: architecture, the states every game needs, readability rules,
12
+ and wiring UI to game state.
13
+
14
+ ## Architecture: DOM overlay by default
15
+
16
+ Genex games are plain Vite + Three.js apps, so the default UI layer is a DOM
17
+ overlay — HTML/CSS on top of the canvas, not text sprites inside the scene:
18
+
19
+ ```html
20
+ <div id="ui"> <!-- fixed, full-screen, pointer-events: none -->
21
+ <div id="hud">…</div>
22
+ <!-- data-phase must exactly match a phase name in setPhase() below — that's how
23
+ screens toggle. A phase with no screen (e.g. "playing") just hides them all. -->
24
+ <div id="screen-loading" class="screen" data-phase="loading">…</div>
25
+ <div id="screen-pause" class="screen" data-phase="paused" hidden>…</div>
26
+ <div id="screen-over" class="screen" data-phase="over" hidden>…</div>
27
+ </div>
28
+ ```
29
+
30
+ ```css
31
+ #ui { position: fixed; inset: 0; pointer-events: none; font-variant-numeric: tabular-nums; }
32
+ #ui button, #ui .screen { pointer-events: auto; }
33
+ ```
34
+
35
+ `pointer-events: none` on the root keeps the canvas playable; re-enable it only
36
+ on elements that are actually clickable. Keep ALL layout in CSS — never
37
+ position UI by mutating inline pixel styles per frame. In-world (diegetic) UI —
38
+ a health bar floating over an enemy, a scoreboard mesh in a stadium — is the
39
+ exception for things that belong to the world, not the default.
40
+
41
+ ## The states every game needs
42
+
43
+ Build these as show/hide layers over one state machine, not as ad-hoc DOM
44
+ edits scattered through the code:
45
+
46
+ 1. **Loading** — visible until assets are ready; players must never stare at a
47
+ black screen. Even "Loading… 3/5" over the dark background is enough.
48
+ 2. **Playing HUD** — the minimal always-on layer (see hierarchy below).
49
+ 3. **Pause** — freeze the loop, dim the scene, show resume/restart. (With the
50
+ bundled physics pack, freezing is built in: `physics.paused = true` plus
51
+ `anims.setPaused(true)` — don't hand-roll a second clock.)
52
+ 4. **Fail / retry** — what happened, the score, and a ONE-KEY instant restart
53
+ (show which key). Restart must not reload the page.
54
+ 5. **Win / next** — celebrate, then offer the next thing to do.
55
+ 6. **Identity moments** — the player's name/guest identity comes from
56
+ `$genex-threejs-embed-auth` (`waitForPlayer()`); leaderboards render from
57
+ `getLeaderboard()`. Never invent your own login UI.
58
+ 7. **Multiplayer lobby/points** — matchmaking status, countdowns, and match
59
+ HUD state come from `$genex-threejs-multiplayer`; render what the SDK
60
+ reports, don't guess at it.
61
+
62
+ ## Hierarchy: if everything shouts, nothing reads
63
+
64
+ Order the HUD by what the player loses the game for ignoring:
65
+
66
+ 1. **Survival/status** — health, time, fuel: biggest, most stable, edge/corner.
67
+ 2. **Objective** — score, laps, wave: prominent but calmer.
68
+ 3. **Moment feedback** — "+100" popups, damage flashes: transient, near the
69
+ action, gone in under a second.
70
+ 4. **Flavor** — combo names, taunts: smallest, skippable.
71
+
72
+ ## Readability rules
73
+
74
+ - **Never cover the player or incoming threats.** Corners and edges belong to
75
+ UI; the center of the screen belongs to the game.
76
+ - **Stable layout.** A score ticking 9 → 10 → 100 must not reflow anything:
77
+ tabular numerals (`font-variant-numeric: tabular-nums`), fixed-width slots,
78
+ meters that change fill — not size.
79
+ - **Contrast against the real scene.** Test text over the brightest AND
80
+ darkest areas of actual gameplay; a soft dark plate or text-shadow beats
81
+ restyling per level.
82
+ - **Desktop first.** Verify at desktop sizes and survive window resizes
83
+ without clipping; don't design phone layouts or test mobile viewports unless
84
+ the user asks. Exception: when a bundled controller (character/car/drone) is
85
+ installed, DO wire its ready-made touch controls behind
86
+ `navigator.maxTouchPoints > 0` — invisible on desktop, and the shared link
87
+ isn't dead on a phone (the controller skill has the wiring).
88
+
89
+ ## Wire UI to game state, never the reverse
90
+
91
+ The game state machine is the single source of truth; the UI renders it.
92
+ Don't keep a second copy of rules in the UI layer (a timer in the HUD and a
93
+ timer in the game WILL drift apart). Buttons and menu keys emit the same
94
+ intents the gameplay input path uses — a "Restart" button and the R key must
95
+ run identical code.
96
+
97
+ ```ts
98
+ // Each .screen carries data-phase (see the HTML block above); the names must match exactly.
99
+ function setPhase(phase: "loading" | "playing" | "paused" | "over" | "won") {
100
+ for (const s of document.querySelectorAll<HTMLElement>("#ui .screen"))
101
+ s.hidden = s.dataset.phase !== phase;
102
+ }
103
+ ```
104
+
105
+ UI sounds (clicks, confirms, win stingers) are one `npx genex sfx` away — see
106
+ `$genex-ai-sfx`.
107
+
108
+ ## Failure modes to catch before the player does
109
+
110
+ - A generic stat dashboard (rows of labels + numbers) instead of a designed
111
+ HUD — pick the 2–3 numbers that matter and style them by hierarchy.
112
+ - UI panels covering the player or the thing about to kill them.
113
+ - Layout shifting as numbers grow.
114
+ - A fail state with no visible restart key, or a restart that reloads the page.
115
+ - Buttons that render but don't emit the game's real input intents.
116
+ - UI logic duplicating game rules and drifting out of sync.
117
+
118
+ Before calling UI done, check it over real gameplay footage at desktop size —
119
+ `$genex-threejs-visual-validation` has the capture discipline.
@@ -2,6 +2,8 @@
2
2
 
3
3
  Use this reference to compose shared scene buffers, lighting effects, atmosphere, bloom, exposure, tone mapping, grading, and feature-local render targets with explicit ownership.
4
4
 
5
+ > **Renderer note:** this reference assumes `WebGPURenderer` + TSL node materials. Check the project's actual renderer first — the Genex scaffold ships vanilla WebGL three.js. On WebGL, adapt the technique with standard materials / `EffectComposer` passes or pick a simpler alternative; never switch renderers mid-project.
6
+
5
7
  ## Contents
6
8
 
7
9
  - production WebGPU pipeline WebGPU graph
@@ -37,7 +37,7 @@ example, the shared-object/ball code, rotation, and host usage. Read
37
37
  npm i @genex-ai/multiplayer
38
38
  ```
39
39
 
40
- This skill targets `@genex-ai/multiplayer` **≥ 0.7.0** (`objects`/`host` since 0.4; `matchmake()` since 0.5; presets + `score()`/`finish()` since 0.6; `createPrivate()`/`joinPrivate()` since 0.7).
40
+ This skill targets `@genex-ai/multiplayer` **≥ 0.7.1** (`objects`/`host` since 0.4; `matchmake()` since 0.5; presets + `score()`/`finish()` since 0.6; `createPrivate()`/`joinPrivate()` since 0.7; matchmake auto-retry + `retry()` since 0.7.1).
41
41
 
42
42
  ## Matchmaking (competitive presets — server-owned)
43
43
 
@@ -51,11 +51,13 @@ HUD from `mm.matchmaking` (its `status`, `queue.position`, `players`/`opponents`
51
51
  // Pass auth as a FUNCTION — one matchmake() handle re-joins the queue/match many times (re-search,
52
52
  // requeue) over a session, and embed tokens rotate (~10 min). A function is read fresh each (re)join;
53
53
  // a static object goes stale and gets rejected mid-session.
54
+ await waitForPlayer(); // identity gate first (guest OR signed-in) — same rule as connect()
54
55
  const mm = await matchmake<MyState>({ url, room: slug, auth: () => getColyseusAuth() });
55
56
  mm.on('matched', () => {/* session is live — start the game */});
56
57
  // each frame: if (mm.session) renderGame(mm.session); else renderSearchingHud(mm.matchmaking);
57
58
  mm.on('matchEnded', ({ winnerId, scores, draw }) => {/* result screen */});
58
- mm.on('error', (e) => {/* a (re)join failed, e.g. auth usually transient; the handle keeps searching */});
59
+ mm.on('error', (e) => {/* queue join failed even after the SDK's automatic retries (backoff, ~10s)
60
+ — persistent, e.g. broken auth. Fix the cause, then call mm.retry(). */});
59
61
 
60
62
  // Report ONLY your own outcome — the server adjudicates. Which call fits depends on the win condition:
61
63
  mm.eliminated(); // I'm out (lastStanding)
@@ -64,8 +66,9 @@ mm.finish(); // I finished the race (firstToFinish)
64
66
  ```
65
67
 
66
68
  Everything is **server-owned** — set once in `package.json` under `genex.matchmaking`, reported at
67
- publish; the client declares nothing. You never run matchmaking logic: the server owns the queue,
68
- roles, winner-stays, forfeit, timeout, and the win condition.
69
+ `genex preview` AND `genex publish` (removing it from package.json clears the stored config on the
70
+ next preview/publish); the client declares nothing. You never run matchmaking logic: the server
71
+ owns the queue, roles, winner-stays, forfeit, timeout, and the win condition.
69
72
 
70
73
  ```jsonc
71
74
  "genex": {
@@ -149,7 +152,9 @@ fights (many writers). A ball on `objects` glides and has one owner. That's the
149
152
  - `set(id, state)` — publish it (only lands while you own it; full flat object each call).
150
153
  - `get(id)` → `{ id, owner, isMine, state, stateRaw }` or `undefined`. `state` is auto-smoothed
151
154
  (or live if `isMine`); `stateRaw` is the raw latest.
152
- - `release(id)` — give up ownership. `remove(id)` — destroy it (for transient bullets/pickups).
155
+ - `release(id)` — give up ownership. `remove(id)` — destroy it (for transient bullets/pickups);
156
+ **owner-only** — claim it first, or let the current owner remove it (the relay rejects a
157
+ non-owner's destroy).
153
158
  - `ids()` — all object ids seen.
154
159
  - `room.isHost` / `room.host` — you are (or who is) the elected authority. Use to pick the single
155
160
  writer of `shared` scores/rounds and the single simulator of host-owned objects. Settles within
@@ -301,7 +306,7 @@ host-driven saving works as long as ANY account is in the room.
301
306
 
302
307
  ## Checklist
303
308
 
304
- - [ ] `npm i @genex-ai/multiplayer` (≥ 0.4.0 for objects/host); config wired into the build.
309
+ - [ ] `npm i @genex-ai/multiplayer` (≥ 0.7.1); config wired into the build.
305
310
  - [ ] `connect()` runs AFTER `await waitForPlayer()` (never `waitForAuth()` — guests would
306
311
  hang) and passes `auth: getColyseusAuth()!` (the relay rejects tokenless joins —
307
312
  see `genex-threejs-embed-auth`).
@@ -18,13 +18,20 @@ already did, and stacking two smoothers adds visible lag.
18
18
  ```ts
19
19
  import * as THREE from "three";
20
20
  import { connect } from "@genex-ai/multiplayer";
21
+ import { waitForPlayer, getColyseusAuth } from "@genex-ai/embed-sdk";
21
22
  import { GENEX } from "./genex.config";
22
23
 
23
24
  // Your published state — EVERY synced field, including discrete ones like hp. `me.set`
24
25
  // replaces your state wholesale, so the tick must send all of these every time (below).
25
26
  type S = { x: number; z: number; q: number[]; hp: number };
26
27
 
27
- const room = await connect<S>({ url: GENEX.colyseusUrl, room: GENEX.slug });
28
+ const { user } = await waitForPlayer(); // player gate (guest OR signed-in) — never waitForAuth()
29
+ const room = await connect<S>({
30
+ url: GENEX.colyseusUrl,
31
+ room: GENEX.slug,
32
+ name: user.name,
33
+ auth: getColyseusAuth()!, // REQUIRED — tokenless joins are rejected. Read fresh each connect; NEVER log it.
34
+ });
28
35
 
29
36
  // --- local player: input mutates this; we render yourself from it (zero latency) ---
30
37
  // This ONE object holds everything you sync. Keep hp/ammo/etc. here too — see the warning below.
@@ -132,8 +139,11 @@ Rules that keep it correct:
132
139
 
133
140
  ## Host authority (scores, rounds, world)
134
141
 
135
- One client is `room.host` (the first joiner, re-elected on leave). Let only the host write agreed
136
- state, so there's a single source of truth no "who increments the score" races:
142
+ One client is `room.host` the earliest **signed-in** player (guests host only while no account
143
+ is present), and the host can change on **join** as well as leave: the first account entering a
144
+ guest-hosted room takes over. Always react to `on('host')` / read `isHost` in your loop, never
145
+ once at startup. Let only the host write agreed state, so there's a single source of truth — no
146
+ "who increments the score" races:
137
147
 
138
148
  ```ts
139
149
  function updateHud() {
@@ -147,8 +157,9 @@ function goal(team: "a" | "b") {
147
157
  room.on("host", () => {/* host migrated — the new host takes over writing */});
148
158
  ```
149
159
 
150
- `room.on('shared', …)` fires when a key is **first set**, not on every change for a live value,
151
- read `room.shared.get(...)` in your loop (as `updateHud` does) rather than relying on the event.
160
+ `room.on('shared', …)` fires on every **distinct** change of a key (a set to the same value is
161
+ de-duped) — but for a value you render every frame, read `room.shared.get(...)` in your loop (as
162
+ `updateHud` does) rather than wiring render state through the event.
152
163
 
153
164
  ## Custom events (shots, emotes, chat)
154
165
 
@@ -62,7 +62,8 @@ physics.onBeforeStep(() => {
62
62
  const clock = new THREE.Clock();
63
63
  renderer.setAnimationLoop(() => {
64
64
  physics.step(clock.getDelta()); // fixed substeps + mesh sync + events
65
- // camera follow, animation mixers render-delta work stays OUT here
65
+ // camera follow, mixers, HUD go HERE (render-delta work, after the step) —
66
+ // never inside onBeforeStep above
66
67
  renderer.render(scene, camera);
67
68
  });
68
69
  ```
@@ -179,7 +179,7 @@ reference).
179
179
  ## End-to-end: a genex model prop
180
180
 
181
181
  ```ts
182
- import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
182
+ import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
183
183
  import { collidersFromObject } from "./controllers/shared/colliders.ts";
184
184
 
185
185
  const loader = new GLTFLoader();