Harden participant and map ownership and validate bounded physical flight

This commit is contained in:
2026-09-19 12:17:01 +02:00
parent dfd7cc49e2
commit 4125018d04
120 changed files with 20043 additions and 47 deletions
@@ -182,11 +182,13 @@ modded class ERR_GameMode
Print("ERR SMOKE COMPLETE: actual gate destruction and sampled prison crossing; repositioned fixture, not walking or multiplayer");
return;
}
if (m_ERRExtractionCase == "powered-flight")
if (m_ERRExtractionCase == "powered-flight" || m_ERRExtractionCase == "powered-translate")
{
if (!ERR_BaseCheck(current == ERR_Phase.DEFEAT && m_ERRBaseStep == 1802, "native powered-flight fixture finished before explicit test reset")) return;
ERR_StopHealthProbes();
Print("ERR SMOKE COMPLETE: native pilot-input powered takeoff, hover, contact landing and shutdown; isolated mechanism fixture reset");
if (m_ERRExtractionCase == "powered-translate")
Print("ERR SMOKE COMPLETE: native powered takeoff, bounded four-metre translation, target hold, contact landing and engine-off; isolated mechanism fixture reset");
else Print("ERR SMOKE COMPLETE: native pilot-input powered takeoff, hover, contact landing and shutdown; isolated mechanism fixture reset");
return;
}
if (m_ERRExtractionCase == "local-admission")
@@ -515,7 +517,7 @@ modded class ERR_GameMode
m_ERRBaseStep = 1900;
return;
}
if (m_ERRExtractionCase == "powered-flight")
if (m_ERRExtractionCase == "powered-flight" || m_ERRExtractionCase == "powered-translate")
{
DamageManagerComponent.Cast(m_ERRBasePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
m_ERRPoweredFlightProbe = new ERR_NativePoweredFlightProbe(this);
@@ -115,6 +115,42 @@ class ERR_PoweredFlightRules
{
return contact && height < 1 && height > -1 && speed < 0.5 && angular < 0.1 && upright > 0.98;
}
static bool TranslationInputs(bool selected, bool actions, bool autohover)
{
return !selected || (actions && autohover);
}
static bool TranslationActive(bool selected, bool frozen, bool actions, bool autohover, int stage)
{
return selected && frozen && actions && autohover && stage == 5;
}
static bool TranslationTarget(vector home, vector start, vector fallback, out vector target)
{
target = vector.Zero;
if (!(vector.DistanceXZ(home, start) < 8)) return false;
vector direction = home - start; direction[1] = 0;
if (direction.Length() < 0.5) { direction = fallback; direction[1] = 0; }
float length = direction.Length();
if (!(length >= 0.5)) return false;
target = start + direction * (4 / length);
return vector.DistanceXZ(start, target) >= 3.99 && vector.DistanceXZ(start, target) <= 4.01 && vector.DistanceXZ(home, target) <= 5;
}
static vector TranslationDemand(vector target, vector position, vector velocity)
{
vector error = target - position; error[1] = 0;
vector desired = error * 0.25;
float speed = desired.Length();
if (speed > 0.5) desired = desired * (0.5 / speed);
velocity[1] = 0;
// Experimental outer velocity request only; native autohover stabilizes attitude.
vector demand = (desired - velocity) * 0.05;
float magnitude = demand.Length();
if (magnitude > 0.03) demand = demand * (0.03 / magnitude);
return demand;
}
static bool TranslationHeld(bool contact, float height, float speed, float vertical, float upright, float drift, float horizontalSpeed, float targetError, float displacement)
{
return !contact && Hover(height, speed, vertical, upright, drift) && horizontalSpeed >= 0 && horizontalSpeed < 0.25 && targetError >= 0 && targetError <= 0.75 && displacement >= 3.25;
}
}
class ERR_NativePoweredFlightProbe
{
@@ -167,6 +203,12 @@ class ERR_NativePoweredFlightProbe
protected bool m_ActionInputs;
protected bool m_Started;
protected bool m_AutohoverMode;
protected bool m_TranslationMode;
protected bool m_TranslationFrozen;
protected bool m_TranslationProven;
protected vector m_TranslationOrigin;
protected vector m_TranslationTarget;
protected int m_TranslationUpdates;
protected bool m_AutohoverSaved;
protected bool m_OriginalAutohover;
protected HelicopterControllerComponent m_AutohoverController;
@@ -178,13 +220,15 @@ class ERR_NativePoweredFlightProbe
static bool Enabled()
{
string enabled, outcome;
return Replication.IsServer() && System.GetCLIParam("errBaseSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "powered-flight";
return Replication.IsServer() && System.GetCLIParam("errBaseSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && (outcome == "powered-flight" || outcome == "powered-translate");
}
static ERR_NativePoweredFlightProbe Current() { return s_Current; }
void ERR_NativePoweredFlightProbe(ERR_GameMode mode)
{
string actionInputs;
m_ActionInputs = System.GetCLIParam("errFlightActionInputs", actionInputs) && actionInputs == "1";
string outcome;
m_TranslationMode = System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "powered-translate";
m_Mode = mode; m_Deadline = GetGame().GetWorld().GetWorldTime() + 240000;
if (Enabled() && !s_Current) s_Current = this;
else m_Stopped = true;
@@ -299,6 +343,13 @@ class ERR_NativePoweredFlightProbe
// Give its lateral controller zero custom cyclic in this experiment.
// Preserve collective/yaw and bounds; never combine two cyclic controllers.
if (m_AutohoverMode) { forward = 0; aside = 0; }
if (ERR_PoweredFlightRules.TranslationActive(m_TranslationMode, m_TranslationFrozen, m_ActionInputs, m_AutohoverMode, m_Stage))
{
vector demand = ERR_PoweredFlightRules.TranslationDemand(m_TranslationTarget, transform[3], velocity);
forward = ERR_PoweredFlightRules.ClampInput(vector.Dot(demand, transform[2]), 0.03);
aside = ERR_PoweredFlightRules.ClampInput(vector.Dot(demand, transform[0]), 0.03);
m_TranslationUpdates++;
}
float headingError = m_Heading - m_Aircraft.GetYawPitchRoll()[0];
while (headingError > 180) headingError -= 360;
while (headingError < -180) headingError += 360;
@@ -415,6 +466,7 @@ class ERR_NativePoweredFlightProbe
if (!m_Simulation || !m_Aircraft || now < m_NextLog) return;
m_NextLog = now + 2000;
if (m_Stage == 4 && now < m_StageDeadline - 57000) m_NextLog = now + 250;
if (m_TranslationMode && m_Stage == 5) m_NextLog = now + 500;
if (m_ActionInputs)
{
bool ownedActions = OwnActionManager(m_OwnedActions);
@@ -429,6 +481,22 @@ class ERR_NativePoweredFlightProbe
PrintFormat("ERR POWERED ROTOR AXIS: local=%1 acceleration=%2", rotorAxis, m_Simulation.GetAcceleration());
PrintFormat("ERR POWERED MOTION: offset=%1 velocity=%2 angularLocal=%3 forward=%4 right=%5", m_Home - transform[3], m_Simulation.GetVelocity(), m_Simulation.GetAngularVelocityLocal(), transform[2], transform[0]);
PrintFormat("ERR POWERED INPUT: pilotHooks=%1 vehicleHooks=%2 readback=%3 collective=%4 cyclicF=%5 cyclicR=%6 antiTorque=%7 rotorRPM=%8 targetRPM=%9", m_ControlUpdates, m_HelicopterUpdates, m_ReadbackOK, m_Input.GetVehicleCollective(), m_Input.GetVehicleCyclicForward(), m_Input.GetVehicleCyclicAside(), m_Input.GetVehicleAntiTorque(), m_Simulation.RotorGetRPM(0), m_Simulation.RotorGetRPMTarget(0));
if (m_TranslationMode && m_TranslationFrozen)
{
vector direction = m_TranslationTarget - m_TranslationOrigin; direction[1] = 0; direction = direction * 0.25;
vector travel = transform[3] - m_TranslationOrigin; travel[1] = 0;
float along = vector.Dot(travel, direction);
vector cross = travel - direction * along;
vector horizontal = m_Simulation.GetVelocity(); horizontal[1] = 0;
PrintFormat("ERR POWERED TRANSLATION: stage=%1 origin=%2 target=%3 position=%4 along=%5 cross=%6 error=%7 horizontalSpeed=%8 updates=%9", m_Stage, m_TranslationOrigin, m_TranslationTarget, transform[3], along, cross.Length(), vector.DistanceXZ(transform[3], m_TranslationTarget), horizontal.Length(), m_TranslationUpdates);
SignalsManagerComponent signals = m_Controller.GetSignalsManager();
if (signals)
{
int cyclicForward = signals.FindSignal("MainRotorCyclicForward");
int cyclicAside = signals.FindSignal("MainRotorCyclicAside");
if (cyclicForward >= 0 && cyclicAside >= 0) PrintFormat("ERR POWERED TRANSLATION SIGNALS: pilotForward=%1 pilotAside=%2 rotorForward=%3 rotorAside=%4 (native autohover may modify rotor commands)", m_Input.GetVehicleCyclicForward(), m_Input.GetVehicleCyclicAside(), signals.GetSignalValue(cyclicForward), signals.GetSignalValue(cyclicAside));
}
}
}
protected bool PollNativeFlightComponents(float now)
{
@@ -533,12 +601,42 @@ class ERR_NativePoweredFlightProbe
// Keep real airborne/failed test actors for diagnosis and mode-owned cleanup.
// Stop never repairs, stabilizes, teleports, refuels or replaces them.
}
protected bool BeginTranslation(float now, vector position, vector fallback)
{
if (!Check(m_TranslationMode && !m_TranslationFrozen && m_Stage == 4 && m_ActionInputs && m_AutohoverMode && AutohoverReady(), "translation starts once after the original native hover stage")) return false;
vector target;
if (!Check(ERR_PoweredFlightRules.TranslationTarget(m_Home, position, fallback, target), "frozen four-metre horizontal target remains within five metres of original home")) return false;
SignalsManagerComponent signals = m_Controller.GetSignalsManager();
if (!Check(signals && signals.FindSignal("MainRotorCyclicForward") >= 0 && signals.FindSignal("MainRotorCyclicAside") >= 0, "existing native cyclic signals available for translation observation")) return false;
m_TranslationOrigin = position;
m_TranslationTarget = target;
m_TranslationFrozen = true;
m_Stage = 5; m_StageDeadline = now + 45000; m_Stable = -1; m_NextLog = 0;
PrintFormat("ERR POWERED TRANSLATION BEGIN: origin=%1 target=%2 originalHome=%3 deadline=%4 overallDeadline=%5", m_TranslationOrigin, m_TranslationTarget, m_Home, m_StageDeadline, m_Deadline);
return true;
}
protected bool CheckTranslation(float now, vector position, float height, float speed, float upright, float drift)
{
if (!ERR_PoweredFlightRules.TranslationActive(m_TranslationMode, m_TranslationFrozen, m_ActionInputs, m_AutohoverMode, m_Stage)) return Check(false, "translation retains its selected inputs and frozen stage ownership");
if (now >= m_StageDeadline) return Check(false, "four-metre translation and five-second target hold finish within 45 seconds");
vector velocity = m_Simulation.GetVelocity();
vector horizontal = velocity; horizontal[1] = 0;
bool held = m_TranslationUpdates >= 3 && ERR_PoweredFlightRules.TranslationHeld(m_Simulation.HasAnyGroundContact(), height, speed, velocity[1], upright, drift, horizontal.Length(), vector.DistanceXZ(position, m_TranslationTarget), vector.DistanceXZ(position, m_TranslationOrigin));
if (!held) m_Stable = -1;
if (held && m_Stable < 0) m_Stable = now;
if (m_Stable < 0 || now - m_Stable < 5000) return true;
if (!Check(true, "actual horizontal translation reaches frozen target and holds five seconds at 7–9m with horizontal speed below 0.25m/s")) return false;
m_TranslationProven = true;
m_Stage = 6; m_StageDeadline = now + 65000; m_Stable = -1;
return true;
}
int Tick(float now)
{
if (m_Complete) return 1;
if (m_Stopped) return -1;
m_Started = true;
if (!Enabled() || !m_Mode || now >= m_Deadline || ERR_MissionRules.Terminal(m_Mode.Phase())) { Check(false, "server fixture active within 240-second bound"); return -1; }
if (!ERR_PoweredFlightRules.TranslationInputs(m_TranslationMode, m_ActionInputs, m_AutohoverMode)) { Check(false, "translation requires actor-local actions and selected native autohover before allocation"); return -1; }
if (m_PreviousTick >= 0 && (now < m_PreviousTick || now - m_PreviousTick > 1500)) { m_Stable = -1; m_PositiveSignalSince = -1; m_NegativeSignalSince = -1; }
m_PreviousTick = now;
if (m_Stage == 0)
@@ -672,6 +770,11 @@ class ERR_NativePoweredFlightProbe
if (!Check(BeginAutohover(), "selected native autohover enabled only after both direct collective consumption proofs")) return -1;
m_Collective = -0.8; m_Stage = 4; m_StageDeadline = now + 65000; m_Stable = -1; return 0;
}
if (m_TranslationMode && m_Stage == 5)
{
if (!CheckTranslation(now, transform[3], height, speed, transform[1][1], drift)) return -1;
return 0;
}
if (m_Stage == 4 || m_Stage == 5)
{
bool hover = !m_Simulation.HasAnyGroundContact() && ERR_PoweredFlightRules.Hover(height, speed, m_Simulation.GetVelocity()[1], transform[1][1], drift);
@@ -681,12 +784,18 @@ class ERR_NativePoweredFlightProbe
if (m_Stable >= 0 && now - m_Stable >= 5000)
{
if (!Check(m_ControlUpdates > 100 && m_Fuel.GetTotalFuel() < m_InitialFuel - 0.05, "actual 7–9 metre powered hover stable five seconds with native fuel consumption")) return -1;
if (m_TranslationMode)
{
if (!BeginTranslation(now, transform[3], transform[0])) return -1;
return 0;
}
m_Stage = 6; m_StageDeadline = now + 65000; m_Stable = -1;
}
return 0;
}
if (m_Stage == 6)
{
if (m_TranslationMode && !m_TranslationProven) { Check(false, "translation reaches its frozen target before landing"); return -1; }
bool landed = ERR_PoweredFlightRules.Landed(m_Simulation.HasAnyGroundContact(), height, speed, m_Simulation.GetAngularVelocity().Length(), transform[1][1]);
if (!landed) m_Stable = -1;
if (landed && m_Stable < 0) m_Stable = now;
+1 -1
View File
@@ -145,7 +145,7 @@ modded class SCR_PlayerController
ERR_Actors.Players(survivors);
Faction friendly = mode.PlayerFaction();
for (int i = survivors.Count() - 1; i >= 0; i--)
if (SCR_FactionManager.SGetFaction(survivors[i]) != friendly) survivors.RemoveOrdered(i);
if (!friendly || !mode.Players() || !mode.Players().CurrentActor(survivors[i]) || SCR_FactionManager.SGetFaction(survivors[i]) != friendly) survivors.RemoveOrdered(i);
int index = survivors.Find(m_ERRServerObserved);
if (index < 0) index = 0;
else index += direction;
@@ -142,7 +142,8 @@ class ERR_SpawnLogic : SCR_SpawnLogic
ERR_Actors.Players(survivors, true);
foreach (IEntity survivor : survivors)
{
if (SCR_FactionManager.SGetFaction(survivor) != mode.PlayerFaction()) continue;
// A controlled replacement or unfinished preload is not a round survivor.
if (!mode.Players() || !mode.Players().CurrentActor(survivor) || !mode.PlayerFaction() || SCR_FactionManager.SGetFaction(survivor) != mode.PlayerFaction()) continue;
if (m_Placement.Near(survivor, m_AuthorizedPositions, position)) return true;
}
return m_Placement.Prison(mode.PrisonPosition(), m_AuthorizedPositions, position);