Buildings and levels for the floor map, and make every identifier searchable

This commit is contained in:
2026-08-17 13:03:54 -04:00
parent 47fb65fdd2
commit fbd99041b0
54 changed files with 4173 additions and 880 deletions

View File

@@ -46,42 +46,60 @@ def test_resolve_asset_position_uses_self_when_set():
class FakeLocation:
mapx = 10
mapy = 20
levelid = 9
class FakeAsset:
mapx = 100
mapy = 200
levelid = 7
location = FakeLocation()
result = resolve_asset_position(FakeAsset())
assert result == {'mapx': 100, 'mapy': 200, 'positionsource': 'self'}
assert result == {'mapx': 100, 'mapy': 200, 'levelid': 7,
'positionsource': 'self'}
def test_resolve_asset_position_falls_back_to_location():
"""When asset has no coords, falls back to location coords."""
"""When asset has no coords, falls back to location coords.
The level comes with them (ADR-017). The asset here carries a STALE levelid
of its own with no coordinates to go with it - a leftover from a position
that was cleared - and returning that level beside the location's
coordinates would draw the location's spot on the wrong drawing.
"""
class FakeLocation:
mapx = 50
mapy = 75
levelid = 9
class FakeAsset:
mapx = None
mapy = None
levelid = 7
location = FakeLocation()
result = resolve_asset_position(FakeAsset())
assert result == {'mapx': 50, 'mapy': 75, 'positionsource': 'location'}
assert result == {'mapx': 50, 'mapy': 75, 'levelid': 9,
'positionsource': 'location'}
def test_resolve_asset_position_handles_asset_without_mapx_attr():
"""Assets that don't yet have mapx/mapy columns degrade gracefully."""
"""Assets that don't yet have mapx/mapy columns degrade gracefully.
Nor a levelid attribute: a plugin extension row passed in here has none,
and reading it must not raise.
"""
class FakeLocation:
mapx = 1
mapy = 2
levelid = 4
class FakeAsset:
location = FakeLocation()
result = resolve_asset_position(FakeAsset())
assert result == {'mapx': 1, 'mapy': 2, 'positionsource': 'location'}
assert result == {'mapx': 1, 'mapy': 2, 'levelid': 4,
'positionsource': 'location'}
# --- Relationship-walk path (priority 2 in the chain) ----------------------
@@ -105,7 +123,8 @@ def _make_rel(rtype_name, neighbor, inheritsposition=True, isactive=True):
return r
def _make_asset(assetid, mapx=None, mapy=None, outgoing=None, incoming=None, location=None):
def _make_asset(assetid, mapx=None, mapy=None, levelid=None, outgoing=None,
incoming=None, location=None):
class FakeAsset:
pass
@@ -113,6 +132,7 @@ def _make_asset(assetid, mapx=None, mapy=None, outgoing=None, incoming=None, loc
a.assetid = assetid
a.mapx = mapx
a.mapy = mapy
a.levelid = levelid
a.outgoing_relationships = outgoing or []
a.incoming_relationships = incoming or []
a.location = location
@@ -121,24 +141,26 @@ def _make_asset(assetid, mapx=None, mapy=None, outgoing=None, incoming=None, loc
def test_resolve_asset_position_walks_partof_edge():
"""Priority 2: inheritsposition=true partof edge resolves from neighbor."""
parent = _make_asset(assetid=2, mapx=300, mapy=400)
parent = _make_asset(assetid=2, mapx=300, mapy=400, levelid=2)
rel = _make_rel('partof', parent)
child = _make_asset(assetid=1, outgoing=[rel])
result = resolve_asset_position(child)
assert result == {'mapx': 300, 'mapy': 400, 'positionsource': 'related'}
assert result == {'mapx': 300, 'mapy': 400, 'levelid': 2,
'positionsource': 'related'}
def test_resolve_asset_position_walks_controls_after_partof():
"""Priority 2 ordering: partof beats controls when both have coords."""
partof_neighbor = _make_asset(assetid=10, mapx=11, mapy=12)
controls_neighbor = _make_asset(assetid=20, mapx=99, mapy=99)
partof_neighbor = _make_asset(assetid=10, mapx=11, mapy=12, levelid=3)
controls_neighbor = _make_asset(assetid=20, mapx=99, mapy=99, levelid=8)
rel_partof = _make_rel('partof', partof_neighbor)
rel_controls = _make_rel('controls', controls_neighbor)
asset = _make_asset(assetid=1, outgoing=[rel_controls, rel_partof])
result = resolve_asset_position(asset)
assert result == {'mapx': 11, 'mapy': 12, 'positionsource': 'related'}
assert result == {'mapx': 11, 'mapy': 12, 'levelid': 3,
'positionsource': 'related'}
def test_resolve_asset_position_skips_non_inheritable_type():
@@ -161,12 +183,14 @@ def test_resolve_asset_position_skips_when_inheritsposition_false():
def test_resolve_asset_position_walks_recursively():
"""The walk recurses: child -> middle -> root, where only root has coords."""
root = _make_asset(assetid=3, mapx=1, mapy=2)
root = _make_asset(assetid=3, mapx=1, mapy=2, levelid=5)
middle = _make_asset(assetid=2, outgoing=[_make_rel('partof', root)])
child = _make_asset(assetid=1, outgoing=[_make_rel('partof', middle)])
result = resolve_asset_position(child)
assert result == {'mapx': 1, 'mapy': 2, 'positionsource': 'related'}
# Carried back up two hops from the node that actually has coordinates.
assert result == {'mapx': 1, 'mapy': 2, 'levelid': 5,
'positionsource': 'related'}
def test_resolve_asset_position_breaks_cycles():
@@ -195,12 +219,13 @@ def test_resolve_asset_position_depth_cap_is_three():
def test_resolve_asset_position_self_beats_related():
"""Priority 1 beats priority 2: asset's own coords win even when a
related neighbor would also resolve."""
neighbor = _make_asset(assetid=2, mapx=99, mapy=99)
neighbor = _make_asset(assetid=2, mapx=99, mapy=99, levelid=9)
rel = _make_rel('partof', neighbor)
asset = _make_asset(assetid=1, mapx=1, mapy=2, outgoing=[rel])
asset = _make_asset(assetid=1, mapx=1, mapy=2, levelid=1, outgoing=[rel])
result = resolve_asset_position(asset)
assert result == {'mapx': 1, 'mapy': 2, 'positionsource': 'self'}
assert result == {'mapx': 1, 'mapy': 2, 'levelid': 1,
'positionsource': 'self'}
def test_resolve_asset_position_related_beats_location():
@@ -209,13 +234,18 @@ def test_resolve_asset_position_related_beats_location():
class FakeLocation:
mapx = 500
mapy = 600
levelid = 9
neighbor = _make_asset(assetid=2, mapx=10, mapy=20)
neighbor = _make_asset(assetid=2, mapx=10, mapy=20, levelid=2)
rel = _make_rel('partof', neighbor)
asset = _make_asset(assetid=1, outgoing=[rel], location=FakeLocation())
asset = _make_asset(assetid=1, levelid=7, outgoing=[rel],
location=FakeLocation())
result = resolve_asset_position(asset)
assert result == {'mapx': 10, 'mapy': 20, 'positionsource': 'related'}
# Three levels are in play - the asset's own stale 7, the location's 9, and
# the neighbour's 2. Only the one that owns the coordinates is correct.
assert result == {'mapx': 10, 'mapy': 20, 'levelid': 2,
'positionsource': 'related'}
def test_resolve_asset_position_inactive_edge_skipped():