← MegaMOO Guide

Coming from LambdaMOO

Bringing an old world across, and the commands you already know.

MegaMOO reimplements the MOO model — numbered objects, single inheritance, properties and verbs living on the objects themselves, edited while the world runs. It does not implement the MOO language. Verbs here are Python.

That is the whole of the incompatibility, and it is worth being blunt about it: a LambdaMOO core will not run on MegaMOO. What this chapter covers is what you can do — bring the world across, keep the code where you can read it, and port it verb by verb.

If you want a MOO that runs MOO code

Use mooR. It is a mature reimplementation in Rust that reads LambdaMOO databases and executes MOO verbs directly, so an existing core keeps working. MegaMOO is the right choice when you would rather write in Python; mooR is the right choice when you have a core you want to keep running.

Importing a database

Importing a whole LambdaMOO database is not part of the engine. It lives in a separate tool, mooport, and that is deliberate: reading a 1994 textdump and deciding what its verbs mean is a different job from running a game, and MegaMOO is meant to stay a lean Python world-building platform rather than become a MOO compatibility layer.

It is also, usually, the wrong thing to want. Measured against stock LambdaCore, seven verbs in ten are redundant before translation starts — the utility objects, the editors, both quota systems, login, the help databases, the option packages. MegaMOO does all of that in engine code, and a world carrying its own copy would be carrying a second implementation of its host. What is worth reclaiming from an old MOO is the world: the rooms, the things, the characters, the behaviour somebody wrote by hand.

mooport is not published yet — it is being written alongside the engine, and the measurements above come from running it against stock LambdaCore and a sixteen-year-old Inferno database. If you have a world you want to bring across, say so: knowing there is somebody waiting is the thing most likely to get it released.

What happens to object numbers

They change, and they have to. A LambdaMOO #10 cannot become a MegaMOO #10 — that number is already the base object here, and a core landing on top of the shipped hierarchy would destroy both.

Every imported object is created fresh, and every object reference stored inside a property is rewritten to match. Each object records where it came from in moo_import_id, so you can always find the thing that used to be #57:

@find 57 isa #1
@ex #412

A reference pointing at something that was not imported — a skipped player, or a number the source database never had — is left visibly as a number and counted in the report. Pointing it at whatever now holds that number would be worse than leaving it obviously broken.

What happens to property names

Most survive untouched. Two kinds cannot:

Both are prefixed rather than dropped: _mail_task becomes moo_mail_task, flags becomes moo_flags. Every rename is listed in the import report, so nothing changes name quietly.

aliases is the one happy exception. MOO means the same thing by it that MegaMOO does, so it maps straight onto the native attribute.

What happens to verbs

This is the part worth understanding properly. Verb code comes across, but nothing imported can run — it is MOO source, and this engine executes Python.

Rather than discard it, the importer keeps every verb's original source exactly as it was, stored as comments beneath a docstring that records where it came from and how to port it. The verb itself is stored hidden, without the execute permission, and at gm3, so no player can reach it by typing its name, and its body ends in return None so that even a contrived call does nothing.

The effect is that your old code is sitting on the right objects, under the right names, waiting to be rewritten — instead of in a tarball you never open. To see what is left:

@grep UNPORTED MOO SOURCE

To port one, read the source in the verb, write the Python in its place, and give the verb rx permissions and an auth level. Delete the docstring when you do — it is what marks the verb as un-ported.

Players are skipped

Player objects are left out unless you pass /players. A player object carries a password hash and a connection history that mean nothing here, and importing one creates an account nobody can log into.

Porting verb code

Most lines fall to a small substitution table. The shape of the code — what is on this, what the verb is called with, how it talks to the player — does not change.

MOOMegaMOO
player:tell("You have ", n)pobj.msg(f"You have {n}")
this:verbname(x)call_verb(this, 'verbname', x)
pass(@args)pass_(*args)
suspend(n)suspend(n) (same meaning: others run, you resume on the next line)
$string_utils:foo(x)su.foo(x)
$object_utils:foo(x)ou.foo(x)
playerpobj
caller_perms()caller
length(x)len(x)
tostr(a, b)f"{a}{b}"
typeof(x) == LISTisinstance(x, list)
{1, 2, 3}[1, 2, 3]
x[1] (1-based)x[0] (0-based)
this.locationthis.location (unchanged)
args, argstr, dobjargs, argstr, dobj (unchanged)
#123#123 (unchanged)
E_PERME_PERM (a value here too)

A few things are worth knowing beyond the table:

Translating a verb in place: @port

The table above is what you do by hand. For a whole verb, @port does the mechanical part for you: paste MOO source, read the Python it produces, and save it only if you agree.

> @port #92.buy Porting MOO code into 'buy' on a merchant (#92). Paste MOO source. '.' alone to finish, '@abort' to cancel. ----- player:tell("That costs ", price, " coins."); return 1; . -- translated -- tell(pobj, f"That costs {price} coins.") return 1 Save this to buy on #92? [y/N]

Nothing is saved without a yes. Anything the translator would not guess at is left in the code as a # PORT: line naming what needs a person, and a verb saved with marks still in it is stored hidden, so a half-ported command cannot be reached by a player typing its name.

The MOO original is kept

What @port saves is the translation with the source commented underneath it. That matters more than it sounds. A translation that runs but means the wrong thing is the expensive failure — it does not raise, it just quietly does something else — and the only way to see it is to read the original beside the Python.

It is also what @port/again reads. The translator improves; when it learns something, a verb that still carries its source can simply be redone rather than dug out of the textdump again:

> @port/again #92.buy Re-translating 'buy' on a merchant (#92) from its kept source (2 lines).

It shows the result and asks, exactly as pasting does — it is the same code path. A verb with no kept source says so and stops, rather than opening an empty editor.

Clean is not the same as correct

"Translated clean" means the translator recognised everything, not that the verb does what it did. Where MOO and MegaMOO differ quietly — this binds differently, verbs() includes inherited names here, verb_info() returns a dict rather than a 1-based list — the output compiles, runs, and is wrong. Marked lines are the ones the translator knows about. The rest is on you, and the way to check is to run the verb.

Commands you already know

These exist because a MOO programmer reaches for them without thinking. They behave as you would expect, with the differences noted.

CommandLevelDoes
@find <text>gm2Find objects anywhere by name. in <room> and isa <parent> narrow it; /exact and /count are available.
@audit [player]gm2Everything a player owns, grouped by location. /rooms, /nowhere for orphans, /count.
@kids <object>gm2What inherits from an object. /all draws the whole descendant tree.
@quota [player]gm2How much of the database someone has built. See the note below.
@grep <text>gm3Search verb code across the database.
@copy <object>gm3Duplicate an object's own properties. Verbs and contents are deliberately not copied.
@ps / @killgm3List running tasks, and stop one.
@dump <object>gm3Write an object out as text, into dumps/.
@load <file>gm3Build a new object from a dump.
@quota reports, it does not enforce

MegaMOO has no quota system. @quota tells you what somebody has actually built — objects, split into rooms, exits and everything else — and shows a limit only if you have set a quota property yourself, clearly marked as advisory. Object creation here is not gated on it.

Moving objects between databases

@dump and @load are the small version of the importer, and the everyday one: they move a single object between two MegaMOO databases — a development world and a live one, say.

@dump/verbs #92
@load 92-BaseMerchant

What travels is what makes the object itself: parent, owner, flags, noun, aliases, properties, and with /verbs its verb code. What does not travel is where it happened to be — location, contents and children are relationships to other objects, and those numbers mean something else in the database you are loading into.

@load always creates a new object. It never overwrites, it refuses a dump whose parent does not exist locally, and it lists any object references pointing at numbers this database does not have. /dry shows you all of that without building anything.