Offline control-plane simulation
The installed package includes a deterministic software harness. It opens no sockets, reads no clock and requires no devices:
python -m yoke.simulation
python -m yoke.simulation --seed 42 --json
python -m yoke.simulation --scenario partition-conflict --trace
Exit status is zero only when every selected scenario passes. --scenario
can be repeated; its default is the full suite. JSON summaries carry
schema_version, the seed, each scenario's checks, final committed and saved
pairing, driver attachment, work counters and a transmission digest. --trace
adds one JSON line per transmitted control datagram, with virtual time,
origin, sequence, type and body. It contains control metadata only.
The suite covers clean pairing, packet loss, duplication, delay, reordering, partition healing with conflicting equal-counter intents, producer loss and return, driver retries, failed persistence, a crash after the durable write but before completion feedback, sequence recovery after clock rollback and passive observer noninterference. Fault scenarios include a clean recovery period; loss also retries the intent after recovery. Passing establishes eventual convergence for that finite schedule, not a guarantee under permanent loss or arbitrary Byzantine behavior.
Simulation in yoke.simulation hosts real Node instances and runs the real
wire encoder/decoder with a simulation PSK. SimDriver supplies deterministic
device responses. Every successful save updates a separate in-memory durable
slot and feeds Node.on_persist_result; failed saves leave that slot intact.
The harness checks that outgoing sequences were durably reserved, applied
ACKs follow durable pairing and attachment follows persistence. Driver status
feedback follows the original effect batch, preserving sequence order and
discarding intermediate command completions as the real runner does.
from yoke.core.types import Pairing, Role
from yoke.simulation import FaultPolicy, Simulation
net = Simulation(seed=7)
net.add_node("producer", Role.PRODUCER)
net.add_node("consumer", Role.CONSUMER)
net.run(3)
net.set_faults(FaultPolicy(drop=0.2, dup=0.3, jitter=0.05, reorder=0.2))
net.intent("consumer", "producer", version=(1, "operator"), origin="operator")
net.run(5)
net.clear_faults()
net.intent("consumer", "producer", version=(1, "operator"), origin="operator")
net.run(8)
net.assert_agreement("consumer", Pairing("producer", (1, "operator")))
Fault policies apply to an exact source/destination link, then a source, then
a destination, then the default. drop, dup and reorder are probabilities;
latency and jitter are seconds. Each duplicated copy receives an independent
delay. Jitter naturally reorders packets; reorder adds an additional delay
to selected copies. A partition takes disjoint groups including any ephemeral
intent origins; unlisted ids are isolated. It blocks packets both when sent
and when delivered, including traffic queued before the partition.
fail_saves, crash_during_next_save, set_online and restart expose field
failure boundaries. A restart retains the last successful durable slot and
can receive a backwards wall_time; virtual timer time remains monotonic.
Endpoint.driver exposes the existing SimDriver fault scripts. inject
accepts arbitrary bytes for authentication/replay regressions. An observer is
an ideal sender-side bus tap using the real Snooper; adding observers
consumes no random draws and generates no protocol traffic. Actual receiver
capture loss and switch multicast behavior belong in network tests.
The scheduler uses a packet heap and revision-tagged timer heap, avoiding a
scan of all nodes on every packet delivery. record=False avoids retaining
datagrams while preserving the digest, counters, protocol work and invariant
checks. An event budget stops a faulty schedule from spinning indefinitely.
Repeatable benchmark
python benchmarks/control.py --nodes 12 --intents 1000 --repeat 5
python benchmarks/control.py --nodes 32 --intents 10000 --repeat 5 --observers 1
The workload uses two HDMI producers and the remaining nodes as consumers, rotates pairings between producers, drains the virtual network and checks every affected consumer's durable pairing and every peer's announced view. Repeats must have identical state, work counters and transmission digest. The JSON report records Python/host details, workload parameters, per-run wall/CPU durations and medians. Timings are informational; there is no speed threshold. Compare the same workload and environment before and after a change. Encoding, authentication, state-machine work, driver commands and save feedback are included; disk fsync, physical drivers and real networking are simulated. This benchmark therefore measures control software, not switch throughput, media payload performance or hardware latency.
The existing tests/simnet.py remains the test-focused harness for historical
convergence regressions. The installed harness uses public core APIs and has
no dependency on the test package. tests/test_simulation.py pins seeded
scenario outcomes, replayable output, clock/socket independence, observer
noninterference, persistence crash boundaries and fault-policy behavior.