Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
122 lines
6.7 KiB
Python
122 lines
6.7 KiB
Python
"""Exercise universal connector exports, resource safety and project isolation."""
|
|
import copy
|
|
import json
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'tools'))
|
|
import terrain_adapter as adapter
|
|
|
|
nogova = json.loads((adapter.ROOT / 'terrain-profiles/nogova.json').read_text())
|
|
production = adapter.files(nogova)
|
|
for directory, expected in [('terrain-integrations/nogova', production),
|
|
('terrain-adapters/nogova', adapter.files(nogova, standalone=True))]:
|
|
for name, content in expected.items():
|
|
assert (adapter.ROOT / directory / name).read_text() == content, (directory, name)
|
|
assert 'addon.gproj' not in production # Terrain authors publish in their own addon.
|
|
production_report = json.loads(production['terrain-adapter.json'])
|
|
assert production_report['mode'] == 'embedded'
|
|
assert production_report['addon'] == nogova['addon']
|
|
assert production_report['required_dependencies'] == [adapter.BASE, adapter.CORE]
|
|
assert nogova['addon'] not in production_report['required_dependencies'] # No self-cycle.
|
|
connector = (adapter.ROOT / 'addons/core/Prefabs/ERR/TerrainConnector.et').read_text()
|
|
assert 'm_TerrainIndependent 1' in connector
|
|
assert all(name in connector for name in ['PerceptionManager', 'SCR_FactionManager', 'SCR_LoadoutManager', 'RadioManagerEntity', 'ScriptedChatEntity'])
|
|
assert 'Nogova' not in connector and 'SCR_AIWorld' not in connector
|
|
assert json.loads(production['terrain-adapter.json'])['navigation_configured']
|
|
assert 'SCR_AIWorld' not in production['Worlds/ERR_nogova_Layers/default.layer']
|
|
assert 'coords 1525 0 5075' in production['Worlds/ERR_nogova_Layers/default.layer']
|
|
# Missing-navigation fixture remains hidden and clearly unqualified.
|
|
nogova.pop('inherit_ai_world',None)
|
|
rendered = adapter.files(nogova)
|
|
report = json.loads(rendered['terrain-adapter.json'])
|
|
assert not report['navigation_configured'] and report['qualification'] == 'unverified'
|
|
assert report['scenarioId'] == '{681921DC9921F915}Missions/ERR_nogova.conf'
|
|
assert nogova['world'] in rendered['Worlds/ERR_nogova.ent']
|
|
layer = rendered['Worlds/ERR_nogova_Layers/default.layer']
|
|
assert adapter.CONNECTOR in layer and 'Eden' not in layer
|
|
assert 'SCR_FactionManager' not in layer # No copied managers to drift between maps.
|
|
assert 'm_bShowInScenarioMenu 0' in rendered['Missions/ERR_nogova.conf']
|
|
standalone = adapter.files(nogova, standalone=True)
|
|
assert nogova['addon'] in standalone['addon.gproj'] and adapter.CORE in standalone['addon.gproj']
|
|
assert json.loads(standalone['terrain-adapter.json'])['addon'] == '26C8E5E5AC063B1A'
|
|
assert standalone['Worlds/ERR_nogova_Layers/default.layer'] == layer
|
|
assert standalone['Missions/ERR_nogova.conf'] == rendered['Missions/ERR_nogova.conf']
|
|
|
|
# A different terrain needs only its own resources/hints, never another connector.
|
|
other = dict(nogova, id='other_map', title='Other Map', addon='AAAAAAAAAAAAAAAA',
|
|
world='{BBBBBBBBBBBBBBBB}Worlds/Other.ent', inherit_ai_world=True, airfields=[], notes='')
|
|
other_files = adapter.files(other)
|
|
assert other_files['Worlds/ERR_other_map_Layers/default.layer'] == 'ERR_GameMode Reintroduced : "'+adapter.CONNECTOR+'" {}\n'
|
|
assert other['world'] in other_files['Worlds/ERR_other_map.ent']
|
|
assert 'Nogova' not in ''.join(other_files.values())
|
|
assert other_files['Missions/ERR_other_map.conf.meta'] != production['Missions/ERR_nogova.conf.meta']
|
|
|
|
ready = copy.deepcopy(nogova)
|
|
ready['navigation'] = {
|
|
'Soldiers': ['{1111111111111111}Configs/Navmesh/Soldiers.conf'],
|
|
'BTRlike': ['{2222222222222222}Navmeshes/Vehicles.nmn'],
|
|
'LowRes': ['{3333333333333333}Navmeshes/LowRes.nmn'],
|
|
}
|
|
built = adapter.files(ready)
|
|
layer = built['Worlds/ERR_nogova_Layers/default.layer']
|
|
assert 'NavmeshFile "{2222222222222222}Navmeshes/Vehicles.nmn"' in layer
|
|
assert ': "{1111111111111111}Configs/Navmesh/Soldiers.conf" {}' in layer
|
|
assert 'NavmeshSettings NavmeshWorld "{5C8C9B750B60C6E2}"' in layer
|
|
assert json.loads(built['terrain-adapter.json'])['navigation_configured']
|
|
assert 'm_bShowInScenarioMenu 1' in built['Missions/ERR_nogova.conf']
|
|
assert adapter.files(ready) == built # Stable scenario IDs and files on regeneration.
|
|
|
|
markers = copy.deepcopy(nogova)
|
|
markers['ai_world'] = '{4444444444444444}Prefabs/AI/TerrainAIWorld.et'
|
|
markers['airfields'] = [[1234, 0, -200], [5000, 30, 6000]]
|
|
marker_files = adapter.files(markers)
|
|
marker_layer = marker_files['Worlds/ERR_nogova_Layers/default.layer']
|
|
assert 'GenericEntity ERR_Airfield_0 {\n coords 1234 0 -200\n}' in marker_layer
|
|
assert 'GenericEntity ERR_Airfield_1' in marker_layer
|
|
assert markers['ai_world'] in marker_layer and 'NavmeshFile' not in marker_layer
|
|
assert json.loads(marker_files['terrain-adapter.json'])['navigation_configured']
|
|
|
|
inherited = copy.deepcopy(nogova)
|
|
inherited['inherit_ai_world'] = True
|
|
inherited_files = adapter.files(inherited)
|
|
assert 'SCR_AIWorld' not in inherited_files['Worlds/ERR_nogova_Layers/default.layer']
|
|
assert json.loads(inherited_files['terrain-adapter.json'])['navigation_configured']
|
|
try:
|
|
adapter.files(dict(ready, inherit_ai_world=True))
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
raise AssertionError('Duplicate AI worlds allowed')
|
|
|
|
for key, value in [('airfields', [[float('nan'), 0, 0]]), ('airfields', [[True, 0, 0]]),
|
|
('airfields', [[1, 2]]), ('airfields', [[1, 2, 3]] * 17),
|
|
('ai_world', '{1111111111111111}bad.conf'), ('id', '../escape'), ('title', 'Bad" title'), ('addon', 'bad'),
|
|
('world', '{1111111111111111}/absolute.ent'),
|
|
('world', '{1111111111111111}Worlds/../escape.ent'),
|
|
('world', '{1111111111111111}Worlds/./escape.ent'),
|
|
('world', '{1111111111111111}Worlds//escape.ent'),
|
|
('world', '{1111111111111111}Worlds/escape.conf'),
|
|
('navigation', {'Unknown': ['{1111111111111111}a.nmn']}),
|
|
('navigation', {'Soldiers': []}),
|
|
('navigation', {'Soldiers': ['{1111111111111111}a.nmn'] * 2})]:
|
|
invalid = copy.deepcopy(nogova); invalid[key] = value
|
|
try:
|
|
adapter.files(invalid)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
raise AssertionError((key, value))
|
|
with tempfile.TemporaryDirectory() as temp:
|
|
dest = Path(temp) / 'adapter'
|
|
adapter.generate(ready, dest)
|
|
before = {str(p.relative_to(dest)): p.read_bytes() for p in dest.rglob('*') if p.is_file()}
|
|
try:
|
|
adapter.generate(nogova, dest)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
raise AssertionError('Existing adapter was overwritten')
|
|
assert before == {str(p.relative_to(dest)): p.read_bytes() for p in dest.rglob('*') if p.is_file()}
|
|
print('PASS: universal connector, embedded/standalone exports, multiple terrains, stable IDs, native nav configs, missing-nav visibility, path validation and overwrite protection')
|