Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
+400
@@ -0,0 +1,400 @@
|
||||
// Detached mounted counts only. Carried and cargo ammunition never enter these totals.
|
||||
class ERR_HQMountedAmmoSnapshot
|
||||
{
|
||||
bool Complete;
|
||||
int Weapons;
|
||||
int Muzzles;
|
||||
int Magazines;
|
||||
int Rounds;
|
||||
int Capacity;
|
||||
int Chambers;
|
||||
int LowMagazines;
|
||||
ERR_HQMountedAmmoSnapshot Snapshot()
|
||||
{
|
||||
ERR_HQMountedAmmoSnapshot copy = new ERR_HQMountedAmmoSnapshot();
|
||||
copy.Complete = Complete; copy.Weapons = Weapons; copy.Muzzles = Muzzles;
|
||||
copy.Magazines = Magazines; copy.Rounds = Rounds; copy.Capacity = Capacity;
|
||||
copy.Chambers = Chambers; copy.LowMagazines = LowMagazines;
|
||||
return copy;
|
||||
}
|
||||
bool Valid()
|
||||
{
|
||||
if (Weapons < 0 || Weapons > 32 || Muzzles < 0 || Muzzles > 128 || Muzzles > Weapons * 8) return false;
|
||||
if (Magazines < 0 || Magazines > Muzzles || Magazines > 128) return false;
|
||||
if (Capacity < Magazines || Capacity > Magazines * 100000 || Rounds < 0 || Rounds > Capacity) return false;
|
||||
if (Chambers < 0 || Chambers > Muzzles * 32 || LowMagazines < 0 || LowMagazines > Magazines) return false;
|
||||
return !Complete || (Weapons == 0 || (Muzzles > 0 && Magazines > 0));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WORKBENCH
|
||||
// Ephemeral opt-in fixture diagnostics; never part of radio reports or policy.
|
||||
class ERR_HQMountedAmmoDiagnostic
|
||||
{
|
||||
int Reasons;
|
||||
int Nodes;
|
||||
int Structural;
|
||||
int Cargo;
|
||||
int Allowed;
|
||||
int Managers;
|
||||
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 + ";";
|
||||
if (Reasons == 0)
|
||||
{
|
||||
FirstPurpose = purpose;
|
||||
if (entity && entity.GetPrefabData()) FirstPrefab = entity.GetPrefabData().GetPrefabName();
|
||||
}
|
||||
Reasons = Reasons | reason;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
class ERR_HQMountedAmmoSampler
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
protected static ref ERR_HQMountedAmmoDiagnostic s_Diagnostic;
|
||||
static void BeginDiagnostic() { s_Diagnostic = new ERR_HQMountedAmmoDiagnostic(); }
|
||||
static ERR_HQMountedAmmoDiagnostic EndDiagnostic()
|
||||
{
|
||||
ref ERR_HQMountedAmmoDiagnostic result = s_Diagnostic; s_Diagnostic = null; return result;
|
||||
}
|
||||
#endif
|
||||
// Native mounted weapons and loaded magazines use InventoryStorageSlot too,
|
||||
// but their container is a weapon slot/muzzle rather than deposit storage.
|
||||
static bool Installed(IEntity entity, InventoryStorageSlot slot, array<IEntity> allowed)
|
||||
{
|
||||
if (!entity || !slot) return false;
|
||||
WeaponSlotComponent mount = WeaponSlotComponent.Cast(slot.GetParentContainer());
|
||||
if (mount && allowed.Contains(mount.GetOwner()) && mount.GetSlotInfo() == slot && mount.GetWeaponEntity() == entity) return true;
|
||||
BaseMuzzleComponent muzzle = BaseMuzzleComponent.Cast(slot.GetParentContainer());
|
||||
if (!muzzle || !allowed.Contains(muzzle.GetOwner())) return false;
|
||||
BaseMagazineComponent magazine = muzzle.GetMagazine();
|
||||
return magazine && magazine.GetOwner() == entity;
|
||||
}
|
||||
// The observed WCS BMP empty control has no runtime InventoryItemComponent.
|
||||
// Native structural attachment can prove this control only, never gun custody.
|
||||
protected static bool StructuralEmpty(BaseWeaponComponent weapon, array<IEntity> allowed, WeaponSlotComponent expectedMount = null)
|
||||
{
|
||||
if (!weapon || WeaponSlotComponent.Cast(weapon) || !weapon.GetOwner()) return false;
|
||||
IEntity owner = weapon.GetOwner();
|
||||
if (!allowed.Contains(owner) || !owner.GetPrefabData()
|
||||
|| owner.GetPrefabData().GetPrefabName() != "{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et") return false;
|
||||
if (BaseWeaponComponent.Cast(owner.FindComponent(BaseWeaponComponent)) != weapon) return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent));
|
||||
// A different nonnull inventory binding is conflicting evidence, not absence.
|
||||
if (item && item.GetParentSlot()) return false;
|
||||
IEntity parent = owner.GetParent();
|
||||
if (!parent || !allowed.Contains(parent)) return false;
|
||||
array<Managed> slots = {}; parent.FindComponents(WeaponSlotComponent, slots);
|
||||
if (slots.IsEmpty() || slots.Count() > 8) return false;
|
||||
WeaponSlotComponent matched = null;
|
||||
foreach (Managed component : slots)
|
||||
{
|
||||
WeaponSlotComponent mount = WeaponSlotComponent.Cast(component);
|
||||
if (!mount || mount.GetOwner() != parent || mount.GetWeaponEntity() != owner) continue;
|
||||
InventoryStorageSlot binding = mount.GetSlotInfo();
|
||||
if (!binding || binding.GetParentContainer() != mount || binding.GetAttachedEntity() != owner) return false;
|
||||
if (matched) return false;
|
||||
matched = mount;
|
||||
}
|
||||
if (!matched || (expectedMount && matched != expectedMount)) return false;
|
||||
array<BaseMuzzleComponent> muzzles = {}; weapon.GetMuzzlesList(muzzles);
|
||||
return muzzles.IsEmpty();
|
||||
}
|
||||
protected static void AddWeapon(BaseWeaponComponent weapon, array<IEntity> allowed, array<BaseWeaponComponent> weapons, ERR_HQMountedAmmoSnapshot result)
|
||||
{
|
||||
if (!weapon || !weapon.GetOwner() || !allowed.Contains(weapon.GetOwner()))
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
IEntity owner = null;
|
||||
if (weapon) owner = weapon.GetOwner();
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(16, "weapon-owner-not-allowed", owner);
|
||||
#endif
|
||||
result.Complete = false; return;
|
||||
}
|
||||
// Native managers can expose both a slot and its physical weapon. Normalize
|
||||
// one exact current installation before deduplication, never another slot.
|
||||
WeaponSlotComponent mount = WeaponSlotComponent.Cast(weapon);
|
||||
if (mount)
|
||||
{
|
||||
IEntity mountedEntity = mount.GetWeaponEntity();
|
||||
InventoryStorageSlot binding = mount.GetSlotInfo();
|
||||
if (!mountedEntity || !allowed.Contains(mountedEntity) || !binding || binding.GetParentContainer() != mount)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(32, "mount-binding-invalid", mountedEntity);
|
||||
#endif
|
||||
result.Complete = false; return;
|
||||
}
|
||||
InventoryItemComponent mountedItem = InventoryItemComponent.Cast(mountedEntity.FindComponent(InventoryItemComponent));
|
||||
if ((!mountedItem || mountedItem.GetParentSlot() != binding)
|
||||
&& !StructuralEmpty(BaseWeaponComponent.Cast(mountedEntity.FindComponent(BaseWeaponComponent)), allowed, mount))
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
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;
|
||||
}
|
||||
weapon = BaseWeaponComponent.Cast(mountedEntity.FindComponent(BaseWeaponComponent));
|
||||
if (!weapon || WeaponSlotComponent.Cast(weapon) || weapon.GetOwner() != mountedEntity)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(128, "mount-weapon-invalid", mountedEntity);
|
||||
#endif
|
||||
result.Complete = false; return;
|
||||
}
|
||||
}
|
||||
if (weapons.Contains(weapon)) return;
|
||||
if (weapons.Count() >= 32)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(256, "weapon-limit32", weapon.GetOwner());
|
||||
#endif
|
||||
result.Complete = false; return;
|
||||
}
|
||||
weapons.Insert(weapon);
|
||||
}
|
||||
protected static void Collect(IEntity root, ERR_HQMountedAmmoSnapshot result, array<IEntity> allowed, array<BaseWeaponComponent> weapons)
|
||||
{
|
||||
if (!root || ChimeraCharacter.Cast(root))
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(1, "invalid-root", root);
|
||||
#endif
|
||||
result.Complete = false; return;
|
||||
}
|
||||
result.Complete = true;
|
||||
array<IEntity> nodes = {root};
|
||||
// Known deposit cargo must not consume the mounted hierarchy budget. Both
|
||||
// categories retain independent caps; the unique queue is capped at their sum.
|
||||
// Skipped crew/unknown nodes remain structural and cannot borrow cargo space.
|
||||
int structuralNodes = 0;
|
||||
int cargoNodes = 0;
|
||||
for (int cursor = 0; cursor < nodes.Count(); cursor++)
|
||||
{
|
||||
IEntity entity = nodes[cursor];
|
||||
bool character = ChimeraCharacter.Cast(entity) != null;
|
||||
bool excludedCargo;
|
||||
bool unknownStorage;
|
||||
// Exact installed slots are checked before deposit exclusion. Never walk
|
||||
// the children of known cargo or seated character roots.
|
||||
if (!character)
|
||||
{
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
|
||||
if (item && item.GetParentSlot() && !Installed(entity, item.GetParentSlot(), allowed))
|
||||
{
|
||||
BaseInventoryStorageComponent storage = item.GetParentSlot().GetStorage();
|
||||
if (!storage || storage.GetPurpose() == EStoragePurpose.PURPOSE_UNSET || storage.GetPurpose() == EStoragePurpose.PURPOSE_ANY)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
int purpose = -1;
|
||||
if (storage) purpose = storage.GetPurpose();
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(2, "ambiguous-storage", entity, purpose);
|
||||
#endif
|
||||
result.Complete = false; unknownStorage = true;
|
||||
}
|
||||
else excludedCargo = (storage.GetPurpose() & EStoragePurpose.PURPOSE_DEPOSIT) != 0;
|
||||
}
|
||||
}
|
||||
if (excludedCargo)
|
||||
{
|
||||
cargoNodes++;
|
||||
if (cargoNodes > 128)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(2048, "cargo-limit128", entity);
|
||||
#endif
|
||||
result.Complete = false; break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
structuralNodes++;
|
||||
if (structuralNodes > 128)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(1024, "structural-limit128", entity);
|
||||
#endif
|
||||
result.Complete = false; break;
|
||||
}
|
||||
if (character || unknownStorage) continue;
|
||||
allowed.Insert(entity);
|
||||
IEntity child = entity.GetChildren();
|
||||
while (child)
|
||||
{
|
||||
bool repeated = nodes.Contains(child);
|
||||
if (repeated || nodes.Count() >= 256)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic)
|
||||
{
|
||||
if (repeated) s_Diagnostic.Reject(4, "repeated-node", child);
|
||||
else s_Diagnostic.Reject(8, "queue-limit256", child);
|
||||
}
|
||||
#endif
|
||||
result.Complete = false; break;
|
||||
}
|
||||
nodes.Insert(child);
|
||||
child = child.GetSibling();
|
||||
}
|
||||
}
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic)
|
||||
{
|
||||
s_Diagnostic.Nodes = nodes.Count(); s_Diagnostic.Allowed = allowed.Count();
|
||||
s_Diagnostic.Structural = structuralNodes; s_Diagnostic.Cargo = cargoNodes;
|
||||
}
|
||||
#endif
|
||||
foreach (IEntity part : allowed)
|
||||
{
|
||||
BaseWeaponManagerComponent manager = BaseWeaponManagerComponent.Cast(part.FindComponent(BaseWeaponManagerComponent));
|
||||
if (manager)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Managers++;
|
||||
#endif
|
||||
array<BaseWeaponComponent> listed = {}; manager.GetWeapons(listed);
|
||||
if (listed.Count() > 8)
|
||||
{
|
||||
#ifdef WORKBENCH
|
||||
if (s_Diagnostic) s_Diagnostic.Reject(512, "manager-limit8", part);
|
||||
#endif
|
||||
result.Complete = false;
|
||||
}
|
||||
else foreach (BaseWeaponComponent entry : listed) AddWeapon(entry, allowed, weapons, result);
|
||||
}
|
||||
BaseWeaponComponent mounted = BaseWeaponComponent.Cast(part.FindComponent(BaseWeaponComponent));
|
||||
if (mounted) AddWeapon(mounted, allowed, weapons, result);
|
||||
}
|
||||
}
|
||||
// These installed resources are inert selection/periscope controls, not guns.
|
||||
// Only omit them after the caller observed an empty native muzzle list and
|
||||
// reciprocal current weapon-slot ownership in this same sampled hierarchy.
|
||||
protected static bool NonFiringPlaceholder(BaseWeaponComponent weapon, array<IEntity> allowed)
|
||||
{
|
||||
if (!weapon || !weapon.GetOwner()) return false;
|
||||
IEntity owner = weapon.GetOwner();
|
||||
if (!allowed.Contains(owner) || !owner.GetPrefabData()) return false;
|
||||
ResourceName resource = owner.GetPrefabData().GetPrefabName();
|
||||
if (resource != "{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et"
|
||||
&& resource != "{7E1DD1CE4D6A23F4}Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et") return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(owner.FindComponent(InventoryItemComponent));
|
||||
if (!item || !item.GetParentSlot()) return StructuralEmpty(weapon, allowed);
|
||||
InventoryStorageSlot slot = item.GetParentSlot();
|
||||
WeaponSlotComponent mount = WeaponSlotComponent.Cast(slot.GetParentContainer());
|
||||
return mount && allowed.Contains(mount.GetOwner()) && mount.GetSlotInfo() == slot && mount.GetWeaponEntity() == owner;
|
||||
}
|
||||
static ERR_HQMountedAmmoSnapshot Inspect(IEntity root)
|
||||
{
|
||||
ERR_HQMountedAmmoSnapshot result = new ERR_HQMountedAmmoSnapshot();
|
||||
array<IEntity> allowed = {};
|
||||
array<BaseWeaponComponent> weapons = {};
|
||||
Collect(root, result, allowed, weapons);
|
||||
result.Weapons = weapons.Count();
|
||||
array<BaseMuzzleComponent> seenMuzzles = {};
|
||||
array<BaseMagazineComponent> seenMagazines = {};
|
||||
array<IEntity> magazineOwners = {};
|
||||
foreach (BaseWeaponComponent weapon : weapons)
|
||||
{
|
||||
array<BaseMuzzleComponent> muzzles = {}; weapon.GetMuzzlesList(muzzles);
|
||||
if (muzzles.IsEmpty())
|
||||
{
|
||||
if (NonFiringPlaceholder(weapon, allowed)) result.Weapons--;
|
||||
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; }
|
||||
foreach (BaseMuzzleComponent muzzle : muzzles)
|
||||
{
|
||||
if (!muzzle || !muzzle.GetOwner() || !allowed.Contains(muzzle.GetOwner())) { result.Complete = false; continue; }
|
||||
if (seenMuzzles.Contains(muzzle)) continue;
|
||||
if (seenMuzzles.Count() >= 128) { result.Complete = false; continue; }
|
||||
seenMuzzles.Insert(muzzle); result.Muzzles++;
|
||||
int barrels = muzzle.GetBarrelsCount();
|
||||
if (barrels < 1 || barrels > 32) result.Complete = false;
|
||||
else for (int barrel = 0; barrel < barrels; barrel++)
|
||||
if (muzzle.IsBarrelChambered(barrel)) result.Chambers++;
|
||||
BaseMagazineComponent magazine = muzzle.GetMagazine();
|
||||
// Projectile-only weapons and absent magazines are unknown, never empty.
|
||||
if (!magazine || !magazine.GetOwner() || !allowed.Contains(magazine.GetOwner())) { result.Complete = false; continue; }
|
||||
if (seenMagazines.Contains(magazine)) continue;
|
||||
if (magazineOwners.Contains(magazine.GetOwner())) { result.Complete = false; continue; }
|
||||
seenMagazines.Insert(magazine); magazineOwners.Insert(magazine.GetOwner());
|
||||
int rounds = magazine.GetAmmoCount();
|
||||
int capacity = magazine.GetMaxAmmoCount();
|
||||
if (rounds < 0 || capacity < 1 || capacity > 100000 || rounds > capacity) { result.Complete = false; continue; }
|
||||
result.Magazines++; result.Rounds += rounds; result.Capacity += capacity;
|
||||
if (rounds <= capacity * 0.25) result.LowMagazines++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
"""Execute actual mounted sampler/snapshot methods against bounded native doubles."""
|
||||
import re,os
|
||||
from pathlib import Path
|
||||
from policy_harness import ROOT, run
|
||||
p=Path(os.environ.get('ERR_MOUNTED_AMMO_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedAmmoSampler.c'))
|
||||
s=p.read_text()
|
||||
for forbidden in ('SetAmmoCount','GetOrigin','GetWorld','Spawn','TryMove','DeleteEntity','GetAgents'):
|
||||
assert forbidden not in s, forbidden
|
||||
code=re.sub(r'//[^\n]*','',s).replace('protected ','').replace('class ','struct ')
|
||||
code=code.replace('BaseWeaponManagerComponent.Cast(part.FindComponent(BaseWeaponManagerComponent))','part->manager')
|
||||
code=code.replace('BaseWeaponComponent.Cast(part.FindComponent(BaseWeaponComponent))','part->weapon')
|
||||
code=code.replace('BaseWeaponComponent.Cast(mountedEntity.FindComponent(BaseWeaponComponent))','mountedEntity->weapon')
|
||||
code=code.replace('BaseWeaponComponent.Cast(owner.FindComponent(BaseWeaponComponent))','owner->weapon').replace('WeaponSlotComponent.Cast(component)','dynamic_cast<Mount*>(component)')
|
||||
code=code.replace('parent.FindComponents(WeaponSlotComponent, slots)','parent->FindMounts(slots)')
|
||||
code=code.replace('InventoryItemComponent.Cast(mountedEntity.FindComponent(InventoryItemComponent))','mountedEntity->item')
|
||||
code=code.replace('WeaponSlotComponent.Cast(weapon)','dynamic_cast<Mount*>(weapon)').replace('binding.GetParentContainer()','binding->mount')
|
||||
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')
|
||||
code=code.replace('protected static ref ', 'inline static ').replace('static ref ', 'inline static ').replace('ref ', '')
|
||||
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 (')
|
||||
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','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'''
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
using string=std::string;
|
||||
struct Prefab{string name="fixture";string GetPrefabName(){return name;}};
|
||||
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 n){this->push_back(n);}bool Contains(T n){return std::find(this->begin(),this->end(),n)!=this->end();}};
|
||||
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;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*;
|
||||
using Managed=Weapon*;using IEntity=Entity*;using BaseWeaponComponent=Weapon*;using BaseMuzzleComponent=Muzzle*;
|
||||
using BaseMagazineComponent=Magazine*;using BaseWeaponManagerComponent=Manager*;
|
||||
struct Entity{Prefab prefab;Prefab*GetPrefabData(){return &prefab;}Item* item=nullptr;bool character=false;Entity* child=nullptr;Entity* sibling=nullptr;Manager* manager=nullptr;Weapon* weapon=nullptr;
|
||||
Entity* parent=nullptr;Array<Weapon*> mounts;Entity* GetParent(){return parent;}void FindMounts(Array<Weapon*>&out){out=mounts;}
|
||||
int reads=0;Entity* GetChildren(){reads++;assert(reads<=1);return child;}Entity* GetSibling(){return sibling;}};
|
||||
struct Magazine{IEntity owner=nullptr;int rounds=25,capacity=100,reads=0;
|
||||
IEntity GetOwner(){return owner;}int GetAmmoCount(){reads++;return rounds;}int GetMaxAmmoCount(){return capacity;}};
|
||||
struct Muzzle{IEntity owner=nullptr;Magazine* mag=nullptr;int barrels=1,chambered=1,reads=0;
|
||||
IEntity GetOwner(){return owner;}int GetBarrelsCount(){return barrels;}
|
||||
bool IsBarrelChambered(int b){reads++;return b<chambered;}Magazine* GetMagazine(){return mag;}};
|
||||
struct Weapon{virtual ~Weapon(){}IEntity owner=nullptr;Array<Muzzle*> muzzles;int reads=0;
|
||||
IEntity GetOwner(){return owner;}void GetMuzzlesList(Array<Muzzle*>& out){reads++;out=muzzles;}};
|
||||
struct Mount:Weapon{IEntity weapon=nullptr;Slot* slot=nullptr;
|
||||
Slot* GetSlotInfo(){return slot;}IEntity GetWeaponEntity(){return weapon;}};
|
||||
struct Manager{Array<Weapon*> weapons;int reads=0;void GetWeapons(Array<Weapon*>& out){reads++;out=weapons;}};
|
||||
'''+code+r'''
|
||||
struct Rig{Entity root,gun,magazine,crew,rifle;Manager manager,crewManager;Weapon weapon,carried;
|
||||
Muzzle muzzle,carriedMuzzle;Magazine mag,carriedMag;
|
||||
Rig(){root.child=&gun;gun.sibling=&magazine;magazine.sibling=&crew;crew.character=true;crew.child=&rifle;
|
||||
root.manager=&manager;manager.weapons={&weapon,&weapon};gun.weapon=&weapon;weapon.owner=&gun;weapon.muzzles={&muzzle,&muzzle};
|
||||
muzzle.owner=&gun;muzzle.mag=&mag;mag.owner=&magazine;
|
||||
crew.manager=&crewManager;crewManager.weapons={&carried};rifle.weapon=&carried;carried.owner=&rifle;
|
||||
carried.muzzles={&carriedMuzzle};carriedMuzzle.owner=&rifle;carriedMuzzle.mag=&carriedMag;carriedMag.owner=&rifle;}
|
||||
ERR_HQMountedAmmoSnapshot* Inspect(){return ERR_HQMountedAmmoSampler::Inspect(&root);}
|
||||
};
|
||||
int main(){
|
||||
// Native observed WCS empty control: no item or null reverse slot, but exact
|
||||
// immediate-parent structural slot. Keep real guns and the weapon cap intact.
|
||||
for(bool hasItem:{false,true})for(bool directFirst:{false,true}){
|
||||
Rig r;Entity dummy;Weapon inert;Mount mount;Item item;Slot slot;
|
||||
dummy.prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";
|
||||
dummy.parent=&r.root;r.root.mounts={&mount};r.crew.sibling=&dummy;dummy.weapon=&inert;inert.owner=&dummy;
|
||||
if(hasItem)dummy.item=&item;
|
||||
mount.owner=&r.root;mount.weapon=&dummy;mount.slot=&slot;slot.mount=&mount;slot.attached=&dummy;
|
||||
if(directFirst)r.manager.weapons={&inert,&mount,&inert,&r.weapon};else r.manager.weapons={&mount,&inert,&mount,&r.weapon};
|
||||
auto s=r.Inspect();assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Muzzles==1&&s->Magazines==1&&s->Rounds==25&&s->Chambers==1);
|
||||
}
|
||||
for(int fault=0;fault<15;fault++){
|
||||
Rig r;Entity dummy,foreign;Weapon inert,otherWeapon;Mount mount,otherMount;Item item;Slot slot,otherSlot;
|
||||
dummy.prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";
|
||||
dummy.parent=&r.root;r.root.mounts={&mount};r.crew.sibling=&dummy;dummy.weapon=&inert;inert.owner=&dummy;
|
||||
mount.owner=&r.root;mount.weapon=&dummy;mount.slot=&slot;slot.mount=&mount;slot.attached=&dummy;r.manager.weapons={&mount,&inert,&r.weapon};
|
||||
if(fault==0){dummy.item=&item;item.slot=&otherSlot;}
|
||||
if(fault==1)dummy.prefab.name="{0000000000000000}Prefabs/Weapons/Core/Armament_Empty.et";
|
||||
if(fault==2)dummy.prefab.name="{52120E4BA120661B}Prefabs/Other.et";
|
||||
if(fault==3)dummy.prefab.name="{7E1DD1CE4D6A23F4}Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et";
|
||||
if(fault==4)inert.muzzles={&r.muzzle};
|
||||
if(fault==5)dummy.parent=nullptr;
|
||||
if(fault==6)dummy.parent=&foreign;
|
||||
if(fault==7)mount.slot=nullptr;
|
||||
if(fault==8)slot.mount=&otherMount;
|
||||
if(fault==9)slot.attached=&foreign;
|
||||
if(fault==10)mount.weapon=&foreign;
|
||||
if(fault==11)mount.owner=&foreign;
|
||||
if(fault==12)r.root.mounts.resize(9,&mount);
|
||||
if(fault==13)r.root.mounts={&mount,&mount};
|
||||
if(fault==14){otherWeapon.owner=&dummy;dummy.weapon=&otherWeapon;}
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Valid());
|
||||
}
|
||||
{Entity root,dummy;Weapon inert;Mount mount;Slot slot;root.child=&dummy;dummy.parent=&root;root.mounts={&mount};
|
||||
dummy.prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";dummy.weapon=&inert;inert.owner=&dummy;
|
||||
mount.owner=&root;mount.weapon=&dummy;mount.slot=&slot;slot.mount=&mount;slot.attached=&dummy;
|
||||
auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(s->Complete&&s->Valid()&&s->Weapons==0&&s->Magazines==0&&s->Rounds==0);}
|
||||
// Controls still occupy bounded collection slots before Inspect omits them.
|
||||
for(int count:{32,33}){Entity root;std::vector<Entity> parents(count),dummies(count);std::vector<Mount> mounts(count);std::vector<Slot> slots(count);std::vector<Weapon> inert(count);
|
||||
root.child=&parents[0];for(int i=0;i<count;i++){if(i+1<count)parents[i].sibling=&parents[i+1];parents[i].child=&dummies[i];parents[i].mounts={&mounts[i]};dummies[i].parent=&parents[i];dummies[i].weapon=&inert[i];inert[i].owner=&dummies[i];
|
||||
dummies[i].prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";mounts[i].owner=&parents[i];mounts[i].weapon=&dummies[i];mounts[i].slot=&slots[i];slots[i].mount=&mounts[i];slots[i].attached=&dummies[i];}
|
||||
auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(s->Complete==(count==32)&&s->Valid()&&s->Weapons==0);}
|
||||
// Installed BMP has three live gun components plus two inert native controls.
|
||||
// Exercise actual Inspect using exact documented resources and reciprocal slots.
|
||||
for(string name:{string("{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et"),string("{7E1DD1CE4D6A23F4}Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et")}){
|
||||
Rig r;Entity dummy;Weapon inert;Mount mount;Item item;Slot slot;dummy.prefab.name=name;
|
||||
r.crew.sibling=&dummy;dummy.weapon=&inert;inert.owner=&dummy;dummy.item=&item;item.slot=&slot;slot.mount=&mount;mount.owner=&r.root;mount.slot=&slot;mount.weapon=&dummy;
|
||||
r.manager.weapons.Insert(&mount);r.manager.weapons.Insert(&inert);
|
||||
auto s=r.Inspect();assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Muzzles==1&&s->Magazines==1&&s->Rounds==25&&s->Capacity==100&&s->Chambers==1&&inert.reads==1);
|
||||
}
|
||||
// A root containing only a known installed inert control is completely unarmed.
|
||||
{Entity root,dummy;Weapon inert;Mount mount;Item item;Slot slot;root.child=&dummy;dummy.prefab.name="{7E1DD1CE4D6A23F4}Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et";dummy.weapon=&inert;inert.owner=&dummy;dummy.item=&item;item.slot=&slot;slot.mount=&mount;mount.owner=&root;mount.slot=&slot;mount.weapon=&dummy;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(s->Complete&&s->Valid()&&s->Weapons==0&&s->Muzzles==0&&s->Magazines==0&&s->Rounds==0&&s->Capacity==0&&s->Chambers==0&&inert.reads==1);}
|
||||
// Both BMP controls coexist: decrement each exactly once while retaining the gun.
|
||||
{Rig r;Entity dummy[2];Weapon inert[2];Mount mount[2];Item item[2];Slot slot[2];r.crew.sibling=&dummy[0];dummy[0].sibling=&dummy[1];
|
||||
dummy[0].prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";dummy[1].prefab.name="{7E1DD1CE4D6A23F4}Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et";
|
||||
for(int i=0;i<2;i++){dummy[i].weapon=&inert[i];inert[i].owner=&dummy[i];dummy[i].item=&item[i];item[i].slot=&slot[i];slot[i].mount=&mount[i];mount[i].owner=&r.root;mount[i].slot=&slot[i];mount[i].weapon=&dummy[i];r.manager.weapons.Insert(&mount[i]);r.manager.weapons.Insert(&inert[i]);}
|
||||
auto s=r.Inspect();assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Muzzles==1&&s->Magazines==1&&s->Rounds==25&&s->Capacity==100&&s->Chambers==1&&inert[0].reads==1&&inert[1].reads==1);}
|
||||
// Unknown no-muzzle weapons and broken/foreign installation remain incomplete.
|
||||
for(int bad=0;bad<6;bad++){
|
||||
Rig r;Entity dummy,foreign;Weapon inert;Mount mount;Item item;Slot slot,other;dummy.prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";
|
||||
r.crew.sibling=&dummy;dummy.weapon=&inert;inert.owner=&dummy;dummy.item=&item;item.slot=&slot;slot.mount=&mount;mount.owner=&r.root;mount.slot=&slot;mount.weapon=&dummy;
|
||||
if(bad==0)dummy.prefab.name="{0000000000000000}Prefabs/Weapons/Core/Armament_Empty.et";
|
||||
if(bad==1)dummy.item=nullptr;
|
||||
if(bad==2)item.slot=nullptr;
|
||||
if(bad==3)mount.owner=&foreign;
|
||||
if(bad==4)mount.slot=&other;
|
||||
if(bad==5)mount.weapon=&foreign;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Valid());
|
||||
}
|
||||
// A known resource carrying an actual muzzle is never silently omitted.
|
||||
{Rig r;r.gun.prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";auto s=r.Inspect();assert(s->Complete&&s->Weapons==1&&s->Rounds==25&&r.muzzle.reads==1);}
|
||||
// An excluded cargo subtree cannot turn a stored placeholder into mounted armament.
|
||||
{Rig r;Entity cargo;Weapon inert;Item item;Slot slot;Storage storage;r.crew.sibling=&cargo;cargo.prefab.name="{52120E4BA120661B}Prefabs/Weapons/Core/Armament_Empty.et";cargo.item=&item;item.slot=&slot;slot.storage=&storage;cargo.weapon=&inert;inert.owner=&cargo;auto s=r.Inspect();assert(s->Complete&&s->Weapons==1&&inert.reads==0);}
|
||||
{auto s=ERR_HQMountedAmmoSampler::Inspect(nullptr);assert(!s->Complete&&s->Valid());}
|
||||
{Entity root;root.character=true;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&root.reads==0);}
|
||||
{Entity root;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(s->Complete&&s->Valid()&&s->Weapons==0);}
|
||||
{Rig r;auto s=r.Inspect();assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Muzzles==1&&s->Magazines==1);
|
||||
assert(s->Rounds==25&&s->Capacity==100&&s->Chambers==1&&s->LowMagazines==1);
|
||||
assert(r.weapon.reads==1&&r.muzzle.reads==1&&r.mag.reads==1&&r.crew.reads==0&&r.crewManager.reads==0&&r.carried.reads==0);
|
||||
auto copy=s->Snapshot();s->Complete=false;s->Rounds=0;s->Capacity=0;s->LowMagazines=0;
|
||||
assert(copy->Complete&©->Rounds==25&©->Capacity==100&©->LowMagazines==1&©->Weapons==1&©->Muzzles==1&©->Magazines==1&©->Chambers==1);}
|
||||
for(int rounds:{0,24,25,26,100}){Rig r;r.mag.rounds=rounds;auto s=r.Inspect();assert(s->Complete&&s->Valid());assert(s->LowMagazines==(rounds<=25));}
|
||||
{Rig r;Muzzle second;second.owner=&r.gun;second.mag=&r.mag;second.chambered=0;r.weapon.muzzles.Insert(&second);
|
||||
auto s=r.Inspect();assert(s->Complete&&s->Muzzles==2&&s->Magazines==1&&s->Chambers==1&&s->Rounds==25&&r.mag.reads==1);}
|
||||
// Manager references into skipped characters, foreign roots, or nulls never count.
|
||||
for(int kind=0;kind<3;kind++){Rig r;Entity foreign;Weapon w;w.owner=&foreign;
|
||||
r.manager.weapons.Insert(kind==0?&r.carried:kind==1?&w:nullptr);auto s=r.Inspect();assert(!s->Complete&&s->Valid()&&s->Weapons==1);}
|
||||
for(int kind=0;kind<3;kind++){Rig r;Entity foreign;
|
||||
if(kind==0)r.muzzle.owner=&foreign;
|
||||
if(kind==1)r.mag.owner=&foreign;
|
||||
if(kind==2)r.mag.owner=nullptr;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Valid()&&s->Rounds==0&&s->Magazines==0);}
|
||||
// Deposited rifles and nested magazines are excluded; installed magazine slots remain admitted.
|
||||
{Rig r;Entity cargo,rifle,magEntity;Item item;Slot slot;Storage storage;Weapon rifleWeapon;
|
||||
r.crew.sibling=&cargo;cargo.child=&rifle;rifle.child=&magEntity;rifle.item=&item;item.slot=&slot;slot.storage=&storage;
|
||||
rifle.weapon=&rifleWeapon;rifleWeapon.owner=&rifle;auto s=r.Inspect();assert(s->Complete&&s->Weapons==1&&rifle.reads==0&&magEntity.reads==0);}
|
||||
{Rig r;Item item;Slot slot;Storage storage;r.magazine.item=&item;item.slot=&slot;slot.storage=&storage;
|
||||
storage.purpose=EStoragePurpose::PURPOSE_ATTACHMENT_PROXY;auto s=r.Inspect();assert(s->Complete&&s->Rounds==25&&s->Magazines==1);}
|
||||
for(int purpose:{0,127}){Rig r;Item item;Slot slot;Storage storage;r.gun.item=&item;item.slot=&slot;slot.storage=&storage;
|
||||
storage.purpose=purpose;auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Valid());}
|
||||
// Native purposes support combined flags; any deposit bit excludes the subtree.
|
||||
{Rig r;Item item;Slot slot;Storage storage;r.gun.item=&item;item.slot=&slot;slot.storage=&storage;
|
||||
storage.purpose=EStoragePurpose::PURPOSE_DEPOSIT|EStoragePurpose::PURPOSE_ATTACHMENT_PROXY;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Valid());}
|
||||
// Manager references cannot rescue a cargo weapon.
|
||||
{Rig r;Item item;Slot slot;Storage storage;r.gun.item=&item;item.slot=&slot;slot.storage=&storage;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Valid());}
|
||||
// Exact native weapon-slot and muzzle containers are not deposit storage.
|
||||
for(int purpose:{-1,0,127}){Rig r;Item gunItem,magItem;Slot gunSlot,magSlot;Storage storage;Mount mount;
|
||||
mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&gunSlot;gunSlot.mount=&mount;r.gun.item=&gunItem;gunItem.slot=&gunSlot;
|
||||
magSlot.muzzle=&r.muzzle;r.magazine.item=&magItem;magItem.slot=&magSlot;
|
||||
if(purpose>=0){storage.purpose=purpose;gunSlot.storage=&storage;magSlot.storage=&storage;}
|
||||
auto s=r.Inspect();assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Magazines==1&&s->Rounds==25&&s->Chambers==1);}
|
||||
// No generic missing-storage bypass: foreign container owners, stale slots and
|
||||
// substituted attached items fail closed, including foreign loaded magazines.
|
||||
for(int fault=0;fault<6;fault++){Rig r;Entity foreign;Item item;Slot slot,other;Mount mount;
|
||||
r.gun.item=&item;item.slot=&slot;slot.mount=&mount;mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&slot;
|
||||
if(fault==0)mount.owner=&foreign;
|
||||
if(fault==1)mount.owner=&r.crew;
|
||||
if(fault==2)mount.weapon=&foreign;
|
||||
if(fault==3)mount.slot=&other;
|
||||
if(fault==4)slot.mount=nullptr;
|
||||
if(fault==5)mount.owner=nullptr;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Rounds==0&&s->Valid());}
|
||||
for(int fault=0;fault<3;fault++){Rig r;Entity foreign;Item item;Slot slot;
|
||||
r.magazine.item=&item;item.slot=&slot;slot.muzzle=&r.muzzle;
|
||||
if(fault==0)r.muzzle.owner=&foreign;
|
||||
if(fault==1)r.muzzle.mag=nullptr;
|
||||
if(fault==2)r.mag.owner=&foreign;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Magazines==0&&s->Rounds==0&&s->Valid());}
|
||||
// An exact loaded magazine inside an excluded cargo rifle does not re-admit it.
|
||||
{Rig r;Item item,magItem;Slot slot,magSlot;Storage deposit;r.gun.item=&item;item.slot=&slot;slot.storage=&deposit;
|
||||
r.magazine.item=&magItem;magItem.slot=&magSlot;magSlot.muzzle=&r.muzzle;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Weapons==0&&s->Magazines==0&&s->Valid());}
|
||||
// Native manager/FindComponent expose the wrapper and the physical weapon.
|
||||
// A UAZ's single PKM must be one weapon even when all those paths overlap.
|
||||
{Rig r;Mount mount;Item item;Slot binding;mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&binding;
|
||||
binding.mount=&mount;item.slot=&binding;r.gun.item=&item;r.root.weapon=&mount;mount.muzzles=r.weapon.muzzles;
|
||||
r.manager.weapons={&mount,&r.weapon,&mount};auto s=r.Inspect();
|
||||
assert(s->Complete&&s->Valid()&&s->Weapons==1&&s->Muzzles==1&&s->Magazines==1&&s->Rounds==25&&s->Chambers==1);
|
||||
assert(mount.reads==0&&r.weapon.reads==1&&r.mag.reads==1);}
|
||||
// Unknown/foreign/empty/nested/stale slot endpoints never become real guns.
|
||||
for(int fault=0;fault<11;fault++){Rig r;Entity foreign;Mount mount,other;Item item;Slot binding,stale;
|
||||
mount.owner=&r.root;mount.weapon=&r.gun;mount.slot=&binding;binding.mount=&mount;item.slot=&binding;r.gun.item=&item;
|
||||
r.root.weapon=&mount;r.manager.weapons={&mount};
|
||||
if(fault==0)mount.weapon=nullptr;
|
||||
if(fault==1)mount.weapon=&foreign;
|
||||
if(fault==2)mount.weapon=&r.root; // nested/self wrapper
|
||||
if(fault==3)binding.mount=&other;
|
||||
if(fault==4)item.slot=&stale;
|
||||
if(fault==5)mount.slot=nullptr;
|
||||
if(fault==6)r.gun.item=nullptr;
|
||||
if(fault==7)mount.owner=&foreign;
|
||||
if(fault==8)r.gun.weapon=nullptr;
|
||||
if(fault==9){other.owner=&r.gun;r.gun.weapon=&other;} // valid first binding, nested wrapper endpoint
|
||||
if(fault==10)r.weapon.owner=&foreign; // component found on target belongs elsewhere
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Valid()&&s->Weapons<=1&&mount.reads==0&&other.reads==0);}
|
||||
// Thirty-two genuine installed guns still fit the weapon cap despite wrappers.
|
||||
{Entity root;Array<Entity> hosts(32),guns(32),mags(32);Array<Weapon> weapons(32);Array<Mount> mounts(32);
|
||||
Array<Muzzle> muzzles(32);Array<Magazine> magazines(32);Array<Item> items(32);Array<Slot> bindings(32);
|
||||
root.child=&hosts[0];for(int i=0;i<32;i++){
|
||||
if(i+1<32)hosts[i].sibling=&hosts[i+1];
|
||||
hosts[i].child=&guns[i];guns[i].child=&mags[i];hosts[i].weapon=&mounts[i];guns[i].weapon=&weapons[i];
|
||||
mounts[i].owner=&hosts[i];mounts[i].weapon=&guns[i];mounts[i].slot=&bindings[i];bindings[i].mount=&mounts[i];
|
||||
items[i].slot=&bindings[i];guns[i].item=&items[i];weapons[i].owner=&guns[i];weapons[i].muzzles={&muzzles[i]};
|
||||
mounts[i].muzzles=weapons[i].muzzles;muzzles[i].owner=&guns[i];muzzles[i].mag=&magazines[i];magazines[i].owner=&mags[i];}
|
||||
auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(s->Complete&&s->Valid()&&s->Weapons==32&&s->Muzzles==32&&s->Magazines==32&&s->Rounds==800);}
|
||||
// Missing/projectile-only magazines and unsupported native values stay incomplete.
|
||||
for(int kind=0;kind<9;kind++){Rig r;
|
||||
if(kind==0)r.muzzle.mag=nullptr;
|
||||
if(kind==1)r.weapon.muzzles={nullptr};
|
||||
if(kind==2)r.weapon.muzzles={};
|
||||
if(kind==3)r.muzzle.barrels=0;
|
||||
if(kind==4)r.muzzle.barrels=33;
|
||||
if(kind==5)r.mag.rounds=-1;
|
||||
if(kind==6)r.mag.capacity=0;
|
||||
if(kind==7)r.mag.capacity=100001;
|
||||
if(kind==8)r.mag.rounds=101;
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Valid());}
|
||||
{Rig r;r.weapon.muzzles=Array<Muzzle*>(9,&r.muzzle);auto s=r.Inspect();assert(!s->Complete&&s->Muzzles==0&&r.muzzle.reads==0);}
|
||||
{Rig r;r.manager.weapons=Array<Weapon*>(9,&r.weapon);auto s=r.Inspect();assert(!s->Complete&&s->Weapons==1&&s->Valid());}
|
||||
// Entity budget also bounds enqueue and cycles, rather than only processed work.
|
||||
for(int count:{128,129,300}){std::vector<Entity> nodes(count);for(int i=0;i<count-1;i++)nodes[i].child=&nodes[i+1];
|
||||
auto s=ERR_HQMountedAmmoSampler::Inspect(&nodes[0]);assert(s->Complete==(count==128));assert(s->Valid());
|
||||
int reads=0;for(auto& e:nodes)reads+=e.reads;assert(reads==128);}
|
||||
{Entity root,child;root.child=&child;child.sibling=&child;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&s->Valid());}
|
||||
{Entity root,child;root.child=&child;child.child=&root;auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&s->Valid());}
|
||||
// Maximum weapon and muzzle bounds remain valid detached counts on overflow.
|
||||
for(int count:{32,33}){Entity root;std::vector<Entity> nodes(count);std::vector<Weapon> weapons(count);
|
||||
root.child=&nodes[0];for(int i=0;i<count;i++){nodes[i].weapon=&weapons[i];weapons[i].owner=&nodes[i];if(i+1<count)nodes[i].sibling=&nodes[i+1];}
|
||||
auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&s->Weapons==32&&s->Valid());}
|
||||
{Entity root;std::vector<Entity> nodes(18);std::vector<Weapon> weapons(17);std::vector<Muzzle> muzzles(136);Magazine mag;mag.owner=&nodes[17];
|
||||
root.child=&nodes[0];for(int i=0;i<18;i++){if(i+1<18)nodes[i].sibling=&nodes[i+1];if(i==17)continue;
|
||||
nodes[i].weapon=&weapons[i];weapons[i].owner=&nodes[i];for(int j=0;j<8;j++){auto& m=muzzles[i*8+j];m.owner=&nodes[i];m.mag=&mag;weapons[i].muzzles.Insert(&m);}}
|
||||
auto s=ERR_HQMountedAmmoSampler::Inspect(&root);assert(!s->Complete&&s->Muzzles==128&&s->Magazines==1&&s->Chambers==128&&s->Valid());}
|
||||
{Rig r;Magazine alias;alias.owner=&r.magazine;Muzzle second;second.owner=&r.gun;second.mag=&alias;r.weapon.muzzles.Insert(&second);
|
||||
auto s=r.Inspect();assert(!s->Complete&&s->Magazines==1&&s->Rounds==25&&alias.reads==0&&s->Valid());}
|
||||
|
||||
// Realistic production load: 128 known deposited items plus mounted root,
|
||||
// gun, loaded magazine and a pruned original crew root exceed128 in total.
|
||||
// Cargo-first and structure-first traversal both retain the same mounted counts.
|
||||
for(bool cargoFirst:{false,true}){Rig r;std::vector<Weapon> cargoWeapons(128);std::vector<Entity> cargo(128);std::vector<Item> items(128);std::vector<Slot> slots(128);Storage deposit;
|
||||
for(int i=0;i<128;i++){cargo[i].weapon=&cargoWeapons[i];cargoWeapons[i].owner=&cargo[i];cargo[i].item=&items[i];items[i].slot=&slots[i];slots[i].storage=&deposit;if(i<127)cargo[i].sibling=&cargo[i+1];}
|
||||
if(cargoFirst){r.root.child=&cargo[0];cargo.back().sibling=&r.gun;}else{r.crew.sibling=&cargo[0];}
|
||||
auto snapshot=r.Inspect();assert(snapshot->Complete&&snapshot->Weapons==1&&snapshot->Magazines==1&&snapshot->Rounds==25&&snapshot->Valid());
|
||||
for(auto& item:cargo){assert(item.reads==0);}for(auto& gun:cargoWeapons){assert(gun.reads==0);}assert(r.weapon.reads==1&&r.carried.reads==0);}
|
||||
// Independent128 structural/128 cargo limits, in either order and interleaved.
|
||||
for(int order:{0,1,2})for(int scenario:{0,1,2,3}){
|
||||
int structures=scenario==1?129:128;int cargoCount=scenario==2||scenario==3?129:(scenario==1?100:128);
|
||||
if(scenario==2)structures=4;
|
||||
std::vector<Entity> nodes(structures),cargo(cargoCount);std::vector<Item> items(cargoCount);std::vector<Slot> slots(cargoCount);Storage deposit;std::vector<Entity*> sequence;
|
||||
for(int i=0;i<cargoCount;i++){cargo[i].item=&items[i];items[i].slot=&slots[i];slots[i].storage=&deposit;}
|
||||
if(order==0){for(auto& e:cargo)sequence.push_back(&e);for(int i=1;i<structures;i++)sequence.push_back(&nodes[i]);}
|
||||
if(order==1){for(int i=1;i<structures;i++)sequence.push_back(&nodes[i]);for(auto& e:cargo)sequence.push_back(&e);}
|
||||
if(order==2){for(int i=0;i<std::max(structures-1,cargoCount);i++){if(i<cargoCount)sequence.push_back(&cargo[i]);if(i+1<structures)sequence.push_back(&nodes[i+1]);}}
|
||||
nodes[0].child=sequence[0];for(int i=0;i<int(sequence.size())-1;i++)sequence[i]->sibling=sequence[i+1];
|
||||
auto snapshot=ERR_HQMountedAmmoSampler::Inspect(&nodes[0]);assert(snapshot->Complete==(scenario==0)&&snapshot->Weapons==0&&snapshot->Valid());
|
||||
int reads=0;for(auto& e:nodes)reads+=e.reads;assert(reads<=128);for(auto& e:cargo)assert(e.reads==0);
|
||||
}
|
||||
// Wire validation rejects impossible or malicious counts.
|
||||
{ERR_HQMountedAmmoSnapshot s;assert(s.Valid());s.Weapons=33;assert(!s.Valid());s.Weapons=1;s.Muzzles=9;assert(!s.Valid());
|
||||
s.Muzzles=1;s.Magazines=2;assert(!s.Valid());s.Magazines=1;s.Capacity=100001;assert(!s.Valid());s.Capacity=100;s.Rounds=101;assert(!s.Valid());
|
||||
s.Rounds=25;s.Chambers=33;assert(!s.Valid());s.Chambers=1;s.LowMagazines=2;assert(!s.Valid());s.LowMagazines=1;s.Complete=true;assert(s.Valid());}
|
||||
}
|
||||
''')
|
||||
run(program)
|
||||
print('PASS: actual mounted hierarchy sampling, ownership exclusion, deduplication, completeness/caps, quarter threshold and detached validation; no runtime policy or ammunition mutation')
|
||||
|
||||
# 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);}
|
||||
for(int purpose:{-1,0,127}){Rig r;Item item;Slot slot;Storage storage;r.gun.item=&item;item.slot=&slot;r.gun.prefab.name="first-gun";if(purpose>=0){slot.storage=&storage;storage.purpose=purpose;}ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&(d->Reasons&2)&&(d->Reasons&16)&&d->FirstPrefab=="first-gun"&&d->FirstPurpose==purpose&&d->Detail.find("ambiguous-storage;")!=string::npos);}
|
||||
{Entity root;Manager manager;root.manager=&manager;manager.weapons.resize(9,nullptr);ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==512&&d->Managers==1&&manager.reads==1);}
|
||||
{Rig r;Mount mount;mount.owner=&r.root;r.manager.weapons={&mount};ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=r.Inspect();auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&(d->Reasons&32));}
|
||||
{ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snap=ERR_HQMountedAmmoSampler::Inspect(nullptr);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snap->Complete&&d->Reasons==1&&d->Nodes==0&&d->Allowed==0);}
|
||||
'''
|
||||
|
||||
cases+=r'''
|
||||
for(int which:{0,1}){Entity root;int cargoCount=which==0?129:256;std::vector<Entity> cargo(cargoCount);std::vector<Item> items(cargoCount);std::vector<Slot> slots(cargoCount);Storage storage;root.child=&cargo[0];
|
||||
for(int i=0;i<cargoCount;i++){cargo[i].item=&items[i];items[i].slot=&slots[i];slots[i].storage=&storage;if(i+1<cargoCount)cargo[i].sibling=&cargo[i+1];}
|
||||
ERR_HQMountedAmmoSampler::BeginDiagnostic();auto snapshot=ERR_HQMountedAmmoSampler::Inspect(&root);auto*d=ERR_HQMountedAmmoSampler::EndDiagnostic();assert(!snapshot->Complete&&d->Cargo==129&&d->Structural==1&&(d->Reasons&2048));
|
||||
if(which==0){assert(d->Nodes==130&&!(d->Reasons&8));}else{assert(d->Nodes==256&&(d->Reasons&8));}}
|
||||
'''
|
||||
run('#define WORKBENCH\n'+program.replace('int main(){','int main(){'+cases))
|
||||
print('PASS: WORKBENCH opt-in diagnostics distinguish cycles/cap/storage/manager/mount failures without repeated native traversal')
|
||||
Reference in New Issue
Block a user