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,162 @@
// One physical escort may intercept a road ahead of delivered travel history.
// This HQ-owned transport uses the ordinary radio graph and never reads players.
modded class ERR_HQ
{
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
protected ERR_HQUnit m_MountedInterceptUnit;
protected ERR_ConvoyTask m_MountedInterceptTask;
protected ref ERR_HQRoute m_MountedInterceptRoute;
protected bool m_MountedInterceptDelivered;
protected ref array<int> m_MountedAttemptBeliefs = {};
protected ref array<int> m_MountedAttemptRevisions = {};
protected override int Assigned()
{
int count = super.Assigned();
if (m_MountedIntercept) count++;
return count;
}
protected override int HuntingAssigned()
{
int count = super.HuntingAssigned();
if (m_MountedIntercept) count++;
return count;
}
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
{
super.ReserveUnit(unit, reserved, first, second);
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
}
protected bool MountedInterceptAttempted(int beliefId, int revision)
{
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
return false;
}
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
{
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
}
protected void ReleaseMountedIntercept(float now, string reason)
{
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyTask task = m_MountedInterceptTask;
// Detach first: physical release may synchronously unregister the group.
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = false;
if (!order) return;
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
}
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
{
if (m_MountedIntercept && unit == m_MountedInterceptUnit) ReleaseMountedIntercept(now, "original formation released");
super.ReleaseUnit(unit, now);
}
protected void TickMountedIntercept(float now)
{
if (!m_MountedIntercept) return;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyInterceptOrder order = m_MountedIntercept;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (!ERR_ForceRules.Finite(now) || now < order.IssuedAt || now >= order.ExpiresAt
|| !MissionActive() || m_Stopping || !unit || !m_Units.Contains(unit) || unit.Group != order.Group
|| unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|| !m_Convoys.InterceptCurrent(m_MountedInterceptTask, order))
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
if (m_MountedInterceptDelivered) return;
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
if (!ERR_HQMountedInterceptRules.Current(order, belief, now) || !m_MountedInterceptRoute)
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
if (delivered == 0) return;
if (delivered < 0)
{ ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
if (!MountedInterceptStatus(unit, vehicle, now) || ConvoyContact(unit, now)
|| !m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now))
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = true;
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escort ordered to the remembered road ahead", order.ReportId);
}
protected void PlanMountedIntercept(float now)
{
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept || m_MountedAttemptBeliefs.Count() >= 256) return;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (Assigned() >= globalLimit || HuntingAssigned() >= ERR_HQPursuitRules.Limit(globalLimit)) return;
array<vector> reserved = {}; int first = -1; int second = -1;
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
// At most two bounded native road queries per normal five-second HQ pass.
int queries;
for (int b = 0; b < m_Memory.Count() && queries < 2; b++)
{
ERR_HQBelief belief = m_Memory.At(b);
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
vector destination; queries++;
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
ERR_HQUnit best;
ERR_ConvoyTask bestTask;
ERR_ConvoyVehicle bestVehicle;
float bestDistance = 1e20;
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
float distance = vector.DistanceXZ(own.Position, destination);
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || distance >= bestDistance) continue;
best = unit; bestTask = task; bestVehicle = vehicle; bestDistance = distance;
}
}
}
if (!best) continue;
ERR_HQRoute route = m_Network.Route(best.Radio, true);
if (!route) continue;
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
order.Token = 400001 + m_MountedAttemptBeliefs.Count(); order.Group = best.Group;
order.SourceId = bestTask.Receipt.SourceId; order.VehicleIndex = bestVehicle.Index;
order.Position = destination; order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) continue;
// The attempt stays spent after radio interruption, physical refusal or loss.
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
best.LogisticsReservation = order.Token;
if (!m_Convoys.ReserveIntercept(bestTask, order, now))
{ if (best.LogisticsReservation == order.Token) best.LogisticsReservation = 0; return; }
m_MountedIntercept = order; m_MountedInterceptUnit = best; m_MountedInterceptTask = bestTask;
m_MountedInterceptRoute = route; m_MountedInterceptDelivered = false;
route.MessageId = m_Journal.Message();
route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, destination, order.BeliefId, order.BeliefRevision, order.ReportId);
m_Journal.Write("MOUNTED INTERCEPT QUEUED", route.Payload, route.MessageId);
return;
}
}
override void Stop()
{
float now;
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
ReleaseMountedIntercept(now, "mission stopped");
super.Stop();
}
}
@@ -0,0 +1,182 @@
// One physical escort may intercept a road ahead of delivered travel history.
// This HQ-owned transport uses the ordinary radio graph and never reads players.
modded class ERR_HQ
{
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
protected ERR_HQUnit m_MountedInterceptUnit;
protected ERR_ConvoyTask m_MountedInterceptTask;
protected ref ERR_HQRoute m_MountedInterceptRoute;
protected bool m_MountedInterceptDelivered;
protected ref array<int> m_MountedAttemptBeliefs = {};
protected ref array<int> m_MountedAttemptRevisions = {};
protected override int Assigned()
{
int count = super.Assigned();
if (m_MountedIntercept) count++;
return count;
}
protected override int HuntingAssigned()
{
int count = super.HuntingAssigned();
if (m_MountedIntercept) count++;
return count;
}
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
{
super.ReserveUnit(unit, reserved, first, second);
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
}
protected bool MountedInterceptAttempted(int beliefId, int revision)
{
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
return false;
}
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
{
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
}
protected void ReleaseMountedIntercept(float now, string reason)
{
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyTask task = m_MountedInterceptTask;
// Detach first: physical release may synchronously unregister the group.
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = false;
if (!order) return;
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
}
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
{
if (m_MountedIntercept && unit == m_MountedInterceptUnit) ReleaseMountedIntercept(now, "original formation released");
super.ReleaseUnit(unit, now);
}
protected void TickMountedIntercept(float now)
{
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyInterceptOrder order = m_MountedIntercept;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (now < order.IssuedAt || now >= order.ExpiresAt
|| !MissionActive() || m_Stopping || !unit || !m_Units.Contains(unit) || unit.Group != order.Group
|| unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|| !m_Convoys.InterceptCurrent(m_MountedInterceptTask, order))
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
if (m_MountedInterceptDelivered) return;
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
if (!ERR_HQMountedInterceptRules.Current(order, belief, now) || !m_MountedInterceptRoute)
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
if (delivered == 0) return;
if (delivered < 0)
{ ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
if (!MountedInterceptStatus(unit, vehicle, now) || ConvoyContact(unit, now)
|| !m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now))
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = true;
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escort ordered to the remembered road ahead", order.ReportId);
}
protected bool HasMountedInterceptCandidate(float now)
{
// Avoid native road surveys until one original escort has usable delivered status.
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
&& MountedInterceptStatus(unit, vehicle, now)) return true;
}
}
return false;
}
protected void PlanMountedIntercept(float now)
{
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept || m_MountedAttemptBeliefs.Count() >= 256) return;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (Assigned() >= globalLimit || HuntingAssigned() >= ERR_HQPursuitRules.Limit(globalLimit)) return;
if (!HasMountedInterceptCandidate(now)) return;
array<vector> reserved = {}; int first = -1; int second = -1;
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
// At most two bounded native road queries per normal five-second HQ pass.
int queries;
for (int b = 0; b < m_Memory.Count() && queries < 2; b++)
{
ERR_HQBelief belief = m_Memory.At(b);
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
vector destination; queries++;
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
ERR_HQUnit best;
ERR_ConvoyTask bestTask;
ERR_ConvoyVehicle bestVehicle;
float bestDistance = 1e20;
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
float distance = vector.DistanceXZ(own.Position, destination);
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || distance >= bestDistance) continue;
best = unit; bestTask = task; bestVehicle = vehicle; bestDistance = distance;
}
}
}
if (!best) continue;
ERR_HQRoute route = m_Network.Route(best.Radio, true);
if (!route) continue;
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
order.Token = 400001 + m_MountedAttemptBeliefs.Count(); order.Group = best.Group;
order.SourceId = bestTask.Receipt.SourceId; order.VehicleIndex = bestVehicle.Index;
order.Position = destination; order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) continue;
// The attempt stays spent after radio interruption, physical refusal or loss.
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
best.LogisticsReservation = order.Token;
if (!m_Convoys.ReserveIntercept(bestTask, order, now))
{ if (best.LogisticsReservation == order.Token) best.LogisticsReservation = 0; return; }
m_MountedIntercept = order; m_MountedInterceptUnit = best; m_MountedInterceptTask = bestTask;
m_MountedInterceptRoute = route; m_MountedInterceptDelivered = false;
route.MessageId = m_Journal.Message();
route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, destination, order.BeliefId, order.BeliefRevision, order.ReportId);
m_Journal.Write("MOUNTED INTERCEPT QUEUED", route.Payload, route.MessageId);
return;
}
}
override void Stop()
{
float now;
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
ReleaseMountedIntercept(now, "mission stopped");
super.Stop();
}
}
@@ -0,0 +1,194 @@
// One physical escort may intercept a road ahead of delivered travel history.
// This HQ-owned transport uses the ordinary radio graph and never reads players.
modded class ERR_HQ
{
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
protected ERR_HQUnit m_MountedInterceptUnit;
protected ERR_ConvoyTask m_MountedInterceptTask;
protected ref ERR_HQRoute m_MountedInterceptRoute;
protected bool m_MountedInterceptDelivered;
protected ref array<int> m_MountedAttemptBeliefs = {};
protected ref array<int> m_MountedAttemptRevisions = {};
override string Status()
{
string result = super.Status();
if (!m_MountedIntercept) return result;
string phase = "queued";
if (m_MountedInterceptDelivered) phase = "delivered";
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
}
protected override int Assigned()
{
int count = super.Assigned();
if (m_MountedIntercept) count++;
return count;
}
protected override int HuntingAssigned()
{
int count = super.HuntingAssigned();
if (m_MountedIntercept) count++;
return count;
}
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
{
super.ReserveUnit(unit, reserved, first, second);
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
}
protected bool MountedInterceptAttempted(int beliefId, int revision)
{
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
return false;
}
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
{
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
}
protected void ReleaseMountedIntercept(float now, string reason)
{
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyTask task = m_MountedInterceptTask;
// Detach first: physical release may synchronously unregister the group.
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = false;
if (!order) return;
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
}
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
{
if (m_MountedIntercept && unit == m_MountedInterceptUnit) ReleaseMountedIntercept(now, "original formation released");
super.ReleaseUnit(unit, now);
}
protected void TickMountedIntercept(float now)
{
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyInterceptOrder order = m_MountedIntercept;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (now < order.IssuedAt || now >= order.ExpiresAt
|| !MissionActive() || m_Stopping || !unit || !m_Units.Contains(unit) || unit.Group != order.Group
|| unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|| !m_Convoys.InterceptCurrent(m_MountedInterceptTask, order))
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
if (m_MountedInterceptDelivered) return;
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
if (!ERR_HQMountedInterceptRules.Current(order, belief, now) || !m_MountedInterceptRoute)
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
if (delivered == 0) return;
if (delivered < 0)
{ ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
if (!MountedInterceptStatus(unit, vehicle, now) || ConvoyContact(unit, now)
|| !m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now))
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = true;
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escort ordered to the remembered road ahead", order.ReportId);
}
protected bool HasMountedInterceptCandidate(float now)
{
// Avoid native road surveys until one original escort has usable delivered status.
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
&& MountedInterceptStatus(unit, vehicle, now)) return true;
}
}
return false;
}
protected void PlanMountedIntercept(float now)
{
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept || m_MountedAttemptBeliefs.Count() >= 256) return;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (Assigned() >= globalLimit || HuntingAssigned() >= ERR_HQPursuitRules.Limit(globalLimit)) return;
if (!HasMountedInterceptCandidate(now)) return;
array<vector> reserved = {}; int first = -1; int second = -1;
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
// At most two bounded native road queries per normal five-second HQ pass.
int queries;
for (int b = 0; b < m_Memory.Count() && queries < 2; b++)
{
ERR_HQBelief belief = m_Memory.At(b);
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
vector destination; queries++;
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
ERR_HQUnit best;
ERR_ConvoyTask bestTask;
ERR_ConvoyVehicle bestVehicle;
ERR_HQRoute bestRoute;
float bestDistance = 1e20;
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
float distance = vector.DistanceXZ(own.Position, destination);
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || distance >= bestDistance) continue;
ERR_HQRoute candidateRoute = m_Network.Route(unit.Radio, true);
if (!candidateRoute) continue;
best = unit; bestTask = task; bestVehicle = vehicle; bestRoute = candidateRoute; bestDistance = distance;
}
}
}
if (!best) continue;
ERR_HQRoute route = bestRoute;
if (!route) continue;
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
order.Token = 400001 + m_MountedAttemptBeliefs.Count(); order.Group = best.Group;
order.SourceId = bestTask.Receipt.SourceId; order.VehicleIndex = bestVehicle.Index;
order.Position = destination; order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) continue;
// The attempt stays spent after radio interruption, physical refusal or loss.
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
best.LogisticsReservation = order.Token;
if (!m_Convoys.ReserveIntercept(bestTask, order, now))
{ if (best.LogisticsReservation == order.Token) best.LogisticsReservation = 0; return; }
m_MountedIntercept = order; m_MountedInterceptUnit = best; m_MountedInterceptTask = bestTask;
m_MountedInterceptRoute = route; m_MountedInterceptDelivered = false;
route.MessageId = m_Journal.Message();
route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, destination, order.BeliefId, order.BeliefRevision, order.ReportId);
m_Journal.Write("MOUNTED INTERCEPT QUEUED", route.Payload, route.MessageId);
return;
}
}
override void Stop()
{
float now;
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
ReleaseMountedIntercept(now, "mission stopped");
super.Stop();
}
}
@@ -0,0 +1,207 @@
// One physical escort may intercept a road ahead of delivered travel history.
// This HQ-owned transport uses the ordinary radio graph and never reads players.
modded class ERR_HQ
{
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
protected ERR_HQUnit m_MountedInterceptUnit;
protected ERR_ConvoyTask m_MountedInterceptTask;
protected ref ERR_HQRoute m_MountedInterceptRoute;
protected bool m_MountedInterceptDelivered;
protected ref array<int> m_MountedAttemptBeliefs = {};
protected ref array<int> m_MountedAttemptRevisions = {};
override string Status()
{
string result = super.Status();
if (!m_MountedIntercept) return result;
string phase = "queued";
if (m_MountedInterceptDelivered) phase = "delivered";
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
}
protected override int Assigned()
{
int count = super.Assigned();
if (m_MountedIntercept) count++;
return count;
}
protected override int HuntingAssigned()
{
int count = super.HuntingAssigned();
if (m_MountedIntercept) count++;
return count;
}
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
{
super.ReserveUnit(unit, reserved, first, second);
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
}
protected bool MountedInterceptAttempted(int beliefId, int revision)
{
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
return false;
}
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
{
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
}
protected void ReleaseMountedIntercept(float now, string reason)
{
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyTask task = m_MountedInterceptTask;
// Detach first: physical release may synchronously unregister the group.
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = false;
if (!order) return;
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
}
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
{
if (m_MountedIntercept && unit == m_MountedInterceptUnit) ReleaseMountedIntercept(now, "original formation released");
super.ReleaseUnit(unit, now);
}
protected override void TickMountedIntercept(float now)
{
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyInterceptOrder order = m_MountedIntercept;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (now < order.IssuedAt || now >= order.ExpiresAt
|| !MissionActive() || m_Stopping || !unit || !m_Units.Contains(unit) || unit.Group != order.Group
|| unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|| !m_Convoys.InterceptCurrent(m_MountedInterceptTask, order))
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
if (m_MountedInterceptDelivered) return;
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
if (!ERR_HQMountedInterceptRules.Current(order, belief, now) || !m_MountedInterceptRoute)
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
if (delivered == 0) return;
if (delivered < 0)
{ ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
if (!MountedInterceptStatus(unit, vehicle, now) || ConvoyContact(unit, now)
|| !m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now))
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = true;
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escort ordered to the remembered road ahead", order.ReportId);
}
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
{
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) return false;
// Service admission excludes the whole original convoy, including sibling
// holds still in transit. Do not spend a delivered revision on a busy task.
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || vehicle.InterceptToken > 0 || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) return false;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && (unit.PendingConvoyOrder || unit.Downlink || unit.LogisticsReservation > 0)) return false;
}
return true;
}
protected bool HasMountedInterceptCandidate(float now)
{
// Avoid native road surveys until one original escort has usable delivered status.
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!MountedInterceptTaskAvailable(task)) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
&& MountedInterceptStatus(unit, vehicle, now)) return true;
}
}
return false;
}
protected override void PlanMountedIntercept(float now)
{
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept || m_MountedAttemptBeliefs.Count() >= 256) return;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (Assigned() >= globalLimit || HuntingAssigned() >= ERR_HQPursuitRules.Limit(globalLimit)) return;
array<vector> reserved = {}; int first = -1; int second = -1;
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
// At most two bounded native road queries per normal five-second HQ pass.
int queries;
for (int b = 0; b < m_Memory.Count() && queries < 2; b++)
{
ERR_HQBelief belief = m_Memory.At(b);
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
vector destination; queries++;
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
ERR_HQUnit best;
ERR_ConvoyTask bestTask;
ERR_ConvoyVehicle bestVehicle;
ERR_HQRoute bestRoute;
float bestDistance = 1e20;
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!MountedInterceptTaskAvailable(task)) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
float distance = vector.DistanceXZ(own.Position, destination);
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || distance >= bestDistance) continue;
ERR_HQRoute candidateRoute = m_Network.Route(unit.Radio, true);
if (!candidateRoute) continue;
best = unit; bestTask = task; bestVehicle = vehicle; bestRoute = candidateRoute; bestDistance = distance;
}
}
}
if (!best) continue;
ERR_HQRoute route = bestRoute;
if (!route) continue;
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
order.Token = 400001 + m_MountedAttemptBeliefs.Count(); order.Group = best.Group;
order.SourceId = bestTask.Receipt.SourceId; order.VehicleIndex = bestVehicle.Index;
order.Position = destination; order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) continue;
// The attempt stays spent after radio interruption, physical refusal or loss.
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
best.LogisticsReservation = order.Token;
if (!m_Convoys.ReserveIntercept(bestTask, order, now))
{ if (best.LogisticsReservation == order.Token) best.LogisticsReservation = 0; return; }
m_MountedIntercept = order; m_MountedInterceptUnit = best; m_MountedInterceptTask = bestTask;
m_MountedInterceptRoute = route; m_MountedInterceptDelivered = false;
route.MessageId = m_Journal.Message();
route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, destination, order.BeliefId, order.BeliefRevision, order.ReportId);
m_Journal.Write("MOUNTED INTERCEPT QUEUED", route.Payload, route.MessageId);
return;
}
}
override void Stop()
{
float now;
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
ReleaseMountedIntercept(now, "mission stopped");
super.Stop();
}
}
@@ -0,0 +1,19 @@
check: /tmp/err-policy-l_0yt6xe/check.cpp:259: int main(): Assertion `ERR_HQMountedInterceptRules::selects==before&&f.service.reserves==0' failed.
Traceback (most recent call last):
File "/tmp/erri-motorized-intercept-review/test_hq_mounted_dispatch.py", line 24, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<81 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-l_0yt6xe/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,207 @@
// One physical escort may intercept a road ahead of delivered travel history.
// This HQ-owned transport uses the ordinary radio graph and never reads players.
modded class ERR_HQ
{
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
protected ERR_HQUnit m_MountedInterceptUnit;
protected ERR_ConvoyTask m_MountedInterceptTask;
protected ref ERR_HQRoute m_MountedInterceptRoute;
protected bool m_MountedInterceptDelivered;
protected ref array<int> m_MountedAttemptBeliefs = {};
protected ref array<int> m_MountedAttemptRevisions = {};
override string Status()
{
string result = super.Status();
if (!m_MountedIntercept) return result;
string phase = "queued";
if (m_MountedInterceptDelivered) phase = "delivered";
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
}
protected override int Assigned()
{
int count = super.Assigned();
if (m_MountedIntercept) count++;
return count;
}
protected override int HuntingAssigned()
{
int count = super.HuntingAssigned();
if (m_MountedIntercept) count++;
return count;
}
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
{
super.ReserveUnit(unit, reserved, first, second);
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
}
protected bool MountedInterceptAttempted(int beliefId, int revision)
{
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
return false;
}
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
{
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
}
protected void ReleaseMountedIntercept(float now, string reason)
{
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyTask task = m_MountedInterceptTask;
// Detach first: physical release may synchronously unregister the group.
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = false;
if (!order) return;
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
if (unit) unit.LogisticsReservation = 0;
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
}
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
{
if (m_MountedIntercept && unit == m_MountedInterceptUnit) ReleaseMountedIntercept(now, "original formation released");
super.ReleaseUnit(unit, now);
}
protected override void TickMountedIntercept(float now)
{
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyInterceptOrder order = m_MountedIntercept;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (now < order.IssuedAt || now >= order.ExpiresAt
|| !MissionActive() || m_Stopping || !unit || !m_Units.Contains(unit) || unit.Group != order.Group
|| unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|| !m_Convoys.InterceptCurrent(m_MountedInterceptTask, order))
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
if (m_MountedInterceptDelivered) return;
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
if (!ERR_HQMountedInterceptRules.Current(order, belief, now) || !m_MountedInterceptRoute)
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
if (delivered == 0) return;
if (delivered < 0)
{ ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
if (!MountedInterceptStatus(unit, vehicle, now) || ConvoyContact(unit, now)
|| !m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now))
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = true;
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escort ordered to the remembered road ahead", order.ReportId);
}
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
{
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) return false;
// Service admission excludes the whole original convoy, including sibling
// holds still in transit. Do not spend a delivered revision on a busy task.
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || vehicle.InterceptToken > 0 || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) return false;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && (unit.PendingConvoyOrder || unit.Downlink || unit.LogisticsReservation > 0)) return false;
}
return true;
}
protected bool HasMountedInterceptCandidate(float now)
{
// Avoid native road surveys until one original escort has usable delivered status.
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!MountedInterceptTaskAvailable(task)) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
&& MountedInterceptStatus(unit, vehicle, now)) return true;
}
}
return false;
}
protected override void PlanMountedIntercept(float now)
{
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept || m_MountedAttemptBeliefs.Count() >= 256) return;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (Assigned() >= globalLimit || HuntingAssigned() >= ERR_HQPursuitRules.Limit(globalLimit)) return;
if (!HasMountedInterceptCandidate(now)) return;
array<vector> reserved = {}; int first = -1; int second = -1;
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
// At most two bounded native road queries per normal five-second HQ pass.
int queries;
for (int b = 0; b < m_Memory.Count() && queries < 2; b++)
{
ERR_HQBelief belief = m_Memory.At(b);
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
vector destination; queries++;
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
ERR_HQUnit best;
ERR_ConvoyTask bestTask;
ERR_ConvoyVehicle bestVehicle;
ERR_HQRoute bestRoute;
float bestDistance = 1e20;
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!MountedInterceptTaskAvailable(task)) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
float distance = vector.DistanceXZ(own.Position, destination);
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || distance >= bestDistance) continue;
ERR_HQRoute candidateRoute = m_Network.Route(unit.Radio, true);
if (!candidateRoute) continue;
best = unit; bestTask = task; bestVehicle = vehicle; bestRoute = candidateRoute; bestDistance = distance;
}
}
}
if (!best) continue;
ERR_HQRoute route = bestRoute;
if (!route) continue;
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
order.Token = 400001 + m_MountedAttemptBeliefs.Count(); order.Group = best.Group;
order.SourceId = bestTask.Receipt.SourceId; order.VehicleIndex = bestVehicle.Index;
order.Position = destination; order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) continue;
// The attempt stays spent after radio interruption, physical refusal or loss.
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
best.LogisticsReservation = order.Token;
if (!m_Convoys.ReserveIntercept(bestTask, order, now))
{ if (best.LogisticsReservation == order.Token) best.LogisticsReservation = 0; return; }
m_MountedIntercept = order; m_MountedInterceptUnit = best; m_MountedInterceptTask = bestTask;
m_MountedInterceptRoute = route; m_MountedInterceptDelivered = false;
route.MessageId = m_Journal.Message();
route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, destination, order.BeliefId, order.BeliefRevision, order.ReportId);
m_Journal.Write("MOUNTED INTERCEPT QUEUED", route.Payload, route.MessageId);
return;
}
}
override void Stop()
{
float now;
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
ReleaseMountedIntercept(now, "mission stopped");
super.Stop();
}
}
@@ -0,0 +1,19 @@
check: /tmp/err-policy-c9ous0vi/check.cpp:266: int main(): Assertion `f.unit.LogisticsReservation==123&&f.service.releases==1&&f.h.releaseUnits==1' failed.
Traceback (most recent call last):
File "/tmp/erri-motorized-intercept-review/test_hq_mounted_dispatch.py", line 24, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<81 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-c9ous0vi/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,207 @@
// One physical escort may intercept a road ahead of delivered travel history.
// This HQ-owned transport uses the ordinary radio graph and never reads players.
modded class ERR_HQ
{
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
protected ERR_HQUnit m_MountedInterceptUnit;
protected ERR_ConvoyTask m_MountedInterceptTask;
protected ref ERR_HQRoute m_MountedInterceptRoute;
protected bool m_MountedInterceptDelivered;
protected ref array<int> m_MountedAttemptBeliefs = {};
protected ref array<int> m_MountedAttemptRevisions = {};
override string Status()
{
string result = super.Status();
if (!m_MountedIntercept) return result;
string phase = "queued";
if (m_MountedInterceptDelivered) phase = "delivered";
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
}
protected override int Assigned()
{
int count = super.Assigned();
if (m_MountedIntercept) count++;
return count;
}
protected override int HuntingAssigned()
{
int count = super.HuntingAssigned();
if (m_MountedIntercept) count++;
return count;
}
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
{
super.ReserveUnit(unit, reserved, first, second);
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
}
protected bool MountedInterceptAttempted(int beliefId, int revision)
{
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
return false;
}
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
{
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
}
protected void ReleaseMountedIntercept(float now, string reason)
{
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyTask task = m_MountedInterceptTask;
// Detach first: physical release may synchronously unregister the group.
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = false;
if (!order) return;
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
}
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
{
if (m_MountedIntercept && unit == m_MountedInterceptUnit) ReleaseMountedIntercept(now, "original formation released");
super.ReleaseUnit(unit, now);
}
protected override void TickMountedIntercept(float now)
{
if (!m_MountedIntercept) return;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyInterceptOrder order = m_MountedIntercept;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (now < order.IssuedAt || now >= order.ExpiresAt
|| !MissionActive() || m_Stopping || !unit || !m_Units.Contains(unit) || unit.Group != order.Group
|| unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|| !m_Convoys.InterceptCurrent(m_MountedInterceptTask, order))
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
if (m_MountedInterceptDelivered) return;
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
if (!ERR_HQMountedInterceptRules.Current(order, belief, now) || !m_MountedInterceptRoute)
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
if (delivered == 0) return;
if (delivered < 0)
{ ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
if (!MountedInterceptStatus(unit, vehicle, now) || ConvoyContact(unit, now)
|| !m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now))
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = true;
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escort ordered to the remembered road ahead", order.ReportId);
}
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
{
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) return false;
// Service admission excludes the whole original convoy, including sibling
// holds still in transit. Do not spend a delivered revision on a busy task.
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || vehicle.InterceptToken > 0 || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) return false;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && (unit.PendingConvoyOrder || unit.Downlink || unit.LogisticsReservation > 0)) return false;
}
return true;
}
protected bool HasMountedInterceptCandidate(float now)
{
// Avoid native road surveys until one original escort has usable delivered status.
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!MountedInterceptTaskAvailable(task)) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
&& MountedInterceptStatus(unit, vehicle, now)) return true;
}
}
return false;
}
protected override void PlanMountedIntercept(float now)
{
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept || m_MountedAttemptBeliefs.Count() >= 256) return;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (Assigned() >= globalLimit || HuntingAssigned() >= ERR_HQPursuitRules.Limit(globalLimit)) return;
if (!HasMountedInterceptCandidate(now)) return;
array<vector> reserved = {}; int first = -1; int second = -1;
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
// At most two bounded native road queries per normal five-second HQ pass.
int queries;
for (int b = 0; b < m_Memory.Count() && queries < 2; b++)
{
ERR_HQBelief belief = m_Memory.At(b);
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
vector destination; queries++;
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
ERR_HQUnit best;
ERR_ConvoyTask bestTask;
ERR_ConvoyVehicle bestVehicle;
ERR_HQRoute bestRoute;
float bestDistance = 1e20;
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!MountedInterceptTaskAvailable(task)) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
float distance = vector.DistanceXZ(own.Position, destination);
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || distance >= bestDistance) continue;
ERR_HQRoute candidateRoute = m_Network.Route(unit.Radio, true);
if (!candidateRoute) continue;
best = unit; bestTask = task; bestVehicle = vehicle; bestRoute = candidateRoute; bestDistance = distance;
}
}
}
if (!best) continue;
ERR_HQRoute route = bestRoute;
if (!route) continue;
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
order.Token = 400001 + m_MountedAttemptBeliefs.Count(); order.Group = best.Group;
order.SourceId = bestTask.Receipt.SourceId; order.VehicleIndex = bestVehicle.Index;
order.Position = destination; order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) continue;
// The attempt stays spent after radio interruption, physical refusal or loss.
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
best.LogisticsReservation = order.Token;
if (!m_Convoys.ReserveIntercept(bestTask, order, now))
{ if (best.LogisticsReservation == order.Token) best.LogisticsReservation = 0; return; }
m_MountedIntercept = order; m_MountedInterceptUnit = best; m_MountedInterceptTask = bestTask;
m_MountedInterceptRoute = route; m_MountedInterceptDelivered = false;
route.MessageId = m_Journal.Message();
route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, destination, order.BeliefId, order.BeliefRevision, order.ReportId);
m_Journal.Write("MOUNTED INTERCEPT QUEUED", route.Payload, route.MessageId);
return;
}
}
override void Stop()
{
float now;
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
ReleaseMountedIntercept(now, "mission stopped");
super.Stop();
}
}
@@ -0,0 +1,19 @@
check: /tmp/err-policy-ol0nqw5v/check.cpp:257: int main(): Assertion `f.h.m_MountedIntercept&&f.unit.LogisticsReservation==400001&&f.service.releases==0&&f.route.calls==0' failed.
Traceback (most recent call last):
File "/tmp/erri-motorized-intercept-review/test_hq_mounted_dispatch.py", line 24, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<81 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-ol0nqw5v/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,27 @@
{
"status": "Design/source review; awaiting implemented integration for actual-method tests",
"baseline_sha256": {
"ERR_HQ.c": "52e601a0571b4942c9d4d0ce820727df8498febd6726efbd7da0c5eff7041b58",
"ERR_ConvoyService.c": "e1d5dff5584f1bf12c146df510c860c3e0e046c989ff758def88ecf6b49c6392",
"ERR_GroundDepotHQ.c": "8748325b0810ea053b28d0ed4c00a75fceae1c0d1cd3b30efb4dafdc397632ac",
"ERR_GroundDepotLoading.c": "36da884a833e7a29743308490a3ce5ae299f7a451d87205a227163892aca4ed0",
"ERR_HQWatch.c": "abe634cf6003ae920ec1ed18e305ed462920204960894520d3dd11f7e5ef1c33",
"ERR_HQStatusSampler.c": "b937eaed913a5095afa8e678dc1c36b5c805541bf8cd5be927069533d6baed1c"
},
"concrete_findings": [
"Proposed300001..300256 token domain overlaps live depot300001..300096; require distinct domain.",
"GroundDepotHQ.Tick runs Plan AFTER super.Tick; base hook changes planning priority relative depot.",
"Current PlanConvoyTask checks vehicle.SupplyToken but not unit.LogisticsReservation: intercept needs veto or explicit combat preemption.",
"Current Eligible rejects seated/support units; shared assignment cleanup cannot classify intercept via that infantry predicate.",
"UnregisterGroup called synchronously by ConvoyVehicle.Release; detach own references before recursive cleanup.",
"Current ConvoyPositionKnown is only count/age gate, not complete validated mounted inventory/root proof."
],
"required_final_tests": [
"Single pending/active count once; both global/hunt caps respected and capacity trim",
"Pending interruption releases claim without mutating authored route; active radio/checkin replacement retains independent delivered commitment",
"Unregister/release/Stop clears exact claim and no foreign claim, no resume during teardown",
"Fresh detached delivered status and memory only; malformed/stale/revision change rejected",
"Exact once-per-revision attempt; no route mutation before downlink or reopening arrived/stopped finite task",
"Competing convoy combat, infantry/mounted/mines/depot admission excluded in both directions"
]
}
@@ -0,0 +1 @@
actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed
@@ -0,0 +1 @@
actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed
@@ -0,0 +1,52 @@
{
"scope": "Actual dispatch methods executed; native radio/physical service and memory policy are boundary doubles",
"source": "/tmp/erri-mounted-intercept-root-1247/ERR_HQMountedDispatch.c",
"source_sha256": "8650c09afc46439a74e17cdf968b09d238ec9437113ffad784a031cddd65440a",
"test": "/tmp/erri-motorized-intercept-review/test_hq_mounted_dispatch.py",
"test_sha256": "27c0bcd6edf33f4455276a3a4e791e08b8a14c09aa8f5506ae175496406f1531",
"green_log": "/tmp/erri-motorized-intercept-review/dispatch-green.log",
"mutations": {
"clock-gate": {
"exit": 1,
"runtime_assertion": true,
"log": "/tmp/erri-motorized-intercept-review/clock-gate-red.log"
},
"candidate-gate": {
"exit": 1,
"runtime_assertion": true,
"log": "/tmp/erri-motorized-intercept-review/candidate-gate-red.log"
},
"nearest-radio": {
"exit": 1,
"runtime_assertion": true,
"log": "/tmp/erri-motorized-intercept-review/nearest-radio-red.log"
},
"claim-release": {
"exit": 1,
"runtime_assertion": true,
"log": "/tmp/erri-motorized-intercept-review/claim-release-red.log"
},
"pending-apply": {
"exit": 1,
"runtime_assertion": true,
"log": "/tmp/erri-motorized-intercept-review/pending-apply-red.log"
},
"whole-task-gate": {
"exit": 1,
"runtime_assertion": true,
"log": "/tmp/erri-motorized-intercept-review/whole-task-gate-red.log"
}
},
"changes_by_reviewer": [
"Nonfinite Tick time returns without releasing exact claim through invalid native time.",
"No eligible original escort/fresh delivered status skips all road surveys.",
"Nearest no-radio candidate skipped; exact nearest reachable radio route retained.",
"Whole-task conflict guard shared by both scans, including sibling physical holds and queued downlinks/logistics claims.",
"Status appends escort, queued/delivered phase, source report and fixed destination."
],
"integration_api_review": "HQ exact logistics token precedes physical ReserveIntercept; release detaches HQ first then releases service before conditionally clearing original unit token. API fields/types and token400001..400256 match rules and physical service; no blocker found after task guard. Service uses exact retained order+private claim on release, independent of mutable order.Token.",
"limits": [
"Separate rules/service suites cover native admission and policy Current, which are controlled boundaries here.",
"No native driving, actual radio timing or interception effectiveness proof."
]
}
@@ -0,0 +1,10 @@
import subprocess,os,json,hashlib
from pathlib import Path
p=Path('/tmp/erri-motorized-intercept-review');source=Path('/tmp/erri-mounted-intercept-root-1247/ERR_HQMountedDispatch.c');s=source.read_text()
nearest=s.replace(' ERR_HQRoute candidateRoute = m_Network.Route(unit.Radio, true);\n if (!candidateRoute) continue;',' ERR_HQRoute candidateRoute = null;').replace(' ERR_HQRoute route = bestRoute;', ' ERR_HQRoute route = m_Network.Route(best.Radio, true);').replace(' ERR_HQRoute bestRoute;','').replace(' bestRoute = candidateRoute;','').replace(' ERR_HQRoute candidateRoute = null;\n','')
cases={'clock-gate':s.replace('if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;','if (!m_MountedIntercept) return;'),'candidate-gate':s.replace(' if (!HasMountedInterceptCandidate(now)) return;',''),'nearest-radio':nearest,'claim-release':s.replace('if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;','if (unit) unit.LogisticsReservation = 0;'),'pending-apply':s.replace(' if (delivered == 0) return;',''),'whole-task-gate':s.replace('if (!MountedInterceptTaskAvailable(task)) continue;','if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) continue;')}
res={};env=os.environ.copy();env.update(PYTHONDONTWRITEBYTECODE='1',PYTHONPATH='/home/jakub/devel/escapistsrereintroduced/tests')
for name,text in cases.items():
f=p/(name+'-mutated.c');f.write_text(text);env['ERR_HQ_MOUNTED_DISPATCH_SOURCE']=str(f);out=subprocess.run(['python3',str(p/'test_hq_mounted_dispatch.py')],env=env,capture_output=True,text=True);log=p/(name+'-red.log');log.write_text(out.stdout+out.stderr);res[name]={'exit':out.returncode,'runtime_assertion':'Assertion' in out.stderr,'log':str(log)}
review={'scope':'Actual dispatch methods executed; native radio/physical service and memory policy are boundary doubles','source':str(source),'source_sha256':hashlib.sha256(source.read_bytes()).hexdigest(),'test':str(p/'test_hq_mounted_dispatch.py'),'test_sha256':hashlib.sha256((p/'test_hq_mounted_dispatch.py').read_bytes()).hexdigest(),'green_log':str(p/'dispatch-green.log'),'mutations':res,'changes_by_reviewer':['Nonfinite Tick time returns without releasing exact claim through invalid native time.','No eligible original escort/fresh delivered status skips all road surveys.','Nearest no-radio candidate skipped; exact nearest reachable radio route retained.','Whole-task conflict guard shared by both scans, including sibling physical holds and queued downlinks/logistics claims.','Status appends escort, queued/delivered phase, source report and fixed destination.'],'integration_api_review':'HQ exact logistics token precedes physical ReserveIntercept; release detaches HQ first then releases service before conditionally clearing original unit token. API fields/types and token400001..400256 match rules and physical service; no blocker found after task guard. Service uses exact retained order+private claim on release, independent of mutable order.Token.','limits':['Separate rules/service suites cover native admission and policy Current, which are controlled boundaries here.','No native driving, actual radio timing or interception effectiveness proof.']}
(p/'dispatch-review.json').write_text(json.dumps(review,indent=2)+'\n');print(json.dumps(review,indent=2));assert all(x['exit']!=0 and x['runtime_assertion'] for x in res.values())
@@ -0,0 +1,205 @@
// One physical escort may intercept a road ahead of delivered travel history.
// This HQ-owned transport uses the ordinary radio graph and never reads players.
modded class ERR_HQ
{
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
protected ERR_HQUnit m_MountedInterceptUnit;
protected ERR_ConvoyTask m_MountedInterceptTask;
protected ref ERR_HQRoute m_MountedInterceptRoute;
protected bool m_MountedInterceptDelivered;
protected ref array<int> m_MountedAttemptBeliefs = {};
protected ref array<int> m_MountedAttemptRevisions = {};
override string Status()
{
string result = super.Status();
if (!m_MountedIntercept) return result;
string phase = "queued";
if (m_MountedInterceptDelivered) phase = "delivered";
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
}
protected override int Assigned()
{
int count = super.Assigned();
if (m_MountedIntercept) count++;
return count;
}
protected override int HuntingAssigned()
{
int count = super.HuntingAssigned();
if (m_MountedIntercept) count++;
return count;
}
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
{
super.ReserveUnit(unit, reserved, first, second);
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
}
protected bool MountedInterceptAttempted(int beliefId, int revision)
{
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
return false;
}
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
{
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
}
protected void ReleaseMountedIntercept(float now, string reason)
{
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyTask task = m_MountedInterceptTask;
// Detach first: physical release may synchronously unregister the group.
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = false;
if (!order) return;
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
}
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
{
if (m_MountedIntercept && unit == m_MountedInterceptUnit) ReleaseMountedIntercept(now, "original formation released");
super.ReleaseUnit(unit, now);
}
protected override void TickMountedIntercept(float now)
{
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyInterceptOrder order = m_MountedIntercept;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (now < order.IssuedAt || now >= order.ExpiresAt
|| !MissionActive() || m_Stopping || !unit || !m_Units.Contains(unit) || unit.Group != order.Group
|| unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|| !m_Convoys.InterceptCurrent(m_MountedInterceptTask, order))
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
if (m_MountedInterceptDelivered) return;
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
if (!ERR_HQMountedInterceptRules.Current(order, belief, now) || !m_MountedInterceptRoute)
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
if (delivered == 0) return;
if (delivered < 0)
{ ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
if (!MountedInterceptStatus(unit, vehicle, now) || ConvoyContact(unit, now)
|| !m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now))
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = true;
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escort ordered to the remembered road ahead", order.ReportId);
}
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
{
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) return false;
// Service admission excludes the whole original convoy, including sibling
// holds still in transit. Do not spend a delivered revision on a busy task.
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || vehicle.InterceptToken > 0 || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) return false;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && (unit.PendingConvoyOrder || unit.Downlink || unit.LogisticsReservation > 0)) return false;
}
return true;
}
protected bool HasMountedInterceptCandidate(float now)
{
// Avoid native road surveys until one original escort has usable delivered status.
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!MountedInterceptTaskAvailable(task)) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
&& MountedInterceptStatus(unit, vehicle, now)) return true;
}
}
return false;
}
protected override void PlanMountedIntercept(float now)
{
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept || m_MountedAttemptBeliefs.Count() >= 256) return;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (Assigned() >= globalLimit || HuntingAssigned() >= ERR_HQPursuitRules.Limit(globalLimit)) return;
if (!HasMountedInterceptCandidate(now)) return;
array<vector> reserved = {}; int first = -1; int second = -1;
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
// At most two bounded native road queries per normal five-second HQ pass.
int queries;
for (int b = 0; b < m_Memory.Count() && queries < 2; b++)
{
ERR_HQBelief belief = m_Memory.At(b);
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
vector destination; queries++;
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
ERR_HQUnit best;
ERR_ConvoyTask bestTask;
ERR_ConvoyVehicle bestVehicle;
float bestDistance = 1e20;
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!MountedInterceptTaskAvailable(task)) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
float distance = vector.DistanceXZ(own.Position, destination);
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || distance >= bestDistance) continue;
best = unit; bestTask = task; bestVehicle = vehicle; bestDistance = distance;
}
}
}
if (!best) continue;
ERR_HQRoute route = m_Network.Route(best.Radio, true);
if (!route) continue;
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
order.Token = 400001 + m_MountedAttemptBeliefs.Count(); order.Group = best.Group;
order.SourceId = bestTask.Receipt.SourceId; order.VehicleIndex = bestVehicle.Index;
order.Position = destination; order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) continue;
// The attempt stays spent after radio interruption, physical refusal or loss.
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
best.LogisticsReservation = order.Token;
if (!m_Convoys.ReserveIntercept(bestTask, order, now))
{ if (best.LogisticsReservation == order.Token) best.LogisticsReservation = 0; return; }
m_MountedIntercept = order; m_MountedInterceptUnit = best; m_MountedInterceptTask = bestTask;
m_MountedInterceptRoute = route; m_MountedInterceptDelivered = false;
route.MessageId = m_Journal.Message();
route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, destination, order.BeliefId, order.BeliefRevision, order.ReportId);
m_Journal.Write("MOUNTED INTERCEPT QUEUED", route.Payload, route.MessageId);
return;
}
}
override void Stop()
{
float now;
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
ReleaseMountedIntercept(now, "mission stopped");
super.Stop();
}
}
@@ -0,0 +1,19 @@
check: /tmp/err-policy-01luvouf/check.cpp:253: int main(): Assertion `f.h.m_MountedIntercept&&f.h.m_MountedInterceptUnit==&u2&&f.h.m_MountedIntercept->VehicleIndex==3&&f.network.calls==2&&!f.unit.LogisticsReservation&&u2.LogisticsReservation==400001' failed.
Traceback (most recent call last):
File "/tmp/erri-motorized-intercept-review/test_hq_mounted_dispatch.py", line 24, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<81 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-01luvouf/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,207 @@
// One physical escort may intercept a road ahead of delivered travel history.
// This HQ-owned transport uses the ordinary radio graph and never reads players.
modded class ERR_HQ
{
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
protected ERR_HQUnit m_MountedInterceptUnit;
protected ERR_ConvoyTask m_MountedInterceptTask;
protected ref ERR_HQRoute m_MountedInterceptRoute;
protected bool m_MountedInterceptDelivered;
protected ref array<int> m_MountedAttemptBeliefs = {};
protected ref array<int> m_MountedAttemptRevisions = {};
override string Status()
{
string result = super.Status();
if (!m_MountedIntercept) return result;
string phase = "queued";
if (m_MountedInterceptDelivered) phase = "delivered";
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
}
protected override int Assigned()
{
int count = super.Assigned();
if (m_MountedIntercept) count++;
return count;
}
protected override int HuntingAssigned()
{
int count = super.HuntingAssigned();
if (m_MountedIntercept) count++;
return count;
}
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
{
super.ReserveUnit(unit, reserved, first, second);
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
}
protected bool MountedInterceptAttempted(int beliefId, int revision)
{
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
return false;
}
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
{
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
}
protected void ReleaseMountedIntercept(float now, string reason)
{
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyTask task = m_MountedInterceptTask;
// Detach first: physical release may synchronously unregister the group.
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = false;
if (!order) return;
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
}
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
{
if (m_MountedIntercept && unit == m_MountedInterceptUnit) ReleaseMountedIntercept(now, "original formation released");
super.ReleaseUnit(unit, now);
}
protected override void TickMountedIntercept(float now)
{
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyInterceptOrder order = m_MountedIntercept;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (now < order.IssuedAt || now >= order.ExpiresAt
|| !MissionActive() || m_Stopping || !unit || !m_Units.Contains(unit) || unit.Group != order.Group
|| unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|| !m_Convoys.InterceptCurrent(m_MountedInterceptTask, order))
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
if (m_MountedInterceptDelivered) return;
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
if (!ERR_HQMountedInterceptRules.Current(order, belief, now) || !m_MountedInterceptRoute)
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
if (delivered < 0)
{ ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
if (!MountedInterceptStatus(unit, vehicle, now) || ConvoyContact(unit, now)
|| !m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now))
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = true;
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escort ordered to the remembered road ahead", order.ReportId);
}
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
{
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) return false;
// Service admission excludes the whole original convoy, including sibling
// holds still in transit. Do not spend a delivered revision on a busy task.
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || vehicle.InterceptToken > 0 || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) return false;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && (unit.PendingConvoyOrder || unit.Downlink || unit.LogisticsReservation > 0)) return false;
}
return true;
}
protected bool HasMountedInterceptCandidate(float now)
{
// Avoid native road surveys until one original escort has usable delivered status.
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!MountedInterceptTaskAvailable(task)) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
&& MountedInterceptStatus(unit, vehicle, now)) return true;
}
}
return false;
}
protected override void PlanMountedIntercept(float now)
{
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept || m_MountedAttemptBeliefs.Count() >= 256) return;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (Assigned() >= globalLimit || HuntingAssigned() >= ERR_HQPursuitRules.Limit(globalLimit)) return;
if (!HasMountedInterceptCandidate(now)) return;
array<vector> reserved = {}; int first = -1; int second = -1;
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
// At most two bounded native road queries per normal five-second HQ pass.
int queries;
for (int b = 0; b < m_Memory.Count() && queries < 2; b++)
{
ERR_HQBelief belief = m_Memory.At(b);
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
vector destination; queries++;
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
ERR_HQUnit best;
ERR_ConvoyTask bestTask;
ERR_ConvoyVehicle bestVehicle;
ERR_HQRoute bestRoute;
float bestDistance = 1e20;
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!MountedInterceptTaskAvailable(task)) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
float distance = vector.DistanceXZ(own.Position, destination);
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || distance >= bestDistance) continue;
ERR_HQRoute candidateRoute = m_Network.Route(unit.Radio, true);
if (!candidateRoute) continue;
best = unit; bestTask = task; bestVehicle = vehicle; bestRoute = candidateRoute; bestDistance = distance;
}
}
}
if (!best) continue;
ERR_HQRoute route = bestRoute;
if (!route) continue;
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
order.Token = 400001 + m_MountedAttemptBeliefs.Count(); order.Group = best.Group;
order.SourceId = bestTask.Receipt.SourceId; order.VehicleIndex = bestVehicle.Index;
order.Position = destination; order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) continue;
// The attempt stays spent after radio interruption, physical refusal or loss.
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
best.LogisticsReservation = order.Token;
if (!m_Convoys.ReserveIntercept(bestTask, order, now))
{ if (best.LogisticsReservation == order.Token) best.LogisticsReservation = 0; return; }
m_MountedIntercept = order; m_MountedInterceptUnit = best; m_MountedInterceptTask = bestTask;
m_MountedInterceptRoute = route; m_MountedInterceptDelivered = false;
route.MessageId = m_Journal.Message();
route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, destination, order.BeliefId, order.BeliefRevision, order.ReportId);
m_Journal.Write("MOUNTED INTERCEPT QUEUED", route.Payload, route.MessageId);
return;
}
}
override void Stop()
{
float now;
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
ReleaseMountedIntercept(now, "mission stopped");
super.Stop();
}
}
@@ -0,0 +1,19 @@
check: /tmp/err-policy-fw5ub4ch/check.cpp:251: int main(): Assertion `f.route.calls==1&&f.service.applies==0' failed.
Traceback (most recent call last):
File "/tmp/erri-motorized-intercept-review/test_hq_mounted_dispatch.py", line 24, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<81 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-fw5ub4ch/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,109 @@
"""Execute actual mounted HQ dispatch methods; radio/physical service are adversarial doubles."""
from policy_harness import ROOT, body, run
from pathlib import Path
import os,re
P=Path(os.environ.get('ERR_HQ_MOUNTED_DISPATCH_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c'))
names=['override string Status(', 'protected override int Assigned(', 'protected override int HuntingAssigned(', 'protected override void ReserveUnit(', 'protected bool MountedInterceptAttempted(', 'protected bool MountedInterceptStatus(', 'protected void ReleaseMountedIntercept(', 'protected override void ReleaseUnit(', 'protected override void TickMountedIntercept(', 'protected bool MountedInterceptTaskAvailable(', 'protected bool HasMountedInterceptCandidate(', 'protected override void PlanMountedIntercept(', 'override void Stop(']
def cpp(s):
s=s.replace('protected ','').replace('override ','').replace('ref ','').replace('out int ','int& ').replace('super.','Base::').replace('null','nullptr').replace('foreach (','for (')
for typ in ['ERR_ConvoyInterceptOrder','ERR_HQUnit','ERR_ConvoyTask','ERR_HQRoute','ERR_ConvoyVehicle','ERR_HQWatchEntry','ERR_HQBelief']:
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
s=s.replace('array<vector> reserved','Array<vector> reserved')
for typ in ['Replication','ERR_ForceRules','ERR_HQMemoryRules','ERR_HQPursuitRules','ERR_HQMountedInterceptRules','vector','string']:
s=s.replace(typ+'.',typ+'::')
for var in ['unit','vehicle','entry','order','task','belief','known','best','bestTask','bestVehicle','route','candidateRoute','own','m_Mode','m_Watch','m_Memory','m_Convoys','m_Journal','m_Network','m_MountedIntercept','m_MountedInterceptTask','m_MountedInterceptRoute']:
s=re.sub(r'\b'+var+r'\.',var+'->',s)
for name in ['StatusSampler','MountedAmmo','Combat','Receipt']:
s=s.replace('->'+name+'.','->'+name+'->')
s=s.replace('GetGame().','GetGame()->').replace('GetWorld().','GetWorld()->').replace('Settings().','Settings()->')
s=re.sub(r'(ERR_\w+\*) (\w+);',r'\1 \2 = nullptr;',s)
s=s.replace('int queries;','int queries=0;').replace('float now;','float now=0;')
s=s.replace('Array<vector> reserved,','Array<vector>& reserved,')
return s
methods='\n'.join(cpp(body(P,n)) for n in names if n not in ('protected bool HasMountedInterceptCandidate(', 'protected bool MountedInterceptTaskAvailable(') or n in P.read_text())
run(r'''
#include <cassert>
#include <vector>
#include <map>
#include <string>
#include <algorithm>
#include <cmath>
#include <functional>
#include <sstream>
struct string:std::string{using std::string::string;string(std::string s):std::string(s){} template<class...T>static string Format(const char*fmt,T...v){std::ostringstream out;out<<fmt;((out<<"|"<<v),...);return out.str();}};
struct vector{float x=0,z=0;static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}bool operator==(vector b)const{return x==b.x&&z==b.z;}};
std::ostream&operator<<(std::ostream&o,vector v){return o<<v.x<<","<<v.z;}
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}void Insert(T v){this->push_back(v);}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
struct Group{};struct Entity{};struct Sampler{bool roster=true,bound=true;bool OwnsRoster(Group*){return roster;}bool ValidateBoundVehicle(Entity*){return bound;}};
struct Ammo{bool Complete=true,valid=true;int Weapons=1,Rounds=20,Chambers=0;bool Valid(){return valid;}};
struct ERR_HQWatchEntry{bool Closed=false;string SourceId="crew";int MessageId=10,Count=3,Strength=3,Fit=3;float ObservedAt=1000,LastDeliveredAt=2000;vector Position;Ammo*MountedAmmo=nullptr;};
struct ERR_HQUnit{struct Group*Group=nullptr;Sampler*StatusSampler=nullptr;string ForceSourceId="crew";int LogisticsReservation=0;bool eligible=true,contact=false,PendingConvoyOrder=false,Downlink=false;Entity*Radio=nullptr;};
struct CombatState{bool Held=false;};struct Receipt{string SourceId="convoy";};
struct ERR_ConvoyVehicle{struct Group*Group=nullptr;struct Entity*Entity=nullptr;int Index=0,InterceptToken=0,SupplyToken=0;bool Released=false,Arrived=false,Registered=true,SupplyHeld=false;CombatState*Combat=nullptr;};
struct ERR_ConvoyTask{bool Stopped=false,Ready=true;struct Receipt*Receipt=nullptr;Array<ERR_ConvoyVehicle*>Vehicles;};
struct ERR_ConvoyInterceptOrder{int Token=0,VehicleIndex=0,BeliefId=0,BeliefRevision=0,ReportId=0;struct Group*Group=nullptr;string SourceId;vector Position;float IssuedAt=0,ExpiresAt=0,ObservedAt=0;};
struct ERR_HQBelief{int BeliefId=1,BeliefRevision=2,MessageId=20;float ObservedAt=1000;bool eligible=true,select=true;vector destination{100,0};};
struct Journal{int rows=0;int Message(){return 200;}void Write(string,string,int=0){rows++;}};
struct ERR_HQRoute{int result=0,calls=0,MessageId=0;string Payload;int Advance(float,Journal*){calls++;return result;}};
struct Watch{ERR_HQWatchEntry*entry=nullptr,*second=nullptr;int SquadLimit(int n){return n;}ERR_HQWatchEntry*Find(string id){return entry&&entry->SourceId==id?entry:(second&&second->SourceId==id?second:nullptr);}};
struct Memory{Array<ERR_HQBelief*>entries;int Count(){return entries.Count();}ERR_HQBelief*At(int n){return entries[n];}ERR_HQBelief*GetById(int n){for(auto b:entries)if(b->BeliefId==n)return b;return nullptr;}};
struct Network{ERR_HQRoute*route=nullptr;Entity*denied=nullptr;int calls=0;ERR_HQRoute*Route(Entity*radio,bool){calls++;return radio==denied?nullptr:route;}};
struct Settings{int cap=3,radius=1000;int Runtime(string s){return s=="supportSquadLimit"?cap:radius;}};
struct Mode{struct Settings settings;struct Settings*Settings(){return &settings;}};
struct Service{Array<ERR_ConvoyTask*>tasks;bool current=true,reserve=true,apply=true;int reserves=0,applies=0,releases=0;std::function<void()>onRelease;
int Count(){return tasks.Count();}ERR_ConvoyTask*At(int n){return tasks[n];}
bool InterceptCurrent(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*){return current;}
bool ReserveIntercept(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*,float){reserves++;return reserve;}
bool ApplyIntercept(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*,float){applies++;return apply;}
void ReleaseIntercept(ERR_ConvoyTask*,ERR_ConvoyInterceptOrder*,float){releases++;if(onRelease)onRelease();}
};
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};
struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}static bool FiniteVector(vector v){return Finite(v.x)&&Finite(v.z);}};
struct ERR_HQMemoryRules{static bool DeliveryFresh(float observed,float now){return std::isfinite(observed)&&std::isfinite(now)&&observed>=0&&now>=observed&&now-observed<=120000;}};
struct ERR_HQPursuitRules{static int Limit(int n){return std::min(n,3);}};
struct ERR_HQMountedInterceptRules{inline static int selects=0;static bool Eligible(ERR_HQBelief*b,float){return b&&b->eligible;}
static bool Current(ERR_ConvoyInterceptOrder*o,ERR_HQBelief*b,float now){return Eligible(b,now)&&o->BeliefId==b->BeliefId&&o->BeliefRevision==b->BeliefRevision&&now>=o->IssuedAt&&now<o->ExpiresAt;}
static bool Select(ERR_HQBelief*b,float,Array<vector>&reserved,vector&dest){selects++;dest=b->destination;return b->select&&!reserved.Contains(dest);}};
struct Clock{Clock*GetWorld(){return this;}float GetWorldTime(){return 3000;}}clock_;Clock*GetGame(){return &clock_;}
struct Base{virtual string Status(){return "base HQ status";}int assigned=0,hunting=0,releaseUnits=0,stops=0;Array<vector>destinations;virtual int Assigned(){return assigned;}virtual int HuntingAssigned(){return hunting;}
virtual void ReserveUnit(ERR_HQUnit*,Array<vector>&r,int&,int&){for(auto p:destinations)r.Insert(p);}virtual void ReleaseUnit(ERR_HQUnit*,float){releaseUnits++;}virtual void Stop(){stops++;}};
struct HQ:Base{
ERR_ConvoyInterceptOrder*m_MountedIntercept=nullptr;ERR_HQUnit*m_MountedInterceptUnit=nullptr;ERR_ConvoyTask*m_MountedInterceptTask=nullptr;ERR_HQRoute*m_MountedInterceptRoute=nullptr;bool m_MountedInterceptDelivered=false,m_Stopping=false,active=true;
Array<int>m_MountedAttemptBeliefs,m_MountedAttemptRevisions;Array<ERR_HQUnit*>m_Units;Watch*m_Watch;Mode*m_Mode;Service*m_Convoys;Memory*m_Memory;Journal*m_Journal;Network*m_Network;
bool MissionActive(){return active;}bool MountedLogisticsEligible(ERR_HQUnit*u){return u&&u->eligible&&!u->LogisticsReservation;}bool ConvoyContact(ERR_HQUnit*u,float){return u->contact;}
'''+methods+r'''
};
struct Fixture{Group group,otherGroup;Entity entity,radio;Sampler sampler;Ammo ammo;ERR_HQWatchEntry entry;ERR_HQUnit unit;CombatState combat;Receipt receipt;ERR_ConvoyVehicle vehicle,unused;ERR_ConvoyTask task;ERR_HQBelief belief;ERR_HQRoute route;Watch watch;Mode mode;Service service;Memory memory;Journal journal;Network network;HQ h;
Fixture(){entry.MountedAmmo=&ammo;unit.Group=&group;unit.Radio=&radio;unit.StatusSampler=&sampler;vehicle.Group=&group;vehicle.Entity=&entity;vehicle.Combat=&combat;unused.Combat=&combat;unused.Released=true;task.Receipt=&receipt;task.Vehicles={&vehicle,&unused,&unused,&unused,&unused,&unused};service.tasks={&task};memory.entries={&belief};watch.entry=&entry;network.route=&route;h.m_Units={&unit};h.m_Watch=&watch;h.m_Mode=&mode;h.m_Convoys=&service;h.m_Memory=&memory;h.m_Journal=&journal;h.m_Network=&network;}
void plan(){h.PlanMountedIntercept(3000);}void queued(){plan();assert(h.m_MountedIntercept&&unit.LogisticsReservation==400001);assert(service.reserves==1&&service.applies==0&&route.calls==0);}
};
int main(){
{Fixture f;assert(f.h.Status()=="base HQ status");f.queued();string queued=f.h.Status();assert(queued.find("base HQ status")!=string::npos&&queued.find("|0|queued|20|100,0")!=string::npos);f.route.result=1;f.h.TickMountedIntercept(4000);assert(f.h.Status().find("|0|delivered|20|100,0")!=string::npos);f.h.Stop();assert(f.h.Status()=="base HQ status");}
{Fixture f;f.queued();assert(f.h.Assigned()==1&&f.h.HuntingAssigned()==1);Array<vector>r;int a=-1,b=-1;f.h.ReserveUnit(&f.unit,r,a,b);assert(r.Count()==1&&r[0]==f.belief.destination);f.h.PlanMountedIntercept(4000);assert(f.service.reserves==1);f.h.TickMountedIntercept(4000);assert(f.route.calls==1&&f.service.applies==0);f.route.result=1;f.h.TickMountedIntercept(5000);assert(f.service.applies==1&&f.h.m_MountedInterceptDelivered);f.belief.BeliefRevision++;f.h.TickMountedIntercept(6000);assert(f.h.m_MountedInterceptDelivered&&f.service.releases==0);f.h.TickMountedIntercept(121000);assert(!f.h.m_MountedIntercept&&f.unit.LogisticsReservation==0);}
// Task-wide logistics and queued sibling downlinks cannot consume a revision or starve a free farther convoy.
for(int kind=0;kind<5;kind++){Fixture f,far;far.unit.ForceSourceId=far.entry.SourceId="crew2";far.receipt.SourceId="convoy2";far.entry.Position={-200,0};f.watch.second=&far.entry;f.service.tasks.Insert(&far.task);f.h.m_Units.Insert(&far.unit);ERR_HQUnit sibling; sibling.Group=&f.otherGroup;f.unused.Group=&f.otherGroup;f.h.m_Units.Insert(&sibling);if(kind==0)f.unused.SupplyToken=123;if(kind==1)f.unused.SupplyHeld=true;if(kind==2)sibling.PendingConvoyOrder=true;if(kind==3)sibling.Downlink=true;if(kind==4)sibling.LogisticsReservation=123;f.plan();assert(f.h.m_MountedIntercept&&f.h.m_MountedInterceptUnit==&far.unit&&!f.unit.LogisticsReservation&&far.unit.LogisticsReservation==400001&&f.service.reserves==1);}
// A disconnected nearest escort cannot starve the farther reachable original escort.
{Fixture f;Group group2;Entity radio2,entity2;ERR_HQUnit u2=f.unit;u2.Group=&group2;u2.Radio=&radio2;u2.ForceSourceId="crew2";ERR_HQWatchEntry entry2=f.entry;entry2.SourceId="crew2";entry2.Position={-200,0};f.watch.second=&entry2;ERR_ConvoyVehicle vehicle2=f.vehicle;vehicle2.Group=&group2;vehicle2.Entity=&entity2;vehicle2.Index=3;f.task.Vehicles[3]=&vehicle2;f.h.m_Units.Insert(&u2);f.network.denied=&f.radio;f.plan();assert(f.h.m_MountedIntercept&&f.h.m_MountedInterceptUnit==&u2&&f.h.m_MountedIntercept->VehicleIndex==3&&f.network.calls==2&&!f.unit.LogisticsReservation&&u2.LogisticsReservation==400001);}
// Invalid world clock must preserve the exact claim for the next finite tick.
{Fixture f;f.queued();f.h.TickMountedIntercept(std::nanf(""));assert(f.h.m_MountedIntercept&&f.unit.LogisticsReservation==400001&&f.service.releases==0&&f.route.calls==0);}
// No own vehicle/fresh crew evidence means no native road surveys.
for(int kind=0;kind<6;kind++){Fixture f;if(kind==0)f.service.tasks.clear();if(kind==1)f.task.Stopped=true;if(kind==2)f.unit.LogisticsReservation=1;if(kind==3)f.entry.Closed=true;if(kind==4)f.sampler.bound=false;if(kind==5)f.combat.Held=true;int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::selects==before&&f.service.reserves==0);}
// Interrupted pending claims never apply physical orders, and the same delivered revision cannot retry.
{Fixture f;f.queued();f.route.result=-1;f.h.TickMountedIntercept(4000);assert(!f.h.m_MountedIntercept&&!f.unit.LogisticsReservation&&f.service.applies==0);f.plan();assert(f.service.reserves==1);f.belief.BeliefRevision++;f.plan();assert(f.service.reserves==2&&f.unit.LogisticsReservation==400002);}
{Fixture f;f.queued();f.belief.BeliefRevision++;f.route.result=1;f.h.TickMountedIntercept(4000);assert(f.route.calls==0&&f.service.applies==0&&!f.h.m_MountedIntercept);}
// Physical validation changes after radio delivery cannot trigger a stale order.
for(int kind=0;kind<7;kind++){Fixture f;f.queued();f.route.result=1;if(kind==0)f.ammo.Complete=false;if(kind==1)f.ammo.valid=false;if(kind==2)f.sampler.bound=false;if(kind==3)f.entry.Fit--;if(kind==4)f.entry.LastDeliveredAt=9000;if(kind==5)f.unit.contact=true;if(kind==6)f.service.apply=false;f.h.TickMountedIntercept(4000);assert(!f.h.m_MountedIntercept&&!f.unit.LogisticsReservation);assert(f.service.applies==(kind==6?1:0));}
// Conditional release and recursive unregister preserve unrelated reservations.
{Fixture f;f.queued();f.unit.LogisticsReservation=123;f.service.onRelease=[&]{assert(!f.h.m_MountedIntercept);f.h.ReleaseUnit(&f.unit,4000);};f.h.TickMountedIntercept(4000);assert(f.unit.LogisticsReservation==123&&f.service.releases==1&&f.h.releaseUnits==1);}
{Fixture f;f.queued();f.h.Stop();assert(!f.h.m_MountedIntercept&&!f.unit.LogisticsReservation&&f.service.releases==1&&f.h.stops==1&&f.service.applies==0);}
// Ordinary assigned and pursuit capacities reserve space before any road query or radio action.
for(int kind=0;kind<3;kind++){Fixture f;if(kind==0)f.mode.settings.cap=0;if(kind==1)f.h.assigned=3;if(kind==2)f.h.hunting=3;int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(!f.h.m_MountedIntercept&&f.service.reserves==0&&ERR_HQMountedInterceptRules::selects==before);}
{Fixture f;f.queued();f.h.assigned=3;f.h.TickMountedIntercept(4000);assert(!f.h.m_MountedIntercept&&f.service.applies==0);}
// Admission is based on detached delivered position, not physical entity telemetry; stock/crew/roles guard it.
for(int kind=0;kind<14;kind++){Fixture f;if(kind==0)f.vehicle.Index=1;if(kind==1)f.vehicle.Index=2;if(kind==2)f.vehicle.Arrived=true;if(kind==3)f.vehicle.SupplyToken=1;if(kind==4)f.combat.Held=true;if(kind==5)f.unit.LogisticsReservation=300001;if(kind==6)f.sampler.roster=false;if(kind==7)f.entry.Position={2000,0};if(kind==8)f.entry.ObservedAt=4000;if(kind==9)f.entry.LastDeliveredAt=0;if(kind==10)f.ammo.Weapons=0;if(kind==11)f.ammo.Rounds=0;if(kind==12)f.entry.Count=2;if(kind==13)f.entry.Closed=true;f.plan();assert(!f.h.m_MountedIntercept&&f.service.reserves==0);}
{Fixture f;f.service.reserve=false;f.plan();assert(!f.unit.LogisticsReservation&&!f.h.m_MountedIntercept);f.service.reserve=true;f.plan();assert(f.service.reserves==1);}
{Fixture f;for(int i=0;i<256;i++){f.h.m_MountedAttemptBeliefs.Insert(i+20);f.h.m_MountedAttemptRevisions.Insert(1);}f.plan();assert(!f.h.m_MountedIntercept&&f.service.reserves==0);}
{Fixture f;ERR_HQBelief b2,b3;f.belief.select=b2.select=false;b2.BeliefId=2;b3.BeliefId=3;f.memory.entries={&f.belief,&b2,&b3};int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::selects-before==2&&f.service.reserves==0);}
}
''')
print('actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed')
@@ -0,0 +1,207 @@
// One physical escort may intercept a road ahead of delivered travel history.
// This HQ-owned transport uses the ordinary radio graph and never reads players.
modded class ERR_HQ
{
protected ref ERR_ConvoyInterceptOrder m_MountedIntercept;
protected ERR_HQUnit m_MountedInterceptUnit;
protected ERR_ConvoyTask m_MountedInterceptTask;
protected ref ERR_HQRoute m_MountedInterceptRoute;
protected bool m_MountedInterceptDelivered;
protected ref array<int> m_MountedAttemptBeliefs = {};
protected ref array<int> m_MountedAttemptRevisions = {};
override string Status()
{
string result = super.Status();
if (!m_MountedIntercept) return result;
string phase = "queued";
if (m_MountedInterceptDelivered) phase = "delivered";
return result + "\n" + string.Format("Mounted interception: escort %1; %2; report M%3; road %4", m_MountedIntercept.VehicleIndex, phase, m_MountedIntercept.ReportId, m_MountedIntercept.Position);
}
protected override int Assigned()
{
int count = super.Assigned();
if (m_MountedIntercept) count++;
return count;
}
protected override int HuntingAssigned()
{
int count = super.HuntingAssigned();
if (m_MountedIntercept) count++;
return count;
}
protected override void ReserveUnit(ERR_HQUnit unit, array<vector> reserved, out int first, out int second)
{
super.ReserveUnit(unit, reserved, first, second);
if (m_MountedIntercept && unit == m_MountedInterceptUnit) reserved.Insert(m_MountedIntercept.Position);
}
protected bool MountedInterceptAttempted(int beliefId, int revision)
{
for (int i = 0; i < m_MountedAttemptBeliefs.Count(); i++)
if (m_MountedAttemptBeliefs[i] == beliefId && m_MountedAttemptRevisions[i] == revision) return true;
return false;
}
protected bool MountedInterceptStatus(ERR_HQUnit unit, ERR_ConvoyVehicle vehicle, float now)
{
if (!unit || !vehicle || !unit.StatusSampler || vehicle.Group != unit.Group || !vehicle.Entity
|| !unit.StatusSampler.OwnsRoster(unit.Group) || !unit.StatusSampler.ValidateBoundVehicle(vehicle.Entity)) return false;
ERR_HQWatchEntry entry = m_Watch.Find(unit.ForceSourceId);
if (!entry || entry.Closed || entry.SourceId != unit.ForceSourceId || entry.MessageId <= 0
|| !ERR_HQMemoryRules.DeliveryFresh(entry.ObservedAt, now) || !ERR_ForceRules.Finite(entry.LastDeliveredAt)
|| entry.LastDeliveredAt < entry.ObservedAt || entry.LastDeliveredAt > now
|| !ERR_ForceRules.FiniteVector(entry.Position) || entry.Count < 1 || entry.Count != entry.Strength
|| entry.Fit != entry.Count || !entry.MountedAmmo || !entry.MountedAmmo.Complete || !entry.MountedAmmo.Valid()) return false;
return entry.MountedAmmo.Weapons > 0 && entry.MountedAmmo.Rounds + entry.MountedAmmo.Chambers > 0;
}
protected void ReleaseMountedIntercept(float now, string reason)
{
ref ERR_ConvoyInterceptOrder order = m_MountedIntercept;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyTask task = m_MountedInterceptTask;
// Detach first: physical release may synchronously unregister the group.
m_MountedIntercept = null; m_MountedInterceptUnit = null; m_MountedInterceptTask = null;
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = false;
if (!order) return;
if (m_Convoys) m_Convoys.ReleaseIntercept(task, order, now);
if (unit && unit.LogisticsReservation == order.Token) unit.LogisticsReservation = 0;
m_Journal.Write("MOUNTED INTERCEPT END", reason, order.ReportId);
}
protected override void ReleaseUnit(ERR_HQUnit unit, float now)
{
if (m_MountedIntercept && unit == m_MountedInterceptUnit) ReleaseMountedIntercept(now, "original formation released");
super.ReleaseUnit(unit, now);
}
protected override void TickMountedIntercept(float now)
{
if (!m_MountedIntercept || !ERR_ForceRules.Finite(now)) return;
ERR_HQUnit unit = m_MountedInterceptUnit;
ERR_ConvoyInterceptOrder order = m_MountedIntercept;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (now < order.IssuedAt || now >= order.ExpiresAt
|| !MissionActive() || m_Stopping || !unit || !m_Units.Contains(unit) || unit.Group != order.Group
|| unit.LogisticsReservation != order.Token || !unit.StatusSampler || !unit.StatusSampler.OwnsRoster(unit.Group)
|| Assigned() > globalLimit || HuntingAssigned() > ERR_HQPursuitRules.Limit(globalLimit)
|| !m_Convoys.InterceptCurrent(m_MountedInterceptTask, order))
{ ReleaseMountedIntercept(now, "expiry, capacity or original ownership changed"); return; }
if (m_MountedInterceptDelivered) return;
ERR_HQBelief belief = m_Memory.GetById(order.BeliefId);
if (!ERR_HQMountedInterceptRules.Current(order, belief, now) || !m_MountedInterceptRoute)
{ ReleaseMountedIntercept(now, "delivered history changed before command arrival"); return; }
int delivered = m_MountedInterceptRoute.Advance(now, m_Journal);
if (delivered == 0) return;
if (delivered < 0)
{ ReleaseMountedIntercept(now, "radio interrupted; authored route retained"); return; }
ERR_ConvoyVehicle vehicle = m_MountedInterceptTask.Vehicles[order.VehicleIndex];
if (!MountedInterceptStatus(unit, vehicle, now) || ConvoyContact(unit, now)
|| !m_Convoys.ApplyIntercept(m_MountedInterceptTask, order, now))
{ ReleaseMountedIntercept(now, "command refused by original crew"); return; }
m_MountedInterceptRoute = null; m_MountedInterceptDelivered = true;
m_Journal.Write("MOUNTED INTERCEPT DELIVERED", "original armed escort ordered to the remembered road ahead", order.ReportId);
}
protected bool MountedInterceptTaskAvailable(ERR_ConvoyTask task)
{
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) return false;
// Service admission excludes the whole original convoy, including sibling
// holds still in transit. Do not spend a delivered revision on a busy task.
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || vehicle.InterceptToken > 0 || vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) return false;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && (unit.PendingConvoyOrder || unit.Downlink || unit.LogisticsReservation > 0)) return false;
}
return true;
}
protected bool HasMountedInterceptCandidate(float now)
{
// Avoid native road surveys until one original escort has usable delivered status.
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
if (unit.Group == vehicle.Group && MountedLogisticsEligible(unit) && !ConvoyContact(unit, now)
&& MountedInterceptStatus(unit, vehicle, now)) return true;
}
}
return false;
}
protected override void PlanMountedIntercept(float now)
{
if (!Replication.IsServer() || m_Stopping || !MissionActive() || m_MountedIntercept || m_MountedAttemptBeliefs.Count() >= 256) return;
int globalLimit = m_Watch.SquadLimit(m_Mode.Settings().Runtime("supportSquadLimit"));
if (Assigned() >= globalLimit || HuntingAssigned() >= ERR_HQPursuitRules.Limit(globalLimit)) return;
if (!HasMountedInterceptCandidate(now)) return;
array<vector> reserved = {}; int first = -1; int second = -1;
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
// At most two bounded native road queries per normal five-second HQ pass.
int queries;
for (int b = 0; b < m_Memory.Count() && queries < 2; b++)
{
ERR_HQBelief belief = m_Memory.At(b);
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
vector destination; queries++;
if (!ERR_HQMountedInterceptRules.Select(belief, now, reserved, destination)) continue;
ERR_HQUnit best;
ERR_ConvoyTask bestTask;
ERR_ConvoyVehicle bestVehicle;
ERR_HQRoute bestRoute;
float bestDistance = 1e20;
for (int t = 0; t < m_Convoys.Count(); t++)
{
ERR_ConvoyTask task = m_Convoys.At(t);
if (!task || task.Stopped || !task.Ready || !task.Receipt || task.Vehicles.Count() != 6) continue;
foreach (ERR_ConvoyVehicle vehicle : task.Vehicles)
{
if (!vehicle || (vehicle.Index != 0 && vehicle.Index != 3 && vehicle.Index != 5)
|| vehicle.Released || vehicle.Arrived || !vehicle.Registered || vehicle.InterceptToken > 0
|| vehicle.SupplyToken > 0 || vehicle.SupplyHeld || vehicle.Combat.Held) continue;
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != vehicle.Group || !MountedLogisticsEligible(unit) || ConvoyContact(unit, now)
|| !MountedInterceptStatus(unit, vehicle, now)) continue;
ERR_HQWatchEntry own = m_Watch.Find(unit.ForceSourceId);
float distance = vector.DistanceXZ(own.Position, destination);
if (distance > m_Mode.Settings().Runtime("responseRadiusMetres") || distance >= bestDistance) continue;
ERR_HQRoute candidateRoute = m_Network.Route(unit.Radio, true);
if (!candidateRoute) continue;
best = unit; bestTask = task; bestVehicle = vehicle; bestRoute = candidateRoute; bestDistance = distance;
}
}
}
if (!best) continue;
ERR_HQRoute route = bestRoute;
if (!route) continue;
ERR_ConvoyInterceptOrder order = new ERR_ConvoyInterceptOrder();
order.Token = 400001 + m_MountedAttemptBeliefs.Count(); order.Group = best.Group;
order.SourceId = bestTask.Receipt.SourceId; order.VehicleIndex = bestVehicle.Index;
order.Position = destination; order.BeliefId = belief.BeliefId; order.BeliefRevision = belief.BeliefRevision;
order.ReportId = belief.MessageId; order.ObservedAt = belief.ObservedAt;
order.IssuedAt = now; order.ExpiresAt = belief.ObservedAt + 120000;
if (!ERR_HQMountedInterceptRules.Current(order, belief, now)) continue;
// The attempt stays spent after radio interruption, physical refusal or loss.
m_MountedAttemptBeliefs.Insert(order.BeliefId); m_MountedAttemptRevisions.Insert(order.BeliefRevision);
best.LogisticsReservation = order.Token;
if (!m_Convoys.ReserveIntercept(bestTask, order, now))
{ if (best.LogisticsReservation == order.Token) best.LogisticsReservation = 0; return; }
m_MountedIntercept = order; m_MountedInterceptUnit = best; m_MountedInterceptTask = bestTask;
m_MountedInterceptRoute = route; m_MountedInterceptDelivered = false;
route.MessageId = m_Journal.Message();
route.Payload = string.Format("original escort %1 to road %2; delivered track %3 revision %4 report M%5", order.VehicleIndex, destination, order.BeliefId, order.BeliefRevision, order.ReportId);
m_Journal.Write("MOUNTED INTERCEPT QUEUED", route.Payload, route.MessageId);
return;
}
}
override void Stop()
{
float now;
if (GetGame() && GetGame().GetWorld()) now = GetGame().GetWorld().GetWorldTime();
ReleaseMountedIntercept(now, "mission stopped");
super.Stop();
}
}
@@ -0,0 +1,19 @@
check: /tmp/err-policy-1ywiksk6/check.cpp:253: int main(): Assertion `f.h.m_MountedIntercept&&f.h.m_MountedInterceptUnit==&far.unit&&!f.unit.LogisticsReservation&&far.unit.LogisticsReservation==400001&&f.service.reserves==1' failed.
Traceback (most recent call last):
File "/tmp/erri-motorized-intercept-review/test_hq_mounted_dispatch.py", line 24, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<81 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-1ywiksk6/check']' died with <Signals.SIGABRT: 6>.