Instance
An instance is a single Pong match: the court two players share and the state that drives it. It simulates every tick, so it carries both state and one authoritative update. The update runs on the server: it bounces the ball off the walls and paddles, then runs the phase machine, scoring and the serve.
<gamehoster-config-contentRoot>/
game.gamehoster.org/
gamehoster-games/
pong/
gamehoster-instance/
gamehoster-instance-schema.json
gamehoster-instance-update.js
Contents
The instance folder holds its state schema and one update script.
| Name | Type | Description |
|---|---|---|
gamehoster-instance-schema.json | file | The court and tuning constants. |
gamehoster-instance-update.js | file | The authoritative per-tick game logic. |
gamehoster-instance-schema.json
The match's state, all public, streamed to both players. The
phase (waiting, playing or over) and
winner drive the flow; the rest are the fixed court size, paddle size, position and
speed, ball speed, and the target score that wins.
[
{
"gamehoster-instance-schema-name": "phase",
"gamehoster-instance-schema-type": "string",
"gamehoster-instance-schema-default": "waiting",
"gamehoster-instance-schema-visibility": "public"
},
{
"gamehoster-instance-schema-name": "winner",
"gamehoster-instance-schema-type": "string",
"gamehoster-instance-schema-default": "none",
"gamehoster-instance-schema-visibility": "public"
},
{
"gamehoster-instance-schema-name": "courtW",
"gamehoster-instance-schema-type": "int",
"gamehoster-instance-schema-default": 800,
"gamehoster-instance-schema-visibility": "public"
},
{
"gamehoster-instance-schema-name": "courtH",
"gamehoster-instance-schema-type": "int",
"gamehoster-instance-schema-default": 480,
"gamehoster-instance-schema-visibility": "public"
},
{
"gamehoster-instance-schema-name": "paddleH",
"gamehoster-instance-schema-type": "int",
"gamehoster-instance-schema-default": 80,
"gamehoster-instance-schema-visibility": "public"
},
{
"gamehoster-instance-schema-name": "paddleX",
"gamehoster-instance-schema-type": "int",
"gamehoster-instance-schema-default": 24,
"gamehoster-instance-schema-visibility": "public"
},
{
"gamehoster-instance-schema-name": "paddleSpeed",
"gamehoster-instance-schema-type": "number",
"gamehoster-instance-schema-default": 8,
"gamehoster-instance-schema-visibility": "public"
},
{
"gamehoster-instance-schema-name": "ballSpeed",
"gamehoster-instance-schema-type": "number",
"gamehoster-instance-schema-default": 6,
"gamehoster-instance-schema-visibility": "public"
},
{
"gamehoster-instance-schema-name": "target",
"gamehoster-instance-schema-type": "int",
"gamehoster-instance-schema-default": 11,
"gamehoster-instance-schema-visibility": "public"
}
]
gamehoster-instance-update.js
The instance's single authoritative update, run each tick on the server. It bounces the ball off the
top and bottom walls and off either paddle, angling the rebound by where the ball met the paddle, then
runs the phase machine: it starts a match once both players are
ready, judges a goal when the ball crosses an edge, bumps
the scorer, ends the game at the target score, and otherwise re-serves. Both the opening
serve direction and the ball's launch angle come from
Gamehoster.Entropy(), so the randomness is
authoritative and identical everywhere.
// The instance's single authoritative update, run each tick on the server: bounce the ball off
// the walls and paddles, then run the phase machine, scoring and the serve. (This was two passes,
// -update-frontend.js and -update-backend.js; both always ran on the server in this order.)
const here = Gamehoster.Context.instanceId
const phase = Gamehoster.Instance.State.Get(here, "phase")
const players = Gamehoster.Player.List()
const courtH = Gamehoster.Instance.State.Get(here, "courtH")
const courtW = Gamehoster.Instance.State.Get(here, "courtW")
// put the ball back in the centre, heading towards `dir` (+1 right, -1 left)
function serve(dir) {
Gamehoster.Instance.State.Set(here, "phase", "playing")
let b = Gamehoster.Entity.List("ball")[0]
if (b === undefined) b = Gamehoster.Entity.Spawn("ball", {})
const speed = Gamehoster.Instance.State.Get(here, "ballSpeed")
Gamehoster.Entity.State.Set(b, "x", courtW / 2)
Gamehoster.Entity.State.Set(b, "y", courtH / 2)
Gamehoster.Entity.State.Set(b, "vx", dir * speed)
Gamehoster.Entity.State.Set(b, "vy", (Gamehoster.Entropy() * 2 - 1) * speed * 0.5) // authoritative → Entropy
// bump the public reset counter: the front-end smooth body snaps the ball to the centre on this
// change instead of interpolating it across the court from wherever it went out.
Gamehoster.Entity.State.Set(b, "resets", Gamehoster.Entity.State.Get(b, "resets") + 1)
}
// ── gathering / results: start a game once both players are ready ──
if (phase === "waiting" || phase === "over") {
if (players.length === 2 && players.every(id => Gamehoster.Player.State.Get(id, "ready"))) {
for (const id of players) {
Gamehoster.Player.State.Set(id, "score", 0)
Gamehoster.Player.State.Set(id, "ready", false)
}
Gamehoster.Instance.State.Set(here, "winner", "none")
serve(Gamehoster.Entropy() < 0.5 ? 1 : -1) // authoritative coin-flip → Entropy
}
return
}
// ── playing: bounce the ball off the top and bottom walls and off the paddles ──
const ball = Gamehoster.Entity.List("ball")[0]
if (ball === undefined) return
const paddleX = Gamehoster.Instance.State.Get(here, "paddleX")
const paddleH = Gamehoster.Instance.State.Get(here, "paddleH")
const x = Gamehoster.Entity.State.Get(ball, "x")
const y = Gamehoster.Entity.State.Get(ball, "y")
const vx = Gamehoster.Entity.State.Get(ball, "vx")
const vy = Gamehoster.Entity.State.Get(ball, "vy")
if (y < 0 || y > courtH) {
Gamehoster.Entity.State.Set(ball, "y", Math.max(0, Math.min(courtH, y)))
Gamehoster.Entity.State.Set(ball, "vy", -vy)
}
for (const id of Gamehoster.Player.List()) {
const side = Gamehoster.Player.State.Get(id, "side")
const px = side === "left" ? paddleX : courtW - paddleX
const py = Gamehoster.Player.State.Get(id, "y")
const toward = side === "left" ? vx < 0 : vx > 0
if (toward && Math.abs(x - px) < Math.abs(vx) + 2 && Math.abs(y - py) < paddleH / 2) {
Gamehoster.Entity.State.Set(ball, "vx", -vx)
Gamehoster.Entity.State.Set(ball, "vy", vy + ((y - py) / (paddleH / 2)) * 2)
}
}
// ── judge a goal: the ball past a court edge scores for the other side ──
const bx = Gamehoster.Entity.State.Get(ball, "x")
if (bx < 0 || bx > courtW) {
const side = bx < 0 ? "right" : "left"
const scorer = players.find(id => Gamehoster.Player.State.Get(id, "side") === side)
const score = Gamehoster.Player.State.Get(scorer, "score") + 1
Gamehoster.Player.State.Set(scorer, "score", score)
if (score >= Gamehoster.Instance.State.Get(here, "target")) {
Gamehoster.Instance.State.Set(here, "winner", side)
Gamehoster.Instance.State.Set(here, "phase", "over")
} else {
serve(side === "left" ? 1 : -1)
}
}