Rating and Debrief
The flight rating is a score from 0 to 1000 with a band word, built from four part scores of 0 to 100 each: climbing, gliding, speed and decisions. The pilot sees it on the gauge at the top of the Debrief, on the Debrief’s section cards and in the Rating section of Compare. The Debrief then answers a set of fixed questions, one section per part plus a Task section. Every number comes from the flight’s own analysis. No glider polar and no model is involved. The behaviour is described in the feature page Debrief and flight rating.
iOS is the reference. Android (:domain:debrief) is a line-by-line port: the same formulas, the same constants and
the same floating-point order, with Swift’s rounded() reproduced as round-half-away-from-zero. Each section below
says “same as iOS” unless a difference is listed.
Inputs this page relies on, specified elsewhere:
| Input | Specified in |
|---|---|
Thermal fields: vario (the thermal’s average climb), duration, altitudeGained, isAttempt (altitudeGained ≤ 0), altitudeLost, turn, entryTime, fixes | Flight analysis §2.3, §5.3–5.4 |
Leg (glide) fields: distance (a 5-turnpoint optimised distance, not the path length), altitudeDifference, averageSpeed, averageGlideRatio | Flight analysis §2.4, §5.5 |
| Take-off, release, landing, engine runs | Flight analysis §5.1 |
| Wind per thermal | Flight analysis §5.4 |
| Free distance and its average speed; best one-hour optimisation | Flight analysis §6, §7.6 |
Best task attempt, isCompleted, averageSpeed, start and finish height and ground speed, averageGlideRatio, averageCruiseSpeed | Flight analysis §2.8, §7.5 |
Terrain elevation per fix, isElevationAvailable | Flight analysis §8.1 |
| The flight’s average glide ratio and cruise speed | Flight figures |
| Leader analysis (thermal matching) and leeching analysis | Flight analysis §2.9, Compare, ranking and statistics |
| Distances between coordinates | Flight analysis §7.1 |
Formatting (units, timeValue, percentageValue) | Flight analysis §9 |
1. The scope: the task, or the whole flight
Section titled “1. The scope: the task, or the whole flight”Most calculations read a scope of the flight rather than the whole track.
- If the flight has a best task attempt whose first fix is earlier than its last fix, the scope is the time range
[first.rawTime, last.rawTime]of that attempt’s fixes. Answers read over this scope say “On task” before their summary. - Otherwise the scope is the whole flight.
| Scope value | Definition |
|---|---|
| Thermal in scope | Its first fix’s rawTime (or entryTime without fixes) lies in the range |
| Glide in scope | Its start fix’s rawTime lies in the range |
thermals | The real thermals (actualThermals: isAttempt == false) in scope, in time order |
allThermals | Every thermal in scope, attempts included |
averageClimb | Σ altitudeGained / Σ duration over allThermals, m/s; nil when Σ duration = 0 |
thermalCount | allThermals.count |
thermallingTime | Σ duration over allThermals, s |
thermallingShare | thermallingTime / total × 100, where total is the range length on a task, else flight.duration; nil when total ≤ 0 |
thermals(turning:) | For one turn direction: count; climb Σ altitudeGained / Σ duration of those thermals (nil if the duration is 0); share count / allThermals.count × 100 (nil with no thermals) |
fixes | The fixes whose rawTime lies in the range (all fixes without a task) |
lowestFix, highestFix | Minimum / maximum pressureAltitude over fixes; without a task the flight’s own lowest and highest fix |
wind | The flight’s wind estimates (keyed by thermal entry time) whose key lies in the range |
The soaring part and its low point
Section titled “The soaring part and its low point”The release time is the release fix’s rawTime; without a release, the take-off’s rawTime + 600 s; without a
take-off, 0.
soaringFixes are the scope’s fixes with rawTime > release and rawTime ≤ the last fix of the scope’s last real
thermal. There are none when the scope has no real thermal. This keeps the tow and the landing approach out.
soaringLowestFix:
- With terrain loaded (
isElevationAvailable): the soaring fix with the smallestpressureAltitude − elevation. - Without terrain: the soaring fix with the smallest
pressureAltitude.
heightAboveGround(fix) = max(0, pressureAltitude − elevation) in metres, or nil without terrain.
Ties in a minimum or maximum keep the first element in time order, on both platforms.
Source: vulture-ios/Packages/FlightScreens/Sources/FlightScreens/FlightDebrief/FlightScope.swift (FlightScope);
vulture-android/domain/debrief/src/main/kotlin/be/fousa/vulture/domain/debrief/FlightScope.kt. Same as iOS.
2. The four part scores
Section titled “2. The four part scores”2.1 The scale
Section titled “2.1 The scale”Every part maps a figure onto 0–100 with one helper:
scaled(value, low, high) = clamp((value − low) / (high − low) × 100, 0, 100)scaled(value, low, high) = 0 when high == lowlow scores 0 and high scores 100. low may be larger than high for a figure where less is better.
A part that has nothing to read from returns no score. It is left out of the rating, and its card says “Not rated”.
2.2 Climbing
Section titled “2.2 Climbing”What the pilot sees: “2.15 m/s · 71 % of your best climb · 9 min lost in weak climbs”.
Conditions (otherwise no climbing score):
- At least 4 real thermals in scope (
scope.thermals.count ≥ 4). A ridge or wave flight is not rated on its climbing. scope.averageClimbexists.- The best lift exists and is greater than 0.
scope.thermallingTime > 0.
Best lift (the day’s best climb as a pilot means it):
- Take the real thermals in scope that lasted at least 120 s. If fewer than 2 did, take all real thermals in scope.
- Sort their
variovalues from high to low. - Take the top
max(2, n / 4)values (integer division). - Best lift = the mean of those values.
Time lost in weak climbs (also the “Where I lost time” answer, §4.2):
for each real thermal in scope with vario < averageClimb and altitudeGained > 0: lost = duration − round(altitudeGained / averageClimb) (seconds) keep it if lost > 0total lost = Σ lostNothing is lost when averageClimb is missing or ≤ 0.
Score:
liftUsed = averageClimb / bestLiftlostShare = totalLost / thermallingTimeclimbing = round(0.6 × scaled(liftUsed, 0.35, 0.75) + 0.4 × scaled(lostShare, 0.30, 0.0))| Constant | Value | Meaning |
|---|---|---|
| Minimum real thermals | 4 | Below it, no score |
| Lasting thermal | 120 s | Duration for the best-lift sample |
| Best-lift sample | top max(2, n/4) | Of the lasting thermals |
| Lift used: 0 points | 0.35 | 35 % of the best lift |
| Lift used: 100 points | 0.75 | 75 % of the best lift |
| Lost share: 0 points | 0.30 | 30 % of the thermalling time lost |
| Lost share: 100 points | 0.0 | Nothing lost |
| Weights | 0.6 / 0.4 | Lift used / lost share |
Facts on the card: the average climb; “x % of your best climb” (liftUsed as a whole percentage); and
“t lost in weak climbs” (in hours and minutes) only when totalLost > 0.
Note that averageClimb includes the attempts (height and time), while the best lift and the time lost read only the
real thermals.
2.3 Gliding
Section titled “2.3 Gliding”What the pilot sees: “18.4 km detour (12 %) · L/D 42:1”.
The gliding score measures how far the cruise track went beyond the straight line.
The line and the detour:
- Task: if the flight has a task and a best task attempt with
distance > 0, the line is the attempt’sdistance. The detour is the sum of the per-leg detours of the task’s legs (OptimizedLeg.detourDistance, a leg without one counts 0). - Otherwise: if the free distance is greater than 0, the line is the free distance. The detour is the
whole-flight detour (below), or 0 when that is
nil. - Otherwise there is no gliding score.
If the line is shorter than 10,000 m, there is no gliding score.
Cruise track length. Both detours walk a list of fixes and collapse every thermal into a straight line:
cruise = 0; i = 1while i < fixes.count: if fixes[i] belongs to a thermal T: last = index of T's last fix in the list cruise += distance(fixes[i−1], fixes[last]); i = last + 1 else: cruise += distance(fixes[i−1], fixes[i]); i = i + 1detour = cruise − line; nil when detour ≤ 100 m- Whole-flight detour (
flightDetour): all the flight’s fixes, against the free distance.nilwith fewer than 2 fixes or no free distance. - Task-leg detour (
detourDistance): the leg’s fixes, from the fix credited at the previous turnpoint to the fix credited at this one, against the straight distance between those two fixes.nilwith fewer than 2 fixes.
Score:
share = detour / linegliding = round(scaled(share, 0.28, 0.08))| Constant | Value | Meaning |
|---|---|---|
| Minimum line | 10,000 m | Below it, no score |
| Detour: 0 points | 28 % | A wander |
| Detour: 100 points | 8 % | Clean |
| Insignificant detour | ≤ 100 m | Counted as none |
The thresholds come from eight logbook flights measured on 2026-10-07: good pilots on assigned tasks fly 10 to 20 % over the line.
Facts: “d detour (x %)”, then “L/D n” from the flight’s average glide ratio when it has one. The glide ratio is shown but not scored: scoring it would need the glider’s index.
2.4 Speed
Section titled “2.4 Speed”What the pilot sees: “92.4 km/h · 2 of 5 on the day”.
Cross-country speed of a flight: the best task attempt’s averageSpeed if that attempt is completed, otherwise
the free distance’s averageSpeed. A flight with neither gets no speed score.
Among several flights. When the Debrief or Compare holds more than one flight, the speed is ranked among the others that have a cross-country speed:
rank = (number of other flights with a strictly higher speed) + 1count = (number of other flights with a speed) + 1speed = round(100 − 60 × (rank − 1) / (count − 1))First of the day scores 100 and last scores 40. Flights with equal speeds share the better rank. The fact “rank of count on the day” is added.
Alone. When no other flight has a speed, the speed is read against a fixed band for the detected sport:
speed = round(scaled(ownSpeed, low / 3.6, high / 3.6)) ownSpeed in m/s| Sport | 0 points | 100 points |
|---|---|---|
| Glider | 50 km/h | 120 km/h |
| Hang glider (delta) | 20 km/h | 60 km/h |
| Paraglider | 15 km/h | 45 km/h |
The speed is not handicapped by the glider’s index.
2.5 Decisions
Section titled “2.5 Decisions”What the pilot sees: “412 m above the ground at 14:32 · 15 % of thermals abandoned”.
The score starts at 100 and loses points:
| Deduction | Points | Condition |
|---|---|---|
| Low point | −35 | Low point height < 300 m |
| Low point | −15 | 300 m ≤ low point height < 500 m |
| Engine | −20 per run | Each engine run starting after the release time |
| Abandoned thermals | −10 | More than 25 % of the thermals in scope are attempts (needs ≥ 4 thermals) |
| Height lost in thermals | −10 | Height lost inside thermals > 10 % of the height gained (needs ≥ 4 thermals) |
decisions = round(max(0, 100 − deductions))Low point height:
- Take
soaringLowestFix(§1). Without one, there is no low-point deduction or fact. - With terrain loaded: the height is
heightAboveGroundof that fix. Fact: “h above the ground at time”. - Without terrain: the height is
lowest.pressureAltitude − takeoff.pressureAltitude. Fact: “h above take-off at time”. The take-off stands in for the ground. This is skipped (no deduction, no fact) when there is no take-off fix, the take-off’s pressure altitude is 0, or the low point is below the take-off (a launch from a slope).
Engine runs: runs whose start fix’s rawTime is greater than the release time. Fact: “Engine used for t”, the
sum of their durations.
Thermalling, only when the scope has at least 4 thermals (attempts included):
attemptShare = attempts / allThermals.count. Deduct 10 ifattemptShare > 0.25. Fact “x % of thermals abandoned” when there is at least one attempt.gained = Σ altitudeGained,lost = Σ heightLostoverallThermals, where a thermal’s height lost is−altitudeLost(0 or positive). Deduct 10 ifgained > 0andlost / gained > 0.10. Fact “h lost again in thermals” whengained > 0andlost > 0.
If no fact was collected (no low point, no engine run and no thermalling fact), there is no decisions score.
P5-4, fixed on both platforms 2026-10-20 (Calculation fixes §1).
Thermal.altitudeLostis the sum of the negative fix-to-fix deltas, so it is never above 0; the readers here and in §4.5 used to ask foraltitudeLost > 0, which never held, so the deduction, the fact and the question never applied. Both platforms now read the height lost,−altitudeLost(Thermal.heightLost).
Source (all parts):
vulture-ios/Packages/FlightScreens/Sources/FlightScreens/FlightRating/FlightRating+Compute.swift (climbing(in:),
bestLift(among:), gliding(of:), speed(of:among:), crossCountrySpeed(of:), decisions(in:),
thermallingDeductions(in:), lowPoint(in:)), FlightRating.swift (scaled);
vulture-ios/Packages/FlightScreens/Sources/FlightScreens/FlightDebrief/FlightQuestionAnswering.swift
(timeLost(in:)), FlightQuestionAnswering+Flight.swift (flightDetour(of:));
vulture-ios/Packages/FlightScreens/Sources/FlightScreens/Models/DetailOptimizationItem+Legs.swift (detourDistance).
Android: vulture-android/domain/debrief/src/main/kotlin/be/fousa/vulture/domain/debrief/FlightRating.kt,
FlightQuestions.kt (timeLost, flightDetour). Same as iOS.
3. The rating
Section titled “3. The rating”3.1 Combining the parts
Section titled “3.1 Combining the parts”A flight is rated only if it has a free distance or a best task attempt. It also needs at least one part score. Otherwise there is no rating, and the Debrief opens without a gauge.
| Part | Weight |
|---|---|
| Climbing | 0.30 |
| Gliding | 0.25 |
| Speed | 0.25 |
| Decisions | 0.20 |
Missing parts hand their weight to the others: the weights of the parts present are scaled to sum to 1.
W = Σ weight of the parts presentweighted = Σ (partScore × weight) / W (0 when W = 0)rating = round(weighted × 10) 0 … 1000Example: climbing 80, gliding 60, speed 70, no decisions score: W = 0.80,
weighted = (24 + 15 + 17.5) / 0.80 = 70.625, rating 706.
3.2 Band words
Section titled “3.2 Band words”| Rating | Band | Colour |
|---|---|---|
| 0–399 | Rough | Red |
| 400–549 | Fair | Orange |
| 550–699 | Solid | Yellow |
| 700–849 | Strong | Green |
| 850–1000 | Outstanding | Brand blue (secondary tint) |
A part’s card takes the colour of the band of partScore × 10. For example, a climbing score of 72 is coloured as
720, Strong.
3.3 Rating a flight among several flights
Section titled “3.3 Rating a flight among several flights”rate(flight, among: flights) is called with every flight on the map (the Debrief) or in the comparison (Compare),
the rated flight included. Only the speed part depends on the other flights (§2.4). Climbing, gliding and
decisions read only the flight itself. The same flight can therefore score differently alone and on a competition
day.
Compare rates every compared flight among all of them. The Debrief rates the selected flight and keeps each flight’s rating once computed, by object identity, so moving the selection back and forth does not rate again.
Source: vulture-ios/Packages/FlightScreens/Sources/FlightScreens/FlightRating/FlightRating.swift
(init(components:), Band, Component.Kind.weight), FlightRating+Compute.swift (rate(_:among:)),
FlightDebrief/FlightDebriefViewModel.swift (ratings), Compare/CompareFlightsViewModel.swift;
Android: FlightRating.kt (rate, weighing, Band.of), FlightRatings.kt,
vulture-android/domain/compare/src/main/kotlin/be/fousa/vulture/domain/compare/CompareData.kt. Same as iOS.
3.4 The task card’s leg strip
Section titled “3.4 The task card’s leg strip”The Task section’s card draws every declared leg as a bar. Its width is the leg’s share of the declared task distance: the sum of the waypoint-to-waypoint distances, or equal shares when that sum is 0. Its colour depends on the leg’s speed against a reference speed:
- Reference: the attempt’s
averageSpeedif the task was completed and that speed is above 0. Otherwise the mean of the flown legs’ speeds. - A leg not flown is grey. With no reference speed, a flown leg is Solid.
| Leg speed / reference | Band |
|---|---|
| < 0.75 | Rough |
| 0.75 – < 0.90 | Fair |
| 0.90 – < 1.05 | Solid |
| 1.05 – < 1.20 | Strong |
| ≥ 1.20 | Outstanding |
Status: “Completed” (with distance and speed), “n of m legs” (n = legs with a speed), “Not started” (with the declared distance) or “No task”.
Source: FlightDebrief/DebriefSection.swift (DebriefTaskSummary.make, color(forSpeed:)); Android
DebriefSection.kt (bandForSpeed). Same as iOS.
4. Debrief questions
Section titled “4. Debrief questions”4.0 Common rules
Section titled “4.0 Common rules”When a question is offered (FlightQuestion.applies(to:)):
| Questions | Offered when |
|---|---|
| Best thermal, Weakest thermal, Longest glide, Where I lost time, Where I stayed too long, My lowest point, My strongest hour, Left or right, The detour, Glides that cost the most height, What made up my speed | At least one flight |
| The wind | Some flight has wind estimates |
| Engine use | Some flight has an engine run |
| My weakest hour | Some flight lasted at least 90 min |
| Thermals I abandoned | Some flight has an attempt |
| Where I lost height in thermals | Some flight has a thermal whose height lost (−altitudeLost) is above 0 |
| The task, leg by leg; My slowest leg | Some flight has a task |
| My start | Some flight has a best task attempt |
| My final glide | Some flight completed its task |
| The task’s rules | Some flight’s task has a start maximum height > 0, a finish minimum height, or an AAT minimum time |
| Who climbed best, Who was highest, Who had the best glide ratio, Who thermalled least, The day’s best thermal, Who flew farthest, Who needed the fewest thermals, Who was lowest, Who followed whom | More than one flight |
| Who flew the task fastest, Who had the fastest final glide, My legs against the fastest, Who started when, Thermals the winner used and I passed | More than one flight, and some flight has a best task attempt |
A section is shown when at least one of its questions is offered. Within a section, the questions about the selected flight come first (“This flight”), then those among the flights (“Among the flights”).
Rows. Single-flight answers are about the selected flight.
- A thermal row reads “Thermal n · time” with “+gain · vario · duration”. n is the thermal’s 1-based position in all the flight’s thermals (attempts included). The time is the first fix’s local time.
- A glide row reads “Glide n · time” with “distance · speed · L/D ratio”. n is the glide’s 1-based position in the flight’s glides.
- A flight title is the competition ID (or registration) in upper case and the pilot’s name, joined by ” · ”. If neither exists, the flight’s date.
“On task” is put before a single-flight summary when the scope is the task. For a ranking it is used only when every flight’s scope is its task.
Task legs come from the attempt’s leg breakdown (DetailOptimizationItem.legs): one leg per pair of consecutive
credited fixes, with the unflown waypoints appended. A flown leg has a duration (last fix − first fix), a
distance (straight line between those fixes) and a speed distance / duration. Legs are numbered 1…n by their
position in the list.
4.1 Task
Section titled “4.1 Task”| Question | Answer |
|---|---|
| The task, leg by leg | Summary: “Completed at speed” (attempt averageSpeed), “Not completed, distance” (attempt distance), or “The task was not started” (no attempt). One row per leg: “Leg n to waypoint”, with ”· Final glide” on the last leg; values: leg distance, leg speed, L/D (the leg’s cruise glide ratio), “k thermals” (the real thermals fully inside the leg). |
| My start | Row “Start · time”: the attempt’s start height (startAltitude, else the first fix’s pressure altitude) and start ground speed. Row “First climb”: the scope’s first real thermal. Summary “Away r of n”: among the flights with an attempt, r = 1 + the number that started strictly earlier (by first-fix rawTime). Shown only with more than one start. |
| My final glide | Only for a completed task. The leg is the last leg with a speed. Row “Final glide · time”: distance, speed, duration (last − first fix), L/D, and the height used first.pressureAltitude − last.pressureAltitude (shown when > 0). Row “Finish · time”: finish height (finishAltitude, else the last fix’s pressure altitude), finish ground speed. Summary “Finished h under the minimum height” when the attempt has a minimumFinishAltitudeDifference (its absolute value). |
| The task’s rules | One row per rule that applies; each says what the flight did, the limit, and “kept” or the excess. Start height: the zone’s maximumAltitude > 0 against startAltitude; broken when the height is above the maximum (“d over”). Finish height: the finish zone’s minimumAltitude, completed tasks only, against finishAltitude; broken when below (“d under”). Task time: the AAT minimum time, completed tasks only, against last − first attempt fix; broken when shorter (“d short”). Summary “Every rule kept” or “Rules broken: n”. Airspace is not checked. |
| My slowest leg | Needs more than one flown leg. Row: the flown leg with the lowest speed (speed, distance, duration). Summary “Average leg v”: the mean of the flown legs’ speeds (not distance-weighted). |
| Who flew the task fastest | The flights with an attempt: completed ones first, then by averageSpeed, highest first. A completed flight shows its speed; others show “Not completed, distance”. |
| My legs against the fastest | The fastest flight is the completed one with the highest averageSpeed. If it is the selected flight: “You were the fastest.” Otherwise the flown legs are paired by position, up to the shorter list. Each row shows “my speed vs their speed” and myDuration − theirDuration as “t behind” (> 0) or “t ahead”. The summary sums the differences: “Against flight, completed at speed · t behind/ahead in all”. |
| Who started when | The flights with an attempt, by start fix time, earliest first: start time, start pressure altitude, start ground speed. |
| Who had the fastest final glide | The completed flights, by the speed of their last flown leg, fastest first: speed, distance, duration. |
| Thermals the winner used and I passed | The fastest completed flight (as above). If that is the selected flight: “You were the fastest.” Otherwise the scope’s real thermals of both flights are matched with the leader analysis: a pair qualifies when their time gap max(entries) − min(exits) is at most 120 s and their centres are at most 1,000 m apart. Pairs are matched greedily, closest first. The winner’s unmatched thermals, sorted by height gained (highest first), up to 8 rows. Summary “flight: k thermals you passed, m shared”. |
4.2 Climbing
Section titled “4.2 Climbing”| Question | Answer |
|---|---|
| Best thermal | The scope’s real thermal with the highest vario. Summary: “Average climb v” (scope.averageClimb). |
| Weakest thermal | The scope’s real thermal with the lowest vario. Same summary. |
| Where I lost time | The thermals of §2.2’s time-lost list, most time lost first, up to 5 rows: “Lost t · vario”. Summary: “total lost in all · Average climb v”. |
| Where I stayed too long | For each real thermal in scope with at least 9 fixes and vario > 0, the tail is its last ⌊count / 3⌋ fixes. tailVario is the mean of the tail fixes’ vario. The thermal is listed when tailVario < vario / 2, with lost time = tail last rawTime − tail first rawTime. Sorted by lost time, up to 5 rows: “Last third t · tailVario vs vario”. Summary “total lost in all”. |
| Left or right | scope.thermals(turning: .left) and (.right) (§1, attempts included): count, share of thermals in %, climb. Summary “Left climbed better” or “Right climbed better” when both climbs exist and differ. Mixed-turn thermals count in neither row. |
| Who climbed best | The flights by scope.averageClimb, highest first, with the thermal count. |
| The day’s best thermal | All real thermals in scope of all flights, by vario, highest first; top 3, each with its flight. |
4.3 Gliding
Section titled “4.3 Gliding”| Question | Answer |
|---|---|
| The detour | Row “The whole flight”: the whole-flight detour (§2.3), when not nil. Row “Worst leg · Leg n to waypoint”: the task leg with the largest detourDistance, when it has one. |
| Longest glide | The scope’s glide with the largest distance. |
| Glides that cost the most height | The scope’s glides with distance ≥ 3,000 m, altitudeDifference < 0 and a glide ratio. Sorted by glide ratio, lowest first; top 3, each with “−height lost”. Summary “Average L/D n”: on a task, the attempt’s averageGlideRatio; otherwise (or when that is missing) the flight’s average glide ratio. |
| The wind | Over the scope’s wind estimates: row “Average wind” (mean of the speeds; the directions are not averaged) and row “Strongest wind · time” (speed and direction of the strongest estimate, with its thermal). |
| Who had the best glide ratio | The flights by the flight’s average glide ratio (whole flight, not scoped), highest first, with average cruise speed. |
4.4 Speed
Section titled “4.4 Speed”| Question | Answer |
|---|---|
| What made up my speed | Row “Cruise”: on a task the attempt’s averageCruiseSpeed, else the flight’s average cruise speed; plus L/D the same way. Row “Circling”: scope.thermallingShare % and thermallingTime. Row “Climb”: scope.averageClimb and “n thermals” (thermalCount, attempts included). Summary “Cross-country speed v”: on a task, the completed attempt’s averageSpeed; otherwise (including an uncompleted task) the free distance’s averageSpeed. |
| My strongest hour | The flight’s best one-hour optimisation (Flight analysis §7.6): row “Best hour · start time”, its distance and average speed. The map stretch runs from the fix at its first index to the fix at its last index. |
| My weakest hour | See §4.4.1. |
| Who flew farthest | The flights by free distance, farthest first, with its average speed. Whole flight, not scoped. |
| Who thermalled least | The flights by scope.thermallingShare, lowest first, with thermalling time and thermal count. |
| Who needed the fewest thermals | The flights by scope.thermalCount (attempts included), fewest first, with average climb and, for a completed task, the task speed. |
4.4.1 My weakest hour
Section titled “4.4.1 My weakest hour”-
Flown fixes. On a task, the scope’s fixes. Otherwise the fixes from the release time (§1) to the landing fix’s
rawTime, both inclusive (no landing: to the end). -
If the flown fixes span less than 90 min (last − first
rawTime), there is no answer. -
Covered distance along the fixes, where a thermal covers only the straight line from its entry to its exit:
covered[0] = 0for i in 1..<n:if fixes[i] and fixes[i−1] belong to the same thermal T:step = (i is T's last fix in the list) ? distance(fixes[entry], fixes[i]) : 0else:step = distance(fixes[i−1], fixes[i]); if fixes[i] is in a thermal: entry = icovered[i] = covered[i−1] + step -
Sliding window. For each start index s, e is the first index with
rawTime[e] − rawTime[s] ≥ 3600. Stop when there is none. The hour’s distance iscovered[e] − covered[s]. Keep the smallest; ties keep the earliest. -
Rows:
- “Weakest hour · time”: its distance and
distance / (rawTime[e] − rawTime[s])as a speed. - “Circling”: the time inside the window spent in the scope’s thermals (attempts included), each thermal clipped to the window, as a % of the window and as a duration.
- “Climb”: for the thermals entered inside the window,
Σ altitudeGained / Σ durationand their count (only whenΣ duration > 0).
- “Weakest hour · time”: its distance and
-
Summary “Average pace v”:
covered[last] / (last − first rawTime)over all the flown fixes.
The question is offered when the flight’s total duration is at least 90 min. The answer then measures the flown
fixes, which can be shorter, so the answer can still be empty.
4.5 Decisions
Section titled “4.5 Decisions”| Question | Answer |
|---|---|
| My lowest point | Row “Lowest point · time”: soaringLowestFix, or the scope’s lowestFix when the soaring part is empty. Value “h above the ground · altitude” with terrain, else the pressure altitude alone. Row “The save”: the first real thermal in scope whose first fix is at or after the low point. |
| Thermals I abandoned | Every attempt in scope, in time order: height gained and duration. Summary “a of n thermals abandoned, t in all” (n = allThermals.count, t = the attempts’ total duration). |
| Where I lost height in thermals | The thermals in scope whose height lost (−altitudeLost) is above 0, by height lost, most first; top 5: “lost h” (positive), the gain and the vario. Summary “lost lost of gained gained” (lost = Σ height lost, gained = Σ max(0, altitudeGained)). |
| Engine use | Every engine run of the flight (not scoped): “Run n · time”, duration and start pressure altitude. Summary “n runs, t in all”. |
| Who was lowest | Each flight’s soaringLowestFix (or scope lowestFix). If every flight has terrain, they are ranked by height above the ground; otherwise by pressure altitude. Lowest first. |
| Who was highest | Each flight’s scope highestFix, by pressure altitude, highest first. |
| Who followed whom | See §4.5.1. |
4.5.1 Who followed whom
Section titled “4.5.1 Who followed whom”This answer is computed in the background (“Reading the tracks…”). It is computed once per set of flights and then kept.
- Fixes per flight: the fixes from the best task attempt’s first index to its last index, or the whole flight without an attempt.
- Leeching: the pairwise leeching analysis runs over every pair of flights. Its windows are merged into one summary per pilot. The algorithm, its constants and the classification are in Compare, ranking and statistics and Flight analysis §2.9. In short: independent if the total window time is under 60 s; follower if the follow ratio is ≥ 0.75; leader if it is ≤ 0.25; otherwise racing.
- Flew alone: for each fix of a flight, it is not alone if some other flight has a fix within 10 s of it (the
nearest in time) that is within 1,000 m. Flew alone % = alone fixes / all fixes × 100.
nilwith ≤ 1 fix or no other flight. - Row per flight: the classification. When the window duration is > 0, also “led t” and “followed t”. Then “x % alone” when known.
Source (all questions):
vulture-ios/Packages/FlightScreens/Sources/FlightScreens/FlightDebrief/FlightQuestion.swift (applies(to:),
section, isAmongFlights), FlightQuestionAnswering.swift, FlightQuestionAnswering+Flight.swift,
FlightQuestionAnswering+Flights.swift, FlightQuestionAnswering+Task.swift,
FlightQuestionAnswering+Breakdown.swift, FlightQuestionAnswering+Hour.swift,
vulture-ios/Packages/FlightScreens/Sources/FlightScreens/Compare/CompareAnalyses.swift,
Compare/CompareFlightsViewModel+Proximity.swift (nearestFix),
vulture-ios/Packages/Flights/Sources/Flights/Models/LeaderAnalysis.swift.
Android: vulture-android/domain/debrief/src/main/kotlin/be/fousa/vulture/domain/debrief/FlightQuestion.kt,
FlightQuestionAnswering.kt, FlightQuestionAnsweringFlight.kt, FlightQuestionAnsweringFlights.kt,
FlightQuestionAnsweringTask.kt, FlightQuestionAnsweringBreakdown.kt, FlightQuestionAnsweringHour.kt,
CompareAnalyses.kt; the screens in vulture-android/feature/debrief only draw them.
5. Platform differences
Section titled “5. Platform differences”The numbers are the same on both platforms. These are the only behaviour differences:
| Where | iOS | Android |
|---|---|---|
| The wind: average and strongest | Reads the wind dictionary in hash order. The floating-point sum of the average, and which of two equally strong estimates is “strongest”, can differ from run to run. | Reads the estimates in time order. Deterministic. |
| My strongest hour, unusable indexes | The map stretch falls back to the optimisation’s turnpoint fixes alone. | The stretch covers the track from the first turnpoint to the last. |
| Places on the map | Hold the Flight, Thermal, Leg or fixes. | Hold the flight id and indexes. The same places. |
Earlier differences in leg numbering and the final-glide label (Android B31–B33) are now fixed on iOS too: the answers number legs 1…n by position and mark the last leg as the final glide. A zero-length leg (two turnpoints credited at one fix) is dropped without bringing the turnpoint back as unflown.
6. Edge cases
Section titled “6. Edge cases”- No rating: no free distance and no task attempt, or no part could be scored (§3.1).
- Divisions by zero are guarded:
scaledwithhigh == lowgives 0;averageClimband the turn climb arenilwithout thermalling time; the speed rank’scount − 1is never 0 because ranking needs at least one other flight; the weighted rating is 0 with no weights. - Rounding: part scores and the rating round half away from zero. The rating is rounded once, after the weighing, from the already-rounded part scores. Time lost per thermal rounds the time at the average climb to whole seconds.
- Percentages in facts (“71 %”) are whole percentages of a 0–1 share. Durations in facts use hours and minutes; durations in answers use the app’s time format (Flight analysis §9).
- Scope mismatch: gliding and the speed rank read the attempt or the whole flight directly, not
FlightScope. “Who had the best glide ratio”, “Who flew farthest” and “Engine use” are whole-flight figures even on a task.
