Compare commits

..

No commits in common. "51dc583818a0df2b88db21fbdd37bc40e01606ee" and "544b7dfee187c0c5b9e8677cb86d7356030039c5" have entirely different histories.

5 changed files with 5 additions and 522 deletions

View file

@ -70,6 +70,5 @@ lessons
plans
-----
- ``docs/plans/roadmap.rst`` — master plan: 12 phases from object model through DSL (start here)
- ``docs/plans/if-terminal.txt`` — level 1 IF architecture design (predates implementation)
- ``docs/plans/zmachine-game-compatibility.rst`` — game test plan for interpreter

View file

@ -1,400 +0,0 @@
roadmap — order of operations
=============================
the master plan. what to build and in what order.
current state: the engine works. command system, game loop, mode stack,
combat (state machine + TOML moves), persistence (SQLite), editor mode,
IF/z-machine (dfrotz + embedded interpreter), terrain generation, content
loading — all built. what's unbuilt is the object model and everything it
enables: zones, things, inventory, containers, portals, verbs, multi-zone
worlds, rich NPCs, player building, and eventually the DSL.
the architecture-plan.txt priority list (items 1-8) is essentially done.
this plan picks up from there.
each phase builds on the one before it. the MUD stays functional
throughout — no phase leaves things broken.
phase 1: object foundation
--------------------------
the base class everything inherits from. pure additive — nothing breaks,
existing tests keep passing.
build:
- Object base class: name, location (Object | None), x (int | None),
y (int | None)
- contents property — reverse lookup: everything whose location is self
- can_accept(obj) method — default: False
- Entity inherits from Object instead of being a standalone dataclass
- Player, Mob unchanged externally — they still work exactly as now
the location field exists but isn't wired up yet. Entity still has x, y
from before, they just come through Object now. no behavioral change.
depends on: nothing
enables: everything below
refs: object-model.rst (hierarchy, containment mechanics)
phase 2: zones + overworld migration
-------------------------------------
the big migration. the overworld becomes a Zone object. players live
inside it. movement, rendering, persistence, mob spawning all update.
build:
- Zone class: width, height, toroidal flag, terrain data, generator
- Zone.can_accept() returns True (zones accept everything)
- Zone.contents_at(x, y) for spatial queries
- convert current World terrain generator into Zone initialization — the
procedural overworld becomes a Zone instance loaded at startup
- Entity.location = zone (players and mobs live in the overworld zone)
- movement system goes through zone: wrapping, passability checks come
from player.location (the zone), not a global world reference
- viewport rendering reads terrain from player.location
- persistence adds zone name to save/load schema
- mob spawning sets location=zone
- remove world injection into command modules — commands access
player.location instead of module-level globals
- send_nearby_message uses zone.contents_at() instead of iterating the
global players dict
the global ``_world`` singleton goes away. ``players`` dict can stay for
session management but spatial queries go through zones.
migration strategy:
- keep World class temporarily as Zone's terrain generator
- migrate one system at a time (movement → rendering → persistence →
broadcasting) with tests green at every step
- object-model.rst "how existing code maps in" section traces each path
depends on: phase 1
enables: multi-zone, portals, interior spaces
refs: object-model.rst (Zone section, migration paths),
zones-and-building.rst (zone properties, wrapping, generators)
phase 3: things + inventory
---------------------------
items exist. you can carry them. the world has stuff in it.
build:
- Thing class: description, portable flag (from Object)
- Entity.can_accept() allows portable Things (inventory)
- pick up / drop commands
- inventory command (list Things whose location is the player)
- items on the ground: Thing at location=zone, x=5, y=10
- look shows nearby items at your coordinates
- thing templates in TOML (content/things/) loaded at startup
- thing spawning: template → instance with location=zone
- viewport shows ground items (new symbol or marker)
- persistence: player inventory persists to SQLite; ground items respawn
from templates on restart
depends on: phase 2 (things need zones to exist in)
enables: equipment, crafting ingredients, quest items, loot
refs: object-model.rst (Thing section), things-and-building.rst (decorator
pattern, content format)
phase 4: containers + portals
-----------------------------
nesting and zone transitions. both depend on Thing existing.
containers:
- Container class: capacity, closed (bool), locked (bool)
- Container.can_accept() checks capacity + open state
- put / get commands (move Thing into/out of Container)
- open / close commands
- lock / unlock (requires key item — first real item interaction)
- nested inventory display (backpack contains: ...)
portals:
- Portal class: target_zone, target_x, target_y, portable=False
- zone transition: step on portal → location changes to target zone,
position changes to target coords
- enter / go command for explicit portal use
- build at least one interior zone (tavern or test room) with hand-built
tile data from TOML
- two-way portals (portal in each zone pointing at the other)
- look shows portal presence, portal descriptions
depends on: phase 3 (portals and containers are Things)
enables: multi-zone world, interior spaces, locked doors, bags, keys
refs: object-model.rst (Container, Portal sections),
zones-and-building.rst (zone data format, hand-built zones)
phase 5: verbs + interaction
----------------------------
the layer that makes the world feel alive. drink from a fountain, pet a
mob, read a sign. anything can have verbs.
build:
- @verb decorator on Object — any object in the world can have verbs
- @thing decorator for spawnable templates with verbs pre-attached
- verb resolution: "drink fountain" → find fountain at player's
coordinates → call its drink verb handler
- verb dispatch integrates with command system — verbs checked as
fallback after global commands fail to match
- content format: verbs defined in TOML alongside thing definitions,
or in python via the decorator
- default verbs: look at / examine (work on everything with a
description)
depends on: phase 4 (need objects with verbs to interact with)
enables: rich interaction, builder content, puzzle creation
refs: things-and-building.rst (@thing/@verb decorator system, full
PepsiCan example)
phase 6: multi-zone content + building
--------------------------------------
the world has places. builders can create more.
build:
- zone loading from TOML files (hand-built zones with tile grids)
- hub / phone-switch zone with portals to different areas
- tutorial zone for new players
- tavern / social zone (interior, bounded, not toroidal)
- paint mode: admin tool for editing zone terrain tile-by-tile
- zone TOML export (runtime state → version-controlled files)
- spawn points per zone (where you appear on entry)
- zone-specific ambient messages
- per-zone mob spawning rules (what mobs live here, density, respawn)
depends on: phase 4 (portals connect zones)
enables: a world with actual geography, builder workflow
refs: zones-and-building.rst (zone format, paint mode, building tools,
portal network design)
phase 7: GMCP + rich client support
------------------------------------
structured data for graphical MUD clients (Mudlet, Blightmud, etc.).
independent of the object model — can be done earlier if desired.
build:
- GMCP option negotiation in telnet handshake (telnetlib3 supports this)
- Char.Vitals package: pl, stamina, max_stamina
- Char.Status package: flying, resting, mode, combat state
- Room.Info package: zone name, x, y, terrain type
- MSDP for real-time variable updates (client-side HP bars, gauges)
- map data for clients that support structured map rendering
telnetlib3 already handles the protocol layer. this phase is defining the
data packages and wiring them to game state changes.
depends on: nothing (can start anytime)
enables: rich MUD client experience alongside raw telnet
refs: DREAMBOOK.md (things to explore: GMCP, MSDP)
phase 8: NPC evolution
----------------------
mobs become characters, not just combat targets.
build:
- dialogue trees: TOML-defined, triggered by "talk to" or "ask" commands
- non-combat behaviors: patrol, wander, idle, shopkeeper, guard
- behavior trees or state machines defined in TOML
- mob reactions to verbs ("pet" the cat, "ask" the bartender about X)
- spawn/respawn system using location=None as template state (die →
location=None, timer → respawn at zone with fresh stats)
- mob schedules (bartender appears in tavern during the day, gone at
night)
- shopkeeper mobs (buy/sell interface using inventory system)
depends on: phase 5 (verbs for interaction), phase 6 (zones for mobs
to inhabit)
enables: living world, quests, commerce
refs: architecture-plan.txt (entity model section),
object-model.rst (location=None for templates/despawned)
phase 9: world systems
----------------------
time, weather, seasons. the world breathes.
build:
- game time cycle: ticks → hours → days → seasons
- time of day affects descriptions ("the tavern is dim in the evening
light" vs "sunlight streams through the windows")
- weather system: rain, wind, storms (per zone or global)
- weather affects gameplay: rain slows movement, storms block flying,
cold water is worse in winter
- seasonal terrain variation: snow in winter, flowers in spring, color
palette shifts
- ambient messages tied to time/weather ("a cold wind picks up")
depends on: phase 6 (zones with descriptions to vary)
enables: atmosphere, seasonal events, weather-dependent gameplay
refs: DREAMBOOK.md (seasons, weather, time of day)
phase 10: player creation + housing
------------------------------------
players shape the world. the MOO dream.
build:
- player-owned zones (personal rooms, houses, pocket dimensions)
- building commands: create zone, set dimensions, paint terrain, place
things
- in-world editing via editor mode (descriptions, verb scripts)
- player-created content stored in SQLite
- admin review + export to files (sqlite → TOML for version control)
- permissions: who can build, who can edit what, visitor access controls
- crafting system: combine things → new thing, recipes in TOML
- IF creation tools: use the IF primitives to build small adventures
from inside the MUD
depends on: phase 6 (zones), phase 5 (verbs), persistence
enables: the MOO vision — player-built, player-programmed world
refs: DREAMBOOK.md (user-created content, presence and community),
architecture-plan.txt (player-created content in sqlite,
engine/content boundary)
phase 11: the DSL
-----------------
replace python callables with a scripting language writable from inside
the MUD. this is the hardest design challenge in the project.
build:
- language design: informed by what phases 1-10 needed to express
- interpreter/runtime: sandboxed, no file/network access
- content definitions gain ``body: dsl:script_name`` option alongside
``body: python:module.function``
- DSL scripts editable in-world via editor mode
- help system teaches the DSL from inside the MUD
- standard library: common verbs, triggers, conditions
- migration path: existing python handlers get DSL equivalents over time
defer specifics until phases 1-10 reveal exactly what the DSL needs to
express. the ecosystem research (dsls.md) catalogs prior art: MOO's
language, LPC, MUSH softcode.
depends on: everything above being stable
enables: non-programmers creating game content from a telnet session
refs: architecture-plan.txt (DSL section, content definition format),
docs/research/dsls.md (prior art)
phase 12: horizons
------------------
things from the dreambook's "explore later" list. order is flexible —
tackle based on interest and what the world needs.
- web client: xterm.js terminal emulator pointed at telnet port. honor
the protocol even in the browser
- MCCP compression for bandwidth-heavy sessions
- inter-MUD communication (IMC2, I3)
- AI-driven NPCs: LLM-backed conversation, personality, memory
- alternate screen buffer: split views (map panel + text panel)
- sound via MSP or links
- procedural dungeon generation: zones with random layout per visit
- quest system: objectives, tracking, rewards, quest-giver NPCs
dependency graph
----------------
::
phase 1: object foundation
phase 2: zones + migration
phase 3: things + inventory
phase 4: containers + portals
phase 5: verbs + interaction
│ phase 7: GMCP (independent)
phase 6: multi-zone content
├────────────────┐
▼ ▼
phase 8: NPCs phase 9: world systems
│ │
└───────┬────────┘
phase 10: player creation
phase 11: DSL
phase 12: horizons
notes
-----
**the phase 2 migration is the riskiest step.** it touches movement,
rendering, persistence, mob spawning, and message broadcasting. the
strategy is incremental: migrate one subsystem at a time, keep tests
green, keep the MUD playable. the object-model.rst doc has a detailed
"how existing code maps in" section for this.
**phase 7 (GMCP) floats.** it doesn't depend on the object model and
can be done whenever energy or interest is there. it's slotted at 7
because the data it reports (zone name, position, combat state) gets
richer as the object model matures, but basic vitals work anytime.
**phases 8-10 have some flexibility.** world systems (weather, time)
could come before NPC evolution. player creation could come before
either. the dependency shown is the tightest path — if housing needs
NPCs (shopkeepers, guards), then 8 before 10. if not, reorder freely.
**the DSL (phase 11) is intentionally last.** the architecture-plan.txt
says "defer it until we know exactly what it needs to express." phases
1-10 discover that organically.
related docs
-------------
- ``DREAMBOOK.md`` — vision, philosophy, wild ideas
- ``docs/how/architecture-plan.txt`` — engine/content boundary, original
priority list (items 1-8 now complete)
- ``docs/how/object-model.rst`` — class hierarchy, containment, verbs,
migration paths
- ``docs/how/zones-and-building.rst`` — zone format, building tools,
paint mode, portal network
- ``docs/how/things-and-building.rst`` — @thing/@verb decorator system
- ``docs/how/combat.rst`` — combat system (built, referenced by phase 8)
- ``docs/how/commands.txt`` — command system (built, extended by new
phases)
- ``docs/research/dsls.md`` — scripting language catalog (phase 11)

View file

@ -2,20 +2,14 @@
from dataclasses import dataclass, field
from mudlib.object import Object
@dataclass
class Entity(Object):
"""Base class for anything with position and identity in the world.
class Entity:
"""Base class for anything with position and identity in the world."""
Inherits name, location from Object. Narrows x, y to int (entities
always have spatial coordinates).
"""
# Entities always have spatial coordinates (override Object's int | None)
x: int = 0
y: int = 0
name: str
x: int
y: int
# Combat stats
pl: float = 100.0 # power level (health and damage multiplier)
stamina: float = 100.0 # current stamina

View file

@ -1,37 +0,0 @@
"""Base class for everything in the world."""
from __future__ import annotations
from dataclasses import dataclass, field
@dataclass
class Object:
"""Base class for everything in the world.
Every object has a location (another Object, or None for top-level things
like zones). The location pointer is the entire containment system
contents is just the reverse lookup.
"""
name: str
location: Object | None = None
x: int | None = None
y: int | None = None
_contents: list[Object] = field(
default_factory=list, init=False, repr=False, compare=False
)
def __post_init__(self) -> None:
if self.location is not None:
self.location._contents.append(self)
@property
def contents(self) -> list[Object]:
"""Everything whose location is this object."""
return list(self._contents)
def can_accept(self, obj: Object) -> bool:
"""Whether this object accepts obj as contents. Default: no."""
return False

View file

@ -1,73 +0,0 @@
"""Tests for the Object base class."""
from mudlib.object import Object
def test_object_creation_minimal():
"""Object can be created with just a name."""
obj = Object(name="rock")
assert obj.name == "rock"
assert obj.location is None
assert obj.x is None
assert obj.y is None
def test_object_creation_with_position():
"""Object can have spatial coordinates."""
obj = Object(name="tree", x=5, y=10)
assert obj.x == 5
assert obj.y == 10
def test_object_creation_with_location():
"""Object can be placed inside another object."""
zone = Object(name="overworld")
tree = Object(name="oak", location=zone, x=3, y=7)
assert tree.location is zone
def test_contents_empty_by_default():
"""An object with nothing inside has empty contents."""
obj = Object(name="empty")
assert obj.contents == []
def test_contents_tracks_children():
"""Contents lists objects whose location is self."""
zone = Object(name="overworld")
tree = Object(name="oak", location=zone, x=3, y=7)
rock = Object(name="rock", location=zone, x=1, y=1)
assert tree in zone.contents
assert rock in zone.contents
assert len(zone.contents) == 2
def test_contents_nested():
"""Containment can nest: zone > player > backpack > item."""
zone = Object(name="overworld")
player = Object(name="jared", location=zone, x=5, y=5)
backpack = Object(name="backpack", location=player)
gem = Object(name="gem", location=backpack)
assert player in zone.contents
assert backpack in player.contents
assert gem in backpack.contents
# gem is NOT directly in zone or player
assert gem not in zone.contents
assert gem not in player.contents
def test_can_accept_default_false():
"""Object.can_accept() returns False by default."""
obj = Object(name="rock")
other = Object(name="sword")
assert obj.can_accept(other) is False
def test_contents_returns_copy():
"""Contents returns a copy, not the internal list."""
zone = Object(name="zone")
Object(name="thing", location=zone)
contents = zone.contents
contents.append(Object(name="intruder"))
assert len(zone.contents) == 1 # internal list unchanged