Typescript/Svelte IF Framework

I was dissatisfied with the limitations of Twine, so I set out to build my own framework for interactive fiction how I wanted it to be, and that means modern JS, Typescript for type checking and editor support and Svelte for easy reactive UI.

Here is the example story, along with it’s source code. The state persists nicely between page refreshes.
Here is the repo.

Update: I now have a port of Cloak of Darkness as a bigger demo.

Features

Once I finish the saving/loading UI and test it a bit on something bigger, I’ll probably declare it usable enough and publish it on npm. For now you can clone the repo and use npm link link the framework directory into your project. You can also modify the example. Since it’s not on npm yet, you’ll have to run npm link ../framework on the example directory to set it up.

Update: Now available on npm, with a template and an example.

I’d love some feedback, even if it’s a bit early for that.

8 Likes

With Twine itself?, which is the application used to edit the content of Passages and to combine those Passages together with a Story Format template to generate a Story HTML file.

Or with the existing Story Formats, that are responsible for supplying the core / default Look-n-Feel & functionality that a Story HTML file has.

But either way, well done. And I look forward to seeing how your alternative progresses.

How much previous programming knowledge, especially that relating to TypeScript & Svelte, do you foresee an Author needing to use your new framework.

eg. Is the framework’s target user-base experienced programmers, or will the uniniated (like authors & artists) be able to use it.

2 Likes

With Twine itself?, which is the application used to edit the content of Passages and to combine those Passages together with a Story Format template to generate a Story HTML file.

My first idea was to make a new story format for Twine (I’d have called it TypeHeaven), but the existing editor extension support in Twine made it impossible to create a Typescript plugin, and because there’s no build step I’d have to ship the whole Svelte compiler and Babel for Typescript to Javascript transpilation in the story format, which would bloat in unnecessarily. Even klembot himself said I should do my own thing.

How much previous programming knowledge, especially that relating to TypeScript & Svelte, do you foresee an Author needing to use your new framework.

I’m obviously biased since I know it already, but I think the TypeScript Handbook and Svelte tutorial should cover everything you’d need. For Typescript you should at least know the basic types available, how to declare interfaces and declare types for function parameters.

If you want you can take a look at my commented example and see for yourself. Though if you have no JS knowledge at all you’re probably not gonna understand anything.

eg. Is the framework’s target user-base experienced programmers, or will the uniniated (like authors & artists) be able to use it.

I’m aiming for a more programmer audience than Twine. I made it mainly to fix the gripes I have with Twine as a programmer after all. Some things will be easier, some will be harder. I found proper reactive UI in Sugarcube difficult (I basically worked with raw HTML and JQuery at that point), with Svelte you get it practically for free. The other thing is editor support: As klembot said in the issue I linked above, I as a programmer am used to a language server, variable name completion, type checking, etc., and that is just not possible in Twine.

If the framework is a bit more complete I’ll make a proper project template and IDE setup guide for VSCode.

If you're interested, I'm using the following extensions in VSCode:

ecmel.vscode-html-css
ritwickdey.LiveServer
svelte.svelte-vscode

2 Likes

First off, congratulations on making this available to IF authors. It’s no small feat. :slight_smile:

When going through your example game, I noticed that the browser address bar changed with #test2 and so on with each passage visited, generating a new browser history link. When I clicked the browser’s back button, I had to click for each game passage rendering that occurred.

I expected the browser to take me back to this intfiction topic page with one click. It’s a nit picky thing, I admit, but I wonder if this is deliberate/necessary or not.

1 Like

First off, congratulations on making this available to IF authors. It’s no small feat. :slight_smile:

If I make the code already, might as well make it public :person_shrugging:. It’s not even that much, maybe 2K lines of code? Another project I have sits at over 10K.

Also to note: I’m using a permissive license (MIT), because the framework is made not just to be used as a singular thing, but you should also be able to modify and replace parts as needed? Don’t like to border sized in the default UI? Copy main.svelte and sidebar.svelte, change the styles and you’re good to go! The only downside: When updating the framework, you’re on your own then and have to port your modifications to the new version.

That reminds me, the default UI should probably also have an about screen that shows the licenses of contained libraries. Not having that is technically breaking some licenses, but I never heard someone sueing for that anyways.

When going through your example game, I noticed that the browser address bar changed with #test2 and so on with each passage visited, generating a new browser history link. When I clicked the browser’s back button, I had to click for each game passage rendering that occurred.
I expected the browser to take me back to this intfiction topic page with one click. It’s a nit picky thing, I admit, but I wonder if this is deliberate/necessary or not.

That’s the only way I know of to implement links colored by visited status. I could rework it and keep a list of visited passages in the engine… I know Sugarcube has this, and I think it does it like that. The advantage of that would be that the visited status is per savegame and not for the whole site.

I changed it to history.replaceState() for now, the URL still gets changed, but you can still navigate back with one click.

I made another demo game now btw., a port of Cloak of Darkness, which is also installable als a PWA and works offline.

Update: Made the new visited system now and included it in an update of Cloak of Darkness.

2 Likes

That was quick!

In your Cloak of Darkness demo, I noticed that not all links show the hand cursor in desktop browsers. I know if you start playing with <a> tags, you can remove the browser’s ability to understand them properly and you have to add those default browser CSS styles manually. I noticed that you don’t have href attributes and that’s usually the culprit.

(Desktop, Firefox, Win10)

1 Like

In your Cloak of Darkness demo, I noticed that not all links show the hand cursor in desktop browsers. I know if you start playing with <a> tags, you can remove the browser’s ability to understand them properly and you have to add those default browser CSS styles manually. I noticed that you don’t have href attributes and that’s usually the culprit.

Yeah, I noticed that, too. Some links don’t lead to a passage and as such have no href. If I wanted to make an actual puzzle where you’d be stumbling around in the dark, all links would get the same dummy href or just none and the correct css.

1 Like

First Release!

I just published the first release of @if-framework/framework to npm along with a template GitHub repo with setup instructions.

I still expect things to change, but what’s there already shouldn’t change too much.

Things I have planned:

  • RPG module with a world state and fighting mechanic
  • Audio system
2 Likes

This looks cool! I’ll check it out when I have time, hopefully. Also

Passage generation from Markdown files.

is a really neat feature to have.

1 Like

It only supports raw HTML though (and the <iff-link> element to link to other passages because I made it a custom element), so Markdown passages are only useful for plain prose. The good news is though that I managed to make a Svelte preprocessor that replaces <iff-markdown> tags with the raw HTML resulting from the markdown, so there is no need to ship the whole Markdown parser at runtime for that. The JS bundle for Cloak of Darkness is just 50K, about as big as one of the 2 font files.

2 Likes

Congratulations.

As one “alternative to Twine” author to another well done on building & releasing something. Also, welcome to the world of being the massive underdog in a one horse race :slight_smile:

Smart move to include examples, include a conversion of CoD, right off the bat. I wish I had been as smart!

What would you say is the no. 1 reason that someone should look at if-framework over Twine? Is it a desire to use TypeScript, for example? Or something else?

3 Likes

What would you say is the no. 1 reason that someone should look at if-framework over Twine? Is it a desire to use TypeScript, for example? Or something else?

I can’t really decide between Svelte and Typescript, but I guess Typescript is a bit better for me personally. Reasons:

  • Instead of having to play the game, a mistyped variable is immediately caught by the editor with nice red squiggly lines.
  • Typescript enables more editor support, like variable name completion and function parameter types
  • Most editors should also display the documentation for any function or variable. You don’t know how long I’ve scrolled through the SugarCube docs… It takes me 16 scroll wheel turns to go from one end of the sidebar to the other alone…

Reason for Svelte:

The Twine story formats are nice to write in… as long as what you’re doing is supported out-of-the-box that is. I made a character editor in Sugarcube, and because the macros aren’t reactive, I had to manually run an update function every time I changed a variable that set the raw HTML via jQuery… Not a nice developer experience.
In contrast Svelte is build with reactivity, you have stores when variables can be changes outside of the component, but as long as you change them inside the component, Svelte magically inserts the needed update code for you.
It works even better than I though, I didn’t think the local and global variable passages in my example would work, especially the automatic save on input change. For that I had to help Svelte a bit though: It only re-runs reactive statements when it sees they contain the changed variable, and since the save code doesn’t need it I added a simple if check that’s always true.

If you compare if-framework to Twine, I’d aim it to be a more modern (the example has Babel configured to transpile the code to support all browsers within the last 2 years, so you can use all moderns JS features) and feature-rich alternative to Snowman (e.g. I provide a saving system, and something similar to the checkpoint system of Snowman happens automatically on passage transition: A save is stored in the browser’s session storage, where it can survive page refreshes).

The thing I have which I wish all Twine games had: Offline support. Maybe I should make the service worker for that and the updater available to Twine users, that’s a pretty independent part of if-framework. You can just drop the service worker file and config wherever you deploy the story and the updater block in the story JS and it works. The updater checks for an update on each page load, if you increment the version field in the config the service worker caches all files again, sends a signal to the page and the updater displays a nice notification on the bottom right to refresh the page. The refresh then loads the new version. As a bonus that also enables your story to be installed as a PWA on supported browsers (pretty much everything except Firefox desktop). You can see that in action if you play my Cloak of Darkness port: Chrome displays it as an icon in the URL bar on desktop, click it to install it as an app.

Smart move to include examples, include a conversion of CoD, right off the bat. I wish I had been as smart!

I just made a post asking what I should use to test the engine, and Cloak of Darkness came up.

As one “alternative to Twine” author to another well done on building & releasing something. Also, welcome to the world of being the massive underdog in a one horse race :slight_smile:

What alternative to Twine have you made?

Edit: Nevermind, found it on your profile. So, what’s your number 1 reason someone should look at Rez over Twine?

I also noticed you made a post about it in “Authoring → Other Development Systems”, maybe I’ll make a post there, too, once I have it a bit more polished and ready for actual users.

2 Likes

Which parts of Twine aren’t supported offline? I don’t think I’ve ever seen a Twine game that you couldn’t just save and then play from the file with no network connection…

2 Likes

That’s the thing: You actually need to save it (including all assets if needed) and then open the local HTML file in the browser or even start a local webserver (IIRC many things aren’t supported from the filesystem due to security reasons. EDIT: Here is the MDN article about that). I wanted to play Trigaea during a long train ride, and I had to download the archive from itch.io and use the Termux CLI app (for which I also develop something btw.) on my phone to start a local webserver so that I could play the game in the browser. That’s not what I call good user experience. The game worked quite well in landscape mode, only some menus were slightly offscreen. Setting the browser to desktop mode fixed that.

I also want my default UI to be mobile compatible, because as I also read today in another thread, people play games on their phones nowadays (myself included, but most mobile games are bad, so I have a select few mobile games and stick to desktop otherwise). I actually prefer reading on my phone (in contrast to on PC), because I can just sit down comfortably with it almost like I would with a book (with a tablet it would probably be perfect).

2 Likes

I also discovered Strand in your Rez post and I noticed that it’s apparently a trend to make DSLs for IF. That’s also the approach most Twine formats (except Snowman) essentially have with the macro systems and the like.

I decided explicitly against making a DSL. That way I have editor support, can use standard tooling and users won’t have to fight with the format if they want to do something that’s unsupported. Advanced macro use in Harlowe or DSLs rival JS in complexity anyways (and there’s a change authors know it already), so why not use something that’s already there?

On that note: Does Rez have a VSCode extension or something? I made one for Twine and a twee compiler, but I realized that at that point I was just fighting with Twine and twee to make it something it isn’t. That’s where the spark to make this framework comes from.

Edit: I just found this quote from you:

But at this point you’re often having to build or stitch together a framework in JS and Twine, at this point, largely offers constraints that, I found more frustrating than inspiring.

And that framework is exactly what I’m trying to make lol.

2 Likes

Ah, ok. Most of the cross-origin security restrictions don’t get in the way of IF pieces too badly, but if you want to do something as flashy as Trigaea you might hit some of them, yeah.

1 Like

I think that’s reasonable and is kind of where I started. I moved from SugarCube to Snowman because I found SC just got in the way and I was more comfortable writing plain JS. But then I realised Twine’s passage structure was neither a great fit, nor a great foundation, for creating the kind of games I am interested in creating.

That said I think we are both targeting a niche-within-a-niche here. I have decided I am happy to live with a niche of 1 if that’s what it amounts to :slight_smile:

I think that, for the use-cases that make Twine less suitable (i.e. significant amounts of game logic, dynamic UI, or just plain size) Rez provides a sturdier framework.

It has a declarative data language that was designed to specify game elements. It constrains where Javascript lives in event handlers and actions and as a framework mostly lets you plug in what you need. It also includes Behaviour Trees in an attempt to further constrain where JS code needs to be written and by whom.

Sadly the game that inspired the effort is probably way over ambitious for a first game, and then again, a lot of my effort has gone into growing the tool to meet the problem, so I lack the evidence to demonstrate the approach.

Perhaps I should try making a simpler game that, nevertheless, puts it through its paces.

2 Likes

It does not, which is a regular source of some irritation to me.

I don’t think it would be conceptually difficult, the Rez language is quite regular (syntactic elegance is important to me). But I lack the know-how for the task and it’s languished as another ‘rainy day’ TODO.

I recognise that impulse :slight_smile:

2 Likes

For my RPG module with a world model, I’ll use JSON, with schemas generated from typescript definitions. That already provides nice editor completion in VSCode out-of-the-box without me having to write an extension. JSON is not the nicest format, but it beats making a parser for a custom one and making a VSCode extension. I’ll probably delegate the “custom world format” to ‘another rainy day’ as you put it. I do want to include some simple behavior stuff in it though, like NPCs being at specific locations at specific points in time, e.g. a shopkeeper isn’t at the storefront 24/7, he goes to sleep at night. And probably something to declare dialogues. And some simple effects, e.g. “if character.health < 10% apply ‘badly wounded’ effect”.

VSCode has great docs for that, and syntax highlighting alone cane be declarative. Anything more fancy like completion needs code though. It’s really not that bad, most of the documentation is good. I made a simple extension to preview Twine games in VSCode’s WebView myself.

Ah yes, idea scope-creep, I know that. It happened with this project already, I have “create a parallelizable entity component system” on my TODO list… Though the normal world model should work for most things, especially since it’s not a real time game. Having to wait a second after clicking “sleep until dawn” isn’t a deal breaker.

1 Like

I’ll grant you that. Rez required me to build a parser, which required me to build a parser combinator library :slight_smile:

But a downside is that JSON isn’t a great format for supporting functions. That was important to me as Rez makes a lot of use of JS functions for dynamic properties and behaviours (both in objects and in systems which are akin to the ECS type of cross-cutting system).

Yeah, but it’s another thing to learn and… given that Rez is a superset of a subset of Javascript I felt it might actually turn out a little complex. Maybe there is a way to transclude the JS language syntax since the places JS can go are well-defined. But it was a problem I haven’t felt up to tackling.