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,607 @@
--- addons/core/Scripts/Game/ERR/00_Core/ERR_JournalText.c (before this task)
+++ addons/core/Scripts/Game/ERR/00_Core/ERR_JournalText.c
@@ -32,6 +32,7 @@
if (end < 0) return Short(message);
string result = message.Substring(0, end);
result.Replace("aggression-uaz-", "UAZ patrol ");
+ result.Replace("aggression-truck-", "Troop-truck patrol ");
return result;
}
static string YesNo(string value)
@@ -45,7 +46,7 @@
if (kind == "REGISTER") return "Group " + Field(message, "group") + " joined HQ: " + Field(message, "members") + " soldiers, faction " + Field(message, "faction") + ". Assignment: " + Field(message, "force") + ".";
if (kind == "SPAWN MEMBER") return "Group " + Field(message, "group") + ": soldier spawned at " + Field(message, "position") + ".";
if (kind == "CONTACT LOCAL") return "Group " + Field(message, "group") + " spotted a target at " + Field(message, "observed") + ". Local combat has started; the report still needs to reach HQ.";
- if (kind == "AGGRESSION") return "HQ has identified " + Field(message, "bodies") + " bodies. Aggression: " + Field(message, "aggression") + ". Total hunter UAZ allowance: " + Field(message, "allowance") + ".";
+ if (kind == "AGGRESSION") return "HQ has identified " + Field(message, "bodies") + " bodies. Aggression: " + Field(message, "aggression") + ". Total hunting vehicle allowance: " + Field(message, "allowance") + ".";
if (kind == "HUNTER SPAWN") return Subject(message) + " deployed with " + Field(message, "crew") + " crew at " + Field(message, "position") + ". Deployment source: " + Field(message, "base") + ".";
if (kind == "HUNTER READY") return Subject(message) + ": driver and gunner are seated; registered with HQ.";
if (kind == "HUNTER ORDER") return Subject(message) + " received its radio order. Drive to " + Field(message, "destination") + ".";
--- addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyBudget.c (before this task)
+++ addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyBudget.c
@@ -4,8 +4,8 @@
int Id;
string SourceId;
bool Consumed;
- int OutstandingBodies = 19;
- int OutstandingGroups = 6;
+ int OutstandingBodies = 10;
+ int OutstandingGroups = 5;
ref array<int> Records = {-1, -1, -1, -1, -1, -1};
}
@@ -20,32 +20,29 @@
void ERR_ConvoyPool(ERR_GameMode mode, int maxConvoys, bool supply = false)
{
m_Mode = mode; m_Supply = supply; m_Limit = Math.Clamp(maxConvoys, 0, 20);
+ // Keep shared headroom for the infantry reassigned to hunters.
if (supply) { m_BodyLimit = 60 + 25 * m_Limit; m_GroupLimit = 256; }
}
static int CrewCount(int vehicle)
{
if (vehicle == 1) return 1;
- if (vehicle == 2) return 9;
+ if (vehicle == 2) return 0;
if (vehicle == 4) return 3;
if (vehicle == 0 || vehicle == 3 || vehicle == 5) return 2;
return 0;
}
int PlannedCount(int index)
{
- if (m_Supply && index == 2) return 1;
- if (m_Supply && (index == 6 || index == 7)) return 7;
return CrewCount(index);
}
int BodyLimit() { return m_BodyLimit; }
int PlannedBodies()
{
- if (m_Supply) return 25;
- return 19;
+ return 10;
}
int PlannedGroups()
{
- if (m_Supply) return 8;
- return 6;
+ return 5;
}
protected bool Current()
{
@@ -89,7 +86,6 @@
foreach (ERR_ConvoyReceipt known : m_Receipts) if (known.SourceId == sourceId) return known;
if (m_Receipts.Count() >= m_Limit || CommittedBodies() + PlannedBodies() > m_BodyLimit || CommittedGroups() + PlannedGroups() > m_GroupLimit) return null;
ERR_ConvoyReceipt receipt = new ERR_ConvoyReceipt(); receipt.Id = m_Receipts.Count() + 1; receipt.SourceId = sourceId;
- if (m_Supply) { receipt.OutstandingBodies = 25; receipt.OutstandingGroups = 8; receipt.Records.Insert(-1); receipt.Records.Insert(-1); }
m_Receipts.Insert(receipt);
return receipt;
}
@@ -101,7 +97,7 @@
}
bool BindRecord(ERR_ConvoyReceipt receipt, int vehicle, ERR_ForceRecord record)
{
- if (!Current() || !Owns(receipt) || !receipt.Consumed || vehicle < 0 || vehicle >= PlannedGroups() || !record) return false;
+ if (!Current() || !Owns(receipt) || !receipt.Consumed || vehicle < 0 || vehicle >= receipt.Records.Count() || PlannedCount(vehicle) <= 0 || !record) return false;
if (receipt.Records[vehicle] >= 0) return receipt.Records[vehicle] == record.Id;
if (m_Mode.Forces().Find(record.Id) != record || record.Kind != ERR_ForceKind.CONVOY || !record.Group || !record.Physical || record.Eliminated) return false;
if (record.SourceId != receipt.SourceId + "-vehicle-" + vehicle.ToString() || record.InitialStrength != PlannedCount(vehicle) || record.Strength != record.InitialStrength) return false;
--- addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c (before this task)
+++ addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c
@@ -5,7 +5,8 @@
static ResourceName VehiclePrefab(int index)
{
if (index == 1) return "{3336BE330C4F355B}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_ammo.et";
- if (index == 2) return "{16C1F16C9B053801}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_transport.et";
+ // Slot 2 is retired: troop transports now belong to the hunter roster.
+ // Keep other canonical slot IDs stable for receipts, cargo and HQ bindings.
if (index == 4) return "{1DC01AAACFFA8B63}Prefabs/Vehicles/Tracked/BMP1/BMP1_Base.et";
if (index == 0 || index == 3 || index == 5) return "{0B4DEA8078B78A9B}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469_PKM.et";
return "";
@@ -21,7 +22,7 @@
static bool Available()
{
array<ResourceName> required = {GroupPrefab(), Soldier(), Rifle(), Magazine(), ERR_Resources.MoveWaypoint()};
- for (int i = 0; i < 6; i++) required.Insert(VehiclePrefab(i));
+ for (int i = 0; i < 6; i++) if (i != 2) required.Insert(VehiclePrefab(i));
foreach (ResourceName prefab : required)
{
Resource resource = Resource.Load(prefab);
@@ -54,7 +55,7 @@
{
if (!positions || !angles || positions.Count() != 6 || angles.Count() != 6) return false;
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
- for (int i = 0; i < positions.Count(); i++) if (!placement.Clear(positions[i], angles[i][0])) return false;
+ for (int i = 0; i < positions.Count(); i++) if (i != 2 && !placement.Clear(positions[i], angles[i][0])) return false;
return true;
}
static bool PartOf(IEntity entity, IEntity root)
@@ -371,6 +372,7 @@
}
protected bool FillCrew(ERR_ConvoyVehicle unit)
{
+ if (unit.Index == 2) return false;
array<BaseCompartmentSlot> all = {};
if (!ERR_ConvoyRules.CollectSeats(unit.Entity, all)) return false;
array<BaseCompartmentSlot> drivers = {}, turrets = {}, cargo = {};
@@ -382,12 +384,11 @@
else if (slot.GetType() == ECompartmentType.CARGO) cargo.Insert(slot);
}
int requiredTurrets = 1;
- if (unit.Index == 1 || unit.Index == 2) requiredTurrets = 0;
+ if (unit.Index == 1) requiredTurrets = 0;
if (unit.Index == 4) requiredTurrets = 2;
- if (drivers.Count() != 1 || turrets.Count() < requiredTurrets || (unit.Index == 2 && cargo.Count() < 8)) return false;
+ if (drivers.Count() != 1 || turrets.Count() < requiredTurrets) return false;
unit.Seats.Insert(drivers[0]);
for (int i = 0; i < requiredTurrets; i++) unit.Seats.Insert(turrets[i]);
- if (unit.Index == 2) for (int i = 0; i < 8; i++) unit.Seats.Insert(cargo[i]);
// Exact planned population is known before any character is created.
if (unit.Seats.Count() != ERR_ConvoyPool.CrewCount(unit.Index)) return false;
AIGroup nativeGroup = unit.Group;
@@ -410,6 +411,7 @@
for (int index = 0; index < 6; index++)
{
ERR_ConvoyVehicle unit = NewVehicle(); unit.Index = index; task.Vehicles.Insert(unit);
+ if (index == 2) { unit.Released = true; continue; }
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), positions[index]));
if (unit.Group) m_Mode.Own(unit.Group);
unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.VehiclePrefab(index), positions[index], angles[index]));
@@ -421,7 +423,7 @@
AICarMovementComponent movement = AICarMovementComponent.Cast(unit.Entity.FindComponent(AICarMovementComponent));
if (movement) movement.SetCruiseSpeed(35);
}
- ERR_HQ.Event("CONVOY CREATED", "six physical vehicles; finite crew created; native readiness and ledger admission pending");
+ ERR_HQ.Event("CONVOY CREATED", "five physical vehicles; troop transport reassigned to hunters; finite crew created; native readiness and ledger admission pending");
return task;
}
protected string ReadinessReason(ERR_ConvoyVehicle unit)
@@ -473,9 +475,10 @@
}
protected bool Admit(ERR_ConvoyTask task, float now)
{
- foreach (ERR_ConvoyVehicle observed : task.Vehicles) InspectReadiness(observed, now);
+ foreach (ERR_ConvoyVehicle observed : task.Vehicles) if (observed.Index != 2) InspectReadiness(observed, now);
foreach (ERR_ConvoyVehicle unit : task.Vehicles)
{
+ if (unit.Index == 2) continue;
if (unit.Registered) continue;
if (!unit.CrewValid(m_Mode)) return false;
array<AIAgent> agents = {}; unit.Group.GetAgents(agents);
--- addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c (before this task)
+++ addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c
@@ -1,5 +1,24 @@
+// One troop truck joins every pair of armed UAZs, within the existing finite
+// vehicle allowance. Trucks can flank/dismount but cannot provide turret cover.
+class ERR_HunterFleetRules
+{
+ static bool Transport(int ordinal) { return ordinal >= 0 && ordinal < 15 && ordinal % 3 == 2; }
+ static ResourceName VehiclePrefab(int ordinal)
+ {
+ if (Transport(ordinal)) return "{16C1F16C9B053801}Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_transport.et";
+ return ERR_ConvoyResources.VehiclePrefab(0);
+ }
+ static bool Seats(bool transport, int drivers, int turrets, int cargo, int total)
+ {
+ if (drivers != 1 || cargo < 1) return false;
+ if (transport) return turrets == 0 && cargo == 14 && total == 15;
+ return turrets > 0 && total <= 12;
+ }
+}
+
class ERR_HunterVehicle : ERR_ConvoyVehicle
{
+ bool TroopTransport;
bool RadioRequested;
ref ERR_EnemyEquipmentCallback RadioOperation;
string Source;
@@ -52,7 +71,7 @@
}
}
-// An escalation allowance of fifteen physical hunter UAZs, separate from supply
+// An escalation allowance of fifteen physical hunting vehicles (UAZs and troop trucks), separate from supply
// convoys. Losses remain physical; aggression never replenishes killed crews.
class ERR_HunterService : ERR_ConvoyService
{
@@ -129,7 +148,7 @@
foreach (ERR_HunterVehicle unit : m_Hunters) if (!unit.Released) count++;
return count;
}
- override string Status() { return string.Format("HQ UAZ patrols: %1 active; %2 deployed from a total allowance of %3 (maximum 15). Lost vehicles are not replaced.", ActiveHunters(), Deployed(), m_Target); }
+ override string Status() { return string.Format("HQ hunting patrols (UAZs + troop trucks): %1 active; %2 deployed from a total allowance of %3 (maximum 15). Lost vehicles are not replaced.", ActiveHunters(), Deployed(), m_Target); }
protected bool StartHunter(ERR_HQ hq, float now)
{
if (m_Hunters.Count() >= m_Target || m_Hunters.Count() >= 15 || m_Mode.Forces().Count() >= 250) return false;
@@ -169,7 +188,8 @@
ERR_HQ.Event("HUNTER WAIT", "deployment deferred: friendly road obstructed, occupied, budget full or players within 400m");
return false;
}
- // Hunter UAZs carry a full response team, unlike the two-person convoy escort.
+ // Each hunter fills its actual seats, including one medic; the transport
+ // carries a driver and fourteen infantry in the same HQ-controlled group.
override protected bool FillCrew(ERR_ConvoyVehicle unit)
{
ResourceName soldier = ERR_EnemyMedicalCatalog.Crew(m_Mode, false);
@@ -185,7 +205,8 @@
else if (seat.GetType() == ECompartmentType.TURRET) turrets.Insert(seat);
else if (seat.GetType() == ECompartmentType.CARGO) cargo.Insert(seat);
}
- if (drivers.Count() != 1 || turrets.IsEmpty() || cargo.IsEmpty() || all.Count() > 12) return false;
+ ERR_HunterVehicle hunter = ERR_HunterVehicle.Cast(unit);
+ if (!hunter || !ERR_HunterFleetRules.Seats(hunter.TroopTransport, drivers.Count(), turrets.Count(), cargo.Count(), all.Count())) return false;
unit.Seats.Insert(drivers[0]);
foreach (BaseCompartmentSlot turret : turrets) unit.Seats.Insert(turret);
int medicIndex = unit.Seats.Count();
@@ -215,10 +236,13 @@
return false;
}
ERR_HunterVehicle unit = new ERR_HunterVehicle();
+ unit.TroopTransport = ERR_HunterFleetRules.Transport(m_Hunters.Count());
unit.Source = "aggression-uaz-" + (m_Hunters.Count() + 1).ToString();
+ if (unit.TroopTransport) unit.Source = "aggression-truck-" + (m_Hunters.Count() + 1).ToString();
+ ResourceName vehiclePrefab = ERR_HunterFleetRules.VehiclePrefab(m_Hunters.Count());
unit.Deadline = now + 120000; m_Hunters.Insert(unit);
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), position));
- unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.VehiclePrefab(0), position, angles));
+ unit.Entity = Vehicle.Cast(ERR_Actors.Spawn(vehiclePrefab, position, angles));
if (unit.Group) m_Mode.Own(unit.Group);
if (unit.Entity) m_Mode.Own(unit.Entity);
if (!unit.Group || !unit.Entity || !ERR_ConvoyRules.EmptyCrewTemplate(unit.Group) || !FillCrew(unit))
@@ -228,7 +252,11 @@
FactionAffiliationComponent affiliation = FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent));
if (affiliation) affiliation.SetAffiliatedFaction(m_Mode.EnemyFaction());
AICarMovementComponent movement = AICarMovementComponent.Cast(unit.Entity.FindComponent(AICarMovementComponent));
- if (movement) movement.SetCruiseSpeed(45);
+ if (movement)
+ {
+ if (unit.TroopTransport) movement.SetCruiseSpeed(35);
+ else movement.SetCruiseSpeed(45);
+ }
ERR_HQ.Event("HUNTER SPAWN", string.Format("%1 identified bodies=%2 aggression=%3/30 allowance=%4/15 base=%5 position=%6 crew=%7", unit.Source, hq.Watch().IdentifiedBodies(), hq.Watch().Aggression(), m_Target, source, position, unit.Members.Count()));
return true;
}
--- addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyService.c (before this task)
+++ addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyService.c
@@ -15,37 +15,7 @@
protected override bool FillCrew(ERR_ConvoyVehicle unit)
{
if (m_Creating) m_Creating.Starts.Insert(unit.Entity.GetOrigin());
- if (unit.Index != 2) return super.FillCrew(unit);
- array<BaseCompartmentSlot> all = {}, drivers = {}, cargo = {};
- if (!m_Creating || !ERR_ConvoyRules.CollectSeats(unit.Entity, all)) return false;
- foreach (BaseCompartmentSlot seat : all)
- {
- if (!seat || seat.IsOccupied() || !seat.IsCompartmentAccessible()) continue;
- if (seat.GetType() == ECompartmentType.PILOT) drivers.Insert(seat);
- if (seat.GetType() == ECompartmentType.CARGO) cargo.Insert(seat);
- }
- if (drivers.Count() != 1 || cargo.Count() != 14) return false;
- AIGroup nativeGroup = unit.Group;
- IEntity driver = drivers[0].SpawnCharacterInCompartment(ERR_ConvoyResources.Soldier(), nativeGroup, ERR_ConvoyResources.GroupPrefab());
- if (driver) { m_Mode.Own(driver); unit.Members.Insert(driver); unit.Seats.Insert(drivers[0]); unit.Driver = driver; }
- if (!driver || nativeGroup != unit.Group) return false;
- for (int index = 0; index < 2; index++)
- {
- ERR_SupplyTeam team = new ERR_SupplyTeam(); m_Creating.Teams.Insert(team);
- team.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), unit.Entity.GetOrigin()));
- if (team.Group) m_Mode.Own(team.Group);
- if (!ERR_ConvoyRules.EmptyCrewTemplate(team.Group)) return false;
- AIFormationComponent formation = AIFormationComponent.Cast(team.Group.FindComponent(AIFormationComponent));
- if (formation) formation.SetFormation("Line");
- nativeGroup = team.Group;
- for (int slot = index; slot < 14; slot += 2)
- {
- IEntity actor = cargo[slot].SpawnCharacterInCompartment(ERR_ConvoyResources.Soldier(), nativeGroup, ERR_ConvoyResources.GroupPrefab());
- if (actor) { m_Mode.Own(actor); team.Members.Insert(actor); team.Seats.Insert(cargo[slot]); }
- if (!actor || nativeGroup != team.Group) return false;
- }
- }
- return true;
+ return super.FillCrew(unit);
}
protected void Wake(SCR_AIGroup group)
{
@@ -57,39 +27,16 @@
protected override bool Admit(ERR_ConvoyTask task, float now)
{
ERR_SupplyConvoyTask supply = ERR_SupplyConvoyTask.Cast(task);
- if (!supply || supply.Teams.Count() != 2) return false;
+ if (!supply || !supply.Teams.IsEmpty()) return false;
// Distant native agents must wake before compartment membership can settle.
foreach (ERR_ConvoyVehicle vehicle : supply.Vehicles)
{
+ if (vehicle.Released) continue;
Wake(vehicle.Group);
foreach (IEntity member : vehicle.Members) ERR_ConvoyMountedCrew.Hold(member, vehicle.Entity);
}
foreach (ERR_SupplyTeam infantry : supply.Teams) Wake(infantry.Group);
if (!super.Admit(supply, now)) return false;
- SCR_AIVehicleUsageComponent truck = SCR_AIVehicleUsageComponent.Cast(supply.Vehicles[2].Entity.FindComponent(SCR_AIVehicleUsageComponent));
- for (int index = 0; index < supply.Teams.Count(); index++)
- {
- ERR_SupplyTeam team = supply.Teams[index];
- if (team.Registered) continue;
- if (!team.Group || team.Group.GetFaction() != m_Mode.EnemyFaction() || team.Members.Count() != 7) return false;
- array<AIAgent> agents = {}; team.Group.GetAgents(agents);
- if (agents.Count() != 7) return false;
- foreach (AIAgent agent : agents)
- if (!agent || !team.Members.Contains(agent.GetControlledEntity()) || !ERR_Actors.CanFight(agent.GetControlledEntity()) || ERR_Actors.Player(agent.GetControlledEntity())) return false;
- for (int slot = 0; slot < 7; slot++)
- if (team.Seats[slot].GetOccupant() != team.Members[slot] || ERR_Actors.Group(team.Members[slot]) != team.Group) return false;
- SCR_AIGroupUtilityComponent utility = team.Group.GetGroupUtilityComponent();
- if (!utility || !truck) return false;
- utility.AddUsableVehicle(truck);
- ERR_ForceRecord record = m_Mode.Forces().Register(supply.Receipt.SourceId + "-vehicle-" + (index + 6).ToString(), ERR_ForceKind.CONVOY, team.Group.GetOrigin(), 7, null, team.Group, now);
- if (!record || !m_Pool.BindRecord(supply.Receipt, index + 6, record)) { supply.Stop(m_Mode, "infantry ledger admission failed; no replacement"); return false; }
- record.Pinned = true; team.Registered = true;
- m_Mode.RegisterGroup(team.Group);
- ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
- if (hq) hq.ReserveGroup(team.Group);
- team.Group.SetPermanentLOD(0); team.Group.ActivateAI();
- foreach (AIAgent active : agents) { active.SetPermanentLOD(0); active.ActivateAI(); }
- }
supply.Column = new ERR_SupplyConvoyColumn(m_Mode, supply);
supply.Tactics = new ERR_SupplyConvoyTactics(m_Mode, supply);
supply.TravelDeadline = now + 600000;
--- addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyTactics.c (before this task)
+++ addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyTactics.c
@@ -470,12 +470,14 @@
intercept.ClearIntercept(intercept.InterceptOrder);
if (intercept.InterceptToken != 0) return;
}
- Clear(); m_State = 1; m_Contact = contact; m_TruckWithdrawn = false; m_OrdersIssued = false;
+ Clear(); m_State = 1; m_Contact = contact; m_TruckWithdrawn = m_Task.Teams.IsEmpty(); m_OrdersIssued = false;
m_NextSearch = now + 45000;
foreach (ERR_SupplyTeam team : m_Task.Teams) { team.SearchPlanned = false; team.SearchStep = 0; }
if (m_Task.Column) m_Task.Column.Reset(now);
m_Task.Waiting = false;
- vector origin = m_Task.Vehicles[2].Entity.GetOrigin();
+ vector origin = contact;
+ foreach (ERR_ConvoyVehicle stagingVehicle : m_Task.Vehicles)
+ if (!stagingVehicle.Released && stagingVehicle.Entity) { origin = stagingVehicle.Entity.GetOrigin(); break; }
foreach (ERR_ConvoyVehicle vehicle : m_Task.Vehicles)
{
vehicle.ClearOrder();
@@ -486,7 +488,7 @@
m_Forward.Normalize(); m_Left = Vector(-m_Forward[2], 0, m_Forward[0]);
Orders(true, now);
m_DismountAttempts = 0; m_NextDismount = now + 10000;
- ERR_HQ.Event("CONVOY COMBAT", m_Task.Receipt.SourceId + ": infantry flank left/right; escorts cover; troop truck holds for dismount");
+ ERR_HQ.Event("CONVOY COMBAT", m_Task.Receipt.SourceId + ": armed escorts take covering positions; ammo truck withdraws");
}
protected void Finish(float now)
{
--- addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c (before this task)
+++ addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c
@@ -169,6 +169,7 @@
m_NextMovementDiagnostic = now + 30000;
for (int i = 0; i < m_Task.Vehicles.Count() && i < 6; i++)
{
+ if (i == 2) continue;
ERR_ConvoyVehicle unit = m_Task.Vehicles[i];
if (!unit || !unit.Entity || !unit.Group) { PrintFormat("ERR CONVOY MOTION: vehicle=%1 missing original identity", i); continue; }
PrintFormat("ERR CONVOY MOTION: vehicle=%1 held=%2 command=%3 report=%4 observed=%5",
@@ -236,12 +237,13 @@
}
protected bool Population(ERR_HQ hq)
{
- if (!Check(m_Task.Vehicles.Count() == 6, "six original vehicles remain tracked")) return false;
+ if (!Check(m_Task.Vehicles.Count() == 6, "canonical slots remain tracked with retired transport")) return false;
array<IEntity> vehicles = {}, crew = {};
array<SCR_AIGroup> groups = {};
array<BaseCompartmentSlot> slots = {};
for (int i = 0; i < 6; i++)
{
+ if (i == 2) continue;
ERR_ConvoyVehicle vehicle = m_Task.Vehicles[i];
if (!Check(vehicle.Entity && vehicle.Entity == m_OriginalVehicles[i] && ERR_Actors.Alive(vehicle.Entity)
&& !vehicles.Contains(vehicle.Entity) && vehicle.Group && !groups.Contains(vehicle.Group),
@@ -279,9 +281,9 @@
if (!Check(vehicle.Driver == vehicle.Members[0]
&& vehicle.Seats[0].GetType() == ECompartmentType.PILOT, "actual driver seat " + i.ToString())) return false;
}
- return Check(crew.Count() == 19 && hq.ConvoyPool().CommittedBodies() <= 60
+ return Check(crew.Count() == 10 && hq.ConvoyPool().CommittedBodies() <= 60
&& m_Task.Receipt.OutstandingBodies == 0 && m_Task.Receipt.OutstandingGroups == 0,
- "nineteen unique crew admitted once within shared sixty-body cap");
+ "ten unique crew admitted once within shared sixty-body cap");
}
protected int Cargo(float now)
{
@@ -353,9 +355,10 @@
if (!Check(hq.StartConvoy(), "canonical HQ dispatch accepted once")) return -1;
m_Task = hq.ConvoyTask();
if (!Check(m_Task && m_Task.Receipt && m_Task.Receipt.Consumed && m_Task.Vehicles.Count() == 6,
- "canonical receipt owns six attempted physical vehicles")) return -1;
+ "canonical receipt owns five attempted physical vehicles and retired slot")) return -1;
foreach (ERR_ConvoyVehicle vehicle : m_Task.Vehicles)
{
+ if (vehicle.Index == 2) { m_OriginalVehicles.Insert(null); m_Origins.Insert(vector.Zero); continue; }
if (!Check(vehicle.Entity != null, "original physical vehicle created")) return -1;
m_OriginalVehicles.Insert(vehicle.Entity); m_Origins.Insert(vehicle.Entity.GetOrigin());
}
@@ -367,7 +370,7 @@
}
bool completed = m_Task && m_Task.Vehicles.Count() == 6;
if (m_Task) foreach (ERR_ConvoyVehicle candidate : m_Task.Vehicles)
- if (!candidate.Arrived) completed = false;
+ if (candidate.Index != 2 && !candidate.Arrived) completed = false;
if (!m_Task || (m_Task.Stopped && !completed))
{
string reason = "missing task";
@@ -387,8 +390,8 @@
if (!Check(!empty && ERR_ForceRules.Finite(fraction) && fraction >= 0 && fraction < 1
&& occupied.Diagnostic().IndexOf("native filled volume collision") >= 0,
"native filled volume detects the actual lead vehicle as a positive blocker")) return -1;
- if (!Check(m_Mode.Forces().Count() == m_InitialLedger + 6,
- "exactly six new Core records after native readiness")) return -1;
+ if (!Check(m_Mode.Forces().Count() == m_InitialLedger + 5,
+ "exactly five new Core records after native readiness")) return -1;
m_Step = 2;
}
if (m_Step == 2)
@@ -400,6 +403,7 @@
float minimum = 1000000;
for (int i = 0; i < 6; i++)
{
+ if (i == 2) continue;
ERR_ConvoyVehicle vehicle = m_Task.Vehicles[i];
if (!vehicle || !vehicle.Entity || (vehicle.Released && !vehicle.Arrived))
{ LossDiagnostic(vehicle, i, now); Check(false, "vehicle released before road movement proof"); return -1; }
@@ -418,9 +422,9 @@
"driving uses finite road-polyline route")) return -1;
if (!Population(hq)) return -1;
if (!Check(!hq.StartConvoy() && hq.Convoys().Count() == 1 && hq.ConvoyTask() == m_Task
- && m_Mode.Forces().Count() == m_InitialLedger + 6 && m_Task.Cargo.Count() == 56,
+ && m_Mode.Forces().Count() == m_InitialLedger + 5 && m_Task.Cargo.Count() == 56,
"later repeated dispatch preserves exact task, force and cargo counts")) return -1;
- Check(true, "all six native vehicles drove at least 20m and leader at least 100m without fixture movement");
+ Check(true, "all five native vehicles drove at least 20m and leader at least 100m without fixture movement");
if (ERR_NativeMountedInterceptProbe.Enabled())
{
m_Intercept = new ERR_NativeMountedInterceptProbe(m_Mode, hq, m_Task, now); m_Step = 5; return 0;
--- addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSupplyConvoyProbe.c (before this task)
+++ addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSupplyConvoyProbe.c
@@ -1,6 +1,6 @@
#ifdef WORKBENCH
// Explicit opt-in fixture. Startup, seating, cargo and driving remain production-owned.
-// The final phase injects one clearly labelled contact to exercise native dismount/reboard.
+// The final phase injects one clearly labelled contact to exercise mounted combat/resume.
class ERR_NativeSupplyConvoyProbe : ERR_NativeConvoyProbe
{
protected ref array<vector> m_StartPositions = {};
@@ -10,27 +10,28 @@
protected float m_PhaseBegan;
protected int m_Phase;
protected ref ERR_SupplyConvoyTask m_CombatTask;
- protected bool m_SawDismount;
- protected bool m_SawBoarding;
+ protected bool m_SawCombat;
protected int m_TestLegs;
protected vector m_RouteResumePosition;
protected int ObserveMotion(ERR_HQ hq)
{
- if (m_StartPositions.Count() != 18) return 0;
+ if (m_StartPositions.Count() != 15) return 0;
int cursor, moved;
for (int index = 0; index < 3; index++)
{
foreach (ERR_ConvoyVehicle vehicle : hq.Convoys().At(index).Vehicles)
{
+ if (vehicle.Index == 2) continue;
+ if (!vehicle.Entity) return -1;
if (vector.DistanceXZ(vehicle.Entity.GetOrigin(), m_StartPositions[cursor]) >= 20) m_Moved[cursor] = true;
if (m_Moved[cursor]) moved++;
cursor++;
}
}
- if (moved == 18 && !m_AllMoved)
+ if (moved == 15 && !m_AllMoved)
{
m_AllMoved = true;
- Print("ERR SUPPLY PASS: all eighteen original vehicles drove at least twenty metres from their recorded spawn positions through native AI");
+ Print("ERR SUPPLY PASS: all fifteen original vehicles drove at least twenty metres from their recorded spawn positions through native AI");
}
return moved;
}
@@ -42,7 +43,7 @@
int moved = ObserveMotion(hq);
if (now >= m_NextLog)
{
- PrintFormat("ERR SUPPLY MOTION: %1/18 original vehicles moved at least20m", moved);
+ PrintFormat("ERR SUPPLY MOTION: %1/15 original vehicles moved at least20m", moved);
PrintFormat("ERR SUPPLY STATE: phase=%1 elapsed=%2 %3", m_Phase, now - m_Began, hq.ConvoyStatus());
for (int logIndex = 0; logIndex < hq.Convoys().Count(); logIndex++)
{
@@ -55,11 +56,12 @@
for (int car = 0; car < m_CombatTask.Vehicles.Count(); car++)
{
ERR_ConvoyVehicle diagnosticCar = m_CombatTask.Vehicles[car];
+ if (!diagnosticCar.Entity) continue;
vector target; if (diagnosticCar.Waypoint) target = diagnosticCar.Waypoint.GetOrigin();
PrintFormat("ERR SUPPLY COLUMN: car=%1 released=%2 arrived=%3 position=%4 target=%5", car + 1, diagnosticCar.Released, diagnosticCar.Arrived, diagnosticCar.Entity.GetOrigin(), target);
if (diagnosticCar.Waypoint) PrintFormat("ERR SUPPLY WAYPOINT: car=%1 distance=%2 nativeCurrent=%3 nativeComplete=%4 routeIndex=%5", car + 1, vector.DistanceXZ(diagnosticCar.Entity.GetOrigin(), target), diagnosticCar.Group.GetCurrentWaypoint() == diagnosticCar.Waypoint, diagnosticCar.Waypoint.IsWithinCompletionRadius(diagnosticCar.Group), diagnosticCar.RouteIndex);
}
- ERR_ConvoyVehicle truck = m_CombatTask.Vehicles[2];
+ ERR_ConvoyVehicle truck = m_CombatTask.Vehicles[1];
SCR_VehicleDamageManagerComponent health = SCR_VehicleDamageManagerComponent.Cast(truck.Entity.FindComponent(SCR_VehicleDamageManagerComponent));
PrintFormat("ERR SUPPLY TRUCK: released=%1 driverAlive=%2 driverGroup=%3 driverAboard=%4 health=%5 retained=%6 position=%7", truck.Released, ERR_Actors.CanFight(truck.Driver), ERR_Actors.Group(truck.Driver) == truck.Group, CompartmentAccessComponent.GetVehicleIn(truck.Driver) == truck.Entity, health.GetHealthScaled(), ERR_ConvoyMountedCrew.Retain(truck.Driver, truck.Entity), truck.Entity.GetOrigin());
SCR_AIGroupUtilityComponent driverGroup = truck.Group.GetGroupUtilityComponent();
@@ -98,10 +100,8 @@
if (!task || task.Stopped) return -1;
if (!task.Ready || !task.MagazinesIssued) return 0;
foreach (ERR_ConvoyCargoCallback pending : task.Cargo) if (!pending.Finished) return 0;
- if (task.Vehicles.Count() != 6 || task.Teams.Count() != 2 || task.Receipt.OutstandingBodies != 0 || task.Receipt.OutstandingGroups != 0) return -1;
- foreach (ERR_SupplyTeam team : task.Teams)
- if (!team.Registered || team.Group == task.Vehicles[2].Group || team.Members.Count() != 7) return -1;
- if (task.Teams[0].Group == task.Teams[1].Group) return -1;
+ if (task.Vehicles.Count() != 6 || !task.Teams.IsEmpty() || task.Receipt.OutstandingBodies != 0 || task.Receipt.OutstandingGroups != 0) return -1;
+ if (!task.Vehicles[2].Released || task.Vehicles[2].Entity || task.Vehicles[2].Group) return -1;
int successful;
foreach (ERR_ConvoyCargoCallback load : task.Cargo) if (load.Succeeded) successful++;
if (successful < 14) { Print("ERR SUPPLY FAIL: weapon and ammunition storage operations missing"); return -1; }
@@ -124,37 +124,31 @@
for (int convoy = 0; convoy < 3; convoy++)
{
ERR_SupplyConvoyTask original = ERR_SupplyConvoyTask.Cast(hq.Convoys().At(convoy));
- if (original.Starts.Count() != 6) return -1;
+ if (original.Starts.Count() != 5) return -1;
foreach (vector start : original.Starts) { m_StartPositions.Insert(start); m_Moved.Insert(false); }
}
m_CombatTask = ERR_SupplyConvoyTask.Cast(hq.Convoys().At(0));
- Print("ERR SUPPLY PASS: production auto-started three six-vehicle convoys, exact ledger admission, two separate seated teams each, finite native cargo");
+ Print("ERR SUPPLY PASS: production auto-started three five-vehicle convoys, exact ledger admission, no troop transport, finite native cargo");
m_Phase = 1; m_PhaseBegan = now;
}
if (m_Phase == 1)
{
if (!m_AllMoved) return 0;
m_CombatTask = ERR_SupplyConvoyTask.Cast(hq.Convoys().At(0));
- vector contact = m_CombatTask.Vehicles[2].Entity.GetOrigin() + "0 0 200";
+ vector contact = m_CombatTask.Vehicles[1].Entity.GetOrigin() + "0 0 200";
m_CombatTask.Tactics.Report(contact, now, now);
- Print("ERR SUPPLY FIXTURE: injected one contact for native dismount/reboard validation; this phase does not prove perception");
+ Print("ERR SUPPLY FIXTURE: injected one contact for mounted combat/resume validation; this phase does not prove perception");
m_Phase = 2; m_PhaseBegan = now;
}
if (m_Phase == 2)
{
if (m_CombatTask.Stopped) return -1;
- bool dismounted = true;
- foreach (ERR_SupplyTeam foot : m_CombatTask.Teams)
- if (!foot.Aboard(m_CombatTask.Vehicles[2].Entity, false)) dismounted = false;
- if (dismounted && !m_SawDismount) { m_SawDismount = true; Print("ERR SUPPLY PASS: both original infantry teams physically dismounted"); }
- if (m_CombatTask.Tactics.State() == 2) m_SawBoarding = true;
- if (now - m_PhaseBegan > 60000 && m_CombatTask.Tactics.State() == 0)
+ if (m_CombatTask.Tactics.State() == 1) m_SawCombat = true;
+ if (now - m_PhaseBegan > 180000 && m_CombatTask.Tactics.State() == 0)
{
- if (!m_SawDismount || !m_SawBoarding) return -1;
- foreach (ERR_SupplyTeam boarded : m_CombatTask.Teams)
- if (boarded.Released || !boarded.Aboard(m_CombatTask.Vehicles[2].Entity, true)) return -1;
- Print("ERR SUPPLY PASS: both original teams reboarded and production travel resumed after quiet interval");
- m_TestLegs = m_CombatTask.Legs; m_RouteResumePosition = m_CombatTask.Vehicles[2].Entity.GetOrigin();
+ if (!m_SawCombat) return -1;
+ Print("ERR SUPPLY PASS: escorts covered and production travel resumed after quiet interval without absent infantry blocking readiness");
+ m_TestLegs = m_CombatTask.Legs; m_RouteResumePosition = m_CombatTask.Vehicles[1].Entity.GetOrigin();
Print("ERR SUPPLY FIXTURE: waiting for physical column arrival, production pause and next base leg; no forced timeout arrival");
m_Phase = 3;
}
@@ -164,15 +158,15 @@
}
protected int Departure()
{
- if (m_CombatTask.Stopped || m_CombatTask.Vehicles[2].Released || m_CombatTask.Bases.Count() < 2) return -1;
+ if (m_CombatTask.Stopped || m_CombatTask.Vehicles[1].Released || m_CombatTask.Bases.Count() < 2) return -1;
if (m_Phase == 3 && m_CombatTask.Legs > m_TestLegs)
{
- m_RouteResumePosition = m_CombatTask.Vehicles[2].Entity.GetOrigin();
+ m_RouteResumePosition = m_CombatTask.Vehicles[1].Entity.GetOrigin();
m_Phase = 4;
Print("ERR SUPPLY FIXTURE: next leg started; record original truck position now, excluding all earlier regroup movement");
return 0;
}
- if (m_Phase == 4 && vector.DistanceXZ(m_CombatTask.Vehicles[2].Entity.GetOrigin(), m_RouteResumePosition) >= 20)
+ if (m_Phase == 4 && vector.DistanceXZ(m_CombatTask.Vehicles[1].Entity.GetOrigin(), m_RouteResumePosition) >= 20)
{
Print("ERR SUPPLY PASS: original truck drives twenty metres AFTER departure on the new native base leg; no replacement convoy");
m_Phase = 5;
@@ -0,0 +1,61 @@
modded class ERR_HunterService
{
protected ref ERR_HunterVehicle m_ERRProbeTruck;
void ERR_CheckTruckCrew(Vehicle vehicle)
{
ERR_HunterVehicle unit = new ERR_HunterVehicle(); unit.Entity = vehicle; unit.TroopTransport = true;
unit.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), vehicle.GetOrigin()));
bool filled = unit.Group && FillCrew(unit);
m_ERRProbeTruck = unit;
PrintFormat("ERR HUNTER FLEET NATIVE CREW QUEUED: filled=%1 members=%2 seats=%3", filled, unit.Members.Count(), unit.Seats.Count());
GetGame().GetCallqueue().CallLater(ERR_FinishTruckProbe, 2000, false);
}
void ERR_FinishTruckProbe()
{
ERR_HunterVehicle unit = m_ERRProbeTruck;
int occupied;
for (int i = 0; i < unit.Seats.Count() && i < unit.Members.Count(); i++)
if (unit.Seats[i].GetOccupant() == unit.Members[i]) occupied++;
PrintFormat("ERR HUNTER FLEET NATIVE CREW: filled=%1 members=%2 seats=%3 occupied=%4", unit.Group.GetAgentsCount() == 15, unit.Members.Count(), unit.Seats.Count(), occupied);
foreach (IEntity actor : unit.Members) if (actor) SCR_EntityHelper.DeleteEntityAndChildren(actor);
if (unit.Group) SCR_EntityHelper.DeleteEntityAndChildren(unit.Group);
if (unit.Entity) SCR_EntityHelper.DeleteEntityAndChildren(unit.Entity);
m_ERRProbeTruck = null;
}
}
modded class ArmaReforgerScripted
{
protected ref ERR_HunterService m_ERRTruckService;
protected bool m_ERRTruckSeatsChecked;
protected float m_ERRTruckProbeAge;
override void OnUpdate(BaseWorld world, float timeslice)
{
super.OnUpdate(world, timeslice);
if (m_ERRTruckSeatsChecked || !m_ERRPresentedTerrainHeader) return;
m_ERRTruckProbeAge += timeslice;
if (m_ERRTruckProbeAge < 4 || !ERR_GameMode.Get() || !ERR_GameMode.Get().EnemyFaction()) return;
m_ERRTruckSeatsChecked = true;
for (int ordinal = 0; ordinal < 3; ordinal++)
{
IEntity vehicle = ERR_Actors.Spawn(ERR_HunterFleetRules.VehiclePrefab(ordinal), ERR_Actors.Ground(Vector(3000 + ordinal * 30, 0, 3000)));
array<BaseCompartmentSlot> seats = {};
bool collected = vehicle && ERR_ConvoyRules.CollectSeats(vehicle, seats);
int drivers, turrets, cargo;
foreach (BaseCompartmentSlot seat : seats)
{
if (seat.GetType() == ECompartmentType.PILOT) drivers++;
if (seat.GetType() == ECompartmentType.TURRET) turrets++;
if (seat.GetType() == ECompartmentType.CARGO) cargo++;
}
bool valid = collected && ERR_HunterFleetRules.Seats(ERR_HunterFleetRules.Transport(ordinal), drivers, turrets, cargo, seats.Count());
PrintFormat("ERR HUNTER FLEET NATIVE: slot=%1 transport=%2 driver=%3 turrets=%4 cargo=%5 total=%6 valid=%7", ordinal, ERR_HunterFleetRules.Transport(ordinal), drivers, turrets, cargo, seats.Count(), valid);
if (ordinal == 2 && valid)
{
m_ERRTruckService = new ERR_HunterService(ERR_GameMode.Get(), null);
m_ERRTruckService.ERR_CheckTruckCrew(Vehicle.Cast(vehicle));
continue;
}
if (vehicle) SCR_EntityHelper.DeleteEntityAndChildren(vehicle);
}
}
}
@@ -0,0 +1,8 @@
14:59:11.847 SCRIPT : Module: Game; loaded 6713x files; 13462x classes; used 33306K of static memory; defines: "PLATFORM_LINUX,ENF_ENABLE_VHC_OVERRIDE,ENF_ENABLE_RPLCOMPONENT_IN_GAMECODE,ENABLE_BASE_DESTRUCTION,BUDGET_OPTIMIZATION_CHECKS,DEBUG,RESPAWN_TIMER_COMPONENT_DEBUG,DEBUG_CONFIGURABLE_DIALOGS,DISABLE_WEAPON_SWITCHING,SCRIPTED_AIM_MODIFIER_DEBUG,WCS_DEFINES_ARMAMENTS,WCS_DEFINES_SPACECORE"; CRC32: 28519cf6
14:59:25.894 SCRIPT : ERR TERRAIN LOOKUP WARM: title=Co10 Escape Reintroduced - Nogova; world=$NogovaReforger:Worlds/Nogova/Nogova_Test.ent
14:59:27.918 SCRIPT : ERR HUNTER FLEET NATIVE: slot=0 transport=0 driver=1 turrets=1 cargo=1 total=3 valid=1
14:59:27.923 SCRIPT : ERR HUNTER FLEET NATIVE: slot=1 transport=0 driver=1 turrets=1 cargo=1 total=3 valid=1
14:59:27.940 SCRIPT : ERR HUNTER FLEET NATIVE: slot=2 transport=1 driver=1 turrets=0 cargo=14 total=15 valid=1
14:59:28.102 SCRIPT : ERR HUNTER FLEET NATIVE CREW QUEUED: filled=1 members=15 seats=15
14:59:29.961 SCRIPT : ERR HUNTER FLEET NATIVE CREW: filled=1 members=15 seats=15 occupied=15
14:59:45.960 SCRIPT : ERR TERRAIN LOOKUP COMPLETE: unchanged=1 correct=1 resource=1 frames=596 elapsed=20.0201
@@ -0,0 +1,71 @@
{
"date": "2026-10-05",
"composition": {
"supply_vehicles": 5,
"supply_crew": 10,
"hunter_cycle": [
"UAZ PKM",
"UAZ PKM",
"Ural transport"
],
"hunter_vehicle_limit_unchanged": 15,
"transport_crew": 15,
"transport_medics": 1
},
"tests_passed": [
"test_hunter_transport",
"test_convoy_budget",
"test_convoy_dispatch",
"test_supply_budget_admission",
"test_supply_convoys",
"test_convoy_column",
"test_native_convoy_probe",
"test_hunter_profiles",
"test_hunter_lifecycle",
"test_hunter_fallback",
"test_hunter_tactics",
"test_hunter_coordination",
"test_hunter_driver_handoff",
"test_hunter_close_contact",
"test_hunter_capture",
"test_convoy_mine_stock",
"test_mounted_initial_stock"
],
"native_result": {
"reason": "complete",
"exit": -15
},
"native_script_errors": 0,
"native_crew_seated": true,
"limitations": [
"No new live multiplayer combat playtest",
"No Workshop publication or live-server restart"
],
"sha256": {
"addons/core/Scripts/Game/ERR/00_Core/ERR_JournalText.c": "1ad00a77cb0be741c7b6ef5dad2d8a8c1ff1fcca3b093df2e132ead0b69f74e1",
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyBudget.c": "fc7e254056c1da4aadd26d4f56d51ee375d5d3331f5f7e5183581108c7474a10",
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "0aff5818d0b6e3f3367a85892ce092c8344ae13503ae3ea0e0597d9f11f6f91e",
"addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c": "9711f53ece715047d4ccf9b4ff06533796f8572e685c15cdc3bb09e3e0dc98b6",
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyService.c": "5206e2819940c322bf6a978026d13082e21b817ed50ad00e05f264e4d9075e8d",
"addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyTactics.c": "7121bc73fe0c5f9f38250ae346490f72cf26df49128a41a50ff9656752776fea",
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeConvoyProbe.c": "87df652b339df4fe4239617752ac5fd9f47aa4396ba94742a0af39a32b54e784",
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSupplyConvoyProbe.c": "89c2696071912500840f4bb91d754f695bf674335b51a7ee1d03727084fd4869",
"tests/test_hunter_transport.py": "41c5ffcfb11bfd9ca13353a29832d301be6d7641e449ca8c254d32ce6597eb24",
"tests/test_convoy_budget.py": "13a0aca210c1039230ea4ab9c24344341ee5c3f5bb0cf340b68f4e01d15aac1e",
"tests/test_convoy_dispatch.py": "d3cba23fa14ece5b8be38d78977eeb6a48e1bbb6bc5e28d99eb58964ffbbfbc1",
"tests/test_supply_budget_admission.py": "61519e61d2b675bf071d595f0d1d3269f88a71101520b9eafa9fe17157d1dbad",
"tests/test_supply_convoys.py": "61465fcf0b9055d07b6b5af553eb240a0349148ad6868c33940c7684f055cdad",
"tests/test_convoy_column.py": "6651f32651d9c27d7c9a51237035910ffa1e60398f44616bbc9535d699021e77",
"tests/test_native_convoy_probe.py": "3d98fed9c597b38e27264108b2a7e5af2a8a8e5b529430d0d95c8a7dfc2eb09f",
"tests/test_hunter_profiles.py": "bdc7898666a7e1078ece35b5dc9bf903d2218843abb24addcea9353fe6b3f7f3",
"tests/test_hunter_lifecycle.py": "4cf06957209aeb08ecacc55fc9c1bb2caffda210a791c86e39a7266b08cbcf01",
"tests/test_hunter_fallback.py": "751516ae8a35aee25f7a21244b7f515baa62567795c36ed6e763db2bd1d2975e",
"tests/test_hunter_tactics.py": "5fd6a3f249bb2dd8a9beb6888a60ac8eb9205f06a9f775119c002651cc17ecff",
"tests/test_hunter_coordination.py": "720ea8413e3957fff485be4f72f7305a2fa7491e5d56f2298a2a4387776a4419",
"tests/test_hunter_driver_handoff.py": "08cc3c56dbe465ae58cf3c2a98103d749f8a59631011313f27cac0999d847f99",
"tests/test_hunter_close_contact.py": "9296bf490c88c3909b3a82a8a5458944820ad47d27e452c4d8de8b7cb857e289",
"tests/test_hunter_capture.py": "cf1b9f8160ff5d776a97be36e1af7bc0360e3ba7576d9e95ca5a29fd961353d6",
"tests/test_convoy_mine_stock.py": "e0073271428f00edf86251c9af5530f39094261b05474058df6b794bd958091f",
"tests/test_mounted_initial_stock.py": "cf7d101d8bd31cbdaaa0e7d66f0d8b39aff9b3b70acf51641902c6d0de5be096"
}
}