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:
2026-10-13 22:52:53 +02:00
parent a951a96edc
commit 6a667d8deb
8104 changed files with 2124945 additions and 2061 deletions
@@ -0,0 +1,27 @@
Scratch-only mounted interception fixture, 2026-10-02
Artifacts
- fix.patch: three WORKBENCH native source files, runner opt-in, new actual-method regression and existing cover harness compatibility adapter.
- source-manifest.json: exact proposed source/test/patch SHA256.
- baseline.json + baseline/: working/index evidence for all previously existing changed files, including inherited runner changes.
- apply-check.log: git apply --check against successor passed. No target edits, staging or native launch occurred.
- run-tests.sh: repeatable focused tests; test.log and cover-test.log both green.
- dwell-reset-mutation.log: deleting the actual dwell reset compiled and assertion-failed; isolated mutation never belongs to patch.
- Register tests/test_native_mounted_intercept.py in tools/check.py separately. No check.py changes included.
Future opt-in command shape (do not execute without parent frozen-source/native preflight):
python3 tools/headless_workbench.py --project chat --smoke convoy --mounted-intercept --rounds 1 --timeout 900 --compat <owned-disposable-prefix> --profile <fresh-external-private-profile>
Retain existing environment/native ownership guards and any required explicit audited external addon argument. --mounted-intercept rejects non-convoy, non-Chat, multiple rounds, timeout below 900, or --convoy-cover. Ordinary default fixture remains unchanged.
Proof boundary
After existing native six-vehicle readiness/cargo/original-actor/movement proof, a fixed 240-second stage arranges two explicitly synthetic on-foot observation samples through an original remote foot radio witness. ContactReport, actual uplink route, HQ delivered memory, ordinary mounted intercept planning and actual downlink delivery remain production paths. Pending/Delivered hooks only observe the production TickMountedIntercept boundary; there is no ApplyIntercept, direct memory insertion/delivery, AI teleport, new force, cargo transfer or refilling.
The fixture pins exact original vehicle/group/crew, order/token/report/revision/destination/expiry. It requires actual detour waypoint installation after radio completion, >=20 m motion and <=15 m target arrival, continuous five-second hold within 5 m with no detour waypoint, retention until fixed expiry, exact claim/detour removal, ordinary route waypoint and >=10 m physical resumed movement. Leaving before dwell completion resets the dwell; leaving after proof before expiry fails. Native actor identities and force/cargo counts remain unchanged.
Outer opt-in deadline is 840 seconds; external minimum is 900. Original base convoy proof remains bounded by its original 480-second stage. No missing capacity/status/road/radio condition is forced to pass.
Interventions and limitations
The original convoy fixture already opens the prison, protects its observer and dispatches a finite authored convoy. New fixture explicitly clears the selected observer's LocalAwareness inbox to isolate two synthetic observation samples. This is synthetic report arrangement, not natural hostile detection or perceptual qualification. Native radio hops, HQ selection, physical AI routing, dwell and resumption remain required and currently UNTESTED in engine. Natural local convoy contact intentionally cancels interception; lack of road clearance, fresh armed escort status, task availability, planner capacity or eligible witness can cause a bounded failure. No settings or gameplay policies are weakened to obtain success.
The actual-method tests double engine boundaries and exercise the complete witness class, actual runner argument AST, and actual convoy terminal branch. The queue/hook methods have source/order checks, not a full native radio simulation. Do not label this native-qualified until a future frozen-source run passes.
Current parent development package is intentionally unaffected by this scratch proposal.
Refresh1456: current source apply-check passed. All three proposed native source files and runner byte-identical to1349. New regression additionally checks visible protected base/production hook declarations and executes actual fixture override for all transition outcomes. See api-review.json; native compilation is still pending. Prior dwell reset red evidence remains at /tmp/erri-mounted-native-1349/dwell-reset-mutation.log.
@@ -0,0 +1,92 @@
{
"verdict": "no concrete native API or inheritance mismatch found; engine compilation remains pending",
"source_changes_from_1349": false,
"tests": [
"test_native_mounted_intercept.py PASS (actual witness, CLI AST, terminal and actual modded hook)",
"test_native_convoy_cover_probe.py PASS"
],
"base_hook_check": "Visible protected empty HQ base method; production modded override in10_AI; fixture override in40_Chat calls super once and retains strong pre-super order/route. Actual hook regression covers pending, negative/cancel, successful delivery, inactive fixture and already-delivered branches. Native multi-modded dispatch still requires planned engine compilation.",
"radio_cursor_semantics": "Native production script Advance ends uplink at Cursor>=Count, downlink at Cursor==Count-1, clears Sending; fixture predicates match. No new invented native method.",
"references": [
{
"path": "/home/jakub/devel/escapistsrereintroduced/addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c",
"sha256": "da2ffb958cdcb47ff375ebe2cbf221e1d726da0fc54ad446d0cad60e0ef2389c",
"methods": [
{
"text": "protected void TickMountedIntercept(float now) {}",
"line": 1311
},
{
"text": "protected void ContactReport(ERR_HQUnit unit, float now)",
"line": 749
},
{
"text": "ERR_HQMemory Memory()",
"line": 467
}
]
},
{
"path": "/home/jakub/devel/escapistsrereintroduced/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c",
"sha256": "8650c09afc46439a74e17cdf968b09d238ec9437113ffad784a031cddd65440a",
"methods": [
{
"text": "protected override void TickMountedIntercept(float now)",
"line": 75
},
{
"text": "m_MountedInterceptRoute.Advance(now, m_Journal)",
"line": 91
},
{
"text": "m_MountedInterceptRoute = null; m_MountedInterceptDelivered = true;",
"line": 99
}
]
},
{
"path": "/home/jakub/devel/escapistsrereintroduced/addons/core/Scripts/Game/ERR/10_AI/ERR_HQNetwork.c",
"sha256": "85e4d2cb27426c07b8631ca411f4b8f5c821f9f331c8c2719cbfa71b3687f08e",
"methods": [
{
"text": "int Advance(float now, ERR_HQJournal journal)",
"line": 55
},
{
"text": "if ((!Downlink && Cursor >= Nodes.Count()) || (Downlink && Cursor == Nodes.Count() - 1))",
"line": 106
}
]
},
{
"path": "/home/jakub/devel/escapistsrereintroduced/addons/core/Scripts/Game/ERR/10_AI/ERR_HQLocalAwareness.c",
"sha256": "124260280ac8eda0e920bfcfc86932c9b21df6b1f7a6aa8acfc43feea3da149f",
"methods": [
{
"text": "void Clear()",
"line": 27
},
{
"text": "void Remember(ERR_HQReport report, float now)",
"line": 45
}
]
},
{
"path": "/home/jakub/devel/escapistsrereintroduced/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQMemoryProbe.c",
"sha256": "d8fec6c138bb0f5bdbf99ee82ffc3ac9b44d1045d8833da63f8aa5f39beeb7e5",
"methods": [
{
"text": "ERR_HQRoute ERR_MemoryFixtureQueue(ERR_HQReport report)",
"line": 37
},
{
"text": "unit.LocalAwareness.Remember(report, report.ObservedAt);",
"line": 50
}
]
}
],
"preservation": "All current four existing baseline file working/index hashes captured; no tracked target edits. Rebase application check succeeds. Unrelated terrain source untouched.",
"apply_check_exit": 0
}
@@ -0,0 +1 @@
PASS: actual convoy cover observation/snapshot/uplink/HOLD/RESUME/cleanup transitions; native boundaries mocked, non-convoy runner opt-in refused
@@ -0,0 +1,571 @@
--- 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']
@@ -0,0 +1,35 @@
{
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c": {
"sha256": "7e6d5b367f71539cdac1e5f406d650d7d06bfd26a3ce3404abfb0ac5d88d3411",
"new": false,
"same_as_1349": true
},
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedInterceptProbe.c": {
"sha256": "34b0b1e38a59f14c4e83d93ec412779945388e6f4d697bda2f6cb90f519a6372",
"new": true,
"same_as_1349": true
},
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c": {
"sha256": "68a5f73c4517692daec9d42b59bc21d3bb64330c8046b957922fd208740b40a6",
"new": false,
"same_as_1349": true
},
"tests/test_native_convoy_cover_probe.py": {
"sha256": "68681753a1ab89a8e8e07ba7f701190bf860d07762e5a06e87da7ba606fc0de6",
"new": false,
"same_as_1349": true
},
"tests/test_native_mounted_intercept.py": {
"sha256": "24810c0a22b221604e1fd8e6596f2d409e558699c5070fef7aae1f74a4fc2f5d",
"new": true,
"same_as_1349": false
},
"tools/headless_workbench.py": {
"sha256": "57f472b7a7642f35e6c2f0e2546631ee941a6f1ea6a5f85365ef59042d674264",
"new": false,
"same_as_1349": true
},
"fix.patch": {
"sha256": "4e91f91c50efc63e4bf2d13955625a67ed46a75c41c431f019db56d6af1ed89f"
}
}
@@ -0,0 +1,103 @@
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
[--external-addons EXTERNAL_ADDONS]
--compat COMPAT --profile PROFILE
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
[--maintenance-refuel]
[--mounted-swap-eligibility]
[--mine-stock] [--mounted-courier]
[--mounted-intercept] [--convoy-cover]
[--field-terrain-audit]
[--field-service-cancel]
[--route-service-cancel]
[--station-teardown-diagnostics]
[--flight-autohover]
[--flight-action-inputs]
[--flight-input-diagnostics]
[--recon-takeover-only]
[--body-recovery-only]
[--rounds ROUNDS] [--timeout TIMEOUT]
[--project {core,chat}]
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
[--external-addons EXTERNAL_ADDONS]
--compat COMPAT --profile PROFILE
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
[--maintenance-refuel]
[--mounted-swap-eligibility]
[--mine-stock] [--mounted-courier]
[--mounted-intercept] [--convoy-cover]
[--field-terrain-audit]
[--field-service-cancel]
[--route-service-cancel]
[--station-teardown-diagnostics]
[--flight-autohover]
[--flight-action-inputs]
[--flight-input-diagnostics]
[--recon-takeover-only]
[--body-recovery-only]
[--rounds ROUNDS] [--timeout TIMEOUT]
[--project {core,chat}]
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
[--external-addons EXTERNAL_ADDONS]
--compat COMPAT --profile PROFILE
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
[--maintenance-refuel]
[--mounted-swap-eligibility]
[--mine-stock] [--mounted-courier]
[--mounted-intercept] [--convoy-cover]
[--field-terrain-audit]
[--field-service-cancel]
[--route-service-cancel]
[--station-teardown-diagnostics]
[--flight-autohover]
[--flight-action-inputs]
[--flight-input-diagnostics]
[--recon-takeover-only]
[--body-recovery-only]
[--rounds ROUNDS] [--timeout TIMEOUT]
[--project {core,chat}]
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
[--external-addons EXTERNAL_ADDONS]
--compat COMPAT --profile PROFILE
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
[--maintenance-refuel]
[--mounted-swap-eligibility]
[--mine-stock] [--mounted-courier]
[--mounted-intercept] [--convoy-cover]
[--field-terrain-audit]
[--field-service-cancel]
[--route-service-cancel]
[--station-teardown-diagnostics]
[--flight-autohover]
[--flight-action-inputs]
[--flight-input-diagnostics]
[--recon-takeover-only]
[--body-recovery-only]
[--rounds ROUNDS] [--timeout TIMEOUT]
[--project {core,chat}]
test_native_mounted_intercept.py: error: --mounted-intercept requires Chat --smoke convoy, one round and timeout at least900 seconds
usage: test_native_mounted_intercept.py [-h] [--steam STEAM]
[--external-addons EXTERNAL_ADDONS]
--compat COMPAT --profile PROFILE
[--smoke {normal-escape-route,escape-route,optional-site-skip,infantry-combat,medical-combat,hunter-faction,unconscious-camera,objective-map,town-garrisons,site-garrisons,radio-interaction,enemy-loadout,aggression,air-logistics,air-assault,air-recon,airfield-markers,opening-tools,mortar-station,generated-sites,hq-local-handoff,infantry-transfer,hq-body,convoy,supply-convoys,mounted-ammo,alarm-hearing,hq-memory,hq-awareness,aircraft-field,aircraft-route,native-end-active,native-end-preparing,aircraft-control,aircraft-cancel,aircraft-translate,map-ownership,mortar-refresh,mortar-salvo,mortar-courier,station-teardown,prison-breach,powered-flight,powered-translate,local-admission,mortar-expiry,mortar-transfer,mortar-fire,patrol-hq,victory,lost-helicopter,player-death,radio-network,hq-response,mortar-inventory,startup-failure,site-survey,cache-loss,extraction-capacity,early-helicopter-loss,extraction-startup-failure,extraction-boundary-failure,transport-loss,transport-survey,lifecycle,objective-ui,recovery,prisoner-kit,medical,casualty-load,casualty-direct,casualty-bench,casualty-remove,environment,environment-restart,patrols,radio-document-loss,radio-document-loss-partial,guard-cancel,casualty-service}]
[--maintenance-refuel]
[--mounted-swap-eligibility]
[--mine-stock] [--mounted-courier]
[--mounted-intercept] [--convoy-cover]
[--field-terrain-audit]
[--field-service-cancel]
[--route-service-cancel]
[--station-teardown-diagnostics]
[--flight-autohover]
[--flight-action-inputs]
[--flight-input-diagnostics]
[--recon-takeover-only]
[--body-recovery-only]
[--rounds ROUNDS] [--timeout TIMEOUT]
[--project {core,chat}]
test_native_mounted_intercept.py: error: --mounted-intercept and --convoy-cover are mutually exclusive
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
PASS: actual CLI rejects wrong mode/project/rounds/899s/conflict before runtime; valid flag and terminal branch identify synthetic-observation qualification
PASS: visible base hook and actual fixture override delegate once; delivery proof uses retained pre-super route only on successful production transition