@genex-ai/cli-demo 0.53.0-dev.120 → 0.54.0-dev.122

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,135 @@
1
+ // SPDX-License-Identifier: MIT
2
+ import type { CharacterAnimations } from "./character-animations.ts";
3
+ import type { CharacterActionMotion } from "./character-controller.ts";
4
+
5
+ export type MotionTrajectorySample = readonly [time: number, x: number, y: number, z: number];
6
+
7
+ export interface MotionTrajectory {
8
+ schema: 1;
9
+ clip: string;
10
+ samples: MotionTrajectorySample[];
11
+ }
12
+
13
+ export interface MotionActionController {
14
+ setActionMotion(motion: CharacterActionMotion | null): void;
15
+ }
16
+
17
+ export async function loadMotionTrajectory(url: string): Promise<MotionTrajectory> {
18
+ const response = await fetch(url);
19
+ if (!response.ok) throw new Error(`[motion-action] ${url} returned HTTP ${response.status}`);
20
+ const value = await response.json() as Partial<MotionTrajectory>;
21
+ if (value.schema !== 1 || !value.clip || !Array.isArray(value.samples) || value.samples.length < 1) {
22
+ throw new Error(`[motion-action] invalid trajectory ${url}`);
23
+ }
24
+ const samples = value.samples as MotionTrajectorySample[];
25
+ let previous = -Infinity;
26
+ for (const sample of samples) {
27
+ if (
28
+ sample.length !== 4 ||
29
+ sample.some((item) => !Number.isFinite(item)) ||
30
+ sample[0] < previous
31
+ ) {
32
+ throw new Error(`[motion-action] invalid trajectory sample in ${url}`);
33
+ }
34
+ previous = sample[0];
35
+ }
36
+ return { schema: 1, clip: value.clip, samples };
37
+ }
38
+
39
+ /**
40
+ * Samples an extracted GLB root curve into ECCTRL's dynamic Rapier body.
41
+ * The animation GLB stays in-place; only this driver can advance the capsule.
42
+ */
43
+ export class MotionActionDriver {
44
+ #controller: MotionActionController;
45
+ #animations: CharacterAnimations;
46
+ #trajectory: MotionTrajectory | null = null;
47
+ #elapsed = 0;
48
+ #onDone: (() => void) | undefined;
49
+
50
+ constructor(controller: MotionActionController, animations: CharacterAnimations) {
51
+ this.#controller = controller;
52
+ this.#animations = animations;
53
+ }
54
+
55
+ get active(): boolean {
56
+ return this.#trajectory !== null;
57
+ }
58
+
59
+ play(trajectory: MotionTrajectory, onDone?: () => void): boolean {
60
+ this.cancel();
61
+ if (!this.#animations.playOneShot(trajectory.clip)) return false;
62
+ this.#trajectory = trajectory;
63
+ this.#elapsed = 0;
64
+ this.#onDone = onDone;
65
+ return true;
66
+ }
67
+
68
+ /** Call once in the fixed-step callback immediately before controller.update(). */
69
+ update(dt: number): void {
70
+ const trajectory = this.#trajectory;
71
+ if (!trajectory || !(dt > 0)) return;
72
+ const duration = trajectory.samples.at(-1)?.[0] ?? 0;
73
+ // Release on the callback AFTER the final interval was applied. Clearing
74
+ // immediately after setActionMotion would erase that velocity before the
75
+ // caller's following controller.update().
76
+ if (this.#elapsed >= duration) {
77
+ this.#finish();
78
+ return;
79
+ }
80
+ const nextTime = Math.min(this.#elapsed + dt, duration);
81
+ const from = sampleTrajectory(trajectory.samples, this.#elapsed);
82
+ const to = sampleTrajectory(trajectory.samples, nextTime);
83
+ const sampleDt = nextTime - this.#elapsed;
84
+ if (sampleDt > 0) {
85
+ this.#controller.setActionMotion({
86
+ x: (to[1] - from[1]) / sampleDt,
87
+ z: (to[3] - from[3]) / sampleDt,
88
+ });
89
+ }
90
+ this.#elapsed = nextTime;
91
+ }
92
+
93
+ cancel(): void {
94
+ if (!this.#trajectory) return;
95
+ this.#controller.setActionMotion(null);
96
+ this.#trajectory = null;
97
+ this.#elapsed = 0;
98
+ this.#onDone = undefined;
99
+ }
100
+
101
+ #finish(): void {
102
+ const done = this.#onDone;
103
+ this.#controller.setActionMotion(null);
104
+ this.#trajectory = null;
105
+ this.#elapsed = 0;
106
+ this.#onDone = undefined;
107
+ done?.();
108
+ }
109
+ }
110
+
111
+ export function sampleTrajectory(
112
+ samples: readonly MotionTrajectorySample[],
113
+ time: number,
114
+ ): MotionTrajectorySample {
115
+ if (samples.length === 0) return [0, 0, 0, 0];
116
+ if (time <= samples[0]![0]) return samples[0]!;
117
+ const last = samples.at(-1)!;
118
+ if (time >= last[0]) return last;
119
+ let high = samples.length - 1;
120
+ let low = 0;
121
+ while (high - low > 1) {
122
+ const middle = Math.floor((low + high) / 2);
123
+ if (samples[middle]![0] <= time) low = middle;
124
+ else high = middle;
125
+ }
126
+ const a = samples[low]!;
127
+ const b = samples[high]!;
128
+ const alpha = (time - a[0]) / Math.max(b[0] - a[0], Number.EPSILON);
129
+ return [
130
+ time,
131
+ a[1] + (b[1] - a[1]) * alpha,
132
+ a[2] + (b[2] - a[2]) * alpha,
133
+ a[3] + (b[3] - a[3]) * alpha,
134
+ ];
135
+ }
@@ -4,14 +4,14 @@
4
4
  // and auto-detected: the free UAL's Blender Rigify `DEF-*` skeleton and the UAL
5
5
  // Pro's UE-mannequin-style skeleton (`pelvis`/`spine_01`/…) that the bundled
6
6
  // core library and every CDN animation pack use. Adapted from the
7
- // official three-vrm Mixamo retarget recipe (@pixiv/three-vrm examples, MIT):
7
+ // official three-vrm humanoid retargeting example (@pixiv/three-vrm examples, MIT):
8
8
  // rewrite each bone track into the VRM's normalized-bone local space using the
9
9
  // SOURCE rig's rest-pose world rotations, and scale the hips translation by the
10
10
  // height ratio. Because vrm-loader calls VRMUtils.rotateVRM0, VRM 0.x and 1.0
11
11
  // share this one path — no per-version flip.
12
12
  //
13
13
  // The HIPS POSITION track is kept (delta from the source rest pose, scaled by
14
- // the hips-height ratio, like the Mixamo recipe). Dropping it pins the hips at
14
+ // the hips-height ratio, like the upstream recipe). Dropping it pins the hips at
15
15
  // bind height, so any pose that lowers the hips (idle stance, walk contact,
16
16
  // punches) lifts the feet off the floor instead — the UAL idle alone holds the
17
17
  // hips ~4.5 cm below rest. All other position tracks are still dropped: the
@@ -134,7 +134,7 @@ export function retargetClips(
134
134
  // (PropertyBinding strips `[].:/ ` and turns spaces into `_`), so a Rigify bone
135
135
  // "DEF-upper_arm.L" shows up in tracks as "DEF-upper_armL". Map those sanitized
136
136
  // names back to the real bones, whose actual names carry the dots the map
137
- // keys on. (Mixamo names are dotless, so the upstream recipe never needed this.)
137
+ // keys on. (Many source rigs are dotless, so the upstream example never needed this.)
138
138
  const sanitize = (name: string): string => name.replace(/\s/g, "_").replace(/[[\]./:]/g, "");
139
139
  const sourceByTrackName = new Map<string, THREE.Object3D>();
140
140
  animationRoot.traverse((o) => {
@@ -220,7 +220,7 @@ export function retargetClips(
220
220
 
221
221
  // Full bind-pose retarget: reproduce the SOURCE bone's world-space motion
222
222
  // on the TARGET bone, accounting for BOTH rigs' bind orientations, then
223
- // express it in the target's local space. Unlike the simplified Mixamo
223
+ // express it in the target's local space. Unlike the simplified source-rig
224
224
  // recipe (source-rest only), this also uses the VRM normalized bone's bind
225
225
  // world rotation — necessary because T-pose limbs are far from identity,
226
226
  // which is what left arms pointing straight up before. Reduces to identity
@@ -0,0 +1,97 @@
1
+ ---
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 when a game needs a custom playable character or motion not covered by the default VRM + UAL lane.
4
+ ---
5
+
6
+ # Genex AI Character
7
+
8
+ Use the existing VRM + UAL character controller by default. Use this lane when
9
+ the game needs a custom generated humanoid or an action unavailable in UAL.
10
+ Both lanes use the same ECCTRL-derived Rapier controller, camera, inputs,
11
+ crossfades, transition rules, and multiplayer authority contract.
12
+
13
+ ## Generate a controller-ready character
14
+
15
+ ```bash
16
+ npx genex character "stylized desert courier, practical layered clothing"
17
+ ```
18
+
19
+ The default request uses Meshy 6 to generate and texture an A-pose humanoid,
20
+ remeshes and rigs it, adds the validated idle/walk/run/jump controller pack,
21
+ and stores every successful model
22
+ and clip at permanent Genex asset URLs. It prints the complete Genex-credit
23
+ quote before enqueueing. The Meshy API key remains server-side; never ask the
24
+ user for one or call Meshy directly from game code.
25
+
26
+ Useful options:
27
+
28
+ ```bash
29
+ npx genex character "compact fantasy knight" --height 1.7 --polycount 40000
30
+ npx genex character "retro space pilot" --animation 466 --no-wait
31
+ npx genex wait <generation-id>
32
+ ```
33
+
34
+ `--animation <id-or-query>` is repeatable. `--no-controller-pack` deliberately
35
+ omits the default locomotion pack. `--no-wait` returns a generation id for
36
+ `genex wait`; it does not create a second paid request.
37
+
38
+ ## Search first; use action IDs
39
+
40
+ ```bash
41
+ npx genex animations search "rifle reload" --json
42
+ npx genex animations search "dance" --category Action --limit 8
43
+ npx genex animations search "walk backward" --in-place
44
+ ```
45
+
46
+ Search is local and free: it reads the committed Meshy catalog and returns the
47
+ numeric action ID, stable key, preview URL, motion policy, controller slots,
48
+ requirements, review status, and estimated Genex cost. Use a returned action
49
+ ID. Never invent an ID or assume an unsupported motion exists. For example,
50
+ the current committed catalog has no skateboard action; build that mechanic
51
+ only after search returns real coverage.
52
+
53
+ Catalog entries are metadata-reviewed. Inspect the preview before choosing a
54
+ specialty action. Only provider-declared `InPlace` loops or measured overrides
55
+ may fill locomotion slots automatically.
56
+
57
+ ## Add actions and refresh the game
58
+
59
+ ```bash
60
+ npx genex character animate <character-id> --action <action-id>
61
+ npx genex character animate <character-id> --action <first-id> --action <second-id> --no-wait
62
+ npx genex controller character --character <character-id>
63
+ ```
64
+
65
+ Ambiguous text queries print ranked candidates instead of silently spending
66
+ credits. Already-installed actions return without another debit. After an
67
+ animation job completes, rerun `genex controller character --character <id>`
68
+ to refresh `public/assets/meshy-character.json`; existing controller source is
69
+ preserved unless `--force` is explicitly used.
70
+
71
+ Load `$genex-threejs-character-controller` for the actual wiring. The install
72
+ command copies the shared controller plus `character/meshy/meshy-loader.ts`.
73
+ The manifest points at the current rigged model and compact animation-only GLBs
74
+ in R2, and carries the skeleton signature, locomotion slots, fallbacks,
75
+ durations, nominal speeds, trajectories, and gameplay requirements.
76
+
77
+ ## Compatibility and motion authority
78
+
79
+ - Meshy clips play only on the exact character revision whose skeleton
80
+ signature matches. There is no runtime retargeting in this lane.
81
+ - Rapier owns the player transform. Animation tracks are normalized
82
+ horizontally in place; never move the rendered character root separately.
83
+ - `controller-loop` clips follow controller-local intent and measured speed.
84
+ The state machine crossfades idle, walk, run, jump, directional slots, and
85
+ explicit fallbacks exactly as it does for VRM + UAL.
86
+ - `anchored-action` is a one-shot at the current controller pose.
87
+ - `planar-root-action` is not enabled for physics movement until the manifest
88
+ says `rootMotionValidated: true`. The current catalog does not validate root
89
+ motion, so do not drive a roll or lunge trajectory merely from its name.
90
+ - `choreography` needs game logic for the named prop, partner, ledge, ladder,
91
+ obstacle, seat, or other environment contract. A clip does not create that
92
+ mechanic.
93
+
94
+ For multiplayer, only the owning client advances the dynamic body and publishes
95
+ its pose plus compact animation state. Remote characters are visual-only: they
96
+ interpolate the owner's transform and replay the matching clip progress. Never
97
+ run a motion driver or a second physics controller for a remote player.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: genex-threejs-character-controller
3
- description: Add a tuned physics character controller to a Genex Three.js game with `npx genex controller character` dynamic-capsule walk/run/jump/crouch (slopes, stairs, moving platforms), follow camera, keyboard + touch input, animation binding. Animation packs (sword, pistol, magic, climb, swim, emotes…) install via `npx genex controller anims <tags>`. Use for any on-foot player or third-person movement, and whenever the user asks for ecctrl — this is that controller, ported to plain Three.js.
3
+ description: Add Genex's tuned ECCTRL-derived physics character controller with `npx genex controller character`: dynamic-capsule movement, follow camera, touch input, personal VRM + UAL animation, or an exact same-rig Meshy character. Use for every on-foot player or third-person movement request.
4
4
  ---
5
5
 
6
6
  # Genex Three.js Character Controller
@@ -19,15 +19,32 @@ animation retargeting, capsule auto-fit, foot IK) and the 12-clip core
19
19
  ~1.3 MB) into `public/assets/`. Need more — swords, pistols, magic, climbing,
20
20
  swimming, emotes? Install exactly what the game uses with
21
21
  `npx genex controller anims <tags|clip names…>` (see Animations below). The
22
- command also writes the player's avatar to `public/assets/avatar.vrm` —
22
+ default command also writes the player's avatar to `public/assets/avatar.vrm` —
23
23
  **your** avatar when you're signed in,
24
24
  otherwise a bundled CC0 default (attribution in `src/controllers/NOTICE.md`).
25
- The character plays as that VRM. The copied files are then owned by the game —
25
+ By default, the character plays as that VRM. The copied files are then owned by the game —
26
26
  edit them freely; re-running skips existing files unless `--force`. Do not write
27
27
  a character controller from scratch and do not swap in a kinematic-controller
28
28
  tutorial: this one is a real dynamic body that pushes crates, rides moving
29
29
  platforms, climbs stairs and slides on too-steep slopes out of the box.
30
30
 
31
+ Use the VRM + UAL lane above by default. When the game needs a custom generated
32
+ humanoid or an action unavailable in UAL, load `$genex-ai-character`, search
33
+ Meshy's library first, generate the character, and install it by character ID:
34
+
35
+ ```bash
36
+ npx genex animations search "rifle reload" --json
37
+ npx genex character "stylized sci-fi courier, practical clothing" --animation 466
38
+ npx genex controller character --character <character-id>
39
+ ```
40
+
41
+ That is a separate, **same-rig Meshy-native lane**. Its animation-only GLBs are
42
+ accepted only when their skeleton signature matches the active character
43
+ revision; this lane does no runtime retargeting. The shared ECCTRL-derived
44
+ dynamic controller stays authoritative for collision, grounding, facing, and
45
+ world translation. The animation layer poses the visual rig; it never
46
+ translates the visual root.
47
+
31
48
  Because these files are game-owned, never run `genex controller character --force` over an edited
32
49
  fork as a migration strategy. Install a fresh copy elsewhere and port only the named changes.
33
50
 
@@ -42,11 +59,13 @@ fork as a migration strategy. Install a fresh copy elsewhere and port only the n
42
59
  | `character/follow-camera.ts` | `FollowCamera` | orbit/zoom chase camera with collision pullback and an opt-in pointer-lock aim mode |
43
60
  | `character/keyboard-input.ts` | `KeyboardInput` | WASD/arrows/Shift/Space/F state, no per-frame polling setup |
44
61
  | `character/touch-joystick.ts` | `TouchJoystick`, `VirtualButton` | mobile controls |
45
- | `character/character-animations.ts` | `CharacterAnimations` | animation state machine + fuzzy clip binding + `playOneShot` + procedural fallback |
46
- | `character/animation-packs.ts` | `loadCharacterClips` | one call loads the core library + every installed `genex controller anims` pack, retargeted onto the VRM |
62
+ | `character/character-animations.ts` | `CharacterAnimations` | animation state machine, directional profiles, speed-matched cadence, `playOneShot`, procedural fallback |
63
+ | `character/animation-packs.ts` | `loadCharacterClips` | loads the core + installed UAL packs and retargets them to the active VRM |
64
+ | `character/meshy/meshy-loader.ts` | `loadMeshyCharacter` | loads a Meshy manifest, exact-signature model/clips, locomotion slots, and fallbacks |
65
+ | `character/motion-actions.ts` | `MotionActionDriver` | applies only validated planar trajectories through the physics controller, never the visual root |
47
66
  | `character/vrm/*` | `loadVrm`, `retargetClips`, `capsuleFromModel`, `FootIK` | load the VRM avatar, retarget library clips onto its humanoid rig, auto-fit the capsule, ground the feet |
48
67
 
49
- ## Minimal wiring
68
+ ## Minimal wiring: personal VRM lane
50
69
 
51
70
  ```ts
52
71
  import { PhysicsWorld } from "./controllers/shared/physics-world.ts";
@@ -147,7 +166,7 @@ preset table with provenance, the density/spring scaling rule, and the
147
166
 
148
167
  ## Animations + animation packs
149
168
 
150
- `CharacterAnimations` resolves nine locomotion states (IDLE / WALK / RUN /
169
+ `CharacterAnimations` resolves locomotion states (IDLE / WALK / RUN /
151
170
  CROUCH_IDLE / CROUCH_MOVE / JUMP_START / JUMP_IDLE / JUMP_FALL / JUMP_LAND)
152
171
  from the controller's live flags and crossfades mixer actions. Every OTHER
153
172
  clip — punches, sword swings, pistol fire, spells, sit, dance, hit reactions —
@@ -155,16 +174,23 @@ plays through `anims.playOneShot("Punch_Jab")`, which layers over locomotion
155
174
  and returns to it when done (punch-on-click is the default).
156
175
 
157
176
  The bundled library carries only the 12 core clips. **Install what the game's
158
- theme needs** from the 120-clip catalog, by tag or exact clip name:
177
+ theme needs** from the 120-clip UAL catalog by tag/name. For motion UAL does
178
+ not cover, use the separate Meshy character lane:
159
179
 
160
180
  ```bash
161
181
  npx genex controller anims sword pistol # a sword+shooter game
162
182
  npx genex controller anims stealth climb crawl # a ninja game
163
183
  npx genex controller anims --list # browse tags; --list <tag> for per-clip details
184
+ npx genex animations search "rifle reload" --json
185
+ npx genex character animate <character-id> --action <action-id>
186
+ npx genex controller character --character <character-id> # refresh the manifest
164
187
  ```
165
188
 
166
- Clips land in `public/assets/anims/` and `loadCharacterClips(vrm)` picks them
167
- up automatically re-run with more tags any time (additive). Read
189
+ Meshy search results include an action ID, stable key, preview, loop mode,
190
+ motion policy, controller slots, requirements, review status, and estimated
191
+ cost. The installed Meshy manifest supplies exact-signature clips, directional
192
+ slots, fallbacks, and cadence data. UAL clips still land in
193
+ `public/assets/anims/` and are additive. Read
168
194
  [references/animations.md](references/animations.md) for the tag catalog with
169
195
  genre hints, `playOneShot` options, overrides, foot IK, and remote-player
170
196
  animation.
@@ -227,6 +253,9 @@ different world.
227
253
  - To animate remotes, sync the six animation booleans (`isOnGround`,
228
254
  `isFalling`, `isMoving`, `runActive`, `jumpActive`, `crouchActive`) and feed
229
255
  them to a per-remote `CharacterAnimations` — see the animations reference.
256
+ - Only the owning client runs `MotionActionDriver`. Remotes play the same
257
+ one-shot event while following smoothed owner-authored position/rotation;
258
+ their animation mixer never moves them through the world.
230
259
  - Load `$genex-threejs-multiplayer` before writing any networking code; it is
231
260
  mandatory for any 2+ player game.
232
261
 
@@ -1,10 +1,10 @@
1
1
  # Character animations
2
2
 
3
3
  `CharacterAnimations` turns the controller's live flags into crossfaded
4
- `THREE.AnimationMixer` playback: nine states, alias-based clip binding that
5
- works with the bundled library, installed animation packs, Mixamo exports, or
6
- arbitrary rigs, and a procedural bob/lean fallback when a model has no usable
7
- clips at all.
4
+ `THREE.AnimationMixer` playback: core states, directional locomotion profiles,
5
+ alias-based clip binding, one-shots, and a procedural bob/lean fallback. The
6
+ default lane retargets UAL packs to VRM; the Meshy lane plays animation-only
7
+ clips only on the exact matching generated rig revision.
8
8
 
9
9
  ## The bundled assets + animation packs
10
10
 
@@ -70,6 +70,100 @@ cloth). `anims.update` takes the RAW render delta — pause/slow-motion go throu
70
70
  `anims.setPaused(true)` / `anims.setTimeScale(0.5)` (fade durations stretch with
71
71
  the time scale so slow motion doesn't pop).
72
72
 
73
+ ## Same-rig Meshy specialty lane
74
+
75
+ Use this lane when a game needs a custom generated humanoid or catalog depth
76
+ that the personal-VRM library does not cover: sports, dance, tactical actions,
77
+ prop interactions, or authored traversal. Load `$genex-ai-character`, search
78
+ first, generate the controller-ready character, and add exact action IDs:
79
+
80
+ ```bash
81
+ npx genex animations search "rifle reload" --json
82
+ npx genex character "stylized sci-fi courier, practical clothing" --animation 466
83
+ npx genex character animate <character-id> --action <action-id>
84
+ npx genex controller character --character <character-id>
85
+ ```
86
+
87
+ Search output exposes the numeric action ID, stable key, preview, loop mode,
88
+ motion policy, controller slots, gameplay requirements, review status, and
89
+ estimated cost. Use a returned ID; do not guess from a name. Search is free and
90
+ local. Character and animation generation print a Genex-credit quote before
91
+ enqueueing, and successful outputs are copied to permanent R2 URLs.
92
+
93
+ The install command writes `public/assets/meshy-character.json`. Wire that
94
+ manifest through the **same** physics controller and camera:
95
+
96
+ ```ts
97
+ import { loadMeshyCharacter } from "./controllers/character/meshy/meshy-loader.ts";
98
+
99
+ const native = await loadMeshyCharacter("./assets/meshy-character.json");
100
+ const fit = capsuleFromModel(native.scene);
101
+ const character = new CharacterController(physics.world, camera, {
102
+ ...characterPresets["default"].options,
103
+ ...fit,
104
+ position: { x: 0, y: 2, z: 0 },
105
+ });
106
+ character.root.add(native.scene);
107
+ native.scene.position.y = fit.modelOffsetY;
108
+ const anims = new CharacterAnimations(native.scene, native.clips, {
109
+ locomotionProfile: native.locomotionProfile,
110
+ });
111
+ ```
112
+
113
+ The loader rejects clips whose manifest skeleton signature differs from the
114
+ current model revision or whose tracks target missing bones. This is deliberate
115
+ same-rig playback, not runtime retargeting. After adding an action, rerun the
116
+ controller command to refresh the manifest; existing controller source remains
117
+ untouched unless `--force` is explicitly used.
118
+
119
+ ### Directional locomotion and cadence
120
+
121
+ Meshy manifests can fill slots such as `idle.default`, `walk.forward`,
122
+ `walk.backward`, `walk.left`, `walk.right`, their diagonal variants, matching
123
+ run/crouch slots, transitions, and jump phases. Under `lockForward`, the state
124
+ machine quantizes controller-local input into eight directions with hysteresis;
125
+ under free-facing movement it uses forward locomotion. Playback cadence follows
126
+ controller move speed divided by each band's authored nominal speed, clamped to
127
+ the profile range, so ECCTRL remains authoritative while feet track velocity.
128
+
129
+ ### Motion policies and authored root actions
130
+
131
+ Meshy results distinguish four policies:
132
+
133
+ - `controller-loop` — looping locomotion; ECCTRL supplies all translation.
134
+ - `anchored-action` — a one-shot that stays at the current controller pose.
135
+ - `planar-root-action` — a one-shot that may carry an extracted trajectory, but
136
+ it remains disabled until real output is validated.
137
+ - `choreography` — a multi-actor or constrained sequence; game code owns the
138
+ participants, anchors, props, and environment contract.
139
+
140
+ All installed GLBs stay horizontally in-place. A planar trajectory may request
141
+ movement only when its manifest entry says `rootMotionValidated: true`; current
142
+ Meshy manifests publish `false`, so these actions remain anchored. Once a real
143
+ canary validates extraction and synchronization, `MotionActionDriver` feeds the
144
+ trajectory into `CharacterController` immediately before
145
+ `character.update()`:
146
+
147
+ ```ts
148
+ import { loadMotionTrajectory, MotionActionDriver } from "./controllers/character/motion-actions.ts";
149
+
150
+ const trajectory = await loadMotionTrajectory("./assets/anims/<trajectory-file>.json");
151
+ const motion = new MotionActionDriver(character, anims);
152
+ motion.play(trajectory);
153
+
154
+ physics.onBeforeStep(() => {
155
+ character.setMovement(kb.getCharacterMovement());
156
+ motion.update(physics.world.timestep);
157
+ character.update();
158
+ });
159
+ ```
160
+
161
+ Resolve the URL from `meshy-character.json`'s `trajectoryUrl` rather than
162
+ guessing it, and refuse it unless `rootMotionValidated` is true. Fulfill every
163
+ declared requirement (`prop`, `partner`, or `environment`) before starting an
164
+ action. Only the owning client runs the driver; remotes play the event while
165
+ following the owner's smoothed transform.
166
+
73
167
  ### Foot IK (optional)
74
168
 
75
169
  `vrm/foot-ik.ts` plants feet on uneven ground (no skating/floating on steps and
@@ -176,18 +270,21 @@ run `--list <tag>` for per-clip descriptions before wiring one-shots:
176
270
  | `shop` | Counter enter/exit/idle/give/show/angry (~0.4 MB) | shopkeeper NPCs |
177
271
  | `drive` | Driving_Loop (~0.05 MB) | vehicles (see `$genex-threejs-vehicle-controllers`) |
178
272
 
179
- For looping poses that should **persist** (aiming, sitting, swimming) rather than
273
+ For legacy-library looping poses that should **persist** (aiming, sitting,
274
+ swimming) rather than
180
275
  play once, drive them through the public `anims.mixer` escape hatch instead; the
181
- `$genex-threejs-vehicle-controllers` skill shows the seated pattern. All clips
182
- are in-place (no root motion) the physics controller owns all translation.
276
+ `$genex-threejs-vehicle-controllers` skill shows the seated pattern. UAL clips
277
+ are in-place. Meshy planar actions remain anchored until their manifest
278
+ explicitly validates the extracted trajectory; in every case the physics
279
+ controller owns world translation.
183
280
 
184
- ## Binding arbitrary rigs (Mixamo included)
281
+ ## Binding arbitrary clip names on a compatible rig
185
282
 
186
283
  `buildClipMap(clips, overrides?)` resolves each state in priority order:
187
284
  explicit override (exact, then case-insensitive) → library exact names →
188
285
  case-insensitive → each alias as a case-insensitive **substring**, shortest
189
- matching clip name wins (so `Walk_Loop` beats `Walk_Bwd_Loop`, and Mixamo's
190
- `walking` beats `walking_backwards`). Aliases include `idle`, `walk`, `run`,
286
+ matching clip name wins (so `Walk_Loop` beats `Walk_Bwd_Loop`, and `walking`
287
+ beats `walking_backwards`). Aliases include `idle`, `walk`, `run`,
191
288
  `jog`, `sprint`, `crouch_idle`, `sneak`, `jump_start`, `takeoff`, `fall`,
192
289
  `land`, and a bare `jump` catch-all so a rig whose only airborne clip is
193
290
  "Jumping" still binds all four jump states. Unbound loop states chain
@@ -195,7 +292,9 @@ matching clip name wins (so `Walk_Loop` beats `Walk_Bwd_Loop`, and Mixamo's
195
292
  without crouch clips sneak in a standing pose instead of T-posing); unbound
196
293
  one-shots stay silent so the previous loop keeps playing.
197
294
 
198
- If a name refuses to bind, pass overrides:
295
+ This resolves names; it does **not** make incompatible skeletons compatible.
296
+ Only pass clips already retargeted to the VRM or authored for the exact Meshy
297
+ rig revision. If a compatible clip name refuses to bind, pass overrides:
199
298
 
200
299
  ```ts
201
300
  const anims = new CharacterAnimations(model, gltf.animations, {
@@ -260,11 +359,12 @@ remoteAnims.update(remoteState.flags, delta);
260
359
  resolves correctly (a missing flag reads as not-crouched).
261
360
 
262
361
  The mixer crossfades exactly as it does locally, so remote players animate
263
- correctly without simulating anything. In v1 every player loads the same
264
- `./assets/avatar.vrm` (the game owner's avatar), so remotes look like the owner
265
- load each remote through `loadVrm` + `retargetClips` just like the local one, and
266
- call `vrm.update(delta)` per remote each frame. Relay one-shot events (punch, hit)
267
- alongside the flags and call `remoteAnims.playOneShot("Hit_Chest")` on receipt.
362
+ correctly without simulating anything. Use the same visual lane as the owner:
363
+ `loadVrm` + retargeted UAL clips for a VRM game (and call `vrm.update(delta)`
364
+ per remote), or `loadMeshyCharacter` for a Meshy game. Relay one-shot events (punch,
365
+ hit, validated planar-action start) alongside the flags and call the matching
366
+ `remoteAnims.playOneShot(...)` on receipt. Never run `MotionActionDriver` for a
367
+ remote: its smoothed owner-authored transform is the sole movement authority.
268
368
  Full networking patterns: `$genex-threejs-multiplayer`.
269
369
 
270
370
  ## Gotchas
@@ -16,7 +16,8 @@ map, execution order, and acceptance gate.
16
16
  | Work needed | Load |
17
17
  | --- | --- |
18
18
  | 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, floating origins | `$genex-threejs-camera-direction` |
19
- | on-foot player movement: walk/run/jump/crouch, third-person character, slopes, stairs, moving platforms, animation binding, extra animation packs (sword/pistol/magic/climb/swim/emotes via `genex controller anims`) | `$genex-threejs-character-controller` |
19
+ | 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` |
20
+ | a custom generated playable humanoid or Meshy animation coverage beyond UAL: search, generate, rig, add exact action IDs, install the same-rig adapter | `$genex-ai-character` + `$genex-threejs-character-controller` |
20
21
  | the player drives or flies something: cars, drones, vehicle physics, gearbox, enter/exit between character and vehicle | `$genex-threejs-vehicle-controllers` |
21
22
  | anything falls, collides, gets pushed, or needs physics: Rapier world setup, colliders for meshes and GLBs, collision events | `$genex-threejs-physics-rapier` |
22
23
  | launch and docking timelines, procedural transform phases, springs, staging, rotating-frame alignment, debris motion | `$genex-threejs-procedural-animation` |
@@ -53,6 +54,14 @@ map, execution order, and acceptance gate.
53
54
  mentions sign-in, saves, progress, per-player state, a persistent world, or
54
55
  leaderboards. Multiplayer auth (`getColyseusAuth`) comes from it too.
55
56
 
57
+ **Character-animation routing:** use the existing VRM + UAL character
58
+ controller by default. Use `npx genex character` when the game needs a custom
59
+ generated humanoid or an action unavailable in UAL. Load `$genex-ai-character`,
60
+ search Meshy actions first with `npx genex animations search "<intent>" --json`,
61
+ and use returned action IDs; never invent IDs or move the visual root. In both
62
+ lanes, the ECCTRL-derived character controller owns collision and world
63
+ translation.
64
+
56
65
  **UI is mandatory routing:** every game has an interface — load
57
66
  `$genex-threejs-game-ui` for every NEW game and run its "Plan the UI first" gate
58
67
  (screen inventory, one shared style brief, tier decisions) right after the game
@@ -113,7 +122,8 @@ commit. Each skill has the exact Three.js loader code.
113
122
 
114
123
  | Work needed | Generate with | Skill |
115
124
  | --- | --- | --- |
116
- | a specific prop / item / character / vehicle as a real mesh | `npx genex model "<prompt>"` | `$genex-ai-model` |
125
+ | a specific prop / item / non-playable static figure / vehicle body as a real mesh | `npx genex model "<prompt>"` | `$genex-ai-model` |
126
+ | a custom generated playable humanoid with a rig and controller-ready animation pack | `npx genex character "<prompt>"` | `$genex-ai-character` |
117
127
  | a described 360° sky / backdrop + image-based lighting | `npx genex skybox "<prompt>"` | `$genex-ai-skybox` |
118
128
  | a specific sound effect tied to an event | `npx genex sfx "<prompt>"` | `$genex-ai-sfx` |
119
129
  | a photoreal surface/material on a mesh or terrain | `npx genex texture "<prompt>" [--terrain]` | `$genex-ai-texture` |