Skip to content

Warehouses.load_dict: skip warehouse refs to airports absent from terrain#441

Merged
rp- merged 1 commit into
pydcs:masterfrom
MacrayBlackhand:fix-warehouse-load-dict-missing-airport
Jun 17, 2026
Merged

Warehouses.load_dict: skip warehouse refs to airports absent from terrain#441
rp- merged 1 commit into
pydcs:masterfrom
MacrayBlackhand:fix-warehouse-load-dict-missing-airport

Conversation

@MacrayBlackhand

Copy link
Copy Markdown
Contributor

Problem

Warehouses.load_dict() calls airport_by_id(x).load_from_dict(...) with no
None-guard:

def load_dict(self, data):
    for x in data.get("airports", {}):
        self.terrain.airport_by_id(x).load_from_dict(data["airports"][x])
    ...

But airport_by_id() is typed Optional[Airport] and returns None when a
warehouse references an airport id that isn't present in the current terrain.
That makes the call None.load_from_dict(...) → AttributeError, which aborts
the entire mission load.

This is reachable from real missions: when a mission is authored before a
terrain/map update that renumbers or removes airports, its saved warehouse data
keeps the old airport ids. The additive Syria map update is a concrete
trigger — it renumbered airports, leaving some previously-used ids (e.g. 78)
unused, so any pre-update Syria mission referencing them now fails to load.

Prior art

This is the same defect reported in #223 ("mission.load() on syria fails,
likely due to undefined warehouse-airport pairs", 2022) — a NoneType in
load_dict. That issue was self-closed by the reporter without a code fix, so
the unguarded call survived. The Syria renumber re-exposes it.

Fix

One-spot guard: if airport_by_id() returns None, skip that id and continue
to the next entry instead of crashing.

def load_dict(self, data):
    for x in data.get("airports", {}):
        airport = self.terrain.airport_by_id(x)
        if airport is None:
            # Warehouse data references an airport id not present in the
            # current terrain (e.g. a mission authored before a map update
            # that renumbered or removed airports).  Skip it rather than
            # crash the whole load on None.load_from_dict().
            continue
        airport.load_from_dict(data["airports"][x])
    ...

Minimal and behavior-preserving for the present-airport path: a valid airport id
loads exactly as before. Only the previously-crashing absent-id case changes —
from AttributeError to a skip. The warehouses sub-dict loop is untouched.

Test

Adds WarehousesTest.test_load_dict_skips_airport_absent_from_terrain: calls
load_dict with an airports dict containing an id not in the Caucasus
terrain followed by a valid id, and asserts (a) it does not raise and (b) the
valid airport entry still loads. The absent id is iterated first so the loop
must continue past it. (Confirmed to fail with the pre-fix AttributeError
without the guard.)

…rain

load_dict() called airport_by_id(x).load_from_dict() with no None-guard, but
airport_by_id() is Optional[Airport] and returns None when a warehouse
references an airport id not present in the current terrain -- aborting the
whole mission load with an AttributeError. This happens with missions authored
before a terrain/map update that renumbered or removed airports (e.g. Syria
after the additive map update left some old ids unused). Skip the absent id
instead of crashing. See pydcs#223 for a prior report of this failure.
@rp- rp- merged commit 72fc5e1 into pydcs:master Jun 17, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants