Reconcile radio-delivered body and status casualties by original roster identity
This commit is contained in:
@@ -7,7 +7,7 @@ s=path.read_text()
|
||||
assert 'GroupLookup' not in s and 'Spawn(' not in s and 'Accept(' not in s and 'm_Contacts' not in s
|
||||
assert 'GetPlayerManager' not in s and 'DeleteEntity' not in s and 'GetTargets' not in s
|
||||
code=re.sub(r'//[^\n]*','',s).replace('protected ','').replace('ref ','')
|
||||
for kind in ('ERR_HQBodyCandidate','ERR_HQBodyScan','ERR_HQReport'):
|
||||
for kind in ('ERR_HQBodyCandidate','ERR_HQBodyScan','ERR_HQReport','ERR_HQStatusSampler'):
|
||||
code=re.sub(r'\b'+kind+r' (?=\w)',kind+'* ',code)
|
||||
code=code.replace('array<'+kind+'>','Array<'+kind+'*>')
|
||||
code=code.replace('array<AIAgent>','Array<AIAgent>')
|
||||
@@ -19,8 +19,9 @@ code=code.replace('foreach (','for (')
|
||||
for name in ('Replication','ERR_HQMemoryRules','ERR_HQBodyRules','ERR_Actors','CompartmentAccessComponent','TraceFlags','ECharacterLifeState','vector'):
|
||||
code=code.replace(name+'.',name+'::')
|
||||
code=code.replace('null','nullptr')
|
||||
for name in ('mode','group','force','character','scan','created','agent','actor','entry','candidate','observer','body','trace','state','report','selected'):
|
||||
for name in ('mode','group','force','character','scan','created','agent','actor','entry','candidate','observer','body','trace','state','report','selected','sampler'):
|
||||
code=re.sub(r'\b'+name+r'\.',name+'->',code)
|
||||
code=code.replace('candidate->Roster.', 'candidate->Roster->')
|
||||
code=code.replace('scan->Group.GetPlayerCount', 'scan->Group->GetPlayerCount')
|
||||
code=code.replace('mode->Forces().','mode->Forces()->').replace('character->GetCharacterController().','character->GetCharacterController()->')
|
||||
code=code.replace('GetGame().GetWorld().','GetGame()->GetWorld()->')
|
||||
@@ -36,6 +37,8 @@ run(r'''
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <map>
|
||||
using string=std::string;
|
||||
struct vector {float x=0,y=0,z=0;float& operator[](int i){return i==0?x:i==1?y:z;}
|
||||
vector operator-(vector v)const{return {x-v.x,y-v.y,z-v.z};}
|
||||
vector operator+(vector v)const{return {x+v.x,y+v.y,z+v.z};}
|
||||
@@ -72,7 +75,16 @@ struct World {int traces=0;float fraction=1;IEntity hit=nullptr;
|
||||
float TraceMove(TraceParam* trace,void*){traces++;trace->TraceEnt=hit;return fraction;}};
|
||||
struct Game {World world;World* GetWorld(){return &world;}};Game game;Game* GetGame(){return &game;}
|
||||
int logs=0;void Print(std::string){logs++;}
|
||||
struct ERR_HQReport {IEntity Target=nullptr;Group* Source=nullptr;vector Position,Velocity;
|
||||
// Deterministic native roster adapter. Actual sampler bind/release methods have their own executable suite.
|
||||
struct ERR_HQStatusSampler {
|
||||
Group* group;Array<IEntity> initial;Array<int> released;string source="formation-1";
|
||||
ERR_HQStatusSampler(Group* g):group(g){for(auto agent:g->agents)initial.Insert(agent->actor);}
|
||||
bool OwnsRoster(Group* g){return group==g;}string InitialSourceId(){return source;}
|
||||
int InitialStrength(){return initial.Count();}IEntity InitialMember(int slot){return initial[slot];}
|
||||
bool CanAttribute(int slot,IEntity actor){return actor&&initial[slot]==actor&&!released.Contains(slot);}
|
||||
};
|
||||
ERR_HQStatusSampler* Roster(Group* group){return new ERR_HQStatusSampler(group);}
|
||||
struct ERR_HQReport {string CasualtySourceId;int CasualtySlot=-1,CasualtyStrength=0;IEntity Target=nullptr;Group* Source=nullptr;vector Position,Velocity;
|
||||
float ObservedAt=-1;std::string TravelMode;int MessageId=0;bool Distress=false,HasVelocity=false;};
|
||||
'''+code+r'''
|
||||
int main(){
|
||||
@@ -84,7 +96,9 @@ int main(){
|
||||
GameMode mode;GameMode::current=&mode;
|
||||
Entity observer,corpse;corpse.position={0,0,20};corpse.dead=true;
|
||||
Agent a,b;a.actor=&observer;b.actor=&corpse;Group group;group.agents={&a,&b};
|
||||
ERR_HQBodyObserver service;service.RegisterGroup(&group);service.RegisterGroup(&group);
|
||||
auto* immutable=Roster(&group); // first roster binds [observer,corpse]
|
||||
group.agents={&b,&a}; // current native order must not determine attribution
|
||||
ERR_HQBodyObserver service;service.RegisterGroup(&group,immutable);service.RegisterGroup(&group,immutable);
|
||||
assert(service.m_Roster.Count()==2&&service.m_Groups.Count()==1);
|
||||
service.TickBudget(0);assert(service.m_Bodies.Count()==1&&corpse.reads==0&&game.world.traces==0);
|
||||
ERR_HQReport* report=nullptr;game.world.fraction=.2;
|
||||
@@ -95,15 +109,19 @@ int main(){
|
||||
assert(report->Target==&corpse&&report->Source==&group&&report->TravelMode=="body");
|
||||
assert(report->Position.z==20&&report->ObservedAt==15000&&!report->MessageId);
|
||||
assert(!report->HasVelocity&&!report->Distress);
|
||||
assert(report->CasualtySourceId=="formation-1"&&report->CasualtySlot==1&&report->CasualtyStrength==2);
|
||||
corpse.position.z=30;assert(report->Position.z==20); // immutable output snapshot
|
||||
assert(!service.Observe(&group,15001,report));
|
||||
// Lost radio delivery receives no acknowledgement: fresh visibility permits retry.
|
||||
assert(service.Observe(&group,30000,report)&&report->Position.z==30);
|
||||
service.ConfirmDelivered(&corpse);assert(!service.Observe(&group,45000,report));
|
||||
// A previously known transfer/player conversion excludes the immutable slot permanently.
|
||||
immutable->released.Insert(1);assert(!service.Observe(&group,45000,report));
|
||||
immutable->released.Clear(); // test adapter restores fixture before delivery suppression
|
||||
service.ConfirmDelivered(&corpse);assert(!service.Observe(&group,60000,report));
|
||||
assert(service.m_Roster.Count()==2&&service.m_Bodies.IsEmpty());
|
||||
service.Stop();assert(service.m_Roster.IsEmpty()&&service.m_Groups.IsEmpty()&&service.m_Traces==0);
|
||||
// Unconscious/incapacitated is not a corpse; own physical awake observers only.
|
||||
corpse.dead=false;corpse.awake=false;service.RegisterGroup(&group);service.TickBudget(0);
|
||||
corpse.dead=false;corpse.awake=false;service.RegisterGroup(&group,Roster(&group));service.TickBudget(0);
|
||||
assert(service.m_Bodies.IsEmpty());corpse.dead=true;observer.awake=false;
|
||||
assert(!service.Observe(&group,15000,report));observer.awake=true;observer.inVehicle=true;
|
||||
assert(!service.Observe(&group,30000,report));observer.inVehicle=false;
|
||||
@@ -114,7 +132,7 @@ int main(){
|
||||
// Twelve native LOS calls maximum across multiple groups in the same interval.
|
||||
service.Stop();Entity dead[24];Agent da[24];Group many;many.agents={&a};
|
||||
for(int i=0;i<24;i++){dead[i].dead=true;dead[i].position={0,0,float(i+15)};da[i].actor=&dead[i];many.agents.Insert(&da[i]);}
|
||||
service.RegisterGroup(&many);game.world.fraction=.2;game.world.traces=0;
|
||||
service.RegisterGroup(&many,Roster(&many));game.world.fraction=.2;game.world.traces=0;
|
||||
assert(!service.Observe(&many,0,report));assert(game.world.traces==12&&service.m_Traces==0);
|
||||
service.TickBudget(4999);assert(service.m_Traces==0);service.TickBudget(5000);assert(service.m_Traces==12);
|
||||
assert(service.m_Bodies.Count()==16);
|
||||
@@ -128,22 +146,22 @@ int main(){
|
||||
nearBody.position.z=-13;int before=game.world.traces;assert(!service.Visible(&observer,&nearBody,point));
|
||||
assert(game.world.traces==before);
|
||||
// Oldest due group wins even when the first three callers exhaust every trace budget.
|
||||
service.Stop();Group contenders[4];for(int i=0;i<4;i++){contenders[i].agents=many.agents;service.RegisterGroup(&contenders[i]);}
|
||||
service.Stop();Group contenders[4];for(int i=0;i<4;i++){contenders[i].agents=many.agents;service.RegisterGroup(&contenders[i],Roster(&contenders[i]));}
|
||||
game.world.hit=nullptr;game.world.fraction=.2;
|
||||
for(int t=0;t<=15000;t+=1000)for(int g=0;g<4;g++)service.Observe(&contenders[g],t,report);
|
||||
assert(service.m_Groups[3]->NextScan==30000); // fourth group actually consumed the t15s turn
|
||||
assert(service.m_Groups[0]->NextScan==15000); // first did not leapfrog never-scanned groups
|
||||
// A caller that becomes inactive can delay others at most one budget window.
|
||||
service.Stop();service.RegisterGroup(&contenders[0]);service.RegisterGroup(&contenders[1]);
|
||||
service.Stop();service.RegisterGroup(&contenders[0],Roster(&contenders[0]));service.RegisterGroup(&contenders[1],Roster(&contenders[1]));
|
||||
service.m_Groups[0]->LastAsked=0;service.m_Groups[1]->NextScan=1;
|
||||
assert(!service.Observe(&contenders[1],1000,report));
|
||||
service.Observe(&contenders[1],6000,report);assert(service.m_Groups[1]->NextScan==21000);
|
||||
// Registry cap is finite, logs once, and deleted identities free future slots.
|
||||
service.Stop();logs=0;Entity actors[160];Agent agents[160];Group groups[5];
|
||||
for(int g=0;g<5;g++){for(int i=0;i<32;i++){int n=g*32+i;agents[n].actor=&actors[n];groups[g].agents.Insert(&agents[n]);}service.RegisterGroup(&groups[g]);}
|
||||
assert(service.m_Roster.Count()==128&&logs==1);service.RegisterGroup(&groups[4]);assert(logs==1);
|
||||
for(int g=0;g<5;g++){for(int i=0;i<32;i++){int n=g*32+i;agents[n].actor=&actors[n];groups[g].agents.Insert(&agents[n]);}service.RegisterGroup(&groups[g],Roster(&groups[g]));}
|
||||
assert(service.m_Roster.Count()==128&&logs==1);service.RegisterGroup(&groups[4],Roster(&groups[4]));assert(logs==1);
|
||||
for(int i=0;i<32;i++)service.m_Roster[i]->Corpse=nullptr;
|
||||
service.RegisterGroup(&groups[4]);assert(service.m_Roster.Count()==128);
|
||||
service.RegisterGroup(&groups[4],Roster(&groups[4]));assert(service.m_Roster.Count()==128);
|
||||
service.Stop();assert(service.m_Roster.IsEmpty());
|
||||
}
|
||||
''')
|
||||
|
||||
Reference in New Issue
Block a user