#!/usr/bin/env -S uv run --script """Trace Lost Pig V8 opcodes — find what's needed for V5+ support. Runs the interpreter step-by-step, collecting unique opcodes hit. When an unimplemented opcode is encountered, reports what was seen and what's missing. """ # ruff: noqa: E402 import sys from collections import Counter from pathlib import Path project_root = Path(__file__).parent.parent sys.path.insert(0, str(project_root / "src")) from mudlib.zmachine import ZMachine, zopdecoder, zstream, zui from mudlib.zmachine.trivialzui import ( TrivialAudio, TrivialFilesystem, TrivialScreen, ) from mudlib.zmachine.zcpu import ( ZCpuNotImplemented, ZCpuQuit, ZCpuRestart, ) story_path = project_root / "content" / "stories" / "LostPig.z8" if not story_path.exists(): print(f"ERROR: {story_path} not found") sys.exit(1) story_bytes = story_path.read_bytes() print(f"Loaded LostPig.z8: {len(story_bytes)} bytes, version {story_bytes[0]}") class AutoInputStream(zstream.ZInputStream): """Input stream that auto-feeds commands.""" def __init__(self): super().__init__() self._input_count = 0 self._max_inputs = 3 def read_line(self, *args, **kwargs): self._input_count += 1 if self._input_count > self._max_inputs: raise ZCpuQuit return "look" audio = TrivialAudio() screen = TrivialScreen() keyboard = AutoInputStream() filesystem = TrivialFilesystem() ui = zui.ZUI(audio, screen, keyboard, filesystem) zm = ZMachine(story_bytes, ui) opcodes_seen = Counter() step_count = 0 max_steps = 500_000 print(f"Tracing up to {max_steps} steps...") print() try: while step_count < max_steps: pc = zm._cpu._opdecoder.program_counter (opcode_class, opcode_number, operands) = ( zm._cpu._opdecoder.get_next_instruction() ) cls_str = zopdecoder.OPCODE_STRINGS.get(opcode_class, f"?{opcode_class}") key = f"{cls_str}:{opcode_number:02x}" try: implemented, func = zm._cpu._get_handler(opcode_class, opcode_number) except Exception as e: print(f"ILLEGAL at step {step_count}: {key} (pc={pc:#x})") print(f" {e}") break opcodes_seen[f"{key} ({func.__name__})"] += 1 if not implemented: print(f"UNIMPLEMENTED at step {step_count}: {key} -> {func.__name__}") print(f" PC: {pc:#x}") print() break try: func(zm._cpu, *operands) except ZCpuQuit: print(f"Game quit after {step_count} steps") break except ZCpuRestart: print(f"Game restart after {step_count} steps") break except ZCpuNotImplemented as e: print(f"NOT IMPLEMENTED at step {step_count}: {key} -> {func.__name__}") print(f" PC: {pc:#x}") print(f" Error: {e}") print() break except Exception as e: print(f"ERROR at step {step_count}: {key} -> {func.__name__}") print(f" PC: {pc:#x}") print(f" Operands: {operands}") print(f" {type(e).__name__}: {e}") import traceback traceback.print_exc() zm._cpu._dump_trace() break step_count += 1 except KeyboardInterrupt: print(f"\nInterrupted at step {step_count}") print(f"\nTotal steps: {step_count}") print(f"Unique opcodes seen: {len(opcodes_seen)}") print() print("Opcodes by frequency:") for op, count in opcodes_seen.most_common(): print(f" {count:>8} {op}")