Build independent base gameplay and source-bound native verification
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
"""Exercise actual Core alias, integer parsing and settings-boundary bodies.
|
||||
Native dispatch/storage and remote ownership remain separate tests.
|
||||
"""
|
||||
from policy_harness import ROOT,body,run
|
||||
admin=ROOT/'addons/core/Scripts/Game/ERR/ERR_AdminService.c'
|
||||
settings=ROOT/'addons/core/Scripts/Game/ERR/ERR_Settings.c'
|
||||
rules='\n'.join(body(path,sig) for path,sig in ((admin,'static string CoreSetting('),(admin,'static bool Integer('),(settings,'static bool Valid(')))
|
||||
rules=(rules.replace('string ', 'std::string ').replace('out int ', 'int& ')
|
||||
.replace('text.Length()', 'int(text.size())').replace('.IsEmpty()', '.empty()')
|
||||
.replace('.Substring(', '.substr(').replace('"0123456789".IndexOf(', 'std::string("0123456789").find(')
|
||||
.replace(' < 0) return false;', ' == std::string::npos) return false;').replace('text.ToInt()', 'std::stoi(text)'))
|
||||
run('#include <cassert>\n#include <string>\n'+rules+r'''
|
||||
int main(){
|
||||
assert(CoreSetting("intel","reveal")=="intelRevealChancePercent");
|
||||
assert(CoreSetting("mission","radio")=="radioDefenceSeconds");
|
||||
for(std::string section:{"", "hq", "mission", "intel", "unknown"})
|
||||
for(std::string option:{"", "interval", "carrying", "radio", "reveal", "unknown"}){
|
||||
bool recognized=(section=="intel"&&option=="reveal")||(section=="mission"&&option=="radio");
|
||||
assert(CoreSetting(section,option).empty()==!recognized);
|
||||
}
|
||||
int parsed=-1;
|
||||
for(std::string malformed:{"", "-1", "+1", "1.0", "10junk", "1e2", "0x10", " 50", "50 ", "1234567"})
|
||||
assert(!Integer(malformed,parsed));
|
||||
for(int value=0;value<=101;++value){
|
||||
assert(Integer(std::to_string(value),parsed)&&parsed==value);
|
||||
assert(Valid(CoreSetting("intel","reveal"),parsed)==(value<=100));
|
||||
}
|
||||
assert(Integer("000050",parsed)&&parsed==50);
|
||||
assert(Integer("999999",parsed)&&!Valid("intelRevealChancePercent",parsed));
|
||||
assert(!Valid("intelRevealChancePercent",-1));
|
||||
assert(Valid("radioDefenceSeconds",30)&&Valid("radioDefenceSeconds",1800));
|
||||
assert(!Valid("radioDefenceSeconds",29)&&!Valid("radioDefenceSeconds",1801));
|
||||
}
|
||||
''')
|
||||
print('PASS: Core document-discovery alias, companion-only alias exclusion, strict integer parsing and setting boundaries')
|
||||
@@ -0,0 +1,17 @@
|
||||
from policy_harness import ROOT, body, run
|
||||
import re
|
||||
source = body(ROOT/'addons/core/Scripts/Game/ERR/ERR_CasualtyGroundSupport.c', 'static bool SupportHit(')
|
||||
source = re.sub(r'(?<![\w.])(\d+\.\d+)(?![\w.])', r'\1f', source)
|
||||
run('#include <cassert>\n' + source + '''
|
||||
int main() {
|
||||
assert(SupportHit(0,1,false,false));
|
||||
assert(SupportHit(0.999,0.85,false,false));
|
||||
assert(!SupportHit(1,1,false,false)); // no hit is no support
|
||||
assert(!SupportHit(-0.01,1,false,false));
|
||||
assert(!SupportHit(0.5,0.849,false,false));
|
||||
assert(!SupportHit(0.5,-1,false,false));
|
||||
assert(!SupportHit(0.5,1,true,false)); // own landing gear cannot support itself
|
||||
assert(!SupportHit(0.5,1,false,true)); // player/vehicle is not static parking support
|
||||
}
|
||||
''')
|
||||
print('PASS: actual short-cast hit, upward support normal, no self support and no dynamic support')
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Run the actual fixture method against a narrow native-damage API fake."""
|
||||
from policy_harness import ROOT, body, run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/ERR_NativeCasualtyLoadProbe.c'
|
||||
method=body(p,'protected bool IncapacitateGroundCasualty(')
|
||||
method=method.replace('protected bool','bool').replace('m_Damage.','m_Damage->').replace('resilience.','resilience->').replace('ERR_Actors.','ERR_Actors::').replace('null','nullptr')
|
||||
run('''
|
||||
#include <cassert>
|
||||
#include <initializer_list>
|
||||
struct NativeHitZone { float health=100; float GetHealth(){return health;} };
|
||||
using HitZone=NativeHitZone*;
|
||||
struct Actor { bool alive=true; };
|
||||
struct Damage {
|
||||
NativeHitZone resilience; bool enabled=true, missing=false, swallow=false; int forces=0;
|
||||
HitZone GetResilienceHitZone(){return missing ? nullptr : &resilience;}
|
||||
bool IsDamageHandlingEnabled(){return enabled;}
|
||||
void EnableDamageHandling(bool value){enabled=value;}
|
||||
void ForceUnconsciousness(){forces++; if(enabled && !swallow)resilience.health=0;}
|
||||
};
|
||||
struct ERR_Actors {static bool Alive(Actor* actor){return actor && actor->alive;}};
|
||||
Actor* m_Casualty; Damage* m_Damage;
|
||||
bool Check(bool value,const char*){return value;}
|
||||
template<class... Args> void PrintFormat(const char*,Args...){}
|
||||
'''+method+'''
|
||||
int main(){
|
||||
Actor actor; m_Casualty=&actor;
|
||||
for(bool original:{false,true}) {
|
||||
Damage damage; m_Damage=&damage; damage.enabled=original;
|
||||
assert(IncapacitateGroundCasualty());
|
||||
assert(damage.forces==1 && damage.resilience.health==0 && actor.alive);
|
||||
assert(damage.enabled==original);
|
||||
// No second artificial reduction: already-zero initial state must fail.
|
||||
assert(!IncapacitateGroundCasualty());
|
||||
assert(damage.enabled==original);
|
||||
}
|
||||
Damage blocked; blocked.enabled=false; blocked.swallow=true; m_Damage=&blocked;
|
||||
assert(!IncapacitateGroundCasualty());
|
||||
assert(blocked.forces==1 && !blocked.enabled && actor.alive);
|
||||
Damage missing; missing.missing=true; m_Damage=&missing;
|
||||
assert(!IncapacitateGroundCasualty() && missing.forces==0);
|
||||
m_Damage=nullptr; assert(!IncapacitateGroundCasualty());
|
||||
Damage dead; m_Damage=&dead; actor.alive=false;
|
||||
assert(!IncapacitateGroundCasualty() && dead.forces==0);
|
||||
}
|
||||
''')
|
||||
print('PASS: actual fixture method permits one native resilience operation and restores either prior protection state, including failure paths')
|
||||
@@ -0,0 +1,41 @@
|
||||
from policy_harness import ROOT, body, run
|
||||
import re
|
||||
path = ROOT/'addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingService.c'
|
||||
source = '\n'.join(body(path, signature) for signature in [
|
||||
'static int RoundAction(', 'static bool ObserveTicket(', 'static bool Within(', 'static bool SettledFor(', 'static bool Ranges(', 'static bool AircraftMotion('])
|
||||
# Enforce float literals are single precision; C++ otherwise promotes these to doubles.
|
||||
source = re.sub(r'(?<![\w.])(\d+\.\d+)(?![\w.])', r'\1f', source)
|
||||
source = source.replace('ERR_Phase.PREPARING', '0').replace('ERR_MissionRules.Terminal(phase)', '(phase == 5 || phase == 6)')
|
||||
run('#include <cassert>\n' + source + '''
|
||||
int main() {
|
||||
assert(RoundAction(0)==0); // pre-start roster callbacks must not latch Stop
|
||||
for (int phase=1;phase<=4;phase++) assert(RoundAction(phase)==1);
|
||||
assert(RoundAction(5)==2); assert(RoundAction(6)==2);
|
||||
for (int flags=0;flags<8;flags++) {
|
||||
bool committed=flags&1, physical=flags&2, authorized=flags&4;
|
||||
bool keep=physical && (committed || authorized);
|
||||
assert(ObserveTicket(committed,physical,authorized)==keep);
|
||||
}
|
||||
assert(ObserveTicket(true,true,false)); // disconnect after irreversible commit retains seat
|
||||
assert(!ObserveTicket(false,true,false)); // before commit it cancels
|
||||
// A late/reordered/expired lease cannot authorize a native entry.
|
||||
assert(Within(1000,1000,3000)); assert(Within(1000,3999,3000));
|
||||
assert(!Within(1000,4000,3000)); assert(!Within(1000,999,3000));
|
||||
assert(!Within(2147483000,-2147483000,3000));
|
||||
// A continuous observation window needs both sufficient settling and fresh data.
|
||||
assert(!SettledFor(-1,5000,5000)); assert(!SettledFor(1000,5999,5999));
|
||||
assert(SettledFor(1000,6000,6000)); assert(SettledFor(1000,6000,7999));
|
||||
assert(!SettledFor(1000,6000,8000)); assert(!SettledFor(1000,6000,5999));
|
||||
assert(!SettledFor(6000,6000,6000)); // recovered/moving body starts a new window
|
||||
// Every participant must independently be inside the physical rescue range.
|
||||
assert(Ranges(3,12,12)); assert(!Ranges(3.01,12,12));
|
||||
assert(!Ranges(3,12.01,12)); assert(!Ranges(3,12,12.01));
|
||||
assert(!Ranges(-1,0,0)); assert(!Ranges(0,-1,0)); assert(!Ranges(0,0,-1));
|
||||
// Contact alone cannot authorize an airborne, inverted, sliding or turning craft.
|
||||
assert(AircraftMotion(true,1,0,0)); assert(AircraftMotion(true,0.85,0.5,0.1));
|
||||
assert(!AircraftMotion(false,1,0,0)); assert(!AircraftMotion(true,-1,0,0));
|
||||
assert(!AircraftMotion(true,0.849,0,0)); assert(!AircraftMotion(true,1,0.501,0));
|
||||
assert(!AircraftMotion(true,1,0,0.101));
|
||||
}
|
||||
''')
|
||||
print('PASS: rescue lease expiry/reordering, fresh five-second settling, independent rescue ranges and grounded upright aircraft motion boundaries')
|
||||
@@ -0,0 +1,43 @@
|
||||
"""Fixture boundaries and actual safe-exit policy; not an engine substitute."""
|
||||
from policy_harness import ROOT, body, run
|
||||
import re
|
||||
source = ROOT/'addons/core/Scripts/Game/ERR/ERR_NativeCasualtyRemoveProbe.c'
|
||||
method = body(source,'static bool SafeExit(')
|
||||
method = re.sub(r'(?<![\w.])(\d+\.\d+)(?![\w.])',r'\1f',method)
|
||||
run('#include <cassert>\n'+method+'''
|
||||
int main() {
|
||||
assert(SafeExit(0,0));
|
||||
assert(SafeExit(1.25,-0.25));
|
||||
assert(SafeExit(1.25,1.5));
|
||||
assert(!SafeExit(1.251,0));
|
||||
assert(!SafeExit(-0.01,0));
|
||||
assert(!SafeExit(1,-0.251));
|
||||
assert(!SafeExit(1,1.501));
|
||||
}
|
||||
''')
|
||||
text=source.read_text()
|
||||
assert text.startswith('#ifdef WORKBENCH')
|
||||
assert 'GetContextIndex("door_l01_door") >= 0' in text
|
||||
assert 'actions.Contains(m_Remove)' in text
|
||||
assert 'PickDoorIndexForPoint(m_Context.GetOrigin())' in text
|
||||
assert 'GetExitTeleportPointInfo()' in text
|
||||
assert 'm_Remove.GetActionDuration() == 1' in text
|
||||
assert 'm_Remove.CanBeShown(m_Caller)' in text and 'm_Remove.CanBePerformed(m_Caller)' in text
|
||||
assert text.count('m_Remove.PerformAction(') == 1
|
||||
assert 'm_Remove.SetCompartmentDoorInfoIndex(m_SavedDoor)' in body(source,'override void Cleanup(')
|
||||
assert 'if (m_RemoveStopped) return -1;' in body(source,'override int Tick(')
|
||||
for forbidden in ('GetOutVehicle(', 'GetOutVehicle_NoDoor(', 'KickFromVehicle(', 'SetUnconscious(', 'm_Casualty.Set', 'm_Casualty).Teleport', 'SetActive('):
|
||||
assert forbidden not in text,forbidden
|
||||
# Existing arbitrary-ground mechanism remains exactly its original contract.
|
||||
base=ROOT/'addons/core/Scripts/Game/ERR/ERR_NativeCasualtyLoadProbe.c'
|
||||
assert 'GetOutVehicle_NoDoor(exitTransform, true, false)' in body(base,'protected bool BeginExit(')
|
||||
assert 'exitTransform[3] = m_GroundPosition;' in body(base,'protected bool BeginExit(')
|
||||
assert body(base,'protected bool ExitSettled(').strip().endswith('return ERR_Actors.Alive(m_Casualty) && vector.Distance(m_Casualty.GetOrigin(), m_GroundPosition) < 4 && m_Seat.IsCompartmentAccessible();\n }')
|
||||
base_text=(ROOT/'addons/core/Scripts/Game/ERR/ERR_NativeBaseSmoke.c').read_text()
|
||||
assert base_text.count('m_ERRExtractionCase == "casualty-remove"') == 3
|
||||
assert 'm_ERRCasualtyLoadProbe = new ERR_NativeCasualtyRemoveProbe(this);' in base_text
|
||||
assert 'm_ERRBaseStep = 802; Transition(ERR_Phase.DEFEAT);' in base_text
|
||||
runner=(ROOT/'tools/headless_workbench.py').read_text()
|
||||
assert runner.count("'casualty-remove'") == 3
|
||||
compile(runner,'headless_workbench.py','exec')
|
||||
print('PASS: actual safe-exit boundary policy, native action/door selection, stop/cache cleanup, separate old mechanism and opt-in runner contracts')
|
||||
@@ -0,0 +1,43 @@
|
||||
"""Exercise the production structural-ticket check after post-commit death.
|
||||
No synthetic fixture is claimed as network/owner-RPC proof.
|
||||
"""
|
||||
from policy_harness import ROOT, body, run
|
||||
path = ROOT/'addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingService.c'
|
||||
physical = body(path, 'protected bool PhysicalTicket(')
|
||||
physical = (physical.replace('protected ', '').replace('ERR_CasualtyLoadTicket ticket', 'Ticket* ticket')
|
||||
.replace('ticket.', 'ticket->').replace('m_Mode.', 'm_Mode->')
|
||||
.replace('ERR_Phase.EXTRACTION', '4').replace('ERR_Actors.Alive', 'Alive')
|
||||
.replace('ERR_CasualtyLoadingRules.TransportBench', 'TransportBench').replace('null', 'nullptr'))
|
||||
policy = '\n'.join(body(path, signature) for signature in ['static bool ObserveTicket(', 'static bool Within('])
|
||||
run('''#include <cassert>
|
||||
struct Entity { bool alive=true; };
|
||||
bool Alive(Entity* entity){return entity && entity->alive;}
|
||||
bool TransportBench(Entity* seat,Entity* aircraft){return seat && aircraft;}
|
||||
struct Ticket { Entity* Aircraft; Entity* Casualty; Entity* Seat; };
|
||||
struct Mode {
|
||||
Entity* aircraft; bool active=true; int phase=4;
|
||||
bool Active(){return active;} int Phase(){return phase;}
|
||||
Entity* ExtractionVehicle(){return aircraft;}
|
||||
};
|
||||
''' + policy + '''
|
||||
struct Service { Mode* m_Mode;
|
||||
''' + physical + '''
|
||||
};
|
||||
int main(){
|
||||
Entity aircraft, body, bench; Mode mode{&aircraft}; Service service{&mode};
|
||||
Ticket ticket{&aircraft,&body,&bench};
|
||||
assert(service.PhysicalTicket(&ticket));
|
||||
body.alive=false; // server death after authorization; owner commit can still be in flight
|
||||
assert(service.PhysicalTicket(&ticket));
|
||||
assert(ObserveTicket(true,service.PhysicalTicket(&ticket),false));
|
||||
assert(!ObserveTicket(false,service.PhysicalTicket(&ticket),false));
|
||||
assert(Within(1000,8999,8000)); // keep observation reservation through its budget
|
||||
assert(!Within(1000,9000,8000)); // eventually release, without another owner command
|
||||
ticket.Casualty=nullptr; assert(!service.PhysicalTicket(&ticket));
|
||||
ticket.Casualty=&body; ticket.Seat=nullptr; assert(!service.PhysicalTicket(&ticket));
|
||||
ticket.Seat=&bench; aircraft.alive=false; assert(!service.PhysicalTicket(&ticket));
|
||||
aircraft.alive=true; mode.active=false; assert(!service.PhysicalTicket(&ticket));
|
||||
mode.active=true; mode.phase=5; assert(!service.PhysicalTicket(&ticket));
|
||||
}
|
||||
''')
|
||||
print('PASS: actual committed-ticket structure preserves reservation after death, rejects missing bodies/seats and ends at observation expiry')
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Execute the actual opt-in Load body; no native engine or GUI is launched."""
|
||||
from policy_harness import ROOT, body, run
|
||||
source=ROOT/'addons/core/Scripts/GameLib/ERR/ERR_DiagnosticNoSplash.c'
|
||||
load=body(source,'override void Load()').replace('override void','void').replace('System.','System::').replace('super.Load()', 'Base::Load()')
|
||||
run('''#include <cassert>
|
||||
#include <string>
|
||||
using string=std::string;
|
||||
struct System {
|
||||
static bool noSplash, explicitScenario;
|
||||
static string project;
|
||||
static bool IsCLIParam(const char*) { return noSplash; }
|
||||
static bool GetCLIParam(const char*, string& value) {value=project;return explicitScenario;}
|
||||
};
|
||||
bool System::noSplash=false; bool System::explicitScenario=false; string System::project;
|
||||
int diagnostics=0;
|
||||
void Print(const char*) {diagnostics++;}
|
||||
struct Base {
|
||||
int nativeLoads=0, s_iSplashShown=0;
|
||||
void Load(){nativeLoads++;s_iSplashShown++;}
|
||||
};
|
||||
struct Subject:Base {
|
||||
bool m_ERRDiagnosticSplashSkipped=false;
|
||||
''' + load + '''
|
||||
};
|
||||
int main(){
|
||||
for(bool flag:{false,true}) for(bool explicitRequest:{false,true})
|
||||
for(const string& project:{string("core"),string("chat"),string(""),string("other")}) {
|
||||
System::noSplash=flag;System::explicitScenario=explicitRequest;System::project=project;
|
||||
diagnostics=0;Subject instance;
|
||||
instance.Load();instance.Load();instance.Load();
|
||||
bool selected=flag && explicitRequest && (project=="core" || project=="chat");
|
||||
assert(instance.nativeLoads==(selected ? 0 : 3));
|
||||
assert(instance.s_iSplashShown==(selected ? 1 : 3));
|
||||
assert(diagnostics==(selected ? 1 : 0));
|
||||
assert(instance.m_ERRDiagnosticSplashSkipped==selected);
|
||||
}
|
||||
// Recreated splash instance on addon reload remains independently bounded.
|
||||
System::noSplash=true;System::explicitScenario=true;System::project="core";
|
||||
Subject first,reloaded;diagnostics=0;
|
||||
first.Load();reloaded.Load();reloaded.Load();
|
||||
assert(first.s_iSplashShown==1 && reloaded.s_iSplashShown==1 && diagnostics==2);
|
||||
}
|
||||
''')
|
||||
print('PASS: actual noSplash Load body requires both explicit diagnostic and noSplash, preserves vanilla otherwise, and handles repeated Load/reload')
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Execute actual environment parsing/formatting and settings policy bodies.
|
||||
Calendar validity expectations come from Python's independent datetime calendar.
|
||||
"""
|
||||
from datetime import date
|
||||
import re
|
||||
from policy_harness import ROOT, body, run
|
||||
settings = ROOT/'addons/core/Scripts/Game/ERR/ERR_Settings.c'
|
||||
service = ROOT/'addons/core/Scripts/Game/ERR/ERR_EnvironmentService.c'
|
||||
admin = ROOT/'addons/core/Scripts/Game/ERR/ERR_AdminService.c'
|
||||
|
||||
def translate(source):
|
||||
source = (source.replace('string ', 'std::string ').replace('out int ', 'int& ')
|
||||
.replace('text.Length()', 'int(text.size())').replace('.IsEmpty()', '.empty()')
|
||||
.replace('.Substring(', '.substr(')
|
||||
.replace('"0123456789".IndexOf(', 'std::string("0123456789").find(')
|
||||
.replace(' < 0) return false;', ' == std::string::npos) return false;')
|
||||
.replace('text.ToInt()', 'std::stoi(text)')
|
||||
.replace('ERR_CommandArguments.Integer', 'Integer').replace('ERR_Settings.Valid', 'Valid'))
|
||||
return re.sub(r'(\w+)\.ToString\(\)', r'std::to_string(\1)', source)
|
||||
|
||||
functions='\n'.join(translate(body(path, sig)) for path, sig in (
|
||||
(settings,'static bool Valid('), (settings,'static int AddedDefault('),
|
||||
(admin,'static bool Integer('), (service,'static string Pad2('),
|
||||
(service,'static string Date('), (service,'static bool ParseDate('),
|
||||
(service,'static string ProfileName('), (service,'static string WeatherStateName('),
|
||||
(service,'static bool ParseProfile('),
|
||||
))
|
||||
cases=[]
|
||||
# Every calendar boundary in range, including leap/century rules and malformed days.
|
||||
for year in range(1900,2051):
|
||||
for month in range(1,13):
|
||||
for day in (0,1,28,29,30,31,32):
|
||||
code=(year-1900)*10000+month*100+day
|
||||
text=f'{year:04}-{month:02}-{day:02}'
|
||||
try: date(year,month,day); valid=True
|
||||
except ValueError: valid=False
|
||||
cases.append(f'{{{code},"{text}",{str(valid).lower()}}}')
|
||||
run('#include <cassert>\n#include <string>\n'+functions+'\nstruct Case {int code; const char* text; bool valid;};\nstatic Case cases[]={' + ','.join(cases) + '};\nint main(){int parsed=-1;' + r'''
|
||||
for (auto c:cases) {assert(Valid("startDateCode",c.code)==c.valid); assert(ParseDate(c.text,parsed)==c.valid); if(c.valid)assert(parsed==c.code && Date(parsed)==c.text);}
|
||||
assert(AddedDefault("startDateCode")==0 && AddedDefault("weatherProfile")==0);
|
||||
assert(Valid("startDateCode",0) && Date(0)=="authored" && ParseDate("authored",parsed) && parsed==0);
|
||||
for(std::string text:{"1900-00-00","1900-00-01","2050-13-01","1899-12-31","2051-01-01","1989-6-01","1989-06-1","1989-06-01junk"," 1989-06-01","1989-06-01 ","198a-06-01","", "1989/06/01"}) assert(!ParseDate(text,parsed));
|
||||
assert(!Valid("startDateCode",-1) && !Valid("startDateCode",1501232));
|
||||
for(int p=0;p<=3;p++){assert(Valid("weatherProfile",p)); assert(ParseProfile(ProfileName(p),parsed)&&parsed==p);}
|
||||
assert(ParseProfile("rainy",parsed)&&parsed==3);
|
||||
assert(!ParseProfile("random",parsed)&&!ParseProfile("storm",parsed));
|
||||
assert(!Valid("weatherProfile",-1)&&!Valid("weatherProfile",4));
|
||||
assert(WeatherStateName(0)=="" && WeatherStateName(1)=="Clear" && WeatherStateName(2)=="Overcast" && WeatherStateName(3)=="Rainy");
|
||||
}
|
||||
''')
|
||||
print('PASS: native settings policy matches calendar boundaries 1900-2050, date/profile parsing is strict, authored defaults preserve old worlds')
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Negative runner checks stop before any native process/audio initialization."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
class EnvironmentRestartRunnerTests(unittest.TestCase):
|
||||
def invoke(self, profile, *extra):
|
||||
return subprocess.run([sys.executable, str(ROOT/'tools/headless_workbench.py'),
|
||||
'--compat', str(profile/'missing-prefix'), '--profile', str(profile),
|
||||
'--smoke', 'environment-restart', '--project', 'core', '--rounds', '2',
|
||||
*extra], text=True, capture_output=True)
|
||||
|
||||
def test_requires_two_core_rounds(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
for extra in [('--rounds','1'), ('--rounds','3'), ('--project','chat')]:
|
||||
with self.subTest(extra=extra):
|
||||
result = self.invoke(Path(directory), *extra)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('requires --project core --rounds 2', result.stderr)
|
||||
|
||||
def test_every_existing_fixture_file_is_refused_and_preserved(self):
|
||||
for name in ['erri-environment-restart.json', 'erri-environment-restart.json.bak',
|
||||
'erri-environment-restart.json.tmp', 'erri-environment-restart-round.json']:
|
||||
with self.subTest(name=name), tempfile.TemporaryDirectory() as directory:
|
||||
profile = Path(directory)
|
||||
path = profile/name
|
||||
path.write_text('prior evidence')
|
||||
result = self.invoke(profile)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('fixture paths must not preexist', result.stderr)
|
||||
self.assertEqual(path.read_text(), 'prior evidence')
|
||||
|
||||
def test_broken_fixture_symlink_is_refused(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
profile = Path(directory)
|
||||
(profile/'erri-environment-restart.json').symlink_to(profile/'missing-target')
|
||||
result = self.invoke(profile)
|
||||
self.assertIn('fixture paths must not preexist', result.stderr)
|
||||
self.assertTrue((profile/'erri-environment-restart.json').is_symlink())
|
||||
|
||||
def test_empty_fixture_reaches_existing_prefix_guard_without_side_effects(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
profile = Path(directory)
|
||||
result = self.invoke(profile)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('must be a disposable cloned prefix', result.stderr)
|
||||
self.assertEqual(list(profile.iterdir()), [])
|
||||
|
||||
def test_existing_environment_outcome_does_not_acquire_restart_path_policy(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
profile = Path(directory)
|
||||
(profile/'erri-environment-restart.json').write_text('prior evidence')
|
||||
result = self.invoke(profile, '--smoke', 'environment', '--rounds', '1')
|
||||
self.assertIn('must be a disposable cloned prefix', result.stderr)
|
||||
self.assertNotIn('fixture paths must not preexist', result.stderr)
|
||||
|
||||
if __name__ == '__main__': unittest.main()
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Execute production time policies; do not claim native simulation coverage."""
|
||||
from policy_harness import ROOT, body, run
|
||||
source = ROOT/'addons/core/Scripts/Game/ERR/ERR_ExtractionPlacement.c'
|
||||
methods = '\n'.join(body(source, signature) for signature in (
|
||||
'static bool CanWake(', 'static bool Fresh(', 'static bool QualificationAllowed(', 'static bool Stable('))
|
||||
run('#include <cassert>\n' + methods + '''
|
||||
int main() {
|
||||
assert(CanWake(true,0,0,30000));
|
||||
assert(CanWake(true,0,29999,30000));
|
||||
assert(!CanWake(false,0,1,30000));
|
||||
assert(!CanWake(true,1,1,30000)); // activation cannot be repeated
|
||||
assert(!CanWake(true,2,1,30000));
|
||||
assert(!CanWake(true,0,30000,30000)); // no wake after expired preparation
|
||||
assert(!CanWake(true,0,30001,30000));
|
||||
assert(QualificationAllowed(false,29999,30000));
|
||||
assert(!QualificationAllowed(false,30000,30000)); // exactly deadline is not first qualification
|
||||
assert(!QualificationAllowed(false,30001,30000));
|
||||
assert(QualificationAllowed(true,30000,30000)); // continuous monitoring may outlive settling
|
||||
assert(QualificationAllowed(true,90000,30000));
|
||||
assert(!Fresh(-1,0));
|
||||
assert(!Fresh(1000,999));
|
||||
assert(Fresh(1000,2500));
|
||||
assert(!Fresh(1000,2501)); // delayed callbacks restart the stability window
|
||||
assert(!Stable(-1,1000,2000));
|
||||
assert(!Stable(1000,999,2000));
|
||||
assert(!Stable(1000,1750,1999));
|
||||
assert(Stable(1000,1750,2000));
|
||||
assert(!Stable(1000,1000,2501));
|
||||
assert(!Stable(1000,2000,1999));
|
||||
}
|
||||
''')
|
||||
text = source.read_text()
|
||||
poll = body(source, 'ERR_PrepareResult Poll(')
|
||||
assert text.count('physics.SetActive(ActiveState.ACTIVE)') == 1
|
||||
assert poll.index('!QualificationAllowed(') < poll.index('m_Result = ERR_PrepareResult.READY;')
|
||||
assert poll.index('if (m_Stopped') < poll.index('physics.SetActive(')
|
||||
assert poll.index('!Replication.IsServer()') < poll.index('physics.SetActive(')
|
||||
assert poll.index('m_Mode.ExtractionVehicle() != m_Aircraft') < poll.index('physics.SetActive(')
|
||||
assert poll.index('m_WakeCount = 1;') < poll.index('physics.SetActive(')
|
||||
assert 'fuel.HasFuel() && ERR_CasualtyLoadingRules.AircraftReady(m_Aircraft)' in poll
|
||||
for forbidden in ('SetVelocity(', 'SetAngularVelocity(', 'SetTransform(', 'SetWorldTransform(', 'Teleport(', 'ApplyImpulse', 'EnableDamageHandling('):
|
||||
assert forbidden not in text, forbidden
|
||||
site = (ROOT/'addons/core/Scripts/Game/ERR/ERR_SiteService.c').read_text()
|
||||
assert 'if (m_ExtractionPlacement) m_ExtractionPlacement.Stop();' in site
|
||||
assert 'Fail(m_ExtractionPlacement.FailureReason())' in site
|
||||
assert 'pendingGuards > 0 || aircraftPlacement == ERR_PrepareResult.PENDING' in site
|
||||
assert 'SetSpawnImmediately(false)' in site and 'PurgeSpawnRequestsForGroup' in site
|
||||
probe = (ROOT/'addons/core/Scripts/Game/ERR/ERR_NativeCasualtyGroundProbe.c').read_text()
|
||||
assert 'placement.Ready() && placement.WakeCount() == 1' in probe
|
||||
assert 'SetActive(' not in probe
|
||||
print('PASS: actual one-shot deadline/stability policies and preparation ownership, fuel, cleanup integration contracts')
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Offline resource compatibility checks; these are not native loading tests."""
|
||||
import hashlib
|
||||
import re
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
CORE = ROOT / "addons/core"
|
||||
VANILLA_NORMALIZED_SHA256 = {
|
||||
"US": "d8d16abbecadc6987ca7eed3b114446d0af5753535c891fcefc20d5b2d7db583",
|
||||
"USSR": "2d3d3af74d0da33017ae3132c613fc9c19c0d20cd850a9830bb208db59e4bf25",
|
||||
}
|
||||
OLD_VEHICLES = {
|
||||
"US": "{D83A5EFEEF6CBF61}Configs/EntityCatalog/US/Vehicles_EntityCatalog_US.conf",
|
||||
"USSR": "{09C2ED5F617A0390}Configs/EntityCatalog/USSR/Vehicles_EntityCatalog_USSR.conf",
|
||||
}
|
||||
|
||||
|
||||
class FactionCatalogScopeTests(unittest.TestCase):
|
||||
def test_only_vehicle_catalog_changes_from_installed_faction_profiles(self):
|
||||
for side in ("US", "USSR"):
|
||||
with self.subTest(side=side):
|
||||
text = (CORE / f"Configs/ERR/Factions/{side}.conf").read_text()
|
||||
restored, count = re.subn(
|
||||
r"\{[A-F0-9]{16}\}Configs/ERR/Factions/MissionVehicles\.conf",
|
||||
lambda _: OLD_VEHICLES[side], text,
|
||||
)
|
||||
self.assertEqual(count, 1)
|
||||
# Pin every other vanilla field, including all inventory/group
|
||||
# references, radio/identity/callsign/rank settings and base.
|
||||
self.assertEqual(hashlib.sha256(restored.encode()).hexdigest(),
|
||||
VANILLA_NORMALIZED_SHA256[side])
|
||||
|
||||
def test_vehicle_catalog_has_only_the_two_authored_assets(self):
|
||||
text = (CORE / "Configs/ERR/Factions/MissionVehicles.conf").read_text()
|
||||
self.assertRegex(text, r"^SCR_EntityCatalogMultiList\s*\{")
|
||||
prefabs = re.findall(r'm_sEntityPrefab\s+"([^\"]+)"', text)
|
||||
self.assertCountEqual(prefabs, [
|
||||
"{259EE7B78C51B624}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469.et",
|
||||
"{DF5CCB7C0FF049F4}Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et",
|
||||
])
|
||||
self.assertEqual(len(prefabs), 2)
|
||||
|
||||
def test_world_uses_own_manager_without_full_vanilla_faction_inheritance(self):
|
||||
manager = (CORE / "Prefabs/ERR/FactionManager.et").read_text()
|
||||
self.assertIn("Prefabs/MP/Managers/Factions/FactionManager_Base.et", manager)
|
||||
self.assertNotIn("FactionManager_USxUSSR.et", manager)
|
||||
for side in ("US", "USSR"):
|
||||
self.assertIn(f"Configs/ERR/Factions/{side}.conf", manager)
|
||||
self.assertNotIn(f"Configs/Factions/{side}.conf", manager)
|
||||
layer = (CORE / "Worlds/ERR_Everon_Layers/default.layer").read_text()
|
||||
self.assertEqual(layer.count("Prefabs/ERR/FactionManager.et"), 1)
|
||||
self.assertNotIn("FactionManager_USxUSSR.et", layer)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,65 @@
|
||||
"""Run actual ownership cancellation/Stop bodies with shared-array fixtures."""
|
||||
from policy_harness import ROOT, body, run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/ERR_SiteService.c'
|
||||
s='\n'.join(body(p, sig) for sig in ('void CancelInitialGuardRequests(', 'void Stop('))
|
||||
for old,new in [
|
||||
('GetGame().','GetGame()->'),
|
||||
('ChimeraAIWorld world', 'ChimeraAIWorld* world'),
|
||||
('ChimeraAIWorld.Cast', 'ChimeraAIWorld::Cast'),
|
||||
('foreach (ERR_SiteRuntime site : m_Sites)', 'for (ERR_SiteRuntime* site : m_Sites)'),
|
||||
('site.Group', 'site->Group'),
|
||||
('site.InitialGuardCount', 'site->InitialGuardCount'),
|
||||
('site->Group.', 'site->Group->'),
|
||||
('new array<ResourceName>()', 'std::make_shared<Array<std::string>>()'),
|
||||
('world.Purge', 'world->Purge'),
|
||||
('m_ExtractionPlacement.', 'm_ExtractionPlacement->'),
|
||||
('ERR_PrepareResult.', 'ERR_PrepareResult::'),
|
||||
]: s=s.replace(old,new)
|
||||
run('''#include <cassert>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
template<typename T> using Array=std::vector<T>;
|
||||
enum class ERR_PrepareResult {FAILED=-1,PENDING,READY};
|
||||
struct Group {bool immediate=true; std::shared_ptr<Array<std::string>> m_aUnitPrefabSlots; int physical=0; void SetSpawnImmediately(bool b){immediate=b;} };
|
||||
struct ERR_SiteRuntime {struct Group* Group; int InitialGuardCount;};
|
||||
struct ChimeraAIWorld {
|
||||
Array<Group*> purged;
|
||||
static ChimeraAIWorld* Cast(ChimeraAIWorld* p){return p;}
|
||||
void PurgeSpawnRequestsForGroup(Group* group){
|
||||
assert(!group->immediate && group->m_aUnitPrefabSlots->empty());
|
||||
purged.push_back(group);
|
||||
}
|
||||
};
|
||||
struct Game {ChimeraAIWorld* world; ChimeraAIWorld* GetAIWorld(){return world;}};
|
||||
Game* game;
|
||||
Game* GetGame(){return game;}
|
||||
struct Placement { int stops=0; void Stop(){stops++;} };
|
||||
struct Owner {
|
||||
std::shared_ptr<Placement> m_ExtractionPlacement;
|
||||
Array<ERR_SiteRuntime*> m_Sites;
|
||||
bool m_Stopped=false;
|
||||
ERR_PrepareResult m_State=ERR_PrepareResult::PENDING;
|
||||
int routeStops=0;
|
||||
void StopPatrols(){routeStops++;}
|
||||
''' + s + '''
|
||||
};
|
||||
int main(){
|
||||
ChimeraAIWorld world; Game instance{&world}; game=&instance;
|
||||
auto authored=std::make_shared<Array<std::string>>(6,"soldier");
|
||||
Group empty{true,authored,0}, partial{true,authored,2}, control{true,authored,6}, unrequested{true,authored,0};
|
||||
ERR_SiteRuntime a{&empty,6},b{&partial,6},c{&control,6},ignored{&unrequested,0},missing{nullptr,6};
|
||||
Owner owner; owner.m_ExtractionPlacement=std::make_shared<Placement>(); owner.m_Sites={&a,&b,&ignored,&missing};
|
||||
owner.Stop(); owner.Stop();
|
||||
assert(owner.m_Stopped && owner.routeStops==1 && world.purged.size()==2 && owner.m_ExtractionPlacement->stops==1);
|
||||
assert(empty.m_aUnitPrefabSlots->empty() && partial.m_aUnitPrefabSlots->empty());
|
||||
assert(authored->size()==6 && control.m_aUnitPrefabSlots==authored && unrequested.m_aUnitPrefabSlots==authored);
|
||||
assert(empty.physical==0 && partial.physical==2 && control.physical==6);
|
||||
assert(control.immediate && unrequested.immediate);
|
||||
Owner ready; ready.m_State=ERR_PrepareResult::READY; ready.m_Sites={&c}; ready.Stop();
|
||||
assert(world.purged.size()==2 && control.immediate && control.m_aUnitPrefabSlots->size()==6);
|
||||
game=nullptr; Owner deletedGame; deletedGame.m_Sites={&c}; deletedGame.Stop();
|
||||
assert(world.purged.size()==2);
|
||||
}
|
||||
''')
|
||||
print('PASS: pending-only cancellation retires copied instance intent, preserves physical/control actors, and stops once')
|
||||
@@ -0,0 +1,309 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exercise process refusal, command scope and evidence gates without native launch."""
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]/'tools'))
|
||||
import headless_game as runner
|
||||
|
||||
|
||||
class GameRunnerTests(unittest.TestCase):
|
||||
def process(self, root, command, comm='wine', pid='123'):
|
||||
proc = root/pid
|
||||
proc.mkdir()
|
||||
(proc/'cmdline').write_bytes(command)
|
||||
(proc/'comm').write_text(comm)
|
||||
(proc/'environ').write_bytes(b'')
|
||||
return proc
|
||||
|
||||
def test_refuses_existing_game_variants_without_mutation(self):
|
||||
for command in (b'Z:/Games/ArmaReforgerSteamDiag.exe\0-world\0x',
|
||||
b'C:\\Games\\ArmaReforgerSteam.exe\0',
|
||||
b'proton\0run\0/Z/ArmaReforgerSteamDiag.exe\0'):
|
||||
with self.subTest(command=command), tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
proc = self.process(root, command)
|
||||
with self.assertRaisesRegex(ValueError, 'game process already exists'):
|
||||
runner.assert_no_game(root)
|
||||
self.assertEqual((proc/'cmdline').read_bytes(), command)
|
||||
|
||||
def test_truncated_comm_still_refuses_empty_cmdline(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
self.process(root, b'', comm='ArmaReforgerStea')
|
||||
with self.assertRaisesRegex(ValueError, 'game process already exists'):
|
||||
runner.assert_no_game(root)
|
||||
|
||||
def test_unrelated_workbench_is_not_a_game_process(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
self.process(root, b'Z:/Workbench/ArmaReforgerWorkbenchSteamDiag.exe\0', comm='ArmaReforgerWor')
|
||||
runner.assert_no_game(root)
|
||||
|
||||
def test_unreadable_process_fails_closed(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
self.process(root, b'')
|
||||
with patch.object(Path, 'read_bytes', side_effect=PermissionError('blocked')):
|
||||
with self.assertRaisesRegex(ValueError, 'Cannot exclude an existing game'):
|
||||
runner.assert_no_game(root)
|
||||
|
||||
def test_same_prefix_process_refused_other_prefix_preserved(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
compat = root/'compat'
|
||||
proc = self.process(root, b'wineserver\0')
|
||||
(proc/'environ').write_bytes(('WINEPREFIX='+str(compat/'pfx')).encode()+b'\0')
|
||||
with self.assertRaisesRegex(ValueError, 'prefix already has a process'):
|
||||
runner.assert_prefix_idle(compat, root)
|
||||
runner.assert_prefix_idle(root/'different', root)
|
||||
|
||||
def test_nondumpable_native_service_does_not_require_environment(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
proc = self.process(root, b'/usr/lib/systemd/systemd\0--user\0', comm='systemd')
|
||||
original = Path.read_bytes
|
||||
def protected_read(path):
|
||||
if path.name == 'environ':
|
||||
self.fail('Unrelated native service environment must not be read')
|
||||
return original(path)
|
||||
with patch.object(Path, 'read_bytes', protected_read), patch.object(runner.os, 'readlink', side_effect=PermissionError('non-dumpable')):
|
||||
runner.assert_prefix_idle(root/'compat', root)
|
||||
self.assertTrue(proc.exists())
|
||||
|
||||
def test_runtime_candidates_require_environment_even_if_exe_is_protected(self):
|
||||
for command, comm in [(b'/usr/bin/wineserver\0', 'wineserver'),
|
||||
(b'/usr/bin/python3\0/Steam/Proton - Experimental/proton\0run\0', 'python3'),
|
||||
(b'/usr/bin/gamescope\0--backend\0headless\0', 'gamescope'),
|
||||
(b'C:\\windows\\system32\\services.exe\0', 'services.exe')]:
|
||||
with self.subTest(comm=comm), tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory); self.process(root, command, comm=comm)
|
||||
original = Path.read_bytes
|
||||
def protected_read(path):
|
||||
if path.name == 'environ':
|
||||
raise PermissionError('protected candidate')
|
||||
return original(path)
|
||||
with patch.object(Path, 'read_bytes', protected_read), patch.object(runner.os, 'readlink', side_effect=PermissionError('protected exe')):
|
||||
with self.assertRaisesRegex(ValueError, 'Cannot verify disposable prefix'):
|
||||
runner.assert_prefix_idle(root/'compat', root)
|
||||
|
||||
def test_unclassifiable_process_remains_fail_closed(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory); self.process(root, b'', comm='unknown')
|
||||
with patch.object(runner.os, 'readlink', side_effect=PermissionError('protected exe')):
|
||||
with self.assertRaisesRegex(ValueError, 'Cannot classify possible Wine'):
|
||||
runner.assert_prefix_idle(root/'compat', root)
|
||||
|
||||
def test_exact_systemd_pam_helper_is_native_without_environment(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory); self.process(root, b'(sd-pam)\0', comm='(sd-pam)')
|
||||
original = Path.read_bytes
|
||||
def protected_read(path):
|
||||
if path.name == 'environ':
|
||||
self.fail('Native PAM helper environment must not be read')
|
||||
return original(path)
|
||||
with patch.object(Path, 'read_bytes', protected_read), patch.object(runner.os, 'readlink', side_effect=PermissionError('non-dumpable')):
|
||||
runner.assert_prefix_idle(root/'compat', root)
|
||||
|
||||
def test_observed_native_apache_signature_is_narrowly_classified(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
proc = self.process(root, b'apache2\0-DFOREGROUND\0', comm='apache2')
|
||||
with patch.object(runner.os, 'readlink', side_effect=PermissionError('protected exe')):
|
||||
self.assertFalse(runner.prefix_process_candidate(proc))
|
||||
(proc/'cmdline').write_bytes(b'apache2\0-DFOREGROUND\0/Steam/proton\0')
|
||||
self.assertTrue(runner.prefix_process_candidate(proc))
|
||||
(proc/'cmdline').write_bytes(b'apache2\0')
|
||||
with self.assertRaisesRegex(ValueError, 'Cannot classify possible Wine'):
|
||||
runner.prefix_process_candidate(proc)
|
||||
|
||||
def test_bare_matching_names_are_not_generically_trusted(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
self.process(root, b'unknown\0', comm='unknown', pid='101')
|
||||
self.process(root, b'another\0', comm='another', pid='102')
|
||||
with patch.object(runner.os, 'readlink', side_effect=PermissionError('protected exe')):
|
||||
with self.assertRaises(ValueError) as raised:
|
||||
runner.assert_prefix_idle(root/'compat', root)
|
||||
self.assertIn('101', str(raised.exception))
|
||||
self.assertIn('102', str(raised.exception)) # report all blockers in one pass
|
||||
|
||||
def test_pam_name_does_not_hide_runtime_arguments(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
proc = self.process(root, b'(sd-pam)\0/Steam/proton\0', comm='(sd-pam)')
|
||||
with patch.object(runner.os, 'readlink', side_effect=PermissionError('protected exe')):
|
||||
self.assertTrue(runner.prefix_process_candidate(proc))
|
||||
|
||||
def test_readonly_inspection_does_not_enter_launcher_or_audio(self):
|
||||
with patch.object(sys, 'argv', ['headless_game.py', '--compat', '/unused-prefix', '--inspect-processes']):
|
||||
with patch.object(runner, 'assert_no_game'), patch.object(runner, 'assert_prefix_idle', side_effect=ValueError('ambiguous process')):
|
||||
with patch.object(runner, 'run') as launch, patch.object(runner.subprocess, 'Popen') as spawn, patch.object(runner.subprocess, 'run') as commands, patch('builtins.print'):
|
||||
self.assertEqual(runner.main(), 1)
|
||||
launch.assert_not_called(); spawn.assert_not_called(); commands.assert_not_called()
|
||||
|
||||
def test_existing_unmarked_profile_and_steam_prefix_refused(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
base = Path(directory)
|
||||
compat = base/'compat'; (compat/'pfx').mkdir(parents=True)
|
||||
(compat/'pfx/system.reg').touch()
|
||||
profile = base/'profile'; profile.mkdir(); (profile/'sentinel').write_text('keep')
|
||||
with self.assertRaisesRegex(ValueError, 'not marked'):
|
||||
runner.validate_paths(base/'source', base/'steam', compat, profile)
|
||||
self.assertEqual((profile/'sentinel').read_text(), 'keep')
|
||||
with self.assertRaisesRegex(ValueError, 'outside Steam'):
|
||||
runner.validate_paths(base/'source', base, compat, base/'fresh')
|
||||
|
||||
def test_profile_cannot_overlap_owned_prefix_or_source(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
base = Path(directory); compat = base/'compat'
|
||||
(compat/'pfx').mkdir(parents=True); (compat/'pfx/system.reg').touch()
|
||||
for profile in (base/'source/profile', compat/'profile', base):
|
||||
with self.assertRaisesRegex(ValueError, 'separate from source'):
|
||||
runner.validate_paths(base/'source', base/'steam', compat, profile)
|
||||
|
||||
def test_command_is_ordinary_game_with_explicit_successor_identity(self):
|
||||
for project, guid in [('core', '2528466DE220D91C'), ('chat', 'FA804F411BDDF2C6')]:
|
||||
command = runner.command_for(runner.ROOT, Path('/Steam'), Path('/compat'), Path('/profile'), project)
|
||||
self.assertEqual(command[command.index('--')+3], '/Steam/steamapps/common/Arma Reforger/ArmaReforgerSteam.exe')
|
||||
self.assertFalse(any('ArmaReforger_BE.exe' in arg or 'ArmaReforgerSteamDiag.exe' in arg for arg in command))
|
||||
self.assertEqual(command[command.index('-addons')+1], guid)
|
||||
self.assertNotIn('-world', command)
|
||||
self.assertEqual(command[command.index('-errOrdinaryScenario')+1], project)
|
||||
for flag in ['-noBackend', '-noSound', '-noSplash', '-noThrow', '-disableCrashReporter']:
|
||||
self.assertIn(flag, command)
|
||||
for unwanted in ['-server', '-gproj', '-errPlay', '-errSmoke', '-plugin', 'Workbench', '-silentCrashReport', '-noCrashDialog']:
|
||||
self.assertFalse(any(unwanted in arg for arg in command))
|
||||
|
||||
def test_ready_requires_game_module_without_workbench_and_normal_phase(self):
|
||||
module = 'SCRIPT : Module: Game; loaded 12x files; defines: "PLATFORM_WINDOWS,ENABLE_DIAG"; CRC32: a\n'
|
||||
phase = 'SCRIPT : Rereintroduced [mission]: phase 0 -> 1\nSCRIPT : ERR ORDINARY: header verified project=core systems=MissionSystems\n'
|
||||
self.assertTrue(runner.game_evidence(module+phase)['ready'])
|
||||
self.assertFalse(runner.game_evidence(phase)['ready'])
|
||||
self.assertFalse(runner.game_evidence(module)['ready'])
|
||||
self.assertFalse(runner.game_evidence(module.replace('ENABLE_DIAG', 'WORKBENCH')+phase)['ready'])
|
||||
for error in ['SCRIPT (E): failure', 'RESOURCES (E): missing', 'ERR SMOKE PASS: fixture leaked', 'Rereintroduced: scenario preparation failed']:
|
||||
self.assertTrue(runner.game_evidence(module+phase+error)['failed'])
|
||||
|
||||
def test_startup_requires_selected_header_and_actual_systems_marker(self):
|
||||
module = 'SCRIPT : Module: Game; defines: "PLATFORM_WINDOWS"; CRC32: a\n'
|
||||
phase = 'SCRIPT : Rereintroduced [mission]: phase 0 -> 1\n'
|
||||
core = 'SCRIPT : ERR ORDINARY: header verified project=core systems=MissionSystems\n'
|
||||
chat = 'SCRIPT : ERR ORDINARY: header verified project=chat systems=MissionSystems\n'
|
||||
self.assertFalse(runner.game_evidence(module+phase, 'core')['ready'])
|
||||
self.assertFalse(runner.game_evidence(module+phase+core, 'chat')['ready'])
|
||||
self.assertTrue(runner.game_evidence(module+phase+chat, 'chat')['ready'])
|
||||
self.assertFalse(runner.game_evidence(module+phase+core.replace('MissionSystems', 'unknown'), 'core')['ready'])
|
||||
error = "GUI (E): Unknown class 'SCR_WidgetExportRuleRoot'\n"
|
||||
result = runner.game_evidence(error+module+phase+core, 'core')
|
||||
self.assertTrue(result['scenario_header_verified'])
|
||||
self.assertTrue(result['failed'])
|
||||
self.assertFalse(result['ready'])
|
||||
|
||||
def test_no_engine_or_audio_spawn_during_import_and_process_refusal(self):
|
||||
with patch.object(runner, 'assert_no_game', side_effect=ValueError('existing game')):
|
||||
with patch.object(runner.subprocess, 'Popen') as spawn, patch.object(runner.subprocess, 'run') as commands:
|
||||
class Args:
|
||||
steam=Path('/steam'); compat=Path('/compat'); profile=Path('/profile')
|
||||
timeout=180; settle=15; failure_grace=0
|
||||
with self.assertRaisesRegex(ValueError, 'existing game'):
|
||||
runner.run(Args())
|
||||
spawn.assert_not_called(); commands.assert_not_called()
|
||||
|
||||
|
||||
class FailureGraceTests(unittest.TestCase):
|
||||
def test_exit_at_settle_boundary_is_not_success(self):
|
||||
monitor = runner.StartupMonitor(100, 180, 5)
|
||||
self.assertIsNone(monitor.observe({'ready': True}, 101))
|
||||
self.assertEqual(monitor.observe({'ready': True}, 106, True), 'process-exited')
|
||||
|
||||
def test_missing_readiness_resets_continuous_settle(self):
|
||||
monitor = runner.StartupMonitor(100, 180, 5)
|
||||
self.assertIsNone(monitor.observe({'ready': True}, 101))
|
||||
self.assertIsNone(monitor.observe({}, 104))
|
||||
self.assertIsNone(monitor.observe({'ready': True}, 106))
|
||||
self.assertEqual(monitor.observe({'ready': True}, 111), 'game-started')
|
||||
|
||||
def test_default_error_stops_immediately(self):
|
||||
monitor = runner.StartupMonitor(100, 180, 15)
|
||||
self.assertEqual(monitor.observe({'failed': True}, 101), 'game-failed')
|
||||
self.assertEqual(monitor.first_failure_at, 101)
|
||||
|
||||
def test_repeated_errors_do_not_renew_grace(self):
|
||||
monitor = runner.StartupMonitor(100, 180, 15, 30)
|
||||
self.assertIsNone(monitor.observe({'failed': True}, 105))
|
||||
self.assertIsNone(monitor.observe({'failed': True}, 134.99))
|
||||
self.assertEqual(monitor.first_failure_at, 105)
|
||||
self.assertEqual(monitor.observe({'failed': True}, 135), 'game-failed')
|
||||
|
||||
def test_readiness_cannot_turn_latched_error_into_success(self):
|
||||
monitor = runner.StartupMonitor(100, 180, 5, 30)
|
||||
self.assertIsNone(monitor.observe({'failed': True}, 101))
|
||||
for now in (102, 110, 130):
|
||||
self.assertIsNone(monitor.observe({'ready': True}, now))
|
||||
self.assertEqual(monitor.observe({'ready': True}, 131), 'game-failed')
|
||||
self.assertIsNone(monitor.ready_at)
|
||||
|
||||
def test_failure_after_readiness_aborts_settle_success(self):
|
||||
monitor = runner.StartupMonitor(100, 180, 5, 30)
|
||||
self.assertIsNone(monitor.observe({'ready': True}, 101))
|
||||
self.assertIsNone(monitor.observe({'failed': True}, 105))
|
||||
self.assertIsNone(monitor.observe({'ready': True}, 106))
|
||||
self.assertEqual(monitor.observe({}, 135), 'game-failed')
|
||||
|
||||
def test_absolute_timeout_cuts_short_grace(self):
|
||||
monitor = runner.StartupMonitor(100, 60, 5, 30)
|
||||
self.assertIsNone(monitor.observe({'failed': True}, 159))
|
||||
self.assertEqual(monitor.observe({'ready': True}, 160), 'game-failed')
|
||||
self.assertEqual(monitor.timeout_result(), 'game-failed')
|
||||
self.assertEqual(monitor.deadline, 160)
|
||||
|
||||
def test_process_exit_during_grace_returns_failed_immediately(self):
|
||||
monitor = runner.StartupMonitor(100, 180, 15, 30)
|
||||
monitor.observe({'failed': True}, 101)
|
||||
self.assertEqual(monitor.observe({}, 102, process_exited=True), 'game-failed')
|
||||
|
||||
def test_final_raw_analysis_cannot_erase_observed_failure(self):
|
||||
monitor = runner.StartupMonitor(100, 180, 15, 30)
|
||||
monitor.observe({'failed': True}, 101)
|
||||
final = monitor.annotate({'ready': True, 'failed': False, 'engine_errors': [], 'game_module_observed': True})
|
||||
self.assertTrue(final['failure_observed_during_polling'])
|
||||
self.assertTrue(final['failed'])
|
||||
self.assertFalse(final['ready'])
|
||||
self.assertEqual(final['engine_errors'], []) # no invented final-log errors
|
||||
self.assertTrue(final['game_module_observed'])
|
||||
|
||||
def test_grace_does_not_delay_normal_success_or_exit(self):
|
||||
monitor = runner.StartupMonitor(100, 180, 15, 30)
|
||||
self.assertIsNone(monitor.observe({'ready': True}, 101))
|
||||
self.assertEqual(monitor.observe({'ready': True}, 116), 'game-started')
|
||||
monitor = runner.StartupMonitor(100, 180, 15, 30)
|
||||
self.assertEqual(monitor.observe({}, 101, process_exited=True), 'process-exited')
|
||||
self.assertEqual(monitor.observe({}, 280), 'timeout')
|
||||
self.assertEqual(monitor.timeout_result(), 'timeout')
|
||||
|
||||
def test_grace_bounds_rejected_before_any_native_mutation(self):
|
||||
for grace in (-1, 31):
|
||||
with self.subTest(grace=grace):
|
||||
with self.assertRaisesRegex(ValueError, 'failure-grace 0..30'):
|
||||
runner.StartupMonitor(100, 180, 15, grace)
|
||||
class Args:
|
||||
steam=Path('/steam'); compat=Path('/compat'); profile=Path('/profile')
|
||||
timeout=180; settle=15; failure_grace=grace
|
||||
with patch.object(runner, 'assert_no_game') as processes, patch.object(runner.subprocess, 'Popen') as spawn:
|
||||
with self.assertRaisesRegex(ValueError, 'failure-grace 0..30'):
|
||||
runner.run(Args())
|
||||
processes.assert_not_called(); spawn.assert_not_called()
|
||||
|
||||
def test_cli_default_and_explicit_grace(self):
|
||||
for extra, expected in (([], 0), (['--failure-grace', '30'], 30)):
|
||||
with self.subTest(extra=extra), patch.object(sys, 'argv', ['headless_game.py', '--compat', '/unused', *extra]):
|
||||
with patch.object(runner, 'run', return_value=1) as run:
|
||||
self.assertEqual(runner.main(), 1)
|
||||
self.assertEqual(run.call_args.args[0].failure_grace, expected)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,29 @@
|
||||
"""Execute initial readiness policy; native population timing needs engine tests."""
|
||||
from policy_harness import ROOT, body, run
|
||||
p = ROOT/'addons/core/Scripts/Game/ERR/ERR_InitialGuardRules.c'
|
||||
s = '\n'.join(body(p, signature) for signature in ('static float TimeoutMilliseconds(', 'static ERR_PrepareResult State(')).replace('ERR_PrepareResult.', 'ERR_PrepareResult::')
|
||||
run('''#include <cassert>
|
||||
#include <initializer_list>
|
||||
enum class ERR_PrepareResult { FAILED=-1,PENDING,READY };
|
||||
''' + s + '''
|
||||
int main() {
|
||||
using R=ERR_PrepareResult;
|
||||
assert(TimeoutMilliseconds()==90000);
|
||||
for(int requested: {1,6,12}) {
|
||||
for(int physical=0;physical<=requested;physical++) {
|
||||
for(int alive=0;alive<=physical;alive++) {
|
||||
assert(State(requested,physical,alive,false,false)==R::PENDING);
|
||||
assert(State(requested,physical,alive,true,false)==((alive==requested)?R::READY:R::PENDING));
|
||||
assert(State(requested,physical,alive,true,true)==R::FAILED);
|
||||
assert(State(requested,physical,alive,false,true)==R::FAILED);
|
||||
}
|
||||
}
|
||||
assert(State(requested,requested+1,requested,true,false)==R::FAILED);
|
||||
}
|
||||
assert(State(0,0,0,true,false)==R::FAILED);
|
||||
assert(State(6,2,3,false,false)==R::FAILED);
|
||||
assert(State(6,-1,0,false,false)==R::FAILED);
|
||||
assert(State(6,1,-1,false,false)==R::FAILED);
|
||||
}
|
||||
''')
|
||||
print('PASS: requested guard population requires complete physical conscious membership before deadline')
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Synthetic identity regression using actual Enforce inventory/count method bodies.
|
||||
|
||||
This checks source policy only; it does not simulate native inventory transfers.
|
||||
"""
|
||||
import unittest
|
||||
from policy_harness import ROOT, body, run
|
||||
|
||||
|
||||
def translated(source):
|
||||
for old, new in [
|
||||
('InventoryStorageManagerComponent inventory', 'InventoryStorageManagerComponent* inventory'),
|
||||
('notnull array<IEntity> items', 'Array<Entity*>& items'),
|
||||
('out array<IEntity> items', 'Array<Entity*>& items'),
|
||||
('array<IEntity>', 'Array<Entity*>'),
|
||||
('IEntity item', 'Entity* item'),
|
||||
('foreach (', 'for ('),
|
||||
('inventory.GetItems', 'inventory->GetItems'),
|
||||
('item.GetPrefabData()', 'item->GetPrefabData()'),
|
||||
('.GetPrefabName()', '->GetPrefabName()'),
|
||||
('ERR_Inventory.', 'ERR_Inventory::'),
|
||||
('int count;', 'int count = 0;'),
|
||||
]:
|
||||
source = source.replace(old, new)
|
||||
return source
|
||||
|
||||
|
||||
class InventoryEnumerationPolicy(unittest.TestCase):
|
||||
def test_duplicate_references_distinct_rounds_and_cross_inventory_union(self):
|
||||
helper = ROOT / 'addons/core/Scripts/Game/ERR/ERR_Inventory.c'
|
||||
mortar = ROOT / 'addons/ai/Scripts/Game/ERR/ERR_MortarBattery.c'
|
||||
methods = '\n'.join(translated(body(helper, signature)) for signature in (
|
||||
'static int AppendUnique(', 'static int Items('))
|
||||
count = translated(body(mortar, 'int InventoryShells('))
|
||||
run(r'''
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
template <typename T> struct Array : std::vector<T> {
|
||||
using std::vector<T>::vector;
|
||||
bool Contains(T v) const { return std::find(this->begin(),this->end(),v)!=this->end(); }
|
||||
void Insert(T v) { this->push_back(v); }
|
||||
void Clear() { this->clear(); }
|
||||
int Count() const { return this->size(); }
|
||||
};
|
||||
struct Prefab { std::string name; std::string GetPrefabName() { return name; } };
|
||||
struct Entity { Prefab* prefab; Prefab* GetPrefabData() { return prefab; } };
|
||||
struct InventoryStorageManagerComponent {
|
||||
Array<Entity*> raw;
|
||||
void GetItems(Array<Entity*>& out) { out = raw; }
|
||||
};
|
||||
const std::string SHELL = "mortar-shell";
|
||||
struct ERR_Inventory {
|
||||
''' + methods + '\n};\n' + count + r'''
|
||||
int main() {
|
||||
Prefab shell{SHELL}, watch{"watch"};
|
||||
Entity roundA{&shell}, roundB{&shell}, watchA{&watch}, missingPrefab{nullptr};
|
||||
InventoryStorageManagerComponent one{{&roundA,&roundA,nullptr,&roundB,&watchA,&watchA,&missingPrefab}};
|
||||
Array<Entity*> unique{&watchA};
|
||||
assert(ERR_Inventory::Items(&one,unique)==4);
|
||||
assert(unique[0]==&roundA && unique[1]==&roundB && unique[2]==&watchA && unique[3]==&missingPrefab);
|
||||
assert(InventoryShells(&one)==2); // Different shells share one prefab, not one identity.
|
||||
assert(one.raw.size()==7 && one.raw[0]==one.raw[1]); // Enumeration remains untouched.
|
||||
InventoryStorageManagerComponent two{{&roundB,&roundB,&roundA}};
|
||||
assert(ERR_Inventory::AppendUnique(&two,unique)==4); // Cross-inventory repeated identities.
|
||||
assert(ERR_Inventory::AppendUnique(nullptr,unique)==4);
|
||||
assert(ERR_Inventory::Items(nullptr,unique)==0 && unique.empty());
|
||||
assert(InventoryShells(nullptr)==0);
|
||||
InventoryStorageManagerComponent repeated{{&roundA,&roundA,&roundA}};
|
||||
assert(InventoryShells(&repeated)==1);
|
||||
InventoryStorageManagerComponent empty{{nullptr,&missingPrefab,&watchA}};
|
||||
assert(InventoryShells(&empty)==0);
|
||||
}
|
||||
''')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Execute the actual prison search body with a recording clearance predicate.
|
||||
This verifies bounded search order/termination, not terrain or body clearance.
|
||||
"""
|
||||
from policy_harness import ROOT,body,run
|
||||
source=ROOT/'addons/core/Scripts/Game/ERR/ERR_SpawnPlacement.c'
|
||||
search=(body(source,'bool Prison(').replace('map<int, vector> reservations','map<int, vector>* reservations')
|
||||
.replace('out vector position','vector& position').replace('ERR_Actors.', 'ERR_Actors::').replace('Math.', 'Math::'))
|
||||
run('''#include <cassert>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
struct vector {float x,y,z;};
|
||||
vector operator+(vector a,vector b){return {a.x+b.x,a.y+b.y,a.z+b.z};}
|
||||
vector Vector(float x,float y,float z){return {x,y,z};}
|
||||
template<class K,class V> struct map {};
|
||||
namespace ERR_Actors {vector Ground(vector value){return value;}}
|
||||
namespace Math {int AbsInt(int value){return std::abs(value);}}
|
||||
struct Search {
|
||||
std::vector<vector> visited;
|
||||
int accept=-1;
|
||||
bool Clear(vector candidate,map<int,vector>*){visited.push_back(candidate);return int(visited.size())-1==accept;}
|
||||
''' + search + '''
|
||||
};
|
||||
int main(){
|
||||
const vector center={6090,100,7160};
|
||||
vector result={-1,-1,-1};
|
||||
Search blocked;
|
||||
assert(!blocked.Prison(center,nullptr,result));
|
||||
assert(blocked.visited.size()==16);
|
||||
assert(result.x==-1&&result.y==-1&&result.z==-1); // no outside fallback
|
||||
std::set<std::pair<float,float>> unique;
|
||||
for(int i=0;i<16;++i){
|
||||
vector candidate=blocked.visited[i];
|
||||
float x=candidate.x-center.x,z=candidate.z-center.z;
|
||||
assert(std::abs(x)<=4.5f&&std::abs(z)<=4.5f);
|
||||
assert(unique.insert({x,z}).second);
|
||||
assert(std::abs(x)==1.5f||std::abs(x)==4.5f);
|
||||
assert(std::abs(z)==1.5f||std::abs(z)==4.5f);
|
||||
int outer=(std::abs(x)==4.5f)+(std::abs(z)==4.5f);
|
||||
assert(outer==(i<4?0:(i<12?1:2)));
|
||||
if(i<12)assert(std::hypot(x,z)<5); // inner and edge spawns meet prior 5m check
|
||||
for(int j=0;j<i;++j){
|
||||
vector previous=blocked.visited[j];
|
||||
assert(std::hypot(candidate.x-previous.x,candidate.z-previous.z)>=3);
|
||||
}
|
||||
}
|
||||
// Select any first available position; never bypass the native clearance predicate.
|
||||
for(int accepted=0;accepted<16;++accepted){
|
||||
Search search;search.accept=accepted;
|
||||
assert(search.Prison(center,nullptr,result));
|
||||
assert(search.visited.size()==unsigned(accepted+1));
|
||||
vector expected=blocked.visited[accepted];
|
||||
assert(result.x==expected.x&&result.y==expected.y&&result.z==expected.z);
|
||||
}
|
||||
|
||||
}
|
||||
''')
|
||||
print('PASS: prison search packs 16 unique 3m-spaced inside-wall centers, prioritizes inner/edge positions and never bypasses clearance')
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Execute the production presentation-state policy; no network claim."""
|
||||
from policy_harness import ROOT, body, run
|
||||
source=ROOT/'addons/core/Scripts/Game/ERR/ERR_RadioFeedback.c'
|
||||
function=body(source,'static ERR_RadioFeedback Next(').replace('ERR_RadioFeedback.', 'ERR_RadioFeedback::')
|
||||
run('''#include <cassert>
|
||||
enum class ERR_RadioFeedback {INACTIVE, WAITING, TRANSMITTING, INTERRUPTED};
|
||||
'''+function+'''
|
||||
int main() {
|
||||
using S=ERR_RadioFeedback;
|
||||
const S emptyExpected[]={S::WAITING,S::WAITING,S::INTERRUPTED,S::INTERRUPTED};
|
||||
for (int previous=0;previous<4;++previous) {
|
||||
S state=static_cast<S>(previous);
|
||||
// Terminal/new-round state always clears; stale occupancy cannot resurrect it.
|
||||
assert(Next(state,false,false)==S::INACTIVE);
|
||||
assert(Next(state,false,true)==S::INACTIVE);
|
||||
assert(Next(state,true,true)==S::TRANSMITTING);
|
||||
assert(Next(state,true,false)==emptyExpected[previous]);
|
||||
}
|
||||
S state=Next(S::INACTIVE,true,false);
|
||||
assert(state==S::WAITING);
|
||||
for (int sample=0;sample<100;++sample) state=Next(state,true,false);
|
||||
assert(state==S::WAITING); // initial wait is not an interruption
|
||||
state=Next(state,true,true);
|
||||
assert(state==S::TRANSMITTING);
|
||||
state=Next(state,true,false);
|
||||
assert(state==S::INTERRUPTED);
|
||||
for (int sample=0;sample<100;++sample) state=Next(state,true,false);
|
||||
assert(state==S::INTERRUPTED); // do not alternate guide messages while empty
|
||||
state=Next(state,true,true);
|
||||
assert(state==S::TRANSMITTING);
|
||||
state=Next(state,false,true);
|
||||
assert(state==S::INACTIVE);
|
||||
assert(Next(state,true,false)==S::WAITING); // fresh round has no stale interruption
|
||||
}
|
||||
''')
|
||||
print('PASS: actual radio feedback policy, initial wait, interruption persistence, resumption and terminal/new-round reset')
|
||||
@@ -0,0 +1,17 @@
|
||||
from policy_harness import ROOT, body, run
|
||||
p = ROOT / 'addons/core/Scripts/Game/ERR/ERR_RecoveryService.c'
|
||||
rules = body(p, 'static int SurvivorKind(') + '\n' + body(p, 'static bool NeedsRescuer(')
|
||||
run('''#include <cassert>
|
||||
''' + rules + '''
|
||||
int main() {
|
||||
for (int flags=0;flags<16;flags++) {
|
||||
bool alive=flags&1, connected=flags&2, same=flags&4, conscious=flags&8;
|
||||
int expected=!alive?0:(!connected||!same)?1:conscious?2:3;
|
||||
assert(SurvivorKind(alive,connected,same,conscious)==expected);
|
||||
}
|
||||
for (int conscious=0;conscious<=12;conscious++)
|
||||
for (int incapacitated=0;incapacitated<=12;incapacitated++)
|
||||
assert(NeedsRescuer(conscious,incapacitated)==(conscious==0&&incapacitated>0));
|
||||
}
|
||||
''')
|
||||
print('PASS: recovery state excludes dead bodies, preserves reservations, requires controlled rescuers and distinguishes empty/all-dead teams')
|
||||
@@ -197,7 +197,7 @@ class ReleaseAuditTests(unittest.TestCase):
|
||||
|
||||
def test_each_native_failure_category_blocks(self):
|
||||
for failure in ('ERR SMOKE FAIL: assertion', 'Virtual Machine Exception',
|
||||
'SCRIPT (E): exception', 'ANIMATION (E): unknown resource', 'Could not find expected endscreen',
|
||||
'SCRIPT (E): exception', 'ANIMATION (E): unknown resource', 'ENGINE (F): Crashed', 'Could not find expected endscreen',
|
||||
"entity won't take damage and wont replicate"):
|
||||
self.write(self.log, 'ERR SMOKE PASS: test\n' + failure + '\nERR SMOKE COMPLETE: test\n')
|
||||
self.refresh_native()
|
||||
|
||||
@@ -4,7 +4,7 @@ from policy_harness import ROOT,body,run
|
||||
spec=importlib.util.spec_from_file_location('import_settings',ROOT/'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)
|
||||
assert result==dict(reportIntervalSeconds=20,supportSquadLimit=2,responseRadiusMetres=1500,aiIntelChancePercent=0,intelRevealChancePercent=100,radioDefenceSeconds=180,startTimeMinutes=840,timeMultiplier=1,startDateCode=0,weatherProfile=0)
|
||||
assert deferred==['ai:convoyCount']
|
||||
assert m.merge()[0]=={k:v[0] for k,v in m.SCHEMA.items()}
|
||||
for key,(_,lo,hi) in m.SCHEMA.items():
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Execute production discovery bounds; native LOS/identity remain runtime checks."""
|
||||
from policy_harness import ROOT, body, run
|
||||
|
||||
source = ROOT / 'addons/core/Scripts/Game/ERR/ERR_SiteDiscoveryRules.c'
|
||||
methods = '\n'.join(body(source, sig) for sig in (
|
||||
'static float RadioRadiusLimit(', 'static bool ValidRadius(', 'static bool InRange('
|
||||
)).replace('ERR_LocationKind.', 'ERR_LocationKind::')
|
||||
run('''#include <cassert>
|
||||
enum class ERR_LocationKind { PRISON, RADIO, EXTRACTION, OUTPOST, DEPOT };
|
||||
''' + methods.replace('int kind,', 'ERR_LocationKind kind,') + '''
|
||||
int main() {
|
||||
using K=ERR_LocationKind;
|
||||
assert(ValidRadius(K::RADIO,0) && ValidRadius(K::RADIO,8));
|
||||
assert(!ValidRadius(K::RADIO,8.01f) && !ValidRadius(K::RADIO,-1));
|
||||
assert(ValidRadius(K::DEPOT,60) && ValidRadius(K::OUTPOST,60));
|
||||
assert(!ValidRadius(K::DEPOT,60.01f));
|
||||
assert(ValidRadius(K::PRISON,0) && ValidRadius(K::EXTRACTION,0));
|
||||
assert(!ValidRadius(K::PRISON,1) && !ValidRadius(K::EXTRACTION,1));
|
||||
assert(!ValidRadius(static_cast<K>(42),0));
|
||||
assert(InRange(8,8,8) && InRange(8,0,0));
|
||||
assert(!InRange(0,0,0) && !InRange(8,8.01f,0));
|
||||
assert(!InRange(8,0,8.01f) && !InRange(8,-1,0) && !InRange(8,0,-1));
|
||||
}
|
||||
''')
|
||||
print('PASS: authored radio/optional-site bounds and physical horizontal/vertical boundaries')
|
||||
Reference in New Issue
Block a user