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:
@@ -0,0 +1,365 @@
|
||||
class ERR_AirPassenger
|
||||
{
|
||||
IEntity Actor;
|
||||
BaseCompartmentSlot Seat;
|
||||
bool Locked = true;
|
||||
bool Jumped;
|
||||
ERR_AssaultParachute Chute;
|
||||
}
|
||||
class ERR_AirAssaultTeam
|
||||
{
|
||||
SCR_AIGroup Group;
|
||||
ref array<ref ERR_AirPassenger> Members = {};
|
||||
ref ERR_AirDropOrder Order;
|
||||
bool Released;
|
||||
void Landed() {} // Canopy completion is observed by the owning load for the entire original team.
|
||||
void Update()
|
||||
{
|
||||
if (Released || !Group || !Order || Group.GetPlayerCount(true) > 0) return;
|
||||
bool survivor;
|
||||
foreach (ERR_AirPassenger member : Members)
|
||||
{
|
||||
if (!ERR_Actors.Alive(member.Actor)) continue;
|
||||
if (!member.Jumped || CompartmentAccessComponent.GetVehicleIn(member.Actor) || member.Actor.GetOrigin()[1] - SCR_TerrainHelper.GetTerrainY(member.Actor.GetOrigin()) > 3) return;
|
||||
survivor = survivor || ERR_Actors.CanFight(member.Actor);
|
||||
}
|
||||
if (!survivor) return;
|
||||
Released = true;
|
||||
Group.ActivateAI();
|
||||
Group.SetPermanentLOD(-1);
|
||||
ERR_HQ.Get().LandAirTeam(Group, Order);
|
||||
}
|
||||
}
|
||||
class ERR_AirDropGeometry
|
||||
{
|
||||
static string LastFailure;
|
||||
static float Along(vector position, vector center, vector direction)
|
||||
{ vector offset = position - center;
|
||||
offset[1] = 0;
|
||||
return vector.Dot(offset, direction);
|
||||
}
|
||||
static bool Corridor(vector position, vector center, vector direction, vector velocity)
|
||||
{
|
||||
float along = Along(position, center, direction);
|
||||
vector lateral = position - center - direction * along;
|
||||
lateral[1] = 0;
|
||||
velocity[1] = 0;
|
||||
return along >= -175 && along <= 250 && lateral.Length() <= 45 && velocity.Length() >= 15 && vector.Dot(velocity.Normalized(), direction) >= 0.8;
|
||||
}
|
||||
static bool LandingLine(vector center, vector direction)
|
||||
{
|
||||
LastFailure = "clear";
|
||||
for (int step = -4; step <= 5; step++)
|
||||
{
|
||||
vector point = ERR_Actors.Ground(center + direction * (step * 50));
|
||||
if (point[1] < 2 || ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), point)) { LastFailure = "water/shore"; return false; }
|
||||
if (!ERR_HQRoadIntercept.Standable(point)) { LastFailure = "landing ground not standable"; return false; }
|
||||
TraceBox trace = new TraceBox();
|
||||
trace.Start = point + "0 3 0";
|
||||
trace.End = point + "0 190 0";
|
||||
trace.Mins = "-4 -1 -4";
|
||||
trace.Maxs = "4 1 4";
|
||||
trace.Flags = TraceFlags.ENTS | TraceFlags.WORLD;
|
||||
if (GetGame().GetWorld().TraceMove(trace, null) < 1) { LastFailure = "obstructed descent column"; return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Eight original passengers, two original teams, one consumed load per aircraft.
|
||||
class ERR_AirAssaultLoad
|
||||
{
|
||||
static const ResourceName PARACHUTE = "{C31F210ABE0781A0}Prefabs/ERR/AssaultParachute.et";
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ERR_ReconFlightComponent m_Flight;
|
||||
protected Vehicle m_Aircraft;
|
||||
protected int m_Serial, m_NextPassenger;
|
||||
protected ref array<BaseCompartmentSlot> m_Seats = {};
|
||||
protected ref array<ref ERR_AirPassenger> m_Passengers = {};
|
||||
protected ref array<ref ERR_AirAssaultTeam> m_Teams = {};
|
||||
protected ref ERR_AirDropOrder m_Order;
|
||||
protected float m_CoverUntil;
|
||||
bool Cover(float now, out vector center) { if (!m_Order || !m_Used || m_Approaching || now >= m_CoverUntil) return false;
|
||||
center = m_Order.Target;
|
||||
return true;
|
||||
}
|
||||
bool NeedsService(float now) { return m_Used && !m_Approaching && now >= m_CoverUntil;
|
||||
}
|
||||
protected float m_LoadDeadline, m_NextBoard, m_NextJump, m_AirspaceUntil;
|
||||
protected bool m_Ready, m_Failed, m_Stopped, m_Used, m_Approaching, m_RunCommitted;
|
||||
protected vector m_Center, m_Direction, m_RunStart, m_RunEnd;
|
||||
static void SetLock(IEntity actor, bool locked)
|
||||
{
|
||||
ERR_HQ hq = ERR_HQ.Get();
|
||||
if (hq && hq.AirAssault()) hq.AirAssault().Lock(actor, locked);
|
||||
}
|
||||
void ReuseRemaining()
|
||||
{
|
||||
m_Used = false;
|
||||
m_Order = null;
|
||||
m_CoverUntil = 0;
|
||||
foreach (ERR_AirPassenger member : m_Passengers)
|
||||
if (member.Actor && CompartmentAccessComponent.GetVehicleIn(member.Actor) == m_Aircraft) { member.Jumped = false;
|
||||
member.Locked = true;
|
||||
}
|
||||
}
|
||||
int Count() { return m_Passengers.Count();
|
||||
}
|
||||
ERR_AirPassenger At(int index) { return m_Passengers[index];
|
||||
}
|
||||
bool Used() { return m_Used;
|
||||
}
|
||||
bool Preparing() { return !m_Ready && !m_Failed && !m_Stopped;
|
||||
}
|
||||
bool Ready()
|
||||
{
|
||||
return m_Ready && !m_Failed && !m_Stopped && !m_Used && m_Flight && !m_Flight.Lost()
|
||||
&& (m_Flight.Phase() == ERR_ReconPhase.SEARCH || m_Flight.Phase() == ERR_ReconPhase.SHADOW) && Aboard() > 0;
|
||||
}
|
||||
void ERR_AirAssaultLoad(ERR_GameMode mode, ERR_ReconFlightComponent flight, int serial)
|
||||
{ m_Mode = mode;
|
||||
m_Flight = flight;
|
||||
m_Aircraft = flight.Aircraft();
|
||||
m_Serial = serial;
|
||||
}
|
||||
protected void Seats(IEntity entity)
|
||||
{
|
||||
BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(entity.FindComponent(BaseCompartmentManagerComponent));
|
||||
if (manager)
|
||||
{
|
||||
array<BaseCompartmentSlot> seats = {};
|
||||
manager.GetCompartments(seats);
|
||||
foreach (BaseCompartmentSlot seat : seats)
|
||||
if (seat && seat.GetType() == ECompartmentType.CARGO && seat.IsCompartmentAccessible() && !seat.IsOccupied() && !m_Seats.Contains(seat)) m_Seats.Insert(seat);
|
||||
}
|
||||
IEntity child = entity.GetChildren();
|
||||
while (child) { Seats(child);
|
||||
child = child.GetSibling();
|
||||
}
|
||||
}
|
||||
protected void Failure(string reason)
|
||||
{
|
||||
m_Failed = true;
|
||||
m_Approaching = false;
|
||||
foreach (ERR_AirPassenger member : m_Passengers) member.Locked = false;
|
||||
ERR_HQ.Event("AIR LOAD UNAVAILABLE", reason + "; original partial entities retained, reconnaissance remains available");
|
||||
}
|
||||
void Load(vector pad)
|
||||
{
|
||||
m_LoadDeadline = GetGame().GetWorld().GetWorldTime() + 30000;
|
||||
Seats(m_Aircraft);
|
||||
ResourceName soldier = m_Mode.FactionSelection().Soldier();
|
||||
Resource chute = Resource.Load(PARACHUTE);
|
||||
if (m_Seats.Count() < 8 || soldier.IsEmpty() || !chute || !chute.IsValid()) { Failure("eight usable cargo seats, soldier or canopy unavailable");
|
||||
return;
|
||||
}
|
||||
for (int t = 0; t < 2; t++)
|
||||
{
|
||||
ERR_AirAssaultTeam team = new ERR_AirAssaultTeam();
|
||||
team.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn("{C31F210ABE078195}Prefabs/ERR/ReconCrew.et", pad));
|
||||
m_Teams.Insert(team);
|
||||
if (!team.Group) { Failure("passenger group spawn failed");
|
||||
return;
|
||||
}
|
||||
m_Mode.Own(team.Group);
|
||||
team.Group.SetFaction(m_Mode.EnemyFaction());
|
||||
team.Group.SetPermanentLOD(0);
|
||||
}
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
ERR_AirPassenger member = new ERR_AirPassenger();
|
||||
member.Seat = m_Seats[i];
|
||||
AIGroup group = m_Teams[i % 2].Group;
|
||||
member.Actor = member.Seat.SpawnCharacterInCompartment(soldier, group, "{C31F210ABE078195}Prefabs/ERR/ReconCrew.et");
|
||||
m_Passengers.Insert(member);
|
||||
m_Teams[i % 2].Members.Insert(member);
|
||||
if (!member.Actor || group != m_Teams[i % 2].Group) { Failure("native passenger creation or group identity failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
protected void Boarding(float now)
|
||||
{
|
||||
foreach (ERR_AirAssaultTeam loadingTeam : m_Teams)
|
||||
{
|
||||
if (!loadingTeam.Group) continue;
|
||||
loadingTeam.Group.ActivateAI();
|
||||
array<AIAgent> loadingAgents = {};
|
||||
loadingTeam.Group.GetAgents(loadingAgents);
|
||||
foreach (AIAgent loadingAgent : loadingAgents) { loadingAgent.SetPermanentLOD(0);
|
||||
loadingAgent.ActivateAI();
|
||||
}
|
||||
}
|
||||
if (now >= m_LoadDeadline) { Failure(string.Format("native passenger boarding timed out; seated=%1/%2", Aboard(), Count()));
|
||||
return;
|
||||
}
|
||||
bool all = m_Passengers.Count() == 8;
|
||||
foreach (ERR_AirPassenger member : m_Passengers)
|
||||
{
|
||||
if (!ERR_Actors.CanFight(member.Actor)) { all = false;
|
||||
continue;
|
||||
}
|
||||
if (CompartmentAccessComponent.GetVehicleIn(member.Actor) == m_Aircraft && member.Seat.GetOccupant() == member.Actor) continue;
|
||||
all = false;
|
||||
if (now < m_NextBoard || CompartmentAccessComponent.GetVehicleIn(member.Actor) || member.Seat.IsOccupied()) continue;
|
||||
SCR_CompartmentAccessComponent access = SCR_CompartmentAccessComponent.Cast(member.Actor.FindComponent(SCR_CompartmentAccessComponent));
|
||||
if (access) access.MoveInVehicle(m_Aircraft, ECompartmentType.CARGO, false, member.Seat);
|
||||
}
|
||||
if (now >= m_NextBoard) m_NextBoard = now + 1000;
|
||||
if (!all) return;
|
||||
for (int t = 0; t < m_Teams.Count(); t++)
|
||||
{
|
||||
ERR_AirAssaultTeam team = m_Teams[t];
|
||||
foreach (ERR_AirPassenger original : team.Members)
|
||||
if (ERR_Actors.Group(original.Actor) != team.Group || !ERR_Actors.SetFaction(original.Actor, m_Mode.EnemyFaction())) { Failure("original passenger faction/group changed");
|
||||
return;
|
||||
}
|
||||
ERR_ForceRecord record = m_Mode.Forces().Register(string.Format("airborne-%1-team-%2", m_Serial, t), ERR_ForceKind.PATROL, m_Aircraft.GetOrigin(), 4, null, team.Group, now);
|
||||
if (!record) { Failure("finite passenger ledger refused registration");
|
||||
return;
|
||||
}
|
||||
record.Pinned = true;
|
||||
m_Mode.RegisterGroup(team.Group);
|
||||
ERR_HQ.Get().ReserveGroup(team.Group);
|
||||
team.Group.ActivateAI();
|
||||
array<AIAgent> agents = {};
|
||||
team.Group.GetAgents(agents);
|
||||
foreach (AIAgent agent : agents) { agent.SetPermanentLOD(0);
|
||||
agent.ActivateAI();
|
||||
}
|
||||
}
|
||||
m_Ready = true;
|
||||
ERR_HQ.Event("AIR LOAD READY", "eight original passengers seated in two four-person teams");
|
||||
}
|
||||
int Aboard()
|
||||
{
|
||||
if (!m_Aircraft) return 0;
|
||||
int count;
|
||||
foreach (ERR_AirPassenger member : m_Passengers)
|
||||
if (ERR_Actors.CanFight(member.Actor) && !ERR_Actors.Player(member.Actor) && CompartmentAccessComponent.GetVehicleIn(member.Actor) == m_Aircraft) count++;
|
||||
return count;
|
||||
}
|
||||
bool HoldsSeat(IEntity actor)
|
||||
{
|
||||
if (m_Stopped || m_Failed || !ERR_Actors.CanFight(actor) || ERR_Actors.Player(actor)) return false;
|
||||
foreach (ERR_AirAssaultTeam team : m_Teams)
|
||||
foreach (ERR_AirPassenger member : team.Members)
|
||||
if (member.Actor == actor && member.Locked && ERR_Actors.Group(actor) == team.Group)
|
||||
return (m_Flight && m_Flight.Phase() < ERR_ReconPhase.PARKED && CompartmentAccessComponent.GetVehicleIn(actor) == m_Aircraft) || (member.Chute && CompartmentAccessComponent.GetVehicleIn(actor) == member.Chute);
|
||||
return false;
|
||||
}
|
||||
void Lock(IEntity actor, bool locked)
|
||||
{ foreach (ERR_AirPassenger member : m_Passengers) if (member.Actor == actor) member.Locked = locked;
|
||||
}
|
||||
bool Begin(ERR_AirDropOrder order, float now)
|
||||
{
|
||||
if (!Ready() || !order || now >= order.ExpiresAt) return false;
|
||||
vector radial = m_Aircraft.GetOrigin() - order.Target;
|
||||
radial[1] = 0;
|
||||
float heading = Math.Atan2(radial[0], radial[2]);
|
||||
bool found;
|
||||
int surveyed;
|
||||
map<string, int> rejected = new map<string, int>();
|
||||
// Several distances and both runway directions provide alternatives around
|
||||
// woodland while retaining every landing/descent safety check.
|
||||
for (int attempt = 0; attempt < 72; attempt++)
|
||||
{
|
||||
int bearing = attempt % 12;
|
||||
int ring = Math.Floor(attempt / 24.0);
|
||||
float radius = 300 + ring * 200;
|
||||
float angle = heading + bearing * Math.PI / 6;
|
||||
radial = Vector(Math.Sin(angle), 0, Math.Cos(angle));
|
||||
m_Center = ERR_Actors.Ground(order.Target + radial * radius);
|
||||
m_Direction = Vector(-radial[2], 0, radial[0]);
|
||||
if ((attempt % 24) >= 12) m_Direction = radial;
|
||||
surveyed++;
|
||||
if (ERR_AirDropGeometry.LandingLine(m_Center, m_Direction)) { found = true; break; }
|
||||
int count; rejected.Find(ERR_AirDropGeometry.LastFailure, count);
|
||||
rejected.Set(ERR_AirDropGeometry.LastFailure, count + 1);
|
||||
}
|
||||
string summary;
|
||||
foreach (string reason, int failures : rejected) summary += reason + "=" + failures.ToString() + "; ";
|
||||
ERR_HQ.Event("AIR DROP SURVEY", string.Format("target=%1 checked=%2 accepted=%3 center=%4; %5", order.Target, surveyed, found, m_Center, summary));
|
||||
if (!found) return false;
|
||||
m_RunStart = m_Center - m_Direction * 350;
|
||||
m_RunEnd = m_Center + m_Direction * 450;
|
||||
m_CoverUntil = now + 300000;
|
||||
m_Order = order;
|
||||
m_Used = true;
|
||||
m_Approaching = true;
|
||||
m_RunCommitted = false;
|
||||
m_NextPassenger = 0;
|
||||
m_NextJump = now;
|
||||
foreach (ERR_AirAssaultTeam team : m_Teams) team.Order = order;
|
||||
m_AirspaceUntil = order.ExpiresAt + 150000;
|
||||
return true;
|
||||
}
|
||||
bool Approach(out vector point)
|
||||
{
|
||||
point = m_RunStart;
|
||||
if (m_RunCommitted) point = m_RunEnd;
|
||||
return m_Approaching && !m_Stopped;
|
||||
}
|
||||
void Abort()
|
||||
{ if (m_Approaching && m_Order) m_CoverUntil = GetGame().GetWorld().GetWorldTime() + 300000;
|
||||
m_Approaching = false;
|
||||
m_RunCommitted = false;
|
||||
}
|
||||
bool BlocksMortar(vector impact, float now)
|
||||
{
|
||||
if (!m_Order || vector.DistanceXZ(impact, m_Order.Target) >= 1200) return false;
|
||||
foreach (ERR_AirPassenger member : m_Passengers)
|
||||
if (member.Chute && member.Actor && CompartmentAccessComponent.GetVehicleIn(member.Actor) == member.Chute) return true;
|
||||
return now < m_AirspaceUntil;
|
||||
}
|
||||
void Update(float now)
|
||||
{
|
||||
if (m_Stopped || !Replication.IsServer() || !m_Mode.Active()) return;
|
||||
foreach (ERR_AirAssaultTeam team : m_Teams) team.Update();
|
||||
if (Preparing()) { Boarding(now);
|
||||
return;
|
||||
}
|
||||
if (!m_Approaching) return;
|
||||
if (!m_Flight || m_Flight.Lost() || m_Flight.Phase() >= ERR_ReconPhase.RETURNING || !ERR_Actors.Alive(m_Aircraft) || now >= m_Order.ExpiresAt)
|
||||
{ Abort();
|
||||
ERR_HQ.Event("AIR DROP ABORTED", "authorization expired or aircraft unavailable; no new passengers created");
|
||||
return;
|
||||
}
|
||||
foreach (ERR_AirAssaultTeam originalTeam : m_Teams)
|
||||
if (!originalTeam.Group || originalTeam.Group.GetPlayerCount(true) > 0 || originalTeam.Group.GetFaction() != m_Mode.EnemyFaction()) { Abort();
|
||||
return;
|
||||
}
|
||||
vector position = m_Aircraft.GetOrigin();
|
||||
if (!m_RunCommitted && vector.DistanceXZ(position, m_RunStart) <= 80) m_RunCommitted = true;
|
||||
if (m_RunCommitted && ERR_AirDropGeometry.Along(position, m_Center, m_Direction) > 300) { Abort();
|
||||
return;
|
||||
}
|
||||
float height = position[1] - SCR_TerrainHelper.GetTerrainY(position);
|
||||
if (!m_RunCommitted || now < m_NextJump || height < 90 || height > 180 || !m_Aircraft.GetPhysics()
|
||||
|| !ERR_AirDropGeometry.Corridor(position, m_Center, m_Direction, m_Aircraft.GetPhysics().GetVelocity())) return;
|
||||
while (m_NextPassenger < m_Passengers.Count())
|
||||
{
|
||||
int index = m_NextPassenger++;
|
||||
ERR_AirPassenger passenger = m_Passengers[index];
|
||||
if (passenger.Jumped || !ERR_Actors.CanFight(passenger.Actor) || ERR_Actors.Player(passenger.Actor) || CompartmentAccessComponent.GetVehicleIn(passenger.Actor) != m_Aircraft) continue;
|
||||
vector exit = m_Aircraft.CoordToParent("12 -4 0");
|
||||
ERR_AssaultParachute chute = ERR_AssaultParachute.Cast(ERR_Actors.Spawn(PARACHUTE, exit));
|
||||
m_NextJump = now + 2000;
|
||||
if (!chute) { ERR_HQ.Event("AIR DROP FAILED", "canopy creation refused; original passenger retained");
|
||||
continue;
|
||||
}
|
||||
m_Mode.Own(chute);
|
||||
passenger.Chute = chute;
|
||||
vector landing = m_Center + m_Direction * ERR_AirDropGeometry.Along(position, m_Center, m_Direction);
|
||||
passenger.Jumped = chute.BeginJump(passenger.Actor, m_Teams[index % 2], landing);
|
||||
ERR_HQ.Event("AIR DROP JUMP", string.Format("original passenger=%1 transfer requested=%2", index, passenger.Jumped));
|
||||
break;
|
||||
}
|
||||
if (m_NextPassenger >= m_Passengers.Count()) Abort();
|
||||
}
|
||||
void Stop()
|
||||
{
|
||||
m_Stopped = true;
|
||||
Abort();
|
||||
foreach (ERR_AirPassenger passenger : m_Passengers) passenger.Locked = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Decisions consume detached HQ memory and delivered friendly check-ins only.
|
||||
class ERR_AirSupportSituation
|
||||
{
|
||||
int BeliefId;
|
||||
int Samples;
|
||||
int Spotted;
|
||||
int GroundFit;
|
||||
int CommittedFit;
|
||||
int ConfirmedLosses;
|
||||
float Age;
|
||||
float Confidence;
|
||||
float Radius;
|
||||
float Speed;
|
||||
bool MotionKnown;
|
||||
bool Infantry;
|
||||
bool AircraftReady;
|
||||
bool ArtilleryBusy;
|
||||
}
|
||||
class ERR_AirSupportPolicy
|
||||
{
|
||||
int Candidate;
|
||||
float ConsideringSince = -1;
|
||||
float NextDrop;
|
||||
string Reason = "waiting for delivered intelligence";
|
||||
static string Refusal(ERR_AirSupportSituation s, bool checkAirspace = true)
|
||||
{
|
||||
if (!s || !s.AircraftReady) return "no ready helicopter passenger load";
|
||||
if (!ERR_HQMemoryRules.Finite(s.Age) || !ERR_HQMemoryRules.Finite(s.Confidence) || !ERR_HQMemoryRules.Finite(s.Radius) || !ERR_HQMemoryRules.Finite(s.Speed)) return "invalid memory sample";
|
||||
if (s.BeliefId <= 0 || s.Samples < 2 || s.Age < 0 || s.Age > 90 || s.Confidence < 0.7 || s.Confidence > 1 || s.Radius < 0 || s.Radius > 160) return "memory is too uncertain; continue reconnaissance";
|
||||
if (!s.Infantry || !s.MotionKnown || s.Speed < 0 || s.Speed > 6) return "target movement does not justify an infantry drop";
|
||||
int needed = Math.Clamp(s.Spotted * 2, 4, 12);
|
||||
if (s.CommittedFit >= needed) return "ground forces already committed in sufficient strength";
|
||||
if (s.GroundFit + s.CommittedFit >= needed && s.ConfirmedLosses < 2) return "nearby ground forces can handle this contact";
|
||||
if (checkAirspace && s.ArtilleryBusy) return "mortar order or shells occupy the approach area";
|
||||
return "";
|
||||
}
|
||||
bool Consider(ERR_AirSupportSituation s, float now)
|
||||
{
|
||||
if (!ERR_HQMemoryRules.Finite(now) || now < 0) return false;
|
||||
// Artillery temporarily closes the approach; it does not invalidate fresh
|
||||
// contact history or the confirmed lack of ground support. Restarting the
|
||||
// timer on every salvo could prevent a drop through every clear interval.
|
||||
string refusal = Refusal(s, false);
|
||||
if (!refusal.IsEmpty()) { Candidate = 0;
|
||||
ConsideringSince = -1;
|
||||
Reason = refusal;
|
||||
return false;
|
||||
}
|
||||
if (now < NextDrop) { Reason = "reinforcement cooldown; retain remaining troops";
|
||||
return false;
|
||||
}
|
||||
if (Candidate != s.BeliefId || ConsideringSince < 0) { Candidate = s.BeliefId;
|
||||
ConsideringSince = now;
|
||||
}
|
||||
if (s.ArtilleryBusy) { Reason = Refusal(s);
|
||||
return false;
|
||||
}
|
||||
float delay = 45000;
|
||||
if (s.ConfirmedLosses >= 2) delay = 20000;
|
||||
if (now - ConsideringSince < delay) { Reason = "confirming persistent contact and ground-force shortfall";
|
||||
return false;
|
||||
}
|
||||
Reason = "persistent infantry contact exceeds available ground response";
|
||||
return true;
|
||||
}
|
||||
void Committed(float now) { NextDrop = now + 480000;
|
||||
Candidate = 0;
|
||||
ConsideringSince = -1;
|
||||
}
|
||||
void Retry(float now) { NextDrop = Math.Max(NextDrop, now + 15000);
|
||||
Candidate = 0;
|
||||
ConsideringSince = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// One immutable authorization; receiver revalidates memory and physical safety.
|
||||
class ERR_AirDropOrder
|
||||
{
|
||||
int BeliefId;
|
||||
int BeliefRevision;
|
||||
int MessageId;
|
||||
vector Target;
|
||||
float ExpiresAt;
|
||||
ref ERR_HQReport Report;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
"""Execute production HQ reinforcement policy against confidence and force scenarios."""
|
||||
from policy_harness import ROOT, run
|
||||
import re
|
||||
source=(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_AirSupportPolicy.c').read_text().split('// One immutable')[0]
|
||||
source=source.replace('class ', 'struct ').replace('\n}', '\n};').replace('ERR_AirSupportSituation s','ERR_AirSupportSituation* s').replace('s.', 's->').replace('ERR_HQMemoryRules.','ERR_HQMemoryRules::').replace('Math.','Math::')
|
||||
source=source.replace('ERR_HQMemoryRules->', 'ERR_HQMemoryRules::')
|
||||
source=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',source)
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
struct string:std::string{using std::string::string;bool IsEmpty(){return empty();}};
|
||||
struct Math {static int Clamp(int x,int a,int b){return std::clamp(x,a,b);}static float Max(float a,float b){return std::max(a,b);}};
|
||||
struct ERR_HQMemoryRules{static bool Finite(float v){return std::isfinite(v);}};
|
||||
'''+source+r'''
|
||||
int main(){
|
||||
ERR_AirSupportSituation s{};s.AircraftReady=true;s.BeliefId=1;s.Samples=3;s.Spotted=3;s.Age=5;s.Confidence=.9;s.Radius=20;s.MotionKnown=true;s.Infantry=true;s.Speed=1;
|
||||
ERR_AirSupportPolicy p;assert(!p.Consider(&s,0));assert(!p.Consider(&s,44999));assert(p.Consider(&s,45000));
|
||||
p.Committed(45000);assert(!p.Consider(&s,50000));assert(!p.Consider(&s,525000));assert(p.Consider(&s,570000));
|
||||
s.CommittedFit=6;assert(!p.Consider(&s,600000));s.CommittedFit=0;s.GroundFit=6;assert(!p.Consider(&s,600000));
|
||||
s.ConfirmedLosses=2;assert(!p.Consider(&s,600000));assert(p.Consider(&s,620000));
|
||||
s.ArtilleryBusy=true;assert(!p.Consider(&s,630000));s.ArtilleryBusy=false;assert(p.Consider(&s,640000));assert(p.Consider(&s,660000));
|
||||
s.BeliefId=2;assert(!p.Consider(&s,670000));assert(!p.Consider(&s,689999));assert(p.Consider(&s,690000));
|
||||
auto good=s;
|
||||
s.Age=91;assert(!p.Consider(&s,700000));s=good;s.Confidence=.69;assert(!p.Consider(&s,700000));
|
||||
s=good;s.Radius=161;assert(!p.Consider(&s,700000));s=good;s.Samples=1;assert(!p.Consider(&s,700000));
|
||||
s=good;s.Infantry=false;assert(!p.Consider(&s,700000));s=good;s.Speed=7;assert(!p.Consider(&s,700000));
|
||||
s=good;s.MotionKnown=false;assert(!p.Consider(&s,700000));s=good;s.AircraftReady=false;assert(!p.Consider(&s,700000));
|
||||
s=good;s.Age=std::numeric_limits<float>::quiet_NaN();assert(!p.Consider(&s,700000));
|
||||
assert(!p.Consider(nullptr,700000));assert(!p.Consider(&good,std::numeric_limits<float>::infinity()));
|
||||
// Artillery blocks authorization, not the history of fresh sightings and
|
||||
// ground-force shortage. Repeated <45-second clear windows used to starve
|
||||
// airborne support by restarting its confirmation timer on every salvo.
|
||||
ERR_AirSupportPolicy blocked;auto contact=good;contact.BeliefId=9;contact.GroundFit=0;contact.ConfirmedLosses=0;
|
||||
assert(!blocked.Consider(&contact,0));
|
||||
contact.ArtilleryBusy=true;
|
||||
assert(!blocked.Consider(&contact,10000));
|
||||
assert(!blocked.Consider(&contact,46000));
|
||||
contact.ArtilleryBusy=false;assert(blocked.Consider(&contact,47000));
|
||||
// Stale intelligence and a changed target still invalidate confirmation.
|
||||
contact.Age=91;assert(!blocked.Consider(&contact,48000));
|
||||
contact.Age=5;assert(!blocked.Consider(&contact,49000));
|
||||
contact.BeliefId=10;assert(!blocked.Consider(&contact,90000));
|
||||
assert(!blocked.Consider(&contact,134999));assert(blocked.Consider(&contact,135000));
|
||||
// Busy airspace must not preconfirm a new drop during the 8-minute cooldown.
|
||||
blocked.Committed(135000);contact.ArtilleryBusy=true;
|
||||
assert(!blocked.Consider(&contact,614999));
|
||||
contact.ArtilleryBusy=false;assert(!blocked.Consider(&contact,615000));
|
||||
assert(!blocked.Consider(&contact,659999));assert(blocked.Consider(&contact,660000));
|
||||
|
||||
}
|
||||
''')
|
||||
print('PASS: production airborne memory confidence, persistence, ground coverage, confirmed losses, artillery veto, cooldown and invalid-input rules')
|
||||
|
||||
from policy_harness import body
|
||||
load=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_AirAssaultLoad.c'
|
||||
cover=body(load,'bool Cover(').replace('out vector center','int& center').replace('m_Order.Target','m_Order->Target')
|
||||
service=body(load,'bool NeedsService(')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
struct Order {int Target=730;};
|
||||
struct Load {
|
||||
Order* m_Order=nullptr;bool m_Used=false,m_Approaching=false;float m_CoverUntil=300000;
|
||||
'''+cover+service+r'''
|
||||
};
|
||||
int main(){
|
||||
Load load;Order order;int center=-1;
|
||||
assert(!load.Cover(0,center)&¢er==-1&&!load.NeedsService(0));
|
||||
load.m_Order=ℴload.m_Used=true;load.m_Approaching=true;
|
||||
assert(!load.Cover(1,center)&&!load.NeedsService(300001));
|
||||
load.m_Approaching=false;assert(load.Cover(299999,center)&¢er==730&&!load.NeedsService(299999));
|
||||
center=-1;assert(!load.Cover(300000,center)&¢er==-1&&load.NeedsService(300000));
|
||||
}
|
||||
''')
|
||||
print('PASS: production cover retains the carried HQ target until its deadline; airport reload waits until the drop pass and cover have finished')
|
||||
Reference in New Issue
Block a user