‘amul’ breathes again

in 1990 it was a 16-bit, AmigaDOS system

I’ve mucked about trying to resurrect AMUL here and there over the years, reworked a bunch of it under the moniker “SMUGL” but once I was done with the particular aspect (sockets, classes, whatever) I never got to the important task of making it work.

After another couple of forays into poking the bear recently I tasked myself with strictly focusing on finding what was making it crash and fail to load.

It turned out that some time in probably 97-98 I’d deleted a line of code necessary for the game to figure out how much data it was loading, oops.

Largely it turned out to be my lack of C++ understanding in ’92. Apparently I learned the lesson but decide not to apply the learnings.

I’ve tried to mostly stick to pretty much C code, but then luxuriated in a few “really does something useful” C++ features to let me focus on the important and fun parts. I’m going to try and keep the struct/class hierarchies flat, with a very limited amount of inheritance to fill in for the absence of interfaces in C++.

It consists of three main components:

Compiler: Which is a bit generous of a name for what it really does, in my mind anyway. It consumes various text files and spits out a set of binary data files.

Engine: AMUL had a server (manager) and copies of the “frame” (a combination client/server) directly manipulated its memory. SMUGL has a single executable which simply forks itself to become a client. Static data is visible to the child and shared memory homes the mutually-modifiable data.

Game code: Someone actually has to describe the game world, commands and behaviors.

The language itself is part data definition and part functional programming, in that you are writing pattern matching sequences followed by lists of steps to execute when those patterns are matched.

verb=help           # How to deal with a player input starting with help
syntax=player=me    # more specific: "help me"
  respond "I'm just the narrator, ask another player?"
syntax=player
  checknear player  # make sure we're near the player you named
  if helping player fail "You're already helping them."
  if helping someone fail "You're already helping @hp"
  help player       # execute the built-in 'help' action
  tell player "@me is now helping you."
  respond "You are now helping @pl."
syntax=player=madhog verb=tank
  fail "I don't think it can be done."
syntax=player verb
  respond "That's not how this works, you tell *me* what *you* want to do."

The current language is overly specialized, but on a modern system I think I can quite comfortably address some of the issues that effectively forced my hand back in the day.

I’ll probably spend the next few days of hacking replacing the “Posix Shared Memory” code with memory-map backed storage so I can run it on Windows, and doing a better job of separating dynamic from static data. Then I’ll whittle down some of the “muscle work” code that I feel comfortable entrusting to the C++ standard library/my own simple classes/templates.

My big plans are for completely replacing the compiler’s parser and parser generators: I’ve fleshed out a design that intentionally falls short of being a complete parser generator but instead meets you half-way with a fairly clean and readable actual-code description of your language as a state machine.

// names starting lower-case are user-written functions that capture
// token(s) and validate them.
error_t newRoom(Token);     // write a function to capture a new room name
error_t addRoomFlag(Token); // write a function to capture/check each flag

auto RoomLine = Optional("room=") + (Identifier()->newRoom) +
                ZeroPlus(FromList(roomFlagList))->roomFlags +
                Peek(EndOfLine());

auto RoomShortDescription = LineOfText()->shortDescription;
auto RoomLongDescription = ZeroPlus(LineOfText())->longDescription;

auto RoomParser = RoomLine() + RoomShortDescription() + RoomLongDescription();

Lastly I want to abandon the current fork-per-client model. I’ve written a small replacement library that emulates a portion of the Amiga’s “Message Ports” message-passing system. By using that and “thread_local” on my excessive number of globals, I think I can quickly get to a working single-process, multi-threaded version (and then get rid of all the globals)

I’m trying to work up to opening up the source repos, but I’m not quite ready to do it.

FreeBSD!

The last functioning binary I have of my Linux port of my MUD language was apparently compiled on FreeBSD some time in the mid-2000s.

So I’m setting up a bunch of FreeBSD Hyper-V machines to try and get a working environment with which to demo it to someone.

And right now I’m all the way back to 4.2, but I have this hunch it was 2.x something, which will mean going back so far that I use CD ISOs.

I’d forgotten how 8-bit retro BSD feels even when it’s shiny new.