The Future of Raconteur
I’m not really ready for a release of this just yet – it’ll be a while, probably at least a week – but I wanted to give people an update of where I’m at with Raconteur. Here’s the current (rough) roadmap.
Raconteur will no longer be a wrapper for Undum; rather, it’ll be a standalone library which will include its own backend engine (“Undum”), initially based off the Undum codebase. The reasons for that are several:
- Raconteur depends on a fork of Undum that is a commonjs module rather than a separate script file; over time, this module has diverged from baseline Undum, and at this point I believe I want to make changes that will not be backported to mainline Undum.
- Undum existed as a weird side-dep of Raconteur, where both your game and Raconteur itself had to include Undum. It was, therefore, possible for a misconfigured module bundler to include two separate versions of Undum in your game, at which point all hell would break loose. With Raconteur “absorbing” the Undum engine, we no longer have this problem.
- Historically, developing Raconteur was always stymied by the fact that whenever changes had to be made to the underlying Undum engine (too often!), I had to go and change a whole separate module, in another git repository, check that it was working, go back to Raconteur, check that the change worked, and so on ad nauseam. One unified codebase means changes can be made more easily.
- One codebase also means we can ditch parts of the Undum API that are superseded by Raconteur or deprecated, as well as move forward with greater integration. This will lead to a smaller, more tightly integrated total library.
- This integration also extends to a testing suite; for Raconteur to really be stable and reliable software, I need to be able to test the entire stack of software that makes it up (including the engine). Right now we are not even close to this kind of coverage.
You’ll note that all of those are advantages for me, not for users of Raconteur. But users get three important things:
- Having only one library means having only one, unified API; which in turn means having only one, unified documentation.
- The new, unified Raconteur can be published as a single, relatively easy to use module on npm that users will have more flexibility to use with whatever build system they prefer (Grunt, Gulp, Brunch, Webpack, what have you).
- Eventually, the test coverage and unified library should lead to faster improvements and bug fixes.
Yes, you did see “publish to npm” in there. Yes, finally.
The Distant Future
The aforementioned changes will produce what I call “Raconteur 1.0”; a stable, usable library that is still based off Undum and implements that legacy API as well as its own new API.
Once this is finished, it will go into a feature freeze and receive only bugfix updates while I work on Raconteur 2. Raconteur 2 at this point is just a plan; I’m not going to make predictions about launch dates on it. Please don’t take any of what I’m about to say as set in stone; in a lot of ways, this is a request for comment more than a definitive statement. But the core idea is this:
First, Raconteur will have a new API based on functional programming and “output transaction” objects. To make things happen, your situation’s methods will no longer imperatively call API functions; rather, they’ll return an object that describes what should be done (Or alternatively just Markdown to be written to the story spool, as you can currently do). This means better support for things that have to happen asynchronously, such as timed output or loading assets; the engine will take care of those issues. This means, for instance, that you can write:
situation 'dark_cave', before: () -> action: 'switch_track' asset: 'assets/cave.mp3'
…and Raconteur will take care of asynchronously loading the song, and once it’s loaded, stopping the previous song and playing the new one. Obviously this is an example of a future thing, and not actually a set in stone specification of what that API will look like.
Game state, too, will be handled under the rubric of “output”; so you will no longer assign to an object. Instead, you’ll do something like this:
situation 'get_paid', before: (world) -> action: 'set_state' property: 'gold' value: world.gold + 100
Besides the gains in dealing with concurrency, this will implement a new save system that allows for autosaving and backtracking, and resolves lingering dependencies on a special random generator for randomness. It will also make loading saves in long games dramatically faster. Save files will be a history of output actions, culminating in entering the last situation entered; they will not, emphatically, work by recapitulation as Undum currently does.
Another benefit of this model is extensibility; if you want Raconteur to be able to do more than it currently can, all you have to do is write a “handler” for a new type of output action. If you want to modify Raconteur to work as an engine to run on a different platform or with a different user interface, all you have to do is override the default handlers; real separation of concern, between “game logic” and “scheduling events” and “talking to the world”, will exist.
There are other things that I hopefully want to get into a future Raconteur (such as better and gentler syntax for defining common dynamic prose effects), but that will have to wait.
As usual, you can reach me through Twitter to comment.