@genex-ai/cli-demo 0.36.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,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 movement (walk/run/jump, slopes, stairs, moving platforms), follow camera, keyboard + touch input, and animation binding. 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 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.
4
4
  ---
5
5
 
6
6
  # Genex Three.js Character Controller
@@ -14,9 +14,13 @@ npm i @dimforge/rapier3d-compat @pixiv/three-vrm # three is already in the sca
14
14
 
15
15
  The command vendors tested, tuned controller code into the game: TypeScript
16
16
  modules into `src/controllers/` (including `character/vrm/` — VRM loading,
17
- animation retargeting, capsule auto-fit, foot IK) and the 46-clip
18
- `animation-library.glb` into `public/assets/`. It also writes the player's
19
- avatar to `public/assets/avatar.vrm` **your** avatar when you're signed in,
17
+ animation retargeting, capsule auto-fit, foot IK) and the 12-clip core
18
+ `animation-library.glb` (idle/walk/run/jump/crouch + hit/death/interact,
19
+ ~1.3 MB) into `public/assets/`. Need more swords, pistols, magic, climbing,
20
+ swimming, emotes? Install exactly what the game uses with
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` —
23
+ **your** avatar when you're signed in,
20
24
  otherwise a bundled CC0 default (attribution in `src/controllers/NOTICE.md`).
21
25
  The character plays as that VRM. The copied files are then owned by the game —
22
26
  edit them freely; re-running skips existing files unless `--force`. Do not write
@@ -36,27 +40,28 @@ platforms, climbs stairs and slides on too-steep slopes out of the box.
36
40
  | `character/keyboard-input.ts` | `KeyboardInput` | WASD/arrows/Shift/Space/F state, no per-frame polling setup |
37
41
  | `character/touch-joystick.ts` | `TouchJoystick`, `VirtualButton` | mobile controls |
38
42
  | `character/character-animations.ts` | `CharacterAnimations` | animation state machine + fuzzy clip binding + `playOneShot` + procedural fallback |
39
- | `character/vrm/*` | `loadVrm`, `retargetClips`, `capsuleFromModel`, `FootIK` | load the VRM avatar, retarget the 46 UAL clips onto its humanoid rig, auto-fit the capsule, ground the feet |
43
+ | `character/animation-packs.ts` | `loadCharacterClips` | one call loads the core library + every installed `genex controller anims` pack, retargeted onto the VRM |
44
+ | `character/vrm/*` | `loadVrm`, `retargetClips`, `capsuleFromModel`, `FootIK` | load the VRM avatar, retarget library clips onto its humanoid rig, auto-fit the capsule, ground the feet |
40
45
 
41
46
  ## Minimal wiring
42
47
 
43
48
  ```ts
44
- import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
45
49
  import { PhysicsWorld } from "./controllers/shared/physics-world.ts";
46
50
  import { CharacterController } from "./controllers/character/character-controller.ts";
47
51
  import { CharacterAnimations } from "./controllers/character/character-animations.ts";
52
+ import { loadCharacterClips } from "./controllers/character/animation-packs.ts";
48
53
  import { characterPresets } from "./controllers/character/presets.ts";
49
54
  import { FollowCamera } from "./controllers/character/follow-camera.ts";
50
55
  import { KeyboardInput } from "./controllers/character/keyboard-input.ts";
51
56
  import { loadVrm } from "./controllers/character/vrm/vrm-loader.ts";
52
- import { retargetClips } from "./controllers/character/vrm/vrm-retarget.ts";
53
57
  import { capsuleFromModel } from "./controllers/character/vrm/capsule-fit.ts";
54
58
 
55
59
  const physics = await PhysicsWorld.create(); // nothing RAPIER-related may run before this resolves
56
60
 
57
- // Load the player's avatar + the animation library, then retarget onto the VRM.
61
+ // Load the player's avatar, then every animation the game has (the bundled core
62
+ // library + any packs installed by `genex controller anims`), retargeted onto the VRM.
58
63
  const { scene: avatar, vrm } = await loadVrm("./assets/avatar.vrm");
59
- const lib = await new GLTFLoader().loadAsync("./assets/animation-library.glb");
64
+ const clips = await loadCharacterClips(vrm);
60
65
 
61
66
  const fit = capsuleFromModel(avatar); // collider fits THIS avatar's bounds
62
67
  const character = new CharacterController(physics.world, camera, {
@@ -70,7 +75,7 @@ character.root.add(avatar); // parent the avatar under the c
70
75
  avatar.position.y = fit.modelOffsetY; // root = capsule CENTER; drop the model so feet touch the floor
71
76
  physics.registerBody(character.body, character.root); // root now follows the body, interpolated
72
77
 
73
- const anims = new CharacterAnimations(avatar, retargetClips(vrm, lib.scene, lib.animations));
78
+ const anims = new CharacterAnimations(avatar, clips);
74
79
  addEventListener("pointerdown", () => anims.playOneShot("Punch_Jab")); // punch on click
75
80
 
76
81
  const kb = new KeyboardInput();
@@ -127,17 +132,39 @@ preset table with provenance, the density/spring scaling rule, and the
127
132
  - The capsule ships with friction `-0.5` **on purpose** (grip is synthesized by
128
133
  the controller). Do not "fix" it to a positive value.
129
134
 
130
- ## Animations
135
+ ## Animations + animation packs
131
136
 
132
- `CharacterAnimations` resolves seven locomotion states (IDLE / WALK / RUN /
133
- JUMP_START / JUMP_IDLE / JUMP_FALL / JUMP_LAND) from the controller's live flags
134
- and crossfades mixer actions. Every OTHER library clip — punches, sword swings,
135
- pistol fire, spells, sit, dance, hit reactions — plays through
136
- `anims.playOneShot("Punch_Jab")`, which layers over locomotion and returns to it
137
- when done (punch-on-click is the default). Read
138
- [references/animations.md](references/animations.md) for the VRM load + retarget
139
- wiring, the **full 46-clip catalog** with genre hints, `playOneShot` options,
140
- overrides, foot IK, and remote-player animation.
137
+ `CharacterAnimations` resolves nine locomotion states (IDLE / WALK / RUN /
138
+ CROUCH_IDLE / CROUCH_MOVE / JUMP_START / JUMP_IDLE / JUMP_FALL / JUMP_LAND)
139
+ from the controller's live flags and crossfades mixer actions. Every OTHER
140
+ clip — punches, sword swings, pistol fire, spells, sit, dance, hit reactions —
141
+ plays through `anims.playOneShot("Punch_Jab")`, which layers over locomotion
142
+ and returns to it when done (punch-on-click is the default).
143
+
144
+ The bundled library carries only the 12 core clips. **Install what the game's
145
+ theme needs** from the 120-clip catalog, by tag or exact clip name:
146
+
147
+ ```bash
148
+ npx genex controller anims sword pistol # a sword+shooter game
149
+ npx genex controller anims stealth climb crawl # a ninja game
150
+ npx genex controller anims --list # browse tags; --list <tag> for per-clip details
151
+ ```
152
+
153
+ Clips land in `public/assets/anims/` and `loadCharacterClips(vrm)` picks them
154
+ up automatically — re-run with more tags any time (additive). Read
155
+ [references/animations.md](references/animations.md) for the tag catalog with
156
+ genre hints, `playOneShot` options, overrides, foot IK, and remote-player
157
+ animation.
158
+
159
+ ## Crouch (built in)
160
+
161
+ `C` toggles crouch (capsule shrinks, speed drops to `crouchSpeedRatio ×
162
+ maxWalkVel`, CROUCH_IDLE/CROUCH_MOVE play). Standing back up is
163
+ ceiling-checked: under a low obstacle the character STAYS crouched and pops up
164
+ automatically once clear; jumping while crouched requests a stand instead of
165
+ jumping. `crouchMode: "hold"` makes it hold-to-crouch;
166
+ `character.setCrouch(bool)` and `character.crouchActive` are the programmatic
167
+ hooks (that's also how a touch button wires in — see Mobile below).
141
168
 
142
169
  ## Mobile: TouchJoystick + VirtualButton (wire by default)
143
170
 
@@ -151,6 +178,11 @@ import { TouchJoystick, VirtualButton } from "./controllers/character/touch-joys
151
178
 
152
179
  const joy = new TouchJoystick({ wrapperStyle: { left: "20px", bottom: "20px" } }); // position is REQUIRED
153
180
  const btnJump = new VirtualButton({ label: "Jump", wrapperStyle: { right: "30px", bottom: "30px" } });
181
+ const btnCrouch = new VirtualButton({
182
+ label: "Crouch",
183
+ wrapperStyle: { right: "100px", bottom: "30px" },
184
+ onPress: () => character.setCrouch(!character.crouchActive), // tap = toggle
185
+ });
154
186
 
155
187
  physics.onBeforeStep(() => {
156
188
  character.setMovement({
@@ -178,8 +210,9 @@ players' physics locally guarantees divergence — every client would compute a
178
210
  different world.
179
211
 
180
212
  - Publish your own `currPos` + yaw on the fixed 10–20 Hz tick, not per frame.
181
- - To animate remotes, sync the five animation booleans and feed them to a
182
- per-remote `CharacterAnimations` see the animations reference.
213
+ - To animate remotes, sync the six animation booleans (`isOnGround`,
214
+ `isFalling`, `isMoving`, `runActive`, `jumpActive`, `crouchActive`) and feed
215
+ them to a per-remote `CharacterAnimations` — see the animations reference.
183
216
  - Load `$genex-threejs-multiplayer` before writing any networking code; it is
184
217
  mandatory for any 2+ player game.
185
218
 
@@ -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` |
@@ -60,13 +60,12 @@ recipes (sports/ball, shooter, co-op).
60
60
  ## Real (AI-generated) assets — `npx genex` commands
61
61
 
62
62
  When the user wants a **specific, recognizable asset** (a named object, a described
63
- sky, a particular sound or surface) rather than something authored in code, generate
64
- it with an `npx genex` command (run inside the project, where the `@genex-ai/cli-demo`
65
- dev dependency makes `genex` resolve to the right CLI). Each drops a real file into
66
- `public/assets/<kind>/` so it ships with the built game load it at runtime as
67
- `./assets/...` (the `public/` prefix is stripped when served; a bare `./assets/` dir
68
- would be dropped from the production build). Each skill has the exact Three.js
69
- loader code.
63
+ sky, a particular sound, surface, image, or clip) rather than something authored in
64
+ code, generate it with an `npx genex` command (run inside the project, where the
65
+ `@genex-ai/cli-demo` dev dependency makes `genex` resolve to the right CLI). Each
66
+ prints a permanent public `assets.genex.technology` URL you load straight from at
67
+ runtime the asset lives in Genex storage (R2), not your repo, so there's nothing to
68
+ commit. Each skill has the exact Three.js loader code.
70
69
 
71
70
  | Work needed | Generate with | Skill |
72
71
  | --- | --- | --- |
@@ -74,6 +73,8 @@ loader code.
74
73
  | a described 360° sky / backdrop + image-based lighting | `npx genex skybox "<prompt>"` | `$genex-ai-skybox` |
75
74
  | a specific sound effect tied to an event | `npx genex sfx "<prompt>"` | `$genex-ai-sfx` |
76
75
  | a photoreal surface/material on a mesh or terrain | `npx genex texture "<prompt>" [--terrain]` | `$genex-ai-texture` |
76
+ | a picture on a plane/sprite — poster, sign, sprite, card art, decal/sticker | `npx genex image "<prompt>" [--transparent]` | `$genex-ai-image` |
77
+ | a video clip on a surface — in-game screen, billboard, cutscene, backdrop | `npx genex video "<prompt>" [--loop]` | `$genex-ai-video` |
77
78
 
78
79
  Prefer the **procedural** skills above for abstract/parametric/animated systems
79
80
  (geometry, materials, sky, water, VFX) — no files, infinite variation. Prefer the
@@ -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);