Overview
A game on VIMP is a plugin package: you supply the simulation core (Rust compiled to Wasm), the configs and the content (maps, images, sounds); the engine supplies the match rules (rounds, teams, votes, chat, panel, statistics), the client with rendering, sound and input, and the platform's lobby and catalog. This page is the model you need to write a game: what you provide, the version numbers, what a room does with your game and the constraints your code runs under.
The host is authoritative: the simulation of a match runs in one place and clients render the result, masking network latency with interpolation and prediction. The host role may move to another player during a match.
What you provide
| Concern | You (the plugin) | The engine |
|---|---|---|
| Entities, movement, weapons, damage, AI | the Rust core | — |
| Rendering (sprites, effects, particles) | PixiJS parts | canvases, camera, the render loop |
| Maps, models, weapons, sounds, textures | the content | loading and playback |
| Rounds, scoring, teams, chat, votes, statistics, panel | config, schemas and texts | the rules and mechanisms |
| Auth form | fields and texts | the form shell and the identity |
| Bots ("scripted participants") | the implementation | lifecycle hooks |
A plugin is a package with dist/manifest.json, two bundles (host and client) and a Wasm binary; the engine loads it at run time and never imports a plugin statically. The host browser is untrusted: anti-cheat is out of scope and the engine has no countermeasure today.
Version numbers
Two independent values:
| Constant | Value | Meaning | Checked where |
|---|---|---|---|
ENGINE_API_VERSION | 4, frozen | Generation label of the plugin contract (GameManifest, HostPlugin, ClientPlugin, WASM ABI, form schema). Not a gate: no plugin is rejected for its age | nowhere at runtime; contract rule B2 checks it is consistent inside the package |
SNAPSHOT_FORMAT_VERSION | 5 | Byte layout of the state frame | inside the WASM core, both ends |
A plugin publishes engineApi in three places that must agree with each other (a mismatch means a stale dist/): manifest.engineApi, hostPlugin.engineApi, clientPlugin.engineApi. Import ENGINE_API_VERSION from vimp-engine/config/opcodes.js rather than hardcoding 4. Agreeing with the installed engine is not required — a game built a year ago runs on today's build.
Compatibility is negotiated by capability, not by number: a game that cannot run without a specific engine feature lists it in the optional manifest.requires (see plugin-api.md). The engine rejects the plugin only when a name is unknown to its capability registry (capabilities), i.e. the game is newer than the engine; the platform lists such a game as unavailable in the lobby, while loadClientPlugin and the standalone SDK throw. The SDK reads the list from HostPlugin.requires / ClientPlugin.requires (solo mode has no manifest), so the same list is declared in all three places and kept equal — rule B2 refuses a package whose manifest and halves disagree. A manifest with no requires needs nothing beyond the base contract.
manifest.id is the game's identity on the platform: it must equal the id the game is registered under (submitting.md); a mismatch makes the game silently disappear from the lobby.
What a room does with your game
- Create. A logged-in user fills the room form (fields from
manifest.roomForm, defaults frommanifest.roomDefaults);maxPlayersis clamped to the game'sroomDefaults.maxPlayers. - Boot. The host plugin is loaded, the required
gameConfigfields are validated, the engine defaults are merged withgameConfigand the room overrides, and the core is built from the result. - Join. A player picks the room and receives the client config and the first snapshot (snapshot.md).
- Play. The simulation steps at a fixed rate and a binary state frame goes out every Nth tick; meta events (chat, votes, panel, stat, sounds, informs) travel as JSON.
- Rotate. A round ends on a team wipe (the timer expiring starts a new one without a result); the map rotates when the map timer expires and a system vote picks the next one (host-meta.md).
- Host change. The room lives while it has people (lobby mode): when the host leaves, lags, hides its tab or is voted out, another member takes the room over. A game with
gameConfig.migration.midRound: truecontinues from the same tick; any other game migrates softly — meta kept, the round restarts, the physics world is not carried. Dedicated and standalone hosts never change.
Threading constraints
The host plugin and everything it imports run in a Web Worker: no window, document, DOM APIs or PixiJS — only isomorphic APIs (Date, Math, performance, setTimeout, queueMicrotask, fetch). The client plugin runs on the main thread and may use PixiJS, but PixiJS must stay a single shared instance supplied by the engine (plugin-api.md, contract rule A1), never bundled into the plugin.
Latency hiding
Three mechanisms smooth the network; all live in the client core, the ClientCore Wasm class from the same Rust binary (details — client-api.md, ABI — core.md):
- Interpolation (provided by
vimp-engine-core): frames are buffered and the world renders in the past (serverNow − 100 ms); events are emitted exactly once, positions are interpolated. - Prediction (your core, e.g.
@vimp-games/tanks): the local entity is simulated by a replica of the authoritative motion model (formulas shared with the game's core); the host confirms input (lastInputSeq), reconciliation replays unconfirmed input, and the discrepancy decays smoothly. - Client-side shot spawning (your core): a shot is seen and heard instantly; duplicates from the host are suppressed by author id.
Key invariants
- Source of truth for ports —
vimp-engine/src/config/wsports.js; for the binary format version —vimp-engine/src/config/opcodes.js; for snapshot keys — the game's own schema, supplied throughHostPlugin.gameConfig.snapshot(see plugin-api.md). - Motion replica parity: authoritative motion and the client prediction replica must share the tick formulas — this is the game's concern (a cargo parity test over
client::predictorin the game's crate); the engine only provides the genericPredictor<G>/interpolation machinery. - A single numeric id space for humans and scripted participants (bots); distinguished via
isScripted/isNetworked. The core operates on numeric ids, meta keys by string — the conversion happens at theGameCoreAdapterboundary.
← Back to the SDK index · Next: Scaffolding a game plugin (create-vimp-game) →