npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

jolt-ts-character-controller

v0.1.0

Published

Imperative Ecctrl-style character and vehicle controllers for jolt-ts.

Readme

jolt-ts-character-controller

Imperative Ecctrl-style character and vehicle controllers for the jolt-ts wrapper. The runtime surface is rendering-free: callers own their Jolt world, render loop, networking, and asset scene.

import { World } from "jolt-ts";
import { CharacterController } from "jolt-ts-character-controller";

const world = await World.create({ gravity: [0, -9.81, 0] });
const controller = new CharacterController({
  world,
  position: [0, 1, 0],
  motionQuality: "linearCast"
});

controller.setForwardDirection(
  { x: 0, y: 0, z: 1 },
  { x: 0, y: 1, z: 0 }
);
controller.setMovement({ forward: true, run: true });
controller.step(1 / 60);
world.step(1 / 60);

The controller has no React, DOM, canvas, or browser dependency. The caller owns world stepping and networking. step(dt) applies Ecctrl-style floating, friction, jump, movement, turning, and platform impulses to a Jolt dynamic capsule body without allocating a snapshot. update(dt) is kept for callers that want the same simulation step plus a freshly allocated snapshot return. When useCustomForward is false, setForwardDirection should receive the camera world direction and camera up vector, matching Ecctrl's camera-relative movement projection. When useCustomForward is true, the same method accepts the custom forward axis directly.

Jolt CCD is exposed through the controller motionQuality option. Use "linearCast" for a dynamic character body that should sweep against thin or fast-moving scene geometry. Kinematic platforms are still driven with Body.moveKinematic(...); Jolt does not stop kinematic bodies during CCD, so the dynamic character or projectile body is the body that opts into linearCast.

The imperative controller exposes Ecctrl-style handle fields such as currPos, currQuat, inputDir, movingDirection, relativeVel, floatingImpulse, standCollider, isOnGround, runActive, lockForward, and turnOnYQuat. standBody is kept as a Jolt-specific alias for the same body returned by Ecctrl's standCollider name.

Animation state is decoupled from physics and rendering. You can use the Ecctrl-compatible resolver with the Jolt controller snapshot, with a custom imperative controller, or just as a plain state machine:

import {
  AnimationStateController,
  createCharacterAnimationStateController
} from "jolt-ts-character-controller";

const animationState = createCharacterAnimationStateController(controller, {
  onChange: (state) => {
    console.log(state);
  }
});

animationState.update();

const customAnimationState = new AnimationStateController({
  getSnapshot: () => ({
    isOnGround: true,
    isFalling: false,
    isMoving: playerSpeed > 0.1,
    runActive: sprintHeld,
    jumpActive: jumpPressed
  })
});

For normal Three.js, compose that state controller with your AnimationMixer actions. This is not tied to React Three Fiber:

import {
  ThreeAnimationController,
  createCharacterAnimationStateController
} from "jolt-ts-character-controller";

const threeAnimation = new ThreeAnimationController({
  stateController: createCharacterAnimationStateController(controller),
  actions
});

mixer.addEventListener("finished", (event) => {
  threeAnimation.notifyFinished(event.action);
});

threeAnimation.update();

Vehicles are imperative too:

import { Shape, World } from "jolt-ts";
import { Vehicle } from "jolt-ts-character-controller";

const world = await World.create({ gravity: [0, -9.81, 0] });
const vehicle = new Vehicle({
  world,
  shape: Shape.box({ halfExtents: [1, 0.4, 2.4] }),
  density: 200,
  carConfig: { engineHorsepower: 600 }
});

vehicle.addWheel({ position: [0.9, 0, 1.8], steerWheel: true, driveWheel: true, brakeWheel: true });
vehicle.addWheel({ position: [-0.9, 0, 1.8], steerWheel: true, driveWheel: true, brakeWheel: true });
vehicle.addWheel({ position: [0.9, 0, -1.8], driveWheel: true, brakeWheel: true });
vehicle.addWheel({ position: [-0.9, 0, -1.8], driveWheel: true, brakeWheel: true });

vehicle.setMovement({ forward: true });
vehicle.update(1 / 60);
world.step(1 / 60);

src/demo.ts ports the upstream Ecctrl demo scene to plain Three plus Jolt: testMap.glb, vehicles.glb, capsule.glb, and AnimationLibrary.glb are served from public/, with character, two car, and drone controllers driven by the imperative API.

Documentation

📚 https://snackdotgame.github.io/jolt-ts-character-controller/ — including live, interactive physics demos.

The full guides and API reference live in the Astro + Starlight site under docs/ — getting started, the character controller (movement, config, custom gravity, moving platforms), animation, cars and drones, network sync, and curves. The site is a self-contained pnpm project with its own lockfile, so it never touches the published package's dependencies:

cd docs
pnpm install   # first time
pnpm dev       # preview at http://localhost:4321
pnpm build     # static output in docs/dist

Performance

Run the deterministic headless controller benchmark with:

pnpm run perf:controller

The benchmark reports the low-allocation controller.step(dt) path, the snapshot-returning controller.update(dt) path, and standalone snapshot allocation cost. For browser-level comparison with upstream Ecctrl, run this demo and the upstream Ecctrl demo side by side, then compare the shared StatsGl FPS/CPU/GPU overlay:

pnpm exec vite --host 127.0.0.1 --port 5177
npm --prefix ../ecctrl run dev -- --host 127.0.0.1 --port 5178