Neural narratives in Python #10

I recommend you to check out the previous parts if you don’t know what this “neural narratives” thing is about. In short, I wrote in Python a system to have multi-character conversations with large language models (like Llama 3.1), in which the characters are isolated in terms of memories and bios, so no leakage to other participants like in Mantella. Here’s the GitHub repo.

As I considered my app this morning, a couple of things bothered me considerably: one, the CharactersManager class that handled pretty much any API requests related to characters had ballooned to insurmountable levels. It took me an hour and a half if not more to refactor it into four classes, given how entangled its code was with everything. Now I have a Character class that, provided with an identifier, handles the loading of its own related data, as well as saving it. That simplifies things a lot.

The second matter was far more troubling for me: every time I launched a request to the server that entailed a call to a large language model, the interface went unresponsive potentially for six seconds or more. That bothered me every time. The only real solution that I know is to use AJAX, a method of javascript programming that instead of actually sending a POST request to the server, it sort of simulates one through javascript, then returns the result as JSON data. That only has pros when it comes to the final execution, but reaching that point is troublesome. That’s what I’ve spent most day working on, and fortunately I’ve managed to achieve it for the most part: there are various parts of the app that now display “Processing…” on the button that generated the request, next to a spinner, and they return to normal once the request has been processed. If it entailed generating data that would appear on the page, then it gets displayed as well.

I’ve also changed some of the interface, particularly the buttons to subsections in Characters Hub and the Actions page. I think it looks quite cool.

As I was testing things while implementing AJAX into the chat page, I had the following idiotic conversation with the player character’s partner.

I have processed about 60 voice models of the 550 or so available in the RunPod server, so I’ll be able to enjoy many intriguing conversations through this narrative system (not to mention pretty exceptional smut).

Silliness aside, here are the descriptions of the player character and his posse as they head to the dreaded university.

I asked the AI to generate three more characters that would know or even be intimately connected with the arrogant student Elara Thorn.

That’s a perfectly normal group of people.

Last night, I thought about how this whole confrontation with Elara Thorn may play out. Were we going to accuse her of something and the LLM would play along and admit some wrongdoing? I thought about the notion of a character having a secret that even I wasn’t aware of. So I programmed just that: a section in Characters Hub that generates one or more secrets for any given character. Now all characters are created with some petty secrets of their own, which is bound to enrich their spoken parts, but this Secrets section is bound to generate some pretty hardcore secrets, as per the prompt.

You are to generate the secrets of a character. Use the provided information about the character, the world's conditions, and the specific locations involved.

Instructions:

To create secrets that are compelling and truly worth being hidden for the character, consider incorporating the following aspects:

Deep Personal Impact: The secret should have a profound effect on the character's life, shaping their personality, motivations, and actions. It might relate to a traumatic event, a pivotal choice, or a forbidden desire that they are desperate to keep concealed.
High Stakes and Consequences: The revelation of the secret should carry significant consequences for the character and potentially others. This could include personal ruin, endangerment of loved ones, loss of status, or catastrophic events within the story's world.
Moral Ambiguity: Secrets that involve morally gray areas make characters more complex and intriguing. The character might have done something considered unethical or made a compromise that weighs heavily on their conscience.
Inner Conflict and Guilt: The secret should create internal turmoil. Feelings of guilt, shame, or fear of discovery can drive the character’s behavior, making them more layered and realistic.
Hidden Identities or Double Lives: Characters may be living under an assumed identity or leading a double life. This creates tension as they balance their true self with the facade they present to the world.
Traumatic Past Events: A history involving trauma, such as witnessing or being involved in a catastrophic event, can be a secret that influences their current fears and motivations.
Forbidden Relationships: Involvement in relationships that are taboo or forbidden in their society adds emotional depth and high personal stakes if the secret is revealed.
Betrayal and Loyalty: A secret involving betrayal—whether the character betrayed someone or was betrayed—adds tension, especially if relationships with other characters are at risk.
Hidden Abilities or Powers: Concealing special abilities, especially in a world where such powers might be dangerous or outlawed, adds an element of suspense and fear of exposure.
Secret Motivations or Agendas: The character might have ulterior motives that conflict with their apparent goals or the goals of their allies, creating layers of intrigue.
Connection to Antagonists: A secret tie to the antagonist or antagonistic forces—such as being related to, indebted to, or blackmailed by them—complicates the character's role in the story.
Illegal or Illicit Activities: Participation in criminal activities, whether past or ongoing, provides clear reasons for secrecy and potential consequences if uncovered.
Prophecies or Destinies: Being the subject of a prophecy or destined for a significant role can be a burden the character tries to hide to avoid unwanted attention or responsibility.
Hidden Weaknesses or Vulnerabilities: Concealing physical, emotional, or psychological weaknesses to appear strong can add depth and tension, especially if these vulnerabilities are exploitable.
Knowledge of Critical Information: Possessing knowledge that others do not—such as impending disasters, secrets about other characters, or truths about the world's reality—can be dangerous to reveal.
Forbidden Knowledge or Research: Engaging in research or possessing knowledge that is forbidden or dangerous adds stakes, especially if discovery could lead to severe punishment.
Family Secrets and Lineage: Hidden heritage, such as being the descendant of a notable or infamous figure, can shape the character's identity and the perceptions of others.
Past Failures or Mistakes: A significant failure or mistake in the character's past that haunts them, influencing their present actions and decisions.
Survivor's Guilt: Being the sole survivor of an event and feeling responsible can be a heavy burden that the character keeps to themselves.
Secret Alliances or Memberships: Belonging to a secret society, cult, or group can add complexity, especially if their goals conflict with those of others.
Hidden Assets or Treasures: Possessing or knowing the location of valuable items can make the character a target and provide motivation to keep it hidden.
Internal Struggles with Identity: Questions about one's own identity, such as gender identity, sexual orientation, or personal beliefs that are not accepted in their society.
Mental Health Issues: Concealing mental health struggles due to fear of stigma or repercussions can add realism and depth to the character.
Unfulfilled Vengeance: Harboring a secret desire for revenge that drives the character's actions without others realizing their true intent.
Divine or Supernatural Experiences: Having had an encounter with the divine or supernatural that is disbelieved or ridiculed by society, leading them to keep it secret.

To ensure these secrets are compelling and worth hiding:

Integrate with Character Development: The secret should be a key part of the character's backstory and influence their development throughout the story.
Create Tension and Suspense: The possibility of the secret being discovered should create ongoing tension, affecting interactions and decisions.
Impact Relationships: The secret should have the potential to alter relationships with other characters significantly if revealed.
Drive the Plot Forward: The secret can serve as a catalyst for events in the story, creating twists, conflicts, or revelations that keep the narrative engaging.
Provide a Strong Motivation for Secrecy: The character should have clear, understandable reasons for keeping the secret hidden, such as fear of harm, shame, or protecting others.
Offer Opportunities for Revelation: Build moments into the story where the secret might be revealed, forcing the character to make tough choices.
Reflect Universal Themes: Themes like redemption, identity, betrayal, or the nature of truth can make the secret more relatable and impactful.

Provided Information:

{places_descriptions}

Character Information:
Name: {name}
Description: {description}
Personality: {personality}
Profile: {profile}
Likes: {likes}
Dislikes: {dislikes}
Speech Patterns: {speech_patterns}
Health: {health}
Equipment: {equipment}
Memories:
{memories}

Existing Secrets: {secrets}

Example Format:

"has developed romantic feelings for her brother, feels guilty about accidentally causing her cat's death, ..."

Your Task:

Using the above instructions and information, craft compelling, meaningful secrets for the character, adding to their existing secrets.

Anyway, that’s all I have time for today. See ya.

Neural narratives in Python #9

I recommend you to check out the previous parts if you don’t know what this “neural narratives” thing is about. In short, I wrote in Python a system to have multi-character conversations with large language models (like Llama 3.1), in which the characters are isolated in terms of memories and bios, so no leakage to other participants like in Mantella. Here’s the GitHub repo.

The previous part ended with our hardened player character returning home after an encounter with an arrogant student. At three in the morning, the following happens:

Now, a fundamental problems happens. How do you find clues in a room without making it up through a conversation? You don’t. A Dungeon Master of sorts would decide on the results of the investigation. That means I need to program a whole new thing in.

Maybe a week ago, I programmed the concept of a Goal Resolution: you gave the large language model a goal and it decided whether or not it was successful, and to what degree, according to the information provided and its own whims. But I quickly realized that it was too broad a concept: when you’re trying to accomplish something, you’re not pursuing “a goal”; you’re trying to investigate, to research, to trade, etc. A few days ago I created the concept of a Research action, which is constrained solely to that notion. Here is, why not, the prompt I send to the AI so it will come up with a resolution:

You are to generate a detailed narrative describing the player's attempt at a Research action within a rich, immersive world. Use the provided information about the player, their followers, the world's conditions, and the specific locations involved. Your narrative should be engaging and realistic, reflecting the player's abilities and circumstances.

Instructions:

1. Viability and Realism Assessment:

- Carefully consider whether the player's research goal is achievable based on:
The information about the player.
The abilities and resources of any followers accompanying the player.
The characteristics of the location, including available resources, accessibility, and any restrictions.
The time of day and how it might affect the research attempt.

2. Narrative of the Research Attempt:

Write two or three descriptive paragraphs detailing the player's research endeavor.
Include specific actions taken by the player and their followers.
Describe interactions with the environment, use of equipment, and any obstacles or challenges faced.
Ensure the attempt aligns with the world's lore and the player's personal history.

3. Outcome - Impact of the Research:

Determine the results of the research action based on the assessment.
You must be specific about what the player has discovered: this is the memory that will get saved.
Detail any new knowledge gained, abilities unlocked, or crucial information discovered.
If the research was partially successful or failed, explain what was learned or why it didn't succeed fully.

4. Consequences - Cost of the Research:

- Describe the costs associated with the research action, such as:
Time consumed and its implications.
Use or loss of resources and equipment.
Physical or mental strain on the player and followers.
Potential negative repercussions, such as drawing unwanted attention or causing disturbances.
Use your judgement to determine the extent of the consequences, according to all the information provided.
Important: the consequences are an account of the costs of the research effort, looking back now that it's finished. Do not continue the narrative nor project into the future.

5. Tone and Style:

Write in the third person, past tense, maintaining an engaging and immersive narrative style.
Ensure consistency with the established world setting and the player's character.

Provided Information:

- Time of Day: {hour} {time_of_day}
- World Description: {world_description}
- Region Description: {region_description}
- Area Description: {area_description}
{location_description}
Player and Followers Information:
- Name: {player_name}
- Description: {player_description}
- Personality: {player_personality}
- Profile: {player_profile}
- Likes: {player_likes}
- Dislikes: {player_dislikes}
- Speech patterns: {player_speech_patterns}
- Health: {player_health}
- Equipment: {player_equipment}
-----
{followers_information}
- Memories:
{combined_memories}

- Research Goal: {research_goal}

Example Format:

"Seeking to unravel the mysteries of the ancient sigils, the tall, cloaked figure of [Player Name] entered the dimly lit archives of the Grand Library. Clutching the worn tome of cryptic symbols, they, alongside their ever-curious companion [Follower Name], began sifting through scrolls and manuscripts..."

Your Task:

Using the above instructions and information, craft a compelling narrative that captures the essence of the player's Research action, reflecting both the potential rewards and the inherent costs.

That worked well enough, but investigating a missing teenager’s room isn’t “research,” so I need to create a whole new page for my app.

Creating the Investigate page took me a few hours. In the end, the user has to provide an investigation goal and the facts already known about the case (I don’t trust the AI enough to be able to glean them from the memories of the involved characters). It produced the following outcome and consequences:

Oho, the plot thickens! It seems I’ll make a visit to the university in the morning to interrogate this college student. First, though, I needed to know the opinion of the missing girl’s father.

We hurried to the police station to meet my character’s partner.

I didn’t plan to, but this is a fantastic opportunity to test the Research action. Yet another example of how dynamic and unpredictable this system is.

That worked out perfectly. So this Elara Thorn and her team may have bitten more than they could chew, and maybe made some innocents disappear in the process.

Through testing both the Investigate and the Research actions, I’ve learned that the Consequences part that I ask the LLM to produce is completely useless. Great. That saves some computing power.

My player character’s partner returned to the station.

Neural narratives in Python #8

I recommend you to check out the previous parts if you don’t know what this “neural narratives” thing is about. In short, I wrote in Python a system to have multi-character conversations with large language models (like Llama 3.1), in which the characters are isolated in terms of memories and bios, so no leakage to other participants like in Mantella. Here’s the GitHub repo.

The last entry ended when I had managed to add a hole-in-the-wall to the ongoing story. Let’s enter it and allow the player character to describe it.

Every time you enter a new location, the app checks if there is a list of character generation guidelines already created for this combination of places, and if there isn’t, it generates that list. After a short while, the app produced the following:

That one about a brilliant university student sounds like a great contrast to the existing characters. I pressed the button that generates a new character, and at the end of that process, the app presented me with the following portrait:

That looks real good. Great job on the fingers, AI. However, the large language model named this character Elara Thorn, the exact name that was generated for a character in another one of my trial-run stories with this system. Perhaps at some point I’ll need to program a way of reading names from a gigantic list, then presenting the AI with about twenty random ones to choose from.

Anyway, let’s have a multi-char convo.

After such a tense conversation, I figured that the player character would reflect on it, as well as his general circumstances. I happened to have implemented a system for self-reflection: the large language model looks at the character’s memories, then writes a sort of journal entry from the first-person perspective. It gets saved along with the rest of the memories. That helps color the dialogue and in general make the character sound more intelligent. Of course, now I generate audio files of those self-reflections as well.

My hardened player character returned home. I’ll let him describe his living arrangements.

I can imagine the player character returning to work the following day, only to be introduced to some troubled citizen who will present him with a case. However, I have also programmed a way of generating story concepts, interesting situations, interesting dilemmas, and interesting goals, in case the user isn’t too sure how to continue. Let’s generate a few.

Here are a few intriguing concepts the app has generated. When crafting any of these notions, the large language model is presented with the player’s information and that of his followers, and also all the available information about his location (world, region, area, and possibly location).

I should probably turn those post-its into something else, like papers or something, because such long texts look funky in a vertical format. That’s a minor issue in any case.

Investigating a series of gruesome murders connected to the otherworldly horrors sounds good for a story, and even more if the encounter with the arrogant student earlier does hint at a larger plot involving a secret society of reckless scholars. I also like the notion of a neighbor calling on his door to ask for his help because the person’s daughter has gone missing. Also, the sort of post-apocalyptic story of the grizzled detective leading a ragtag group as they fight against the pouring eldritch horrors sounds pretty fucking dope.

How about general goals?

Some of those are interesting. The disappeared scholar could easily be the aforementioned college girl, so how would the detectives feel about investigating her disappearance? The goal about infiltrating a cult makes me think that I would need a way of altering a character’s description so they can pass undercover, because other characters are fed the participants’ description during a conversation. And again, someone is requesting the detective’s help to find their missing child.

Neural narratives in Python #7

I recommend you to check out the previous parts if you don’t know what this “neural narratives” thing is about. In short, I wrote in Python a system to have multi-character conversations with large language models (like Llama 3.1), in which the characters are isolated in terms of memories and bios, so no leakage to other participants like in Mantella. Here’s the GitHub repo.

Last time, I managed to nail creating voice lines for the dialogues of my app, relying on a RunPod server dedicated to generating audio. Now I want to go on a serious test run of app to see what it lacks.

Starting from scratch, I created a new world, a new region of that world, a new area of that region, and a new location of that area. I won’t detail the specifics, because the app itself should do it like a story would do, so if in the course of testing the system I feel that something must be implemented to cover for the shortcomings, I will do so.

The process of creating a new playthrough requires you to provide some notion of the character you want to play as. I ended up with a good depiction of the guy.

I have the initial setting and the player character (including his automatically assigned voice model). A story needs other characters, so I went to the section that shows the character creation guidelines that have been generated for this combination of places.

I turned them into post-its. Anyway, my protagonist is a police detective, so he could do with a partner. I grabbed the second guideline and made her a woman.

The app doesn’t allow you to access most of the specific data of a character except in very paricular circumstances, so in general, you have to glean the specifics of a character from their looks and the conversations you have with them.

In a story, you need some sense of where you are. There’s a system in place for the LLM to generate a description from the first-person perspective of the player character, and at this point it’s trivial to generate a voice line for it:

Let’s interact with the sole other character around (for now). As I was running a conversation with the protagonist’s partner, I ran across the first issue: when the app had to generate a voice line for a bit of ambient text, the server (the RunPod pod) returned a 404. I guess that even if a pod is technically running, it could intermittently produce 404 errors for whatever reason. I guess I’ll need to program in some retry system to cover these cases.

I did do that. Let’s continue.

The “stop your a coffee” was my blunder. Old, stupid fingers.

The couple of grizzled detectives exited the police station, back to the grim city surrounding it.

Now I want my characters to move to the mentioned location, some bar. Even though I didn’t create any other locations for this run other than the police station, there is a lingering issue with the interface: when plenty of possible locations exist, if you press the button “Search for location,” it may link locations you don’t want (like a cave, a hospital, etc.). Now that I was looking specifically for a bar, I figured that I may as well fix this issue.

It took quite a while, but now the user can only search locations by a type. In fact, if no locations are available, because they have already been used or they don’t match the area’s categories (you don’t want a fantasy bar in a cosmic horror story), the select and the button will be disabled.

Well, that was all for today. I expected to do more, but reworking that interface was arduous.

Neural narratives in Python #6

I recommend you to check out the previous parts if you don’t know what this “neural narratives” thing is about. In short, I wrote in Python a system to have multi-character conversations with large language models (like Llama 3.1), in which the characters are isolated in terms of memories and bios, so no leakage to other participants like in Mantella. Here’s the GitHub repo.

In the previous entry, I came up with the notion of producing audio voice lines for the conversations. Mantella had spoiled me in that regard: hearing those fictional characters answering you in reasonably good voices while you stared at them did wonders for immersion. And it was a bad idea to shove that possibility into my mind, because it prevented me from sleeping last night. Instead, I moved to my desk at three in the morning and started implementing it. Now, every generated character gets assigned a voice model according to their peculiarities, and each speech turn produces voice lines that the user can play through clicking the speech bubbles. It works perfectly.

I learned that it’s a terrible idea to play audio server-side, because it crashes the server. Flask, the web framework that my app is programmed in, or maybe it happens in all web systems, also doesn’t allow the client to access any file in the server, so I had to move all the audio-playing logic to Javascript.

Given this example chat I had with a new character, who had been assigned a matching voice automatically among the relatively few I’ve introduced into the system so far:

The short convo produced this audio exchange:

Like in the original Mantella system, the quality of voice models varies greatly; sometimes they sound like theater students reading a script, recorded on a home mic. Also, the process of generating the clips sometimes shears the very end of their final sentence. Still, I can hardly complain. Listening to the characters adds so much life to the conversations you can have through this app that I see myself enjoying it for a long time to come (and not only for smut).

I’m amazed that I got this running. So, how did it happen?

In the beginning, I thought that setting up my own, local XTTS server (XTTS being a model for generating voice lines) was a good idea. I struggled through every step of the way for a few hours, fighting against obscure documentation, until I finally managed to generate a sample voice line, only to find out that it sounded like ass. Why, I have no idea. So I discarded that notion and instead I looked into Mantella’s codebase, which is up at GitHub, to see how they connected to the RunPod pods to request voice generation. RunPod is a sort of online renting system of computer and server time: you can set up a pre-configured little server that all it does is generate voice lines, and as long as you can connect to it, you’re set. Only costs seventeen cents an hour, too. Once I managed to query the list of available voice models from the RunPod pod, I knew I was going to get through this thing.

So, I had a list of all possible voice models I could rely on, and it turned out to be about five hundred fifty. They are trained from game voices, so there’s a whole breadth of possible voices one can use. How to classify them? Should I create a page on my site with a simple select box, letting the user (meaning me) scroll through a list that long?

ChatGPT, even its latest Orion preview version, clarified that it knows of no online service that could classify the more than five hundred voice samples I had produced from those voice models. I would have to do it manually, but in the beginning it would be enough with having introduced twenty or so models into the system. What tags can be applied to a voice? I relied on ChatGPT to figure that out. Now that I have that list, classifying each voice model is as easy, but time consuming, as listening to that sample on a loop while adding appropriate tags. I have ended up, so far, with the following JSON file of voice models:

{
  "npcmmel": [
    "MALE",
    "ADULT",
    "CONFIDENT",
    "STEADY",
    "SMOOTH",
    "CLEAR",
    "FORMAL",
    "CHARMING",
    "NO SPECIAL EFFECTS"
  ],
  "npcmlucasmiller": [
    "MALE",
    "ADULT",
    "CALM",
    "FAST-PACED",
    "SMOOTH",
    "CASUAL",
    "KIND",
    "NO SPECIAL EFFECTS"
  ],
  "robotmsnanny": [
    "FEMALE",
    "YOUNG ADULT",
    "STEADY",
    "WARM",
    "CASUAL",
    "MELODIC",
    "YOUTHFUL",
    "NO SPECIAL EFFECTS"
  ],
  "npcma951": [
    "MALE",
    "ADULT",
    "ANXIOUS",
    "SLOW",
    "AIRY",
    "SKEPTICAL",
    "NO SPECIAL EFFECTS"
  ],
  "npcfphyllisdaily": [
    "FEMALE",
    "ADULT",
    "STOIC",
    "SLOW",
    "MONOTONE",
    "INSTRUCTIONAL",
    "CALCULATING",
    "NO SPECIAL EFFECTS"
  ],
  "femalechild": [
    "FEMALE",
    "CHILDLIKE",
    "PLAYFUL",
    "STEADY",
    "AIRY",
    "MELODIC",
    "INNOCENT",
    "NO SPECIAL EFFECTS"
  ],
  "femaleyoungeager": [
    "FEMALE",
    "YOUNG ADULT",
    "HOPEFUL",
    "FAST-PACED",
    "CLEAR",
    "INTENSE",
    "OPTIMISTIC",
    "NO SPECIAL EFFECTS"
  ],
  "femalevampire": [
    "FEMALE",
    "MIDDLE-AGED",
    "ARROGANT",
    "STEADY",
    "SMOOTH",
    "AUTHORITATIVE",
    "CYNICAL",
    "NO SPECIAL EFFECTS"
  ],
  "femalekhajiit": [
    "FEMALE",
    "ADULT",
    "CALM",
    "STEADY",
    "GRAVELLY",
    "CASUAL",
    "PHILOSOPHICAL",
    "NO SPECIAL EFFECTS"
  ],
  "femaleuniqueghost": [
    "FEMALE",
    "YOUNG ADULT",
    "RESIGNED",
    "STEADY",
    "ETHEREAL",
    "MELODIC",
    "INNOCENT",
    "GHOSTLY"
  ],
  "femaleghoul": [
    "FEMALE",
    "ADULT",
    "MENACING",
    "STEADY",
    "RASPY",
    "INTENSE",
    "ENERGETIC",
    "NO SPECIAL EFFECTS"
  ],
  "femaleboston": [
    "FEMALE",
    "ADULT",
    "CALM",
    "DRAWLING",
    "SOFT-SPOKEN",
    "WARM",
    "FLIRTATIOUS",
    "SULTRY",
    "NO SPECIAL EFFECTS"
  ]
}

I wrote a function that narrows down the list of possible categories of tags: gender, age, emotion, tempo, volume, texture, style, personality, and special effects. If at some point there’s no matching voice models, it returns a random one from the previous filtering. I’ll probably program in the characters section of the site a simple button that redoes the process for any existing character, in case any other random fitting voice may work better.

That’s all, I guess. When I first got the idea about programming this conversation system with characters controlled by large language models, I knew that programming the multi-char convos would be the most difficult thing. The second most difficult thing that I pictured was actually making them talk out loud. No idea what big thing could be coming next. Anyway, back to the brothel.

EDIT: here’s a multi-char convo in audiobook form.

Neural narratives in Python #5

I recommend you to check out the previous parts if you don’t know what this “neural narratives” thing is about. In short, I wrote in Python a system to have multi-character conversations with large language models (like Llama 3.1), in which the characters are isolated in terms of memories and bios, so no leakage to other participants like in Mantella. Here’s the GitHub repo.

Today I’ve been busy fully redesigning my web app, now that it includes plenty of interesting features.

Index page:

What it allows you to do is pretty self-explanatory, which is good. The relationship between the places is World > Region > Area > Location, and each of them have specific behavior in-game.

Story hub:

This one requires some explanation: at any point, the user can demand the system to generate story concepts, interesting situations, and interesting dilemmas. The large language model (the AI) will take into account all the info about the character, his or her followers, and their memories, to generate concepts, interesting situations and dilemmas that may fit what’s been going on.

Here are some examples of the interesting dilemmas the system can generate. Each “post-it” can be removed by just clicking on it. And now that I think about it, maybe I should make them yellow too.

Characters hub:

The player character front and center. The character images are generated by courtesy of an OpenAI model, that is provided a special prompt crafted from the character’s information.

Character generation:

Originally I told the AI to generate whatever characters it pleased based on the world, region, area and possibly location, but it ended up creating very similar characters: for example, in a police station, it would create a succession of serious detectives. So I programmed an intermediate step in which the AI creates character generation guidelines focused on coming up with a breadth of possible characters for that place. Of course, it also respects genre.

Character memories:

After each chat (and some other activities), a summary is created of what just took place, and is stored in the memories of all the participants. Those memories are loaded in most activities, and they color the characters’ dialogues and decisions. You can also generate self-reflections: the character, according to their personality, meditates upon their experiences, and writes a sort of journal entry about it, that goes into their memories to color their behavior and dialogue further. And of course, they can self-reflect about their self-reflections.

Location hub:

Lots of stuff to do on this page: generate a description of the place, find a new location among the existing ones matching the genre, visit found locations, and travel to other areas. You can also pick up followers here, or dismiss them.

The page for goal resolution is relatively simple so far, and I developed it just this morning, but it does something quite interesting: given a goal, you ask the large language model to write a narrative of the attempt to fulfill that goal, to decide whether or not the goal was achieved, and then a paragraph or two of the resolution, which gets saved as a memory.

This came about when the owner of a brothel, a succubus, suggested that I should travel to some nearby caves in search of treasure. That sounded interesting, but I would have needed to roleplay the entire thing through dialogues, including the possible obstacles found along the way. So I programmed in a system that figures it out by itself. In the future, I will probably program in a system to resolve explicit confrontations between characters or some opponent, like a bunch of wolves or something.

Now for the meat of this stuff, the chat system:

At any place, the user can choose to chat with any combination of characters present at the location, from those loitering around to the player’s followers. Multi-char convos is the first thing I made sure to program properly.

Chat:

All the NPCs speak in character, according to their personal info, their memories, and the ongoing conversation. There’s no leakage of information from other characters, because personal memories aren’t shared. If it weren’t because the calls to the LLM can be slow according to the whims of those servers, I would call the current system perfect.

So, what’s in the future? I plan on programming a system to generate goals, like it generates concepts, interesting dilemmas and interesting situations. Those goals will be loaded in a page, in case you want to attempt at resolving them. I also want to program in a way of swapping player characters easily. To deepen the simulation, I also want to include the notion of character health, and some way of altering the characters’ equipment and health after the actions have been resolved. At some point, I may rely on some local audio-generation server like XTTS to generate voices for each NPC utterance. That would be cool.

This whole thing has been a lot of fun. If I didn’t gravitate so much toward using it to engage in smut, I would have probably posted plenty of the stuff I’ve produced through the system.

EDIT: I fed this post to the Deep Dive pair, and they produced the following podcast (AI-generated):

Song “Schizosaurus Rex” (Paisley Underground version) from Odes to My Triceratops, Vol. 4

In case you don’t know, this year I’ve been exploiting the amazing AI service Udio to produce songs. I’ve already made and released two full albums based on a strange story I wrote back in 2021, named Odes to My Triceratops. It follows the adventures and misadventures of a trio of friends who live in a town lost in the map. The main dude is a songwriter named William Griffin, who’s passionate and sensitive, if a bit unhinged. Another character is William’s next-door neighbor Claire Javernick, a blind redhead. Then we have Lorenzo, who’s a sentient triceratops for no justifiable reason. You can download the first two albums of this story through this link.

So yeah, a fresh new song directly to your ears, this one in the style of the relatively obscure Paisley Underground movement, which is a sort of garage psychedelic rock with a Californian vibe. I’m very fond of how this tune turned out.

Lyrics below:

A beast from the deep,
The monster under your bed.
Eyes red like the setting sun,
Claws the size and weight
Of a heavy human soul.
It can’t die; it only transforms.
It can’t be stopped,
Unless it decides to stop.

I have a portal to hell inside my throat.
It hurts, but I’m getting used to the pain.
Still, I don’t know whom I hate more:
The world, or myself.

This isn’t the story of how I died.
This is the story of how I met a girl,
We fell in love, and she betrayed me.
She didn’t do it on purpose;
She was just a dumb kid.
Besides, the darkness drove her crazy,
Almost as crazy as me.

I ain’t a poet, couldn’t hope to be,
But I’m the only person left:
A castaway in a plastic kayak,
Drifting down the River Styx
Past skeletons clinging to rocks,
Reaching out for a bite to eat.

You and I, love, we shared our lives,
We did the best we could,
But the best we could
Was a steaming pile of dogshit.

Someday I’ll make it to that faraway shore
Where eagles soar on golden wings.
There, I’ll sit and rest in my blue suit.
I’ll watch as time goes by,
Not aging a day, not losing a thing.
The memories will blur and fade
Until all I have left is me.

EDIT: I fed this post to the Google AI thing that generates podcasts out of your material. Yes, I’m writing the lyrics, you guys (who are by the way unaware of the fact that they’re AIs themselves). Check it out.

AI podcast about Alma: a Successful Case Study

Back in 2021 I wrote this short story about a therapist and his troubled patient named Alma. Man, 2021 was one prolific year. Anyway, I’ve presented this tale to the Google AI thing that generates podcasts out of your source material so the pair of hosts would do a review. Check it out.

You can read the entirety of this story on here:

Alma: a Successful Case Study, Pt. 1
Alma: a Successful Case Study, Pt. 2

AI podcast about Odes to My Triceratops, Vol. 3

This weird thing that Google has released automatically creates short podcasts based on any source of information you give it. I suppose it’s quite useful for serious purposes, but I’ve fed it all my posts about the AI-generated songs I made for the third volume of Odes to My Triceratops. Listening to these almost perfect AI voices talking realistically about my stuff is really eerie. They made a couple of mistakes assigning lyrics to their proper songs, though.

Anyway, I thought this was cool.