Reconcile radio-delivered body and status casualties by original roster identity
This commit is contained in:
@@ -17,7 +17,8 @@ def cpp(s):
|
||||
s=s.replace('foreach (AIAgent* agent : m_Agents)','for (AIAgent* agent : m_Agents)')
|
||||
return s
|
||||
methods='\n'.join(cpp(body(P,s)) for s in ('bool Bound()', 'protected bool Eligible(', 'bool Bind(',
|
||||
'bool ObserveDeaths(', 'ERR_HQStatusReport Sample(', 'void Clear('))
|
||||
'string InitialSourceId(', 'int InitialStrength(', 'bool OwnsRoster(', 'IEntity InitialMember(',
|
||||
'bool CanAttribute(', 'bool ObserveDeaths(', 'ERR_HQStatusReport Sample(', 'void Clear('))
|
||||
common='\n'.join(cpp(body(U,s)) for s in ('static bool Finite(', 'static bool PositionValid('))
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
@@ -57,7 +58,7 @@ struct SCR_FactionManager{static Faction* SGetFaction(IEntity* a){return a?a->fa
|
||||
struct ERR_HQRadio{static int Capability(IEntity* a){return ERR_Actors::CanFight(a)&&a->radio?1:0;}};
|
||||
struct ERR_HQStatusReport{
|
||||
string SourceId;int MessageId=0;vector Position;int Strength=0,Count=0,Fit=0,ConfirmedLosses=0;
|
||||
float ObservedAt=-1;
|
||||
float ObservedAt=-1;Array<int> DeathSlots;
|
||||
};
|
||||
struct ERR_HQPursuitRules{
|
||||
'''+common+r'''
|
||||
@@ -82,6 +83,11 @@ int main(){
|
||||
group.agents[3]=&agents[2];assert(!sample.Bind(&group,"site",4,&enemy));group.agents[3]=&agents[3];
|
||||
assert(sample.Bind(&group,"site",4,&enemy)&&sample.Bound());
|
||||
assert(sample.Bind(&group,"site",4,&enemy)&&!sample.Bind(&group,"other-source",4,&enemy));
|
||||
assert(sample.InitialSourceId()=="site"&&sample.InitialStrength()==4&&sample.OwnsRoster(&group));
|
||||
assert(sample.InitialMember(1)==&members[1]&&!sample.InitialMember(-1)&&!sample.InitialMember(4));
|
||||
std::reverse(group.agents.begin(),group.agents.end());
|
||||
assert(sample.InitialMember(1)==&members[1]&&sample.CanAttribute(1,&members[1]));
|
||||
assert(!sample.CanAttribute(1,&members[2]));std::reverse(group.agents.begin(),group.agents.end());
|
||||
auto first=sample.Sample(1000,1,&members[0]);assert(first&&first->Count==4&&first->Fit==4&&first->Strength==4);
|
||||
assert(first->Position.x==100&&first->SourceId=="site");
|
||||
group.position.x=200;assert(first->Position.x==100); // detached snapshot
|
||||
@@ -91,14 +97,22 @@ int main(){
|
||||
members[1].damage.dead=true;members[1].group=nullptr;group.agents.erase(group.agents.begin()+1);
|
||||
assert(sample.ObserveDeaths()&&sample.m_Deaths==1);
|
||||
assert(sample.ObserveDeaths()&&sample.m_Deaths==1); // counted once
|
||||
assert(sample.CanAttribute(1,&members[1]));
|
||||
sample.m_Group=nullptr; // native all-dead group handle invalidates while the retained corpse still exists
|
||||
assert(sample.CanAttribute(1,&members[1])); // candidate-held sampler preserves original slot without live group
|
||||
sample.m_Group=&group;
|
||||
sample.m_Members[1]=nullptr; // simulate native weak-reference invalidation after corpse cleanup
|
||||
auto losses=sample.Sample(3000,3,&members[0]);assert(losses&&losses->Count==3&&losses->ConfirmedLosses==1);
|
||||
assert(losses->DeathSlots.Count()==1&&losses->DeathSlots[0]==1);
|
||||
assert(first->DeathSlots.Count()==0); // earlier report array is detached
|
||||
// Deleted unknown actor and missing damage component cannot be manufactured into deaths.
|
||||
sample.m_Members[2]=nullptr;members[3].hasDamage=false;
|
||||
auto absent=sample.Sample(4000,4,&members[0]);
|
||||
assert(absent&&absent->Count==1&&absent->ConfirmedLosses==1);
|
||||
members[3].hasDamage=true;members[3].player=true;assert(sample.ObserveDeaths());
|
||||
assert(!sample.CanAttribute(3,&members[3]));
|
||||
members[3].player=false;members[3].damage.dead=true;assert(sample.ObserveDeaths()&&sample.m_Deaths==1);
|
||||
assert(!sample.CanAttribute(3,&members[3])); // released slot cannot be rediscovered as our casualty
|
||||
// Never adopt an externally attached replacement as one of the authored original members.
|
||||
IEntity replacement;replacement.group=&group;replacement.faction=&enemy;AIAgent replacementAgent;replacementAgent.actor=&replacement;
|
||||
group.agents.Insert(&replacementAgent);assert(!sample.Sample(5000,5,&replacement));
|
||||
@@ -114,6 +128,35 @@ int main(){
|
||||
// No sender survives: local evidence may update, but a radio report cannot be fabricated.
|
||||
members[0].damage.dead=true;assert(sample.ObserveDeaths()&&sample.m_Deaths==2);
|
||||
assert(!sample.Sample(7000,7,&members[0])&&!sample.Sample(7000,7,nullptr));
|
||||
|
||||
// Transfer and death between slow ticks: a retained foreign group proves the
|
||||
// old initial slot was released, even without an earlier living observation.
|
||||
SCR_AIGroup source,destination;source.faction=&enemy;destination.faction=&enemy;
|
||||
IEntity moved,stayed,recipient;AIAgent movedAgent,stayedAgent,recipientAgent;
|
||||
moved.faction=&enemy;moved.group=&source;movedAgent.actor=&moved;
|
||||
stayed.faction=&enemy;stayed.group=&source;stayedAgent.actor=&stayed;
|
||||
recipient.faction=&enemy;recipient.group=&destination;recipientAgent.actor=&recipient;
|
||||
source.agents.Insert(&stayedAgent);source.agents.Insert(&movedAgent);destination.agents.Insert(&recipientAgent);
|
||||
// Independent source instances exercise body and status gates without one
|
||||
// gate's release masking a bug in the other gate.
|
||||
Sampler priorStatus,priorBody,later;
|
||||
assert(priorStatus.Bind(&source,"before-transfer",2,&enemy));
|
||||
assert(priorBody.Bind(&source,"before-transfer",2,&enemy));
|
||||
moved.group=&destination;source.agents.pop_back();destination.agents.Insert(&movedAgent);
|
||||
assert(later.Bind(&destination,"after-transfer",2,&enemy));moved.damage.dead=true;
|
||||
assert(!priorBody.CanAttribute(1,&moved)&&priorBody.m_Released[1]);
|
||||
assert(priorStatus.ObserveDeaths()&&priorStatus.m_Deaths==0&&priorStatus.m_Released[1]);
|
||||
assert(later.ObserveDeaths()&&later.m_Deaths==1);
|
||||
auto oldFormation=priorStatus.Sample(8000,8,&stayed);
|
||||
auto newFormation=later.Sample(8000,9,&recipient);
|
||||
assert(oldFormation&&oldFormation->Count==1&&oldFormation->ConfirmedLosses==0&&oldFormation->DeathSlots.Count()==0);
|
||||
assert(newFormation&&newFormation->Count==1&&newFormation->ConfirmedLosses==1&&newFormation->DeathSlots[0]==1);
|
||||
// Returning to the original group cannot reactivate a released slot.
|
||||
moved.group=&source;assert(!priorBody.CanAttribute(1,&moved));
|
||||
assert(priorStatus.ObserveDeaths()&&priorStatus.m_Deaths==0);
|
||||
// Normal native corpse detachment still permits its destination attribution.
|
||||
moved.group=nullptr;assert(later.CanAttribute(1,&moved));
|
||||
assert(!priorBody.CanAttribute(1,&moved)&&priorStatus.ObserveDeaths()&&priorStatus.m_Deaths==0);
|
||||
sample.Clear();assert(!sample.Bound()&&sample.m_Members.Count()==0&&sample.m_Deaths==0);
|
||||
}
|
||||
''')
|
||||
|
||||
Reference in New Issue
Block a user