Skip to content

Battle Debug

Records a detailed timeline of every Pixelmon battle so staff can find out why a battle got stuck on Waiting.... It only watches. It never changes, advances or ends a battle.

What it does

  • Writes one timeline file per battle: battle events, every battle packet in both directions, and regular snapshots of the full server-side battle state.
  • Flags anything that looks wrong, and writes a stuck_waiting_detected record once a battle has been waiting longer than the limit (30 seconds by default).
  • Keeps recording for 30 seconds after a battle ends, to catch problems during teardown.
  • Deletes old files by itself (14 days or 2 GiB by default).
  • Does not record chat.

Runs on the server only. Players install nothing.

How players use it

Players do not interact with this mod.

Staff guide

There are no commands. When a player reports a stuck battle:

  1. Write down the player name and the time (with time zone) it got stuck.
  2. Find the battle in battledebug/player-index/<date>.jsonl by player name or UUID. Dates and times in the files are UTC.
  3. Open the matching file in battledebug/battles/<date>/. File names are <time>_b<index>_<players>.jsonl.
  4. Look for stuck_waiting_detected and the anomaly labels near it, then read backwards to the last packet each side sent.

Do not end the battle on the mod's behalf. The stuck battle is the evidence. End it the normal way (/endbattle) after the timeline has the state you need.

Configuration

config/battledebug.properties. It is created on first boot with every key filled in. Changes need a restart.

Key What it controls
stateSampleTicks How often a full battle snapshot is taken (20 ticks = 1 second)
waitingSampleTicks How often while a battle is waiting
suspiciousWaitingSeconds How long a wait counts as stuck
postEndCaptureSeconds How long to keep recording packets after a battle ends
retentionDays, maxDiskMiB When old files are deleted
maxPacketChars, queueCapacity Size limits that keep output and memory bounded

Invalid values fall back to the default with a warning in the log.

Data and storage

Plain files in the server folder, all UTC:

Path Holds
battledebug/battles/YYYY-MM-DD/ One .jsonl timeline per battle
battledebug/player-index/YYYY-MM-DD.jsonl Which player was in which battle file
battledebug/orphan-packets/YYYY-MM-DD.jsonl Battle packets seen just outside a tracked battle

Every line has a UTC time, epoch milliseconds, a sequence number, the server tick, the session id and the record type. Everything here is safe to delete. The mod also prunes it by itself.

Troubleshooting

Symptom Cause Fix
No files in battledebug/battles/ Mod not loaded, or an old version Check mods/ for battledebug-*.jar. Version 1.0.1 recorded nothing; use 1.0.2 or later
Log flooded with JsonIOException from BattleDebugManager Version 1.0.1 Update
Players cannot join after installing it Version 1.0.0 Update. 1.0.1 and later fix it
A field shows <unreduced:...> That object type could not be turned into JSON Expected. Only that field is lost, not the record

Boot line that shows it loaded:

Battle Debug <version> loaded: writing read-only Pixelmon telemetry under battledebug/

It also creates config/battledebug.properties and the battledebug/ folder.

Limit: this mod sees only the server. It can prove what the server sent, what the client sent back, and what the server's state was. It cannot prove whether the player's client showed or applied a packet. That would need a client-side companion mod and a modpack update.

Maintainer notes

  • The rule for this mod: every call into game state can throw, so all of it goes inside the guard. Both early bugs were a try that did not cover the line that threw.
  • 1.0.0 made main unjoinable. InboundBattlePacketMixin called context.player() outside its try. During login, NeoForge throws there, which killed Pixelmon's FinishSyncDataEntryPacket for every joining player. The server looked up but nobody could join.
  • 1.0.1 recorded nothing. BattleStateSnapshot put live game objects into the JSON map, and Gson tried to reflect into java.util.concurrent.atomic, which is not open. One bad field killed the whole record. 1.0.2 stops leaking raw objects, and SafeJson claims every type Gson would reflect over and writes <unreduced:<class>> <toString> instead.
  • Private fields are read by reflection, guarded. A missing or renamed field is recorded as such.
  • Object graphs are depth- and size-limited to avoid cycles and huge lines.
  • Output goes through one writer thread with a bounded queue, flushed per line, so a crash keeps almost everything.
  • Two mixins hook Pixelmon packet handling: InboundBattlePacketMixin and OutboundBattlePacketMixin.

Building

./gradlew clean build

Output: build/libs/battledebug-<version>.jar. Server mods/ only. Restart to load.

Full command, permission and config tables: Reference.