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:
1 parent
d80f85b3f6
commit
611a1a5952
102 files changed
+25853
No files matched your search
@@ -0,0 +1,355 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the staged client-perf configs (tiers + Phase 2 bench variants).
|
||||
|
||||
Every override is applied to a *base* file and must hit a key that already
|
||||
exists in that base, otherwise the build fails. Bases are either the observed
|
||||
defaults generated by the exact pinned jar (DH 3.3.2, Sodium 0.8.13, EMF 3.3.9,
|
||||
Flywheel 1.0.6, Sable 2.0.5, SPR 1.5.1 - versions verified against the
|
||||
2026-09-24 launch log) or hand-authored templates whose keys were read from the
|
||||
pinned jar's source (see README.md, "Key provenance").
|
||||
|
||||
Output: out/<variant>/<path relative to the game dir>, plus out/MANIFEST.md.
|
||||
|
||||
python3 build.py # from this directory; stdlib only
|
||||
python3 build.py --ship # also copy T-default into the repo (see SHIP), then
|
||||
# run `packwiz refresh` from the repo root
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
OBSERVED = HERE.parent / "data" / "observed-default-configs"
|
||||
TEMPLATES = HERE / "templates"
|
||||
OUT = HERE / "out"
|
||||
REPO = HERE.parents[2]
|
||||
|
||||
# What `--ship` copies from out/T-default into the repo, and where.
|
||||
# Default Options seeds these client-side only, and only if the file is absent
|
||||
# (see README §1). Not shipped: sable-client.toml and sodium-options.json (no change
|
||||
# from upstream). modernfix-mixins.properties is held back until bench block B6
|
||||
# passes; when it does, add it here with destination "config/modernfix-mixins.properties"
|
||||
# (enforced packwiz file, because ModernFix reads it before Default Options runs) and
|
||||
# re-include it in .packwizignore.
|
||||
DO = "config/defaultoptions"
|
||||
SHIP = {
|
||||
"options.txt": f"{DO}/options.txt",
|
||||
"config/DistantHorizons.toml": f"{DO}/extra/config/DistantHorizons.toml",
|
||||
"config/entity_model_features.json": f"{DO}/extra/config/entity_model_features.json",
|
||||
"config/flywheel-client.toml": f"{DO}/extra/config/flywheel-client.toml",
|
||||
"config/lambdynlights.toml": f"{DO}/extra/config/lambdynlights.toml",
|
||||
"config/ssrd-common.toml": f"{DO}/extra/config/ssrd-common.toml",
|
||||
"config/sound_physics_remastered/soundphysics.properties":
|
||||
f"{DO}/extra/config/sound_physics_remastered/soundphysics.properties",
|
||||
"config/sound_physics_remastered/sound_rates.properties":
|
||||
f"{DO}/extra/config/sound_physics_remastered/sound_rates.properties",
|
||||
}
|
||||
|
||||
# game-dir path -> (base file, format, may_append_new_keys)
|
||||
FILES = {
|
||||
"options.txt": (TEMPLATES / "options.txt", "options", False),
|
||||
"config/DistantHorizons.toml": (OBSERVED / "DistantHorizons.toml", "toml", False),
|
||||
"config/sodium-options.json": (OBSERVED / "sodium-options.json", "json", False),
|
||||
"config/entity_model_features.json": (OBSERVED / "entity_model_features.json", "json", False),
|
||||
"config/flywheel-client.toml": (OBSERVED / "flywheel-client.toml", "toml", False),
|
||||
"config/sable-client.toml": (OBSERVED / "sable-client.toml", "toml", False),
|
||||
"config/ssrd-common.toml": (TEMPLATES / "config/ssrd-common.toml", "toml", False),
|
||||
"config/lambdynlights.toml": (TEMPLATES / "config/lambdynlights.toml", "toml", False),
|
||||
"config/modernfix-mixins.properties": (TEMPLATES / "config/modernfix-mixins.properties", "properties", False),
|
||||
"config/sound_physics_remastered/soundphysics.properties": (
|
||||
OBSERVED / "sound_physics_remastered/soundphysics.properties", "properties", False),
|
||||
# SPR merges partial files over its generated defaults (SoundRateConfig.load)
|
||||
"config/sound_physics_remastered/sound_rates.properties": (
|
||||
TEMPLATES / "config/sound_physics_remastered/sound_rates.properties", "properties", True),
|
||||
}
|
||||
|
||||
DH_Q = "client.advanced.graphics.quality"
|
||||
DH_G = "client.advanced.graphics"
|
||||
DH_T = "common.multiThreading"
|
||||
DH_W = "common.worldGenerator"
|
||||
|
||||
# Create 6.0.10 sound IDs (from the observed sound_rates.properties, same Create version).
|
||||
# Over the cap, SPR plays the sound dry (SoundPhysics.java: setDefaultEnvironment), it is not dropped.
|
||||
CREATE_NOISY = [
|
||||
"create:cogs", "create:mixing", "create:mixing_compounded_1",
|
||||
"create:mechanical_press_activation", "create:mechanical_press_activation_compounded_1",
|
||||
"create:mechanical_press_activation_belt", "create:mechanical_press_activation_belt_compounded_1",
|
||||
"create:crushing_1", "create:crushing_2", "create:crushing_3",
|
||||
"create:saw_activate_stone", "create:saw_activate_wood",
|
||||
"create:funnel_flap", "create:funnel_flap_compounded_1",
|
||||
"create:depot_slide", "create:depot_plop", "create:steam",
|
||||
"create:sanding_short", "create:sanding_long",
|
||||
]
|
||||
|
||||
|
||||
def sound_caps(n):
|
||||
return {s.replace(":", "\\:"): n for s in CREATE_NOISY}
|
||||
|
||||
|
||||
# Observed DH values were computed on a 4-thread box; DH's real default is
|
||||
# ceil(nproc * 0.5) (ThreadPresetConfigEventHandler) = 4 on every 8-thread fleet
|
||||
# machine. Applied first to every variant so C0 is the fleet's true default.
|
||||
UPSTREAM = {"config/DistantHorizons.toml": {(DH_T, "numberOfThreads"): 4}}
|
||||
|
||||
# Per-install values that must not ship: DH regenerates missing keys with their
|
||||
# default (ConfigFileHandler.loadEntry -> saveEntry); serverId is new Random().nextInt().
|
||||
STRIP = {"config/DistantHorizons.toml": [("server", "serverId")]}
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Tiers. "default" is the pack-wide conservative default; deck/high are deltas
|
||||
# on top of it; mid == default.
|
||||
# --------------------------------------------------------------------------
|
||||
DEFAULT = {
|
||||
"options.txt": {
|
||||
"renderDistance": 8, # server view-distance is 8; more loads nothing in MP
|
||||
"simulationDistance": 8, # singleplayer only
|
||||
"biomeBlendRadius": 1, # 3x3; chunk-build cost (Sodium tooltip)
|
||||
"mipmapLevels": 2, # block atlas is >=4096^2
|
||||
"entityDistanceScaling": 0.75, # slider steps are 0.25
|
||||
},
|
||||
"config/DistantHorizons.toml": {
|
||||
(DH_Q, "lodChunkRenderDistanceRadius"): 128, # default 256
|
||||
(DH_G, "enableSsao"): False, # default true
|
||||
(DH_T, "numberOfThreads"): 2, # default ceil(nproc*0.5) = 4 on the fleet
|
||||
},
|
||||
"config/entity_model_features.json": {
|
||||
"animationLODDistance": 12, # default 20; lower = more frame skipping
|
||||
"modelUpdateFrequency": "Slow", # default Average; model refresh, not animation
|
||||
},
|
||||
"config/flywheel-client.toml": {
|
||||
("flw_backends", "lightSmoothness"): "TRI_LINEAR", # default SMOOTH
|
||||
},
|
||||
"config/ssrd-common.toml": {
|
||||
("", "physicsRenderDistance"): 16, # default 128; slider floor is minPhysicsRenderDistance=16
|
||||
},
|
||||
"config/lambdynlights.toml": {
|
||||
("", "mode"): "fast", # default fancy; fast = TickMode.SLOW
|
||||
},
|
||||
"config/modernfix-mixins.properties": {
|
||||
"mixin.perf.dynamic_resources": "true", # GATED on bench block B6
|
||||
},
|
||||
"config/sound_physics_remastered/soundphysics.properties": {
|
||||
"environment_evaluation_ray_count": 16, # default 32
|
||||
"environment_evaluation_ray_bounces": 2, # default 4
|
||||
},
|
||||
"config/sound_physics_remastered/sound_rates.properties": sound_caps(4),
|
||||
}
|
||||
|
||||
DECK = {
|
||||
"options.txt": {"biomeBlendRadius": 0, "particles": 1, "entityShadows": "false"},
|
||||
# = DH's own LOW quality preset (RenderQualityPresetConfigEventHandler) + radius 64
|
||||
"config/DistantHorizons.toml": {
|
||||
(DH_Q, "lodChunkRenderDistanceRadius"): 64,
|
||||
(DH_Q, "horizontalQuality"): "LOW",
|
||||
(DH_Q, "verticalQuality"): "LOW",
|
||||
(DH_Q, "transparency"): "DISABLED",
|
||||
(DH_Q, "vanillaFadeMode"): "SINGLE_PASS",
|
||||
(DH_Q, "lodBiomeBlending"): 1,
|
||||
},
|
||||
"config/sodium-options.json": {
|
||||
"quality.weather_quality": "FAST",
|
||||
"quality.leaves_quality": "FAST",
|
||||
},
|
||||
"config/entity_model_features.json": {
|
||||
"animationLODDistance": 8,
|
||||
"retainDetailOnLowFps": False,
|
||||
},
|
||||
"config/flywheel-client.toml": {("flw_backends", "lightSmoothness"): "FLAT"},
|
||||
"config/lambdynlights.toml": {
|
||||
("", "mode"): "fastest",
|
||||
("light_sources", "entities"): False,
|
||||
},
|
||||
"config/sound_physics_remastered/soundphysics.properties": {"strict_occlusion": "true"},
|
||||
}
|
||||
|
||||
HIGH = { # strong GPU, but still a 4C/8T CPU (true of every fleet machine)
|
||||
"options.txt": {"mipmapLevels": 4, "biomeBlendRadius": 2, "entityDistanceScaling": 1.0},
|
||||
"config/DistantHorizons.toml": {(DH_G, "enableSsao"): True},
|
||||
"config/flywheel-client.toml": {("flw_backends", "lightSmoothness"): "SMOOTH"},
|
||||
"config/ssrd-common.toml": {("", "physicsRenderDistance"): 32},
|
||||
"config/lambdynlights.toml": {("light_sources", "entities"): False},
|
||||
"config/sound_physics_remastered/soundphysics.properties": {"strict_occlusion": "true"},
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Phase 2 bench variants. C0 = every file at upstream default (explicit, so stale
|
||||
# instance edits can't leak in). Everything else is C1 (= T-mid) plus ONE change.
|
||||
# BENCH is layered on every bench variant: uncapped, vsync off, phase0 pins,
|
||||
# no DH world generation during runs (world is pre-generated).
|
||||
# --------------------------------------------------------------------------
|
||||
BENCH = {
|
||||
"options.txt": {"maxFps": 260, "enableVsync": "false", "renderDistance": 8,
|
||||
"simulationDistance": 8},
|
||||
"config/DistantHorizons.toml": {(DH_W, "generatorPlan"): "DISABLED"},
|
||||
}
|
||||
|
||||
|
||||
def c1(extra):
|
||||
return [DEFAULT, extra]
|
||||
|
||||
|
||||
VARIANTS = {
|
||||
# player tiers (no BENCH layer)
|
||||
"T-default": ([DEFAULT], False),
|
||||
"T-deck": ([DEFAULT, DECK], False),
|
||||
"T-mid": ([DEFAULT], False),
|
||||
"T-high": ([DEFAULT, HIGH], False),
|
||||
# bench: composites
|
||||
"C0": ([], True),
|
||||
"C1": ([DEFAULT], True),
|
||||
"C1-deck": ([DEFAULT, DECK], True),
|
||||
"C1-high": ([DEFAULT, HIGH], True),
|
||||
# bench: Distant Horizons
|
||||
"dh-r256": (c1({"config/DistantHorizons.toml": {(DH_Q, "lodChunkRenderDistanceRadius"): 256}}), True),
|
||||
"dh-r192": (c1({"config/DistantHorizons.toml": {(DH_Q, "lodChunkRenderDistanceRadius"): 192}}), True),
|
||||
"dh-r64": (c1({"config/DistantHorizons.toml": {(DH_Q, "lodChunkRenderDistanceRadius"): 64}}), True),
|
||||
"dh-ssao": (c1({"config/DistantHorizons.toml": {(DH_G, "enableSsao"): True}}), True),
|
||||
"dh-thr4": (c1({"config/DistantHorizons.toml": {(DH_T, "numberOfThreads"): 4}}), True),
|
||||
"dh-qlow": (c1({"config/DistantHorizons.toml": DECK["config/DistantHorizons.toml"]
|
||||
| {(DH_Q, "lodChunkRenderDistanceRadius"): 128}}), True),
|
||||
# bench: Flywheel / ModernFix / Sable / SSRD
|
||||
"fw-flat": (c1({"config/flywheel-client.toml": {("flw_backends", "lightSmoothness"): "FLAT"}}), True),
|
||||
"fw-smooth": (c1({"config/flywheel-client.toml": {("flw_backends", "lightSmoothness"): "SMOOTH"}}), True),
|
||||
"mf-dynres-off": (c1({"config/modernfix-mixins.properties": {"mixin.perf.dynamic_resources": "false"}}), True),
|
||||
"ssrd-32": (c1({"config/ssrd-common.toml": {("", "physicsRenderDistance"): 32}}), True),
|
||||
"ssrd-64": (c1({"config/ssrd-common.toml": {("", "physicsRenderDistance"): 64}}), True),
|
||||
"ssrd-128": (c1({"config/ssrd-common.toml": {("", "physicsRenderDistance"): 128}}), True),
|
||||
"sable-noshade": (c1({"config/sable-client.toml": {("", "sub_level_dynamic_shading"): False}}), True),
|
||||
# bench: entities / lights / sound
|
||||
"emf-lod20": (c1({"config/entity_model_features.json": {"animationLODDistance": 20,
|
||||
"modelUpdateFrequency": "Average"}}), True),
|
||||
"emf-lod8": (c1({"config/entity_model_features.json": {"animationLODDistance": 8,
|
||||
"retainDetailOnLowFps": False}}), True),
|
||||
"ldl-fancy": (c1({"config/lambdynlights.toml": {("", "mode"): "fancy"}}), True),
|
||||
"ldl-fastest": (c1({"config/lambdynlights.toml": {("", "mode"): "fastest",
|
||||
("light_sources", "entities"): False}}), True),
|
||||
"spr-up": (c1({"config/sound_physics_remastered/soundphysics.properties": {
|
||||
"environment_evaluation_ray_count": 32, "environment_evaluation_ray_bounces": 4}}), True),
|
||||
"spr-strict": (c1({"config/sound_physics_remastered/soundphysics.properties": {"strict_occlusion": "true"}}), True),
|
||||
"spr-nocap": (c1({"config/sound_physics_remastered/sound_rates.properties": sound_caps(-1)}), True),
|
||||
# bench: vanilla / Sodium
|
||||
"sod-thr3": (c1({"config/sodium-options.json": {"performance.chunk_builder_threads": 3}}), True),
|
||||
"opt-blend2": (c1({"options.txt": {"biomeBlendRadius": 2}}), True),
|
||||
"opt-blend0": (c1({"options.txt": {"biomeBlendRadius": 0}}), True),
|
||||
"opt-mip4": (c1({"options.txt": {"mipmapLevels": 4}}), True),
|
||||
}
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
def toml_val(v):
|
||||
if isinstance(v, bool):
|
||||
return "true" if v else "false"
|
||||
if isinstance(v, (int, float)):
|
||||
return str(v)
|
||||
return json.dumps(v)
|
||||
|
||||
|
||||
def apply_toml(text, ov, name):
|
||||
lines = text.split("\n")
|
||||
hdr = re.compile(r"^\s*\[([^\]]+)\]\s*$")
|
||||
for (section, key), val in ov.items():
|
||||
cur, hits = "", []
|
||||
for i, ln in enumerate(lines):
|
||||
m = hdr.match(ln)
|
||||
if m:
|
||||
cur = m.group(1).strip()
|
||||
continue
|
||||
if cur == section and re.match(rf"^\s*{re.escape(key)}\s*=", ln):
|
||||
hits.append(i)
|
||||
if len(hits) != 1:
|
||||
raise SystemExit(f"{name}: [{section}] {key}: {len(hits)} matches in base")
|
||||
i = hits[0]
|
||||
indent = re.match(r"^(\s*)", lines[i]).group(1)
|
||||
lines[i] = f"{indent}{key} = {toml_val(val)}"
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def apply_json(text, ov, name):
|
||||
data = json.loads(text)
|
||||
for path, val in ov.items():
|
||||
node, parts = data, path.split(".")
|
||||
for p in parts[:-1]:
|
||||
node = node[p]
|
||||
if parts[-1] not in node:
|
||||
raise SystemExit(f"{name}: {path} not in base")
|
||||
if type(node[parts[-1]]) is not type(val):
|
||||
raise SystemExit(f"{name}: {path} type {type(node[parts[-1]]).__name__} != {type(val).__name__}")
|
||||
node[parts[-1]] = val
|
||||
return json.dumps(data, indent=2) + "\n"
|
||||
|
||||
|
||||
def apply_kv(text, ov, name, sep, append):
|
||||
lines = text.rstrip("\n").split("\n")
|
||||
for key, val in ov.items():
|
||||
sval = toml_val(val) if isinstance(val, bool) else str(val)
|
||||
rx = re.compile(rf"^{re.escape(key)}{re.escape(sep)}")
|
||||
hits = [i for i, ln in enumerate(lines) if rx.match(ln)]
|
||||
if len(hits) > 1 or (not hits and not append):
|
||||
raise SystemExit(f"{name}: {key}: {len(hits)} matches in base")
|
||||
if hits:
|
||||
lines[hits[0]] = f"{key}{sep}{sval}"
|
||||
else:
|
||||
lines.append(f"{key}{sep}{sval}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def strip(text, keys, name):
|
||||
lines = text.split("\n")
|
||||
hdr = re.compile(r"^\s*\[([^\]]+)\]\s*$")
|
||||
for section, key in keys:
|
||||
cur, hits = "", []
|
||||
for i, ln in enumerate(lines):
|
||||
m = hdr.match(ln)
|
||||
if m:
|
||||
cur = m.group(1).strip()
|
||||
elif cur == section and re.match(rf"^\s*{re.escape(key)}\s*=", ln):
|
||||
hits.append(i)
|
||||
if len(hits) != 1:
|
||||
raise SystemExit(f"{name}: strip [{section}] {key}: {len(hits)} matches")
|
||||
del lines[hits[0]]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def render(layers, name):
|
||||
base, fmt, append = FILES[name]
|
||||
text = strip(base.read_text(), STRIP.get(name, []), name)
|
||||
merged = {}
|
||||
for layer in [UPSTREAM] + layers:
|
||||
merged.update(layer.get(name, {}))
|
||||
if fmt == "toml":
|
||||
return apply_toml(text, merged, name), merged
|
||||
if fmt == "json":
|
||||
return apply_json(text, merged, name), merged
|
||||
sep = ":" if fmt == "options" else "="
|
||||
return apply_kv(text, merged, name, sep, append), merged
|
||||
|
||||
|
||||
def main():
|
||||
if OUT.exists():
|
||||
shutil.rmtree(OUT)
|
||||
manifest = ["# Generated by build.py - do not edit\n",
|
||||
"Changed keys per variant, relative to the upstream default of each file.\n"]
|
||||
for vid, (layers, bench) in VARIANTS.items():
|
||||
layers = list(layers) + ([BENCH] if bench else [])
|
||||
manifest.append(f"\n## {vid}\n")
|
||||
for name in FILES:
|
||||
text, merged = render(layers, name)
|
||||
dest = OUT / vid / name
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
dest.write_text(text)
|
||||
for k, v in merged.items():
|
||||
k = f"[{k[0]}] {k[1]}" if isinstance(k, tuple) else k
|
||||
manifest.append(f"- `{name}`: `{k}` = `{v}`\n")
|
||||
(OUT / "MANIFEST.md").write_text("".join(manifest))
|
||||
print(f"wrote {len(VARIANTS)} variants x {len(FILES)} files to {OUT}")
|
||||
if "--ship" in sys.argv:
|
||||
shutil.rmtree(REPO / DO, ignore_errors=True)
|
||||
for src, dst in SHIP.items():
|
||||
(REPO / dst).parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(OUT / "T-default" / src, REPO / dst)
|
||||
print(f"shipped {len(SHIP)} files to {REPO / DO}; now run `packwiz refresh`")
|
||||
|
||||
|
||||
main()
|
||||
Reference in new issue
Block a user