add Box3D-powered Bad Movers game
All checks were successful
build / image (push) Successful in 1m39s
All checks were successful
build / image (push) Successful in 1m39s
This commit is contained in:
144
packages/shared/test/movers.test.mjs
Normal file
144
packages/shared/test/movers.test.mjs
Normal file
@@ -0,0 +1,144 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import {
|
||||
FURNITURE,
|
||||
MOVERS_BOT_IDS,
|
||||
MOVERS_PHYSICS_BACKEND,
|
||||
MOVERS_SNAPSHOT_RATE,
|
||||
MOVERS_TICK_RATE,
|
||||
moversGame,
|
||||
} from "../dist/index.js";
|
||||
|
||||
function inputPacket(sequence, targetTick, input = {}) {
|
||||
return {
|
||||
sequence,
|
||||
targetTick,
|
||||
observedTick: Math.max(0, targetTick - 1),
|
||||
input: {
|
||||
forward: 0,
|
||||
strafe: 0,
|
||||
sprint: false,
|
||||
grab: false,
|
||||
throwItem: false,
|
||||
closeDoors: false,
|
||||
...input,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test("Bad Movers is a fourth complete game built through defineMultiplayerGame", () => {
|
||||
assert.equal(moversGame.tickRateHz, MOVERS_TICK_RATE);
|
||||
assert.equal(moversGame.snapshotRateHz, MOVERS_SNAPSHOT_RATE);
|
||||
const server = moversGame.createServer();
|
||||
assert.equal(server.currentState.players.filter((player) => player.bot).length, MOVERS_BOT_IDS.length);
|
||||
assert.equal(server.currentState.furniture.length, Object.keys(FURNITURE).length);
|
||||
assert.deepEqual(MOVERS_PHYSICS_BACKEND, {
|
||||
name: "Box3D",
|
||||
version: "0.1.0",
|
||||
bindingVersion: "0.2.0",
|
||||
runtime: "WebAssembly SIMD",
|
||||
solver: "single-threaded deterministic",
|
||||
subSteps: 4,
|
||||
});
|
||||
|
||||
server.addPlayer(1);
|
||||
server.addPlayer(2);
|
||||
assert.equal(server.currentState.players.find((player) => player.id === 1).team, "yellow");
|
||||
assert.equal(server.currentState.players.find((player) => player.id === 2).team, "blue");
|
||||
});
|
||||
|
||||
test("real Box3D gravity advances full 3D furniture transforms", () => {
|
||||
const server = moversGame.createServer();
|
||||
const box = server.currentState.furniture.find((item) => item.kind === "mystery-box");
|
||||
Object.assign(box, {
|
||||
x: 0,
|
||||
y: 8,
|
||||
z: 0,
|
||||
velocityX: 0,
|
||||
velocityY: 0,
|
||||
velocityZ: 0,
|
||||
});
|
||||
for (let tick = 0; tick < 12; tick += 1) server.step();
|
||||
assert.ok(box.y < 7, `expected Box3D gravity to drop the box, got y=${box.y}`);
|
||||
assert.ok(box.velocityY < 0);
|
||||
assert.ok(Number.isFinite(box.rotationX + box.rotationY + box.rotationZ + box.rotationW));
|
||||
});
|
||||
|
||||
test("independent Box3D authorities produce the same deterministic match", () => {
|
||||
const first = moversGame.createServer();
|
||||
const second = moversGame.createServer();
|
||||
for (let tick = 0; tick < 600; tick += 1) {
|
||||
first.step();
|
||||
second.step();
|
||||
}
|
||||
assert.deepEqual(first.currentState, second.currentState);
|
||||
});
|
||||
|
||||
test("only the owning mover receives exact stamina", () => {
|
||||
const server = moversGame.createServer();
|
||||
server.addPlayer(1);
|
||||
server.addPlayer(2);
|
||||
const yellowView = server.createSnapshot(1, 0).state;
|
||||
const blueView = server.createSnapshot(2, 0).state;
|
||||
assert.equal(yellowView.players.find((player) => player.id === 1).stamina, 100);
|
||||
assert.equal(yellowView.players.find((player) => player.id === 2).stamina, null);
|
||||
assert.equal(blueView.players.find((player) => player.id === 1).stamina, null);
|
||||
assert.equal(blueView.players.find((player) => player.id === 2).stamina, 100);
|
||||
assert.equal("nextEventId" in yellowView, false);
|
||||
});
|
||||
|
||||
test("furniture only scores when a mover closes their own truck doors", () => {
|
||||
const server = moversGame.createServer();
|
||||
server.addPlayer(1);
|
||||
const player = server.currentState.players.find((candidate) => candidate.id === 1);
|
||||
const urn = server.currentState.furniture.find((item) => item.kind === "urn");
|
||||
Object.assign(player, { x: -34, z: 0 });
|
||||
Object.assign(player, { carryingId: urn.id, grabbing: false });
|
||||
Object.assign(urn, { x: -35, z: 0, damage: 10, carriedBy: player.id });
|
||||
assert.equal(server.currentState.yellowScore, 0);
|
||||
|
||||
assert.equal(server.submitInput(1, inputPacket(1, 1, { closeDoors: true })).accepted, true);
|
||||
const events = server.step().events;
|
||||
assert.equal(urn.securedBy, "yellow");
|
||||
assert.equal(server.currentState.yellowScore, Math.round(FURNITURE.urn.value * 0.9));
|
||||
assert.ok(events.some((event) => event.type === "doors" && event.team === "yellow"));
|
||||
assert.ok(events.some((event) => event.type === "secured" && event.itemId === urn.id));
|
||||
});
|
||||
|
||||
test("Bad Movers bots can complete physical deliveries headlessly", () => {
|
||||
const server = moversGame.createServer();
|
||||
let secured = 0;
|
||||
for (let tick = 0; tick < 2_400; tick += 1) {
|
||||
for (const event of server.step().events) {
|
||||
if (event.type === "secured") secured += 1;
|
||||
}
|
||||
}
|
||||
assert.ok(secured >= 2, `expected bot deliveries, saw ${secured}`);
|
||||
assert.ok(server.currentState.yellowScore + server.currentState.blueScore > 0);
|
||||
assert.ok(server.currentState.furniture.every((item) => Number.isFinite(item.x + item.z + item.damage)));
|
||||
});
|
||||
|
||||
test("Bad Movers input and state round-trip through the generic protocol", () => {
|
||||
const server = moversGame.createServer();
|
||||
server.addPlayer(7);
|
||||
const client = moversGame.createClient();
|
||||
client.initialize(7, server.createSnapshot(7, 10));
|
||||
const packet = client.createInput({
|
||||
forward: 1,
|
||||
strafe: -0.5,
|
||||
sprint: true,
|
||||
grab: true,
|
||||
throwItem: false,
|
||||
closeDoors: false,
|
||||
}, 1);
|
||||
const decoded = moversGame.protocol.decodeClient(
|
||||
moversGame.protocol.encodeClient({ kind: "input", packet }),
|
||||
);
|
||||
assert.equal(decoded.kind, "input");
|
||||
assert.deepEqual(decoded.packet, packet);
|
||||
assert.equal(server.submitInput(7, decoded.packet).accepted, true);
|
||||
server.step();
|
||||
client.step();
|
||||
client.reconcile(server.createSnapshot(7, 20));
|
||||
assert.ok(client.currentState.players.some((player) => player.id === 7));
|
||||
});
|
||||
Reference in New Issue
Block a user