Files
onion-mc/phase0/P0-steamdeck.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

9.7 KiB
Raw Blame History

Phase 0 — P0: Steam Deck

Setup and measurement procedure for the lowest-end test machine in the Onion MC client tuning fleet.


Profile

CPU AMD Zen 2, 4 cores / 8 threads, 2.4–3.5 GHz
GPU AMD RDNA2 (Van Gogh), 8 CU, integrated
RAM 16 GB LPDDR5, shared with the GPU
Display 1280×800 — LCD is 60 Hz, OLED is 90 Hz
OS SteamOS (Arch-based, read-only rootfs)
Binding constraint GPU compute, memory bandwidth, shared memory pressure
What this tier tests The floor. If the ladder holds here it holds anywhere.
Expected DH answer Off, or the minimum viable preset

Determine your panel

The LCD panel is 60 Hz. If this is an LCD Deck, targets of 120 and 144 are meaningless on this machine — it can only validate the 30 and 60 rows of the ladder. Record which panel you have; it changes what this machine can prove.


Shared measurement protocol

Applies to every machine in the fleet. Duplicated here so this document stands alone.

  1. Uncapped, vsync off during measurement. Apply the FPS cap only after tuning. With a cap or vsync active, every configuration "hits" the target and the ladder never fires.
  2. Metric is p99 frametime and 1% low, not average FPS.
  3. Discard 10–15 s of warmup per scene — JIT, chunk load, initial GC.
  4. Settle after any render-distance or DH change. Both trigger rebuilds; measuring immediately measures the rebuild, not the steady state.
  5. Three runs per configuration, report the median and the spread. A single run of anything is noise.
  6. One variable per run. If you change two things, you have learned nothing.
  7. Guardrails: if the FPS limiter is active, the governor moved, or RAM headroom changed materially, the run is invalid — discard rather than interpret.

Step 1 — Preconditions

  • Plugged into the charger. Battery vs. AC changes APU power behaviour.
  • Switch to Desktop Mode. All measurement happens here, launching Prism from a terminal. Gaming Mode routes the overlay through MangoApp, which cannot log.
  • Instance installed on internal storage, not a microSD card. Card I/O affects chunk loading and DH LOD reads.
  • Confirm panel refresh rate and record it.

Step 2 — Disable the competing overlay

SteamOS's built-in performance overlay is MangoApp, not MangoHud. It is an IPC bridge and cannot write CSV logs. Running it alongside MangoHud produces two stacked overlays and aborts logging.

  • Quick Access Menu (...) → Performance
  • Performance Overlay Level → 0
  • Framerate Limit → Off (critical — otherwise every config "hits" the cap)
  • TDP Limit → Off, or pin it to a fixed value (e.g. 15 W) and never change it between sessions. Whichever you choose, record it and keep it constant.
  • Check that no per-game profile is re-enabling any of the above.
  • If Decky Loader is installed, disable any MangoHud/benchmark plugin that routes through MangoApp.

Step 3 — CPU governor and thermals

The Deck's governor is normally schedutil. Two options:

  • Recommended: leave it as SteamOS sets it, but record it (cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor) and keep it identical across every session.
  • Pinning performance gives more consistent clocks but increases heat on a handheld, which risks thermal throttling — usually a worse trade for repeatability.

Thermal soak: let the Deck play for ~5 minutes before the first measured run. A cold Deck boosts then throttles; the first runs of a session are not representative. This is the single biggest source of bogus deltas on this machine.

Step 4 — Install MangoHud

SteamOS's rootfs is read-only, and anything installed via pacman can be removed by a SteamOS update. Re-check this before every session.

sudo steamos-readonly disable
sudo pacman -S --needed mangohud
sudo steamos-readonly enable

Then find the actual paths — do not assume them:

pacman -Ql mangohud | grep -E 'mangohud$|libMangoHud'

This tells you the wrapper binary and the shared library path. Note them; you will need them if the wrapper route fails.

Alternative if you don't want to touch the rootfs: MangoHudPy is built for SteamOS/Bazzite and manages MangoHud configs and log uploads. Verify it can write CSV logs before relying on it — some SteamOS MangoHud tooling routes through the MangoApp IPC shim, which explicitly cannot write logs.

Step 5 — MangoHud configuration

Generate the config from the packaged example rather than trusting a transcription:

mkdir -p ~/.config/MangoHud ~/mcbench/logs
cp /usr/share/doc/mangohud/MangoHud.conf ~/.config/MangoHud/MangoHud.conf   # verify path with pacman -Ql

Then set these keys in ~/.config/MangoHud/MangoHud.conf:

output_folder=/home/deck/mcbench/logs    # required for logging
autostart_log=1                          # start logging automatically
log_duration=30                          # seconds; match your scene length
fps_limit=0                              # uncapped
frametime=1
frame_timing=1
# percentiles: see the packaged example for the exact key/syntax — it defines a
# '+'-separated list of percentiles emitted in the benchmark summary

Confirm the exact percentile key name against the packaged example file. Logging can also be toggled at runtime (toggle_logging, default Shift_L+F2).

Step 6 — Launcher and JVM

Use Prism Launcher (or ATLauncher) with the pack imported, then launch it from a terminal in Desktop Mode so the environment is under your control.

Java: 21 (Arch jre21-openjdk or Temurin 21). Use the same JVM build across all three machines where possible — different JVM builds are a confound.

Wrapper command — Prism → Edit Instance → Settings → Custom Commands → Wrapper command. This is the most reliable way to hook the Java process directly:

mangohud

If no overlay appears, or Minecraft fails to start, try:

mangohud --dlsym

JVM arguments (held identical across all three machines; only -Xmx differs):

-Xmx5G
-XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200
-XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC
-XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M
-XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4
-XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90
-XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32
-XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1

On -Xmx5G: the Deck's 16 GB is shared with the GPU, so heap is not free. Start at 5 G, never exceed 6 G, and treat the final value as a Phase 0 output derived from GC data, not a guess. Omit -XX:+AlwaysPreTouch here — committing the whole heap up front costs memory you may not have.

Step 7 — Pin the in-game settings

These are controlled variables. Set them once, record them, and do not change them between runs unless the run is specifically testing that setting.

  • Graphics: Fancy
  • Render distance: 8 (matches the server's view-distance)
  • Particles: All
  • Clouds: Fancy
  • Entity shadows: On
  • Biome blend: fixed value, recorded
  • Mipmap levels: 4
  • GUI scale: fixed number (not Auto)
  • Max framerate: Unlimited
  • VSync: Off
  • FOV, resolution, fullscreen mode: fixed and recorded

Step 8 — Verification run

Prove the harness works before collecting any real data.

  • Launch the pack from Prism with the wrapper command set.
  • Confirm the MangoHud overlay appears over Minecraft.
  • Load the benchmark scene, play 30 s.
  • Confirm a CSV file appeared in ~/mcbench/logs.
  • Open the CSV and confirm it contains a frametime column.
  • Run the same configuration three times and check the spread. If the spread is large, nothing downstream is trustworthy — fix that before continuing.

Step 9 — Per-session checklist

  • Charger connected
  • Desktop Mode
  • SteamOS overlay level 0, FPS limit off, TDP as recorded
  • Governor matches previous sessions
  • MangoHud still installed (SteamOS update may have removed it)
  • 5-minute thermal soak completed
  • CSV output folder writable

Step 10 — Recording sheet

Log one row per run:

Field Example
Date / session 2026-09-28-a
Machine P0-SteamDeck-LCD
Config ID baseline / dh-off / emf-off / …
Scene A-base / B-terrain / C-gui
Run # 1 / 2 / 3
CSV file Minecraft_2026-09-28_14-22-01.csv
p50 / p95 / p99 frametime (ms)
1% low FPS
Avg FPS
Peak measured frametime (ms)
Notes e.g. "thermals settled", "DH disabled"

Machine-specific caveats

  • The overlay trap. SteamOS gives you MangoApp, which looks like MangoHud and cannot write logs. Getting CSV requires launching through the MangoHud wrapper, in Desktop Mode, not through Gaming Mode.
  • VRAM is not measurable here. The GPU shares system memory, so there is no separate VRAM number to track. Watch total system RAM and swap instead; memory pressure is what you will actually see.
  • SteamOS updates remove pacman-installed packages. Verify MangoHud exists at the start of every session.
  • 60 Hz panel (LCD) cannot validate 120/144 targets. Document this rather than pretending those rows are tested.
  • Smallest thermal envelope of the fleet. The warm-up soak is not optional.
  • Per-core performance is much lower than the other two machines despite the identical 4C/8T topology. Do not assume a CPU-side result from P1/P2 transfers here at the same magnitude — only the direction should be expected to match.