Flamesnake

TaleSpindle

Community Flamesnake
Updated

ai-text-engine: 给 AI 的文字冒险引擎 + MCP 服务器(AI 通过 MCP 工具创建剧情、校验并导出单文件 HTML 游戏)

TaleSpindle

An agent-first interactive narrative engine. TaleSpindle lets Claude, Codex, Cursor, and otherMCP-capable agents create, validate, test, and export self-contained HTML text adventures.

  • 剧情 = 纯 JSON 数据:AI 用工具增删节点,无需写代码;
  • 通用机制:变量 / 道具 / 旗标 / 条件选项 / 节点进入效果,够做悬疑、RPG、怪谈等题材;
  • 单文件导出story_export 产出一个自包含 index.html,双击即玩、可发任何人、零依赖;
  • 质量闭环:静态校验、结局见证、失败路线、DOM 重放与体验评估共同发现结构和交互问题;
  • 推理与演出:内置证据、推论、谜题、人物关系、受控富文本和轻量声画效果;
  • 宿主无关:核心是普通 Node.js + MCP 服务,未来可以通过薄适配层接入不同 Agent Harness。

设计与创作参考:叙事文本与节点连续性 · 世界/阶段状态

安装

正式 npm 包名为 @marianaj/talespindle。普通用户不需要 clone 仓库、运行构建,也不需要安装 DeepSeek Harness;只需 Node.js 20+ 和一个支持 stdio MCP 的 Agent 客户端。

Codex:三步安装

# 1. 检查 Node、数据目录、预构建运行时和 MCP
npx -y @marianaj/talespindle@latest doctor

# 2. 安装配套创作 Skill
npx -y @marianaj/talespindle@latest install-skill --client codex

# 3. 把 npm 包持久注册为 Codex MCP server
codex mcp add talespindle -- npx -y @marianaj/talespindle@latest mcp

完成后重启或刷新 Codex,在新任务中检查是否出现 story_newstory_validatestory_exportstory_ 工具。Skill 可见但没有这些工具,说明 MCP 尚未连接。

升级时无需重新注册 MCP;重新安装 Skill 即可同步新版创作流程:

npx -y @marianaj/talespindle@latest doctor
npx -y @marianaj/talespindle@latest install-skill --client codex --force

Codex 与通用 Agent Skill 默认安装到 ~/.agents/skills/talespindle-authorinstall-skill 也支持 --client agents--client claude;已有同名 Skill 时,只有显式传入 --force 才会覆盖。

其他 MCP 客户端

Claude Desktop、Cursor 等使用 JSON 配置的客户端可注册同一条 npm 命令:

{
  "mcpServers": {
    "talespindle": {
      "command": "npx",
      "args": ["-y", "@marianaj/talespindle@latest", "mcp"]
    }
  }
}

使用 TOML 配置的客户端可采用:

[mcp_servers."talespindle"]
command = "npx"
args = ["-y", "@marianaj/talespindle@latest", "mcp"]
enabled = true
startup_timeout_sec = 30
tool_timeout_sec = 360

不同客户端读取的配置位置不同;仓库根目录的 .mcp.json 不能证明客户端已经注册服务器。已有的 ai-text-engine MCP 键名和 CLI 别名仍兼容,但新安装统一使用 talespindle

作品目录

npm 版本默认把作品写到操作系统用户数据目录,不会写进 node_modules。设置 TALESPINDLE_HOME 可指定数据根目录,作品位于其 projects/ 子目录;旧变量 AI_TEXT_ENGINE_HOME 仍兼容。

npx -y @marianaj/talespindle@latest init --home C:/path/to/talespindle-data

仓库自身的 projects/ 是开发者本地创作区,已被 Git 整体忽略;其中的 story.json、导出 HTML 和个人作品不会进入源码提交或 npm 包。

从源码开发

npm install
npm run build
npm test
npm run mcp
npm run check:release
npm run check:package

构建后也可直接使用本地 CLI:

node dist/cli.js doctor
node dist/cli.js install-skill --client codex
codex mcp add talespindle-local -- node E:/path/to/talespindle/dist/cli.js mcp

配套 Skill 源文件位于 skill/SKILL.md。客户端加载的是已安装副本;修改 Skill 源文件后需要重新运行 install-skill --forcenpm run check:release 会读取本机 projects/ 做兼容性回归,但不会复制、修改或上传作品。

验证

node scripts/verify-mcp.mjs      # stdio 握手 + 工具清单(应输出 VERIFY OK)
node scripts/demo.mjs            # 端到端演示:AI 全流程构建《迷雾车站》并导出
node scripts/verify-export.mjs   # 验证导出 HTML 内嵌剧情可玩
node scripts/build-integrated-mystery.mjs   # 通过 MCP 构建综合悬疑样板《雨夜遗嘱》
node scripts/verify-integrated-mystery.mjs  # 实际操作运行时,验收完整真相路线

第一轮 Agent 泛化盲测题见 docs/evals/blind-prompts.md;隐藏评分表见 docs/evals/evaluation-rubric.md。生成 Agent 只能看到题目,不能看到评分表。

产品核心的近期与远期路线、阶段依赖、暂不做事项和质量门槛见docs/product-roadmap.md。当前正在完成首个“插件就绪”版本:质量闭环、严格声画契约、受控富文本、部署诊断与版本发布;叙事声景、世界状态、拟态网页、受控空间和 Meta 叙事不作为首个插件前置条件。插件封装边界和发布清单见 docs/plugin-readiness.md。文字、媒介、空间与远期 3D 能力的产品边界见docs/narrative-freedom.md:所有自由度必须服务文字冒险体验,并保持结构化、可校验和可降级。

MCP 工具

工具 用途
story_new 创建项目(写 projects/<标题>/story.json 骨架);已存在则复用
story_get 读取整个剧情 JSON
story_get_node 读取单个节点及其入边,避免局部修稿时全量读取
story_review_transitions 分页返回源末段、选项承接和目标首段,用于连续性审查
story_patch_choice 只修改一个选项;可断言旧 label/target 防止按过期索引误改
story_upsert_node 创建/覆盖节点(结局自动登记到结局表)
story_delete_node 删除节点(有引用时需 force: true,会报告断链)
story_delete_ending 从结局表删除结局(被节点使用时拒绝)
story_upsert_achievement / story_delete_achievement 添加/删除成就定义
story_upsert_document / story_delete_document 添加/删除线索/文档(规则守则/便条/信件,可被收集查看)
story_upsert_evidence / story_delete_evidence 添加/删除可用于推理的证据
story_upsert_deduction / story_delete_deduction 添加/删除证据组合推论
story_upsert_character / story_delete_character 添加/删除包含关系维度与秘密的人物
story_upsert_puzzle / story_delete_puzzle 添加/删除密码谜题、渐进提示与成功效果
story_validate 校验 + 路径探索模拟(结局覆盖统计)
story_evaluate 作品体验评估:互动、机制、演出、重复导航与 walk 健康度;不打总分
story_walk 结局见证与覆盖诊断(可重放路径 / 预算占用 / 热点节点;可调 maxStates 等参数)
story_graph 生成 mermaid 分支图(审查结构用)
story_export 导出单文件 HTML(校验不通过时拒绝)
story_set_meta 更新副标题 / 作者 / 主题 / HUD 统计条 / 初始持续声景
story_set_presentation 设置 novel/dossier/chat/cinematic 外壳与字体、密度、形状、选项风格
story_list 列出所有项目
story_delete_project 删除整个项目
story_observability / story_observability_reset 查看/清零本地 MCP 成本摘要(调用、字节、粗略 token、耗时、重复覆盖)

项目数据存放在 projects/<标题>/story.json,导出物在 projects/<标题>/dist/index.html

复杂开放结构可调用story_walk { title, diagnostics: true, topNodes: 10, maxStates?: 100000, witnessMaxStates?: 25000 }reachability 为每个已证明可达的结局返回一条可重放 witnessfailures.witnesses 返回已经找到的条件软锁与无结局终点路线,failures.complete 表示失败搜索是否完整。即使全量探索超出maxStates,walker 也会在每个缺失结局上追加一次目标导向搜索。coverage.complete 单独表示全状态覆盖是否完成,coverage.reasons 说明受限原因,budget.utilizationhotNodes 用来定位高频 hub/回环。兼容字段 truncated 只表示主探索耗尽全局状态预算,不再等同于“结局不可达”。构建后可用 node scripts/verify-walk-witnesses.mjs "作品名" 在真实 Game 中批量重放这些见证;再用 node scripts/verify-dom-witnesses.mjs "作品名" 自动操作真实运行时的选项、推理板、谜题输入与返回按钮,并确认失败见证确实会把玩家锁住;发现失败路径时命令以非零状态退出。两者都不会评价谜题公平性、文案提示和演出节奏,不能完全替代人工试玩。

导出前可调用 story_evaluate { title } 集中检查作品。它返回事实指标与带证据的候选问题,包括无状态自循环、重复选择结果和未回收的证据/推论/谜题;不输出总分,也不会因题材未采用谜题或人物关系而机械告警。第一轮校准基线见docs/evals/evaluation-baseline.md

盲测前调用 story_observability_reset {},完成后调用 story_observability {},可得到本次 MCP进程内的调用次数、失败、请求/响应字节、粗略 token、耗时、全量/单节点读取、转场审查、walk、导出和重复覆盖摘要。摘要只保留聚合数字与资源 id,不记录剧情正文,也不等同于模型平台账单。

剧情数据格式

interface Story {
  meta: { title: string; subtitle?: string; version?: string; author?: string }
  start: string                                // 起始节点 id
  nodes: Record<string, StoryNode>             // 节点表
  endings: Record<string, EndingMeta>          // 结局表(upsert 节点时自动登记)
}

interface StoryNode {
  id: string
  text: string                    // 正文;支持 {varName} 插值、{#inventory} 道具列表、\n 换行
  choices: Choice[]               // 空数组 = 结局节点(必须带 ending)
  ending?: EndingMeta             // { id, title, kind: 'good'|'bad'|'true'|'hidden' }
  onEnter?: Effects               // 进入本节点时生效
  soundscape?: SoundscapeSpec | 'silence' // 持续声景切换点;未声明则沿用
  tags?: string[]                 // 仅供 AI/作者管理,不影响游戏
  note?: string                   // 设计备注,不进入游戏
}

interface Choice {
  label: string                   // 按钮文案(支持 {var} 插值)
  target: string                  // 目标节点 id
  when?: Condition                // 显示条件(不满足则选项隐藏)
  effects?: Effects               // 选择后生效
}

interface Effects {
  set?: Record<string, number | string | boolean>  // 赋值变量
  add?: Record<string, number>                     // 数值增减(不存在时从 0 起算)
  rand?: { var: string; min: number; max: number }[] // 随机赋整数(含两端)
  violation?: string[]                             // 记录违规规则 id(去重,条件 #violated)
  day?: number                                     // 推进天数(增量;最小 1)
  gain?: string[]                                  // 获得道具
  lose?: string[]                                  // 失去道具
  gainDocs?: string[]                              // 获得线索/文档
  gainEvidence?: string[]                          // 获得推理证据
  adjustRelation?: { characterId: string; stat: string; add: number }[]
  remember?: string[]                              // 记录关键行为记忆
  revealSecrets?: string[]                         // 揭示秘密(characterId:secretId)
  flag?: Record<string, boolean>                   // 旗标(与变量同命名空间)
}

interface Condition {
  var?: string
  op?: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'exists' | 'has' | 'not_has'
  value?: number | string | boolean
  and?: Condition[]
  or?: Condition[]
  not?: Condition
}

要点:

  • 条件 has / not_has:普通 var 视为道具名并检查 inventory;对于 #visited#docs#evidence#deduction#violated#memory#secret#puzzle 等集合型特殊变量,value 是要检查的 id;集合变量同时兼容既有 eq / ne 写法;
  • 条件 exists:检查变量是否已定义;其余比较符与 vars[var] 比较;
  • 旗标与变量同命名空间flag 效果写进 vars),条件可直接引用;
  • 特殊变量#steps(步数)、#ending(结局 id)、#visited(访问过某节点)、#docs(获得过某文档)、#evidence(获得过某证据)、#deduction(已确认某推论)、#day(当前天数,数值比较)、#violated(违反过某规则,eq 判断);
  • 结局节点:choices: [] 且带 ending;选项的 target 必须指向存在的节点;
  • 正文/选项文案中的 {未写入变量} 会被 story_validate 报告(疑似拼写错误);
  • 正文插值还支持 {#day}(显示天数)。

音效 / 动画

节点可声明氛围效果(story_upsert_node 时带上):

{
  "id": "dark_hall",
  "text": "走廊尽头一片漆黑……",
  "sfx": "heartbeat",          // 严格枚举;未知音效在写入时拒绝
  "fx": ["flicker"],           // 卡片动画:shake / flicker / glitch / pulse
  "onEnter": { "day": 1, "violation": ["r_curfew"], "rand": [{ "var": "恐惧", "min": 1, "max": 5 }] }
}
  • 音效(Web Audio 程序化合成,零外部文件):click(选项)、page(线索翻页)、heartbeat(心跳)、drone(低频氛围)、achievement(成就)、shock(惊吓)、ending_good / ending_bad / ending_true / ending_hidden(结局);
  • 选项点击、成就解锁、线索翻页、结局自动播放对应音效;标题屏/游戏画面右上角 🔊 按钮可静音(偏好持久化);
  • 动画(可调幅度/频率):shake 抖动 / flicker 持续闪烁 / glitch 毛刺 / pulse 脉动 /unstable 不稳定灯(随机间隔连闪爆发,模拟坏灯);支持带参数规格{ name, intensity?, speed? }——intensity 幅度倍率范围 0.1..2(0.3=轻微,2=剧烈)、speed 频率倍率范围 0.25..4(2=快一倍,0.5=慢一倍),默认 1(即原版参数);越界值在写入时拒绝;
  • 例如「不稳定的灯」:"fx": [{ "name": "unstable", "intensity": 0.6, "speed": 1 }](大部分时间正常,随机 2-5 秒触发一次连闪两三下,再安静一阵);
  • 尊重系统「减弱动态效果」设置。

持续声景

sfx 是一次性事件,soundscape 是跨节点持续的环境声。初始声景可用story_set_meta { soundscape } 设置;节点只在声音发生变化时声明,后续未声明节点自动沿用,用 "soundscape": "silence" 淡出到寂静。切换采用固定交叉淡化,不开放任意音符、频率或音频文件。

{ "name": "rain", "intensity": "subtle" }

可用声景:rainwindstormwavesbroadcastelectricventilationenginevoid;强度为 subtlemediumstrong。静音会立即影响短音效和持续声景,关闭游戏时共享 AudioContext 与所有持续声音都会释放。

世界状态与阶段

meta.world 表示表世界/里世界等叙事位面,meta.phase 表示白天/夜晚、正常/警报等阶段。二者使用相同的紧凑结构,可覆盖主题、视觉配方和持续声景;选项逻辑继续显式使用{ "op": "eq", "var": "#world", "value": "other" }#phase,不要藏进 CSS。

{
  "world": {
    "initial": "surface",
    "states": {
      "surface": { "label": "表世界", "theme": "paper" },
      "other": {
        "label": "里世界",
        "theme": "cyber",
        "presentation": { "shell": "cinematic", "typography": "mono" },
        "soundscape": { "name": "void", "intensity": "strong" }
      }
    }
  },
  "phase": {
    "initial": "day",
    "states": { "day": {}, "night": { "soundscape": { "name": "wind" } } }
  }
}

用节点 onEnter 或选项 effects 中的 world: "other"phase: "night" 切换。状态会进入存档和 walk 状态键;恢复存档时主题、排版、声景与可见选项同步恢复。完整契约见docs/world-state.md

受控富文本

文本块可用 segments 混排强调、斜体、血字、耳语、终端、手写、电视色块,以及条件遮挡/乱码。节点与文本块的 text 仍是必填纯文本回退;不接受任意 HTML/CSS。

{
  "type": "para",
  "text": "病历写着:凌晨三点。",
  "segments": [
    { "text": "病历写着:" },
    {
      "text": "凌晨三点",
      "style": "redacted",
      "revealWhen": { "op": "eq", "var": "#evidence", "value": "e_真实时间" }
    },
    { "text": "。血迹未干。", "style": "blood" }
  ]
}

允许样式:emphasisitalicbloodwhisperredactedglitchcorruptterminalhandwrittenbroadcast。条件引用会参与静态校验;未揭示的真实文本不会写入页面 DOM。创作规范见 skill/references/rich-text.md

规则怪谈玩法配方(违规度 + 天数循环)

  • 违规度:违反规则处加 violation: ["r_xxx"];条件 #violated 判断是否违反过(解锁后续分支);
  • 天数循环:过夜节点 onEnter: { day: 1 };HUD 显示天数:story_set_meta { hud: [{ var: "#day", label: "第几天", max: 7 }] }
  • 随机性rand 效果产生浮动值(伤害/掉宝/随机事件);成就/条件可直接引用生成的变量。

成就 / 主题 / HUD

成就Story.achievements,用 story_upsert_achievement 管理):

interface Achievement {
  id: string
  title: string
  description: string
  icon?: string          // emoji
  hidden?: boolean       // 解锁前在列表显示「???」(隐藏成就)
  when: Condition        // 达成条件;特殊变量 #steps(步数)/ #ending(结局 id)/ #visited(访问过某节点)
}

解锁后游戏中弹 toast,标题屏「成就」入口查看列表;解锁记录随存档保存。

主题Story.meta.theme,用 story_set_meta { theme }):

  • 内置:dark(悬疑)/ cyber(霓虹)/ cozy(温馨浅色)/ paper(复古纸页);
  • 自定义:传配色对象 { background, card, border, borderGlow, text, textDim, accent, danger, gold, green, purple }
  • 未知主题名导出时自动回退 dark

HUD 统计条Story.meta.hud,好感度/理智值等数值变量的可视化):

{ var: '好感度', label: '好感度', max: 100 }   // max 为进度条满值

游戏画面顶部显示进度条与数值;配合 effects.add 即可实现好感度系统(普通数值变量语义不变)。

文档 / 线索系统(规则怪谈核心载体)

线索/文档Story.documents,用 story_upsert_document 管理):

interface StoryDocument {
  id: string
  title: string
  kind?: 'rules' | 'note' | 'letter' | 'doc'   // 守则 / 便条 / 信 / 文档
  text: string                                  // 正文(支持 {var} 插值)
}
  • 节点/选项用 effects.gainDocs: ['d_id'] 让玩家收集线索(进线索夹,不进道具栏);
  • 游戏画面右上角出现「线索 N」按钮 → 线索夹列表 → 点开查看正文(按 kind 排版);
  • 条件可用 #docs{ op: 'eq', var: '#docs', value: 'd_id' } 判断是否已获得某线索(用于解锁后续选项/成就)。

文本块StoryNode.blocks,存在时优先于 text 渲染,适合同一节点混合排版):

blocks: [
  { type: 'title', title: '雾中车站', text: '雾中车站' },
  { type: 'para', text: '凌晨一点半,雾把车站裹成一只茧……' },
  { type: 'rules', title: '售票窗口告示', text: '1. 末班车 23:00 发车。\n2. 雾天禁止在站台逗留。' },
  { type: 'note', text: '(纸条,字迹潦草)' },
]

块类型:title 标题 / para 段落 / rules 规则清单(等宽金字)/ note 便条(斜体灰)/ letter 信件。配合线索夹,即可还原《动物园规则怪谈》式「多份矛盾守则 + 玩家自行推理」的玩法。

证据组合推理

推理玩法区分三个概念:Document 是可阅读载体,Evidence 是可用于论证的材料,Deduction 是玩家在推理板用证据确认的命题。

interface Evidence {
  id: string
  title: string
  description: string
  kind?: 'document' | 'object' | 'testimony' | 'observation'
  source?: string
}

interface Deduction {
  id: string
  statement: string
  description?: string
  hint?: string              // 证据不足时的非剧透调查方向
  requires: { all?: string[]; anyOf?: string[][] }
  onConfirmed?: Effects
}

节点用 gainEvidence 发放证据。玩家在推理板选择证据组合;推论成立后可用 #deduction 条件解锁对话、场景或结局。节点 objective 可显示当前目标,推论 hint 可提示调查方向。完整设计约定见 docs/deduction-mvp.md,可运行样例见 examples/deduction-demo.story.json

人物关系、记忆与秘密

人物通过 story_upsert_character 定义,并在同一人物内声明关系维度和秘密。关系数值表示程度,记忆表示关系变化的叙事原因,秘密表示玩家已经获知的角色信息。

interface Character {
  id: string
  name: string
  description: string
  relations?: Record<string, { label: string; initial?: number; min?: number; max?: number }>
  secrets?: Record<string, { id: string; title: string; description: string }>
}
  • 关系条件:{ op: 'gte', var: '#relation:maid:trust', value: 2 }
  • 记忆条件:{ op: 'eq', var: '#memory', value: 'protected_maid' }
  • 秘密条件:{ op: 'eq', var: '#secret', value: 'maid:hidden_corridor' }

玩家可在游戏中的“人物”页查看当前关系和已揭示秘密。完整约定见 docs/relationships-mvp.md,样例见 examples/relationship-demo.story.json

密码谜题与渐进提示

第一版谜题采用确定性的密码输入,不依赖联网模型:

interface Puzzle {
  id: string
  title: string
  prompt: string
  kind: 'code'
  solution: string
  caseSensitive?: boolean
  hints?: string[]
  requires?: Condition
  onSolved?: Effects
}

玩家通过游戏内信息推导答案,在“谜题”页输入并验证;错误尝试、提示进度和已解状态都会保存。节点用 #puzzle 条件解锁后续内容。完整约定见 docs/puzzles-mvp.md,样例见 examples/puzzle-demo.story.json

AI 使用指引(典型工作流)

  1. story_new { title, subtitle } — 创建项目(自带 start + 示例结局骨架);
  2. story_delete_node { nodeId: 'end', force: true } + story_delete_ending { endingId: 'e_end' }— 清掉示例骨架(或用 story_upsert_node 覆盖 start 节点);
  3. story_upsert_node { node } × N — 逐个写节点(先写节点再统一校验,中途断链属正常);
  4. story_graph { title } — 用 mermaid 检查分支结构;
  5. story_validate { title } — 校验 + 路径探索模拟,确认所有结局可达、无断链;
  6. story_export { title } — 导出单文件 HTML;把 outputPath 交给用户即可。

示例成品:《迷雾车站》(10 节点 / 3 结局,含条件选项、道具「旧伞」、旗标、线索文档与规则文本块),运行 node scripts/demo.mjs 通过 MCP 协议全流程生成到 projects/迷雾车站/dist/index.html。注:projects/ 是 AI 运行时生成的项目目录(不入库),仓库内不包含具体游戏。

目录结构

src/
├── core/          # 引擎核心(纯逻辑,无 DOM)
│   ├── types.ts   #   数据模型
│   ├── schema.ts  #   共享 Story Schema(zod 校验 / 版本迁移)
│   ├── engine.ts  #   Game 状态机
│   ├── conditions.ts / effects.ts
│   ├── validate.ts / walk.ts
│   └── fixtures.ts / *.test.ts
├── export/        # 单文件 HTML 导出
│   ├── runtime.ts #   运行时渲染器(打包进 HTML)
│   ├── exporter.ts#   esbuild bundle + 导出流程
│   ├── template.ts#   HTML 模板 + 内联 CSS
│   └── *.test.ts
├── mcp/           # MCP 服务器
│   ├── projects.ts #  项目存储(projects/ 目录)
│   ├── handlers.ts #  工具实现(可单测)
│   └── server.ts   #  stdio transport + 工具注册
scripts/           # build-runtime / verify-mcp / demo / verify-export
projects/          # AI 创建的游戏项目(gitignore)

MCP Server · Populars

MCP Server · New

    JanYork

    LWC — Proactive Memory for AI Agents

    Agent-driven proactive memory CLI for AI agents — autonomously recall, maintain, and evolve persistent, source-grounded knowledge across sessions.

    Community JanYork
    mixelpixx

    Konnect *BETA Release

    AI-assisted PCB design for KiCAD 10. Native KiCAD plugin — a single Rust binary exposing 171 schematic, layout, routing, design-review, and manufacturing tools to Claude, or the LLM of your choosing

    Community mixelpixx
    mixelpixx

    Nimrod

    Web research for Claude over MCP: quality-scored Google search, clean extraction, deep research. Hosted connector for claude.ai/Desktop/Code + Nimrod Desktop toolkit (skills, agent, hooks).

    Community mixelpixx
    Minima-AI-Inc

    minima

    On-premises conversational RAG with configurable containers

    Community Minima-AI-Inc
    Minima-AI-Inc

    minima MCP server

    On-premises conversational RAG with configurable containers

    Community Minima-AI-Inc