Player

The files that describe a Pong player: the one field they supply when they join, the per-instance state the server keeps for them, their side, paddle, score and ready flag, while they are connected, and the two scripts the server runs, one the moment they join, one when they leave.

<gamehoster-config-contentRoot>/
  game.gamehoster.org/
    gamehoster-games/
      pong/
        gamehoster-player/
          gamehoster-player-join-schema.json
          gamehoster-player-schema.json
          gamehoster-player-join.js
          gamehoster-player-leave.js
The gamehoster-player/ folder: the join and state schemas and the two lifecycle scripts.

Contents

The player folder holds two schemas and the two lifecycle scripts.

NameTypeDescription
gamehoster-player-join-schema.jsonfileThe data a player supplies on connect.
gamehoster-player-schema.jsonfileThe per-player state.
gamehoster-player-join.jsfileRoute the player in and place their paddle.
gamehoster-player-leave.jsfileDrop a match back to waiting when a player leaves.

gamehoster-player-join-schema.json

The shape of the data a client sends to join. Pong asks for one field, the player's display name, which the join script copies onto their state.

[
  {
    "gamehoster-player-join-schema-name": "name",
    "gamehoster-player-join-schema-type": "string"
  }
]
pong/gamehoster-player/gamehoster-player-join-schema.json

gamehoster-player-schema.json

The per-instance state carried for each player. Every field is public, so the server owns it and streams it to both players. name, side, ready and score drive the match; the paddle position y is moved on the server by the player's moveY commands, and every paddle, your own included, is drawn from the interpolated server frame a fixed buffer behind.

[
  {
    "gamehoster-player-schema-name": "name",
    "gamehoster-player-schema-type": "string",
    "gamehoster-player-schema-visibility": "public"
  },
  {
    "gamehoster-player-schema-name": "side",
    "gamehoster-player-schema-type": "string",
    "gamehoster-player-schema-visibility": "public"
  },
  {
    "gamehoster-player-schema-name": "ready",
    "gamehoster-player-schema-type": "bool",
    "gamehoster-player-schema-default": false,
    "gamehoster-player-schema-visibility": "public"
  },
  {
    "gamehoster-player-schema-name": "score",
    "gamehoster-player-schema-type": "int",
    "gamehoster-player-schema-default": 0,
    "gamehoster-player-schema-visibility": "public"
  },
  {
    "gamehoster-player-schema-name": "y",
    "gamehoster-player-schema-type": "number",
    "gamehoster-player-schema-default": 240,
    "gamehoster-player-schema-visibility": "public",
    "gamehoster-player-schema-min": 0,
    "gamehoster-player-schema-max": 480,
    "gamehoster-player-schema-decimals": 1
  }
]
pong/gamehoster-player/gamehoster-player-schema.json

gamehoster-player-join.js

Runs on the server as a player connects. It first matchmakes: it joins a game still waiting for an opponent, or creates a fresh one. Then it sets up their state: the free side (left or right), their supplied name, a zero score, a cleared ready flag, and a paddle centred on the court.

// Runs on the server the moment a player connects. It first routes them into an
// instance (a game still waiting for an opponent, or a fresh one), then sets up
// their per-instance state (a free side, their name, a centred paddle).

// route: join a waiting game that has room, else start a new one
const open = Gamehoster.Instance.List().find(id =>
  Gamehoster.Instance.Info(id).players < Gamehoster.Instance.Info(id).capacity &&
  Gamehoster.Instance.State.Get(id, "phase") === "waiting")

Gamehoster.Instance.Join(open !== undefined ? open : Gamehoster.Instance.Create())

// set up: whichever side is free. Now that we have joined, Gamehoster.Context.playerId
// names us and Context.instanceId our court.
const me = Gamehoster.Context.playerId
const here = Gamehoster.Context.instanceId
const taken = Gamehoster.Player.List()
  .filter(id => id !== me)
  .map(id => Gamehoster.Player.State.Get(id, "side"))

Gamehoster.Player.State.Set(me, "side", taken.includes("left") ? "right" : "left")
Gamehoster.Player.State.Set(me, "name", Gamehoster.Join.Get("name"))
Gamehoster.Player.State.Set(me, "score", 0)
Gamehoster.Player.State.Set(me, "ready", false)
Gamehoster.Player.State.Set(me, "y", Gamehoster.Instance.State.Get(here, "courtH") / 2)
pong/gamehoster-player/gamehoster-player-join.js

gamehoster-player-leave.js

Runs when a player disconnects. The engine reclaims their paddle; if a match was in play, the instance drops back to waiting so it can pair up with the next player who arrives.

// A player has left. The engine reclaims their paddle; if a match was in play,
// drop back to waiting for a new opponent.

const here = Gamehoster.Context.instanceId
if (Gamehoster.Instance.State.Get(here, "phase") === "playing")
  Gamehoster.Instance.State.Set(here, "phase", "waiting")
pong/gamehoster-player/gamehoster-player-leave.js

gamehoster-player-predict.js

Client only: predicts your OWN paddle. The baked client runs it each tick, fed the live target the tick body sets through Gamehoster.Input.Set, moves the paddle toward it at the paddle speed (the same step the server's moveY applies), then eases toward the authoritative position so prediction and server stay together. Your paddle answers the instant you move; the ball and the rival paddle are just interpolated.

// Client-only: predict our OWN paddle. The client runtime runs this each tick, fed our live
// target (where the pointer or held key wants the paddle), moves the paddle toward it at the
// paddle speed, then eases toward the authoritative position. So the paddle answers our input at
// once while staying true to the server. The ball and the rival paddle are just interpolated.

const me = Gamehoster.Context.playerId
const here = Gamehoster.Context.instanceId
if (Gamehoster.Instance.State.Get(here, "phase") !== "playing") return

const speed = Gamehoster.Instance.State.Get(here, "paddleSpeed")
const half  = Gamehoster.Instance.State.Get(here, "paddleH") / 2
const court = Gamehoster.Instance.State.Get(here, "courtH")

let y = Gamehoster.Player.State.Get(me, "y")
if (typeof y !== "number") { const a = Gamehoster.Authoritative.Get(me, "y"); y = typeof a === "number" ? a : court / 2 }

// move toward the target this tick, capped at the paddle's speed (matches the server's moveY)
const target = Gamehoster.Input.Get("targetY")
if (typeof target === "number") { const d = target - y; y += Math.max(-speed, Math.min(speed, d)) }

// ease toward the authoritative paddle so prediction and server stay together
const ay = Gamehoster.Authoritative.Get(me, "y")
if (typeof ay === "number") y += (ay - y) * 0.15

Gamehoster.Player.State.Set(me, "y", Math.max(half, Math.min(court - half, y)))
pong/gamehoster-player/gamehoster-player-predict.js