- Java 93.5%
- Shell 6.5%
| docs | ||
| gradle/wrapper | ||
| scripts | ||
| src/main | ||
| tests | ||
| .gitignore | ||
| build.gradle | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| HANDOFF.md | ||
| LICENSE | ||
| README.md | ||
| settings.gradle | ||
EkaiiEntityCull
Client-side visual culling of stacked entities for Minecraft 26.1.2 (Fabric).
When the same EntityType piles up in a small spatial cell (boats, item entities,
minecarts, mobs), the renderer is told to early-return for everything past the
configured threshold. The server is never touched — this is purely a visual
trick for the client.
Initial use-case: the ekaii creaclone stacked-propulsion machines (500+ boats inside a single block) — render only 10 so your FPS doesn't collapse, while gameplay behaviour stays untouched.
Behaviour
- Per client tick: every entity in
level.entitiesForRendering()is sorted into buckets keyed by(EntityType, spatial cell). Within each bucket, the firstmax_per_bucketentities (by stable entity-id) are kept; the rest are flagged and skipped at render time. - The render mixin (
EntityRenderDispatcher.shouldRenderat HEAD) cancels rendering for non-allowed entities — this hides their model, name tag, bounding box, and any per-entity particle/effect drawing the dispatcher would have done. - Never culled:
Playerinstances, and entities with a custom name tag (toggle viapreserve_named_entities).
Settings UI
Three ways to configure, pick whichever is on your desktop:
-
Mod Menu (recommended): install Mod Menu + Cloth Config, open the mods list (ESC → "Mods" in vanilla, or Mod Menu's icon on the main menu), click the gear icon next to "EkaiiEntityCull" → vanilla-styled settings screen with general toggles and a per-entity-type override tab.
-
Sodium (if installed): the mod auto-registers a tab inside Sodium's "Video Settings" screen, complete with Sodium-style sliders, tooltips, and impact badges. Per-type overrides are reachable via an "External" button from there.
-
Config file + command (headless / mod-pack):
- Edit
~/Library/Application Support/EkaiiLauncher/synced/config/ekaii-entity-cull.json5(orconfig/ekaii-entity-cull.json5in the run dir). /ekaiicull reload | set <key> <value> | (no args)in chat.
- Edit
The mod works without any of those three mods installed. The toggle key (H), the slash commands, and the F3 overlay are always available regardless.
Controls
- H — toggle on/off (rebindable in vanilla Controls menu), shows an actionbar message.
/ekaiicull reload— re-read the config file./ekaiicull set <key> <value>— mutate + persist a config field at runtime. Keys:enabled,max_per_bucket,bucket_size_blocks,preserve_named,force_visibility,cull_use_y_in_key./ekaiicull debugf3— toggle F3 injection event capture (v0.2.4). First invocation arms the ring buffer; second invocation prints a summary- the last 20 events into chat. Use this to verify the duplicate-line fix without restarting the client.
/ekaiicull(no args) — print current state in chat.
Config
~/Library/Application Support/EkaiiLauncher/synced/config/ekaii-entity-cull.json5
Defaults:
// EkaiiEntityCull config — JSON5 (line comments + trailing commas OK).
// Reload at runtime: /ekaiicull reload Toggle: H key (rebind in Controls).
{
"enabled": true,
"maxPerBucket": 10,
"bucketSizeBlocks": 1,
"perTypeOverride": {},
"preserveNamedEntities": true,
"forceVisibility": false,
"cullUseYInKey": false
}
perTypeOverride keys are EntityType registry IDs (e.g. "minecraft:boat": 3).
The path-only form ("boat": 3) also works. Set a value to 0 in the screen
to remove the override (use the global maxPerBucket).
forceVisibility (default false, changed in v0.2.3)
Controls whether the entities we KEEP are FORCED to render, overriding
downstream cullers like tr7zw EntityCulling and moreculling on the same
shouldRender hook.
-
false(default) — we only cull the entities we want hidden; decisions on the rest are left to vanilla logic and any other mod that also hooksshouldRender. Critically, this lets tr7zw EntityCulling's tick-skip optimization apply to our kept entities — that's tr7zw's main perf win (~1% low FPS gain). Recommended for almost everyone. -
true— kept entities are forced visible no matter what. Use this ONLY if you have stacked entities inside an enclosed machine (e.g. boats inside a sealed propulsion box) AND they all turn fully invisible because tr7zw's occlusion ray-trace marks them as hidden. The trade-off is that forcing visibility short-circuits tr7zw before its tick-skip runs, so you lose the 1% low FPS gain.
How to flip if your stacked-machine boats disappear:
/ekaiicull set force_visibility true
Or open Mod Menu → EkaiiEntityCull → gear icon → toggle "Force visibility". Or open Sodium settings → EkaiiEntityCull tab → toggle "Force visibility".
(Upgrading from v0.2.2? Your saved config still has whatever value you had
before. To take the new default, run /ekaiicull set force_visibility false
or delete the line from the JSON file and /ekaiicull reload.)
cullUseYInKey (default false, since v0.2.2)
false = column bucket (XZ only) — vertically-stacked entities cluster
into one bucket regardless of Y. true = cubic bucket (XYZ) — each Y
layer is its own bucket. Column is the right key for the boat-propulsion
use case (the motivating workload).
F3 debug
A single line in the F3 right column shows:
[EntityCull] ON | kept 10 / culled 44 / total 54 N=10 bk=1(col) maxB=54 fv=n
Boat: 10/54
Item: 8/103
kept/culled/total= decision breakdown.N=max_per_bucket.bk=1(col)= bucket size in blocks, mode (col)umn or (xyz) cubic.maxB= biggest single bucket population this tick (diagnoses scatter).fv=Y/n=forceVisibilityon/off.
Below: top-3 types by visual difference (most aggressively culled). The block only appears when F3 is actually visible — it never leaks onto the main menu or regular GUI.
v0.2.4 duplicate-line fix: v0.2.1, v0.2.2, and v0.2.3 all shipped with
a still-visible "duplicate [EntityCull] line" bug. v0.2.3's thread-local
frame gate was reset by each F3 sub-panel's extractRenderState call (MC
26's split-overlay architecture, amplified by Lithium / Sodium / tr7zw on
the same hook), so the gate kept re-arming. v0.2.4 walks the F3 line list
itself before appending and bails out if a line already starts with
[EntityCull] — call-path-independent and bulletproof regardless of how
many times the inject fires per frame.
Debugging the F3 injection in-game: run /ekaiicull debugf3 to start
capturing RESET / EXTRACT-APPEND / EXTRACT-skip events into a ring buffer,
open F3 for a few seconds, then run /ekaiicull debugf3 again to print a
summary + the last 20 events into chat. A healthy session shows
APPEND >= 1 and SKIP(scan) + SKIP(gate) matching whatever extra calls
the F3 pipeline made — no duplicates either way.
Screenshots
Placeholders — to be added once v0.2.0 is in-game tested:
docs/img/modmenu-entry.png— Mod Menu gear buttondocs/img/cloth-screen-general.png— General settings tabdocs/img/cloth-screen-per-type.png— Per-type overridesdocs/img/sodium-tab.png— Sodium settings tabdocs/img/f3-overlay.png— F3 right column with EntityCull stats
Build
./gradlew build
# → build/libs/ekaii-entity-cull-0.2.4.jar
Install
cp build/libs/ekaii-entity-cull-0.2.4.jar \
"$HOME/Library/Application Support/EkaiiLauncher/instances/26.1.2-1/.minecraft/mods/"
Stack
- Minecraft 26.1.2
- Fabric Loader 0.19.2, Fabric API 0.149.1+26.1.2
- Cloth Config 26.1.154, Mod Menu 18.0.0-beta.1, Sodium 0.8.12+mc26.1.2 (all three optional at runtime)
- Cached mojmap (Mojang no longer publishes 26.1.2 mappings — see HANDOFF.md)
- Loom 1.15.5, Gradle 9.4.0, Java 25 (Temurin)
License
MIT. See LICENSE.
Acknowledgements
The settings-UI patterns are inspired by tr7zw's EntityCulling (Mod Menu +
Cloth-like screen + Sodium-coexistence) — see docs/tr7zw-entityculling-analysis.md
for the full pattern study and the "what we borrowed vs. what we didn't"
decision log. None of tr7zw's code is bundled; our implementation is clean-room.