Method

Every number on this site, where it comes from, and what it cannot tell you. If something here is wrong, that is a bug worth reporting.

Cost attribution

Circinus instruments Harmony patches and selected engine methods with a prefix and a postfix that read a stopwatch. Cost is attributed to the mod whose declaring assembly owns the patch method, which resolves reliably: on a 916-mod list, 1,723 of 1,723 patch costs were attributed with none unresolved.

Time is reported as share of measured frame time, never as raw milliseconds. Milliseconds are entangled with the hardware they were recorded on, so averaging them across machines produces a hardware census wearing a leaderboard's clothing.

share = mod_ms / total_measured_frame_ms * 100   per run, then aggregated

Instrumentation is not free, so it is disclosed rather than hidden: our own patches are tagged and shown in the tables, and a run records whether profiling was active.

Rates and denominators

A count is not a measurement. "1.4 million calls" says nothing until you know what it was counted over, so every count on this site and in the tool is divided by something before it is shown. The thing it is divided by is always the frame.

calls/frame x µs/call ÷ 1000  =  ms per frame   the row can be checked against itself

That identity is the reason for the choice. Share of frame, average milliseconds, worst frame, calls per frame and microseconds per call all divide by the same window, so a row is arithmetically closed: the numbers multiply out, and one that does not is a bug you can see without leaving the table.

Why not per second, or per game tick

Both were tried. Neither survives contact with the question being asked.

Per secondmoves with game speed and frame rate. The same code at 3x speed reports three times the rate, which measures the speed setting
Per 900 game ticksimmune to game speed, but its denominator is zero whenever the game is paused, and it combines with nothing else in its own row — every neighbouring column divides by frames instead
Per frameframes never stop advancing while there is anything to measure, and it shares its denominator with every column beside it

The per-tick figure is still recorded and still shown, in the detail panel, because it answers a real and different question: how often does this happen in the simulation. That is not the same question as where did my frame go, and it is no longer allowed to headline a table built to answer the second one.

Cost per call is what tells you the fix

Share of frame says something is expensive. It cannot say why. Five thousand calls at 0.4µs and three calls at 700µs produce the same share and need opposite remedies — call it less often, versus make the call cheaper. Both figures are published for every patch, because publishing only one of them hands the reader a conclusion they cannot act on.

Rates gain decimal places as they shrink. A patch that runs once every 250 frames is a real reading, and rounding it to 0.0 would make it indistinguishable from one that never ran at all.

Seen, measured, ranked

Three different run counts appear beside each mod, and they are shown as three because collapsing them into one hides which question is being answered.

Seenruns the mod was loaded in. Presence. Increases whenever anyone submits a run containing it, whether or not they profiled anything
Measuredruns that profiled it. Nothing is instrumented until a target is armed in game, so this is normally far smaller. It is the denominator of every cost figure
Rankedruns whose cost could be normalised to a share of frame, which needs a recorded measurement window. The 25-run floor applies to this one

seen ≥ measured ≥ ranked always holds. A large gap between the first two is information about the corpus rather than about the mod: it means plenty of people run it and nobody has pointed the profiler at it.

This was a bug, and it was visible

The mods list previously rendered the narrowest of these three counts under the broadest label. A mod present in hundreds of runs could sit at a run count that never moved, because the number being displayed only counted the runs that happened to have armed it. If you noticed the column looking frozen, you were reading it correctly and it was wrong.

Tick rate and targets

Ticks per second is not one quantity. RimWorld's target rate is 60 x TickRateMultiplier, so it depends entirely on the speed the player chose, and on any mod that alters the multiplier:

Normaltarget 60 tps
Fasttarget 180 tps
Superfasttarget 360 tps
Ultrafasttarget 900 tps
Moddedanything, 330, 660 and 3000 all occur in real runs

A player achieving 200 tps at normal speed has hit the ceiling; one achieving 200 at ultrafast is at 22% of target and struggling. Comparing those two raw numbers is meaningless, so every comparison on this site uses percentage of target.

simulation = achieved_tps / target_tps * 100
Frames per second is a separate measurement

Ticks per second is the simulation; frames per second is rendering. They have different targets, different bottlenecks and different fixes. The simulation can stall while rendering stays perfectly smooth, and the reverse. They are never combined into one figure.

The four speeds are not four fixed rates

The multiplier is computed, not looked up, and the engine changes it without being asked. All of the following are vanilla readings:

Superfast, nothing happening12x → target 720
Superfast, no map loaded18x → target 1080
Ultrafast, boosted150x → target 9000
Any speed, during dangerforced to 1x → target 60

So a band is always labelled by the target that was observed, never by the speed button that was pressed. A run recorded during a raid reads as normal speed throughout, because that is what the engine was doing.

Comparing across speeds

A mean tick rate over a whole run is not a property of the game. Tick rate is capped by the speed the player selected, so the average reports how long each speed button was held as much as it reports the code. Two runs of identical performance disagree by a factor of three if one sat at normal and the other at fast.

What that did to comparisons

A run that happened to spend longer at ultrafast looked like an improvement. A run interrupted by a raid — which pins the engine to normal speed — looked like a regression. Neither statement was about the mod list, and both were being reported as verdicts.

Every run is therefore broken into speed bands, one per observed target, and two runs are compared band by band. Within a band the ceiling is identical in both by construction, so what is left is the game.

compared within a band, never across one   the whole-run mean is shown as context, not as evidence

The speed sweep

Left alone, a recording captures whatever speeds the player happened to use, and two runs rarely overlap enough to compare. The speed sweep makes coverage deliberate: it drives the game through normal, fast, superfast and ultrafast in turn, dwelling at each, so every run ends with comparable samples at all four.

It verifies rather than assumes. Setting the game speed is a request the engine can refuse or override, so the sweep records the target it observed in each phase and the run says so when a phase did not run at the rate it asked for. A sweep that was silently overridden and a sweep that worked must not produce the same-looking run.

Paused samples are dropped rather than counted as a zero-tick band. Frame-driven work keeps running while paused, so those samples are real — they are just not evidence about tick rate, and a zero band would drag every average down in proportion to time spent reading a menu.

The hardware index

The index is a measured score, not a specification lookup. Circinus runs a frozen single-threaded benchmark and reports:

index = millions of workload iterations per second   higher is faster

The workload mixes dependent integer and floating point arithmetic with an unpredictable branch, so it cannot be vectorised or optimised away, and it is deliberately similar in character to pathfinding and tick maths. An index that ranked machines differently from how RimWorld ranks them would be worse than no index.

Best of five, not the average

Every source of interference is one-directional: thermal throttling, background load and laptop power profiles can only ever make a pass slower. The fastest of five passes is therefore the least contaminated estimate, and averaging just folds in whatever noise happened to be present.

Frozen by contract

Changing the workload changes every score, so the benchmark carries a version. Scores from different versions are refused rather than silently mixed. Version 1 reported an arbitrary 100000/ms; version 2 reports the iteration rate above, and no v1 score is comparable to a v2 one.

Because it is measured rather than looked up, two machines with the same processor and different cooling, memory or power settings will score differently. That is the point.

How cohorts are formed

A cohort is the set of runs that are actually comparable to yours. Three axes:

Bucketing on processor and graphics model strings instead would produce thousands of cohorts of two runs each, and the minimum-sample floor would then make nothing publishable at all. A measured index collapses that into a usable number of groups and needs no lookup table to curate.

Changing the mod count changes the comparison, not the run

On the cohort page, the mod count describes the run being compared. Raising it says that run had more mods loaded and still reached the same tick rate, which compares more favourably. It does not model adding mods to your game, which would lower the result rather than raise the ranking.

Percentiles

Placement is reported as a percentile of the cohort, not as a distance from the median. "27% above the median" reads naturally as "above 27% of machines", which is a different and much stronger claim; a percentile says exactly one thing.

percentile = P(cohort_run < your_run) * 100

The distribution is modelled as normal around the cohort median. Once enough real runs exist this will be replaced by the empirical distribution, which needs no assumption about shape.

Correlations

The predictors panel reports Pearson correlation between each hardware attribute and the achieved percentage of target, across the cohort. RimWorld's simulation is overwhelmingly single-thread CPU bound, which is why the CPU index correlates strongly and the graphics card essentially not at all.

This is measured, not asserted

Players routinely blame the graphics card for a low tick rate and buy the wrong upgrade. Capturing all three attributes lets the site demonstrate the relationship rather than claim it, which costs nothing extra once the data is collected.

When we refuse

A tool that answers everything confidently is a tool that is sometimes confidently wrong. Circinus declines in these cases by design:

Fewer than 25 runsno ranking, no verdict, figures shown but not placed
Profiling mismatchan observed run is not comparable to an unobserved one
Different iteration countsa different iteration count is a different experiment
Different calibration versionsscores are not on the same scale
Incomplete runskept and labelled, never silently averaged in

A missing profiler reads as no data, never as zero cost. Otherwise an absent measurement would look like a performance win.

Induced cost, which we cannot attribute

Cost is time spent inside a mod's own code. If mod A spawns four hundred extra things and mod B's tick becomes more expensive as a result, that time is recorded against B, and B looks guilty.

Separating the two requires a controlled comparison on one machine with and without mod A, which is the run diff feature in the tool itself. The site cannot do it and does not claim to. Every figure here means "time spent in this mod's code" and never "time this mod caused".

Measured versus projected

Hardware indices are labelled in the interface:

Projected entries are estimates and are marked as such wherever they appear. They will be replaced by observed medians as runs arrive, not by better guesses. An uncorrected projection is indistinguishable from a measurement to anybody reading the site, which is why the label is carried all the way through.

The tier names are provisional in the same way. They should be percentiles of the observed corpus rather than fixed thresholds, and will be once a corpus exists.

What gets sent

Submission is opt-in and off by default.

File paths are scrubbed before anything leaves the machine, which is covered by tests. No account, no email, nothing personal. Export and delete by install id.

API

Everything the site shows is served by a versioned REST API using the same types the in-game mod writes, so the client and the server cannot drift apart in how a run document is read.

GET /api/v1/meta    /runs    /runs/{id}    /runs/{a}/diff/{b}    /findings

Open the API reference →