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

88 lines
7.9 KiB
Python

"""Actual bounded carried PMN4 sampler and detached counts; no native qualification."""
from pathlib import Path
import os,re
from policy_harness import ROOT,body,run
BASE=Path(os.environ.get('ERR_MINE_STATUS_ROOT',ROOT))
P=BASE/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMineSampler.c'
A=BASE/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQAmmoSampler.c'
s=P.read_text()
for forbidden in ('TrySpawn','TryMove','SetOrigin','GetOrigin','ActivateTrigger','GetAgents','GetWorld','SetAmmoCount'):
assert forbidden not in s,forbidden
snapshot=A.read_text().split('class ERR_HQAmmoSampler')[0]
owned=body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c',' static bool StoredUnder(')
def cpp(s):
s=re.sub(r'//[^\n]*','',s).replace('class ','struct ').replace('protected ','').replace('foreach (','for (').replace('array<','Array<').replace('null','nullptr')
s=s.replace('InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent))','actor->inventory')
for entity in ('item','entity'):
s=s.replace(f'InventoryItemComponent.Cast({entity}.FindComponent(InventoryItemComponent))',f'{entity}->carried')
s=s.replace('SCR_PressureTriggerComponent.Cast(item.FindComponent(SCR_PressureTriggerComponent))','item->trigger')
s=s.replace('DamageManagerComponent.Cast(item.FindComponent(DamageManagerComponent))','item->hasDamage ? &item->damage : nullptr')
s=s.replace('item.FindComponent(SCR_AntiPersonnelMineCollisionHandlerComponent)','item->collision')
for name in ('Replication','ERR_Actors','ERR_EnemyLoadoutCatalog','ERR_InfantryAmmoTransfer'):
s=s.replace(name+'.',name+'::')
s=re.sub(r'\bERR_HQAmmoSnapshot (?=\w)','ERR_HQAmmoSnapshot* ',s)
for name in ('actor','group','result','inventory','item','entity','carried','trigger','copy','storage','damage'):
s=re.sub(r'\b'+name+r'\.',name+'->',s)
s=s.replace('GetPrefabData().','GetPrefabData()->').replace('GetParentSlot().','GetParentSlot()->')
s=re.sub(r'\b(bool|int) (\w+);',r'\1 \2=0;',s)
return s.replace('\n}\n','\n};\n')
run(r'''
#include <cassert>
#include <vector>
#include <string>
#include <algorithm>
using string=std::string;
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}void Insert(T v){this->push_back(v);}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
struct Entity;struct Group;struct Inventory;using IEntity=Entity*;using SCR_AIGroup=Group*;
struct Prefab{string name="PMN4";string GetPrefabName(){return name;}};
struct Trigger{bool active=false,byDefault=false;bool IsActivated(){return active;}bool IsActivatedByDefault(){return byDefault;}};using SCR_PressureTriggerComponent=Trigger*;
struct Storage{Entity*owner=nullptr;Entity*GetOwner(){return owner;}};using BaseInventoryStorageComponent=Storage*;
struct Slot{Entity*attached=nullptr;Storage*storage=nullptr;Entity*GetAttachedEntity(){return attached;}Storage*GetStorage(){return storage;}};
struct Carried{Slot*slot=nullptr;Slot*GetParentSlot(){return slot;}};using InventoryItemComponent=Carried*;
struct Damage{bool dead=false;bool IsDestroyed(){return dead;}};using DamageManagerComponent=Damage*;
struct Entity{Group*group=nullptr;Inventory*inventory=nullptr;Prefab*prefab=nullptr;Carried*carried=nullptr;Trigger*trigger=nullptr;Entity*parent=nullptr;
Damage damage;bool hasDamage=true;bool alive=true,player=false,collision=true;Prefab*GetPrefabData(){return prefab;}Entity*GetParent(){return parent;}};
struct Group{bool slave=false;int players=0;bool IsSlave(){return slave;}int GetPlayerCount(bool){return players;}};
struct Inventory{Entity*owner=nullptr;Array<IEntity>items;int reads=0;IEntity GetOwner(){return owner;}void GetItems(Array<IEntity>&a){reads++;a=items;}};using InventoryStorageManagerComponent=Inventory*;
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};
struct ERR_EnemyLoadoutCatalog{inline static string MINE="PMN4";};
struct ERR_Actors{static bool Alive(IEntity e){return e&&e->alive;}static bool Player(IEntity e){return e&&e->player;}static struct Group*Group(IEntity e){return e?e->group:nullptr;}};
struct ERR_InfantryAmmoTransfer{
'''+cpp(owned)+r'''
};
'''+cpp(snapshot)+cpp(s)+r'''
struct Fixture{Entity actor,mine,other,bag;Group group;Inventory inventory;Prefab prefab,otherPrefab;Carried carried,bagCarried;Slot slot,bagSlot;Storage storage,bagStorage;Trigger trigger;
Fixture(){actor.group=&group;actor.inventory=&inventory;inventory.owner=&actor;inventory.items={&mine};mine.prefab=&prefab;mine.carried=&carried;mine.trigger=&trigger;carried.slot=&slot;slot.attached=&mine;slot.storage=&storage;storage.owner=&actor;otherPrefab.name="rifle";other.prefab=&otherPrefab;}
ERR_HQAmmoSnapshot Sample(){ERR_HQAmmoSnapshot s;s.MineComplete=true;s.Complete=true;s.LauncherComplete=true;s.Members=1;ERR_HQMineSampler::Inspect(&actor,&group,&s);return s;}
};
int main(){
{Fixture f;f.inventory.items={&f.mine,&f.other,&f.mine};auto s=f.Sample();assert(s.MineComplete&&s.ReadyMines==1&&s.MineMembers==1&&s.Valid(1));
auto frozen=s.Snapshot();f.inventory.items.clear();s.ReadyMines=0;assert(frozen->ReadyMines==1&&frozen->MineMembers==1&&frozen->MineComplete);}
{Fixture f;f.inventory.items.clear();auto s=f.Sample();assert(s.MineComplete&&s.ReadyMines==0&&s.Valid(1));}
// Existing nested carried storage is accepted; no weapon manager is required.
{Fixture f;f.storage.owner=&f.bag;f.bag.carried=&f.bagCarried;f.bagCarried.slot=&f.bagSlot;f.bagSlot.attached=&f.bag;f.bagSlot.storage=&f.bagStorage;f.bagStorage.owner=&f.actor;
auto s=f.Sample();assert(s.MineComplete&&s.ReadyMines==1);}
// Known armed/default-armed/damaged items are not available unarmed mines.
for(int k=0;k<3;k++){Fixture f;if(k==0)f.trigger.active=true;if(k==1)f.trigger.byDefault=true;if(k==2)f.mine.damage.dead=true;
auto s=f.Sample();assert(s.MineComplete&&s.ReadyMines==0&&s.Complete&&s.LauncherComplete);}
// Unsupported/broken inventory or PMN4 custody never means known empty.
for(int k=0;k<13;k++){Fixture f;switch(k){case 0:f.actor.inventory=nullptr;break;case 1:f.inventory.owner=&f.other;break;
case 2:f.inventory.items.resize(129,&f.mine);break;case 3:f.inventory.items={nullptr};break;case 4:f.mine.prefab=nullptr;break;
case 5:f.mine.carried=nullptr;break;case 6:f.carried.slot=nullptr;break;case 7:f.slot.attached=&f.other;break;
case 8:f.storage.owner=&f.other;break;case 9:f.mine.collision=false;break;case 10:f.mine.trigger=nullptr;break;case 11:f.slot.storage=nullptr;break;case 12:f.mine.hasDamage=false;break;}
auto s=f.Sample();assert(!s.MineComplete&&s.ReadyMines==0&&s.Complete&&s.LauncherComplete&&s.Valid(1));}
// Cyclic ownership is bounded rather than accepted as carried stock.
{Fixture f;f.storage.owner=&f.mine;auto s=f.Sample();assert(!s.MineComplete&&s.ReadyMines==0);}
// Foreign/dead/player/oversized roster rejection does not query inventory.
for(int k=0;k<6;k++){Fixture f;Group other;if(k==0)f.actor.group=&other;if(k==1)f.actor.alive=false;if(k==2)f.actor.player=true;
if(k==3){f.group.players=1;}if(k==4){f.group.slave=true;}if(k==5){Replication::server=false;}
auto s=f.Sample();assert(!s.MineComplete&&s.MineMembers==0&&f.inventory.reads==0);Replication::server=true;}
{Fixture f;ERR_HQAmmoSnapshot s;s.MineComplete=true;s.MineMembers=32;ERR_HQMineSampler::Inspect(&f.actor,&f.group,&s);assert(!s.MineComplete&&s.MineMembers==32&&f.inventory.reads==0);}
{Fixture f;f.inventory.items.resize(128,&f.mine);auto s=f.Sample();assert(s.MineComplete&&s.ReadyMines==1);}
// Impossible wire counts and incomplete coverage are rejected independently.
{ERR_HQAmmoSnapshot s;assert(s.Valid(0)&&!s.MineComplete);s.ReadyMines=1;assert(!s.Valid(0));s.ReadyMines=0;s.MineMembers=-1;assert(!s.Valid(1));
s.MineMembers=1;s.MineComplete=true;assert(s.Valid(1)&&!s.Valid(2));s.ReadyMines=129;assert(!s.Valid(1));s.ReadyMines=-1;assert(!s.Valid(1));}
}
''')
print('PASS: actual carried PMN4 inventory, original-owner guards, bounded deduplication, independent unknown state and detached counts')