feat: VANITY d6-pool rule-pack
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { yearZero } from './year-zero.js';
|
||||
import { cocD100 } from './coc-d100.js';
|
||||
import { vanityD6 } from './vanity-d6.js';
|
||||
|
||||
const packs = new Map([[yearZero.id, yearZero], [cocD100.id, cocD100]]);
|
||||
const packs = new Map([[yearZero.id, yearZero], [cocD100.id, cocD100], [vanityD6.id, vanityD6]]);
|
||||
|
||||
export function getRulePack(id) { return packs.get(id) || null; }
|
||||
export function registerRulePack(pack) { packs.set(pack.id, pack); }
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// VANITY (Draft 13) core roll: 5-6 = Success; Stumble = 0 successes with 2+ ones.
|
||||
export const vanityD6 = {
|
||||
id: 'vanity-d6',
|
||||
label: 'd6 Pool (VANITY)',
|
||||
params: [{ key: 'difficulty', label: 'Successes needed', default: 1 }],
|
||||
interpret(results, params = {}) {
|
||||
let successes = 0, ones = 0;
|
||||
for (const r of results) {
|
||||
if (r.value >= 5) successes += 1;
|
||||
if (r.value === 1) ones += 1;
|
||||
}
|
||||
const difficulty = params.difficulty ?? 1;
|
||||
const isSuccess = successes >= difficulty;
|
||||
return {
|
||||
successes,
|
||||
ones,
|
||||
difficulty,
|
||||
isSuccess,
|
||||
stumble: successes === 0 && ones >= 2,
|
||||
twist: ones >= 1,
|
||||
style: successes > difficulty,
|
||||
canPush: true,
|
||||
};
|
||||
},
|
||||
summary(v) {
|
||||
const base = `${v.successes} success${v.successes === 1 ? '' : 'es'}`;
|
||||
const verdict = v.isSuccess ? '' : ' — fail';
|
||||
const stumble = v.stumble ? ' · STUMBLE (+1 Bane)' : (v.twist ? ' · a 1 (GM twist)' : '');
|
||||
return `${base}${verdict}${stumble}`;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { vanityD6 } from '../../src/dice/rulepacks/vanity-d6.js';
|
||||
import { getRulePack } from '../../src/dice/rulepacks/index.js';
|
||||
|
||||
const pool = (...vals) => vals.map((v) => ({ sides: 6, value: v }));
|
||||
|
||||
describe('vanity-d6 rule-pack', () => {
|
||||
it('counts 5s and 6s as successes', () => {
|
||||
const v = vanityD6.interpret(pool(6, 5, 2), { difficulty: 1 });
|
||||
expect(v.successes).toBe(2);
|
||||
expect(v.isSuccess).toBe(true);
|
||||
expect(v.style).toBe(true); // more successes than needed
|
||||
});
|
||||
it('stumbles on zero successes with two or more 1s', () => {
|
||||
const v = vanityD6.interpret(pool(1, 1, 3), { difficulty: 1 });
|
||||
expect(v.successes).toBe(0);
|
||||
expect(v.ones).toBe(2);
|
||||
expect(v.stumble).toBe(true);
|
||||
expect(v.isSuccess).toBe(false);
|
||||
});
|
||||
it('a lone 1 is a GM twist, not a stumble', () => {
|
||||
const v = vanityD6.interpret(pool(1, 4, 4), { difficulty: 1 });
|
||||
expect(v.twist).toBe(true);
|
||||
expect(v.stumble).toBe(false);
|
||||
});
|
||||
it('respects the difficulty (successes needed)', () => {
|
||||
const v = vanityD6.interpret(pool(6, 2, 2), { difficulty: 2 });
|
||||
expect(v.successes).toBe(1);
|
||||
expect(v.isSuccess).toBe(false);
|
||||
});
|
||||
it('defaults difficulty to 1, summarises, and is registered', () => {
|
||||
const v = vanityD6.interpret(pool(5, 3));
|
||||
expect(v.difficulty).toBe(1);
|
||||
expect(vanityD6.summary(v)).toContain('1 success');
|
||||
expect(getRulePack('vanity-d6')).toBe(vanityD6);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user