clone

an endless marble-race generator in the browser; the url is the seed, so every track can be shared

main
marbles / tests / manifest_test.ts
manifest_test.ts 3.0K
1/**2 * Invariants the Marble Kit manifest must satisfy for the generator to trust3 * it. Skips (loudly) when the pipeline has not been run yet.4 */5import { assert, assertEquals } from "@std/assert";6import { fromFileUrl, isAbsolute, resolve } from "@std/path";7import type { PackManifest } from "../src/shared/types.ts";89const ROOT = fromFileUrl(new URL("../", import.meta.url));10const MANIFEST_PATH = resolve(ROOT, "assets/marble-kit.manifest.json");1112function load(): PackManifest | null {13 try {14 return JSON.parse(Deno.readTextFileSync(MANIFEST_PATH)) as PackManifest;15 } catch {16 return null;17 }18}1920const manifest = load();21const skip = manifest === null;2223if (skip) {24 console.warn(25 `\nSKIPPING manifest tests: ${MANIFEST_PATH} not found.\n` +26 `Run \`deno task assets:import\` to generate it.\n`,27 );28}2930/** Non-null accessor for the guarded tests below. */31function m(): PackManifest {32 return manifest as PackManifest;33}3435function test(name: string, fn: () => void): void {36 Deno.test({ name, ignore: skip, fn });37}3839test("manifest: header describes the Marble Kit", () => {40 assertEquals(m().version, 1);41 assertEquals(m().pack, "Marble Kit");42 assertEquals(m().slug, "marble-kit");43 assertEquals(m().license, "CC0");44});4546test("manifest: 162 pieces, count field agrees", () => {47 assertEquals(m().pieces.length, 162);48 assertEquals(m().pieceCount, 162);49});5051test("manifest: every piece classifies to a known family", () => {52 const unknown = m().pieces.filter((p) => p.family === "unknown").map((p) =>53 p.id54 );55 assertEquals(unknown, [], `unclassified pieces: ${unknown.join(", ")}`);56});5758test("manifest: piece ids are unique", () => {59 const ids = m().pieces.map((p) => p.id);60 assertEquals(new Set(ids).size, ids.length);61});6263test("manifest: every piece rests on y=0", () => {64 for (const p of m().pieces) {65 assert(66 Math.abs(p.bboxMin[1]) < 0.02,67 `${p.id}: bboxMin[1] = ${p.bboxMin[1]}, expected ~0`,68 );69 }70});7172test("manifest: bounding boxes are non-degenerate", () => {73 for (const p of m().pieces) {74 for (let axis = 0; axis < 3; axis++) {75 assert(76 p.bboxMax[axis] >= p.bboxMin[axis],77 `${p.id}: bboxMax[${axis}] < bboxMin[${axis}]`,78 );79 }80 }81});8283test("manifest: footprints occupy at least one grid cell", () => {84 for (const p of m().pieces) {85 assert(86 p.footprint[0] >= 1 && p.footprint[1] >= 1,87 `${p.id}: footprint ${JSON.stringify(p.footprint)}`,88 );89 }90});9192test("manifest: every piece has geometry", () => {93 for (const p of m().pieces) {94 assert(p.vertices > 0, `${p.id}: ${p.vertices} vertices`);95 assert(p.triangles > 0, `${p.id}: ${p.triangles} triangles`);96 }97});9899test("manifest: glb exists and its size matches glbBytes", () => {100 const glbPath = isAbsolute(m().glbPath)101 ? m().glbPath102 : resolve(ROOT, m().glbPath);103 const stat = Deno.statSync(glbPath);104 assert(stat.isFile, `${glbPath} is not a file`);105 assertEquals(stat.size, m().glbBytes, `size mismatch for ${glbPath}`);106});