Files
EscapistsReintroduced/tests/test_hunter_recovery_wait.py

75 lines
4.4 KiB
Python

"""Execute recovery deadlines and cross-assignment rejection memory from production."""
from policy_harness import ROOT, body, run
ai = ROOT / 'addons/core/Scripts/Game/ERR/10_AI'
wait = body(ai / 'ERR_HunterCrewRecovery.c', 'class ERR_HunterRecoveryWait')
run('#include <cassert>\nstruct ' + wait.removeprefix('class ') + r''';
int main() {
ERR_HunterRecoveryWait wait;
assert(!wait.Expired(false,false,false,0,1000)); // admission owns its own deadline
assert(!wait.Expired(true,false,false,0,2000));
assert(!wait.Expired(true,false,false,0,121999));
assert(wait.Expired(true,false,false,0,122000));
// Repeated combat/seat-failure transitions cannot restart the deadline.
assert(!wait.Expired(true,false,true,0,123000));
assert(!wait.Expired(true,false,false,4,124000));
assert(!wait.Expired(true,false,false,-1,125000)); // no physics sample
assert(wait.Expired(true,false,false,3,126000));
assert(!wait.Expired(true,true,false,0,127000)); // recovered or adopted driver
assert(!wait.Expired(true,false,false,0,200000));
assert(!wait.Expired(true,false,false,0,319999));
assert(wait.Expired(true,false,false,0,320000));
wait.Reset(); // deliberate tactical dismount owns its separate time budget
assert(!wait.Expired(true,false,false,0,500000));
assert(!wait.Expired(true,false,false,0,1000)); // world clock reset
assert(!wait.Expired(true,false,false,0,120999));
assert(wait.Expired(true,false,false,0,121000));
}
''')
memory = body(ai / 'ERR_HunterTactics.c', 'class ERR_HunterApproachMemory')
memory = memory.replace('protected ', '').replace('ref array<vector>', 'Array<Vec>').replace('ref array<float>', 'Array<float>')
memory = memory.replace('array<vector> excluded', 'Array<Vec>& excluded').replace('foreach (vector position :', 'for (Vec position :')
memory = memory.replace('vector position', 'Vec position').replace('vector.DistanceXZ', 'Distance').replace('float RetryAfter;', 'float RetryAfter = 0;')
run(r'''
#include <cassert>
#include <vector>
#include <cmath>
struct Vec {float x=0;}; float Distance(Vec a,Vec b){return std::abs(a.x-b.x);}
template<class T>struct Array:std::vector<T>{
int Count(){return this->size();}void Remove(int i){this->erase(this->begin()+i);}void Insert(T value){this->push_back(value);}
};
struct ''' + memory.removeprefix('class ') + r''';
int main(){
ERR_HunterApproachMemory crew;crew.Reject({100},1000);
assert(crew.RetryAfter==61000);
Array<Vec> nextAssignment;crew.Append(nextAssignment,9000);
assert(nextAssignment.size()==1&&nextAssignment[0].x==100); // eight seconds after release
crew.Reject({101},2000);Array<Vec> refreshed;crew.Append(refreshed,301000);
assert(refreshed.size()==1); // nearly identical stage refreshes, does not duplicate
Array<Vec> expired;crew.Append(expired,302000);assert(expired.empty());
for(int i=0;i<12;i++)crew.Reject({float(i*100)},400000+i);
Array<Vec> bounded;crew.Append(bounded,400020);assert(bounded.size()==8&&bounded[0].x==400);
ERR_HunterApproachMemory otherCrew;Array<Vec> independent;otherCrew.Append(independent,400020);assert(independent.empty());
Array<Vec> cleared;crew.Append(cleared,700011);assert(cleared.empty());
}
''')
# Wiring guards complement execution: the blocked-seat branch must reach this
# clock before normal travel's invalid-crew early return, and every planner
# candidate (including already parked vehicles) must consult shared memory.
service = (ai / 'ERR_HunterService.c').read_text()
tick = body(ai / 'ERR_HunterService.c', 'override void Tick(float now)')
assert tick.index('unit.Recovery.Wait.Expired(') < tick.index('if (!unit.CrewValid(m_Mode)) continue;')
assert tick.count('unit.Recovery.Wait.Reset()') == 2
assert 'AbandonStuckVehicle(unit, "driver recovery timed out after two minutes")' in tick
assert 'ref ERR_HunterApproachMemory FailedApproaches' in service
plan = body(ai / 'ERR_HunterTactics.c', 'bool Plan(array<vector> occupied)')
assert 'if (!Delivered && now < Unit.FailedApproaches.RetryAfter) return false;' in plan
assert 'Unit.FailedApproaches.Append(excluded, now);' in plan
assert plan.count('approachForward, excluded)') == 2
assert 'foreach (vector failedPosition : excluded)' in plan
assert 'foreach (vector reserved : excluded)' in plan
assert 'Unit.FailedApproaches.Reject(Stage, now);' in body(ai / 'ERR_HunterTactics.c', 'protected void RejectStage(')
print('PASS: blocked crew deadline survives combat interruptions, defers unsafe exits, resets after recovery; failed stages survive assignments with bounded expiry')