A Proposed Design for an LLM-Powered Text-Based MMORPG

In this writeup I will specify a few high level components that I think would be crucial for implementing an LLM-powered Text-Based MMORPG. I will identify what I consider to be the key architectural components for building a system that can support an arbitrary number of players interacting within this invented world.

Overview

Let's first consider the architecture at the highest possible level:

An actor (the player) can interact with the Game Interface using chat messages. The game interface will then convert each incoming message into a Create/Read/Update/Destroy message to N entities registered in the Entities database.

To provide some definitions:

  • Actor: Any player interacting with the platform. They are capable of performing actions on behalf of their character exclusively through the Game Interface.
  • Game Interface: A chat interface through which each player can manipulate their character (and by extent the Entities in the game).
  • Entities: A database containing an entry for every Entity present in the game.
  • Entity: Any distinctly identifiable object in the game world. A player's character is an entity. A tool is an entity. An area in which events take place is an entity.

Gameplay Loop

The core gameplay loop consists of an actor performing an action, then the game updating its state in accordance with the actors action.

Diagram of core gameplay loop

In this diagram we can see that a single player message will trigger an update on all nearby entities. Let's explore an example to see how this would work.

Player 1: Pick up the shovel

player_entity: Entity(id=1, name="thorne", state="")

nearby_entities: [Entity(name="shovel"), Entity(name="dirt patch"), Entity(name="treasure", state="buried in the dirt patch)]

Now we will update each entity:

player_effect = message_to_entity_effect("Player 1: pick up the shovel", player_entity, nearby_entities) # passing nearby entities is necessary to verify that a shovel is available
update_entity(player_effect) # where this is the updated state of the player
for entity in nearby_entities:
  accessories = [player_entity] + nearby_entities.pop(entity) # psuedocode for ignoring the current entity
  result = message_to_entity_effect("Player 1: pick up the shovel", entity, accessories)
  update_entity(result)

respond_to_player()

After updates we should see the resulting states:

player_entity: Entity(id=1, name="thorne", state="holding Entity(id=id, name='shovel'", events=['picked up a shovel']))

nearby_entities: [Entity(name="shovel", state="being held by Entity(id=1, name='thorne'), Entity(name="dirt patch"), Entity(name="treasure", state="buried in the dirt patch)]

Game Response: Thorne picks up the shovel nearby

Now lets dig.

Player 1: Dig a hole in the ground

Skipping the updates...

player_entity: Entity(id=1, name="thorne", state="holding Entity(id=id, name='shovel'. Just dug a hold in the ground. A bit tired", events=['dug a hole in the ground', 'picked up a shovel']))

nearby_entities: [Entity(name="shovel", state="being held by Entity(id=1, name='thorne'), Entity(name="dirt patch", state="a hole is dug out of it", events=["Entity(id=1) dug a hole"]), Entity(name="treasure", state="revealed but partially buried still in the dirt patch")]

Game Response: Thorne digs a hole in the ground and reveals the edge of what looks to be a treasure chest!

We can see how the rest might continue.

Comments on the Gameplay Loop

You might be concerned that I am skipping over a lot of implementation details. How is the state updated? How are we going to keep track of the shovel if the player moves away? My answer, LLMs. Each time a player performs an action we just ask the LLM to generate a reasonable new state and description for the event that just transpired. I am skipping over the logic involved with creating/deleting entities though.

Alternative Approaches

It might be non-useful to keep track of history on a per-entity basis. Alternatively we could consider an additional table, Locations, to store this sort of archival information:

An alternative high-level design for the game architecture.

This has the benefit of not needing to do some sort of distance measurement from the player entity each time they perform an action. It can also help appropriately scope the entities that might be affected by a player action. Mentally modify the gameplay loop where applicable.

Conclusion

While the architecture I have proposed is quite simple, I think it is just as powerful. LLMs enable us to hold highly unstructured data, and convert it to meaningful modifications to entity states. In a follow up article I plan to address the actual programming architecture one would need to build out this system.

As always, feel free to contact me at

contact [at] thornewolf [dot] com

Subscribe to Thorne Wolfenbarger - Blog

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe