Files
onion-mc/analysis/audit/recipes.py
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

162 lines
7.3 KiB
Python

import pickle,json,collections,re,itertools
D=pickle.load(open("data.pkl","rb"))
mods=set(D["mods"])|{"minecraft","c","neoforge"}
files=D["files"]
# ---------- resource-level overrides ----------
byres=collections.defaultdict(list)
for jar,path,j in files: byres[path].append((jar,j))
legacy=collections.Counter()
for path,l in byres.items():
p=path.split("/")
if p[2]=="recipes" or (p[2]=="tags" and p[3] in("items","blocks")): legacy[l[0][0]]+=1
# ---------- tags ----------
def is_tag_path(p): return p[2]=="tags" and p[3]=="item"
tagdefs=collections.defaultdict(list) # tag -> [(jar, values, replace)]
for path,l in byres.items():
p=path.split("/")
if not is_tag_path(p): continue
tag=p[1]+":"+"/".join(p[4:])[:-5]
for jar,j in l:
if not isinstance(j,dict): continue
tagdefs[tag].append((jar,j.get("values",[]),j.get("replace",False)))
items=set()
for k in D["lang"]:
_,ns,pa=k.split("."); items.add(ns+":"+pa)
items|=D["models"]
def ns(x): return x.split(":")[0] if ":" in x else "minecraft"
def full(x): return x if ":" in x else "minecraft:"+x
tagcache={}; tagissues=[]
def resolve(tag,stack=()):
if tag in tagcache: return tagcache[tag]
if tag in stack: return set()
out=set()
for jar,vals,rep in tagdefs.get(tag,[]):
if rep: out=set()
for v in vals:
req=True
if isinstance(v,dict): req=v.get("required",True); v=v.get("id")
if not isinstance(v,str): continue
if v.startswith("#"):
t=full(v[1:])
if req and t not in tagdefs: tagissues.append((tag,jar,v,"missing-tag"))
out|=resolve(t,stack+(tag,))
else:
v=full(v)
if v not in items:
if req: tagissues.append((tag,jar,v,"missing-item" if ns(v) in mods else "missing-mod-item"))
continue
out.add(v)
tagcache[tag]=out; return out
for t in list(tagdefs): resolve(t)
# ---------- conditions ----------
unknown_cond=collections.Counter()
def cond(c):
if isinstance(c,list): return all(cond(x) for x in c)
t=c.get("type","")
if t in("neoforge:mod_loaded","forge:mod_loaded"): return c.get("modid") in mods
if t in("neoforge:not","forge:not"): return not cond(c["value"])
if t in("neoforge:and","forge:and"): return all(cond(x) for x in c["values"])
if t in("neoforge:or","forge:or"): return any(cond(x) for x in c["values"])
if t in("neoforge:item_exists","forge:item_exists"): return full(c["item"]) in items
if t in("neoforge:tag_empty","forge:tag_empty"): return not resolve(full(c["tag"]))
if t in("neoforge:true","forge:true"): return True
if t in("neoforge:false","forge:false"): return False
unknown_cond[t]+=1; return True
# ---------- ingredients ----------
def ing(x):
"""-> frozenset of items, or None if unknown type"""
if x is None: return frozenset()
if isinstance(x,str):
return frozenset(resolve(full(x[1:]))) if x.startswith("#") else frozenset([full(x)])
if isinstance(x,list):
s=set()
for y in x:
r=ing(y)
if r is None: return None
s|=r
return frozenset(s)
if isinstance(x,dict):
if "item" in x and isinstance(x["item"],str): return frozenset([full(x["item"])])
if "tag" in x: return frozenset(resolve(full(x["tag"])))
if "id" in x and "count" in x and isinstance(x["id"],str): return frozenset([full(x["id"])])
if "ingredient" in x: return ing(x["ingredient"])
t=x.get("type","")
if t in("neoforge:compound",): return ing(x.get("children"))
if t=="neoforge:components": return ing(x.get("items"))
if "items" in x: return ing(x["items"])
if t=="neoforge:difference":
b=ing(x.get("base")); s=ing(x.get("subtracted"))
return None if b is None or s is None else b-s
if t=="neoforge:intersection":
ch=[ing(c) for c in x.get("children",[])]
if None in ch or not ch: return None
return frozenset(set.intersection(*map(set,ch)))
return None
def result(j):
r=j.get("result")
if r is None: return None
if isinstance(r,str): return (full(r),1)
if isinstance(r,dict):
i=r.get("id") or r.get("item")
if isinstance(i,dict): i=i.get("id") or i.get("item")
return (full(i) if isinstance(i,str) else str(i), r.get("count",1))
if isinstance(r,list):
return tuple(sorted(result({"result":(e.get("item",e) if isinstance(e,dict) else e)}) + ((e.get("chance",1),) if isinstance(e,dict) else ()) for e in r))
return str(r)
# ---------- active recipes (last jar wins per path; note collisions) ----------
active={}; overrides=[]; missingrefs=[]
for path,l in byres.items():
p=path.split("/")
if p[2]!="recipe" or not path.endswith(".json"): continue
rid=p[1]+":"+"/".join(p[3:])[:-5]
live=[]
for jar,j in l:
if not isinstance(j,dict): continue
c=j.get("neoforge:conditions")
if c is not None and not cond(c): continue
live.append((jar,j))
if len(live)>1:
uniq={json.dumps(j,sort_keys=True) for _,j in live}
if len(uniq)>1: overrides.append((rid,[jr for jr,_ in live]))
if live: active[rid]=live[-1]
# cross-mod overrides of another namespace's recipes (single providing jar from other mod)
owner={}
for m,l in D["mods"].items():
for x in l: owner.setdefault(x["jar"].split("!")[0],set()).add(m)
foreign=collections.defaultdict(list)
for rid,(jar,j) in active.items():
n=ns(rid)
if n not in owner.get(jar,set()) and jar not in("base_minecraft.jar",) and n not in("c",):
foreign[(jar,n)].append(rid)
# ---------- unknown refs in active recipes ----------
def refs(o,acc):
if isinstance(o,dict):
for k,v in o.items():
if k in("item","id","tag") and isinstance(v,str) and k!="type": acc.append((k,v))
else: refs(v,acc)
elif isinstance(o,list):
for v in o: refs(v,acc)
return acc
for rid,(jar,j) in active.items():
if j.get("type","").startswith(("neoforge:","forge:")) and "conditional" in j.get("type",""): continue
for k,v in refs({kk:vv for kk,vv in j.items() if kk not in("neoforge:conditions",)},[]):
if k=="tag": continue
if ":" in v and ns(v) not in mods and not v.startswith("#"):
missingrefs.append((rid,jar,v)); break
pickle.dump(dict(active=active,tagcache=tagcache,items=items),open("active.pkl","wb"))
print("== legacy (pre-1.21 'recipes/' or 'tags/items/') files, inert ==")
for k,v in legacy.most_common(): print(f" {k}: {v}")
print("== recipe path collisions with differing content (load order decides) ==",len(overrides))
for r,js in overrides: print(" ",r,js)
print("== jars overriding another namespace's recipes ==")
for (jar,n),l in sorted(foreign.items(),key=lambda x:-len(x[1])): print(f" {jar} -> {n}: {len(l)} e.g. {l[:3]}")
print("== active recipes referencing items from absent mods (will fail to load) ==",len(missingrefs))
for x in missingrefs[:80]: print(" ",x)
print("== tag issues (required entry missing => whole tag fails to load) ==")
seen=set()
for t,jar,v,k in tagissues:
if (t,v) in seen: continue
seen.add((t,v)); print(" ",k,t,"<-",v,"from",jar)
print("== unknown condition types (assumed true) ==",dict(unknown_cond))
print("active recipes",len(active))