Skip to content

Architecture

PFC is a custom flight model layered on top of Enfusion's rigid-body simulation. The engine integrates the rigid body; PFC's scripts apply per-surface lift/drag and thrust via Physics.ApplyImpulseAt, plus angular-rate damping to keep the airframe stable.

Built on Wheeled_Base.et

The reference Cessna inherits the vanilla Wheeled_Base.et. The wheeled simulation is neutralized - only the suspension and wheel-bone visuals remain - so the inherited SCR_CarControllerComponent still handles ownership transfer and grants the input context (CarContext) that the flight controller reads.

Flying on a car base has sharp edges

The vanilla car controls share keys with flight: the handbrake stays engaged because CarThrust is 0, CarBrake is on S (= pitch back), and CarShift is on Q/E (= yaw). PFC neutralizes all of these every frame. See Prefab Setup for the full list of wheeled-sim settings that must be right, and Input & Controls for the key de-conflicting.

Components

Component Responsibility
PFC_FlightModel The flight model. Builds aero surfaces, applies per-surface lift/drag + thrust impulses, spools engine RPM, angular damping, wind/gusts, publishes instrument signals, debug draw. Runs on owner + server.
PFC_FlightController Reads the shared Airplane_* pilot input from CarContext, smooths it (slew-rate limited), exposes pitch/roll/yaw/throttle to the flight model, relays input to the server, and injects ground steering.
PFC_FreeLookController Device-aware cockpit free look for the local pilot: mouse & keyboard always free-look, gamepad pans only while the Freelook modifier is held and keeps the released angle. Needs ForcedFreeLook 1 on the pilot slot.
PFC_NwkMovementComponent Replicates owner→proxy movement state (transform + velocity) so remote viewers see smooth motion.
PFC_AeroSurface A single aerodynamic panel: given local airflow + air density, returns the lift+drag force. Pure math, no engine state.
PFC_AeroSurfaceConfig Runtime aero parameters for one surface (chord, span, lift slope, stall, etc.).
PFC_AeroSurfaceDef The editable surface definition placed in the prefab; builds a config and resolves its axes, handles X-mirroring.
PFC_PilotUtil Helper to determine the local active pilot.

Variant hooks

PFC_FlightModel exposes a set of protected virtual hooks so a variant can subclass the model and replace one piece of the simulation without forking EOnSimulate. The base implementations reproduce the classic prop behaviour exactly, so plain PFC aircraft are unaffected.

Hook Replaces Base behaviour
GetSurfaceDeflection(axis, pitch, roll, yaw, maxDef, out deflection) per-axis surface deflection the four core axes; returns false for unknown axes (surface stays undriven)
GetSurfaceAirVelocityLS(owner, physics, surfLocalPos, velocityLS) per-surface local airflow every surface sees the CoM airflow (no rotational flow)
GetFlapTargetAngle() flap target angle (deg) 0 — the core never deploys flaps
GetFuselageDragArea(speed, airDensity) fuselage drag area returns m_fFuselageDragArea
UpdateEngineSpool(throttle, timeSlice, destroyed) RPM spool linear ramp at m_fRPMRate
ComputeThrustMagnitude(speed, airDensity, destroyed) total thrust (N) 0 at idle → full at max RPM, × health
GetThrustHealthMultiplier() damage → power scaling linear to m_fMinHealthThrustFraction
OnAeroSimulate(owner, physics, timeSlice, speed, aoaDeg, airDensity) end-of-tick extension point empty

New control axes can be added with modded enum PFC_ControlAxis and handled in a GetSurfaceDeflection override. The first consumer of these hooks is the Jet Flight Core mod (JFC_FlightModel), which layers turbojet spool, transonic drag rise, airbrake, rotational-flow damping, G-limits and buffet on top of the core — see Building a Variant.

Execution flow (per simulation tick)

EOnSimulate (owner / server only)
 ├─ PFC_FlightController.PollInput()      → smoothed pitch/roll/yaw/throttle
 ├─ set each surface's deflection          → GetSurfaceDeflection() per axis
 ├─ guard against NaN / teleport-spike velocity (reset or clamp)
 ├─ RefreshWind()                          → steady wind + gradient + gusts
 ├─ for each surface: CalculateForce(GetSurfaceAirVelocityLS()) → ApplyImpulseAt(surfacePos, force·dt)
 ├─ fuselage drag impulse                  → GetFuselageDragArea()
 ├─ UpdateEngineSpool(), thrust impulse at CoM ← ComputeThrustMagnitude()
 ├─ speed-scaled angular-rate damping
 ├─ compute normal G-load
 ├─ OnAeroSimulate()                       → variant extension point (empty in the core)
 └─ (owner, once registered) UpdateSignals() → instrument MP signals

EOnFrame (all peers)
 ├─ PFC_FlightController.ApplyGroundSteering()  (also zeroes CarThrust/CarBrake/CarShift)
 ├─ UpdatePropellerVisuals()   (owner/server from local RPM; proxies from replicated RPM signal)
 └─ DrawDebug()                (F6)

Authority model

  • EOnSimulate returns immediately on a peer that is neither the owner nor the server, so the aerodynamic simulation only ever runs on the authoritative peer.
  • The owner publishes instrument signals; they replicate to everyone for free.
  • Proxies receive replicated movement via PFC_NwkMovementComponent and drive their own propeller spin from the replicated, normalized engine-RPM signal.
  • OnTicksOnRemoteProxy() returns true so the component keeps ticking on proxies (needed for the local visual updates).

See Networking & MP for the replication and signal details, including the compression and boot-window pitfalls.