Entity

The non-player things. Pong has one entity type: the ball. There is only ever one on the court, spawned by the serve. The ball moves every tick, so it carries a schema and one update, both run authoritatively on the server.

<gamehoster-config-contentRoot>/
  game.gamehoster.org/
    gamehoster-games/
      pong/
        gamehoster-entities/
          ball/
            gamehoster-entity-schema.json
            gamehoster-entity-update.js
            gamehoster-entity-smooth.js
The gamehoster-entities/ folder: one folder per entity type, here just the ball, with a schema, an authoritative update, and a client-only smooth body.

Contents

One folder per entity type; the ball holds a parameter schema and one update.

NameTypeDescription
ball/directoryThe single ball: a schema, an update, and a smooth body.

ball/

The single ball in play. Its fields are the position x, y, the velocity vx, vy, and a resets counter the server bumps on every re-serve, all public, so the ball streams to both players, who draw it from the interpolated server frame a fixed buffer behind. The bounded position and velocity fields carry min/max/decimals range hints; the client-only gamehoster-entity-smooth.js body uses resets to snap the ball across a re-serve instead of sliding it back across the court.

gamehoster-entity-schema.json

[
  {
    "gamehoster-entity-schema-name": "x",
    "gamehoster-entity-schema-type": "number",
    "gamehoster-entity-schema-default": 400,
    "gamehoster-entity-schema-visibility": "public",
    "gamehoster-entity-schema-min": 0,
    "gamehoster-entity-schema-max": 800,
    "gamehoster-entity-schema-decimals": 1
  },
  {
    "gamehoster-entity-schema-name": "y",
    "gamehoster-entity-schema-type": "number",
    "gamehoster-entity-schema-default": 240,
    "gamehoster-entity-schema-visibility": "public",
    "gamehoster-entity-schema-min": 0,
    "gamehoster-entity-schema-max": 480,
    "gamehoster-entity-schema-decimals": 1
  },
  {
    "gamehoster-entity-schema-name": "vx",
    "gamehoster-entity-schema-type": "number",
    "gamehoster-entity-schema-default": 0,
    "gamehoster-entity-schema-visibility": "public",
    "gamehoster-entity-schema-min": -6,
    "gamehoster-entity-schema-max": 6,
    "gamehoster-entity-schema-decimals": 2
  },
  {
    "gamehoster-entity-schema-name": "vy",
    "gamehoster-entity-schema-type": "number",
    "gamehoster-entity-schema-default": 0,
    "gamehoster-entity-schema-visibility": "public",
    "gamehoster-entity-schema-min": -6,
    "gamehoster-entity-schema-max": 6,
    "gamehoster-entity-schema-decimals": 2
  },
  {
    "gamehoster-entity-schema-name": "resets",
    "gamehoster-entity-schema-type": "int",
    "gamehoster-entity-schema-default": 0,
    "gamehoster-entity-schema-visibility": "public"
  }
]
pong/gamehoster-entities/ball/gamehoster-entity-schema.json

gamehoster-entity-update.js

// The ball's per-tick update, run authoritatively on the server: one integration step.
// x/y/vx/vy are public, so the ball's position streams to every viewer each send and the
// client interpolates it between packets. Bouncing and scoring are the instance update's job.

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

const me = Gamehoster.Context.entityId
Gamehoster.Entity.State.Set(me, "x", Gamehoster.Entity.State.Get(me, "x") + Gamehoster.Entity.State.Get(me, "vx"))
Gamehoster.Entity.State.Set(me, "y", Gamehoster.Entity.State.Get(me, "y") + Gamehoster.Entity.State.Get(me, "vy"))
pong/gamehoster-entities/ball/gamehoster-entity-update.js

gamehoster-entity-smooth.js

Client-only. It sits beside the ball's schema and shapes how the ball eases each frame. Everything is smoothed automatically; this body only overrides x/y across a re-serve, snapping to the latest authoritative position (watched through the resets counter) instead of sliding the ball back across the court.

// Client-only: how the ball's streamed fields are shown each render frame. By default the runtime
// interpolates every numeric field between the two samples straddling the render tick, which is
// exactly right while the ball flies across the court. But after a point the server RESETS the
// ball to the court centre, and interpolating position across that jump would slide the ball
// smoothly from where it went out back to the middle — a line straight across the map.
//
// The ball carries a public `resets` counter that the server bumps on every re-centre. While the
// interpolated (smoothed) counter still lags the authoritative one — i.e. the render tick has not
// yet reached the reset in the sample timeline — we SNAP position to the latest authoritative
// value instead of interpolating across the gap. Velocity and normal flight keep the default
// smoothing untouched.
if (Gamehoster.Smooth("resets") !== Gamehoster.Latest("resets")) {
  Gamehoster.Set("x", Gamehoster.Latest("x"))
  Gamehoster.Set("y", Gamehoster.Latest("y"))
}
pong/gamehoster-entities/ball/gamehoster-entity-smooth.js