# 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. ```sh 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 /.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//` 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/` is copied to `/` **only if** the target is absent | `ExtraDefaultOptionsHandler.loadDefaults` | | Timing | `EARLY_LOAD` runs at `GameConfig.` RETURN, which is in `Main`, *before* `new Minecraft()`. NeoForge 1.21.1 constructs mods inside `Minecraft.` (`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/ <- out/T-default/config/ (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`): ```gitignore # 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.