@genex-ai/cli-demo 0.90.0-dev.227 → 0.92.0-dev.229

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "0.90.0-dev.227",
3
+ "version": "0.92.0-dev.229",
4
4
  "description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,11 +46,13 @@ license (SPDX: `CC0-1.0`). No attribution is required by the license; this
46
46
  notice is provided as a courtesy — supporting Quaternius on Patreon is a nice
47
47
  way to say thanks.
48
48
 
49
- ## Default avatar — CC0 1.0 Universal
49
+ ## Fallback avatar — CC0 1.0 Universal
50
50
 
51
- The character controller plays as your VRM avatar. When you have not chosen one
52
- (or run offline), the bundled default `assets/default-avatar.vrm` is copied to
53
- `public/assets/avatar.vrm`. It is **Wizzir** from the **100 Avatars** project by
51
+ The player's body is the game's own generated character; a VRM avatar is the
52
+ fallback lane. `genex controller character` copies the bundled default
53
+ `assets/default-avatar.vrm` to `public/assets/avatar.vrm`, which covers local
54
+ dev and load failures when that lane runs. It is **Wizzir** from the
55
+ **100 Avatars** project by
54
56
  **Polygonal Mind** (polygonalmind.com), dedicated to the public domain under
55
57
  CC0 1.0 Universal (SPDX: `CC0-1.0`). Attribution is a courtesy, not required.
56
58
 
@@ -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
 
@@ -146,44 +169,64 @@ Catalog entries are metadata-reviewed. Inspect the preview before choosing a
146
169
  specialty action. Only provider-declared `InPlace` loops or measured overrides
147
170
  may fill locomotion slots automatically.
148
171
 
149
- ## Custom animations — `genex motion`
172
+ ## Custom animations — `genex character animate "<verb>"`
150
173
 
151
174
  Catalog search is ALWAYS the first stop — its stock actions cover most verbs
152
- with no GPU spend. When the catalog genuinely lacks the motion (a bespoke
153
- weapon-hold locomotion set, a signature move, a boss telegraph), `genex
154
- motion` generates it from text on the platform GPU service and compiles it
155
- locally into a rig-independent set:
175
+ with no spend. When the catalog genuinely lacks the motion (a signature move, a
176
+ boss telegraph, a full 8-way movement set), say what the character should DO in
177
+ plain words and the platform generates it for this character's own rig:
156
178
 
157
179
  ```bash
158
- npx genex motion gen "soldier rifle low-ready idle, fully upright, head held high" --takes 4 --no-wait
159
- npx genex motion verify takes/ # objective gates — local, free, reproducible
160
- npx genex motion compile takes/rifle-idle.npz --out src/motion/rifle.json
161
- npx genex motion install --set rifle # vendored runtime + the proven rifle starter set
180
+ npx genex character animate <character-id> "overhead slam" "parry and recover" --no-wait
181
+ npx genex character animate <character-id> --locomotion --no-wait
182
+ npx genex character animate <character-id> "victory pose" --video ./take-3.mp4
162
183
  ```
163
184
 
164
- Billing is per take (default 4 per request); everything after `gen` is local.
165
- Unlike catalog clips (exact-rig only), compiled motion sets retarget onto
166
- Meshy/Mixamo GLB rigs and VRM avatars alike the vendored `src/motion/rigs.js`
167
- carries both verified retarget formulas, and it is the game's file to tune.
168
- Read [references/motion.md](references/motion.md) BEFORE your first
169
- `motion gen`: it carries the prompt wording ladder, the take-naming contract
170
- that drives compilation, the rig limits (finger-less hands cannot curl around
171
- a grip), and the full wiring. Wording mistakes cost paid takes.
185
+ One clip per verb. You never choose the method the platform routes each verb
186
+ (reuse a shared-library motion, generate movement, act it out on video first, or
187
+ generate from a description) and prints the plan BEFORE anything is charged.
188
+
189
+ **Show the plan to the user and let them change it before you continue.** That
190
+ is the one stop in this lane; everything after it runs in the background while
191
+ you keep building.
192
+
193
+ Read [references/motion-generation.md](references/motion-generation.md) before
194
+ the first run: it carries what makes a good verb, which verbs this cannot do,
195
+ and the footage requirements for `--video`.
196
+
197
+ `--locomotion` generates the full 8-way walk + run set (16 clips) — the
198
+ directional slots the shipped controller resolves but the stock pack leaves
199
+ empty, so a strafe stops being a faked forward walk. For an enemy that just
200
+ follows a path, `--lean` keeps it to forward walk + run.
201
+
202
+ Animate an enemy the same way — `genex creature animate <id> "<verb>"`. A
203
+ creature is already a character underneath; the alias exists so you don't have
204
+ to know that.
172
205
 
173
206
  ## Add actions and refresh the game
174
207
 
175
208
  ```bash
176
209
  npx genex character animate <character-id> --action <action-id>
177
210
  npx genex character animate <character-id> --action <first-id> --action <second-id> --no-wait
211
+ npx genex character motions <character-id> # what is installed right now
178
212
  npx genex controller character --character <character-id>
179
213
  ```
180
214
 
215
+ `--action` picks an existing catalog clip; a free-text verb generates one. Both
216
+ land in the same manifest and play through the same state machine.
217
+
181
218
  Ambiguous text queries print ranked candidates instead of silently spending
182
219
  credits. Already-installed actions return without another debit. After an
183
220
  animation job completes, rerun `genex controller character --character <id>`
184
221
  to refresh `public/assets/meshy-character.json`; existing controller source is
185
222
  preserved unless `--force` is explicitly used.
186
223
 
224
+ Six slots belong to the reviewed controller pack and cannot be replaced —
225
+ `idle.default`, `walk.forward`, `run.forward`, `crouch.forward`, `crouch.idle`,
226
+ `jump.full`. A generated locomotion set fills the other fourteen directions; the
227
+ plan says so before you spend anything. A character built without the pack
228
+ (every `genex creature`) takes all sixteen.
229
+
187
230
  Meshy manifests bypass browser cache, so newly installed actions must work
188
231
  after preview without asking the player to disable cache.
189
232
 
@@ -231,4 +274,27 @@ durations, nominal speeds, trajectories, and gameplay requirements.
231
274
  For multiplayer, only the owning client advances the dynamic body and publishes
232
275
  its pose plus compact animation state. Remote characters are visual-only: they
233
276
  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.
277
+ run a motion driver or a second physics controller for a remote player. In a
278
+ game with a generated character, every remote wears it — one
279
+ `loadRemotePlayerCharacter` call, one shared parsed base, N cheap clones.
280
+
281
+ ## Troubleshooting
282
+
283
+ - **"Not authorized"** — run `npx genex init` first (in the project — it resolves this project's own CLI) (it writes your `GENEX_TOKEN`).
284
+ - **"Out of credits" (`insufficient_credits`)** — the account has no credits left for
285
+ this character stage. Tell the user the facts the CLI printed: their balance, this
286
+ stage's cost, and when their credits refill. Then keep the player on the profile
287
+ VRM avatar (the fallback lane — no code change, it is already what the game
288
+ loads), tell them in one plain line that the game is wearing the platform avatar
289
+ because the character couldn't be generated, record
290
+ `Player character: VRM — out of credits` in DESIGN.md, and mark the spot with
291
+ `// TODO(genex): regenerate when credits refill`. Do not stop the session over
292
+ this, and do not hand-build a stand-in humanoid.
293
+ - **"Email not verified" (`email_verification_required`)** — generation credits
294
+ unlock after the account's email is verified. Give the user the verify link the
295
+ CLI printed, wait for them to confirm, then re-run the command.
296
+ - **The character is stuck mid-lane** — each stage is a separate generation. `npx
297
+ genex wait --all` prints one line per job; a failed `preview` or `finalize` is
298
+ re-run from the id of the stage before it, never from the beginning.
299
+ - **The rig or a pose is bad** — reject and regenerate. Never repair limbs at
300
+ runtime; validation never rewrites limb animation.
@@ -0,0 +1,125 @@
1
+ # Generating character motion
2
+
3
+ `npx genex character animate <character-id> "<verb>"` — one clip per verb, on
4
+ this character's own rig. Read this before the first run; it is short, and the
5
+ first two sections are where the credits go.
6
+
7
+ ```bash
8
+ npx genex character animate <id> "overhead slam" --no-wait
9
+ npx genex character animate <id> --locomotion --no-wait
10
+ npx genex character animate <id> "leap the gap and roll on landing" --duration 6
11
+ npx genex character animate <id> "victory pose" --video ./take-3.mp4
12
+ npx genex character motions <id>
13
+ npx genex creature animate <id> "wind-up ground pound" --no-wait
14
+ ```
15
+
16
+ You do not choose how a verb is made. The platform prints a plan first — which
17
+ verbs it will reuse, generate, or act out on video, and one total. **Show the
18
+ plan to the user.** That is the single stop in this lane; after it, everything
19
+ runs in the background while you keep building. Collect it later with
20
+ `genex wait --all`.
21
+
22
+ ## Write the verb like a stage direction
23
+
24
+ The generator turns your words into a body performance, so name the BODY, not
25
+ the intent. "Overhead slam" is a motion; "attack" is a category.
26
+
27
+ | instead of | write |
28
+ | --- | --- |
29
+ | `attack` | `heavy two-handed overhead slam, ending in a low crouch` |
30
+ | `hurt` | `stagger back two steps from a chest impact, arms flailing` |
31
+ | `win` | `plant both feet, raise one fist overhead, chest out` |
32
+ | `die` | `drop to the knees, then fall forward onto the chest` |
33
+
34
+ Three things make a verb generate well:
35
+
36
+ - **Name the beats.** A wind-up, a committed main beat, a recovery. A motion with
37
+ one beat reads as a twitch.
38
+ - **Say where the weight goes.** "Sinks into the knees as it lands" is the single
39
+ most useful phrase you can add — without it you tend to get a bow from the
40
+ waist where you wanted a crouch.
41
+ - **Keep it one person, standing, on the floor.** No props that define the pose,
42
+ no partner, no furniture.
43
+
44
+ ## What this cannot do
45
+
46
+ Do not spend on these — pick a different approach instead:
47
+
48
+ - **Fingers and fine manipulation** — picking a lock, typing, a trigger squeeze,
49
+ threading a needle. Generated rigs have no finger bones, so the hand simply
50
+ will not do it. Fake it with a prop animation or a cut.
51
+ - **Two characters interacting** — a handshake, a grapple, a carry. Only one body
52
+ is generated. Animate each side separately and time them in code.
53
+ - **Anything a prop defines** — swinging on a rope, climbing a specific ladder,
54
+ sitting in a specific chair. The clip does not know your geometry; drive those
55
+ with `$genex-threejs-procedural-animation` instead.
56
+ - **Non-bipeds** — quadrupeds, fliers, blobs. See `$genex-threejs-creatures`;
57
+ those stay static models plus procedural motion.
58
+
59
+ ## Room to land the beats: `--duration`
60
+
61
+ Some verbs are made by generating a short reference video of a performer and
62
+ converting THAT into motion — the plan says which ones ("acted out on video").
63
+ The footage is a hard ceiling on the animation, and the default 3 seconds is a
64
+ tight fit for anything with more than one beat: a performer given too little
65
+ time rushes, and a rushed wind-up-slam-recover comes back as a bow.
66
+
67
+ `--duration 3|4|5|6` gives those verbs more room. Use it when the verb you wrote
68
+ genuinely has three beats — a leap with a landing roll, a stagger that recovers,
69
+ a combo. Leave it alone for a single action; longer is not better, it is just
70
+ longer, and it costs more.
71
+
72
+ It only reaches verbs the platform routes to video, and the plan tells you
73
+ whether it applied. It cannot change footage you supplied with `--video` —
74
+ that clip already has a length.
75
+
76
+ ## Movement: `--locomotion`
77
+
78
+ Generates the full 8-way walk + run set — 16 clips. This fills the directional
79
+ slots the shipped controller already resolves (`walk.forward-left`,
80
+ `run.back-right`, and so on) but that the stock pack leaves empty, so strafing
81
+ stops being a forward walk played sideways.
82
+
83
+ Six slots belong to the reviewed controller pack and cannot be replaced:
84
+ `idle.default`, `walk.forward`, `run.forward`, `crouch.forward`, `crouch.idle`,
85
+ `jump.full`. A generated set fills the other fourteen; the plan says which. A
86
+ character built without the pack — every `genex creature` — takes all sixteen.
87
+
88
+ For an enemy that follows a path, `--lean` generates forward walk + run only.
89
+ Enemies have no controller state machine driving a direction picker, so the
90
+ other six directions would never play. Ask for the full set when a boss
91
+ genuinely circles the player.
92
+
93
+ ## Your own footage: `--video`
94
+
95
+ `--video ./clip.mp4` converts a real performance instead of a generated one.
96
+ The footage is a hard ceiling on the result — nothing can add weight the
97
+ performer never committed — so it has to meet the extractor's requirements:
98
+
99
+ - **Exactly one person**, alone in frame.
100
+ - **Whole body visible**, head to feet, for the entire clip. A limb that leaves
101
+ the frame is a gap that cannot be recovered.
102
+ - **Locked-off camera.** No pan, no zoom, no handheld drift, no cuts.
103
+ - **Plain contrasting background**, and clothing in distinct solid colors — no
104
+ long coat, cape, or skirt over the legs.
105
+ - **Even lighting.** Heavy shadows or a silhouette degrade the extraction.
106
+ - **2–60 seconds.** `.mp4` or `.mov`.
107
+
108
+ Phone recordings work. So do screen captures and gameplay clips.
109
+
110
+ The video is checked against these rules before anything is converted, and a
111
+ failure tells you which requirement broke and roughly when — re-shoot against
112
+ that note rather than re-running the same file.
113
+
114
+ ## After the clips land
115
+
116
+ ```bash
117
+ npx genex controller character --character <character-id>
118
+ ```
119
+
120
+ Then play the game and watch the motion on the real character. Numbers and a
121
+ completed job prove the clip installed; only your eyes prove it reads right.
122
+
123
+ A clip that plays as a T-pose or does not play at all means it did not bind —
124
+ check `genex character motions <id>` to see what is actually installed. Do not
125
+ "fix" it with runtime bone corrections; regenerate it.
@@ -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