FEnigma/src/fenigma/ballistics.py
Dominik Roth ba6b07a476 Sweep em-dashes out of code, docstrings, and requirements.txt
Same style fix applied to the README earlier, extended everywhere:
replaced " -- " with commas/colons/periods (picking whichever reads
right per occurrence, splitting into two sentences where the clauses
were independent), fixed a few user-facing strings along the way
(entity list rows, placement/strike toasts, ambiguous-candidate tag,
shell picker button label). Left three intentional non-prose uses
alone: the "unassigned" dash glyph in firing_panel.py (and its
docstring diagram), and ocr.py's dash-variant regex character class,
which needs to literally match em/en-dashes in OCR'd text.

Also caught and fixed a stale models.py docstring claiming "no solver
yet" (solver.py has existed for a while) while touching that
paragraph anyway, and a formatting artifact in coord_dialog.py's
docstring left by the sed pass (misaligned comma from a since-removed
alignment gap).

Verified: py_compile across all files, the 9-screenshot OCR
regression sweep, and a GTK smoke test exercising the edited
toast/placement code paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 20:27:53 +02:00

47 lines
1.4 KiB
Python

"""Firing-solution math, from High Command's gunnery tables.
Distances/bearings reuse the same board-units-are-km convention as
solver.py. Powder charge controls elevation: more charge, lower arc for
the same distance, up to MAX_POWDER_CHARGE; min_powder_charge() is the
least charge that can still reach a given distance at all.
"""
from __future__ import annotations
import math
from .models import Coord
MAX_POWDER_CHARGE = 6
def distance_km_point(a: tuple[float, float], b: tuple[float, float]) -> float:
return math.hypot(b[0] - a[0], b[1] - a[1])
def bearing_deg_point(a: tuple[float, float], b: tuple[float, float]) -> float:
"""Compass bearing from a to b: 0 = north (+row), 90 = east (+col),
matching solver.py's convention, the inverse of
solver.point_from_bearing_distance()."""
return math.degrees(math.atan2(b[0] - a[0], b[1] - a[1])) % 360
def distance_km(a: Coord, b: Coord) -> float:
return distance_km_point(a.as_fraction(), b.as_fraction())
def bearing_deg(a: Coord, b: Coord) -> float:
return bearing_deg_point(a.as_fraction(), b.as_fraction())
def min_powder_charge(dist_km: float, eps: float = 1e-9) -> int:
n = dist_km / 5
rounded = round(n)
if abs(n - rounded) < eps:
return max(1, rounded)
return math.ceil(n)
def elevation_deg(dist_km: float, charges: int) -> float:
return 12 * dist_km / charges