This page covers the code-level implementation of wiRedPanda's simulation engine: the four-state logic system, simulation loop phases, topological sorting, wiring data structures, and feedback loop handling.
For a conceptual overview of the simulation model, see the [Simulation Guide](Simulation.md).
---
## Four-State Logic
All signals in wiRedPanda use four states, defined in `App/Core/Enums.h`:
| State | Value | Meaning | Visual Color |
|------------|-------|------------------------------|--------------|
| `Unknown` | -1 | Not yet resolved / floating | Gray |
| `Inactive` | 0 | Logic LOW | Dark green (theme-dependent) |
| `Active` | 1 | Logic HIGH | Green (theme-dependent) |
| `Error` | 2 | Bus conflict / indeterminate | Red |
`App/Core/StatusOps.h` provides logic operations that respect these states with **domination rules**:
- **AND**: `Inactive` dominates — `AND(0, Unknown) = 0` because any false input makes the AND false.
- **OR**: `Active` dominates — `OR(1, Unknown) = 1` because any true input makes the OR true.
- **NOT**: Inverts `Active`/`Inactive`; passes `Unknown` and `Error` through unchanged.
- **XOR**: Requires all inputs to be known; any `Unknown` yields `Unknown`.
This matters for simulation correctness: a combinational gate can produce a determinate output even when some inputs are unconnected or unknown, which matches real-world digital logic behavior.
### Strict vs. Permissive Input Updates
Elements choose which input-reading strategy to use:
- **`simUpdateInputsAllowUnknown()`** (permissive) — Used by combinational gates. Allows `Unknown`/`Error` through so domination rules can resolve them. Only fails if a port is truly unconnected with no default.
- **`simUpdateInputs()`** (strict) — Used by sequential elements. Any `Unknown` or `Error` input causes the element to propagate `Unknown` to all outputs and bail out. Sequential elements need complete, valid data.
---
## Simulation Loop
The simulation (`App/Simulation/Simulation.cpp`) runs on a **1ms QTimer** tick. Each tick executes these phases in order:
```mermaid
flowchart TD
timer(["QTimer (1 ms)"])
clocks["0. updateClock()
Advance clock elements (real-time)"]
step1["1. updateOutputs()
Propagate user inputs: buttons, switches"]
step2["2. updateLogic()
Process elements in topological order:
gates (zero-delay), flip-flops (edge)"]
feedback{"Feedback loop?"}
run_logic["Run updateLogic()
on feedback elements"]
settled{"Settled or
iter >= 10?"}
step3["3. updatePort()
Propagate values via connections (out->in)"]
step4["4. Update outputs
LEDs, displays, buzzers redraw"]
wait(["Wait for next tick (1 ms)"])
timer --> clocks --> step1 --> step2 --> feedback
feedback -- Yes --> run_logic --> settled
settled -- No --> run_logic
settled -- Yes --> step3
feedback -- No --> step3
step3 --> step4 --> wait
```
---
## Topological Sorting
Before simulation starts, the engine builds a dependency graph from wire connections and computes a **topological sort** (`App/Core/Priorities.h`):
- Each element is assigned a **priority** = length of the longest path from it to any sink (output), plus one.
- Elements with higher priority are evaluated first (sources before sinks).
- This guarantees that when an element's `updateLogic()` runs, all its predecessors have already been updated this cycle.
```
Input -> AND -> OR -> LED
1st 2nd 3rd 4th
```
This is done during the `initialize()` phase, which builds the dependency graph from the scene's connections.
---
## Simulation Wiring
During simulation initialization, physical wire connections are converted into a lightweight predecessor map:
```cpp
struct SimInputConnection {
GraphicElement *sourceElement = nullptr;
int sourceOutputIndex = 0;
};
```
Each element stores a vector of these for its inputs. During `updateLogic()`, reading an input is just a direct pointer dereference — no graph traversal needed per tick.
---
## Feedback Loop Handling
Some circuits have feedback (e.g., an SR latch where outputs feed back to inputs). The engine detects these using **Tarjan's strongly connected components** algorithm:
1. Any element in a cycle is marked as a **feedback node**.
2. Instead of a single-pass evaluation, the engine uses **iterative settling**: it runs `updateLogic()` on all elements, checks if any output changed, and repeats — up to 10 iterations or until convergence.
3. If the circuit does not converge (e.g., a ring oscillator), a warning is emitted via the status bar.
---
## See Also
- [Simulation Guide](Simulation.md) — Conceptual overview of the simulation model
- [Element System](Element-System.md) — How elements implement `updateLogic()`
- [Architecture Overview](Architecture-Overview.md) — Where the simulation engine fits in the overall architecture