Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
71 lines
5.8 KiB
Python
71 lines
5.8 KiB
Python
"""Execute production speech gating with simulated native perception and playback."""
|
|
import re
|
|
from policy_harness import ROOT, body, run
|
|
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_SoldierSpeechContext.c'
|
|
rules='\n'.join(body(p,x) for x in ['static bool Quiet(', 'static bool Calm(', 'static int CountPhrase(', 'static bool Approaching('])
|
|
s=body(p,'override protected void TransmitRequest(').replace('override protected ','')
|
|
s=s.replace('SCR_AITalkRequest rq','SCR_AITalkRequest* rq').replace('SCR_AICommsHandler partner','Handler* partner')
|
|
s=s.replace('m_Agent.GetParentGroup()', 'hasGroup')
|
|
s=s.replace('super.TransmitRequest(rq)','Stock(rq)').replace('GetGame().GetWorld().GetWorldTime()','clockNow').replace('GetGame().ERR_SoldierCallout','Callout')
|
|
for n in ['rq','partner','m_ERRSpeechPartner','m_Entity']:
|
|
s=s.replace(n+'.',n+'->')
|
|
for n in ['ERR_SoldierConversationRules','ECommunicationType','SCR_EAITalkRequestState','ERR_Actors']:
|
|
s=s.replace(n+'.',n+'::')
|
|
s=s.replace('vector.Distance','Distance')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <initializer_list>
|
|
struct ERR_SoldierConversationRules{
|
|
'''+rules+r'''
|
|
};
|
|
namespace ECommunicationType{enum{REPORT_IDLE,REPORT_CONTACT,REPORT_ENGAGING,REPORT_RELOADING};}
|
|
namespace SCR_EAITalkRequestState{enum{IDLE,TRANSMITTING};}
|
|
struct SCR_AITalkRequest{int m_ERRSpeechPhrase=-1,m_eCommType=ECommunicationType::REPORT_CONTACT,m_eState=0;};
|
|
struct Actor{bool fit=true;float pos=0;float GetOrigin(){return pos;}};
|
|
namespace ERR_Actors{bool CanFight(Actor*a){return a&&a->fit;}}
|
|
float Distance(float a,float b){return std::abs(a-b);}
|
|
float clockNow=100000;
|
|
struct Handler{
|
|
bool hasGroup=true,enabled=true,calm=true,plays=true,operationValid=true,m_ERRSpeechThreat=false,m_ERRSpeechOvert=false,m_ERRSpeechApproaching=false;
|
|
float m_ERRSpeechNextLine=0,m_ERRSpeechNextReport=0,m_ERRSpeechReplyAt=0;
|
|
int m_ERRSpeechConversation=0,m_ERRSpeechVisible=3,m_ERRSpeechReply=-1,stock=0,calls=0,lastPhrase=-1;
|
|
Actor actor,other,*m_Entity=&actor,*m_ERRSpeechPartner=&other;
|
|
void*m_VoNComponent=nullptr; Handler*mate=nullptr;
|
|
bool ERR_SpeechEnabled(){return enabled;}void ERR_SpeechObserve(float){}bool ERR_SpeechGroupCalm(){return calm;}
|
|
Handler*ERR_SpeechFindPartner(){return mate;}
|
|
bool ERR_SpeechOperationValid(SCR_AITalkRequest*,float){return operationValid;}
|
|
void Stock(SCR_AITalkRequest*){stock++;}
|
|
bool Callout(Actor*,SCR_AITalkRequest*,void*,int phrase){calls++;lastPhrase=phrase;return plays;}
|
|
'''+s+r'''
|
|
};
|
|
int main(){
|
|
for(bool threat:{false,true})for(bool overt:{false,true}){
|
|
assert(ERR_SoldierConversationRules::Quiet(threat,overt)==(threat&&!overt));
|
|
assert(ERR_SoldierConversationRules::Calm(threat,overt)==(!threat&&!overt));
|
|
}
|
|
assert(ERR_SoldierConversationRules::CountPhrase(0)==-1);
|
|
assert(ERR_SoldierConversationRules::CountPhrase(3)==1003);
|
|
assert(ERR_SoldierConversationRules::CountPhrase(9)==1004);
|
|
assert(ERR_SoldierConversationRules::Approaching(2000,100,95));
|
|
assert(!ERR_SoldierConversationRules::Approaching(2000,100,100)); // observer movement alone
|
|
assert(!ERR_SoldierConversationRules::Approaching(10000,100,90)); // stale track
|
|
assert(!ERR_SoldierConversationRules::Approaching(100,100,90)); // tiny noisy interval
|
|
{Handler h;SCR_AITalkRequest q;h.m_ERRSpeechThreat=true;h.plays=false;h.TransmitRequest(&q);assert(!h.stock&&!h.calls&&q.m_eState==1);} // no stock leak
|
|
{Handler h;SCR_AITalkRequest q;h.enabled=false;h.TransmitRequest(&q);assert(h.stock==1&&!h.calls);} // optional feature off
|
|
{Handler h;SCR_AITalkRequest q;q.m_ERRSpeechPhrase=1100;h.enabled=false;h.TransmitRequest(&q);assert(!h.stock&&!h.calls);} // generated request cannot leak after disable
|
|
{Handler h;SCR_AITalkRequest q;h.m_ERRSpeechThreat=true;h.m_ERRSpeechOvert=true;h.TransmitRequest(&q);assert(h.calls==1&&h.lastPhrase==1003&&!h.stock);}
|
|
{Handler h;SCR_AITalkRequest q;h.m_ERRSpeechOvert=true;h.m_ERRSpeechApproaching=true;h.TransmitRequest(&q);assert(h.lastPhrase==1005);}
|
|
{Handler h;SCR_AITalkRequest q;h.plays=false;h.TransmitRequest(&q);assert(h.stock==1);} // allowed native fallback
|
|
{Handler h;SCR_AITalkRequest q;q.m_eCommType=ECommunicationType::REPORT_IDLE;h.calm=false;h.TransmitRequest(&q);assert(!h.calls&&!h.stock);}
|
|
{Handler h,partner;h.mate=&partner;SCR_AITalkRequest q;q.m_eCommType=ECommunicationType::REPORT_IDLE;q.m_ERRSpeechPhrase=1100;h.TransmitRequest(&q);assert(partner.m_ERRSpeechReply==1101&&partner.m_ERRSpeechReplyAt==107000&&partner.m_ERRSpeechPartner==h.m_Entity);}
|
|
{Handler h,partner;h.mate=&partner;h.plays=false;SCR_AITalkRequest q;q.m_ERRSpeechPhrase=1100;q.m_eCommType=ECommunicationType::REPORT_IDLE;h.TransmitRequest(&q);assert(partner.m_ERRSpeechReply==-1&&!h.stock);} // no reply to an unheard question
|
|
{Handler h;SCR_AITalkRequest q;q.m_ERRSpeechPhrase=1101;q.m_eCommType=ECommunicationType::REPORT_IDLE;h.other.pos=10;h.TransmitRequest(&q);assert(!h.calls&&!h.stock);} // separated after queueing
|
|
{Handler h;SCR_AITalkRequest q;h.m_ERRSpeechNextLine=clockNow+1;h.TransmitRequest(&q);assert(!h.calls&&!h.stock);} // no overlapping lines
|
|
{Handler h;SCR_AITalkRequest q;q.m_ERRSpeechPhrase=1200;q.m_eCommType=ECommunicationType::REPORT_IDLE;h.m_ERRSpeechThreat=true;h.m_ERRSpeechOvert=true;h.calm=false;h.TransmitRequest(&q);assert(h.calls==1&&h.lastPhrase==1200&&!h.stock);} // actual covering is tactical speech during overt combat
|
|
{Handler h;SCR_AITalkRequest q;q.m_ERRSpeechPhrase=1200;h.operationValid=false;h.TransmitRequest(&q);assert(!h.calls&&!h.stock);} // changed action cannot leak through native fallback
|
|
{Handler h;SCR_AITalkRequest q;q.m_ERRSpeechPhrase=1201;h.m_ERRSpeechThreat=true;h.TransmitRequest(&q);assert(!h.calls&&!h.stock);} // orders do not expose an undiscovered squad
|
|
}
|
|
''')
|
|
print('PASS: covert silence including stock fallback, actual counts, observed approach, optional-feature isolation, reply guards and cooldown')
|