user@caretc:~$
system: online
ARTICLES>SIMULATION / OT / ENGINEERING
8 MIN READ

RootSpace: Baby Steps

How do you begin building an engine intended to simulate years of industrial history? Apparently, with two tanks and a pump.

PROJECTRootSpace →

I’ve been thinking about building something like RootSpace for years.

And, despite the rather ambitious ideas I’ve been entertaining, I’m not starting entirely from scratch.

I’ve attempted plenty of games and game engines over the years. Most never became finished games, but that doesn’t mean nothing came out of them. Each left behind little ideas, experiments and lessons about how I might approach things differently next time.

Similarly, building highly configurable user interfaces for industrial systems has given me plenty of opportunities to think about how complex systems might be represented, configured and assembled from reusable pieces.

There’s quite a collection of ideas rattling around in my head.

RootSpace is an opportunity to see whether some of them can work together.

The problem, of course, is where to begin.

Start somewhere

RootSpace’s eventual ambitions are substantial. I want to experiment with industrial processes, simulated control systems, procedural environments, operational histories and persistent actors.

It’s very easy to imagine what the finished thing might look like.

It’s considerably harder to decide what to build first.

I didn’t want to spend months designing an enormous engine around requirements that might never materialise. Equally, I wanted the first example to be sufficiently realistic to challenge the architecture.

So I settled on a small water-transfer system.

Two tanks, connected by a pump, with some instrumentation and a simulated PLC.

Waterworks 01.

flowchart LR
    T1["Tank 01"]
    P["Pump 01"]
    T2["Tank 02"]

    S1["LT101<br/>Level sensor"]
    S2["LT102<br/>Level sensor"]

    PLC["PLC01"]

    T1 -->|"Water"| P
    P -->|"Water"| T2

    T1 -.->|"Level"| S1
    T2 -.->|"Level"| S2

    S1 -->|"AI0"| PLC
    S2 -->|"AI1"| PLC

    PLC -->|"DO0: Pump command"| P

It’s simple enough that I can understand the entire system without much effort, but it has enough going on to provide a meaningful first test.

There’s a physical process, equipment behaviour, instrumentation and control. Things have to interact, and the result needs to make some sense.

That seemed like a reasonable place to start.

Three layers

One of the first ideas I wanted to test was separating the simulated world into three fundamental concepts.

Configuration describes what exists and how it is put together.

Behaviour describes what the equipment wants to do.

Runtime state describes what is actually happening.

That distinction might seem obvious, but I’ve found it useful to make the boundaries explicit.

Take a pump, for example.

Its configuration might describe its nominal flow rate and which tanks it connects. A Lua behaviour can decide what the pump should attempt during a simulation step. The simulation engine then applies the physical constraints and updates the runtime state.

The pump might request a certain flow rate, but the source tank could be empty or the destination full.

The behaviour doesn’t get to invent water.

I’ve been particularly keen on Lua because it offers a route towards the sort of content-driven, moddable engine I’ve imagined. A specialist might eventually define highly detailed equipment behaviour without having to modify the core simulation.

Waterworks 01 doesn’t prove that all the elaborate nested-component ideas will work, of course.

But the separation between configuration, behaviour and runtime state is now more than a diagram. There’s an actual simulation using it.

And I’m rather pleased about that.

Making the pieces talk

The next challenge was connecting the process to something resembling an industrial control system.

A pump moving water between two tanks is interesting enough as a first simulation, but I want RootSpace to represent the layers through which real industrial equipment is controlled and observed.

So Waterworks 01 also has level sensors and a simulated PLC.

The sensors observe the physical state of the tanks and feed analogue inputs on the PLC. In the other direction, a digital output can command the pump.

At this stage, the control path is effectively mocked through RootSpace’s own interactive terminal.

I can pause the simulation, change a PLC output, resume it and watch the resulting chain of events.

The command affects the pump. The pump’s behaviour requests a flow. The process simulation moves the water according to its constraints. The sensors observe the changed levels, and the PLC inputs acquire the new measurements.

It’s a tiny example of the feedback between control and physical process that real industrial systems depend on.

There isn’t a real industrial protocol involved yet, and I’m certainly not claiming to have built a fully functioning PLC emulator.

But the basic chain is there.

And perhaps more importantly, it’s becoming possible to see how the different layers of RootSpace fit together.

Three versions of the truth

There’s another architectural decision in Waterworks 01 that I’m particularly pleased with.

One of the things I wanted to avoid was a simulation where every component simply reads the same underlying variables and everybody magically agrees about what is happening.

That’s not really how industrial systems work.

There’s a difference between the physical state of a process, what an instrument measures, and what a control system believes it is seeing.

So I’ve deliberately separated those representations.

Physical reality is maintained by the tank model. It knows how much water is actually present, according to the simulation.

Observed reality belongs to the level sensor. It observes the tank and produces a measurement.

Interpreted reality belongs to the PLC. It acquires the sensor’s measurement and maintains its own representation of the input.

flowchart TD
    A["PHYSICAL REALITY<br/>Tank model"]
    B["OBSERVED REALITY<br/>Level sensor"]
    C["INTERPRETED REALITY<br/>PLC input state"]

    A -->|"Physical quantity"| B
    B -->|"Measured signal"| C

For now, the model is relatively straightforward. The simulated sensor observes the tank level, and the PLC input acquires that observation without the sorts of faults and interpretation errors I eventually want to introduce.

But importantly, those are separate pieces of state.

That means they don’t necessarily have to agree forever.

Imagine a tank containing 5,000 litres of water.

A faulty level transmitter might report a value corresponding to 3,000 litres. A misconfigured PLC might then scale that perfectly valid instrument signal incorrectly and believe there are 7,500 litres.

Three different representations of the same process.

Only one describes the actual physical state.

This separation opens up possibilities that I find particularly interesting.

A drifting sensor might gradually mislead an operator. An instrument could become stuck while the physical process continues changing. A control-system configuration error might produce incorrect readings even though the instrumentation itself is functioning properly.

And, eventually, an adversary might manipulate the information available to the control system without changing the underlying physical process at all.

Those sorts of situations could be useful for cybersecurity exercises, operator training and synthetic data generation.

The simulation would retain the authoritative physical state, while the various components and actors within the world might have very different understandings of what is happening.

I haven’t implemented all those failure modes. The current example is intentionally simple.

But the architectural boundaries are already there, and that’s what I wanted to test.

It’s a fairly small detail in Waterworks 01.

I suspect it could become rather important later.

A small thing that feels like something

I think this has been the most encouraging part of the first implementation.

Waterworks 01 is not particularly impressive in isolation.

There are only a handful of simulated components. The process is extremely simple. Almost everything that makes the wider RootSpace idea exciting is still waiting to be built.

But the architectural separation works for this example.

Custom Lua behaviour works. The process respects its physical constraints. The instrumentation and simulated PLC I/O can interact with it.

The tank model knows what the process is actually doing, the sensors know what they observe, and the PLC knows what it has acquired.

For now, those views agree. They won’t necessarily have to forever.

There’s also an independent simulation clock, allowing the world to progress at different speeds without being tied directly to wall-clock time.

Individually, these are small steps.

Together, though, they already feel like the beginning of an engine rather than just a collection of unrelated experiments.

That’s quite satisfying.

It’s also why I wanted to start with a reasonably complete miniature system rather than building a huge collection of disconnected features.

Every new layer has something real, albeit simulated, to interact with.

And with each addition, I’m getting a slightly better idea of whether the architectural decisions make sense.

The next interesting bit

The control path currently works through the development interface, which is useful for proving that the simulation behaves as expected.

But it isn’t how I ultimately want people to interact with RootSpace.

The next step is where things could get considerably more interesting.

I want to add the representation of PLC memory and registers, followed by an industrial protocol such as Modbus TCP.

That would mean an external client could potentially interact with the simulated equipment through something resembling a real industrial control interface.

A command would arrive through the protocol, affect PLC state, command the pump and ultimately change the simulated physical process.

Measurements would travel back in the other direction.

That’s the part I’m really looking forward to.

It would begin to move RootSpace beyond being a simulation that I can control from its own terminal and towards an industrial environment that existing engineering and diagnostic tools can interact with.

And crucially, those tools would be interacting with a system that actually has something going on underneath.

Not simply a Modbus server exposing a few arbitrary registers, but a simulated industrial process whose state changes for reasons that make sense within its own little world.

Once that works, there are rather a lot of interesting possibilities.

Still baby steps

I don’t think Waterworks 01 tells me whether RootSpace will eventually become a good CTF engine, a useful operator-training platform or a way of generating interesting AI research datasets.

Those are much bigger questions.

It certainly doesn’t prove that I can build believable actors, complex nested components or years of simulated industrial history.

But it has given me a first opportunity to put some long-standing architectural ideas into practice.

And so far, I’m happy with what I’ve found.

There is something rather nice about finally taking an idea that’s been circling around for years and watching even a very small part of it work.

Two tanks, a pump, a few sensors and a mocked control chain.

Not exactly the grand industrial world I’ve been imagining.

But it already feels like something.

And I think that’s a rather good first baby step.


The source code and technical documentation live in the RootSpace GitHub repository, currently private during early development.