Dev Diary #34: We removed the scripting language; rules are now Rust code

In short: MKA Engine used a small scripting language, a separate helper language, to write its game rules. It was removed in seventeen steps between 2 and 3 November 2026. Content files now hold only definitions, starting data and text translations, and rules are written as functions in Rust, a general-purpose programming language that the engine itself is also written in. The closing checks on the final record passed, but that does not mean every game rule works in its new form. The cost is real: mods written in the old scripting language do not work in this version, and every mod that writes a rule now has to be compiled.
Why was a second language one too many?
If a game's rulebook were written in two separate languages, both the writer and the reader would have to know both. In our case, something close to that was true. Inside the engine, a small language decided when an event could happen and what it did once it happened. A content writer could write a condition in it, put an effect in order, or set the weight of an AI option. The language had its own reader, its own error messages and its own compiler.
Over time, this language became an unnecessary second language. The decision-maker's assessment pointed the same way: we had built a sub-language that was more than we needed. The design document, written on 2 November, proposed a single route instead. Rules would be written in Rust, and the game and its mods would use the same language. Its opening note was also clear: the decision was its owner's to make, and only the data format would remain from the content language. We explained why we chose Rust in a separate diary: Dev Diary #0: Why we write our own Rust game engine.
What was the language part of?
The language was not used only by content files. The design document also counts AI decisions, the event system, the succession law, modifier conditions, the rules for vision and authority, and historical effects with a start date as users of it. So the language had spread into the engine's inner logic, well beyond a data format. As a result, removing it was not the deletion of one file: the removal touched many parts of the engine. Around it there were helper tools as well: a language server that gave editors help, and tools that dumped the language's documentation.
The design document's measurement of our first game content, taken on 2 November, counts 15 files and 53,267 lines. Only one file held rules: a tax decision group with 4 options and a money decision group with 3 options. The design planned to write the condition and weight of each of these seven options as a separate Rust function.
What stayed, what went?
What stayed was the data format: definitions, starting data, text translations and the data patches mods make. The event titles and option texts also stay in the data files, with their translations. The plain files that say which event, which decision group or which starting value exists are still in place.
What went were the small programs embedded in that data: the condition language, the effect language, the formula language that writes number calculations, the tool that turns those calculations into executable code, and the evaluation contexts. The layer that spelled out what a condition means and previewed an effect went too, as did the what-if queries and running scripts from the console. The engine's own AI layers for strategy, personality, plan, knowledge, budget and military went as well. They were strategy content written in formulas and conditions; from now on, strategy will be written in the game's own Rust code.
The event mechanism stayed. The mechanism that decides when an event is chosen in the calendar, and how long a choice takes, is still in the engine. What went was the language that wrote an event's condition and effect. The AI decision groups also kept their timing inside the engine: how often they think, how they split the work into slices, their delay and their step budget. Only the condition and weight of each option became Rust functions.
The save file format did not change, and the event calendar and the AI's pending decision rows stayed the same. But because the content schema changed, the design document expects older saves to be rejected.

How is a rule written now?
The data file says which option exists, what text it shows and which numbers it works with. A Rust function, named in the file, decides whether the option applies and how heavily it weighs.
The design's shared rule is this: the data says what exists and which numbers it works with, and Rust says on what basis that holds. Rust functions come in two kinds. Functions used for conditions and weights only read: they change nothing in the world and make no random draws. Effects can change the world: they can create or delete a record, or fire an event. An effect's writes are applied only if the call finishes without error.
Before, the condition and weight of an option were short expressions written in the language, inside the file. Now the data file only says "the weight of this option is the function with this name". The function itself is written in Rust, compiled and added to the game.
A mod may call, by name, the functions of mods loaded before it, but calls cannot loop back on each other, so no cycle can form. A multiplier's condition works the same way: a function whose name is written in the content decides it.
If an unknown name is written, loading stops. The error gives the file, the line and the column; the project does not load in that state, so the game does not open with that content. The error appears before the game starts, in front of the content writer. The same check applies to AI decision options, the succession law and multiplier conditions.

What changes for mods?
For mod authors, the biggest change is that rules are now Rust code. Mods written in the old scripting language do not work in this version, because backward compatibility was not kept. That is a concrete cost for mod authors.
The design document states what this means for much of Paradox-style modding. Adding a line to an event's condition now means rewriting the whole function behind that condition. In the new model, you point the event's condition field at a function of your own, and that function rewrites the entire condition. The design calls this a deliberate choice: it raises the bar for writing mods. It also says the guide should state this cost plainly.
A mod can replace or disable the systems of mods loaded before it, and that applies to the engine systems the game connects as well. Action chains have gone. Work that used to happen at the start of a month is now written as Rust code tied to that point in time, and several mods can attach code to the same point; they run in load order.
Every mod that writes a rule must be compiled. Only mods that change numbers, names, text translations or content records can stay uncompiled. Running scripts from the console, the plain-language explanation of what a condition means, and effect previews have also gone. A game that wants tooltips can build them from its own function. The mod interface changed during the removal.
There is a gain as well. Game and mods are written in the same language. An unknown name appears as an error with the file and line at load time. And because the event calendar stays in the engine, a mod does not have to rewrite it.
Why did the order matter?
Before taking a wall down, you need to know where the ceiling resting on it sits. The removal went the same way. The design defined each step as one subsystem, and asked that after each step the engine compile and that step's checks pass.
The order also followed dependencies. The design lists the content tests as step two, but that step ran after step thirteen, because the tests checked succession and condition features that were only removed in steps nine and thirteen. Before removing a part, every place that uses it had to be found; the order made that necessary. The design also asked that the steps be recorded one at a time and in order, so that they would not collide with other work.
How did the removal run?
The removal started on 2 November and was finished on 3 November. Five of the seventeen steps were recorded on 2 November and the other twelve on 3 November; the final record was saved on the afternoon of 3 November. The steps fall into four groups:
| Group | What was removed or moved |
|---|---|
| Developer tools | Running scripts from the console, the keyword help of the editors' language server, the explanation and preview layer, and script cost warnings |
| Moving rules into functions | Effects moved to Rust; mods can call each other's functions and replace or disable systems; events were tied to function names and event action types were removed; AI decision options, the succession law and the multiplier condition moved to functions |
| The core of the rule language | The condition language, the effect language, the formula language and the evaluation contexts were removed; the loop, list-scan and nested-action limits left the project settings |
| Tests and documents | Script-based content tests were removed; the Rust tests that should take their place are not claimed complete in this post; the content-writing guide was rewritten to describe the data format |

What did the closing checks show?
On the final record, saved on the afternoon of 3 November, the code checking tool gave no warnings, no test in the workspace checks failed, and the engine passed its tests on three build targets: two processor architectures and one portable target. The dependency check also passed. The record also notes that some tests were skipped.
This shows that the engine builds and passes its checks on those targets. It does not show everything. We did not re-check the test result of each step one by one; this post relies on the results recorded at the final record. We did not measure the speed of rules; we do not say that the new functions are faster or slower than the old ones, and the design also states that the cost of a call was not measured. Some engine tests use a synthetic map world instead of game data, so these results alone do not show that every rule in the game's real content works correctly. The game's own content checks are outside the scope of this post.
The design said that the behaviour of events, AI and succession would need to be rewritten as Rust tests. This post does not claim that the rewrite is complete.
What happened to the loop and list limits?
On a game board, a rule does not multiply just because the turn is long. On a computer, a rule that enters a loop can make a game step last longer. The loop and list-scan limits we described in our earlier post (#13) existed for this reason. Those limits are no longer in the project settings. The settings now carry only the mods' work and memory limits; if a loop, list-scan or nested-action setting is written, loading gives an error.
Event conditions and decision weights now become one function call per evaluation. The design notes that each such call also counts toward the mod's work count. So the limit did not disappear entirely: the loop and list counters are gone, while the mod work and memory limits and the counting of each call remain.
This post does not describe a separate counting scheme that replaces the loop and list limits. What happens when the work count is exceeded is also outside the scope of this post.
Open design questions, settled
The design listed seven open questions; on 2 November each was settled on the development side. The ones that matter to readers and mod authors are:
- A mod may call, by name, the functions of mods loaded before it; a call cycle cannot form.
- Replacing or disabling a system covers the systems of mods loaded earlier, and the engine systems the game connects.
- In the succession law, the function calculates the generation gap itself; this decision did not change the mod interface.
- The save upgrade feature stays. It lets games carry their saves into new versions; it is not compatibility with the old code base.
- The content-writing guide was rewritten for the data format, and its script sections were removed.
What can the AI see?
In card games with hidden information, an opponent cannot see the cards in your hand. In a computer game, this rule has to be enforced by code. In the old system, a protection layer limited what the AI's formulas could read. That layer has gone. A Rust function that calculates a decision's weight can now read the game's state directly, so in a game with fog of war, protecting what is seen became the job of the game's own code.
The design document records this as an open risk. Its note for the eighth step says that, in one test scenario, the AI did not choose a target it could not see. That is one scenario, not a general guarantee. If needed, the engine can add a separate arrangement that reads what the observer saw; that would be a separate engine task.
Open items and what is not measured
Three jobs remain open. First, the cost of calls has not been measured, so there is no data on how fast the new rules are. The design notes that a batch call, which evaluates all of an owner's options in one call, could be added if needed. Second, protecting information that the AI must not see is a job for the game's code; if needed, the engine side will be a separate job. Third, the game's own content has not been checked in its new form within this post's scope; according to the design, these checks run on the game's side, in a game session.
What this post does not say
This post covers the removal of the engine's rule language, the closing checks and the design decisions. We did not measure whether every rule in the game works in its new form, how fast the new rules are, how a mod behaves in the new version, or what players will notice in the game. These need a separate review.
Frequently asked questions
Will mods written in the old scripting language still work?
Not in this version; backward compatibility was not kept. Their rules must be rewritten as Rust functions. Definitions, starting data, text translations and data patches stay as data.
What is needed to write a rule?
A Rust function, its compilation, and the function's name in the data file. The data files stay as data and carry only the function's name.
Can a mod use another mod's functions?
Yes, but only those of mods loaded before it, by name. It cannot call functions of mods loaded after it, so no call cycle can form.
Will my old saves still load?
The save file format did not change. But because the content schema changed, the design document expects older saves to be rejected. We did not test loading an old save.
Is the game faster now?
This post did not measure it. The design also notes that the cost of calls was not measured, so this post makes no speed claim.
Did the event system also go away?
No. The mechanism that decides when events are chosen in the calendar stayed in the engine; what went was the scripting language that wrote an event's condition and effect.