Rez v1.8.0 — An Open-Source tool for creating choice based games/IF

I’ve just released Rez v1.8.0

Rez is a simple, declarative, language for creating choice based games & interactive fiction. The syntax is easy to learn being largely composed of elements and attributes.

Rez uses Javascript functions to implement dynamic behaviours. JS functions appear as attribute values for handling events and can also be used for implementing custom components.

It’s relatively simple but designed to make large/complex games manageable.

It includes asset management, a renderer with support for different scene styles, a template language with user components, support for actors & behaviours, items & inventories, and more.

Here is a short example that nevertheless covers a number of features including event handling, forms, dynamic templates, element references, scenes & card layout, and more.

%% Include the standard library
%(stdlib.rez)

@game {
  name: "my_game"
  title: "My Game"
  author: "Matt Mower"
  author_email: "self@mattmower.com"
  game_homepage: "https://rez-lang.com/"

  IFID: "B37043A4-6362-11F0-989B-CA41670CF0BC"
  archive_format: 1
  version: "1.0"
  created: "2025-07-17 23:06:41.694789Z"

  initial_scene_id: #scene_one

  layout: ```
  <!-- Main Content -->
  <section class="main-content is-fullheight">
    <div class="container">${content}</div>
  </section>
  <!-- End -->
  ```
}

@scene scene_one {
  initial_card_id: #whats_your_name
}

@actor player {
  $global: true
  name: _
}

@component error (bindings, assigns, content) => {
  if(bindings.card.error !== "") {
    return `<div>${bindings.card.error}</div>`;
  } else {
    return "";
  }
}

@component live_input (bindings, assigns, content) => {
  let {type, name, bind} = assigns;

  return `<input type="${type}" name="${name}" rez-bind="${bind}" rez-live class="input" />`;
}

@card whats_your_name {
  error: ""
  content: ```
  <p>This game is called ${game.title}.</p>
  <form name="player_form" rez-live>
    <p>What is your name: <.live_input type="text" bind="player.name" /></p>
    <.error />
  </form>
  ```

  on_player_form: (card) => {
    if($player.name.length > 0) {
      card.error = "";
      return RezEvent.playCard("your_name_is");
    } else {
      card.error = "You must enter a name!";
      return RezEvent.render();
    }
  }
}

@card your_name_is {
  liked: false
  on_render: (card) => {
    card.liked = Boolean.random();
  }
  bindings: [
    player: #player
  ]
  content: ```
    <p>Your name is ${player.name}. What a $if(card.liked) -> {% nice %} (true) -> {% horrible %} name!</p>
    <a card="whats_your_name">Try a different name</a>
  ```
}

Major changes in v1.8

  • Replaced built-in node validation with the @schema directive (see stdlib for examples of schema directives for built-in elements)
  • @item is no longer a base element but an alias of @card which demonstrates that aliases work well.
  • RezInventory allows for adding anything with a type: attribute
  • Removed the table attribute data type
  • Pass params to game scene_start events
  • Pass params to scene start events
  • Started documenting the stdlib
  • Updated tests
  • Game start_events is now handled as a bindings list
  • Updated to Ergo 1.0.4 and improved attribute parsing error messages
  • The enter key when pressed in the input of a rez-live form now triggers the ‘submit’ event to its card

I started my previous thread in Jan 2024 and it’s grown to be several hundred replies & over 30-min reading time, so I don’t feel too bad about starting a new one.

8 Likes

Just released v1.8.1.

There’s only one code change: I finally fixed the names of the stdlib functions for converting strings between different cases. They were all over the place and now all the same, e.g. toCamelCase().

What I have done is a lot of updates to the documentation. I completed the stdlib documentation and fixed up the tables of contents which weren’t rendering (or rendering properly) across all the documentation.

Last night I released v1.8.2.

The major changes:

Adds the ^c:#id copy initializer. This completes the basic picture on “id based programming” for procedural content. Here’s the simplest possible example:

@elem skill = object

@skill sk_shooting {
  $template: true
  name: "Pew Pew"
  level: ^i{return Math.cl_rand_int_between(1, 10);}
}

@actor player {
  name: "Steve"
  shooting_id: ^#c:sk_shooting
}

By marking the skill object as a template the initialisation of the level: attribute doesn’t happen when the object is created, rather it gets deferred until the object is copied. The ^i{} initialiser says “initialise the value of this attribute to the result of the given Javascript expression”.

The new ^c: prefix says “this attribute value will be initialised with the id of a copy of the named object”. When the player is initialised it will copy sk_shooting, creating a new object (with id sk_shooting_1) whose level: will then be initialised with a value from 1 to 10 (biased towards the middle of that range).

So now you can connect together copies of objects into a whole assembly. There are still some rough edges and syntax that could be improved, but I’m making use of it.

You might be wondering, why not something simpler?

@actor player {
  name: "Steve"
  shooting: ^i{return Math.cl_rand_int_between(1, 10);}
}

Wouldn’t that be much simpler? And you’d be right. For simple examples it is simper and more direct to work directly with the attribute value.

But as I have started working with procedural generation of NPCs I have found that a composition strategy helps tame the complexity. For example in Fleet Commander different kinds of NPCs have different “skill packages” each representing an officer class and composed of a mixture of skills where there is a lot of overlap.

A number of fixes and small changes were also made that are listed in the release notes.

1 Like

I’m working on what will be Rez v1.8.4 which introduces two changes, one invisible and one visible:

In working with the behaviour trees I found that they could be simplified. Rez’s behaviour trees were a port of my prior Clojure behaviour tree library. The insight is that Rez game elements already form a blackboard for preserving/sharing state, but i wasn’t using them as such. v1.8.4 does and simplifies how the whole system works.

As an aside I’ve also settled on implementing “behaviour-tree-per-element” rather than trying to “blend” different trees into a single unified tree. It seems there is ample prior art here so I feel on safe ground. The one remaining question is the ordering of trees. This matters in cases where agents are coordinating because “who goes first” can have an effect on the outcomes/narrative. I don’t see an obvious winner here. In FC I am tending towards randomising the officer list on each run although that has its own downsides.

The visible change is the introduction of the @const directive that creates both a compile time & runtime constant.

@const ultimate_answer = 42

Will define a constant that can be referred to in attribute values:

@actor vroomfondel {
  the_ultimate_answer: $ultimate_answer
}

as well as in the JS runtime:

console.log(`The ultimate answer is ${$ultimate_answer}`);

At the moment it only supports bool, number, and string values although there is a case for letting it support any legal value.

It’s a small change but I was starting to accumulate “magic numbers” in my source and this lets me lift them out, making things more readable, as well as making it easier to refer to the same values within JS event handlers.

1 Like

I have now released v1.8.4 which, among a number of things, adds the @const directive and, as I prophesied, you can now make any legal value (in particular lists) a const. There may be some types that won’t translate into very useful JS values but then I can’t see any good reason you’d make one a const.

1 Like

After a long time away I’ve just come back to working on my game. That’s also lead me to fixing two bugs in Rez.

  • The RezList method randomWithoutStarvation() was completely broken
  • The @generator element was also broken

The v1.8.5 release fixes both of those.

What the @generator element allows me to do is create a number of copies of an element, so:

# Template for creating officer actors
@actor T_officer {
  $template: true

  given_name: ^i{return $given_names.randomWithoutStarvation();}
  family_name: ^i{return $family_names.randomWithoutStarvation();}
}

@generator cadet_pool {
  $global: true

  source: #T_officer
  priority: 1
  count: 100
}

At runtime will give me a RezList with id cadet_pool that contains 100 fully initialized copies of the T_officer template (in my example they will all have names).

So at run-time:

> cadet = $cadet_pool.randomFromBag();
> console.log(`Cadet ${cadet.given_name} ${cadet.family_name}`);
Cadet Andi Burke
> cadet = $cadet_pool.randomFromBag();
> console.log(`Cadet ${cadet.given_name} ${cadet.family_name}`);
Cadet Charlie McDaniel

I can get randomly generated cadets from the pool. I’m using this for officers, ships, star sectors, and all kinds.

3 Likes

A super-nerdy Rez release v1.8.6

It updates Rez to the latest Elixir and OTP28 as well as bringing a number of dependencies up to date. It also fixes a few bugs. But the main change is the addition of the @pragma compiler directive.

This represents something I have wanted for a long time. There are a things hard-coded in the compiler that shouldn’t be and that offer no simple way to configure. So @pragma allow you to run an author written Lua script as part of the compilation process.

That is to say, a pragma has access to the AST at various points in the compilation process. It can examine & modify it, including adding new nodes (for example adding synthesised assets).

Here’s the first application:

By adding:

@pragma(after_process_ast) source_explorer

in my game file, it runs a Lua script (installed in pragmas/source_explorer.lua) after the compiler has processed the AST nodes but before it outputs anything.

The script generates a source explorer mini-site from the compiled game sources and adds it to the dist folder for distribution with the rest of the game.

This uses the Lua & Luerl libraries. A remarkable technical achievement that provide a complete implementation of Lua in Erlang.

3 Likes

Another use I have for this is that Rez uses Javascript for writing event handlers and actions. Ideally there would be a way to lint that JS code and look for problems.

But this is something I was in two minds about building into the compiler itself. For example is it something you would want to run every time? Is there only one tool for linting?

Now I can write a lint-pragma that can be turned on or off and use different lint tools/configurations without re-releasing Rez itself.

I’ve released v1.8.9 which improves the @pragma directive.

I’ve improved the PluginAPI that Lua @pragma scripts use to interact with the AST and file-system. One thing this will allow me to do is replace the clunky Ruby script I was using for processing Heroicon assets with a @pragma that will be run as part of the asset pipeline.

I’ve also rethought how assets get packaged. In the past all assets were flattened into the output dist folder. Now paths are preserved. It was a relatively easy change to make and avoids the obvious lurking problem of two identically named asset files.

@const values can now appear in ${…} template expressions.

Fixed some bugs.

1 Like

I’m really quite happy with how the Source Explorer is working now.

1 Like

More nerdy stuff. That clunky Ruby script that I had to remember to run, has turned into this Lua script (in pragmas/icons.lua):

-- receives 'compilation' and 'values'

-- values[1] is the path where the Heroicon .svg files should be

-- the plugin will create an inlined asset for each file
-- arrow-path.svg becomes icon_arrow_path
-- whose content attribute is the SVG source

function printTable(t, indent)
    indent = indent or 0
    for k, v in pairs(t) do
        local prefix = string.rep("  ", indent) .. tostring(k) .. ": "
        if type(v) == "table" then
            print(prefix)
            printTable(v, indent + 1)
        else
            print(prefix .. tostring(v))
        end
    end
end

function clean_filename(filename)
  filename = filename:gsub("-", "_")
  filename = filename:gsub("%.svg$", "")
  return filename
end

function make_icon_asset(id, path)
  local asset = rez.asset.make(id, path)
  asset = rez.node.set_attr_value(asset, "$inline", "boolean", true)
  return asset;
end

do
  local icons_path = values[1]

  local files, err = rez.plugin.ls(icons_path)
  if files then
    for i, icon_file in ipairs(files) do
      local icon_name = clean_filename(icon_file)
      local asset_id = "icon_" .. icon_name
      local icon_path = icons_path .. "/" .. icon_file
      local icon_asset = make_icon_asset(asset_id, icon_path)
      compilation = rez.compilation.add_content(compilation, icon_asset)
    end
  end

  return compilation
end

Then, by adding the following to my .rez source file:

@pragma(after_process_ast) icons("assets/img/icons")

the compiler will automatically convert every icon file in assets/img/icons into an asset with the file content inlined into a content attribute. For example assets/img/icons/arrow-path.svg is turned into an asset with id icon_arrow_path.

No need to manually remember to run scripts or create and manage @asset definitions manually. Make me happy.

2 Likes