four-docs-sites-and-sixty-releases.md
2026-10-11 | 8 min read

Four Docs Sites and Sixty Releases: The Flight Sim Stack Grew Up

Flight SimulationGoMSFS 2024MCPOpen Source

Do you know that feeling when a side project stops being a side project? Not because you decided anything, but because one Friday you open the changelog, scroll, keep scrolling, and realise the thing you wrote to read airspeed out of a flight simulator now runs an airport.

That happened this week. So let me show you what the flight sim stack looks like now, and why it needed four documentation sites before I could keep going.

One disclaimer up front, because the release count below invites the wrong assumption: this is still a night and weekend project. There is no team, no roadmap, no sprint. There is a day job, and then there is this — evenings, and the odd Saturday that disappears entirely.

The part that is easy to say and hard to believe

$ gh release list -R mrlm-net/simconnect --limit 5
v0.89.2  2026-10-11
v0.89.1  2026-10-11
v0.89.0  2026-10-11
v0.88.0  2026-10-11
v0.87.0  2026-10-11

Five days earlier that list said v0.27.1. Sixty-some minor releases in a working week, every one of them a feature branch that landed green. Not a rewrite — an accumulation. Each release answered one question that the previous release made possible to ask.

Which is exactly the problem. When a library moves that fast, the README stops being documentation and becomes an archaeological site.

Fail #1: the README was the product

For most of its life mrlm-net/simconnect was documented by its own README plus a handful of examples. That works beautifully for a DLL wrapper. You have maybe fifteen functions, you list them, you show a Connect() snippet, done. I wrote a post about exactly that, back when the whole point was no CGo, please.

It stops working the moment the library has opinions. Wake turbulence separation has opinions. Phraseology has opinions — ICAO by default, FAA at US airports, both checked against the quoted documents, because “close enough” phraseology sounds wrong to anyone who has actually listened to a frequency. You cannot put that in a bullet list. It needs a page, with the citation next to the rule.

So this week all four projects got the same treatment: a real documentation site, on one shared design, light and dark, with a changelog that is generated from CHANGELOG.md instead of my memory.

ProjectDocsWhat it is
simconnectsimconnect.mrlm.netThe Go wrapper, and now the traffic and ATC engine on top of it
simconnect-mcpsimconnect-mcp.mrlm.netMCP server — the sim, exposed to AI assistants
voice-goiogoio.mrlm.netOffline ATC voice, in and out
simbriefsimbrief.mrlm.netSimBrief API client, flight plans and OFPs

One design system, four accent colours, the same sidebar, the same llm.txt on every one of them so an assistant can read the docs without scraping HTML. Boring on purpose. The interesting part should be the content, not the chrome.

TIP: If you maintain more than two libraries, build the docs template once and treat it as a dependency. Build it four times and you get four sites that rot at their own pace, which is how you end up never updating any of them.

Fail #2: “AI traffic” in MSFS is not traffic

Here is the thing nobody tells you when you start spawning aircraft through SimConnect. You can create an AI aircraft, give it a flight plan, and watch it fly. It will also turn at waypoints like it’s on rails, taxi through other aircraft, land whether or not the runway is occupied, and cheerfully ignore that the wind changed twenty minutes ago.

So the library grew the missing half. Not as a monolith — as components that never own the message stream. Your loop feeds Handle(msg), which means they compose with the Manager and with each other instead of fighting over the connection.

What landed:

  • Ground. Airport layout read from facility data — runways with both ends, parking stands (C22, S22A), taxi points with hold-shorts, taxi paths with their taxiway names. Then a taxi graph and A* routing: stand → runway hold-short, with the taxiway sequence and the runway crossings spelled out.

  • Departures and arrivals. Spawn at a stand, push back with a tug, taxi, hold short, take off on a SID. Arrive on a STAR and an approach, land, exit the runway, taxi in, park on a stand that actually fits the aircraft. Turn around and go again.

  • Airborne ATC. A landing sequence per runway, first come first served, re-sequencing go-arounds. Wake turbulence separation — ICAO and RECAT-EU — with final spacing that follows the weather. Conflicts predicted five minutes out and resolved with the least disturbing change: speed first, then level, then heading.

  • Schedules. Timetables built from airlines, fleets, routes and time-of-day waves, with a traffic manager that spawns them and then adapts to what it sees — landing flow, ground stops, late inbounds, an aircraft stuck on a taxiway.

  • Jetways. Which deserve their own paragraph: MSFS 2024 has no way to dock a jetway to a NonATC aircraft. No SimConnect call, no event, no variable — TOGGLE_JETWAY works for the user aircraft only. I probed it live, at our aircraft and at the jetway, with and without control released. Only aircraft the sim’s own ATC parks get jetways. So the workaround is invisible ParkedATC helpers: a placeholder holds the gate, steps aside, and three seconds later an invisible helper of the right type takes it under our aircraft, and the jetway docks to that. One at a time, because the sim will not park a helper next to an occupied gate — at LKPR that is 19 of 27 gates.

    This is not my discovery and not my request. Developers have been asking for jetway control of arbitrary aircraft for a while — there is an open devsupport thread from March and an earlier one on the Facilities API. Go and vote on them. The day that call exists, the helpers and every side effect below them get deleted, and I will enjoy the diff.

    The side effects are documented too, because the helpers are real aircraft: anything enumerating SimConnect traffic — Little Navmap, TCAS displays, other add-ons — will see about nineteen aircraft titled “MyCrew Jetway Helper” parked at LKPR. A workaround without its reasoning is just a weird line of code waiting to be “cleaned up”; a workaround without its blast radius written down is worse.

The physics got attention too. Standard-rate turns by airframe instead of MSFS AI’s hard waypoint snaps. Injected take-offs with engine spool-up. A stable glide path to touchdown, crabbed into a crosswind and de-crabbed in the flare, with the touchdown point varying a little from landing to landing — because it does.

Fail #3: silent controllers

You can build all of that and it still feels like a diorama, because nothing talks.

voice-goio is the answer, and I deliberately kept it a separate library. It knows nothing about SimConnect, nothing about MSFS, no assumption about where the transmissions come from. The application hands it text and gets audio; it hands the application tags back.

app text ─▶ normalise ─▶ TTS ─▶ radio chain ─▶ player ─▶ PlaybackEvent
pilot speech ─▶ STT ─▶ Recognition{intent, callsign, value} ─▶ app

The constraints I set, and kept:

  • Fully offline. Everything runs on the user’s PC. Pull the network cable and the controller still answers you.
  • Zero dependencies. go.mod has no require lines at all. Standard library only, CGO_ENABLED=0 everywhere.
  • A real radio chain. Band-pass, noise, squelch, separate channels for radio, intercom and cabin PA, with generated chimes.
  • 1,145 voices across 23 accents, so a controller keeps the same voice on every frequency they work, shift changes included, and the crews never borrow a controller’s voice.

That last point sounds like a detail and is not. Hearing the same voice as approach and then as tower breaks the illusion harder than a wrong readback does.

NOTE: The output side runs on Windows hardware in two applications today. Recognition is written and cross-compiled on every commit, but has never been exercised against a live engine. I would rather say that plainly than let you discover it.

Fail #4: nobody reads an SDK for fun

Which brings me to simconnect-mcp, and the part I did not expect to enjoy this much.

It is a Model Context Protocol server for the simulator. Two modes — documentation fetch, which runs anywhere, and live SimConnect, which is Windows-only — or both at once with MCP_MODE=both. In docs mode you get 15 tools over the SDK reference and 1,800+ SimVars. In simconnect mode you get 63 more against the running sim. 78 total.

# cross-platform: ask about SimVars, events, structures, error codes
MCP_MODE=docs simconnect-mcp

# Windows, MSFS running: read and control the sim
MCP_MODE=simconnect simconnect-mcp

The reason this matters is not “AI in my flight sim” as a bullet point. It is that the SimConnect documentation is, as I have said before, charmingly vintage — and eighteen hundred SimVars is exactly the kind of corpus a human should not be grepping. Asking “which variable gives me the parking brake state and what units does it report” and getting the answer with its SDK citation is a genuinely better workflow than the one I had.

And then, because the traffic engine is a library and not an application, the MCP server could just expose it. start_schedule, stop_schedule, get_schedule. You can ask an assistant to bring an airport to life — an airline timetable with departures, arrivals, turnarounds and overflights, sequenced by a tower and an approach controller — and then go fly into it.

The boring sibling: SimBrief

simbrief got less noise and more correctness. It now decodes real OFPs in both JSON v2 and XML into typed structures, reads V1, VR, V2 and Vref straight out of the runway analysis, and builds dispatch URLs through a fluent builder with validation.

The design decision I am most pleased with is a custom Number type that preserves the raw value, so a missing field stays missing instead of silently becoming 0. If you have ever shipped a bug where a zero fuel figure looked like a valid fuel figure, you know why I bothered. It is Apache 2.0, standard library only, Go 1.21+.

On licensing, since people ask

simbrief is Apache 2.0. The other three — simconnect, simconnect-mcp, voice-goio — moved to the Business Source License 1.1, free for non-commercial use, commercial licensing on request.

I will not pretend that is a purely technical decision. When a project is a DLL wrapper, permissive is obvious. When it is five days of sixty releases building a traffic and ATC engine that a commercial add-on could drop in whole, “obvious” changes shape. Hobbyists, students and open-source consumers lose nothing. Someone shipping a paid product has a conversation with me first. That seems fair, and the alternative is that I stop having evenings for it.

Conclusion

To cut the long story short: the stack stopped being a wrapper. It reads the sim, it fills an airport, it sequences the arrivals, it speaks on the radio, it plans the flight, and it answers to an assistant. The docs sites are not a cosmetic change — they are what let me keep shipping at this rate without the project becoming unreadable to anyone but me.

If you are building anything against MSFS in Go, start at simconnect.mrlm.net. If you just want to watch an airport run itself, the MCP server is the shortest path there — and make demo-arrival in voice-goio is the single best thirty seconds of this whole thing. Four controller positions, four frequencies, three handoffs, twelve pilot calls, and every recognised tag printed next to the audio that produced it.

It is still pre-1.0, it is still built after hours, and versions come fast because nobody is stopping me. I would not have it any other way right now. :-)

References

[0] made by wanted.solutions *bash
~/blog/four-docs-sites-and-sixty-releases
no incidents (today) 04:20 CET