84 lines
6.4 KiB
Python
84 lines
6.4 KiB
Python
"""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')
|