加载器联测加强 + Sodium 诊断探针
## tests/test_loader_render.py ★ 划清本文件的**射程**(诚实标注): 它证明的是「Knot 能被我们驱动,且在加载器下渲染能力照常」。 它**不**证明「真实 mod(带 mixin 的)能工作」—— 因为它用空 mods 目录。 实测原因:真实 mod 的 mixin 目标是 intermediary 名,而我们跑 named ⇒ 同一 JVM 只能一个命名空间 ⇒ 真实 mixin mod 在本路线下不生效。 ⇒ 「加载器能起动」与「mod 能用」是**两件事**,别把前者读成后者。 这条注释是被自己坑出来的:早先那份 6/6 全绿的报告, 读起来像"加载器路线可用",实际只验证了"Knot 能起来"。 ## tools/probe_sodium.py 诊断脚本:在装了 Sodium 的实例上跑一次注入 + debug, 打印它自己的状态(chunkStatus / chunkReady / loadQueue / rebuildQueue / visibleChunks / chunksDebug),并出一张图。 ★ 为什么需要它:vanilla 的 `countRenderedChunks()` 在 Sodium 下 **永远返回 0**(它数的是 vanilla 的网格),光看这个数会把 「换了渲染器」误判成「没有数据」。而 blockinfo 能读方块 ⇒ 数据是在的。 ⇒ 排障必须问**渲染器自己**,不能问 vanilla 的计数器。 (debug 命令里的 sodium* 字段就是这条思路的落地: sodiumLoaded / sodiumTracker_chunkStatus / sodiumTracker_chunkReady / sodiumRebuildQueue / sodiumChunksDebug …)
This commit is contained in:
134
tools/probe_sodium.py
Normal file
134
tools/probe_sodium.py
Normal file
@ -0,0 +1,134 @@
|
||||
"""探针:Sodium 下区块到底有没有进它的 tracker(diagnose sodium tracker)。
|
||||
|
||||
用 Python 层的实例 + build.sh 的产物,跑一次 setblocks + debug,读 Sodium 自己的状态。
|
||||
用法: python3 tools/probe_sodium.py [mods|empty]
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
from mcfold.render import SECTION_VOLUME # noqa: E402
|
||||
|
||||
STORE = Path("/home/newqqagent/mcview/store/vanilla-1.20.1")
|
||||
OUT = STORE / ".mcfold"
|
||||
INST = Path("/tmp/exp-inst-root/demo-shader")
|
||||
USE_MODS = (sys.argv[1] if len(sys.argv) > 1 else "mods") == "mods"
|
||||
MODS = str(INST / "mods") if USE_MODS else "/tmp/empty-mods"
|
||||
PNG = sys.argv[2] if len(sys.argv) > 2 else "/tmp/sodium-probe.png"
|
||||
|
||||
|
||||
def classpath() -> str:
|
||||
libs = [l.strip() for l in (OUT / "classpath.txt").read_text().splitlines()
|
||||
if l.strip() and "/org/ow2/asm/" not in l]
|
||||
fab = sorted(str(p) for p in (OUT / "fabric").glob("*.jar"))
|
||||
return os.pathsep.join(libs + fab + [
|
||||
str(STORE / "versions/1.20.1/1.20.1.jar"),
|
||||
str(OUT / "classes-intermediary.jar"),
|
||||
])
|
||||
|
||||
|
||||
def rle(v):
|
||||
out = []
|
||||
for x in v:
|
||||
if out and out[-1][0] == x:
|
||||
out[-1][1] += 1
|
||||
else:
|
||||
out.append([x, 1])
|
||||
return out
|
||||
|
||||
|
||||
def main() -> int:
|
||||
Path("/tmp/empty-mods").mkdir(exist_ok=True)
|
||||
proc = subprocess.Popen(
|
||||
["java", "-Xmx2000M", "-Djava.awt.headless=true",
|
||||
f"-Djava.library.path={OUT / 'natives'}",
|
||||
f"-Dmcfold.names={OUT / 'names-intermediary.json'}",
|
||||
f"-Dfabric.modsFolder={MODS}",
|
||||
"-cp", classpath(), "LoaderBoot",
|
||||
"--gameDir", str(INST),
|
||||
"--assets", str(STORE / "assets"), "--assetIndex", "1.20.1"],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
|
||||
text=True, bufsize=1,
|
||||
env={**os.environ, "DISPLAY": ":98", "HOME": str(INST)})
|
||||
|
||||
def rpc(obj):
|
||||
proc.stdin.write(json.dumps(obj) + "\n")
|
||||
proc.stdin.flush()
|
||||
while True:
|
||||
line = proc.stdout.readline()
|
||||
if not line:
|
||||
return None
|
||||
s = line.strip()
|
||||
if s.startswith("{") and f'"id":{obj["id"]}' in s:
|
||||
return json.loads(s)
|
||||
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < 300:
|
||||
line = proc.stdout.readline()
|
||||
if not line:
|
||||
break
|
||||
if '"ready"' in line:
|
||||
break
|
||||
|
||||
try:
|
||||
cap = rpc({"id": 1, "cmd": "capacity"})
|
||||
ids = rpc({"id": 2, "cmd": "resolve", "blocks": [
|
||||
"minecraft:stone", "minecraft:grass_block[snowy=false]",
|
||||
"minecraft:oak_log[axis=y]", "minecraft:oak_leaves[persistent=true]"]})["ids"]
|
||||
stone, grass, log, leaves = ids
|
||||
rpc({"id": 3, "cmd": "viewport", "cx": 0, "cz": 0, "radius": 12})
|
||||
rpc({"id": 4, "cmd": "view", "hideGui": True})
|
||||
|
||||
secs = []
|
||||
for idx in range(cap["sectionsCount"]):
|
||||
y0 = idx * 16 - 64
|
||||
v = [0] * SECTION_VOLUME
|
||||
for yy in range(16):
|
||||
for zz in range(16):
|
||||
for xx in range(16):
|
||||
wy = y0 + yy
|
||||
h = 68 + int(2.5 * math.sin(xx * 0.4) * math.cos(zz * 0.4))
|
||||
i = yy * 256 + zz * 16 + xx
|
||||
if wy < h - 4:
|
||||
v[i] = stone
|
||||
elif wy == h - 4:
|
||||
v[i] = grass
|
||||
elif wy == h - 3:
|
||||
if (xx + zz) % 7 == 0:
|
||||
v[i] = log
|
||||
elif (xx * zz) % 5 == 0:
|
||||
v[i] = leaves
|
||||
secs.append({"index": idx, "rle": rle(v)})
|
||||
n = 10
|
||||
for cx in range(-3, 4):
|
||||
for cz in range(-3, 4):
|
||||
rpc({"id": n, "cmd": "setblocks", "cx": cx, "cz": cz, "sections": secs})
|
||||
n += 1
|
||||
|
||||
dbg = rpc({"id": 89, "cmd": "debug"})
|
||||
print(" ── debug ──")
|
||||
for k, v in (dbg or {}).items():
|
||||
if k != "image":
|
||||
print(f" {k} = {v}")
|
||||
|
||||
rpc({"id": 90, "cmd": "camera", "x": 8.0, "y": 72.0, "z": 8.0,
|
||||
"yaw": 42.0, "pitch": 30.0})
|
||||
res = rpc({"id": 91, "cmd": "render", "frames": 120, "out": PNG})
|
||||
print(f" vanilla计数: chunks={res.get('chunksRendered')} "
|
||||
f"colors={res.get('distinctColors')} → {PNG}")
|
||||
return 0
|
||||
finally:
|
||||
try:
|
||||
rpc({"id": 999, "cmd": "shutdown"})
|
||||
proc.wait(timeout=20)
|
||||
except Exception:
|
||||
proc.kill()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user