// Unqualified 100m field-route prototype, admitted by the typed service only. // No allocation or HQ dispatch; the shared registry/bridge owns native inputs. class ERR_AircraftFieldFlight : ERR_AircraftFlight { protected ref ERR_AircraftFieldEnvelope m_Field; protected int m_FieldPhase; // 0 startup, 1 cruise, 2 braking, 3 slow settling protected vector m_FieldReference; protected vector m_FieldIntegral; protected vector m_FieldLandingPoint; protected bool m_FieldStopFrozen; protected bool m_FieldArrived; protected float m_FieldLowSince = -1; int FieldPhase() { return m_FieldPhase; } bool Arrived() { return m_FieldArrived; } vector LandingPoint() { return m_FieldLandingPoint; } vector ReferenceVelocity() { return m_FieldReference; } vector IntegralDemand() { return m_FieldIntegral; } vector Destination() { vector result = "0 0 0"; if (m_Field) result = m_Field.Destination(); return result; } // Candidate native fuel quantities, not a demonstrated range/reserve model. protected bool FuelAdmission(Vehicle aircraft) { if (!aircraft) return false; FuelManagerComponent fuel = FuelManagerComponent.Cast(aircraft.FindComponent(FuelManagerComponent)); return fuel && fuel.HasFuel() && fuel.GetTotalFuel() >= 50; } bool StartField(ERR_GameMode mode, Vehicle aircraft, SCR_AIGroup group, IEntity pilot, PilotCompartmentSlot seat, ERR_AircraftFieldEnvelope field, int token, float deadline) { if (m_StartAttempted || m_Field || !field || !ERR_AircraftFieldRules.Length(vector.DistanceXZ(field.Home(), field.Destination()))) return false; if (!ERR_AircraftFieldRules.LeaseAdmitted(Now(), deadline) || !FuelAdmission(aircraft)) return false; m_Field = field; m_TranslationRequested = true; vector home = field.Home(); m_TranslationTarget = field.Destination(); m_TranslationTarget[1] = home[1] + 8; m_FieldLandingPoint = field.Home(); return super.Start(mode, aircraft, group, pilot, seat, field, token, deadline); } override bool Start(ERR_GameMode mode, Vehicle aircraft, SCR_AIGroup group, IEntity pilot, PilotCompartmentSlot seat, ERR_AircraftFlightEnvelope qualification, int token, float deadline) { return false; } override bool StartTranslation(ERR_GameMode mode, Vehicle aircraft, SCR_AIGroup group, IEntity pilot, PilotCompartmentSlot seat, ERR_AircraftFlightEnvelope qualification, int token, float deadline, vector target) { return false; } protected bool FieldContains(vector position, float upright) { if (!m_Field || !m_Field.Contains(position, upright)) return false; if (m_FieldPhase == 0) return vector.DistanceXZ(position, m_Home) < 8; return true; } protected override bool Grounded() { if (!super.Grounded()) return false; if (m_State == ERR_AircraftFlightState.LANDING || m_State == ERR_AircraftFlightState.SHUTDOWN) return vector.DistanceXZ(m_Aircraft.GetOrigin(), m_FieldLandingPoint) <= 0.75; return true; } // A normal arrival and an airborne cancellation both brake before descent. // Once selected, the landing centre and braking deadline cannot be renewed. protected bool BeginFieldBrake(float now) { if (m_FieldStopFrozen) return true; if (!Owns() || !m_Field || !Healthy()) { Fail("field braking ownership or capability unavailable"); return false; } if (m_Simulation.HasAnyGroundContact()) { Fail("field airborne braking refused ground contact"); return false; } vector point = m_Field.Destination(); if (m_Cancelled) { point = m_Aircraft.GetOrigin(); vector velocity = m_Simulation.GetVelocity(); velocity[1] = 0; float speed = velocity.Length(); if (speed >= 0.25) point = point + velocity * (ERR_AircraftFieldRules.BrakingReserve(speed) / speed); } point[1] = m_Home[1]; if (!m_Field.Contains(point, 1)) { Fail("field stopping centre outside original qualified corridor"); return false; } // Cancel can arrive while airborne during the signed collective checks, // before shared startup enables autohover. Match the base landing handshake. m_Controller.SetAutohoverEnabled(true); if (!m_Controller.GetAutohoverEnabled()) { Fail("native field braking autohover enable refused"); return false; } m_FieldLandingPoint = point; m_FieldStopFrozen = true; // Remove cruise integral once; retain the bounded vector slew toward rest. // Repeated cancellation must not reset this state or the frozen deadline. m_FieldIntegral = "0 0 0"; m_FieldPhase = 2; m_State = ERR_AircraftFlightState.TRANSLATING; m_FieldLowSince = -1; m_Stable = -1; // Includes braking, two low-speed seconds and five seconds of final hold. m_StageDeadline = Math.Min(m_Deadline, now + 45000); PrintFormat("ERR AIRCRAFT FIELD BRAKE: token=%1 cancelled=%2 centre=%3 deadline=%4 originalDeadline=%5", m_Token, m_Cancelled, point, m_StageDeadline, m_Deadline); return true; } protected override bool Landing(float now) { if (!Owns() || !m_Field) { Relinquish("field landing lost original ownership"); return false; } if (m_Cancelled) { if (m_FieldPhase != 3) return BeginFieldBrake(now); if (m_Stable < 0 || now - m_Stable < 5000) return true; // retain the current settle/hold budget } if (!m_FieldStopFrozen || m_FieldPhase != 3 || m_Stable < 0 || now - m_Stable < 5000) { Fail("field descent requires observed braking and destination hold"); return false; } if (!super.Landing(now)) return false; m_StageDeadline = Math.Min(m_Deadline, now + 25000); return true; } protected override bool BeginTranslation(float now, vector position) { if (!m_Field || !m_TranslationRequested || m_TranslationStarted || m_State != ERR_AircraftFlightState.CLIMB) { Cancel("field stage ownership refused"); return false; } SignalsManagerComponent signals = m_Controller.GetSignalsManager(); if (!signals || signals.FindSignal("MainRotorCyclicForward") < 0 || signals.FindSignal("MainRotorCyclicAside") < 0 || !m_Controller.GetAutohoverEnabled()) { Cancel("native field cyclic signals unavailable"); return false; } m_TranslationOrigin = position; m_TranslationDistance = vector.DistanceXZ(position, m_Field.Destination()); m_TranslationStarted = true; m_State = ERR_AircraftFlightState.TRANSLATING; m_FieldPhase = 1; m_FieldReference = "0 0 0"; m_FieldIntegral = "0 0 0"; m_Stable = -1; m_StageDeadline = Math.Min(m_Deadline, now + 160000); PrintFormat("ERR AIRCRAFT FIELD BEGIN: token=%1 home=%2 destination=%3 actualOrigin=%4 deadline=%5", m_Token, m_Home, m_Field.Destination(), position, m_Deadline); return true; } protected override bool ControlLease() { if (!Lease()) return false; if (!Owns()) { Relinquish("field callback lost original ownership"); return false; } float now = Now(); if (now < 0 || now >= m_Deadline || now >= m_StageDeadline) { Fail("bounded field stage expired"); return false; } if (!Healthy() || !m_Readback) { Fail("field capability or native input readback lost"); return false; } if (m_FieldPhase == 2 && m_Simulation.HasAnyGroundContact()) { Fail("field ground contact during airborne braking"); return false; } vector transform[4]; m_Aircraft.GetWorldTransform(transform); if (!FieldContains(transform[3], transform[1][1]) || !ERR_AircraftFieldRules.SpeedAllowed(m_Simulation.GetVelocity())) { Fail("qualified field position, attitude or speed exceeded"); return false; } return true; } protected vector FieldDemand(vector position, vector velocity, float dt) { vector target = m_Field.Destination(); if (m_FieldStopFrozen) target = m_FieldLandingPoint; if (m_FieldPhase == 3 || m_State == ERR_AircraftFlightState.LANDING) return ERR_AircraftTranslationRules.Demand(target, position, velocity); vector direction = target - position; direction[1] = 0; float remaining = direction.Length(); vector desired = "0 0 0"; // Braking targets rest immediately, independently of distance or bearing. // A lagging native airframe can still accelerate while the reference slews; // only native measurements can qualify the declared actual-speed reserve. if (m_FieldPhase != 2 && remaining > 0.001) desired = direction * (2 / remaining); m_FieldReference = ERR_AircraftFieldRules.RampReference(m_FieldReference, desired, dt); vector nextIntegral; vector demand = ERR_AircraftFieldRules.VelocityDemand(m_FieldReference, velocity, m_FieldIntegral, dt, nextIntegral); m_FieldIntegral = nextIntegral; return demand; } override void PrepareActions(CharacterControllerComponent controller, IEntity owner, ActionManager actions, float dt) { if (m_State != ERR_AircraftFlightState.TRANSLATING && m_State != ERR_AircraftFlightState.LANDING) { super.PrepareActions(controller, owner, actions, dt); return; } if (!Lease() || controller != m_Character || owner != m_Pilot || actions != m_Actions || !ControlLease()) return; if (!m_Readback || !ERR_AircraftFieldRules.ValidStep(dt) || dt > 0.2) return; if (!actions.IsContextActive("HelicopterContext")) actions.ActivateContext("HelicopterContext", 1); if (!actions.IsContextActive("HelicopterContext")) { m_Readback = false; return; } if (!m_Controller.GetAutohoverEnabled()) { Fail("native field autohover lost"); return; } vector position = m_Aircraft.GetOrigin(); vector velocity = m_Simulation.GetVelocity(); float height = position[1] - m_Home[1]; float error = ERR_AircraftFlightRules.Vertical(height, m_State == ERR_AircraftFlightState.LANDING) - velocity[1]; m_Collective = Math.Clamp(m_Collective + error * dt * 0.12, -0.95, 0.8); float collective = Math.Clamp(m_Collective + error * 0.12, -1, 0.9); float headingError = m_Heading - m_Aircraft.GetYawPitchRoll()[0]; while (headingError > 180) headingError -= 360; while (headingError < -180) headingError += 360; vector angular = m_Simulation.GetAngularVelocityLocal(); float torque = Math.Clamp(headingError * 0.01 - angular[1] * 0.3, -0.3, 0.3); vector transform[4]; m_Aircraft.GetWorldTransform(transform); vector demand = FieldDemand(transform[3], m_Simulation.GetVelocity(), dt); float limit = 0.08; if (m_FieldPhase == 3 || m_State == ERR_AircraftFlightState.LANDING) limit = 0.03; float forward = Math.Clamp(vector.Dot(demand, transform[2]), -limit, limit); float aside = Math.Clamp(vector.Dot(demand, transform[0]), -limit, limit); if (m_State == ERR_AircraftFlightState.LANDING && Grounded()) { collective = -1; forward = 0; aside = 0; } bool a = Pair("HelicopterCollectiveIncrease", "HelicopterCollectiveDecrease", collective); bool b = Pair("HelicopterCyclicForward", "HelicopterCyclicBack", forward); bool c = Pair("HelicopterCyclicRight", "HelicopterCyclicLeft", aside); bool d = Pair("HelicopterAntiTorqueRight", "HelicopterAntiTorqueLeft", torque); m_Readback = a && b && c && d; m_ActionUpdates++; m_LastControl = Now(); if (m_State == ERR_AircraftFlightState.TRANSLATING) m_TranslationUpdates++; } protected override void CheckTranslation(float now, vector position, float height, float speed, float upright) { if (!m_Field || !m_Controller.GetAutohoverEnabled()) { Fail("field route or autohover unavailable"); return; } vector velocity = m_Simulation.GetVelocity(); float horizontal = ERR_AircraftFieldRules.HorizontalSpeed(velocity); if (m_FieldPhase == 1) { if (ERR_AircraftFieldRules.ShouldBrake(vector.DistanceXZ(position, m_Field.Destination()), horizontal)) BeginFieldBrake(now); return; } if (m_FieldPhase == 2) { if (m_Simulation.HasAnyGroundContact()) { Fail("field ground contact during airborne braking"); return; } if (!(horizontal < 0.25)) m_FieldLowSince = -1; else if (m_FieldLowSince < 0) m_FieldLowSince = now; if (!ERR_AircraftFieldRules.LowSpeedHeld(horizontal, m_FieldLowSince, now) || !(m_FieldReference.Length() <= 0.01)) return; m_FieldPhase = 3; m_FieldReference = "0 0 0"; m_FieldIntegral = "0 0 0"; m_Stable = -1; // No new deadline: slow positioning/hold belongs to the same braking budget. } bool held = m_FieldPhase == 3 && !m_Simulation.HasAnyGroundContact() && ERR_AircraftFlightRules.Hover(height, speed, velocity[1], upright) && horizontal < 0.25 && vector.DistanceXZ(position, m_FieldLandingPoint) <= 0.75; if (!held) { m_Stable = -1; return; } if (m_Stable < 0) m_Stable = now; if (now - m_Stable < 5000) return; if (!m_Cancelled) { m_FieldArrived = true; m_TranslationProven = true; } Landing(now); } override void Tick() { if (!ControlLease()) return; float now = Now(); // The owner must continue Tick even with no players. Do not transition an // inherited startup callback into a field phase halfway through native input. // 86.5s soft threshold reserves the 85s phase budget plus the maximum // accepted 1.5s observation gap. Hard stage/original deadlines never move. bool reserveAbort = ERR_AircraftFieldRules.AbortDue(now + 1500, m_Deadline); bool fuelAbort = !m_Fuel || !(m_Fuel.GetTotalFuel() > 20); if (!m_Cancelled && !m_FieldArrived && m_State != ERR_AircraftFlightState.LANDING && m_State != ERR_AircraftFlightState.SHUTDOWN && (reserveAbort || fuelAbort)) { string reason = "field remaining lease reserved for braking and landing"; if (fuelAbort) reason = "field candidate low-fuel reserve reached"; Cancel(reason); if (Finished()) return; } // The stock-independent shared startup includes native signed collective // consumption, engine/fuel proof and five-second hover without duplication. if (m_State != ERR_AircraftFlightState.TRANSLATING && m_State != ERR_AircraftFlightState.LANDING && m_State != ERR_AircraftFlightState.SHUTDOWN) { super.Tick(); return; } if (m_LastTick >= 0 && (now < m_LastTick || now - m_LastTick > 1500)) { m_Stable = -1; m_FieldLowSince = -1; } m_LastTick = now; if (m_LastControl < 0 || now - m_LastControl > 1000) { Fail("field pilot callback stopped"); return; } if (m_State != ERR_AircraftFlightState.SHUTDOWN && !m_Controller.IsEngineOn()) { Fail("field running engine lost"); return; } vector transform[4]; m_Aircraft.GetWorldTransform(transform); if (m_State == ERR_AircraftFlightState.TRANSLATING) { CheckTranslation(now, transform[3], transform[3][1] - m_Home[1], m_Simulation.GetVelocity().Length(), transform[1][1]); return; } if (m_State == ERR_AircraftFlightState.LANDING) { if (!m_Controller.GetAutohoverEnabled()) { Fail("field landing autohover lost"); return; } if (!Grounded()) m_Stable = -1; else if (m_Stable < 0) m_Stable = now; if (m_Stable >= 0 && now - m_Stable >= 2000) Shutdown(now); return; } if (m_State == ERR_AircraftFlightState.SHUTDOWN && !m_Controller.IsEngineOn()) { if (!Grounded()) { Fail("field engine-off physical ground check failed"); return; } if (m_Cancelled) m_State = ERR_AircraftFlightState.CANCELLED; else m_State = ERR_AircraftFlightState.COMPLETE; Release(true); RecordState(); } } }