import assert from "node:assert/strict"; import fs from "node:fs"; import test from "node:test"; import vm from "node:vm"; // Evaluate the compiler's actual pure contracts and validator without its // filesystem-heavy catalog generation. Source DBCs stay read-only. const source = fs.readFileSync(new URL("./compile-campaign-catalog.mjs", import.meta.url), "utf8"); function section(startMarker, endMarker) { const start = source.indexOf(startMarker); const end = source.indexOf(endMarker, start); assert(start >= 0 && end > start, `Missing compiler section ${startMarker}`); return source.slice(start, end); } const { contracts, validate } = vm.runInNewContext([ section("const pinnedEffect = ", "const AUXILIARY_NON_BOSS_CASTS = "), section("function pinnedSourceSpellFor(spell)", "function basicAttack("), "({ contracts: PINNED_SOURCE_SPELLS, validate: pinnedSourceSpellFor })", ].join("\n")); function spellRow(id, sourceProfile) { const signature = contracts.get(id).signatures[sourceProfile]; return { id, sourceProfile, ...JSON.parse(JSON.stringify(signature)) }; } test("rejects attribute drift in both pinned Dual Wield profiles and Thrash", () => { for (const [id, ascension, epoch] of [[674, 336, 80], [29651, 80, 80], [3391, 262_160, 262_160]]) { for (const [profile, attributes0] of [["ascension", ascension], ["epoch", epoch]]) { const row = spellRow(id, profile); assert.equal(row.attributes0, attributes0); assert.equal(validate(row), contracts.get(id)); assert.throws(() => validate({ ...row, attributes0: attributes0 ^ 1 }), /contract drift/); } } }); test("rejects a changed Thrash die roll instead of silently changing its two-swing contract", () => { for (const profile of ["ascension", "epoch"]) { const row = spellRow(3391, profile); assert.equal(row.effects[0].basePoints, 2); assert.equal(row.effects[0].dieSides, 1); assert.equal(contracts.get(3391).extraAttackCount, 2); row.effects[0].dieSides = 0; assert.throws(() => validate(row), /contract drift/); } }); test("fails closed for an unknown source profile on a pinned weapon utility", () => { assert.throws(() => validate({ ...spellRow(674, "ascension"), sourceProfile: "unknown" }), /no unknown source profile/); }); test("rejects changed shout trigger links, child effects, and shared area caps", () => { for (const profile of ["ascension", "epoch"]) { for (const [parent, child, maximum] of [[5246, 20511, 5], [19134, 29544, 0]]) { const row = spellRow(parent, profile); assert.equal(row.effects[0].triggerSpellId, child); assert.equal(row.maxAffectedTargets, maximum); assert.equal(validate(row), contracts.get(parent)); assert.throws(() => validate({ ...row, maxAffectedTargets: maximum + 1 }), /contract drift/); row.effects[0].triggerSpellId = child + 1; assert.throws(() => validate(row), /contract drift/); const childRow = spellRow(child, profile); assert.equal(validate(childRow), contracts.get(child)); childRow.effects[0].auraType += 1; assert.throws(() => validate(childRow), /contract drift/); } } }); test("rejects shout spell-mechanic and any raw attribute drift", () => { for (const profile of ["ascension", "epoch"]) { for (const id of [5246, 20511, 19134, 29544]) { const row = spellRow(id, profile); assert.equal(row.spellMechanic, 5); assert.throws(() => validate({ ...row, spellMechanic: 0 }), /contract drift/); for (let word = 0; word < 8; word += 1) { const field = `attributes${word}`; assert.equal(typeof row[field], "number"); assert.throws(() => validate({ ...row, [field]: row[field] ^ 1 }), /contract drift/); } } } }); test("pins complete Critical Block ranks including aura107 siblings without assigning a creature", () => { for (const profile of ["ascension", "epoch"]) { for (const id of [47294, 47295, 47296]) { const row = spellRow(id, profile); assert.equal(validate(row), contracts.get(id)); assert.equal(row.durationIndex, 0); assert.equal(row.effects[0].auraType, 253); assert.equal(row.effects[1].auraType, 107); const changed = structuredClone(row); changed.effects[1].basePoints += 1; assert.throws(() => validate(changed), /contract drift/); } } });