From b775d468508d4eb23e7cf56e2915dd4ded1a9aa3 Mon Sep 17 00:00:00 2001 From: ctan Date: Sun, 20 Sep 2026 00:16:18 +0200 Subject: [PATCH] fix(rules): document the burst center-to-center convention as a deferred SRD approximation - the SRD (aiming-a-spell.md) measures a burst from a grid INTERSECTION with the far-edge rule ('count from intersection to intersection'; a square is inside only 'if the far edge of a square is within the spell's area'), but the engine's burst origin is a CELL (target_pos, centered_on_caster, resolved target) counted center-to-center 5-10-5 in _creatures_in_range; matching RAW exactly needs an intersection coordinate added to AreaEffect/Action.target_pos/the observation area snapshot contract plus a per-caster corner choice (the far-edge boundary result depends entirely on which of the cell's four corners is the origin), so it is DEFERRED with a precise explanation in areas.py and a pointer from _creatures_in_range; no behavior change, golden untouched --- src/pf1e_simulator/areas.py | 31 +++++++++++++++++++++++++++++++ src/pf1e_simulator/combat.py | 6 ++++++ 2 files changed, 37 insertions(+) diff --git a/src/pf1e_simulator/areas.py b/src/pf1e_simulator/areas.py index 80af376..bd05ef4 100644 --- a/src/pf1e_simulator/areas.py +++ b/src/pf1e_simulator/areas.py @@ -32,6 +32,37 @@ PF1e (CRB, Magic — Aiming a Spell): Bursts need no template here: they are circles around a center cell and are resolved by ``CombatEngine._area_creatures`` (which additionally drops creatures without line of effect from the burst's center). + +BURST MEASUREMENT - DOCUMENTED DEFERRED APPROXIMATION (cell vs intersection). +The SRD ("Aiming a Spell - Area") measures a burst from a point of origin that +is ALWAYS a grid intersection: "instead of counting from the center of one +square to the center of the next, you count from intersection to intersection", +and a square is inside the area only "if the far edge of a square is within the +spell's area" (a square the area "only touches ... [at] the near edge" is +unaffected). The engine cannot apply that rule faithfully because its burst +origin is a CELL, not an intersection: ``Action.target_pos``, the +``centered_on_caster`` caster square and the resolved target's square are all +cells, and ``CombatEngine._creatures_in_range`` counts center-to-center 5-10-5 +(``grid.distance(center, cell) <= radius_cells``). Two consequences make a +faithful fix out of scope for a contract-preserving pass: + + 1. A cell has four corner intersections, and which one is the point of origin + is a caster choice the engine never records. The far-edge rule's boundary + result (which diagonal squares are in vs out) depends entirely on that + choice, so there is no single "SRD-correct" square set to compute from a + cell center without first adding an intersection coordinate to the data + model. + 2. Representing the origin as an intersection would change ``AreaEffect``, + ``Action.target_pos`` and the observation ``area`` snapshot contract (a + documented Phase 2 LLM frontier), and would rework the burst line-of-effect + and Reflex-cover origins (``{aim}`` / ``{center}`` cells). + +Until that representation change is made, bursts stay centered on the resolved +cell and measured center-to-center under 5-10-5 - a symmetric, conservative +approximation whose boundary squares can differ from RAW by one square on +diagonals (e.g. a 15/20-ft burst's far diagonal cells). This is the residual +recorded in docs/conformance-srd.md ("origin mesuré centre-à-centre plutôt +qu'intersection-à-intersection"). """ from __future__ import annotations diff --git a/src/pf1e_simulator/combat.py b/src/pf1e_simulator/combat.py index 81231a3..16050e0 100644 --- a/src/pf1e_simulator/combat.py +++ b/src/pf1e_simulator/combat.py @@ -7573,6 +7573,12 @@ class CombatEngine: ) -> list[CombatantState]: """Return creatures within radius_cells of center. + The burst center is a CELL and the radius is counted center-to-center + under 5-10-5 - a documented approximation of the SRD's + intersection-to-intersection far-edge measurement (see the + "BURST MEASUREMENT" note in ``areas.py`` for why a faithful + intersection origin is deferred). + ``active_only`` (default) keeps only acting creatures; an area spell passes False so its burst also catches dying/unconscious creatures (SRD aiming-a-spell.md: a burst "affects whatever it catches in its