ctan
3be22b9c55
feat(rules): alignment-gated protection from evil/law wards via attacker context
...
Add attacker-alignment modifier conditions (attacker_is_evil/lawful/good/
chaotic) evaluated against an optional attacker context threaded through
_stat_modifiers/_modifier_condition_holds, _attack_ac_mods (resolve_attack
AC path), resolve_save and _area_save (spell save paths), plus the fear
aura. Without a source in scope the condition is False, so existing rolls
are untouched (golden transcripts bit-identical).
Give the four protection_from_evil/law(+communal) JSONs their SRD buff:
+2 deflection to AC and +2 resistance on fort/ref/will, gated on the
attacker/caster alignment (SRD: bonuses apply 'against attacks or effects
created by evil creatures'); duration 1 min/level approximated as 10
rounds (engine convention). Descriptions document the unmodeled mental-
control suppression and communal duration-split clauses. Level/range/
save/SR verified against the KB.
2026-09-20 12:57:29 +02:00
..
2026-09-16 19:04:14 +02:00
2026-09-19 13:12:36 +02:00
2026-09-13 12:44:35 +02:00
2026-09-19 13:34:30 +02:00
2026-09-17 21:44:19 +02:00
2026-09-17 21:44:19 +02:00
2026-09-16 19:04:14 +02:00
2026-09-19 22:38:39 +02:00
2026-09-19 22:27:46 +02:00
2026-09-13 18:56:17 +02:00
2026-09-19 13:22:19 +02:00
2026-09-19 22:38:39 +02:00
2026-09-16 19:04:14 +02:00
2026-09-19 22:27:46 +02:00
2026-09-13 11:01:07 +02:00
2026-09-19 13:34:30 +02:00
2026-09-16 19:04:14 +02:00
2026-09-16 19:04:14 +02:00
2026-09-11 15:01:42 +02:00
2026-09-16 19:04:14 +02:00
2026-09-19 22:27:46 +02:00
2026-09-19 22:27:46 +02:00
2026-09-16 19:04:14 +02:00
2026-09-13 12:44:35 +02:00
2026-09-17 21:44:19 +02:00
2026-09-16 19:04:14 +02:00
2026-09-19 22:27:46 +02:00
2026-09-19 22:27:46 +02:00
2026-09-13 12:44:35 +02:00
2026-09-13 12:44:35 +02:00
2026-09-13 12:44:35 +02:00
2026-09-11 20:19:03 +02:00
2026-09-17 22:41:00 +02:00
2026-09-17 23:26:45 +02:00
2026-09-17 21:40:27 +02:00
2026-09-17 21:44:19 +02:00
2026-09-19 22:27:46 +02:00
2026-09-19 22:27:46 +02:00
2026-09-19 22:27:46 +02:00
2026-09-13 12:32:45 +02:00
2026-09-13 10:48:09 +02:00
2026-09-13 12:44:35 +02:00
2026-09-11 15:01:42 +02:00
2026-09-13 12:44:35 +02:00
fix(rules): guidance is a one-shot +1 competence bonus on a single attack roll - SRD spells/guidance.md grants '+1 competence bonus on a SINGLE attack roll, saving throw, or skill check' (duration 1 minute or until discharged); the engine had no spell effect that could write the one-shot attack track (aid_attack_bonus is hardwired to aid-another +2 and expires at the start of the beneficiary's next turn), and a persistent BuffEffect would misrepresent the discharge rule (it would boost every roll for a minute) and would become the first buff spell in the casters' known-spell order, degrading default_policy; a new OneShotEffect type is added (track/value/bonus_type/duration_rounds, parser 'one_shot', SpellEffect union, summary 'one-shot +X type on track'), stored as ActiveOneShot on CombatantState (one_shot_bonuses, decremented with the other duration-tracked effects, exposed in the snapshot), discharged by the FIRST roll of the encoded track via _discharge_one_shot (best value wins, each casting is its own charge) and folded into the resolve_modifiers pool so bonus-type stacking applies; the attack track rides the attack roll only, NOT the crit-confirm roll (the SRD grants it to a SINGLE attack roll, so unlike aid another the confirmation is untouched - _attack_roll_modifiers returns (pool_mod, attack_roll_mod) and resolve_attack uses the latter for the total while the confirm keeps the former); the save and skill tracks are wired in the mechanism (resolve_save folds a save-track shot into save_mods, resolve_skill discharges a skill-track shot into _skill_static_bonus via extra_mods) but guidance.json encodes the attack track only - the SRD 'attack OR save OR skill' choice has no declaration channel in the engine, so the attack-roll variant is encoded (documented); _apply_targeted_spell_effect dispatches OneShotEffect to _apply_one_shot_effect (a successful save negates it, 'one-shot negated'); guidance.json now carries the effect (1 minute = 10 rounds) and default_policy never casts it (cantrips have no spell slot and the one-shot type is not a buff/heal/offensive pick); _spell_effect_summary split into _damage_effect_summary/_non_damage_effect_summary to stay under the PLR0911 return cap; test_spell_registry_batch2._EFFECT_TYPES gains OneShotEffect; +14 tests (test_one_shot_guidance.py: cast-and-discharge on the next attack, not on the crit-confirm roll, save/skill tracks, stacking by bonus type, expiry at 10 rounds, save negation, snapshot exposure, no auto-cast by default_policy); golden untouched; 1705 tests green
2026-09-20 09:53:53 +02:00
2026-09-19 22:27:46 +02:00
2026-09-11 15:01:42 +02:00
2026-09-11 15:01:42 +02:00
2026-09-19 22:27:46 +02:00
2026-09-17 23:26:45 +02:00
2026-09-19 13:12:36 +02:00
2026-09-11 15:01:42 +02:00
2026-09-13 10:48:09 +02:00
2026-09-11 15:01:42 +02:00
2026-09-13 12:44:35 +02:00
2026-09-13 11:01:07 +02:00
2026-09-16 15:36:43 +02:00
2026-09-13 12:44:35 +02:00
2026-09-13 12:05:56 +02:00
2026-09-19 22:27:46 +02:00
2026-09-13 12:44:35 +02:00
2026-09-19 22:27:46 +02:00
2026-09-13 12:44:35 +02:00
2026-09-19 13:22:19 +02:00
2026-09-11 15:01:42 +02:00
2026-09-13 12:44:35 +02:00
2026-09-11 15:01:42 +02:00
2026-09-19 22:27:46 +02:00
2026-09-17 21:44:19 +02:00
2026-09-20 12:57:29 +02:00
2026-09-20 12:57:29 +02:00
2026-09-20 12:57:29 +02:00
2026-09-20 12:57:29 +02:00
2026-09-17 21:44:19 +02:00
2026-09-17 21:44:19 +02:00
2026-09-17 21:28:00 +02:00
2026-09-11 20:19:03 +02:00
2026-09-11 15:01:42 +02:00
fix(rules): resistance is a +1 resistance bonus on fort/ref/will saves for 1 minute - SRD spells/resistance.md grants the subject a +1 resistance bonus on saving throws for 1 minute (Will negates, harmless); the resistance bonus type already exists in the stacking model (effects.py BonusType), so the buff is expressible as a BuffEffect and was only deferred because _find_buff_spell returns the FIRST BuffEffect in known-spell order and cantrips cost no slots - encoding it would have made default_policy cast Resistance every round (for Tammara it would preempt Divine Favor and never kill anything); resistance.json now carries a BuffEffect (+1 resistance on fort/ref/will, 10 rounds = 1 minute) and _find_buff_spell skips level-0 spells (cantrips have no spell slot in the engine's budget - spell_slots_left has no level-0 key - and the policy deliberately never auto-casts them, a cantrip buff would be cast every round degrading the policy), so Resistance cannot preempt a leveled buff (Divine Favor) in the known-spell order; leveled buffs keep the historical first-match behavior (if the first one's slots are exhausted the buff branch gives up - the golden pins that); the resistance bonus type does not stack with itself (resolve_modifiers keeps the higher value); +9 tests (test_resistance.py: buff shape, cast on a willing ally consumes no d20 and applies without a save roll, the bonus rides save rolls, no self-stacking, expiry at 10 rounds, _find_buff_spell skips the slot-less cantrip / returns None without a leveled buff, _choose_spell_action proposes Divine Favor and never Resistance); golden untouched; 1714 tests green
2026-09-20 10:10:32 +02:00
2026-09-19 22:27:46 +02:00
2026-09-13 12:44:35 +02:00
2026-09-19 22:27:46 +02:00
2026-09-13 12:05:56 +02:00
2026-09-20 12:32:14 +02:00
2026-09-19 22:27:46 +02:00
2026-09-13 17:39:52 +02:00
2026-09-13 10:48:09 +02:00
2026-09-11 15:01:42 +02:00
2026-09-11 15:01:42 +02:00
2026-09-12 12:31:06 +02:00
2026-09-11 15:01:42 +02:00
2026-09-20 12:32:14 +02:00
2026-09-19 13:27:55 +02:00
2026-09-20 10:48:08 +02:00
2026-09-19 22:27:46 +02:00
2026-09-19 22:27:46 +02:00
2026-09-17 21:44:19 +02:00
2026-09-19 11:47:34 +02:00
2026-09-19 11:47:34 +02:00
2026-09-19 22:27:46 +02:00
2026-09-11 15:01:42 +02:00
2026-09-11 15:01:42 +02:00
2026-09-17 23:26:45 +02:00
2026-09-13 12:44:35 +02:00
2026-09-19 22:27:46 +02:00
2026-09-20 10:32:05 +02:00
2026-09-19 22:27:46 +02:00
2026-09-13 12:44:35 +02:00