import assert from "node:assert/strict"; import test from "node:test"; import { DEAD_AIR_ARTIFACT_SPAWN, DEAD_AIR_EXTRACTION, DEAD_AIR_SNAPSHOT_RATE, DEAD_AIR_TICK_RATE, DEAD_AIR_WARDEN_ID_BASE, deadAirGame, } from "../dist/index.js"; function input(overrides = {}) { return { forward: 0, strafe: 0, yaw: 0, pitch: 0, fire: false, sprint: false, reload: false, interact: false, toggleFlashlight: false, throwDecoy: false, ...overrides, }; } test("DEAD AIR runs through the generic multiplayer contract", () => { assert.equal(deadAirGame.tickRateHz, DEAD_AIR_TICK_RATE); assert.equal(deadAirGame.snapshotRateHz, DEAD_AIR_SNAPSHOT_RATE); const server = deadAirGame.createServer(); server.addPlayer(1); const player = server.currentState.players.find((candidate) => candidate.id === 1); player.x = DEAD_AIR_ARTIFACT_SPAWN.x; player.z = DEAD_AIR_ARTIFACT_SPAWN.z; assert.deepEqual(server.submitInput(1, { sequence: 1, targetTick: 1, observedTick: 0, input: input({ interact: true }), }), { accepted: true }); const grabbed = server.step(); assert.equal(player.carryingArtifact, true); assert.ok(grabbed.events.some((event) => event.type === "artifact" && event.action === "grabbed")); player.x = DEAD_AIR_EXTRACTION.x; player.z = DEAD_AIR_EXTRACTION.z; const extracted = server.step(); assert.equal(player.extractions, 1); assert.ok(extracted.events.some((event) => event.type === "artifact" && event.action === "extracted")); }); test("an occluded warden never appears in snapshots and becomes anonymous sound", () => { const server = deadAirGame.createServer(); server.addPlayer(1); const viewer = server.currentState.players.find((player) => player.id === 1); const hidden = server.currentState.players.find((player) => player.id === DEAD_AIR_WARDEN_ID_BASE); for (const player of server.currentState.players) { if (player.bot && player.id !== hidden.id) player.alive = false; } viewer.x = -25; viewer.z = 0; viewer.yaw = Math.PI / 2; viewer.flashlight = true; hidden.x = -10; hidden.z = 0; hidden.flashlight = false; const snapshot = server.createSnapshot(1, 0).state; assert.equal(snapshot.players.some((player) => player.id === hidden.id), false); assert.equal(JSON.stringify(snapshot).includes(hidden.id.toString()), false); const perception = server.createPerceptions(1, [{ id: 777, type: "shot", sourceId: hidden.id, x: hidden.x, z: hidden.z, yaw: hidden.yaw, pitch: 0, }])[0]; assert.equal(perception.type, "sound"); assert.equal(perception.cue, "gunshot"); assert.equal("sourceId" in perception, false); assert.equal("x" in perception, false); assert.equal("z" in perception, false); assert.ok(Number.isInteger(perception.direction)); }); test("private objective coordinates are redacted outside sight", () => { const server = deadAirGame.createServer(); server.addPlayer(1); const viewer = server.currentState.players.find((player) => player.id === 1); viewer.x = -28; viewer.z = 28; viewer.yaw = Math.PI; viewer.flashlight = false; const snapshot = server.createSnapshot(1, 0).state; assert.deepEqual(snapshot.artifact, { visible: false, x: null, z: null, carrierId: null, }); assert.equal("artifactX" in snapshot, false); assert.equal("nextEventId" in snapshot, false); }); test("visible wardens expose pose but redact combat inventory", () => { const server = deadAirGame.createServer(); server.addPlayer(1); const viewer = server.currentState.players.find((player) => player.id === 1); const visible = server.currentState.players.find((player) => player.id === DEAD_AIR_WARDEN_ID_BASE); viewer.x = -30; viewer.z = 30; viewer.yaw = Math.PI / 2; visible.x = -25; visible.z = 30; visible.health = 31; visible.magazine = 2; const replicated = server.createSnapshot(1, 0).state.players.find((player) => player.id === visible.id); assert.ok(replicated); assert.equal(replicated.x, visible.x); assert.equal(replicated.health, null); assert.equal(replicated.magazine, null); assert.equal(replicated.reserve, null); });