The Chord Language — Author Reference

A writer-facing reference for Chord, Sharpee's story language (.story files), covering every construct in plain language with a working, compile-checked example. Companion to the formal grammar (chord-grammar.md, chord.ebnf); where they disagree, the grammar and the parser win. Chord v1 (locked 2026-07-14) ships with Sharpee 3.0.

1. Reading a .story file

1.1 What a story file is

A .story file is an optional story header followed by a flat list of declarations. There are only two kinds of top-level things: create blocks, which put entities into the world, and define blocks, which add vocabulary the rest of the story can use (conditions, phrases, verbs, text hatches, traits, actions, sequences). Everything else in the language lives inside one of those blocks, attached to the thing it belongs to. There is no top-level "rules" section: behavior is owned by entities and traits, not floated beside them.

The smallest complete story is a room and a player:

story "Harness Smoke Test" by "Sharpee Docs"
  id: chord-ref-smoke
  version: 0.0.1

create the Reference Desk
  a room

  A quiet corner of the documentation stacks.

create the player
  starts in the Reference Desk

1.2 Lines, blocks, and indentation

Chord is line-oriented: every rule is one source line, and structure comes from indentation. Indentation is spaces only; a tab is a lex.tab-indent error. A block opens with its header line and closes either at dedent or with an explicit end line, depending on the block:

BlockOpens withCloses with
story header fieldsstory linededent
create blockcreate linededent
define phrases <locale>header linededent
define traitheader lineend trait
define actionheader linededent
define sequenceheader lineend sequence
define phraseheader lineend phrase
on / after clauseheader lineend on / end after
selectselect … lineend select
each blockeach <name> lineend each
prose blockfirst bare linededent or blank line

The end line must match its opener: an on clause closes with end on, never end after.

Four kinds of token make up a line. Words are letters followed by letters, digits, apostrophes, hyphens, or underscores (cant-leave and you'd are each one word). Numbers are plain digits with optional dotted parts (3, 1.0.0). Strings are double-quoted with no escape sequences. Markers are {name} forms that appear only inside prose (see §2.6).

2. Building your world

2.1 The story header

The header names the story and carries its identity fields. story and the quoted title are required if a header is present at all; by is optional; the indented lines below are free key: value fields, of which id, version, and blurb are the conventional set. The header may also own story-wide states and scores, using the same two line forms every other owner uses (§2.7, §2.8):

story "The Walled Garden" by "Sharpee Docs"
  id: walled-garden
  version: 1.0.0
  blurb: A pocket world for the Chord language reference.
  states: daylight, dusk
  score explorer worth 5

The story starts in the first declared state (daylight here), and any behavior body can move it forward with change the story to dusk.

2.2 Creating things

create puts one entity into the world. The name after create is the entity's display name; its internal ID is derived mechanically (words lowercased and joined with hyphens, the leading article stripped, so the Potting Shed becomes potting-shed). You never write IDs; you always refer to things by name.

create the watering can
  aka can, tin can, sprinkler
  in the Potting Shed

  A dented tin watering can with a long spout.

aka adds aliases. When a name is referenced anywhere else in the story, resolution tries the exact name first, then an exact alias, then a unique in-order word subset (the can finds the watering can if nothing else matches). Ambiguity or a miss is a load error with a suggestion; Chord never guesses.

2.3 Kinds, traits, and settings

The lines between the create header and the first blank line compose the entity. Each comma-separated term is either a kind noun or a trait, distinguished by the article: a room and a supporter are kind nouns (bundles of traits), while bare words like scenery, wearable, switchable, and light-source are individual traits.

create the workbench
  a supporter with capacity 3
  scenery
  in the Root Cellar

  A scarred oak workbench.

with attaches settings. The last token of each setting is its value and the words before it are the key (capacity 3); several settings join with and. When a value is an entity name, the article marks where the name starts (lockable with key the staff keycard).

A trait can be conditional: while puts it under the control of a condition, live at play time.

create the Root Cellar
  a room, dark while the oil lantern is not lit
  aka cellar

  Earthen walls swallow what little light there is.

The blank line matters: composition lines come before the first blank line in the block, and prose comes after it (§2.6). That rule is what keeps a description that happens to start with a trait-like word from being read as composition.

2.4 Placing and wearing

Three placement forms position an entity when the story starts: in puts it inside a room or container, on puts it on a supporter, and starts in is how actors (usually the player) name their starting room. wears puts a wearable onto an actor already dressed in it.

create the seed packet
  on the potting table

  A paper packet of runner-bean seeds.

create the player
  starts in the Greenhouse
  wears the straw hat

  Mud on your boots, dirt under your nails.

Note the disambiguation: on followed by an article is placement (on the potting table); on followed by a bare word opens a behavior clause (on watching it, §3).

2.5 Exits and blocked exits

Rooms connect with direction lines. Ten directions exist: the eight compass points plus up and down. A connection is one-way as written; write the return exit in the other room.

create the Rose Walk
  a room
  north to the Herb Garden
  east to the Orchard
  south is blocked: hedge-too-thick

  Gravel crunches underfoot between the rose beds.

A blocked exit refuses travel and speaks the named phrase (the key after the colon must be a phrase the story defines, §5.3). The block can be conditional:

create the Orchard
  a room
  west to the Rose Walk
  up is blocked while the ladder is not here: no-way-up

  Apple trees stand in crooked rows.

2.6 Prose, paragraphs, and markers

Prose is any bare paragraph: no keyword, just sentences. In a create block the first bare paragraph after the blank line is the entity's description, and consecutive bare paragraphs append to it as separate paragraphs.

create the Reading Nook
  a room

  A stone bench curves around a sundial{weather}. Ivy softens
  every wall.

  Someone has left a folded blanket at one end of the bench.

Within a paragraph, line breaks are yours to place: lines join with single spaces when the text is rendered, so you can wrap prose at any width. A blank line is a real paragraph break.

A marker like {weather} splices generated text into prose at that spot. Every bare lowercase marker must name something the story declares: a phrase (§5.2) or a text hatch (§5.5). Here weather is a cycling phrase, so the sundial reads differently on different visits:

define phrase weather, cycling
  whose shadow lies crisp in the afternoon sun
or
  whose face dims as a cloud slides past
end phrase

One marker is built in: {br} forces a hard line break with no paragraph gap, for text whose line structure is the point. The name br is reserved; you cannot declare a phrase or hatch with it.

create the sundial plaque
  aka plaque
  scenery
  in the Reading Nook

  The engraving is worn but legible:{br}
  I COUNT ONLY SUNNY HOURS{br}
  EST. 1898

A lone " is ordinary prose punctuation, so dialogue can open in one paragraph and close in another without upsetting the lexer.

2.7 States

An entity (or trait, or the story itself) can declare named states with a states: line. The owner starts in the first state listed. States are the language's replacement for flags and ad-hoc booleans: they are declared, ordered, and visible to conditions (when it is still), select on dispatch (§4.1), and change (§3.7).

create the fountain
  scenery
  in the Orangery
  states, reversible: flowing, still

  A marble fountain in the middle of the floor.

  on touching it
    change it to still when it is flowing
    phrase fountain-stopped when it is still
  end on

State order is a promise. Without , reversible, a change back to an earlier state is a load error (analysis.irreversible-state): a marrow declared states: entire, sliced can never be un-sliced, and the compiler holds you to it. Add , reversible only when the story really does move both ways, as the fountain does.

2.8 Scores live on owners

A score <name> worth N line declares a scoring identity on the owner it appears under: the story header, a create block, a trait, or an action. award <name> grants it from any behavior body.

create the prize marrow
  aka marrow
  in the Orangery
  states: entire, sliced
  score sliced-the-marrow worth 10

  A rosette-winning marrow of absurd size.

  on cutting it
    change it to sliced
    award sliced-the-marrow
    phrase marrow-sliced
  end on

Score identities are owner-scoped: two entities can each declare score fed worth 10 and they are different scores. Awards dedupe by identity, so awarding the same score twice grants it once; there is no need to guard award behind a first-time check. The full scoring model, including trait- and action-owned scores, is in §4.5.

2.9 The first-visit description

A room can carry a first time block: prose shown in place of the standing description the first time the player sees the room, after which the ordinary description takes over.

create the Maze Heart
  a room

  first time
    You did not expect the hedge maze to open out like this: a perfect
    circle of turf, silent, with a single stone chair at its center.

  A circle of turf enclosed by high hedge walls. A stone chair faces
  the way in.

This form is rooms-only and first-time-only: second time and the other ordinals are load errors here. (Inside behavior bodies, the full ordinal family is available; that different construct is §4.3.)

2.10 Per-entity phrase overrides

When a trait or the platform speaks for an entity, the entity can talk back in its own voice. A phrase <key>: line in a create block overrides the text registered under that key, for this entity only. The override can carry a strategy and or variants, exactly like a define phrase (§5.2):

create the garden robin
  aka robin
  scenery
  in the Aviary Corner
  states, reversible: perched, hopping

  A round little robin with one white tail feather.

  on watching it
    change it to hopping when it is perched
    phrase robin-watched
  end on

  phrase robin-watched, cycling:
    The robin cocks its head at you, entirely unimpressed.
  or
    The robin hops once, then pretends you are not there.

  phrase detail while it is hopping:
    It hops from perch to perch as you watch.

The story-wide robin-watched text (defined in define phrases) is the default; the robin's own cycling variants win whenever the robin is the one being watched. Under the hood the override registers a derived key scoped to the entity (garden-robin.robin-watched), which is also how descriptions get their keys (garden-robin.description).

The detail key is special: it appends a live detail sentence to the entity's description and requires a while condition. On ordinary overrides, while is not allowed (analysis.override-gate); use a strategy or variants instead.

3. Giving things behavior

3.1 on and after

Behavior attaches to its owner as an on or after clause inside the owner's create block (or inside a trait, §5.7). The two words divide one moment: on runs before the action commits and may stop it; after runs once it has happened and reacts.

create the hive box
  aka hive, box
  scenery, openable
  in the Apiary
  states: humming, quiet

  A white wooden hive box, lid held down by a brick.

  on opening it
    refuse when it is humming: bees-disturbed
    change it to quiet
  end on

  after opening it
    phrase honey-smell
  end after

The division is enforced: refuse, refuse when, and must are parse errors inside an after body (parse.react-refusal). If a reaction wants to stop something, it is not a reaction; move it to on. Every clause closes with an end line matching its opener.

3.2 What a clause can bind

Three forms follow on or after. The common one binds a verb to the owner: on opening it, where it means this entity for the rest of the clause. The verb is a single word in its -ing form, and it must be a verb the story's actions or the platform's standard actions understand.

The second form listens to a verb aimed at anything, and names the role the clause cares about. Roles belong to the action: taking has a taker and an item, so a scarecrow can watch every theft in the garden. The third form, every turn, ticks with the clock rather than reacting to an action; it is on only, since a turn is not something you react to after the fact.

create the scarecrow
  scenery
  in the Kitchen Garden

  A scarecrow in a donated raincoat.

  on taking anything as the taker
    phrase scarecrow-stare
  end on

  on every turn while dusk, once
    phrase scarecrow-shadow
  end on

An entity-owned every turn clause fires only while the player is present with its owner; the scarecrow performs to an audience or not at all. Note while dusk: a story-level state (§2.1) is referenced as a bare word in conditions.

3.3 while, once, before, after

Every clause form takes an optional while <condition> gate, checked each time the clause would fire. After the gate, comma modifiers may follow. , once limits the clause to a single firing for the whole story, as the scarecrow's shadow moment above shows.

, before <trait> and , after <trait> order clauses when several traits answer the same verb on one entity:

define trait creaky
  phrases en-US
    hinge-creak:
      The hinges creak horribly.

  on opening it, before alarmed
    phrase hinge-creak
  end on
end trait

A cabinet composed openable, alarmed, creaky creaks first, then rings its alarm bell, regardless of the order the traits were listed in.

3.4 Conditions

Conditions appear after while, after when, in refuse when, and in blocked exits. The combinators are and, or, and not, in the usual precedence (not binds tightest, or loosest) with parentheses to override; one chance in N rolls dice.

  on petting it
    phrase cat-tolerates when it is dozing
    phrase cat-glare when it is alert and not one chance in 4
    phrase cat-purr when (it is alert and one chance in 4) or it wears the ribbon
    change it to alert when it is dozing
  end on

The predicates test what an author would point at:

  on examining it
    phrase barrow-contents when it holds the apple
    phrase barrow-visible when the player can see it
    phrase barrow-in-gate when it is in the Orchard Gate
    phrase barrow-is-thing when it is a container
  end on

In full: is a/is an tests kind or trait, is in tests location, is here tests whether an entity shares the player's location, plain is compares against a state, a state adjective, or a value, has, holds, and wears test possession, and can see / can reach test scope. Every predicate takes not after is, or not in front. Subjects and values are written the way you name anything: it, the player, an entity name, its <field>, or a possessive like the player's location. A multi-word name simply runs until the next structural word (is, has, holds, wears, can, and, or, then, to, while, with), so name your entities around those.

State words resolve against their subject: when it is dozing works because the cat declares dozing. The closed catalog of platform state adjectives (open, closed, locked, unlocked, on, off, worn, lit) reads trait state the same way. An unknown word is a load error with a nearest-valid suggestion, not a silent false.

A named condition (§5.1) whose it is left free is an open condition: a test any entity might pass. any and no quantify over one:

define condition gathered-tomato: it is a tomato and it is in the harvest basket
define condition vine-tomato: it is a tomato and it is not in the harvest basket
  on examining it
    phrase basket-empty when no gathered-tomato
    phrase basket-laden when any gathered-tomato
  end on

Quantifiers demand an open condition; any over a closed one (no free it, like the story is dusk) is analysis.closed-condition-selection. Inside an open condition, keep to predicates the analyzer can check for an arbitrary subject: kind, placement, and possession tests. A state test on it (it is ripe) is rejected as an unknown value, because no one subject owns the state word there.

3.5 Requirements: must

A must line states what has to be true for the action to proceed, and names the phrase spoken when it is not. The subject opens the line in lowercase (the, it, or its), which is how a requirement stays distinct from prose. One fixture exercises every form:

  on stocking it
    it must be a tool: not-a-tool
    it must be any loose-tool: already-racked
    the player must be in the Potting Bench: too-far-away
    the player must hold the secateurs: hands-empty
    the player must see the tool rack: too-dark
    the player must reach the tool rack: out-of-reach
    phrase racked
  end on

must be a/an tests kind, must be in tests place, must be any tests membership in an open condition (§3.4), plain must be compares a state or value, must have/hold/wear test possession, and must see/reach test scope. Requirements are legal in on bodies and as define action lines (§5.8), never in after bodies.

There is deliberately no must not. Requirements are positive; prohibitions are refusals, and the two forms sit naturally side by side:

  on sharpening it
    it must be dull: already-sharp
    refuse when the player wears the gardening mittens: mittens-on
    change it to sharp
    phrase sharpened
  end on

Writing must not … (or must be no …) is a parse error (parse.must-negative) whose fix-it points at refuse when.

3.6 Refusals: refuse

refuse stops the action and speaks a phrase. The conditional form refuse when <condition>: <key> refuses only when its condition holds, as the mittens line above shows. The bare form refuses always, for things that are simply never allowed:

  on taking it
    refuse all-thorns
  end on

Refusing on a negated condition (refuse when not …) is flagged (analysis.negated-requirement): that is a requirement in disguise, and must says it better.

3.7 The statements

Nine statements do the story's work. The pumpkin below uses most of them:

  on measuring it
    set its tally to 3
    phrase tape-reading with girth = its tally
    emit pumpkin-measured
  end on

  on picking it
    change it to picked
    award picked-the-champion, once
    move it to the Compost Corner when it is picked
    phrase picked-note
    win harvest-glory when its tally is 3
  end on

3.8 The when suffix

Any of phrase, emit, change, move, remove, award, win, and lose can carry a trailing when <condition>, making that one statement conditional without any block structure; several examples above use it. set and bare refuse do not take the suffix. Do not confuse it with the when <value> arm header inside select on (§4.1): same word, different position, different job.

4. Branching, iteration, and progression

The statements in §3 each do one thing. This chapter is about shaping what happens over time: choosing between several bodies, doing something once per matching entity, keeping score, ending the story, and running a scripted timeline. All of it is still made of the statements you already have; these are the blocks that route between them.

4.1 select on a value

select on <value> dispatches to the arm whose when matches the value, and runs nothing if none do. The classic use is a state machine: one arm per declared state, each speaking for that state.

  on examining it
    select on its state
      when dormant
        phrase bulb-dormant
      when sprouting
        phrase bulb-sprouting
      when blooming
        phrase bulb-blooming
    end select
  end on

The value after on is any value expression (§3.4): here its state, the amaryllis bulb's current state out of dormant, sprouting, blooming. Each when <word> heads an arm whose body is ordinary statements. The block closes with end select, and each or-free arm simply dedents. Coverage is not enforced — an unmatched value falls through silently, which is what you want for "react only to these states."

Do not confuse this arm-header when <word> with the statement-final when <condition> suffix (§3.8). Same word, different job: the suffix gates one statement on a condition, while the arm header names a value to match. select on compares values; it does not evaluate conditions.

4.2 select with a strategy

select <strategy> ignores the world and picks an arm by a rule about how often the block has fired. The arms are separated by or, exactly like the variants of a define phrase (§5.2), because it is the same machinery: the five strategies are the phrase-algebra Choice selectors under author-facing names.

  on ringing it
    select cycling
      phrase chime-bright
        The chime rings a bright, clean note.
    or
      phrase chime-lower
        It answers itself a third lower.
    or
      phrase chime-clatter
        The tubes clatter together, all music gone.
    end select
  end on

The five strategies:

StrategyBehavior
cyclingarms in order, wrapping back to the first forever
stoppingarms in order, then stays on the last
randomlya fresh seeded random arm every firing
stickyone seeded random arm, then that same arm forever
first-timethe first arm once, the second arm ever after

randomly and sticky draw from the same seeded, save-persistent stream as every other random effect in Chord, so a replay or a restored save reproduces the same choices (never Math.random). first-time is the "novelty then routine" shape — a drawer that is a discovery the first time and a shrug thereafter:

  on opening it
    select first-time
      phrase drawer-discovery
        The drawer shrieks open on a jumble of twine, labels, and one
        surprised spider.
    or
      phrase drawer-routine
        The drawer shrieks open. The spider has moved out.
    end select
  end on

The retired adverbs ordered and once are gone (§6.3): ordered is now stopping, and there is no phrase-strategy once — its old job is covered by first-time and stopping. The clause modifier , once (§3.3) is a different construct at a different position and is untouched.

4.3 Ordinal blocks

An ordinal block runs its body only on the Nth time the enclosing clause fires. The ordinals first time through tenth time appear in statement position, interleaved with ordinary statements:

  on knocking it
    phrase knock-echo
    first time
      phrase knock-first
    second time
      change it to smudged
      phrase knock-second
    fifth time
      phrase knock-fifth
  end on

Every knock speaks knock-echo; the ordinal blocks add to it on the first, second, and fifth knock only. The ordinals need not be contiguous — jumping first, second, fifth is fine, and the gaps simply do nothing. Each block dedents to close; there is no end keyword.

This is the same keyword pair as the create-block first time description (§2.9), and the collision is worth naming. Inside a create block, at the top level of the block, first time opens the room's first-visit description and only first time is legal there. Inside a behavior body, first time opens an ordinal block and the whole family through tenth time is available. Position decides which one you get: a first time directly under create is a description, a first time inside on/after is an ordinal.

4.4 each blocks

each <condition> runs its body once for every entity that satisfies a named open condition (§3.4) — a condition whose it is left free, so it describes a set rather than testing one thing. Inside the body, the match is the current entity; it keeps meaning the clause's owner.

define condition frame-seedling: it is a seedling and it is in the cold frame
define condition labeled-pot: it is a pot and it is in the potting shed
  on planting it
    each frame-seedling
      move the match to the Nursery Bed
      phrase planted-out with plant = the match
    end each
    phrase planting-done
  end on

Each matching seedling is moved and announced; then, after the loop, planting-done speaks once. The body is ordinary statements, so move, change, phrase, and the rest all work, and the match is a value you can pass as a parameter (with plant = the match) or use as a move/change target. The iteration order is the order entities were created, and an empty match set simply runs the body zero times.

each blocks nest. The binder the match always refers to the innermost loop:

  on auditing it
    each frame-seedling
      each labeled-pot
        phrase audit-pair with pot = the match
      end each
    end each
  end on

each is legal wherever statements are — on bodies, action bodies, trait clauses, sequence steps — and never at the top level (parse.each-top-level). Its condition must be a declared open condition; a closed condition or a story state is analysis.closed-condition-selection, and the match outside any each body is analysis.match-outside-each. match is a reserved name: you cannot call an entity, alias, trait field, or grammar slot match.

4.5 Scoring

A score <name> worth N line declares a scoring identity, and award <name> grants it (first seen in §2.8). The line is legal under four owners — the story header, a create block, a define trait, and a define action — and this section shows all four in one story, because where a score lives is a real design choice.

The story header owns scores that belong to the whole game:

story "Scoring" by "Sharpee Docs"
  id: chord-ref-scoring
  version: 0.0.1
  score green-thumb worth 5

A trait owns a score every entity composed with it can grant, without the trait knowing which entities those are:

define trait prunable
  score tidy-secateurs worth 5

  phrases en-US
    pruned:
      You snip away the dead wood.

  on pruning it
    award tidy-secateurs
    phrase pruned
  end on
end trait

A create block owns a score specific to that one entity — and here is the payoff of owner-scoping. The damask rose and the moss rose both declare score deadheaded worth 5, and those are two different scores, one per rose, because each is owned by its own entity:

create the damask rose
  aka damask
  prunable
  in the Rose Border
  score deadheaded worth 5

  A crimson damask, badly overgrown.

  after pruning it
    award deadheaded
    award green-thumb
  end after

The damask's after clause awards its own deadheaded, and also the story-wide green-thumb. award <name> resolves owner-first: it looks for a score of that name on the enclosing owner, then falls back to the story header. So the same word deadheaded under the damask means the damask's score, and under the moss rose means the moss rose's — no collision, no prefixing.

An action owns a score granted whenever the action succeeds:

define action composting
  grammar
    compost :stuff
  score turned-the-heap worth 10
  award turned-the-heap
  phrase composted
  phrases en-US
    composted:
      Straight onto the heap with it.

Two properties fall out of this model. First, the maximum score is the sum of every declared worth, computed at load time. Second, awards dedupe by identity (ADR-129): awarding the same score twice grants it once, so award is idempotent and the explicit , once modifier (§3.7) is never needed for correctness — it only documents intent. This is why the pattern is "declare the score on the owner, award it in an after clause" rather than guarding awards behind first-time checks.

4.6 Endings: win and lose

win and lose end the story. Each optionally names a phrase to speak on the way out, and each takes the statement-final when suffix (§3.8), so an ending can be conditional without a wrapping block.

  on pulling it
    remove it
    win garden-perfect
  end on
  on poking it
    lose stung when one chance in 2
    phrase near-miss
  end on

The first ends in victory, speaking garden-perfect. The second loses only on a coin-flip (when one chance in 2); when the flip spares you, the lose is skipped and near-miss speaks instead. The phrase name is optional — bare win and bare lose end the story with only the platform's default ending text.

4.7 Sequences

A define sequence is a scripted timeline: a named list of steps, each anchored to when it fires and carrying a body of ordinary statements. It replaces the hand-rolled turn-counting daemons of the old platform with one declarative block. There are three anchor forms, and this story uses all three across three sequences.

The first two anchors are clock-based. at turn N fires at an absolute turn counted from the story's start; N turns later fires relative to the previous step, so you can chain a timeline without arithmetic:

define sequence gathering storm
  at turn 2
    phrase storm-distant
      Thunder mutters somewhere beyond the wall.
  3 turns later
    phrase storm-near
      The light goes pewter, and the first fat drops crater the dust.
  2 turns later
    phrase storm-breaks
      The storm breaks all at once, rain hammering the gravel.
    change the story to raining
end sequence

The steps here land on turns 2, 5, and 7. A step body is ordinary statements: the last step both speaks and moves the story into its raining state.

The third anchor is state-based. when <name> becomes <state> fires the step when that owner transitions into the named state — the story itself, or any entity. This lets one sequence react to another's effect. The storm above moved the story to raining; a second sequence waits on exactly that:

define sequence take shelter
  when the story becomes raining
    phrase shelter-note
      Anywhere with a roof suddenly seems like a very good idea.
end sequence

The same anchor works on an entity's own states. When the bonfire is lit and changes to burning, a third sequence notices:

define sequence smoke drifts
  when the bonfire becomes burning
    phrase smoke-note
      A ribbon of blue smoke begins to drift across the garden.
end sequence

A sequence name may be several words (gathering storm, take shelter), and the block always closes with an explicit end sequence. Within a step, prose indented under a phrase line is declare-and-emit text (§3.7), which is why these steps need no separate define phrases block.

5. Defining vocabulary and text

Everything so far attaches behavior to things that already exist. The define family is how you add to the language itself: name a condition, name a bundle of text, teach the parser a verb, mint a trait or an action other entities can be composed with. These are the only other top-level declarations besides create, and unlike create they declare kinds and vocabulary, not instances in the world.

5.1 define condition

define condition <name>: <condition> names a condition so you can reuse it by name. The name is a single hyphenated token (not a multi-word prose name), and the body is the ordinary condition grammar (§3.4).

define condition trug-in-hand: the player holds the trug
define condition ripe-tomato: it is a tomato and it is not in the trug

The distinction that matters is open versus closed, and it is decided entirely by whether the body mentions it:

Using one where the other belongs is a load error, never a silent guess: any over a closed condition is analysis.closed-condition-selection, and a bare open condition in a truth position is analysis.open-condition-truth. The rule of thumb is "mention it when you mean a set, leave it out when you mean a fact."

5.2 define phrase

define phrase gives a phrase key one or more or-separated variants and a strategy for choosing between them, closing with end phrase. The strategy is the same five-name set as select <strategy> (§4.2), because it is the same machinery: select chooses between statement bodies, define phrase chooses between texts.

define phrase parrot-chatter, randomly
  Polly wants a cracker!
or
  SQUAWK! Pretty bird! Pretty bird!
or
  Pieces of eight! Pieces of eight!
end phrase

Each variant is a prose block (§2.6) — the only form phrase text takes; the old same-line quoted and bare forms were removed (§6.3). Blank lines inside a variant are paragraph breaks, and {br} still forces a hard line break.

A phrase header may carry a trailing while <condition> gate. This is mainly for description-marker phrases (§2.6): the gate decides whether the fragment appears at all, live at render time. Here a keeper line only splices into the aviary description while the zookeeper is actually present:

define phrase keeper-note, cycling while the zookeeper is here
  where a keeper is refilling the feeders
or
  where a keeper is chalking today's talks on a board
end phrase

Fragments spliced into prose are written bare — no leading separator and no trailing full stop. The platform owns the join: a marker mid- sentence joins with a comma, a marker after a sentence joins with a space. Writing the separator yourself earns a diagnostic.

The verbatim modifier (mutually exclusive with the strategies) exempts a phrase's text from whitespace collapse, preserving its exact line structure and relative indentation — for signs, verse, and ASCII where the layout is the point:

define phrase plaque-text, verbatim
  MACAWS OF THE AMERICAS
    donated by the Willowbrook Trust
  Please do not tap the glass
end phrase

A verbatim phrase can be spoken by a phrase statement but cannot splice at a description marker (analysis.verbatim-marker): its whole point is preserved line structure, which a mid-sentence splice would break.

5.3 define phrases (locale blocks)

Where define phrase declares one key with variants, define phrases <locale> declares many plain keys at once, under a named locale. This is the localizable form — the same block stdlib uses — and the one to reach for when your text is a flat catalog of keyed strings rather than a single choice:

define phrases en-US
  rack-front:
    The front of every card is the same beaming flamingo.
  rack-back:
    Each back is pre-stamped, as if the zoo expects you to write home
    before you have even seen it.

Each entry is key: on its own line followed by an indented prose block. The block is dedent-terminated — there is no end phrases. A key here is a plain phrase with no strategy; for variants or a strategy, use define phrase (§5.2) instead. Throughout this reference the small supporting texts live in exactly this kind of block at the foot of each fixture.

5.4 define verb

define verb <word> {or <word>} means <pattern> teaches the parser a new surface verb by mapping it onto an existing action's pattern. A (something) in the pattern is a slot the parser fills from the player's input:

define verb hang or hook means put (something) on (something)
define verb sniff means smell (something)

hang the jacket on the hook and hook the jacket on the hook now both route through the standard put … on … action; sniff the jacket routes through smell. This is verb vocabulary only — aliases onto patterns the platform already understands. To define a genuinely new action with its own grammar and behavior, use define action (§5.8).

5.5 define text (text hatches)

Some text cannot be authored as static variants — it depends on computation the language deliberately does not do (§2.5's "no counting"). A text hatch bridges to a small piece of TypeScript that produces the text. define text <name> from "<module>" binds a marker name to a named export of an author-supplied module:

define text weather from "./extras.ts"

{weather} in prose now renders whatever the module's weather export produces. The TypeScript side implements the platform's PhraseProducer contract and touches the world only through the narrow, typed context the loader hands it:

import type { PhraseProducer } from '@sharpee/if-domain';

export const weather: PhraseProducer = () => ({
  kind: 'literal',
  text: 'something ominous with the clouds',
});

The name br is reserved and cannot be a hatch name. One limitation to state plainly: this reference's compile-check harness runs the Chord compiler, which validates the define text line and the marker binding but does not load the TypeScript module — hatch binding happens at story-load time, under the author's own build. So the .story side of every hatch example here is verified; the TypeScript stub is illustrative, checked by the author's toolchain, not by this document's harness.

5.6 define action/behavior hatches

Text is one hatch kind; the other two bridge to whole actions and capability behaviors, for logic genuinely outside the language. The syntax mirrors the text hatch exactly:

define action dowsing from "./extras.ts"
define behavior tide-clock from "./extras.ts"

define action … from binds an action implementing the platform's Action interface; define behavior … from binds a CapabilityBehavior (ADR-090). Both are governed by the hatch legitimacy rule (design.md §5.6): a hatch is legitimate only when it implements a public platform interface, or does pure non-IF computation, with data crossing the boundary through that interface. If the language can already express what the hatch does, the hatch is misuse and the fix is the Chord form; if it can't, that is a language gap worth filing. In particular, the chord.* state namespace is off-limits — those keys are the loader's private encoding and change without warning. As with text hatches, the binding is out of this harness's scope; the .story lines above compile, and the TypeScript is the author's to build and verify.

5.7 define trait

A trait is a reusable bundle of data, states, scores, phrases, and behavior that entities compose with (§2.3). define trait <name> … end trait declares one. The block below carries one of everything:

define trait feedable
  data
    food: entity
    ration: optional number
    diet: one of hay, seed, fish
  states, reversible: hungry, content
  score fed worth 10

  phrases en-US
    no-food:
      You have nothing {the item} would want.
    already-fed:
      {capitalize the item} has had quite enough already.
    fed:
      The feed vanishes in seconds.

  on feeding it
    the player must have its food: no-food
    it must be hungry: already-fed
    change it to content
    award fed
    phrase fed
  end on
end trait

The parts, top to bottom:

An entity picks the trait up on a composition line, passing its data with with:

create the pygmy goats
  aka goats
  feedable with food the handful of feed and diet hay
  in the Petting Zoo

A state name declared by two different composed traits on one entity is a collision (analysis.state-collision), caught at load time.

5.8 define action

define action <name> … end (dedent-terminated) declares a brand-new action: its grammar, its requirements, its refusals, and its body. This one action shows the whole surface:

define action petting
  grammar
    pet :animal
    pat :animal
    stroke :animal → each nearby creature
  the animal must be reachable
  score gentle-hands worth 5
  refuse without animal: pet-what
  refuse when the animal is a snake: glass-way
  otherwise refuse cant-pet
  award gentle-hands
  phrase petted

  phrases en-US
    pet-what:
      Pet what?
    glass-way:
      You settle for pressing a hand to the cool glass.
    cant-pet:
      {capitalize the animal} really is not the sort of thing you can pet.
    petted:
      You give a good long scritch.

Reading it line by line:

The relationship to traits: define action declares the verb, its grammar, and its default outcomes; a define trait with an on <verb> clause is how a particular kind of thing responds to that verb. Together they are the story-author's version of stdlib's action-plus- behavior pattern — the goats above are pettable because petting exists as an action and something makes the goats respond to it.

6. Tooling

6.1 sharpee compose

sharpee compose is the command that turns a .story file into Story IR and, in the process, runs every load-time gate. It is the author's version of the compile check this whole reference is built on.

sharpee compose <file.story> [--check] [-o <ir.json>]

There are two modes. Plain sharpee compose file.story runs the gates and then goes further: it binds any hatches, builds the world, and creates the player, so that "it composed" means "it actually loads." On success it writes the IR as JSON to standard output (or to a file with -o). sharpee compose --check file.story stops after the gates — no IR is emitted and no load is attempted. --check is the CI form: the fast yes/no you run to know a story is well-formed.

The output streams are split on purpose. Standard output carries only the IR JSON, so -o and a shell redirect produce clean machine-readable output; every human-facing line — diagnostics, the pass/fail summary, the load report — goes to standard error. That split is what lets sharpee compose file.story > story.ir.json do the right thing.

The exit code is the contract for scripts:

CodeMeaning
0gate-clean (and, in full mode, the IR loaded)
1the story failed the load-time gates, or a hatch source broke a rule
2a usage error — no file, missing argument, unknown flag

6.2 Reading diagnostics

Every gate speaks the same line format:

<file>:<line>:<column> <severity> [<code>] <message>

For example:

zoo.story:42:3 error [analysis.irreversible-state] `intact` is
declared before `trampled`; a `change` back is a load error unless the
set is `states, reversible: …`.

The <code> is the stable identifier — the same code this reference cites throughout (analysis.irreversible-state, parse.react-refusal, and so on) — and the <message> usually carries the fix. error severity means the story will not load; warning means it loads but something reads oddly (the fragment-terminator warnings on description markers are the common one).

The governing idea is that loading is atomic: a story either passes every gate and produces usable IR, or it fails and produces none. There is no partial load. This is why the reference can promise that a compiling example really works — "compiles" is not a syntax check, it is the full parse-and-analyze pass that the runtime itself depends on.

The parser is built to give you one diagnostic per mistake rather than a cascade. After it reports a parse error, it resynchronizes at the next end line or top-level keyword and carries on, so a single missing end on produces a single message pointing at the clause, not a wall of confused errors from everything after it. When you do see several diagnostics at once, they are genuinely several problems — fix the first and recompile.

The gates come in two families, distinguished by their code prefix. parse.* gates are structural: a tab in the indentation, a missing end, a removed construct (§6.3). analysis.* gates are semantic — they fire after a clean parse, when the shapes are right but the meaning is not. The analyzer gates you are most likely to meet, each named where it is first explained:

The three-ring boolean-state gate deserves its own mention, because it catches the single most common instinct carried over from other systems: reaching for a boolean. If you declare a state set that looks like a flag — true/false, a platform pair like open/closed, or a negation pair like fed/unfed — the analyzer refuses it (analysis.boolean-state, analysis.shadow-state, analysis.negated-state) and tells you to name what the thing is in each state, or to compose the trait that already owns that state. A state names a condition of the thing, never the absence of another state.

6.3 Migrating from removed constructs

Chord's grammar is a one-way ratchet: constructs that were removed on the way to v1 are not merely undocumented, they are actively refused. Each removed form produces a specific parse.* error whose message names its replacement, so a story written against an older sketch fails loudly and points the way, rather than misbehaving quietly. Every row below is backed by an expected-to-fail fixture in this reference's harness, which asserts the exact code still fires.

Removed formDiagnostic codeReplacement
top-level when <actor> <verb>s … ruleparse.removed-whenan on/after clause on the owner (§3.1)
top-level once <condition> ruleparse.removed-oncethe , once clause modifier (§3.3)
top-level every N turns ruleparse.removed-everya define sequence (§4.7) or every turn clause (§3.2)
define flag <name>parse.removed-flagowner states: (§2.7) or a derived condition (§5.1)
flag trait-field typeparse.removed-flag-fieldtrait states (§5.7)
if / else / end ifparse.removed-ifmust (§3.5), the when suffix (§3.8), or select (§4.1)
top-level define score … worth Nparse.removed-scorean owner-attached score line (§4.5)
ordered / once phrase strategyparse.phrase-strategy-retiredstopping / first-time (§4.2)

The through-line is stickiness (design.md, given 9): every one of these removals took a floating, top-level behavior and re-homed it on the thing it is actually about. A when rule floated beside the world; an on clause lives on the room it reacts in. A global flag floated in no one's namespace; a states: line lives on its owner. The migration is almost always "find the thing this was really about, and move it there."

The richest case is if, because a single removed construct fans out to three replacements. This no longer parses:

  on opening it
    if it is locked-fast
      phrase clunk
    end if
  end on

It reports parse.removed-if, and which replacement you want depends on what the if was doing. If it was guarding the whole action — this may not proceed unless a condition holds — it becomes a must requirement (§3.5). If it was making one statement conditional, it becomes that statement plus a when suffix (phrase clunk when it is locked-fast, §3.8). If it was choosing between several bodies by a value, it becomes a select (§4.1). The removed if had all three jobs; Chord gives each its own, clearer form.

Appendix: the formal grammar

This reference is the narrative companion to Chord's formal grammar, not a second source of truth. Where this document and the grammar disagree, the grammar and the parser win.

The one structural fact worth restating here is the block-closing table from §1.2: some blocks close on dedent (create, define action, define phrases, story header), and some close with an explicit end line that must match its opener (define traitend trait, define sequenceend sequence, define phraseend phrase, onend on, afterend after, selectend select, eachend each). When in doubt about whether a block needs an end, that table in §1.2 is the authority.