## 🐛 Bug Fixes
- **Fixed Folia crash: `AsyncCatcher` error on `Chunk getEntities call`** — The `ENTITY_SOUND_EFFECT` packet handler was calling `world.getEntities()` on the Netty IO thread, which Folia blocks. Replaced with a thread-safe entity position cache that requires zero Bukkit API calls on the network thread.
- **Fixed Folia raycast thread-safety violation** — The raycast task was running on the `GlobalRegionScheduler` while accessing world block data across different region threads. Each player's raycast is now scheduled on their owning region thread.
## ⚡ Performance Improvements
- **Early packet-type filtering** — The packet listener now checks the packet type *before* any Bukkit API calls. Previously, `Bukkit.getPlayer()`, `player.getWorld()`, `player.hasPermission()`, and `player.getGameMode()` were executed for **every single outbound packet** (chat, tab list, keep-alive, etc.) — even though we only handle ~10 specific packet types. This eliminates unnecessary overhead for ~90% of all outbound packets.
- **Bypass permission caching** — `player.hasPermission()` and `player.getGameMode()` are no longer called on the Netty IO thread. A thread-safe bypass cache is updated on the main thread during player join, quit, world change, gamemode change, and `/fpac bypass`, so the packet handler only performs a fast map lookup.
- **Cached reflection calls** — All scheduler reflection in `PlatformUtil` (`getMethod`, `invoke`) is now resolved once at class load instead of on every scheduler call. The tile entity reflection field in the chunk handler is also cached statically.
- **Reduced debug logging overhead** — Debug string concatenation in the hot packet path is now properly guarded and only runs when debug mode is actually enabled.
## 📦 Housekeeping
- Bumped config version to `4.2`
Developer information
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle.
maven { name = "Modrinth"; url = "https://api.modrinth.com/maven" }