Imports (uses:)
The run/user/app state schema is game/season-global — one persisted value cannot have a
different type per scene — so it lives in a single source-of-truth schema document that each scene
imports, rather than being redeclared everywhere. That import is the uses: frontmatter key.
---kind: scenecharacter: elenaseason: 1episode: 3uses: state.schema.yaml---
## Carrying the Choice Forward
@narrator: Previously, a choice was made.
<match subject="run.choseHelp"> <when is="true"> @elena: Thanks for helping me back then. </when> <otherwise> @elena: ... </otherwise></match>(From docs/examples/carry-ep.lute.) The scene reads run.choseHelp, which is declared in the imported
state.schema.yaml — not inline.
Declaration files
Section titled “Declaration files”A .schema.yaml file is a plain YAML declaration map — no --- envelope, no body, just
state: / defs: (and, for the relational layer, entities: / relations: / facts: / rules:).
It carries no character/season/episode, because it is imported and validated in import mode,
never run as a scene.
state: run.choseHelp: { type: bool, default: false } user.level: { type: int, default: 1 }defs: helped: "run.choseHelp" atLeast: { type: bool, params: { n: int }, cel: "user.level >= n" }A def is its CEL body as a string — helped: "run.choseHelp" — and its type is inferred from that
body. Since dsl 0.26.0 §2.7 a body that is a visited('…') or validAt(…) call infers bool, so
pierSeen: "visited('south.pier')" needs no long form. A def with params:, or one whose type the
checker cannot infer, takes the long form { type: bool, cel: "…" }; any other shape is
E-DEF-DECL. For a def in an imported schema, whole-project lute check reports E-DEF-DECL once, at the
schema line, ending (imported by N documents), rather than at every importing document.
Import paths are resolved relative to the importing scene file, so a scene and its schema must
travel together (copying a scene to /tmp without its schema reports E-USES-NOT-FOUND).
Only genuinely scene-local scene.* declarations may appear inline in a scene’s state: block. A
scene must not redeclare or override an imported run/user/app path.
The import DAG
Section titled “The import DAG”uses:, extends:, and
components: all share one import discipline — they form a
directed acyclic graph:
- Cycles are a static error — the diagnostic prints the offending chain (
E-USES-CYCLE). - Imported schemas are loaded and checked before any scene.
- Duplicate
defsnames across imports are an error — no silent shadowing. - Import refs are canonicalized, so two paths to the same file are one schema identity, and a file reached by two routes (a diamond) is deduped, not double-declared.
uses: unions peers: a name declared by two peer schemas is a conflict. To refine rather than
union — overriding a base’s declaration with your own — use extends: instead, whose
lower-precedence base layering and override rules are covered in
Components & extends.
---kind: scenecharacter: elenaseason: 1episode: 4uses: child.schema.yaml---(From docs/examples/extends-demo.lute, whose child.schema.yaml itself extends: a base — the DAG runs several files deep.)
Missing files, cycles, and parse errors on any imported file — schema or component — surface through
the shared E-USES-{NOT-FOUND,CYCLE,PARSE} diagnostics.
Project defaults
Section titled “Project defaults”A project whose every document imports the same schemas says so once, under defaults: in
lute.project.yaml. Every document under the manifest inherits the keys, and uses: paths there
resolve against the manifest’s directory:
defaults: luteVersion: "0.30.0" uses: - schema/world.schema.yaml - schema/items.schema.yaml - schema/areas/*.schema.yaml questTier: runSince dsl 0.26.0 §2.4:
- A
defaults.usesentry may be a glob (*,**). It expands in path order, so each area of a project written by several authors keeps its ownschema/areas/<area>.schema.yaml, and a new area needs no manifest edit. A glob that matches nothing, over an empty directory or one that does not exist yet (git keeps no empty directory), imports nothing and is no error. A literal path must exist. questTier: run | usersets thetier=of every<quest>that writes none (see<quest>). Without it the default staysuser.
The files a glob brings in are peers like any other uses: import: a name two of them declare is
still a conflict, except that an entity kind may be extended with add:.
See Multi-author projects for how the areas divide the schema.