Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
116 lines
7.7 KiB
Python
116 lines
7.7 KiB
Python
"""Execute tactical cue selection, queueing and playback revalidation from production."""
|
|
import re
|
|
from policy_harness import ROOT, body, run
|
|
P=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQOperationSpeech.c'
|
|
C=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_SoldierSpeechContext.c'
|
|
E=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQExecution.c'
|
|
T=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQTask.c'
|
|
def cpp(s):
|
|
s=s.replace('protected ','').replace('null','nullptr')
|
|
for typ in ('ERR_HQTask','ERR_HQExecutionReport','ERR_HQUnit','SCR_AITalkRequest'):
|
|
s=re.sub(r'\b'+typ+r' (\w+)',typ+r'* \1',s)
|
|
for n in ('task','report','unit','request','m_Agent','m_VoNComponent','Group','Task','Execution'):
|
|
s=s.replace(n+'.',n+'->')
|
|
for n in ('ERR_HQOperationRole','ERR_HQExecutionState','ERR_HQExecutionRules','ERR_HQOperationSpeechRules','ERR_SoldierConversationRules','ECommMethod','ECommunicationType','vector','SCR_EAITalkRequestPreset'):
|
|
s=s.replace(n+'.',n+'::')
|
|
return s
|
|
enums=body(T,'enum ERR_HQOperationRole')+';'+body(E,'enum ERR_HQExecutionState')+';'
|
|
enums=enums.replace('enum ','enum class ')
|
|
rules=cpp(body(P,'static int Phrase(')+body(P,'static bool QueuedFresh('))
|
|
execute=cpp(body(C,'void ERR_SpeechOperation(')+body(C,'protected bool ERR_SpeechOperationValid('))
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <limits>
|
|
'''+enums+r'''
|
|
struct ERR_HQTask{int OperationId=1,OperationRevision=2,ExecutionId=10;float ExpiresAt=100000,SupportUntil=21000;ERR_HQOperationRole OperationRole=ERR_HQOperationRole::RESERVE;};
|
|
struct ERR_HQExecutionReport{int OperationId=1,OperationRevision=2,ExecutionId=10;float ObservedAt=10000,SupportUntil=21000,SearchedRadius=0;bool PhysicallyArrived=true,HasContact=false,SupportReady=false;ERR_HQExecutionState State=ERR_HQExecutionState::ARRIVED;};
|
|
struct ERR_HQExecutionRules{static bool Terminal(ERR_HQExecutionState s){return s==ERR_HQExecutionState::COMPLETED||s==ERR_HQExecutionState::FAILED||s==ERR_HQExecutionState::SUPERSEDED;}static bool SupportFresh(float at,float now){return at>now&&at<=now+30000;}};
|
|
struct ERR_HQOperationSpeechRules{
|
|
'''+rules+r'''
|
|
};
|
|
struct Agent;
|
|
struct Group{Agent*leader=nullptr;bool slave=false;int players=0;bool IsSlave(){return slave;}int GetPlayerCount(bool){return players;}Agent*GetLeaderAgent(){return leader;}};
|
|
struct Agent{Group*group=nullptr;Group*GetParentGroup(){return group;}};
|
|
struct Execution{ERR_HQExecutionReport report;bool pending=true;ERR_HQExecutionReport*Snapshot(int,int,float){return pending?&report:nullptr;}};
|
|
struct ERR_HQUnit{int Id=1,LogisticsReservation=0;bool HomeGuard=false,ReservedForSupport=false;struct Group*Group=nullptr;ERR_HQTask*Task=nullptr;struct Execution*Execution=nullptr;};
|
|
namespace ECommMethod{enum{DIRECT,RADIO};}
|
|
namespace ECommunicationType{enum{REPORT_IDLE};}
|
|
namespace SCR_EAITalkRequestPreset{enum{IRRELEVANT};}
|
|
namespace vector{int Zero=0;}
|
|
struct Von{int method=ECommMethod::DIRECT;int GetCommMethod(){return method;}};
|
|
struct SCR_AITalkRequest{int m_ERRSpeechPhrase=-1,m_ERROperationExecution=0;float m_ERROperationQueuedAt=-1;ERR_HQUnit*m_ERROperationUnit=nullptr;SCR_AITalkRequest(int,void*,int,int,bool,bool,int){}};
|
|
struct ERR_SoldierConversationRules{static bool Quiet(bool threat,bool overt){return threat&&!overt;}};
|
|
struct Handler{bool enabled=true,m_ERRSpeechThreat=false,m_ERRSpeechOvert=false;float m_ERROperationNext=0,m_ERRSpeechNextLine=0;int m_ERROperationLastExecution=0,m_ERROperationLastPhrase=-1,requests=0;Agent*m_Agent=nullptr;Von*m_VoNComponent=nullptr;SCR_AITalkRequest*queued=nullptr;
|
|
bool ERR_SpeechEnabled(){return enabled;}void ERR_SpeechObserve(float){}void AddRequest(SCR_AITalkRequest*q){requests++;queued=q;}
|
|
'''+execute+r'''
|
|
};
|
|
struct Fixture{ERR_HQTask task;Execution exec;ERR_HQUnit unit;Agent actor;Group group;Von von;Handler comms;
|
|
Fixture(){unit.Group=&group;unit.Task=&task;unit.Execution=&exec;actor.group=&group;group.leader=&actor;comms.m_Agent=&actor;comms.m_VoNComponent=&von;}
|
|
int Phrase(float now=10000){return ERR_HQOperationSpeechRules::Phrase(&task,&exec.report,now);}
|
|
};
|
|
int main(){
|
|
{Fixture f;assert(f.Phrase()==1203);f.exec.report.PhysicallyArrived=false;assert(f.Phrase()==-1);}
|
|
for(int failure=0;failure<9;failure++){Fixture f;
|
|
if(failure==0)f.exec.report.ExecutionId++;
|
|
if(failure==1)f.exec.report.OperationId++;
|
|
if(failure==2)f.exec.report.OperationRevision++;
|
|
if(failure==3)f.task.ExpiresAt=10000;
|
|
if(failure==4)f.exec.report.ObservedAt=10001;
|
|
if(failure==5)f.exec.report.ObservedAt=7499;
|
|
if(failure==6)f.exec.report.ObservedAt=-1;
|
|
if(failure==7)f.exec.report.ObservedAt=std::numeric_limits<float>::quiet_NaN();
|
|
if(failure==8)f.exec.report.State=ERR_HQExecutionState::FAILED;
|
|
assert(f.Phrase()==-1);
|
|
}
|
|
{Fixture f;f.task.OperationRole=ERR_HQOperationRole::FIX;f.exec.report.HasContact=true;
|
|
assert(f.Phrase()==-1);f.exec.report.SupportReady=true;assert(f.Phrase()==1200);
|
|
f.exec.report.HasContact=false;assert(f.Phrase()==-1); // remembered-area fire never says it can cover an advance
|
|
f.exec.report.HasContact=true;f.exec.report.SupportUntil=10000;assert(f.Phrase()==-1);
|
|
f.exec.report.SupportUntil=21000;f.exec.report.State=ERR_HQExecutionState::SUPERSEDED;assert(f.Phrase()==-1);
|
|
}
|
|
{Fixture f;f.task.OperationRole=ERR_HQOperationRole::SEARCH;f.exec.report.State=ERR_HQExecutionState::COMPLETED;
|
|
assert(f.Phrase()==-1);f.exec.report.SearchedRadius=10;assert(f.Phrase()==1204);
|
|
f.exec.report.HasContact=true;assert(f.Phrase()==-1);
|
|
}
|
|
{Fixture f;f.task.OperationRole=ERR_HQOperationRole::FLANK;f.exec.report.State=ERR_HQExecutionState::TRAVELLING;
|
|
assert(f.Phrase()==1201);f.task.SupportUntil=10000;assert(f.Phrase()==-1);
|
|
f.task.OperationRole=ERR_HQOperationRole::WITHDRAW;assert(f.Phrase()==1202);
|
|
f.exec.report.State=ERR_HQExecutionState::BLOCKED;assert(f.Phrase()==-1);
|
|
}
|
|
{Fixture f;f.comms.ERR_SpeechOperation(&f.unit,10000);assert(f.comms.requests==1&&f.comms.queued->m_ERRSpeechPhrase==1203);
|
|
assert(f.comms.ERR_SpeechOperationValid(f.comms.queued,10001));
|
|
f.comms.ERR_SpeechOperation(&f.unit,10001);assert(f.comms.requests==1);
|
|
f.exec.report.ObservedAt=40000;f.comms.ERR_SpeechOperation(&f.unit,40000);assert(f.comms.requests==1); // same order/action is not repeated
|
|
f.task.ExecutionId++;f.exec.report.ExecutionId++;f.comms.ERR_SpeechOperation(&f.unit,40000);assert(f.comms.requests==2);
|
|
}
|
|
for(int failure=0;failure<11;failure++){Fixture f;f.comms.ERR_SpeechOperation(&f.unit,10000);auto q=f.comms.queued;assert(q);
|
|
if(failure==0)f.unit.Task=nullptr;
|
|
if(failure==1)f.task.ExecutionId++;
|
|
if(failure==2)f.exec.pending=false; // acknowledgement is not fresh physical proof
|
|
if(failure==3)f.unit.HomeGuard=true;
|
|
if(failure==4)f.unit.ReservedForSupport=true;
|
|
if(failure==5)f.unit.LogisticsReservation=1;
|
|
if(failure==6)f.group.slave=true;
|
|
if(failure==7)f.group.players=1;
|
|
if(failure==8)f.group.leader=nullptr;
|
|
if(failure==9)f.actor.group=nullptr;
|
|
if(failure==10)f.exec.report.HasContact=true;
|
|
assert(!f.comms.ERR_SpeechOperationValid(q,10001));
|
|
}
|
|
{Fixture f;f.comms.ERR_SpeechOperation(&f.unit,10000);f.exec.report.ObservedAt=13000;
|
|
assert(!f.comms.ERR_SpeechOperationValid(f.comms.queued,13000)); // stale queue cannot become true again
|
|
}
|
|
for(int failure=0;failure<4;failure++){Fixture f;
|
|
if(failure==0)f.comms.enabled=false;
|
|
if(failure==1)f.von.method=ECommMethod::RADIO;
|
|
if(failure==2)f.comms.m_ERRSpeechThreat=true;
|
|
if(failure==3)f.group.leader=nullptr;
|
|
f.comms.ERR_SpeechOperation(&f.unit,10000);assert(!f.comms.requests);
|
|
}
|
|
assert(!ERR_HQOperationSpeechRules::QueuedFresh(10000,9999));
|
|
assert(ERR_HQOperationSpeechRules::QueuedFresh(10000,12500));
|
|
assert(!ERR_HQOperationSpeechRules::QueuedFresh(10000,12501));
|
|
}
|
|
''')
|
|
print('PASS: actual-action speech, finite current cover, local search wording, silence after replacement/transfer/stale queue, stable cooldown and optional direct speech')
|