Fold COASTAL_BATTERY into HOSTILE_ARTILLERY, it's the same thing

Same pattern already established for AMMO_CACHE -> SUPPLY_CACHE: a
'Coastal Battery' is just a heavy fixed-emplacement HostileArtillery
under a different name in the game's own text, not a meaningfully
different unit type worth its own enum member. Removed the
TargetType.COASTAL_BATTERY member, added 'CoastalBattery' to
ocr.py's _TYPE_WORD_ALIASES (so 'Coastal Battery#2:' still parses,
now as a HostileArtillery), and a models.py migration entry so any
save file written before this change still loads correctly.

Verified the migration directly (COASTAL_BATTERY -> HOSTILE_ARTILLERY,
existing AMMO_CACHE -> SUPPLY_CACHE unaffected) and updated the one
test that asserted the old type.
This commit is contained in:
Dominik Moritz Roth 2026-08-09 20:06:44 +02:00
parent 57e73c58ea
commit d6694b585b
3 changed files with 13 additions and 8 deletions

View File

@ -41,10 +41,9 @@ class TargetType(Enum):
FDC = "FDC" # Fire Direction Center, coordinates enemy counter-battery fire FDC = "FDC" # Fire Direction Center, coordinates enemy counter-battery fire
INFANTRY = "Infantry" # hostile ground troops INFANTRY = "Infantry" # hostile ground troops
MECHANIZED = "Mechanized" # hostile armored/vehicle unit MECHANIZED = "Mechanized" # hostile armored/vehicle unit
HOSTILE_ARTILLERY = "Hostile Artillery" HOSTILE_ARTILLERY = "Hostile Artillery" # "Coastal Battery" is just this, see _TYPE_WORD_ALIASES in ocr.py
HOSTILE_TANK = "Hostile Tank" HOSTILE_TANK = "Hostile Tank"
PILLBOX = "Pillbox" # armoured emplacement, fixed position PILLBOX = "Pillbox" # armoured emplacement, fixed position
COASTAL_BATTERY = "Coastal Battery" # heavy fixed emplacement, reported by listening posts
MARINE_GARRISON = "Marine Garrison" # allied unit, requests fire support (see Target.requested_time) MARINE_GARRISON = "Marine Garrison" # allied unit, requests fire support (see Target.requested_time)
ENEMY = "Enemy" # ad-hoc installation named directly in the intel text ENEMY = "Enemy" # ad-hoc installation named directly in the intel text
# ("Enemy Signal Station", "Enemy Field Command"), not one of the # ("Enemy Signal Station", "Enemy Field Command"), not one of the
@ -60,8 +59,9 @@ class TargetType(Enum):
# Renamed/removed enum members, for loading save files written before the # Renamed/removed enum members, for loading save files written before the
# rename. AMMO_CACHE turned out to be a misreading of the game's actual # rename. AMMO_CACHE turned out to be a misreading of the game's actual
# "SupplyCache" name and was dropped in favor of it. # "SupplyCache" name and was dropped in favor of it. COASTAL_BATTERY
_TARGET_TYPE_MIGRATIONS = {"AMMO_CACHE": "SUPPLY_CACHE"} # turned out to just be a HOSTILE_ARTILLERY under a different name.
_TARGET_TYPE_MIGRATIONS = {"AMMO_CACHE": "SUPPLY_CACHE", "COASTAL_BATTERY": "HOSTILE_ARTILLERY"}
def _migrate_target_type(name: str) -> TargetType: def _migrate_target_type(name: str) -> TargetType:

View File

@ -515,8 +515,10 @@ _BARE_CLUE_VALUE_RE = re.compile(
_TYPE_BY_SHORT = {t.short: t for t in TargetType} _TYPE_BY_SHORT = {t.short: t for t in TargetType}
# The game's typewriter has used "AmmoCache" for what's now modeled as # The game's typewriter has used "AmmoCache" for what's now modeled as
# SupplyCache, treat it as the same type rather than dropping the target. # SupplyCache, and "CoastalBattery" for what's just a HostileArtillery
_TYPE_WORD_ALIASES = {"AmmoCache": "SupplyCache"} # under a different name, treat both as the same type rather than
# dropping the target or inventing a redundant enum member for it.
_TYPE_WORD_ALIASES = {"AmmoCache": "SupplyCache", "CoastalBattery": "HostileArtillery"}
_REF_NAMED_RE = re.compile(rf"^{_TYPE_ID_FRAGMENT}") _REF_NAMED_RE = re.compile(rf"^{_TYPE_ID_FRAGMENT}")

View File

@ -94,14 +94,17 @@ def test_enemy_destroyed_report():
def test_listening_post_and_coastal_battery(): def test_listening_post_and_coastal_battery():
"""'Coastal Battery' is an alias for HostileArtillery (see
_TYPE_WORD_ALIASES), not its own TargetType, it's just the
fixed-emplacement flavor of the same thing."""
text = """Listening Post#1 at K6 7:8 audio reports on: text = """Listening Post#1 at K6 7:8 audio reports on:
Coastal Battery#2: Coastal Battery#2:
Distance 6.28km South-East from Listening Post#1 Distance 6.28km South-East from Listening Post#1
""" """
info = ocr.parse_text(text) info = ocr.parse_text(text)
assert info.reference_points["ListeningPost#1"][2] == Coord("K", 6, 7, 8) assert info.reference_points["ListeningPost#1"][2] == Coord("K", 6, 7, 8)
assert (TargetType.COASTAL_BATTERY, "2") in info.targets assert (TargetType.HOSTILE_ARTILLERY, "2") in info.targets
_, clues, *_ = info.targets[(TargetType.COASTAL_BATTERY, "2")] _, clues, *_ = info.targets[(TargetType.HOSTILE_ARTILLERY, "2")]
assert clues == [ocr.Clue(reference="ListeningPost#1", bearing_deg=135.0, distance_km=6.28)] assert clues == [ocr.Clue(reference="ListeningPost#1", bearing_deg=135.0, distance_km=6.28)]