agentleFS
wolffewolffe/three.js-skills/skills/character/character-kit/SKILL.md

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.

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
  1. Very specific:

    Create the renderer, scene and camera as usual (WebGPURenderer, await init()).

    line 52
  2. Very specific:

    Create the cameras: createCameraManager(camera, { fov }), createFirstPersonCamera(...), createThirdPersonCamera(...).

    line 60
  3. Very specific:

    Create the walker: createWalker({ character, input, ...

    line 62
  4. Distinctive:

    Start the animation loop.

    line 63
  5. Distinctive:

    Every frame, in this order.

    line 67
  6. Very specific:

    No per-frame allocation in hot paths: reuse Vector3, Euler, result objects.

    line 104

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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.

PieceSkillOwns
inputcharacter-inputwhich keys mean which actions, pointer deltas, edge presses
walkingcharacter-walkingthe physics capsule and how it moves
cameracharacter-camerawhere the camera is and where it looks
modelcharacter-modelthe 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.

QuantityValue
standing height1.8 m
crouch height1.0 m
capsule radius0.35 m
eye height standing / crouched1.6 m / 0.9 m
walk / run / crouch speed4 / 7 / 2 m/s
jump impulse8 m/s (about 1.6 m apex at g = 18)
gravity18 m/s² (stronger than Earth; makes jumps snappy)
max slope45°
step up0.55 m (one stair riser with margin)
stick to floor0.5 m (keeps feet down over bumps and down slopes)
crouch ease10 /s exponential
turn rate (third person facing)18 /s exponential
mouse sensitivity0.002–0.003 rad per pixel
third-person orbit distance3 m default, 1.2–14 m by scroll
orbit pitch range0.1 to 1.52 rad (never under the ground, never straight down)
camera fov75°

Boot order

  1. Create the renderer, scene and camera as usual (WebGPURenderer, await init()).
  2. createCharacterInput(canvas) and input.setLookMode('drag' | 'capture').
  3. Initialise Jolt and build the physics world with two object layers (static and moving). The walking skill ships createJoltCharacter(...) that takes the Jolt module, the JoltInterface, the PhysicsSystem and the moving layer index.
  4. Build level geometry and add a collider for every solid mesh. A floating mesh with no collider is a bug.
  5. createCharacterModel() and add model.root to the scene.
  6. Create the cameras: createCameraManager(camera, { fov }), createFirstPersonCamera(...), createThirdPersonCamera(...).
  7. Create the walker: createWalker({ character, input, ... }).
  8. 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 setTransform and update.

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

  • dt from requestAnimationFrame must 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 call Quat.prototype.sIdentity() fresh each time rather than caching the result.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.