character-kit
Skill4 starsChanged 15 days agoMITView on GitHub
Put a playable walking character into any Three.js 0.187.0 WebGPU scene. Use when a scene, game or simulation needs a first or third person character that walks, runs, crouches and jumps over level geometry. Parents character-walking, character-camera, character-model and character-input.
In this repository28 other files it gives its agents.
- character-cameraSkill
- character-inputSkill
- character-modelSkill
- character-walkingSkill
- engineered-environmentsSkill
- megacitySkill
- station-maglevSkill
- greenhouseSkill
- pipesSkill
- procedural-geometrySkill
- signageSkill
- indirect-lightingSkill
- lightingSkill
- glassSkill
- interior-mappingSkill
- material-compositionSkill
- metalSkill
- paintSkill
- rustSkill
- tsl-materialsSkill
- waterSkill
- threejs-directorSkill
- card-grassSkill
- far-shadeSkill
- ocean-waterSkill
- photo-skySkill
- snow-weatherSkill
- jolt-drivingSkill
The file
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 2 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 52
Create the renderer, scene and camera as usual (WebGPURenderer, await init()).
- Very specific: line 60
Create the cameras: createCameraManager(camera, { fov }), createFirstPersonCamera(...), createThirdPersonCamera(...).
- Very specific: line 62
Create the walker: createWalker({ character, input, ...
- Distinctive: line 63
Start the animation loop.
- Distinctive: line 67
Every frame, in this order.
- Very specific: line 104
No per-frame allocation in hot paths: reuse Vector3, Euler, result objects.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Character kit
Target: Three.js 0.187.0, WebGPU renderer (three/webgpu), TSL (three/tsl), Jolt 1.1.0
(jolt-physics WASM) for the capsule. No WebGL fallback, no GLSL, no model files.
A playable character is four independent pieces and a thin wiring layer. Keep them separate: each is reusable on its own, and the wiring is the only place that knows about the scene.
| Piece | Skill | Owns |
| input | character-input | which keys mean which actions, pointer deltas, edge presses |
| walking | character-walking | the physics capsule and how it moves |
| camera | character-camera | where the camera is and where it looks |
| model | character-model | the visible figure and its animation |
The wiring layer (about 120 lines in the recipe below) reads input, decides a wish direction from the camera yaw, moves the capsule, steps physics, then places the camera or the model from the capsule's new position.
Measurements
These are the numbers that make a character feel right on a metric level. Change them only for a different genre.
| Quantity | Value |
| standing height | 1.8 m |
| crouch height | 1.0 m |
| capsule radius | 0.35 m |
| eye height standing / crouched | 1.6 m / 0.9 m |
| walk / run / crouch speed | 4 / 7 / 2 m/s |
| jump impulse | 8 m/s (about 1.6 m apex at g = 18) |
| gravity | 18 m/s² (stronger than Earth; makes jumps snappy) |
| max slope | 45° |
| step up | 0.55 m (one stair riser with margin) |
| stick to floor | 0.5 m (keeps feet down over bumps and down slopes) |
| crouch ease | 10 /s exponential |
| turn rate (third person facing) | 18 /s exponential |
| mouse sensitivity | 0.002–0.003 rad per pixel |
| third-person orbit distance | 3 m default, 1.2–14 m by scroll |
| orbit pitch range | 0.1 to 1.52 rad (never under the ground, never straight down) |
| camera fov | 75° |
Boot order
- Create the renderer, scene and camera as usual (
WebGPURenderer,await init()). createCharacterInput(canvas)andinput.setLookMode('drag' | 'capture').- Initialise Jolt and build the physics world with two object layers (static and
moving). The walking skill ships
createJoltCharacter(...)that takes the Jolt module, theJoltInterface, thePhysicsSystemand the moving layer index. - Build level geometry and add a collider for every solid mesh. A floating mesh with no collider is a bug.
createCharacterModel()and addmodel.rootto the scene.- Create the cameras:
createCameraManager(camera, { fov }),createFirstPersonCamera(...),createThirdPersonCamera(...). - Create the walker:
createWalker({ character, input, ... }). - Start the animation loop.
Frame order
Every frame, in this order. The order matters: the camera must consume the pointer delta before the wish direction is computed from its yaw, and physics must step before anything reads the capsule.
const update = (dt) => {
crouchHeld = input.isAction('crouch');
walker.setCrouch(crouchHeld); // swaps the capsule shape when it can
cameraManager.update(dt); // consumes look delta, updates yaw/pitch
const yaw = firstPerson ? fp.rotation.yaw : tp.orbit.yaw;
const move = input.getMove(); // -1/0/1 axes, shared object
const speed = crouchHeld ? 2 : (run ? 7 : 4);
walker.move(move, yaw, speed, input.wasPressed('jump'), dt);
physics.step(dt); // jolt.Step, clamp dt to 1/30, 2 substeps over 1/55
const pos = walker.position(); // capsule centre, shared object
if (firstPerson) camera.position.set(pos.x, pos.y + eye(t) - radius, pos.z);
else { model.setTransform(feetY, pos.x, pos.z, facing, t); model.update(dt, { speed, grounded, runSpeed: 7 }); }
};Edge presses (wasPressed) are cleared at the end of the frame, after all consumers,
not when read. The input skill explains the late-update hook.
Interfaces between pieces
- Input to walking:
getMove()gives{ x, z }in -1/0/1;isAction(name);wasPressed(name). - Camera to walking: a yaw in radians. The walker rotates the move axes by yaw so the caller never does trigonometry.
- Walking to camera and model:
position()(capsule centre),grounded, the current capsule height. Objects returned every frame are shared scratch objects; copy them if kept. - Model to nothing: it only receives
setTransformandupdate.
Conventions worth keeping
- Factory functions and closures, no classes. Each factory returns a plain object.
- No per-frame allocation in hot paths: reuse
Vector3,Euler, result objects. - The capsule's feet are at
centre.y - (height/2 - radius); eye height is measured from the feet. Do not place the camera at the capsule centre plus a constant. - Crouch is a shape swap, not a scale: Jolt refuses the swap if the standing capsule would intersect a ceiling, so the character stays crouched under a table. Ease the visible height toward the capsule height so the camera and model do not pop.
- Standing still on an edge must not creep: the ground normal tilts on a lip, so zero the horizontal velocity when there is no wish direction and the character is on ground.
- Gravity, speeds and heights live in one constants object; the model and camera read heights from the walker, not from their own copies.
Pitfalls
dtfromrequestAnimationFramemust be clamped (1/30) before physics or a tab switch becomes a launch.- The third-person camera needs a smoothed follow target (16 /s exponential) or physics jitter shows as camera jitter.
- Pointer lock can be refused (embedded pages, some browsers); keep drag look as the fallback and never block on the lock.
- Jolt objects are WASM: destroy temporary
Vec3/RVec3/settings after use, and callQuat.prototype.sIdentity()fresh each time rather than caching the result.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

