| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- import { resolve } from "node:path";
- import { describe, expect, it } from "vitest";
- import { createEngine, HOOK_NAMES, seedRandom, type HookContext, type IntentSystem, type System, type TimeSystem } from "../src/index.js";
- const repoRoot = resolve(process.cwd(), "../..");
- const contentDir = resolve(repoRoot, "content");
- const modsDir = resolve(repoRoot, "mods");
- describe("Engine", () => {
- it("start loads content, seeds world, and advancing a day fires the example mod hook", async () => {
- const seen: { event: string; payload: unknown }[] = [];
- const spy: System = {
- id: "spy",
- init() {},
- on(event, payload) {
- seen.push({ event, payload });
- },
- };
- const engine = createEngine({ contentDir, modsDir, systems: [spy] });
- await engine.start();
- expect(engine.registry.ids("map")).toEqual(["farm"]);
- expect(engine.registry.ids("time")).toEqual(["default"]);
- const map = engine.world.get("farm");
- expect(map?.type).toBe("map");
- expect(map?.width).toBe(12);
- expect(map?.height).toBe(12);
- expect(Array.isArray(map?.layers)).toBe(true);
- const player = engine.world.get("player");
- expect(player?.type).toBe("player");
- expect(player?.x).toBe(2);
- expect(player?.y).toBe(2);
- expect(engine.hooks.bound("onDayStart")).toBe(true);
- const time = engine.systems.get<TimeSystem>("time");
- expect(time).toBeDefined();
- time!.advance(480);
- expect(seen.some((s) => s.event === "mod:example-day-start")).toBe(true);
- const modEmit = seen.find((s) => s.event === "mod:example-day-start")!.payload as {
- modId: string;
- day: number;
- season: string;
- };
- expect(modEmit.modId).toBe("example-mod");
- expect(modEmit.day).toBe(2);
- expect(modEmit.season).toBe("spring");
- await engine.stop();
- });
- it("facade exposes a seeded rng; same seed replays identical draws (D28)", async () => {
- const engine = createEngine({ contentDir, modsDir, seed: 42 });
- await engine.start();
- try {
- expect(engine.rng.state()).toEqual({ seed: 42, calls: 0 });
- const expected = seedRandom(42);
- expect(engine.rng.next()).toBe(expected.next());
- expect(engine.rng.nextInt(10)).toBe(expected.nextInt(10));
- expect(engine.rng.state().calls).toBe(2);
- } finally {
- await engine.stop();
- }
- });
- it("facade rng defaults to a random seed when no seed is provided (D28)", async () => {
- const engine = createEngine({ contentDir, modsDir });
- await engine.start();
- try {
- expect(typeof engine.rng.state().seed).toBe("number");
- expect(Number.isFinite(engine.rng.state().seed)).toBe(true);
- expect(engine.rng.next()).toBeGreaterThanOrEqual(0);
- expect(engine.rng.next()).toBeLessThan(1);
- } finally {
- await engine.stop();
- }
- });
- it("seeds player stamina/skills fields and fires onPlayerIntent with the intent payload (D29)", async () => {
- const engine = createEngine({ contentDir, modsDir });
- await engine.start();
- try {
- expect(engine.world.get("player")).toMatchObject({ stamina: 100, skills: {} });
- expect(HOOK_NAMES).toHaveLength(16);
- expect(HOOK_NAMES).toContain("onPlayerIntent");
- const seen: unknown[] = [];
- const unsubscribe = engine.hooks.on("onPlayerIntent", (ctx: HookContext) => seen.push(ctx.payload));
- try {
- const intent = engine.systems.get<IntentSystem>("intent");
- if (!intent) throw new Error("intent system not booted");
- intent.handle("move", { tile: { x: 3, y: 4 } });
- intent.handle("interact", { tile: { x: 9, y: 9 } });
- } finally {
- unsubscribe();
- }
- expect(seen).toHaveLength(2);
- expect(seen[0]).toMatchObject({ action: "move", tile: { x: 3, y: 4 }, ok: true });
- expect(seen[1]).toMatchObject({ action: "interact", tile: { x: 9, y: 9 }, ok: true });
- } finally {
- await engine.stop();
- }
- });
- });
|