Files
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

572 lines
41 KiB
Diff

--- a/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c
+++ b/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c
@@ -70,6 +70,7 @@
{
protected ERR_GameMode m_Mode;
protected ref ERR_NativeConvoyCoverProbe m_Cover;
+ protected ref ERR_NativeMountedInterceptProbe m_Intercept;
protected ref ERR_ConvoyTask m_Task;
protected ref array<vector> m_Origins = {};
protected ref array<IEntity> m_OriginalVehicles = {};
@@ -297,6 +298,9 @@
int Tick(ERR_HQ hq, float now)
{
if (!Enabled() || !hq || !m_Mode || m_Mode != ERR_GameMode.Get() || m_Failed) return -1;
+ if (ERR_NativeMountedInterceptProbe.Requested() && ERR_NativeConvoyCoverProbe.Enabled())
+ { Check(false, "mounted-intercept and convoy-cover fixtures are mutually exclusive"); return -1; }
+ if (m_Step == 5) return m_Intercept.Tick(now);
if (m_Step == 4) return m_Cover.Tick(hq, now);
if (m_Deadline == 0) m_Deadline = now + 480000;
if (now >= m_Deadline) { Check(false, "native fixture deadline"); return -1; }
@@ -391,6 +395,10 @@
&& m_Mode.Forces().Count() == m_InitialLedger + 6 && m_Task.Cargo.Count() == 56,
"later repeated dispatch preserves exact task, force and cargo counts")) return -1;
Check(true, "all six native vehicles drove at least 20m and leader at least 100m without fixture movement");
+ if (ERR_NativeMountedInterceptProbe.Enabled())
+ {
+ m_Intercept = new ERR_NativeMountedInterceptProbe(m_Mode, hq, m_Task, now); m_Step = 5; return 0;
+ }
if (ERR_NativeConvoyCoverProbe.Enabled())
{
m_Cover = new ERR_NativeConvoyCoverProbe(m_Mode); m_Step = 4;
@@ -398,6 +406,6 @@
}
return 1;
}
- void Stop() { if (m_Cover) m_Cover.Stop(); }
+ void Stop() { if (m_Intercept) m_Intercept.Stop(); if (m_Cover) m_Cover.Stop(); }
}
#endif
--- /dev/null
+++ b/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedInterceptProbe.c
@@ -0,0 +1,258 @@
+#ifdef WORKBENCH
+// Two explicitly synthetic witness inbox samples; actual originating call,
+// radio hops, HQ intake/planning and downlink remain production-owned.
+modded class ERR_HQ
+{
+ ERR_HQRoute ERR_InterceptFixtureQueue(ERR_HQReport report)
+ {
+ if (!ERR_NativeMountedInterceptProbe.Enabled() || !report) return null;
+ foreach (ERR_HQUnit unit : m_Units)
+ {
+ if (!unit.Group || unit.ReservedForSupport || !unit.Radio || unit.Uplink || unit.PendingReport || unit.PendingSweep) continue;
+ if (unit.PendingSupplyStatus || unit.PendingStatus || !unit.LocalAwareness) continue;
+ ERR_HQRoute route = m_Network.Route(unit.Radio);
+ if (!route || route.Nodes.IsEmpty()) continue;
+ bool healthy = true;
+ for (int i = 0; i < route.Nodes.Count(); i++)
+ if (!route.Nodes[i] || !route.Nodes[i].Valid(i == route.Nodes.Count() - 1)) healthy = false;
+ if (!healthy) continue;
+ report.Source = unit.Group;
+ unit.LocalAwareness.Clear(); unit.LocalAwareness.Remember(report, report.ObservedAt);
+ unit.NextContact = 0;
+ ContactReport(unit, GetGame().GetWorld().GetWorldTime());
+ if (!unit.Uplink || !unit.PendingReport || unit.PendingReport.Target != report.Target) return null;
+ report.MessageId = unit.PendingReport.MessageId;
+ return unit.Uplink;
+ }
+ return null;
+ }
+ protected override void TickMountedIntercept(float now)
+ {
+ ERR_NativeMountedInterceptProbe proof = ERR_NativeMountedInterceptProbe.Active();
+ ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
+ ref ERR_HQRoute route = m_MountedInterceptRoute;
+ bool delivered = m_MountedInterceptDelivered;
+ if (proof && order && !delivered) proof.Pending(order, route, m_MountedInterceptTask, now);
+ super.TickMountedIntercept(now);
+ if (proof && order && !delivered && m_MountedIntercept == order && m_MountedInterceptDelivered)
+ proof.Delivered(order, route, now);
+ }
+}
+
+class ERR_NativeMountedInterceptProbe
+{
+ protected static ERR_NativeMountedInterceptProbe s_Active;
+ protected ERR_GameMode m_Mode;
+ protected ERR_HQ m_HQ;
+ protected ref ERR_ConvoyTask m_Task;
+ protected ref array<IEntity> m_Vehicles = {};
+ protected ref array<IEntity> m_Crew = {};
+ protected ref array<SCR_AIGroup> m_Groups = {};
+ protected ref ERR_HQReport m_First;
+ protected ref ERR_HQReport m_Second;
+ protected ref ERR_HQRoute m_FirstRoute;
+ protected ref ERR_HQRoute m_SecondRoute;
+ protected ref ERR_HQRoute m_Downlink;
+ protected ref ERR_ConvoyInterceptOrder m_Order;
+ protected ERR_ConvoyVehicle m_Intercept;
+ protected AIWaypoint m_Detour;
+ protected vector m_Direction;
+ protected vector m_FirstPosition;
+ protected vector m_DeliveryPosition;
+ protected vector m_HoldPosition;
+ protected vector m_ResumePosition;
+ protected vector m_Destination;
+ protected float m_StartedAt;
+ protected float m_Deadline;
+ protected int m_Token;
+ protected int m_BeliefId;
+ protected int m_BeliefRevision;
+ protected int m_ReportId;
+ protected float m_ExpiresAt;
+ protected float m_DeliveredAt = -1;
+ protected float m_HoldAt = -1;
+ protected float m_ResumeAt = -1;
+ protected float m_NextLog;
+ protected int m_LedgerCount;
+ protected int m_RouteIndex;
+ protected int m_Step;
+ protected bool m_Held;
+ protected bool m_Failed;
+ protected bool m_Stopped;
+ static bool Requested()
+ {
+ string flag;
+ return ERR_NativeConvoyProbe.Enabled() && System.GetCLIParam("errMountedIntercept", flag) && flag == "1";
+ }
+ static string CompletionText(bool intercept)
+ {
+ if (intercept) return "ERR SMOKE COMPLETE: native mounted interception with two synthetic observations, actual radio and physical detour/hold/expiry/resume; explicit fixture reset";
+ return "ERR SMOKE COMPLETE: native convoy readiness, cargo and road movement; explicit fixture reset";
+ }
+ static bool Enabled()
+ {
+ string flag, conflict;
+ return ERR_NativeConvoyProbe.Enabled() && System.GetCLIParam("errMountedIntercept", flag) && flag == "1"
+ && (!System.GetCLIParam("errConvoyCover", conflict) || conflict != "1");
+ }
+ static ERR_NativeMountedInterceptProbe Active() { return s_Active; }
+ void ERR_NativeMountedInterceptProbe(ERR_GameMode mode, ERR_HQ hq, ERR_ConvoyTask task, float now)
+ {
+ m_Mode = mode; m_HQ = hq; m_Task = task; m_StartedAt = now; m_Deadline = now + 240000;
+ s_Active = this;
+ if (!mode || !hq || !task || !task.Ready || task.Stopped || task.Vehicles.Count() != 6 || !task.Route || task.Route.Count() < 2)
+ { Check(false, "original ready finite convoy unavailable"); return; }
+ if (!Check(mode.Forces() != null, "original force ledger available")) return;
+ m_LedgerCount = mode.Forces().Count();
+ foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
+ {
+ if (!Check(vehicle && vehicle.Entity && vehicle.Group && vehicle.Members.Count() <= 19, "bounded original vehicle and crew identities")) return;
+ m_Vehicles.Insert(vehicle.Entity); m_Groups.Insert(vehicle.Group);
+ foreach (IEntity actor : vehicle.Members) m_Crew.Insert(actor);
+ }
+ int target = Math.Min(task.Vehicles[0].RouteIndex + 1, task.Route.Count() - 1);
+ if (target < 1) target = 1;
+ m_Direction = task.Route.At(target) - task.Route.At(target - 1); m_Direction[1] = 0;
+ if (!Check(m_Direction.Length() >= 40, "authored road leg provides synthetic sample direction")) return;
+ m_Direction.Normalize(); m_FirstPosition = task.Route.At(target) - m_Direction * 330;
+ Print("ERR MOUNTED INTERCEPT ARRANGEMENT: two synthetic on-foot witness inbox positions derived from original authored route; no entity movement, perception claim or injected delivery");
+ }
+ protected bool Check(bool condition, string detail)
+ {
+ if (condition) { Print("ERR SMOKE PASS: mounted intercept " + detail); return true; }
+ m_Failed = true; Print("ERR SMOKE FAIL: mounted intercept " + detail, LogLevel.ERROR); return false;
+ }
+ protected bool Original()
+ {
+ if (!m_Mode || m_Mode != ERR_GameMode.Get() || m_Mode.Command() != m_HQ || !m_Mode.Active() || !m_Mode.Forces()) return false;
+ if (!m_Task || m_Task.Stopped || !m_Task.Ready || m_Task.Vehicles.Count() != 6 || m_Task.Cargo.Count() != 56) return false;
+ if (m_Vehicles.Count() != 6 || m_Groups.Count() != 6 || m_Mode.Forces().Count() != m_LedgerCount) return false;
+ int crew;
+ for (int i = 0; i < 6; i++)
+ {
+ ERR_ConvoyVehicle vehicle = m_Task.Vehicles[i];
+ if (!vehicle || vehicle.Entity != m_Vehicles[i] || vehicle.Group != m_Groups[i] || vehicle.Released || !vehicle.CrewValid(m_Mode)) return false;
+ foreach (IEntity actor : vehicle.Members)
+ { if (crew >= m_Crew.Count() || m_Crew[crew] != actor) return false; crew++; }
+ }
+ return crew == m_Crew.Count();
+ }
+ protected ERR_HQReport Sample(float now, vector position)
+ {
+ ERR_HQReport report = new ERR_HQReport(); report.Target = m_Mode;
+ report.ObservedAt = now; report.Position = position; report.TravelMode = "on foot";
+ return report;
+ }
+ protected bool UplinkDelivered(ERR_HQReport report, ERR_HQRoute route)
+ {
+ if (!report || !report.Source || !route || route.StartedAt < report.ObservedAt || route.Downlink || route.Failed || !route.Originated || route.Sending || route.Nodes.IsEmpty()) return false;
+ if (route.MessageId != report.MessageId || route.Cursor < route.Nodes.Count()) return false;
+ ERR_HQBelief belief = m_HQ.Memory().Get(m_Mode);
+ return belief && belief.MessageId == report.MessageId && belief.ObservedAt == report.ObservedAt && belief.Position == report.Position;
+ }
+ void Pending(ERR_ConvoyInterceptOrder order, ERR_HQRoute route, ERR_ConvoyTask task, float now)
+ {
+ if (m_Stopped || m_Failed || m_Order) return;
+ if (!m_Second || !Check(task == m_Task && order.ReportId == m_Second.MessageId && UplinkDelivered(m_Second, m_SecondRoute), "planner uses the second actually delivered synthetic sample")) return;
+ if (!Check(route && route.Downlink && !route.Failed && !route.Nodes.IsEmpty(), "real HQ downlink queued")) return;
+ m_Token = order.Token; m_BeliefId = order.BeliefId; m_BeliefRevision = order.BeliefRevision; m_ReportId = order.ReportId;
+ m_Order = order; m_Downlink = route; m_ExpiresAt = order.ExpiresAt; m_Destination = order.Position;
+ if (!Check(order.VehicleIndex == 0 || order.VehicleIndex == 3 || order.VehicleIndex == 5, "original armed escort selected")) return;
+ m_Intercept = task.Vehicles[order.VehicleIndex];
+ if (!Check(m_Intercept.Entity == m_Vehicles[order.VehicleIndex] && m_Intercept.InterceptOrder == order
+ && m_Intercept.InterceptToken == order.Token && !m_Intercept.InterceptActive && !m_Intercept.InterceptWaypoint,
+ "pending radio claim has no delivered detour waypoint")) return;
+ Check(m_ExpiresAt > now && m_ExpiresAt <= m_Second.ObservedAt + 120000, "fixed original observation deadline retained");
+ }
+ void Delivered(ERR_ConvoyInterceptOrder order, ERR_HQRoute route, float now)
+ {
+ if (m_Stopped || m_Failed || m_DeliveredAt >= 0) return;
+ if (!Check(order && route && m_Order && m_Intercept && order == m_Order && route == m_Downlink && !route.Failed && !route.Sending
+ && route.StartedAt >= order.IssuedAt && route.Cursor == route.Nodes.Count() - 1 && now > route.StartedAt,
+ "ordinary HQ tick completes actual downlink before detour execution")) return;
+ if (!Check(Original() && m_Intercept.InterceptActive && m_Intercept.InterceptWaypoint && !m_Intercept.Waypoint,
+ "same original escort owns separate native detour waypoint")) return;
+ m_Detour = m_Intercept.InterceptWaypoint; m_RouteIndex = m_Intercept.RouteIndex;
+ m_DeliveryPosition = m_Intercept.Entity.GetOrigin(); m_DeliveredAt = now;
+ Check(vector.DistanceXZ(m_DeliveryPosition, m_Destination) >= 40, "delivery starts at least40m from fixed road target");
+ }
+ int Tick(float now)
+ {
+ if (!Enabled() || m_Stopped || m_Failed) return -1;
+ if (!CheckTime(now)) return -1;
+ if (!Original()) { Check(false, "original physical task, vehicles, groups, crew and finite cargo retained"); return -1; }
+ if (now >= m_NextLog)
+ {
+ m_NextLog = now + 10000;
+ PrintFormat("ERR MOUNTED INTERCEPT NATIVE: step=%1 first=%2 second=%3 order=%4 delivered=%5 held=%6", m_Step, m_First != null, m_Second != null, m_Order != null, m_DeliveredAt >= 0, m_Held);
+ if (m_Intercept) PrintFormat("ERR MOUNTED INTERCEPT MOTION: position=%1 target=%2 distance=%3 index=%4 active=%5 expires=%6", m_Intercept.Entity.GetOrigin(), m_Destination, vector.DistanceXZ(m_Intercept.Entity.GetOrigin(), m_Destination), m_Intercept.RouteIndex, m_Intercept.InterceptActive, m_ExpiresAt);
+ }
+ if (m_Step == 0)
+ {
+ ERR_HQReport first = Sample(now, m_FirstPosition);
+ ERR_HQRoute firstRoute = m_HQ.ERR_InterceptFixtureQueue(first);
+ if (!firstRoute) return 0;
+ m_First = first; m_FirstRoute = firstRoute; m_Step = 1;
+ Check(!m_HQ.Memory().Get(m_Mode), "synthetic target has no HQ memory before actual uplink"); return 0;
+ }
+ if (m_Step == 1)
+ {
+ if (!UplinkDelivered(m_First, m_FirstRoute)) return 0;
+ if (now - m_First.ObservedAt < 5000) return 0;
+ if (!Check(now - m_First.ObservedAt <= 60000, "two synthetic samples have bounded credible interval")) return -1;
+ ERR_HQReport second = Sample(now, m_FirstPosition + m_Direction * ((now - m_First.ObservedAt) * 0.0025));
+ ERR_HQRoute secondRoute = m_HQ.ERR_InterceptFixtureQueue(second);
+ if (!secondRoute) return 0;
+ m_Second = second; m_SecondRoute = secondRoute; m_Step = 2;
+ Check(true, "first synthetic sample actually delivered; second queued through original witness radio"); return 0;
+ }
+ if (m_DeliveredAt < 0) return 0;
+ if (now < m_ExpiresAt)
+ {
+ if (m_Intercept.InterceptOrder != m_Order || !m_Intercept.InterceptActive || m_Intercept.InterceptToken != m_Token || m_Order.Token != m_Token || m_Order.BeliefId != m_BeliefId || m_Order.BeliefRevision != m_BeliefRevision || m_Order.ReportId != m_ReportId
+ || m_Intercept.RouteIndex != m_RouteIndex || m_Order.Position != m_Destination || m_Order.ExpiresAt != m_ExpiresAt)
+ { Check(false, "delivered original detour changed before fixed expiry"); return -1; }
+ float distance = vector.DistanceXZ(m_Intercept.Entity.GetOrigin(), m_Destination);
+ bool settled = !m_Intercept.InterceptWaypoint && distance <= 15;
+ if (m_HoldAt >= 0 && vector.DistanceXZ(m_HoldPosition, m_Intercept.Entity.GetOrigin()) > 5) settled = false;
+ if (!settled)
+ {
+ if (m_Held) { Check(false, "escort left its proved hold before fixed expiry"); return -1; }
+ m_HoldAt = -1;
+ }
+ if (settled)
+ {
+ if (m_HoldAt < 0)
+ {
+ if (!Check(vector.DistanceXZ(m_DeliveryPosition, m_Intercept.Entity.GetOrigin()) >= 20, "original escort physically drove to fixed detour target")) return -1;
+ m_HoldAt = now; m_HoldPosition = m_Intercept.Entity.GetOrigin();
+ }
+ if (!m_Held && now - m_HoldAt >= 5000)
+ {
+ if (!Check(vector.DistanceXZ(m_HoldPosition, m_Intercept.Entity.GetOrigin()) <= 5, "original seated escort physically holds target for five seconds")) return -1;
+ m_Held = true;
+ }
+ }
+ return 0;
+ }
+ if (!m_Held) { Check(false, "fixed expiry reached before physical detour arrival and hold proof"); return -1; }
+ if (m_Intercept.InterceptToken != 0 || m_Intercept.InterceptActive || m_Intercept.InterceptWaypoint || m_HQ.LogisticsToken(m_Intercept.Group) != 0) return 0;
+ if (m_ResumeAt < 0)
+ {
+ if (!m_Intercept.Waypoint) return 0;
+ if (!Check(m_Intercept.RouteIndex >= m_RouteIndex, "natural expiry restores original route without rewinding progress")) return -1;
+ m_ResumeAt = now; m_ResumePosition = m_Intercept.Entity.GetOrigin(); return 0;
+ }
+ if (vector.DistanceXZ(m_ResumePosition, m_Intercept.Entity.GetOrigin()) < 10) return 0;
+ if (!Check(Original() && m_Intercept.RouteIndex >= m_RouteIndex, "same original escort physically resumes at least10m after natural expiry")) return -1;
+ Check(true, "two synthetic observations, actual uplinks, production planning/downlink, physical detour/hold/expiry/resume; no natural perception claim");
+ return 1;
+ }
+ protected bool CheckTime(float now)
+ {
+ if (ERR_ForceRules.Finite(now) && now >= m_StartedAt && now < m_Deadline) return true;
+ Check(false, "fixed240s interception fixture deadline; no retry or forced eligibility"); return false;
+ }
+ void Stop() { m_Stopped = true; if (s_Active == this) s_Active = null; }
+}
+#endif
--- a/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
+++ b/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
@@ -117,6 +117,7 @@
if (ERR_NativeMountedCourierProbe.Enabled()) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 750000;
if (ERR_NativeMaintenanceProbe.Enabled()) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;
if (m_ERRTestConvoy) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 540000;
+ if (ERR_NativeMountedInterceptProbe.Enabled()) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 840000;
if (outcome == "supply-convoys") m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 840000;
if (ERR_NativeConvoyCoverProbe.Enabled()) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 840000;
if (m_ERRTestHQMemory) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 360000;
@@ -414,7 +415,7 @@
m_ERRSmokeDone = true;
GetGame().GetCallqueue().Remove(ERR_SmokeTick);
ERR_StopHQPatrol();
- Print("ERR SMOKE COMPLETE: native convoy readiness, cargo and road movement; explicit fixture reset");
+ Print(ERR_NativeMountedInterceptProbe.CompletionText(ERR_NativeMountedInterceptProbe.Enabled()));
return;
}
if (m_ERRTestPlayerDeath)
@@ -1016,7 +1017,9 @@
{
int result = m_ERRConvoyProbe.Tick(ERR_HQ.Cast(Command()), now);
if (result == 0) return;
- if (!ERR_Check(result == 1, "native six-vehicle convoy readiness, cargo and road movement")) return;
+ string convoyVerdict = "native six-vehicle convoy readiness, cargo and road movement";
+ if (ERR_NativeMountedInterceptProbe.Enabled()) convoyVerdict = "synthetic observations, actual mounted interception radio and physical detour/hold/expiry/resume";
+ if (!ERR_Check(result == 1, convoyVerdict)) return;
m_ERRSmokeStep = 1061;
Transition(ERR_Phase.DEFEAT);
}
--- a/tests/test_native_convoy_cover_probe.py
+++ b/tests/test_native_convoy_cover_probe.py
@@ -111,12 +111,14 @@
method=body(P,signature);assert method.index(base)<method.index(hook)
assert 'entry.Report = unit.PendingReport.Snapshot()' in s
for forbidden in ('ApplyCombatOrder(', 'PendingReport = new', 'new ERR_HQRoute', 'SetAmmoCount(', 'TrySpawnPrefabToStorage(', 'SetOrigin('):assert forbidden not in s
-convoy=(ROOT/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c').read_text()
+convoy_path=Path(os.environ.get('ERR_CONVOY_PROBE_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c'))
+convoy=convoy_path.read_text()
assert convoy.index('all six native vehicles drove at least 20m')<convoy.index('m_Cover = new')
assert 'if (m_Step == 4) return m_Cover.Tick(hq, now);' in convoy
-assert 'void Stop() { if (m_Cover) m_Cover.Stop(); }' in convoy
-smoke=(ROOT/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c').read_text();assert 'if (m_ERRConvoyProbe) m_ERRConvoyProbe.Stop();' in smoke
-runner=ROOT/'tools/headless_workbench.py'
+stop=body(convoy_path,'void Stop(')
+assert 'if (m_Cover) m_Cover.Stop();' in stop and 'if (m_Intercept) m_Intercept.Stop();' in stop
+smoke=Path(os.environ.get('ERR_NATIVE_SMOKE_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c')).read_text();assert 'if (m_ERRConvoyProbe) m_ERRConvoyProbe.Stop();' in smoke
+runner=Path(os.environ.get('ERR_MOUNTED_INTERCEPT_RUNNER',ROOT/'tools/headless_workbench.py'))
for variant in ('victory','hq-body'):
result=subprocess.run([sys.executable,str(runner),'--smoke',variant,'--convoy-cover','--project','chat','--rounds','1','--timeout','900','--compat','/tmp/erri-unused-cover-compat','--profile','/tmp/erri-unused-cover-profile'],capture_output=True,text=True)
assert result.returncode==2 and '--convoy-cover requires --smoke convoy' in result.stderr,result.stderr
--- /dev/null
+++ b/tests/test_native_mounted_intercept.py
@@ -0,0 +1,190 @@
+"""Actual opt-in native interception witness and fixed lifecycle, with native boundaries doubled."""
+from pathlib import Path
+import os,re,ast,argparse
+from policy_harness import ROOT,body,run
+P=Path(os.environ.get('ERR_MOUNTED_INTERCEPT_PROBE',ROOT/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedInterceptProbe.c'))
+s=P.read_text();code=s[s.index('class ERR_NativeMountedInterceptProbe\n'):s.rindex('#endif')]
+code=code.replace('class ERR_NativeMountedInterceptProbe','struct ERR_NativeMountedInterceptProbe').replace('protected ','').replace('ref ','').replace('foreach (','for (').replace('array<','Array<')
+for t in ('ERR_NativeMountedInterceptProbe','ERR_GameMode','ERR_HQ','ERR_ConvoyTask','ERR_HQReport','ERR_HQRoute','ERR_ConvoyInterceptOrder','ERR_ConvoyVehicle','ERR_HQBelief'):
+ code=re.sub(r'\b'+t+r' (?=\w)',t+'* ',code)
+code=code.replace('static ERR_NativeMountedInterceptProbe* s_Active;', 'inline static ERR_NativeMountedInterceptProbe* s_Active=null;')
+code=code.replace('void ERR_NativeMountedInterceptProbe(', 'ERR_NativeMountedInterceptProbe(')
+for v in ('mode','hq','task','vehicle','report','route','order','belief','m_Mode','m_HQ','m_Task','m_First','m_Second','m_Intercept','m_Order'):
+ code=re.sub(r'\b'+v+r'\.',v+'->',code)
+for v in ('Route','Entity'):code=code.replace('->'+v+'.','->'+v+'->')
+code=code.replace('task->Vehicles[0].', 'task->Vehicles[0]->').replace('Forces().','Forces()->').replace('Memory().','Memory()->')
+for t in ('ERR_NativeConvoyProbe','ERR_NativeMountedInterceptProbe','System','Math','ERR_ForceRules','ERR_GameMode','LogLevel','vector'):code=code.replace(t+'.',t+'::')
+code=code.replace('null','nullptr').replace('\n}\n','\n};\n')
+code=re.sub(r'\b(bool|int|float|IEntity|SCR_AIGroup|AIWaypoint|[A-Za-z_]\w*\*) (\w+);',r'\1 \2{};',code)
+run(r'''
+#include <cassert>
+#include <cmath>
+#include <string>
+#include <map>
+#include <vector>
+using string=std::string;
+struct vector{float x=0,y=0,z=0;float&operator[](int i){return i==0?x:i==1?y:z;}vector operator-(vector b){return {x-b.x,y-b.y,z-b.z};}vector operator+(vector b){return{x+b.x,y+b.y,z+b.z};}vector operator*(float f){return{x*f,y*f,z*f};}bool operator==(vector b){return x==b.x&&y==b.y&&z==b.z;}bool operator!=(vector b){return !(*this==b);}float Length(){return std::hypot(x,z);}void Normalize(){float n=Length();x/=n;z/=n;}static float DistanceXZ(vector a,vector b){return(a-b).Length();}};
+template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Insert(T v){this->push_back(v);}};
+struct Entity{vector position;vector GetOrigin(){return position;}};using IEntity=Entity*;
+struct Group{};using SCR_AIGroup=Group*;struct Waypoint{};using AIWaypoint=Waypoint*;
+struct ERR_HQReport{IEntity Target=nullptr;Group*Source=nullptr;float ObservedAt=0;vector Position;string TravelMode;int MessageId=0;};
+struct ERR_HQRoute{bool Downlink=false,Failed=false,Originated=false,Sending=false;int MessageId=0,Cursor=0;float StartedAt=-1;Array<int>Nodes={1};};
+struct ERR_HQBelief{int MessageId=0;float ObservedAt=0;vector Position;};
+struct ERR_ConvoyInterceptOrder{int Token=400001,VehicleIndex=0,BeliefId=1,BeliefRevision=2,ReportId=2;Group*Group=nullptr;vector Position{700,0,0};float IssuedAt=10000,ExpiresAt=120000,ObservedAt=0;};
+struct Road{Array<vector>points={{0,0,0},{800,0,0}};int Count(){return points.Count();}vector At(int i){return points[i];}};
+struct ERR_GameMode;
+struct ERR_ConvoyVehicle{struct Entity*Entity=nullptr;struct Group*Group=nullptr;Array<IEntity>Members;bool Released=false,valid=true,InterceptActive=false;int RouteIndex=0,InterceptToken=0;ERR_ConvoyInterceptOrder*InterceptOrder=nullptr;AIWaypoint InterceptWaypoint=nullptr,Waypoint=nullptr;bool CrewValid(ERR_GameMode*){return valid;}};
+struct ERR_ConvoyTask{bool Ready=true,Stopped=false;Array<ERR_ConvoyVehicle*>Vehicles;Array<int>Cargo=Array<int>(56);struct Road road;struct Road*Route=&road;};
+struct Ledger{int count=6;int Count(){return count;}};
+struct ERR_HQ;struct ERR_GameMode:Entity{inline static ERR_GameMode*current=nullptr;ERR_HQ*command=nullptr;Ledger ledger;bool active=true;static ERR_GameMode*Get(){return current;}bool Active(){return active;}ERR_HQ*Command(){return command;}Ledger*Forces(){return &ledger;}};
+struct Memory{ERR_HQBelief*belief=nullptr;ERR_HQBelief*Get(IEntity){return belief;}};
+struct ERR_HQ{struct Memory memory;int token=400001,queued=0;ERR_HQRoute*next=nullptr;Group*source=nullptr;struct Memory*Memory(){return &memory;}int LogisticsToken(Group*){return token;}ERR_HQRoute*ERR_InterceptFixtureQueue(ERR_HQReport*r){queued++;r->MessageId=queued;r->Source=source;return next;}};
+struct ERR_NativeConvoyProbe{inline static bool enabled=true;static bool Enabled(){return enabled;}};
+namespace System{std::map<string,string>flags={{"errMountedIntercept","1"}};bool GetCLIParam(string key,string&value){auto it=flags.find(key);if(it==flags.end())return false;value=it->second;return true;}}
+namespace Math{int Min(int a,int b){return a<b?a:b;}}
+namespace ERR_ForceRules{bool Finite(float n){return std::isfinite(n);}}
+namespace LogLevel{const int ERROR=1;}
+void Print(string,int=0){}template<class...T>void PrintFormat(string,T...){}
+'''.replace('Group*Group=nullptr','struct Group*Group=nullptr').replace('struct struct','struct')+code+r'''
+struct Setup{
+ ERR_GameMode mode;ERR_HQ hq;ERR_ConvoyTask task;ERR_ConvoyVehicle v[6];Entity e[6],actors[6];Group groups[6];ERR_HQReport second;ERR_HQRoute uplink,downlink;ERR_HQBelief belief;ERR_ConvoyInterceptOrder order;Waypoint detour,ordinary;ERR_NativeMountedInterceptProbe*p;
+ Setup(){ERR_GameMode::current=&mode;mode.command=&hq;hq.source=&groups[1];for(int i=0;i<6;i++){v[i].Entity=&e[i];v[i].Group=&groups[i];v[i].Members={&actors[i]};task.Vehicles.Insert(&v[i]);}p=new ERR_NativeMountedInterceptProbe(&mode,&hq,&task,0);
+ second.Target=&mode;second.Source=&groups[1];second.MessageId=2;second.ObservedAt=0;second.Position={370,0,0};uplink.MessageId=2;uplink.Cursor=1;uplink.Originated=true;uplink.StartedAt=0;belief.MessageId=2;belief.ObservedAt=0;belief.Position=second.Position;hq.memory.belief=&belief;
+ downlink.Downlink=true;downlink.StartedAt=10000;downlink.Cursor=0;order.Group=&groups[0];p->m_Second=&second;p->m_SecondRoute=&uplink;p->m_Step=2;v[0].InterceptToken=order.Token;v[0].InterceptOrder=&order;}
+ void Pending(){p->Pending(&order,&downlink,&task,10000);}void Delivered(){v[0].InterceptActive=true;v[0].InterceptWaypoint=&detour;p->Delivered(&order,&downlink,12000);}
+};
+int main(){
+ {Setup f;f.Pending();f.Delivered();f.e[0].position=f.order.Position;f.v[0].InterceptWaypoint=nullptr;assert(f.p->Tick(20000)==0);
+ f.e[0].position.x+=100;assert(f.p->Tick(23000)==0&&f.p->m_HoldAt<0);f.e[0].position=f.order.Position;assert(f.p->Tick(26000)==0&&!f.p->m_Held);assert(f.p->Tick(30000)==0&&!f.p->m_Held);assert(f.p->Tick(31000)==0&&f.p->m_Held);
+ f.e[0].position.x+=6;assert(f.p->Tick(32000)==-1);}
+ assert(ERR_NativeMountedInterceptProbe::Enabled());System::flags["errConvoyCover"]="1";assert(!ERR_NativeMountedInterceptProbe::Enabled());System::flags.erase("errConvoyCover");
+ {Setup f;f.Pending();assert(f.p->m_Order==&f.order&&!f.p->m_Failed&&f.p->m_DeliveredAt<0);f.Delivered();assert(!f.p->m_Failed&&f.p->m_DeliveredAt==12000);
+ assert(f.p->Tick(13000)==0);f.e[0].position=f.order.Position;f.v[0].InterceptWaypoint=nullptr;assert(f.p->Tick(20000)==0);assert(f.p->Tick(25000)==0&&f.p->m_Held);
+ f.v[0].InterceptToken=0;f.v[0].InterceptActive=false;f.v[0].InterceptOrder=nullptr;f.v[0].Waypoint=&f.ordinary;f.hq.token=0;assert(f.p->Tick(120000)==0);f.e[0].position.x+=9;assert(f.p->Tick(121000)==0);f.e[0].position.x+=1;assert(f.p->Tick(122000)==1);
+ f.p->Stop();assert(f.p->Tick(122001)==-1&&ERR_NativeMountedInterceptProbe::Active()==nullptr);}
+ for(int bad=0;bad<6;bad++){Setup f;
+ if(bad==0)f.uplink.Failed=true;
+ if(bad==1)f.uplink.Cursor=0;
+ if(bad==2)f.uplink.Originated=false;
+ if(bad==3)f.uplink.Sending=true;
+ if(bad==4)f.belief.MessageId=9;
+ if(bad==5)f.uplink.StartedAt=-1;
+ f.Pending();assert(f.p->m_Failed&&!f.p->m_Order);}
+ for(int bad=0;bad<5;bad++){Setup f;f.Pending();
+ if(bad==0)f.downlink.Failed=true;
+ if(bad==1)f.downlink.Sending=true;
+ if(bad==2)f.downlink.Cursor=-1;
+ if(bad==3)f.downlink.StartedAt=-1;
+ if(bad==4)f.v[0].Waypoint=&f.ordinary;
+ f.Delivered();assert(f.p->m_Failed&&f.p->m_DeliveredAt<0);}
+ {Setup f;f.Pending();f.Delivered();assert(f.p->Tick(120000)==-1&&!f.p->m_Held);} // elapsed expiry cannot substitute for movement
+ {Setup f;f.Pending();f.Delivered();f.order.Position.x++;assert(f.p->Tick(13000)==-1);} // detached target remains fixed
+ {Setup f;f.Pending();f.Delivered();f.v[0].Entity=&f.e[1];assert(f.p->Tick(13000)==-1);} // replacement cannot qualify
+ {Setup f;assert(f.p->CheckTime(239999));assert(!f.p->CheckTime(240000));}
+ {Setup f;assert(!f.p->CheckTime(-1));}
+ assert(ERR_NativeMountedInterceptProbe::CompletionText(true).find("synthetic")!=string::npos);
+ assert(ERR_NativeMountedInterceptProbe::CompletionText(false)=="ERR SMOKE COMPLETE: native convoy readiness, cargo and road movement; explicit fixture reset");
+}
+''')
+assert s.startswith('#ifdef WORKBENCH')
+for forbidden in ('ApplyIntercept(', 'ReserveIntercept(', '.Advance(', '.SetOrigin(', 'EnableDamageHandling(', 'TrySpawn', 'Spawn(', 'm_Memory.Ingest', 'm_Memory.Deliver'):
+ assert forbidden not in s,forbidden
+hook=body(P,'protected override void TickMountedIntercept(')
+assert hook.count('super.TickMountedIntercept(now)')==1
+assert hook.index('proof.Pending')<hook.index('super.TickMountedIntercept(now)')<hook.index('proof.Delivered')
+print('PASS: actual native interception witness requires real radio cursor/lineage, original actor identities, fixed movement/hold/expiry/resume and immutable deadline; no movement/delivery injection')
+# Execute only argparse declarations and the exact opt-in validation/command AST.
+# No filesystem/process setup or native runner is executed.
+runner=Path(os.environ.get('ERR_MOUNTED_INTERCEPT_RUNNER',ROOT/'tools/headless_workbench.py'))
+tree=ast.parse(runner.read_text());declarations=[];guards=[];flag=None
+for node in tree.body:
+ if isinstance(node,ast.Assign) and any(isinstance(t,ast.Name) and t.id=='p' for t in node.targets):declarations.append(node)
+ if isinstance(node,ast.Expr) and isinstance(node.value,ast.Call) and isinstance(node.value.func,ast.Attribute) and isinstance(node.value.func.value,ast.Name) and node.value.func.value.id=='p' and node.value.func.attr=='add_argument':declarations.append(node)
+ if isinstance(node,ast.If) and any(isinstance(n,ast.Attribute) and n.attr=='mounted_intercept' for n in ast.walk(node.test)):
+ if any(isinstance(n,ast.Call) and isinstance(n.func,ast.Attribute) and n.func.attr=='error' for n in ast.walk(node)):guards.append(node)
+ elif any(isinstance(n,ast.AugAssign) and isinstance(n.target,ast.Name) and n.target.id=='command' for n in ast.walk(node)):flag=node
+assert len(guards)==2 and flag is not None
+base=['--compat','/tmp/erri-unused-intercept-prefix','--profile','/tmp/erri-unused-intercept-profile']
+def validate(args):
+ scope={'argparse':argparse,'Path':Path,'__doc__':'test'}
+ exec(compile(ast.Module(body=declarations,type_ignores=[]),str(runner),'exec'),scope)
+ scope['a']=scope['p'].parse_args(base+args)
+ exec(compile(ast.Module(body=guards,type_ignores=[]),str(runner),'exec'),scope)
+ scope['command']=[];exec(compile(ast.Module(body=[flag],type_ignores=[]),str(runner),'exec'),scope)
+ return scope['command']
+valid=['--mounted-intercept','--smoke','convoy','--project','chat','--rounds','1','--timeout','900']
+assert validate(valid)==['-errMountedIntercept','1']
+assert validate(['--smoke','convoy','--timeout','600'])==[]
+for replacement in [('convoy','victory'),('chat','core'),('1','2'),('900','899')]:
+ args=valid.copy();args[args.index(replacement[0])]=replacement[1]
+ try:validate(args)
+ except SystemExit as error:assert error.code==2
+ else:raise AssertionError(args)
+try:validate(valid+['--convoy-cover'])
+except SystemExit as error:assert error.code==2
+else:raise AssertionError('conflicting fixture accepted')
+convoy=Path(os.environ.get('ERR_CONVOY_PROBE_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c')).read_text()
+assert convoy.index('all six native vehicles drove at least 20m')<convoy.index('m_Intercept = new ERR_NativeMountedInterceptProbe')
+assert 'if (m_Step == 5) return m_Intercept.Tick(now);' in convoy
+smoke=Path(os.environ.get('ERR_NATIVE_SMOKE_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c'))
+assert 'if (ERR_NativeMountedInterceptProbe.Enabled()) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 840000;' in smoke.read_text()
+assert 'synthetic observations, actual mounted interception radio and physical detour/hold/expiry/resume' in smoke.read_text()
+terminal=body(smoke,'if (m_ERRTestConvoy)\n\t\t{').replace('ERR_Phase.DEFEAT','2').replace('GetGame().GetCallqueue().Remove(ERR_SmokeTick);','removed = true;').replace('ERR_StopHQPatrol();','stopped = true;').replace('Print(','Record(').replace('ERR_NativeMountedInterceptProbe.','ERR_NativeMountedInterceptProbe::')
+completion=body(P,'static string CompletionText(')
+run(r'''
+#include <cassert>
+#include <string>
+using string=std::string;
+struct ERR_NativeMountedInterceptProbe{inline static bool enabled=false;static bool Enabled(){return enabled;}
+'''+completion+r'''};
+struct Smoke{bool m_ERRTestConvoy=true,m_ERRSmokeDone=false,removed=false,stopped=false;int m_ERRSmokeStep=1061;string marker;bool ERR_Check(bool ok,string){return ok;}void Record(string text){marker=text;}void Terminal(int current){
+'''+terminal+r'''
+}};
+int main(){Smoke ordinary;ordinary.Terminal(2);assert(ordinary.m_ERRSmokeDone&&ordinary.marker==ERR_NativeMountedInterceptProbe::CompletionText(false));
+ ERR_NativeMountedInterceptProbe::enabled=true;Smoke mounted;mounted.Terminal(2);assert(mounted.m_ERRSmokeDone&&mounted.removed&&mounted.stopped&&mounted.marker.find("synthetic")!=string::npos);
+ Smoke premature;premature.m_ERRSmokeStep=106;premature.Terminal(2);assert(!premature.m_ERRSmokeDone&&premature.marker.empty());}
+''')
+print('PASS: actual CLI rejects wrong mode/project/rounds/899s/conflict before runtime; valid flag and terminal branch identify synthetic-observation qualification')
+# The native fixture is a second modded layer over the production dispatch.
+# Missing base hook previously caused a native compile failure; keep its exact
+# declaration and the production override in this integration contract.
+hqbase=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'
+dispatch=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c'
+assert body(hqbase,'protected void TickMountedIntercept(float now)').endswith('{}')
+production=body(dispatch,'protected override void TickMountedIntercept(float now)')
+assert 'm_MountedInterceptRoute.Advance(now, m_Journal)' in production
+assert 'm_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now)' in production
+for declaration in ('protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;',
+ 'protected ref ERR_HQRoute m_MountedInterceptRoute;',
+ 'protected ERR_ConvoyTask m_MountedInterceptTask;',
+ 'protected bool m_MountedInterceptDelivered;'):
+ assert declaration in dispatch.read_text(),declaration
+hook=body(P,'protected override void TickMountedIntercept(').replace('protected override void','void').replace('ref ','').replace('super.TickMountedIntercept','Base::TickMountedIntercept').replace('ERR_NativeMountedInterceptProbe.Active()','ERR_NativeMountedInterceptProbe::Active()')
+for t in ('ERR_NativeMountedInterceptProbe','ERR_ConvoyInterceptOrder','ERR_HQRoute'):
+ hook=re.sub(r'\b'+t+r' (?=\w)',t+'* ',hook)
+hook=hook.replace('proof.Pending','proof->Pending').replace('proof.Delivered','proof->Delivered')
+run(r'''
+#include <cassert>
+#include <vector>
+struct ERR_ConvoyInterceptOrder{};struct ERR_HQRoute{};struct ERR_ConvoyTask{};
+std::vector<int> events;
+struct ERR_NativeMountedInterceptProbe{
+ inline static ERR_NativeMountedInterceptProbe* active=nullptr;ERR_ConvoyInterceptOrder*order=nullptr;ERR_HQRoute*route=nullptr;
+ static ERR_NativeMountedInterceptProbe*Active(){return active;}
+ void Pending(ERR_ConvoyInterceptOrder*o,ERR_HQRoute*r,ERR_ConvoyTask*,float){order=o;route=r;events.push_back(1);}
+ void Delivered(ERR_ConvoyInterceptOrder*o,ERR_HQRoute*r,float){assert(o==order&&r==route&&r);events.push_back(3);}
+};
+struct Base{
+ ERR_ConvoyInterceptOrder*m_MountedIntercept=nullptr;ERR_HQRoute*m_MountedInterceptRoute=nullptr;ERR_ConvoyTask*m_MountedInterceptTask=nullptr;bool m_MountedInterceptDelivered=false;int outcome=0,calls=0;
+ virtual void TickMountedIntercept(float){events.push_back(2);calls++;if(outcome==1){m_MountedInterceptRoute=nullptr;m_MountedInterceptDelivered=true;}if(outcome==-1){m_MountedIntercept=nullptr;m_MountedInterceptRoute=nullptr;}}
+};
+struct Fixture:Base{
+'''+hook+r'''
+};
+int main(){ERR_NativeMountedInterceptProbe p;ERR_NativeMountedInterceptProbe::active=&p;ERR_ConvoyInterceptOrder o;ERR_HQRoute r;
+ for(int outcome=-1;outcome<=1;outcome++){events.clear();Fixture f;f.m_MountedIntercept=&o;f.m_MountedInterceptRoute=&r;f.outcome=outcome;f.TickMountedIntercept(2);assert(f.calls==1);assert(events==(outcome==1?std::vector<int>{1,2,3}:std::vector<int>{1,2}));}
+ events.clear();ERR_NativeMountedInterceptProbe::active=nullptr;Fixture ordinary;ordinary.TickMountedIntercept(2);assert(ordinary.calls==1&&events==std::vector<int>{2});
+ events.clear();ERR_NativeMountedInterceptProbe::active=&p;Fixture delivered;delivered.m_MountedIntercept=&o;delivered.m_MountedInterceptDelivered=true;delivered.TickMountedIntercept(2);assert(delivered.calls==1&&events==std::vector<int>{2});
+}
+''')
+print('PASS: visible base hook and actual fixture override delegate once; delivery proof uses retained pre-super route only on successful production transition')
--- a/tools/headless_workbench.py
+++ b/tools/headless_workbench.py
@@ -19,6 +19,7 @@
p.add_argument('--mounted-swap-eligibility', action='store_true', help='Read-only mounted-ammo variant: one exact finite cargo/mounted magazine CanSwap call; no transfer or radio proof')
p.add_argument('--mine-stock', action='store_true', help='Read-only mounted-ammo check of exact callback-proved finite PMN4 cargo before and after natural radio delivery')
p.add_argument('--mounted-courier', action='store_true', help='Mounted-ammo variant: intentionally deplete one original installed magazine, then require natural HQ radio, finite pickup, native exchange and original-seat return')
+p.add_argument('--mounted-intercept', action='store_true', help='After convoy driving, two synthetic witness samples exercise real HQ radio/planning and original escort detour/hold/expiry/resume')
p.add_argument('--convoy-cover', action='store_true', help='After convoy driving, arrange the existing prisoner to verify native sighting/radio HOLD and RESUME')
p.add_argument('--field-terrain-audit', action='store_true', help='After a fixed aircraft-field survey refusal, log one read-only 1681-node coarse terrain grid; never selects or qualifies a corridor')
p.add_argument('--field-service-cancel', action='store_true', help='Explicit aircraft-field variant: Stop the original retained service during measured cruise; requires physical cancellation landing')
@@ -58,6 +59,8 @@
if a.mounted_swap_eligibility and a.smoke != 'mounted-ammo': p.error('--mounted-swap-eligibility requires --smoke mounted-ammo')
if a.mounted_courier and (a.smoke != 'mounted-ammo' or a.project != 'chat' or a.rounds != 1 or a.timeout < 780): p.error('--mounted-courier requires --smoke mounted-ammo --project chat --rounds 1 and timeout at least780 seconds')
if a.mounted_courier and a.mounted_swap_eligibility: p.error('--mounted-courier and --mounted-swap-eligibility are mutually exclusive')
+if a.mounted_intercept and (a.smoke != 'convoy' or a.project != 'chat' or a.rounds != 1 or a.timeout < 900): p.error('--mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds')
+if a.mounted_intercept and a.convoy_cover: p.error('--mounted-intercept and --convoy-cover are mutually exclusive')
if a.convoy_cover and a.smoke != 'convoy': p.error('--convoy-cover requires --smoke convoy')
if a.convoy_cover and a.timeout < 900: p.error('--convoy-cover requires timeout at least900 seconds')
if a.smoke == 'supply-convoys' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 900): p.error('supply-convoys requires Chat, one round, and at least900 seconds')
@@ -126,6 +129,7 @@
except (FileNotFoundError, PermissionError, ProcessLookupError): pass
command = ['gamescope', '--backend', 'headless', '-W', '1280', '-H', '720', '-w', '1280', '-h', '720', '-r', '30', '--', str(proton), 'run', str(wb/'ArmaReforgerWorkbenchSteamDiag.exe'), '-gproj', 'Z:'+str(ROOT/'addons/core/addon.gproj'), '-addonsDir', 'Z:'+str(ROOT/'addons'), '-profile', profile_arg, '-logconsole', '-wbModule=WorldEditor', '-load', 'Worlds/ERR_Everon.ent', '-plugin=ERR_ValidateWorldPlugin', '-errPlay', '1', '-errSmoke', '1', '-errSmokeOutcome', a.smoke, '-noThrow', '-disableCrashReporter']
if a.convoy_cover: command += ['-errConvoyCover', '1']
+if a.mounted_intercept: command += ['-errMountedIntercept', '1']
if a.mounted_swap_eligibility: command += ['-errMountedSwapEligibility', '1']
if a.mounted_courier: command += ['-errMountedCourier', '1']
if a.mine_stock: command += ['-errMineStock', '1']