-
Notifications
You must be signed in to change notification settings - Fork 0
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.
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.
-
Substitute — an explicit user-defined title→model override (
matches[title]). -
Original —
GetModel(title, livery): an exact string match against the currently scannedmodelslist. This is the common case when the exact livery is installed. -
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 viaPreferByOperator(see below). -
Category — same idea as
Icao, but one level looser: same Doc8643classCodefirst, then a loose "platform + engine type" bucket (e.g. any jet, any prop) if nothing shares the exact class code. Also resolved viaPreferByOperator. -
Auto — a prefix/substring match: JoinFS keeps a
prefixListmapping title-prefixes to full installed titles, and tries progressively shorter prefixes of the requested title until one hits. - Default — the typerole's configured default model (e.g. "any Airliner" → a specific fallback aircraft).
- Last resort — the first model in the installed list, or nothing.
When tier 3 (Icao) or tier 4 (Category) finds several installed candidates of the right type, PreferByOperator narrows it down:
- Exact
icao_airlinematch (e.g. requestedDLH→ an installed model whoseaircraft.cfgalso declaresicao_airline="DLH"). - Otherwise, word-overlap between the requested livery/variation name and installed variation names (e.g. both contain "Lufthansa").
- 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."
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.
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.cfgfiles 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'sbase_containerresolves 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.
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
Originalmatch, 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>'(SimConnectCREATE_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.
When an aircraft shows up red / never renders:
- Check the JoinFS log for
ERROR - Failed to inject aircraft/ERROR - Failed to inject objectand note theSubmodel it tried to use. - If
Subis 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. - If it's genuinely gone from disk and the log shows a different, substituted
Submodel: matching worked as intended (tierIcao/Category/Auto/Default) — if it still fails to inject, that substituted model itself may be broken/uninstallable. - Check Scan For Models for any additional folders that duplicate content already reachable through the simulator's own
Communityfolder (see pitfall above), especially if you use an add-on manager that relocates packages via symlinks/junctions. - If replaying a recording, confirm it was made after the ICAO-fallback fix described above — older recordings won't have
icaoType/icaoAirlineand can only use the weakerAuto/Default/last-resort tiers.