@genex-ai/cli-demo 0.88.0-dev.219 → 0.91.0-dev.228

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.
@@ -0,0 +1,217 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // The player's body, resolved once: the game's OWN generated character when
3
+ // this game has one, the visiting player's profile avatar when it does not.
4
+ //
5
+ // WHY THIS EXISTS: before it, the two lanes were different code — different
6
+ // loader, different CharacterAnimations arguments, and a per-frame call that
7
+ // appeared and disappeared. So the boot path written at hour 0 (the avatar,
8
+ // the only lane that works before a character exists) had to be REWRITTEN at
9
+ // hour 2 when the generated character landed, by an agent low on context with
10
+ // ten things half-done. It wasn't, and games shipped wearing the platform's
11
+ // stock avatar instead of the character they were designed around. ONE shape
12
+ // for both lanes means the character arriving later is a file drop, not an
13
+ // edit — `genex controller character --character <id>` writes the manifest and
14
+ // the next reload picks it up.
15
+ //
16
+ // The generated character is the default for any game where a human body
17
+ // appears on screen (first-person included — remotes, a look-down body, a
18
+ // shadow, a menu portrait). The avatar is the FALLBACK: a declared temporary
19
+ // body while the character renders, or the stand-in when generation genuinely
20
+ // could not happen (out of credits, failed, unverified).
21
+ import * as THREE from "three";
22
+ import * as SkeletonUtils from "three/addons/utils/SkeletonUtils.js";
23
+ import { VRMUtils } from "@pixiv/three-vrm";
24
+ import type { LocomotionProfile } from "./character-animations.ts";
25
+ import { loadCharacterClips } from "./animation-packs.ts";
26
+ import {
27
+ loadMeshyCharacter,
28
+ type LoadMeshyCharacterOptions,
29
+ type MeshyCharacter,
30
+ } from "./meshy/meshy-loader.ts";
31
+ import { loadVrm, loadVrmCloneBase } from "./vrm/vrm-loader.ts";
32
+
33
+ /** "generated" = this game's own themed character; "avatar" = the fallback. */
34
+ export type PlayerCharacterKind = "generated" | "avatar";
35
+
36
+ export interface PlayerCharacter {
37
+ kind: PlayerCharacterKind;
38
+ /** Renderable root — parent it under `character.root`. */
39
+ scene: THREE.Group;
40
+ /** Clips for CharacterAnimations (exact-rig generated clips, or retargeted UAL). */
41
+ clips: THREE.AnimationClip[];
42
+ /** Present only in the generated lane; pass straight through to CharacterAnimations. */
43
+ locomotionProfile?: LocomotionProfile;
44
+ /** Once per RENDER frame, after physics.step(). Ticks the VRM humanoid + spring
45
+ * bones in the fallback lane; a no-op for native rigs. Always call it — that
46
+ * is the point: the boot code never changes when the lane does. */
47
+ update(delta: number): void;
48
+ dispose(): void;
49
+ }
50
+
51
+ /** A REMOTE player's body: visual only, so no per-frame update and no physics. */
52
+ export interface RemotePlayerCharacter {
53
+ kind: PlayerCharacterKind;
54
+ scene: THREE.Group;
55
+ clips: THREE.AnimationClip[];
56
+ locomotionProfile?: LocomotionProfile;
57
+ /** Detach on 'leave'. GPU resources belong to the shared base, never a clone. */
58
+ dispose(): void;
59
+ }
60
+
61
+ export interface LoadPlayerCharacterOptions {
62
+ /** The visiting player's VRM (`user.avatarUrl` from the embed identity). Fallback lane only. */
63
+ avatarUrl?: string | null;
64
+ /** Asset base. Default "./assets/". */
65
+ base?: string;
66
+ /** Manifest override. Default `${base}meshy-character.json`. */
67
+ manifestUrl?: string;
68
+ /** Passed through to the Meshy loader — the quality kit's decoder-wired loader
69
+ * and per-tier model rung ladder (`$genex-threejs-adaptive-quality`). */
70
+ meshy?: LoadMeshyCharacterOptions;
71
+ }
72
+
73
+ /**
74
+ * Does this game have a generated character? A dev server answers HTTP 200 with
75
+ * `index.html` for ANY unknown path, so `res.ok` is not evidence — the JSON
76
+ * parse plus the manifest's own identity fields are. False is the quiet,
77
+ * expected answer for a game that has not generated its character yet.
78
+ *
79
+ * Exported for tests: this probe is the whole routing decision.
80
+ */
81
+ export async function probeGeneratedCharacter(url: string): Promise<boolean> {
82
+ try {
83
+ const response = await fetch(url, { cache: "no-store" });
84
+ if (!response.ok) return false;
85
+ const manifest = (await response.json()) as { schema?: number; rig?: string } | null;
86
+ return manifest?.schema === 1 && manifest.rig === "meshy-biped";
87
+ } catch {
88
+ return false; // absent, or the dev server's index.html — same meaning here
89
+ }
90
+ }
91
+
92
+ // One PARSED base per manifest URL — the clone source for remote players, so N
93
+ // remotes cost ~1 character of GPU memory instead of N (the same trade the VRM
94
+ // lane's loadVrmCloneBase makes). Never animated; clones carry their own mixer.
95
+ const generatedBaseCache = new Map<string, Promise<MeshyCharacter>>();
96
+ // Retargeted fallback clips per (avatar URL + asset base) — retargeting is per
97
+ // humanoid rig, and every clone of one URL shares one base rig.
98
+ const fallbackClipsCache = new Map<string, Promise<THREE.AnimationClip[]>>();
99
+
100
+ function generatedBase(url: string, options?: LoadMeshyCharacterOptions): Promise<MeshyCharacter> {
101
+ let cached = generatedBaseCache.get(url);
102
+ if (!cached) {
103
+ cached = loadMeshyCharacter(url, options);
104
+ cached.catch(() => generatedBaseCache.delete(url)); // failures retry next call
105
+ generatedBaseCache.set(url, cached);
106
+ }
107
+ return cached;
108
+ }
109
+
110
+ /**
111
+ * The LOCAL player's body. Wire it once at boot and never touch it again:
112
+ *
113
+ * ```ts
114
+ * const player = await loadPlayerCharacter({ avatarUrl: user.avatarUrl });
115
+ * const fit = capsuleFromModel(player.scene);
116
+ * character.root.add(player.scene);
117
+ * player.scene.position.y = fit.modelOffsetY;
118
+ * const anims = new CharacterAnimations(player.scene, player.clips, {
119
+ * locomotionProfile: player.locomotionProfile,
120
+ * });
121
+ * // per render frame: anims.update(character, delta); player.update(delta);
122
+ * ```
123
+ */
124
+ export async function loadPlayerCharacter(
125
+ options: LoadPlayerCharacterOptions = {},
126
+ ): Promise<PlayerCharacter> {
127
+ const base = options.base ?? "./assets/";
128
+ const manifestUrl = options.manifestUrl ?? `${base}meshy-character.json`;
129
+ const fallbackUrl = `${base}avatar.vrm`;
130
+
131
+ if (await probeGeneratedCharacter(manifestUrl)) {
132
+ try {
133
+ const native = await loadMeshyCharacter(manifestUrl, options.meshy);
134
+ return {
135
+ kind: "generated",
136
+ scene: native.scene,
137
+ clips: native.clips,
138
+ locomotionProfile: native.locomotionProfile,
139
+ update: (delta) => native.update(delta),
140
+ dispose: () => native.dispose(),
141
+ };
142
+ } catch (error) {
143
+ // LOUD: this game HAS a character and it failed — a dead R2 URL or a rig
144
+ // mismatch, not the ordinary "no character yet" path above.
145
+ console.warn(
146
+ "[player-character] the generated character failed to load — falling back to the avatar",
147
+ error,
148
+ );
149
+ }
150
+ }
151
+
152
+ const { scene, vrm } = await loadVrm(options.avatarUrl || fallbackUrl).catch(() =>
153
+ loadVrm(fallbackUrl),
154
+ );
155
+ const clips = await loadCharacterClips(vrm, { base });
156
+ return {
157
+ kind: "avatar",
158
+ scene,
159
+ clips,
160
+ update: (delta) => vrm.update(delta),
161
+ dispose: () => VRMUtils.deepDispose(scene),
162
+ };
163
+ }
164
+
165
+ /**
166
+ * A REMOTE player's body. In a game with a generated character EVERY player
167
+ * wears it — the game should look like the game that was designed, and a mixed
168
+ * roster (one themed knight plus three stock avatars) is the same incoherence
169
+ * as capsule-and-cone remotes. Without a generated character each remote keeps
170
+ * their own `avatarUrl`, which is the right look for a game with no themed cast.
171
+ *
172
+ * Visual only: no physics body, no controller, no per-frame update. Clones share
173
+ * the base's geometry/materials/textures, so `dispose()` detaches — it never
174
+ * disposes GPU resources the base and every other clone still use.
175
+ */
176
+ export async function loadRemotePlayerCharacter(
177
+ options: LoadPlayerCharacterOptions = {},
178
+ ): Promise<RemotePlayerCharacter> {
179
+ const base = options.base ?? "./assets/";
180
+ const manifestUrl = options.manifestUrl ?? `${base}meshy-character.json`;
181
+ const fallbackUrl = `${base}avatar.vrm`;
182
+
183
+ if (await probeGeneratedCharacter(manifestUrl)) {
184
+ try {
185
+ const shared = await generatedBase(manifestUrl, options.meshy);
186
+ const scene = SkeletonUtils.clone(shared.scene) as THREE.Group;
187
+ return {
188
+ kind: "generated",
189
+ scene,
190
+ clips: shared.clips,
191
+ locomotionProfile: shared.locomotionProfile,
192
+ dispose: () => scene.removeFromParent(),
193
+ };
194
+ } catch (error) {
195
+ console.warn(
196
+ "[player-character] the generated character failed to load for a remote player — falling back to their avatar",
197
+ error,
198
+ );
199
+ }
200
+ }
201
+
202
+ const requested = options.avatarUrl || fallbackUrl;
203
+ const url = await loadVrmCloneBase(requested).then(
204
+ () => requested,
205
+ () => fallbackUrl,
206
+ );
207
+ const shared = await loadVrmCloneBase(url);
208
+ const clipsKey = `${url}${base}`;
209
+ let clips = fallbackClipsCache.get(clipsKey);
210
+ if (!clips) {
211
+ clips = loadCharacterClips(shared.vrm, { base });
212
+ clips.catch(() => fallbackClipsCache.delete(clipsKey));
213
+ fallbackClipsCache.set(clipsKey, clips);
214
+ }
215
+ const scene = SkeletonUtils.clone(shared.scene) as THREE.Group;
216
+ return { kind: "avatar", scene, clips: await clips, dispose: () => scene.removeFromParent() };
217
+ }
@@ -1,16 +1,39 @@
1
1
  ---
2
2
  name: genex-ai-character
3
- description: Generate a controller-ready Meshy humanoid with Genex, search the committed Meshy animation catalog by gameplay intent, add exact same-rig actions, and install the shared physics controller's Meshy-native adapter. Use for the game's themed character — the default whenever the protagonist is visibleor for motion not covered by the VRM + UAL lane.
3
+ description: Generate a controller-ready Meshy humanoid with Genex, search the committed Meshy animation catalog by gameplay intent, add exact same-rig actions, and install the shared physics controller's Meshy-native adapter. Use for the game's own character — the player's body in every game where a human body appears on screen and for motion the bundled UAL library doesn't cover.
4
4
  ---
5
5
 
6
6
  # Genex AI Character
7
7
 
8
- A themed Meshy character is the DEFAULT for any game whose protagonist is
9
- visible (third-person, or first-person with co-op/remote players); the
10
- VRM + UAL controller is the instant placeholder while it builds, and the
11
- final character only for games with no themed protagonist. Both lanes use
12
- the same ECCTRL-derived Rapier controller, camera, inputs, crossfades,
13
- transition rules, and multiplayer authority contract.
8
+ **The game's own generated character IS the player's body.** Not the platform's
9
+ profile avatar that is the fallback. The rule applies wherever a human body
10
+ appears on screen: third-person obviously, and first-person too, the moment
11
+ co-op or versus remotes, a look-down body, a shadow, a death or spectator
12
+ camera, or a menu portrait shows one. "The camera is in the head" is not an
13
+ exemption; **"no human body ever appears in this game" is.** A game whose
14
+ player is genuinely not a person — a car, a ship, an RTS cursor, a puzzle
15
+ board — generates that object with `npx genex model` instead.
16
+
17
+ **Start it EARLY** — enqueue the character right after the design interview,
18
+ alongside the Stage-1 HUD concept, not as an afterthought once the world is
19
+ built. Its concept candidates ride the same review beat as the HUD concept, and
20
+ firing at minute 0 means it lands around the v0 preview instead of long after
21
+ it.
22
+
23
+ Until it lands, the player's body is the profile VRM avatar. That is a fallback
24
+ in two shapes and both are spoken aloud: a **temporary** body while the
25
+ character renders (say so in one plain line — the avatar is fully textured and
26
+ animated, so nothing on screen will look unfinished enough to remind you), or
27
+ the **stand-in** when generation genuinely could not happen (out of credits,
28
+ failed, email unverified) — then record `Player character: VRM — <reason>` in
29
+ DESIGN.md. Never let it become the game's character by default.
30
+
31
+ Both lanes use the same ECCTRL-derived Rapier controller, camera, inputs,
32
+ crossfades, transition rules, and multiplayer authority contract — and the
33
+ same one-call boot path (`loadPlayerCharacter`, in
34
+ `$genex-threejs-character-controller`), so when the character lands mid-build,
35
+ `npx genex controller character --character <id>` is the whole switch. There is
36
+ no code to rewrite, which is exactly why there is no excuse to skip it.
14
37
 
15
38
  **Two lanes, two approval shapes:**
16
39
 
@@ -231,4 +254,27 @@ durations, nominal speeds, trajectories, and gameplay requirements.
231
254
  For multiplayer, only the owning client advances the dynamic body and publishes
232
255
  its pose plus compact animation state. Remote characters are visual-only: they
233
256
  interpolate the owner's transform and replay the matching clip progress. Never
234
- run a motion driver or a second physics controller for a remote player.
257
+ run a motion driver or a second physics controller for a remote player. In a
258
+ game with a generated character, every remote wears it — one
259
+ `loadRemotePlayerCharacter` call, one shared parsed base, N cheap clones.
260
+
261
+ ## Troubleshooting
262
+
263
+ - **"Not authorized"** — run `npx genex init` first (in the project — it resolves this project's own CLI) (it writes your `GENEX_TOKEN`).
264
+ - **"Out of credits" (`insufficient_credits`)** — the account has no credits left for
265
+ this character stage. Tell the user the facts the CLI printed: their balance, this
266
+ stage's cost, and when their credits refill. Then keep the player on the profile
267
+ VRM avatar (the fallback lane — no code change, it is already what the game
268
+ loads), tell them in one plain line that the game is wearing the platform avatar
269
+ because the character couldn't be generated, record
270
+ `Player character: VRM — out of credits` in DESIGN.md, and mark the spot with
271
+ `// TODO(genex): regenerate when credits refill`. Do not stop the session over
272
+ this, and do not hand-build a stand-in humanoid.
273
+ - **"Email not verified" (`email_verification_required`)** — generation credits
274
+ unlock after the account's email is verified. Give the user the verify link the
275
+ CLI printed, wait for them to confirm, then re-run the command.
276
+ - **The character is stuck mid-lane** — each stage is a separate generation. `npx
277
+ genex wait --all` prints one line per job; a failed `preview` or `finalize` is
278
+ re-run from the id of the stage before it, never from the beginning.
279
+ - **The rig or a pose is bad** — reject and regenerate. Never repair limbs at
280
+ runtime; validation never rewrites limb animation.
@@ -213,7 +213,11 @@ See `$genex-threejs-multiplayer` for the `shared` channel rules and the room API
213
213
  still re-renders and alpha is destroyed either way: after any masked edit,
214
214
  re-run `--clean`/re-extraction downstream. A targeting scope, not a pixel
215
215
  freeze.
216
- - `--clean <url>` — ML background removal of THAT image only (prompt recorded, unused).
216
+ - `--clean <url>` — ML background removal of THAT image only (prompt recorded,
217
+ unused). **For an image that HAS a background.** Running it over a
218
+ `--transparent` result re-keys edges that were already correct and can spray
219
+ colour speckle across the rim and into the art — `ui trim`/`extract`/`audit`
220
+ refuse that output, and nothing repairs it but going back to the original.
217
221
  - `--upscale <url>` — 2x utility upscale of THAT image (prompt recorded,
218
222
  unused; ~2-credit class). Use before printing/large billboards, not by
219
223
  default.
@@ -373,12 +373,20 @@ not):
373
373
  - **Logotype (default yes):**
374
374
  `npx genex image "the word 'EMBERFALL' as an ornate engraved game logo, <style brief>" --transparent`
375
375
  — the wordmark alone on a transparent background, in the brief's display
376
- register, no extra text or scenery. Clean with `--bg-mode glyph` if letter
377
- counters get eaten, trim with `npx genex ui trim`. Then VERIFY it is
378
- actually transparent view the trimmed PNG over a bright test background;
379
- background removal sometimes leaves an opaque disc or plate behind the
380
- mark, and that plate ships as a hole punched in your key art. If any
381
- background remains, re-run the clean. Short names (one or two
376
+ register, no extra text or scenery. Then trim with `npx genex ui trim`.
377
+ **`--transparent` already returns a finished cutout a `--clean` pass is a
378
+ REPAIR for a defect you can see, never routine polish.** Re-clean ONLY if
379
+ the letter counters came out filled: `--bg-mode glyph` for hard flat
380
+ shapes, `--bg-mode matte` for soft metallic, chromed, or glowing edges
381
+ (most ornate wordmarks are soft edges `glyph` is the wrong cutter for
382
+ them). Then VERIFY over a bright test background, looking for BOTH
383
+ failures: **(1)** an opaque disc or plate behind the mark — that plate
384
+ ships as a hole punched in your key art; **(2)** coloured speckle or a
385
+ rainbow rim along the letter edges — that means the clean pass DAMAGED the
386
+ art, so go back to the `--transparent` original and ship that. Running a
387
+ background remover over an image that was already transparent re-keys every
388
+ edge it should have left alone; `ui trim`/`extract`/`audit` now refuse that
389
+ output outright, and nothing downstream repairs it. Short names (one or two
382
390
  words) come out best. Wire it as the menu title AND the loader mark; the
383
391
  DOM keeps an accessible text fallback (`aria-label` or visually-hidden
384
392
  text).
@@ -23,12 +23,25 @@ towers, or any object painted into the sky render at infinite distance: they
23
23
  never get closer as the player moves, they sit at the wrong parallax against
24
24
  the real 3D world, and they read as broken the moment the camera strafes.
25
25
  Structures belong in the scene as geometry (`npx genex model`); the sky
26
- carries atmosphere — light, weather, clouds, haze, stars. The CLI enforces
27
- this: every skybox prompt gets an environment-only suffix appended (you'll
28
- see `↳ environment-only guard applied` and the full final prompt is stored
29
- with the generation). If you genuinely need content baked into the sky
30
- e.g. a space station panorama for a scene with no world geometry pass
31
- `--raw` to send your prompt exactly as written.
26
+ carries atmosphere — light, weather, clouds, haze, stars.
27
+
28
+ **Writing "no cathedral" does not rescue a prompt that also says "cathedral".**
29
+ Image models are steered by the nouns they ARE given; a ban loses to any
30
+ positive mention of the same thing in the same prompt. A real pilot asked for
31
+ a *"cinematic dark anime gothic atmosphere, ruined cathedral courtyard mood,
32
+ **no buildings or structures**"* — and got a full gothic cathedral with a
33
+ courtyard balustrade and a baked ground plane. Five negations in that prompt,
34
+ zero effect. So never name a structure in a skybox prompt **even to exclude
35
+ it**: describe only light, colour, cloud, weather and time of day.
36
+
37
+ The CLI enforces this both ways. It appends a closed-positive environment
38
+ clause to every prompt (you'll see `↳ environment-only guard applied`; the
39
+ full final prompt is stored with the generation), and it **refuses outright,
40
+ before spending any credits**, when the prompt itself names a structure or
41
+ object. Landforms are fine — "golden hour over misty mountains", "a distant
42
+ sea horizon", "towering cumulonimbus" all pass. If you genuinely need content
43
+ baked into the sky — e.g. a space station panorama for a scene with no world
44
+ geometry — pass `--raw` to send your prompt exactly as written.
32
45
 
33
46
  ## Run
34
47
 
@@ -318,17 +318,35 @@ way this skill is worn by the main agent: you stay the director.
318
318
  holding the only free slot"). The table is accountability, not ceremony —
319
319
  inline can be the right call.
320
320
 
321
- ## 7. The game's character (Meshy) — themed by default
322
-
323
- **A themed character is the DEFAULT for any game whose protagonist is
324
- VISIBLE** third-person, or first-person with co-op/remote players who see
325
- each other. Put its row in the Assets table up front and install the
326
- VRM + UAL controller (`npx genex controller character`) as the instant
327
- placeholder. The VRM stays the FINAL character only for games with no
328
- themed protagonist to sell (a generic exploration toy, a faceless solo
329
- first-person game say which in one line). A capsule or hand-built
330
- primitive standing in for a person is never a shipped state, for the local
331
- player or a remote one.
321
+ ## 7. The game's character (Meshy) — the player's body
322
+
323
+ **The game's own generated character IS the player's body**, wherever a
324
+ human body appears on screen. Third-person obviously; first-person too, the
325
+ moment remotes, a look-down body, a shadow, a death or spectator camera, or
326
+ a menu portrait shows one. "The camera is in the head" is not an exemption —
327
+ **"no human body ever appears in this game" is**, and a game whose player is
328
+ genuinely not a person (a car, a ship, an RTS cursor, a board) generates
329
+ that object with `npx genex model` instead.
330
+
331
+ Put its row in the Assets table up front and **enqueue it with your first
332
+ art actions** — its concepts ride the same review beat as the Stage-1 HUD
333
+ concept, so firing at minute 0 lands it around the v0 preview instead of
334
+ after it. `npx genex controller character` installs the controller and the
335
+ fallback body in one command; the boot path is written once and never
336
+ rewritten (`loadPlayerCharacter` — see
337
+ `$genex-threejs-character-controller`), so when the character lands,
338
+ `npx genex controller character --character <id>` is the entire switch.
339
+
340
+ The profile VRM avatar is the FALLBACK, in two shapes and both spoken
341
+ aloud: a temporary body while the character renders (say so plainly — it's
342
+ a fully textured animated humanoid, so nothing on screen will look
343
+ unfinished enough to remind you), or the stand-in when generation genuinely
344
+ could not happen (out of credits, failed, unverified), recorded in DESIGN.md
345
+ as `Player character: VRM — <reason>`. A capsule or hand-built primitive
346
+ standing in for a person is never a shipped state, for the local player or a
347
+ remote one. In a game with a generated character, every remote wears it —
348
+ a mixed roster of one themed hero plus stock avatars is the same incoherence
349
+ as capsule-and-cone remotes.
332
350
 
333
351
  **The default lane has ONE user stop, and it rides the concept review.**
334
352
  When the user names a visual reference, inspect references before writing
@@ -20,9 +20,9 @@ Three.js release or branch, and do not blindly copy demo architecture.
20
20
  | Work needed | Load |
21
21
  | --- | --- |
22
22
  | shot composition, chase/side/orbit rigs, camera handoffs, projection ownership, pointer look, mouse-aimed action (shooter, FPS/first-person, sniper, turret, crosshair/reticle), mouse-look, hand-rolled steering/pan/look input signs (screen-direction contract), floating origins | `$genex-threejs-camera-direction` |
23
- | on-foot player movement: walk/run/jump/crouch, third-person character, slopes, stairs, moving platforms, personal VRM animation, directional locomotion, transitions, action motion | `$genex-threejs-character-controller` |
24
- | the game's themed character — the DEFAULT whenever the protagonist is visible (third-person, or first-person with co-op/remote players); one user stop riding the concept review, owner-ratified auto-pick on silence (director §7) — or Meshy animation coverage beyond UAL: reference-informed A-pose concepts, exact action IDs, same-rig adapter | `$genex-ai-character` + `$genex-threejs-character-controller` |
25
- | remote player bodies in multiplayer — NEVER hand-built primitives: the themed character model in themed games, the player's `p.avatarUrl` VRM otherwise | `$genex-threejs-multiplayer` + `$genex-threejs-character-controller` |
23
+ | on-foot player movement: walk/run/jump/crouch, third-person character, slopes, stairs, moving platforms, the player's body loader, directional locomotion, transitions, action motion | `$genex-threejs-character-controller` |
24
+ | the game's own generated character — the player's BODY wherever a human body appears on screen (first-person included; the exemption is "no human body ever appears", not "the camera is in the head"), enqueued with the first art actions; one user stop riding the concept review, owner-ratified auto-pick on silence (director §7) — or Meshy animation coverage beyond UAL: reference-informed A-pose concepts, exact action IDs, same-rig adapter | `$genex-ai-character` + `$genex-threejs-character-controller` |
25
+ | remote player bodies in multiplayer — NEVER hand-built primitives: the game's generated character when it has one (everyone wears it), the player's `p.avatarUrl` VRM only when it doesn't | `$genex-threejs-multiplayer` + `$genex-threejs-character-controller` |
26
26
  | the game has enemies, NPCs, or creatures — **load whenever an enemy roster exists**: rigged bipeds via `npx genex creature`, static + procedural motion for other body shapes, plus the mechanical floor every enemy owes (collider, verified facing, hit reaction, death moment) | `$genex-threejs-creatures` |
27
27
  | the player drives or flies something: cars, drones, vehicle physics, gearbox, enter/exit between character and vehicle | `$genex-threejs-vehicle-controllers` |
28
28
  | playable on phones: touch/mobile input for any game — joystick, virtual buttons, drag zones, per-genre touch recipes, rotate-device overlay — wired by default for every NEW game when a recipe fits (skip with a one-line reason) | `$genex-threejs-touch-controls` |