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

183 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <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`):
```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.