Rez v1.6.0 — open source engine for making procedural narrative games

I’ve released Rez v1.6.2 with two new conveniences:

The ^i{} initializer attribute type now supports an optional priority from 1-10. Initializers with lower priority score run before those with a higher priority score.

This allows one initializer to depend upon the value of a previous initializer, something that was previously impossible. For example:

@actor rando {
  gender: ^i:1{return ["male", "female"].randomElement();}
  given_name: ^i:2{return $(`${this.gender}_names`).randomElement();}
  family_name: ^i:1{return $("family_names").randomElement();}
  name: ^i{return `${this.given_name} ${this.family_name}`;}
}

This is a big one and removes the need for some ^p{} properties and cleans up some on_init and on_copy handlers.

The RezDieRoll class now supports the idea of advantage and disadvantage on die rolls.

Rez now has a makeDie("1d20+1") function for creating RezDieRoll instances from a string specification.

1 Like

I try to favour composition for most things but, yes, sometimes it’s the right tool for the job :slight_smile:

Well this is a bit of a surprise, Claude turns out to be quite capable of writing Rez code. It helps that Rez is declarative and mainly consists of values or Javascript functions but feeding it the language & element catalog as project source docs, it seems to have picked up the structure and relationships and can build me examples.

Released v1.6.4

This update adds the following:

  • Adds $init_after: attribute that allows specifying a custom init order for elements
  • Dispatches document_loaded event on DOMContentLoaded
  • Calls to setAttribute() trigger on_set_attr
  • Stdlib: +Array#zip, +Array#min, +Array#max, +Math#perc, +RezBasicObject#unmap_attr
  • Stdlib: +Rez.D4, +Rez.D6, +Rez.D8, +Rez.D10, +Rez.D12, +Rez.D20, +Rez.D100
  • Stdlib: -Number#chance

The two biggest changes are the new on_set_attr handler behaviour and the on_document_loaded behaviour.

For example I have something like this:

@skill base_skill {
  value: 1
  potential: 4
  score: 3

  on_set_attr: (obj, params) => {
    const {attrName, oldValue, newValue} = params;
    if(attrName == "value" || attrName == "potential") {
      obj.setAttribute("score", skill.value + ((0.5 * skill.potential).roundp()), false);
    }
  }
}

I could have defined score as:

score: ^p{
  return this.value + (0.5*this.potential).roundp();
}

and it would have been fine but it felt wasteful given that score is getting read a lot but changed very infrequently. I could also have provided a special method for changing the value or potential fields. But it felt error prone. So, now you can respond to changes to attributes.

Next I wanted to create a progress dialog that tracks the progress of my world builder while it is generating the sector and its history. This process takes quite a few seconds and blocks the UI.

Rez includes Alpine.JS which makes this pretty easy with setTimeout scheduling the work:

<div id="progress-container" x-data="{message: 'Initializing simulation...',progress: 0}">
  <div class="box mt-6">
    <h3 class="title is-4">
      <span x-show="progress<100" class="icon-text">
        <span class="icon">
          <i class="fas fa-spinner fa-spin"></i>
        </span>
      </span>
      Building the Galaxy
    </h3>

    <p class="mb-4" x-text="message"></p>

    <progress
      class="progress is-primary"
      x-bind:value="progress"
      max="100"
      x-text="Math.round(progress) + '%'">
    </progress>
  </div>
</div>

However there is one big problem. Alpine.js has to be loaded with defer which means it’s not available when the Game.start() method is called and the view gets painted. So using the regular event handlers Rez makes available don’t help.

Enter the on_document_loaded handler. This is triggered by the DOMContentLoaded event and ensures that the Alpine reference is available.

@card c_world_building {
  ...

  simulate: function() {
    const progressData = Alpine.$data(document.getElementById("progress-container"));

    if(this.cur_year < this.end_year) {
      this.cur_year += 1;
      const progress = (100 * (this.cur_year - this.start_year) / (this.end_year - this.start_year)).roundp();

      progressData.message = `Simulating ${this.cur_year}`;
      progressData.progress = progress;

      this.world_builder.simulate_year(this.cur_year);
      setTimeout(this.simulate.bind(this));
    } else {
      progressData.message = "Finished";
      setTimeout(() => {$game.startSceneWithId("sc_academy")}, 1500);
    }
  }

  on_document_loaded: (card) => {
    card.cur_year = card.start_year;
    card.simulate();
  }

  ...
}

The card has painted the display but the simulator doesn’t get started until after the document load is finished.

1 Like

Just released Rez v1.6.6

The main change is that @alias has been deprecated in favour of @elem where the main difference is you can now do this:

@elem hat = item
@defaults hat {
  type: :hat
  bogie_would_approve: false
}

@elem wool_hat = hat
@defaults wool_hat {
  material: :wool
}

@wool_hat black_fedora {
  color: :black
  bogie_would_approve: true
}

It’s a kind of poor mans inheritance because all its really doing is compile-time copying attribute defaults up the chain from whichever built-in element type you started with to the element being defined.

This can be blended with the @mixin element allowing you to bring in properties and functions from outside. Mixins operate at run-time and don’t involve copying.

I continue to fix & tune as I work with Rez on Fleet Commander so am releasing v1.6.8.

This version updates to Elixir v1.17/Erlang v27.7 and so brings Rez completely up to date with current Elixir.

Additionally it adds a new syntax for dice-roll attributes, e.g. ^r:2d6+1 and a new shorthand where defining an attribute with a _die suffix automatically generates a corresponding property with a _roll suffix.

So defining:

temp_die: ^r:d10-1

means you automatically can automatically use temp_roll as a property to return temp_die values.

Some initialization bugs and ordering issues have been cleaned up so that you dont have to use on_copy so often and can more and more depending on ^i attributes.

In FleetCommander I am generating hundreds of stars and similar numbers of officers and it’s all working pretty nicely at this point. For example here is the template for creating G class stars like sol:

@main_sequence_star g_star {
  $template: true

  class: "G"

  luminosities: [9 9 8 8 7 7 6 6 5 5]
  types: ["G" "G" "G" "G" "M" "M" "M" "M" "M" "M"]

  base_color: {r: 255 g: 244 b: 232}

  min_zol: 0.8
  max_zol: 1.6
  mod_zol: 0.02
}

Makes use of @elem aliases, initializers, and defaults. Systems can also generate planetary bodies using a semi-realistic system borrowed (with permission) from my friend Chris Bateman’s game Outlands. I still need to hook up the zone-of-life calculation from the star generator to the planet generator. But soon I shall have a sector with hundreds of star systems to explore.

I’ve been reading Edwin MacRae’s book Narrative Design for Indies and the more I read the less interested in plot I become and the more interested in story.

Do you mean the story that comes from unscripted, emergent gameplay?

1 Like

I think so, yes. I’m still feeling my way here but what I am taking away is how plot works against player agency. MacRae quotes Hemingway:

The king dies then the queen dies is a story. The king dies and the queen dies of grief is a plot.

In this context story means theme & tone while plot means causality.

High causality is desirable in novels and movies because they don’t have gameplay. The more gameplay we have the less we require the causality of plot to make sense of things and the more we may find it undesirable because it constrains our freedom of action.

MacRae gives examples of games where small, independent, units of story can be used to convey a sense of character and place, while allowing the rest of the story to emerge through gameplay.

I’m still figuring this stuff out but it’s giving me a way to think about what I like in video games: that I deeply enjoy story but also desire high agency. Perhaps this explains something I have sometimes struggled with about IF: when it is tightly plotted like a story and where the puzzles “have a solution.”

In terms of FC this means that plot is something the player will largely not experience directly (although, at the moment, there is still an overarching plot). However plot will be something that NPCs experience very directly in the context of situation their ships are sent to deal with and how those play out.

2 Likes

Your explanation makes sense to me. If I understand it correctly…

In order to have player freedom in a simulated world, authorial control (plot) needs to be loosened. However, the concept of authorship changes from that of a scripted plot to scripted rules that dynamically provide a semblance of plot.

Idea: The consequences of your actions could read like a news feed and how the other factions view your motivations.

1 Like

I’m not really sure what I understand yet myself, but I feel like we’re more or less on the same wavelength.

In this case “scripted rules” is, I think, about gameplay mechanics that, done well, allow the player to infer causality (plot) for themselves without the game needing to spell it out. I think.

I begin to feel that I am exploring an outer edge of what this community considers to be interactive fiction. I certainly feel a long way from Zork.

1 Like

The news feed idea I mentioned was the “spelling out” part. :wink: