MD Tick Profiler
SkillFiles & storageVisualize and explain MD's recurring per-tick scripted workload (the daily/weekly/monthly on_action hooks and everything they run) as an interactive flamegraph sized by script ops with file:line links, plus a text report and bounded static spot checks. Use for any question about MD performance, lag, tick cost, on_actions, "what runs each day/week/month", why the game is slow, or profiling the mod — even without the word "profiler".
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the MD Tick Profiler skill
What this skill tells your AI
The instructions your AI receives, as published by millenniumdawn/millennium-dawn in .claude/skills/md-tick-profiler/SKILL.md and read by ahel’s review.
The base-game profile console command is usable for bounded in-game spot tests
and provides measured timing. This skill also gives a drill-down call tree of
what each recurring tick runs and what's causing the load, reconstructed
statically from the scripts by tools/analysis/tick_audit.py. Native
profiling and static analysis answer different questions and neither replaces
the other.
Three complementary surfaces:
- Flamegraph (
--flamegraph): a self-contained interactive HTML call tree. Root → cadence (daily/weekly/monthly) → on_action hook → thescripted_effectsit calls → the events/decisions those fire. Every node is sized by ops and every node links to itsfile:linein VS Code. - Text report (
--list): precise, greppable itemization for when the user wants exact lists rather than a picture. - Static spot checks (
--spot-check): context-aware findings for known hot patterns, with deterministic JSON and optional severity gating.
What "ops" means (say this to the user; it's the crux)
HOI4 runs scripts on a clock. Every in-game day/week/month it executes a to-do
list of scripted instructions for every country. An op is one such
instruction — a key = ... statement: set a variable, check a condition
(limit/trigger), fire an event, call an effect. A node's op count is its own
statements plus everything it transitively calls. More ops = more work per
tick = more lag. It is a proxy for cost, not measured milliseconds. Native
profile gives measured timing for bounded workloads. Always frame it this way
so the user isn't misled into reading ops as time.
It is a call tree, not a call graph: a shared effect called from three hooks is counted under all three (exactly how a profiler attributes cost), so the tree totals are larger than the flat per-hook numbers in the text report — that's expected, not a bug.
Workflow
Native measured spot tests
Use native profile for short before/after measurements of a specific workload.
Keep the save, game speed, map position, open UI, and observation window the
same. Repeat the sample before drawing conclusions. Use the static report to
identify source locations worth measuring, then use native timing to verify
whether a change matters in game.
1. Generate the flamegraph
Run from the mod root. Write to a temp path so you don't clutter the repo, then open it in the user's browser.
# from the Millennium_Dawn mod root
python tools/run.py tick_audit --flamegraph "$TMPDIR/md_ticks.html" --tree "$TMPDIR/md_ticks.json"
On Windows the temp dir is %TEMP% (in the Bash tool, "$TEMP" or
/tmp-style paths under it). A reliable cross-platform choice: write to the
system temp directory. Then open it:
- Windows:
start "" "<path>" - macOS:
open "<path>" - Linux:
xdg-open "<path>"
If the user is working in VS Code, mention they can also right-click the HTML →
"Open with Live Server" or just open the file — and that the file:line links
in the page open straight into their editor.
The command prints the total op count. The --tree JSON is optional but handy:
read it to pull the headline findings (below) without screenshotting.
2. Point the user at the top of each tick
After generating, read the --tree JSON and tell the user, per cadence, the
one or two heaviest hooks — that's where any optimization pays off. For each
cadence the tree's children are already sorted heaviest-first, so:
daily → children[0] (name, total ops, file)
weekly → children[0]
monthly → children[0]
Report them plainly, e.g. "Your daily tick is dominated by on_daily in
MD_antarctica_on_actions.txt:426 at N ops — open it and expand to see the
antarctica station-processing effects underneath." Name the file and let them
click in.
3. Let them explore
Tell the user the three interactions:
- Click any row to open what it calls (lazy, so it stays fast).
- Click a
file:lineto jump to that exact spot in VS Code. - Type in the filter box (e.g. a country tag
PER, a systemmoney, an event namespaceeconvent) to collapse the tree to just matching nodes with the path expanded — great for "show me everything the economy does each month" or "what does Persia run".
Static spot checks
Run the broad defaults or name a file or directory:
python tools/run.py tick_audit --spot-check
python tools/run.py tick_audit --spot-check common/on_actions --fail-on high
python tools/run.py tick_audit --spot-check common/decisions --format json
The static rules carry execution context into findings. Daily global loops rank
higher than monthly tagged loops, focus completion stays one-shot, and startup
loops are suppressed. The report also records conservative assumptions learned
from prior reviews, including cheap can_staff_* triggers and the distinction
between fixed-tag scope opens and unbounded loops.
When the user wants exact lists, not a picture
Use the text report instead of (or alongside) the flamegraph. It shares the same accuracy guarantees.
# Daily, weekly, monthly summary plus heaviest countries
python tools/run.py tick_audit
python tools/run.py tick_audit --list hooks --cadence weekly
python tools/run.py tick_audit --list events --cadence monthly
python tools/run.py tick_audit --list decisions --limit 0
python tools/run.py tick_audit --tag USA # everything USA runs
Full detail on the report, the accuracy contract, and every flag lives in references/reading-the-flamegraph.md — read it before answering questions about why a number looks the way it does (e.g. "why is every country's daily column the same", "does this event really fire", global-vs-per-country hooks, staggering).
Accuracy — the promise to keep
The analysis only counts what genuinely runs on a cadence: work reachable from a
real recurring hook. It never treats an event as "fires weekly" just because it
exists in events/, and it does not attribute events fired deep inside
shared, tag-gated scripted_effects. For example,
if = { limit = { original_tag = HOL } ... } is not attributed to every
country. Those effects are counted as work (ops) but not as fires,
because their firing can't be proven statically. If the user asks whether
something "really fires", explain this and open the file:line so they can read
the guard themselves. Never oversell ops as measured time. Use native profile
for bounded measured spot tests and this static audit for broad source and
reachability coverage.
Signals
- GitHub stars
- 122
- Forks
- 84
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
md-tick-profiler- Source
- github.com/millenniumdawn/millennium-dawn