import assert from "node:assert/strict"; import test from "node:test"; import { FLUX_BOT_IDS, FLUX_SNAPSHOT_RATE, FLUX_TICK_RATE, fluxGame, } from "../dist/index.js"; function createThrustPacket(sequence, targetTick, thrust = true) { return { sequence, targetTick, observedTick: Math.max(0, targetTick - 1), input: { thrust }, }; } test("Flux Relay is a complete game defined through the public authoring API", () => { assert.equal(fluxGame.tickRateHz, FLUX_TICK_RATE); assert.equal(fluxGame.snapshotRateHz, FLUX_SNAPSHOT_RATE); const server = fluxGame.createServer(); server.addPlayer(1); assert.deepEqual(server.submitInput(1, createThrustPacket(1, 1)), { accepted: true, }); const events = []; for (let tick = 0; tick < 180; tick += 1) { events.push(...server.step().events); } assert.ok(server.currentState.cyanScore >= 2); assert.equal(server.currentState.orangeScore, 0); assert.ok( events.some((event) => event.type === "round-won" && event.team === "cyan"), ); assert.ok(server.currentState.players.some((player) => player.id === FLUX_BOT_IDS[0])); }); test("Flux snapshots disclose exact energy only to its owner", () => { const server = fluxGame.createServer(); server.addPlayer(1); server.addPlayer(2); const playerOneView = server.createSnapshot(1, 0).state; const playerTwoView = server.createSnapshot(2, 0).state; assert.equal(playerOneView.players.find((player) => player.id === 1)?.energy, 100); assert.equal(playerOneView.players.find((player) => player.id === 2)?.energy, null); assert.equal(playerTwoView.players.find((player) => player.id === 1)?.energy, null); assert.equal(playerTwoView.players.find((player) => player.id === 2)?.energy, 100); assert.ok( playerOneView.players .filter((player) => player.bot) .every((player) => player.energy === null), ); assert.equal("nextEventId" in playerOneView, false); }); test("the same private Flux event becomes a self cue or anonymous field cue", () => { const server = fluxGame.createServer(); server.addPlayer(1); server.addPlayer(2); const player = server.currentState.players.find((candidate) => candidate.id === 1); player.energy = 0.2; server.submitInput(1, createThrustPacket(1, 1)); const result = server.step(); const overheat = result.events.find((event) => event.type === "overheated"); assert.ok(overheat); const ownCue = server.createPerceptions(1, [overheat])[0]; const fieldCue = server.createPerceptions(2, [overheat])[0]; assert.equal(ownCue.scope, "self"); assert.equal(fieldCue.scope, "field"); assert.equal("playerId" in ownCue, false); assert.equal("playerId" in fieldCue, false); }); test("Flux uses the generic protocol, prediction, acknowledgement, and reconciliation", () => { const server = fluxGame.createServer(); server.addPlayer(7); const client = fluxGame.createClient(); client.initialize(7, server.createSnapshot(7, 10)); const packet = client.createInput({ thrust: true }, 1); const wirePacket = fluxGame.protocol.decodeClient( fluxGame.protocol.encodeClient({ kind: "input", packet }), ); assert.equal(wirePacket.kind, "input"); assert.deepEqual(wirePacket.packet, packet); assert.equal(server.submitInput(7, packet).accepted, true); client.step(); const result = server.step(); client.acknowledge(result.acknowledgements[0].sequence); const wireSnapshot = fluxGame.protocol.decodeServer( fluxGame.protocol.encodeServer({ kind: "snapshot", snapshot: server.createSnapshot(7, 20), }), ); assert.equal(wireSnapshot.kind, "snapshot"); client.reconcile(wireSnapshot.snapshot); const local = client.currentState.players.find((player) => player.id === 7); assert.equal(local.thrust, true); assert.ok(local.energy < 100); });