import { STAGES, N, D_SPACING, X_SWAY, districtPosition, samplePath, cameraFor } from "./theme"; describe("strategy/theme — stage data", () => { it("exposes five stages and a matching N", () => { expect(STAGES).toHaveLength(5); expect(N).toBe(5); }); it("has unique, well-formed stage descriptors", () => { const keys = STAGES.map((s) => s.key); expect(new Set(keys).size).toBe(keys.length); for (const s of STAGES) { expect(s.n).toMatch(/^\d{2}$/); expect(s.theme).toMatch(/^#[0-9A-Fa-f]{6}$/); expect(s.kicker.length).toBeGreaterThan(0); } }); }); describe("strategy/theme — districtPosition()", () => { it("sits on the floor (y=0), sways x by parity, steps z by -D_SPACING", () => { expect(districtPosition(0)).toEqual([-X_SWAY, 0, -0]); expect(districtPosition(1)).toEqual([X_SWAY, 0, -D_SPACING]); expect(districtPosition(2)).toEqual([-X_SWAY, 0, -2 * D_SPACING]); expect(districtPosition(3)).toEqual([X_SWAY, 0, -3 * D_SPACING]); }); it("places even districts left and odd districts right", () => { for (let i = 0; i < N; i++) { const [x] = districtPosition(i); if (i % 2 === 0) expect(x).toBeLessThan(0); else expect(x).toBeGreaterThan(0); } }); it("marches monotonically into -Z", () => { for (let i = 1; i < N; i++) { expect(districtPosition(i)[2]).toBeLessThan(districtPosition(i - 1)[2]); } }); }); describe("strategy/theme — samplePath()", () => { it("equals districtPosition() at integer indices", () => { for (let i = 0; i < N; i++) { const sampled = samplePath(i); const exact = districtPosition(i); sampled.forEach((v, k) => expect(v).toBeCloseTo(exact[k], 10)); } }); it("linearly interpolates between adjacent districts", () => { const [x, y, z] = samplePath(0.5); expect(x).toBeCloseTo(0, 10); // midpoint of -X_SWAY and +X_SWAY expect(y).toBeCloseTo(0, 10); expect(z).toBeCloseTo(-D_SPACING / 2, 10); }); it("clamps indices below 0 to the first district", () => { expect(samplePath(-3)).toEqual(districtPosition(0)); }); it("clamps indices above N-1 to the last district", () => { expect(samplePath(99)).toEqual(districtPosition(N - 1)); }); it("never produces NaN across a fine sweep", () => { for (let p = 0; p <= 1.0001; p += 0.05) { const idx = p * (N - 1); for (const v of samplePath(idx)) expect(Number.isNaN(v)).toBe(false); } }); }); describe("strategy/theme — cameraFor()", () => { it("keeps the camera behind (+Z) and above (+Y) the active district", () => { for (let i = 0; i < N; i++) { const { pos, look } = cameraFor(i); const [, , dz] = districtPosition(i); expect(pos[2]).toBeGreaterThan(dz); // camera is back from the district expect(pos[1]).toBeGreaterThan(look[1]); // camera looks down expect(look[2]).toBeLessThan(pos[2]); // look target leads forward } }); it("never produces NaN across a fine sweep", () => { for (let p = 0; p <= 1.0001; p += 0.05) { const { pos, look } = cameraFor(p * (N - 1)); for (const v of [...pos, ...look]) expect(Number.isNaN(v)).toBe(false); } }); });