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.
This commit is contained in:
logan committed 2026-10-14 18:15:32 -04:00
1 parent d80f85b3f6
commit 611a1a5952
102 files changed
+25853

No files matched your search

+176
View File
@@ -0,0 +1,176 @@
# Onion MC — client performance levers (Phase 1, unmeasured)
Fleet (from `phase0/`): **P0** Steam Deck (GPU/shared-RAM bound), **P1** 7700K + GTX 1060 6 GB
(VRAM bound), **P2** 6700K + 1080 Ti with ~8 GB free RAM (main-thread + heap bound). Every
machine is 4C/8T. That makes CPU-side render cost, chunk/LOD worker threads and GC the
common denominator.
Impact estimates are informed guesses until Phase 2 measures them. Tags: **V** verified
in source or config, **D** documented upstream, **C** community, **I** inference.
Sources are in `research/` and `data/`.
---
## 1. The shape of the problem
From `data/jarscan-report.md`:
| Surface | Size | Who dominates |
| --- | --- | --- |
| Mixin classes on per-frame paths | 951 (121 `@Overwrite` / `@Redirect`) | Sable 127, Iris 114, Controlify 84 (GUI), Sodium 48, EMF 42, Create 30, ETF 27, Subtle Effects 26 |
| Mods subscribing to per-frame events | 58 | Puzzles Lib, Create, Alex's Caves, Mowzie's, Twilight Forest, Ars Nouveau, Balm, Xaero's |
| Mods on client / player / entity tick events | 79 | — |
| Block + item atlas | ~60k 16×16-sprite equivalents (≥4096²) | **Handcrafted 27 %, Chipped 21 %, Rechiseled 9 %** (58 % together) |
| Baked models / blockstates | 69,442 / 16,361 | Chipped 16.8k, Rechiseled 13.9k, Handcrafted 12.4k models (62 %) |
| Entity types | ~600 | Alex's Mobs 140, Alex's Caves 92, Twilight Forest 80, Ars Nouveau 51 |
| Animated atlas textures | 312 | Frights' Delight 69, TF 44, Chipped 30, Ars 29 |
| Post-processing / shader payloads | — | Lodestone 59 files, Alex's Caves 38 (7 post), Charta 31, Exposure 21 (19 post); vanilla core-shader overrides from Sable, Sodium, Xaero's ×2 |
What a player pays **by default today**, before choosing any options:
- **Shaders and resource packs are not on by default.** packwiz only downloads them; the
last real launch had `resourcePacks:[]` and no shaderpack. So Fresh Animations, Better
Leaves, Complementary and MakeUp cost nothing until a player enables them. **I**
- **Distant Horizons is mandatory and its defaults are heavy:** 256-chunk LOD radius, SSAO
on, AA on, MEDIUM/MEDIUM quality, transparency COMPLETE. **V** (observed `DistantHorizons.toml`)
- **Every mod's own defaults ship unchanged.** The pack ships **no client performance
config**. Its only configs are PetProtect, Akashic Tome and the Paxi datapack.
- The JVM, heap and GC are whatever each player's Prism instance has. The old instance had
`MaxMemAlloc=8192` and stock G1.
That last bullet is the reason for §2.
---
## 2. Meta-levers (enablers; do these first)
| # | Lever | Why |
| --- | --- | --- |
| M1 | **A way to ship tuned defaults without clobbering player changes.** **Resolved (2026-10-14, Phase 1b):** use Default Options (`config/defaultoptions/`, client-only, copy-if-absent), plus an enforced packwiz file for `modernfix-mixins.properties`, which is read at mixin time. `preserve` survives `packwiz refresh` but leaks into the server dir, which matters for DH `[server]`/`[common]` and SSRD COMMON. Evidence: `proposed-configs/README.md` §1. | Without this, every config lever below depends on 10 players hand-editing files. |
| M2 | **Per-tier profiles** (Deck / mid / high). Ship one conservative default, plus a short "if your machine is X, change Y" doc. Optionally make heavy client-only cosmetics packwiz `optional` (today only Controlify is). | Fleet constraints differ in direction. P2 should lower DH *threads*; P1 should lower DH *distance and quality*. One global ordering won't fit (already noted in `phase0/P2`). |
| M3 | **Profile with spark on the client** (`/sparkc profiler`; spark is already in the pack) in Phase 2, not only FPS percentiles. | 951 frame-path mixins means attribution must come from a profiler, not from guessing which mod is slow. |
---
## 3. Ranked levers
### Tier 1 — biggest expected wins, config or policy only
| # | Lever | Mechanism | Helps | Risk | Conf. |
| --- | --- | --- | --- | --- | --- |
| **1** | **Distant Horizons defaults per tier.** `lodChunkRenderDistanceRadius` 256 → ~64–96 (P0), 128–192 (P1), ~128 (P2). Turn SSAO off on P0 and P1. Lower the CPU thread preset on P2. Keep cave culling on. LODs come from the server; do `/dh pregen` server-side, not Chunky. | DH cost scales with LOD distance in VRAM, GPU and CPU. It is the largest always-on renderer the pack adds. | all; P1 VRAM, P0 everything | visual only | V/D |
| **2** | **Shader policy.** Default off, opt-in per machine. If on: MakeUp UF, or Complementary at ≤ MEDIUM profile, shadow distance ≤ 8 chunks, entity shadows off. | With any shaderpack active, **Flywheel marks both GPU backends unsupported** (`!ShadersModHelper.isShaderPackInUse()`, checked in source). Create then renders every kinetic block entity on the CPU, and again in the shadow pass. EMF/GeckoLib entities are also redrawn for shadows, and DH adds a shadow pass over all LODs. | all; P0 hardest | none (policy) | **V** |
| **3** | **Entity count, set on the server.** *Shipped in `d80f85b`, see `SPAWN-TUNING.md`.* Tune Alex's Mobs spawn weights: the 1.21.1 port has an open issue about inflated vanilla spawn rates (#62). Cap Fowlplay: #214 reports ~400 birds even after tuning. Cap Critters & Companions spawns via datapack (it has no config). | Every entity pipeline (vanilla, EMF/FA, GeckoLib, ETF, LambDynamicLights, Perception trails) multiplies by visible entity count. | all clients + server tick | gameplay feel | D/C |
| **4** | **EMF/ETF throttles.** `animationLODDistance` 20 → ~8–12. Consider `retainDetailOnLowFps=false` on P0. `modelUpdateFrequency` Average → Slow. Keep `animationFrameSkipDuringIrisShadowPass=true` (already the default, **V** observed). Treat the FA Extensions pack as opt-in. | FA turns every vanilla mob into an animated CEM model. EMF's frame-skip-by-distance is the throttle. | P0, P2 | animation smoothness at range | V |
| **5** | **JVM heap and GC per machine** (Prism settings; packwiz can't ship them). Heap: P0 5–6 G, P1 6–8 G, P2 ≤ 5–6 G (not the old instance's 8 G). Benchmark three GCs: client-tuned G1 (brucethemoose set), Generational ZGC (`-XX:+UseZGC -XX:+ZGenerational`; plain `UseZGC` on JDK 21 is the *non*-generational one), and Shenandoah. | DH warns about G1 itself. GC pauses show up as 1 % lows. P2's 8 GB headroom makes heap size a hard constraint. | P2, P0 | low | D/V |
| **6** | **ModernFix `mixin.perf.dynamic_resources=true`.** Off by default (**V** in the observed `modernfix-mixins.properties`). Also consider `dynamic_entity_renderers`. | 69k baked models and 16k blockstates sit in heap. The author documents ~−55 % memory even with FerriteCore. ModernFix has a CTM sub-option (`dynamic_resources.ctm`); test against Fusion and Athena. | P2, P0 (RAM) | med: missing textures or crashes with CTM/model mods | D/V |
| **7** | **Flywheel `lightSmoothness` SMOOTH → FLAT or TRI_LINEAR** (`flywheel-client.toml`). | The config comment itself says "may have a large performance impact". It only matters when shaders are off, i.e. when Flywheel is actually used. | all, Create-heavy bases | visual | V |
| **7b** | **Sable / SSRD sub-level budget.** Sable has no renderer of its own. Its `SODIUM_REACHAROUND` path runs Sodium's chunk renderer once per visible sub-level, so cost is roughly sub-levels × meshed sections, on top of terrain. Default the client SSRD distance low (8–16 chunks), not the 128-chunk server cap. Keep `sub_level_skylight_shadows=false` (the default, **V** observed). | Each airship is a second terrain pass. Under shaders, shadows also trigger open Iris #3164 sub-level corruption. | all; P0, P1 first | visual range of airships | V (config) / I (magnitude) |
| **8** | **LambDynamicLights**: mode → fast or fastest. On P0/P2 also set `light_sources.entities=false` (keep self). | Each moving light source triggers Sodium chunk-section rebuilds. On 4-core CPUs those compete with chunk builders, which default to 2 threads. | P2, P0 | less responsive light | V |
### Tier 2 — solid, smaller or more situational
| # | Lever | Helps | Conf. |
| --- | --- | --- | --- |
| 9 | **Sound Physics Remastered**: ray count 32 → 16, bounces 4 → 2, `strict_occlusion=true` (1 occlusion path instead of 9). Cap noisy Create and mob sounds in `sound_rates.properties`. Cost scales with concurrent sounds, so a Create floor is the worst case. | P2, P0 | V |
| 10 | **Vanilla and Sodium options**: render distance 8 to match the server (the old client had 12, which loads nothing extra in multiplayer). Biome blend 2 → 1 or 0. Mipmap 4 → 2 on P1/P0 (the atlas is ≥4096²). Entity distance 75–80 %. Sodium chunk threads auto-resolve to **2** on every fleet machine; try 3 on P1/P2. | all | V/I |
| 11 | **Better Leaves needs leaf culling** if anyone enables it. Use **Cull Leaves** (same author, made for it) or More Culling. **Do not use Sodium Leaf Culling**: it crashes on Sodium ≥ 0.8.11. | all, forests | D |
| 12 | **Twilight Forest / Alex's Caves client effects.** TF `cloudBlockPrecipitationDistance` → 0 and aurora toggles. AC `biome_ambient_light_coloring=false` for shader users. DH flags AC as partially incompatible (**V**, log). | in those dimensions | C/D |
| 13 | **Jade.** Pre-tune `hide-blocks.json` (player heads) and container-content display. Sophisticated Backpacks' "20–30 FPS when looking at a backpack" report was traced to Jade's content display. | all | C |
| 14 | **Bump Puzzles Lib 21.1.62 → 21.1.64**, as a *bugfix*, not a perf lever. The 21.1.64 cache is in `FabricEventFactory`, which isn't in the NeoForge jar. The NeoForge 62 → 64 diff is access-transformer restorations (21.1.63) plus one item-properties class. Applied in `a01bc3b`. | — | **V** (jar diff) |
| 15 | **Xaero's**: cap world-map loaded regions, keep the minimap modest. Radar as dots. | P2 heap | D |
| 16 | **WATUT**: raise `tickReceiveAndRenderRateOfGUIUpdates`, or `dontSendDetailedGUIInfo=true`. Watch the open TF conflict (#106). **Perception**: remove XP-orb trails. **Subtle Effects**: dust clouds off on P0. **FWA**: chain/lantern animations off on P0. | P0, P2 | V/C |
| 17 | **Platform**: `mesa_glthread=true` + gamemode on the Deck. NVCP "Prefer maximum performance" on P1. Leave threaded optimisation alone; Sodium manages it. | P0, P1 | D/C |
### Tier 3 — content weight (structural; decide after measuring)
- **Handcrafted + Chipped + Rechiseled** = 58 % of the atlas and 62 % of all models. They
cost startup time, heap (baked models) and some VRAM. Lever 6 mitigates the heap part.
Removing any of them is a content decision, but they're the only lever that moves startup
and model RAM materially. Chipped requires Athena, and Rechiseled requires Fusion.
- **Fresh Animations (+ Extensions)**: the largest documented entity-render multiplier,
especially with shaders (EMF #443, Oculus era: 165 → 40 FPS with 30+ mobs). It's already
opt-in because it's a resource pack. Keep it that way and say so in the player doc.
---
## 4. Mods to add (all confirmed with NeoForge 1.21.1 builds via the Modrinth API)
**Owner decisions, 2026-10-14:**
- Adding any helpful mod is allowed. Default Options, Sodium Extra, Reese's Sodium Options,
BadOptimizations and Particle Core were **added in `a01bc3b`**.
- The trials are bench block B12b.
- More Culling is out: its NeoForge build excludes 1.21.1.
- Shaders stay **opt-in**.
| Mod | Why | Verdict |
| --- | --- | --- |
| **Default Options** (`default-options`) | Lever M1 delivery mechanism; Balm already present | **Add** |
| **Sodium Extra** + **Reese's Sodium Options** | Expose particle, animation and fog toggles per machine. RSO works with Controlify, which matters on the Deck. | **Add** |
| **Particle Core** (`particle-core` 0.3.3) | Particle frustum culling, async ticking, per-type limits. Big particle sources here: Subtle Effects, Perception, Ars, Alex's Caves, Sooty Chimneys. | **Add, then verify** |
| **BadOptimizations** (2.4.1) | Lightmap and sky caching; self-disables on conflicts | **Add** |
| **Gnetum** (3.3.6) | Spreads HUD rendering over frames. This pack is HUD-heavy (Xaero's, Jade, AppleSkin, titles, plaques). The author says it's compatible with ImmediatelyFast. Replaces Exordium, which has no NF 1.21.1 build. | **Trial** |
| **Cull Leaves** or **More Culling** | Only if Better Leaves stays (lever 11) | **Add one** |
| **Better Block Entities** (1.3.4, built for Sodium 0.8.12–0.8.13) | Meshes static chests, signs and beds into terrain. Its EMF compat is only for 1.21.11+, so test it with FA. | **Trial** |
| **Flerovium** | Item, particle and entity render culling. Higher visual-glitch risk with layered models. | **Trial** |
| **Super Resolution** (alpha) | FSR upscaling. Potentially the biggest P0 lever, but alpha, and Complementary has no SR interface. | **Trial on P0 only** |
| **Ixeris** | Moves event polling off the render thread. Big win only on Windows with high-poll mice. Has open crash reports on older NVIDIA cards. | **Trial on P1** |
| **Iris Flywheel Compat** | Restores Flywheel instancing under shaders, which would neutralise most of lever 2's Create penalty. Unofficial and breaks on Create/Iris updates. | **Trial only if shaders are wanted** |
**Skip**: Sodium Leaf Culling (crashes), ScalableLux (breaks DH), Moonrise and C2ME on the
client (no client gain in multiplayer), Exordium / Krypton / Nvidium / VulkanMod / FastChest /
YOSBR (no NeoForge 1.21.1 build), Memory Leak Fix (obsolete), Sodium Options API (superseded
by Sodium 0.8), Sodium Dynamic Lights (duplicates LambDynamicLights).
## 5. Mods worth considering for removal
**Owner decision, 2026-10-14: remove no mods.** Kept for reference only. Mitigate with
config instead (spawn tuning: `analysis/client-perf/SPAWN-TUNING.md`).
Ordered by cost to value. None is urgent; measure first.
| Mod | Cost evidence | Note |
| --- | --- | --- |
| **Miner's Delight** | It's the *only* consumer of **Lodestone** (10 frame mixins incl. 3 overwrite/redirect, 59 shader files, post effects). **V** (dependency scan) | Removing one food addon drops a whole rendering library |
| **Fowlplay** | Open 1.21.1 issue: hundreds of birds; per-tick flight pathfinding (server) | Remove if spawn caps don't hold |
| **Charta** | 31 shader files registered at load; niche use | Only if nobody plays cards (both sides) |
| **WATUT** | Streams other players' GUI state each tick; open TF conflict | Flavour only |
| **Emojiful** | Font-render hooks; known chat freezes upstream | Low impact; social call |
| **Fresh Animations Extensions** | Extra CEM models on top of FA | Keep opt-in, recommend against on P0 |
| **AmbientSounds** | Largest jar (85 MB of ogg); RAM residency unmeasured | Measure heap first |
**Not removable, contrary to r4's suggestion**: one of Fusion/Athena. Chipped *requires*
Athena and Rechiseled *requires* Fusion (**V**, `neoforge.mods.toml` dependencies).
---
## 6. Phase 2 — test order
**Superseded by [`BENCH-MATRIX.md`](BENCH-MATRIX.md)**: world, scenes, config IDs, blocks
B0–B13 with decision rules.
## 7. Corrections to subagent research (verified by the orchestrator)
Added in Phase 1b (2026-10-14):
- **DH default threads is 4 on the fleet, not 2.** The default is `ceil(nproc × 0.5)`
(`ThreadPresetConfigEventHandler` @ 3.3.2). The observed `numberOfThreads = 2` came from
the owner's 4-thread machine (`nproc` = 4). Every other observed DH value matches the
source defaults.
- **Puzzles Lib 21.1.64 has no perf change on NeoForge** (see lever 14). r9's lead and this
file's earlier "the fix *is* in 21.1.64 for 1.21.1" were wrong for our loader.
- **r6's Shenandoah flags crash JDK 21.** `-XX:ShenandoahGCMode=iu` needs
`-XX:+UnlockExperimentalVMOptions` (tested on OpenJDK 21.0.12). Use plain
`-XX:+UseShenandoahGC`.
- **SSRD's distance is a COMMON config** (`config/ssrd-common.toml`,
`physicsRenderDistance`, default 128, slider floor 16), and the server reads it too. It
is not stored in Sodium's options.
- r4 "remove one CTM engine": wrong. Both are hard dependencies (see §5).
- r9 "Puzzles Lib ~8 % render-thread overhead": the measurement was on MC 26.2/Fabric. The
fix *is* in 21.1.64 for 1.21.1; the size of the gain there is unknown.
- r3/r8 cite EMF #443 for magnitude. That report is from the Oculus/Forge era and is closed.
Treat the 4× figure as directional only.
- Static scan's "Sable overrides vanilla core shaders" (r7 checked it): real but small, just `worldborder.*` from the nested Veil. The substantive part is Sable's runtime patching of Flywheel's internal shaders.
- r1's Flywheel-under-shaders claim was checked in `Backends.java` on the 1.21.1 branch:
confirmed.