Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
96 lines
6.7 KiB
Python
96 lines
6.7 KiB
Python
"""Execute the actual detached squad judgment; no native perception or healing is simulated."""
|
|
from policy_harness import ROOT, run
|
|
import re
|
|
|
|
path = ROOT / 'addons/core/Scripts/Game/ERR/10_AI/ERR_HQSquadJudgment.c'
|
|
source = path.read_text()
|
|
code = source.replace('class ', 'struct ').replace('Math.', 'Math::').replace('ERR_HQMemoryRules.', 'ERR_HQMemoryRules::')
|
|
code = re.sub(r'^(})$', r'\1;', code, flags=re.M)
|
|
code = re.sub(r'\b(int|float|bool) (\w+);', r'\1 \2{};', code)
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <string>
|
|
using string=std::string;
|
|
struct Math{static float Clamp(float x,float a,float b){return std::clamp(x,a,b);}static float Max(float a,float b){return std::max(a,b);}};
|
|
struct ERR_HQMemoryRules{static bool DeliveryFresh(float at,float now){return std::isfinite(at)&&std::isfinite(now)&&at>=0&&now>=at&&now-at<=120000;}};
|
|
''' + code + r'''
|
|
int main(){
|
|
// Stable personalities affect caution only, never stats or enemy knowledge.
|
|
ERR_HQSquadJudgment bold,measured,cautious;bold.Init(3);measured.Init(4);cautious.Init(5);
|
|
assert(bold.Caution==0&&measured.Caution==1&&cautious.Caution==2);
|
|
assert(bold.Temperament()=="resolute"&&measured.Temperament()=="measured"&&cautious.Temperament()=="cautious");
|
|
for(auto*s:{&bold,&measured,&cautious}) assert(s->Sample(0,0,4,4,4,true,400,true,false));
|
|
for(auto*s:{&bold,&measured,&cautious}) assert(s->Sample(60000,60000,4,3,3,true,300,true,false));
|
|
assert(bold.Pressure==24&&measured.Pressure==27&&cautious.Pressure==30);
|
|
assert(bold.Recovering&&measured.Recovering&&cautious.Recovering);
|
|
assert(!bold.Withdrawing&&!cautious.Withdrawing&&cautious.RecoverUntil>bold.RecoverUntil);
|
|
// Lost/inoperative companions matter even when nominal strength has not
|
|
// changed. Isolation amplifies their effect; being alone by itself does not.
|
|
ERR_HQSquadJudgment isolated;isolated.Init(5);assert(isolated.Sample(0,0,4,4,4,true,400,false,false));
|
|
assert(!isolated.Recovering&&isolated.Pressure==0);
|
|
assert(isolated.Sample(60000,60000,4,4,2,true,200,false,false));
|
|
assert(isolated.UnfitPresent&&isolated.Pressure==66&&isolated.Withdrawing&&isolated.EscalatedAt==60000);
|
|
float stress=isolated.Pressure,deadline=isolated.RecoverUntil;
|
|
// Duplicated or out-of-order packets, invalid rosters, NaN/future/stale
|
|
// clocks are rejected without changing state, even if their payload heals.
|
|
assert(!isolated.Sample(60000,70000,4,4,4,true,400,true,false));
|
|
assert(!isolated.Sample(59000,70000,4,4,4,true,400,true,false));
|
|
assert(!isolated.Sample(80000,70000,4,4,4,true,400,true,false));
|
|
assert(!isolated.Sample(80000,200001,4,4,4,true,400,true,false));
|
|
assert(!isolated.Sample(std::numeric_limits<float>::quiet_NaN(),90000,4,4,4,true,400,true,false));
|
|
assert(!isolated.Sample(90000,90000,4,2,3,true,400,true,false));
|
|
assert(!isolated.Sample(90000,90000,4,5,3,true,400,true,false));
|
|
assert(!isolated.Sample(90000,90000,4,4,3,true,-1,true,false));
|
|
assert(isolated.Pressure==stress&&isolated.RecoverUntil==deadline&&isolated.ObservedAt==60000);
|
|
// Clock time does not improve the state; quiet delivered observations do.
|
|
// A long gap buys one sample, and frequent packets cannot heal rapidly.
|
|
assert(isolated.Sample(61000,61000,4,4,2,true,200,false,false));
|
|
assert(isolated.Pressure==66&&isolated.StableReports==0);
|
|
assert(isolated.Sample(180000,180000,4,4,2,true,200,false,false));
|
|
assert(isolated.Pressure==51&&isolated.StableReports==1&&isolated.Recovering);
|
|
assert(isolated.Sample(210000,210000,4,4,2,true,200,false,false));
|
|
assert(isolated.Recovering&&isolated.Pressure==36);
|
|
assert(isolated.Sample(240000,240000,4,4,2,true,200,false,false));
|
|
assert(isolated.Recovering&&isolated.Pressure==21);
|
|
assert(isolated.Sample(270000,270000,4,4,2,true,200,false,false));
|
|
assert(isolated.Recovering&&isolated.Withdrawing&&isolated.Pressure==6&&isolated.StableReports==3);
|
|
assert(isolated.Sample(300000,300000,4,4,4,true,200,false,false));
|
|
assert(!isolated.Recovering&&!isolated.Withdrawing&&!isolated.UnfitPresent); // delivered recovery, no invented healing
|
|
// An initial roster can report incapacitated teammates without a previous
|
|
// casualty baseline; leaders still reserve the squad to protect treatment.
|
|
ERR_HQSquadJudgment caring;caring.Init(3);assert(caring.Sample(0,0,4,4,3,true,300,true,false));
|
|
assert(caring.Recovering&&!caring.Withdrawing&&caring.UnfitPresent);
|
|
assert(caring.Sample(60000,60000,4,4,3,true,300,true,false));
|
|
assert(caring.Sample(120000,120000,4,4,3,true,300,true,false));assert(caring.Recovering);
|
|
assert(caring.Sample(180000,180000,4,4,4,true,300,true,false));assert(!caring.Recovering);
|
|
// Ammunition decline alone does not invent a firefight. Spending under a
|
|
// delivered recent contact builds pressure across observations.
|
|
ERR_HQSquadJudgment firing;firing.Init(3);assert(firing.Sample(0,0,4,4,4,true,500,true,false));
|
|
assert(firing.Sample(60000,60000,4,4,4,true,400,true,false));assert(firing.Pressure==0&&!firing.Recovering);
|
|
assert(firing.Sample(120000,120000,4,4,4,true,350,true,true));assert(firing.Pressure==10);
|
|
assert(firing.Sample(180000,180000,4,4,4,true,300,true,true));assert(firing.Pressure==20);
|
|
assert(firing.Sample(240000,240000,4,4,4,true,250,true,true));assert(firing.Pressure==30&&firing.Recovering);
|
|
// Unknown ammo cannot clear a known shortage, nor can a small resupply
|
|
// oscillate a squad between assault and withdrawal at the threshold.
|
|
ERR_HQSquadJudgment low;low.Init(4);assert(low.Sample(0,0,4,4,4,true,59,true,false));
|
|
assert(low.AmmoLow&&low.Withdrawing&&low.Recovering);
|
|
assert(low.Sample(60000,60000,4,4,4,false,999,true,false));
|
|
assert(low.Sample(120000,120000,4,4,4,true,119,true,false));assert(low.AmmoLow&&low.Recovering);
|
|
assert(low.Sample(180000,180000,4,4,4,true,120,true,false));assert(!low.AmmoLow&&!low.Recovering);
|
|
// Fresh quiet status does not heal actual reported manpower depletion.
|
|
ERR_HQSquadJudgment depleted;depleted.Init(3);
|
|
for(int at:{0,60000,120000,180000}) assert(depleted.Sample(at,at,4,1,1,true,100,true,false));
|
|
assert(depleted.Withdrawing&&depleted.Recovering);
|
|
// Recent contact interrupts recovery even if ammunition happens unchanged.
|
|
assert(measured.Sample(180000,180000,4,3,3,true,300,true,true));assert(measured.StableReports==0&&measured.Recovering);
|
|
assert(measured.Sample(240000,240000,4,3,3,true,300,true,false));assert(measured.Recovering);
|
|
assert(measured.Sample(300000,300000,4,3,3,true,300,true,false));assert(!measured.Recovering);
|
|
}
|
|
''')
|
|
for forbidden in ('GetPlayers(', 'GetPlayerManager(', 'GetOrigin(', 'GetGame(', 'IEntity', 'SCR_AIGroup', 'Spawn(', 'Math.Random'):
|
|
assert forbidden not in source
|
|
print('PASS: actual squad judgment, stable caution, delivered casualty/contact/ammo trends, isolation, replay/time validation, recovery hysteresis, no invented healing or enemy knowledge')
|