Releases: MalifaxLax/megamoo
Release list
MegaMOO 0.10.0-beta5
MegaMOO 0.10.0-beta5
A security fix and a small usability one. Upgrading is a good idea if you
run a world with staff verbs on it.
pip install --upgrade megamoo
No world changes are needed. The fix is entirely engine-side, so an
existing world gets it by upgrading the engine — nothing to migrate, no
database edit, no verb to reload.
The gm level a verb declares is now enforced
A verb carries the gm level it requires, and help uses that level to
decide whether to list a command for you. That value gated nothing else.
Whether a command actually refused you depended entirely on the
auth_level(pobj) < 3 guard written at the top of the verb — so a staff
verb whose guard was missing was open to anybody who typed its name.
Two places in the engine described this check as though it already
existed: the verb loader logged that it was "gating dispatch" at a level,
and shutdown() explained that its own guard was necessary because "the
command parser's auth check does not cover a call that arrives through
call_verb". Neither was true. Both are now.
The rule is auth_level(player) >= verb.auth — the level the verb asks
for, not a fixed number — checked where a typed command is resolved. A
refused verb answers "Do what?", the same as one that does not exist, so
the staff command list is not discoverable by watching which names deny
you.
Was your world exposed? Almost certainly not. This required a staff
verb with no guard in its body, and neither the starter world nor
Shadowfall had one — all 155 staff verbs across both declare exactly what
their guards enforce. Nothing loses reachability in this release. If you
wrote your own staff verbs, the thing to check is that each one still
opens with its auth_level(pobj) < N guard; the loader now derives the
verb's level from that guard, so having it makes both mechanisms agree.
call_verb remains deliberately unguarded. An internal call must not be
subject to the caller's level, which is exactly why staff verbs keep
their own guard — this does not replace it, it stops a forgotten one from
meaning the command is simply open.
@make answers to @create
@create is what LambdaMOO calls it and the first thing most people
type; the starter world had only @make, so the obvious spelling
answered "Do what?". It is one verb with two names rather than a second
verb, so there is one body and one set of behaviour.
Also
- The verb watcher no longer prints one warning per file for verb
directories whose object does not exist. A world under development
accumulates these — averbs/900/you sketched before building #900 —
and eleven warnings said one thing. Now one line naming the objects. evaland/resolve names from your inventory even when your
character has no location. Previously the inventory was reachable only
as a side effect of standing in a room, so a character in chargen or an
isolation container could not name a thing in their own hands and got a
bare NameError.- The splash shows
$versionwhen a world sets one, falling back to the
engine's version. A world under development has a version of its own,
and a player arriving at your game cares which game it is rather than
which server it runs on.
Verifying this release
All three are built from the same commit, and the wheel and sdist here
are byte-identical to the ones on PyPI.
megamoo-0.10.0b5-py3-none-any.whl
8b4dc50c80458aed7925d0cc6fbdeac33498ffb7d149147369d96c4fa5e5008e
megamoo-0.10.0b5.tar.gz
3f004eac3ccd3cd9c4386988bf6af5bc31069c903777a4e5400bcad919ddeac0
megamoo-0.10.0b5-beta.zip
162225accc45b10be883828badeb3c6940052ed8c55684eed4af83478bbf3a61
Guide: https://malifaxlax.github.io/megamoo/
Discussions: https://github.com/MalifaxLax/megamoo/discussions
Discord: https://discord.gg/E74YsbbpCA
MegaMOO 0.10.0-beta4
Take this one. b3 was correct for a single player; with two people in a room it was not.
pip install --upgrade megamoo
megamoo init mygame
cd mygame && megamoo --devFixed
- Every character introduced itself as "ICharacter." Emit substitution prefers
cnameand chargen only ever setname, so every third-person message named the prototype. The actor never sees those messages, which is why it survived so long. - Ordinary exits announced departures as
%%S %%OMODE out.— a default message left in the retired%sigil, doubled by the display escape. - Rooms counted each player once per move, forever.
plistcompared objects against a list of object numbers, so arrivals always appended and departures never removed. It was wrong in four separate places; rooms already carrying the damage now repair themselves. @move ... with <message>never delivered its message — it was readingiobjfor text the parser had already put indobj2.@telpassed no subject to any of its three emits, so&Snever substituted — which is why the stock wizard's message hardcoded a name. It also told you about your own departure.- A client that typed before the prompt lost its first line. The opening negotiation window read input and discarded it. Invisible to a person, reliably fatal to a script.
- Containers weigh what they hold (the check was per-item), and a sheath now refuses a cannonball — the opening was computed from the two largest dimensions instead of the two smallest.
@adverb ... with rx auth=3created a verb namedrx auth=3.=is matched anywhere in a string and wins over a later word preposition; the verb readsargstrnow, and the rule is documented in the guide.find_verbscanned local verbs linearly before consulting its own index: 8,819 ns on#3 Base_Character, where every staff command lives. Now a flat 148 ns.
Also
TLS on a second port, MCCP2 compression, MSSP for listing sites, megamoo.toml actually read, and fifteen config settings deleted that were declared, documented, sometimes validated, and read by no code at all.
Community
Questions and help: GitHub Discussions. Answers stay public and searchable.
Verified from this wheel rather than the source tree: installed into a clean virtualenv, megamoo init, then the browser client over HTTP and the game over telnet with two connections — splash, login, walking, chargen, entering the game, a room emit naming the actor to the other player, and @checkpoint writing a real timestamp. 355 tests.
MegaMOO 0.10.0-beta3
The release the starter world survives. If you have any 0.10.0 beta, take this one — b0, b1 and b2 all ship a world that cannot be played.
pip install --upgrade megamoo
megamoo init mygame
cd mygame && megamoo --devFixed since b0
- Checkpoints kept exactly one file. The filename was
checkpoint_&Y&m&d_&H&M&S— a format string with no directives — so every checkpoint overwrote the last while the command said ten were kept. - Nothing could be picked up.
getreadsitem.hands, declared on a room, sogetraised on every item.drop,give,wearandputwith it. - Characters could not be created. Chargen died on the gender prompt.
- Eating, drinking, buying and offering all raised on first contact.
@telcould not teleport, because a predicate was declared only where it was true.- The browser client was a 404 for everyone who installed it — the assets were never packaged, so it worked only from a clone.
- The login splash named the developer's own game, and chargen offered his characters as occupied slots.
New
- TLS, on a second port so
telnetkeeps working. Refuses to start rather than quietly serving plaintext. - MCCP2 compression — 56% fewer bytes on a measured session.
- MSSP, so listing sites can poll the server; advertises the TLS port.
megamoo.tomlis read. It was written byinitand read by nothing.
Removed
Fifteen config settings that were declared, documented, in some cases validated at startup, and read by no code at all — including ssl_enabled, which validated your certificate and then served plaintext.
Verified from this wheel rather than the source tree: installed into a clean virtualenv, megamoo init, then the browser client over HTTP and the whole game over telnet — splash, login, walking, chargen, entering the game, get, eat, and @checkpoint writing a real timestamp. 352 tests.
MegaMOO 0.10.0-beta
MegaMOO 0.10.0-beta
The engine becomes installable, and stops containing a game.
pip install megamoo
megamoo init mygame
cd mygame && megamoo --dev
Breaking: the substitution sigil moved from % to &. An existing world
must be converted with tools/migrate_sigil.py or its message tokens
render as literal text. '%' is Python's formatting operator, so
"%<245>%s" % name raised ValueError -- the commonest formatting idiom
in the language colliding with the commonest display idiom in the
engine. '&' was chosen by counting real game text: across 5,217 output
strings, '%' appeared 1,132 times, '|' 14, and '&' zero.
Breaking: mm.db and 'moo verbs/' are no longer in the repository root.
They are the template 'megamoo init' copies into a game directory that
belongs to the builder. Upgrading is 'pip install --upgrade megamoo'
with nothing to merge.
Also: FileIO builtins (fileread/filewrite/fileappend and nine more);
get_database(); @program and @PORT write the file before the database,
with disk as the source of truth; startup reconciles verb files changed
while the server was down.
MegaMOO 0.9.0-beta
Two things a verb could not do before: wait without freezing the game, and
talk to anything outside the server.
suspend
suspend(n) steps aside for a while and then carries on from the next line,
with your variables exactly as you left them. Other verbs run meanwhile.
pobj.msg("You begin meditating...")
suspend(5)
pobj.msg("You feel refreshed.")Until now the only way to wait was pause(), which sleeps the verb thread and
freezes every player and every ticker for the duration. That makes patrols,
rituals and staged events straightforward:
for _step in ('north', 'east', 'south', 'west'):
call_verb(this, 'gmove', args=_step)
suspend(10)Exactly one verb still runs at any instant. That guarantee is load-bearing —
a fifth of the verb corpus reads a property, changes it and writes it back, and
interleaving would silently lose updates — so it is preserved. What changes is
that "one at a time" no longer means "one until it finishes". Serialisation
moved from a single-worker pool to an explicit baton, and the pool grew so a
parked verb cannot deadlock the server.
A suspend is a yield point: the world can move across it. An object read before
the call may have moved or been recycled by the time the verb wakes. Re-read
what matters — the same rule MOO has always had.
request
request() calls something outside the server — a web service, a local model —
and returns immediately. The answer arrives later by calling a verb:
request('http://127.0.0.1:11434/api/generate',
reply='npc_said', on=this, method='POST',
json={'model': 'llama3.2', 'prompt': argstr, 'stream': False},
tag=pobj.objnum)The reply verb receives ok, status, body, error and tag as ordinary
variables. Failures arrive the same way, with ok false and error saying
why — by then your verb has already returned, so there is nowhere for an
exception to go.
The response body is never run as code. It arrives as a value, so a model that
emits something resembling Python cannot get it executed.
Between them these are what AI-driven NPCs need: the model call without
freezing the world, and the ability to wait mid-verb.
Fixes
- The login screen reported the wrong version — the starter said
0.7.0while
running0.9. There were four version strings disagreeing with each other;
there is now one, and the MOTD derives from it. Task.suspend's documentation described asuspend()builtin that did not
exist, with an example that could not work. It exists now.
Also
- New guide chapter: Waiting, and talking to the outside
- 23 new tests, including the one-verb-at-a-time invariant itself
Requires Python 3.10+. No pip install needed for the core server.
unzip megamoo-0.9.0-beta.zip
cd megamoo
python3 megamoo.py mm.db # default port 7777
telnet localhost 7777
MegaMOO 0.8.0-beta
Eleven staff commands, a LambdaMOO database importer, and a guide chapter for
people arriving from a MOO.
Bring an old world across
@import reads a classic LambdaMOO database — format versions 1 through 4, so
LambdaMOO up to 1.8 — and creates objects from it, carrying the hierarchy and
properties and remapping object references as it goes. @import/dry reports
what it would do without creating anything; do that first.
Verb code comes across too, but inert. It is MOO source and this engine runs
Python, so nothing imported can execute. Rather than discard it, each verb keeps
its original source verbatim beneath a docstring recording where it came from —
source object, owner, permissions, argument spec — and a substitution table for
porting it. The verb is stored hidden, without the execute permission, at gm3.
Your old code ends up sitting on the right objects under the right names,
waiting to be rewritten, instead of in a tarball you never open.
@grep UNPORTED MOO SOURCE
lists what is left to do.
Tested against JHCore-DEV-2: 237 objects and 2729 verb programs parse in 0.04s
with no warnings and import with no failures.
New commands
| Command | Level | Does |
|---|---|---|
@find |
gm2 | Find objects anywhere by name; in <room>, isa <parent>, /exact, /count |
@audit |
gm2 | What a player owns, grouped by location; /rooms, /nowhere, /count |
@kids |
gm2 | What inherits from an object; /all draws the descendant tree |
@quota |
gm2 | How much of the database someone has built |
@copy |
gm3 | Duplicate an object's own properties |
@grep |
gm3 | Search verb code across the database |
@ps / @kill |
gm3 | List running tasks, and stop one |
@dump / @load |
gm3 | Move a single object between databases as text |
@import |
gm3 | Import a LambdaMOO database |
Fixes
getattr defaults never worked on MOO objects. A missing property returns a
sentinel rather than raising, so getattr(exit, 'lockfail', 'You cannot pass.')
returned the sentinel — which prints as an empty string. A locked exit with no
custom message printed a blank line. The same held for climb and jump failure
text, open/close messages, stand-up messages, sit prepositions, seat counts and
effect chances. 97 call sites corrected.
Room broadcasts bypassed per-object msg overrides. msg_room called
notify() on each occupant directly rather than going through the overridable
msg verb, so a deafened character still heard everything.
@unset crashed before the first @set of a session instead of reporting
"Nothing to undo."
API eval ran as an exit. It defaulted to #7, described in a comment as the
admin player; #7 is the chargen arch. It now finds a wizard.
A stale duplicate verb file would have silently reverted a fix on the next
full reload.
Also
- MOO compatibility:
pass_(*args),tell(), theE_*error values as
first-class values, and the LambdaMOO string utilities onsu call_verbtakes positional arguments- A new guide chapter, Coming from LambdaMOO
- The Command Reference now lists all 74 commands; it was missing eleven
- 29 new tests for the importer
Requires Python 3.10+. No pip install needed for the core server.
unzip megamoo-0.8.0-beta.zip
cd megamoo
python3 megamoo.py mm.db # default port 7777
telnet localhost 7777
mm.db is the starter world: the base object hierarchy, character generation,
and the full building suite. Point the server at any other filename to
initialize a fresh schema instead.
MegaMOO 0.7.3-beta
Security and correctness fixes. mm.db in this archive already contains the verb changes; an existing world picks up the engine fixes but not the verbs.
Fixed: passwords were echoed in plaintext over telnet
network.py defined the telnet ECHO option and never sent it, so a password typed at the login prompt appeared on the player’s own screen — and therefore in scrollback, screenshots, screen shares and recordings. The web client had implemented its own masking and its comment assumed telnet already handled this.
Per RFC 857, the server now sends IAC WILL ECHO before a password prompt and IAC WONT ECHO once the line has been read, so exactly one line is masked and echo is restored whether the login succeeds or fails. The prompt pattern moved to globals.py so both transports share one definition.
Anyone running an earlier build should assume passwords typed over telnet have been visible, and rotate accordingly.
Fixed: new characters were never gated by roundtime
do_wait — which reports whether a character may act — only consults roundtime for a character whose status carries a truthy life. Characters leaving chargen had an empty status, so no IC command was ever held, including the thirteen gated in 0.7.2 and movement.
Chargen now sets status["life"] = 1 and rt = 0. Characters created before this keep an empty status and need a backfill; setting life inside status (not as a property of its own) is what do_wait reads.
Fixed: verbs edited in-game were stale on disk
msg, msg_room and _allow had been edited in the database and never written back, leaving the files older than the database. That matters more than staleness: the auto-reloader pushes disk over database, so an old file silently reverts the newer verb the moment anything touches it. Every verb in the archive now matches its database copy — 201 of 201, checked before this release was built.
Upgrading an existing world
Engine fixes arrive with the files. Verb and database changes do not. Either start from the mm.db here, or copy the verbs you want from moo verbs/ and let the auto-reloader pick them up. For an existing world, also set status["life"] = 1 on characters that predate this release, or roundtime will not hold them.
Also in this release
su gains the LambdaMOO $string_utils surface: from_list, english_list, explode, words, first_word, rest, trim/triml/trimr, space, left/right/centre, capitalise, a_or_an, ordinal, group_number, is_numeric, pluralise, find_prefix, index_delimited, strip_chars, strip_all_but, char_list.
$string_utils is aliased to the same object, so porting only changes the call syntax:
$string_utils:from_list(lst, ", ") MOO
$string_utils.from_list(lst, ", ") here
Where MOO and Python differ, these follow MOO — explode() drops empty pieces, capitalise() leaves the rest of the string alone, find_prefix() returns -1 on an ambiguous prefix, and ordinal() handles the teens.
MegaMOO 0.7.2-beta
Bug-fix release. The database in this archive already contains the verb changes; an existing world picks up the engine fixes but not the verbs (see the end).
Fixed: verbs could not return a value to their caller
Verb code runs inside a generated wrapper function, so a verb writing result = X was binding a local that vanished when the function returned None. Every verb using the documented result idiom had been answering None to every caller.
do_wait was the visible casualty. It reports whether a character may act — roundtime, unconscious, sleeping, paralyzed, webbed, immobilized, entangled, imprisoned, bound — and sets result = True in all eleven branches. So if call_verb(pobj, "do_wait"): never fired, while the verb still printed [Wait: N seconds] on its way out, which is exactly why it looked as though it worked. Five shipped verbs guarded on it (lock, unlock, latch, unlatch, buy) and all five were no-ops.
The wrapper now honours both return X and result = X. An explicit return still wins.
IC commands now gate on do_wait
Thirteen commands — get drop put give wear remove open close climb jump tap go inventory — each tested pobj.rt alone and missed the eight immobilising conditions. They now call do_wait.
Movement is included: typing a direction dispatches the compass verb, which had no roundtime check at all, so a character could walk away mid-roundtime. That gate is on the #17 (ICRoom) copy only — the same verb is defined separately on #15 and #16, and #16 is the out-of-character room, where roundtime from a fight should never strand anyone.
Verb code reads as attribute access
The verb library used getattr(obj, "prop", default) throughout — 627 calls across 112 of 202 files. The defence was never needed: a missing property returns a falsy sentinel rather than raising. 523 calls are now plain attribute access.
_NullAttr gains ordering, treating a missing property as 0, so pobj.rt > 0 is legal for a property that need not exist anywhere. Comparison against a non-number still raises.
Also
moo/moo_compat.py—tell(),pass_()and the sixteen MOO error values as first-class comparable values, injected into every verb namespacehelpno longer lists engine machinery or every abbreviation of every command, and no longer advertises staff commands to players who cannot run them- Documentation corrected: verb code runs with full Python privileges, and the gm3 gate on
@programis the security boundary — there is no sandbox
Upgrading an existing world
Engine fixes arrive with the files. The verb changes live in the database and do not. Either start from the mm.db here, or copy the verbs you want from moo verbs/ — the auto-reloader will pick them up.
MegaMOO 0.7.1-beta
Bug-fix release. Upgrading is a matter of replacing the engine files; mm.db in this archive already contains the fixes, so a world you have already started needs the database changes applied by hand (see below).
Fixed: help was unusable for non-staff characters
Reported on the MOO Talk list. A character without staff auth saw roughly a hundred help entries, and some of them did not work. Three separate causes:
- Every abbreviation was listed separately.
helpiterated the resolved-verb table, which is keyed by every legal prefix of every verb name, so a single command appeared as@u,@un,@unhidev,@unhideve,@unhidever,@unhideverb. The listing now keys on the canonical name, which also collapses aliases —@setrather than@setand@val. - Engine machinery was listed as commands. Hook verbs invoked internally by name (
look_here,on_get,msg,_rt, and similar) appeared alongside real commands. These cannot simply be flagged hidden — that removes them from verb dispatch as well, which breakslook— sohelpnow filters them by name. - Six commands were advertised and then refused.
helpfilters on a verb’sauthvalue, but@set,@val,@title,@unset,@unhideverband@rempropdeclared0while their code required gm2 or gm3. They were offered to everyone and then answeredDo what?, which is also the response for an unrecognised command. Their declared levels now match their guards.
One verb implements all twenty-four movement commands, so collapsing to a canonical name would have shown only n; the listing shows the long forms instead.
Added: MOO compatibility layer
moo/moo_compat.py, injected into every verb namespace:
tell(who, ...)— concatenates and sends, so portedplayer:tell(...)reads as writtenpass_(...)— calls the same verb on the parent, as MOO’spass()does (passis reserved in Python)E_PERM,E_TYPE,E_PROPNFand the rest of the sixteen MOO error values, as first-class values that can be compared with==, stored, and raised
Verb code cannot import these — the sandbox withholds __import__ — so they arrive in the namespace the same way su and ou do.
Applying the database fixes to an existing world
The help verb lives in the database, so an existing world does not pick these up from the engine. Either start from the mm.db in this archive, or set the six auth levels with @verbauth (@set 3, @val 3, @title 2, @unset 3, @unhideverb 3, @remprop 3) and replace the help verb on #15 with the copy in this release.
MegaMOO 0.7.0-beta
A text-based virtual world engine — a modern LambdaMOO in Python 3, with no third-party dependencies.
Getting started
Download the zip below, unpack it, and run:
python3 megamoo.py mm.db mygame.db # make your own world from the starter
./mm mygame.db # run it
telnet localhost 6770Log in as wizard with the password megamoo, and change it with setpass before anyone else can reach your world.
What's in the download
The engine, the mm.db starter world (157 objects: base prototypes, an out-of-character lobby, a character generator, one in-character room and a wizard account), the verb library, the user guide, and the developer manual.
Requires Python 3.10 or newer. Nothing to install.
Status
Beta. The engine has not yet been load-tested or play-tested at scale — suitable for development and single-developer worlds rather than an open multiplayer deployment.