Skip to content

Model Matching

joeherwig edited this page Jul 10, 2026 · 2 revisions

Model Matching

JoinFS has to turn "the aircraft/livery the other pilot (or a recording) is using" into "a model I can actually ask the simulator to create". This page documents the matching algorithm in Substitution.cs (Match()), what data feeds it, and a known pitfall around duplicate/redundant model scan folders that can make a perfectly-installed livery fail to render.

Where matching happens

Substitution.Match() is called for every remote/AI/recorded aircraft JoinFS needs to render. It receives the aircraft's reported title (and, on MSFS2024, livery/variation), its ICAO type designator (e.g. A20N), its ICAO airline code (e.g. DLH), and a typerole (Airliner, SingleProp, ...). It returns a Model (a locally scanned/installed aircraft) plus a Substitution.Type describing how it was matched.

The Substitution.Type is shown as a suffix on the Sub Model column in the Aircraft list:

Type Suffix shown Meaning
Substitute (S) Explicit user override (Settings ▸ Model Matching)
Original (none) Exact title (+ livery, MSFS2024) found among currently scanned models
Icao (none) Same ICAO type designator (e.g. both A20N), livery chosen by operator preference
Category (none) Same Doc8643 class/category (platform + engine type), livery chosen by operator preference
Auto (A) Prefix/substring match against installed titles
Default (D) Per-typerole configured default model
AI (AI) Reserved for an (currently disabled) embedding-based matcher

If nothing above matches at all, JoinFS falls back to the first installed model in its list, or renders nothing if no models are installed.

Matching tiers, in order

  1. Substitute — an explicit user-defined title→model override (matches[title]).
  2. OriginalGetModel(title, livery): an exact string match against the currently scanned models list. This is the common case when the exact livery is installed.
  3. Icao — only tried when the aircraft reported an ICAO type (icaoType.Length > 0). Looks up all installed models sharing that type designator (icaoIndex[icaoType]) and picks one via PreferByOperator (see below).
  4. Category — same idea as Icao, but one level looser: same Doc8643 classCode first, then a loose "platform + engine type" bucket (e.g. any jet, any prop) if nothing shares the exact class code. Also resolved via PreferByOperator.
  5. Auto — a prefix/substring match: JoinFS keeps a prefixList mapping title-prefixes to full installed titles, and tries progressively shorter prefixes of the requested title until one hits.
  6. Default — the typerole's configured default model (e.g. "any Airliner" → a specific fallback aircraft).
  7. Last resort — the first model in the installed list, or nothing.

PreferByOperator — picking which livery within a type/category match

When tier 3 (Icao) or tier 4 (Category) finds several installed candidates of the right type, PreferByOperator narrows it down:

  1. Exact icao_airline match (e.g. requested DLH → an installed model whose aircraft.cfg also declares icao_airline="DLH").
  2. Otherwise, word-overlap between the requested livery/variation name and installed variation names (e.g. both contain "Lufthansa").
  3. Otherwise, the first candidate found during scanning.

This is the mechanism that satisfies the original request behind this page: "replay an A320 with a Lufthansa livery that's no longer installed — JoinFS should find some other installed A20N/A320 with a DLH livery instead of rendering nothing."

What has to be true for the Icao/Category tiers to fire

Both tiers require icaoType (and ideally icaoAirline) to actually be present on the request. For live network traffic this was always the case — it comes from the aircraft's flight plan.

For recorded flight playback, this used to be missing: Recorder.cs's own lightweight Obj/Aircraft record classes never captured icaoType/icaoAirline, so every replay passed empty strings into matching, silently skipping tiers 3 and 4 no matter what. This has been fixed — icaoType/icaoAirline are now recorded (sourced from Sim.Aircraft.flightPlan.icaoType/icaoAirline at record time) and persisted in the recording file (format version bumped: MSFS2024 builds 21004→21005, other builds 21003→21004, using the same version-gated read pattern as the existing livery field so old recordings still load — they just won't get the ICAO fallback, since they never captured it).

Practical implication: a recording made before this fix has no ICAO data baked in. Replaying it will behave like before (weaker fallback tiers only). Re-record to get the new behavior.

The model cache, and why it can go stale

Installed models are scanned from the simulator's own folders plus any additional folders you've configured, and cached to disk at:

%LOCALAPPDATA%\JoinFS-<Simulator>\models - <SimulatorName>.txt
  • Every connection unconditionally loads this cache file into memory (LoadModels()), regardless of whether the files on disk still exist.
  • Only an actual scan rebuilds it from what's really on disk: models.Clear() followed by a live folder walk. This runs automatically on connect if "Scan at launch" is checked in the Scan For Models dialog (Settings.Default.ModelScanOnConnection), or manually via Scan For Models…Scan.
  • Scanning parses aircraft.cfg/sim.cfg files it finds and trusts them — it does not verify that the package actually loads in the simulator (e.g. it never checks whether a livery's base_container resolves to an installed base aircraft).

Consequence: if you delete a livery folder but never re-run a scan (or "Scan at launch" is off), JoinFS keeps matching to it from the stale cache — SimConnect will then refuse to create it (SIMCONNECT_EXCEPTION_CREATE_OBJECT_FAILED, logged as ERROR - Failed to inject aircraft ...), and the aircraft list shows a permanently red, never-created ghost. Re-running Scan For Models purges genuinely-deleted entries.

Known pitfall: redundant/duplicate scan folders (e.g. MSFS Addon Linker)

Tools like MSFS Addon Linker relocate large add-on packages (liveries, aircraft) out of the Community folder onto another drive, then create a symlink/junction back into Community so the simulator still sees them as installed there.

The Scan For Models dialog lets you add additional folders on top of whatever the simulator itself reports — these are simply appended to the scan list, with no deduplication or validation against what the simulator's own content system actually has loaded.

If you point one of these additional folders at the Addon Linker's raw storage/source location — instead of only scanning through the actual Community folder (which already reaches that content via the junction) — JoinFS ends up registering the same aircraft from two different roots. This was exactly the scenario that produced a red, unrendered aircraft even though the livery was genuinely installed and JoinFS's own model list contained a seemingly perfect, exact-title match (tier Original, no fallback needed) — SimConnect still refused to create it.

Symptoms:

  • Aircraft list "Distance" cell is stuck red; nothing renders.
  • The Sub Model column shows the exact title/variation you expected — this is an Original match, not a fallback, so it's not a "matching couldn't find anything" problem.
  • The log shows ERROR - Failed to inject aircraft '<callsign>' ... - Sub '<title>' (SimConnect CREATE_OBJECT_FAILED).

Fix: only configure JoinFS's Folders/additional-folders to point at the actual Community folder(s) the simulator uses — not an add-on manager's separate source/staging folder that already reaches the sim through a symlink/junction elsewhere. Duplicating a source doesn't add any real content; it just gives JoinFS a second, potentially inconsistent view of the same aircraft to pick from. If you're not sure whether a configured folder is redundant, remove it and re-run Scan For Models — if the aircraft you care about still resolves and renders correctly, you didn't need it.

Troubleshooting checklist

When an aircraft shows up red / never renders:

  1. Check the JoinFS log for ERROR - Failed to inject aircraft / ERROR - Failed to inject object and note the Sub model it tried to use.
  2. If Sub is exactly the aircraft you thought you removed: it's probably still on disk somewhere JoinFS scans (or in the persisted models cache). Confirm on disk, then re-run Scan For Models.
  3. If it's genuinely gone from disk and the log shows a different, substituted Sub model: matching worked as intended (tier Icao/Category/Auto/Default) — if it still fails to inject, that substituted model itself may be broken/uninstallable.
  4. Check Scan For Models for any additional folders that duplicate content already reachable through the simulator's own Community folder (see pitfall above), especially if you use an add-on manager that relocates packages via symlinks/junctions.
  5. If replaying a recording, confirm it was made after the ICAO-fallback fix described above — older recordings won't have icaoType/icaoAirline and can only use the weaker Auto/Default/last-resort tiers.

Clone this wiki locally