Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
166 lines
12 KiB
Diff
166 lines
12 KiB
Diff
--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c
|
|
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c
|
|
@@ -22,6 +22,20 @@
|
|
if (m_Sites && !m_Sites.FailureReason().IsEmpty()) return m_Sites.FailureReason();
|
|
return "Scenario or additional force preparation failed.";
|
|
}
|
|
+ #ifdef WORKBENCH
|
|
+ // The historical catalog predates finite mobile site squads. Configure this
|
|
+ // isolated fixture before ordinary population, never borrow permanent guards.
|
|
+ protected bool ERR_ConfigurePatrolHQFixture(string outcome)
|
|
+ {
|
|
+ if (m_TerrainIndependent || outcome != "patrol-hq") return true;
|
|
+ if (!m_RuntimeDefinition) return false;
|
|
+ ERR_SiteDefinition site = m_RuntimeDefinition.Find("everon-outpost-1");
|
|
+ if (!site || site.Kind != ERR_LocationKind.OUTPOST || !site.Guarded || site.ResponseCount != 0) return false;
|
|
+ site.ResponseCount = 2;
|
|
+ site.ResponseOffset = "-20 0 -20";
|
|
+ return m_RuntimeDefinition.Valid();
|
|
+ }
|
|
+ #endif
|
|
ERR_PrepareResult Prepare()
|
|
{
|
|
ERR_GameMode mode = ERR_GameMode.Get();
|
|
@@ -42,6 +56,7 @@
|
|
string fixture;
|
|
if (!m_TerrainIndependent && System.GetCLIParam("errSmokeOutcome", fixture) && fixture != "normal-escape-route" && fixture != "mounted-ammo" && fixture != "alarm-hearing" && fixture != "optional-site-skip" && fixture != "infantry-combat" && fixture != "site-garrisons" && fixture != "generated-sites" && fixture != "airfield-markers" && fixture != "air-recon" && fixture != "air-logistics" && fixture != "air-assault")
|
|
foreach (ERR_SiteDefinition fixedSite : m_RuntimeDefinition.Sites) fixedSite.RandomClear = false;
|
|
+ if (!ERR_ConfigurePatrolHQFixture(fixture)) { m_Failed = true; return ERR_PrepareResult.FAILED; }
|
|
#endif
|
|
if (m_TerrainIndependent) m_Generated = new ERR_TerrainSites();
|
|
else m_Generated = new ERR_GeneratedSites();
|
|
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c
|
|
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c
|
|
@@ -25,9 +25,11 @@
|
|
task.ExpiresAt = order.ObservedAt + 120000;
|
|
unit.PendingTask = task;
|
|
unit.PendingOrder = order;
|
|
+ float queuedRadius = task.SearchRadiusMetres;
|
|
DeliverOrder(unit, order.ObservedAt);
|
|
search = unit.Waypoint;
|
|
- return search && unit.Assignment == order;
|
|
+ if (search) PrintFormat("ERR PATROL HQ RADIUS: queued=%1 native=%2", queuedRadius, search.GetCompletionRadius());
|
|
+ return search && unit.Assignment == order && search.GetCompletionRadius() == queuedRadius;
|
|
}
|
|
return false;
|
|
}
|
|
@@ -77,6 +79,7 @@
|
|
m_Site = m_Mode.Sites().Find("everon-outpost-1");
|
|
if (!Check(m_Site && m_Site.Responders && m_Site.Responders.Group, "existing mobile outpost responders available")) return -1;
|
|
m_Group = m_Site.Responders.Group;
|
|
+ if (!Check(m_Site.Responders.InitialGuardCount == 2 && m_Mode.ForceLiving(m_Group) == 2, "two original mobile responders remain distinct from permanent guards")) return -1;
|
|
m_Group.GetWaypoints(m_Baseline);
|
|
if (!Check(!m_Baseline.IsEmpty() && hq.ERR_TestPatrolSearch(m_Group, m_Search), "real delivered-order method assigns existing squad")) return -1;
|
|
m_Step = 1; m_Wait = now + 750; return 0;
|
|
--- a/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
|
|
+++ b/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
|
|
@@ -699,8 +699,10 @@
|
|
{
|
|
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
|
|
if (!ERR_Check(UnlockPrison(m_ERRSmokePlayer) && Phase() == ERR_Phase.RADIO_SEARCH, "patrol HQ fixture leaves prison through canonical admission before observing movement")) return;
|
|
- m_ERRHQPatrolMovement = new ERR_NativePatrolProbe(this);
|
|
- m_ERRSmokeStep = 130;
|
|
+ // HQ tests the distinct mobile responders; the subsequent physical
|
|
+ // traversal still observes the untouched permanent guard patrol.
|
|
+ m_ERRHQPatrolOrder = new ERR_NativePatrolHQProbe(this);
|
|
+ m_ERRSmokeStep = 131;
|
|
return;
|
|
}
|
|
if (m_ERRTestMapOwnership)
|
|
--- a/tests/test_native_memory_deadlines.py
|
|
+++ b/tests/test_native_memory_deadlines.py
|
|
@@ -100,7 +100,7 @@
|
|
|
|
# Execute the actual scenario-selection expressions: normal route must retain
|
|
# the ordinary catalog AND random placement, while historical probes stay fixed.
|
|
-scenario=(REPO/'addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c').read_text()
|
|
+scenario=Path(os.environ.get('ERR_SCENARIO_SOURCE',REPO/'addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c')).read_text()
|
|
conditions=[]
|
|
for variable in ('historicalFixture','fixture'):
|
|
line=next(line.strip() for line in scenario.splitlines() if line.strip().startswith('if (') and 'System.GetCLIParam("errSmokeOutcome", '+variable+')' in line)
|
|
@@ -134,24 +134,78 @@
|
|
#include <cassert>
|
|
#include <string>
|
|
struct Fixture;
|
|
-struct ERR_NativePatrolProbe {explicit ERR_NativePatrolProbe(Fixture* owner);};
|
|
+struct ERR_NativePatrolHQProbe {explicit ERR_NativePatrolHQProbe(Fixture* owner);};
|
|
+struct ERR_NativePatrolProbe {explicit ERR_NativePatrolProbe(Fixture*){assert(false && "permanent patrol must follow responder HQ proof");}};
|
|
struct Fixture {
|
|
bool m_ERRTestPatrolHQ=true,allowed=true,wrongPhase=false,active=false;
|
|
int m_ERRSmokePlayer=7,phase=1,unlocks=0,failures=0,allocations=0,protections=0,m_ERRSmokeStep=0;
|
|
+ ERR_NativePatrolHQProbe* m_ERRHQPatrolOrder=nullptr;
|
|
ERR_NativePatrolProbe* m_ERRHQPatrolMovement=nullptr;
|
|
- ~Fixture(){delete m_ERRHQPatrolMovement;}
|
|
+ ~Fixture(){delete m_ERRHQPatrolOrder;delete m_ERRHQPatrolMovement;}
|
|
void ProtectOriginal(int actor){assert(actor==7);protections++;}
|
|
bool UnlockPrison(int actor){assert(actor==7);unlocks++;if(!allowed)return false;if(!wrongPhase){phase=2;active=true;}return true;}
|
|
int Phase(){return phase;}
|
|
bool ERR_Check(bool ok,std::string){if(!ok)failures++;return ok;}
|
|
void Start(){BRANCH}
|
|
};
|
|
-ERR_NativePatrolProbe::ERR_NativePatrolProbe(Fixture* owner){assert(owner->phase==2&&owner->active&&owner->unlocks==1);owner->allocations++;}
|
|
-int main(){
|
|
- Fixture healthy;healthy.Start();assert(healthy.m_ERRSmokeStep==130&&healthy.allocations==1&&healthy.failures==0&&healthy.protections==1);
|
|
+ERR_NativePatrolHQProbe::ERR_NativePatrolHQProbe(Fixture* owner){assert(owner->phase==2&&owner->active&&owner->unlocks==1);owner->allocations++;}
|
|
+int main(){
|
|
+ Fixture healthy;healthy.Start();assert(healthy.m_ERRSmokeStep==131&&healthy.allocations==1&&healthy.failures==0&&healthy.protections==1);
|
|
Fixture refused;refused.allowed=false;refused.Start();assert(refused.unlocks==1&&refused.failures==1&&refused.allocations==0&&refused.m_ERRSmokeStep==0&&!refused.active);
|
|
Fixture wrong;wrong.wrongPhase=true;wrong.Start();assert(wrong.unlocks==1&&wrong.failures==1&&wrong.allocations==0&&wrong.m_ERRSmokeStep==0);
|
|
Fixture other;other.m_ERRTestPatrolHQ=false;other.Start();assert(other.unlocks==0&&other.allocations==0&&other.protections==0&&other.m_ERRSmokeStep==0);
|
|
}
|
|
'''.replace('BRANCH',patrol_branch))
|
|
-print('PASS: actual patrol fixture leaves prison before movement observation and refuses failed transition')
|
|
+assert 'if (result == 1) { m_ERRHQPatrolMovement = new ERR_NativePatrolProbe(this); m_ERRSmokeStep = 132; }' in body(smoke_path,'protected void ERR_SmokeTick(')
|
|
+print('PASS: actual patrol fixture leaves prison before responder HQ proof, followed by separate permanent-patrol observation')
|
|
+
|
|
+# Execute the isolated definition mutation on the actual legacy outpost fields.
|
|
+# Definition.Valid is a boundary double here; full production authoring policy
|
|
+# remains enforced by the normal preparation path and repository checks.
|
|
+scenario_path=Path(os.environ.get('ERR_SCENARIO_SOURCE',REPO/'addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c'))
|
|
+legacy=(REPO/'addons/core/Configs/ERR/Tests/LegacySites.conf').read_text()
|
|
+start=legacy.index('Id "everon-outpost-1"');end=legacy.index('\n }',start);outpost=legacy[start:end]
|
|
+assert 'ResponseCount' not in outpost and 'Guarded 1' in outpost and 'GuardOffset 16 0 0' in outpost
|
|
+assert outpost.count('ERR_SitePatrolPoint')==4
|
|
+if 'protected bool ERR_ConfigurePatrolHQFixture(' in scenario:
|
|
+ configure=body(scenario_path,'protected bool ERR_ConfigurePatrolHQFixture(').replace('protected ','').replace('ERR_SiteDefinition site =','Site* site =').replace('m_RuntimeDefinition.','m_RuntimeDefinition->').replace('site.','site->').replace('ERR_LocationKind.OUTPOST','3').replace('"-20 0 -20"','Vec{-20,0,-20}')
|
|
+ assert scenario.index('ERR_ConfigurePatrolHQFixture(fixture)') < scenario.index('m_Generated.Begin(m_RuntimeDefinition)') < scenario.index('m_Sites.Begin(m_RuntimeDefinition')
|
|
+else:
|
|
+ # Baseline has no prepopulation mutation; executing its unchanged definition
|
|
+ # must fail the required two-mobile-originals assertion below.
|
|
+ configure='bool ERR_ConfigurePatrolHQFixture(string){return true;}'
|
|
+run(r'''
|
|
+#include <cassert>
|
|
+#include <string>
|
|
+using string=std::string;
|
|
+struct Vec{float x,y,z;};
|
|
+struct Site {int Kind=3,ResponseCount=0,GuardCount=0,patrolPoints=4,stock=4;bool Guarded=true;Vec ResponseOffset{-12,0,0},GuardOffset{16,0,0};};
|
|
+struct Definition{Site site;bool present=true,valid=true;int validations=0;Site*Find(string id){assert(id=="everon-outpost-1");return present?&site:nullptr;}bool Valid(){validations++;return valid;}};
|
|
+struct Fixture {bool m_TerrainIndependent=false;Definition* m_RuntimeDefinition=nullptr;METHOD};
|
|
+int main(){
|
|
+ Definition d;Fixture f;f.m_RuntimeDefinition=&d;assert(f.ERR_ConfigurePatrolHQFixture("patrol-hq"));
|
|
+ assert(d.site.ResponseCount==2&&d.site.ResponseOffset.x==-20&&d.site.ResponseOffset.z==-20&&d.site.ResponseOffset.y==0&&d.validations==1);
|
|
+ assert(d.site.Guarded&&d.site.GuardCount==0&&d.site.GuardOffset.x==16&&d.site.patrolPoints==4&&d.site.stock==4);
|
|
+ for(string outcome:{"","normal-escape-route","patrol","victory","hq-memory"}){Definition other;Fixture g;g.m_RuntimeDefinition=&other;assert(g.ERR_ConfigurePatrolHQFixture(outcome)&&other.site.ResponseCount==0&&other.validations==0);}
|
|
+ Definition terrain;Fixture t;t.m_TerrainIndependent=true;t.m_RuntimeDefinition=&terrain;assert(t.ERR_ConfigurePatrolHQFixture("patrol-hq")&&terrain.site.ResponseCount==0);
|
|
+ for(int fault=0;fault<6;fault++){Definition bad;Fixture x;x.m_RuntimeDefinition=&bad;if(fault==0)x.m_RuntimeDefinition=nullptr;if(fault==1)bad.present=false;if(fault==2)bad.site.Kind=0;if(fault==3)bad.site.Guarded=false;if(fault==4)bad.site.ResponseCount=1;if(fault==5)bad.valid=false;assert(!x.ERR_ConfigurePatrolHQFixture("patrol-hq"));}
|
|
+}
|
|
+'''.replace('METHOD',configure))
|
|
+# Test actual final receipt decision. Delivery/native allocation are covered by
|
|
+# their own methods and the real opt-in run; this checks the native getter is
|
|
+# required instead of accepting an assignment with the untouched prefab radius.
|
|
+patrol_path=Path(os.environ.get('ERR_PATROL_HQ_SOURCE',REPO/'addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c'))
|
|
+patrol=body(patrol_path,'bool ERR_TestPatrolSearch(')
|
|
+assert 'float queuedRadius = task.SearchRadiusMetres;' in patrol
|
|
+assert patrol.index('float queuedRadius =') < patrol.index('DeliverOrder(unit,')
|
|
+tail=patrol[patrol.index(' search = unit.Waypoint;'):patrol.index('\n }',patrol.index(' search = unit.Waypoint;'))].replace('unit.','unit->').replace('search.','search->')
|
|
+run(r'''
|
|
+#include <cassert>
|
|
+#include <string>
|
|
+struct Waypoint{float radius=30;float GetCompletionRadius(){return radius;}};
|
|
+struct Unit{Waypoint*WaypointValue=nullptr;int Assignment=1;};
|
|
+int logs=0;void PrintFormat(std::string,float,float){logs++;}
|
|
+bool Delivered(Unit* unit,int order,float queuedRadius){Waypoint*search=nullptr;TAIL}
|
|
+int main(){Waypoint wp;Unit unit;unit.WaypointValue=℘assert(!Delivered(&unit,1,45));wp.radius=45;assert(Delivered(&unit,1,45));wp.radius=110;assert(Delivered(&unit,1,110));assert(!Delivered(&unit,1,45));unit.Assignment=2;assert(!Delivered(&unit,1,110));unit.WaypointValue=nullptr;int before=logs;assert(!Delivered(&unit,1,110)&&logs==before);}
|
|
+'''.replace('TAIL',tail.replace('unit->Waypoint','unit->WaypointValue')))
|
|
+print('PASS: exact patrol-hq finite responder preparation and native search-radius receipt verification')
|