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

Client-perf configs: generator, tiers, bench variants

Status (a01bc3b): the owner approved the mechanism below. Default Options is in the pack, and the T-default files are shipped under config/defaultoptions/ via build.py --ship. Every value is still unmeasured. ../BENCH-MATRIX.md decides them. ModernFix dynamic_resources is held back until B6 passes.

python3 build.py                     # regenerates out/ (stdlib only, ~1 s)
python3 build.py --ship              # + copies T-default into config/defaultoptions/
~/go/bin/packwiz refresh             # from the repo root, after --ship
python3 apply_variant.py C1 <instance>/.minecraft   # bench: apply a variant (merges options.txt)

Iteration loop: edit DEFAULT / DECK / HIGH in build.py, then build.py --ship, then packwiz refresh, then commit. The shipped defaults are seed-once: they reach fresh installs only. Players who already have the files follow ../PLAYER-TUNING.md §1.

Path What
build.py Single source of truth: tiers, bench variants, base files. Fails if an override names a key that isn't already in the base file. That enforces the "confirmed keys only" rule mechanically.
templates/ Hand-written bases for files without a same-version observed default (keys taken from the pinned jar or its source, see §3)
out/T-default, T-deck, T-mid, T-high Player tiers. T-mid = T-default
out/C0, C1, C1-*, dh-*, fw-*, … Phase 2 bench variants (see ../BENCH-MATRIX.md)
out/MANIFEST.md Generated list of every changed key per variant
apply_variant.py Bench: apply out/<ID>/ to an instance, merging options.txt

1. Delivery mechanism (LEVERS M1): resolved

Recommendation: add Default Options, and stage the tuned files under config/defaultoptions/. The one exception is modernfix-mixins.properties, which ships as a normal (enforced) packwiz file. Evidence follows. All of it was read from the exact versions in use: packwiz ef87d96 (the commit ~/go/bin/packwiz was built from, per go version -m), packwiz-installer v0.5.14 (the jar in the Prism instance reports Implementation-Version: v0.5.14), and Default Options 21.1.8 (source tag 1.21.1@94e2f17 plus the Modrinth jar).

packwiz preserve = true

Question Answer Evidence
How is it set? Only by hand-editing index.toml; no CLI flag Preserve exists only in core/indexfiles.go:29; no cmd/ reference
Does packwiz refresh keep it? Yes, as long as the file stays on disk and not ignored. updateFileEntry mutates the loaded entry in place. core/indexfiles.go updateFileEntry. Tested: kept across refresh, a content change + refresh, and an unrelated packwiz update
When is it lost? Any refresh while the file is absent or ignored deletes the entry (Refresh() drops entries that are not markedFound). Re-adding it comes back without preserve. core/index.go Refresh(). Tested: delete → refresh → restore → refresh = preserve gone
What does the installer do? Writes the file only if the destination does not exist. It never updates it afterwards. DownloadTask.kt:222-227
Side effects (a) When the installer skips the file, the manifest entry gets no cachedLocation, so every launch logs "invalidated, marked for redownloading" and the installer can't take its "pack unchanged" fast path (it re-reads the index each launch). (b) If the installer did write the file, a later removal from the index deletes the player's copy (UpdateManager.kt:250-262). source
Timing Pre-launch (Prism PreLaunchCommand), so it beats everything, including mixin-time configs instance.cfg
Sided? No. Plain index files have no side. This repo is the server directory, so a file in config/ also configures the server. indexFile struct

Default Options 21.1.8 (BlayTheNinth)

Question Answer Evidence
Compatible? NeoForge [21-beta,), MC [1.21,), Balm [21.0.39,). The pack has Balm 21.0.66. jar neoforge.mods.toml
options.txt config/defaultoptions/options.txt is copied only if options.txt is absent. key_* lines are stripped (keybinds have their own handler). DefaultOptionsDefaultHandlers, SimpleDefaultOptionsFileHandler.shouldLoadDefaults
Any config file config/defaultoptions/extra/<path> is copied to <gamedir>/<path> only if the target is absent ExtraDefaultOptionsHandler.loadDefaults
Timing EARLY_LOAD runs at GameConfig.<init> RETURN, which is in Main, before new Minecraft(). NeoForge 1.21.1 constructs mods inside Minecraft.<init> (ClientModLoader.begin, Minecraft.java.patch), so the files land before any mod constructor creates its config. They land after mixin bootstrap. GameConfigMixin; NeoForge 1.21.1 patch
Sided? Client-only mod (dist = CLIENT), and the files sit in config/defaultoptions/, which no server-side code reads —
Updating defaults later Editing the shipped defaults never clobbers a player's live file. Removing them never deletes anything. copy-if-absent

Why this split

  1. Server bleed rules out preserve for most files. DistantHorizons.toml carries [server] and [common.multiThreading] sections that the server-side DH also reads. ssrd-common.toml is a NeoForge COMMON config: SSRD 1.8.7 registers it with ModConfig.Type.COMMON and reads physicsRenderDistance server-side in ChunkMapTrackedEntityMixin and SubLevelTrackingSystemMixin. Shipping a client value of 16 into the repo's config/ would also change the server's tracking. config/defaultoptions/extra/ keeps them client-only.
  2. Both mechanisms are seed-once. Neither reaches an instance that already has the file. Existing players need a one-time reset (see ../PLAYER-TUNING.md §1). That's a wash.
  3. ModernFix is the exception. It reads config/modernfix-mixins.properties from its mixin plugin (ModernFixMixinPlugin), which runs before GameConfig, so Default Options is too late on first launch. Ship it enforced (a plain packwiz file, no preserve). It's a pack-correctness flag, not a preference, and enforcing it keeps it revertible for everyone if dynamic_resources misbehaves. The server reads it too, but dynamic_resources only patches client resource loading.
  4. Default Options also gives default keybinds and default resource packs (its journal is used only for resource packs), with no index hand-editing and nothing to silently lose on a refresh.

Proposed repo layout (not applied)

config/defaultoptions/options.txt                     <- out/T-default/options.txt
config/defaultoptions/extra/config/<file>             <- out/T-default/config/<file>  (all except ModernFix)
config/modernfix-mixins.properties                    <- out/T-default/config/modernfix-mixins.properties

.packwizignore, appended at the bottom (tested with packwiz refresh in a scratch copy: it indexes exactly these 11 files and still ignores runtime config/*.json):

# Client perf defaults: seeded by Default Options (copy-if-absent, client only)
!config/defaultoptions
!config/defaultoptions/**
# Read at mixin bootstrap, before Default Options runs: enforced by packwiz
!config/modernfix-mixins.properties

Applied in a01bc3b, minus the ModernFix lines (held for B6). packwiz wrote side = "client" for Default Options by itself.


2. Tiers

T-default is the pack-wide default and equals T-mid. T-deck and T-high are deltas on top of it. Every fleet machine is 4C/8T, so the CPU-side levers stay on in every tier. "High" means a strong GPU, not a strong CPU.

File Key Upstream Default / mid Deck High
options.txt renderDistance / simulationDistance 12 / 12 8 / 8
biomeBlendRadius 2 1 0 2
mipmapLevels 4 2 4
entityDistanceScaling 1.0 0.75 1.0
particles / entityShadows 0 / true 1 / false
DistantHorizons.toml lodChunkRenderDistanceRadius 256 128 64
enableSsao true false true
numberOfThreads ceil(nproc/2) = 4 2
horizontal / vertical quality, transparency, vanillaFadeMode, lodBiomeBlending MEDIUM / MEDIUM, COMPLETE, DOUBLE_PASS, 3 LOW / LOW, DISABLED, SINGLE_PASS, 1 (= DH's own LOW preset)
sodium-options.json quality.weather_quality / leaves_quality DEFAULT FAST / FAST
entity_model_features.json animationLODDistance 20 12 8
modelUpdateFrequency Average Slow
retainDetailOnLowFps true false
flywheel-client.toml flw_backends.lightSmoothness SMOOTH TRI_LINEAR FLAT SMOOTH
lambdynlights.toml mode fancy fast fastest
light_sources.entities true false false
soundphysics.properties environment_evaluation_ray_count / ray_bounces 32 / 4 16 / 2
strict_occlusion false true true
sound_rates.properties 19 noisy Create sounds -1 (unlimited) 4 per tick
ssrd-common.toml physicsRenderDistance 128 16 32
modernfix-mixins.properties mixin.perf.dynamic_resources false true (gated on B6)
sable-client.toml — — unchanged

Notes:

  • Sable: the defaults are already right (sub_level_skylight_shadows=false). The file is staged only so the bench variant sable-noshade (sub_level_dynamic_shading=false) has a base. Don't ship it unless B4 shows a win.
  • SSRD: 16 is the floor of SSRD's own slider (minPhysicsRenderDistance = 16). Players can still raise it in Sodium's video settings.
  • High-tier DH radius stays 128 because P2 (the fleet's "high" GPU) has only ~8 GB of free RAM. A player with ≥16 GB free and a 6+ core CPU can go to 192–256 (PLAYER-TUNING.md).
  • DH presets are GUI-only (qualityPresetSetting and threadPresetSetting are ONLY_IN_GUI), so the files set the raw keys. The values are taken from DH's own preset tables (RenderQualityPresetConfigEventHandler, ThreadPresetConfigEventHandler @ 3.3.2).
  • Stripped: DH [server] serverId is a per-install new Random().nextInt() ("DO NOT CHANGE"). It's removed so each install generates its own. DH re-adds missing keys with their defaults (ConfigFileHandler.loadEntry → saveEntry).

3. Key provenance

"Observed" means the file was generated on 2026-09-24 by the same jar version that's pinned now. Versions were checked against that launch's latest.log and the instance's mods/: 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. For DH, all 172 observed values were also diffed against Config.java @ 3.3.2. The only differences are the two machine-computed ones (numberOfThreads, serverId), so the file holds no owner edits.

File Base Keys confirmed in
DistantHorizons.toml observed (3.3.2) enum values from EDhApi* classes in the jar; defaults and presets from DH core 3.3.2 source
sodium-options.json observed (0.8.13) SodiumOptions$LeavesQuality / $WeatherQuality = DEFAULT/FANCY/FAST/FABULOUS (nested mod jar); Gson LOWER_CASE_WITH_UNDERSCORES
entity_model_features.json observed (3.3.9) ETFConfig$UpdateFrequency = Never/Slow/Average/Fast/Instant (ETF 7.2.4 jar)
flywheel-client.toml observed (1.0.6) allowed values are listed in the file's own comment
sable-client.toml observed (2.0.5) —
soundphysics.properties observed (1.5.1) —
sound_rates.properties template, partial SoundRateConfig.load() starts from all registered sounds at -1 and overlays file entries (SPR 1.21.1-1.5.1 source). Over-cap sounds play without physics (SoundPhysics.java). Create IDs come from the observed list.
lambdynlights.toml template DynamicLightsConfig @ v4.8.11+1.21.1. Strings match the nested NeoForge jar. Modes are lowercase off/fastest/fast/fancy; dotted keys become TOML tables; missing keys fall back via getOrElse.
ssrd-common.toml template Config.java @ SSRD HEAD = 1.8.7 (comments and translation keys match the jar). ModConfig.Type.COMMON from ssrd.class.
modernfix-mixins.properties template option name and default from the observed file's own option list (5.27.24)
options.txt template, partial key names from the instance's real 1.21.1 options.txt (version:3955). version is included so vanilla doesn't run the options DataFixer from 0.

Not drafted (keys unverified this session, Tier 2): Twilight Forest cloudBlockPrecipitationDistance, Alex's Caves biome_ambient_light_coloring, Jade, Xaero's, WATUT, Perception, Subtle Effects, FWA.