Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
154 lines
10 KiB
Diff
154 lines
10 KiB
Diff
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c
|
|
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c
|
|
@@ -40,6 +40,51 @@
|
|
string Detail;
|
|
string FirstPrefab;
|
|
int FirstPurpose = -1;
|
|
+ // Called before Reject, once per reason in this explicit diagnostic sample.
|
|
+ // These labels describe sampled relationships; they never authorize a weapon.
|
|
+ void SlotEvidence(int reason, IEntity entity, WeaponSlotComponent mount, InventoryStorageSlot binding, InventoryItemComponent item, array<IEntity> allowed)
|
|
+ {
|
|
+ if ((Reasons & reason) != 0) return;
|
|
+ if (reason == 64) Detail += "mount-slot-evidence:";
|
|
+ else Detail += "zero-muzzle-slot-evidence:";
|
|
+ if (item) Detail += "item=present;";
|
|
+ else Detail += "item=absent;";
|
|
+ InventoryStorageSlot parent = null;
|
|
+ if (item) parent = item.GetParentSlot();
|
|
+ if (!parent) Detail += "parent=null;";
|
|
+ else if (parent == binding) Detail += "parent=same-binding;";
|
|
+ else Detail += "parent=different-binding;";
|
|
+ if (!binding) Detail += "binding=null;";
|
|
+ else
|
|
+ {
|
|
+ if (binding.GetAttachedEntity() == entity) Detail += "binding-attached=exact;";
|
|
+ else Detail += "binding-attached=other-or-null;";
|
|
+ if (binding.GetParentContainer() == mount) Detail += "binding-container=exact-mount;";
|
|
+ else Detail += "binding-container=other-or-null;";
|
|
+ }
|
|
+ IEntity owner = null;
|
|
+ if (mount) owner = mount.GetOwner();
|
|
+ if (owner && allowed.Contains(owner)) Detail += "mount-owner=allowed;";
|
|
+ else Detail += "mount-owner=absent-or-foreign;";
|
|
+ if (owner && owner.GetPrefabData()) Detail += "mount-prefab=" + owner.GetPrefabData().GetPrefabName() + ";";
|
|
+ if (!parent) return;
|
|
+ if (parent.GetAttachedEntity() == entity) Detail += "parent-attached=exact;";
|
|
+ else Detail += "parent-attached=other-or-null;";
|
|
+ WeaponSlotComponent parentMount = WeaponSlotComponent.Cast(parent.GetParentContainer());
|
|
+ if (parent.GetParentContainer() == mount && mount) Detail += "parent-container=exact-mount;";
|
|
+ else if (parentMount) Detail += "parent-container=other-mount;";
|
|
+ else if (!parent.GetParentContainer()) Detail += "parent-container=null;";
|
|
+ else Detail += "parent-container=non-mount;";
|
|
+ if (!parentMount) return;
|
|
+ IEntity parentOwner = parentMount.GetOwner();
|
|
+ if (parentOwner && allowed.Contains(parentOwner)) Detail += "parent-mount-owner=allowed;";
|
|
+ else Detail += "parent-mount-owner=absent-or-foreign;";
|
|
+ if (parentOwner && parentOwner.GetPrefabData()) Detail += "parent-mount-prefab=" + parentOwner.GetPrefabData().GetPrefabName() + ";";
|
|
+ if (parentMount.GetSlotInfo() == parent) Detail += "parent-mount-slot=exact;";
|
|
+ else Detail += "parent-mount-slot=other-or-null;";
|
|
+ if (parentMount.GetWeaponEntity() == entity) Detail += "parent-mount-weapon=exact;";
|
|
+ else Detail += "parent-mount-weapon=other-or-null;";
|
|
+ }
|
|
void Reject(int reason, string detail, IEntity entity, int purpose = -1)
|
|
{
|
|
if ((Reasons & reason) == 0) Detail += detail + ";";
|
|
@@ -104,7 +149,11 @@
|
|
if (!mountedItem || mountedItem.GetParentSlot() != binding)
|
|
{
|
|
#ifdef WORKBENCH
|
|
- if (s_Diagnostic) s_Diagnostic.Reject(64, "mount-item-slot-mismatch", mountedEntity);
|
|
+ if (s_Diagnostic)
|
|
+ {
|
|
+ s_Diagnostic.SlotEvidence(64, mountedEntity, mount, binding, mountedItem, allowed);
|
|
+ s_Diagnostic.Reject(64, "mount-item-slot-mismatch", mountedEntity);
|
|
+ }
|
|
#endif
|
|
result.Complete = false; return;
|
|
}
|
|
@@ -272,7 +321,23 @@
|
|
if (muzzles.IsEmpty())
|
|
{
|
|
if (NonFiringPlaceholder(weapon, allowed)) result.Weapons--;
|
|
- else result.Complete = false;
|
|
+ else
|
|
+ {
|
|
+#ifdef WORKBENCH
|
|
+ if (s_Diagnostic)
|
|
+ {
|
|
+ IEntity owner = weapon.GetOwner();
|
|
+ InventoryItemComponent item = InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent));
|
|
+ WeaponSlotComponent mount = null;
|
|
+ InventoryStorageSlot binding = null;
|
|
+ if (item && item.GetParentSlot()) mount = WeaponSlotComponent.Cast(item.GetParentSlot().GetParentContainer());
|
|
+ if (mount) binding = mount.GetSlotInfo();
|
|
+ s_Diagnostic.SlotEvidence(4096, owner, mount, binding, item, allowed);
|
|
+ s_Diagnostic.Reject(4096, "zero-muzzle-not-proven-placeholder", owner);
|
|
+ }
|
|
+#endif
|
|
+ result.Complete = false;
|
|
+ }
|
|
continue;
|
|
}
|
|
if (muzzles.Count() > 8) { result.Complete = false; continue; }
|
|
--- a/tests/test_hq_mounted_ammo_sampler.py
|
|
+++ b/tests/test_hq_mounted_ammo_sampler.py
|
|
@@ -15,6 +15,8 @@
|
|
code=code.replace('InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent))','entity->item')
|
|
code=code.replace('InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent))','owner->item').replace('ResourceName resource', 'string resource')
|
|
code=code.replace('WeaponSlotComponent.Cast(slot.GetParentContainer())','slot->mount').replace('BaseMuzzleComponent.Cast(slot.GetParentContainer())','slot->muzzle')
|
|
+code=code.replace('WeaponSlotComponent.Cast(parent.GetParentContainer())','parent->mount').replace('WeaponSlotComponent.Cast(item.GetParentSlot().GetParentContainer())','item->GetParentSlot()->mount')
|
|
+code=code.replace('parent.GetParentContainer()', 'parent->Container()')
|
|
code=code.replace('EStoragePurpose.','EStoragePurpose::').replace('GetParentSlot().','GetParentSlot()->')
|
|
code=code.replace('ChimeraCharacter.Cast(entity) != nullptr','entity->character').replace('ChimeraCharacter.Cast(entity) != null','entity->character')
|
|
code=code.replace('ChimeraCharacter.Cast(root)','root->character').replace('ChimeraCharacter.Cast(entity)','entity->character')
|
|
@@ -22,10 +24,11 @@
|
|
code=re.sub(r'\bERR_HQMountedAmmoDiagnostic (?=\w)', 'ERR_HQMountedAmmoDiagnostic* ',code)
|
|
code=code.replace('s_Diagnostic.', 's_Diagnostic->').replace('GetPrefabData().', 'GetPrefabData()->')
|
|
code=re.sub(r'\bERR_HQMountedAmmoSnapshot (?=\w)','ERR_HQMountedAmmoSnapshot* ',code)
|
|
-code=code.replace('array<','Array<').replace('foreach (','for (').replace('null','nullptr')
|
|
+code=code.replace('array<','Array<').replace('foreach (','for (')
|
|
+code=re.sub(r'"[^"]*"|\bnull\b',lambda m:m.group() if m.group().startswith(chr(34)) else 'nullptr',code)
|
|
code=code.replace('Array<IEntity> allowed, Array<BaseWeaponComponent> weapons,','Array<IEntity>& allowed, Array<BaseWeaponComponent>& weapons,')
|
|
code=code.replace('Array<IEntity> allowed, Array<BaseWeaponComponent> weapons)', 'Array<IEntity>& allowed, Array<BaseWeaponComponent>& weapons)')
|
|
-for name in ('copy','result','weapon','root','entity','child','part','manager','mounted','muzzle','magazine','item','storage','mount','mountedEntity','mountedItem','owner'):
|
|
+for name in ('copy','result','weapon','root','entity','child','part','manager','mounted','muzzle','magazine','item','storage','mount','mountedEntity','mountedItem','owner','parent','binding','parentMount','parentOwner'):
|
|
code=re.sub(r'\b'+name+r'\.',name+'->',code)
|
|
code=re.sub(r'\b(bool|int) (\w+);',r'\1 \2=0;',code).replace('\n}\n','\n};\n')
|
|
program=(r'''
|
|
@@ -41,7 +44,7 @@
|
|
struct Entity;struct Weapon;struct Muzzle;struct Magazine;struct Manager;struct Mount;
|
|
struct EStoragePurpose{static const int PURPOSE_UNSET=0,PURPOSE_DEPOSIT=1,PURPOSE_WEAPON_PROXY=2,PURPOSE_ATTACHMENT_PROXY=4,PURPOSE_ANY=127;};
|
|
struct Storage{int purpose=EStoragePurpose::PURPOSE_DEPOSIT;int GetPurpose(){return purpose;}};
|
|
-struct Slot{Mount* mount=nullptr;Muzzle* muzzle=nullptr;Storage* storage=nullptr;Storage* GetStorage(){return storage;}};
|
|
+struct Slot{Mount* mount=nullptr;Muzzle* muzzle=nullptr;Storage* storage=nullptr;Entity* attached=nullptr;Entity* GetAttachedEntity(){return attached;}void* Container(){return mount?static_cast<void*>(mount):muzzle?static_cast<void*>(muzzle):static_cast<void*>(storage);}Storage* GetStorage(){return storage;}};
|
|
struct Item{Slot* slot=nullptr;Slot* GetParentSlot(){return slot;}};
|
|
using InventoryStorageSlot=Slot*;using WeaponSlotComponent=Mount*;
|
|
using InventoryItemComponent=Item*;using BaseInventoryStorageComponent=Storage*;
|
|
@@ -259,6 +262,26 @@
|
|
# WORKBENCH records are opt-in. Re-run every original geometry/count assertion
|
|
# in that build too, plus exact failure witnesses from the same traversal.
|
|
cases=r'''
|
|
+ // Exact reason64 distinguishes absent item, null slot and differing slot,
|
|
+ // while actual Inspect remains incomplete and retains the same ammunition.
|
|
+ for(int fault=0;fault<3;fault++){
|
|
+ Rig r;Mount mount;Item item;Slot binding,other;Storage storage;storage.purpose=EStoragePurpose::PURPOSE_ATTACHMENT_PROXY;other.storage=&storage;
|
|
+ mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&binding;binding.mount=&mount;binding.attached=&r.gun;
|
|
+ r.manager.weapons={&mount};r.gun.item=fault==0?nullptr:&item;
|
|
+ if(fault==2)item.slot=&other;
|
|
+ other.mount=&mount;other.attached=&r.gun;
|
|
+ ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();
|
|
+ assert(!snap->Complete&&snap->Weapons==1&&snap->Rounds==25&&(d->Reasons&64));
|
|
+ assert(d->Detail.find(fault==0?"item=absent;":"item=present;")!=string::npos);
|
|
+ assert(d->Detail.find(fault==2?"parent=different-binding;":"parent=null;")!=string::npos);
|
|
+ assert(d->Detail.find("binding-attached=exact;")!=string::npos&&d->Detail.find("binding-container=exact-mount;")!=string::npos);
|
|
+ assert(d->Detail.find("mount-owner=allowed;")!=string::npos&&d->Detail.find("mount-prefab=fixture;")!=string::npos);
|
|
+ if(fault==2)assert(d->Detail.find("parent-mount-slot=other-or-null;")!=string::npos);
|
|
+ }
|
|
+ {Rig r;r.weapon.muzzles={};ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();
|
|
+ assert(!snap->Complete&&snap->Weapons==1&&snap->Rounds==0&&(d->Reasons&4096));
|
|
+ assert(d->Detail.find("zero-muzzle-not-proven-placeholder;")!=string::npos&&d->Detail.find("zero-muzzle-slot-evidence:item=absent;parent=null;")!=string::npos);}
|
|
+''' + r'''
|
|
{Rig r;ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(snap->Complete&&d->Reasons==0&&d->Nodes==4&&d->Allowed==3&&d->Managers==1);assert(!ERR_HQMountedAmmoSampler::EndDiagnostic());}
|
|
{Entity root;root.child=&root;ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==4&&d->Nodes==1&&d->Allowed==1&&root.reads==1);}
|
|
{Entity root;std::vector<Entity> children(128);root.child=&children[0];for(int i=0;i<127;i++)children[i].sibling=&children[i+1];ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==1024&&d->Nodes==129&&d->Allowed==128&&d->Structural==129&&d->Cargo==0&&children[127].reads==0);}
|