/** * The body a snapshot must carry, stated once. * * The client renderer (`scene.js`, `topdown.js`, `presence.js`, `gun.js`) * reads fixed fields off every body in `snap.players` and off `snap.me`. A * game that renames one — `down` for `alive` — is not refused by anything at * runtime: every enemy simply draws as a corpse. So the shape is a contract * here, and each game tests its real snapshot against it. * * Every body in `snap.players` has: * id: string who; `me` is skipped in the scene by this id * name: string the tag over the body * x, y: number where, in world units * angle: number which way it faces, radians * alive: boolean false draws a corpse * hp: number 0..100; a bar shows under 100 * mate: boolean on the viewer's side * * `snap.me` has, besides id, x, y, angle, alive and hp: * ammo: number rounds in the magazine * reloading: boolean mid-reload */ type Kind = "string" | "number" | "boolean"; const BODY: Record = { id: "string", name: "string", x: "number", y: "number", angle: "number", alive: "boolean", hp: "number", mate: "boolean", }; const ME: Record = { id: "string", x: "number", y: "number", angle: "number", alive: "boolean", hp: "number", ammo: "number", reloading: "boolean", }; function problems(value: unknown, shape: Record, label: string): string[] { if (typeof value !== "object" || value === null) return [`${label} is not an object`]; const out: string[] = []; for (const [field, kind] of Object.entries(shape)) { const got = (value as Record)[field]; if (typeof got !== kind || (kind === "number" && !Number.isFinite(got))) out.push(`${label}.${field} should be a ${kind}, is ${String(got)}`); } return out; } /** What is wrong with one body of `snap.players`; empty when it is fit to draw. */ export const bodyProblems = (body: unknown, label = "body"): string[] => problems(body, BODY, label); /** What is wrong with `snap.me`; empty when the gun and the camera can read it. */ export const meProblems = (me: unknown): string[] => problems(me, ME, "me"); /** Every problem in a snapshot's bodies and `me`. */ export function snapshotProblems(snap: { me?: unknown; players?: unknown }): string[] { const players = Array.isArray(snap.players) ? snap.players : null; return [ ...meProblems(snap.me), ...(players ? players.flatMap((p, i) => bodyProblems(p, `players[${i}]`)) : ["players is not an array"]), ]; }