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). 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. 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:
analysis.irreversible-state: achangebackward through a non-reversiblestate set.analysis.state-collision: two composed traits declaring the same state name.analysis.closed-condition-selection:any/no/eachover a closed condition.analysis.open-condition-truth: a bare open condition used as a truth test.analysis.negated-requirement:refuse when not …, which is a requirement in disguise; usemust.analysis.unbound-marker: a{marker}naming no declared phrase or hatch.analysis.remove-player:remove the player, which has no defined meaning.analysis.match-outside-each:the matchused outside aneachbody.
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.