@genex-ai/cli-demo 0.17.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 (38) 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 +13 -72
  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 +31 -18
  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
  37. package/templates/controllers/shared/NETWORKING.md +0 -11
  38. package/templates/skills/genex-threejs-multiplayer/references/host-physics.md +0 -111
@@ -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();
@@ -188,8 +188,8 @@ shot. That is a presentation-aware choice, not a physical rule.
188
188
  Docking phases:
189
189
 
190
190
  ```text
191
- Endurance spin = 3.15 rad/s
192
- Ranger spin-up = 6.5 s
191
+ station spin = 3.15 rad/s
192
+ shuttle spin-up = 6.5 s
193
193
  approach starts = 4.0 s
194
194
  approach duration = 14.5 s
195
195
  dock settle = 3.0 s
@@ -201,7 +201,7 @@ dock radial offset = 0.35
201
201
  Every phase uses a named `smoothstepRange(start, end, time)`. The sequence does
202
202
  not hide all timing in one normalized zero-to-one value.
203
203
 
204
- Endurance:
204
+ The rotating station:
205
205
 
206
206
  ```text
207
207
  currentSpinRate = lerp(3.15, 0, spinDownT)
@@ -209,14 +209,14 @@ spinAngle += currentSpinRate * dt
209
209
  orientation = baseOrientation * rotation(localForward, spinAngle)
210
210
  ```
211
211
 
212
- The docking frame is recomputed from the newly rotated Endurance every frame.
212
+ The docking frame is recomputed from the newly rotated station every frame.
213
213
 
214
214
  ## Docking-frame decomposition
215
215
 
216
216
  At approach start:
217
217
 
218
218
  ```text
219
- offset = rangerPosition - dockPort
219
+ offset = shuttlePosition - dockPort
220
220
  parallel = dot(offset, dockAxis)
221
221
  radialVector = offset - dockAxis * parallel
222
222
  radialDistance = length(radialVector)
@@ -250,7 +250,7 @@ lateral error.
250
250
 
251
251
  ## Spring convergence and terminal lock
252
252
 
253
- Ranger position follows target through a vector spring:
253
+ The shuttle position follows target through a vector spring:
254
254
 
255
255
  ```text
256
256
  acceleration =
@@ -269,7 +269,7 @@ the docking axis:
269
269
 
270
270
  ```text
271
271
  alignment = quaternionFromUnitVectors(localUp, -dockAxis)
272
- spin = quaternionAround(dockAxis, rangerSpinAngle)
272
+ spin = quaternionAround(dockAxis, shuttleSpinAngle)
273
273
  orientation = spin * alignment
274
274
  ```
275
275
 
@@ -279,7 +279,7 @@ can retain imperceptible but destabilizing residual motion.
279
279
 
280
280
  ## Peeling and released debris
281
281
 
282
- Endurance debris has two states.
282
+ Station debris has two states.
283
283
 
284
284
  Attached peel:
285
285
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: genex-threejs-screen-space-ambient-occlusion
3
- description: Implement screen-space ambient occlusion for Genex Three.js games. Use for GTAO-style horizon sampling, bent normals, contact grounding, depth reconstruction, bilateral upsampling, temporal smoothing, halo diagnosis, and AO that supports readable form.
3
+ description: Implement screen-space ambient occlusion for Genex Three.js games. Use for GTAO-style horizon sampling, bent normals, contact grounding, depth reconstruction, bilateral upsampling, halo diagnosis, and AO that supports readable form.
4
4
  ---
5
5
 
6
6
  # Genex Three.js Screen-Space Ambient Occlusion
@@ -2,6 +2,8 @@
2
2
 
3
3
  Use this reference for a bounded-cost WebGPU/TSL ambient-visibility pass with half-resolution horizon integration, bent normals, bilateral reconstruction, and directional ambient tint.
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
  1. Gather budget and depth convention
@@ -1,10 +1,15 @@
1
1
  ---
2
2
  name: genex-threejs-shadow-systems
3
- description: Implement stable shadow systems for Genex Three.js games. Use for large worlds, directional cascades, cached clipmaps, terrain shadows, city scenes, moving cameras, targeted invalidation, quality tiers, texel stabilization, and readable contact shadows.
3
+ description: Implement stable shadow systems for Genex Three.js games. Use for large worlds, directional cascades, cached clipmaps, terrain shadows, city scenes, moving cameras, targeted invalidation, texel stabilization, and readable contact shadows.
4
4
  ---
5
5
 
6
6
  # Genex Three.js Shadow Systems
7
7
 
8
+ **When NOT to load this:** for a bounded scene (an arena, a room, a small level),
9
+ three.js's default `shadowMap` on one directional light with a tight shadow
10
+ frustum is enough — skip this skill. Load it when the camera roams a large world
11
+ and shadows shimmer, swim, or run out of coverage.
12
+
8
13
  Use a single shadow map only when its receiver region is genuinely bounded. For large moving views, make shadow coverage an explicit spatial hierarchy.
9
14
 
10
15
  ## Cached clipmap workflow
@@ -2,6 +2,8 @@
2
2
 
3
3
  Use this reference for stable directional shadows across a large procedural scene using committed light-space centers, texel snapping, bounded refresh budgets, cross-level blending, and targeted invalidation.
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
  1. Representation and defaults
@@ -28,8 +28,8 @@ map, execution order, and acceptance gate.
28
28
  | planets, terrain, craters, biome fields, coastlines, spherical detail | `$genex-threejs-procedural-planets` |
29
29
  | sky scattering, planetary shells, depth-based aerial perspective | `$genex-threejs-atmosphere-aerial-perspective` |
30
30
  | weather-driven raymarched clouds and cloud shadows | `$genex-threejs-volumetric-clouds` |
31
- | FFT oceans, hybrid FFT/Gerstner clear water, stylized above/below ocean optics, spectral cascades, choppy derivatives, Jacobian whitecaps | `$genex-threejs-spectral-ocean` |
32
- | authored analytic waves, bounded heightfield pools, object ripples, differential-area caustics, ray-traced pool volume optics, shared normals, heuristic refraction, fallback absorption, crest foam | `$genex-threejs-water-optics` |
31
+ | hero open-water FFT oceans (expensive only when open water IS the game): spectral cascades, hybrid FFT/Gerstner clear water, choppy derivatives, Jacobian whitecaps | `$genex-threejs-spectral-ocean` |
32
+ | **default water**: an ocean, sea, lake, river, or pool the game plays on or around — authored analytic waves, bounded heightfield pools, object ripples, differential-area caustics, shared normals, heuristic refraction, fallback absorption, crest foam | `$genex-threejs-water-optics` |
33
33
  | falling snow, snow accumulation, model snow caps, wet asphalt puddles, procedural ripple normals, splash flipbooks, rain streaks, shared weather envelopes, surface wetness | `$genex-threejs-precipitation-surfaces` |
34
34
  | curved-ray black holes, accretion disks, wormholes | `$genex-threejs-raymarched-space-effects` |
35
35
  | particles, trails, plasma, shockwaves, layered event effects | `$genex-threejs-procedural-vfx` |
@@ -40,7 +40,16 @@ map, execution order, and acceptance gate.
40
40
  | eye adaptation, tone mapping, LUT grading, output color | `$genex-threejs-exposure-color-grading` |
41
41
  | shared depth/normal/velocity ownership and multi-pass ordering | `$genex-threejs-image-pipeline` |
42
42
  | fixed-view diagnostics, seed sweeps, temporal and budget evidence | `$genex-threejs-visual-validation` |
43
+ | HUD, menus, pause/win/lose screens, score and health displays, overlays, loading screens, on-screen text and buttons, UI state flow | `$genex-threejs-game-ui` |
44
+ | the game works but feels flat, floaty, or unresponsive: input response, acceleration curves, camera shake, hit feedback, hitstop, cooldowns, difficulty ramp, fail/retry loop | `$genex-threejs-game-feel` |
43
45
  | realtime multiplayer: movement sync, a shared ball/NPC, host-run scores/enemies, shots/emotes, persistence | `$genex-threejs-multiplayer` |
46
+ | player identity, sign-in, guests, saves/progress, per-player state, a shared persistent world, leaderboards — **mandatory for every game** | `$genex-threejs-embed-auth` |
47
+
48
+ **Identity is mandatory routing:** every Genex game ships with the embed SDK — load
49
+ `$genex-threejs-embed-auth` unconditionally before writing boot code (it wires
50
+ `initEmbed(...)` and the `waitForPlayer()` gate), and route to it whenever the task
51
+ mentions sign-in, saves, progress, per-player state, a persistent world, or
52
+ leaderboards. Multiplayer auth (`getColyseusAuth`) comes from it too.
44
53
 
45
54
  **Multiplayer is mandatory routing:** if the game has 2+ players sharing a world, loading
46
55
  `$genex-threejs-multiplayer` is **required** before any networking code — the SDK auto-smooths
@@ -90,7 +99,18 @@ concept-driven — a richer first build beats a grey-box one.
90
99
  - Start from the playable game target: player verb, scene scale, camera distance,
91
100
  input mode, and frame budget.
92
101
  - Build silhouette, motion, and material readability before adding image effects.
102
+ Never dress a primitive shape in glow or bloom to fake quality — authored
103
+ forms first, then materials, then lighting, then effects last.
93
104
  - Prefer deterministic seeds and named controls for every procedural system.
94
105
  - Keep game logic, simulation state, visual fields, and screen-space passes
95
106
  separated unless coupling is intentional.
96
- - Use `$genex-threejs-visual-validation` before declaring a visual task done.
107
+ - Use `$genex-threejs-visual-validation` before declaring graphics/procedural-system
108
+ work done. **Game fast path:** for game tasks that loaded no procedural/visual-system
109
+ skill, done = a screenshot plus an interaction smoke check (load the page, press each
110
+ control, see the visible response) — don't run the full diagnostic gate.
111
+ - Entering an **existing or remixed project**: read before writing — learn the
112
+ current renderer choice, physics setup, and file conventions first, then
113
+ extend them. Don't rebuild working systems or switch renderers mid-project.
114
+ - For three.js APIs **not covered by any skill here**, consult the official
115
+ three.js documentation (https://threejs.org/docs/) rather than guessing —
116
+ the skills cover Genex-specific and hard-won patterns, not the whole engine.
@@ -4,9 +4,9 @@ Use this reference when a Genex game request touches multiple visual systems.
4
4
 
5
5
  ## Three.js version and references
6
6
 
7
- For new Genex browser-game projects, use the current stable Three.js release and
8
- pin it exactly in `package.json`. As of June 26, 2026, that is
9
- `"three": "0.185.0"`.
7
+ For new Genex browser-game projects, use the current stable Three.js release
8
+ (what `npm i three` installs) and keep the project on the version that is
9
+ actually installed — match all docs and examples to it.
10
10
 
11
11
  For existing projects, inspect and respect the installed Three.js version unless
12
12
  the user asks to upgrade. Use official Three.js docs first, then official
@@ -17,25 +17,42 @@ Three.js release or branch, and do not blindly copy demo architecture.
17
17
 
18
18
  1. Define the game contract: player verb, win/interaction loop, target device,
19
19
  camera distance, scene scale, motion, and frame budget.
20
- 2. Wire the gameplay layer for the player verb: the physics world via
20
+ 2. Wire player identity before any boot code: `$genex-threejs-embed-auth` is
21
+ mandatory for every game (`initEmbed(...)` + the `waitForPlayer()` gate) —
22
+ saves, leaderboards, and multiplayer auth all come from it.
23
+ 3. Wire the gameplay layer for the player verb: the physics world via
21
24
  `$genex-threejs-physics-rapier` when anything falls, collides, or gets
22
25
  pushed; on-foot movement via `$genex-threejs-character-controller`;
23
26
  driving/flying and character↔vehicle enter/exit via
24
27
  `$genex-threejs-vehicle-controllers`.
25
- 3. Select the minimum scene-generation skills: geometry, materials, vegetation,
26
- architecture, planets, water, precipitation, clouds, or VFX.
27
- 4. Add camera direction when framing, controls, transitions, or scale perception
28
+ 4. Select the minimum scene-generation skills: geometry, materials, vegetation,
29
+ architecture, planets, water, precipitation, clouds, or VFX. Show a loading
30
+ overlay from the very first asset load a player must never stare at a black
31
+ screen; the rest of the UI states come at step 10.
32
+ 5. Add camera direction when framing, controls, transitions, or scale perception
28
33
  affect play.
29
- 5. Add procedural animation when object motion needs authored phases,
34
+ 6. Add procedural animation when object motion needs authored phases,
30
35
  convergence, looping, or deterministic timelines.
31
- 6. Add shared fields before writing multiple independent noise layers.
32
- 7. Add lighting, atmosphere, and shadows only after the no-post baseline reads.
33
- 8. Add image-pipeline, bloom, exposure, grading, or AO last.
34
- 9. Validate in a real browser with fixed seeds, captures, interaction checks,
35
- and performance evidence.
36
+ 7. Add shared fields before writing multiple independent noise layers.
37
+ 8. Add lighting, atmosphere, and shadows only after the no-post baseline reads.
38
+ 9. Add image-pipeline, bloom, exposure, grading, or AO last.
39
+ 10. Once the loop is playable, wire the interface states via
40
+ `$genex-threejs-game-ui` (HUD, pause, fail/retry, win, and the full loading
41
+ state grown from the step-4 overlay) and run a feel pass via
42
+ `$genex-threejs-game-feel` (input response, camera, impact feedback, retry
43
+ speed).
44
+ 11. Validate in a real browser with fixed seeds, captures, interaction checks,
45
+ and performance evidence.
36
46
 
37
47
  ## Acceptance gate
38
48
 
49
+ **Game fast path:** for a game task that loaded no procedural/visual-system skill,
50
+ done = a screenshot plus an interaction smoke check (load the page, press each
51
+ control, assert a visible response — `$genex-threejs-visual-validation` has the
52
+ procedure). The list below applies to routed *visual-system* scenes, and each
53
+ system-specific item (debug views, seed manifests, tier knobs) applies only when
54
+ the corresponding skill was loaded.
55
+
39
56
  A routed Genex scene is incomplete until it exposes:
40
57
 
41
58
  - deterministic or reproducible inputs;
@@ -69,8 +86,4 @@ NPC, claimed on contact) and a room `host` (single writer of scores, single simu
69
86
  enemies). That skill covers the rules that keep it smooth (draw `state` directly, render
70
87
  yourself and objects you own from a local object, quaternion rotation, `stateRaw` for
71
88
  hit-tests), the per-genre recipes (sports/ball, shooter, co-op), config wiring, and the
72
- persistent-world API. Reconnection is built into the SDK (render `reconnecting`/
73
- `reconnected`, never rebuild it), and **contested physics** (two players pushing one
74
- object — sumo, tug-of-war, shared crates) routes to its host-authoritative pattern
75
- (`inputs` + `onHostTick`, see that skill's host-physics reference) — never claim-on-touch.
76
- Use only the APIs that skill documents — do not invent transport methods.
89
+ persistent-world API. Use only the APIs that skill documents — do not invent transport methods.
@@ -5,6 +5,8 @@ description: Build large procedural oceans for Genex Three.js games. Use for FFT
5
5
 
6
6
  # Genex Three.js Spectral Ocean
7
7
 
8
+ **If the game just needs a sea/lake to float on or fly over, use `$genex-threejs-water-optics` instead** — this skill is the expensive hero-water path, for scenes where open water IS the subject.
9
+
8
10
  Treat an ocean as a sampled stochastic wave field with explicit frequency-space ownership. Do not approximate this target with a pile of Gerstner waves, scrolling normal maps, or unrelated foam noise.
9
11
 
10
12
  ## Build order
@@ -2,6 +2,8 @@
2
2
 
3
3
  Use this reference for a large, unbounded-looking ocean whose identity comes from directional spectral synthesis, staged inverse FFTs, derivative maps, Jacobian whitecaps, and coherent optical shading.
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
  1. Architecture and cascade partition
@@ -57,6 +57,13 @@ renderer.setAnimationLoop(() => {
57
57
  Never call a controller's `update()` from the render loop, never step the
58
58
  world yourself, and never feed the render delta into physics code.
59
59
 
60
+ **One input path.** With an `EnterExitManager`, vehicle input flows ONLY through
61
+ each `registerVehicle`'s `applyInput` (routed while driving). Delete any direct
62
+ `car.setMovement(...)`/`drone.setMovement(...)` calls from your before-step —
63
+ leaving one in makes WASD drive the empty car while the player is on foot. The
64
+ standalone `setMovement` wiring in the car/drone references applies only to
65
+ games with no on-foot character.
66
+
60
67
  ## Wiring references
61
68
 
62
69
  - [references/car.md](references/car.md) — chassis body + colliders, wheels,
@@ -82,6 +89,23 @@ from its recipe (exact code in the references).
82
89
  | `racing-drone` | drone | agile FPV-style racer (~5 kg) | Genex-authored |
83
90
  | `heavy-lifter` | drone | ~298 kg cargo platform, POSITION gains pre-scaled | upstream demo, verbatim |
84
91
 
92
+ ## Custom generated bodies
93
+
94
+ `npx genex model` GLBs drop straight in as vehicle visuals — but generate them
95
+ **without the moving parts**: a car body with no wheels ("red sports car body,
96
+ no wheels"), a drone frame with no propellers. Wheels and blades are
97
+ code-driven (`wheel.modelObject` steers/bounces/spins; a propeller spins its
98
+ `spinModel`), so moving parts baked into the body mesh stand frozen while the
99
+ physics moves the vehicle — it reads as broken. A wheel-less or bladeless body
100
+ is valid and drives/flies correctly; add per-corner wheel meshes or per-mount
101
+ blades only when you want them visible. Exact wiring in the car/drone
102
+ references.
103
+
104
+ On enter/exit: the on-foot character **disappears into the vehicle on enter
105
+ (`park()` hides it) and reappears at the exit point on dismount** — designed
106
+ behavior, not a bug. Don't leave the character mesh standing next to a car it
107
+ is supposedly driving; see [references/enter-exit.md](references/enter-exit.md).
108
+
85
109
  ## Multiplayer: vehicle occupancy is shared state
86
110
 
87
111
  If the game is multiplayer, **who is in what vehicle must be synced** — a
@@ -68,6 +68,10 @@ scene.add(car.chassisObject);
68
68
  physics.registerBody(car.body, car.chassisObject); // interpolated render sync
69
69
 
70
70
  // 5. Input + fixed-step update (BEFORE world.step(), via onBeforeStep).
71
+ // NOTE: this direct setMovement wiring is for a car-only game. When an
72
+ // EnterExitManager is in play, DELETE it — the manager routes input via its
73
+ // applyInput only while driving (see enter-exit.md); keeping this line too
74
+ // makes WASD drive the empty car while the player is on foot.
71
75
  const kb = new KeyboardInput(); // WASD/arrows drive+steer, Space = brake
72
76
  physics.onBeforeStep(() => {
73
77
  car.setMovement(kb.getCarMovement());
@@ -77,13 +81,37 @@ physics.onBeforeStep(() => {
77
81
 
78
82
  `setMovement` merges field-wise — send the complete `CarMovementIntent`
79
83
  every frame (a stale partial leaves old `true`s behind), which
80
- `kb.getCarMovement()` already does. Touch input: pass
81
- `{ joystickL: { x, y } }` from `TouchJoystick` (pushing right steers right).
84
+ `kb.getCarMovement()` already does.
85
+
86
+ **Touch input — wire it by default, not on request:** published games get opened
87
+ on phones from shared links. Pass `{ joystickL: { x, y } }` from `TouchJoystick`
88
+ (pushing right steers right) and show it only on touch devices
89
+ (`joy.setVisible(navigator.maxTouchPoints > 0)`) — invisible on desktop.
90
+ Phone-specific layouts stay ask-only.
82
91
 
83
92
  If an on-foot character shares the world, create its body with
84
93
  `userData: { controller: { excludeVehicleRay: true } }` so the wheels never
85
94
  treat the player as drivable ground.
86
95
 
96
+ ## Custom generated bodies (`npx genex model`)
97
+
98
+ A generated GLB works as the visual chassis — add it under `car.chassisObject`
99
+ exactly like `carBodyMesh` above. Two rules keep it looking right:
100
+
101
+ - **Generate the body WITHOUT wheels** — prompt it explicitly, e.g.
102
+ `npx genex model "red sports car body, no wheels"`. Wheels are code-driven:
103
+ each `ShapeCastWheel` steers, bounces, and spins whatever sits in
104
+ `wheel.modelObject`, so wheels baked into the body mesh stand frozen while
105
+ the invisible real wheels do the work — it reads as a broken car.
106
+ - **Wheel-less is a valid look.** The suspension shapecasts need no wheel
107
+ visuals at all: a body with empty `modelObject`s sits and drives correctly.
108
+ Add per-corner wheel meshes (the cylinder in the wiring above, or a small
109
+ generated wheel model) only when you want wheels visible.
110
+
111
+ Scale and center the GLB to hug the chassis colliders, and mind the
112
+ +Z-forward convention when orienting it. Collider strategy for GLBs lives in
113
+ `$genex-threejs-physics-rapier`.
114
+
87
115
  ## Presets and provenance
88
116
 
89
117
  | Preset | Drivetrain | Suspension | Notes |
@@ -65,6 +65,9 @@ const drone = new DroneController({
65
65
  propellers,
66
66
  config: preset.config,
67
67
  });
68
+ // NOTE: direct setMovement wiring is for a drone-only game. With an
69
+ // EnterExitManager, DELETE it — the manager's applyInput routes input only
70
+ // while piloting (see enter-exit.md); keeping it flies the empty drone.
68
71
  const kb = new KeyboardInput(); // W/S throttle, A/D yaw, arrows pitch/roll
69
72
  physics.onBeforeStep(() => {
70
73
  drone.setMovement(kb.getDroneMovement());
@@ -73,8 +76,24 @@ physics.onBeforeStep(() => {
73
76
  ```
74
77
 
75
78
  The keyboard scheme is deliberately asymmetric (WASD = throttle/yaw, arrows
76
- = pitch/roll — they are NOT aliases; do not "unify" them). Touch sticks map
77
- as `joystickL` = climb/yaw, `joystickR` = pitch/roll.
79
+ = pitch/roll — they are NOT aliases; do not "unify" them).
80
+
81
+ **Touch input — wire it by default, not on request:** published games get opened
82
+ on phones from shared links. Two `TouchJoystick`s map as `joystickL` =
83
+ climb/yaw, `joystickR` = pitch/roll; show them only on touch devices
84
+ (`setVisible(navigator.maxTouchPoints > 0)`) — invisible on desktop.
85
+ Phone-specific layouts stay ask-only.
86
+
87
+ ## Custom generated frames (`npx genex model`)
88
+
89
+ A generated GLB works as the body visual (`chassis.add(glb)`), but **generate
90
+ the frame only, without propellers** — prompt it explicitly, e.g.
91
+ `npx genex model "carbon quadcopter drone frame, no propellers"`. Blades are
92
+ code-driven: each propeller spins its `spinModel` object, so blades baked into
93
+ the body mesh stand still while the drone flies — it reads as broken. Keep the
94
+ one-mount-`Object3D`-per-propeller wiring above and put a blade mesh (simple
95
+ geometry or a small generated model) in each mount as `spinModel`; a bladeless
96
+ frame also flies correctly if that's the look.
78
97
 
79
98
  ## Control modes: VELOCITY vs POSITION
80
99
 
@@ -22,6 +22,21 @@ Read [references/visual-validation.md](references/visual-validation.md)
22
22
  for visual contracts, required inspection controls, mechanism-specific
23
23
  evidence, temporal checks, budgets, and explicit rejection criteria.
24
24
 
25
+ ## Interaction smoke check (the game fast path)
26
+
27
+ For plain game tasks — nothing from the procedural/visual-system pack loaded —
28
+ this is the whole acceptance gate, and it is also the minimum for every game:
29
+
30
+ 1. Load the page in a real browser; the canvas renders (no black screen, no
31
+ console errors).
32
+ 2. Press each documented control once (keys, pointer); assert a **visible
33
+ response** to every one — the player moves, the camera turns, the button
34
+ fires.
35
+ 3. Capture one screenshot of live gameplay.
36
+
37
+ Everything deeper (baselines, seed sweeps, mosaics, budgets) belongs to
38
+ visual-system work — the sequence above.
39
+
25
40
  ## Required evidence
26
41
 
27
42
  - fixed camera and seed manifest;
@@ -5,6 +5,10 @@ description: Build volumetric cloud systems for Genex Three.js games. Use for we
5
5
 
6
6
  # Genex Three.js Volumetric Clouds
7
7
 
8
+ **Static clouds in a fixed sky → `$genex-ai-skybox` instead** (one command, a
9
+ real 360° image). This skill is for moving, weather-driven, or fly-through
10
+ clouds.
11
+
8
12
  Cloud quality comes from density organization, lighting, and temporal stability—not from increasing march steps over unstructured noise.
9
13
 
10
14
  ## System order
@@ -2,6 +2,8 @@
2
2
 
3
3
  Use this reference for bounded or analytic water with shared displacement and normals, derivative-filtered detail, analytic reflection, heuristic refraction, absorption, and crest foam. Use `$genex-threejs-spectral-ocean` for stochastic FFT seas.
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
  - cinematic implementation displaced ocean
@@ -16,7 +18,7 @@ Use this reference for bounded or analytic water with shared displacement and no
16
18
 
17
19
  ## cinematic implementation displaced ocean
18
20
 
19
- The Miller’s Planet scene uses five authored Gerstner-style components:
21
+ The shallow-sea demo scene uses five authored Gerstner-style components:
20
22
 
21
23
  | Direction X/Z | Amplitude | Wavelength | Steepness |
22
24
  | --- | ---: | ---: | ---: |
@@ -1,11 +0,0 @@
1
- # Networking these controllers
2
-
3
- These controllers simulate **your own player only** (self-authoritative, zero input
4
- latency). To show OTHER players' rigs in a multiplayer game, publish a small flat state on
5
- a fixed tick and play it back on a visual-only remote rig — never instantiate a controller
6
- or a Rapier body for a remote player.
7
-
8
- The complete recipe (what to publish per controller, remote playback, contested-contact
9
- physics via the host) lives in the multiplayer skill:
10
- `genex-threejs-multiplayer` → `references/host-physics.md`. Load that skill before writing
11
- any networking code.
@@ -1,111 +0,0 @@
1
- # Host-authoritative physics — contested objects + networked controllers
2
-
3
- Two networking tiers exist for moving things, and picking the right one per object is the
4
- whole trick:
5
-
6
- | Object kind | Tier | Why |
7
- | --- | --- | --- |
8
- | One-touch (kick a ball, throw a crate once) | **claim-on-touch** (`objects.claim` on contact, owner simulates) | lowest latency — your kick lands instantly |
9
- | Sustained contact / contested (two players pushing one crate, tug-of-war, sumo, shared vehicles) | **host-authoritative** (this doc) | one simulation owns the contest — no ownership ping-pong, physics stays consistent |
10
-
11
- Claim-on-touch under sustained contact means every contact steals ownership, resets the
12
- simulation, and the object judders between two owners' views. Host-authoritative kills that
13
- by construction: **the host runs the ONE Rapier world for contested objects; everyone else
14
- sends inputs.**
15
-
16
- ## The pattern (complete)
17
-
18
- Every client runs this same code — `onHostTick` only fires on the current host, so there is
19
- no "am I host?" bookkeeping and host migration is automatic:
20
-
21
- ```ts
22
- // ---- inputs: NON-hosts (and the host itself) report intent, ~10-15Hz or on action ----
23
- // Routed by the relay to the CURRENT HOST ONLY — never broadcast, cheap.
24
- if (pushingCrate) room.inputs.send({ obj: "crate", push: dir.toArray() });
25
-
26
- // ---- simulation: runs ONLY on the host, survives migration ----
27
- const pending: { obj: string; push: number[] }[] = [];
28
- room.inputs.on((fromId, payload) => {
29
- const p = payload as { obj?: string; push?: number[] };
30
- if (p?.obj && Array.isArray(p.push)) pending.push(p as { obj: string; push: number[] });
31
- });
32
-
33
- room.onHostTick(30, (dtMs) => {
34
- // 1) First tick after election: adopt the objects + seed the physics world from the
35
- // last published truth (stateRaw) — NEVER from zero, or the world teleports.
36
- for (const id of CONTESTED_IDS) {
37
- const view = room.objects.get(id);
38
- if (!view || !view.isMine) {
39
- room.objects.claim(id);
40
- seedRapierBody(id, view?.stateRaw); // position/rotation/velocity from the wire
41
- }
42
- }
43
- // 2) Apply everyone's inputs to the ONE authoritative Rapier world.
44
- for (const { obj, push } of pending.splice(0)) applyImpulse(obj, push);
45
- // 3) Step and publish (flat state: numbers + one [x,y,z,w] quaternion).
46
- rapierWorld.step();
47
- for (const id of CONTESTED_IDS) {
48
- const b = bodyOf(id);
49
- room.objects.set(id, {
50
- x: r2(b.translation().x), y: r2(b.translation().y), z: r2(b.translation().z),
51
- q: quatArray(b.rotation()),
52
- });
53
- }
54
- });
55
- const r2 = (v: number) => Math.round(v * 100) / 100; // quantize — floats are JSON bloat
56
-
57
- // ---- rendering: identical on every client, host included ----
58
- for (const id of CONTESTED_IDS) {
59
- const view = room.objects.get(id);
60
- if (view) meshOf(id).position.set(view.state.x, view.state.y, view.state.z); // auto-smoothed
61
- }
62
- ```
63
-
64
- Rules that make it correct:
65
-
66
- - **Sim internals that must survive migration** (velocities, cooldowns, aggro) either live in
67
- the published object state or get mirrored at low rate into a dedicated object
68
- (`objects.set("sim", …)`) the next host reads on adoption. Positions/rotations come free
69
- via `stateRaw`.
70
- - **Latency honesty:** a non-host's push lands after ~RTT to the relay. At casual scale that
71
- reads as weight, not lag. The host's own pushes are instant — that asymmetry is the tier's
72
- price; don't fight it with client-side guessing.
73
- - **Rate budget:** the host publishes N contested objects at 20–30 Hz; keep N modest (≤ ~10)
74
- and state flat + quantized. Inputs are single-receiver and cheap.
75
- - **Do not** run a second Rapier body for a contested object on non-hosts "for prediction" —
76
- that's the double-simulation version of double-smoothing. Draw `state`.
77
-
78
- ## Networked controllers (the vendored character / vehicle / drone)
79
-
80
- The `genex controller` controllers are **local-only physics** — each player simulates their
81
- OWN rig (self-authoritative, zero latency). Networking them is publish-and-playback, never
82
- remote simulation:
83
-
84
- ```ts
85
- // You: after your controller's update, on the fixed tick (~15Hz)
86
- room.me.set({
87
- x: r2(rig.position.x), y: r2(rig.position.y), z: r2(rig.position.z),
88
- q: rig.quaternion.toArray().map(r2), // quaternion — never a scalar yaw
89
- anim: rig.animState, // discrete → remotes read via stateRaw
90
- // vehicle extras: steer: r2(steerAngle), wheel: r2(wheelSpinPhase)
91
- });
92
-
93
- // Remote players: drive a VISUAL-ONLY rig from smoothed state — no Rapier body, no
94
- // controller instance for remotes. Wheels/limbs animate from the published params.
95
- const p = room.players.get(id)!;
96
- remoteMesh.position.set(p.state.x, p.state.y, p.state.z);
97
- remoteMesh.quaternion.fromArray(p.state.q);
98
- remoteAnimator.play(p.stateRaw.anim); // discrete values from stateRaw
99
- ```
100
-
101
- What to publish per controller:
102
-
103
- - **character**: `x/y/z`, `q`, `anim` (state-machine id), optionally `speed` for blend trees.
104
- - **vehicle**: body `x/y/z` + `q`, `steer` angle, a `wheel` spin phase (remotes spin wheels
105
- procedurally — never sync per-wheel transforms).
106
- - **drone**: `x/y/z`, `q`, rotor throttle if the visual needs it.
107
-
108
- Player-vs-player physical contact (bumping cars) stays approximate at this tier — each
109
- client is authoritative over itself, so contacts are cosmetic. If a game's core loop IS
110
- contested vehicle contact, that's the host-authoritative tier above, with the vehicles as
111
- host-simulated objects and player inputs over `inputs.send`.