Files
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

77 lines
5.6 KiB
Python

"""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','Tactics','CloseContact'):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++;}void Stop(ERR_GameMode*){stops++;}};
struct Native{RecoveryTask* Recovery=nullptr;RecoveryTask* Tactics=nullptr;RecoveryTask* CloseContact=nullptr;bool Released=false,PlayerCaptureObserved=false;int delegated=0,clears=0;GroupType* Group=nullptr;void* Entity=nullptr;void* Downlink=nullptr;void CancelHQDownlink(){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,close;unit.Recovery=&recovery;unit.CloseContact=&close;
unit.Release(&mode);assert(unit.Released&&!unit.Downlink&&unit.clears==1&&unit.delegated==0);
assert(recovery.stops==1&&close.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')
# Execute the cancellation bridge itself, including teardown with unavailable HQ/world.
cancel=body(source,'void CancelHQDownlink()').replace('ERR_HQ hq','ERR_HQ* hq').replace('ERR_HQ.Get()', 'ERR_HQ::Get()').replace('hq.', 'hq->').replace('Downlink.', 'Downlink->').replace('GetGame().GetWorld().GetWorldTime()', 'GetGame()->GetWorld()->GetWorldTime()').replace('GetGame().GetWorld()', 'GetGame()->GetWorld()').replace('null','nullptr')
run(r'''
#include <cassert>
#include <string>
using string=std::string;
struct World{float GetWorldTime(){return 1234;}};
struct Game{World world;World*current=&world;World*GetWorld(){return current;}}game;
Game*currentGame=&game;Game*GetGame(){return currentGame;}
struct ERR_HQ{inline static ERR_HQ*current=nullptr;int calls=0,message=0;string source;float at=0;static ERR_HQ*Get(){return current;}void CoordinatedHunterDelivery(string s,int m,bool delivered,float now){assert(!delivered);calls++;source=s;message=m;at=now;}};
struct Route{int MessageId=77;};
struct Hunter{string Source="original-hunter";Route*Downlink=nullptr;
'''+cancel+r'''
};
int main(){ERR_HQ hq;ERR_HQ::current=&hq;Hunter unit;Route route;unit.Downlink=&route;
unit.CancelHQDownlink();assert(!unit.Downlink&&hq.calls==1&&hq.message==77&&hq.source==unit.Source&&hq.at==1234);
unit.CancelHQDownlink();assert(hq.calls==1);
for(int missing=0;missing<3;missing++){
ERR_HQ::current=&hq;currentGame=&game;game.current=&game.world;unit.Downlink=&route;
if(missing==0)ERR_HQ::current=nullptr;
if(missing==1)currentGame=nullptr;
if(missing==2)game.current=nullptr;
unit.CancelHQDownlink();assert(!unit.Downlink&&hq.calls==1);
}
}
''')
print('PASS: actual hunter cancellation releases exactly the pending source/message once; teardown clears ownership without HQ/game/world')