Contents
In short: In MKA Engine an event is a record defined in a content file: it happens by itself after a given mean time, or an effect fires it; it offers the player options and the answer arrives as a command. A modifier is a bonus or penalty attached to a country or province, temporary or permanent: it is summed, multiplied or flips a switch. Both are deterministic; draws come from separate streams keyed by step and record.
Think of a famine in a strategy game. A "drought has begun" screen appears; the harvest shrinks for three months; an advisor offers two options. These three things, the event, the modifier and the option, are the backbone of grand strategy games. In this diary we describe how the engine holds them. We saw the language the rules are written in in Diary #5; here we look at where that language runs.
What is an event?
An event is one of the content types; the game says which type is the event in project.bsm. An event is made of:
- a scope (does the event happen in a country or a province),
- a condition (can it happen now),
- an immediate effect and an after effect,
- optional options; each option has its own condition and effect.
It starts in one of two ways. Either it happens by itself after a mean time (in days), or only when triggered: an effect, an action or a hook fires it. The research event in our test game is an example of the second: each technology is an option and the option's condition says "this technology has not been taken yet"; the chosen technology becomes taken. This is an example from the test game; it is not the rule of a real game.
When does a random event happen?
"On average once in so many days" does not mean rolling a die every day. For each event and scope pair a wait is drawn once and written into a calendar; when the time is up the event is tried. The draw is keyed by the step, the event and the scope and comes from the calendar's own source; no system draws from it. The event's own draws (its condition, options, effects) come from a separate stream reserved for the event.
The practical result is twofold: with the same seed the same events happen on the same days (the stateless generator of Diary #1), and an event's day does not slip because a system rolled an extra die.
Where in the day does an event run?
In Diary #4 we counted the seven stops of a day. An event runs in two places:
- After the timers, before the systems: events whose time has come are fired.
- At the end of the day, before the digest: scopes whose condition has to be re-tested because of that day's changes are tried again.
Hooks, the code of the game's Rust mod that runs at the step boundary, come after these two and can fire an event by hand. If an event asks for another action through an effect, that action runs right after that event, in the same phase.
Options: the answer is a command
When an event with options fires, a choice row is opened. The player gives the answer with a command that goes to everyone over the network; the answer is the number of the option in written order (the first option is 0). A choice has a deadline; if no answer comes, when the step reaches that day the default option is applied. The AI answers with the same command.
The choice and its deadline sit in the table as columns, not in a timer; so they live in the store with the other data, as in Diary #3.
What is a modifier?
A country's harvest may be "100"; a drought puts -3 on it, a good ruler +2. Writing these bonuses by hand into the country's column would mean forgetting to take them back when the drought ends. Instead each bonus or penalty stays as a separate instance: who it is on, which type it is and how much is known. The total is computed from the instances every time.
There are three kinds:
| Kind | How it combines | Example |
|---|---|---|
| Additive | The amounts of the instances are summed and added to the base | Harvest: drought -3, good ruler +2, total -1 |
| Multiplicative | Amounts are summed, the factor becomes 1 + total | Tax: instances +10% and -5%, factor 1.05 |
| Switch | On if any instance names it with a non-zero amount | When on, the command type tied to the switch is refused in that scope |
For the multiplicative kind the amounts are summed first and then become the factor; so two "+10%" instances make 1.20, not 1.21. (The numbers in the example are a calculation, not a measurement.)
In our test game a drought set writes harvest = -3; a storm set reads, for example, the severity column of its source and applies -1 per unit of severity. The AI's hard setting adds a set that gives its country harvest = 2 every day. This too is an example from the engine's test game.
How are the totals kept?
When a condition is written as modifier:harvest > 0.5 it reads the scope's total of that type. These totals are read often, so instead of summing the instances every time they sit in a derived cache. The cache is not saved; it is rebuilt from the instances when the game loads. So in the save the instances are the single source of truth, the totals come out of them, and the two cannot drift apart.
Totals move only when the world changes: for example after the timers (so events read the current total), after the events (so the systems read it), and after every command, if a command type can be closed by a switch (so the next command sees the right state). The move looks at the changed rows of the store; it does not scan the whole table.
Timed modifiers
A drought is not permanent. A timed instance is ended by its own timer: the timer deletes it in that day's timer phase, before events and systems run. There is a subtle rule for the switch kind: a switch applies before its end day, not on it. Because the timer phase comes after the command phase, the commands of the end day are not stopped by the switch. Knowing this answers a mod author's question "do commands pass on the day the switch ends?": they do.
Modifier sets
An event does not write single instances; it adds a set: drought = { modifiers = { harvest = -3 } }. Sets are numbered by the type order in project.bsm, then by record order; the event names a set by this number (with a definition like @SET_DROUGHT) and attaches it to a scope with the spawn effect. Sets can have conditions of their own, but the condition of a set cannot read the total it feeds: otherwise a self-feeding loop such as "I have the bonus if I have the bonus" could be built.
Limits
- No measurement: In this diary we give no measurement of the speed of events and modifiers; we describe the rules and the machinery.
- Examples are from the test game: The events and sets we show are from the engine's test game and are not the rules of a real game.
- Machinery, not event rules: What a drought is and which event happens at which mean time is the game's business; the engine only provides the machinery that holds and runs them deterministically.
- Modifiers and events are built together: If an event or action reads totals, the game must also have a modifier set; otherwise the build fails with an error when the game is set up.
- The engine is in development: Fields and keywords may change.
Frequently asked questions
How do random events work in strategy games?
In MKA Engine a wait is drawn once for each event and scope pair and written into the calendar; when the time is up the event's condition is tested. The draw is keyed by step, event and scope, so with the same seed the same events happen on the same days.
What is a modifier?
A bonus or penalty on a country or province. It is additive, multiplicative or a switch; each is kept as a separate instance and the total is computed from the instances. A timed one is ended by its own timer.
What happens if the player does not answer an event option?
A choice has a deadline; when the step reaches that day the default option is applied. The answer is a command that goes to everyone over the network; the AI answers with the same command.
How do two multiplicative modifiers combine?
Their amounts are summed first and the factor becomes 1 plus the total. So two "+10%" make 1.20, not 1.21.
Sources and records
What this diary describes rests on the engine's own decisions and guide; we used no external sources. Our internal records (the engine repository is closed, so we cannot link them):
- Step phase order: events running after the timers and before the systems, and the place of hooks.
- Content language decision: effects run only at an event, command or hook.
- Script contexts: totals read in the simulation context.
- Modifier machinery and events: the engine guide and the test game.
Next up is the economy toolkit: markets, distribution and flow networks.
More on this will come
MKA Engine is still in development. We will keep sharing our work on events and modifiers in new diaries as new features arrive.
If you have questions or ideas about this diary, write to us from the contact page. For an overview of the engine see the MKA Engine page, and for all diaries the Developer Diaries list.
Sources and related docs
You may also like
- How is a strategy game's world map made?
For MKA Engine we built a 27,357-location world map of 1337 from real data: how terrain, rivers, straits, population and location borders were made.
- The road to a game engine
How we started MKA Engine: reading four games, treating determinism as law, moving phase by phase with measurements, and where we are now.