Integrate physical radio calls and finite vehicle maintenance

This commit is contained in:
2026-09-20 06:37:17 +02:00
parent 963cab50c0
commit 915476f709
152 changed files with 26426 additions and 28 deletions
+34 -13
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env python3
"""Execute production bounded radio graph and hop-delivery state machine."""
from pathlib import Path
import re, subprocess, tempfile
import os, re, subprocess, tempfile
ROOT=Path(__file__).resolve().parents[1]
source=(ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQNetwork.c').read_text()
source=Path(os.environ.get('ERR_NETWORK_SOURCE', ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQNetwork.c')).read_text()
def extract(signature):
a=source.index(signature);b=source.index('{',a);end=b+1;depth=1
while depth:depth+=(source[end]=='{')-(source[end]=='}');end+=1
@@ -43,7 +43,7 @@ struct SCR_AIGroup{bool enemy=true;};struct IEntity{vector pos;bool alive=true;i
struct ERR_HQRadioNode{IEntity* Carrier{};SCR_AIGroup* Group{};int UnitId{},Capability{};bool Valid(bool manpack=false){return Carrier&&Carrier->alive&&!Carrier->captured&&Carrier->radio>=(manpack?2:1);}std::string Label(){return "unit "+std::to_string(UnitId);}};
struct ERR_HQJournal{Arr<std::string> events;void Write(std::string kind,std::string text,int){events.Insert(kind+" "+text);}};
'''
routefields='Arr<ERR_HQRadioNode*> Nodes;bool Downlink{},Sending{},Failed{};int Cursor{},MessageId{};float Due{},StartedAt=-1;vector SentFrom;std::string Payload;'
routefields='Arr<ERR_HQRadioNode*> Nodes;bool Downlink{},Sending{},Failed{},Originated{};int Cursor{},MessageId{};float Due{},StartedAt=-1,OriginDue=-1;vector SentFrom;std::string Payload;'
networkfields='Arr<ERR_HQRadioNode*> m_Nodes;Arr<int> m_Parents,m_Depths;Map<IEntity*,int> m_Indices;Map<std::string,Arr<int>*> m_Cells;bool Overflow{};'
route='struct ERR_HQRoute{'+routefields+'\n'+'\n'.join(trans(extract(x)) for x in ['int Advance(', 'protected string SenderLabel(', 'protected int Fail('])+'};\n'
network='struct Network{'+networkfields+'\n'+'\n'.join(trans(extract(x)) for x in ['void Clear(', 'protected string Cell(', 'void Add(', 'void Build(', 'ERR_HQRoute Route('])+'};\n'
@@ -53,19 +53,40 @@ int main(){
c[0].radio=2;for(int i=0;i<5;i++)network.Add(&c[i],&group,37+i,c[i].radio);network.Build();
assert(network.Route(&c[0])->Nodes.Count()==1);assert(network.Route(&c[3])->Nodes.Count()==4);assert(!network.Route(&c[4]));
ERR_HQJournal journal;auto up=network.Route(&c[2]);assert(up->Nodes.Count()==3);
assert(up->Advance(0,&journal)==0);assert(up->Advance(1999,&journal)==0);assert(up->Advance(2000,&journal)==0);
c[2].alive=false;assert(up->Advance(4000,&journal)==0);c[0].alive=false;assert(up->Advance(6000,&journal)==-1);
assert(up->Advance(0,&journal)==0);assert(!up->Sending&&!up->Originated&&up->OriginDue==3000);
assert(journal.events.Count()==1&&journal.events.back().find("CALL ")==0);
assert(up->Advance(2999,&journal)==0);assert(journal.events.Count()==1);
assert(up->Advance(3000,&journal)==0);assert(up->Sending&&up->Originated&&up->Due==5000);
assert(up->Advance(5000,&journal)==0);
c[2].alive=false;assert(up->Advance(7000,&journal)==0);c[0].alive=false;assert(up->Advance(9000,&journal)==-1);
assert(journal.events.back().find("LOST")!=std::string::npos); // final transmission needs its live sender
for(auto& carrier:c)carrier.alive=true;
auto good=network.Route(&c[2]);assert(good->Advance(0,&journal)==0);assert(good->Advance(2000,&journal)==0);
c[2].alive=false;assert(good->Advance(4000,&journal)==0);assert(good->Advance(6000,&journal)==1);
assert(journal.events.back().find("HQ received")!=std::string::npos); // an already completed earlier hop survives its sender
auto severed=network.Route(&c[0]);assert(severed->Advance(0,&journal)==0);c[0].radio=0;assert(severed->Advance(2000,&journal)==-1);c[0].radio=2;
auto good=network.Route(&c[2]);assert(good->Advance(0,&journal)==0);assert(good->Advance(3000,&journal)==0);assert(good->Advance(5000,&journal)==0);
c[2].alive=false;assert(good->Advance(7000,&journal)==0);assert(good->Advance(9000,&journal)==1);
assert(journal.events.back().find("HQ received")!=std::string::npos); // a completed earlier hop survives its sender
auto severed=network.Route(&c[0]);assert(severed->Advance(0,&journal)==0);assert(severed->Advance(3000,&journal)==0);c[0].radio=0;assert(severed->Advance(5000,&journal)==-1);c[0].radio=2;
for(auto& carrier:c)carrier.alive=true;
auto lost=network.Route(&c[2]);assert(lost->Advance(0,&journal)==0);c[1].radio=0;assert(lost->Advance(2000,&journal)==-1);assert(lost->Advance(4000,&journal)==-1);
c[1].radio=1;auto moving=network.Route(&c[2]);moving->Advance(0,&journal);c[1].pos=V(499);assert(moving->Advance(2000,&journal)==-1);c[1].pos=V(500);
auto down=network.Route(&c[2],true);assert(down->Nodes[0]->Carrier==&c[0]);assert(down->Advance(0,&journal)==0);
auto lost=network.Route(&c[2]);assert(lost->Advance(0,&journal)==0);assert(lost->Advance(3000,&journal)==0);c[1].radio=0;assert(lost->Advance(5000,&journal)==-1);assert(lost->Advance(7000,&journal)==-1);
c[1].radio=1;auto moving=network.Route(&c[2]);moving->Advance(0,&journal);moving->Advance(3000,&journal);c[1].pos=V(499);assert(moving->Advance(5000,&journal)==-1);c[1].pos=V(500);
// No originating transmission may survive radio loss, death/incapacitation, or capture during the hold.
for(int reason=0;reason<3;reason++){
auto interrupted=network.Route(&c[0]);ERR_HQJournal calls;assert(interrupted->Advance(10000,&calls)==0);
if(reason==0)c[0].radio=1;
if(reason==1)c[0].alive=false;
if(reason==2)c[0].captured=true;
assert(interrupted->Advance(11500,&calls)==-1);assert(!interrupted->Sending&&!interrupted->Originated);
assert(calls.events.Count()==2&&calls.events[0].find("CALL ")==0&&calls.events[1].find("LOST ")==0);
c[0].radio=2;c[0].alive=true;c[0].captured=false;
assert(interrupted->Advance(15000,&calls)==-1);assert(calls.events.Count()==2);
}
auto direct=network.Route(&c[0]);ERR_HQJournal directLog;
assert(direct->Advance(0,&directLog)==0);assert(direct->Advance(2999,&directLog)==0);
assert(direct->Advance(3000,&directLog)==0);assert(direct->Advance(4999,&directLog)==0);assert(direct->Advance(5000,&directLog)==1);
assert(directLog.events.Count()==4); // CALL, CALL COMPLETE, TRANSMIT, RECEIVE exactly once.
ERR_HQRoute empty;assert(empty.Advance(0,&journal)==-1);assert(!empty.Originated&&!empty.Sending);
int beforeDown=journal.events.Count();auto down=network.Route(&c[2],true);assert(down->Nodes[0]->Carrier==&c[0]);assert(down->Advance(0,&journal)==0);
assert(down->Advance(2000,&journal)==0);assert(down->Advance(4000,&journal)==0);assert(down->Advance(6000,&journal)==1);
assert(!down->Originated&&down->OriginDue==-1);for(int j=beforeDown;j<journal.events.Count();j++)assert(journal.events[j].find("CALL")!=0);
auto captured=network.Route(&c[2],true);captured->Advance(0,&journal);c[0].captured=true;assert(captured->Advance(2000,&journal)==-1);c[0].captured=false;
auto timeout=network.Route(&c[2]);timeout->Advance(0,&journal);assert(timeout->Advance(30001,&journal)==-1);
Network disconnected;disconnected.Add(&c[1],&group,1,1);disconnected.Build();assert(!disconnected.Route(&c[1]));
@@ -73,7 +94,7 @@ int main(){
network.Clear();assert(!network.Route(&c[0]));
Network crowded;std::vector<IEntity> many(2050);for(auto& carrier:many){carrier.radio=2;crowded.Add(&carrier,&group,1,2);}assert(crowded.Overflow&&crowded.m_Nodes.Count()==2048);
crowded.Build();assert(crowded.Overflow); // bounded dense graph work
std::cout<<"PASS: real-carrier shortest-hop paths, 500m boundary, three-hop cap, no-manpack failure, hop timing, completed-hop survival, interrupted final uplink, radio loss, capture, motion, timeout, reset and network bounds\n";
std::cout<<"PASS: real-carrier shortest-hop paths, 500m boundary, three-hop cap, no-manpack failure, three-second originating-call interruption, unchanged downlink hop timing, completed-hop survival, interrupted final uplink, radio loss, capture, motion, timeout, reset and network bounds\n";
}
'''
with tempfile.TemporaryDirectory(prefix='eai-hq-network-') as tmp: