Files
syncer/packages/shared/test/flux.test.mjs
Syncer Deploy 96b73922cc
All checks were successful
build / image (push) Successful in 50s
add generic stateful input streaming
2026-08-28 11:27:53 -03:00

116 lines
3.9 KiB
JavaScript

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);
const thrustPacket = createThrustPacket(1, 1);
assert.deepEqual(server.submitInput(1, thrustPacket), {
accepted: true,
});
const events = [];
for (let tick = 0; tick < 180; tick += 1) {
if (tick > 0 && tick % 6 === 0) {
assert.deepEqual(server.submitInput(1, thrustPacket), { accepted: true });
}
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);
});