Adds the PF1e two-axis alignment code (LG/NG/CG/LN/N/CN/LE/NE/CE,
default N) to the Combatant model with a strict validator, derived
is_lawful/is_chaotic/is_good/is_evil properties, exposure in the
observation snapshot, and alignment values for all monsters and PCs
sourced from the bestiary KB and the Foundry sheets.
Guidance (new OneShotEffect), resistance (+1 resistance save buff, with the
cantrip-skip policy guard), virtue (temporary HP track) and stabilize
(auto-stabilization) are now modeled; spiritual_weapon and saving_finale
remain deferred with a precise engine reason. Gate 1734.
Rounds 1-3 closed the assumed residuals: run bounded by CON endurance + Run
feat, harmless spells forgo the save on willing targets, self-centered bursts
for bless/bane, Selective Channeling, the lich stat block aligned to the KB,
summon-stats disambiguation, healing_done exposure, multi-cell difficult-terrain
move pricing, and the grazed-caster-cell cone/line fix. The burst intersection
origin is deferred with a precise rationale. Rewrites the residual table to
drop the now-fixed and stale rows, leaving only the genuinely remaining items.
Round 3 (attack resolution, bestiary/PC stat blocks, action economy) and
round 4 (remaining spells, PC feats) found 2 High, ~11 Med and ~70 Low
findings. Records the 22 fix commits (e0bf484..0c3f5fd), both justified
golden updates (Weapon Focus, Divine Favor), and the residual inventory:
~13 deferred spell effects, 20 feat/racial mismatches, and the missing
subsystems (alignment, items, skills). Journal marked paused at round 4.
Consolidates the round-1 audit: 15 fixed divergences (one commit each,
312f9fd..ea60b2e), the deliberate out-of-scope rules, the assumed
residuals, and the verified-conformant guard rails. Linked from README.
- Paragraphe « Mourir et stabilisation » : les morts-vivants et les
constructeurs sont detruits des hp <= 0 (SRD traits Undead/Construct :
'immediately destroyed when reduced to 0 hit points or fewer'), sans
bande d'invalidite ni agonie - conformance deja codee (59caf67).
- Paragraphe « Mouvement » : la traverse des ennuis a 3 categories de
taille d'ecart avec AoO d'entree (SRD 'Moving Through a Square' : 'Any
creature can move through a square occupied by a creature three size
categories larger than itself [...] provoke attacks of opportunity
from those creatures' ; 'You can't end your movement in the same
square as another creature unless it is helpless') ; la mention
'les ennemis bloquent toujours le chemin' est corrigee (blocage total
seulement a 0-2 categories).
- « Limites hors perimetre » : nouvelle entree squeeze (largeur de
corridor vs taille non modelisee), entree des approximations du
pass-through (Acrobatics Tumbling et la clause 'counts as 2 squares'
des tres grosses creatures inertes non modelisees), et entree de
l'echec d'incantation du sourd (tache 11, differe).
- « Attaques multiples par action » : verifie conforme aux armes
naturelles apres e5691bd (pas d'iteratifs, secondaires a -5,
Multiattack -2) - aucune modification necessaire.
- Compteur de tests de la section « Developpement » : 1408 -> 1491.
SRD Cure Light Wounds: 'Since undead are powered by negative energy,
this spell deals damage to them instead of curing their wounds. An
undead creature can apply spell resistance, and can attempt a Will save
to take half damage.'
- _apply_heal_effect takes the rolled save; an undead target takes the
rolled healing as damage (halved on a successful save; SR already
resolved upstream, DR never applies to spell energy, no bleeding stop
or stabilization). Living targets are bit-identical.
- cure_light/cure_moderate descriptions fixed: the SRD save line is
'Will half (harmless)', not 'negates'.
- Deafened 20% verbal spell failure DEFERRED to the README « Limites
hors perimetre »: SpellSpec carries no components data (would need a
verbal flag on all 108 spells) and sound-based Perception is not
modeled either.
SRD spells/mage-armor.md: 'An invisible but tangible field of force
surrounds the subject of a mage armor spell, providing a +4 armor bonus
to AC' - 'School conjuration (creation) [force]', level 1, 'Casting Time
1 standard action', 'Range touch', 'Duration 1 hour/level (D)',
'Spell Resistance no'.
- data/spells/mage_armor.json: buff +4 armor to AC, 600 rounds (1
hour/level modeled fixed, the shield.json convention). Description
documents the known simplification: the engine bakes worn armor into
the AC total and cannot see it, so the armor bonus stacks additively
with that baked value (two mage armors still do not stack).
- effects.py: 'armor' added to BonusType; absent from _STACKING_TYPES it
behaves as a keep-the-highest type (verified by resolve_modifiers
tests).
- Spell-count pins 107 -> 108 (test_runner_spells, batch3, test_spells)
and the README references.
- Golden transcript untouched (buff branch only fires when no offensive
spell is selected; the lich still opens with Magic Missile).
PF1e (CRB "Natural Attacks"):
- Natural weapons (natural=true) get exactly one swing per full attack,
regardless of BAB (no BAB iteratives for natural attacks).
- Secondary natural attacks (secondary=true) attack at BAB -5; the
Multiattack feat reduces this to -2.
- Manifested in combat.py (_full_attack_weapon, _resolve_swing),
models.py (AttackSpec.natural/secondary), 6 data JSONs, 4 new tests,
and README documentation.
1461 passed, ruff clean, basedpyright 0 errors.
Bladed Dash's description now matches the SRD (standard action, move up
to 30 ft in a straight line without provoking attacks of opportunity,
single melee attack at highest BAB against any creature adjacent along
the path) and the README's spell-limits section records the engine
approximation: a +30 ft speed buff for 1 round, with the free melee
attack, the no-AoO movement, the straight-line constraint and the
circumstance attack bonus left unmodeled.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
PF1e (SRD spells/cure-light-wounds.md): 'cures 1d8 points of damage +1
point per caster level (maximum +5)'; (spells/cure-moderate-wounds.md):
'cures 2d8 points of damage +1 point per caster level (maximum +10)'.
A HealEffect now carries the same DamageFlatScaling spec as damage
effects (S1b) and resolves it through effective_formula(caster_level)
at cast time, so the flat bonus grows with the caster and stops at the
stated cap. The engine passes the caster's level into _apply_heal_effect
(required keyword: no silent level-0 under-heal); Maximize Spell
maximizes the scaled formula. Cure Light/Moderate Wounds ship the SRD
values; inflict_light_wounds already carried its scaling_flat.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultimate Combat Firearm Rules: a firearm attack rolling a natural 1 gains the
broken condition (weapon_broken, logged MISFIRE). Quick Clear now actually
clears the first misfired held firearm for 1 grit (reported no-op without
grit), and occupies the standard budget slot via _effective_slot; the
swift-consumed filter is slot-based so the standard deed survives.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
SRD Ready - Initiative Consequences of Readying: 'your initiative result becomes the count on which you took the readied action' and you 'act immediately ahead of the character whose action triggered your readied action'. _check_readied fired the payload off-turn but never touched the readier's initiative. It now calls _ready_shift_initiative, which rewrites the readier's _initiative_keys entry (trigger total + 0.5, keeping its mod and prior index) using the same tuple shape _execute_delay writes, so _build_round_order holds the new position for all following rounds. Deterministic: no RNG consumed, current round order untouched.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
SRD Full-Round Actions - Run: 'you can move up to four times your speed in a straight line'. _execute_run reused the Dijkstra move machinery, so it turned corners around walls. It now walks the straight Bresenham line (_run_line_path) and stops at the first obstacle (impassable terrain, difficult terrain, or a blocking creature), mirroring the charge clear-path logic. Keeps the 4x budget, step-by-step AoO provocation, moved_this_turn and the running/Dex-loss flag; a destination the straight line cannot honour is rejected (unreachable_destination).
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
SRD Casting Time: a spell that takes 1 round is a full-round action
that comes into effect just before the caster's turn in the next round;
the caster then acts normally. _cast_spell now spends the slot and runs
the start-of-casting gates, then records a PendingCast (spell, target,
center, metamagic, slot level, hp at cast); delivery (touch attack, SR,
saves, effects) resolves at the start of the caster's next turn, before
the policy runs. Net hp lost in between forces the injured-while-casting
concentration check (DC 10 + damage + spell level, no Combat Casting) or
the spell fizzles; a caster knocked out of the fight loses it. Exposed
in the observation via pending_cast. Bounded model documented: net-hp
measurement instead of a per-damage-site hook; long (1 minute+) casting
stays out of scope.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
SRD Surprise: when some but not all combatants are aware of their
opponents, a surprise round precedes round 1. Aware combatants take a
single standard OR move action plus free actions (swift and full-round
are dropped, full_attack collapses to one standard attack, withdraw
survives as the Restricted Withdraw standard); unaware combatants skip
their turn. New Combatant.aware flag (default true, parsed at the JSON
boundary) keeps every existing encounter bit-identical, exposed in the
observation snapshot. Unaware combatants are not flagged flat-footed
(documented simplification).
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
SpellSpec gains spell_resistance: bool = True (JSON 'spell_resistance'). When false, _spell_delivery_blocked skips the targeted SR check and _apply_area_effect skips the per-creature SR check (threaded through the effect chain). Verified each imported spell against the local KB and set false on the 28 whose SRD block says 'Spell Resistance: no' (Acid Arrow, Flurry of Snowballs, Web, Darkness, Sanctuary, the Protection from X spells, etc.). Added no-SR targeted/area tests and a high-SR control in test_spell_resistance.py, and the README.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
ConditionEffect gains save_partial (JSON 'on_success': condition_name + duration_rounds). On a successful save that would otherwise negate, _apply_condition_effect now imposes the weaker condition (SRD fear.md: panicked on a failed Will save, shaken for 1 round on a success). Extended _metamagic_transform_sub to preserve/double it; extracted _impose_condition helper. Updated fear.json, added failed/successful-save and loader tests in test_spells.py, and the README.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>