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:
@@ -0,0 +1 @@
|
||||
PASS: actual engineer native group-envelope admission and phase cancellation, preparation/deadline, original custody/identity, once-only callback removal, cancellation quarantine, withdrawal/clearance, native refusal and owned-action cleanup; native execution untested
|
||||
@@ -0,0 +1,81 @@
|
||||
// Mission-owned finite attempt history. Registration and assignment release
|
||||
// never reset it. Values are copied before native work; no live enemy queries.
|
||||
class ERR_EngineerMinePost
|
||||
{
|
||||
ref ERR_ForceRecord Record;
|
||||
SCR_AIGroup Group;
|
||||
string Source;
|
||||
int Strength;
|
||||
ref ERR_HQTask Task;
|
||||
ref ERR_HQReport Assignment;
|
||||
int MessageId;
|
||||
int BeliefId;
|
||||
int Revision;
|
||||
float QueuedAt;
|
||||
float ObservedAt;
|
||||
vector Post;
|
||||
bool HasSite;
|
||||
vector Site;
|
||||
}
|
||||
class ERR_EngineerMinePosts
|
||||
{
|
||||
protected ref array<ref ERR_EngineerMinePost> m_Posts = {};
|
||||
int Count() { return m_Posts.Count(); }
|
||||
bool Eligible(ERR_ForceRecord record, string source, ERR_HQTask task, ERR_HQReport assignment, float now)
|
||||
{
|
||||
if (!record || !record.Group || !record.Physical || record.Eliminated || source.IsEmpty() || record.SourceId != source) return false;
|
||||
if (record.InitialStrength < 1 || record.InitialStrength > 32 || !task || !assignment || m_Posts.Count() >= 32) return false;
|
||||
if (!task.RoadIntercept || task.ReleaseOnly || task.Investigation() || !task.SecuritySite.IsEmpty()) return false;
|
||||
if (task.BeliefId <= 0 || task.BeliefRevision <= 0 || assignment.MessageId <= 0) return false;
|
||||
if (!ERR_ForceRules.Finite(now) || !ERR_ForceRules.Finite(task.QueuedAt) || !ERR_ForceRules.Finite(task.ExpiresAt)
|
||||
|| !ERR_ForceRules.Finite(assignment.ObservedAt) || assignment.ObservedAt < 0 || task.QueuedAt < assignment.ObservedAt
|
||||
|| task.QueuedAt > now || now >= task.ExpiresAt) return false;
|
||||
if (!ERR_ForceRules.FiniteVector(task.Destination) || !ERR_ForceRules.FiniteVector(assignment.Position)) return false;
|
||||
int used;
|
||||
foreach (ERR_EngineerMinePost previous : m_Posts)
|
||||
{
|
||||
if (!previous) return false;
|
||||
float separation = vector.DistanceXZ(task.Destination, previous.Post);
|
||||
if (!ERR_ForceRules.Finite(separation) || separation <= 160) return false;
|
||||
if (previous.Task == task || previous.Assignment == assignment) return false;
|
||||
// Same source or physical group under another record cannot acquire quota.
|
||||
if (previous.Record != record)
|
||||
{
|
||||
if (previous.Source == source || previous.Group == record.Group) return false;
|
||||
continue;
|
||||
}
|
||||
if (previous.Source != source || previous.Group != record.Group || previous.Strength != record.InitialStrength) return false;
|
||||
used++;
|
||||
if (used >= 5 || task.QueuedAt <= previous.QueuedAt || assignment.ObservedAt <= previous.ObservedAt
|
||||
|| assignment.MessageId == previous.MessageId) return false;
|
||||
if (task.BeliefId == previous.BeliefId && task.BeliefRevision <= previous.Revision) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Returns immutable entry index or -1; a refused site/item still consumes it.
|
||||
int Consume(ERR_ForceRecord record, string source, ERR_HQTask task, ERR_HQReport assignment, float now)
|
||||
{
|
||||
if (!Eligible(record, source, task, assignment, now)) return -1;
|
||||
ERR_EngineerMinePost entry = new ERR_EngineerMinePost();
|
||||
entry.Record = record; entry.Group = record.Group; entry.Source = source; entry.Strength = record.InitialStrength;
|
||||
entry.Task = task; entry.Assignment = assignment; entry.MessageId = assignment.MessageId;
|
||||
entry.BeliefId = task.BeliefId; entry.Revision = task.BeliefRevision;
|
||||
entry.QueuedAt = task.QueuedAt; entry.ObservedAt = assignment.ObservedAt; entry.Post = task.Destination;
|
||||
m_Posts.Insert(entry);
|
||||
return m_Posts.Count() - 1;
|
||||
}
|
||||
bool RecordSite(int index, vector site)
|
||||
{
|
||||
if (index < 0 || index >= m_Posts.Count() || !ERR_ForceRules.FiniteVector(site)) return false;
|
||||
ERR_EngineerMinePost entry = m_Posts[index];
|
||||
if (!entry || entry.HasSite || !ERR_EngineerMineSite.InBand(entry.Post, site)) return false;
|
||||
entry.Site = site; entry.HasSite = true;
|
||||
return true;
|
||||
}
|
||||
void PriorSites(out array<vector> sites)
|
||||
{
|
||||
sites.Clear();
|
||||
foreach (ERR_EngineerMinePost entry : m_Posts)
|
||||
if (entry.HasSite) sites.Insert(entry.Site);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
--- a/tests/test_engineer_mine.py
|
||||
+++ b/tests/test_engineer_mine.py
|
||||
@@ -9,7 +9,9 @@
|
||||
s=s.replace('protected override ','').replace('protected ','').replace('null','nullptr').replace('ref ','')
|
||||
s=s.replace('array<','Array<').replace(' = {};',';').replace('foreach (','for (')
|
||||
s=s.replace('array<IEntity> attempted','Array<IEntity>* attempted')
|
||||
- for typ in ['SCR_AIGroupUtilityComponent','ERR_GameMode','ERR_HQUnit','ERR_HQTask','SCR_ChimeraAIAgent','SCR_InventoryStorageManagerComponent','SCR_PressureTriggerComponent','InventoryItemComponent','DamageManagerComponent','FactionAffiliationComponent','Faction','Physics']:
|
||||
+ s=s.replace('Array<vector> priorSites','Array<vector>* priorSites').replace('priorSites.','priorSites->').replace(': priorSites)',': *priorSites)')
|
||||
+ s=s.replace(', m_PriorSites)',', &m_PriorSites)').replace('Math.Clamp','Math::Clamp')
|
||||
+ for typ in ['ERR_ForceRecord','SCR_AIGroupUtilityComponent','ERR_GameMode','ERR_HQUnit','ERR_HQTask','SCR_ChimeraAIAgent','SCR_InventoryStorageManagerComponent','SCR_PressureTriggerComponent','InventoryItemComponent','DamageManagerComponent','FactionAffiliationComponent','Faction','Physics']:
|
||||
s=re.sub(r'\b'+typ+r' (\w+)',typ+r'* \1',s)
|
||||
for ns in ['ERR_ForceRules','Replication','ERR_GameMode','ERR_HunterCombat','ERR_Actors','CompartmentAccessComponent','SCR_FactionManager','SCR_ChimeraAIAgent','SCR_AIUtils','ERR_EnemyLoadoutCatalog','ERR_Inventory','ERR_InfantryAmmoTransfer','InventoryItemComponent','DamageManagerComponent','FactionAffiliationComponent','ChimeraCharacter','Vehicle','SCR_InventoryStorageManagerComponent','SCR_PressureTriggerComponent','ERR_EngineerMineRules','ERR_HQ','vector']:
|
||||
s=s.replace(ns+'.',ns+'::')
|
||||
@@ -25,9 +27,9 @@
|
||||
s=s.replace('ERR_EngineerMine(Array<IEntity> attempted)','ERR_EngineerMine(Array<IEntity>* attempted)')
|
||||
s=s.replace('Faction* faction;','Faction* faction=nullptr;').replace('Array<AIActionBase>', 'Array<AIActionBase*>').replace('void ERR_EngineerMine(', 'ERR_EngineerMine(')
|
||||
return s
|
||||
-policies='\n'.join(body(P,x) for x in ['static bool Within(','static float PrepareSince(','static bool Prepared(','static bool MayArm('])
|
||||
+policies='\n'.join(body(P,x) for x in ['static bool CorridorClear(','static bool WithinWorkArea(','static bool Within(','static float PrepareSince(','static bool Prepared(','static bool MayArm('])
|
||||
callback='\n'.join(body(P,x) for x in ['protected void Finish(','protected override void OnComplete(','protected override void OnAllComplete(','protected override void OnFailed(','protected override void OnAllFailed('])
|
||||
-methods='\n'.join(body(P,x) for x in ['void ERR_EngineerMine(','bool Finished(','protected bool Refuse(','protected bool Quiet(','protected bool Owned(','protected bool MineValid(','protected bool Carried(','protected bool Grounded(','protected bool Placed(','protected void ClearAction(','protected void Move(','protected void WaitAtMine(','protected bool CheckFriendly(','protected bool SelectActor(','bool Begin(','void Stop(','void Tick('])
|
||||
+methods='\n'.join(body(P,x) for x in ['void ERR_EngineerMine(','bool Finished(','bool BlocksNextPost(','protected bool Refuse(','protected bool Quiet(','protected bool Owned(','protected bool MineValid(','protected bool Carried(','protected bool Grounded(','protected bool Placed(','protected void ClearAction(','protected void Move(','protected void WaitAtMine(','protected bool CheckFriendly(','protected bool SelectActor(','bool Begin(','void Stop(','void Tick('])
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
@@ -36,10 +38,11 @@
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
struct string:std::string {using std::string::string;using std::string::operator=;bool IsEmpty(){return empty();}};
|
||||
-struct vector {float x=0,y=0,z=0;float LengthSq(){return x*x+y*y+z*z;}
|
||||
+struct vector {float x=0,y=0,z=0;float operator[](int i){return i==0?x:i==1?y:z;}float LengthSq(){return x*x+y*y+z*z;}
|
||||
static float Distance(vector a,vector b){return std::sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z));}
|
||||
static float DistanceXZ(vector a,vector b){a.y=b.y=0;return Distance(a,b);}};
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}bool Contains(T t){return std::find(this->begin(),this->end(),t)!=this->end();}void Insert(T t){this->push_back(t);}};
|
||||
+namespace Math{float Clamp(float v,float a,float b){return std::max(a,std::min(v,b));}}
|
||||
struct Entity;using IEntity=Entity*;struct SCR_AIGroup;struct SCR_ChimeraAIAgent;struct SCR_InventoryStorageManagerComponent;struct ERR_EngineerMineRemoval;
|
||||
struct Faction {bool enemy=false;bool IsFactionEnemy(Faction* f){return f&&f->enemy;}};
|
||||
struct Physics {vector velocity;vector GetVelocity(){return velocity;}};
|
||||
@@ -101,7 +104,7 @@
|
||||
'''+cpp(callback)+r'''};
|
||||
bool SCR_InventoryStorageManagerComponent::TryRemoveItemFromInventory(IEntity,void*,ERR_EngineerMineRemoval* cb){calls++;callback=cb;if(inlineResult>0)cb->OnComplete();if(inlineResult<0)cb->OnFailed();return accepted;}
|
||||
struct ERR_EngineerMine {
|
||||
- ERR_GameMode* m_Mode=nullptr;ERR_HQUnit* m_Unit=nullptr;SCR_AIGroup* m_Group=nullptr;ERR_HQTask* m_Task=nullptr;ERR_HQReport* m_Assignment=nullptr;ERR_ForceRecord* m_Record=nullptr;string m_SourceId;int m_Strength=0;IEntity m_Actor=nullptr;int m_Slot=0;IEntity m_Mine=nullptr;SCR_PressureTriggerComponent* m_Trigger=nullptr;SCR_InventoryStorageManagerComponent* m_Inventory=nullptr;SCR_AIUtilityComponent* m_Utility=nullptr;SCR_AIBehaviorBase* m_Action=nullptr;ERR_EngineerMineRemoval* m_Removal=nullptr;Array<IEntity>* m_Attempted=nullptr;Array<IEntity> m_IntelFolders;vector m_Position,m_Rally;float m_Started=-1,m_LastTick=-1,m_PreparedAt=-1,m_NextClearance=0;int m_State=0,m_Checked=0;bool m_FriendlyNear=false,m_Begun=false,m_Stopped=false;
|
||||
+ ERR_GameMode* m_Mode=nullptr;ERR_HQUnit* m_Unit=nullptr;SCR_AIGroup* m_Group=nullptr;ERR_HQTask* m_Task=nullptr;ERR_HQReport* m_Assignment=nullptr;ERR_ForceRecord* m_Record=nullptr;string m_SourceId;int m_Strength=0;IEntity m_Actor=nullptr;int m_Slot=0;IEntity m_Mine=nullptr;SCR_PressureTriggerComponent* m_Trigger=nullptr;SCR_InventoryStorageManagerComponent* m_Inventory=nullptr;SCR_AIUtilityComponent* m_Utility=nullptr;SCR_AIBehaviorBase* m_Action=nullptr;ERR_EngineerMineRemoval* m_Removal=nullptr;Array<IEntity>* m_Attempted=nullptr;Array<IEntity> m_IntelFolders;Array<vector> m_PriorSites;vector m_Position,m_Rally;float m_Started=-1,m_LastTick=-1,m_PreparedAt=-1,m_NextClearance=0;int m_State=0,m_Checked=0;bool m_FriendlyNear=false,m_Begun=false,m_Stopped=false;
|
||||
'''+cpp(methods)+r'''};
|
||||
struct Fixture {Faction faction,hostile{true};SCR_AIGroup group;SCR_AIGroupUtilityComponent groupUtility;SCR_AIUtilityComponent utility;SCR_ChimeraAIAgent agent;SCR_InventoryStorageManagerComponent inventory;Entity actor,mine;Prefab prefab;Physics physics;InventoryItemComponent item;SCR_PressureTriggerComponent trigger;Sampler roster;ERR_HQTask task;ERR_HQReport report;ERR_ForceRecord record;Ledger ledger;ERR_GameMode mode;ERR_HQUnit unit;Array<IEntity> attempted;ERR_EngineerMine controller{&attempted};
|
||||
Fixture(){ERR_GameMode::current=&mode;Replication::server=true;ERR_HunterCombat::engaged=false;currentGame=&game;game.world.query.clear();game.world.queries=0;
|
||||
@@ -114,6 +117,22 @@
|
||||
void Withdraw(){Prepare();Drop();inventory.callback->OnComplete();controller.Tick(12001);assert(controller.m_State==3&&mine.moves==1&&utility.additions==3);}
|
||||
};
|
||||
int main(){
|
||||
+ // History geometry uses bounded detached coordinates, not live mine entities.
|
||||
+ Array<vector> hazards;hazards.Insert({20,0,20});
|
||||
+ assert(!ERR_EngineerMineRules::CorridorClear({0,0,0},{40,0,0},&hazards));hazards[0].z=20.001f;
|
||||
+ assert(ERR_EngineerMineRules::CorridorClear({0,0,0},{40,0,0},&hazards));
|
||||
+ hazards[0]={20,0,0};assert(!ERR_EngineerMineRules::CorridorClear({0,0,0},{0,0,0},&hazards));
|
||||
+ assert(ERR_EngineerMineRules::WithinWorkArea({0,0,0},{60,100,0}));assert(!ERR_EngineerMineRules::WithinWorkArea({0,0,0},{60.001f,0,0}));
|
||||
+ for(int invalid=0;invalid<3;invalid++){Fixture f;Array<vector> prior;if(invalid==0)prior.Insert({20,0,5});if(invalid==1)prior.resize(33);if(invalid==2)prior.Insert({NAN,0,0});f.actor.position={-20,0,0};assert(!f.controller.Begin(&f.mode,&f.unit,&f.task,{60,0,0},1000,&prior));assert(f.attempted.empty()&&!f.utility.additions&&!f.inventory.calls);}
|
||||
+ {Fixture f;Array<vector> prior;prior.Insert({100,0,0});assert(f.controller.Begin(&f.mode,&f.unit,&f.task,{30,0,0},1000,&prior));prior[0]={30,0,0};f.controller.Tick(2000);assert(!f.controller.Finished()&&f.controller.m_PriorSites[0].x==100);f.actor.position={60,0,0};f.controller.Tick(3000);assert(!f.controller.Finished());auto action=f.controller.m_Action;f.actor.position={60.001f,0,0};f.controller.Tick(4000);assert(f.controller.Finished()&&action->completed==1&&!f.inventory.calls);}
|
||||
+ {Fixture f;Array<vector> prior;prior.Insert({50,0,30});assert(f.controller.Begin(&f.mode,&f.unit,&f.task,{30,0,0},1000,&prior));f.actor.position={50,0,11};f.controller.Tick(2000);assert(f.controller.Finished()&&!f.inventory.calls&&!f.trigger.calls);}
|
||||
+ // Active job blocks its exact original record. Stopped pending native removal
|
||||
+ // remains blocking even if its surviving item is destroyed; callbacks only settle.
|
||||
+ {Fixture f;ERR_ForceRecord other;f.Begin();assert(f.controller.BlocksNextPost(&f.record)&&!f.controller.BlocksNextPost(&other)&&!f.controller.BlocksNextPost(nullptr));f.controller.Stop();assert(!f.controller.BlocksNextPost(&f.record));}
|
||||
+ for(int completion=0;completion<3;completion++){Fixture f;f.Prepare();f.controller.Stop();auto callback=f.inventory.callback;assert(f.controller.BlocksNextPost(&f.record));f.mine.alive=false;assert(f.controller.BlocksNextPost(&f.record));if(completion==0)callback->OnComplete();if(completion==1)callback->OnFailed();if(completion==2)f.controller.m_Mine=nullptr;assert(!f.controller.BlocksNextPost(&f.record));f.Drop();callback->OnComplete();f.controller.Tick(14000);assert(f.inventory.calls==1&&!f.mine.moves&&!f.trigger.calls&&f.attempted.Count()==1);}
|
||||
+ // An exact different physical mine can be used at a distinct post, while the
|
||||
+ // first exact entity remains in the shared lifetime tombstone.
|
||||
+ {Fixture f;f.Begin();f.controller.Stop();Entity second=f.mine;SCR_PressureTriggerComponent secondTrigger;InventoryItemComponent secondItem;second.trigger=&secondTrigger;second.item=&secondItem;secondItem.slot=&f.inventory;f.inventory.items={&f.mine,&second};ERR_HQTask next;ERR_HQReport nextReport;next.Destination={200,0,0};f.unit.Task=&next;f.unit.Assignment=&nextReport;f.actor.position={200,0,0};f.group.center={200,0,0};Array<vector> prior;prior.Insert({30,0,0});ERR_EngineerMine job(&f.attempted);assert(job.Begin(&f.mode,&f.unit,&next,{230,0,0},2000,&prior));assert(job.m_Mine==&second&&f.attempted.Count()==2&&f.attempted[0]==&f.mine);}
|
||||
// Current native defend radius is strict and three dimensional: the broad
|
||||
// 25..60 m mission band does not authorize bypassing the group's own envelope.
|
||||
for(int fault=0;fault<4;fault++){Fixture f;SCR_DefendWaypoint defend;f.group.waypoint=&defend;vector point={30,0,0};
|
||||
--- a/tests/test_engineer_mine_hq.py
|
||||
+++ b/tests/test_engineer_mine_hq.py
|
||||
@@ -1,80 +1,56 @@
|
||||
-"""Execute actual HQ engineer admission/maintenance; native movement is separate."""
|
||||
+"""Actual HQ admission with actual finite post ledger; native jobs are boundary doubles."""
|
||||
from pathlib import Path
|
||||
import os,re
|
||||
from policy_harness import ROOT,body,run
|
||||
+from test_engineer_mine_posts import BOUNDARY,ledger_cpp
|
||||
p=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'))
|
||||
s=body(p,'protected void MaintainEngineerMines(')
|
||||
-s=s.replace('protected ','').replace('int active;','int active=0;').replace('vector position;','vector position=0;')
|
||||
-s=s.replace('foreach (ERR_HQUnit unit : m_Units)','for (ERR_HQUnit* unit : m_Units)').replace('foreach (ERR_EngineerMine job : m_EngineerMineJobs)','for (ERR_EngineerMine* job : m_EngineerMineJobs)')
|
||||
-for name in ('Replication','vector','ERR_EngineerMineSite','ERR_GameMode'):s=s.replace(name+'.',name+'::')
|
||||
-for name in ('unit','m_Mode','job'):s=s.replace(name+'.',name+'->')
|
||||
-s=s.replace('->EngineerMine.','->EngineerMine->').replace('->Task.','->Task->').replace('->Group.','->Group->')
|
||||
-s=s.replace('vector::DistanceXZ','DistanceXZ')
|
||||
-run(r'''
|
||||
-#include <cassert>
|
||||
-#include <vector>
|
||||
-#include <cmath>
|
||||
-using vector=float;
|
||||
-float DistanceXZ(float a,float b){return std::fabs(a-b);}
|
||||
-struct Replication{static bool server;static bool IsServer(){return server;}};bool Replication::server=true;
|
||||
-struct ERR_GameMode{static ERR_GameMode* current;static ERR_GameMode* Get(){return current;}bool active=true;bool Active(){return active;}};ERR_GameMode* ERR_GameMode::current=nullptr;
|
||||
-struct Task{bool RoadIntercept=true;float ExpiresAt=100000,Destination=0;};
|
||||
-struct Group{float origin=0;float GetOrigin(){return origin;}};
|
||||
-struct AttemptArray:std::vector<int>{int Count(){return size();}};
|
||||
+s=s.replace('protected ','').replace('int active;','int active=0;').replace('bool blocked;','bool blocked=false;').replace('vector position;','vector position;')
|
||||
+s=s.replace('array<vector> priorSites = {};','Array<vector> priorSites;')
|
||||
+for typ in ['ERR_HQUnit','ERR_EngineerMine','ERR_ForceRecord']:s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
||||
+s=s.replace('foreach (','for (')
|
||||
+for name in ('ERR_ForceRules','Replication','vector','ERR_EngineerMineSite','ERR_GameMode'):s=s.replace(name+'.',name+'::')
|
||||
+for name in ('unit','m_Mode','job','previous','record'):s=re.sub(r'\b'+name+r'\.',name+'->',s)
|
||||
+for name in ['EngineerMine','Task','Group','StatusSampler']:s=s.replace('->'+name+'.','->'+name+'->')
|
||||
+s=s.replace('Forces().','Forces()->')
|
||||
+run(BOUNDARY+ledger_cpp()+r'''
|
||||
+struct Ledger{Array<ERR_ForceRecord*> records;ERR_ForceRecord* FindGroup(SCR_AIGroup* group){for(auto r:records)if(r->Group==group)return r;return nullptr;}};
|
||||
+struct Replication{static inline bool server=true;static bool IsServer(){return server;}};
|
||||
+struct ERR_GameMode{static inline ERR_GameMode* current=nullptr;static ERR_GameMode* Get(){return current;}bool active=true;Ledger ledger;bool Active(){return active;}Ledger* Forces(){return &ledger;}};
|
||||
+using AttemptArray=Array<int>;
|
||||
+struct Sampler{bool owns=true;int strength=2;bool OwnsRoster(SCR_AIGroup*){return owns;}int InitialStrength(){return strength;}};
|
||||
struct ERR_HQUnit;
|
||||
-struct ERR_EngineerMine {
|
||||
- int ticks=0,stops=0;bool finished=false;
|
||||
- AttemptArray& attempts;
|
||||
- ERR_EngineerMine(AttemptArray& a):attempts(a){}
|
||||
- bool Begin(ERR_GameMode*,ERR_HQUnit*,Task*,float,float);
|
||||
- void Tick(float){ticks++;}bool Finished(){return finished;}void Stop(){stops++;finished=true;}
|
||||
-};
|
||||
-struct ERR_HQUnit{
|
||||
- ERR_EngineerMine* EngineerMine=nullptr;bool EngineerMineAttempted=false;
|
||||
- struct Task* Task=nullptr;void* Assignment=nullptr;void* PendingTask=nullptr;void* PendingOrder=nullptr;void* Downlink=nullptr;
|
||||
- struct Group* Group=nullptr;bool eligible=true,fighting=false,begin=true;
|
||||
- bool Eligible(ERR_GameMode*){return eligible;}
|
||||
-};
|
||||
-bool ERR_EngineerMine::Begin(ERR_GameMode*,ERR_HQUnit* unit,struct Task*,float,float){attempts.push_back(1);return unit->begin;}
|
||||
-struct ERR_EngineerMineSite{static bool valid;static int calls;static bool Select(float,float& p){calls++;p=30;return valid;}};
|
||||
-bool ERR_EngineerMineSite::valid=true;int ERR_EngineerMineSite::calls=0;
|
||||
-struct Jobs:std::vector<ERR_EngineerMine*>{int Count(){return size();}void Insert(ERR_EngineerMine* j){push_back(j);}};
|
||||
-struct HQ{
|
||||
- Jobs m_EngineerMineJobs;
|
||||
- bool m_Stopping=false;ERR_GameMode* m_Mode=nullptr;AttemptArray m_EngineerMineAttempts;std::vector<ERR_HQUnit*> m_Units;
|
||||
- bool Fighting(ERR_HQUnit* unit){return unit->fighting;}
|
||||
-'''+s+r'''
|
||||
-};
|
||||
-struct Fixture{
|
||||
- ERR_GameMode mode;HQ h;Task tasks[4];Group groups[4];ERR_HQUnit units[4];int report;
|
||||
- Fixture(){ERR_GameMode::current=&mode;h.m_Mode=&mode;for(int i=0;i<4;i++){units[i].Task=&tasks[i];units[i].Group=&groups[i];units[i].Assignment=&report;h.m_Units.push_back(&units[i]);}}
|
||||
+struct ERR_EngineerMine{int ticks=0,stops=0;bool finished=false,unresolved=false;ERR_ForceRecord* record=nullptr;Array<vector> prior;AttemptArray& attempts;
|
||||
+ ERR_EngineerMine(AttemptArray& a):attempts(a){} bool Begin(ERR_GameMode*,ERR_HQUnit*,ERR_HQTask*,vector,float,Array<vector> sites={});
|
||||
+ void Tick(float){ticks++;}bool Finished(){return finished;}void Stop(){stops++;finished=true;}bool BlocksNextPost(ERR_ForceRecord* r){return r==record&&(!finished||unresolved);}};
|
||||
+struct ERR_HQUnit{ERR_EngineerMine* EngineerMine=nullptr;bool EngineerMineAttempted=false;ERR_HQTask* Task=nullptr;ERR_HQReport* Assignment=nullptr;void* PendingTask=nullptr;void* PendingOrder=nullptr;void* Downlink=nullptr;SCR_AIGroup* Group=nullptr;Sampler* StatusSampler=nullptr;string ForceSourceId;bool eligible=true,fighting=false,begin=true;bool Eligible(ERR_GameMode*){return eligible;}};
|
||||
+bool ERR_EngineerMine::Begin(ERR_GameMode* mode,ERR_HQUnit*unit,ERR_HQTask*,vector,float,Array<vector> sites){record=mode->Forces()->FindGroup(unit->Group);prior=sites;if(unit->begin)attempts.Insert(1);return unit->begin;}
|
||||
+struct HQ{Array<ERR_EngineerMine*> m_EngineerMineJobs;ERR_EngineerMinePosts m_EngineerMinePosts;bool m_Stopping=false;ERR_GameMode* m_Mode=nullptr;AttemptArray m_EngineerMineAttempts;Array<ERR_HQUnit*> m_Units;bool Fighting(ERR_HQUnit*u){return u->fighting;}
|
||||
+'''+s+r'''};
|
||||
+struct Fixture{ERR_GameMode mode;HQ h;ERR_HQTask tasks[40];ERR_HQReport reports[40];SCR_AIGroup groups[4];ERR_ForceRecord records[4];Sampler samplers[4];ERR_HQUnit units[4];
|
||||
+ Fixture(){ERR_GameMode::current=&mode;h.m_Mode=&mode;ERR_EngineerMineSite::valid=true;for(int i=0;i<40;i++){tasks[i].Destination={float(i*1000),0,0};tasks[i].QueuedAt=10+i*10;tasks[i].BeliefRevision=i+1;reports[i].ObservedAt=5+i*10;reports[i].MessageId=i+1;}for(int i=0;i<4;i++){records[i].Group=&groups[i];records[i].SourceId=std::to_string(i);units[i].ForceSourceId=records[i].SourceId;units[i].Group=&groups[i];units[i].StatusSampler=&samplers[i];mode.ledger.records.Insert(&records[i]);NewPost(i,i);h.m_Units.Insert(&units[i]);}}
|
||||
+ void NewPost(int u,int n){units[u].Task=&tasks[n];units[u].Assignment=&reports[n];groups[u].position=tasks[n].Destination;}
|
||||
+ void Tick(){h.MaintainEngineerMines(1000);}
|
||||
~Fixture(){for(auto j:h.m_EngineerMineJobs)delete j;}
|
||||
};
|
||||
int main(){
|
||||
- {Fixture f;f.h.MaintainEngineerMines(1);assert(f.h.m_EngineerMineAttempts.Count()==1);assert(!f.units[1].EngineerMineAttempted);
|
||||
- f.h.MaintainEngineerMines(2);assert(f.units[0].EngineerMine->ticks==1&&f.h.m_EngineerMineAttempts.Count()==2);
|
||||
- f.h.MaintainEngineerMines(3);assert(!f.units[2].EngineerMineAttempted);
|
||||
- f.units[0].EngineerMine->Stop();f.h.MaintainEngineerMines(4);assert(f.units[2].EngineerMineAttempted&&!f.units[3].EngineerMineAttempted);assert(f.h.m_EngineerMineAttempts.Count()==3);
|
||||
- f.units[1].EngineerMine->Stop();f.units[2].EngineerMine->Stop();f.h.MaintainEngineerMines(5);f.units[3].EngineerMine->Stop();f.h.MaintainEngineerMines(6);assert(f.h.m_EngineerMineAttempts.Count()==4);}
|
||||
- {Fixture f;f.h.MaintainEngineerMines(1);auto job=f.units[0].EngineerMine;job->Stop();f.h.m_Units.clear();f.h.MaintainEngineerMines(2);assert(f.h.m_EngineerMineJobs.Count()==1&&job->ticks==1);}
|
||||
- {Fixture f;for(int i=0;i<32;i++){auto job=new ERR_EngineerMine(f.h.m_EngineerMineAttempts);job->Stop();f.h.m_EngineerMineJobs.Insert(job);}int before=ERR_EngineerMineSite::calls;f.h.MaintainEngineerMines(1);assert(!f.units[0].EngineerMineAttempted&&ERR_EngineerMineSite::calls==before);}
|
||||
- {Fixture f;for(auto& u:f.units)u.begin=false;int before=ERR_EngineerMineSite::calls;f.h.MaintainEngineerMines(1);assert(ERR_EngineerMineSite::calls==before+1&&!f.units[1].EngineerMineAttempted);f.h.MaintainEngineerMines(2);assert(ERR_EngineerMineSite::calls==before+2&&f.units[0].EngineerMine->ticks==1);}
|
||||
- {Fixture f;ERR_EngineerMineSite::valid=false;int before=ERR_EngineerMineSite::calls;f.h.MaintainEngineerMines(1);assert(ERR_EngineerMineSite::calls==before+1&&!f.units[1].EngineerMineAttempted);f.h.MaintainEngineerMines(2);assert(ERR_EngineerMineSite::calls==before+2);ERR_EngineerMineSite::valid=true;}
|
||||
- for(int invalid=0;invalid<9;invalid++) {Fixture f;f.h.m_Units.resize(1);auto& u=f.units[0];
|
||||
- if(invalid==0){u.Assignment=nullptr;}if(invalid==1){u.Task=nullptr;}if(invalid==2){f.tasks[0].RoadIntercept=false;}
|
||||
- if(invalid==3){u.PendingTask=&f.report;}if(invalid==4){u.PendingOrder=&f.report;}if(invalid==5){u.Downlink=&f.report;}
|
||||
- if(invalid==6){u.eligible=false;}if(invalid==7){u.fighting=true;}if(invalid==8){f.groups[0].origin=21;}
|
||||
- int before=ERR_EngineerMineSite::calls;f.h.MaintainEngineerMines(1);assert(!u.EngineerMineAttempted&&!u.EngineerMine&&ERR_EngineerMineSite::calls==before);}
|
||||
- {Fixture f;f.h.m_Units.resize(1);f.tasks[0].ExpiresAt=1;f.h.MaintainEngineerMines(1);assert(!f.units[0].EngineerMineAttempted);}
|
||||
- {Fixture f;f.h.m_Units.resize(1);ERR_EngineerMineSite::valid=false;int before=ERR_EngineerMineSite::calls;f.h.MaintainEngineerMines(1);f.h.MaintainEngineerMines(2);assert(f.units[0].EngineerMineAttempted&&!f.units[0].EngineerMine&&ERR_EngineerMineSite::calls==before+1);ERR_EngineerMineSite::valid=true;}
|
||||
- {Fixture f;f.h.m_Units.resize(1);f.units[0].begin=false;f.h.MaintainEngineerMines(1);f.h.MaintainEngineerMines(2);assert(f.units[0].EngineerMine->stops==1&&f.h.m_EngineerMineAttempts.Count()==1);}
|
||||
- {Fixture f;f.h.MaintainEngineerMines(1);f.h.m_EngineerMineAttempts.resize(256);f.h.MaintainEngineerMines(2);assert(f.units[0].EngineerMine->ticks==1&&!f.units[2].EngineerMineAttempted);}
|
||||
- {Fixture f;Replication::server=false;f.h.MaintainEngineerMines(1);Replication::server=true;f.h.m_Stopping=true;f.h.MaintainEngineerMines(1);f.h.m_Stopping=false;f.mode.active=false;f.h.MaintainEngineerMines(1);f.mode.active=true;ERR_GameMode::current=nullptr;f.h.MaintainEngineerMines(1);assert(f.h.m_EngineerMineAttempts.empty());}
|
||||
+ // Genuine second delivered post succeeds; no timer retry or replenishment at old post.
|
||||
+ {Fixture f;f.h.m_Units.resize(1);f.Tick();auto first=f.units[0].EngineerMine;first->Stop();f.Tick();assert(f.h.m_EngineerMineJobs.Count()==1);f.NewPost(0,4);f.Tick();assert(f.h.m_EngineerMineJobs.Count()==2&&f.h.m_EngineerMineAttempts.Count()==2);assert(f.units[0].EngineerMine!=first&&f.units[0].EngineerMine->prior.size()==1&&f.units[0].EngineerMine->prior[0].x==30);}
|
||||
+ {Fixture f;f.Tick();f.Tick();assert(f.h.m_EngineerMineJobs.Count()==2);int calls=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==calls);f.units[0].EngineerMine->Stop();f.Tick();assert(f.h.m_EngineerMineJobs.Count()==3);}
|
||||
+ // Settled old controller retained even after unit pointer disappears; pending receipt blocks exact original force.
|
||||
+ {Fixture f;f.h.m_Units.resize(1);f.Tick();auto first=f.units[0].EngineerMine;first->Stop();first->unresolved=true;f.NewPost(0,4);f.units[0].EngineerMine=nullptr;int calls=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==calls&&first->ticks==1);first->unresolved=false;f.Tick();assert(f.h.m_EngineerMineJobs.Count()==2);f.h.m_Units.Clear();f.Tick();assert(first->ticks==3);}
|
||||
+ // Four consumed attempts survive current unit replacement. No-item Begin failures are still consumed.
|
||||
+ {Fixture f;f.h.m_Units.resize(1);f.units[0].begin=false;for(int i=0;i<5;i++){f.NewPost(0,i*4);int before=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==before+(i<4));}assert(f.h.m_EngineerMinePosts.Count()==4&&f.h.m_EngineerMineJobs.Count()==4&&f.h.m_EngineerMineAttempts.empty());ERR_HQUnit replacement=f.units[0];f.h.m_Units[0]=&replacement;replacement.Task=&f.tasks[24];replacement.Assignment=&f.reports[24];f.groups[0].position=f.tasks[24].Destination;f.Tick();assert(f.h.m_EngineerMinePosts.Count()==4);}
|
||||
+ {Fixture f;ERR_EngineerMineSite::valid=false;int calls=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==calls+1&&f.h.m_EngineerMinePosts.Count()==1&&f.h.m_EngineerMineJobs.empty());f.Tick();assert(ERR_EngineerMineSite::calls==calls+2&&f.h.m_EngineerMinePosts.Count()==2);}
|
||||
+ {Fixture f;f.h.m_Units.resize(1);ERR_EngineerMineSite::valid=false;f.Tick();int calls=ERR_EngineerMineSite::calls;ERR_EngineerMineSite::valid=true;f.Tick();assert(ERR_EngineerMineSite::calls==calls&&f.h.m_EngineerMinePosts.Count()==1);}
|
||||
+ {Fixture f;for(int i=0;i<32;i++){auto j=new ERR_EngineerMine(f.h.m_EngineerMineAttempts);j->Stop();f.h.m_EngineerMineJobs.Insert(j);}int calls=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==calls);}
|
||||
+ {Fixture f;f.Tick();f.h.m_EngineerMineAttempts.resize(256);int calls=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==calls&&f.units[0].EngineerMine->ticks==1);}
|
||||
+ for(int fault=0;fault<12;fault++){Fixture f;f.h.m_Units.resize(1);auto&u=f.units[0];if(fault==0)u.Assignment=nullptr;if(fault==1)u.Task=nullptr;if(fault==2)f.tasks[0].RoadIntercept=false;if(fault==3)u.PendingTask=&f;if(fault==4)u.PendingOrder=&f;if(fault==5)u.Downlink=&f;if(fault==6)u.eligible=false;if(fault==7)u.fighting=true;if(fault==8)f.groups[0].position.x=21;if(fault==9)f.samplers[0].owns=false;if(fault==10)f.samplers[0].strength=1;if(fault==11)f.tasks[0].ExpiresAt=1000;int calls=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==calls&&f.h.m_EngineerMinePosts.Count()==0&&!u.EngineerMine);}
|
||||
+ {Fixture f;Replication::server=false;f.Tick();Replication::server=true;f.h.m_Stopping=true;f.Tick();f.h.m_Stopping=false;f.mode.active=false;f.Tick();f.mode.active=true;ERR_GameMode::current=nullptr;f.Tick();assert(f.h.m_EngineerMinePosts.Count()==0);}
|
||||
}
|
||||
-'''.replace('Task* Task=', 'struct Task* Task=').replace('Group* Group=', 'struct Group* Group='))
|
||||
-source=p.read_text()
|
||||
+''')
|
||||
assert 'if (EngineerMine) EngineerMine.Stop();' in body(p,'void ReleaseAssignment(')
|
||||
assert 'MaintainEngineerMines(now);' in body(p,'override void Tick(')
|
||||
assert 'm_EngineerMineAttempts.Clear();' in body(p,'override void Stop(')
|
||||
assert 'foreach (ERR_EngineerMine job : m_EngineerMineJobs) job.Stop();' in body(p,'override void Stop(')
|
||||
-print('PASS: actual HQ mine admission, one site query per tick, maximum two active/32 retained jobs, delivered-only original post, once-per-unit refusal latch, 256-item ceiling and maintenance/teardown ownership')
|
||||
+print('PASS: actual HQ and post ledger: second delivered post, one query/tick,2 active/32 jobs,4 consumed lifetime attempts, no-stock/query refusal consumption, pending old job and original identity constraints')
|
||||
--- /dev/null
|
||||
+++ b/tests/test_engineer_mine_posts.py
|
||||
@@ -0,0 +1,53 @@
|
||||
+"""Actual finite post ledger; engine identity/vector boundaries are doubles."""
|
||||
+import os,re
|
||||
+from pathlib import Path
|
||||
+from policy_harness import ROOT,run
|
||||
+P=Path(os.environ.get('ERR_ENGINEER_MINE_POSTS_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMinePosts.c'))
|
||||
+BOUNDARY=r'''
|
||||
+#include <cassert>
|
||||
+#include <cmath>
|
||||
+#include <string>
|
||||
+#include <vector>
|
||||
+#include <algorithm>
|
||||
+struct string:std::string{using std::string::string;using std::string::operator=;string(std::string s):std::string(s){} bool IsEmpty(){return empty();}};
|
||||
+struct vector{float x=0,y=0,z=0;static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}};
|
||||
+template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}void Insert(T p){this->push_back(p);}void Clear(){this->clear();}};
|
||||
+struct SCR_AIGroup{vector position;vector GetOrigin(){return position;}};
|
||||
+struct ERR_ForceRecord{SCR_AIGroup* Group=nullptr;bool Physical=true,Eliminated=false;string SourceId="s";int InitialStrength=2;};
|
||||
+struct ERR_HQTask{bool RoadIntercept=true,ReleaseOnly=false,investigation=false;string SecuritySite;int BeliefId=1,BeliefRevision=1;vector Destination;float QueuedAt=10,ExpiresAt=100000;bool Investigation(){return investigation;}};
|
||||
+struct ERR_HQReport{int MessageId=1;float ObservedAt=5;vector Position;};
|
||||
+namespace ERR_ForceRules{bool Finite(float f){return std::isfinite(f);}bool FiniteVector(vector p){return Finite(p.x)&&Finite(p.y)&&Finite(p.z);}}
|
||||
+struct ERR_EngineerMineSite{static inline bool valid=true;static inline int calls=0;static bool InBand(vector a,vector b){float d=vector::DistanceXZ(a,b);return d>=25&&d<=60;}static bool Select(vector post,vector&site){calls++;site={post.x+30,post.y,post.z};return valid;}};
|
||||
+'''
|
||||
+def ledger_cpp(path=P):
|
||||
+ s=path.read_text().replace('class ','struct ').replace('protected ','').replace('ref ','').replace('out array<vector> sites','Array<vector>& sites').replace('array<','Array<').replace(' = {};',';')
|
||||
+ for typ in ['ERR_EngineerMinePost','ERR_ForceRecord','SCR_AIGroup','ERR_HQTask','ERR_HQReport']:
|
||||
+ s=re.sub(r'\b'+typ+r'(?=\s+\w)',typ+'*',s);s=s.replace('Array<'+typ+'>','Array<'+typ+'*>')
|
||||
+ for ns in ['ERR_ForceRules','vector','ERR_EngineerMineSite']:s=s.replace(ns+'.',ns+'::')
|
||||
+ for obj in ['record','task','assignment','previous','entry']:s=re.sub(r'\b'+obj+r'\.',obj+'->',s)
|
||||
+ s=s.replace('foreach (','for (').replace('int used;','int used=0;')
|
||||
+ # Enforce initializes scalar fields to zero; C++ aggregate members must match.
|
||||
+ s=re.sub(r'^( (?:bool|int|float|ERR_ForceRecord\*|SCR_AIGroup\*|ERR_HQTask\*|ERR_HQReport\*) \w+);$',r'\1{};',s,flags=re.M)
|
||||
+ s=s.replace('\n}\n','\n};\n')
|
||||
+ return s
|
||||
+TEST=r'''
|
||||
+struct Fixture{SCR_AIGroup group;ERR_ForceRecord record;ERR_HQTask tasks[40];ERR_HQReport reports[40];ERR_EngineerMinePosts posts;
|
||||
+ Fixture(){record.Group=&group;for(int i=0;i<40;i++){tasks[i].Destination={float(i*200),0,0};tasks[i].QueuedAt=10+i*10;tasks[i].BeliefRevision=1+i;reports[i].ObservedAt=5+i*10;reports[i].MessageId=i+1;}}
|
||||
+ int Take(int i){return posts.Consume(&record,"s",&tasks[i],&reports[i],1000);}};
|
||||
+int main(){
|
||||
+ {Fixture f;assert(f.Take(0)==0);assert(f.Take(1)==1);assert(f.Take(2)==2);assert(f.Take(3)==3);assert(f.Take(4)==-1&&f.posts.Count()==4);}
|
||||
+ {Fixture f;assert(f.Take(0)==0);assert(f.Take(0)==-1);f.tasks[0].Destination={900,0,0};f.tasks[0].QueuedAt=900;f.reports[0].ObservedAt=800;f.reports[0].MessageId=999;f.tasks[0].BeliefRevision=999;
|
||||
+ assert(f.Take(1)==1);assert(f.posts.RecordSite(0,{30,0,0}));assert(!f.posts.RecordSite(0,{31,0,0}));Array<vector> sites;f.posts.PriorSites(sites);assert(sites.size()==1&&sites[0].x==30);sites[0].x=900;f.posts.PriorSites(sites);assert(sites[0].x==30);}
|
||||
+ for(int fault=0;fault<8;fault++){Fixture f;f.Take(0);if(fault==0)f.tasks[1].Destination={160,0,0};if(fault==1)f.tasks[1].QueuedAt=10;if(fault==2)f.reports[1].ObservedAt=5;if(fault==3)f.reports[1].MessageId=1;if(fault==4)f.tasks[1].BeliefRevision=1;if(fault==5)f.tasks[1].BeliefRevision=0;if(fault==6)f.tasks[1].ExpiresAt=1000;if(fault==7)f.tasks[1].Destination.x=NAN;assert(f.Take(1)==-1&&f.posts.Count()==1);}
|
||||
+ {Fixture f;f.Take(0);f.tasks[1].Destination.x=160.001f;assert(f.Take(1)==1);}
|
||||
+ {Fixture f;f.Take(0);f.tasks[1].BeliefId=2;f.tasks[1].BeliefRevision=1;assert(f.Take(1)==1);}
|
||||
+ {Fixture f;f.Take(0);ERR_ForceRecord alias=f.record;assert(f.posts.Consume(&alias,"s",&f.tasks[1],&f.reports[1],1000)==-1);alias.SourceId="alias";assert(f.posts.Consume(&alias,"alias",&f.tasks[1],&f.reports[1],1000)==-1);f.record.SourceId="changed";assert(f.posts.Consume(&f.record,"changed",&f.tasks[1],&f.reports[1],1000)==-1);}
|
||||
+ {Fixture f;f.Take(0);SCR_AIGroup replacement;f.record.Group=&replacement;assert(f.Take(1)==-1);f.record.Group=&f.group;f.record.InitialStrength=3;assert(f.Take(1)==-1);}
|
||||
+ {Fixture f;assert(!f.posts.RecordSite(-1,{30,0,0}));assert(f.Take(0)==0);assert(!f.posts.RecordSite(0,{24,0,0}));assert(!f.posts.RecordSite(0,{61,0,0}));assert(!f.posts.RecordSite(0,{30,NAN,0}));Array<vector> empty;f.posts.PriorSites(empty);assert(empty.empty());assert(f.posts.RecordSite(0,{25,0,0}));}
|
||||
+ {Fixture f;ERR_ForceRecord records[33];SCR_AIGroup groups[33];for(int i=0;i<33;i++){records[i].Group=&groups[i];records[i].SourceId=std::to_string(i);int result=f.posts.Consume(&records[i],records[i].SourceId,&f.tasks[i],&f.reports[i],1000);assert(result==(i<32?i:-1));}assert(f.posts.Count()==32);}
|
||||
+ for(int fault=0;fault<10;fault++){Fixture f;if(fault==0)f.record.Physical=false;if(fault==1)f.record.Eliminated=true;if(fault==2)f.record.InitialStrength=33;if(fault==3)f.record.Group=nullptr;if(fault==4)f.tasks[0].ReleaseOnly=true;if(fault==5)f.tasks[0].investigation=true;if(fault==6)f.tasks[0].SecuritySite="site";if(fault==7)f.tasks[0].QueuedAt=1001;if(fault==8)f.reports[0].ObservedAt=11;if(fault==9)f.reports[0].Position.x=NAN;assert(f.Take(0)==-1&&f.posts.Count()==0);}
|
||||
+}
|
||||
+'''
|
||||
+if __name__=='__main__':
|
||||
+ run(BOUNDARY+ledger_cpp()+TEST)
|
||||
+ print('PASS: actual mine post ledger immutable history, strict chronology/evidence, aliases, >160m separation, detached sites and finite4/32 consumed attempts')
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual HQ and post ledger: second delivered post, one query/tick,2 active/32 jobs,4 consumed lifetime attempts, no-stock/query refusal consumption, pending old job and original identity constraints
|
||||
@@ -0,0 +1,333 @@
|
||||
// Decisions consumed by the finite original-item controller below.
|
||||
class ERR_EngineerMineRules
|
||||
{
|
||||
// Straight XZ corridors are conservative local checks, not native route proofs.
|
||||
static bool CorridorClear(vector start, vector end, array<vector> priorSites)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(start) || !ERR_ForceRules.FiniteVector(end)) return false;
|
||||
if (!priorSites) return true;
|
||||
if (priorSites.Count() > 32) return false;
|
||||
float dx = end[0] - start[0], dz = end[2] - start[2];
|
||||
float squared = dx * dx + dz * dz;
|
||||
if (!ERR_ForceRules.Finite(squared)) return false;
|
||||
foreach (vector site : priorSites)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(site)) return false;
|
||||
float along = 0;
|
||||
if (squared > 0)
|
||||
{
|
||||
float projection = (site[0] - start[0]) * dx + (site[2] - start[2]) * dz;
|
||||
if (!ERR_ForceRules.Finite(projection)) return false;
|
||||
along = projection / squared;
|
||||
if (!ERR_ForceRules.Finite(along)) return false;
|
||||
along = Math.Clamp(along, 0, 1);
|
||||
}
|
||||
float x = start[0] + along * dx - site[0], z = start[2] + along * dz - site[2];
|
||||
float clearance = x * x + z * z;
|
||||
if (!ERR_ForceRules.Finite(clearance) || clearance <= 400) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool WithinWorkArea(vector post, vector actor)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(post) || !ERR_ForceRules.FiniteVector(actor)) return false;
|
||||
float distance = vector.DistanceXZ(post, actor);
|
||||
return ERR_ForceRules.Finite(distance) && distance <= 60;
|
||||
}
|
||||
static bool Within(float started, float previous, float now)
|
||||
{
|
||||
return ERR_ForceRules.Finite(started) && ERR_ForceRules.Finite(previous) && ERR_ForceRules.Finite(now)
|
||||
&& started >= 0 && previous >= started && now >= previous && now - started < 180000;
|
||||
}
|
||||
static float PrepareSince(float since, float now, bool near, bool engaged)
|
||||
{
|
||||
if (!ERR_ForceRules.Finite(now) || now < 0 || !near || engaged) return -1;
|
||||
if (!ERR_ForceRules.Finite(since) || since < 0 || since > now) return now;
|
||||
return since;
|
||||
}
|
||||
static bool Prepared(float since, float now)
|
||||
{
|
||||
return ERR_ForceRules.Finite(since) && ERR_ForceRules.Finite(now) && since >= 0 && now - since >= 10000;
|
||||
}
|
||||
static bool MayArm(bool grounded, bool ownerValid, float distance, bool friendlyNear)
|
||||
{
|
||||
return grounded && ownerValid && ERR_ForceRules.Finite(distance) && distance >= 20 && !friendlyNear;
|
||||
}
|
||||
}
|
||||
|
||||
// Callback records only terminal evidence. It never moves, arms or retries items.
|
||||
class ERR_EngineerMineRemoval : ScriptedInventoryOperationCallback
|
||||
{
|
||||
int Result;
|
||||
protected void Finish(int result) { if (!Result) Result = result; }
|
||||
protected override void OnComplete() { Finish(1); }
|
||||
protected override void OnAllComplete() { Finish(1); }
|
||||
protected override void OnFailed() { Finish(-1); }
|
||||
protected override void OnAllFailed() { Finish(-1); }
|
||||
}
|
||||
|
||||
// One original soldier, one carried PMN4, one accepted HQ road-interception post.
|
||||
// Loose cancelled items remain ordinary unarmed loot. Armed mines retain native
|
||||
// lifetime; a round's world reset owns their destruction, not this controller.
|
||||
class ERR_EngineerMine
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ERR_HQUnit m_Unit;
|
||||
protected SCR_AIGroup m_Group;
|
||||
protected ref ERR_HQTask m_Task;
|
||||
protected ref ERR_HQReport m_Assignment;
|
||||
protected ref ERR_ForceRecord m_Record;
|
||||
protected string m_SourceId;
|
||||
protected int m_Strength;
|
||||
protected IEntity m_Actor;
|
||||
protected int m_Slot;
|
||||
protected IEntity m_Mine;
|
||||
protected SCR_PressureTriggerComponent m_Trigger;
|
||||
protected SCR_InventoryStorageManagerComponent m_Inventory;
|
||||
protected SCR_AIUtilityComponent m_Utility;
|
||||
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 ref array<vector> m_PriorSites = {};
|
||||
protected vector m_Position;
|
||||
protected vector m_Rally;
|
||||
protected float m_Started = -1;
|
||||
protected float m_LastTick = -1;
|
||||
protected float m_PreparedAt = -1;
|
||||
protected float m_NextClearance;
|
||||
protected int m_State; // 0 approach,1 continuous preparation,2 removal pending,3 withdraw,4 finished
|
||||
protected int m_Checked;
|
||||
protected bool m_FriendlyNear;
|
||||
protected bool m_Begun;
|
||||
protected bool m_Stopped;
|
||||
|
||||
void ERR_EngineerMine(array<IEntity> attempted) { m_Attempted = attempted; }
|
||||
bool Finished() { return m_Stopped || m_State == 4; }
|
||||
bool BlocksNextPost(ERR_ForceRecord record)
|
||||
{
|
||||
if (!record || record != m_Record) return false;
|
||||
if (m_Begun && !Finished()) return true;
|
||||
// A late terminal callback or exact deletion settles only the old job.
|
||||
// Destroyed but surviving items and uncertain native refusals remain pinned.
|
||||
return false;
|
||||
}
|
||||
protected bool Refuse() { Stop(); return false; }
|
||||
protected bool Quiet()
|
||||
{
|
||||
return m_Unit && !m_Unit.InfantryCombat.Held && !ERR_HunterCombat.Active(m_Group);
|
||||
}
|
||||
protected bool Owned()
|
||||
{
|
||||
if (m_Stopped || !Replication.IsServer() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active()) return false;
|
||||
if (!m_Unit || !m_Group || m_Unit.Group != m_Group || !m_Task || m_Unit.Task != m_Task) return false;
|
||||
if (!m_Assignment || m_Unit.Assignment != m_Assignment || !m_Task.RoadIntercept || m_Task.ReleaseOnly) return false;
|
||||
if (m_Task.Investigation() || !m_Task.SecuritySite.IsEmpty() || m_Unit.PendingTask || m_Unit.PendingOrder) return false;
|
||||
if (m_Unit.ReservedForSupport || m_Unit.LogisticsReservation > 0 || m_Group.IsSlave() || m_Group.GetPlayerCount(true) > 0) return false;
|
||||
SCR_AIGroupUtilityComponent groupUtility = m_Group.GetGroupUtilityComponent();
|
||||
if (!groupUtility || !groupUtility.IsPositionAllowed(m_Position)) return false;
|
||||
if (!m_Mode.EnemyFaction() || m_Group.GetFaction() != m_Mode.EnemyFaction() || !m_Unit.StatusSampler) return false;
|
||||
if (vector.Distance(m_Task.Destination, m_Rally) > 0.01 || !m_Mode.Forces() || !m_Record) return false;
|
||||
if (m_Mode.Forces().FindGroup(m_Group) != m_Record || m_Record.Group != m_Group || !m_Record.Physical || m_Record.Eliminated) return false;
|
||||
if (m_Record.SourceId != m_SourceId || m_Unit.ForceSourceId != m_SourceId || m_Record.InitialStrength != m_Strength) return false;
|
||||
if (!m_Unit.StatusSampler.OwnsRoster(m_Group) || m_Unit.StatusSampler.InitialStrength() != m_Strength) return false;
|
||||
if (!m_Actor || !ERR_Actors.CanFight(m_Actor) || ERR_Actors.Player(m_Actor) || CompartmentAccessComponent.GetVehicleIn(m_Actor)) return false;
|
||||
vector actorPosition = m_Actor.GetOrigin();
|
||||
if (!ERR_EngineerMineRules.WithinWorkArea(m_Rally, actorPosition) || !ERR_EngineerMineRules.WithinWorkArea(m_Rally, m_Position)) return false;
|
||||
if (!ERR_EngineerMineRules.CorridorClear(actorPosition, actorPosition, m_PriorSites)
|
||||
|| !ERR_EngineerMineRules.CorridorClear(m_Position, m_Position, m_PriorSites)
|
||||
|| !ERR_EngineerMineRules.CorridorClear(m_Rally, m_Rally, m_PriorSites)) return false;
|
||||
if (!m_Unit.StatusSampler.CanAttribute(m_Slot, m_Actor, false) || ERR_Actors.Group(m_Actor) != m_Group) return false;
|
||||
if (SCR_FactionManager.SGetFaction(m_Actor) != m_Mode.EnemyFaction()) return false;
|
||||
SCR_ChimeraAIAgent agent = SCR_ChimeraAIAgent.Cast(SCR_AIUtils.GetAIAgent(m_Actor));
|
||||
if (!agent || !m_Utility || agent.m_UtilityComponent != m_Utility || !m_Utility.m_AIInfo) return false;
|
||||
if (!m_Inventory || m_Actor.FindComponent(SCR_InventoryStorageManagerComponent) != m_Inventory) return false;
|
||||
return true;
|
||||
}
|
||||
protected bool MineValid()
|
||||
{
|
||||
if (!ERR_Actors.Alive(m_Mine) || !m_Mine.GetPrefabData() || m_Mine.GetPrefabData().GetPrefabName() != ERR_EnemyLoadoutCatalog.MINE) return false;
|
||||
if (!m_Trigger || m_Mine.FindComponent(SCR_PressureTriggerComponent) != m_Trigger || m_Trigger.IsActivated()) return false;
|
||||
return m_Attempted && m_Attempted.Contains(m_Mine);
|
||||
}
|
||||
protected bool Carried()
|
||||
{
|
||||
if (!MineValid() || !m_Inventory) return false;
|
||||
array<IEntity> items = {}; ERR_Inventory.Items(m_Inventory, items);
|
||||
return items.Count() <= 128 && items.Contains(m_Mine) && ERR_InfantryAmmoTransfer.StoredUnder(m_Mine, m_Actor);
|
||||
}
|
||||
protected bool Grounded()
|
||||
{
|
||||
if (!MineValid() || !m_Inventory) return false;
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(m_Mine.FindComponent(InventoryItemComponent));
|
||||
if (!item || item.GetParentSlot() || m_Mine.GetParent()) return false;
|
||||
array<IEntity> items = {}; ERR_Inventory.Items(m_Inventory, items);
|
||||
return items.Count() <= 128 && !items.Contains(m_Mine);
|
||||
}
|
||||
protected bool Placed()
|
||||
{
|
||||
if (!Grounded() || vector.Distance(m_Mine.GetOrigin(), m_Position) > 1) return false;
|
||||
Physics physics = m_Mine.GetPhysics();
|
||||
return physics && physics.GetVelocity().LengthSq() <= 0.25;
|
||||
}
|
||||
protected void ClearAction()
|
||||
{
|
||||
if (m_Action && m_Actor && m_Utility && m_Utility.m_AIInfo)
|
||||
{
|
||||
SCR_ChimeraAIAgent agent = SCR_ChimeraAIAgent.Cast(SCR_AIUtils.GetAIAgent(m_Actor));
|
||||
if (agent && agent.m_UtilityComponent == m_Utility)
|
||||
{
|
||||
array<ref AIActionBase> actions = {}; m_Utility.GetActions(actions);
|
||||
if (actions.Count() <= 128 && actions.Contains(m_Action)) m_Action.Complete();
|
||||
}
|
||||
}
|
||||
m_Action = null;
|
||||
}
|
||||
protected void Move(vector destination)
|
||||
{
|
||||
ClearAction();
|
||||
m_Action = new SCR_AIMoveIndividuallyBehavior(m_Utility, null, destination, 1000, 0, null, 1);
|
||||
m_Utility.AddAction(m_Action);
|
||||
}
|
||||
protected void WaitAtMine()
|
||||
{
|
||||
ClearAction();
|
||||
m_Action = new SCR_AIWaitBehavior(m_Utility, null);
|
||||
m_Action.SetPriority(1000);
|
||||
m_Utility.AddAction(m_Action);
|
||||
}
|
||||
protected bool CheckFriendly(IEntity entity)
|
||||
{
|
||||
if (++m_Checked > 256) { m_FriendlyNear = true; return false; }
|
||||
if (!entity || (!ChimeraCharacter.Cast(entity) && !Vehicle.Cast(entity))) return true;
|
||||
DamageManagerComponent damage = DamageManagerComponent.Cast(entity.FindComponent(DamageManagerComponent));
|
||||
if (damage && damage.IsDestroyed()) return true;
|
||||
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(entity.FindComponent(FactionAffiliationComponent));
|
||||
Faction faction;
|
||||
if (affiliation) faction = affiliation.GetAffiliatedFaction();
|
||||
if (faction && m_Mode.EnemyFaction().IsFactionEnemy(faction)) return true;
|
||||
// Unconscious allies and unknown affiliations both prevent arming.
|
||||
m_FriendlyNear = true;
|
||||
return false;
|
||||
}
|
||||
protected bool SelectActor()
|
||||
{
|
||||
if (!m_Unit.StatusSampler.OwnsRoster(m_Group) || m_Strength < 1 || m_Strength > 32) return false;
|
||||
for (int slot = 0; slot < m_Strength; slot++)
|
||||
{
|
||||
IEntity actor = m_Unit.StatusSampler.InitialMember(slot);
|
||||
if (!actor || !ERR_Actors.CanFight(actor) || ERR_Actors.Player(actor) || CompartmentAccessComponent.GetVehicleIn(actor)) continue;
|
||||
if (!m_Unit.StatusSampler.CanAttribute(slot, actor, false) || vector.DistanceXZ(actor.GetOrigin(), m_Rally) > 20) continue;
|
||||
SCR_ChimeraAIAgent agent = SCR_ChimeraAIAgent.Cast(SCR_AIUtils.GetAIAgent(actor));
|
||||
if (!agent || !agent.m_UtilityComponent || !agent.m_UtilityComponent.m_AIInfo) continue;
|
||||
SCR_InventoryStorageManagerComponent inventory = SCR_InventoryStorageManagerComponent.Cast(actor.FindComponent(SCR_InventoryStorageManagerComponent));
|
||||
if (!inventory) continue;
|
||||
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
|
||||
if (items.Count() > 128) continue;
|
||||
foreach (IEntity mine : items)
|
||||
{
|
||||
if (!ERR_Actors.Alive(mine) || m_Attempted.Contains(mine) || !mine.GetPrefabData() || mine.GetPrefabData().GetPrefabName() != ERR_EnemyLoadoutCatalog.MINE) continue;
|
||||
if (!ERR_InfantryAmmoTransfer.StoredUnder(mine, actor)) continue;
|
||||
SCR_PressureTriggerComponent trigger = SCR_PressureTriggerComponent.Cast(mine.FindComponent(SCR_PressureTriggerComponent));
|
||||
if (!trigger || trigger.IsActivated() || trigger.IsActivatedByDefault()) continue;
|
||||
m_Actor = actor; m_Slot = slot; m_Mine = mine; m_Trigger = trigger;
|
||||
m_Inventory = inventory; m_Utility = agent.m_UtilityComponent;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool Begin(ERR_GameMode mode, ERR_HQUnit unit, ERR_HQTask task, vector minePosition, float now, array<vector> priorSites = null)
|
||||
{
|
||||
if (m_Begun || m_Stopped) return false;
|
||||
m_Begun = true;
|
||||
if (!Replication.IsServer() || !mode || mode != ERR_GameMode.Get() || !mode.Active()) return Refuse();
|
||||
if (!unit || !unit.Group || !unit.StatusSampler || !task || unit.Task != task || !unit.Assignment) return Refuse();
|
||||
if (!ERR_ForceRules.Finite(now) || now < 0 || !ERR_ForceRules.FiniteVector(minePosition)) return Refuse();
|
||||
if (!m_Attempted || m_Attempted.Count() >= 256 || !mode.Forces()) return Refuse();
|
||||
m_Mode = mode; m_Unit = unit; m_Task = task; m_Assignment = unit.Assignment; m_Group = unit.Group;
|
||||
m_SourceId = unit.ForceSourceId; m_Strength = unit.StatusSampler.InitialStrength(); m_Record = mode.Forces().FindGroup(m_Group);
|
||||
m_Position = minePosition; m_Rally = task.Destination;
|
||||
float distance = vector.DistanceXZ(m_Position, m_Rally);
|
||||
if (!ERR_ForceRules.FiniteVector(m_Rally) || distance < 25 || distance > 60 || now >= task.ExpiresAt) return Refuse();
|
||||
if (priorSites)
|
||||
{
|
||||
if (priorSites.Count() > 32) return Refuse();
|
||||
foreach (vector prior : priorSites)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(prior)) return Refuse();
|
||||
m_PriorSites.Insert(prior);
|
||||
}
|
||||
}
|
||||
if (!SelectActor() || !Owned() || !Quiet()) return Refuse();
|
||||
if (!ERR_EngineerMineRules.CorridorClear(m_Actor.GetOrigin(), m_Position, m_PriorSites)
|
||||
|| !ERR_EngineerMineRules.CorridorClear(m_Position, m_Rally, m_PriorSites)) return Refuse();
|
||||
// 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");
|
||||
return true;
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
if (m_Stopped) return;
|
||||
m_Stopped = true; m_State = 4;
|
||||
ClearAction();
|
||||
// Retain removal evidence and shared item quarantine. No late completion can arm.
|
||||
}
|
||||
void Tick(float now)
|
||||
{
|
||||
if (!m_Begun || Finished()) return;
|
||||
if (!ERR_EngineerMineRules.Within(m_Started, m_LastTick, now)) { Stop(); return; }
|
||||
m_LastTick = now;
|
||||
if (!Owned() || !Quiet() || !MineValid() || now >= m_Task.ExpiresAt) { Stop(); return; }
|
||||
if (m_State <= 1)
|
||||
{
|
||||
if (!Carried()) { Stop(); return; }
|
||||
bool near = vector.DistanceXZ(m_Actor.GetOrigin(), m_Position) <= 3;
|
||||
m_PreparedAt = ERR_EngineerMineRules.PrepareSince(m_PreparedAt, now, near, false);
|
||||
if (m_State == 0 && near) { m_State = 1; WaitAtMine(); }
|
||||
if (!ERR_EngineerMineRules.Prepared(m_PreparedAt, now)) return;
|
||||
// Set state and callback BEFORE native submission; never repeat the removal.
|
||||
m_Removal = new ERR_EngineerMineRemoval(); m_State = 2;
|
||||
bool accepted = m_Inventory.TryRemoveItemFromInventory(m_Mine, null, m_Removal);
|
||||
if (!accepted && !m_Removal.Result) { Stop(); return; }
|
||||
return;
|
||||
}
|
||||
if (m_State == 2)
|
||||
{
|
||||
if (!m_Removal || m_Removal.Result < 0) { Stop(); return; }
|
||||
// Observed transfer ends our custody; a later nearby drop cannot restore it.
|
||||
// Original inventory ownership may still settle after the native callback.
|
||||
InventoryItemComponent item = InventoryItemComponent.Cast(m_Mine.FindComponent(InventoryItemComponent));
|
||||
bool attached = m_Mine.GetParent() || (item && item.GetParentSlot());
|
||||
if (attached && !ERR_InfantryAmmoTransfer.StoredUnder(m_Mine, m_Actor)) { Stop(); return; }
|
||||
if (!m_Removal.Result || !Grounded()) return;
|
||||
if (vector.DistanceXZ(m_Actor.GetOrigin(), m_Position) > 3) { Stop(); return; }
|
||||
// A callback proves removal, not continued custody of a distant loose item.
|
||||
if (vector.Distance(m_Mine.GetOrigin(), m_Actor.GetOrigin()) > 3
|
||||
|| vector.Distance(m_Mine.GetOrigin(), m_Position) > 3) { Stop(); return; }
|
||||
m_Mine.SetOrigin(m_Position);
|
||||
m_State = 3; Move(m_Rally);
|
||||
return;
|
||||
}
|
||||
if (m_State != 3 || !Grounded() || vector.Distance(m_Mine.GetOrigin(), m_Position) > 1) { Stop(); return; }
|
||||
if (now < m_NextClearance || vector.DistanceXZ(m_Actor.GetOrigin(), m_Position) < 20) return;
|
||||
m_NextClearance = now + 1000;
|
||||
m_Checked = 0; m_FriendlyNear = false;
|
||||
if (!GetGame() || !GetGame().GetWorld()) { Stop(); return; }
|
||||
GetGame().GetWorld().QueryEntitiesBySphere(m_Position, 20, CheckFriendly, queryFlags: EQueryEntitiesFlags.ALL);
|
||||
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())
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/erri-engineer-posts-tests-0725/scratch/err-policy-62a5yjin/check.cpp:146: int main(): Assertion `f.h.m_EngineerMineJobs.Count()==2&&f.h.m_EngineerMineAttempts.Count()==2' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-engineer-posts-tests-0725/tests/test_engineer_mine_hq.py", line 16, in <module>
|
||||
run(BOUNDARY+ledger_cpp()+r'''
|
||||
~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
struct Ledger{Array<ERR_ForceRecord*> records;ERR_ForceRecord* FindGroup(SCR_AIGroup* group){for(auto r:records)if(r->Group==group)return r;return nullptr;}};
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
...<32 lines>...
|
||||
}
|
||||
^
|
||||
''')
|
||||
^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/erri-engineer-posts-tests-0725/scratch/err-policy-62a5yjin/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/erri-engineer-posts-tests-0725/scratch/err-policy-1673sbz9/check.cpp:381: int main(): Assertion `f.controller.BlocksNextPost(&f.record)' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-engineer-posts-tests-0725/tests/test_engineer_mine.py", line 33, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<198 lines>...
|
||||
}
|
||||
^
|
||||
''')
|
||||
^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/erri-engineer-posts-tests-0725/scratch/err-policy-1673sbz9/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual mine post ledger immutable history, strict chronology/evidence, aliases, >160m separation, detached sites and finite4/32 consumed attempts
|
||||
@@ -0,0 +1,12 @@
|
||||
check: /tmp/erri-engineer-posts-tests-0725/scratch/err-policy-nzidhxwh/check.cpp:102: int main(): Assertion `f.Take(4)==-1&&f.posts.Count()==4' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-engineer-posts-tests-0725/tests/test_engineer_mine_posts.py", line 52, in <module>
|
||||
run(BOUNDARY+ledger_cpp()+TEST)
|
||||
~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
|
||||
subprocess.run([str(p/'check')],check=True)
|
||||
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/usr/lib/python3.14/subprocess.py", line 578, in run
|
||||
raise CalledProcessError(retcode, process.args,
|
||||
output=stdout, stderr=stderr)
|
||||
subprocess.CalledProcessError: Command '['/tmp/erri-engineer-posts-tests-0725/scratch/err-policy-nzidhxwh/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"status": "pass",
|
||||
"source_hashes": {
|
||||
"/tmp/erri-engineer-posts-source-0725/addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c": "d873b5b689e918f8c907918d673304a184ac2e4a4cb6face527b37a67821ee9f",
|
||||
"/tmp/erri-engineer-posts-source-0725/addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMinePosts.c": "ab0171a510d46fad6df5b477ae27cb03016b894700e4111e9a5b99d8061fe3a6",
|
||||
"/tmp/erri-engineer-controller-source-0725/addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMine.c": "defb12924ea5dfe68f86c2bbb4b07362f016ffee810f720d13f01d81881b3cfc"
|
||||
},
|
||||
"tests_sha256": {
|
||||
"tests/test_engineer_mine.py": "300023f165be86f681d3980e844823c73e37b98c5855a2142d9d373fdce3e9ab",
|
||||
"tests/test_engineer_mine_hq.py": "991666e1218282ce5ffca4ad3f47cf25ec436c6dc091951cc4db395e0cde01a6",
|
||||
"tests/test_engineer_mine_posts.py": "6185b5ab6576948fd8b47bb945fbe40107ec6944eafde2d04dd60ff49d5ec89c"
|
||||
},
|
||||
"baseline_test_sha256": {
|
||||
"tests/test_engineer_mine.py": "244ab49155000e5939a6c9d9e8f01fe8b14115119d548ba423ceffdd33ca9946",
|
||||
"tests/test_engineer_mine_hq.py": "822f50e363be3ba03bc5d12748cfc93d83e93552d66797b01afc6460e7ab7d5d"
|
||||
},
|
||||
"patch_sha256": "a19a3a602cb2611bd20855fbcaad38dc31cbe09e3f6d343c05e946557d2dbd1d",
|
||||
"results": [
|
||||
{
|
||||
"name": "old-hq-red",
|
||||
"exit_code": 1,
|
||||
"compiled": true,
|
||||
"log_sha256": "5aa1cf3182bbf0484faeb37ea19822b5245f80bb05cc2d1f186f68e735e48808"
|
||||
},
|
||||
{
|
||||
"name": "quota-mutation-red",
|
||||
"exit_code": 1,
|
||||
"compiled": true,
|
||||
"log_sha256": "8447dfb3819fe13be2c138e57302932a7c922856b6f52bbc0114a7a8ca3debaa"
|
||||
},
|
||||
{
|
||||
"name": "pending-mutation-red",
|
||||
"exit_code": 1,
|
||||
"compiled": true,
|
||||
"log_sha256": "e7e41260b0d7b64c4f4935179c30b66b873ee708b5923344cf601ffc3a1dab6c"
|
||||
},
|
||||
{
|
||||
"name": "posts-green",
|
||||
"exit_code": 0,
|
||||
"compiled": true,
|
||||
"log_sha256": "ed2ac192287b36c0d58605c307521f380f75d0683cbf4a74f0a0033e83c67c9b"
|
||||
},
|
||||
{
|
||||
"name": "hq-green",
|
||||
"exit_code": 0,
|
||||
"compiled": true,
|
||||
"log_sha256": "382fe94c8093ce1d364f354d9fde23fb251543c27eecb954f5b8f25017aab135"
|
||||
},
|
||||
{
|
||||
"name": "controller-green",
|
||||
"exit_code": 0,
|
||||
"compiled": true,
|
||||
"log_sha256": "42ed037cc22a9224478449736d1f43e57cc89fa06ddc7e0f09f37771f3ca0b2a"
|
||||
}
|
||||
],
|
||||
"coverage": [
|
||||
"Actual ledger classes: immutable consumed post/evidence values, task/report alias refusal, strict160m separation, chronological replay refusal,4-per-force/32-total attempts, record/source/group aliases and detached once-recorded sites.",
|
||||
"Actual HQ MaintainEngineerMines calls actual ledger. First completed job then second spatially separate delivered post succeeds. Old retained pending job blocks admission even after unit.EngineerMine pointer clears. Two-active/32-retained/256-item caps and one query/tick preserved. No-site/no-item refusals remain consumed; replacement HQUnit cannot reset original-force quota.",
|
||||
"Entire existing actual controller harness retained and extended. Copied prior hazard positions, invalid/overcap input, strict20m corridors and60m work area, active history exclusion, second distinct original item and shared tombstone all checked. BlocksNextPost uses exact force record: active blocks, stopped pending survives destroyed nonnull item, terminal callback or exact deletion settles without late movement/arming.",
|
||||
"Baseline actual HQ compiles then fails new-post success. Two targeted source mutations compile then fail: fifth lifetime allowance and omitted stopped pending-removal blocker."
|
||||
],
|
||||
"limits": [
|
||||
"C++ source-method translation with vector/native job/inventory/AI boundary doubles. No Enforce compiler, native navigation, physical two-post mining, networking or path-safety guarantee.",
|
||||
"No target/original mutations or full suite. New test_engineer_mine_hq imports shared ledger harness definitions from adjacent test_engineer_mine_posts; main guard prevents duplicate execution."
|
||||
],
|
||||
"all_compilers_finished": true
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
from pathlib import Path
|
||||
import subprocess,os,json,hashlib,difflib
|
||||
r=Path('/home/jakub/devel/escapistsrereintroduced');o=Path('/tmp/erri-engineer-posts-tests-0725');a=Path('/tmp/erri-engineer-posts-source-0725');c=Path('/tmp/erri-engineer-controller-source-0725');rel='addons/core/Scripts/Game/ERR/10_AI/'
|
||||
hq=a/rel/'ERR_HQ.c';posts=a/rel/'ERR_EngineerMinePosts.c';controller=c/rel/'ERR_EngineerMine.c'
|
||||
env=dict(os.environ,PYTHONPATH=str(r/'tests'),TMPDIR=str(o/'scratch'),ERR_HQ_SOURCE=str(hq),ERR_ENGINEER_MINE_POSTS_SOURCE=str(posts),ERR_ENGINEER_MINE_SOURCE=str(controller))
|
||||
mutationPosts=o/'five-attempts.c';s=posts.read_text();assert 'used >= 4' in s;mutationPosts.write_text(s.replace('used >= 4','used >= 5',1))
|
||||
mutationController=o/'ignore-pending.c';s=controller.read_text();old='return m_Removal && !m_Removal.Result && m_Mine;';assert old in s;mutationController.write_text(s.replace(old,'return false;',1))
|
||||
cases=[('old-hq-red','test_engineer_mine_hq.py',{'ERR_HQ_SOURCE':str(a/'baseline'/rel/'ERR_HQ.c')},False),('quota-mutation-red','test_engineer_mine_posts.py',{'ERR_ENGINEER_MINE_POSTS_SOURCE':str(mutationPosts)},False),('pending-mutation-red','test_engineer_mine.py',{'ERR_ENGINEER_MINE_SOURCE':str(mutationController)},False),('posts-green','test_engineer_mine_posts.py',{},True),('hq-green','test_engineer_mine_hq.py',{},True),('controller-green','test_engineer_mine.py',{},True)]
|
||||
results=[]
|
||||
for name,test,overrides,positive in cases:
|
||||
active=env|overrides
|
||||
with (o/(name+'.log')).open('w') as log:res=subprocess.run(['python3',str(o/'tests'/test)],env=active,stdout=log,stderr=subprocess.STDOUT)
|
||||
text=(o/(name+'.log')).read_text();assert (res.returncode==0)==positive,(name,text)
|
||||
if not positive:assert 'Assertion' in text and 'failed.' in text and "Command '['g++'" not in text,(name,text)
|
||||
results.append({'name':name,'exit_code':res.returncode,'compiled':True,'log_sha256':hashlib.sha256((o/(name+'.log')).read_bytes()).hexdigest()})
|
||||
patch=''
|
||||
for name in ['test_engineer_mine.py','test_engineer_mine_hq.py','test_engineer_mine_posts.py']:
|
||||
before=r/'tests'/name;old=before.read_text() if before.exists() else '';new=(o/'tests'/name).read_text();patch+=''.join(difflib.unified_diff(old.splitlines(True),new.splitlines(True),fromfile='a/tests/'+name if before.exists() else '/dev/null',tofile='b/tests/'+name))
|
||||
(o/'fix.patch').write_text(patch)
|
||||
review={'status':'pass','source_hashes':{str(p):hashlib.sha256(p.read_bytes()).hexdigest() for p in [hq,posts,controller]},'tests_sha256':{str(p.relative_to(o)):hashlib.sha256(p.read_bytes()).hexdigest() for p in sorted((o/'tests').glob('*.py'))},'baseline_test_sha256':{str(p.relative_to(o/'baseline')):hashlib.sha256(p.read_bytes()).hexdigest() for p in sorted((o/'baseline/tests').glob('*.py'))},'patch_sha256':hashlib.sha256((o/'fix.patch').read_bytes()).hexdigest(),'results':results,'coverage':['Actual ledger classes: immutable consumed post/evidence values, task/report alias refusal, strict160m separation, chronological replay refusal,4-per-force/32-total attempts, record/source/group aliases and detached once-recorded sites.','Actual HQ MaintainEngineerMines calls actual ledger. First completed job then second spatially separate delivered post succeeds. Old retained pending job blocks admission even after unit.EngineerMine pointer clears. Two-active/32-retained/256-item caps and one query/tick preserved. No-site/no-item refusals remain consumed; replacement HQUnit cannot reset original-force quota.','Entire existing actual controller harness retained and extended. Copied prior hazard positions, invalid/overcap input, strict20m corridors and60m work area, active history exclusion, second distinct original item and shared tombstone all checked. BlocksNextPost uses exact force record: active blocks, stopped pending survives destroyed nonnull item, terminal callback or exact deletion settles without late movement/arming.','Baseline actual HQ compiles then fails new-post success. Two targeted source mutations compile then fail: fifth lifetime allowance and omitted stopped pending-removal blocker.'],'limits':['C++ source-method translation with vector/native job/inventory/AI boundary doubles. No Enforce compiler, native navigation, physical two-post mining, networking or path-safety guarantee.','No target/original mutations or full suite. New test_engineer_mine_hq imports shared ledger harness definitions from adjacent test_engineer_mine_posts; main guard prevents duplicate execution.'],'all_compilers_finished':True}
|
||||
(o/'review.json').write_text(json.dumps(review,indent=2)+'\n');print(json.dumps(review,indent=2))
|
||||
Reference in New Issue
Block a user