Route delivered HQ tracks to temporary road interception and refresh playtest packages
This commit is contained in:
@@ -693,11 +693,14 @@ class ERR_HQ : ERR_CommandService
|
||||
if (!belief || belief.BeliefRevision != task.BeliefRevision
|
||||
|| !ERR_HQPursuitRules.Actionable(belief.Confidence, (now - belief.ObservedAt) / 1000, belief.Radius))
|
||||
{ LostOrder(unit, now); return; }
|
||||
if (task.RoadIntercept && !ERR_HQRoadIntercept.Eligible(belief, now)) { LostOrder(unit, now); return; }
|
||||
}
|
||||
if (Fighting(unit)) { LostOrder(unit, now); return; }
|
||||
int deliveryId;
|
||||
if (unit.Downlink) deliveryId = unit.Downlink.MessageId;
|
||||
AIWaypoint waypoint = AIWaypoint.Cast(ERR_Actors.Spawn(ERR_Resources.SearchWaypoint(), ERR_Actors.Ground(task.Destination)));
|
||||
ResourceName waypointResource = ERR_Resources.SearchWaypoint();
|
||||
if (task.RoadIntercept) waypointResource = ERR_Resources.DefendWaypoint();
|
||||
AIWaypoint waypoint = AIWaypoint.Cast(ERR_Actors.Spawn(waypointResource, ERR_Actors.Ground(task.Destination)));
|
||||
if (!waypoint) { LostOrder(unit, now); m_Journal.Write("ORDER FAILED", "waypoint spawn failed", order.MessageId); return; }
|
||||
unit.Release();
|
||||
unit.Task = task;
|
||||
@@ -715,7 +718,7 @@ class ERR_HQ : ERR_CommandService
|
||||
protected void Sweep(ERR_HQUnit unit, float now)
|
||||
{
|
||||
ERR_HQTask task = unit.Task;
|
||||
if (!task || !unit.Assignment || task.SweepQueued || !unit.Waypoint || !unit.Eligible(m_Mode)) return;
|
||||
if (!task || task.RoadIntercept || !unit.Assignment || task.SweepQueued || !unit.Waypoint || !unit.Eligible(m_Mode)) return;
|
||||
if (Fighting(unit) || vector.DistanceXZ(unit.Group.GetOrigin(), task.Destination) > 40)
|
||||
{ task.ArrivedAt = -1; return; }
|
||||
// Quiet combat state alone does not establish a negative search. Use this
|
||||
@@ -923,6 +926,7 @@ class ERR_HQ : ERR_CommandService
|
||||
int limit = ERR_HQPursuitRules.Limit(globalLimit);
|
||||
int radius = m_Mode.Settings().Get("responseRadiusMetres");
|
||||
int assigned = Assigned();
|
||||
int roadQueries;
|
||||
// Snapshot bounded own-force eligibility once per five-second planning pass.
|
||||
m_PlanBeliefs.Clear();
|
||||
for (int index = 0; index < m_Memory.Count(); index++) m_PlanBeliefs.Insert(m_Memory.At(index));
|
||||
@@ -987,6 +991,12 @@ class ERR_HQ : ERR_CommandService
|
||||
float age = (now - belief.ObservedAt) / 1000;
|
||||
ERR_HQPursuitRole role = ERR_HQPursuitRules.Role(belief.Confidence, age, belief.Radius, belief.HasVelocity, slot);
|
||||
vector destination = ERR_HQPursuitRules.Objective(belief.PredictedPosition, belief.Velocity, belief.HasVelocity, belief.Confidence, age, belief.Radius, role, slot);
|
||||
bool roadIntercept;
|
||||
if (role == ERR_HQPursuitRole.INTERCEPT)
|
||||
{
|
||||
if (roadQueries < 2) { roadQueries++; roadIntercept = ERR_HQRoadIntercept.Select(belief, now, reserved, destination); }
|
||||
if (!roadIntercept) role = ERR_HQPursuitRole.SEARCH;
|
||||
}
|
||||
if (role == ERR_HQPursuitRole.CONTAIN) destination = Containment(belief, destination);
|
||||
if (role == ERR_HQPursuitRole.SEARCH && !m_Coverage.Select(belief.PredictedPosition, belief.Radius, belief.Position, now, destination, reserved)) continue;
|
||||
ERR_HQUnit best;
|
||||
@@ -1011,7 +1021,7 @@ class ERR_HQ : ERR_CommandService
|
||||
}
|
||||
if (!best) continue;
|
||||
ERR_HQTask task = new ERR_HQTask();
|
||||
task.BeliefId = belief.BeliefId; task.BeliefRevision = belief.BeliefRevision; task.Role = role;
|
||||
task.BeliefId = belief.BeliefId; task.BeliefRevision = belief.BeliefRevision; task.Role = role; task.RoadIntercept = roadIntercept;
|
||||
task.Destination = destination; task.QueuedAt = now;
|
||||
task.ExpiresAt = now + ERR_HQPursuitRules.TaskLifetimeSeconds(role, vector.DistanceXZ(best.PlanPosition, destination)) * 1000;
|
||||
task.ApproachBearing = bestBearing;
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
// Optional HQ destination refinement over delivered memory and authored roads.
|
||||
// No player/target entity reads, forces, props, assignments or independent timer.
|
||||
class ERR_HQRoadIntercept
|
||||
{
|
||||
static bool Eligible(ERR_HQBelief belief, float now)
|
||||
{
|
||||
if (!belief || belief.Kind != ERR_HQMemoryKind.SIGHTING || belief.SampleCount < 2 || belief.MessageId <= 0) return false;
|
||||
if (!ERR_ForceRules.Finite(now) || !ERR_ForceRules.Finite(belief.LastDeliveredAt) || belief.LastDeliveredAt > now || belief.LastDeliveredAt < belief.ObservedAt) return false;
|
||||
if (belief.TravelMode != "on foot" && belief.TravelMode != "in vehicle") return false;
|
||||
if (!ERR_HQPursuitRules.Heading(belief.Velocity, belief.HasVelocity)) return false;
|
||||
vector velocity = belief.Velocity; velocity[1] = 0;
|
||||
if (velocity.Length() < 2) return false;
|
||||
return ERR_HQPursuitRules.Role(belief.Confidence, (now - belief.ObservedAt) / 1000, belief.Radius, true, 0) == ERR_HQPursuitRole.INTERCEPT;
|
||||
}
|
||||
static bool Ahead(vector origin, vector velocity, vector point)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_ForceRules.FiniteVector(point) || !ERR_HQPursuitRules.Heading(velocity, true)) return false;
|
||||
vector delta = point - origin; delta[1] = 0; velocity[1] = 0;
|
||||
float distance = delta.Length();
|
||||
return distance >= 40 && distance <= 250 && vector.Dot(delta.Normalized(), velocity.Normalized()) >= 0.8;
|
||||
}
|
||||
static bool Unreserved(vector point, array<vector> reserved)
|
||||
{
|
||||
if (!reserved || reserved.Count() > 128) return false;
|
||||
foreach (vector other : reserved)
|
||||
if (!ERR_ForceRules.FiniteVector(other) || vector.DistanceXZ(point, other) < 80) return false;
|
||||
return true;
|
||||
}
|
||||
static bool Better(float score, vector point, float bestScore, vector best)
|
||||
{
|
||||
if (score < bestScore) return true;
|
||||
if (score > bestScore) return false;
|
||||
if (point[0] != best[0]) return point[0] < best[0];
|
||||
if (point[2] != best[2]) return point[2] < best[2];
|
||||
return point[1] < best[1];
|
||||
}
|
||||
// Projection stays on one actual road segment, never a chord through bends.
|
||||
static bool FromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector result, out float score)
|
||||
{
|
||||
result = vector.Zero; score = 1e20;
|
||||
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
|
||||
bool found;
|
||||
for (int i = 1; i < points.Count(); i++)
|
||||
{
|
||||
vector delta = points[i] - points[i - 1]; delta[1] = 0;
|
||||
float length = delta.Length();
|
||||
if (length < 0.01) continue;
|
||||
vector offset = seed - points[i - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
|
||||
float distance = vector.DistanceXZ(seed, candidate);
|
||||
if (distance > 100 || !Ahead(origin, velocity, candidate) || !Unreserved(candidate, reserved)) continue;
|
||||
if (!found || Better(distance, candidate, score, result)) { result = candidate; score = distance; found = true; }
|
||||
}
|
||||
return found;
|
||||
}
|
||||
static bool Standable(vector position)
|
||||
{
|
||||
if (!ERR_ForceRules.FiniteVector(position) || ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), position)) return false;
|
||||
array<vector> edges = {"0.4 0 0", "-0.4 0 0", "0 0 0.4", "0 0 -0.4"};
|
||||
foreach (vector edge : edges)
|
||||
{
|
||||
vector sample = ERR_Actors.Ground(position + edge);
|
||||
if (!ERR_ForceRules.FiniteVector(sample) || Math.AbsFloat(sample[1] - position[1]) > 0.4) return false;
|
||||
}
|
||||
TraceBox body = new TraceBox(); body.Start = position + "0 1 0"; body.End = body.Start + "0 0.01 0";
|
||||
body.Mins = "-0.35 -0.9 -0.35"; body.Maxs = "0.35 0.9 0.35";
|
||||
body.Flags = TraceFlags.WORLD | TraceFlags.ENTS;
|
||||
return GetGame().GetWorld().TraceMove(body, null) == 1;
|
||||
}
|
||||
static bool Select(ERR_HQBelief belief, float now, array<vector> reserved, out vector result)
|
||||
{
|
||||
result = vector.Zero;
|
||||
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
|
||||
vector seed = ERR_HQPursuitRules.Objective(belief.PredictedPosition, belief.Velocity, true, belief.Confidence,
|
||||
(now - belief.ObservedAt) / 1000, belief.Radius, ERR_HQPursuitRole.INTERCEPT, 0);
|
||||
if (!ERR_ForceRules.FiniteVector(seed)) return false;
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
|
||||
if (roads.Count() > 128) return false;
|
||||
int segments;
|
||||
bool found;
|
||||
vector best;
|
||||
float bestScore = 1e20;
|
||||
foreach (BaseRoad road : roads)
|
||||
{
|
||||
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 4) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
segments += points.Count() - 1;
|
||||
if (segments > 4096) return false;
|
||||
vector candidate;
|
||||
float score;
|
||||
if (!FromPolyline(points, belief.PredictedPosition, belief.Velocity, seed, reserved, candidate, score)) continue;
|
||||
if (!found || Better(score, candidate, bestScore, best)) { best = candidate; bestScore = score; found = true; }
|
||||
}
|
||||
if (!found) return false;
|
||||
vector grounded = ERR_Actors.Ground(best);
|
||||
// Terrain grounding must not turn an elevated road/bridge into an offroad post below it.
|
||||
if (!ERR_ForceRules.FiniteVector(grounded) || Math.AbsFloat(grounded[1] - best[1]) > 2 || !Standable(grounded)) return false;
|
||||
result = grounded;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
class ERR_HQTask
|
||||
{
|
||||
bool ReleaseOnly;
|
||||
bool RoadIntercept;
|
||||
ref ERR_HQTask ReplacesTask;
|
||||
string WatchSourceId;
|
||||
int WatchMessageId;
|
||||
@@ -19,6 +20,7 @@ class ERR_HQTask
|
||||
string Name()
|
||||
{
|
||||
if (Investigation()) return "investigate missing patrol";
|
||||
if (RoadIntercept) return "temporary infantry road interception";
|
||||
if (Role == ERR_HQPursuitRole.INTERCEPT) return "intercept";
|
||||
if (Role == ERR_HQPursuitRole.CONTAIN) return "contain";
|
||||
return "search";
|
||||
|
||||
@@ -117,6 +117,76 @@ class ERR_NativeHQMemoryProbe
|
||||
report.ObservedAt = now; report.Position = position; report.TravelMode = "on foot";
|
||||
return report;
|
||||
}
|
||||
// Read-only native road evidence with explicitly arranged delivered-value inputs.
|
||||
// No natural perception, real radio delivery or force movement is claimed here.
|
||||
protected bool LocalRoadPolicy()
|
||||
{
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!Check(world && world.GetRoadNetworkManager(), "road fixture has native Everon road manager")) return false;
|
||||
vector center = m_Mode.PrisonPosition();
|
||||
array<BaseRoad> roads = {};
|
||||
world.GetRoadNetworkManager().GetRoadsInAABB(center + "-500 -1000 -500", center + "500 1000 500", roads);
|
||||
if (!Check(!roads.IsEmpty() && roads.Count() <= 128, "road fixture native query stays within bounded road count")) return false;
|
||||
int attempts, inspected;
|
||||
for (int r = 0; r < roads.Count() && r < 16 && attempts < 8; r++)
|
||||
{
|
||||
BaseRoad road = roads[r];
|
||||
if (!road || road.GetWidth() < 4) continue;
|
||||
array<vector> points = {}; road.GetPoints(points);
|
||||
if (points.Count() < 2 || points.Count() > 1024) continue;
|
||||
for (int p = 1; p < points.Count() && attempts < 8 && inspected < 4096; p++)
|
||||
{
|
||||
inspected++;
|
||||
vector midpoint = vector.Lerp(points[p - 1], points[p], 0.5);
|
||||
vector direction = points[p] - points[p - 1]; direction[1] = 0;
|
||||
if (direction.Length() < 1 || vector.DistanceXZ(center, midpoint) > 500) continue;
|
||||
direction.Normalize(); attempts++;
|
||||
// Three metres/second produces a 60m future lead at this zero-age sample.
|
||||
ERR_HQMemory memory = new ERR_HQMemory();
|
||||
ERR_HQReport first = new ERR_HQReport();
|
||||
first.Target = m_Mode; first.MessageId = 1; first.ObservedAt = 6000;
|
||||
first.Position = midpoint - direction * 72; first.TravelMode = "on foot";
|
||||
ERR_HQReport second = first.Snapshot();
|
||||
second.MessageId = 2; second.ObservedAt = 10000; second.Position = midpoint - direction * 60;
|
||||
if (!Check(memory.Ingest(first, 6000) && memory.Ingest(second, 10000), "arranged detached road snapshots accepted by private memory")) return false;
|
||||
ERR_HQBelief belief = memory.Get(m_Mode);
|
||||
array<vector> reserved = {};
|
||||
vector chosen;
|
||||
if (!ERR_HQRoadIntercept.Select(belief, 10000, reserved, chosen)) continue;
|
||||
if (!Check(ERR_HQRoadIntercept.Ahead(belief.PredictedPosition, belief.Velocity, chosen)
|
||||
&& vector.DistanceXZ(chosen, midpoint) <= 100.1 && ERR_HQRoadIntercept.Standable(chosen),
|
||||
"native road choice is forward, nearby, dry and physically clear")) return false;
|
||||
// Independently measure the chosen point against native segment geometry.
|
||||
bool onRoad;
|
||||
int verifiedSegments;
|
||||
foreach (BaseRoad verify : roads)
|
||||
{
|
||||
if (!verify || verify.GetWidth() < 4) continue;
|
||||
array<vector> vertices = {}; verify.GetPoints(vertices);
|
||||
if (vertices.Count() < 2 || vertices.Count() > 1024) continue;
|
||||
verifiedSegments += vertices.Count() - 1;
|
||||
if (!Check(verifiedSegments <= 4096, "road membership verification stays within segment budget")) return false;
|
||||
for (int v = 1; v < vertices.Count(); v++)
|
||||
{
|
||||
vector delta = vertices[v] - vertices[v - 1]; delta[1] = 0;
|
||||
float length = delta.Length(); if (length < 0.01) continue;
|
||||
vector offset = chosen - vertices[v - 1]; offset[1] = 0;
|
||||
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
|
||||
vector projected = vector.Lerp(vertices[v - 1], vertices[v], fraction);
|
||||
if (vector.DistanceXZ(chosen, projected) < 0.1 && Math.AbsFloat(chosen[1] - projected[1]) <= 2) { onRoad = true; break; }
|
||||
}
|
||||
if (onRoad) break;
|
||||
}
|
||||
if (!Check(onRoad, "chosen intercept lies on an actual native road segment")) return false;
|
||||
PrintFormat("ERR ROAD FIXTURE: arranged origin=%1 velocity=%2 chosen=%3 attempts=%4", belief.Position, belief.Velocity, chosen, attempts);
|
||||
memory.Tick(56000);
|
||||
if (!Check(!ERR_HQRoadIntercept.Select(memory.Get(m_Mode), 56000, reserved, chosen),
|
||||
"same arranged belief aged46seconds refuses road interception")) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return Check(false, "bounded road fixture found no suitable native candidate; no fallback or force mutation");
|
||||
}
|
||||
protected bool LocalPolicies()
|
||||
{
|
||||
ERR_ForceLedger ledger = new ERR_ForceLedger();
|
||||
@@ -233,7 +303,7 @@ class ERR_NativeHQMemoryProbe
|
||||
{
|
||||
if (!Check(hq.ERR_MemoryFixtureRegisteredForces() == 6,
|
||||
"actual six site groups are registered once in the Core force ledger")) return -1;
|
||||
if (!LocalPolicies()) return -1;
|
||||
if (!LocalPolicies() || !LocalRoadPolicy()) return -1;
|
||||
m_ValueChecked = true;
|
||||
}
|
||||
m_FirstPosition = m_Mode.PrisonPosition() + "300 0 300";
|
||||
|
||||
Reference in New Issue
Block a user