@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/README.md +4 -1
- package/dist/index.js +598 -91
- package/package.json +1 -1
- package/templates/controllers/NOTICE.md +6 -4
- package/templates/controllers/character/player-character.ts +217 -0
- package/templates/skills/genex-ai-character/SKILL.md +91 -25
- package/templates/skills/genex-ai-character/references/motion-generation.md +125 -0
- package/templates/skills/genex-ai-image/SKILL.md +5 -1
- package/templates/skills/genex-ai-menu/SKILL.md +14 -6
- package/templates/skills/genex-ai-skybox/SKILL.md +19 -6
- package/templates/skills/genex-game-director/SKILL.md +29 -11
- package/templates/skills/genex-game-director/references/routing-map.md +4 -3
- package/templates/skills/genex-threejs-character-controller/SKILL.md +105 -52
- package/templates/skills/genex-threejs-character-controller/references/animations.md +62 -50
- package/templates/skills/genex-threejs-creatures/SKILL.md +14 -4
- package/templates/skills/genex-threejs-embed-auth/SKILL.md +5 -3
- package/templates/skills/genex-threejs-multiplayer/SKILL.md +10 -8
- package/templates/skills/genex-threejs-multiplayer/references/host-physics.md +8 -7
- package/templates/skills/genex-threejs-procedural-animation/SKILL.md +2 -2
- package/templates/skills/genex-ai-character/references/motion.md +0 -145
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "0.
|
|
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
|
-
##
|
|
49
|
+
## Fallback avatar — CC0 1.0 Universal
|
|
50
50
|
|
|
51
|
-
The
|
|
52
|
-
|
|
53
|
-
`public/assets/avatar.vrm
|
|
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
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
159
|
-
npx genex
|
|
160
|
-
npx genex
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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,
|
|
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.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|