Previewing lyre, a narrative scripting system for quality-based narratives

Yeah, I am much more in the camp where I think it’s valuable to supply something that needs as little purpose-built tooling as possible, including working with whatever text editor a user prefers. This comes from me personally disliking both VS Code and purpose-built IDEs, which most narrative scripting tools have standardized on these days; I want to be able to use Vim, and I want other people who prefer, say, Helix to also have a good experience.

So it’s important that to me that you’d be able to write a syntax-highlighting parser for the language from scratch without tearing your hair out; it has value to be able to ship a tree-sitter parser alongside the “real” language tooling; and it’s important that you not rely on highlighting to spot places where the parser might miss a tricky disambiguation.

What I’m toying with new though is a syntax where storylet, from, branch, and quality declarations allow a “barestring” title, similar to YAML, which is terminated by a newline:

quality grue_risk: Risk Of Being Eaten By A Grue

They also allow double-colon syntax to go right into a multiline body:

storylet start::
    I'm in danger of being eaten by a grue.

    If only I hadn't sold my lantern...
    --- // A terminator is still required.

Ending a title template string with :\n creates a body:

storylet start: Gruesville:
    I'm in danger of being eaten by a grue.
    ---

It’s also possible to say that this colon always represents a single-line title then a body, thus getting rid of the second colon:

storylet start: Gruesville
    I'm in danger of being eaten by a grue.
    ---

But then any entity without a body would have to use a quote. This may be a reasonable compromise.

branch "Run away!"
    // No body here

Trying to automagically determine whether the next line is body text or not might work if (like in loreline) there was a strict, small set of possible tokens that the next line would start with. But that would just be moving the delimitation problem. Right now (in this colon syntax) you can write:

storylet start: Beginner Forest:
    Wow, it's like the starter area of an RPG!
    ---
    image: "beginning.jpg"
    initiation!
    orb_frotzed = true
    yak_shorn = true
    spline_reticulated = true

Which is to say, a line of “code” can start with an arbitrary identifier and can look like basically anything. Whereas if we wanted to make loreline-style lexing reasonable, it’d have to be:

storylet start: Beginner Forest
    Wow, it's like the starter area of an RPG!
    data
        image: "beginning.jpg"
    effects
        initiation!
        orb_frotzed = true
        yak_shorn = true
        spline_reticulated = true

Which I don’t really think is a huge win in terms of conciseness, readability, or typing less.

From my experience playing around with it, there were occasional moments where I relied on the syntax highlighting to realize that something I wanted to be plain text was actually being interpreted as code. But I also haven’t really tried using it without the syntax highlighting (I was mainly using the VS Code extension).

I definitely think these look like substantial improvements to me.

One thing about colons vs. quotation marks is that even if it’s not fewer characters, I find the colon considerably easier to type. Moving the pinky horizontally in the way that’s required to hit the " key is fairly uncomfortable. I also find that whitespace is in many cases a more readable delimiter than a character that buts right up against what it’s delimiting, like quotation marks do.

Also, to me “concision” heavily includes better use of vertical space, so getting rid of delimiters that take an entire line, like the old “”", is definitely a noticeable improvement to me on that front.

My rationale is that if you are using a plain text editor without any syntax highlighting and without any code completion, you could for sure not notice you wrote an expression while you expected it to be prose, but this is not a problem specific to Loreline. The same is true for another stricter syntax, where you could forget a colon or another delimiter, breaking the code. In both cases, you would notice it when trying to run the script, either by getting an error or more generally by getting an output different from what you expected.

It’s more the ambiguity that I’m concerned about. Maybe once you’re very familiar with the language it would be less of an issue, but the idea that something could look like a perfectly normal sentence, but be interpreted as code for non-obvious reasons, is a little bit annoying. Not too much, mind you.

Sure. The price has to be paid somewhere. A necessary means to an end regarding Loreline :smiley:

For the record (because it’s mildly relevant here), the reason why I raised a github issue about the choice introspection features in Loreline is because I was trying to use them heavily to make some sort of pleasant-to-use storylet/QBN system, which is something that I’ve long been trying to find a nice way to do in Ink. Although I think I may have finally more-or-less solved it in Ink recently.

This second option is pretty nice to read because you can more easily visualize the blocks, but really that’s very subjective opinions anyway. The best language is the one you want to use!

Yes, your issue has been fixed a while ago, but forgot to close it, just did now… The next update of Loreline should be very helpful for storylet/QBN systems anyway (but I have hijacked this thread enough :sweat_smile: . I’ll open another one when I want to share more details about storylets and saliency in Loreline).

Oh, nice!

I am very interested in this as someone who loved StoryNexus and now works almost exclusively in Ink & Godot - when this is stable enough for new developers I’d love to run a game jam with the Cryptic Conservatory crew

I like the feel of the quality changes at the end of the section rather than the start: back matter vs. front matter seems like such a small change but it reads so nicely.

Yeah I think where I’m sort of landing is that all of these are valid ways to start a body or a title:

storylet start: Title

storylet start:: Title
    Body
    ---

storylet start::
    # Title
    Body
    ---

With one special case for niceness, which is that the -> arrow, at the start of a new line, is also interpreted as the end of the body (and an arrow)

branch:: Body
    Title
    -> next

branch:: Body
    Title
    -(x > y)-> divert
    -> next

The reason for the -( syntax, which makes it look a bit like an arrow with stuff written on the haft, is really that I think lexing a plain ( at the start of a line is way too likely to create confusion with actual normal text. One possibility might be to change it so the conditional guards on arrows are like | expr | instead of (expr), though the nice thing about this “tagged arrow” version is that the parser doesn’t have to know about it (the lexer just emits a normal paren) and the syntax for an arrow statement can make the parens optional outside of this body context. But I think really that more special-casing of arrow syntax after bodies (beyond just the basic case of a plain arrow) needs to wait for some semantic tweaks to how branch routes work anyway.

I like the tagged arrow

Would you envision requesting environment providing an indicator of the desired outcomes, and then the sorting algorithm using that? i.e. the storylets themselves don’t match that desired outcome as a condition?

I think down the line it may be possible to extend lyre to do these things. It’s plausible the language could end up with some API for doing static analysis for effects (likely as a side benefit of implementing a proper type checker). So you could very reasonably write a function that asks “for this set of qualities S and this set of operators ζ, show me all qualities with ζ S effects” and hook that up.

The internal representation of expressions in lyre is as s-expressions (represented as js arrays); lyre doesn’t generate code for a “virtual machine” in a conventional sense. Effects are just sexps shaped like (+= quality_id 10), which the compiler bundles inside essentially a flatmap and evaluates within a monad that carries the world state with it. So it’s very easy to write code to semantically query the effects of a storylet by searching inside that structure. It’s also plausible to just evaluate the effects of storylets to check; lyre is designed to do that without mutating the existing state, so it can easily look ahead at future effects.

That would be a nice feature indeed. Need to keep this in mind for Loreline, I like that.

In my current project I’ve been treating it as an actual hard requirement that selected storylets must satisfy the current “balancing query” (which is usually something simple like requesting a specific skill check or “risk of injury” or something), and the storylets themselves check whether they qualify at the time of evaluating their conditions, because that seems to be what’s most convenient for me in Ink. But there could be other approaches to the same approximate goal.

EDIT: To clarify, the bit that would actually need to be built in to the language would be automatically detecting and categorizing the possible effects, like Bruno describes above. My current project relies on me manually tagging the storylets with all of their possible outcomes, which works but is annoying.

I can see that!

It would be hard for me to implement this in MQBN any other way, because Storylets load passages by default, and therefore don’t contain any information about what side effects they may have (a storylet loaded passage can load other passages, or have a whole chain of navigation before the next storylet, so there’s really no way to analyse that).

So the Storylets would definitely need internal tagging as to their effects.