@genex-ai/cli-demo 0.55.0-dev.124 → 0.56.0-dev.126
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 +1 -1
- package/dist/index.js +50 -2
- package/package.json +1 -1
- package/templates/controllers/character/meshy/meshy-loader.ts +66 -5
- package/templates/skills/genex-ai-character/SKILL.md +8 -1
- package/templates/skills/genex-threejs-character-controller/SKILL.md +5 -0
- package/templates/skills/genex-threejs-visual-validation/SKILL.md +5 -1
package/README.md
CHANGED
|
@@ -141,7 +141,7 @@ one call (`image-main` + `image-alt-N` URLs) — pick the best instead of re-rol
|
|
|
141
141
|
`genex character "<prompt>"` runs a controller-ready Meshy workflow through the
|
|
142
142
|
same authenticated credit, BullMQ/Redis, and R2 generation pipeline. It defaults
|
|
143
143
|
to a Meshy 6 textured A-pose humanoid with browser-oriented remeshing, rigging, and the
|
|
144
|
-
|
|
144
|
+
preview-reviewed neutral-v2 idle/walk/run/crouch/jump pack. `--animation <id-or-query>` is repeatable;
|
|
145
145
|
`--height`, `--polycount`, `--no-controller-pack`, and `--no-wait` adjust the
|
|
146
146
|
request. The CLI prints an atomic component quote before enqueueing.
|
|
147
147
|
|
package/dist/index.js
CHANGED
|
@@ -3239,6 +3239,19 @@ async function installMeshyCharacterManifest(args) {
|
|
|
3239
3239
|
`
|
|
3240
3240
|
);
|
|
3241
3241
|
args.log.dim(` public/assets/meshy-character.json (${args.characterId}, current revision)`);
|
|
3242
|
+
const pack = manifest.controllerPack;
|
|
3243
|
+
if (typeof pack?.key === "string" && typeof pack.version === "number") {
|
|
3244
|
+
args.log.success(`Meshy controller pack ${pack.key} v${pack.version}`);
|
|
3245
|
+
} else {
|
|
3246
|
+
args.log.warn("Legacy Meshy manifest \u2014 regenerate the character for the preview-reviewed neutral-v2 locomotion pack.");
|
|
3247
|
+
}
|
|
3248
|
+
const actionIds = (manifest.clips ?? []).map((clip) => clip.actionId).filter((actionId) => typeof actionId === "number");
|
|
3249
|
+
args.log.dim(` installed action ids: ${actionIds.length > 0 ? actionIds.join(", ") : "none"}`);
|
|
3250
|
+
const slots = Object.keys(manifest.locomotion?.bindings ?? manifest.locomotion?.slots ?? {}).sort();
|
|
3251
|
+
args.log.dim(` locomotion slots: ${slots.length > 0 ? slots.join(", ") : "none"}`);
|
|
3252
|
+
const crouchCovered = slots.includes("crouch.idle") && slots.includes("crouch.forward");
|
|
3253
|
+
if (crouchCovered) args.log.success("Visual crouch coverage: idle + move");
|
|
3254
|
+
else args.log.warn("Visual crouch coverage is incomplete; physics crouch may fall back to a standing pose.");
|
|
3242
3255
|
}
|
|
3243
3256
|
async function installOwnerAvatar(args) {
|
|
3244
3257
|
const { root, srcDir, apiUrl, token, log } = args;
|
|
@@ -12129,7 +12142,36 @@ var CURATED = {
|
|
|
12129
12142
|
[-2]: { loop: true, motionPolicy: "controller-loop", controllerSlots: ["walk.forward"] },
|
|
12130
12143
|
[-1]: { loop: true, motionPolicy: "controller-loop", controllerSlots: ["run.forward"] },
|
|
12131
12144
|
[0]: { loop: true, motionPolicy: "controller-loop", controllerSlots: ["idle.default"] },
|
|
12132
|
-
[
|
|
12145
|
+
[243]: {
|
|
12146
|
+
loop: true,
|
|
12147
|
+
motionPolicy: "controller-loop",
|
|
12148
|
+
controllerSlots: ["idle.default"],
|
|
12149
|
+
reviewStatus: "preview-reviewed"
|
|
12150
|
+
},
|
|
12151
|
+
[466]: {
|
|
12152
|
+
loop: false,
|
|
12153
|
+
motionPolicy: "anchored-action",
|
|
12154
|
+
controllerSlots: ["jump.full"],
|
|
12155
|
+
reviewStatus: "preview-reviewed"
|
|
12156
|
+
},
|
|
12157
|
+
[613]: {
|
|
12158
|
+
loop: true,
|
|
12159
|
+
motionPolicy: "controller-loop",
|
|
12160
|
+
controllerSlots: ["walk.forward"],
|
|
12161
|
+
reviewStatus: "preview-reviewed"
|
|
12162
|
+
},
|
|
12163
|
+
[616]: {
|
|
12164
|
+
loop: true,
|
|
12165
|
+
motionPolicy: "controller-loop",
|
|
12166
|
+
controllerSlots: ["crouch.forward"],
|
|
12167
|
+
reviewStatus: "preview-reviewed"
|
|
12168
|
+
},
|
|
12169
|
+
[658]: {
|
|
12170
|
+
loop: true,
|
|
12171
|
+
motionPolicy: "controller-loop",
|
|
12172
|
+
controllerSlots: ["run.forward"],
|
|
12173
|
+
reviewStatus: "preview-reviewed"
|
|
12174
|
+
}
|
|
12133
12175
|
};
|
|
12134
12176
|
var LOOP_SUBCATEGORIES = /* @__PURE__ */ new Set(["Idle", "Walking", "Running", "CrouchWalking", "Swimming"]);
|
|
12135
12177
|
var CHOREOGRAPHY_SUBCATEGORIES = /* @__PURE__ */ new Set([
|
|
@@ -12373,6 +12415,12 @@ async function runCharacter(opts) {
|
|
|
12373
12415
|
}
|
|
12374
12416
|
log.plain(c.bold("Character quote"));
|
|
12375
12417
|
log.plain(` ${price.credits} Genex credits = base ${price.baseCredits} + actions ${price.animationCredits}`);
|
|
12418
|
+
if (price.controllerPackKey) {
|
|
12419
|
+
log.plain(
|
|
12420
|
+
` controller pack ${c.cyan(price.controllerPackKey)} \xB7 provider actions ${(price.controllerActionIds ?? []).join(", ")}`
|
|
12421
|
+
);
|
|
12422
|
+
}
|
|
12423
|
+
log.dim(` ${price.actionsGenerated} Meshy animation task${price.actionsGenerated === 1 ? "" : "s"} in this request.`);
|
|
12376
12424
|
log.plain("");
|
|
12377
12425
|
await runGenerate("character", {
|
|
12378
12426
|
...opts,
|
|
@@ -12490,7 +12538,7 @@ async function runAnimationsSearch(opts) {
|
|
|
12490
12538
|
];
|
|
12491
12539
|
log.plain(` ${c.cyan(String(result.actionId))} ${c.bold(result.name)} ${c.dim(result.key)}`);
|
|
12492
12540
|
log.dim(
|
|
12493
|
-
` ${result.motionPolicy}${result.inPlace ? " \xB7 InPlace" : ""}${slots}${requirements.length > 0 ? ` \xB7 needs ${requirements.join(", ")}` : ""}`
|
|
12541
|
+
` ${result.motionPolicy}${result.inPlace ? " \xB7 InPlace" : ""}${slots}${requirements.length > 0 ? ` \xB7 needs ${requirements.join(", ")}` : ""} \xB7 ${result.reviewStatus}`
|
|
12494
12542
|
);
|
|
12495
12543
|
log.dim(` preview ${result.previewUrl} \xB7 estimated additional cost ${result.estimatedCredits} credits`);
|
|
12496
12544
|
}
|
package/package.json
CHANGED
|
@@ -9,12 +9,21 @@ export type MeshyMotionPolicy =
|
|
|
9
9
|
| "planar-root-action"
|
|
10
10
|
| "choreography";
|
|
11
11
|
|
|
12
|
+
export type MeshyLocomotionBindingMode = "loop" | "one-shot" | "pose";
|
|
13
|
+
|
|
14
|
+
export interface MeshyLocomotionBinding {
|
|
15
|
+
clip: string;
|
|
16
|
+
mode: MeshyLocomotionBindingMode;
|
|
17
|
+
phase?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
export interface MeshyCharacterManifest {
|
|
13
21
|
schema: 1;
|
|
14
22
|
characterId: string;
|
|
15
23
|
revision: number;
|
|
16
24
|
manifestVersion: number;
|
|
17
25
|
rig: "meshy-biped";
|
|
26
|
+
controllerPack?: { key: string; version: number };
|
|
18
27
|
model: {
|
|
19
28
|
url: string;
|
|
20
29
|
heightMeters: number | null;
|
|
@@ -37,6 +46,7 @@ export interface MeshyCharacterManifest {
|
|
|
37
46
|
}>;
|
|
38
47
|
locomotion: {
|
|
39
48
|
slots: Record<string, string>;
|
|
49
|
+
bindings?: Record<string, MeshyLocomotionBinding>;
|
|
40
50
|
fallbacks: Record<string, string>;
|
|
41
51
|
nominalSpeed?: Partial<Record<"walk" | "run" | "crouch", number>>;
|
|
42
52
|
playbackRate: { min: number; max: number };
|
|
@@ -77,6 +87,57 @@ function compatibleTracks(model: THREE.Object3D, clips: THREE.AnimationClip[]):
|
|
|
77
87
|
}));
|
|
78
88
|
}
|
|
79
89
|
|
|
90
|
+
type TrackConstructor = new (
|
|
91
|
+
name: string,
|
|
92
|
+
times: ArrayLike<number>,
|
|
93
|
+
values: ArrayLike<number>,
|
|
94
|
+
interpolation?: number,
|
|
95
|
+
) => THREE.KeyframeTrack;
|
|
96
|
+
|
|
97
|
+
/** Materialize one reviewed frame as an independent mixer clip for crouch-idle. */
|
|
98
|
+
export function createPoseClip(source: THREE.AnimationClip, name: string, phase = 0): THREE.AnimationClip {
|
|
99
|
+
const sampleTime = Math.max(0, Math.min(1, phase)) * Math.max(0, source.duration);
|
|
100
|
+
const tracks = source.tracks.map((track) => {
|
|
101
|
+
const valueSize = track.getValueSize();
|
|
102
|
+
const sampled = new Float32Array(valueSize);
|
|
103
|
+
const interpolant = (track as THREE.KeyframeTrack & {
|
|
104
|
+
createInterpolant(result: Float32Array): { evaluate(time: number): ArrayLike<number> };
|
|
105
|
+
}).createInterpolant(sampled);
|
|
106
|
+
interpolant.evaluate(sampleTime);
|
|
107
|
+
const Track = track.constructor as TrackConstructor;
|
|
108
|
+
return new Track(track.name, [0], sampled, track.getInterpolation());
|
|
109
|
+
});
|
|
110
|
+
// A non-zero duration keeps the static pose well-defined for looped mixer actions.
|
|
111
|
+
return new THREE.AnimationClip(name, 1, tracks);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function resolveMeshyLocomotion(
|
|
115
|
+
manifest: MeshyCharacterManifest,
|
|
116
|
+
sourceClips: readonly THREE.AnimationClip[],
|
|
117
|
+
): { clips: THREE.AnimationClip[]; slots: Record<string, string>; missingBindings: string[] } {
|
|
118
|
+
const clips = [...sourceClips];
|
|
119
|
+
const slots = { ...manifest.locomotion.slots };
|
|
120
|
+
const missingBindings: string[] = [];
|
|
121
|
+
for (const [slot, binding] of Object.entries(manifest.locomotion.bindings ?? {})) {
|
|
122
|
+
const source = clips.find((clip) => clip.name === binding.clip);
|
|
123
|
+
if (!source) {
|
|
124
|
+
missingBindings.push(slot);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (binding.mode === "pose") {
|
|
128
|
+
const poseName = `${binding.clip}__${slot.replaceAll(".", "_")}__pose`;
|
|
129
|
+
clips.push(createPoseClip(source, poseName, binding.phase));
|
|
130
|
+
slots[slot] = poseName;
|
|
131
|
+
} else {
|
|
132
|
+
slots[slot] = binding.clip;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const [slot, fallback] of Object.entries(manifest.locomotion.fallbacks)) {
|
|
136
|
+
if (!slots[slot] && slots[fallback]) slots[slot] = slots[fallback];
|
|
137
|
+
}
|
|
138
|
+
return { clips, slots, missingBindings };
|
|
139
|
+
}
|
|
140
|
+
|
|
80
141
|
export async function loadMeshyCharacter(manifestUrl: string): Promise<MeshyCharacter> {
|
|
81
142
|
const response = await fetch(manifestUrl);
|
|
82
143
|
if (!response.ok) throw new Error(`[meshy-character] ${manifestUrl} returned HTTP ${response.status}`);
|
|
@@ -106,18 +167,18 @@ export async function loadMeshyCharacter(manifestUrl: string): Promise<MeshyChar
|
|
|
106
167
|
if (compatible.length === 1) compatible[0]!.name = item.entry.key;
|
|
107
168
|
clips.push(...compatible);
|
|
108
169
|
}
|
|
109
|
-
const
|
|
110
|
-
for (const
|
|
111
|
-
|
|
170
|
+
const locomotion = resolveMeshyLocomotion(manifest, clips);
|
|
171
|
+
for (const slot of locomotion.missingBindings) {
|
|
172
|
+
console.warn(`[meshy-character] locomotion binding ${slot} references a clip that did not load.`);
|
|
112
173
|
}
|
|
113
174
|
return {
|
|
114
175
|
kind: "meshy-native",
|
|
115
176
|
scene: base.scene,
|
|
116
|
-
clips,
|
|
177
|
+
clips: locomotion.clips,
|
|
117
178
|
manifest,
|
|
118
179
|
rigSignature: manifest.model.skeletonSignature,
|
|
119
180
|
locomotionProfile: {
|
|
120
|
-
slots,
|
|
181
|
+
slots: locomotion.slots,
|
|
121
182
|
nominalSpeed: manifest.locomotion.nominalSpeed,
|
|
122
183
|
playbackRate: manifest.locomotion.playbackRate,
|
|
123
184
|
},
|
|
@@ -17,7 +17,8 @@ npx genex character "stylized desert courier, practical layered clothing"
|
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
The default request uses Meshy 6 to generate and texture an A-pose humanoid,
|
|
20
|
-
remeshes and rigs it, adds the
|
|
20
|
+
remeshes and rigs it, adds the preview-reviewed neutral-v2
|
|
21
|
+
idle/walk/run/crouch/jump controller pack,
|
|
21
22
|
and stores every successful model
|
|
22
23
|
and clip at permanent Genex asset URLs. It prints the complete Genex-credit
|
|
23
24
|
quote before enqueueing. The Meshy API key remains server-side; never ask the
|
|
@@ -35,6 +36,12 @@ npx genex wait <generation-id>
|
|
|
35
36
|
omits the default locomotion pack. `--no-wait` returns a generation id for
|
|
36
37
|
`genex wait`; it does not create a second paid request.
|
|
37
38
|
|
|
39
|
+
Before handoff, capture idle, walk, run, crouch-idle, crouch-move, and jump.
|
|
40
|
+
Inspect shoulders, elbows, wrists, and hands as well as the feet. Reject
|
|
41
|
+
shrugging palms-up poses, permanently raised elbows, or a gait whose
|
|
42
|
+
upper-body style contradicts the requested character. “No T-pose” is not
|
|
43
|
+
an animation-quality check.
|
|
44
|
+
|
|
38
45
|
## Search first; use action IDs
|
|
39
46
|
|
|
40
47
|
```bash
|
|
@@ -205,6 +205,11 @@ jumping. `crouchMode: "hold"` makes it hold-to-crouch;
|
|
|
205
205
|
`character.setCrouch(bool)` and `character.crouchActive` are the programmatic
|
|
206
206
|
hooks (that's also how a touch button wires in — see Mobile below).
|
|
207
207
|
|
|
208
|
+
Press every control that the HUD or handoff advertises. For Meshy characters,
|
|
209
|
+
crouch passes only when both the capsule behavior and a visibly crouched pose
|
|
210
|
+
are confirmed. Capture crouch-idle and crouch-move separately; a standing
|
|
211
|
+
animation over a shortened capsule is not working crouch.
|
|
212
|
+
|
|
208
213
|
## Mobile: TouchJoystick + VirtualButton (wire by default)
|
|
209
214
|
|
|
210
215
|
**Wire these whenever this controller is installed — not only when asked.**
|
|
@@ -44,7 +44,11 @@ everything twice.
|
|
|
44
44
|
sign-in gate.
|
|
45
45
|
2. Press each documented control once (keys, pointer); assert a **visible
|
|
46
46
|
response** to every one — the player moves, the camera turns, the button
|
|
47
|
-
fires.
|
|
47
|
+
fires. For an animated character, capture idle, walk, run, crouch-idle,
|
|
48
|
+
crouch-move, and jump. Inspect shoulders, elbows, wrists, and hands as well
|
|
49
|
+
as the feet: a fully bound non-T-pose can still be stylistically broken.
|
|
50
|
+
For Meshy characters, crouch passes only when the capsule behavior and a
|
|
51
|
+
visibly crouched pose both respond.
|
|
48
52
|
3. Capture one screenshot of live gameplay — of the **game**, not a sign-in
|
|
49
53
|
gate or loading screen. A capture of the SDK's "Sign in to play" overlay is
|
|
50
54
|
NOT gameplay evidence. Evidence captured in local test mode must be labeled
|