My Role
I was the sole programmer for Sacrifice For Sale, a visual novel developed in the Unity game engine. The development team was fairly small, consisting of one director/writer, one artist/editor, and myself.
Ownership
I implemented and maintained 100% of the game's code for the Windows and Linux versions directly on top of Unity, with the only dependencies being some first-party Unity packages and Steamworks.NET.
The tools used (notably IVNT) were also all designed, built, and maintained by myself, with the design process of course involving input from and discussions with the actual users.
I was also responsible for deployment and updates on Steam. When Unity disclosed CVE-2025-59489 (NIST), I implemented the fix and released the fixed build within a few hours.
Technical Scope
The code-base is split into twelve packages that are mostly designed to independently solve a single problem. For example, IARM (Iota Automatic Registry Manager), which is less than 200 LOC, allows for creating "Registries" that represent a central location for certain kinds of assets (for example, the game contains an "AudioRegistry", which contains every audio file in the project). This allows both editor tools and the built game to use the same API to access assets while also acting as a cache. Some other notable packages are:
- a text animation system (<400 LOC)
- a state-machine-based tweening library that optionally integrates into XDS (<500 LOC)
- a text-based, version-control and merge-friendly, human-readable and modifiable serialization library (<400 LOC)
- a telemetry library that achieves GDPR-compliance by associating sessions with ephemeral identifiers (<300 LOC for the client and <1k LOC for the server)
- a SIMD-accelerated, multithreaded, Damerau-Levenshtein-edit-distance-based spell-checker (<1.5k LOC C# and <2.2k LOC zig)
- XDS (<11k LOC)
- IVNT (<4k LOC)
The entire game's story is written in only four graphs totaling over 8k nodes and 30k words, with the largest graph containing over 3,200 nodes. My initial vision was for graphs to be something akin to functions in conventional programming languages. However, the writer and editor found it more convenient to work with a small number of huge graphs, which necessitated improvements to graph navigation, among other things. This culminated in the complete rewrite of the graph's UI from UI Toolkit into IMGUI because UI Toolkit's layout calculation performance rapidly degraded until it took over a minute to open a graph of less than a thousand nodes. This was obviously unacceptable for development, and it seemed to be a fairly deep-rooted issue with UI Toolkit that I couldn't quickly fix on my end, so a rewrite was the only viable option.
Performance Characteristics
The actual game runs without issue on old hardware (32-but Windows is supported). Anecdotally, Sacrifice For Sale was the only game to run at a smooth frame-rate during a showcase/event the director attended.
As mentioned, performance was also a consideration for the editor tooling. Aside from the aforementioned issue with UI Toolkit layout calculation times, a minimum of 30FPS in debug mode was maintained throughout development on all developers' machines, and load and iteration times were kept to a minimum.
Console Ports
The ports (Nintendo Switch, PS4/5, Xbox Series X|S) were handled by Sometimes You, a publisher that specializes in porting desktop games to console. My involvement was limited to technical consultation and support. There were no major issues with the process. The codebase was fundamentally designed and tested for platform-independence, with Windows, Linux, and WebGL (for the demo) having been deployment targets since day 1. I had also already implemented gamepad controls for the initial desktop release.
Maintenance
While the game released on desktop in 2024 and on console in 2025, maintenance of the codebase and technical support for the team continues because of a work-in-progress update that's going to add a much-requested feature. The update hasn't been officially announced yet so I can't go into any more detail, but most of the technical foundation is done, with the main bottleneck being asset generation/acquisition.
Furthermore, I'm the sole programmer on a new project that uses the same in-house libraries/tools as Sacrifice For Sale, but with a significantly expanded gameplay scope.
The Game
There's nothing particularly noteworthy about the game itself from a technical perspective, except that it's all done without a visual novel framework, just Unity. The game includes save/load functionality, skipping already-read dialog, a log, various settings, text animations, sprite tweening animations, and other expected features.
The game's runtime integrates with the Steamworks API via Steamworks.NET.
IVNT
Aside from the game itself and the runtime for the branching story, I programmed the internal tools used to design the story, most notably the "Iota Visual Novel Tool" (IVNT):
The tool was developed to make it as easy as possible to write radically branching dialog and storylines while minimizing organizational work. While the screenshots might look very complex, bear in mind that the game has over 500 choices and is very choice-dense, which would be functionally impossible with a conventional text-based approach like in Ren'Py.
Since IVNT allows creating, reading, modifying, and writing variables, as well as branching and recursion, it's effectively a Turing-complete visual scripting language.
The main issue when working with graph-based languages is organizing the nodes. This is solved by having the nodes arrange themselves into a tree-based layout. That means that writers and editors don't need to constantly rearrange everything, they can just create a new node anywhere in the graph, and the tool takes care of the layout.
IVNT also includes a basic Damerau-Levenshtein-based spellchecker written completely from scratch (based on the Wagner-Fischer algorithm with the single-row optimization) that leverages SIMD and multi-threading.
XDS
Technically, IVNT is based on the "Extensible Dialog System" (XDS), which implements most of the underlying functionality. I wrote XDS to be trivially extensible, so while IVNT is currently the only functional implementation, it's easy to customize XDS to fit any game with branching dialog.
To demonstrate XDS' API, here's the actual implementation of the
SetPositionNode in IVNT:
using Iota.TweenStateMachine;
using Iota.TweenStateMachine.Tweeners;
using ITSM;
using UnityEngine;
using XDS;
using XDS.NodeViewFieldSpecifiers;
namespace Iota.Ivnt {
#if !IVNT_OVERRIDE_NODE_CREATE_SetScreenPosition
[ContextMenuCreate("'Set Position' Node", "IVNT/Create 'Set Position' Node")]
#endif
[ViewMode(ViewMode.Classic)]
public sealed class SetPositionNode : Node {
public enum Axis {
X,
Y
}
[Version(0)]
public SceneObjectRef obj = new();
[Version(0)]
public bool WaitForTweensToEnd = false;
[Version(0)]
public Axis axis;
/// <summary>
/// -1.0 is bottom/left, 1.0 is top/right
/// <summary>
[Version(0)]
public float pos;
public override void Execute () {
Transform t = this.obj.Resolve();
switch (this.axis) {
case Axis.X: {
t.TweenPositionX(this.pos);
break;
}
case Axis.Y: {
t.TweenPositionY(this.pos);
break;
}
}
if (this.WaitForTweensToEnd && Tweener._currentSettings.Time != 0) {
XdsMaster.Interrupts.Add(new WaitUntil(() => Tweener.ActiveTweens.Count == 0));
}
}
}
}