Reference

Authoring style

The grammar tells you what's legal; this tells you what's good — the conventions that keep a .trl file clear to read and safe to extend.

The governing idea: a .trl file is an accretive log. Like the play it mirrors, it grows by accumulation — entities and facts are laid down and never deleted, only reinterpreted. The guidance that follows works with that grain.

Bare is here, loud is elsewhere

One abstraction pays off across the whole language. A .trl has context axes — which story-plane a fact lives on (the level axis) and which story-time it happens at (the story-time axis). On every such axis the rule is identical:

A bare reference resolves to the active context. The only thing ever decorated is a reference that leaves it — and that displacement is always loud, written right where it happens.

A directive sets the active context for a region, the 95% case (a reference that stays put) costs nothing, and the rare crossing is visible in the source. Learn it once and it transfers between axes: <<heist>> sets the active plane and <<heist|name>> is a loud crossing; @@ day1 sets the active story-time and name @@ day2 is a loud anchor. The one difference falls out of what each axis is — a node has exactly one home plane (so a bare reference to a foreign plane is a mistake — cross loudly), but spans a whole worldline across story-time (so this character, now is always a valid bare reference; only its other-time self must be anchored). The levels and story-time sections below are the two working instances.

Two phases: declarations, then the log

Every file has a clear divide. Declarations and setup come first — the cast, the objects, the places, the seeds. The temporal event log comes second — the arc ▸ act ▸ scene ▸ beat timeline, in reading order. Everything the timeline names already exists above it. In practice the split is often physical, too: stable setup lives in an immutable corpus of .trl files you import, while a session plays out in an open, ongoing log-style file.

// @title  The Appraisal   ·   @version 1.0
// (setup usually lives in the corpus:  import "trl/modules/index.trl")

// ===== CAST =====
char vale [+Protagonist] [+Grifter]
char renn [+Antagonist] [+Collector]

// ===== OBJECTS =====
obj nightingale [+MacGuffin] [+Coveted]

// ===== SEEDS =====  (plant now, surface later)
?vale [~Pursues(goal=justice)]?

// ===== THE EVENT LOG =====  (reading order is time)
scene the_appraisal {
  beat 1 { vale -- renn : "earns his trust" }
  beat 2 { evt the_switch [&Betrays(agent=vale, target=renn)] }
}

The preamble is machine-read

That leading // @title line isn't just a comment — the leading // block is metadata, surfaced next to a recognition so a tool never re-parses a file the engine already read. A // @name value line is a tag (the value kept verbatim; repeat a name for a list), the non-tag prose is captured as a description, and // @laconic <one line> is the one-line summary. Conventional keys — @source, @trope, @category — sit beside anything a corpus invents (@icd11, @occurrence); the reader keeps them all.

Two rules keep it honest. Only the leading block counts: a comment trailing a facet or sitting inside a rule is annotation, never metadata. And metadata is inert to matching — it never gates, scores, or changes what is recognized, so adding or removing a header tag leaves recognition and evaluation byte-for-byte unchanged. The editor surfaces these fields in its Recognitions panel and hover cards; the CLI reads them per hit with shape … --emit source,laconic --format json.

Front-load the cast

Declare every entity — char, obj, set, evt, arc, work, concept — before the log uses it. The firm rule: nothing in a scene/beat may name an entity that wasn't declared up top. An entity that springs into being mid-story is the defect this guide is meant to prevent — even a thing the story only points at from the future (a Chekhov's gun, a planted twist) gets a bare declaration up front, then occurs later.

And declare generously. Lay out the whole table of pieces before you play, even ones whose payoff is far off or still uncertain — the cost of an unused declaration is one line; the cost of a missing one is a story you have to stop and scaffold mid-sentence.

Scope to meaning — local by default

Front-loading is about order, not breadth. An element's reach should match its meaning: declare a thing where it's used, no wider. Front-load what recurs or pays off later; keep everything else local. A one-scene set or obj can be declared just above its scene; a one-off flavor tag stays bespoke — a red, unresolved tag is the honest mark that it isn't shared vocabulary, so don't promote color to the corpus; a name earns a shared declaration only once it recurs across tropes.

The same instinct governs a facet's role in a rule. When a facet is load-bearing for one trope's discriminator, mark it on the tag (the ! / ? / *w marks in Make the discriminator bind), not in distant corpus configuration — so an author reading the trope can see what it does without carrying the whole corpus in their head.

Plant ambiguity early, sharpen late

The accretive style lets you declare a thing before you know what it means. Seed it now, commit later — a latent fact ?…? (present in the graph, surfaced to no one), an ambiguity *( A | B ) as name* (one of these, or something not yet named), and resolve name -> concrete for the in-world moment it becomes fact. Uncertainty is a first-class material.

Naming is ontological

Split the verb from the state

A tag's domain facet is canonical — a name always carries the same sigil (= Body, # Mind, % Essence, @ Rel, & Verb). Don't reuse one name across facets. The classic split is the act vs. the resulting state: the act is a present-tense &Verb on an evt; the state is a past participle on whatever the act lands on, carrying the facet its condition denotes. A rule reads the act and writes the state.

// the ACT — a &Verb on an evt, present tense
evt turn [&Betrays(agent=vorn, target=aldric)]

// the resulting STATE — a past participle on whom it lands.
// # = Mind (feeling betrayed is mental); = is Body, % is Essence
aldric [#Betrayed] [#Resentful]

(+ is the unmarked default — write it explicitly, no naked tags. It's a dynamic, not a facet; the dynamics are + straight · - subverted · ~ deconstructed/intent · ? averted · ! invoked.)

Type a slot only where the type is clear

A signature can type each param — Stash(item: obj [+Container]) — and the validator then checks every call site (and warns if the value lacks a required constraint tag). Typing is gradual: an untyped slot is fine; add a type where the corpus concretely passes one kind. But a plain typed param is a required role — every recognizing event must fill it. If a verb is used two ways, one form supplying the slot and one not, type it optional with ?:: Onsets(subject: char, course?: concept [+Course], finding) domain-checks a course= value when present yet still matches a course-less call. Rule of thumb: type it, and add ? if some legitimate call leaves it out. (Full syntax on the grammar page.)

Epistemic discipline

!S!absolutenarrator ground truth, immutable — a death, a revealed fact. Use sparingly.
(plain)assertedthe default — what's true on this plane, now.
*S*contingenttrue-but-perspective-dependent; collapses under a perspective view.
?S?latentpresent, unsurfaced — the seam a perspective cuts along. Plant early, surface late.

A non_canon plane can't mint an !absolute! fact. More in epistemic levels.

Temporal hygiene

Make the discriminator bind

For a trope vignette, assert the trigger facts the rule reads and let the engine derive the recognition — hand-asserting a rule's own conclusions makes the example look right while the rule never fires (and a hardcoded output can even block it). But firing isn't enough: a rule can fire at full confidence on every cousin in its family. Recognition keeps only the when: event patterns for recall and re-checks static tags in coverage — but only on entities an event role bound. A tag floating beside an unbound var is invisible. So the feature that distinguishes the trope must ride an event:

rule Anti_Hero {
  when:
    char $c [+Sympathetic_Focus] [+Unscrupulous!]   // ! the load-bearing anchor
    evt $deed [&Crosses(agent=$c)]                   // the deed binds $c, so its tags re-check
  then:
    $c [+AntiHero]
}

Discriminate by positive evidence — never by negation or magnitude

Define a character type — or any trope — by the positive constellation of acts and traits that constitute it, not by what it lacks or by a number.

The work and its skeleton

Most files model the story inside a work; sometimes you need the work itself — the film, the novel, the game. That's the work entity, and its medium rides on the Essence facet (work the_film [%Film]), never on a prop. Medium-format conventions — credits, a prologue, a post-credits stinger — are structure, not properties: model each as a concept slot on the work's skeleton, not a [+Credits]-style tag.

A scene occupies its slot with the @ presence edge, written right in the scene head: scene the_stinger @ post_credits { … } reads “the stinger occupies the post-credits slot.” (It's sugar for a leading the_stinger @ post_credits edge inside the block — engine ≥ 0.6.0.) A rule then keys on the slot the same way (when: scene $s @ post_credits).

// the work-as-artifact — its medium rides on the Essence (%) facet
work the_film [%Film]
char nick_fury [+Spy]

// a structural slot on the work's skeleton (credits, prologue, the stinger)
concept post_credits

// the scene OCCUPIES the slot — the @ presence edge rides in the scene head
scene the_stinger @ post_credits {
  beat 1 { evt tease [&Recruits(agent=nick_fury)] }
}

Two senses of “target” that don't collide: @ places a node at a slot or location (presence — where a thing sits), while target= names an event's patient (&Betrays(agent=vale, target=renn)whom an act lands on).

Levels: home plane, loud crossings

Declare a node on its home plane; reference it from another plane via a loud crossing <<home|name>> — never a bare, silent reference. A plane change is always visible in the source. See diegetic levels.

POV planes follow a naming convention. A character's point of view lives on a plane named pov_<charname> — e.g. <<pov_renn : non_canon>>. It's typically non_canon (a perception, not ground truth), and the perspective(renn) lens surfaces it by name. Use it to keep a character's possibly-false world-model on its own plane, apart from canon.

Before you ship

A good file validates clean, carries no undeclared referents (front-load), and — for a story — fires the recognitions it means to.