import test from "node:test"; import assert from "node:assert/strict"; import vm from "node:vm"; import { workerSource } from "../engine/script-host.ts"; import { entity } from "../engine/schema.ts"; function worker() { const messages: any[] = []; const context = vm.createContext({ self: {}, postMessage: (m: any) => messages.push(structuredClone(m)), structuredClone, Math, console, }); vm.runInContext(workerSource, context); return { send: (data: any) => { context.self.onmessage({ data: structuredClone(data) }); return messages.pop(); }, }; } test("script field defaults, overrides and per-instance state remain independent", () => { const w = worker(); const scripts = [ { id: "counter", name: "Counter", fields: { step: { type: "number", default: 2 } }, source: "({start(api){api.state.count=0},update(api){api.state.count+=api.params.step;api.patch(api.get().id,{components:{data:{count:api.state.count}}})}})", }, ]; const a = entity( "Default", { script: { scriptId: "counter" } }, [0, 0, 0], "a", ); const b = entity( "Override", { script: { scriptId: "counter", params: { step: 5 } } }, [0, 0, 0], "b", ); w.send({ type: "init", entities: [a, b], scripts }); for (let tick = 1; tick <= 3; tick++) { const frame = w.send({ type: "tick", entities: [a, b], input: {}, dt: 0.02, }); assert.equal( frame.commands.find((c: any) => c.id === "a").patch.components.data.count, tick * 2, ); assert.equal( frame.commands.find((c: any) => c.id === "b").patch.components.data.count, tick * 5, ); } }); test("newly spawned instances receive start and update; failing behavior is isolated", () => { const scripts = [ { id: "good", name: "Good", source: '({start(api){api.state.ticks=0;api.log("start")},update(api){api.state.ticks++;api.log(api.state.ticks)}})', fields: {}, }, { id: "bad", name: "Bad", source: '({update(){throw Error("fixture failure")}})', fields: {}, }, ], w = worker(), a = entity("A", { script: { scriptId: "good" } }, [0, 0, 0], "a"); w.send({ type: "init", entities: [a], scripts }); const b = entity("B", { script: { scriptId: "good" } }, [0, 0, 0], "b"), bad = entity("Bad", { script: { scriptId: "bad" } }, [0, 0, 0], "bad"); const m = w.send({ type: "tick", entities: [a, b, bad], input: {}, dt: 0.1 }); assert.equal( m.commands.filter((c: any) => c.id === "b" && c.message === "start").length, 1, ); assert.ok(m.commands.some((c: any) => c.id === "a" && c.message === "1")); assert.ok(m.commands.some((c: any) => c.type === "error")); const second = w.send({ type: "tick", entities: [a, b, bad], input: {}, dt: 0.1, }); assert.ok( second.commands.some((c: any) => c.id === "b" && c.message === "2"), ); assert.ok(!second.commands.some((c: any) => c.type === "error")); });