Files
EscapistsReintroduced/tests/test_hq_report_selection.py
T

82 lines
4.5 KiB
Python

"""Execute the actual bounded HQ report scheduler; radio integration stays native."""
from policy_harness import ROOT, body, run
from pathlib import Path
import os
source = ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQObservation.c'
hq = Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQ.c'))
select = body(source, 'ERR_HQVisualContact Select(')
select = select.replace('array<ref ERR_HQVisualContact> samples', 'Array<ERR_HQVisualContact> samples')
select = select.replace('ERR_HQVisualContact selected;', 'ERR_HQVisualContact selected = nullptr;')
select = select.replace('foreach (ERR_HQVisualContact sample : samples)', 'for (ERR_HQVisualContact sample : samples)')
select = select.replace('sample.Entity', 'sample->Entity')
queued = body(source, 'void Queued(')
run(r'''
#include <cassert>
#include <algorithm>
#include <vector>
struct Entity {int id;}; using IEntity=Entity*;
struct Sample {IEntity Entity;}; using ERR_HQVisualContact=Sample*;
template<class T> struct Array: std::vector<T> {
using std::vector<T>::vector;
int Count(){return this->size();}
int Find(T a){auto i=std::find(this->begin(),this->end(),a);return i==this->end()?-1:int(i-this->begin());}
void Remove(int i){this->erase(this->begin()+i);}
void Insert(T a){this->push_back(a);}
};
struct Selection {
Array<IEntity> m_Recent;
'''+select+queued+r'''
};
int main(){
Entity actors[80]{}; Sample samples[80]{};
for(int i=0;i<80;i++){actors[i].id=i;samples[i].Entity=&actors[i];}
Array<ERR_HQVisualContact> visible{&samples[0],&samples[1],&samples[2]};
Selection choice;
assert(!choice.Select({}));assert(!choice.Select({nullptr}));
Sample invalid{nullptr};assert(!choice.Select({&invalid}));
// Failed route allocation doesn't consume a turn: repeated Select is read only.
for(int i=0;i<5;i++)assert(choice.Select(visible)==&samples[0]);
assert(choice.m_Recent.empty());choice.Queued(nullptr);assert(choice.m_Recent.empty());
// Stable native order: every visible actor is reported before anyone repeats.
for(int i=0;i<30;i++){
auto selected=choice.Select(visible);assert(selected==&samples[i%3]);
choice.Queued(selected->Entity);assert(choice.m_Recent.Count()==3||i<2);
}
// Reordering native targets does not reset fairness. New contacts take priority.
assert(choice.Select({&samples[2],&samples[1],&samples[0]})==&samples[0]);
assert(choice.Select({&samples[0],&samples[3]})==&samples[3]);
choice.Queued(&actors[3]);
// Lost sight never reports a remembered actor; only current samples can win.
assert(choice.Select({&samples[2]})==&samples[2]);
// Interrupted delivery counts as an attempt, allowing another current contact.
choice.Queued(&actors[0]);assert(choice.Select(visible)==&samples[1]);
choice.Queued(&actors[1]);assert(choice.Select(visible)==&samples[2]);
// Repeated queue identity is deduplicated, and vanished references are purged.
choice.Queued(&actors[1]);assert(choice.m_Recent.Count()==4);
choice.m_Recent[0]=nullptr;choice.Queued(&actors[4]);
assert(choice.m_Recent.Find(nullptr)==-1);
// History and full 64-contact scheduling remain bounded under turnover.
Selection many;Array<ERR_HQVisualContact> sixtyFour;
for(int i=0;i<64;i++)sixtyFour.Insert(&samples[i]);
for(int i=0;i<192;i++){
auto selected=many.Select(sixtyFour);assert(selected==&samples[i%64]);
many.Queued(selected->Entity);assert(many.m_Recent.Count()<=64);
}
for(int i=64;i<80;i++)many.Queued(&actors[i]);
assert(many.m_Recent.Count()==64&&many.m_Recent.Find(&actors[0])==-1);
// Scheduling is per squad/round, never shared HQ intelligence.
Selection other;assert(other.Select(visible)==&samples[0]);
}
''')
observe=body(hq,'protected void Observe(')
assert 'samples[0]' not in observe
assert observe.index('ERR_HQObservation.Gather(')<observe.index('unit.ReportSelection.Select(samples)')
assert observe.index('unit.Uplink || now < unit.NextReport')<observe.index('unit.ReportSelection.Select(samples)')
assert observe.index('if (!route) return;')<observe.index('unit.PendingReport = report;')<observe.index('unit.Uplink = route;')<observe.index('unit.ReportSelection.Queued(seen.Entity)')
assert observe.count('unit.ReportSelection.Queued(')==1
assert 'Accept(' not in observe and 'AddWaypoint' not in observe
assert 'report.ObservedAt = seen.ObservedAt;' in observe
assert 'ERR_HQObservation.Describe(report, samples, now);' in observe
assert 'ref ERR_HQReportSelection ReportSelection = new ERR_HQReportSelection();' in hq.read_text()
print('PASS: actual per-squad bounded report selector rotates visible targets, survives ordering/turnover, records only queued attempts, and preserves radio intake ordering')