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:
+90
@@ -0,0 +1,90 @@
|
||||
[ComponentEditorProps(category: "Reintroduced", description: "Authored scenario definition and finite population lifecycle")]
|
||||
class ERR_ScenarioComponentClass : ScriptComponentClass {}
|
||||
class ERR_ScenarioComponent : ScriptComponent
|
||||
{
|
||||
[Attribute("", UIWidgets.ResourceNamePicker, "Scenario site definitions", "conf")]
|
||||
protected ResourceName m_Definition;
|
||||
[Attribute("0", UIWidgets.CheckBox, "Generate all sites on this terrain; requires terrain-specific AI navigation")]
|
||||
protected bool m_TerrainIndependent;
|
||||
protected ref ERR_SiteService m_Sites;
|
||||
protected bool m_Started;
|
||||
protected ref ERR_ScenarioDefinition m_RuntimeDefinition;
|
||||
protected ref ERR_GeneratedSites m_Generated;
|
||||
protected bool m_Finished;
|
||||
protected bool m_Failed;
|
||||
|
||||
ERR_SiteService Sites() { return m_Sites; }
|
||||
string FailureReason()
|
||||
{
|
||||
ERR_HQ hq = ERR_HQ.Get();
|
||||
if (hq && hq.Scouts() && !hq.Scouts().Error().IsEmpty()) return hq.Scouts().Error();
|
||||
if (m_Generated && !m_Generated.Error().IsEmpty()) return m_Generated.Error();
|
||||
if (m_Sites && !m_Sites.FailureReason().IsEmpty()) return m_Sites.FailureReason();
|
||||
return "Scenario or additional force preparation failed.";
|
||||
}
|
||||
ERR_PrepareResult Prepare()
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (!Replication.IsServer() || !mode || m_Failed) return ERR_PrepareResult.FAILED;
|
||||
if (m_Finished) return ERR_PrepareResult.READY;
|
||||
if (!m_Started)
|
||||
{
|
||||
m_Started = true;
|
||||
#ifdef WORKBENCH
|
||||
string historicalFixture;
|
||||
if (!m_TerrainIndependent && System.GetCLIParam("errSmokeOutcome", historicalFixture) && historicalFixture != "normal-escape-route" && historicalFixture != "mounted-ammo" && historicalFixture != "alarm-hearing" && historicalFixture != "optional-site-skip" && historicalFixture != "infantry-combat" && historicalFixture != "site-garrisons" && historicalFixture != "generated-sites" && historicalFixture != "opening-tools" && historicalFixture != "airfield-markers" && historicalFixture != "air-recon" && historicalFixture != "air-logistics" && historicalFixture != "air-assault")
|
||||
m_Definition = "{ACA98B44E2173605}Configs/ERR/Tests/LegacySites.conf";
|
||||
#endif
|
||||
m_RuntimeDefinition = ERR_ScenarioDefinition.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab(m_Definition));
|
||||
if (!m_RuntimeDefinition || !m_RuntimeDefinition.Valid()) { m_Failed = true; return ERR_PrepareResult.FAILED; }
|
||||
#ifdef WORKBENCH
|
||||
// Existing deterministic probes intentionally use authored coordinates.
|
||||
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;
|
||||
#endif
|
||||
if (m_TerrainIndependent) m_Generated = new ERR_TerrainSites();
|
||||
else m_Generated = new ERR_GeneratedSites();
|
||||
if (!m_Generated.Begin(m_RuntimeDefinition)) { m_Failed = true; return ERR_PrepareResult.FAILED; }
|
||||
}
|
||||
ERR_PrepareResult placement = m_Generated.Tick();
|
||||
if (placement == ERR_PrepareResult.FAILED) m_Failed = true;
|
||||
if (placement != ERR_PrepareResult.READY) return placement;
|
||||
if (!m_Sites)
|
||||
{
|
||||
m_Sites = new ERR_SiteService(mode);
|
||||
if (!m_Sites.Begin(m_RuntimeDefinition, FieldMap())) { m_Failed = true; return ERR_PrepareResult.FAILED; }
|
||||
}
|
||||
ERR_PrepareResult result = m_Sites.Prepare();
|
||||
if (result != ERR_PrepareResult.READY) return result;
|
||||
ERR_PrepareResult population = PrepareAdditionalPopulation();
|
||||
if (population == ERR_PrepareResult.FAILED) { m_Failed = true; m_Sites.Rollback(); }
|
||||
if (population != ERR_PrepareResult.READY) return population;
|
||||
if (!PrepareAdditionalForces())
|
||||
{
|
||||
m_Failed = true;
|
||||
m_Sites.Rollback();
|
||||
return ERR_PrepareResult.FAILED;
|
||||
}
|
||||
mode.OnPhaseChanged().Insert(m_Sites.PhaseChanged);
|
||||
mode.OnStop().Insert(m_Sites.Stop);
|
||||
mode.OnTick().Insert(m_Sites.Tick);
|
||||
GetGame().GetCallqueue().CallLater(ActivateSites, 2000, false);
|
||||
m_Finished = true;
|
||||
return ERR_PrepareResult.READY;
|
||||
}
|
||||
protected void ActivateSites()
|
||||
{
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (mode && mode.Active() && m_Sites) m_Sites.Activate();
|
||||
}
|
||||
protected ERR_PrepareResult PrepareAdditionalPopulation() { return ERR_PrepareResult.READY; }
|
||||
protected bool PrepareAdditionalForces() { return true; }
|
||||
protected ResourceName FieldMap() { return ERR_Supplies.Map(); }
|
||||
protected override void OnDelete(IEntity owner)
|
||||
{
|
||||
if (GetGame()) GetGame().GetCallqueue().Remove(ActivateSites);
|
||||
if (m_Sites) m_Sites.Stop();
|
||||
super.OnDelete(owner);
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
#ifdef WORKBENCH
|
||||
// Test access only: trigger the real delivered-order method on an existing
|
||||
// registered squad. Radio transit is intentionally not simulated by this test.
|
||||
modded class ERR_HQ
|
||||
{
|
||||
bool ERR_TestPatrolSearch(SCR_AIGroup group, out AIWaypoint search)
|
||||
{
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit.Group != group) continue;
|
||||
if (unit.Assignment || unit.PendingOrder || unit.Waypoint || !unit.Eligible(m_Mode)) return false;
|
||||
ERR_HQReport order = new ERR_HQReport();
|
||||
order.Target = group;
|
||||
order.Source = group;
|
||||
order.Position = group.GetOrigin() + "40 0 0";
|
||||
order.ObservedAt = GetGame().GetWorld().GetWorldTime();
|
||||
order.MessageId = m_Journal.Message();
|
||||
Accept(order, order.ObservedAt);
|
||||
ERR_HQBelief belief = m_Memory.Get(group);
|
||||
if (!belief) return false;
|
||||
ERR_HQTask task = new ERR_HQTask();
|
||||
task.BeliefId = belief.BeliefId; task.BeliefRevision = belief.BeliefRevision;
|
||||
task.SearchRadiusMetres = ERR_HQPursuitRules.SearchRadius(belief.Radius);
|
||||
task.Destination = order.Position; task.QueuedAt = order.ObservedAt;
|
||||
task.ExpiresAt = order.ObservedAt + 120000;
|
||||
unit.PendingTask = task;
|
||||
unit.PendingOrder = order;
|
||||
DeliverOrder(unit, order.ObservedAt);
|
||||
search = unit.Waypoint;
|
||||
return search && unit.Assignment == order;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool ERR_TestPatrolRelease(SCR_AIGroup group, AIWaypoint search)
|
||||
{
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit.Group != group || unit.Waypoint != search) continue;
|
||||
unit.Release();
|
||||
return !unit.Assignment && !unit.PendingOrder && !unit.Waypoint;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool ERR_TestPatrolCombat(SCR_AIGroup group, AIWaypoint search, bool contact, float now, int expected)
|
||||
{
|
||||
foreach (ERR_HQUnit unit : m_Units)
|
||||
{
|
||||
if (unit.Group != group || unit.Waypoint != search) continue;
|
||||
return unit.InfantryCombat.Update(unit, unit.Eligible(m_Mode), contact, now) == expected;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class ERR_NativePatrolHQProbe
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ERR_SiteRuntime m_Site;
|
||||
protected SCR_AIGroup m_Group;
|
||||
protected AIWaypoint m_Search;
|
||||
protected ref array<AIWaypoint> m_Baseline = {};
|
||||
protected int m_Step;
|
||||
protected float m_Wait;
|
||||
void ERR_NativePatrolHQProbe(ERR_GameMode mode) { m_Mode = mode; }
|
||||
protected bool Check(bool condition, string detail)
|
||||
{
|
||||
if (condition) { Print("ERR SMOKE PASS: patrol HQ " + detail); return true; }
|
||||
Print("ERR SMOKE FAIL: patrol HQ " + detail, LogLevel.ERROR); return false;
|
||||
}
|
||||
int Tick(float now)
|
||||
{
|
||||
if (now < m_Wait) return 0;
|
||||
ERR_HQ hq = ERR_HQ.Get();
|
||||
if (!Check(hq != null, "real HQ available")) return -1;
|
||||
if (m_Step == 0)
|
||||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
if (m_Step == 1)
|
||||
{
|
||||
array<AIWaypoint> queue = {};
|
||||
m_Group.GetWaypoints(queue);
|
||||
if (!Check(m_Search && !queue.IsEmpty() && queue[0] == m_Search && m_Group.GetCurrentWaypoint() == m_Search, "Search preempts native patrol")) return -1;
|
||||
foreach (AIWaypoint waypoint : m_Baseline)
|
||||
if (!Check(queue.Contains(waypoint), "preempted baseline route remains queued")) return -1;
|
||||
if (!Check(hq.ERR_TestPatrolCombat(m_Group, m_Search, true, now, 1), "injected local contact pauses actual HQ search")) return -1;
|
||||
queue.Clear(); m_Group.GetWaypoints(queue);
|
||||
if (!Check(!queue.Contains(m_Search), "combat removes HQ search from native queue without deleting it")) return -1;
|
||||
foreach (AIWaypoint home : m_Baseline)
|
||||
if (!Check(queue.Contains(home), "combat preserves baseline patrol ownership")) return -1;
|
||||
if (!Check(hq.ERR_TestPatrolCombat(m_Group, m_Search, false, now + 14999, 0), "quiet grace period prevents immediate marching")) return -1;
|
||||
if (!Check(hq.ERR_TestPatrolCombat(m_Group, m_Search, false, now + 15000, 2), "quiet period resumes the same unexpired assignment")) return -1;
|
||||
queue.Clear(); m_Group.GetWaypoints(queue);
|
||||
if (!Check(!queue.IsEmpty() && queue[0] == m_Search, "resumed search is queued exactly once at the front")) return -1;
|
||||
if (!Check(hq.ERR_TestPatrolRelease(m_Group, m_Search), "real HQ release removes only its assignment")) return -1;
|
||||
m_Step = 2; m_Wait = now + 750; return 0;
|
||||
}
|
||||
if (m_Step == 2)
|
||||
{
|
||||
AIWaypoint current = m_Group.GetCurrentWaypoint();
|
||||
if (!Check(current && m_Baseline.Contains(current), "native home defence returns after Search release")) return -1;
|
||||
m_Step = 3; return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
ERR_HQ hq = ERR_HQ.Get();
|
||||
if (hq && m_Site && m_Search) hq.ERR_TestPatrolRelease(m_Group, m_Search);
|
||||
}
|
||||
void ~ERR_NativePatrolHQProbe() { Stop(); }
|
||||
}
|
||||
#endif
|
||||
+1165
File diff suppressed because it is too large
Load Diff
+157
@@ -0,0 +1,157 @@
|
||||
"""Actual native-fixture startup/deadline gate, engine boundaries doubled."""
|
||||
import os,sys,re
|
||||
from pathlib import Path
|
||||
REPO=Path(os.environ.get('ERR_REPO', Path(__file__).resolve().parents[1]))
|
||||
sys.path.insert(0,str(REPO/'tests'))
|
||||
from policy_harness import body,run
|
||||
p=Path(os.environ.get('ERR_MEMORY_PROBE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQMemoryProbe.c'))
|
||||
s=body(p,'int Tick(ERR_HQ hq, float now)')
|
||||
# Execute the unchanged deadline/startup prefix; payload/native radio assertions
|
||||
# after this gate remain the full fixture's responsibility.
|
||||
s=s[:s.index(' if (!m_ValueChecked)')]+ ' }\n return 1;\n }'
|
||||
s=s.replace('ERR_HQ hq','ERR_HQ* hq').replace('hq.','hq->')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
using string = std::string;
|
||||
struct CLI {string outcome="hq-memory"; void GetCLIParam(string,string& value){value=outcome;}} System;
|
||||
struct ERR_HQ{bool ready=true;bool ERR_ProbeSquadsReady(){return ready;}};
|
||||
struct Probe{bool enabled=true,m_Mode=true,m_Failed=false,m_AutoChecked=false;int m_Step=0,automatic=0,checks=0;float m_Deadline=0;bool Enabled(){return enabled;}void Check(bool,std::string){checks++;m_Failed=true;}int AutomaticConvoy(ERR_HQ*){if(automatic==1)m_AutoChecked=true;return automatic;}
|
||||
METHOD
|
||||
};
|
||||
int main(){ERR_HQ hq;
|
||||
Probe p;assert(p.Tick(&hq,1000)==0&&p.m_Deadline==151000);assert(p.Tick(&hq,130000)==0&&p.m_Deadline==151000);p.automatic=1;assert(p.Tick(&hq,145000)==1&&p.m_Deadline==265000);assert(p.Tick(&hq,200000)==1&&p.m_Deadline==265000);assert(p.Tick(&hq,265000)==1);assert(p.Tick(&hq,265001)==-1&&p.checks==1);
|
||||
Probe missing;assert(missing.Tick(&hq,0)==0);assert(missing.Tick(&hq,150001)==-1&&missing.checks==1);
|
||||
Probe refused;refused.Tick(&hq,0);refused.automatic=-1;assert(refused.Tick(&hq,40000)==-1&&refused.m_Deadline==150000&&!refused.m_AutoChecked);
|
||||
Probe unavailable;hq.ready=false;assert(unavailable.Tick(&hq,0)==0);assert(unavailable.Tick(&hq,150001)==-1);hq.ready=true;
|
||||
System.outcome="hq-awareness"; hq.ready=false; Probe awareness; assert(awareness.Tick(&hq,1000)==1 && !awareness.m_AutoChecked && awareness.m_Deadline==151000); assert(awareness.Tick(&hq,151001)==-1);
|
||||
Probe invalid;invalid.enabled=false;assert(invalid.Tick(&hq,0)==-1&&invalid.m_Deadline==0);
|
||||
}
|
||||
'''.replace('METHOD',s))
|
||||
smoke=Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c')).read_text()
|
||||
assert 'if (m_ERRTestHQMemory) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 360000;' in smoke
|
||||
assert 'if (m_ERRTestHQBody) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 240000;' in smoke
|
||||
# Execute the actual startup selector, so enlarged full-route budgets cannot
|
||||
# accidentally extend a death/radio test or shorten an existing specialized run.
|
||||
start=smoke.index('m_ERRTestLostHelicopter = System.GetCLIParam(')
|
||||
end=smoke.index('OnStop().Insert(ERR_StopHQPatrol);',start)
|
||||
selector=smoke[start:end].replace('System.GetCLIParam("errSmokeOutcome", outcome) && ', '')
|
||||
selector=selector.replace('GetWorld().GetWorldTime()', 'now').replace('ERR_NativeConvoyCoverProbe.Enabled()', 'cover').replace('ERR_NativeMountedCourierProbe.Enabled()', 'courier').replace('ERR_NativeMaintenanceProbe.Enabled()', 'maintenance').replace('ERR_NativeMountedInterceptProbe.Enabled()', 'intercept')
|
||||
flags=sorted(set(re.findall(r'\bm_ERRTest\w+',selector)))
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
using string = std::string;
|
||||
float deadline(string outcome,float now,bool cover=false,bool courier=false,bool maintenance=false,bool intercept=false){
|
||||
[[maybe_unused]] bool DECLARATIONS;
|
||||
float m_ERRSmokeDeadline=0;
|
||||
SELECTOR
|
||||
return m_ERRSmokeDeadline;
|
||||
}
|
||||
int main(){
|
||||
for(float now : {0.0f, 42000.0f}) {
|
||||
for(string outcome : {"", "victory", "lost-helicopter"}) assert(deadline(outcome,now)==now+300000);
|
||||
for(string outcome : {"escape-route", "normal-escape-route", "player-death", "radio-network", "hq-response", "map-ownership"}) assert(deadline(outcome,now)==now+150000);
|
||||
assert(deadline("patrol-hq",now)==now+420000);
|
||||
assert(deadline("hq-memory",now)==now+360000);
|
||||
assert(deadline("hq-body",now)==now+240000);
|
||||
assert(deadline("convoy",now)==now+540000);
|
||||
assert(deadline("convoy",now,false,false,false,true)==now+840000);
|
||||
assert(deadline("supply-convoys",now)==now+840000);
|
||||
assert(deadline("convoy",now,true)==now+840000);
|
||||
assert(deadline("mounted-ammo",now)==now+330000);
|
||||
assert(deadline("mounted-ammo",now,false,true)==now+750000);
|
||||
assert(deadline("normal-escape-route",now,false,false,true)==now+330000);
|
||||
}
|
||||
}
|
||||
'''.replace('DECLARATIONS', ','.join(flag+'=false' for flag in flags)).replace('SELECTOR',selector))
|
||||
print('PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines')
|
||||
|
||||
# The new route diagnostic must leave the original administrator/reroll stress
|
||||
# intact and may skip only those extras, never clue or radio progression gates.
|
||||
route=body(Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c')), 'bool ERR_CheckRouteExtras(float now)').replace('ERR_NativeMaintenanceProbe.Enabled()', 'maintenance')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
struct Probe {
|
||||
bool m_ERRTestEscapeRoute=false,rerollReady=false,maintenance=false,maintenanceReady=false;
|
||||
int maintenanceCalls=0; bool ERR_RunMaintenance(float){maintenanceCalls++;return maintenanceReady;}
|
||||
int adminResult=0,adminCalls=0,rerollCalls=0,failures=0;
|
||||
int ERR_ProbeAdminMenu(float){adminCalls++;return adminResult;}
|
||||
bool ERR_Check(bool result,std::string){if(!result)failures++;return result;}
|
||||
bool ERR_CheckIntelReroll(float){rerollCalls++;return rerollReady;}
|
||||
METHOD
|
||||
};
|
||||
int main(){
|
||||
Probe route;route.m_ERRTestEscapeRoute=true;assert(route.ERR_CheckRouteExtras(100));
|
||||
assert(route.adminCalls==0&&route.rerollCalls==0&&route.failures==0);
|
||||
route.maintenance=true;assert(!route.ERR_CheckRouteExtras(101)&&route.maintenanceCalls==1);
|
||||
route.maintenanceReady=true;assert(route.ERR_CheckRouteExtras(102)&&route.maintenanceCalls==2&&route.adminCalls==0&&route.rerollCalls==0);
|
||||
Probe full;assert(!full.ERR_CheckRouteExtras(100)&&full.adminCalls==1&&full.rerollCalls==0);
|
||||
full.adminResult=-1;assert(!full.ERR_CheckRouteExtras(200)&&full.failures==1&&full.rerollCalls==0);
|
||||
full.adminResult=1;assert(!full.ERR_CheckRouteExtras(300)&&full.rerollCalls==1);
|
||||
full.rerollReady=true;assert(full.ERR_CheckRouteExtras(400)&&full.rerollCalls==2);
|
||||
}
|
||||
'''.replace('METHOD',route))
|
||||
assert 'if (!ERR_CheckRouteExtras(now)) return;' in smoke
|
||||
assert 'if (!ERR_Check(m_ERRSmokeRadio.Knowledge() == 2,' in smoke
|
||||
assert 'if (!ERR_Check(Phase() == ERR_Phase.EXTRACTION,' in smoke
|
||||
print('PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths')
|
||||
|
||||
# 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()
|
||||
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)
|
||||
expression=line[4:-1].replace('System.GetCLIParam("errSmokeOutcome", '+variable+')','supplied').replace(variable,'outcome')
|
||||
conditions.append(expression)
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
using string=std::string;
|
||||
bool legacy(bool supplied,string outcome,bool m_TerrainIndependent=false){return LEGACY;}
|
||||
bool fixed(bool supplied,string outcome,bool m_TerrainIndependent=false){return FIXED;}
|
||||
int main(){
|
||||
for(string route : {"normal-escape-route","generated-sites","optional-site-skip"}) {assert(!legacy(true,route));assert(!fixed(true,route));}
|
||||
for(string route : {"escape-route","victory","transport-survey"}) {assert(legacy(true,route));assert(fixed(true,route));}
|
||||
assert(!legacy(false,"escape-route")&&!fixed(false,"escape-route"));
|
||||
}
|
||||
'''.replace('LEGACY',conditions[0]).replace('FIXED',conditions[1]))
|
||||
print('PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged')
|
||||
|
||||
# Execute the actual patrol-hq admission branch. The existing production
|
||||
# prison-AI/progression tests cover canonical UnlockPrison and phase callbacks;
|
||||
# here native damage, allocation and unlock boundaries are explicit doubles.
|
||||
smoke_path=Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c'))
|
||||
smoke_tick=body(smoke_path,'protected void ERR_SmokeTick(')
|
||||
a=smoke_tick.index('if (m_ERRTestPatrolHQ)'); b=smoke_tick.index('{',a); i=b+1; depth=1
|
||||
while depth:
|
||||
depth+=(smoke_tick[i]=='{')-(smoke_tick[i]=='}');i+=1
|
||||
patrol_branch=smoke_tick[a:i]
|
||||
patrol_branch=patrol_branch.replace('DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);','ProtectOriginal(m_ERRSmokePlayer);').replace('ERR_Phase.RADIO_SEARCH','2')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
struct Fixture;
|
||||
struct ERR_NativePatrolProbe {explicit ERR_NativePatrolProbe(Fixture* owner);};
|
||||
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_NativePatrolProbe* m_ERRHQPatrolMovement=nullptr;
|
||||
~Fixture(){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);
|
||||
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')
|
||||
Reference in New Issue
Block a user