Simulation-First Thinking
Why I like starting engineering software with a small model before building the full system.
When a system has real constraints, I prefer to build a small simulation before I build the final workflow.
That does not mean turning every project into a research platform. It means creating a cheap place to ask better questions: how noisy are the inputs, how often do edge cases appear, and what does a failure look like before users or downstream services have to deal with it?
The useful middle layer
Simulation is most useful when it sits between a sketch and production code. It is concrete enough to expose bad assumptions, but light enough to throw away when the shape of the problem changes.
For aerospace-flavoured software, that middle layer is especially valuable. Sensors drift. Timing matters. Control authority is limited. A route, estimate, or decision can be locally valid and still operationally fragile.
What I look for
- Inputs that represent the messy parts of the real system.
- Outputs that make failure visible without a debugger.
- A narrow loop that lets the model change quickly.
The goal is not to predict everything. The goal is to make the hard parts visible early enough that the production design has fewer surprises.
The real world does not keep the happy path intact
Any system that interacts with the physical world or with human behaviour will eventually receive an input its designers did not expect.
The cause may be ordinary: a noisy sensor, a delayed operator response, an incomplete form, a duplicated message, a sudden demand spike, or two individually harmless events arriving in the wrong order. It may also be a disturbance outside the normal operating envelope: a failed actuator, a communications outage, unusual weather, or a user finding a perfectly valid but deeply inconvenient sequence of actions.
The software may be deterministic, but its boundary with the world is not. Sensors carry noise and bias. Networks reorder, delay, and lose messages. People misunderstand interfaces, change their minds, and act strategically. Physical components wear down. Other systems fail in ways that propagate.
That makes unforeseen failure modes less of an exception than the phrase suggests. The exact event may be surprising, but the fact that some surprising event will occur should not be.
The difficult failures are often combinations rather than single faults. A delayed packet is manageable. A stale estimate is manageable. A saturated actuator may be manageable. All three at the same moment can create a state that no nominal test ever visits.
Simulation is a place to disturb the system deliberately
A useful simulation does more than replay the expected workflow. It lets the environment misbehave on purpose.
I want to vary at least four kinds of conditions:
- Boundary inputs: missing fields, extreme values, malformed messages, impossible coordinates, and values that are valid individually but inconsistent together.
- Disturbances: sensor noise, bias, drift, dropped measurements, degraded actuators, traffic bursts, and resource contention.
- Timing and order: delays, duplicated events, clock skew, retries, races, and state transitions that happen earlier or later than expected.
- Behaviour: users abandoning a workflow, operators reacting slowly, competing agents changing strategy, or demand moving away from the assumed distribution.
The point is not to invent one elaborate disaster story. It is to turn assumptions into variables, then move those variables far enough to find where the design stops behaving safely.
That can be as simple as sweeping a parameter across its plausible range. It can mean Monte Carlo runs over uncertain inputs, fault injection into a service graph, a hardware-in-the-loop test, or a discrete-event model of queues and timing. The technique matters less than the question being asked:
Which assumptions have to remain true for this system to stay correct, stable, and recoverable?
Once those assumptions are explicit, they can become test cases, operating limits, alerts, fallback modes, or reasons to change the design.
Black Swans are a warning about the model
Nassim Nicholas Taleb uses the term Black Swan for an event that sits outside ordinary expectations, has extreme impact, and looks more explainable in hindsight than it was in advance. This summary of The Black Swan also makes an important point: conventional prediction tools can be weakest where rare events dominate the outcome.
There is an obvious limit here. If I can describe an event precisely enough to assign it a clean probability and put it in a scenario table, it is not entirely unforeseen. A simulator cannot enumerate the unknown unknowns. Running a million samples from the wrong distribution only produces a very precise picture of the wrong world.
Simulation is still useful, but for a slightly different reason. It can test the system's fragility without predicting the exact event that will expose it.
Instead of asking only, "What is the probability of this specific catastrophe?", I can ask:
- What happens when a critical input disappears?
- Which component has an unbounded failure cost?
- Does one bad event cascade across the system?
- Can the system enter a state from which it cannot recover?
- Do operators receive enough information to intervene?
- Which safety margin disappears first as conditions move away from nominal?
These questions do not forecast a Black Swan. They look for designs that turn surprise into catastrophe. That is a more achievable engineering target.
Cheap failures are valuable
Real-world testing is necessary, but it is often slow, expensive, difficult to repeat, or unsafe at the limits. A simulation can compress time, reset the state, run scenarios in parallel, and reproduce the same failure with one variable changed.
That changes the economics of learning. A failed simulation run may cost seconds of compute and a few minutes of inspection. The equivalent lesson in a deployed backend might arrive as corrupted data, an outage, or a long recovery. At the hardware boundary it could consume test equipment, damage a component, or put people and vehicles at risk.
Simulation does not remove the need for prototypes, field tests, staged rollouts, redundancy, or physical safety analysis. It makes those expensive activities better targeted. If a cheap model can reject a brittle architecture or reveal a missing fallback, there is little value in waiting for the real system to demonstrate the same point more convincingly.
The savings are not only in preventing the worst outcome. They also come from avoiding repeated integration cycles, shortening investigations, and discovering early that the system was optimised for an operating regime it rarely encounters.
Fuzzing the input boundary
Fuzzing is the software-testing version of deliberately making the environment uncooperative. A fuzzer automatically supplies invalid, unexpected, malformed, or semi-random inputs and watches for crashes, hangs, invariant violations, information leaks, or other unexpected behaviour. NIST describes fuzz testing as submitting combinations of inputs to a target to reveal how it responds.
Pure random bytes are useful for some parsers, but many systems need structured fuzzing. A message can be syntactically valid JSON and still contain a negative duration, an impossible latitude, an empty identifier, a timestamp decades ahead, or two fields whose relationship violates a business rule. The more stateful the system, the more useful it becomes to fuzz sequences as well as individual values:
create -> cancel -> retry -> confirm
create -> confirm -> duplicate confirm -> timeout
update -> stale update -> delete -> delayed updateThose sequences are small simulations of an environment that does not respect the intended order.
The test also needs an oracle: a property that says what must remain true even when the input is strange. The program must not crash. An account balance must not be created twice. A controller must stay within its commanded limits. A rejected message must not mutate state. A retry must not repeat an irreversible action.
Fuzzing and system simulation operate at different scales, but they share the same habit. Both stop treating the expected input as the complete specification.
What the simulation has to admit
A simulation can create false confidence if its assumptions stay hidden. A clean dashboard and thousands of successful runs do not compensate for omitted dynamics, unrealistic distributions, or a failure detector that cannot see the failures that matter.
I would therefore keep three caveats attached to any result:
- The model has a boundary. State what it includes and what it ignores.
- Randomness is not realism by itself. Distributions need evidence, calibration, or an honest label that they are exploratory.
- Success is weaker evidence than failure. A failed run proves that the modelled design can break. A successful run proves only that it survived the scenarios that were actually modelled.
The useful outcome is not a claim that the system is surprise-proof. It is a design with fewer brittle assumptions, clearer operating limits, observable failures, and a rehearsed path back to safety.
That is why I like simulation-first thinking. It does not promise foresight. It gives uncertainty somewhere cheaper to land.