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

sindicate

v0.21.0

Published

Sindicate — open-world game engine for three.js WebGPU. Streaming worlds, characters, mounts & vehicles, missions, crowds.

Readme

SINDICATE

Open-world game engine for three.js WebGPU. Streaming worlds, characters, mounts & vehicles, missions, ambient crowds — extracted from a shipped browser western (Dustwater) and consumed as an npm package by every game built on it.

  • Design & migration plan: ENGINE_DESIGN.md — the adversarially-reviewed blueprint. Read it first.
  • Contracts: docs/contracts/ — the written interface each subsystem exposes and each game implements.
  • Reports from games: ISSUES.md — what is wrong. UPDATES.md — what to add. See Reports from games below; this is how the engine actually improves.
  • Consumption: games depend on "sindicate": "^X.Y.Z" from npm. Source ESM, no build step; three and three-mesh-bvh are peer dependencies.
  • The rule: engine code changes only in this repo — never copy engine files into a game to hack them.

Depend on the published package, not a file: link. A link always resolves to whatever is in your working copy, so it can never catch a problem with what actually ships — a different surface: the files package.json's files selects, reached through its exports map. 0.17.0 published a ReferenceError that every file:-linked game was blind to, because their working copy was fine. All four games now take the tarball.

What's in the box

Migration steps 1–2 (of the plan in ENGINE_DESIGN.md §9): the verified-clean world/systems tier plus the complete core pipeline — rendering, assets, animation, retargeting, input — extracted from western, with game data arriving through registration calls instead of imports. Full reference: docs/api/.

Core

  • assets — Synty FBX/GLB loading, atlas binding with shared cached materials, multi-skeleton pack collapse, clip cooking to JSON, texture warm-up.
  • anim — Animator/RawAnimator state machine: auto retargeting, bone-masked overlay layer, per-clip timing trims, safe one-shot callbacks.
  • retarget — bind-pose-delta retargeting so clips authored on one rig play on differently-named rigs.
  • input — keyboard + mouse + gamepad behind one API with edge detection, data-driven pad bindings, analog sticks.
  • renderer — WebGPU with WebGL2 fallback, opt-in TRAA, post-processing with a single game-supplied TSL screen-grade slot.
  • quality — low/medium/high presets, per-game persistence, live apply, one-way FPS AutoTuner.
  • utils & decals — shared math/noise helpers plus DecalField, a ring-buffer-budgeted surface decal system.

World

  • collision — merged-static MeshBVH capsule collision, built sync or in a Web Worker for streamed tiles.
  • grass — streaming instanced grass clumps: GPU wind, trampling with spring-back, dryness/wetness/snow.
  • water — see-through low-poly water: scene refraction with Beer–Lambert absorption, fresnel sky reflection.
  • wind & weather uniforms — the single source of weather truth: CPU Wind sim + shared TSL uniform singletons.

Systems

  • shatter — pooled prop debris: real shard meshes flung, bounced, settled and faded, pure CPU, nothing resident.

Vendor

  • FBXLoader — vendored three.js FBXLoader patched to merge all FBX animation layers; pinned against three ^0.184.

Callable API

Games plug in at explicit registration points — all boot-time, before first use (see each doc's gotchas for ordering):

import {
  setClipDefaults, setAtlasEmissiveHook, setQualityStorageKey,
  Input, DEFAULT_PAD_BINDINGS, createRenderer, createPostProcessing, wantsTRAA,
} from 'sindicate';

// Per-game quality storage key — BEFORE the first getQuality read
// (games sharing an origin in dev otherwise overwrite each other).
setQualityStorageKey('mygame_quality');

// Game animation data: per-clip timing trims + which source rig authored each clip.
setClipDefaults({
  timing: { swing: { start: 0.12, speed: 1.4, end: 0.85 } },
  rigs:   { swing: 'syntyKnight' },
});

// TSL emissive term added into every atlas material — BEFORE the first
// applyAtlas/instantiate (materials are cached).
setAtlasEmissiveHook((albedo) => lampFieldTerm(albedo));

// Unified input with the game's own pad layout (data, not code).
const input = new Input(canvas, { padBindings: DEFAULT_PAD_BINDINGS });

// Renderer + post pipeline; the grade is the game's one screen-look slot,
// driven live via post.gradeAmount.value — never rebuilt at runtime.
const { renderer } = await createRenderer(container);
const post = createPostProcessing(renderer, scene, camera, {
  traa: wantsTRAA(),
  grade: Fn(([color, amount]) => /* identity at amount 0 */ color),
});

Games on Sindicate

| Game | Role | Consumes | |---|---|---| | western (Dustwater) | the parity benchmark — the engine was extracted from it, so anything it does differently is a regression | ^0.18.0, branch sindicate-port | | fable (Aldenvale) | the generality test — same engine, nothing in common with a western | ^0.18.0, branch sindicate-port | | MilitaryGame (Blacksand) | the third consumer, and the one that finds things: Synty + Kubold + Kevin Iglesias packs, where western's assumptions about clip completeness, weapon count and audio layout are all false | ^0.18.0 | | TakeTheCity | born on the engine — no western lineage at all, so it exercises the road, city and vehicle tiers cold | ^0.18.0 |

Reports from games

A game that hits an engine limitation writes it up rather than patching around it. Two files, kept in this repo so the engine owns its own backlog:

  • ISSUES.md — what is wrong. Defects, and places where "what western's assets look like" got encoded as "what an asset pack looks like".
  • UPDATES.md — what to add. Every item additive: an engine default good enough to be the demo, plus an override point so a game can change it without forking.

What a good report contains

The reports that have actually moved this engine all share a shape, and it is worth copying:

  • Provenance per item, because it decides how much to trust it. [live] — hit while running, diagnosed to a line, workaround in place. [verified] — re-checked against the current source while writing. [doc] — from a doc read, not re-checked. A [doc] item gets confirmed against source before anyone touches it; three of them turned out to be in a different file from the one named.
  • The line, and the mechanism. Not "animations break on weapon switch" but "registerMasked() snapshots actions[srcName] and nothing invalidates it".
  • The symptom that was actually observed, especially when it looks unrelated to the cause. "Thrown grenades hung in mid-air with frozen fuses" was an unguarded optional-chain in footsteps, four systems away. That distance is the most useful thing in the report.
  • Whether western is affected, argued rather than assumed.
  • A suggested order, because the reporter knows which one is costing them time today.

The rules a fix is held to

  1. The default is today's behaviour. Register nothing, change nothing — that is what makes a list like this safe to land against two shipped games. Returning null from an override means "engine decides" for the same reason.
  2. Override decisions, not methods. override('Animator.play', fn) only legalises the fragility: play's signature is implementation shape and will move. "What should the body be playing?" is a question, and questions have stable contracts. Every design gap in ISSUES.md Part 2 is a question the engine answered with a hardcoded string.
  3. Fail at registration, not at frame 900. An unknown name or a wrong type throws at construction, where the stack trace points at the mistake.
  4. Verify by behaviour, not by syntax. node --check passes on an undeclared identifier — it is a runtime ReferenceError. Two regressions shipped that way. And a test that watches what was asked for is blind to what the ask did: one fix was verified by checking the call and was still wrong, because the resulting animation looped for ever. Drive the real per-frame sequence and watch the result over more than one clip length.
  5. Exporting a default is half its value. A default you cannot read is a magic number; one you can import, inspect and spread from is documentation that cannot drift — DUSTWATER_SFX(dirs) over a list buried in a loader.
  6. Schedule the big ones deliberately. N-slot weapons and the seat/occupant abstraction are both correctly deferred: landing them opportunistically is how a parity benchmark moves without anyone deciding it should.

The defaults doctrine

From UPDATES.md U0, and the bar for anything new: can a new game run without supplying this, and is replacing it one declaration? Three kinds of default, and picking the wrong one is how engines get heavy:

  • D1 — code defaults. Every tunable gets a working value; nothing is required just to construct. Ship everywhere, no exceptions.
  • D2 — contract defaults. For anything whose real payload is a file, ship the contract and never the bytes: the bone-name map, the clip-name convention, the socket names, the expected directory shape. The mesh is not the hard part and cannot legally ship anyway; the rig map is, and it is pure data.
  • D3 — procedural placeholders. Where a default would have to be a binary, generate one in code. A blockout humanoid weighs nothing and cannot 404 — and it is the honest way to build an AI system, because a behaviour bug should be visible without any art at all.