Integrate delivered HQ memory, finite physical convoy and RHS WCS dependencies
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
#ifdef WORKBENCH
|
||||
// Failure diagnostics only. Every query is read-only and uses the production seed/volume rules.
|
||||
class ERR_NativeConvoyRoadSurvey
|
||||
{
|
||||
void Run(vector seed)
|
||||
{
|
||||
if (!ERR_NativeConvoyProbe.Enabled()) return;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager())
|
||||
{
|
||||
Print("ERR CONVOY SURVEY: native AI world or road-network manager unavailable"); return;
|
||||
}
|
||||
array<BaseRoad> roads = {};
|
||||
vector low = seed + "-500 -1000 -500", high = seed + "500 1000 500";
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(low, high, roads);
|
||||
PrintFormat("ERR CONVOY SURVEY: seed=%1 AABB=%2..%3 candidates=%4; inspecting at most16, production cap128",
|
||||
seed, low, high, roads.Count());
|
||||
for (int r = 0; r < roads.Count() && r < 16; r++)
|
||||
{
|
||||
BaseRoad road = roads[r];
|
||||
if (!road) { Print("ERR CONVOY SURVEY: null candidate"); continue; }
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
float length, nearest = 1e20;
|
||||
for (int i = 1; i < points.Count() && i <= 1024; i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float segment = delta.Length(); length += segment;
|
||||
if (segment < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (segment * segment), 0, 1);
|
||||
nearest = Math.Min(nearest, vector.DistanceXZ(seed, vector.Lerp(points[i - 1], points[i], fraction)));
|
||||
}
|
||||
vector first, last;
|
||||
if (!points.IsEmpty()) { first = points[0]; last = points[points.Count() - 1]; }
|
||||
PrintFormat("ERR CONVOY SURVEY: road=%1 width=%2 points=%3 XZlength=%4 nearest=%5 first=%6 last=%7",
|
||||
r, road.GetWidth(), points.Count(), length, nearest, first, last);
|
||||
array<vector> positions, angles;
|
||||
ERR_ForceRoute route;
|
||||
float proximity;
|
||||
bool polyline = ERR_ConvoyRoadPlan.FromPolyline(points, seed, positions, angles, route, proximity);
|
||||
PrintFormat("ERR CONVOY SURVEY: road=%1 widthAllowed=%2 FromPolyline=%3 proximity=%4",
|
||||
r, road.GetWidth() >= 4, polyline, proximity);
|
||||
if (!polyline) continue;
|
||||
for (int i = 0; i < positions.Count(); i++) positions[i] = ERR_Actors.Ground(positions[i]);
|
||||
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
|
||||
for (int i = 0; i < positions.Count() && i < 6; i++)
|
||||
{
|
||||
bool clear = placement.Clear(positions[i], angles[i][0]);
|
||||
PrintFormat("ERR CONVOY SURVEY: road=%1 vehicle=%2 ground=%3 heading=%4 GroundClear=%5",
|
||||
r, i, positions[i], angles[i][0], clear);
|
||||
Print("ERR CONVOY SURVEY: " + placement.Diagnostic());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Opt-in native fixture: production road survey, canonical HQ dispatch and native driving.
|
||||
// The fixture does not create vehicles, seat crew, move entities or advance convoy service.
|
||||
class ERR_NativeConvoyProbe
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ref ERR_ConvoyTask m_Task;
|
||||
protected ref array<vector> m_Origins = {};
|
||||
protected ref array<IEntity> m_OriginalVehicles = {};
|
||||
protected ref array<IEntity> m_CargoBefore = {};
|
||||
protected float m_Deadline;
|
||||
protected float m_ReadyAt;
|
||||
protected float m_NextLog;
|
||||
protected float m_NextMovementDiagnostic;
|
||||
protected int m_InitialLedger;
|
||||
protected int m_Step;
|
||||
protected bool m_Failed;
|
||||
|
||||
static bool Enabled()
|
||||
{
|
||||
string enabled, outcome;
|
||||
return Replication.IsServer() && System.GetCLIParam("errSmoke", enabled) && enabled == "1"
|
||||
&& System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "convoy";
|
||||
}
|
||||
void ERR_NativeConvoyProbe(ERR_GameMode mode) { m_Mode = mode; }
|
||||
protected bool Check(bool condition, string description)
|
||||
{
|
||||
if (condition) { Print("ERR SMOKE PASS: convoy " + description); return true; }
|
||||
m_Failed = true; Print("ERR SMOKE FAIL: convoy " + description, LogLevel.ERROR); return false;
|
||||
}
|
||||
protected InventoryStorageManagerComponent TruckInventory()
|
||||
{
|
||||
if (!m_Task || m_Task.Vehicles.Count() != 6 || !m_Task.Vehicles[1].Entity) return null;
|
||||
return InventoryStorageManagerComponent.Cast(m_Task.Vehicles[1].Entity.FindComponent(InventoryStorageManagerComponent));
|
||||
}
|
||||
protected void PopulationDiagnostic(ERR_ConvoyVehicle vehicle, array<AIAgent> agents, int expected)
|
||||
{
|
||||
PrintFormat("ERR CONVOY POPULATION: vehicle=%1 registered=%2 members=%3 seats=%4 agents=%5 expected=%6",
|
||||
vehicle.Index, vehicle.Registered, vehicle.Members.Count(), vehicle.Seats.Count(), agents.Count(), expected);
|
||||
if (vehicle.Index != 0) return;
|
||||
for (int i = 0; i < agents.Count() && i < 19; i++)
|
||||
{
|
||||
AIAgent agent = agents[i];
|
||||
IEntity controlled;
|
||||
AIGroup parent;
|
||||
if (agent) { controlled = agent.GetControlledEntity(); parent = agent.GetParentGroup(); }
|
||||
string prefab = "none";
|
||||
if (controlled && controlled.GetPrefabData()) prefab = controlled.GetPrefabData().GetPrefabName();
|
||||
PrintFormat("ERR CONVOY POPULATION: returned agent=%1 controlled=%2 prefab=%3 parent=%4 expectedGroup=%5",
|
||||
agent, controlled, prefab, parent, vehicle.Group);
|
||||
}
|
||||
for (int i = 0; i < vehicle.Members.Count() && i < 19; i++)
|
||||
{
|
||||
IEntity actor = vehicle.Members[i];
|
||||
AIControlComponent control;
|
||||
AIAgent agent;
|
||||
AIGroup parent;
|
||||
if (actor) control = AIControlComponent.Cast(actor.FindComponent(AIControlComponent));
|
||||
if (control) agent = control.GetControlAIAgent();
|
||||
if (agent) parent = agent.GetParentGroup();
|
||||
PrintFormat("ERR CONVOY POPULATION: intended member=%1 actor=%2 controlAgent=%3 parent=%4 expectedGroup=%5",
|
||||
i, actor, agent, parent, vehicle.Group);
|
||||
}
|
||||
}
|
||||
protected string MovementAction(AIActionBase action)
|
||||
{
|
||||
if (!action) return "none";
|
||||
SCR_AIActionBase scripted = SCR_AIActionBase.Cast(action);
|
||||
string detail = action.ToString();
|
||||
if (scripted) detail = scripted.GetActionDebugInfo();
|
||||
if (detail.Length() > 200) detail = detail.Substring(0, 200);
|
||||
return string.Format("%1 state=%2 suspended=%3", detail, action.GetActionState(), action.GetActionIsSuspended());
|
||||
}
|
||||
protected void MovementDiagnostic(float now)
|
||||
{
|
||||
if (!m_Task || now < m_NextMovementDiagnostic || m_Origins.Count() != 6) return;
|
||||
m_NextMovementDiagnostic = now + 30000;
|
||||
for (int i = 0; i < m_Task.Vehicles.Count() && i < 6; i++)
|
||||
{
|
||||
ERR_ConvoyVehicle unit = m_Task.Vehicles[i];
|
||||
if (!unit || !unit.Entity || !unit.Group) { PrintFormat("ERR CONVOY MOTION: vehicle=%1 missing original identity", i); continue; }
|
||||
AIWaypoint current = unit.Group.GetCurrentWaypoint();
|
||||
vector target, nativeTarget;
|
||||
float targetDistance = -1;
|
||||
bool nativeWithin;
|
||||
if (unit.Waypoint)
|
||||
{
|
||||
target = unit.Waypoint.GetOrigin();
|
||||
targetDistance = vector.DistanceXZ(unit.Entity.GetOrigin(), target);
|
||||
nativeWithin = unit.Waypoint.IsWithinCompletionRadius(unit.Group);
|
||||
}
|
||||
if (current) nativeTarget = current.GetOrigin();
|
||||
PrintFormat("ERR CONVOY MOTION: vehicle=%1 travel=%2 pos=%3 routeIndex=%4 released=%5 arrived=%6 groupOrigin=%7",
|
||||
i, vector.DistanceXZ(m_Origins[i], unit.Entity.GetOrigin()), unit.Entity.GetOrigin(), unit.RouteIndex,
|
||||
unit.Released, unit.Arrived, unit.Group.GetOrigin());
|
||||
PrintFormat("ERR CONVOY MOTION: vehicle=%1 ownWaypoint=%2 currentWaypoint=%3 same=%4 target=%5 distance=%6 nativeWithin=%7 nativeTarget=%8",
|
||||
i, unit.Waypoint != null, current != null, current == unit.Waypoint, target, targetDistance, nativeWithin, nativeTarget);
|
||||
SCR_AIGroupUtilityComponent groupUtility = unit.Group.GetGroupUtilityComponent();
|
||||
string groupAction = "no group utility", driverBehavior = "no driver utility";
|
||||
if (groupUtility) groupAction = MovementAction(groupUtility.GetCurrentAction());
|
||||
AIControlComponent control;
|
||||
if (unit.Driver) control = AIControlComponent.Cast(unit.Driver.FindComponent(AIControlComponent));
|
||||
AIAgent driverAgent;
|
||||
if (control) driverAgent = control.GetControlAIAgent();
|
||||
SCR_AIUtilityComponent driverUtility;
|
||||
if (driverAgent) driverUtility = SCR_AIUtilityComponent.Cast(driverAgent.FindComponent(SCR_AIUtilityComponent));
|
||||
if (driverUtility) driverBehavior = MovementAction(driverUtility.GetCurrentBehavior());
|
||||
PrintFormat("ERR CONVOY MOTION: vehicle=%1 groupAction=%2 driverBehavior=%3", i, groupAction, driverBehavior);
|
||||
}
|
||||
}
|
||||
protected bool Population(ERR_HQ hq)
|
||||
{
|
||||
if (!Check(m_Task.Vehicles.Count() == 6, "six original vehicles remain tracked")) return false;
|
||||
array<IEntity> vehicles = {}, crew = {};
|
||||
array<SCR_AIGroup> groups = {};
|
||||
array<BaseCompartmentSlot> slots = {};
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
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),
|
||||
"vehicle and group identity " + i.ToString())) return false;
|
||||
vehicles.Insert(vehicle.Entity); groups.Insert(vehicle.Group);
|
||||
int expected = ERR_ConvoyPool.CrewCount(i);
|
||||
array<AIAgent> agents = {}; vehicle.Group.GetAgents(agents);
|
||||
PopulationDiagnostic(vehicle, agents, expected);
|
||||
if (!Check(vehicle.Registered && vehicle.Members.Count() == expected && vehicle.Seats.Count() == expected
|
||||
&& agents.Count() == expected, "native group membership matches exact crew plan " + i.ToString())) return false;
|
||||
ERR_ForceRecord record = m_Mode.Forces().FindGroup(vehicle.Group);
|
||||
if (!Check(record && record.Kind == ERR_ForceKind.CONVOY && record.Physical && !record.Eliminated
|
||||
&& record.Strength == expected && record.InitialStrength == expected
|
||||
&& record.Id == m_Task.Receipt.Records[i], "Core ledger owns physical convoy group " + i.ToString())) return false;
|
||||
for (int seatIndex = 0; seatIndex < expected; seatIndex++)
|
||||
{
|
||||
IEntity actor = vehicle.Members[seatIndex];
|
||||
BaseCompartmentSlot seat = vehicle.Seats[seatIndex];
|
||||
if (!Check(actor && ERR_Actors.CanFight(actor) && !crew.Contains(actor)
|
||||
&& ERR_Actors.Group(actor) == vehicle.Group && seat && !slots.Contains(seat) && seat.GetOccupant() == actor
|
||||
&& ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(actor), vehicle.Entity),
|
||||
"original crew occupies intended native compartment " + crew.Count().ToString())) return false;
|
||||
crew.Insert(actor); slots.Insert(seat);
|
||||
bool nativeMember;
|
||||
foreach (AIAgent agent : agents) if (agent && agent.GetControlledEntity() == actor) nativeMember = true;
|
||||
if (!Check(nativeMember, "seated actor is a real native group member")) return false;
|
||||
}
|
||||
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
|
||||
&& m_Task.Receipt.OutstandingBodies == 0 && m_Task.Receipt.OutstandingGroups == 0,
|
||||
"nineteen unique crew admitted once within shared sixty-body cap");
|
||||
}
|
||||
protected int Cargo(float now)
|
||||
{
|
||||
if (!m_Task.CargoIssued) return 0;
|
||||
if (m_Task.Cargo.Count() != 56) { Check(false, "cargo request count differs from56"); return -1; }
|
||||
int rifles, magazines;
|
||||
foreach (ERR_ConvoyCargoCallback operation : m_Task.Cargo)
|
||||
{
|
||||
if (!operation || !operation.Finished)
|
||||
{
|
||||
if (now - m_ReadyAt > 60000) { Check(false, "cargo callback deadline"); return -1; }
|
||||
return 0;
|
||||
}
|
||||
if (!operation.Succeeded) { Check(false, "native cargo insertion failed"); return -1; }
|
||||
if (operation.Prefab == ERR_ConvoyResources.Rifle()) rifles++;
|
||||
if (operation.Prefab == ERR_ConvoyResources.Magazine()) magazines++;
|
||||
}
|
||||
if (!Check(rifles == 8 && magazines == 48, "cargo requests are eight rifles and forty-eight magazines")) return -1;
|
||||
InventoryStorageManagerComponent inventory = TruckInventory();
|
||||
if (!Check(inventory != null, "real ammunition truck inventory exists")) return -1;
|
||||
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
|
||||
rifles = 0; magazines = 0;
|
||||
foreach (IEntity item : items)
|
||||
{
|
||||
if (!item || m_CargoBefore.Contains(item) || !item.GetPrefabData()) continue;
|
||||
ResourceName prefab = item.GetPrefabData().GetPrefabName();
|
||||
if (prefab == ERR_ConvoyResources.Rifle()) rifles++;
|
||||
if (prefab == ERR_ConvoyResources.Magazine()) magazines++;
|
||||
}
|
||||
if (!Check(rifles == 8 && magazines >= 48,
|
||||
"eight new physical rifles and at least forty-eight magazines found in truck")) return -1;
|
||||
return 1;
|
||||
}
|
||||
static bool MovementComplete(float leaderMetres, float minimumMetres)
|
||||
{
|
||||
return ERR_ForceRules.Finite(leaderMetres) && ERR_ForceRules.Finite(minimumMetres)
|
||||
&& leaderMetres >= 100 && minimumMetres >= 20;
|
||||
}
|
||||
int Tick(ERR_HQ hq, float now)
|
||||
{
|
||||
if (!Enabled() || !hq || !m_Mode || m_Mode != ERR_GameMode.Get() || m_Failed) return -1;
|
||||
if (m_Deadline == 0) m_Deadline = now + 480000;
|
||||
if (now >= m_Deadline) { Check(false, "native fixture deadline"); return -1; }
|
||||
if (m_Step == 0)
|
||||
{
|
||||
// Existing finite support must finish ordinary readiness before measuring the ledger baseline.
|
||||
array<ref ERR_MortarBattery> batteries = {}; hq.Mortars().Batteries(batteries);
|
||||
foreach (ERR_MortarBattery battery : batteries)
|
||||
{
|
||||
if (!battery || battery.Stopped()) { Check(false, "baseline mortar readiness failed"); return -1; }
|
||||
if (!battery.StartupActivated) return 0;
|
||||
}
|
||||
// Canonical authored supply-site seed; a missing suitable real road is a test failure.
|
||||
ERR_SiteRuntime site = m_Mode.Sites().Find("everon-supply-1");
|
||||
if (!Check(site && site.Definition, "authored supply-site road survey seed exists")) return -1;
|
||||
vector seed = site.Definition.Position;
|
||||
m_InitialLedger = m_Mode.Forces().Count();
|
||||
Print("ERR CONVOY NATIVE: road survey seed=" + seed.ToString());
|
||||
if (!hq.PrepareConvoy(seed))
|
||||
{
|
||||
ERR_NativeConvoyRoadSurvey survey = new ERR_NativeConvoyRoadSurvey(); survey.Run(seed);
|
||||
Check(false, "production road survey refused seed; read-only diagnostics above"); return -1;
|
||||
}
|
||||
Check(true, "production road survey accepted six real road positions");
|
||||
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;
|
||||
foreach (ERR_ConvoyVehicle vehicle : m_Task.Vehicles)
|
||||
{
|
||||
if (!Check(vehicle.Entity != null, "original physical vehicle created")) return -1;
|
||||
m_OriginalVehicles.Insert(vehicle.Entity); m_Origins.Insert(vehicle.Entity.GetOrigin());
|
||||
}
|
||||
ERR_Inventory.Items(TruckInventory(), m_CargoBefore);
|
||||
if (!Check(!hq.StartConvoy() && hq.ConvoyTask() == m_Task && hq.Convoys().Count() == 1,
|
||||
"duplicate canonical dispatch creates no second native task")) return -1;
|
||||
if (!Check(!hq.ConvoyPool().Reserve("native-over-cap"), "second convoy reservation rejected by finite cap")) return -1;
|
||||
m_Step = 1;
|
||||
}
|
||||
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 (!m_Task || (m_Task.Stopped && !completed))
|
||||
{
|
||||
string reason = "missing task";
|
||||
if (m_Task) reason = m_Task.Reason;
|
||||
Check(false, "production convoy stopped before proof: " + reason); return -1;
|
||||
}
|
||||
if (m_Step == 1)
|
||||
{
|
||||
if (!m_Task.Ready) return 0;
|
||||
m_ReadyAt = now;
|
||||
if (!Population(hq)) return -1;
|
||||
ERR_ConvoyPlacement occupied = new ERR_ConvoyPlacement();
|
||||
Vehicle lead = m_Task.Vehicles[0].Entity;
|
||||
bool empty = occupied.Clear(ERR_Actors.Ground(lead.GetOrigin()), lead.GetAngles()[0]);
|
||||
float fraction = occupied.LastTraceFraction();
|
||||
Print("ERR CONVOY NATIVE: existing lead vehicle blocker control; " + occupied.Diagnostic());
|
||||
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;
|
||||
m_Step = 2;
|
||||
}
|
||||
if (m_Step == 2)
|
||||
{
|
||||
int cargo = Cargo(now);
|
||||
if (cargo <= 0) return cargo;
|
||||
m_Step = 3;
|
||||
}
|
||||
float minimum = 1000000;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
ERR_ConvoyVehicle vehicle = m_Task.Vehicles[i];
|
||||
if (!vehicle.Entity || (vehicle.Released && !vehicle.Arrived)) { Check(false, "vehicle released before road movement proof"); return -1; }
|
||||
minimum = Math.Min(minimum, vector.DistanceXZ(m_Origins[i], vehicle.Entity.GetOrigin()));
|
||||
}
|
||||
float leader = vector.DistanceXZ(m_Origins[0], m_Task.Vehicles[0].Entity.GetOrigin());
|
||||
if (now >= m_NextLog)
|
||||
{
|
||||
m_NextLog = now + 10000;
|
||||
PrintFormat("ERR CONVOY NATIVE: leader travel=%1m minimum vehicle travel=%2m; age=%3s",
|
||||
leader, minimum, (now - m_ReadyAt) / 1000);
|
||||
}
|
||||
MovementDiagnostic(now);
|
||||
if (!MovementComplete(leader, minimum)) return 0;
|
||||
if (!Check(m_Task.Route && m_Task.Route.Count() >= 2 && !m_Task.Route.Looped(),
|
||||
"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,
|
||||
"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");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user