import { describe, it, expect } from "vitest"; import { mulberry32, hashModuleId, buildPieceSpecs, ASSEMBLY_EASE } from "./ModuleLaunchAnimation"; describe("launch animation randomness engine", () => { it("mulberry32 is deterministic per seed and well-distributed", () => { const a = mulberry32(12345); const b = mulberry32(12345); const seqA = [a(), a(), a(), a()]; const seqB = [b(), b(), b(), b()]; expect(seqA).toEqual(seqB); seqA.forEach((v) => { expect(v).toBeGreaterThanOrEqual(0); expect(v).toBeLessThan(1); }); }); it("different seeds produce different assemblies (re-seed per launch)", () => { const specs1 = buildPieceSpecs(mulberry32(hashModuleId("e") ^ 1), 11); const specs2 = buildPieceSpecs(mulberry32(hashModuleId("e") ^ 2), 11); const sig1 = JSON.stringify(specs1.map((s) => [s.from.x, s.from.y, s.from.rotate, s.from.delay])); const sig2 = JSON.stringify(specs2.map((s) => [s.from.x, s.from.y, s.from.rotate, s.from.delay])); expect(sig1).not.toEqual(sig2); }); it("piece transforms stay inside the mandated motion budget", () => { const specs = buildPieceSpecs(mulberry32(42), 11); expect(specs).toHaveLength(11); specs.forEach((s) => { const distance = Math.hypot(s.from.x, s.from.y); expect(distance).toBeGreaterThanOrEqual(240); expect(distance).toBeLessThanOrEqual(720); expect(Math.abs(s.from.rotate)).toBeGreaterThanOrEqual(8); expect(Math.abs(s.from.rotate)).toBeLessThanOrEqual(45); expect(s.from.scale).toBeGreaterThanOrEqual(0.55); expect(s.from.scale).toBeLessThanOrEqual(1.35); // stagger: i*55 + 40..90ms expect(s.from.delay).toBeGreaterThan(0); // slow-motion duration 2.4–3.2s expect(s.from.duration).toBeGreaterThanOrEqual(2.4); expect(s.from.duration).toBeLessThanOrEqual(3.2); }); }); it("uses the mandated slow-motion easing curve", () => { expect(Array.from(ASSEMBLY_EASE)).toEqual([0.22, 1, 0.36, 1]); }); });