System Architecture
A systems-level view of Mock Machines: how the code is layered, which way dependencies point, and the output and serving surfaces that wrap the core engine. For the dynamic behaviour — the load pipeline and the simulation loop — see Process Flow; for the type graph, see the Data Model.
The layered package structure
Section titled “The layered package structure”Mock Machines is a single Go module. Dependencies point strictly downward:
the CLI orchestrates everything; the internal/api (REST) and internal/mcp
adapters and the internal/graphviz visualizer wrap the engine through the
facade; and the engine (internal/engine) depends only on the standard library
and one YAML parser.
Two code surfaces: facade and engine
Section titled “Two code surfaces: facade and engine”Callers (CLI, REST, MCP, tests) only ever touch the facade (mock_machine/),
which exposes four nested receivers — Lab → Workbench → Scenario → Experiment.
The engine (internal/engine/...) is the implementation; internal/ makes
it unreachable from outside the module, so the facade is its only legal consumer.
Domains within internal/engine
Section titled “Domains within internal/engine”The engine groups into responsibility-aligned subpackages.
| Subpackage | Responsibility |
|---|---|
model/ | Schema & domain types — the shape of an entity, its states, distributions, and the declared scheduling regime. |
compile/ | Turns parsed config into a runnable program: load/ (YAML → typed Config), validate/, plan/ (entity order + partitions), diagnostics/ (--analyze, --dump). |
runtime/ | The simulation loop: experiment.go, executor.go, scheduler.go, and eval/fsm.go (condition eval). |
store/ | Entity storage: the EntityStore interface and the columnar store_archetype.go; dataset/ for CSV/Parquet/seed I/O. |
telemetry/ | Observability: eventlog.go, decisionlog.go, timing.go. |
Entity storage
Section titled “Entity storage”An entity is an instance of a state machine — a current state with its typed
fields and references. At runtime the live entities of each machine type are
stored together in an archetype: one Archetype per machine type, laid out
column-wise as a struct-of-arrays. Code addresses an entity by an
EntityHandle{machineIdx, row}; field access goes through an ArchetypeView
using a precomputed FieldMapping, so reads and writes are O(1) with zero
reflection — cache-friendly iteration over thousands of entities per turn.
The execution model: partitions
Section titled “The execution model: partitions”Concurrency is not declared by the modeller — the planner derives it. At load,
BuildExecutionPlan unions machines connected by spawn, message, or reference
edges into partitions. Disjoint partitions share no state, so they run
independently.
| Entry point | Used by | How it runs |
|---|---|---|
Run(N) | Batch CLI, servers | The partition-loop engine. Each partition runs its own N-turn loop with its own clock, cascade queue, future-queue, and log writers; partitions run on separate goroutines and their logs are concatenated. |
Step / targeted request | Interactive control | Advances by exactly one turn; optionally requests one named event on one entity. |
The serving & output layer
Section titled “The serving & output layer”Several thin, single-purpose surfaces wrap the engine:
- REST API & MCP (
internal/api,internal/mcp) — sibling adapters over the same facade, exposing scenarios and experiments. - Visualization (
internal/graphviz) — one state-transition diagram per machine: a.dotper machine,dot -Tpngfor--png, and a single embedded-SVG HTML report for--html. - Diagnostics —
--analyze(parallelism verdict, hazards, spawn/message/ reference graphs) and--dump(the fully-resolved config as JSON), both early-exit. - Result persistence — one CSV/Parquet table per machine type plus an
experiment.jsonmanifest andevent_log.jsonl;--duckdbimports them into a single DuckDB database.