【游戏】迷雾镇(Mist Town)AI 沙箱游戏详细设计与部署指南(附源代码)

本项目站内资源源代码下载地址

1. 概述

1.1 迷雾镇

迷雾镇(Mist Town) 是一款基于 AI 大语言模型(LLM)驱动的单人 RPG 网页游戏。玩家扮演一名误入永恒迷雾笼罩小镇的旅行者,通过探索 25 个区域、与 4 位 NPC 进行深度 AI 对话、收集资源、合成道具、推进 5 章主线剧情,最终解开封印、驱散迷雾。

核心设计理念

  • AI 即灵魂:每个 NPC 不是预设对话树,而是拥有完整"灵魂文件"的 AI 实体,LLM 根据实时上下文生成沉浸式、不可重复的对白
  • 渐进式探索:世界随剧情逐步解锁,避免信息过载,保持探索新鲜感
  • 日式 RPG 沉浸感:动漫风格视觉、稀有度粒子特效、动态天气与时间系统

1.2 核心特性一览

特性 说明
AI 驱动对话 每个 NPC 拥有"灵魂文件",LLM 根据上下文实时生成对话,拒绝机械感
灵活 LLM 配置 支持 OpenAI 兼容 API,可接入 DeepSeek / 通义千问 / 智谱等主流模型
渐进式章节 5 章主线剧情,25 个区域随章节逐步解锁,节奏张弛有度
深度 NPC 系统 关系值 / 信任度 / 心情 / 日程 / 忙碌状态 / 送礼偏好 / 每日互动限制
丰富玩法 采集 / 合成 / 交易 / 调查 / 休息 / 随机事件 / 成就 / 日志
日式 RPG 风格 动漫风 NPC 头像 + 场景图片 + 物品卡片稀有度粒子特效
多槽位存档 localStorage 持久化,支持多存档槽位 + 30 秒自动存档
响应式设计 桌面端双栏布局 + 移动端全屏面板覆盖

1.3 技术栈摘要

层级 技术 选择理由
框架 Next.js 16.1.1 (React 19) App Router 架构,服务端 API 与前端同构
构建 / 运行时 Bun 极速安装与构建,原生 TypeScript 支持
语言 TypeScript 5 全栈类型安全,减少运行时错误
状态管理 Zustand 5.0.6 (persist 中间件) 轻量、无样板代码、原生支持持久化
数据库 Prisma 6.11.1 + SQLite 零运维、开发友好、ORM 类型安全
UI 组件 shadcn/ui (Radix 全系列) 可复用、无障碍、Tailwind 深度集成
动画 Framer Motion 12.23.2 声明式动画,性能优异
样式 Tailwind CSS 4 原子化 CSS,开发效率极高
AI SDK z-ai-web-dev-sdk 0.0.18 内置 AI 能力,快速接入
反向代理 Caddy 配置极简,自动 HTTPS,动态端口转发

2. 技术架构

2.1 整体架构:三层分离

迷雾镇采用浏览器 → 反向代理 → 应用服务器的三层架构,清晰分离静态资源、API 服务与扩展服务:
#mermaid-svg-jCxmM9XWcIt5VtjU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jCxmM9XWcIt5VtjU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jCxmM9XWcIt5VtjU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jCxmM9XWcIt5VtjU .error-icon{fill:#552222;}#mermaid-svg-jCxmM9XWcIt5VtjU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jCxmM9XWcIt5VtjU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jCxmM9XWcIt5VtjU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jCxmM9XWcIt5VtjU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jCxmM9XWcIt5VtjU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jCxmM9XWcIt5VtjU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jCxmM9XWcIt5VtjU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jCxmM9XWcIt5VtjU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jCxmM9XWcIt5VtjU .marker.cross{stroke:#333333;}#mermaid-svg-jCxmM9XWcIt5VtjU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jCxmM9XWcIt5VtjU p{margin:0;}#mermaid-svg-jCxmM9XWcIt5VtjU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jCxmM9XWcIt5VtjU .cluster-label text{fill:#333;}#mermaid-svg-jCxmM9XWcIt5VtjU .cluster-label span{color:#333;}#mermaid-svg-jCxmM9XWcIt5VtjU .cluster-label span p{background-color:transparent;}#mermaid-svg-jCxmM9XWcIt5VtjU .label text,#mermaid-svg-jCxmM9XWcIt5VtjU span{fill:#333;color:#333;}#mermaid-svg-jCxmM9XWcIt5VtjU .node rect,#mermaid-svg-jCxmM9XWcIt5VtjU .node circle,#mermaid-svg-jCxmM9XWcIt5VtjU .node ellipse,#mermaid-svg-jCxmM9XWcIt5VtjU .node polygon,#mermaid-svg-jCxmM9XWcIt5VtjU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jCxmM9XWcIt5VtjU .rough-node .label text,#mermaid-svg-jCxmM9XWcIt5VtjU .node .label text,#mermaid-svg-jCxmM9XWcIt5VtjU .image-shape .label,#mermaid-svg-jCxmM9XWcIt5VtjU .icon-shape .label{text-anchor:middle;}#mermaid-svg-jCxmM9XWcIt5VtjU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jCxmM9XWcIt5VtjU .rough-node .label,#mermaid-svg-jCxmM9XWcIt5VtjU .node .label,#mermaid-svg-jCxmM9XWcIt5VtjU .image-shape .label,#mermaid-svg-jCxmM9XWcIt5VtjU .icon-shape .label{text-align:center;}#mermaid-svg-jCxmM9XWcIt5VtjU .node.clickable{cursor:pointer;}#mermaid-svg-jCxmM9XWcIt5VtjU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jCxmM9XWcIt5VtjU .arrowheadPath{fill:#333333;}#mermaid-svg-jCxmM9XWcIt5VtjU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jCxmM9XWcIt5VtjU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jCxmM9XWcIt5VtjU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jCxmM9XWcIt5VtjU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jCxmM9XWcIt5VtjU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jCxmM9XWcIt5VtjU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jCxmM9XWcIt5VtjU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jCxmM9XWcIt5VtjU .cluster text{fill:#333;}#mermaid-svg-jCxmM9XWcIt5VtjU .cluster span{color:#333;}#mermaid-svg-jCxmM9XWcIt5VtjU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jCxmM9XWcIt5VtjU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jCxmM9XWcIt5VtjU rect.text{fill:none;stroke-width:0;}#mermaid-svg-jCxmM9XWcIt5VtjU .icon-shape,#mermaid-svg-jCxmM9XWcIt5VtjU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jCxmM9XWcIt5VtjU .icon-shape p,#mermaid-svg-jCxmM9XWcIt5VtjU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jCxmM9XWcIt5VtjU .icon-shape .label rect,#mermaid-svg-jCxmM9XWcIt5VtjU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jCxmM9XWcIt5VtjU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jCxmM9XWcIt5VtjU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jCxmM9XWcIt5VtjU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 🖥️ 服务器端
数据层
Next.js Standalone (:3000)
HTTP API (fetch)
默认请求
?XTransformPort=*
🔌 mini-services

(动态端口)
扩展微服务

通过 XTransformPort 查询参数转发
🌐 浏览器端
React 19 + Next.js 16

App Router
15+ 游戏组件

SceneView / ChatPanel / Inventory...
Zustand Store

  • persist 中间件
    localStorage

多槽位存档
Caddy (:81)

反向代理
API Routes
POST /api/game/chat

AI 对话
GET/POST/DELETE /api/llm/config

LLM 配置
GET /api/

Health Check
Prisma Client
SQLite

custom.db

架构设计哲学

  1. Caddy 作为统一入口 :所有流量先经过 Caddy,通过查询参数 XTransformPort 实现动态服务路由,无需修改 Caddy 配置即可接入新微服务
  2. Next.js Standalone 模式:构建为独立服务器,不依赖 Node.js 运行时,部署更轻量
  3. SQLite 零运维:单文件数据库,适合中小型游戏数据,无需额外数据库服务

2.2 核心模块职责

模块 路径 行数 核心职责
类型定义 src/lib/game/types.ts ~507 全部游戏类型定义(GameState / NPC / Item / ChapterPuzzle 等),是整个项目的类型契约
世界数据 src/lib/game/world.ts ~1523 25 个地点 / 4 个 NPC / ~100 种物品 / 商店 / 对话数据,游戏世界的"圣经"
事件系统 src/lib/game/events.ts --- 30+ 随机事件 / 6 种合成配方 / 14 个成就,提供意外惊喜
章节谜题 src/lib/game/puzzles.ts --- 5 章主线剧情 / 目标检测 / 区域解锁,驱动游戏进程
存档系统 src/lib/game/save-slots.ts --- localStorage 多槽位存档 / 元数据索引,支持"多周目"
状态管理 src/store/game-store.ts ~1947 Zustand Store + persist,40+ 种 Action,游戏的"中央神经系统"
AI 对话 API src/app/api/game/chat/route.ts ~373 动态 Prompt 组装 + 双 LLM 策略,NPC 的"大脑"
LLM 配置 API src/app/api/llm/config/route.ts ~150 LLM 配置 CRUD,支持多模型切换
数据库访问 src/lib/db.ts --- Prisma 单例客户端,防止连接泄漏

3. 游戏系统设计

3.1 世界地图:5×5 迷雾网格

游戏世界是一个5×5 的网格地图,共 25 个区域,按纬度分为 5 行,随章节逐步解锁:
#mermaid-svg-YdC2ARasCQG77YWO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YdC2ARasCQG77YWO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YdC2ARasCQG77YWO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YdC2ARasCQG77YWO .error-icon{fill:#552222;}#mermaid-svg-YdC2ARasCQG77YWO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YdC2ARasCQG77YWO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YdC2ARasCQG77YWO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YdC2ARasCQG77YWO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YdC2ARasCQG77YWO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YdC2ARasCQG77YWO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YdC2ARasCQG77YWO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YdC2ARasCQG77YWO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YdC2ARasCQG77YWO .marker.cross{stroke:#333333;}#mermaid-svg-YdC2ARasCQG77YWO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YdC2ARasCQG77YWO p{margin:0;}#mermaid-svg-YdC2ARasCQG77YWO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YdC2ARasCQG77YWO .cluster-label text{fill:#333;}#mermaid-svg-YdC2ARasCQG77YWO .cluster-label span{color:#333;}#mermaid-svg-YdC2ARasCQG77YWO .cluster-label span p{background-color:transparent;}#mermaid-svg-YdC2ARasCQG77YWO .label text,#mermaid-svg-YdC2ARasCQG77YWO span{fill:#333;color:#333;}#mermaid-svg-YdC2ARasCQG77YWO .node rect,#mermaid-svg-YdC2ARasCQG77YWO .node circle,#mermaid-svg-YdC2ARasCQG77YWO .node ellipse,#mermaid-svg-YdC2ARasCQG77YWO .node polygon,#mermaid-svg-YdC2ARasCQG77YWO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YdC2ARasCQG77YWO .rough-node .label text,#mermaid-svg-YdC2ARasCQG77YWO .node .label text,#mermaid-svg-YdC2ARasCQG77YWO .image-shape .label,#mermaid-svg-YdC2ARasCQG77YWO .icon-shape .label{text-anchor:middle;}#mermaid-svg-YdC2ARasCQG77YWO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YdC2ARasCQG77YWO .rough-node .label,#mermaid-svg-YdC2ARasCQG77YWO .node .label,#mermaid-svg-YdC2ARasCQG77YWO .image-shape .label,#mermaid-svg-YdC2ARasCQG77YWO .icon-shape .label{text-align:center;}#mermaid-svg-YdC2ARasCQG77YWO .node.clickable{cursor:pointer;}#mermaid-svg-YdC2ARasCQG77YWO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YdC2ARasCQG77YWO .arrowheadPath{fill:#333333;}#mermaid-svg-YdC2ARasCQG77YWO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YdC2ARasCQG77YWO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YdC2ARasCQG77YWO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YdC2ARasCQG77YWO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YdC2ARasCQG77YWO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YdC2ARasCQG77YWO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YdC2ARasCQG77YWO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YdC2ARasCQG77YWO .cluster text{fill:#333;}#mermaid-svg-YdC2ARasCQG77YWO .cluster span{color:#333;}#mermaid-svg-YdC2ARasCQG77YWO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YdC2ARasCQG77YWO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YdC2ARasCQG77YWO rect.text{fill:none;stroke-width:0;}#mermaid-svg-YdC2ARasCQG77YWO .icon-shape,#mermaid-svg-YdC2ARasCQG77YWO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YdC2ARasCQG77YWO .icon-shape p,#mermaid-svg-YdC2ARasCQG77YWO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YdC2ARasCQG77YWO .icon-shape .label rect,#mermaid-svg-YdC2ARasCQG77YWO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YdC2ARasCQG77YWO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YdC2ARasCQG77YWO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YdC2ARasCQG77YWO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 🌊 南部(Ch3-4 解锁)
地下密道
沼泽地
河边小屋
渔码头
渡口
⛏️ 中南部(Ch1-2 解锁)
荒废矿洞
古井
西门
农田
谷仓
🏘️ 中心(初始解锁)
神社废墟
月影湖
镇中心
集市广场
醉月酒馆
🌿 中北部(Ch1-2 解锁)
精灵花园
黑森林
东门
草药田
铁匠铺
🌲 北部(Ch3-4 解锁)
森林深处
古老树林
迷雾小径
山间溪流
高山瞭望

每个地点包含

  • 相邻位置:支持 4 方向移动(上/下/左/右),部分地点有单向通道
  • 常驻 NPC:该地点可对话的角色
  • 可采集物品:该地点特有的资源(如草药田产草药,矿洞产铁矿)
  • 5 时段气氛描写:黎明 / 上午 / 下午 / 黄昏 / 夜晚,每个时段有独特的场景描述
  • 危险等级:0-5,影响随机事件触发概率(如矿洞危险等级高,更容易遭遇怪物)
  • 场景图片:动漫风 PNG 场景图,随天气动态叠加效果

3.2 时间与天气系统:动态世界氛围

时间循环

游戏采用5 时段循环制
#mermaid-svg-wXyBr3QryI86FMLi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wXyBr3QryI86FMLi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wXyBr3QryI86FMLi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wXyBr3QryI86FMLi .error-icon{fill:#552222;}#mermaid-svg-wXyBr3QryI86FMLi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wXyBr3QryI86FMLi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wXyBr3QryI86FMLi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wXyBr3QryI86FMLi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wXyBr3QryI86FMLi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wXyBr3QryI86FMLi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wXyBr3QryI86FMLi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wXyBr3QryI86FMLi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wXyBr3QryI86FMLi .marker.cross{stroke:#333333;}#mermaid-svg-wXyBr3QryI86FMLi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wXyBr3QryI86FMLi p{margin:0;}#mermaid-svg-wXyBr3QryI86FMLi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wXyBr3QryI86FMLi .cluster-label text{fill:#333;}#mermaid-svg-wXyBr3QryI86FMLi .cluster-label span{color:#333;}#mermaid-svg-wXyBr3QryI86FMLi .cluster-label span p{background-color:transparent;}#mermaid-svg-wXyBr3QryI86FMLi .label text,#mermaid-svg-wXyBr3QryI86FMLi span{fill:#333;color:#333;}#mermaid-svg-wXyBr3QryI86FMLi .node rect,#mermaid-svg-wXyBr3QryI86FMLi .node circle,#mermaid-svg-wXyBr3QryI86FMLi .node ellipse,#mermaid-svg-wXyBr3QryI86FMLi .node polygon,#mermaid-svg-wXyBr3QryI86FMLi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wXyBr3QryI86FMLi .rough-node .label text,#mermaid-svg-wXyBr3QryI86FMLi .node .label text,#mermaid-svg-wXyBr3QryI86FMLi .image-shape .label,#mermaid-svg-wXyBr3QryI86FMLi .icon-shape .label{text-anchor:middle;}#mermaid-svg-wXyBr3QryI86FMLi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wXyBr3QryI86FMLi .rough-node .label,#mermaid-svg-wXyBr3QryI86FMLi .node .label,#mermaid-svg-wXyBr3QryI86FMLi .image-shape .label,#mermaid-svg-wXyBr3QryI86FMLi .icon-shape .label{text-align:center;}#mermaid-svg-wXyBr3QryI86FMLi .node.clickable{cursor:pointer;}#mermaid-svg-wXyBr3QryI86FMLi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wXyBr3QryI86FMLi .arrowheadPath{fill:#333333;}#mermaid-svg-wXyBr3QryI86FMLi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wXyBr3QryI86FMLi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wXyBr3QryI86FMLi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wXyBr3QryI86FMLi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wXyBr3QryI86FMLi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wXyBr3QryI86FMLi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wXyBr3QryI86FMLi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wXyBr3QryI86FMLi .cluster text{fill:#333;}#mermaid-svg-wXyBr3QryI86FMLi .cluster span{color:#333;}#mermaid-svg-wXyBr3QryI86FMLi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wXyBr3QryI86FMLi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wXyBr3QryI86FMLi rect.text{fill:none;stroke-width:0;}#mermaid-svg-wXyBr3QryI86FMLi .icon-shape,#mermaid-svg-wXyBr3QryI86FMLi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wXyBr3QryI86FMLi .icon-shape p,#mermaid-svg-wXyBr3QryI86FMLi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wXyBr3QryI86FMLi .icon-shape .label rect,#mermaid-svg-wXyBr3QryI86FMLi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wXyBr3QryI86FMLi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wXyBr3QryI86FMLi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wXyBr3QryI86FMLi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 🌅 黎明

5:00-8:00
☀️ 上午

8:00-12:00
🌤️ 下午

12:00-17:00
🌆 黄昏

17:00-20:00
🌙 夜晚

20:00-5:00

时间推进规则

  • 每次移动到相邻地点:推进 1 个时段
  • 每次休息:推进 1 个时段,恢复 HP/EN
  • 每次对话:推进 1 个时段(模拟对话耗时)
  • 完成 5 个时段循环后,进入新的一天
天气系统:马尔可夫链转移

天气不是随机切换,而是基于马尔可夫转移矩阵,当前天气影响下一时段天气概率:

当前天气 ↓ \ 下一时段 → ☀️ clear 🌫️ misty 🌧️ rainy ⛈️ stormy
☀️ clear 40% 30% 20% 10%
🌫️ misty 20% 40% 25% 15%
🌧️ rainy 15% 25% 40% 20%
⛈️ stormy 10% 20% 30% 40%

设计意图

  • 自稳定状态:clear 和 misty 有较高概率保持自身状态(40%),体现天气的连续性
  • 极端天气过渡:stormy 有 30% 概率降级为 rainy,体现暴风雨不会持续太久
  • 迷雾镇特色:misty 作为"常态天气",有较高的自保持概率,强化"永恒迷雾"的世界观

天气影响

  • 视觉:场景图片叠加天气滤镜(雨天变暗、雾天模糊)
  • 事件:stormy 天气增加危险事件概率
  • NPC:部分 NPC 在雨天有特殊对话

3.3 NPC 系统:AI 驱动的灵魂

3.3.1 四位核心 NPC
NPC 角色 常驻地点 核心秘密 性格定位
老陈 酒馆老板 醉月酒馆 见证镇子 30 年变迁,知道矿难真相和封印秘密 世故、热心、话里有话
灵儿 草药师 草药田 父亲在矿难中失踪,血液与灵石共鸣 温柔、坚强、略带忧伤
赵铁匠 铁匠 铁匠铺 前铁血军团士兵,隐姓埋名赎罪 粗犷、沉默、内心柔软
巫婆婆 神社守护者 神社废墟 300 年守护封印,知道"深渊之眼" 神秘、睿智、说话带预言色彩
3.3.2 灵魂文件(SoulFile):NPC 的"人格内核"

每个 NPC 拥有一个灵魂文件,这是 LLM 对话的 System Prompt 基础,定义了 NPC 的"人格边界":
#mermaid-svg-hjDJnQguBMI0sDuI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hjDJnQguBMI0sDuI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hjDJnQguBMI0sDuI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hjDJnQguBMI0sDuI .error-icon{fill:#552222;}#mermaid-svg-hjDJnQguBMI0sDuI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hjDJnQguBMI0sDuI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hjDJnQguBMI0sDuI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hjDJnQguBMI0sDuI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hjDJnQguBMI0sDuI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hjDJnQguBMI0sDuI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hjDJnQguBMI0sDuI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hjDJnQguBMI0sDuI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hjDJnQguBMI0sDuI .marker.cross{stroke:#333333;}#mermaid-svg-hjDJnQguBMI0sDuI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hjDJnQguBMI0sDuI p{margin:0;}#mermaid-svg-hjDJnQguBMI0sDuI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hjDJnQguBMI0sDuI .cluster-label text{fill:#333;}#mermaid-svg-hjDJnQguBMI0sDuI .cluster-label span{color:#333;}#mermaid-svg-hjDJnQguBMI0sDuI .cluster-label span p{background-color:transparent;}#mermaid-svg-hjDJnQguBMI0sDuI .label text,#mermaid-svg-hjDJnQguBMI0sDuI span{fill:#333;color:#333;}#mermaid-svg-hjDJnQguBMI0sDuI .node rect,#mermaid-svg-hjDJnQguBMI0sDuI .node circle,#mermaid-svg-hjDJnQguBMI0sDuI .node ellipse,#mermaid-svg-hjDJnQguBMI0sDuI .node polygon,#mermaid-svg-hjDJnQguBMI0sDuI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hjDJnQguBMI0sDuI .rough-node .label text,#mermaid-svg-hjDJnQguBMI0sDuI .node .label text,#mermaid-svg-hjDJnQguBMI0sDuI .image-shape .label,#mermaid-svg-hjDJnQguBMI0sDuI .icon-shape .label{text-anchor:middle;}#mermaid-svg-hjDJnQguBMI0sDuI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hjDJnQguBMI0sDuI .rough-node .label,#mermaid-svg-hjDJnQguBMI0sDuI .node .label,#mermaid-svg-hjDJnQguBMI0sDuI .image-shape .label,#mermaid-svg-hjDJnQguBMI0sDuI .icon-shape .label{text-align:center;}#mermaid-svg-hjDJnQguBMI0sDuI .node.clickable{cursor:pointer;}#mermaid-svg-hjDJnQguBMI0sDuI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hjDJnQguBMI0sDuI .arrowheadPath{fill:#333333;}#mermaid-svg-hjDJnQguBMI0sDuI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hjDJnQguBMI0sDuI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hjDJnQguBMI0sDuI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hjDJnQguBMI0sDuI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hjDJnQguBMI0sDuI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hjDJnQguBMI0sDuI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hjDJnQguBMI0sDuI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hjDJnQguBMI0sDuI .cluster text{fill:#333;}#mermaid-svg-hjDJnQguBMI0sDuI .cluster span{color:#333;}#mermaid-svg-hjDJnQguBMI0sDuI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hjDJnQguBMI0sDuI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hjDJnQguBMI0sDuI rect.text{fill:none;stroke-width:0;}#mermaid-svg-hjDJnQguBMI0sDuI .icon-shape,#mermaid-svg-hjDJnQguBMI0sDuI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hjDJnQguBMI0sDuI .icon-shape p,#mermaid-svg-hjDJnQguBMI0sDuI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hjDJnQguBMI0sDuI .icon-shape .label rect,#mermaid-svg-hjDJnQguBMI0sDuI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hjDJnQguBMI0sDuI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hjDJnQguBMI0sDuI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hjDJnQguBMI0sDuI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 角色名、年龄、职业

性格定位、背景故事
对话原则、价值观

决策偏好
绝对不能说的话

绝对不能做的事
不知道自己是 AI

不知道游戏机制
语气、口癖、句式

方言/古语特征
分层秘密

信任度解锁
📜 SoulFile

灵魂文件
🎭 identity

身份锚点
⚖️ behavioralHeuristics

行为准则
🚫 redLines

红线约束
🧠 cognitiveFilter

认知过滤
💬 speechStyle

说话风格
🔐 secretKnowledge

秘密知识
LLM System Prompt

灵魂文件字段详解

字段 作用 示例(老陈)
identity 身份锚点,LLM 的"自我认知" "我是醉月酒馆老板老陈,50 岁,在迷雾镇开了 30 年酒馆"
behavioralHeuristics 行为准则,指导对话策略 "对陌生人保持礼貌但有所保留;对常客热情;提到矿难时表情凝重"
redLines 红线,绝对禁止的行为 "不能承认自己存在于游戏中;不能说'我是一个 AI';不能透露自己是代码生成的"
cognitiveFilter 认知过滤,NPC 不知道的信息 "不知道玩家的真实身份;不知道游戏外的世界;不知道'存档'概念"
speechStyle 说话风格,塑造语言特征 "带江湖气,偶尔用'小子/姑娘'称呼玩家;说到关键处会停顿喝酒"
secretKnowledge 秘密知识,分层解锁 信任度 0-30:知道矿难发生;30-60:知道矿难与封印有关;60-100:知道封印即将破裂
3.3.3 互动系统:多维关系网络

#mermaid-svg-RHqjB2MJ3vIdls4d{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RHqjB2MJ3vIdls4d .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RHqjB2MJ3vIdls4d .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RHqjB2MJ3vIdls4d .error-icon{fill:#552222;}#mermaid-svg-RHqjB2MJ3vIdls4d .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RHqjB2MJ3vIdls4d .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RHqjB2MJ3vIdls4d .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RHqjB2MJ3vIdls4d .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RHqjB2MJ3vIdls4d .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RHqjB2MJ3vIdls4d .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RHqjB2MJ3vIdls4d .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RHqjB2MJ3vIdls4d .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RHqjB2MJ3vIdls4d .marker.cross{stroke:#333333;}#mermaid-svg-RHqjB2MJ3vIdls4d svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RHqjB2MJ3vIdls4d p{margin:0;}#mermaid-svg-RHqjB2MJ3vIdls4d .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RHqjB2MJ3vIdls4d .cluster-label text{fill:#333;}#mermaid-svg-RHqjB2MJ3vIdls4d .cluster-label span{color:#333;}#mermaid-svg-RHqjB2MJ3vIdls4d .cluster-label span p{background-color:transparent;}#mermaid-svg-RHqjB2MJ3vIdls4d .label text,#mermaid-svg-RHqjB2MJ3vIdls4d span{fill:#333;color:#333;}#mermaid-svg-RHqjB2MJ3vIdls4d .node rect,#mermaid-svg-RHqjB2MJ3vIdls4d .node circle,#mermaid-svg-RHqjB2MJ3vIdls4d .node ellipse,#mermaid-svg-RHqjB2MJ3vIdls4d .node polygon,#mermaid-svg-RHqjB2MJ3vIdls4d .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RHqjB2MJ3vIdls4d .rough-node .label text,#mermaid-svg-RHqjB2MJ3vIdls4d .node .label text,#mermaid-svg-RHqjB2MJ3vIdls4d .image-shape .label,#mermaid-svg-RHqjB2MJ3vIdls4d .icon-shape .label{text-anchor:middle;}#mermaid-svg-RHqjB2MJ3vIdls4d .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RHqjB2MJ3vIdls4d .rough-node .label,#mermaid-svg-RHqjB2MJ3vIdls4d .node .label,#mermaid-svg-RHqjB2MJ3vIdls4d .image-shape .label,#mermaid-svg-RHqjB2MJ3vIdls4d .icon-shape .label{text-align:center;}#mermaid-svg-RHqjB2MJ3vIdls4d .node.clickable{cursor:pointer;}#mermaid-svg-RHqjB2MJ3vIdls4d .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RHqjB2MJ3vIdls4d .arrowheadPath{fill:#333333;}#mermaid-svg-RHqjB2MJ3vIdls4d .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RHqjB2MJ3vIdls4d .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RHqjB2MJ3vIdls4d .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RHqjB2MJ3vIdls4d .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RHqjB2MJ3vIdls4d .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RHqjB2MJ3vIdls4d .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RHqjB2MJ3vIdls4d .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RHqjB2MJ3vIdls4d .cluster text{fill:#333;}#mermaid-svg-RHqjB2MJ3vIdls4d .cluster span{color:#333;}#mermaid-svg-RHqjB2MJ3vIdls4d div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RHqjB2MJ3vIdls4d .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RHqjB2MJ3vIdls4d rect.text{fill:none;stroke-width:0;}#mermaid-svg-RHqjB2MJ3vIdls4d .icon-shape,#mermaid-svg-RHqjB2MJ3vIdls4d .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RHqjB2MJ3vIdls4d .icon-shape p,#mermaid-svg-RHqjB2MJ3vIdls4d .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RHqjB2MJ3vIdls4d .icon-shape .label rect,#mermaid-svg-RHqjB2MJ3vIdls4d .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RHqjB2MJ3vIdls4d .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RHqjB2MJ3vIdls4d .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RHqjB2MJ3vIdls4d :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} NPC 互动维度
❤️ 关系值

-100 ~ 100

态度倾向
🔐 信任度

0 ~ 100

秘密解锁
😊 心情

7 种状态

对话语气
📅 日程

5 时段位置

忙碌状态
🎁 送礼

4 级偏好

关系变化
⏳ 每日限制

对话/送礼次数

次日重置
🔗 跨 NPC 引用

谈论他人

揭示世界观

互动机制详解

维度 机制 影响
关系值 -100 ~ 100,初始 0 影响对话语气(敌对→警惕→一般→友好→信任)
信任度 0 ~ 100,每 30 点解锁一层秘密 决定 NPC 透露秘密的程度,是剧情推进的关键
心情转换 7 种事件触发心情变化 送礼/任务完成/玩家到访/离开/时间变化/天气变化/章节完成
日程安排 每个 NPC 在 5 个时段有不同位置 如老陈早上在集市采购,下午在酒馆营业,晚上在酒馆二楼休息
忙碌状态 特定时段 NPC 忙时显示"忙碌"标记 忙碌时不可对话,增加真实感
送礼偏好 每个 NPC 有 4 种偏好等级 最爱(+15 关系)、喜欢(+8)、一般(+3)、讨厌(-10)
每日互动限制 每天对话和送礼次数有上限 防止玩家通过刷对话/送礼快速刷满关系,强制"等待"机制
跨 NPC 引用 NPC 可以谈论其他 NPC 如老陈会说"灵儿那丫头最近总在矿洞附近转悠",揭示世界观信息

3.4 物品系统:约 100 种物品,4 种稀有度

物品分为 4 种类型,每种类型有明确的 gameplay 功能:

类型 说明 示例 使用场景
resource 基础资源,用于合成 木材、草药、铁矿、迷雾水晶 合成系统的原材料
consumable 消耗品,使用后获得效果 疗伤药剂(+30 HP)、活力饮料(+50 EN) 战斗中恢复状态
quest 任务物品,推进主线 灵石、古卷、月华露 章节目标交付
key 关键物品,解锁功能 镇子地图 解锁新区域或功能

稀有度视觉特效(日式 RPG 风格):
#mermaid-svg-7RQ2wBXHFmo5q0Fq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7RQ2wBXHFmo5q0Fq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .error-icon{fill:#552222;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .marker.cross{stroke:#333333;}#mermaid-svg-7RQ2wBXHFmo5q0Fq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7RQ2wBXHFmo5q0Fq p{margin:0;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .cluster-label text{fill:#333;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .cluster-label span{color:#333;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .cluster-label span p{background-color:transparent;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .label text,#mermaid-svg-7RQ2wBXHFmo5q0Fq span{fill:#333;color:#333;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .node rect,#mermaid-svg-7RQ2wBXHFmo5q0Fq .node circle,#mermaid-svg-7RQ2wBXHFmo5q0Fq .node ellipse,#mermaid-svg-7RQ2wBXHFmo5q0Fq .node polygon,#mermaid-svg-7RQ2wBXHFmo5q0Fq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .rough-node .label text,#mermaid-svg-7RQ2wBXHFmo5q0Fq .node .label text,#mermaid-svg-7RQ2wBXHFmo5q0Fq .image-shape .label,#mermaid-svg-7RQ2wBXHFmo5q0Fq .icon-shape .label{text-anchor:middle;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .rough-node .label,#mermaid-svg-7RQ2wBXHFmo5q0Fq .node .label,#mermaid-svg-7RQ2wBXHFmo5q0Fq .image-shape .label,#mermaid-svg-7RQ2wBXHFmo5q0Fq .icon-shape .label{text-align:center;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .node.clickable{cursor:pointer;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .arrowheadPath{fill:#333333;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7RQ2wBXHFmo5q0Fq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7RQ2wBXHFmo5q0Fq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7RQ2wBXHFmo5q0Fq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .cluster text{fill:#333;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .cluster span{color:#333;}#mermaid-svg-7RQ2wBXHFmo5q0Fq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7RQ2wBXHFmo5q0Fq rect.text{fill:none;stroke-width:0;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .icon-shape,#mermaid-svg-7RQ2wBXHFmo5q0Fq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .icon-shape p,#mermaid-svg-7RQ2wBXHFmo5q0Fq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .icon-shape .label rect,#mermaid-svg-7RQ2wBXHFmo5q0Fq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7RQ2wBXHFmo5q0Fq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7RQ2wBXHFmo5q0Fq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7RQ2wBXHFmo5q0Fq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ⚪ Common

银边

基础物品
🟢 Uncommon

绿色光晕 + 光点

较稀有
🟣 Rare

紫色脉冲 + 光环 + 光点

稀有
🟡 Legendary

金色爆裂 + 光束 + 双光环 + 旋转光晕

传说

3.5 合成系统:6 种配方

合成是资源转化为实用道具的核心玩法,需要收集特定材料:

产物 材料 效果 剧情关联
疗伤药剂 草药 ×2 回复 30 HP 基础生存道具
活力饮料 浆果 ×2 + 蜂蜜 回复 50 EN 基础生存道具
灵力罗盘 迷雾水晶 ×2 + 古币 导航道具 主线 Ch4 必需
驱雾香 稀有草药 + 迷雾水晶 驱散迷雾 主线 Ch2 必需
暗影护符 古老树皮 + 灵石碎片 降低危险事件概率 探索辅助
幸运护身符 金块 + 古币 提高采集稀有物品概率 收集辅助

3.6 交易系统:NPC 独立商店

每个 NPC 拥有独立商店,交易物品清单不同:

  • 老陈(酒馆):售卖食物、饮料、基础消耗品
  • 灵儿(草药田):售卖草药、药剂、稀有植物
  • 赵铁匠(铁匠铺):售卖矿石、工具、武器材料
  • 巫婆婆(神社):售卖神秘物品、古币、灵石碎片

价格机制:基于物品稀有度定价,买卖价格有差异(买入价 = 卖出价 × 1.5)

3.7 章节主线系统:5 章剧情驱动

5 章主线是游戏的"骨架",每章有明确的目标类型解锁区域
#mermaid-svg-U4SUN0IDbA2m7p3i{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-U4SUN0IDbA2m7p3i .error-icon{fill:#552222;}#mermaid-svg-U4SUN0IDbA2m7p3i .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-U4SUN0IDbA2m7p3i .marker{fill:#333333;stroke:#333333;}#mermaid-svg-U4SUN0IDbA2m7p3i .marker.cross{stroke:#333333;}#mermaid-svg-U4SUN0IDbA2m7p3i svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-U4SUN0IDbA2m7p3i p{margin:0;}#mermaid-svg-U4SUN0IDbA2m7p3i .edge{stroke-width:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .section--1 rect,#mermaid-svg-U4SUN0IDbA2m7p3i .section--1 path,#mermaid-svg-U4SUN0IDbA2m7p3i .section--1 circle,#mermaid-svg-U4SUN0IDbA2m7p3i .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .section--1 text{fill:#ffffff;}#mermaid-svg-U4SUN0IDbA2m7p3i .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-depth--1{stroke-width:17;}#mermaid-svg-U4SUN0IDbA2m7p3i .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .lineWrapper line{stroke:#ffffff;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled circle,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:lightgray;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:#efefef;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-0 rect,#mermaid-svg-U4SUN0IDbA2m7p3i .section-0 path,#mermaid-svg-U4SUN0IDbA2m7p3i .section-0 circle,#mermaid-svg-U4SUN0IDbA2m7p3i .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-U4SUN0IDbA2m7p3i .section-0 text{fill:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .node-icon-0{font-size:40px;color:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-depth-0{stroke-width:14;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .lineWrapper line{stroke:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled circle,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:lightgray;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:#efefef;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-1 rect,#mermaid-svg-U4SUN0IDbA2m7p3i .section-1 path,#mermaid-svg-U4SUN0IDbA2m7p3i .section-1 circle,#mermaid-svg-U4SUN0IDbA2m7p3i .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .section-1 text{fill:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .node-icon-1{font-size:40px;color:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-depth-1{stroke-width:11;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .lineWrapper line{stroke:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled circle,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:lightgray;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:#efefef;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-2 rect,#mermaid-svg-U4SUN0IDbA2m7p3i .section-2 path,#mermaid-svg-U4SUN0IDbA2m7p3i .section-2 circle,#mermaid-svg-U4SUN0IDbA2m7p3i .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .section-2 text{fill:#ffffff;}#mermaid-svg-U4SUN0IDbA2m7p3i .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-depth-2{stroke-width:8;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .lineWrapper line{stroke:#ffffff;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled circle,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:lightgray;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:#efefef;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-3 rect,#mermaid-svg-U4SUN0IDbA2m7p3i .section-3 path,#mermaid-svg-U4SUN0IDbA2m7p3i .section-3 circle,#mermaid-svg-U4SUN0IDbA2m7p3i .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .section-3 text{fill:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .node-icon-3{font-size:40px;color:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-depth-3{stroke-width:5;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .lineWrapper line{stroke:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled circle,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:lightgray;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:#efefef;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-4 rect,#mermaid-svg-U4SUN0IDbA2m7p3i .section-4 path,#mermaid-svg-U4SUN0IDbA2m7p3i .section-4 circle,#mermaid-svg-U4SUN0IDbA2m7p3i .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .section-4 text{fill:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .node-icon-4{font-size:40px;color:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-depth-4{stroke-width:2;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .lineWrapper line{stroke:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled circle,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:lightgray;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:#efefef;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-5 rect,#mermaid-svg-U4SUN0IDbA2m7p3i .section-5 path,#mermaid-svg-U4SUN0IDbA2m7p3i .section-5 circle,#mermaid-svg-U4SUN0IDbA2m7p3i .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .section-5 text{fill:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .node-icon-5{font-size:40px;color:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-depth-5{stroke-width:-1;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .lineWrapper line{stroke:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled circle,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:lightgray;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:#efefef;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-6 rect,#mermaid-svg-U4SUN0IDbA2m7p3i .section-6 path,#mermaid-svg-U4SUN0IDbA2m7p3i .section-6 circle,#mermaid-svg-U4SUN0IDbA2m7p3i .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .section-6 text{fill:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .node-icon-6{font-size:40px;color:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-depth-6{stroke-width:-4;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .lineWrapper line{stroke:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled circle,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:lightgray;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:#efefef;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-7 rect,#mermaid-svg-U4SUN0IDbA2m7p3i .section-7 path,#mermaid-svg-U4SUN0IDbA2m7p3i .section-7 circle,#mermaid-svg-U4SUN0IDbA2m7p3i .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .section-7 text{fill:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .node-icon-7{font-size:40px;color:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-depth-7{stroke-width:-7;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .lineWrapper line{stroke:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled circle,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:lightgray;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:#efefef;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-8 rect,#mermaid-svg-U4SUN0IDbA2m7p3i .section-8 path,#mermaid-svg-U4SUN0IDbA2m7p3i .section-8 circle,#mermaid-svg-U4SUN0IDbA2m7p3i .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .section-8 text{fill:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .node-icon-8{font-size:40px;color:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-depth-8{stroke-width:-10;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .lineWrapper line{stroke:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled circle,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:lightgray;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:#efefef;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-9 rect,#mermaid-svg-U4SUN0IDbA2m7p3i .section-9 path,#mermaid-svg-U4SUN0IDbA2m7p3i .section-9 circle,#mermaid-svg-U4SUN0IDbA2m7p3i .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .section-9 text{fill:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .node-icon-9{font-size:40px;color:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-depth-9{stroke-width:-13;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .lineWrapper line{stroke:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled circle,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:lightgray;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:#efefef;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-10 rect,#mermaid-svg-U4SUN0IDbA2m7p3i .section-10 path,#mermaid-svg-U4SUN0IDbA2m7p3i .section-10 circle,#mermaid-svg-U4SUN0IDbA2m7p3i .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .section-10 text{fill:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .node-icon-10{font-size:40px;color:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .edge-depth-10{stroke-width:-16;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-U4SUN0IDbA2m7p3i .lineWrapper line{stroke:black;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled circle,#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:lightgray;}#mermaid-svg-U4SUN0IDbA2m7p3i .disabled text{fill:#efefef;}#mermaid-svg-U4SUN0IDbA2m7p3i .section-root rect,#mermaid-svg-U4SUN0IDbA2m7p3i .section-root path,#mermaid-svg-U4SUN0IDbA2m7p3i .section-root circle{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-U4SUN0IDbA2m7p3i .section-root text{fill:#ffffff;}#mermaid-svg-U4SUN0IDbA2m7p3i .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-U4SUN0IDbA2m7p3i .edge{fill:none;}#mermaid-svg-U4SUN0IDbA2m7p3i .eventWrapper{filter:brightness(120%);}#mermaid-svg-U4SUN0IDbA2m7p3i :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Ch1 迷雾初临 目标 与老陈交谈 访问集市 解锁 草药田、铁匠铺 神社废墟、黑森林 谷仓 Ch2 神社的低语 目标 合成驱雾香 在神社找到灵石 解锁 古老树林、精灵花园 荒废矿洞、农田 Ch3 地下的秘密 目标 赵铁匠信任度 ≥ 20 在地下密道找古卷 解锁 地下密道、沼泽地 河边小屋 Ch4 月华之约 目标 合成灵力罗盘 夜晚在古老树林收集月华露 解锁 森林深处、山间溪流 渡口 Ch5 封印解除 目标 黎明/黄昏在神社找巫婆婆 完成封印仪式 解锁 高山瞭望、渔码头 迷雾镇 5 章主线剧情

目标类型系统(8 种):

目标类型 说明 示例
talk_to_npc 与指定 NPC 对话 Ch1 与老陈交谈
visit_location 访问指定地点 Ch1 访问集市
collect_item 收集指定物品 Ch2 收集驱雾香材料
craft_item 合成指定物品 Ch2 合成驱雾香
npc_trust NPC 信任度达标 Ch3 赵铁匠信任度 ≥ 20
use_item_at 在指定地点使用物品 Ch4 在古老树林使用灵力罗盘
time_at_location 在特定时间访问地点 Ch4 夜晚在古老树林
gather_quest_item 在地点采集任务物品 Ch4 收集月华露

3.8 随机事件系统:30+ 种意外

随机事件为探索增加不确定性,分为 5 类:

类型 说明 示例
危险事件 HP 损失、遭遇怪物 "迷雾中窜出一只暗影狼,你损失了 20 HP"
发现事件 找到隐藏物品 "你在树洞中发现了一瓶古老的药剂"
遭遇事件 偶遇 NPC 或神秘人物 "在河边遇到一位钓鱼的老人,他给了你一条线索"
神秘事件 超自然现象 "迷雾中传来低语,你的灵石碎片发出微光"
祝福事件 获得临时增益 "一道阳光穿透迷雾,你感到精力充沛(EN 恢复 20)"

触发条件 :事件绑定地点标签 (如 forest / mine)和时间标签(如 night / dawn),按概率触发。危险等级高的地点更容易触发危险事件。

3.9 成就系统:14 个成就,6 类

成就系统提供长期目标,增强重复游玩价值:

类别 成就示例
探索类 "漫步者"(访问 5 个地点)、"探险家"(访问 15 个地点)
收集类 "收藏家"(收集 20 种物品)、"富翁"(拥有 500 金币)
关系类 "朋友"(任一 NPC 关系 ≥ 50)、"知己"(任一 NPC 信任度 ≥ 80)
合成类 "炼金术士"(完成 3 次合成)、"工匠"(完成全部 6 种合成)
任务类 "觉醒者"(完成 Ch1)、"解封者"(完成 Ch5)
特殊类 "幸运儿"(连续触发 3 次祝福事件)、"幸存者"(在暴风雨中存活 3 天)

4. AI / LLM 集成

4.1 对话 API:NPC 的"大脑接口"

端点POST /api/game/chat

请求体结构

typescript 复制代码
interface ChatRequest {
  npcId: string;                    // NPC 标识(如 "lao-chen")
  message: string;                  // 玩家输入的消息
  dialogueHistory: DialogueMessage[];  // 最近 10 条对话(上下文)
  worldContext: WorldContext;       // 当前世界状态(地点/时间/天气)
  npcState: NPCState;              // NPC 当前状态(关系/信任/心情)
  chapterContext?: ChapterContext;  // 章节进度(当前章节/目标/提示)
  giftContext?: GiftContext;       // 送礼上下文(最近送的物品/NPC 反应)
  interactionContext?: InteractionContext;  // 互动上下文(今日对话次数/上次访问)
  activeTopic?: string;             // 当前话题(如 "矿难"、"封印")
}

响应体结构

typescript 复制代码
interface ChatResponse {
  reply: string;                    // NPC 回复(截断到 500 字符)
  mood: NPCMood;                    // 更新后的心情(如 happy / sad / angry)
  relationshipChange: number;       // 关系变化值(如 +5 / -3)
  memoryUpdate: string;             // 新增记忆(如 "玩家询问了矿难")
}

4.2 动态 Prompt 组装:150 行 System Prompt 的构建艺术

assembleSystemPrompt() 函数是 AI 系统的核心,它动态组装约 150 行的中文 System Prompt,确保 NPC 回复一致、沉浸、有深度
#mermaid-svg-HBOBSe0PlEVMyv64{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HBOBSe0PlEVMyv64 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HBOBSe0PlEVMyv64 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HBOBSe0PlEVMyv64 .error-icon{fill:#552222;}#mermaid-svg-HBOBSe0PlEVMyv64 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HBOBSe0PlEVMyv64 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HBOBSe0PlEVMyv64 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HBOBSe0PlEVMyv64 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HBOBSe0PlEVMyv64 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HBOBSe0PlEVMyv64 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HBOBSe0PlEVMyv64 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HBOBSe0PlEVMyv64 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HBOBSe0PlEVMyv64 .marker.cross{stroke:#333333;}#mermaid-svg-HBOBSe0PlEVMyv64 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HBOBSe0PlEVMyv64 p{margin:0;}#mermaid-svg-HBOBSe0PlEVMyv64 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HBOBSe0PlEVMyv64 .cluster-label text{fill:#333;}#mermaid-svg-HBOBSe0PlEVMyv64 .cluster-label span{color:#333;}#mermaid-svg-HBOBSe0PlEVMyv64 .cluster-label span p{background-color:transparent;}#mermaid-svg-HBOBSe0PlEVMyv64 .label text,#mermaid-svg-HBOBSe0PlEVMyv64 span{fill:#333;color:#333;}#mermaid-svg-HBOBSe0PlEVMyv64 .node rect,#mermaid-svg-HBOBSe0PlEVMyv64 .node circle,#mermaid-svg-HBOBSe0PlEVMyv64 .node ellipse,#mermaid-svg-HBOBSe0PlEVMyv64 .node polygon,#mermaid-svg-HBOBSe0PlEVMyv64 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HBOBSe0PlEVMyv64 .rough-node .label text,#mermaid-svg-HBOBSe0PlEVMyv64 .node .label text,#mermaid-svg-HBOBSe0PlEVMyv64 .image-shape .label,#mermaid-svg-HBOBSe0PlEVMyv64 .icon-shape .label{text-anchor:middle;}#mermaid-svg-HBOBSe0PlEVMyv64 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HBOBSe0PlEVMyv64 .rough-node .label,#mermaid-svg-HBOBSe0PlEVMyv64 .node .label,#mermaid-svg-HBOBSe0PlEVMyv64 .image-shape .label,#mermaid-svg-HBOBSe0PlEVMyv64 .icon-shape .label{text-align:center;}#mermaid-svg-HBOBSe0PlEVMyv64 .node.clickable{cursor:pointer;}#mermaid-svg-HBOBSe0PlEVMyv64 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HBOBSe0PlEVMyv64 .arrowheadPath{fill:#333333;}#mermaid-svg-HBOBSe0PlEVMyv64 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HBOBSe0PlEVMyv64 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HBOBSe0PlEVMyv64 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HBOBSe0PlEVMyv64 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HBOBSe0PlEVMyv64 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HBOBSe0PlEVMyv64 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HBOBSe0PlEVMyv64 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HBOBSe0PlEVMyv64 .cluster text{fill:#333;}#mermaid-svg-HBOBSe0PlEVMyv64 .cluster span{color:#333;}#mermaid-svg-HBOBSe0PlEVMyv64 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HBOBSe0PlEVMyv64 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HBOBSe0PlEVMyv64 rect.text{fill:none;stroke-width:0;}#mermaid-svg-HBOBSe0PlEVMyv64 .icon-shape,#mermaid-svg-HBOBSe0PlEVMyv64 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HBOBSe0PlEVMyv64 .icon-shape p,#mermaid-svg-HBOBSe0PlEVMyv64 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HBOBSe0PlEVMyv64 .icon-shape .label rect,#mermaid-svg-HBOBSe0PlEVMyv64 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HBOBSe0PlEVMyv64 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HBOBSe0PlEVMyv64 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HBOBSe0PlEVMyv64 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 成功
失败
ChatRequest

请求数据
1️⃣ 灵魂文件

identity / heuristics / redLines

filter / style / secrets
2️⃣ 世界状态

地点 / 时间 / 天气 / 气氛
3️⃣ 关系描述

根据关系值映射态度
4️⃣ 秘密知识

信任度分层解锁

0-30 / 30-60 / 60-100
5️⃣ 记忆

最近 20 条 NPC 记忆
6️⃣ 章节上下文

当前章节 / 封印之物 / 提示
7️⃣ 送礼上下文

最近礼物 / NPC 反应
8️⃣ 互动上下文

今日次数 / 上次访问 / 忙碌 / 跨 NPC 引用
📝 System Prompt

~150 行中文
🤖 LLM

生成回复
📋 解析 JSON

reply / mood / relationshipChange / memoryUpdate
✅ ChatResponse
🔄 Fallback

extractRawText

使用原始文本

Prompt 组装优先级(从高到低):

  1. 灵魂文件:定义 NPC 的"人格底线",优先级最高
  2. 红线约束:绝对不能做的事(如不能说自己是 AI)
  3. 认知过滤:NPC 不知道的信息(如不知道游戏机制)
  4. 世界状态:当前地点、时间、天气、气氛描写,提供"临场感"
  5. 关系描述:根据关系值映射为"非常信任 / 比较友好 / 一般 / 警惕 / 敌对"
  6. 秘密知识:根据信任度(每 30 点一层)逐步透露,制造"探索感"
  7. 记忆:最近 20 条 NPC 记忆,确保对话连续性
  8. 章节上下文:当前章节描述、封印之物收集状态、当前提示
  9. 送礼上下文:最近赠送物品及 NPC 反应,体现"人情往来"
  10. 互动上下文:今日对话次数、上次访问时间、忙碌状态、跨 NPC 引用、任务物品反应

12 条回复要求(约束 LLM 输出):

  1. 角色一致性:始终保持角色设定,不跳出角色
  2. 简洁:回复 2-5 句,避免长篇大论
  3. 不承认是 AI:绝不提及自己是人工智能或代码
  4. 中文回复:除非玩家用外语,否则始终用中文
  5. 礼物回应:如果刚收到礼物,必须表达感谢或反应
  6. 对话疲劳:如果今日对话次数过多,表现出疲惫或不耐烦
  7. 想念玩家:如果长时间未访问,表现出想念或询问去向
  8. 跨 NPC 引用:可以谈论其他 NPC,但基于 NPC 的视角(可能带有偏见)
  9. 时间感知:根据当前时间调整语气(黎明清新 / 夜晚疲惫)
  10. 天气感知:根据天气调整对话(雨天忧郁 / 晴天开朗)
  11. 秘密分层:信任度不足时,对秘密话题含糊其辞或转移话题
  12. 情绪真实:心情变化要自然,不突兀

4.3 双 LLM 策略:自定义优先,SDK 兜底

#mermaid-svg-1KGPsQi1ulDuSF4N{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1KGPsQi1ulDuSF4N .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1KGPsQi1ulDuSF4N .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1KGPsQi1ulDuSF4N .error-icon{fill:#552222;}#mermaid-svg-1KGPsQi1ulDuSF4N .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1KGPsQi1ulDuSF4N .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1KGPsQi1ulDuSF4N .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1KGPsQi1ulDuSF4N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1KGPsQi1ulDuSF4N .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1KGPsQi1ulDuSF4N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1KGPsQi1ulDuSF4N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1KGPsQi1ulDuSF4N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1KGPsQi1ulDuSF4N .marker.cross{stroke:#333333;}#mermaid-svg-1KGPsQi1ulDuSF4N svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1KGPsQi1ulDuSF4N p{margin:0;}#mermaid-svg-1KGPsQi1ulDuSF4N .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1KGPsQi1ulDuSF4N .cluster-label text{fill:#333;}#mermaid-svg-1KGPsQi1ulDuSF4N .cluster-label span{color:#333;}#mermaid-svg-1KGPsQi1ulDuSF4N .cluster-label span p{background-color:transparent;}#mermaid-svg-1KGPsQi1ulDuSF4N .label text,#mermaid-svg-1KGPsQi1ulDuSF4N span{fill:#333;color:#333;}#mermaid-svg-1KGPsQi1ulDuSF4N .node rect,#mermaid-svg-1KGPsQi1ulDuSF4N .node circle,#mermaid-svg-1KGPsQi1ulDuSF4N .node ellipse,#mermaid-svg-1KGPsQi1ulDuSF4N .node polygon,#mermaid-svg-1KGPsQi1ulDuSF4N .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1KGPsQi1ulDuSF4N .rough-node .label text,#mermaid-svg-1KGPsQi1ulDuSF4N .node .label text,#mermaid-svg-1KGPsQi1ulDuSF4N .image-shape .label,#mermaid-svg-1KGPsQi1ulDuSF4N .icon-shape .label{text-anchor:middle;}#mermaid-svg-1KGPsQi1ulDuSF4N .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1KGPsQi1ulDuSF4N .rough-node .label,#mermaid-svg-1KGPsQi1ulDuSF4N .node .label,#mermaid-svg-1KGPsQi1ulDuSF4N .image-shape .label,#mermaid-svg-1KGPsQi1ulDuSF4N .icon-shape .label{text-align:center;}#mermaid-svg-1KGPsQi1ulDuSF4N .node.clickable{cursor:pointer;}#mermaid-svg-1KGPsQi1ulDuSF4N .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1KGPsQi1ulDuSF4N .arrowheadPath{fill:#333333;}#mermaid-svg-1KGPsQi1ulDuSF4N .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1KGPsQi1ulDuSF4N .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1KGPsQi1ulDuSF4N .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1KGPsQi1ulDuSF4N .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1KGPsQi1ulDuSF4N .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1KGPsQi1ulDuSF4N .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1KGPsQi1ulDuSF4N .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1KGPsQi1ulDuSF4N .cluster text{fill:#333;}#mermaid-svg-1KGPsQi1ulDuSF4N .cluster span{color:#333;}#mermaid-svg-1KGPsQi1ulDuSF4N div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1KGPsQi1ulDuSF4N .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1KGPsQi1ulDuSF4N rect.text{fill:none;stroke-width:0;}#mermaid-svg-1KGPsQi1ulDuSF4N .icon-shape,#mermaid-svg-1KGPsQi1ulDuSF4N .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1KGPsQi1ulDuSF4N .icon-shape p,#mermaid-svg-1KGPsQi1ulDuSF4N .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1KGPsQi1ulDuSF4N .icon-shape .label rect,#mermaid-svg-1KGPsQi1ulDuSF4N .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1KGPsQi1ulDuSF4N .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1KGPsQi1ulDuSF4N .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1KGPsQi1ulDuSF4N :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

30 秒超时




玩家消息

ChatRequest
📋 查询数据库

LLMConfig

apiUrl / apiKey / modelId
有自定义配置?
🌐 自定义 LLM

OpenAI 兼容 API

POST /v1/chat/completions
📦 z-ai-web-dev-sdk

默认 AI 引擎
请求成功?
📋 解析 JSON

reply / mood / relationshipChange / memoryUpdate
🔄 回退到 SDK

z-ai-web-dev-sdk
JSON 解析成功?
✅ ChatResponse

返回给前端
📝 extractRawText

使用原始文本作为 reply

mood 默认 neutral

策略说明

  • 自定义 LLM 优先:如果玩家配置了自定义 LLM(如 DeepSeek),优先使用,获得更好的对话质量
  • SDK 兜底:未配置或自定义 LLM 失败时,自动回退到内置 SDK,确保游戏始终可玩
  • 超时设置:30 秒,防止 LLM 响应过慢导致前端卡死
  • JSON 容错 :如果 LLM 返回非 JSON 格式(如纯文本),使用 extractRawText 提取回复内容,确保不崩溃

4.4 LLM 配置管理:灵活切换模型

端点GET / POST / DELETE /api/llm/config

方法 功能 安全设计
GET 读取当前活跃 LLM 配置 API Key 脱敏显示(仅前 4 位和后 4 位,中间用 * 代替)
POST 保存/更新 LLM 配置 先停用所有旧配置,再创建/更新新配置,确保只有一个活跃配置
DELETE 删除当前活跃 LLM 配置 删除后自动回退到 SDK

支持的预设

预设 API 地址 模型 ID 适用场景
OpenAI https://api.openai.com/v1 gpt-4o / gpt-4o-mini 通用,对话质量高
DeepSeek https://api.deepseek.com/v1 deepseek-chat 中文优化,性价比高
通义千问 https://dashscope.aliyuncs.com/compatible-mode/v1 qwen-turbo 国内访问稳定
智谱 https://open.bigmodel.cn/api/paas/v4 glm-4 长文本支持好

5. 前端组件架构

5.1 组件树:从入口到叶子

#mermaid-svg-zNFBfsHMBbzssecQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zNFBfsHMBbzssecQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zNFBfsHMBbzssecQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zNFBfsHMBbzssecQ .error-icon{fill:#552222;}#mermaid-svg-zNFBfsHMBbzssecQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zNFBfsHMBbzssecQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zNFBfsHMBbzssecQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zNFBfsHMBbzssecQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zNFBfsHMBbzssecQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zNFBfsHMBbzssecQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zNFBfsHMBbzssecQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zNFBfsHMBbzssecQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zNFBfsHMBbzssecQ .marker.cross{stroke:#333333;}#mermaid-svg-zNFBfsHMBbzssecQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zNFBfsHMBbzssecQ p{margin:0;}#mermaid-svg-zNFBfsHMBbzssecQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zNFBfsHMBbzssecQ .cluster-label text{fill:#333;}#mermaid-svg-zNFBfsHMBbzssecQ .cluster-label span{color:#333;}#mermaid-svg-zNFBfsHMBbzssecQ .cluster-label span p{background-color:transparent;}#mermaid-svg-zNFBfsHMBbzssecQ .label text,#mermaid-svg-zNFBfsHMBbzssecQ span{fill:#333;color:#333;}#mermaid-svg-zNFBfsHMBbzssecQ .node rect,#mermaid-svg-zNFBfsHMBbzssecQ .node circle,#mermaid-svg-zNFBfsHMBbzssecQ .node ellipse,#mermaid-svg-zNFBfsHMBbzssecQ .node polygon,#mermaid-svg-zNFBfsHMBbzssecQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zNFBfsHMBbzssecQ .rough-node .label text,#mermaid-svg-zNFBfsHMBbzssecQ .node .label text,#mermaid-svg-zNFBfsHMBbzssecQ .image-shape .label,#mermaid-svg-zNFBfsHMBbzssecQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-zNFBfsHMBbzssecQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zNFBfsHMBbzssecQ .rough-node .label,#mermaid-svg-zNFBfsHMBbzssecQ .node .label,#mermaid-svg-zNFBfsHMBbzssecQ .image-shape .label,#mermaid-svg-zNFBfsHMBbzssecQ .icon-shape .label{text-align:center;}#mermaid-svg-zNFBfsHMBbzssecQ .node.clickable{cursor:pointer;}#mermaid-svg-zNFBfsHMBbzssecQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zNFBfsHMBbzssecQ .arrowheadPath{fill:#333333;}#mermaid-svg-zNFBfsHMBbzssecQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zNFBfsHMBbzssecQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zNFBfsHMBbzssecQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zNFBfsHMBbzssecQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zNFBfsHMBbzssecQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zNFBfsHMBbzssecQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zNFBfsHMBbzssecQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zNFBfsHMBbzssecQ .cluster text{fill:#333;}#mermaid-svg-zNFBfsHMBbzssecQ .cluster span{color:#333;}#mermaid-svg-zNFBfsHMBbzssecQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zNFBfsHMBbzssecQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zNFBfsHMBbzssecQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-zNFBfsHMBbzssecQ .icon-shape,#mermaid-svg-zNFBfsHMBbzssecQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zNFBfsHMBbzssecQ .icon-shape p,#mermaid-svg-zNFBfsHMBbzssecQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zNFBfsHMBbzssecQ .icon-shape .label rect,#mermaid-svg-zNFBfsHMBbzssecQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zNFBfsHMBbzssecQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zNFBfsHMBbzssecQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zNFBfsHMBbzssecQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} layout.tsx

根布局
page.tsx

入口页面
GameErrorBoundary

错误边界
GameWorld.tsx

游戏主容器 ~1154 行
IntroScreen

首页
ChapterIntro

章节介绍弹窗
GameOver / GameWon

游戏结束覆盖层
主游戏界面
创建新游戏
加载存档

SaveSlot 卡片
LLMConfigInline

内联 LLM 配置
左侧 50%

场景区
右侧 50%

面板区
底部动作栏
弹层组件
StatusBar

HP / EN / 金币 / 天气 / 天数 / 章节
SceneView.tsx

场景视图 ~668 行
MiniMap

5×5 网格小地图
SceneHeader

场景图片 + 叠加层
WeatherOverlay

天气动画
NPCRemarkBubble

NPC 主动搭话
NPCPortraitCard

NPC 头像卡片
QuestProgress

封印之物收集
NPCScheduleTracker

NPC 日程
可用操作

采集 / 休息 / 移动
ChatPanel.tsx

对话面板 ~657 行
InventoryPanel

背包
CraftingPanel

合成
TradePanel

交易
JournalPanel

日志
PuzzlePanel

章节任务
AchievementsPanel

成就
NPC 头像头部
动态话题按钮
跨 NPC 话题
送礼区域
关系变化浮动指示器
地图
背包
合成
剧情
成就
调查
提示
模型
保存
首页
ItemCardPopup.tsx

物品卡片弹窗 ~562 行

3D 翻转 + 稀有度粒子
EventPopup

随机事件弹窗
GameToast

通知 Toast

5.2 核心组件职责

组件 文件 行数 核心职责 性能要点
GameWorld GameWorld.tsx ~1154 游戏主容器,管理所有面板切换、章节介绍、游戏结束状态 使用 Zustand selector 精确订阅
SceneView SceneView.tsx ~668 场景渲染:场景图、天气动画、NPC 卡片、日程追踪、移动操作 场景图片懒加载
ChatPanel ChatPanel.tsx ~657 NPC 对话:话题建议、送礼、跨 NPC 引用、关系变化指示器 消息列表虚拟化
ItemCardPopup ItemCardPopup.tsx ~562 日式 RPG 物品卡片:3D 翻转、稀有度粒子特效、3.5 秒自动消失 粒子位置确定性生成(消除抖动)
StatusBar StatusBar.tsx --- 顶部状态栏:HP / EN / 金币 / 天气 / 天数 / 章节 实时更新
InventoryPanel InventoryPanel.tsx --- 背包面板:物品列表 + 使用 / 丢弃操作 按类型分组
CraftingPanel CraftingPanel.tsx --- 合成面板:已知配方 + 材料状态 材料不足时禁用
TradePanel TradePanel.tsx --- 交易面板:NPC 商店买卖 价格实时计算
PuzzlePanel PuzzlePanel.tsx --- 章节任务面板:当前章节目标 + 提示 目标完成状态
AchievementsPanel AchievementsPanel.tsx --- 成就面板:进度条 + 14 成就列表 解锁动画
JournalPanel JournalPanel.tsx --- 日志面板:按类型分类的冒险记录 时间倒序
MiniMap MiniMap.tsx --- 小地图:5×5 网格,高亮当前及相邻位置 区域解锁状态
EventPopup EventPopup.tsx --- 随机事件弹窗 模态聚焦
GameToast GameToast.tsx --- 通知 Toast,4 秒自动消失,支持 aria-live 无障碍
LLMConfigPanel LLMConfigPanel.tsx --- LLM 配置面板:API URL / Key / Model + 预设 + 测试 连接测试

5.3 性能优化策略

策略 实现方式 效果
Zustand Selector 所有 15+ 组件使用 selector 精确订阅所需状态 避免不必要的重渲染,提升帧率
状态变更统一走 dispatch 杜绝直接 setState 调用,所有变更通过 dispatch(action) 可预测的状态流,便于调试
确定性动画 ItemCardPopup 的粒子位置使用 itemId 作为种子确定性生成 消除重新渲染时的粒子位置抖动
场景图片懒加载 使用 Next.js Image 组件的 lazy loading 减少初始加载时间
消息列表虚拟化 ChatPanel 消息列表仅渲染可视区域 长对话不卡顿

6. 数据流与状态管理

6.1 Zustand Store:游戏的"中央神经系统"

#mermaid-svg-vCbOPKD5LhguTAhi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vCbOPKD5LhguTAhi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vCbOPKD5LhguTAhi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vCbOPKD5LhguTAhi .error-icon{fill:#552222;}#mermaid-svg-vCbOPKD5LhguTAhi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vCbOPKD5LhguTAhi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vCbOPKD5LhguTAhi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vCbOPKD5LhguTAhi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vCbOPKD5LhguTAhi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vCbOPKD5LhguTAhi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vCbOPKD5LhguTAhi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vCbOPKD5LhguTAhi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vCbOPKD5LhguTAhi .marker.cross{stroke:#333333;}#mermaid-svg-vCbOPKD5LhguTAhi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vCbOPKD5LhguTAhi p{margin:0;}#mermaid-svg-vCbOPKD5LhguTAhi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vCbOPKD5LhguTAhi .cluster-label text{fill:#333;}#mermaid-svg-vCbOPKD5LhguTAhi .cluster-label span{color:#333;}#mermaid-svg-vCbOPKD5LhguTAhi .cluster-label span p{background-color:transparent;}#mermaid-svg-vCbOPKD5LhguTAhi .label text,#mermaid-svg-vCbOPKD5LhguTAhi span{fill:#333;color:#333;}#mermaid-svg-vCbOPKD5LhguTAhi .node rect,#mermaid-svg-vCbOPKD5LhguTAhi .node circle,#mermaid-svg-vCbOPKD5LhguTAhi .node ellipse,#mermaid-svg-vCbOPKD5LhguTAhi .node polygon,#mermaid-svg-vCbOPKD5LhguTAhi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vCbOPKD5LhguTAhi .rough-node .label text,#mermaid-svg-vCbOPKD5LhguTAhi .node .label text,#mermaid-svg-vCbOPKD5LhguTAhi .image-shape .label,#mermaid-svg-vCbOPKD5LhguTAhi .icon-shape .label{text-anchor:middle;}#mermaid-svg-vCbOPKD5LhguTAhi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vCbOPKD5LhguTAhi .rough-node .label,#mermaid-svg-vCbOPKD5LhguTAhi .node .label,#mermaid-svg-vCbOPKD5LhguTAhi .image-shape .label,#mermaid-svg-vCbOPKD5LhguTAhi .icon-shape .label{text-align:center;}#mermaid-svg-vCbOPKD5LhguTAhi .node.clickable{cursor:pointer;}#mermaid-svg-vCbOPKD5LhguTAhi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vCbOPKD5LhguTAhi .arrowheadPath{fill:#333333;}#mermaid-svg-vCbOPKD5LhguTAhi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vCbOPKD5LhguTAhi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vCbOPKD5LhguTAhi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vCbOPKD5LhguTAhi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vCbOPKD5LhguTAhi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vCbOPKD5LhguTAhi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vCbOPKD5LhguTAhi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vCbOPKD5LhguTAhi .cluster text{fill:#333;}#mermaid-svg-vCbOPKD5LhguTAhi .cluster span{color:#333;}#mermaid-svg-vCbOPKD5LhguTAhi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vCbOPKD5LhguTAhi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vCbOPKD5LhguTAhi rect.text{fill:none;stroke-width:0;}#mermaid-svg-vCbOPKD5LhguTAhi .icon-shape,#mermaid-svg-vCbOPKD5LhguTAhi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vCbOPKD5LhguTAhi .icon-shape p,#mermaid-svg-vCbOPKD5LhguTAhi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vCbOPKD5LhguTAhi .icon-shape .label rect,#mermaid-svg-vCbOPKD5LhguTAhi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vCbOPKD5LhguTAhi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vCbOPKD5LhguTAhi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vCbOPKD5LhguTAhi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} dispatch(action)
selector 订阅
Action 系统

40+ 种 Action
MOVE

移动
TALK / NPC_REPLY / PLAYER_MESSAGE

对话
GATHER / USE_ITEM / CRAFT / REST / INVESTIGATE

互动
BUY_ITEM / SELL_ITEM

交易
GIFT_ITEM

送礼
NPC_REACTION / UPDATE_NPC_MOOD

NPC 状态
ADVANCE_TIME

时间
LOAD_SAVE_SLOT / SAVE_CURRENT_SLOT

存档
GET_HINT / COMPLETE_OBJECTIVE / ADVANCE_CHAPTER

章节
Zustand Store

game-store.ts ~1947 行
GameState

~40 个字段
持久化字段

localStorage
瞬时字段

内存-only
isInitialized
day / timeOfDay / weather
hp / energy / coins
inventory / questItems
npcStates / unlockedLocations
completedObjectives / completedChapters / currentChapter
journalEntries / unlockedAchievements / completedCrafts
TOGGLE_PANEL / DISMISS_ITEM_CARD

UI 控制
acquiredItem / dismissedItemId
dialogueHistory
React Components

6.2 持久化策略:localStorage 多槽位存档

#mermaid-svg-Bw6HeZZPBQ6O0N1V{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Bw6HeZZPBQ6O0N1V .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .error-icon{fill:#552222;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .marker.cross{stroke:#333333;}#mermaid-svg-Bw6HeZZPBQ6O0N1V svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Bw6HeZZPBQ6O0N1V p{margin:0;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .cluster-label text{fill:#333;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .cluster-label span{color:#333;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .cluster-label span p{background-color:transparent;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .label text,#mermaid-svg-Bw6HeZZPBQ6O0N1V span{fill:#333;color:#333;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .node rect,#mermaid-svg-Bw6HeZZPBQ6O0N1V .node circle,#mermaid-svg-Bw6HeZZPBQ6O0N1V .node ellipse,#mermaid-svg-Bw6HeZZPBQ6O0N1V .node polygon,#mermaid-svg-Bw6HeZZPBQ6O0N1V .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .rough-node .label text,#mermaid-svg-Bw6HeZZPBQ6O0N1V .node .label text,#mermaid-svg-Bw6HeZZPBQ6O0N1V .image-shape .label,#mermaid-svg-Bw6HeZZPBQ6O0N1V .icon-shape .label{text-anchor:middle;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .rough-node .label,#mermaid-svg-Bw6HeZZPBQ6O0N1V .node .label,#mermaid-svg-Bw6HeZZPBQ6O0N1V .image-shape .label,#mermaid-svg-Bw6HeZZPBQ6O0N1V .icon-shape .label{text-align:center;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .node.clickable{cursor:pointer;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .arrowheadPath{fill:#333333;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Bw6HeZZPBQ6O0N1V .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bw6HeZZPBQ6O0N1V .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Bw6HeZZPBQ6O0N1V .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .cluster text{fill:#333;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .cluster span{color:#333;}#mermaid-svg-Bw6HeZZPBQ6O0N1V div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Bw6HeZZPBQ6O0N1V rect.text{fill:none;stroke-width:0;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .icon-shape,#mermaid-svg-Bw6HeZZPBQ6O0N1V .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .icon-shape p,#mermaid-svg-Bw6HeZZPBQ6O0N1V .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .icon-shape .label rect,#mermaid-svg-Bw6HeZZPBQ6O0N1V .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bw6HeZZPBQ6O0N1V .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Bw6HeZZPBQ6O0N1V .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Bw6HeZZPBQ6O0N1V :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 浏览器存储
自动同步
手动保存
手动保存
更新索引
自动触发
mist-town-save-1

存档槽 1
mist-town-save-2

存档槽 2
mist-town-save-3

存档槽 3
mist-town-saves-index

元数据索引

槽位 / 时间 / 章节
Zustand persist

中间件
useAutoSave()

每 30 秒

持久化字段(关键游戏数据):

  • isInitializeddayhpenergyinventorynpcStatesquestItems
  • unlockedLocationscompletedObjectivescompletedChapterscurrentChapter
  • journalEntriesunlockedAchievementscompletedCrafts

不持久化字段(瞬时 UI 状态):

  • activePanelactiveNPCIdacquiredItemdialogueHistory

存档兼容机制

  • 加载旧存档时自动迁移新增字段(如 interactionState
  • 清理 undefined 字段,防止 JSON 序列化错误

6.3 竞态条件防护:安全的状态更新

#mermaid-svg-6HQKKeTTcGLikHdq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6HQKKeTTcGLikHdq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6HQKKeTTcGLikHdq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6HQKKeTTcGLikHdq .error-icon{fill:#552222;}#mermaid-svg-6HQKKeTTcGLikHdq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6HQKKeTTcGLikHdq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6HQKKeTTcGLikHdq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6HQKKeTTcGLikHdq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6HQKKeTTcGLikHdq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6HQKKeTTcGLikHdq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6HQKKeTTcGLikHdq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6HQKKeTTcGLikHdq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6HQKKeTTcGLikHdq .marker.cross{stroke:#333333;}#mermaid-svg-6HQKKeTTcGLikHdq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6HQKKeTTcGLikHdq p{margin:0;}#mermaid-svg-6HQKKeTTcGLikHdq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6HQKKeTTcGLikHdq .cluster-label text{fill:#333;}#mermaid-svg-6HQKKeTTcGLikHdq .cluster-label span{color:#333;}#mermaid-svg-6HQKKeTTcGLikHdq .cluster-label span p{background-color:transparent;}#mermaid-svg-6HQKKeTTcGLikHdq .label text,#mermaid-svg-6HQKKeTTcGLikHdq span{fill:#333;color:#333;}#mermaid-svg-6HQKKeTTcGLikHdq .node rect,#mermaid-svg-6HQKKeTTcGLikHdq .node circle,#mermaid-svg-6HQKKeTTcGLikHdq .node ellipse,#mermaid-svg-6HQKKeTTcGLikHdq .node polygon,#mermaid-svg-6HQKKeTTcGLikHdq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6HQKKeTTcGLikHdq .rough-node .label text,#mermaid-svg-6HQKKeTTcGLikHdq .node .label text,#mermaid-svg-6HQKKeTTcGLikHdq .image-shape .label,#mermaid-svg-6HQKKeTTcGLikHdq .icon-shape .label{text-anchor:middle;}#mermaid-svg-6HQKKeTTcGLikHdq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6HQKKeTTcGLikHdq .rough-node .label,#mermaid-svg-6HQKKeTTcGLikHdq .node .label,#mermaid-svg-6HQKKeTTcGLikHdq .image-shape .label,#mermaid-svg-6HQKKeTTcGLikHdq .icon-shape .label{text-align:center;}#mermaid-svg-6HQKKeTTcGLikHdq .node.clickable{cursor:pointer;}#mermaid-svg-6HQKKeTTcGLikHdq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6HQKKeTTcGLikHdq .arrowheadPath{fill:#333333;}#mermaid-svg-6HQKKeTTcGLikHdq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6HQKKeTTcGLikHdq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6HQKKeTTcGLikHdq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6HQKKeTTcGLikHdq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6HQKKeTTcGLikHdq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6HQKKeTTcGLikHdq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6HQKKeTTcGLikHdq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6HQKKeTTcGLikHdq .cluster text{fill:#333;}#mermaid-svg-6HQKKeTTcGLikHdq .cluster span{color:#333;}#mermaid-svg-6HQKKeTTcGLikHdq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6HQKKeTTcGLikHdq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6HQKKeTTcGLikHdq rect.text{fill:none;stroke-width:0;}#mermaid-svg-6HQKKeTTcGLikHdq .icon-shape,#mermaid-svg-6HQKKeTTcGLikHdq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6HQKKeTTcGLikHdq .icon-shape p,#mermaid-svg-6HQKKeTTcGLikHdq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6HQKKeTTcGLikHdq .icon-shape .label rect,#mermaid-svg-6HQKKeTTcGLikHdq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6HQKKeTTcGLikHdq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6HQKKeTTcGLikHdq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6HQKKeTTcGLikHdq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 竞态条件防护机制
确保只有一个定时器


安全合并
追踪已关闭的卡片 ID
全部走 dispatch
时间推进

scheduleTimeAdvance()
已有定时器?
清除旧定时器

再设置新定时器
设置新定时器
章节更新

applyChapterUpdate()
合并章节状态

防止覆盖更新的数据
物品卡片

dismissedItemId
防止重复 dispatch

消除重复弹窗
对话状态
对话框组件不直接 setState

避免状态不一致


7. 数据库设计

7.1 数据库选型:SQLite 零运维

特性 说明
数据库 SQLite(本地文件数据库)
ORM Prisma 6.11.1
数据库文件 db/custom.db
连接字符串 DATABASE_URL=file:/home/z/my-project/db/custom.db
选择理由 零运维、单文件、开发友好、适合中小型数据

7.2 数据模型:4 张核心表

#mermaid-svg-rQxJhESwCSvvrvMS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rQxJhESwCSvvrvMS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rQxJhESwCSvvrvMS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rQxJhESwCSvvrvMS .error-icon{fill:#552222;}#mermaid-svg-rQxJhESwCSvvrvMS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rQxJhESwCSvvrvMS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rQxJhESwCSvvrvMS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rQxJhESwCSvvrvMS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rQxJhESwCSvvrvMS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rQxJhESwCSvvrvMS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rQxJhESwCSvvrvMS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rQxJhESwCSvvrvMS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rQxJhESwCSvvrvMS .marker.cross{stroke:#333333;}#mermaid-svg-rQxJhESwCSvvrvMS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rQxJhESwCSvvrvMS p{margin:0;}#mermaid-svg-rQxJhESwCSvvrvMS .entityBox{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-rQxJhESwCSvvrvMS .relationshipLabelBox{fill:hsl(80, 100%, 96.2745098039%);opacity:0.7;background-color:hsl(80, 100%, 96.2745098039%);}#mermaid-svg-rQxJhESwCSvvrvMS .relationshipLabelBox rect{opacity:0.5;}#mermaid-svg-rQxJhESwCSvvrvMS .labelBkg{background-color:rgba(248.6666666666, 255, 235.9999999999, 0.5);}#mermaid-svg-rQxJhESwCSvvrvMS .edgeLabel .label{fill:#9370DB;font-size:14px;}#mermaid-svg-rQxJhESwCSvvrvMS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rQxJhESwCSvvrvMS .edge-pattern-dashed{stroke-dasharray:8,8;}#mermaid-svg-rQxJhESwCSvvrvMS .node rect,#mermaid-svg-rQxJhESwCSvvrvMS .node circle,#mermaid-svg-rQxJhESwCSvvrvMS .node ellipse,#mermaid-svg-rQxJhESwCSvvrvMS .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rQxJhESwCSvvrvMS .relationshipLine{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-rQxJhESwCSvvrvMS .marker{fill:none!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-rQxJhESwCSvvrvMS .edgeLabel{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rQxJhESwCSvvrvMS .edgeLabel .label rect{fill:rgba(232,232,232, 0.8);}#mermaid-svg-rQxJhESwCSvvrvMS .edgeLabel .label text{fill:#333;}#mermaid-svg-rQxJhESwCSvvrvMS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 拥有
GameSession
String
id
PK
CUID 主键
String
state
JSON 字符串,完整游戏状态
DateTime
createdAt
创建时间
DateTime
updatedAt
更新时间
NPCMemory
String
id
PK
CUID 主键
String
npcId
NPC 标识
String
sessionId
FK
游戏会话 ID
String
memory
记忆内容
String
type
类型:dialogue / event / relationship_change
Int
turnIndex
记忆顺序
DateTime
createdAt
创建时间
GameEvent
String
id
PK
CUID 主键
String
type
类型:discovery / quest / relationship / world
String
title
事件标题
String
content
事件内容
Int
day
发生天数
String
timeOfDay
发生时段
DateTime
createdAt
创建时间
LLMConfig
String
id
PK
CUID 主键
String
name
配置名称(默认 default)
String
apiUrl
LLM API 基础 URL
String
apiKey
API 密钥
String
modelId
模型 ID
Boolean
isActive
是否活跃
DateTime
createdAt
创建时间
DateTime
updatedAt
更新时间

表职责说明

当前用途 未来扩展
GameSession 预留,目前存档主要走 localStorage 未来支持服务端存档、跨设备同步
NPCMemory 预留,目前记忆主要走 localStorage 未来支持长期记忆、跨会话记忆
GameEvent 预留,目前事件主要走 localStorage 未来支持事件回放、统计分析
LLMConfig 当前核心用途:存储 LLM 配置 支持多配置切换、配置模板

注意:实际游戏存档(玩家进度、物品、NPC 状态等)主要存储在浏览器 localStorage 中(通过 Zustand persist),数据库目前主要用于存储 LLM 配置。GameSession / NPCMemory / GameEvent 表为后续服务端扩展预留。


8. 部署架构

8.1 网络拓扑:Caddy 统一入口

#mermaid-svg-peaoGMby54CQ71i8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-peaoGMby54CQ71i8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-peaoGMby54CQ71i8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-peaoGMby54CQ71i8 .error-icon{fill:#552222;}#mermaid-svg-peaoGMby54CQ71i8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-peaoGMby54CQ71i8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-peaoGMby54CQ71i8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-peaoGMby54CQ71i8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-peaoGMby54CQ71i8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-peaoGMby54CQ71i8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-peaoGMby54CQ71i8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-peaoGMby54CQ71i8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-peaoGMby54CQ71i8 .marker.cross{stroke:#333333;}#mermaid-svg-peaoGMby54CQ71i8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-peaoGMby54CQ71i8 p{margin:0;}#mermaid-svg-peaoGMby54CQ71i8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-peaoGMby54CQ71i8 .cluster-label text{fill:#333;}#mermaid-svg-peaoGMby54CQ71i8 .cluster-label span{color:#333;}#mermaid-svg-peaoGMby54CQ71i8 .cluster-label span p{background-color:transparent;}#mermaid-svg-peaoGMby54CQ71i8 .label text,#mermaid-svg-peaoGMby54CQ71i8 span{fill:#333;color:#333;}#mermaid-svg-peaoGMby54CQ71i8 .node rect,#mermaid-svg-peaoGMby54CQ71i8 .node circle,#mermaid-svg-peaoGMby54CQ71i8 .node ellipse,#mermaid-svg-peaoGMby54CQ71i8 .node polygon,#mermaid-svg-peaoGMby54CQ71i8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-peaoGMby54CQ71i8 .rough-node .label text,#mermaid-svg-peaoGMby54CQ71i8 .node .label text,#mermaid-svg-peaoGMby54CQ71i8 .image-shape .label,#mermaid-svg-peaoGMby54CQ71i8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-peaoGMby54CQ71i8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-peaoGMby54CQ71i8 .rough-node .label,#mermaid-svg-peaoGMby54CQ71i8 .node .label,#mermaid-svg-peaoGMby54CQ71i8 .image-shape .label,#mermaid-svg-peaoGMby54CQ71i8 .icon-shape .label{text-align:center;}#mermaid-svg-peaoGMby54CQ71i8 .node.clickable{cursor:pointer;}#mermaid-svg-peaoGMby54CQ71i8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-peaoGMby54CQ71i8 .arrowheadPath{fill:#333333;}#mermaid-svg-peaoGMby54CQ71i8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-peaoGMby54CQ71i8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-peaoGMby54CQ71i8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-peaoGMby54CQ71i8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-peaoGMby54CQ71i8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-peaoGMby54CQ71i8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-peaoGMby54CQ71i8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-peaoGMby54CQ71i8 .cluster text{fill:#333;}#mermaid-svg-peaoGMby54CQ71i8 .cluster span{color:#333;}#mermaid-svg-peaoGMby54CQ71i8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-peaoGMby54CQ71i8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-peaoGMby54CQ71i8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-peaoGMby54CQ71i8 .icon-shape,#mermaid-svg-peaoGMby54CQ71i8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-peaoGMby54CQ71i8 .icon-shape p,#mermaid-svg-peaoGMby54CQ71i8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-peaoGMby54CQ71i8 .icon-shape .label rect,#mermaid-svg-peaoGMby54CQ71i8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-peaoGMby54CQ71i8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-peaoGMby54CQ71i8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-peaoGMby54CQ71i8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 🖥️ 服务器
默认请求
?XTransformPort=3003
?XTransformPort=3004
🌐 Internet
Caddy (:81)

反向代理 + 动态端口转发
Next.js (:3000)

Standalone 模式
mini-services

(动态端口,如 :3003)
其他微服务

(动态端口)
Prisma Client
SQLite

custom.db

8.2 Caddy 配置:极简动态路由

caddyfile 复制代码
:81 {
    # 动态端口转发:当 URL 包含 ?XTransformPort=3003 时,转发到对应端口
    @transform_port_query { query XTransformPort=* }
    handle @transform_port_query {
        reverse_proxy localhost:{query.XTransformPort}
    }

    # 默认请求转发到 Next.js
    handle {
        reverse_proxy localhost:3000
    }
}

Caddy 配置亮点

  • 动态端口转发 :通过查询参数 XTransformPort 实现,无需修改 Caddy 配置即可接入新服务
  • 默认回退:无查询参数时转发到 Next.js,确保主应用始终可用
  • WebSocket 支持:Caddy 原生支持 WebSocket 代理,可用于 Socket.io 等实时通信

8.3 硬件要求

项目 最低要求 推荐配置 说明
CPU 2 核 4 核 Next.js + LLM API 调用需要一定计算能力
内存 2 GB 4 GB SQLite 内存缓存 + Next.js 运行时
磁盘 1 GB 5 GB 游戏资源(图片、音频)+ 数据库
运行时 Bun ≥ 1.1.0 Bun ≥ 1.3.0 构建和运行都需要 Bun
系统 Linux (amd64) Ubuntu 20.04+ / Debian 11+ 生产环境推荐

9. 构建与部署流程

9.1 构建流程:从源码到部署包

#mermaid-svg-nM40g6JXqv4xrNsA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nM40g6JXqv4xrNsA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nM40g6JXqv4xrNsA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nM40g6JXqv4xrNsA .error-icon{fill:#552222;}#mermaid-svg-nM40g6JXqv4xrNsA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nM40g6JXqv4xrNsA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nM40g6JXqv4xrNsA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nM40g6JXqv4xrNsA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nM40g6JXqv4xrNsA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nM40g6JXqv4xrNsA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nM40g6JXqv4xrNsA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nM40g6JXqv4xrNsA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nM40g6JXqv4xrNsA .marker.cross{stroke:#333333;}#mermaid-svg-nM40g6JXqv4xrNsA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nM40g6JXqv4xrNsA p{margin:0;}#mermaid-svg-nM40g6JXqv4xrNsA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nM40g6JXqv4xrNsA .cluster-label text{fill:#333;}#mermaid-svg-nM40g6JXqv4xrNsA .cluster-label span{color:#333;}#mermaid-svg-nM40g6JXqv4xrNsA .cluster-label span p{background-color:transparent;}#mermaid-svg-nM40g6JXqv4xrNsA .label text,#mermaid-svg-nM40g6JXqv4xrNsA span{fill:#333;color:#333;}#mermaid-svg-nM40g6JXqv4xrNsA .node rect,#mermaid-svg-nM40g6JXqv4xrNsA .node circle,#mermaid-svg-nM40g6JXqv4xrNsA .node ellipse,#mermaid-svg-nM40g6JXqv4xrNsA .node polygon,#mermaid-svg-nM40g6JXqv4xrNsA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nM40g6JXqv4xrNsA .rough-node .label text,#mermaid-svg-nM40g6JXqv4xrNsA .node .label text,#mermaid-svg-nM40g6JXqv4xrNsA .image-shape .label,#mermaid-svg-nM40g6JXqv4xrNsA .icon-shape .label{text-anchor:middle;}#mermaid-svg-nM40g6JXqv4xrNsA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nM40g6JXqv4xrNsA .rough-node .label,#mermaid-svg-nM40g6JXqv4xrNsA .node .label,#mermaid-svg-nM40g6JXqv4xrNsA .image-shape .label,#mermaid-svg-nM40g6JXqv4xrNsA .icon-shape .label{text-align:center;}#mermaid-svg-nM40g6JXqv4xrNsA .node.clickable{cursor:pointer;}#mermaid-svg-nM40g6JXqv4xrNsA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nM40g6JXqv4xrNsA .arrowheadPath{fill:#333333;}#mermaid-svg-nM40g6JXqv4xrNsA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nM40g6JXqv4xrNsA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nM40g6JXqv4xrNsA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nM40g6JXqv4xrNsA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nM40g6JXqv4xrNsA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nM40g6JXqv4xrNsA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nM40g6JXqv4xrNsA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nM40g6JXqv4xrNsA .cluster text{fill:#333;}#mermaid-svg-nM40g6JXqv4xrNsA .cluster span{color:#333;}#mermaid-svg-nM40g6JXqv4xrNsA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nM40g6JXqv4xrNsA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nM40g6JXqv4xrNsA rect.text{fill:none;stroke-width:0;}#mermaid-svg-nM40g6JXqv4xrNsA .icon-shape,#mermaid-svg-nM40g6JXqv4xrNsA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nM40g6JXqv4xrNsA .icon-shape p,#mermaid-svg-nM40g6JXqv4xrNsA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nM40g6JXqv4xrNsA .icon-shape .label rect,#mermaid-svg-nM40g6JXqv4xrNsA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nM40g6JXqv4xrNsA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nM40g6JXqv4xrNsA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nM40g6JXqv4xrNsA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 构建流程

.zscripts/build.sh
构建产物
next-service-dist/

Next.js standalone
mini-services-dist/

微服务打包文件
db/custom.db

数据库文件
Caddyfile

反向代理配置
start.sh

启动脚本
1️⃣ 检查项目目录存在
2️⃣ bun install

安装依赖
3️⃣ bun run build

Next.js standalone 构建

输出到 .next/standalone
4️⃣ mini-services 构建

(如存在)
5️⃣ 收集构建产物

到 /tmp/build_fullstack_$BUILD_ID/
6️⃣ 同步数据库结构

bun run db:push
7️⃣ 打包为 .tar.gz

关键构建命令

bash 复制代码
# 开发环境
bun run dev                     # 开发服务器 (:3000)

# 生产构建
bun run build                   # Next.js standalone 构建
# 构建脚本自动执行:
#   next build
#   cp -r .next/static .next/standalone/.next/
#   cp -r public .next/standalone/

# 数据库管理
bun run db:push                 # 同步 Prisma schema 到数据库
bun run db:generate             # 生成 Prisma Client

9.2 启动流程:从部署包到运行服务

#mermaid-svg-Puax9ArysNkIZRiH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Puax9ArysNkIZRiH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Puax9ArysNkIZRiH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Puax9ArysNkIZRiH .error-icon{fill:#552222;}#mermaid-svg-Puax9ArysNkIZRiH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Puax9ArysNkIZRiH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Puax9ArysNkIZRiH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Puax9ArysNkIZRiH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Puax9ArysNkIZRiH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Puax9ArysNkIZRiH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Puax9ArysNkIZRiH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Puax9ArysNkIZRiH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Puax9ArysNkIZRiH .marker.cross{stroke:#333333;}#mermaid-svg-Puax9ArysNkIZRiH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Puax9ArysNkIZRiH p{margin:0;}#mermaid-svg-Puax9ArysNkIZRiH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Puax9ArysNkIZRiH .cluster-label text{fill:#333;}#mermaid-svg-Puax9ArysNkIZRiH .cluster-label span{color:#333;}#mermaid-svg-Puax9ArysNkIZRiH .cluster-label span p{background-color:transparent;}#mermaid-svg-Puax9ArysNkIZRiH .label text,#mermaid-svg-Puax9ArysNkIZRiH span{fill:#333;color:#333;}#mermaid-svg-Puax9ArysNkIZRiH .node rect,#mermaid-svg-Puax9ArysNkIZRiH .node circle,#mermaid-svg-Puax9ArysNkIZRiH .node ellipse,#mermaid-svg-Puax9ArysNkIZRiH .node polygon,#mermaid-svg-Puax9ArysNkIZRiH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Puax9ArysNkIZRiH .rough-node .label text,#mermaid-svg-Puax9ArysNkIZRiH .node .label text,#mermaid-svg-Puax9ArysNkIZRiH .image-shape .label,#mermaid-svg-Puax9ArysNkIZRiH .icon-shape .label{text-anchor:middle;}#mermaid-svg-Puax9ArysNkIZRiH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Puax9ArysNkIZRiH .rough-node .label,#mermaid-svg-Puax9ArysNkIZRiH .node .label,#mermaid-svg-Puax9ArysNkIZRiH .image-shape .label,#mermaid-svg-Puax9ArysNkIZRiH .icon-shape .label{text-align:center;}#mermaid-svg-Puax9ArysNkIZRiH .node.clickable{cursor:pointer;}#mermaid-svg-Puax9ArysNkIZRiH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Puax9ArysNkIZRiH .arrowheadPath{fill:#333333;}#mermaid-svg-Puax9ArysNkIZRiH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Puax9ArysNkIZRiH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Puax9ArysNkIZRiH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Puax9ArysNkIZRiH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Puax9ArysNkIZRiH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Puax9ArysNkIZRiH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Puax9ArysNkIZRiH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Puax9ArysNkIZRiH .cluster text{fill:#333;}#mermaid-svg-Puax9ArysNkIZRiH .cluster span{color:#333;}#mermaid-svg-Puax9ArysNkIZRiH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Puax9ArysNkIZRiH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Puax9ArysNkIZRiH rect.text{fill:none;stroke-width:0;}#mermaid-svg-Puax9ArysNkIZRiH .icon-shape,#mermaid-svg-Puax9ArysNkIZRiH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Puax9ArysNkIZRiH .icon-shape p,#mermaid-svg-Puax9ArysNkIZRiH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Puax9ArysNkIZRiH .icon-shape .label rect,#mermaid-svg-Puax9ArysNkIZRiH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Puax9ArysNkIZRiH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Puax9ArysNkIZRiH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Puax9ArysNkIZRiH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 启动流程

.zscripts/start.sh
1️⃣ 检查数据库文件

db/custom.db 存在
2️⃣ 启动 Next.js Standalone

PORT=3000

DATABASE_URL=file:/app/db/custom.db

bun server.js &
3️⃣ 启动 mini-services

(如存在)&
4️⃣ 启动 Caddy

exec caddy run --config Caddyfile

(前台运行,主进程)
5️⃣ 信号处理

SIGTERM/SIGINT → 优雅关闭所有子进程

启动脚本核心逻辑

  • Next.js 后台运行bun server.js &,作为后台进程
  • mini-services 后台运行sh ./mini-services-start.sh &,作为后台进程
  • Caddy 前台运行exec caddy run --config Caddyfile,作为主进程,确保容器/进程管理器能正确监控
  • 优雅关闭:捕获 SIGTERM/SIGINT,依次关闭子进程,防止数据丢失

9.3 生产环境部署:3 步上线

bash 复制代码
# 1. 解压部署包
tar -xzf build_fullstack_*.tar.gz -C /app/

# 2. 进入部署目录
cd /app

# 3. 运行启动脚本(一键启动所有服务)
sh start.sh

# 4. 访问应用
# http://your-server:81

9.4 环境变量

变量 说明 默认值 必填
DATABASE_URL SQLite 数据库路径 file:/app/db/custom.db
PORT Next.js 服务端口 3000
HOSTNAME Next.js 绑定地址 0.0.0.0
NODE_ENV 运行环境 production

9.5 开发环境启动

bash 复制代码
# 一键开发启动
sh .zscripts/dev.sh

# 或手动执行
bun install
bun run db:push    # 初始化数据库
bun run dev        # 启动开发服务器 (:3000)

10. 配置指南

10.1 LLM 配置:选择你的 AI 引擎

在游戏首页点击**"模型配置"按钮,或游戏内底部工具栏点击模型图标**,进入 LLM 配置面板:

配置项 说明 示例
API 地址 OpenAI 兼容 API 的基础 URL https://api.openai.com/v1
API Key 认证密钥 sk-xxxxxxxx
模型 ID 模型标识符 gpt-4odeepseek-chat

预设选项(一键填充):

预设 API 地址 模型 特点
OpenAI https://api.openai.com/v1 gpt-4o 通用最强,中文支持好
DeepSeek https://api.deepseek.com/v1 deepseek-chat 中文优化,性价比高
通义千问 https://dashscope.aliyuncs.com/compatible-mode/v1 qwen-turbo 国内访问稳定
智谱 https://open.bigmodel.cn/api/paas/v4 glm-4 长文本支持好

注意 :如果未配置自定义 LLM,系统会自动回退到内置的 z-ai-web-dev-sdk 作为默认 AI 引擎,确保游戏始终可玩。

10.2 数据库初始化

bash 复制代码
# 首次部署时初始化数据库(创建表结构)
bun run db:push

10.3 用户数据目录

数据类型 存储位置 说明
游戏存档 浏览器 localStorage mist-town-save-{slotId},多槽位
LLM 配置 服务器 SQLite db/custom.db,LLMConfig 表
游戏资源 服务器文件系统 public/game/ 目录(NPC 头像、场景图片等)

11. 项目结构

复制代码
mist_town/
├── 📄 配置文件
│   ├── .env                          # 环境变量(DATABASE_URL)
│   ├── .gitignore                    # Git 忽略文件
│   ├── Caddyfile                     # Caddy 反向代理配置
│   ├── bun.lock                      # Bun 依赖锁定文件
│   ├── components.json               # shadcn/ui 组件配置
│   ├── eslint.config.mjs             # ESLint 配置
│   ├── next.config.ts                # Next.js 配置(standalone 输出)
│   ├── package.json                  # 项目依赖和脚本
│   ├── postcss.config.mjs            # PostCSS 配置
│   ├── tailwind.config.ts            # Tailwind CSS 配置
│   ├── tsconfig.json                 # TypeScript 配置
│   └── worklog.md                    # 开发工作日志(9 次迭代)
│
├── 🔧 部署脚本 (.zscripts/)
│   ├── build.sh                      # 构建打包脚本
│   ├── dev.sh                        # 开发环境启动脚本
│   ├── start.sh                      # 生产环境启动脚本
│   ├── mini-services-build.sh        # mini-services 构建脚本
│   ├── mini-services-install.sh      # mini-services 依赖安装脚本
│   └── mini-services-start.sh        # mini-services 启动脚本
│
├── 📋 任务说明 (agent-ctx/)
│   └── [Agent 任务说明书]
│
├── 🗄️ 数据库 (db/)
│   └── custom.db                     # SQLite 数据库文件
│
├── 📥 下载目录 (download/)
│
├── 💡 示例代码 (examples/)
│   └── websocket/                    # Socket.io 聊天室示例
│
├── 🔌 微型服务 (mini-services/)
│   └── .gitkeep
│
├── 🗃️ ORM 定义 (prisma/)
│   └── schema.prisma                 # 数据库 Schema 定义
│
├── 🎨 静态资源 (public/)
│   └── game/
│       ├── npcs/                     # NPC 动漫头像 PNG
│       ├── scenes/                   # 25 个场景图片 PNG
│       ├── logo.svg                  # Logo
│       └── robots.txt
│
├── 📊 工具结果 (tool-results/)
│
└── 💻 源代码 (src/)
    ├── app/                          # Next.js App Router
    │   ├── api/
    │   │   ├── game/chat/route.ts       # AI 对话 API (~373 行)
    │   │   ├── llm/config/route.ts      # LLM 配置 CRUD API (~150 行)
    │   │   └── route.ts                 # Health Check API
    │   ├── globals.css                  # 全局样式
    │   ├── layout.tsx                   # 根布局
    │   └── page.tsx                     # 入口页面
    │
    ├── components/
    │   ├── game/                         # 游戏组件 (15 个)
    │   │   ├── AchievementsPanel.tsx     # 成就面板
    │   │   ├── ChatPanel.tsx            # 对话面板 (~657 行)
    │   │   ├── CraftingPanel.tsx        # 合成面板
    │   │   ├── EventPopup.tsx           # 随机事件弹窗
    │   │   ├── GameToast.tsx            # 通知 Toast
    │   │   ├── GameWorld.tsx            # 游戏主容器 (~1154 行)
    │   │   ├── InventoryPanel.tsx       # 背包面板
    │   │   ├── ItemCardPopup.tsx        # 物品卡片弹窗 (~562 行)
    │   │   ├── JournalPanel.tsx         # 日志面板
    │   │   ├── LLMConfigPanel.tsx       # LLM 配置面板
    │   │   ├── MiniMap.tsx              # 小地图
    │   │   ├── PuzzlePanel.tsx          # 章节任务面板
    │   │   ├── SceneView.tsx            # 场景视图 (~668 行)
    │   │   ├── StatusBar.tsx            # 状态栏
    │   │   └── TradePanel.tsx           # 交易面板
    │   └── ui/                           # shadcn/ui 组件库 (~50 个)
    │
    ├── hooks/
    │   ├── use-mobile.ts                # 移动端检测
    │   └── use-toast.ts                 # Toast 管理
    │
    ├── lib/
    │   ├── game/                        # 游戏逻辑库
    │   │   ├── types.ts                 # 类型定义 (~507 行)
    │   │   ├── world.ts                 # 世界数据 (~1523 行)
    │   │   ├── events.ts                # 事件系统
    │   │   ├── puzzles.ts               # 章节谜题系统
    │   │   └── save-slots.ts            # 存档系统
    │   ├── db.ts                        # Prisma 数据库客户端
    │   └── utils.ts                     # 工具函数
    │
    └── store/
        └── game-store.ts                # Zustand 状态管理 (~1947 行)

附录 A:核心文件清单

文件 行数 说明 复杂度
src/store/game-store.ts ~1947 Zustand 状态管理,40+ Action ⭐⭐⭐⭐⭐
src/lib/game/world.ts ~1523 25 地点 / 4 NPC / ~100 物品 / 对话数据 ⭐⭐⭐⭐⭐
src/components/game/GameWorld.tsx ~1154 游戏主容器,面板切换管理 ⭐⭐⭐⭐
src/components/game/SceneView.tsx ~668 场景视图,天气/NPC/移动 ⭐⭐⭐⭐
src/components/game/ChatPanel.tsx ~657 对话面板,话题/送礼/关系 ⭐⭐⭐⭐
src/components/game/ItemCardPopup.tsx ~562 物品卡片弹窗,3D 翻转 + 粒子 ⭐⭐⭐⭐
src/lib/game/types.ts ~507 全部类型定义,项目类型契约 ⭐⭐⭐
src/app/api/game/chat/route.ts ~373 AI 对话 API,动态 Prompt 组装 ⭐⭐⭐⭐
src/app/api/llm/config/route.ts ~150 LLM 配置 API,CRUD + 脱敏 ⭐⭐
prisma/schema.prisma ~49 数据库 Schema,4 张表定义
相关推荐
小赖同学啊2 小时前
智能连接器集群化高可用生产方案
linux·运维·人工智能
ZStack开发者社区2 小时前
基于AI Agent的ZCF API文档全链路自动化
运维·人工智能·自动化
沈麽鬼2 小时前
别瞎用AI写代码!90%开发者都搞错了AI编程的底层逻辑
人工智能·ai编程·trae
小陈爱编程2 小时前
我终于把 Codex 的 API 配置理顺了:从踩坑到跑通
人工智能
不爱洗脚的小滕2 小时前
【Agent】如何为 AI Agent 设计高可用的 Tools
人工智能·aigc·ai编程·rag
姗姗来迟了2 小时前
前端传图片给多模态 Agent:压缩、预览、格式那些破事
人工智能
Sam09272 小时前
Spec Coding 和 Vibe Coding 的区别:AI Coding 从感觉驱动到规格驱动
人工智能·ai
Kobebryant-Manba3 小时前
学习RNN(简洁实现)
人工智能·rnn·学习
德迅--文琪3 小时前
当前 2026 年 AI 狂潮时代,抗 DDoS 产品公司品牌推荐
人工智能·ddos