forked from phenom/healer-man
Release Healer Man 0.1.11
This commit is contained in:
1 parent
5f1a14d8de
commit
13970d03c8
18 files changed
+514
-19
No files matched your search
@@ -0,0 +1,145 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DUNGEON_DEFINITIONS, dungeonCanEnter } from "./dungeonRegistry";
|
||||
import manastormRegistry from "./generated/manastormAssetPackages.json";
|
||||
import type { ManastormStageAssetPackage } from "./manastormStageAssets";
|
||||
import {
|
||||
MAX_SYNCHRONOUS_PARTY_NAVIGATION_TRIANGLES,
|
||||
shouldLoadPartyNavigation,
|
||||
} from "./partyNavigationPolicy";
|
||||
|
||||
interface GlbPrimitive {
|
||||
readonly attributes?: Readonly<Record<string, number>>;
|
||||
readonly indices?: number;
|
||||
readonly mode?: number;
|
||||
}
|
||||
|
||||
interface GlbJson {
|
||||
readonly accessors?: readonly { readonly count?: number }[];
|
||||
readonly meshes?: readonly { readonly primitives?: readonly GlbPrimitive[] }[];
|
||||
}
|
||||
|
||||
const GLB_MAGIC = 0x46546c67;
|
||||
const GLB_JSON_CHUNK = 0x4e4f534a;
|
||||
|
||||
function readGlbJson(filePath: string): GlbJson {
|
||||
const glb = readFileSync(filePath);
|
||||
expect(glb.readUInt32LE(0), filePath).toBe(GLB_MAGIC);
|
||||
expect(glb.readUInt32LE(4), filePath).toBe(2);
|
||||
expect(glb.readUInt32LE(16), filePath).toBe(GLB_JSON_CHUNK);
|
||||
const jsonLength = glb.readUInt32LE(12);
|
||||
return JSON.parse(glb.toString("utf8", 20, 20 + jsonLength)) as GlbJson;
|
||||
}
|
||||
|
||||
/** Mirrors PartyPopulation's selection of the mesh with the most positions. */
|
||||
function runtimeSelectedTriangleCount(filePath: string): number {
|
||||
const glb = readGlbJson(filePath);
|
||||
let largestVertexCount = 0;
|
||||
let triangleCount = 0;
|
||||
for (const mesh of glb.meshes ?? []) {
|
||||
for (const primitive of mesh.primitives ?? []) {
|
||||
if (primitive.mode !== undefined && primitive.mode !== 4) continue;
|
||||
const positionAccessor = primitive.attributes?.POSITION;
|
||||
const vertexCount = positionAccessor === undefined
|
||||
? 0
|
||||
: glb.accessors?.[positionAccessor]?.count ?? 0;
|
||||
if (vertexCount <= largestVertexCount) continue;
|
||||
largestVertexCount = vertexCount;
|
||||
const elementCount = primitive.indices === undefined
|
||||
? vertexCount
|
||||
: glb.accessors?.[primitive.indices]?.count ?? 0;
|
||||
expect(elementCount % 3, filePath).toBe(0);
|
||||
triangleCount = elementCount / 3;
|
||||
}
|
||||
}
|
||||
expect(triangleCount, filePath).toBeGreaterThan(0);
|
||||
return triangleCount;
|
||||
}
|
||||
|
||||
function regularNavigationPath(
|
||||
dungeonId: string,
|
||||
navigation: { readonly fileName: string; readonly url?: string },
|
||||
): string {
|
||||
if (navigation.url?.startsWith("/")) {
|
||||
return path.join(process.cwd(), "public", ...navigation.url.split("/").filter(Boolean));
|
||||
}
|
||||
return path.join(
|
||||
process.cwd(),
|
||||
"src",
|
||||
"assets",
|
||||
"game",
|
||||
"dungeons",
|
||||
dungeonId,
|
||||
navigation.fileName,
|
||||
);
|
||||
}
|
||||
|
||||
function manastormNavigationPath(url: string): string {
|
||||
return path.join(process.cwd(), "public", ...url.split("/").filter(Boolean));
|
||||
}
|
||||
|
||||
describe("party navigation fleet safety", () => {
|
||||
it("covers every regular dungeon and keeps actual synchronous geometry under budget", () => {
|
||||
const enabled = [];
|
||||
const fallback = [];
|
||||
|
||||
expect(DUNGEON_DEFINITIONS).toHaveLength(84);
|
||||
for (const dungeon of DUNGEON_DEFINITIONS) {
|
||||
expect(dungeonCanEnter(dungeon.id), dungeon.id).toBe(true);
|
||||
const navigation = dungeon.assets.navigation;
|
||||
expect(navigation, dungeon.id).toBeDefined();
|
||||
expect(Number.isInteger(navigation?.triangleCount), dungeon.id).toBe(true);
|
||||
expect(navigation?.triangleCount, dungeon.id).toBeGreaterThan(0);
|
||||
|
||||
const actualTriangles = runtimeSelectedTriangleCount(regularNavigationPath(
|
||||
dungeon.id,
|
||||
navigation!,
|
||||
));
|
||||
// Metadata may conservatively count multiple primitives, but must never
|
||||
// under-report the runtime-selected primitive used by the graph builder.
|
||||
expect(navigation!.triangleCount, dungeon.id).toBeGreaterThanOrEqual(actualTriangles);
|
||||
|
||||
if (shouldLoadPartyNavigation("dungeon", dungeon, null)) {
|
||||
enabled.push(dungeon.id);
|
||||
expect(dungeon.validation.status, dungeon.id).toBe("green");
|
||||
expect(actualTriangles, dungeon.id)
|
||||
.toBeLessThanOrEqual(MAX_SYNCHRONOUS_PARTY_NAVIGATION_TRIANGLES);
|
||||
} else {
|
||||
fallback.push(dungeon.id);
|
||||
}
|
||||
}
|
||||
|
||||
expect(enabled).toHaveLength(31);
|
||||
expect(fallback).toHaveLength(53);
|
||||
});
|
||||
|
||||
it("covers every Manastorm package and keeps actual synchronous geometry under budget", () => {
|
||||
const packages = manastormRegistry.packages as unknown as readonly ManastormStageAssetPackage[];
|
||||
const fallbackDungeon = DUNGEON_DEFINITIONS[0];
|
||||
const enabled = [];
|
||||
const fallback = [];
|
||||
|
||||
expect(packages).toHaveLength(75);
|
||||
for (const assetPackage of packages) {
|
||||
const declaredTriangles = assetPackage.navigation.triangleCount;
|
||||
expect(Number.isInteger(declaredTriangles), assetPackage.id).toBe(true);
|
||||
expect(declaredTriangles, assetPackage.id).toBeGreaterThan(0);
|
||||
const actualTriangles = runtimeSelectedTriangleCount(
|
||||
manastormNavigationPath(assetPackage.navigation.url),
|
||||
);
|
||||
expect(declaredTriangles, assetPackage.id).toBe(actualTriangles);
|
||||
|
||||
if (shouldLoadPartyNavigation("manastorm", fallbackDungeon, assetPackage)) {
|
||||
enabled.push(assetPackage.id);
|
||||
expect(actualTriangles, assetPackage.id)
|
||||
.toBeLessThanOrEqual(MAX_SYNCHRONOUS_PARTY_NAVIGATION_TRIANGLES);
|
||||
} else {
|
||||
fallback.push(assetPackage.id);
|
||||
}
|
||||
}
|
||||
|
||||
expect(enabled).toHaveLength(41);
|
||||
expect(fallback).toHaveLength(34);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user