Files
EscapistsReintroduced/tests/test_hq_operation_speech.py
T
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

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')