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

@vimp-games/snakes

v0.15.3

Published

VIMP Snakes — a VIMP game plugin

Readme

VIMP Snakes

A VIMP game plugin (@vimp-games/snakes): a snake arena.

The game

One circular arena. Snakes are always moving; you steer, you never stop. Crystals appear at random in three sizes — eat them to grow, and the bigger the crystal the more you grow and the more you score.

| Key | Does | | --- | --- | | A / D | turn left / right | | W | boost — nearly double speed, paid for in crystals, which drop behind you | | R | respawn after a crash (the OK button of the result screen presses it for you) |

Mouse and touch are the second way to play, and the only one a phone has: press and hold — the snake turns towards the point under the pointer, no faster than the keys would turn it; double tap and keep holding — boost, for as long as the finger stays down. Releasing hands the snake back to whatever heading it had, and taking hold of A/D cancels the pointer target. The channel is muted while the chat or the stat table is open, so typing a message never steers the snake.

Three ways it ends and only one of them is your fault:

  • your head touches the boundary of the disc — you crash;
  • your head runs into another snake in front of you — you crash, and everything you were carrying scatters over the map where you died. The crash is only ever the one who drove into it: a snake that swings into you from the side or from behind takes itself out and leaves you alone (two snakes that meet head on are both driving into each other, and both die);
  • your head touches your own tail — nothing happens, it passes over.

One number follows you and a crash does not reset it: score — the crystals you have eaten plus a flat 15 per snake that ran into you. The victim keeps its own score; what it was carrying scatters on the map for whoever gets there first, so killing the leader is still worth it, just not twice. The HUD shows the score alone; what you are carrying right now is told by the size of your snake. A kill is also worth a point of rank, the number /rank reports and the one that survives the session.

The first two seconds of a life are yours: a fresh snake appears whole — body and all, not a head that has to grow one — stands still and blinks, kills nobody and cannot be killed. Long enough to read the arena, and long enough for whoever was flying at that spot to steer around you.

The heavier you get the wider you steer: 3.4 rad/s empty, falling with the square root of the crystals down to a floor of 1.4. A leader stays steerable and stops being nimble — the fastest snake in the arena is a small one.

One life is one game: the score of a crashed life is its result, and Tab shows the game's global daily top ten — the best single game of the day across every server — rather than this room. The top ten of the day wears a diamond pattern down the body, the top ten of the month a crown. Everyone is a player: there are no teams, no spectators and nothing to vote on.

Run it

npm install
npm run core:build   # REQUIRED before npm run dev
npm run dev

npm run core:build is not optional and not a later step: the dev harness (dev/main.js) imports the wasm out of core/pkg-web/, so until the Rust core has been built once Vite fails to resolve the import and npm run dev dies on startup. The same holds before the master is started against this package, even in dev mode — build the core and then npm run build at least once.

In the lobby, against another player

npm run dev is a single tab against bots. To play this game through the lobby master (two tabs, a real room), link both halves and start the master from the engine checkout:

# once, in this package and in ~/Sites/my/vimp-tanks
npm link

# once, in the engine checkout — BOTH games in ONE command, or the second
# link overwrites the first
cd ~/Sites/my/vimp
npm link @vimp-games/tanks @vimp-games/snakes

npm run dev            # master on https://localhost:3002

The master builds its game catalog from node_modules/@vimp-games/* when GAMES_MATRIX is unset, sorted by id — so snakes comes first and is the lobby's active game, which is what makes the Create server button clickable (the lobby can only host the active game). To pin a different one, start the master with the catalog spelled out:

GAMES_MATRIX='[{"id":"tanks","package":"@vimp-games/tanks"}]' npm run dev

This package must be built (npm run core:build && npm run build) before the master starts: the catalog reads dist/manifest.json, not the sources.

Commands

npm run core:build      # wasm-pack -> core/pkg-web (runtime) + core/pkg-node (tests)
npm run core:test       # cargo test --workspace, including the motion parity suite
npm run build           # dist/: both bundles, maps, sounds, manifest.json
npm run check:contract  # static engine<->game contract check (vimp-contract)
npm test                # vitest
npm run dev             # a match against bots in the tab
npm run audio:process   # ffmpeg: assets/audio-raw/ -> build/sounds/ (optional)

The headless runner lives in the engine checkout and is the primary verification loop — see scenarios/ below.

Layout

| Path | What | | --- | --- | | core/ | the Rust crate vimp-snakes-core — movement, growth, collisions | | src/host/ | HostPlugin: runs in a Web Worker, no DOM and no PixiJS | | src/client/ | ClientPlugin: render parts, bakers and the result screen | | src/config/ | game, client, auth, snapshot and sound configuration | | src/data/ | the map, the snake model, the palette and the arena theme | | scenarios/ | headless scenarios for npm run sim | | scripts/ | build steps: bundles -> dist/ + manifest.json | | dev/ | the standalone dev harness — never published |

Only dist/ is published (files: ["dist"]).

Three decisions worth knowing before reading the code

The core owns life and death; the engine is never told. No CoreEvent::Death is ever emitted. That is what makes the round endless (a round only ends through a reported kill) and what makes respawning possible at all (the engine has no per-player respawn inside a round — its only spawn primitive is private to _startRound). The consequence is that the engine also never writes score or deaths, which is why src/host/StatBridge.js keeps the whole scoring model instead — eaten, kills and score, per game id — off the core's custom events. See the note atop src/config/game.js.

There is no physics. A snake is a polyline; the arena edge, other bodies and crystals are distance tests. The engine's Rapier world is stepped and stays empty. That is what makes the client's prediction exact — the predictor runs the same core/src/motion.rs the host does, with no solver in between to disagree about.

The arena is derived, not configured. The game half of the init JSON is a fixed field set, and a free-form gameConfig.parts.* key reaches the client and never the core — so the disc comes out of the map grid by one formula both halves apply (core/src/arena.rs, src/client/parts/Arena.js, src/data/maps/arena.js).

The arena grows with the crowd. The area per snake is the difficulty curve, so the grid is a function of how many are in the room: buildArena(count) in src/data/maps/arena.js owns the law, src/host/ArenaScaler.js owns when it applies, and the core's population custom event is what triggers it. The resize is a coreAdapter.createMap plus a MAP_DATA re-send, not an engine map change: the round, the panel and the stat table survive it untouched.

Headless verification

From the engine checkout, with this package linked:

npm run sim -- --game <path to vimp-snakes> --scenario <path>/scenarios/movement.json

| Scenario | Exercises | | --- | --- | | movement.json | cruising and turning, with prediction drift checked against tight thresholds | | crash-and-respawn.json | driving into the boundary, staying dead, the respawn key | | growth.json | two players, three bots, crystals, the boost, /bot | | pointer.json | steering to a point with the mouse/finger, the keyboard taking over mid-run, the double-tap boost | | bots.json | /bot <count> as a SET: six bots, then two, then a refused count, then none |

All five are expected to pass with --determinism. The sim runs the built plugin (dist/), so npm run build before it or you are testing the previous version. Two invariants skip by design: roundLifecycle (this game has no round end) and, in two of the four scenarios, predictionDrift (a crash and a respawn are legitimate one-off spikes — movement.json and pointer.json are the two that watch for drift that grows).

Sounds without ffmpeg

The real pipeline is assets/audio-raw/*.wav → npm run audio:process → build/sounds/*.{webm,mp3}. That needs ffmpeg, so the package also ships ready-made placeholders in assets/sounds/: when build/sounds/ is absent, scripts/copy-game-sounds.js falls back to them and the first build stays green on a bare machine. The two cues are pickup and death, both played positionally by src/client/parts/Snake.js.

Documentation

Full documentation lives in docs/en/:

Русская версия

For engine-side concepts (transport, master server, plugin contract), see the engine's own docs: vimp-engine/docs/en/.

Engine

The engine versions this game builds against are the pins themselves — vimp-engine in package.json and vimp-engine-core in Cargo.toml.

Game id: snakes. The plugin contract lives in the engine repository under docs/ai/.