Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
175 lines
8.9 KiB
Python
175 lines
8.9 KiB
Python
"""Actual bounded negative-search coverage rules; physical radio delivery is HQ's boundary."""
|
|
from policy_harness import ROOT, body, run
|
|
P=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQCoverage.c'
|
|
PURSUIT=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c'
|
|
|
|
def cpp(s):
|
|
return (s.replace('protected ', '').replace('Math.', 'Math::').replace('vector.', 'vector::')
|
|
.replace('ERR_HQPursuitRules.', 'ERR_HQPursuitRules::').replace('ERR_HQCoverageRules.', 'ERR_HQCoverageRules::')
|
|
.replace('out vector ', 'vector& ').replace('array<vector> reserved', 'Array<vector>* reserved')
|
|
.replace('ERR_HQSearchReport report', 'ERR_HQSearchReport* report').replace('report.', 'report->')
|
|
.replace('reserved.Count()', 'reserved->Count()').replace('= null', '= nullptr')
|
|
.replace('foreach (vector position : reserved)', 'for (vector position : *reserved)')
|
|
.replace('foreach (int known, float searched : m_Cells)', 'for (auto [known, searched] : m_Cells)')
|
|
.replace('int oldestKey;', 'int oldestKey=0;').replace('bool found;', 'bool found=false;'))
|
|
common='\n'.join(cpp(body(PURSUIT,s)) for s in ('static bool Finite(', 'static bool PositionValid('))
|
|
rules='\n'.join(cpp(body(P,s)) for s in ('static bool PositionValid(', 'static int Cell(', 'static int Key(',
|
|
'static bool Recent(', 'static bool ValidReport(', 'static bool Point('))
|
|
methods='\n'.join(cpp(body(P,s)) for s in ('int Count(', 'void Forget(', 'bool Recent(vector', 'bool Ingest(',
|
|
'protected bool Reserved(', 'bool Select(', 'void Clear('))
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <vector>
|
|
struct vector{
|
|
float x=0,y=0,z=0;
|
|
float& operator[](int i){return i==0?x:i==1?y:z;}
|
|
float operator[](int i)const{return i==0?x:i==1?y:z;}
|
|
static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}
|
|
};
|
|
vector Vector(float x,float y,float z){return {x,y,z};}
|
|
struct Math{
|
|
static float Clamp(float n,float lo,float hi){return std::clamp(n,lo,hi);}
|
|
static float Floor(float n){return std::floor(n);}
|
|
static float AbsFloat(float n){return std::abs(n);}
|
|
};
|
|
template<class T>struct Array:std::vector<T>{int Count(){return this->size();}};
|
|
template<class K,class V>struct Map:std::map<K,V>{
|
|
bool Contains(K k){return this->count(k);}V Get(K k){return this->count(k)?this->at(k):V{};}
|
|
int Count(){return this->size();}void Set(K k,V v){(*this)[k]=v;}
|
|
void Remove(K k){this->erase(k);}void Clear(){this->clear();}
|
|
};
|
|
struct ERR_HQPursuitRules{
|
|
'''+common+r'''
|
|
};
|
|
struct ERR_HQCoverageRules{
|
|
'''+rules+r'''
|
|
};
|
|
struct ERR_HQSearchReport{
|
|
int SourceId=1,MessageId=1,BeliefId=1,BeliefRevision=1;
|
|
vector Position{250,12,250};float ObservedAt=0;
|
|
bool SweepCompleted=true,ContactFound=false;
|
|
};
|
|
struct Coverage{
|
|
Map<int,float>m_Cells;Map<int,int>m_Messages;
|
|
'''+methods+r'''
|
|
};
|
|
int main(){
|
|
using R=ERR_HQCoverageRules;
|
|
assert(R::Cell(0)==0&&R::Cell(499.9)==0&&R::Cell(500)==1);
|
|
assert(R::Cell(-.1)==-1&&R::Cell(-500)==-1&&R::Cell(-500.1)==-2);
|
|
assert(R::Key({-1,0,0})!=R::Key({0,0,-1}));
|
|
assert(R::Recent(0,900000)&&!R::Recent(0,900001)&&!R::Recent(1,0));
|
|
assert(R::ValidReport(1,1,1,1,true,false,0,120000));
|
|
assert(!R::ValidReport(1,1,1,1,true,false,0,120001));
|
|
assert(!R::ValidReport(1,1,1,1,false,false,0,1000));
|
|
assert(!R::ValidReport(1,1,1,1,true,true,0,1000));
|
|
assert(!R::ValidReport(1,1,0,1,true,false,0,1000));
|
|
assert(!R::ValidReport(1,1,1,0,true,false,0,1000));
|
|
float nan=std::numeric_limits<float>::quiet_NaN();
|
|
assert(!R::ValidReport(1,1,1,1,true,false,nan,1000));
|
|
assert(!R::PositionValid({nan,0,0})&&!R::PositionValid({1000000,0,0}));
|
|
vector point;
|
|
assert(R::Point(0,0,{0,12,0},0,point)&&point.x==0&&point.z==0);
|
|
assert(R::Point(0,0,{180,12,180},25,point)&&point.x==180&&point.z==180);
|
|
assert(!R::Point(2,2,{180,12,180},25,point));
|
|
// Undelivered reports remain inert objects. Only Ingest applies the delivered snapshot.
|
|
Coverage coverage;ERR_HQSearchReport report;
|
|
assert(coverage.Count()==0&&!coverage.Recent(report.Position,1000));
|
|
assert(coverage.Ingest(&report,1000)&&coverage.Count()==1);
|
|
assert(coverage.Recent(report.Position,900000)&&!coverage.Recent(report.Position,900001));
|
|
assert(!coverage.Ingest(&report,1000));
|
|
report.Position={750,12,250};report.BeliefRevision=2;
|
|
assert(!coverage.Ingest(&report,1000)&&!coverage.Recent(report.Position,1000));
|
|
report.MessageId=2;report.ObservedAt=1000;
|
|
assert(coverage.Ingest(&report,2000)&&coverage.Count()==2);
|
|
report.MessageId=1;report.Position={1250,12,250};
|
|
assert(!coverage.Ingest(&report,2000));
|
|
// Invalid/future/old/incomplete/positive observations never advance replay guards.
|
|
ERR_HQSearchReport invalid;invalid.SourceId=2;
|
|
for(int field=0;field<8;field++){
|
|
invalid=ERR_HQSearchReport{};invalid.SourceId=2;
|
|
if(field==0)invalid.ObservedAt=2001;
|
|
if(field==1)invalid.SweepCompleted=false;
|
|
if(field==2)invalid.ContactFound=true;
|
|
if(field==3)invalid.BeliefId=0;
|
|
if(field==4)invalid.BeliefRevision=0;
|
|
if(field==5)invalid.Position.x=nan;
|
|
if(field==6)invalid.MessageId=0;
|
|
if(field==7)invalid.ObservedAt=-1;
|
|
assert(!coverage.Ingest(&invalid,2000)&&!coverage.m_Messages.Contains(2));
|
|
}
|
|
assert(!coverage.Ingest(nullptr,2000));
|
|
|
|
// A fresh positive sighting may reopen a coarse cell, without replaying an old negative.
|
|
Coverage reopened;ERR_HQSearchReport searched;
|
|
assert(reopened.Ingest(&searched,1000));
|
|
reopened.Forget({1,12,1});assert(reopened.Count()==0);
|
|
assert(!reopened.Ingest(&searched,1000));
|
|
searched.MessageId=2;searched.ObservedAt=2000;
|
|
assert(reopened.Ingest(&searched,2000));
|
|
reopened.Forget({nan,0,0});assert(reopened.Count()==1);
|
|
// Delivery latency does not restart the 900-second coverage lifetime.
|
|
Coverage delayed;ERR_HQSearchReport late;
|
|
assert(delayed.Ingest(&late,120000));
|
|
assert(delayed.Recent(late.Position,900000)&&!delayed.Recent(late.Position,900001));
|
|
Coverage old;assert(!old.Ingest(&late,120001)&&old.Count()==0);
|
|
// Bounded cell retention evicts the oldest only for genuinely newer coverage.
|
|
Coverage bounded;ERR_HQSearchReport item;
|
|
for(int i=0;i<128;i++){
|
|
item.MessageId=i+1;item.Position={float(i*500+250),12,250};item.ObservedAt=1000+i;
|
|
assert(bounded.Ingest(&item,2000));
|
|
}
|
|
assert(bounded.Count()==128);
|
|
item.MessageId=129;item.Position={70000,12,250};item.ObservedAt=999;
|
|
assert(!bounded.Ingest(&item,2000)&&bounded.Count()==128);
|
|
item.MessageId=130;item.ObservedAt=1500;
|
|
assert(bounded.Ingest(&item,2000)&&bounded.Count()==128);
|
|
assert(!bounded.Recent({250,12,250},2000)&&bounded.Recent(item.Position,2000));
|
|
// Replay watermarks do not grow or evict an old source's identity under pressure.
|
|
Coverage sources;
|
|
for(int i=1;i<=128;i++){
|
|
item=ERR_HQSearchReport{};item.SourceId=i;item.ObservedAt=i;
|
|
assert(sources.Ingest(&item,1000));
|
|
}
|
|
item.SourceId=129;item.ObservedAt=129;
|
|
assert(!sources.Ingest(&item,1000)&&sources.m_Messages.Count()==128);
|
|
item.SourceId=1;item.MessageId=2;item.ObservedAt=130;
|
|
assert(sources.Ingest(&item,1000)&&sources.m_Messages.Count()==128);
|
|
// Nearest unswept candidates remain inside uncertainty, including a small circle.
|
|
Coverage search;vector center{250,12,250},destination;
|
|
assert(search.Select(center,25,{1000,0,250},1000,destination));
|
|
assert(R::Key(destination)==R::Key(center)&&vector::DistanceXZ(center,destination)<=25);
|
|
item=ERR_HQSearchReport{};assert(search.Ingest(&item,1000));
|
|
assert(!search.Select(center,25,{1000,0,250},1000,destination));
|
|
assert(search.Select(center,25,{1000,0,250},900001,destination));
|
|
assert(search.Select(center,600,{1000,0,250},1000,destination));
|
|
assert(destination.x==750&&destination.z==250);
|
|
Array<vector> reserved;reserved.push_back({501,0,1}); // different point, same reserved cell
|
|
assert(search.Select(center,600,{1000,0,250},1000,destination,&reserved));
|
|
assert(R::Key(destination)!=R::Key(reserved[0]));
|
|
assert(vector::DistanceXZ(center,destination)<=600);
|
|
Coverage tiny;reserved.clear();reserved.push_back({1,0,1});
|
|
assert(!tiny.Select(center,25,center,1000,destination,&reserved));
|
|
for(int i=0;i<7;i++)reserved.push_back(center);
|
|
assert(reserved.Count()==8&&tiny.Select(center,2000,center,1000,destination,&reserved));
|
|
reserved.push_back(center);
|
|
assert(!tiny.Select(center,2000,center,1000,destination,&reserved));
|
|
assert(!tiny.Select(center,2001,center,1000,destination));
|
|
assert(!tiny.Select(center,-1,center,1000,destination));
|
|
assert(!tiny.Select(center,nan,center,1000,destination));
|
|
assert(!tiny.Select(center,100,center,-1,destination));
|
|
assert(!tiny.Select({nan,0,0},100,center,1000,destination));
|
|
assert(!tiny.Select(center,100,{nan,0,0},1000,destination));
|
|
sources.Clear();assert(sources.Count()==0&&sources.m_Messages.Count()==0);
|
|
}
|
|
''')
|
|
source=P.read_text()
|
|
for forbidden in ('GetGame(', 'GetOrigin(', 'GetPlayers(', 'GetControlledEntity(', 'Spawn(', 'CallLater(', 'IEntity'):
|
|
assert forbidden not in source
|
|
assert all(len(line)<120 for line in source.splitlines())
|
|
print('PASS: actual bounded delivered-negative coverage, replay/freshness/expiry/invalid guards, oldest eviction, small-circle and nearest-unswept selection, reserved-cell exclusion and cleanup')
|