wren/val Public Public: anyone can read this; nobody can change it.
forked from benjivers/val
workbenchthis machine
Files a9ddab6ef893cac593648df9ff6b4d2d3d14b7bc
| 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("a small room hears every tick, a full one every other", () => { |
| 18 | expect(broadcastEvery(0)).toBe(1); |
| 19 | expect(broadcastEvery(1)).toBe(1); |
| 20 | expect(broadcastEvery(2)).toBe(1); |
| 21 | expect(broadcastEvery(3)).toBe(2); |
| 22 | expect(broadcastEvery(10)).toBe(2); |
| 23 | }); |
| 24 | |
| 25 | /** A seat that only counts what it was sent. */ |
| 26 | function listen(session: string) { |
| 27 | const got: string[] = []; |
| 28 | attach(session, match().players[0]!.id, `test-${session}`, session, (data) => got.push(data)); |
| 29 | return got; |
| 30 | } |
| 31 | |
| 32 | // The arena is one match shared by the whole test run, and another file may |
| 33 | // be holding a seat of its own while this one runs; what is checked is that |
| 34 | // the loop broadcasts at exactly the cadence the room it is in asks for. |
| 35 | test("the loop sends at the cadence the seat count asks for", () => { |
| 36 | const one = listen("rate-a"); |
| 37 | const two = listen("rate-b"); |
| 38 | const every = broadcastEvery(seatCount()); |
| 39 | advance(1); |
| 40 | // Thirty ticks to the second, one snapshot every `every` of them. |
| 41 | expect(one.length).toBe(30 / every); |
| 42 | expect(two.length).toBe(one.length); |
| 43 | release("rate-a", "rate-a"); |
| 44 | release("rate-b", "rate-b"); |
| 45 | }); |
| 46 | |
| 47 | test("a full room halves the cadence rather than multiplying the traffic", () => { |
| 48 | const seats = ["rate-c", "rate-d", "rate-e"].map(listen); |
| 49 | expect(seatCount()).toBeGreaterThan(2); |
| 50 | advance(1); |
| 51 | for (const got of seats) expect(got.length).toBe(15); |
| 52 | for (const session of ["rate-c", "rate-d", "rate-e"]) release(session, session); |
| 53 | }); |
| 54 | |
| 55 | test("a snapshot stays small enough to send thirty times a second", () => { |
| 56 | // The trade is only worth making if the payload is small. Measured over a |
| 57 | // live round it is 3.2-4.5 KiB a seat, mean 3.4 — a hundred KiB a second |
| 58 | // at 30Hz, and at most two seats are sent that much. This is the number |
| 59 | // the pull quotes, so it is checked here rather than assumed; the ceiling |
| 60 | // is loose enough not to fail on a busy tick and tight enough that a |
| 61 | // snapshot which doubled in size would say so. |
| 62 | for (let i = 0; i < 40 && match().phase !== "live"; i++) advance(1); |
| 63 | const bytes = Buffer.byteLength(snapshot(match().players[0]!.id)); |
| 64 | expect(bytes).toBeGreaterThan(200); |
| 65 | expect(bytes).toBeLessThan(8000); |
| 66 | }); |