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

forked from benjivers/val

workbenchthis machine
Files 674c40ddee5c18295fdc6a04cd14d0d0c8bbfce9
1
/**
2
 * How often the arena speaks.
3
 *
4
 * The simulation is 30Hz either way; what is decided here is how much of it
5
 * the browser is told about, which is the difference between a body drawn
6
 * from fifteen samples a second and one drawn from thirty.
7
 */
8
9
import { expect, test } from "bun:test";
10
import { mkdtempSync } from "node:fs";
11
import { tmpdir } from "node:os";
12
import { join } from "node:path";
13
14
process.env.FORGE_APP_DATA ??= mkdtempSync(join(tmpdir(), "app-"));
15
const { advance, attach, broadcastEvery, match, release, seatCount, snapshot } = await import("./arena.ts");
16
17
test("every room this match can hold hears every tick", () => {
18
  // The third seat used to halve the rate, which drew every other body 42ms
19
  // further in the past for everyone. What it saved is in the comment on the
20
  // rule; the ten-seat room is the whole of this game.
21
  for (let filled = 0; filled <= 10; filled++) expect(broadcastEvery(filled)).toBe(1);
22
  // Past the measured room the old cadence stays, unmeasured rather than right.
23
  expect(broadcastEvery(11)).toBe(2);
24
});
25
26
/** A seat that only counts what it was sent. */
27
function listen(session: string) {
28
  const got: string[] = [];
29
  attach(session, match().players[0]!.id, `test-${session}`, session, (data) => got.push(data));
30
  return got;
31
}
32
33
// The arena is one match shared by the whole test run, and another file may
34
// be holding a seat of its own while this one runs; what is checked is that
35
// the loop broadcasts at exactly the cadence the room it is in asks for.
36
test("the loop sends at the cadence the seat count asks for", () => {
37
  const one = listen("rate-a");
38
  const two = listen("rate-b");
39
  const every = broadcastEvery(seatCount());
40
  advance(1);
41
  // Thirty ticks to the second, one snapshot every `every` of them.
42
  expect(one.length).toBe(30 / every);
43
  expect(two.length).toBe(one.length);
44
  release("rate-a", "rate-a");
45
  release("rate-b", "rate-b");
46
});
47
48
test("a third seat no longer halves what everybody is sent", () => {
49
  const seats = ["rate-c", "rate-d", "rate-e"].map(listen);
50
  expect(seatCount()).toBeGreaterThan(2);
51
  advance(1);
52
  for (const got of seats) expect(got.length).toBe(30);
53
  for (const session of ["rate-c", "rate-d", "rate-e"]) release(session, session);
54
});
55
56
test("a snapshot stays small enough to send thirty times a second", () => {
57
  // The trade is only worth making if the payload is small. Measured over a
58
  // live round it is ~1.6 KiB a seat since the board went sticky and the
59
  // vision filter came in — 48 KiB a second at 30Hz, and every seat is sent
60
  // that much now. This is the number the cadence's comment quotes, so it
61
  // is checked here rather than assumed; the ceiling is loose enough not to
62
  // fail on a busy tick and tight enough that a snapshot which doubled in
63
  // size would say so.
64
  for (let i = 0; i < 40 && match().phase !== "live"; i++) advance(1);
65
  const bytes = Buffer.byteLength(snapshot(match().players[0]!.id));
66
  expect(bytes).toBeGreaterThan(200);
67
  expect(bytes).toBeLessThan(8000);
68
});
69
70
test("the snapshot carries the speed the server will move you at, not the reasons for it", () => {
71
  // The browser used to rebuild this rule from four fields and was right
72
  // only until a fifth was added. What it gets now is the answer: every
73
  // reason the match has to speed a body up, slow it down or hold it still,
74
  // resolved here, in units a second, to a tenth like every other number in
75
  // the snapshot.
76
  for (let i = 0; i < 40 && match().phase !== "live"; i++) advance(1);
77
  const state = match();
78
  const me = state.players[0]!;
79
  const was = { ...me };
80
  const phase = state.phase;
81
  const seen = () => JSON.parse(snapshot(me.id)).me as Record<string, unknown>;
82
  const speed = () => seen().speed;
83
84
  Object.assign(me, { alive: true, haste: 0, slow: 0, planting: 0, defusing: 0, weapon: "classic" });
85
  expect(speed()).toBe(250);
86
  me.weapon = "operator";
87
  expect(speed()).toBe(215);
88
  me.weapon = "classic";
89
  me.haste = 2;
90
  expect(speed()).toBe(337.5); // exactly what the tick will use
91
  me.haste = 0;
92
  me.slow = 2;
93
  expect(speed()).toBe(137.5);
94
  me.slow = 0;
95
96
  // Every way the match holds a body still arrives as a zero, and the client
97
  // is told none of them: a fifth one would need no change over there.
98
  me.planting = 1;
99
  expect(speed()).toBe(0);
100
  me.planting = 0;
101
  me.defusing = 1;
102
  expect(speed()).toBe(0);
103
  me.defusing = 0;
104
  me.alive = false;
105
  expect(speed()).toBe(0);
106
  me.alive = true;
107
  // Including the phases whose tick returns before it moves anybody.
108
  for (const stopped of ["end", "over"] as const) {
109
    state.phase = stopped;
110
    expect(speed()).toBe(0);
111
  }
112
  state.phase = phase;
113
  expect(speed()).toBe(250);
114
115
  // The three fields that existed only to be re-derived are gone with it.
116
  const me2 = seen();
117
  expect(me2).not.toHaveProperty("haste");
118
  expect(me2).not.toHaveProperty("slow");
119
  expect(me2).not.toHaveProperty("defusing");
120
  // What it costs the wire: a name and a number, once a seat a tick.
121
  const field = `"speed":${speed()},`;
122
  expect(snapshot(me.id)).toContain(field);
123
  expect(field.length).toBeLessThanOrEqual(16);
124
125
  Object.assign(me, was);
126
});
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts