跳到主要内容

语言指南 · Story DSL v1

把故事写成可读的源码

Lattice 是 TypeApe 的声明式故事语言。它同时记录可读正文与正文周围的精确结构,因此同一个项目既能编辑,也能校验和体验。

1

故事身份与项目元数据

2

段落一个可阅读的叙事单元

3

文本写在花括号里的正文

4

流转显式连接,绝不依赖文件顺序

使用完整语法参考

本页负责建立语言心智模型。需要查精确声明形态、允许值、所有权规则或错误修复方式时,使用以下四章:

  1. 源码文件与声明——导入、注释、分隔符、标识符、字符串、正文、元数据、Story 字段、实体和 Scene。
  2. Block 与线性流转——所有 Block 字段与 purpose、叙述、Dialogue 展示、连续性批注、终结语句和完整线性约束。
  3. CYOA 语法参考——选择、Predicate 模式、落地 State、Group、初始 State、Guard、Pool、Module、Effect 和穷举运行时规则。
  4. 校验与诊断——parse 与 validate 的区别、错误恢复、引用错误、软锁、运行时循环、警告,以及校验能做什么、不能做什么。

TypeApe 当前读写 Story DSL v1。规则不成立时,诊断会指向相关源码,并说明需要修复什么。

文件、导入与值

配置的入口文件是项目根。使用带引号的相对路径拆分项目:

text
# main.la
use "./story/characters.la";
use "./story/opening.la";

载入所有导入后,项目只能有一个 story 声明。导入必须留在项目内,不能形成循环,也不能重复载入同一个文件。

Lattice 只使用少数几种值:

形式示例含义
标识符openingch_mara稳定 ID 与引用
字符串"zh-CN"精确的带引号文本
正文{ 一束光苏醒。 }可包含换行的叙事文本
列表[opening, resolution]有序元数据值
布尔值 / 数字true3扩展或事件允许的字面量

注释以 # 开始。导入、没有主体的声明、内容条目和流转语句都以分号结束。元数据属性用逗号分隔。自定义元数据键必须以 x_ 开始;它会被保留,但不会改变内置行为。

Story、实体与场景

Story 声明确定阅读入口和项目格式:

text
story main (
  title: "信号花园",
  language: "zh-CN",
  format: novel,
  entry: opening,
  prose_unit: non_whitespace_graphemes,
  prose_scope: authored_body,
  prose_minimum: 4500,
  prose_target: 5000,
  prose_maximum: 5500,
  beat_order: [opening, midpoint, resolution]
);

title、带引号的 languageformatentry 为必填项。initial_state 只适用于 CYOA。

可选的正文目标用于持久记录创作要求。prose_unitprose_scopeprose_target 必须一起声明。word_like 统计类单词单元;中文“字数”请求适合使用 non_whitespace_graphemes。可选的最小值和最大值必须包含目标值。

实体为角色、地点和物品提供稳定身份:

text
entity character ch_mara (
  name: "玛拉",
  role: protagonist,
  traits: [observant, stubborn]
);

entity location loc_garden (name: "信号花园");
entity item it_key (name: "信号钥匙");

场景组织叙事语境。pov 必须引用角色,location 必须引用地点:

text
scene garden (
  title: "花园",
  beat: opening,
  pov: ch_mara,
  location: loc_garden,
  prose_target: 1200
) {
  # Block 写在这里。
}

Block、正文与对白

Block 是流转能直接寻址的最小段落。达到发布就绪时,它需要全局唯一 ID、标题、叙事目的、可见内容,以及恰好一个结束流转。在大纲阶段,正文可以暂时为空,同时检查稳定 ID、摘要、流转和可选的正文预算。

text
block opening (
  title: "警告",
  purpose: setup,
  summary: { 玛拉找到了信号源。 },
  prose_target: 500,
  setup: [signal_arc]
) {
  text: {
    一束光从花园地下苏醒。
  };

  dialogue ch_mara (
    mode: whisper,
    emotion: cautious,
    stage_direction: { 她俯身贴近土壤。 }
  ): { 别再来了。 };

  next -> answer;
}

对白说话者必须是已经声明的角色。mode 可以是 speechthoughtwhispershout;位置、情绪、表情、姿势和舞台提示都是可选的展示元数据。

叙事连续性批注

setuprequiresresolves 标记轻量的叙事承诺。它们帮助校验铺垫与回收,但不会创建路径,也不会改变 CYOA State。

结构化事件可以留下更丰富的创作证据:

text
foreshadowing fs_signal: event {
  subject: ch_mara,
  object: it_key,
  predicate: notices
};

reveals rv_signal resolves fs_signal: event {
  subject: ch_mara,
  object: it_key,
  predicate: understands
};

线性故事流转

每个线性 Block 都以一次自动继续或一个结局结束:

text
next -> target_block;
text
end;

配置的入口必须沿一条无分支、无环的链到达唯一结局。线性故事会拒绝选择、合流、孤立 Block、循环、State、Pool 和 Module。

CYOA 选择与结局

CYOA 可以分支,也可以合流。普通选择至少有两个选项:

text
choice {
  option inspect_key: { 检查钥匙 } -> inspect;
  option leave_garden: { 离开花园 } -> departure;
}

选项标签描述读者能理解的意图。选择会改变路径拓扑,但不会自动记住读者选了什么。后续规则如果依赖这个决定,应把它建模为已经声明的有限 State。

有限 State

CYOA State 有意保持有限和封闭:没有脚本、变量、计数器、随机数或任意表达式。

先声明 Predicate 模式,再声明一个完全落地的 State:

text
predicate knows (subject: character, object: item);

state st_knows_signal (title: "已经理解信号"): event {
  subject: ch_mara,
  object: it_key,
  predicate: knows
};

不属于组的 State 是永久标记,用 add 激活。State Group 是互斥插槽,用 set 切换成员:

text
state group location (
  members: [st_location_gate, st_location_garden]
);

Guard 使用 State ID 列表:

  • requires:列出的 State 必须全部激活。
  • requires_any:至少有一个列出的 State 激活。
  • forbids:列出的 State 必须全部未激活。

Pool 与 Floating Module

Pool 会暂停主路径,并展示符合条件、只能完成一次的 Module。只有退出 Guard 成立后,主路径才会继续。

text
pool investigation (
  title: "调查花园",
  exit_requires: [st_knows_signal]
);

scene garden (title: "花园") {
  block arrival (title: "抵达", purpose: setup) {
    text: { 埋藏的钥匙在玛拉手下搏动。 };
    enter investigation -> departure;
  }
}

Module 只属于一个 Pool,不能跳到主故事或其他 Module:

text
module md_decode (
  title: "解读钥匙",
  pool: investigation,
  entry: decode,
  forbids: [st_knows_signal]
) {
  scene decoding (title: "埋藏的编码") {
    block decode (title: "解读", purpose: development) {
      text: { 光芒逐渐显现为一串耐心的序列。 };
      complete (add: [st_knows_signal]);
    }
  }
}

进入 Pool 以及每次完成 Module 后,TypeApe 都会先检查退出条件,再展示仍符合条件的未完成 Module。如果退出为假且没有 Module 可用,故事会发生软锁,校验也会失败。

解析、检查、校验、体验

每完成一组连贯源码修改,都按相同顺序检查:

  1. 解析配置的入口。
    先修复语法与导入诊断,再解释后续阶段。
  2. 校验并检查大纲。
    正文尚未完成时,检查结构、创作契约、精确正文进度和最大的局部缺口。
  3. 按发布标准校验。
    恢复可见内容要求,修复所有错误,并根据预期体验判断每一条警告。
  4. 阅读受影响的路径。
    一张有效的图仍然可能偏离创作意图。

除非身份本身必须改变,否则应保留稳定 ID。TypeApe 报错时,应修复真正的源码问题,而不是用 x_ 元数据隐藏错误。

下一篇:阅读完整的源码与声明参考