Files
onion-mc/analysis/client-perf/harness/b14
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
..

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

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.