Implement V3 restore opcode:
- Add QuetzalParser.load_from_bytes() to parse save data from memory
- Wire op_restore to call filesystem.restore_game() and parse result
- Validate IFhd matches current story (release/serial/checksum)
- Restore dynamic memory, call stack, and program counter
- Branch true on success, false on failure/cancellation
Fix IFF chunk padding bug:
- Add padding byte to odd-length chunks in QuetzalWriter
- Ensures proper chunk alignment for parser compatibility
Add comprehensive tests:
- Branch false when filesystem returns None
- Branch false without zmachine reference
- Branch true on successful restore
- Verify memory state matches saved values
- Handle malformed save data gracefully
Implement full save functionality for V3 z-machine:
- Fixed QuetzalWriter._generate_anno_chunk() to return bytes
- Added QuetzalWriter.generate_save_data() to produce IFF container
- Updated QuetzalWriter.write() to use new method and binary mode
- Added zmachine reference to ZCpu for QuetzalWriter access
- Added _program_counter property to ZCpu for Quetzal access
- Implemented op_save to call QuetzalWriter and filesystem
- Updated tests for op_save (success, failure, IFF validation)
- Added filesystem mock to MockUI for testing
- Added _call_stack to MockStackManager for QuetzalWriter
All tests pass. Save now generates valid IFF/FORM/IFZS data with
IFhd, CMem, Stks, and ANNO chunks.
The IFhd chunk contains 13 bytes of metadata identifying the story
and current execution state:
- Release number (2 bytes) from header
- Serial number (6 bytes) from header
- Checksum (2 bytes) from header
- Program counter (3 bytes) from CPU state
This allows save files to be validated against the story file.
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.
Spec fixes: implement op_test (bitwise AND branch), add missing branch
to op_get_child, handle call-to-address-0 as no-op in op_call/_call.
I/O fixes: correct keyboard API (keyboard_input.read_line), non-TTY
fallbacks in trivialzui, stdout flush for immediate output. Graceful
handling of unmapped ZSCII characters. Add instruction trace buffer
for debugging.
op_verify now performs actual checksum validation against the header
instead of raising NotImplemented. ZLexer is injected into ZCpu and
sread tokenizes input into the parse buffer per the V3 spec.
Implements op_print_addr, op_print_num, op_ret, and op_show_status following
TDD approach with tests first. Each opcode now properly decodes/prints text,
handles signed numbers, returns from routines, or acts as a no-op as appropriate.
Implemented 10 opcodes for object tree manipulation: get_sibling,
test_attr, set_attr, clear_attr, jin, remove_obj, print_obj,
get_prop_addr, get_next_prop, and get_prop_len.
Added 6 supporting methods to ZObjectParser: set_attribute,
clear_attribute, remove_object, get_property_data_address,
get_next_property, and get_property_length.
Fixed bug in insert_object where sibling chain walk never advanced
the prev pointer, potentially causing infinite loops.
Added 16 unit tests with MockObjectParser to verify CPU opcode
behavior. All tests passing.