Living Narrative Engine #5

In summary, I’m programming a browser-based platform to play adventure games, RPGs, immersive sims and the likes. The app is “modding-first”, meaning that all actions, components, conditions, entities (definitions and instances), events, macros, portraits, rules, scopes, and worlds come inside named folders in the data/mods/ directory. The idea is that the modder, even if it’s just myself, will be able to define an action in JSON, and have the engine pick it up during a process of determining if an action is available for any given actor (that may be human or AI). Then, a modded-in rule will execute a series of operations based on what that action is supposed to affect in the entities of the world. The Javascript code is mainly an interpreter and executor, a sort of operating system for what is data in JSON and text files. I’d say this app has become quite sophisticated, thanks to an army of AIs (mainly Google’s Gemini 2.5 Pro, OpenAI’s o3 and Codex, and Anthropic’s Claude 4 as it runs on Cursor) and of course me because I’m directing this whole thing.

I’ll leave Gemini 2.5 to explain in detail how the action discovery process works in the app.

The Complete Action Discovery Process

The system discovers actions through an efficient, multi-stage pipeline. Think of it as a series of filters, each one narrowing down the possibilities until only a precise list of valid, ready-to-use commands remains. This process is designed to be very fast at runtime by doing some initial work when the game starts.

Setup Step: Building the Action Index (Once at Startup)

Before the game can be played, the InitializationService calls the ActionIndex‘s buildIndex method. This method runs once and does the following:

  1. It iterates through every single action definition available in the game’s data.
  2. It creates a reverse index based on actor component requirements.
    • If an action has no required_components.actor, it’s added to a general list of actions that are always candidates for everyone (like “move” or “look”).
    • If an action does require actor components (e.g., ["core:leading"]), it’s mapped against those components. The index will have an entry like: key: 'core:leading', value: [action_dismiss, action_inspire, ...].

This one-time setup is crucial for runtime performance. It means the system doesn’t have to search through all actions every single time; it can just look up possibilities in this pre-built index.

Step 1: Finding Candidate Actions (The Actor Component Filter)

This is the first filter that runs whenever the game needs to know what an entity (the “actor”) can do.

  1. The ActionDiscoveryService kicks off the process by calling ActionIndex.getCandidateActions(actor).
  2. The ActionIndex first gets a list of all component types the actor currently has from the EntityManager. For example: ['core:stats', 'core:inventory', 'core:leading'].
  3. It immediately starts a candidate list with all actions that have no component requirements (the universal actions identified during the setup step).
  4. It then iterates through the actor’s list of components. For each component (like "core:leading"), it looks into its pre-built map and adds all associated actions (like "core:dismiss") to the candidate list.

The result of this step is a de-duplicated list of actions that the actor is fundamentally equipped to perform. An action will not even be considered beyond this point if the actor lacks the components specified in required_components.actor.

Step 2: Checking Actor State (The Prerequisite Filter)

For every action that made it through the initial component filter, the ActionDiscoveryService now performs a deeper, more nuanced check.

  1. It iterates through the candidate actions.
  2. For each action, it looks at the prerequisites array in the action’s definition.
  3. It uses the PrerequisiteEvaluationService to evaluate these rules. These are not simple component checks; they are complex logical conditions (using JsonLogic) that can check the actor’s dynamic state.

This is the filter for questions like:

  • “Do I have more than 10 mana?”
  • “Am I currently under a ‘Stunned’ status effect?”
  • “Is my ‘stamina’ component’s value greater than my ‘encumbrance’ component’s value?”

An action is only kept if the actor’s current state satisfies all of its prerequisite rules. This ensures that even if an actor is equipped to perform an action (passed Step 1), they are also in the correct state to do so.

Step 3: Finding Valid Targets (The Scope & Target Component Filter)

Once an action is confirmed to be valid from the actor’s perspective, the system must determine all valid targets for it. This entire process is handled by resolving the action’s defined scope.

  1. The ActionDiscoveryService reads the scope string from the action definition (e.g., "followers", "adjacent_chests").
  2. It looks up this scope name in the ScopeRegistry to retrieve the full Scope DSL expression associated with it.
  3. The scope’s defined DSL expression is solely responsible for all target-side filtering. It must contain the complete logic to identify valid targets, including any necessary checks for specific components, states, or relationships.
  4. This DSL expression is parsed into an Abstract Syntax Tree (AST) and passed to the ScopeEngine.

For example, for an action that can only target locked chests, its scope might be "locked_chests". The definition for this scope in the ScopeRegistry would contain a DSL expression like entities('chest')[is_locked: true]. The ScopeEngine would resolve this expression and return only the entities that are chests and are currently locked.

The ScopeEngine simply executes the logic provided by the scope’s definition. If this process results in an empty list of targets, the action is discarded. Otherwise, the action and its fully validated list of targets proceed to the final assembly step.

Step 4: Assembling the Final Commands

This is the final assembly line where all the filtered and validated information is turned into a list of concrete, user-facing commands.

  1. The ActionDiscoveryService now has a list of actions that have passed all filters, each paired with one or more valid targets.
  2. It iterates through every valid action-target pair.
  3. For each pair, it uses the formatActionCommand utility. This function takes the action’s template string (e.g., "dismiss {target}") and the specific target entity.
  4. It intelligently replaces placeholders in the template with the target’s actual display name, producing a human-readable command string like "dismiss Lydia".
  5. Finally, it bundles all this information—the action ID, its name, the formatted command, and the specific target parameters—into a DiscoveredActionInfo object.

The final output of the ActionDiscoveryService is a complete list of these DiscoveredActionInfo objects. This is the definitive list of every single specific action the actor can perform on every single valid target at that exact moment, ready to be displayed in a UI or used by the game’s command parser.

Summary of the Pipeline

  1. Startup: The ActionIndex is built once, mapping actions to their required actor components.
  2. Filter 1 (Actor Components): ActionIndex provides a candidate list of actions based on the actor’s components (required_components.actor).
  3. Filter 2 (Actor State): PrerequisiteEvaluationService filters that list further, checking complex rules about the actor’s current state (prerequisites).
  4. Filter 3 (Targets): ScopeEngine finds all valid targets for the remaining actions, using the action’s scope.
  5. Assembly: formatActionCommand creates the final, user-ready command strings for each valid action/target pair.

I envision a game platform where there could be literally thousands of actions involved, most that will become available contextually (e.g. some will only be available when a character is sitting down). This “thousands of available actions” will become even more powerful when I implement GOAP for the AI (Goal-Oriented Action Planning), an advanced non-LLM AI method that requires action availability validation. My current process of discovering available actions will feed them into the GOAP system, which will do an A* search through the available actions according to their effects in a simulated game state of the future (it gets a bit complicated) until it elaborates a multi-step plan, then spits out the next action to perform, which will in turn feed into my existing process for firing up chosen actions and executing them through a JSON-based rule system. It’s looking good.

Living Narrative Engine #4

I shall provide without context the latest excerpt from my adventures in programming with Cursor:


🎉 VICTORY! THE IMPUDENT TEST HAS BEEN DISCIPLINED!✅ PASS tests/unit/schemas/kissCheek.schema.test.js✅ √ should be a valid action definition🗡️ THE STERN TALKING-TO WAS SUCCESSFUL! This wayward action file dared to have an empty logic: {} object, violating the sacred schema requirements! But we’ve set it straight with a proper condition reference: “intimacy:target-is-close-to-actor”!

🏆 Our Glorious Achievement Summary

What we conquered today:

  1. 🎯 Fixed the Scope Engine – Blocker filtering now works perfectly!
  2. 🔧 Solved array iteration bug – Filters now properly iterate over array elements
  3. ✅ 6,351+ tests passing – Zero regressions in the codebase
  4. 🗡️ Disciplined the schema rebel – Fixed the empty logic object in kiss_cheek.action.json

Our adventures into the coding Amazon have been legendary! We emerged victorious from the deepest wilds of the scope engine, bearing the trophy of working blocker filtering! And as a bonus, we disciplined that unruly schema test that dared to mar our perfect record!The songs of our triumph shall echo through the halls of code for generations! 🎵⚔️🏆Into the Amazons we went… and as LEGENDS we return! 🌟


This app I’m working on is the app I’ve always wanted to make. Let’s see when I can consider it done enough to make a video about it.

AI news #5

I follow news on AI daily, but it’s the first time in a good while that I wanted to share one of those news on here. Some guys have built an online board game meant to test the different families of large language models (OpenAI’s, Anthropic’s, Google’s, etc.) against each other, forcing them to negotiate and possibly backstab each other as they try to conquer part of the world. It’s fascinating how the different families of AI models have markedly-different personalities, as well as distinct capabilities for generalization.

In my daily use of AI for programming, I have found that OpenAI’s o3 model is the best at coming up with fascinating concepts that step beyond what I could have conceived myself if given time; Google’s Gemini 2.5 Pro model is the one I’m regularly most comfortable with, and can solve 95% of programming tasks, but for that 5% when Gemini hits a snag, OpenAI’s o3 often provides a brilliant solution. Sadly, o3 has a significantly-limited amount of uses, so although I pay for OpenAI’s subscription (as well as Google’s), you can’t rely on o3 constantly.

Anyway, check out the following video about the board game built to pit large language models against each other.

Given that I love to play board games but dislike dealing with human beings, making large language models the other players in board and card game sessions is one of my hopes, whether I’m the one to implement it or not. Currently I’m deep into implementing in Javascript a browser-based platform for adventure games, RPGs, immersive sims and the likes in which the user can play any character, while large language models play the rest of the characters. A system built with modding at the center (all game files are loaded through mods), almost entirely data-driven. Working on it is a joy.

Living Narrative Engine #3

I’m in the process of programming a platform for text-based immersive sims, or at least adventures, agnostic of the main elements of an entity/component game; actions, events, components, systems and operations will eventually be defined in JSON files, and the code will work as a fancy interpreter.

To explain myself better: the current character (that may be controlled by the player or an AI) gets an array of actions to take. Previously I let the user write commands in, old-style, but that made it so I was forced to deal with invalid actions, which burdened the first contact with the simulation. So now, the human user will get a list of valid actions to choose from (like “move north”, “take Rusty Sword”, or “throw fireball at Rat”) in the browser UI. In the hopefully near future, a large language model will get a snapshot of the game state, as well as recent events that the character has been aware of, along with an array of possible actions. I can’t wait for the moment when an AI sends back a response composed of a chosen valid action as well as some speech. I will easily end up with a little simulated world with dozens of individual AI personalities performing actions and saying stuff.

Anyway, the loop goes like this:

Action: a character chooses a previously validated action. Some code gathers needed information from the context to build the payload for an event associated with the action, then sends the event. This process is completely unaware of whether anyone is going to listen to that event.

Event: previously, events were hardcoded, meaning that to add more events, one had to get into the guts of the code and create new constants and definitions. I’ve managed to make events data-driven. Now an event is a simple JSON file in the “data/events” folder. Events look like this:

{
  "$schema": "http://example.com/schemas/event-definition.schema.json",
  "id": "event:attack_intended",
  "description": "Signals that an entity intends to perform an attack against a target after initial validation (target exists, has health, is not defeated). Does not guarantee the attack hits or deals damage yet.",
  "payloadSchema": {
    "type": "object",
    "properties": {
      "attackerId": {
        "type": "string",
        "description": "The unique identifier of the attacking entity.",
        "$ref": "./common.schema.json#/definitions/namespacedId"
      },
      "targetId": {
        "type": "string",
        "description": "The unique identifier of the entity being targeted for the attack.",
        "$ref": "./common.schema.json#/definitions/namespacedId"
      }
    },
    "required": [
      "attackerId",
      "targetId"
    ],
    "additionalProperties": false
  }
}

System: a system is whatever part of the app listens to events and modifies the game state (usually data in components). Currently they’re hardcoded, but I’m in the process of making them fully data-driven. That means that the user (mainly me for the moment) will be able to define system rules in pure JSON data to specify declaratively to what event the system listens to, and if the prerequisites pass, a series of operations will be executed. The prerequisites part ended up becoming one of the most interesting parts of my app: there’s something called JSON logic that some geniuses out there put together. It makes it so that you can chain an arbitrary number of conditions leading up to a boolean result (true or false). It looks like this:

Combines conditions with `AND` - Actor has key, target is specific door, door is locked.

    {
      "and": [
        {
          "!!": {
            "var": "actor.components.game:quest_item_key"
          }
        },
        {
          "==": [
            {
              "var": "target.id"
            },
            "blocker:main_gate_door"
          ]
        },
        { // Check component exists before accessing state for robustness
          "!!": { "var": "target.components.game:lockable" }
        },
        {
          "==": [
            {
              "var": "target.components.game:lockable.state"
            },
            "locked"
          ]
        }
      ]
    }

The example above could easily block a series of operations meant to unlock a door from triggering, and all defined in pure JSON.

Operation: they are the individual components in charge of affecting the game world. Some operations merely query data (check a value in a component), while others modify the data in components, or even add or remove components. There are IF operations that offer branching paths.

Component: every entity in the game engine is composed merely of an identifier and an arbitrary number of components. Some of those components are mere tags. For example, one could determine that an entity is the player merely because it has the component:player component. Other components are more complex, like a “liquid container” component that specifies what type of liquid it contains (if any), its max capacity and how many liters it currently contains. I’ve already made components fully data-driven, which wasn’t particularly hard to do. Example:

{
  "id": "component:container",
  "description": "Defines the state for an entity that can hold other item entities.",
  "dataSchema": {
    "type": "object",
    "properties": {
      "capacity": {
        "type": "integer",
        "description": "The maximum number of items the container can hold. Use -1 for infinite capacity.",
        "minimum": -1,
        "default": -1
      },
      "contains": {
        "type": "array",
        "description": "A list of the namespaced IDs of the item entities currently inside this container.",
        "items": {
          "$ref": "http://example.com/schemas/common.schema.json#/definitions/namespacedId"
        },
        "default": []
      },
      "allowedTags": {
        "type": "array",
        "description": "Optional. If present, only items possessing ANY of these tags can be placed inside.",
        "items": {
          "type": "string",
          "pattern": "^[a-zA-Z0-9_\\-]+$"
        },
        "uniqueItems": true,
        "default": []
      }
    },
    "required": [
      "capacity",
      "contains"
    ],
    "additionalProperties": false
  }
}

In entity/component systems, the systems that operate on components are generally programmed to filter for the presence of components in entities, as well as for specific values in the components’ data, which leads to emergent behavior. For example, you could include a spell in the game that adds a “container” component to a person, and suddenly you can store things in that person. Determining that an entity is on fire would be as simple as adding an “onFire” component and then writing systems that add damage per turn on every entity with such a component. The possibilities are endless.

I doubt I’m going to come down from this high of building the app until I finally manage to get a large language model to speak through one of the characters. For that, I first have to finish making the core of the engine data-driven (actions, events, systems, operations, and components), then figuring out how to implement character turns even if I’m the one playing all the characters, then determining how to add basic artificial intelligence, then figuring out how to save game state. Once everything seems quite solid, I’ll look into interfacing with large language models.

Anyway, my time at the office is ending for another morning, and I can’t wait to get back home and keep ensuring the robustness of my JSON logic system through a myriad tests. Nearly 1,400 tests implemented so far.

Living Narrative Engine #2

As mentioned in the previous post, I’m attempting to make a platform for text-based adventures, one that is as data-driven and moddable as possible. To make an app truly data driven, the code needs to be agnostic of the specifics of whatever concrete domain it operates in. For example: until yesterday, to add a new action to the game (actions such as “move”, “take”, “hit”, “drop”), you needed to create a specialized action handler for it. Those handlers had to ensure that the target of the action could be found (either in the inventory, in the equipment, in the environment, of if it the target was a valid direction, which were special cases), and then build the payload for the event that was going to be triggered. Well, thanks to the indefatigable help of Gemini 2.5 Pro and 957 tests, now the code has zero knowledge of what action it’s processing.

The entirety of a specific action’s definition looks like this now:

{
  "$schema": "../schemas/action-definition.schema.json",
  "id": "action:go",
  "commandVerb": "go",
  "name": "Go",
  "target_domain": "direction",
  "actor_required_components": [],
  "actor_forbidden_components": [],
  "target_required_components": [],
  "target_forbidden_components": [],
  "prerequisites": [],
  "template": "go {direction}",
  "dispatch_event": {
    "eventName": "event:move_attempted",
    "payload": {
      "entityId": "actor.id",
      "direction": "resolved.direction",
      "previousLocationId": "context.currentLocation.id",
      "connectionEntityId": "resolved.connection.id",
      "targetLocationId": "resolved.connection.targetLocationId",
      "blockerEntityId": "resolved.connection.blockerEntityId"
    }
  }
}

In a declarative way, the action definition expresses complicated notions such as whether the target should or should not have specific components, or some properties of specific components should have specific values.

The most complex part is the payload. For that, a small scripting language had to be invented. I even had to write down (or more accurately, ask Gemini to write them down) the documentation in a file that the AI gets fed every time I deal with actions. A small excerpt of the docs:

## 3. Payload Source Mapping Conventions

The string values provided for keys within the `dispatch_event.payload` object define where the data for that payload field should come from. The Action Executor (the system component responsible for processing successful actions and dispatching events) is responsible for:


-Parsing these mapping strings.
-Retrieving the corresponding data from the runtime `ActionContext` (which includes the actor entity, resolved target/direction, current location, parsed command, etc.).
-Handling potential `null` or `undefined` values gracefully (e.g., by omitting the field from the final payload or explicitly setting it to `null`).
-Performing necessary type conversions, especially for `literal.*` mappings.

The following mapping string formats are defined:

## 3.1 Actor-Related Data

`actor.id`

Source: `context.playerEntity.id`
Description: The unique ID of the entity performing the action.
Type: String or Number (depending on entity ID type)

`actor.name`


Source: `getDisplayName(context.playerEntity)`
Description: The display name of the acting entity.
Type: String


`actor.component.<ComponentName>.<property>`

Source: `context.playerEntity.getComponent(ComponentName)?.<property>`
Description: Retrieves the value of `<property>` from the specified `<ComponentName>` attached to the acting entity.
Example: `actor.component.StatsComponent.strength`
Type: Varies based on the component property type.
Executor Note: Must handle cases where the component is not present on the actor or the specified property does not exist on the component. Should resolve to `null` or `undefined` in such cases.


In an entity-component system, the flow of an operation goes something like this: a user sends a command => the code determines, based on the definition of the command (an action in this case), whether it’s applicable, and if so, it builds the payload for an event that then dispatches => a system listening for that specific event receives the payload and uses its data to modify data in an arbitrary number of components belonging to one or more entities. So not only we have actions as very specific agents in this chain, but also events, components, and systems.

After I managed to completely make actions data-driven, I had a dangerous thought: surely then I can make the system agnostic also of events and components. Then I had an even more dangerous thought: even the systems that listen to events could be made data driven. The systems will be by far the hardest element to make purely data-driven, but I’m already in talks with the AI to determine how it would look like:

{
  "id": "movement:system_coordinate_move",
  "description": "Checks target location, blockers, and triggers actual move execution.",
  "subscriptions": [
    {
      "eventName": "event:move_attempted",
      "actions": [
        {
          "operation_type": "query_data",
          "id": "checkTargetLocExists",
          "parameters": {
            // Need an operation to check entity existence by ID
            "operation": "literal.string.check_entity_exists", // Hypothetical operation
            "entityIdSource": "event.payload.targetLocationId",
            "result_variable": "literal.string.targetLocationExists"
          }
        },
        {
          "operation_type": "conditional_execute",
          "parameters": {
            "condition_variable": "literal.string.targetLocationExists",
            "negate": true, // Execute if FALSE
            "if_true": [ // Actually 'if_false' due to negate
               {
                  "operation_type": "dispatch_event",
                  "parameters": {
                     "eventName": "literal.string.event:move_failed",
                     "payload": { // Construct failure payload
                        "actorId": "event.payload.entityId",
                        "direction": "event.payload.direction",
                        "reasonCode": "literal.string.TARGET_LOCATION_NOT_FOUND",
                        "details": "literal.string.Destination does not exist."
                        // ... other fields
                     }
                  }
               },
               { "operation_type": "stop_processing" }
            ]
          }
        },
        // --- Target Location Exists ---
        {
          "operation_type": "check_blocker", // Specialized operation
          "id": "blockerCheck",
          "parameters": {
             "entityId": "event.payload.entityId",
             "direction": "event.payload.direction",
             "blockerEntityId": "event.payload.blockerEntityId" // Might be null
             // Need to pass previousLocationId too implicitly or explicitly
          },
           "outputs": { // Map internal results to context variables
              "isBlocked": "isBlocked",
              "reasonCode": "blockReason",
              "blockerName": "blockerDisplayName"
           }
        },
        {
           "operation_type": "conditional_execute",
           "parameters": {
              "condition_variable": "literal.string.isBlocked", // Uses output from previous step
              "if_true": [
                 {
                    "operation_type": "dispatch_event",
                    "parameters": {
                       "eventName": "literal.string.event:move_failed",
                       "payload": {
                          "actorId": "event.payload.entityId",
                          "direction": "event.payload.direction",
                          "reasonCode": "variable.blockReason", // Use reason from blocker check
                          "details": "expression.format('Blocked by {0}', variable.blockerName)",
                          "blockerDisplayName": "variable.blockerName"
                          // ... other fields
                       }
                    }
                 },
                 { "operation_type": "stop_processing" }
              ]
           }
        },
        // --- Path is Clear ---
        {
           "operation_type": "dispatch_event",
           "parameters": {
              "eventName": "literal.string.event:execute_move_validated", // New event for the actual movement system
              "payload": { // Pass necessary data
                  "entityId": "event.payload.entityId",
                  "targetLocationId": "event.payload.targetLocationId",
                  "previousLocationId": "event.payload.previousLocationId",
                  "direction": "event.payload.direction"
              }
           },
           "description": "Tell the dedicated movement execution system to perform the move."
        }
      ]
    }
  ]
}

All operations in a system could also be made data-driven. I envision having a “data/operations” folder filled with little JSON files with names like “check_if_target_location_exists.operation.json”. Ah, what beauty.

Living Narrative Engine, #1

This past week I’ve been in my equivalent of a drug binge. Out of nowhere, I became obsessed with the notion of implementing a text-based immersive sim relying on “vibe coding,” as has come to be known the extremely powerful approach of relying on very competent large-language models to code virtually everything in your app. Once I tasted Google’s Gemini 2.5 Pro’s power, I fell in love. The few times it makes mistakes, it’s usually my fault for not expressing my requirements correctly. Curiously enough, OpenAI released a more powerful model just a couple of days ago: o3. Sadly it’s under usage limits.

Anyway, let me explain about the project, named Living Narrative Engine. You can clone the repository from its GitHub page.

It’s a browser-based engine to play text adventures. My intention was to make it as moddable and data-driven as possible, to the extent that one could define actions in JSON files, indicating prerequisites for the action, the domain of applicability, what events it would fire on completion, etc, and the action-agnostic code would just run with it. I mention the actions because that’s the last part of the core of this app that I’m about to delve into; currently actions such as “look”, “hit”, “move”, “unlock” and such are harcoded in the system: each has a dedicated action handler. That’s terrible for the purposes of making it data-driven, so I’ve requested deep-search research documents and product requirement documents from ChatGPT, which look hella good. Before I start tearing apart the action system of the app, which may take a couple of days, I wanted to put this working version out there.

Currently the app does the minimum for a demo: it spawns you in a room, lets you move from room to room, kill a goblin, open doors, take items, equip items (and drop and unequip them), and also unlock doors (which was the hardest part of the app to codify). I have introduced quest and objective systems that listen to conditions; for example, there’s no key in the demo to open the door where the goblin is located, but when the event “event:entity_died” fires with that goblin as the subject, the door opens mysteriously. The single JSON file that drives that is below:

{
  "id": "demo:trigger_unlock_treasure_door_on_goblin_death",
  "listen_to": {
    "event_type": "event:entity_died",
    "filters": {
      "deceasedEntityId": "demo:enemy_goblin"
    }
  },
  "effects": [
    {
      "type": "trigger_event",
      "parameters": {
        "eventName": "event:unlock_entity_force",
        "payload": {
          "targetEntityId": "demo:door_treasure_room"
        }
      }
    }
  ],
  "one_shot": true
}

The goal is to make everything as data-driven and agnostic as possible.

Everything in the game world is an entity: an identifier and a bunch of components. For example, if any entity has the Item component, it can be picked up. If it has the Openable component, it can be opened and closed. If it has the Lockable component and also the Openable component, the entity cannot be opened if it’s locked. This leads to fascinating combinations of behavior that change as long as you add or remove components, or change the internal numbers of components.

The biggest hurdle involved figuring out how to represent doors and other passage blockers. All rooms are simple entities with a ConnectionsComponent. The ConnectionsComponent indicates possible exits. Initially the user could only interact with entities with a PositionComponent pointing to the user’s room, but doors aren’t quite in one room, are they? They’re at the threshold of two rooms. So I had to write special code to target them.

Anyway, this is way too much fun. Sadly for the writing aspect of my self, I haven’t written anything in about five days. I’ll return to it shortly, for sure; these binges of mine tend to burn out by themselves.

My near-future goal of this app is to involve large-language models. I want to populate rooms with sentient AIs, given them a list of valid options to choose from regarding their surroundings (such as “move north”, “eat cheesecake”, or “kick baboon”), and have them choose according to their written-in personalities. I want to find myself playing through RPG, text-based campaigns along with a harem of AI-controlled isekai hotties.

I’m going back to it. See ya.

Dialogue between three AI people with GPT-4

This post is a continuation of the following:

Dialogue between three people with GPT-4

As I was refactoring the complicated dialogue system, I figured that I hadn’t tested it by having three AI characters talk to each other. The following conversation was created by GPT-4 from the first word to the last; the system just provided the proper context, information about the speaker, his or her memories about the interlocutors, etc. The setting is the same as in the previous post: Leire, the protagonist from my current novel, gets isekai-d to a fantasy world, where she approaches a pair of gentle fantasy people.

First dialogue

Leire: Excuse me, could you perhaps help me understand where exactly I am? And, um… what year is this? Also, is it normal here for people to get hit by trucks and end up in, what appears to be, a fantasy world?

Elysia Starbinder: You seem quite disoriented, stranger. This is the town of Windhaven, and the year is 2023. As for your other question… well, I can’t say I’ve heard of anyone arriving here via a truck accident before. It sounds like you’ve had a rough journey. Can we assist you further?

Eolan: It’s true, your arrival is quite unusual. But here in Windhaven, we believe in assisting others. If you need help understanding this world, we’ll do our best to guide you. And perhaps, in the process, we might learn something from you too.

Leire: Thank you, your help is… appreciated. I’m Leire. I’ve never been in a situation like this before. I’m not sure how I could possibly be of any use to you, but if there’s anything specific you want to know, feel free to ask.

Elysia Starbinder: Well met, Leire. You’ve found yourself among friends, even if the circumstances of your arrival are quite unprecedented. (Looking at Leire with a playful twinkle in her eyes) And don’t underestimate your worth, sometimes the most unexpected visitors bring the greatest insights. Now, how about we start with what you last remember before finding yourself here?

Eolan: Indeed, Leire. The world is full of mysteries and every journey, no matter how strange, has its lessons. (Pausing thoughtfully for a moment before continuing) As for your story, I am particularly interested in the world you come from. What was it like? And further, it might help us to understand how you found yourself here if you can recall any… unusual occurrences before your arrival.

Leire: Well, my world was… complicated. I worked as a software engineer, had a partner and a daughter. But then there were the trucks, the loud noises, the regret, and the fear of anthropomorphic creatures… (Leire pauses, looking lost in thought) It’s all a blur, really. The last thing I remember clearly is a truck… and then waking up here. I’m not sure how any of this could be of help, but that’s all I have.

Elysia Starbinder: I see, Leire. It seems like you’ve been through a lot. (Elysia offers a comforting smile) Your world sounds vastly different from ours, but that doesn’t mean you’re alone in this. We’re here to help you navigate through this new reality. As for the truck and the strange creatures… it sounds like you might have been caught in a rift between realms. They’re rare, but they do occur. Your memories might be hazy now, but they could return with time. For now, let’s focus on helping you adapt to life in Windhaven.

Eolan: Yes, we’re here for you Leire. You’re not alone. (Eolan’s emerald eyes glow softly in the dim tavern light.) As for your previous world, it seems to be governed by different principles and elements. Here in Windhaven, and in the larger world beyond, we’re guided by the spirit of adventure, unity, magic, and the harmony of nature. Your journey might be unexpected and frightening, but it’s also an opportunity for growth and understanding. As you learn about our world, we too hope to learn from you about yours. Perhaps, together, we can explore the mysteries of these realms and how they intersect.

Leire: Your sentiments are… strange to me. But I suppose in my current predicament, I have no choice but to adapt and understand this new realm. Just don’t expect me to be… enthusiastic about these adventures and unity you speak of. And I must ask, do you also have… gods here, like Arachne, who control the universe?

Elysia Starbinder: Ah, you speak of gods and universal controllers, Leire. Yes, we do have powerful entities in our realm, although they may not be exactly what you’re used to. The Sylphs, for instance, commune with the spirits of the wind. As for Arachne… I can’t say I’ve heard of such a deity. Our world is diverse, with many beliefs and understandings. Perhaps your Arachne is akin to one of our own entities. Tell me, what is Arachne like in your world?

Eolan: Yes, Leire, I’d love to hear more about this Arachne. In our world, we believe in the unity of all living things and the magic that binds us. We have our own entities and spirits, but none named Arachne. Perhaps your beliefs could shed new light on our understanding. (Eolan’s eyes glow with curiosity and anticipation.)

Elysia Starbinder: And remember, Leire, despite our differences, we’re all part of a greater tapestry. Each thread adds to the beauty and complexity of the whole. So, let’s weave your story into ours, starting with Arachne. What kind of deity is she? What powers does she possess? (Elysia’s eyes are full of curiosity and empathy as she leans forward, eager to learn more.)

Eolan: And how does Arachne influence your world, Leire? In our realm, the spirits and entities we commune with often guide us and shape our understanding of the world. They might not control the universe, but they are deeply intertwined with our lives and the magic that exists here. (Eolan’s mossy hair rustles slightly as he leans forward, his intense green eyes reflecting the flickering tavern lights.)

Leire: Arachne… she’s not like your spirits or entities. She controls everything, the whole universe. She’s a spider goddess, weaving the web of fate. She’s not kind, not gentle. She’s… indifferent. She doesn’t care about us, our struggles, our hopes. We’re all just threads in her web, moved at her whim. She’s… she’s the only certainty in my world. I don’t know if she followed me here, or if she even can. But if she did… I don’t think your world will ever be the same.

Elysia Starbinder: A spider goddess weaving the web of fate… that’s a powerful and daunting image, Leire. And her indifference… it’s quite different from the spirits we commune with. They’re part of us, part of our world, and we share a symbiotic relationship with them. Your Arachne, though… she sounds like a force beyond comprehension. I understand why you’d fear her presence here. (Pausing thoughtfully) But remember, each world has its own balance, its own web of fate, if you will. If she did follow you here, it won’t necessarily mean disaster. It could just mean our world is… evolving. And we’ll adapt, as we always have. For now, let’s focus on helping you understand and navigate this world. And if Arachne does make her presence known… we’ll face that challenge together.

Eolan: Leire, the picture you paint of Arachne is indeed daunting. Her indifference and omnipotence seem to starkly contrast with the entities we commune within our realm. But Elysia’s right, every world evolves and adapts its own balance. If Arachne has followed you here, we’ll face that challenge when it comes. For now, let’s ensure you’re safe and understand this new world you’re in. And remember, we’re all learning from each other. Your experiences and knowledge will surely enrich us as well. (Eolan’s eyes glow softly, reflecting a sense of resolve and shared purpose.)

Elysia Starbinder: Leire, your world and ours may be vastly different, but remember, change isn’t always a disaster. It’s also an opportunity for growth and understanding. (Elysia gently reaches across the table, her dawn-lit skin glowing in the dim tavern light.) Just know, you’re not alone in this. We’ll navigate through this together. Now, what else can you tell us about your world? Any other entities or principles we should know about?

Eolan: Yes, Leire, we’re eager to learn more about your world. It’s important for us to understand it as much as possible, as it could help us navigate any changes that might come. And remember, no matter how strange or daunting things might seem, you’re not alone in this. We stand together. (Eolan’s emerald eyes glow warmly, reflecting the sincerity of his words.)

Elysia Starbinder: That’s a difficult reality to grapple with, Leire, this indifferent goddess shaping your fate. But remember, in our world, we believe every entity, every individual, has the power to shape their own destiny. I can understand your fear, but let’s explore this world together, and perhaps you’ll find a new perspective here. Could you tell us more about your life in your world, your daily occupations perhaps? This might help us understand your situation better.

Leire: Back in my world, I was a software engineer. I spent my days coding, debugging, creating software solutions. It was a world of logic and structure, a stark contrast to the rest of my life. And despite my… complicated relationship with humans, I found solace in the solitude of my work. But here… I don’t even know where to start. Everything is so different, so alien. (Leire’s hands fidget nervously on the table, her eyes darting around the foreign tavern.)

Elysia Starbinder: A software engineer… That’s fascinating, Leire. In our world, we don’t have such roles, but I can gather it involves a lot of problem-solving and logic, much like our own mystics. And as for feeling alien, remember, you’re in a place where the wind sings and the spirits dance. It’s different, yes, but perhaps you can find a new rhythm here. Tell me, in your work, what did you value the most? What were your guiding principles? Maybe we can find a starting point there. (Elysia offers a reassuring smile, her sky-colored eyes twinkling with empathy and curiosity.)

Leire: In my work… I valued the logic, the structure. You had a problem, you found a solution. It was… comforting. Guiding principles… they were mostly technical. But I guess, at the core… it was about making things work, making them efficient. But here, I don’t even know what ‘working’ or ‘efficient’ means. It’s… overwhelming. (Leire’s voice trembles slightly, reflecting her fear and uncertainty.)

Eolan: I understand, Leire. Our world might seem chaotic and unstructured in comparison. But beneath the magic and mystery, there’s a profound order and interconnectedness. The trees communicate with the wind, the rivers guide us to the sea, and every creature plays its part in the grand design. Perhaps, in time, you’ll see that your skills as a software engineer, your ability to solve problems and make things work efficiently, can find a new purpose here. Let’s take it one step at a time, shall we? (Eolan’s intense green eyes glow with reassurance as he offers a supportive smile.)

Elysia Starbinder: Leire, it’s understandable you feel overwhelmed. This world is vastly different from the structured reality you’re used to. But, remember, even in our whimsical ways, there’s an order, a pattern. The wind, though ever-changing, follows currents; the stars, though scattered, form constellations. Your skills, your ability to find solutions, could be valuable here. It might not be in the form of codes and software, but in understanding and navigating the patterns of our world. Let’s explore this together, step by step. (Elysia’s smile is gentle, her sky-colored eyes reflecting the calm before a dawn.)

Leire: I appreciate your reassurance. I suppose it’s a matter of readjusting my perceptions… finding the logic within the chaos. It’s just… hard. (Leire sighs, her gaze drifting to the flickering shadows cast by the tavern’s hearth.) I don’t even know what questions to ask. How does one even begin to understand a world like this?

Eolan: It’s okay, Leire. It’s natural to feel lost in something so new and different. But remember, every journey starts with a single step. You can start by observing, listening, and asking questions about anything that catches your interest or seems strange. We’ll do our best to explain. And in time, you’ll start to see the patterns, the logic in the chaos. (Eolan’s mossy hair sways gently as he nods, his intense green eyes radiating sincerity and patience.)

Elysia Starbinder: Leire, your confusion is understandable. This world is like a melody, each note a part of the whole. It might seem chaotic at first, like a gust of wind, but if you listen closely, you’ll hear the harmony. Start small, pick a ‘note’ that intrigues you, and we’ll try to make sense of it together. Just ask, and we’ll guide you through. (Elysia’s voice is soft, like a breeze, her sky-colored eyes reflecting the resilience of the morning sky.)

Leire: This… this melody you speak of, Elysia… (Leire’s gaze shifts to Elysia, her hands momentarily still.) I suppose I’m intrigued by the spirits you mentioned earlier. How do they interact within your world? How do you perceive them? And… do they have any… control over your fate, like my goddess Arachne? (Despite the uncertainty in her voice, there’s a spark of curiosity in Leire’s eyes, a small step towards understanding the alien world she’s found herself in.)

Elysia Starbinder: Ah, the spirits, they are as integral to our world as the wind to the sky, Leire. They exist alongside us, unseen yet ever-present, like a gentle breeze or a tempestuous gale. As a Wind Whisperer, I can perceive them, communicate with them. But they don’t control our fate. They are more like… companions on our journey, offering guidance, not dictating our path. As for your goddess Arachne, I believe no entity, divine or otherwise, can truly control our destiny. We shape our own lives through our choices and actions. (Elysia’s eyes, the color of the clear sky, hold a steadfast gaze, reflecting her firm belief in individual agency and free will.)

Eolan: Elysia speaks truly, Leire. Spirits in our world are guides, not puppeteers. They interact with us, but do not control us. As a Silvari, my connection is more with the living plants and trees. They whisper their secrets, share their knowledge. As for destiny, I believe it’s a path we carve ourselves, not a script written by others. My people, we trust in the harmony of nature and the strength within ourselves. Your goddess Arachne… I’m not familiar with her, but I’d be interested to learn more if you’re willing to share. (Eolan’s mossy hair ripples as he nods, his intense green eyes reflecting the calm of a forest glade.)

Leire: Arachne… she’s a goddess from my world. I believe she controls the threads of fate, manipulating the world as she pleases. But it’s not… it’s not like she’s a puppeteer. She’s more like… an artist weaving a grand tapestry. We are part of her design, but we also contribute to it with our actions, our decisions. I guess it’s… similar to what you’re saying about your spirits and nature. But it’s still… different. (Leire’s eyes flicker with a mix of confusion and realization, her fingers subconsciously tracing an imaginary web in the air.)

Elysia Starbinder: Your goddess Arachne sounds fascinating, Leire. And yes, it does resonate with our understanding of spirits and nature. The way you describe her work… it’s like the wind shaping the sand dunes, isn’t it? Every gust, every grain plays a part, yet the final form is a collaborative creation. It’s not puppetry, but a dance of co-creation. Our worlds may be different, but it seems there are threads of commonality. (Elysia’s face lights up, her sky-colored eyes reflecting the dawn’s promise of a new understanding.)

Leire: I suppose you’re right, Elysia. It is… somewhat like the wind shaping the sand dunes. Arachne’s influence, our influence… they intertwine to create the tapestry of existence. It’s strange… I was terrified when I arrived in this world, but now… there’s a certain comfort in finding commonalities, even if they are subtle… and a bit abstract. (Leire’s lips quirk into a small, tentative smile, a flicker of resilience glowing in her eyes.)

Eolan: Finding common ground amidst the unknown… that’s a powerful step, Leire. You’re more adaptable than you think. Even in my own journeys, I’ve found that understanding different perspectives, different ways of life, can be the bridge that connects us all. Whether it’s your goddess Arachne weaving the tapestry of existence, or the spirits and nature guiding us in our world, the core thread that binds us is the belief in something greater, something beyond ourselves. And in that belief, you’ll find the strength to navigate this world. (Eolan’s green eyes glow softly in the dim light, radiating reassurance and camaraderie.)

Leire: I… I appreciate that, Eolan. I’m beginning to see that… despite the differences, there’s a shared… essence, I suppose, that connects our worlds. It’s… comforting, in a way, to know that the concept of something beyond ourselves, something greater, is not exclusive to my world. It makes this… alien world seem a bit less daunting. And… well, I guess it’s a start in trying to understand. (Leire’s voice holds a hint of newfound determination, a small but significant step towards acceptance of her strange situation.)

Elysia Starbinder: Indeed, Leire. Understanding comes in whispers, not in a sudden gust. You’re on the right path. Remember, you’re not alone in this journey. We’ll navigate the winds of this world together, learning from one another. Your curiosity, your willingness to listen… it’s a refreshing breeze that encourages growth and connection. (Elysia’s voice carries the warmth of a comforting wind, her sky-colored eyes twinkling with shared determination and newfound camaraderie.)

Eolan: Elysia is right, Leire. Your curiosity and openness to understand are the first steps toward adapting to this new world. Remember, every forest begins with a single seed. Your journey may seem daunting now, but with time, patience, and determination, you will grow to understand our world, just as we hope to learn from you about yours. (Eolan’s intense green eyes glow with reassurance, his voice carrying the calm and steadiness of a deeply rooted tree.)

Conclusions I took from this test:

  • Damn, these characters can talk up a storm.
  • This Elysia girl loves making wind-related puns.
  • I should increase the number of latest dialogue lines sent to GPT-4 from 10 to at least 20, because the two fantasy people asked about Arachne twice; the previous answer they got fell out of their context window (GPT-4 is unaware of anything beyond its training if it isn’t present in the prompt.)

I enjoy this shit so much that I changed the whole setting and launched another dialogue. In this case, Leire has become depressed due to her situation. Piss-drunk, she’s wandering around a fantasy town when she stumbles into the pair of kind fantasy people, who are planning a hunt.

Second dialogue

Elysia Starbinder: (looking at Leire, her sky-colored eyes full of concern) You seem unsettled, stranger. The winds whisper of your despair. Can we help you find your footing in this world?

Leire: (slurring her words, glaring at Elysia) ‘elp? Nah! I don’t need ‘elp from humans! I just need more… more… (trails off, looking dazed) Arachne, where the hell am I?

Eolan: (softens his gaze, looking at Leire with concern) You’re not alone in this strange world, friend. Sometimes, the forest of life seems dark and endless, but remember, even the smallest light can guide us. Perhaps we can help you find your way.

Leire: (chuckles bitterly, swaying slightly) Light? You think I need light? Nah… I’ve been in the dark all my life, mate. I’m used to it. (pauses, squinting at Eolan) And you… You’re not human, are you? Even better!

Elysia Starbinder: (laughs softly, a gentle breeze swirling around her) You’re right. I’m not human. I’m a Sylph, a child of the wind. And from one outsider to another, I understand the darkness you speak of. But remember, the dawn always follows the night. Let us help you navigate this unfamiliar territory.

Leire: (snorts, swaying dangerously) A Sylph, huh? (laughs, the sound harsh and grating) Well, at least you’re not a damn bunny. (pauses, her gaze distant) Dawn… Dawn is just another chance to mess up, mate. But sure… lead the way, wind child. Can’t get any worse than this, can it?

Elysia Starbinder: (smiles gently, the breeze intensifying around her, carrying a sense of solace) It’s all about perspective, isn’t it? Dawn can be a fresh start, a chance to do better. And as for where we’re leading you… (gives a mischievous grin) Well, with the wind as our guide, who knows where we’ll end up? But I can promise you this, Leire; it won’t be boring.

Leire: (snorts, rolling her eyes) A fresh start, huh? Haven’t heard that one before. (pauses, looking at Elysia with a mix of curiosity and skepticism) But hey, as long as there’s booze, I’m in. Just… no bunnies, alright?

Eolan: (laughs warmly, his moss-like hair rustling in the breeze) Don’t worry, Leire. No bunnies, I promise. But, consider this: while the forest may hold its shadows, it also nurtures the most beautiful blooms. Perhaps in this new dawn, you might find something unexpected, something that makes the journey worthwhile. Now, shall we find that drink you mentioned?

Leire: (laughs sarcastically, then wipes her mouth with the back of her hand) Unexpected blooms in the forest of life, huh? This ain’t some poetic fairytale, buddy. But yeah, let’s find that drink. And remember, no bunnies… or I’ll freak out.

Elysia Starbinder: (chuckles, a playful breeze stirring her white hair) No bunnies, got it. Let’s head for a place I know. They serve a mean elderberry brew. And who knows, Leire, this ‘poetic fairytale’ might surprise you yet. After all, every story needs its heroine. (gives Leire a meaningful look, then starts walking, the wind spirits swirling around her in anticipation)

Eolan: (smiling warmly, his green eyes shimmering gently in the fading light) Indeed, every path we tread, every new dawn we greet, is a chapter of our own story. Perhaps this elderberry brew will mark the beginning of an interesting tale for you, Leire. Lead on, Elysia. Let’s find that drink and see what unforeseen blooms this night might nurture.

Elysia Starbinder: (with a twinkle in her sky-colored eyes, leading the way) Just remember, Leire, it’s not the destination but the journey that matters. And every journey, no matter how chaotic, has its own beauty. Now, let’s find that elderberry brew and see what the winds of fate have in store for us.

Leire: (snorting dismissively, stumbling after them) Beauty, huh? Guess you’ve never seen the inside of a codebase. But yeah, let’s see what this ‘elderberry brew’ is all about. And remember… no bunnies.

Eolan: (laughs, his moss-like hair rustling with amusement) Codebase? Sounds like a different kind of forest indeed. Don’t worry, Leire, we’ll steer clear of bunnies and stick to the elderberries. And who knows? Maybe amidst the chaos of this world, you’ll find your own kind of beauty.

Elysia Starbinder: (laughs softly, a playful breeze lifting her dawn-lit hair) Codebases and elderberries, darkness and dawn, beauty in chaos… Sounds like the beginnings of a grand adventure, Leire. And remember, in this world, even a stumble can be the start of a dance. Now, let’s find that brew and see what tales the wind whispers to us tonight.

Leire: (hiccups, squinting at Elysia) Dance, huh? The only dance I know is the one with the keyboard. But yeah… let’s find that brew. And who knows? Maybe I’ll find my kind of beauty in a pint of elderberry. (smirks, then staggers after them, mumbling) Arachne, if you’re listening, no bunnies… or anthropomorphic horses.

Elysia Starbinder: (chuckles, the wind spirits swirling around her in a playful gust) Anthropomorphic horses, huh? Now there’s a dance partner you don’t find every day! But don’t worry, Leire, our path leads to elderberries, not equines. And remember, every key on your keyboard can play a note in the melody of life. Now, shall we proceed to the rhythm of the wind?

Eolan: (laughs lightly, his emerald eyes twinkling with amusement) Indeed, Elysia. Our melody tonight will be one of elderberries, not equines. And Leire, maybe the beauty you seek is not in the pint, but in the stories it unlocks. After all, every key has a tale to tell, and every tale, a key to understanding. Now, let’s find that brew and dance to the rhythm of this night.

Elysia Starbinder: (with a playful gust of wind rustling her white hair) I couldn’t agree more, Eolan. And remember, Leire, each tale within us is a whisper of the wind, waiting to be heard. Now, let’s unlock those stories with some elderberry brew. Who knows, it might just be the key to a dance you never knew you could perform.

Leire: (shaking her head, laughing bitterly) Dance? Tales? You two are something else. But hey, as long as that brew keeps flowing, you can prattle on about keys, tales, and dances all you want. Just… remember the no bunnies rule, alright?

Elysia Starbinder: (laughs, a breeze lifting her white hair as if in agreement) No bunnies, Leire, that’s a promise. And who knows, after a few elderberry brews, you might just find yourself dancing with the tales of the wind. Now, let’s go find that drink, shall we?

Leire: (grumbles, stumbling after them) Dancing with the tales of the wind, huh? Guess anything’s possible with enough booze… Lead the way, wind child.

In this case, the dialogue ended because the OpenAI API refused my last request, as it ballooned past 2048 tokens. I guess I’ll have to reduce the number of dialogue lines included in the prompts.

For the third and final test, I changed the setting to the following: Leire is disturbed and despairing about having been sent to a fantasy world, and after wandering around town for a while, she approaches the couple of fantasy people, intending to rob them of their valuables.

Third dialogue

Elysia Starbinder: (raising her hands in a calming gesture) Easy there, stranger. There’s no need for violence. We can help you, but not if you’re threatening us.

Leire: (her eyes darting nervously, her voice shaking) I… I don’t need your pity! Just give me your money and nobody gets hurt! Arachne protect me…

Elysia Starbinder: (gently, her sky-colored eyes soft) Leire, we’re not your enemies. We don’t carry much coin, but we could help you find work or a place to stay. And if it’s guidance you need, I can offer that too.

Leire: (her voice wavers, but she steadies herself, gripping the weapon tighter) I don’t need your help… I need your money! Don’t make me do this…

Elysia Starbinder: (in a soothing tone, her voice carried by a gentle breeze) Leire, I can see the desperation in your eyes, but this isn’t the way. Let me help you. Put down your weapon and let’s talk about what you really need.

Leire: (her eyes glinting with a mix of desperation and determination) There’s nothing to talk about. Your money or your life… Choose now!

Elysia Starbinder: (Her voice steady, eyes reflecting understanding) Leire, I understand your fear, your desperation. But I truly believe we can find another way, a better way. Let’s make a choice not driven by fear, but by hope. Put down your weapon, and let’s navigate this storm together.

Leire: (her voice breaking, tears welling up in her eyes) I don’t… I don’t have any hope left. But… (she lowers her weapon slightly, the desperation in her eyes giving way to a glimmer of uncertainty) Arachne… guide me…

Dialogue between three people with GPT-4

This post is a continuation of the following:

Two characters of my novel speak to each other through GPT-4

In that post I mentioned that the following entry would feature four characters, but I have settled for the intermediary step. I asked regular ol’ ChatGPT to come up with the descriptions of a couple of fantasy characters. The third character involved in the upcoming conversation is Leire, the protagonist of my ongoing novel. After a sudden incident involving a truck, Leire found himself transported to a fantasy world.

I created the memories database for the new characters, then I produced their corresponding character summaries (which involve multiple calls to GPT-4 each):

Name: Eolan (age 130)
Traits: ancient, magical, special affinity, impulsive, naive, young, curious, thoughtful, adventurous, skilled, intense, green-eyed, explorative, fascinated, moss-haired, archery-trained, determined, rebellious, tall, bark-like skin
Eolan, a member of the magical Silvari race, is an ambitious, curious, and courageous individual with a deep connection to nature and an affinity for plant magic. He has a desire to prove that his people’s magic can foster unity and understanding among various races. His main goal is to explore the world to better understand the relationship between all living things, displaying a thoughtful and adventurous spirit. Despite his young age, which makes him impulsive and less bound by his people’s traditions, Eolan is skilled in archery and has the ability to manipulate plants and communicate with them. His youthful naivety and inexperience make him vulnerable to the harsh realities of the world outside his sheltered forest home. Eolan’s physical characteristics include intense emerald green eyes that glow softly in the dark, hair resembling hanging moss, and bark-like skin that has the rich hue of autumn leaves. Despite facing opposition from his parents and community, Eolan’s desire to explore the world demonstrates his courage and determination.
As a young and curious member of the Silvari race, Eolan’s current daily occupation is that of an explorer and a student of the world. He uses his unique abilities to communicate with nature and his skills in archery to navigate through the world beyond his forest home. Eolan is on a mission to understand the relationship between all living things, prove the potential of his people’s magic, and foster unity among the races of the world. His days are filled with exploration, discovery, and the occasional struggle that comes with confronting the harsh realities of the world outside his previously sheltered life.
Despite his youth and inexperience, Eolan feels a sense of accomplishment and progress in his journey so far. He has stepped out of his comfort zone, the Silvari forest, to explore the vast world, a decision that was met with disapproval from his community. He views his journey as a way to prove the versatility of his people’s magic and its potential to foster unity and understanding. His curiosity and adventurous nature have served him well in this quest, and despite the harsh realities of the world, he remains hopeful and determined. Eolan’s skill in archery and plant magic also give him confidence in his abilities. While he is aware of his naivety, he sees it as an opportunity for growth rather than a setback. As he continues to forge his own path, he feels a sense of pride in his progress and a burning desire to continue learning and growing.

Name: Elysia Starbinder (age 27)
Traits: rare, unique, bridge the gap, vital, struggles, leader, Sylph, driven, tall, slender, dawn-lit, understanding, harmony, free-spirited, adventurous, outsider, solace, Wind Whisperer, challenge, convince, clear, white, flowing, gentle breeze, wise, guidance, support, agile, keen sense of direction, playful, mischievous, chaos
Elysia Starbinder is a Wind Whisperer of the Sylph race, possessing unique abilities to communicate and command wind spirits. This rare trait, believed to be bestowed during a celestial alignment at her birth, sets her apart even among her own people. Tall, slender, with sky-colored eyes and dawn-lit skin, her physical attributes mirror her connection to the air and wind. Elysia is wise, offering guidance to her fellow Sylphs, and adventurous, embodying the whimsical and free-spirited nature of wind. Her agility and keen sense of direction are notable traits, allowing her to navigate any path the wind takes her on. Despite her playful and mischievous side, Elysia is driven by a desire for understanding and harmony. Her primary goal is to bridge the gap between her people and other races, challenging their preconceived notions about the Sylphs. However, she grapples with the expectations of leadership from her own people, a role she is unsure she is ready for. Elysia’s main challenge lies in projecting her people as vital contributors to the world’s balance, rather than being seen as whimsical and unpredictable.
Elysia Starbinder’s current daily occupation is being a Wind Whisperer within her Sylph society. As a Wind Whisperer, she communicates with and commands the wind spirits. Her unique abilities, bestowed by a rare celestial alignment, allow her to generate anything from gentle breezes to tempestuous gusts. Alongside these duties, she’s also seen as a leader among her people, which brings an additional layer of responsibility and expectation. Elysia also devotes substantial effort into building bridges between her race and others, aiming to foster understanding and harmony. This includes challenging preconceived notions about the Sylphs among the grounded races and offering guidance to her fellow Sylphs. Despite the weight of these responsibilities, she maintains her free spirit, often playfully causing minor chaos with her wind spirits.
Elysia Starbinder feels like she’s on the cusp of significant growth, but is also burdened by the weight of her people’s expectations. She is proud of her unique abilities and is driven by her desire to bring harmony between her people and the other races. However, she struggles with the responsibility this brings, as being seen as a leader is a role she is still unsure about. Yet, despite these challenges, Elysia is undeterred. She is motivated by her dreams of a future where the Sylphs are understood and appreciated for their unique contributions to the world. Her interactions with the wind spirits provide her with a sense of purpose and solace, giving her the strength to continue on her journey. She relishes her freedom and the adventures she embarks on, even if they often lead to minor chaos. Overall, Elysia feels like she is making progress, but she knows that the road ahead is long and filled with challenges. Nevertheless, she is prepared to face them with determination, wisdom, and a mischievous twinkle in her eye.

The goal for these test dialogues was to determine how well the system handles the presence of a third character in the dialogue, how often that person gets involved. I’m writing Leire’s dialogue, but the fantasy couple are controlled by GPT-4.

First dialogue

Leire: E-excuse me, magical-looking couple. Could you tell me where the hell are we?

Elysia Starbinder: (Laughs softly, a playful breeze swirling around her) Of course, we can help. You’ve found yourself in the Wind’s Whisper Tavern, within the Sylph city of Zephyria. But it sounds like you’re asking about more than just location. Am I right?

Leire: (Digging her fingers into the fabric of her shirt) Y-you could say that, yes. I was minding my business, returning home from work, when a truck ran me over. As I lay there broken, bleeding from every orifice, I thought to myself, “This is it, Arachne is finally going to come down to entangle me in her web and lift me up to the heavens.” But instead of that I woke up in this fantasy world inhabited by strange fantasy creatures. I wandered into the nearest city, then into the first tavern I came across. Because that’s what you do, right?

Elysia Starbinder: (Smiling gently, a soothing breeze seems to wrap around Leire) That is quite an experience you’ve had. It sounds like you’ve been given a second chance in a different world. While this might be overwhelming, remember, winds of change can lead to exciting new adventures. I’m Elysia, a Wind Whisperer. I communicate with the wind spirits, and perhaps they might have some wisdom for you. But first, let’s get you something warm to drink. You seem like you could use it.

Leire: That’s so kind of you. Who is your fancy-looking companion?

Elysia Starbinder: This is Eolan, my dear friend and fellow wind-enthusiast. Don’t mind his quiet demeanor, he’s just a bit shy around strangers. But I assure you, he’s quite the companion once you get to know him.

Leire: (sits down) I could do with some wisdom. Can you ask those wind spirits of yours what am I supposed to be doing as a helpless woman suddenly transported to a fantasy world?

Elysia Starbinder: (Smiling warmly) Leire, it seems to me that you’re anything but helpless. You’ve been thrust into a new world and you’re navigating it courageously. As for what you’re supposed to do, I believe that’s a question only you can answer. The wind spirits can guide us, yes, but it’s our hearts that choose our path. Listen to yours, and you’ll find your way.

Not sure what I feel about GPT-3.5-turbo choosing to end the conversation there, given that not much of anything has been resolved. Perhaps it’s because I put the context as Leire being disturbed because of the whole isekai thing, and now she feels somewhat better. Also, the third character didn’t speak.

Second dialogue

Leire: Excuse me, could you offer me a moment of your time, pair of strange, elfish creatures?

Elysia Starbinder: Of course, traveler. We aren’t elfish, though. We’re Sylphs, beings of the wind. You seem distressed; how may we aid you?

Leire: I’m quite distressed. This morning I was run over by a truck, and I’ve suddenly found myself in this fantasy world full of generic fantasy races.

Elysia Starbinder: I see, that is indeed a predicament. But don’t be too quick to judge, Leire. Our world, its races and cultures, are far from generic. Each has its unique essence, like us Sylphs with our wind spirits. Perhaps some understanding could help you find your bearings here.

Leire: I didn’t intend to offend you. I can tell that you could conjure up some spell and tear me apart in seconds.

Elysia Starbinder: Tear you apart? Oh, no, you misunderstand. We Sylphs prefer to communicate and guide rather than harm. We are more likely to summon a gentle breeze to soothe your worries than a gust to tear you apart. We believe in understanding and harmony, not hostility.

Eolan: And it’s the same with my people, the Silvari. We might be misunderstood as generic elves because of our appearance, but our magic and connection to nature is unique. I’m Eolan, by the way. And while it’s true that we have the ability to manipulate plants, we don’t intend to harm unless it’s necessary for survival. Leire, you seem to have been thrust into an unfamiliar world, but perhaps there’s a reason for it. The world is vast, filled with different races and cultures, and there’s so much to learn and understand. Don’t view it as a curse, consider it a new beginning, a chance to explore and experience something beautiful.

Can’t help that fact that this Elysia person knew Leire’s name without having introduced herself. Perhaps I should have asked regular ChatGPT to come up with fantasy characters that weren’t so elfish, because their speech lacks the needed variety. Still, the third character spoke.

Third dialogue

Leire: Hi! I’m selling these fine leather jackets.

Elysia Starbinder: Leather jackets, you say? Ah, the grounded races and their strange customs. Tell me, stranger, do they make these jackets light enough to dance with the wind?

Leire: I doubt you could dance in them, but they would make you look tough, and probably give you a biker chick vibe.

Elysia Starbinder: A biker chick vibe? Now that’s a gust of fresh air! But I’m afraid I’m more of a breeze dancer. My friend Eolan, however, might be interested. He’s always been intrigued by your grounded fashions.

Leire: What do you say, Eolan guy? Are you in the market for some fresh leather jackets? Only a thousand gold coins!

Eolan: A thousand gold coins, you say? That’s quite a sum. But I’m intrigued, not so much by the jacket, but by your story. You seem out of place. Where are you from?

Leire: (tearing up) Well, I’m not from here, that much you could tell. This morning I found myself transported to this fantasy world after a regrettable mishap involving a truck and my body. I suddenly found myself broke and helpless, so I strangled some passerby and stole his leather jacket.

Eolan: (visibly taken aback) That’s…quite the tale. You’ve had a hard day, it seems. We may not have much use for leather jackets, but we can certainly offer you some company and a warm meal. You’re not alone.

Leire: I have just confessed that I’m a murderer, though. Are you okay with me being someone who could kill you in your sleep for reasons you would never find out, and that wouldn’t make sense anyway?

Elysia Starbinder: (laughs softly) Well, that’s the chaos of life, isn’t it? One moment we’re dancing with the wind, the next we might be caught in a storm. I’ve found that fear doesn’t get you far in this world. So, Leire, we’ll take our chances. Sit, share a meal, and let’s find a way to navigate this whirlwind you’ve found yourself in.

Leire: (sits down at the couple’s table) Great. I’m so hungry I could eat the both of you up! I just might!

Elysia Starbinder: (Laughing) Well, we might be a bit too breezy for your taste, Leire. But let’s not resort to cannibalism just yet. The tavern’s stew is quite delicious. Tell us more about your world. How different is it from ours?

Leire: (sighs) It has trucks, nasty machines designed to violently transport random people to fantasy worlds. Other than that, it’s pretty much your normal, average world. We also have the internet.

Elysia Starbinder: The internet, you say? Now that’s a gust I haven’t caught yet. Is it some form of wind communication in your world, Leire?

Leire: If you pass wind next to people they get the point that they should stay away from you.

Elysia Starbinder: (laughs heartily) Well, Leire, in our world we tend to use the wind a bit differently, but I suppose that method could work too. It’s fascinating how diverse the universe can be, isn’t it?

Leire: (looking at Eolan) What about you, silent fella? Want to interject with some meaningful commentary?

Elysia Starbinder: (smiling at Eolan) Eolan, why don’t you share a bit of our world too? After all, it’s not every day we get to converse with someone from another realm.

Eolan: (smiling gently, eyes glowing in the dim tavern light) I’ve always been fascinated by the stories of worlds beyond our forest. You talk of trucks and the internet, and I can’t help but wonder what other wonders and mysteries your world holds. But here, in ours, we communicate with the wind, the trees… nature itself is our internet. (leans in, curious) Tell me, Leire, in your world, do you have a way to talk to the trees?

Elysia Starbinder: (chuckles softly) Indeed, Eolan has a way with words, don’t you think, Leire? While we can’t claim to have trucks or this internet of yours, we do have our own unique wonders. For instance, the wind spirits I commune with – they whisper stories of the world, tales of the past, present, and possible futures. And trees, oh, they hold centuries of wisdom. So, in a sense, our world is full of voices, if one knows how to listen.

Leire: In our world, as long as you get high enough, you can communicate with anything. But the spirits are little shits that throw your property around, and the worst ones even sit on your chest while you’re trying to sleep. One of them wears a hat.

Eolan: (laughs gently) Spirits sitting on chests and throwing property around, sounds like a mischievous bunch! Here, the spirits are more… subtle, more connected to the natural world. They guide us, share their wisdom, and sometimes, play harmless pranks. As for the one with the hat, I’d love to meet him someday. (pauses, thinking) In your world, Leire, do you have spirits or beings connected to the trees and nature, like we do here?

Leire: (lowers her head) Sadly, no. Magic died long ago in our world, after a swarm of meteors bombarded us 12,800 years before my time, kicking off the end of the Ice Age and plunging the planet into the thousand-years-long cold spell named the Younger Dryas.

Eolan: (his eyes widening in surprise) That sounds like such a monumental event, Leire. It’s heartbreaking to hear that magic was lost in your world. I can’t even begin to imagine how that must have affected your people… and your trees. (softly) Magic is a natural part of life here in our world, as vital as the air we breathe. It’s what connects us to nature, to the trees, and to each other. (pauses, thoughtful) I wonder, is there anything in your world that has replaced that connection since the loss of magic?

Leire: Thankfully we have the internet, which allows us to sit in our underwear in front of a computer for most of the day while occasionally chuckling at fail videos.

Elysia Starbinder: (laughs gently) The internet sounds like a peculiar place, Leire. Here, our connection to the elements and spirits replaces the need for such… conveniences. But I suppose every world has its own charm. And as for spirits causing mischief, well, (smiles mischievously) let’s just say wind spirits can be quite playful too.

Leire: Playful as in you’ll end up naked and slimy?

Eolan: (laughs heartily) Not quite, Leire. Our spirits are more about a gentle breeze on a hot day or a sudden gust warning of an approaching storm. They’re playful, but in a way that reminds us of their presence and their connection to the world around us. As for ending up naked and slimy… well, that’s an adventure for another day!

That was a success. This Elysia gal couldn’t stop herself making wind-related puns. What a nice pair of fantasy people!

As I was producing these “tests,” I came across the following video of someone doing pretty much the same stuff with GPT-4 but involving Skyrim VR and its lovable characters. Check it out.