Harden base progression and cleanup; validate finite physical resupply
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""Run the real archiver against synthetic sealed startup logs, without engines."""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0,str(ROOT/'tools'))
|
||||
from native_provenance import snapshot,persist,bindings,seal_run
|
||||
from test_vanilla_game_control import BASE
|
||||
|
||||
ORDINARY='''00 SCRIPT : Module: Game; defines: "PLATFORM_WINDOWS"
|
||||
00 SCRIPT : ERR ORDINARY: header verified project=core systems=MissionSystems
|
||||
00 SCRIPT : Rereintroduced [mission]: phase 0 -> 1
|
||||
00 SCRIPT : ERR ORDINARY: local participant verified project=core phase=prison
|
||||
'''
|
||||
|
||||
class ArchiveControl(unittest.TestCase):
|
||||
def archive(self,project,result,control=None,cleanup=True,content=None):
|
||||
tmp=tempfile.TemporaryDirectory(prefix='erri-archive-control-test-');self.addCleanup(tmp.cleanup)
|
||||
root=Path(tmp.name);(root/'addons/core').mkdir(parents=True)
|
||||
(root/'addons/core/addon.gproj').write_text('Synthetic fixture source, not a native project')
|
||||
tools=root/'tools';tools.mkdir()
|
||||
for name in ('archive_native.py','native_report.py','source_manifest.py','native_provenance.py','headless_game.py'):
|
||||
shutil.copyfile(ROOT/'tools'/name,tools/name)
|
||||
session=root/'session';session.mkdir()
|
||||
(session/'console.log').write_text(content if content is not None else BASE if project=='vanilla-control' else ORDINARY)
|
||||
captured=snapshot(root,['synthetic-archive-test-no-engine']);persist(session,captured)
|
||||
data={**bindings(captured),'started_unix':json.loads(captured[1])['captured_unix'],**seal_run(root,session,captured),'project':project,'result':result,'cleanup_confirmed':cleanup}
|
||||
if control is not None:data['control_only']=control
|
||||
(session/'runner.json').write_text(json.dumps(data))
|
||||
# Deliberately false saved status; actual archiver must recompute sealed log.
|
||||
(session/'game-startup-report.json').write_text('{"ready":true,"failed":false}')
|
||||
command=[sys.executable,str(tools/'archive_native.py'),str(session),'control']
|
||||
done=subprocess.run(command,text=True,capture_output=True)
|
||||
dest=root/'docs/evidence/control'
|
||||
self.assertTrue(dest.exists(),done.stderr)
|
||||
self.assertEqual((dest/'console.log.txt').read_bytes(),(session/'console.log').read_bytes())
|
||||
return done,json.loads((dest/'report.json').read_text()),command,dest
|
||||
def test_clean_control_gets_own_result_and_no_successor_qualification(self):
|
||||
result,report,command,dest=self.archive('vanilla-control','vanilla-control-started',True)
|
||||
self.assertEqual(result.returncode,0,result.stderr)
|
||||
self.assertTrue(report['control_metadata_consistent']);self.assertTrue(report['control_only'])
|
||||
self.assertFalse(report['normal_mission_ready']);self.assertFalse(report['scenario_header_verified'])
|
||||
self.assertIn('no successor',report['qualification'])
|
||||
before=(dest/'report.json').read_bytes()
|
||||
duplicate=subprocess.run(command,capture_output=True,text=True)
|
||||
self.assertNotEqual(duplicate.returncode,0);self.assertEqual((dest/'report.json').read_bytes(),before)
|
||||
def test_control_rejects_ordinary_result_or_inconsistent_flag(self):
|
||||
for result,flag in [('game-started',True),('vanilla-control-started',None),('vanilla-control-started',False),('vanilla-control-started','true')]:
|
||||
with self.subTest(result=result,flag=flag):
|
||||
done,report,_,_=self.archive('vanilla-control',result,flag)
|
||||
self.assertEqual(done.returncode,1);self.assertTrue(report['ready'])
|
||||
def test_control_retains_native_failure_despite_saved_success(self):
|
||||
done,report,_,_=self.archive('vanilla-control','vanilla-control-started',True,content=BASE+"00 GUI (E): Unknown class 'SCR_WidgetExportRuleRoot'")
|
||||
self.assertEqual(done.returncode,1);self.assertFalse(report['ready']);self.assertTrue(report['engine_errors'])
|
||||
def test_cleanup_is_still_required(self):
|
||||
done,_,_,_=self.archive('vanilla-control','vanilla-control-started',True,cleanup=False)
|
||||
self.assertEqual(done.returncode,1)
|
||||
def test_ordinary_current_and_legacy_status_remain_strict(self):
|
||||
for flag in (None,False):
|
||||
done,report,_,_=self.archive('core','game-started',flag)
|
||||
self.assertEqual(done.returncode,0,done.stderr);self.assertTrue(report['normal_mission_ready'])
|
||||
for result,flag in [('vanilla-control-started',False),('game-started',True)]:
|
||||
done,_,_,_=self.archive('core',result,flag);self.assertEqual(done.returncode,1)
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
@@ -41,3 +41,94 @@ int main(){
|
||||
}
|
||||
''')
|
||||
print('PASS: actual committed-ticket structure preserves reservation after death, rejects missing bodies/seats and ends at observation expiry')
|
||||
|
||||
# Exercise production queue ownership and the complete pending loop. The fake
|
||||
# queue invokes the actual callback while no ordinary service Tick is called.
|
||||
begin = body(path, 'bool Begin(')
|
||||
assert begin.index('ticket.Seat.IsReservedBy(casualty)') < begin.index('ScheduleMaintenance();') < begin.index('controller.ERR_PrepareCasualtyLoad(')
|
||||
methods = '\n'.join(body(path, signature) for signature in [
|
||||
'protected void ScheduleMaintenance(', 'protected void CancelMaintenance(',
|
||||
'protected void MaintainPending(', 'protected void Resolve(', 'void Stop('])
|
||||
methods = (methods.replace('protected ', '').replace('ERR_CasualtyLoadTicket ticket', 'Ticket* ticket')
|
||||
.replace('array<ref ERR_CasualtyLoadTicket> snapshot = {}', 'Array<Ticket*> snapshot')
|
||||
.replace('foreach (ERR_CasualtyLoadTicket item : m_Pending)', 'for (Ticket* item : m_Pending)')
|
||||
.replace('foreach (Ticket* ticket : snapshot)', 'for (Ticket* ticket : snapshot)')
|
||||
.replace('m_Mode.', 'm_Mode->').replace('ticket.', 'ticket->')
|
||||
.replace('ticket->Seat.', 'ticket->Seat->').replace('m_OnResolved.', 'm_OnResolved->')
|
||||
.replace('GetGame().GetCallqueue().', 'GetGame()->GetCallqueue()->')
|
||||
.replace('CallLater(MaintainPending, 250, true)', 'CallLater([this](){MaintainPending();}, 250, true)')
|
||||
.replace('Remove(MaintainPending)', 'Remove(0)').replace('null', 'nullptr'))
|
||||
for namespace in ['Replication', 'System', 'ERR_MissionRules', 'ERR_CasualtyLoadingRules', 'CompartmentAccessComponent', 'ERR_Actors']:
|
||||
methods = methods.replace(namespace + '.', namespace + '::')
|
||||
run('''#include <cassert>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
template<class T> struct Array {
|
||||
std::vector<T> data;
|
||||
bool IsEmpty(){return data.empty();} int Count(){return data.size();}
|
||||
void Insert(T item){data.push_back(item);} void Clear(){data.clear();}
|
||||
int Find(T item){auto it=std::find(data.begin(),data.end(),item);return it==data.end()?-1:it-data.begin();}
|
||||
void Remove(int index){data.erase(data.begin()+index);} T operator[](int index){return data[index];}
|
||||
auto begin(){return data.begin();} auto end(){return data.end();}
|
||||
};
|
||||
struct Entity {bool alive=true; Entity* vehicle=nullptr;};
|
||||
struct Seat {Entity* reserved=nullptr;Entity* occupant=nullptr;
|
||||
bool IsReservedBy(Entity* entity){return reserved==entity;}
|
||||
void SetReserved(Entity* entity){reserved=entity;} Entity* GetOccupant(){return occupant;}
|
||||
};
|
||||
struct Ticket {Entity* Casualty;Entity* Aircraft;struct Seat* Seat;bool Committed=true;int CommittedAt=1000;int Started=1000;Entity* Caller=nullptr;};
|
||||
struct Mode {int phase=4;int Phase(){return phase;}};
|
||||
namespace Replication {bool server=true;bool IsServer(){return server;}}
|
||||
namespace System {int now=1000;int GetTickCount(){return now;}}
|
||||
namespace ERR_MissionRules {bool Terminal(int phase){return phase>=5;}}
|
||||
namespace ERR_Actors {bool Alive(Entity* actor){return actor&&actor->alive;}}
|
||||
namespace CompartmentAccessComponent {Entity* GetVehicleIn(Entity* actor){return actor?actor->vehicle:nullptr;}}
|
||||
namespace ERR_CasualtyLoadingRules {
|
||||
''' + policy + '''
|
||||
bool Nearby(Mode*,Entity*,Entity*){return true;}
|
||||
}
|
||||
struct Queue {
|
||||
bool active=false;int starts=0,removes=0;std::function<void()> callback;
|
||||
void CallLater(std::function<void()> fn,int interval,bool repeat){assert(interval==250&&repeat);assert(!active);active=true;starts++;callback=fn;}
|
||||
void Remove(int){assert(active);active=false;removes++;}
|
||||
void Fire(){if(active){auto retained=callback;retained();}}
|
||||
};
|
||||
struct Game {Queue queue;Queue* GetCallqueue(){return &queue;}} game;
|
||||
Game* GetGame(){return &game;}
|
||||
struct Invoker {int calls=0,successes=0;std::function<void()> observer;
|
||||
void Invoke(Entity*,bool success){calls++;successes+=success;if(observer){auto retained=observer;retained();}}
|
||||
};
|
||||
struct Service {
|
||||
Mode* m_Mode;bool m_Stopped=false,m_MaintenanceQueued=false;
|
||||
Array<Ticket*> m_Pending;Array<int> m_Settling;Invoker* m_OnResolved;
|
||||
bool PhysicalTicket(Ticket* ticket){return ticket->Casualty&&ticket->Aircraft&&ticket->Seat;}
|
||||
bool ValidTicket(Ticket*){return false;} // disconnected participants cannot authorize again
|
||||
''' + methods + '''
|
||||
void Add(Ticket* ticket){m_Pending.Insert(ticket);ticket->Seat->SetReserved(ticket->Casualty);ScheduleMaintenance();}
|
||||
};
|
||||
int main(){
|
||||
Mode mode;Entity casualty,aircraft;Seat seat;Invoker events;Service service{&mode,false,false,{}, {},&events};
|
||||
Ticket first{&casualty,&aircraft,&seat};
|
||||
service.Add(&first);service.ScheduleMaintenance();assert(game.queue.starts==1);
|
||||
System::now=8999;game.queue.Fire();assert(service.m_Pending.Count()==1&&seat.IsReservedBy(&casualty)&&events.calls==0);
|
||||
System::now=9000;game.queue.Fire();assert(service.m_Pending.IsEmpty()&&!seat.reserved&&events.calls==1&&!events.successes&&!game.queue.active);
|
||||
game.queue.Fire();service.MaintainPending();assert(events.calls==1&&game.queue.removes==1);
|
||||
// Before commit the disconnected life is invalid immediately, without waiting
|
||||
// for a roster callback or granting a command from maintenance.
|
||||
Ticket uncommitted{&casualty,&aircraft,&seat};uncommitted.Committed=false;
|
||||
service.Add(&uncommitted);game.queue.Fire();assert(events.calls==2&&!game.queue.active&&!seat.reserved);
|
||||
// Resolve cancels its callback before notifying; a reentrant authorized request
|
||||
// may arm a fresh callback without that callback being removed afterwards.
|
||||
Ticket next{&casualty,&aircraft,&seat};next.CommittedAt=10000;
|
||||
Ticket ending{&casualty,&aircraft,&seat};ending.CommittedAt=1000;
|
||||
events.observer=[&](){events.observer=nullptr;service.Add(&next);};
|
||||
service.Add(&ending);System::now=10000;game.queue.Fire();
|
||||
assert(service.m_Pending.Count()==1&&game.queue.active&&seat.IsReservedBy(&casualty));
|
||||
service.Stop();assert(service.m_Stopped&&!game.queue.active&&!seat.reserved&&service.m_Pending.IsEmpty());
|
||||
int resolved=events.calls,removed=game.queue.removes;
|
||||
service.Stop();game.queue.Fire();assert(events.calls==resolved&&game.queue.removes==removed);
|
||||
assert(events.successes==0); // no acknowledgement or expired command manufactures loading
|
||||
}
|
||||
''')
|
||||
print('PASS: actual pending callback expires an empty-server reservation once, preserves its full lease, and cancels on completion/Stop')
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Execute the production coordinate policy; native probe checks marker storage."""
|
||||
from policy_harness import ROOT, body, run
|
||||
source=ROOT/'addons/core/Scripts/Game/ERR/ERR_DiscoveryMap.c'
|
||||
policy=body(source,'static int Coordinate(').replace('Math.Round','std::round').replace('Math.Floor','std::floor')
|
||||
run('''#include <cassert>
|
||||
#include <cmath>
|
||||
'''+policy+'''
|
||||
int main(){
|
||||
assert(Coordinate(6123.25f,1)==6250);
|
||||
assert(Coordinate(7345.75f,1)==7250);
|
||||
assert(Coordinate(6123.25f,2)==6123);
|
||||
assert(Coordinate(7345.75f,2)==7346);
|
||||
// Cell boundaries, negative world coordinates and deterministic repetition.
|
||||
assert(Coordinate(0,1)==250 && Coordinate(499.99f,1)==250);
|
||||
assert(Coordinate(500,1)==750 && Coordinate(-0.01f,1)==-250);
|
||||
assert(Coordinate(-500,1)==-250 && Coordinate(-500.01f,1)==-750);
|
||||
for(int i=-20000;i<=20000;i++){
|
||||
const float actual=i+0.25f;
|
||||
const int rough=Coordinate(actual,1);
|
||||
assert(std::abs(actual-rough)<=250);
|
||||
assert(rough==Coordinate(actual,1));
|
||||
assert(Coordinate(rough-249.9f,1)==rough);
|
||||
assert(Coordinate(rough+249.9f,1)==rough);
|
||||
assert(std::abs(actual-Coordinate(actual,2))<=0.5f);
|
||||
}
|
||||
}
|
||||
''')
|
||||
print('PASS: actual discovery coordinates disclose stable 500m cell center until identified, then exact point, including negative/boundary coordinates')
|
||||
@@ -0,0 +1,106 @@
|
||||
"""Execute actual scope policy; structural cleanup checks are not native proof."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from policy_harness import body, run
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SOURCE = ROOT / 'addons/core/Scripts/Game/ERR/ERR_ItemStationTeardown.c'
|
||||
PROBE = ROOT / 'addons/core/Scripts/Game/ERR/ERR_NativeItemStationTeardownProbe.c'
|
||||
|
||||
class ItemStationTeardown(unittest.TestCase):
|
||||
def test_actual_recovery_gate_all_inputs(self):
|
||||
method = body(SOURCE, 'static bool Recover(')
|
||||
run('#include <cassert>\n' + method + '''
|
||||
int main() {
|
||||
for (int mask=0; mask<32; ++mask) {
|
||||
bool initialized=mask&1, scoped=mask&2, offWorld=mask&4;
|
||||
bool catalogPresent=mask&8, editMode=mask&16;
|
||||
assert(Recover(initialized,scoped,offWorld,catalogPresent,editMode)==(mask==7));
|
||||
}
|
||||
}
|
||||
''')
|
||||
|
||||
def test_real_validation_precedes_owned_subscription_capture(self):
|
||||
source = SOURCE.read_text()
|
||||
self.assertNotIn('override bool InitValidSetup', source)
|
||||
init = body(SOURCE, 'protected override void DelayedInit(')
|
||||
self.assertIn('ERR_GameMode.Get() && ERR_KnownOffWorldKit(owner)', init)
|
||||
self.assertIn('super.DelayedInit(owner)', init)
|
||||
subscription = body(SOURCE, 'protected override void AddRemoveOnDamageStateChanged(')
|
||||
self.assertLess(subscription.index('super.AddRemove'), subscription.index('m_ERRTeardownInitialized = true'))
|
||||
self.assertIn('if (!subscribe || !m_ERRTeardownInitializing) return;', subscription)
|
||||
scope = body(SOURCE, 'protected bool ERR_KnownOffWorldKit(')
|
||||
self.assertIn('owner.GetWorld() == GetGame().GetWorld()', scope)
|
||||
self.assertIn('{AE578EEA4244D41F}', scope)
|
||||
self.assertIn('{21EF98BFC1EB3793}', scope)
|
||||
self.assertEqual(scope.count('prefab =='), 2)
|
||||
self.assertIn('modded class SCR_ResupplyMedicalGadgetSupportStationComponent', source)
|
||||
|
||||
def test_cleanup_has_real_detach_and_unchanged_normal_path(self):
|
||||
deletion = body(SOURCE, 'override void OnDelete(')
|
||||
self.assertIn('super.OnDelete(owner);', deletion)
|
||||
self.assertIn('m_EntityCatalogManager != null', deletion)
|
||||
self.assertLess(deletion.index('if (m_ERRTeardownDone) return;'), deletion.index('Remove(DelayedInit)'))
|
||||
self.assertIn('AddRemoveOnDamageStateChanged(owner, false);', deletion)
|
||||
self.assertIn('TEMP_GetOnInteractorReplicated().Remove(TEMP_OnInteractorReplicated)', deletion)
|
||||
self.assertIn('m_ERRTeardownResource = null;', deletion)
|
||||
self.assertNotIn('InitValidSetup(', deletion)
|
||||
self.assertNotIn('LogLevel', deletion)
|
||||
|
||||
def test_fault_injection_is_owned_bounded_and_measures_callbacks(self):
|
||||
source = PROBE.read_text()
|
||||
self.assertTrue(source.startswith('#ifdef WORKBENCH'))
|
||||
self.assertIn('BaseWorld.CreateWorld("Preview", "ERRStationTeardownProbe")', source)
|
||||
self.assertIn('m_World != GetGame().GetWorld()', source)
|
||||
self.assertIn('m_EntityCatalogManager = null;', source)
|
||||
self.assertNotIn('s_Instance =', source)
|
||||
self.assertNotIn('InitValidSetup(', source)
|
||||
self.assertIn('CallLater(DelayedInit, 100, false, GetOwner())', source)
|
||||
self.assertEqual(source.count('OnDelete(GetOwner());'), 2)
|
||||
self.assertIn('now + 500', source)
|
||||
self.assertIn('now + 10000', source)
|
||||
self.assertIn('m_Station.ERR_TeardownProbeInitCalls() == 0', source)
|
||||
self.assertGreaterEqual(source.count('m_Station.ERR_TeardownProbeResourceCalls() == 1'), 3)
|
||||
self.assertIn('m_Index < 2', source)
|
||||
self.assertIn('if (m_Kit) delete m_Kit;', source)
|
||||
|
||||
def test_integrated_smoke_steps_finish_only_after_probe_success(self):
|
||||
source = (ROOT / 'addons/core/Scripts/Game/ERR/ERR_NativeBaseSmoke.c').read_text()
|
||||
self.assertIn('protected ref ERR_NativeItemStationTeardownProbe m_ERRItemStationTeardownProbe;', source)
|
||||
self.assertIn('if (m_ERRItemStationTeardownProbe) m_ERRItemStationTeardownProbe.Stop();', source)
|
||||
self.assertIn('if (m_ERRBaseStep == 2000)', source)
|
||||
self.assertIn('int result = m_ERRItemStationTeardownProbe.Tick(now);', source)
|
||||
self.assertIn('if (result == 1) { m_ERRBaseStep = 2002; Transition(ERR_Phase.DEFEAT); }', source)
|
||||
self.assertIn('current == ERR_Phase.DEFEAT && m_ERRBaseStep == 2002', source)
|
||||
for preserved in ('m_ERRPrisonBreachProbe.Tick(now)', 'm_ERRPoweredFlightProbe.Tick(now)', 'ERR_NativeDiscoveryMapProbe.Run()'):
|
||||
self.assertIn(preserved, source)
|
||||
|
||||
def invoke_runner(self, profile, *extra):
|
||||
return subprocess.run([sys.executable, str(ROOT/'tools/headless_workbench.py'),
|
||||
'--compat', str(profile/'missing-prefix'), '--profile', str(profile),
|
||||
'--smoke', 'station-teardown', '--project', 'core', '--rounds', '1',
|
||||
*extra], text=True, capture_output=True)
|
||||
|
||||
def test_runner_rejects_wrong_project_or_round_count_before_launch(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
profile = Path(directory)
|
||||
for extra in [('--project', 'chat'), ('--rounds', '2')]:
|
||||
with self.subTest(extra=extra):
|
||||
result = self.invoke_runner(profile, *extra)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('station-teardown requires --project core --rounds 1', result.stderr)
|
||||
self.assertEqual(list(profile.iterdir()), [])
|
||||
|
||||
def test_runner_valid_case_stops_at_missing_prefix_without_side_effects(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
profile = Path(directory)
|
||||
result = self.invoke_runner(profile)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('must be a disposable cloned prefix', result.stderr)
|
||||
self.assertEqual(list(profile.iterdir()), [])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,50 @@
|
||||
"""Run actual fixture placement geometry; protect native clearance/stock boundary."""
|
||||
import re
|
||||
from policy_harness import ROOT, body, run
|
||||
p = ROOT / 'addons/ai/Scripts/Game/ERR/ERR_NativeMortarCourierProbe.c'
|
||||
text = p.read_text()
|
||||
geometry = body(p, 'static vector StorageOffset(')
|
||||
count = int(re.search(r'STORAGE_CANDIDATES = (\d+)', text).group(1))
|
||||
required = int(re.search(r'STORAGE_COUNT = (\d+)', text).group(1))
|
||||
assert count == 64 and required == 5
|
||||
assert '%' not in geometry, 'Use native Math.Mod; C++ remainder hides Enforce errors'
|
||||
run('''#include <cassert>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
struct vector {float x,y,z;static const vector Zero;};const vector vector::Zero{0,0,0};
|
||||
vector Vector(float x,float y,float z){return {x,y,z};}
|
||||
namespace Math {int Mod(int a,int b){return a%b;}}
|
||||
constexpr int STORAGE_CANDIDATES=64;
|
||||
''' + geometry.replace('Math.', 'Math::').replace('vector.Zero', 'vector::Zero') + '''
|
||||
int main(){
|
||||
std::vector<vector> seen;
|
||||
for(int i=0;i<STORAGE_CANDIDATES;i++){
|
||||
auto p=StorageOffset(i);
|
||||
assert(p.y==0&&p.x>=20&&p.x<=41&&p.z>=15&&p.z<=36);
|
||||
assert(std::hypot(p.x,p.z)<55);
|
||||
for(auto old:seen)assert(std::hypot(p.x-old.x,p.z-old.z)>=3);
|
||||
seen.push_back(p);
|
||||
}
|
||||
for(int x=20;x<=41;x+=3)for(int z=15;z<=36;z+=3){
|
||||
int matches=0;for(auto p:seen)if(p.x==x&&p.z==z)matches++;assert(matches==1);
|
||||
}
|
||||
for(int invalid:{-1,64,999}){auto p=StorageOffset(invalid);assert(p.x==0&&p.y==0&&p.z==0);}
|
||||
}
|
||||
''')
|
||||
tick = body(p, 'int Tick(')
|
||||
start = tick.index('int tested, lowGround, obstructed;')
|
||||
end = tick.index('m_Stage = 1;', start)
|
||||
placement = tick[start:end]
|
||||
assert 'i < STORAGE_CANDIDATES && m_Stores.Count() < STORAGE_COUNT' in placement
|
||||
assert 'if (position[1] <= 1) { lowGround++; continue; }' in placement
|
||||
assert 'if (!SCR_WorldTools.TraceCilinderUtil(position + "0 1 0", 1.3, 2, TraceFlags.ENTS | TraceFlags.WORLD)) { obstructed++; continue; }' in placement
|
||||
assert placement.index('TraceCilinderUtil') < placement.index('ERR_Actors.Spawn(BOX, position)')
|
||||
assert placement.index('m_Mode.Own(box)') < placement.index('box.FindComponent(')
|
||||
assert placement.count('ERR_Actors.Spawn(') == 1
|
||||
assert 'm_Boxes.Count() == STORAGE_COUNT && m_Stores.Count() == STORAGE_COUNT' in placement
|
||||
assert ') return -1;' in placement[placement.index('m_Boxes.Count() == STORAGE_COUNT'):]
|
||||
for forbidden in ('TryMoveItem', 'TrySpawn', 'Delete', 'SetOrigin', 'Teleport', 'HandleDamage', 'SHELL'):
|
||||
assert forbidden not in placement, forbidden
|
||||
assert tick.index('m_Stage = 1;', start) < tick.index('source.TryMoveItemToStorage(')
|
||||
assert 'Boxes with borrowed originals remain owned by normal mission cleanup.' in text
|
||||
print('PASS: 64 bounded distinct storage candidates; native gates, five-store barrier and finite-stock ownership retained; native placement pending')
|
||||
@@ -0,0 +1,130 @@
|
||||
"""Execute production finite-courier policies and native-operation observation.
|
||||
API doubles do not establish engine inventory, navigation, radio or MP behavior.
|
||||
"""
|
||||
from policy_harness import ROOT, body, run
|
||||
import re
|
||||
r=ROOT/'addons/ai/Scripts/Game/ERR'
|
||||
p=r/'ERR_MortarLogistics.c'; t=r/'ERR_MortarCourierTransfer.c'
|
||||
# C++ would accept an Enforce ownership annotation as an identifier.
|
||||
# Reject that native compile regression before executing the source body.
|
||||
assert not re.search(r'\b(?:bool|int|float|string)\s+(?:owned|event|autoptr|typename|proto|modded|notnull)\b', p.read_text())
|
||||
policies='\n'.join(body(p,s) for s in ('static bool Fresh(', 'static bool Need(', 'static bool NewRequest(', 'static bool Near(', 'static bool ActiveTrip('))
|
||||
run('''#include <cassert>
|
||||
'''+policies+'''
|
||||
int main(){
|
||||
assert(Fresh(100,100)&&Fresh(100,180100));
|
||||
assert(Fresh(0,0)&&Fresh(0,180000)&&!Fresh(0,180001));
|
||||
assert(!Fresh(-1,-1)&&!Fresh(-1,0)&&!Fresh(-1,100)&&!Fresh(-0.001f,0));
|
||||
assert(!Fresh(100,99)&&!Fresh(100,180101));
|
||||
assert(Need(0)&&Need(6)&&!Need(-1)&&!Need(7));
|
||||
assert(NewRequest(4,3,true)&&!NewRequest(4,4,true)&&!NewRequest(3,4,true));
|
||||
assert(!NewRequest(1,0,false)&&!NewRequest(0,0,true));
|
||||
assert(Near(0)&&Near(12)&&!Near(12.001)&&!Near(-1));
|
||||
assert(ActiveTrip(true,false,1,1,true,true));
|
||||
assert(!ActiveTrip(false,false,1,1,true,true));
|
||||
assert(!ActiveTrip(true,true,1,1,true,true));
|
||||
assert(!ActiveTrip(true,false,0,0,true,true));
|
||||
assert(!ActiveTrip(true,false,1,2,true,true));
|
||||
assert(!ActiveTrip(true,false,1,1,false,true));
|
||||
assert(!ActiveTrip(true,false,1,1,true,false));
|
||||
}
|
||||
''')
|
||||
poll=body(t,'int Poll(')
|
||||
poll=poll.replace('bool exact;','bool exact=false;').replace('InventoryItemComponent item;','InventoryItemComponent* item=nullptr;')
|
||||
poll=poll.replace('if (m_Shell) item = InventoryItemComponent.Cast(m_Shell.FindComponent(InventoryItemComponent));','if (m_Shell) item = m_Shell;')
|
||||
poll=poll.replace('item.GetParentSlot().GetStorage()','item->GetParentSlot()->GetStorage()').replace('item.GetParentSlot()','item->GetParentSlot()')
|
||||
poll=poll.replace('m_Service.TransferAllowed','m_Service->TransferAllowed')
|
||||
run('''#include <cassert>
|
||||
struct Slot {void* value;void* GetStorage(){return value;}};
|
||||
struct InventoryItemComponent {Slot* slot;Slot* GetParentSlot(){return slot;}};
|
||||
struct Service {bool valid=true;int token=1;bool TransferAllowed(int captured,InventoryItemComponent*,int,int,float){return valid&&token==captured;}};
|
||||
'''+body(t,'static bool Proven(')+'''
|
||||
struct Move {
|
||||
Service service; Service* m_Service=&service;
|
||||
bool source=true,destination=false,m_Accepted=true,m_Invalidated=false,m_Resolved=false;
|
||||
int m_Acknowledgement=0,m_Trip=1,m_From=1,m_To=2,m_Source=1,m_Destination=2;
|
||||
float m_Deadline=8000;void* m_Storage=this;
|
||||
Slot slot{this};InventoryItemComponent item{&slot};InventoryItemComponent* m_Shell=&item;
|
||||
bool Contains(int owner){return m_Shell&&(owner==1?source:destination);}
|
||||
'''+body(t,'void Acknowledge(')+body(t,'void Invalidate(')+body(t,'bool Unresolved(')+poll+'''
|
||||
};
|
||||
int main(){
|
||||
for(int bits=0;bits<16;bits++)assert(Proven(bits&1,bits&2,bits&4,bits&8)==(bits==13));
|
||||
Move pending;assert(pending.Poll(0)==0&&pending.Unresolved());
|
||||
pending.Acknowledge(true);pending.Acknowledge(false);assert(pending.m_Acknowledgement==1);
|
||||
assert(pending.Poll(1)==0);pending.destination=true;assert(pending.Poll(2)==0);
|
||||
pending.source=false;pending.slot.value=nullptr;assert(pending.Poll(3)==0);
|
||||
pending.slot.value=pending.m_Storage;assert(pending.Poll(4)==1&&!pending.Unresolved());
|
||||
Move canceled;canceled.Invalidate();assert(canceled.Poll(1)==-1&&canceled.Unresolved());
|
||||
canceled.source=false;canceled.destination=true;canceled.Acknowledge(true);
|
||||
assert(canceled.Poll(2)==-1&&!canceled.Unresolved()); // late physical success releases reservation, never authority
|
||||
Move expired;assert(expired.Poll(8000)==-1&&expired.Unresolved());
|
||||
expired.source=false;expired.destination=true;expired.Acknowledge(true);
|
||||
assert(expired.Poll(9000)==-1&&!expired.Unresolved());
|
||||
Move replaced;replaced.service.token=2;replaced.source=false;replaced.destination=true;replaced.Acknowledge(true);
|
||||
assert(replaced.Poll(1)==-1); // callback from previous trip cannot supply new trip
|
||||
Move captured;captured.service.valid=false;assert(captured.Poll(1)==-1&&captured.Unresolved());
|
||||
Move rejected;rejected.Acknowledge(false);assert(rejected.Poll(1)==-1&&!rejected.Unresolved());
|
||||
Move lost;lost.m_Shell=nullptr;lost.Acknowledge(true);assert(lost.Poll(1)==-1&&!lost.Unresolved());assert(lost.Poll(8000)==-1&&!lost.Unresolved());
|
||||
}
|
||||
''')
|
||||
accept=body(p,'bool AcceptsStatus(').replace('ERR_MortarSupplyStatus report','ERR_MortarSupplyStatus* report').replace('m_Battery.','m_Battery->').replace('report.','report->').replace('ERR_MortarLogisticsRules.','Rules::')
|
||||
run('''#include <cassert>
|
||||
struct Rules {'''+policies+'''};
|
||||
struct ERR_MortarSupplyStatus {void* Group;void* Gun;float ObservedAt;int MessageId;int Shells;};
|
||||
struct Battery {void* Group;void* Gun;};
|
||||
struct Service {bool m_Stopped=false;int m_State=1,m_ConsumedMessage=0;Battery* m_Battery=nullptr;
|
||||
'''+accept+'''};
|
||||
int main(){int group,gun,other;Battery battery{&group,&gun};Service service;service.m_Battery=&battery;
|
||||
ERR_MortarSupplyStatus report{&group,&gun,100,1,0};assert(service.AcceptsStatus(&report,101));
|
||||
assert(!service.AcceptsStatus(nullptr,101));
|
||||
report.Gun=&other;assert(!service.AcceptsStatus(&report,101));report.Gun=&gun;
|
||||
report.Group=&other;assert(!service.AcceptsStatus(&report,101));report.Group=&group;
|
||||
report.Shells=7;assert(!service.AcceptsStatus(&report,101));report.Shells=0;
|
||||
assert(!service.AcceptsStatus(&report,180101));assert(!service.AcceptsStatus(&report,99));
|
||||
report.ObservedAt=-1;assert(!service.AcceptsStatus(&report,100));report.ObservedAt=100;
|
||||
service.m_ConsumedMessage=1;assert(!service.AcceptsStatus(&report,101));report.MessageId=2;assert(service.AcceptsStatus(&report,101));
|
||||
service.m_State=3;assert(!service.AcceptsStatus(&report,101));service.m_State=1;
|
||||
service.m_Stopped=true;assert(!service.AcceptsStatus(&report,101));
|
||||
}
|
||||
''')
|
||||
print('PASS: actual finite-courier age, token, exact ownership/callback and delivered-status admission reject stale, replayed, captured, canceled and ambiguous work')
|
||||
# Run the actual gun-capture gate with narrow native identity/compartment doubles.
|
||||
target=body(p,'bool TargetAvailable(')
|
||||
target=target.replace('m_Battery.','m_Battery->').replace('m_HQ.Mortars().FindGun','m_HQ->Mortars()->FindGun').replace('m_Mode.EnemyFaction()','m_Mode->EnemyFaction()')
|
||||
target=target.replace('SCR_FactionManager.SGetFaction','SCR_FactionManager::SGetFaction').replace('ERR_Actors.','ERR_Actors::')
|
||||
target=target.replace('SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_OriginalGun.FindComponent(SCR_AIVehicleUsageComponent));','Usage* usage = m_OriginalGun->usage;')
|
||||
target=target.replace('IEntity occupant','Actor* occupant').replace('usage.GetTurretCompartmentSlot().GetOccupant()','usage->GetTurretCompartmentSlot()->GetOccupant()').replace('usage.GetTurretCompartmentSlot()','usage->GetTurretCompartmentSlot()')
|
||||
run('''#include <cassert>
|
||||
struct Usage;
|
||||
struct Actor {void* faction;void* group;bool alive=true;bool player=false;Usage* usage=nullptr;};
|
||||
struct Slot {Actor* occupant=nullptr;Actor* GetOccupant(){return occupant;}};
|
||||
struct Usage {Slot slot;bool present=true;Slot* GetTurretCompartmentSlot(){return present?&slot:nullptr;}};
|
||||
struct Battery {Actor* Gun;void* Group;bool StartupActivated=true;bool operational=true;bool stopped=false;bool Stopped(){return stopped;}bool Operational(){return operational;}};
|
||||
struct Registry {Battery* battery;Battery* FindGun(Actor* gun){return battery&&battery->Gun==gun?battery:nullptr;}};
|
||||
struct HQ {Registry registry;Registry* Mortars(){return ®istry;}};
|
||||
struct Mode {void* faction;void* EnemyFaction(){return faction;}};
|
||||
struct SCR_FactionManager {static void* SGetFaction(Actor* actor){return actor->faction;}};
|
||||
struct ERR_Actors {static bool CanFight(Actor* a){return a->alive;}static bool Player(Actor* a){return a->player;}static void* Group(Actor* a){return a->group;}};
|
||||
struct Members {Actor* member;bool IsEmpty(){return !member;}bool Contains(Actor* a){return a==member;}};
|
||||
struct Service {Battery* m_Battery;Actor* m_OriginalGun;void* m_OriginalBatteryGroup;HQ* m_HQ;Mode* m_Mode;Members m_BatteryMembers;
|
||||
'''+target+'''
|
||||
};
|
||||
int main(){int faction,group,foreign;Usage usage;Actor gun{&faction,&group};gun.usage=&usage;Actor original{&faction,&group};
|
||||
Battery battery{&gun,&group};HQ hq{{&battery}};Mode mode{&faction};Service service{&battery,&gun,&group,&hq,&mode,{&original}};
|
||||
assert(service.TargetAvailable());usage.slot.occupant=&original;assert(service.TargetAvailable());
|
||||
original.player=true;assert(!service.TargetAvailable());original.player=false;
|
||||
original.alive=false;assert(!service.TargetAvailable());original.alive=true;
|
||||
original.group=&foreign;assert(!service.TargetAvailable());original.group=&group;
|
||||
original.faction=&foreign;assert(!service.TargetAvailable());original.faction=&faction;
|
||||
Actor stranger{&faction,&group};usage.slot.occupant=&stranger;assert(!service.TargetAvailable());usage.slot.occupant=&original;
|
||||
gun.faction=&foreign;assert(!service.TargetAvailable());gun.faction=&faction;
|
||||
usage.present=false;assert(!service.TargetAvailable());usage.present=true;
|
||||
battery.operational=false;assert(!service.TargetAvailable());battery.operational=true;
|
||||
battery.stopped=true;assert(!service.TargetAvailable());battery.stopped=false;
|
||||
battery.Gun=&stranger;assert(!service.TargetAvailable());battery.Gun=&gun;
|
||||
hq.registry.battery=nullptr;assert(!service.TargetAvailable());hq.registry.battery=&battery;
|
||||
service.m_BatteryMembers.member=nullptr;assert(!service.TargetAvailable());
|
||||
}
|
||||
''')
|
||||
print('PASS: actual native-gun ownership gate rejects player/foreign/replaced/dead occupant, faction change, missing seat and missing registry')
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Execute production-of-fixture geometry rules, without native mesh claims."""
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
from policy_harness import body, run
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
HELPER = ROOT/'addons/core/Scripts/Game/ERR/ERR_NativeFlightFootprint.c'
|
||||
PROBE = ROOT/'addons/core/Scripts/Game/ERR/ERR_NativePoweredFlightProbe.c'
|
||||
|
||||
class FlightFootprint(unittest.TestCase):
|
||||
def test_actual_complete_envelope_gate(self):
|
||||
run('#include <cassert>\n#include <limits>\n'+body(HELPER,'static bool Fits(')+'''
|
||||
int main(){
|
||||
assert(Fits(28,0,1)); assert(Fits(30,1,1));
|
||||
assert(!Fits(30,1.01f,1)); assert(!Fits(32,0,1));
|
||||
assert(!Fits(15,24,1)); // smaller craft still cannot escape its clear column
|
||||
assert(!Fits(0,0,1)); assert(!Fits(-1,0,1));
|
||||
assert(!Fits(20,-1,1)); assert(!Fits(20,0,6)); assert(!Fits(20,0,-4));
|
||||
float nan=std::numeric_limits<float>::quiet_NaN();
|
||||
float inf=std::numeric_limits<float>::infinity();
|
||||
assert(!Fits(nan,0,1)); assert(!Fits(20,nan,1)); assert(!Fits(20,0,nan));
|
||||
assert(!Fits(inf,0,1)); assert(!Fits(20,inf,1));
|
||||
for(int r=1;r<=40;r++)for(int d=0;d<=40;d++){
|
||||
bool expected=r<32&&d<32&&r+d+9<=40;
|
||||
assert(Fits(r,d,1)==expected);
|
||||
}
|
||||
}
|
||||
''')
|
||||
|
||||
def test_actual_rotor_cylinder_sphere_overbounds_every_orientation(self):
|
||||
run('#include <cassert>\n#include <cmath>\n#include <initializer_list>\n'+body(HELPER,'static float CylinderSphere(')+'''
|
||||
int main(){
|
||||
for(float radius:{2.3f,12.f})for(float thickness:{.75f,1.5f}) {
|
||||
float pivot=14.5f; float bound=CylinderSphere(pivot,radius,thickness);
|
||||
assert(bound>=pivot+std::hypot(radius,thickness));
|
||||
for(int angle=0;angle<360;angle++) {
|
||||
float a=angle*3.14159265f/180; float x=radius*std::cos(a),z=radius*std::sin(a);
|
||||
float distance=std::sqrt(x*x+thickness*thickness+(z+pivot)*(z+pivot));
|
||||
assert(distance<=bound+.0001f);
|
||||
}
|
||||
}
|
||||
assert(CylinderSphere(13.6f,2.3f,.75f)>15);
|
||||
}
|
||||
''')
|
||||
|
||||
def test_native_measurement_is_bounded_and_requires_both_damage_sweeps(self):
|
||||
source=HELPER.read_text()
|
||||
for text in ['MAX_ENTITIES = 256','MAX_DEPTH = 16','MAX_HITZONES = 512',
|
||||
'entities.Contains(child)', 'CountAllHitZones()', 'GetAllHitZones(zones)',
|
||||
'aircraft.GetWorldTransform(worldTransform)', 'origin = worldTransform[3]',
|
||||
'entity.GetWorldBounds(mins, maxs)', 'simulation.RotorGetTransform(rotor, localRotor)',
|
||||
'simulation.RotorGetRadius(rotor)', 'rotorZone.GetPointInfo()',
|
||||
'point.GetWorldTransform(rotorWorld)', 'mainSweeps != 1 || tailSweeps != 1',
|
||||
'expectedRadius = 12; thickness = 1.5', 'expectedRadius = 2.3; thickness = 0.75',
|
||||
'Math.Sqrt(scaleSquared)', 'radius = Math.Max(modelRadius, Math.Max(damageRadius, simulationRadius))']:
|
||||
self.assertIn(text,source)
|
||||
self.assertNotIn('simulation.GetWorldTransform(',source)
|
||||
for forbidden in ['SetOrigin(', 'SetWorldTransform(', 'SetVelocity(', 'SetHealth(', 'Spawn(', 'ForceStartEngine(']:
|
||||
self.assertNotIn(forbidden,source)
|
||||
|
||||
def test_gate_precedes_single_engine_request_and_tightens_drift(self):
|
||||
source=PROBE.read_text()
|
||||
self.assertLess(source.index('ERR_NativeFlightFootprint.Measure('),source.index('m_Controller.StartEngine()'))
|
||||
self.assertEqual(source.count('m_Controller.StartEngine()'),1)
|
||||
self.assertIn('m_Home = measuredOrigin;',source)
|
||||
self.assertIn('drift >= 8',source)
|
||||
self.assertNotIn('drift > 22',source)
|
||||
self.assertIn('clearance.Mins = "-40 -1 -40"; clearance.Maxs = "40 1 40"',source)
|
||||
self.assertIn('clearance.End = candidate + "0 60 0"',source)
|
||||
self.assertIn('refinementAnchor = bestCandidate',source)
|
||||
|
||||
if __name__=='__main__': unittest.main()
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Read-only diagnostic boundaries; no native control/application proof."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from policy_harness import body
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
SOURCE=ROOT/'addons/core/Scripts/Game/ERR/ERR_NativeFlightInputDiagnostics.c'
|
||||
|
||||
class FlightInputDiagnostics(unittest.TestCase):
|
||||
def test_observers_have_no_control_or_setting_mutators(self):
|
||||
source=SOURCE.read_text()
|
||||
self.assertTrue(source.startswith('#ifdef WORKBENCH'))
|
||||
for forbidden in ['.Set', '.AddOrFindSignal(', '.AddOrFindMPSignal(', '.StartEngine(', '.StopEngine(', '.Spawn', '.Teleport(', '.Delete', 'Check(', 'Transition(']:
|
||||
self.assertNotIn(forbidden,source)
|
||||
self.assertNotIn('modded class SCR_CharacterControllerComponent',source)
|
||||
self.assertIn('super.ApplyPilot(controller, owner, dt);',source)
|
||||
self.assertIn('super.Diagnostic(now);',source)
|
||||
|
||||
def test_exact_actor_and_authority_before_observation(self):
|
||||
for signature in ['override void ApplyPilot(', 'void ERR_ObserveAppliedFlightInput(']:
|
||||
method=body(SOURCE,signature)
|
||||
self.assertIn('controller != m_Character || owner != m_Pilot',method)
|
||||
self.assertIn('!ERR_InputDiagActive()',method)
|
||||
self.assertLess(method.index('owner != m_Pilot'),method.index('m_Input.GetVehicleCollective()'))
|
||||
active=body(SOURCE,'protected bool ERR_InputDiagActive(')
|
||||
self.assertIn('System.GetCLIParam("errFlightInputDiag", enabled)',active)
|
||||
self.assertIn('!m_Bound || m_Stopped || !m_Input',active)
|
||||
self.assertIn('return PoweredAuthority();',active)
|
||||
|
||||
def test_existing_signal_indexes_and_bounded_correlated_samples(self):
|
||||
lookup=body(SOURCE,'protected string ERR_ExistingRotorSignal(')
|
||||
self.assertIn('signals.FindSignal(name)',lookup)
|
||||
self.assertLess(lookup.index('if (index < 0) return "missing";'),lookup.index('signals.GetSignalValue(index)'))
|
||||
source=SOURCE.read_text()
|
||||
for name in ['MainRotorCollective','MainRotorCyclicForward','MainRotorCyclicAside','TailRotorCollective']:
|
||||
self.assertIn('ERR_ExistingRotorSignal(signals, "'+name+'")',source)
|
||||
self.assertIn('m_Controller.GetAutohoverEnabled()',source)
|
||||
self.assertIn('now >= m_NextLog',source)
|
||||
self.assertIn('m_ERRInputDiagSamples >= 120',source)
|
||||
self.assertEqual(source.count('PrintFormat('),3)
|
||||
self.assertIn('m_ERRAppliedSequence = m_ERRWrittenSequence;',source)
|
||||
self.assertIn('m_ControlUpdates == before',source)
|
||||
|
||||
def test_optional_runner_flag_parses_then_refuses_missing_prefix(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
profile=Path(directory)
|
||||
result=subprocess.run([sys.executable,str(ROOT/'tools/headless_workbench.py'),
|
||||
'--smoke','powered-flight','--project','core','--rounds','1','--timeout','420',
|
||||
'--compat',str(profile/'missing-prefix'),'--profile',str(profile),
|
||||
'--flight-input-diagnostics'],text=True,capture_output=True)
|
||||
self.assertNotEqual(result.returncode,0)
|
||||
self.assertIn('must be a disposable cloned prefix',result.stderr)
|
||||
self.assertEqual(list(profile.iterdir()),[])
|
||||
runner=(ROOT/'tools/headless_workbench.py').read_text()
|
||||
self.assertIn("if a.flight_input_diagnostics: command += ['-errFlightInputDiag', '1']",runner)
|
||||
|
||||
if __name__=='__main__': unittest.main()
|
||||
@@ -0,0 +1,77 @@
|
||||
"""Execute the fixture's deterministic pad survey geometry; no terrain claims."""
|
||||
from policy_harness import ROOT, body, run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/ERR_NativePoweredFlightProbe.c'
|
||||
source=body(p,'static vector PadOffset(')
|
||||
source+='\n'+body(p,'static vector RefineOffset(')
|
||||
source+='\n'+body(p,'static vector AirportOffset(')
|
||||
# C++ accepts %, but Enforce does not. Fail before the C++ harness can hide
|
||||
# that native compilation error; only namespace spelling is translated.
|
||||
assert '%' not in source, 'Enforce remainder requires Math.Mod, not the C++ % operator'
|
||||
s=source.replace('Math.','Math::').replace('vector.Zero','vector::Zero')
|
||||
run('''#include <cassert>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
struct vector {float x,y,z;static const vector Zero;};const vector vector::Zero{0,0,0};
|
||||
vector Vector(float x,float y,float z){return {x,y,z};}
|
||||
namespace Math {constexpr float PI=3.14159265358979323846f;float Cos(float a){return std::cos(a);}float Sin(float a){return std::sin(a);}float Mod(float value,float divisor){return std::fmod(value,divisor);}}
|
||||
constexpr int PAD_CANDIDATES=128;
|
||||
constexpr int PAD_REFINEMENTS=80;
|
||||
constexpr int AIRPORT_CANDIDATES=169;
|
||||
'''+s+'''
|
||||
int main(){
|
||||
std::vector<vector> seen;
|
||||
for(int i=0;i<PAD_CANDIDATES;i++){
|
||||
auto p=PadOffset(i);float radius=std::hypot(p.x,p.z);assert(p.y==0&&radius>=119.99f&&radius<=500.01f);
|
||||
for(auto old:seen)assert(std::hypot(p.x-old.x,p.z-old.z)>1);
|
||||
seen.push_back(p);
|
||||
if(i<12){float angle=i*Math::PI/6;assert(std::hypot(p.x-std::cos(angle)*120,p.z-std::sin(angle)*120)<.001f);}
|
||||
if(i>=12&&i<108){
|
||||
int ring=(i-12)/24;int sample=(i-12)-24*ring;
|
||||
float phase=(ring==1||ring==3)?0.5f:0.f;
|
||||
float expectedRadius=200+80*ring;float angle=(sample+phase)*2*Math::PI/24;
|
||||
assert(std::fabs(radius-expectedRadius)<.001f);
|
||||
assert(std::hypot(p.x-std::cos(angle)*expectedRadius,p.z-std::sin(angle)*expectedRadius)<.001f);
|
||||
}
|
||||
if(i>=108)assert(std::fabs(radius-500)<.001f);
|
||||
}
|
||||
assert(seen.size()==128);
|
||||
for(int invalid:{-1,128,999}){auto p=PadOffset(invalid);assert(p.x==0&&p.y==0&&p.z==0);}
|
||||
seen.clear();
|
||||
for(int i=0;i<PAD_REFINEMENTS;i++){
|
||||
auto p=RefineOffset(i);assert(p.y==0&&std::hypot(p.x,p.z)>9.99f&&std::hypot(p.x,p.z)<56.57f);
|
||||
assert(std::fabs(p.x)<=40&&std::fabs(p.z)<=40);
|
||||
for(auto old:seen)assert(std::hypot(p.x-old.x,p.z-old.z)>9.99f);
|
||||
seen.push_back(p);
|
||||
}
|
||||
assert(seen.size()==80);
|
||||
for(int x=-40;x<=40;x+=10)for(int z=-40;z<=40;z+=10){
|
||||
if(x==0&&z==0)continue;
|
||||
int matches=0;for(auto p:seen)if(p.x==x&&p.z==z)matches++;assert(matches==1);
|
||||
}
|
||||
for(int invalid:{-1,80,999}){auto p=RefineOffset(invalid);assert(p.x==0&&p.y==0&&p.z==0);}
|
||||
seen.clear();
|
||||
for(int i=0;i<AIRPORT_CANDIDATES;i++){
|
||||
auto p=AirportOffset(i);assert(p.y==0&&std::fabs(p.x)<=300&&std::fabs(p.z)<=300);
|
||||
for(auto old:seen)assert(std::hypot(p.x-old.x,p.z-old.z)>49.99f);
|
||||
seen.push_back(p);
|
||||
}
|
||||
assert(seen.size()==169);
|
||||
for(int x=-300;x<=300;x+=50)for(int z=-300;z<=300;z+=50){
|
||||
int matches=0;for(auto p:seen)if(p.x==x&&p.z==z)matches++;assert(matches==1);
|
||||
}
|
||||
for(int invalid:{-1,169,999}){auto p=AirportOffset(invalid);assert(p.x==0&&p.y==0&&p.z==0);}
|
||||
}
|
||||
''')
|
||||
# The native safety queries remain boundaries, not fake geometry measurements.
|
||||
find=body(p,'protected bool FindPad(')
|
||||
assert 'maximumDelta > 0.8' in find and 'Math.Cos(side) * 14' in find
|
||||
assert 'TraceCilinderUtil(candidate + "0 2.5 0", 40, 4, TraceFlags.ENTS)' in find
|
||||
assert 'clearance.Start = candidate + "0 3 0"; clearance.End = candidate + "0 60 0"' in find
|
||||
assert 'clearance.Mins = "-40 -1 -40"; clearance.Maxs = "40 1 40"' in find
|
||||
assert find.index('TraceMove(clearance, null) < 1') < find.index('result = candidate; return true;')
|
||||
assert 'result = bestCandidate' not in find
|
||||
assert find.count('refinementAnchor = bestCandidate') == 1
|
||||
assert 'index == ERR_PoweredFlightRules.PAD_CANDIDATES' in find
|
||||
assert 'ERR_PoweredFlightRules.PAD_CANDIDATES + ERR_PoweredFlightRules.PAD_REFINEMENTS' in find
|
||||
assert 'localCandidates + ERR_PoweredFlightRules.AIRPORT_CANDIDATES' in find
|
||||
print('PASS: 128 coarse+80 local refinement+169 airport grid candidates retain all native clearance gates; real terrain untested')
|
||||
@@ -50,13 +50,104 @@ int main(){
|
||||
for control in ('Collective','CyclicForward','CyclicAside','AntiTorque'):
|
||||
self.assertIn('m_Input.SetVehicle'+control+'(',source)
|
||||
self.assertIn('m_Input.GetVehicle'+control+'()',source)
|
||||
def test_native_initialization_precedes_single_pilot_allocation(self):
|
||||
source=SOURCE.read_text()
|
||||
tick=body(SOURCE,'int Tick(')
|
||||
spawn=tick.split('if (m_Stage == 0)',1)[1].split('if (m_Stage == 8)',1)[0]
|
||||
initialize=tick.split('if (m_Stage == 8)',1)[1].split('if (m_Stage == 1)',1)[0]
|
||||
seating=tick.split('if (m_Stage == 1)',1)[1].split('\n Diagnostic(now)',1)[0]
|
||||
self.assertNotIn('SpawnCharacterInCompartment',spawn)
|
||||
self.assertIn('m_Stage = 8; m_StageDeadline = now + 30000',spawn)
|
||||
self.assertEqual(source.count('.SpawnCharacterInCompartment('),1)
|
||||
call=initialize.index('.SpawnCharacterInCompartment(')
|
||||
for guard in ('now >= m_StageDeadline', 'if (!componentsReady) return 0;', 'm_PilotSpawnRequested = true;', 'm_Group.GetAgentsCount() == 0'):
|
||||
self.assertLess(initialize.index(guard),call)
|
||||
self.assertNotIn('m_StageDeadline =',initialize)
|
||||
self.assertIn('nativeGroup == m_Group',initialize)
|
||||
self.assertIn('m_Controller.GetPilotCompartmentSlot() != m_Seat',seating)
|
||||
self.assertIn('m_Controller.GetPilotCompartmentSlot() == m_Seat',body(SOURCE,'protected bool PoweredAuthority('))
|
||||
def test_empty_primary_native_seat_and_readiness_are_observed(self):
|
||||
method=body(SOURCE,'protected bool PollNativeFlightComponents(')
|
||||
for guard in ('manager.GetCompartments(slots)', 'slots.Count() <= 64', 'PilotCompartmentSlot.Cast(slot)', 'pilotSlot.GetOwner() != m_Aircraft', 'pilotSlot.GetVehicle() != m_Aircraft', 'GetCompartmentName() != "PilotCompartment"', 'ECompartmentType.PILOT', '!pilotSlot.IsCompartmentAccessible()', 'pilotSlot.GetOccupant() || pilotSlot.IsReserved() || pilotSlot.IsGetInLocked()', 'm_Simulation.IsValid()', 'm_Fuel.HasFuel()'):
|
||||
self.assertIn(guard,method)
|
||||
self.assertIn('now >= m_NextInitializationLog || now >= m_StageDeadline',method)
|
||||
self.assertIn('m_NextInitializationLog = now + 2000',method)
|
||||
self.assertIn('return m_Controller && validSimulation && m_Seat && fueled;',method)
|
||||
self.assertNotIn('.Spawn(',method)
|
||||
self.assertNotIn('.SpawnCharacterInCompartment(',method)
|
||||
def test_seating_diagnostics_observe_all_guards_without_mutation(self):
|
||||
method=body(SOURCE,'protected void SeatingDiagnostic(')
|
||||
for predicate in ('OwnPilot()', 'm_Seat.GetOccupant() == m_Pilot', 'm_Controller.GetPilotCompartmentSlot() == m_Seat', 'ERR_CasualtyLoadingRules.AircraftReady(m_Aircraft)', 'ERR_Actors.Group(m_Pilot)', 'ERR_Actors.CanFight(m_Pilot)', 'physics.IsActive()', 'physics.IsDynamic()', 'physics.GetVelocity()', 'physics.GetAngularVelocity()', 'm_Simulation.HasAnyGroundContact()', 'm_Simulation.IsValid()', 'access.IsGettingIn()', 'access.IsGettingOut()', 'm_Simulation.GetWorldTransform(nativeTransform)', 'm_Aircraft.GetWorldTransform(entityTransform)'):
|
||||
self.assertIn(predicate,method)
|
||||
self.assertIn('now < m_NextSeatingLog && now < m_StageDeadline',method)
|
||||
self.assertIn('m_NextSeatingLog = now + 2000',method)
|
||||
self.assertIn('ERR_CasualtyGroundSupport.Parked(m_Aircraft, now >= m_StageDeadline)',method)
|
||||
for mutation in ('.Set', '.Spawn', '.Delete', '.GetInVehicle(', 'm_StageDeadline =', 'm_Stable =', 'Check('):
|
||||
self.assertNotIn(mutation,method)
|
||||
seating=body(SOURCE,'int Tick(').split('if (m_Stage == 1)',1)[1].split('\n Diagnostic(now);',1)[0]
|
||||
self.assertIn('SeatingDiagnostic(now);',seating)
|
||||
self.assertLess(seating.index('SeatingDiagnostic(now);'),seating.index('now >= m_StageDeadline'))
|
||||
def test_memberless_group_and_exact_pilot_lod_are_explicit(self):
|
||||
source=SOURCE.read_text()
|
||||
self.assertIn('static const ResourceName GROUP = "{000CD338713F2B5A}Prefabs/AI/Groups/Group_Base.et";',source)
|
||||
self.assertNotIn('Prefabs/Groups/OPFOR/Group_USSR_Base.et',source)
|
||||
self.assertIn('m_Group.InitFactionKey("USSR") && m_Group.GetFactionName() == "USSR"',source)
|
||||
initialize=body(SOURCE,'int Tick(').split('if (m_Stage == 8)',1)[1].split('if (m_Stage == 1)',1)[0]
|
||||
self.assertIn('pilotControl.GetControlAIAgent()',initialize)
|
||||
self.assertIn('m_PilotAgent && OwnPilot()',initialize)
|
||||
self.assertIn('m_OriginalPilotLOD = m_PilotAgent.GetPermanentLOD()',initialize)
|
||||
self.assertIn('m_PilotAgent.SetPermanentLOD(0); m_PilotAgent.ActivateAI();',initialize)
|
||||
self.assertEqual(source.count('m_PilotAgent.SetPermanentLOD(0)'),1)
|
||||
self.assertIn('m_PilotLODSaved && m_PilotAgent && OwnPilot() && !PlayerPresent()',body(SOURCE,'void Stop()'))
|
||||
self.assertIn('m_PilotAgent.SetPermanentLOD(m_OriginalPilotLOD)',body(SOURCE,'void Stop()'))
|
||||
def test_native_collective_encoding_preserves_signed_controller(self):
|
||||
methods='\n'.join(body(SOURCE,key).replace('Math.','Math::') for key in ['static float ClampInput(', 'static float NativeCollective('])
|
||||
run('#include <cassert>\n#include <algorithm>\nstruct Math { static float Clamp(float x,float low,float high){return std::clamp(x,low,high);} };\n'+methods+'''
|
||||
int main(){
|
||||
assert(NativeCollective(-1)==0); assert(NativeCollective(0)==0.5f); assert(NativeCollective(1)==1);
|
||||
assert(NativeCollective(-5)==0 && NativeCollective(5)==1);
|
||||
float previous=-1;
|
||||
for(int i=-100;i<=100;i++) {float command=NativeCollective(i/100.f); assert(command>=0 && command<=1 && command>=previous); previous=command;}
|
||||
}
|
||||
''')
|
||||
pilot=body(SOURCE,'void ApplyPilot(')
|
||||
self.assertLess(pilot.index('ERR_PoweredFlightRules.NativeCollective(collective)'),pilot.index('m_Input.SetVehicleCollective(collective)'))
|
||||
self.assertIn('m_Input.GetVehicleCollective() == 0',SOURCE.read_text())
|
||||
def test_control_and_flight_geometry_use_actual_world_pose(self):
|
||||
for signature in ('void ApplyPilot(', 'protected void Diagnostic(', 'int Tick('):
|
||||
method=body(SOURCE,signature)
|
||||
self.assertIn('m_Aircraft.GetWorldTransform(transform)',method)
|
||||
self.assertNotIn('m_Simulation.GetWorldTransform(transform)',method)
|
||||
# Internal simulation pose remains diagnostic only, never geometry feedback.
|
||||
self.assertEqual(SOURCE.read_text().count('m_Simulation.GetWorldTransform('),1)
|
||||
def test_final_apply_is_the_only_integrator_and_positive_signal_is_required(self):
|
||||
source=SOURCE.read_text()
|
||||
prepare=body(SOURCE,'override void OnPrepareControls(')
|
||||
native_apply=body(SOURCE,'protected override void OnApplyControls(')
|
||||
observe=body(SOURCE,'void ObservePilotPreparation(')
|
||||
self.assertNotIn('ApplyPilot',prepare)
|
||||
self.assertNotIn('.SetVehicle',observe)
|
||||
self.assertNotIn('m_Collective =',observe)
|
||||
self.assertLess(native_apply.index('super.OnApplyControls(owner, timeSlice)'),native_apply.index('probe.ApplyPilot(this, owner, timeSlice)'))
|
||||
self.assertEqual(source.count('m_Collective + error * dt'),1)
|
||||
self.assertEqual(source.count('probe.ApplyPilot('),1)
|
||||
gate=body(SOURCE,'int Tick(').split('if (m_Stage == 9)',1)[1].split('if (m_Stage == 4 ||',1)[0]
|
||||
self.assertIn('now >= m_StageDeadline',gate)
|
||||
self.assertIn('signals.FindSignal("MainRotorCollective")',gate)
|
||||
self.assertLess(gate.index('signalIndex >= 0'),gate.index('signals.GetSignalValue(signalIndex)'))
|
||||
self.assertIn('now - m_PositiveSignalSince < 500',gate)
|
||||
self.assertIn('m_ControlUpdates >= m_PositiveGateUpdates + 3',gate)
|
||||
method=body(SOURCE,'static bool CollectiveObserved(').replace('Math.','Math::')
|
||||
run('#include <cassert>\n#include <cmath>\nstruct Math { static float AbsFloat(float x){return std::abs(x);} };\n'+method+'''
|
||||
int main(){assert(CollectiveObserved(.05f,.05f));assert(!CollectiveObserved(.05f,0));assert(!CollectiveObserved(0,0));assert(!CollectiveObserved(.05f,1));assert(!CollectiveObserved(.5f,.5f));assert(!CollectiveObserved(.05f,NAN));}
|
||||
''')
|
||||
def test_stop_and_engine_shutdown_are_bounded(self):
|
||||
source=SOURCE.read_text();stop=body(SOURCE,'void Stop()')
|
||||
self.assertIn('OwnPilot() && !PlayerPresent()',stop)
|
||||
self.assertIn('s_Current = null',stop)
|
||||
self.assertNotIn('StopEngine',stop) # no engine kill on midair fail/capture
|
||||
self.assertNotIn('DeleteSpawned',stop)
|
||||
self.assertIn('m_Stage >= 4 && m_Stage <= 6 && !m_Controller.IsEngineOn()',source)
|
||||
self.assertIn('((m_Stage >= 4 && m_Stage <= 6) || m_Stage == 9) && !m_Controller.IsEngineOn()',source)
|
||||
self.assertIn('m_Fuel.GetTotalFuel() < m_InitialFuel - 0.05',source)
|
||||
self.assertIn('m_Controller.StopEngine(false)',source)
|
||||
self.assertIn('ERR_PoweredFlightRules.Landed(m_Simulation.HasAnyGroundContact()',source)
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Boundary/discontinuity regressions execute production policy bodies, not a copy.
|
||||
Native probe separately exercises the direct locked-door boundary and destroyed gate.
|
||||
"""
|
||||
from policy_harness import ROOT, body, run
|
||||
path=ROOT/'addons/core/Scripts/Game/ERR/ERR_PrisonEscapeService.c'
|
||||
functions='\n'.join(body(path,s) for s in ('static bool Inside(', 'static bool Continuous(', 'static bool Registration(', 'static bool Grounded(', 'static bool Crossing(')).replace('Math.AbsFloat','std::abs')
|
||||
run('''#include <cassert>
|
||||
#include <cmath>
|
||||
#include <initializer_list>
|
||||
'''+functions+'''
|
||||
int main() {
|
||||
// Initial identity, unique intact gate and complete bounded traversal required.
|
||||
assert(Registration(true,1,true,true));
|
||||
assert(!Registration(false,1,true,true));
|
||||
assert(!Registration(true,0,true,true));
|
||||
assert(!Registration(true,2,true,true));
|
||||
assert(!Registration(true,1,false,true));
|
||||
assert(!Registration(true,1,true,false));
|
||||
// Four ordinary sides and diagonal boundary behavior; exact boundary still in.
|
||||
for (float sign : {-1.f,1.f}) {
|
||||
assert(Crossing(true,sign*7,0,sign*9,0,1000,2));
|
||||
assert(Crossing(true,0,sign*7,0,sign*9,1000,2));
|
||||
assert(!Crossing(true,sign*7,0,sign*8,0,1000,1));
|
||||
}
|
||||
assert(Crossing(true,7,7,9,9,1000,std::sqrt(8.f)));
|
||||
// Gate health is deliberately absent: a registered genuine exit counts even
|
||||
// when intact. Missing registration is still rejected above and by Tick.
|
||||
// An unarmed/already-outside body or no crossing cannot finish prison.
|
||||
assert(!Crossing(false,7,0,9,0,1000,2));
|
||||
assert(!Crossing(true,4,0,5,0,1000,1));
|
||||
assert(!Crossing(true,9,0,10,0,1000,1));
|
||||
// Reject far teleport, expired sample, clock reversal/duplicate and flight.
|
||||
assert(!Crossing(true,0,0,35,0,1000,35));
|
||||
assert(!Crossing(true,7,0,9,0,2501,2));
|
||||
assert(!Crossing(true,7,0,9,0,0,2));
|
||||
assert(!Crossing(true,7,0,9,0,-1,2));
|
||||
assert(Grounded(0.1f) && Grounded(2.5f));
|
||||
assert(!Grounded(2.501f) && !Grounded(-0.501f));
|
||||
// Native spawn candidates can arm; an outside/perimeter-only arrival cannot.
|
||||
for (float x : {-4.5f,-1.5f,1.5f,4.5f})
|
||||
for (float z : {-4.5f,-1.5f,1.5f,4.5f}) assert(Inside(x,z,5.5));
|
||||
assert(!Inside(6,0,5.5) && Inside(6,0,8));
|
||||
assert(Continuous(2500,31.5f) && !Continuous(2500,31.501f));
|
||||
}
|
||||
''')
|
||||
print('PASS: actual prison registration/crossing policies reject missing/duplicate/destroyed initial gate, incomplete ownership/scan, outside-only body, jumps and stale samples; physical exit independent of later gate damage')
|
||||
|
||||
# Actual action bodies, with only native type/member syntax adapted to C++.
|
||||
# This tests direct execution, not merely a duplicate availability predicate.
|
||||
action_path=ROOT/'addons/core/Scripts/Game/ERR/ERR_Actions.c'
|
||||
class ActionSource:
|
||||
def read_text(self):
|
||||
source=action_path.read_text()
|
||||
return source[source.index('class ERR_PrisonDoorAction'):source.index('class ERR_TuneRadioAction')]
|
||||
action=ActionSource()
|
||||
availability=body(action,'override bool CanBePerformedScript(').replace('override ','').replace('ERR_GameMode mode = ERR_GameMode.Get();','ERR_GameMode* mode = ERR_GameMode::Get();').replace('mode.Phase()','mode->Phase()').replace('ERR_Phase.PRISON','ERR_Phase::PRISON').replace('super.CanBePerformedScript','Base::CanBePerformedScript')
|
||||
execution=body(action,'override void PerformAction(').replace('override ','').replace('super.PerformAction','Base::PerformAction')
|
||||
run('''#include <cassert>
|
||||
using IEntity=int;
|
||||
enum ERR_Phase {PREPARING,PRISON,RADIO_SEARCH,RADIO_DEFENCE};
|
||||
struct ERR_GameMode {int phase=PRISON;static ERR_GameMode* current;static ERR_GameMode* Get(){return current;}int Phase(){return phase;}};
|
||||
ERR_GameMode* ERR_GameMode::current=nullptr;
|
||||
struct Base {bool nativeAllowed=true;int calls=0,owner=0,user=0;bool CanBePerformedScript(IEntity){return nativeAllowed;}void PerformAction(IEntity pOwnerEntity,IEntity pUserEntity){calls++;owner=pOwnerEntity;user=pUserEntity;}};
|
||||
struct Action:Base {
|
||||
'''+availability+execution+'''
|
||||
};
|
||||
int main(){
|
||||
Action action;action.PerformAction(42,7);assert(action.calls==0);
|
||||
ERR_GameMode mode;ERR_GameMode::current=&mode;
|
||||
mode.phase=PREPARING;action.PerformAction(42,7);assert(action.calls==0);
|
||||
mode.phase=PRISON;action.PerformAction(42,7);assert(action.calls==0); // AI direct invocation blocked
|
||||
mode.phase=RADIO_SEARCH;action.nativeAllowed=false;action.PerformAction(42,7);assert(action.calls==0);
|
||||
action.nativeAllowed=true;action.PerformAction(42,7);assert(action.calls==1&&action.owner==42&&action.user==7);
|
||||
mode.phase=RADIO_DEFENCE;action.PerformAction(42,8);assert(action.calls==2&&action.user==8);
|
||||
mode.phase=PRISON;action.PerformAction(42,7);assert(action.calls==2); // revalidated at execution time
|
||||
}
|
||||
''')
|
||||
print('PASS: actual prison door execution blocks direct AI/UI bypass while locked and delegates unchanged native action once after unlock and permission approval')
|
||||
@@ -0,0 +1,74 @@
|
||||
"""Execute actual gate method and document pre-inventory guard; no native/MP claim."""
|
||||
from policy_harness import ROOT, body, run
|
||||
unlock = body(ROOT/'addons/core/Scripts/Game/ERR/ERR_GameMode.c', 'bool UnlockPrison(')
|
||||
recover = body(ROOT/'addons/core/Scripts/Game/ERR/ERR_DiscoveryService.c', 'protected void Recover(')
|
||||
# Run precisely the production admission prefix. Inventory is the observable
|
||||
# boundary: rejected bodies must never inspect or spend their carried documents.
|
||||
assert recover.count('InventoryStorageManagerComponent inventory') == 1
|
||||
prefix = recover[:recover.index('InventoryStorageManagerComponent inventory')]
|
||||
prefix = prefix.replace('protected void Recover(', 'void Recover(') + 'inventoryReads++;\n}'
|
||||
def cpp(s):
|
||||
for namespace in ('Replication','ERR_Actors','SCR_FactionManager','vector','ERR_Phase'):
|
||||
s=s.replace(namespace+'.', namespace+'::')
|
||||
for var in ('m_Mode','m_Roster','player'):
|
||||
s=s.replace(var+'.',var+'->')
|
||||
return s.replace('Players().','Players()->')
|
||||
run('''#include <cassert>
|
||||
struct Point {float x;};
|
||||
struct Faction {};
|
||||
struct Actor {bool alive=true;bool conscious=true;Faction* faction=nullptr;Point origin{0};Point GetOrigin(){return origin;}};
|
||||
using IEntity=Actor*;
|
||||
namespace Replication {bool server=true;bool IsServer(){return server;}}
|
||||
namespace ERR_Actors {bool Alive(IEntity p){return p&&p->alive;}bool CanFight(IEntity p){return Alive(p)&&p->conscious;}}
|
||||
namespace SCR_FactionManager {Faction* SGetFaction(IEntity p){return p?p->faction:nullptr;}}
|
||||
namespace vector {float DistanceXZ(Point a,Point b){return a.x>b.x?a.x-b.x:b.x-a.x;}}
|
||||
namespace ERR_Phase {constexpr int PRISON=1,RADIO_SEARCH=2;}
|
||||
struct Roster {IEntity actor=nullptr;bool connected=true,used=true,sameAccount=true; bool CurrentActor(IEntity p){return p&&p==actor&&connected&&used&&sameAccount;}};
|
||||
struct Mode {
|
||||
Roster* m_Roster=nullptr;Faction* faction=nullptr;int m_Phase=1, transitions=0;
|
||||
Roster* Players(){return m_Roster;}Faction* PlayerFaction(){return faction;}
|
||||
Point PrisonPosition(){return {0};}bool Transition(int phase){m_Phase=phase;transitions++;return true;}
|
||||
'''+cpp(unlock)+'''
|
||||
};
|
||||
struct Discovery {Mode* m_Mode=nullptr;int inventoryReads=0;
|
||||
'''+cpp(prefix)+'''
|
||||
};
|
||||
int main(){
|
||||
Faction friendly,foreign;Actor prisoner,other;prisoner.faction=&friendly;other.faction=&friendly;
|
||||
Roster roster;roster.actor=&prisoner;Mode mode;mode.m_Roster=&roster;mode.faction=&friendly;
|
||||
Discovery discovery;discovery.m_Mode=&mode;
|
||||
auto denyBoth=[&](Actor* p){mode.m_Phase=1;int before=discovery.inventoryReads, prior=mode.transitions;assert(!mode.UnlockPrison(p));discovery.Recover(p);assert(discovery.inventoryReads==before&&mode.transitions==prior);};
|
||||
denyBoth(nullptr);denyBoth(&other); // player-controlled replacement is not the round body
|
||||
roster.connected=false;denyBoth(&prisoner);roster.connected=true;
|
||||
roster.used=false;denyBoth(&prisoner);roster.used=true;
|
||||
roster.sameAccount=false;denyBoth(&prisoner);roster.sameAccount=true;
|
||||
prisoner.faction=&foreign;denyBoth(&prisoner);prisoner.faction=&friendly;
|
||||
mode.faction=nullptr;denyBoth(&prisoner);mode.faction=&friendly;
|
||||
mode.m_Roster=nullptr;denyBoth(&prisoner);mode.m_Roster=&roster;
|
||||
prisoner.alive=false;denyBoth(&prisoner);prisoner.alive=true;
|
||||
Replication::server=false;denyBoth(&prisoner);Replication::server=true;
|
||||
// Restoring the canonical reconnect relation requires no replacement life.
|
||||
mode.m_Phase=1;assert(mode.UnlockPrison(&prisoner));discovery.Recover(&prisoner);assert(discovery.inventoryReads==1);
|
||||
// Canonical living unconscious inventory still reveals documents, but no lock picking.
|
||||
mode.m_Phase=1;prisoner.conscious=false;assert(!mode.UnlockPrison(&prisoner));discovery.Recover(&prisoner);assert(discovery.inventoryReads==2);prisoner.conscious=true;
|
||||
prisoner.origin.x=15.01;assert(!mode.UnlockPrison(&prisoner));prisoner.origin.x=15;assert(mode.UnlockPrison(&prisoner));assert(!mode.UnlockPrison(&prisoner));
|
||||
discovery.m_Mode=nullptr;discovery.Recover(&prisoner);assert(discovery.inventoryReads==2);
|
||||
}
|
||||
''')
|
||||
print('PASS: actual gate and pre-inventory document guards reject noncanonical/faction/life/disconnect/server mismatches; canonical reconnect and unconscious carried intel preserved')
|
||||
|
||||
# Native probe safety contracts: actual engine behavior remains for the central run.
|
||||
probe=(ROOT/'addons/core/Scripts/Game/ERR/ERR_NativeProgressionIdentityProbe.c').read_text()
|
||||
assert probe.startswith('#ifdef WORKBENCH') and probe.rstrip().endswith('#endif')
|
||||
assert probe.count('life.Actor = null;') == 2
|
||||
for segment in probe.split('life.Actor = null;')[1:]:
|
||||
assert 'life.Actor = savedActor;' in segment
|
||||
injection=segment[:segment.index('life.Actor = savedActor;')]
|
||||
for forbidden in ('Check(', 'return', 'CallLater', 'Bind(', 'Disconnect(', 'SetControlledEntity', 'SetAffiliatedFaction', 'AccountId ='):
|
||||
assert forbidden not in injection, forbidden
|
||||
assert 'super.Recover(player);' in probe.split('life.Actor = null;')[2].split('life.Actor = savedActor;')[0]
|
||||
assert 'm_Spent.Contains(folder)' in probe and 'location.Knowledge() < 2' in probe
|
||||
assert 'GetParentSlot().GetStorage().Contains(folder)' in probe
|
||||
assert probe.index('if (!candidate)') < probe.index('ERR_NativeProgressionIdentityProbe.AttemptDocument();')
|
||||
assert 'm_Spent.Contains(candidate) && m_Spent.Count() > spent && target.Knowledge() > previousKnowledge' in probe
|
||||
print('PASS: WB-only native fault injection restores both body mutations before checks/returns and requires eligible held-document negative/positive proof; runtime pending')
|
||||
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Real addon handoff bytes, mutations and publication failures; no native engine."""
|
||||
from contextlib import redirect_stdout
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
import zipfile
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'tools'))
|
||||
import source_bundle as bundle
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class SourceBundleTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.home = Path(self.temp.name)
|
||||
self.root = self.home / 'source'
|
||||
shutil.copytree(ROOT / 'addons', self.root / 'addons')
|
||||
(self.root / 'docs').mkdir()
|
||||
for name in ('addons.json', 'vanilla-resources.json', 'acceptance.json'):
|
||||
shutil.copy2(ROOT / 'docs' / name, self.root / 'docs' / name)
|
||||
self.output = self.home / 'bundle.zip'
|
||||
|
||||
def edit(self, path, old, new):
|
||||
p = self.root / path
|
||||
text = p.read_text()
|
||||
self.assertIn(old, text)
|
||||
p.write_text(text.replace(old, new))
|
||||
|
||||
def test_deterministic_bytes_ignore_source_mtime_and_mode(self):
|
||||
first, manifest = bundle.build(self.root)
|
||||
for p in (self.root / 'addons').rglob('*'):
|
||||
if p.is_file():
|
||||
os.utime(p, (1700000000, 1700000000))
|
||||
p.chmod(0o600)
|
||||
second, again = bundle.build(self.root)
|
||||
self.assertEqual(first, second)
|
||||
self.assertEqual(manifest, again)
|
||||
with zipfile.ZipFile(io.BytesIO(first)) as archive:
|
||||
self.assertEqual(archive.namelist(), sorted(archive.namelist()))
|
||||
self.assertEqual(set(archive.namelist()), set(manifest['files']) | {'BUNDLE-MANIFEST.json'})
|
||||
self.assertEqual(json.loads(archive.read('BUNDLE-MANIFEST.json')), manifest)
|
||||
for name, digest in manifest['files'].items():
|
||||
self.assertEqual(hashlib.sha256(archive.read(name)).hexdigest(), digest)
|
||||
self.assertTrue(all(info.date_time == (1980, 1, 1, 0, 0, 0) for info in archive.infolist()))
|
||||
self.assertFalse(manifest['native_build'] or manifest['release_package'])
|
||||
self.assertIn(b'DEVELOPMENT SOURCE BUNDLE', archive.read('BUNDLE-README.md'))
|
||||
self.assertEqual(set(manifest['projects']), set(bundle.PROJECTS))
|
||||
|
||||
def test_generated_vendor_original_profile_and_evidence_are_excluded(self):
|
||||
before, _ = bundle.build(self.root)
|
||||
for name in ('addons/core/cache/private.c', 'addons/core/vendor/asset.c',
|
||||
'addons/ai/original/restricted.c', 'addons/core/.git/config',
|
||||
'addons/core/evidence/raw.c', 'addons/core/runtime/account.c',
|
||||
'addons/chat/profiles/profile.c', 'addons/core/.build/file.c',
|
||||
'addons/core/resourceDatabase.rdb', 'addons/core/packed.pak',
|
||||
'addons/core/runtime.log', 'docs/evidence/private.log'):
|
||||
p = self.root / name; p.parent.mkdir(parents=True, exist_ok=True); p.write_text('DO NOT COPY')
|
||||
after, _ = bundle.build(self.root)
|
||||
self.assertEqual(before, after)
|
||||
|
||||
def test_changed_resource_changes_archive_and_manifest(self):
|
||||
first, manifest = bundle.build(self.root)
|
||||
name = 'addons/core/Scripts/Game/ERR/ERR_Actors.c'
|
||||
p = self.root / name; p.write_text(p.read_text() + '\n// reviewed successor edit\n')
|
||||
second, updated = bundle.build(self.root)
|
||||
self.assertNotEqual(first, second)
|
||||
self.assertNotEqual(manifest['files'][name], updated['files'][name])
|
||||
|
||||
def test_identity_and_external_dependency_refused(self):
|
||||
name = 'addons/core/addon.gproj'
|
||||
self.edit(name, '2528466DE220D91C', '1111111111111111')
|
||||
with self.assertRaisesRegex(ValueError, 'identity/dependency'): bundle.build(self.root)
|
||||
self.edit(name, '1111111111111111', '2528466DE220D91C')
|
||||
self.edit(name, '58D0FB3206B6F859', '5F16D7E4A1CBE075')
|
||||
with self.assertRaisesRegex(ValueError, 'identity/dependency'): bundle.build(self.root)
|
||||
|
||||
def test_missing_transitive_project_refused(self):
|
||||
shutil.rmtree(self.root / 'addons/intel')
|
||||
with self.assertRaisesRegex(ValueError, 'identity/dependency'): bundle.build(self.root)
|
||||
|
||||
def test_resource_dependency_outside_closure_refused(self):
|
||||
p = self.root / 'addons/core/Scripts/Game/ERR/BadReference.c'
|
||||
meta = next((self.root / 'addons/map').rglob('*.meta')).read_text()
|
||||
import re
|
||||
resource = re.search(r'Name "([^"\n]+)"', meta)[1]
|
||||
p.write_text('class ERR_Test { ResourceName asset = "' + resource + '"; }')
|
||||
with self.assertRaisesRegex(ValueError, 'dependency closure'): bundle.build(self.root)
|
||||
|
||||
def test_metadata_and_unknown_asset_format_refused(self):
|
||||
(self.root / 'addons/core/Prefabs/ERR/Unknown.xob').write_bytes(b'vendor')
|
||||
with self.assertRaisesRegex(ValueError, 'unreviewed'): bundle.build(self.root)
|
||||
(self.root / 'addons/core/Prefabs/ERR/Unknown.xob').unlink()
|
||||
next((self.root / 'addons/core').rglob('*.et.meta')).unlink()
|
||||
with self.assertRaisesRegex(ValueError, 'metadata|unresolved'): bundle.build(self.root)
|
||||
|
||||
def test_source_symlink_and_nonregular_refused(self):
|
||||
p = self.root / 'addons/core/Scripts/Game/ERR/Link.c'
|
||||
p.symlink_to('/etc/hostname')
|
||||
with self.assertRaisesRegex(ValueError, 'symlink'): bundle.build(self.root)
|
||||
p.unlink(); os.mkfifo(p)
|
||||
with self.assertRaisesRegex(ValueError, 'nonregular'): bundle.build(self.root)
|
||||
|
||||
def test_unsafe_or_case_colliding_member_refused(self):
|
||||
p = self.root / 'addons/core/Scripts/Game/ERR/escape\\outside.c'; p.write_text('')
|
||||
with self.assertRaisesRegex(ValueError, 'unsafe'): bundle.build(self.root)
|
||||
p.unlink()
|
||||
(self.root / 'addons/core/Scripts/Game/ERR/err_actors.c').write_text('')
|
||||
with self.assertRaisesRegex(ValueError, 'collision'): bundle.build(self.root)
|
||||
|
||||
def test_validation_failure_with_output_argument_leaves_no_archive(self):
|
||||
self.edit('addons/core/addon.gproj', '2528466DE220D91C', '1111111111111111')
|
||||
real_build = bundle.build
|
||||
with patch.object(bundle, 'build', side_effect=lambda **kw: real_build(self.root, **kw)), redirect_stdout(io.StringIO()):
|
||||
self.assertEqual(bundle.main(['--output', str(self.output)]), 1)
|
||||
self.assertFalse(self.output.exists())
|
||||
self.assertEqual(list(self.home.glob('.erri-bundle-*')), [])
|
||||
|
||||
def test_changed_tree_during_collection_refused(self):
|
||||
real_inventory = bundle.inventory
|
||||
calls = 0
|
||||
def changing_inventory(root):
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
if calls == 2:
|
||||
(root / 'addons/core/Scripts/Game/ERR/Late.c').write_text('// late addition')
|
||||
return real_inventory(root)
|
||||
with patch.object(bundle, 'inventory', side_effect=changing_inventory):
|
||||
with self.assertRaisesRegex(ValueError, 'tree changed'): bundle.build(self.root)
|
||||
self.assertFalse(self.output.exists())
|
||||
|
||||
def test_atomic_new_output_no_overwrite_or_symlink(self):
|
||||
content, _ = bundle.build(self.root)
|
||||
bundle.write_new(self.output, content, self.root)
|
||||
self.assertEqual(self.output.read_bytes(), content)
|
||||
with self.assertRaises(ValueError): bundle.write_new(self.output, b'bad', self.root)
|
||||
self.assertEqual(self.output.read_bytes(), content)
|
||||
link = self.home / 'link.zip'; link.symlink_to(self.output)
|
||||
with self.assertRaises(ValueError): bundle.write_new(link, b'bad', self.root)
|
||||
self.assertEqual(self.output.read_bytes(), content)
|
||||
|
||||
def test_publication_failure_removes_temporary_and_leaves_no_output(self):
|
||||
with patch.object(bundle.os, 'link', side_effect=OSError('publication failure')):
|
||||
with self.assertRaises(OSError): bundle.write_new(self.output, b'bytes', self.root)
|
||||
self.assertFalse(self.output.exists())
|
||||
self.assertEqual(list(self.home.glob('.erri-bundle-*')), [])
|
||||
|
||||
def test_default_preview_creates_nothing_and_release_uses_real_pending_audit(self):
|
||||
real_build = bundle.build
|
||||
with patch.object(bundle, 'build', side_effect=lambda **kw: real_build(self.root, **kw)), redirect_stdout(io.StringIO()) as out:
|
||||
self.assertEqual(bundle.main([]), 0)
|
||||
self.assertTrue(json.loads(out.getvalue())['preview'])
|
||||
self.assertFalse(self.output.exists())
|
||||
with patch.object(bundle, 'build', side_effect=lambda **kw: real_build(self.root, **kw)), redirect_stdout(io.StringIO()) as out:
|
||||
self.assertEqual(bundle.main(['--mode', 'release', '--output', str(self.output)]), 1)
|
||||
self.assertIn('cases.twelve_players: status is pending', out.getvalue())
|
||||
self.assertFalse(self.output.exists())
|
||||
self.assertEqual(list(self.home.glob('.erri-bundle-*')), [])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Vanilla control command/profile/readiness tests; never starts engine or audio."""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]/'tools'))
|
||||
import headless_game as runner
|
||||
|
||||
BASE = '''00 ENGINE : Loaded addons:
|
||||
00 ENGINE : gproj: './addons/core/core.gproj' guid: '5614BBCCBB55ED1C'
|
||||
00 ENGINE : gproj: './addons/data/ArmaReforger.gproj' guid: '58D0FB3206B6F859'
|
||||
00 SCRIPT : Module: GameLib; defines: "PLATFORM_WINDOWS"
|
||||
00 SCRIPT : Module: Game; defines: "PLATFORM_WINDOWS"
|
||||
00 WORLD : Entities load 'worlds/MainMenuWorld/MainMenuWorld.ent'
|
||||
00 DEFAULT : Entered main menu.
|
||||
'''
|
||||
|
||||
class VanillaControl(unittest.TestCase):
|
||||
def test_command_has_no_successor_path_addons_helper_server_or_skip(self):
|
||||
with patch.object(Path, 'read_text', side_effect=AssertionError('Control must not inspect successor project')):
|
||||
command=runner.command_for(Path('/source'),Path('/steam'),Path('/private-prefix'),Path('/private-profile'),runner.CONTROL)
|
||||
for arg in ('-addonsDir','-addons','-errOrdinaryScenario','-world','-server','-config','-noSplash'):
|
||||
self.assertNotIn(arg,command)
|
||||
self.assertFalse(any('/source' in part for part in command))
|
||||
self.assertIn('-noBackend',command);self.assertIn('-noSound',command)
|
||||
self.assertEqual(command[:3],['gamescope','--backend','headless'])
|
||||
self.assertEqual(command[command.index('-profile')+1],'Z:/private-profile')
|
||||
self.assertTrue(any(part.endswith('/ArmaReforgerSteam.exe') for part in command))
|
||||
def test_unknown_project_refused(self):
|
||||
with self.assertRaises(ValueError): runner.command_for(Path('/s'),Path('/s'),Path('/p'),Path('/p'),'other')
|
||||
def test_profile_must_be_fresh_or_control_owned(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
p=Path(tmp)/'profile';runner.validate_control_profile(p);self.assertFalse(p.exists())
|
||||
p.mkdir();(p/runner.PROFILE_MARKER).write_text(json.dumps(runner.PROFILE_ID))
|
||||
with self.assertRaises(ValueError):runner.validate_control_profile(p)
|
||||
(p/runner.CONTROL_MARKER).write_text(json.dumps(runner.CONTROL_ID));runner.validate_control_profile(p)
|
||||
(p/runner.CONTROL_MARKER).unlink();(p/runner.CONTROL_MARKER).symlink_to(p/runner.PROFILE_MARKER)
|
||||
with self.assertRaises(ValueError):runner.validate_control_profile(p)
|
||||
def test_strict_main_menu_control_not_mission_success(self):
|
||||
report=runner.game_evidence(BASE,runner.CONTROL)
|
||||
self.assertTrue(report['ready']);self.assertTrue(report['control_only'])
|
||||
for key in ('scenario_header_verified','local_participant_verified','normal_mission_ready'):
|
||||
self.assertFalse(report[key])
|
||||
self.assertEqual(report['assertions_passed'],0)
|
||||
def test_available_inventory_is_not_loaded_addon_evidence(self):
|
||||
self.assertFalse(runner.vanilla_evidence(BASE.replace('Loaded addons:','Available addons:'))['ready'])
|
||||
self.assertFalse(runner.vanilla_evidence(BASE.replace("00 ENGINE : gproj: './addons/core/core.gproj' guid: '5614BBCCBB55ED1C'\n",''))['ready'])
|
||||
def test_unexpected_loaded_addon_fails_even_with_clean_menu(self):
|
||||
foreign=BASE.replace("00 SCRIPT : Module: GameLib", "00 ENGINE : gproj: '/addon/addon.gproj' guid: '2528466DE220D91C'\n00 SCRIPT : Module: GameLib")
|
||||
report=runner.vanilla_evidence(foreign);self.assertTrue(report['failed']);self.assertFalse(report['ready'])
|
||||
def test_no_readiness_without_each_module_world_and_menu(self):
|
||||
for omitted in ('Module: GameLib;', 'Module: Game;', "Entities load 'worlds/MainMenuWorld/MainMenuWorld.ent'", 'Entered main menu.'):
|
||||
self.assertFalse(runner.vanilla_evidence(BASE.replace(omitted,'absent'))['ready'],omitted)
|
||||
def test_errors_wrong_defines_and_successor_activity_fail(self):
|
||||
for addition in ("00 GUI (E): Unknown class 'SCR_WidgetExportRuleRoot'",'ERR SMOKE PASS: anything','ERR ORDINARY: ready','Rereintroduced [mission]: phase 0 -> 1'):
|
||||
report=runner.vanilla_evidence(BASE+addition);self.assertTrue(report['failed']);self.assertFalse(report['ready'])
|
||||
for define in ('WORKBENCH','ENF_WB'):
|
||||
self.assertTrue(runner.vanilla_evidence(BASE.replace('PLATFORM_WINDOWS','PLATFORM_WINDOWS,'+define))['failed'])
|
||||
def test_control_bound_refusal_never_launches_or_configures_audio(self):
|
||||
args=SimpleNamespace(steam=Path('/steam'),compat=Path('/prefix'),profile=Path('/profile'),timeout=121,settle=15,failure_grace=0,project=runner.CONTROL)
|
||||
with patch.object(runner,'assert_no_game'),patch.object(runner.subprocess,'Popen',side_effect=AssertionError('launch')),patch.object(runner.subprocess,'run',side_effect=AssertionError('audio/process')):
|
||||
with self.assertRaisesRegex(ValueError,'bounded'):runner.run(args)
|
||||
def test_latched_control_error_cannot_be_overwritten_by_later_menu(self):
|
||||
monitor=runner.StartupMonitor(0,120,15,5)
|
||||
self.assertIsNone(monitor.observe(runner.vanilla_evidence(BASE+'00 GUI (E): bad'),1))
|
||||
self.assertEqual(monitor.observe(runner.vanilla_evidence(BASE),6),'game-failed')
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
Reference in New Issue
Block a user