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

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);}