Rez v1.7.0 — open source language & engine for making procedural narrative games

Released v1.2.7 with binaries for macOS, Linux, and Windows.

Changes:

Bindings are now specified as a list where a binding can depend on previous bindings.
Prefix a binding value with * to auto-dereference ids (object or array)
Input bindings can now bind to scene & game

Stdlib adds:

Boolean.rand()
RezList.randomFromBag()

Filters:

char_at
2 Likes

Over the weekend I spent a little time working on a new authoring guide that is intended to come at things from a ‘how to get things’ done perspective and derived from the lessons I am learning as I build with it myself.

I’ve put up the first draft of Chapter 1 and I would be grateful for any feedback. There is a risk it may already be too fast paced and even that feedback would be useful.

Later chapters will build on the foundations and introduce more concepts.

2 Likes

This is really good! It’s so useful for people like me who know the basics of HTML/JS/CSS, but not many deeper programming concepts or languages. I haven’t installed Rez yet, but there was nothing that I didn’t understand. I felt like I was typing and creating a Rez game… without typing and creating a Rez game. :slight_smile:

The biggest thing I get from the tutorial is that Rez appears much less intimidating to me now. I can’t express enough how nice this is for newcomers.

1 Like

That’s great to hear, thank you. I’ll carry on with this in my spare moments.

The next chapter will go into more depth about structuring with scenes.

Then through some of the built in objects like inventory & items, actors & relationships, and so on.

Then likely covering events and where Javascript code plugs in to create dynamic behaviour.

Finally tee’ing up the cookbook and more advanced topics.

1 Like

Released Rez v1.2.8 for macOS, Linux, and Windows.

The main change for authors are:

  • Implements the new simplified link syntax

Instead of writing <a data-event="card" data-target="id"> you can write <a card="id"> and the link will get rewritten in the background.

Also supports <a scene="...">, <a interlude="...">, and <a resume>. To pass params just add data-foo="bar".

  • Improvements to event param handling

Params are now easily obtained via bindings.

  • Improvements to event handling

Event Handlers now return a RezEvent instead of a plain object with an API to specify how the event should be handled.

1 Like

There are still some rough edges here or there but I think 1.2.9 (which will come with a couple more bugs fixes) could be it. The true 1.0. I’m working with it now and finding it pretty nice.

The way bindings work now has made things a lot easier. The new link syntax. The ability to pass params between cards & scenes to make them more customisable. The flexible code blocks.

I’m not finding myself doing a lot of “well I wish I could …” thinking recently.

That is going to make it a lot easier to focus on documentation and examples.

3 Likes

I like the tutorial. Nicely done.

I find myself trying to relate Rez concepts to traditional IF concepts. This is going to be a thing, depending on whether the author is coming fresh into IF or has used previous systems.

For example, the concept of “player location” appears to be represented by the current scene card. So then i think, if you had an inventory, how would that work? Perhaps the next tutorial will cover this. hope so.

Then i think, in general, will Rez support a world model, and if so, how will that work. If it does, it would definitely be worth explaining how it relates to the way traditional IF systems work. For example, scope and all that.

good stuff.

2 Likes

:slight_smile: thank you!

We all have different histories here, what are the key concepts you see as traditional/expected?

In an early version of Rez, I had concepts for map & location but they didn’t sit well with the idea of scene & card, and the progression from card-to-card and scene-to-scene. As you observe “the player is seeing the current card, it’s where they are”.

In the end I realised that cards can fulfil all the useful properties of a location (esp since they can have arbitrary attributes) and if necessary I just add an attribute like location: :foo to tie together cards that “belong in the same place”. Assuming you need that.

In the game I am working on the players inventory is a separate @scene that you interlude to (an interlude allows you to jump to a new scene, then resume the original scene at the point where you left off. Interludes can also have interludes, I keep them in a stack).

A forthcoming chapter will definitely build an inventory.

In fact this is axiomatic to my approach. The @game is the model containing the actors, relationships, inventories, items, effects, factions, plots, and so on. With systems designed to help these objects dance around each other.

I guess I haven’t made that point clearly in the authoring guide and perhaps I should (but without getting too much into the mechanics).

Thanks for your thoughts.

Matt

2 Likes

It’s great to see how Rez is thinking out of the box. Of course, none of the traditional IF concepts need to be done in the same way, but only talked about. so people understand how the new way relates to the old way.

I had the same thing with locations in Strand. In fact strand doesn’t have locations as special objects at all. This created a problem when I wondered how would I implement something like “go to X”. Only recently I managed to get the system to deduce objects that are locations and their connectivity. Kind of working backwards.

A core feature of trad IF implementation is “scope”. Essentially this is the list of all things the player can currently interact. It’s needed for parser resolution. But maybe you don’t need it for choices. Something to watch, as it may pop up. I use it a bit also for clickable objects in text, in order to resolve. But in most cases, those clicks can use an internal ID, which is global.

Darkness is another irritating concept that’s part of trad IF. I decided to mostly ignore it and certainly not put it into the core system. The way you’d implement darkness in Strand is, when dark, to seed the scope with the player rather than the player’s location. This would make all things in the location unreachable, except for those on the player’s person. So there’s that scope thing again.

Other things popular in trad if:

  • doors
  • containers
  • surfaces (aka supporters)
  • cardinal directions
  • body parts
  • scenery
  • wearables
  • edibles

Well, all of these are in Strand, but none of them are part of the runtime code, only the DSL.

The question for Rez is whether any of these things are needed for the choice system. That’s because, as i understand it, Rez isn’t a pure choice system because it will have/has a world model, absent in pure-choice systems.

In any case, these are the sorts of things authors will probably want to ask about. The answer for Rez may well be - you don’t get them. But if there’s a Rez way to have something like it, that should be mentioned.

Best of luck.

Ah, yes, I think this is a very different problem in a choice based game as the context is (typically) far less implicit. The card (or cards) you are looking at delineate the choices available, so they do much of the heavy lifting (at least in Rez).

For example if the card is presenting a location, how do we present links that can take the player to other locations?

I’ve solved this in a couple of ways. For example just adding an exits: attribute that maps potential exits to other cards representing other locations. Then I can use an embedded card that displays exits (it’s template uses $foreach() to dynamically render those exits appropriately).

Because an exit is just a data structure it’s easy to handle things like hidden exits, locks, and so forth. The difference being that I would naturally build an “unlock” action into the interface for changing location. It’s less generic and more explicit (and I hate puzzles :))

Containers I have in the item/inventory system. It is flexible enough to handle wearables, body parts, etc…

Surfaces is an interesting one. I suspect it is easily handled mechanically by the inventory using an “on top” slot, for example. But I would have to test & see.

I think what you are calling “hybrid” is linked to my distinction between IF as a (potentially) branching narrative system & as a game. If you’ll allow me to avoid having to define narrative or game and just accept that — given sufficient time & alcohol we could hammer out some way to determine what is a game & what is a story that would cover 80% of the cases.

Now in a pure-choice narrative there is a little need for a world model because it’s “all in the cards”. Each card presents exactly what is needed in that moment and there is a 1:1 mapping between every situation and card.

But in the kind of games I have in mind this is not so and so a world model is required to hold the state that the player is exploring. In this sense while the modality is totally different (choice vs parser) the underlying conceptual framework is very similar.

However where a parser based game is usually (usually!) a semi-linear narrative unlocked by puzzles that require game model for state, the kind of games I am interested in require the state to drive a simulation loop and emit narrative (almost as a by-product).

That’s interesting. I feel like Strand may be operating at a higher conceptual-level. In Rez there is, more or less, a 1:1 correspondence between the @elements that you author and some backing object representing the element concept in the runtime system.

Okay teeny tiny milestone but I’ve just created my first, fully procedurally generated, NPC.

It’s taken a little bit of figuring out to make the bits dance together between the template @actor the initialisation & copying code, and how dynamic properties get made & initialised. I’m not sure this is “right” yet, but it works.

Rez has three types of element:

  • static — defined at author time with attributes defined at author time.
  • dynamic — defined at author time with some attributes defined at runtime, for example using the ^i{} initialiser.
  • generated — defined at runtime, based on an author time template, with mostly runtime attributes (because you want them to vary from each other) initialised by the on_copy event handler.

Example:

@actor t_base_npc {
  $global: true
  $template: true

  given_name: _
  family_name: _
  name: ^p{return `${this.given_name} ${this.family_name}`;}

  skills_id: _

  on_copy: (copy, params) => {
    copy.given_name = $given_names.randomElement();
    copy.family_name = $family_names.randomFromBag();

    const skills = $t_skills.copyWithAutoId();
    $game.addGameObject(skills);
    copy.skills = skills;
  }
}

// somewhere else
const npc = $t_base_npc.copyWithAutoId();
$game.addGameObject(npc);

console.log(npc.name); // Prints "Kira Lewis"

The placeholder values (_) for given_name and family_name ensure that properties are created for those attributes at runtime even if they have no author time value.

The name property is defined to use those properties. The values themselves are filled in during on_copy. In this case we want each NPC to have their own skills, so skills are created by copying a template $t_skills element.

I think there is probably a lot more to do here and it occurs to me that I might want some kind of “world generator” that can coordinate how the generated elements are created and relate to each other. Doing it from the individual on_copy handlers is possible but likely to be messy and there may be sequencing issues.

As an aside $global is a new system attribute specifying whether a global variable, $<id>, should be created to refer to this element. It’s a trivial improvement but I find it tidies up references to common lookup objects.

2 Likes

Released v1.2.9 for macOS, Linux, and Windows.

  • Adds $global attribute for creating global references to elements
  • When doing a resume merge new params
  • Fixes some bugs
  • Renames & reorganises a bunch of the rng JS functions
  • Drops ^v{} code block which was deprecated in favour of ^p{}

The RNG functions are:

  • Number.chance()
  • Math.rand_int(lim)
  • Math.rand_int_between(lo, hi)
  • Math.rand_f_between(lo, hi)
  • Math.cl_rand_f_between(lo, hi)
  • Math.cl_rand_int(lim)
  • Math.cl_rand_int_between(lo, hi)

The cl_ variants uses central limit to create normally distributed random values.

1 Like

What’s the standard deviation of the cl ones? Presumably the mean is halfway between lo and hi, but there are a lot of different normal distributions with a given mean.

2 Likes

Looks like it’s a triangular distribution, not a normal one? Average two uniform random numbers between lo and hi.

ALso @sandbags does that even run? Looks to me like it should be calling rand_f_between and you have randf_between.

2 Likes

Good catch. Was renaming and moving the randomisation functions to group & clarify them and missed this one.

I’m a long way from any real math but I believe that the average of two uniform distributions is a normal one.

Sure and I couldn’t tell you off-hand what the σ is, I’d have to go work it out. In practice I just plotted the distribution and said “looks about right”.

The average of n samples gets closer and closer to a normal distribution as n increases, but for n = 2 it’s still a good way off: the probability density function (histogram, if you put the float values in discrete buckets) is a pyramid/triangle rather than a bell-shaped curve. You can get a much better approximation by just averaging more samples, maybe up to a dozen seems to be in common use. Wikipedia has a figure of what the sum (i.e., before dividing by n) looks like for min = 0, max = 1 and several choices of n.

3 Likes

Right, that makes sense. In a different library I had a way to specify how many samples to average. In practice I found n=2 gives just enough pull to the middle to satisfy my needs but in general it might be useful to have a way to do that. Thanks.

1 Like

I’ve been putting it off but the need finally arose for rendering a card from another cards $foreach.

You can already render cards-within-cards in static form using the blocks: attribute to specify the #id of cards you want to render from within the content: template. A common example is rendering a card as a sidebar:

@card card_1 {
  blocks: [#sidebar]
  content: ```
    <div class="column">${sidebar}</div>
    <div class="column">Other stuff</div>
  ```
}

When #card_1 gets rendered the card #sidebar gets rendered first and the rendered content is automatically bound under the same name. So the sidebar binding contains the rendered content of the #sidebar card and can be interpolated using the expression ${sidebar}.

However this doesn’t help if you (a) don’t know which card you want to render, or (b) want to render it more than once as you do in a $foreach.

I just about got this working by implementing a render_card filter but it was pretty… unergonomic:

@card c_char_list
  bindings: [
    characters: *game.characters
  ]
  content: ```
    $foreach(c:characters) {%
      ${"c_char_desc" | render_card: c, $block, …}
    %}
  ```
}

First this is rather opaque looking, it’s not at all easy to see what is happening here.

Second, passing the card id as a string expression to be filtered is just janky.

Third, having to pass the $block binding to the filter. You have to because the renderer can’t do its thing without it but, ugh.

Lastly, how to specify parameters? Actually this is what broke me, filters aren’t meant for this level of complexity and filter parameters don’t parse object syntax.

At this point I threw out the render_card filter approach and started working on a $partial template expression:

$partial(#card_id, {param1: value1, param2: value2, …})

Here is an example showing how it works within a $foreach, and passing params to the card being included so that it can customise its own content:

@card c_char_list {
  bindings: [
    characters: *game.characters
  ]
  content: ```
    <h2>Cast of characters</h2>
    $foreach(c:characters) {%
      $partial(#c_char_desc, {target: c})
    %}
  ```
}

@card c_char_desc {
  bindings: [
    name: *params.target.name
  ]
  content: ```
  <div class="box">${name}</div>
  ```
}

You can also pass a bound expression instead of an #id so if our character had something like:

@actor gubbins {
  template_id: #c_main_actor
}

then you could use something like:

@card c_char_list {
  bindings: [
    characters: *game.characters
  ]
  content: ```
    $foreach(c:characters) {%
      $partial(c.template_id, …)
    %}
  ```

and dynamically specify the template at the same time, allowing different characters to be rendered with different partial templates.

I’m actually a little surprised how easy this was to implement, I hate looking at the renderer code, and the template compiler even more so, but it was actually fairly straightforward and worked first time.

With luck this is the last change I need to make to the template system and rendering code.

This will be in v1.2.10.

2 Likes

I knew there was a reason I implemented the $do{…} template expression.

In practice because Rez has a good set of event handlers you should never need to run code within a template, for example a @card can have an on_render handler that is called just prior to rendering, or on_start that is called before even the render process begins. It’s cleaner and neater than littering templates with ad hoc code.

However, what is useful is to be able to write:

$do{debugger;}

inside a template and drop into the JS debugger in the template rendering context. I can see all of the bindings that have been made and quickly debug why something didn’t work as expected.

2 Likes