Skip to content

Scaffolding a game plugin (create-vimp-game) ​

The engine imports no game code by path: a game is a separate npm package that ships a dist/manifest.json and two bundles. Writing that package from zero means reproducing a build pipeline, a Rust crate, a snapshot schema and about thirty silent invariants before the first pixel appears — and the usual result is a plugin that builds but never shows up in the lobby.

create-vimp-game writes that package for you, already playable:

bash
npm create vimp-game my-game

The generator is published as its own npm package, create-vimp-game; it has no runtime dependencies, so npm create does not install a tree before asking its first question.

The command ​

bash
npm create vimp-game <directory> [-- <options>]

Everything after -- reaches the scaffolder. Without --yes and with a live terminal it asks six questions (directory, game id, title, package name, author, repository); with --yes, under a pipe or in CI it takes the defaults silently.

OptionMeaning
--id <id>game id, kebab-case — the URL segment /games/<id>/, the catalog key and HostPlugin.id (default: the directory name, normalised)
--title <title>human-readable title (default: the id, title-cased)
--package <name>npm package name (default: @vimp-games/<id>)
--repository <url>project repository, full URL or user/repo — written to both repository and homepage; the engine links the game's entry form to homepage (else repository), and rule A7 warns when it is absent (default: not written at all)
--author <name>author in package.json and the LICENSE
--yes, -yaccept all defaults, ask nothing
--forceallow a non-empty target directory
--no-gitskip git init (the first commit is always yours)
--engine-path <p>dev only: depend on a local engine checkout
--core-path <p>dev only: [patch.crates-io] for vimp-engine-core
--help, --versionprint usage / the scaffolder's own version

cargo and wasm-pack are checked after generation. Missing ones are a warning, not a failure: the project is written either way, and the error would otherwise surface on npm run core:build with no project to fix it in.

Versions are never hardcoded in the template. The vimp-engine and vimp-engine-core pins are read from src/versions.generated.json, written by the prepack hook at publish time. A stale pin is a silent breakage that only surfaces when the core is built, so the template carries no version literal to go stale.

What you get ​

A minimal but complete game — not a copy of tanks, and not a skeleton of TODOs:

  • two playing teams plus spectators, one actor class, one hitscan weapon, one map, bots;
  • all graphics are procedural (PixiJS Graphics plus bakers): the package ships no images at all. Sounds are two placeholder webm+mp3 pairs, with the assets/audio-raw/ → npm run audio:process pipeline wired but optional (it needs ffmpeg);
  • the full build infrastructure of plugin-api.md: two Vite modes (client, host), the five build scripts, a Cargo workspace with the crate <id>-core, Vitest with a unit and an integration project, ESLint;
  • a Rust core where the movement math sits in one motion.rs shared by the server step and the client predictor, with a parity test guarding that sharing;
  • a dev harness (index.html + dev/main.js) starting a standalone match against bots — see standalone.md;
  • a CLAUDE.md stating the thread boundaries, the contract constants and the check commands, for whoever (or whatever) writes the gameplay next.

Layout and per-file contracts: plugin-api.md and packaging.md; the documentation a finished plugin must ship is described there too (Documentation the plugin must ship).

The check loop ​

In the generated project, in this order:

bash
npm install
npm run core:build      # cargo + wasm-pack → core/pkg-web, core/pkg-node
npm run check:contract  # vimp-contract: the static engine↔game contract
npm run core:test       # cargo test --workspace (includes the parity test)
npm test && npx eslint .
npm run build           # dist/: both bundles, maps, sounds, manifest.json
npm run sim             # a headless match on the real core, host swapped every 0.5 s
npm run dev             # the same match in the browser, against bots

npm run core:build is not optional and not a later step: dev/main.js imports the wasm from core/pkg-web/, so until the core has been built once npm run dev dies on startup while resolving that import.

check:contract is the step that pays for itself. It is vimp-contract from the engine package — 34 rules over package.json, vite.config.js, core/Cargo.toml, dist/manifest.json and both plugin halves imported as modules; a rule with no input reports skip, so it is usable from the first commit. It catches exactly the class of mistake that otherwise reaches the browser as a black canvas: a wrong auth parameter name, a snapshot key with no gameSets entry, a part missing from entitiesOnCanvas. Details: debugging.md.


← Previous: Overview · Next: Standalone SDK (browser) →

vimp-engine 0.38.1 · vimp-engine-core 0.23.2 · create-vimp-game 0.5.4