Ripple Effect

For game developers

Story logic you can drop into any engine

Writers build the story in the editor. The engine plays it inside your game and hands your code what happened — and how any of it looks, sounds or saves is yours.

  • TypeScript
  • C
  • C++
  • C#
  • Dart
  • WebAssembly
Decisions

A choice doesn’t have to be a button

A choice is an index your game passes, so it can come from anything the game knows about — not only from text the player clicks. And every choice can carry a condition, so what is on offer follows the world: where the player stands, how a character feels about them, what a faction remembers.

That works because the story and your game share their state, all the time, in both directions.

  • Somewhere the player goes: the game writes where they are, and a choice opens
  • Someone the player knows: a relationship that grows over the story opens what was closed
  • Something the player does: the game takes a choice itself, from a door, an item or a gesture
  • The clock: a passage takes its own choice when its countdown runs out
Brannoc
execcompleted
Brannoc · entity
1trust
Comparator
aresult
b≥3
Get variable
execcompleted
zone
"lighthouse"value
Comparator
aresult
b=="jetty"
The harbour gate
the-keeper-the-harbour-gate
Brannoc has the only key to the harbour gate, and the tide is already turning.
when · Ask for the key
when · Take the boat
Ask Brannoc for the key
Take the boat
Wait for the tide
30 s, then Wait for the tide
Two-way, all the time

Your game and the story talk to each other

Console variables and entities are the shared memory: the story moves them as it plays, your game moves them as the player does, and each side hears about the other’s changes. Callbacks name the moments that matter, so gameplay can react to the story — and the story to gameplay.

Every event and what it can be aimed at

  • Into the story: choices and answers, console variables written and reset, entities removed, the language changed, a save resumed.
  • Out of it: every passage and question as data, what moved after each step — variables, entities and what the story remembers — and the full cast of entities with their typed attributes whenever you ask.
  • Named callbacks: moments a writer declares in the editor on twenty kinds of event, aimed at a board, a passage, a variable, a component or a state.
  • A callback carries no code and cannot change the story; your game decides what it means
Your game
The story
  1. session.writeCvar('zone', 'jetty')

    The player walked onto the jetty.

  2. { kind: 'changed', cvars: [zone] }

    The story saw it, and says what moved.

  3. callback reachedTheJetty

    A named moment the writer declared — cvar.changed, aimed at zone.

  4. session.choose(1)

    The game takes “Take the boat” — no button pressed.

  5. { kind: 'show', passage }

    The next passage, for the game to draw its own way.

  6. callback brannocTrusts

    entity.field.changed, aimed at Brannoc’s trust.

  7. session.entities

    Read the cast: Brannoc { trust: 3 }, every attribute typed.

Language integrations

Four bindings, one engine

Each binding is its own open repository and its own package, built against a published engine release and pinned to it. The bindings are MIT; the engine is free and royalty-free to ship, unmodified, inside your product.

  • TypeScript and JavaScript on npm: the engine as a WebAssembly component, and an optional Web Audio speaker for its soundscapes.
  • C and C++ through CMake: the engine’s C ABI, fetched as a released binary, with a header-only C++17 layer over it.
  • C# on NuGet: netstandard2.1, with the engine with or without Python in its own package.
  • Dart on pub.dev, for Flutter and Flame — natively, or in a browser over the same web component.
npm
npm install @ripplefx/player
import { Campaign, Player } from '@ripplefx/player';

const campaign = await Campaign.load('/campaigns/the-keeper/');
const player = new Player(campaign.session({ seed: Date.now() }), {
  onEffect(effect) {
    if (effect.kind === 'show') drawPassage(effect.passage);
  },
});
player.start();
Built for engines that aren’t ours

Fits a custom engine, a tool, or a page

Nothing in the engine assumes a game engine of its own, a main loop, an audio device or a renderer. It is a library that answers calls, so it fits an in-house engine, an editor tool, a server or a web page as readily as a commercial one.

  • A C ABI, so anything with a foreign-function interface can host it
  • Desktop, Android and iOS builds, and a WebAssembly component for the browser
  • Your clock: a hold is milliseconds you wait, never a timer the engine runs
  • Your speaker: a soundscape is a schedule your audio code plays, or ignores
  • A no-Python engine build for hosts that want none, and an export checked against it

Your own engine

over the C ABI

An in-house tool

C#, Dart or C++

A web page

a WebAssembly component

A phone

Android and iOS builds

The engine

Open a folder and start writing

No account, no project scaffold, nothing written into your directory that you did not ask for.