Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
+88
-11
@@ -9,13 +9,14 @@ def cpp(s):
|
||||
('ERR_HQWatchRules.','ERR_HQWatchRules::'),('null','nullptr'),
|
||||
('report.SourceId.IsEmpty()','report.SourceId.empty()'),('report.SourceId.Length()','report.SourceId.size()')]:
|
||||
s=s.replace(old,new)
|
||||
for typ in ('ERR_HQStatusReport','ERR_HQWatchEntry','ERR_HQReport','ERR_HQDeliveredCasualties','ERR_HQAmmoSnapshot'):
|
||||
for typ in ('ERR_HQStatusReport','ERR_HQWatchEntry','ERR_HQReport','ERR_HQDeliveredCasualties','ERR_HQAmmoSnapshot','ERR_HQBelief'):
|
||||
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
||||
for var in ('entry','report','validation'):s=s.replace(var+'.',var+'->')
|
||||
for var in ('entry','report','validation','contact','belief'):s=s.replace(var+'.',var+'->')
|
||||
s=s.replace('entry->Casualties.', 'entry->Casualties->')
|
||||
s=s.replace('report->CasualtySourceId.IsEmpty()', 'report->CasualtySourceId.empty()')
|
||||
s=s.replace('report->CasualtySourceId.Length()', 'report->CasualtySourceId.size()')
|
||||
s=s.replace('report->DeathSlots,', '&report->DeathSlots,')
|
||||
s=s.replace('entry->MountedAmmo.', 'entry->MountedAmmo->').replace('report->MountedAmmo.', 'report->MountedAmmo->')
|
||||
s=s.replace('entry->AmmoState.', 'entry->AmmoState->').replace('report->AmmoState.', 'report->AmmoState->')
|
||||
return s
|
||||
helper=(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQDeliveredCasualties.c').read_text()
|
||||
@@ -29,6 +30,11 @@ ammo=ammo_source[:ammo_source.index('class ERR_HQAmmoSampler')]
|
||||
ammo=ammo.replace('class ','struct ').replace('ERR_HQAmmoSnapshot Snapshot(', 'ERR_HQAmmoSnapshot* Snapshot(')
|
||||
ammo=ammo.replace('ERR_HQAmmoSnapshot copy', 'ERR_HQAmmoSnapshot* copy').replace('copy.', 'copy->')
|
||||
ammo=re.sub(r'\b(bool|int) (\w+);',r'\1 \2=0;',ammo).replace('\n}\n','\n};\n')
|
||||
mounted_source=(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c').read_text()
|
||||
mounted=mounted_source[:mounted_source.index('class ERR_HQMountedAmmoSampler')]
|
||||
mounted=mounted.replace('class ', 'struct ').replace('ERR_HQMountedAmmoSnapshot Snapshot(', 'ERR_HQMountedAmmoSnapshot* Snapshot(')
|
||||
mounted=mounted.replace('ERR_HQMountedAmmoSnapshot copy', 'ERR_HQMountedAmmoSnapshot* copy').replace('copy.', 'copy->')
|
||||
mounted=re.sub(r'\b(bool|int) (\w+);', r'\1 \2=0;', mounted).replace('\n}\n', '\n};\n')
|
||||
rules='\n'.join(cpp(body(P,s)) for s in ('static bool Valid(', 'static bool RequestDue(',
|
||||
'static bool InvestigationDue(', 'static int SquadLimit(', 'static int Aggression(', 'static int Hunters('))
|
||||
methods='\n'.join(cpp(body(P,s)) for s in ('int Count()', 'ERR_HQWatchEntry Find(', 'ERR_HQWatchEntry At(',
|
||||
@@ -37,11 +43,16 @@ methods='\n'.join(cpp(body(P,s)) for s in ('int Count()', 'ERR_HQWatchEntry Find
|
||||
'bool MarkRequested(', 'bool InvestigationDue(string', 'bool MarkInvestigating(', 'bool FinishInvestigation(', 'void Clear('))
|
||||
common='\n'.join(cpp(body(U,s)) for s in ('static bool Finite(', 'static bool PositionValid('))
|
||||
snapshot=cpp(body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQReport.c','ERR_HQReport Snapshot(')).replace('copy.', 'copy->')
|
||||
accept=cpp(body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c','protected void Accept('))
|
||||
accept=cpp(body(Path(os.environ.get('ERR_HQ_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c')),'protected void Accept('))
|
||||
accept=accept.replace('ERR_MissionRules.', 'ERR_MissionRules::').replace('ERR_HQMemoryRules.', 'ERR_HQMemoryRules::')
|
||||
accept=accept.replace('ERR_HQMemoryKind.', 'ERR_HQMemoryKind::').replace('vector.DistanceXZ(', 'vector::DistanceXZ(')
|
||||
accept=accept.replace('previous.', 'previous->')
|
||||
accept=accept.replace('previous.', 'previous->').replace('vector.Zero', 'vector{}')
|
||||
accept=accept.replace('string.Format(', 'Format(')
|
||||
# Execute the real existing HQ uplink delivery branch, with the native route result doubled.
|
||||
uplink=body(Path(os.environ.get('ERR_HQ_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c')), 'if (unit.Uplink)')
|
||||
uplink=cpp(uplink).replace('unit.', 'unit->')
|
||||
for field in ('Uplink','PendingSweep','Task'):
|
||||
uplink=uplink.replace('unit->'+field+'.','unit->'+field+'->')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
@@ -70,7 +81,7 @@ template<class K,class V>struct Map:std::map<K,V>{
|
||||
struct ERR_HQPursuitRules{
|
||||
'''+common+r'''
|
||||
};
|
||||
'''+helper+ammo+r'''
|
||||
'''+helper+ammo+mounted+r'''
|
||||
struct ERR_HQReport{
|
||||
string RoadStatus="road unknown"; string TravelMode="body",CasualtySourceId="patrol-1";bool Distress=false;int MessageId=99,CasualtySlot=0,CasualtyStrength=4;
|
||||
vector Position{900,30,900},Velocity;float ObservedAt=1000,DetailsObservedAt=-1;
|
||||
@@ -80,12 +91,12 @@ struct ERR_HQReport{
|
||||
};
|
||||
struct ERR_HQStatusReport{
|
||||
string SourceId="patrol-1";int MessageId=1;vector Position{100,20,300};
|
||||
ERR_HQAmmoSnapshot* AmmoState=nullptr;
|
||||
ERR_HQAmmoSnapshot* AmmoState=nullptr;ERR_HQMountedAmmoSnapshot* MountedAmmo=nullptr;
|
||||
int Strength=4,Count=4,Fit=4,ConfirmedLosses=0;float ObservedAt=0;Array<int> DeathSlots;
|
||||
};
|
||||
struct ERR_HQWatchEntry{
|
||||
string SourceId;int MessageId=0,RequestId=0;vector Position;
|
||||
ERR_HQAmmoSnapshot* AmmoState=nullptr;
|
||||
ERR_HQAmmoSnapshot* AmmoState=nullptr;ERR_HQMountedAmmoSnapshot* MountedAmmo=nullptr;
|
||||
int Strength=0,Count=-1,Fit=-1,ConfirmedLosses=0;Array<int> IdentifiedSlots;
|
||||
ERR_HQDeliveredCasualties* Casualties=new ERR_HQDeliveredCasualties();float LastBodyDeliveredAt=-1;
|
||||
float ObservedAt=-1,LastDeliveredAt=-1,AskedAt=-1,NextAttempt=0;
|
||||
@@ -102,17 +113,82 @@ struct Watch{
|
||||
struct ERR_MissionRules{static bool ContactFresh(float t,float now){return std::isfinite(t)&&std::isfinite(now)&&t>=0&&now>=t&&now-t<=120000;}};
|
||||
struct ERR_HQMemoryKind{static constexpr int SIGHTING=0,DISTRESS=1,BODY=2;};
|
||||
struct ERR_HQMemoryRules{static int Kind(ERR_HQReport* r){return r->Distress?1:r->TravelMode=="body"?2:0;}};
|
||||
struct MemoryAdapter{int attempts=0;bool Ingest(ERR_HQReport* r,float){attempts++;return r->Target!=nullptr;}int Count(){return 0;}};
|
||||
struct ERR_HQBelief{bool HasVelocity=false;vector Velocity;};
|
||||
struct MemoryAdapter{ERR_HQBelief* Get(void*){return nullptr;}int attempts=0;bool Ingest(ERR_HQReport* r,float){attempts++;return r->Target!=nullptr;}int Count(){return 0;}};
|
||||
struct BodyAdapter{int confirmations=0;void ConfirmDelivered(void*){confirmations++;}};
|
||||
struct CoverageAdapter{int forgotten=0;void Forget(vector){forgotten++;}};
|
||||
struct CoverageAdapter{int forgotten=0;float observedAt=-1;void Forget(vector,float time){forgotten++;observedAt=time;}};
|
||||
struct JournalAdapter{void Write(string,string,int){}};
|
||||
template<class... Args> string Format(string,Args...){return "delivered";}
|
||||
struct HQDelivery{float m_NextAssignment=5000;
|
||||
struct HQDelivery{struct LocalCoverage{void Forget(vector,float){}}m_LocalCoverage;float m_NextAssignment=5000;
|
||||
Watch m_Watch;MemoryAdapter m_Memory;BodyAdapter m_Bodies;struct RecoveryAdapter{int calls=0;void ReportBody(ERR_HQReport*){calls++;}} m_Recovery;CoverageAdapter m_Coverage;JournalAdapter m_Journal;
|
||||
Array<ERR_HQReport*>m_Contacts;
|
||||
'''+accept+r'''
|
||||
};
|
||||
struct SweepWire {int BeliefId=0,BeliefRevision=0,WatchMessageId=0,WatchRequestId=0;string WatchSourceId;bool SweepQueued=false;};
|
||||
struct RouteWire {int result=0,calls=0;int Advance(float,JournalAdapter&){calls++;return result;}};
|
||||
struct StatusUnit {void* PendingExecution=nullptr;RouteWire* Uplink=nullptr;ERR_HQStatusReport* PendingStatus=nullptr;ERR_HQReport* PendingReport=nullptr;SweepWire *PendingSweep=nullptr,*Task=nullptr;void* PendingSupplyStatus=nullptr;void* PendingMortarSpot=nullptr;};
|
||||
struct RadioIntake {void AcceptExecution(StatusUnit*,float){assert(false);}struct Mortars {int delivered=0;} m_Mortars;void AcceptMortarSpot(void*,float){m_Mortars.delivered++;}Watch m_Watch;JournalAdapter m_Journal;struct Logistics {void Receive(void*,float){assert(false);}}m_Logistics;
|
||||
void AcceptSweep(SweepWire*,float){assert(false);}void Accept(ERR_HQReport*,float){assert(false);}
|
||||
void Deliver(StatusUnit* unit,float now) {
|
||||
'''+uplink+r'''
|
||||
}
|
||||
};
|
||||
int main(){
|
||||
// Physical mine evidence is detached at sampling, then copied again only on delivered radio.
|
||||
ERR_HQAmmoSnapshot original;original.Complete=true;original.Members=4;
|
||||
original.LauncherComplete=true;original.LauncherMuzzles=2;original.MineComplete=true;original.MineMembers=4;original.ReadyMines=3;
|
||||
ERR_HQStatusReport mineWire;mineWire.AmmoState=original.Snapshot();
|
||||
original.LauncherMuzzles=0;original.ReadyMines=0;original.MineComplete=false;original.MineMembers=0;
|
||||
assert(mineWire.AmmoState!=&original&&mineWire.AmmoState->ReadyMines==3&&mineWire.AmmoState->MineComplete&&mineWire.AmmoState->MineMembers==4);
|
||||
assert(mineWire.AmmoState->LauncherMuzzles==2);
|
||||
RadioIntake radio;StatusUnit sender;RouteWire route;sender.Uplink=&route;sender.PendingStatus=&mineWire;
|
||||
radio.Deliver(&sender,1000);assert(!radio.m_Watch.Count()&&sender.PendingStatus==&mineWire); // still travelling
|
||||
route.result=-1;radio.Deliver(&sender,2000);assert(!radio.m_Watch.Count()&&!sender.PendingStatus&&!sender.Uplink); // interrupted route cannot deliver
|
||||
sender.Uplink=&route;sender.PendingStatus=&mineWire;route.result=1;radio.Deliver(&sender,3000);
|
||||
auto mines=radio.m_Watch.Find(mineWire.SourceId);assert(mines&&mines->AmmoState!=mineWire.AmmoState&&mines->AmmoState->ReadyMines==3&&mines->AmmoState->MineComplete&&mines->AmmoState->MineMembers==4);
|
||||
assert(mines->AmmoState->LauncherMuzzles==2);mineWire.AmmoState->LauncherMuzzles=0;assert(mines->AmmoState->LauncherMuzzles==2);
|
||||
mineWire.AmmoState->ReadyMines=0;mineWire.AmmoState->MineComplete=false;assert(mines->AmmoState->ReadyMines==3&&mines->AmmoState->MineComplete);
|
||||
// Mortar feedback shares the real uplink dispatcher without bypassing delivery.
|
||||
int correction=1;sender.PendingMortarSpot=&correction;sender.Uplink=&route;route.result=0;
|
||||
radio.Deliver(&sender,3100);assert(radio.m_Mortars.delivered==0&&sender.PendingMortarSpot);
|
||||
route.result=-1;radio.Deliver(&sender,3200);assert(radio.m_Mortars.delivered==0&&!sender.PendingMortarSpot);
|
||||
sender.PendingMortarSpot=&correction;sender.Uplink=&route;route.result=1;
|
||||
radio.Deliver(&sender,3300);assert(radio.m_Mortars.delivered==1&&!sender.PendingMortarSpot&&!sender.Uplink);
|
||||
// Relabelled stale observations and old radio arrivals cannot overwrite the latest delivered evidence.
|
||||
mineWire.MessageId=2;sender.Uplink=&route;sender.PendingStatus=&mineWire;radio.Deliver(&sender,4000);
|
||||
assert(mines->MessageId==1&&mines->AmmoState->ReadyMines==3&&!sender.PendingStatus);
|
||||
mineWire.ObservedAt=4000;sender.Uplink=&route;sender.PendingStatus=&mineWire;radio.Deliver(&sender,124001);
|
||||
assert(mines->MessageId==1&&mines->AmmoState->MineComplete);
|
||||
sender.Uplink=&route;sender.PendingStatus=&mineWire;radio.Deliver(&sender,5000);
|
||||
assert(mines->MessageId==2&&!mines->AmmoState->MineComplete&&mines->AmmoState->ReadyMines==0&&mines->AmmoState->MineMembers==4);
|
||||
mineWire.MessageId=3;mineWire.ObservedAt=5000;mineWire.AmmoState->MineComplete=true;mineWire.AmmoState->MineMembers=3;
|
||||
assert(!radio.m_Watch.Ingest(&mineWire,6000)&&mines->MessageId==2); // false full-roster claim
|
||||
mineWire.AmmoState->MineMembers=4;mineWire.AmmoState->ReadyMines=513;
|
||||
assert(!radio.m_Watch.Ingest(&mineWire,6000)&&mines->MessageId==2); // impossible finite count
|
||||
mineWire.AmmoState->ReadyMines=0;assert(radio.m_Watch.Ingest(&mineWire,6000)&&mines->AmmoState->MineComplete&&mines->AmmoState->ReadyMines==0);
|
||||
mineWire.MessageId=4;mineWire.ObservedAt=6000;mineWire.AmmoState->LauncherMuzzles=-1;
|
||||
assert(!radio.m_Watch.Ingest(&mineWire,7000)&&mines->MessageId==3);
|
||||
mineWire.AmmoState->LauncherMuzzles=129;assert(!radio.m_Watch.Ingest(&mineWire,7000));
|
||||
// Mounted payload obeys the same delivered chronology and is deep copied.
|
||||
Watch mountedWatch;ERR_HQStatusReport mountedReport;ERR_HQMountedAmmoSnapshot mountedState;
|
||||
mountedState.Complete=true;mountedState.Weapons=1;mountedState.Muzzles=1;mountedState.Magazines=1;
|
||||
mountedState.Rounds=40;mountedState.Capacity=50;mountedState.Chambers=1;mountedState.LowMagazines=1;
|
||||
mountedReport.MountedAmmo=&mountedState;
|
||||
assert(!mountedWatch.Find(mountedReport.SourceId));
|
||||
assert(mountedWatch.Ingest(&mountedReport,100));auto mountedEntry=mountedWatch.Find(mountedReport.SourceId);
|
||||
assert(mountedEntry->MountedAmmo!=&mountedState&&mountedEntry->MountedAmmo->Complete&&mountedEntry->MountedAmmo->Rounds==40);
|
||||
assert(mountedEntry->MountedAmmo->Weapons==1&&mountedEntry->MountedAmmo->Muzzles==1&&mountedEntry->MountedAmmo->Magazines==1);
|
||||
assert(mountedEntry->MountedAmmo->Capacity==50&&mountedEntry->MountedAmmo->Chambers==1&&mountedEntry->MountedAmmo->LowMagazines==1);
|
||||
mountedState.Rounds=0;assert(mountedEntry->MountedAmmo->Rounds==40);
|
||||
mountedReport.MessageId=2;assert(!mountedWatch.Ingest(&mountedReport,101)&&mountedEntry->MountedAmmo->Rounds==40);
|
||||
mountedReport.ObservedAt=1;mountedState.Rounds=51;
|
||||
assert(!mountedWatch.Ingest(&mountedReport,101)&&mountedEntry->MessageId==1&&mountedEntry->MountedAmmo->Rounds==40);
|
||||
mountedState.Rounds=0;mountedState.Complete=false;
|
||||
assert(mountedWatch.Ingest(&mountedReport,101)&&!mountedEntry->MountedAmmo->Complete&&mountedEntry->MountedAmmo->Rounds==0);
|
||||
mountedReport.MessageId=3;mountedReport.ObservedAt=2;mountedReport.MountedAmmo=nullptr;
|
||||
assert(mountedWatch.Ingest(&mountedReport,102)&&!mountedEntry->MountedAmmo);
|
||||
mountedReport.SourceId="invalid-mounted";mountedReport.MountedAmmo=&mountedState;mountedState.Magazines=-1;
|
||||
assert(!mountedWatch.Ingest(&mountedReport,103)&&mountedWatch.Count()==1); // invalid cannot allocate an entry
|
||||
using R=ERR_HQWatchRules;
|
||||
assert(R::SquadLimit(0,100)==0&&R::SquadLimit(-1,100)==0);
|
||||
assert(R::SquadLimit(2,0)==2&&R::SquadLimit(2,1)==2&&R::SquadLimit(2,2)==3);
|
||||
@@ -260,6 +336,7 @@ int main(){
|
||||
wire->CasualtySourceId="stale";intake.Accept(wire,121001);assert(!intake.m_Watch.Find("stale"));
|
||||
wire->ObservedAt=2000;wire->TravelMode="walking";wire->Target=&local;
|
||||
intake.Accept(wire,2000);assert(intake.m_NextAssignment==2000);assert(!intake.m_Watch.Find("stale")&&intake.m_Contacts.Count()==1);
|
||||
assert(intake.m_Coverage.forgotten==1&&intake.m_Coverage.observedAt==wire->ObservedAt);
|
||||
wire->TravelMode="body";wire->Distress=true;intake.Accept(wire,2001);
|
||||
assert(!intake.m_Watch.Find("stale")&&intake.m_Contacts.Count()==1); // distress cannot claim bodily identity
|
||||
Watch bodyOnly;body.CasualtySourceId="no-survivor";body.CasualtySlot=0;body.ObservedAt=1000;
|
||||
@@ -286,4 +363,4 @@ for forbidden in ('IEntity','SCR_AIGroup','GetGame(','GetOrigin(','GetPlayers(',
|
||||
'GetControlledEntity(','Spawn(','CallLater(','ERR_HQMemory'):
|
||||
assert forbidden not in source,forbidden
|
||||
assert all(len(line)<120 for line in source.splitlines())
|
||||
print('PASS: delivered-only status snapshots, two missed check-ins/reply deadline, last reported coordinates, episode/replay guards, confirmed-loss idempotence and 256-record cap')
|
||||
print('PASS: delivered-only status snapshots, two missed check-ins/reply deadline, last reported coordinates, episode/replay guards, confirmed-loss idempotence, detached/validated mounted payload replacement 256-record cap and detached carried-mine radio delivery/chronology')
|
||||
|
||||
Reference in New Issue
Block a user