Compare commits

..
44 Commits
Author SHA1 Message Date
logan f949c169a2 feat(perf): add one-step tier setup script for existing players
Default Options only seeds configs when absent, so existing players never
got the tuned settings. build.py --ship now also writes the Deck/Mid/High
tier files to config/onion-perf/<tier>/, and onion-perf-setup.{sh,ps1,bat}
(installed into the instance folder) backs up and replaces the tier's
config files and merges only the tuned video keys into options.txt,
keeping keybinds, audio and vsync/max-FPS choices.

PLAYER-TUNING: point players at the script; bench B4 GC verdict (G1 for
everyone; ZGC/Shenandoah tested worse); 16 GB PCs 5-6 GB heap.
2026-10-18 15:31:20 -04:00
logan cf7ea5617e docs(perf): record owner decisions on Flerovium and spawn tuning 2026-10-18 15:09:41 -04:00
logan 217c346c02 revert(perf): remove Flerovium and the spawn tuning
Run 3 benches (analysis/client-perf/RESULTS-proxy.md): Flerovium's run-2
win did not reproduce (S1 slightly worse, S3 within noise), and the
moderate spawn tuning (d80f85b, 8e8483d) left total mob counts unchanged
across 4 runs per config. Owner approved removing both. The mods now
regenerate their default spawn configs.
2026-10-18 15:06:51 -04:00
logan 4f450e9612 docs(perf): add owner report, run-3 results and player tuning notes 2026-10-18 14:53:23 -04:00
logan c5d25faa7c fix(pack): re-hash spawn config index entries after boot re-save
7841361 refreshed the index while alexsmobs/akashictome configs held their
runtime re-saved contents, so packwiz-installer rejected the committed files
(Hash invalid) and the server could not start.
2026-10-18 14:18:32 -04:00
logan 784136142a feat(perf): ship ModernFix dynamic_resources, add Flerovium and Iris Flywheel Compat
Bench (proxy 9900X/7800XT, see analysis/client-perf/RESULTS-proxy.md):
- ModernFix mixin.perf.dynamic_resources=true: startup -7.6 s, S1 p99
  9.11 -> 8.48 ms, S2 1% low 236 -> 264 fps (B6). Enforced packwiz file
  because ModernFix reads it before Default Options. Missing-texture check
  waived by owner; issues to be reported from play.
- Flerovium 1.1.2 (client): mob-pen S3 p99 ~0.9 ms better (B12b).
- Iris Flywheel Compat 2.4.0 (client): with a shaderpack, Create floor
  p99 13.3 -> 4.0 ms (Flywheel instancing kept under Iris) (B12b).
Deck tier already ships DH's LOW quality preset, so no DH change.
2026-10-18 10:16:45 -04:00
logan a9f33f3a52 docs(bench): add run-2 results (B4h, B7, B12b, B14) and journal 2026-10-18 09:57:30 -04:00
logan 60e10b35a3 fix(bench): reset instance JVM settings for sessions without --jvm
apply_jvm wrote instance.cfg and never restored it, so B5-B12a in run 1
ran on B4's last set (Shenandoah, 6 GB). Within-block verdicts hold
(same JVM on both sides); B7's no-shader baseline is re-run in run 2.

Also: B14 spawn-count automation (b14/: fresh seeded world per run,
offline-located biome sites, datapack entity counts, journaled config
variants) and RESULTS-proxy.md with run-1 verdicts.
2026-10-17 22:28:20 -04:00
logan d20b0fb11a docs(journal): matrix run 1 complete; 06:00 pause notes 2026-10-17 06:06:09 -04:00
logan ccc9695250 feat(bench): add --since filter to analyze; journal session 2026-10-17 01:35:41 -04:00
logan 4fa9a6ab81 feat(bench): automate B7 shader profiles, wire B12b trials, settle 45s
- gen_shader_profiles.py + shader-profiles/: Iris per-pack option files
  resolved from each pack's profile.* lines (Iris has no profile key)
- bench.py: flag rows shader_not_loaded when Iris falls back to vanilla
  rendering (analyze.py treats it as invalid)
- matrix: shader_options session key, trial jar globs pinned to the
  downloaded Modrinth builds, post-join settle 45 s (B0 v1 showed rep 1
  still warming at 10 s)
2026-10-16 23:34:52 -04:00
logan a15c912ad0 feat(bench): add matrix runner, gamescope and per-session controls
- bench.py: gamescope-headless display (default, 1920x1080; ~2% cost vs
  desktop for isolation from the shared Wayland desktop), --shader,
  --dh, --disable-mods/--add-mods, --world-zip, crash-safe restore
  journal, spectator mode in the plan (elevated cameras fell to the
  sea floor and S4 measured underwater darkness)
- matrix.toml/matrix.py: declarative, resumable B0-B14 runner keyed on
  mods hash (warns when B0 must be re-run)
- build.py: bench-only resource-pack variants (C1-fa, C1-fa-ext,
  emf-lod*-fa, C1-bl); every bench variant pins resourcePacks so packs
  don't leak between sessions; shipped tier output unchanged
- analyze.py: tagged labels use their own block's baseline
2026-10-16 22:14:45 -04:00
logan 8f56e2f780 docs(journal): log WATUT fix and harness v2 session 2026-10-15 17:08:29 -04:00
logan e18bd3ccbb feat(bench): add datapack-driven harness v2 with boot checks
xdotool chat injection silently fails under KDE Wayland/Xwayland, so the
v1 harness recorded runs whose scene commands never executed. v2 drives
scenes from a generated world datapack, detects crashes, aligns MangoHud
CSVs by file birth time, flags vsync/backup/low-n windows, and records
full run metadata to JSONL; analyze.py applies the BENCH-MATRIX win rule.
2026-10-15 17:07:52 -04:00
logan a84319bb2f fix(mods): add MidnightLib 1.10.0 to fix startup CME crash
Fright's Delight jar-in-jars MidnightLib 1.9.2, whose MidnightConfig.init
races during NeoForge parallel mod construction (ConcurrentModificationException).
The broken mod state left mod_resources unloaded, so WATUT's reloadShaders
mixin crashed on particle.json - WATUT was the victim, not the cause.
1.10.0 synchronizes the entries map (upstream 1fe69729); FML selects it over
the JiJ copy. Verified: 10/10 clean cold launches to world join.

Also adds analysis/state/ (Phase 2 state capture).
2026-10-15 17:07:52 -04:00
Onion Agent e5e369a693 chore(bench): add client bench harness and Phase 2 handoff
The client now boots cleanly on the bench machine (the CAC spawn-key
crash was fixed in 8e8483d). Add the harness used to drive bench scenes
and capture frametimes, the bench-world generation workflow, and a
self-contained handoff brief. All files live under analysis/ (packwizignored),
so nothing player-facing changes.

- harness/rcon.py: minimal RCON client for the temp worldgen server
- harness/run_scene.py: apply variant -> launch Prism under MangoHud ->
  xdotool scene commands -> p50/p95/p99/1%-low from the CSV
- HANDOFF.md: state, gotchas, next steps
- build.py: disable DH autoUpdater for bench variants (suppress modal)
2026-10-14 23:16:31 -04:00
logan 8e8483d072 fix(config): correct Critters and Companions spawn keys to snake_case
YACL serialises config with FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES
(and serializeNulls), so the camelCase keys biomeSpec, leafInsect, seaBunny,
stagBeetle, rolyPoly and stickBug were never read. Every SpawnEntry then
deserialised with biomeSpec = null, and CAC 2.7.0's SpawnEntry.isTag() calls
biomeSpec.startsWith("#") without a null check, crashing FMLCommonSetup on
both the dedicated server and the client.

Rename the keys; all tuned weights and biome specs are unchanged. Missing
keys still fall back to mod defaults (partial file). Refresh the index and
pack hashes.
2026-10-14 20:55:13 -04:00
logan 611a1a5952 docs: client-perf analysis, bench harness and work journal
Everything the next agent needs to run the Phase 2 optimisation loop on a
machine that can launch the client:

- analysis/client-perf/: LEVERS (ranked levers + corrections), BENCH-MATRIX
  (world, scenes, config IDs, blocks B0-B14 with decision rules),
  PLAYER-TUNING, SPAWN-TUNING, research briefs, the jar scanner and its
  output, observed default configs, and proposed-configs/ (build.py
  generates tiers and bench variants and fails on unknown keys;
  apply_variant.py applies a variant and merges options.txt).
- analysis/client-perf/bench/onion-bench/: bench-world datapack (scene
  teleports, Create grid, mob pen, sound storm, 120 s teleport tour).
- analysis/audit/: the static recipe/dependency audit, including gen_fixes.py,
  which generates the shipped Paxi datapack and was never tracked.
- phase0/: per-machine measurement harness (Steam Deck, 7700K/1060,
  6700K/1080 Ti).
- .pi/journal.md: running work log; read it first.

All of it is packwizignored, so nothing here ships to players.
2026-10-14 18:15:32 -04:00
logan d80f85b3f6 feat(config): moderate spawn tuning for Alex's Mobs, Fowl Play, Critters
Every visible entity multiplies the client render cost (vanilla, EMF/FA,
GeckoLib, ETF, dynamic lights) and the server tick. These cuts target only the
species that are heavy by weight x group size. Rare mobs keep their defaults.

- Alex's Mobs: halve 9 common weights (gazelle and capuchin herds, straddler,
  rocky roller, underminer, elephant, blobfish, mudskipper, caiman).
- Fowl Play: lower duck/goose/gull/sparrow weights and max group sizes
  (upstream #214, ~400 ducks on rivers, is still open in 1.2.3).
- Critters and Companions: about 40% off sea bunnies and the forest/lush bug
  rules. It does have a spawn config; configVersion 2 skips its v1->v2
  migration.

All three are partial files. NeoForge (TOML) and YACL 3.8.2 (JSON5) keep
defaults for missing keys. Shipped enforced from config/ because the server
reads them from this repo. Details and the B14 measurement plan:
analysis/client-perf/SPAWN-TUNING.md.
2026-10-14 18:14:50 -04:00
logan 5919955548 chore(analysis): archive finished reviews, drop stale duplicates
Move the completed 2026-09 candidate review (MODLIST-REVIEW/-GROUPS,
CANDIDATE-APPENDIX, PACK-CONTEXT), FINDINGS.md and the shipped FTB Chunks swap
write-up into analysis/archive/, with a README mapping their old paths. The
review's build pieces, subagent intermediates and Modrinth API dumps are
deleted: the assembled reports carry their content. MODLIST-GROUPS.md was a
byte-for-byte duplicate of analysis/groups-table.md.

Also: drop the now-pointless FINDINGS/MODLIST packwizignore lines (analysis/
covers the archive), and gitignore *.tar.gz so the 650 MB map-migration world
tarball can't be committed by accident.
2026-10-14 18:08:48 -04:00
logan a01bc3b2f1 feat(perf): ship client perf defaults via Default Options, add perf mods
Pack-side half of the client performance work (analysis/client-perf/).

- Add Default Options 21.1.8 (client). It copies config/defaultoptions/
  options.txt and extra/** into place only when the target is absent, so
  player changes are never clobbered. It is also client-only. packwiz
  `preserve` would also land these files in this repo's server config, which
  matters for DistantHorizons.toml [server]/[common] and SSRD's COMMON config.
- Seed provisional mid-tier defaults generated by
  analysis/client-perf/proposed-configs/build.py --ship: render distance 8,
  DH LOD radius 128 / SSAO off / 2 threads, EMF animation LOD 12, Flywheel
  TRI_LINEAR lighting, LambDynamicLights fast, SSRD 16 chunks, Sound Physics
  16 rays / 2 bounces plus per-tick caps on noisy Create sounds. All values
  are unmeasured; Phase 2 (BENCH-MATRIX.md) replaces them. ModernFix
  dynamic_resources is held back until bench block B6 passes.
- Add Sodium Extra 0.9.4, Reese's Sodium Options 2.2.4, BadOptimizations
  2.4.1 and Particle Core 0.3.3 (all client-side). Their declared ranges match
  the pins (Sodium 0.8.13, fzzy_config, Kotlin for Forge).
- Bump Puzzles Lib 21.1.62 -> 21.1.64 as a bugfix: 21.1.63 restores dropped
  access transformers. The event-cache change in 21.1.64 is Fabric-only.
2026-10-14 18:05:45 -04:00
logan aa1d2739f1 feat(mods): add Doggy Talents Next, drop Etched and Grassier Grass
Doggy Talents Next 1.19.1 (NeoForge 1.21.1) overhauls the wolf with a
talent/bond system and companion AI. Better Dogs X DTN 1.2.2 adds its
cosmetic skins; Modrinth only lists a 1.21 NeoForge build, but its
mods.toml declares neoforge >=20.5 and minecraft >=1.18.2, so it loads
on the pack's 21.1.251.

Remove Etched: a second disc/recording pipeline beside music-disc-maker,
duplicating blank discs, boomboxes and albums.

Remove Grassier Grass: its only open risk was shader compatibility with
the shipped Complementary Reimagined.
2026-10-13 18:32:01 -04:00
logan fc649b4ecd fix(pack): add Paxi datapack resolving cross-mod recipe collisions
A static audit of every jar found recipes that shadow each other, so one
output becomes uncraftable depending on load order. Paxi (whose only
dependency, YUNG's API, is already in the pack) loads a global datapack
above mod data to fix them:

- Potato: extradelight, moredelight and rusticdelight all cut a potato with
  a knife. Chain them instead (potato -> rustic slices -> extradelight
  sliced, slices -> moredelight diced) so each keeps its yield.
- Drop rustic potato slices from c:crops/potato (re-cut/stew dupe loop) and
  raw rustic coffee beans from c:crops/coffee/cooked.
- Bread slices, Twilight Forest knives, poisonous potato / rotten tomato /
  apple / cucumber / coffee crates: keep one recipe each.
- Handcrafted sheets shadowed Comforts sleeping bags, and its wood plate
  shadowed every Twilight Forest drying rack: give them distinct patterns.
- Fisherman's Haven worms can had two outputs: keep one, add conversions.
- Pin rustic's beef stew (extradelight disables it: load-order roulette)
  and gate Twilight Delight's Neapolitan-only ice creams.

The datapack is generated by analysis/audit/gen_fixes.py (not shipped).
.packwizignore re-includes it directory by directory because packwiz skips
ignored directories outright.
2026-10-06 23:42:42 -04:00
logan e7d6691466 feat(pack): ship offline usernamecache to clients for Jade
Jade resolves tamed-animal owner names client-side; its Mojang profile
lookup can't resolve offline-mode UUIDs and it falls back to NeoForge's
UsernameCache, which the client loads from <game>/usernamecache.json.
Ship that file (side = "client", installed at the game root) so migrated
owners stop showing as "???".

Maintain the mapping with tools/sync-usernamecache.py after new logins.
2026-10-06 23:28:00 -04:00
logan 0adfb6c656 feat(pack): add Icy's Better Horses and its Modonomicon dependency
Icy's Better Horses declares client_side/server_side as required, so it ships
to both sides. It hard-requires Modonomicon [1.120.4,) on BOTH sides, which the
pack was missing; GeckoLib [4.7.5,) is already present at 4.9.3, and
cloth-config (optional) is already present too.

Pinned to the NeoForge 1.21.1 builds: Modrinth lists two different files both
numbered 2.1.0.1 (Forge/1.20.1 and NeoForge/1.21.1), and resolving by version
number alone silently picks the wrong one.

Also ignore shaderpacks/*.zip and xaero-migration-kit/ in .packwizignore. The
former duplicated the existing .pw.toml metafiles and the latter is local
migration scratch that the file already says must never ship, but packwiz
refresh was indexing both into the pack.
2026-10-06 19:43:10 -04:00
logan e36bff8a4d chore(pack): pin Akashic Tome's whitelist to this pack's guidebooks
Enumerated every guidebook-type item across all 209 mods (30 items) and pinned
them explicitly in config/akashictome-common.toml.

Finding: no config was strictly required. Akashic Tome's "Whitelisted Names"
substring-matches the item's registry PATH (verified from AttachementRecipe
bytecode: getPath + toLowerCase + contains, with getHoverName and
getDescriptionId absent), and the shipped 15-entry default list already covers
every book in this pack. The explicit id list is what keeps the tome working
if that name list is ever trimmed.

All Patchouli books share the single item id patchouli:guide_book and cannot
be listed individually. They still morph under their real owning mod, because
Patchouli's ItemModBook overrides getCreatorModId to return the book's owner.

Also drops the shipped defaults' whitelist ids and mod aliases, which all
reference mods that are not installed.
2026-10-06 19:12:36 -04:00
logan 387ee1ef9b chore(pack): make config files opt-in and ship a PetProtect default
.packwizignore ignored config/ and defaultconfigs/ wholesale. They are now
ignored per-entry (config/*) with deliberately authored files re-included at
the bottom of the file, so one config can ship without any risk of pulling
every runtime-generated config into the index - which matters because this
repo doubles as a server directory.

Ships config/petprotect.json disabling BOTH pet damage and death protection.
PetProtect's isInvulnerableTo mixin gates on prevent_pet_damage alone, so
disabling prevent_pet_death by itself leaves pets invulnerable to nearly all
damage and Pet Cemetery's death -> collar -> respawn loop still never fires.
prevent_pet_attack remains true, so pets are not provoked into fights.

Without this file the two mods cancel each other: PetProtect's defaults keep
pets immortal, so Pet Cemetery is dead weight.

The config installs on clients as well as the server; that is harmless since
petprotect itself is side = "server", but packwiz has no per-file side flag
for non-metafile entries.
2026-10-06 18:27:42 -04:00
logan e371e51856 feat(pack): add gameplay, media and social mods
etched, findme, emojiful, what-are-they-up-to, akashic-tome, chalk-mod,
  mermod, too-many-bows, tarotcards-remastered, comforts, room-for-two,
  playerrevive, charta, minopp, waystones-teleport-pets

findme is side = "both" because its search-across-inventories feature is
server-backed; emojiful's jar self-declares CLIENT despite Modrinth
reporting server_side required, so the server copy is inert.

tarotcards-remastered, room-for-two and too-many-bows reuse identical
version_number strings across the 1.21.1 / 1.21.11 / 26.x builds; packwiz
resolved all three to the correct 1.21.1 filenames, but keep that in mind
when bumping them by hand.
2026-10-06 18:26:49 -04:00
logan b6ebdb850d feat(pack): add Farmer's Delight expansions, decoration and pet mods
Farmer's Delight addons (pack already ran FD 1.21.1-1.3.4 plus 7 Delights):
  extradelight, veggies-delight, fruits-delight, rustic-delight,
  more-delight, miners-delight, dungeons_delight, barbeques-delight-forge,
  frights-delight, overweight-farming, fishermans-haven

Decoration / worldgen:
  portfolio (jar, not the datapack - it ships the client textures),
  items-displayed-forge, sooty-chimneys, mystical-oak-tree

Mobs and pets:
  fowlplay, pet-cemetery, petprotect,
  friends-and-foes-flowery-mooblooms-forge

Deliberately excluded: expanded-delight (beta-only, built against the FD
1.2.4 line), hearth-and-harvest (no per-crop config, so its corn/peanut/
cotton/salt/blueberry duplicates could not be trimmed, and dropping it also
removes two cutting-board collisions).

petprotect is forced to side = "server": Modrinth marks it client_side
unsupported and its mixins cancel pet damage, so client-side installation
risks damage-prediction divergence. packwiz only special-cases client-only
mods, so this needs re-applying if the metafile is ever rewritten by
"packwiz update".
2026-10-06 18:26:30 -04:00
logan 455e398018 feat(pack): add 14 client-side mods and 12 supporting libraries
Client QoL / visual / audio:
  grassier-grass, perception, inventory-particles, fwa,
  advancement-plaques, keybind-atlas, held-item-tooltips, zume, sound,
  extreme_sound_muffler, now-playing, cat_jam, loot-journal,
  travelers-titles

Libraries:
  moonlight, shatterbyte-lib (OctoLib), runiclib, delight-lib,
  lodestonelib, mossylib, puzzles-lib, mru, fragmentum, smartbrainlib,
  blueprint, coroutil

perception and sound carry side = "client", so neither is installed on the
dedicated server.

Two of the libraries are dependencies that Modrinth metadata does not
declare, so packwiz cannot resolve them automatically: perception hard-links
OctoLib and dungeons_delight links runiclib. Adding them up front means the
later stages resolve without a missing-class crash.
2026-10-06 18:25:44 -04:00
logan 86b6620fba feat(pack): add Immersive Paintings
Drag-and-drop custom paintings that work on servers. NeoForge 1.21.1
build 0.7.9; required dependency Fzzy Config is already in the pack.
2026-10-06 14:55:43 -04:00
logan 07ec53727e feat(pack): add Music Disc Maker
Craft playable music discs from YouTube/Spotify/SoundCloud links.
NeoForge 1.21.1 build 3.0.5; no new dependencies.
2026-10-06 14:55:40 -04:00
logan 1108ed2abb Merge pull request 'feat(pack): add Mazes' (#1) from yonlu/onion-mc:add-mazes into main
Reviewed-on: #1
2026-10-04 19:27:58 +00:00
yonlu cb90f36e98 feat(pack): add Mazes
Mazes is a NeoForge 1.21.1 mod that adds a Maze Generator block: place it,
open the GUI, and it builds a flat, configurable maze on the block's own
level. The border is a subtractive cell mask cut out with pencil/rectangle
tools, so it can fill odd shapes and rooms instead of only a rectangle.

The mod is not on Modrinth or CurseForge, so the jar is hosted as a release
asset on yonlu/mazes and referenced with a packwiz url metafile. It targets
NeoForge 21.1.0+, so the pack's 21.1.251 is fine, and side is "both" because
it registers a block entity and network payloads.
2026-10-04 15:23:43 -04:00
logan 2fcb8b492e chore(pack): drop FTB Quests, FTB Teams and FTB XMod Compat
FTB Teams was the only thing holding FTB Quests down -- FTB Quests' 1.21.1
build requires it, and its bytecode references dev/ftb/mods/ftbteams in 14
classes including the event handler, so it breaks at runtime without it. With
quests unused, the whole block goes. XMod Compat only bridged
Quests/Chunks/Ranks, all of which are now gone.

FTB Library stays because FTB Backups 3 builds its config UI on it
(Panel/Theme/Widget/ConfigGroup/ConfigValue/SNBTCompoundTag, 13 classes, and
it does not bundle them despite declaring only neoforge). Architectury API
stays because FTB Library references it in 47 classes.

Verified before removing: FTB Library and FTB Backups 3 reference ftbteams,
ftbquests and ftbxmodcompat in zero classes each, and nothing else in the pack
references them.

CurseForge-sourced mods drop from 9 to 5; pack is 146 mods. OPAC's built-in
party system is now the only one, so primaryPartySystem should be left at its
default rather than pointed at ftbteams.
2026-10-01 01:21:24 -04:00
logan 769af6fa2d chore(tools): add FTB Chunks -> Xaero's map reveal migration tooling
The swap does not carry players' explored terrain across. FTB Chunks stores no
rendered map (its region zips hold chunks.dat plus five data PNGs, one of which
packs height into an image channel), and Xaero's region format is closed and now
version 6 with per-pixel NBT blobs, so the community converters no longer work.
Rather than convert pixels, prune a copy of the world save down to exactly the
chunks a player revealed and let Xaero's own generator map it.

Validated against real data: extracting the 167-region / 98,170-chunk mask
cross-checks clean against FTB's own pixel data, and Anvil pruning round-trips
byte-identically (15.7 MB -> 471 KB on a live region file, slot-to-chunk mapping
confirmed against 1024 chunk NBT headers).

Deferred for now: needs the world save and one singleplayer pass per player.
See analysis/FTB-XAERO-MAP-MIGRATION.md.
2026-09-30 22:04:56 -04:00
logan 1d02bf1cf6 feat(pack): replace FTB Chunks with Open Parties and Claims and Xaero's maps
FTB Chunks is the only mod in the FTB suite that nothing else depends on --
FTB Quests requires FTB Library and FTB Teams only, and ships no claim-chunk
task type -- so it can leave without touching quests. Open Parties and Claims
takes over claims and force-loading, and the Xaero's suite restores the
minimap, world map and waypoints that b0542f9 removed.

The three Xaero's entries are byte-identical to the ones b0542f9 deleted, so
this reverts the map half of that commit rather than integrating anything new.
Xaero's + OPAC is also the pairing both projects document: the world map
displays OPAC claims and lets you claim and forceload from the map screen.

Drops config/ftbchunks-client.snbt and the .packwizignore negation that
shipped it, since the FTB Chunks minimap it enabled no longer exists. Also
keeps authoring/review material and migration scratch out of the pack index.

Server-side follow-up (not shippable, lives in the world save): set
maxPlayerClaimForceloads to 25, point primaryPartySystem at FTB Teams, and
decide claims.forceload.offlineForceload.
2026-09-30 22:04:56 -04:00
logan 7e998fa39f feat(pack): add Quick Skin
Lets players change their skin in-game without leaving the server.
2026-09-29 20:38:45 -04:00
logan b7a7d5add3 chore(pack): drop Aeronautics Iris Fix
It is distributed via CurseForge metadata only, which packwiz cannot
fetch automatically and forces a manual download. Remove it so the pack
stays fully automatic; Iris Veil Compat retains the Create Aeronautics
visual fix for Veil under shaders.
2026-09-29 13:12:54 -04:00
logan c6eb2e3cd5 chore(pack): stop tracking C2ME runtime cache
cache/c2me-dfc is regenerated on every server start (the .class files
even swap contents between runs) and is already excluded from the pack
by .packwizignore. Drop it from git and add a .gitignore so it cannot
be committed again.
2026-09-29 13:11:35 -04:00
logan eeaca31162 feat(pack): make Distant Horizons mandatory for all clients
DH was optional (default on), which still lets launchers/exporters skip
it and left the server's LOD data unused. Drop the [option] block so it
is required and installed for every client.
2026-09-29 13:05:31 -04:00
logan b7fde64573 Merge branch 'mod-pack-additions' into main
Integrate Iris + shader packs, better-leaves bump, and fresh-animations
extensions alongside the server-side Clumps/C2ME/Noisium work on main.
index.toml and pack.toml regenerated with packwiz refresh.
2026-09-29 13:05:19 -04:00
logan c1fefd797e chore(pack): bump Motschen's Better Leaves to 9.6 2026-09-29 10:33:38 -04:00
logan b75ceb14c5 feat(pack): add Iris, shader packs, and new resource packs
- Add Iris Shaders 1.8.14-beta.1 (NeoForge) plus Iris Veil Compat and
  Aeronautics Iris Fix so Veil/Create Aeronautics visuals render
  correctly under shader packs.
- Add Fresh Animations: Extensions and Motschen's Better Leaves 8.0
  resource packs.
- Ship Complementary Reimagined and MakeUp - Ultra Fast shader packs
  to clients via shaderpacks/.
2026-09-29 03:11:47 -04:00
343 changed files with 46520 additions and 13884 deletions

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
# C2ME runtime worldgen class cache (regenerated at runtime; see .packwizignore)
cache/
# Immersive Paintings player-upload cache (regenerated at runtime; see .packwizignore)
immersive_paintings_cache/
# Large or regenerable local artifacts
*.tar.gz
analysis/client-perf/proposed-configs/out/
__pycache__/
+51 -4
View File
@@ -11,13 +11,18 @@ run.sh
*.jar
*.jar.log
run.bat
# Server/runtime config is never tracked; only explicit modpack-default configs
# (e.g. the FTB Chunks client config) are shipped.
# Server/runtime config is opt-in. Everything under these directories is
# ignored; deliberately authored defaults are re-included at the bottom of
# this file. This repo doubles as a server directory, so a blanket un-ignore
# here would pull every runtime-generated config into the index.
config/*
!config/ftbchunks-client.snbt
defaultconfigs/*
kubejs/
defaultconfigs/
local/
# Authoring/review material - belongs in the repo, never in the pack
analysis/
phase0/
ftbbackups3/
patchouli_books/
.sable/
@@ -26,8 +31,50 @@ patchouli_books/
*.pid
.pi/
resourcepacks/*.zip
shaderpacks/*.zip
start.sh
tools/
# C2ME runtime worldgen cache (regenerated at runtime)
cache/
# Immersive Paintings player-upload cache (regenerated at runtime)
immersive_paintings_cache/
# Scratch artifacts from the map-migration tooling - never ship these
sp-world/
xaero-migration-kit/
*.tar.gz
*.tgz
# --- Shipped config defaults ----------------------------------------------
# These must come AFTER every ignore above (last matching pattern wins),
# including the blanket "*.json" in the authoring section. Re-include only
# files we deliberately author and want enforced on install.
!config/petprotect.json
!config/akashictome-common.toml
# Paxi global datapack with cross-mod recipe/tag fixes (generated by
# analysis/audit/gen_fixes.py). Paxi's runtime load-order files stay ignored.
!config/paxi
config/paxi/*
!config/paxi/datapacks
config/paxi/datapacks/*
!config/paxi/datapacks/onion-fixes
!config/paxi/datapacks/onion-fixes/**
# Client perf defaults (analysis/client-perf/proposed-configs/build.py --ship).
# Default Options copies them into place client-side, only when the target is absent.
!config/defaultoptions
!config/defaultoptions/**
# Spawn tuning reverted 2026-10-18: bench B14 showed no measurable load reduction.
# ModernFix mixin overrides (build.py SHIP; enforced - ModernFix reads it before
# Default Options runs). dynamic_resources=true per bench B6.
!config/modernfix-mixins.properties
# Player tier overrides (build.py --ship) plus the per-machine setup script that
# applies a tier to an existing install (see PLAYER-TUNING.md §1). Must come after
# the blanket config/* and *.json patterns above (last matching pattern wins), and
# the directory itself must be re-included before anything under it.
!config/onion-perf
!config/onion-perf/**
!onion-perf-setup.sh
!onion-perf-setup.ps1
!onion-perf-setup.bat
+601
View File
@@ -0,0 +1,601 @@
# Onion MC — work journal
## Session 2026-10-06 — 60-mod expansion, integration review
### What this is
**Onion MC**, a curated Minecraft modpack managed with **packwiz**.
- Minecraft **1.21.1**, NeoForge **21.1.251**
- **209 mods** (was 149 four commits ago)
- packwiz lives at `~/go/bin/packwiz` — **not on PATH**
- Remote: `ssh://git.ljones.me:2222/logan/onion-mc.git`, branch `main`
- Commit style: Conventional Commits, imperative subject, body explains *why*
- The repo **doubles as a server directory** (`.packwizignore` excludes `libraries/`,
`world*/`, `logs/`, `crash-reports/`, `*.jar`, `run.sh`). Never blanket-un-ignore
anything there — you will index runtime junk.
### Environment (checked 2026-10-06)
- This machine is **NixOS**: no `java` on PATH; use `nix shell nixpkgs#jdk21` for
anything server-side. No server is installed in the repo yet (no `libraries/`,
`run.sh`, or NeoForge jar).
- Client = Prism instance `~/.local/share/PrismLauncher/instances/Onion MC/`. Its
pre-launch hook runs `packwiz-installer-bootstrap` against
`https://git.ljones.me/logan/onion-mc/raw/branch/main/pack.toml`, so launching it
pulls the pushed pack. Last launch (`logs/latest.log`) is **2026-09-24**, i.e.
before all five commits below — confirms the expansion has never been booted.
- `.pi/` is in `.packwizignore`, so this journal never ships.
### State
Five commits, all pushed to `main`, HEAD = `e36bff8`:
| Commit | Contents |
| --- | --- |
| `455e398` | 14 client-side mods + 12 libraries |
| `b6ebdb8` | 11 Farmer's Delight addons + 4 decoration + 4 pet/mob |
| `e371e51` | 15 gameplay/media/social |
| `387ee1e` | config policy change + PetProtect default |
| `e36bff8` | Akashic Tome whitelist config |
Verified after each stage: mod set matches intent exactly (nothing auto-added by
packwiz, nothing missing, nothing removed), `index.toml` hash matches `pack.toml`.
**Two configs ship** (new capability — see policy in gotchas):
- `config/petprotect.json`
- `config/akashictome-common.toml`
### Goal
A **working, well-integrated 209-mod pack**:
1. **Boots clean** on client and dedicated server — no mixin failures, no missing deps.
2. **No duplicate systems** — the pack has a history of removing them (FTB Chunks →
Open Parties and Claims; FTB Quests/Teams/XMod Compat deleted). New mods must not
reintroduce overlapping mechanics without a reason.
3. **No broken recipe chains** — every consumed item stays obtainable.
4. **Performance held** — Sodium, Iris, ImmediatelyFast, EntityCulling, ModernFix,
FerriteCore, Distant Horizons mandatory, C2ME/Noisium server-side, Dynamic FPS.
5. **Tested, not assumed** — nothing has been booted yet.
### Decisions made — do not relitigate without cause
**Excluded candidates:**
- `sable_beyond` — every 1.21.1 build is alpha; wide Create/Aeronautics sublevel-mass
mixin surface. Owner's call.
- `expanded-delight` — beta-only, built against FD 1.2.4 (pack runs 1.3.4); sweet
potato / cranberry / peanut / chili / cinnamon / salt duplicate kept mods.
- `hearth-and-harvest` — no per-crop config, so corn/peanut/cotton/salt/blueberry
dupes couldn't be trimmed. Dropping it also removed two cutting-board collisions
(wheat, sunflower).
- `display-delight` — zero risk but ~250 plate items incl. dishes from Delight mods
we don't run. Could be added any time.
**Rustic Delight dedupe declined.** Its flags are whole content-family switches:
- `enable_cotton` — only dupe partner was hearth-and-harvest (not added).
- `enable_coffee` — real dupe vs extradelight, but cascades into coffee-braised beef,
Haste potions, cherry-blossom/syrup chains.
- `squids_drop_calamari` — also strips calamari from creative tab and villager
trades; `enable_fried_foods` depends on it.
Remaining "duplicates" are tag-merged (`c:crops/coffee_beans` etc.) and benign.
**Kept after being flagged:**
- Grassier Grass — reported shader breakage is Rethinking Voxels-specific; issue
lists Complementary Reimagined (what we ship) as working. BSL bug is separate.
- Perception — OctoLib/ShatterLib is a maintained zero-dep lib; trails are particles,
shake is camera; jar has no shader/post-process assets.
### Open questions for the owner
1. **Potato cutting-board collision → needs a datapack loader.** Only real
unresolved defect. `c:crops/potato` + knife matches both
`extradelight:cutting/sliced_potato` (4×) and `moredelight:cutting/diced_potatoes`
(1×). Neither output is obtainable otherwise; they feed 4 and ~10 recipes. The
losing chain breaks permanently. Fix: rewrite both recipes to emit the combined
output via a datapack loader reading from `config/` (**Paxi** or **Global Packs**)
— a new mod, awaiting approval.
2. **Keep PetProtect?** With both protection flags off it only stops pets being
provoked into fights. Dropping it + config restores vanilla retaliation; Pet
Cemetery works alone.
3. **Keep `mystical-oak-tree`?** Costs Moonlight (2.3 MB) for a 1-in-280-chunk
feature — near-zero value on a pre-generated map. Offered to exclude.
4. **Rustic Delight coffee flag** — one line, if the second coffee should go.
5. **`display-delight`** — add later, or not.
### Known and potential conflicts
| Area | State |
| --- | --- |
| **PetProtect ↔ Pet Cemetery** | Resolved *only* by the shipped config. `isInvulnerableTo` gates on `prevent_pet_damage` alone, so disabling `prevent_pet_death` alone leaves pets immortal. Both flags `false`. Delete the config and Pet Cemetery is dead weight. |
| **extradelight recipe overrides** | 169 files under `data/farmersdelight/recipe/`, ~25 path-colliding with FD's own. Live, ungated. FD recipe book changes noticeably. |
| **Twilight Forest stripping (16 pairs)** | `farmers-cutting-twilight-forest` and `twilight-delight` both cut TF logs into `stripped_X` + `tree_bark`. Identical outputs — harmless. Pre-existing. |
| **Curios slot merging** | `artifacts`, `too-many-bows`, `tarotcards-remastered`, `mermod` define player slots, no `replace`. Should merge — **verify in game once**. |
| **Sophisticated Core disc handlers** | Verified clean: first-supports-wins, predicates disjoint. |
| **`dungeons_delight`** | **29 vanilla mixins** incl. `BlockBehaviourMixin`/`LevelRendererMixin`. Broadest surface added. Untested vs ModernFix/Sodium. |
| **`fowlplay` spawning** | ~13 bird species via `NaturalSpawnerMixin` — spawns in already-generated chunks. Watch tick time. |
| **`fishermans-haven`** | Lighthouse spacing 9/separation 6 (dense); several biome modifiers use **bare un-namespaced biome IDs** (may log errors / silently not spawn). Salt lakes in every biome via `neoforge:any`. |
| **`extradelight` legacy `forge/` biome modifiers** | Inert on NeoForge (working `neoforge/` copies exist). Harmless. |
| **`etched` ↔ `music-disc-maker`** | No code conflict (mixin points re-verified), but duplicate content: two blank discs, two boomboxes, two 9-disc albums, two recording pipelines. Design decision. |
| **Tag merges** | Several mods write the same `c:` crop tags (3 garlics, 3 peanuts, 3 salts, 2 corns…). Benign. **Bell pepper and citrus NOT merged** (`crops/bellpepper` vs `crops/bell_pepper`, `crops/lemon` vs `foods/fruits/lemon`). |
### Not verified — nothing has been booted
All findings are static analysis (jars, bytecode, data files, Modrinth API). Untested:
- Whether the pack **boots at all**, client *and* server.
- `dungeons_delight`'s 29 mixins vs the perf stack.
- Grassier Grass vs **Complementary Reimagined** and **MakeUp Ultra Fast** (MakeUp
untested by anyone).
- Whether Terralith biomes carry `#c:is_*` / `#minecraft:is_overworld` tags that
wild-crop/tree biome modifiers key off (generation density only).
- Whether `fishermans-haven`'s bare-ID biome modifiers parse.
- Whether `extradelight`'s inline-feature `neoforge:` biome modifiers place.
- Whether `extradelight`'s cookbook opens (ships `patchouli_books/cookbook2/` dirs but
**no `book.json`**). The item itself is real.
- `room-for-two` (beta; compat untested by author) and `petprotect` (beta).
- PetProtect edge case: logging out while downed may lose items (upstream #184,
1.19.2, never fixed).
- Curios slot merge rendering all four mods' slots.
### Method notes and gotchas
- **packwiz `side` quirk**: only client-only mods are special-cased; mods with
`client_side: unsupported` still get `side = "both"`. `petprotect` manually set to
`side = "server"` (as `c2me-neoforge` and `noisium` already were).
**`packwiz update` rewrites metafiles and resets these.**
- **Config policy is opt-in.** `.packwizignore` ignores `config/*` and
`defaultconfigs/*`; shipping a file needs `!config/<file>` at the **bottom** (last
match wins), after the blanket `*.json` rule.
- **Duplicate `version_number` traps**: `tarotcards-remastered`, `room-for-two`,
`too-many-bows` reuse version strings across 1.21.1 / 1.21.11 / 26.x. Pin exact
filenames by hand.
- **Undeclared deps on Modrinth**: `perception` → `shatterbyte-lib` (OctoLib),
`dungeons_delight` → `runiclib`. `mystical-oak-tree` → `moonlight` is declared in
the jar but not the API.
- **cdn.modrinth.com returns HTTP 501 to Range requests** — download whole jars.
`unzip` is not installed; use `python3 -c "import zipfile..."`.
- **`lodestonelib`** is the real slug; `lodestone` has no 1.21.1 build.
- `FINDINGS.md`, `MODLIST-GROUPS.md`, `MODLIST-REVIEW.md` are untracked,
packwizignored, and **~60 mods stale**. They hold the FTB-swap reasoning, OPAC
config decisions and the original per-mod compat audit — refresh, don't re-derive.
### Next steps, in order
1. **Boot the pack.** Client first (launch the Prism instance — it self-updates from
the pushed `pack.toml`), then the dedicated server.
2. Check logs for: missing deps, mixin apply failures, `fishermans-haven`
biome-modifier parse errors, anything referencing `dev/ftb/mods/ftbteams`
(soft-dep risk from the FTB removal).
3. Smoke-test: Grassier Grass + Complementary Reimagined, `dungeons_delight` mixins,
curios slots, Pet Cemetery death→collar loop.
4. Watch tick time and entity counts (fowlplay, fishermans-haven, mooblooms).
5. Bring open questions to the owner, potato collision first.
## Static audit 2026-10-06 (all 209 jars + vanilla + NeoForge data)
Scripts/output: `analysis/audit/` (jars cached in `/tmp/onion/jars`, ephemeral).
Deps clean (no missing/out-of-range/incompatible; lambdynlights_runtime is a
false positive — nested under META-INF/jars). No item-tag load failures.
Corrections to the handoff:
- Potato board collision is **3-way**: + `rusticdelight:potato_slices` (81
consuming recipes, board-only source). Vanilla `getRecipesFor` sorts by result
translation key, so `extradelight:sliced_potato` likely wins; rustic + moredelight
lose. moredelight diced potatoes/bread slices still reachable via Create saw.
- New: bread + knife → extradelight vs moredelight bread slice.
- rusticdelight tags `potato_slices` as `c:crops/potato` (re-cut/dupe loop);
veggiesdelight tags sweet potato there too. rustic coffee beans sit in both
`c:crops/coffee` and `c:crops/coffee/cooked`.
- extradelight *disables* 14 FD recipes (relocated to oven/mixing bowl/feast/
chiller — all still obtainable); FD beef_stew is rustic-live vs
extradelight-disabled → load order decides.
Crafting-grid collisions (one output uncraftable): dungeonsdelight vs
twilightdelight TF knives (×4); dungeonsdelight vs frightsdelight crates (×2);
extradelight vs fruitsdelight apple crate, vs hamsterpets cucumber crate; coffee
bag/sack/crate; comforts sleeping bag (vertical) vs handcrafted sheet; fishermans
worms can → own worm vs aquaculture worm. Pre-existing: handcrafted wood plate vs
17 TF drying racks; create vs TF copper nugget (tag-unified, harmless).
## Fix pass 2026-10-06 — pushed as `fc649b4` (on top of `e7d6691`)
- Added **Paxi** 5.1.3 (only dep YUNG's API, already present; injects packs at TOP).
- Datapack `config/paxi/datapacks/onion-fixes/` (43 files), generated by
`analysis/audit/gen_fixes.py` — edit the script, not the JSON. Shipped via a
dir-by-dir re-include chain at the bottom of `.packwizignore` (packwiz's
matcher needs negations *without* trailing slashes or it SkipDirs the parent).
- Re-audit with the pack on top: all real collisions gone. Remaining flags are
benign (dough shaping UI, mixing-bowl fluid variants, Aquaculture Delight
neptunium tiering, Create/TF copper nugget).
- Correction: rustic potato_slices has 3 direct consumers, not 81 (81 was via
the polluted `c:crops/potato` tag).
- `c:crops/potato` and `c:crops/coffee/cooked` now use `replace: true` — any
future mod adding to them must be added to gen_fixes.py.
- Orphaned on purpose (uncraftable, still uncraftable-from): dungeonsdelight TF
knives + crates, hamster cucumber crate, fruitsdelight apple crate. Consider
hiding them in JEI.
- Needs in-game check: `/datapack list` shows onion-fixes (pink), recipes in JEI.
- Rebased over upstream `0adfb6c` (Icy's Better Horses + Modonomicon, 212 mods);
re-audit clean. Note: Better Horses' modId is `icys_better_horses` but it
registers under `icys-better-horses` — the audit's absent-mod check flags it
falsely.
## Session 2026-10-14 — client performance analysis, Phase 1 (static baseline)
HEAD `aa1d273` (212 mods), unchanged. **Nothing committed, nothing changed in the
pack.** All output is in `analysis/client-perf/` (untracked; `analysis/` is
packwizignored).
### What exists now
- `analysis/client-perf/LEVERS.md` — **the deliverable**: ranked levers (Tier 1–3),
meta-levers, add/remove candidates, Phase 2 test order, corrections to the
subagent research. Read this first.
- `analysis/client-perf/README.md` — index and method.
- `scripts/jarscan.py` + `scripts/report.py` → `data/jarscan.json`,
`data/jarscan-report.md`. Parses class-file annotations for real `@Mixin` targets
and injector kinds, per-frame/tick event refs, atlas px (one frame per animated
texture), models, shaders, sounds and entities. It skips the Fabric-only nested
jars of multi-loader jars (DH, LambDynamicLights). Runs in ~35 s over
`/tmp/onion/jars` (ephemeral; refetch with `analysis/audit/fetch.py`; resource
and shader packs were also dropped there).
- `data/observed-default-configs/`: generated configs copied from the Prism
instance's last launch (2026-09-24, the older 110-mod pack). They are the
real-default reference (DH, Sodium, EMF, ModernFix, Flywheel, Sable, SPR…).
- `research/r1..r9-*.md`: nine subagent briefs with confidence tags and URLs.
### Key facts established (verified)
- 951 per-frame mixin classes (121 Overwrite/Redirect); top: Sable 127, Iris 114,
Controlify 84 (GUI), Sodium 48, EMF 42.
- Handcrafted + Chipped + Rechiseled = 58% of the block atlas and 62% of the 69k
models.
- Flywheel 1.0.x disables both GPU backends when *any* shaderpack is active
(checked in `Backends.java`). The pack has no Iris Flywheel Compat.
- DH defaults: LOD radius 256, SSAO and AA on, 2 worker threads. EMF
`animationFrameSkipDuringIrisShadowPass` is already true. ModernFix
`dynamic_resources` is off by default. Flywheel `lightSmoothness = SMOOTH`.
- Sable renders each sub-level by re-driving Sodium's chunk renderer.
`sub_level_skylight_shadows=false` by default; Iris #3164 (sub-level corruption
with shadows) is open.
- Chipped requires Athena and Rechiseled requires Fusion, so neither CTM engine is
removable. Lodestone is required *only* by Miner's Delight.
- Puzzles Lib 21.1.64 has an event-dispatch caching fix; the pack pins 21.1.62.
- Shaders and resource packs are opt-in in practice (the last launch had none
enabled). The pack ships no perf config, and JVM/heap is per-player Prism (old
instance: 8 GB, stock G1).
### Decisions pending (owner)
1. Delivery mechanism for tuned defaults: **Default Options** mod (Balm is already
present) vs packwiz `preserve = true` (semantics with `packwiz refresh`
unverified).
2. Shader policy: default off / opt-in per machine?
3. Server-side spawn tuning (Alex's Mobs #62, Fowlplay #214, Critters &
Companions datapack).
4. Removal candidates: Miner's Delight (drops Lodestone), Fowlplay, Charta, WATUT,
Emojiful.
5. Add candidates: Default Options, Sodium Extra + Reese's Sodium Options,
Particle Core, BadOptimizations; trials: Gnetum, Better Block Entities,
Flerovium, Super Resolution (Deck), Ixeris (P1), Iris Flywheel Compat.
### Next steps
- Phase 2 = benchmark per `LEVERS.md` §6 using the `phase0/` harness (P0 Deck, P1
7700K/1060, P2 6700K/1080 Ti with 8 GB RAM free). Capture the atlas size, heap and
startup time at baseline.
- Before benchmarking: the pack has never booted at this mod count (see the
2026-10-06 session), so do a boot/log check first.
- Free change regardless of benchmarks: bump Puzzles Lib to 21.1.64.
## Session 2026-10-14 (b) — client perf Phase 1b: delivery, staged configs, bench matrix
HEAD `aa1d273`, unchanged. **Nothing committed; zero tracked-file changes; no mods
added/removed.** All output is under `analysis/client-perf/` (untracked, packwizignored).
### Produced
- `proposed-configs/` — `build.py` generates player tiers (`out/T-default|deck|mid|high`)
and 32 bench variants (`out/C0`, `C1`, `dh-*`, `fw-*`, …) from same-version observed
defaults or source-verified templates. **The build fails if an override names a key
that isn't in the base**, so key confirmation is enforced. README §1 = M1 decision,
§2 = tier table, §3 = key provenance.
- `BENCH-MATRIX.md` — replaces LEVERS §6: bench world recipe, scenes S1–S6, JVM sets J0–J3,
blocks B0–B13 with decision rules and budget (~25 h machine time).
- `bench/onion-bench/` — datapack (pack_format 48): scene teleports, `/fill` Create grid
(2,304 kinetic BEs), mob pen, a 24-sounds/tick storm, and a 120 s teleport tour.
**Never run in game**: syntax unverified, and the S2 `/fill`-placed kinetic network may
need motors re-placed by hand (checklist in matrix §2).
- `PLAYER-TUNING.md` — tiers, heap/GC, shader and RP guidance, Deck env vars.
- `proposed-changes/puzzles-lib-21.1.64.patch` — `git apply --check` passes on HEAD.
It touches only `puzzles-lib.pw.toml` + index/pack hashes. A single-mod
`packwiz update puzzles-lib` does **not** reset the side fields on petprotect, c2me or
noisium; only those three are at risk, from bulk updates. Its only dependent is
HeldItemTooltips, range `[21.1.1,)`.
### Verified this session (don't re-derive)
- **packwiz `preserve`** (packwiz `ef87d96` = the installed binary; installer v0.5.14 =
the Prism jar): settable only by hand-editing `index.toml`. It survives refresh, content
change and unrelated `update` (tested). It is **silently dropped** if a refresh runs
while the file is absent or ignored. The installer writes the file only if it's absent.
A skipped file re-logs "invalidated" every launch, and removing a file the installer
wrote deletes the player's copy. Index files have no `side`, so they also land on the
server (this repo *is* the server dir).
- **Default Options 21.1.8**: copy-if-absent for `config/defaultoptions/options.txt`
(`key_` lines stripped) and `config/defaultoptions/extra/**`. Runs at `GameConfig.<init>`:
before NeoForge constructs mods (`ClientModLoader.begin` is inside `Minecraft.<init>`),
but after mixin bootstrap, so too late for `modernfix-mixins.properties`. Balm
`[21.0.39,)`; the pack has 21.0.66.
- Observed defaults come from the **same jar versions as the current pins** (DH 3.3.2,
Sodium 0.8.13, EMF 3.3.9, Create 6.0.10 / Flywheel 1.0.6, Sable 2.0.5, SPR 1.5.1).
All 172 DH values match `Config.java` @ 3.3.2, except DH `numberOfThreads`: the true
default is ceil(nproc/2) = **4** on the fleet (the observed 2 came from this 4-thread
box). `serverId` is per-install random and is stripped from shipped files.
- SSRD 1.8.7 config = `config/ssrd-common.toml` (COMMON; the server reads it too).
`physicsRenderDistance` defaults to 128, slider floor 16.
- LambDynamicLights 4.8.11 = `config/lambdynlights.toml`; `mode` is lowercase
off/fastest/fast/fancy; `[light_sources] entities`.
- SPR merges a partial `sound_rates.properties` over its generated defaults. Sounds over
the cap play without physics; they are not dropped.
- **Puzzles Lib 21.1.64 has no perf change on NeoForge**: `FabricEventFactory` isn't in the
NeoForge jar. The 62→64 diff is access transformers plus one class. LEVERS #14 is
corrected.
- r6's `-XX:ShenandoahGCMode=iu` aborts JDK 21 without UnlockExperimentalVMOptions
(tested). GenZGC and the brucethemoose G1 set parse cleanly.
### Owner decisions pending (presented 2026-10-14)
1. Approve **Default Options** as the delivery mechanism, plus enforced
`modernfix-mixins.properties`. This is a mod add plus 3 `.packwizignore` lines (drafted in
proposed-configs README §1).
2. Apply the Puzzles Lib patch (bugfix only).
3. Shader policy (B7 produces the data).
4. Server spawn tuning (Alex's Mobs / Fowlplay / Critters & Companions).
5. Removal candidates (Miner's Delight → Lodestone, Fowlplay, Charta, WATUT, Emojiful).
6. Add candidates (Sodium Extra + RSO, Particle Core, BadOptimizations; trials). These gate
B12.
### Next steps
1. Boot check (matrix §0). Nothing else can proceed until the pack boots.
2. Build the bench world on P2 (matrix §2); fix any datapack parse errors on `/reload`.
3. Run B0 → B13; write the winners into `build.py`; regenerate; then ship via the approved
mechanism. Existing players need the file-reset step in PLAYER-TUNING §1.
## Session 2026-10-14 (c) — owner decisions applied, pushed for the client machine
The owner answered the pending list; this session acted on it, committed, and pushed.
**The next agent works on a machine that can run the client.** This machine is NixOS with
no `java`, no client and no `unzip`. Those notes are machine-specific; check what the new
machine has.
### Owner decisions (2026-10-14), now settled
1. Delivery = **Default Options** (+ enforced `modernfix-mixins.properties` after B6).
Done.
2. Puzzles Lib → 21.1.64. Done (bugfix only).
3. Shaders: **opt-in** (already the case: packwiz downloads them, nothing enables them).
4. Spawn tuning allowed if not detrimental to gameplay. Done, moderate (below).
5. **Remove no mods.** LEVERS §5 is reference only.
6. Add any helpful mods. Added the five "Add" verdicts. Trials are left to bench B12b, and
the agent may add trial winners.
### Commits (all pushed to `main`; Prism instances self-update from it)
- `a01bc3b` feat(perf): Default Options + `config/defaultoptions/` (8 provisional
mid-tier files from `build.py --ship`); Sodium Extra 0.9.4, Reese's Sodium Options 2.2.4,
BadOptimizations 2.4.1, Particle Core 0.3.3 (all `side = "client"`; deps already present);
Puzzles Lib 21.1.64. **217 mods.**
- `5919955` chore: stale reviews archived to `analysis/archive/` (README maps old paths);
intermediates and API dumps deleted; `*.tar.gz` gitignored.
- `d80f85b` feat(config): spawn tuning, enforced partial files
`config/alexsmobs-common.toml`, `config/fowlplay.json5`,
`config/crittersandcompanions-common.json5`. See `analysis/client-perf/SPAWN-TUNING.md`.
- docs commit: `analysis/client-perf/` (minus the generated `out/`), `analysis/audit/`
(incl. `gen_fixes.py`, which builds the shipped Paxi datapack and was never tracked
before), `phase0/`, and this journal.
### Rules of the road for the iteration
- Change tuned defaults **only** through `analysis/client-perf/proposed-configs/build.py`
(`DEFAULT` / `DECK` / `HIGH`), then `build.py --ship`, then `~/go/bin/packwiz refresh`
(wherever packwiz lives on that machine), then commit.
- Shipped defaults are copy-if-absent. Changing them reaches **fresh installs only**;
existing players use PLAYER-TUNING §1.
- `modernfix-mixins.properties` ships only after B6 passes. Instructions are in the `SHIP`
comment in `build.py`.
- Any new mod: verify its NeoForge 1.21.1 build and its `neoforge.mods.toml` ranges against
the pins, check `side`, rerun B0. **More Culling is unusable**: its NeoForge build
declares `minecraft [1.21,1.21.1)`.
- `packwiz update` (bulk) resets the hand-set `side = "server"` on petprotect, c2me-neoforge
and noisium. A single-mod update is fine.
- On first boot, the mods re-save the partial spawn files in full. Don't commit those
expansions by accident (`git diff config/`).
- `sp-world.tar.gz` (650 MB, untracked, gitignored) is a world save from the map-migration
work. Leave it alone.
### Next steps
1. BENCH-MATRIX §0 boot check (now covers Default Options seeding, the RSO/SSRD video
screen, YACL spawn-config loading, and the Iris↔Sodium report).
2. Build the bench world (§2), then run B0 → B14. Write the winners into `build.py`, ship,
refresh, commit.
3. Report shader-policy data (B7) and any trial-mod verdicts (B12b) to the owner.
## Session 2026-10-15 — Phase 2 begins: client boots, bench harness built
Machine: **Manjaro**, Java 21 (OpenJDK), Prism 11.0.3, RX 7800 XT / Ryzen 9 9900X / 91 GB.
Unlike the previous machine this one runs the client. Full brief: `analysis/client-perf/HANDOFF.md`.
### Boot check (client) — essentially PASS
Pack reaches the main menu cleanly (ModernFix "Game took 29.3 s"; atlas 4096x4096x2;
Default Options seeds `options.txt` correctly). No missing deps, no mixin apply failures,
no `ftbteams`, no fishermans-haven errors, no YACL "Failed to deserialize". 22 benign ERRORs.
The one real blocker — **Critters & Companions spawn keys camelCase vs YACL's snake_case
policy**, NPE in `CACSpawnConfig$SpawnEntry.isTag`, broken mod state — was fixed and pushed
by the owner as **`8e8483d`** while this session ran. Alex's Mobs / Fowl Play spawn tuning
verified applied in-game.
**Open:** intermittent **world-load** crash in WATUT
(`ShaderInstanceBlur` → `FileNotFoundException: minecraft:shaders/core/particle.json`).
First load worked, next crashed. Needs repro; owner says remove no mods.
### Built this session (all under `analysis/client-perf/`, packwizignored)
- `harness/rcon.py`, `harness/run_scene.py` — apply variant → launch Prism into the bench
world under user-local MangoHud → drive scenes via xdotool → slice p99/1%-low from CSV.
Verified end-to-end once (C1/S1, but vsync-locked; `vblank_mode=0` added after).
- User-local MangoHud 0.8.4 (`~/.local/mangohud`, wrapper `~/.local/bin/mangohud-local`).
- Bench world `saves/onionbench` generated by temp server `~/onion-bench/server-bench`
(S2 Create floor, S3 mob pen placed+verified). S4 airships not built.
- `build.py` BENCH layer: `enableAutoUpdater=false` (suppress DH update modal). Tiers
unchanged; **nothing shipped**.
### Not done
The matrix blocks B0–B14. Fleet P0/P1/P2 are unreachable → any data here is a high-tier
proxy. Only one non-decision-grade run exists. Next agent: kill the WATUT crash, confirm
uncapped frametimes, then run B0→B14 per `BENCH-MATRIX.md`.
### Pushed
`8e8483d` (owner, CAC keys). This session's analysis/harness commit follows; it touches no
shipped file.
## Session 2026-10-14 (d) — state capture for handoff (orchestrator)
No pack changes; HEAD stayed `e5e369a`. Three read-only scouts inventoried the
interrupted session's artifacts; findings folded into the new **`analysis/state/`**
directory (6 markdown files: README, pack+environment, timeline, current-state, plan,
artifact-map) — that directory is now the canonical onboarding point for a new agent.
Facts established this session (all evidence-backed):
- The 20:34 FML crash report is the **CAC spawn-key NPE** (fixed by `8e8483d`) — *not*
the WATUT crash. No crash report exists for WATUT yet; it remains open and blocking.
- The dedicated server from the boot check is **still running** (tmux `onion-mc`,
PID 1699563, since 21:01) with an unidentified RCON keepalive polling every 30 s.
Left alone per rules.
- Working-tree diffs (`akashictome`, `alexsmobs` configs) are benign boot re-saves:
whitelist intact; tuned spawn values retained. Per rule, restore before staging —
not committed.
- Bench instance `~/onion-bench/prism/instances/OnionBench` is fully provisioned
(`.minecraft`, `saves/onionbench` 26 MB, Default Options seeded, bench pins,
`BENCH_VARIANT.txt = C1`). One earlier scout misread it as unprovisioned — verified
false by direct `ls`.
- Jar cache `/tmp/onion/jars` is gone (ephemeral; `analysis/audit/fetch.py` rebuilds).
State of the effort at capture: **Phase 2 begun, zero matrix blocks run.** Next agent:
follow `analysis/state/04-plan.md` §"Next actions" (WATUT → verify uncapped frametimes
→ finish S4 → B0→B14 → ship → report). `analysis/state/` itself is uncommitted — fold
it into the next commit.
## Session 2026-10-15 (b) — WATUT crash root-caused + fixed; harness v2
Commits: `a84319b` (fix(mods): MidnightLib 1.10.0 + `analysis/state/`), `e18bd3c`
(feat(bench): harness v2). Not pushed.
- **WATUT crash = victim, not cause.** Fright's Delight 1.4.8 jar-in-jars MidnightLib
1.9.2; its `MidnightConfig.init` races under NeoForge parallel mod construction →
`ConcurrentModificationException` → broken mod state → `mod_resources` never loads →
WATUT's unguarded `reloadShaders` mixin throws on `particle.json`. Fix: standalone
`midnightlib` 1.10.0 (`side=both`; upstream fix 1fe69729, synchronizedMap). FML picks
it over the JiJ copy (log: "Attempted to select a dependency jar ... Using Mod File").
Verified 10/10 cold launches to world join, 0 CME, all on 1.10.0. Frightsdelight
doesn't touch `entries` (binary-compat risk checked). **Dedicated server still runs
1.9.2 until its next restart + jar install** — server-side exposure untested.
- **Uncapped frametimes verified**: C1/S1 p50 2.9 ms, p99 9.5 ms, avg 297 fps
(harness v2 smoke, `~/onion-bench/results/session.jsonl`, label `smoke`). The earlier
60 fps row in `harness-results.csv` is void.
- **Harness v1 was silently broken**: KDE Plasma is Wayland; xdotool into Xwayland `:1`
drops input when a native window has focus → no `/function`, no markers, ever. Also:
partial `options.txt` re-triggered the accessibility onboarding screen (blocks
`--world`), and FTB Backups fired ~7 s after every join (inside capture windows).
- **Harness v2** (`analysis/client-perf/harness/bench.py`, `analyze.py`, `tests/`,
README): `boot --n N --join` reliability loop; `session` drives scenes via a
per-session generated datapack (armed only during the session, disarmed in
`finally`), log markers with ms timestamps, CSV picked by birth time, validity flags,
full metadata JSONL; `analyze.py` applies the BENCH-MATRIX win rule. 18 offline tests.
- Bench-instance-only edits: FTB Backups `auto: false`
(`config/ftbbackups3-server.snbt`); harness pins in `options.txt` (re-applied per run).
- Observed noise: the idle dedicated server uses 23–78 % of a core; amdgpu DPM is
`auto`; the bench window shares the owner's live desktop.
Next: owner calls on noise controls (see harness README / session report), then S4
build → B0 (`bench.py session --variant C0 --plan s1 --reps 3 --label B0`, ×3
sessions) → matrix.
## Session 2026-10-16 — bench world finished, harness v3, matrix running
Owner: stop the dedicated server while benching (done via console `stop`; tmux
`onion-mc` is gone — restart with `./start.sh` in tmux when benching ends; packwiz will
pull MidnightLib 1.10.0, jar also dropped in `mods/`). Gamescope integration approved.
- **Harness**: `--display gamescope-headless` default @1920x1080 (~2 % cost vs desktop,
isolates from the shared KDE Wayland desktop). Per-session `--shader/--shader-options/
--dh/--disable-mods/--add-mods/--world-zip` with a crash-safe restore journal
(`bench.py restore`). Plan forces **spectator** — elevated scene cameras previously fell
(S4 measured the sea floor at y=34). `shader_not_loaded` flag. `matrix.py` +
`matrix.toml` = resumable B0–B14 runner; B7 profiles generated from pack
`profile.*` lines (`gen_shader_profiles.py`); B12b trial jars in `~/onion-bench/trials/`.
Post-join settle 45 s (rep 1 still warming at 10 s). Commits `c6341d0`, `4fa9a6a`.
- **Bench world** built headlessly on `~/onion-bench/server-bench` via RCON:
S4 = 4 Sable sub-levels (5×7×15 planks/glass, levitite floor + 2 interior layers —
the 1-layer design sank ~14 blocks per physics wake), via `/sable assemble area`;
stable at y≈169.24 across restarts and a 60 s client session. Removed 370 stray
force-loaded chunks (S2/S3/S4 ticked in every scene). DH LODs pre-generated with
`/dh pregen start minecraft:overworld <x> 0 256` at x=0 and x=3680 (1.2 GB sqlite).
S2 rotates (Speed 16 sampled), S3 = 30 villagers.
Zips in `~/onion-bench/worlds/` (sha256):
`bench-world-v1.zip` 144caf5fcdfea1a0eac0c787fdcded949e1a67df6ac112400f37be30ce7fb76e,
`-s4-1.zip` 213b9b8871be527d0b5ac2e34615e8c2c1e3b206277fb37549a3014e2b895a38,
`-s4-0.zip` 6b2a2bbd16f9f2e5aba34d53b09752d30fb7f9c3a97c24c46131b54d12bdc234.
- **Matrix** run 1 (tmux `bench-matrix`, log `~/onion-bench/results/matrix-run1.log`):
B0,B1,B2,B3,B4,B5,B6,B7(noshader),B8–B12a. B0 noise (settle 45): p99 spread 0.40 ms,
1%-low spread 6.6 fps. **B2: C1 beats C0 on every scene** (S1 p99 8.23 vs 9.33;
S3 1%-low 186 vs 110) → no bisect stop. Early B3: dh-qlow wins S1, dh-ssao loses,
dh-thr4 loses S5 (threads stay 2). Analyze with `--since 2026-10-16T22:24`.
- Remaining after run 1: B4h (`--param gc=`), B7 shader rows, B12b, B13, B14, ship,
owner report. Analysis proxy-only; noise floor gpu_dpm_auto (no root).
- **2026-10-17 06:00 pause**: matrix run 1 finished first (**56/56 ok, 0 failures**,
B0–B12a incl. proxy B5). Timer `pause-bench-start-server.sh` started the server, but
tmux was born inside the oneshot service's cgroup and systemd killed it (and the
server, SIGKILL 1 s after `Done`, no players) when the unit exited. Restarted 06:05 in
its own scope (`systemd-run --user --scope tmux …`, unit `onion-mc-tmux.scope`);
script fixed the same way. **Server is up (25565).**
- B4 GC verdict: J1 vs J0 S5 1%-low +1.3 fps < 6.6 spread → tie → **J0**.
- Resume (stop the server first): `cd analysis/client-perf/harness &&
python3 matrix.py run B4h,B7,B12b --param gc=J0` (20 sessions, ~3.5 h). Then B13
(manual spark), B14 (manual spawn counts), ship, owner report.
## Session 2026-10-18 — run 2 + B14 done; server back
Overnight: 23:25 pause (owner) → 01:26 resume timer → matrix run 2 finished 03:18
(B4h, B7, B12b; all sessions ok after retries) → B7.noshader re-run on default JVM →
B14 defaults + shipped (33 rows each, biome_ok, config check ok) → server up 04:31.
06:00 safety timer saw the server running and left it (its "not Done" warning was a
scrolled-pane false alarm; readiness check now uses port 25565).
Results (full tables in `analysis/client-perf/RESULTS-proxy.md` §Run 2):
- B4h: smallest heap within spread = **5 GB** (4 GB loses S5) → PLAYER-TUNING note.
- B7: shaders' big cost is S2 — Flywheel backends drop (974 → ~120 fps avg) for every
profile; MakeUp shadowless near-free elsewhere, Complementary ~2× on S4.
- B12b: **Iris Flywheel Compat wins hugely under shaders** (S2 p99 13.3 → 4.0);
**Flerovium wins S3** (entities; B12b's S3 baseline anomalous, so confirm once).
Gnetum tie; BBE, Super Resolution, Ixeris (+1 crash), Cull Leaves rejected.
- B14 (n=1): rule says back off, but driven by one goose/duck herd at the warm-ocean
site; sea bunny never spawned in either config → inconclusive, needs ≥3 runs/config.
- Super Resolution's first launch rewrote bench `fml.toml` (`earlyWindowControl=false`,
unjournaled) → reset. Harness should snapshot/diff the whole config tree per session.
Remaining: B6 missing-texture check → ship modernfix-mixins; add Iris Flywheel Compat +
Flerovium (visual check, then `packwiz modrinth add`, then re-run B0); DH low-quality
visual check; B13 spark; B14 ×3 more; ship via build.py; owner report.
## Session 2026-10-18 (b) — shipped, run 3, owner report
- Shipped `7841361`: ModernFix dynamic_resources (enforced config; owner waived texture
check), Flerovium 1.1.2, Iris Flywheel Compat 2.4.0. Deck already had DH LOW.
- **Incident**: 7841361 refreshed the index while alexsmobs/akashictome held boot
re-saves → server packwiz install "Hash invalid" → server down 14:06–14:18. Fixed
`c5d25fa`. Rule: `git checkout -- config/` **before** `packwiz refresh`, not just
before staging.
- Run 3 (chain-0418): B0v2 noise p99 0.41 ms / 1%-low 1.6 fps. **Flerovium did not
hold up** (S1 loss, S3 too noisy) → recommend removal (owner decision pending).
B14 ×4: totals ~flat (jungle 130→128, river 208→212, warm ocean 145→136) → spawn
tuning harmless but ineffective; keep/revert is the owner's call.
- Docs: `analysis/client-perf/OWNER-REPORT.md` (new), RESULTS-proxy §Run 3,
PLAYER-TUNING (5 GB heap; Iris Flywheel Compat under shaders).
- Verified ModernFix honors the shipped override on the server boot log.
- Open: owner decisions (Flerovium, spawn tuning); B13 spark (optional); fleet
confirmation; push when the owner says so.
- Owner approved: **Flerovium removed, spawn tuning reverted** (`217c346`). Server was
stopped while the three spawn files were deleted so it couldn't keep re-saved tuned
copies; restarted, Alex's Mobs regenerated defaults (gazelle weight 40). Bench
instance jar removed too. b14.py's `shipped` config now has nothing to ship.
+214
View File
@@ -0,0 +1,214 @@
# FTB Chunks reveal -> Xaero's World Map: what transfers and how
**Status:** mask extraction implemented and validated against real data; world-save pruning implemented
and unit-tested; final step blocked on a copy of the server world save.
**Goal:** give each player a native Xaero's map covering, as closely as possible, exactly the terrain
they had revealed in FTB Chunks.
Confidence tags as in `analysis/archive/FINDINGS.md`.
---
## 1. Why we do not convert the pixels
FTB Chunks does **not** store a rendered map image. Verified by decompiling the pack's exact build,
`ftb-chunks-neoforge-2101.1.22.jar` (class strings in `MapManager`, `MapRegionData`, `MapDimension`,
`MapChunk`):
```
<game>/local/ftbchunks/data/<serverUUID>/
├── dimensions.txt, block_map.txt, biome_map.txt
└── <dimension>/
└── <XXXXX-XXXXX>.zip # region = 512x512 blocks; name = region coord + 60000, %05X-%05X
├── chunks.dat # the chunks this player has actually seen
├── data.png # NOT viewable: (height << 16) | (water|light|biome) per pixel
├── foliage.png # biome-tinted foliage RGB
├── grass.png # biome-tinted grass RGB
├── water.png # biome-tinted water RGB
└── blocks.png # FTB's internal 24-bit block colour index
```
The colour a player actually saw is computed at runtime by combining `blocks.png`'s index with FTB's own
`BlockColor` model via `block_map.txt`. Reproducing it means reimplementing FTB Chunks' renderer, and the
result would look like FTB's map - i.e. worse than native Xaero's. So we take the **reveal footprint**
from FTB Chunks and the **pixels** from Xaero's.
## 2. Why we also do not write Xaero region files
The current Xaero's region format is not the format the community tools understand. Comparing a real
`region.xaero` from Xaero's World Map 1.46.0 (extracted from the client instance) with the format
`JMtoXaero/RegionToXaero` writes:
| | old format (RegionToXaero) | real 1.46.0 |
| --- | --- | --- |
| header | `FF` then int 4 | `FF 00 06 00 08` + varying byte (format 6) |
| pixel payload | 4-byte parameters (+ optional state / colour / biome) | embeds NBT compounds |
| size for region `0,0` | ~25 KB | **355 KB** |
The 1.46.0 payload contains literal NBT - e.g. `0A 00 00 08 00 04 "Name" 00 10 "minecraft:gravel"` - so
each pixel now carries a block descriptor. This matches `JMtoXaero`'s own admission that its region
**merger** broke once Xaero changed the format. Writing this format is a live reverse-engineering project
against a closed, obfuscated, moving target. Not worth it.
**Therefore: drive Xaero's own generator, and control what it can see.**
## 3. The mechanism
Both mods use the same geometry - a region is 512x512 blocks = 32x32 chunks - so an FTB region maps 1:1
onto one Anvil region file, and an FTB within-region chunk `(x,z)` maps 1:1 onto Anvil slot
`x + z*32`. That makes the transplant exact rather than approximate:
1. Extract each player's reveal set from their `chunks.dat` files.
2. Prune a copy of the world save to exactly those chunks (Anvil header/sector surgery; chunk payloads
copied byte-for-byte, no NBT re-encoding).
3. The player opens the pruned save in **singleplayer** with the pack; Xaero's maps it directly from the
save, so absent chunks simply never appear on the map.
4. Copy the generated instance
(`xaero/world-map/World/null/<map>/`) into the server's folder
(`xaero/world-map/Multiplayer_advilplug.top/<DIM>/`).
Result: **per-chunk-exact reveal extent**, rendered natively by Xaero's. Residual differences are only
the colour palette/shading (Xaero's, not FTB's) - which is the whole point.
## 4. What the real data says
Measured on the supplied archive - **Stirwick**, `minecraft:overworld`:
| Metric | Value |
| --- | --- |
| Region files | 167 |
| Revealed chunks | **98,170** (of 171,008 in the bounding box) |
| Region extent | x `-9..9`, z `-14..7` |
| Block extent | x `-4608..5120`, z `-7168..4096` |
| Fully revealed regions (`1024/1024`) | 10 |
| Median region fill | 560 / 1024 (~55%) |
| Sparse regions (`<64` chunks) | 10 |
| `chunks.dat` vs `data.png` disagreements | **0** |
Two consequences:
- **`chunks.dat` alone is a trustworthy mask.** It was cross-checked against the `data.png` height
sentinel (`HeightUtils.UNKNOWN = Short.MIN_VALUE + 1 = -32767`) for all 167 regions. Zero
disagreements. Suspicion that claimed-but-never-visited chunks would inflate the set did not
materialise.
- **Region-granularity pruning is not good enough.** The median region is only ~55% explored, and 10
regions are under 6%. Copying whole `.mca` files a player touched would reveal roughly twice the
terrain they had seen. Chunk-level pruning is worth the extra ~40 lines.
`data.png` decoding detail, for anyone revisiting this: FTB writes `(height << 16) | wlb` into a
`TYPE_INT_ARGB` image, so after PNG round-trip the 16-bit height is `(A << 8) | R` and the
water/light/biome word is `(G << 8) | B`. Unrevealed pixels are exactly `A=0x80, R=0x01`.
## 5. Tooling
Both scripts are stdlib-only and live in `tools/` (which `.packwizignore` excludes, so they never ship).
`tools/migrate-player.sh` - the driver, does steps 1-2 and prints the manual remainder:
```
tools/migrate-player.sh Stirwick <ftb-data-or-archive> <world-dir> \
--dimension minecraft:overworld --work /var/tmp/xaero-migration
```
`tools/ftbchunks-reveal.py` - **validated against Stirwick's real 167-region archive.**
Emits a JSON mask of global chunk coordinates per dimension. Pass `--dimension` when the input is a
bare directory/zip of region files, which cannot name its own dimension.
```
tools/ftbchunks-reveal.py <ftb-data-or-archive> --out mask.json --dimension minecraft:overworld --verify
```
`tools/filter-xaero-regions.py` - post-harvest cleanup. Compares the Xaero's map folder against the
mask and removes regions the player never revealed (i.e. terrain Minecraft regenerated around the
login position). File-name-only, so no Xaero format knowledge needed; reports before deleting.
```
tools/filter-xaero-regions.py <game>/xaero/world-map/Multiplayer_advilplug.top/null/ mask.json
```
`tools/prune-world-to-reveal.py` - **validated end-to-end against a real region file from the server**
(`world/region/r.1.-2.mca`, 15.7 MB):
| Check | Result |
| --- | --- |
| Anvil parse (1024 slots, all zlib, 2.5-21.8 KB payloads) | OK |
| Slot -> chunk mapping (`slot = x + z*32`), confirmed by reading `xPos`/`zPos` NBT | **1024 / 1024 exact** |
| Prune against that player's FTB mask (52 of 1024 chunks revealed) | 52 kept / 972 dropped |
| Output sector-aligned | yes |
| Kept slot set == mask | yes |
| Kept payloads + timestamps byte-identical | yes |
| Kept chunks re-parse with correct coordinates | 52 / 52 |
| Size | 15,745,024 -> 471,040 bytes (3.0%) |
Earlier synthetic tests also cover nested per-dimension `region`/`entities`/`poi` trees not being
duplicated.
```
tools/prune-world-to-reveal.py <world-copy> mask.json <out-dir> --dimension minecraft:overworld
```
`--minimal` writes only `level.dat` plus the pruned `region/` tree - the smallest thing Xaero's can
open and map, and the best fit when the full save is too large to copy around. `--dry-run` reports
kept/dropped counts per region without writing anything. Runtime is ~0.15 s per 15 MB region.
## 6. Running it where the save lives
The tools are stdlib-only Python, so nothing has to be transferred: run them on the machine that holds
the world. The live world is **not** on the VPS (the VPS is only a proxy in front of the real server),
so this has to run where the save actually lives. The per-player footprint is only the pruned `region/`
tree for the regions that player revealed, not the whole save.
```bash
tools/migrate-player.sh Stirwick <stirwick-ftbchunks-data> <world-dir> \
--dimension minecraft:overworld --work /var/tmp/xaero-migration
```
That extracts the mask (with the `chunks.dat` vs `data.png` cross-check) and prunes the world,
dry-run first. Then, by hand:
3. Copy the pruned world (`<work>/<player>/sp-world`) to a machine that can run the pack and open it in
**singleplayer** once. Let Xaero's map it, then quit.
4. Move the generated instance, `xaero/world-map/World/null/<map>/`, into the server's map folder,
`xaero/world-map/Multiplayer_advilplug.top/<DIM>/`.
5. Run `tools/filter-xaero-regions.py` on that folder to drop regions the player never revealed.
6. Delete the scratch directory - usually a few hundred MB.
### Two things that will bite
- **The server does not need stopping, but freeze saves while it runs.** The pruner only reads the world
and writes to a separate output directory. However a running server rewrites region files on autosave
and rewrites `level.dat` in place, so a read can tear - and half a `level.dat` is an unopenable
singleplayer world. From the console: `save-off` -> dry run -> real run -> `save-all` -> `save-on`.
The pass takes a couple of minutes. Never forget `save-on`, and never point `--out` at the live world.
A region that does fail to read is reported, skipped, and left unwritten; re-run to retry it.
- **Opening the world regenerates chunks near the login position.** Minecraft loads and generates the
chunks around wherever the player spawns, so terrain the player never revealed can reappear on the
map if it happens to sit near world spawn. It is bounded by render distance (well under one
512x512 region at typical settings), and it is easy to undo: step 4's cleanup compares the harvested
region file names against the mask and deletes any region the player never revealed. Keeping
`level.dat`'s spawn inside the revealed area, or not walking around in step 3, avoids the rest.
- **Do not copy the world with hardlinks.** `cp -al` would make the singleplayer client's own writes to
`level.dat` land in the live server save. A real copy, or `--minimal` into a fresh directory, is safe.
## 7. Open items
- **Needs: the full world save, read in place on the server.** One real region file (`r.1.-2.mca`) has
now validated the reader, the slot mapping and the writer, so the remaining unknowns are operational
rather than format-related: total runtime over 167 regions (extrapolating ~0.15 s per 15 MB region,
a couple of minutes) and the size of the pruned output. A full 1024-chunk region on this server is
~15 MB; the player revealed 57.4% of the bounding box overall, so expect the pruned per-player world
to land in the low hundreds of MB. It is scratch data and can be deleted once the Xaero's map has
been harvested.
- **Per-player, per-dimension.** Only `minecraft:overworld` was supplied and only one player's data.
Modded dimensions will each need an FTB-data -> Xaero-dimension mapping. Xaero names its dimension
folders `null`/`DIM-1`/`DIM1` for vanilla and uses the `minecraft$overworld` form (`$`, not `:`) in
`dimension_config.txt`; a `dimensions.txt` with more than one entry will confirm the rest.
- **Step 3 costs one client launch per player.** It cannot be done server-side; Xaero's maps from a save
on the client. ~10 launches for this pack.
- **Waypoints are a separate, easier win.** FTB stores them in `waypoints.json` next to the region data
(empty, 16 bytes, for the sampled player). Xaero waypoints are plain files under `xaero/minimap/`,
so a converter is straightforward - worth doing at the same time if anyone has waypoints to keep.
- **Fallbacks if the save never materialises:** region-granularity pruning (coarse, ~2x over-reveal), or
shipping the full "Convert All Regions" map to everyone (over-reveals everything, zero code).
- Nothing here is time-sensitive: removing the mod from the pack does not touch `local/ftbchunks/`, so
every client's reveal data is still on disk and this remains doable later.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,265 @@
# Onion MC — candidate mods grouped for review
Every candidate grouped by what it is *for*, with what it brings, what it costs in dependencies, how much it changes gameplay, the verdict, and the thing to watch. **ADDED** = already added on the `mod-pack-additions` branch. **ALREADY-IN-PACK** = duplicate of an installed mod.
Verdict legend: **ADD** (ship in the core cohort) · **ADD-OPTIONAL** (good; taste/library decision) · **MAYBE** (test or design decision first) · **SKIP** (don't) · **ALREADY-IN-PACK** · **ADDED**. `New deps` lists required libraries not already in the pack (candidate deps marked).
## Shaders & render stack
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **Iris Shaders** (`iris`) | Shader-pack loader compatible with existing OptiFine shader packs | — | Overhaul | ADDED | Already added on branch mod-pack-additions (see Post-review update) |
| **Iris Veil Compat** (`iris-veil-compat`) | Bridges the Veil rendering engine into Iris shaderpacks so Veil-rendered visuals (Aeronautics wings, glass panels, contrails) render correctly under a shaderpack; r… | Iris Shaders | None | ADDED | Already added on branch mod-pack-additions (see Post-review update) |
| **Veil** (`veil`) | developer tooling for advanced rendering — JSON-defined framebuffers, custom post-processing pipeline, full OpenGL shader support + shader injection, color themes/e… | — | None by itself | SKIP | rendering-engine tooling with no dependent mod in the pack, on the most fragile part of the stack |
| **Shine** (`shine`) | Shader-free visual effects: bloom, colored lighting, atmosphere, weather, particles, interactive details; in-game config screen | — | Client-cosmetic | MAYBE | confirmed Sodium 0.8.12 compatibility, but partial feature loss on that version, overlap with Subtle Effects, and a privileged positio… |
| **Grassier Grass** (`grassier-grass`) | Per-blade custom wind shader: grass leans and ripples with rolling gusts | — | Client-cosmetic | MAYBE | best-looking pick in the batch, but Sodium 0.8-alpha compatibility is unproven; gate on a crash/visual test |
| **Fog** (`fog`) | Complete fog revamp: start/end distance, cave fog that thickens with depth, weather- and time-based (sunset/moon-phase) fog colors, biome-tinted fog including modde… | MRU | Client-cosmetic | MAYBE | attractive but unproven next to DH/SSRD; only add after an in-game fog + DH visual test |
| **ATMOSPHERICS** (`atmospherics`) | Real-time per-biome customization of fog, sky, clouds, haze and stars with smooth biome transitions; day/night split, comets, star editing | — | Client-cosmetic | SKIP | visual upside not worth the risk on the Sodium+DH+SSRD stack; no shader-adjacent additions should ride on that axis per pack rules |
| **Immersive Winds** (`immersive-winds`) | Wind system driving particle rain/snow, rain-splash particles, and sway animation for grass, plants, lanterns, and fire | — | Client-cosmetic | SKIP | Sinytra-only NeoForge support is a red flag by pack policy, and Grassier Grass already covers sway if that's added |
| **Puddles & Floods** (`puddles-floods`) | Puddle blocks appearing from rain, dripping blocks, flowing water, splash potions, broken cauldrons, and melting snow; puddles combine into floods in low-lying terr… | Forgified Fabric API | Client-cosmetic | SKIP | Fabric-API-derived dependency (red flag) for a cosmetic payoff with real server tick cost; revisit only if the pack accepts Forgified… |
## Client ambience & visual FX
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **AAA Particles** (`aaa-particles`) | Library that loads Effekseer (`.efkefc`) particle effects; by default replaces the vanilla lightning effect with an Effekseer one | — | Client-cosmetic | SKIP | a library with no in-pack consumer; standalone value is only a lightning visual swap |
| **AAA Particles: World** (`aaa-particles-world`) | In-world Effekseer effects: restored world lightning, explosion effect replacement (default on since 1.0.2), loot beams (1.0.3) | AAA Particles | Client-cosmetic | SKIP | purely cosmetic addon whose required base (AAA Particles) is itself a skip |
| **Particle Effects** (`particle-effects`) | Unique textured particle visuals for every vanilla status effect | — | Client-cosmetic | ADD-OPTIONAL | pure client polish, zero server footprint |
| **Perception** (`perception`) | Immersive client visuals: projectile/entity trails and directional screen-shake triggered by loud sounds, explosions, fast movement (also aids threat localization) | — | Client-cosmetic | ADD-OPTIONAL | safe client ambience; optional enough that individual players could skip it |
| **Visuality: Reforged** (`visuality-forge`) | cosmetic particles — crystal sparkles, mob-hit particles, custom slime blobs, environmental particles | — | Client-cosmetic | ADD-OPTIONAL | safe client-only polish; consider whether Subtle Effects already covers the need |
| **Inventory Particles** (`inventory-particles`) | Data-driven particles on inventory items (dust for gunpowder/dyes, glow for luminous items, etc.), with physics/rotation customization | MossyLib | Client-cosmetic | MAYBE | pretty and unusually modded-item-friendly; fine as a client-side optional, but pure cosmetics |
| **Fancy World Animations [FWA]** (`fwa`) | Smooth open/close animations for doors, trapdoors, fence gates, levers, buttons, repeaters, cauldrons, composters, lecterns, campfires, chiseled bookshelves, vaults… | — | Client-cosmetic | ADD-OPTIONAL | strong client polish with a blacklist safety valve; verify against Sable/ImmediatelyFast rendering before shipping |
| **Cosy Critters & Creepy Crawlies** (`cosy-critters`) | Client-side ambient fauna: bird flocks that land high and scatter from entities/projectiles; tiny spiders emerging from cobwebs; moths around sky-exposed light bloc… | — | Client-cosmetic | ADD-OPTIONAL | cheap atmosphere for explorers; skip if the ambience budget is full |
| **Better Animations Collection** (`better-animations-collection`) | Re-animated vanilla mob models: wiggly ghast/squid tentacles, fluffy wagging wolf tails, flowing ocelot/cat tails, pig snout+ears, cow udder, chicken bills, etc., w… | Puzzles Lib | Client-cosmetic | ADD-OPTIONAL | pure atmosphere polish for one new library dep |
| **Immersive UI** (`immersive-ui`) | Animated UI: particle trail for carried items of non-common rarity, carried-item wiggle/scaling, smooth hotbar selector animation, floating matched container items | ShatterLib | OctoLib | Client-cosmetic | SKIP | two extra libraries for cosmetic overlap with Inventory Particles; poor value per pin |
| **Effectual** (`effectual`) | Atmospheric particles: breath steam in cold biomes, underwater bubbles (mouth/chests/pots), drip on exiting water, footprints + snow residue, cave dust, lava-adjace… | TLib | Client-cosmetic | MAYBE | duplicates Subtle Effects' niche and drags in a new lib; only add after an overlap audit, which the config-less pack model makes costly |
| **Blur+** (`blur-plus`) | Gaussian-blur shader behind all GUI backgrounds, with fade animations, ARGB gradient/colors, per-GUI exclusions and radius config | MidnightLib | Client-cosmetic | ADD-OPTIONAL | safe cosmetic; only bother if the pack wants GUI polish |
| **Camera Overhaul** (`cameraoverhaul`) | Client-side camera rotation effects: strafe/turn tilt, jump/fall pitch, explosion shake, idle sway; per-effect intensity config (0 disables) | — | Client-cosmetic | ADD-OPTIONAL | cheap and configurable, entirely a taste/accessibility call |
| **Cinematic Respawn** (`cinematic-respawn`) | Replaces the death screen with a cinematic camera + fade transition into respawn; optional auto-respawn and skippable animation; fully client-side | — | Client-cosmetic | ADD-OPTIONAL | pleasant, zero-risk client polish; taste call on auto-respawn |
| **Smart Particles** (`smart-particles`) | Distance-prioritized particle culling ("smart particle culling") plus camera/frustum culling of off-screen particles | — | Client-cosmetic | ADD-OPTIONAL | targeted client-side stabilizer with no server footprint; watch mixin overlap with ImmediatelyFast |
## World mechanics & hazards
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **Burnt** (`burnt-basic`) | Persistent, spreading fire: lit blocks smolder → burn → leave burnt block variants (with charcoal drops), integrated wind system (read via flags), biome-aware burn… | — | Moderate | ADD-OPTIONAL | distinctive survival flavor with controls, but server-grief/tick cost means it's a taste call, not a default |
| **Burnt + Additions** (`burnt`) | Everything in `burnt-basic` plus: Flint Bow (flame arrows), Fire/Blaze Barrels (delayed blast + ignition), random structural collapse events, smoker→burnt-wood reci… | — | Moderate | MAYBE | the fuller variant is attractive but heavier; if fire flavor is wanted, choose this over basic deliberately (and still treat as option… |
| **Tree Physics** (`tree-physics`) | cutting a tree with Rooted Dirt beneath it assembles the trunk/canopy into a Sable physics sub-level and topples it realistically; crouch-chop opts out; building on… | — | Moderate | MAYBE | the most on-theme Sable use possible, but per-tree physics sub-levels on the Aeronautics stack demand a dev-server soak test before sh… |
| **Sable Beyond** (`sable_beyond`) | Create interop with Sable sublevels: mechanical arms interacting with sublevels, encased fans pushing/pulling sublevels, dynamic mass for fluid tanks/spouts/drains/… | — | Moderate | MAYBE | the most functionally attractive Aeronautics enhancement in the batch, but alpha software on the pack's most fragile stack; isolated t… |
| **Ragdoll Reactions** (`ragdoll-reactions`) | Players ragdoll in reaction to crashes, being run over, sharp direction changes, and explosions | Sable: Ragdolls | Minor | MAYBE | only alongside Sable: Ragdolls, and only after crash-testing with Aeronautics |
| **Sable: Ragdolls** (`sable-ragdolls`) | Physics-driven player ragdolls using Sable's physics system (H to tumble, Shift to get up) | — | Minor | MAYBE | fun, Sable-native, and unlocks two other candidates; test with Aeronautics before adopting |
| **Sable: Ragdoll Corpse** (`sable-ragdoll-corpse`) | On death, a fully simulated ragdoll corpse holding your entire inventory (items safe in an internal inventory) | Sable: Ragdolls | Minor | SKIP | Gravestone already owns this job; stacking two death-item systems on the Sable physics stack adds fragility for no new capability |
| **Lunar** (`lunar`) | Cycle of lunar events: Bad Omen Moon, Big/Tiny Moon (gravity changes), Blood Moon (harder mobs), Crimson Moon (nether mob spawning in Overworld), Hero/Miner Moon (b… | — | Moderate | MAYBE | fun server-wide content, but gravity/spawn events touching Aeronautics physics is an unquantified risk in this pack |
| **Celestria** (`celestria`) | Server-triggered, synced shooting stars at night (grant Luck; command-spawnable; client renders via a WorldRenderer mixin, with a client-only fallback mode for vani… | — | Minor | MAYBE | nice ambience, but stale build + WorldRenderer mixin on the Sodium/DH/Sable stack needs an in-pack smoke test first |
| **Mystical Oak Tree** (`mystical-oak-tree`) | A rare talking oak tree with 300+ written lore/joke/fact lines, trust mechanic, self-defense; "dynamically grabbed from the web" content | — | Minor | ADD-OPTIONAL | harmless flavor for a friends pack; nothing about it strains the stack |
## HUD & client UI
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **Auto HUD** (`autohud`) | Dynamically hides HUD elements that aren't currently interesting (keybind toggle, per-element config); hides persistent status effects and shows remaining effect ti… | Fabric API | Client-cosmetic | ADD-OPTIONAL | Metadata Fabric-API dep is Fabric-build-only; native NeoForge build |
| **Armor Hud** (`armor-hud`) | HUD overlay showing each equipped armor piece with icons and color-coded durability bars (green/yellow/red/black thresholds) | — | Client-cosmetic | ADD-OPTIONAL | tiny, dependency-free HUD addition |
| **Immersive Armor HUD** (`immersive-armor-hud`) | Pixel-perfect 7×7 armor icons above the vanilla armor bar showing which armor pieces are worn and per-piece durability warnings | TxniLib | Client-cosmetic | ADD-OPTIONAL | cheap vanilla-style HUD upgrade; minor recency caveat |
| **Health Bars** (`new-health-bars`) | HUD health panel: mob name, health, armor, damage taken with animated bars | Puzzles Lib | Client-cosmetic (informational) | MAYBE | redundant with Jade unless the group wants damage numbers; if added, de-overlap with Jade deliberately |
| **Neat** (`neat`) | Minimalistic unit frames (health bars colored by HP %, armor, smite/arthropod attribute icons, stack grouping, team colors, toggle keybind) over entities | — | Client-cosmetic | ADD-OPTIONAL | mature, zero-risk combat readability; prefer over the Damage Indicator if choosing one |
| **JeremySeq's Damage Indicator** (`jeremyseqs-damage-indicator`) | Client-side directional damage arrow, floating damage text, text health bars for mobs, optional blood overlay | — | Client-cosmetic | MAYBE | harmless, but functionally redundant with Neat if both are added; pick one combat-info mod |
| **Advancement Plaques** (`advancement-plaques`) | Replaces advancement toasts with glowing plaques for Task/Goal/Challenge; configurable position, distance, whitelist; resource-pack customizable | — | Client-cosmetic | ADD-OPTIONAL | safe client polish on a dep already in pack; one test-instance check on the sound caveat |
| **GUI Effect Timer** (`gui-effect-timer`) | Countdown text (s/m/h/d abbreviations) inside status-effect HUD badges, updated per tick; color configurable; supports infinite/command-set effects | Collective | Client-cosmetic (informational) | ADD-OPTIONAL | tiny, safe, useful; effectively free once Collective is pinned for Followers Teleport Too |
| **Keybind Atlas** (`keybind-atlas`) | Client-side visual keyboard/mouse overlay showing every keybinding, color-coded assigned/unused/hidden, with source-mod attribution and in-overlay rebinding | — | None | ADD-OPTIONAL | genuinely useful in a pack this size, zero risk |
| **Distinct Potions** (`distinct-potions`) | Distinct bottle styles for greater/extended potions, distinct colors for awkward/mundane/thick base potions | Puzzles Lib | Client-cosmetic | ADD-OPTIONAL | cheap visual QoL; only worth it if the pack accepts pulling in Puzzles Lib for a single client mod |
| **Obscure Tooltips** (`obscure-tooltips`) | Fully animated tooltip styling: glowing borders, particle effects, rarity-adaptive ornaments, integrated 3D gear previews inside tooltips, smooth scrolling and line… | Fragmentum | None | MAYBE | attractive but redundant with Legendary Tooltips; only add after confirming they don't fight over tooltip rendering, or as a replaceme… |
| **Tooltip Overhaul** (`tooltip-overhaul`) | complete vanilla tooltip replacement — scrollable/wrapping tooltips, equipment comparison, pinned tooltips, 30+ frames, 37+ effects, animations, 3D item icons/previ… | — | Client-cosmetic | SKIP | collides with Legendary Tooltips; the pack should carry one tooltip restyler, and it already does |
| **Visual Workbench** (`visual-workbench`) | crafting tables retain items in the grid between crafts; ingredients + result rendered on top of the table; contents drop when broken | Puzzles Lib | Minor | ADD-OPTIONAL | popular, polished Fuzs mod; costs one small library |
| **Held Item Tooltips** (`held-item-tooltips`) | Rich info plate above the hotbar on item switch: enchantments, durability, potion effects, item attributes, map IDs, firework/banner/book details | Puzzles Lib | Client-cosmetic (informational) | ADD | low-risk, high-frequency QoL; library cost shared with other Puzzles-Lib picks |
| **GUI Compass** (`gui-compass`) | HUD text readout of direction + coordinates ("FXYZ"), shown only with a compass in inventory (default) | Collective | Client-cosmetic | MAYBE | harmless, but marginal over F3 and redundant if Field Guide (with Immersive Overlays) is added |
| **Better Third Person** (`better-third-person`) | Independent free-rotating third-person camera (full 360°), 8-direction movement without camera rotation, full item use in third person, vehicle/swim/elytra support… | — | Client-cosmetic | ADD-OPTIONAL | safe, popular camera QoL with no deps; just smoke-test it while piloting Aeronautics contraptions |
| **Dynamic Crosshair** (`dynamiccrosshair`) | Context-sensitive crosshair: hides/changes based on what is looked at, held item, and action; highly configurable with optional YACL config screen | Fabric API | Client-cosmetic | ADD-OPTIONAL | pure client polish, but must be tested against ImmediatelyFast and dropped if HUD glitches appear |
| **Emojiful** (`emojiful`) | Emojis in chat, including animated ones, with autocomplete and an emoji picker | — | Client-cosmetic | ADD-OPTIONAL | fun for a 10-player social server with zero gameplay footprint |
| **Quick Skin** (`quick-skin`) | In-game skin library GUI (import by username or drag & drop), Classic/Slim model swapping | — | Client-cosmetic | ADD-OPTIONAL | client nicety; preferred over SkinChanger (newer, GUI, more features) |
| **SkinChanger** (`skinchanger`) | `/skin set|clear` and `/cape set|clear` commands by username or URL, working on offline-mode servers; admin variants targeting other players | — | Client-cosmetic | SKIP | redundant with Quick Skin (GUI, HD skins, animated capes) and stale: last updated 2024-10-26 vs. Quick Skin's 2026-09-28 |
| **Akashic Tome** (`akashic-tome`) | A single "Book of Books" item (craft: book + bookshelf) that stores all mod documentation books and transforms into any of them; right-click menu to switch, sneak-r… | — | None | ADD-OPTIONAL | cheap, low-risk QoL that scales well with a 142-mod pack |
| **Field Guide** (`field-guide`) | Spyglass-based scanning of entities/blocks to unlock entries in a personal in-game encyclopedia | Item Descriptions, Immersive Overlays | Minor | ADD-OPTIONAL | excellent fit for a 10-player exploration pack; price of admission is two extra NeoForge libs |
| **Certain Questing Additions** (`certain-questing-additions`) | FTB Quests UI upgrades: entity rendering in quest descriptions/backgrounds, APNG image support, per-chapter shader-based backgrounds, hover/open/zoom animations, gl… | — | None | ADD-OPTIONAL | nice polish for a quested pack; harmless if dropped |
| **FindMe** (`findme`) | Hover an item in JEI and press Y to mark which nearby inventories contain it (cloud particles) | — | Minor | ADD | zero-dep, low-risk, high-frequency QoL |
| **Zume** (`zume`) | hold-to-zoom keybind (FOV zoom) with zoom-level increase/decrease keybinds; auto-reloading JSON5 config in the global `.minecraft` dir | — | None (view convenience; no gamepl… | ADD | zero-dep, zero-server, fills an obvious gap next to Sodium+DH distance viewing |
## Audio & music
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **Sounds** (`sound`) | 170+ new/reworked SFX (UI, chat, item, block sounds); configurable per event and extensible via resource packs; spiritual successor to ExtraSounds/Auditory | MRU | Client-cosmetic | ADD-OPTIONAL | high polish, zero server footprint; costs one new small library |
| **Extreme sound muffler** (`extreme_sound_muffler`) | Client-side selective sound muffling via an inventory-button/keybind GUI with per-sound volume sliders | — | Client-cosmetic | ADD | zero-risk, zero-server-cost QoL that is actively maintained (3.56, 2026-09-22) |
| **Sounds Be Gone!** (`soundsbegone`) | Mute any vanilla/modded sound event from a "recently played" list, with unmute audit trail | — | Client-cosmetic (no gameplay chan… | ADD-OPTIONAL | tiny, safe, real accessibility value for a 10-player group |
| **Simple Music Control** (`simple-music-control`) | Halved delay between background music tracks, next-track keybind (default M), now-playing widget | — | Client-cosmetic | ADD | zero-risk client QoL whose only dependency is already present |
| **Now Playing** (`now-playing`) | Toast/hotbar popup announcing the current music track (background music and jukeboxes), with placement/timing/sprite resource-pack options | — | None | ADD-OPTIONAL | trivial, safe; most valuable if music-content mods are added |
| **More Music Discs** (`more-music-discs`) | Custom music discs (YouTube-sourced tracks, used with permission) found as chest loot, mob drops, or creative tab; loot-table injection fully configurable/disableab… | Deimos | None-Minor | ADD-OPTIONAL | cheap flavor; verify default disc-in-every-loot-pool density is wanted |
| **Net Music: Better Experience** (`net-music-play-list`) | Playlist items and a portable music player built on the "Net Music" mod; playlist discs for jukeboxes/CD burners | Net Music | None-Minor | SKIP | dependent on an absent streaming-music base mod with service/licensing baggage; poor fit |
| **VinURL** (`vinurl`) | custom music discs that download and play audio from YouTube/file-hosting URLs via jukebox; `/vinurl` commands; auto-downloads yt-dlp, deno, and FFmpeg binaries at… | Sinytra Connector, Forgified Fabric API, oωo (owo-lib) | Minor (custom music per player) | SKIP | Connector red flag, runtime binary downloads, and licensing minefield; not worth it for jukebox music |
| **Immersive Melodies** (`immersive-melodies`) | 7 instruments playing 25 bundled songs; custom melodies via in-game drag-and-drop or datapacks (MIDI/ABC) | — | Minor | ADD-OPTIONAL | fun multiplayer toy at essentially zero mechanical risk |
| **Even More Instruments!** (`evenmoreinstruments`) | Playable instruments: trombone, violin, keyboard, guitar, saxophone, koto, shamisen, pipa; all 16 note blocks as playable instruments | Genshin Instruments | Minor | SKIP | abandoned per its own README and requires Genshin Instruments, which the pack does not have |
| **cat_jam** (`cat_jam`) | Client-side animation: cats bob/jam to the rhythm of music discs, with per-disc routines | — | Client-cosmetic | ADD | tiny, client-only, documented compat with a mod already in the pack |
| **Etched** (`etched`) | Custom music discs burned from URLs (MP3, WAV, OGG, SoundCloud) via the Etching Table with label customization | — | Minor | ADD-OPTIONAL | great social toy for a small server; settle the URL-content policy first |
## Food & farming
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **Chef's Delight - Farmer's Delight Villagers** (`chefs-delight`) | Two villager professions (Cook, Chef) trading FD food, plus village structures for them (cook/chef houses) | — | Minor (if it worked) | SKIP | 1.21.1 build is bound to FD Refabricated, which conflicts with the pack's existing Farmer's Delight |
| **Expanded Delight** (`expanded-delight`) | Farmer's Delight expansion: new crops, blocks, workstations and more, added "gradually" on top of the base mod | — | Moderate | ADD-OPTIONAL | strongly on-theme and the best-covered addon for Display Delight, but accept the author-declared beta instability or wait for stable |
| **More Delight (for Farmer's Delight)** (`more-delight`) | Extra Farmer's Delight meals and ingredients, bread-slice sandwiches, toast, wooden/stone knives | Delight Lib | Minor | ADD-OPTIONAL | safe, on-theme content; the Delight family is already large, so it's cumulative rather than filling a gap |
| **Miner's Delight** (`miners-delight`) | Farmer's Delight cave-food addon: cave carrots (new crop + gossypium flower), bat/silverfish snacks, copper pot and copper cup cooking utensils, cave-themed meals | Lodestone | Minor | ADD-OPTIONAL | strong thematic fit with the Delight family, but the author's own beta warning plus a new library dep argue for wait-and-see |
| **Dungeon's Delight** (`dungeons_delight`) | Monster Pot cooking station (heated by spawner-like blocks) producing Monster Foods | — | Moderate | ADD-OPTIONAL | thematically perfect next to the pack's existing Delight collection; sanity-check the new effect types against Apotheosis stacking |
| **Barbeque's Delight [Forge/NeoForge]** (`barbeques-delight-forge`) | Grills + skewer cooking loop (place → smoke cue → sneak-flip → collect; burning on failure), ingredients basin crafting of skewers | — | Minor | ADD-OPTIONAL | frictionless fit with the existing Delight stack |
| **Fright's Delight** (`frights-delight`) | 56 horror-themed Farmer's Delight foods/drinks/candies crafted from hostile-mob drops (rotten flesh, spider eye, ghast tear, slime, cobweb, wither/soul berries) | — | Minor | ADD-OPTIONAL | fits the pack's Delight collection; horror theme is a taste call |
| **Veggies Delight** (`veggies-delight`) | 7 new vegetables (bell pepper, broccoli, cauliflower, garlic, sweet potato, turnip, zucchini) + forageable dandelion leaf; temperature-based wild veggie worldgen; v… | — | Minor | ADD-OPTIONAL | low-risk content that deepens the pack's largest content family |
| **Fruits Delight** (`fruits-delight`) | 17 fruits (peach, mango, lychee, durian, …) with unique per-fruit processed-food effects (Strength+Rage Aura, Night Vision, Healing Aura, walk-on-ice, eat-when-full… | — | Moderate | ADD | big, popular (2.2M downloads) FD expansion that plugs into two pack pillars (Create, Terralith) |
| **Rustic Delight** (`rustic-delight`) | New crops: bell peppers (red/yellow/green), coffee, cotton, with wild spawns (e.g., jungle wild bell peppers/coffee) | — | Minor | ADD | textbook fit: same-dependency addon for an existing content pillar |
| **Cultural Delights** (`cultural-delights`) | New world-cuisine crops: avocado, eggplant, cucumber, corn, beans, cinnamon | Cook's Collection | Moderate | ADD-OPTIONAL | on-theme food content, but it requires adopting Cook's Collection and the Delight shelf is already crowded |
| **Hearth and Harvest** (`hearth-and-harvest`) | New farmable crops, some with unique growth mechanics, plus new interactive food/resource crafting systems | — | Moderate | ADD-OPTIONAL | solid FD-family expansion; coordinate corn overlap with Haunted Harvest before adding both |
| **Haunted Harvest** (`haunted-harvest`) | Trick-or-treating: baby villagers wear 9 mask types, walk at night, collect candy from adult villagers (with witch interactions and egg-throwing on refusal); candy-… | Moonlight Lib | Minor | MAYBE | charming, but adds a lib, modifies villager AI, and double-buys corn with Hearth and Harvest |
| **Overweight Farming** (`overweight-farming`) | Rare "overweight" super-crops (beets/carrots/potatoes/beans) with boosted yields, moonlight growth bonus, straw hat (breeding twins/triplets), peeling/waxing/melon-… | — | Minor | ADD-OPTIONAL | small, self-contained farming charm |
| **AlcoCraft+** (`alcocraft`) | Brewing keg with 10+ beer recipes (hops, sugar, apples, water) | — | Minor | ADD-OPTIONAL | small, self-contained fun; no flags |
| **Fishing Real** (`fishing-real`) | Fish hooked as items spawn as live entities instead (salmon → live salmon, etc.) | — | Minor | ADD-OPTIONAL | cheap, fun, low-risk; worth a small datapack pass to include Aquaculture species |
| **FishingOverhaul** (`fishingoverhaul`) | Reel-in minigame: fish circles a bar; right-click inside the catch region to land loot; crit region doubles loot | — | Minor | ADD | declared Aquaculture 2 compat makes this a natural pack fit |
| **Fisherman's Haven** (`fishermans-haven`) | Fishing expansion: new rods, traps and nets that catch multiple fish at once | — | Moderate | ADD-OPTIONAL | no deps, distinct fishing mechanics that complement Aquaculture 2; accept the niche overlap deliberately |
| **Farmer's Knives** (`farmers-knives`) | Farmer's Delight knives in many modded materials — for mods actually in this pack that means The Twilight Forest (Ironwood, Fiery, Steeleaf, Knightmetal) plus a cop… | — | Minor | ADD-OPTIONAL | cheap thematic glue whose payoff is real but limited to Twilight Forest materials since the other supported tool mods aren't in the pa… |
| **Display Delight** (`display-delight`) | Placeable 3D food blocks for every Farmer's Delight (+ addon) food; placed foods are neither block entities nor entities (explicitly lag-friendly) | — | Minor | ADD | direct, low-risk upgrade to the pack's food-and-decor identity |
| **Create: Factory** (`create-factory`) | Create-themed foods: rolls, waffles, apples and related items/recipes; optional extra recipes when Create: Confectionery or Ecologics are present | — | Minor | ADD-OPTIONAL | harmless, but the food family is already the pack's most crowded content lane; low marginal value |
| **Delightful Creators** (`delightful-creators`) | Farmer's Delight ↔ Create automation bridge: new fluids for soups, stews and sauces; mixer recipes to produce them; bowl filling from fluids | — | Moderate | MAYBE | pick one FD↔Create bridge; Slice & Dice is already installed and proven, so this needs a deliberate "both approaches" decision |
| **Berry Good** (`berry-good`) | Berry QoL: bush placement moved to "pips" so bushes aren't placed accidentally; other simple berry-use improvements (excerpt details only the placement change) | Blueprint | Minor | SKIP | an entire additional library dependency (Blueprint) for one minor placement QoL; revisit only if the pack adopts Team Abnormals mods w… |
| **Abnormals Delight** (`abnormals-delight`) | Farmer's Delight integration for Team Abnormals mods: FD cabinets in every Abnormals wood type, Duck/Deer meat cuts, cake slices alongside Neapolitan | Blueprint | Minor (and in this pack, near-zer… | SKIP | required Blueprint dep plus an ecosystem (Team Abnormals) the pack doesn't run; mostly dead content |
| **ExtraDelight** (`extradelight`) | Kitchen workstations: Oven (3×3 slow-baking), Dough Shaping Station, Mixing Bowl, Mortar & Pestle, Drying Rack, Melting Pot (solids→fluids), Chiller (fluids→solids) | — | Moderate | ADD-OPTIONAL | the meatiest Delight addon in the batch and a natural kitchen-tier progression; confirm the block-entity load on the dedicated server… |
## Create & technology
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **Create: Bells & Whistles** (`bellsandwhistles`) | 36 decorative/utility Create blocks: station platforms, headlights, "Metro" casing/panel/window palette, andesite/brass/copper grab bars & bogie steps & door steps… | — | Minor | ADD-OPTIONAL | strong deco value for a Create pack; verify blocks behave on Aeronautics contraptions first |
| **Create: Design n' Decor** (`create-design-n-decor`) | Create-focused QOL decoration: block variants (multiple crushing-wheel variants), containers, and extra factory-styled deco blocks | — | Minor | ADD-OPTIONAL | safe factory decoration; nice with the pack's Chipped/Rechiseled angle |
| **Create: Teleporters** (`create-teleporters`) | Create-styled teleportation: TP Links with a configurable range, plus entity and item teleporters in multiple sizes | — | Moderate | ADD-OPTIONAL | clean fit for Create logistics with no flagged conflicts; decide whether the pack wants dimensional play |
| **Create: Structures Arise** (`create-structures-arise`) | 28 new Create-themed worldgen structures scattered across the world | — | Moderate | ADD-OPTIONAL | strong fit for a Create + exploration pack, but it thickens an already structure-saturated worldgen stack with no config knobs shipped |
| **Create Waystones Recipes** (`create-waystones-recipes`) | Replaces all Waystones crafting recipes with Create-style (machine/mechanical) recipes and rebalances them; pairs with an optional texture/GUI resource pack | — | Minor | ADD-OPTIONAL | safe; a deliberate progression/taste decision for the questbook rather than a technical one |
| **Create: Sophisticated Backpacks Compat** (`create-sophisticated-backpacks-compat`) | A curated set of Create machine recipes for Sophisticated Backpacks items/components, enabling automation of key SB components inside Create factories (only "carefu… | — | Minor | MAYBE | plausible value, but check for recipe collisions with the official Create Integration already in the pack before committing |
| **Create: Dynamic Lights** (`create-dynamic-lights`) | Dynamic lighting for moving Create contraptions (client-side light via Sodium-family dynamic lights), plus an optional server-side "light block" mode that places re… | Sodium Dynamic Lights | Client-cosmetic | SKIP | redundant with LambDynamicLights, wrong rendering stack assumptions (Embeddium/Rubidium), and its niche is better covered (or delibera… |
| **Create Sable Dynamic Lights** (`create-sable-dynamic-lights`) | Dynamic-light support for Create contraptions and Sable sub-levels (moving contraptions/sub-levels actually illuminate surroundings, incl. on Aeronautics ships) | Sodium Dynamic Lights | Client-cosmetic | SKIP | requires a second dynamic-lights engine that duplicates LambDynamicLights and couples to the fragile Sodium/Sable version pin |
| **CreateBetterFps** (`createbetterfps`) | FPS optimization for Create scenes ("up to 50%" with a shaderpack, 7→35 fps with 512 waterwheels) via Sodium API usage and backface culling | — | None | MAYBE | Iris + Sodium now in pack, so unblocked in principle, but it patches Create's code on the Aeronautics/Sable stack - test first |
| **Sophisticated Inventory Interactions** (`sophisticated-inventory-interactions`) | Search field inside vanilla/modded container GUIs; sort modes (name/mod/count/tags) | — | Minor (pure inventory-management… | MAYBE | good mod, but it replaces rather than stacks with Client Sort; pick one container-sorting solution |
| **Sophisticated Item Actions** (`sophisticated-item-actions`) | Keybind highlights nearby storages containing the held/hovered/JEI-bookmarked item (green exact / blue type match) | — | Minor (inventory-flow QoL, Terrar… | ADD-OPTIONAL | genuinely new capability that plays well with the Sophisticated + JEI stack already in the pack |
| **Sophisticated Sorter** (`sophisticated-sorter`) | Sophisticated Core sort buttons on most regular container screens (name/mod/count/tags modes) | — | Minor | SKIP | redundant with Client Sort in-pack and with Sophisticated Inventory Interactions in this same batch; side metadata is also "unknown" f… |
| **Refined Storage - JEI Integration** (`refined-storage-jei-integration`) | JEI ↔ Refined Storage recipe transfer for Crafting Grid and Pattern Grid, incl. autocrafting tasks from JEI preview | Refined Storage | None in this pack | SKIP | dead weight without Refined Storage |
## Decoration & building
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **Beautiful Campfires** (`beautiful-campfires`) | Wood-themed campfire variants (birch, etc.) so the crafted wood determines appearance; vanilla campfire recipe restricted to oak; includes the fire-type variants (r… | — | Minor | ADD-OPTIONAL | cheap cosmetic polish; document the oak-only recipe change in pack notes |
| **Sooty Chimneys** (`sooty-chimneys`) | Brick, Cobblestone, Mud Brick, Stone Brick, Iron, Copper, and Terracotta chimneys emitting smoke | — | Minor | ADD-OPTIONAL | safe, charming builder content with negligible cost |
| **Items Displayed [NeoForge]** (`items-displayed-forge`) | Sneak-right-click placement of items (sherds, smithing templates, ingots, etc.) as world-facing 3D models; craftable "Item Display" stand rotatable to any angle, va… | — | Minor | ADD-OPTIONAL | safe builder content that fits the pack's decoration mods |
| **Too Many Paintings!** (`too-many-paintings`) | 50+ new paintings including famous artworks (Van Gogh, da Vinci); works in existing worlds | — | Client-cosmetic (placed paintings… | ADD-OPTIONAL | zero-dep cosmetic content, safe on both sides |
| **Portfolio** (`portfolio`) | 100+ paintings by artist Yapetto across 10 themed catalogues (Still Life, Void Manor, Death, Rana, Far Lands, Anchor, Giant, Elemental, Cloud Cuckoo, Keeping Up) | — | Client-cosmetic | ADD | zero-dependency pure content that fills an empty decorative niche |
| **Display Case** (`displaycase`) | A decorative single-item display block; clicking with honeycomb stops the spin (axe unwaxes) | — | Client-cosmetic | MAYBE | fine in isolation, but choose between this and Fetzi's Displays (same batch) rather than shipping both display systems |
| **Fetzi's Displays** (`fetzis-displays`) | Glass cloches in 16 colors, katana stands, and wall holders with item-name plaques | — | Client-cosmetic | MAYBE | good feature set but redundant with Display Case (batch) + Handcrafted; pick one display system for the pack |
| **Gallery** (`gallery`) | Icon textures for all painting variants; right-click-with-painting GUI to search/select variants | Blueprint | Client-cosmetic (decorator QoL) | MAYBE | nice decorator QoL but drags in the Blueprint library for a niche feature |
| **Spooky Doors** (`spooky-doors`) | horror-game door behavior for all doors — drag to half-open/peek, slam by fast drag, run-into to open, creak sound set; Honeycomb/Ghast Tear toggle | — | Minor | ADD-OPTIONAL | small, charming, Balm-based and low-risk |
| **Boatload** (`boatload`) | Boats with chests, boats with furnaces, large boats, and boats with banners | Blueprint | Minor | ADD-OPTIONAL | pleasant water QoL, but chest-boat overlap with vanilla makes it low-value; needs Blueprint |
| **Supplementaries** (`supplementaries`) | Vanilla+ block set: jars, signposts, faucets, weather vanes, spring launchers, sconces, planters, lights, decoration + automation | Moonlight Lib | Moderate | ADD-OPTIONAL | Create conflict issues all closed; NeoForge-first. Needs Moonlight Lib; smoke-test |
| **Accents** (`accents`) | Dyeable cosmetic accessories (hats, back items, e.g. partial-coloring Top/Pirate Hat) with small situational stat bonuses (e.g. Sheathed Katana +5% damage worn on b… | Fragmentum | Minor | ADD-OPTIONAL | low-risk cosmetic+minor-stat layer; costs one new library dep |
## Mobs & wildlife
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **Animal Garden - Porcupine** (`animal-garden-porcupine`) | Tamable/breedable porcupines spawning in forests (JSON-configurable spawn tags); quill damage to nearby attackers; apple taming, sweet-berry breeding/healing, incre… | — | Minor | ADD-OPTIONAL | tiny, dependency-free, fits the existing tameable-mob theme |
| **Fowl Play** (`fowlplay`) | ~13 bird species (hawks, gulls, ravens, crows, ducks, geese incl. swan/emden variants, songbirds, penguins) with realistic flight, sounds, and ecosystem behavior; s… | SmartBrainLib | Minor | ADD-OPTIONAL | strong ambience fit for the pack's mob roster; costs one AI lib |
| **Companions!** (`companions-mod`) | 10 tamable companion creatures with abilities, hostile mobs with unique skills, new weapons, a boss; Small/Great Essence economy (Great Chalice, Soul Furnace, fille… | Knight Lib | Moderate | MAYBE | decent content but redundant with the existing mob roster and it drags in a new library; only if the pack wants more combat-pet content |
| **Hybrid Aquatic** (`hybrid-aquatic`) | Large marine mob roster: new fish species, sharks, crustaceans, critters, otters, nautiluses, and a new miniboss | Hybrid API [HAPI], Biolith | Major | ADD-OPTIONAL | biggest content win in this batch; gate on a worldgen + Aquaculture overlap test because of the two new library pins |
| **Alex's Mobs Continued** (`alexs-mobs-continued`) | Unofficial continuation of Alex's Mobs: the full ~116-mob roster (grizzly bears, tigers, gorillas, sharks, orcas, soul vultures...), taming/breeding/mounts/bucketin… | CodxLib | Overhaul (if it *replaced* the ex… | SKIP | direct duplicate of an existing pack mod; would only ever be a 1:1 swap project |
| **Friends&Foes - Flowery Mooblooms (Forge/NeoForge)** (`friends-and-foes-flowery-mooblooms-forge`) | One moobloom cow variant per flower (allium, sunflower, lilac, etc.) | — | Minor | ADD-OPTIONAL | cheap flavor that slots into an installed mod; skip only if moobloom density is unwanted |
| **Pet Cemetery** (`pet-cemetery`) | Dead pets drop Pet Collars; use a charged Respawn Anchor to revive them as Zombie pets (tankier, curable) or Skeleton pets (faster, unrecoverable) with stat tradeof… | Blueprint | Minor | ADD-OPTIONAL | emotionally valuable on a friends server; verify modded-pet support first |
| **PetProtect** (`petprotect`) | Server-side protection toggles for tamed mobs in PvP: prevent pet player-damage, pet death, pet attacks on players; owner-damage, creative, and totem-effect options… | — | Minor | ADD | tiny, dependency-free, and directly prevents grief/accident on a 10-player PvP-capable server full of tamed mods' pets |
| **Mob Lassos** (`mob-lassos`) | Craftable lassos to carry mobs in inventory: golden (land), aquatic, diamond (no timer), emerald (villagers via contract), hostile (target below 50% HP, bites carri… | Puzzles Lib | Minor-Moderate | ADD-OPTIONAL | useful QoL; check boss exclusion config before shipping to a pack with Apotheosis |
| **Realm RPG: Fallen Adventurers** (`realm-rpg-fallen-adventurers`) | 50+ treasure-bearing skeleton types generating across biomes and dimensions, dropping ores, materials, food, gear, and enchanted books | — | Minor | ADD-OPTIONAL | easy exploration flavor; note the 1.21.1 build dates to 2025-01 (aging but stable, 2.3M downloads) |
| **Spawn** (`spawn-mod`) | Overworld wilderness overhaul: new biomes, many animal mobs with variants, ambience, mechanics | Teal Lib | Major | MAYBE | substantial content but collides with the pack's curated worldgen and mob roster; needs Teal Lib and dedicated testing before inclusion |
| **Mermod** (`mermod`) | Magical necklace (Curios-style accessory) that transforms the wearer into a merperson in water (per description; details on wiki, not README) | — | Minor | ADD-OPTIONAL | small, cheap, fits the pack's exploration theme; low information confidence due to thin README |
| **Punchy! x Tiny Takeover** (`tiny-takeover-x-punchy`) | Carry baby mobs in first person with dedicated animations (crouch + right-click; "I" key playful animation) | — | Minor | MAYBE | verify whether Punchy/Tiny Takeover base mods are actually required and that the NeoForge build is real before adding |
| **Environmental** (`environmental`) | New overworld biomes: Blossom Woods, Pine Barrens, Marshes | Blueprint | Moderate | MAYBE | its content substantially duplicates what Terralith + Alex's Mobs already deliver, and it requires pulling in Blueprint for a single m… |
| **Primal** (`mod-primal`) | Wilderness-immersion content released in "Hunters" (1.0) and "Hunt & Gather" (1.1) updates; further specifics are deferred to an external wiki that the README itsel… | — | unknown | MAYBE | sparse documentation; inspect the wiki and jar contents before committing |
| **Pixelmon** (`pixelmon`) | Every Pokémon Gen 1–9 (1000+) with handmade models/animations, plus battling, trading, breeding | — | Overhaul | SKIP | total-conversion-scale content that steamrolls the pack's identity, worldgen, and mob budget |
## Combat, gear & magic
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **Too Many Bows** (`too-many-bows`) | 38 unique bows with special abilities; new attributes (Bow Draw Speed, Bow Damage, Ranged Weapon Damage) | — | Moderate | ADD-OPTIONAL | strong synergy with Better Combat and Curios already in the pack |
| **Reliquary Reincarnations** (`reliquary-reincarnations`) | Magical utility items: Coin of Fortune (item/XP magnet), Interdiction Torch (pushes mobs), Mob Charms (per-mob-type ignore), Sojourner's Staff (torch staff), Void T… | — | Moderate | ADD-OPTIONAL | content-rich and maintained, but demands a balance pass against Apotheosis/Artifacts |
| **More Relics** (`more-relics`) | 25+ relics (accessory items with unique/evolved forms, e.g. Tyrant Mask, Whispering Amulet evolution chains) distributed across structure chests | ShatterLib | OctoLib, Relics | Moderate | SKIP | needs a missing base mod + two libraries, carries self-documented version-pin fragility, and duplicates the Artifacts niche the pack a… |
| **Relics: Arcane Abilities** (`arcane-abilities`) | Dungeons containing three relic artifacts: Fate Beacon (structure finding + teleport marks), Shadow Lantern (darkness abilities: blinding, invisibility, time manipu… | — | Moderate | SKIP | its own README requires Relics + Hammer Lib (missing); would need both plus a balance review to reconsider |
| **Dream Relics** (`dream-relics`) | Curios-integrated accessories: necklaces/rings/bracelets/special items with abilities (damage scaling vs missing/target health, multi-jump boots, XP-granting grail) | — | Moderate | MAYBE | duplicate niche vs Artifacts with Apotheosis balance implications; needs a curation pass before adding |
| **TarotCards: Remastered** (`tarotcards-remastered`) | 22 Major Arcana cards found only in loot chests (buried treasure, shipwrecks, dungeons), each granting a passive buff while carried; dedicated Curios slot; deck com… | — | Minor | ADD-OPTIONAL | clean fit for an exploration/loot pack; explicitly team-aware |
| **Skillcloaks** (`skillcloaks`) | Curios back-slot cloaks from villager trades (colored sacks from master-tier villagers): Agility, Attack, Construction (teleport to spawn), Cooking, Crafting (porta… | — | Minor | ADD-OPTIONAL | solid Curios content on an existing dependency; consider adding Lucent for the full feature set |
| **Ars Elixirum** (`ars-elixirum`) | Free-form elixir brewing: ingredients with seed-dependent alchemical properties, player-authored/saveable/shareable recipes (recipe books on lecterns) | Fragmentum | Moderate | ADD-OPTIONAL | deep alchemy content with one new library dep; confirm in testing that it doesn't hard-require anything beyond Fragmentum |
| **Ars Ocultas** (`ars-ocultas`) | Ars Nouveau ↔ Occultism compatibility: spirits usable from Containment Jars, 7×7 auto item-processing around jars, item-capability piping into jars (pipes/hoppers/s… | Occultism | Moderate if Occultism were presen… | SKIP | required dependency Occultism is absent from both the pack and this batch |
| **Estrogen** (`estrogen`) | Estrogen Pill → "Girl Power" effect enabling a dash movement ability (+ hidden abilities); Estrogen Patches worn in their own Curios-style slots | Cynosure | Moderate | SKIP | meme-toned content whose headline feature alters player movement near a fragile physics stack, and which costs two extra pieces (Cynos… |
| **Create: Estrogen** (`create-estrogen`) | The Create module of the Estrogen mod: a Centrifuge multiblock/machine for new fluid processing recipes, plus custom package (Create packager) shipping recipes; JEI… | Estrogen | Moderate | MAYBE | can't be evaluated in isolation (dep is a batch candidate), self-declared in-development, and tone may not fit the pack; decide togeth… |
| **Small Ships** (`small-ships`) | Medieval sailable ships (R to open/close sail, W/S speed), biome-temperature speed bonuses, ship inventory | — | Moderate | MAYBE | appealing content, but a beta build with old updates parked next to Aeronautics/Sable physics; needs a dedicated compat test or should… |
## Exploration & worldgen
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **Dynamic Trees** (`dynamictrees`) | Trees as growing multiblock structures: seeds self-plant, trees grow biome/temperature/rainfall-dependently, grow around obstacles, die in darkness | — | Major | SKIP | a transformative tree overhaul whose coverage against the pack's 10+ worldgen mods is unverified and which demands config curation the… |
| **Spawn Animations** (`spawn-animations`) | hostile mobs dig out of the ground or poof into existence when spawning (works for zombies, skeletons, creepers, endermen, etc.); server-side, command-driven settin… | Quilted Fabric API (QFAPI)…, Fabric API | None | MAYBE | mild flourish; if wanted, use the datapack form to dodge the Fabric dep chain rather than the mod |
| **Spawn Animations Compats** (`spawn-animations-compats`) | datapack adding Spawn Animations support to 50+ modded mobs (Enderman Overhaul, Deeper and Darker, The Graveyard, Galosphere, etc.) | Spawn Animations | None | SKIP | only meaningful if Spawn Animations is added, and its supported mob list barely overlaps this pack |
| **Loot Journal: Pickup Notifier** (`loot-journal`) | Animated item/XP pickup notifications with smart stacking, rare-find highlights, nearby-player pickup tracking, themes, and in-game config | Fragmentum | Client-cosmetic | ADD-OPTIONAL | nice HUD polish; in-game config means no shipped-config maintenance |
| **Traveler's Titles** (`travelers-titles`) | RPG-style fade-in titles when entering biomes/dimensions; Waystone-named region titles (1.18+ Forge/NeoForge), custom sounds via resource pack, dimension/biome blac… | — | Client-cosmetic | ADD | cheap, polished, and it makes the pack's giant curated worldgen legible to players |
| **Death Count** (`death-count`) | A per-player death counter displayed in the tab player list | — | Minor | ADD-OPTIONAL | harmless novelty for a 10-player server, though equally achievable with a one-line vanilla scoreboard datapack |
| **Chalk** (`chalk-mod`) | Craftable chalk (calcite / loot) that draws arrow/dot/symbol marks on block faces; dyeable 16 colors; glowstone/glow-ink glowing marks; chalk box for storing/select… | — | Minor | ADD | cheap, self-contained, fills a real gap for cave exploration |
## Social, sleep & death
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **Comforts** (`comforts`) | Sleeping bags (portable bed that doesn't set spawn) and hammocks (sleep day→night), 16 colors + dyeing; optional config mechanics (well-rested, sleepiness, break ch… | — | Minor–Moderate | ADD | staple adventure QoL, well-maintained, no dependency cost |
| **Sleep Tight** (`sleep-tight`) | Sleep overhaul: hammocks (day rest), night bags (spawn-less temporary beds), dream essence, home-bed rewards, slower early-game sleep progression | Moonlight Lib | Moderate | ADD-OPTIONAL | popular, well-scoped sleep rework; requires adding Moonlight Lib and choosing between it and Room For Two rather than stacking blindly |
| **Room For Two** (`room-for-two`) | Multiple players sharing one bed; >2 players stack; camera faces your sleeping direction; `/sleeppos [front|back|left|right]` preference | — | Minor | ADD-OPTIONAL | harmless fun at BETA maturity; pick it OR Sleep Tight's ecosystem, and test if both |
| **PlayerRevive** (`playerrevive`) | A 60-second post-death window in which friends can revive you instead of a hard respawn | — | Moderate | ADD-OPTIONAL | strong social fit, but it rebalances death and needs a Gravestone interplay check |
| **Incapacitated** (`incapacitated`) | Downed state between life and death: crawling, bleed-out timer, 3 downs before true death (configurable); lava and similar causes bypass instantly | — | Major | MAYBE | great co-op mechanic but changes core death rules; needs owner sign-off plus a Gravestone interaction test |
| **Death Knell** (`death-knell`) | 35+ new death messages, including circumstance-specific ones (cookie-weapon kills, axe dismemberment) and generic variants | — | Client-cosmetic | ADD | zero-risk flavor whose deps are already installed |
| **Charta** (`charta`) | Card-game system: 3 games (Crazy Eights, "Fun", Solitaire), card tables in 3 sizes for up to 8 players, decks found in village/dungeon loot, in-game tutorials; API… | — | Minor | ADD-OPTIONAL | charming multiplayer flavor, zero risk if unused |
| **Mino++** (`minopp`) | Playable Uno-style card game: placeable table, scroll-wheel card selection, bot players (`/summon minopp:mino_auto_player`, bot config requires YACL), rule variants… | — | Minor | ADD-OPTIONAL | charming social content with no risk surface; pure taste call |
| **Piggyback** (`piggyback`) | Replaces the default sit-on-head player mechanic with a piggyback-style ride (right-click a crouching player with an empty hand) | Faewulf's Lib | Minor | ADD-OPTIONAL | harmless co-op fun, but requires adding Faewulf's Lib to the pack |
| **What Are They Up To (Watut)** (`what-are-they-up-to`) | in-world indicators when other players are typing (speed-animated), GUI-activity visuals (hand clicking at a screen), idle/sleeping player visuals, and Zz idle mark… | CoroUtil | Minor | ADD-OPTIONAL | genuinely fun on a small friend group; costs one small library |
| **Open Parties and Claims** (`open-parties-and-claims`) | Chunk claiming + forceload, player parties, party chat, admin controls, permission-API integration (FTB Ranks, LuckPerms), explicit FTB Teams and Xaero's map integr… | — | Overhaul of base-protection/socia… | SKIP | direct duplicate of FTB Chunks + FTB Teams; would only make sense as a wholesale replacement, which contradicts the installed FTB Ques… |
| **Welcome Screen** (`welcome-screen`) | one-time welcome screen on first world/server join and update-"what's new" screens, fully customizable via FancyMenu | FancyMenu, Necronomicon API | None | SKIP | 2-3 new dependencies for a screen a server MOTD/pin note can replace |
| **Early Bedtime** (`early-bedtime`) | Server-side configurable earlier sleeping threshold (default 1000 ticks / ~50s sooner) | TxniLib | Minor | SKIP | solves a shader-specific problem this shaderless pack doesn't have, at the cost of adopting TxniLib |
| **Horror messages** (`horror-messages`) | A random horror chat message ("Help me", "Behind you", binary "LEAVE") broadcast to all players every 10–15 minutes | — | None | SKIP | chat-spam gimmick; only defensible if the pack deliberately becomes a horror pack |
## Server, admin & performance
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **BlueMap** (`bluemap`) | Reads world files and generates 3D maps of the whole surface, viewable in a browser via its own built-in web app | — | None | ADD-OPTIONAL | genuinely useful for a small dedicated server, but it's ops overhead (webserver, ports, configs) rather than content |
| **Better Compatibility Checker** (`better-compatibility-checker`) | Replaces the default loader server-list compatibility check with modpack project-ID/version comparison, showing tick/X for players adding the pack's server | — | None | MAYBE | only earns its slot if the build pipeline commits to updating its config each release |
| **Let Me Despawn** (`lmd`) | Lets mobs that picked up/equipped items despawn naturally (dropping the items first), with configurable exclusion list and live `/letmedespawn add|remove` commands;… | Almanac | Minor | ADD | clear server-side perf win for a 10-player mob-dense pack; add Almanac with it |
| **More Culling** (`moreculling`) | Client-side culling changes for multiple render categories (per description/wiki: e.g. leaves, block-entity type cases) to cut rendered geometry | — | None | MAYBE | plausible FPS gain, but redundant-adjacent to Entity Culling and unvetted against the pack's fragile rendering core; test in isolation… |
| **BadOptimizations** (`badoptimizations`) | Client micro-optimizations: skip lightmap texture re-uploads when nothing affecting brightness changed; gate debug renderers (bees/game events/gametest/villager AI)… | — | None | SKIP | marginal, hard-to-measure gains on a stack that already covers these areas (constraint 6); not worth extra mixin surface for a ~4% syn… |
| **AsyncParticles** (`asyncparticles`) | Client particle optimizations: GPU-accelerated particle vertex rendering, async particle ticking, particle culling, async light cache | — | None (pure client optimization) | SKIP | a named crash interaction with in-pack C2ME that the pack cannot pre-configure, plus beta render-layer mixins against the fragile Sabl… |
| **Followers Teleport Too** (`followers-teleport-too`) | Non-sitting pets (wolves/cats) near the player teleport along with them on any teleport | Collective | Minor | ADD-OPTIONAL | clear QoL win with Waystones for ~10 players; costs one shared lib |
| **Waystones Teleport Pets** (`waystones-teleport-pets`) | pets teleport alongside players using Waystones without needing a lead attached | — | Minor QoL for pet owners | ADD | tiny, targeted, and fixes a real annoyance for a 10-player server full of tamed pets |
| **Carry On + Create Aeronautics Compat** (`carryon-aeronautics-compat`) | Patches Carry On so blocks can be picked up from Aeronautics/Sable simulated contraptions | Carry On | Moderate (when it applied); effec… | SKIP | self-declared obsolete (fix upstreamed to Carry On 2.2.6+), base mod absent, documented server-hang known issue |
| **Carry On Extend** (`carry-on-extend`) | Shift+Q to throw the entity/block you are currently carrying (Carry On add-on) | Carry On | Minor | SKIP | dead on arrival without Carry On; revisit only if the pack adopts Carry On itself |
## Already in pack (duplicates)
| Mod | What it brings | New deps | Impact | Verdict | Watch-out |
| --- | --- | --- | --- | --- | --- |
| **Aquaculture 2** (`aquaculture`) | (duplicate entry) — 30+ biome-specific fish, neptunium gear, hooks/bait/line rods, tackle box, fish fillets and fishing loot | — | Moderate (as installed) | ALREADY-IN-PACK | exact duplicate of the existing Aquaculture 2 entry |
| **Aquaculture Delight** (`aquaculture-delight`) | (duplicate entry) — Farmer's Delight × Aquaculture 2 compatibility: fish dishes, Neptunium knife, knife cross-compat, cooking pot/cutting board recipes | — | Minor (as installed) | ALREADY-IN-PACK | exact duplicate of the existing Aquaculture Delight entry |
| **Apothic Combat** (`apothic-combat`) | (duplicate entry) — Compatibility between Apotheosis and Better Combat: registers two-handed Better Combat weapons as HEAVY_WEAPON; removes the Entity Reach attribu… | — | Major (as installed) | ALREADY-IN-PACK | duplicates the pack's existing Apothic Combat entry |
| **Ars Creo** (`ars-creo`) | (duplicate entry) — Ars Nouveau × Create addon: Starbuncle Wheel (SU generation), spell/timer turrets on contraptions, Source Jars as contraption parts | — | Moderate (as installed) | ALREADY-IN-PACK | duplicate of the existing Ars Creo entry; the "missing dep" line is a dead-ID artifact, not a real gap |
| **Create Aeronautics: Compatibility** (`create-aeronautics-compatability`) | (per README) Fixes for mods that break on Sable sub-levels/physics ships: Etched, WATERFrAMES, Alex's Mobs + Alex's Caves (pathfinding RAM/OOM fix), Another Furnitu… | — | Moderate | ALREADY-IN-PACK | duplicates the existing "Create Aeronautics: Compatibility" entry in the pack list; do not add a second copy |
| **Nature's Compass** (`natures-compass`) | (Duplicate entry) Biome-locating compass with searchable GUI, HUD info, config blacklists | — | N/A | ALREADY-IN-PACK | byte-for-byte duplicate of the existing Nature's Compass entry; drop from the candidate list |
| **Separate Sable Render Distance** (`ssrd`) | (duplicate) — Sable physics objects rendering beyond view distance with Distant Horizons/Voxy; Sodium-menu sub-level render distance slider; server-friendly `/ssrd… | — | None | ALREADY-IN-PACK | duplicate of the pack's existing SSRD entry; exclude from the candidate list |
| **Sophisticated Backpacks Create Integration** (`sophisticated-backpacks-create-integration`) | Lets Sophisticated Backpacks sit on Create contraptions with all upgrades (Pickup, Magnet, Filter, Void) functional while in motion | — | Minor (already shipped; storage a… | ALREADY-IN-PACK | exact duplicate of the pack's own Sophisticated Backpacks Create Integration entry |
| **Sophisticated Storage Create Integration** (`sophisticated-storage-create-integration`) | Sophisticated Storage barrels/chests/shulker boxes usable on Create contraptions (trains, gantries, flywheels) with upgrades, tier upgrades, and Storage Tool functi… | — | Minor (already shipped) | ALREADY-IN-PACK | exact duplicate of the pack's own Sophisticated Storage Create Integration entry |
@@ -0,0 +1,688 @@
# Onion MC — Candidate Modlist Review
**Pack:** Onion MC 0.1.0 — Minecraft 1.21.1 / NeoForge 21.1.251 (packwiz)
**Candidates reviewed:** 189 minetest/mod links (mods, plugins, datapacks) supplied by the operator
**Existing pack:** 142 tracked entries (133 Modrinth + 9 CurseForge)
**Date:** 2026-09-29 (post-review update: see Section 0)
---
## 0. Post-review update — new commits and the `mod-pack-additions` branch
After the first pass of this review, the remote was fetched and two things changed the premises:
**A. New commits on `origin/main`** (local `main` was 2 commits behind):
- `be07001` — **Clumps** added (XP-orb merging, `side = both`): cuts XP-orb entity count, entity packets
and server tick cost. Low-risk, standard server-perf mod; no conflict with anything reviewed here.
- `25b3f78` — C2ME runtime `cache/` untracked from the pack index (housekeeping).
**B. Branch `mod-pack-additions`** (2 commits ahead of the local `main`; **not merged**, and checked
out in a separate worktree at `~/.herdr/worktrees/onion-mc/mod-pack-additions`). It adds:
| Added | Detail |
| --- | --- |
| **Iris Shaders** | `iris-neoforge-1.8.14-beta.1+mc1.21.1`, `side = client` |
| **Iris Veil Compat** | `irisveil-0.3.0.jar` — bridges Veil (embedded in Sable) through Iris so Create Aeronautics visuals render correctly under shaders |
| **Aeronautics Iris Fix** | CurseForge `aero-shadow-fix-2.1.0.jar` — third-party fix for Create Aeronautics balloon envelopes rendering as ghost artifacts under Iris |
| **Shaderpacks** | Complementary Reimagined r5.9.3, MakeUp Ultra Fast 9.5f |
| **Resourcepacks** | Motschen's Better Leaves 9.6, Fresh Animations: Extensions v1.8.1 |
**What this changes in this report:**
- The **"no shaders" premise is retired**. Onion MC is moving to Iris + a curated shader list, with two
purpose-built compat patches for the exact Aeronautics/Iris problems `FINDINGS.md` flagged.
- **`iris` and `iris-veil-compat` move from SKIP to `ADDED`** — they are now pack content.
- **`createbetterfps` moves from SKIP to MAYBE.** Its blocker was *"1.21.1 needs Sodium and Iris"*;
with Iris now present it is technically unblocked, but it patches Create's code directly and benefits
only shader scenes, so it must be tested against the Aeronautics/Sable stack before shipping.
- **Shader-adjacent candidates get a second look**: `shine`, `grassier-grass`, `fog`, and
`ATMOSPHERICS` now sit next to a real shader pipeline. `shine`/`grassier-grass` (Sodium terrain
shaders) become more relevant; `fog`/`ATMOSPHERICS` (sky/fog rewrites) now conflict with the
shaderpacks' own fog/sky, so their SKIP/MAYBE verdicts stand for a stronger reason.
- **Residual risk is not zero.** The fix chain is `iris` + `iris-veil-compat` + `aeronautics-iris-fix`,
and `aeronautics-iris-fix` is a small third-party CurseForge patch (~2.6K downloads, present in ~11
modpacks). Known Aeronautics/Iris issues beyond balloon shadows exist (e.g. heat-particle rendering
has a separate fix mod). Treat the Aeronaautics-under-shaders combination as **verified-in-progress**,
and keep the `phase0/` measurement plan for shader on/off before/after.
- Sections below are otherwise unchanged, except where marked **(updated)**.
---
## 1. Executive summary
**Every one of the 189 candidates has a 1.21.1 NeoForge-compatible build.** Nothing on the list is
blocked by version availability. The real filters are **dependency cost**, **overlap**, and a small
number of **hard conflicts**.
Headline result:
| Verdict | Count |
| --- | --- |
| **ADD** — clear, low-risk win | **18** |
| **ADD-OPTIONAL** — good, but a taste/librarary decision | **88** |
| **MAYBE** — needs an in-pack test or a design decision first | **36** |
| **SKIP** — conflict, dead dependency, duplication, or wrong stack | **36** |
| **ALREADY-IN-PACK** — duplicate of an installed mod | **9** |
| **ADDED** — already on `mod-pack-additions` | **2** |
The single most useful finding is that **the candidate list is dominated by "more of what the pack
already has"**. Onion MC already runs five Farmer's Delight addons, a full Create stack, ~20 structure
mods, a large mob roster, and a saturated client-performance stack. Most candidates are another Delight
addon, another decoration mod, another HUD mod, or another culling/perf mod. Adding all the safe ones
would bloat the pack and its library surface without changing the shape of the game.
**Recommended core: 20 mods + 2 libraries** (Section 4). Everything else is either a deliberate
expansion choice, a test-first item, or a skip.
The second finding is **dependency economics**. The candidates would collectively drag in **~25 new
NeoForge library mods** (`Puzzles Lib`, `Moonlight Lib`, `Blueprint`, `Fragmentum`, `Collective`,
`Almanac`, `Delight Lib`, `Lodestone`, `SmartBrainLib`, `Coralib`, `Faewulf's Lib`, `Teal Lib`, etc.).
Several are shared by multiple candidates, so the sane strategy is to **pick one or two content
families and pay for the libraries those need** — not to adopt candidates piecemeal.
The third finding is that a **few candidates are actively dangerous** in this specific pack: anything
that needs **Iris/shaders** (the pack deliberately has none; Create Aeronautics and SSRD both
document Iris rendering issues), anything **Fabric-only via Sinytra Connector**, a **second dynamic
lights engine** next to LambDynamicLights, and a **second claim/party system** next to FTB.
See the risk register in Section 8.
---
## 2. How this review was produced (and how much to trust it)
- **Deterministic data (high confidence).** All 189 links were resolved through the **Modrinth API v2**
— project metadata, loader/side support, every 1.21.1 build, and the **required-dependency graph**
of the selected 1.21.1 NeoForge build. The existing pack was read directly from its 142 `.pw.toml`
files. Counts, loader support, version availability and required dependencies in this report are
machine-derived, not guessed.
- **README/feature analysis (medium-high confidence).** Each candidate's Modrinth README was used as
the primary source for what it adds and for author-declared compatibility notes. Where the README
was silent, the assessment says so.
- **Compatibility claims (medium confidence).** Named conflict claims come from mod READMEs, official
issue trackers, or the pack's own `FINDINGS.md`. They are cited in the detailed appendix and should
be re-tested in a dev instance before shipping — a closed issue or a fixed version can invalidate any
of them.
- **Where this report is uncertain** it says `unknown`, `unverified`, or lists a test gate. Treat the
verdict as a prior, not a guarantee.
### Corrections applied after validation
The bulk research pass was independently checked on the highest-impact claims. Three corrections were
made; the full per-mod appendix retains the corrected view:
1. **`veil` — the "no consumer mod" rationale was wrong.** Veil is **embedded inside Sable**
(`embedded` dependency in Sable's own metadata), which is why `iris-veil-compat` exists. The verdict
is still **SKIP**, but the correct reason is *"Veil is already jar-in-jar inside Sable; adding it
standalone is a duplicate/conflict on the fragile render stack"* — not "nothing uses it".
2. **`autohud` — the Fabric-API "red flag" was a metadata artifact.** The README explicitly scopes the
Fabric API requirement to the Fabric build; the NeoForge build is native. Verdict raised from MAYBE
to ADD-OPTIONAL.
3. **`supplementaries` — the Create conflict is stale.** All three cited issues (Create #7405,
Create #9755, Supplementaries #1436) are **closed**, and both mods are now ~18 months newer. Verdict
raised from MAYBE to ADD-OPTIONAL, with a Create smoke test still recommended.
---
## 3. Baseline: what the pack is, and what that implies
From `FINDINGS.md`, `pack.toml`, `index.toml` and the `phase0/` tuning docs, the decision-relevant
facts are:
| Fact | Consequence for candidate screening |
| --- | --- |
| **Shaders: now being added via `mod-pack-additions`** — Iris + shaderpacks + two compat patches (see Section 0); previously none by design. | Iris, `iris-veil-compat` and the shaderpacks are now pack content. Shader-adjacent candidates are re-evaluated in Section 0; sky/fog rewrites now also conflict with the shaderpacks themselves. |
| **Fragile render core:** Sodium `0.8.13` + Distant Horizons + SSRD + Sable's intrusive mixins. Sodium is version-pinned by Sable (≥ 0.8.12-alpha.2). | Any mod that hooks the sky/fog/weather/terrain render path or couples to a Sodium version is test-first at best. |
| **Create + Sable + Aeronautics is the highest-risk component.** Five Sable compat patches already exist. | Create addons and anything touching player/block physics (gradient, ragdolls, tree physics, ships) need a dev-server test. |
| **Client-perf stack is saturated:** Sodium, Lithium, ModernFix, FerriteCore, EntityCulling, ImmediatelyFast, Dynamic FPS, AllTheLeaks, C2ME, Noisium. | Additional generic perf/culling mods have small marginal gains and add mixin surface. |
| **No configs shipped.** `.packwizignore` excludes `config/*` except one FTB Chunks file. | Candidates that require config curation (Burnt, More Music Discs, Lunar, Celestia, Sleep Tight, Worldgen structure tuning) carry a maintenance cost the pack does not currently pay. |
| **Worldgen is curated and already dense:** Terralith + Tectonic + Incendium + Nullscape + Towns and Towers + When Dungeons Arise + 9 YUNG's mods + Explorify + Sparse Structures. | Biome-adding and structure-adding candidates meet a stack that is already at the "loading" end and already uses Sparse Structures to tame it. |
| **Content pillars:** Farmer's Delight (+5 addons), Create, Aquaculture 2, Apotheosis, Sophisticated Storage/Backpacks, Waystones/Gravestone/Lootr, Alex's Mobs/Caves, Ars Nouveau, Twilight Forest. | The best candidates are those that *plug into* a pillar with the same dependencies, not those that start a new one. |
| **~10-player dedicated server matters as much as the client.** | Server-side candidates (`lmd`, PetProtect, claim mods, structure mods, BlueMap) are judged on TPS/ops cost, not just fun. |
| **A dedicated Create Aeronautics compatibility mod is installed** and explicitly patches `Sleep Tight` and `Etched` among others. | Those two candidates have a positive, author-provided compatibility signal for the Sable stack. |
---
## 4. Recommended core — add these
Chosen for **value ÷ risk**: high player-facing value, no hard conflicts, and either zero new
libraries or one small, widely-used library. This is the set I would ship as a first cohort.
| # | Mod | What it adds | Anchor / relationship | Gameplay impact | New libs | Compat notes |
| --- | --- | --- | --- | --- | --- | --- |
| 1 | **Supplementaries** (`supplementaries`) | Large Vanilla+ block set: jars, signposts, faucets, spring launchers, sconces, planters, rope, lights, decoration + light automation | Decoration family (Chipped/Handcrafted/Rechiseled); NeoForge-first project | **Moderate** | **Moonlight Lib** | Old Create 1.21.1 conflicts all closed; smoke-test with Create. Also unlocks Sleep Tight/Haunted Harvest. |
| 2 | **Let Me Despawn** (`lmd`) | Mobs that picked up gear despawn naturally (dropping the gear first) | Server perf next to Lithium/ModernFix; does **not** replace the chunk engine | Minor (server **perf gain**) | **Almanac** | Directly helps a mob-dense Apotheosis/Alex's Mobs server. Check affixed-mob drops. |
| 3 | **Comforts** (`comforts`) | Sleeping bags (no spawn-set) + hammocks (skip day); dyeable | Expedition QoL next to Waystones/Gravestone | Minor | none | 22.9M downloads, current 1.21.1 NeoForge build. |
| 4 | **Traveler's Titles** (`travelers-titles`) | RPG biome/dimension entry titles; Waystones region titles; Twilight Forest built in | Exploration family | Client-cosmetic | none (YUNG's API in pack) | Makes the curated Terralith/Tectonic world legible. |
| 5 | **Zume** (`zume`) | Hold-to-zoom keybind with adjustable zoom levels | Client utility; no zoom mod in pack | Client-cosmetic | none | Zero server footprint. |
| 6 | **FindMe** (`findme`) | Hover an item in JEI, press Y to locate it in nearby inventories | JEI + Sophisticated Storage loop | Minor | none (Architectury in pack) | High-frequency QoL for a storage-heavy pack. |
| 7 | **Sophisticated Item Actions** (`sophisticated-item-actions`) | Keybind-highlight nearby storages holding an item; deposit/restock without opening GUIs | Sophisticated Core/Backpacks/Storage + JEI | Minor | none | Fills a real gap; JEI-aware. |
| 8 | **Chalk** (`chalk-mod`) | Dyeable chalk marks on block faces (glow variants) + chalk box | Navigation next to Waystones/compasses/Ping Wheel | Minor | none | Fills the cave-marking niche nothing else covers. |
| 9 | **PetProtect** (`petprotect`) | Server toggle: tamed mobs can't be hurt/killed by players in PvP | Pet family | Minor (server policy) | none | Cheap grief insurance on a friends server. |
| 10 | **Waystones Teleport Pets** (`waystones-teleport-pets`) | Pets follow you through Waystones without a lead | Direct **Waystones** addon | Minor | none | Server-side. |
| 11 | **Extreme Sound Muffler** (`extreme_sound_muffler`) | Per-sound volume/mute GUI + ranged "anchor" muffling | Client audio control over AmbientSounds + Sound Physics + Create noise | Client-cosmetic | none | Client-only; complements (doesn't replace) Sound Physics Remastered. |
| 12 | **Simple Music Control** (`simple-music-control`) | Halved music delay, next-track key, now-playing widget | Client audio QoL | Client-cosmetic | none (Fzzy Config in pack) | Zero risk. |
| 13 | **Held Item Tooltips** (`held-item-tooltips`) | Info plate on item switch: enchants, durability, effects, shulker previews | Complements Enchantment Descriptions + Legendary Tooltips (different surface) | Client-cosmetic | **Puzzles Lib** | Puzzles Lib also unlocks Visual Workbench / Distinct Potions / Mob Lassos. |
| 14 | **Display Delight** (`display-delight`) | Placeable 3D models of every Delight food (no block entities — lag-friendly), food plates | **All five** installed Delight addons | Minor | none | README lists explicit compat with Ocean's/End's/Aquaculture Delight. |
| 15 | **Fruits Delight** (`fruits-delight`) | 17 fruits, 90 fruit foods, jam/jello cauldrons, 18 plants/14 fruit trees | Farmer's Delight; author-claimed **Create + Terralith** compat | **Moderate** | none | 2.2M downloads; plugs into two existing pillars. |
| 16 | **Rustic Delight** (`rustic-delight`) | Bell peppers, coffee (Speed/Haste), cotton, pancakes, fried foods | Farmer's Delight | Minor | none | Textbook same-dependency addon. |
| 17 | **Cat Jam** (`cat_jam`) | Cats bob to music discs, with per-disc routines | Client charm; **Alex's Mobs** discs explicitly supported | Client-cosmetic | none | Client-only, oldest-but-stable build. |
| 18 | **Death Knell** (`death-knell`) | 35+ situational death messages | Flavor | Client-cosmetic | none (Prickle + Bookshelf in pack) | Zero risk, deps already installed. |
| 19 | **Portfolio** (`portfolio`) | 100+ paintings in 10 themed catalogues | Decoration family | Client-cosmetic | none | Ships as mod or datapack; zero deps. |
| 20 | **FishingOverhaul** (`fishingoverhaul`) | Reel-in fishing minigame with crit regions and per-fish tuning | **Aquaculture 2** (README states compat) | Minor | none | Changes the fishing interaction, not progression. |
**Bundle cost:** 20 mods, 2 new libraries (`Moonlight Lib`, `Almanac`), one of which (`Moonlight Lib`)
unlocks further candidates later. No config maintenance. No server-render risk. Nothing here touches
Sodium, DH, Sable, Create physics, or worldgen.
**Runner-up core picks** (equally safe, pick if the theme appeals): **Sounds** (`sound`, +MRU) for
170+ upgraded SFX; **Particle Effects** and **Perception** for client ambience; **Distinct Potions**
(+Puzzles Lib, already paid for by Held Item Tooltips); **GUI Effect Timer** (+Collective);
**Neat** for mob health bars; **Advancement Plaques** (Iceberg already in pack); **Visual Workbench**
(+Puzzles Lib).
---
## 5. Strong optional additions, by content family
These are all defensible `ADD-OPTIONAL`s. Use them to steer the pack deliberately, not to accumulate.
### 5.1 Farmer's Delight expansion (the pack's most crowded lane)
Farmer's Delight already has 5 addons. Adding more is cumulative, not gap-filling.
| Mod | What it adds | New libs | Notes |
| --- | --- | --- | --- |
| **Expanded Delight** | More crops/workstations; best-covered by Display Delight (37 foods) | none | Author-declared **beta**. |
| **ExtraDelight** | 7 kitchen workstations (oven, chiller, melting pot…), cookware | none | Block-entity heavy; confirm server load. |
| **Miner's Delight** | Cave-food addon (cave carrots, copper pot/cup) | **Lodestone** | Author-declared **beta**. |
| **More Delight** | Sandwiches, toast, stone/wood knives; Create-compatible | **Delight Lib** | Safe, low marginal value. |
| **Dungeon's Delight** | Monster Pot, inverted "monster effects", Rotbulb crop | none | Check effect stacking with Apotheosis. |
| **Barbeque's Delight** | Grill/skewer loop + seasonings | none | Frictionless. |
| **Fright's Delight** | 56 horror foods from mob drops, cursed berries | none | Horror theme is a taste call. |
| **Veggies Delight** | 7 vegetables, temperature-based wild spawns, village Depot Houses | none | Low-risk; village structures are additive. |
| **Hearth and Harvest** | Crops, corn mazes, salt caverns, ranching utilities | none | Corn overlaps Haunted Harvest — pick one. |
| **Haunted Harvest** | Trick-or-treating villagers, pumpkin carving, corn | **Moonlight Lib** | Shares Moonlight with Supplementaries. |
| **Cultural Delights** | Avocado/eggplant/cucumber/corn/beans/cinnamon, world cuisine | **Cook's Collection** | Extra lib for one addon. |
| **Delightful Creators** | Second FD↔Create automation bridge (fluids/soups) | none | **Overlaps the installed Slice & Dice** — deliberate "both routes" decision. |
### 5.2 Create expansion (test every one against Aeronautics/Sable)
| Mod | What it adds | New libs | Notes |
| --- | --- | --- | --- |
| **Create: Bells & Whistles** | 36 deco/utility blocks (platforms, headlights, train grab bars) | none | Decorative; smoke-test on contraptions. |
| **Create: Design n' Decor** | Create-styled deco/variant blocks | none | Active maintenance; low risk. |
| **Create: Teleporters** | TP links, entity/item teleporters, pocket-dimension portal | none | Adds a dimension; independent of worldgen. |
| **Create: Waystones Recipes** | Replaces Waystones recipes with Create machine recipes | none | Progression design decision. |
| **Create: Factory** | Create-themed foods | none | Crowded food lane; low value. |
| **Create: Structures Arise** | 28 Create-themed worldgen structures | none | Thickens an already structure-saturated worldgen stack. |
| **Create: Sophisticated Backpacks Compat** | Third-party recipe pack for SB components | none | Likely partial **recipe overlap** with the installed official integration — check JEI. |
### 5.3 Decoration & building
**Items Displayed**, **Beautiful Campfires**, **Sooty Chimneys**, **Too Many Paintings**, **Spooky Doors**,
**Boatload** (+Blueprint), **Display Case** / **Fetzi's Displays** / **Gallery** (+Blueprint, *pick one
display system*), **Etched** (music discs from URLs — note: **Create Aeronautics Compat explicitly
patches Etched**, a positive signal; content-policy caveat), **Accents** (+Fragmentum).
### 5.4 HUD, client polish & ambience
**Neat** (prefer over **JeremySeq's Damage Indicator** — pick one mob-info mod), **Armor HUD** or
**Immersive Armor HUD** (+TxniLib), **Advancement Plaques**, **AutoHUD**, **GUI Effect Timer** (+Collective),
**Keybind Atlas**, **Blur+** (+MidnightLib), **Distinct Potions** (+Puzzles Lib), **Emojiful**,
**Quick Skin** (prefer over SkinChanger), **Sounds Be Gone**, **Now Playing** (+Deimos for discs),
**Loot Journal** (+Fragmentum), **Particle Effects**, **Perception**, **Smart Particles**,
**Better Animations Collection** (+Puzzles Lib), **Visuality: Reforged**, **Better Third Person**,
**Camera Overhaul**, **Cinematic Respawn**, **Mermod**, **Piggyback** (+Faewulf's Lib),
**Cosy Critters**, **Fancy World Animations** (blacklist safety valve exists).
### 5.5 Mobs, combat & gear
**Fowl Play** (+SmartBrainLib), **Animal Garden – Porcupine** (zero deps), **Hybrid Aquatic**
(+Hybrid API + Biolith; big ocean overhaul — gate on a worldgen/Aquaculture test), **Realm RPG: Fallen
Adventurers** (explicit Twilight Forest support), **Reliquary Reincarnations** (needs a balance pass vs
Apotheosis/Artifacts), **Too Many Bows** (explicitly recommends Better Combat + Curios, both in pack),
**Skillcloaks** (Curios), **TarotCards: Remastered** (team-aware via FTB Teams), **Mob Lassos**
(+Puzzles Lib; check boss exclusion), **Friends&Foes – Flowery Mooblooms** (same author as installed F&F),
**Pet Cemetery** (+Blueprint; verify modded-pet support), **Animal Garden**, **Dream Relics** (balance overlap with Artifacts).
### 5.6 Social & minigames
**Immersive Melodies**, **Charta**, **Mino++**, **Watut** (+CoroUtil), **PlayerRevive** (Gravestone
interplay test), **Incapacitated** (changes core death rules — owner sign-off).
### 5.7 Sleep
**Sleep Tight** (+Moonlight Lib; **explicitly patched by the installed Create Aeronautics Compat**, a
positive signal; adds bedbugs/nightmares), **Room For Two** (beta; pick **one** of these two).
### 5.8 Server / admin
**BlueMap** (web map — ops overhead: port, web server, bStats opt-out; async to the server thread),
**Followers Teleport Too** (+Collective; overlaps Waystones Teleport Pets — pick one),
**Better Compatibility Checker** (only if the pack pipeline will update `bcc-common.toml` each release).
### 5.9 Fishing & food extras
**Fishing Real** (fish spawn as live entities; defaults cover vanilla only — needs datapack JSONs for
Aquaculture species), **Overweight Farming**, **AlcoCraft+**, **Create: Factory**.
---
## 6. Full master table (all 189 candidates)
Verdicts and one-line rationale per candidate. `MAYBE` = test-first or a design decision. The detailed
**Adds / Relationship / Missing deps / Compatibility / Perf** breakdown for every mod is in the
appendix (`analysis/assess/chunk*.md`, consolidated in `analysis/CANDIDATE-APPENDIX.md`). The same
candidates grouped by purpose with a **“what it brings”** column are in **`MODLIST-GROUPS.md`**.
> **(updated)** `iris` and `iris-veil-compat` are now `ADDED` (branch), and `createbetterfps` is now
> `MAYBE` — see Section 0.
### ADD (18)
| Mod | Impact | Rationale |
| --- | --- | --- |
| **cat_jam** (`cat_jam`) | Client-cosmetic | tiny, client-only, documented compat with a mod already in the pack. |
| **Chalk** (`chalk-mod`) | Minor | cheap, self-contained, fills a real gap for cave exploration. |
| **Comforts** (`comforts`) | Minor–Moderate | staple adventure QoL, well-maintained, no dependency cost. |
| **Death Knell** (`death-knell`) | Client-cosmetic | zero-risk flavor whose deps are already installed. |
| **Display Delight** (`display-delight`) | Minor | direct, low-risk upgrade to the pack's food-and-decor identity. |
| **Extreme sound muffler** (`extreme_sound_muffler`) | Client-cosmetic | zero-risk, zero-server-cost QoL that is actively maintained (3.56, 2026-09-22). |
| **FindMe** (`findme`) | Minor | zero-dep, low-risk, high-frequency QoL. |
| **FishingOverhaul** (`fishingoverhaul`) | Minor | declared Aquaculture 2 compat makes this a natural pack fit. |
| **Fruits Delight** (`fruits-delight`) | Moderate | big, popular (2.2M downloads) FD expansion that plugs into two pack pillars (Create, Terralith). |
| **Held Item Tooltips** (`held-item-tooltips`) | Client-cosmetic (informational). | low-risk, high-frequency QoL; library cost shared with other Puzzles-Lib picks. |
| **Let Me Despawn** (`lmd`) | Minor | clear server-side perf win for a 10-player mob-dense pack; add Almanac with it. |
| **PetProtect** (`petprotect`) | Minor | tiny, dependency-free, and directly prevents grief/accident on a 10-player PvP-capable server full of tamed mods' pets. |
| **Portfolio** (`portfolio`) | Client-cosmetic | zero-dependency pure content that fills an empty decorative niche. |
| **Rustic Delight** (`rustic-delight`) | Minor | textbook fit: same-dependency addon for an existing content pillar. |
| **Simple Music Control** (`simple-music-control`) | Client-cosmetic. | zero-risk client QoL whose only dependency is already present. |
| **Traveler's Titles** (`travelers-titles`) | Client-cosmetic. | cheap, polished, and it makes the pack's giant curated worldgen legible to players. |
| **Waystones Teleport Pets** (`waystones-teleport-pets`) | Minor QoL for pet owners. | tiny, targeted, and fixes a real annoyance for a 10-player server full of tamed pets. |
| **Zume** (`zume`) | None (view convenience; no gameplay ch | zero-dep, zero-server, fills an obvious gap next to Sodium+DH distance viewing. |
### ADD-OPTIONAL (88)
| Mod | Impact | Rationale |
| --- | --- | --- |
| **Fisherman's Haven** (`fishermans-haven`) | Moderate | no deps, distinct fishing mechanics that complement Aquaculture 2; accept the niche overlap deliberately. |
| **Accents** (`accents`) | Minor | low-risk cosmetic+minor-stat layer; costs one new library dep. |
| **Advancement Plaques** (`advancement-plaques`) | Client-cosmetic. | safe client polish on a dep already in pack; one test-instance check on the sound caveat. |
| **Akashic Tome** (`akashic-tome`) | None | cheap, low-risk QoL that scales well with a 142-mod pack. |
| **AlcoCraft+** (`alcocraft`) | Minor | small, self-contained fun; no flags. |
| **Animal Garden - Porcupine** (`animal-garden-porcupine`) | Minor | tiny, dependency-free, fits the existing tameable-mob theme. |
| **Armor Hud** (`armor-hud`) | Client-cosmetic. | tiny, dependency-free HUD addition. |
| **Ars Elixirum** (`ars-elixirum`) | Moderate | deep alchemy content with one new library dep; confirm in testing that it doesn't hard-require anything beyond Fragmentum. |
| **Auto HUD** (`autohud`) | Client-cosmetic. | Metadata Fabric-API dep is Fabric-build-only; native NeoForge build. |
| **Barbeque's Delight [Forge/NeoForge]** (`barbeques-delight-forge`) | Minor | frictionless fit with the existing Delight stack. |
| **Beautiful Campfires** (`beautiful-campfires`) | Minor | cheap cosmetic polish; document the oak-only recipe change in pack notes. |
| **Better Animations Collection** (`better-animations-collection`) | Client-cosmetic. | pure atmosphere polish for one new library dep. |
| **Better Third Person** (`better-third-person`) | Client-cosmetic. | safe, popular camera QoL with no deps; just smoke-test it while piloting Aeronautics contraptions. |
| **BlueMap** (`bluemap`) | None | genuinely useful for a small dedicated server, but it's ops overhead (webserver, ports, configs) rather than content. |
| **Blur+** (`blur-plus`) | Client-cosmetic | safe cosmetic; only bother if the pack wants GUI polish. |
| **Boatload** (`boatload`) | Minor | pleasant water QoL, but chest-boat overlap with vanilla makes it low-value; needs Blueprint. |
| **Burnt** (`burnt-basic`) | Moderate | distinctive survival flavor with controls, but server-grief/tick cost means it's a taste call, not a default. |
| **Camera Overhaul** (`cameraoverhaul`) | Client-cosmetic | cheap and configurable, entirely a taste/accessibility call. |
| **Certain Questing Additions** (`certain-questing-additions`) | None | nice polish for a quested pack; harmless if dropped. |
| **Charta** (`charta`) | Minor | charming multiplayer flavor, zero risk if unused. |
| **Cinematic Respawn** (`cinematic-respawn`) | Client-cosmetic | pleasant, zero-risk client polish; taste call on auto-respawn. |
| **Cosy Critters & Creepy Crawlies** (`cosy-critters`) | Client-cosmetic | cheap atmosphere for explorers; skip if the ambience budget is full. |
| **Create Waystones Recipes** (`create-waystones-recipes`) | Minor | safe; a deliberate progression/taste decision for the questbook rather than a technical one. |
| **Create: Bells & Whistles** (`bellsandwhistles`) | Minor | strong deco value for a Create pack; verify blocks behave on Aeronautics contraptions first. |
| **Create: Design n' Decor** (`create-design-n-decor`) | Minor | safe factory decoration; nice with the pack's Chipped/Rechiseled angle. |
| **Create: Factory** (`create-factory`) | Minor | harmless, but the food family is already the pack's most crowded content lane; low marginal value. |
| **Create: Structures Arise** (`create-structures-arise`) | Moderate | strong fit for a Create + exploration pack, but it thickens an already structure-saturated worldgen stack with no config knobs shipped. |
| **Create: Teleporters** (`create-teleporters`) | Moderate | clean fit for Create logistics with no flagged conflicts; decide whether the pack wants dimensional play. |
| **Cultural Delights** (`cultural-delights`) | Moderate | on-theme food content, but it requires adopting Cook's Collection and the Delight shelf is already crowded. |
| **Death Count** (`death-count`) | Minor | harmless novelty for a 10-player server, though equally achievable with a one-line vanilla scoreboard datapack. |
| **Distinct Potions** (`distinct-potions`) | Client-cosmetic | cheap visual QoL; only worth it if the pack accepts pulling in Puzzles Lib for a single client mod. |
| **Dungeon's Delight** (`dungeons_delight`) | Moderate | thematically perfect next to the pack's existing Delight collection; sanity-check the new effect types against Apotheosis stacking. |
| **Dynamic Crosshair** (`dynamiccrosshair`) | Client-cosmetic | pure client polish, but must be tested against ImmediatelyFast and dropped if HUD glitches appear. |
| **Emojiful** (`emojiful`) | Client-cosmetic | fun for a 10-player social server with zero gameplay footprint. |
| **Etched** (`etched`) | Minor | great social toy for a small server; settle the URL-content policy first. |
| **Expanded Delight** (`expanded-delight`) | Moderate | strongly on-theme and the best-covered addon for Display Delight, but accept the author-declared beta instability or wait for stable. |
| **ExtraDelight** (`extradelight`) | Moderate | the meatiest Delight addon in the batch and a natural kitchen-tier progression; confirm the block-entity load on the dedicated server before committing. |
| **Fancy World Animations [FWA]** (`fwa`) | Client-cosmetic | strong client polish with a blacklist safety valve; verify against Sable/ImmediatelyFast rendering before shipping. |
| **Farmer's Knives** (`farmers-knives`) | Minor | cheap thematic glue whose payoff is real but limited to Twilight Forest materials since the other supported tool mods aren't in the pack. |
| **Field Guide** (`field-guide`) | Minor | excellent fit for a 10-player exploration pack; price of admission is two extra NeoForge libs. |
| **Fishing Real** (`fishing-real`) | Minor | cheap, fun, low-risk; worth a small datapack pass to include Aquaculture species. |
| **Followers Teleport Too** (`followers-teleport-too`) | Minor | clear QoL win with Waystones for ~10 players; costs one shared lib. |
| **Fowl Play** (`fowlplay`) | Minor | strong ambience fit for the pack's mob roster; costs one AI lib. |
| **Friends&Foes - Flowery Mooblooms (Forge/NeoForge)** (`friends-and-foes-flowery-mooblooms-forge`) | Minor | cheap flavor that slots into an installed mod; skip only if moobloom density is unwanted. |
| **Fright's Delight** (`frights-delight`) | Minor | fits the pack's Delight collection; horror theme is a taste call. |
| **GUI Effect Timer** (`gui-effect-timer`) | Client-cosmetic (informational). | tiny, safe, useful; effectively free once Collective is pinned for Followers Teleport Too. |
| **Hearth and Harvest** (`hearth-and-harvest`) | Moderate | solid FD-family expansion; coordinate corn overlap with Haunted Harvest before adding both. |
| **Hybrid Aquatic** (`hybrid-aquatic`) | Major | biggest content win in this batch; gate on a worldgen + Aquaculture overlap test because of the two new library pins. |
| **Immersive Armor HUD** (`immersive-armor-hud`) | Client-cosmetic. | cheap vanilla-style HUD upgrade; minor recency caveat. |
| **Immersive Melodies** (`immersive-melodies`) | Minor | fun multiplayer toy at essentially zero mechanical risk. |
| **Items Displayed [NeoForge]** (`items-displayed-forge`) | Minor | safe builder content that fits the pack's decoration mods. |
| **Keybind Atlas** (`keybind-atlas`) | None | genuinely useful in a pack this size, zero risk. |
| **Loot Journal: Pickup Notifier** (`loot-journal`) | Client-cosmetic | nice HUD polish; in-game config means no shipped-config maintenance. |
| **Mermod** (`mermod`) | Minor | small, cheap, fits the pack's exploration theme; low information confidence due to thin README. |
| **Miner's Delight** (`miners-delight`) | Minor | strong thematic fit with the Delight family, but the author's own beta warning plus a new library dep argue for wait-and-see. |
| **Mino++** (`minopp`) | Minor | charming social content with no risk surface; pure taste call. |
| **Mob Lassos** (`mob-lassos`) | Minor-Moderate | useful QoL; check boss exclusion config before shipping to a pack with Apotheosis. |
| **More Delight (for Farmer's Delight)** (`more-delight`) | Minor | safe, on-theme content; the Delight family is already large, so it's cumulative rather than filling a gap. |
| **More Music Discs** (`more-music-discs`) | None-Minor | cheap flavor; verify default disc-in-every-loot-pool density is wanted. |
| **Mystical Oak Tree** (`mystical-oak-tree`) | Minor | harmless flavor for a friends pack; nothing about it strains the stack. |
| **Neat** (`neat`) | Client-cosmetic | mature, zero-risk combat readability; prefer over the Damage Indicator if choosing one. |
| **Now Playing** (`now-playing`) | None | trivial, safe; most valuable if music-content mods are added. |
| **Overweight Farming** (`overweight-farming`) | Minor | small, self-contained farming charm. |
| **Particle Effects** (`particle-effects`) | Client-cosmetic | pure client polish, zero server footprint. |
| **Perception** (`perception`) | Client-cosmetic | safe client ambience; optional enough that individual players could skip it. |
| **Pet Cemetery** (`pet-cemetery`) | Minor | emotionally valuable on a friends server; verify modded-pet support first. |
| **Piggyback** (`piggyback`) | Minor | harmless co-op fun, but requires adding Faewulf's Lib to the pack. |
| **PlayerRevive** (`playerrevive`) | Moderate | strong social fit, but it rebalances death and needs a Gravestone interplay check. |
| **Quick Skin** (`quick-skin`) | Client-cosmetic | client nicety; preferred over SkinChanger (newer, GUI, more features). |
| **Realm RPG: Fallen Adventurers** (`realm-rpg-fallen-adventurers`) | Minor | easy exploration flavor; note the 1.21.1 build dates to 2025-01 (aging but stable, 2.3M downloads). |
| **Reliquary Reincarnations** (`reliquary-reincarnations`) | Moderate | content-rich and maintained, but demands a balance pass against Apotheosis/Artifacts. |
| **Room For Two** (`room-for-two`) | Minor | harmless fun at BETA maturity; pick it OR Sleep Tight's ecosystem, and test if both. |
| **Skillcloaks** (`skillcloaks`) | Minor | solid Curios content on an existing dependency; consider adding Lucent for the full feature set. |
| **Sleep Tight** (`sleep-tight`) | Moderate | popular, well-scoped sleep rework; requires adding Moonlight Lib and choosing between it and Room For Two rather than stacking blindly. |
| **Smart Particles** (`smart-particles`) | Client-cosmetic | targeted client-side stabilizer with no server footprint; watch mixin overlap with ImmediatelyFast. |
| **Sooty Chimneys** (`sooty-chimneys`) | Minor | safe, charming builder content with negligible cost. |
| **Sophisticated Item Actions** (`sophisticated-item-actions`) | Minor (inventory-flow QoL, Terraria-st | genuinely new capability that plays well with the Sophisticated + JEI stack already in the pack. |
| **Sounds** (`sound`) | Client-cosmetic. | high polish, zero server footprint; costs one new small library. |
| **Sounds Be Gone!** (`soundsbegone`) | Client-cosmetic (no gameplay change; p | tiny, safe, real accessibility value for a 10-player group. |
| **Spooky Doors** (`spooky-doors`) | Minor | small, charming, Balm-based and low-risk. |
| **Supplementaries** (`supplementaries`) | Moderate | Create conflict issues all closed; NeoForge-first. Needs Moonlight Lib; smoke-test. |
| **TarotCards: Remastered** (`tarotcards-remastered`) | Minor | clean fit for an exploration/loot pack; explicitly team-aware. |
| **Too Many Bows** (`too-many-bows`) | Moderate | strong synergy with Better Combat and Curios already in the pack. |
| **Too Many Paintings!** (`too-many-paintings`) | Client-cosmetic (placed paintings are | zero-dep cosmetic content, safe on both sides. |
| **Veggies Delight** (`veggies-delight`) | Minor | low-risk content that deepens the pack's largest content family. |
| **Visual Workbench** (`visual-workbench`) | Minor | popular, polished Fuzs mod; costs one small library. |
| **Visuality: Reforged** (`visuality-forge`) | Client-cosmetic. | safe client-only polish; consider whether Subtle Effects already covers the need. |
| **What Are They Up To (Watut)** (`what-are-they-up-to`) | Minor | genuinely fun on a small friend group; costs one small library. |
### MAYBE (36)
| Mod | Impact | Rationale |
| --- | --- | --- |
| **Better Compatibility Checker** (`better-compatibility-checker`) | None. | only earns its slot if the build pipeline commits to updating its config each release. |
| **Burnt + Additions** (`burnt`) | Moderate | the fuller variant is attractive but heavier; if fire flavor is wanted, choose this over basic deliberately (and still treat as optional). |
| **Celestria** (`celestria`) | Minor | nice ambience, but stale build + WorldRenderer mixin on the Sodium/DH/Sable stack needs an in-pack smoke test first. |
| **Companions!** (`companions-mod`) | Moderate | decent content but redundant with the existing mob roster and it drags in a new library; only if the pack wants more combat-pet content. |
| **Create: Estrogen** (`create-estrogen`) | Moderate | can't be evaluated in isolation (dep is a batch candidate), self-declared in-development, and tone may not fit the pack; decide together with the Estrogen entry. |
| **Create: Sophisticated Backpacks Compat** (`create-sophisticated-backpacks-compat`) | Minor | plausible value, but check for recipe collisions with the official Create Integration already in the pack before committing. |
| **CreateBetterFps** (`createbetterfps`) | None | Iris + Sodium now in pack, so unblocked in principle, but it patches Create's code on the Aeronautics/Sable stack - test first. |
| **Delightful Creators** (`delightful-creators`) | Moderate | pick one FD↔Create bridge; Slice & Dice is already installed and proven, so this needs a deliberate "both approaches" decision. |
| **Display Case** (`displaycase`) | Client-cosmetic | fine in isolation, but choose between this and Fetzi's Displays (same batch) rather than shipping both display systems. |
| **Dream Relics** (`dream-relics`) | Moderate | duplicate niche vs Artifacts with Apotheosis balance implications; needs a curation pass before adding. |
| **Effectual** (`effectual`) | Client-cosmetic | duplicates Subtle Effects' niche and drags in a new lib; only add after an overlap audit, which the config-less pack model makes costly. |
| **Environmental** (`environmental`) | Moderate | its content substantially duplicates what Terralith + Alex's Mobs already deliver, and it requires pulling in Blueprint for a single mod. |
| **Fetzi's Displays** (`fetzis-displays`) | Client-cosmetic | good feature set but redundant with Display Case (batch) + Handcrafted; pick one display system for the pack. |
| **Fog** (`fog`) | Client-cosmetic. | attractive but unproven next to DH/SSRD; only add after an in-game fog + DH visual test. |
| **Gallery** (`gallery`) | Client-cosmetic (decorator QoL). | nice decorator QoL but drags in the Blueprint library for a niche feature. |
| **Grassier Grass** (`grassier-grass`) | Client-cosmetic. | best-looking pick in the batch, but Sodium 0.8-alpha compatibility is unproven; gate on a crash/visual test. |
| **GUI Compass** (`gui-compass`) | Client-cosmetic. | harmless, but marginal over F3 and redundant if Field Guide (with Immersive Overlays) is added. |
| **Haunted Harvest** (`haunted-harvest`) | Minor | charming, but adds a lib, modifies villager AI, and double-buys corn with Hearth and Harvest. |
| **Health Bars** (`new-health-bars`) | Client-cosmetic (informational). | redundant with Jade unless the group wants damage numbers; if added, de-overlap with Jade deliberately. |
| **Incapacitated** (`incapacitated`) | Major | great co-op mechanic but changes core death rules; needs owner sign-off plus a Gravestone interaction test. |
| **Inventory Particles** (`inventory-particles`) | Client-cosmetic. | pretty and unusually modded-item-friendly; fine as a client-side optional, but pure cosmetics. |
| **JeremySeq's Damage Indicator** (`jeremyseqs-damage-indicator`) | Client-cosmetic | harmless, but functionally redundant with Neat if both are added; pick one combat-info mod. |
| **Lunar** (`lunar`) | Moderate | fun server-wide content, but gravity/spawn events touching Aeronautics physics is an unquantified risk in this pack. |
| **More Culling** (`moreculling`) | None | plausible FPS gain, but redundant-adjacent to Entity Culling and unvetted against the pack's fragile rendering core; test in isolation or skip. |
| **Obscure Tooltips** (`obscure-tooltips`) | None | attractive but redundant with Legendary Tooltips; only add after confirming they don't fight over tooltip rendering, or as a replacement. |
| **Primal** (`mod-primal`) | unknown | sparse documentation; inspect the wiki and jar contents before committing. |
| **Punchy! x Tiny Takeover** (`tiny-takeover-x-punchy`) | Minor | verify whether Punchy/Tiny Takeover base mods are actually required and that the NeoForge build is real before adding. |
| **Ragdoll Reactions** (`ragdoll-reactions`) | Minor | only alongside Sable: Ragdolls, and only after crash-testing with Aeronautics. |
| **Sable Beyond** (`sable_beyond`) | Moderate | the most functionally attractive Aeronautics enhancement in the batch, but alpha software on the pack's most fragile stack; isolated testing and backups are mandatory. |
| **Sable: Ragdolls** (`sable-ragdolls`) | Minor | fun, Sable-native, and unlocks two other candidates; test with Aeronautics before adopting. |
| **Shine** (`shine`) | Client-cosmetic. | confirmed Sodium 0.8.12 compatibility, but partial feature loss on that version, overlap with Subtle Effects, and a privileged position on the fragile rendering stack. |
| **Small Ships** (`small-ships`) | Moderate | appealing content, but a beta build with old updates parked next to Aeronautics/Sable physics; needs a dedicated compat test or should pass. |
| **Sophisticated Inventory Interactions** (`sophisticated-inventory-interactions`) | Minor (pure inventory-management QoL). | good mod, but it replaces rather than stacks with Client Sort; pick one container-sorting solution. |
| **Spawn** (`spawn-mod`) | Major | substantial content but collides with the pack's curated worldgen and mob roster; needs Teal Lib and dedicated testing before inclusion. |
| **Spawn Animations** (`spawn-animations`) | None | mild flourish; if wanted, use the datapack form to dodge the Fabric dep chain rather than the mod. |
| **Tree Physics** (`tree-physics`) | Moderate | the most on-theme Sable use possible, but per-tree physics sub-levels on the Aeronautics stack demand a dev-server soak test before shipping. |
### SKIP (36)
| Mod | Impact | Rationale |
| --- | --- | --- |
| **AAA Particles** (`aaa-particles`) | Client-cosmetic. | a library with no in-pack consumer; standalone value is only a lightning visual swap. |
| **AAA Particles: World** (`aaa-particles-world`) | Client-cosmetic. | purely cosmetic addon whose required base (AAA Particles) is itself a skip. |
| **Abnormals Delight** (`abnormals-delight`) | Minor (and in this pack, near-zero). | required Blueprint dep plus an ecosystem (Team Abnormals) the pack doesn't run; mostly dead content. |
| **Alex's Mobs Continued** (`alexs-mobs-continued`) | Overhaul (if it *replaced* the existin | direct duplicate of an existing pack mod; would only ever be a 1:1 swap project. |
| **Ars Ocultas** (`ars-ocultas`) | Moderate if Occultism were present; in | required dependency Occultism is absent from both the pack and this batch. |
| **AsyncParticles** (`asyncparticles`) | None (pure client optimization). | a named crash interaction with in-pack C2ME that the pack cannot pre-configure, plus beta render-layer mixins against the fragile Sable/Create stack; revisit only if particle load measurably hurts cli |
| **ATMOSPHERICS** (`atmospherics`) | Client-cosmetic. | visual upside not worth the risk on the Sodium+DH+SSRD stack; no shader-adjacent additions should ride on that axis per pack rules. |
| **BadOptimizations** (`badoptimizations`) | None. | marginal, hard-to-measure gains on a stack that already covers these areas (constraint 6); not worth extra mixin surface for a ~4% synthetic-FPS delta per the author's own screenshots. |
| **Berry Good** (`berry-good`) | Minor. | an entire additional library dependency (Blueprint) for one minor placement QoL; revisit only if the pack adopts Team Abnormals mods wholesale. |
| **Carry On + Create Aeronautics Compat** (`carryon-aeronautics-compat`) | Moderate (when it applied); effectivel | self-declared obsolete (fix upstreamed to Carry On 2.2.6+), base mod absent, documented server-hang known issue. |
| **Carry On Extend** (`carry-on-extend`) | Minor | dead on arrival without Carry On; revisit only if the pack adopts Carry On itself. |
| **Chef's Delight - Farmer's Delight Villagers** (`chefs-delight`) | Minor (if it worked) | 1.21.1 build is bound to FD Refabricated, which conflicts with the pack's existing Farmer's Delight. |
| **Create Sable Dynamic Lights** (`create-sable-dynamic-lights`) | Client-cosmetic | requires a second dynamic-lights engine that duplicates LambDynamicLights and couples to the fragile Sodium/Sable version pin. |
| **Create: Dynamic Lights** (`create-dynamic-lights`) | Client-cosmetic | redundant with LambDynamicLights, wrong rendering stack assumptions (Embeddium/Rubidium), and its niche is better covered (or deliberately declined) elsewhere. |
| **Dynamic Trees** (`dynamictrees`) | Major | a transformative tree overhaul whose coverage against the pack's 10+ worldgen mods is unverified and which demands config curation the pack explicitly does not ship. |
| **Early Bedtime** (`early-bedtime`) | Minor | solves a shader-specific problem this shaderless pack doesn't have, at the cost of adopting TxniLib. |
| **Estrogen** (`estrogen`) | Moderate | meme-toned content whose headline feature alters player movement near a fragile physics stack, and which costs two extra pieces (Cynosure + separate Create module) for weak fit. |
| **Even More Instruments!** (`evenmoreinstruments`) | Minor | abandoned per its own README and requires Genshin Instruments, which the pack does not have. |
| **Horror messages** (`horror-messages`) | None | chat-spam gimmick; only defensible if the pack deliberately becomes a horror pack. |
| **Immersive UI** (`immersive-ui`) | Client-cosmetic. | two extra libraries for cosmetic overlap with Inventory Particles; poor value per pin. |
| **Immersive Winds** (`immersive-winds`) | Client-cosmetic. | Sinytra-only NeoForge support is a red flag by pack policy, and Grassier Grass already covers sway if that's added. |
| **More Relics** (`more-relics`) | Moderate | needs a missing base mod + two libraries, carries self-documented version-pin fragility, and duplicates the Artifacts niche the pack already covers. |
| **Net Music: Better Experience** (`net-music-play-list`) | None-Minor | dependent on an absent streaming-music base mod with service/licensing baggage; poor fit. |
| **Open Parties and Claims** (`open-parties-and-claims`) | Overhaul of base-protection/social sys | direct duplicate of FTB Chunks + FTB Teams; would only make sense as a wholesale replacement, which contradicts the installed FTB Quests/Chunks/Teams chain. |
| **Pixelmon** (`pixelmon`) | Overhaul | total-conversion-scale content that steamrolls the pack's identity, worldgen, and mob budget. |
| **Puddles & Floods** (`puddles-floods`) | Client-cosmetic | Fabric-API-derived dependency (red flag) for a cosmetic payoff with real server tick cost; revisit only if the pack accepts Forgified Fabric API. |
| **Refined Storage - JEI Integration** (`refined-storage-jei-integration`) | None in this pack | dead weight without Refined Storage. |
| **Relics: Arcane Abilities** (`arcane-abilities`) | Moderate | its own README requires Relics + Hammer Lib (missing); would need both plus a balance review to reconsider. |
| **Sable: Ragdoll Corpse** (`sable-ragdoll-corpse`) | Minor | Gravestone already owns this job; stacking two death-item systems on the Sable physics stack adds fragility for no new capability. |
| **SkinChanger** (`skinchanger`) | Client-cosmetic. | redundant with Quick Skin (GUI, HD skins, animated capes) and stale: last updated 2024-10-26 vs. Quick Skin's 2026-09-28. |
| **Sophisticated Sorter** (`sophisticated-sorter`) | Minor. | redundant with Client Sort in-pack and with Sophisticated Inventory Interactions in this same batch; side metadata is also "unknown" for both sides. |
| **Spawn Animations Compats** (`spawn-animations-compats`) | None. | only meaningful if Spawn Animations is added, and its supported mob list barely overlaps this pack. |
| **Tooltip Overhaul** (`tooltip-overhaul`) | Client-cosmetic. | collides with Legendary Tooltips; the pack should carry one tooltip restyler, and it already does. |
| **Veil** (`veil`) | None by itself | rendering-engine tooling with no dependent mod in the pack, on the most fragile part of the stack. |
| **VinURL** (`vinurl`) | Minor (custom music per player). | Connector red flag, runtime binary downloads, and licensing minefield; not worth it for jukebox music. |
| **Welcome Screen** (`welcome-screen`) | None. | 2-3 new dependencies for a screen a server MOTD/pin note can replace. |
### ALREADY-IN-PACK (9)
| Mod | Impact | Rationale |
| --- | --- | --- |
| **Apothic Combat** (`apothic-combat`) | Major (as installed) | duplicates the pack's existing Apothic Combat entry. |
| **Aquaculture 2** (`aquaculture`) | Moderate (as installed). | exact duplicate of the existing Aquaculture 2 entry. |
| **Aquaculture Delight** (`aquaculture-delight`) | Minor (as installed). | exact duplicate of the existing Aquaculture Delight entry. |
| **Ars Creo** (`ars-creo`) | Moderate (as installed). | duplicate of the existing Ars Creo entry; the "missing dep" line is a dead-ID artifact, not a real gap. |
| **Create Aeronautics: Compatibility** (`create-aeronautics-compatability`) | Moderate | duplicates the existing "Create Aeronautics: Compatibility" entry in the pack list; do not add a second copy. |
| **Nature's Compass** (`natures-compass`) | N/A | byte-for-byte duplicate of the existing Nature's Compass entry; drop from the candidate list. |
| **Separate Sable Render Distance** (`ssrd`) | None | duplicate of the pack's existing SSRD entry; exclude from the candidate list. |
| **Sophisticated Backpacks Create Integration** (`sophisticated-backpacks-create-integration`) | Minor (already shipped; storage automa | exact duplicate of the pack's own Sophisticated Backpacks Create Integration entry. |
| **Sophisticated Storage Create Integration** (`sophisticated-storage-create-integration`) | Minor (already shipped). | exact duplicate of the pack's own Sophisticated Storage Create Integration entry. |
### ADDED (2)
| Mod | Impact | Rationale |
| --- | --- | --- |
| **Iris Shaders** (`iris`) | Overhaul | Already added on branch mod-pack-additions (see Post-review update). |
| **Iris Veil Compat** (`iris-veil-compat`) | None | Already added on branch mod-pack-additions (see Post-review update). |
## 7. Skip list — the 36 I would not add, by reason
### 7.1 Render-stack violations (updated)
**`Iris Shaders` and `Iris Veil Compat` are no longer skips — they are now `ADDED` on the
`mod-pack-additions` branch (Section 0).** The entries below remain rejected.
| Mod | Reason |
| --- | --- |
| **ATMOSPHERICS** | Rewrites sky/fog/cloud rendering. It already clashed with the Sodium + DH + SSRD axis and warned about Sodium *"Improved Transparency"*; now it also fights the shaderpacks' own sky/fog. |
| **Veil** | Already **embedded inside Sable**; adding it standalone duplicates the render engine. |
| **Fog** | Fog/sky coverage the newly added shaderpacks already provide; Distant Horizons compatibility is an open upstream question. |
**(updated)** The previously listed `Iris Shaders`, `Iris Veil Compat` and `CreateBetterFps` moved:
`iris`/`iris-veil-compat` to **ADDED**; `CreateBetterFps` to **MAYBE** (its blocker was *"1.21.1 needs
Sodium and Iris"*, and Iris is now present, but it patches Create's code on the Aeronautics/Sable stack).
`Shine` and `Grassier Grass` stay **MAYBE**; both are Sodium-coupled and now also sit next to a real
shader pipeline.
### 7.2 Fabric-only / Sinytra Connector dependency (hard)
| Mod | Reason |
| --- | --- |
| **Immersive Winds** | README: *"NEOFORGE REQUIRES SINYTRA TO RUN, ALL MOD FILES ARE FABRIC."* |
| **VinURL** | Sinytra + Forgified Fabric API + owo-lib, **and** it auto-downloads and executes `yt-dlp`/`deno`/`FFmpeg` at runtime — supply-chain and licensing exposure on the server. |
| **Puddles & Floods** | Requires Forgified Fabric API for a cosmetic puddle effect with real rain-driven tick cost. |
### 7.3 Dead dependency / missing base mod (hard)
| Mod | Reason |
| --- | --- |
| **Ars Ocultas** | Requires **Occultism**, absent from pack and list. |
| **Carry On Extend** | Requires **Carry On**, absent. |
| **Carry On + Create Aeronautics Compat** | Author-declared **obsolete** (fix upstreamed to Carry On 2.2.6+); base absent; documented server-hang on picking up players onto contraptions. |
| **Net Music: Better Experience** | Requires **Net Music**, an external NetEase streaming service with login/licensing baggage. |
| **Refined Storage – JEI Integration** | Requires **Refined Storage**, absent — the pack uses Sophisticated Storage. |
| **Relics: Arcane Abilities** | README requires **Relics + Hammer Lib** (contradicting its metadata); both absent. |
| **Even More Instruments!** | Author has **retired** the project; requires **Genshin Instruments**, absent. |
| **AAA Particles / AAA Particles: World** | A library with no consumer mod in this pack; the addon depends on the skipped base. |
| **Spawn Animations Compats** | Only meaningful with Spawn Animations, and its supported mob list barely overlaps this pack (Alex's Mobs is not on it). |
### 7.4 Duplication — the pack already owns this (hard)
| Mod | Duplicates |
| --- | --- |
| **Alex's Mobs Continued** | The installed **Alex's Mobs (Unofficial Port)** — same mod/ID namespace; only a 1:1 swap is possible. |
| **Open Parties and Claims** | **FTB Chunks + FTB Teams** (and FTB Quests assumes that ecosystem). Two claim systems = split protection logic. |
| **Sable: Ragdoll Corpse** | **Gravestone** (which already ships Sable + Curios compat) — two death-item systems on the physics stack. |
| **SkinChanger** | **Quick Skin** (newer, GUI, HD skins); SkinChanger is stale (2024-10). |
| **Sophisticated Sorter** | **Client Sort** (installed). Three-way overlap with Sophisticated Inventory Interactions. |
| **Tooltip Overhaul** | **Legendary Tooltips** — the pack should carry one tooltip restyler. |
| **Create: Dynamic Lights** / **Create Sable Dynamic Lights** | **LambDynamicLights** (installed) — a second dynamic-lights engine; both also assume Embeddium/Rubidium or Sodium Dynamic Lights and recommend the banned shader stack. |
| **Chef's Delight** | Its 1.21.1 builds target **Farmer's Delight Refabricated**, a colliding reimplementation of the installed Farmer's Delight. |
### 7.5 Wrong shape for this pack — scale, worldgen, or marginal value
| Mod | Reason |
| --- | --- |
| **Pixelmon** | Total-conversion scale (1000+ entities, its own structures/economy); its README recommends a dedicated Pixelmon pack. Would steamroll the pack identity, worldgen and mob budget. |
| **Dynamic Trees** | Transformative tree overhaul with unverified coverage across 10+ worldgen mods and no config shipped. |
| **Estrogen / Create: Estrogen** | Meme content; movement-dash mechanic near the Sable physics stack; needs Cynosure + the Create module. |
| **More Relics** | Needs the absent Relics base + two libs; self-documented version-pin fragility; duplicates the Artifacts niche. |
| **AsyncParticles** | Documents a C2ME crash (in-pack) that needs a config edit; beta render-layer mixins against the Sable/Create axis. A candidate for a later test, not now. |
| **BadOptimizations** | Marginal gains on an already-saturated client perf stack; author's own screenshots show ~4%. |
| **Welcome Screen** | Three new libraries (FancyMenu + Necronomicon + Better Compatibility Checker) for a cosmetic first-join screen. |
| **Horror messages** | Chat-spam gimmick; tone mismatch with the pack. |
| **Immersive UI** | Two libraries for cosmetic overlap with Inventory Particles. |
| **Early Bedtime** | Solves a shader-only darkness problem this pack does not have; costs TxniLib. |
| **Abnormals Delight / Berry Good** | Require **Blueprint** for content that is inert (no Team Abnormals mods) or tiny. |
| **Sophisticated Sorter / SkinChanger** | (See duplication above.) |
---
## 8. Swap candidates — is replacing an installed mod justified?
Yes, for a small number of pairs — but the bar is higher than "the new one is shinier". A swap is only
worth it when the candidate is **(a) strictly more capable or better maintained, (b) no worse against
the fragile stack** (Sodium/DH/SSRD/Sable/Create), and **(c) safe for existing worlds** (entity/item
IDs, already-generated chunks, claims, quest data). Otherwise the incumbent wins by default.
The newly fetched branch is itself an example of a justified swap: the pack's "no shaders" stance was
replaced only because purpose-built compatibility patches (`iris-veil-compat`, `aeronautics-iris-fix`)
neutralised the specific blocker.
| Installed mod | Candidate replacement | Case for swapping | Case against | Recommendation |
| --- | --- | --- | --- | --- |
| **Alex's Mobs (Unofficial Port)** | **Alex's Mobs Continued** | Actively-maintained continuation, same content, deliberately Citadel-free (bundles what it needs); removes reliance on an unofficial port. | Two forks of one mod: entity/item IDs and NBT may differ, so an in-place swap can orphan tamed mobs/items. Citadel still needed by Alex's Caves. | **Evaluate on a world copy.** Best maintenance swap on the list, not urgent. |
| **Client Sort** | **Sophisticated Inventory Interactions** | Built on Sophisticated Core (installed); adds GUI search, container↔player transfer, remembered searches; deepens an existing pillar. | Newer/less proven; keybind and sort-button overlap; sorts containers, not the whole UI. | **Plausible upgrade — test UI collisions.** Do not also add Sophisticated Sorter. |
| **Legendary Tooltips** | **Obscure Tooltips** | Animated styling plus in-tooltip 3D gear previews — a real capability gain. | Adds Fragmentum; per-frame cost; coexistence unverified, so it must be a *swap*, never a stack. | **Swap-or-keep, never both.** |
| **LambDynamicLights** | **Sodium Dynamic Lights + Create Sable Dynamic Lights** | Would light moving Create contraptions and Aeronautics sub-levels, which LambDynamicLights does not. | SDL is coupled to the exact Sodium alpha Sable pins, and the Create mods assume Embeddium/Rubidium or the shader ecosystem. Trades a stable engine-agnostic mod for a version-fragile one. | **Do not swap now.** Revisit if contraption lighting becomes a pain point. |
| **Gravestone (+ Sable/Curios compat)** | **Sable: Ragdoll Corpse** | Novel physics-corpse recovery, Sable-native. | Gravestone is proven and already Sable/Curios-patched; two death-item systems risk item-loss precedence bugs on the physics stack. | **Keep Gravestone.** |
| **FTB Chunks + FTB Teams** | **Open Parties and Claims** | More granular party/claim permissions; LuckPerms/FTB Ranks hooks. | Wholesale replacement breaks FTB Quests/Teams and FTB's permission model; two claim systems cannot coexist. | **Keep FTB.** |
| **Client Sort** | **Sophisticated Sorter** | Sophisticated styling. | Less proven, unknown side metadata, duplicates Client Sort *and* Sophisticated Inventory Interactions. | **No** — if replacing, choose Sophisticated Inventory Interactions. |
| **Slice & Dice** | **Delightful Creators** | Different FD↔Create automation flavor (fluids/soups vs sequenced assembly). | Slice & Dice is installed and proven; running both duplicates recipes and clutters JEI. | **Only as a deliberate flavor swap**, never alongside. |
| **EntityCulling** | **More Culling** | — | Different scope (entities vs block/other culling); complementary, not alternatives. | **Not a swap.** |
| *(no skin mod installed)* | **SkinChanger vs Quick Skin** | — | — | If adding one, **Quick Skin** (newer, GUI, HD skins, animated capes). |
**Swap mechanics note:** packwiz makes the file change trivial, but the risk lives in the world. For any
swap that touches registered content (Alex's Mobs, dynamic lights, death handling), test on a **copy of
the live world** and diff entity/item IDs. Structural swaps (claim system, sorting UI, tooltip layer)
mostly affect client UI and are lower risk, but still change player muscle memory.
---
## 9. Dependency economics and the collision map
### 9.1 New libraries the candidates would introduce
Ordered by how many candidates each unlocks. **Bold** = recommended if you adopt that family.
| Library | Unlocks | Recommendation |
| --- | --- | --- |
| **Puzzles Lib** | Held Item Tooltips, Visual Workbench, Distinct Potions, Health Bars, Mob Lassos, Better Animations Collection | Buy once — high value |
| **Moonlight Lib** | Supplementaries, Sleep Tight, Haunted Harvest | Buy once — high value; also a Supplementaries prerequisite |
| Blueprint | Environmental, Pet Cemetery, Gallery, Boatload, Berry Good, Abnormals Delight | Only if you adopt several |
| Fragmentum | Accents, Ars Elixirum, Obscure Tooltips, Loot Journal | Only if you adopt several |
| Collective | Followers Teleport Too, GUI Compass, GUI Effect Timer | Cheap; bundle or skip |
| **Almanac** | Let Me Despawn | Buy (core pick) |
| MRU | Sounds, Fog | Cheap; pairs well |
| TxniLib | Early Bedtime, Immersive Armor HUD | Only if adopting |
| Delight Lib / Lodestone / Cook's Collection / Deimos / SmartBrainLib / CoroUtil / Faewulf's Lib / Teal Lib / MidnightLib / TLib / MossyLib / Knight Lib | one Delight/utility candidate each | Weigh individually |
| Item Descriptions + Immersive Overlays | Field Guide | Two libs for one mod |
| Hybrid API + Biolith | Hybrid Aquatic | Gate on a worldgen test |
| Sable: Ragdolls | Ragdoll Reactions (and the skipped corpse mod) | Only with Aeronautics crash-testing |
| Sinytra Connector + Forgified Fabric API | VinURL, Immersive Winds, Puddles & Floods | **Avoid** |
### 9.2 Candidate-vs-candidate collisions (pick one)
- **Mob health UI:** Neat vs JeremySeq's Damage Indicator
- **Tooltip restyler:** Obscure Tooltips vs existing Legendary Tooltips (prefer existing)
- **Display furniture:** Display Case vs Fetzi's Displays (+ existing Handcrafted)
- **Corn crop:** Hearth and Harvest vs Haunted Harvest
- **Grass sway:** Grassier Grass vs Immersive Winds (Immersive Winds is Fabric-only — Grassier Grass wins)
- **Carried-item particles:** Immersive UI (skipped) vs Inventory Particles
- **HUD coordinates:** GUI Compass vs Field Guide's Immersive Overlays dependency
- **Pet teleport:** Waystones Teleport Pets vs Followers Teleport Too
- **Container sorting:** Client Sort (installed) vs Sophisticated Inventory Interactions / Sophisticated Sorter
- **Sleep overhaul:** Room For Two vs Sleep Tight
- **Fire flavor:** Burnt vs Burnt + Additions (never both)
- **FD↔Create bridge:** Slice & Dice (installed) vs Delightful Creators
### 9.3 Test-first items (not rejections — gates)
| Candidate | Gate |
| --- | --- |
| Supplementaries | Create 6.0.10 smoke test (old conflicts are closed) |
| CreateBetterFps | Iris now present, but it patches Create directly — test on the Aeronautics/Sable stack |
| Grassier Grass | Sodium 0.8.12/0.8.13-alpha compatibility; custom terrain shader; interaction with shaderpacks |
| Fog | Distant Horizons/SSRD visual test (upstream DH issue history); shaderpack fog already covers this |
| Shine | Sodium 0.8.12+ confirmed, but partial feature loss on the pinned alpha |
| More Culling | Rendering-core test next to EntityCulling/Sable/DH |
| Tree Physics | Dev-server soak test; per-tree Sable physics sub-levels |
| Sable Beyond | Alpha software; backup + crash-test with Aeronautics |
| Ragdoll Reactions / Sable: Ragdolls | Crash-test with Aeronautics before adopting the cluster |
| Lunar | Gravity/spawn events vs Create Aeronautics physics + Apothic Spawners |
| AutoHUD | Confirm the NeoForge jar's real dependency list (metadata artifact) |
| Spawn Animations | Confirm NeoForge jar deps; prefer the datapack form |
| Hybrid Aquatic | Terralith/Tectonic/Lithostitched worldgen + Aquaculture loot overlap |
| Dream Relics / Reliquary / Too Many Bows | Balance pass vs Apotheosis + Artifacts |
| PlayerRevive / Incapacitated | Gravestone interplay; owner sign-off on death-rule changes |
---
## 10. Adoption plan
**Phase A — Core cohort (this is the recommendation).** Add the 20 mods in Section 4 plus
`Moonlight Lib` and `Almanac`. Expected result: a noticeably richer QoL/exploration/decor layer,
one clear server-perf gain (`lmd`), zero new render/physics risk, no config maintenance.
**Phase B — Pick one expansion family.** Choose **one** of: (a) Farmer's Delight expansion, (b) Create
decoration, (c) client ambience, (d) mobs/combat gear. Adopt its shared library and 3–6 mods. Run a
`/spark health` baseline before and after.
**Phase C — Test-first candidates on a dev server.** Take the Section 9.3 gates one at a time, one
variable per run, with backups. Promote only what survives.
**Phase D — Server infrastructure.** Decide on BlueMap (ops cost) and the FTB Chunks tuning already
recommended in `FINDINGS.md`; neither is urgent game content.
**Never / not without a rewrite:** Sinytra-Connector dependency chains, a second dynamic-lights engine,
and a second claim system. (Shaders are no longer on this list — the pack has adopted them with purpose-
built compat patches; see Section 0.)
---
## 11. Appendix — detailed per-mod assessments
The full per-candidate breakdown (Adds / Relationship / Missing deps / Gameplay impact /
Compatibility / Perf cost / Verdict, with citations where web sources were used) is retained:
- Raw per-chunk assessments: `analysis/assess/chunk01.md` … `chunk07.md`
(27 mods each, 189 total). `analysis/CANDIDATE-APPENDIX.md` consolidates these and carries a
corrections note (Section 2).
- **Grouped decision table (start here for “which should I add?”): `MODLIST-GROUPS.md`** — every
candidate by purpose, with what it brings, new deps, gameplay impact, verdict and the watch-out.
- Machine-readable candidate data: `analysis/summary.json` (title, side, loaders, 1.21.1 build,
required deps, in-pack flag) and `analysis/verdicts.json`.
- Research digest used for the assessments: `analysis/chunks/chunk*.md`,
`analysis/raw/projects.json`, `analysis/raw/versions.json`.
- Pack context given to the reviewers: `analysis/PACK-CONTEXT.md`.
### Confidence legend used throughout
- **High** — read from Modrinth/loader metadata, the mod's own README, or the pack's own files.
- **Medium** — inferred from the README, or a compatibility claim from a third-party source.
- **Unknown/unverified** — evidence absent; the report says so rather than guessing.
@@ -0,0 +1,73 @@
# Onion MC — pack context for candidate-mod assessment
## Target platform
- Minecraft **1.21.1**, **NeoForge 21.1.251** (packwiz). Loader = NeoForge. Forge-only mods
generally work; Fabric-only mods do **not** work without Sinytra Connector (a red flag).
- Mixed **client + dedicated server** pack (~10 players). Server performance matters as much
as client FPS.
- Existing pack: ~142 mod entries (133 Modrinth + 9 CurseForge).
## Existing mods (titles, for compatibility reasoning)
3D Skin Layers, Adorable Hamster Pets, Alex's Caves (Unofficial Port), Alex's Mobs (Unofficial
Port), AllTheLeaks, AmbientSounds, Apotheosis (+ Apothic Attributes, Apothic Category Compat,
Apothic Combat, Apothic Enchanting, Apothic Spawners), AppleSkin, Aquaculture 2, Aquaculture
Delight, Architectury API, Ars Creo, Ars Nouveau, Ars Nouveau's Flavors & Delight, Artifacts,
Athena, BaguetteLib, Balm, Better Combat, Bookshelf, Chipped, Chunky, Citadel (unofficial port),
Client Sort, Cloth Config, Collapsible Groups, Controlify, Create, Create Aeronautics, Create
Aeronautics: Compatibility, Create: Dragons Plus, Create: Enchantment Industry, Create Slice &
Dice, CreativeCore, Cristel Lib, Critters and Companions, Curios API, Distant Horizons, Duper's
Auto Walk, Dynamic FPS, EMF (Entity Model Features), Emotecraft, Enchantment Descriptions, End's
Delight, Entity Culling, ETF (Entity Texture Features), Explorer's Compass, Explorify, Exposure,
Farmer's Cutting: Twilight Forest, Farmer's Delight, FerriteCore, Friends&Foes, FTB Backups 3,
FTB Chunks, FTB Library, FTB Quests, FTB Teams, FTB XMod Compat, Fusion, Fzzy Config, Geckolib,
Gravestone (+ Sable & Curios compat), Handcrafted, Iceberg, ImmediatelyFast, Incendium, Jade,
Jade Sable Compat, JEI, Kotlin for Forge, LambDynamicLights, Legendary Tooltips, Lithium,
Lithostitched, Lootr, Macaw's (Bridges/Doors/Lights/Roofs/Windows), MezzConfig, ModernFix,
Mouse Tweaks, Mowzie's Mobs, Nature's Compass, Noisium, Nullscape, Ocean's Delight, Patchouli,
Ping Wheel, Placebo, playerAnimator, Prickle, Prism, Rechiseled (+ Chipped), Resourceful Lib,
Sable, Sable Physics Compat, SSRD, Sodium, Sophisticated Backpacks (+ Create Integration),
Sophisticated Core, Sophisticated Storage (+ Create Integration), Sound Physics Remastered,
spark, Sparse Structures, Subtle Effects, SuperMartijn642's Config/Core Lib, Tectonic, Terralith,
The Twilight Forest, Tiny Dragons, Towns and Towers, Twilight's Flavor & Delight, Waystones
(+ Sable), When Dungeons Arise, Wings Of Fire, YACL, YUNG's API + 9 YUNG's structure mods,
C2ME, Noisium.
## Hard constraints / known landmines (do not contradict these)
1. **No shaders.** `Create Aeronautics` README says it has visual issues with **Iris**; SSRD also
warns certain Iris packs cause rendering issues. The pack deliberately has no Iris/Oculus.
2. **Sodium must stay >= 0.8.12-alpha.2.** `sable` declares older Sodium incompatible.
3. **Create declares `radium` and `palladium` incompatible.** Never add them. Avoid server-perf
mods that wholesale replace the chunk/lighting engine in ways Lithium/ModernFix already cover.
4. **SSRD requires Sodium + Distant Horizons**, and DH must be >= 1.8.7 for SSRD (already).
5. **Sable is self-described as "incredibly intrusive", mixin-heavy, prone to compat issues.**
Treat any mod that touches Create/Sable rendering or physics as high-risk.
6. Client perf stack already includes: Sodium, Lithium, ModernFix, FerriteCore, EntityCulling,
ImmediatelyFast, Dynamic FPS, AllTheLeaks, C2ME, Noisium, EMF/ETF, Distant Horizons. Assume
overlap when a candidate is another perf/culling/memory mod.
7. Pack ships **no configs** except one FTB Chunks client file (`.packwizignore` excludes
`config/*`), so any candidate that needs config editing is a maintenance cost.
8. `sky/weather/render`-engine replacements (Veil, Iris, shaders) sit on the most fragile part of
the stack (Sodium + DH + Sable).
## Existing content families a candidate may enhance or duplicate
- **Farming/food:** Farmer's Delight + Ars Delight, Aquaculture Delight, End's Delight, Ocean's
Delight, Twilight's Flavor & Delight, Farmer's Cutting: Twilight Forest.
- **Create:** base Create + Aeronautics + Dragons Plus + Enchantment Industry + Slice & Dice, plus
Sophisticated Backpacks/Storage Create integrations.
- **Aquaculture 2** already present.
- **Exploration:** Terralith, Tectonic, Incendium, Nullscape, Towns and Towers, When Dungeons
Arise, 9 YUNG's mods, Explorify, Sparse Structures.
- **Mobs:** Alex's Mobs/Caves, Mowzie's Mobs, Friends&Foes, Critters and Companions, Tiny Dragons,
Wings of Fire, Adorable Hamster Pets.
- **QoL/HUD:** AppleSkin, Jade, JEI, Client Sort, Mouse Tweaks, Ping Wheel, Nature's Compass,
Explorer's Compass, Waystones, Gravestone, Lootr, Controlify, Emotecraft, 3D Skin Layers,
Enchantment Descriptions, Legendary Tooltips, Subtle Effects, Rechiseled/Chipped/Handcrafted.
- **Apotheosis** governs affixes, spawners, enchanting, bosses, world tiers.
- **Better Combat x Apotheosis Compat** (apothic-combat) is already installed — it disables the
attack-range attribute and makes the attack-range affix inert.
## Deliverable requested from the subagent
A per-mod structured assessment. Be evidence-based: use the supplied Modrinth README excerpt as
the primary source; use `web_search`/`read_url` only to resolve genuine uncertainty (notable
incompatibilities, hard dependencies, whether a mod is abandoned, whether it conflicts with
anything in the existing list). Never invent compatibility claims. Mark confidence.
+671
View File
@@ -0,0 +1,671 @@
# Onion MC - Configuration, Compatibility & Performance Review
**Pack:** Onion MC 0.1.0 - Minecraft 1.21.1 / NeoForge 21.1.251 - 146 mods (packwiz; 141 Modrinth + 5 CurseForge)
**Scope:** options and settings that improve inter-mod behaviour, performance, or are useful to the
players and the server administrator.
> **Snapshot note.** The mod-list statistics in section 1 were measured against an earlier pack
> revision (140 entries). The pack has since changed: **FTB Chunks was replaced by Open Parties and
> Claims + Xaero's World Map/Minimap**, the rest of the FTB suite that depended on it (**FTB Quests,
> FTB Teams, FTB XMod Compat**) was removed, Iris/shaders landed upstream, and Clumps/C2ME/Noisium were
> added. Sections 3.3 and 7.1 have been updated for these; section 1's counts have not been re-run.
---
## How to read this document
Every claim is tagged with a confidence level, because "a Reddit comment said so" and "I read the
mod's config source" are not the same thing.
| Tag | Meaning |
| --- | --- |
| **VERIFIED** | Read from the mod's own jar metadata, upstream source code, or bundled default config. |
| **DOCUMENTED** | Stated in official project docs (Modrinth/GitHub/wiki/NeoForge docs). |
| **COMMUNITY** | User reports (Reddit, forums). Directionally useful, not authoritative. |
| **INFERENCE** | My reasoning from the above. |
Methods used: Modrinth API (project + version metadata for all 131 Modrinth-tracked mods), extraction
of all 131 mod jars (reading `META-INF/neoforge.mods.toml` for dependencies, sides and declared
incompatibilities), upstream source/config files on GitHub/GitLab, and official documentation.
> **Section 8 lists what is still unverified.** Nothing there should be acted on as fact.
---
## 1. Pack state - it is in good shape
This matters because it means the work here is **tuning, not repair**. There is no fire to put out.
### 1.1 Integrity - VERIFIED
| Check | Result |
| --- | --- |
| Version validity (every pinned version supports 1.21.1 + NeoForge/Forge) | **129 / 131 OK** |
| Unsatisfied required dependencies | **0** |
| `side` flags that disagree with Modrinth's authoritative client/server support | **0** |
| Mods that would be wrongly installed on a dedicated server | **0** |
- The two non-"release" pins are **Incendium** and **Sound Physics Remastered**, both tagged
`alpha` on Modrinth. For Incendium that is normal (Stardust Labs datapacks); for Sound Physics
Remastered 1.5.1 it is currently the *only* 1.21.1 NeoForge build.
- Libs that look "missing" are correctly bundled via jar-in-jar: `apollib` (inside Tectonic and
Lithostitched), `flywheel` + `ponder` (inside Create), `atmosphere` (Slice & Dice),
`sablecompanion` (Sable), `mezz_config` (JEI), `Registrate` (Create/Ars Delight/Twilight Delight).
- The 16 client-only mods carry `side = "client"`, so `packwiz-installer -s server` will exclude them.
**The server mod list is already correct - do not hand-prune it.**
### 1.2 Version currency - VERIFIED
108 / 131 pins are the newest 1.21.1 NeoForge build. The 23 that are behind are mostly trivial patch
bumps, with three worth a decision:
| Mod | Pinned | Newest 1.21.1 | Note |
| --- | --- | --- | --- |
| Terralith | 2.5.8 | **2.6.2** | Minor bump. **New worlds only** - see section 2.4. |
| Apotheosis | 8.8.0 | 8.9.0 | Feature release. |
| Sophisticated Storage | 1.5.91 | 1.6.0 | Minor bump. |
Also available: **NeoForge 21.1.252** (pack pins .251). The 21.1.x line is the 1.21.1 LTS branch and
still receives fixes, so a patch bump is low-risk if you want bugfixes.
### 1.3 Declared incompatibilities - VERIFIED
These are asserted by the mods' own metadata. They are not opinions.
| Mod | Declares incompatible | In pack? |
| --- | --- | --- |
| `create` | `radium`, `palladium` | No [OK] - **never add Radium** |
| `sodium` | `embeddium` (any version) | No [OK] - **never add Embeddium** |
| `sable` | `scalablelux`, `sablecompanion` > 1.6.0 | No [OK] |
| `distanthorizons` | `ssrd` <= **1.8.6** | 1.8.7 [OK] - **one release clear of a hard break** |
| `geckolib` | `geckoanimfix` | No [OK] |
| `cristel-lib` | `t_and_t` <= 1.13.3 | 1.13.11 [OK] |
### 1.4 Hard version constraints - do not break these
**VERIFIED** from mod metadata. These are the version pins that are load-bearing:
1. **Sodium must stay >= `0.8.12-alpha.2+mc1.21.1`.** `sable` declares every Sodium below that
incompatible. Current 0.8.13 is fine. *Downgrading Sodium breaks Create Aeronautics.*
2. **SSRD must stay >= 1.8.7.** Distant Horizons declares SSRD <= 1.8.6 incompatible.
3. **`create-enchantment-industry` requires Create >= 6.0.10** and `create_dragons_plus` >= 1.11.9.
The pack sits *exactly* on both minimums, so Create cannot be downgraded.
4. **`ssrd` requires `sable` and `sodium`** (`sodium` at `side=CLIENT`, so the dedicated server is
unaffected).
5. **Towns and Towers must stay > 1.13.3** (cristel-lib constraint).
---
## 2. Where the server load actually comes from
Four cost centres dominate, in rough order of impact. Everything in section 3 maps to one of these.
1. **Worldgen & structure density** - this pack stacks Terralith + Tectonic + Incendium + Nullscape +
Towns and Towers + When Dungeons Arise + nine YUNG's mods + Explorify. Structure-heavy worldgen is
the classic cause of chunk-generation spikes and stutter while players explore.
2. **Apotheosis spawners and entities** - Apotheosis spawners can manufacture mob counts far beyond
vanilla, and spawner-spawned mobs are *deliberately prevented from despawning*.
3. **Create + Sable physics** - Create Aeronautics runs a physics pipeline. Sable's own README calls
itself "an incredibly intrusive mod" that "makes extensive use of mixins" and is "prone to many
compatibility issues". This is the highest-risk component in the pack.
4. **OPAC claim force-loading + offline chunk loading** - per-player force-loaded chunks that tick
even when nobody is online.
---
## 3. Server tuning
### 3.1 Structure density - the single biggest lever
**Sparse Structures is already in the pack and already active.** - DOCUMENTED/VERIFIED
- Config file: **`config/sparsestructures.json5`** (confirmed in `Constants.java`:
`Path.of("config", "sparsestructures.json5")`).
- **`spreadFactor` - default `2`.** This multiplies structure spacing globally: >1 = rarer,
1 = untouched, <1 = more common. So the pack is *already* halving structure frequency versus
default. Good default; raising it is the cheapest TPS win available.
- **`customSpreadFactors`** - array of `{ "structure": "namespace:name", "factor": n }`. The value
may be a **structure set** or a **structure name**; using a structure name modifies the whole set
it belongs to.
- `/dumpstructuresets` dumps every structure set to a file - use this to get real names instead of
guessing.
- **Changes require a full game restart.**
- Source: <https://github.com/MaxenceDC/sparsestructures> (`sparse-structures-default-config.json5`)
Suggested next step - push the expensive/oversized structure sets harder than the global 2x:
```json5
{
"spreadFactor": 2,
"customSpreadFactors": [
{ "structure": "minecraft:mansion", "factor": 2 }
// run /dumpstructuresets, then add entries for the dungeons_arise:*
// and t_and_t:* sets with factor 3-4
]
}
```
> **Caveat (INFERENCE):** I have not verified the exact `dungeons_arise` / `t_and_t` structure-set
> names. Use `/dumpstructuresets` on the server to obtain them rather than copying names from the web.
**Chunky** (in pack) is the other half: pregenerate so players don't force live generation.
DOCUMENTED - commands are `/chunky radius <n>`, `/chunky world <dim>`, `/chunky center`, `/chunky start`,
`/chunky pause`, `/chunky continue`. See section 3.4 for the Chunky <-> Distant Horizons interaction, which
matters here.
### 3.2 Apotheosis - entity accumulation
**VERIFIED** by reading the config source of Apothic Spawners 1.21 branch.
Config file: **`config/apothic_spawners.cfg`** - section `[general]`:
| Key | Default | Notes |
| --- | --- | --- |
| **`Entity Despawn Delay`** | **600** | Ticks that spawner-spawned mobs are *prevented from despawning* after spawning. Range 0-24000. |
| `Spawners Drop Empty` | false | Clears stored entity when broken. |
| `Capturing Drop Chance` | 0.005 | Per-level spawn-egg drop chance. |
| `Spawner Silk Level` | 1 | Silk touch level needed; -1 disables. |
| `Spawner Silk Damage` | 100 | Durability damage per spawner harvested. |
`Entity Despawn Delay` is the lever most people miss. At the default 600 ticks, every spawner keeps
its output alive for 30 seconds regardless of mobcap pressure. On a server with several upgraded
spawners this is a steady entity tax. **Lowering it to 0-100 is a direct, low-risk TPS improvement**
and is unlikely to be noticed by players (vanilla spawners have no such protection).
> Note: this config surface is *small*. In Apotheosis 8.x the spawner **modifiers** (spawn range,
> delay, count, ...) are **datapack-driven**, not config-driven. So you cannot cap spawner throughput
> from the config file alone - `Entity Despawn Delay` plus the loot-table/datapack layer is what you
> have.
Config file: **`config/apotheosis/apotheosis.cfg`** - sections and keys (defaults in parentheses), all **VERIFIED**:
> **Note the directory.** Apotheosis's module configs are *not* in the config root: `configDir` is
defined as `<config>/apotheosis` (`ApothicAttributes.java`, line 58), and `getConfigFile(p)` returns
`<config>/apotheosis/<p>.cfg`. The one exception is Apothic Spawners, which uses Placebo's
`Configuration(String)` constructor and therefore lands at the config root as
`config/apothic_spawners.cfg`. Do not go looking for `config/apotheosis.cfg` - it does not exist.
| Section | Key | Default |
| --- | --- | --- |
| `bosses` | `Boss Spawn Cooldown` | 3600 ticks (3 min) |
| `bosses` | `Boss Announcement Range` | vanilla spawn distance + 12 blocks |
| `bosses` | `Curse Boss Items` / `Boss Auto-Aggro` / `Boss Glowing On Spawn` | false / false / true |
| `worldgen` | **`Generation Dimension Whitelist`** | `["overworld"]` |
| `spawners` | `Spawner Value Chance` | 0.11 |
| `affixes` | `Cleave Players`, `Disable Quark Tooltips for Affix Items`, `Torch Placement Item` | false / true / `minecraft:torch` |
| `augmenting` | `Upgrade Sigil Cost` / `Upgrade Level Cost` / `Reroll Sigil Cost` / `Reroll Level Cost` | 2 / 225 / 1 / 175 |
| `potion_charms` | `Restrict Charms to Curios` | false |
| `quality_of_life` | `Enable Item Linking`, `Item Linking Cooldown`, `Enable Equipment Comparisons` | true / 100 / true |
| `flair` | `Enable Affix Item Effects` | true (clientside) |
| `world_tiers` | `Enable Manual World Tier Changes` | true |
Two worth acting on:
- **`Boss Spawn Cooldown`** (3600): invading bosses are among the most expensive things Apotheosis
spawns. Raising this is a legitimate, cheap difficulty/load dial.
- **`Generation Dimension Whitelist`** = `["overworld"]`: Apotheosis worldgen currently only runs in
the Overworld. This pack has Twilight Forest, plus Incendium's Nether and Nullscape's End. If you
want rogue spawners/boss dungeons there you must add the dimension IDs - and understand that doing
so *increases* worldgen cost.
Related configs, same pattern (`config/apotheosis/apothic_attributes.cfg`,
`config/apotheosis/apothic_enchanting.cfg`, and `config/apotheosis/name_generation.cfg`):
- `apothic_attributes.cfg` -> `[combat_rules]` holds the damage formulas as **editable expressions**
(`Protection Formula`, `A-Value Formula`, `Armor Formula`, `Armor Toughness Formula`,
`Negative Armor Factor`=0.015). These are how you retune Apotheosis combat *without* a mod - useful
because Apotheosis's armour model is the thing players most often call "broken" in a pack with
Better Combat. `[general] Hidden Attributes` controls what shows in the attributes GUI (supports
namespace wildcards and `!` negation entries).
- `apothic_enchanting.cfg` -> `[client]` `Sculkshelf Noise Chance` = **200** (1-in-200 chance per
*client tick* to play a sculk shelf sound; `0` disables). Minor client perf/noise lever.
### 3.3 Open Parties and Claims - force-loading and ticking
**Replaces FTB Chunks** (removed 2026-09-30 in favour of Open Parties and Claims + Xaero's
World Map/Minimap; see `analysis/FTB-CHUNKS-SWAP.md`). Everything below is the OPAC equivalent of the
tuning this section used to document for FTB Chunks.
**DOCUMENTED** - OPAC's main config lives **inside the world save**, not the pack:
| File | Purpose |
| --- | --- |
| `world/serverconfig/openpartiesandclaims-server.toml` | Main server config (limits, protection, forceloading, party backend). |
| `world/serverconfig/openpartiesandclaims-*` | Global "player configs" (default player config, server-claim, expired-claim, wilderness). |
| `<server dir>/config/openpartiesandclaims-server.toml` | Reported location on Fabric/Quilt only. |
**Warning - stop the server before editing.** The README is explicit: the server may overwrite your
changes otherwise. Do **not** assume the file the server generated matches any list here; read the
generated file, which carries inline `# Default:` comments for every key.
**Easiest route for an admin:** in-game, the mod UI is bound to `'` by default. Claim/party commands
are `/oclaims`, `/oparties`, `/opac` and `/opm` (party chat). Server-wide options are still edited in
the file, not the UI.
**`defaultconfigs/` and packwiz cannot help here** - the file is in the world save, so it cannot be
shipped at all. See section 7.1.
Keys and defaults - **DOCUMENTED where the project's own issue quotes or README state them**, else
flagged:
| Key | Default | Why it matters |
| --- | --- | --- |
| `maxPlayerClaims` | 500 | Per-player claim cap. Same as FTB Chunks' `max_claimed_chunks`, so no capacity loss on the swap. Overridable via the `xaero.pac_max_claims` permission. |
| `maxPlayerClaimForceloads` | low/0 - **verify** | Per-player forceload cap. **This pack sets it to 25** (matching FTB Chunks' old `max_force_loaded_chunks`); the stock default is not 25, so leaving it unset silently removes forceloading. Overridable via `xaero.pac_max_forceloads`. |
| `claims.forceload.offlineForceload` | **verify** (documented as "true" in a 2021 issue, so possibly stale) | Force-loaded chunks stay loaded while the owner is **offline**. This is the OPAC equivalent of FTB Chunks' `chunk_load_offline`, and the same farms-vs-TPS trade applies: 25 forceloads x ~10 players is potentially 250 chunks ticking around the clock. |
| `primaryPartySystem` | built-in | **Left at OPAC's built-in default** - FTB Teams was removed along with FTB Quests, so the built-in system is now the only one. Pointing this at `ftbteams` would be a no-op that logs missing-mod noise. |
| `staticFakePlayers` | - | Position-bound fake players for automation (miners, auto-clickers). The analogue of FTB Chunks' `fake_players`. |
| `playerConfigurablePlayerConfigOptions` / `opConfigurablePlayerConfigOptions` | - | Which options players (and OPs) may change in their own config. Adding a key to these arrays is what exposes it - e.g. `claims.forceload.offlineForceload` is not player-changeable until listed. |
| protection exception lists | - | `forcedBlockProtectionExceptionList`, `forcedEntityProtectionExceptionList`, `itemUseProtectionExceptionList`, plus the optional per-claim groups. This is OPAC's answer to FTB Chunks' protection; modded blocks/entities that misbehave go here. |
**Recommended starting point for a ~10-player server:** keep `maxPlayerClaims = 500`,
`maxPlayerClaimForceloads = 25` (decided 2026-09-30), leave `primaryPartySystem` at the built-in
default, and take a deliberate decision on `claims.forceload.offlineForceload` - it is the same straight
farms-vs-TPS trade FTB Chunks' `chunk_load_offline` was, with a larger blast radius now that each player
may forceload 25 chunks.
**Reliability caveat - COMMUNITY (open upstream issues).** OPAC force-loading has a recurring "marked
the chunk but it does not actually stay loaded" failure mode with issues open at the time of writing
(#855, #927, plus #237/#528 further back). Because this pack's farm design leans on offline
force-loading, verify per-player forceloading on a staging world *before* trusting it, and re-test
after each OPAC bump.
Sources: <https://github.com/thexaero/open-parties-and-claims> (README: config locations, permission
nodes, `primaryPartySystem`, `staticFakePlayers`), <https://modrinth.com/mod/open-parties-and-claims>
(commands, keybind, FAQ; also states the mod is in beta),
<https://github.com/thexaero/open-parties-and-claims/issues/228> and
<https://github.com/thexaero/open-parties-and-claims/issues/731> (default keys and the
`offlineForceload` description), <https://github.com/thexaero/open-parties-and-claims/issues/855>
(force-loading reliability).
### 3.4 Distant Horizons - decide deliberately, and mind Chunky
**DOCUMENTED** - Distant Horizons has real server-side support and it is opt-in. From the official
"Server Owners" wiki:
- **DH >= 2.3 is required on a server**; older builds crash. The pack's 3.3.2 is fine.
- **"Using the same version / build on both the server and the client is strongly preferred."**
- Server support only engages if **"Distant Generation" is enabled on both server and client**;
the same applies to every toggle with a server-side counterpart.
- DH is **optional on both sides** - vanilla clients can still connect. So marking it optional in the
pack is correct and safe.
- Default settings need **>= 500 KB/s (4 Mbit/s)** per client. If players see "Timed out" kicks, lower
their client bandwidth settings.
- **"Pre-generating LODs is highly recommended to avoid performance issues."**
- **Warning: Chunky interaction:** *"Running Chunky will also generate & update LODs, but it's not
recommended to rely on this behavior for pre-generation. LODs and chunks are independent, and by
loading a lot of chunks quickly you risk overloading DH and ending up with a lot of holes. Either
use DH's pre-generation, or temporarily disable the mod entirely."*
**This pack has both Chunky and DH.** If you pregenerate with Chunky, expect DH LOD holes. Use
`/dh pregen` instead, or disable DH while Chunky runs.
Server-side config keys (use `/dh config <key>`; the command prints the current value, description and
the config file path, which is the reliable way to find the file):
| Setting | Purpose |
| --- | --- |
| `common.threadPreset` | CPU load preset for DH's worker threads. |
| `common.maxDataTransferSpeed` | Per-player bandwidth cap. |
| `generation.mode` | Set to `INTERNAL_SERVER` to generate chunks alongside LODs. |
| `generation.requestRateLimit` | Requests/second cap. |
| `generation.maxRequestDistance` | Per-player generation radius. |
| `generation.nSized` | Experimental: generate LODs directly at the required size. |
| `generation.bounds.x` / `.z` / `.radius` | Global generation area restriction (disabled while `radius` is 0). |
| `realTimeUpdates.playerDistance` | How far chunk updates are converted to LOD pushes. |
| `syncOnLoad.rateLimit` / `syncOnLoad.maxRequestDistance` | Login-time sync throttling. |
| `levelKeyPrefix` | **Required if several servers share a proxy** - set a unique string. |
Also: pregenerate **beyond the world border** if you use one, or clients will show LOD holes at the edge.
**Avoid running `/dh pregen` on a server people are actively playing on.**
Source: <https://gitlab.com/distant-horizons-team/distant-horizons/-/wikis/1-user-guide/1-frequently-asked-questions/5-server-owners/Server-Owners>
### 3.5 Diagnosing lag: spark is already installed
**DOCUMENTED** - `spark` 1.10.124 is in the pack, so no setup is needed. Per spark's own docs, ~30
seconds of profiling is enough for useful results.
Admin workflow:
- `/spark tps` - accurate TPS.
- `/spark health` - TPS, tick durations (min/avg/max), CPU usage, GC activity. **Start here.**
- `/spark profiler start --timeout 60` - sample the server; then `/spark profiler stop`.
Target specific threads with `--thread`, and record only slow periods to cut noise.
- Use GC figures from `/spark health` to decide heap/GC flags rather than guessing.
- On Linux x86_64 spark uses async-profiler via `perf_events`, which gives better results than the
fallback `ThreadMXBean` engine - worth knowing when comparing reports.
Docs: <https://spark.lucko.me/docs>
---
## 4. Client tuning and cross-mod conflicts
### 4.1 The SSRD -> Distant Horizons -> Sodium chain
**VERIFIED/DOCUMENTED.** SSRD ("Separate Sable Render Distance") is a *client rendering* addon that
exists to decouple Sable's sub-level render distance from the normal one.
- SSRD's own README: **"Distant Horizons or voxy AND Sodium MUST be installed on the Client!"**
- **Versions 1.8 and up REQUIRE Sodium 0.8.** The pack has Sodium 0.8.13 [OK] and SSRD 1.8.7 [OK].
- **But Distant Horizons is marked `optional = true, default = false` in the pack.**
**INFERENCE:** any player who leaves DH disabled gets a non-functional SSRD. Either ship DH by
default, or mark SSRD optional alongside DH so the two travel together.
- SSRD has **server-side gamerules**:
- `/gamerule ssrdMaxRenderDistance` - default **128** chunks (server-wide cap).
- `/gamerule ssrdForceloadLimit` - default **2** per player for `/ssrd forceload`.
- `/ssrd forceload` does not require op but respects that cap.
- SSRD README also warns: *"Certain Iris shader packs cause rendering issues."*
### 4.2 EntityCulling will break Ars Nouveau's spell visuals
**DOCUMENTED** - EntityCulling's README: *"Client-side entities, commonly used by magic mods for
animations, may not behave as expected. Whitelist the relevant entities via the config screen for tick
culling and/or entity culling."*
**INFERENCE:** this pack has Ars Nouveau, which uses client-side entities for spell/projectile visuals,
plus Create Aeronautics (Sable) and several mob mods with custom animations. EntityCulling has a wide
whitelist surface and its config screen supports per-entity toggles - expect to need it. EntityCulling
is a solid win in general (it is one of the biggest client FPS wins on busy servers), so whitelist
rather than remove.
### 4.3 Should you add shaders? Probably not yet
**DOCUMENTED**, two independent blockers:
1. **Create Aeronautics 1.1.3 README:** *"Aeronautics currently has visual issues when used with
**Iris Shaders**."*
2. **SSRD README:** certain Iris shader packs cause rendering issues.
Combined with the fact that the pack contains **no shader mod at all** (no Iris/Oculus) and that DH +
Sodium + Sable already form a tight, version-sensitive trio, adding shaders introduces a new
compatibility axis in exactly the most fragile part of the pack. **INFERENCE: leave shaders out** unless
you are willing to own that debugging surface.
### 4.4 Tectonic - optional terrain knobs
**VERIFIED** from Tectonic v3 config source. File: **`config/tectonic.json`** (DOCUMENTED via the
official changelog: "Added a proper config to the mod version at config/tectonic.json"). Keys and
defaults:
| Group | Key | Default |
| --- | --- | --- |
| `general` | `mod_enabled` / `snow_start_offset` | true / 128 |
| `global_terrain` | `vertical_scale` / `increased_height` / `lava_tunnels` | 1.125 / **false** / true |
| `continents` | `ocean_offset` / `continents_scale` / `erosion_scale` / `ridge_scale` | -0.8 / 0.13 / 0.25 / 0.25 |
| `continents` | `underground_rivers` / `river_lanterns` / `flat_terrain_skew` / `rolling_hills` / `jungle_pillars` | true / true / 0.1 / true / true |
| `islands` | `enabled` / `noise_scale` | true / 0.11 |
| `oceans` | `ocean_depth` / `deep_ocean_depth` / `monument_offset` / `remove_frozen_ocean_ice` | -0.22 / -0.45 / -30 / false |
| `biomes` | `temperature_multiplier` / `temperature_scale` / ... | 1.0 / 0.25 / ... |
Note `increased_height` is already `false` - good. It raises the build/generation ceiling to **y640**,
which is a meaningful worldgen and memory cost. **Do not enable it on a server.**
The Tectonic wiki (<https://github.com/Apollounknowndev/tectonic/wiki/Config>) explains every option;
the in-game mod-menu config screen shows them with tooltips.
---
## 5. Terralith + Tectonic - the combination is correct
**DOCUMENTED**, and this is a commonly-messed-up setup, so it's worth confirming it's right:
- Terralith's README: *"Terralith is **fully compatible with the mod version of Tectonic**."*
- Tectonic's README: *"Tectonic has a special version with Terralith compatibility. **This version is
shipped in the mod version of Tectonic**, but it is available as a standalone datapack
[Terratonic]."*
- Terralith's README: *"If you are using the **datapack** versions, you must use **Terratonic
instead** of Tectonic."*
The pack has **both as mods**, so the Terralith-compat layer is already active.
**Do not add Terratonic** - it is for the datapack route and would conflict.
### 5.1 Terralith updates require a new world
**DOCUMENTED** - Terralith's README: *"It is **not recommended to add Terralith to an existing world**
- reset yours first, or use a new world."*
Also relevant: *"In 2.6.0, the **mod** version of Terralith relies on **Lithostitched** as a required
dependency."* The pack has Terralith **2.5.8** and **already ships Lithostitched 1.8.0**, so the
eventual 2.6.x update is dependency-safe. **But it is a worldgen change - new worlds only.**
**INFERENCE / worth checking:** if the live server world predates the addition of Terralith (the pack
added it recently), terrain seams and abrupt biome transitions are expected and are not a bug.
---
## 6. Cross-mod notes worth knowing
### 6.1 Apothic Combat silently disables the attack-range affix
**DOCUMENTED** - from the Apothic Combat README (the "Better Combat x Apotheosis Compat" mod):
The problem it solves: Apotheosis adds a "Heavy Weapon" category (loosely like Better Combat's
two-handed weapons, but unrelated), and **Apotheosis/Forge treat attack range as an entity attribute**
that grows with gems and affixes, while **Better Combat defines attack range as an immutable weapon
property** in its own JSON format - so they cannot both work.
What the compat mod does:
- Registers all two-handed Better Combat weapons automatically as `HEAVY_WEAPON` for Apotheosis.
- **"Removes the Entity Reach Attribute from all weapons, and makes the attack range affix
nonfunctional."**
**Player-facing consequence:** any gem/affix investment in attack range does nothing in this pack.
That is a legitimate thing to tell players, and if you want, the README notes that *disabling the
Apotheosis Attack Range affix from appearing at all* is possible via datapack.
Source: <https://modrinth.com/mod/apothic-combat>
### 6.2 Apotheosis enchanting vs other enchanting overhauls
**COMMUNITY.** The well-known Apotheosis enchanting-table conflict is with **Quark**, which is *not* in
this pack, so that specific issue does not apply. No verified conflict with Ars Nouveau's enchanting
was found. Flagging only that both touch the enchanting system; if you see an enchanting-table UI
problem, test with Apothic Enchanting disabled first.
### 6.3 Lootr - exact keys, and one gotcha
**DOCUMENTED** - file **`config/lootr-common.toml`**. Lootr **must be installed on both client and
server** (its FAQ is explicit). Selected keys and defaults:
| Key | Default | Notes |
| --- | --- | --- |
| `disable_break` | false | Prevents breaking Lootr containers except sneaking in **creative**. |
| `blast_resistant` | false | Makes Lootr containers explosion-proof. |
| `decay_all` | false | **All** chests decay after first open (overrides the per-list options). |
| `decay_value` | 6000 | Ticks for a decaying container to decay (5 min). |
| `decay_dimensions` / `decay_modids` / `decay_loot_tables` | `[]` | Targeted decay. |
| `refresh_all` | false | **All** chests refresh after first open. |
| `refresh_value` | 24000 | Ticks to refresh (20 min). |
| `refresh_dimensions` / `refresh_modids` / `refresh_loot_tables` | `[]` | Targeted refresh. |
| `convert_wooden_chests` / `convert_trapped_chests` / `convert_mineshafts` | true | Conversion scope. |
| `convert_quark` | true | Quark isn't in this pack; harmless. |
| `skip_unloaded` | true | Skip containers in unloaded chunks (queued for later). |
| `maximum_age` | 3600 | How long a container may sit in the conversion queue (180x20 ticks). |
| `loot_modid_blacklist` / `loot_table_blacklist` / `dimension_blacklist` / `dimension_whitelist` | `[]` | Exclusions. |
| `power_comparators` | true | Comparators output 1 - **important for trap-based structures**; `false` makes traps fire on load. |
| `report_unresolved_tables` | false | Log unresolved loot tables. |
| `randomise_seed` | true | Warning: see caveat below. |
| `vanilla_textures` | false | Client-side; hides Lootr textures. |
Two things to flag:
- **Warning: `randomise_seed`'s documentation is contradictory** in the official wiki - the Forge section
and the Fabric section describe opposite behaviours. **Do not change this key on the strength of the
wiki alone**; test on a scratch world first.
- **Warning: Structure blacklists do not work on 1.21.1.** The wiki states plainly: *"structure blacklists
are not functional outside of 1.16.5 due to tagging and holder changes."* So `decay_structures` and
`loot_structure_blacklist` are dead keys here. Don't waste time on them.
Lootr interacts with the pack's structure mods (When Dungeons Arise, the nine YUNG's mods) and with
Apotheosis loot tables by converting their chests. If a specific structure's loot behaves oddly, the
`loot_table_blacklist` / `loot_modid_blacklist` route is the supported fix - not the structure keys.
### 6.4 Known compatibility risks to keep on the radar
- **Sable / Create Aeronautics** - self-described as highly intrusive and mixin-heavy. If you get a
weird crash or a visual bug you can't explain, **test with Sable removed first**. The five Sable
compat addons in the pack (`sablecompat`, `aeronauticscompat`, `gravestone_sable_compat`,
`waystonessable`, `sablejade`) exist precisely because it needed patching.
- **Create declares `radium` and `palladium` incompatible** - this rules out the most obvious
"add more server performance mods" candidates. See section 7.
---
## 7. Config mechanics - how to actually apply any of this
This is the part that decides whether the rest of the document is usable.
**DOCUMENTED** (NeoForge config docs) - three locations, and they behave differently:
| Location | Scope | Synced to clients? |
| --- | --- | --- |
| `config/` | Global, both sides | Depends on config type |
| `world/serverconfig/` | **Per-world override** of `SERVER`-type configs | Yes - server wins |
| `defaultconfigs/` | Seeds new worlds | n/a |
- `SERVER`-type configs are **synced from server to client** and can be overridden per world in
`world/serverconfig/`. `STARTUP` / `COMMON` / `CLIENT` are **not synced**.
- Server configs are only editable via the in-game screen on a local world; they are greyed out on a
multiplayer server - so on a real server you edit the files.
- **The Apotheosis stack is the exception:** those are Placebo `.cfg` files under
`config/apotheosis/` (+ `config/apothic_spawners.cfg`) and are **not** NeoForge `SERVER`-type
configs, so they are not synced and not per-world. They're plain global files you edit directly.
**This also means `defaultconfigs/` will not seed them** - see 7.1.
### 7.1 The pack currently cannot ship server configs
**VERIFIED** - `.packwizignore` contains:
```
config/
defaultconfigs/
```
So **every** config is excluded from the pack index. Consequences:
- None of the tuning in section 3 can be delivered to players or to a fresh server via the pack as it stands.
- A fresh server generates stock defaults for everything.
This is a cleaner end state than before: the only config the pack used to ship was the FTB Chunks
minimap client file, and it left with FTB Chunks (section 3.3).
**Recommendation (INFERENCE):** if you want these settings to be part of the pack rather than tribal
knowledge, un-ignore the specific files you care about (a `!config/<path>` rule alongside the `config/`
ignore) and/or use `defaultconfigs/` for per-world `SERVER`-type defaults.
**Caveat - the systems this document tunes are mostly unreachable from packwiz.**
- **OPAC** keeps its main config in `world/serverconfig/`, inside the world save. Not shippable, ever.
- the **Apotheosis stack** uses Placebo `.cfg` files, which `defaultconfigs/` does not seed.
The one exception now worth knowing: **Xaero's** server-side settings live in `<server dir>/config/xaero/`
(`world-map`, `minimap`), so a `!config/xaero/*` rule *would* let the pack ship enforced map profiles
if that is ever wanted. Those configs also register the status effects that block non-modded clients,
so shipping them has a real consequence - see the Xaero's pages before doing it.
Note the `.packwizignore` also excludes `*.json` globally, which would also exclude
`config/tectonic.json` even if `config/*` were relaxed. It needs an explicit `!` rule.
### 7.2 Installing on the server
**DOCUMENTED** - use the server side flag so client-only mods are skipped:
```
java -jar packwiz-installer-bootstrap.jar -g -s server <pack-url>
```
`-g` disables the optional-mods GUI, `-s server` downloads only server-side mods. Because every
`side = "client"` flag in this pack is already correct (section 1.1), this produces a correct server mod list
automatically.
---
## 8. Prioritised recommendations
Ordered by value / risk. **P0 items are the ones I'd do first.**
| # | Pri | Change | Where | Evidence |
| --- | --- | --- | --- | --- |
| 1 | **P0** | Set `Entity Despawn Delay` to `0`-`100` (from 600) | `config/apothic_spawners.cfg` -> `[general]` | VERIFIED source |
| 2 | **P0** | Decide `claims.forceload.offlineForceload` (true = farms run while owners are offline, costs TPS); set `maxPlayerClaimForceloads` to 25 | OPAC server config, `world/serverconfig/openpartiesandclaims-server.toml` | DOCUMENTED |
| 3 | **P0** | Leave `primaryPartySystem` at the built-in default (FTB Teams is gone; pointing it at `ftbteams` is a no-op) | OPAC server config | VERIFIED |
| 4 | **P0** | Fix SSRD/DH mismatch: ship DH by default, or mark SSRD optional with it | `mods/*.pw.toml` | VERIFIED + DOCUMENTED |
| 5 | **P0** | Whitelist Ars Nouveau (and Sable/Create) entities in EntityCulling | client config screen | DOCUMENTED |
| 6 | P1 | Raise `spreadFactor` for the expensive structure sets via `customSpreadFactors` (use `/dumpstructuresets`) | `config/sparsestructures.json5` | VERIFIED source |
| 7 | P1 | Cap SSRD via `/gamerule ssrdMaxRenderDistance` (default 128 chunks) | server command | DOCUMENTED |
| 8 | P1 | Pregenerate with `/dh pregen`, **not** Chunky, or disable DH while Chunky runs | server | DOCUMENTED |
| 9 | P1 | Raise `Boss Spawn Cooldown` above 3600 if invader load is heavy | `config/apotheosis/apotheosis.cfg` -> `[bosses]` | VERIFIED source |
| 10 | P1 | Establish a `/spark health` -> `/spark profiler` baseline before and after changes | server | DOCUMENTED |
| 11 | P1 | Un-ignore the specific configs you want shipped (or adopt `defaultconfigs/`) | `.packwizignore` | VERIFIED |
| 12 | P2 | Consider NeoForge 21.1.251 -> 21.1.252 | `pack.toml` | VERIFIED |
| 13 | P2 | Tell players the attack-range affix is inert (Apothic Combat) | player comms | DOCUMENTED |
| 14 | P2 | Decide on the 23 outdated pins (Terralith 2.6.2 is **new-world only**) | `mods/*.pw.toml` | VERIFIED |
| 15 | P2 | Leave `increased_height: false` in Tectonic; never add Embeddium/Radium/shaders | various | VERIFIED/DOCUMENTED |
| 16 | P2 | Set `levelKeyPrefix` if you ever run multiple servers behind a proxy | DH server config | DOCUMENTED |
---
## 9. Not verified / open questions
Listed so nobody mistakes absence of evidence for evidence of absence.
1. **Client-side performance options not yet reviewed in depth** - Distant Horizons' client quality/
thread presets, ImmediatelyFast, Dynamic FPS, EMF/ETF, Fusion/Athena, Legendary Tooltips, Prism.
These are very likely fine at defaults; DH's client quality preset is the one worth player guidance.
2. **Missing server-side performance mods not yet assessed** - ServerCore, Noisium, Clumps,
Connectivity, Packet Fixer, ThreadTweak on NeoForge 1.21.1, and whether each overlaps with the
already-installed Lithium/ModernFix/FerriteCore/AllTheLeaks. **Note Create's declared
incompatibility with Radium and Palladium**, which removes two candidates. Do not add anything here
without checking for overlap with Lithium and ModernFix first.
3. **Create / Sable / Aeronautics config surface** - not yet read from source. SSRD and the Sable
gamerules in section 4.1 are the only Sable-family settings verified so far.
4. **FTB Backups 3** config keys (interval, retention, compression) - not yet verified. Relevant to
disk/IO on the server.
5. **Waystones, GraveStone, Sophisticated Storage/Backpacks, Curios, Artifacts** config keys - not yet
verified.
6. **JVM flags / heap sizing** for this pack size - not yet researched. Use `/spark health` GC data
(section 3.5) to decide empirically rather than copying generic flags.
7. **`server.properties` / gamerule tuning** (view-distance vs simulation-distance, entity cramming,
mobcaps, `network-compression-threshold` for large modded packets) - not yet verified.
8. **Exact `dungeons_arise` / `t_and_t` structure-set names** - use `/dumpstructuresets`.
9. **Lootr `randomise_seed` semantics** - the official wiki contradicts itself; test before changing.
10. **The 5 CurseForge-sourced mods** (FTB Backups 3, FTB Library, AllTheLeaks, Collapsible Groups,
Twilight Forest) could not be version-checked by packwiz for currency without a CurseForge API key.
The FTB Chunks swap plus the later FTB suite removal cut this blind spot from nine mods to five.
---
## Appendix A - Verified config file paths
| Mod | File | Format |
| --- | --- | --- |
| Tectonic | `config/tectonic.json` | JSON |
| Sparse Structures | `config/sparsestructures.json5` | JSON5 |
| Apotheosis (adventure) | `config/apotheosis/apotheosis.cfg` | Placebo CFG |
| Apothic Attributes | `config/apotheosis/apothic_attributes.cfg` | Placebo CFG |
| Apothic Spawners | `config/apothic_spawners.cfg` (config root - see note in section 3.2) | Placebo CFG |
| Apothic Enchanting | `config/apotheosis/apothic_enchanting.cfg` | Placebo CFG |
| Apotheosis name generation | `config/apotheosis/name_generation.cfg` | Placebo CFG |
| Open Parties and Claims | `world/serverconfig/openpartiesandclaims-server.toml` (main server config - inside the world save) plus `openpartiesandclaims-*` player configs alongside it | TOML |
| Xaero's World Map / Minimap | server-side: `config/xaero/world-map`, `config/xaero/minimap`; client map + waypoint data under the instance `xaero/` directory | TXT / binary |
| Lootr | `config/lootr-common.toml` | TOML |
| Distant Horizons | use `/dh config <key>` to print the path | TOML |
## Appendix B - Sources
- Modrinth API v2 (`/project/{id}`, `/version/{id}`, `/project/{id}/version?game_versions=["1.21.1"]`) - pack inventory, sides, incompatibilities, version currency.
- All 131 mod jars - `META-INF/neoforge.mods.toml` (dependencies, sides, declared incompatibilities).
- Apotheosis stack config source: `Shadows-of-Fire/Apotheosis@1.21`, `Apothic-Spawners@1.21`, `Apothic-Attributes@1.21`, `Apothic-Enchanting@1.21`, `Placebo@1.21`.
- Sparse Structures: `MaxenceDC/sparsestructures@1.21` (`Constants.java`, `sparse-structures-default-config.json5`).
- Tectonic: `Apollounknowndev/tectonic@v3` (`config/state/ConfigState.java`), Tectonic wiki `/wiki/Config`, official changelog for `config/tectonic.json`.
- NeoForge config docs: <https://docs.neoforged.net/docs/misc/config/>
- Distant Horizons server owners wiki: <https://gitlab.com/distant-horizons-team/distant-horizons/-/wikis/1-user-guide/1-frequently-asked-questions/5-server-owners/Server-Owners>
- OPAC README and Modrinth page (config locations, permission nodes, `primaryPartySystem`, `staticFakePlayers`, commands, beta status): <https://github.com/thexaero/open-parties-and-claims>, <https://modrinth.com/mod/open-parties-and-claims>
- OPAC default keys and `offlineForceload` semantics (issue quotes): <https://github.com/thexaero/open-parties-and-claims/issues/228>, <https://github.com/thexaero/open-parties-and-claims/issues/731>
- OPAC force-loading reliability reports: <https://github.com/thexaero/open-parties-and-claims/issues/855>, <https://github.com/thexaero/open-parties-and-claims/issues/927>
- Xaero's World Map / Minimap server-side config locations and modpack terms: <https://modrinth.com/mod/xaeros-world-map>, <https://modrinth.com/mod/xaeros-minimap>
- Lootr wiki: <https://github.com/LootrMinecraft/Lootr/wiki/Lootr-Configuration>
- spark docs: <https://spark.lucko.me/docs>
- Modrinth/CurseForge project pages and READMEs for Sable, Create Aeronautics, SSRD, Terralith, EntityCulling, Apothic Combat, FerriteCore, Lithium, Chunky.
+179
View File
@@ -0,0 +1,179 @@
# FTB Chunks -> Open Parties and Claims + Xaero's maps
**Status:** shipped - 2026-09-30 (`1d02bf1`, pushed). FTB Quests/Teams/XMod Compat removed after the
swap; see section 8.
**Decision:** replace FTB Chunks with **Open Parties and Claims (OPAC)** for claims/forceloading and
**Xaero's World Map + Minimap** for mapping, and drop the rest of the FTB suite that depended on it.
Remaining FTB mods: **FTB Backups 3** and **FTB Library** only.
**Accepted cost:** every player re-claims their land and re-creates their waypoints, and any FTB Quests
content and per-team progress is discarded.
Confidence tags follow `FINDINGS.md`: **VERIFIED** / **DOCUMENTED** / **COMMUNITY** / **INFERENCE**.
---
## 1. Why this works at all
The pack's earlier review (`MODLIST-REVIEW.md` §8) recommended keeping FTB Chunks on the grounds that
"wholesale replacement breaks FTB Quests/Teams". That is only true of a *wholesale* FTB removal.
**VERIFIED:** FTB Quests' build declares `ftb-library` + `ftb-teams` as required and `ftb-xmod-compat`
as optional - **not** FTB Chunks. FTB Quests also ships **no** claim-chunk task type. So FTB Chunks is
the one mod in the FTB suite that can leave without touching quests, provided Teams stays.
**VERIFIED:** nothing else in the pack declares FTB Chunks as a required dependency. The only
cross-mod hooks are inside FTB XMod Compat (KubeJS/GameStages progression, rank-based claim limits),
which degrade to inert modules.
## 2. What changed
| Action | Path |
| --- | --- |
| Removed | `mods/ftb-chunks-forge.pw.toml` (was `ftb-chunks-neoforge-2101.1.22`, CF 314906) |
| Removed | `config/ftbchunks-client.snbt` (the `minimap.enabled=true` modpack default) |
| Edited | `.packwizignore` - dropped `config/*` + `!config/ftbchunks-client.snbt`, back to `config/` |
| Added | `mods/open-parties-and-claims.pw.toml` - `neoforge-1.21.1-0.31.6` (`side = "both"`) |
| Added | `mods/xaeros-world-map.pw.toml` - `neoforge-1.21.1-1.46.0` (`side = "both"`) |
| Added | `mods/xaeros-minimap.pw.toml` - `neoforge-1.21.1-26.5.0` (`side = "both"`) |
| Added | `mods/xaeros-minimap-world-map-waystones-compatibility-forge.pw.toml` - `2.1.0` |
| Regenerated | `index.toml` (147 entries) and the `[index]` hash in `pack.toml` |
Net: **146 mods** (138 Modrinth/other + 8 CurseForge), down from 9 CurseForge-exclusive mods. All four
new mods are Modrinth-hosted, so they are checkable by `packwiz update`.
The three Xaero's entries are byte-identical to the ones deleted in `b0542f9` (2026-09-28) - same
versions, same sha512s - so this is a revert of the map half of that commit, not a new integration.
## 3. Where the pack traded one thing for another
| FTB Chunks capability | Replacement | Notes |
| --- | --- | --- |
| Chunk claims + protection | OPAC | Server config now lives in the world save, not the pack. |
| Forceload + offline ticking | OPAC `claims.forceload.offlineForceload` | Farms-vs-TPS trade is unchanged; reliability is less proven (see §5). |
| Minimap + fullscreen map | Xaero's Minimap + World Map | Now two client mods instead of one. |
| Waypoints + teleport | Xaero's Minimap | **No migration path** - see §4. |
| Party/team claims | OPAC's built-in party system | FTB Teams was removed, so this is the only party system now. `/oparties`, `/opm` for party chat. |
| Waystones on the map | `xaeros_waystones_compatibility` | Restored; it had been removed with the maps. |
| FTB Ranks permission overrides | `xaero.pac_max_claims` / `xaero.pac_max_forceloads` | Moot in practice: FTB Ranks was never installed. |
## 4. Migration runbook (server operator)
**Before the update**
1. Take a world backup. FTB Chunks' claim data stays in the save after the mod is removed, so this
backup is the rollback path.
2. Have each player screenshot or export their FTB Chunks waypoints. They live client-side under
`local/ftbchunks/`; **there is no FTB Chunks -> Xaero's converter** (only JourneyMap -> Xaero tools
exist). Re-entry is manual.
3. Capture who owned which claims/forceloads, so players have a checklist and an admin can pre-claim
using OPAC server-claim mode if desired.
**Cutover (one update, low-activity window)**
4. Do **not** run FTB Chunks and OPAC side by side - two claim systems means split protection logic.
Hard cutover.
5. Start the server once with OPAC installed so it generates
`world/serverconfig/openpartiesandclaims-server.toml`, then **stop the server**.
6. Edit the generated file (it carries `# Default:` comments for every key) and set at minimum:
- `maxPlayerClaims = 500` (matches the old FTB Chunks cap)
- `maxPlayerClaimForceloads = 25` (**decided 2026-09-30**; the stock default is lower, so leaving it
alone silently removes forceloading)
- `primaryPartySystem` -> leave at the built-in default (FTB Teams is gone; do **not** point it at
`ftbteams`)
- a decision on `claims.forceload.offlineForceload` (farms while offline vs TPS)
- add `claims.forceload.offlineForceload` to `playerConfigurablePlayerConfigOptions` /
`opConfigurablePlayerConfigOptions` if players should be able to toggle their own
7. Re-claim: each player re-claims their base and re-forceloads, via the OPAC UI (`'`) or the Xaero's
world map (right-click / drag-select).
**After**
8. Verify per-player forceloading actually keeps chunks ticking with the owner offline. This is the
known weak point (§5).
9. Verify protection across the modded stack - especially Create contraptions, Sable ships, and
fake-player automation.
10. Re-tier any rank/permission limits you were relying on.
## 5. Risks and open items
- **OPAC force-loading reliability is the one thing that can invalidate the swap.** Open upstream
issues report chunks marked forceloadable but never actually loaded (#855, #927, plus #237/#528).
**COMMUNITY.** Test before trusting; re-test on every OPAC bump. If it fails, this swap should be
rolled back - forceloading is the one feature the pack cannot work around.
- **OPAC is self-described beta** and releases frequently (0.31.6, 2026-09-10). **DOCUMENTED.**
- **OPAC and Xaero's are version-coupled** - OPAC's README tells users to keep the Xaero's mods on
their latest versions. Bump them together.
- **Claim protection parity is unverified.** OPAC protects vanilla plus a supported-mod list (Create is
built in on Forge), and needs manual exception entries for anything else. Open reports exist for
creeper explosions (#731) and modded projectiles (#758) inside claims. **COMMUNITY.** The pack's
Apotheosis/Sophisticated/Ars Nouveau/Alex's Mobs surface needs a pass.
- **Sable / Create Aeronautics.** A crash with Sable contraptions was reported (#873) and is **closed**,
but was not re-verified against 0.31.6. **COMMUNITY.** Ships are not covered by plain chunk claims
under either system; the pack has no ship-claim mod. Options if that matters:
`aeroclaims` ("Create Aeronautics: Claims", NeoForge 1.21.1, works with **OPAC or FTB**), or
`sable-protect`. Note that dropping FTB Chunks does close the door on `ftbchunksaerospace` and
`Create-Aeronautics-FTB-Chunks`.
- **Licensing.** OPAC is **LGPL-3.0** - no pack conditions. Xaero's World Map and Minimap are
**All Rights Reserved**: because this pack is distributed from `git.ljones.me` and not from
CurseForge/Modrinth, the terms require an **easily accessible credit link to an official Xaero page**
in the pack. Add one. **VERIFIED** (from the mods' own "Modpacks and Other" sections).
- **`side = "both"` for Xaero's** puts them on the server too. That buys reliable multi-world map
identity and server-side map profiles, and matches the pre-`b0542f9` configuration, but the server
then registers status effects that stop *vanilla* clients joining unless disabled in
`config/xaero/*`. All players get mods via packwiz, so this is not expected to matter.
- **Vanilla-client / optional-client is actually improved** for claims: OPAC's client side is optional,
so a player without it is still protected and can use `/oclaims` chat commands.
## 6. Rollback
Re-add `mods/ftb-chunks-forge.pw.toml` (and the client config if the minimap default is wanted) and
`packwiz refresh`. FTB Chunks' claim/forceload data is untouched in the world save, so existing claims
resume. Then remove OPAC and the Xaero's mods. Keep the pre-swap world backup regardless.
Note: rolling back the FTB Quests/Teams removal is a separate, larger job - re-adding them restores the
mods but **not** any quest content or team progress that was discarded, unless `<world>/ftbquests/` was
archived first.
## 7. Follow-up: removing the rest of the FTB suite
Follow-on decision (2026-09-30): drop **FTB Quests, FTB Teams and FTB XMod Compat** as well, leaving
only FTB Backups 3 and FTB Library.
**Why FTB Teams could not go on its own** - FTB Quests has a hard runtime dependency on it, not just a
launcher one:
| Evidence | Result |
| --- | --- |
| `FTB-Quests@1.21.1/main` build.gradle | `requires('ftb-library-forge')`, `requires('ftb-teams-forge')` |
| FTB Quests bytecode | references `dev/ftb/mods/ftbteams/` in **14 classes**, incl. `FTBQuestsEventHandler` |
| FTB Quests internals | `TeamData` class - quest progress is keyed per team |
The `mandatory = false` in FTB's `neoforge.mods.toml` files is launcher metadata; the bytecode
references are what actually break. FTB Quests also ships no claim-chunk task type, so nothing about
claims depended on it.
**Why XMod Compat went with it:** its integrations were Quests/Chunks/Ranks plus JEI/KubeJS/LuckPerms
bridges. With Quests and Chunks gone there is nothing left for it to bridge.
**Why Library stayed:** FTB Backups 3 uses FTB Library's config/UI classes (`Panel`, `Theme`, `Widget`,
`ConfigGroup`, `ConfigValue`, `SNBTCompoundTag`) - 13 classes, and it does **not** bundle them, despite
declaring only `neoforge` in its own metadata. Library in turn references `dev/architectury` in 47
classes, so Architectury API stays too.
**Verified before removing:** FTB Library and FTB Backups 3 reference `ftbteams`, `ftbquests` and
`ftbxmodcompat` in **0 classes** each, and nothing else in the pack references XMod Compat.
**Consequences accepted:** any authored FTB Quests content and per-team quest progress is discarded.
There is no longer any party system to point OPAC at, so `primaryPartySystem` stays at OPAC's built-in
default. The pack dropped from 9 to **5 CurseForge-sourced mods** and from 149 to 146 mods.
## 8. Docs that need follow-up
- **Map/waypoint rescue from the old FTB data** - see `analysis/FTB-XAERO-MAP-MIGRATION.md` and the
`tools/ftbchunks-reveal.py` + `tools/prune-world-to-reveal.py` scripts. Mask extraction is validated;
the world-save pruning step is written and unit-tested but not yet run.
- `FINDINGS.md` - §3.3, §7.1, §8 rows 2-3, appendix A and appendix B updated in this change; §1's
mod-count statistics were left at their earlier snapshot (a note was added).
- `MODLIST-REVIEW.md` §7.4 and §8 still say Open Parties and Claims is a SKIP/duplication and "Keep
FTB". **Stale** - the swap is now the intended design.
- `MODLIST-GROUPS.md` / `analysis/master-table.md` / `analysis/groups-table.md` carry the same stale
OPAC verdict.
- Not done: re-adding `xaero-train-map` (was `Ov2r3aVY`, 1.0.0). It is unblocked again by this change and
the pack has Create trains; one-line addition if wanted.
+21
View File
@@ -0,0 +1,21 @@
# Archive
Finished or superseded work, kept for its reasoning. It is **not maintained**: mod counts
and verdicts reflect the pack at the time each file was written. Paths inside these files
refer to their original locations.
| File | Was at | What it is |
| --- | --- | --- |
| `FINDINGS.md` | `/FINDINGS.md` | Config / compat / performance review of the 146-mod pack (2026-10-01): OPAC config decisions, per-mod compat notes. About 70 mods stale. |
| `FTB-CHUNKS-SWAP.md` | `analysis/` | FTB Chunks → OPAC + Xaero's swap. Shipped `1d02bf1`. |
| `2026-09-candidate-review/MODLIST-REVIEW.md` | `/MODLIST-REVIEW.md` | The 189-candidate mod review behind the 60-mod expansion (assembled report) |
| `2026-09-candidate-review/MODLIST-GROUPS.md` | `/MODLIST-GROUPS.md` | Same review grouped by purpose (was duplicated as `analysis/groups-table.md`) |
| `2026-09-candidate-review/CANDIDATE-APPENDIX.md` | `analysis/` | Per-mod detail for that review |
| `2026-09-candidate-review/PACK-CONTEXT.md` | `analysis/` | The pack brief given to the reviewers |
Deleted in the 2026-10-14 cleanup:
- the review's build pieces (`report-part*.md`, `master-table.md`, `groups-table.md`) and
intermediates (`chunks/`, `assess/`, `*.json`, `candidates.txt`);
- `analysis/raw/` (Modrinth API dumps);
- `analysis/audit/*.out` (rerun the scripts);
- the applied Puzzles Lib patch.
+25
View File
@@ -0,0 +1,25 @@
import pickle,json,collections,re
A=pickle.load(open("active.pkl","rb"));act=A["active"];tc=A["tagcache"]
prod=collections.defaultdict(list); cons=collections.defaultdict(set)
tagsof=collections.defaultdict(set)
for t,s in tc.items():
for i in s: tagsof[i].add(t)
for rid,(jar,j) in act.items():
r=json.dumps(j.get("result",""))+json.dumps(j.get("results",""))+json.dumps(j.get("output",""))
for m in set(re.findall(r'"(?:id|item)": "([^"]+)"',r)): prod[m].append(rid)
body=json.dumps({k:v for k,v in j.items() if k not in("result","results","output")})
for m in re.findall(r'"item": "([^"]+)"|"id": "([^"]+)"',body):
cons[m[0] or m[1]].add(rid)
for m in re.findall(r'"tag": "([^"]+)"',body): cons["#"+m].add(rid)
def info(i):
c=set(cons[i])
for t in tagsof[i]: c|=cons["#"+t]
return len(prod[i]),len(c),sorted(prod[i])[:6]
for i in """extradelight:bread_slice moredelight:bread_slice extradelight:sliced_potato moredelight:diced_potatoes rusticdelight:potato_slices
dungeonsdelight:fiery_knife twilightdelight:fiery_knife dungeonsdelight:ironwood_knife twilightdelight:ironwood_knife dungeonsdelight:knightmetal_knife twilightdelight:knightmetal_knife dungeonsdelight:steeleaf_knife twilightdelight:steeleaf_knife
frightsdelight:poisonous_potato_crate dungeonsdelight:poisonous_potato_crate frightsdelight:rotten_tomato_crate dungeonsdelight:rotten_tomato_crate
handcrafted:wood_plate twilightforest:oak_drying_rack comforts:sleeping_bag_red handcrafted:red_sheet
adorablehamsterpets:cucumber_crate extradelight:cucumber_crate_item fruitsdelight:apple_crate extradelight:apple_crate
rusticdelight:coffee_beans_bag extradelight:coffee_bean_sack extradelight:coffee_cherry_crate
fishermans_haven:worm aquaculture:worm create:copper_nugget twilightforest:copper_nugget twilightdelight:rainbow_ice_cream twilightdelight:refreshing_ice_cream twilightdelight:twilight_ice_cream""".split():
print(i, *info(i))
+144
View File
@@ -0,0 +1,144 @@
import pickle,json,collections,sys
sys.setrecursionlimit(10000)
exec(open("recipes.py").read().split("# ---------- active recipes")[0].split("# ---------- resource-level")[0]) # imports only
A=pickle.load(open("active.pkl","rb")); active=A["active"]; tagcache=A["tagcache"]
exec(open("recipes.py").read().split("# ---------- ingredients ----------")[1].split("# ---------- active recipes")[0].replace("resolve(","R("))
def full(x): return x if ":" in x else "minecraft:"+x
def R(t): return tagcache.get(t,set())
def ns(x): return x.split(":")[0]
EMPTY=frozenset(["<empty>"])
def shaped(j):
pat=j.get("pattern"); key=j.get("key",{})
if not pat: return None
g=[]
for row in pat:
r=[]
for ch in row:
if ch==" ": r.append(EMPTY)
else:
s=ing(key.get(ch))
if s is None: return None
r.append(s)
g.append(tuple(r))
return tuple(g)
def mirror(g): return tuple(tuple(reversed(r)) for r in g)
def inter(a,b): return bool(a&b)
def grid_match(a,b):
return len(a)==len(b) and len(a[0])==len(b[0]) and all(inter(x,y) for ra,rb in zip(a,b) for x,y in zip(ra,rb))
def bip(a,b):
if len(a)!=len(b): return False
match={}
def tr(i,seen):
for k in range(len(b)):
if k in seen or not inter(a[i],b[k]): continue
seen.add(k)
if k not in match or tr(match[k],seen): match[k]=i; return True
return False
return all(tr(i,set()) for i in range(len(a)))
def out(j):
try: return result(j)
except Exception: return str(j.get("result"))[:80]
groups=collections.defaultdict(list)
for rid,(jar,j) in active.items(): groups[j.get("type","?")].append((rid,jar,j))
print({k:len(v) for k,v in sorted(groups.items(),key=lambda x:-len(x[1]))[:40]})
res=collections.defaultdict(list)
def report(kind,a,b,same):
res[(kind,same)].append((a,b))
# --- crafting: shaped vs shaped, shapeless vs shapeless, shaped vs shapeless ---
SH=[]; SL=[]
for rid,jar,j in groups["minecraft:crafting_shaped"]:
g=shaped(j)
if g: SH.append((rid,g,out(j)))
for rid,jar,j in groups["minecraft:crafting_shapeless"]:
l=[ing(x) for x in j.get("ingredients",[])]
if None in l or not l: continue
SL.append((rid,l,out(j)))
idx=collections.defaultdict(set)
for i,(rid,g,o) in enumerate(SH):
for it in g[0][0]: idx[(len(g),len(g[0]),it)].add(i)
pairs=set()
for i,(rid,g,o) in enumerate(SH):
for gg in (g,mirror(g)):
for it in gg[0][0]:
for k in idx[(len(g),len(g[0]),it)]:
if k>i and grid_match(gg,SH[k][1]): pairs.add((i,k))
for i,k in pairs:
a,b=SH[i],SH[k]; report("shaped",(a[0],a[2]),(b[0],b[2]),a[2]==b[2])
idx=collections.defaultdict(set)
for i,(rid,l,o) in enumerate(SL):
for it in set().union(*l): idx[(len(l),it)].add(i)
pairs=set()
for i,(rid,l,o) in enumerate(SL):
small=min(l,key=len)
for it in small:
for k in idx[(len(l),it)]:
if k>i and bip(l,SL[k][1]): pairs.add((i,k))
for i,k in pairs:
a,b=SL[i],SL[k]; report("shapeless",(a[0],a[2]),(b[0],b[2]),a[2]==b[2])
# shaped vs shapeless (shapeless matches any arrangement with same nonempty cells)
for rid,g,o in SH:
cells=[c for r in g for c in r if c is not EMPTY]
for it in cells[0]:
for k in idx[(len(cells),it)]:
if bip(cells,SL[k][1]): report("shaped~shapeless",(rid,o),(SL[k][0],SL[k][2]),o==SL[k][2])
# --- single-input processing types ---
def single(j):
x=j.get("ingredient")
if x is None and isinstance(j.get("ingredients"),list) and len(j["ingredients"])==1: x=j["ingredients"][0]
return ing(x) if x is not None else None
for t in ["minecraft:smelting","minecraft:blasting","minecraft:smoking","minecraft:campfire_cooking"]:
L=[(rid,single(j),out(j)) for rid,jar,j in groups[t]]
L=[x for x in L if x[1]]
idx=collections.defaultdict(list)
for i,(rid,s,o) in enumerate(L):
for it in s: idx[it].append(i)
seen=set()
for it,ix in idx.items():
for a in ix:
for b in ix:
if a<b and (a,b) not in seen:
seen.add((a,b)); report(t,(L[a][0],L[a][2]),(L[b][0],L[b][2]),L[a][2]==L[b][2])
# cutting board
def tool(j):
t=j.get("tool")
if isinstance(t,dict) and t.get("type","").endswith("ability"): return ("ability",t.get("action") or t.get("item_ability"))
s=ing(t); return ("items",s)
def tools_overlap(a,b):
if a[0]!=b[0]: return False
return a[1]==b[1] if a[0]=="ability" else inter(a[1] or frozenset(),b[1] or frozenset())
for t in [k for k in groups if k.endswith(":cutting")]:
L=[]
for rid,jar,j in groups[t]:
s=ing(j.get("ingredients"));
if s: L.append((rid,s,tool(j),out(j)))
idx=collections.defaultdict(list)
for i,x in enumerate(L):
for it in x[1]: idx[it].append(i)
seen=set()
for it,ix in idx.items():
for a in ix:
for b in ix:
if a<b and (a,b) not in seen and tools_overlap(L[a][2],L[b][2]):
seen.add((a,b)); report(t,(L[a][0],L[a][3],sorted(L[a][1])[:2] if len(L[a][1])>2 else sorted(L[a][1])),(L[b][0],L[b][3]),L[a][3]==L[b][3])
# multi-ingredient machine types (exact multiset w/ overlap): cooking pots, mixers etc
GENERIC=[k for k in groups if not k.startswith("minecraft:") and not k.endswith(":cutting")]
for t in GENERIC:
L=[]
for rid,jar,j in groups[t]:
x=j.get("ingredients") or ([j["ingredient"]] if "ingredient" in j else None)
if not isinstance(x,list): continue
l=[ing(y) for y in x]
if None in l or not l or any(len(s)==0 for s in l): continue
extra=json.dumps({k:j.get(k) for k in ("container","tool","heatRequirement","heat_requirement","processing_time","fluid","inputFluid") if k in j},sort_keys=True)
L.append((rid,l,out(j),extra))
idx=collections.defaultdict(set)
for i,(rid,l,o,e) in enumerate(L):
for it in set().union(*l): idx[(len(l),it)].add(i)
for i,(rid,l,o,e) in enumerate(L):
for it in min(l,key=len):
for k in idx[(len(l),it)]:
if k>i and L[k][3]==e and bip(l,L[k][1]):
report(t,(rid,o),(L[k][0],L[k][2]),o==L[k][2])
pickle.dump(dict(res),open("coll.pkl","wb"))
for (kind,same),l in sorted(res.items()):
print(f"## {kind} {'SAME-OUTPUT(dup)' if same else 'CONFLICT'}: {len(set(map(str,l)))}")
+42
View File
@@ -0,0 +1,42 @@
import pickle,re
D=pickle.load(open("data.pkl","rb")); mods=D["mods"]
mods.setdefault("minecraft",[dict(jar="base",version="1.21.1",deps=[],nested=False)])
def vt(v): return [int(x) if x.isdigit() else x for x in re.split(r'[.\-+_]',v)]
def cmp(a,b):
a,b=vt(a),vt(b)
for x,y in zip(a,b):
if x==y: continue
if type(x)==type(y): return -1 if x<y else 1
return -1 if isinstance(x,str) else 1
return (len(a)>len(b))-(len(a)<len(b))
def inrange(v,r):
r=r.strip()
if not r or r=="*": return True
for part in re.findall(r'[\[\(][^\]\)]*[\]\)]',r) or [None]:
if part is None: return cmp(v,r)>=0
lo,_,hi=part[1:-1].partition(",")
if "," not in part:
if cmp(v,lo)==0: return True
continue
ok=True
if lo.strip(): c=cmp(v,lo.strip()); ok&= c>0 or (c==0 and part[0]=="[")
if hi.strip(): c=cmp(v,hi.strip()); ok&= c<0 or (c==0 and part[-1]=="]")
if ok: return True
return False
# duplicate modids among top-level jars
for mid,l in mods.items():
top=[x for x in l if not x["nested"]]
if len(top)>1: print("DUP-MODID",mid,[x["jar"] for x in top])
for mid,l in sorted(mods.items()):
e=[x for x in l if not x["nested"]] or l
for d in e[0]["deps"]:
t=str(d.get("type",d.get("mandatory") and "required" or "optional")).lower()
if d.get("mandatory") is True: t="required"
did=d.get("modId"); side=d.get("side","BOTH")
if t not in("required","incompatible","discouraged"): continue
if did not in mods:
if t=="required": print("MISSING",mid,"->",did,d.get("versionRange"),side)
continue
v=mods[did][0]["version"]; r=d.get("versionRange","")
if t=="required" and not v.startswith("$") and not inrange(v,r): print("VERSION",mid,"->",did,"needs",r,"have",v)
if t in("incompatible","discouraged") and (not r or inrange(v,r)): print(t.upper(),mid,"->",did,r,"have",v)
+29
View File
@@ -0,0 +1,29 @@
import pickle,collections,re,json
D=pickle.load(open("data.pkl","rb")); A=pickle.load(open("active.pkl","rb")); tc=A["tagcache"]
models=D["models"]
name={}
for k,v in D["lang"].items():
_,ns,p=k.split("."); name.setdefault(ns+":"+p,v[0][1])
spec=collections.defaultdict(set)
for t,s in tc.items():
if t.startswith("c:") and "/" in t and len(s)<=8:
for i in s: spec[i].add(t)
new=set("""extradelight moredelight rusticdelight dungeonsdelight frightsdelight fruitsdelight minersdelight veggiesdelight overweight_farming barbequesdelight fishermans_haven fowlplay etched music_disc_maker too_many_bows mermod tarotcards chalk charta comforts petcemetery friendsandfoes""".split())
def norm(n): return re.sub(r'[^a-z]','',n.lower())
by=collections.defaultdict(set)
for i,n in name.items():
if i in models and isinstance(n,str) and "spawn_egg" not in i and not i.startswith(("sophisticated",)):
by[norm(n)].add(i)
# also group by registry path
for i in models:
p=i.split(":")[1]
if len(p)>3: by["path:"+p].add(i)
seen=set()
for k,s in sorted(by.items()):
nss={i.split(":")[0] for i in s}
if len(nss)<2 or "minecraft" in nss or not (nss&new): continue
key=frozenset(s)
if key in seen: continue
seen.add(key)
s=sorted(s); common=set.intersection(*[spec.get(i,set()) for i in s])
print(("UNIFIED " if common else "SPLIT ")+name.get(s[0],s[0])+": "+", ".join(s)+((" via "+",".join(sorted(common))) if common else ""))
+48
View File
@@ -0,0 +1,48 @@
import zipfile, io, json, tomllib, re, pickle, os, glob
def lenient_json(b):
s=b.decode("utf-8-sig",errors="replace")
try: return json.loads(s)
except Exception:
s2=re.sub(r'//[^\n]*','',s); s2=re.sub(r',(\s*[}\]])',r'\1',s2)
try: return json.loads(s2)
except Exception: return None
mods={} # modid -> dict(jar, version, deps, nested)
files=[] # (jar, path, json) for data recipe/tags + lang
lang={}; models=set(); errors=[]
def walk(z, jarname, nested=False):
names=z.namelist()
for t in ("META-INF/neoforge.mods.toml","META-INF/mods.toml"):
if t in names:
raw=z.read(t).decode("utf-8",errors="replace")
try: m=tomllib.loads(raw)
except Exception as e: errors.append((jarname,"toml",str(e))); m={}
for md in m.get("mods",[]):
mid=md.get("modId"); ver=str(md.get("version"))
if ver.startswith("${"):
mf=z.read("META-INF/MANIFEST.MF").decode(errors="replace") if "META-INF/MANIFEST.MF" in names else ""
mm=re.search(r'Implementation-Version: *(\S+)',mf); ver=mm.group(1) if mm else ver
deps=m.get("dependencies",{}).get(mid,[])
mods.setdefault(mid,[]).append(dict(jar=jarname,version=ver,deps=deps,nested=nested,file=t))
break
for n in names:
if n.startswith("META-INF/jarjar/") and n.endswith(".jar"):
try: walk(zipfile.ZipFile(io.BytesIO(z.read(n))), jarname+"!"+n.split("/")[-1], True)
except Exception as e: errors.append((jarname,n,str(e)))
elif n.startswith("data/") and n.endswith(".json"):
p=n.split("/")
if len(p)>=4 and (p[2] in("recipe","recipes") or (p[2]=="tags" and p[3] in("item","items","block","blocks"))):
j=lenient_json(z.read(n))
if j is None: errors.append((jarname,n,"badjson"))
files.append((jarname,n,j))
elif n.startswith("assets/") and n.endswith("lang/en_us.json"):
j=lenient_json(z.read(n))
if isinstance(j,dict):
for k,v in j.items():
if k.startswith(("item.","block.")) and k.count(".")==2: lang.setdefault(k,[]).append((jarname,v))
elif n.startswith("assets/") and "/models/item/" in n and n.endswith(".json"):
p=n.split("/"); models.add(p[1]+":"+"/".join(p[4:])[:-5])
jars=["base_minecraft.jar","base_neoforge.jar"]+sorted(glob.glob("jars/*.jar"))
for j in jars: walk(zipfile.ZipFile(j), os.path.basename(j))
pickle.dump(dict(mods=mods,files=files,lang=lang,models=models,errors=errors),open("data.pkl","wb"))
print(len(mods),len(files),len(lang),len(models),len(errors))
for e in errors[:20]: print(e)
+21
View File
@@ -0,0 +1,21 @@
import tomllib, hashlib, os, glob, shutil, urllib.request, concurrent.futures as cf
P="/home/logan/Development/onion-mc/mods"
srcs=[os.path.expanduser("~/.local/share/PrismLauncher/instances/Onion MC/minecraft/mods"),"/tmp/fd/jars"]
def h(p): return hashlib.new
def one(f):
t=tomllib.load(open(f,"rb")); fn=t["filename"]; d=t["download"]; out=f"jars/{fn}"
def ok(p):
hh=hashlib.new(d["hash-format"]); hh.update(open(p,"rb").read()); return hh.hexdigest()==d["hash"]
if os.path.exists(out) and ok(out): return fn,"cached"
for s in srcs:
p=os.path.join(s,fn)
if os.path.exists(p) and ok(p): shutil.copy(p,out); return fn,"local"
url=d.get("url")
if not url: return fn,"NOURL "+str(t.get("update"))
req=urllib.request.Request(url.replace(" ","%20"),headers={"User-Agent":"onion-mc-audit"})
open(out,"wb").write(urllib.request.urlopen(req,timeout=120).read())
return fn,"dl" if ok(out) else "BADHASH"
with cf.ThreadPoolExecutor(8) as ex:
for fn,s in ex.map(one,sorted(glob.glob(P+"/*.pw.toml"))):
if s not in("cached","local","dl"): print(s,fn)
print(len(os.listdir("jars")))
+107
View File
@@ -0,0 +1,107 @@
"""Generate config/paxi/datapacks/onion-fixes from the audit findings.
Run from repo root after extract.py has built /tmp/onion/data.pkl."""
import json, os, pickle, shutil, copy
D = pickle.load(open("/tmp/onion/data.pkl", "rb"))
orig = {}
for jar, p, j in D["files"]:
orig.setdefault(p, {})[jar.split("-")[0]] = j
OUT = "config/paxi/datapacks/onion-fixes"
shutil.rmtree(OUT, ignore_errors=True)
def write(path, obj):
f = os.path.join(OUT, path)
os.makedirs(os.path.dirname(f), exist_ok=True)
with open(f, "w") as fh:
json.dump(obj, fh, indent=2); fh.write("\n")
def recipe(rid, obj):
ns, p = rid.split(":")
write(f"data/{ns}/recipe/{p}.json", obj)
def disable(rid):
recipe(rid, {"neoforge:conditions": [{"type": "neoforge:false"}]})
def src(path, mod):
return copy.deepcopy(orig[path][mod])
write("pack.mcmeta", {"pack": {"pack_format": 48,
"description": "Onion MC cross-mod recipe/tag fixes"}})
KNIFE = {"tag": "c:tools/knife"}
def cut(ing, out, n):
return {"type": "farmersdelight:cutting", "ingredients": [ing],
"result": [{"item": {"id": out, "count": n}}], "tool": KNIFE}
# 1. Potato: one board recipe per input, chained so each mod keeps its yield.
# potato -> 2 rustic slices (rustic's recipe, untouched)
# rustic slice -> 2 extradelight sliced potato (4/potato, as before)
# 2 rustic slices -> 1 moredelight diced potatoes (1/potato, as before;
# moredelight's Create saw recipe still gives 2/potato)
recipe("extradelight:cutting/sliced_potato",
cut({"item": "rusticdelight:potato_slices"}, "extradelight:sliced_potato", 2))
disable("moredelight:cutting/diced_potatoes")
recipe("onion:diced_potatoes_from_potato_slices", {
"type": "minecraft:crafting_shapeless", "category": "misc",
"ingredients": [{"item": "rusticdelight:potato_slices"}] * 2,
"result": {"id": "moredelight:diced_potatoes", "count": 1}})
# Slices are not whole potatoes (dupe loop via stews / re-cutting).
write("data/c/tags/item/crops/potato.json", {"replace": True, "values": [
"minecraft:potato", "veggiesdelight:sweet_potato",
{"id": "#forge:crops/potato", "required": False}]})
# 2. Bread: extradelight's slice wins; moredelight's sandwiches use c:bread_slices.
# Widen extradelight's input to c:foods/bread so cauliflower bread still slices.
r = src("data/extradelight/recipe/cutting/bread_knife.json", "extradelight")
r["ingredients"] = [{"tag": "c:foods/bread"}]
recipe("extradelight:cutting/bread_knife", r)
disable("moredelight:cutting/bread_slice")
# 3. Coffee: raw rustic beans are not roasted beans; crates use their own items.
write("data/c/tags/item/crops/coffee/cooked.json", {"replace": True, "values": [
"extradelight:coffee_beans", "rusticdelight:roasted_coffee_beans"]})
for rid, item in [("extradelight:coffee_cherries_to_block", "extradelight:coffee_cherries"),
("extradelight:coffee_beans_to_block", "extradelight:coffee_beans")]:
ns, p = rid.split(":")
r = src(f"data/{ns}/recipe/{p}.json", "extradelight")
r["ingredients"] = [{"item": item}] * 9
recipe(rid, r)
# 4. Twilight Forest knives: twilightdelight owns them; drop dungeonsdelight's copies.
for k in ["fiery_knife", "fiery_iron_knife", "ironwood_knife", "knightmetal_knife", "steeleaf_knife"]:
disable(f"dungeonsdelight:integration/twilightforest/crafting_table/{k}")
# 5. Crates: keep one crafting recipe per crate (uncrafting recipes untouched).
disable("minecraft:poisonous_potato_crate") # dungeonsdelight's; frightsdelight keeps it
disable("minecraft:rotten_tomato_crate") # dungeonsdelight's; frightsdelight keeps it
disable("fruitsdelight:apple_crate") # extradelight keeps it
disable("adorablehamsterpets:cucumber_crate") # extradelight's accepts any c:crops/cucumber
# 6. Handcrafted sheets: shapeless 3 wool collided with Comforts' vertical
# sleeping bags; make sheets a horizontal row.
COLORS = "white orange magenta light_blue yellow lime pink gray light_gray cyan purple blue brown green red black".split()
for c in COLORS:
r = src(f"data/handcrafted/recipe/{c}_sheet.json", "handcrafted")
recipe(f"handcrafted:{c}_sheet", {"type": "minecraft:crafting_shaped",
"category": r.get("category", "misc"), "key": {"#": r["ingredients"][0]},
"pattern": ["###"], "result": r["result"]})
# 7. Handcrafted wood plate (any 3 slabs in a row) shadowed all TF drying racks.
r = src("data/handcrafted/recipe/wood_plate.json", "handcrafted")
r["pattern"] = ["##"]
r["result"]["count"] = 2
recipe("handcrafted:wood_plate", r)
# 8. Fisherman's Haven worms can: one recipe, plus 1:1 worm conversions.
disable("fishermans_haven:recipe_worms_aquaculture")
for a, b in [("fishermans_haven:worm", "aquaculture:worm"), ("aquaculture:worm", "fishermans_haven:worm")]:
recipe(f"onion:{b.split(':')[0]}_worm_from_{a.split(':')[0]}_worm", {
"type": "minecraft:crafting_shapeless", "category": "misc",
"ingredients": [{"item": a}], "result": {"id": b, "count": 1}})
# 9. Twilight Delight ice creams need Neapolitan (absent): gate instead of log errors.
for k in ["rainbow_ice_cream", "refreshing_ice_cream", "twilight_ice_cream"]:
r = src(f"data/twilightdelight/recipe/{k}.json", "twilightdelight")
r = {"neoforge:conditions": [{"type": "neoforge:mod_loaded", "modid": "neapolitan"}], **r}
recipe(f"twilightdelight:{k}", r)
# 10. Beef stew: rustic replaces it, extradelight disables it -> load-order roulette.
# Pin rustic's version so the cooking pot recipe always exists.
recipe("farmersdelight:cooking/beef_stew",
src("data/farmersdelight/recipe/cooking/beef_stew.json", "rusticdelight"))
print("ok")
+161
View File
@@ -0,0 +1,161 @@
import pickle,json,collections,re,itertools
D=pickle.load(open("data.pkl","rb"))
mods=set(D["mods"])|{"minecraft","c","neoforge"}
files=D["files"]
# ---------- resource-level overrides ----------
byres=collections.defaultdict(list)
for jar,path,j in files: byres[path].append((jar,j))
legacy=collections.Counter()
for path,l in byres.items():
p=path.split("/")
if p[2]=="recipes" or (p[2]=="tags" and p[3] in("items","blocks")): legacy[l[0][0]]+=1
# ---------- tags ----------
def is_tag_path(p): return p[2]=="tags" and p[3]=="item"
tagdefs=collections.defaultdict(list) # tag -> [(jar, values, replace)]
for path,l in byres.items():
p=path.split("/")
if not is_tag_path(p): continue
tag=p[1]+":"+"/".join(p[4:])[:-5]
for jar,j in l:
if not isinstance(j,dict): continue
tagdefs[tag].append((jar,j.get("values",[]),j.get("replace",False)))
items=set()
for k in D["lang"]:
_,ns,pa=k.split("."); items.add(ns+":"+pa)
items|=D["models"]
def ns(x): return x.split(":")[0] if ":" in x else "minecraft"
def full(x): return x if ":" in x else "minecraft:"+x
tagcache={}; tagissues=[]
def resolve(tag,stack=()):
if tag in tagcache: return tagcache[tag]
if tag in stack: return set()
out=set()
for jar,vals,rep in tagdefs.get(tag,[]):
if rep: out=set()
for v in vals:
req=True
if isinstance(v,dict): req=v.get("required",True); v=v.get("id")
if not isinstance(v,str): continue
if v.startswith("#"):
t=full(v[1:])
if req and t not in tagdefs: tagissues.append((tag,jar,v,"missing-tag"))
out|=resolve(t,stack+(tag,))
else:
v=full(v)
if v not in items:
if req: tagissues.append((tag,jar,v,"missing-item" if ns(v) in mods else "missing-mod-item"))
continue
out.add(v)
tagcache[tag]=out; return out
for t in list(tagdefs): resolve(t)
# ---------- conditions ----------
unknown_cond=collections.Counter()
def cond(c):
if isinstance(c,list): return all(cond(x) for x in c)
t=c.get("type","")
if t in("neoforge:mod_loaded","forge:mod_loaded"): return c.get("modid") in mods
if t in("neoforge:not","forge:not"): return not cond(c["value"])
if t in("neoforge:and","forge:and"): return all(cond(x) for x in c["values"])
if t in("neoforge:or","forge:or"): return any(cond(x) for x in c["values"])
if t in("neoforge:item_exists","forge:item_exists"): return full(c["item"]) in items
if t in("neoforge:tag_empty","forge:tag_empty"): return not resolve(full(c["tag"]))
if t in("neoforge:true","forge:true"): return True
if t in("neoforge:false","forge:false"): return False
unknown_cond[t]+=1; return True
# ---------- ingredients ----------
def ing(x):
"""-> frozenset of items, or None if unknown type"""
if x is None: return frozenset()
if isinstance(x,str):
return frozenset(resolve(full(x[1:]))) if x.startswith("#") else frozenset([full(x)])
if isinstance(x,list):
s=set()
for y in x:
r=ing(y)
if r is None: return None
s|=r
return frozenset(s)
if isinstance(x,dict):
if "item" in x and isinstance(x["item"],str): return frozenset([full(x["item"])])
if "tag" in x: return frozenset(resolve(full(x["tag"])))
if "id" in x and "count" in x and isinstance(x["id"],str): return frozenset([full(x["id"])])
if "ingredient" in x: return ing(x["ingredient"])
t=x.get("type","")
if t in("neoforge:compound",): return ing(x.get("children"))
if t=="neoforge:components": return ing(x.get("items"))
if "items" in x: return ing(x["items"])
if t=="neoforge:difference":
b=ing(x.get("base")); s=ing(x.get("subtracted"))
return None if b is None or s is None else b-s
if t=="neoforge:intersection":
ch=[ing(c) for c in x.get("children",[])]
if None in ch or not ch: return None
return frozenset(set.intersection(*map(set,ch)))
return None
def result(j):
r=j.get("result")
if r is None: return None
if isinstance(r,str): return (full(r),1)
if isinstance(r,dict):
i=r.get("id") or r.get("item")
if isinstance(i,dict): i=i.get("id") or i.get("item")
return (full(i) if isinstance(i,str) else str(i), r.get("count",1))
if isinstance(r,list):
return tuple(sorted(result({"result":(e.get("item",e) if isinstance(e,dict) else e)}) + ((e.get("chance",1),) if isinstance(e,dict) else ()) for e in r))
return str(r)
# ---------- active recipes (last jar wins per path; note collisions) ----------
active={}; overrides=[]; missingrefs=[]
for path,l in byres.items():
p=path.split("/")
if p[2]!="recipe" or not path.endswith(".json"): continue
rid=p[1]+":"+"/".join(p[3:])[:-5]
live=[]
for jar,j in l:
if not isinstance(j,dict): continue
c=j.get("neoforge:conditions")
if c is not None and not cond(c): continue
live.append((jar,j))
if len(live)>1:
uniq={json.dumps(j,sort_keys=True) for _,j in live}
if len(uniq)>1: overrides.append((rid,[jr for jr,_ in live]))
if live: active[rid]=live[-1]
# cross-mod overrides of another namespace's recipes (single providing jar from other mod)
owner={}
for m,l in D["mods"].items():
for x in l: owner.setdefault(x["jar"].split("!")[0],set()).add(m)
foreign=collections.defaultdict(list)
for rid,(jar,j) in active.items():
n=ns(rid)
if n not in owner.get(jar,set()) and jar not in("base_minecraft.jar",) and n not in("c",):
foreign[(jar,n)].append(rid)
# ---------- unknown refs in active recipes ----------
def refs(o,acc):
if isinstance(o,dict):
for k,v in o.items():
if k in("item","id","tag") and isinstance(v,str) and k!="type": acc.append((k,v))
else: refs(v,acc)
elif isinstance(o,list):
for v in o: refs(v,acc)
return acc
for rid,(jar,j) in active.items():
if j.get("type","").startswith(("neoforge:","forge:")) and "conditional" in j.get("type",""): continue
for k,v in refs({kk:vv for kk,vv in j.items() if kk not in("neoforge:conditions",)},[]):
if k=="tag": continue
if ":" in v and ns(v) not in mods and not v.startswith("#"):
missingrefs.append((rid,jar,v)); break
pickle.dump(dict(active=active,tagcache=tagcache,items=items),open("active.pkl","wb"))
print("== legacy (pre-1.21 'recipes/' or 'tags/items/') files, inert ==")
for k,v in legacy.most_common(): print(f" {k}: {v}")
print("== recipe path collisions with differing content (load order decides) ==",len(overrides))
for r,js in overrides: print(" ",r,js)
print("== jars overriding another namespace's recipes ==")
for (jar,n),l in sorted(foreign.items(),key=lambda x:-len(x[1])): print(f" {jar} -> {n}: {len(l)} e.g. {l[:3]}")
print("== active recipes referencing items from absent mods (will fail to load) ==",len(missingrefs))
for x in missingrefs[:80]: print(" ",x)
print("== tag issues (required entry missing => whole tag fails to load) ==")
seen=set()
for t,jar,v,k in tagissues:
if (t,v) in seen: continue
seen.add((t,v)); print(" ",k,t,"<-",v,"from",jar)
print("== unknown condition types (assumed true) ==",dict(unknown_cond))
print("active recipes",len(active))
+190
View File
@@ -0,0 +1,190 @@
# Phase 2 benchmark matrix
This replaces `LEVERS.md` §6. It is meant to run without judgement calls: every config is a
generated directory, every scene is a datapack function, and every block ends in a decision
rule. The measurement protocol and machine prep are in `../../phase0/P{0,1,2}-*.md`
(uncapped, vsync off, p99 frametime + 1 % low, 15 s warm-up discarded, 3 runs, one variable
per comparison). This file adds what phase0 leaves open: the world, the scenes, the configs
and the order.
Machines: **P0** Steam Deck · **P1** 7700K + GTX 1060 6 GB (Windows) · **P2** 6700K + 1080 Ti,
~8 GB free RAM.
---
## 0. Prerequisite: boot check
The pack has never booted at its current size: 217 mods after `a01bc3b` added Default
Options, Sodium Extra, Reese's Sodium Options, BadOptimizations and Particle Core. Before
any timing, launch once and check `latest.log` for:
- missing deps and mixin apply failures;
- `fishermans-haven` biome-modifier errors;
- `ftbteams` references;
- Default Options' `Populated … with default options` lines (proves the seeding works);
- no YACL "Failed to deserialize" lines for `fowlplay` or `crittersandcompanions`
(spawn tuning, `SPAWN-TUNING.md`);
- a working Sodium video-settings screen (Reese's Sodium Options replaces it; SSRD injects a
slider into it).
r5 also flags a reported Iris 1.8.14-beta.1 ↔ Sodium 0.8.13 mixin incompatibility on
NeoForge 1.21.1, with a third-party "Iris-Sodium Compat" patch on CurseForge. Check for it
with a shaderpack enabled. **If the pack doesn't boot cleanly, fix that first. Bisect the
five new mods before anything older.**
## 1. Fixed inputs
| Input | Value |
| --- | --- |
| Pack | one commit, recorded in every row (`git rev-parse --short HEAD`). The Puzzles Lib bump goes in before B0 if approved, never mid-matrix. |
| Bench instance | a **separate** Prism instance per machine with the packwiz `PreLaunchCommand` **removed**, so it can't self-update mid-matrix. Mods are copied from a normal install of the recorded commit. |
| Java | Temurin 21 on all three (phase0). Check `java -XX:+UseShenandoahGC -version` works (needed for J3). |
| World | `bench-world-v1.zip`, built once on P2 (§2), sha256 recorded, unpacked identically on every machine |
| Configs | `analysis/client-perf/proposed-configs/out/<ID>/`, generated by `build.py` at the recorded commit |
| In-game pins | phase0 Step 7/8. The `options.txt` keys that matter are already in every bench variant (`maxFps:260`, `enableVsync:false`, `renderDistance:8`, `simulationDistance:8`). GUI scale, FOV, resolution and fullscreen are recorded per machine and never changed. |
| Resource packs / shaders | none, unless the block says otherwise |
### Applying a config
With the game closed, run
`python3 proposed-configs/apply_variant.py <ID> <instance>/.minecraft`. It copies all 10
config files (every variant has every file, so nothing from a previous variant survives)
and **merges** `options.txt` key by key, so GUI scale, resolution and keybinds are kept.
It also writes the ID to `.minecraft/BENCH_VARIANT.txt`; check it before each run. On
Windows without Python, copy the `config/` tree by hand and edit the listed `options.txt`
keys in game. Default Options seeds only missing files, so it never interferes.
**Relaunch for every config change**, even ones a mod could hot-reload. Run all of that
config's scenes in one session, in the order S1 → S2 → S3 → S4 → S5 → S6.
### JVM sets (Prism → Java arguments; `-Xmx` per block)
All four were checked to parse on OpenJDK 21.0.12.
| ID | Arguments (plus `-Xmx`) |
| --- | --- |
| **J0** (phase0 default) | `-XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1` |
| **J1** (client G1, brucethemoose) | `-XX:+UseG1GC -XX:MaxGCPauseMillis=37 -XX:G1HeapRegionSize=16M -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=23 -XX:G1ReservePercent=20 -XX:G1MixedGCCountTarget=3 -XX:G1HeapWastePercent=20 -XX:InitiatingHeapOccupancyPercent=10 -XX:G1RSetUpdatingPauseTimePercent=0 -XX:MaxTenuringThreshold=1 -XX:GCTimeRatio=99 -XX:+DisableExplicitGC -XX:+PerfDisableSharedMem` |
| **J2** (Generational ZGC) | `-XX:+UseZGC -XX:+ZGenerational -XX:+DisableExplicitGC -XX:+PerfDisableSharedMem`. Plain `-XX:+UseZGC` on JDK 21 is the old non-generational ZGC. |
| **J3** (Shenandoah) | `-XX:+UseShenandoahGC -XX:+DisableExplicitGC -XX:+PerfDisableSharedMem`. **Not** r6's `-XX:ShenandoahGCMode=iu`: on JDK 21 that aborts VM startup unless `-XX:+UnlockExperimentalVMOptions` is added (tested), and the mode is gone in later JDKs. |
Default heap outside B4 (phase0): P0 `-Xmx5G`, P1 `-Xmx8G`, P2 `-Xmx6G`, all with J0.
---
## 2. Bench world (built once on P2)
1. Copy `bench/onion-bench/` into a new world's `datapacks/` folder. Create the world:
Creative, cheats on, seed `onionbench`, default world type. Run `/function bench:setup`.
2. **S2**: `/function bench:s2`, then `/function bench:s2_setup`. Check that the shafts and
cogwheels **rotate**. If the `/fill`-placed network didn't initialise, break and re-place
one motor per row by hand. Accept when F3 shows Flywheel instances and every row turns.
3. **S3**: `/function bench:s3`, then `/function bench:s3_setup`. Check the counts with
`/execute if entity @e[type=villager,x=1999,y=150,z=-1,dx=25,dy=8,dz=25]` (expect 30).
4. **S4**: `/function bench:s4` (camera at 3000 172 -10, facing south). Build one
Aeronautics test ship (about 5×7×15 blocks, planks + glass + 1 propeller) and assemble it
so it hovers at y≈170. Copy it with a Create schematic to get **4 ships, centred at z =
32, 96, 192 and 384** (2, 6, 12 and 24 chunks away). SSRD 16 then cuts only the 384 one.
Record the sub-level count Sable shows. This is the only hand-built scene. It's baked into
the zip, so runs never rebuild it. To get the 0- and 1-ship states, break ships into
blocks *in a copy of the world*: `bench-world-v1-s4-0.zip` and `-s4-1.zip`.
5. **DH pre-generation**: use the upstream DH file
(`data/observed-default-configs/DistantHorizons.toml`: `SURFACE_THEN_CHUNKS`, radius
256), then stand at S1 until DH's F3 generation queue reads 0.
Run `/function bench:tour_start` with the same settings, waiting at each hop until the
queue drains (re-issue `bench:tour/stepN` by hand). Do the same at S4. Every bench config
then runs with `generatorPlan = "DISABLED"`, so no world generation happens during
measurement.
6. Leave the world at S1, save, quit and zip it.
### Scenes
| ID | Command | What it loads | Capture |
| --- | --- | --- | --- |
| **S1** vista | `/function bench:s1` | 0 200 0, facing south, 20° down. Pure terrain + DH LODs | 15 s warm-up + 30 s, hands off |
| **S2** Create floor | `/function bench:s2` | 2,304 kinetic BEs (Flywheel instancing; vanilla BE path under shaders) | 15 + 30 s |
| **S2s** sound storm | S2 + `/function bench:storm_on` (stop: `storm_off`) | 24 sounds/tick from 3 capped Create IDs (8 per ID per tick vs cap 4) | 15 + 30 s, sound on |
| **S3** mob pen | `/function bench:s3` | 30 villagers, 15 cows, 15 sheep, 10 allays carrying torches (moving LDL sources), 10 dropped torches | 15 + 30 s |
| **S4** airships | `/function bench:s4` | 0 / 1 / 4 Sable sub-levels at 2–24 chunks | 15 + 30 s |
| **S5** tour | `/function bench:s1`, wait 10 s, then `/function bench:tour_start` | 24 teleports of 160 blocks every 5 s along +x: chunk + LOD streaming, GC churn | **all 120 s, no warm-up discard** |
| **S6** GUI | at S1, press E with the JEI overlay open; mouse parked over the JEI list | GUI / JEI / tooltip path | 15 + 30 s |
---
## 3. Config IDs
Generated by `proposed-configs/build.py`. `out/MANIFEST.md` lists every changed key.
**C0** = every file at upstream default; **C1** = the proposed pack default (= mid tier).
All other IDs are C1 with **one** change.
| ID | Change vs C1 |
| --- | --- |
| C0 | everything back to upstream (DH 256 / SSAO on / 4 threads, Flywheel SMOOTH, LDL fancy, SPR 32/4, mip 4, blend 2, ModernFix dynres off, …) |
| C1-deck, C1-high | the tier deltas from `proposed-configs/README.md` §2 |
| dh-r256 / dh-r192 / dh-r64 | DH LOD radius (C1 = 128) |
| dh-off | C1, then DH options → *Enable Rendering* off after each launch. That toggle is GUI-only and never written to the file. |
| dh-ssao / dh-thr4 / dh-qlow | SSAO on / 4 DH threads / DH's LOW quality preset at radius 128 |
| fw-flat / fw-smooth | Flywheel `lightSmoothness` (C1 = TRI_LINEAR) |
| mf-dynres-off | ModernFix `dynamic_resources=false` |
| ssrd-32 / -64 / -128 | SSRD `physicsRenderDistance` (C1 = 16) |
| sable-noshade | Sable `sub_level_dynamic_shading=false` |
| emf-lod20 / emf-lod8 | EMF LOD 20 + Average (= upstream) / LOD 8 + `retainDetailOnLowFps=false` |
| ldl-fancy / ldl-fastest | LambDynamicLights mode (C1 = fast); `fastest` also turns entity light sources off |
| spr-up / spr-strict / spr-nocap | SPR rays 32/4 (upstream) / `strict_occlusion=true` / Create sound caps removed |
| sod-thr3 | Sodium `chunk_builder_threads=3` (C1 = 0 = auto = 2) |
| opt-blend2 / opt-blend0 / opt-mip4 | biome blend 5×5 / off; mipmap 4 |
---
## 4. Blocks, in run order
"Win" means the median p99 frametime improves by more than that machine's **B0 spread**, and
the 1 % low doesn't get worse by more than the spread. Ties keep the upstream value (less
change). FPS floors for tier picks: **P0 1 % low ≥ 30**, **P1/P2 1 % low ≥ 60**.
| Block | Machines | Configs (× JVM) | Scenes | Records | Decision rule |
| --- | --- | --- | --- | --- | --- |
| **B0** noise floor | all | C0 ×3 sessions | S1 | p99 / 1 % spread | Defines each machine's threshold. P2 also checks `MemAvailable` drift (phase0 Step 4). |
| **B1** baseline | all | C0 | S1–S6 | + `Game took N seconds to start` (ModernFix log line), the `Created: …blocks.png-atlas` line, `/sparkc heapsummary` after S1 loads, VRAM peak (P1/P2) | Reference numbers for everything below |
| **B2** composite | all; tiers on their machine | C1 (all), C1-deck (P0), C1-high (P2) | S1–S6 | as B1 | If C1 isn't a win on ≥ 2 machines, stop and bisect with B3–B11 before shipping anything |
| **B3** Distant Horizons | all | dh-r256, dh-r192, C1, dh-r64, dh-off | S1, S5 | + DH memory in F3 | **Radius per tier** = the largest radius meeting the FPS floor on S1 *and* S5: P0 → deck, P1 → default, P2 → high |
| | P0, P1 | dh-ssao, dh-qlow | S1 | | Adopt SSAO-off / LOW-quality for that tier on a win |
| | all | dh-thr4 | S1, S5 | | Keep 2 threads unless 4 is a win on S5 *and* not a loss on S1 |
| **B4** heap & GC | P2, then P0 | C1 (C1-deck on P0) × J0, J1, J2, J3 at `-Xmx6G` (P2) / `-Xmx5G` (P0); then the winner at 4 / 5 / 6 G | S5 (120 s), S2 | + `/sparkc gc` after S5 (pause count / max / total), swap delta (P2) | GC = best S5 1 % low. Heap = the smallest size within the spread of the best. Feeds `PLAYER-TUNING.md` §2. P1 isn't run (32 GB, not a constraint). |
| **B5** Sable / SSRD | P0, P1 | world s4-0 / s4-1 / s4-4 × C1; s4-4 × ssrd-32, ssrd-64, ssrd-128, sable-noshade | S4 | sub-level count | SSRD per tier = the largest value meeting the floor with 4 ships. sable-noshade ships only on a win *and* if it looks acceptable. |
| | P1 | C1, s4-4, J0 | S4, **45 min** (2 runs) | FPS at 0 / 15 / 30 / 45 min, `/sparkc heapsummary` at each | Decay > spread is a Sable #1011-class finding: report it, no config fix |
| **B6** ModernFix | P2, P0 | C1 vs mf-dynres-off, 3 cold launches each | S1, S2, S6 | startup, heap after load, missing-texture check (below) | Ship `dynamic_resources=true` only if: heap −300 MB or more *or* startup faster beyond spread, **and** zero missing textures, **and** no crash in 3 launches |
| **B7** shaders | P1, P2: all rows; P0: MakeUp rows only | C1 with: none / MakeUp `shadowless_medium` / MakeUp `medium` / Complementary `LOW` / Complementary `MEDIUM` (profiles set in Iris' shader settings) | S2, S1, S4 (s4-4) | `Flywheel` backend line in F3 (expect off with shaders); visual check for Iris #3164 on ships | Data for the owner's shader policy, no auto-decision. Report the FPS cost per profile and whether ship corruption appears with shadow profiles |
| **B8** entities | P2, P0 (P1: FA rows only) | C1 / C1 + Fresh Animations / C1 + FA + Extensions / emf-lod20 + FA / emf-lod8 + FA; ldl-fancy, ldl-fastest (no FA) | S3 | | EMF: adopt the smallest LOD that's a win with FA on. LDL: per tier, the cheapest mode that's a win. FA guidance → `PLAYER-TUNING.md` §4 |
| **B9** Flywheel | P0, P1 | fw-flat, C1, fw-smooth (no shaders) | S2 | | Per tier: the smoothest value meeting the floor |
| **B10** Sound Physics | P2, P0 | spr-up, C1, spr-strict, spr-nocap | S2s | | Adopt each change that's a win. Listen for obvious audio breakage. |
| **B11** vanilla / Sodium | P1, P2 | sod-thr3 | S5, S1 | | 3 threads only if S5 wins and S1 doesn't lose |
| | all | opt-blend2, opt-blend0 | S5 | | Lowest blend radius that's a win, per tier |
| | P1 | opt-mip4 | S1, S2 | VRAM peak | Mip 4 for mid/high only if VRAM peak ≤ 5.5 GB and no loss |
| **B12a** added-mod ablation | P2, P0 (P1: Sodium Extra only) | C1 with **one** of BadOptimizations / Particle Core / Sodium Extra removed from `mods/` | S1, S3, S2s | | Keep the mod unless removing it is a win (it costs FPS) or it breaks something visible. Sodium Extra + RSO stay regardless for their per-machine options. |
| **B12b** trials | per mod: Gnetum (all), Better Block Entities (all, with FA on and off), Flerovium (all), Super Resolution (P0), Ixeris (P1), Iris Flywheel Compat (with shaders, B7 scenes), Cull Leaves + MidnightLib (with Better Leaves) | C1 + one mod | the scenes that mod targets | | Owner pre-approved helpful adds. Add with `packwiz modrinth add` only on a win with no breakage. **Not More Culling**: its NeoForge build declares `minecraft [1.21,1.21.1)`. A new mod set means re-running B0 first. |
| **B13** profile | P2 | the worst scene from B2 under C1 | that scene | `/sparkc profiler start --timeout 60`, upload or save the report | Flag any mod over 5 % of render-thread time that isn't Sodium, Iris, DH or Flywheel |
| **B14** spawn tuning | P2 | singleplayer, defaults vs the shipped spawn files | river, jungle, warm ocean (10 min each) | per-species and total entity counts | Procedure and keep/back-off rule: `SPAWN-TUNING.md` §B14 |
**Missing-texture check (B6)**: in creative, open JEI, search `@chipped`, `@rechiseled` and
`@handcrafted`, scroll each list, and place a 4×4 wall of 3 connected-texture Rechiseled
blocks. Any magenta/black texture or broken CTM seam fails the check.
### Rough budget
Assumes ~4 min per launch (B1 measures the real figure) and ~3 min per scene for 3 × 45 s.
| Block | Launches (all machines) | Hours |
| --- | --- | --- |
| B0–B2 | ~12 | ~5 |
| B3 | ~20 | ~4 |
| B4 | ~14 | ~3 |
| B5 | ~12 + 1.5 h decay | ~4 |
| B6 | 12 cold launches | ~2 |
| B7–B11 | ~45 | ~7 |
| **Total** | | **~25 h machine time**, ~10 h per machine |
## 5. Output
One CSV row per run, using the phase0 recording sheet plus `config_id`, `jvm_set`, `xmx`,
`scene`, `world_sha`, `pack_commit`. When the matrix is done, write the winners back into
`proposed-configs/build.py` (DEFAULT/DECK/HIGH), regenerate, and only then present the files
for shipping.
+109
View File
@@ -0,0 +1,109 @@
# Onion MC client-perf — handoff (2026-10-15)
Self-contained brief for the next agent. Read `.pi/journal.md` (last sections) and
`BENCH-MATRIX.md` too, but this file is the live state on this machine.
## Goal
Finish the **Phase 2 client-performance loop**: measure the staged configs, decide by
`BENCH-MATRIX.md`'s rules, write winners into `proposed-configs/build.py`
(`DEFAULT` / `DECK` / `HIGH`), `build.py --ship`, `packwiz refresh`, commit (Conventional
Commits) and push. Pushes go live to players on their next Prism launch.
## Repo / pack state
- Pack: **217 mods**, MC 1.21.1, NeoForge 21.1.251. Git `HEAD == origin/main == 8e8483d`.
- `8e8483d` fixed the one real boot blocker: the Critters & Companions partial spawn config
used camelCase keys (`biomeSpec`) but YACL serialises with
`FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES`; every entry loaded `biomeSpec = null` and
`CACSpawnConfig$SpawnEntry.isTag()` NPE'd in `FMLCommonSetupEvent`, leaving the client in a
"broken mod state" (119 refused events). Fixed to snake_case; **already pushed**.
- The Alex's Mobs (TOML) and Fowl Play (YACL camelCase) spawn files are correct and were
verified applied in-game. Do not commit the mod/server **full re-saves** of
`config/*` (the running server rewrites them): always `git checkout -- config/` before
staging.
- Nothing was shipped this session. Shipped `config/defaultoptions/**` is unchanged from
`a01bc3b`. `build.py` was only patched in the **BENCH** layer (below); the tiers are
untouched.
## Boot check (client, this machine) — essentially PASS
Reached main menu cleanly. `Sound engine started` 01:26:53; ModernFix "Game took 29.3 s to
start"; blocks atlas `4096x4096x2`; Default Options correctly seeded `options.txt`
(renderDistance 8, maxFps 120, vsync true). No missing deps, no mixin apply failures, no
`ftbteams` refs, no fishermans-haven biome errors, no YACL "Failed to deserialize". 22 ERROR
lines, all benign (pride access-transformer, Controlify @Shadow, FTB Library legacy
textures, 4 GeckoLib molang parses, icys-better-horses pack path, Xaero version expiry,
Veil failing to recompile 2 lodestone `nineslice` shaders).
**Open boot-adjacent issue (not yet fixed):** an intermittent crash on **world load**:
```
java.io.FileNotFoundException: minecraft:shaders/core/particle.json
at com.corosus.watut.ShaderInstanceBlur.<init>(ShaderInstanceBlur.java:27)
at GameRenderer.handler$jhl000$watut$onLoadShaders(...)
```
First world load succeeded and measured; the next crashed during "Rendering overlay". WATUT
is a LEVERS §5 removal candidate but the owner says **remove no mods** — look for a WATUT
config/patch or a reliable retry. Confirm whether real players hit it before shipping.
## Tooling installed on this machine (all user-local, no root)
- **Prism Launcher 11.0.3** (`/usr/bin/prismlauncher`). Data root `~/onion-bench/prism`,
instance id **`OnionBench`**, game dir
`~/onion-bench/prism/instances/OnionBench/.minecraft` (214 jars). A valid Microsoft
account is in `~/onion-bench/prism/accounts.json` (device-code login, owner-approved).
- **MangoHud 0.8.4** extracted to `~/.local/mangohud`; wrapper **`~/.local/bin/mangohud-local`**
(exports `MANGOHUD_CONFIG`, `LD_PRELOAD` shim, `vblank_mode=0`). CSVs land in
`~/onion-bench/mangologs/`. The instance has `WrapperCommand=mangohud-local`.
- **Bench world**: `~/onion-bench/prism/instances/OnionBench/.minecraft/saves/onionbench`
(seed `onionbench`, creative, cheats). S2 Create floor (2304 kinetic BEs) and S3 mob pen
(30 villagers/15 cows/15 sheep/10 allays) were placed via RCON and verified. S4 airships
NOT built. Datapack lives in the world's `datapacks/onion-bench`.
- **Temp server** for worldgen: `~/onion-bench/server-bench` (symlinks repo `mods`/
`libraries`, copies `config`), port 25566 / RCON 25576, currently **stopped**.
- **Harness** (this is new, untracked, packwizignored): `analysis/client-perf/harness/`
- `rcon.py` — minimal RCON client.
- `run_scene.py` — applies a variant, launches Prism into the bench world under MangoHud,
drives scenes with xdotool chat, slices the CSV into p50/p95/p99/1%-low. Verified working
end-to-end once (C1/S1).
## Measurements so far — NOT decision-grade
One run: **C1 / S1** p50 16.72 ms, p99 17.24 ms, peak 17.60 ms, avg 59.8 FPS, 1% low 57.5,
n=1889. It was **vsync-locked at ~60 FPS** (tight 16.7 ms floor). `vblank_mode=0` was added
afterwards but the next launch crashed on world load, so uncapped data is not collected yet.
Treat all of this as pipeline validation only.
## Next steps (in order)
1. Resolve the WATUT world-load crash; re-run one C1/S1 and confirm FPS > 60 (vblank off).
2. Run the matrix in order, per `BENCH-MATRIX.md`: **B0** noise floor (C0/S1 ×3) → **B1**
baseline (C0, S1–S6) → **B2** composite (C1, and C1-deck/C1-high) → **B3** DH radius
(`dh-r256`, `dh-r192`, `C1`, `dh-r64`, `dh-off` on S1/S5) → **B4** heap/GC (use `jcmd`/
`jstat`, or spark, at 5/6 G and J0–J3) → **B6** ModernFix → **B7** shaders → **B8**
entities → **B9** Flywheel → **B10** SPR → **B11** Sodium/blend → **B12a/b** ablation and
trials → **B13** spark profile → **B14** spawn counts.
3. Write winners into `build.py`, `python3 build.py --ship`, `~/go/bin/packwiz refresh`,
commit. Ship `config/modernfix-mixins.properties` only if B6 passes (see SHIP comment).
4. Update `PLAYER-TUNING.md` (tiers, heap/GC, shader profiles), `LEVERS.md`,
`BENCH-MATRIX.md`, and append a journal section.
## Fleet note
P0 (Steam Deck), P1 (7700K/1060) and P2 (6700K/1080 Ti) are **not reachable** from this box
(no routes / no SSH targets). Everything measured here is a **high-tier proxy** on a Ryzen 9
9900X + RX 7800 XT, 91 GB RAM. Label results as proxies; absolute FPS floors do not map to
tier decisions.
## Gotchas learned this session
- Prism 11 `--offline` fails a fresh instance with "Required libraries are missing"
(`EnsureOfflineLibraries`). Launch **online** once to download libs/assets; after that
offline works.
- `xdotool windowactivate --sync` **hangs** under kwin; use `windowactivate` + `windowfocus`.
- MangoHud needs `MANGOHUD_CONFIG` set explicitly and `LD_PRELOAD` **exported**.
- The owner's dedicated server runs from the repo root (`java @user_jvm_args.txt
@libraries/.../unix_args.txt`, port 25565). Never kill it; never touch `world/`. The temp
bench server used separate ports/paths and is stopped.
- `analysis/` is packwizignored (safe to commit); `config/` is server-rewritten (restore
before staging).
+176
View File
@@ -0,0 +1,176 @@
# Onion MC — client performance levers (Phase 1, unmeasured)
Fleet (from `phase0/`): **P0** Steam Deck (GPU/shared-RAM bound), **P1** 7700K + GTX 1060 6 GB
(VRAM bound), **P2** 6700K + 1080 Ti with ~8 GB free RAM (main-thread + heap bound). Every
machine is 4C/8T. That makes CPU-side render cost, chunk/LOD worker threads and GC the
common denominator.
Impact estimates are informed guesses until Phase 2 measures them. Tags: **V** verified
in source or config, **D** documented upstream, **C** community, **I** inference.
Sources are in `research/` and `data/`.
---
## 1. The shape of the problem
From `data/jarscan-report.md`:
| Surface | Size | Who dominates |
| --- | --- | --- |
| Mixin classes on per-frame paths | 951 (121 `@Overwrite` / `@Redirect`) | Sable 127, Iris 114, Controlify 84 (GUI), Sodium 48, EMF 42, Create 30, ETF 27, Subtle Effects 26 |
| Mods subscribing to per-frame events | 58 | Puzzles Lib, Create, Alex's Caves, Mowzie's, Twilight Forest, Ars Nouveau, Balm, Xaero's |
| Mods on client / player / entity tick events | 79 | — |
| Block + item atlas | ~60k 16×16-sprite equivalents (≥4096²) | **Handcrafted 27 %, Chipped 21 %, Rechiseled 9 %** (58 % together) |
| Baked models / blockstates | 69,442 / 16,361 | Chipped 16.8k, Rechiseled 13.9k, Handcrafted 12.4k models (62 %) |
| Entity types | ~600 | Alex's Mobs 140, Alex's Caves 92, Twilight Forest 80, Ars Nouveau 51 |
| Animated atlas textures | 312 | Frights' Delight 69, TF 44, Chipped 30, Ars 29 |
| Post-processing / shader payloads | — | Lodestone 59 files, Alex's Caves 38 (7 post), Charta 31, Exposure 21 (19 post); vanilla core-shader overrides from Sable, Sodium, Xaero's ×2 |
What a player pays **by default today**, before choosing any options:
- **Shaders and resource packs are not on by default.** packwiz only downloads them; the
last real launch had `resourcePacks:[]` and no shaderpack. So Fresh Animations, Better
Leaves, Complementary and MakeUp cost nothing until a player enables them. **I**
- **Distant Horizons is mandatory and its defaults are heavy:** 256-chunk LOD radius, SSAO
on, AA on, MEDIUM/MEDIUM quality, transparency COMPLETE. **V** (observed `DistantHorizons.toml`)
- **Every mod's own defaults ship unchanged.** The pack ships **no client performance
config**. Its only configs are PetProtect, Akashic Tome and the Paxi datapack.
- The JVM, heap and GC are whatever each player's Prism instance has. The old instance had
`MaxMemAlloc=8192` and stock G1.
That last bullet is the reason for §2.
---
## 2. Meta-levers (enablers; do these first)
| # | Lever | Why |
| --- | --- | --- |
| M1 | **A way to ship tuned defaults without clobbering player changes.** **Resolved (2026-10-14, Phase 1b):** use Default Options (`config/defaultoptions/`, client-only, copy-if-absent), plus an enforced packwiz file for `modernfix-mixins.properties`, which is read at mixin time. `preserve` survives `packwiz refresh` but leaks into the server dir, which matters for DH `[server]`/`[common]` and SSRD COMMON. Evidence: `proposed-configs/README.md` §1. | Without this, every config lever below depends on 10 players hand-editing files. |
| M2 | **Per-tier profiles** (Deck / mid / high). Ship one conservative default, plus a short "if your machine is X, change Y" doc. Optionally make heavy client-only cosmetics packwiz `optional` (today only Controlify is). | Fleet constraints differ in direction. P2 should lower DH *threads*; P1 should lower DH *distance and quality*. One global ordering won't fit (already noted in `phase0/P2`). |
| M3 | **Profile with spark on the client** (`/sparkc profiler`; spark is already in the pack) in Phase 2, not only FPS percentiles. | 951 frame-path mixins means attribution must come from a profiler, not from guessing which mod is slow. |
---
## 3. Ranked levers
### Tier 1 — biggest expected wins, config or policy only
| # | Lever | Mechanism | Helps | Risk | Conf. |
| --- | --- | --- | --- | --- | --- |
| **1** | **Distant Horizons defaults per tier.** `lodChunkRenderDistanceRadius` 256 → ~64–96 (P0), 128–192 (P1), ~128 (P2). Turn SSAO off on P0 and P1. Lower the CPU thread preset on P2. Keep cave culling on. LODs come from the server; do `/dh pregen` server-side, not Chunky. | DH cost scales with LOD distance in VRAM, GPU and CPU. It is the largest always-on renderer the pack adds. | all; P1 VRAM, P0 everything | visual only | V/D |
| **2** | **Shader policy.** Default off, opt-in per machine. If on: MakeUp UF, or Complementary at ≤ MEDIUM profile, shadow distance ≤ 8 chunks, entity shadows off. | With any shaderpack active, **Flywheel marks both GPU backends unsupported** (`!ShadersModHelper.isShaderPackInUse()`, checked in source). Create then renders every kinetic block entity on the CPU, and again in the shadow pass. EMF/GeckoLib entities are also redrawn for shadows, and DH adds a shadow pass over all LODs. | all; P0 hardest | none (policy) | **V** |
| **3** | **Entity count, set on the server.** *Shipped in `d80f85b`, see `SPAWN-TUNING.md`.* Tune Alex's Mobs spawn weights: the 1.21.1 port has an open issue about inflated vanilla spawn rates (#62). Cap Fowlplay: #214 reports ~400 birds even after tuning. Cap Critters & Companions spawns via datapack (it has no config). | Every entity pipeline (vanilla, EMF/FA, GeckoLib, ETF, LambDynamicLights, Perception trails) multiplies by visible entity count. | all clients + server tick | gameplay feel | D/C |
| **4** | **EMF/ETF throttles.** `animationLODDistance` 20 → ~8–12. Consider `retainDetailOnLowFps=false` on P0. `modelUpdateFrequency` Average → Slow. Keep `animationFrameSkipDuringIrisShadowPass=true` (already the default, **V** observed). Treat the FA Extensions pack as opt-in. | FA turns every vanilla mob into an animated CEM model. EMF's frame-skip-by-distance is the throttle. | P0, P2 | animation smoothness at range | V |
| **5** | **JVM heap and GC per machine** (Prism settings; packwiz can't ship them). Heap: P0 5–6 G, P1 6–8 G, P2 ≤ 5–6 G (not the old instance's 8 G). Benchmark three GCs: client-tuned G1 (brucethemoose set), Generational ZGC (`-XX:+UseZGC -XX:+ZGenerational`; plain `UseZGC` on JDK 21 is the *non*-generational one), and Shenandoah. | DH warns about G1 itself. GC pauses show up as 1 % lows. P2's 8 GB headroom makes heap size a hard constraint. | P2, P0 | low | D/V |
| **6** | **ModernFix `mixin.perf.dynamic_resources=true`.** Off by default (**V** in the observed `modernfix-mixins.properties`). Also consider `dynamic_entity_renderers`. | 69k baked models and 16k blockstates sit in heap. The author documents ~−55 % memory even with FerriteCore. ModernFix has a CTM sub-option (`dynamic_resources.ctm`); test against Fusion and Athena. | P2, P0 (RAM) | med: missing textures or crashes with CTM/model mods | D/V |
| **7** | **Flywheel `lightSmoothness` SMOOTH → FLAT or TRI_LINEAR** (`flywheel-client.toml`). | The config comment itself says "may have a large performance impact". It only matters when shaders are off, i.e. when Flywheel is actually used. | all, Create-heavy bases | visual | V |
| **7b** | **Sable / SSRD sub-level budget.** Sable has no renderer of its own. Its `SODIUM_REACHAROUND` path runs Sodium's chunk renderer once per visible sub-level, so cost is roughly sub-levels × meshed sections, on top of terrain. Default the client SSRD distance low (8–16 chunks), not the 128-chunk server cap. Keep `sub_level_skylight_shadows=false` (the default, **V** observed). | Each airship is a second terrain pass. Under shaders, shadows also trigger open Iris #3164 sub-level corruption. | all; P0, P1 first | visual range of airships | V (config) / I (magnitude) |
| **8** | **LambDynamicLights**: mode → fast or fastest. On P0/P2 also set `light_sources.entities=false` (keep self). | Each moving light source triggers Sodium chunk-section rebuilds. On 4-core CPUs those compete with chunk builders, which default to 2 threads. | P2, P0 | less responsive light | V |
### Tier 2 — solid, smaller or more situational
| # | Lever | Helps | Conf. |
| --- | --- | --- | --- |
| 9 | **Sound Physics Remastered**: ray count 32 → 16, bounces 4 → 2, `strict_occlusion=true` (1 occlusion path instead of 9). Cap noisy Create and mob sounds in `sound_rates.properties`. Cost scales with concurrent sounds, so a Create floor is the worst case. | P2, P0 | V |
| 10 | **Vanilla and Sodium options**: render distance 8 to match the server (the old client had 12, which loads nothing extra in multiplayer). Biome blend 2 → 1 or 0. Mipmap 4 → 2 on P1/P0 (the atlas is ≥4096²). Entity distance 75–80 %. Sodium chunk threads auto-resolve to **2** on every fleet machine; try 3 on P1/P2. | all | V/I |
| 11 | **Better Leaves needs leaf culling** if anyone enables it. Use **Cull Leaves** (same author, made for it) or More Culling. **Do not use Sodium Leaf Culling**: it crashes on Sodium ≥ 0.8.11. | all, forests | D |
| 12 | **Twilight Forest / Alex's Caves client effects.** TF `cloudBlockPrecipitationDistance` → 0 and aurora toggles. AC `biome_ambient_light_coloring=false` for shader users. DH flags AC as partially incompatible (**V**, log). | in those dimensions | C/D |
| 13 | **Jade.** Pre-tune `hide-blocks.json` (player heads) and container-content display. Sophisticated Backpacks' "20–30 FPS when looking at a backpack" report was traced to Jade's content display. | all | C |
| 14 | **Bump Puzzles Lib 21.1.62 → 21.1.64**, as a *bugfix*, not a perf lever. The 21.1.64 cache is in `FabricEventFactory`, which isn't in the NeoForge jar. The NeoForge 62 → 64 diff is access-transformer restorations (21.1.63) plus one item-properties class. Applied in `a01bc3b`. | — | **V** (jar diff) |
| 15 | **Xaero's**: cap world-map loaded regions, keep the minimap modest. Radar as dots. | P2 heap | D |
| 16 | **WATUT**: raise `tickReceiveAndRenderRateOfGUIUpdates`, or `dontSendDetailedGUIInfo=true`. Watch the open TF conflict (#106). **Perception**: remove XP-orb trails. **Subtle Effects**: dust clouds off on P0. **FWA**: chain/lantern animations off on P0. | P0, P2 | V/C |
| 17 | **Platform**: `mesa_glthread=true` + gamemode on the Deck. NVCP "Prefer maximum performance" on P1. Leave threaded optimisation alone; Sodium manages it. | P0, P1 | D/C |
### Tier 3 — content weight (structural; decide after measuring)
- **Handcrafted + Chipped + Rechiseled** = 58 % of the atlas and 62 % of all models. They
cost startup time, heap (baked models) and some VRAM. Lever 6 mitigates the heap part.
Removing any of them is a content decision, but they're the only lever that moves startup
and model RAM materially. Chipped requires Athena, and Rechiseled requires Fusion.
- **Fresh Animations (+ Extensions)**: the largest documented entity-render multiplier,
especially with shaders (EMF #443, Oculus era: 165 → 40 FPS with 30+ mobs). It's already
opt-in because it's a resource pack. Keep it that way and say so in the player doc.
---
## 4. Mods to add (all confirmed with NeoForge 1.21.1 builds via the Modrinth API)
**Owner decisions, 2026-10-14:**
- Adding any helpful mod is allowed. Default Options, Sodium Extra, Reese's Sodium Options,
BadOptimizations and Particle Core were **added in `a01bc3b`**.
- The trials are bench block B12b.
- More Culling is out: its NeoForge build excludes 1.21.1.
- Shaders stay **opt-in**.
| Mod | Why | Verdict |
| --- | --- | --- |
| **Default Options** (`default-options`) | Lever M1 delivery mechanism; Balm already present | **Add** |
| **Sodium Extra** + **Reese's Sodium Options** | Expose particle, animation and fog toggles per machine. RSO works with Controlify, which matters on the Deck. | **Add** |
| **Particle Core** (`particle-core` 0.3.3) | Particle frustum culling, async ticking, per-type limits. Big particle sources here: Subtle Effects, Perception, Ars, Alex's Caves, Sooty Chimneys. | **Add, then verify** |
| **BadOptimizations** (2.4.1) | Lightmap and sky caching; self-disables on conflicts | **Add** |
| **Gnetum** (3.3.6) | Spreads HUD rendering over frames. This pack is HUD-heavy (Xaero's, Jade, AppleSkin, titles, plaques). The author says it's compatible with ImmediatelyFast. Replaces Exordium, which has no NF 1.21.1 build. | **Trial** |
| **Cull Leaves** or **More Culling** | Only if Better Leaves stays (lever 11) | **Add one** |
| **Better Block Entities** (1.3.4, built for Sodium 0.8.12–0.8.13) | Meshes static chests, signs and beds into terrain. Its EMF compat is only for 1.21.11+, so test it with FA. | **Trial** |
| **Flerovium** | Item, particle and entity render culling. Higher visual-glitch risk with layered models. | **Trial** |
| **Super Resolution** (alpha) | FSR upscaling. Potentially the biggest P0 lever, but alpha, and Complementary has no SR interface. | **Trial on P0 only** |
| **Ixeris** | Moves event polling off the render thread. Big win only on Windows with high-poll mice. Has open crash reports on older NVIDIA cards. | **Trial on P1** |
| **Iris Flywheel Compat** | Restores Flywheel instancing under shaders, which would neutralise most of lever 2's Create penalty. Unofficial and breaks on Create/Iris updates. | **Trial only if shaders are wanted** |
**Skip**: Sodium Leaf Culling (crashes), ScalableLux (breaks DH), Moonrise and C2ME on the
client (no client gain in multiplayer), Exordium / Krypton / Nvidium / VulkanMod / FastChest /
YOSBR (no NeoForge 1.21.1 build), Memory Leak Fix (obsolete), Sodium Options API (superseded
by Sodium 0.8), Sodium Dynamic Lights (duplicates LambDynamicLights).
## 5. Mods worth considering for removal
**Owner decision, 2026-10-14: remove no mods.** Kept for reference only. Mitigate with
config instead (spawn tuning: `analysis/client-perf/SPAWN-TUNING.md`).
Ordered by cost to value. None is urgent; measure first.
| Mod | Cost evidence | Note |
| --- | --- | --- |
| **Miner's Delight** | It's the *only* consumer of **Lodestone** (10 frame mixins incl. 3 overwrite/redirect, 59 shader files, post effects). **V** (dependency scan) | Removing one food addon drops a whole rendering library |
| **Fowlplay** | Open 1.21.1 issue: hundreds of birds; per-tick flight pathfinding (server) | Remove if spawn caps don't hold |
| **Charta** | 31 shader files registered at load; niche use | Only if nobody plays cards (both sides) |
| **WATUT** | Streams other players' GUI state each tick; open TF conflict | Flavour only |
| **Emojiful** | Font-render hooks; known chat freezes upstream | Low impact; social call |
| **Fresh Animations Extensions** | Extra CEM models on top of FA | Keep opt-in, recommend against on P0 |
| **AmbientSounds** | Largest jar (85 MB of ogg); RAM residency unmeasured | Measure heap first |
**Not removable, contrary to r4's suggestion**: one of Fusion/Athena. Chipped *requires*
Athena and Rechiseled *requires* Fusion (**V**, `neoforge.mods.toml` dependencies).
---
## 6. Phase 2 — test order
**Superseded by [`BENCH-MATRIX.md`](BENCH-MATRIX.md)**: world, scenes, config IDs, blocks
B0–B13 with decision rules.
## 7. Corrections to subagent research (verified by the orchestrator)
Added in Phase 1b (2026-10-14):
- **DH default threads is 4 on the fleet, not 2.** The default is `ceil(nproc × 0.5)`
(`ThreadPresetConfigEventHandler` @ 3.3.2). The observed `numberOfThreads = 2` came from
the owner's 4-thread machine (`nproc` = 4). Every other observed DH value matches the
source defaults.
- **Puzzles Lib 21.1.64 has no perf change on NeoForge** (see lever 14). r9's lead and this
file's earlier "the fix *is* in 21.1.64 for 1.21.1" were wrong for our loader.
- **r6's Shenandoah flags crash JDK 21.** `-XX:ShenandoahGCMode=iu` needs
`-XX:+UnlockExperimentalVMOptions` (tested on OpenJDK 21.0.12). Use plain
`-XX:+UseShenandoahGC`.
- **SSRD's distance is a COMMON config** (`config/ssrd-common.toml`,
`physicsRenderDistance`, default 128, slider floor 16), and the server reads it too. It
is not stored in Sodium's options.
- r4 "remove one CTM engine": wrong. Both are hard dependencies (see §5).
- r9 "Puzzles Lib ~8 % render-thread overhead": the measurement was on MC 26.2/Fabric. The
fix *is* in 21.1.64 for 1.21.1; the size of the gain there is unknown.
- r3/r8 cite EMF #443 for magnitude. That report is from the Oculus/Forge era and is closed.
Treat the 4× figure as directional only.
- Static scan's "Sable overrides vanilla core shaders" (r7 checked it): real but small, just `worldborder.*` from the nested Veil. The substantive part is Sable's runtime patching of Flywheel's internal shaders.
- r1's Flywheel-under-shaders claim was checked in `Backends.java` on the 1.21.1 branch:
confirmed.
+102
View File
@@ -0,0 +1,102 @@
# Client performance — owner report (2026-10-18)
Plain-language summary of the Phase 2 benchmarks. Full tables and method:
[`RESULTS-proxy.md`](RESULTS-proxy.md). Player-facing advice:
[`PLAYER-TUNING.md`](PLAYER-TUNING.md). All numbers come from **one stand-in machine**
(Ryzen 9 9900X / RX 7800 XT, 1920×1080), not the fleet machines — see Caveats.
## TL;DR
- The tuned defaults we already ship beat upstream defaults in **every** test scene, and
start the game about 6 s faster.
- This session shipped faster startup (ModernFix dynamic resources, −7.6 s) and a fix that
keeps Create's fast renderer working with shaders on (Iris Flywheel Compat).
- Shaders stay **opt-in**. MakeUp shadowless is nearly free except on big Create builds;
Complementary is expensive. Fresh Animations is the most expensive optional visual.
- Players should give Minecraft **5 GB** of memory — 4 GB is measurably worse, more is not faster.
- Two decisions for you: **remove Flerovium** (its win didn't hold up — I recommend yes), and
**keep or revert the spawn tuning** (harmless, but it doesn't measurably cut mob load).
## What shipped
| Commit | Change |
|---|---|
| `7841361` | ModernFix `mixin.perf.dynamic_resources=true` (enforced `config/modernfix-mixins.properties`); added **Flerovium 1.1.2** and **Iris Flywheel Compat 2.4.0** (both client-only) |
| `c5d25fa` | Fix: `7841361`'s pack index held hashes of server-re-saved spawn configs, so the server's install failed ("Hash invalid") and it didn't start at 14:06. Re-hashed; server up 14:18. Nothing was pushed. |
The Deck tier already uses Distant Horizons' LOW quality preset — the setting that won the
DH test — so no DH change was needed. Nothing has been pushed yet.
## Measured gains
| What | Before | After |
|---|---|---|
| Tuned defaults vs upstream — vista p99 frametime | 9.33 ms | 8.23 ms |
| Tuned defaults vs upstream — mob pen 1 % low | 110 fps | 186 fps |
| Tuned defaults vs upstream — startup | 31.4 s | 25.4 s |
| ModernFix dynamic resources — startup | 33.9 s | 26.3 s (plus better frametimes) |
| Iris Flywheel Compat — Create floor with MakeUp shaders, p99 | 13.26 ms | 3.98 ms |
| Iris Flywheel Compat — same scene, average fps | 117 | 644 |
(Lower p99 = fewer stutters; higher 1 % low = smoother worst moments.)
## Shaders
- Keep them **opt-in** (the pack downloads them; nothing turns them on).
- **MakeUp – Ultra Fast, shadowless profile** is close to free on terrain and airships.
- Without help, every shaderpack turns off Create's fast renderer: the Create floor dropped
from ~974 to ~120 fps average in every profile. **Iris Flywheel Compat**, now shipped, fixes
that (table above). It's an unofficial mod, so it may break when Create or Iris update.
- **Complementary** is costly: about 2.7 ms worse p99 on the vista and about twice the frame
time on the airship scene.
- Shader **shadows corrupt airships** (Iris issue #3164) — recommend shadowless profiles.
## Fresh Animations
Mob-pen p99 goes from 4.32 ms to **8.65 ms** with Fresh Animations, and **10.20 ms** with
the Extensions pack too. Keep it opt-in; Deck players shouldn't use it.
## Memory
**5 GB** is the smallest heap that performed as well as the best. 4 GB lost about 23 fps of
1 % low on the teleport tour; 6 and 8 GB were no better. Players set this in their launcher.
## Spawn tuning
Measured 4 times per config at three biome sites. Total mob counts barely changed
(jungle 129.5 → 128.0, river 208.0 → 211.5, warm ocean 145.0 → 136.0). The tuned species
are a bit rarer, as intended, and nothing really disappeared (the automated "back off"
flag came from species that only ever showed up once per run). **It's harmless, but it
doesn't measurably reduce the load.** No config was changed.
## Decisions needed from you
> **Resolved 2026-10-18:** both removed in `217c346` (Flerovium dropped; spawn tuning
> reverted, the mods regenerated their defaults on the server).
1. **Remove Flerovium?** Its earlier win was measured against an unusually bad baseline.
On a fair re-test it was worse on the vista and a tie on the mob pen. **Recommended: yes.**
2. **Spawn tuning: keep or revert?** Keeping it costs nothing; reverting returns the mods'
own spawn rates. Either is fine performance-wise.
## Caveats
- **One stand-in machine.** The fleet machines (Steam Deck, 7700K + GTX 1060, 6700K +
1080 Ti) were unreachable, so results are relative and unconfirmed on real player
hardware. This GPU makes some costs (airships, DH distance) look cheaper than they are
on older cards.
- GPU clocks weren't pinned (no root access), which adds a little noise.
- A harness bug ran part of the first run on the wrong Java settings. Comparisons inside
each test are still fair; details in `RESULTS-proxy.md`.
## Not done
- Spark CPU profile of the worst scene (B13).
- Confirmation on the fleet machines.
- The airship decay test (B5), which needs the fleet machines.
## Please report texture problems
ModernFix dynamic resources loads textures and models on demand. We skipped the
missing-texture check at your request, so if anyone sees purple/black "missing" blocks or
items in play, report the block/item name.
+145
View File
@@ -0,0 +1,145 @@
# Onion MC: performance settings for players
These are things the pack can't set for you, plus the settings to change for your machine.
All values below are final (benchmarked in Phase 2; see `RESULTS-proxy.md` in this folder).
## 1. Which tier are you?
| Tier | Typical machine | Examples |
| --- | --- | --- |
| **Deck** | handheld or integrated graphics, shared RAM | Steam Deck, laptop iGPU |
| **Mid** (pack default) | 4–6 core CPU, ~6 GB VRAM graphics card | GTX 1060 6 GB, RX 580 |
| **High** | 8 GB+ VRAM card | GTX 1080 Ti, RTX 3060+ |
The pack's defaults are the **mid** tier. They're applied only when the config file doesn't
exist yet. **If you played before the defaults shipped**, close Minecraft and run the
tier setup script in the instance folder to apply a tier over your current settings:
- **Windows:** double-click `onion-perf-setup.bat` in the instance folder.
- **Linux / Steam Deck / macOS:** run `sh onion-perf-setup.sh` in the instance folder.
Pick your tier when prompted. The script backs up everything it changes (to
`onion-perf-backup/<date-time>/`), replaces these 7 config files (the **deck** tier also
ships `config/sodium-options.json`; **high** has the same 7 as mid), and merges the tuned
video keys into `options.txt` — your keybinds, audio and every other setting stay as
they are. Nothing else is touched.
Manual fallback: copy the files below from `config/onion-perf/<tier>/` in the instance
folder over your own configs, and set the `options.txt` values from the table below by
hand (don't copy a tier's `options.txt` over yours — that resets all your video, audio
and control settings, keybinds included):
```
config/DistantHorizons.toml
config/entity_model_features.json
config/flywheel-client.toml
config/lambdynlights.toml
config/ssrd-common.toml
config/sound_physics_remastered/soundphysics.properties
config/sound_physics_remastered/sound_rates.properties
```
The whole tier table below is applied by the script:
| Setting | Where | Deck | Mid | High |
| --- | --- | --- | --- | --- |
| LOD render distance | Options → Distant Horizons button | 64 | 128 | 128 (192–256 if you have ≥16 GB free RAM and 6+ cores) |
| DH ambient occlusion (SSAO) | DH → Advanced → Graphics | off | off | on |
| DH quality preset | DH quick options | Low | Medium | Medium |
| Biome blend | Video Settings | off | 3×3 | 5×5 |
| Mipmap levels | Video Settings | 2 | 2 | 4 |
| Particles / entity shadows | Video Settings | Decreased / off | All / on | All / on |
| Leaves & weather quality | Sodium → Quality | Fast | Default | Default |
| Sub-level (airship) render distance | Sodium video settings slider | 16 | 16 | 32 |
| Dynamic lights | LambDynamicLights settings | Fastest, entities off | Fast | Fast, entities off |
Leave **render distance at 8**. The server sends 8 chunks, so anything higher loads nothing
extra in multiplayer. Distant Horizons draws everything beyond that.
## 2. Java memory and garbage collector (Prism → Edit Instance → Settings → Java)
Use **Java 21** (Temurin 21 recommended). More memory is *not* faster: a bigger heap makes
each GC pause longer. Bench (B4h): **5 GB is enough; 4 GB is measurably worse** (tour 1 % low
−23 fps); 6 and 8 GB were no faster than 5. Don't go below 5 GB on any machine.
| Your machine | Max memory (`-Xmx`) | Min memory |
| --- | --- | --- |
| Steam Deck (16 GB shared with the GPU) | **5 GB**, never above 6 | leave low (512 MB) |
| 16 GB PC | **5–6 GB** | leave low |
| 32 GB PC | **6–8 GB** | may equal max |
| PC with little free RAM (other apps/containers running) | min(6 GB, free RAM − 2 GB) | leave low |
**JVM arguments** — bench block B4 settled this: the client-tuned G1 line below and the
phase-0 G1 set tied (S5 tour 1 % low 226.8 vs 228.1 fps), while Generational ZGC and
Shenandoah were measurably worse on the tour scene (1 % low 216.6 and 173.4 vs 226.8 fps).
So: **the G1 line below, for everyone.** Paste it into *Java arguments* (Prism adds
`-Xmx` itself from the memory box):
- **Default (all machines):** client-tuned G1
```
-XX:+UseG1GC -XX:MaxGCPauseMillis=37 -XX:G1HeapRegionSize=16M -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=23 -XX:G1ReservePercent=20 -XX:G1MixedGCCountTarget=3 -XX:G1HeapWastePercent=20 -XX:InitiatingHeapOccupancyPercent=10 -XX:G1RSetUpdatingPauseTimePercent=0 -XX:MaxTenuringThreshold=1 -XX:GCTimeRatio=99 -XX:+DisableExplicitGC -XX:+PerfDisableSharedMem
```
- Don't switch to Generational ZGC — not even on 32 GB machines. Distant Horizons
recommends it in a log warning about G1, but here it **tested worse** (tour 1 % low
216.6 fps vs 226.8 for G1; Shenandoah was worse still at 173.4).
Don't add `-XX:+AlwaysPreTouch` on the Deck or on low-RAM PCs. Don't copy
`-XX:ShenandoahGCMode=iu` from older guides: Java 21 refuses to start with it.
## 3. Shaders
*Pending the owner's shader policy. Interim advice:* **shaders off** is the pack default.
They cost more in this pack than usual:
- On its own, Flywheel (Create's GPU renderer) **switches off when any shaderpack is on**.
The pack now ships **Iris Flywheel Compat**, which keeps it working under shaders (bench:
Create floor p99 13.3 → 4.0 ms with MakeUp). It's unofficial, so it may lag behind
Create/Iris updates; if Create machines render wrong with shaders, report it.
- Shader **shadows corrupt airships** (Sable sub-levels; Iris issue #3164).
- Distant Horizons adds a shadow pass over all its terrain.
If you want shaders anyway:
| Tier | Pack | Profile (shader settings screen) |
| --- | --- | --- |
| Deck | MakeUp – Ultra Fast | `shadowless_low` or `shadowless_medium`. These have no shadows, so no airship corruption. |
| Mid | MakeUp – Ultra Fast | `shadowless_medium`, or `low` if you accept shadow artefacts on airships |
| High | Complementary Reimagined | `LOW` or `MEDIUM` (MEDIUM = 8-chunk shadows). Avoid `HIGH` and above near Create builds. |
## 4. Resource packs
The pack downloads these but doesn't enable them:
- **Fresh Animations** (+ **FA: Extensions**): re-models and animates every vanilla mob. It's
the most expensive optional visual in the pack, especially with shaders and near villages
or farms. **Deck: don't use it.** Mid: Fresh Animations alone is OK, but skip Extensions.
High: fine.
- **Better Leaves**: much denser leaves. The pack has no leaf-culling mod for it yet, so
**avoid it on Deck and mid** until one is added.
## 5. Steam Deck
- Run Prism in **Desktop Mode** (flatpak from prismlauncher.org).
- Prism → Edit Instance → Settings → **Environment variables**: `mesa_glthread=true`.
Minecraft uses Mesa's OpenGL driver (radeonsi), and glthread helps the Deck's CPU-bound
draw path.
- Prism → Custom Commands → **Wrapper command**: `gamemoderun` (the flatpak reaches GameMode
through the desktop portal).
- Native 1280×800. Cap FPS at the panel rate (60 LCD / 90 OLED) **after** tuning.
Uncapped only matters for benchmarks.
## 6. Other platforms
- **Windows + NVIDIA**: NVIDIA Control Panel → Manage 3D settings → Program settings → your
Java `javaw.exe` → *Power management mode: Prefer maximum performance*. Leave *Threaded
optimization* alone: the driver ignores it for Minecraft, and Sodium handles it.
- **Linux + NVIDIA**: use the proprietary driver, not nouveau. Sodium already sets
`__GL_THREADED_OPTIMIZATIONS=0`, so you don't need to.
## 7. Still slow? Change these first
1. Turn shaders off.
2. Lower the Distant Horizons LOD distance (biggest always-on cost).
3. Disable Fresh Animations.
4. Lower the airship render distance (Sodium video settings).
5. Lower biome blend (helps stutter while flying, not steady FPS).
+53
View File
@@ -0,0 +1,53 @@
# Client performance analysis — Phase 1 (static baseline)
Goal: map the **client-side optimisation surface** of Onion MC (212 mods, MC 1.21.1 /
NeoForge 21.1.251) and rank the levers *before* benchmarking. Nothing here has been
measured on a client yet; Phase 2 benchmarks it (see `../../phase0/` for the fleet harness).
**Start with [`LEVERS.md`](LEVERS.md)**: the prioritised lever list, plus add and remove
candidates.
## Layout
| Path | What |
| --- | --- |
| `LEVERS.md` | Synthesis: ranked levers, per-machine expectations, add/remove candidates, corrections |
| `proposed-configs/` | **Phase 1b**: staged tuned configs (tiers + bench variants, generated by `build.py`) and the delivery-mechanism decision (README §1) |
| `BENCH-MATRIX.md` | **Phase 1b**: the Phase 2 run sheet (world, scenes S1–S6, config IDs, blocks B0–B13, decision rules) |
| `bench/onion-bench/` | Datapack for the bench world: scene teleports, `/fill` setups, sound storm, 120 s tour |
| `SPAWN-TUNING.md` | Shipped server spawn tuning (Alex's Mobs, Fowl Play, Critters): evidence and the B14 measurement plan |
| `PLAYER-TUNING.md` | **Phase 1b**: player guide (tiers, JVM/heap, shaders, resource packs, Deck) |
| `data/jarscan-report.md` | Static scan of every pinned jar: per-frame mixins, per-frame event hooks, atlas/model/shader/sound payloads, entity counts |
| `data/jarscan.json` | Raw scan output (re-generate, don't hand-edit) |
| `data/observed-default-configs/` | Generated defaults from the last real client launch (2026-09-24, older 110-mod pack). Use these to see actual default values |
| `research/r1-render-core.md` | Sodium / Iris + shaderpacks / Flywheel / ModernFix / ImmediatelyFast / EntityCulling / mipmaps |
| `research/r2-dh-xaero.md` | Distant Horizons 3.3.2 client + Xaero's minimap / world map |
| `research/r3-entity-animation.md` | EMF/ETF + Fresh Animations, GeckoLib, player animation mods, LambDynamicLights |
| `research/r4-effects-audio-hud.md` | Sound Physics, AmbientSounds, Subtle Effects, Perception, WATUT, TF / Alex's Caves visuals, HUD mods |
| `research/r5-candidate-mods.md` | Perf mods *not* in the pack (availability on NeoForge 1.21.1, compat, verdict) |
| `research/r6-jvm-platform.md` | GC / heap / JVM, Mesa / NVIDIA driver, Steam Deck platform levers |
| `research/r7-sable-create-client.md` | Sable / Aeronautics / SSRD / Create + Flywheel client cost |
| `research/r8-sweep-content-mobs.md` | Known-issue sweep: mob and content mods |
| `research/r9-sweep-libs-ui-misc.md` | Known-issue sweep: libraries, UI, misc client hooks |
| `scripts/jarscan.py`, `scripts/report.py` | The scanner |
| `scripts/jx.py` | Jar inspection without unzip/java: `ls`, `cat`, `str` (class-file string constants); nested jars as `outer.jar!inner.jar` |
Re-run the scan after the mod set changes:
```sh
# jars: download every mods/*.pw.toml url into a dir (analysis/audit/fetch.py does this)
python3 scripts/jarscan.py /tmp/onion/jars ../.. data/jarscan.json
python3 scripts/report.py data/jarscan.json data/jarscan-report.md
```
## Method and confidence
- **Static scan.** The scanner parses class-file annotations to get real `@Mixin` targets and
injector kinds. It reads constant pools for event-type references and PNG headers for
texture sizes, and it skips the Fabric-only payloads of multi-loader jars. The numbers are
*surface*, not cost: a mixin on `LevelRenderer` may be a one-line early-return.
- **Research.** Nine subagent briefs (deepseek-v4.1-flash / glm-5.3-flash) are tagged
VERIFIED / DOCUMENTED / COMMUNITY / INFERENCE with URLs. Load-bearing claims were
spot-checked by the orchestrator. Corrections are listed in `LEVERS.md` §7.
- **Observed defaults.** These come from the old Prism instance's generated configs. They
are the authoritative "what a player gets today" values for the mods that existed then.
+218
View File
@@ -0,0 +1,218 @@
# Bench results — proxy machine, run 1 (B0–B12a)
**Proxy data only.** One machine (Ryzen 9 9900X / RX 7800 XT, Manjaro), not the fleet (P0 Deck,
P1 7700K+1060, P2 6700K+1080 Ti). Run 1: 2026-10-16 22:25 → ~2026-10-17 05:00 EDT, 56 sessions
(57 launches incl. one auto-retry), 306/309 capture windows valid. Source:
`~/onion-bench/results/session.jsonl` (`ts >= 2026-10-16T22:24`), verdicts from
`harness/analyze.py --baseline C1 --noise B0 --since 2026-10-16T22:24`.
Run 2 (B4h, B7 shader rows, B12b, B14) and Run 3 (new mod set, Flerovium confirmation,
B14 ×4) are below; B13 is not done.
## Method
- **Harness**: `harness/bench.py` + `matrix.py`. Every session is one cold launch at
1920×1080 in headless gamescope, world restored from `bench-world-v1*.zip` (DH LODs
pre-generated, `generatorPlan=DISABLED`), scenes driven by an in-world datapack, spectator
camera, FTB Backups off, dedicated server stopped.
- **Captures**: per scene 15 s warm-up + 30 s capture (S5 tour 120 s, no warm-up) × 3 reps,
45 s settle after join. Frametimes from MangoHud; metrics = p50/p99 frametime, 1 % low
(frametime-based), avg fps.
- **Win rule (BENCH-MATRIX §4)**: win = median p99 improves by more than the B0 p99 spread
AND 1 % low doesn't drop by more than the B0 1 %-low spread; loss = worse beyond either;
else tie (ties keep the current/upstream value).
- **Noise floor (B0, C0 × 3 sessions on S1)**: p99 spread **0.40 ms**, 1 %-low spread
**6.6 fps** (p99 9.64 ms, 1 % low 88.3 fps).
- **Baselines**: each variant is compared with the C1 session(s) of its own block
(`C1@B3`, `C1@B11.baseline`, …), i.e. same JVM and same time window.
## Highlights
- **The shipped tuning (C1) beats upstream defaults (C0) on every scene.** C0 (B1) vs C1
(B2), same JVM (Prism default, 8 GB):
| Scene | p99 C0 → C1 (ms) | 1 % low C0 → C1 (fps) | avg fps C0 → C1 |
|---|---|---|---|
| S1 vista | 9.33 → 8.23 | 93.0 → 104.9 | 218 → 272 |
| S2 Create floor | 3.79 → 3.22 | 218.7 → 259.8 | 694 → 975 |
| S3 mob pen | 7.43 → 4.46 | 110.1 → 186.0 | 255 → 395 |
| S4 airships | 2.93 → 2.44 | 298.0 → 341.3 | 789 → 1073 |
| S5 tour | 3.47 → 2.95 | 194.2 → 223.8 | 788 → 1017 |
Startup ("Game took"): C0 31.38 s → C1 25.36 s. Atlas: C0 4096×4096×4 → C1 4096×4096×2.
- **Distant Horizons is the biggest single render cost.** Turning DH off (B3.dh-off) gives
S1 p99 7.99 → 5.38 ms, 1 % low 105.5 → 144.0; S5 3.09 → 2.34 ms, 211.2 → 282.4.
DH's LOW quality preset at C1's radius 128 (`dh-qlow`) is a **win** on S1 (−0.96 ms,
+9.5 fps). SSAO costs 1.25 ms p99 on S1.
- **Fresh Animations is expensive.** S3 p99 4.32 → 8.65 ms (FA) / 10.20 ms (FA +
Extensions); 1 % low 184.6 → 104.9 / 89.0. It is opt-in (not enabled by default) — keep
it that way and document the cost.
- **The added performance mods earn their place.** Removing BadOptimizations, Particle Core
or Sodium Extra each loses (Particle Core / Sodium Extra: S3 1 % low −24 / −25 fps).
- **ModernFix `dynamic_resources` (B6)**: startup 7.6 s faster (26.32 vs 33.92 s median) and
better frametimes with it on. Ship is gated only on the manual missing-texture check
(see B6 row).
## Per-block verdicts
Numbers are p99 ms / 1 % low fps; Δ is vs the block's C1 baseline (+ = better p99).
| Block | Change tested | Result | Verdict | Ship action |
|---|---|---|---|---|
| B0 | noise floor, C0 × 3 on S1 | p99 9.64 (spread 0.40), 1 % low 88.3 (spread 6.6) | — | thresholds for all blocks |
| B1 | C0 baseline, S1–S5 | see highlights table; startup 31.38 s | reference | — |
| B2 | C1 vs C0 | C1 wins S1–S5 (table above) | **win** | C1 stays shipped; no bisect needed |
| B2 | C1-deck vs C1 | S1 7.08/115.0 vs 8.23/104.9 (win); S2/S4/S5 tie; S3 4.84/167.5 vs 4.46/186.0 (loss) | mixed | keep Deck tier as is (S3 loss: check `entityShadows`/particles on fleet P0) |
| B2 | C1-high vs C1 | S1 tie; S2–S5 losses (e.g. S4 2.66/311.6 vs 2.44/341.3) | expected cost | keep High as opt-in quality tier |
| B3 | DH radius 64 | S1 7.46/111.1 vs 7.99/105.5 (win); S5 tie | win S1 | already the Deck value; no change to C1 |
| B3 | DH radius 192 / 256 | S1 tie (7.69/105.5, 8.07/101.5); S5 tie | tie | **decision**: on this GPU 256 (upstream) ties 128; fleet floors decide — keep 128 until fleet data |
| B3 | DH LOW quality preset @ r128 (`dh-qlow`) | S1 7.03/115.0 vs 7.99/105.5 (+0.96, +9.5) | **win** (S1 only) | candidate for DEFAULT after an S5 run + visual check |
| B3 | DH SSAO on | S1 9.24/95.3 (−1.25, −10.2) | loss | confirms C1 SSAO off |
| B3 | DH threads 4 | S1 tie; S5 3.32/197.4 vs 3.09/211.2 (loss) | loss | threads stay 2 |
| B3 | DH off (reference) | S1 5.38/144.0; S5 2.34/282.4 | — | cost reference only |
| B4 | GC J0/J1/J2/J3 @ 6 GB, S2+S5 | S5: J0 3.07/226.8, J1 3.11/228.1 (tie), J2 3.40/216.6 (loss), J3 3.75/173.4 (loss); S2 all tie | **J0** | keep J0; heap ladder = B4h (run 2) |
| B5 | Sable ships 0/1/4 (proxy) | S4 3.00/280.6, 3.12/261.6, 2.97/273.2 | — | ships nearly free on this GPU |
| B5 | SSRD 32/64/128, sable-noshade (4 ships) | 3.02/265.0, 3.03/264.7, 3.11/261.9, 3.04/263.0 vs pooled C1 (−8 to −11 fps 1 % low) | loss/noise | keep C1 (SSRD 16, shading on); decay run deferred (fleet P0/P1) |
| B6 | ModernFix dynamic_resources off vs on (C1) | S1 9.11/83.8 vs 8.48/98.6; S2 3.29/236.1 vs 3.18/264.0; startup 33.92 vs 26.32 s | dynres on **wins** | ship `modernfix-mixins.properties` **after** the manual missing-texture check |
| B8 | Fresh Animations (+Ext) on S3 | 8.65/104.9 (FA), 10.20/89.0 (FA+Ext) vs 4.32/184.6 | large cost | keep opt-in; document in PLAYER-TUNING |
| B8 | EMF animation LOD 20 / 8 (with FA) | vs C1-fa (LOD 12) 8.65/104.9: LOD 20 8.91/101.8 (tie), LOD 8 8.16/108.0 (+0.49, +3.1 → win) | LOD 8 wins under FA | optional: LOD 8 for FA users (PLAYER-TUNING note) |
| B8 | LambDynamicLights fancy / fastest | 4.41/181.8, 4.35/187.7 vs 4.32/184.6 | tie | keep C1 `fast` |
| B9 | Flywheel light flat / smooth (S2) | 3.45/246.6 (tie), 3.46/238.9 (−8.6, loss) vs 3.44/247.5 | tie/loss | keep C1 TRI_LINEAR |
| B10 | Sound Physics rays up / strict / no caps (S2s) | 3.69/216.2 (loss), 3.58/233.7 (tie), 3.64/216.9 (loss) vs 3.52/237.6 | loss/tie | keep C1 |
| B11 | Sodium 3 builder threads, blend 0/2, mipmaps 4 | all losses except blend 2 (S5 tie); e.g. mip4 S1 8.82/97.6 vs 8.12/104.2 | loss/tie | keep C1 |
| B12a | remove BadOptimizations / Particle Core / Sodium Extra | each loses ≥1 scene (e.g. S3 3.93/205.3, 4.63/188.4, 4.61/187.5 vs 3.87/212.5) | keep all | no change |
| B4h, B7, B12b, B14 | see **Run 2** below | | | |
| B13 | spark profile | — | **pending (manual)** | — |
## Run 2 (2026-10-17/18): B4h, B7, B12b, B14
Same machine and method; **default JVM** (run 1's B5–B12a ran on a leaked J3/6 GB JVM —
harness bug fixed in `60e10b3`; within-block verdicts unaffected). Verdicts use the B0
thresholds (p99 0.40 ms, 1 % low 6.6 fps); one session per row, so treat single-scene
margins near the threshold as soft. Numbers: p99 ms / 1 % low fps.
### B4h — heap ladder (J0, S2 + S5)
| heap | S2 | S5 |
|---|---|---|
| 4 GB | 3.58 / 229.1 | 3.32 / 188.7 |
| 5 GB | 3.58 / 238.6 | 3.13 / 209.1 |
| 6 GB | 3.63 / 225.7 | 3.14 / 211.8 |
| 8 GB | 3.58 / 234.5 | 3.18 / 209.2 |
**Smallest heap within the winner's spread: 5 GB** (S5 1 % low 209.1 vs best 211.8).
4 GB loses on S5 (−23 fps). Heap is a launcher setting, not a shipped config →
PLAYER-TUNING recommendation (≥ 5 GB), not a `build.py` change.
### B7 — shaders (data for owner; no auto-decision)
| profile | S1 vegetation | S2 Create floor | S4 airships |
|---|---|---|---|
| none (run-2 re-run) | 8.13 / 103.6 (274 fps avg) | 3.28 / 260.4 (974) | 2.50 / 332.5 (1072) |
| MakeUp shadowless-medium | 8.69 / 97.3 (263) | 12.88 / 67.9 (120) | 2.58 / 314.0 (1050) |
| MakeUp medium | 10.04 / 88.4 (213) | 12.32 / 71.9 (121) | 2.94 / 280.4 (779) |
| Complementary LOW | 10.85 / 83.2 (182) | 13.23 / 64.0 (120) | 4.16 / 205.2 (444) |
| Complementary MEDIUM | 10.92 / 78.5 (175) | 13.27 / 67.4 (118) | 4.33 / 192.8 (401) |
All shader rows loaded the pack (no `shader_not_loaded`). The dominant cost is **S2**:
any shaderpack makes Flywheel drop its GPU backends, so the Create floor goes from
974 to ~120 fps average (p99 3.3 → ~13 ms) regardless of profile. Elsewhere MakeUp
shadowless is nearly free; Complementary costs ~2.7 ms p99 on S1 and ~2× frame time on S4.
→ Supports **opt-in shaders**, MakeUp first, and **Iris Flywheel Compat** (B12b).
### B12b — trial mods (each vs its own baseline)
| trial | result | verdict |
|---|---|---|
| **Iris Flywheel Compat** 2.4.0 (MakeUp default, vs shader-baseline) | S2 **3.98 / 218.3 vs 13.26 / 65.8** (avg 644 vs 117 fps); S1 9.92 / 84.8 vs 10.23 / 83.8 (tie); S4 3.10 / 266.1 vs 3.01 / 277.6 (1 % low −11.5, loss) | **win under shaders** (restores Flywheel instancing) — add after a visual check of Create contraptions/airships with a shaderpack (unofficial mod; breaks on Create/Iris updates) |
| **Flerovium** 1.1.2 (vs C1) | S3 **3.48 / 229.9 vs 5.71 / 147.4**; S1/S2/S5 tie | **win** on entities — but the B12b C1 S3 baseline (5.71) is worse than every run-1 C1 S3 (4.32–4.46), so the true gain is ~0.9 ms / +45 fps vs run 1. Add after one confirmation pair. **Superseded by Run 3** (did not hold up) |
| Gnetum 3.3.6 | S3 4.41 / 189.3 (vs anomalous 5.71; ≈ run-1 C1) ; S1/S2/S5 tie | tie in practice → no add |
| Better Block Entities 1.3.4 | S2 3.51 / 245.3 (loss), S3 loss; with FA 9.86 / 89.8 vs 9.52 / 87.4 (tie) | reject |
| Super Resolution 0.9.2-alpha.2 | S1 8.74 / 99.7 (loss), S5 1 % low −16.6 (loss) | reject on this proxy (P0-only lever; its first launch rewrites `fml.toml` and demands a restart) |
| Ixeris 4.6.8 | S1 p99 −0.49 (loss), S5 1 % low −12.2 (loss); **one crash** (NPE in `GameRules.getInt` during server tick, retry ok) | reject |
| Cull Leaves 4.1.1 (vs Better Leaves baseline) | S1 tie; S5 1 % low 208.8 vs 217.1 (loss) | reject |
New mod adds (Iris Flywheel Compat, Flerovium) ⇒ **re-run B0** before any later matrix work.
### B14 — spawn tuning (defaults vs shipped, 10 min per site, **1 run each**)
*Superseded by Run 3 (4 runs per config).*
| site | defaults total / tuned | shipped total / tuned |
|---|---|---|
| jungle (−8 107 −201) | 123 / 5 | 118 / 3 |
| river (904 63 88) | 208 / 29 | 208 / 25 |
| warm ocean (−200 63 56) | 155 / 22 | 130 / 0 |
Automated rule says **back off**, but it is driven by one Fowl Play herd: at the warm-ocean
site defaults spawned 14 geese + 8 ducks (on nearby land/water inside the 128-block
sphere), shipped none; the other "vanished" species (gazelle, dragonfly) had max count 1.
Sea bunny — the warm-ocean target — appeared under neither config, so that site doesn't
exercise it. **Inconclusive at n = 1**: needs ≥ 3 interleaved runs per config (≈ 3 h)
before any back-off; if it persists, the candidate is Fowl Play duck/goose weights.
All runs: 33 count rows, `biome_ok=1`, config check ok.
## Run 3 (2026-10-18): new mod set, Flerovium confirmation, B14 ×4
Same machine and method, default JVM, **new mod set** (shipped in `7841361`: + Flerovium
1.1.2, + Iris Flywheel Compat 2.4.0, ModernFix `dynamic_resources=true`). Numbers are
p99 ms / 1 % low fps, medians of session medians.
### B0v2 — noise floor for the new mod set (C0, S1, 3 sessions)
| p99 | p99 spread | 1 % low | 1 % low spread | avg fps |
|---|---|---|---|---|
| 9.74 | **0.41 ms** | 87.2 | **1.6 fps** | 219 |
These are the thresholds for any later matrix work on this mod set.
### Flerovium confirmation (C1, 2 sessions each, interleaved on/off)
| scene | Flerovium on | Flerovium off | Δ (on vs off) | verdict |
|---|---|---|---|---|
| S1 vista | 8.42 / 99.8 | 8.02 / 105.0 | p99 −0.40, 1 % low −5.2 | **loss** (1 % low drop > 1.6) |
| S3 mob pen | 5.55 / 151.9 | 5.94 / 141.1 | p99 +0.39 (just under 0.41) | **tie / inconclusive** — S3 session spread is huge (p99 1.42–2.34 ms, 1 % low 32–61 fps) |
**The run-2 win did not hold up** (it was measured against an anomalous S3 baseline).
**Recommendation: remove Flerovium** — awaiting owner OK; it is currently shipped in `7841361`.
### B14 — spawn tuning, 4 runs per config
Median final count at 10 min within 128 blocks (total / tuned species):
| site | defaults | shipped |
|---|---|---|
| jungle | 129.5 / 4.0 | 128.0 / 2.5 |
| river | 208.0 / 34.5 | 211.5 / 28.5 |
| warm ocean | 145.0 / 9.5 | 136.0 / 0.5 |
The automated rule says **back off** (snail vanished at the jungle site, gazelle at the warm
ocean), but both peaked at 1 per run — noise, not a disappearance. Sea bunny never appeared
under either config, so this site doesn't exercise it. The warm-ocean tuned drop is mostly
a Fowl Play goose/duck herd. **Verdict: the tuning is harmless but does not measurably
reduce total entity load.** Owner decides keep vs revert; no config change made.
### Shipping incident
`7841361`'s `index.toml` captured hashes of runtime re-saved spawn configs, so the server's
packwiz install failed (`Hash invalid`) at 14:06 and the server didn't start. Fixed by
re-hashing in `c5d25fa` (server up 14:18). Nothing was pushed.
## Caveats
- Run 2: Super Resolution's first launch flipped `earlyWindowControl` in the bench `fml.toml` (not journaled by the harness); later run-2 sessions started with it off. Loading-screen only — frametimes unaffected; startup times after 01:54 are not comparable. Reset 2026-10-18.
- **Single proxy machine.** Fleet machines (P0–P2) were unreachable; the fleet fps floors
(P0 1 % low ≥ 30, P1/P2 ≥ 60) don't map to a 7800 XT — every verdict here is relative.
B2's "C1 wins on ≥ 2 machines" gate is satisfied on one machine only.
- **This GPU hides render costs.** Airships (B5), Flywheel lighting and DH radius are cheap
here; the fleet's older GPUs may rank them differently. B5 decay is deferred to P0/P1.
- **GPU clocks unpinned** (amdgpu DPM `auto`, no root); every row carries `gpu_dpm_auto`.
- **JVM differed between block groups (harness bug).** B0–B3 ran Prism's default args at
8 GB; B4 set each GC at 6 GB; `bench.py` never restored `instance.cfg`, so **B5–B12a all
ran on J3 Shenandoah, 6 GB**. Every verdict compares sessions inside one block (same JVM),
so verdicts stand, but absolute numbers aren't comparable across those groups and the B0
noise floor was measured under the other JVM.
- **One crash**: B10 `spr-nocap`, first attempt — `OutOfMemoryError: Ran out of memory
reading level data` after an unusually slow (~1.5 min) resource load, on the leaked
Shenandoah/6 GB JVM. The automatic retry was clean.
- Single sessions per variant (except B0/B6, 3 each); 3 reps per scene per session.
+63
View File
@@ -0,0 +1,63 @@
# Spawn tuning (server-side lever 3)
**Owner brief (2026-10-14):** tune spawns, but not in a way that hurts gameplay much. All
three files ship **enforced** from `config/` (this repo is the server directory, and clients
get them too, which only matters in singleplayer). Each file is **partial**: the mod fills
every other key with its default. Research came from three subagent briefs (2026-10-14);
every load-bearing fact below was re-checked against the pinned jar or the source at the
matching version. **Unmeasured:** bench block B14 (below) checks it.
## What shipped
### Alex's Mobs 1.22.17: `config/alexsmobs-common.toml`
- Format: NeoForge `ModConfigSpec` **COMMON** (`AlexsMobs.class`: `registerConfig(COMMON, …)`;
no custom filename). Keys are flat under `[spawning]` (`CommonConfig`: `push("spawning")`;
`buildInt` ignores its category argument). A partial file gets corrected and re-saved by
NeoForge.
- Changed (weight, 0–1000; group sizes are hard-coded in `AMWorldRegistry`): gazelle 40→20
(herds of 7), capuchinMonkey 28→14 (groups of 9–16), elephant 30→15, straddler 70→35,
rockyRoller 60→30, underminer 50→25, blobfish 30→15, mudskipper 28→14, caiman 29→15.
Everything rare is untouched.
- Issue #62 on Raguto/AlexsMobs-1.21.1 ("tons of monsters" / inflated *vanilla* spawns) is
open, unexplained, and no config addresses it. If it shows up, A/B test a world without
Alex's Mobs.
### Fowl Play 1.2.3: `config/fowlplay.json5`
- Format: YACL v3 JSON5 (`YACLIntegration`: `fowlplay.json5`). YACL 3.8.2
(`GsonConfigSerializer.loadSafely`) keeps defaults for missing fields and re-saves; a bad
value is logged and left at its default, not a crash.
- Changed (default → new): duck weight 4→2, max group 6→4 · goose 4→2, 4→3 · gull 5→3, 8→5 ·
sparrow 20→12, 6→4. Songbirds and raptors are untouched.
- Context: #214 (~400 ducks on rivers) is open. 1.2.1 added a 200-tick spawn gate. The
category caps (`fowlplay_birds` 20, `fowlplay_ambient_birds` 15) scale with loaded chunks
like vanilla, and only became configurable *after* 1.2.3. If counts are still high, the
next lever is a Paxi datapack that narrows `#fowlplay:spawns_ducks` (currently includes
forest tags), added through `analysis/audit/gen_fixes.py`.
### Critters and Companions 2.7.0: `config/crittersandcompanions-common.json5`
- It *does* have a spawn config (contrary to LEVERS' "no config"): YACL JSON5, nested
`spawning` object, one `List<SpawnEntry>` per mob. `SpawnEntry` is a record
`{biomeSpec, weight, min, max}` with no custom type adapter, so it serialises as plain Gson
record JSON. The jar has no biome-modifier JSON, so a datapack *cannot* reduce these spawns.
- A listed mob's list **replaces** its defaults, so each changed mob lists all its default
biomes. `"configVersion": 2` is required, otherwise the v1→v2 migration runs.
- Changed: sea bunny (the worst: warm ocean 32 × groups ≤4 → 20 × ≤3; other oceans 16 → 10,
max 4 → 3), leaf insect jungle 14→8 / forest 6→4, dragonfly lush 10→6, and the
ladybug / stag beetle / roly poly / snail / stick bug / weevil rules about −40 %. Unchanged:
jumping spider, red panda, ferret, dumbo octopus, otter, koi, shima enaga.
## Verify on first boot (part of BENCH-MATRIX §0)
- `latest.log` has no YACL "Failed to deserialize" lines for `fowlplay` or `crittersandcompanions`.
- After the first launch, each file has been re-saved in full with our values intact.
(In the repo working tree that shows up as a diff: **don't commit the expanded files**
unless you mean to.)
## B14: measure it (P2, singleplayer, Creative, `doMobSpawning true`)
Fresh world `onionspawn`, at a river/forest edge (ducks), a jungle (capuchins, leaf
insects) and a warm ocean (sea bunnies). Stand still 10 minutes at each, then count with
`/execute if entity @e[type=<id>,distance=..128]` for `fowlplay:duck`, `fowlplay:gull`,
`alexsmobs:capuchin_monkey`, `crittersandcompanions:sea_bunny`, and in total
(`@e[type=!player,distance=..128]`). Compare against the same world with these three
config files removed (defaults). **Keep the tuning** if totals drop and every species still
appears; **back it off** if a species effectively disappears. Spot-check that vanilla
passives (cows, sheep) still spawn normally.
@@ -0,0 +1 @@
tp @a 0 200 0 0 20
@@ -0,0 +1 @@
tp @a 1023 165 -10 0 40
@@ -0,0 +1,98 @@
# Create floor: 2304 kinetic block entities. Verify they rotate (see BENCH-MATRIX.md).
fill 998 150 -1 1048 150 48 minecraft:smooth_stone
fill 1000 151 0 1047 151 0 create:shaft[axis=x]
setblock 999 151 0 create:creative_motor[facing=east]
fill 1000 151 1 1047 151 1 create:cogwheel[axis=x]
setblock 999 151 1 create:creative_motor[facing=east]
fill 1000 151 2 1047 151 2 create:shaft[axis=x]
setblock 999 151 2 create:creative_motor[facing=east]
fill 1000 151 3 1047 151 3 create:cogwheel[axis=x]
setblock 999 151 3 create:creative_motor[facing=east]
fill 1000 151 4 1047 151 4 create:shaft[axis=x]
setblock 999 151 4 create:creative_motor[facing=east]
fill 1000 151 5 1047 151 5 create:cogwheel[axis=x]
setblock 999 151 5 create:creative_motor[facing=east]
fill 1000 151 6 1047 151 6 create:shaft[axis=x]
setblock 999 151 6 create:creative_motor[facing=east]
fill 1000 151 7 1047 151 7 create:cogwheel[axis=x]
setblock 999 151 7 create:creative_motor[facing=east]
fill 1000 151 8 1047 151 8 create:shaft[axis=x]
setblock 999 151 8 create:creative_motor[facing=east]
fill 1000 151 9 1047 151 9 create:cogwheel[axis=x]
setblock 999 151 9 create:creative_motor[facing=east]
fill 1000 151 10 1047 151 10 create:shaft[axis=x]
setblock 999 151 10 create:creative_motor[facing=east]
fill 1000 151 11 1047 151 11 create:cogwheel[axis=x]
setblock 999 151 11 create:creative_motor[facing=east]
fill 1000 151 12 1047 151 12 create:shaft[axis=x]
setblock 999 151 12 create:creative_motor[facing=east]
fill 1000 151 13 1047 151 13 create:cogwheel[axis=x]
setblock 999 151 13 create:creative_motor[facing=east]
fill 1000 151 14 1047 151 14 create:shaft[axis=x]
setblock 999 151 14 create:creative_motor[facing=east]
fill 1000 151 15 1047 151 15 create:cogwheel[axis=x]
setblock 999 151 15 create:creative_motor[facing=east]
fill 1000 151 16 1047 151 16 create:shaft[axis=x]
setblock 999 151 16 create:creative_motor[facing=east]
fill 1000 151 17 1047 151 17 create:cogwheel[axis=x]
setblock 999 151 17 create:creative_motor[facing=east]
fill 1000 151 18 1047 151 18 create:shaft[axis=x]
setblock 999 151 18 create:creative_motor[facing=east]
fill 1000 151 19 1047 151 19 create:cogwheel[axis=x]
setblock 999 151 19 create:creative_motor[facing=east]
fill 1000 151 20 1047 151 20 create:shaft[axis=x]
setblock 999 151 20 create:creative_motor[facing=east]
fill 1000 151 21 1047 151 21 create:cogwheel[axis=x]
setblock 999 151 21 create:creative_motor[facing=east]
fill 1000 151 22 1047 151 22 create:shaft[axis=x]
setblock 999 151 22 create:creative_motor[facing=east]
fill 1000 151 23 1047 151 23 create:cogwheel[axis=x]
setblock 999 151 23 create:creative_motor[facing=east]
fill 1000 151 24 1047 151 24 create:shaft[axis=x]
setblock 999 151 24 create:creative_motor[facing=east]
fill 1000 151 25 1047 151 25 create:cogwheel[axis=x]
setblock 999 151 25 create:creative_motor[facing=east]
fill 1000 151 26 1047 151 26 create:shaft[axis=x]
setblock 999 151 26 create:creative_motor[facing=east]
fill 1000 151 27 1047 151 27 create:cogwheel[axis=x]
setblock 999 151 27 create:creative_motor[facing=east]
fill 1000 151 28 1047 151 28 create:shaft[axis=x]
setblock 999 151 28 create:creative_motor[facing=east]
fill 1000 151 29 1047 151 29 create:cogwheel[axis=x]
setblock 999 151 29 create:creative_motor[facing=east]
fill 1000 151 30 1047 151 30 create:shaft[axis=x]
setblock 999 151 30 create:creative_motor[facing=east]
fill 1000 151 31 1047 151 31 create:cogwheel[axis=x]
setblock 999 151 31 create:creative_motor[facing=east]
fill 1000 151 32 1047 151 32 create:shaft[axis=x]
setblock 999 151 32 create:creative_motor[facing=east]
fill 1000 151 33 1047 151 33 create:cogwheel[axis=x]
setblock 999 151 33 create:creative_motor[facing=east]
fill 1000 151 34 1047 151 34 create:shaft[axis=x]
setblock 999 151 34 create:creative_motor[facing=east]
fill 1000 151 35 1047 151 35 create:cogwheel[axis=x]
setblock 999 151 35 create:creative_motor[facing=east]
fill 1000 151 36 1047 151 36 create:shaft[axis=x]
setblock 999 151 36 create:creative_motor[facing=east]
fill 1000 151 37 1047 151 37 create:cogwheel[axis=x]
setblock 999 151 37 create:creative_motor[facing=east]
fill 1000 151 38 1047 151 38 create:shaft[axis=x]
setblock 999 151 38 create:creative_motor[facing=east]
fill 1000 151 39 1047 151 39 create:cogwheel[axis=x]
setblock 999 151 39 create:creative_motor[facing=east]
fill 1000 151 40 1047 151 40 create:shaft[axis=x]
setblock 999 151 40 create:creative_motor[facing=east]
fill 1000 151 41 1047 151 41 create:cogwheel[axis=x]
setblock 999 151 41 create:creative_motor[facing=east]
fill 1000 151 42 1047 151 42 create:shaft[axis=x]
setblock 999 151 42 create:creative_motor[facing=east]
fill 1000 151 43 1047 151 43 create:cogwheel[axis=x]
setblock 999 151 43 create:creative_motor[facing=east]
fill 1000 151 44 1047 151 44 create:shaft[axis=x]
setblock 999 151 44 create:creative_motor[facing=east]
fill 1000 151 45 1047 151 45 create:cogwheel[axis=x]
setblock 999 151 45 create:creative_motor[facing=east]
fill 1000 151 46 1047 151 46 create:shaft[axis=x]
setblock 999 151 46 create:creative_motor[facing=east]
fill 1000 151 47 1047 151 47 create:cogwheel[axis=x]
setblock 999 151 47 create:creative_motor[facing=east]
@@ -0,0 +1 @@
tp @a 2011 158 -6 0 35
@@ -0,0 +1,84 @@
# Mob pen: 30 villagers, 15 cows, 15 sheep, 10 torch-carrying allays, 10 dropped torches.
fill 1999 150 -1 2024 157 24 minecraft:glass hollow
fill 2000 150 0 2023 150 23 minecraft:grass_block
kill @e[type=!player,x=1990,y=140,z=-10,dx=45,dy=30,dz=45]
summon minecraft:villager 2002 151 2 {PersistenceRequired:1b}
summon minecraft:villager 2004 151 2 {PersistenceRequired:1b}
summon minecraft:villager 2006 151 2 {PersistenceRequired:1b}
summon minecraft:villager 2008 151 2 {PersistenceRequired:1b}
summon minecraft:villager 2010 151 2 {PersistenceRequired:1b}
summon minecraft:villager 2012 151 2 {PersistenceRequired:1b}
summon minecraft:villager 2014 151 2 {PersistenceRequired:1b}
summon minecraft:villager 2016 151 2 {PersistenceRequired:1b}
summon minecraft:villager 2018 151 2 {PersistenceRequired:1b}
summon minecraft:villager 2020 151 2 {PersistenceRequired:1b}
summon minecraft:villager 2002 151 5 {PersistenceRequired:1b}
summon minecraft:villager 2004 151 5 {PersistenceRequired:1b}
summon minecraft:villager 2006 151 5 {PersistenceRequired:1b}
summon minecraft:villager 2008 151 5 {PersistenceRequired:1b}
summon minecraft:villager 2010 151 5 {PersistenceRequired:1b}
summon minecraft:villager 2012 151 5 {PersistenceRequired:1b}
summon minecraft:villager 2014 151 5 {PersistenceRequired:1b}
summon minecraft:villager 2016 151 5 {PersistenceRequired:1b}
summon minecraft:villager 2018 151 5 {PersistenceRequired:1b}
summon minecraft:villager 2020 151 5 {PersistenceRequired:1b}
summon minecraft:villager 2002 151 8 {PersistenceRequired:1b}
summon minecraft:villager 2004 151 8 {PersistenceRequired:1b}
summon minecraft:villager 2006 151 8 {PersistenceRequired:1b}
summon minecraft:villager 2008 151 8 {PersistenceRequired:1b}
summon minecraft:villager 2010 151 8 {PersistenceRequired:1b}
summon minecraft:villager 2012 151 8 {PersistenceRequired:1b}
summon minecraft:villager 2014 151 8 {PersistenceRequired:1b}
summon minecraft:villager 2016 151 8 {PersistenceRequired:1b}
summon minecraft:villager 2018 151 8 {PersistenceRequired:1b}
summon minecraft:villager 2020 151 8 {PersistenceRequired:1b}
summon minecraft:cow 2002 151 12 {PersistenceRequired:1b}
summon minecraft:cow 2006 151 12 {PersistenceRequired:1b}
summon minecraft:cow 2010 151 12 {PersistenceRequired:1b}
summon minecraft:cow 2014 151 12 {PersistenceRequired:1b}
summon minecraft:cow 2018 151 12 {PersistenceRequired:1b}
summon minecraft:cow 2002 151 15 {PersistenceRequired:1b}
summon minecraft:cow 2006 151 15 {PersistenceRequired:1b}
summon minecraft:cow 2010 151 15 {PersistenceRequired:1b}
summon minecraft:cow 2014 151 15 {PersistenceRequired:1b}
summon minecraft:cow 2018 151 15 {PersistenceRequired:1b}
summon minecraft:cow 2002 151 18 {PersistenceRequired:1b}
summon minecraft:cow 2006 151 18 {PersistenceRequired:1b}
summon minecraft:cow 2010 151 18 {PersistenceRequired:1b}
summon minecraft:cow 2014 151 18 {PersistenceRequired:1b}
summon minecraft:cow 2018 151 18 {PersistenceRequired:1b}
summon minecraft:sheep 2003 151 13 {PersistenceRequired:1b}
summon minecraft:sheep 2007 151 13 {PersistenceRequired:1b}
summon minecraft:sheep 2011 151 13 {PersistenceRequired:1b}
summon minecraft:sheep 2015 151 13 {PersistenceRequired:1b}
summon minecraft:sheep 2019 151 13 {PersistenceRequired:1b}
summon minecraft:sheep 2003 151 16 {PersistenceRequired:1b}
summon minecraft:sheep 2007 151 16 {PersistenceRequired:1b}
summon minecraft:sheep 2011 151 16 {PersistenceRequired:1b}
summon minecraft:sheep 2015 151 16 {PersistenceRequired:1b}
summon minecraft:sheep 2019 151 16 {PersistenceRequired:1b}
summon minecraft:sheep 2003 151 19 {PersistenceRequired:1b}
summon minecraft:sheep 2007 151 19 {PersistenceRequired:1b}
summon minecraft:sheep 2011 151 19 {PersistenceRequired:1b}
summon minecraft:sheep 2015 151 19 {PersistenceRequired:1b}
summon minecraft:sheep 2019 151 19 {PersistenceRequired:1b}
summon minecraft:allay 2002 154 20 {PersistenceRequired:1b,HandItems:[{id:"minecraft:torch",count:1},{}]}
summon minecraft:allay 2004 154 20 {PersistenceRequired:1b,HandItems:[{id:"minecraft:torch",count:1},{}]}
summon minecraft:allay 2006 154 20 {PersistenceRequired:1b,HandItems:[{id:"minecraft:torch",count:1},{}]}
summon minecraft:allay 2008 154 20 {PersistenceRequired:1b,HandItems:[{id:"minecraft:torch",count:1},{}]}
summon minecraft:allay 2010 154 20 {PersistenceRequired:1b,HandItems:[{id:"minecraft:torch",count:1},{}]}
summon minecraft:allay 2012 154 20 {PersistenceRequired:1b,HandItems:[{id:"minecraft:torch",count:1},{}]}
summon minecraft:allay 2014 154 20 {PersistenceRequired:1b,HandItems:[{id:"minecraft:torch",count:1},{}]}
summon minecraft:allay 2016 154 20 {PersistenceRequired:1b,HandItems:[{id:"minecraft:torch",count:1},{}]}
summon minecraft:allay 2018 154 20 {PersistenceRequired:1b,HandItems:[{id:"minecraft:torch",count:1},{}]}
summon minecraft:allay 2020 154 20 {PersistenceRequired:1b,HandItems:[{id:"minecraft:torch",count:1},{}]}
summon minecraft:item 2002 151 22 {Age:-32768s,PickupDelay:32767s,Item:{id:"minecraft:torch",count:1}}
summon minecraft:item 2004 151 22 {Age:-32768s,PickupDelay:32767s,Item:{id:"minecraft:torch",count:1}}
summon minecraft:item 2006 151 22 {Age:-32768s,PickupDelay:32767s,Item:{id:"minecraft:torch",count:1}}
summon minecraft:item 2008 151 22 {Age:-32768s,PickupDelay:32767s,Item:{id:"minecraft:torch",count:1}}
summon minecraft:item 2010 151 22 {Age:-32768s,PickupDelay:32767s,Item:{id:"minecraft:torch",count:1}}
summon minecraft:item 2012 151 22 {Age:-32768s,PickupDelay:32767s,Item:{id:"minecraft:torch",count:1}}
summon minecraft:item 2014 151 22 {Age:-32768s,PickupDelay:32767s,Item:{id:"minecraft:torch",count:1}}
summon minecraft:item 2016 151 22 {Age:-32768s,PickupDelay:32767s,Item:{id:"minecraft:torch",count:1}}
summon minecraft:item 2018 151 22 {Age:-32768s,PickupDelay:32767s,Item:{id:"minecraft:torch",count:1}}
summon minecraft:item 2020 151 22 {Age:-32768s,PickupDelay:32767s,Item:{id:"minecraft:torch",count:1}}
@@ -0,0 +1 @@
tp @a 3000 172 -10 0 5
@@ -0,0 +1,12 @@
# Run once after creating the bench world (creative, cheats on).
gamerule doDaylightCycle false
gamerule doWeatherCycle false
gamerule doMobSpawning false
gamerule doPatrolSpawning false
gamerule doTraderSpawning false
gamerule doInsomnia false
gamerule doFireTick false
gamerule announceAdvancements false
time set 6000
weather clear
difficulty normal
@@ -0,0 +1 @@
schedule clear bench:storm_tick
@@ -0,0 +1 @@
function bench:storm_tick
@@ -0,0 +1,26 @@
# Called every tick while the storm runs. Stop with bench:storm_off.
playsound create:mechanical_press_activation block @a 1004 151 4 1 1
playsound create:mechanical_press_activation block @a 1020 151 4 1 1
playsound create:mechanical_press_activation block @a 1036 151 4 1 1
playsound create:mechanical_press_activation block @a 1044 151 20 1 1
playsound create:mechanical_press_activation block @a 1004 151 28 1 1
playsound create:mechanical_press_activation block @a 1020 151 44 1 1
playsound create:mechanical_press_activation block @a 1036 151 36 1 1
playsound create:mechanical_press_activation block @a 1044 151 44 1 1
playsound create:cogs block @a 1004 151 4 1 1
playsound create:cogs block @a 1020 151 4 1 1
playsound create:cogs block @a 1036 151 4 1 1
playsound create:cogs block @a 1044 151 20 1 1
playsound create:cogs block @a 1004 151 28 1 1
playsound create:cogs block @a 1020 151 44 1 1
playsound create:cogs block @a 1036 151 36 1 1
playsound create:cogs block @a 1044 151 44 1 1
playsound create:mixing block @a 1004 151 4 1 1
playsound create:mixing block @a 1020 151 4 1 1
playsound create:mixing block @a 1036 151 4 1 1
playsound create:mixing block @a 1044 151 20 1 1
playsound create:mixing block @a 1004 151 28 1 1
playsound create:mixing block @a 1020 151 44 1 1
playsound create:mixing block @a 1036 151 36 1 1
playsound create:mixing block @a 1044 151 44 1 1
schedule function bench:storm_tick 1t
@@ -0,0 +1,2 @@
tp @a 0 180 0 -90 15
schedule function bench:tour/step1 100t
@@ -0,0 +1,2 @@
tp @a 160 180 0 -90 15
schedule function bench:tour/step2 100t
@@ -0,0 +1,2 @@
tp @a 1600 180 0 -90 15
schedule function bench:tour/step11 100t
@@ -0,0 +1,2 @@
tp @a 1760 180 0 -90 15
schedule function bench:tour/step12 100t
@@ -0,0 +1,2 @@
tp @a 1920 180 0 -90 15
schedule function bench:tour/step13 100t
@@ -0,0 +1,2 @@
tp @a 2080 180 0 -90 15
schedule function bench:tour/step14 100t
@@ -0,0 +1,2 @@
tp @a 2240 180 0 -90 15
schedule function bench:tour/step15 100t
@@ -0,0 +1,2 @@
tp @a 2400 180 0 -90 15
schedule function bench:tour/step16 100t
@@ -0,0 +1,2 @@
tp @a 2560 180 0 -90 15
schedule function bench:tour/step17 100t
@@ -0,0 +1,2 @@
tp @a 2720 180 0 -90 15
schedule function bench:tour/step18 100t
@@ -0,0 +1,2 @@
tp @a 2880 180 0 -90 15
schedule function bench:tour/step19 100t
@@ -0,0 +1,2 @@
tp @a 3040 180 0 -90 15
schedule function bench:tour/step20 100t
@@ -0,0 +1,2 @@
tp @a 320 180 0 -90 15
schedule function bench:tour/step3 100t
@@ -0,0 +1,2 @@
tp @a 3200 180 0 -90 15
schedule function bench:tour/step21 100t
@@ -0,0 +1,2 @@
tp @a 3360 180 0 -90 15
schedule function bench:tour/step22 100t
@@ -0,0 +1,2 @@
tp @a 3520 180 0 -90 15
schedule function bench:tour/step23 100t
@@ -0,0 +1,2 @@
tp @a 3680 180 0 -90 15
tellraw @a {"text":"bench tour done"}
@@ -0,0 +1,2 @@
tp @a 480 180 0 -90 15
schedule function bench:tour/step4 100t
@@ -0,0 +1,2 @@
tp @a 640 180 0 -90 15
schedule function bench:tour/step5 100t
@@ -0,0 +1,2 @@
tp @a 800 180 0 -90 15
schedule function bench:tour/step6 100t
@@ -0,0 +1,2 @@
tp @a 960 180 0 -90 15
schedule function bench:tour/step7 100t
@@ -0,0 +1,2 @@
tp @a 1120 180 0 -90 15
schedule function bench:tour/step8 100t
@@ -0,0 +1,2 @@
tp @a 1280 180 0 -90 15
schedule function bench:tour/step9 100t
@@ -0,0 +1,2 @@
tp @a 1440 180 0 -90 15
schedule function bench:tour/step10 100t
@@ -0,0 +1 @@
function bench:tour/step0
@@ -0,0 +1,23 @@
schedule clear bench:tour/step1
schedule clear bench:tour/step2
schedule clear bench:tour/step3
schedule clear bench:tour/step4
schedule clear bench:tour/step5
schedule clear bench:tour/step6
schedule clear bench:tour/step7
schedule clear bench:tour/step8
schedule clear bench:tour/step9
schedule clear bench:tour/step10
schedule clear bench:tour/step11
schedule clear bench:tour/step12
schedule clear bench:tour/step13
schedule clear bench:tour/step14
schedule clear bench:tour/step15
schedule clear bench:tour/step16
schedule clear bench:tour/step17
schedule clear bench:tour/step18
schedule clear bench:tour/step19
schedule clear bench:tour/step20
schedule clear bench:tour/step21
schedule clear bench:tour/step22
schedule clear bench:tour/step23
@@ -0,0 +1 @@
{"pack": {"pack_format": 48, "description": "Onion MC client-perf bench scenes (Phase 2)"}}
+312
View File
@@ -0,0 +1,312 @@
# Static client-perf surface scan
Generated by `scripts/jarscan.py` + `scripts/report.py` from the exact jars pinned in `mods/*.pw.toml`.
All numbers are static (what the jar *contains*), not runtime cost. They tell us where cost *can* come from.
- **frame mixins** = mixin classes whose `@Mixin` target is on a per-frame path (LevelRenderer, GameRenderer, entity/item/block-entity renderers, Gui, ParticleEngine, blaze3d, Sodium, Iris, Camera).
- **risky** = `@Overwrite`/`@Redirect` injectors inside those frame mixins (replace vanilla logic; most likely to conflict with Sodium/Iris/ModernFix).
- **frame events** = NeoForge/Architectury/Fabric per-frame event types referenced by the mod’s classes.
- **atlas sprites** = block+item textures in 16×16-sprite equivalents (what lands in `blocks.png-atlas`, mipmapped, in VRAM).
## Pack totals (client-loaded mods)
| metric | value |
| --- | --- |
| client-loaded mods | 209 (server-only: 3) |
| mixin classes (client-loaded) | 3883 |
| frame-path mixin classes | 951 |
| frame-path @Overwrite/@Redirect | 121 |
| mods hooking ≥1 per-frame event | 58 |
| mods hooking client/player/entity tick events | 79 |
| block+item textures | 25277 files, 15.4 Mpx ≈ 60138 16×16 sprites |
| implied blocks atlas (lower bound, + vanilla) | ≥ 4096×4096 ; ×4 B ×1.33 mip ≈ 85 MiB VRAM at the square size |
| animated block/item textures | 312 |
| entity textures | 37.7 Mpx |
| models / blockstates | 69442 / 16361 |
| entity types (lang proxy) | 603 |
| blocks / items (lang proxy) | 15422 / 5178 |
| geckolib geo+animation files | 264 |
| vanilla core-shader overrides | sable, sodium, xaeros-minimap, xaeros-world-map |
| post-effect files | alexs-caves-(unofficial-port)(7), charta(1), citadel-(1.21.1-port)(1), exposure(19), lodestonelib(2), playerrevive(1), shatterbyte-lib(1) |
## Per-frame mixin surface
| mod | side | frame mixins | risky | all mixins | hottest targets |
| --- | --- | ---: | ---: | ---: | --- |
| sable | both | 127 | 29 | 496 | LevelRenderer×23, GameRenderer×12, Camera×5, RenderTarget×4, EntityRenderDispatcher×3, EntityRenderer×3 |
| iris | client | 114 | 28 | 171 | LevelRenderer×11, GlStateManager×7, GameRenderer×4, RenderType×4, BufferBuilder×3, DebugScreenOverlay×3 |
| controlify | client | 84 | 0 | 143 | AbstractSelectionList×4, AbstractContainerScreen×3, AbstractWidget×3, CreativeModeInventoryScreen×3, Gui×3, Screen×3 |
| sodium | client | 48 | 28 | 111 | LevelRenderer×5, ItemRenderer×3, BufferBuilder×2, DebugScreenOverlay×2, RenderChunkRegion×2, RenderSystem×2 |
| entity-model-features | client | 42 | 0 | 64 | CustomHeadLayer×2, EntityModelSet×2, GameRenderer×2, ModelPart$Cube×2, ParrotOnShoulderLayer×2, ArrowRenderer×1 |
| create | both | 30 | 0 | 101 | LevelRenderer×6, GameRenderer×2, HumanoidArmorLayer×2, PoseStack×2, RenderSystem×2, Screen×2 |
| entitytexturefeatures | client | 27 | 0 | 60 | BlockEntityRenderDispatcher×1, BufferBuilder×1, ElytraLayer×1, EndCrystalRenderer×1, EnderDragonRenderer×1, EntityRenderDispatcher×1 |
| subtle-effects | both | 26 | 0 | 118 | ParticleEngine×3, GameRenderer×2, BubbleColumnUpParticle×1, BubbleColumnUpParticle$Provider×1, BubbleParticle×1, BubbleParticle$Provider×1 |
| immediatelyfast | client | 25 | 11 | 39 | GameRenderer×3, DebugScreenOverlay×2, GuiGraphics×2, AbstractContainerScreen×1, BufferUploader×1, ChatScreen×1 |
| modernfix | both | 21 | 4 | 156 | CreateWorldScreen×2, ItemRenderer×2, LivingEntityRenderer×2, ConnectScreen×1, CubeDefinition×1, EnderDragonRenderer×1 |
| create-aeronautics | both | 20 | 0 | 134 | LevelRenderer×5, CreativeModeInventoryScreen×2, AbstractContainerScreen×1, CompassItemPropertyFunction×1, CreateWorldScreen×1, CreateWorldScreen$WorldTab×1 |
| emotecraft | both | 18 | 2 | 29 | LivingEntityRenderer×2, ModelPart×2, PlayerModel×2, Camera×1, CapeLayer×1, ElytraLayer×1 |
| playeranimator | both | 16 | 2 | 17 | LivingEntityRenderer×2, PlayerModel×2, Camera×1, CapeLayer×1, ElytraLayer×1, EntityRenderDispatcher×1 |
| adorable-hamster-pets | both | 12 | 0 | 38 | Gui×2, LivingEntityRenderer×2, AbstractContainerScreen×1, BlockEntityRenderDispatcher×1, EntityRenderDispatcher×1, ItemInHandLayer×1 |
| exposure | both | 12 | 1 | 41 | DebugScreenOverlay×2, AnvilScreen×1, Camera×1, GameRenderer×1, Gui×1, HumanoidModel×1 |
| icys-better-horses | both | 12 | 0 | 56 | AbstractContainerScreen×2, HorseModel×2, LivingEntityRenderer×2, AbstractHorseRenderer×1, Camera×1, GameRenderer×1 |
| moonlight | both | 12 | 0 | 82 | AgeableListModel×1, CartographyTableScreen×1, GameRenderer×1, HumanoidModel×1, ItemInHandRenderer×1, Lighting×1 |
| 3dskinlayers | client | 11 | 0 | 23 | CustomHeadLayer×1, Deadmau5EarsLayer×1, GuiGraphics×1, ModelPart×1, NativeImage×1, PlayerModel×1 |
| alexs-caves-(unofficial-port) | both | 11 | 0 | 61 | HumanoidModel×3, Camera×1, GameRenderer×1, HumanoidArmorLayer×1, ItemFrameRenderer×1, LevelRenderer×1 |
| doggy-talents-next | both | 11 | 0 | 17 | Camera×2, CubeDefinition×1, CubeDeformation×1, Gui×1, LayerDefinition×1, LivingEntityRenderer×1 |
| lambdynamiclights | client | 11 | 0 | 42 | LevelRenderer×3, DebugScreenOverlay×1, EntityRenderer×1, GameRenderer×1, Particle×1, ParticleEngine×1 |
| lodestonelib | both | 10 | 3 | 15 | AbstractContainerScreen×1, CreativeModeInventoryScreen×1, EffectProgram×1, GameRenderer×1, Gui×1, GuiGraphics×1 |
| iceberg | both | 9 | 0 | 22 | GuiGraphics×2, HumanoidArmorLayer×2, TooltipRenderUtil×2, ClientTextTooltip×1, Gui×1, Screen×1 |
| shatterbyte-lib | both | 9 | 1 | 11 | GameRenderer×3, AbstractWidget×2, AbstractContainerScreen×1, Gui×1, ParticleEngine×1, Screen×1 |
| ssrd | both | 9 | 1 | 24 | BlockEntityRenderer×1, EntityRenderDispatcher×1, Frustum×1, GameRenderer×1, LevelRenderer×1, RenderSectionManager×1 |
| what-are-they-up-to | both | 9 | 1 | 11 | Screen×2, GameRenderer×1, Gui×1, NativeImage×1, ParticleEngine×1, PlayerModel×1 |
| xaeros-minimap | both | 9 | 0 | 30 | AbstractButton×1, AgeableListModel×1, EnderDragonRenderer$DragonModel×1, HierarchicalModel×1, ListModel×1, LlamaModel×1 |
| entityculling | client | 8 | 0 | 19 | BlockEntityRenderDispatcher×1, BlockEntityRenderer×1, DebugScreenOverlay×1, EntityRenderer×1, GuiGraphics×1, LevelRenderer×1 |
| fusion-connected-textures | client | 8 | 0 | 33 | EntityModelSet×1, EntityRenderDispatcher×1, EntityRenderer×1, EntityRenderers×1, ItemRenderer×1, RenderChunkRegion×1 |
| sound | client | 8 | 0 | 33 | AbstractContainerScreen×1, ChatComponent×1, ChatScreen×1, CreativeModeInventoryScreen×1, CreativeModeInventoryScreen$ItemPickerMenu×1, EditBox×1 |
| ars-nouveau | both | 7 | 1 | 46 | LevelRenderer×2, AbstractSliderButton×1, ChatComponent×1, EntityRenderer×1, GameRenderer×1, Screen×1 |
| chalk-mod | both | 7 | 0 | 16 | ClientTooltipComponent×2, DebugScreenOverlay×2, AbstractContainerScreen×1, EntityRenderer×1, LevelRenderer×1 |
| distanthorizons | both | 7 | 2 | 43 | GlFramebuffer×2, DebugScreenOverlay×1, FogRenderer×1, LevelRenderer×1, LightTexture×1, OptionsScreen×1 |
| fwa | client | 7 | 1 | 12 | BlockRenderer×2, BlockEntityRenderDispatcher×1, LevelRenderer×1, RenderChunkRegion×1, ShadowRenderer×1, ShulkerBoxRenderer×1 |
| slice-and-dice | both | 7 | 0 | 20 | BufferBuilder×1, GameRenderer×1, ItemRenderer×1, ParticleEngine×1, RenderSystem×1, RenderType×1 |
| artifacts | both | 6 | 0 | 56 | PlayerRenderer×3, GameRenderer×1, HumanoidModel×1, LivingEntityRenderer×1 |
| blueprint | both | 6 | 0 | 36 | Camera×1, ModelPart×1, PlayerRenderer×1, RabbitRenderer×1, SkinCustomizationScreen×1, WorldOpenFlows×1 |
| clientsort | both | 6 | 0 | 15 | AbstractContainerScreen×2, Screen×2, CreativeModeInventoryScreen×1, CreativeModeInventoryScreen$SlotWrapper×1 |
| dungeons_delight | both | 6 | 1 | 29 | Gui×2, AbstractContainerScreen×1, EffectRenderingInventoryScreen×1, HumanoidModel×1, LevelRenderer×1 |
| dynamic-fps | client | 6 | 0 | 10 | DebugScreenOverlay×1, GameRenderer×1, Gui×1, LoadingOverlay×1, ToastComponent×1, Window×1 |
## Per-frame event subscribers
| mod | side | frame events | tick events |
| --- | --- | --- | --- |
| puzzles-lib | both | ComputeFovModifierEvent, CustomizeGuiOverlayEvent, RenderBlockScreenEffectEvent, RenderFrameEvent, RenderGuiEvent, RenderGuiLayerEvent, RenderHandEvent, RenderHighlightEvent, RenderLevelStageEvent, RenderLivingEvent, RenderNameTagEvent, RenderPlayerEvent, RenderTooltipEvent, ViewportEvent | ClientTickEvent, EntityTickEvent, LevelTickEvent, PlayerTickEvent |
| create | both | CustomizeGuiOverlayEvent, RenderArmEvent, RenderFrameEvent, RenderGuiLayerEvent, RenderHandEvent, RenderHighlightEvent, RenderLevelStageEvent, RenderPlayerEvent, RenderTooltipEvent, ViewportEvent | ClientTickEvent, EntityTickEvent, LevelTickEvent, PlayerTickEvent |
| alexs-caves-(unofficial-port) | both | ComputeFovModifierEvent, CustomizeGuiOverlayEvent, RenderBlockScreenEffectEvent, RenderGuiLayerEvent, RenderHandEvent, RenderLevelStageEvent, RenderLivingEvent, RenderNameTagEvent, ViewportEvent | ClientTickEvent, EntityTickEvent, PlayerTickEvent |
| mowzies-mobs | both | ComputeFovModifierEvent, CustomizeGuiOverlayEvent, RenderFrameEvent, RenderGuiLayerEvent, RenderHandEvent, RenderLevelStageEvent, RenderLivingEvent, RenderNameTagEvent, ViewportEvent | EntityTickEvent, LevelTickEvent, PlayerTickEvent |
| the-twilight-forest | both | ComputeFovModifierEvent, CustomizeGuiOverlayEvent, RenderArmEvent, RenderFrameEvent, RenderGuiLayerEvent, RenderHighlightEvent, RenderLevelStageEvent, RenderLivingEvent, ViewportEvent | ClientTickEvent, EntityTickEvent, PlayerTickEvent |
| ars-nouveau | both | RenderFrameEvent, RenderGuiLayerEvent, RenderHandEvent, RenderHighlightEvent, RenderLevelStageEvent, RenderTooltipEvent | ClientTickEvent, LevelTickEvent, PlayerTickEvent |
| balm | both | ComputeFovModifierEvent, GuiDrawEvent, RenderGuiEvent, RenderGuiLayerEvent, RenderHandEvent, RenderHighlightEvent | ClientTickEvent, ClientTickHandler, EntityTickEvent, LevelTickEvent, PlayerTickEvent |
| alexs-mobs(1.21.1) | both | RenderGuiLayerEvent, RenderHandEvent, RenderLevelStageEvent, RenderLivingEvent, ViewportEvent | ClientTickEvent, EntityTickEvent |
| xaeros-minimap | both | CustomizeGuiOverlayEvent, RenderFrameEvent, RenderGuiEvent, RenderGuiLayerEvent, RenderLevelStageEvent | ClientTickEvent, PlayerTickEvent |
| architectury-api | both | ClientGuiEvent, CustomizeGuiOverlayEvent, RenderGuiEvent, RenderTooltipEvent | ClientTickEvent, LevelTickEvent, PlayerTickEvent |
| doggy-talents-next | both | RenderArmEvent, RenderLevelStageEvent, RenderPlayerEvent, ViewportEvent | ClientTickEvent |
| modernfix | both | CustomizeGuiOverlayEvent, RenderFrameEvent, RenderLivingEvent, RenderPlayerEvent | ClientTickEvent |
| adorable-hamster-pets | both | ClientGuiEvent, RenderLevelStageEvent, WorldRenderEvents | ClientTickEvent |
| apotheosis | both | RenderHighlightEvent, RenderLevelStageEvent, RenderTooltipEvent | ClientTickEvent, EntityTickEvent |
| citadel-(1.21.1-port) | both | RenderLevelStageEvent, RenderPlayerEvent, RenderTooltipEvent | ClientTickEvent, EntityTickEvent |
| lodestonelib | both | RenderFrameEvent, RenderLevelStageEvent, ViewportEvent | ClientTickEvent, LevelTickEvent |
| slice-and-dice | both | RenderGuiLayerEvent, RenderLevelStageEvent, RenderTooltipEvent | ClientTickEvent, LevelTickEvent |
| alltheleaks | both | CustomizeGuiOverlayEvent, RenderLivingEvent | ClientTickEvent, EntityTickEvent, LevelTickEvent |
| appleskin | both | CustomizeGuiOverlayEvent, RenderTooltipEvent | ClientTickEvent, EntityTickEvent |
| arsdelight | both | RenderLevelStageEvent, RenderLivingEvent | ClientTickEvent, PlayerTickEvent |
| artifacts | both | RenderArmEvent, RenderLivingEvent | ClientTickEvent, EntityTickEvent |
| barbeques-delight-forge | both | RenderLevelStageEvent, RenderLivingEvent | ClientTickEvent, PlayerTickEvent |
| create-aeronautics | both | RenderGuiEvent, RenderLevelStageEvent | ClientTickEvent, LevelTickEvent |
| creativecore | both | RenderFrameEvent, RenderGuiEvent | ClientTickEvent, LevelTickEvent |
| distanthorizons | both | RenderLevelStageEvent, WorldRenderEvents | |
| exposure | both | RenderGuiEvent, RenderLevelStageEvent | |
| extradelight | both | ComputeFovModifierEvent, ViewportEvent | |
| fruits-delight | both | RenderLevelStageEvent, RenderLivingEvent | ClientTickEvent, PlayerTickEvent |
| jade | both | CustomizeGuiOverlayEvent, RenderGuiEvent | ClientTickEvent |
| modonomicon | both | RenderFrameEvent, RenderLevelStageEvent | ClientTickEvent |
| patchouli | both | RenderFrameEvent, RenderTooltipEvent | ClientTickEvent |
| playerrevive | both | RenderFrameEvent, RenderGuiLayerEvent | ClientTickEvent, PlayerTickEvent |
| sable | both | RenderLevelStageEvent, ViewportEvent | ClientTickEvent |
| sophisticated-core | both | RenderLevelStageEvent, RenderTooltipEvent | ClientTickEvent, LevelTickEvent |
| twilight-delight | both | RenderLevelStageEvent, RenderLivingEvent | ClientTickEvent, PlayerTickEvent |
| akashic-tome | both | RenderGuiLayerEvent | |
| comforts | both | RenderPlayerEvent | ClientTickEvent |
| dynamic-fps | client | RenderGuiEvent | ClientTickEvent |
| ferrite-core | both | RenderLevelStageEvent | |
| ftb-library-forge | both | ClientGuiEvent | ClientTickEvent |
| fwa | client | RenderLevelStageEvent | |
| iceberg | both | RenderTooltipEvent | |
| iris | client | RenderLevelStageEvent | |
| jei | both | RenderTooltipEvent | ClientTickEvent |
| keybind-atlas | client | RenderGuiEvent | |
| legendary-tooltips | client | RenderTooltipEvent | |
| minopp | both | ComputeFovModifierEvent | ClientTickEvent |
| mru | both | HudRenderCallback | |
| music-disc-maker | both | RenderLevelStageEvent | ClientTickEvent |
| quick-skin | both | ClientGuiEvent | ClientTickEvent |
| rechiseled | both | RenderLevelStageEvent | |
| resourceful-lib | both | RenderHighlightEvent | |
| shatterbyte-lib | both | RenderLevelStageEvent | ClientTickEvent |
| sophisticated-backpacks | both | RenderLevelStageEvent | ClientTickEvent, EntityTickEvent, LevelTickEvent |
| sophisticated-storage | both | RenderHighlightEvent | ClientTickEvent, LevelTickEvent |
| subtle-effects | both | RenderLevelStageEvent | ClientTickEvent |
| xaeros-world-map | both | RenderFrameEvent | ClientTickEvent, PlayerTickEvent |
| zume | client | ViewportEvent | |
## Block/item atlas pressure
| mod | side | sprites (16² eq.) | files | animated | % of pack | models | blockstates |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
| handcrafted | both | 16,359 | 976 | 0 | 27.2 | 12394 | 267 |
| chipped | both | 12,754 | 12299 | 30 | 21.2 | 16832 | 6981 |
| rechiseled | both | 5,680 | 657 | 0 | 9.4 | 13855 | 3627 |
| create | both | 4,012 | 1083 | 4 | 6.7 | 2684 | 643 |
| ars-nouveau | both | 3,778 | 547 | 29 | 6.3 | 870 | 200 |
| too-many-bows | both | 3,322 | 166 | 0 | 5.5 | 150 | 0 |
| extradelight | both | 1,847 | 1847 | 0 | 3.1 | 3759 | 822 |
| adorable-hamster-pets | both | 1,051 | 121 | 0 | 1.7 | 82 | 10 |
| doggy-talents-next | both | 981 | 192 | 0 | 1.6 | 164 | 6 |
| the-twilight-forest | both | 889 | 827 | 44 | 1.5 | 3107 | 523 |
| create-aeronautics | both | 740 | 261 | 1 | 1.2 | 605 | 141 |
| alexs-caves-(unofficial-port) | both | 717 | 711 | 27 | 1.2 | 1284 | 354 |
| macaws-doors | both | 702 | 702 | 0 | 1.2 | 1394 | 246 |
| dungeons_delight | both | 450 | 327 | 17 | 0.7 | 416 | 65 |
| wings-of-fire | both | 417 | 57 | 2 | 0.7 | 53 | 7 |
| alexs-mobs(1.21.1) | both | 410 | 254 | 13 | 0.7 | 438 | 37 |
| tiny-dragons | both | 378 | 9 | 1 | 0.6 | 25 | 3 |
| rustic-delight | both | 363 | 348 | 0 | 0.6 | 423 | 55 |
| sophisticated-storage | both | 349 | 271 | 0 | 0.6 | 299 | 59 |
| farmers-delight | both | 348 | 348 | 2 | 0.6 | 369 | 132 |
| immersive-paintings | both | 341 | 89 | 0 | 0.6 | 4 | 0 |
| fruits-delight | both | 254 | 254 | 0 | 0.4 | 649 | 200 |
| items-displayed-forge | both | 243 | 129 | 0 | 0.4 | 315 | 109 |
| twilight-delight | both | 233 | 221 | 11 | 0.4 | 356 | 95 |
| frights-delight | both | 217 | 172 | 69 | 0.4 | 271 | 43 |
| mowzies-mobs | both | 174 | 70 | 1 | 0.3 | 96 | 9 |
| veggies-delight | both | 170 | 170 | 0 | 0.3 | 178 | 29 |
| exposure | both | 166 | 52 | 1 | 0.3 | 54 | 8 |
| macaws-windows | both | 164 | 164 | 0 | 0.3 | 1887 | 328 |
| arsdelight | both | 159 | 142 | 0 | 0.3 | 165 | 26 |
## Animated atlas textures (uploaded every tick unless culled by Sodium)
| mod | side | animated |
| --- | --- | ---: |
| frights-delight | both | 69 |
| the-twilight-forest | both | 44 |
| chipped | both | 30 |
| ars-nouveau | both | 29 |
| alexs-caves-(unofficial-port) | both | 27 |
| macaws-lights-and-lamps | both | 18 |
| dungeons_delight | both | 17 |
| alexs-mobs(1.21.1) | both | 13 |
| apotheosis | both | 12 |
| twilight-delight | both | 11 |
| moonlight | both | 10 |
| create-enchantment-industry | both | 7 |
| create | both | 4 |
| apothic-enchanting | both | 3 |
| ends-delight | both | 2 |
| farmers-delight | both | 2 |
| macaws-roofs | both | 2 |
| slice-and-dice | both | 2 |
| sophisticated-core | both | 2 |
| wings-of-fire | both | 2 |
## Entity content (renderer + animation load)
| mod | side | entity types | entity tex Mpx | geckolib files |
| --- | --- | ---: | ---: | ---: |
| alexs-mobs(1.21.1) | both | 140 | 7.17 | 0 |
| alexs-caves-(unofficial-port) | both | 92 | 11.45 | 0 |
| the-twilight-forest | both | 80 | 0.95 | 0 |
| ars-nouveau | both | 51 | 0.37 | 132 |
| aquaculture | both | 40 | 0.05 | 0 |
| mowzies-mobs | both | 30 | 0.38 | 38 |
| critters-and-companions | both | 17 | 0.47 | 60 |
| items-displayed-forge | both | 17 | 0.07 | 0 |
| icys-better-horses | both | 16 | 5.37 | 14 |
| fowlplay | both | 14 | 0.09 | 0 |
| friends-and-foes-forge | both | 14 | 0.16 | 0 |
| fishermans-haven | both | 11 | 0.00 | 0 |
| tiny-dragons | both | 10 | 0.00 | 9 |
| create | both | 9 | 0.02 | 0 |
| adorable-hamster-pets | both | 7 | 0.26 | 4 |
| dungeons_delight | both | 7 | 0.05 | 0 |
| create-aeronautics | both | 6 | 0.01 | 0 |
| pet-cemetery | both | 6 | 0.01 | 0 |
| wings-of-fire | both | 6 | 0.00 | 2 |
| exposure | both | 5 | 0.00 | 0 |
| immersive-paintings | both | 4 | 0.00 | 0 |
| blueprint | both | 3 | 0.00 | 0 |
| doggy-talents-next | both | 3 | 10.09 | 0 |
| charta | both | 2 | 0.03 | 0 |
| handcrafted | both | 2 | 0.00 | 0 |
| jade | both | 2 | 0.00 | 0 |
| lootr | both | 2 | 0.00 | 0 |
| too-many-bows | both | 2 | 0.02 | 0 |
| artifacts | both | 1 | 0.10 | 0 |
| farmers-delight | both | 1 | 0.07 | 0 |
## Shader assets
| mod | side | shader files | post effects | core overrides |
| --- | --- | ---: | ---: | ---: |
| lodestonelib | both | 59 | 2 | 0 |
| alexs-caves-(unofficial-port) | both | 38 | 7 | 0 |
| charta | both | 31 | 1 | 0 |
| xaeros-minimap | both | 31 | 0 | 31 |
| xaeros-world-map | both | 31 | 0 | 31 |
| distanthorizons | both | 23 | 0 | 0 |
| exposure | both | 21 | 19 | 0 |
| what-are-they-up-to | both | 16 | 0 | 0 |
| ars-nouveau | both | 9 | 0 | 0 |
| sodium | client | 9 | 0 | 3 |
| blueprint | both | 6 | 0 | 0 |
| moonlight | both | 4 | 0 | 0 |
| playerrevive | both | 4 | 1 | 0 |
| the-twilight-forest | both | 4 | 0 | 0 |
| apotheosis | both | 3 | 0 | 0 |
| citadel-(1.21.1-port) | both | 3 | 1 | 0 |
| create | both | 3 | 0 | 0 |
| sable | both | 3 | 0 | 3 |
| shatterbyte-lib | both | 3 | 1 | 0 |
| ssrd | both | 1 | 0 | 0 |
## Sound payload
| mod | side | ogg MB |
| --- | --- | ---: |
| ambientsounds | client | 85.5 |
| alexs-caves-(unofficial-port) | both | 68.8 |
| mowzies-mobs | both | 33.2 |
| create-aeronautics | both | 26.2 |
| alexs-mobs(1.21.1) | both | 20.3 |
| the-twilight-forest | both | 17.3 |
| adorable-hamster-pets | both | 15.2 |
| sound | client | 7.9 |
| playerrevive | both | 5.1 |
| dungeons_delight | both | 5.1 |
| apothic-enchanting | both | 4.8 |
| doggy-talents-next | both | 4.3 |
| ars-nouveau | both | 4.3 |
| friends-and-foes-forge | both | 3.9 |
| apotheosis | both | 3.9 |
## Jar size
| mod | side | MB |
| --- | --- | ---: |
| ambientsounds | client | 84.8 |
| alexs-caves-(unofficial-port) | both | 74.9 |
| mowzies-mobs | both | 36.0 |
| music-disc-maker | both | 35.0 |
| create-aeronautics | both | 33.1 |
| distanthorizons | both | 28.2 |
| the-twilight-forest | both | 27.2 |
| alexs-mobs(1.21.1) | both | 26.6 |
| adorable-hamster-pets | both | 20.3 |
| ars-nouveau | both | 20.1 |
| create | both | 19.1 |
| chipped | both | 15.0 |
| sable | both | 13.0 |
| when-dungeons-arise | both | 11.8 |
| rechiseled | both | 11.5 |
| doggy-talents-next | both | 11.3 |
| extradelight | both | 11.0 |
| sound | client | 8.7 |
| controlify | client | 8.0 |
| fragmentum | both | 7.8 |
## Resource packs / shader packs
- **better-leaves** (both): atlas sprites 2,469, entity tex 0.00 Mpx, models 3223, blockstates 871, CEM/EMF files 0, shader files 0
- **complementary-reimagined** (both): atlas sprites 0, entity tex 0.00 Mpx, models 0, blockstates 0, CEM/EMF files 0, shader files 0
- **fresh-animations** (both): atlas sprites 0, entity tex 0.65 Mpx, models 0, blockstates 0, CEM/EMF files 240, shader files 0
- **fresh-animations-extensions** (both): atlas sprites 0, entity tex 0.48 Mpx, models 0, blockstates 0, CEM/EMF files 218, shader files 0
- **makeup-ultra-fast-shaders** (both): atlas sprites 0, entity tex 0.00 Mpx, models 0, blockstates 0, CEM/EMF files 0, shader files 0
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,981 @@
_version = 4
[server]
#
# Defines the distance the player will receive updates around.
realTimeUpdateDistanceRadiusInChunks = 256
#
# Prefix of the level keys sent to the clients.
# If the mod is running behind a proxy, each backend should use a unique value.
# If this value is empty, level key will be based on the server's seed hash.
levelKeyPrefix = ""
#
# Defines the distance allowed to be synchronized around the player.
# Should be the same or larger than maxGenerationRequestDistance in most cases.
maxSyncOnLoadRequestDistance = 4096
#
# If true, clients will receive updated LODs when joining or loading new LODs.
synchronizeOnLoad = true
#
# Custom server key used which can be used to always reuse the same LOD data folder,
# for cases when the server doesn't have a static IP for some reason.
# If this value is empty, the client itself decides which folder name to use.
# Requires rejoining the server to apply after changing.
serverKey = ""
#
# Defines the distance allowed to generate around the player.
maxGenerationRequestDistance = 4096
#
# Makes the server send level keys for each world.
# Disable this if you use alternative ways to send level keys.
sendLevelKeys = true
#
# DO NOT CHANGE UNLESS YOU KNOW WHAT YOU'RE DOING.
# Autogenerated ID used to prevent multiple independent servers from accidentally
# writing over each other's LODs when the same serverKey is set on both.
serverId = 1518119460
#
# How many LOD generation requests per second should a client send?
# Also limits the number of client requests allowed to stay in the server's queue.
generationRequestRateLimit = 20
#
# How many LOD sync requests per second should a client send?
# Also limits the amount of player's requests allowed to stay in the server's queue.
syncOnLoadRateLimit = 50
#
# If true, clients will receive real-time LOD updates for chunks outside the client's render distance.
enableRealTimeUpdates = true
#
# Maximum global speed for uploading LODs to the clients, in KB/s.
# Value of 0 disables the limit.
globalBandwidthLimit = 0
#
# Maximum per-player speed for uploading LODs to the clients, in KB/s.
# Value of 0 disables the limit.
playerBandwidthLimit = 500
#
# Enables adaptive transfer speed based on client performance.
# If true, DH will automatically adjust transfer rate to minimize connection lag.
# If false, transfer speed will remain fixed.
enableAdaptiveTransferSpeed = false
[common.lodBuilding]
#
# How should block data be compressed when creating LOD data?
# This setting will only affect new or updated LOD data,
# any data already generated when this setting is changed will be
# unaffected until it is modified or re-loaded.
#
# MERGE_SAME_BLOCKS
# Every block/biome change is recorded in the database.
# This is what DH 2.0 and 2.0.1 all used by default and will store a lot of data.
# Expected Compression Ratio: 1.0
#
# VISUALLY_EQUAL
# Only visible block/biome changes are recorded in the database.
# Hidden blocks (IE ores) are ignored.
# Expected Compression Ratio: 0.7
worldCompression = "VISUALLY_EQUAL"
#
# Requires a level restart to apply.
#
# Setting this to a less strict mode increases DB read/write
# speed at the cost of potential data loss/corruption if
# the computer looses power
# (unexpected program shutdown should be safe for all options).
#
# FULL
# ACID - data will never be lost or corrupted
# Slow
# At max throughput: ~2k saves per sec, ~70% disk usage
#
# NORMAL
# May not be durable - some data may be lost on power loss, but DB should be safe from corruption
# Fast
# At max throughput: ~14k saves per sec, ~40% disk usage
#
# OFF
# Not consistent - database may be corrupted during power loss
# Very Fast
# At max throughput: ~16k saves per sec, ~6% disk usage
databaseSyncMode = "NORMAL"
dataCompression = "Z_STD_BLOCK"
#
# Enabling this will drastically speed up chunk processing time
# if you are CPU limited.
# Disabling it will drastically speed up chunk processing time
# if you are disk speed/IO limited.
#
# Normally DH will attempt to skip creating LODs for chunks it's already seen
# and that haven't changed.
#
# Disabling this option can also fix a rare bug where
# modified chunks are skipped due to being too similar to
# their saved hash.
disableUnchangedChunkCheck = false
[common.multiThreading]
#
# How many threads should be used by Distant Horizons?
numberOfThreads = 2
#
# A value between 1.0 and 0.0 that represents the percentage
# of time each thread can run before going idle.
#
# This can be used to reduce CPU usage if the thread count
# is already set to 1 for the given option, or more finely
# tune CPU performance.
threadRunTimeRatio = "1.0"
#
# What Java thread priority should DH's primary thread pools run with?
#
# You probably don't need to change this unless you are also
# running C2ME and are seeing thread starvation in either C2ME or DH.
threadPriority = 5
[common.logging]
#
# If enabled, the mod will log information about the renderer setup, cleanup, and any issues it may encounter.
# This can be useful for debugging.
logRendererEventToFile = "INFO"
#
# If enabled, the mod will log information about the world generation process.
# This can be useful for debugging.
logWorldGenEventToFile = "INFO"
#
# If enabled, the mod will log information about the renderer OpenGL process.
# This can be useful for debugging.
logRendererGLEventToChat = "ERROR"
#
#
globalChatMaxLevel = "ERROR"
#
# If enabled, the mod will log information about the world generation process.
# This can be useful for debugging.
logWorldGenChunkLoadEventToFile = "INFO"
#
# If enabled, the mod will log information about the renderer OpenGL process.
# This can be useful for debugging.
logRendererGLEventToFile = "INFO"
#
# If enabled, the mod will log information about network operations.
# This can be useful for debugging.
logNetworkEventToFile = "INFO"
#
# If enabled, config changes sent by the server will be logged.
logConnectionConfigChangesToFile = "WARN"
#
#
globalFileMaxLevel = "INFO"
[common.logging.warning]
#
# If enabled, a message will be displayed in chat if explicit garbage collection
# is disabled.
# This is known to cause out-of-memory issues
# and is better solved with a concurrent garbage collector.
showExplicitGcDisabledWarning = false
#
# If enabled, a chat message will be displayed when DH has too many chunks
# queued for updating.
showSlowWorldGenSettingWarnings = true
#
# If enabled, a chat message will be displayed when DH has too many chunks
# queued for updating.
showUpdateQueueOverloadedChatWarning = false
#
# If enabled, a message will be logged if explicit garbage collection
# is disabled.
# This is known to cause out-of-memory issues
# and is better solved with a concurrent garbage collector.
logExplicitGcDisabledWarning = false
#
# If enabled, a chat message will be displayed if Java doesn't have enough
# memory allocated to run DH well.
showLowMemoryWarningOnStartup = true
#
# If enabled, a chat message will be displayed when a replay is started
# giving some basic information about how DH will function.
showReplayWarningOnStartup = true
#
# If enabled, a chat message will be displayed when a potentially problematic
# mod is installed alongside DH.
showModCompatibilityWarningsOnStartup = true
#
# If enabled, a chat message will be displayed if vanilla MC's
# render distance is higher than the recommended amount.
showHighVanillaRenderDistanceWarning = true
#
# If enabled, a chat message will be displayed if DH detects
# that any pooled objects have been garbage collected.
showPoolInsufficientMemoryWarning = true
#
# If enabled, a message will be logged if the garbage
# collector Java is currently using is known
# to cause frame stuttering and/or other issues.
logGarbageCollectorWarning = true
#
# If enabled, a chat message will be displayed in chat if the garbage
# collector Java is currently using is known
# to cause frame stuttering and/or other issues.
showGarbageCollectorWarning = true
[common.worldGenerator]
#
# How should distant generator progress be displayed?
#
# OVERLAY: may be the same as CHAT for some Minecraft versions
# CHAT
# LOG
# DISABLED
showGenerationProgress = "DISABLED"
#
# If the SURFACE_THEN_CHUNKS generator plan is
# in use, this percentage can be defined to reduce how far the
# second high-quality generation pass will go
# as a percentage of your total render distance:
# -1.0 = Infinity
# 0.0 = 0%
# 0.5 = 50%
# 1.0 = 100%
# 2.0 = 200%
#
# Does nothing on a dedicated server.
surfaceRegenMaxDistancePercent = "-1.0"
#
# The max radius in chunks around the central point where world generation is allowed.
# If this value is set to 0, generation bounds are disabled and the render distance will be used.
#
# This should only be set if you have a pre-generated world that has a very limited size.
# Setting this on a normal MC world will prevent the world generator from filling
# out your render distance.
generationMaxChunkRadius = 0
#
# If the Minecraft server thread is taking too long
# Distant Horizons will pause world gen to reduce server stuttering/lag.
limitIfServerUnhealthy = true
#
# How often should the distant generator progress be displayed?
generationProgressDisplayIntervalInSeconds = 2
#
# When logging generation progress also include the rate at which chunks
# are being generated.
# This can be useful for troubleshooting performance.
generationProgressIncludeChunksPerSecond = true
#
# How detailed should LODs be generated outside the vanilla render distance?
#
# PRE_EXISTING_ONLY
# Only create LOD data for already generated chunks.
#
# FEATURES
# Generate everything except structures.
# Note: This may cause world generator bugs or instability when paired with certain world generator mods.
#
# INTERNAL_SERVER
# Ask the local server to generate/load each chunk.
# This is the most compatible and will generate structures correctly,
# but may cause server/simulation lag.
# Note: unlike other modes this option DOES save generated chunks to
# Minecraft's region files.
chunkGeneratorMode = "FEATURES"
#
# Defines how LODs will be generated
# outside the vanilla render distance.
# In multiplayer, this may be overridden by the server.
#
# SURFACE_THEN_CHUNKS
# The rough surface will be generated first
# then chunks will be generated to fill in
# missing features (ie trees and villages).
#
# Recommended for modded or vanilla worlds.
#
# SURFACE_ONLY
# Only the rough surface will be generated.
# Chunk features like trees and villages
# won't be generated.
#
# Recommended for computers that don't have
# enough CPU power or RAM space to handle
# the chunk generator.
#
# CHUNKS_ONLY
# Slower than either surface options,
# but provides more accurate terrain.
#
# Recommended for custom worlds
# where the surface generator would
# generate incorrect terrain.
#
# DISABLED
# No distant terrain will be generated.
generatorPlan = "SURFACE_THEN_CHUNKS"
#
# If true and the player/camera is moving quickly
# Distant Horizons will pause world gen to improve loading speed
# and reduce CPU load.
pauseIfMovingQuickly = true
#
# For how many seconds should instructions for disabling the distant generator progress be displayed?
# Setting this to 0 hides the instructional message so the world gen progress is shown immediately when it starts.
generationProgressDisableMessageDisplayTimeInSeconds = 20
#
# The center Z chunk position that the world gen max radius is centered around.
generationCenterChunkZ = 0
#
# The center X chunk position that the world gen max radius is centered around.
generationCenterChunkX = 0
[client]
#
# Should Distant Horizon's config button appear in Minecraft's options screen next to the fov slider?
showDhOptionsButtonInMinecraftUi = true
[client.advanced.autoUpdater]
#
# If DH should use the nightly (provided by Gitlab), or stable (provided by Modrinth) build.
# If [AUTO] is selected DH will update to new stable releases if the current jar is a stable jar
# and will update to new nightly builds if the current jar is a nightly jar (IE the version number ends in '-dev').
updateBranch = "AUTO"
#
# Automatically check for updates on game launch?
enableAutoUpdater = true
#
# Should Distant Horizons silently, automatically download and install new versions?
# This setting is force disabled on dedicated servers for stability reasons.
enableSilentUpdates = false
[client.advanced.debugging]
#
# If enabled this will disable (most) vanilla Minecraft rendering.
#
# NOTE: Do not report any issues when this mode is on!
# This setting is only for fun and debugging.
# Mod compatibility is not guaranteed.
lodOnlyMode = false
#
# What renderer is active?
#
# DEFAULT: Default lod renderer
# DEBUG_TRIANGLE: Debug testing renderer
# DISABLED: Disable rendering
rendererMode = "DEFAULT"
#
# If true overlapping quads will be rendered as bright red for easy identification.
# If false the quads will be rendered normally.
showOverlappingQuadErrors = false
#
# If true OpenGL Buffer garbage collection will be logged
# this also includes the number of live buffers.
logBufferGarbageCollection = false
#
# Should specialized colors/rendering modes be used?
#
# OFF: LODs will be drawn with their normal colors.
# SHOW_DETAIL: LODs' color will be based on their detail level.
# SHOW_BLOCK_MATERIAL: LODs' color will be based on their material.
# SHOW_OVERLAPPING_QUADS: LODs will be drawn with total white, but overlapping quads will be drawn with red.
debugRenderingColors = "OFF"
#
# Stops vertex colors from being passed.
# Useful for debugging shaders
enableWhiteWorld = false
#
# If enabled the LODs will render as wireframe.
renderWireframe = false
#
# If true several keys can be used to toggle debug states.
# F6 - enable/disable LOD rendering
# F7 - enable/disable LOD only rendering
# F8 - cycle through the different debug rendering modes
enableDebugKeybindings = false
[client.advanced.debugging.debugWireframe]
#
# Render LOD section status?
showRenderSectionStatus = false
#
# Render queued network sync on load tasks?
showNetworkSyncOnLoadQueue = false
#
# Render full data update/lock status?
showFullDataUpdateStatus = false
#
# Render queued world gen tasks?
showWorldGenQueue = false
#
# Render Quad Tree Rendering status?
showQuadTreeRenderStatus = false
#
# If enabled, various wireframes for debugging internal functions will be drawn.
#
# NOTE: There WILL be performance hit!
# Additionally, only stuff that's loaded after you enable this
# will render their debug wireframes.
enableRendering = false
[client.advanced.debugging.openGl]
#
# Defines how OpenGL errors are handled.
# Requires rebooting Minecraft to change.
# Will catch OpenGL errors thrown by other mods.
overrideVanillaGLLogger = true
#
# Defines how OpenGL errors are handled.
# May incorrectly catch OpenGL errors thrown by other mods.
#
# IGNORE: Do nothing.
# LOG: write an error to the log.
# LOG_THROW: write to the log and throw an exception.
# Warning: this should only be enabled when debugging the LOD renderer
# as it may break Minecraft's renderer when an exception is thrown.
glErrorHandlingMode = "IGNORE"
#
# If true each Open GL error will only be logged once.
# Enabling this may cause some error logs to be missed.
# Does nothing if overrideVanillaGLLogger is set to false.
#
# Generally this can be kept as 'true' to prevent log spam.
# However, Please set this to 'false' if a developer needs your log to debug a GL issue.
onlyLogGlErrorsOnce = true
[client.advanced.debugging.exampleConfigScreen]
shortTest = "69"
mapTest = "{}"
byteTest = "8"
longTest = "42069"
listTest = ["option 1", "option 2", "option 3"]
boolTest = false
doubleTest = "420.69"
floatTest = "0.42069"
linkableTest = 420
intTest = 69420
stringTest = "Test input box"
[client.advanced.debugging.f3Screen]
#
# Shows info about the render thread tasks.
showRenderThreadTasks = false
#
# Shows how many chunks are queued for processing and the max count that can be queued.
showQueuedChunkUpdateCount = true
#
# Shows the memory use and array counts for each DH object pool.
showSeparatedObjectPools = false
#
# Shows the player's LOD position.
showPlayerPos = true
#
# Shows the combined memory use and array counts for all DH pooled objects.
showCombinedObjectPools = false
#
# Defines what internal detail level the player position will be shown as.
# Internal detail level means: 6 = 1x1 block, 7 = 2x2 blocks, etc.
playerPosSectionDetailLevel = 6
#
# Only show levels that DH is actively rendering.
onlyShowRenderingLevels = true
#
# Shows info about each thread pool.
showThreadPools = true
#
# Shows what levels are loaded and world gen/rendering info about those levels.
showLevelStatus = true
[client.advanced.debugging.positionFinderDebugging]
positionFinderEnable = false
positionFinderMinBlockY = -64
positionFinderZPos = 0
positionFinderXPos = 0
positionFinderMaxBlockY = 125
positionFinderDetailLevel = 6
positionFinderMarginPercent = "0.0"
[client.advanced.graphics]
#
# Enabling Anti-Aliasing smooths edges and reduces flickering when moving.
enableAntiAliasing = true
#
# Enable Screen Space Ambient Occlusion
enableSsao = true
#
# If true some vanilla graphics settings will be automatically changed
# during DH setup to provide a better experience.
#
# IE disabling vanilla clouds (which render on top of DH LODs),
# and chunk fading (DH already fades MC chunks)
overrideVanillaGraphicsSettings = true
[client.advanced.graphics.culling]
#
# Determines how far from the camera Distant Horizons will start rendering.
# Measured as a percentage of the vanilla render distance.
#
# -1 = auto, overdraw will change based on the vanilla render distance.
#
# Higher values will prevent LODs from rendering behind vanilla blocks at a higher distance,
# but may cause holes in the world.
# Holes are most likely to appear when flying through unloaded terrain.
#
# Increasing the vanilla render distance increases the effectiveness of this setting.
overdrawPrevention = "-1.0"
#
# Identical to the other frustum culling option
# only used when a shader mod is present using the DH API
# and the shadow pass is being rendered.
#
# Disable this if shadows render incorrectly.
disableShadowPassFrustumCulling = false
#
# A comma separated list of block resource locations that will be replaced by water
# if they're visible on the water's surface.
waterSubSurfaceBlockReplacementCsv = "minecraft:kelp,minecraft:tall_seagrass,minecraft:seagrass"
#
# A comma separated list of block resource locations that won't be rendered by DH.
# Air is always included in this list.
#
# Note:
# If you see gaps, or holes you may have to change
# worldCompression to [MERGE_SAME_BLOCKS] and re-generate the LODs.
ignoredRenderBlockCsv = "minecraft:barrier,minecraft:structure_void,minecraft:light,minecraft:tripwire,minecraft:brown_mushroom"
#
# A comma separated list of block resource locations that will be removed
# when on top of water.
waterSurfaceBlockReplacementCsv = "minecraft:lily_pad"
#
# Defines what blocks should be rendered as LODs.
#
# NONE: Include all blocks in the LODs
# NON_COLLIDING: Only render solid blocks in the LODs (tall grass, torches, etc. will be ignored)
blocksToIgnore = "NON_COLLIDING"
#
# If true LODs outside the player's camera
# aren't drawn, increasing GPU performance.
#
# If false all LODs are drawn, even those behind
# the player's camera, decreasing GPU performance.
#
# Disable this if you see LODs disappearing at the corners of your vision.
disableFrustumCulling = false
#
# If false all beacons near the camera won't be drawn to prevent vanilla overdraw.
# If true all beacons will be rendered.
#
# Generally this should be left as true. It's main purpose is for debugging
# beacon updating/rendering.
disableBeaconDistanceCulling = true
#
# Should the blocks underneath avoided blocks gain the color of the avoided block?
#
# True: a red flower will tint the grass below it red.
# False: skipped blocks will not change color of surface below them.
tintWithAvoidedBlocks = true
#
# If enabled caves won't be rendered.
#
# Note: for some world types this can cause
# overhangs or walls for floating objects.
# Tweaking the caveCullingHeight, can resolve some
# of those issues.
enableCaveCulling = true
#
# At what Y value should cave culling start?
# Lower this value if you get walls for areas with 0 light.
caveCullingHeight = 60
#
# A comma separated list of block resource locations that shouldn't be rendered
# if they are in a 0 sky light underground area.
# Air is always included in this list.
#
# Defaults to an empty list since most cave blocks will be automatically ignored due to being:
# transparent, non-solid, or liquids, but new blocks can be added here if needed.
ignoredRenderCaveBlockCsv = ""
#
# If set to true the overdraw prevention radius will get closer
# to the camera when flying/moving quickly.
#
# This helps reduce issues where Minecraft can't load or
# generate chunks fast enough to keep up with DH.
reduceOverdrawWithFastMovement = true
[client.advanced.graphics.texture]
#
# A comma separated list of block resource locations
# that DH will render their sides using the bottom texture.
# Partial matches/incomplete resource locations will also match.
#
# Example: "minecraft:grass_block,nylium"
#
# Changes require a restart.
blocksDontUseSideTextureCsv = "grass_block,mycelium,nylium,dirt_path,grass"
#
# The highest detail level number that can render with block textures.
# At higher detail levels each LOD covers multiple blocks,
# which can essentially make textures smaller than a pixel and therefore invisible.
#
# Larger numbers texture more distant LODs
# but increase memory usage.
maxTexturedLodDetailLevel = 2
#
# If true nearby and zoomed-in LODs will render with their block's
# actual texture instead of a single flat color.
#
# Only applies to high detail LODs where the texture is actually visible,
# distant LODs always use flat colors.
# Some shader packs nay not have an affect.
enableTexturedLods = true
#
# A comma separated list of block resource locations
# that DH won't render textures on.
# Partial matches/incomplete resource locations will also match.
#
# Example: "minecraft:grass_block,nylium"
#
# Changes require a restart.
blocksDontRenderTextureCsv = "minecraft:bamboo"
#
# A comma separated list of tag names
# that DH will render their sides using the bottom texture.
#
# Example: "grass_blocks,nylium"
#
# Changes require a restart.
blockTagsDontUseSideTextureCsv = "grass_blocks,nylium"
#
# A comma separated list of block resource locations
# that DH will use rasterization to determine
# the face textures.
#
# Can be used to fix issues with mods/blocks where
# a side uses just part of a modeled block's texture.
#
# Example: "minecraft:beacon"
#
# Changes require a restart.
blocksAlwaysRasterizeTextureCsv = "minecraft:beacon"
[client.advanced.graphics.noiseTexture]
#
# Should a noise texture be applied to LODs?
#
# This is done to simulate textures and make the LODs appear more detailed.
enableNoiseTexture = true
#
# Defines how far should the noise texture render before it fades away. (in blocks)
# Set to 0 to disable noise from fading away
noiseDropoff = 1024
#
# How many steps of noise should be applied to LODs?
noiseSteps = 4
#
# How intense should the noise should be?
noiseIntensity = "0.05"
[client.advanced.graphics.experimental]
#
# A comma separated list of dimension resource locations where DH won't render.
#
# Example: "minecraft:the_nether,minecraft:the_end"
# For Minecraft 1.12.2 and older:
# Open the F3 screen and copy the dimension_name:ID shown in yellow. Example: "overworld:0"
#
# Note:
# Some DH settings will be disabled and/or changed to improve
# visuals when DH rendering is disabled.
ignoredDimensionCsv = ""
#
# Requires a restart to change.
#
# Options:
# AUTO - changes based on the most likely API for that MC version
# OPEN_GL - The Default for MC 1.21.11 and older (supports Iris shaders)
# BLAZE_3D - The Default for MC 26.1.2 and newer (supports Vulkan)
renderingEngine = "AUTO"
#
# This is the earth size ratio when applying the curvature shader effect.
# Note: Enabling this feature may cause rendering bugs.
#
# 0 = flat/disabled
# 1 = 1 to 1 (6,371,000 blocks)
# 100 = 1 to 100 (63,710 blocks)
# 10000 = 1 to 10000 (637.1 blocks)
#
# Note: Due to current limitations, the min value is [50]
# and the max value is 5000. Any values outside this range
# will be set to 0 (disabled).
earthCurveRatio = 0
[client.advanced.graphics.genericRendering]
#
# A comma separated separated list of dimension resource locations where DH clouds will render.
#
# Example: "minecraft:overworld,minecraft:the_end"
#
# Changes require a world re-load.
dimensionEnabledCloudRenderingCsv = "minecraft:overworld"
#
# If true LOD clouds will be rendered.
enableCloudRendering = true
#
# If true LOD beacon beams will be rendered wider at extreme distances,
# making them easier to see.
# If false all LOD beacon beams will only ever be 1 block wide.
expandDistantBeacons = true
#
# Sets the maximum height beacons will render up to.
#
# Requires a world re-load to take affect.
beaconRenderHeight = 6000
#
# If true LOD beacon beams will be rendered.
enableBeaconRendering = true
#
# If true non terrain objects will be rendered by DH.
# i.e. beacon beams and clouds.
enableGenericRendering = true
#
# False = DH will render a single layer of clouds, like vanilla Minecraft.
# True = DH will render 3 layers of clouds at different heights.
enableMultiLayerClouds = true
[client.advanced.graphics.quality]
#
# What is the maximum detail LODs can render at?
# Higher settings will increase memory and GPU usage.
#
# CHUNK: render 1 LOD for each Chunk.
# HALF_CHUNK: render 4 LODs for each Chunk.
# FOUR_BLOCKS: render 16 LODs for each Chunk.
# TWO_BLOCKS: render 64 LODs for each Chunk.
# BLOCK: render 256 LODs for each Chunk (width of one block).
#
# Fastest: CHUNK
# Fanciest: BLOCK
maxHorizontalResolution = "BLOCK"
#
# If true LODs will fade away as you get closer to them.
# If false LODs will cut off abruptly at a set distance from the camera.
# This setting is affected by the vanilla overdraw prevention config.
ditherDhFade = true
#
# If true DH will try to use the camera position when
# determining LOD quality drop-off.
# If false DH will use the player's position.
#
# Enabling helps free-cam mods render correctly.
# Disabling helps multi-camera mods render correctly (ie Immersive Portals or camera mods).
useCameraPositionForQualityDropOff = true
#
# How bright LOD colors are.
#
# 0 = black
# 1 = normal
# 2 = near white
brightnessMultiplier = "1.0"
#
# How many detail levels zooming in can increase LOD quality by.
# Higher numbers allow stronger zooms to render crisper terrain,
# but will increase memory and GPU usage while zoomed in.
#
# A vanilla spyglass needs 4 detail levels to reach its full quality.
maxZoomQualityIncrease = 4
#
# How should LODs be shaded?
#
# AUTO: Uses the same side shading as vanilla Minecraft blocks.
# ENABLED: Simulates Minecraft's block shading for LODs.
# Can be used to force LOD shading when using some shaders.
# DISABLED: All LOD sides will be rendered with the same brightness.
lodShading = "AUTO"
#
# How saturated LOD colors are.
#
# 0 = black and white
# 1 = normal
# 2 = very saturated
saturationMultiplier = "1.0"
#
# This indicates how well LODs will represent
# overhangs, caves, floating islands, etc.
# Higher options will make the world more accurate, butwill increase memory and GPU usage.
#
# Lowest Quality: HEIGHT_MAP
# Highest Quality: PIXEL_ART
verticalQuality = "MEDIUM"
#
# The radius of the mod's render distance. (measured in chunks)
#
# Note: this is a best effort number.
# The actual render distance may be above or below this number
# depending on your other graphic settings.
lodChunkRenderDistanceRadius = 256
#
# How should the sides and bottom of grass block LODs render?
#
# AS_GRASS: all sides of dirt LOD's render using the top (green) color.
# FADE_TO_DIRT: sides fade from grass to dirt.
# AS_DIRT: sides render entirely as dirt.
grassSideRendering = "FADE_TO_DIRT"
#
# This indicates how far apart drops in LOD quality are.
#
# Higher settings will increase the distance between drops
# but will increase memory and GPU usage.
horizontalQuality = "MEDIUM"
#
# How should LOD transparency be handled.
#
# COMPLETE: LODs will render transparent.
# DISABLED: LODs will be opaque.
transparency = "COMPLETE"
#
# This is the same as vanilla Biome Blending settings for Lod area.
# Note: anything above '0' will slow down LOD loading time.
#
# '0' equals to Vanilla Biome Blending of '1x1' or 'OFF',
# '1' equals to Vanilla Biome Blending of '3x3',
# '2' equals to Vanilla Biome Blending of '5x5'...
lodBiomeBlending = 3
#
# How should vanilla Minecraft fade into Distant Horizons LODs?
#
# NONE: Fastest, there will be a pronounced border between DH and MC rendering.
# SINGLE_PASS: Fades after MC's transparent pass, opaque blocks underwater won't be faded.
# DOUBLE_PASS: Slowest, fades after both MC's opaque and transparent passes, provides the smoothest transition.
vanillaFadeMode = "DOUBLE_PASS"
#
# If true LOD quality will increase when the camera is zoomed in,
# IE when using a spyglass or zoom mod.
#
# Only LODs visible through the camera view are affected.
#
# When zoomed in LODs will load to the same detail level
# they would have if you were close to them.
increaseQualityWhenZoomedIn = true
[client.advanced.graphics.fog]
#
# Should Minecraft's fog render?
# Note: Other mods may conflict with this setting.
enableVanillaFog = false
#
# What is the maximum fog thickness?
#
# 0.0: No fog.
# 1.0: Fully opaque fog.
farFogMax = "1.0"
#
# Determines if fog is drawn on DH LODs.
enableDhFog = true
#
# At what distance should the far fog start?
#
# 0.0: Fog starts at the player's position.
# 1.0: Fog starts at the closest edge of the vanilla render distance.
# 1.414: Fog starts at the corner of the vanilla render distance.
farFogStart = "0.4"
#
# What is the minimum fog thickness?
#
# 0.0: No fog.
# 1.0: Fully opaque fog.
farFogMin = "0.0"
#
# What color should fog use?
#
# USE_WORLD_FOG_COLOR: Use the world's fog color.
# USE_SKY_COLOR: Use the sky's color.
colorMode = "USE_WORLD_FOG_COLOR"
#
# How should the fog thickness should be calculated?
#
# LINEAR: Linear based on distance (will ignore 'density')
# EXPONENTIAL: 1/(e^(distance*density))
# EXPONENTIAL_SQUARED: 1/(e^((distance*density)^2)
farFogFalloff = "EXPONENTIAL_SQUARED"
#
# Used in conjunction with the Fog Falloff.
farFogDensity = "2.5"
#
# Where should the far fog end?
#
# 0.0: Fog ends at player's position.
# 1.0: Fog ends at the closest edge of the vanilla render distance.
# 1.414: Fog ends at the corner of the vanilla render distance.
farFogEnd = "1.0"
[client.advanced.graphics.fog.heightFog]
#
# Where should the height fog start?
#
# ABOVE_CAMERA: Height fog starts at the camera and goes towards the sky
# BELOW_CAMERA: Height fog starts at the camera and goes towards the void
# ABOVE_AND_BELOW_CAMERA: Height fog starts from the camera to goes towards both the sky and void
# ABOVE_SET_HEIGHT: Height fog starts from a set height and goes towards the sky
# BELOW_SET_HEIGHT: Height fog starts from a set height and goes towards the void
# ABOVE_AND_BELOW_SET_HEIGHT: Height fog starts from a set height and goes towards both the sky and void
heightFogDirection = "BELOW_SET_HEIGHT"
#
# What is the minimum fog thickness?
#
# 0.0: No fog.
# 1.0: Fully opaque fog.
heightFogMin = "0.0"
#
# If the height fog is calculated around a set height, what is that height position?
heightFogBaseHeight = "80.0"
#
# What is the maximum fog thickness?
#
# 0.0: No fog.
# 1.0: Fully opaque fog.
heightFogMax = "1.0"
#
# How should the height fog thickness should be calculated?
#
# LINEAR: Linear based on height (will ignore 'density')
# EXPONENTIAL: 1/(e^(height*density))
# EXPONENTIAL_SQUARED: 1/(e^((height*density)^2)
heightFogFalloff = "EXPONENTIAL_SQUARED"
#
# What is the height fog's density?
heightFogDensity = "20.0"
#
# How should height effect the fog thickness?
# Note: height fog is combined with the other fog settings.
#
# SPHERICAL: Fog is calculated based on camera distance.
# CYLINDRICAL: Ignore height, fog is calculated based on horizontal distance.
#
# MAX: max(heightFog, farFog)
# ADDITION: heightFog + farFog
# MULTIPLY: heightFog * farFog
# INVERSE_MULTIPLY: 1 - (1-heightFog) * (1-farFog)
# LIMITED_ADDITION: farFog + max(farFog, heightFog)
# MULTIPLY_ADDITION: farFog + farFog * heightFog
# INVERSE_MULTIPLY_ADDITION: farFog + 1 - (1-heightFog) * (1-farFog)
# AVERAGE: farFog*0.5 + heightFog*0.5
heightFogMixMode = "SPHERICAL"
#
# Should the start of the height fog be offset?
#
# 0.0: Fog start with no offset.
# 1.0: Fog start with offset of the entire world's height. (Includes depth)
heightFogStart = "0.0"
#
# Should the end of the height fog be offset?
#
# 0.0: Fog end with no offset.
# 1.0: Fog end with offset of the entire world's height. (Include depth)
heightFogEnd = "0.6"
[client.advanced.multiplayer]
#
# How should multiplayer save folders should be named?
#
# NAME_ONLY: Example: "Minecraft Server"
# IP_ONLY: Example: "192.168.1.40"
# NAME_IP: Example: "Minecraft Server IP 192.168.1.40"
# NAME_IP_PORT: Example: "Minecraft Server IP 192.168.1.40:25565"NAME_IP_PORT_MC_VERSION: Example: "Minecraft Server IP 192.168.1.40:25565 GameVersion 1.16.5"
serverFolderNameMode = "NAME_ONLY"
@@ -0,0 +1,35 @@
[visuals]
#whether to cave maps are visible when held by players from the third-person perspective.
cave_maps_visible_in_third_person = true
#whether to shake the screen from tremorsaurus stomping, nuclear explosions, etc.
screen_shaking = true
#true if some block models, like uranium ore or abyssmarine bricks render as fullbright. May increase load time, no gameplay performance impact.
emissive_block_models = true
#whether to make the screen flash white during nuclear explosions.
nuclear_bomb_flash = true
#true if some biomes, such as primordial caves, have ambient light that makes the biome easier to see in.
biome_ambient_light = true
#true if some biomes, such as toxic caves, apply a color to ambient light. May conflict with shaders.
biome_ambient_light_coloring = true
#true if some biomes, such as primordial caves, have an always well-lit sky when in them. May conflict with shaders.
biome_sky_overrides = true
#true if some biomes, such as abyssal chasm, have an thicker water fog to them. May conflict with shaders.
biome_sky_fog_overrides = true
#true if ambersol block renders with rays of light emerging from it.
ambersol_shines = true
#true if irradiated effect makes mobs glow. May conflict with shaders.
radiation_glow_effect = true
#determines how far to the left the subterranodon flight indicator renders on the screen when mounted. Negative numbers will render it on the right.
# Default: 22
# Range: -12000 ~ 12000
subterranodon_indicator_x = 22
#determines how far from bottom the subterranodon flight indicator renders on the screen when mounted.
# Default: 6
# Range: -12000 ~ 12000
subterranodon_indicator_y = 6
#true if sugar rush makes the world more saturated. May conflict with shaders.
sugar_rush_saturation_effect = true
[audio]
#whether nuclear explosions briefly muffle other sounds.
nuclear_bomb_muffles_sounds = true
@@ -0,0 +1,14 @@
{
"ingredientDedupe": false,
"debugItemStackModifications": false,
"version": 1,
"debugNativeImage": false,
"disableSearchTree": false,
"logIntervalInMinutes": 10,
"showSummaryOnDebugScreen": true,
"memoryUsageWarningPercentage": 90,
"debugChunkLoading": false,
"debugThreadsStuck": false,
"disableEternalStarlightProgress": false,
"skipTickingUnloadedFluxNetworks": false
}
@@ -0,0 +1,228 @@
{
"general": {
"engines": 0,
"volume": 1,
"useSoundMasterSource": false,
"scanStepAmount": 100,
"playSoundWithOffset": true
},
"dimensions": {
"abyssalcraft": 1,
"betweenlands": 1,
"cave": 1,
"end": 1,
"galacticraft": 1,
"galacticraft.planets": 1,
"midnight": 1,
"nether": 1,
"surface": 1,
"twilightforest": 1,
"void": 1
},
"regions": {
"abyssalcraft_dark": {
"overall": 1,
"suspense.suspense": 1
},
"end_unknown": {
"overall": 1,
"suspense.suspense": 1
},
"galacticraft_planets_wind": {
"overall": 1,
"wind.light-wind": 1
},
"nether_suspense": {
"overall": 1,
"nether": 1
},
"twilightforest_unknown": {
"overall": 1,
"suspense.suspense": 1
},
"void_dark": {
"overall": 1,
"suspense.suspense": 1
},
"beach": {
"overall": 1,
"beach": 1,
"seagull": 1,
"seagull-long": 1
},
"cave_ambience": {
"overall": 1,
"cave": 1,
"cave-water": 1,
"cave-deep": 1,
"cave-large": 1
},
"cave_deep-dark": {
"overall": 1,
"deep-dark": 1,
"water-droplets": 1
},
"cave": {
"overall": 1
},
"cave_lush": {
"overall": 1,
"cave-lush": 1,
"frog": 1
},
"cold_artic": {
"overall": 1,
"heavy-wind": 1
},
"cold": {
"overall": 1,
"wind.light-wind": 1
},
"desert": {
"overall": 1,
"cicadas-desert": 1,
"wind.light-wind": 1
},
"forest_cold": {
"overall": 1,
"crow": 1
},
"forest": {
"overall": 1,
"bird-ambience": 1,
"owl": 1
},
"forest_roofed": {
"overall": 1,
"bird-ambience-spooky": 1
},
"grass": {
"overall": 1,
"cricket": 1,
"cricket-night": 1,
"cicadas-night": 1
},
"highland": {
"overall": 1,
"heavy-wind": 1,
"wind-in-leaves": 1
},
"jungle": {
"overall": 1,
"cricket-jungle": 1,
"cricket-jungle-night": 1,
"bird-ambience-jungle": 1,
"bird-ambience-jungle-night": 1
},
"mesa": {
"overall": 1,
"wind-mesa": 1
},
"mushroom_fields": {
"overall": 1,
"cicadas-mushroom-fields": 1,
"special-mushroom-fields": 1
},
"ocean": {
"overall": 1,
"ocean": 1,
"wind.light-wind": 1
},
"ocean_deep": {
"overall": 1,
"whale": 1
},
"pale-garden": {
"overall": 1,
"pale-garden-ambience": 1,
"owl": 1
},
"plains": {
"overall": 1,
"cricket": 1,
"cricket-night": 1,
"cicadas-night": 1
},
"rain": {
"overall": 1,
"rain": 1
},
"savanna": {
"overall": 1,
"cricket": 1,
"cicadas": 1,
"cicadas-desert": 1,
"cricket-warm-night": 1,
"wolf": 1,
"long-wolf": 1,
"bird-warm": 1
},
"sky": {
"overall": 1,
"wind.light-wind": 1
},
"space": {
"overall": 1
},
"storm_away": {
"overall": 1,
"storm-away": 1,
"howling-wind": 1
},
"storm_close": {
"overall": 1,
"storm-close": 1,
"wind": 1
},
"surface": {
"overall": 1
},
"swamp": {
"overall": 1,
"cricket-swamp": 1,
"cricket-warm-night": 1,
"frog": 1,
"mouring_dove": 1
},
"taiga": {
"overall": 1,
"bird-ambience-huge": 1
},
"underwater": {
"overall": 1,
"underwater": 1,
"underwater-deep": 1
},
"underworld": {
"overall": 1,
"underworld": 1
},
"warden": {
"overall": 1,
"warden": 1
}
},
"categories": {
"animal": {
"animal": 1,
"bird": {
"bird": 1,
"bird_day": 1,
"bird_night": 1
},
"cicadas": 1,
"cricket": {
"cricket": 1,
"cricket_day": 1,
"cricket_night": 1
}
},
"cave": 1,
"suspense": 1,
"weather": 1,
"wind": 1
},
"fade-volume": 0.005,
"fade-pitch": 0.005,
"silent-dimensions": []
}
@@ -0,0 +1,68 @@
#Lighting
[lights]
#Show the supporter message. This is set to false after the first time.
showSupporterMessage = true
#If dynamic lights are enabled
lightsEnabled = false
#How bright the touch light is
# Default: 8
# Range: 0 ~ 15
touchLightLuminance = 8
#How long the touch light lasts in ticks
# Default: 8
# Range: 0 ~ 40
touchLightDuration = 8
#Light level an entity should emit when dynamic lights are on
#Example entry: minecraft:blaze=15
entity_lights = ["minecraft:blaze=10", "ars_nouveau:orbit=15", "ars_nouveau:linger=15", "ars_nouveau:spell_proj=15", "minecraft:magma_cube=8", "ars_nouveau:flying_item=10", "minecraft:spectral_arrow=8", "ars_nouveau:follow_proj=10"]
#Light level an item should emit when held when dynamic lights are on
#Example entry: minecraft:stick=15
item_lights = ["minecraft:redstone_torch=10", "minecraft:soul_lantern=12", "minecraft:glow_ink_sac=10", "minecraft:verdant_froglight=15", "minecraft:blaze_rod=10", "minecraft:shroomlight=10", "minecraft:lantern=14", "minecraft:soul_torch=10", "minecraft:glow_berries=8", "minecraft:glowstone_dust=8", "minecraft:pearlescent_froglight=15", "minecraft:nether_star=14", "minecraft:glowstone=15", "minecraft:torch=14", "minecraft:ochre_froglight=15", "minecraft:lava_bucket=15"]
#Overlay
[overlays]
#X offset for the tooltip
# Default: 20
# Range: > -2147483648
xTooltip = 20
#Y offset for the tooltip
# Default: 0
# Range: > -2147483648
yTooltip = 0
#X offset for the Mana Bar
# Default: 0
# Range: > -2147483648
xManaBar = 0
#Y offset for the Mana Bar
# Default: 0
# Range: > -2147483648
yManaBar = 0
#X offset for the Spell Name
# Default: 10
# Range: > -2147483648
xSpellName = 10
#Y offset for the Spell Name
# Default: 30
# Range: > -2147483648
ySpellName = 30
#If the Storage Lectern should show the recipe book icon
showRecipeBook = true
#If scrolling in the Storage Lectern should be inverted
invertLecternScrolling = false
#Inform the player of Dynamic lights once.
informLights = false
#Whether the Selection HUD is toggled or held
toggleSelectionHUD = true
#Misc
[misc]
#Use simplified renderer for Warp Portals
no_end_portal_render = false
#Disables the skyweave renderer. Disable if your sky is broken with shaders.
disable_skyweave = false
#Show spell tooltips with glyphs instead of plain text
glyphTooltips = true
#Enables transparent/opaque rendering of elements in the book GUI. Disable if it leads to crash with Sodium derivatives
gui_transparency = true
#Disables translucent particles. Disable if your particles are invisible with shaders.
opaque_particles = false
@@ -0,0 +1,147 @@
#.
#Client-only settings - If you're looking for general settings, look inside your worlds serverconfig folder!
[client]
#.
#Show item descriptions on Shift and controls on Ctrl.
enableTooltips = true
#.
#Display a tooltip when looking at overstressed components.
enableOverstressedTooltip = true
#.
#Log a stack-trace when rendering issues happen within a moving contraption.
explainRenderErrors = false
#.
#Higher density means more spawned particles.
# Default: 0.5
# Range: 0.0 ~ 1.0
fanParticleDensity = 0.5
#.
#[in Blocks]
#Maximum Distance to the player at which items in Blocks' filter slots will be displayed
# Default: 10.0
# Range: 1.0 ~ 3.4028234663852886E38
filterItemRenderDistance = 10.0
#.
#Choose the menu row that the Create config button appears on in the main menu
#Set to 0 to disable the button altogether
# Default: 2
# Range: 0 ~ 4
mainMenuConfigButtonRow = 2
#.
#Offset the Create config button in the main menu by this many pixels on the X axis
#The sign (-/+) of this value determines what side of the row the button appears on (left/right)
# Default: -4
# Range: > -2147483648
mainMenuConfigButtonOffsetX = -4
#.
#Choose the menu row that the Create config button appears on in the in-game menu
#Set to 0 to disable the button altogether
# Default: 3
# Range: 0 ~ 5
ingameMenuConfigButtonRow = 3
#.
#Offset the Create config button in the in-game menu by this many pixels on the X axis
#The sign (-/+) of this value determines what side of the row the button appears on (left/right)
# Default: -4
# Range: > -2147483648
ingameMenuConfigButtonOffsetX = -4
#.
#Setting this to true will prevent Create from sending you a warning when playing with Fabulous graphics enabled
ignoreFabulousWarning = false
#.
#Disable to prevent being rotated while seated on a Moving Contraption
rotateWhenSeated = true
#.
#Configure your vision range when submerged in Create's custom fluids
[client.fluidFogSettings]
#.
#The vision range through honey will be multiplied by this factor
# Default: 1.0
# Range: 0.125 ~ 256.0
honey = 1.0
#.
#The vision range though chocolate will be multiplied by this factor
# Default: 1.0
# Range: 0.125 ~ 256.0
chocolate = 1.0
#.
#Settings for the Goggle Overlay
[client.goggleOverlay]
#.
#Offset the overlay from goggle- and hover- information by this many pixels on the respective axis; Use /create overlay
# Default: 20
# Range: > -2147483648
overlayOffsetX = 20
#.
#Offset the overlay from goggle- and hover- information by this many pixels on the respective axis; Use /create overlay
# Default: 0
# Range: > -2147483648
overlayOffsetY = 0
#.
#Enable this to use your custom colors for the Goggle- and Hover- Overlay
customColorsOverlay = false
#.
#The custom background color to use for the Goggle- and Hover- Overlays, if enabled
#[in Hex: #AaRrGgBb]
#[@cui:IntDisplay:#]
# Default: -267386864
# Range: > -2147483648
customBackgroundOverlay = -267386864
#.
#The custom top color of the border gradient to use for the Goggle- and Hover- Overlays, if enabled
#[in Hex: #AaRrGgBb]
#[@cui:IntDisplay:#]
# Default: 1347420415
# Range: > -2147483648
customBorderTopOverlay = 1347420415
#.
#The custom bot color of the border gradient to use for the Goggle- and Hover- Overlays, if enabled
#[in Hex: #AaRrGgBb]
#[@cui:IntDisplay:#]
# Default: 1344798847
# Range: > -2147483648
customBorderBotOverlay = 1344798847
#.
#Sound settings
[client.sound]
#.
#Make cogs rumble and machines clatter.
enableAmbientSounds = true
#.
#Maximum volume modifier of Ambient noise
# Default: 0.10000000149011612
# Range: 0.0 ~ 1.0
ambientVolumeCap = 0.10000000149011612
#.
#Mod Integration and Recipe Viewer
[client.recipeViewerIntegration]
#.
#How Recipe Viewer search should interact with Stock Keepers
#Allowed Values: SYNC_BOTH, SYNC_FROM_JEI, SYNC_FROM_STOCK_KEEPER, NONE
syncRecipeViewerSearch = "SYNC_BOTH"
#.
#Railway related settings
[client.trains]
#.
#How far away the Camera should zoom when seated on a train
# Default: 3.0
# Range: 0.0 ~ 3.4028234663852886E38
mountedZoomMultiplier = 3.0
#.
#Display nodes and edges of a Railway Network while f3 debug mode is active
showTrackGraphOnF3 = false
#.
#Additionally display materials of a Rail Network while f3 debug mode is active
showExtendedTrackGraphOnF3 = false
#.
#Display Track Networks and Trains on supported map mods
showTrainMapOverlay = true
#.
#Track Network Color on maps
#Allowed Values: RED, GREY, WHITE
trainMapColorTheme = "RED"
@@ -0,0 +1,36 @@
{
"allowedCEM": "ALL",
"logModelCreationData": false,
"debugOnRightClick": false,
"renderModeChoice": "NORMAL",
"vanillaModelHologramRenderMode_2": "OFF",
"modelExportMode": "NONE",
"automaticModelExporting": false,
"attemptPhysicsModPatch_2": "CUSTOM",
"modelUpdateFrequency": "Average",
"entityRenderModeOverrides": {},
"entityPhysicsModPatchOverrides": {},
"entityVanillaHologramOverrides": {},
"modelsNamesDisabled": [],
"allowEBEModConfigModify": true,
"animationLODDistance": 20,
"retainDetailOnLowFps": true,
"retainDetailOnLargerMobs": true,
"animationFrameSkipDuringIrisShadowPass": true,
"preventFirstPersonHandAnimating": false,
"onlyClientPlayerModel": false,
"doubleChestAnimFix": true,
"enforceOptifineVariationRequiresDefaultModel": false,
"enforceOptifineVariationRequiresDefaultModel_v2": false,
"resetPlayerModelEachRender": true,
"resetPlayerModelEachRender_v2": true,
"onlyDebugRenderOnHover": false,
"enforceOptifineSubFoldersVariantOnly": false,
"enforceOptiFineAnimSyntaxLimits": true,
"allowOptifineFallbackProperties": true,
"showReloadErrorToast": true,
"exportRotations": false,
"asmMaths": true,
"logASM": false,
"parrotShoulderPositionAnimatesByDefault": true
}
@@ -0,0 +1,41 @@
{
"optifine_limitRandomVariantGapsBy10": false,
"optifine_allowWeirdSkipsInTrueRandom": true,
"optifine_preventBaseTextureInOptifineDirectory": true,
"illegalPathSupportMode": "None",
"enableCustomTextures": true,
"enableCustomBlockEntities": true,
"textureUpdateFrequency_V2": "Fast",
"enableEmissiveTextures": true,
"enableEnchantedTextures": true,
"enableEmissiveBlockEntities": true,
"emissiveRenderMode": "DULL",
"alwaysCheckVanillaEmissiveSuffix": true,
"enableArmorAndTrims": true,
"skinFeaturesEnabled": true,
"skinTransparencyMode": "ETF_SKINS_ONLY",
"skinTransparencyInExtraPixels": true,
"skinFeaturesEnableTransparency": true,
"skinFeaturesEnableFullTransparency": false,
"tryETFTransparencyForAllSkins": false,
"enableEnemyTeamPlayersSkinFeatures": true,
"enableBlinking": true,
"blinkFrequency": 150,
"blinkLength": 1,
"advanced_IncreaseCacheSizeModifier": 1.0,
"debugLoggingMode": "None",
"logTextureDataInitialization": false,
"hideConfigButton": false,
"stackDebugPrinting": false,
"configButtonLoc": "BOTTOM_RIGHT",
"disableVanillaDirectoryVariantTextures": false,
"use3DSkinLayerPatch": true,
"enableFullBodyWardenTextures": true,
"entityEmissiveOverrides": {},
"propertiesDisabled": [],
"propertyInvertUpdatingOverrides": [],
"entityRandomOverrides": {},
"entityEmissiveBrightOverrides": {},
"entityRenderLayerOverrides": {},
"entityLightOverrides": {}
}
@@ -0,0 +1,149 @@
[ui]
#Tooltips for Developing Film Rolls and Copying Photographs will be shown on Film Rolls and Photographs respectively, describing the crafting recipe.
#Only when JEI is not installed. (Only JEI shows these recipes, not REI or EMI)
recipe_tooltips_without_jei = true
#Details about Camera configuring will be shown in Camera item tooltip.
camera_details_tooltip = true
#Film Roll Frames will be shown in the camera tooltip.
#Default: true
camera_film_frames_tooltip = true
#Film Roll fullness bar will be shown on the Camera item.
#Default: false
camera_shows_film_bar = false
#Photographer name will be shown in Photograph's tooltip.
photograph_photographer_name_tooltip = false
#Crosshair will not get in the way when holding a photograph.
photograph_in_hand_hide_crosshair = true
#When looking at the Camera Stand, in-world tooltip will show information about the camera on it. Default: true
camera_stand_tooltip = true
#Album will show how many photographs it contains in a tooltip.
album_show_photos_count = true
[ui.album]
#Color in hex format. AARRGGBB.
font_main_color = "FFB59774"
#Color in hex format. AARRGGBB.
font_secondary_color = "FFEFE4CA"
#Color in hex format. AARRGGBB.
selection_color = "FF8888FF"
#Color in hex format. AARRGGBB.
selection_unfocused_color = "FFBBBBFF"
[viewfinder]
#Clicking middle mouse button will open Viewfinder Controls. This is independent of Open Camera Controls keybind.
#Allows opening camera controls without dismounting from a vehicle - and keeping controls on sneak or other button as well.
#Default: true
middle_click_controls = true
#How much zooming influences mouse sensitivity.
#0 - no change to sensitivity. 1 - full effect.
#Default: 0.75
# Default: 0.75
# Range: 0.0 ~ 1.0
zoom_sensitivity_influence = 0.75
#Color in hex format. AARRGGBB.
background_color = "FA1F1D1B"
#Color in hex format. AARRGGBB.
font_main_color = "FF2B2622"
#Color in hex format. AARRGGBB.
font_secondary_color = "FF7A736C"
#HUD will be hidden while looking through viewfinder. Default: true
hide_hud_while_in_viewfinder = true
#X offset of a viewfinder status icon. Default: 0
# Default: 0
# Range: -999 ~ 999
status_icon_offset_x = 0
#Y offset of a viewfinder status icon. Default: 0
# Default: 0
# Range: -999 ~ 999
status_icon_offset_y = 0
[capture]
#Keep Post Effect (vanilla shader) when capturing an image.
#It is sometimes used by mods to change how player sees the world. (Cold Sweat's overheating blur, Supplementaries mob heads, for example).
#In vanilla, it's only used when spectating a creeper/enderman/etc.
#Default: false
keep_post_effect = false
#Delay in ticks before capturing an image when shooting with flash.
#If you experience flash synchronization issues (Flash having no effect on the image) - try increasing the value.
# Default: 4
# Range: 1 ~ 8
flash_capture_delay_ticks = 4
#Force legacy (pre 1.21) capturing method for taking images. Enable if you experiencing issues with resulting images.
#Direct method will be used regardless of this setting if mods defined in 'force_direct_capture_default_mods' is installed.
#Default: false
force_direct_capture = false
#Direct capture will be used if any of these mods is installed.
#Format: '["mod_id", "mod_id"]'. Default: [veil, pmweather]
force_direct_capture_default_mods = ["veil", "pmweather"]
#Delay in frames before capturing an image if 'direct_capture' method is in use.
#Set to higher value when leftovers of GUI elements (such as nameplates) are visible on the images
#(some shaders have temporal effects that take several frames to disappear fully)
# Default: 0
# Range: 0 ~ 100
direct_capture_delay_frames = 0
#Sets the game to 'panoramic mode' when capturing the image with background capture method (default).
#Enabling this might fix some graphical issues, such as water being invisible.
#If captured images are still not looking properly - enable 'force_direct_capture'.
#Default: false
background_capture_use_panoramic_mode = false
[capture.loading]
#Loading from file will only accept a path relative to game directory, when on the dedicated server (multiplayer).
#This option is for protection against malicious actions using Projector or load commands.
#Default: false
multiplayer_file_loading_only_from_game_dir = false
#How loading from URL should behave when on the dedicated server (multiplayer).
#This option is for protection against malicious actions using Projector or load commands.
#Default: ONLY_ALLOWED_DOMAINS
#Allowed Values: ALL, ONLY_ALLOWED_DOMAINS, NONE
multiplayer_url_loading = "ONLY_ALLOWED_DOMAINS"
#List of allowed domains for loading from URl, if 'multiplayer_url_loading' is set to ONLY_ALLOWED_DOMAINS.
multiplayer_url_loading_allowed_domains = ["i.imgur.com", "cdn.discordapp.com", "media.discordapp.net", "raw.githubusercontent.com"]
#List of allowed domains for loading from URl, if 'multiplayer_url_loading' is set to ONLY_ALLOWED_DOMAINS.
multiplayer_url_loading_allowed_subdomains = [".imgur.com", ".discordapp.com"]
[render]
#Makes photos in Photograph Frame render with alignment to 16 pixel grid (like paintings). Just for fun. Default: false
pixel_perfect_photograph_frame = false
#Photographs in Item Frame will be rendered as image instead of an item icon. Default: false
photograph_renders_in_item_frame = false
#Projected photographs (using Interplanar Projector) made by other players will be censored (pixelated). Default: false
censor_projected_photographs_made_by_others = false
#All photographs made by other players will will be censored (pixelated). Default: false
censor_all_photographs_made_by_others = false
#Distance from the player beyond which Photograph Frame would not be rendered. Default: 64
#Note: this number may not relate to distance in blocks exactly. It's influenced by render distance and entity distance settings.
# Default: 64
# Range: 8 ~ 128
photograph_frame_culling_distance = 64
#Depth offset to Photograph in Photograph Frame. Can be used to fix issues with some 3D resourcepacks. Value of 0.015 is good for 'Classic 3D' resourcepack. Default: 0.0
# Default: 0.0
# Range: -1.0 ~ 1.0
photograph_frame_image_offset = 0.0
[integration]
#Useful information about some items will be shown in JEI description category. Default: true
jei_information = true
#[Real Camera] Disable player model rendering when looking through viewfinder. Default: true
real_camera_disable_in_viewfinder = true
[export]
#When the Photograph you took is viewed in UI (by using a Photograph), image will be exported to '<instance>/exposures' folder as a png.
export_viewed_photographs = true
#When exporting, exposures will be organized to subfolders with world name.
export_organize_by_world = true
#Exported exposures will be scaled by this multiplier.
#Given the default exposure size of 320 pixels - this will produce:
#320/640/960/1280/etc image. Be careful with larger frame sizes.
#Default: 2
# Default: 2
# Range: 1 ~ 10
export_size_multiplier = 2
[tutorial]
#Toast that teaches hovering mouse over camera parts will be shown when attachments menu is first opened. Default: true.
#*This setting will be automatically set to false on first show.*
attachments_show_info_toast = true
#Toast that teaches wiki opening will be shown when attachments menu is first opened, after info toast. Default: true.
#*This setting will be automatically set to false on first show.*
attachments_show_wiki_toast = true
@@ -0,0 +1,24 @@
#Use a slightly more compact, but also slightly slower representation for block states
compactFastMap = false
#Replace objects used to detect multi-threaded access to chunks by a much smaller field. This option is disabled by default due to very rare and very hard-to-reproduce crashes, use at your own risk!
useSmallThreadingDetector = false
#Cache the predicate instances used in multipart models
cacheMultipartPredicates = true
#Do not create a new MultipartBakedModel instance for each block state using the same multipartmodel. Requires cacheMultipartPredicates to be enabled
multipartDeduplication = true
#Deduplicate cached data for blockstates, most importantly collision and render shapes
blockstateCacheDeduplication = true
#Save memory overhead from empty data component maps/patched
dataComponentPatch = true
#Avoid creation of new strings when creating ModelResourceLocations
modelResourceLocations = true
#Use smaller data structures for "simple" models, especially models with few side-specific faces
modelSides = true
#Replace the blockstate neighbor table
replaceNeighborLookup = true
#Populate the neighbor table used by vanilla. Enabling this slightly increases memory usage, but can help with issues in the rare case where mods access it directly.
populateNeighborTable = false
#Do not store the properties of a state explicitly and read themfrom the replace neighbor table instead. Requires replaceNeighborLookup to be enabled
replacePropertyMap = true
#Deduplicate vertex data of baked quads in the basic model implementations
bakedQuadDeduplication = true
@@ -0,0 +1,14 @@
#Select the backend to use. Set to "DEFAULT" to let Flywheel decide.
backend = "DEFAULT"
#Enable or disable instance update limiting with distance.
limitUpdates = true
#The number of worker threads to use. Set to -1 to let Flywheel decide. Set to 0 to disable parallelism. Requires a game restart to take effect.
# Default: -1
# Range: -1 ~ 4
workerThreads = -1
#Config options for Flywheel's built-in backends.
[flw_backends]
#How smooth Flywheel's shader-based lighting should be. May have a large performance impact.
#Allowed Values: FLAT, TRI_LINEAR, SMOOTH, SMOOTH_INNER_FACE_CORRECTED
lightSmoothness = "SMOOTH"
@@ -0,0 +1,22 @@
{
"REGULAR_INFO": "----- Regular config values below -----",
"font_atlas_resizing": true,
"map_atlas_generation": true,
"hud_batching": true,
"fast_text_lookup": true,
"fast_buffer_upload": true,
"COSMETIC_INFO": "----- Cosmetic only config values below (Does not optimize anything) -----",
"dont_add_info_into_debug_hud": false,
"EXPERIMENTAL_INFO": "----- Experimental config values below (Rendering glitches may occur) -----",
"experimental_disable_error_checking": false,
"experimental_disable_resource_pack_conflict_handling": false,
"experimental_sign_text_buffering": false,
"experimental_screen_batching": false,
"DEBUG_INFO": "----- Debug only config values below (Do not touch) -----",
"debug_only_and_not_recommended_disable_universal_batching": false,
"debug_only_and_not_recommended_disable_mod_conflict_handling": false,
"debug_only_and_not_recommended_disable_hardware_conflict_handling": false,
"debug_only_print_additional_error_information": false,
"debug_only_use_last_usage_for_batch_ordering": false,
"debug_only_detailed_memory_leak_detection": false
}
@@ -0,0 +1,6 @@
# This is the configuration file for Lithium.
#
# You can find information on editing this file and all the available options here:
# https://github.com/CaffeineMC/lithium-fabric/wiki/Configuration-File
#
# By default, this file will be empty except for this notice.
@@ -0,0 +1,2 @@
#These JEI plugins will be loaded on the main thread
blacklist_async_jei_plugins = ["jepb:jei_plugin"]
@@ -0,0 +1,105 @@
# This is the configuration file for ModernFix.
# In general, prefer using the config screen to editing this file. It can be accessed
# via the standard mod menu on your respective mod loader. Changes will, however,
# require restarting the game to take effect.
#
# The following options can be enabled or disabled if there is a compatibility issue.
# Add a line with your option name and =true or =false at the bottom of the file to enable
# or disable a rule. For example:
# mixin.perf.dynamic_resources=true
# Do not include the #. You may reset to defaults by deleting this file.
#
# To enable features that are still in testing, add a line at the bottom setting the stability level:
# stability_level=BETA
#
# Available options:
# mixin.bugfix.chunk_deadlock=true # (default)
# mixin.bugfix.cofh_core_crash=true # (default)
# mixin.bugfix.concurrency=true # (default)
# mixin.bugfix.ctm_resourceutil_cme=true # (default)
# mixin.bugfix.ender_dragon_leak=true # (default)
# mixin.bugfix.entity_pose_stack=true # (default)
# mixin.bugfix.extra_experimental_screen=true # (default)
# mixin.bugfix.fix_config_crashes=true # (default)
# mixin.bugfix.missing_block_entities=false # (default)
# mixin.bugfix.packet_leak=false # (default)
# mixin.bugfix.paper_chunk_patches=true # (default)
# mixin.bugfix.recipe_book_type_desync=true # (default)
# mixin.bugfix.restore_old_dragon_movement=false # (default)
# mixin.bugfix.skip_redundant_saves=false # (default)
# mixin.bugfix.sprite_borders=true # (default)
# mixin.bugfix.unsafe_modded_shape_caches=true # (default)
# mixin.bugfix.world_leaks=true # (default)
# mixin.bugfix.world_screen_skipped=true # (default)
# mixin.devenv=false # (default)
# mixin.feature.blockentity_incorrect_thread=false # (default)
# mixin.feature.branding=true # (default)
# mixin.feature.cause_lag_by_disabling_threads=false # (default)
# mixin.feature.direct_stack_trace=false # (default)
# mixin.feature.integrated_server_watchdog=true # (default)
# mixin.feature.log_stdout_in_log_files=true # (default)
# mixin.feature.mcfunction_profiling=true # (default)
# mixin.feature.measure_time=true # (default)
# mixin.feature.registry_event_progress=true # (default)
# mixin.feature.remove_chat_signing=false # (default)
# mixin.feature.remove_telemetry=true # (default)
# mixin.feature.snapshot_easter_egg=true # (default)
# mixin.feature.spam_thread_dump=false # (default)
# mixin.feature.spark_profile_launch=false # (default)
# mixin.feature.spark_profile_world_join=false # (default)
# mixin.feature.stalled_chunk_load_detection=false # (default)
# mixin.feature.suppress_narrator_stacktrace=true # (default)
# mixin.feature.warn_missing_perf_mods=true # (default)
# mixin.launch.class_search_cache=true # (default)
# mixin.perf.attribute_supplier_dedup=true # (default)
# mixin.perf.cache_blockstate_cache_arrays=true # (default)
# mixin.perf.cache_profile_texture_url=true # (default)
# mixin.perf.cache_strongholds=true # (default)
# mixin.perf.capability_list_compaction=true # (default)
# mixin.perf.chunk_meshing=true # (default)
# mixin.perf.clear_mixin_classinfo=false # (default)
# mixin.perf.compact_bit_storage=true # (default)
# mixin.perf.compact_entity_models=true # (default)
# mixin.perf.compact_imposterprotochunks=true # (default)
# mixin.perf.compact_mojang_registries=true # (default)
# mixin.perf.compress_unihex_font=true # (default)
# mixin.perf.datapack_reload_exceptions=true # (default)
# mixin.perf.dedicated_reload_executor=true # (default)
# mixin.perf.deduplicate_climate_parameters=false # (default)
# mixin.perf.deduplicate_location=false # (default)
# mixin.perf.deduplicate_wall_shapes=true # (default)
# mixin.perf.dynamic_dfu=true # (default)
# mixin.perf.dynamic_entity_renderers=false # (default)
# mixin.perf.dynamic_languages=true # (default)
# mixin.perf.dynamic_resources=false # (default)
# mixin.perf.dynamic_resources.ctm=true # (default)
# mixin.perf.dynamic_resources.ldlib=true # (default)
# mixin.perf.dynamic_structure_manager=true # (default)
# mixin.perf.encoder_cache_leak=true # (default)
# mixin.perf.faster_ingredients=true # (default)
# mixin.perf.faster_item_rendering=false # (default)
# mixin.perf.faster_texture_stitching=true # (default)
# mixin.perf.fix_loop_spin_waiting=true # (default)
# mixin.perf.forge_registry_alloc=true # (default)
# mixin.perf.ingredient_item_deduplication=true # (default)
# mixin.perf.lazy_search_tree_registry=true # (default)
# mixin.perf.memoize_creative_tab_build=true # (default)
# mixin.perf.model_optimizations=true # (default)
# mixin.perf.mojang_registry_size=true # (default)
# mixin.perf.optimize_surface_rules=true # (default)
# mixin.perf.patchouli_deduplicate_books=true # (default)
# mixin.perf.reduce_blockstate_cache_rebuilds=true # (default)
# mixin.perf.release_protochunks=true # (default)
# mixin.perf.remove_biome_temperature_cache=true # (default)
# mixin.perf.resourcepacks=true # (default)
# mixin.perf.smart_ingredient_sync=true # (default)
# mixin.perf.state_definition_construct=true # (default)
# mixin.perf.suspend_integrated_server_during_load=true # (default)
# mixin.perf.tag_id_caching=true # (default)
# mixin.perf.thread_priorities=true # (default)
# mixin.perf.ticking_chunk_alloc=true # (default)
# mixin.perf.worldgen_allocation=true # (default)
# mixin.safety=true # (default)
# stability_level=GA # (default)
#
# User overrides go here.
@@ -0,0 +1,17 @@
[client]
#Toggles the lantern glow effect, which may look bad with certain shaders.
glow_effect = true
#Toggles the Umvuthana footprint effects, which may decrease performance.
umvuthana_footprints = true
#Enable camera shaking during certain mob attacks and abilities.
do_camera_shake = true
#Play boss battle themes during boss encounters.
play_boss_music = true
#Use custom boss health bar textures, if the boss has them.
custom_boss_bar = true
#Use custom player animations.
custom_player_anims = true
#Set to true to hide your own 3rd-person player animations while you are in 1st-person view mode. This is useful with mods that render the 3rd-person model in 1st-person, as some of the Mowzie's Mobs 3rd-person animations can block the camera.
hide_player_anims_in_first_person = false
#Play Umvuthana Crane heal sounds. Turn this off if you are experiencing crashes when Cranes appear during Umvuthi's boss battle.
crane_heal_sounds = true
@@ -0,0 +1,8 @@
#EXPERIMENTAL: Enable the NeoForge block rendering pipeline - fixes the lighting of custom models.
experimentalForgeLightPipelineEnabled = false
#When enabled, NeoForge will show any warnings that occurred during loading.
showLoadWarnings = true
#Set to true to use a combined DEPTH_STENCIL attachment instead of two separate ones.
useCombinedDepthStencilAttachment = false
#A config option mainly for developers. Logs out configuration values that do not have translations when running a client in a development environment.
logUntranslatedConfigurationWarnings = true
@@ -0,0 +1,21 @@
#Whether sub-levels should apply block shading dynamically
sub_level_dynamic_shading = true
#Whether sub-levels can occlude the water surface
sub_level_water_occlusion = true
#Whether sub-levels should cast a shadow on the world
sub_level_skylight_shadows = false
#Whether to draw loaded chunks on the client in the chunk debug renderer
debug_draw_loaded_chunks = false
#The distance back in game-ticks that the snapshot interpolation should operate
# Default: 1.5
# Range: 0.0 ~ 100.0
sub_level_snapshot_interpolation_delay_ticks = 1.5
#The renderer to use for sub-levels
#Allowed Values: SODIUM_REACHAROUND
sub_level_renderer = "SODIUM_REACHAROUND"
#The zoom sensitivity for sub-level camera types
# Default: 0.2
# Range: 0.0 ~ 100.0
sub_level_zoom_sensitivity = 0.2
#If Sable should attempt to establish a UDP connection with the server, to receive sub-level movement data over a UDP channel
attempt_udp_networking = true
@@ -0,0 +1,36 @@
#.
#Settings of Simulated Items
[items]
#.
#.
#When to display physics properties in block tooltips
#Allowed Values: ALWAYS, SHIFT, GOGGLES, SHIFT_GOGGLES, NEVER
displayProperties = "GOGGLES"
#.
#The sensitivity of scrolling when holding a sub-level with the Creative Physics Staff
# Default: 0.6000000238418579
# Range: 0.0 ~ 3.4028234663852886E38
physics_staff_scroll_sensitivity = 0.6000000238418579
#.
#The sensitivity of rotation when holding a sub-level with the Creative Physics Staff
# Default: 0.3499999940395355
# Range: 0.0 ~ 3.4028234663852886E38
physics_staff_rotate_sensitivity = 0.3499999940395355
#.
#If ponders that have new additions should display the "(New!)" text
show_new_ponders = true
#.
#Settings of Simulated Blocks
[block]
#.
#.
#Offsets the Steering Wheel GUI on the X axis.
# Default: 0
# Range: -1000 ~ 1000
steering_wheel_x_offset = 0
#.
#Offsets the Steering Wheel GUI on the Y axis.
# Default: 0
# Range: -1000 ~ 1000
steering_wheel_y_offset = 0
@@ -0,0 +1,20 @@
{
"enableHat": true,
"enableJacket": true,
"enableLeftSleeve": true,
"enableRightSleeve": true,
"enableLeftPants": true,
"enableRightPants": true,
"baseVoxelSize": 1.15,
"bodyVoxelWidthSize": 1.05,
"headVoxelSize": 1.18,
"renderDistanceLOD": 14,
"enableSkulls": true,
"enableSkullsItems": true,
"skullVoxelSize": 1.1,
"fastRender": true,
"compatibilityMode": true,
"irisCompatibilityMode": false,
"applySodiumWorkaround": false,
"firstPersonPixelScaling": 1.1
}
@@ -0,0 +1,6 @@
# This is the configuration file for Sodium.
#
# You can find information on editing this file and all the available options here:
# https://github.com/CaffeineMC/sodium/wiki/Configuration-File
#
# By default, this file will be empty except for this notice.
@@ -0,0 +1,30 @@
{
"quality": {
"weather_quality": "DEFAULT",
"leaves_quality": "DEFAULT",
"enable_vignette": true,
"use_closest_point_entity_sort": false
},
"advanced": {
"enable_memory_tracing": false,
"use_advanced_staging_buffers": true,
"cpu_render_ahead_limit": 3
},
"performance": {
"chunk_builder_threads": 0,
"chunk_build_defer_mode": "ALWAYS",
"animate_only_visible_textures": true,
"use_entity_culling": true,
"use_fog_occlusion": true,
"use_block_face_culling": true,
"use_no_error_g_l_context": true,
"quad_splitting_mode": "SAFE"
},
"notifications": {
"has_cleared_donation_button": false,
"has_seen_donation_prompt": false
},
"debug": {
"terrain_sorting_enabled": true
}
}
@@ -0,0 +1,219 @@
# Values for blocks can be defined as follows:
#
# By sound type:
# WOOD=1.0
#
# By block tag:
# \#minecraft\:logs=1.0
#
# By block ID:
# minecraft\:oak_log=1.0
# Amethyst (Sound Type)
AMETHYST=1.0
# Amethyst Cluster (Sound Type)
AMETHYST_CLUSTER=1.0
# Ancient Debris (Sound Type)
ANCIENT_DEBRIS=1.0
# Anvil (Sound Type)
ANVIL=1.0
# Azalea (Sound Type)
AZALEA=1.0
# Azalea Leaves (Sound Type)
AZALEA_LEAVES=1.0
# Bamboo (Sound Type)
BAMBOO=0.1
# Bamboo Sapling (Sound Type)
BAMBOO_SAPLING=0.1
# Bamboo Wood (Sound Type)
BAMBOO_WOOD=1.0
# Bamboo Wood Hanging Sign (Sound Type)
BAMBOO_WOOD_HANGING_SIGN=1.0
# Basalt (Sound Type)
BASALT=1.0
# Big Dripleaf (Sound Type)
BIG_DRIPLEAF=1.0
# Bone Block (Sound Type)
BONE_BLOCK=1.0
# Calcite (Sound Type)
CALCITE=1.0
# Candle (Sound Type)
CANDLE=1.0
# Cave Vines (Sound Type)
CAVE_VINES=1.0
# Chain (Sound Type)
CHAIN=0.0
# Cherry Leaves (Sound Type)
CHERRY_LEAVES=1.0
# Cherry Sapling (Sound Type)
CHERRY_SAPLING=1.0
# Cherry Wood (Sound Type)
CHERRY_WOOD=1.0
# Cherry Wood Hanging Sign (Sound Type)
CHERRY_WOOD_HANGING_SIGN=1.0
# Chiseled Bookshelf (Sound Type)
CHISELED_BOOKSHELF=1.0
# Copper (Sound Type)
COPPER=1.0
# Coral Block (Sound Type)
CORAL_BLOCK=1.0
# Crop (Sound Type)
CROP=0.0
# Decorated Pot (Sound Type)
DECORATED_POT=1.0
# Decorated Pot Cracked (Sound Type)
DECORATED_POT_CRACKED=1.0
# Deepslate (Sound Type)
DEEPSLATE=1.0
# Deepslate Bricks (Sound Type)
DEEPSLATE_BRICKS=1.0
# Deepslate Tiles (Sound Type)
DEEPSLATE_TILES=1.0
# Dripstone Block (Sound Type)
DRIPSTONE_BLOCK=1.0
# Flowering Azalea (Sound Type)
FLOWERING_AZALEA=1.0
# Froglight (Sound Type)
FROGLIGHT=1.0
# Frogspawn (Sound Type)
FROGSPAWN=1.0
# Fungus (Sound Type)
FUNGUS=0.0
# Gilded Blackstone (Sound Type)
GILDED_BLACKSTONE=1.0
# Glass (Sound Type)
GLASS=0.1
# Glow Lichen (Sound Type)
GLOW_LICHEN=0.0
# Grass (Sound Type)
GRASS=1.0
# Gravel (Sound Type)
GRAVEL=1.0
# Hanging Roots (Sound Type)
HANGING_ROOTS=1.0
# Hanging Sign (Sound Type)
HANGING_SIGN=1.0
# Hard Crop (Sound Type)
HARD_CROP=1.0
# Honey Block (Sound Type)
HONEY_BLOCK=0.5
# Ladder (Sound Type)
LADDER=0.0
# Lantern (Sound Type)
LANTERN=1.0
# Large Amethyst Bud (Sound Type)
LARGE_AMETHYST_BUD=0.0
# Lily Pad (Sound Type)
LILY_PAD=0.0
# Lodestone (Sound Type)
LODESTONE=1.0
# Mangrove Roots (Sound Type)
MANGROVE_ROOTS=1.0
# Medium Amethyst Bud (Sound Type)
MEDIUM_AMETHYST_BUD=0.0
# Metal (Sound Type)
METAL=1.0
# Moss (Sound Type)
MOSS=0.75
# Moss Carpet (Sound Type)
MOSS_CARPET=0.1
# Mud (Sound Type)
MUD=1.0
# Muddy Mangrove Roots (Sound Type)
MUDDY_MANGROVE_ROOTS=1.0
# Mud Bricks (Sound Type)
MUD_BRICKS=1.0
# Netherite Block (Sound Type)
NETHERITE_BLOCK=1.0
# Netherrack (Sound Type)
NETHERRACK=1.0
# Nether Bricks (Sound Type)
NETHER_BRICKS=1.0
# Nether Gold Ore (Sound Type)
NETHER_GOLD_ORE=1.0
# Nether Ore (Sound Type)
NETHER_ORE=1.0
# Nether Sprouts (Sound Type)
NETHER_SPROUTS=1.0
# Nether Wart (Sound Type)
NETHER_WART=1.0
# Nether Wood (Sound Type)
NETHER_WOOD=1.0
# Nether Wood Hanging Sign (Sound Type)
NETHER_WOOD_HANGING_SIGN=1.0
# Nylium (Sound Type)
NYLIUM=1.0
# Packed Mud (Sound Type)
PACKED_MUD=1.0
# Pointed Dripstone (Sound Type)
POINTED_DRIPSTONE=0.0
# Polished Deepslate (Sound Type)
POLISHED_DEEPSLATE=1.0
# Powder Snow (Sound Type)
POWDER_SNOW=0.1
# Rooted Dirt (Sound Type)
ROOTED_DIRT=1.0
# Roots (Sound Type)
ROOTS=0.0
# Sand (Sound Type)
SAND=1.0
# Scaffolding (Sound Type)
SCAFFOLDING=0.0
# Sculk (Sound Type)
SCULK=1.0
# Sculk Catalyst (Sound Type)
SCULK_CATALYST=1.0
# Sculk Sensor (Sound Type)
SCULK_SENSOR=1.0
# Sculk Shrieker (Sound Type)
SCULK_SHRIEKER=1.0
# Sculk Vein (Sound Type)
SCULK_VEIN=1.0
# Shroomlight (Sound Type)
SHROOMLIGHT=1.0
# Slime Block (Sound Type)
SLIME_BLOCK=1.0
# Small Amethyst Bud (Sound Type)
SMALL_AMETHYST_BUD=0.0
# Small Dripleaf (Sound Type)
SMALL_DRIPLEAF=0.0
# Snow (Sound Type)
SNOW=0.1
# Soul Sand (Sound Type)
SOUL_SAND=1.0
# Soul Soil (Sound Type)
SOUL_SOIL=1.0
# Spore Blossom (Sound Type)
SPORE_BLOSSOM=0.0
# Stem (Sound Type)
STEM=1.0
# Stone (Sound Type)
STONE=1.0
# Suspicious Gravel (Sound Type)
SUSPICIOUS_GRAVEL=1.0
# Suspicious Sand (Sound Type)
SUSPICIOUS_SAND=1.0
# Sweet Berry Bush (Sound Type)
SWEET_BERRY_BUSH=0.0
# Tuff (Sound Type)
TUFF=1.0
# Twisting Vines (Sound Type)
TWISTING_VINES=0.0
# Vine (Sound Type)
VINE=0.0
# Wart Block (Sound Type)
WART_BLOCK=1.0
# Weeping Vines (Sound Type)
WEEPING_VINES=0.0
# Wet Grass (Sound Type)
WET_GRASS=0.1
# Wood (Sound Type)
WOOD=1.0
# Wool (Sound Type)
WOOL=1.5
# Jukebox (Block)
minecraft\:jukebox=0.0
# Lava (Block)
minecraft\:lava=0.75
# Water (Block)
minecraft\:water=0.25
@@ -0,0 +1,213 @@
# Values for blocks can be defined as follows:
#
# By sound type:
# WOOD=1.0
#
# By block tag:
# \#minecraft\:logs=1.0
#
# By block ID:
# minecraft\:oak_log=1.0
# Amethyst (Sound Type)
AMETHYST=1.5
# Amethyst Cluster (Sound Type)
AMETHYST_CLUSTER=0.5
# Ancient Debris (Sound Type)
ANCIENT_DEBRIS=0.5
# Anvil (Sound Type)
ANVIL=0.5
# Azalea (Sound Type)
AZALEA=0.5
# Azalea Leaves (Sound Type)
AZALEA_LEAVES=0.5
# Bamboo (Sound Type)
BAMBOO=0.5
# Bamboo Sapling (Sound Type)
BAMBOO_SAPLING=0.5
# Bamboo Wood (Sound Type)
BAMBOO_WOOD=0.5
# Bamboo Wood Hanging Sign (Sound Type)
BAMBOO_WOOD_HANGING_SIGN=0.5
# Basalt (Sound Type)
BASALT=1.5
# Big Dripleaf (Sound Type)
BIG_DRIPLEAF=0.5
# Bone Block (Sound Type)
BONE_BLOCK=1.5
# Calcite (Sound Type)
CALCITE=1.5
# Candle (Sound Type)
CANDLE=0.5
# Cave Vines (Sound Type)
CAVE_VINES=0.5
# Chain (Sound Type)
CHAIN=0.5
# Cherry Leaves (Sound Type)
CHERRY_LEAVES=0.5
# Cherry Sapling (Sound Type)
CHERRY_SAPLING=0.5
# Cherry Wood (Sound Type)
CHERRY_WOOD=0.5
# Cherry Wood Hanging Sign (Sound Type)
CHERRY_WOOD_HANGING_SIGN=0.5
# Chiseled Bookshelf (Sound Type)
CHISELED_BOOKSHELF=0.5
# Copper (Sound Type)
COPPER=1.25
# Coral Block (Sound Type)
CORAL_BLOCK=0.2
# Crop (Sound Type)
CROP=0.5
# Decorated Pot (Sound Type)
DECORATED_POT=0.5
# Decorated Pot Cracked (Sound Type)
DECORATED_POT_CRACKED=0.5
# Deepslate (Sound Type)
DEEPSLATE=1.5
# Deepslate Bricks (Sound Type)
DEEPSLATE_BRICKS=1.5
# Deepslate Tiles (Sound Type)
DEEPSLATE_TILES=1.5
# Dripstone Block (Sound Type)
DRIPSTONE_BLOCK=0.5
# Flowering Azalea (Sound Type)
FLOWERING_AZALEA=0.5
# Froglight (Sound Type)
FROGLIGHT=0.5
# Frogspawn (Sound Type)
FROGSPAWN=0.5
# Fungus (Sound Type)
FUNGUS=0.5
# Gilded Blackstone (Sound Type)
GILDED_BLACKSTONE=0.5
# Glass (Sound Type)
GLASS=0.75
# Glow Lichen (Sound Type)
GLOW_LICHEN=0.5
# Grass (Sound Type)
GRASS=0.3
# Gravel (Sound Type)
GRAVEL=0.3
# Hanging Roots (Sound Type)
HANGING_ROOTS=0.5
# Hanging Sign (Sound Type)
HANGING_SIGN=0.5
# Hard Crop (Sound Type)
HARD_CROP=0.5
# Honey Block (Sound Type)
HONEY_BLOCK=0.1
# Ladder (Sound Type)
LADDER=0.5
# Lantern (Sound Type)
LANTERN=0.5
# Large Amethyst Bud (Sound Type)
LARGE_AMETHYST_BUD=0.5
# Lily Pad (Sound Type)
LILY_PAD=0.5
# Lodestone (Sound Type)
LODESTONE=0.5
# Mangrove Roots (Sound Type)
MANGROVE_ROOTS=0.5
# Medium Amethyst Bud (Sound Type)
MEDIUM_AMETHYST_BUD=0.5
# Metal (Sound Type)
METAL=1.25
# Moss (Sound Type)
MOSS=0.1
# Moss Carpet (Sound Type)
MOSS_CARPET=0.5
# Mud (Sound Type)
MUD=0.5
# Muddy Mangrove Roots (Sound Type)
MUDDY_MANGROVE_ROOTS=0.5
# Mud Bricks (Sound Type)
MUD_BRICKS=0.5
# Netherite Block (Sound Type)
NETHERITE_BLOCK=1.5
# Netherrack (Sound Type)
NETHERRACK=1.1
# Nether Bricks (Sound Type)
NETHER_BRICKS=1.5
# Nether Gold Ore (Sound Type)
NETHER_GOLD_ORE=1.1
# Nether Ore (Sound Type)
NETHER_ORE=1.1
# Nether Sprouts (Sound Type)
NETHER_SPROUTS=0.5
# Nether Wart (Sound Type)
NETHER_WART=0.5
# Nether Wood (Sound Type)
NETHER_WOOD=0.5
# Nether Wood Hanging Sign (Sound Type)
NETHER_WOOD_HANGING_SIGN=0.5
# Nylium (Sound Type)
NYLIUM=0.5
# Packed Mud (Sound Type)
PACKED_MUD=0.5
# Pointed Dripstone (Sound Type)
POINTED_DRIPSTONE=0.5
# Polished Deepslate (Sound Type)
POLISHED_DEEPSLATE=1.5
# Powder Snow (Sound Type)
POWDER_SNOW=0.5
# Rooted Dirt (Sound Type)
ROOTED_DIRT=0.5
# Roots (Sound Type)
ROOTS=0.5
# Sand (Sound Type)
SAND=0.2
# Scaffolding (Sound Type)
SCAFFOLDING=0.5
# Sculk (Sound Type)
SCULK=0.5
# Sculk Catalyst (Sound Type)
SCULK_CATALYST=0.5
# Sculk Sensor (Sound Type)
SCULK_SENSOR=0.5
# Sculk Shrieker (Sound Type)
SCULK_SHRIEKER=0.5
# Sculk Vein (Sound Type)
SCULK_VEIN=0.5
# Shroomlight (Sound Type)
SHROOMLIGHT=0.5
# Slime Block (Sound Type)
SLIME_BLOCK=0.5
# Small Amethyst Bud (Sound Type)
SMALL_AMETHYST_BUD=0.5
# Small Dripleaf (Sound Type)
SMALL_DRIPLEAF=0.5
# Snow (Sound Type)
SNOW=0.15
# Soul Sand (Sound Type)
SOUL_SAND=0.2
# Soul Soil (Sound Type)
SOUL_SOIL=0.2
# Spore Blossom (Sound Type)
SPORE_BLOSSOM=0.5
# Stem (Sound Type)
STEM=0.4
# Stone (Sound Type)
STONE=1.5
# Suspicious Gravel (Sound Type)
SUSPICIOUS_GRAVEL=0.5
# Suspicious Sand (Sound Type)
SUSPICIOUS_SAND=0.5
# Sweet Berry Bush (Sound Type)
SWEET_BERRY_BUSH=0.5
# Tuff (Sound Type)
TUFF=1.5
# Twisting Vines (Sound Type)
TWISTING_VINES=0.5
# Vine (Sound Type)
VINE=0.5
# Wart Block (Sound Type)
WART_BLOCK=0.5
# Weeping Vines (Sound Type)
WEEPING_VINES=0.5
# Wet Grass (Sound Type)
WET_GRASS=0.5
# Wood (Sound Type)
WOOD=0.4
# Wool (Sound Type)
WOOL=0.1
@@ -0,0 +1,99 @@
# Enables/Disables all sound effects
enabled=true
# Affects how quiet a sound gets based on distance
# Lower values mean distant sounds are louder
# This setting requires you to be in singleplayer or having the mod installed on the server
# 1.0 is the physically correct value
attenuation_factor=1.0
# The ray distance at which reverb starts
# 0.0 disables reverb attenuation
reverb_attenuation_distance=0.0
# The volume of simulated reverberations
reverb_gain=1.0
# The brightness of reverberation
# Higher values result in more high frequencies in reverberation
# Lower values give a more muffled sound to the reverb
reverb_brightness=1.0
# The distance of reverb relative to the sound distance
reverb_distance=1.5
# The amount of sound that will be absorbed when traveling through blocks
block_absorption=1.0
# Higher values mean smaller objects won't be considered as occluding
occlusion_variation=0.35
# The default amount of sound reflectance energy for all blocks
# Lower values result in more conservative reverb simulation with shorter reverb tails
# Higher values result in more generous reverb simulation with higher reverb tails
default_block_reflectivity=0.5
# The default amount of occlusion for all blocks
# Lower values will result in sounds being less muffled through walls
# Higher values mean sounds will be not audible though thicker walls
default_block_occlusion_factor=1.0
# Minecraft won't allow sounds to play past a certain distance
# This parameter is a multiplier for how far away a sound source is allowed to be in order for it to actually play
# This setting only takes affect in singleplayer worlds and when installed on the server
sound_distance_allowance=4.0
# A value controlling the amount that air absorbs high frequencies with distance
# A value of 1.0 is physically correct for air with normal humidity and temperature
# Higher values mean air will absorb more high frequencies with distance
# 0 disables this effect
air_absorption=1.0
# How much sound is filtered when the player is underwater
# 0.0 means no filter
# 1.0 means fully filtered
underwater_filter=0.9
# Whether sounds like cave, nether or underwater ambient sounds should have sound physics
evaluate_ambient_sounds=false
# The number of rays to trace to determine reverberation for each sound source
# More rays provides more consistent tracing results but takes more time to calculate
# Decrease this value if you experience lag spikes when sounds play
environment_evaluation_ray_count=32
# The number of rays bounces to trace to determine reverberation for each sound source
# More bounces provides more echo and sound ducting but takes more time to calculate
# Decrease this value if you experience lag spikes when sounds play
environment_evaluation_ray_bounces=4
# If sound hits a non-full-square side, block occlusion is multiplied by this
non_full_block_occlusion_factor=0.25
# The maximum amount of rays to determine occlusion
# Directly correlates to the amount of blocks between walls that are considered
max_occlusion_rays=16
# The amount at which occlusion is capped
max_occlusion=64.0
# If enabled, the occlusion calculation only uses one path between the sound source and the listener instead of 9
strict_occlusion=false
# Whether to try calculating where the sound should come from based on reflections
sound_direction_evaluation=true
# Skip redirecting non-occluded sounds (the ones you can see directly)
redirect_non_occluded_sounds=true
# If music discs or other longer sounds should be frequently reevaluated
update_moving_sounds=false
# The interval in ticks that moving sounds are reevaluated
# Lower values mean more frequent reevaluation but also more lag
# This option only takes effect if update_moving_sounds is enabled
sound_update_interval=5
# The maximum distance a sound can be processed
max_sound_processing_distance=512.0
# Disable level clone and cache. This will fall back to original main thread access.
# WARNING! Enabling this will cause instability and issues with other mods.
unsafe_level_access=false
# The radius of chunks to clone for level access
level_clone_range=4
# The maximum number of ticks to retain the cloned level in the cache
level_clone_max_retain_ticks=20
# The maximum distance a player can move from the cloned origin before invalidation
level_clone_max_retain_block_distance=16
# Enables debug logging
debug_logging=false
# Provides more information about occlusion in the logs
occlusion_logging=false
# Provides more information about the environment calculation in the logs
environment_logging=false
# Provides more information about how long computations take
performance_logging=false
# If enabled, the path of the sound will be rendered in game
render_sound_bounces=false
# If enabled, occlusion will be visualized in game
render_occlusion=false
# Enables/Disables sound effects for Simple Voice Chat audio
simple_voice_chat_integration=true
# Enables/Disables hearing your own echo with Simple Voice Chat
simple_voice_chat_hear_self=false
+142
View File
@@ -0,0 +1,142 @@
# Bench harness v2
`bench.py` drives the OnionBench Prism instance (`~/onion-bench/prism/instances/OnionBench`)
with **no input injection**: a generated world datapack runs the scenes and prints
markers, the harness only reads logs and the MangoHud CSV. `run_scene.py` is the legacy
xdotool harness, kept for reference. Stdlib only, Python 3.
## Loop
```sh
cd analysis/client-perf/harness
python3 bench.py boot --n 3 --variant C1 # cold launches -> results/boot.jsonl
python3 bench.py boot --n 3 --join # ... into the world (join time)
python3 bench.py session --variant C0 --plan s1 --reps 3 --label B0 # x3 sessions for B0
python3 bench.py session --variant C1 --plan s1,s2,s3,s4,s5 --label B2
python3 bench.py session --variant C1 --plan s5 --jvm J2 --heap 6144 --label B4
python3 analyze.py --baseline C0 --noise B0 [--label B2] # markdown table + verdicts
python3 -m unittest discover -s tests # offline tests (from this dir)
```
`session --dry-run` prints the datapack it would write and changes nothing. `bench.py meta`
prints the environment snapshot; `bench.py disarm` removes a leftover plan pack.
## Per-session instance changes (undone after every session)
```sh
python3 bench.py session --variant C1 --plan s1,s2,s4 --shader ComplementaryReimagined_r5.9.3.zip \
[--shader-options profile.txt] --label B7
python3 bench.py session --variant C1 --plan s1 --dh off
python3 bench.py session --variant C1 --plan s1 --disable-mods 'badoptimizations*,particle_core*'
python3 bench.py session --variant C1 --plan s2 --add-mods ~/trials/gnetum.jar
python3 bench.py session --variant C1 --plan s4 --world-zip ~/onion-bench/worlds/bench-world-v1-s4-1.zip
python3 bench.py restore # after a hard kill: replay .harness-pending.json
```
- `--shader NAME|off` (default `off`): `config/iris.properties` `enableShaders` /
`shaderPack`; NAME must exist in `shaderpacks/`. `--shader-options FILE` is copied to
`shaderpacks/<NAME>.txt` (Iris' per-pack options file; journaled whenever a shader is set).
- `--dh on|off` (default `on`): `[client.advanced.debugging] rendererMode` =
`DEFAULT` / `DISABLED` in `config/DistantHorizons.toml`, set after the variant (DH's
`quickEnableRendering` is GUI-only and maps to this key).
- `--disable-mods GLOB,..`: case-insensitive globs over `mods/*.jar`, renamed to
`.jar.disabled`; a glob that matches nothing is an error. `--add-mods PATH,..` copies jars
in (name collision = error). `mods_hash` is taken after these changes.
- `--world-zip PATH`: `saves/onionbench` -> `saves/onionbench.harness-bak`, zip extracted
(world at the zip root or under one top directory, needs `level.dat`); sha256 recorded.
- Every session first resets `options.txt` `resourcePacks` / `incompatibleResourcePacks`
to `["vanilla","mod_resources"]` / `[]` (variants may override); not undone.
- Crash safety: before the first change `session` writes `.minecraft/.harness-pending.json`
(write-ahead: each entry precedes its change; file backups in `.minecraft/.harness-backup/`).
The normal `finally` and `bench.py restore` run the same restore, newest entry first;
failed entries stay in the journal. `session` and `boot` refuse to start while it exists.
SIGTERM unwinds through the restore; SIGKILL leaves the journal for `bench.py restore`.
- Rows record `shader`, `shader_options(_sha256)`, `dh`, `disabled_mods`, `added_mods`,
`world_zip(_sha256)`, `resource_packs`, `restore_errors`; warn flags `gpu_dpm_auto` (RX
7800 XT `power_dpm_force_performance_level` != `high`), `server_running`,
`restore_failed`.
- **boot**: N launches; waits for `Game took N seconds` (or `joined the game` with
`--join`); outcome `ok` / `crash` / `timeout`. Crash = new file in `crash-reports/` or
`hs_err_pid*.log`, client java exited, or a log line with `Failed to create mod
instance`, a `/FATAL]` level tag, `Cowardly refusing`, or a crash-report header.
- **session**: applies the variant (`proposed-configs/apply_variant.py`), then pins
`options.txt` (no onboarding/narrator, no pause on focus loss, `maxFps:260`, vsync
off), hashes `config/` (minus `.bak`/`.tmp`/spark buffers) and `options.txt`, writes the
plan datapack, launches once into `onionbench`, tails `latest.log` until `BENCH_DONE`,
crash, exit or timeout, kills the client, removes the plan pack, then slices the CSV.
One JSON row per scene x rep window goes to `~/onion-bench/results/session.jsonl`;
every session's `latest.log` is archived under `~/onion-bench/results/logs/`.
- Only java processes whose command line contains `instances/OnionBench` are ever
signalled. The dedicated server (`unix_args.txt`) is only observed (running + %CPU).
- `--jvm J0..J3` / `--heap MB` rewrite `JvmArgs` / `MaxMemAlloc` in the bench
`instance.cfg` (first original saved as `instance.cfg.harness-orig`). Without them the
instance is left as is; the effective `-X*` flags are read from the running client.
## Datapack mechanism
The scene functions live in the permanent pack `saves/onionbench/datapacks/onion-bench`
(`bench:s1` ... `bench:tour_start`, source in `../bench/onion-bench`). Per session the
harness writes `datapacks/onion-bench-autoplan/` (pack_format 48, 1.21.1 `function/`
dirs) with a session id `pYYYYMMDDHHMMSS`:
- `#minecraft:load` -> `bench:autoplan/<sid>/load`: unless score `#<sid> bench_plan` is
set, poll every second (`schedule`) until `execute if entity @a`;
- `begin`: set the score (a `/reload` won't restart the plan), clear stale
`storm_tick` / tour schedules, `say BENCH_PLAN <sid> begin`, wait `--settle` s;
- chained `stepN` functions: `execute as @a[limit=1] at @s run function bench:<scene>`,
`say BENCH_MARK warm|start|end <scene> <rep>`, `schedule` the warm-up / capture ticks;
`say BENCH_DONE <sid>` at the end.
S5 is fixed by BENCH-MATRIX: `bench:s1`, 10 s, `bench:tour_start`, all 120 s captured
(warm-up ignored). S2s = S2 + `bench:storm_on` during warm-up and capture. Plans must
follow S1 -> S5 order (`--any-order` overrides). Function names contain the session id, so
schedules saved into `level.dat` by an autosave can never fire in a later session. The pack
is deleted in a `finally`, so normal play of the world is unaffected; after a hard kill
of the harness itself run `bench.py disarm`.
Markers are taken from the server log line (`[Server] BENCH_MARK ...`, ms timestamps).
The MangoHud CSV used is one created at/after launch (never a stale one); wall time =
file birth time (`stat -c %.9W`, else the name's timestamp) + `elapsed`, and the method is
recorded as `align`. Per window: n, p50/p95/p99/peak ms, avg fps (1000/mean frametime),
1 % low = 1000/mean of the worst 1 % frametimes, and the legacy fps-column 1 % low.
Flags: `vsync_suspect` (p50 within 3 % of 60 Hz or an xrandr refresh), `low_n`
(n < 500 or < half of duration x avg fps), `crashed`/`timeout` (window never ended),
`session_crash` (window ended, session crashed later), `backup_in_window` (backup or
autosave line from 5 s before start to end; warn only). `analyze.py` drops rows with
any non-warn flag unless `--include-invalid`.
Metadata per row: label, machine (`proxy-9900X-7800XT`), git sha + dirty, harness sha,
variant + config/options hashes, mods hash/count, Java version, cfg JVM args + effective
flags, MaxMemAlloc, kernel, CPU governor, per-GPU DPM level + sclk, MangoHud's GPU
string, MemAvailable before/after, load average, server running + %CPU, startup and
join seconds, block-atlas size, `level.dat` hash.
## Known limits
- The desktop is **KDE Plasma on Wayland** (kwin_wayland + Xwayland at `:1`). xdotool
only reaches Xwayland windows, `windowactivate --sync` hangs under kwin, focus-steal
prevention can drop keystrokes, and typed chat lands in whatever has focus. Hence no
input injection at all in v2.
- B7 shader profiles are automated: `gen_shader_profiles.py` resolves each pack's
`profile.NAME` (shaders.properties, Iris ProfileSet semantics) into
`shader-profiles/<pack-stem>.<profile>.txt`; matrix.toml passes it as `shader_options`
-> `--shader-options`. Re-run the generator (or `--check`) after a shaderpack update.
- Still manual: **S6** (JEI/GUI overlay needs a key press), checks needing F3
reads, `/sparkc` heap/GC reads (B1/B4).
- MangoHud's `log_duration=1800` in `~/.local/bin/mangohud-local` caps one CSV at 30 min;
`session` warns when a plan is longer (B5's 45-min decay run needs a split or a larger
value).
- The integrated server ticks the plan, so scene timing stretches if it lags; windows use
the marker wall times, not tick counts. The marker chat line is visible for ~10 s at
the start of each capture (identical across variants).
- **FTB Backups 3** backs up the world right after join (its index
`ftbbackups3/backups.json` doesn't parse, so it treats the world as never backed up)
and every `backup_timer` = 120 min. **Done (bench instance only):** `auto: false` in
the instance's `config/ftbbackups3-server.snbt`. Never edit the repo's `config/` for this.
- Stop the dedicated server (tmux `onion-mc`) while benching (owner-approved); it used up
to ~78 % of a core idle. Rows are flagged `server_running` if it is up.
- amdgpu DPM is `auto` (no root); rows are flagged `gpu_dpm_auto`. For tighter noise:
`echo high | sudo tee /sys/class/drm/card1/device/power_dpm_force_performance_level`.
- Scenes run in **spectator** mode (set by the plan) so elevated cameras don't fall.
+201
View File
@@ -0,0 +1,201 @@
#!/usr/bin/env python3
"""Summarise bench.py session rows and apply the BENCH-MATRIX win rule.
analyze.py [--file ~/onion-bench/results/session.jsonl] [--label B2]
[--baseline C0 --noise B0] [--include-invalid] [--exclude-backup]
Groups windows by (label, variant, scene). Each session contributes its median p99 and
median 1 % low (frametime-based); a group's value is the median of its session medians
and its spread is max - min of those. With --baseline/--noise, every non-baseline group
is compared with the baseline variant on the same scene: same label if present, else the
same block (a tagged label 'B3.dh-off' looks in 'B3', 'B3.baseline', then all 'B3.*'
pooled), else all labels pooled. A tagged session of the baseline variant itself is
compared with 'B3' / 'B3.baseline' when one exists: win = median p99 improves by more than the noise label's p99 spread
AND 1 % low does not worsen by more than its 1 % low spread; loss = p99 worsens beyond
the spread or 1 % low drops beyond it; otherwise tie (ties keep upstream defaults).
Rows flagged crashed / timeout / no_data / no_markers / low_n / vsync_suspect are
excluded unless --include-invalid. Stdlib only.
"""
import argparse
import json
import statistics
import sys
from pathlib import Path
INVALID = {"crashed", "timeout", "no_data", "no_markers", "low_n", "vsync_suspect",
"shader_not_loaded"}
DEFAULT_FILE = Path.home() / "onion-bench" / "results" / "session.jsonl"
def load_rows(path):
rows = []
for line in Path(path).read_text().splitlines():
line = line.strip()
if line:
rows.append(json.loads(line))
return [r for r in rows if r.get("kind", "window") == "window"]
def is_valid(row, exclude_backup=False):
flags = set(row.get("flags") or [])
if not row.get("metrics") or flags & INVALID:
return False
return not (exclude_backup and "backup_in_window" in flags)
def summarise(rows):
"""{(label, variant, scene): stats} from valid window rows."""
by_session = {}
for r in rows:
key = (r.get("label"), r["variant"], r["scene"])
by_session.setdefault(key, {}).setdefault(r["session"], []).append(r["metrics"])
out = {}
for key, sessions in by_session.items():
p99s = [statistics.median(m["p99_ms"] for m in ms) for ms in sessions.values()]
lows = [statistics.median(m["one_low_fps"] for m in ms) for ms in sessions.values()]
fps = [statistics.median(m["avg_fps"] for m in ms) for ms in sessions.values()]
out[key] = {
"sessions": len(sessions),
"windows": sum(len(ms) for ms in sessions.values()),
"p99": statistics.median(p99s), "p99_spread": max(p99s) - min(p99s),
"low": statistics.median(lows), "low_spread": max(lows) - min(lows),
"fps": statistics.median(fps),
}
return out
def noise_spread(summary, noise_label):
"""(p99 spread, 1 % low spread) of the noise block, worst case across its groups."""
groups = [v for (lab, _, _), v in summary.items() if lab == noise_label]
if not groups:
return None
return max(g["p99_spread"] for g in groups), max(g["low_spread"] for g in groups)
def verdict(base, cand, spread_p99, spread_low):
gain = base["p99"] - cand["p99"] # > 0: candidate's p99 is lower (better)
low_drop = base["low"] - cand["low"] # > 0: candidate's 1 % low is worse
if gain > spread_p99 and low_drop <= spread_low:
return "win"
if gain < -spread_p99 or low_drop > spread_low:
return "loss"
return "tie"
def block_of(label):
"""'B3.dh-off' -> 'B3'; untagged labels (and None) are their own block."""
return label.split(".", 1)[0] if label else label
def _pooled(rows, labels, baseline, scene, name):
pooled = [r for r in rows if r["variant"] == baseline and r["scene"] == scene
and (labels is None or r.get("label") in labels)]
if not pooled:
return None
return summarise([dict(r, label=name) for r in pooled]).get((name, baseline, scene))
def baseline_for(summary, rows, label, scene, baseline):
"""(baseline stats, where-label) for one candidate group, or (None, None).
Order: the candidate's own label; then its block - the untagged block label
('B3', for tagged candidates), '<block>.baseline', then every '<block>.*' label pooled; then all labels
pooled. A tagged session of the baseline variant itself (e.g. 'B3.dh-off' running
C1 against baseline C1) is compared only with 'B3' or 'B3.baseline'."""
block = block_of(label)
tagged = bool(label) and block != label
order = [label] + ([block] if tagged else []) + ([f"{block}.baseline"] if block else [])
for lab in order:
if (lab, baseline, scene) in summary:
return summary[(lab, baseline, scene)], lab
if block:
labels = {r.get("label") for r in rows
if r.get("label") == block or str(r.get("label")).startswith(block + ".")}
b = _pooled(rows, labels, baseline, scene, f"{block}.*")
if b is not None:
return b, f"{block}.*"
b = _pooled(rows, None, baseline, scene, "*")
return (b, "*") if b is not None else (None, None)
def self_baseline(summary, label, scene, baseline):
"""Baseline for a tagged group that runs the baseline variant itself, or None
(then the group *is* the baseline)."""
block = block_of(label)
if not label or block == label:
return None, None
for lab in (block, f"{block}.baseline"):
if lab != label and (lab, baseline, scene) in summary:
return summary[(lab, baseline, scene)], lab
return None, None
def table(summary, rows, baseline=None, noise=None):
spreads = noise_spread(summary, noise) if noise else None
full = summarise(rows) # baselines may live in labels filtered out of the table
hdr = ["label", "variant", "scene", "sess", "win#", "p99 ms", "p99 spr", "1%low",
"1%low spr", "avg fps"]
if baseline:
hdr += ["vs", "Δp99 ms", "Δ1%low", "verdict"]
out = ["| " + " | ".join(hdr) + " |", "|" + "---|" * len(hdr)]
for key in sorted(summary, key=lambda k: (str(k[0]), k[2], k[1])):
lab, var, scene = key
s = summary[key]
cells = [str(lab), var, scene, str(s["sessions"]), str(s["windows"]),
f"{s['p99']:.2f}", f"{s['p99_spread']:.2f}", f"{s['low']:.1f}",
f"{s['low_spread']:.1f}", f"{s['fps']:.0f}"]
if baseline:
b, blab = (self_baseline(full, lab, scene, baseline) if var == baseline
else baseline_for(full, rows, lab, scene, baseline))
if var == baseline and b is None:
cells += ["—", "", "", "baseline"]
else:
if b is None:
cells += ["—", "", "", "no baseline"]
elif spreads is None:
cells += [f"{baseline}@{blab}", f"{b['p99'] - s['p99']:+.2f}",
f"{s['low'] - b['low']:+.1f}", "no noise data"]
else:
cells += [f"{baseline}@{blab}", f"{b['p99'] - s['p99']:+.2f}",
f"{s['low'] - b['low']:+.1f}", verdict(b, s, *spreads)]
out.append("| " + " | ".join(cells) + " |")
if baseline and noise:
out.append("")
out.append(f"noise ({noise}): " + (
f"p99 spread {spreads[0]:.2f} ms, 1%low spread {spreads[1]:.1f} fps"
if spreads else "no rows - verdicts unavailable"))
return "\n".join(out)
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
ap.add_argument("--file", default=str(DEFAULT_FILE))
ap.add_argument("--label", action="append", help="only these labels (repeatable); "
"the noise label is always kept")
ap.add_argument("--baseline", help="baseline variant, e.g. C0")
ap.add_argument("--noise", help="label whose spread is the win threshold, e.g. B0")
ap.add_argument("--include-invalid", action="store_true")
ap.add_argument("--exclude-backup", action="store_true")
ap.add_argument("--since", help="only rows with ts >= this ISO time, e.g. 2026-10-16T22:24")
a = ap.parse_args(argv)
rows = load_rows(a.file)
if a.since:
rows = [r for r in rows if r.get("ts", "") >= a.since]
total = len(rows)
if not a.include_invalid:
rows = [r for r in rows if is_valid(r, a.exclude_backup)]
warn = sum(1 for r in rows if "backup_in_window" in (r.get("flags") or []))
shown = rows
if a.label:
keep = set(a.label) | ({a.noise} if a.noise else set())
shown = [r for r in rows if r.get("label") in keep]
if not shown:
print("no valid rows", file=sys.stderr)
return 1
print(table(summarise(shown), rows, a.baseline, a.noise))
print(f"\n{len(rows)}/{total} windows valid; {warn} with backup_in_window (warn only)")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+169
View File
@@ -0,0 +1,169 @@
# B14: spawn-tuning measurement
`b14.py` automates SPAWN-TUNING.md §B14. It compares the upstream spawn defaults with the
shipped partial files (`d80f85b`, `8e8483d`: `alexsmobs-common.toml`, `fowlplay.json5`,
`crittersandcompanions-common.json5`). Each comparison stands still for 10 minutes at a
river, a jungle and a warm ocean, and counts entities within 128 blocks.
It reuses `../bench.py` as a module: `launch`, `kill_client`, `LogTail`, `monitor`, the
`Journal` / `restore_pending` crash-safe restore, `apply_pins`, `apply_dh`, and
`zip_world_root`. It needs nothing beyond the Python 3 standard library.
## Loop
```sh
cd analysis/client-perf/harness
python3 b14/b14.py run --config defaults --dry-run # world, config plan, datapack
python3 b14/b14.py run --config defaults # ~32 min, all three biomes
python3 b14/b14.py run --config shipped
python3 b14/b14.py summary # SPAWN-TUNING §B14 verdict
```
- `--biome river,jungle,warm_ocean` (default `all`) runs the sites in that order in one
launch.
- `--minutes 10` (default) sets the stay at each site.
- Repeating runs (for example two of each config, interleaved
defaults → shipped → defaults → shipped) gives medians. The summary takes the median
across runs.
- Results go to `~/onion-bench/results/b14.jsonl`. Each count line is a `b14_count` row,
and each launch is a `b14_session` row (outcome, config check, git/mods hash, sites,
archived log at `results/logs/session-<sid>.log`).
- `b14.py restore` (or `bench.py restore`, which uses the same journal) undoes a
hard-killed run.
- `b14.py parse LOG` prints the count rows found in any archived log.
The script refuses to start in these cases:
- a restore journal is pending;
- a bench client is running;
- any `matrix.py run`, `bench.py session` or other `b14.py run` process exists. It shares
the OnionBench instance with the matrix, so **never run it while the matrix runs.**
## Design
### Throwaway world `saves/onionspawn`, one per run
- It contains only the bench world's `level.dat` (seed `onionbench`, gamerules, datapack
list), the `onion-bench` datapack (it has no load or tick tags) and `serverconfig/`.
- It has no `region/`, `entities/` or `data/` (no forceloads, no DH database). Every chunk
around a site is therefore generated during the run, under the config being measured.
That covers both kinds of spawning: the initial creatures placed at world generation
and natural spawning.
- The seed is the same, so the terrain is identical between runs. The world is journaled
as a `tree` entry and deleted on restore. The bench world `saves/onionbench` is never
touched.
### Sites are pre-located offline (`b14.py locate`)
`/locate biome` can't be used from a datapack. Functions run with suppressed output, and
`execute store` only captures the distance, not the coordinates.
So `locate` parses the overworld region files in `bench-world-v1.zip` with a small NBT
reader (`nbt.py`):
- It reads each chunk's biome palette at the surface, using the `MOTION_BLOCKING_NO_LEAVES`
heightmap in full chunks and `WORLD_SURFACE_WG` in proto chunks.
- For each target biome it picks the chunk whose 5×5 neighbourhood has the largest share
of that biome.
- Sites must be at least 192 blocks from the world spawn and the saved player position
(spawn chunks and the first-load area stay outside the 128-block count sphere), and at
least 320 blocks from each other (the count spheres don't overlap).
Biomes depend only on the seed, so the coordinates are valid for any world with this seed
and mod set. They are committed in `sites.json` (from zip sha256
`144caf5f…`, seed −306676217):
| biome | x y z | biome at site | share in 5×5 chunks |
|---|---|---|---|
| river | 904 63 88 | minecraft:river | 0.37 |
| jungle | −8 107 −201 | minecraft:jungle | 0.52 |
| warm_ocean | −200 63 56 | minecraft:warm_ocean | 0.75 |
Every count line carries `biome_ok`, an in-game `execute if biome` check at the player's
feet (`minecraft:river`, `#minecraft:is_jungle`, `minecraft:warm_ocean`). If the
regenerated terrain doesn't match the offline lookup, the summary marks that biome
invalid instead of silently measuring the wrong place.
### Config variants (journaled, bench instance only)
- `defaults` deletes the three files. Each mod regenerates its upstream defaults on load
(the same as "these three config files removed" in SPAWN-TUNING). They aren't in
`config/defaultoptions/extra/`, so Default Options can't put them back.
- `shipped` writes `git show <rev>:config/<file>` (default `HEAD`), meaning the committed
partials, not the working tree's boot re-saves.
- After the run, and before the restore, the files the mods re-saved are read back and
checked against the expected values. A mismatch fails the run (`config_check.ok=false`),
and the summary ignores that run. The expected upstream defaults were verified from the
jars:
- Alex's Mobs: the `# Default:` comments in its re-saved TOML: gazelle 40, capuchin 28,
elephant 30, straddler 70, rocky roller 60, underminer 50, blobfish 30,
mudskipper 28, caiman 29.
- Fowl Play: `FPConfig.<init>` bytecode: duck 4/max 6, goose 4/4, gull 5/8,
sparrow 20/6.
- Critters and Companions: `CACCommonConfig$SpawningConfig.<init>`: sea bunny warm
ocean 32 (max 4), other oceans 16; leaf insect jungle 14, forest 6; dragonfly lush 10.
### Datapack (`saves/onionspawn/datapacks/b14-autoplan`, pack_format 48)
- **Start-up:** a load tag polls once a second until a player exists. Then it pins the
rules that matter for spawning:
- `gamemode creative` (it counts for spawning and despawning, where spectators don't,
and the player can't die);
- `doMobSpawning true`;
- the daylight, weather, patrol, trader, insomnia and warden rules;
- time 6000 and clear weather.
- **Per site:**
1. Teleport above the site.
2. 5 s later: clear a 3×3×3 pocket, put a barrier at y−1 (a platform over water), and
teleport onto it.
3. Count at t=0 (+10 s), then every 60 s up to t=10. Each count re-asserts the barrier
and the teleport, so the player never drifts.
- **Each count** runs `execute store result score #<alias> b14 if entity
@e[type=<id>,distance=..128]` as the player, for the 22 tuned species and 10 vanilla
spot-check species. It also stores:
- `total` = `@e[type=!player,distance=..128]`, as SPAWN-TUNING defines it;
- `nonitem`, which also excludes items and XP orbs;
- `tuned`, the sum of the tuned species.
- **Output:** a `tellraw` prints one line, which the client logs as `[System] [CHAT]`:
`B14 <sid> <biome> t=<min> biome_ok=1 total=N nonitem=N tuned=N alexsmobs:gazelle=N ...`.
- **Errors:** `Failed to load function b14:` in the log (an unknown entity id makes the
whole function unloadable) aborts the run as `datapack_error`.
### Entity ids
All are checked against the jars in the bench instance. Each has an `entity.<mod>.<name>`
lang key, and is either in the mod's registry class strings (`CACEntities`) or has a
`loot_table/entities/<name>.json`.
- **Alex's Mobs** (`alexsmobs-1.22.17`): `gazelle`, `capuchin_monkey`, `elephant`,
`straddler`, `rocky_roller`, `underminer`, `blobfish`, `mudskipper`, `caiman`.
- **Fowl Play** (`fowlplay-1.2.3`): `duck`, `goose`, `gull`, `sparrow`.
- **Critters and Companions** (`crittersandcompanions 2.7.0`): `leaf_insect`, `sea_bunny`,
`dragonfly`, `ladybug`, `stag_beetle`, `roly_poly`, `snail`, `stick_bug`, `weevil`.
### Verdict (`summary`, SPAWN-TUNING §B14)
- **Per biome**, using the final count (t = minutes):
- **keep** if the median `total` drops under shipped *and* no tuned species that
appeared at any minute under defaults is absent under shipped;
- **back off** (listing the species) if one disappears;
- **no drop** otherwise.
- **Warnings:** `rare_warn` flags species whose shipped maximum is under 25 % of the
defaults maximum (when defaults saw at least 4). `vanilla_gone` flags cows or sheep
that vanished.
- **Overall:** `keep` only if all three biomes keep.
- **Excluded runs:** runs with a failed config check or a non-ok outcome are ignored. A
biome with `biome_ok != 1` is `invalid`.
## Manual prep and limits
- **Prep:** nothing beyond what's committed. `sites.json` is already generated. Re-run
`b14.py locate --out b14/sites.json` only if the seed, the source zip or the
biome-affecting mods (Terralith, Tectonic) change.
- **Thin biome coverage:** the source world has only about 2300 chunks with final biomes,
so the river and jungle patches are small (37 % and 52 % of the 5×5 neighbourhood). The
128-block count sphere also covers neighbouring biomes. This is the same for both
configs, but it lowers the absolute counts.
- **Better sites (optional):** on `~/onion-bench/server-bench` (seed `onionbench`), run
`/locate biome minecraft:river` (and jungle, warm_ocean), check the share around each
result, and put the coordinates into `sites.json` by hand. The `y` value is the first
free block above the surface or the water.
- **Jungle site from a proto chunk:** its `y` is the height before trees are placed. The
3×3×3 air pocket keeps a tree from burying the player, and `biome_ok` still guards the
biome.
- **Hostile mobs in `total`:** difficulty stays as in `level.dat` (normal), so hostiles
from caves within 128 blocks count toward `total`. `tuned` and `nonitem` are recorded
separately for reading the result.
- **Machine:** this is proxy-machine data (singleplayer, simulation distance 8 pinned).
Spawning is server logic, so the GPU doesn't matter, but CPU load does affect chunk
generation speed.
+855
View File
@@ -0,0 +1,855 @@
#!/usr/bin/env python3
"""B14 spawn-tuning measurement (SPAWN-TUNING.md §B14), automated.
b14.py locate [--zip bench-world-v1.zip] [--out sites.json] # offline, once
b14.py run --config {defaults,shipped} [--biome river,jungle,warm_ocean]
[--minutes 10] [--dry-run]
b14.py summary [--file ~/onion-bench/results/b14.jsonl]
b14.py parse LOG # debug: count lines
b14.py restore # = bench.py restore
One `run` = one client launch into a throwaway world `saves/onionspawn` that holds only
the bench world's level.dat (seed `onionbench`, gamerules, datapack list), so every
chunk around the sites is generated fresh under the config being measured. A generated
datapack sets doMobSpawning true + Creative, parks the player on a barrier block at each
pre-located site (sites.json), and every 60 s prints a parsable chat line
`B14 <sid> <biome> t=<min> biome_ok=1 total=N <entity id>=N ...` counted within 128
blocks. The spawn config variant is journaled through bench.Journal, so `bench.py
restore` undoes a hard-killed run. Stdlib only.
"""
import argparse
import json
import os
import re
import signal
import statistics
import subprocess
import sys
import time
import zipfile
from pathlib import Path
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))
sys.path.insert(0, str(HERE.parent))
import bench # noqa: E402 (harness module: launch, Journal, LogTail, monitor, ...)
import nbt # noqa: E402
WORLD = "onionspawn"
PACK = "b14-autoplan"
SITES_FILE = HERE / "sites.json"
SOURCE_ZIP = bench.BENCH / "worlds" / "bench-world-v1.zip"
RESULTS_FILE = bench.RESULTS / "b14.jsonl"
# The three shipped partial files (d80f85b, 8e8483d). `defaults` deletes them so each mod
# regenerates its upstream defaults; `shipped` writes the committed partials.
SPAWN_FILES = ["alexsmobs-common.toml", "fowlplay.json5", "crittersandcompanions-common.json5"]
# Tuned species (verified against the jars' lang keys + registries, see README).
TUNED = [
("am_gazelle", "alexsmobs:gazelle"),
("am_capuchin", "alexsmobs:capuchin_monkey"),
("am_elephant", "alexsmobs:elephant"),
("am_straddler", "alexsmobs:straddler"),
("am_rocky_roller", "alexsmobs:rocky_roller"),
("am_underminer", "alexsmobs:underminer"),
("am_blobfish", "alexsmobs:blobfish"),
("am_mudskipper", "alexsmobs:mudskipper"),
("am_caiman", "alexsmobs:caiman"),
("fp_duck", "fowlplay:duck"),
("fp_goose", "fowlplay:goose"),
("fp_gull", "fowlplay:gull"),
("fp_sparrow", "fowlplay:sparrow"),
("cac_leaf_insect", "crittersandcompanions:leaf_insect"),
("cac_sea_bunny", "crittersandcompanions:sea_bunny"),
("cac_dragonfly", "crittersandcompanions:dragonfly"),
("cac_ladybug", "crittersandcompanions:ladybug"),
("cac_stag_beetle", "crittersandcompanions:stag_beetle"),
("cac_roly_poly", "crittersandcompanions:roly_poly"),
("cac_snail", "crittersandcompanions:snail"),
("cac_stick_bug", "crittersandcompanions:stick_bug"),
("cac_weevil", "crittersandcompanions:weevil"),
]
# Vanilla spot-check (SPAWN-TUNING: "cows, sheep still spawn normally") + biome natives.
VANILLA = [
("mc_cow", "minecraft:cow"), ("mc_sheep", "minecraft:sheep"), ("mc_pig", "minecraft:pig"),
("mc_chicken", "minecraft:chicken"), ("mc_parrot", "minecraft:parrot"),
("mc_ocelot", "minecraft:ocelot"), ("mc_tropical_fish", "minecraft:tropical_fish"),
("mc_cod", "minecraft:cod"), ("mc_squid", "minecraft:squid"),
("mc_dolphin", "minecraft:dolphin"),
]
# Species SPAWN-TUNING §B14 names as the point of each biome (reported first).
FOCUS = {
"river": ["fowlplay:duck", "fowlplay:gull"],
"jungle": ["alexsmobs:capuchin_monkey", "crittersandcompanions:leaf_insect"],
"warm_ocean": ["crittersandcompanions:sea_bunny"],
}
BIOMES = {
"river": {"match": {"minecraft:river"}, "check": "minecraft:river"},
"jungle": {"match": {"minecraft:jungle", "minecraft:sparse_jungle",
"minecraft:bamboo_jungle"}, "check": "#minecraft:is_jungle"},
"warm_ocean": {"match": {"minecraft:warm_ocean"}, "check": "minecraft:warm_ocean"},
}
# Upstream defaults of the keys we check after load (SPAWN-TUNING "default -> new").
EXPECT_DEFAULTS = {
"alexsmobs-common.toml": {
"gazelleSpawnWeight": 40, "capuchinMonkeySpawnWeight": 28, "elephantSpawnWeight": 30,
"straddlerSpawnWeight": 70, "rockyRollerSpawnWeight": 60, "underminerSpawnWeight": 50,
"blobfishSpawnWeight": 30, "mudskipperSpawnWeight": 28, "caimanSpawnWeight": 29},
"fowlplay.json5": {
"duckSpawnWeight": 4, "duckMaxGroupSize": 6, "gooseSpawnWeight": 4,
"gooseMaxGroupSize": 4, "gullSpawnWeight": 5, "gullMaxGroupSize": 8,
"sparrowSpawnWeight": 20, "sparrowMaxGroupSize": 6},
"crittersandcompanions-common.json5": {
"sea_bunny@minecraft:warm_ocean": 32, "leaf_insect@#c:is_jungle": 14,
"leaf_insect@#c:is_forest": 6, "dragonfly@#c:is_lush": 10},
}
COUNT_RE = re.compile(r"\bB14 (?P<sid>b14-\d+) (?P<biome>[a-z_]+) t=(?P<t>\d+) (?P<rest>.*)$")
DONE_RE = re.compile(r"B14_DONE (b14-\d+)")
FUNC_FAIL_RE = re.compile(r"Failed to load function b14:|Unknown function b14:")
def log(msg):
print(f"[b14] {msg}", flush=True)
# ---------------------------------------------------------------- config variants
def shipped_texts(rev="HEAD", repo=bench.REPO):
out = {}
for name in SPAWN_FILES:
r = subprocess.run(["git", "-C", str(repo), "show", f"{rev}:config/{name}"],
capture_output=True, text=True)
if r.returncode != 0:
raise RuntimeError(f"git show {rev}:config/{name} failed: {r.stderr.strip()}")
out[name] = r.stdout
return out
def render_variant(config, shipped):
"""{file name: text to write, or None = delete (mod regenerates defaults)}."""
if config == "defaults":
return {name: None for name in SPAWN_FILES}
if config == "shipped":
return {name: shipped[name] for name in SPAWN_FILES}
raise ValueError(f"unknown config {config!r}")
def _cac_weight(text, mob, spec):
m = re.search(rf'"?{re.escape(mob)}"?\s*:\s*\[(.*?)\]', text, re.S)
if not m:
return None
for entry in m.group(1).split("}"):
if f'"{spec}"' in entry:
w = re.search(r'"?weight"?\s*:\s*(-?\d+)', entry)
return int(w.group(1)) if w else None
return None
def extract_values(name, text, keys):
"""Values of `keys` from a partial or mod-re-saved spawn file (None = absent)."""
out = {}
for k in keys:
if name.endswith(".toml"):
m = re.search(rf"^\s*{re.escape(k)}\s*=\s*(-?\d+)", text, re.M)
out[k] = int(m.group(1)) if m else None
elif "@" in k:
mob, spec = k.split("@", 1)
out[k] = _cac_weight(text, mob, spec)
else:
m = re.search(rf'"?{re.escape(k)}"?\s*:\s*(-?\d+)', text)
out[k] = int(m.group(1)) if m else None
return out
def expected_values(config, shipped):
if config == "defaults":
return {n: dict(v) for n, v in EXPECT_DEFAULTS.items()}
return {n: extract_values(n, shipped[n], EXPECT_DEFAULTS[n]) for n in SPAWN_FILES}
def check_config(game, config, shipped):
"""Compare what the mods re-saved after load with what the variant should produce."""
want = expected_values(config, shipped)
got, mismatch = {}, []
for name in SPAWN_FILES:
p = Path(game) / "config" / name
if not p.is_file() and config == "defaults":
got[name] = dict(want[name]) # never written: the mod ran on its defaults
continue
text = p.read_text(errors="replace") if p.is_file() else ""
got[name] = extract_values(name, text, want[name])
for k, v in want[name].items():
if got[name][k] != v:
mismatch.append(f"{name}:{k} want {v} got {got[name][k]}")
return {"ok": not mismatch, "mismatch": mismatch, "values": got}
def apply_spawn_config(game, journal, files):
for name, text in files.items():
rel = f"config/{name}"
journal.backup_file(rel)
p = Path(game) / rel
if text is None:
if p.exists():
p.unlink()
else:
p.write_text(text)
def config_diff(game, files, shipped):
"""Key-level plan of the config change (for --dry-run): checked keys now -> after."""
out = []
for name, text in files.items():
p = Path(game) / "config" / name
cur = p.read_text(errors="replace") if p.is_file() else None
keys = EXPECT_DEFAULTS[name]
now = extract_values(name, cur, keys) if cur else {}
if text is None:
out.append(f"--- config/{name}: DELETE ({len(cur)} bytes now)" if cur
else f"--- config/{name}: DELETE (absent now)")
out.append(" mod regenerates upstream defaults on load")
after = keys
else:
out.append(f"--- config/{name}: WRITE shipped partial ({len(text)} bytes)")
after = extract_values(name, shipped[name], keys)
for k in keys:
mark = "" if now.get(k) == after[k] else " <- changes"
out.append(f" {k}: {now.get(k)} -> {after[k]}{mark}")
if text is not None:
out.extend(" | " + ln for ln in text.splitlines())
return "\n".join(out)
# ---------------------------------------------------------------- datapack
def _score(name):
return {"score": {"name": f"#{name}", "objective": "b14"}}
def count_function():
lines = ["# B14 entity counts around the executing position (the player)",
"scoreboard players set #tuned b14 0"]
for alias, eid in TUNED + VANILLA:
lines.append(f"execute store result score #{alias} b14 "
f"if entity @e[type={eid},distance=..128]")
for alias, _ in TUNED:
lines.append(f"scoreboard players operation #tuned b14 += #{alias} b14")
lines.append("execute store result score #total b14 "
"if entity @e[type=!minecraft:player,distance=..128]")
lines.append("execute store result score #nonitem b14 if entity "
"@e[type=!minecraft:player,type=!minecraft:item,"
"type=!minecraft:experience_orb,distance=..128]")
return "\n".join(lines) + "\n"
def tellraw(sid, biome, t):
parts = ["", {"text": f"B14 {sid} {biome} t={t} biome_ok="}, _score("biome_ok")]
for key, alias in [("total", "total"), ("nonitem", "nonitem"), ("tuned", "tuned")]:
parts += [{"text": f" {key}="}, _score(alias)]
for alias, eid in TUNED + VANILLA:
parts += [{"text": f" {eid}="}, _score(alias)]
return "tellraw @a " + json.dumps(parts, separators=(",", ":"))
def build_timeline(sid, sites, minutes, settle_s, arrive_s=10, gen_s=5):
"""[(commands, delay_ticks_to_next)] for the unrolled plan."""
ns = f"b14:{sid}"
tl = []
for biome, s in sites:
x, y, z = s["x"], s["y"], s["z"]
pos = f"{x + 0.5} {y} {z + 0.5}"
# proto-chunk sites come from the pre-feature heightmap: clear a 3x3x3 pocket in
# case a tree landed on the column, then stand on a barrier (also over water).
hold = [f"fill {x - 1} {y} {z - 1} {x + 1} {y + 2} {z + 1} minecraft:air",
f"setblock {x} {y - 1} {z} minecraft:barrier",
f"execute in minecraft:overworld run tp @a {pos} 0 0"]
tl.append(([f"execute in minecraft:overworld run tp @a {x + 0.5} {y + 2} {z + 0.5} 0 0"],
gen_s * 20))
tl.append((hold + [f"say B14_ARRIVE {sid} {biome} {x} {y} {z}"], arrive_s * 20))
for t in range(minutes + 1):
cmds = hold + [
f"execute as @a[limit=1] at @s run function {ns}/count",
f"execute as @a[limit=1] at @s store success score #biome_ok b14 "
f"if biome ~ ~ ~ {BIOMES[biome]['check']}",
tellraw(sid, biome, t)]
tl.append((cmds, 1200 if t < minutes else 20))
tl.append(([f"say B14_DONE {sid}"], 0))
return tl
def plan_seconds(n_sites, minutes, settle_s, arrive_s=10, gen_s=5):
return settle_s + n_sites * (gen_s + arrive_s + minutes * 60 + 1)
def generate_datapack(sid, sites, minutes, settle_s):
"""{relative path: text}; namespace b14, functions under b14:<sid>/."""
ns, holder = f"b14:{sid}", "#armed"
base = f"data/b14/function/{sid}"
files = {
"pack.mcmeta": json.dumps({"pack": {
"pack_format": 48,
"description": f"Onion B14 spawn plan {sid} (generated by b14.py; "
f"throwaway world)"}}) + "\n",
"data/minecraft/tags/function/load.json": json.dumps({"values": [f"{ns}/load"]}) + "\n",
f"{base}/load.mcfunction":
"scoreboard objectives add b14 dummy\n"
f"execute unless score {holder} b14 matches 1.. run schedule function {ns}/poll 20t\n",
f"{base}/poll.mcfunction":
f"execute if entity @a run function {ns}/begin\n"
f"execute unless entity @a run schedule function {ns}/poll 20t\n",
f"{base}/begin.mcfunction":
f"scoreboard players set {holder} b14 1\n"
# Creative: counts as a spawning/despawning player (spectators don't), and
# can't die. Every rule that changes spawning is pinned, not inherited.
"gamemode creative @a\n"
"gamerule doMobSpawning true\n"
"gamerule doDaylightCycle false\n"
"gamerule doWeatherCycle false\n"
"gamerule doPatrolSpawning false\n"
"gamerule doTraderSpawning false\n"
"gamerule doInsomnia false\n"
"gamerule doWardenSpawning false\n"
"time set 6000\n"
"weather clear\n"
f"say B14_PLAN {sid} begin\n"
f"schedule function {ns}/step0 {max(1, settle_s * 20)}t\n",
f"{base}/count.mcfunction": count_function(),
}
tl = build_timeline(sid, sites, minutes, settle_s)
for i, (cmds, delay) in enumerate(tl):
body = list(cmds)
if delay > 0 and i + 1 < len(tl):
body.append(f"schedule function {ns}/step{i + 1} {delay}t")
files[f"{base}/step{i}.mcfunction"] = "\n".join(body) + "\n"
return files
# ---------------------------------------------------------------- world
def world_members(zip_path):
"""Members copied into the throwaway world: level.dat, the bench datapack (it is in
level.dat's enabled list; no load/tick tags) and serverconfig/. No chunks/entities/
data/ (forceloads, DH db) so every chunk is generated under the measured config."""
prefix = bench.zip_world_root(zip_path)
with zipfile.ZipFile(zip_path) as z:
names = z.namelist()
keep = []
for n in names:
rel = n[len(prefix):]
if n.endswith("/") or not n.startswith(prefix):
continue
if rel == "level.dat" or rel.startswith(("datapacks/onion-bench/", "serverconfig/")):
keep.append((n, rel))
if not any(rel == "level.dat" for _, rel in keep):
raise ValueError(f"{zip_path}: no level.dat")
return keep
def build_world(game, journal, zip_path, files):
root = Path(game) / "saves" / WORLD
if root.exists():
raise ValueError(f"{root} exists (stale B14 world?) - run `b14.py restore` or remove it")
members = world_members(zip_path)
journal.record({"op": "tree", "path": f"saves/{WORLD}"})
with zipfile.ZipFile(zip_path) as z:
for n, rel in members:
dst = root / rel
dst.parent.mkdir(parents=True, exist_ok=True)
dst.write_bytes(z.read(n))
write_pack(root, files)
return root
def write_pack(world_root, files):
d = Path(world_root) / "datapacks" / PACK
for rel, text in files.items():
p = d / rel
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(text)
# ---------------------------------------------------------------- locate (offline)
# Chunk statuses whose biome palette is final (before "biomes" it is a placeholder).
BIOME_STATUSES = {"biomes", "noise", "surface", "carvers", "features", "initialize_light",
"light", "spawn", "full"}
def _chunk_info(chunk):
"""Surface samples of one chunk, or None. Full chunks use MOTION_BLOCKING_NO_LEAVES
(ground under trees, water surface); proto chunks the worldgen WORLD_SURFACE_WG."""
status = str(chunk.get("Status", "")).split(":")[-1]
if status not in BIOME_STATUSES:
return None
hm = nbt.heightmap(chunk) or nbt.heightmap(chunk, "WORLD_SURFACE_WG")
if hm is None:
return None
ys = {}
for y in hm:
ys[y] = ys.get(y, 0) + 1
mode_y = max(ys, key=lambda k: (ys[k], -k))
samples = []
for lx, lz in ((2, 2), (2, 10), (10, 2), (10, 10), (8, 8)):
y = hm[lz * 16 + lx]
samples.append(nbt.biome_at(chunk, lx, y - 1, lz))
col = None # a column at the modal height (ground, not a trunk or hole), near centre
best = None
for lz in range(16):
for lx in range(16):
if hm[lz * 16 + lx] == mode_y:
d = (lx - 8) ** 2 + (lz - 8) ** 2
if best is None or d < best:
best, col = d, (lx, lz)
centre = nbt.biome_at(chunk, col[0], mode_y - 1, col[1])
return {"samples": samples, "y": mode_y, "col": col, "centre": centre,
"full": status == "full"}
def scan_world(zip_path, stride=1):
"""{(cx, cz): info} for every fully generated overworld chunk in the zip."""
prefix = bench.zip_world_root(zip_path)
out = {}
with zipfile.ZipFile(zip_path) as z:
regions = [i for i in z.infolist()
if i.filename.startswith(prefix + "region/") and i.filename.endswith(".mca")
and i.file_size > 8192]
for info in regions:
rx, rz = (int(v) for v in info.filename.rsplit("/", 1)[1].split(".")[1:3])
for lx, lz, chunk in nbt.region_chunks(z.read(info.filename)):
if stride > 1 and (lx % stride or lz % stride):
continue
ci = _chunk_info(chunk)
if ci:
out[(rx * 32 + lx, rz * 32 + lz)] = ci
return out
def pick_sites(chunks, avoid, min_gap=320, avoid_gap=192, radius=2):
"""Best chunk per target biome: centre column in the biome, maximal share of the
biome in the (2r+1)^2 neighbourhood; far from `avoid` points and from each other."""
chosen = {}
for biome, spec in BIOMES.items():
match = spec["match"]
cands = []
for (cx, cz), ci in chunks.items():
if ci["centre"] not in match:
continue
hit = tot = forest = 0
for dx in range(-radius, radius + 1):
for dz in range(-radius, radius + 1):
n = chunks.get((cx + dx, cz + dz))
if not n:
continue
for b in n["samples"]:
tot += 1
hit += b in match
forest += bool(b) and "forest" in b
if tot < 5 * 9:
continue # fewer than 9 known chunks around it
score = hit / tot + (0.5 * forest / tot if biome == "river" else 0.0) \
+ (0.05 if ci["full"] else 0.0)
x, z = cx * 16 + ci["col"][0], cz * 16 + ci["col"][1]
cands.append((score, x, ci["y"], z, hit / tot, ci["full"], ci["centre"]))
for score, x, y, z, frac, full, centre in sorted(cands, reverse=True):
pts = list(avoid) + [(s["x"], s["z"]) for s in chosen.values()]
gaps = [avoid_gap] * len(avoid) + [min_gap] * len(chosen)
if all((x - px) ** 2 + (z - pz) ** 2 >= g * g for (px, pz), g in zip(pts, gaps)):
chosen[biome] = {"x": x, "y": y, "z": z, "biome": centre,
"frac": round(frac, 3), "score": round(score, 3),
"full_chunk": full, "candidates": len(cands)}
break
return chosen
def avoid_points(zip_path):
prefix = bench.zip_world_root(zip_path)
with zipfile.ZipFile(zip_path) as z:
_, ld = nbt.load_gzip(z.read(prefix + "level.dat"))
d = ld.get("Data", {})
pts = [(d.get("SpawnX", 0), d.get("SpawnZ", 0))]
pos = (d.get("Player") or {}).get("Pos")
if pos:
pts.append((int(pos[0]), int(pos[2])))
seed = (d.get("WorldGenSettings") or {}).get("seed")
return pts, seed
def cmd_locate(a):
t0 = time.time()
chunks = scan_world(a.zip, a.stride)
avoid, seed = avoid_points(a.zip)
sites = pick_sites(chunks, avoid)
hist = {}
for ci in chunks.values():
hist[ci["centre"]] = hist.get(ci["centre"], 0) + 1
log(f"scanned {len(chunks)} chunks with final biomes in {time.time() - t0:.0f}s; avoid {avoid}")
for b, n in sorted(hist.items(), key=lambda kv: -kv[1])[:25]:
print(f" {n:6d} {b}")
missing = sorted(set(BIOMES) - set(sites))
doc = {"source_zip": str(a.zip), "source_sha256": bench.sha256_file(a.zip),
"seed": seed, "avoid": avoid, "stride": a.stride,
"note": "y = first free block (full chunks: MOTION_BLOCKING_NO_LEAVES, proto "
"chunks: WORLD_SURFACE_WG); the player stands on a barrier at y-1. "
"Valid only for this seed + mod set (biomes are seed-deterministic).",
"sites": sites}
print(json.dumps(doc, indent=1))
if a.out:
Path(a.out).write_text(json.dumps(doc, indent=1) + "\n")
log(f"wrote {a.out}")
if missing:
log(f"NO SITE for {missing}: not in the generated chunks; see README (manual locate)")
return 1
return 0
# ---------------------------------------------------------------- parse / summary
def parse_count_line(line):
m = COUNT_RE.search(line)
if not m:
return None
vals = {}
for tok in m.group("rest").split():
k, sep, v = tok.partition("=")
if sep and re.fullmatch(r"-?\d+", v):
vals[k] = int(v)
return {"sid": m.group("sid"), "biome": m.group("biome"), "t": int(m.group("t")),
"biome_ok": vals.pop("biome_ok", None), "total": vals.pop("total", None),
"nonitem": vals.pop("nonitem", None), "tuned": vals.pop("tuned", None),
"counts": vals}
def parse_lines(lines, sid=None):
"""Count rows from log lines; the client logs each tellraw once as [CHAT]."""
rows, seen = [], set()
for ln in lines:
r = parse_count_line(ln)
if not r or (sid and r["sid"] != sid):
continue
key = (r["sid"], r["biome"], r["t"])
if key in seen:
continue
seen.add(key)
rows.append(r)
return rows
def load_rows(path):
p = Path(path)
if not p.exists():
return [], []
rows = [json.loads(ln) for ln in p.read_text().splitlines() if ln.strip()]
return ([r for r in rows if r.get("kind") == "b14_count"],
[r for r in rows if r.get("kind") == "b14_session"])
def summarize(counts, sessions):
"""{biome: {config: stats}} plus verdict per SPAWN-TUNING §B14."""
bad = {s["sid"] for s in sessions
if s.get("outcome") != "ok" or not (s.get("config_check") or {}).get("ok")}
runs = {}
for r in counts:
if r["sid"] in bad:
continue
runs.setdefault((r["biome"], r["config"], r["sid"]), []).append(r)
out = {}
for (biome, config, sid), rs in runs.items():
rs.sort(key=lambda r: r["t"])
final = rs[-1]
st = out.setdefault(biome, {}).setdefault(config, {
"runs": 0, "final_total": [], "final_nonitem": [], "final_tuned": [],
"seen": {}, "final": {}, "biome_ok": True, "minutes": []})
st["runs"] += 1
st["minutes"].append(final["t"])
st["final_total"].append(final["total"])
st["final_nonitem"].append(final["nonitem"])
st["final_tuned"].append(final["tuned"])
st["biome_ok"] &= all(r.get("biome_ok") == 1 for r in rs)
for eid in set().union(*(r["counts"] for r in rs)):
mx = max(r["counts"].get(eid, 0) for r in rs)
st["seen"][eid] = max(st["seen"].get(eid, 0), mx)
st["final"].setdefault(eid, []).append(final["counts"].get(eid, 0))
tuned_ids = {eid for _, eid in TUNED}
verdicts = {}
for biome, by in out.items():
d, s = by.get("defaults"), by.get("shipped")
if not (d and s):
verdicts[biome] = {"verdict": "incomplete",
"have": sorted(by)}
continue
dt, stt = statistics.median(d["final_total"]), statistics.median(s["final_total"])
gone = sorted(e for e in tuned_ids if d["seen"].get(e, 0) > 0 and s["seen"].get(e, 0) == 0)
rare = sorted(e for e in tuned_ids
if d["seen"].get(e, 0) >= 4 and 0 < s["seen"].get(e, 0) < 0.25 * d["seen"][e])
vanilla_gone = sorted(e for _, e in VANILLA if e in ("minecraft:cow", "minecraft:sheep")
and d["seen"].get(e, 0) > 0 and s["seen"].get(e, 0) == 0)
ok_biome = d["biome_ok"] and s["biome_ok"]
v = "keep" if stt < dt and not gone else ("back off" if gone else "no drop")
verdicts[biome] = {"verdict": v if ok_biome else "invalid (biome check failed)",
"total_defaults": dt, "total_shipped": stt, "disappeared": gone,
"rare_warn": rare, "vanilla_gone": vanilla_gone}
vs = [v["verdict"] for v in verdicts.values()]
overall = ("incomplete" if not vs or len(verdicts) < len(BIOMES)
or any(v not in ("keep", "back off", "no drop") for v in vs)
else "back off" if "back off" in vs else "keep" if all(v == "keep" for v in vs)
else "review")
return out, verdicts, overall
def render_summary(out, verdicts, overall):
lines = ["| biome | config | runs | final total | nonitem | tuned | focus (max seen) |",
"|---|---|---|---|---|---|---|"]
for biome in sorted(out):
for config in ("defaults", "shipped"):
st = out[biome].get(config)
if not st:
continue
focus = ", ".join(f"{e.split(':')[1]}={st['seen'].get(e, 0)}"
for e in FOCUS.get(biome, []))
lines.append(f"| {biome} | {config} | {st['runs']} | "
f"{statistics.median(st['final_total'])} | "
f"{statistics.median(st['final_nonitem'])} | "
f"{statistics.median(st['final_tuned'])} | {focus} |")
lines.append("")
for biome, v in sorted(verdicts.items()):
lines.append(f"- **{biome}**: {v['verdict']}"
+ (f" (total {v['total_defaults']} -> {v['total_shipped']}"
f"{'; disappeared ' + ', '.join(v['disappeared']) if v['disappeared'] else ''}"
f"{'; rare ' + ', '.join(v['rare_warn']) if v['rare_warn'] else ''}"
f"{'; vanilla gone ' + ', '.join(v['vanilla_gone']) if v['vanilla_gone'] else ''})"
if "total_defaults" in v else f" (have {v.get('have')})"))
lines.append(f"\n**Overall: {overall}** (keep = totals drop in every biome and no tuned "
f"species that appears under defaults vanishes; SPAWN-TUNING §B14)")
return "\n".join(lines)
def cmd_summary(a):
counts, sessions = load_rows(a.file)
if not counts:
log(f"no B14 rows in {a.file}")
return 1
out, verdicts, overall = summarize(counts, sessions)
print(render_summary(out, verdicts, overall))
return 0
def cmd_parse(a):
rows = parse_lines(Path(a.log).read_text(errors="replace").splitlines())
for r in rows:
print(json.dumps(r, sort_keys=True))
return 0 if rows else 1
# ---------------------------------------------------------------- run
def _ancestors(pid):
out = set()
while pid > 1 and pid not in out:
out.add(pid)
try:
stat = Path(f"/proc/{pid}/stat").read_text()
pid = int(stat.rsplit(")", 1)[1].split()[1])
except (OSError, ValueError, IndexError):
break
return out
def bench_busy():
"""Other harness processes (matrix runner / bench.py session / another B14 run) that
share the instance; our own process and its parent shells are excluded."""
mine = _ancestors(os.getpid())
busy = []
for d in Path("/proc").iterdir():
if not d.name.isdigit() or int(d.name) in mine:
continue
try:
args = (d / "cmdline").read_bytes().split(b"\0")
except OSError:
continue
s = " ".join(x.decode(errors="replace") for x in args)
if re.search(r"\bmatrix\.py run\b|\bbench\.py session\b|\bb14\.py run\b", s) \
and "--dry-run" not in s:
busy.append(int(d.name))
return busy
def load_sites(path, biomes):
doc = json.loads(Path(path).read_text())
sites = doc.get("sites", {})
missing = [b for b in biomes if b not in sites]
if missing:
raise ValueError(f"{path}: no site for {missing} (run `b14.py locate` or add by hand)")
return [(b, sites[b]) for b in biomes], doc
def cmd_run(a):
biomes = list(BIOMES) if a.biome == "all" else bench.split_list(a.biome)
for b in biomes:
if b not in BIOMES:
sys.exit(f"unknown biome {b!r}; choose from {sorted(BIOMES)}")
try:
sites, sites_doc = load_sites(a.sites, biomes)
shipped = shipped_texts(a.rev)
files = render_variant(a.config, shipped)
world_members(a.source_zip)
except (ValueError, OSError, RuntimeError, zipfile.BadZipFile) as e:
sys.exit(str(e))
sid = "b14-" + time.strftime("%Y%m%d%H%M%S")
pack = generate_datapack(sid, sites, a.minutes, a.settle)
budget = plan_seconds(len(sites), a.minutes, a.settle)
timeout = a.timeout or int(a.boot_timeout + budget + 180)
game = bench.GAME
if a.dry_run:
log(f"dry run {sid}: config={a.config} rev={a.rev} biomes={biomes} "
f"minutes={a.minutes} plan {budget}s timeout {timeout}s dh={a.dh}")
for b, s in sites:
log(f" site {b}: {s}")
log(f"world saves/{WORLD} <- {a.source_zip}: "
f"{[rel for _, rel in world_members(a.source_zip)][:6]}...")
print(config_diff(game, files, shipped))
if bench.pending_exists():
log(f"WARNING: {game / bench.PENDING} exists; a real run would refuse")
busy = bench_busy()
if busy:
log(f"WARNING: harness processes running {busy}; a real run would refuse")
for rel, text in pack.items():
print(f"--- {PACK}/{rel}")
print(text, end="")
return 0
bench.refuse_pending()
busy = bench_busy()
if busy:
sys.exit(f"harness processes running (pids {busy}); B14 shares the bench instance")
if bench.server_procs() and not a.allow_server:
log("WARNING: a dedicated server is running (noise only; spawning is client-side)")
env = bench.bench_env()
bench.preflight(a.kill_stale)
signal.signal(signal.SIGTERM, lambda *_: sys.exit(143))
journal = bench.Journal.begin(game, sid)
prism, state, tail = None, {"done": False, "fail": None}, None
outcome, detail, check, meta, failed = "error", None, None, None, []
t_launch = time.time()
try:
bench.reset_resource_packs(game)
bench.apply_pins()
bench.apply_dh(game, journal, a.dh == "on")
apply_spawn_config(game, journal, files)
build_world(game, journal, a.source_zip, pack)
meta = {"git": bench.git_state(), "mods": bench.mods_hash(game / "mods"),
"variant": bench.read_text(game / "BENCH_VARIANT.txt")}
before = bench.crash_files()
log(f"{sid}: config={a.config} biomes={biomes} {a.minutes} min, timeout {timeout}s")
t_launch = time.time()
prism = bench.launch(env, WORLD)
tail = bench.LogTail(game / "logs" / "latest.log")
def on_line(line):
r = parse_count_line(line)
if r and r["sid"] == sid and "[CHAT]" in line:
log(f" {r['biome']} t={r['t']} total={r['total']} tuned={r['tuned']} "
f"biome_ok={r['biome_ok']}")
if DONE_RE.search(line):
state["done"] = True
if FUNC_FAIL_RE.search(line):
state["fail"] = line.strip()[:300]
outcome, detail, _ = bench.monitor(
tail, prism, before, lambda: state["done"] or state["fail"], timeout, on_line)
if state["fail"]:
outcome, detail = "datapack_error", state["fail"]
finally:
if prism is not None:
bench.kill_client(prism)
if tail is not None:
tail.poll()
try:
check = check_config(game, a.config, shipped) # mods' re-saved files, pre-restore
except OSError as e:
check = {"ok": False, "mismatch": [f"read failed: {e}"], "values": {}}
failed = bench.restore_pending(game)
for e, err in failed:
log(f"RESTORE FAILED {e}: {err}")
if failed:
log(f"journal kept at {game / bench.PENDING}; fix and run `b14.py restore`")
log_path = bench.archive_log(f"session-{sid}")
rows = parse_lines(tail.lines if tail else [], sid)
base = {"sid": sid, "config": a.config, "rev": a.rev, "minutes": a.minutes,
"ts": time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(t_launch)),
"machine": bench.MACHINE}
for r in rows:
bench.append_jsonl(RESULTS_FILE, {**base, **r, "kind": "b14_count",
"site": dict(sites)[r["biome"]]})
bench.append_jsonl(RESULTS_FILE, {
**base, "kind": "b14_session", "outcome": outcome, "detail": detail,
"biomes": biomes, "sites": dict(sites), "sites_sha256": sites_doc.get("source_sha256"),
"config_check": check, "rows": len(rows), "log": log_path,
"git_sha": meta["git"][0] if meta else None,
"git_dirty": meta["git"][1] if meta else None,
"mods_hash": meta["mods"][0] if meta else None,
"mods_count": meta["mods"][1] if meta else None,
"bench_variant": meta["variant"] if meta else None,
"restore_failed": [e for e, _ in failed]})
log(f"{sid} {outcome}{': ' + detail if detail else ''}; {len(rows)} count rows; "
f"config check {'ok' if check and check['ok'] else check and check['mismatch']}")
want = len(sites) * (a.minutes + 1)
return 0 if outcome == "ok" and len(rows) == want and check and check["ok"] else 1
def cmd_restore(a):
if bench.client_pids():
sys.exit("bench client is running; close it before restoring")
if not bench.pending_exists():
log("nothing pending")
return 0
failed = bench.restore_pending(bench.GAME)
for e, err in failed:
log(f"FAILED {e}: {err}")
return 1 if failed else 0
def build_parser():
ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
sub = ap.add_subparsers(dest="cmd", required=True)
r = sub.add_parser("run", help="one client launch: config variant x biome sites")
r.add_argument("--config", required=True, choices=["defaults", "shipped"])
r.add_argument("--biome", default="all",
help="comma list of river,jungle,warm_ocean (in this order), or all")
r.add_argument("--minutes", type=int, default=10)
r.add_argument("--settle", type=int, default=20, help="seconds after join before site 1")
r.add_argument("--sites", default=str(SITES_FILE))
r.add_argument("--source-zip", default=str(SOURCE_ZIP),
help="world whose level.dat (seed, gamerules) seeds the throwaway world")
r.add_argument("--rev", default="HEAD", help="git rev of the shipped partial files")
r.add_argument("--dh", choices=["on", "off"], default="off")
r.add_argument("--boot-timeout", type=int, default=300)
r.add_argument("--timeout", type=int, default=None)
r.add_argument("--kill-stale", action="store_true")
r.add_argument("--allow-server", action="store_true", help="silence the server warning")
r.add_argument("--dry-run", action="store_true")
r.set_defaults(fn=cmd_run)
lo = sub.add_parser("locate", help="find biome sites offline from a world zip")
lo.add_argument("--zip", default=str(SOURCE_ZIP))
lo.add_argument("--out", default=None, help=f"write sites json (e.g. {SITES_FILE})")
lo.add_argument("--stride", type=int, default=1, help="sample every Nth chunk")
lo.set_defaults(fn=cmd_locate)
s = sub.add_parser("summary", help="defaults vs shipped, SPAWN-TUNING §B14 rule")
s.add_argument("--file", default=str(RESULTS_FILE))
s.set_defaults(fn=cmd_summary)
p = sub.add_parser("parse", help="print count rows found in a log file")
p.add_argument("log")
p.set_defaults(fn=cmd_parse)
rs = sub.add_parser("restore", help="undo a hard-killed run (same journal as bench.py)")
rs.set_defaults(fn=cmd_restore)
return ap
def main(argv=None):
a = build_parser().parse_args(argv)
return a.fn(a)
if __name__ == "__main__":
sys.exit(main())
+162
View File
@@ -0,0 +1,162 @@
"""Minimal read-only NBT / Anvil region reader (stdlib only) for B14 site finding.
Enough of the format to read level.dat and 1.18+ chunk biomes + heightmaps:
https://minecraft.wiki/w/NBT_format, /Region_file_format, /Chunk_format.
"""
import gzip
import io
import math
import struct
import zlib
END, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, BYTE_ARRAY, STRING, LIST, COMPOUND, \
INT_ARRAY, LONG_ARRAY = range(13)
class _Reader:
def __init__(self, data):
self.b, self.i = data, 0
def take(self, n):
s = self.b[self.i:self.i + n]
if len(s) != n:
raise ValueError("truncated NBT")
self.i += n
return s
def unpack(self, fmt):
size = struct.calcsize(fmt)
return struct.unpack(fmt, self.take(size))
def string(self):
(n,) = self.unpack(">H")
return self.take(n).decode("utf-8", "replace")
def payload(self, t):
if t == BYTE:
return self.unpack(">b")[0]
if t == SHORT:
return self.unpack(">h")[0]
if t == INT:
return self.unpack(">i")[0]
if t == LONG:
return self.unpack(">q")[0]
if t == FLOAT:
return self.unpack(">f")[0]
if t == DOUBLE:
return self.unpack(">d")[0]
if t == BYTE_ARRAY:
(n,) = self.unpack(">i")
return self.take(n)
if t == STRING:
return self.string()
if t == LIST:
et, n = self.unpack(">bi")
return [self.payload(et) for _ in range(max(0, n))]
if t == COMPOUND:
out = {}
while True:
(ct,) = self.unpack(">b")
if ct == END:
return out
name = self.string()
out[name] = self.payload(ct)
if t == INT_ARRAY:
(n,) = self.unpack(">i")
return list(struct.unpack(f">{n}i", self.take(4 * n)))
if t == LONG_ARRAY:
(n,) = self.unpack(">i")
return list(struct.unpack(f">{n}q", self.take(8 * n)))
raise ValueError(f"bad NBT tag type {t}")
def loads(data):
"""Parse an uncompressed NBT blob -> (root name, root compound)."""
r = _Reader(data)
(t,) = r.unpack(">b")
if t != COMPOUND:
raise ValueError("NBT root is not a compound")
name = r.string()
return name, r.payload(COMPOUND)
def load_gzip(data):
"""level.dat style (gzip) NBT."""
return loads(gzip.decompress(data))
def region_chunks(data):
"""Yield (local_x, local_z, chunk_compound) for every chunk in an .mca blob."""
if len(data) < 8192:
return
for idx in range(1024):
off = int.from_bytes(data[idx * 4:idx * 4 + 3], "big")
if off == 0:
continue
start = off * 4096
if start + 5 > len(data):
continue
length = int.from_bytes(data[start:start + 4], "big")
comp = data[start + 4]
blob = data[start + 5:start + 4 + length]
try:
if comp == 2:
raw = zlib.decompress(blob)
elif comp == 1:
raw = gzip.GzipFile(fileobj=io.BytesIO(blob)).read()
elif comp == 3:
raw = blob
else: # 4 = lz4 (not in vanilla defaults) / 128+ = external .mcc
continue
_, chunk = loads(raw)
except (ValueError, zlib.error, OSError, struct.error):
continue
yield idx % 32, idx // 32, chunk
def _unsigned(v):
return v & 0xFFFFFFFFFFFFFFFF
def unpack_packed(longs, bits, count):
"""1.16+ packed array: entries never span longs."""
per = 64 // bits
mask = (1 << bits) - 1
out = []
for i in range(count):
word = _unsigned(longs[i // per])
out.append((word >> ((i % per) * bits)) & mask)
return out
def section_biome_grid(section):
"""64 biome ids (index = (y4*4 + z4)*4 + x4) for one chunk section, or None."""
b = section.get("biomes")
if not b or not b.get("palette"):
return None
pal = b["palette"]
if len(pal) == 1 or "data" not in b:
return [pal[0]] * 64
bits = max(1, math.ceil(math.log2(len(pal))))
return [pal[i] if i < len(pal) else pal[0] for i in unpack_packed(b["data"], bits, 64)]
def heightmap(chunk, kind="MOTION_BLOCKING_NO_LEAVES", min_y=-64, height=384):
"""256 world-y values (first free block above the top matching block), idx = z*16+x."""
hm = (chunk.get("Heightmaps") or {}).get(kind)
if not hm:
return None
bits = math.ceil(math.log2(height + 1))
return [v + min_y for v in unpack_packed(hm, bits, 256)]
def biome_at(chunk, x, y, z):
"""Biome id at block (x, y, z) in this chunk (x, z may be world coords)."""
sy = y >> 4
for s in chunk.get("sections", []):
if s.get("Y") == sy:
grid = section_biome_grid(s)
if grid is None:
return None
return grid[(((y & 15) >> 2) * 4 + ((z & 15) >> 2)) * 4 + ((x & 15) >> 2)]
return None
@@ -0,0 +1,49 @@
{
"source_zip": "/home/logan/onion-bench/worlds/bench-world-v1.zip",
"source_sha256": "144caf5fcdfea1a0eac0c787fdcded949e1a67df6ac112400f37be30ce7fb76e",
"seed": -306676217,
"avoid": [
[
0,
0
],
[
3000,
-9
]
],
"stride": 1,
"note": "y = first free block (full chunks: MOTION_BLOCKING_NO_LEAVES, proto chunks: WORLD_SURFACE_WG); the player stands on a barrier at y-1. Valid only for this seed + mod set (biomes are seed-deterministic).",
"sites": {
"river": {
"x": 904,
"y": 63,
"z": 88,
"biome": "minecraft:river",
"frac": 0.373,
"score": 0.373,
"full_chunk": false,
"candidates": 17
},
"jungle": {
"x": -8,
"y": 107,
"z": -201,
"biome": "minecraft:jungle",
"frac": 0.52,
"score": 0.52,
"full_chunk": false,
"candidates": 19
},
"warm_ocean": {
"x": -200,
"y": 63,
"z": 56,
"biome": "minecraft:warm_ocean",
"frac": 0.75,
"score": 0.75,
"full_chunk": false,
"candidates": 31
}
}
}
@@ -0,0 +1,413 @@
"""Offline tests for b14.py (temp dirs only; no game, no git writes)."""
import gzip
import json
import struct
import sys
import tempfile
import unittest
import zipfile
import zlib
from pathlib import Path
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE.parent))
sys.path.insert(0, str(HERE.parent.parent))
import b14 # noqa: E402
import bench # noqa: E402
import nbt # noqa: E402
AM_PARTIAL = """# partial
[spawning]
\tgazelleSpawnWeight = 20
\tcapuchinMonkeySpawnWeight = 14
\telephantSpawnWeight = 15
\tstraddlerSpawnWeight = 35
\trockyRollerSpawnWeight = 30
\tunderminerSpawnWeight = 25
\tblobfishSpawnWeight = 15
\tmudskipperSpawnWeight = 14
\tcaimanSpawnWeight = 15
"""
FP_PARTIAL = """// partial
{
"duckSpawnWeight": 2, "duckMaxGroupSize": 4, "gooseSpawnWeight": 2,
"gooseMaxGroupSize": 3, "gullSpawnWeight": 3, "gullMaxGroupSize": 5,
"sparrowSpawnWeight": 12, "sparrowMaxGroupSize": 4
}
"""
CAC_PARTIAL = """{
"configVersion": 2,
"spawning": {
"leaf_insect": [
{"biome_spec": "#c:is_jungle", "weight": 8, "min": 1, "max": 1},
{"biome_spec": "#c:is_forest", "weight": 4, "min": 1, "max": 1}
],
"sea_bunny": [
{"biome_spec": "minecraft:ocean", "weight": 10, "min": 1, "max": 2},
{"biome_spec": "minecraft:warm_ocean", "weight": 20, "min": 1, "max": 3}
],
"dragonfly": [
{"biome_spec": "#c:is_river", "weight": 4, "min": 1, "max": 1},
{"biome_spec": "#c:is_lush", "weight": 6, "min": 1, "max": 1}
]
}
}
"""
SHIPPED = {"alexsmobs-common.toml": AM_PARTIAL, "fowlplay.json5": FP_PARTIAL,
"crittersandcompanions-common.json5": CAC_PARTIAL}
# What the mods write back (re-saved, json5 unquoted keys, tabs) with default values.
AM_RESAVED_DEFAULT = "[spawning]\n" + "".join(
f"\t#Spawn Weight\n\t# Default: {v}\n\t{k} = {v}\n"
for k, v in b14.EXPECT_DEFAULTS["alexsmobs-common.toml"].items())
FP_RESAVED_DEFAULT = "{\n" + ",\n".join(
f"\t{k}: {v}" for k, v in b14.EXPECT_DEFAULTS["fowlplay.json5"].items()) + "\n}"
CAC_RESAVED_DEFAULT = """{
\tconfigVersion: 2,
\tspawning: {
\t\tleaf_insect: [
\t\t\t{
\t\t\t\tbiome_spec: "#c:is_jungle",
\t\t\t\tweight: 14,
\t\t\t\tmin: 1,
\t\t\t\tmax: 1
\t\t\t},
\t\t\t{ biome_spec: "#c:is_forest", weight: 6, min: 1, max: 1 }
\t\t],
\t\tred_panda: [ { biome_spec: "#c:is_jungle", weight: 8, min: 1, max: 2 } ],
\t\tsea_bunny: [
\t\t\t{ biome_spec: "minecraft:ocean", weight: 16, min: 1, max: 2 },
\t\t\t{ biome_spec: "minecraft:warm_ocean", weight: 32, min: 1, max: 4 }
\t\t],
\t\tdragonfly: [
\t\t\t{ biome_spec: "#c:is_river", weight: 4, min: 1, max: 1 },
\t\t\t{ biome_spec: "#c:is_lush", weight: 10, min: 1, max: 1 }
\t\t]
\t}
}"""
class ConfigVariants(unittest.TestCase):
def test_render(self):
d = b14.render_variant("defaults", SHIPPED)
self.assertEqual(set(d), set(b14.SPAWN_FILES))
self.assertTrue(all(v is None for v in d.values()))
s = b14.render_variant("shipped", SHIPPED)
self.assertEqual(s["fowlplay.json5"], FP_PARTIAL)
with self.assertRaises(ValueError):
b14.render_variant("nope", SHIPPED)
def test_extract_partial_and_resaved(self):
keys = b14.EXPECT_DEFAULTS
got = b14.extract_values("crittersandcompanions-common.json5", CAC_PARTIAL,
keys["crittersandcompanions-common.json5"])
self.assertEqual(got["sea_bunny@minecraft:warm_ocean"], 20)
self.assertEqual(got["leaf_insect@#c:is_jungle"], 8)
self.assertEqual(got["dragonfly@#c:is_lush"], 6)
got = b14.extract_values("crittersandcompanions-common.json5", CAC_RESAVED_DEFAULT,
keys["crittersandcompanions-common.json5"])
self.assertEqual(got, keys["crittersandcompanions-common.json5"])
self.assertEqual(b14.extract_values("fowlplay.json5", FP_RESAVED_DEFAULT,
keys["fowlplay.json5"]), keys["fowlplay.json5"])
self.assertEqual(b14.extract_values("alexsmobs-common.toml", AM_RESAVED_DEFAULT,
keys["alexsmobs-common.toml"]),
keys["alexsmobs-common.toml"])
# "Default: 40" comment lines must not be mistaken for the value
self.assertEqual(b14.extract_values("alexsmobs-common.toml", AM_PARTIAL,
["gazelleSpawnWeight"]), {"gazelleSpawnWeight": 20})
def test_check_config_and_journaled_apply(self):
with tempfile.TemporaryDirectory() as td:
game = Path(td)
(game / "config").mkdir()
originals = {"alexsmobs-common.toml": AM_RESAVED_DEFAULT + "# orig\n",
"fowlplay.json5": FP_RESAVED_DEFAULT} # CAC absent on purpose
for n, t in originals.items():
(game / "config" / n).write_text(t)
j = bench.Journal.begin(game, "b14-1")
b14.apply_spawn_config(game, j, b14.render_variant("shipped", SHIPPED))
for n in b14.SPAWN_FILES:
self.assertEqual((game / "config" / n).read_text(), SHIPPED[n])
self.assertTrue(b14.check_config(game, "shipped", SHIPPED)["ok"])
chk = b14.check_config(game, "defaults", SHIPPED)
self.assertFalse(chk["ok"])
self.assertIn("fowlplay.json5:duckSpawnWeight want 4 got 2", chk["mismatch"])
self.assertEqual(bench.restore_pending(game), [])
for n, t in originals.items():
self.assertEqual((game / "config" / n).read_text(), t)
self.assertFalse((game / "config" / "crittersandcompanions-common.json5").exists())
self.assertFalse((game / bench.PENDING).exists())
def test_defaults_delete_and_restore(self):
with tempfile.TemporaryDirectory() as td:
game = Path(td)
(game / "config").mkdir()
for n in b14.SPAWN_FILES:
(game / "config" / n).write_text("x" + n)
j = bench.Journal.begin(game, "b14-2")
b14.apply_spawn_config(game, j, b14.render_variant("defaults", SHIPPED))
self.assertFalse(any((game / "config" / n).exists() for n in b14.SPAWN_FILES))
self.assertTrue(b14.check_config(game, "defaults", SHIPPED)["ok"]) # absent = defaults
self.assertFalse(b14.check_config(game, "shipped", SHIPPED)["ok"])
# what the mods write after load
(game / "config" / "alexsmobs-common.toml").write_text(AM_RESAVED_DEFAULT)
(game / "config" / "fowlplay.json5").write_text(FP_RESAVED_DEFAULT)
(game / "config" / "crittersandcompanions-common.json5").write_text(CAC_RESAVED_DEFAULT)
self.assertTrue(b14.check_config(game, "defaults", SHIPPED)["ok"])
bench.restore_pending(game)
for n in b14.SPAWN_FILES:
self.assertEqual((game / "config" / n).read_text(), "x" + n)
def test_config_diff(self):
with tempfile.TemporaryDirectory() as td:
game = Path(td)
(game / "config").mkdir()
(game / "config" / "fowlplay.json5").write_text(FP_RESAVED_DEFAULT)
txt = b14.config_diff(game, b14.render_variant("shipped", SHIPPED), SHIPPED)
self.assertIn("duckSpawnWeight: 4 -> 2 <- changes", txt)
self.assertIn("config/alexsmobs-common.toml: WRITE", txt)
txt = b14.config_diff(game, b14.render_variant("defaults", SHIPPED), SHIPPED)
self.assertIn("config/fowlplay.json5: DELETE", txt)
SITES = [("river", {"x": 904, "y": 63, "z": 88}), ("warm_ocean", {"x": -200, "y": 63, "z": 56})]
class Datapack(unittest.TestCase):
def setUp(self):
self.files = b14.generate_datapack("b14-20261018000000", SITES, 2, 20)
self.base = "data/b14/function/b14-20261018000000"
def test_structure(self):
f = self.files
self.assertEqual(json.loads(f["pack.mcmeta"])["pack"]["pack_format"], 48)
self.assertEqual(json.loads(f["data/minecraft/tags/function/load.json"]),
{"values": ["b14:b14-20261018000000/load"]})
begin = f[f"{self.base}/begin.mcfunction"]
self.assertIn("gamerule doMobSpawning true", begin)
self.assertIn("gamemode creative @a", begin)
self.assertNotIn("spectator", "".join(f.values()))
self.assertIn("step0 400t", begin)
# 2 sites x (tp, arrive, t=0..2) + done
steps = [k for k in f if "/step" in k]
self.assertEqual(len(steps), 2 * (2 + 3) + 1)
self.assertIn("B14_DONE", f[f"{self.base}/step{len(steps) - 1}.mcfunction"])
def test_counts_and_chain(self):
count = self.files[f"{self.base}/count.mcfunction"]
for _, eid in b14.TUNED + b14.VANILLA:
self.assertIn(f"@e[type={eid},distance=..128]", count)
self.assertIn("@e[type=!minecraft:player,distance=..128]", count)
self.assertEqual(count.count("+= #"), len(b14.TUNED))
tl = b14.build_timeline("b14-20261018000000", SITES, 2, 20)
delays = [d for _, d in tl]
self.assertEqual(delays[:5], [100, 200, 1200, 1200, 20])
self.assertEqual(delays[-1], 0)
for i in range(len(tl) - 1):
body = self.files[f"{self.base}/step{i}.mcfunction"]
self.assertIn(f"schedule function b14:b14-20261018000000/step{i + 1} ", body)
s2 = self.files[f"{self.base}/step2.mcfunction"]
self.assertIn("setblock 904 62 88 minecraft:barrier", s2)
self.assertIn("tp @a 904.5 63 88.5", s2)
self.assertIn("if biome ~ ~ ~ minecraft:river", s2)
def test_tellraw_roundtrip(self):
"""The rendered tellraw text, with scores substituted, parses back."""
cmd = b14.tellraw("b14-20261018000000", "river", 3)
parts = json.loads(cmd[len("tellraw @a "):])
text = "".join(p if isinstance(p, str) else p.get("text", "7") for p in parts)
line = ("[18Oct2026 00:10:00.000] [Render thread/INFO] [net.minecraft.client.gui."
f"components.ChatComponent/]: [System] [CHAT] {text}")
r = b14.parse_count_line(line)
self.assertEqual((r["sid"], r["biome"], r["t"], r["total"], r["biome_ok"]),
("b14-20261018000000", "river", 3, 7, 7))
self.assertEqual(set(r["counts"]), {eid for _, eid in b14.TUNED + b14.VANILLA})
def test_world_build(self):
with tempfile.TemporaryDirectory() as td:
td = Path(td)
zp = td / "w.zip"
with zipfile.ZipFile(zp, "w") as z:
z.writestr("onionbench/level.dat", b"lvl")
z.writestr("onionbench/region/r.0.0.mca", b"x")
z.writestr("onionbench/data/chunks.dat", b"x")
z.writestr("onionbench/datapacks/onion-bench/pack.mcmeta", "{}")
z.writestr("onionbench/serverconfig/a.toml", "a")
game = td / "game"
(game / "saves").mkdir(parents=True)
j = bench.Journal.begin(game, "b14-3")
root = b14.build_world(game, j, zp, self.files)
self.assertEqual((root / "level.dat").read_bytes(), b"lvl")
self.assertFalse((root / "region").exists())
self.assertFalse((root / "data").exists())
self.assertTrue((root / "serverconfig/a.toml").exists())
self.assertTrue((root / "datapacks" / b14.PACK / "pack.mcmeta").exists())
with self.assertRaises(ValueError):
b14.build_world(game, bench.Journal(game, "x"), zp, self.files)
bench.restore_pending(game)
self.assertFalse(root.exists())
def _line(sid, biome, t, total, counts, ok=1):
body = " ".join(f"{k}={v}" for k, v in counts.items())
return (f"[x] [Render thread/INFO] [ChatComponent/]: [System] [CHAT] B14 {sid} {biome} "
f"t={t} biome_ok={ok} total={total} nonitem={total} tuned=0 {body}")
class ParseAndSummary(unittest.TestCase):
def test_parse_dedupes_and_filters(self):
lines = [_line("b14-1", "river", 0, 5, {"fowlplay:duck": 1}),
_line("b14-1", "river", 0, 5, {"fowlplay:duck": 1}),
"[Server thread/INFO]: [Server] B14_ARRIVE b14-1 river 1 2 3",
_line("b14-2", "river", 0, 9, {"fowlplay:duck": 2}),
"noise B14 garbage"]
rows = b14.parse_lines(lines)
self.assertEqual(len(rows), 2)
self.assertEqual(len(b14.parse_lines(lines, "b14-2")), 1)
self.assertEqual(rows[0]["counts"], {"fowlplay:duck": 1})
def _rows(self, config, sid, biome, totals, duck):
out = []
for t, (tot, d) in enumerate(zip(totals, duck)):
r = b14.parse_count_line(_line(sid, biome, t, tot, {"fowlplay:duck": d,
"minecraft:cow": 1}))
out.append({**r, "config": config, "kind": "b14_count"})
return out
def _sess(self, sid, ok=True):
return {"kind": "b14_session", "sid": sid, "outcome": "ok",
"config_check": {"ok": ok}}
def test_keep_and_back_off(self):
counts = (self._rows("defaults", "b14-1", "river", [10, 40], [2, 6])
+ self._rows("shipped", "b14-2", "river", [8, 25], [0, 3]))
sess = [self._sess("b14-1"), self._sess("b14-2")]
out, verdicts, overall = b14.summarize(counts, sess)
self.assertEqual(verdicts["river"]["verdict"], "keep")
self.assertEqual(out["river"]["shipped"]["seen"]["fowlplay:duck"], 3)
self.assertEqual(overall, "incomplete") # jungle / warm_ocean missing
counts2 = (self._rows("defaults", "b14-1", "river", [10, 40], [2, 6])
+ self._rows("shipped", "b14-2", "river", [8, 25], [0, 0]))
_, v2, o2 = b14.summarize(counts2, sess)
self.assertEqual(v2["river"]["verdict"], "back off")
self.assertEqual(v2["river"]["disappeared"], ["fowlplay:duck"])
self.assertIn("back off", b14.render_summary(*b14.summarize(counts2, sess)))
def test_bad_sessions_and_biome_check_excluded(self):
counts = (self._rows("defaults", "b14-1", "river", [10, 40], [2, 6])
+ self._rows("shipped", "b14-2", "river", [8, 25], [1, 3]))
_, v, _ = b14.summarize(counts, [self._sess("b14-1"), self._sess("b14-2", ok=False)])
self.assertEqual(v["river"]["verdict"], "incomplete")
for r in counts:
r["biome_ok"] = 0
_, v, _ = b14.summarize(counts, [self._sess("b14-1"), self._sess("b14-2")])
self.assertTrue(v["river"]["verdict"].startswith("invalid"))
def test_all_biomes_keep(self):
counts, sess = [], []
for i, biome in enumerate(b14.BIOMES):
counts += self._rows("defaults", f"b14-{i}0", biome, [5, 30], [1, 2])
counts += self._rows("shipped", f"b14-{i}1", biome, [5, 20], [1, 1])
sess += [self._sess(f"b14-{i}0"), self._sess(f"b14-{i}1")]
self.assertEqual(b14.summarize(counts, sess)[2], "keep")
# ---------------------------------------------------------------- nbt / locate
def _tag(t, name, payload):
n = name.encode()
return bytes([t]) + struct.pack(">H", len(n)) + n + payload
def _str(s):
b = s.encode()
return struct.pack(">H", len(b)) + b
def _compound(*items):
return b"".join(items) + b"\x00"
def _chunk_nbt(biome, height=70, status="minecraft:full"):
hm_longs = []
vals = [height + 64] * 256 # stored relative to min_y -64, 9 bits, 7 per long
for i in range(0, 256, 7):
w = 0
for k, v in enumerate(vals[i:i + 7]):
w |= v << (9 * k)
hm_longs.append(w if w < 2 ** 63 else w - 2 ** 64)
hm = struct.pack(">i", len(hm_longs)) + struct.pack(f">{len(hm_longs)}q", *hm_longs)
section = _compound(
_tag(nbt.BYTE, "Y", struct.pack(">b", (height - 1) >> 4)),
_tag(nbt.COMPOUND, "biomes", _compound(
_tag(nbt.LIST, "palette", bytes([nbt.STRING]) + struct.pack(">i", 1) + _str(biome)))))
root = _compound(
_tag(nbt.STRING, "Status", _str(status)),
_tag(nbt.LIST, "sections", bytes([nbt.COMPOUND]) + struct.pack(">i", 1) + section),
_tag(nbt.COMPOUND, "Heightmaps", _compound(
_tag(nbt.LONG_ARRAY, "MOTION_BLOCKING_NO_LEAVES", hm))))
return bytes([nbt.COMPOUND]) + _str("") + root
def _region(chunks):
"""chunks: {(lx, lz): nbt bytes} -> .mca bytes."""
header, body, sector = bytearray(8192), bytearray(), 2
for (lx, lz), raw in chunks.items():
data = zlib.compress(raw)
blob = struct.pack(">I", len(data) + 1) + b"\x02" + data
blob += b"\x00" * (-len(blob) % 4096)
n = len(blob) // 4096
idx = lz * 32 + lx
header[idx * 4:idx * 4 + 4] = sector.to_bytes(3, "big") + bytes([n])
body += blob
sector += n
return bytes(header) + bytes(body)
class NbtAndLocate(unittest.TestCase):
def test_packed(self):
self.assertEqual(nbt.unpack_packed([0b10_01_00 | (3 << 62)], 2, 3), [0, 1, 2])
self.assertEqual(nbt.unpack_packed([-1], 4, 2), [15, 15])
def test_chunk_roundtrip(self):
_, c = nbt.loads(_chunk_nbt("minecraft:river", 63))
self.assertEqual(nbt.heightmap(c)[0], 63)
self.assertEqual(nbt.biome_at(c, 5, 62, 9), "minecraft:river")
ld = gzip.compress(bytes([nbt.COMPOUND]) + _str("") + _compound(
_tag(nbt.COMPOUND, "Data", _compound(_tag(nbt.INT, "SpawnX", struct.pack(">i", 7))))))
self.assertEqual(nbt.load_gzip(ld)[1]["Data"]["SpawnX"], 7)
def test_scan_and_pick(self):
reg = {}
for lx in range(5):
for lz in range(5):
reg[(lx, lz)] = _chunk_nbt("minecraft:river" if lx < 3 else "minecraft:forest", 63)
reg[(lx + 20, lz)] = _chunk_nbt("minecraft:warm_ocean", 63)
reg[(lx + 10, lz + 20)] = _chunk_nbt("minecraft:jungle", 80,
"minecraft:structure_starts")
with tempfile.TemporaryDirectory() as td:
zp = Path(td) / "w.zip"
with zipfile.ZipFile(zp, "w") as z:
z.writestr("w/level.dat", b"")
z.writestr("w/region/r.0.0.mca", _region(reg))
chunks = b14.scan_world(zp)
self.assertEqual(len(chunks), 50) # structure_starts placeholders skipped
sites = b14.pick_sites(chunks, avoid=[(-1000, -1000)])
self.assertEqual(sites["river"]["biome"], "minecraft:river")
self.assertEqual(sites["river"]["y"], 63)
self.assertIn("warm_ocean", sites)
self.assertNotIn("jungle", sites)
self.assertGreaterEqual(abs(sites["warm_ocean"]["x"] - sites["river"]["x"]), 320)
# avoid radius excludes everything near the avoid point
self.assertEqual(b14.pick_sites(chunks, avoid=[(40, 40), (360, 40)]), {})
class Busy(unittest.TestCase):
def test_ancestors_include_self(self):
import os
self.assertIn(os.getpid(), b14._ancestors(os.getpid()))
self.assertNotIn(os.getpid(), b14.bench_busy())
if __name__ == "__main__":
unittest.main()
File diff suppressed because it is too large. Load diff
Loaded 100 of 343 files, more files were not shown because too many files have changed in this diff. Show more