Skip to content

Host meta layer ​

The part of the host a game author observes: how the engine drives the game's Wasm core and routes what the core reports, and the engine-owned meta layer on top of it — the participant registry, the gameplay rules the engine owns (rounds, scoring, teams, map rotation, spectators), the managers and the modules. A game parameterises these through gameConfig; it cannot replace them.

Core calls and events ​

The engine drives the game's Wasm core and routes what the core reports (the ABI itself is in plugin-api.md):

  • Lifecycle. The map is loaded with load_map (it arrives already scaled, so the core loads it with scale: 1). A human joins through spawn_actor and leaves through remove_actor; a scripted participant (bot) uses spawn_scripted_actor/remove_scripted_actor; a player data change calls reset_actor. On a layered (2.5D) map a respawn point may name its level ([x, y, angle, level]) and the engine passes it on with set_actor_level(gameId, level) — the call is feature-detected, an older core without it derives the level from the geometry.
  • Input. Player input reaches the core through apply_input; the core confirms the sequence number in the frame's player block.
  • Events. After each step the engine drains take_events() and routes the standard dictionary itself: panelSet/panelActive update the panel (field is the game's own panel-schema key), death goes to the kill report (scores, round outcome), shake shakes the camera. custom is the only type with game-specific meaning: it is handed as is to the optional HostPlugin.onCoreEvent(data, { panel, vimp }). The core works on numeric ids (u32), the meta layer on string keys — the engine converts at that boundary.
  • Frames. The snapshot is packed with pack_body/pack_frame; the first frame of a joining player is built from the core's players_data().

Meta layer ​

The engine's meta layer runs the match on top of the core's events. Code a plugin adds to the host (command handlers, modules) must use isomorphic APIs only — see overview.md.

The map list a room rotates through is the catalog's current one at the moment the room starts.

Participants ​

Humans and scripted participants (bots) share one registry and one numeric id space; they are told apart by the isScripted/isNetworked getters, not by the shape of the id. Every participant has gameId, name, model, team, teamId and status; a scripted participant's name is scripted.namePrefix plus its id from the game config.

Engine-owned gameplay rules ​

These rules live in the engine and cannot be replaced; a game parameterises them through gameConfig or implements an alternative entirely inside its WASM core. The per-manager details are below.

  • Rounds. A round ends when a team is wiped (every participant, humans and scripted, not alive) and survivors remain in at most one other team; with three or more teams it goes on until only one team has anyone alive. The winner is the team that still has survivors, however the last player died (an enemy kill, a team kill, self-destruction, the environment, a killer who has already left) — every death event runs the wipe check. A wipe with no survivors anywhere (both last players died at once, or a single-team game) ends the round with no winner: every player gets the defeat cue (spectators victory) and a round-end message with no winning team. The round timer expiring does not end the round with a result — it starts a new one with no score change. The next round starts after timers.roundRestartDelay. A game that is not round-based is closest to one long round (a large roundTime, respawns in the core) or a single team, in which no wipe can occur.
  • Scoring. Killing an enemy: killer score +1 and rank points +1, victim deaths +1 and status = 'dead'. A team kill: killer score −1 and points −1, victim deaths +1. A suicide: victim deaths +1 only. A killer who already left: no frag, the victim still dies and the round still resolves. Each wiped team's head gets deaths +1 once per round, and at round end the surviving team's head gets score +1 (none on a draw). The ±1 rule is written synchronously with the kill report and there is no hook to change it. A dead player becomes a spectator watching the killer until the next round.
  • Sound cues fired here: frag to the killer, death to the victim, victory to the winning team and to spectators (on any outcome), defeat to everyone else at round end. Exactly five engine events exist (roundStart, victory, defeat, frag, death); gameConfig.soundCues maps them to the names in the game's sound config, cued sounds bypass the world voice limit, an unmapped cue is not sent, and {} disables them.
  • Teams. gameConfig.teams maps team names to numeric ids and includes the spectator team; under noSpectators: true it holds exactly one team and a joining human goes straight into it (no vote). One playing team is a valid configuration. Team capacity is respawns[team].length on the current map. Joining a full team calls scripted.removeOneForHuman(team); when it returns false the player gets TEAMS_TEAM_FULL and stays put. Switching team within timers.teamChangeGracePeriod of the round start is free; later the switcher dies and spectates until the next round. Dropping below 2 active humans resets the statistics and immediately starts a new round — unless the game declares endlessRound: true, which switches off every engine-initiated round restart (this rule, the team wipe and the round timer); /nr and map changes still restart it.
  • Map rotation. timers.mapTime expiring starts a system vote offering mapsInVote maps from the catalog; a player may also propose a map through the vote menu (a single proposal is applied directly); the switch waits timers.mapChangeDelay. A map change re-creates the scripted participants, resets the panel and the stat table, clears the votes and flushes the rank and state to the platform.
  • Spectators. Dead players and members of the spectator team follow another participant (the camera tracks the killer after a death); spectatorKeys (nextPlayer/prevPlayer, from key set 0) cycle the watched player; the cursor auto-hides after 3 s without mouse movement.
  • Game informs. The codes are fixed by the engine (vimp-engine/src/config/gameCodes.js): winnerTeam → index 0 of gameInform.list, roundStart → 1, gameOver → 2. The indexes are positional — reordering the array changes the meaning; code 1 also triggers the panel/logo animation on the client. Technical informs (techInformList, with engine defaults) are indexed as listed in plugin-api.md; placeholders {0}, {1} come from the params array.
  • Initial vote. gameConfig.initialVote (usually 'teamChange') is the vote sent to a player right after their first frame; null drops them straight into the game.
  • Player profile. rank is an engine-owned integer in a cross-game format rendered by the platform's lobby; state is the game's own JSON blob seeded from playerState.defaultState, read and written through onCoreEvent's vimp object and flushed on round end, map change and departure. Both are namespaced per (user, gameId); because the host is an untrusted browser, both are technically forgeable — a known limit of the P2P model.
  • Kicks. There is no kick vote and no /ban endpoint. Protection from a bad host is the engine's host migration — the automatic triggers (overload, hidden tab, network lag) and the /changehost vote, which the platform counts, not the host (lobby mode only). None of it passes through the plugin, and the game's vote menu gets no item for it. Thresholds and close codes: game-config.md and snapshot.md.

Managers and modules a game configures ​

A game does not replace the engine's managers and modules; it drives them through gameConfig and the plugin hooks:

ComponentWhat the game suppliesBehaviour visible to the game
RoundManagerteams, map respawns, scripted, timers.*, endlessRoundrounds, teams and map rotation exactly as in the rules above
CommandProcessorchat commands through HostPlugin.chatCommands (registerCommand(name, handler))the engine has no commands of its own; a handler is handler(ctx, gameId, args); an unknown command answers "Command not found"; when several humans are active a command may go through a vote
VoteCoordinator, Votegame votes (createVote), their textstopic cooldown timeBlockedVote, lifetime voteTime; more than 7 options paginate; a tie is resolved by a random pick; a vote created during an active one waits in a queue
Panelpanel.fields, activeKeyper-user HUD; only changes are sent, once per snapshot tick; authoritative values (health, ammo) live in the core and reach the panel through its events
Statthe stat schema (columns, aggregation, teams)scoreboard rows and team totals; a delta per tick, the full state on join
ChatHostPlugin.systemMessages (the game's code groups) and, on the client, their textsthe engine groups s/v/m/c have English defaults; a game code without a text is dropped silently; every system message is a code, data goes in params
TimerManagertimers.*the game loop, round and map timers, RTT pings, idle checks, deferred round restart and map change
RTTManagerrtt.*, idleKickTimeoutlatency tracking and kicks, see snapshot.md
PlayerDataSyncplayerState.defaultStateper-participant rank/state, see plugin-api.md

← Previous: Rust Engine Core (vimp-engine-core) · Next: Client runtime for game authors →

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