Implements portal-based zone transitions with the enter command.
Players can enter portals at their position to move to target zones
with specified coordinates. Includes departure/arrival messaging to
nearby players and automatic look output in the destination zone.
Portals are matched by partial name or exact alias match.
Extended ThingTemplate with optional container fields (capacity, closed, locked).
When a template includes capacity, spawn_thing now creates a Container instead
of a regular Thing.
Added two example container templates:
- chest.toml: non-portable, capacity 5, starts closed
- sack.toml: portable, capacity 3, starts open
Containers now display their state when viewed:
- Closed containers show "(closed)"
- Open empty containers show "(open, empty)"
- Open containers with items show "(open, containing: item1, item2)"
This applies to both ground items in the look command and inventory items.
Added _format_thing_name helper to both look.py and things.py to handle
the display formatting consistently.
Implements load_zone() and load_zones() functions to parse zone
definitions from TOML files. Wires zone loading into server startup
to register all zones from content/zones/ directory. Updates player
zone lookup to use the registry instead of hardcoded overworld check.
Includes tavern.toml as first hand-built interior zone (8x6 bounded).
Implements a module-level zone registry for looking up zones by name.
Includes register_zone() and get_zone() functions with comprehensive
tests covering single/multiple zones, unknown lookups, and overwrites.
Loads thing templates from content/things/ at startup. Registers
get/drop/inventory commands via things module import. Reconstructs
player inventory from saved template names on login, with graceful
fallback for unknown templates.
Inventory saved as JSON list of thing template names in an inventory
column. Migration adds column to existing databases. load_player_data
returns inventory list, save_player serializes Thing names from contents.
ThingTemplate dataclass mirrors MobTemplate pattern. load_thing_template
and load_thing_templates parse TOML files from content/things/. spawn_thing
creates Thing instances from templates. Includes rock and fountain examples.
Object.move_to() handles containment transfer: removes from old location's
contents, updates location pointer and coordinates, adds to new location.
get/drop commands use move_to to transfer Things between zone and inventory.
Supports name and alias matching for item lookup.
Thing is an Object subclass with description, portable flag, and aliases.
Entity.can_accept() returns True for portable Things, enabling the
containment model where entities carry items in their contents.
Removes dependency on global players dict for spatial queries by using
Zone.contents_at() for spectator lookup. Makes _world local to run_server()
since it's only used during initialization to create the overworld Zone.
Updates test fixtures to provide zones for spatial query tests.
- Removed world module-level variable from look.py
- look.cmd_look() now uses player.location.get_viewport() instead of world.get_viewport()
- look.cmd_look() uses zone.contents_near() to find nearby entities instead of iterating global players/mobs lists
- Wrapping calculations use zone.width/height/toroidal instead of world properties
- Added type check for player.location being a Zone instance
- Removed look.world injection from server.py
- Updated all tests to remove look.world injection
- spawn_mob() and combat commands also migrated to use Zone (player.location)
- Removed orphaned code from test_mob_ai.py and test_variant_prefix.py
Adds zone_name field to PlayerData and accounts table to track which
zone a player is in. Defaults to 'overworld'. Includes migration logic
to handle existing databases without the column. Server now resolves
zone from zone_name when loading player data.
Removed module-level world variable and replaced all world.wrap() calls
with player.location.wrap(). Added Zone assertion for type safety,
matching the pattern in movement.py. Updated tests to remove fly.world
injection since it's no longer needed.
Movement commands now access the zone through player.location instead of
a module-level world variable. send_nearby_message uses
zone.contents_near() to find nearby entities, eliminating the need for
the global players dict and manual distance calculations.
Tests updated to create zones and add entities via location assignment.
Zone(Object) is a spatial area with a terrain grid. Supports
toroidal wrapping and bounded clamping, passability checks,
viewport extraction, and contents_at(x, y) spatial queries.
Zones are top-level containers (location=None) that accept
everything via can_accept().
Entity gains location from Object and narrows x/y back to int
(entities always have spatial coordinates). No behavioral change —
all existing tests pass unchanged.
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.
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.
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.
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.
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.
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.
Implements Phase 4 of the z-machine compatibility plan.
Creates automated regression tests that smoke-test all supported games
(V3, V5, V8) by loading each story, executing basic commands, and verifying
the interpreter doesn't crash.
Key features:
- Parametrized test covering 7 games (zork1, curses, photopia, Tangle,
shade, LostPig, anchor)
- QuietScreen class that disables [MORE] prompts for unattended testing
- AutoInputStream that auto-feeds commands then exits cleanly
- Tests verify: no crashes, unimplemented opcodes, and minimum instruction count
- All tests pass in ~2 seconds
Tests skip gracefully if story files aren't present, making this safe to
run in CI or on systems without all game files.