feat: Zapał regen block flag, tests, changelog for 2026-10-26 sync (partial)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E48f1kXhsEbAUxdkNP1yDM
This commit is contained in:
2026-10-29 22:55:34 +01:00
co-authored by Claude Sonnet 5.5
parent 23c1e94ab5
commit d9d94a465c
4 changed files with 61 additions and 1 deletions
+15
View File
@@ -5,6 +5,21 @@ All notable changes to the **HbM RPG v3** Foundry VTT system.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] - 2026-10 sync (partial, Foundry Sync Brief 2026-10-26)
### Added
- Casting penalties from armour: Średni −1 die, Ciężki −3 dice and +2 Mana (also overcast), Tarcza −1 die (stacks); Pancerz Wiary removes armour but not shield penalties. Gear flag `armor.ignoresCastingPenalty` for special-case armour.
- Every spell, sacred ones included, can be cast as a regular spell or as a prayer (cast dialog "Sposób rzucenia"). Prayer = Dusza + Oddanie, Mana + 1 Zapał; sacred spells default to prayer.
- `damageCategory` spell flag: +1 damage for category Obrażenia (off by default; vault frontmatter has no category yet).
- `runic` spell school (alias of the `runes` discipline for the discipline gate).
- Pure helpers in `rules.ts`: `golemLimit`, `fallDamage`, `survivalTest`, `zealRegenBlocked`, `madnessThreshold` (not all wired yet).
- Characters have `mentalIllnesses`.
### Changed
- Wiedźmia Magia: Poziom Skomplikowania = number of Symbols; `complexityLevel` removed from spells.
- Hartowany w Ogniu: flat −2 per fire hit (min 0) instead of halving.
- Próg Obłędu = Dusza + 3 (Krasnolud +1); reaching it resets Punkty Obłędu and rolls the illness k100. Replaces the old 3/6/10/15 thresholds.
## [1.5.2] - 2026-10-26
### Fixed
+5
View File
@@ -123,7 +123,12 @@ export function registerCombatHooks(): void {
}
// Per-turn Zeal regen (characters) - base 1 + talent bonus from flag.
// Blocked when since the previous turn the actor paid for an extra Action or an extra Immediate Action
// (flag `paidExtra`, set by whoever spends the Zapał; prayers / own-cost Talents never set it).
if (actor.type === 'character') {
const blocked = Boolean(actor.getFlag?.(FLAG_SCOPE, 'paidExtra'));
if (actor.getFlag?.(FLAG_SCOPE, 'paidExtra')) await actor.unsetFlag?.(FLAG_SCOPE, 'paidExtra');
if (blocked) return;
const sys = actor.system as { attributes: { zeal: { value: number; max: number } } };
const regen = getZealRegen(actor);
const next = Math.min(sys.attributes.zeal.max, sys.attributes.zeal.value + regen);
+40
View File
@@ -0,0 +1,40 @@
import { describe, it, expect } from 'vitest';
import {
castingArmorPenalty, hardenedFireDamage, madnessThreshold, golemLimit, fallDamage, survivalTest,
resolveCastAsPrayer, zealRegenBlocked,
} from '../../src/logic/rules';
describe('2026-10-26 sync rules', () => {
it('armour casting penalties stack and Pancerz Wiary spares only armour', () => {
expect(castingArmorPenalty([{ armorType: 'medium' }])).toEqual({ dice: 1, mana: 0 });
expect(castingArmorPenalty([{ armorType: 'heavy' }, { armorType: 'shield' }])).toEqual({ dice: 4, mana: 2 });
expect(castingArmorPenalty([{ armorType: 'heavy' }, { armorType: 'shield' }], true)).toEqual({ dice: 1, mana: 0 });
expect(castingArmorPenalty([{ armorType: 'medium', ignoresCastingPenalty: true }])).toEqual({ dice: 0, mana: 0 });
});
it('Hartowany w Ogniu is flat -2, min 0', () => {
expect(hardenedFireDamage(5)).toBe(3);
expect(hardenedFireDamage(1)).toBe(0);
});
it('Próg Obłędu, golem limit, fall damage, Test Przeżycia', () => {
expect(madnessThreshold(4)).toBe(7);
expect(madnessThreshold(4, 'dwarf')).toBe(8);
expect(golemLimit(5)).toBe(2);
expect(golemLimit(1)).toBe(1);
expect(fallDamage(2)).toBe(0);
expect(fallDamage(6)).toBe(4);
expect(fallDamage(6, true)).toBe(2);
expect(fallDamage(100)).toBe(20);
expect(survivalTest(2)).toEqual({ threshold: 4, successes: 1 });
expect(survivalTest(6, 1)).toEqual({ threshold: 5, successes: 3 });
});
it('sacred spells default to prayer but can be cast as regular spells', () => {
const sacred = { system: { castingMode: 'sacred' } };
expect(resolveCastAsPrayer(sacred, {})).toBe(true);
expect(resolveCastAsPrayer(sacred, { castAsPrayer: false })).toBe(false);
expect(resolveCastAsPrayer({ system: { castingMode: 'standard' } }, {})).toBe(false);
});
it('paid extra actions block Zapał regen', () => {
expect(zealRegenBlocked(0, 0)).toBe(false);
expect(zealRegenBlocked(0, 1)).toBe(true);
});
});
+1 -1
View File
@@ -19,7 +19,7 @@ describe('spell-parser', () => {
expect(s.damageBase).toBe('magicalAbilities/2');
expect(s.damageType).toBe('magical');
expect(s.difficulty).toEqual({ threshold: 4, successes: 1 });
expect(s.overcastOptions).toEqual([{ description: '+1 punkt Obrażeń (najwyżej dwa Poziomy Nadczarowania)', manaPerStep: 1 }]);
expect(s.overcastOptions).toEqual([{ description: '+1 punkt Obrażeń', manaPerStep: 1 }]);
expect(s).not.toHaveProperty('circle');
for (const d of docs) {