FERS began at the University of Cape Town as a signal-level simulator for multistatic and netted radar research. Its receiver-domain model brings geometry, propagation delay, waveforms, timing, noise, and interference together as complex samples a researcher can analyse. Brooker’s original work established that scientific foundation.
By the time FERS reached my master’s project, it had a long-lived batch workflow and a modernised C++23 baseline. I worked on turning that baseline into a shared platform: one simulation core used by command-line and desktop clients, with common scenario semantics, several native radar modes, and offline and live receiver output.
The hard part was keeping the meaning of a receiver sample intact while changing almost everything around it.
The Model I Inherited
A signal-level simulator occupies a useful position between a simple geometric sketch and a full electromagnetic model. FERS forms complex receiver samples from sources, paths, and targets. At that observation level, a researcher can study timing, Doppler, phase, coherent addition, noise, and interference before building a physical experiment.
That receiver-domain meaning guided every extension. A new waveform mode had to enter the same propagation and receiver model. A new interface had to edit the same authoritative scenario. A new output path had to carry samples whose timing and metadata a researcher could interpret.
The original research established pulsed and carrier-free behaviour and a batch XML workflow. The executable codebase carried it forward. The modernised C++23 baseline improved ownership, dependencies, build structure, and regression coverage. My master’s work built on those foundations with the library architecture, language boundaries, interactive workflow, additional waveform modes, output paths, and verification programme.
| Stage | Software shape | What carried forward |
|---|---|---|
| Original research | Batch scenarios and a signal-level model | Complex receiver samples and multistatic geometry |
| Long-lived executable | XML in, simulation run, files out | The established radar formulation |
| Modernised C++23 baseline | Clearer ownership, build, and regression suite | A maintainable native starting point |
| Master’s platform extension | Shared library, clients, modes, and output sinks | One receiver-domain model across workflows |
Why an Executable Was No Longer Enough
The existing executable served a clear workflow: give it an XML scenario, run it, inspect the output. Visual scenario editing changed that equation. The desktop client needed to change a loaded world, inspect it, run it, and recover from an invalid edit. Library callers and live consumers needed access to the same simulation behaviour.
I kept those clients close to the native core. Duplicating radar logic in the desktop application would create two sources of simulation truth. A remote service would add transport, session identity, and cancellation rules to a workflow whose clients run locally. A library gives both clients direct calls into one world.
I chose a C++23 library, libfers, as the owner of the simulation world. A narrow C interface exposes opaque contexts and operations to clients. The command-line application calls it directly. The desktop application reaches the same core through a Rust/Tauri bridge, while React presents and edits validated scenario state.
flowchart TB
cli[Command-line client] --> api[Public C interface]
ui[React desktop UI] --> rust[Rust/Tauri bridge]
rust --> api
api --> core[libfers C++ core]
core --> blocks[Receiver sample blocks]
blocks --> hdf[HDF5 files]
blocks --> live[Live packet output]
Both clients reach the same radar semantics and execution path. The desktop editor and batch runner offer different ways to work with a scenario while sharing its physics.
The C Boundary Had a Specific Job
The public C interface exposes a small set of operations over opaque contexts. C++ object layout, templates, exceptions, and allocator choices stay inside libfers. The command-line client can call that boundary directly; the desktop client can wrap it in Rust. The interface remains small enough to document and test as a contract.
That boundary still needed precision. Who creates and destroys a context? Which buffers belong to the caller? What happens after an error? May callbacks re-enter configuration? Can a client hold a pointer to an object after replacing a scenario? An opaque handle hides representation only if its lifecycle rules are equally clear.
Rust owns the native handle on the desktop side and contains foreign calls in the bridge. React receives structured state. C++ owns the simulation world. The C interface gives these parts a stable meeting point.
Identity Is Not One Thing
Identity means different things at each boundary. A native pointer names an object instance right now. A process-local simulation ID names a typed object during one run. XML uses human-readable names and references that survive saving and reloading. JSON transfers interactive state to a client whose ordinary number type cannot exactly represent every 64-bit integer.
We therefore carried native IDs into JSON as decimal strings. An XML round trip can preserve the declared relationship graph while allocating new process-local IDs. A saved name is not a promise that a pointer or numeric ID will remain identical after reload.
| Representation | Lifetime | What it identifies |
|---|---|---|
| Native pointer | One object instance | The allocation used by the current world |
| Simulation ID | One running process | A typed object inside that process |
| JSON decimal string | Current process session | The full process-local ID without numeric rounding |
| XML name and reference | Saved scenario | A declared relationship to reconstruct on load |
The distinction matters during replacement. A UI holding a native identity after the underlying object changes can address the wrong object. A saved XML reference has a different job: reconstructing the relationship when a scenario loads. Each representation carries the identity appropriate to its lifetime.
Publishing State Without Half a Scenario
Interactive clients create another design pressure: a user can change a scenario that is already loaded. A full replacement and a small edit have different costs and failure modes.
For a complete JSON replacement, the core builds a candidate world, resolves references, and validates it before publishing it as active. If validation rejects the candidate, the active world stays in place under the documented state contract. This gives the operation a visible commit point. An invalid late reference should not leave the first half of the scene edited.
flowchart TB
update[Full JSON update] --> candidate[Build candidate world B]
candidate --> resolve[Resolve references and validate]
resolve --> decision{Accepted?}
decision -- yes --> publish[Publish B as active world]
decision -- no --> retain[Keep active world A]
For a granular edit, rebuilding the whole world would be wasteful and disruptive. The desktop path sends ordered object-level updates. Full and granular writes share one dispatch order, repeated snapshots can coalesce, and recovery re-establishes client alignment after a rejected or interrupted update.
The editor needs to know which version the engine is actually using. Candidate validation, publication, and recovery make that answer precise.
One Clock for Different Radar Modes
The runtime had to represent pulsed, continuous-wave (CW), frequency-modulated continuous-wave (FMCW), and stepped-frequency continuous-wave (SFCW) behavior together. Running four independent simulators and adding their output files afterward would miss interactions that occur before receiver processing, gating, noise, and quantization.
FERS uses a hybrid event-and-sample model. Scheduled events mark sparse transitions such as a pulse start or a receiver window. Between event times, active streaming sources and receivers advance over sample intervals. When the next event timestamp is admitted, continuous state advances to that boundary and the events at the timestamp are dispatched as a batch. Mode-specific source state enters a common path and coherent receiver sum.
This design avoided a global fixed time step small enough for every waveform at every moment. It also avoided separate mode clocks that would have to be reconciled after the fact. The price was making timing rules explicit: equal-time events, sample endpoints, delayed echoes, source retirement, and shutdown behavior all needed contracts and tests.
SFCW makes the timing rule tangible. The active RF frequency changes by step and dwell, while a receiver can observe a delayed echo emitted during an earlier dwell. FERS selects the transmit frequency at the path’s retarded time, then feeds that contribution into the common receiver calculation.
The same timing rule appears in the controlled SFCW range result.

The Receiver Block Became the Output Boundary
Once the channel and receiver have formed timestamped complex samples, delivery becomes a different problem. Offline research needs files that can be reopened with samples and metadata intact. A live consumer needs packets, bounded buffering, a pacing clock, and a policy for what happens when the sender or receiver cannot keep up.
I put those delivery choices after a common receiver-block lifecycle. HDF5 serves offline analysis. The FERS VITA profile serves live packet consumers. Both receive blocks from the same simulation world. The live path applies pacing and backpressure; the continuous HDF5 path carries a whole-duration memory cost.
flowchart TB
blocks[Receiver I/Q blocks] --> hdf[HDF5 file]
blocks --> live[Packet sender]
hdf --> file[Offline analysis]
live --> queue[Pacing and bounded queue]
queue --> udp[Live UDP consumer]
The live output is documented as a FERS profile of the VITA packet family, with its own packet, timestamp, scale, and queue contracts. Those details let a consumer interpret the stream and gave us concrete behaviour to exercise in integration tests.
What the Evidence Proved
The evaluation asked three different questions. Do the software paths behave correctly? Do the radar calculations agree with independent expectations? Does a recorded case reproduce the behaviour observed in a physical experiment? Each question needed its own evidence.
The dissertation used software suites, controlled single-mode scenarios with independent expectations, mixed-mode scenarios, and end-to-end interface and lifecycle scenarios. Fifty-three predefined simulation and workflow scenarios met their acceptance criteria. The matrix covered pulsed, CW, FMCW, SFCW, every non-singleton combination of the four modes, and the principal workflow paths. Delay, Doppler, phase, noise, scheduling, identity, and output behaviour each had explicit checks.
| Verification lane | Scenarios | Main question |
|---|---|---|
| Pulsed, CW, FMCW, SFCW | 7 per mode | Do receiver quantities agree with independent expectations? |
| Mixed modes | 18 | Do modes share scheduling and receiver output coherently? |
| End-to-end workflows | 7 | Do interfaces, lifecycle, and output paths hold together? |
| Total | 53 |

A recorded-FM passive-radar case brought measured evidence into the programme. In the clean Constantiaberg—Malmesbury case, the expected range and Doppler cells matched across 45 four-second intervals against an independently derived track. A paired interference case showed the broadband-noise mechanism through raised background and reduced target contrast.
The result is a clear set of findings: the software and signal relationships passed their controlled checks, and the clean recorded-FM case matched its physical reference. I can point to the exact test and output behind each statement.
What Comes Next
The current model uses free-space narrowband propagation and point targets. The next scientific extensions are terrain-aware paths, clutter, polarization, and richer target response, tested against calibrated measurements. On the systems side, long-running simulations would benefit from more incremental sample storage and bounded timing histories.
Longer runs will give us measurements of memory, throughput, cancellation latency, and stream closure to guide the next changes to buffering and output.
The Systems Lesson I Took Away
My master’s work taught me that preserving a scientific model while changing its software shape is fundamentally an exercise in authority and boundaries. The library owns the world. The C interface owns a small cross-language promise. XML and JSON carry different forms of identity. Events and samples share one time model. Receiver blocks separate physics from delivery. Verification and validation answer different questions.
Those decisions turned an inherited executable-centered simulator into an extensible research platform. A researcher can reach the same receiver-domain model through more workflows, and each new path has a set of checks that ties it back to the underlying signal model.
The FERS repository contains the code and project documentation. The dissertation follows the lineage, architecture, implementation, verification, and recorded-FM study in full.