What to get right first
The ordering rules and one-shot constraints that cost an afternoon each — the media bootstrap, the frozen policy, restart versus start, and settling a command.
All documentation — Start here
Start here
Concepts
Authoring for a game
Playing a story
The generated code
The plugin and sync
On this page
- Install the media catalog before anything asks for a picture
- A player’s policy is frozen once it is applied
- Start is not restart
- Seal your entity data
- Settle a command exactly once, on your own thread
- Drain the gate failures
- Read the restore report before you restore again
- Save through the save writer, not your serializer
- Release what you load
- Smaller ones, worth knowing once
- Types you cannot new
- Where to go from here
Most of this plugin is forgiving. A handful of things are not, and each of them fails in a way that does not point at its own cause — a blank portrait, a story that never resumes, a gate that reads false after a load.
They are collected here because they are cheap to avoid and expensive to diagnose.
Install the media catalog before anything asks for a picture
MediaCatalog.Install(MediaIndex.KeyById, MediaIndex.KeyByCode, MediaIndex.ExtensionByKey);A player’s policy is frozen once it is applied
PlayerPolicy declares what your host draws — the node types it never shows, the ones it always
shows, the fields it hides. Hand it in at construction.
Every write after that throws and names the property. And a player refuses to attach a policy that declares anything to a story that is already playing.
Same shape, same reason: pass onError to the constructor rather than subscribing afterwards. The
policy check runs inside the constructor, so a handler attached after it is already too late to hear
the one error you most wanted.
Start is not restart
EngineSession.Start(), DialogueEngine.Start() and TaleShardPlayer.Play() all clear the
engine’s own history — and all leave the runtime holding the previous playthrough’s values.
Call IScriptRuntime.Reset() first if you mean restart. Or call TaleShardPlayer.Restart(), which
is exactly that pairing.
The symptom if you do not: a second playthrough that begins with the first one’s inventory, and gates that are already true.
Seal your entity data
EntitySet<T> throws on every public member until it is sealed, and Seal() is internal — so
the fix is always the generated bootstrap. The exception says so by name:
Call GameBootstrapper.Setup() before reading …
Settle a command exactly once, on your own thread
A CommandCompletion is the token an awaited command settles when it finishes.
Settling twice is refused too; the first answer stands.
This is also why an IEngineDiagnostics implementation must be thread-safe — it is the one thing
here that can be written to from wherever a command handler happens to run.
Drain the gate failures
IScriptRuntime.TakeFailures() returns every gate failure since the last call, and clears them.
Read the restore report before you restore again
IScriptRuntime.LastRestoreReport says what the last load had to change — dropped entities, dropped
variables, variables that fell back to their authored default, refused values.
Every restore overwrites it. Read it before restoring again.
It matters because a silently defaulted variable makes “why is my gate false after loading?” unanswerable. A variable the save predates takes its authored default; a variable the project no longer declares is dropped. Neither is configurable — two supported load behaviours would mean two sets of saves in the field — so the report is the whole story.
Save through the save writer, not your serializer
Read never throws and returns null for any reason it could not be read. Use TryRead when the
reason matters — it checks in a deliberate order: empty, then length before any parse, then the
parse, then the format version, then each field.
Release what you load
MediaLibrary is refcounted, and nothing frees anything on your behalf. No LRU, no memory
budget, no automatic eviction — which also means nothing is ever freed behind your back.
One load, one Release<T> — including for a load that came back empty, because a failed slot
still counts its callers.
The type argument names which load: Release<Sprite>(reference). One media file can be held as more
than one kind of asset, and they are counted apart.
ReleaseAll() exists for a scene teardown. Nothing calls it for you.
Smaller ones, worth knowing once
TaleShardPlayer.WaitForEnd() | Create it after the story starts. A wait created while nothing is playing is already satisfied. |
ContrastPolicy.ReadableOn | Throws on an unreadable background — deliberately, unlike an unreadable foreground, which is reported as an outcome |
RuntimeValue.OfList | Throws on a nested list. The language has none. |
UIStrings.SetLanguage | Refuses the engine’s internal default key by name. Pass the project’s language code. |
ResourcesMediaSource and AddressablesMediaSource | Self-register at BeforeSceneLoad. Registering your own must either match type, name and priority — it is then dropped as a duplicate — or unregister the built-in first. |
Types you cannot new
Several have a private constructor, so a static factory is the only way in. If the compiler refuses
your new, this is why — not a missing reference.
| Type | The way in |
|---|---|
EngineSession | Create(…) · FromDirectory(…) |
EngineState | Idle · Presenting · AwaitingChoice · Suspended · Finished |
ResolvedRef | ForEntity · ForMedia · ForTag · ForCommand · ForNode · ForBoard |
RuntimeValue | Null · Of(…) · OfList · OfStrings |
StoryText | Parse(html) |
ContrastPolicy | Off · ReadableOn(…) |
EntityDisplay | Of(ResolvedActor, …) · Of(ResolvedEntity, …) |
MediaAddress | From(…) · Raw(…) · FromCode(…) · Unresolved(…) |
Where to go from here
Each rule above has a reference page behind it.
| The play loop | Session, engine, step — every member |
| What a step contains | Fields, choices, entities, values |
| Saving a playthrough | What a save carries, and the two load rules |
| Loading media | Addresses, the library, and writing a source |
| The Unity player | The component most integrations start at |
| Scripting and commands | The runtime seam, values, and the completion token |
| Diagnostics | The two channels, and every code |
| Drawing a line of text | Rich text, escaping, and the formatter |
| Colour and how an entity looks | Contrast, the palette, the hex edge |
| The project’s UI strings | The one thing this layer localizes |
| Generated entity data | Entity sets and the seal |
| Playing a story | The narrative version of all of this |