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

321 lines
26 KiB
Plaintext

--- /dev/null
+++ b/addons/core/Scripts/Game/ERR/20_Intel/ERR_MineIntel.c
@@ -0,0 +1,167 @@
+// Historical evidence bound to an exact physical document. Undisclosed records
+// stay server-local; no live mine or soldier position is queried on recovery.
+class ERR_MineIntelRecord
+{
+ IEntity Mine;
+ vector Area;
+ ref array<IEntity> Folders = {};
+ bool Revealed;
+}
+class ERR_MineIntel
+{
+ protected ERR_GameMode m_Mode;
+ protected ref array<ref ERR_MineIntelRecord> m_Records = {};
+ protected int m_Bindings;
+ protected bool m_Stopped;
+ void ERR_MineIntel(ERR_GameMode mode) { m_Mode = mode; }
+ protected bool Current()
+ {
+ return Replication.IsServer() && !m_Stopped && m_Mode && m_Mode == ERR_GameMode.Get() && m_Mode.Active();
+ }
+ protected bool Generic(IEntity folder)
+ {
+ return folder && folder.GetPrefabData() && folder.GetPrefabData().GetPrefabName() == ERR_Resources.IntelFolder()
+ && !folder.FindComponent(ERR_DocumentComponent) && m_Mode.Discovery() && !m_Mode.Discovery().IsSpent(folder);
+ }
+ // Called once at accepted engineer-job admission, before approaching the site.
+ void Candidates(IEntity actor, out array<IEntity> folders)
+ {
+ folders.Clear();
+ if (!Current() || !ERR_Actors.CanFight(actor) || ERR_Actors.Player(actor)) return;
+ if (!m_Mode.EnemyFaction() || SCR_FactionManager.SGetFaction(actor) != m_Mode.EnemyFaction()) return;
+ InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
+ if (!inventory) return;
+ array<IEntity> found = {}; ERR_DiscoveryService.Folders(inventory, found);
+ if (found.Count() > 128) return;
+ foreach (IEntity folder : found)
+ {
+ if (folders.Count() >= 16) break;
+ if (Generic(folder) && ERR_InfantryAmmoTransfer.StoredUnder(folder, actor) && !folders.Contains(folder)) folders.Insert(folder);
+ }
+ }
+ bool Seal(IEntity actor, IEntity mine, vector position, array<IEntity> candidates)
+ {
+ if (!Current() || !ERR_Actors.CanFight(actor) || ERR_Actors.Player(actor)) return false;
+ if (!m_Mode.EnemyFaction() || SCR_FactionManager.SGetFaction(actor) != m_Mode.EnemyFaction()) return false;
+ if (!candidates || candidates.IsEmpty() || candidates.Count() > 16 || m_Records.Count() >= 32 || m_Bindings >= 128) return false;
+ if (!ERR_ForceRules.FiniteVector(position) || !ERR_Actors.Alive(mine) || !mine.GetPrefabData()) return false;
+ if (mine.GetPrefabData().GetPrefabName() != ERR_EnemyLoadoutCatalog.MINE || mine.GetParent()) return false;
+ if (vector.Distance(mine.GetOrigin(), position) > 1) return false;
+ InventoryItemComponent item = InventoryItemComponent.Cast(mine.FindComponent(InventoryItemComponent));
+ SCR_PressureTriggerComponent trigger = SCR_PressureTriggerComponent.Cast(mine.FindComponent(SCR_PressureTriggerComponent));
+ if (!item || item.GetParentSlot() || !trigger || !trigger.IsActivated()) return false;
+ foreach (ERR_MineIntelRecord known : m_Records) if (known.Mine == mine) return false;
+ array<IEntity> held = {}; Candidates(actor, held);
+ ref ERR_MineIntelRecord record = new ERR_MineIntelRecord();
+ foreach (IEntity folder : candidates)
+ {
+ if (m_Bindings + record.Folders.Count() >= 128) break;
+ if (held.Contains(folder) && !record.Folders.Contains(folder)) record.Folders.Insert(folder);
+ }
+ if (record.Folders.IsEmpty()) return false;
+ record.Mine = mine;
+ record.Area = Vector(ERR_DiscoveryMapRules.Coordinate(position[0], 1), 0, ERR_DiscoveryMapRules.Coordinate(position[2], 1));
+ m_Bindings += record.Folders.Count();
+ m_Records.Insert(record);
+ return true;
+ }
+ bool Recover(IEntity player, IEntity folder)
+ {
+ if (!Current() || !m_Mode.Players() || !m_Mode.Players().CurrentActor(player) || !ERR_Actors.Alive(player)) return false;
+ if (!m_Mode.PlayerFaction() || SCR_FactionManager.SGetFaction(player) != m_Mode.PlayerFaction() || !Generic(folder)) return false;
+ if (!ERR_InfantryAmmoTransfer.StoredUnder(folder, player)) return false;
+ InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(player.FindComponent(InventoryStorageManagerComponent));
+ if (!inventory) return false;
+ array<IEntity> held = {}; ERR_DiscoveryService.Folders(inventory, held);
+ if (held.Count() > 128 || !held.Contains(folder)) return false;
+ bool revealed = false;
+ foreach (ERR_MineIntelRecord record : m_Records)
+ {
+ if (record.Revealed || !record.Folders.Contains(folder)) continue;
+ if (m_Mode.ERR_RevealMineArea(record.Area)) { record.Revealed = true; revealed = true; }
+ }
+ return revealed;
+ }
+ void Stop() { m_Stopped = true; m_Records.Clear(); m_Bindings = 0; }
+}
+
+modded class ERR_GameMode
+{
+ protected ref ERR_MineIntel m_ERRMineIntel;
+ // Only recovered, quantized historical warnings are replicated, including JIP.
+ [RplProp()]
+ protected ref array<vector> m_ERRMineAreas = {};
+ protected ref array<ref ERR_DiscoveryMarker> m_ERRMineMarkers = {};
+ protected bool m_ERRMineIntelStopped;
+ protected SCR_MapMarkerManagerComponent m_ERRMineMarkerOwner;
+ ERR_MineIntel MineIntel() { return m_ERRMineIntel; }
+ protected override void EOnInit(IEntity owner)
+ {
+ super.EOnInit(owner);
+ if (SCR_Global.IsEditMode()) return;
+ if (Replication.IsServer()) m_ERRMineIntel = new ERR_MineIntel(this);
+ OnStop().Insert(ERR_StopMineIntel);
+ if (RplSession.Mode() != RplMode.Dedicated) GetGame().GetCallqueue().CallLater(ERR_UpdateMineMarkers, 1500, true);
+ }
+ bool ERR_RevealMineArea(vector area)
+ {
+ if (!Replication.IsServer() || m_ERRMineIntelStopped || this != ERR_GameMode.Get() || !Active()) return false;
+ if (!ERR_ForceRules.FiniteVector(area)) return false;
+ // Canonicalize at the publication boundary too: never publish precise points.
+ vector cell = Vector(ERR_DiscoveryMapRules.Coordinate(area[0], 1), 0, ERR_DiscoveryMapRules.Coordinate(area[2], 1));
+ if (m_ERRMineAreas.Contains(cell)) return true;
+ if (m_ERRMineAreas.Count() >= 32) return false;
+ m_ERRMineAreas.Insert(cell);
+ Replication.BumpMe();
+ return true;
+ }
+ protected void ERR_ClearMineMarkers()
+ {
+ SCR_MapMarkerManagerComponent manager = m_ERRMineMarkerOwner;
+ foreach (ERR_DiscoveryMarker marker : m_ERRMineMarkers)
+ {
+ if (manager) manager.ERR_RemoveDiscoveryMarker(marker);
+ else if (marker) marker.OnDelete();
+ }
+ m_ERRMineMarkers.Clear();
+ m_ERRMineMarkerOwner = null;
+ }
+ protected void ERR_UpdateMineMarkers()
+ {
+ if (!GetGame() || m_ERRMineIntelStopped || this != ERR_GameMode.Get() || !Active()) { ERR_ClearMineMarkers(); return; }
+ if (!GetGame().GetPlayerController()) return;
+ SCR_MapMarkerManagerComponent manager = SCR_MapMarkerManagerComponent.GetInstance();
+ if (manager != m_ERRMineMarkerOwner) ERR_ClearMineMarkers();
+ if (!manager || !manager.GetMarkerConfig()) return;
+ if (m_ERRMineAreas.Count() > 32) { ERR_ClearMineMarkers(); return; }
+ if (m_ERRMineMarkers.Count() > m_ERRMineAreas.Count()) ERR_ClearMineMarkers();
+ m_ERRMineMarkerOwner = manager;
+ for (int i = m_ERRMineMarkers.Count(); i < m_ERRMineAreas.Count(); i++)
+ {
+ ERR_DiscoveryMarker marker = new ERR_DiscoveryMarker();
+ marker.SetType(SCR_EMapMarkerType.PLACED_CUSTOM);
+ marker.SetMarkerConfigID(0);
+ marker.SetDiscoveryPosition(m_ERRMineAreas[i], 1);
+ marker.SetLocationIcon(ERR_LocationKind.OUTPOST, "", 1);
+ marker.SetCustomText("Reported mine area (500 m); may still be active");
+ manager.InsertStaticMarker(marker, true);
+ m_ERRMineMarkers.Insert(marker);
+ }
+ }
+ protected void ERR_StopMineIntel()
+ {
+ if (m_ERRMineIntelStopped) return;
+ m_ERRMineIntelStopped = true;
+ if (m_ERRMineIntel) m_ERRMineIntel.Stop();
+ if (GetGame() && GetGame().GetCallqueue()) GetGame().GetCallqueue().Remove(ERR_UpdateMineMarkers);
+ ERR_ClearMineMarkers();
+ if (Replication.IsServer()) { m_ERRMineAreas.Clear(); Replication.BumpMe(); }
+ OnStop().Remove(ERR_StopMineIntel);
+ }
+ void ~ERR_GameMode()
+ {
+ if (GetGame() && GetGame().GetCallqueue()) GetGame().GetCallqueue().Remove(ERR_UpdateMineMarkers);
+ if (m_ERRMineIntel) m_ERRMineIntel.Stop();
+ ERR_ClearMineMarkers();
+ }
+}
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMine.c
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMine.c
@@ -55,6 +55,7 @@
protected ref SCR_AIBehaviorBase m_Action;
protected ref ERR_EngineerMineRemoval m_Removal;
protected ref array<IEntity> m_Attempted;
+ protected ref array<IEntity> m_IntelFolders = {};
protected vector m_Position;
protected vector m_Rally;
protected float m_Started = -1;
@@ -204,6 +205,7 @@
// The shared exact-item tombstone survives pickup, transfer and late callbacks.
m_Attempted.Insert(m_Mine);
if (!Carried()) return Refuse();
+ if (m_Mode.MineIntel()) m_Mode.MineIntel().Candidates(m_Actor, m_IntelFolders);
m_Started = now; m_LastTick = now; m_State = 0;
Move(m_Position);
ERR_HQ.Event("ENGINEER MINE", "original carried PMN4 assigned; one approach and finite 180s deadline");
@@ -261,7 +263,11 @@
if (!ERR_EngineerMineRules.MayArm(Placed(), Owned() && Quiet(), vector.DistanceXZ(m_Actor.GetOrigin(), m_Position), m_FriendlyNear)) return;
m_Trigger.SetUser(m_Actor);
m_Trigger.ActivateTrigger();
- if (m_Trigger.IsActivated()) ERR_HQ.Event("ENGINEER MINE", "original PMN4 armed after engineer withdrawal and physical friendly clearance");
+ if (m_Trigger.IsActivated())
+ {
+ if (m_Mode.MineIntel()) m_Mode.MineIntel().Seal(m_Actor, m_Mine, m_Position, m_IntelFolders);
+ ERR_HQ.Event("ENGINEER MINE", "original PMN4 armed after engineer withdrawal and physical friendly clearance");
+ }
Stop();
}
}
--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryService.c
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryService.c
@@ -53,6 +53,8 @@
foreach (IEntity folder : folders)
{
if (!folder || m_Spent.Contains(folder) || m_Spent.Count() >= 4096) continue;
+ bool mineRecovered = false;
+ if (m_Mode.MineIntel()) mineRecovered = m_Mode.MineIntel().Recover(player, folder);
array<ERR_LocationComponent> locations = {};
m_Mode.Locations(locations);
array<ERR_LocationComponent> eligible = {};
@@ -70,7 +72,7 @@
}
if (eligible.IsEmpty())
{
- if (!document && MarkSpent(folder)) m_Mode.ERR_IntelExhausted(!sealedTarget.IsEmpty());
+ if (!document && MarkSpent(folder) && !mineRecovered) m_Mode.ERR_IntelExhausted(!sealedTarget.IsEmpty());
continue;
}
ERR_LocationComponent chosen = eligible.GetRandomElement();
--- /dev/null
+++ b/tests/test_mine_intel.py
@@ -0,0 +1,96 @@
+"""Actual mine evidence/publication/marker bodies with native doubles, not engine/MP proof."""
+import os,re
+from pathlib import Path
+from policy_harness import ROOT,body,run
+P=Path(os.environ.get('ERR_MINE_INTEL_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/20_Intel/ERR_MineIntel.c'))
+def cpp(s):
+ s=s.replace('protected ','').replace('ref ','').replace('out ','').replace('null','nullptr').replace('array<','Array<').replace(' = {};',';').replace('foreach (','for (')
+ s=s.replace('Array<IEntity> folders)', 'Array<IEntity>& folders)').replace('Array<IEntity> candidates)', 'Array<IEntity>& candidates)').replace('!candidates || ','')
+ for t in ['InventoryStorageManagerComponent','InventoryItemComponent','SCR_PressureTriggerComponent','ERR_MineIntelRecord','ERR_DiscoveryMarker','SCR_MapMarkerManagerComponent']:
+ s=re.sub(r'\b'+t+r' (\w+)',t+r'* \1',s)
+ for ns in ['Replication','ERR_GameMode','ERR_Actors','SCR_FactionManager','ERR_Resources','ERR_DiscoveryService','ERR_InfantryAmmoTransfer','ERR_ForceRules','ERR_EnemyLoadoutCatalog','ERR_DiscoveryMapRules','InventoryStorageManagerComponent','InventoryItemComponent','SCR_PressureTriggerComponent','SCR_MapMarkerManagerComponent','ERR_LocationKind','SCR_EMapMarkerType','vector']:
+ s=s.replace(ns+'.',ns+'::')
+ for obj in ['m_Mode','folder','actor','mine','player','inventory','item','trigger','record','known','manager','marker','m_ERRMineIntel']:
+ s=re.sub(r'\b'+obj+r'\.',obj+'->',s)
+ s=s.replace('bool revealed;', 'bool revealed=false;')
+ s=s.replace('GetPrefabData().','GetPrefabData()->').replace('Discovery().','Discovery()->').replace('Players().','Players()->').replace('GetGame().','GetGame()->').replace('GetCallqueue().','GetCallqueue()->').replace('OnStop().','OnStop()->')
+ s=re.sub(r'FindComponent\((\w+)\)',r'FindComponent("\1")',s)
+ s=s.replace('Remove(ERR_UpdateMineMarkers)','Remove(1)').replace('Remove(ERR_StopMineIntel)','Remove(2)')
+ return s
+service='\n'.join(body(P,x) for x in ['protected bool Current(','protected bool Generic(','void Candidates(','bool Seal(','bool Recover(','void Stop('])
+destructor=body(P,'void ~ERR_GameMode(')
+mode='\n'.join(body(P,x) for x in ['bool ERR_RevealMineArea(','protected void ERR_ClearMineMarkers(','protected void ERR_UpdateMineMarkers(','protected void ERR_StopMineIntel('])
+# Enforce references are not modelled by raw C++ pointers. Lifecycle bodies only.
+run(r'''
+#include <cassert>
+#include <cmath>
+#include <string>
+#include <vector>
+#include <algorithm>
+struct vector {float a[3]={0,0,0};float& operator[](int n){return a[n];}bool operator==(const vector&b)const{return a[0]==b.a[0]&&a[1]==b.a[1]&&a[2]==b.a[2];}static float Distance(vector a,vector b){return std::sqrt(std::pow(a[0]-b[0],2)+std::pow(a[1]-b[1],2)+std::pow(a[2]-b[2],2));}};
+vector Vector(float x,float y,float z){return {{x,y,z}};}
+template<class T>struct Array:std::vector<T>{int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Clear(){this->clear();}bool Contains(T a){return std::find(this->begin(),this->end(),a)!=this->end();}void Insert(T a){this->push_back(a);}};
+struct Entity;using IEntity=Entity*;struct Prefab {std::string name="intel";std::string GetPrefabName(){return name;}};
+struct InventoryStorageManagerComponent {Array<IEntity> folders;static InventoryStorageManagerComponent*Cast(void*p){return (InventoryStorageManagerComponent*)p;}};
+struct InventoryItemComponent {void*slot=nullptr;void*GetParentSlot(){return slot;}static InventoryItemComponent*Cast(void*p){return (InventoryItemComponent*)p;}};
+struct SCR_PressureTriggerComponent {bool active=true;bool IsActivated(){return active;}static SCR_PressureTriggerComponent*Cast(void*p){return (SCR_PressureTriggerComponent*)p;}};
+struct Entity {bool alive=true,player=false,objective=false;int faction=1,reads=0;IEntity owner=nullptr,parent=nullptr;vector position;Prefab prefab;InventoryStorageManagerComponent*inv=nullptr;InventoryItemComponent item;SCR_PressureTriggerComponent trigger;
+ Prefab*GetPrefabData(){reads++;return &prefab;}IEntity GetParent(){reads++;return parent;}vector GetOrigin(){reads++;return position;}
+ void*FindComponent(std::string t){reads++;if(t=="ERR_DocumentComponent")return objective?this:nullptr;if(t=="InventoryStorageManagerComponent")return inv;if(t=="InventoryItemComponent")return &item;if(t=="SCR_PressureTriggerComponent")return &trigger;return nullptr;}};
+namespace Replication {bool server=true;int bumps=0;bool IsServer(){return server;}void BumpMe(){bumps++;}}
+namespace ERR_Actors {bool CanFight(IEntity e){return e&&e->alive;}bool Alive(IEntity e){return e&&e->alive;}bool Player(IEntity e){return e&&e->player;}}
+namespace SCR_FactionManager {int SGetFaction(IEntity e){return e?e->faction:0;}}
+namespace ERR_Resources {std::string IntelFolder(){return "intel";}}
+namespace ERR_EnemyLoadoutCatalog {std::string MINE="mine";}
+namespace ERR_DiscoveryService {void Folders(InventoryStorageManagerComponent*i,Array<IEntity>&f){f=i->folders;}}
+namespace ERR_InfantryAmmoTransfer {bool StoredUnder(IEntity f,IEntity a){return f&&a&&f->owner==a;}}
+namespace ERR_ForceRules {bool FiniteVector(vector a){return std::isfinite(a[0])&&std::isfinite(a[1])&&std::isfinite(a[2]);}}
+namespace ERR_DiscoveryMapRules {int Coordinate(float x,int){return std::floor(x/500.f)*500+250;}}
+struct DiscoveryService {Array<IEntity> spent;bool IsSpent(IEntity f){return spent.Contains(f);}};
+struct PlayersService {IEntity current=nullptr;bool CurrentActor(IEntity e){return e&&e==current;}};
+struct Queue {int removed=0;void Remove(int){removed++;}};
+struct Game {bool controller=true;Queue queue;bool GetPlayerController(){return controller;}Queue*GetCallqueue(){return &queue;}}game;Game*currentGame=&game;Game*GetGame(){return currentGame;}
+struct Invoker {int removed=0;void Remove(int){removed++;}};
+namespace ERR_LocationKind {int OUTPOST=3;}namespace SCR_EMapMarkerType {int PLACED_CUSTOM=1;}
+struct ERR_DiscoveryMarker {vector position;std::string label;int deleted=0;void SetType(int){}void SetMarkerConfigID(int){}void SetDiscoveryPosition(vector p,int){position=p;}void SetLocationIcon(int,std::string,int){}void SetCustomText(std::string s){label=s;}void OnDelete(){deleted++;}};
+struct SCR_MapMarkerManagerComponent {static inline SCR_MapMarkerManagerComponent*current=nullptr;bool config=true;Array<ERR_DiscoveryMarker*> inserted,removed;static SCR_MapMarkerManagerComponent*GetInstance(){return current;}bool GetMarkerConfig(){return config;}void InsertStaticMarker(ERR_DiscoveryMarker*m,bool){inserted.Insert(m);}void ERR_RemoveDiscoveryMarker(ERR_DiscoveryMarker*m){removed.Insert(m);m->OnDelete();}};
+struct ERR_MineIntel;
+struct ERR_GameMode {static inline ERR_GameMode*current=nullptr;bool active=true;int enemy=1,player=2;DiscoveryService discovery;PlayersService players;Invoker stop;
+ ERR_MineIntel*m_ERRMineIntel=nullptr;Array<vector>m_ERRMineAreas;Array<ERR_DiscoveryMarker*>m_ERRMineMarkers;bool m_ERRMineIntelStopped=false;SCR_MapMarkerManagerComponent*m_ERRMineMarkerOwner=nullptr;
+ static ERR_GameMode*Get(){return current;}bool Active(){return active;}int EnemyFaction(){return enemy;}int PlayerFaction(){return player;}DiscoveryService*Discovery(){return &discovery;}PlayersService*Players(){return &players;}Invoker*OnStop(){return &stop;}
+ void Destroy();bool ERR_RevealMineArea(vector);void ERR_ClearMineMarkers();void ERR_UpdateMineMarkers();void ERR_StopMineIntel();};
+struct ERR_MineIntelRecord {IEntity Mine=nullptr;vector Area;Array<IEntity> Folders;bool Revealed=false;};
+struct ERR_MineIntel {ERR_GameMode*m_Mode;Array<ERR_MineIntelRecord*>m_Records;int m_Bindings=0;bool m_Stopped=false;ERR_MineIntel(ERR_GameMode*m):m_Mode(m){}
+'''+cpp(service)+r'''};
+'''+re.sub(r'\b(bool|void) (ERR_\w+)\(',r'\1 ERR_GameMode::\2(',cpp(mode))+cpp(destructor).replace('void ~ERR_GameMode()', 'void ERR_GameMode::Destroy()')+r'''
+struct Fixture {ERR_GameMode mode;ERR_MineIntel intel{&mode};Entity engineer,player,folder,mine;InventoryStorageManagerComponent ei,pi;Array<IEntity> candidates;
+ Fixture(){ERR_GameMode::current=&mode;Replication::server=true;currentGame=&game;game.controller=true;SCR_MapMarkerManagerComponent::current=nullptr;mode.m_ERRMineIntel=&intel;mode.players.current=&player;player.player=true;player.faction=2;engineer.inv=&ei;player.inv=&pi;folder.owner=&engineer;ei.folders.Insert(&folder);mine.prefab.name="mine";mine.position=Vector(1030,0,2340);}
+ void Capture(){intel.Candidates(&engineer,candidates);assert(candidates.Count()==1);}
+ void Seal(){Capture();assert(intel.Seal(&engineer,&mine,mine.position,candidates));assert(mode.m_ERRMineAreas.empty());}
+ void Transfer(){ei.folders.Clear();pi.folders.Insert(&folder);folder.owner=&player;}
+};
+int main(){
+ Fixture base;base.Seal();assert(!base.intel.Seal(&base.engineer,&base.mine,base.mine.position,base.candidates));base.Transfer();int reads=base.mine.reads;int actorReads=base.engineer.reads;base.mine.alive=false;base.mine.position=Vector(9000,0,9000);base.intel.Recover(&base.player,&base.folder);assert(base.mode.m_ERRMineAreas.Count()==1&&base.mode.m_ERRMineAreas[0]==Vector(1250,0,2250));assert(base.mine.reads==reads&&base.engineer.reads==actorReads);base.intel.Recover(&base.player,&base.folder);assert(base.mode.m_ERRMineAreas.Count()==1);
+ // Original site content is never modified or looked up by the evidence service.
+ // An objective, foreign/spent folder or a newly issued folder cannot be sealed.
+ for(int fault=0;fault<6;fault++){Fixture f;f.Capture();Entity newFolder;newFolder.owner=&f.engineer;
+ switch(fault){case 0:f.folder.objective=true;break;case 1:f.folder.owner=&f.player;break;case 2:f.mode.discovery.spent.Insert(&f.folder);break;case 3:f.ei.folders.Clear();f.ei.folders.Insert(&newFolder);break;case 4:f.engineer.faction=2;break;case 5:f.engineer.player=true;break;}
+ assert(!f.intel.Seal(&f.engineer,&f.mine,f.mine.position,f.candidates)&&f.intel.m_Records.empty()&&f.mode.m_ERRMineAreas.empty());}
+ // Unarmed, moved, destroyed, attached and unrelated mines cannot create records.
+ for(int fault=0;fault<5;fault++){Fixture f;f.Capture();vector position=f.mine.position;switch(fault){case 0:f.mine.trigger.active=false;break;case 1:f.mine.position=Vector(9999,0,0);break;case 2:f.mine.alive=false;break;case 3:f.mine.item.slot=&f.ei;break;case 4:f.mine.prefab.name="other";break;}assert(!f.intel.Seal(&f.engineer,&f.mine,position,f.candidates));}
+ for(int fault=0;fault<8;fault++){Fixture f;f.Seal();f.Transfer();ERR_GameMode newer;Entity other;switch(fault){case 0:f.mode.players.current=&other;break;case 1:f.player.alive=false;break;case 2:f.player.faction=1;break;case 3:f.folder.owner=&other;break;case 4:f.pi.folders.Clear();break;case 5:f.mode.discovery.spent.Insert(&f.folder);break;case 6:ERR_GameMode::current=&newer;break;case 7:f.intel.Stop();break;}f.intel.Recover(&f.player,&f.folder);assert(f.mode.m_ERRMineAreas.empty());}
+ Fixture caps;caps.Capture();caps.intel.m_Bindings=128;assert(!caps.intel.Seal(&caps.engineer,&caps.mine,caps.mine.position,caps.candidates));caps.intel.m_Bindings=0;caps.intel.m_Records.resize(32,nullptr);assert(!caps.intel.Seal(&caps.engineer,&caps.mine,caps.mine.position,caps.candidates));
+ Fixture publish;for(int i=0;i<32;i++)assert(publish.mode.ERR_RevealMineArea(Vector(i*500,0,0)));assert(publish.mode.ERR_RevealMineArea(Vector(10,0,20)));assert(!publish.mode.ERR_RevealMineArea(Vector(32000,0,0)));assert(!publish.mode.ERR_RevealMineArea(Vector(NAN,0,0)));
+ // Failed publication must remain recoverable instead of consuming warning.
+ Fixture retry;retry.Seal();retry.Transfer();for(int i=0;i<32;i++)retry.mode.m_ERRMineAreas.Insert(Vector(i*500+250,0,250));retry.intel.Recover(&retry.player,&retry.folder);assert(!retry.intel.m_Records[0]->Revealed);retry.mode.m_ERRMineAreas.Clear();retry.intel.Recover(&retry.player,&retry.folder);assert(retry.intel.m_Records[0]->Revealed);
+ // Marker polling waits through preparation/JIP and missing native dependencies.
+ Fixture markers;markers.mode.m_ERRMineAreas.Insert(Vector(1250,0,2250));markers.mode.active=false;markers.mode.ERR_UpdateMineMarkers();assert(!markers.mode.m_ERRMineIntelStopped);markers.mode.active=true;markers.mode.ERR_UpdateMineMarkers();assert(markers.mode.m_ERRMineMarkers.empty());SCR_MapMarkerManagerComponent first,second;SCR_MapMarkerManagerComponent::current=&first;first.config=false;markers.mode.ERR_UpdateMineMarkers();assert(first.inserted.empty());first.config=true;game.controller=false;markers.mode.ERR_UpdateMineMarkers();assert(first.inserted.empty());game.controller=true;markers.mode.ERR_UpdateMineMarkers();markers.mode.ERR_UpdateMineMarkers();assert(first.inserted.Count()==1&&first.inserted[0]->position==Vector(1250,0,2250));assert(first.inserted[0]->label=="Reported mine area (500 m); may still be active");
+ SCR_MapMarkerManagerComponent::current=&second;markers.mode.ERR_UpdateMineMarkers();assert(first.removed.Count()==1&&second.inserted.Count()==1);markers.mode.active=false;markers.mode.ERR_UpdateMineMarkers();assert(second.removed.Count()==1);markers.mode.active=true;markers.mode.ERR_UpdateMineMarkers();assert(second.inserted.Count()==2);markers.mode.ERR_StopMineIntel();markers.mode.ERR_StopMineIntel();assert(markers.intel.m_Stopped&&markers.mode.m_ERRMineMarkers.empty()&&markers.mode.m_ERRMineAreas.empty()&&second.removed.Count()==2);markers.mode.ERR_UpdateMineMarkers();assert(second.inserted.Count()==2);
+ // Shrink rebuild must retain manager ownership for replacement marker cleanup.
+ Fixture shrink;SCR_MapMarkerManagerComponent shrinking;SCR_MapMarkerManagerComponent::current=&shrinking;shrink.mode.m_ERRMineAreas.Insert(Vector(250,0,250));shrink.mode.m_ERRMineAreas.Insert(Vector(750,0,250));shrink.mode.ERR_UpdateMineMarkers();assert(shrinking.inserted.Count()==2);shrink.mode.m_ERRMineAreas.resize(1);shrink.mode.ERR_UpdateMineMarkers();assert(shrinking.removed.Count()==2&&shrinking.inserted.Count()==3&&shrink.mode.m_ERRMineMarkerOwner==&shrinking);shrink.mode.ERR_StopMineIntel();assert(shrinking.removed.Count()==3);
+ Fixture destroyed;SCR_MapMarkerManagerComponent deletion;SCR_MapMarkerManagerComponent::current=&deletion;destroyed.mode.m_ERRMineAreas.Insert(Vector(250,0,250));destroyed.mode.ERR_UpdateMineMarkers();int removedCallbacks=game.queue.removed;destroyed.mode.Destroy();assert(destroyed.intel.m_Stopped&&destroyed.mode.m_ERRMineMarkers.empty()&&deletion.removed.Count()==1&&game.queue.removed==removedCallbacks+1);
+ Fixture bindings;Array<Entity> moreFolders;moreFolders.resize(16);bindings.ei.folders.Clear();for(auto&f:moreFolders){f.owner=&bindings.engineer;bindings.ei.folders.Insert(&f);}bindings.intel.Candidates(&bindings.engineer,bindings.candidates);assert(bindings.candidates.Count()==16);Array<Entity> mines;mines.resize(9);for(int i=0;i<9;i++){mines[i].prefab.name="mine";mines[i].position=Vector(30,0,40);bool sealed=bindings.intel.Seal(&bindings.engineer,&mines[i],mines[i].position,bindings.candidates);assert(sealed==(i<8));}assert(bindings.intel.m_Bindings==128&&bindings.intel.m_Records.Count()==8);
+ Fixture replica;Replication::server=false;assert(!replica.mode.ERR_RevealMineArea(Vector(1000,0,2000)));replica.intel.Candidates(&replica.engineer,replica.candidates);assert(replica.candidates.empty());
+}
+''')
+print('PASS: actual mine evidence sealing/recovery, historical coarse publication, caps, canonical ownership, marker join retry/owner-change/stop; native arming and replicated vector-array transport untested')