Skip to content

Flight identity

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

A flight has several ids. Each one is right for one job only:

IdWhat it isScopeUse it for
Analysis id (Flight.id)64-bit FNV-1a over filename, header date, first-fix time and fix countThe same on every launch and every platform, for the same parsed fileEquality and hashing of a parsed flight, the phases tab’s remembered scroll position, the header.id in the golden JSON
Sync idSHA-256 of the IGC file’s original bytes, as 64 lowercase hex charactersGlobal: the same file is the same id on every device and platformThe Firestore document id users/{uid}/flights/{id}, the Cloud Storage blob igc/{id}.igc.gz, and the Android Room primary key
FilenameCoreFlight.filenameSyncs through CloudKit; not uniqueHandoff between Apple devices (persistentIdentifier, VultureHandoffFlights)
Core Data object URIobjectID.uriRepresentation()One device’s store onlyThe iOS Flight widget link, Spotlight and the App Intents FlightEntity. Never put it in a link that leaves the device

Content that crosses devices or platforms (a link, a share, a sync record) carries the sync id, never a local object id. Deep links lists the one link that breaks this rule today: the widget’s /open/flight?id=.

iOSvulture-ios/Packages/Flights/Sources/Flights/Models/Flight+Identity.swift (FlightIdentifier.make), tests in Packages/Flights/Tests/FlightsTests/Models/FlightIdentityTests.swift
Androidvulture-android/domain/flight/src/main/kotlin/be/fousa/vulture/domain/flight/model/FlightIdentifier.kt, test domain/flight/src/test/.../FlightIdentityTest.kt
Offset basis0xcbf29ce484222325
Prime0x100000001b3

The bytes are hashed in this order. Each byte is XORed in and then multiplied by the prime, wrapping at 64 bits.

  1. The filename’s UTF-8 bytes. This includes a generated filename when the file had none.
  2. date.timeIntervalSince1970 as the bit pattern of a Double (Kotlin doubleToRawLongBits), least significant byte first. The date is UTC midnight of the HFDTE date.
  3. startRecordingTime (the first fix, in seconds since UTC midnight) as an Int64, least significant byte first.
  4. fixCount as an Int64, least significant byte first.

The result is read back as a signed 64-bit integer. It is deliberately not Swift’s Hasher, which uses a new random seed in every process. The id is persisted, so it must be bit-identical on both platforms.

This id identifies a parsed flight, not a file. Two different files with the same name, date, start time and fix count would collide. Do not use it as a storage key.

The rule and the other record ids (tasks, home bases, map files, competitions) are in Sync schema §4, decision P8B-D9. In short:

  • The flight id is the SHA-256 of the IGC file’s original bytes, as imported. The same file on two devices makes one document.
  • Android uses it as the flight’s primary key everywhere (vulture-android/data/db/.../entity/FlightEntity.kt, FlightRepository.kt). Re-importing the same file is silently skipped.
  • Risk R4: iOS keeps only CoreFlightData.rawContent (a String). For a file with non-UTF-8 header bytes (Latin-1 pilot names), re-encoding that text gives different bytes, and so a second id. The proposed fix is in sync-schema §4, “Risk: flight ids of non-UTF-8 IGC files”.

iOS syncs through NSPersistentCloudKitContainer, which has no unique constraints. A flight has no id field of its own. The model is in persistence and settings §1.3.

iOS todayHow it identifies a flightSync-schema equivalent
CoreFlight + CoreFlightData.rawContentIts content. Importing looks for an existing flight with identical rawContent. After a CloudKit import, flights with the same filename and identical rawContent are de-duplicated, and the oldest by updatedAt is kept (DataManager.processPersistentHistory())flights/{sha256(bytes)}: the same rule made structural, so de-duplication is no longer needed
data (logbook) / recentData (recents) relationshipsn/ainLogbook
Handoff FlightActivityfilename (vulture-ios/Packages/Map/Sources/Map/Handoff/FlightHandoff.swift)Could carry the sync id once iOS has one
Widget, Spotlight, FlightEntityCore Data URILocal only, no mapping

When iOS joins Firestore, it computes the sync id from the stored text. Flights need no new Core Data attribute. Tasks and map files get a syncID attribute. See sync-schema §7, “How iOS maps onto this”, and ios-changes A4.