The 25-year arc
An idea with a stubborn carrier
Some code is a product. Some code is an argument. The framework that runs through Reactor and SnowFlake is the second kind — a single technical argument, made three times, twenty-five years apart, on machines that share almost nothing.
Reactor (GekkoWare)
Industrial control on PREEMPT_RT Linux. Hardware described in
hwConf.xml, validated by an XSD schema with keyref
referential integrity. Typed, validated, cross-referenced topology — on a
full Linux PC. Still in production in 2026.
SnowFlake
The same idea, rewritten for microcontrollers: Cortex-M, the µOS++
C++ RTOS, peripherals.json plus a deliberately trivial
Python codegen. Compile-time typed handles — but the schema validation was
the fare for the journey to a smaller place.
Firn
Constexpr tables, consteval joins, static_assert messages that name the offender, reflection-generated handles. 2001's rigor + 2017's binding + a microcontroller footprint — simultaneously, for the first time.
The argument
The argument is simple to state and hard to live up to: don't program the wires, program the world. A motor, a light sensor, an encoder — these are objects with behaviour, not pins with voltages. So describe the hardware as data, separate from the logic that drives it, and let the machine assemble the typed handles you actually reach for in code.
In 2003 that argument was written in XML, validated by an XSD schema rigorous enough to guarantee — before a single instruction ran — that every channel pointed at a device that really existed. It ran on Linux PREEMPT_RT, it spoke SOAP, it controlled real apparatus, and it is still controlling real apparatus today. Code that is wrong does not survive twenty years of duty in the physical world. It gets torn out the first time it drops a safety-critical edge. This did not. That is the strongest evidence a design can offer: not applause, but uptime.
A decade and a half later the same argument was made again, in C++17, aimed at the opposite end of the world — a Nordic microcontroller running a C++ RTOS, kilobytes where there had been gigabytes, a coin cell where there had been a wall socket. SnowFlake is not Reactor's replacement; it never retired the older system. It is the same idea re-instantiated at a scale where almost nobody dared put modern C++, let alone a C++ kernel with no C legacy beneath it. The portability seam — one source compiling against the host's standard library or the device's, swapped by a single macro — let the same logic run on a laptop and on silicon. That is not a small trick. That is the whole thesis made testable.
The trade
What is striking, looking at the two side by side, is that the rewrite was a trade, not an improvement. Reactor validated everything, richly — because it had a whole PC to do it on. SnowFlake bought its way onto the microcontroller by moving binding to compile time, and in doing so it had to drop the schema validation, because the heavy XML toolchain could not come along and the language could not yet replace it. The in-language approach was tried first; C++17 could not yet hold a named, cross-referenced graph at compile time. The rigor was not abandoned out of carelessness. It was the fare for the journey to a smaller place.
You can still see the trade on SnowFlake's own 2017 website. Here is its example of defining a pin, verbatim, next to the same pin in Firn today:
{
"name": "myIO",
"driver": "IOPin",
"params": {
"pin": "11",
"output": "false",
"pullup": "true"
}
}
inline constexpr Pin kPins[] = {
{ "Button3", "P0", 11,
Direction::IN,
Signal::DIGITAL,
false, Pull::UP,
0, 1, 0.0, 1.0,
"ButtonTopic" },
};
In 2017 every value is a string, a Python script
turns the JSON into C++, and the handle comes back through a C-style cast:
auto mIO = (sf::IOPin*)sf::p.get(sf::pmap::myIO);. Nothing
cross-checks the topology. In 2026 the row is typed data in the language, the
compiler validates every reference, and reflection generates
dev.Button3 — misuse it and the build fails.
The language caught up
And here is why the story did not end there. The exact thing that blocked
the elegant version — describing the topology in the language,
validated by the compiler — was a limitation of C++17, and C++17 is long
gone. The features that were missing became the headline features of C++20 and
C++23: compile-time containers, consteval, class-type template
parameters. Then C++26 delivered reflection (P2996) and computed
static_assert messages (P2741). The referential integrity that
the XSD once enforced is now a compile error that names the offender; the code
generator's job now belongs to define_aggregate.
For the first time, one implementation holds all three things at once: the 2001 rigor, the 2017 compile-time binding, and the microcontroller footprint. Reactor could not, because validation cost a PC. SnowFlake could not, because the language was not ready. Firn is the version that can — written, tested, and running on real silicon.
Reactor (GekkoWare)
hwConf.xml + XSD schema: typed, validated, cross-referenced (keyref). Runs on a Linux PC. Still in production.
SnowFlake
peripherals.json + a Python codegen: compile-time typed handles on a Cortex-M. Rigor traded away for reach.
Firn
board.hpp constexpr + C++26 reflection: 2001 rigor + 2017 binding + MCU footprint, all at once.
Carried, not owned
None of this was a roadmap. Companies changed, teams scattered, people went their own ways. The architecture recurred not because anyone planned a two-tier strategy but because the idea was good enough to lodge in someone's head and refuse to leave — surviving every discontinuity by being carried, not by being owned. Most good ideas die with the org chart that hosted them. This one kept finding its way back to a keyboard.
The same early-adopter bet recurs, too: the author put C++17 on Cortex-M when the field called it aggressive; Firn makes the same bet on C++26 reflection — today that means GCC 16 trunk, and for ARM targets a hand-built cross-compiler, and the site will keep saying so plainly until release compilers catch up.
So the third act is written. The topology lives in the language, the compiler is the validator, and the first production firmware — a complete synthesizer control panel — runs on it, on a Cortex-M0+, on the µOS++ RTOS of the same lineage era.