Integrate owned Intel rerolls, discovery feedback and fair HQ reports
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""Execute production feedback queue/delivery bodies with fake engine boundaries."""
|
||||
from policy_harness import ROOT, body, run
|
||||
P=ROOT/'addons/intel/Scripts/Game/ERR/ERR_IntelDiscoveryFeedback.c'
|
||||
source=P.read_text()
|
||||
def cpp(s):
|
||||
s=s.replace('protected ', '').replace('static ', '')
|
||||
for name in ('Replication','Math','ERR_Phase','ERR_LocationKind','ERR_GameMode','ERR_IntelDiscoveryText','SCR_Global','SCR_ChatComponent'):
|
||||
s=s.replace(name+'.',name+'::')
|
||||
s=s.replace('ERR_LocationComponent location','ERR_LocationComponent* location')
|
||||
s=s.replace('location.', 'location->').replace('GetWorld().','GetWorld()->').replace('GetGame().','GetGame()->')
|
||||
s=s.replace('int seen;','int seen=0;').replace('int identified;','int identified=0;').replace('int rough;','int rough=0;')
|
||||
s=s.replace('foreach (ERR_LocationComponent* location, int knowledge : m_ERRIntelPending)','for (auto [location, knowledge] : m_ERRIntelPending.values)')
|
||||
s=s.replace('Rpc(ERR_ReceiveDiscoveryFeedback, ', 'Rpc(')
|
||||
s=s.replace('OnLocationDiscovered().Remove(ERR_QueueDiscoveryFeedback);','OnLocationDiscovered().Remove(0);')
|
||||
s=s.replace('OnTick().Remove(ERR_FlushDiscoveryFeedback);','OnTick().Remove(0);')
|
||||
s=s.replace('string.Format(', 'Format(')
|
||||
return s
|
||||
methods='\n'.join(cpp(body(P,s)) for s in ('protected void ERR_QueueDiscoveryFeedback(', 'protected void ERR_FlushDiscoveryFeedback(', 'protected void ERR_ReceiveDiscoveryFeedback(', 'protected void ERR_StopDiscoveryFeedback('))
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
struct string:std::string {using std::string::string;using std::string::operator=;string(std::string s):std::string(s){}bool IsEmpty()const{return empty();}};
|
||||
string Format(string text,int identified,int rough){text.replace(text.find("%1"),2,std::to_string(identified));text.replace(text.find("%2"),2,std::to_string(rough));return text;}
|
||||
struct Replication {static bool server;static bool IsServer(){return server;}};bool Replication::server=true;
|
||||
struct SCR_Global {static bool edit;static bool IsEditMode(){return edit;}};bool SCR_Global::edit=false;
|
||||
struct SCR_ChatComponent {static std::vector<string> messages;static void RadioProtocolMessage(string text){messages.push_back(text);}};std::vector<string> SCR_ChatComponent::messages;
|
||||
struct Math {static float Max(float a,float b){return a>b?a:b;}};
|
||||
namespace ERR_Phase {enum {PREPARING,PRISON,RADIO_SEARCH,EXTRACTION};}
|
||||
namespace ERR_LocationKind {enum {PRISON,RADIO,EXTRACTION,OUTPOST,DEPOT};}
|
||||
struct ERR_LocationComponent {int kind=ERR_LocationKind::OUTPOST,knowledge=1;string label="Hidden outpost";int Kind(){return kind;}int Knowledge(){return knowledge;}string Label(){return label;}};
|
||||
template<class K,class V> struct Map {std::map<K,V> values;bool Find(K k,V& v){auto i=values.find(k);if(i==values.end())return false;v=i->second;return true;}bool Contains(K k){return values.count(k);}int Count(){return values.size();}bool IsEmpty(){return values.empty();}void Set(K k,V v){values[k]=v;}void Clear(){values.clear();}};
|
||||
struct World {float time=0;float GetWorldTime(){return time;}} world;
|
||||
struct Game {bool controller=true;bool GetPlayerController(){return controller;}} game;
|
||||
Game* currentGame=&game;Game* GetGame(){return currentGame;}
|
||||
struct Events {int removes=0;void Remove(int){removes++;}};
|
||||
struct ERR_IntelDiscoveryText {static
|
||||
'''+cpp(body(P,'static string Message('))+r'''
|
||||
};
|
||||
struct ERR_GameMode {
|
||||
static ERR_GameMode* current;static ERR_GameMode* Get(){return current;}
|
||||
bool active=true;int phase=ERR_Phase::RADIO_SEARCH;bool Active(){return active;}int Phase(){return phase;}World* GetWorld(){return &world;}
|
||||
Map<ERR_LocationComponent*,int> m_ERRIntelSeen,m_ERRIntelPending;
|
||||
float m_ERRIntelMessageAt=0;bool m_ERRIntelFeedbackStopped=false;int m_ERRIntelMessageSequence=0,m_ERRIntelReceivedSequence=0;
|
||||
Events discovered,tick;Events& OnLocationDiscovered(){return discovered;}Events& OnTick(){return tick;}
|
||||
int rpcCount=0;void Rpc(int sequence,string message){rpcCount++;ERR_ReceiveDiscoveryFeedback(sequence,message);}
|
||||
'''+methods+r'''
|
||||
};
|
||||
ERR_GameMode* ERR_GameMode::current=nullptr;
|
||||
int main(){
|
||||
ERR_GameMode m;ERR_GameMode::current=&m;ERR_LocationComponent site;
|
||||
// Do not reveal startup sites, prison or automatic helicopter disclosure.
|
||||
m.phase=ERR_Phase::PREPARING;m.ERR_QueueDiscoveryFeedback(&site,0,1);assert(m.m_ERRIntelPending.IsEmpty());
|
||||
m.phase=ERR_Phase::RADIO_SEARCH;for(int kind:{ERR_LocationKind::PRISON,ERR_LocationKind::EXTRACTION}){site.kind=kind;m.ERR_QueueDiscoveryFeedback(&site,0,1);}assert(m.m_ERRIntelPending.IsEmpty());
|
||||
site.kind=ERR_LocationKind::OUTPOST;Replication::server=false;m.ERR_QueueDiscoveryFeedback(&site,0,1);assert(m.m_ERRIntelPending.IsEmpty());Replication::server=true;
|
||||
m.ERR_QueueDiscoveryFeedback(nullptr,0,1);m.ERR_QueueDiscoveryFeedback(&site,1,1);m.ERR_QueueDiscoveryFeedback(&site,0,2);m.ERR_QueueDiscoveryFeedback(&site,-1,1);assert(m.m_ERRIntelPending.IsEmpty());
|
||||
// Rough clues do not disclose labels, kinds or coordinates. Listen callbacks dedupe.
|
||||
m.ERR_QueueDiscoveryFeedback(&site,0,1);m.ERR_FlushDiscoveryFeedback(1499);assert(SCR_ChatComponent::messages.empty());
|
||||
m.ERR_FlushDiscoveryFeedback(1500);assert(m.rpcCount==1&&SCR_ChatComponent::messages.size()==1);assert(SCR_ChatComponent::messages.back().find("Hidden")==string::npos);assert(SCR_ChatComponent::messages.back().find("500 m")!=string::npos);
|
||||
m.ERR_QueueDiscoveryFeedback(&site,0,1);assert(m.m_ERRIntelPending.IsEmpty());
|
||||
site.knowledge=2;world.time=2000;m.ERR_QueueDiscoveryFeedback(&site,1,2);m.ERR_FlushDiscoveryFeedback(6499);assert(m.rpcCount==1);
|
||||
m.ERR_FlushDiscoveryFeedback(6500);assert(m.rpcCount==2&&SCR_ChatComponent::messages.back().find("Hidden outpost")!=string::npos);
|
||||
// A same-batch rough→exact update becomes one final identified location.
|
||||
ERR_GameMode batch;ERR_GameMode::current=&batch;ERR_LocationComponent a,b;world.time=0;
|
||||
batch.ERR_QueueDiscoveryFeedback(&a,0,1);a.knowledge=2;batch.ERR_QueueDiscoveryFeedback(&a,1,2);batch.ERR_QueueDiscoveryFeedback(&b,0,1);batch.ERR_FlushDiscoveryFeedback(1500);
|
||||
assert(SCR_ChatComponent::messages.back().find("1 identified locations and 1 search areas")!=string::npos);
|
||||
// End/restart cancels pending output and removes both hooks, new mode is clean.
|
||||
world.time=2000;b.knowledge=2;batch.ERR_QueueDiscoveryFeedback(&b,1,2);batch.ERR_StopDiscoveryFeedback();assert(batch.m_ERRIntelSeen.IsEmpty()&&batch.m_ERRIntelPending.IsEmpty()&&batch.discovered.removes==1&&batch.tick.removes==1);
|
||||
int calls=batch.rpcCount;batch.ERR_FlushDiscoveryFeedback(99999);batch.ERR_QueueDiscoveryFeedback(&b,0,2);assert(batch.rpcCount==calls&&batch.m_ERRIntelPending.IsEmpty());
|
||||
ERR_GameMode next;ERR_GameMode::current=&next;auto shown=SCR_ChatComponent::messages.size();batch.ERR_ReceiveDiscoveryFeedback(999,"old round");assert(SCR_ChatComponent::messages.size()==shown);
|
||||
// Remote delivery gates old sequence, absent controller, terminal and editor state.
|
||||
Replication::server=false;next.ERR_ReceiveDiscoveryFeedback(1,"remote");next.ERR_ReceiveDiscoveryFeedback(1,"duplicate");assert(SCR_ChatComponent::messages.size()==shown+1);
|
||||
game.controller=false;next.ERR_ReceiveDiscoveryFeedback(2,"absent");game.controller=true;next.active=false;next.ERR_ReceiveDiscoveryFeedback(2,"ended");next.active=true;SCR_Global::edit=true;next.ERR_ReceiveDiscoveryFeedback(2,"editor");SCR_Global::edit=false;assert(next.m_ERRIntelReceivedSequence==1);
|
||||
currentGame=nullptr;next.ERR_ReceiveDiscoveryFeedback(2,"no game");currentGame=&game;
|
||||
next.ERR_ReceiveDiscoveryFeedback(2,"new");assert(SCR_ChatComponent::messages.size()==shown+2);
|
||||
// Queue is bounded even for malformed/unusually large authored scenarios.
|
||||
Replication::server=true;ERR_GameMode bounded;ERR_GameMode::current=&bounded;ERR_LocationComponent many[65];for(auto& item:many)bounded.ERR_QueueDiscoveryFeedback(&item,0,1);assert(bounded.m_ERRIntelSeen.Count()==64&&bounded.m_ERRIntelPending.Count()==64);
|
||||
assert(ERR_IntelDiscoveryText::Message(0,0,"secret").empty());assert(ERR_IntelDiscoveryText::Message(1,0,"")=="A location identified. Check your field map.");
|
||||
}
|
||||
''')
|
||||
for required in ('OnLocationDiscovered().Insert(ERR_QueueDiscoveryFeedback)', 'OnTick().Insert(ERR_FlushDiscoveryFeedback)', 'OnStop().Insert(ERR_StopDiscoveryFeedback)', '[RplRpc(RplChannel.Reliable, RplRcver.Broadcast)]'):
|
||||
assert required in source
|
||||
for forbidden in ('HideHint(', 'CallLater(', 'Discover(', 'SetKnowledge('):
|
||||
assert forbidden not in source
|
||||
print('PASS: production Intel feedback coalescing, rough secrecy, rate limit, bounded dedupe, local/remote delivery and round cleanup; native UI/RPC not exercised')
|
||||
Reference in New Issue
Block a user