Everything below is exactly what the code integrates — symbols map one-to-one
onto the PARAMS block at the top of the file. Frame: anchor at the origin,
+x downwind, +y crosswind (right-handed), +z up; ground at z = 0.
Logarithmic atmospheric boundary layer, blowing along +x, with an optional multiplicative gust factor built from three slow sines:
The kite is a point mass m at p with velocity v, plus one extra state: the heading h, a unit vector kept in the tangent plane of the tether sphere. With er = p/|p| the radial direction, each substep re-projects h ⊥ er and renormalizes (a cheap parallel transport). The apparent wind includes the kite's own velocity — this is what makes a downloop load up and an upstroke go light:
Sheeting (power ∈ [0,1]) moves CL and CD together along a quadratic polar, so depowering lowers both lift and glide ratio E = CL/CD and the kite drifts toward the window edge:
A luff (front-stall) factor kills lift when the apparent wind acquires an inward radial component — i.e. when the kite is pushed from outside the window shell, as when it overflies zenith. With ξ = (va·er)/|va|:
The spanwise axis and lift direction are built from heading and apparent wind; the identity va×(h×va) = h|va|2 − va(va·h) means L̂ is the normalized projection of the heading perpendicular to the flow — the kite pulls toward where its nose points. Steering input u rolls the lift vector about the flow axis (Rodrigues rotation):
Forces on the kite particle (q = ½ρ|va|2A is the dynamic pressure times area):
The structural span ŝst is the orthonormalized L̂roll×h frame axis, so it banks with steering; the two wingtips (and line attachments) ride at p ± d·ŝst with d = tip_offset·√(A/9).
The single most important line in the file. Commanded turn rate scales with apparent wind speed — a parked kite barely answers the bar, a kite ripping across the window whips around. Two passive yaw torques share one geometric factor: gravity noses the kite toward tangential "down" (tg, the projection of −ẑ onto the tangent plane, |tg| = sin θpolar, vanishing at zenith), while nose-up trim yaws it toward zenith — what makes a released kite climb back to 12 o'clock:
Integration: h is rotated about er by ψ̇·h each substep (h the substep length), then re-projected into the new tangent plane. u > 0 is the pilot's right; the roll and the turn law share the sign so the trajectory chases the nose.
Each side is a chain of N = 16 particles from its bar end (anchor, inverse mass 0, at y = ±0.25 m) to its wingtip; the wingtip point itself belongs to the kite, so the last segment's correction acts on the kite particle. Node mass = (line_mass/2)/N. Per rendered frame: 10 substeps of length h; per substep — apply forces, predict positions semi-implicitly, run 8 Gauss–Seidel passes over all segment constraints (alternating line order to avoid L/R solver bias), project ground contact, then recover velocities from the position delta:
The segment constraint is an inequality — it only acts when stretched. This is what produces slack line, sag, and the snap on re-tension:
The accumulated multiplier of each anchor-most segment gives the physical line force, the number a kiter recognizes at the bar (shown summed, and split L/R):
Per segment, only the component of relative wind normal to the segment matters (tangential drag on a 2 mm line is negligible); the force is split between the two end nodes. With t̂ the segment tangent and vrel = W(zmid)x̂ − v̄seg:
Ground is a hard projection: line nodes to z ≥ 0.03 m, the kite centre to z ≥ 0.25 m (wingtip clearance); on contact, downward velocity is zeroed and horizontal velocity decays 10% per substep (friction). A global linear damping of 0.04 s−1 removes residual high-frequency jitter, speeds are hard-clamped at 120 m/s, and any non-finite state triggers an automatic reset.
Heading pursuit — the same law a human uses: point the nose at the waypoint. The target sits at azimuth ±45°, elevation 40°, flipping side whenever the kite's azimuth passes ±30°. With t̂wp the waypoint direction projected on the tangent plane:
Three parameters dominate observable behaviour: E_max (crosswind speed and
pull), g_k (loop period per bar input), and kite_mass (effective,
incl. added mass — light-wind sink and pendulum period). Fit against footage: loop period at
known wind, maximum stable park elevation, sink rate below flying wind.
window.SIM exposes setInput(u, power),
fastForward(seconds) (headless physics, no rendering) and
telemetry() for automated parameter sweeps.
| Symbol | PARAMS | Default | Role |
|---|---|---|---|
| ρ, g | rho, g | 1.225, 9.81 | air density, gravity |
| A, m | kite_area, kite_mass | 9 m², 3 kg | projected area, effective mass |
| CL,max/min, CD,min, Emax | C_L_max/min, C_D_min, E_max | 0.9 / 0.15, 0.15, 6.0 | drag polar |
| gk, kroll | g_k, k_roll | 0.10 rad/m, 0.6 rad | turn-rate gain, lift roll |
| kgrav, ktrim, Cside | k_grav_turn, k_trim, C_side | 0.20, 0.05, 0.6 | passive yaw & sideslip stability |
| L, dline, N | line_length, line_diameter, N | 24 m, 2 mm, 16 | tether discretization (per side) |
| α, S, it | compliance, substeps, iterations | 10−8, 10, 8 | XPBD solver |