Skip to content

Golden data

As of 2026-10-19 (vulture-ios 3176c4cc4, vulture-android 85fd9cc6).

Android’s results must match iOS’s. This is checked against golden data: JSON files that iOS test targets write from the iOS code, and that Android’s unit tests read. The data is never edited by hand. When the iOS analysis changes, export again and commit the new files to vulture-android.

The exporters are Swift Testing tests that are skipped unless an environment variable switches them on. With xcodebuild, pass each variable with a TEST_RUNNER_ prefix.

ExporterFileSwitchOutputWrites
Flightsvulture-ios/Packages/Flights/Tests/FlightsTests/Golden/GoldenDataExporter.swift, with GoldenFlightWriter*.swift, GoldenJSON.swift, GoldenReadme.swiftVULTURE_EXPORT_GOLDEN=1VULTURE_GOLDEN_DIR, default vulture-android/domain/flight/src/test/resources/golden (an absolute path on the owner’s Mac)One <fixture>.json per fixture IGC in Packages/Flights/Tests/Flights/: the whole FlightEnricher result. Also a README.md stamped with VULTURE_GOLDEN_COMMIT
Unitsvulture-ios/Packages/Preferences/Tests/PreferencesTests/UnitGoldenExporter.swiftVULTURE_EXPORT_GOLDEN=1Same directory<fixture>.units.json: every formattable value of the flight JSON, formatted in every unit. It reads the flight files, so run it after the Flights exporter
Airspacevulture-ios/Packages/Airspace/Tests/AirspaceTests/Golden/AirspaceGoldenExporter.swift (added 2026-10-19, a157827b2)AIRSPACE_GOLDEN_IN and AIRSPACE_GOLDEN_OUT (both required); AIRSPACE_GOLDEN_FULL keeps outlines for files over 20 000 pointsAIRSPACE_GOLDEN_OUT, meant for vulture-android/domain/airspace/src/test/resources/golden/Per file <file>.json (id, name, class, limits, frequency, transponder code, outline) and vectors.json with the parser vectors

The Flights exporter never downloads anything. A fixture with a <fixture>.elevation.json cache is analysed with it (elevationSource: "cache"), and the others without elevations ("none"). No default observation zones are set, and the sport is detected.

Export commands (from the README the exporter writes):

Terminal window
cd vulture-ios/Packages/Flights
xcodebuild test -scheme Flights -destination 'platform=iOS Simulator,name=<iPhone>' \
-only-testing:FlightsTests/GoldenDataExporter \
TEST_RUNNER_VULTURE_EXPORT_GOLDEN=1 \
TEST_RUNNER_VULTURE_GOLDEN_COMMIT=$(git rev-parse --short HEAD)
cd ../Preferences
xcodebuild test -scheme Preferences -destination 'platform=iOS Simulator,name=<iPhone>' \
-only-testing:PreferencesTests/UnitGoldenExporter \
TEST_RUNNER_VULTURE_EXPORT_GOLDEN=1
SetPath in vulture-androidTaken fromRead by
Flight analysis: 14 fixtures × (<name>.json + <name>.units.json). The fixtures are ben300, cylinder_start_no_pev, hang_glider, iv_egc_task6, jelle500, jelle600, koen, para_freeflight, para_marcourt, pev_cylinder_start, slow_glider, tijl1000, wim1000, wim40domain/flight/src/test/resources/golden/iOS GoldenDataExporter and UnitGoldenExporter. The README names iOS commit 033b43827. Committed to vulture-android on 2026-10-10 (fd906f52):domain:flightdetail DetailFixtureTablesTest, which checks the summary and pages of koen, iv_egc_task6, hang_glider, para_freeflight and tijl1000 against the iOS figures and the iOS unit strings
Fixture IGCs (plus 3 OBGN 2026 task-2 flights), elevation caches, time zonesdomain/flight/src/test/resources/{igc,elevation,timezone}iOS Packages/Flights/Tests/Flights/:domain:flight tests
Airspace: CTR, Klippeneck and Montlucon .cub, synthetic openAIP and OpenAir files, a Latin-1 CRLF OpenAir file, vectors.json, scan/domain/airspace/src/test/resources/golden/A Swift copy of the exporter run in a scratch copy of Packages/Airspace (vulture-android/domain/airspace/scripts/GoldenExporter.swift, laid out by scripts/format_golden.py), committed 2026-10-11 (909be6d7). iOS now has its own exporter (see above), which ios-changes A3 asked forAirspaceGoldenTest, AirspaceVectorsTest, AirspaceInfringementScannerGoldenTest
Zone outlinesdomain/waypoints/src/test/resources/zone_outlines_ios.txtiOS:domain:waypoints tests
Sync schema documentsdomain/sync/src/test/resources/schema/Android (for iOS to match when it joins)SchemaGoldenTest

033b43827 could not be resolved in the local vulture-ios checkout. The exporter itself arrived in 361c3486c (2026-10-10). The stamped hash may come from a branch or a history that was later rewritten. Take the next export with VULTURE_GOLDEN_COMMIT set to a commit on main.

The flight analysis itself is also checked against iOS by one-off parity dumps. These are described in vulture-android/domain/flight/README.md (“Parity evidence”), where a throwaway Swift test and ParityDumpTest / ScoringParityDumpTest write the same JSON. A fresh golden export is still to do: ios-changes A1, once iOS has the FIXED(Q…) fixes. Until then FixedQuirksTest and restoreIosQuirks cover the difference.

From the README the exporter writes:

  • Doubles are written with %.17g, so they round-trip exactly. "inf", "-inf" and "nan" stand for the non-finite values. null is a Swift nil.
  • Units are the iOS model’s: metres, m/s and seconds. The exceptions are distanceTraveled per fix (km), and percentages, which run from 0 to 100.
  • rawTime is seconds since UTC midnight of the flight date, and keeps counting up across midnight. time is the local time of day.
  • Indexes refer to arrays in the same file: a fix by its index in fixes, thermal into thermalCandidates, leg into legs.
  • header.id is the FNV-1a flight identity.

Keep the floating-point operation order identical to iOS. Floating-point addition and multiplication are not associative. Summing in another order, fusing a multiply-add, or using a different library formula changes the last bits. A threshold check (a thermal start, a zone crossing, a landing index) can then flip, and the difference grows from there. Stated in vulture-android/CLAUDE.md:

  • Port the Swift expression as it is written: same operand order, same intermediate variables, same loop order.
  • Never use String.format for numbers in domain code. Never use android.location.Location distances, or JTS or Turf geometry. Use :core:geo and :core:swift (Swift rounding and min/max semantics).
  • Only use Java APIs that Android 10 has. For example, Math.fma is Android 13+, so use core.geo.fusedMultiplyAdd.
  • Copy the numeric quirks marked Copy in vulture-android/docs/decisions.md bug for bug. Fixing one is a decision for both platforms (see Port decisions).

Known differences that are allowed:

ValueDifferenceWhy
Ground speed per fix, and values derived from itAt most 4 ulp (about 6e-16 relative) on about 10 % of fixesThe JVM’s sin/cos/atan2 against Apple’s libm. Compare with a tight tolerance
Task, speed and comparison doublesWithin 3.4e-11 relativeThe same libm differences, amplified by the distance-travelled subtraction
averageWindSpeedLast bit, between iOS exportsiOS sums a dictionary in hash order, which changes per run. Android sums in time order. Compare with a tolerance
Integers, indexes, events, phases, countsNone: must be identical
  • After any change to Packages/Flights, Packages/Waypoints, Packages/Airspace or the unit formatters on iOS.
  • After iOS applies a FIXED(…) change from ios-changes. Android then drops the matching restoreIosQuirks switch.
  • Commit the new files to vulture-android in one change, with the iOS commit in the README, and run ./gradlew test there.