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.
183 lines
13 KiB
Markdown
183 lines
13 KiB
Markdown
# 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.
|