Badges update automatically and link to the mod page on MineUniverse.
A server-side Minecraft mod that answers the question profilers usually dodge: which dimension, chunk and base is loading the server right now?
Tools like spark tell you what code is slow. LagMap tells you where in the world the work is. It samples every loaded chunk on a timer, ranks them by an estimated load score, and writes a snapshot you can read in chat, in JSON, or in a self-contained HTML report.
forceLoadedChunkCount always reports the total)The score is an estimate, not a measurement
estimatedScoreis a weighted count of what a chunk contains. A chunk full of ticking block entities scores high because such chunks usually cost tick time - but a single pathological entity can out-cost a thousand hoppers and LagMap will not see it. Use the score to decide where to look next, then confirm with spark. Every export repeats this disclaimer so it cannot be lost when a report is pasted into a bug tracker.
All commands require permission level 2 by default (configurable, see permissionLevel).
| Command | Effect |
|---|---|
/lagmap status |
MSPT windows, TPS, sampler state, and per-dimension totals from the last sample. |
/lagmap top [count] |
The hottest chunks server-wide from the last sample. count defaults to 10, max 100. |
/lagmap dump |
Samples immediately and writes latest.json + latest.html, regardless of config. |
/lagmap start |
Starts periodic sampling. |
/lagmap stop |
Stops periodic sampling. Tick timing keeps running. |
/lagmap tp <dimension> <chunkX> <chunkZ> |
Teleports you to the centre of that chunk. Player-only. |
Typical investigation:
/lagmap status # is MSPT actually high, and which dimension is heaviest?
/lagmap dump # take a fresh sample right now
/lagmap top 20 # which chunks dominate that dimension?
/lagmap tp minecraft:overworld 145 -302
/lagmap tp derives its Y coordinate from the surface heightmap, which is meaningful in
overworld-like dimensions and misleading in the Nether or in custom dimensions - it will
warn you. Teleporting loads the destination chunk, which is the one place LagMap causes
chunk loading.
Written to config/lagmap/ relative to the server directory:
| File | Contents |
|---|---|
latest.json |
The machine-readable snapshot. Stable key order, schemaVersion: 1. |
latest.html |
Self-contained report with the snapshot embedded. Double-click to open. |
Both are written to a temporary file and moved into place, so a viewer polling the JSON never reads a half-written document.
web/viewer.html in this repository is the same viewer with no data baked in: open it and
drop any latest.json onto it. Useful for reading a snapshot someone sent you.
{
"schemaVersion": 1,
"timestamp": "2026-08-20T12:00:00Z",
"chunkEnumeration": "chunkmap (complete: every fully-loaded chunk)",
"scoreFormula": "estimated (idleEntities*0.10) + ...",
"mspt": {
"last": 48.20,
"tps10s": 20.00,
"last10s": { "samples": 200, "average": 45.10, "min": 38.00, "max": 91.40 },
"last30s": { ... },
"last60s": { ... }
},
"totals": { "dimensions": 3, "loadedChunks": 1841, "entities": 2204, "blockEntities": 9033 },
"dimensions": [ { "dimensionId": "...", "totalEstimatedScore": 0, "hottestChunks": [ ... ] } ],
"topChunks": [ { "dimensionId": "...", "chunkX": 0, "chunkZ": 0, "estimatedScore": 0, ... } ]
}
Fields worth knowing:
chunkEnumeration - how loaded chunks were discovered. chunkmap means the list is
complete. fallback means chunks held open by an exotic mod ticket may be missing; treat
the report as a lower bound.entityCount vs tickingEntityCount - a chunk outside the ticking range still holds
its contents but costs no tick time. A big gap between the two means "lots of stuff, not
currently expensive".scannedChunkCount vs loadedChunkCount - these differ normally, and the gap is not a
bug. loadedChunkCount is Minecraft's own count of chunk holders, which includes chunks
that are only partially generated or sitting on the border of the loaded area.
scannedChunkCount counts the fully loaded chunks LagMap actually inspected - those are
the only ones that hold entities and block entities. A gap only indicates truncation when
scannedChunkCount equals maxChunksPerDimension.sampleDurationMicros - how long LagMap itself took. Watch this; the tool must not
become the lag.config/lagmap.toml, generated on first start. Highlights:
| Key | Default | Notes |
|---|---|---|
sampling.enabledOnStart |
true |
Start sampling automatically. |
sampling.sampleIntervalSeconds |
5 |
Lower is more responsive and more expensive. |
sampling.maxChunksPerDimension |
20000 |
Safety cap per dimension per sample. |
sampling.collectTypeBreakdown |
true |
Turn off on very large servers to cut sample cost. |
sampling.nearbyPlayerRadiusChunks |
8 |
Radius used to attribute players to a hot chunk. |
reporting.topChunksGlobal |
25 |
Hot chunks kept server-wide per snapshot. |
reporting.writeJsonEachSample |
true |
Set false to only write on /lagmap dump. |
reporting.permissionLevel |
2 |
Vanilla permission level required for /lagmap. |
scoring.*Weight |
see file | Weights for the heuristic score. |
Scoring weights default to idleEntity 0.10, idleBlockEntity 0.05, tickingEntity 1.00,
tickingBlockEntity 1.50. Idle contents are not weighted at zero because they still cost
memory, chunk saving and network bandwidth - just not tick time.
LagMap is a lag tool, so it is built not to be the lag:
System.nanoTime() calls into a preallocated ring buffer./lagmap tp is the sole exception, and only for the destination chunk.)sampleDurationMicros in every snapshot tells you exactly what a sample cost.Requires a JDK to run Gradle. Minecraft 1.20.1 itself needs Java 17, which Gradle will download automatically via the foojay toolchain resolver if you do not have it.
./gradlew build
The mod jar lands in build/libs/lagmap-1.20.1-0.1.0.jar.
Gradle 8.14 supports Java 17 through 24. If your default java is newer, point Gradle at a
supported JDK:
JAVA_HOME=/path/to/jdk-21 ./gradlew build
Run a test server with ./gradlew runServer (accept the EULA in run/eula.txt on first
launch).
Version-specific code is deliberately confined:
| Package | Depends on Minecraft? | Notes |
|---|---|---|
com.lagmap.model |
no | Plain records. |
com.lagmap.core |
no | Tick windows, sampling schedule. |
com.lagmap.score |
no | Scoring only. |
com.lagmap.export |
no | JSON + HTML. |
com.lagmap.platform |
no | The SnapshotCollector seam. |
com.lagmap.platform.forge1201 |
yes | The 1.20.1 implementation. |
com.lagmap.command, LagMapMod, LagMapConfig |
yes | Brigadier, Forge events, Forge config. |
A port means writing one new SnapshotCollector, re-wiring the entry point, and adapting the
command/config classes. Everything else moves unchanged.
The one piece of reflection in the mod lives in
platform/forge1201/LoadedChunkAccess.java: ChunkMap#getChunks() is protected and has no
public equivalent. It is resolved once at class-init (Mojang-mapped and SRG names are both
tried) and falls back to a public-API scan if that fails. The sampling path itself performs
no name lookups.
| File | Purpose |
|---|---|
README.md |
What LagMap is and how to use it (this file). |
ROADMAP.md |
Planned work, ordered by value-to-effort. Long-term wishlist. |
MEMORY.md |
Current development state: what is proven, what is untested, what to do next. Updated every session. |
DECISIONS.md |
Why the code is the way it is. Append-only. |
CLAUDE.md |
Repo-specific guidance for AI coding agents. |
MIT.