What a build needs from a project
The authoring decisions that reach a compiler: codes that become tags and names, what an unimplemented command does, option sets, and the start nobody guesses.
All documentation — Authoring for a build
Start here
Concepts
Authoring for a build
Playing a story
The generated assets and code
The plugin and sync
On this page
- Codes become tags, and names
- Entity codes
- Input action codes
- Commands: optional to implement, never silent
- Option sets versus inline options
- The start the engine will not guess
- Gates and requirements do not treat blankness the same way
- Media filenames are addresses
- Rich text and plain text are different fields
- Languages
Most of what an author does in the editor never reaches your build as anything but data. A short list of decisions does — and each of them has a consequence a writer cannot see from inside the editor.
Codes become tags, and names
Almost every code an author types becomes a gameplay tag, and — with generated types on — a C++ identifier: node types, their fields, entities, variables, commands, tags, boards, option sets.
So the guidance to a writer is short: codes that look like identifiers produce tags and names somebody can read. Nothing judges whether a code reads well, so nothing will tell them otherwise.
Three consequences worth knowing precisely:
| Two codes that differ only in case are one code | a gameplay tag is a name and a name compares without case; so does every keyed store. The first declared wins, and the sync reports the other. TaleShard refuses such a pair of any kind, comparing codes without case and without anything but letters and digits (gold, Gold and go_ld are one code), and renames the later one when the project is opened; a pair synced before that is reported as code-clash. Two variables differing only in case stay two variables, and the later one by id is numbered (Gold_2, GetGold2) — see The tags |
| Node type codes | the generated library comes from a sanitised, uniquified name — never from the raw code |
| Field codes are unique per namespace, not per type | a type can carry the same code at its top level and inside a repeater. Both survive; the repeater’s are read through Get Blocks |
Entity codes
Two entities sharing a code resolve first-wins, with one duplicate-entity-code diagnostic at
load. Nothing crashes and nothing is dropped; one of them is simply unreachable by code.
An entity’s id survives a rename and its code does not, which is why the payload and every save key on the id and carry the code alongside.
Input action codes
A record of a definition whose mentions draw as a keycap — the Puzzles & world pack’s Input
action — is an input action, and its code is the one your game maps to its own action:
Crouch to IA_Crouch. Renaming the record changes only the label a player reads; changing its
code breaks that mapping, silently, in every prompt that names it. Its key name, pad control and
touch gesture fields are previews the editor draws — the binding a player sees is your game’s. See
an input action’s glyph.
Commands: optional to implement, never silent
Declaring a command in the editor gives the programmer something to answer it with: a TaleShard Command event in Blueprint, and with generated C++ an event on the generated command class. The build never breaks over a command nobody has implemented yet, and an awaited command never leaves the story hanging:
| Declared as | In Blueprint | With generated C++, left unimplemented |
|---|---|---|
| Ordinary | an event with the arguments as typed pins | Runs, and warns once — command-not-implemented, naming the event |
| Awaited | the same event with a Done pin, which the story waits on | Fails the completion with an error naming the event, and the story carries on |
In Blueprint, a command with no event of its own reaches the command handler’s On Command, and the story does not wait for it. With generated C++, an awaited command is failed rather than left waiting because an unanswered wait is not a no-op but an infinite hang — the story would suspend and nothing would ever settle the completion.
Marking a command as awaited is a request to a programmer, not a setting — worth saying out loud before a project has fifty of them. Receiving one is implementing commands.
Option sets versus inline options
A select field can draw its values from a reusable option set, or carry its own options inline. Only the first produces an enum.
| Inline options | A reusable option set | |
|---|---|---|
| Integer values | None | Yes |
| Generated C++ | a plain string | a real UENUM, plus a tag per option |
| A variable of that type | text | the enum type — ETaleShardMood::Calm |
The payload carries the option set’s id and not its option list, which is why the mapping cannot be built for an inline select at all.
An option the project no longer declares fails Try Parse; the plain Parse answers the enum’s
first entry. A set with more than 255 options gets no enum at all — the tags stay.
The start the engine will not guess
A project names a start board and, optionally, a start node.
Set the game start explicitly. It is one field, and it is the difference between a build that plays and a build that opens on whatever node happened to sort first.
Gates and requirements do not treat blankness the same way
| Blank means | |
|---|---|
| A gate on an exit | Fails. A gate that is present and empty is an unfinished edit on a path meant to be conditional. |
| A requirement row on a node | Passes. An author who has not written the test yet has not blocked anything. |
Both are deliberate and neither is configurable.
Two more, worth telling a writer once:
- A node’s unmet requirements are hoisted onto every choice that leads to it, so the requirement is written once on the destination and every route in shows locked.
- A jump bypasses them, because a jump is already an authored override of the graph.
Media filenames are addresses
A synced media file’s code is what its tag carries and what the media index resolves. Renaming the file in the editor changes the code, and an entity’s portrait is looked up by the reference the payload holds, not by name.
Nothing breaks silently here — a missing address is reported — but it does mean the names an author chooses are a public surface. Loading media has the rest.
Rich text and plain text are different fields
A rich-text field and a plain-text field carry different things, and the conversion to something drawable is different for each. That is a programmer’s problem — the presenter widget decides, see drawing a line of text — but it is worth knowing that the choice is made in the editor, and that a stage direction typed into a plain field can contain angle brackets safely while the same characters in a rich field are formatting.
Languages
The master language’s values live in the fields themselves; every other language lives in a translation bag. There is no runtime fallback chain — the sync fills a missing translation with the master language’s string.
So a partly-translated project syncs and plays. It just plays with some lines in the master language, which is the right failure and is not reported as an error.