Ariadne.PerformanceTests times ArcScheduler against ticking every arc each frame. Run Bin\Release\Ariadne.PerformanceTests.exe for the 32-bit build the games ship on, and Ariadne.PerformanceTests64.exe for the same code as 64-bit. Arguments: --quick for fewer rounds, --min-sleep=N for the scheduler's minimum sleep, and text to run only the scenarios with it in their name. It refuses to run under a debugger or from a Debug build, and warns when the machine is busy. Each row has the two sides take turns batch by batch, so a busy machine inflates both and the ratio still holds. Before timing a scheduler row, it runs both hosts for 300 frames and throws unless every arc woke the same number of times. What it showed (2026-10-12, 8 logical processors, one busy at 20 to 40 percent). Speedups are over ticking every arc, for 32-bit and then 64-bit: A sleeping arc costs nothing: 100000 arcs asleep take 0.07 us a frame, against 3.7 to 5.9 ms to tick them all. Waking every 100 frames is 6.5 to 12 times faster (13 to 22 in 64-bit), every 20 frames 1.8 to 2.8 times (2.5 to 4.6), every 10 frames 1.1 to 1.5 times (1.9 to 2.5). Arcs waking at mixed intervals, 12 to 20 frames apart, are 1.1 to 1.9 times faster (2.0 to 3.2). Waking every 5 frames or every frame, and arcs that never wait, are 0.75 to 0.9 times (0.87 to 1.0). Those arcs never leave the running list, so what they pay is the scheduler's own bookkeeping, about 5 to 10 ns an arc on top of a 35 to 50 ns tick, plus the two stores a Wait makes to say when it wakes. Nothing allocates, including a frame where 300 arcs with different deadlines wake together. Before MinimumSleepFrames an arc that waited a frame at a time went through the queue each frame and ran 4 to 12 times slower than a plain tick. A trip through the queue is about 200 ns where a tick is 35 to 50, so sleeping pays only for waits of more than about 8 frames. A lower minimum was tried again with the faster queue and only made waits of 5 frames worse: 0.45 to 0.7 times asleep, against 0.75 times awake. --min-sleep=N sets it for a run. The arcs that wake in a frame are put back in registration order by Amphora's Sort.Items. Radix passes there gained 16 to 25 percent on the mixed-interval rows and 4 to 15 percent on the other herds at 10000 arcs and up, over Array.Sort with a comparer. At 100000 arcs waits of 10 frames now just break even (1.0 to 1.15 times in 32-bit), where they lost at 0.6 times before, so the default suits arcs from the thousands up to a hundred thousand. The herd rows (2026-10-16) put 1000 to 100000 arcs on one Trigger and raise it on a period. The control polls a flag in every arc each frame, as a script looping on Yield() would. Each timed call is a whole period, because a raise every 100 frames is missed by a batch of a few frames. Before timing a row it checks that both sides woke every arc the same number of times. Nothing allocates, including the frame that raises. In 32-bit and then 64-bit: Arcs waiting while nothing is raised cost 0.04 to 0.07 us a frame, against 25 to 5000 us to poll them. A raise every 100 frames is 23 to 29 times faster than polling (27 to 44), every 10 frames 2.3 to 3.0 times (2.6 to 4.5). A raise every frame is 0.23 to 0.31 times (0.27 to 0.46), so a trigger that fires each frame at every arc costs 3 to 4 times what a poll does. A woken arc costs about 80 to 90 ns at 1000 or 10000 arcs, and 120 to 150 at 100000, where the arcs no longer fit in the cache. In 64-bit it is 90 to 120 ns, and 180 to 200 at 100000. That covers the raise, the tick that resumes the arc and the subscription it makes to wait again, against about 25 ns for a poll that finds nothing. So a trigger wins when an event is rarer than every few frames for the arcs waiting on it, and polling wins for one that every arc needs every frame. The 64-bit run was on a machine 45 percent busy, so read its ratios and not its times. Arcs here subscribe in the order they were added, so the raise never sorts them.