fix(rules): knock prone only on a 5-point overrun margin

PF1e CRB 'Overrun': 'If your maneuver is successful, you move through
the target's space. If your attack exceeds your opponent's CMD by 5 or
more, ... the target is knocked prone.' The engine knocked the target
prone on ANY success. A plain success (margin 0-4) now only moves the
attacker through; prone requires total - CMD >= 5 (_OVERRUN_PRONE_MARGIN).

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-09-11 22:22:57 +02:00
parent b453b19be2
commit 5568e0f8af
3 changed files with 47 additions and 14 deletions
+20 -10
View File
@@ -246,6 +246,7 @@ _PRECISION_IMMUNE_TYPES: frozenset[str] = frozenset({"undead", "construct"})
_AID_ANOTHER_DC = 10 # PF1e: aid another is an attack roll against AC 10
_AID_ANOTHER_BONUS = 2 # PF1e: a successful aid grants +2 (next attack or AC)
_RUN_SPEED_FACTOR = 4 # PF1e: running is a full-round action at 4x speed
_OVERRUN_PRONE_MARGIN = 5 # PF1e: overrun knocks prone only on a 5+ CMB margin
_SAVE_DESCRIPTOR_CONDITIONS = frozenset({
"fear", "cold", "electricity", "fey", "divination", "poison", "disease",
"mind_affecting",
@@ -4077,19 +4078,28 @@ class CombatEngine:
if not m.success:
self._maneuver_fail(state, target, "overrun", m)
return
target.active_conditions.append(
ActiveCondition(
condition=CONDITIONS_BY_NAME["prone"],
rounds_left=1,
source="maneuver: overrun",
# PF1e (CRB "Overrun"): a successful maneuver moves you through the
# target's square; the target is knocked prone only if the attack
# exceeds its CMD by 5 or more.
knocked_prone = m.total - m.target_cmd >= _OVERRUN_PRONE_MARGIN
if knocked_prone:
target.active_conditions.append(
ActiveCondition(
condition=CONDITIONS_BY_NAME["prone"],
rounds_left=1,
source="maneuver: overrun",
)
)
)
beyond = self._beyond_cell(state.pos, target.pos)
if self._cells_free(self._footprint_at(beyond, state), state):
moved_through = self._cells_free(self._footprint_at(beyond, state), state)
if moved_through:
state.pos = beyond
outcome = "SUCCESS (prone, moved through)"
else:
outcome = "SUCCESS (prone)"
achieved = ", ".join(
label
for label, flag in (("prone", knocked_prone), ("moved through", moved_through))
if flag
)
outcome = f"SUCCESS ({achieved})" if achieved else "SUCCESS"
self._log(
f"round {self._current_round} {state.combatant.id}: overrun vs "
f"{target.combatant.id} d20={m.roll}+{m.attacker_cmb}={m.total} "