Erinaldordso commited on
Commit
24010a2
·
verified ·
1 Parent(s): 191f447

Compact actual Creative history and diagnose player-save exceptions

Browse files

Fix compaction after the earlier history bound; validate the rendered observation to catch interactions between patches. Add a small reproducible Java 17 agent that exposes the exception swallowed by vanilla player saving, preserving catch behavior. Regression and diagnostic-agent tests passed.

diagnostics/PlayerSaveDiagnostics.java ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import java.lang.instrument.ClassFileTransformer;
2
+ import java.lang.instrument.Instrumentation;
3
+ import java.security.ProtectionDomain;
4
+ import java.util.HashSet;
5
+ import java.util.Set;
6
+ import jdk.internal.org.objectweb.asm.*;
7
+
8
+ /** Log the exception vanilla 1.19 discards when player saving fails. */
9
+ public final class PlayerSaveDiagnostics {
10
+ public static void premain(String args, Instrumentation instrumentation) {
11
+ instrumentation.addTransformer(new ClassFileTransformer() {
12
+ public byte[] transform(ClassLoader loader, String name, Class<?> type,
13
+ ProtectionDomain domain, byte[] bytes) {
14
+ if (!"net/minecraft/class_29".equals(name)) return null;
15
+ try {
16
+ ClassReader reader = new ClassReader(bytes);
17
+ ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
18
+ int[] injected = {0};
19
+ reader.accept(new ClassVisitor(Opcodes.ASM8, writer) {
20
+ public MethodVisitor visitMethod(int access, String method, String desc,
21
+ String signature, String[] exceptions) {
22
+ MethodVisitor delegate = super.visitMethod(access, method, desc, signature, exceptions);
23
+ if (!"method_262".equals(method)) return delegate;
24
+ return new MethodVisitor(Opcodes.ASM8, delegate) {
25
+ final Set<Label> handlers = new HashSet<>();
26
+ boolean pending;
27
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String caught) {
28
+ if ("java/lang/Exception".equals(caught)) handlers.add(handler);
29
+ super.visitTryCatchBlock(start, end, handler, caught);
30
+ }
31
+ public void visitLabel(Label label) {
32
+ super.visitLabel(label);
33
+ if (handlers.contains(label)) pending = true;
34
+ }
35
+ public void visitVarInsn(int opcode, int variable) {
36
+ if (pending && opcode == Opcodes.ASTORE) {
37
+ // Preserve the original exception and catch behavior.
38
+ super.visitInsn(Opcodes.DUP);
39
+ super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
40
+ "printStackTrace", "()V", false);
41
+ pending = false;
42
+ injected[0]++;
43
+ }
44
+ super.visitVarInsn(opcode, variable);
45
+ }
46
+ };
47
+ }
48
+ }, 0);
49
+ System.err.println("[player-save-diagnostics] exception logging hooks=" + injected[0]);
50
+ return injected[0] == 0 ? null : writer.toByteArray();
51
+ } catch (Throwable failure) {
52
+ System.err.println("[player-save-diagnostics] skipped: " + failure);
53
+ return null;
54
+ }
55
+ }
56
+ });
57
+ }
58
+ }
diagnostics/build.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Reproducibly build the Java 17 diagnostic agent; no Minecraft dependencies."""
2
+ from pathlib import Path
3
+ import subprocess
4
+ import tempfile
5
+ import zipfile
6
+
7
+ root = Path(__file__).resolve().parent
8
+ with tempfile.TemporaryDirectory() as directory:
9
+ subprocess.run([
10
+ "java", "-m", "jdk.compiler/com.sun.tools.javac.Main",
11
+ "--add-exports", "java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED",
12
+ "-source", "17", "-target", "17", "-d", directory,
13
+ str(root / "PlayerSaveDiagnostics.java"),
14
+ ], check=True)
15
+ with zipfile.ZipFile(root / "player-save-diagnostics.jar", "w", zipfile.ZIP_DEFLATED) as archive:
16
+ def write(name, data):
17
+ entry = zipfile.ZipInfo(name, (2026, 1, 1, 0, 0, 0))
18
+ entry.compress_type = zipfile.ZIP_DEFLATED
19
+ archive.writestr(entry, data)
20
+ write("META-INF/MANIFEST.MF", b"Manifest-Version: 1.0\nPremain-Class: PlayerSaveDiagnostics\n\n")
21
+ for path in sorted(Path(directory).glob("*.class")):
22
+ write(path.name, path.read_bytes())
diagnostics/player-save-diagnostics.jar ADDED
Binary file (3.83 kB). View file
 
minecraft_stack.py CHANGED
@@ -362,7 +362,10 @@ class MinecraftStack:
362
  READY_FILE.unlink(missing_ok=True)
363
  proc = self._spawn(
364
  "minecraft",
365
- [str(self._ensure_minecraft_java()), "-Xms1G", "-Xmx8G", "-jar", str(launcher), "nogui"],
 
 
 
366
  JAVA_DIR,
367
  )
368
  if not _wait_tcp("127.0.0.1", 25565, 120):
 
362
  READY_FILE.unlink(missing_ok=True)
363
  proc = self._spawn(
364
  "minecraft",
365
+ [str(self._ensure_minecraft_java()), "-Xms1G", "-Xmx8G",
366
+ "--add-exports=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED",
367
+ f"-javaagent:{Path(__file__).resolve().parent / 'diagnostics/player-save-diagnostics.jar'}",
368
+ "-jar", str(launcher), "nogui"],
369
  JAVA_DIR,
370
  )
371
  if not _wait_tcp("127.0.0.1", 25565, 120):
tests/test_player_save_diagnostics.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Verify exception logging does not replace vanilla's catch or success path."""
2
+ from pathlib import Path
3
+ import subprocess
4
+ import tempfile
5
+
6
+ root = Path(__file__).resolve().parents[1]
7
+ with tempfile.TemporaryDirectory() as directory:
8
+ folder = Path(directory)
9
+ source = folder / "class_29.java"
10
+ source.write_text('''package net.minecraft;
11
+ public class class_29 {
12
+ public void method_262(boolean fail) {
13
+ try { if(fail) throw new java.io.IOException("diagnostic sentinel"); }
14
+ catch(Exception e) { System.out.println("original catch retained"); }
15
+ }
16
+ public static void main(String[] args) {
17
+ new class_29().method_262(true);
18
+ new class_29().method_262(false);
19
+ System.out.println("normal return retained");
20
+ }
21
+ }''')
22
+ subprocess.run(["java", "-m", "jdk.compiler/com.sun.tools.javac.Main", "-d", directory, str(source)], check=True)
23
+ result = subprocess.run([
24
+ "java", "--add-exports=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED",
25
+ f"-javaagent:{root / 'diagnostics/player-save-diagnostics.jar'}",
26
+ "-cp", directory, "net.minecraft.class_29",
27
+ ], capture_output=True, text=True, check=True)
28
+ assert result.stderr.count("java.io.IOException: diagnostic sentinel") == 1
29
+ assert "exception logging hooks=1" in result.stderr
30
+ assert result.stdout == "original catch retained\nnormal return retained\n"
31
+ print("PASS player-save exception visible; original catch and normal return unchanged")
tests/test_voyager_patch.py CHANGED
@@ -111,9 +111,9 @@ def test_resume():
111
  def check_curriculum_behavior(source):
112
  tree = ast.parse(source)
113
  cls = next(n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == "CurriculumAgent")
114
- names = {"get_task_context", "propose_next_ai_task", "parse_ai_message", "render_human_message"}
115
  methods = [n for n in cls.body if isinstance(n, ast.FunctionDef) and n.name in names]
116
- ns = {"AIMessage": SimpleNamespace, "HumanMessage": SimpleNamespace, "validate_task": validate_task}
117
  exec(compile(ast.Module(body=methods, type_ignores=[]), "curriculum.py", "exec"), ns)
118
  agent = SimpleNamespace(completed_tasks=["Obtain a spruce log"], failed_tasks=[])
119
  for name in names:
@@ -128,6 +128,14 @@ def check_curriculum_behavior(source):
128
  retry = agent.llm.call_args_list[1].args[0]
129
  assert len(retry) == 4 and "Task already completed" in retry[-1].content
130
  assert len(messages) == 2
 
 
 
 
 
 
 
 
131
  agent.run_qa = Mock(side_effect=AssertionError("Survival QA must not run"))
132
  agent.run_qa_step2_answer_questions = agent.run_qa
133
  agent.render_observation = Mock(return_value={"inventory": "Inventory: netherite tools"})
 
111
  def check_curriculum_behavior(source):
112
  tree = ast.parse(source)
113
  cls = next(n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == "CurriculumAgent")
114
+ names = {"get_task_context", "propose_next_ai_task", "parse_ai_message", "render_human_message", "render_observation"}
115
  methods = [n for n in cls.body if isinstance(n, ast.FunctionDef) and n.name in names]
116
+ ns = {"AIMessage": SimpleNamespace, "HumanMessage": SimpleNamespace, "validate_task": validate_task, "task_history": task_history}
117
  exec(compile(ast.Module(body=methods, type_ignores=[]), "curriculum.py", "exec"), ns)
118
  agent = SimpleNamespace(completed_tasks=["Obtain a spruce log"], failed_tasks=[])
119
  for name in names:
 
128
  retry = agent.llm.call_args_list[1].args[0]
129
  assert len(retry) == 4 and "Task already completed" in retry[-1].content
130
  assert len(messages) == 2
131
+ agent.progress = 100
132
+ agent.warm_up = {"optional_inventory_items": 0}
133
+ agent.completed_tasks = ["Mine 1 stone block to craft and upgrade tools", "Mine 1 stone"]
134
+ observed = {"status": {"biome": "snowy_plains", "timeOfDay": "sunrise", "entities": {}, "health": 20,
135
+ "food": 20, "position": {"x": 0, "y": 74, "z": 0}, "equipment": [], "inventoryUsed": 0},
136
+ "voxels": ["stone", "grass_block"], "blockRecords": [], "inventory": {}}
137
+ observation = agent.render_observation(events=[["observe", observed]], chest_observation="")
138
+ assert observation["completed_tasks"] == "Completed tasks so far: Mine 1 stone\n\n"
139
  agent.run_qa = Mock(side_effect=AssertionError("Survival QA must not run"))
140
  agent.run_qa_step2_answer_questions = agent.run_qa
141
  agent.render_observation = Mock(return_value={"inventory": "Inventory: netherite tools"})
voyager_stack.py CHANGED
@@ -515,11 +515,11 @@ async function exploreUntil(bot, direction, maxTime = 60, callback) {
515
  curriculum_text = curriculum_path.read_text(encoding="utf-8")
516
  curriculum_text = curriculum_text.replace(
517
  '", ".join(self.completed_tasks) if self.completed_tasks else "None"',
518
- '", ".join(list(dict.fromkeys(self.completed_tasks))[-20:]) if self.completed_tasks else "None"',
519
  )
520
  curriculum_text = curriculum_text.replace(
521
  'failed_tasks = ", ".join(self.failed_tasks) if self.failed_tasks else "None"',
522
- 'failed_tasks = ", ".join(list(dict.fromkeys(self.failed_tasks))[-20:]) if self.failed_tasks else "None"',
523
  )
524
 
525
  creative_context_method = ''' def get_task_context(self, task):
 
515
  curriculum_text = curriculum_path.read_text(encoding="utf-8")
516
  curriculum_text = curriculum_text.replace(
517
  '", ".join(self.completed_tasks) if self.completed_tasks else "None"',
518
+ 'task_history(self.completed_tasks) if self.completed_tasks else "None"',
519
  )
520
  curriculum_text = curriculum_text.replace(
521
  'failed_tasks = ", ".join(self.failed_tasks) if self.failed_tasks else "None"',
522
+ 'failed_tasks = task_history(self.failed_tasks) if self.failed_tasks else "None"',
523
  )
524
 
525
  creative_context_method = ''' def get_task_context(self, task):