diff --git a/src/pf1e_simulator/map.py b/src/pf1e_simulator/map.py index dcf87c9..1adc4f4 100644 --- a/src/pf1e_simulator/map.py +++ b/src/pf1e_simulator/map.py @@ -20,6 +20,12 @@ Pos = tuple[int, int] # (row, col), 0-indexed _ELEVATION_CHARS = frozenset("0123456789.") # digit = height, '.' = ground level +# PF1e (SRD, Measuring Distance): one grid square is 5 feet. The engine's +# whole geometry (5-10-5 diagonals, step costs, ranges, auras) is hard-coded +# to that scale, so a map declaring any other square size is a data error -- +# fail fast at load instead of silently mis-scaling. +_SQUARE_SIZE_FT = 5 + _COVER_BONUS_LEVELS = frozenset({0, 2, 4, 8}) # PF1e: none / partial / standard / improved @@ -152,6 +158,14 @@ def load_map(path: Path) -> MapSpec: except ValidationError as exc: raise MapValidationError(path, str(exc)) from exc + if raw.square_size_ft != _SQUARE_SIZE_FT: + msg = ( + f"square_size_ft must be {_SQUARE_SIZE_FT} (one grid square is 5 " + "feet, SRD Measuring Distance); the engine geometry is hard-coded " + f"to 5-ft squares, got {raw.square_size_ft}" + ) + raise MapValidationError(path, msg) + terrain = _split_rows(raw.terrain, "terrain", path) height, width = len(terrain), len(terrain[0]) unknown = sorted({ch for row in terrain for ch in row if ch not in raw.legend}) diff --git a/tests/test_map.py b/tests/test_map.py index b10eef9..d1f50f2 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -73,6 +73,20 @@ def test_rejects_ragged_rows(tmp_path: Path) -> None: load_map(_write(tmp_path, body)) +def test_rejects_non_five_foot_square_size(tmp_path: Path) -> None: + """SRD Measuring Distance: one grid square is 5 ft; the engine geometry + is hard-coded to that scale, so any other declared size is a load error + (fail fast) rather than a silent mis-scaling.""" + body = _minimal_map().replace('name: "Test"\n', 'name: "Test"\nsquare_size_ft: 10\n') + with pytest.raises(MapValidationError, match="square_size_ft must be 5"): + load_map(_write(tmp_path, body)) + + +def test_accepts_explicit_five_foot_square_size(tmp_path: Path) -> None: + body = _minimal_map().replace('name: "Test"\n', 'name: "Test"\nsquare_size_ft: 5\n') + assert load_map(_write(tmp_path, body)).square_size_ft == 5 + + def test_rejects_char_missing_from_legend(tmp_path: Path) -> None: body = _minimal_map().replace(" #.#\n", " #x#\n") with pytest.raises(MapValidationError):