# FPV and X-class drone models

These aircraft use real, downloaded Team BlackSheep open frame CAD. The flat
carbon parts are converted from the source drawing contours into extruded 3D
geometry, including the large cutouts and mounting holes. Motors, electronics,
cameras, battery packs, propellers, landing feet and simulator paint are locally
authored additions. These are assembled simulator builds based on the frames,
not imported complete commercial aircraft meshes or manufacturer-certified
flight models.

| Simulator aircraft | Frame source | Motor diagonal | Propellers | Representative setup |
| --- | --- | --- | --- | --- |
| 5-inch FPV | SOURCE ONE v3 | 230 mm | 5-inch, three blades | 700 g, 6S 1400 mAh |
| X-Class 13-inch | SOURCE X v1.0 | 800 mm | 13-inch, two blades | 3.8 kg, 12S 5000 mAh |

## Sources and licence

- SOURCE ONE v3, Team BlackSheep and project contributors:
  <https://github.com/tbs-trappy/source_one/tree/v3>.
- SOURCE X v1.0, Team BlackSheep / Horus Drones:
  <https://github.com/ps915/source_x>.
- SOURCE X's source README specifies an 800 mm motor-to-motor distance,
  4 mm bottom plate, 2 mm top plate, 25 × 25 mm square tube arms and a 700 g
  bare frame. The assembled simulator mass includes its power system.

The source frame drawings and their derived frame contour data remain under
**GNU GPL version 3**. Original source files and notices are retained in
`models/drone_sources/source_one/` and `models/drone_sources/source_x/`.
Exact download URLs, byte counts and SHA-256 hashes are recorded in
`models/drone_sources/SOURCES.json`.
The source projects are credited here; no author or source links are inserted
into the aircraft picker or other runtime interface. The simulator versions
are named 5-inch FPV and X-Class to distinguish these adapted assemblies from
unmodified source-frame products.

## Conversion and rendering

`tools/convert_drone_cad.py` reads the downloaded DXF plate outlines. Circular
and curved edges are sampled and simplified with a 0.12 mm contour tolerance.
Concentric counterbore annotations are flattened to a single aperture for a
clean triangulated surface. Drafting dimensions, leaders, drawing borders and
unrelated views are omitted. The frame drawings provide the shape; the
converter supplies the plate thickness, assembly transform and SI units.

Each output pack embeds all contour arrays. `src/droneModel.js` extrudes them
using Three.js and adds the flight-ready assembly. Static parts are merged by
material to keep draw calls low; four individual motor RPM values animate the
props with alternating directions, mirrored CW/CCW blade shapes and separate blur discs. All decal textures
are generated with canvas. No model or texture requests occur at runtime.

The 5-inch build is adapted to a square-X, 230 mm motor layout: the source plate
shapes retain their millimetre dimensions, while each supplied arm outline is
rotated around its inner mounting region and uniformly scaled to approximately
97.7% to place its motor bore exactly at the solver's motor centre. The X-class
build uses a square-X, 800 mm layout and dimensioned square tube arms. The
added landing feet match the solver's ground contact points exactly; the
assemblies are not machining or assembly instructions for real hardware.

Inspect a source drawing with:

```powershell
python tools/convert_drone_cad.py --inspect models/drone_sources/source_x/SourceX_v1.0_bottom_plate_4mm.dxf
```

Rebuild both packs with `python tools/convert_drone_cad.py --all` (Python standard
library only), or use `--x-class` / `--fpv` for one aircraft.
Visual checks are available in `tools/drone_preview.html`, including a view
that uses the same physical scale for both aircraft. Run
`node tools/test_drone_models.cjs` for geometry and animation checks.

## Flight-model scope

All quads share one multirotor solver (`src/dronePhysics.js`, 480 Hz fixed
substeps): sticks → Betaflight rates → flight-mode setpoint → PID rate loop →
constrained four-rotor allocation → individual motor/ESC lag → rotor
aerodynamics → rigid body. Allocation preserves roll/pitch before reducing yaw,
shifts common thrust for airmode, and respects motor limits. There is no GPS,
altitude hold, collective pitch or helicopter governor.

### Rates and flight modes

- **Rates** follow Betaflight's `applyBetaflightRates()` exactly
  (`SIM.betaflightRate`): `x' = x·|x|³·expo + x·(1−expo)`,
  `rate = 200·rcRate·x' / (1 − |x|·superRate)`, with RC rates above 2.0
  boosted by 14.54 per unit. Setup stores the **full-stick rate**; the centre
  RC rate is derived as `rate·(1 − superRate)/200` (`SIM.betaflightRcRate`).
  LOW/SPORT/HIGH scale the result by 50/75/100%.
- **ACRO**: centred sticks stop rotation, not bank angle.
- **ANGLE**: stick sets the tilt (`angleMaxDeg` 55°, the total tilt of a
  diagonal stick is bounded too); a P level loop (`levelGain`, 1/s) turns the
  angle error into a body-rate setpoint for the same rate PID, so the craft
  self-levels at centre stick and recovers from inverted. No flips.
- **HORIZON**: the ANGLE loop at centre stick, blending linearly to pure ACRO
  rates by `horizonTransition` (75%) deflection and fading when banked beyond
  ~105°, so full stick flips and rolls.
- **Setup → Flight mode** chooses ACRO / HORIZON / ANGLE (MODE keeps selecting
  LOW/SPORT/HIGH rates), or *MODE switch* where MODE 0/1/2 = ANGLE / HORIZON /
  ACRO at full rates — the usual Betaflight three-position mode switch.
  Factory defaults: both quads ACRO (unchanged behaviour).

### Rotor, motor and battery model

- **Thrust** per rotor `T = T₀·(rpm/maxRpm)²·inflow·ETL·ground·wash`:
  - *Axial inflow*: climbing along the thrust axis unloads a fixed-pitch prop,
    `1 − V/(pitchSpeed·rpm/maxRpm)`. Descending loads it (blade-element
    `(V_p − u)/(V_p − v_h)`, capped at +30% for blade stall), where the disc
    inflow `u = V_c + v_i` takes `v_i` from Leishman's empirical vortex-ring
    curve for −2 < V_c/v_h < 0 and the windmill-brake momentum branch below.
    So a sink at constant throttle settles at a bounded rate instead of
    running away, and a modest throttle increase punches back out of the
    wake. This gives props their vertical damping and limits top speed.
  - *Translational lift*: `1 + translationalLift·μ²/(μ²+1)` with μ = edgewise
    speed / hover induced velocity `v_h = √(T/2ρA)`.
  - *Ground effect*: Cheeseman–Bennett `1/(1 − (R/4z)²)` per rotor when upright.
  - *Prop wash / vortex ring*: sinking along the thrust axis at
    `d = −V_axial/v_h` ≈ 0.4–2 (peak ~1.15) re-ingests the wake. Mean thrust
    falls by `vrsLoss`, and each rotor buffets independently by `washNoise`
    (three deterministic incommensurate sines, 4–17 Hz), which the rate loop
    cannot fully reject — the familiar propwash wobble when dropping into your
    own air or after a punch-out from a dive. Edgewise speed sweeps the wake
    away; climbing has none. `state.propWash` (0..1) exposes it.
- **Blade flapping / duct lip lift**: a moment tilting the rotors away from the
  relative wind (`flapMoment`, N·m per m/s at full rpm), so fast forward flight
  pitches the nose up a little and the I-term must hold it.
- **Motors**: first-order rpm lag (rise/fall time constants); rise slows as
  pack voltage sags. Yaw sums rotor drag reaction **and** spin-up reaction
  `J·dΩ/dt` (`rotorInertia`), giving the sharp yaw onset of real quads; net
  rotor angular momentum adds gyroscopic coupling. Symmetric throttle changes
  stay yaw-neutral.
- **Electrical power** per motor = shaft power / `driveEfficiency` + iron
  losses. Shaft power at full rpm comes from momentum theory with the rotor
  figure of merit, `T₀^1.5 / (FM·√(2ρA))`, scaled by f³ and by the prop's
  power coefficient as climb inflow unloads it (shared with the airplanes);
  iron/bearing/ESC switching losses are `ironLossW·f^1.5` (a 2207 1750 KV
  motor draws ~1.6 A no-load on 6S). Avionics (`avionicsW`) add FC, receiver,
  camera and VTX. The old model used one combined efficiency (FM·η ≈ 0.5) at
  every load and 100 % usable capacity, so it hovered 19 / 22 min; real 5-inch
  quads hover ~7–9 A on 6S.
- **Battery**: the shared LiPo/Li-ion model (`SIM.cellOcv` in
  `airplanePhysics.js`): resting cell voltage on a measured discharge curve
  (4.2 V full, 3.7–3.9 V plateau, knee below ~15–20 %, 3.0 V at 0 %; Li-ion
  `battery.chemistry: 'liion'` sits lower and slopes to 2.8 V), DC resistance
  per cell ≈ 6.5 mΩ·Ah / capacity for LiPo (≈ 60 for Li-ion) unless
  `cellResistance` is set, doubling toward empty, and the loaded voltage that
  delivers the power (V(E − V)/R = P). Charge is coulomb-counted: the HUD %
  is the fraction of rated mAh. Motor rpm (and so max thrust ∝ V²) follows the
  *loaded* voltage, so punch-outs sag, a tired pack loses top end, and an
  empty pack fades progressively. **No cutoff** (Betaflight has none): at 0 %
  the quad keeps flying into a 3 % over-discharge reserve whose voltage
  collapses toward 1 V/cell; the stick needed to hover climbs until even full
  throttle cannot hold height, and only the flat pack (reserve gone) browns
  out the ESCs and stops the motors. An empty (0 %) pack cannot be re-armed.
  The predicted hover throttle includes the hover sag.
- **Turbulence**: the airplanes' seeded Dryden model (`SIM.createTurbulence`)
  supplies the mean wind, gusts and the vertical-gust differences across the
  left/right and front/back rotor pairs. Each rotor's thrust changes by
  −ΔV/V<sub>pitch</sub> per m/s of extra inflow (the same blade-element slope
  as climb unloading), so an updraft under one side rocks the quad. Near the
  ground (4 m, 6 m/s gusty wind) ANGLE/HORIZON hold it within ~0.3° (5-inch)
  / 0.7° (X-Class) peak; at 200 m the eddies are hundreds of metres long and
  it mostly drifts. Replays stay bit-identical.
- Body drag uses per-axis areas (frontal vs. top), plus rotor H-force drag;
  ground contacts, hard landings, inverted impacts and prop strikes (open
  props only) end the flight. Ground contact is progressive (soft at rest,
  ~60 g at 3 cm) so a survivable impact stops within the clearance under the
  props. A flat touchdown is a hard landing when ½mv² exceeds the frame's
  `crashEnergy`: 5-inch carbon 17 J (≈7 m/s, a 2.5 m drop), X-Class 18 J
  (≈3.1 m/s for 3.8 kg).

### Coefficients

| | 5-inch | X-Class |
| --- | --- | --- |
| Mass (kg) | 0.70 | 3.8 |
| Static thrust / rotor (N) | 14 | 55 |
| Thrust : weight | 8.2 | 5.9 |
| Hover throttle | 32% | 39% |
| Max rpm | 32 000 | 12 500 |
| Motor rise / fall (ms) | 28 / 20 | 70 / 50 |
| Inertia xx / yy / zz (kg·m²) | .0031 / .0055 / .0031 | .21 / .40 / .21 |
| Rotor inertia J (kg·m²) | 7e−6 | 2.6e−4 |
| Pitch speed (m/s) | 70 | 58 |
| Drag area x / y / z (m²) | .012 / .015 / .012 | .06 / .085 / .06 |
| VRS loss / buffet | .12 / .3 | .12 / .3 |
| Flap moment | .0006 | .01 |
| Pack, cell resistance (mΩ) | 6S 1400 LiPo, 4.6 | 12S 5000 LiPo, 2.5 |
| FM / drive η / iron loss per motor / avionics | .42 / .85 / 40 W / 10 W | .48 / .85 / 60 W / 18 W |
| Static full power (fresh pack) | ~3.5 kW | ~9.0 kW |
| Hover current / power | 7.5 A / 266 W/kg | 13.8 A / 182 W/kg |
| Hover endurance to 20 % / to 0 % | 8.4 / 10.3 min | 16.2 / 19.8 min |
| Crash energy (flat-impact limit) | 17 J (7.0 m/s) | 18 J (3.1 m/s) |
| Rates rp / yaw (°/s), super | 720 / 540, .7 / .6 | 400 / 300, .6 / .5 |
| Rate PID Kp / Ki / Kd, level gain | 24 / 50 / .075, 7 | 10 / 8 / .1, 4 |
| Flat-out (ANGLE 55°, full stick) | ~138 km/h | ~126 km/h |
| Level top speed (~75° tilt) | ~150 km/h | ~130 km/h |

The physics also supports ducted rotors (`params.ducted`: shrouded blades never
prop-strike) and a factory ANGLE default; `tools/test_drone_physics.cjs`
exercises those with test-only airframes from `tools/airframe_fixtures.cjs`
(a ducted 3-inch and a 7-inch on a sagging 6S Li-ion pack).

Inertia, thrust curves, motor lag and assembled mass are representative
estimates, not measurements validated against either CAD source; the
aerodynamic terms are engineering models tuned for plausible feel, not CFD.
Physical references consulted for the control and rotor model:

- PX4 control allocation: <https://docs.px4.io/v1.14/en/concept/control_allocation>.
- PX4 controller diagrams: <https://docs.px4.io/v1.14/en/flight_stack/controller_diagrams>.
- NASA rotor performance research: <https://rotorcraft.arc.nasa.gov/Publications/files/78-2022-1186.pdf>.
- Betaflight rate curve: `src/main/fc/rc.c`, `applyBetaflightRates()`.
- Momentum theory, vortex-ring state and ground effect (Leishman, *Principles
  of Helicopter Aerodynamics*; Cheeseman & Bennett 1955).

Manoeuvre results (hover, punch-outs, top speed, flips/rolls against the rate
setpoints, split-S, power loop, prop-wash, sag, self-levelling, ground
contact) are tabulated in `models/FLIGHT_TEST_RESULTS.md` and asserted by
`tools/test_drone_manoeuvres.cjs`. A flat touchdown beyond the frame's
crash-energy speed is a hard landing, judged on the first contacting substep.
