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

@siltrun/stage

v0.1.2

Published

Silt 2D client baseline: the PixiJS stage every Silt title starts from. App bootstrap (DPR/resize/context-loss), three-model camera, unified touch+keyboard input, juice primitives (particles/haptics/pattern fills), bloom, procedural audio kit, remote-enti

Readme

@siltrun/stage

The 2D stage every Silt title starts from, extracted from a shipped PixiJS game shell. App bootstrap, camera, unified touch+keyboard input, juice primitives, procedural audio, remote-entity interpolation.

What it is not: a game framework. No scene conventions, no ECS, no draw abstractions. You draw with raw Pixi Graphics/Text: vector redraw per frame, additive layers, bloom on top. The stage owns the plumbing every title otherwise re-derives (and gets subtly wrong on phones).

npm i @siltrun/stage pixi.js

The ceremony invariant

A consumer's entire per-game obligation is:

const stage = await createStage(mountEl);        // 1 — the app
const cam = createCamera(stage);                 // 2 — one camera call
cam.fitRect({ x: 0, y: 0, w: 900, h: 900 });     //     (or follow / fitAll)
const pad = dpad(mountEl, { onDir: d => room.events.send({ turn: d }) }); // 3 — one input primitive

Anything beyond that creeping into a consumer is a design failure in THIS package — report it, don't absorb it.

Quickstart (vanilla TS)

import { createStage, createCamera, createGameClock } from "@siltrun/stage";
import { vectorInput } from "@siltrun/stage/input";
import { createParticles } from "@siltrun/stage/juice";
import { Graphics } from "pixi.js";

const stage = await createStage(document.getElementById("game")!, { background: 0x0b0b0d });
const cam = createCamera(stage);
const clock = createGameClock();
const input = vectorInput(stage.app.canvas);

const ship = new Graphics();
stage.world.addChild(ship);                       // world-space: camera-driven
const fx = new Graphics();
fx.blendMode = "add";                             // light stacks on light
stage.world.addChild(fx);
const particles = createParticles();

cam.follow(() => pos, { baseZoom: 0.6, speedZoom: 0.4, maxSpeed: 1500, lead: 0.12 });

stage.app.ticker.add((tk) => {
  const dt = clock.tick(tk.deltaMS);
  const v = input.read();                         // touch drag ∪ WASD, one read
  // …integrate your sim with v…
  particles.step(dt, fx);
  cam.update(dt);
});

stage.onRestore(() => redrawStaticLayers());      // mobile GPU context loss is normal
// stage.dispose() on teardown

⚠️ Await createStage inside an async main() — never as a top-level await. In a Vite/Rollup production build, a top-level await createStage(...) deadlocks the page with no error: the entry chunk's evaluation blocks on app.init, which dynamically imports Pixi's renderer chunk, which statically depends back on the entry chunk — a circular chunk wait. Dev mode works (unbundled modules), so the hang only appears in the built app.

React wrapper (deliberately not shipped as a hook)

function Game() {
  const hostRef = useRef<HTMLDivElement>(null);
  useEffect(() => {
    let stage: StageHandle | undefined;
    let cancelled = false;
    createStage(hostRef.current!).then((s) => {
      if (cancelled) { s.dispose(); return; }
      stage = s;
      // …build the game against s…
    });
    return () => { cancelled = true; stage?.dispose(); };
  }, []);
  return <div ref={hostRef} className="absolute inset-0" />;
}

Keeping react out of this package's peer set is deliberate: vanilla TS clients consume the stage too. If this wrapper turns out to be copy-pasted identically everywhere, a @siltrun/stage/react subpath is a future call. Not built today.

Modules

| Import | Contents | |---|---| | @siltrun/stage | createStage (DPR-capped bootstrap, ResizeObserver-owned resize, safe-area probes, context-restore registry, perf overlay), createCamera (fitRect / follow / fitAll + shake), createGameClock (dt clamp + hit-stop), math guards (num, lerpColor, lerpAngle, approach, decay, rng, …) | | @siltrun/stage/input | vectorInput (dragStick ∪ keyVector — the phone+desktop analog default), dragStick, keyVector, dpad (turn/hold: keys + swipe + hold-relative-to-center), tapBoard | | @siltrun/stage/juice | createParticles (pooled, streak rendering), haptic, makeTilePattern (engraved hatch/stipple fills) | | @siltrun/stage/bloom | createBloom — pulls the optional pixi-filters peer; flat-look games skip this subpath entirely | | @siltrun/stage/audio | createAudioKitblip/noiseHit/engine over a compressor bus; iOS silent-switch + unlock handled. The kit is the instrument; your game composes its own beats | | @siltrun/stage/interp | createInterpolator — render-in-the-past + ghost-coast + NaN rejection at the seam. Consumes snapshots and never speaks wire, so it stays independent of the transport | | @siltrun/stage/smoother | createCorrectionSmoother — eases the predicted SELF across reconcile corrections (nudge → decaying offset; snap on teleport-scale). Zero added latency for normal motion. Interp smooths OTHERS, the smoother smooths SELF — never cross them (interp on self re-adds the latency prediction removes). Pairs with @siltrun/client/predict |

Camera models

  • fitRect — a god-view fixed board, or a single-screen floor. Re-fires on stage.onResize.
  • follow — camera-follow, in two shapes: { posSmooth: 17 } for a pure ease, or { speedZoom, lead } for a rigid follow that widens with speed. The defaults are tuned feel numbers taken from a shipped game.
  • fitAll — spectator big-screen framing.

shake(amp) composes onto any model. Hit-stop lives on the clock, not the camera.

HUD conventions (DOM-first — deliberately no component library)

Every Silt title builds chrome in DOM (crisper text, CSS-styleable, accessible), not Pixi. The stage gives you stage.safeArea() for notch/home-bar insets; the rest is convention:

  • uppercase letterspaced microcopy for status lines; tabular-nums for numbers
  • overlays absolutely positioned over the mount element (position:relative is ensured)
  • Pixi Text only for things living in world space (nameplates, gate numbers), and throttle world-text updates to ~10 Hz: a .text change re-rasterizes the glyph texture, so setting it every frame is a per-frame raster you did not ask for

Art direction

The stage takes no aesthetic opinions. Bring your own palette.

The look the Silt sample titles share, if you want a starting point: monochrome engraving on bone. Ink #0b0b0d, bone #eae7de, hatch and stipple fills via makeTilePattern, warmth (amber, copper) reserved for things that carry meaning, and no yellow. Your own art direction always wins over this.

Mobile acceptance (what a stage-based game gets by default)

  1. Fills the phone viewport correctly — pair with this meta block in index.html:
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="mobile-web-app-capable" content="yes" />
    <style>html, body { overscroll-behavior: none; } </style>
    (the stage sets touch-action:none on its canvas itself)
  2. Steerable by touch — every input primitive listens to touch AND keyboard from birth.
  3. Holds frame rate on DPR-2/3 phones — DPR cap 2, MSAA policy, bloom budget defaults (if a phone chokes, lower bloom strength, never its resolution).
  4. Survives backgrounding (onRestore) and unlocks audio on first gesture — wire createAudioKit().unlock as any input primitive's onGesture.

Peer dependencies

pixi.js >=8 <9 is a peerDependency, so your game owns its Pixi version. pixi-filters is an optional peer used only by the ./bloom subpath; flat-look games never install it. The create-siltrun templates pin pixi.js ^8.19.0, the version these defaults were tuned against.

Docs

Full guides and API reference: https://silt.run/docs/