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:
@@ -0,0 +1,123 @@
|
||||
"""Actual convoy/team release, HQ retention/eligibility and exact claim cleanup."""
|
||||
import os,re
|
||||
from pathlib import Path
|
||||
from policy_harness import ROOT,body,run
|
||||
base=Path(os.environ.get('ERR_CONVOY_SURVIVOR_ROOT',str(ROOT)))
|
||||
ai=base/'addons/core/Scripts/Game/ERR/10_AI'
|
||||
def translate(s):
|
||||
s=re.sub(r'//[^\n]*','',s).replace('override ','').replace('null','nullptr')
|
||||
s=s.replace('ERR_HQ.Cast(mode.Command())','mode.Command()')
|
||||
s=s.replace('array<AIAgent>','Array<AIAgent*>').replace('array<IEntity>','Array<IEntity*>')
|
||||
s=re.sub(r'foreach \(([^:]+):',r'for (\1:',s)
|
||||
for t in ('ERR_GameMode','SCR_AIGroup','ERR_ForceRecord','ERR_HQUnit','IEntity','AIAgent','ERR_HQ'):
|
||||
s=re.sub(r'\b'+t+r' (\w+)',t+r'* \1',s)
|
||||
for n in ('mode','m_Mode','group','Group','record','unit','actor','agent','member','hq'):
|
||||
s=re.sub(r'\b'+n+r'\.',n+'->',s)
|
||||
s=s.replace('StatusSampler.','StatusSampler->').replace('Forces().','Forces()->').replace('Command().','Command()->')
|
||||
for n in ('Replication','ERR_GameMode','ERR_Actors','ERR_ForceKind','ERR_ConvoyRules'):
|
||||
s=s.replace(n+'.',n+'::')
|
||||
s=s.replace('Combat.Held','Combat.Held')
|
||||
s=re.sub(r'ERR_HQ\* hq;',r'ERR_HQ* hq=nullptr;',s)
|
||||
s=re.sub(r'ERR_ForceRecord\* record;',r'ERR_ForceRecord* record=nullptr;',s)
|
||||
return s
|
||||
hq=ai/'ERR_HQ.c'
|
||||
# On the baseline the new helper is absent and never called; execute the old
|
||||
# release methods unchanged so the red control fails a gameplay assertion.
|
||||
retain=translate(body(hq,'bool ReleaseConvoySurvivors(')) if 'bool ReleaseConvoySurvivors(' in hq.read_text() else 'bool ReleaseConvoySurvivors(SCR_AIGroup*){return false;}'
|
||||
claims=translate(body(hq,'void ReleaseLogistics('))
|
||||
eligible=translate(body(hq,'bool Eligible(ERR_GameMode mode)')).replace('bool survivor;','bool survivor=false;')
|
||||
vehicle=translate(body(ai/'ERR_ConvoyService.c','void Release(ERR_GameMode mode)'))
|
||||
team=translate(body(ai/'ERR_SupplyConvoyTactics.c','void Release(ERR_GameMode mode)'))
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count()const{return this->size();}void Insert(T x){this->push_back(x);}bool Contains(T x)const{return std::find(this->begin(),this->end(),x)!=this->end();}};
|
||||
struct SCR_AIGroup;struct ERR_HQ;struct ERR_GameMode;
|
||||
struct IEntity{bool alive=true,player=false,fighting=true,mounted=false;int faction=1;SCR_AIGroup*group=nullptr;};
|
||||
struct AIAgent{IEntity*actor=nullptr;int lod=0,writes=0;IEntity*GetControlledEntity(){return actor;}void SetPermanentLOD(int n){lod=n;writes++;}};
|
||||
struct SCR_AIGroup{int faction=1,players=0,lod=0;bool slave=false;Array<AIAgent*>agents;int GetFaction(){return faction;}int GetPlayerCount(bool){return players;}bool IsSlave(){return slave;}void GetAgents(Array<AIAgent*>&a){a=agents;}void SetPermanentLOD(int n){lod=n;}};
|
||||
namespace Replication{bool server=true;bool IsServer(){return server;}}
|
||||
namespace ERR_Actors{bool Alive(IEntity*a){return a&&a->alive;}bool Player(IEntity*a){return a&&a->player;}bool CanFight(IEntity*a){return Alive(a)&&a->fighting;}}
|
||||
namespace CompartmentAccessComponent{bool GetVehicleIn(IEntity*a){return a&&a->mounted;}}
|
||||
namespace ERR_ConvoyRules{bool captured=false;bool Captured(void*,SCR_AIGroup*){return captured;}}
|
||||
namespace ERR_ForceKind{enum{CONVOY,GARRISON};}
|
||||
struct ERR_ForceRecord{bool Physical=true,Eliminated=false,Pinned=true;int Kind=ERR_ForceKind::CONVOY,InitialStrength=2;std::string SourceId="original";};
|
||||
struct Ledger{ERR_ForceRecord record;bool present=true;ERR_ForceRecord*FindGroup(SCR_AIGroup*){return present?&record:nullptr;}};
|
||||
struct ERR_GameMode{static ERR_GameMode*current;bool active=true;ERR_HQ*command=nullptr;Ledger ledger;static ERR_GameMode*Get(){return current;}bool Active(){return active;}ERR_HQ*Command(){return command;}Ledger*Forces(){return &ledger;}int EnemyFaction(){return 1;}};ERR_GameMode*ERR_GameMode::current=nullptr;
|
||||
struct Sampler{bool bound=true;std::string source="original";SCR_AIGroup*group=nullptr;Array<IEntity*>members;bool released[2]={false,false};bool OwnsRoster(SCR_AIGroup*g){return bound&&g==group;}std::string InitialSourceId(){return source;}int InitialStrength(){return members.Count();}IEntity*InitialMember(int i){return members[i];}bool CanAttribute(int slot,IEntity*a,bool observe){assert(!observe);return a&&!released[slot]&&!a->player&&a->group==group&&a->faction==1;}};
|
||||
struct ERR_HQUnit{SCR_AIGroup*Group=nullptr;void*Utility=this;std::string ForceSourceId="original";Sampler*StatusSampler=nullptr;bool ReservedForSupport=true;int LogisticsReservation=0;bool PendingConvoyOrder=false,PendingOrder=false,PendingCheckin=false,Downlink=false;int pendingClears=0;bool reports=true;
|
||||
void CancelPending(){PendingConvoyOrder=PendingOrder=PendingCheckin=Downlink=false;pendingClears++;}
|
||||
'''+eligible.replace('CompartmentAccessComponent.','CompartmentAccessComponent::')+r'''
|
||||
};
|
||||
struct ERR_HQ{bool m_Stopping=false;ERR_GameMode*m_Mode=nullptr;Array<ERR_HQUnit*>m_Units;int unregistrations=0,registrations=0,equipmentStops=0;
|
||||
void UnregisterGroup(SCR_AIGroup*g){unregistrations++;equipmentStops++;for(auto u:m_Units)if(u->Group==g){u->Group=nullptr;u->Utility=nullptr;u->reports=false;}}
|
||||
'''+claims+retain+r'''
|
||||
};
|
||||
struct VehicleOwner{bool Released=false,Registered=true,PlayerCaptureObserved=false;void*InterceptOrder=nullptr;SCR_AIGroup*Group=nullptr;void*Entity=this;struct{bool Held=true;}Combat;int SupplyToken=101;bool SupplyHeld=true;int clears=0,intercepts=0;
|
||||
void ClearIntercept(void*){intercepts++;}void ClearOrder(){clears++;}
|
||||
'''+vehicle+r'''
|
||||
};
|
||||
struct TeamOwner{bool Released=false,Registered=true,PlayerCaptureObserved=false;SCR_AIGroup*Group=nullptr;int clears=0;void Clear(){clears++;}
|
||||
'''+team+r'''
|
||||
};
|
||||
struct Case{ERR_GameMode mode;ERR_HQ hq;SCR_AIGroup group,foreign;IEntity one,two,extra;AIAgent a,b,c;Sampler sampler;ERR_HQUnit unit;VehicleOwner vehicle;TeamOwner team;
|
||||
Case(){mode.command=&hq;ERR_GameMode::current=&mode;hq.m_Mode=&mode;hq.m_Units={&unit};unit.Group=&group;unit.StatusSampler=&sampler;sampler.group=&group;one.group=two.group=extra.group=&group;a.actor=&one;b.actor=&two;c.actor=&extra;sampler.members={&one,&two};group.agents={&a,&b};vehicle.Group=team.Group=&group;}
|
||||
void Release(bool passenger){if(passenger)team.Release(&mode);else vehicle.Release(&mode);}
|
||||
};
|
||||
int main(){
|
||||
for(bool passenger:{false,true}){
|
||||
Case c;c.unit.PendingConvoyOrder=true;c.unit.Downlink=true;c.Release(passenger);
|
||||
assert(c.hq.unregistrations==0&&c.unit.Group==&c.group&&c.unit.Utility&&c.unit.reports); // old release silenced living original survivors
|
||||
assert(!c.unit.ReservedForSupport&&c.unit.Eligible(&c.mode)&&!c.unit.Downlink&&c.unit.pendingClears==1);
|
||||
assert(!c.mode.ledger.record.Pinned&&c.hq.registrations==0&&c.hq.equipmentStops==0);
|
||||
assert(c.group.lod==-1&&c.a.writes==1&&c.b.writes==1);
|
||||
c.Release(passenger);assert(c.unit.pendingClears==1&&c.a.writes==1);
|
||||
}
|
||||
for(bool passenger:{false,true}){
|
||||
Case c;c.one.fighting=false;c.two.alive=false;c.group.agents={&c.a};c.Release(passenger);
|
||||
assert(c.hq.unregistrations==0&&!c.unit.Eligible(&c.mode)); // unconscious original retains future recovery/report identity
|
||||
c.one.fighting=true;assert(c.unit.Eligible(&c.mode));
|
||||
}
|
||||
{Case c;c.one.mounted=true;c.Release(false);assert(c.hq.unregistrations==0&&!c.unit.Eligible(&c.mode));c.one.mounted=false;assert(c.unit.Eligible(&c.mode));}
|
||||
for(bool passenger:{false,true})for(int bad=0;bad<22;bad++){
|
||||
Case c;
|
||||
if(bad==0)c.mode.active=false;
|
||||
if(bad==1)c.hq.m_Stopping=true;
|
||||
if(bad==2)ERR_GameMode::current=nullptr;
|
||||
if(bad==3)c.mode.command=nullptr;
|
||||
if(bad==4)c.group.players=1;
|
||||
if(bad==5)c.group.faction=2;
|
||||
if(bad==6)c.group.slave=true;
|
||||
if(bad==7)c.mode.ledger.present=false;
|
||||
if(bad==8)c.mode.ledger.record.Physical=false;
|
||||
if(bad==9)c.mode.ledger.record.Eliminated=true;
|
||||
if(bad==10)c.mode.ledger.record.Kind=ERR_ForceKind::GARRISON;
|
||||
if(bad==11)c.unit.ForceSourceId="foreign";
|
||||
if(bad==12)c.sampler.source="foreign";
|
||||
if(bad==13)c.sampler.bound=false;
|
||||
if(bad==14)c.one.group=&c.foreign;
|
||||
if(bad==15)c.one.player=true;
|
||||
if(bad==16)c.one.faction=2;
|
||||
if(bad==17)c.group.agents.Insert(&c.c);
|
||||
if(bad==18)c.group.agents.Insert(&c.a);
|
||||
if(bad==19)c.group.agents={&c.b};
|
||||
if(bad==20)c.one.alive=c.two.alive=false;
|
||||
if(bad==21)c.sampler.released[0]=true;
|
||||
c.Release(passenger);
|
||||
assert(c.hq.unregistrations==(bad==3?0:1));assert(c.unit.ReservedForSupport);
|
||||
}
|
||||
{Case c;ERR_ConvoyRules::captured=true;c.Release(false);assert(c.hq.unregistrations==1);ERR_ConvoyRules::captured=false;}
|
||||
for(bool passenger:{false,true}){Case c;c.vehicle.Registered=c.team.Registered=false;c.Release(passenger);assert(c.hq.unregistrations==1);}
|
||||
// Real HQ claim release keeps an unsettled inventory transaction quarantined,
|
||||
// ignores somebody else's token and permits retasking only after exact settlement.
|
||||
for(bool passenger:{false,true}){Case c;c.unit.LogisticsReservation=200007;c.Release(passenger);
|
||||
assert(c.unit.LogisticsReservation==200007&&!c.unit.Eligible(&c.mode));
|
||||
c.hq.ReleaseLogistics(&c.group,200008);assert(!c.unit.Eligible(&c.mode));
|
||||
c.hq.ReleaseLogistics(&c.group,200007);assert(c.unit.Eligible(&c.mode)&&c.unit.reports);
|
||||
}
|
||||
{Case c;c.unit.Downlink=true;c.unit.PendingOrder=true;c.Release(false);assert(c.unit.Downlink&&c.unit.PendingOrder&&c.unit.pendingClears==0);} // no cancellation of another owner's order
|
||||
}
|
||||
''')
|
||||
print('PASS: actual convoy/team Release retains original active survivors and HQ identity, rejects takeover/terminal/unknown roster, preserves exact logistics quarantine and normal eligibility')
|
||||
@@ -0,0 +1,93 @@
|
||||
"""Execute actual event, tracked-hunter match, bounded ancestry and both release layers."""
|
||||
import os,re
|
||||
from pathlib import Path
|
||||
from policy_harness import ROOT,body,run
|
||||
base=Path(os.environ.get('ERR_HUNTER_CAPTURE_ROOT',ROOT))
|
||||
p=base/'addons/core/Scripts/Game/ERR/10_AI'
|
||||
h=p/'ERR_HunterService.c';c=p/'ERR_ConvoyService.c'
|
||||
def tr(s):
|
||||
s=re.sub(r'//[^\n]*','',s).replace('override ','').replace('protected ','').replace('null','nullptr')
|
||||
s=s.replace('ERR_HQ.Cast(m_Mode.Command())','m_Mode.Command()').replace('ERR_HQ.Cast(mode.Command())','mode.Command()')
|
||||
s=s.replace('array<AIAgent>','Array<AIAgent*>')
|
||||
s=re.sub(r'foreach \(([^:]+):',r'for (\1:',s)
|
||||
for t in ('IEntity','ERR_GameMode','ERR_HQ','ERR_HunterVehicle','BaseCompartmentManagerComponent','ERR_ForceRecord','AIAgent'):
|
||||
s=re.sub(r'\b'+t+r' (\w+)',t+r'* \1',s)
|
||||
for n in ('m_Mode','mode','hq','unit','Group','agent','record','Recovery','entity'):
|
||||
s=re.sub(r'\b'+n+r'\.',n+'->',s)
|
||||
s=s.replace('Forces().','Forces()->').replace('Players().','Players()->').replace('Command().','Command()->').replace('Hunters().','Hunters()->')
|
||||
for n in ('Replication','ERR_GameMode','ERR_ConvoyRules','SCR_EntityHelper'):s=s.replace(n+'.',n+'::')
|
||||
s=s.replace('ERR_HQ* hq;','ERR_HQ* hq=nullptr;').replace('int depth;','int depth=0;')
|
||||
return s
|
||||
service=tr(body(h,'void PlayerEntered(')) if 'void PlayerEntered(' in h.read_text() else 'void PlayerEntered(IEntity*,IEntity*){}'
|
||||
event=tr(body(h,'override protected void OnCompartmentEntered(')).replace('super.','NativeAccess::') if 'override protected void OnCompartmentEntered(' in h.read_text() else 'void OnCompartmentEntered(IEntity* t,BaseCompartmentManagerComponent*m,int i,int s,bool b){NativeAccess::OnCompartmentEntered(t,m,i,s,b);}'
|
||||
base_release=tr(body(c,'void Release(ERR_GameMode mode)'))
|
||||
hunter_release=tr(body(h,'override void Release(ERR_GameMode mode)')).replace('super.','VehicleOwner::')
|
||||
part=tr(body(c,'static bool PartOf('))
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}};
|
||||
struct IEntity{IEntity*parent=nullptr;IEntity*GetParent(){return parent;}};
|
||||
struct AIAgent{int lod=0;void SetPermanentLOD(int n){lod=n;}};
|
||||
struct GroupType{int lod=0;AIAgent a;void SetPermanentLOD(int n){lod=n;}void GetAgents(Array<AIAgent*>&v){v={&a};}};
|
||||
struct BaseCompartmentManagerComponent{};
|
||||
struct Service;struct ERR_HQ;
|
||||
struct Roster{IEntity*current=nullptr;void*CurrentActor(IEntity*a){return a&&a==current?this:nullptr;}};
|
||||
struct ERR_ForceRecord{bool Pinned=true;};struct Ledger{ERR_ForceRecord r;ERR_ForceRecord*FindGroup(GroupType*){return &r;}};
|
||||
struct ERR_GameMode{inline static ERR_GameMode*current=nullptr;bool active=true;ERR_HQ*command=nullptr;Roster roster;Ledger ledger;static ERR_GameMode*Get(){return current;}bool Active(){return active;}ERR_HQ*Command(){return command;}Roster*Players(){return &roster;}Ledger*Forces(){return &ledger;}};
|
||||
namespace Replication{bool server=true;bool IsServer(){return server;}}
|
||||
namespace ERR_ConvoyRules{bool occupied=false;bool Captured(IEntity*,GroupType*){return occupied;}
|
||||
'''+part+r'''
|
||||
}
|
||||
struct ERR_HQ{Service*service=nullptr;int retained=0,unregistered=0,recoveries=0;Service*Hunters(){return service;}bool ReleaseConvoySurvivors(GroupType*){retained++;return true;}void UnregisterGroup(GroupType*){unregistered++;}void ReleaseRecovery(GroupType*){recoveries++;}};
|
||||
struct RecoveryTask{int stops=0;bool*captured=nullptr;void Stop(){stops++;if(captured)assert(*captured);}};
|
||||
struct VehicleOwner{bool Released=false,Registered=true,PlayerCaptureObserved=false;IEntity*Entity=nullptr;GroupType*Group=nullptr;void*InterceptOrder=nullptr;struct{bool Held=true;}Combat;int SupplyToken=7;bool SupplyHeld=true;int orders=1,clears=0;void ClearIntercept(void*){}void ClearOrder(){orders=0;clears++;}
|
||||
'''+base_release+r'''
|
||||
};
|
||||
struct ERR_HunterVehicle:VehicleOwner{RecoveryTask*Recovery=nullptr;void*Downlink=this;
|
||||
'''+hunter_release+r'''
|
||||
};
|
||||
struct Service{bool m_Stopped=false;ERR_GameMode*m_Mode=nullptr;Array<ERR_HunterVehicle*>m_Hunters;
|
||||
'''+service+r'''
|
||||
};
|
||||
struct NativeAccess{IEntity*owner=nullptr;int nativeCalls=0;IEntity*GetOwner(){return owner;}void OnCompartmentEntered(IEntity*,BaseCompartmentManagerComponent*,int,int,bool){nativeCalls++;if(ERR_GameMode::current)ERR_GameMode::current->roster.current=nullptr;ERR_ConvoyRules::occupied=false;}};
|
||||
struct Access:NativeAccess{
|
||||
'''+event+r'''
|
||||
};
|
||||
struct Case{ERR_GameMode mode,other;ERR_HQ hq,otherHQ;Service service,foreign;ERR_HunterVehicle unit;GroupType group;IEntity player,vehicle,turret,unrelated;RecoveryTask recovery;Access access;
|
||||
Case(){Replication::server=true;ERR_GameMode::current=&mode;mode.command=&hq;mode.roster.current=&player;hq.service=&service;service.m_Mode=&mode;service.m_Hunters={&unit};unit.Entity=&vehicle;unit.Group=&group;unit.Recovery=&recovery;turret.parent=&vehicle;access.owner=&player;}
|
||||
void Enter(IEntity*t=nullptr){access.OnCompartmentEntered(t?t:&turret,nullptr,1,2,false);}
|
||||
};
|
||||
int main(){
|
||||
{IEntity root;assert(ERR_ConvoyRules::PartOf(&root,&root));}
|
||||
{Case c;c.recovery.captured=&c.unit.PlayerCaptureObserved;
|
||||
// Native occupancy is not visible yet; super removes current identity to model
|
||||
// disconnect/body loss before next one-second poll. Event must win that gap.
|
||||
c.Enter();assert(c.unit.PlayerCaptureObserved&&c.unit.Released);assert(c.access.nativeCalls==1);
|
||||
assert(c.hq.unregistered==1&&c.hq.retained==0&&c.hq.recoveries==0);
|
||||
assert(c.recovery.stops==1&&c.unit.Downlink==nullptr&&c.unit.orders==0&&c.unit.clears==1);
|
||||
assert(!c.mode.ledger.r.Pinned&&c.unit.Entity==&c.vehicle&&c.unit.Group==&c.group);
|
||||
c.mode.roster.current=&c.player;c.Enter();assert(c.access.nativeCalls==2&&c.recovery.stops==1&&c.hq.unregistered==1);
|
||||
}
|
||||
for(int bad=0;bad<9;bad++){Case c;
|
||||
if(bad==0)Replication::server=false;
|
||||
if(bad==1)c.service.m_Stopped=true;
|
||||
if(bad==2)c.mode.active=false;
|
||||
if(bad==3)c.service.m_Mode=&c.other;
|
||||
if(bad==4)c.mode.command=&c.otherHQ;
|
||||
if(bad==5)c.hq.service=&c.foreign;
|
||||
if(bad==6)c.mode.roster.current=&c.unrelated;
|
||||
if(bad==7)c.unit.Entity=nullptr;
|
||||
if(bad==8)c.service.m_Hunters.resize(16,&c.unit);
|
||||
c.Enter();assert(!c.unit.PlayerCaptureObserved&&!c.unit.Released&&c.hq.unregistered==0&&c.access.nativeCalls==1);
|
||||
}
|
||||
{Case c;c.Enter(&c.unrelated);assert(!c.unit.Released&&c.access.nativeCalls==1);}
|
||||
{Case c;IEntity chain[33];for(int i=0;i<32;i++)chain[i].parent=&chain[i+1];chain[32].parent=&c.vehicle;c.Enter(&chain[0]);assert(!c.unit.Released);}
|
||||
{Case c;IEntity loop;loop.parent=&loop;c.Enter(&loop);assert(!c.unit.Released);}
|
||||
{Case c;c.Enter(&c.vehicle);assert(c.unit.Released);} // root as well as turret child
|
||||
{Case c;c.unit.Release(&c.mode);assert(c.unit.Released&&c.hq.recoveries==1&&c.hq.unregistered==0);c.Enter();assert(!c.unit.PlayerCaptureObserved&&c.hq.unregistered==0&&c.hq.recoveries==1);} // abandoned asset does not revoke independent survivors
|
||||
{Case c;c.unit.PlayerCaptureObserved=true;c.unit.Release(&c.mode);assert(c.hq.unregistered==1&&c.hq.retained==0&&!c.unit.Downlink);} // latch reaches both release layers without current occupant
|
||||
{Case c;c.service.PlayerEntered(nullptr,&c.vehicle);c.service.PlayerEntered(&c.player,nullptr);assert(!c.unit.Released);}
|
||||
}
|
||||
''')
|
||||
print('PASS: actual player-entry hook latches original hunter capture before native callbacks; both release layers cancel ownership without new entities/refund; bounded current-owner guards and prior-release policy')
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Production hunter release and objective-role policy, without native spawning."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
from policy_harness import ROOT, body, run
|
||||
source=Path(os.environ.get('ERR_HUNTER_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c'))
|
||||
s=body(source,'override void Release(ERR_GameMode mode)')
|
||||
s=s.replace('override ','').replace('super.','Native::').replace('null','nullptr')
|
||||
s=s.replace('ERR_GameMode mode','ERR_GameMode* mode').replace('ERR_ForceRecord record','ERR_ForceRecord* record')
|
||||
s=s.replace('ERR_HQ hq = ERR_HQ.Cast(mode.Command())', 'ERR_HQ* hq = mode->Command()')
|
||||
s=s.replace('array<AIAgent> agents = {};','Array<AIAgent*> agents;').replace('foreach (AIAgent agent : agents)','for (AIAgent* agent : agents)')
|
||||
for name in ('mode','record','Group','agent','hq','Recovery'):s=s.replace(name+'.',name+'->')
|
||||
s=s.replace('Forces().','Forces()->').replace('ERR_ConvoyRules.','ERR_ConvoyRules::')
|
||||
objective=body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ObjectiveDefence.c','static bool Objective(').replace('ERR_LocationKind.','ERR_LocationKind::')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
template<class T>using Array=std::vector<T>;
|
||||
struct AIAgent{int lod=0,writes=0;void SetPermanentLOD(int x){lod=x;writes++;}};
|
||||
struct GroupType{int lod=0;AIAgent agent;void SetPermanentLOD(int x){lod=x;}void GetAgents(Array<AIAgent*>& a){a={&agent};}};
|
||||
struct ERR_ForceRecord{bool Pinned=true;};
|
||||
struct Ledger{ERR_ForceRecord record;ERR_ForceRecord* FindGroup(GroupType*){return &record;}};
|
||||
struct ERR_HQ{int releases=0;void ReleaseRecovery(GroupType*){releases++;}};
|
||||
struct ERR_GameMode{bool active=true;Ledger ledger;ERR_HQ hq;bool Active(){return active;}Ledger* Forces(){return &ledger;}ERR_HQ* Command(){return &hq;}};
|
||||
namespace ERR_ConvoyRules{bool captured=false;bool Captured(void*,GroupType*){return captured;}}
|
||||
struct RecoveryTask{int stops=0;void Stop(){stops++;}};
|
||||
struct Native{RecoveryTask* Recovery=nullptr;bool Released=false,PlayerCaptureObserved=false;int delegated=0,clears=0;GroupType* Group=nullptr;void* Entity=nullptr;void* Downlink=nullptr;
|
||||
void Release(ERR_GameMode*){delegated++;Released=true;}void ClearOrder(){clears++;}
|
||||
};
|
||||
struct Hunter:Native{
|
||||
'''+s+r'''
|
||||
};
|
||||
namespace ERR_LocationKind{enum{PRISON,RADIO,EXTRACTION,PATROL,DEPOT,OUTPOST,MORTAR,HOSPITAL};}
|
||||
'''+objective+r'''
|
||||
int main(){
|
||||
ERR_GameMode mode;GroupType group;Hunter unit;unit.Group=&group;unit.Entity=&group;unit.Downlink=&mode;RecoveryTask recovery;unit.Recovery=&recovery;
|
||||
unit.Release(&mode);assert(unit.Released&&!unit.Downlink&&unit.clears==1&&unit.delegated==0);
|
||||
assert(recovery.stops==1);
|
||||
assert(!mode.ledger.record.Pinned&&mode.hq.releases==1&&group.lod==-1&&group.agent.writes==1);
|
||||
assert(unit.Entity==&group&&unit.Group==&group); // living and dead actors/vehicle are preserved
|
||||
unit.Release(&mode);assert(unit.clears==1&&mode.hq.releases==1&&group.agent.writes==1);
|
||||
Hunter captured;ERR_ConvoyRules::captured=true;captured.Release(&mode);assert(captured.delegated==1&&mode.hq.releases==1);
|
||||
ERR_ConvoyRules::captured=false;Hunter stopped;mode.active=false;stopped.Release(&mode);assert(stopped.delegated==1);
|
||||
Hunter missing;missing.Release(nullptr);assert(missing.delegated==1);
|
||||
assert(Objective(ERR_LocationKind::RADIO)&&Objective(ERR_LocationKind::DEPOT)&&Objective(ERR_LocationKind::OUTPOST)&&Objective(ERR_LocationKind::EXTRACTION));
|
||||
assert(!Objective(ERR_LocationKind::PRISON)&&!Objective(ERR_LocationKind::PATROL)&&!Objective(ERR_LocationKind::MORTAR));
|
||||
}
|
||||
''')
|
||||
print('PASS: actual hunter release preserves survivors/HQ, releases driving ownership once, delegates capture/teardown, and excludes prison/patrol/mortar from generic objective defence')
|
||||
Reference in New Issue
Block a user