Enable finite automatic convoy startup and scoped gunner skill

This commit is contained in:
2026-09-20 08:23:19 +02:00
parent 782c163c68
commit 5bd66d76ca
44 changed files with 3694 additions and 48 deletions
+22 -5
View File
@@ -1,11 +1,23 @@
from pathlib import Path
import importlib.util,json,tempfile
import importlib.util,json,tempfile,os,subprocess,sys
BASE=Path(os.environ.get("ERR_CONVOY_SETTINGS_ROOT", Path(__file__).resolve().parents[1]))
from policy_harness import ROOT,body,run
spec=importlib.util.spec_from_file_location('import_settings',ROOT/'tools/import_settings.py')
spec=importlib.util.spec_from_file_location('import_settings',BASE/'tools/import_settings.py')
m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)
result,deferred=m.merge({'configVersion':1,'reportIntervalSeconds':20.0,'supportSquadLimit':2,'responseRadiusMetres':1500,'convoyCount':3},{'aiIntelChancePercent':0,'intelRevealChancePercent':100})
assert result==dict(reportIntervalSeconds=20,supportSquadLimit=2,responseRadiusMetres=1500,aiIntelChancePercent=0,intelRevealChancePercent=100,radioDefenceSeconds=180,startTimeMinutes=840,timeMultiplier=1,startDateCode=0,weatherProfile=0,stingerChanceOneIn=15)
assert deferred==['ai:convoyCount']
assert result==dict(reportIntervalSeconds=20,supportSquadLimit=2,responseRadiusMetres=1500,aiIntelChancePercent=0,intelRevealChancePercent=100,radioDefenceSeconds=180,startTimeMinutes=840,timeMultiplier=1,startDateCode=0,weatherProfile=0,stingerChanceOneIn=15,automaticConvoyEnabled=1)
assert deferred==['ai:convoyCount=3 mapped to automaticConvoyEnabled=1; successor capped at one initial convoy']
for count in (0,1,3,20):
values,notes=m.merge(ai={'convoyCount':count})
assert values['automaticConvoyEnabled']==int(count>0)
assert bool(notes)==(count>1)
for invalid in (-1,21,True,False,None,'1',1.5,float('nan'),float('inf')):
try:m.merge(ai={'convoyCount':invalid})
except ValueError:pass
else:raise AssertionError(invalid)
try:m.merge(ai={'convoyCount':0,'automaticConvoyEnabled':1})
except ValueError:pass
else:raise AssertionError('Ambiguous old/new settings accepted')
assert m.merge(ai={'stingerChanceOneIn':37})[0]['stingerChanceOneIn']==37
assert m.merge(ai={'stingerChanceOneIn':0})[0]['stingerChanceOneIn']==0
assert m.merge()[0]=={k:v[0] for k,v in m.SCHEMA.items()}
@@ -23,8 +35,13 @@ with tempfile.TemporaryDirectory() as tmp:
else:raise AssertionError('Existing destination overwritten')
assert dest.read_bytes()==before
assert sorted(p.name for p in Path(tmp).iterdir())==['rereintroduced.json']
legacy=Path(tmp)/'legacy-ai.json';legacy.write_text('{"convoyCount":3}');original=legacy.read_bytes()
preview=subprocess.run([sys.executable,str(BASE/'tools/import_settings.py'),'--ai',str(legacy)],capture_output=True,text=True,check=True)
assert json.loads(preview.stdout)['automaticConvoyEnabled']==1
assert 'successor capped at one initial convoy' in preview.stderr and legacy.read_bytes()==original
# Test the actual runtime validator against the migration schema at each boundary.
validator=body(ROOT/'addons/core/Scripts/Game/ERR/ERR_Settings.c','static bool Valid(').replace('string key','std::string key')
validator=body(BASE/'addons/core/Scripts/Game/ERR/ERR_Settings.c','static bool Valid(').replace('string key','std::string key')
checks=[]
for key,(_,lo,hi) in m.SCHEMA.items():
for value,expected in [(lo-1,False),(lo,True),(hi,True),(hi+1,False)]: