From e9fa9366fa16fc739745541a4bc4783b130e8975 Mon Sep 17 00:00:00 2001 From: Tim Evans Date: Mon, 20 Jul 2026 13:52:41 +0100 Subject: [PATCH] feat: Year-Zero rule-pack + rule-pack registry --- src/dice/rulepacks/index.js | 6 ++++++ src/dice/rulepacks/year-zero.js | 13 +++++++++++++ tests/dice/year-zero.test.js | 26 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 src/dice/rulepacks/index.js create mode 100644 src/dice/rulepacks/year-zero.js create mode 100644 tests/dice/year-zero.test.js diff --git a/src/dice/rulepacks/index.js b/src/dice/rulepacks/index.js new file mode 100644 index 0000000..32c6973 --- /dev/null +++ b/src/dice/rulepacks/index.js @@ -0,0 +1,6 @@ +import { yearZero } from './year-zero.js'; + +const packs = new Map([[yearZero.id, yearZero]]); + +export function getRulePack(id) { return packs.get(id) || null; } +export function registerRulePack(pack) { packs.set(pack.id, pack); } diff --git a/src/dice/rulepacks/year-zero.js b/src/dice/rulepacks/year-zero.js new file mode 100644 index 0000000..246a085 --- /dev/null +++ b/src/dice/rulepacks/year-zero.js @@ -0,0 +1,13 @@ +export const yearZero = { + id: 'year-zero', + label: 'Year-Zero', + interpret(results) { + let successes = 0, ones = 0; + for (const r of results) { + if (r.value >= 10) successes += 2; + else if (r.value >= 6) successes += 1; + if (r.value === 1) ones += 1; + } + return { successes, ones, canPush: true, isCritical: successes >= 2 }; + }, +}; diff --git a/tests/dice/year-zero.test.js b/tests/dice/year-zero.test.js new file mode 100644 index 0000000..7d0971a --- /dev/null +++ b/tests/dice/year-zero.test.js @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import { yearZero } from '../../src/dice/rulepacks/year-zero.js'; +import { getRulePack, registerRulePack } from '../../src/dice/rulepacks/index.js'; + +describe('year-zero rule-pack', () => { + it('counts 6-9 as one success and 10+ as two', () => { + const v = yearZero.interpret([{ sides: 8, value: 8 }, { sides: 8, value: 4 }, { sides: 12, value: 11 }]); + expect(v.successes).toBe(3); // 8 -> 1, 4 -> 0, 11 -> 2 + expect(v.isCritical).toBe(true); + }); + + it('reports ones for stress on a push', () => { + const v = yearZero.interpret([{ sides: 6, value: 1 }, { sides: 6, value: 1 }, { sides: 6, value: 6 }]); + expect(v.ones).toBe(2); + expect(v.successes).toBe(1); + expect(v.canPush).toBe(true); + }); + + it('is resolvable from the registry by id', () => { + expect(getRulePack('year-zero')).toBe(yearZero); + expect(getRulePack('nope')).toBe(null); + const custom = { id: 'x', label: 'X', interpret: () => ({}) }; + registerRulePack(custom); + expect(getRulePack('x')).toBe(custom); + }); +});