Integrate delivered HQ memory, finite physical convoy and RHS WCS dependencies
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
"""Actual convoy dispatch transaction and exact native seat-allocation policy."""
|
||||
import os,re
|
||||
from pathlib import Path
|
||||
from policy_harness import ROOT,body,run
|
||||
path=Path(os.environ.get('ERR_CONVOY_SERVICE',str(ROOT/'addons/ai/Scripts/Game/ERR/ERR_ConvoyService.c')))
|
||||
source=path.read_text()
|
||||
# Dispatch's native calls are doubled; budget behavior is executed separately.
|
||||
code=body(path,'ERR_ConvoyTask Dispatch(')
|
||||
code=re.sub(r'//[^\n]*','',code)
|
||||
code=code.replace('array<vector>','Array<vector>&')
|
||||
code=code.replace('SCR_AIGroup.Cast(', 'static_cast<SCR_AIGroup*>(').replace('Vehicle.Cast(', 'static_cast<Vehicle*>(')
|
||||
code=code.replace('FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent))','&unit->Entity->affiliation').replace('AICarMovementComponent.Cast(unit.Entity.FindComponent(AICarMovementComponent))','&unit->Entity->movement')
|
||||
for cls in ['ERR_ConvoyTask','ERR_ConvoyReceipt','ERR_ForceRoute','ERR_ConvoyVehicle','FactionAffiliationComponent','AICarMovementComponent']:
|
||||
code=re.sub(r'\b'+cls+r' ([A-Za-z_]\w*)',cls+r'* \1',code)
|
||||
for obj in ['m_Mode','m_Pool','task','unit','affiliation','movement']:
|
||||
code=re.sub(r'\b'+obj+r'\.',obj+'->',code)
|
||||
for cls in ['ERR_ForceRules','ERR_ConvoyRules','ERR_ConvoyResources','ERR_Actors','ERR_HQ']:
|
||||
code=code.replace(cls+'.',cls+'::')
|
||||
code=code.replace('EnemyFaction().','EnemyFaction()->').replace('Group.Get','Group->Get').replace('null','nullptr')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
using string=std::string;struct vector{};
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;void Insert(T v){this->push_back(v);}int Count(){return this->size();}};
|
||||
struct Faction{string key="USSR";string GetFactionKey(){return key;}};Faction enemy;
|
||||
struct IEntity{virtual ~IEntity()=default;};
|
||||
struct FactionAffiliationComponent{void SetAffiliatedFaction(Faction*){}};
|
||||
struct AICarMovementComponent{int speed=0;void SetCruiseSpeed(int v){speed=v;}};
|
||||
struct SCR_AIGroup:IEntity{Faction* GetFaction(){return &enemy;}int GetAgentsCount(){return 0;}};
|
||||
struct Vehicle:IEntity{FactionAffiliationComponent affiliation;AICarMovementComponent movement;};
|
||||
struct ERR_GameMode{Array<IEntity*>owned;Faction* EnemyFaction(){return &enemy;}void Own(IEntity* e){owned.Insert(e);}};
|
||||
struct ERR_ForceRoute{};
|
||||
struct ERR_ConvoyReceipt{bool used=false;};
|
||||
struct ERR_ConvoyPool{int consumes=0;bool Consume(ERR_ConvoyReceipt* r){if(!r||r->used)return false;r->used=true;consumes++;return true;}};
|
||||
struct ERR_ConvoyVehicle{int Index=0;Vehicle* Entity=nullptr;SCR_AIGroup* Group=nullptr;};
|
||||
struct ERR_ConvoyTask{ERR_ConvoyReceipt* Receipt=nullptr;ERR_ForceRoute* Route=nullptr;float Deadline=0;Array<ERR_ConvoyVehicle*>Vehicles;bool stopped=false;void Stop(ERR_GameMode*,string){stopped=true;}};
|
||||
namespace ERR_ForceRules{bool Finite(float f){return f==f;}}
|
||||
namespace ERR_ConvoyRules{bool valid=true,clear=true,empty=true;bool EmptyCrewTemplate(SCR_AIGroup*){return empty;}bool GroundClear(Array<vector>&,Array<vector>&){return clear;}bool Plan(Array<vector>&,Array<vector>&,ERR_ForceRoute*){return valid;}}
|
||||
namespace ERR_ConvoyResources{bool available=true;bool Available(){return available;}string GroupPrefab(){return "group";}string VehiclePrefab(int i){return "vehicle"+std::to_string(i);}}
|
||||
namespace ERR_HQ{void Event(string,string){}}
|
||||
namespace ERR_Actors{int calls=0,fail=0;IEntity* Spawn(string prefab,vector,vector=vector{}){calls++;if(calls==fail)return nullptr;if(prefab=="group")return new SCR_AIGroup();return new Vehicle();}}
|
||||
struct Service{ERR_GameMode* m_Mode;ERR_ConvoyPool* m_Pool;Array<ERR_ConvoyTask*>m_Tasks;bool current=true;int crews=0,failCrew=-1;
|
||||
bool Current(){return current;}bool FillCrew(ERR_ConvoyVehicle* unit){crews++;return unit->Index!=failCrew;}
|
||||
''' + code + r'''
|
||||
};
|
||||
int main(){
|
||||
ERR_GameMode mode;ERR_ConvoyPool pool;Service service{&mode,&pool};ERR_ConvoyReceipt receipt;ERR_ForceRoute route;Array<vector>positions(6),angles(6);
|
||||
ERR_ConvoyRules::valid=false;assert(!service.Dispatch(&receipt,positions,angles,&route,100));assert(!receipt.used&&ERR_Actors::calls==0);ERR_ConvoyRules::valid=true;
|
||||
ERR_ConvoyRules::clear=false;assert(!service.Dispatch(&receipt,positions,angles,&route,100));assert(!receipt.used&&ERR_Actors::calls==0);ERR_ConvoyRules::clear=true;
|
||||
ERR_ConvoyResources::available=false;assert(!service.Dispatch(&receipt,positions,angles,&route,100));assert(!receipt.used&&ERR_Actors::calls==0);ERR_ConvoyResources::available=true;
|
||||
enemy.key="US";assert(!service.Dispatch(&receipt,positions,angles,&route,100));assert(!receipt.used&&ERR_Actors::calls==0);enemy.key="USSR";
|
||||
auto task=service.Dispatch(&receipt,positions,angles,&route,100);assert(task&&task->Vehicles.Count()==6&&!task->stopped&&receipt.used&&pool.consumes==1&&ERR_Actors::calls==12&&mode.owned.Count()==12&&service.crews==6);
|
||||
assert(!service.Dispatch(&receipt,positions,angles,&route,100));assert(ERR_Actors::calls==12);
|
||||
for(int failed=1;failed<=12;failed++){
|
||||
ERR_GameMode partial;ERR_ConvoyPool budget;Service run{&partial,&budget};ERR_ConvoyReceipt ticket;ERR_Actors::calls=0;ERR_Actors::fail=failed;
|
||||
auto result=run.Dispatch(&ticket,positions,angles,&route,0);assert(result&&result->stopped&&ticket.used&&budget.consumes==1&&run.m_Tasks.Count()==1);int attempted=ERR_Actors::calls;
|
||||
assert(!run.Dispatch(&ticket,positions,angles,&route,1)&&ERR_Actors::calls==attempted);assert(partial.owned.Count()==attempted-1);
|
||||
}
|
||||
ERR_Actors::fail=0;ERR_ConvoyRules::empty=false;ERR_GameMode unexpected;ERR_ConvoyPool rejectBudget;Service wrongTemplate{&unexpected,&rejectBudget};ERR_ConvoyReceipt rejectTicket;
|
||||
auto rejected=wrongTemplate.Dispatch(&rejectTicket,positions,angles,&route,100);assert(rejected&&rejected->stopped&&rejectTicket.used&&wrongTemplate.crews==0);ERR_ConvoyRules::empty=true;
|
||||
ERR_Actors::fail=0;ERR_GameMode seats;ERR_ConvoyPool budget;Service noCrew{&seats,&budget};noCrew.failCrew=2;ERR_ConvoyReceipt ticket;
|
||||
auto partial=noCrew.Dispatch(&ticket,positions,angles,&route,100);assert(partial&&partial->stopped&&partial->Vehicles.Count()==3&&ticket.used&&seats.owned.Count()==6);
|
||||
}
|
||||
''')
|
||||
# Execute the seat-selection and creation body. Failed capacity cannot shrink
|
||||
# the plan, and every native successful character is immediately owned.
|
||||
fill=body(path,'protected bool FillCrew(').replace('protected ','')
|
||||
fill=re.sub(r'//[^\n]*','',fill).replace('array<BaseCompartmentSlot>','Array<BaseCompartmentSlot*>')
|
||||
for cls in ['ERR_ConvoyVehicle','BaseCompartmentSlot','AIGroup','IEntity']:
|
||||
fill=re.sub(r'\b'+cls+r' ([A-Za-z_]\w*)',cls+r'* \1',fill)
|
||||
for obj in ['unit','slot','seat','m_Mode']:
|
||||
fill=re.sub(r'\b'+obj+r'\.',obj+'->',fill)
|
||||
for cls in ['ERR_ConvoyRules','ECompartmentType','ERR_ConvoyPool','ERR_ConvoyResources']:
|
||||
fill=fill.replace(cls+'.',cls+'::')
|
||||
fill=fill.replace('foreach (','for (')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
using ResourceName=int;
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;void Insert(T v){this->push_back(v);}int Count(){return this->size();}};
|
||||
enum class ECompartmentType{PILOT,TURRET,CARGO};struct IEntity{};struct AIGroup{};
|
||||
struct BaseCompartmentSlot{ECompartmentType type;bool occupied=false,accessible=true,fail=false;int spawns=0;ECompartmentType GetType(){return type;}bool IsCompartmentAccessible(){return accessible;}bool IsOccupied(){return occupied;}IEntity* SpawnCharacterInCompartment(ResourceName,AIGroup*&,ResourceName){spawns++;return fail?nullptr:new IEntity();}};
|
||||
struct Vehicle{Array<BaseCompartmentSlot*>seats;};
|
||||
struct ERR_ConvoyVehicle{int Index=0;Vehicle* Entity;AIGroup* Group;Array<BaseCompartmentSlot*>Seats;Array<IEntity*>Members;IEntity* Driver=nullptr;};
|
||||
namespace ERR_ConvoyRules{bool CollectSeats(Vehicle* v,Array<BaseCompartmentSlot*>& out){out=v->seats;return true;}}
|
||||
namespace ERR_ConvoyPool{int CrewCount(int i){if(i==1)return 1;if(i==2)return 9;if(i==4)return 3;return 2;}}
|
||||
namespace ERR_ConvoyResources{int Soldier(){return 1;}int GroupPrefab(){return 2;}}
|
||||
struct Mode{int owned=0;void Own(IEntity*){owned++;}};
|
||||
struct Service{Mode* m_Mode;
|
||||
''' + fill + r'''
|
||||
};
|
||||
int main(){
|
||||
for(int i=0;i<6;i++){
|
||||
Vehicle vehicle;AIGroup group;Mode mode;Service service{&mode};ERR_ConvoyVehicle unit;unit.Index=i;unit.Entity=&vehicle;unit.Group=&group;
|
||||
BaseCompartmentSlot driver{ECompartmentType::PILOT},gunner{ECompartmentType::TURRET},commander{ECompartmentType::TURRET};vehicle.seats={&driver,&gunner,&commander};
|
||||
BaseCompartmentSlot seats[8];for(auto& seat:seats){seat.type=ECompartmentType::CARGO;vehicle.seats.Insert(&seat);}
|
||||
assert(service.FillCrew(&unit));assert(unit.Members.Count()==ERR_ConvoyPool::CrewCount(i)&&mode.owned==unit.Members.Count()&&unit.Driver==unit.Members[0]);
|
||||
}
|
||||
Vehicle vehicle;AIGroup group;Mode mode;Service service{&mode};ERR_ConvoyVehicle unit;unit.Index=2;unit.Entity=&vehicle;unit.Group=&group;
|
||||
BaseCompartmentSlot driver{ECompartmentType::PILOT};vehicle.seats={&driver};assert(!service.FillCrew(&unit)&&driver.spawns==0&&mode.owned==0);
|
||||
BaseCompartmentSlot seats[8];for(auto& seat:seats){seat.type=ECompartmentType::CARGO;vehicle.seats.Insert(&seat);}seats[3].fail=true;
|
||||
assert(!service.FillCrew(&unit)&&unit.Members.Count()==4&&mode.owned==4);assert(seats[4].spawns==0);
|
||||
}
|
||||
''')
|
||||
print('PASS: actual convoy dispatch validates before receipt consumption, consumes once before native mutation, retains all12 partial-failure paths, refuses implicit faction replacement, and allocates exact19 bodies without capacity shrinking')
|
||||
|
||||
# The resource must suppress authored allocation before native EOnInit, not try
|
||||
# to purge a queued default squad after custom seat occupants have been added.
|
||||
prefab=ROOT/'addons/ai/Prefabs/ERR/ConvoyCrew.et'
|
||||
text=prefab.read_text()
|
||||
assert '"{000CD338713F2B5A}Prefabs/AI/Groups/Group_Base.et"' in text
|
||||
assert 'm_bSpawnImmediately 0' in text and 'm_faction "USSR"' in text
|
||||
assert re.search(r'm_aUnitPrefabSlots\s*{\s*}',text)
|
||||
assert 'Group_USSR_Base.et' not in text
|
||||
identity=re.search(r'Name "([^"\n]+)"',prefab.with_name(prefab.name+'.meta').read_text())[1]
|
||||
assert identity in body(path,'static ResourceName GroupPrefab(')
|
||||
empty=body(path,'static bool EmptyCrewTemplate(')
|
||||
empty=empty.replace('SCR_AIGroup group','SCR_AIGroup* group').replace('group.','group->').replace('m_aUnitPrefabSlots.IsEmpty()', 'm_aUnitPrefabSlots->IsEmpty()')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
struct Slots{bool empty=true;bool IsEmpty(){return empty;}};
|
||||
struct SCR_AIGroup{bool immediate=false;int count=0;Slots* m_aUnitPrefabSlots=nullptr;bool GetSpawnImmediately(){return immediate;}int GetAgentsCount(){return count;}};
|
||||
'''+empty+r'''
|
||||
int main(){SCR_AIGroup group;Slots slots;assert(EmptyCrewTemplate(&group));group.m_aUnitPrefabSlots=&slots;assert(EmptyCrewTemplate(&group));slots.empty=false;assert(!EmptyCrewTemplate(&group));slots.empty=true;group.immediate=true;assert(!EmptyCrewTemplate(&group));group.immediate=false;group.count=1;assert(!EmptyCrewTemplate(&group));assert(!EmptyCrewTemplate(nullptr));}
|
||||
''')
|
||||
print('PASS: own memberless prefab disables automatic spawn before initialization; actual admission rejects populated, immediate or authored-slot templates before finite crew creation')
|
||||
Reference in New Issue
Block a user