New Python / INK based Interactive Fiction engine

Folks,

I’m midway to the next major update to the Interactive Fiction / Ink Engine update (to v1.5). But that’s putting the cart before the horse.

What is the “Ink Engine”?

It is a completely clean reverse engineered version of Inkle’s Ink interactive fiction engine in Python. See GitHub - bschollnick/ink_engine: A standalone, Django-free Ink interactive-fiction interpreter and plugin engine · GitHub for the GitHub repository for it.

But, that’s not all (™)!

  • Since one of the reasons I did this is because I’m porting some 3rd party games to it, there were features that were needed but weren’t standard with Ink. Please note, none of these are required to be used.
    • Cost Tables / Shop functionality (e.g. not real money, but “purchasing” something from a shop keeper, etc.)
    • Player Inventory management
      • “container” management - basically a subset of inventory management but for bags, containers, chests, etc.
    • location management (aka mapping) - Where buildings, roads, paths, etc are.
      • character occupancy management - where characters are in-relationship to the locations
    • Scheduling - Managing the “clock” in the game world, as well as managing character schedules (e.g. shop is open from 8am - 5pm, and handles the shopkeep’s location during and away from open hours)
    • Quest management
    • Skill management
    • Game loads / saves / restarts

All of that is baked into v1.0!

I’m currently working on v1.5, which adds further features to the plugins.

But as I am putting this in the development forum, it should be obvious that this is effectively an SDK. Real effort has been placed into making this equivalent in output to the inkle “standard” version of ink.

There are built-in alternative tags to support images, movies, and other media.

But this is an embedded SDK.

I also have a companion IF Player - GitHub - bschollnick/if-player: A standalone, native desktop Ink interactive-fiction player · GitHub
Which takes this ink engine, and wraps it with a GUI for playing ink based games.

I do have to ask—why put all the work into a clean-room reimplementation when the original project is MIT-licensed? That’s one of the most permissive licenses around.

The repository says that initial check-ins were made by claude, the claude AI chatbot. Does that mean this is an AI derivative?

I added the AI tag to the post. Please remove it if you disagree.

Is it really “reverse engineered” if it’s based on explicit documentation for the json runtime?

Also, why is it described as “Django-free”? Why would I expect it to use Django in the first place?

do have to ask—why put all the work into a clean-room reimplementation when the original project is MIT-licensed? That’s one of the most permissive licenses around.

Well, true. Maybe I shouldn’t be stressing that quite as much, but since I’m using a totally different language, and it’s designed in a totally different manner from the original inkle implementation, the only thing that is the same is the language specification.

Looking at the original source wouldn’t help, since it’s designed to plugin to a web server, so IF games can be played online in a stateless manner.

So in my opinion, there’s no point using the original source as a reference source. Thus it’s a clean reverse engineered version.

And also, this way, any errors are MY fault.

The repository says that initial check-ins were made by claude, the claude AI chatbot. Does that mean this is an AI derivative?

I use claude as a tool since I’m one person. I won’t argue, but the ideas, and implementation is mine.

Is it really “reverse engineered” if it’s based on explicit documentation for the json runtime?

What is reverse engineering? Looking at a specification, and then working backwards from there.
That’s what was done.

No original inkle / ink engine source code was examined in creating this ink engine.

Also, why is it described as “Django-free”? Why would I expect it to use Django in the first place?

I swear, I keep finding that referenced… I thought I removed it from all documentation. If you could point out where, I’d appreciate it.

Why was it there? Because the ink engine was originally started as an idea for the QuickBBS Django based Web server, to allow the web server to allow users play ink based games.

After a bit, I decided to split the ink engine out, as it’s own library so that I could create the IF Player as a stand-alone IF game player… And so that other people could embed it if they wished.

The “Django free” was originally referred to in the ink engine documentation, and IF Player documentation, because they aren’t django based. I’m not sure what I was thinking, because it didn’t add some clarity. After a few revisions, when reviewing the documentation again, some common sense hit me, and I removed those references.

If I have missed it somewhere, please feel free to point it out, so I can take a baseball bat to those references. It doesn’t make sense in a stand-alone library / player.

See, to me “reverse engineering” means looking at an existing already-implemented system and trying to figure out how it works. I would say examining Infocom’s Z-machine files and interpreters to figure out the spec was “reverse engineering”, while building a new interpreter that fits that spec is…just normal software development. Someone who builds a new web browser isn’t reverse-engineering HTML, after all.

The repo description. “A standalone, Django-free Ink interactive-fiction interpreter and plugin engine”.

I know this all comes off as nitpicky, but I’m not getting a clear picture of what this actually is. A clean-room reverse-engineering project is mostly useful when you want to get around someone’s software license (e.g. if you don’t want to abide by the GPL and care enough to spend a lot of money on a workaround), but that’s not really relevant when the original project is MIT-licensed. The title of the page itself advertises that it’s “Django-free” which isn’t really relevant to…anything. I don’t see any documentation of the cost tables, inventory, map, etc that you advertised in the first post.

The impression I’m left with is that you asked Claude for a Python port of Ink, and you don’t have a solid idea yourself of what benefits this has or what it’s for beyond the usual LLM waffle. If that’s not correct, then please elaborate—what does this do, why should I use it?

I have more questions now.

  • Looking at your quickBBS to understand the original use case, I don’t see why you would write an entirely new Python implementation of Ink to use server-side, rather than just using inkjs (probably the most reliably supported version of Ink aside from the main Unity implementation) to make a viewer that runs client-side?
  • Even if you wanted to do it server-side in Python, why not use one of the existing Python bindings for Ink, namely blade-ink-python and/or the python bindings for inkcpp?
  • If the intended purpose was to support viewing ink files in a more general file browser, why include a bunch of plugins to add features that would only be used by games developed specifically for use in this engine? That seems like an entirely different use case.
  • Why come so close to having no external dependencies, only to then introduce pyyaml for the purpose of reading a manifest file, when it would be more natural to use the established “global tags” feature of Ink to encode project metadata instead? Or, if you absolutely need a manifest file, why not use json to avoid the dependency?
  • Is there an example game that demonstrates the use of the included plugins? From the documentation, they don’t look like they would be useful or pleasant to actually use.

The repo description. “A standalone, Django-free Ink interactive-fiction interpreter and plugin engine”.

Well, as I mentioned, I’ve updated the repo, so that must have been before that…

ink-engine

Date Created: 2026-09-15
Last Updated: 2026-09-20
Last Reviewed: 2026-09-20

A standalone Ink interactive-fiction interpreter and plugin engine.

ink-engine runs compiled Ink stories (.ink.json/.inkj) and provides a minimal plugin contract for applications (games) to extend the interpreter with their own stateful mechanics — inventory, scheduling, character occupancy, and so on — without the engine itself knowing anything about how it is embedded: trust and security gating, storage, or any particular application’s user interface conventions.

But either way, I understand your point. And as I mentioned, I had already tried to address that.

The impression I’m left with is that you asked Claude for a Python port of Ink, and you don’t have a solid idea yourself of what benefits this has or what it’s for beyond the usual LLM waffle. If that’s not correct, then please elaborate—what does this do, why should I use it?

I disagree, simply because I know what I want to use this for, but that doesn’t mean it’s what you want to use it for. I’m not a professional technical writer, so please feel free to give me suggestions to help? What should I emphasize?

There are tremendous side benefits from this engine, including the “batteries included” plugins that are add-on benefits.

I’d suggest taking a look at the plugin guide/bindings guide to get an idea on the scope of the plugins that shipped in v1.0. I’ll point out that there have been enhancements since v1.0, that I’m still working on.

If you want to discount this because you don’t see the benefit, that’s fine…If you have creative suggestions to help clarify, then I’m all ears.

Even if you wanted to do it server-side in Python, why not use one of the existing Python bindings for Ink, namely blade-ink-python and/or the python bindings for inkcpp?

Every python port I looked at was unsupported. If I was going to use unsupported code, then I might as well use my own. That being said, I missed those.

Looking at your quickBBS to understand the original use case, I don’t see why you would write an entirely new Python implementation of Ink to use server-side, rather than just using inkjs (probably the most reliably supported version of Ink aside from the main Unity implementation) to make a viewer that runs client-side?

Because in my opinion inkle’s implementation of INK is very incomplete. There are tons of concepts and functionality that are common in IF games, that INK doesn’t natively support.

Loading a JS version of ink doesn’t solve any of those.

Even if you wanted to do it server-side in Python, why not use one of the existing Python bindings for Ink, namely blade-ink-python and/or the python bindings for inkcpp?

Once again, it doesn’t really solve the problem.

I wanted a core to offer a starting point, and offer add-ons and improvements that are forward facing, but still allow the backward compatibility to allow “standard” games to be able to be played.

If the intended purpose was to support viewing ink files in a more general file browser, why include a bunch of plugins to add features that would only be used by games developed specifically for use in this engine? That seems like an entirely different use case.

Well, quite frankly, maybe it’s one way to get inkle to actually innovate.
Consider this another example of xkcd # 927.

Why come so close to having no external dependencies, only to then introduce pyyamlfor the purpose of reading a manifest file, when it would be more natural to use the established “global tags” feature of Ink to encode project metadata instead? Or, if you absolutely need a manifest file, why not use json to avoid the dependency?

First, YAML is not a significant dependency. And because quite frankly I was re-inventing the wheel.

In trying to prevent the manifest file from containing potentially executable code, I was using the AST code to read and decode the file.

But the real answer was to move the manifest file from being an executable file to a standard YAML file. I originally had considered the standard .ini or other structured file, but due to the expected data YAML was the cleaner answer.

Is there an example game that demonstrates the use of the included plugins? From the documentation, they don’t look like they would be useful or pleasant to actually use.

Yes, they are currently Work in progress. If you have a suggestion for IF games to use as examples, please feel free.

I am working on this between my work hours, family, and so forth. So I’m getting quite a bit work done.

I’ve toyed with cloak of darkness, but I feel it’s a bit small, but of course that’s the idea. But it revealed an option for navigation that I ended getting nerd sniped on. So I’m working on that change.

Ink is already designed to be highly extensible through variable observers, external functions, tags, and the fact that the presentation of content and selection of choices is handled programmatically. If what you want is primarily a plugin management wrapper, it still seems much more natural to use one of the existing Ink implementations as a base, and then build a plugin layer on top of that, without rewriting the core runtime from scratch.

Regarding the general overall vision: In my opinion, Ink is already great at what it does, precisely because of how general & friendly to customization it is. It operates at a really well-chosen level of abstraction, while being very easy to extend or integrate into a more complicated world model. All of my mild annoyances about its functionality are very easily addressed through small amounts of custom code in whatever environment I’m embedding it in.

While I can hypothetically see the appeal of some sort of composable plugin ecosystem, there are already a wide range of community-built templates and engines for specific genres of game providing useful extensions to Ink functionality, so I don’t think that there are currently any major technical limitations on the community’s ability to extend Ink and share their extensions with others. I also don’t think that Python is a particularly good choice as the basis for some sort of general-purpose plugin ecosystem.

The plugins you offer, while possibly useful for the particular type of game that you want to make, are much more narrowly tailored than what I would want or expect from the core Ink language. And that’s setting aside my opinions (based on what I can glean from the documentation) about the actual design of those plugins.

I’d suggest you switch to TOML. It’s supported in the standard library since Python 3.11.

I’d suggest you switch to TOML. It’s supported in the standard library since Python 3.11.

I considered TOML but the manifest required some features that TOML didn’t support whereas YAML does.

It’s a minor requirement, and I personally am not a fan of YAML since it’s very white space dependent.

But TOML would have made the NEW_GAME_FIELDS very convoluted. It would have been a list of dictionaries, with varying keys, nested options, etc that could have been very frustrating to a non-python programmer. Even too me, the examples that I put together to compare the two file formats were very hard to read directly.

Toml also could cause false rejections with a literal…

Examples:

Previous v1.00 python dundered init.py:

NEW_GAME_FIELDS = [ {"var": "player_name", "type": "text", "label": "What is your name?", "default": "Bob"}, { "var": "player_is_man", "type": "radio_image", "label": "Are you a man or a woman?", "default": True, "options": [ {"value": {"player_is_man": True}, "label": "Male", "image": "male.png"}, {"value": {"player_is_man": False}, "label": "Female", "image": "female.png"}, ], }, ]

YAML:

NEW_GAME_FIELDS:
  - var: player_name
    type: text
    label: What is your name?
    default: Bob
  - var: player_is_man
    type: radio_image
    label: Are you a man or a woman?
    default: true
    options:
      - value: {player_is_man: true}
        label: Male
        image: male.png
      - value: {player_is_man: false}
        label: Female
        image: female.png

TOML — Option A, array-of-tables:

[[new_game_fields]]
var = "player_name"
type = "text"
label = "What is your name?"
default = "Bob"

[[new_game_fields]]
var = "player_is_man"
type = "radio_image"
label = "Are you a man or a woman?"
default = true

[[new_game_fields.options]]
value = { player_is_man = true }
label = "Male"
image = "male.png"

[[new_game_fields.options]]
value = { player_is_man = false }
label = "Female"
image = "female.png"

TOML — Option B, inline-table arrays:

new_game_fields = [
  { var = "player_name", type = "text", label = "What is your name?", default = "Bob" },
  { var = "player_is_man", type = "radio_image", label = "Are you a man or a woman?", default = true, options = [
      { value = { player_is_man = true }, label = "Male", image = "male.png" },
      { value = { player_is_man = false }, label = "Female", image = "female.png" },
  ] },
]

TOML option A, is readable, but there is a lot of duplication of details, which makes the possibility of missing something quite more likely.

TOML option B, is fine for a programmer, but to me is still a bit too noisy for the target demographic, which is non-programmers and programmers.

YAML, is much to my annoyance, much clearer to read. It doesn’t require a programming background to understand. And if the file is formatted incorrectly, it’ll abort.

I’m aiming this for non-programmers to be able to use, not just python programmers. The ink code should run without any issues, this is for the optional “setup” style questions (e.g. What is your name, etc).