Calculation fixes
Summary
Section titled “Summary”Writing the Calculations section from the code turned up five numbers that do not show what they say. The owner asked for all five to be fixed on 2026-10-20. Three are on both platforms, two only on iOS (Android has no recording or weather):
| # | What the pilot sees | Platforms | Section |
|---|---|---|---|
| 1 | Height lost in thermals never counts: no deduction in the rating’s Decisions part, and “Where I lost height in thermals” is never offered (P5-4) | iOS, Android | Height lost |
| 2 | “Lowest entry altitude” on the Thermals page shows the flight’s lowest point, not the lowest thermal entry | iOS, Android | Lowest entry |
| 3 | The statistics’ “Total altitude” (Thermals) sums each flight’s largest single climb, not its height gained in thermals | iOS, Android | Thermal height |
| 4 | The in-flight AGL cell shows metres next to the pilot’s altitude unit | iOS | AGL cell |
| 5 | METAR cloud layers reported as broken (BKN) are dropped | iOS | Broken clouds |
Fixes 1 and 3 change numbers pilots already know (the rating, the statistics), so each lands on both platforms in the same release.
Behaviour
Section titled “Behaviour”1. Height lost in thermals
Section titled “1. Height lost in thermals”Thermal.altitudeLost is the sum of the drops between consecutive fixes, so it is 0 or negative
(Thermals). Every reader below asks for altitudeLost > 0, which is never true.
The rule from now on: the height lost in a thermal is −altitudeLost (0 or positive). altitudeLost itself
keeps its sign, so the thermal sheet (which shows it as a drop) does not change.
| Reader | Today | New |
|---|---|---|
Question “Where I lost height in thermals”, applies | a thermal with altitudeLost > 0 (never) | a flight with a thermal whose height lost is above 0 |
| Its answer | thermals with altitudeLost > 0, by altitudeLost, most first, top 5 | thermals with height lost above 0, by height lost, most first, top 5; each row shows “lost h” with h positive, then the gain and the vario; the summary’s “lost” is Σ height lost |
| Rating, Decisions part | lost = Σ altitudeLost; −10 when gained > 0 and lost / gained > 0.10 (never); fact when lost > 0 (never) | lost = Σ height lost over the scope’s thermals; the same −10 and the same fact, which now apply |
Everything else in the rating stays: gained = Σ altitudeGained over the same thermals, the threshold 0.10, the 10
points. Worked example (from ios-changes P5-4): a thermal through 1000, 1050, 1030, 1080 m has altitudeLost = −20,
so it lost 20 m; a flight with it offers the question and lists that thermal.
Effect: a flight that lost more than a tenth of its thermal gains again inside the thermals loses 10 points in Decisions, about 20 points off the 0–1000 rating (Decisions weighs 0.20). The rating is computed when the Debrief or Compare opens, so no stored data changes.
2. Lowest entry altitude
Section titled “2. Lowest entry altitude”The Thermals page’s “Lowest entry altitude” shows lowestThermalEntryFix’s pressure altitude: the lowest
entryAltitude of a thermal that is not an attempt, which the pipeline already computes
(Flight analysis §5.4). With no such thermal the row shows no value, as any missing
value does. lowestAltitudeFix keeps its other uses (the barogram, the Debrief’s scope).
3. Total altitude in the statistics
Section titled “3. Total altitude in the statistics”“Total altitude” in the statistics’ Thermals group becomes the sum, over the year’s logbook flights, of each
flight’s height gained in thermals: Σ altitudeGained of its thermals that are not attempts (in metres, as an
integer). The label stays. The stored altitudeGained (the largest single climb in a gliding block) keeps its
other uses: the logbook row’s figure and the sync summary.
The statistics read stored values, not the IGC, so this needs a new stored value; see Data.
4. The in-flight AGL cell
Section titled “4. The in-flight AGL cell”The iPhone’s in-flight AGL cell formats its value like the MSL cell: converted to the pilot’s altitude unit
(altitudeString), not the raw metres (stringValue). The Live Activity already does this.
5. Broken cloud layers
Section titled “5. Broken cloud layers”CloudData reads coverage BKN as Broken (it reads BKC today, which METARs never use). BKN030 gives Broken at
3,000 ft. SKC, CLR and VV keep their current handling.
One new per-flight value, for fix 3:
| Field | Type | Meaning |
|---|---|---|
thermalAltitudeGained | integer, metres, optional | Σ altitudeGained of the flight’s thermals that are not attempts. Absent: not computed yet |
- iOS: a new optional
CoreFlightattribute (Core Data lightweight migration), set inCoreFlight+CoreDatainsertandupdatenext toaltitudeGained. - Android: a new
FlightEntitycolumn (Room migration), set inFlightRows.withAnalysis, and a new key in the Firestoresummarymap. Update Sync schema §summary:thermalAltitudeGained, optional; an older app ignores it. - Existing flights: Android fills it by bumping
ANALYSIS_VERSION, which re-analyses every flight. iOS has no re-analysis of all flights and gets no backfill (decision 5). - Statistics while a flight has no value yet: the flight adds 0 to “Total altitude” (as a flight without thermals does), and the row’s other years are unaffected.
None.
Strings
Section titled “Strings”None new. The “lost h” value of the height-lost answer reuses flight.debrief.value.lost, now with a positive
height.
Decisions
Section titled “Decisions”| # | Question | Decision | Date |
|---|---|---|---|
| 1 | Fix the five numbers? | Yes, all five, on both platforms where they exist. | 2026-10-20 |
| 2 | P5-4: how to read the height lost? | As −altitudeLost at the three readers; altitudeLost keeps its sign. This settles P5-4. | 2026-10-20 |
| 3 | “Total altitude”: rename the row or change the value? | Change the value to the height gained in thermals; keep the label. | 2026-10-20 |
| 4 | Route | An analysis, built by the iOS and Android sessions (the app repos are read-only from vulture-docs). | 2026-10-20 |
| 5 | Backfill thermalAltitudeGained for old iOS flights? | No. An old iOS flight gets the value when it is next updated; until then it adds 0 to “Total altitude”. | 2026-10-20 |
Open questions
Section titled “Open questions”None.
Platforms
Section titled “Platforms”| # | Code (vulture-ios) | Change |
|---|---|---|
| 1 | Packages/FlightScreens/Sources/FlightScreens/FlightDebrief/FlightQuestion.swift (applies(to:), .heightLost), FlightQuestionAnswering+Breakdown.swift (heightLost(in:)), FlightRating/FlightRating+Compute.swift (thermallingDeductions(in:)) | Read −altitudeLost; update FlightQuestionBreakdownTests (it counts altitudeLost > 0 too) and add the 1000/1050/1030/1080 m test |
| 2 | Packages/FlightScreens/Sources/FlightScreens/FlightDetail/Tabs/DetailTabThermalsViewModel.swift | lowestThermalEntryFix instead of lowestAltitudeFix |
| 3 | Packages/Persistence/.../CoreFlight.swift, CoreFlight+CoreData.swift, the Core Data model; Packages/FlightScreens/Sources/FlightScreens/Statistics/FlightsStatisticsProviding.swift, FlightsStatisticsViewModel.swift (.totalAltiudeGained) | New attribute, set on insert and update; statistics sum it |
| 4 | Modules/App/Sources/Map/InFlight/InFlightMapViewModel.swift (aglDataItem.value = agl?.stringValue) | altitudeString, as the MSL cell |
| 5 | Packages/AviationWeather/Sources/AviationWeather/Models/CloudData.swift | case "BKN"; a test for BKN030 |
Android
Section titled “Android”| # | Code (vulture-android) | Change |
|---|---|---|
| 1 | domain/debrief/.../FlightQuestion.kt (HEIGHT_LOST), FlightQuestionAnsweringBreakdown.kt, FlightRating.kt (thermalling deductions) | Read -altitudeLost; drop the three PARITY comments; update FlightQuestionBreakdownTest and add the same test as iOS |
| 2 | domain/flightdetail/.../ThermalsTab.kt | lowestThermalEntryFix |
| 3 | data/db/.../entity/FlightEntity.kt + migration, data/db/.../FlightRows.kt, domain/sync/.../FlightCodec.kt and data/sync/.../FlightAdapter.kt (summary), domain/logbook/.../LogbookStatistics.kt, StatisticsFlight.kt (and its doc comment); bump ANALYSIS_VERSION | New column and summary key; statistics sum it |
Both: CHANGELOG.md entries (the rating can drop for flights that lost height in thermals; the statistics’ total
altitude now counts thermal height), and parity golden data if the rating or statistics are in it.
No changes.
- vulture-ios: fixes 1–5, with tests; changelog (2026-10-20). The AGL cell uses
mslString(altitudeStringwithout the unit), because the cell’s label already shows the unit. - vulture-android: fixes 1–3, with tests, the Room migration and the
ANALYSIS_VERSIONbump; changelog. - vulture-docs: Sync schema summary field (with the Android session).
- vulture-docs: when built, remove the bug notes from Rating and Debrief,
Thermals, Compare, ranking and statistics,
In flight and tools and Flight figures;
mark P5-4 done in ios-changes; set
status: shipped.
