Harden base progression and cleanup; validate finite physical resupply

This commit is contained in:
2026-09-19 10:39:39 +02:00
parent 40a474934c
commit 7c53b4bb81
377 changed files with 51882 additions and 92 deletions
+69
View File
@@ -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()
+91
View File
@@ -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')
+28
View File
@@ -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')
+106
View File
@@ -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()
+50
View File
@@ -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')
+130
View File
@@ -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 &registry;}};
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')
+71
View File
@@ -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()
+77
View File
@@ -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')
+92 -1
View File
@@ -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)
+78
View File
@@ -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')
+74
View File
@@ -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')
+171
View File
@@ -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()
+71
View File
@@ -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()