1
0
Fork 0
polaris-task-force/tests/int/adjacency.int.spec.ts
2026-09-20 02:43:23 -04:00

303 lines
9.8 KiB
TypeScript

import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { getPayload, type CollectionSlug, type Payload, type Where } from "payload";
import type { Map as MapDoc, Resource, Structure } from "@/payload-types";
import config from "@payload-config";
import dotenv from "dotenv";
import { auraBonusAt, type AuraSourceLike } from "@/lib/base/adjacency";
import { processProductionTick } from "@/lib/base/production";
dotenv.config({ path: ".env" });
const RUN = `aura-${Date.now()}`;
const LEXICAL_EMPTY = {
root: {
children: [{ text: "" }],
direction: null,
format: "" as const,
indent: 0,
type: "text",
version: 1,
},
};
let payload: Payload;
const createdIds = {
maps: [] as number[],
resources: [] as number[],
structureBlueprints: [] as number[],
gameStructures: [] as number[],
classifications: [] as number[],
};
async function makeResource(name: string, massPerUnit = 1): Promise<Resource> {
return (await payload.create({
collection: "resources",
data: {
name,
codeName: `${RUN}-${name.toLowerCase().replace(/\s+/g, "-")}`,
approvalStatus: "in_progress",
type: "physical",
baseValue: 1,
rarity: "common",
unitOfMeasure: "kg",
massPerUnit,
gridWidth: 1,
gridHeight: 1,
},
overrideAccess: true,
depth: 0,
})) as unknown as Resource;
}
async function makeBlueprint(data: {
name: string;
materialResource: number;
outputs?: { resource: number; amountPerHour: number }[];
maxCapacityMass?: number;
aura?: {
effectType: "production_multiplier" | "capacity_mass";
value: number;
rangeMeters: number;
};
}): Promise<Structure> {
return (await payload.create({
collection: "structures",
data: {
name: data.name,
codeName: data.name.toLowerCase().replace(/\s+/g, "_"),
approvalStatus: "in_progress",
description: LEXICAL_EMPTY as unknown as Structure["description"],
category: "logistics",
materials: [{ resource: data.materialResource, amount: 1 }],
constructionDurationMinutes: 1,
terrainType: "land",
maxHealth: 100,
maxCapacityMass: data.maxCapacityMass,
productionOutputs: data.outputs,
...(data.aura ? { aura: data.aura } : {}),
},
overrideAccess: true,
depth: 0,
})) as unknown as Structure;
}
async function makeSite(data: {
name: string;
blueprintId: number;
materialResource: number;
point?: [number, number];
constructionStatus?: "awaiting_materials" | "building" | "complete";
}): Promise<{ id: number }> {
return (await payload.create({
collection: "game-structures",
data: {
name: data.name,
type: data.blueprintId,
map: createdIds.maps[0],
coordinates: data.point ?? [3000, 3000],
constructionStatus: data.constructionStatus ?? "complete",
storedResources: [{ resource: data.materialResource, amount: 10, gridX: 0, gridY: 0 }],
},
overrideAccess: true,
depth: 0,
})) as unknown as { id: number };
}
async function assignLabor(siteId: number): Promise<void> {
const classification = (await payload.create({
collection: "labor-classifications",
data: {
name: `${RUN} Crew ${siteId}`,
key: `${RUN}-crew-${siteId}`,
defaultUnitSalary: 1,
availableHeadcount: 50,
},
overrideAccess: true,
depth: 0,
})) as unknown as { id: number };
createdIds.classifications.push(classification.id);
await payload.create({
collection: "npc-staffing",
data: {
structure: siteId,
positionType: "other",
headcount: 5,
laborClassification: classification.id,
positionTitle: "Labor crew",
status: "active",
},
overrideAccess: true,
});
}
async function voidAmount(siteId: number, resourceId: number): Promise<number> {
const doc = (await payload.findByID({
collection: "game-structures",
id: siteId,
depth: 1,
overrideAccess: true,
})) as unknown as { voidStorage: { resource: number | Resource; amount: number }[] };
return doc.voidStorage.reduce(
(total, entry) =>
total +
((typeof entry.resource === "object" ? entry.resource.id : entry.resource) === resourceId
? entry.amount
: 0),
0,
);
}
describe("Adjacency auras", () => {
describe("pure aura math", () => {
const source = (over: Partial<AuraSourceLike> & { id: number }): AuraSourceLike => ({
coordinates: [5000, 5000],
constructionStatus: "complete",
blueprint: { aura: { effectType: "production_multiplier", value: 0.5, rangeMeters: 2000 } },
...over,
});
const target = { id: 99, coordinates: [6000, 5000] as [number, number] };
it("sums in-range auras and ignores out-of-range, inactive, and self sources", () => {
expect(
auraBonusAt(target, [
source({ id: 1 }),
source({ id: 2 }),
source({ id: 3, coordinates: [50000, 50000] }),
source({ id: 4, constructionStatus: "building" }),
source({
id: 5,
blueprint: { aura: { effectType: "capacity_mass", value: 300, rangeMeters: 2000 } },
}),
]),
).toEqual({ productionMultiplier: 1, capacityMass: 300 });
expect(auraBonusAt(target, [source({ id: 1 }), source({ id: 99 })])).toEqual({
productionMultiplier: 0.5,
capacityMass: 0,
});
expect(auraBonusAt({ id: 7, coordinates: null }, [source({ id: 1 })])).toEqual({
productionMultiplier: 0,
capacityMass: 0,
});
});
});
describe("production tick", () => {
beforeAll(async () => {
payload = await getPayload({ config });
createdIds.maps.push(
(
(await payload.create({
collection: "maps",
data: {
name: `${RUN} map`,
worldSizeWidth: 8192,
worldSizeHeight: 8192,
basemapMode: "image",
},
overrideAccess: true,
depth: 0,
})) as unknown as MapDoc
).id,
);
});
afterAll(async () => {
const sweep = async (collection: CollectionSlug, where: Where) => {
const found = await payload
.find({ collection, where, limit: 500, depth: 0, overrideAccess: true })
.catch(() => null);
for (const doc of found?.docs ?? []) {
await payload.delete({ collection, id: doc.id, overrideAccess: true }).catch(() => {});
}
};
await sweep("npc-staffing", { structure: { in: createdIds.gameStructures } });
await sweep("game-event-logs", { structure: { in: createdIds.gameStructures } });
await sweep("game-structures", { name: { like: `${RUN}%` } });
await sweep("structures", { name: { like: `${RUN}%` } });
await sweep("labor-classifications", { name: { like: `${RUN}%` } });
await sweep("resources", { name: { like: `${RUN}%` } });
await sweep("maps", { name: { like: `${RUN}%` } });
});
it("boosts a neighbor's output with a production aura", async () => {
const timber = await makeResource("Logs");
const fuel = await makeResource("Ethanol");
createdIds.resources.push(timber.id, fuel.id);
const producer = await makeBlueprint({
name: `${RUN} Boiler`,
materialResource: timber.id,
outputs: [{ resource: fuel.id, amountPerHour: 1000 }],
});
const auraTower = await makeBlueprint({
name: `${RUN} Pylon`,
materialResource: timber.id,
aura: { effectType: "production_multiplier", value: 0.5, rangeMeters: 2000 },
});
createdIds.structureBlueprints.push(producer.id, auraTower.id);
const site = await makeSite({
name: `${RUN} Boiler site`,
blueprintId: producer.id,
materialResource: timber.id,
point: [4000, 3000],
});
createdIds.gameStructures.push(site.id);
const tower = await makeSite({
name: `${RUN} Pylon site`,
blueprintId: auraTower.id,
materialResource: timber.id,
point: [3000, 3000],
});
createdIds.gameStructures.push(tower.id);
await assignLabor(site.id);
const start = new Date();
await processProductionTick(payload, undefined, start, [site.id]);
await processProductionTick(payload, undefined, new Date(start.getTime() + 3_600_000), [
site.id,
]);
await expect(voidAmount(site.id, fuel.id)).resolves.toBe(1500);
});
it("raises a neighbor's production capacity with a capacity aura", async () => {
const timber = await makeResource("Sticks");
const fuel = await makeResource("Biodiesel");
createdIds.resources.push(timber.id, fuel.id);
const producer = await makeBlueprint({
name: `${RUN} Still`,
materialResource: timber.id,
outputs: [{ resource: fuel.id, amountPerHour: 1000 }],
maxCapacityMass: 100,
});
const auraTower = await makeBlueprint({
name: `${RUN} Silo Pylon`,
materialResource: timber.id,
aura: { effectType: "capacity_mass", value: 500, rangeMeters: 2000 },
});
createdIds.structureBlueprints.push(producer.id, auraTower.id);
const site = await makeSite({
name: `${RUN} Still site`,
blueprintId: producer.id,
materialResource: timber.id,
point: [4000, 3000],
});
createdIds.gameStructures.push(site.id);
const tower = await makeSite({
name: `${RUN} Silo Pylon site`,
blueprintId: auraTower.id,
materialResource: timber.id,
point: [3000, 3000],
});
createdIds.gameStructures.push(tower.id);
await assignLabor(site.id);
const start = new Date();
await processProductionTick(payload, undefined, start, [site.id]);
await processProductionTick(payload, undefined, new Date(start.getTime() + 3_600_000), [
site.id,
]);
await expect(voidAmount(site.id, fuel.id)).resolves.toBe(600);
});
});
});