Game configuration reference
The configuration a game plugin adds to, or reads from, the engine: the engine's host defaults a game may override, the game half of the host config, the client config (clientDefaults plus the game's own), the game's auth form and sound catalog, the engine's ports and opcodes, the system chat messages, the clock and the game data. The environment variables and the platform's own config files are not part of the plugin contract.
Engine host defaults (vimp-engine/config/hostDefaults.js)
Source: vimp-engine/src/config/hostDefaults.js. The engine half of the host config: limits, timers, kick policies, and the spectator keyset (spectating is an engine mechanism). The host Worker merges it with the active game plugin's HostPlugin.gameConfig and layers the room's settings on top.
| Parameter | Value | Description |
|---|---|---|
isDevMode | false | Development-mode flag: unlocks dev chat commands and the debug recorder in HostGame (debugging.md). A room sets it from room.isDevMode, which the client fills from import.meta.env.DEV — in a production bundle it stays false |
maxPlayers | 30 | The default participant limit; a host's room clamps it to the creator's setting (capped by the game's roomDefaults.maxPlayers), counted by humans |
chatMaxLength | 60 | The max chat message length (authoritative on the host; must match the maxlength of the input in the chat view) |
spectatorKeys | nextPlayer/prevPlayer | Commands of a spectator or inactive player (switching the observed player) |
Timers (timers, ms)
| Parameter | Value | Description |
|---|---|---|
timeStep | 1000/120 | The core's physics tick step (~120 Hz) |
networkSendRate | 4 | A snapshot is sent every Nth tick (4 → 30 packets/sec) |
roundTime | 120000 | Round duration |
mapTime | 600000 | Map duration |
roomTimeMin / roomTimeMax | 10000 / 3600000 | Server-side clamp bounds for the room's user-set roundTime/mapTime (the lobby form is not a trust boundary) |
voteTime | 10000 | How long a vote window stays open |
timeBlockedVote | 30000 | Cooldown between votes on the same topic |
teamChangeGracePeriod | 10000 | The team-change window at round start |
roundRestartDelay | 5000 | Pause between rounds |
mapChangeDelay | 2000 | Pause before a map switch after a vote |
rttPingInterval | 3000 | RTT ping interval |
idleCheckInterval | 30000 | How often idleness is checked |
Kicks (rtt, idleKickTimeout)
rtt.maxMissedPings: 5— consecutive missed pong replies before a kick;rtt.maxLatency: 1000— smoothed (EMA) latency (ms) above which a player is kicked; the threshold is sized for P2P hosting over home connections (a real RTT of 200–300 ms and spikes at a map change are normal);idleKickTimeout.player: 120000— kicks an idle player (2 minutes);idleKickTimeout.spectator: null—nulldisables the kick (spectators are never kicked).
The game half of the host config
The game half of the host config reaches the Worker as the active game plugin's HostPlugin.gameConfig field (the host Worker loads HostPlugin dynamically by entries.host from the active GameManifest) — parameters like friendlyFire, mapScale, teams, scripted, soundCues, the stat/panel/playerKeys schemas, and playerState.defaultState. This is entirely game-owned data; the concrete values are documented by the game itself. The engine syncs rank/state with the platform (plugin-api.md); rank and state are opaque as far as the engine is concerned — only the game interprets their shape.
coreParams — an optional, opaque dictionary of the game core's own parameters. buildCoreConfig merges it into the game half of the core config (hostPlugin.createCore(...)), where the keys the engine does know (friendlyFire, models, weapons, playerKeys, panel) win, so a game cannot swap out the engine's part of the contract. The engine neither reads nor validates the rest — it only delivers it to GameSim::new, which is what lets a game add a parameter to its core without an engine release (@vimp-games/tanks, for one, passes the 2.5D fall parameters this way).
mapFallTime — seconds of falling per level of height (0.35 by default). Unlike coreParams it is an engine key: buildCoreConfig puts it in the engine half, because the trajectory (FallModel in the core) is shared by whatever the game drops and by the map's own dynamic bodies, which the engine steps itself. A map body that runs out of floor under it falls to the nearest slab below.
A game with levels of its own usually owns that number already, in coreParams.levels.fallTime — the value its core uses for tanks and for its client-side prediction of the very same crates. Two independent fields would send the host's crate and its own client's crate down at different speeds, silently, so buildCoreConfig takes coreParams.levels.fallTime (when it is a finite positive number) as mapFallTime too. The engine key stays the default for games without levels, and an explicit override still wins over both.
maps[].levelHeight — the height of one level in world units (unscaled, the core applies the map scale itself; defaults to the tile size). It makes the ramp slope dimensionless — rise * levelHeight / span instead of «levels per pixel» — so climb constants (thrust, hull pitch, dust) can be tuned as gradients. Validated by the core and by rule E4: finite and greater than 0.
maps[].volumes (and maps[].levels[n].volumes) — an optional { "<layers key>": height } dictionary: the visual height of a render layer in levels. It is validated by the core and by contract rule E4 (the key has to name a render layer of the same level, the height has to be within (0, 8]), travels through the host untouched and reaches the render part as volume; the geometry is the game's business.
spectatorKeys — a spectator's commands (nextPlayer/prevPlayer); the set is engine-owned and lives in vimp-engine/src/config/hostDefaults.js. playerKeys (a player's commands) is game config, with a bitmask key (1 << n, used by the predictor and the core in the input history) and an optional type:
type: 0(default) — a repeatable action: starts on keyDown, ends on keyUp (movement, turret rotation);type: 1— fires once on keyDown.
The client config: clientDefaults.js + the game's own client config
The client's CONFIG_DATA is assembled from two halves: the engine defaults — vimp-engine/src/config/clientDefaults.js (interpolation, control modes/service keys, the engine modules' DOM structures, techInformList) and the game half, supplied by the active game plugin's HostPlugin.buildClientGameConfig() (parts.*, canvases, the player keyset, panel/stat schemas, chat/vote/gameInform texts, initIdList). The deep merge is done by buildClientConfig in the host's Worker; before sending it appends:
modules.vote.params.time=game:timers:voteTime;prediction— data for the client-side motion and shooting replica (timeStep,playerKeys,models,weapons, all game-owned).
The full table of which config fields are engine-owned vs. game-supplied lives in plugin-api.md (ClientPlugin API section).
interpolation — snapshot interpolation (engine)
delay: 100— ms; the world renders in the past (renderTime = serverNow − delay), ~3 frames at 30 packets/sec;maxFrameAge: 1000— a safety cleanup of stale buffered frames.
divergence — prediction divergence detector (engine, optional)
Absent from the production config, and then the frame path does nothing extra. Consumed by the client core, set from a scenario's divergence field in a headless run: thresholds (positional over the player block), defaultThreshold, capacity (ring buffer), angles (indices of angle components, compared on the circle). See debugging.md.
modules.canvasManager — canvases and camera
The common dynamicCamera parameters are engine-owned; the canvases set is game-owned. The canvas elements are generated by the client from this config (the key is the element id; width/height — the initial size before the first resize):
| Parameter | Description |
|---|---|
aspectRatio | The aspect ratio ('16:9'). The canvas fills the window while keeping the ratio. Without it — 100% of the window |
fixSize | A fixed size in px ('150' — a square, '200:100' — a rectangle). Disables aspectRatio and adaptive scaling |
baseScale | The base zoom ('numerator:denominator'). For adaptive canvases — the scale at a reference width of 1920px (result = width/1920 × baseScale); for fixed ones — a constant multiplier |
dynamicCamera | Enables the dynamic camera (look-ahead + speed-based zoom) |
shakeCamera | Allows camera shake |
Adaptive scaling guarantees the same field of view on any monitor (reference: Full HD, 1920px).
dynamicCamera (common parameters): lookAheadFactor (camera offset ahead of motion), zoomOutFactor/maxZoomOut (zooming out with speed), smoothnessPosition/smoothnessZoom/smoothnessVelocity (smoothing).
pointerCanvas (game, optional) — the canvas whose coordinate system the pointer channel is converted into; the first declared canvas by default.
Canvas names, sizes, and zoom are game-owned; e.g. @vimp-games/tanks defines vimp (16:9, 5:1 zoom, dynamic camera, shake) and radar (150×150px, 1:8 scale).
modules.controls — controls
keySetList(game) — an array ofkeyCode: 'command'sets, entirely game-defined (e.g.@vimp-games/tanksuses two:[0]— spectator (n/p— switch the watched player),[1]— player (w/s/a/d— movement,k/l/u— turret,j— fire,n/p— weapon switch)). Which set is active is dictated by the host over port17(KEYSET_DATA).pointer(game, optional) — the pointer channel (mouse/finger/stylus). Omit it and the engine attaches no pointer listener at all. Keys:keySets(indices ofkeySetListthe channel is live in; default — all),doubleTapMs/doubleTapPx(double-tap thresholds, default300/40),sendIntervalMs(the floor between twomovemessages, default50). The wire format is"seq:aim:x:y:flags"with a world point; see client-api.md.modes(engine) — UI modes:c— chat,m— vote,tab— stats.cmds(engine) — service keys (escape,enter), with top priority, used within modes.
Other modules
DOM structures (elems) are engine-owned; texts and schemas are game-owned:
chat— DOM element ids, output limits (listLimit: 5lines,lineTime: 15000ms), and a cache — engine; system message templates (messages, game): a code registry of groups, engine-owned groupss(statuses),v(votes),m(maps),c(commands) plus any groups the game plugin registers (e.g.@vimp-games/tanksaddsbfor bots). The host only sends'group:number:params', the client assembles the text. The engine groups have English defaults insrc/config/chatMessages.js(see below); the game's string at the same index overrides one,''hides the message,null/missing keeps the default. A game group is entirely the game's.panel— thecontainerIdcontainer (engine); the mapping from server keys (t,h,wa,w1,w2) to fields (keys) and the typed field schemafields(game): an ordered list of{ name, elem, type: 'bar'|'value'|'time'|'weapon', max?, blocks? }—PanelViewgenerates the panel DOM and rendering behavior from the types, not from field names.stat— the container id (engine); thecolumnslabels, head/body tables (heads,bodies), andsortList(game) —StatViewgenerates the scoreboard DOM from the schema;sortList— sort parameters: an array of[cell index, descending?]pairs; on a tie, comparison moves to the next pair.vote— DOM ids/classes (engine) and vote templates (templates, game):[a title with {0} placeholders, options (an array — static, a string — request the list from the host), timeOff].menu— the main vote menu's items.gameInform/techInformList— templates for on-screen game messages (the element id — engine, thelisttexts — game) and technical screens (engine: room full, idle/latency kicks, etc.).initIdList(game) — which modules/canvases to initialize at startup (vimp,radar,panel,chat); the initialization mechanism is engine-owned. These DOM ids stay hidden until authentication completes;panelis then shown asdisplay: flex, every other id asdisplay: block.
Details that are easy to miss:
- Panel cells.
bardraws a block bar —blocksdefaults to30,maxto100, with a colour ramp across the blocks, a low-value blink and an animated refill at round start (500 ms);valueis a plain number;timeisM:SS;weapongets the.activeclass when it is the active weapon. Thetkey is hardcoded by the engine as the round time in seconds. - Stat columns.
columnsare header labels matched positionally to the host's columnkeyindexes; a team present inbodiesbut not inheads(spectators) gets rows but no aggregate header;sortListsorts numerically (~~textContent), so a text column sorts as0. The engine writes its own five names into the columns declared and drops the rest, but its CSS lays out only five (#stat …:nth-child(1)…(5), plus the.line1–.line3row classes): a further column has no width until the game restates the layout instyles(contract ruleC6warns about it). - Stat mode
leaderboard. A game whose scoreboard is the global top declaresmodules.stat.params.mode: 'leaderboard'(withperiod,limit,columns) andstatMode: 'leaderboard'ingameConfig— on both halves, or the host broadcasts a room table nobody draws, or the client draws a table nobody fills (contract ruleC11is an error on either). The view renders one.stat-leaderboardlist of.stat-row(place · nick · score) pushed by the host onACCOLADES_DATA; the caller's own row is highlighted (is-self), replaces the last row when they are outside the top, and shows—when they are not ranked for the period. The engine's CSS gives only a bare three-column skeleton (ruleC6checks that the game'sstylesmention.stat-leaderboard). - Vote UI. A template is
[title, values?, timeOff?];valuesis an array of labels or'teams'/'maps'for engine-substituted lists;timeOff: truehides the countdown.menuis the player-initiated vote list — the engine adds nothing to it (the "Change host" vote starts with/changehost, and its window is the engine vote@changeHost; names with@are reserved).params.timeis injected fromtimers.voteTime. The wholemodules.voteblock is optional: a game with nothing to vote on may omit it.
The game's auth config
The auth form schema (HostPlugin.authSchema: DOM element ids, form parameters, the game's validators, texts) is entirely game-owned data; the engine only provides the neutral auth shell (title, help sections, a Start button — no name field: the nick comes from the player's verified identity) and AuthView, which fills in the game's title/help sections from texts. authSchema.params typically declares only game-specific fields (e.g. @vimp-games/tanks's model, validated by its own isValidModel); the engine's isValidName exists for a game that opts into a form-typed name field, but is unused by the default form since the nick comes from the verified lobby identity token, not user input. Validation runs on the client (with validators from the game bundle) and is repeated by the host (Worker) as the actual authority; only elems/params/texts travel over the wire (AUTH_DATA, port 1) — the validator code doesn't. The game's own auth config is documented in its own repo's docs.
The game's sound catalog
The sound catalog (file names, priorities, volumes, loop flags, codec list) is game data, served under the game's assetsBase. Playback mechanics (voice limits, priorities) are engine-owned — see client-api.md. The optional parts.sounds.spatial block (projection profile, virtual elevation, spread radius, PannerNode attributes) is documented there too; a game that omits it gets the engine defaults. No .env variable is involved.
Ports and opcodes (vimp-engine/config/wsports.js, opcodes.js)
wsports.js— the numeric port registry for the game protocol (the source of truth). Full tables — snapshot.md.opcodes.js— the binary snapshot format version (SNAPSHOT_FORMAT_VERSION = 5),ENGINE_API_VERSIONandHOT_FLAGS. The snapshot key registry is game data, supplied throughHostPlugin.gameConfig.snapshot(a numeric id +kindper key, which drives the block's byte layout). An unregistered key breaks frame packing. Details — snapshot.md.gameCodes.js— theGAME_INFORM_DATA(port 7) message codes (winnerTeam/roundStart/gameOver), the source of truth shared by the host and the client (code1also triggers the round-start panel/logo animation).
System chat messages (vimp-engine/config/chatMessages.js)
English default texts of the engine's chat codes, groups s, v, m, c (index = code number). They are kept apart from clientDefaults.js because config merging replaces arrays whole — a game's s array would wipe the defaults. ChatModel takes the game's string at the same index first; '' hides the message; null or a missing index falls back to the default. A new engine code goes into ENGINE_MESSAGE_CODES, this table and the B8 rule's reserved ranges in one change (tests/config/chatMessages.test.js checks they agree).
| Group | Texts, by index |
|---|---|
s | 0 "Team {0} is full. Your current team: {1}", 1 "Your team: {0}", 2 "Your new team: {0}", 3 "Your new status: spectator", 4 "{0} killed {1}", 5 "{0} joined the game", 6 "{0} left the game", 7 "Host changed", 8 "You are no longer the host (connection lost)", 9 "Host changed: the previous host was lagging", 10 "… went inactive", 11 "… had a poor connection" |
v | 0 "A vote has been created", 1 "Voting has started", 2 "Your vote has been accepted", 3 "Voting is temporarily unavailable", 4 "Vote passed", 5 "Vote failed", 6 "Usage: /changehost", 7 "You are the host — use “Hand over host” in the room menu", 8 "No connection to the master server", 9 "A host vote was held recently", 10 "No other player can host", 11 "A host vote is already in progress", 12 "A host vote is not possible right now", 13 "Vote to change host passed ({0}/{1})", 14 "Vote to change host failed ({0}/{1})", 15 "Host vote cancelled" |
m | 0 "Current map: {0}", 1 "Next map: {0}" |
c | 0 "Command not found", 1 "Your rank: {0}" |
s:8 and v:6–v:15 are added by the client itself and never travel the wire. Placeholders are {0}, {1}, … filled from the params array.
Clock (vimp-engine/lib/clock.js)
Not a config file but the injection point that makes a match reproducible: a singleton exposing now() (epoch ms, Date.now), monotonic() (high resolution, performance.now), random(), setTimeout/clearTimeout/setInterval/clearInterval, plus install(custom) (returns a rollback function) and reset().
Every host timer goes through the engine's timer base, which takes its timer functions from clock; host call sites use clock.now()/clock.monotonic() /clock.random() instead of the globals. Defaults resolve the globals at call time, so production behaviour (and vi.useFakeTimers() in tests) is unchanged, while the headless runner can swap in a VirtualClock and run a ten-minute match in seconds — deterministically. See debugging.md.
Game data (models, weapons, maps)
Model/tank parameters, weapon definitions, and maps are entirely game-owned static data — see the active game plugin's own docs (e.g. @vimp-games/tanks's docs/en/configuration.md) for their concrete shape and values. One cross-cutting invariant to know as an engine contributor: motion-model coefficients are typically shared between a game's authoritative core and its client prediction replica, so games gate changes to them behind their own cargo parity tests.
← Previous: Plugin API · Next: Snapshot schema and wire formats →