Guitar practice in the woods (2025-05-23)

This morning my new field recorder, a Tascam DR-40X, arrived. On the afternoon, I hurried to the nearby woods, where I usually go to play in relative quiet, to test the Tascam. Which works great for my purposes, except for the goddamn wind sensitivity. I’ll have to buy one of those deadcat muffs.

Anyway, here’s about an hour of me playing the guitar and “singing.”

Guitar recording test

I bought a Tascam DR-40X to record myself playing the guitar in the woods. I’ve recorded myself at home playing a couple of songs. Unfortunately the Barcelona jungle left me with a disease of some kind, and I’ve been expelling mucus, coughing, and shitting near liquid for this last week. So don’t expect anything resembling proper singing in this video.

Anyway, the Tascam works wonders at home. I suspect the wind will screw with the quality, but that’ll be a matter of buying one of those fluffy cap things. Without further ado, the video below features me playing my Alhambra guitar and “singing.”

Life update (05/11/2025)

Two days from now I’ll be in Barcelona, on a days-long trip for which I’m not in the mood. It’s supposed to involve research for a writing project I’m supposedly working on, although I haven’t written anything in a month. Barcelona is a beautiful city. Unfortunately, it’s also a crime-ridden shithole. I expect to feel anxious from the moment I step outside of the rented apartment.

I haven’t been in the mood for much recently. I may actually be having a mid-life crisis, although I’m past the midpoint of my life; now forty, and very unlikely to live to eighty. I keep fantasizing about dropping everything and moving away to some cheap town, to a one-bedroom place near nature, where I could live in peace while working part-time at the most. If that ever happens, I’ll likely be in my late fifties, or sixties. Mainly, I want to get away from everything and everyone I’ve ever known. Unfortunately, due to my brain configuration, my intrusive thoughts keep reminding me of every terrible little thing that has ever happened to me. I can’t flee from that.

A song came to mind: Jackson C. Frank’s “Blues Run the Game.” Jackson was a well-respected songwriter in the sixties and seventies. When he was twelve or thirteen, during music class in middle school, the school’s boiler exploded just under them. Jackson survived with half of his body burned. His girlfriend, Marlene, burned to death. In spirit, Jackson died that day, although it took his body decades to catch up. He wrote one song directly about his dead twelve-year-old girlfriend (“Marlene”), although obviously most of his songs are tinted by what happened. In the seventies, Jackson lived in England, and dated a then-famous musician named Sandy Denny. Shortly after they broke up and Jackson returned to the States, Sandy fell down the stairs of her home and died.

Jackson went crazy, likely out of PTSD and depression. He couldn’t find in himself to produce a new album, and he couldn’t get the first album reissued, as Paul Simon, who held the rights, wouldn’t do so. Jackson ended up homeless in NY. A fan sought him out and offered to house the songwriter and help him revitalize his career. As Jackson was waiting on a bench, some hoodlum shot out one of his eyes with a BB. Jackson died maybe one or two years later from a disease.

Here’s to you. Creating art can’t save anyone, but at least it captures what needs to survive.

Life update (05/05/2025)

These days, my beloved guitar satisfies my emotional needs. I head to nearby wooded areas to play. This Saturday, I had walked to one of my favorite spots: in front of a huge tree, on a relatively unknown trail. As I was playing through Joanna Newsom’s “Kingfisher,” suddenly I heard someone hollering. I tensed up, but didn’t look up until someone threw his voice at me, interrupting someone who unequivocally was playing an instrument. I raised my gaze to the grotesque sight of a topless gypsy holding a dining room chair over his head. Of course this fucking mongoloid had to talk to me as I was playing the guitar. He asked if I played rumbas. I told him I didn’t know what that was. He then said that it was flamenco. I told him no. Shortly after, he hollered back to someone to following him, then continued on his way, likely to drink and leave the bottles and other litter there. A couple of other people, presumably gypsies although I couldn’t tell, followed in silence. One of them was a young woman. I got the feeling they felt a bit embarrassed. I finished Joanna Newsom’s “Kingfisher” to the best of my abilities, and then packed up my things and left.

People don’t learn from history; a well-known fact. If we did, we would have learned from the fall of the western half of the Roman Empire, and would have realized that some terrible mistakes should never be repeated: first, don’t convert to Christianity. Second, don’t share your civilization with barbarians. You may enjoy diversity on your plate, until someone shits on it, and then the whole plate is ruined. As for me, I’m not remotely a diversity enjoyer: I want everything in its right place.

Anyway, I suspect that such an encounter with one of the locusts of society would have dissuaded me for a while from playing outside, but the very next day, at about half past three in the afternoon, I picked up my guitar and headed to the deeper woods (in the opposite direction from the other woods). First I headed past the Roman foundries (a reminder that we used to be the city of Oiasso), but the place I picked to play, close to the river, obviously interfered sonically with my playing, so I picked up my things and ended up setting up shop on a raised area next to the foundries. I had only come across a pair of women on my way there, so I thought the afternoon would be quite tranquil. However, I found myself playing songs for older couples and families with children, who stopped to record the foundries, and also ventured deeper into the woods. These people were civilized, so the only interruption I got was three tweens clapping at me as they walked past. Guitar-playing impresses girls, I guess.

When I was in middle school, I remember an instance in which I had to read some essay in class, and I was so nervous, as usual, about speaking in public that my hand shook to the extent that you could hear the rustle of the paper I was holding. Now I casually play the guitar in front of strangers. I’m not entirely comfortable in front of people, of course; I never am even in the best of circumstances. But my concern is that someone may mess with me or even attack me. I don’t feel any genuine connection with human beings, so it’s quite similar to how I’d feel if a deer suddenly stopped to listen. I’d also worry that it may flip out and charge at me, offended at some aspect of my playing. Sadly we don’t have deers around.

Well. Five more days to go, and my vacation starts. I’m heading to Barcelona. Not really in the mood for it, but it’s writing-related, so I’ll have to endure through plenty of aspects of that city that no doubt will infuriate me.

Life update (05/02/2025)

This morning I woke up rattled from a nightmare. I suppose most people’s nightmares involve being physically attacked or pursued, but in my case, my worst nightmares are about ceasing to understand. As far as I remember, most of last night’s dream was like that, but the part I remember the most involved a meeting with my boss and two other coworkers. I wasn’t able to follow their conversation, nor couldn’t understand my boss’ icy attitude toward me. Then he asked me something about a suitcase (that may have been an expression, but the details have slipped through my fingers). I sat there trying to comprehend what he was asking, while my coworkers and my boss looked at me with a mix of disappointment and irritation. I asked, “What does that mean?” My boss looked pissed at my stupidity or ineptitude. Then he asked me if I had done the “context packet,” or something similar. I said that I had no clue what he was talking about. He became irate toward me. When I tried to defend myself, without getting particularly agitated, I was accused of being unable to control myself.

As usual, a mere recounting of a dream doesn’t properly transmit the experience, that of sitting there in that dream office trying my best to understand what was being demanded of me, and yet failing to do so. That’s not far from my every day experience living in the world as an autistic man. In fact, most meetings serve as reminders that my brain doesn’t work like other people’s, as most of the exchanges feel like non-sequiturs to me. I’m usually waiting for the part when someone specifies what needs to be done.

It doesn’t help that I have experienced such moments of my brain failing to comprehend the world, mainly through my experience with migraines. I’m still not convinced that my last one wasn’t a mini-stroke. Back in April of last year, my then boss put me in charge of organizing the replacement of about nine hundred printers throughout the hospital complex where I work. It was a fucking nightmare. Near the end of it, during a day in which I was also hit in the balls by the careless Gen Z worker I had to deal with at the time (he told me a couple of times how eager he was to get back home and play some more Fortnite), I suffered a hemiplegic migraine: suddenly, I started having trouble understanding what I was looking at. Then I smelled something like burnt dust. The right half of my face, and then my right arm to my fingertips, went numb. I ended up in the ER. Three weeks or so later I had an MRI done, but they discarded brain damage. However, I’ve read online that some strokes don’t show up on an MRI. I’ve experienced trouble writing coherently: I sometimes skip letters or mix them up, but I’m not sure if that wasn’t happening beforehand. Maybe it’s just part of the general decay. In any case, one of my biggest fears is suffering a stroke that renders me incapable.

I turned forty about a week ago, and that made me think back to my experience with people over the decades. Growing as a human for me has meant becoming increasingly aware of how much my brain lacks when it comes to social processing. I see myself back as a child, hunched over and drawing because I couldn’t relate to anyone around me, and couldn’t even keep a conversation going for a minute without feeling lost. Of course, when I became a teenager, the problems grew tenfold. My intimate relationships always ended up hurting others as well as me. And I lack the sense of connection with human beings that is generally referred to as “empathy,” so it would be unfair for me to try to get close to others, which in the past I’ve done mostly for curiosity or for writing-related purposes. I do fantasize about intimacy, and I don’t mean just sex, but I guess I’ll have to wait for reincarnation, or incarnated AIs.

Not much else to say beyond these semi-random thoughts. I’ve been busy programming my platform for text-based immersive sims, which is a challenge I’m eager to tackle every day. Whenever I go outside, it’s almost exclusively to delve into a wooded area and play my beloved guitar. If you’re into playing string instruments, you know how much your calloused fingers yearn to return to those strings, to immerse yourself in the emotions captured in the songs, each a unique spell. Playing through Joanna Newsom’s “Kingfisher,” for example, puts me in a trance that snatches me away from this lackluster world into a better place full of meaning.

The wooded area I head to most times is almost unknown, located by the side of an incline road heading into the hilly depths of the province; in the Basque Country, the moment you start heading uphill, it’s like going back in time, and you’re bound to come across very few people, if any at all. The last four or five times I went to play at my usual spot, I only saw one person, and he freaked out when he suddenly noticed a guy sitting there in silence with a guitar (I was about to start playing a song).

Anyway, only six days of work to go, and then I’ll enjoy two weeks of vacation. I hope that along the way, I manage to snatch my one-track mind back to writing; the longer I stay away from it, the more unhinged I feel.

Life update (04/29/2025)

I’m now a forty-year-old man, which is one of the things that happen when you turn forty. When I was in my teens, I thought I wouldn’t make it past eighteen. When I hit rock bottom at about twenty-one and I intended to exit this life through the emergency door, I didn’t think I would see that afternoon. And now I have gray hairs in my beard. It hasn’t been a “glad I stuck around” kind of deal; I’m not too happy about being alive.

Anyway, my goal for my forties is to become even more emotionally and physically independent from human beings. My thirties, that included years of working, showed me that all non-necessary interactions with humans, including listening to their grating voices and sounds, as well as their inanity, can literally send me to the ER. I had two episodes of arrhythmia, and then an even scarier hemiplegic migraine, the three of them triggered by stress. Around that time I also experienced a torn retina, although I don’t know to what extent I can fault stress or the health issues I was experiencing at the time. The point is, any extra interaction with humans can ruin me in potentially permanent ways, so to the extent I can get away with, I won’t look people in the eye, and I will wear my noise-canceling headphones to drown out the world’s nonsense. I have to respect my brain’s peculiar needs instead of conceding to other people’s.

Next month I’m going on a trip to Barcelona. The funny thing is that the trip is related to a story I’m writing; I intended to do some research. But I haven’t been writing at all these past couple of weeks due to my sudden obsession with developing a program. I hope to return to it soon enough; I have been feeling my mind deteriorating, becoming increasingly unhinged, which always happens when writing doesn’t ground me. Also, I miss hanging out with Elena.

Speaking of hanging out with non-existing people: I still have daily daydreams about going on time-travel-related adventures with a certain Alicia Western. Most days I don’t even open the ebook reader or my tablet; I just close my eyes and run scenarios in my head. In one of the most recent daydreams, I introduced Alicia to the wonders of augmented reality through a headset made in the 2030s. The headset comes included with an advanced AI named Hypatia, that helps Alicia with her mathematical research.

I don’t know if I intended to say anything else. Barely anyone reads my posts anyway, so this is pure self-expression.

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.

Neural Pulse, Pt. 11 (Fiction)

In an electric flash and crackle, my muscles seized, and my vision flared white. As I crumpled backward like a dead weight, my left arm and the side of my head slammed into the control panel. My brain thrummed with electricity. It reeked of burning.

In the whiteness, the silhouette of a spacesuit materialized, looming over me. Several shadows clamped onto my arms with claws. One shadow dug its knees into my abdomen and crushed my face between its palms. I tried to scream, but only a ragged whimper escaped my throat. The tangle of shadows obscured my sight, swallowing me. A shadow snatched my hair and pulled; hundreds of points on my scalp prickled tight. Another shadow smothered my nose and mouth.

When I could feel my arms again, I lashed out at the shadows, thrashing as I braced myself against the control panel and the seat. I lunged for a silhouette—Mara’s spacesuit—but she sidestepped, and I plummeted onto the cockpit floor. A blow to the crown of my head plunged me into a murky confusion.

My wrists were bound behind my back—duct tape, I glimpsed, as Mara, crouched by my knees, finished wrapping my ankles. She straightened and hobbled backward. She stepped on the electroshock lance lying discarded on the floor and slipped, but the oxygen recycler clipped to the back of her suit arrested her fall as it struck the hatch.

Gauges of different shapes bulged on her belt like ammunition magazines. The suit’s chest inflated and deflated rhythmically. Mara unlatched her helmet and pulled it off, revealing her ashen face: mouth agape with baby-pink lips; livid, doubled bags under her eyes; strands of black hair plastered to her forehead with sweat. She leaned back against the hatch, gasping through her mouth, the corners glistening with saliva as she scrutinized me with intense, glazed eyes.

The cockpit reeked of sweat and burnt fuses. The shadows had congealed into a mass of human-shaped silhouettes, their hatred addling my brains, boiling me in a cauldron. Mara’s outline, as if traced with a thick black marker, pulsed and expanded.

No more anticipating how to defend myself, because I have you trapped. Thanks to you, the station doesn’t know we came down to the planet. With the tools of the xenobiologist you murdered, I will rip out your tongue, gouge out your eyes, bore into your face.

Mara crouched, setting her helmet on the floor. Exhaustion contorted her actress-like features, as if some illness burdened her with insomnia and pain.

“I thought I was marooned on this planet. I could have just called the station for rescue, but they’d fire me for nothing, and my pride would rather I suffocated than admit I needed help. Now I know—when we found the artifact, I should have tied you up then. Because you, being you, would just stick your nose right up to an alien machine that, for all you knew, could have detonated the outpost. And to understand what drove you to kill that xenobiologist, I imitated you. I stuck my nose up to that thing, and I saw my reflection. Now I know. Unfortunately, I know.” She regarded me like a comatose patient and waved a gloved palm. “Can you hear me? Did I scramble your brain?”

“I hear.”

My voice emerged as a rasp. I coughed. My mouth tasted of metal.

“And you understand?”

I nodded.

The black veil obscuring the cockpit stirred, rippling. Concentrated energy, like the air crackling before a storm. With Mara’s every gesture, the shadows shifted. Their bony claws crushed my thighs, cinching around my spine through suit, skin, and flesh.

A bead of sweat trickled down Mara’s forehead. She rubbed her face, swallowed. Her pupils constricted.

“Is that what you think? That I’ve convinced myself I’ve subdued you? That you’ll fool me until I let you go? That then you’ll finally strangle me? And even if the station calls it murder, no one will bother investigating, because most people who knew me would thank you for killing me.”

“I’m not thinking. When I try, my brain protests.”

Mara hunched down opposite me, reaching out to study the blow on my head, but halfway there her features pinched. She drew herself up, crossing her arms.

“I heard you telling me to come closer. Because you’ll break free, dig your nails into my corneas, and rip my jaw apart.”

My guts roiled; acid surged up my throat.

“You think I think things like that?”

“I feel this second consciousness… it betrays your thoughts as clearly as if you spoke them aloud. Maybe I’ll never understand how the artifact interfered with our minds, not just our language, but it’s a trick.”

I pushed my torso off the floor, sliding my back up the side of a seat inch by inch, trying not to provoke her, until my stomach settled. My head ached where she’d struck me. The throbbing in my skull clouded and inflamed my thoughts.

“You saw him. Jing. What I did.”

“I saw someone down there. I’d need dental records or DNA to be sure, but I trust elimination. I thought you’d claim it was an accident.”

“It was. I attacked the shadows. You feel them, don’t you?”

Mara took a deep breath.

“They’re pawing at me, trying to suffocate me. Products of my own besieged brain, I know, but I can hardly call them pleasant.”

“I wanted to keep it from affecting you. But at least now you understand.”

“Make no mistake. That xenobiologist is lying with his face beaten to a pulp in the second sublevel of an alien outpost because you are you.”

I pressed my lips together, erecting a wall against escaping words. I looked away from Mara’s eyes, concentrating on deepening my breaths. The muscles in my forearms were taut. Pain flared in my constricted wrists. This woman had fired an electroshock lance at me, beaten me, bound me, and now she was assaulting my character.

With her boot-tip, Mara nudged her helmet; it wobbled like a small boat.

“Although the jolts in my neurons, the shadows, and this other consciousness intruding in my mind unnerve me, the effect isn’t so different from how I’ve always felt around people. The two consciousnesses will learn to get along.”

“If you’re not exaggerating,” I said gravely, “I am truly sorry, Mara.”

She pushed damp strands of hair from her forehead and scrubbed it with the back of her glove, smudging it with dust. The corners of her lips sagged as if weights hung from them.

“Thanks for the sympathy.”

“Were you afraid I planned to do the same thing to you as I did to Jing?”

“Can you blame me for removing the opportunity?”

She limped heavily over to my seat and sat down sideways. As she leaned an elbow on the control panel, a shadow shoved my torso against the seat I leaned on; my lungs emptied. I shuddered, sinking into black water.

Mara had said we imagined the shadows, even if they affected us. I writhed onto my back, pushing with my heels until my head touched the cockpit hatch. My wrists throbbed, crushed tight. A shadow pressed down on my chest like someone sitting there, yet no physical presence had stopped me from moving. The artifact had hijacked my senses.

Mara regarded me from above, pale and cold like a queen enthroned.

“I wouldn’t have killed you,” I said. “You’re my friend.”

“Am I?”

Between the pulses of my headache, I tried to decipher her expression.

“To me, you are.”

“I like you. I tolerate you. But often, being around you feels like rolling in nettles, Kirochka.”

“Almost everything irritates you.”

“You’re incapable of seeing people as anything other than reflections of yourself. What you instinctively feel is right, you impose as right for everyone.” She shook her head, then leaned forward, her tone hardening as if she were tired of holding back. “You insist you have to drag me away from my interests, my studies, as if imitating your actions and hobbies would somehow make me impulsive and reckless too. Admit it or not, you think the rest of humanity are just primitive creatures evolving towards becoming you.” She jabbed a finger at her chest. “I need time to myself, Kirochka. Solitude. Reading. Designing one of my machines, or building it. You think people need to be prevented from thinking.”

Exhaustion was crushing me. I imagined another version of myself laughing, suggesting a drink or a movie, assuming Mara’s mood could be cured by a few laps in the pool. But my vision blurred. I blinked, swallowed to make my vocal cords obey.

“We’ve had good times.”

“The best were when I was enduring idiots and tolerating awful music.”

“You showed them you’re smart. Got half the tracking team to stop calling you ‘black dwarf’.”

“Yes, because those morons’ gossip was costing me sleep. You think I need to prove anything to them? They can believe whatever they want.”

Shadows crouched nearby, focusing their hatred on me, clawing at my skin, crushing my flesh with bony grips. They tormented me like chronic pain, but while Mara and I talked, I kept the torture submerged.

“Things went well for you, for a while, with that man you met. I don’t take credit, but would you have met him dining alone?”

The woman, deflated, blinked her glazed eye, rubbing it as if removing grit.

“You’re right. I miss things by focusing on research instead of acting like a savage. But I assure you, Kirochka, we’re too different for me ever to consider you a friend. Sooner or later, we’d stop tolerating each other.”

“We can bridge the differences.”

“You talk to fill silences. You pressure people for attention. You live for interaction. I could never sustain a friendship with someone like that.”

“Do you use me to get things?”

“Everyone uses everyone, if only to feel better about themselves. I just refrain from feeding illusions.” She drew herself up, as if recalling an injustice, and rebuked me with her eyes. “Besides, I didn’t stop running because I was lazy. I barely eat, and nobody’s chasing me in my apartment. Running bores me to death.”

“I wanted the company.”

Mara shook her head. Her tired gaze roamed the cockpit, as if seeing through the walls.

“When you called a few hours ago, I thought you wanted to drag me out drinking with you and the other pilots. I considered pretending I’d fallen asleep with the sound nullifier on. I should have.”

I contorted like a snake, sliding my back up the hatch. I leaned the oxygen recycler back, resting my head against the cool metal. Judging by the ache, when I undressed, my arms would be covered in lurid bruises.

“I consider you a friend. You listen when I need it. My professional peers, the ones who think they’re my friends, even my boyfriend—they’d tell me to shut up for ruining the mood.”

“When have you ever listened to me?”

“I want to. But I have to pry the words out of you.”

“Maybe that should have told you something, Kirochka.”

“That you hate me.”

She sighed, the effort seeming immense, like lifting a great weight.

“I don’t like human beings. I would have chosen to be anything else.”

Flashes on the communications monitor distracted me. Though Mara was still speaking, her words faded to a murmur beneath my notice. The headache pulsed, reddening my vision. Why did the monitor alert snag my attention? I snapped fully alert. It meant an incoming call.


Author’s note: I wrote this novella in Spanish about ten years ago. It’s contained in the collection titled Los dominios del emperador búho.

Today’s song is “Body Betrays Itself” by Pharmakon.

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.