feat: Panic & Glory rule-pack
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { yearZero } from './year-zero.js';
|
||||
import { cocD100 } from './coc-d100.js';
|
||||
import { vanityD6 } from './vanity-d6.js';
|
||||
import { panicGlory } from './panic-glory.js';
|
||||
|
||||
const packs = new Map([[yearZero.id, yearZero], [cocD100.id, cocD100], [vanityD6.id, vanityD6]]);
|
||||
const packs = new Map([[yearZero.id, yearZero], [cocD100.id, cocD100], [vanityD6.id, vanityD6], [panicGlory.id, panicGlory]]);
|
||||
|
||||
export function getRulePack(id) { return packs.get(id) || null; }
|
||||
export function registerRulePack(pack) { packs.set(pack.id, pack); }
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Continuum house mechanic: 1d6 + (20 - current SAN), six bands.
|
||||
const BANDS = [
|
||||
{ max: 6, key: 'glory-unleashed', label: 'GLORY UNLEASHED', effect: '2D10 temp HP · ignore SAN loss 3 rounds · auto-succeed one pulp feat · +1 Glory' },
|
||||
{ max: 9, key: 'heroic-surge', label: 'HEROIC SURGE', effect: '1D6 temp HP · +20% next 2 rolls · +1 Glory' },
|
||||
{ max: 13, key: 'focused-fury', label: 'FOCUSED FURY', effect: 'reroll next fail, then −1D3 SAN' },
|
||||
{ max: 16, key: 'wavering-resolve', label: 'WAVERING RESOLVE', effect: '−20% next action · POW×5 or panic' },
|
||||
{ max: 19, key: 'desperate-flinch', label: 'DESPERATE FLINCH', effect: 'drop weapon / freeze · lose action' },
|
||||
{ max: Infinity, key: 'full-panic', label: 'FULL PANIC', effect: '−1D10 SAN · flee, freeze, or act irrationally' },
|
||||
];
|
||||
|
||||
export const panicGlory = {
|
||||
id: 'panic-glory',
|
||||
label: 'Panic & Glory',
|
||||
params: [{ key: 'san', label: 'Current SAN', default: 20 }],
|
||||
interpret(results, params = {}) {
|
||||
const roll = results[0]?.value ?? 0;
|
||||
const san = params.san ?? 20;
|
||||
const total = roll + (20 - san);
|
||||
const band = BANDS.find((b) => total <= b.max);
|
||||
return { d6: roll, san, total, band, gloryPoint: total <= 9 };
|
||||
},
|
||||
summary(v) {
|
||||
return `${v.total} → ${v.band.label}`;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { panicGlory } from '../../src/dice/rulepacks/panic-glory.js';
|
||||
import { getRulePack } from '../../src/dice/rulepacks/index.js';
|
||||
|
||||
const d6 = (v) => [{ sides: 6, value: v }];
|
||||
|
||||
describe('panic-glory rule-pack', () => {
|
||||
it('adds (20 - SAN) to the d6', () => {
|
||||
const v = panicGlory.interpret(d6(3), { san: 20 });
|
||||
expect(v.total).toBe(3);
|
||||
expect(v.band.key).toBe('glory-unleashed');
|
||||
expect(v.gloryPoint).toBe(true);
|
||||
});
|
||||
it('bands a mid result correctly', () => {
|
||||
const v = panicGlory.interpret(d6(4), { san: 10 }); // 4 + 10 = 14
|
||||
expect(v.total).toBe(14);
|
||||
expect(v.band.key).toBe('wavering-resolve');
|
||||
expect(v.gloryPoint).toBe(false);
|
||||
});
|
||||
it('caps into full panic at 20+', () => {
|
||||
const v = panicGlory.interpret(d6(5), { san: 5 }); // 5 + 15 = 20
|
||||
expect(v.band.key).toBe('full-panic');
|
||||
});
|
||||
it('defaults SAN to 20, summarises, and is registered', () => {
|
||||
const v = panicGlory.interpret(d6(6));
|
||||
expect(v.total).toBe(6);
|
||||
expect(panicGlory.summary(v)).toBe('6 → GLORY UNLEASHED');
|
||||
expect(getRulePack('panic-glory')).toBe(panicGlory);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user