Commit graph

198 commits

Author SHA1 Message Date
d9e9d1b785
Add Object base class with containment primitives
Object provides name, location, x/y, contents reverse-lookup, and
can_accept() — the foundation for the containment tree that zones,
things, and inventory will build on.
2026-02-11 18:40:31 -05:00
74538756d5
Handle object 0 (nothing) gracefully in object parser
Per the Z-machine spec, object 0 means "nothing" and operations on it
should be safe no-ops: get_child/get_sibling/get_parent return 0,
test_attr returns false, set/clear_attr are no-ops, etc. Previously
these threw ZObjectIllegalObjectNumber, crashing on games like Curses
that pass object 0 to get_child during room transitions.
2026-02-10 18:32:36 -05:00
5a98adb6ee
Add instruction tracing to step_fast and improve error messages
step_fast() never recorded trace entries, so crash dumps always showed
an empty trace. Now records PC + opcode info in the same deque as
step(). Also includes exception type in player-facing error messages
when the exception string is empty.
2026-02-10 18:29:27 -05:00
c8d9bdfae9
Map empty Enter to ZSCII 13 in read_char
MudInputStream.read_char() returned 0 for empty input, which no game
recognizes as a valid keypress. Now returns 13 (Enter/Return) so
"press any key" prompts like Curses' intro work from a MUD client.
2026-02-10 18:26:41 -05:00
fd977b91a2
Guard bare > stripping with has_prompt check 2026-02-10 17:53:11 -05:00
b81bc3edc8
Add consistent > prompt for IF mode in server loop 2026-02-10 17:50:35 -05:00
ac1d16095e
Strip trailing > prompt from embedded z-machine output 2026-02-10 17:50:06 -05:00
909ee0932b
Replace deprecated chunk module with inline IFF parser
The chunk module was deprecated in 3.11 and removed in 3.13.
Our usage was minimal (read name, size, data, skip padding),
so a small _read_iff_chunk() helper replaces it with no deps.
2026-02-10 17:20:01 -05:00
3140a4d617
Add return_addr to ZStackBottom for uniform frame access
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.
2026-02-10 17:16:25 -05:00
6d29ec00fb
Implement stub opcodes for game compatibility
set_colour, piracy, erase_line, get_cursor, not_v5, print_table
2026-02-10 17:10:29 -05:00
bc1a2e5489
Add undo command support 2026-02-10 16:49:46 -05:00
1b3a3646d6
Pre-consume store byte in op_aread and op_read_char before blocking reads
Without this, MUD-level saves during read_line/read_char capture PC pointing
at the store byte, which gets misinterpreted as an opcode on restore.
2026-02-10 15:45:52 -05:00
bb2f1989cb
Optimize z-machine hot loop: fast step, dispatch table, inline bit ops
Add step_fast() that skips trace/logging overhead (saves ~22% at 1M+
avoided log calls). Pre-resolve opcode dispatch table at init to
eliminate per-instruction version checks and isinstance calls. Replace
BitField allocations with direct bit masks in opcode decoder.

Cold start: 4720ms -> 786ms. Steady state: ~500ms -> ~460ms.
2026-02-10 15:05:34 -05:00
802c72819c
Fix op_read_char to accept optional timing arguments
Lost Pig calls read_char with only the required first operand.
The Z-machine spec says time and input_routine are optional.
2026-02-10 14:48:57 -05:00
5f12a4f841
Suppress upper window writes in MudScreen to fix Lost Pig output
V5+ games write room names to the upper window (status line) via
select_window(1). Since select_window was a no-op, status line text
leaked into the main output buffer, causing ">Outside" on prompt lines.

Track the active window and only buffer writes to window 0 (lower).
2026-02-10 14:36:42 -05:00
e55294af78
Implement V5+ save/restore opcodes and handle in-game saves on restore
- op_save_v5: generates Quetzal save, stores 1 on success / 0 on failure
- op_restore_v5: loads Quetzal save, stores 2 ("restored") via store byte
- _try_restore: detect V5+ in-game saves (0xBE 0x00 before PC) and process
  the store byte with result 2, matching the V3 branch-on-restore pattern
2026-02-10 14:18:42 -05:00
602da45ac2
Fix IF bugs: case-insensitive story lookup, double prompt, phantom restore command
- _find_story() now compares path.stem.lower() so "lostpig" matches "LostPig.z8"
- Server no longer writes its own prompt in IF mode (game handles prompting)
- Suppress phantom game output on restore (saved PC past sread causes garbage)
- Route .z5/.z8 files to EmbeddedIFSession now that V5+ is supported
2026-02-10 14:16:19 -05:00
8a5ef7b1f6
Implement V5+ opcodes: aread, save_undo, shifts, scan_table, and more
Implements the opcode set needed for Lost Pig (V8):
- op_aread: V5+ input with text at byte 2, char count at byte 1,
  stores terminating character
- op_save_undo/op_restore_undo: stub returning -1/0 (undo not yet
  available, game continues without it)
- op_log_shift/op_art_shift: logical and arithmetic bit shifts
- op_scan_table: table search with configurable entry size and
  word/byte comparison
- op_tokenize: re-tokenize text buffer against dictionary
- op_copy_table: memory copy/zero with forward/backward support
- op_set_font: returns 1 for font 1, 0 for others
- op_print_unicode/op_check_unicode: basic Unicode output support

Lost Pig now runs to completion: 101K steps, 61 unique opcodes,
3 full input cycles with room descriptions rendering correctly.
2026-02-10 13:51:28 -05:00
d71f221277
Implement V5+ call variants and fix double-byte operand decoding
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.
2026-02-10 13:48:08 -05:00
38e60ae40c
Fix insert_object to remove from old parent before inserting
The old code inserted the object into the new parent first, then
tried to remove it from the old parent. This corrupted the sibling
chain because the object's sibling pointer was already modified.

The Z-spec says "if O already has a parent, it is first removed."
Now delegates to remove_object() before inserting.
2026-02-10 13:47:58 -05:00
e61dcc3ac4
Implement extended opcode decoder for V5+
The 0xBE prefix byte triggers extended opcode parsing. Reads the
opcode number from the next byte, then parses operand types using
the same format as VAR opcodes. Required for all V5+ games.
2026-02-10 13:37:27 -05:00
11d939a70f
Relax version gates to accept V8 story files
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).
2026-02-10 13:37:22 -05:00
e0573f4229
Fix two zmachine bugs found during code audit
quetzal.py: typo in _parse_umem() — cmem.dynamic_start should be
cmem._dynamic_start. Would crash on uncompressed memory restore.

zcpu.py: op_erase_window had if/if/else instead of if/elif/else.
When window_number was -1, the second if fell through to else,
calling erase_window(-1) after the correct reset path.
2026-02-10 13:30:44 -05:00
c52e59c5d4
Process V3 save branch on restore to advance PC past branch data
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.
2026-02-10 13:15:16 -05:00
8526e48247
Fix Quetzal Stks field mapping: return_pc to caller, varnum to frame
return_pc for each frame belongs on the caller's program_counter (the
resume address when this routine exits). varnum is the store variable
that receives the return value, stored as return_addr on the frame
itself. Also handle flags bit 4 (discard result → return_addr=None).
2026-02-10 12:39:40 -05:00
776cfba021
Pad restored local vars to 15 slots
The Quetzal save format only stores the number of local vars the
routine declared, but the runtime indexes local_vars[0..14] for any
variable access. Restored routines had short lists, causing IndexError
on the first command after restore.
2026-02-10 12:00:15 -05:00
1ee89e5e3c
Log full traceback on interpreter crash
The error handler only captured str(e), losing the stack trace.
Now logs the full traceback so crashes are actually debuggable.
2026-02-10 12:00:10 -05:00
65a080608a
Fix stack manager references after Quetzal restore
QuetzalParser._parse_stks() creates a new ZStackManager and sets it on
zmachine._stackmanager, but ZCpu._stackmanager and ZOpDecoder._stack
still pointed to the old empty stack. After restore, all stack ops
(local var reads, routine returns, stack pops) used the wrong stack,
causing the interpreter to crash on the first command.
2026-02-10 11:51:50 -05:00
15e1d807aa
Move z-machine restore before interpreter thread start
Replaces the async _do_restore() (called after thread launch) with a
synchronous _try_restore() called before the thread starts. This
eliminates the race condition where restore mutates z-machine state
while the interpreter thread is running.

The restore prefix message is now part of start()'s return value
instead of being sent separately in play.py.
2026-02-10 11:51:45 -05:00
7c1d1efcdb
Wire embedded z-machine interpreter into MUD mode stack
EmbeddedIFSession runs the hybrid interpreter in a daemon thread,
bridged to the async MUD loop via threading.Event synchronization.
.z3 files use the embedded path; other formats fall back to dfrotz.

- MUD ZUI components: MudScreen (buffered output), MudInputStream
  (thread-safe input), MudFilesystem (quetzal saves), NullAudio
- save/restore via QuetzalWriter/QuetzalParser and :: escape commands
- state inspection: get_location_name(), get_room_objects()
- error reporting for interpreter crashes
- fix quetzal parser bit slice bug: _parse_stks used [0:3] (3 bits,
  max 7 locals) instead of [0:4] (4 bits, max 15) — Zork uses 15
2026-02-10 11:18:16 -05:00
b0fb9b5e2c
Wire op_restore to QuetzalParser and filesystem
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
2026-02-10 10:13:45 -05:00
a5053e10f2
Wire op_save to QuetzalWriter and filesystem
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.
2026-02-10 10:13:45 -05:00
2b8c177977
Fix off-by-one in QuetzalParser return_pc parsing 2026-02-10 10:13:33 -05:00
0c6eadb0da
Implement QuetzalWriter._generate_ifhd_chunk()
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.
2026-02-10 09:47:24 -05:00
ec4e53b2d4
Fix backspace echo for terminals that send chr(127)
The input detection handled both chr(8) and chr(127) but the echo
logic only checked chr(8). Most modern terminals send chr(127) for
backspace, so the cursor never moved back visually.
2026-02-09 23:11:43 -05:00
bbd70e8577
Truncate words to dictionary resolution before lookup 2026-02-09 23:04:56 -05:00
6e62ca203b
Fix return value storage in finish_routine 2026-02-09 23:04:54 -05:00
fa31f39a65
Fix signed comparison in op_jl 2026-02-09 23:04:52 -05:00
127ca5f56e
Fix BitField slice and off-by-one bugs in zobjectparser.py
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.
2026-02-09 22:54:32 -05:00
5ffbe660fb
Flush stdout after character echo in readline 2026-02-09 22:03:06 -05:00
2ce82e7d87
Add unit tests for op_test, op_verify, and op_get_child
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.
2026-02-09 21:52:08 -05:00
48727c4690
Log warning for undefined ZSCII characters 2026-02-09 21:47:20 -05:00
8ba1701c9f
Dump instruction trace for all error exceptions, not just illegal opcode
Wrap opcode execution with try-except to catch all ZCpuError subclasses except
ZCpuQuit and ZCpuRestart, which are normal control flow. This ensures we get
trace dumps for divide by zero, not implemented, and other errors.
2026-02-09 21:47:13 -05:00
0f5fada29e
Remove duplicate routine_address == 0 check from op_call
The _call() method already handles this case, so the check in op_call is redundant.
2026-02-09 21:46:59 -05:00
9a0bacd581
Document write_word permission model in zmemory 2026-02-09 21:46:52 -05:00
1bcb83961a
Guard against word-not-found in sread tokenization
If text.find(word_str, offset) returns -1, use the offset as a fallback.
2026-02-09 21:46:45 -05:00
28a097997c
Add explanatory comment to ZStackBottom sentinel 2026-02-09 21:46:40 -05:00
eeb65e0cc2
Remove debug print statements from zcpu opcode dispatch
The exception that follows provides the context, so these prints are redundant.
2026-02-09 21:46:34 -05:00
e5329d6788
Fix 7-bit truncation bug in write_global corrupting game state 2026-02-09 21:45:54 -05:00
311a67e80a
Fix bugs found running Zork 1 through hybrid interpreter
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.
2026-02-09 21:36:30 -05:00
8d749fbb7e
Add missing newline to A2 alphabet table for V3
The V3 A2 table needs newline (ZSCII 13) at code 7, matching viola's
implementation. The previous fix only removed the erroneous '<' but
didn't add the newline, leaving the table at 24 chars instead of 25.
2026-02-09 21:17:04 -05:00
9116777603
Fix A2 alphabet table with extra character shifting symbols
DEFAULT_A2 had 26 chars instead of 25 due to an erroneous '<'. This
shifted all symbol mappings, causing periods to render as exclamation
marks and other garbled punctuation in Zork 1 output.
2026-02-09 21:11:12 -05:00
e1c6a92368
Fix Python 3 integer division in zmachine modules
Python 2 `/` did integer division, Python 3 returns float. Changed to
`//` in zobjectparser (attribute byte offset) and zmemory (address
bounds checks). Zork 1 hit this on the first test_attr opcode.
2026-02-09 21:07:16 -05:00
8eb2371ce1
Add stack and local_vars to ZStackBottom sentinel
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.
2026-02-09 21:05:27 -05:00
61765fa6ba
Allow game writes to header region in dynamic memory
write_word() was routing all header writes through game_set_header()
which enforced overly strict per-byte permissions. Zork 1 legitimately
writes to header address 2 on startup. Now uses the same dynamic/static
boundary check that viola does, matching the reference implementation.
2026-02-09 21:03:10 -05:00
d218929f02
Add step() method to ZCpu for async MUD integration
Extracts the run loop body into step() which executes one instruction
and returns True/False for continue/halt. run() now delegates to step().
2026-02-09 20:59:03 -05:00
fb8cbf7219
Implement op_verify and wire ZLexer into sread for Zork 1
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.
2026-02-09 20:57:15 -05:00
72dd047b7b
Port 5 complex opcodes to hybrid z-machine interpreter
Implement op_sread (text input), op_save/restore (file I/O stubs),
op_restart (exception-based reset), op_input_stream and op_sound_effect
(no-op stubs). Add ZCpuRestart exception. All implementations follow TDD
with comprehensive unit tests.
2026-02-09 20:44:22 -05:00
c76ee337d3
Port 4 medium opcodes to hybrid z-machine interpreter
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.
2026-02-09 20:44:22 -05:00
1b9d84f41a
Port 10 object tree opcodes to hybrid z-machine interpreter
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.
2026-02-09 20:44:21 -05:00
5ea030a0ac
Re-copy fixed repos/zvm source into src/mudlib/zmachine
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.
2026-02-09 20:44:21 -05:00
dcc952d4c5
Port 12 trivial opcodes to hybrid z-machine interpreter 2026-02-09 20:44:21 -05:00
108091bfae
Add blank line before IF mode prompt to match dfrotz 2026-02-09 17:55:14 -05:00
f8e9ae0acc
Add spacing between IF session exit messages 2026-02-09 17:55:14 -05:00
54cd0f6656
Strip dfrotz prompt even without preceding newline 2026-02-09 17:55:14 -05:00
05c9a48bb3
Strip dfrotz prompt even with trailing whitespace 2026-02-09 17:28:28 -05:00
8dc2d4b934
Auto-confirm overwrite and clean up save feedback 2026-02-09 17:10:30 -05:00
da176a1363
Use get_running_loop() instead of deprecated get_event_loop() 2026-02-09 17:07:27 -05:00
e8f16ca18a
Speed up IF reads with chunked I/O and short idle timeout 2026-02-09 17:05:27 -05:00
43fce6a4ed
Fix line length issues in IF session tests 2026-02-09 16:45:36 -05:00
76488139c8
Add error handling for process communication failures in IF save/restore 2026-02-09 16:45:12 -05:00
bb1f9bbbd8
Prevent double-save when quitting IF sessions 2026-02-09 16:44:13 -05:00
8893525647
Sanitize player names in IF save paths to prevent path traversal 2026-02-09 16:43:09 -05:00
57afe9a3ce
Wire restore into play command
When starting an IF game, check for existing save file and restore
if present. Shows 'restoring saved game...' message and broadcasts
restored game state to spectators.

Also cleaned up redundant tests that didn't properly mock the
auto-save functionality now present in ::quit and stop().
2026-02-09 16:39:15 -05:00
6879da0964
Wire ::save escape command 2026-02-09 16:32:50 -05:00
5f0fec14ef
Add restore functionality to IF sessions 2026-02-09 16:32:23 -05:00
e2bd260538
Add save functionality to IF sessions 2026-02-09 16:31:47 -05:00
6ea82a8496
Add save directory helpers for IF sessions 2026-02-09 16:31:12 -05:00
057a746687
Wire up spectator broadcasting in server and play command
- server.py: broadcast IF output to spectators after each input (skip :: escape commands)
- server.py: broadcast leave message when player exits IF mode
- play.py: broadcast game intro text when player starts a game

Spectators at the same x,y coordinates now see formatted output with
[PlayerName's terminal] header and game text.
2026-02-09 16:25:39 -05:00
3dd095b9ea
Add broadcast_to_spectators helper
Helper function sends messages to all players at the same x,y coordinates
as the source player, skipping the source player themselves. Used for IF
spectator broadcasting.
2026-02-09 16:24:48 -05:00
c246732b86
Add mention of ::help 2026-02-09 16:14:34 -05:00
bc69f46d1a
List stories when cannot find 2026-02-09 16:14:21 -05:00
6308248d14
Fix IF session cleanup on failure and player disconnect 2026-02-09 16:11:28 -05:00
b133f2febe
Add play command for starting interactive fiction games 2026-02-09 16:10:29 -05:00
dc342224b1
Wire IF mode into server shell loop and player model 2026-02-09 15:57:24 -05:00
d210033f33
Add IFSession class for interactive fiction subprocess management
TDD implementation of IFSession that manages a dfrotz subprocess.
IFResponse dataclass follows the editor pattern with output/done fields.
IFSession handles spawning dfrotz, routing input, and detecting the prompt.
Escape commands (::quit, ::help) are handled without sending to dfrotz.
2026-02-09 15:54:47 -05:00
6fd19d769b
Add a docker container solution 2026-02-09 12:34:56 -05:00
af941b329b
Add spawn command and wire mobs into server
Phase 6: spawn command creates mobs at player position from loaded
templates. Server loads mob templates from content/mobs/ at startup,
injects world into combat/commands module, and runs process_mobs()
each game loop tick after process_combat().
2026-02-09 11:54:29 -05:00
d15238eb4e
Add mob AI for combat decisions
Phase 5: process_mobs() runs each tick, handling mob attack and defense
decisions. Mobs pick random attacks from their move list when IDLE,
swap roles if needed, and attempt defense during TELEGRAPH/WINDOW with
a 40% chance of correct counter. 1-second cooldown between actions.
Training dummies with empty moves never fight back.
2026-02-09 11:54:29 -05:00
91c1af86e2
Handle mob defeat in combat resolution
Phase 4: when combat ends, determine winner/loser. If the loser is a
Mob, despawn it and send a victory message to the winner. If the loser
is a Player fighting a Mob, send a defeat message instead.
2026-02-09 11:54:29 -05:00
ca53357730
Render mobs as * in viewport
Phase 3: look command now collects alive mob positions using the same
wrapping-aware relative position calc as players, and renders them as *
with the same priority as other players (after @ but before effects).
2026-02-09 11:54:29 -05:00
e6bfd77464
Add mob target resolution in combat commands
Phase 2: do_attack now searches the mobs registry after players dict
when resolving a target name. Players always take priority over mobs
with the same name. World instance injected into combat/commands module
for wrapping-aware mob proximity checks.
2026-02-09 11:54:29 -05:00
84cd75e3a3
Add mob templates, registry, and spawn/despawn/query
Phase 1 of fightable mobs: MobTemplate dataclass loaded from TOML,
global mobs list, spawn_mob/despawn_mob/get_nearby_mob with
wrapping-aware distance. Mob entity gets moves and next_action_at fields.
2026-02-09 11:54:29 -05:00
f36085c921
Add rest command for stamina recovery 2026-02-08 22:16:47 -05:00
0f7404cb12
Fix prefix matching for combat move variants
The variant handler now supports prefix matching for directional variants.
This allows 'pa hi' to match 'parry high', 'pa lo' to match 'parry low', etc.

Implementation:
- First tries exact match on variant key
- Falls back to prefix matching if no exact match
- Returns unique match if exactly one variant starts with the prefix
- Shows disambiguation message if multiple variants match
- Shows error with valid options if no variants match

Tests cover exact match, prefix match, ambiguous prefix, no match,
single-char prefix, case-insensitivity, and preservation of target args.
2026-02-08 14:41:40 -05:00
9c1c6a9e22
Add description field to CombatMove dataclass 2026-02-08 14:32:51 -05:00
77c2e40e0e
Add reload command for hot-reloading TOML content 2026-02-08 14:32:51 -05:00
37766ad69f
Fix typecheck errors in prefix disambiguation 2026-02-08 13:50:26 -05:00
68fa08d776
Extract shared prefix matching helper 2026-02-08 13:48:00 -05:00
7c21310dcd
Remove alias display from command and skill listings 2026-02-08 13:42:00 -05:00