@genex-ai/cli-demo 0.37.0 → 0.38.0

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.
@@ -1,37 +1,43 @@
1
1
  # Character animations
2
2
 
3
3
  `CharacterAnimations` turns the controller's live flags into crossfaded
4
- `THREE.AnimationMixer` playback: seven states, alias-based clip binding that
5
- works with the bundled library, Mixamo exports, or arbitrary rigs, and a
6
- procedural bob/lean fallback when a model has no usable clips at all.
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.
7
8
 
8
- ## The bundled assets
9
+ ## The bundled assets + animation packs
9
10
 
10
11
  `npx genex controller character` sets the game up to play as a **VRM avatar**:
11
12
 
12
13
  - `public/assets/avatar.vrm` — the player's avatar (yours when signed in, else a
13
14
  bundled CC0 default). Always present; always one path.
14
- - `public/assets/animation-library.glb` (6.4 MB) — 46 clips on a shared
15
- Quaternius rig (provenance in `src/controllers/NOTICE.md`).
15
+ - `public/assets/animation-library.glb` (~1.3 MB) — the 12-clip core
16
+ (idle/walk/jog/sprint, the jump trio, crouch idle+move, hit, death, interact)
17
+ on a shared Quaternius rig (provenance in `src/controllers/NOTICE.md`).
18
+ - `public/assets/anims/*.glb` — OPTIONAL per-clip packs installed by
19
+ `npx genex controller anims <tags|clip names…>` from the 120-clip Quaternius
20
+ Universal Animation Library Pro catalog (see the tag catalog below). Re-runs
21
+ are additive; `--reset` starts over; a `manifest.json` alongside lists what's
22
+ installed.
16
23
 
17
24
  VRM helpers live in `src/controllers/character/vrm/`. Install three-vrm once:
18
25
  `npm i @pixiv/three-vrm`.
19
26
 
20
- Load the avatar, retarget the library clips onto its humanoid rig, and auto-fit
21
- the capsule all with **relative** paths so the published game works under its
22
- subpath:
27
+ Load the avatar, then EVERYTHING the game has via `loadCharacterClips` core
28
+ library + installed packs, each retargeted onto the avatar's humanoid rig
29
+ all with **relative** paths so the published game works under its subpath:
23
30
 
24
31
  ```ts
25
- import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
26
32
  import { loadVrm } from "./controllers/character/vrm/vrm-loader.ts";
27
- import { retargetClips } from "./controllers/character/vrm/vrm-retarget.ts";
33
+ import { loadCharacterClips } from "./controllers/character/animation-packs.ts";
28
34
  import { capsuleFromModel } from "./controllers/character/vrm/capsule-fit.ts";
29
35
  import { CharacterController } from "./controllers/character/character-controller.ts";
30
36
  import { CharacterAnimations } from "./controllers/character/character-animations.ts";
31
37
  import { characterPresets } from "./controllers/character/presets.ts";
32
38
 
33
39
  const { scene, vrm } = await loadVrm("./assets/avatar.vrm");
34
- const lib = await new GLTFLoader().loadAsync("./assets/animation-library.glb");
40
+ const clips = await loadCharacterClips(vrm); // core + every installed pack, retargeted
35
41
 
36
42
  // capsuleFromModel derives the collider from the avatar's bounds — no manual
37
43
  // per-avatar tuning even as heights/proportions vary across the library.
@@ -44,11 +50,13 @@ const character = new CharacterController(physics.world, camera, {
44
50
  character.root.add(scene);
45
51
  scene.position.y = fit.modelOffsetY; // root = capsule CENTER; drop the model so feet touch the floor
46
52
 
47
- // retargetClips maps the Quaternius rig onto the VRM's normalized humanoid rig
48
- // (VRM 0.x and 1.0 alike); the result feeds CharacterAnimations unchanged.
49
- const anims = new CharacterAnimations(scene, retargetClips(vrm, lib.scene, lib.animations));
53
+ const anims = new CharacterAnimations(scene, clips);
50
54
  ```
51
55
 
56
+ (Advanced: to load a single GLB by hand, `retargetClips(vrm, gltf.scene,
57
+ gltf.animations)` from `vrm/vrm-retarget.ts` is what `loadCharacterClips` uses
58
+ internally — it auto-detects the source rig per file.)
59
+
52
60
  Per render frame, **after** `physics.step(delta)`:
53
61
 
54
62
  ```ts
@@ -73,11 +81,27 @@ import { FootIK } from "./controllers/character/vrm/foot-ik.ts";
73
81
  const footIK = new FootIK(vrm, (foot) => {
74
82
  const hit = physics.world.castRay(
75
83
  new RAPIER.Ray({ x: foot.x, y: foot.y + 0.5, z: foot.z }, { x: 0, y: -1, z: 0 }),
76
- 1.5, true);
84
+ 1.5, true, RAPIER.QueryFilterFlags.EXCLUDE_SENSORS,
85
+ undefined, undefined, character.body); // exclude the character's own capsule
77
86
  return hit ? foot.y + 0.5 - hit.timeOfImpact : null;
87
+ }, {
88
+ isActive: () => character.isOnGround, // keep the jump pose while airborne
89
+ allowReachDown: () => !anims.oneShotActive, // REQUIRED with one-shots: they are
90
+ // choreography — without this a firing stance over a step edge drags the pelvis
91
+ // down into the staircase. While gated, feet are only lifted as much as needed
92
+ // to stay out of the step under them (anti dig-in).
78
93
  });
79
- // each frame, after vrm.update(delta):
80
- footIK.update(delta);
94
+ ```
95
+
96
+ ORDER IS LOAD-BEARING — foot IK poses the VRM's *normalized* bones, which
97
+ `vrm.update()` then copies onto the render mesh. Run it BETWEEN the two (after
98
+ `vrm.update` it has no visible effect at all — the next frame's mixer tick
99
+ overwrites it before the copy):
100
+
101
+ ```ts
102
+ anims.update(character, delta); // 1. mixer poses the normalized rig
103
+ footIK.update(delta); // 2. plant the feet on that pose
104
+ vrm.update(delta); // 3. copy normalized -> render mesh + spring bones
81
105
  ```
82
106
 
83
107
  ## States and default clips
@@ -89,14 +113,16 @@ The pure resolver (`resolveAnimationState`) maps controller flags to one of:
89
113
  | `IDLE` | `Idle_Loop` | starts playing immediately on construction |
90
114
  | `WALK` | `Walk_Loop` | |
91
115
  | `RUN` | `Jog_Fwd_Loop` | **the default run clip**; `Sprint_Loop` also binds via alias — force it with an override if the user wants an all-out sprint look |
116
+ | `CROUCH_IDLE` | `Crouch_Idle_Loop` | while `crouchActive`; degrades to `IDLE` on rigs without crouch clips |
117
+ | `CROUCH_MOVE` | `Crouch_Fwd_Loop` | crouched + moving; degrades to `WALK` |
92
118
  | `JUMP_START` | `Jump_Start` | one-shot, played at 1.6× so it finishes inside the hop |
93
119
  | `JUMP_IDLE` | `Jump_Loop` | airborne, moving up |
94
120
  | `JUMP_FALL` | `Jump_Loop` | airborne, moving down (shares the clip — no restart mid-air) |
95
121
  | `JUMP_LAND` | `Jump_Land` | one-shot |
96
122
 
97
- ## One-shot actions + the 46-clip catalog
123
+ ## One-shot actions + the 120-clip pack catalog
98
124
 
99
- The seven states above cover locomotion. **Every other clip** plays through
125
+ The nine states above cover locomotion. **Every other clip** plays through
100
126
  `anims.playOneShot(clipName, options?)`: it crossfades the clip over the current
101
127
  motion, plays it once, then hands control back to the state machine. Returns
102
128
  `false` if the clip name isn't in the set you passed to the constructor.
@@ -116,31 +142,44 @@ addEventListener("pointerdown", () => {
116
142
  `options`: `fadeIn` (default 0.1 s), `timeScale`, `clamp` (hold the final pose —
117
143
  for deaths), `onDone`.
118
144
 
119
- Full catalog (clip motion reach for it when):
145
+ The 12 core clips are always available; everything else comes from
146
+ `npx genex controller anims <selectors…>` — selectors are **tags** (install a
147
+ themed set) or **exact clip names** (cherry-pick), freely mixed:
120
148
 
121
- | Clip(s) | Motion | Use for |
149
+ ```bash
150
+ npx genex controller anims sword pistol # tags: a sword + shooter game
151
+ npx genex controller anims stealth Celebration # a tag + one exact clip
152
+ npx genex controller anims --list # all tags with sizes
153
+ npx genex controller anims --list sword magic # per-clip durations + descriptions
154
+ ```
155
+
156
+ Tag catalog (tag → clips → reach for it when). Pick tags from the game's theme;
157
+ run `--list <tag>` for per-clip descriptions before wiring one-shots:
158
+
159
+ | Tag | Clips (≈size) | Use for |
122
160
  | --- | --- | --- |
123
- | `Punch_Jab` / `Punch_Cross` | quick / heavy punch | melee, click-to-attack (default) |
124
- | `Punch_Enter` | raise fists | enter a fighting stance |
125
- | `Hit_Chest` / `Hit_Head` | flinch | taking damage (multiplayer-friendly reactions) |
126
- | `Death01` | collapse | death pair with `clamp: true` |
127
- | `Sword_Idle` / `Sword_Attack` / `Sword_Attack_RM` | ready / swing / swing+step | melee weapons (`_RM` = root motion, travels) |
128
- | `Pistol_Idle_Loop` / `Pistol_Aim_Up` / `_Neutral` / `_Down` | aim poses | shooters pick by camera pitch |
129
- | `Pistol_Shoot` / `Pistol_Reload` | fire / reload | shooter actions |
130
- | `Spell_Simple_Enter` / `_Idle_Loop` / `_Shoot` / `_Exit` | cast cycle | magic / RPG |
131
- | `Roll` / `Roll_RM` | dodge roll | dodge (`_RM` travels) |
132
- | `Interact` / `PickUp_Table` / `Fixing_Kneeling` | reach / pick up / kneel-work | pickups, levers, crafting |
133
- | `Sitting_Enter` / `_Idle_Loop` / `_Talking_Loop` / `_Exit` | sit / sit-idle / chat / stand | seats, vehicles, dialogue |
134
- | `Dance_Loop` / `Idle_Talking_Loop` / `Idle_Torch_Loop` | dance / gesture / torch idle | emotes, NPCs, ambience |
135
- | `Push_Loop` / `Crouch_Idle_Loop` / `Crouch_Fwd_Loop` | push / crouch / crouch-walk | pushing, stealth |
136
- | `Swim_Idle_Loop` / `Swim_Fwd_Loop` | tread / swim | water |
137
- | `Walk_Formal_Loop` | stiff walk | override `WALK` for a formal gait |
138
- | `Driving_Loop` | seated at a wheel | vehicles (see `$genex-threejs-vehicle-controllers`) |
139
- | `A_TPose` | rest pose | reference only |
161
+ | `core` | Idle/Walk/Jog/Sprint loops, Jump trio, Crouch idle+fwd, Hit_Chest, Death01, Interact | already bundled never needs installing |
162
+ | `locomotion-extra` | Jog strafe/diagonal/lean ×10, Turn90_L/R, Walk_Formal_Loop, Sprint_Enter/Exit (~1 MB) | 8-way strafe rigs, formal NPC gaits, sprint transitions |
163
+ | `stealth` | Crouch_Enter/Exit + crouched strafe/diagonal/backward ×9 (~0.6 MB) | stealth games (the built-in crouch only needs core; these add direction variety + transitions) |
164
+ | `crawl` | Crawl enter/exit/idle + 4 directions (~0.5 MB) | prone crawling, vents, tunnels |
165
+ | `climb` | Climb enter/exit/idle, up/down/left/right, ClimbLedge (~0.6 MB) | ladders, walls, parkour |
166
+ | `parkour` | BackFlip, Roll, Dodge_Left/Right (~0.3 MB) | dodges and flips |
167
+ | `brawl` | Punch_Jab/Cross, Kick, PunchKick_Enter/Exit (~0.35 MB) | fist fighting |
168
+ | `sword` | Sword_Enter/Exit/Idle/Attack/Attack_Standing (~0.35 MB) | melee weapons |
169
+ | `pistol` | Pistol_Idle_Loop, Aim_Up/Neutral/Down (pick by camera pitch), Shoot, Reload (~0.3 MB) | shooters |
170
+ | `magic` | Spell_Simple + Spell_Double enter/exit/idle/shoot cycles (~0.5 MB) | casters Double's shoot is a channel/beam loop |
171
+ | `damage` | Hit_Head/Shoulder_L/R/Stomach, Death02 (~0.3 MB) | directional hit reactions beyond the core pair |
172
+ | `swim` | Swim_Idle_Loop, Swim_Fwd_Loop (~0.15 MB) | water |
173
+ | `sit` | Sitting enter/exit + 5 idles, GroundSit set (~0.7 MB) | seats, campfires, dialogue |
174
+ | `emote` | Celebration, Crying, Dance_Loop, Drink, talking/tired/look-around idles, Rock/Paper/Scissors (~0.7 MB) | emotes, NPCs, minigames |
175
+ | `interact` | PickUp_Kneeling/Table, Fixing_Kneeling, Push enter/exit/loop (~0.4 MB) | pickups, levers, crafting, pushing |
176
+ | `shop` | Counter enter/exit/idle/give/show/angry (~0.4 MB) | shopkeeper NPCs |
177
+ | `drive` | Driving_Loop (~0.05 MB) | vehicles (see `$genex-threejs-vehicle-controllers`) |
140
178
 
141
179
  For looping poses that should **persist** (aiming, sitting, swimming) rather than
142
180
  play once, drive them through the public `anims.mixer` escape hatch instead; the
143
- `$genex-threejs-vehicle-controllers` skill shows the seated pattern.
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.
144
183
 
145
184
  ## Binding arbitrary rigs (Mixamo included)
146
185
 
@@ -149,10 +188,12 @@ explicit override (exact, then case-insensitive) → library exact names →
149
188
  case-insensitive → each alias as a case-insensitive **substring**, shortest
150
189
  matching clip name wins (so `Walk_Loop` beats `Walk_Bwd_Loop`, and Mixamo's
151
190
  `walking` beats `walking_backwards`). Aliases include `idle`, `walk`, `run`,
152
- `jog`, `sprint`, `jump_start`, `takeoff`, `fall`, `land`, and a bare `jump`
153
- catch-all so a rig whose only airborne clip is "Jumping" still binds all four
154
- jump states. Unbound loop states chain (RUN↔WALK, JUMP_IDLE↔JUMP_FALL);
155
- unbound one-shots stay silent so the previous loop keeps playing.
191
+ `jog`, `sprint`, `crouch_idle`, `sneak`, `jump_start`, `takeoff`, `fall`,
192
+ `land`, and a bare `jump` catch-all so a rig whose only airborne clip is
193
+ "Jumping" still binds all four jump states. Unbound loop states chain
194
+ (RUN↔WALK, JUMP_IDLE↔JUMP_FALL, CROUCH_IDLE→IDLE, CROUCH_MOVE→WALK rigs
195
+ without crouch clips sneak in a standing pose instead of T-posing); unbound
196
+ one-shots stay silent so the previous loop keeps playing.
156
197
 
157
198
  If a name refuses to bind, pass overrides:
158
199
 
@@ -195,9 +236,9 @@ const anims = new CharacterAnimations(placeholderMesh, []); // procedural fallba
195
236
 
196
237
  ## Remote players (multiplayer)
197
238
 
198
- The snapshot type is structural — **anything** with the five booleans works,
239
+ The snapshot type is structural — **anything** with the six booleans works,
199
240
  which is exactly what remote players need. Remote players have no physics and
200
- no `CharacterController` (see the SKILL's multiplayer rule): sync the five
241
+ no `CharacterController` (see the SKILL's multiplayer rule): sync the six
201
242
  flags from the owner and feed them straight in.
202
243
 
203
244
  ```ts
@@ -208,12 +249,16 @@ const flags = {
208
249
  isMoving: character.isMoving,
209
250
  runActive: character.runActive,
210
251
  jumpActive: character.jumpActive,
252
+ crouchActive: character.crouchActive,
211
253
  };
212
254
 
213
255
  // Receiver: one CharacterAnimations per remote model, fed the synced flags.
214
256
  remoteAnims.update(remoteState.flags, delta);
215
257
  ```
216
258
 
259
+ `crouchActive` is additive: an older game syncing only five flags still
260
+ resolves correctly (a missing flag reads as not-crouched).
261
+
217
262
  The mixer crossfades exactly as it does locally, so remote players animate
218
263
  correctly without simulating anything. In v1 every player loads the same
219
264
  `./assets/avatar.vrm` (the game owner's avatar), so remotes look like the owner —
@@ -64,6 +64,9 @@ tip-overs mean the `autoBalance*` pair is too soft.
64
64
  | "drifts sideways through turns" | raise `rejectVelFactor` toward 1 (default 1; `ice-slide` lowers it to 0.2) |
65
65
  | "too slow / too fast" | `maxWalkVel` (default 2) / `maxRunVel` (default 5) |
66
66
  | "run should be hold, not toggle" | `enableToggleRun: false` (default true = Shift toggles) |
67
+ | "sneaks too fast / too slow" | `crouchSpeedRatio` (default 0.45 × `maxWalkVel`) |
68
+ | "can't fit under the obstacle when crouched" | lower `crouchCapsuleScale` (default 0.6 — the crouched capsule's cylinder half-height as a fraction of standing; the head drops by `2*(1-scale)*capsuleHalfHeight`) |
69
+ | "crouch should be hold, not toggle" | `crouchMode: "hold"` (default `"toggle"` — C flips it) |
67
70
  | "no control in the air" | raise `airDragFactor` (default 0.1) |
68
71
  | "falls too fast at terminal velocity" | `fallingMaxVel` (default 20 m/s) |
69
72
  | "leans too much when running" | lower `moveImpulsePointOffset` (default 0.5; 0 = no lean) |
@@ -224,7 +224,7 @@ remote simulation:
224
224
 
225
225
  The vendored controllers expose their pose as `currPos` (a `THREE.Vector3`) and `currQuat` (a
226
226
  `THREE.Quaternion`) — **not** `.position` / `.quaternion` — plus boolean state getters. Character
227
- animation is driven by **five booleans**, not a single enum; publish the booleans and let remotes
227
+ animation is driven by **six booleans**, not a single enum; publish the booleans and let remotes
228
228
  reconstruct the animation. There is **no** `rig.animState`, no `remoteAnimator.play(...)`, and no
229
229
  vehicle `wheelSpinPhase` getter.
230
230
 
@@ -235,9 +235,9 @@ const q = character.currQuat; // THREE.Quaternion
235
235
  room.me.set({
236
236
  x: r2(p.x), y: r2(p.y), z: r2(p.z),
237
237
  q: [r2(q.x), r2(q.y), r2(q.z), r2(q.w)], // quaternion array — never a scalar yaw
238
- // character animation = 5 booleans (the CharacterController exposes each as a getter):
238
+ // character animation = 6 booleans (the CharacterController exposes each as a getter):
239
239
  g: character.isOnGround, f: character.isFalling, m: character.isMoving,
240
- r: character.runActive, j: character.jumpActive,
240
+ r: character.runActive, j: character.jumpActive, c: character.crouchActive,
241
241
  });
242
242
 
243
243
  // Remote players: a VISUAL-ONLY avatar — NO Rapier body, NO controller instance for remotes.
@@ -248,15 +248,16 @@ remoteAvatar.group.position.set(pl.state.x, pl.state.y, pl.state.z);
248
248
  remoteAvatar.group.quaternion.fromArray(pl.state.q);
249
249
  const raw = pl.stateRaw; // discrete flags: read RAW, never smoothed
250
250
  remoteAvatar.update(
251
- { isOnGround: !!raw.g, isFalling: !!raw.f, isMoving: !!raw.m, runActive: !!raw.r, jumpActive: !!raw.j },
251
+ { isOnGround: !!raw.g, isFalling: !!raw.f, isMoving: !!raw.m, runActive: !!raw.r, jumpActive: !!raw.j, crouchActive: !!raw.c },
252
252
  dt,
253
253
  );
254
254
  ```
255
255
 
256
256
  What to publish per controller:
257
257
 
258
- - **character**: `currPos` → `x/y/z`, `currQuat` → `q`, and the five booleans above
259
- (`isOnGround`/`isFalling`/`isMoving`/`runActive`/`jumpActive`). Remotes rebuild the animation
258
+ - **character**: `currPos` → `x/y/z`, `currQuat` → `q`, and the six booleans above
259
+ (`isOnGround`/`isFalling`/`isMoving`/`runActive`/`jumpActive`/`crouchActive` the last is
260
+ additive; a peer syncing only five still animates, minus crouch). Remotes rebuild the animation
260
261
  with `avatar.update(flags, dt)` — see the `genex-threejs-character-controller` animations
261
262
  reference for the flag set (single source of truth; don't invent a `play(anim)` call).
262
263
  - **vehicle**: body `currPos` → `x/y/z` + `currQuat` → `q`. For visible steering, publish the
@@ -16,7 +16,7 @@ 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, floating origins | `$genex-threejs-camera-direction` |
19
- | on-foot player movement: walk/run/jump, third-person character, slopes, stairs, moving platforms, animation binding | `$genex-threejs-character-controller` |
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` |
20
20
  | the player drives or flies something: cars, drones, vehicle physics, gearbox, enter/exit between character and vehicle | `$genex-threejs-vehicle-controllers` |
21
21
  | anything falls, collides, gets pushed, or needs physics: Rapier world setup, colliders for meshes and GLBs, collision events | `$genex-threejs-physics-rapier` |
22
22
  | launch and docking timelines, procedural transform phases, springs, staging, rotating-frame alignment, debris motion | `$genex-threejs-procedural-animation` |
@@ -109,8 +109,9 @@ a seated pilot; that is game glue on the `onHandoff` seam. The pattern: keep
109
109
  a reference to the character's visual model, re-parent it into a seat anchor
110
110
  under the vehicle's chassis object, and play the seat clips through a
111
111
  dedicated `THREE.AnimationMixer` (the locomotion state machine is idle while
112
- parked). The clips ship in `animation-library.glb` (installed by
113
- `npx genex controller character`).
112
+ parked). The seat clips are animation-pack clips — install them once with
113
+ `npx genex controller anims sit drive` (they land in `public/assets/anims/`
114
+ and `loadCharacterClips` from the character controller picks them up).
114
115
 
115
116
  ```ts
116
117
  const seatMixer = new THREE.AnimationMixer(characterModel);