Files
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

108 lines
5.6 KiB
Python

"""Generate config/paxi/datapacks/onion-fixes from the audit findings.
Run from repo root after extract.py has built /tmp/onion/data.pkl."""
import json, os, pickle, shutil, copy
D = pickle.load(open("/tmp/onion/data.pkl", "rb"))
orig = {}
for jar, p, j in D["files"]:
orig.setdefault(p, {})[jar.split("-")[0]] = j
OUT = "config/paxi/datapacks/onion-fixes"
shutil.rmtree(OUT, ignore_errors=True)
def write(path, obj):
f = os.path.join(OUT, path)
os.makedirs(os.path.dirname(f), exist_ok=True)
with open(f, "w") as fh:
json.dump(obj, fh, indent=2); fh.write("\n")
def recipe(rid, obj):
ns, p = rid.split(":")
write(f"data/{ns}/recipe/{p}.json", obj)
def disable(rid):
recipe(rid, {"neoforge:conditions": [{"type": "neoforge:false"}]})
def src(path, mod):
return copy.deepcopy(orig[path][mod])
write("pack.mcmeta", {"pack": {"pack_format": 48,
"description": "Onion MC cross-mod recipe/tag fixes"}})
KNIFE = {"tag": "c:tools/knife"}
def cut(ing, out, n):
return {"type": "farmersdelight:cutting", "ingredients": [ing],
"result": [{"item": {"id": out, "count": n}}], "tool": KNIFE}
# 1. Potato: one board recipe per input, chained so each mod keeps its yield.
# potato -> 2 rustic slices (rustic's recipe, untouched)
# rustic slice -> 2 extradelight sliced potato (4/potato, as before)
# 2 rustic slices -> 1 moredelight diced potatoes (1/potato, as before;
# moredelight's Create saw recipe still gives 2/potato)
recipe("extradelight:cutting/sliced_potato",
cut({"item": "rusticdelight:potato_slices"}, "extradelight:sliced_potato", 2))
disable("moredelight:cutting/diced_potatoes")
recipe("onion:diced_potatoes_from_potato_slices", {
"type": "minecraft:crafting_shapeless", "category": "misc",
"ingredients": [{"item": "rusticdelight:potato_slices"}] * 2,
"result": {"id": "moredelight:diced_potatoes", "count": 1}})
# Slices are not whole potatoes (dupe loop via stews / re-cutting).
write("data/c/tags/item/crops/potato.json", {"replace": True, "values": [
"minecraft:potato", "veggiesdelight:sweet_potato",
{"id": "#forge:crops/potato", "required": False}]})
# 2. Bread: extradelight's slice wins; moredelight's sandwiches use c:bread_slices.
# Widen extradelight's input to c:foods/bread so cauliflower bread still slices.
r = src("data/extradelight/recipe/cutting/bread_knife.json", "extradelight")
r["ingredients"] = [{"tag": "c:foods/bread"}]
recipe("extradelight:cutting/bread_knife", r)
disable("moredelight:cutting/bread_slice")
# 3. Coffee: raw rustic beans are not roasted beans; crates use their own items.
write("data/c/tags/item/crops/coffee/cooked.json", {"replace": True, "values": [
"extradelight:coffee_beans", "rusticdelight:roasted_coffee_beans"]})
for rid, item in [("extradelight:coffee_cherries_to_block", "extradelight:coffee_cherries"),
("extradelight:coffee_beans_to_block", "extradelight:coffee_beans")]:
ns, p = rid.split(":")
r = src(f"data/{ns}/recipe/{p}.json", "extradelight")
r["ingredients"] = [{"item": item}] * 9
recipe(rid, r)
# 4. Twilight Forest knives: twilightdelight owns them; drop dungeonsdelight's copies.
for k in ["fiery_knife", "fiery_iron_knife", "ironwood_knife", "knightmetal_knife", "steeleaf_knife"]:
disable(f"dungeonsdelight:integration/twilightforest/crafting_table/{k}")
# 5. Crates: keep one crafting recipe per crate (uncrafting recipes untouched).
disable("minecraft:poisonous_potato_crate") # dungeonsdelight's; frightsdelight keeps it
disable("minecraft:rotten_tomato_crate") # dungeonsdelight's; frightsdelight keeps it
disable("fruitsdelight:apple_crate") # extradelight keeps it
disable("adorablehamsterpets:cucumber_crate") # extradelight's accepts any c:crops/cucumber
# 6. Handcrafted sheets: shapeless 3 wool collided with Comforts' vertical
# sleeping bags; make sheets a horizontal row.
COLORS = "white orange magenta light_blue yellow lime pink gray light_gray cyan purple blue brown green red black".split()
for c in COLORS:
r = src(f"data/handcrafted/recipe/{c}_sheet.json", "handcrafted")
recipe(f"handcrafted:{c}_sheet", {"type": "minecraft:crafting_shaped",
"category": r.get("category", "misc"), "key": {"#": r["ingredients"][0]},
"pattern": ["###"], "result": r["result"]})
# 7. Handcrafted wood plate (any 3 slabs in a row) shadowed all TF drying racks.
r = src("data/handcrafted/recipe/wood_plate.json", "handcrafted")
r["pattern"] = ["##"]
r["result"]["count"] = 2
recipe("handcrafted:wood_plate", r)
# 8. Fisherman's Haven worms can: one recipe, plus 1:1 worm conversions.
disable("fishermans_haven:recipe_worms_aquaculture")
for a, b in [("fishermans_haven:worm", "aquaculture:worm"), ("aquaculture:worm", "fishermans_haven:worm")]:
recipe(f"onion:{b.split(':')[0]}_worm_from_{a.split(':')[0]}_worm", {
"type": "minecraft:crafting_shapeless", "category": "misc",
"ingredients": [{"item": a}], "result": {"id": b, "count": 1}})
# 9. Twilight Delight ice creams need Neapolitan (absent): gate instead of log errors.
for k in ["rainbow_ice_cream", "refreshing_ice_cream", "twilight_ice_cream"]:
r = src(f"data/twilightdelight/recipe/{k}.json", "twilightdelight")
r = {"neoforge:conditions": [{"type": "neoforge:mod_loaded", "modid": "neapolitan"}], **r}
recipe(f"twilightdelight:{k}", r)
# 10. Beef stew: rustic replaces it, extradelight disables it -> load-order roulette.
# Pin rustic's version so the cooking pot recipe always exists.
recipe("farmersdelight:cooking/beef_stew",
src("data/farmersdelight/recipe/cooking/beef_stew.json", "rusticdelight"))
print("ok")