Skip to content
TipDocs

Documentation

How Loft computes what it shows, where the model is weak, and how its accuracy is measured. The repository is open — the math here is meant to be checked, not trusted.

Methods

Loft is implemented clean-room from published methods (OpenRocket is GPL; none of its code is used). Everything runs in SI internally. Where a method is an approximation, it is flagged here and in the limitations log. The source is in the open repository, file paths noted below.

Importing designs

Loft reads three design formats: OpenRocket .ork (a ZIP, gzip, or raw XML with an <openrocket> root; lib/ork/), RockSim .rkt (XML with a <RockSimDocument> root; lib/rkt/), and RASAero II .CDX1 (XML with a <RASAeroDocument> root; lib/rasaero/). The importer sniffs the root element and picks the adapter. Each adapter is a thin translator into one internal rocket model — the simulator only ever sees that model, never a file format — so the same physics flies any of them and a future importer (RocketPy) is another adapter, not a second engine. The RockSim adapter is implemented clean-room from RockSim's published file specification (its RockSim_Xml_Doc.txt, the RockSim engine-file format, and the documented shape/finish codes); RockSim stores lengths in millimetres, masses in grams, and diameters as diameters, all converted to SI on import. Unknown parts are skipped with a warning rather than failing the whole file.

The RASAero II adapter is likewise clean-room, from the published RASAero II user manual and from inspecting real .CDX1 exports; RASAero works in US customary throughout — inches, pounds, feet, ft/s, °F, inHg, mph — all converted to SI on import. Only the design file is read; RASAero's own aerodynamics are not reimplemented, and the numbers it stored sit beside Loft's as an independent cross-check, exactly as OpenRocket's and RockSim's stored results do.

Mass on a RASAero import works differently, and it has to. A .CDX1 carries no materials and no per-part masses at all — RASAero is an aerodynamics and trajectory tool, so the flyer types in a single launch weight and CG per simulation. Loft therefore takes the geometry as massless and carries the stated launch weight as one airframe mass component: its mass is the launch weight less the resolved motor's own loaded mass, and its station is solved from ma·xa + mm·xm = M·xcg so the loaded vehicle balances exactly where the file says. That keeps one component tree every surface reads the same way, and it means swapping the motor in the app correctly keeps the airframe and changes only the motor. When the motor can't be weighed from the bundled data, the whole stated weight is placed at the stated CG and the import says so.

Coordinate model & integrator

The flight is integrated with a fixed-step 4th-order Runge–Kutta scheme (lib/sim/simulate.ts). The state is carried as full 3-D position and velocity vectors, but this session integrates translational motion in the vertical plane: thrust and drag act along the flight path (velocity-aligned), assuming the rocket flies at a small angle of attack — which holds for a stable rocket in light wind. Rotational dynamics (weathercocking, pitch/yaw) are not integrated; static stability is computed and reported separately. The vector-shaped state is deliberate: extending to a full 6-DOF solve is additive, not a rewrite.

The step is phase-adaptive. Boost and coast — where thrust and drag change quickly — use a fine fixed step (0.01 s). The long descent under recovery is different: once a canopy is open the vehicle settles to a near-constant terminal velocity, an all-but-linear fall that a much coarser step integrates just as accurately, so the descent runs at a 0.2 s ceiling — set from a convergence study (lib/sim/descent-convergence.test.ts: halving it moves the landing point and flight time by under a tenth of a percent). The descent is the bulk of a full flight's cost, so this ceiling matters most for the hundreds of flights a Monte-Carlo runs — it is a fifth to a quarter of the whole run. An open parachute's quadratic drag is a stiff decay, though, and a fast deployment (a mistimed early ejection, a payload popping its chute at separation speed) can push an explicit step past its stability limit and diverge. So through the opening transient the descent step is shortened to hold dt·λ within the RK4 stability region, where λ = ρ·(Cd·A)·v/m is the linearised drag-response rate — small while the speed is high, relaxing back to the ceiling as the canopy brings the rocket to terminal. The result is stable for any deploy speed without paying for a fine step over the whole descent.

Launch rail & thrust-to-weight

While the rocket is still on the launch rail (position along the rail ≤ the guide length) its motion is constrained to the rail axis: thrust and gravity act along the rail and any lateral acceleration is reacted by the rail, so it cannot pitch until it clears the guide. The speed at which it leaves the rail — the rail-exit velocity — is the speed the fins first have to stabilise the airframe; below about 15 m/s(50 ft/s) that is flagged, as a stable departure is not assured. It is read at the exact instant the rocket has travelled the rail length — interpolated within the integration step rather than sampled at the step's end — so a coarse step doesn't overshoot the crossing and report an optimistically high departure speed; the figure matches an independent 6-DOF engine (RocketPy) to a fraction of a percent.

The liftoff thrust-to-weight ratio is the peak thrust developed while clearing the rail divided by the loaded weight (F / m·g) — the most basic launch-safety check, and, unlike rail-exit velocity, independent of how long the rail is. Below 1 : 1 the rocket cannot leave the pad at all, which Loft flags as a warning (the apogee it would otherwise report is essentially zero and meaningless); below the 5 : 1 figure commonly taught for high-power rockets it is flagged as a caution to verify the rail is long enough. These are rules of thumb, not verdicts.

Sources: minimum rail-departure velocity and the 5:1 thrust-to-weight guideline as given in the NAR/Tripoli high-power safety guidance and standard model-rocketry texts (e.g. Stine & Stine, Handbook of Model Rocketry); the rail-constraint and thrust-to-weight formulation follows Niskanen's OpenRocket technical documentation.

Atmosphere

Temperature, pressure, and density follow the U.S. Standard Atmosphere (1976) layer model (lib/sim/atmosphere.ts), integrated from the hydrostatic equation for an ideal gas — the isothermal exponential form in isothermal layers, the power-law form under a lapse rate. Speed of sound is √(γ·R·T); dynamic viscosity follows Sutherland's law. A non-standard ground state (a warm, high, or low-pressure day) shifts the sea-level anchors while keeping the standard structure aloft — this is what the optional “today's conditions” re-run uses.

Sources: U.S. Standard Atmosphere, 1976 (NOAA/NASA/USAF); Sutherland (1893) as tabulated in standard fluid-dynamics references.

Mass, CG & inertia

Mass properties (lib/sim/mass.ts) are built from the component tree. Where geometry defines mass unambiguously — tubes, rings, fins — it is computed from the component's dimensions and its material density. Bodies of revolution (nose cones, transitions) are integrated numerically along the contour for volume and centroid (lib/sim/shapes.ts), with a shell subtracted when a wall thickness is given. A nose cone or transition also carries a shoulder— the collar that plugs into the neighbouring tube — whose material (a tube of the shoulder's own wall, plus a bulkhead disc when it is capped) is massed too and placed at its own station, so it shifts the CG the way the real part does; on a small model rocket the nose shoulder alone can be several grams that would otherwise be dropped. Recovery and fitting parts are stored the same way — as a material plus geometry, not an explicit mass — so their mass is computed too: a parachute or streamer from its canopy area and areal density, a shock cord from its line density times cord length, and a launch lug from its bulk material over its tube wall. A rail button is stored differently again — an outer and an inner diameter, a total height and the two flange heights, and no wall thickness at all — so it is massed as the spool those describe: a base flange, a shank standing it off the airframe, and an outer flange. That is the moulded body only; the steel screw a button is mounted with is not part of the geometry the file states, so where a design gives the part's own mass instead, that figure wins. On a small model rocket these are grams; on a high-power rocket a long tubular-nylon harness is a real, CG-shifting mass that would otherwise be silently dropped. A fitting's mass is counted once per instance, so a pair of rail buttons weighs twice what one does and changing that count in the editor moves the mass and the parasitic drag together; the editor states the mass of one of them, and a mass stated in the design file scales the same way.

What a stated weight or balance point replaces

An explicit <overridemass> or <overridecg> in the design always wins; when it is flagged to override all subcomponents— a section weighed as a whole — that one figure stands in for the section and everything inside it, rather than being added to the parts' own computed masses. On a single part, a stated balance point describes the whole of it, shoulder included, and a stated weight moves none of it: the part keeps the balance point its geometry gives it — shoulder counted — and only its weight is replaced. That is what the two fields mean in the format, and it is what a knife-edge reading of the part in your hand measures, so the number you write is the number you took. (A section weighed as a whole is the other case and does move its own balance point: one figure replaces the section's parts, so it is placed at the carrier's own centroid rather than at the mass-weighted centre of the parts it stands in for.) The parts table says which is which. A mass the design file states outright — an OpenRocket override, a RockSim KnownMass— is marked, and so is one carried through from the source tool's own calculation rather than recomputed here; an unmarked mass is Loft's, derived from the part's geometry and its material. Measured across the 35-design corpus: 108 masses stated by the design, 60 carried from the source tool, 401 computed here. They are different claims and a bare number cannot tell them apart. The mark says what the FILE states, not that somebody weighed it — a design exported from Loft and reopened comes back with the masses Loft computed written into it, and marked accordingly. You can state either figure yourself. The editor takes both a weight and a balance point for the nose cone and the body tube — the number you read off a scale and the one you find on a shell balance — and the mass & balance breakdown marks those as yours, distinct from the design's claim and from Loft's own arithmetic. (The parts table marks a stated MASS; it carries no balance-point column yet.) A balance point is measured from the part's own fore end, as OpenRocket's override tab asks for it, and describes the WHOLE part — a cone's shoulder included. So the figure to state is a knife-edge reading of the part as it sits in your hand, and on a cone whose shoulder carries much of its weight that balance point can sit behind the cone's own base; the field allows for it. It is still bounded by the part that physically exists, shoulder and all: a station off the end of that cannot mean anything, so it is clamped rather than flown. Where a stated balance point would not reach the flight at all — the part carries no weight of its own for one to place — the field says so instead of accepting a number that changes nothing. The centre of gravity is mass-weighted; pitch inertia is the sum of each part's own inertia plus a parallel-axis term. Propellant burns off over the flight, so mass and CG are time-varying.

Weighing a part yourself

You can put your own weight on a canopy. The mass Loft computes for a parachute comes from its area and an areal density, which is a fair estimate of fabric and nothing else — it cannot see the shroud lines, the swivel or the deployment bag, and on a small rocket those can outweigh the canopy. So /design takes a canopy mass beside the drag coefficient: put the packed chute on a kitchen scale and type what it says. The figure is flown as-is rather than scaled, it survives a resize in the same edit — resizing a canopy scales the mass Loft estimated, never the one you weighed — and the caption beneath both fields says whose number each one is. This is the field the design formats reach for most: 22 of the 64 <overridemass> elements in the reference corpus sit on parachutes, more than on any other kind of part, and Loft could read them from the first importer while offering no way to write one.

The same field is on the internal structure — centring rings, tube couplers, bulkheads, engine blocks and motor-mount tubes, which share one selection and one set of controls. Counted across the reference corpus by kind, those five carry 45 masses that the design or its own tool supplied rather than Loft computing them, more than the nose cone and the body tube together — a plywood ring cut on somebody's bandsaw weighs what it weighs, not what its diameter and a book density say. Unlike the canopy, these parts have no weight of their own in the model at all: their mass is derived from the geometry and the stock every time, so a figure you type is stored as an explicit override, exactly as an <overridemass> from the file is, and the parts table marks it as yours.

Stated masses, and where they win

A RockSim .rkt is the exception to the “compute from geometry” rule: it stores RockSim's own per-part mass — the calculated value, or the measured (“known”) value when the design is set to use it — so Loft honours those as per-part overrides and flies the exact masses the design specifies. That keeps a RockSim import faithful to its source and keeps the stored-results comparison about the aerodynamics and integration rather than a mass-model difference. (An .ork stores no per-part mass, so there Loft computes it as above.)

Auto radii and auto bores

Nose-cone contours use the standard published profile equations (conical, tangent ogive, ellipsoid, power, parabolic, and the Haack series).

OpenRocket marks a radius that should match a neighbour as auto. Loft resolves those before simulating — a body tube takes its neighbour's radius, a transition end takes the body it meets, and an internal part (coupler, ring, inner tube, bulkhead) fits inside its enclosing part, whether that's a tube, a coupler, or the nose. When a body radius still can't be matched to a neighbour — a whole airframe left auto, anchored only by a boat-tail end or an internal part — it falls back to the rocket's largest known radius (the same value the aerodynamic reference is taken from) so the airframe keeps a defined, self-consistent size instead of collapsing to a drag-free, near-massless needle flown against a borrowed reference area. This mirrors OpenRocket, whoseauto radius searches fore and aft for a dimensioned section and only then uses a default. The substitution is flagged in the import warnings, never silent; only when nothing anywhere resolves is a section finally treated as zero — and even then it is never left undefined, so one unresolved part can't poison the total mass and reference area.

An internal part's BORE has its own auto rule, and it decides that part's mass. OpenRocket states it in one line — “matches inner diameter to the outer diameter of the child inner tube” — and its own source takes the largest inner tube among the ring's siblings, clamps the result to the ring's own outer radius, and leaves 0 (a solid disc) when it finds none. Loft reproduces that, because the file was written by that tool and auto means whatever it computes. Read instead as a missing number, the bore fell back to the outer radius less a wall thickness the file often does not state either — which is no hole at all, and therefore, since the volume is π(ro² − ri²)L, no material and no mass. Anything that is not a centring ring, and states no wall, gets a 1.5 mm one; that is Loft's number rather than the file's and the import warnings say so. It matters because these parts have no aerodynamic term at all, so every dimension of them reaches the flight through mass alone — which sets the centre of gravity, and with it the stability margin.

Aerodynamic stability — Barrowman

The centre of pressure and normal-force-coefficient slope come from the Barrowman equations (lib/sim/aero.ts), the standard subsonic, small-angle method. Each body-of-revolution and fin set contributes a normal-force slope C and a centre of pressure, combined as a coefficient-weighted mean.

nose: C_Nα = 2·(r_base/r_ref)², X_cp = L − V/A_base transition: C_Nα = 2·[(r_aft/r_ref)² − (r_fore/r_ref)²] fins (N): C_Nα = K_fb · 4N(s/d)² / [1 + √(1 + (2·l_m/(C_r+C_t))²)] interference: K_fb = 1 + r_body/(s + r_body) static margin (cal) = (X_cp − X_cg) / d_ref

What d_ref is, since every figure above is quoted in it. A design may state its own reference, or ask for its nose's diameter; otherwise Loft takes the widest component in the design. Stated plainly here because the caliber is invisible in the number that depends on it — a margin reads the same whether the reference behind it is right or wrong.

Where that rule is imperfect, and it is worth knowing which way.“Widest component” ought to mean the widest part of the airframe — the outer mould line the air actually meets — because a part that lives inside the airframe is not a caliber the rocket presents to the airstream. When you build in Loft that is what you get: a coupler or a centring ring wider than the tube holding it is refused rather than flown, because it would quote your stability in calibers of a diameter your rocket does not have. Animported file is taken as its author drew it, so a design whose internal part is genuinely wider than its own airframe would still set d_ref from that part. None of the 35 real designs Loft tests against is drawn that way, so this is a gap in the rule rather than a number any of them is currently getting wrong.

Fin planforms beyond the trapezoid

Elliptical and freeform fin sets use their area- and span-equivalent trapezoid for the normal-force slope. An elliptical fin's centre of pressure, though, is taken exactly for its planform: integrating the quarter-chord aerodynamic centre over the elliptical chord c(y)=c_root·√(1−(y/s)²) gives X_cp = (½ − 2/3π)·c_root ≈ 0.288·c_root aft of the root leading edge — further aft than the equivalent trapezoid, so it no longer under-predicts the margin. A freeform fin carries no explicit chord or span — only an outline of points — so its semi-span, root chord, planform area, and sweep are derived from that outline first; without that step the fin would read as zero-span and add no normal force. Its centre of pressure is then taken exactly from the same outline by strip theory — X_cp = ∫(x_LE + ¼c)·c dy / ∫c dy over the polygon, the chord-weighted mean of the local quarter-chord line — which reduces to the trapezoid formula for a trapezoidal outline and to 0.288·c_root for an elliptical one, so an unusual planform is no longer flattened to an equal-area trapezoid for stability. Being a fraction of the chord, it is invariant when a geometry edit stretches the fin spanwise. A degenerate part — a fin set with no fins, span, or chord, or a nose with no radius — contributes no normal force rather than a division by zero, so a malformed or placeholder part can't leave the centre of pressure and static margin undefined (which would also silently suppress the low-stability warning).

Tube fins

Tube fins are not plates, so the Barrowman fin equations do not apply to them. A tube fin is a short open duct: at a small angle of attack the streamtube it captures enters inclined at α and leaves aligned with the tube's — and so the rocket's — axis. Slender-body/momentum theory, the same theory behind the nose and transition terms above, gives the reaction from that turning as N = ρV²·A_duct·α, i.e.

C_Nα = 2·A_duct / A_ref  per radian,  A_duct = Σ N·π·r_inner²

— exactly the nose-cone form 2·(A_base/A_ref) with the captured area in place of the base area. The turning is distributed along the duct rather than concentrated at either lip, so the resultant is taken at the tube's mid-chord. RockSim's own tube-fin example file stores the numbers its engine computed for the same set — BarrowmanCNa = 10.2204 at BarrowmanXN = 0.524222 m — and this relation reproduces them to within 1% and 2.6% of the chord respectively. Against OpenRocket's stored per-step CP on its tube-fin example Loft sits about 0.9 caliber forward, i.e. conservative; see Limitations. A tube with no bore left (a degenerate part) captures nothing and contributes no normal force.

Sources:J. S. Barrowman & J. A. Barrowman, “The Practical Calculation of the Aerodynamic Characteristics of Slender Finned Vehicles” (1966/1967); as compiled in the public Apogee Peak of Flight newsletters (#149, #150, #157) and the OpenRocket technical documentation. For the duct/inlet normal force: J. N. Nielsen, Missile Aerodynamics (McGraw-Hill, 1960), slender-body treatment of flow-through inlets.

Stability trim by nose ballast

Stability trim (nose-ballast goal-seek).When a design's margin is thin, Loft solves the nose ballast that would bring it to a healthy value directly, rather than making you read it off a sweep (lib/sim/trim.ts). The margin is measured at the loaded centre of gravity, and ballast of mass b at the nose station x_n moves the loaded CG to the mass-weighted blend x_cg(b) = (M·x_cg₀ + b·x_n)/(M + b) while the aerodynamic CP does not move with mass — so the ballast for a target margin is closed-form, b = M·(x_cg,target − x_cg₀)/(x_n − x_cg,target) with x_cg,target = X_cp − margin·d_ref. As b → ∞ the CG asymptotes to the nose station, so nose ballast can make a design no stiffer than (X_cp − x_n)/d_ref; a target above that ceiling is unreachable by weight alone (the fins are too small or too far forward), which Loft says plainly instead of prescribing an ever-growing lump of lead. It is the inverse of the ballast parameter sweep, sharing the same mass and CG model the flight uses.

Stability trim by fin position

Stability trim (fin-position goal-seek).The weight-free companion: instead of adding nose weight, it solves how far to slide the fin group to reach a target margin. The static margin is very nearly linear in fin station — the fins' centre-of-pressure contribution moves one-for-one with them — so the slope dm/dx is taken from a small finite difference of the exact Barrowman CP and the dry CG (both computed without a flight; moving the fins also drags their own mass, nudging the CG, which is folded in) and inverted: x_target = x₀ + (m_target − m₀)·d / (dCP/dx − dCG/dx). Uniquely, this is the only lever that can reduce an over-stable, weathercock-prone margin — moving the fins forward — which nose ballast, adding only stability, cannot. The solved station, typed into the fin-position what-if, reproduces the target margin (round-tripped against a real flight in the tests).

Drag

Zero-lift drag is a component buildup referenced to the reference area (lib/sim/aero.ts):

Body friction and base drag

Fin terms

Tube fins as a drag term

Body-shape pressure terms

Parasitic and wave drag

The summed coefficient is capped at a physical ceiling well above any real nose-forward rocket. That cap is a numerical guard, not a model term — it never engages on a real flight, only on malformed geometry (say a unit-scale import error), where an astronomically large drag would otherwise destabilise the fixed-step integrator and report a nonsensical apogee.

Sources: S. F. Hoerner, Fluid-Dynamic Drag (1965); the drag treatment in the OpenRocket technical documentation; standard flat-plate friction correlations; the Sears–Haack / Von Kármán minimum-drag-body result and the published nose-cone drag comparisons for the wave-drag shape ranking. The drag model is the largest source of error — see limitations.

Motors

A design references a motor by manufacturer and designation but does not embed its thrust curve, so Loft resolves the motor against a bundled database of real RASP .eng curves from ThrustCurve.org (lib/motors/). Thrust is linearly interpolated in time; propellant mass is depleted in proportion to delivered impulse (constant-Isp assumption), so motor mass falls from loaded to casing mass over the burn. Matching prefers an exact designation, then a looser substring or class-and-thrust core (so a Cesaroni “838J293-13A” still resolves to “J293”). A motor is matched on every name it is published under — ThrustCurve's manufacturer designation and its common class-and-thrust name — because a design file may carry either: a Cesaroni reload sold as part number “648J285-15A” is written into an OpenRocket file as “J285”, and both name the same motor. Those catalogued names come from ThrustCurve's certification record rather than the RASP file header, which carries whatever the curve's author typed — and so does the casing envelope. That is not bookkeeping: the motor-swap list filters candidates by mount diameter and the design diagram draws the casing, so one bundled 54 mm motor whose header claimed 75 mm was being offered for the wrong mount. A curve whose catalogued name is more specific than the one a design writes — AeroTech's bundled H100W curve is the H100W_DMS, a different product line from the RMS reload — resolves as an approximate match and says so, rather than claiming to be the motor the design named. An exact designation matches regardless of a maker-string difference, but a loose match never crosses manufacturers — a design's “K550” is left unresolved rather than silently matched to a different maker's “K550W”. The UI flags an approximate or failed match. A motor cluster(OpenRocket's cluster configuration, e.g. a “4-ring”) is flown as that many identical motors on the centreline — full thrust and propellant, plus the structural mass of that many motor tubes where the design holds the cluster in an inner tube — with the count shown on the motor tag. A cluster carried by the airframe tube itself describes no extra tubes, so none are counted; see limitations. Where a design assigns different motors to separate mounts and only some resolve, the flight runs on the resolved ones and a warning reports the under-counted thrust.

Staging

In-line (serial) stages fly in sequence. By default the bottom stage lights at launch and each stage above air-starts when the stage below burns out, plus any ignition delay it specifies — so a boosted-dart coast between separation and the sustainer's air-start is honoured. That default is what OpenRocket calls an automatic ignition event, and a design can say otherwise: the file records an event per motor per configuration, and Loft follows it. A launch event fires that motor at liftoff wherever it sits in the stack — one OpenRocket example lights its middle stage first — and an event that can never arrive (a burnout trigger on the bottom-most stage, with nothing beneath it to burn out) leaves that motor unlit, riding as inert mass, which is what the file's own stored mass and thrust traces show. Reading those events took that design from 49.8% high to 9.4% low, with rail-exit velocity landing within 0.4% of the stored figure. A serial stack also parts at one joint: the stage that separates takes everything below it with it, rather than the stack shedding one stage per event. A stage separates on the event the design specifies: by default (and for the usual boosted staging) when it finishes burning, but a stage set to separate at its own ejection charge hangs on until that charge fires — often a long delay, so a payload or dual-section rocket stays whole until near apogee and only then parts, rather than splitting at burnout. When it separates its structure and empty casing leave the vehicle, and the flight continues on the stages still attached. The simulator recomputes mass, the reference area, and the drag buildup for the attached stack at each separation (each phase's vehicle is the top-most stages, evaluated with the same mass and aero code as a single stage), so a dead booster is no longer lofted to apogee. Because the vertical-plane solve is a point mass, only the total mass, thrust, and reference drag change across a separation — the trajectory doesn't depend on where the centre of gravity sits within the vehicle. Stability does depend on it: the stages stack nose-to-tail into one continuous airframe, so the centre of gravity and Barrowman centre of pressure are computed for whichever stages are attached. Because an upper stage can be stable inside the loaded stack yet unstable once it flies alone, the sustainer's own static margin — evaluated loaded, right after separation, its worst case — is checked separately and flagged if it drops below 1 cal. The exception is a payload section that pops its chute on the separation (a lower-stage-separation recovery): it is under canopy from that instant and never flies ballistically, so a finless payload isn't flagged as an unstable upper stage. Only the final (sustainer) stage's descent is tracked; a separated booster's own recovery isn't. Parallel and strap-on staging is not modelled (see the limitations).

Ignition timing is resolved per motor, not just per stage, so a second motor within one stage can be air-started after its own delay while the first burns from launch — the delay is read from the flown configuration, so a design that sets a different air-start time in each of its stored simulations flies each one distinctly. The airstarted motor rides as dead weight until it lights, then adds its thrust; the vehicle's peak speed and apogee shift with the timing, exactly as a staged air-start does.

Recovery & drift

Each recovery device deploys on its event — apogee, a set altitude, the motor's ejection charge, or the separation of the stage below it (the payload/dual-section charge that both parts the sections and pops the chute) — plus any deploy delay it specifies: the vehicle free-falls on body drag until the canopy opens, so a delayed deployment reports the higher speed reached at line-stretch. An ejection deployment fires at the real charge time (burnout plus the design's delay), so a too-short delay opens the canopy before apogee while still ascending, and a too-long delay opens it late at speed — or, if the charge would fire after the rocket is already down, not at all. A plugged motor (OpenRocket's delay = none, RockSim's negative ejection delay) carries no charge at all, so a device waiting on one never opens and the flight is ballistic — Loft does not assume an altimeter that the design does not describe. A design that simply states no delay is different: that is silence rather than a statement, and such a device falls back to deploying at apogee. Both a pre-apogee opening and a ballistic (no-deploy) descent are flagged. Descent then uses the summed deployed drag areas (Cd·A); the steady descent rate this gives is checked against the ~3–6 m/s (10–20 ft/s) most designs aim for, and a firm (>25 ft/s) or hard (>35 ft/s) landing under a too-small canopy is flagged as a caution or warning — a rule of thumb, not a verdict. Descent drift is the canopy drifting with the wind; with the “today's conditions” re-run, the wind varies with altitude from the winds-aloft profile.

Two landing speeds, because they answer different questions. Ground-hit speed is the vertical descent rate at the moment of impact. That is the convention the rules of thumb above are written in, the one the per-section landing energy a waiver is judged on is computed from, and the one every design file's own stored figure uses — so it is the number Loft compares against OpenRocket, RockSim and RASAero. Arrival speed is the total speed over the ground with the sideways drift included, and it appears only when the wind makes it materially larger. Both are real: a canopy descending at a comfortable 5 m/s in a 20 mph wind arrives at about 10 m/s over the ground, and a glancing arrival is harder on an airframe than the descent rate alone suggests — but it is the descent rate that says whether the canopy is big enough. Folding the two together is a mistake Loft used to make: the hard-landing flag then fired on the weather rather than on the recovery, and the landing energy read almost four times too high on a windy day.

Recovery sizing (goal-seek). When a design lands harder than it should, Loft solves the canopy that would bring it down gently rather than leaving you to guess and re-fly (lib/sim/recovery.ts). It is the recovery-side companion to the stability trim, and closed-form: at terminal velocity drag balances weight, v = √(2·m·g / (ρ·CdAtotal)), with m the burnout (descent) mass, ρ the air density at the field, and CdAtotal = CdAchute + ½·Aref — the same airframe body-drag term the descent integrates — so the canopy for a target speed is CdAchute = 2·m·g/(ρ·v²) − ½·Aref, reported as a drag area and an equivalent diameter. Because it uses the flight's own descent model, a canopy sized this way, flown, lands at the target speed — checked against a real flight in lib/sim/recovery.test.ts. The goal-seek names one size; the recovery-size what-if (Design workspace) is its open-ended companion — a multiplier on every deployed device's drag area that re-flies the design with a bigger or smaller canopy, so you can watch the descent rate, drift, and deployment speed move together (a bigger canopy lands softer but drifts farther and snaps open harder). It scales only the recovery, never the ascent, so apogee and stability are untouched; the Monte-Carlo landing scatter honours it.

The optimum ejection delay Loft reports is the delay (from burnout) that would deploy the recovery at apogee — a property of the airframe, motor, and launch conditions, not of the delay actually flown. It is measured from a free coast to the true apogee with recovery suppressed, so a design flown with a too-short delay — whose canopy opens early and cuts the climb short — still gets a sound recommendation rather than an even shorter one that would compound the mistake.

Fin flutter

Fins have their own elasticity, and above a critical airspeed — the flutter boundary— a bending-and-twisting oscillation stops damping out and diverges, shredding the fin (and often the rocket). Loft estimates each fin set's flutter speed along the ascent and reports the worst-case margin— the flutter speed divided by the peak airspeed the fin actually sees — sampling the real ambient pressure and speed of sound at every altitude the vehicle passes through, since the boundary rises as the air thins. Neither OpenRocket nor RockSim reports this, so it is Loft's own safety heuristic.

On a staged rocket each fin set is judged only over the part of the flight its own stage was still attached for.A booster's fins come off with the booster, so the speed the sustainer reaches afterwards is not a speed those fins ever saw. Until 2026-08-02 Loft charged them with it anyway, and because the reported margin is the worst across all fin sets, a shed booster could supply the whole warning: on a three-stage sample design the red flag came from a fin set that left the stack at 0.86 s, judged at 2.37 s and 77 m/s. Over its own flight that same fin set has a margin of 2.11 rather than 0.68. The worst fin set also selects which fin the thicken-to hint names, so the wrong one being worst pointed the fix at the wrong part of the rocket.

The estimate is the simplified flutter-boundary closed form, Vf = a·√( G / [ 1.337·A³·P·(λ+1) / (2·(A+2)·(t/c)³) ] ), with a the local speed of sound, G the fin material's shear modulus, A the exposed-fin aspect ratio, λ the taper ratio, and t/c the thickness ratio on the root chord — so flutter speed climbs with the cube of the thickness ratio and the square root of stiffness, and falls with aspect ratio (thin, high-aspect fins flutter first). The shear modulus is taken from the design's own fin material (G10 fibreglass is assumed, and said so, when the material is missing or unrecognised). This is a preliminary-design estimate, method-dependent to roughly ±20% — the fuller NACA method, which adds a chordwise mass-balance term, tends to sit a little lower — so Loft keeps a recommended margin of ≥ 1.5× and cautions (never reassures) when it is thin; it never certifies a fin as flutter-safe.

When the margin is thin, Loft names the fix rather than just saying “thicken the fins”: since the flutter speed rises with the 1.5 power of the thickness ratio and the peak airspeed the margin is taken against barely moves with thickness, the thickness for a target margin is closed-form, ttarget = tnow·(margintarget/marginnow)^(2/3). It errs a touch thick — a thicker fin also drags a little more and lowers the peak airspeed, so the flown margin comes out just above the target — which is the safe direction for a fin caution. Shortening the span or a stiffer material reaches the same margin; the suggestion is a starting point to check with the fin-thickness what-if, not a substitute for a real structural design.

Sources: D. J. Martin, NACA TN 4197, Summary of Flutter Experiences as a Guide to the Preliminary Design of Lifting Surfaces on Missiles(1958); the closed form as popularised for rocketry by Apogee Components' Peak of Flight newsletter #291.

Monte-Carlo dispersion

A single flight is one draw from an uncertain reality: a rail is never perfectly plumb, wind gusts and shifts, a motor's total impulse varies from one unit to the next, and a built airframe rarely hits its CAD mass exactly. The dispersion tool flies the design a few hundred times with those inputs jittered around their nominal values and reports the spread of the outcomes — the apogee band to expect, the radius from the pad that contains 95% of the landings (the recovery area to plan for), and the landing-speed band (its 95th percentile is the hardest landing to size recovery against, which a heavier-than-CAD build pushes up) — with, when some flights land firm, the fraction that land firm (over 7.6 m/s) or hard (over 10.7 m/s), the same rule-of-thumb thresholds the flight's own hard-landing warning uses. It also reports the landing-energy band (½·m·v² for the whole vehicle) — the figure many fields and waivers cap per section, so its 95th percentile is the worst case to check against a limit; a design that lands in separated sections divides it among them, so read the whole-airframe number as conservative. All FOUR landing figures — speed, energy, drift and the recovery radius — describe only the dispersed flights that reached the ground inside the simulation's time cap, and the panel says how many of the total that was. A flight still descending at the cap has no landing speed and no landing energy — it carries zeros that are placeholders rather than measurements, and averaging those in reported a soft landing that never happened: on one real design at a legal recovery size, every sample was such a placeholder and the panel read 0.00 m/s and 0 J. Drift and the recovery radius joined that population on 2026-08-02, and they are why the rule is stated as four rather than two: an un-landed flight's drift is not a placeholder zero but the distance it had covered when the cap stopped it, so it read as an ordinary smaller number and pulled the recovery area DOWN — understating the figure whose only job is to say how much ground to plan for. Where none of them land, all four are withheld with the reason instead, and the landing scatter says so rather than drawing an empty plot. Every sample runs through the same solver as the main flight; nothing about the physics changes. The uncertainty is entirely in the inputs, which are your own stated assumptions, so the result is an honest propagation of that spread — not a claim of new precision.

How each input is drawn

Each input is drawn from a normal distribution about its nominal value at the one-sigma spread you set: the motor impulse scales the thrust curve (a motor's propellant mass is essentially fixed, so its lot-to-lot variation is in average thrust); the dry mass scales the airframe's structure uniformly (so the centre of gravity holds and only the total mass moves); the aerodynamic drag scales the zero-lift drag coefficient (the drag model's own uncertainty — the single largest error source, so its spread belongs in the band rather than being treated as exact; it does not touch a deployed canopy's drag area); the recovery drag scales every open canopy's Cd·A (a parachute's drag coefficient is only known to ±10–20%, and it sets the descent — so this is the main driver of the landing-speed band, the piece the ascent drag deliberately leaves out); the rail angle adds a lean to the launch rod; and the wind speed varies around the nominal being flown. Those nominals are the launch conditions in force — the design's own stored setup, with whatever you have changed under Conditions on top, so the dispersion answers for the same day the flight above it does rather than for the day the file was saved. Impulse, dry mass, and drag are the main drivers of the apogee band; rail angle and wind drive the landing scatter. The rail-lean and wind directions are sampled uniformly from all bearings, so the landing scatter maps the recovery area regardless of the day's wind heading. Under today's weather that is not so, and the panel says which case it is in: the solver then flies a whole wind profile rather than one surface figure, on the forecast's own bearings. A spread on the surface wind has nothing to vary — that field is greyed out with a note — and the scatter is the spread of one forecast rather than a disc over all headings, so read it as the recovery area for THAT day rather than for any wind. The whole run is driven by a fixed-seed pseudo-random generator, so the same design and the same dispersions reproduce the same cloud — a dispersion is a stable property of the design, not wall-clock noise. The reported bands are 5th-to-95th percentiles: of every flown sample for apogee and max speed, and of the flights that reached the ground for the four landing figures.

Waiver-ceiling reporting

If you enter a waiver ceiling, Loft reports the fraction of the dispersed flights whose apogee topped it — the “chance of busting the ceiling” a high-power flyer weighs against an altitude limit. It is read from the samples already flown (changing it doesn't re-fly), and it inherits the model's own systematic apogee error, so it is a planning cue to keep real margin against — never a guarantee.

Live weather (optional)

The “re-fly for today” feature fetches current surface conditions and a pressure-level winds-aloft profile from Open-Meteo (keyless, no account), the same source the sibling Window uses. It is the only part of Loft that touches the network, always behind an explicit tap.

The two halves of that fetch come from different parts of one response, so Loft pairs them explicitly. Surface temperature, pressure and wind are the live observation; the winds-aloft profile is one hour of a 24-hour forecast day, and Loft takes the hour that matches the surface reading's own timestamp. The Conditions panel states which hour the profile is for, because a drift estimate is only as current as the wind above the pad.

Between two pressure levels the wind speed is interpolated linearly and the direction is interpolated along the shorter compass arc — a bearing is not a scalar, and interpolating one as though it were reverses the wind at every layer whose levels straddle north.