wren/val Public Public: anyone can read this; nobody can change it.

forked from benjivers/val

workbenchthis machine
Files wren/42-engine-renderer-colour-bodies-by-side-no
1
/**
2
 * The body a snapshot must carry, stated once.
3
 *
4
 * The client renderer (`scene.js`, `topdown.js`, `presence.js`, `gun.js`)
5
 * reads fixed fields off every body in `snap.players` and off `snap.me`. A
6
 * game that renames one — `down` for `alive` — is not refused by anything at
7
 * runtime: every enemy simply draws as a corpse. So the shape is a contract
8
 * here, and each game tests its real snapshot against it.
9
 *
10
 * Every body in `snap.players` has:
11
 *   id: string        who; `me` is skipped in the scene by this id
12
 *   name: string      the tag over the body
13
 *   x, y: number      where, in world units
14
 *   angle: number     which way it faces, radians
15
 *   alive: boolean    false draws a corpse
16
 *   hp: number        0..100; a bar shows under 100
17
 *   mate: boolean     on the viewer's side
18
 *
19
 * and may have:
20
 *   side: string      which side it is on, for a game of three sides or
21
 *                     more; each non-mate side then draws in its own colour
22
 *                     (`static/engine/sides.js`). Leave it out and every
23
 *                     non-mate draws in the one enemy colour, as before.
24
 *
25
 * `snap.me` has, besides id, x, y, angle, alive and hp:
26
 *   ammo: number        rounds in the magazine
27
 *   reloading: boolean  mid-reload
28
 */
29
30
type Kind = "string" | "number" | "boolean";
31
32
const BODY: Record<string, Kind> = {
33
  id: "string",
34
  name: "string",
35
  x: "number",
36
  y: "number",
37
  angle: "number",
38
  alive: "boolean",
39
  hp: "number",
40
  mate: "boolean",
41
};
42
43
/** Fields a body may leave out, but must type right when it sends them. */
44
const BODY_OPTIONAL: Record<string, Kind> = {
45
  side: "string",
46
};
47
48
const ME: Record<string, Kind> = {
49
  id: "string",
50
  x: "number",
51
  y: "number",
52
  angle: "number",
53
  alive: "boolean",
54
  hp: "number",
55
  ammo: "number",
56
  reloading: "boolean",
57
};
58
59
function problems(value: unknown, shape: Record<string, Kind>, label: string, optional: Record<string, Kind> = {}): string[] {
60
  if (typeof value !== "object" || value === null) return [`${label} is not an object`];
61
  const out: string[] = [];
62
  const fields = [...Object.entries(shape), ...Object.entries(optional)];
63
  for (const [field, kind] of fields) {
64
    const got = (value as Record<string, unknown>)[field];
65
    if (got === undefined && field in optional) continue;
66
    if (typeof got !== kind || (kind === "number" && !Number.isFinite(got))) out.push(`${label}.${field} should be a ${kind}, is ${String(got)}`);
67
  }
68
  return out;
69
}
70
71
/** What is wrong with one body of `snap.players`; empty when it is fit to draw. */
72
export const bodyProblems = (body: unknown, label = "body"): string[] => problems(body, BODY, label, BODY_OPTIONAL);
73
74
/** What is wrong with `snap.me`; empty when the gun and the camera can read it. */
75
export const meProblems = (me: unknown): string[] => problems(me, ME, "me");
76
77
/** Every problem in a snapshot's bodies and `me`. */
78
export function snapshotProblems(snap: { me?: unknown; players?: unknown }): string[] {
79
  const players = Array.isArray(snap.players) ? snap.players : null;
80
  return [
81
    ...meProblems(snap.me),
82
    ...(players ? players.flatMap((p, i) => bodyProblems(p, `players[${i}]`)) : ["players is not an array"]),
83
  ];
84
}
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts