yeah i think it was about 10MB of DLLs minimum and the response i saw constantly from the emscripten side of things was “DLLs are highly cacheable.” ok :+1:

needless to say i think it’s gonna be a bit before WASM is a serious consideration for web-based hypertext games.

1 Like

I wrote up a quick concept of what the new syntax might be. It ended up being something visually similar to YAML, but doesn’t follow its same logic.

  • Nesting will be done based on indentation, similar to Python and YAML.
  • A - will precede a command and a + will precede text.
  • Lines not beginning with either token will be considered as values associated with the previous command/text line (indentation will be ignored), like tags and such. You can also use ;; to achieve the same effect.
  • Like current sadako script and Ink, it will collect choices and then wait for input when it sees something that isn’t a choice. Using a - or + without text is enough to separate a group of choices from a group of choices.
  • The do command is a script block, so it’ll be allowed to have multiple lines. That’ll be the only command that allows that.

I think that’s it. The rest should be pretty self explanatory. Any thoughts on this?

Concept Example
- page: erin;; tag: bleh

  - label: intro
  + Erin is squirms in her seat, nervously tapping her foot. Her eyes are still fixed on the office door, shaking her head in disbelief.
    class: dialogue
  - jump: talk_menu
  - if: %.erin.intro_talk
    - do: 
      _.x = 1;
      _.y = 2;
    + "Come on. We should start looking for {:%.erin.intro_vanessa::Vanessa::the tenants:}."
  - jump: go.eoc
  - label: intro_talk
    + Erin looks at you with a frantic expression. "$:name! What are we going to do?"
  - choice: "Maybe Vanessa will know what to do?" 
      label: intro_vanessa
    + Erin groans. "Dad didn't even tell us who she was." She scratches her scalp in aggravation. After a sigh she says, "But, yeah.. We should probably try to find her."
  - choice: "Manage the estate, I guess."
	+ "Well, yeah. But isn't this a bit much? I know you've had management experience, but this place is ginormous and we don't know anything about it. We haven't even met the tenants yet!"
  - choice: "Then let's go do that."
  + "Right. Let's do this."
  - jump: go.eoc

  - label: following
  - if: $.room === "office"
    - if: %.erin.following_office_father
      + Erin flicks some papers on the desk while mumbling to herself. She doesn't seem to be in the mood to talk.
    - jump: go.back
  + Erin is idly playing with things on your father's desk.
  - jump: talk_menu
  - label: following_office_father
  + Erin just shakes her head. "Dad is so uptight. Everything is so neat and tidy." Her eyes narrow. "I just want to mess it all up."

  - choice: "Do you dislike our father?"[] you ask.
    + "Dislike?" She tilts her head in thought. "No, not dislike. Just.. He left us, ya know? I'm not sure I can forgive him for that." She scrunches her face into a scowl and says, "And <i>this</i> situation isn't helping."
    - jump: go.eoc
  - while: 1 == 1
    + This loops forever.
  - do: _.a = 0
  - for: _.a == 0; _.a < 5; _.a++
    + The value is _:a.
    - if: _.a === 3
      + Breaking early.
      - break:
  + The end.

Bouncing off what Dan said about needing an admirable game; perhaps consider participating in the fast approaching ECTOComp’s 4 hour “Petite Mort” category. Nobody expects a masterpiece in only 4 hours so it seems like a low risk and low (time) cost opportunity to get an example game out. You could also do a screen recording as you make your game which might drum up more interest. :slight_smile:


Dramatic improvement. Ship it. (Not in the fanfic sense.)


Well, against all advice and common sense, I’ve decided not to change Sadako’s syntax. This will probably be the death of Sadako (which sounds like one The Ring’s sequels), but that’s just how it is.

I spent literally all day yesterday from wake to sleep plotting out a new syntax and then implementing it, and I probably got about 30% complete when I realized that it just isn’t going to work for me. In order to use another syntax, I need to go against my original design goals, which really rubs me the wrong way.

At the risk of sounding selfish, I designed this for me and no one else. Not in the beginning at least. As time went on I decided to share with others, but not if it means sacrificing what I valued in it.

Basically my design goals were this:

  • Support all browsers back to at minimum IE9 (ES5). I’ve had sketchy results with minimizers supporting older browsers, so I just keep everything old school with for loops and wrote my own string replacement function instead of using template literals.

  • I wanted you to be able to write your script directly into your HTML file. A compiler is nice because you can split your files up, but for simple tests and games, you can write your story script right into a textarea tag in your HTML file and it’ll turn it into a game. That’s why Sadako is three files: sadako.js (the game engine), kayako.js (the compiler), and kayako_cli.js (the wrapper to turn kayako.js into a command line compiler).

  • I wanted to you to be able to rewrite all of the output sections of the code to suit your needs. There isn’t any HTML stuff buried in the engine. It’s all separated into exposed functions that can be changed. This is how I made the text adventure and VN demos.

  • You need to be able to save in the game whenever you want. Changing the script should not break your save file. This unfortunately comes at the cost of having to assign labels to choices or having a bit of progess lost during choice blocks, but this is my workaround for an issue inherent in the Ink syntax that I based my original concept on.

  • I wanted my script to be as concise as possible. I like to keep my line counts to a minimum.

    • A lot of my ideas for that were taken from Ink, which is where the token counts comes from. Token counts are a bit ugly, but they avoid bracket matching or space-sensitive indenting.
    • Bracket matching uses too much vertical space for me, but it also falls apart on choice blocks for me, because they can be so long.
    • Space-sensitive breaks the line separators (the ;; token), which means every command would have to be on a new line, which also kills vertical height. In other words, this:
      + Choice;; You picked a choice!;; >> next_page
      would become:
      + Choice
          You picked a choice!
          >> next_page
  • I wanted the story text to be your focus, not the script. The entire reason I chose to use a bazillion tokens was because languages like SugarCube and ChoiceScript become difficult to read for me when you’re forced to parse out the reserved words when reading your story. The code and the dialogue all blend together and it becomes messy. This is not an issue in normal programming where the reserved words intentionally stand out because the rest of the code is all tokens and numbers. Writing interactive fiction requires a different kind of syntax priority, IMO.

  • Related to conciseness, I wanted to do a lot with as little as possible. Like this line:

    {test} Click [:*#= "page" + (1 + 2) @: here:] to see more. :: !%.test

    This assigns the label “test” to this line so you can jump to it later, as well as count how many times the line has been seen. The part at the end of the line only displays the line if hasn’t been seen yet (ie. it’s seen count is zero). The script block adds a link named “here” that displays a popup box containing the content of “page3”. How would I write that in another syntax?

    <<set $test +=1>>
    <<if $test === 1>>Click <<link "here">><<run Dialog.wiki(Story.get("page" + (1 + 2)).text); Dialog.open()>><</link>> to see more.<</if>>

    I can’t even make out the sentence from the code in that.

  • I want you to be able to write javascript directly into your story text, not walled off into a script block (which you can also do). For example this works:

    Welcome to $:locations[$.player_location].name.toUpperCase()! I hope you enjoy your stay!`. 

    The side-effect of this is being forced to use odd tokens that wouldn’t clash with JS. That’s why tokens like ::, ;;, [:, {:, etc exist. They would be syntax errors in JS.

I guess that may be it? Anyway, I didn’t choose the syntax haphazardly. It kind of formed to fit my needs and meet my criteria. I understand that it’s not the prettiest thing to look at.

And to be clear, I’m definitely not trying to suggest my language is better than others. It’s just designed to correct annoyances that I had with other languages. So it’s better for me, but that’s not to say it’s better in general.

Sorry for the huge post. I don’t think I ever really shared where I was coming from on this, so I thought it’d be good to share my reasoning.


This is a really crucial point. ChoiceScript started with the theory that there would be a sigil at the start of a line to indicate “this is a line of code.” In ChoiceScript, all code lines begin with a command, each of which starts with a *, so if a line starts with *, it’s code, and otherwise, it’s text.

Only later did I decide to add ${} inline variable replacements, which allow some code to intermingle with text on the same line, and then, reluctantly, I added @{} multireplace, which absolutely does have the problem you describe of intermingling code and text.

What convinced me to go with ${} was that the syntax was pretty distinctive, and that it’s straightforward to write a syntax highlighter for it, and typically only a single token (a variable name) would appear in the braces, so it would be fairly legible regardless. (I think @{} might have been a bad idea, but it was highly demanded.)

I wonder whether the moral of this story, especially apropos the accessibility thread from the other day, is that hypertext source (text containing inline links) is inherently hard to read.

I think hypertext is particularly hard to read when the “link” is really acting as a button that runs a few short lines of code. In React JSX, you can write:

You can click <button class="link" onclick={(e)=>e.preventDefault(); const foo = getFoo(); setBar(foo + 1);}>here</button>
to make the thing happen.

But this totally blows up the flow of the sentence. Generally speaking, people recommend that you define a quicky function elsewhere:

const clickHandler = (e) => {
  const foo = getFoo();
  setBar(foo + 1);

return <div>You can click <button class="link" onclick={clickHandler}>here</button>
  to make the thing happen.</div>

That’s somewhat better, but it’s still pretty hard to read the sentence. Your approach of cramming the code into a few short (but not-yet-taken) bits of punctuation represents a totally different approach. It seems to me that it would be much harder to learn, but it might be easier to use if you can mentally recall what the punctuation does.

But if you don’t use inline hypertext, if you just don’t embed buttons in the middle of your story text, the problems mostly just go away, including some of the more serious accessibility issues.


I think this goal is not achievable in practice.

Just because you’ve labeled your blocks doesn’t mean it’s safe to resume from an arbitrary point in the story. Consider this example:

  1. You release v1 with three parts: chapters 1, 2, and 3
  2. The player plays through v1 chapter 2
  3. In v2, you introduce a new variable who_is_the_killer, that you set in chapter 1 and use in chapter 3
  4. The player resumes playing from chapter 2

What’s supposed to happen now? The who_is_the_killer variable never got set in chapter 1, so it can’t be read/used in chapter 3.

If you think of the game state (all of its variables and their values) as a database, any change to that database’s schema will require a migration. There’s no general-purpose way of automating database migration.

And, for that matter, what if the script deletes a label?

In ChoiceScript, we handle this by restarting the current chapter when you upgrade, which usually doesn’t lose very much play time, and by migrating old saves that have missing variables by adding pre-defined default values. That doesn’t guarantee that upgrading will be bug free, not by a long shot. (We mostly just ignore the bugs that result.)

Yeah, I get what you’re saying. But basically I meant that if you had the code like this in Ink:

+ Choice A
	Answer 1
+ Choice B
	Answer 2

and then you choose choice A and save, then edit your code to be:

+ New Choice
	Oops. Forgot one.
+ Choice A
	Answer 1
+ Choice B
	Answer 2

If you load your save, it will crash. This is because Ink creates the ID of the choices relative to its position in the list. Adding a new entry bumps them all down and breaks the save. If you added it after Choice B, it’d be fine.

My solution (though not optimal, but at least avoids that issue) was that it simply doesn’t save if you’re in a choice block. It creates a save state that will be saved whoever you click a link that redirects to a page or a label or if you choose a choice that has an assigned label. When you save, it’ll write that save state to storage that way on loading your game it will jump you to watcher page or label was in that save state. This means you can move it wherever you want in the code and it won’t break.

So to create save safe code for choice you just do:

+ {a} Choice A
	Answer 1 
+ {b} Choice B
	Answer 2

Labels are only unique to each page, so it’s not a big deal if you use similar names on each page.

Or you can be lazy like me and only slap a label onto ones where it’d be worth saving, like if it exited a conversation or got you an item.

Yeah, I guess I’m saying that this isn’t really a full solution, and it’s not clear that the added friction of labeling a bunch of options is worth the price. Just rewind to the start of the file or whatever.

(Other commercial systems store the game state in a DB instead of in a text file; the GUI editor autogenerates IDs for each choice point.)

Yeah. I’m not sure what a great solution is though. I really dislike the idea of having to worry about breaking saves if you add a single line of code, as is the case with Ink. Twine avoids the issue by only using passages.

Edit: For some reason it took longer than it should have for me to get what you were saying.

You’re probably right that it’d be easier to just rewind to the last page or something. I don’t do chapters like ChoiceScript, but the pages would work just as well. But I like the feature of being able to hit refresh on the page after compiling and it’s right where it was. Also, I got the label on choices to work, so it’s bit too late now. :stuck_out_tongue:

Oh, to be clear, the labels aren’t forced for choices. They’re only if you want to. So that’s why I only put them on ones I want to save and not on ones that lost progress doesn’t matter on. Specifically they’re good for ones that have the jump command returning you to a page or label so that your save will be on the page or label when you reload. It’s disorienting if it doesn’t do that.

IE9 has a miniscule market share (https://www.w3counter.com/trends) and I suspect these are only business computers running legacy apps that require it. You should really consider dropping support for it. ES6 has many awesome features. I think it’s a fair tradeoff.

You’re probably right. However, my thought wasn’t so much that I wanted to support IE9 specifically, but if I could at least support IE9 then I could probably be safe in assuming it’d run on all of the weird mobile browsers out there.

So far I haven’t had to abandon any features while trying to support it, although it has made things wordier in areas.

I’d say it’s not even achievable in theory. :)

(Okay, it is achievable, but only by putting all the hard work on the author. Having it work automatically? No.)

Yeah. I know it’s not completely avoidable, but I’d like to do my best to avoid the pitfall of forcing the player to restart their game when you update your game.

This probably isn’t as big an issue with Inform games and the like because updating is generally for bug fixes. Probably the same deal with ChoiceScript games. But there are a lot of Twine games that release weekly or monthly story updates, not just bug fixes. If they decide to add a few lines to something earlier in the story, or move a block of code from one section to another, that seems like a really silly reason to force a restart.

Sadako does actually do a bit of migrating save files. It’s not perfect, but it does a decent job transitioning older save files to current versions. As long as you’re not expecting a default value to have been changed by that point in the story, of course.

FYI Inform has no save-upgrade mechanism at all. If you upgrade to a newer version, you have to throw away all of your old saves; they don’t work at all in new versions, even if the fix was just fixing a typo.

Ah. It’s been like a decade since I messed with I6 and I didn’t play much with I7 so I couldn’t remember how they worked. I thought I6 did have save-upgrade ability for some reason.

I’m glad I never got far with my I6 game then. I was planning on releasing it in a serial manner like I was just saying. That would have blown it out of the water. :sweat_smile:

I released a new version.

The highlight of this release is that you can now pass arguments to anything that jumps to a page or label, so this is not just normal jumps but also dialog popups and reveal links.

It looks something like this:

## start
    >> #greet >> ["Bob", "Sam"]

## greet
    "Hello!" &:args[0] shouted.
	>> reply >> "&:args[1]"
	"Hi, &:args[1]. My name is &:args[0]."
	<< END

	= reply
	"Hey there. I'm &:args"
// outputs
"Hello!" Bob shouted.
"Hey there. I'm Sam"
"Hi, Sam. My name is Bob."

Perhaps you can see from the example, but arguments are relative to the jump they’re in, even though you use sadako.args to reference it. Returning from a jump restores the calling script’s arguments.

For other methods of calling pages and labels, you just follow the page or label name with >> followed by some JavaScript to assign to sadako.args, which you can reference easily with the shortcut &.args (the variable) or &:args (the value).

So for other options it’d be:

// redirect link
[:greet >> ["Bob", "Sam"]:]

// dialog link
[:* #greet >> ["Bob", "Sam"]:]

// reveal link
[:+ #greet >> ["Bob", "Sam"]:]

This also works for >>= includes, including + >>= choice includes. For example:

## start
    + Test 1
    + >>= #example >> "Bob"
    + Test 2

## example 
	+ Just choose the choice, &:args.
		You did it!
// outputs
<Test 1>
<Just choose the choice, Bob.>
<Test 2>
1 Like

Hei @tayruh, I’m designing my own game engine HyperSigil, surprisingly similar to yours in some core ideas, for example being able to run the compiler in the browser, or making sure all HTML can be changed. I decided to try if I could copy your demo “Rainy Day”. I had mild success with that, still a WIP, to be honest, it has been of great help to see the shortcomings of my own game engine. In my defense, I designed the engine for longer passages (like twine), the concept of making paragraphs flow completely evaded me. So, although you may end up being the only user of Sadako (hopefully not), I’ll surely be keeping an eye on it. And I’ll try the VN next.


Let me know when you have an engine to play around with. I like checking out new engines. :slight_smile:


In my defense, I designed the engine for longer passages (like twine), the concept of making paragraphs flow completely evaded me.

I’m curious what you meant by “longer passages”. The passages in Sadako can be just as long as in Twine. I prefer to write them short because I tend to think that if you have to scroll the page too much, you’re at the risk of boring the reader, and walls of text are overwhelming. But that’s a preference in my writing style, not a limitation of Sadako.

I’m also not sure what you mean about “making paragraphs flow”. :thinking:

I mean that in your model you show each line/paragraph independently, like blocks, you make choices on what to show next as well as on what to clear, so for example you can jump around from on point within the passage to another and continue the flow of blocks there. For you, passages are simply a way to organize such blocks. In my model, you don’t jump around within a passage, there no clear separable blocks by default, you only jump around from one passage to another, each passage may include conditional logic to determine what to show and what not, but it’s a one time full pass, if you want to change what is being displayed you must change the state and reload the whole passage. This is fine for passages that are mostly static content with variable replacement, but if you have some kind of dialogue system (like the Erin talk in your example) a flow works much better.

So where you do (pseudo-code):

Some text
[Question A?] Answer A
[Question B?] Answer B
Say no matter what you answer.

I had to do (pseudo-code, not the real syntax)

<if state = 0>
Some text
[Question A?] <set state to 1><reload>
[Question B?] <set state to 2><reload> 
<if state = 1>Question A? Option A</if>
<if state = 2>Question B? Option B</if>
<if state = 1 or state = 2>Say no matter what you answer.</if>

I’m trying to mitigate the problem by creating a tag the encapsulates also a flow like logic, so that you can do:

        Some text
        [Question A?] Answer A
        [Question B?] Answer B
        Say no matter what you answer.
1 Like