Advance standalone admission, finite artillery and native validation
This commit is contained in:
@@ -18,4 +18,4 @@ int main(){for(int site=0;site<4;site++)for(int order=0;order<10;order++){
|
||||
}
|
||||
}}
|
||||
''')
|
||||
print('PASS: migrated salvo offsets are deterministic, spread and bounded (firing controller pending)')
|
||||
print('PASS: migrated salvo offsets are deterministic, spread and bounded (native firing and complete salvo qualification are separate)')
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
import contextlib
|
||||
import importlib.util
|
||||
import io
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
spec = importlib.util.spec_from_file_location('dedicated_native', Path(__file__).parents[1] / 'tools/dedicated_native.py')
|
||||
runner = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(runner)
|
||||
|
||||
class DedicatedNativeTests(unittest.TestCase):
|
||||
def probe(self, namespace='isolated', interfaces=None, routes=None, ipv6=''):
|
||||
interfaces = interfaces if interfaces is not None else [(1, 'lo')]
|
||||
routes = routes if routes is not None else 'Iface Destination Gateway\n'
|
||||
def content(path, *args):
|
||||
return io.StringIO(ipv6 if path.endswith('ipv6_route') else routes)
|
||||
with patch('os.readlink', return_value=namespace), patch('socket.if_nameindex', return_value=interfaces), patch('builtins.open', side_effect=content), patch.object(sys, 'argv', ['probe', 'host', '/server']), patch('os.execv') as launch, contextlib.redirect_stdout(io.StringIO()):
|
||||
try:
|
||||
exec(runner.PROBE, {})
|
||||
except AssertionError:
|
||||
self.assertFalse(launch.called)
|
||||
return False
|
||||
launch.assert_called_once_with('/server', ['/server'])
|
||||
return True
|
||||
|
||||
def test_isolation_gate_executes_only_private_loopback(self):
|
||||
self.assertTrue(self.probe())
|
||||
self.assertFalse(self.probe(namespace='host'))
|
||||
self.assertFalse(self.probe(interfaces=[(1, 'lo'), (2, 'eth0')]))
|
||||
self.assertFalse(self.probe(routes='Iface Destination\neth0 00000000\n'))
|
||||
self.assertFalse(self.probe(ipv6='0000 eth0\n'))
|
||||
self.assertTrue(self.probe(ipv6='0000 lo\n'))
|
||||
|
||||
def test_vanilla_control_has_no_successor_or_custom_systems(self):
|
||||
args=runner.engine_args('vanilla-everon')
|
||||
self.assertNotIn('-addons',args)
|
||||
self.assertNotIn('-addonsDir',args)
|
||||
self.assertNotIn('-worldSystemsConfig',args)
|
||||
self.assertEqual(args[-2:],['-server','Missions/21_GM_Eden.conf'])
|
||||
self.assertEqual(args[0],'/tmp/runtime/ArmaReforgerServer')
|
||||
|
||||
def test_manifest_detects_mutation_and_refuses_links(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root=Path(directory); target=root/'a.c'; target.write_text('first')
|
||||
old=runner.manifest(root); target.write_text('second')
|
||||
self.assertNotEqual(old,runner.manifest(root))
|
||||
(root/'linked').symlink_to(target)
|
||||
with self.assertRaises(ValueError): runner.manifest(root)
|
||||
|
||||
def test_routes_do_not_mix_public_config_or_client_flags(self):
|
||||
for route in ['header','diag-world','vanilla-everon']:
|
||||
args=runner.engine_args(route)
|
||||
self.assertNotIn('-config',args)
|
||||
self.assertNotIn('-client',args)
|
||||
self.assertIn('-noBackend',args)
|
||||
self.assertNotIn('-rpl-validation-rdb-disable',args)
|
||||
normal=runner.engine_args('header')
|
||||
self.assertEqual(normal[0],'/tmp/runtime/ArmaReforgerServer')
|
||||
self.assertTrue(normal[normal.index('-server')+1].endswith('ERR_Everon_Base.conf'))
|
||||
self.assertNotIn('-worldSystemsConfig',normal)
|
||||
self.assertIn('-worldSystemsConfig',runner.engine_args('diag-world'))
|
||||
|
||||
if __name__ == '__main__': unittest.main()
|
||||
@@ -0,0 +1,104 @@
|
||||
"""Synthetic files test no-launch planning; never execute a server or client."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]/'tools'))
|
||||
import dedicated_preflight as tool
|
||||
|
||||
|
||||
class DedicatedPreflightTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.tmp.cleanup)
|
||||
root = Path(self.tmp.name)
|
||||
self.source, self.runtime = root/'source', root/'runtime'
|
||||
self.runtime.mkdir()
|
||||
for project, header in tool.HEADERS.items():
|
||||
addon = self.source/'addons'/project
|
||||
path = addon/header
|
||||
path.parent.mkdir(parents=True)
|
||||
(addon/'addon.gproj').write_text('GameProject { GUID "2528466DE220D91C" }')
|
||||
path.write_text('SCR_MissionHeader { World "'+tool.WORLD+'" SystemsConfig "'+tool.SYSTEMS+'" }')
|
||||
path.with_suffix('.conf.meta').write_text('MetaFileClass { Name "{AD62322D5213C440}'+header+'" }')
|
||||
(self.runtime/'runtime-origin.json').write_text(json.dumps({'app_id':1874900,'purpose':'private anonymous Reforger dedicated server preparation; no server launched'}))
|
||||
|
||||
def plan(self, **kwargs):
|
||||
return tool.build_plan(self.source,self.runtime,**kwargs)
|
||||
|
||||
def installed(self, app='1874900', architecture=62):
|
||||
directory=self.runtime/'server';directory.mkdir(exist_ok=True)
|
||||
binary=directory/'ArmaReforgerServer'
|
||||
data=bytearray(20);data[:6]=b'\x7fELF\x02\x01';data[18:20]=architecture.to_bytes(2,'little')
|
||||
binary.write_bytes(data);binary.chmod(0o700)
|
||||
manifests=directory/'steamapps';manifests.mkdir(exist_ok=True)
|
||||
(manifests/'appmanifest_1874900.acf').write_text('"AppState" { "appid" "'+app+'" "buildid" "synthetic-build" "StateFlags" "4" }')
|
||||
return binary
|
||||
|
||||
def test_missing_runtime_still_prepares_strict_loopback_review(self):
|
||||
result,config,_=self.plan()
|
||||
self.assertFalse(result['launch_allowed']);self.assertFalse(result['multiplayer_passed'])
|
||||
self.assertEqual(result['genuine_clients_verified'],0)
|
||||
self.assertIn('missing or incomplete',result['blockers'][0])
|
||||
self.assertEqual(config['bindAddress'],'127.0.0.1')
|
||||
self.assertEqual(config['publicAddress'],'127.0.0.1')
|
||||
self.assertFalse(config['game']['visible'])
|
||||
self.assertTrue(config['game']['gameProperties']['fastValidation'])
|
||||
self.assertTrue(config['game']['gameProperties']['battlEye'])
|
||||
self.assertTrue(config['operating']['disableCrashReporter'])
|
||||
self.assertNotIn('rcon',config);self.assertNotIn('a2s',config)
|
||||
|
||||
def test_binary_is_inspected_never_executed_and_cannot_establish_mp(self):
|
||||
self.installed()
|
||||
with patch('subprocess.run',side_effect=AssertionError('must not run')),patch('subprocess.Popen',side_effect=AssertionError('must not spawn')):
|
||||
result,_,_=self.plan()
|
||||
runtime=result['runtime']
|
||||
self.assertTrue(runtime['binary_elf_x86_64']);self.assertTrue(runtime['install_complete'])
|
||||
self.assertEqual(runtime['steam_build_id'],'synthetic-build')
|
||||
self.assertIsNone(runtime['engine_version']);self.assertFalse(runtime['native_runtime_executed'])
|
||||
self.assertFalse(result['launch_allowed']);self.assertTrue(result['blockers'])
|
||||
|
||||
def test_wrong_app_rejected_wrong_arch_incomplete(self):
|
||||
self.installed(app='1890870')
|
||||
with self.assertRaisesRegex(ValueError,'stable dedicated'):self.plan()
|
||||
self.installed(architecture=183)
|
||||
result,_,_=self.plan();self.assertFalse(result['runtime']['binary_elf_x86_64'])
|
||||
self.assertIn('missing or incomplete',result['blockers'][0])
|
||||
|
||||
def test_symlink_to_existing_server_rejected(self):
|
||||
binary=self.installed();target=self.runtime/'other-server';binary.rename(target);binary.symlink_to(target)
|
||||
with self.assertRaisesRegex(ValueError,'symlink'):self.plan()
|
||||
|
||||
def test_header_and_port_validation(self):
|
||||
for port in (0,1023,65536):
|
||||
with self.assertRaisesRegex(ValueError,'unprivileged'):self.plan(port=port)
|
||||
header=self.source/'addons/core'/tool.HEADERS['core']
|
||||
header.write_text(header.read_text().replace(tool.SYSTEMS,'wrong'))
|
||||
with self.assertRaisesRegex(ValueError,'SystemsConfig'):self.plan()
|
||||
|
||||
def test_project_selection_and_source_snapshot_change(self):
|
||||
before,config,_=self.plan(project='chat')
|
||||
self.assertEqual(before['scenario']['project'],'chat')
|
||||
self.assertTrue(config['game']['scenarioId'].endswith(tool.HEADERS['chat']))
|
||||
(self.source/'addons/core/new.c').write_text('// independent new source')
|
||||
after,_,_=self.plan(project='chat')
|
||||
self.assertNotEqual(before['source_manifest_sha256'],after['source_manifest_sha256'])
|
||||
|
||||
def test_write_new_private_review_only_preserves_existing_evidence(self):
|
||||
result,config,manifest=self.plan();destination=self.runtime/'review'
|
||||
tool.write_plan(destination,result,config,manifest)
|
||||
self.assertEqual(json.loads((destination/'server-config.review.json').read_text()),config)
|
||||
self.assertFalse(json.loads((destination/'preflight.json').read_text())['listener_started'])
|
||||
self.assertEqual(tool.digest(destination/'source-manifest.json'),result['source_manifest_sha256'])
|
||||
with self.assertRaisesRegex(ValueError,'never overwritten'):tool.write_plan(destination,result,config,manifest)
|
||||
with self.assertRaisesRegex(ValueError,'direct child'):tool.write_plan(self.source/'review',result,config,manifest)
|
||||
|
||||
def test_unmarked_runtime_and_source_overlap_refused(self):
|
||||
result,config,manifest=self.plan();(self.runtime/'runtime-origin.json').unlink()
|
||||
with self.assertRaisesRegex(ValueError,'origin marker'):tool.write_plan(self.runtime/'review',result,config,manifest)
|
||||
with self.assertRaisesRegex(ValueError,'separate'):tool.build_plan(self.source,self.source/'runtime')
|
||||
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
@@ -179,7 +179,7 @@ class GameRunnerTests(unittest.TestCase):
|
||||
|
||||
def test_ready_requires_game_module_without_workbench_and_normal_phase(self):
|
||||
module = 'SCRIPT : Module: Game; loaded 12x files; defines: "PLATFORM_WINDOWS,ENABLE_DIAG"; CRC32: a\n'
|
||||
phase = 'SCRIPT : Rereintroduced [mission]: phase 0 -> 1\nSCRIPT : ERR ORDINARY: header verified project=core systems=MissionSystems\n'
|
||||
phase = 'SCRIPT : Rereintroduced [mission]: phase 0 -> 1\nSCRIPT : ERR ORDINARY: header verified project=core systems=MissionSystems\nSCRIPT : ERR ORDINARY: local participant verified project=core phase=prison\n'
|
||||
self.assertTrue(runner.game_evidence(module+phase)['ready'])
|
||||
self.assertFalse(runner.game_evidence(phase)['ready'])
|
||||
self.assertFalse(runner.game_evidence(module)['ready'])
|
||||
@@ -190,8 +190,8 @@ class GameRunnerTests(unittest.TestCase):
|
||||
def test_startup_requires_selected_header_and_actual_systems_marker(self):
|
||||
module = 'SCRIPT : Module: Game; defines: "PLATFORM_WINDOWS"; CRC32: a\n'
|
||||
phase = 'SCRIPT : Rereintroduced [mission]: phase 0 -> 1\n'
|
||||
core = 'SCRIPT : ERR ORDINARY: header verified project=core systems=MissionSystems\n'
|
||||
chat = 'SCRIPT : ERR ORDINARY: header verified project=chat systems=MissionSystems\n'
|
||||
core = 'SCRIPT : ERR ORDINARY: header verified project=core systems=MissionSystems\nSCRIPT : ERR ORDINARY: local participant verified project=core phase=prison\n'
|
||||
chat = 'SCRIPT : ERR ORDINARY: header verified project=chat systems=MissionSystems\nSCRIPT : ERR ORDINARY: local participant verified project=chat phase=prison\n'
|
||||
self.assertFalse(runner.game_evidence(module+phase, 'core')['ready'])
|
||||
self.assertFalse(runner.game_evidence(module+phase+core, 'chat')['ready'])
|
||||
self.assertTrue(runner.game_evidence(module+phase+chat, 'chat')['ready'])
|
||||
@@ -202,6 +202,18 @@ class GameRunnerTests(unittest.TestCase):
|
||||
self.assertTrue(result['failed'])
|
||||
self.assertFalse(result['ready'])
|
||||
|
||||
def test_world_without_normal_local_admission_is_not_ready(self):
|
||||
world = ('SCRIPT : Module: Game; defines: "PLATFORM_WINDOWS"; CRC32: a\n'
|
||||
'SCRIPT : Rereintroduced [mission]: phase 0 -> 1\n'
|
||||
'SCRIPT : ERR ORDINARY: header verified project=core systems=MissionSystems\n')
|
||||
result = runner.game_evidence(world, 'core')
|
||||
self.assertTrue(result['scenario_header_verified'])
|
||||
self.assertFalse(result['local_participant_verified'])
|
||||
self.assertFalse(result['ready'])
|
||||
self.assertFalse(result['failed']) # incomplete admission may still arrive
|
||||
self.assertFalse(runner.game_evidence(world+'ERR ORDINARY: local participant verified project=chat phase=prison\n', 'core')['ready'])
|
||||
self.assertTrue(runner.game_evidence(world+'ERR ORDINARY: local participant verified project=core phase=prison\n', 'core')['ready'])
|
||||
|
||||
def test_no_engine_or_audio_spawn_during_import_and_process_refusal(self):
|
||||
with patch.object(runner, 'assert_no_game', side_effect=ValueError('existing game')):
|
||||
with patch.object(runner.subprocess, 'Popen') as spawn, patch.object(runner.subprocess, 'run') as commands:
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Execute actual resolver/context/latch bodies with opaque native handles.
|
||||
UUID generation/nullness and native session/controller APIs are substituted;
|
||||
this is not authentication, native GenV4 or multiplayer evidence.
|
||||
"""
|
||||
from policy_harness import ROOT, body, run
|
||||
import re
|
||||
source=ROOT/'addons/core/Scripts/Game/ERR/ERR_LocalAdmission.c'
|
||||
s=source.read_text().split('\nmodded class ERR_GameMode')[0]
|
||||
s=s[s.index('class ERR_LocalAdmissionRules'):]
|
||||
s=s.replace('class ERR_LocalAdmissionRules','struct ERR_LocalAdmissionRules').replace('class ERR_LocalAdmission\n','struct ERR_LocalAdmission\n')
|
||||
s=s.replace('\n}\n','\n};\n').replace('protected ','')
|
||||
s=s.replace('void ERR_LocalAdmission(', 'ERR_LocalAdmission(')
|
||||
s=s.replace('ERR_GameMode', 'Mode').replace('SCR_PlayerController', 'Controller').replace('PlayerManager', 'Manager')
|
||||
s=re.sub(r'\b(Mode|Controller|Manager) (mode|controller|manager|m_Mode|m_Controller)\b',r'\1* \2',s)
|
||||
s=s.replace('Mode.Get()', 'Mode::Get()').replace('RplMode.None','0')
|
||||
s=s.replace('Replication.IsServer()', 'isServer').replace('RplSession.Mode()', 'sessionMode')
|
||||
s=s.replace('GetGame().', 'GetGame()->').replace('manager.', 'manager->').replace('controller.', 'controller->')
|
||||
s=s.replace('System.GetCLIParam', 'System::GetCLIParam').replace('string project;', 'std::string project;')
|
||||
s=s.replace('bool connected, sameManager;', 'bool connected=false, sameManager=false;').replace('int controllerId;', 'int controllerId=0;')
|
||||
s=s.replace('ERR_LocalAdmissionRules.', 'ERR_LocalAdmissionRules::')
|
||||
s=s.replace('UUID.NULL_UUID','UUID::NULL_UUID').replace('UUID.GenV4()', 'UUID::GenV4()').replace('null','nullptr')
|
||||
# Enforce initializes managed fields; retain that contract in this C++ model.
|
||||
s=s.replace('Mode* m_Mode;', 'Mode* m_Mode=nullptr;').replace('Controller* m_Controller;', 'Controller* m_Controller=nullptr;')
|
||||
s=re.sub(r'\b(bool|int) (m_\w+);',r'\1 \2=0;',s)
|
||||
run('''#include <cassert>
|
||||
#include <string>
|
||||
int generated=0;bool isServer=true;int sessionMode=0;
|
||||
struct UUID {int value=0;bool IsNull(){return value<=0;}bool operator==(UUID other){return value==other.value;}static UUID NULL_UUID;static UUID GenV4(){return UUID{++generated+100};}};
|
||||
UUID UUID::NULL_UUID{0};
|
||||
struct Controller {int id=1;int GetPlayerId(){return id;}};
|
||||
struct Manager {int count=1;bool connected=true;Controller* controller=nullptr;int GetPlayerCount(){return count;}bool IsPlayerConnected(int id){return connected;}Controller* GetPlayerController(int id){return controller;}};
|
||||
struct Mode {static Mode* current;static Mode* Get(){return current;}};Mode* Mode::current=nullptr;
|
||||
struct Game {Manager* manager=nullptr;Controller* local=nullptr;Manager* GetManager(){return manager;}Controller* GetPlayerController(){return local;}};
|
||||
Game* game=nullptr;Game* GetGame(){return game;}void Print(const char*){}
|
||||
namespace System {bool GetCLIParam(const char*,std::string&){return false;}}
|
||||
template<class... Args> void PrintFormat(Args... args) {}
|
||||
'''+s+'''
|
||||
int main(){
|
||||
// Exact None only; every context condition independently matters.
|
||||
for(int mode=-1;mode<5;++mode)for(int count=0;count<3;++count)for(int flags=0;flags<16;++flags)
|
||||
assert(ERR_LocalAdmissionRules::Context(flags&1,mode,count,flags&2,flags&4,flags&8,1,1)==(mode==0&&count==1&&flags==15));
|
||||
assert(!ERR_LocalAdmissionRules::Context(true,0,1,true,true,true,0,0));
|
||||
assert(!ERR_LocalAdmissionRules::Context(true,0,1,true,true,true,1,2));
|
||||
Mode round,nextRound;Mode::current=&round;Controller owner,replacement;Manager manager;manager.controller=&owner;Game world{&manager,&owner};game=&world;
|
||||
ERR_LocalAdmission admission(&round);
|
||||
UUID selected=admission.Resolve(&round,1,&owner,UUID{0});assert(!selected.IsNull()&&admission.LocalKey()&&generated==1);
|
||||
assert(admission.Resolve(&round,1,&owner,UUID{0})==selected&&generated==1);
|
||||
assert(admission.Resolve(&round,1,&owner,UUID{55})==selected&&generated==1); // late BI arrival cannot make fresh life
|
||||
assert(admission.Validate());
|
||||
assert(!admission.Disconnected(2));assert(admission.Validate());
|
||||
assert(admission.Disconnected(1));assert(!admission.Validate());
|
||||
assert(admission.Resolve(&round,1,&owner,UUID{55}).IsNull()); // irreversible within this mode
|
||||
ERR_LocalAdmission native(&round);assert(native.Resolve(&round,1,&owner,UUID{55})==UUID{55});
|
||||
assert(!native.LocalKey()&&native.Resolve(&round,1,&owner,UUID{0})==UUID{55}&&generated==1);
|
||||
native.Stop();assert(native.Resolve(&round,1,&owner,UUID{0}).IsNull());assert(!native.Validate());
|
||||
// Replacement, mode switch, manager/local mismatch and additional connections latch.
|
||||
for(int fault=0;fault<6;++fault){
|
||||
sessionMode=0;Mode::current=&round;manager.controller=&owner;world.local=&owner;manager.count=1;manager.connected=true;
|
||||
ERR_LocalAdmission a(&round);assert(!a.Resolve(&round,1,&owner,UUID{0}).IsNull());
|
||||
if(fault==0)sessionMode=1;
|
||||
if(fault==1)manager.controller=&replacement;
|
||||
if(fault==2)world.local=&replacement;
|
||||
if(fault==3)manager.count=2;
|
||||
if(fault==4)manager.connected=false;
|
||||
if(fault==5)Mode::current=&nextRound;
|
||||
assert(!a.Validate());
|
||||
sessionMode=0;Mode::current=&round;manager.controller=&owner;world.local=&owner;manager.count=1;manager.connected=true;
|
||||
assert(!a.Validate());assert(a.Resolve(&round,1,&owner,UUID{88}).IsNull());
|
||||
}
|
||||
// A newly created round owns a fresh key. Old resolver cannot be reused.
|
||||
Mode::current=&nextRound;ERR_LocalAdmission next(&nextRound);UUID nextKey=next.Resolve(&nextRound,1,&owner,UUID{0});assert(!(nextKey==selected));
|
||||
assert(admission.Resolve(&nextRound,1,&owner,UUID{0}).IsNull());
|
||||
// Fresh network modes remain native even on an absent controller/local world,
|
||||
// after Stop, on null backend data, and with changing native identities.
|
||||
for(int mode: {1,2,3}){
|
||||
sessionMode=mode;int before=generated;ERR_LocalAdmission network(&nextRound);game=nullptr;
|
||||
assert(network.Resolve(&nextRound,9,nullptr,UUID{41})==UUID{41});
|
||||
assert(network.Resolve(&nextRound,9,nullptr,UUID{0}).IsNull());
|
||||
network.Stop();assert(network.Validate());
|
||||
assert(network.Resolve(&nextRound,9,nullptr,UUID{42})==UUID{42});
|
||||
assert(!network.Selected()&&!network.LocalKey()&&generated==before);game=&world;
|
||||
}
|
||||
// Incomplete local context cannot generate a key and cannot later self-repair.
|
||||
sessionMode=0;manager.count=0;int before=generated;ERR_LocalAdmission invalid(&nextRound);
|
||||
assert(invalid.Resolve(&nextRound,1,&owner,UUID{0}).IsNull()&&generated==before);
|
||||
manager.count=1;assert(invalid.Resolve(&nextRound,1,&owner,UUID{0}).IsNull());
|
||||
}
|
||||
''')
|
||||
print('PASS: actual local admission context/selection/latch, same-round late identity, replacement/mode failure, fresh round and untouched network path')
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Execute actual aim-equivalence and renewal control flow with synthetic API data.
|
||||
|
||||
No fake native ballistic result is represented as a measured firing result.
|
||||
"""
|
||||
import re
|
||||
from policy_harness import ROOT, body, run
|
||||
r=ROOT/'addons/ai/Scripts/Game/ERR'
|
||||
solution=r/'ERR_MortarAimSolution.c'; firing=r/'ERR_MortarFiring.c'
|
||||
eq=body(solution,'static bool Equivalent(').replace('vector.Distance','vector::Distance').replace('Math.AbsFloat','std::fabs')
|
||||
matches=body(solution,'bool Matches(').replace('ERR_MortarBattery battery','ERR_MortarBattery* battery').replace('ERR_HQReport report','ERR_HQReport* report').replace('battery.','battery->')
|
||||
fresh=body(r/'ERR_MortarPrediction.c','static bool Fresh(').replace('ERR_HQReport report','ERR_HQReport* report').replace('report.','report->')
|
||||
renew=body(firing,'bool ERR_RevalidateAim(')
|
||||
for typ in ['ERR_MortarBattery','ERR_MortarSystem','ERR_HQReport','ERR_MortarAimSolution']:
|
||||
renew=re.sub(r'\b'+typ+r' (battery|system|report|commanded|updated)\b',typ+r'* \1',renew)
|
||||
renew=renew.replace('IEntity gun;','IEntity gun=nullptr;').replace('ResourceName ammo;','std::string ammo;')
|
||||
for name in ['battery','system','report','commanded','updated']:renew=renew.replace(name+'.',name+'->')
|
||||
for name in ['ERR_MortarBattery','ERR_MortarAimRules','ERR_MortarPrediction']:renew=renew.replace(name+'.',name+'::')
|
||||
# Native trace formatting is deliberately inert. Authorization branches remain
|
||||
# the production method body, and every solver invocation is counted below.
|
||||
renew=re.sub(r', string\.Format\([^\n]*\)\);',');',renew)
|
||||
run('''#include <cassert>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
struct vector {float x=0,y=0,z=0; static float Distance(vector a,vector b){return std::sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z));}};
|
||||
using IEntity=void*;
|
||||
struct ERR_HQReport {IEntity Target=this;bool Distress=false;float ObservedAt=0;std::string TravelMode="foot";};
|
||||
struct ERR_MortarPrediction {static constexpr float MAX_REPORT_AGE_MS=60000;
|
||||
'''+fresh+'''};
|
||||
struct ERR_MortarAimRules {
|
||||
'''+eq+'''};
|
||||
struct Node;struct ERR_MortarAimSolution;
|
||||
struct ERR_MortarBattery {static inline std::string SHELL="nativeHE";IEntity Gun=this;ERR_MortarAimSolution* AimSolution=nullptr;Node* AimSolver=nullptr;int OrderId=1,OrderShots=0;float AimedAt=1000; void Trace(const char*,const char*){}};
|
||||
struct ERR_MortarAimSolution {IEntity Gun=nullptr;ERR_HQReport* Report=nullptr;int OrderId=1,Shot=0,Charge=0;vector GunOrigin{},Impact{500,0,0},AimTarget{500,500,0};float FlightTime=30;
|
||||
'''+matches+'''};
|
||||
struct ERR_MortarSystem {int rejects=0;bool Withhold(ERR_MortarBattery*,const char*){rejects++;return false;}const char* ContactFailure(ERR_MortarBattery*){return "stale";}};
|
||||
struct Node {
|
||||
static constexpr int PORT_ARTILLERY_ENTITY=1,PORT_AMMO_PREFAB=2;
|
||||
IEntity portGun=nullptr;std::string portAmmo=ERR_MortarBattery::SHELL;ERR_MortarAimSolution* calculated=nullptr;int solves=0;
|
||||
void GetVariableIn(int,IEntity& out){out=portGun;}void GetVariableIn(int,std::string& out){out=portAmmo;}
|
||||
ERR_MortarAimSolution* ERR_Solve(ERR_MortarBattery*,ERR_MortarSystem* system,ERR_HQReport*,float){solves++;if(!calculated)system->rejects++;return calculated;}
|
||||
'''+renew+'''};
|
||||
struct Fixture {
|
||||
ERR_MortarBattery battery;ERR_HQReport report;ERR_MortarAimSolution original,next;ERR_MortarSystem system;Node node;
|
||||
Fixture(){original.Gun=battery.Gun;original.Report=&report;next=original;battery.AimSolution=&original;battery.AimSolver=&node;node.portGun=battery.Gun;node.calculated=&next;}
|
||||
bool attempt(float now=25000){return node.ERR_RevalidateAim(&battery,&system,&report,now);}
|
||||
};
|
||||
int main(){
|
||||
Fixture stable; assert(stable.attempt()); assert(stable.node.solves==1&&stable.battery.AimedAt==25000);
|
||||
assert(stable.report.ObservedAt==0&&stable.battery.AimSolution==&stable.original);
|
||||
assert(stable.attempt(45000)&&stable.node.solves==2); // fresh recomputation every renewal
|
||||
assert(!stable.attempt(60001)&&stable.node.solves==2&&stable.battery.AimedAt==45000); // observation never renewed
|
||||
Fixture limit;assert(limit.attempt(60000));
|
||||
Fixture future;future.report.ObservedAt=26000;assert(!future.attempt()&&future.node.solves==0);
|
||||
Fixture gone;gone.battery.AimSolver=nullptr;assert(!gone.attempt()&&gone.node.solves==0);
|
||||
Fixture wrongGun;wrongGun.node.portGun=&wrongGun.report;assert(!wrongGun.attempt()&&wrongGun.node.solves==0);
|
||||
Fixture wrongAmmo;wrongAmmo.node.portAmmo="other";assert(!wrongAmmo.attempt()&&wrongAmmo.node.solves==0);
|
||||
Fixture newReport;ERR_HQReport different;newReport.original.Report=&different;assert(!newReport.attempt()&&newReport.node.solves==0);
|
||||
Fixture nextOrder;nextOrder.battery.OrderId++;assert(!nextOrder.attempt()&&nextOrder.node.solves==0);
|
||||
Fixture nextShot;nextShot.battery.OrderShots++;assert(!nextShot.attempt()&&nextShot.node.solves==0);
|
||||
Fixture solverFail;solverFail.node.calculated=nullptr;assert(!solverFail.attempt()&&solverFail.battery.AimedAt==1000);
|
||||
Fixture charge;charge.next.Charge++;assert(!charge.attempt()&&charge.battery.AimedAt==1000);
|
||||
Fixture gunMove;gunMove.next.GunOrigin.y=.051f;assert(!gunMove.attempt());
|
||||
Fixture impactMove;impactMove.next.Impact.y=.101f;assert(!impactMove.attempt());
|
||||
Fixture aimMove;aimMove.next.AimTarget.y+=.101f;assert(!aimMove.attempt());
|
||||
Fixture flight;flight.next.FlightTime+=.051f;assert(!flight.attempt());
|
||||
Fixture invalid;invalid.next.FlightTime=std::numeric_limits<float>::quiet_NaN();assert(!invalid.attempt());
|
||||
Fixture slowDrift;slowDrift.next.Impact.x+=.06f;assert(slowDrift.attempt());
|
||||
slowDrift.next.Impact.x+=.06f;assert(!slowDrift.attempt(35000)); // compare original, not last accepted approximation
|
||||
assert(slowDrift.battery.AimSolution==&slowDrift.original&&slowDrift.original.Impact.x==500);
|
||||
}
|
||||
''')
|
||||
# The tested renewal is used at both existing pre-shot and physical-transfer
|
||||
# gates; it may neither emit a replacement barrel command nor change a report.
|
||||
source=firing.read_text();system=(r/'ERR_MortarSystem.c').read_text()
|
||||
assert source.count('system.AllowShot(battery)')==2
|
||||
assert 'SetVariableOut' not in body(firing,'bool ERR_RevalidateAim(')
|
||||
assert 'ObservedAt =' not in source
|
||||
assert 'now - battery.AimedAt > 10000' in system
|
||||
assert 'battery.AimSolver.ERR_RevalidateAim(battery, this, report, now)' in system
|
||||
assert source.count('BallisticTable.GetAimHeightOfProjectileAltitudeFromSource(')==1
|
||||
assert 'GetAmmoInitialSpeedCoefficients(ammo, coefficients)' in body(firing,'protected ERR_MortarAimSolution ERR_Solve(')
|
||||
print('PASS: actual aim renewal requires fresh same-report solve and original-command equivalence; native firing untested')
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Execute the production report ledger; native scheduler coverage is separate."""
|
||||
from pathlib import Path
|
||||
from policy_harness import body, run
|
||||
r=Path(__file__).resolve().parents[1]
|
||||
p=r/'addons/ai/Scripts/Game/ERR/ERR_MortarReportLedger.c'
|
||||
a=body(p,'bool Available(').replace('m_Attempts.', 'm_Attempts.')
|
||||
c=body(p,'bool Claim(')
|
||||
run('''#include <cassert>
|
||||
#include <map>
|
||||
#include <iterator>
|
||||
struct Entries {std::map<int,float> entries; int Count(){return entries.size();}
|
||||
int GetKey(int i){return std::next(entries.begin(),i)->first;}
|
||||
float GetElement(int i){return std::next(entries.begin(),i)->second;}
|
||||
void Remove(int key){entries.erase(key);} bool Contains(int key){return entries.count(key);}
|
||||
void Insert(int key,float time){entries.emplace(key,time);}};
|
||||
struct ERR_MortarPrediction {static constexpr float MAX_REPORT_AGE_MS=60000;};
|
||||
struct Ledger {Entries m_Attempts;
|
||||
'''+a.replace('ERR_MortarPrediction.', 'ERR_MortarPrediction::')+c+'''};
|
||||
int main(){
|
||||
Ledger a; assert(a.Available(1,0,0));assert(a.Available(1,0,10)); // unavailable radio must not claim
|
||||
assert(a.Claim(1,0,100));assert(!a.Available(1,0,200));assert(!a.Claim(1,0,50000));
|
||||
assert(!a.Claim(1,10000,50000)); // rewritten timestamp cannot refresh same message
|
||||
assert(a.Claim(2,200,300));assert(!a.Claim(1,0,60000)); // A/B/A replay
|
||||
assert(!a.Available(1,0,60001));assert(a.Claim(3,60001,60001));
|
||||
assert(a.m_Attempts.Count()==2);assert(a.Claim(4,60300,60300));assert(a.m_Attempts.Count()==2);
|
||||
assert(!a.Claim(0,0,0));assert(!a.Claim(-1,0,0));assert(!a.Claim(5,-1,0));
|
||||
assert(!a.Claim(5,100,99));assert(!a.Claim(5,0,60001));
|
||||
Ledger bounded;for(int i=1;i<=128;i++)assert(bounded.Claim(i,0,0));
|
||||
assert(!bounded.Claim(129,1,1));assert(bounded.Claim(130,60001,60001));assert(bounded.m_Attempts.Count()==1);
|
||||
Ledger nextRound;assert(nextRound.Claim(1,0,0));
|
||||
}
|
||||
''')
|
||||
b=r/'addons/ai/Scripts/Game/ERR/ERR_MortarBattery.c';s=r/'addons/ai/Scripts/Game/ERR/ERR_MortarSystem.c'
|
||||
request=body(b,'bool BeginRequest(')
|
||||
assert request.index('m_ReportLedger.Claim(')<request.index('Cancel(')<request.index('OrderId++')
|
||||
assert 'm_ReportLedger' not in body(b,'void Cancel(')
|
||||
tick=body(s,'void Tick(')
|
||||
assert tick.index('battery.CanRequest(report, now)')<tick.index('m_HQ.Downlink(')<tick.index('selected.BeginRequest(')
|
||||
print('PASS: actual mortar report ledger prevents duplicate response after cancellation, expiry and interleaved reports; native scheduler separate')
|
||||
@@ -0,0 +1,101 @@
|
||||
"""Actual Enforce policies and transaction observation body under narrow API fakes."""
|
||||
from policy_harness import ROOT, body, run
|
||||
r=ROOT/'addons/ai/Scripts/Game/ERR'
|
||||
readiness=r/'ERR_MortarReadiness.c'; transfer=r/'ERR_MortarSupplyTransfer.c'; battery=r/'ERR_MortarBattery.c'
|
||||
policy='\n'.join([body(readiness,'static ERR_MortarSupplyState Population('),body(readiness,'static bool AllocationValid('),body(readiness,'static int CycleGate('),body(transfer,'static bool Authority('),body(transfer,'static bool Proven(')])
|
||||
policy=policy.replace('ERR_MortarSupplyState.','ERR_MortarSupplyState::')
|
||||
run('''#include <cassert>
|
||||
enum class ERR_MortarSupplyState {FAILED=-1,PENDING,READY};
|
||||
'''+policy+'''
|
||||
int main(){
|
||||
using S=ERR_MortarSupplyState;
|
||||
assert(Population(0,0,0,false,false)==S::PENDING);
|
||||
assert(Population(3,3,0,true,false)==S::PENDING);
|
||||
assert(Population(4,3,1,false,false)==S::PENDING);
|
||||
assert(Population(4,3,1,true,false)==S::READY);
|
||||
assert(Population(4,3,1,true,true)==S::FAILED);
|
||||
assert(Population(0,0,0,false,true)==S::FAILED);
|
||||
assert(Population(5,3,2,true,false)==S::FAILED);
|
||||
assert(Population(4,4,0,true,false)==S::FAILED);
|
||||
assert(AllocationValid(0)&&AllocationValid(9));
|
||||
assert(!AllocationValid(-1)&&!AllocationValid(10));
|
||||
assert(Authority(true,false,1,1));
|
||||
assert(!Authority(false,false,1,1));
|
||||
assert(!Authority(true,true,1,1));
|
||||
assert(!Authority(true,false,1,2));
|
||||
for(int bits=0;bits<16;bits++) {
|
||||
bool callback=bits&1, source=bits&2, destination=bits&4, exact=bits&8;
|
||||
assert(Proven(callback,source,destination,exact)==(bits==13));
|
||||
}
|
||||
int counter=0;
|
||||
assert(CycleGate(true,0,counter,8,1)==0); // no physical projectile, no count
|
||||
for(int i=0;i<1000;i++) if(CycleGate(true,1,counter,8,0)==1) counter++;
|
||||
assert(counter==0); // pending supply never invokes native super
|
||||
assert(CycleGate(true,1,counter,8,-1)==-1);
|
||||
if(CycleGate(true,1,counter,8,1)==1) counter++;
|
||||
assert(counter==1 && CycleGate(true,1,counter,8,1)==0); // same event not counted twice
|
||||
assert(CycleGate(false,2,counter,8,1)==-1);
|
||||
assert(CycleGate(true,8,7,8,-1)==1); // final confirmed round needs no ninth shell
|
||||
}
|
||||
''')
|
||||
# Retain actual control flow; translate only native member/type syntax and the
|
||||
# component lookup into a directly supplied fake item component.
|
||||
poll=body(transfer,'ERR_MortarSupplyState Poll(')
|
||||
poll=poll.replace('ERR_MortarSupplyState.','ERR_MortarSupplyState::').replace('Replication.IsServer()','Replication::IsServer()').replace('InventoryItemComponent item;','InventoryItemComponent* item=nullptr;').replace('if (m_Shell) item = InventoryItemComponent.Cast(m_Shell.FindComponent(InventoryItemComponent));','if (m_Shell) item = m_Shell;').replace('item.GetParentSlot().GetStorage()','item->GetParentSlot()->GetStorage()').replace('item.GetParentSlot()','item->GetParentSlot()')
|
||||
ack=body(transfer,'void Acknowledge(')
|
||||
run('''#include <cassert>
|
||||
enum class ERR_MortarSupplyState {FAILED=-1,PENDING,READY};
|
||||
struct Replication { static bool IsServer(){return true;} };
|
||||
struct Slot { void* storage; void* GetStorage(){return storage;} };
|
||||
struct InventoryItemComponent { Slot* slot; Slot* GetParentSlot(){return slot;} };
|
||||
'''+body(transfer,'static bool Proven(')+'''
|
||||
struct Transfer {
|
||||
bool valid=true,source=true,destination=false,m_Accepted=true,m_Invalidated=false,m_Resolved=false,m_Ready=false;
|
||||
int m_Source=1,m_Destination=2,m_Acknowledgement=0;
|
||||
float m_Deadline=8000;
|
||||
void* m_Storage=this;
|
||||
Slot slot{this}; InventoryItemComponent item{&slot}; InventoryItemComponent* m_Shell=&item;
|
||||
bool Valid(float){return valid&&!m_Invalidated;}
|
||||
bool Contains(int which){return m_Shell && (which==1 ? source : destination);}
|
||||
'''+ack+poll+'''
|
||||
};
|
||||
int main(){
|
||||
using S=ERR_MortarSupplyState;
|
||||
Transfer t; assert(t.Poll(0)==S::PENDING && !t.m_Resolved);
|
||||
t.Acknowledge(true); t.Acknowledge(false); assert(t.m_Acknowledgement==1);
|
||||
assert(t.Poll(1)==S::PENDING && !t.m_Resolved); // callback alone cannot supply
|
||||
t.destination=true; assert(t.Poll(2)==S::PENDING); // source still claims ownership
|
||||
t.source=false; t.slot.storage=nullptr; assert(t.Poll(3)==S::PENDING); // wrong storage
|
||||
t.slot.storage=t.m_Storage; assert(t.Poll(4)==S::READY && t.m_Resolved);
|
||||
Transfer stopped; stopped.m_Invalidated=true; stopped.source=false; stopped.destination=true; stopped.Acknowledge(true);
|
||||
assert(stopped.Poll(5)==S::FAILED && !stopped.m_Ready && stopped.m_Resolved);
|
||||
Transfer timeout; assert(timeout.Poll(8000)==S::FAILED && !timeout.m_Resolved && timeout.m_Invalidated);
|
||||
timeout.source=false;timeout.destination=true;timeout.Acknowledge(true);
|
||||
assert(timeout.Poll(9000)==S::FAILED && timeout.m_Resolved && !timeout.m_Ready); // late success cannot authorize
|
||||
Transfer late;late.source=false;late.destination=true;late.Acknowledge(true);
|
||||
assert(late.Poll(8000)==S::FAILED); // no deadline-boundary success bypass
|
||||
Transfer failed;failed.Acknowledge(false);failed.Acknowledge(true);
|
||||
assert(failed.Poll(1)==S::FAILED && failed.m_Acknowledgement==-1 && failed.m_Resolved);
|
||||
Transfer gone;gone.m_Shell=nullptr;gone.Acknowledge(true);
|
||||
assert(gone.Poll(1)==S::PENDING && !gone.m_Resolved); // ambiguous until deadline, never another move
|
||||
Transfer unauthorized;unauthorized.valid=false;
|
||||
assert(unauthorized.Poll(0)==S::FAILED && !unauthorized.m_Resolved);
|
||||
}
|
||||
''')
|
||||
# Source contract checks constrain integration that cannot run without the engine.
|
||||
b=battery.read_text();sys=(r/'ERR_MortarSystem.c').read_text();f=(r/'ERR_MortarFiring.c').read_text();rd=readiness.read_text();tr=transfer.read_text()
|
||||
update=body(battery,'void Update(float now)')
|
||||
assert update.index('m_Readiness.Poll(now)') < update.index('if (!Operational())')
|
||||
assert 'm_Allocated = true;' in rd and rd.count('TrySpawnPrefabToStorage(ERR_MortarBattery.SHELL')==1
|
||||
assert rd.index('m_Allocated = true;') < rd.index('TrySpawnPrefabToStorage(ERR_MortarBattery.SHELL')
|
||||
assert 'm_aUnitPrefabSlots = new array<ResourceName>()' in rd and 'PurgeSpawnRequestsForGroup(group)' in rd
|
||||
assert 'DeleteSpawned' not in body(readiness,'void Stop(')
|
||||
assert 'battery.Stop();' in body(r/'ERR_MortarSystem.c','void Stop(')
|
||||
assert tr.count('TryMoveItemToStorage(')==1 and 'GetItem()' not in tr.replace('callback.GetItem()','')
|
||||
assert 'm_AttemptedShells.Insert(item)' in b and 'm_AttemptedShells.Contains(item)' in b
|
||||
assert 'm_Transfer.CapturedOrder() == OrderId' in b
|
||||
assert 'battery.PendingSupplyReport' in sys and 'supply == ERR_MortarSupplyState.PENDING) return;' in sys
|
||||
shoot=f[f.index('modded class SCR_AIStaticArtilleryBehavior_ShootingLogic'):]
|
||||
assert shoot.index('battery.SupplyState(')<shoot.index('super.EOnTaskSimulate(')
|
||||
assert shoot.count('super.EOnTaskSimulate(')==1 and 'battery.SupplyGunner()' not in shoot
|
||||
print('PASS: actual startup/generation/ownership/timeout/callback policies and native-counter wait; integration preserves one-shot allocation and pending-crew cancellation')
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Native fixture's actual snapshot predicate rejects changed life/body evidence."""
|
||||
from policy_harness import ROOT, body, run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/ERR_NativeLocalAdmissionProbe.c'
|
||||
s=body(p,'protected bool Unchanged()').replace('protected ','').replace('ERR_Phase.PRISON','1').replace('GetGame().','GetGame()->')
|
||||
for name in ['m_Mode','m_Controller','m_Life','m_Logic']:
|
||||
s=s.replace(name+'.',name+'->')
|
||||
s=s.replace('Players().','Players()->').replace('GetPlayerManager().','GetPlayerManager()->')
|
||||
run('''#include <cassert>
|
||||
using IEntity=void*;
|
||||
struct UUID {int value=1; bool operator==(UUID o){return value==o.value;}};
|
||||
struct Life {UUID AccountId;bool Connected=true,UsedLife=true;IEntity Actor=nullptr;};
|
||||
struct Controller {IEntity actor=nullptr;IEntity GetControlledEntity(){return actor;}};
|
||||
struct Roster {Life* life=nullptr;Life* current=nullptr;bool canEnter=false;Life* Get(int){return life;}Life* CurrentActor(IEntity){return current;}bool CanEnter(int,int){return canEnter;}};
|
||||
struct Mode {Roster roster;Roster* Players(){return &roster;}};
|
||||
struct Manager {Controller* controller=nullptr;Controller* GetPlayerController(int){return controller;}};
|
||||
struct Game {Manager manager;Manager* GetPlayerManager(){return &manager;}};Game game;Game* GetGame(){return &game;}
|
||||
struct Logic {bool idle=true;bool ERR_LocalProbeIdle(int){return idle;}};
|
||||
struct Probe {
|
||||
Mode mode;Life life,other;Controller controller;Logic logic;int actor=0;
|
||||
Mode* m_Mode=&mode;Life* m_Life=&life;Controller* m_Controller=&controller;Logic* m_Logic=&logic;IEntity m_Actor=&actor;
|
||||
UUID m_Key;int m_Connection=1,m_Participants=1,participants=1;
|
||||
Probe(){life.Actor=m_Actor;controller.actor=m_Actor;mode.roster.life=&life;mode.roster.current=&life;game.manager.controller=&controller;}
|
||||
int Participants(){return participants;}
|
||||
'''+s+'''};
|
||||
int main(){
|
||||
Probe baseline;assert(baseline.Unchanged());
|
||||
for(int fault=0;fault<13;++fault){
|
||||
Probe p;
|
||||
if(fault==0)p.m_Controller=nullptr;
|
||||
if(fault==1)p.m_Actor=nullptr;
|
||||
if(fault==2)p.m_Life=nullptr;
|
||||
if(fault==3)p.mode.roster.life=&p.other;
|
||||
if(fault==4)p.mode.roster.current=nullptr;
|
||||
if(fault==5)p.life.AccountId.value=2;
|
||||
if(fault==6)p.life.Connected=false;
|
||||
if(fault==7)p.life.UsedLife=false;
|
||||
if(fault==8)p.life.Actor=nullptr;
|
||||
if(fault==9)p.controller.actor=nullptr;
|
||||
if(fault==10)game.manager.controller=nullptr;
|
||||
if(fault==11)p.participants=2;
|
||||
if(fault==12)p.logic.idle=false;
|
||||
assert(!p.Unchanged());
|
||||
}
|
||||
Probe spent;spent.mode.roster.canEnter=true;assert(!spent.Unchanged());
|
||||
}
|
||||
''')
|
||||
source=p.read_text()
|
||||
assert source.startswith('#ifdef WORKBENCH')
|
||||
assert 'UUID.GenV4' not in source and 'OnPlayerDisconnected_S(' not in source
|
||||
assert 'm_Logic.OnPlayerAuditSuccess_S(m_Connection);' in source
|
||||
assert 'm_Logic.ERR_LocalProbeRefuseController(m_Connection)' in source
|
||||
print('PASS: native local-admission snapshot rejects changed life/key/body/participant/pending state; authentication is not simulated')
|
||||
@@ -0,0 +1,81 @@
|
||||
"""Execute actual fixture commit/event/closure gates; not native artillery simulation."""
|
||||
from policy_harness import ROOT, body, run
|
||||
p=ROOT/'addons/ai/Scripts/Game/ERR/ERR_NativeMortarFiringProbe.c'
|
||||
policies='\n'.join(body(p, sig) for sig in ['static bool CommitFresh(', 'static bool EventBound(', 'static bool ExpiredClosure('])
|
||||
policies=policies.replace('ERR_MortarPrediction.', 'ERR_MortarPrediction::').replace('string failure', 'Text failure')
|
||||
methods='\n'.join(body(p,sig) for sig in ['void NativeCommit(', 'void NativeClosing(', 'void NativeProjectile('])
|
||||
methods=methods.replace('ERR_MortarBattery battery','Battery* battery').replace('IEntity gun, IEntity projectile','Entity* gun, Entity* projectile').replace('string reason, string failure','Text reason, Text failure')
|
||||
methods=methods.replace('m_Battery.', 'm_Battery->').replace('battery.', 'battery->').replace('Report.', 'Report->').replace('projectile.GetOrigin()', 'projectile->GetOrigin()')
|
||||
methods=methods.replace('GetGame().GetWorld().GetWorldTime()', 'clockNow').replace('ERR_NativeMortarEvidenceRules.', 'ERR_NativeMortarEvidenceRules::').replace('ERR_MortarSalvo.', 'ERR_MortarSalvo::')
|
||||
run('''#include <cassert>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
float clockNow=20000;
|
||||
struct Text:std::string {using std::string::string;bool Contains(const char* part){return find(part)!=npos;}};
|
||||
struct ERR_MortarPrediction {static constexpr float MAX_REPORT_AGE_MS=60000;};
|
||||
struct ERR_MortarSalvo {static constexpr int ROUNDS=8;};
|
||||
struct ERR_NativeMortarEvidenceRules {
|
||||
'''+policies+'''};
|
||||
struct Entity {int GetOrigin(){return 0;}};
|
||||
struct Report {int MessageId=7;float ObservedAt=1000;};
|
||||
struct Battery {bool CommandOpen=true,Released=true,stopped=false;int SourceId=7,OrderId=1,OrderShots=0;float AimedAt=19000,FlightTime=20;struct Report report;struct Report* Report=&report;bool Stopped(){return stopped;}};
|
||||
struct Items {std::vector<Entity*> values;bool Contains(Entity* p){return std::find(values.begin(),values.end(),p)!=values.end();}void Insert(Entity* p){values.push_back(p);}};
|
||||
template<class... Args>void PrintFormat(Args...args){}
|
||||
struct Probe {
|
||||
bool m_Stopped=false,m_BadProjectile=false,m_WaitForExpiry=true,m_CommitPending=false,m_ClosureSeen=false,m_ClosureComplete=false,m_ClosureExpired=false;
|
||||
Battery* m_Battery;Entity* m_Gun;Items m_Projectiles;int m_Events=0,m_Step=1,m_Source=7,m_Order=1,m_Commits=0,m_CommitOrder=0,m_CommitShot=0;float m_ObservedAt=1000,m_CommittedAt=0;std::string m_BadReason;
|
||||
'''+methods+'''
|
||||
};
|
||||
int main(){
|
||||
using Rules=ERR_NativeMortarEvidenceRules;
|
||||
assert(Rules::EventBound(true,1,1,0,0,60999,90000)); // original fresh commit survives delayed native emission
|
||||
assert(Rules::CommitFresh(1000,1000,61000));assert(!Rules::CommitFresh(1000,1000,61001));
|
||||
assert(!Rules::CommitFresh(1100,1000,20000));assert(!Rules::CommitFresh(1000,1000,999));
|
||||
assert(Rules::ExpiredClosure(1000,61001,"aim: delivered target report expired or invalid"));
|
||||
assert(!Rules::ExpiredClosure(1000,61000,"aim: delivered target report expired or invalid"));
|
||||
assert(!Rules::ExpiredClosure(1000,65000,"native artillery activity FAILED"));
|
||||
Entity gun,other,projectile;Battery b;Probe p;p.m_Battery=&b;p.m_Gun=&gun;
|
||||
p.NativeProjectile(&other,&projectile);assert(!p.m_BadProjectile&&p.m_Events==0);
|
||||
p.NativeCommit(&b,clockNow);assert(p.m_CommitPending&&p.m_Commits==1&&!p.m_BadProjectile);
|
||||
p.NativeProjectile(&gun,&projectile);assert(p.m_Events==1&&!p.m_BadProjectile&&!p.m_CommitPending);
|
||||
p.NativeProjectile(&gun,&projectile);assert(p.m_BadProjectile&&p.m_Events==1);
|
||||
Probe nullEvent;nullEvent.m_Battery=&b;nullEvent.m_Gun=&gun;nullEvent.NativeProjectile(&gun,nullptr);assert(nullEvent.m_BadProjectile&&nullEvent.m_Events==0);
|
||||
Probe stoppedObserver;stoppedObserver.m_Battery=&b;stoppedObserver.m_Gun=&gun;stoppedObserver.m_Stopped=true;stoppedObserver.NativeCommit(&b,20000);stoppedObserver.NativeProjectile(&gun,&projectile);assert(stoppedObserver.m_Commits==0&&stoppedObserver.m_Events==0&&!stoppedObserver.m_BadProjectile);
|
||||
// Physical launch after report expiry is legitimate if its commit was fresh.
|
||||
b=Battery();b.Report=&b.report;b.AimedAt=60500;Probe delayed;delayed.m_Battery=&b;delayed.m_Gun=&gun;delayed.m_Step=3;
|
||||
delayed.NativeCommit(&b,60999);clockNow=62999;delayed.NativeProjectile(&gun,&projectile);
|
||||
assert(delayed.m_Events==1&&!delayed.m_BadProjectile);
|
||||
for(int fault=0;fault<10;++fault){
|
||||
b=Battery();b.Report=&b.report;clockNow=20000;Probe q;q.m_Battery=&b;q.m_Gun=&gun;
|
||||
if(fault==0)b.CommandOpen=false;
|
||||
if(fault==1)b.Released=false;
|
||||
if(fault==2)b.SourceId=8;
|
||||
if(fault==3)b.AimedAt=-1;
|
||||
if(fault==4)b.AimedAt=9999;
|
||||
if(fault==5)b.FlightTime=0;
|
||||
if(fault==6)q.m_Step=2;
|
||||
if(fault==7)b.report.ObservedAt=1001;
|
||||
if(fault==8)b.OrderId=2;
|
||||
if(fault==9){clockNow=61001;b.AimedAt=61000;}
|
||||
q.NativeCommit(&b,clockNow);assert(q.m_BadProjectile&&!q.m_CommitPending&&q.m_Commits==0);
|
||||
}
|
||||
b=Battery();b.Report=&b.report;clockNow=20000;Probe missing;missing.m_Battery=&b;missing.m_Gun=&gun;
|
||||
missing.NativeProjectile(&gun,&projectile);assert(missing.m_BadProjectile);
|
||||
Probe duplicate;duplicate.m_Battery=&b;duplicate.m_Gun=&gun;duplicate.NativeCommit(&b,20000);duplicate.NativeCommit(&b,20000);assert(duplicate.m_BadProjectile&&duplicate.m_Commits==1);
|
||||
for(int fault=0;fault<3;++fault){
|
||||
b=Battery();b.Report=&b.report;clockNow=20000;Probe changed;changed.m_Battery=&b;changed.m_Gun=&gun;changed.NativeCommit(&b,20000);
|
||||
if(fault==0)b.OrderId++;
|
||||
if(fault==1)b.OrderShots++;
|
||||
if(fault==2)clockNow=19999;
|
||||
|
||||
changed.NativeProjectile(&gun,&projectile);assert(changed.m_BadProjectile);
|
||||
}
|
||||
b=Battery();b.Report=&b.report;clockNow=65000;Probe expired;expired.m_Battery=&b;expired.m_Gun=&gun;
|
||||
expired.NativeClosing(&b,"native aim or firing authorization rejected","aim: delivered target report expired or invalid");assert(expired.m_ClosureSeen&&expired.m_ClosureExpired&&!expired.m_BadProjectile);
|
||||
Probe unrelated;unrelated.m_Battery=&b;unrelated.NativeClosing(&b,"native activity FAILED","");assert(unrelated.m_BadProjectile);
|
||||
Probe stopped;stopped.m_Battery=&b;b.stopped=true;stopped.NativeClosing(&b,"HQ stopped","aim: delivered target report expired or invalid");assert(stopped.m_BadProjectile);
|
||||
b.stopped=false;b.OrderShots=8;Probe complete;complete.m_Battery=&b;complete.m_Events=8;clockNow=50000;complete.NativeClosing(&b,"native artillery activity COMPLETED","");assert(complete.m_ClosureComplete&&!complete.m_BadProjectile);
|
||||
}
|
||||
''')
|
||||
print('PASS: actual probe binds native events to fresh immutable commits, permits delayed emission, rejects unrelated expiry closure and premature stop')
|
||||
@@ -0,0 +1,84 @@
|
||||
"""Source policy and actual fixture completion checks; not an engine test."""
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
from policy_harness import body, run
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SOURCE = ROOT/'addons/ai/Scripts/Game/ERR/ERR_NativeMortarTransferProbe.c'
|
||||
|
||||
class NativeMortarTransferChecks(unittest.TestCase):
|
||||
def test_original_identity_conservation_policy(self):
|
||||
method = body(SOURCE, 'static bool Conservation(')
|
||||
run(method + '''
|
||||
#include <cassert>
|
||||
int main() {
|
||||
for(int original=0;original<30;original++) for(int actual=0;actual<30;actual++)
|
||||
for(int all=0;all<2;all++) for(int unique=0;unique<2;unique++)
|
||||
assert(Conservation(original,actual,all,unique)==(original==27 && actual==27 && all && unique));
|
||||
}
|
||||
''')
|
||||
def test_actual_move_completion_requires_callback_slot_and_deadline(self):
|
||||
method=body(SOURCE, 'protected int MoveResult(').replace('protected int','int').replace('InventoryItemComponent item;', 'InventoryItemComponent* item;').replace('InventoryItemComponent item =', 'InventoryItemComponent* item =').replace('InventoryItemComponent.Cast','InventoryItemComponent::Cast').replace('FindComponent(InventoryItemComponent)','FindComponent(0)').replace('null','nullptr')
|
||||
for name in ('m_MoveCallback','m_MoveShell','m_MoveDestination','item'):
|
||||
method=method.replace(name+'.',name+'->')
|
||||
method=method.replace('GetParentSlot().', 'GetParentSlot()->')
|
||||
run('''
|
||||
#include <cassert>
|
||||
struct Entity;
|
||||
struct Storage { bool has=false; bool Contains(Entity*) { return has; } };
|
||||
struct Slot { Storage* storage=nullptr; Storage* GetStorage() { return storage; } };
|
||||
struct InventoryItemComponent {
|
||||
Slot* slot=nullptr;
|
||||
static InventoryItemComponent* Cast(InventoryItemComponent* x) { return x; }
|
||||
Slot* GetParentSlot() { return slot; }
|
||||
};
|
||||
struct Entity { InventoryItemComponent* item=nullptr; InventoryItemComponent* FindComponent(int) { return item; } };
|
||||
struct Callback { int Result=0; };
|
||||
struct Probe {
|
||||
Callback* m_MoveCallback=nullptr; Entity* m_MoveShell=nullptr;
|
||||
Storage* m_MoveDestination=nullptr; float m_MoveDeadline=100;
|
||||
''' + method + '''
|
||||
};
|
||||
int main() {
|
||||
Probe p; Callback cb; Entity shell; Storage target,wrong; Slot slot; InventoryItemComponent item;
|
||||
p.m_MoveCallback=&cb; p.m_MoveShell=&shell; p.m_MoveDestination=⌖
|
||||
assert(p.MoveResult(99)==0); // acceptance alone is not completion
|
||||
cb.Result=1; assert(p.MoveResult(99)==0); // callback alone is insufficient
|
||||
target.has=true; assert(p.MoveResult(99)==0); // exact item metadata required
|
||||
shell.item=&item; item.slot=&slot; slot.storage=&wrong;
|
||||
assert(p.MoveResult(99)==0); // apparent membership in wrong storage is insufficient
|
||||
slot.storage=⌖ assert(p.MoveResult(100)==-1); // bounded deadline
|
||||
assert(p.m_MoveCallback==&cb); // failure retains pending operation and borrowed shell
|
||||
cb.Result=-1; assert(p.MoveResult(99)==-1);
|
||||
cb.Result=1; assert(p.MoveResult(99)==1);
|
||||
assert(!p.m_MoveCallback && !p.m_MoveShell && !p.m_MoveDestination);
|
||||
assert(p.MoveResult(99)==1); // completion cannot replay an inventory operation
|
||||
}
|
||||
''')
|
||||
def test_no_shell_creation_or_mutation_and_safe_cleanup(self):
|
||||
source=SOURCE.read_text()
|
||||
self.assertEqual(source.count('ERR_Actors.Spawn('),1)
|
||||
self.assertIn('ERR_Actors.Spawn(STORE,',source)
|
||||
for forbidden in ('TrySpawnPrefab', 'SetHealth(', 'ForceUnconsciousness', 'DeleteRplEntity', 'TotalShots++', 'LoadMagazine', 'GetShootingLogic'):
|
||||
self.assertNotIn(forbidden,source)
|
||||
self.assertNotRegex(source,r'TotalShots\s*=(?!=)')
|
||||
stop=body(SOURCE,'void Stop()')
|
||||
self.assertIn('if (!m_MoveCallback)',stop)
|
||||
self.assertIn('storage && items.IsEmpty()',stop)
|
||||
self.assertIn('m_System.Stop()',stop)
|
||||
self.assertEqual(source.count('ERR_Actors.DeleteSpawned('),1)
|
||||
def test_scoped_hook_cancel_and_exact_request_capture(self):
|
||||
source=SOURCE.read_text()
|
||||
self.assertTrue(source.startswith('#ifdef WORKBENCH'))
|
||||
self.assertIn('outcome == "mortar-transfer"',source)
|
||||
self.assertIn('if (m_ERRTransferFixture) return;',source)
|
||||
self.assertIn('super.Tick(now);',source)
|
||||
self.assertIn('m_CallbackAtCancel = m_ObservedTransfer.ERR_CallbackState()',source)
|
||||
self.assertIn('m_ObservedTransfer.CapturedOrder() == m_Battery.OrderId',source)
|
||||
self.assertIn('m_Battery.OrderId != m_CancelOrder',source)
|
||||
self.assertIn('m_Battery.ERR_TransferAttempts() != m_AttemptsAtStart + 2',source)
|
||||
self.assertIn('late callback timing not exercised',source)
|
||||
self.assertIn('Has(entry.OriginalOwner, entry.Shell)',source)
|
||||
self.assertEqual(body(SOURCE,'protected bool BeginMove(').count('TryMoveItemToStorage('),1)
|
||||
self.assertIn('return BeginMove(shell, storage, now)',body(SOURCE,'protected bool Stash('))
|
||||
|
||||
if __name__=='__main__': unittest.main()
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Run actual extracted fixture rules; no native flight or Enforce claims."""
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
from policy_harness import body, run
|
||||
SOURCE=Path(__file__).resolve().parents[1]/'addons/core/Scripts/Game/ERR/ERR_NativePoweredFlightProbe.c'
|
||||
class PoweredFlightPolicy(unittest.TestCase):
|
||||
def test_actual_authority_all_inputs(self):
|
||||
method=body(SOURCE,'static bool Authority(')
|
||||
run('#include <cassert>\n'+method+'''
|
||||
int main(){for(int mask=0;mask<256;mask++) {
|
||||
bool server=mask&1, enabled=mask&2, stopped=mask&4, pilot=mask&8, seat=mask&16, player=mask&32, alive=mask&64, fuel=mask&128;
|
||||
assert(Authority(server,enabled,stopped,pilot,seat,player,alive,fuel)==(server&&enabled&&!stopped&&pilot&&seat&&!player&&alive&&fuel));
|
||||
}}
|
||||
''')
|
||||
def test_actual_hover_landing_and_bounded_input_rules(self):
|
||||
methods='\n'.join(body(SOURCE,key).replace('Math.','Math::') for key in ['static float ClampInput(', 'static float DesiredVertical(', 'static bool Hover(', 'static bool Landed('])
|
||||
run('''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
struct Math { static float Clamp(float x,float low,float high){return std::clamp(x,low,high);} static float AbsFloat(float x){return std::abs(x);} };
|
||||
'''+methods+'''
|
||||
int main(){
|
||||
assert(ClampInput(3)==1 && ClampInput(-3)==-1);
|
||||
assert(ClampInput(0.8f,0.15f)==0.15f);
|
||||
assert(DesiredVertical(0,false)==1.5f && DesiredVertical(8,false)==0);
|
||||
assert(DesiredVertical(8,true)==-0.8f && DesiredVertical(0,true)==0);
|
||||
assert(Hover(8,0.2f,0.1f,1,1));
|
||||
assert(!Hover(0,0,0,1,0) && !Hover(11,0,0,1,0));
|
||||
assert(!Hover(8,2,0,1,0) && !Hover(8,0,0.6f,1,0));
|
||||
assert(!Hover(8,0,0,0.9f,0) && !Hover(8,0,0,1,9));
|
||||
assert(Landed(true,0.1f,0.1f,0.01f,1));
|
||||
assert(!Landed(false,0,0,0,1)); // low stationary hover is never landing
|
||||
assert(!Landed(true,2,0,0,1) && !Landed(true,0,1,0,1));
|
||||
assert(!Landed(true,0,0,0.2f,1) && !Landed(true,0,0,0,0.8f));
|
||||
}
|
||||
''')
|
||||
def test_native_input_only_and_local_hooks(self):
|
||||
source=SOURCE.read_text()
|
||||
for forbidden in ('SetVelocity(', 'SetAngularVelocity(', 'SetWorldTransform(', 'SetOrigin(', 'SetAngles(', '.Teleport(', 'ForceStartEngine(', '.EngineStart(', 'RotorSetForce', 'RotorSetTorque', 'SetHealth(', 'SetFuel(', 'SetGlobalFuelConsumptionScale', 'GetInputManager('):
|
||||
self.assertNotIn(forbidden,source)
|
||||
self.assertTrue(source.startswith('#ifdef WORKBENCH'))
|
||||
self.assertIn('outcome == "powered-flight"',source)
|
||||
self.assertIn('controller != m_Character || owner != m_Pilot',source)
|
||||
self.assertIn('controller != m_Controller',source)
|
||||
self.assertIn('super.OnPrepareControls(owner, am, dt, player)',source)
|
||||
self.assertEqual(source.count('.StartEngine()'),1)
|
||||
self.assertEqual(source.count('physics.SetActive('),1)
|
||||
self.assertIn('m_Wakes == 0',source)
|
||||
for control in ('Collective','CyclicForward','CyclicAside','AntiTorque'):
|
||||
self.assertIn('m_Input.SetVehicle'+control+'(',source)
|
||||
self.assertIn('m_Input.GetVehicle'+control+'()',source)
|
||||
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_Fuel.GetTotalFuel() < m_InitialFuel - 0.05',source)
|
||||
self.assertIn('m_Controller.StopEngine(false)',source)
|
||||
self.assertIn('ERR_PoweredFlightRules.Landed(m_Simulation.HasAnyGroundContact()',source)
|
||||
if __name__=='__main__': unittest.main()
|
||||
Reference in New Issue
Block a user