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:
npm create vimp-game my-gameThe 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
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.
| Option | Meaning |
|---|---|
--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, -y | accept all defaults, ask nothing |
--force | allow a non-empty target directory |
--no-git | skip 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, --version | print 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
Graphicsplus bakers): the package ships no images at all. Sounds are two placeholderwebm+mp3pairs, with theassets/audio-raw/→npm run audio:processpipeline 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 aunitand anintegrationproject, ESLint; - a Rust core where the movement math sits in one
motion.rsshared 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.mdstating 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:
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 botsnpm 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.