Integrate startup optics and supply vehicle lock picking
This commit is contained in:
@@ -13,9 +13,9 @@ code=code.replace('static const ResourceName','static constexpr const char*').re
|
||||
code=code.replace('string result','std::string result').replace('array<IEntity>','std::vector<IEntity*>')
|
||||
code=code.replace('BaseWeaponManagerComponent.Cast(Actor.FindComponent(BaseWeaponManagerComponent))','Actor->weapons')
|
||||
code=code.replace('InventoryStorageManagerComponent.Cast(Actor.FindComponent(InventoryStorageManagerComponent))','Actor->inventory')
|
||||
for cls in ('ERR_GameMode','SCR_AIGroup','SCR_AIGroupUtilityComponent','IEntity','SCR_AICombatComponent','ERR_EnemyEquipmentCallback','BaseWeaponManagerComponent','InventoryStorageManagerComponent'):
|
||||
for cls in ('ERR_GameMode','SCR_AIGroup','SCR_AIGroupUtilityComponent','IEntity','SCR_AICombatComponent','ERR_EnemyEquipmentCallback','ERR_EnemyOptics','BaseWeaponManagerComponent','InventoryStorageManagerComponent'):
|
||||
code=re.sub(r'\b'+cls+r' ([a-zA-Z_]\w*)',cls+r'* \1',code)
|
||||
for obj in ('mode','group','utility','Actor','Group','Combat','Operation','item','weapons','inventory'):
|
||||
for obj in ('mode','group','utility','Actor','Group','Combat','Optics','Operation','item','weapons','inventory'):
|
||||
code=code.replace(obj+'.',obj+'->')
|
||||
for cls in ('Replication','SCR_Global','ERR_GameMode','ERR_Actors','ERR_EnemyEquipmentRules','EAISkill','ERR_HQ','Math','EStoragePurpose'):
|
||||
code=code.replace(cls+'.',cls+'::')
|
||||
@@ -67,13 +67,14 @@ struct NativeCombat {
|
||||
IEntity* GetOwner(){return owner;}EAISkill GetAISkill(){return skill;}
|
||||
virtual void SetAISkill(EAISkill value){skill=value;}virtual void ResetAISkill(){skill=EAISkill::ROOKIE;}
|
||||
};
|
||||
struct ERR_EnemyOptics{int stops=0;void Stop(){stops++;}};
|
||||
''' + code + r'''
|
||||
std::vector<std::unique_ptr<ERR_EnemyEquipmentCallback>> operations;
|
||||
ERR_EnemyEquipmentCallback* NewOperation(){operations.emplace_back(new ERR_EnemyEquipmentCallback());return operations.back().get();}
|
||||
struct Soldier {
|
||||
SCR_AIGroup group;BaseWeaponManagerComponent weapons;InventoryStorageManagerComponent inventory;IEntity actor;SCR_AICombatComponent combat;
|
||||
SCR_AIGroup group;BaseWeaponManagerComponent weapons;InventoryStorageManagerComponent inventory;IEntity actor;SCR_AICombatComponent combat;ERR_EnemyOptics optics;
|
||||
Soldier(){actor.group=&group;actor.weapons=&weapons;actor.inventory=&inventory;combat.owner=&actor;}
|
||||
ERR_EnemyEquipmentEntry Entry(){ERR_EnemyEquipmentEntry e;e.Actor=&actor;e.Group=&group;e.Combat=&combat;return e;}
|
||||
ERR_EnemyEquipmentEntry Entry(){ERR_EnemyEquipmentEntry e;e.Actor=&actor;e.Group=&group;e.Combat=&combat;e.Optics=&optics;return e;}
|
||||
};
|
||||
int main(){
|
||||
ERR_GameMode mode;ERR_GameMode::current=&mode;
|
||||
@@ -123,7 +124,7 @@ int main(){
|
||||
skill.combat.SetAISkill(EAISkill::ROOKIE);assert(skill.combat.GetAISkill()==EAISkill::ROOKIE);
|
||||
Soldier moved;SCR_AIGroup replacement;moved.combat.ERR_BindEnemySkill(&mode,&moved.group);moved.actor.group=&replacement;
|
||||
moved.combat.SetAISkill(EAISkill::ROOKIE);assert(moved.combat.GetAISkill()==EAISkill::ROOKIE);
|
||||
Soldier stopped;auto terminal=stopped.Entry();terminal.Check(&mode);terminal.Stop(&mode);stopped.inventory.callback->OnComplete();
|
||||
Soldier stopped;auto terminal=stopped.Entry();terminal.Check(&mode);terminal.Stop(&mode);assert(stopped.optics.stops==1);stopped.inventory.callback->OnComplete();
|
||||
for(int n=0;n<10;n++){terminal.Check(&mode);}assert(stopped.inventory.requests==1&&terminal.Complete);
|
||||
}
|
||||
''')
|
||||
@@ -136,7 +137,7 @@ for cls in ('ERR_GameMode','SCR_AIGroup','ERR_EnemyEquipmentGroup','ERR_EnemyEqu
|
||||
coordinator=re.sub(r'\b'+cls+r' ([a-zA-Z_]\w*)',cls+r'* \1',coordinator)
|
||||
for obj in ('m_Mode','existing','state','group','agent','entry','actor'):
|
||||
coordinator=coordinator.replace(obj+'.',obj+'->')
|
||||
coordinator=coordinator.replace('Group.GetAgents','Group->GetAgents').replace('Combat.ERR_','Combat->ERR_')
|
||||
coordinator=coordinator.replace('Group.GetAgents','Group->GetAgents').replace('Combat.ERR_','Combat->ERR_').replace('Optics.Check','Optics->Check')
|
||||
for cls in ('Replication','ERR_GameMode','ERR_EnemyEquipmentRules'):coordinator=coordinator.replace(cls+'.',cls+'::')
|
||||
coordinator=coordinator.replace('void ERR_EnemyEquipment(', 'ERR_EnemyEquipment(')
|
||||
coordinator=coordinator.replace('GetGame().GetWorld().GetWorldTime()','worldTime').replace('foreach (','for (').replace('null','nullptr')
|
||||
@@ -155,9 +156,15 @@ struct IEntity{SCR_AICombatComponent* combat=nullptr;bool eligible=true;};
|
||||
struct AIAgent{IEntity* actor=nullptr;IEntity* GetControlledEntity(){return actor;}};
|
||||
struct SCR_AIGroup{Array<AIAgent*> agents;void GetAgents(Array<AIAgent*>& out){out=agents;}};
|
||||
namespace ERR_EnemyEquipmentRules{bool Eligible(ERR_GameMode*,SCR_AIGroup*,IEntity* actor){return actor&&actor->eligible;}}
|
||||
struct ERR_EnemyOptics{
|
||||
IEntity* actor;SCR_AIGroup* group;int checks=0,stops=0;
|
||||
ERR_EnemyOptics(IEntity* a,SCR_AIGroup* g):actor(a),group(g){}
|
||||
void Check(ERR_GameMode*){checks++;}void Stop(){stops++;}
|
||||
};
|
||||
struct ERR_EnemyEquipmentEntry{
|
||||
ERR_EnemyOptics* Optics=nullptr;bool Complete=false;
|
||||
IEntity* Actor=nullptr;SCR_AIGroup* Group=nullptr;SCR_AICombatComponent* Combat=nullptr;bool Released=false;int SkillChecks=0,checks=0,stops=0;
|
||||
void Check(ERR_GameMode*){checks++;}void Stop(ERR_GameMode*){stops++;Released=true;Combat=nullptr;}
|
||||
void Check(ERR_GameMode*){checks++;Complete=true;}void Stop(ERR_GameMode*){stops++;Released=true;if(Optics)Optics->Stop();Combat=nullptr;}
|
||||
};
|
||||
''' + coordinator + r'''
|
||||
int main(){
|
||||
@@ -165,8 +172,11 @@ int main(){
|
||||
SCR_AIGroup group;IEntity soldier;SCR_AICombatComponent combat;soldier.combat=&combat;AIAgent agent;agent.actor=&soldier;group.agents.Insert(&agent);
|
||||
service.RegisterGroup(&group);service.RegisterGroup(&group);assert(service.m_Groups.Count()==1);
|
||||
service.Tick(0);assert(service.m_Entries.Count()==0);service.Tick(3000);assert(service.m_Entries.Count()==1&&combat.binds==1);
|
||||
service.Tick(3001);assert(service.m_Entries[0]->checks==1);service.Tick(6000);assert(service.m_Entries[0]->checks==2&&combat.binds==1);
|
||||
service.UnregisterGroup(&group);service.Tick(9000);assert(service.m_Entries[0]->Released&&service.m_Entries[0]->checks==2);
|
||||
auto entry=service.m_Entries[0];assert(entry->Optics&&entry->Optics->actor==&soldier&&entry->Optics->group==&group);
|
||||
assert(entry->Complete&&entry->Optics->checks==1); // Stinger is already complete; optics has its own startup completion.
|
||||
service.Tick(3001);assert(entry->checks==1&&entry->Optics->checks==1);
|
||||
service.Tick(6000);assert(entry->checks==2&&entry->Optics->checks==2&&combat.binds==1);
|
||||
service.UnregisterGroup(&group);service.Tick(9000);assert(service.m_Entries[0]->Released&&service.m_Entries[0]->checks==2&&entry->Optics->checks==2&&entry->Optics->stops==1);
|
||||
service.RegisterGroup(&group);service.Tick(12000);assert(service.m_Entries.Count()==1&&combat.binds==1);
|
||||
service.Stop();service.RegisterGroup(&group);service.Tick(50000);assert(service.m_Groups.Count()==0&&service.m_Stopped);
|
||||
ERR_EnemyEquipment delayed(&mode);SCR_AIGroup late;delayed.RegisterGroup(&late);delayed.Tick(3000);late.agents.Insert(&agent);delayed.Tick(6000);assert(delayed.m_Entries.Count()==1);
|
||||
@@ -174,7 +184,13 @@ int main(){
|
||||
ERR_EnemyEquipment bounded(&mode);for(int i=0;i<140;i++){bounded.RegisterGroup(new SCR_AIGroup());}assert(bounded.m_Groups.Count()==128);
|
||||
ERR_EnemyEquipment incomplete(&mode);SCR_AIGroup unresolved;IEntity body;AIAgent bodyAgent;bodyAgent.actor=&body;unresolved.agents.Insert(&bodyAgent);incomplete.RegisterGroup(&unresolved);
|
||||
for(int i=1;i<=12;i++){incomplete.Tick(i*3000);}assert(incomplete.m_Entries.Count()==1&&incomplete.m_Entries[0]->SkillChecks==5);
|
||||
incomplete.Stop();
|
||||
auto incompleteOptics=incomplete.m_Entries[0]->Optics;int checksBeforeStop=incompleteOptics->checks;
|
||||
incomplete.Stop();incomplete.Tick(60000);assert(incompleteOptics->stops==1&&incompleteOptics->checks==checksBeforeStop);
|
||||
ERR_EnemyEquipment capture(&mode);SCR_AIGroup capturedGroup;IEntity capturedBody;AIAgent capturedAgent;capturedAgent.actor=&capturedBody;
|
||||
capturedGroup.agents.Insert(&capturedAgent);capture.RegisterGroup(&capturedGroup);capture.Tick(3000);
|
||||
auto capturedOptics=capture.m_Entries[0]->Optics;capturedBody.eligible=false;capture.Tick(6000);
|
||||
assert(capturedOptics->checks==1&&capturedOptics->stops==1&&capture.m_Entries[0]->Released);
|
||||
capturedBody.eligible=true;capture.Tick(9000);assert(capturedOptics->checks==1);
|
||||
}
|
||||
''')
|
||||
print('PASS: actual equipment coordinator registration dedupe, startup delay, five member scans, finite component retries, unregister/stop terminal behavior and 128-group bound')
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
"""Execute actual startup optics service through native attachment API doubles; no native proof."""
|
||||
from pathlib import Path
|
||||
import os,re
|
||||
from policy_harness import ROOT,run
|
||||
P=Path(os.environ.get('ERR_OPTICS_SOURCE',ROOT/'addons/ai/Scripts/Game/ERR/ERR_EnemyOptics.c'))
|
||||
s=P.read_text()
|
||||
assert 'GetGame(' not in s and 'CallLater' not in s and 'DeleteEntity' not in s
|
||||
callback=(ROOT/'addons/ai/Scripts/Game/ERR/ERR_EnemyEquipment.c').read_text().split('class ERR_EnemyEquipmentCallback',1)[1].split('class ERR_EnemyEquipmentEntry',1)[0]
|
||||
code='class ERR_EnemyEquipmentCallback'+callback+s
|
||||
code=re.sub(r'//[^\n]*','',code).replace('protected ','').replace('override ','').replace('class ','struct ')
|
||||
for src,dst in {
|
||||
'BaseWeaponManagerComponent.Cast(m_Actor.FindComponent(BaseWeaponManagerComponent))':'m_Actor->manager',
|
||||
'InventoryStorageManagerComponent.Cast(m_Actor.FindComponent(InventoryStorageManagerComponent))':'m_Actor->inventory',
|
||||
'BaseWeaponComponent.Cast(job.Rifle.FindComponent(BaseWeaponComponent))':'job->Rifle->weapon',
|
||||
'BaseWeaponComponent.Cast(rifle.FindComponent(BaseWeaponComponent))':'rifle->weapon',
|
||||
'WeaponAttachmentsStorageComponent.Cast(job.Rifle.FindComponent(WeaponAttachmentsStorageComponent))':'job->Rifle->storage',
|
||||
'entity.FindComponent(SCR_2DOpticsComponent)':'entity->scope2D',
|
||||
'entity.FindComponent(SCR_2DPIPSightsComponent)':'entity->scope2DPip',
|
||||
'entity.FindComponent(SCR_PIPSightsComponent)':'entity->scopePip',
|
||||
}.items():code=code.replace(src,dst)
|
||||
code=code.replace('ref array<ref ERR_EnemyOpticsJob>','Array<ERR_EnemyOpticsJob*>').replace('ref array<','Array<').replace('array<','Array<')
|
||||
code=code.replace('out Array<ResourceName> result','Array<ResourceName>& result')
|
||||
for name in ('ERR_EnemyEquipmentCallback','ERR_EnemyOpticsJob','ERR_EnemyOpticsInspection'):
|
||||
code=re.sub(r'\b(?:ref )?'+name+r' (?=\w)',name+'* ',code)
|
||||
code=code.replace('void ERR_EnemyOptics(','ERR_EnemyOptics(')
|
||||
code=code.replace('EWeaponType type','int type').replace('foreach (','for (').replace('null','nullptr')
|
||||
for name in ('ERR_EnemyEquipmentRules','ERR_HQ','Math','EStoragePurpose','EWeaponType'):code=code.replace(name+'.',name+'::')
|
||||
for name in ('entity','child','weapon','sights','slot','job','manager','inventory','storage','inspection'):
|
||||
code=re.sub(r'\b'+name+r'\.',name+'->',code)
|
||||
code=code.replace('job->Operation.','job->Operation->')
|
||||
code=re.sub(r'\b(bool|int|IEntity|SCR_AIGroup) (\w+);',r'\1 \2{};',code)
|
||||
code=re.sub(r'\b(ERR_EnemyEquipmentCallback\*) (\w+);',r'\1 \2{};',code)
|
||||
code=code.replace('\n}\n','\n};\n')
|
||||
preamble=r'''
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
using string=std::string;using ResourceName=std::string;
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Insert(T t){this->push_back(t);}void Remove(int index){this->erase(this->begin()+index);}bool Contains(T t){return std::find(this->begin(),this->end(),t)!=this->end();}};
|
||||
struct Entity;struct Group;struct Weapon;struct Manager;struct Inventory;struct Storage;struct Slot;struct Sights;struct ERR_GameMode{};
|
||||
using IEntity=Entity*;using SCR_AIGroup=Group*;using BaseWeaponComponent=Weapon*;using BaseWeaponManagerComponent=Manager*;
|
||||
using InventoryStorageManagerComponent=Inventory*;using WeaponAttachmentsStorageComponent=Storage*;using InventoryStorageSlot=Slot*;using AttachmentSlotComponent=Slot*;using BaseSightsComponent=Sights*;
|
||||
struct ScriptedInventoryOperationCallback{virtual void OnComplete(){}virtual void OnFailed(){}virtual void OnAllComplete(){}virtual void OnAllFailed(){}virtual ~ScriptedInventoryOperationCallback()=default;};
|
||||
struct EWeaponType{static const int WT_SNIPERRIFLE=1,WT_RIFLE=2,WT_MACHINEGUN=3,WT_HANDGUN=4;};
|
||||
struct EStoragePurpose{static const int PURPOSE_ANY=0;};struct Math{static int RandomInt(int,int){return 0;}};
|
||||
struct Group{};
|
||||
struct Slot{IEntity attached=nullptr;IEntity GetAttachedEntity(){return attached;}};
|
||||
struct Storage{Array<Slot*>slots;int GetSlotsCount(){return slots.Count();}Slot* GetSlot(int n){return slots[n];}};
|
||||
struct Sights{IEntity owner=nullptr;IEntity GetOwner(){return owner;}};
|
||||
struct Weapon{IEntity owner=nullptr;int kind=EWeaponType::WT_RIFLE;Sights*sights=nullptr;Array<Slot*>slots;
|
||||
IEntity GetOwner(){return owner;}int GetWeaponType(){return kind;}Sights* GetAttachedSights(){return sights;}void GetAttachments(Array<Slot*>&out){out=slots;}};
|
||||
struct Manager{Array<IEntity>weapons;void GetWeaponsList(Array<IEntity>&out){out=weapons;}};
|
||||
struct Entity{Manager*manager=nullptr;Inventory*inventory=nullptr;Weapon*weapon=nullptr;Storage*storage=nullptr;
|
||||
Entity*child=nullptr,*sibling=nullptr;Group*group=nullptr;bool eligible=true,scope2D=false,scope2DPip=false,scopePip=false;
|
||||
IEntity GetChildren(){return child;}IEntity GetSibling(){return sibling;}};
|
||||
struct Inventory{int submits=0,checks=0;bool accept=true,fit=true;int callback=0;ScriptedInventoryOperationCallback*last=nullptr;Array<int>requested;
|
||||
bool CanInsertResourceInStorage(ResourceName,Storage*,int){checks++;return fit;}
|
||||
bool TrySpawnPrefabToStorage(ResourceName,Storage*,int slot,int,ScriptedInventoryOperationCallback*cb){submits++;requested.Insert(slot);last=cb;
|
||||
if(callback==1){cb->OnComplete();}if(callback==2){cb->OnFailed();}return accept;}};
|
||||
struct ERR_EnemyEquipmentRules{static bool Eligible(ERR_GameMode* mode,Group*group,IEntity actor){return mode&&group&&actor&&actor->eligible&&actor->group==group;}};
|
||||
struct ERR_HQ{static void Event(string,string){}};
|
||||
'''
|
||||
# Enforce class arguments are reference types.
|
||||
code=re.sub(r'\bERR_GameMode (?=\w)','ERR_GameMode* ',code)
|
||||
main=r'''
|
||||
struct Fixture{ERR_GameMode mode;Group group,other;Entity actor,rifle;Weapon weapon;Manager manager;Inventory inv;Storage storage;Slot slot;
|
||||
Fixture(){actor.group=&group;actor.manager=&manager;actor.inventory=&inv;manager.weapons={&rifle};rifle.weapon=&weapon;rifle.storage=&storage;weapon.owner=&rifle;storage.slots={&slot};}
|
||||
};
|
||||
int main(){
|
||||
// Existing mounted/nested/sights magnified optics are preserved. Occupied red dots aren't overwritten.
|
||||
for(int style=0;style<3;style++){Fixture f;Entity optic,adapter;Sights sights;Slot attach;
|
||||
optic.scope2D=style==0;optic.scope2DPip=style==1;optic.scopePip=style==2;if(style==0){f.rifle.child=&adapter;adapter.child=&optic;}
|
||||
if(style==1){sights.owner=&optic;f.weapon.sights=&sights;}
|
||||
if(style==2){attach.attached=&optic;f.weapon.slots={&attach};}
|
||||
ERR_EnemyOptics s(&f.actor,&f.group);assert(s.Check(&f.mode));assert(f.inv.submits==0&&s.m_Jobs[0]->Confirmed);}
|
||||
{Fixture f;Entity reddot;f.slot.attached=&reddot;ERR_EnemyOptics s(&f.actor,&f.group);for(int i=0;i<6;i++)s.Check(&f.mode);assert(f.inv.submits==0);}
|
||||
// Positive grant targets one concrete native free compatible slot; accepted unknown is never repeated.
|
||||
{Fixture f;ERR_EnemyOptics s(&f.actor,&f.group);assert(!s.Check(&f.mode));assert(f.inv.submits==1&&f.inv.requested[0]==0);
|
||||
for(int i=0;i<20;i++){s.Check(&f.mode);}assert(f.inv.submits==1&&s.m_Checks==5&&s.m_Jobs[0]->Terminal);
|
||||
f.inv.last->OnFailed();s.Check(&f.mode);assert(f.inv.submits==1);}
|
||||
// Both sync/async success terminate even if the optic disappears before physical observation.
|
||||
for(int sync=0;sync<2;sync++){Fixture f;f.inv.callback=sync;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);
|
||||
if(!sync){f.inv.last->OnAllComplete();}assert(s.Check(&f.mode));assert(f.inv.submits==1&&!s.m_Jobs[0]->Confirmed);}
|
||||
// Callback first terminal result wins; never turn a success into another grant.
|
||||
{Fixture f;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);f.inv.last->OnComplete();f.inv.last->OnFailed();s.Check(&f.mode);assert(f.inv.submits==1);}
|
||||
// Explicit failures permit a different prefab/mount, but only one request per check and never fifth.
|
||||
for(int kind=0;kind<3;kind++){Fixture f;f.inv.accept=kind!=0;f.inv.callback=kind==1?2:0;ERR_EnemyOptics s(&f.actor,&f.group);
|
||||
for(int i=0;i<5;i++){s.Check(&f.mode);if(kind==2&&f.inv.last)f.inv.last->OnAllFailed();}
|
||||
assert(f.inv.submits==4&&s.m_Jobs[0]->Rejected.Count()>=3);assert(s.m_Jobs[0]->Attempt==48);}
|
||||
{Fixture f;f.inv.accept=false;f.inv.callback=1;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);s.Check(&f.mode);assert(f.inv.submits==1);}
|
||||
// Delayed inventory is bounded; fifth check can observe a starting rifle but cannot issue it.
|
||||
{Fixture f;f.actor.inventory=nullptr;ERR_EnemyOptics s(&f.actor,&f.group);for(int i=0;i<4;i++)assert(!s.Check(&f.mode));f.actor.inventory=&f.inv;assert(s.Check(&f.mode));assert(f.inv.submits==0);}
|
||||
{Fixture f;f.inv.fit=false;ERR_EnemyOptics s(&f.actor,&f.group);for(int i=0;i<5;i++)s.Check(&f.mode);assert(f.inv.submits==0&&f.inv.checks==28);}
|
||||
// Exact original actor and original physical weapon identity are terminal; no picked-up replacements.
|
||||
{Fixture f;f.inv.fit=false;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);Entity replacement;replacement.weapon=&f.weapon;replacement.storage=&f.storage;
|
||||
f.manager.weapons={&replacement};f.inv.fit=true;assert(s.Check(&f.mode));f.manager.weapons={&f.rifle};s.Check(&f.mode);assert(f.inv.submits==0&&s.m_Jobs.Count()==1);}
|
||||
{Fixture f;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);f.actor.group=&f.other;assert(s.Check(&f.mode));f.actor.group=&f.group;f.inv.last->OnFailed();s.Check(&f.mode);assert(f.inv.submits==1);}
|
||||
{Fixture f;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);s.Stop();f.inv.last->OnFailed();s.Check(&f.mode);assert(f.inv.submits==1);}
|
||||
{Fixture f;f.actor.eligible=false;ERR_EnemyOptics s(&f.actor,&f.group);assert(s.Check(&f.mode));assert(f.inv.submits==0);}
|
||||
// Duplicate native weapon roster entries don't create duplicate requests; nonrifles are ignored.
|
||||
{Fixture f;f.manager.weapons={&f.rifle,&f.rifle};ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==1&&s.m_Jobs.Count()==1);}
|
||||
{Fixture f;f.weapon.kind=EWeaponType::WT_HANDGUN;ERR_EnemyOptics s(&f.actor,&f.group);for(int i=0;i<5;i++)s.Check(&f.mode);assert(f.inv.submits==0);}
|
||||
// Malformed/oversized hierarchies fail closed, including cycles and too-deep adapters.
|
||||
{Fixture f;f.rifle.child=&f.rifle;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==0);}
|
||||
{Fixture f;Entity child;f.rifle.child=&child;child.sibling=&child;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==0);}
|
||||
{Fixture f;for(int i=0;i<17;i++)f.weapon.slots.Insert(&f.slot);ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==0);}
|
||||
{Fixture f;for(int i=0;i<17;i++)f.storage.slots.Insert(&f.slot);ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==0);}
|
||||
{Fixture f;for(int i=0;i<9;i++)f.manager.weapons.Insert(&f.rifle);ERR_EnemyOptics s(&f.actor,&f.group);assert(s.Check(&f.mode));assert(f.inv.submits==0);}
|
||||
}
|
||||
'''
|
||||
run(preamble+code+main)
|
||||
print('Enemy optics actual-service ownership, preservation, callback, deadline and bounded-inspection policies passed')
|
||||
@@ -1,9 +1,11 @@
|
||||
"""Execute both production monitor bodies; native hint/camera boundaries are fakes."""
|
||||
from policy_harness import ROOT, body, run
|
||||
GUIDE=ROOT/'addons/core/Scripts/Game/ERR/ERR_PlayerGuide.c'
|
||||
from pathlib import Path
|
||||
import os
|
||||
GUIDE=Path(os.environ.get('ERR_GUIDE_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/ERR_PlayerGuide.c'))
|
||||
OBSERVER=ROOT/'addons/core/Scripts/Game/ERR/ERR_Observer.c'
|
||||
def cpp(text):
|
||||
text=text.replace('protected ', '')
|
||||
text=text.replace('protected ', '').replace('override ', '').replace('super.OnControlledEntityChanged(from, to);', 'nativeChanges++;')
|
||||
for name in ('ERR_GameMode','ERR_Phase','ERR_ObserverReason','ERR_Actors','ERR_ObjectiveText','SCR_HintManagerComponent','ERR_ObserverGuidance','Resource','ETransformMode','SCR_ManualCamera','SCR_RespawnSystemComponent','RplId'):
|
||||
text=text.replace(name+'.',name+'::')
|
||||
for typename in ('ERR_GameMode','SCR_HintManagerComponent','Resource','SCR_RespawnSystemComponent','CameraManager'):
|
||||
@@ -12,14 +14,14 @@ def cpp(text):
|
||||
text=text.replace(variable+'.',variable+'->')
|
||||
text=text.replace('GetGame().','GetGame()->').replace('EntitySpawnParams params = new EntitySpawnParams();','EntitySpawnParams params;')
|
||||
return text
|
||||
methods=cpp(body(GUIDE,'protected void ERR_UpdateGuide('))+'\n'+cpp(body(OBSERVER,'protected void ERR_UpdateObserverClient('))
|
||||
methods=cpp(body(GUIDE,'override void OnControlledEntityChanged('))+'\n'+cpp(body(GUIDE,'protected void ERR_UpdateGuide('))+'\n'+cpp(body(OBSERVER,'protected void ERR_UpdateObserverClient('))
|
||||
guidance=cpp(body(OBSERVER,'static string Message('))+'\n'+cpp(body(OBSERVER,'static bool Refresh('))
|
||||
run(r'''#include <cassert>
|
||||
#include <string>
|
||||
struct string:std::string {using std::string::string;using std::string::operator=;string(std::string s):std::string(s){}bool IsEmpty()const{return empty();}};
|
||||
namespace ERR_Phase {enum {PREPARING,PRISON,RADIO_SEARCH,RADIO_DEFENCE,EXTRACTION};}
|
||||
namespace ERR_ObserverReason {enum {NONE,SPENT_LIFE,LATE_EXTRACTION,SPAWN_UNAVAILABLE};}
|
||||
struct Actor {bool alive=true;bool unconscious=false;} actor;
|
||||
struct Actor {bool alive=true;bool unconscious=false;} actor;using IEntity=Actor*;
|
||||
struct ERR_Actors {static bool Alive(Actor* a){return a&&a->alive;}};
|
||||
struct ERR_GameMode {int phase=ERR_Phase::PRISON;bool active=true;string message="prison";static ERR_GameMode* current;static ERR_GameMode* Get(){return current;}int Phase(){return phase;}bool Active(){return active;}} mode;
|
||||
ERR_GameMode* ERR_GameMode::current=&mode;
|
||||
@@ -42,6 +44,7 @@ struct RplId {static int Invalid(){return -1;}};
|
||||
struct Game {int spawns=0;SCR_ManualCamera* SpawnEntityPrefab(Resource*,int,EntitySpawnParams){spawns++;return &camera;}int GetWorld(){return 1;}CameraManager* GetCameraManager(){return &manager;}} game;
|
||||
Game* GetGame(){return &game;}
|
||||
struct Controller {
|
||||
int nativeChanges=0,starts=0;void ERR_StartGuide(){starts++;}
|
||||
int m_ERRGuidePhase=-1;string m_ERRGuideMessage;
|
||||
Actor* controlled=&actor;int m_ERRObserverReason=ERR_ObserverReason::NONE;
|
||||
SCR_ManualCamera* m_ERRObserverCamera=nullptr;Vec m_ERRObserverAnchor;
|
||||
@@ -64,6 +67,19 @@ int main(){
|
||||
mode.message="transmission interrupted";hints.accepts=false;g.ERR_UpdateGuide();assert(g.m_ERRGuideMessage=="prison");
|
||||
hints.accepts=true;g.ERR_UpdateGuide();assert(hints.shown==2&&g.m_ERRGuideMessage==mode.message);
|
||||
mode.phase=ERR_Phase::RADIO_SEARCH;g.ERR_UpdateGuide();assert(hints.shown==3);
|
||||
// Reacquiring a character in the same phase clears delivery once; native
|
||||
// lifecycle and the existing monitor remain responsible for actual delivery.
|
||||
Actor replacement;int shown=hints.shown;
|
||||
g.OnControlledEntityChanged(&actor,&replacement);g.controlled=&replacement;
|
||||
assert(g.nativeChanges==1&&g.starts==1&&g.m_ERRGuidePhase==-1);
|
||||
hints.accepts=false;g.ERR_UpdateGuide();assert(hints.shown==shown&&g.m_ERRGuidePhase==-1);
|
||||
hints.accepts=true;g.ERR_UpdateGuide();assert(hints.shown==shown+1);
|
||||
g.ERR_UpdateGuide();assert(hints.shown==shown+1);
|
||||
g.OnControlledEntityChanged(&replacement,&replacement);g.ERR_UpdateGuide();assert(hints.shown==shown+1);
|
||||
g.OnControlledEntityChanged(&replacement,nullptr);g.controlled=nullptr;
|
||||
g.ERR_UpdateGuide();assert(hints.shown==shown+1);
|
||||
g.OnControlledEntityChanged(nullptr,&replacement);g.controlled=&replacement;
|
||||
g.ERR_UpdateGuide();assert(hints.shown==shown+2&&g.nativeChanges==4&&g.starts==4);
|
||||
// Explicit observation, death and absence suppress ordinary phase instructions.
|
||||
for(int excluded=0;excluded<3;excluded++){
|
||||
Controller c;if(excluded==0)c.m_ERRObserverReason=ERR_ObserverReason::SPENT_LIFE;
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
"""Execute production cover-fixture event transitions with native boundaries mocked."""
|
||||
from pathlib import Path
|
||||
import os,re,subprocess,sys
|
||||
from policy_harness import ROOT,body,run
|
||||
P=Path(os.environ.get('ERR_CONVOY_COVER_SOURCE',ROOT/'addons/chat/Scripts/Game/ERR/ERR_NativeConvoyCoverProbe.c'))
|
||||
s=P.read_text()
|
||||
def cpp(s):
|
||||
s=s.replace('protected ','').replace('ref ','').replace('foreach (','for (').replace('null','nullptr')
|
||||
for t in ('ERR_NativeConvoyCoverReport','ERR_HQUnit','ERR_HQReport','ERR_HQRoute','ERR_ConvoyCombatOrder'):
|
||||
s=re.sub(r'\b'+t+r' (?=\w)',t+'* ',s)
|
||||
for v in ('entry','unit','report','order','route','observer','m_Lead','m_Damage'):
|
||||
s=re.sub(r'\b'+v+r'\.',v+'->',s)
|
||||
for field in ('Report','PendingReport','Uplink','Utility','Combat','Entity'):
|
||||
s=s.replace('->'+field+'.','->'+field+'->')
|
||||
return s
|
||||
methods='\n'.join(cpp(body(P,k)) for k in ('protected ERR_NativeConvoyCoverReport Find(', 'void Observed(', 'void Accepted(', 'void Delivered(', 'void Stop('))
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
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 x){this->push_back(x);}bool Contains(T x){return std::find(this->begin(),this->end(),x)!=this->end();}};
|
||||
struct Actor{float origin=7;float GetOrigin(){return origin;}};using IEntity=Actor*;
|
||||
struct ERR_HQReport{int MessageId=1;IEntity Target=nullptr;ERR_HQReport* Snapshot(){return new ERR_HQReport(*this);}};
|
||||
struct ERR_HQRoute{bool Failed=false,Downlink=false;float StartedAt=1;int Cursor=0;Array<int>Nodes={1,2};};
|
||||
struct Utility{IEntity observer=nullptr;IEntity ERR_ResponseVisualObserver(ERR_HQReport*,float){return observer;}};
|
||||
struct ERR_HQUnit{ERR_HQReport* PendingReport=nullptr;ERR_HQRoute* Uplink=nullptr;struct Utility* Utility=nullptr;};
|
||||
struct ERR_NativeConvoyCoverReport{ERR_HQReport* Report=nullptr;ERR_HQRoute* Uplink=nullptr;bool Accepted=false;};
|
||||
struct ERR_ConvoyCombatOrder{int Group=1,CommandId=1,ReportId=1;bool Hold=true;float ObservedAt=10;};
|
||||
struct Combat{bool Held=false;int LastCommandId=1;};
|
||||
struct Lead{int Group=1;struct Combat* Combat=nullptr;bool Waypoint=false;int RouteIndex=2;Actor* Entity=nullptr;};
|
||||
struct Damage{bool enabled=true;int writes=0;void EnableDamageHandling(bool b){enabled=b;writes++;}};
|
||||
struct Probe{
|
||||
bool m_Stopped=false,m_Failed=false,m_Held=false,m_Resumed=false,m_Protected=false,m_DamageWasEnabled=true;
|
||||
int m_RouteIndex=0,checks=0;float m_HeldAt=0,m_ResumedAt=0,m_HoldPosition=0,m_ResumePosition=0;
|
||||
Lead* m_Lead=nullptr;IEntity m_Player=nullptr;Damage* m_Damage=nullptr;
|
||||
Array<IEntity>m_Forces;Array<ERR_NativeConvoyCoverReport*>m_Reports;inline static Probe* s_Active=nullptr;
|
||||
bool Check(bool ok,string){checks++;
|
||||
if(!ok){m_Failed=true;Stop();}return ok;}
|
||||
'''+methods+r'''
|
||||
};
|
||||
struct Setup{
|
||||
Actor player,enemy,stranger;ERR_HQReport report;ERR_HQRoute uplink,downlink;Utility utility;ERR_HQUnit unit;
|
||||
Combat combat;Lead lead;ERR_ConvoyCombatOrder order;Probe p;Damage damage;
|
||||
Setup(){report.Target=&player;utility.observer=&enemy;unit.PendingReport=&report;unit.Uplink=&uplink;unit.Utility=&utility;
|
||||
lead.Combat=&combat;lead.Entity=&enemy;p.m_Lead=&lead;p.m_Player=&player;p.m_Forces={&enemy};
|
||||
p.m_Damage=&damage;p.m_Protected=true;damage.enabled=false;Probe::s_Active=&p;downlink.Downlink=true;downlink.Cursor=1;}
|
||||
void observed(){p.Observed(&unit,20);}
|
||||
void accepted(){uplink.Cursor=2;p.Accepted(&report,22);}
|
||||
void hold(){combat.Held=true;lead.Waypoint=false;p.Delivered(&order,&downlink,true,30);}
|
||||
void resume(){order.Hold=false;order.CommandId=2;combat.LastCommandId=2;combat.Held=false;lead.Waypoint=true;p.Delivered(&order,&downlink,false,60010);}
|
||||
};
|
||||
int main(){
|
||||
// Observation snapshots the real pending report only for the existing prisoner and original observer.
|
||||
Setup s;s.observed();assert(s.p.m_Reports.Count()==1);auto snapshot=s.p.m_Reports[0]->Report;
|
||||
assert(snapshot!=&s.report&&snapshot->Target==&s.player);s.report.MessageId=9;assert(snapshot->MessageId==1);s.report.MessageId=1;
|
||||
s.p.Accepted(&s.report,21);assert(!s.p.m_Reports[0]->Accepted); // radio still in transit
|
||||
s.accepted();assert(s.p.m_Reports[0]->Accepted);s.hold();assert(s.p.m_Held&&!s.p.m_Failed&&s.p.m_RouteIndex==2&&s.p.m_HeldAt==30);
|
||||
s.p.Delivered(&s.order,&s.downlink,false,40);assert(s.p.m_HeldAt==30); // repeated HOLD cannot replace first measurement
|
||||
s.resume();assert(s.p.m_Resumed&&!s.p.m_Failed&&s.p.m_ResumedAt==60010);
|
||||
s.p.Stop();assert(s.damage.enabled&&s.damage.writes==1&&Probe::s_Active==nullptr);s.p.Stop();assert(s.damage.writes==1);
|
||||
int checks=s.p.checks;s.p.Delivered(&s.order,&s.downlink,false,61000);s.p.Observed(&s.unit,61000);assert(s.p.checks==checks&&s.p.m_Reports.Count()==1);
|
||||
for(int exclusion=0;exclusion<5;exclusion++){
|
||||
Setup x;
|
||||
if(exclusion==0)x.report.Target=&x.stranger;
|
||||
if(exclusion==1)x.unit.Uplink=nullptr;
|
||||
if(exclusion==2)x.utility.observer=nullptr;
|
||||
if(exclusion==3)x.utility.observer=&x.stranger;
|
||||
if(exclusion==4)x.p.m_Reports.resize(64,nullptr);
|
||||
int before=x.p.m_Reports.Count();x.observed();assert(x.p.m_Reports.Count()==before);
|
||||
}
|
||||
// Failed or unstarted uplinks cannot establish delivered evidence.
|
||||
for(int invalid=0;invalid<3;invalid++){
|
||||
Setup x;x.observed();x.uplink.Cursor=2;
|
||||
if(invalid==0)x.uplink.Failed=true;
|
||||
if(invalid==1)x.uplink.StartedAt=-1;
|
||||
if(invalid==2)x.uplink.Cursor=1;
|
||||
x.p.Accepted(&x.report,30);assert(!x.p.m_Reports[0]->Accepted);x.hold();assert(x.p.m_Failed&&!x.p.m_Held&&x.damage.enabled);
|
||||
}
|
||||
// Rejected production order is ignored; completed physical downlink requires exact cursor/direction.
|
||||
for(int ignored=0;ignored<3;ignored++){
|
||||
Setup x;x.observed();x.accepted();
|
||||
if(ignored==0)x.order.Group=2;
|
||||
if(ignored==1)x.combat.LastCommandId=2;
|
||||
if(ignored==2)x.downlink.Failed=true;
|
||||
x.hold();assert(!x.p.m_Held&&!x.p.m_Failed&&x.p.checks==0);
|
||||
}
|
||||
for(int invalid=0;invalid<3;invalid++){
|
||||
Setup x;x.observed();x.accepted();
|
||||
if(invalid==0)x.downlink.Downlink=false;
|
||||
if(invalid==1)x.downlink.Cursor=0;
|
||||
if(invalid==2)x.downlink.StartedAt=-1;
|
||||
x.hold();assert(x.p.m_Failed&&!x.p.m_Held);
|
||||
}
|
||||
// A successful command cannot waive waypoint ownership, preserved route index or quiet period.
|
||||
for(int invalid=0;invalid<4;invalid++){
|
||||
Setup x;x.observed();x.accepted();x.hold();x.order.Hold=false;x.order.CommandId=2;x.combat.LastCommandId=2;
|
||||
x.combat.Held=false;x.lead.Waypoint=true;bool hadWaypoint=false;float now=60010;
|
||||
if(invalid==0)x.lead.RouteIndex=3;
|
||||
if(invalid==1)hadWaypoint=true;
|
||||
if(invalid==2)x.lead.Waypoint=false;
|
||||
if(invalid==3)now=60009;
|
||||
x.p.Delivered(&x.order,&x.downlink,hadWaypoint,now);assert(x.p.m_Failed&&!x.p.m_Resumed&&x.damage.enabled);
|
||||
}
|
||||
Setup previouslyDisabled;previouslyDisabled.p.m_DamageWasEnabled=false;previouslyDisabled.p.Stop();assert(!previouslyDisabled.damage.enabled&&previouslyDisabled.damage.writes==1);
|
||||
}
|
||||
''')
|
||||
assert s.startswith('#ifdef WORKBENCH')
|
||||
for signature,base,hook in [('protected override void Observe(', 'super.Observe(unit, now)', 'proof.Observed'),('protected override void Accept(', 'super.Accept(report, now)', 'proof.Accepted'),('protected override void DeliverConvoyOrder(', 'super.DeliverConvoyOrder(unit, now)', 'proof.Delivered')]:
|
||||
method=body(P,signature);assert method.index(base)<method.index(hook)
|
||||
assert 'entry.Report = unit.PendingReport.Snapshot()' in s
|
||||
for forbidden in ('ApplyCombatOrder(', 'PendingReport = new', 'new ERR_HQRoute', 'SetAmmoCount(', 'TrySpawnPrefabToStorage(', 'SetOrigin('):assert forbidden not in s
|
||||
convoy=(ROOT/'addons/chat/Scripts/Game/ERR/ERR_NativeConvoyProbe.c').read_text()
|
||||
assert convoy.index('all six native vehicles drove at least 20m')<convoy.index('m_Cover = new')
|
||||
assert 'if (m_Step == 4) return m_Cover.Tick(hq, now);' in convoy
|
||||
assert 'void Stop() { if (m_Cover) m_Cover.Stop(); }' in convoy
|
||||
smoke=(ROOT/'addons/chat/Scripts/Game/ERR/ERR_NativeSmoke.c').read_text();assert 'if (m_ERRConvoyProbe) m_ERRConvoyProbe.Stop();' in smoke
|
||||
runner=ROOT/'tools/headless_workbench.py'
|
||||
for variant in ('victory','hq-body'):
|
||||
result=subprocess.run([sys.executable,str(runner),'--smoke',variant,'--convoy-cover','--project','chat','--rounds','1','--timeout','900','--compat','/tmp/erri-unused-cover-compat','--profile','/tmp/erri-unused-cover-profile'],capture_output=True,text=True)
|
||||
assert result.returncode==2 and '--convoy-cover requires --smoke convoy' in result.stderr,result.stderr
|
||||
# Cover receives its own full budget after ordinary convoy proof; default budget stays bounded.
|
||||
assert 'if (m_ERRTestConvoy) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 540000;' in smoke
|
||||
assert 'if (ERR_NativeConvoyCoverProbe.Enabled()) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 840000;' in smoke
|
||||
result=subprocess.run([sys.executable,str(runner),'--smoke','convoy','--convoy-cover','--project','chat','--rounds','1','--timeout','899','--compat','/tmp/erri-unused-cover-compat','--profile','/tmp/erri-unused-cover-profile'],capture_output=True,text=True)
|
||||
assert result.returncode==2 and '--convoy-cover requires timeout at least900 seconds' in result.stderr,result.stderr
|
||||
assert "if a.convoy_cover: command += ['-errConvoyCover', '1']" in runner.read_text()
|
||||
print('PASS: actual convoy cover observation/snapshot/uplink/HOLD/RESUME/cleanup transitions; native boundaries mocked, non-convoy runner opt-in refused')
|
||||
@@ -0,0 +1,55 @@
|
||||
"""Actual WORKBENCH-only native-context fallback; no production admission bypass."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
from policy_harness import ROOT,body,run
|
||||
base=Path(os.environ.get('ERR_VEHICLE_DIAGNOSTICS_ROOT',str(ROOT)))
|
||||
source=base/'addons/core/Scripts/Game/ERR/ERR_NativeVehicleSecurityProbe.c'
|
||||
wrapped=body(source,'protected bool DriverAvailable(').replace('protected ','').replace('m_DriverAction.','m_DriverAction->')
|
||||
run('''#include <cassert>
|
||||
struct Action {bool allowed=false,fallback=true;int context=-1,checks=0,diagnostics=0,bindings=0;bool CanBePerformedScript(int){checks++;return allowed;}void ERR_ProbeRejectedEntry(int){diagnostics++;}int ERR_ProbeResolvedDoor(int){return context;}bool ERR_ProbeDeclaredDriverDoor(int){bindings++;return fallback;}};
|
||||
struct Probe{Action* m_DriverAction;int m_Player=7;
|
||||
'''+wrapped+'''
|
||||
};
|
||||
int main(){Action a;Probe p{&a};a.allowed=true;assert(p.DriverAvailable()&&a.checks==1&&a.diagnostics==0&&a.bindings==0);
|
||||
a.allowed=false;a.context=2;assert(!p.DriverAvailable()&&a.diagnostics==1&&a.bindings==0); // real selected context cannot be overridden
|
||||
a.context=-1;assert(p.DriverAvailable()&&a.diagnostics==2&&a.bindings==1);
|
||||
a.fallback=false;assert(!p.DriverAvailable()&&a.diagnostics==3&&a.bindings==2);
|
||||
}
|
||||
''')
|
||||
method=body(source,'bool ERR_ProbeDeclaredDriverDoor(')
|
||||
method=method.replace('BaseCompartmentSlot seat','BaseCompartmentSlot* seat').replace('array<int> doors = {};','Array doors;').replace('seat.','seat->').replace('m_CompartmentManager.','m_CompartmentManager->')
|
||||
method=method.replace('SCR_CompartmentAccessComponent.','SCR_CompartmentAccessComponent::').replace('ECompartmentType.PILOT','PILOT')
|
||||
run('''#include <cassert>
|
||||
#include <vector>
|
||||
using IEntity=int;enum {PILOT=1,CARGO=2};
|
||||
struct Array:std::vector<int>{int Count(){return size();}};
|
||||
struct BaseCompartmentSlot{int type=PILOT;std::vector<int> doors={3};void GetAvailableDoorIndices(Array& a){a.assign(doors.begin(),doors.end());}};
|
||||
struct Manager {int valid=3;bool GetDoorInfo(int i){return i==valid;}};
|
||||
struct SCR_CompartmentAccessComponent{static int GetCompartmentType(BaseCompartmentSlot* s){return s->type;}};
|
||||
void PrintFormat(const char*,int,int,bool){}
|
||||
struct Action {Manager* m_CompartmentManager;BaseCompartmentSlot* seat;int context=-1,stored=-1,checks=0,sets=0,diagnostics=0;bool nativeAllowed=true;
|
||||
int GetRelevantDoorIndex(IEntity){return context;}BaseCompartmentSlot* GetCompartmentSlot(){return seat;}
|
||||
int GetCompartmentDoorInfoIndex(){return stored;}void SetCompartmentDoorInfoIndex(int i){stored=i;sets++;}
|
||||
bool CanBePerformedScript(IEntity){checks++;return nativeAllowed&&stored==3;}
|
||||
void ERR_ProbeRejectedEntry(IEntity){diagnostics++;}
|
||||
'''+method+'''
|
||||
};
|
||||
int main(){Manager m;BaseCompartmentSlot s;Action a;a.m_CompartmentManager=&m;a.seat=&s;
|
||||
assert(a.ERR_ProbeDeclaredDriverDoor(7)&&a.stored==-1&&a.checks==1&&a.sets==2); // derived door 3, never hardcoded driver 0
|
||||
a.nativeAllowed=false;assert(!a.ERR_ProbeDeclaredDriverDoor(7)&&a.stored==-1&&a.diagnostics==1&&a.sets==4);
|
||||
a.context=2;assert(!a.ERR_ProbeDeclaredDriverDoor(7)&&a.sets==4);a.context=-1;
|
||||
s.type=CARGO;assert(!a.ERR_ProbeDeclaredDriverDoor(7)&&a.sets==4);s.type=PILOT;
|
||||
s.doors={};assert(!a.ERR_ProbeDeclaredDriverDoor(7));s.doors={3,4};assert(!a.ERR_ProbeDeclaredDriverDoor(7));
|
||||
s.doors={4};assert(!a.ERR_ProbeDeclaredDriverDoor(7)&&a.sets==4&&a.checks==2);
|
||||
a.seat=nullptr;assert(!a.ERR_ProbeDeclaredDriverDoor(7));a.seat=&s;a.m_CompartmentManager=nullptr;assert(!a.ERR_ProbeDeclaredDriverDoor(7));
|
||||
}
|
||||
''')
|
||||
diagnostics=body(source,'void ERR_ProbeRejectedEntry(')
|
||||
for forbidden in ('SetCompartmentDoorInfoIndex(', 'SetAffiliatedFaction(', 'ClearAffiliatedFaction(', 'GetInVehicle(', 'EnableDamageHandling(', 'TryBreach('):assert forbidden not in diagnostics
|
||||
assert 'GetCannotPerformReason()' in diagnostics and 'GetRelevantDoorIndex(user)' in diagnostics
|
||||
assert 'CanGetInVehicleViaDoor' in diagnostics and 'GetStoredResources' in diagnostics
|
||||
assert diagnostics.index('door >= 0 && m_CompartmentManager.GetDoorInfo(door)') < diagnostics.index('m_CompartmentManager.GetDoorUser(door)')
|
||||
assert 'if (index < 0 || !m_CompartmentManager.GetDoorInfo(index))' in diagnostics
|
||||
assert 'missing or invalid context; no indexed native query issued' in diagnostics
|
||||
assert 'modded class ERR_SecureGetInAction' in source.read_text() and source.read_text().startswith('#ifdef WORKBENCH')
|
||||
print('PASS: actual diagnostic fallback runs only on failed missing-context call, derives unique native pilot door, preserves stock eligibility and restores prior index on success/failure; diagnostics mutate no game state.')
|
||||
@@ -0,0 +1,175 @@
|
||||
"""Execute staged/production security methods; native held UI/replication pending."""
|
||||
import os,re
|
||||
from pathlib import Path
|
||||
from policy_harness import ROOT,body,run
|
||||
base=Path(os.environ.get('ERR_VEHICLE_SECURITY_ROOT',str(ROOT)))
|
||||
component=base/'addons/core/Scripts/Game/ERR/ERR_VehicleSecurityComponent.c'
|
||||
actions=base/'addons/core/Scripts/Game/ERR/ERR_VehicleSecurityActions.c'
|
||||
|
||||
def translate(value):
|
||||
value=value.replace('out string reason','string& reason').replace('string.Empty','string{}').replace('override ','')
|
||||
for t in ('ERR_GameMode','Physics','ERR_VehicleSecurityComponent'):
|
||||
value=re.sub(r'\b'+t+r' (\w+)',t+r'* \1',value)
|
||||
for t in ('ERR_GameMode','ERR_Actors','Replication','CompartmentAccessComponent','ERR_VehicleSecurityComponent','vector','Vehicle','ERR_Phase'):
|
||||
value=value.replace(t+'.',t+'::')
|
||||
for var in ('mode','physics','user','entity','security'):
|
||||
value=value.replace(var+'.',var+'->')
|
||||
value=value.replace('GetOwner().','GetOwner()->').replace('mode->Players().','mode->Players()->')
|
||||
value=value.replace('super.','Base::').replace('null','nullptr')
|
||||
value=re.sub(r'(?<![\w.])(\d+\.\d+)(?![\w.])',r'\1f',value)
|
||||
return value
|
||||
# Execute the actual preparation hook separately from breach eligibility.
|
||||
configuration=translate(body(component,'bool Configure('))
|
||||
run("""#include <cassert>
|
||||
struct Entity {bool vehicle=true,alive=true;};using IEntity=Entity*;
|
||||
struct Vehicle {static IEntity Cast(IEntity e){return e&&e->vehicle?e:nullptr;}};
|
||||
struct ERR_Phase {static const int PREPARING=1,ACTIVE=2;};
|
||||
struct ERR_GameMode {int phase=ERR_Phase::PREPARING;static ERR_GameMode* current;
|
||||
static ERR_GameMode* Get(){return current;}int Phase(){return phase;}};
|
||||
ERR_GameMode* ERR_GameMode::current=nullptr;
|
||||
struct ERR_Actors {static IEntity observed;static bool Alive(IEntity e){observed=e;return e&&e->alive;}};
|
||||
IEntity ERR_Actors::observed=nullptr;
|
||||
struct Replication {static bool server;static int bumps;static bool IsServer(){return server;}static void BumpMe(){bumps++;}};
|
||||
bool Replication::server=true;int Replication::bumps=0;
|
||||
struct Security {IEntity owner=nullptr;bool m_Configured=false,m_Breached=false;IEntity GetOwner(){return owner;}
|
||||
"""+configuration+"""
|
||||
};
|
||||
int main(){
|
||||
// Reject every invalid preparation boundary without replication or state changes.
|
||||
for(int variant=0;variant<6;variant++){
|
||||
ERR_GameMode mode;ERR_GameMode::current=&mode;Entity vehicle;Security s;s.owner=&vehicle;
|
||||
Replication::server=true;Replication::bumps=0;
|
||||
if(variant==0)Replication::server=false;
|
||||
if(variant==1)ERR_GameMode::current=nullptr;
|
||||
if(variant==2)mode.phase=ERR_Phase::ACTIVE;
|
||||
if(variant==3)s.owner=nullptr;
|
||||
if(variant==4)vehicle.vehicle=false;
|
||||
if(variant==5)vehicle.alive=false;
|
||||
assert(!s.Configure()&&!s.m_Configured&&!s.m_Breached&&Replication::bumps==0);
|
||||
}
|
||||
ERR_GameMode mode;ERR_GameMode::current=&mode;Replication::server=true;
|
||||
Entity vehicle,unrelated;unrelated.alive=false;Security s;s.owner=&vehicle;
|
||||
assert(s.Configure()&&s.m_Configured&&!s.m_Breached&&Replication::bumps==1);
|
||||
assert(ERR_Actors::observed==&vehicle); // Liveness is checked on this component's exact vehicle.
|
||||
assert(s.Configure()&&Replication::bumps==1&&!s.m_Breached);
|
||||
s.m_Breached=true;assert(s.Configure()&&s.m_Breached&&s.m_Configured&&Replication::bumps==1);
|
||||
// Invalid repeat calls do not relock or reset configured state.
|
||||
vehicle.alive=false;assert(!s.Configure()&&s.m_Breached&&s.m_Configured&&Replication::bumps==1);vehicle.alive=true;
|
||||
mode.phase=ERR_Phase::ACTIVE;assert(!s.Configure()&&s.m_Breached&&s.m_Configured&&Replication::bumps==1);
|
||||
mode.phase=ERR_Phase::PREPARING;Replication::server=false;
|
||||
assert(!s.Configure()&&s.m_Breached&&s.m_Configured&&Replication::bumps==1);
|
||||
}
|
||||
""")
|
||||
|
||||
methods='\n'.join(translate(body(component,s)) for s in ('bool Locked(', 'static bool Motion(', 'static bool Reach(', 'bool CanBreach(', 'bool TryBreach('))
|
||||
run('''#include <cassert>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
using string=std::string;
|
||||
struct vector {float x=0; float Length(){return x;} static float Distance(vector a,vector b){return std::abs(a.x-b.x);} };
|
||||
struct Physics {vector speed,angular; vector GetVelocity(){return speed;} vector GetAngularVelocity(){return angular;} };
|
||||
struct Entity {bool player=true,awake=true,alive=true,aboard=false; vector pos; Physics* physics=nullptr; vector GetOrigin(){return pos;} Physics* GetPhysics(){return physics;} vector CoordToParent(vector){return pos;} };
|
||||
using IEntity=Entity*;
|
||||
struct Roster {IEntity admitted=nullptr; bool CurrentActor(IEntity e){return e&&e==admitted;} };
|
||||
struct ERR_GameMode {bool active=true;Roster roster;bool rosterExists=true; static ERR_GameMode* current;static ERR_GameMode* Get(){return current;}bool Active(){return active;} Roster* Players(){return rosterExists?&roster:nullptr;} };
|
||||
ERR_GameMode* ERR_GameMode::current=nullptr;
|
||||
struct ERR_Actors {static bool Player(IEntity e){return e&&e->player;}static bool CanFight(IEntity e){return e&&e->alive&&e->awake;}static bool Alive(IEntity e){return e&&e->alive;} };
|
||||
struct Replication {static bool server;static int bumps; static bool IsServer(){return server;}static void BumpMe(){bumps++;} };bool Replication::server=true;int Replication::bumps=0;
|
||||
struct CompartmentAccessComponent {static bool GetVehicleIn(IEntity e){return e&&e->aboard;} };
|
||||
struct Security {bool m_Configured=true,m_Breached=false,empty=true,clear=true;IEntity owner;vector m_LockAnchor; IEntity GetOwner(){return owner;}bool EmptyVehicle(){return empty;}bool ClearReach(IEntity,vector){return clear;}
|
||||
'''+methods+'''
|
||||
};
|
||||
int main(){
|
||||
Physics physics;Entity vehicle,player,other;vehicle.physics=&physics;
|
||||
ERR_GameMode mode; ERR_GameMode::current=&mode;mode.roster.admitted=&player;
|
||||
Security s;s.owner=&vehicle;string reason;
|
||||
assert(s.CanBreach(&player,reason)&&reason.empty());
|
||||
for(float bad : {-1.f,0.251f}) assert(!Security::Motion(bad,0));
|
||||
assert(Security::Motion(0.25,0.1)&&!Security::Motion(0,0.101));
|
||||
assert(Security::Reach(2)&&!Security::Reach(-1)&&!Security::Reach(2.001));
|
||||
s.m_Configured=false;assert(!s.TryBreach(&player));s.m_Configured=true;
|
||||
mode.active=false;assert(!s.TryBreach(&player));mode.active=true;
|
||||
player.player=false;assert(!s.TryBreach(&player));player.player=true;
|
||||
player.awake=false;assert(!s.TryBreach(&player));player.awake=true;
|
||||
player.alive=false;assert(!s.TryBreach(&player));player.alive=true;
|
||||
player.aboard=true;assert(!s.TryBreach(&player));player.aboard=false;
|
||||
vehicle.alive=false;assert(!s.TryBreach(&player));vehicle.alive=true;
|
||||
vehicle.physics=nullptr;assert(!s.TryBreach(&player));vehicle.physics=&physics;
|
||||
physics.speed.x=0.26;assert(!s.TryBreach(&player));physics.speed.x=0;
|
||||
physics.angular.x=0.11;assert(!s.TryBreach(&player));physics.angular.x=0;
|
||||
s.empty=false;assert(!s.TryBreach(&player));s.empty=true;
|
||||
player.pos.x=2.01;assert(!s.TryBreach(&player));player.pos.x=2;
|
||||
s.clear=false;assert(!s.CanBreach(&player,reason)&&reason.find("obstructed")!=string::npos);assert(!s.TryBreach(&player));s.clear=true;
|
||||
// Body replacement, absent roster and direct replica execution cannot unlock.
|
||||
assert(!s.TryBreach(&other));mode.roster.admitted=nullptr;assert(!s.TryBreach(&player));mode.roster.admitted=&player;
|
||||
mode.rosterExists=false;assert(!s.TryBreach(&player));mode.rosterExists=true;
|
||||
Replication::server=false;assert(!s.TryBreach(&player));Replication::server=true;
|
||||
ERR_GameMode::current=nullptr;assert(!s.TryBreach(&player));ERR_GameMode::current=&mode;
|
||||
assert(Replication::bumps==0&&s.Locked());
|
||||
assert(s.TryBreach(&player)&&!s.Locked()&&Replication::bumps==1);
|
||||
assert(!s.TryBreach(&player)&&!s.TryBreach(&other)&&Replication::bumps==1);
|
||||
}
|
||||
''')
|
||||
|
||||
class Section:
|
||||
def __init__(self,name,next_name=None):
|
||||
source=actions.read_text();self.source=source[source.index('class '+name):]
|
||||
if next_name:self.source=self.source[:self.source.index('class '+next_name)]
|
||||
def read_text(self):return self.source
|
||||
for name,next_name in [('ERR_SecureGetInAction','ERR_SecureOpenVehicleDoorAction'),('ERR_SecureOpenVehicleDoorAction',None)]:
|
||||
section=Section(name,next_name)
|
||||
methods='\n'.join(translate(body(section,s)) for s in ('override bool CanBePerformedScript(', 'override void PerformAction('))
|
||||
run('''#include <cassert>
|
||||
#include <string>
|
||||
using IEntity=int;
|
||||
using string=std::string;
|
||||
struct ERR_VehicleSecurityComponent {bool locked=true;static ERR_VehicleSecurityComponent* security;static ERR_VehicleSecurityComponent* Find(IEntity){return security;}bool Locked(){return locked;}};
|
||||
ERR_VehicleSecurityComponent* ERR_VehicleSecurityComponent::security=nullptr;
|
||||
struct Base {bool allowed=true;int calls=0;IEntity GetOwner(){return 42;}void SetCannotPerformReason(std::string){}bool CanBePerformedScript(IEntity){return allowed;}void PerformAction(IEntity,IEntity){calls++;}};
|
||||
struct Action:Base {
|
||||
'''+methods+'''
|
||||
};
|
||||
int main(){Action a;ERR_VehicleSecurityComponent s;ERR_VehicleSecurityComponent::security=&s;
|
||||
a.PerformAction(42,7);assert(a.calls==0);s.locked=false;a.allowed=false;a.PerformAction(42,7);assert(a.calls==0);
|
||||
a.allowed=true;a.PerformAction(43,7);assert(a.calls==0);a.PerformAction(42,7);assert(a.calls==1);
|
||||
s.locked=true;a.PerformAction(42,7);assert(a.calls==1);
|
||||
ERR_VehicleSecurityComponent::security=nullptr;a.PerformAction(42,7);assert(a.calls==2); // unrelated owner delegates native policy
|
||||
}
|
||||
''')
|
||||
|
||||
|
||||
# The held action invokes only the owning security component on the server; its
|
||||
# TryBreach path above performs fresh checks rather than trusting availability.
|
||||
execution=translate(body(Section('ERR_ForceVehicleLockAction','ERR_SecureGetInAction'),'override void PerformAction('))
|
||||
run("""#include <cassert>
|
||||
using IEntity=int;
|
||||
struct Replication {static bool server;static bool IsServer(){return server;}};bool Replication::server=false;
|
||||
struct ERR_VehicleSecurityComponent {int calls=0,last=0;static ERR_VehicleSecurityComponent* security;static ERR_VehicleSecurityComponent* Find(IEntity){return security;}void TryBreach(IEntity user){calls++;last=user;}};
|
||||
ERR_VehicleSecurityComponent* ERR_VehicleSecurityComponent::security=nullptr;
|
||||
struct Action {IEntity GetOwner(){return 42;}
|
||||
"""+execution+"""
|
||||
};
|
||||
int main(){Action a;ERR_VehicleSecurityComponent s;ERR_VehicleSecurityComponent::security=&s;
|
||||
a.PerformAction(42,7);assert(s.calls==0);Replication::server=true;
|
||||
a.PerformAction(43,7);assert(s.calls==0);a.PerformAction(42,7);assert(s.calls==1&&s.last==7);
|
||||
ERR_VehicleSecurityComponent::security=nullptr;a.PerformAction(42,7);assert(s.calls==1);
|
||||
}
|
||||
""")
|
||||
|
||||
# Exact resource overrides cover both front seats, three separately attached rear
|
||||
# seats and all four door actions without replacing physical vehicle resources.
|
||||
prefabs=base/'addons/core/Prefabs/ERR/Vehicles'
|
||||
uaz=(prefabs/'SupplyUAZ.et').read_text();rear=(prefabs/'SupplyUAZRearSeats.et').read_text()
|
||||
assert '"{259EE7B78C51B624}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469.et"' in uaz
|
||||
assert '"{780AAF0A51AEE61A}Prefabs/Vehicles/Wheeled/UAZ469/VehParts/Seats/UAZ469_seats_rear.et"' in rear
|
||||
assert uaz.count('OpenDoorAction ERR_SecureOpenVehicleDoorAction')==4
|
||||
assert uaz.count('CompartmentAction ERR_SecureGetInAction')==2
|
||||
assert rear.count('CompartmentAction ERR_SecureGetInAction')==3
|
||||
for slot in ['Passenger_l02','Passenger_r02','Passenger_m02']:assert 'CargoCompartmentSlot '+slot in rear
|
||||
assert 'SupplyUAZRearSeats.et' in uaz and 'Duration 4' in uaz and 'VisibilityRange 2' in uaz
|
||||
assert uaz.count('-0.95 1.05 0.3')==2 # reach anchor matches visible held-action context
|
||||
for signature,result in [('override bool HasLocalEffectOnlyScript(',False),('override bool CheckOnServerFirstScript(',True),('override bool CanBroadcastScript(',False)]:
|
||||
assert 'return '+str(result).lower()+';' in body(Section('ERR_ForceVehicleLockAction','ERR_SecureGetInAction'),signature)
|
||||
assert '[RplProp()] protected bool m_Breached;' in component.read_text()
|
||||
assert 'modded class' not in actions.read_text()+component.read_text()
|
||||
print('PASS: actual preparation Configure guards/idempotence/breach preservation, security eligibility/final current-body server mutation and all scoped action guards; all five seat/four door resource overrides. Native hold, prefab merge, replication and boarding remain unverified.')
|
||||
@@ -0,0 +1,99 @@
|
||||
"""Native fixture cleanup and real SiteService admission wrapper, without engine claims."""
|
||||
import os,re
|
||||
from pathlib import Path
|
||||
from policy_harness import ROOT,body,run
|
||||
base=Path(os.environ.get('ERR_VEHICLE_NATIVE_ROOT',str(ROOT)))
|
||||
source=base/'addons/core/Scripts/Game/ERR/ERR_NativeVehicleSecurityProbe.c'
|
||||
stop=body(source,'void Stop(').replace('null','nullptr')
|
||||
stop=stop.replace('ChimeraCharacter character = ChimeraCharacter.Cast(m_Player);','ChimeraCharacter* character = m_Player;')
|
||||
for name in ('m_Damage','m_Mode','character'):stop=stop.replace(name+'.',name+'->')
|
||||
stop=stop.replace('m_Mode->Players().','m_Mode->Players()->').replace('ERR_Actors.','ERR_Actors::').replace('CompartmentAccessComponent.','CompartmentAccessComponent::')
|
||||
run('''#include <cassert>
|
||||
struct Damage {bool enabled=true;int changes=0;void EnableDamageHandling(bool b){enabled=b;changes++;}};
|
||||
struct ChimeraCharacter {bool awake=true,aboard=false;int teleports=0;void Teleport(int){teleports++;}};
|
||||
struct Roster {ChimeraCharacter* current=nullptr;bool CurrentActor(ChimeraCharacter* e){return current==e;}};
|
||||
struct Mode {Roster roster;bool hasRoster=true;Roster* Players(){return hasRoster?&roster:nullptr;}};
|
||||
struct ERR_Actors{static bool CanFight(ChimeraCharacter* c){return c&&c->awake;}};
|
||||
struct CompartmentAccessComponent {static bool GetVehicleIn(ChimeraCharacter* c){return c&&c->aboard;}};
|
||||
struct Probe {bool m_Stopped=false,m_Moved=true,m_DamageChanged=true,m_PreviousDamageEnabled=true;Damage* m_Damage;Mode* m_Mode;ChimeraCharacter* m_Player;int m_Original=0;
|
||||
'''+stop+'''
|
||||
};
|
||||
int main(){
|
||||
Damage damage;Mode mode;ChimeraCharacter oldBody,replacement;mode.roster.current=&oldBody;
|
||||
Probe p;p.m_Damage=&damage;p.m_Mode=&mode;p.m_Player=&oldBody;
|
||||
damage.enabled=false;p.Stop();assert(damage.enabled&&damage.changes==1&&oldBody.teleports==1);
|
||||
p.Stop();assert(damage.changes==1&&oldBody.teleports==1); // idempotent
|
||||
p.m_Stopped=false;p.m_Moved=true;p.m_Damage=&damage;p.m_DamageChanged=true;p.m_PreviousDamageEnabled=false;mode.roster.current=&replacement;
|
||||
p.Stop();assert(!damage.enabled&&damage.changes==2&&replacement.teleports==0&&oldBody.teleports==1); // restore exact old damage state, no stale-body teleport
|
||||
p.m_Stopped=false;p.m_Damage=&damage;p.m_DamageChanged=false; // rejected before protection applied
|
||||
p.Stop();assert(damage.changes==2);
|
||||
p.m_Stopped=false;p.m_Damage=&damage;p.m_DamageChanged=true;p.m_PreviousDamageEnabled=true;mode.roster.current=&oldBody;oldBody.awake=false;
|
||||
p.Stop();assert(damage.enabled&&damage.changes==3&&oldBody.teleports==1);
|
||||
p.m_Stopped=false;p.m_Damage=&damage;p.m_DamageChanged=true;oldBody.awake=true;oldBody.aboard=true;
|
||||
p.Stop();assert(damage.changes==4&&oldBody.teleports==1);
|
||||
p.m_Stopped=false;p.m_Damage=&damage;p.m_DamageChanged=true;mode.hasRoster=false;
|
||||
p.Stop();assert(damage.changes==5&&oldBody.teleports==1);
|
||||
}
|
||||
''')
|
||||
text=source.read_text();tick=body(source,'int Tick(');inspect=body(source,'protected bool Inspect(')
|
||||
assert tick.index('CurrentActor(m_Player)') < tick.index('m_Damage.EnableDamageHandling(false)') < tick.index('Teleport(transform)')
|
||||
assert 'ERR_Actors.Spawn(' not in text and 'GetInVehicle(' not in text and 'SetTotalFuel' not in text
|
||||
assert 'seats.Count() == 5' in inspect and 'slot.GetGetInAction()' in inspect
|
||||
assert 'front == 2 && rear == 3' in inspect and 'doors.Count() == 4' in inspect
|
||||
assert 'ERR_VehicleSecurityComponent.Find(action.GetOwner()) == m_Security' in inspect
|
||||
assert '!action.CanBePerformedScript(m_Player)' in inspect and 'action.PerformAction(action.GetOwner(), m_Player);' in inspect
|
||||
assert '!door.CanBePerformedScript(m_Player)' in inspect and 'door.PerformAction(door.GetOwner(), m_Player);' in inspect
|
||||
assert 'm_Security.CanBreach(m_Player, reason)' in tick and '!m_Security.TryBreach(m_Player)' in tick
|
||||
assert 'DriverAvailable()' in tick
|
||||
assert 'm_DriverAction.CanBePerformedScript(m_Player)' in body(source,'protected bool DriverAvailable(')
|
||||
transport=(base/'addons/core/Scripts/Game/ERR/ERR_NativeTransportProbe.c').read_text()
|
||||
smoke=(base/'addons/core/Scripts/Game/ERR/ERR_NativeBaseSmoke.c').read_text()
|
||||
assert 'new ERR_NativeTransportProbe(this, m_ERRBasePlayer)' in smoke and 'if (m_ERRTransportProbe) m_ERRTransportProbe.Stop();' in smoke
|
||||
assert transport.index('m_SecurityProbe.Tick(now)') < transport.index('IEntity driver = Character(0)')
|
||||
assert 'void Stop() { if (m_SecurityProbe) m_SecurityProbe.Stop(); }' in transport
|
||||
print('PASS: actual native-prelude cleanup restores only original changed damage, refuses stale/incapacitated/aboard teleport; bounded five-seat/four-door fixture wiring checked. Native execution remains pending.')
|
||||
|
||||
# Existing transport tests did not extract CreateTransport. Test its real new
|
||||
# Configure hook with native construction/FindComponent calls represented by doubles.
|
||||
site=ROOT/'addons/core/Scripts/Game/ERR/ERR_SiteService.c'
|
||||
create=body(site,'protected bool CreateTransport(').replace('protected ','').replace('null','nullptr')
|
||||
for name in ('ERR_SiteRuntime','ERR_SiteTransport','CarControllerComponent','ERR_VehicleSecurityComponent'):
|
||||
create=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',create)
|
||||
for name in ('site','spec','controller','security'):create=create.replace(name+'.',name+'->')
|
||||
create=create.replace('site->Definition.','site->Definition->')
|
||||
create=create.replace('ERR_Actors.','ERR_Actors::').replace('ERR_VehicleSecurityComponent.','ERR_VehicleSecurityComponent::')
|
||||
create=create.replace('Vehicle.Cast(entity)','CastVehicle(entity)').replace('CarControllerComponent.Cast(site->Transport.FindComponent(CarControllerComponent))','FindController(site->Transport)')
|
||||
run('''#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
using string=std::string;using vector=int;
|
||||
int Vector(int x,int,int){return x;}
|
||||
struct CarControllerComponent {bool parked=false;void SetPersistentHandBrake(bool b){parked=b;}};
|
||||
struct Entity;
|
||||
struct ERR_VehicleSecurityComponent {CarControllerComponent* controller;bool result=true;int calls=0;bool Configure(){assert(controller&&controller->parked);calls++;return result;}static ERR_VehicleSecurityComponent* Find(Entity* e);};
|
||||
struct Entity {bool vehicle=true;CarControllerComponent* controller=nullptr;ERR_VehicleSecurityComponent* security=nullptr;};using IEntity=Entity*;
|
||||
ERR_VehicleSecurityComponent* ERR_VehicleSecurityComponent::Find(Entity* e){return e->security;}
|
||||
IEntity CastVehicle(IEntity e){return e->vehicle?e:nullptr;}
|
||||
CarControllerComponent* FindController(IEntity e){return e->controller;}
|
||||
struct ERR_SiteTransport {int Offset=0,ClearanceRadius=4,MaximumTerrainRange=1,Prefab=123,Heading=180;};
|
||||
struct Definition {ERR_SiteTransport* Transport=nullptr;string Id="supply";};
|
||||
struct ERR_SiteRuntime {struct Definition* Definition;IEntity Transport=nullptr;};
|
||||
struct ERR_Actors {static IEntity spawned;static int calls;static int Ground(int p){return p;}static IEntity Spawn(int,int,int){calls++;return spawned;}};IEntity ERR_Actors::spawned=nullptr;int ERR_Actors::calls=0;
|
||||
struct Array:std::vector<IEntity>{void Insert(IEntity e){push_back(e);}};
|
||||
struct Service{bool clear=true;string failure;Array m_Entities;bool Fail(string s){failure=s;return false;}bool TransportClearance(int,int,int){return clear;}
|
||||
'''+create+'''
|
||||
};
|
||||
int main(){Service service;Definition def;ERR_SiteRuntime site{&def};ERR_SiteTransport spec;
|
||||
assert(service.CreateTransport(&site,0)&&ERR_Actors::calls==0);def.Transport=&spec;
|
||||
service.clear=false;assert(!service.CreateTransport(&site,0)&&ERR_Actors::calls==0);service.clear=true;
|
||||
assert(!service.CreateTransport(&site,0)&&service.m_Entities.empty());
|
||||
Entity vehicle;CarControllerComponent controller;ERR_VehicleSecurityComponent security;ERR_Actors::spawned=&vehicle;
|
||||
vehicle.vehicle=false;assert(!service.CreateTransport(&site,0)&&service.m_Entities.back()==&vehicle);vehicle.vehicle=true;
|
||||
assert(!service.CreateTransport(&site,0));vehicle.controller=&controller;
|
||||
assert(service.CreateTransport(&site,0)&&controller.parked); // ordinary vehicle has no security hook
|
||||
vehicle.security=&security;security.controller=&controller;security.result=false;controller.parked=false;
|
||||
assert(!service.CreateTransport(&site,0)&&security.calls==1&&service.failure.find("security configuration")!=string::npos);
|
||||
security.result=true;controller.parked=false;assert(service.CreateTransport(&site,0)&&security.calls==2&&site.Transport==&vehicle);
|
||||
}
|
||||
''')
|
||||
print('PASS: actual CreateTransport requires successful scoped Configure after handbrake, preserves ordinary vehicles, tracks spawned entity before failures and never treats failed lock registration as ready.')
|
||||
Reference in New Issue
Block a user