Player

A player in Quake 0 is a fighter: a small rectangle that runs, jumps and drops through platforms, and fires automatically at the nearest rival. This folder holds what a player sends to join, the state each one carries, and the scripts that run over a fighter's life — on join and leave, the per-viewer relevance pass, and the client-only render body.

gamehoster-player-join-schema.json

All a player supplies on connect is a display name.

[ { "gamehoster-player-join-schema-name": "name",
    "gamehoster-player-join-schema-type": "string" } ]

gamehoster-player-schema.json

The state each fighter carries splits in two. The schema holds only the public fields, streamed to every viewer to be drawn (the bounded ones carry min/max/decimals range hints). The physics scratch and timers the per-tick update reads and writes are not in the schema at all: they live in the fighter's Player.Persistent bag, seeded on join and never streamed. The table marks where each field lives.

FieldTypeWhereMeaning
namestringpublicDisplay name. (There is deliberately no colour field — a fighter's colour is chosen by each front end, not stored on the server.)
x, ynumberpublicTop-left of the fighter rectangle, in tiles. Streamed and interpolated.
facingintpublic-1 or 1: which way the muzzle points.
lastHitTickintpublicWhen you were last hit — the front end fades a flash from it.
deaths, scoreintpublicTimes fallen off (bumped on every respawn), and points from rivals falling off — your own score resets to 0 when you fall. A change in deaths is watched by the client-only gamehoster-player-smooth.js body, which snaps the fighter across a respawn instead of interpolating.
vx, vynumberPersistentVelocity. Horizontal is run-speed×direction; vertical is the jump/gravity value.
dirintPersistentHeld run intent (-1/0/1) from the move command.
onGroundboolPersistentStanding on a platform this tick (you can only jump then).
controllableboolPersistentFalse while flying from a hit — you steer again only on landing.
jumpHeld, dropHeld, dropUsedboolPersistentJump-key hold (jump off every landing while held), down-key hold, and the one-drop-per-press latch.
firePhase, fireBlockUntilintPersistentThe tick offset your auto-fire lands on, and a tick until which a hit mutes it.

The seven public fields are the schema; the ten Persistent fields (vx, vy, dir, onGround, controllable, jumpHeld, dropHeld, dropUsed, firePhase, fireBlockUntil) are the free-form server-only bag.

gamehoster-player-join.js

Runs the moment a player connects: it routes them into an instance that still has room (or opens a fresh one), then sets up their fighter. It writes the public state (name, a random column well above the level so they fall in from off the top of the screen, facing, the last-hit stamp and the death/score counters), then seeds the server-only physics and timers — velocity, run intent, the grounded and controllable flags, the key-hold latches, and a fire phase so everyone's constant shooting is staggered — into the fighter's Player.Persistent bag, which is never streamed. No colour is set: that is a front-end choice.

gamehoster-player-leave.js

When a player leaves, their in-flight bullets are destroyed; the level and everyone else are untouched.

gamehoster-player-view.js

Per-viewer relevance, run once per viewer at the send rate. The level is small and holds at most five fighters, so there is no fog of war: it simply marks every other player and every bullet and hit as relevant, and the whole arena is shown to everyone.

gamehoster-player-smooth.js

Client-only, and it ships with the game's baked client. By default the client interpolates every numeric field between the two server samples straddling the render tick, which is exactly right while a fighter runs and falls. This body overrides that default for one case only: a respawn. A fighter that drops off the bottom reappears at a fresh column above the top of the screen, and interpolating position across that jump would slide it straight up the map. It watches the public deaths counter — while the smoothed counter still lags the authoritative one it snaps x/y to the latest value instead of interpolating across the gap. Every other field, and normal running and falling, keeps the default smoothing. See Frontend.