ZStackBottom already had stack and local_vars for uniform treatment,
but was missing return_addr. Adding it removes 5 type: ignore
suppressions and fixes all ty possibly-missing-attribute warnings.
New opcodes: op_call_vn, op_call_vn2, op_call_vs2, op_catch,
op_check_arg_count. All call variants delegate to existing _call().
ZRoutine now tracks arg_count for check_arg_count.
Fixed zopdecoder double-byte operand parsing for call_vs2/call_vn2:
the old code called _parse_operands_byte() twice, but this method
reads both type byte AND operands together. The second call would
read operand data as a type byte. Refactored into _read_type_byte()
+ _parse_operand_list() so both type bytes are read before any
operand data.
Also fixed the double-byte detection: was checking opcode[0:7] (7
bits = 0x7A for call_vn2) instead of opcode_num (5 bits = 0x1A).
The check never matched, so double-byte opcodes were always
mis-parsed.
V8 uses the same format as V5 (object model, opcodes, stack) with
two differences: packed address scaling (×8 instead of ×4) and max
file size (512KB instead of 256KB).
zmemory: add V8 size validation and packed_address case
zobjectparser: accept version 8 alongside 4-5 in all checks
zstackmanager: allow V8 stack initialization
V6-7 remain unsupported (different packed address format with offsets).
The Quetzal spec stores the PC pointing at the save instruction's
branch data. On restore, this branch must be processed as "save
succeeded" to advance the PC to the actual next instruction. Without
this, the branch bytes were decoded as an opcode, corrupting execution.
Detect the save opcode (0xB5) immediately before the restored PC to
distinguish in-game saves from out-of-band saves (which don't need
branch processing). Also improve error diagnostics: pop_stack now
raises ZStackPopError with frame context, and the instruction trace
dumps on all exceptions.
Property parsing had 4 classes of bug, all producing wrong addresses
for object property data:
1. Off-by-one in shortname skip: addr += 2*len missed the length byte
itself, causing property table scan to start 1 byte too early.
Affected get_prop_addr_len and get_next_property.
2. V3 property number slices bf[4:0] extracted 4 bits not 5.
Property numbers 16-31 were read as 0-15.
3. V3 property size slices bf[7:5] extracted 2 bits not 3.
Properties larger than 4 bytes got wrong sizes.
4. V5 property number/size slices bf[5:0] extracted 5 bits not 6.
Also fixed get_property_length reading size from the wrong byte in
V5 two-byte headers (was reading first byte which has property
number, instead of second byte which has size).
Root cause for all slice bugs: BitField uses Python [start:stop)
semantics but code was written as [high:low] inclusive notation.
Same class of bug as the write_global fix in commit e5329d6.
Adds comprehensive test coverage for three newly implemented Z-machine opcodes:
- op_test: Tests bitmap flag checking with all flags set, some missing, zero
flags, and identical values
- op_verify: Tests checksum verification with matching and mismatched checksums
- op_get_child: Tests getting first child of object with and without children
Also extends MockMemory with generate_checksum() and MockObjectParser with
get_child() to support the new tests.
ZStackBottom lacked attributes that normal stack operations need when
returning from a subroutine to the top level. Zork 1 hit this on the
first subroutine return during startup.
Copies the cleaned-up zvm source (ruff-compliant, ty-clean) back into
the zmachine module. Adds __init__.py with proper exports and updates
.gitignore for debug.log/disasm.log.