Sharpee - v1.0.0-alpha.1

I started having Claude write the blog posts, but I usually have a detailed prompt and context available to direct the post. Then I read through it and clean up what I don’t like (Claude can be very enthusiastic and that’s irritating).

I always ensure the posts are inline with the work and the code and my intent.

I’m just not especially interested in spending my time reading what an LLM has to say about new IF platforms. If no human cares enough to talk about it, that tells me I shouldn’t care either.

I could have ChatGPT read the blog posts and generate a response conveying my hypothetical feelings on them, but…what’s the point? What does that accomplish?

13 Likes

I’m a good coder, but I suck at unit testing outside of identifying what to test and how. The first time I asked Claude to write the unit tests, it was pretty bad at it. But as I’ve said in other places, Opus was the leap forward that made a massive difference in code quality.

I have also stepped through dozens of tests myself searching for design flaws, so I’ve seen first-hand what Claude is producing.

Am I 100% confident, no. I expect any issues to shake out in creating games. The architecture is sound. That I am 100% sure of.

It might get a little verbose here and that’s fine, but if anyone wants to directly chime in, I’m on ifMUD and there is a #sharpee channel.

The LLM wrote it at my direction and I edited it. If that’s still a bridge too far, I cannot help you. I’d also note I have zero interest in debating how and when to use an LLM or if anyone feels they should never be used. That’s a different debate and I recently posted a blog entry (written entirely by me!) about my thoughts about that. To summarize:

  • LLMs and art == very evil
  • LLMs and narrative text == stupid
  • LLMs and short administrative text == fine
  • LLMs and coding == groundbreaking

Please folks. If you want to talk about the underlying architecture, I’m open. But if the discussion turns to anti-LLM stuff, I’m not your guy and have direct results proving the severe negativity wrong. It can write clean code. It can handle complexity. It can write good unit tests. That was half the reason I have engaged in this project and spent 2000-3000 hours of my time on it, leveraging decades of software development experience.

This was neither possible nor impossible with Claude, but it was FASTER.

4 Likes

I’m interested to see what kind of games you can make with it – until you have one I don’t think there’s too much to discuss beyond the LLM use and merits thereof.

8 Likes

100% agree. That is the next phase of development. Building a larger game, creating the client, and publishing it.

1 Like

Honestly I’d be interested in seeing a smaller game first – not quite Cloak of Darkness size, but something you might see in the Ectocomp Petit Mort division. (Without the time limit though, unless you want to.)

3 Likes

I’ve had this one story (Reflections) in my WIP list for years. It’s mostly written and I know the beginning and ending. It is small. That’s the plan. I might even get it done by the IFComp deadline.

3 Likes

Agreed - although it couldn’t be entered in Ectocomp I think which has a zero AI rule this year. However something short ish would be good. And with source code please.

@DavidC your linked blog post says:

I submitted a story for IFComp. If I can get Sharpee working in the next week, I think I can get the code for my story done fairly quickly (using Claude). The story itself has been written for years, so implementing the text and puzzles is trivial. If not, I will still get a story published before the end of the year. The likely output will be WebAssembly, which can run anywhere from one file.

Is that game going into IFComp 2025? In other words to be finished and ready in the next week? Or are you looking further ahead?

I’m not clued up on the implementation/architecture side of things. Will a WebAssembly game be playable under MacOS X though? That’s what I’d need. Thanks.

I have expressed my views on AI elsewhere. Very not keen. However I wish you well with your system. But yes, we need a proof of concept game, even a short one. Not just Cloak of Darkness. Good luck!

1 Like

WebAssembly seems to be the best IF version of a cross-platform client, so that is my primary target.

1 Like

Not necessarily a bridge too far or anything. I’m honestly a lot less anti-AI than most of the forum. It’s just…

To me, having an LLM make your initial release announcement for you conveys a fundamental lack of interest in communicating about the project. I’d love to read your thoughts on designing a new IF tool, but I’m not interested in Claude’s thoughts on that, for the same reason as this:

Fundamentally, LLMs can produce code and all that, I have no doubts there—but I also have no interest in trying to communicate with one when it has no internal experience to be communicated. In my opinion, trying to actually communicate with an LLM—trying to discuss my experiences in a way that other people can understand—is an exercise in futility.

So I’m not interested in reading LLM-produced blog posts and announcements for the same reason that you’re presumably not interested in me copy-pasting those posts into ChatGPT, telling it to write a response, and sending that back to you. The reason I post on this forum is to communicate, and LLMs are fundamentally incapable of that.

I’d love to hear your thoughts on the advantages and disadvantages of a new system, ideally with some sort of demonstration attached, but only if they’re coming from someone I can actually hold a meaningful conversation with.

10 Likes

There are two kinds of blog entries. One is just an info dump of what’s been completed or problems I’ve run into. I let Claude write those. The second is design, architecture, and discovery. I write those.

If you read through the blog from the beginning, I wrote everything up until July 30th when info-dumps were easier.

But I think your point is taken. The info dumps can be in the repo. They don’t need to be blogged.

I’m interested in what all of the heavy engineering offers the story author in terms of ergonomics. For example, in most systems, you can update the description of an object by writing something like sponge.description = "It's wet." or Now the description of the sponge is "It's wet." In Sharpee, you have to query the world model by name for the sponge entity (which can fail), then query the entity for its IdentityTrait (which can also fail), then assign a new description to the IdentityTrait instance. You can’t output an ad-hoc message during action processing because output is supposed to be handled by the text service, so instead you have to spend seven or eight lines constructing and returning an event of type game.message.

Cloak of Darkness implementations usually seem to run to 100-150 lines. In Sharpee it’s more than 800. What’s being gained in exchange for requiring the author to write five times as much code?

6 Likes

The current story implementation uses the stdlib directly. This layer enforces structure and is not meant for casual story authoring.

I have continually tested a fluent layer on top of stdlib that would dramatically simplify the coding requirements.

Something becoming wet would be as simple as adding a custom WetTrait to the entity. You’d already know the ID and should be able to resolve by that.

The Text Service would now understand and report the state correctly.

I would like just to note, that usually the long-description of the rooms is a routine instead of constant text. Constant text can be replaced easily, but routine allows much more, including printing an optional, variable text based on circumstances. This way is used in ADL (Adventure Definition Language), as LDESC is a special routine ID. In AdvSys the routine can be also inherited from parent class, thanks to the object-oriented model.

We’d probably update IdentityTrait as such:

interface IdentityTrait extends ITrait {
  type: TraitType.IDENTITY;
  name: string;
  description: string | DescriptionProvider;
  firstTimeDescription?: string;  // Special first-time text
  examined?: boolean;  // Track if examined before
}

and use DescriptionProvider to handle special cases:

interface DescriptionProvider {
  type: 'static' | 'random' | 'conditional' | 'function';
  
  // For static
  text?: string;
  
  // For random
  variations?: string[];
  
  // For conditional
  conditions?: {
    condition: string;  // Reference to a check function
    text: string;
  }[];
  
  // For function (loaded at runtime, not serialized)
  provider?: (entity: IFEntity, context: any) => string;
}

…which would look something like…

const painting = {
  traits: {
    [TraitType.IDENTITY]: {
      name: 'old painting',
      description: {
        type: 'random',
        variations: [
          'The painting depicts a stormy seascape, waves crashing against jagged rocks.',
          'Looking at the painting, you notice subtle details in the brushwork suggesting hidden depths.',
          'The painting seems to shift slightly as you look at it, the waves almost appearing to move.',
          'Dark clouds dominate the painting\'s sky, with a barely visible ship on the horizon.'
        ]
      }
    }
  }
}

..or..

const mirror = {
  traits: {
    [TraitType.IDENTITY]: {
      name: 'ornate mirror',
      description: {
        type: 'conditional',
        conditions: [
          {
            condition: 'is_broken',
            text: 'The mirror is shattered into a spider web of cracks, distorting your reflection into a thousand fragments.'
          },
          {
            condition: 'is_dirty', 
            text: 'The mirror\'s surface is covered in dust and grime, showing only a vague outline of your form.'
          },
          {
            condition: 'default',
            text: 'The ornate mirror reflects your image clearly in its polished surface.'
          }
        ]
      }
    }
  }
}

..but this is just a swag. These are the kinds of “tree-shaking” things that still need to happen.

There are a number of ways to implement this (which is good). This is one of them with a custom DIPPING action (not shown):

const rag = world.createEntity('rag', 'item');
rag.attributes.isWet = false;
rag.attributes.descriptions = {
  dry: 'A clean, dry rag.',
  wet: 'A soaking wet rag.'
};

rag.on = {
  'if.event.dipped': function(event) {
    if (event.data.targetIsLiquid) {
      this.attributes.isWet = true;
      this.get(TraitType.IDENTITY).description = this.attributes.descriptions.wet;
    }
  }
};

We do indeed update the entity’s description directly based on an event.

Another example:

const painting = world.createEntity('painting', 'item');
painting.attributes.descriptions = [
  'A stormy seascape.',
  'The painting seems to shift.',
  'Dark clouds dominate the canvas.'
];

painting.on = {
  'if.event.examined': function() {
    const identity = this.get(TraitType.IDENTITY);
    const descs = this.attributes.descriptions;
    identity.description = descs[Math.floor(Math.random() * descs.length)];
  }
};

There is a potential need for a formal StateTrait that would allow the author to create sophisticated transitions, but you can look at things like that in the repo: docs/architecture/design.

The event processor, trait system, and Typescript’s ability to tack on properties is invaluable for solving problems like this.

(at some point, I can envision building a Claude API enabled tool to lets people describe a similar problem and have it generate implementation options similar to other playgrounds on the Internet).

So far, all of these examples look very unwieldy compared to any other IF language. You’ve been saying there’ll be a better interface for this eventually, but right now, the current interface is all we really have to judge by.

Do you have examples of things that would be easier with this system and harder (or impossible!) in Inform, TADS, Dialog, etc?

3 Likes

look at this potential cloak sample: