feat: rule-pack contract v2 (params + summary) and registry listing

This commit is contained in:
2026-07-20 16:15:02 +01:00
parent fee86b8dd1
commit 4ebf8d3994
5 changed files with 34 additions and 4 deletions
+4 -3
View File
@@ -11,6 +11,7 @@ export class DiceTray extends HTMLElement {
this._systemId = 'year-zero';
this.rng = Math.random;
this.last = null;
this.params = {};
}
get sides() { return this._sides; }
set sides(v) { this._sides = Number(v); this.render(); }
@@ -24,7 +25,7 @@ export class DiceTray extends HTMLElement {
roll() {
const { results, total } = rollPool([{ sides: this._sides, count: this._count }], this.rng);
const pack = getRulePack(this._systemId);
const verdict = pack ? pack.interpret(results) : null;
const verdict = pack ? pack.interpret(results, this.params) : null;
this.last = { results, total, verdict };
this.render();
this.dispatchEvent(new CustomEvent('rolled', { detail: this.last, bubbles: true }));
@@ -36,9 +37,9 @@ export class DiceTray extends HTMLElement {
const felt = this.last
? this.last.results.map((r) => `<span class="rolled" data-value="${r.value}">${r.value}</span>`).join('')
: '<span class="hint">Tap Roll</span>';
const pack = getRulePack(this._systemId);
const verdict = this.last?.verdict
? `${this.last.verdict.successes} success${this.last.verdict.successes === 1 ? '' : 'es'}` +
(this.last.verdict.ones ? ` · ${this.last.verdict.ones} stress` : '')
? (pack && pack.summary ? pack.summary(this.last.verdict) : '')
: '';
this.innerHTML = `
+3
View File
@@ -4,3 +4,6 @@ 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); }
export function listRulePacks() {
return [...packs.values()].map((p) => ({ id: p.id, label: p.label }));
}
+6 -1
View File
@@ -1,7 +1,8 @@
export const yearZero = {
id: 'year-zero',
label: 'Year-Zero',
interpret(results) {
params: [],
interpret(results, params = {}) {
let successes = 0, ones = 0;
for (const r of results) {
if (r.value >= 10) successes += 2;
@@ -10,4 +11,8 @@ export const yearZero = {
}
return { successes, ones, canPush: true, isCritical: successes >= 2 };
},
summary(v) {
const base = `${v.successes} success${v.successes === 1 ? '' : 'es'}`;
return v.ones ? `${base} · ${v.ones} stress` : base;
},
};
+9
View File
@@ -0,0 +1,9 @@
import { describe, it, expect } from 'vitest';
import { getRulePack, listRulePacks } from '../../src/dice/rulepacks/index.js';
describe('rule-pack registry', () => {
it('lists registered packs as {id,label}', () => {
const packs = listRulePacks();
expect(packs.some((p) => p.id === 'year-zero' && typeof p.label === 'string')).toBe(true);
});
});
+12
View File
@@ -24,3 +24,15 @@ describe('year-zero rule-pack', () => {
expect(getRulePack('x')).toBe(custom);
});
});
describe('year-zero contract v2', () => {
it('exposes an empty params list and a summary string', () => {
expect(yearZero.params).toEqual([]);
expect(yearZero.summary({ successes: 2, ones: 1 })).toBe('2 successes · 1 stress');
expect(yearZero.summary({ successes: 1, ones: 0 })).toBe('1 success');
});
it('interpret ignores a params argument', () => {
const v = yearZero.interpret([{ sides: 8, value: 8 }], { anything: true });
expect(v.successes).toBe(1);
});
});