# sim/difficulty Difficulty controller simulator for Igneum (3 October 2026). Model, candidates, results and the devnet record behind `docs/analysis/difficulty-2026-10-03.md` and section 2.3 of `docs/spec/02-consensus.md`. ## Files | File | What | |---|---| | `sim.py` | the simulator: block production from a hash-rate profile through six controllers, metrics as markdown | | `devnet-2026-10-03.csv` | every header of the overnight devnet (observer node wRPC, `ws://127.0.0.1:28640`): hash, DAA score, blue score, timestamp, bits, difficulty (2^255 / target, the node's convention), log2 target, epoch, chain flag, parent count, selected parent | | `results.md` | raw simulator output: synthetic set, timestamp-noise set, tuning sweeps, record replay, test-network comparison | | `testnet-igneum-2026-10-03.csv`, `testnet-kaspa-2026-10-03.csv` | every header of the two 3-node CPU test networks (Igneum rule, 20 min; Kaspa rule, 10 min), same columns | ## Model One selected chain, DAA score = height, target 1 block per second, solve times exponential with mean (expected hashes per block) / (hash rate). A hash-rate profile is piecewise constant in time (miners joining and leaving) or keyed to the 3,600-block epoch (program cost steps). Timestamps are the true times in milliseconds; `--ts-noise N` adds uniform +-N ms miner clock jitter. Metrics per step: first time the 121-block centred mean of the expected block rate is within 10% of target; "settled" = the first time it then stays within 10% for 100 blocks; overshoot past target on the far side; blocks spent above 2x and below 0.5x target rate; worst inter-block gap. Steady state: standard deviation of the expected rate ratio over a steady hour, blocks-per-minute coefficient of variation (Poisson alone gives 0.129 at 60 blocks per minute). ## Controllers | Name | Rule | |---|---| | `kaspa` | KIP-4 sampled DAA exactly as the fork runs it: samples at every 4th block, 661 samples, average target of the window without its earliest block, times measured over expected, genesis bits held until 150 samples (600 blocks) | | `monero` | Monero `next_difficulty`: 720 blocks, timestamps sorted, 60 cut from each end, lag 15 | | `lwma60`, `lwma120` | Zawy LWMA-1 (2018 reference), linear weights, solvetimes in [-132 s, 6 T] | | `igneum` | the Igneum rule of spec 2.3 (two lanes, work-over-time estimators, epoch windows, 3% harden and 10% ease clamps) | | `igneum-literal` | the same with the brief's literal trigger (short-window rate off target by 25%), kept to show the chatter | ## Runs python3 sim.py synthetic set, every controller python3 sim.py --ts-noise 500 the same with +-500 ms timestamp jitter python3 sim.py --tune parameter sweep for the Igneum rule python3 sim.py --record devnet-2026-10-03.csv --events 2026-10-03T19:28:05+00:00,2026-10-03T19:42:40+00:00 exact replay check of Kaspa's rule on the record, then the record as a hash-rate profile through every controller python3 sim.py --dump up50:igneum per-block trajectory (t ms, height, D, hash rate, rate ratio) Requirements: Python 3 only (numpy not needed). The full synthetic set takes about two minutes. ## Limits No DAG (one chain, no parallel blocks, no reds), no network latency, no miner template refresh delay, honest timestamps unless `--ts-noise`. The exact-replay check of Kaspa's rule matches the record bit for bit while the devnet was a chain and diverges once it widened past one block per second, because a chain-only replay cannot see the merged blocks that the sampled window includes.