Fix DashScope env loading for scene image generation

This commit is contained in:
2026-04-06 15:01:15 +08:00
parent fcd8d727b0
commit d678929064
23 changed files with 4943 additions and 138 deletions

View File

@@ -0,0 +1,957 @@
# 服务端部署与 CORS 完整技术方案
日期:`2026-04-05`
## 1. 文档目标
本文要解决的不只是“当前部署到服务器后浏览器报 CORS”而是同时解决下面几类问题
1. 浏览器访问大模型、图片生成等第三方服务时的跨域问题
2. 前端不能安全暴露 API Key 的问题
3. 当前项目开发期依赖 Vite middleware生产环境缺少正式 API 服务的问题
4. 后续服务端能力扩展时,避免再次推倒重来
目标结论很明确:
- **浏览器以后不再直连第三方大模型 / 图片服务**
- **浏览器只访问我们自己的站点域名下的 `/api/*`**
- **生产环境新增独立 Node API 服务Vite 代理只保留给开发环境**
- **把运行时接口、编辑器接口、异步任务、存储能力分层设计**
---
## 2. 结合当前仓库的现状判断
从当前仓库可以确认几件事:
### 2.1 前端已经按“走本地代理”在写
当前前端代码并不是直接请求第三方接口,而是请求:
- `/api/llm/chat/completions`
- `/api/custom-world/scene-image`
- `/api/item-overrides`
- `/api/npc-visual-overrides`
- `/api/character-overrides`
- `/api/scene-overrides`
- `/api/state-function-overrides`
- `/api/character-visual/publish`
- `/api/animation/publish`
这说明项目方向本身就是对的:**前端应该访问自己的 API 层,而不是浏览器直连外部服务。**
### 2.2 当前 API 层还只是开发期能力
这些接口现在主要由 [scripts/dev-server/localApiPlugins.ts](/E:/Repos/Genarrative/scripts/dev-server/localApiPlugins.ts) 挂在 Vite dev/preview 服务器里。
这在本地开发阶段很方便,但生产环境存在几个明显问题:
1. `Vite dev server / preview server` 不适合长期承担正式后端职责
2. 编辑器写接口、文件读写、代理转发都混在构建配置链路里
3. 未来加入鉴权、限流、审计、任务队列、数据库时会非常难扩展
4. 浏览器部署后如果没有这层正式 API 服务,就会重新退回“直连第三方接口”,于是重新触发 CORS
### 2.3 工程审查文档也已经指出同样风险
[docs/PROJECT_WORK_EXPERIENCE_PLAYBOOK.md](/E:/Repos/Genarrative/docs/PROJECT_WORK_EXPERIENCE_PLAYBOOK.md) 明确沉淀过一条经验:
- 浏览器直连会遇到 CORS
- 更稳的方案是开发服务器代理,再由前端请求 `/api/llm/...`
[docs/ENGINEERING_OPTIMIZATION_REVIEW_2026-03-29.md](/E:/Repos/Genarrative/docs/ENGINEERING_OPTIMIZATION_REVIEW_2026-03-29.md) 也明确指出:
- 编辑器、运行时、类后端能力全部耦合在 Vite 配置里
- 未来如果做独立部署、多人协作、远程编辑、权限控制,会非常难迁移
所以这次不是简单“补个 CORS header”就完了而是应该顺势把正式服务端边界立起来。
---
## 3. CORS 问题的根因
## 3.1 浏览器的跨域限制不是第三方接口“能调用”就能绕过
只要浏览器页面域名和目标接口域名不一致,就可能触发跨域限制。
典型场景:
- 页面在 `https://game.example.com`
- 浏览器直接请求 `https://ark.cn-beijing.volces.com/...`
- 或直接请求 `https://dashscope.aliyuncs.com/...`
此时就算第三方服务本身可用,只要对方没有返回允许你站点的 CORS 头,浏览器依然会拦截。
## 3.2 这类请求几乎一定会触发预检
因为现在请求通常具备以下特征:
- `Content-Type: application/json`
- `Authorization: Bearer ...`
- `POST`
- 流式返回 / SSE
这类请求很容易先发一个 `OPTIONS` 预检请求。只要预检没过,真正请求根本发不出去。
## 3.3 即使第三方临时支持 CORS也不应该让浏览器直连
因为浏览器直连还有更大的问题:
1. API Key 会暴露到前端
2. 无法统一限流和熔断
3. 无法统一记录调用日志
4. 无法对不同上游做协议适配
5. 无法在后续接入鉴权、配额、用户级审计
所以**正确方向不是“让浏览器跨域成功访问第三方”,而是“让浏览器根本不需要跨域访问第三方”。**
---
## 4. 推荐的总体方案
## 4.1 核心原则
1. **同源优先**:浏览器尽量只访问当前站点同域下的 `/api/*`
2. **密钥只在服务端存在**:前端不再持有真实第三方 Key
3. **运行时接口与编辑器接口分层**
4. **长耗时任务异步化**:图片、视频、资产发布不要长期卡在同步 HTTP 请求里
5. **存储外置化**:生产环境不要继续把生成文件直接写回源码目录
6. **可观测性内建**:日志、追踪、限流、告警一开始就留口子
## 4.2 推荐目标架构
```mermaid
flowchart LR
A["玩家浏览器 / 编辑器浏览器"] --> B["CDN / Nginx"]
B --> C["静态前端 dist"]
B --> D["Node API Gateway / BFF"]
D --> E["LLM Provider Adapter"]
D --> F["Image / Media Adapter"]
D --> G["业务服务层"]
D --> H["Redis / Queue"]
D --> I["PostgreSQL"]
D --> J["对象存储 OSS / S3"]
H --> K["Worker 异步任务进程"]
K --> F
K --> J
K --> I
```
这套架构的意思是:
- `Nginx` 负责统一入口、静态资源、反向代理
- `Node API Gateway / BFF` 负责真正的 API 接入
- 第三方大模型、图片服务不再暴露给浏览器
- `Redis / Queue + Worker` 负责后续长任务
- `PostgreSQL` 负责持久化业务数据
- `OSS / S3` 负责图片、动画、导出资产等对象存储
---
## 5. 服务分层设计
## 5.1 Web 层
职责:
- 托管前端 `dist`
- 提供 SPA 回退到 `index.html`
-`/api/*` 代理给 Node API 服务
- 统一做 TLS、压缩、缓存、静态资源头
推荐:
- `Nginx``OpenResty`
- 线上前面可再加 CDN
## 5.2 API Gateway / BFF 层
职责:
- 接收浏览器请求
- 做 CORS、鉴权、限流、审计
- 统一代理 LLM、图片生成、编辑器接口
- 把前端协议转换成上游协议
- 屏蔽第三方差异
推荐技术:
- `Node.js 22 + TypeScript`
- Web 框架可选 `Fastify``Express`
建议取舍:
- 如果要**最快迁移当前项目**,可以先上 `Express`
- 如果要**从一开始就更重视插件化、schema、性能**,可以直接上 `Fastify`
本方案不强绑定框架,重点是边界,而不是框架名。
## 5.3 Adapter 层
不要让业务代码直接到处写第三方接口细节,建议抽成单独适配层:
- `llmAdapter`
- `imageAdapter`
- `storageAdapter`
- `queueAdapter`
职责:
- 统一请求签名
- 统一错误结构
- 统一超时、重试、熔断
- 统一日志字段
## 5.4 业务服务层
建议把业务服务按域拆开,而不是继续长在 Vite 插件里:
- `runtimeService`
- 聊天/剧情推进
- 自定义世界生成
- 场景图片生成
- `editorService`
- 预设读写
- override 管理
- 资源发布
- `assetService`
- 生成图片入库
- 角色/动画资源清单
- `authService`
- 登录
- 用户角色
- 会话/Token
## 5.5 Worker 层
以下能力建议逐步迁移到异步任务:
- 自定义世界场景图生成
- 角色动画素材生成
- 视频/大图后处理
- 导出包构建
理由:
1. 任务时间长,浏览器同步等待容易超时
2. 失败重试不方便
3. 需要排队与限流
4. 后续多用户并发时,不能把 Node API 线程长期占住
---
## 6. 推荐的域名与流量策略
## 6.1 最推荐:前端与 API 同域
推荐站点入口:
- `https://game.example.com/` 提供前端静态资源
- `https://game.example.com/api/*` 提供 API
这种方式下:
- 浏览器看见的是**同源**
- 主站绝大多数请求**根本不需要 CORS**
- 部署最稳
- Cookie、会话、CSRF、防缓存等策略也更容易统一
这是**解决 CORS 的首选方案**。
## 6.2 备选:前后端分子域
如果以后要把 API 单独托管,也可以用:
- 前端:`https://app.example.com`
- API`https://api.example.com`
此时才需要真正开启 CORS 白名单:
- 只允许 `https://app.example.com`
- 管理后台再额外允许 `https://admin.example.com`
- 不允许 `*`
## 6.3 不推荐:浏览器直接访问第三方服务
以下方式不再推荐:
- 浏览器直接请求火山、DashScope、OpenAI 等服务
- 浏览器直接持有厂商 Key
- 浏览器直接上传生成资产到第三方后再回写本地
这种方式即使短期能跑,也会反复被 CORS、安全和审计问题反噬。
---
## 7. CORS 策略设计
## 7.1 总体策略
### 运行时主站
如果使用同域反代:
- 玩家访问 `game.example.com`
- API 也在 `game.example.com/api/*`
此时**运行时主站可以不依赖 CORS**。
### 管理后台 / 编辑器
如果未来编辑器单独部署到:
- `https://editor.example.com`
而 API 在:
- `https://api.example.com`
才对编辑器开启白名单 CORS。
## 7.2 允许策略
建议只允许这些 Origin
- `https://app.example.com`
- `https://editor.example.com`
- 开发环境 `http://localhost:3000`
并通过环境变量配置:
```env
CORS_ALLOW_ORIGINS=https://app.example.com,https://editor.example.com,http://localhost:3000
ADMIN_CORS_ALLOW_ORIGINS=https://editor.example.com,http://localhost:3000
```
## 7.3 响应头建议
如果命中白名单,返回:
```http
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin
Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,OPTIONS
Access-Control-Allow-Headers: Authorization,Content-Type,X-Request-Id,X-CSRF-Token
Access-Control-Max-Age: 600
```
只有在你明确使用 Cookie 会话时,才加:
```http
Access-Control-Allow-Credentials: true
```
## 7.4 不建议的配置
不要这样配:
```http
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
```
这既不安全,也不符合规范。
## 7.5 预检请求处理
`OPTIONS` 请求必须在 API 层快速返回 `204`,不要把它继续转发到业务逻辑。
建议:
- 统一中间件处理
- 只要 Origin 不在白名单,直接拒绝
- 预检通过后才进入业务处理
## 7.6 流式接口注意项
`/api/llm/chat/completions` 这种流式输出接口,还要补这些处理:
- `Cache-Control: no-cache`
- `Connection: keep-alive`
- `X-Accel-Buffering: no`
否则 `Nginx` 可能把流式内容缓存/聚合后再一次性吐给浏览器,导致前端看起来“流式失效”。
---
## 8. API 边界重构建议
建议不要继续把所有接口都平铺在根 `/api` 下,最好按域分层。
## 8.1 运行时 API
建议:
- `/api/runtime/llm/chat/completions`
- `/api/runtime/custom-world/scene-image`
- `/api/runtime/story/*`
- `/api/runtime/save/*`
职责:
- 面向玩家运行时
- 高并发、可限流
- 不允许直接写源文件
## 8.2 编辑器 API
建议:
- `/api/editor/item-overrides`
- `/api/editor/npc-visual-overrides`
- `/api/editor/character-overrides`
- `/api/editor/scene-overrides`
- `/api/editor/state-function-overrides`
- `/api/editor/assets/character-visual/publish`
- `/api/editor/assets/animation/publish`
职责:
- 面向创作者、运营、内部编辑器
- 必须鉴权
- 必须审计
- 不建议对公网完全开放
## 8.3 内部任务 API
建议:
- `/api/internal/jobs/*`
- `/api/internal/hooks/*`
职责:
- Worker 回调
- 内部系统同步
- 不给浏览器直接调用
---
## 9. 生产环境最小可行架构
如果你希望先尽快上线,而不是一上来就做重微服务,推荐先落这个版本。
## 9.1 单机版拓扑
```mermaid
flowchart TD
A["Browser"] --> B["Nginx"]
B --> C["dist 静态站点"]
B --> D["Node API Service"]
D --> E["LLM / DashScope"]
D --> F["本机持久化目录或对象存储"]
D --> G["PostgreSQL可后补"]
```
## 9.2 单机版适合解决的事
- 当前 CORS
- API Key 服务端托管
- 基础流式代理
- 简单编辑器接口
- 初步日志与限流
## 9.3 单机版暂时接受的妥协
- 图片生成先继续同步请求
- 小规模文件先存本机挂载目录
- 先不拆 Worker
- 编辑器暂时只给内网/白名单用户
这能保证你**先把项目稳稳部署起来**,而不是为了“最终形态”迟迟不落地。
---
## 10. 中期演进架构
当出现这些需求时,再进入下一阶段:
- 多人同时在线
- 多创作者协作
- 图片/视频生成任务变多
- 需要账号体系、存档、云同步
- 需要审计和版本回滚
推荐演进为:
```mermaid
flowchart LR
A["CDN / Nginx"] --> B["Web"]
A --> C["API Cluster"]
C --> D["Redis"]
C --> E["PostgreSQL"]
C --> F["OSS / S3"]
C --> G["Worker Cluster"]
G --> H["LLM / Image Vendor"]
```
演进重点:
1. API 服务可多实例部署
2. 任务通过队列解耦
3. 文件写对象存储
4. 业务状态入数据库
5. 后台操作有审计日志
---
## 11. 对当前仓库最关键的改造建议
## 11.1 第一优先级:把 Vite 里的 API 能力抽出来
当前 [scripts/dev-server/localApiPlugins.ts](/E:/Repos/Genarrative/scripts/dev-server/localApiPlugins.ts) 里的能力,建议分三类迁移:
### A. 运行时代理接口
- `LLM_PROXY_PATH`
- `CUSTOM_WORLD_SCENE_IMAGE_PATH`
这两类要迁移到正式 `server/` 服务里。
### B. 编辑器读写接口
- `item-overrides`
- `npc-visual-overrides`
- `character-overrides`
- `monster-overrides`
- `scene-overrides`
- `scene-npc-overrides`
- `state-function-overrides`
这类接口要保留,但必须加:
- 登录鉴权
- 角色权限
- 操作日志
- 环境隔离
### C. 资源发布接口
- `character-visual/publish`
- `animation/publish`
这类接口后续最适合迁移到:
- 对象存储
- 任务队列
- 元数据表
生产环境不建议继续“直接写 `public/generated-*` + 回写源码 JSON”。
## 11.2 第二优先级:把写源码文件改成写业务存储
开发期把 JSON 直接写回 `src/data/*.json` 可以接受,但生产环境不建议继续这样做。
建议演进路径:
### 短期
- 写到 `data/overrides/*.json`
- 作为运行数据目录挂载到服务器磁盘
- 和源码目录分离
### 中期
- `override` 元数据写 PostgreSQL
- 原始 JSON 内容也可写数据库 JSONB
- 大文件、图片、动画写对象存储
## 11.3 第三优先级:前端环境变量收敛
前端建议只保留少量公开变量,例如:
```env
VITE_API_BASE_URL=/api
VITE_LLM_PROXY_BASE_URL=/api/runtime/llm
VITE_SCENE_IMAGE_PROXY_BASE_URL=/api/runtime/custom-world/scene-image
```
而这些变量必须只存在服务端:
```env
LLM_BASE_URL=...
LLM_API_KEY=...
DASHSCOPE_BASE_URL=...
DASHSCOPE_API_KEY=...
DATABASE_URL=...
REDIS_URL=...
OBJECT_STORAGE_BUCKET=...
JWT_SECRET=...
```
---
## 12. 推荐的目录结构
在当前仓库基础上,建议逐步演化成:
```text
src/ # 前端
server/
app.ts # API 入口
routes/
runtime/
llm.ts
customWorld.ts
editor/
overrides.ts
assets.ts
internal/
jobs.ts
services/
llmService.ts
imageService.ts
overrideService.ts
publishService.ts
adapters/
llmAdapter.ts
dashscopeAdapter.ts
storageAdapter.ts
queueAdapter.ts
middleware/
cors.ts
auth.ts
rateLimit.ts
requestId.ts
config/
env.ts
workers/
mediaWorker.ts
storage/
uploads/
generated/
docs/
```
这样做的好处:
1. 前后端职责清晰
2. Vite 回到构建职责
3. 服务端逻辑可以独立部署
4. 以后做测试、监控、扩容都更自然
---
## 13. Nginx 参考配置
下面是一份适合当前项目思路的参考配置。
```nginx
server {
listen 80;
server_name game.example.com;
root /srv/genarrative/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /assets/ {
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
}
location /api/ {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Request-Id $request_id;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# 对流式接口很关键
proxy_buffering off;
add_header X-Accel-Buffering no;
}
}
```
如果未来拆成 `app.example.com` + `api.example.com`,再在 API 服务层开启精确 CORS 白名单。
---
## 14. API 服务中的 CORS 中间件建议
伪代码如下:
```ts
const allowOrigins = new Set([
'https://app.example.com',
'https://editor.example.com',
'http://localhost:3000',
]);
function applyCors(req, res) {
const origin = req.headers.origin;
if (!origin) {
return;
}
if (!allowOrigins.has(origin)) {
return;
}
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
res.setHeader(
'Access-Control-Allow-Methods',
'GET,POST,PUT,PATCH,DELETE,OPTIONS',
);
res.setHeader(
'Access-Control-Allow-Headers',
'Authorization,Content-Type,X-Request-Id,X-CSRF-Token',
);
res.setHeader('Access-Control-Max-Age', '600');
}
app.use((req, res, next) => {
applyCors(req, res);
if (req.method === 'OPTIONS') {
res.status(204).end();
return;
}
next();
});
```
要点:
1. 返回的是**请求方 origin 原值**,不是 `*`
2. 一定带 `Vary: Origin`
3. `OPTIONS` 在中间件层直接结束
4. 未命中白名单就不放行
---
## 15. 鉴权与安全建议
## 15.1 运行时接口
如果当前只是单人原型或内测,可以先允许匿名访问部分运行时接口,但仍建议至少加:
- IP 级限流
- 请求大小限制
- 上游超时
- 请求日志
## 15.2 编辑器接口
编辑器接口不能继续裸奔。
至少加:
1. 登录态
2. 角色权限
3. 操作人标识
4. 审计日志
5. 管理后台 Origin 白名单
建议角色最少拆成:
- `player`
- `editor`
- `admin`
## 15.3 密钥管理
所有第三方 Key 都只放在服务端:
- `.env.production`
- 服务器 secret manager
- 容器 secret
不要放到:
- `VITE_*`
- 浏览器 localStorage
- 前端 bundle
## 15.4 限流与熔断
建议至少做两层:
### Nginx 层
- IP 限流
- 突发连接限制
### API 层
- 用户 / Token 限流
- 按接口限流
- 上游失败熔断
这对图片生成和大模型请求尤其关键。
---
## 16. 存储设计建议
## 16.1 当前最容易出问题的点
当前开发期接口里存在:
- 直接读写 JSON 文件
- 直接写入 `public/generated-*`
这在单机开发是方便的,但上线后会遇到:
1. 多实例之间文件不同步
2. 发布新版本时生成文件可能丢失
3. 容器重启后本地文件丢失
4. 无法做版本与审计
## 16.2 推荐存储分层
### 结构化数据
用 PostgreSQL
- 用户
- 存档
- override 元数据
- 任务记录
- 审计日志
### 大文件 / 资源文件
用对象存储:
- 场景图
- 角色图
- 动画帧
- 导出资源
### 短生命周期状态
用 Redis
- 任务队列
- 限流计数
- 短期缓存
- 会话
---
## 17. 日志与可观测性
至少记录这些字段:
- `requestId`
- `userId` / `editorId`
- `route`
- `origin`
- `upstreamVendor`
- `model`
- `statusCode`
- `latencyMs`
- `timeoutMs`
- `errorCode`
- `errorMessage`
对大模型/图片接口尤其要记录:
1. 请求耗时
2. 上游状态码
3. 失败正文摘要
4. 重试次数
5. 任务 ID
这样以后排查时,才不会再次回到“只看到浏览器报 CORS不知道真因”的状态。
---
## 18. 推荐实施顺序
## 阶段 1先解决正式上线
目标:
- 前端可部署
- API Key 不暴露
- 不再浏览器直连第三方
- 当前 CORS 问题彻底解决
动作:
1. 新建独立 `server/` 服务
2.`/api/llm/chat/completions``/api/custom-world/scene-image` 迁过去
3. Nginx 统一反代 `/api/*`
4. 前端保持原有 `/api/*` 调用方式
5. 先在同域部署,尽量不碰 CORS
## 阶段 2把编辑器接口接入正式权限
动作:
1. editor API 独立命名空间
2. 增加登录、角色、审计
3. 把“写源码文件”改成“写业务数据目录或数据库”
## 阶段 3接入异步任务与对象存储
动作:
1. 图片生成改为 job queue
2. 结果回写数据库与对象存储
3. API 只返回任务状态与资源地址
## 阶段 4补齐平台化能力
动作:
1. 用户系统
2. 云存档
3. 限流配额
4. 后台审计
5. 监控告警
---
## 19. 最终推荐结论
针对这个项目,最稳、最适合当前现状、也兼顾未来的方案是:
1. **不要尝试让浏览器直接跨域访问第三方模型服务**
2. **新增独立 Node API 服务,替代当前生产环境对 Vite middleware 的依赖**
3. **通过 Nginx 把前端和 `/api/*` 统一到同一域名下,优先从架构层消灭大部分 CORS**
4. **把 API 分成 runtime / editor / internal 三层**
5. **把图片生成、资源发布逐步迁移到对象存储 + 异步任务**
6. **把未来的鉴权、限流、审计、日志、数据库能力预留在 BFF 层**
一句话总结:
**真正解决 CORS 的最佳方式,不是给浏览器更多跨域权限,而是让浏览器只访问你自己的服务;同时把当前开发期代理能力升级成正式的生产 API 架构。**
---
## 20. 适合立即执行的落地清单
如果现在就要开始做,建议按下面顺序推进:
1. 新建 `server/` 目录,先迁出 LLM 代理与场景图片代理
2. 把线上部署改成 `Nginx -> dist + Node API`
3. 保持前端仍然请求 `/api/llm``/api/custom-world/scene-image`
4. 先使用同域部署,不主动引入跨子域 CORS
5. editor 写接口上线前先加鉴权,不要裸开放
6. 生成文件先写挂载目录,下一步再迁对象存储
7. 为流式接口补 `proxy_buffering off`
8. 为 API 层补 `requestId + latency + upstream error` 日志
如果后续需要,我可以继续把这份文档下一步直接细化成:
- `server/` 目录结构草案
- `Express/Fastify` 的接口骨架
- `Nginx` 正式可用配置
- `Dockerfile + docker-compose` 部署方案
- 当前仓库对应的迁移 checklist

View File

@@ -0,0 +1,498 @@
# AI 原生经典 RPG 体验对标引擎 PRD
更新时间:`2026-04-06`
## 0. 目标
这份 PRD 建立在已有的:
- `AI_NATIVE_CROSS_GENRE_STORY_ENGINE_PRD_2026-04-06.md`
- `AI_NATIVE_NARRATIVE_THREAD_ITEM_AND_WORLD_NPC_PRD_2026-04-06.md`
之上,进一步回答一个更明确的问题:
**如何让当前仓库里的 AI 原生剧情引擎,不只是“会生成剧情”,而是能在现有游戏框架中,驱动出对标《仙剑》《轩辕剑》《古剑》《黑神话:悟空》《博德之门》这类经典作品的角色扮演体验。**
这里的“对标”不是复制题材、桥段或美术风格,而是抽取这些作品背后的体验能力:
1. 让玩家记住角色,而不只是记住设定。
2. 让世界里的地点、旧物、传闻、队友、任务彼此互相讲同一件事。
3. 让玩家的选择、立场、关系、误判、探索真正改变后续体验。
4. 让剧情推进同时拥有“作者性”和“系统性”。
一句话目标:
**把当前项目的 AI 原生叙事,升级成一种能稳定产出“经典单机 RPG 体验质感”的剧情引擎。**
## 1. 研究结论先说
综合参考对象后,可以把这几类经典体验抽象成 5 个核心方向:
1. `仙剑` 型体验
- 强角色记忆点
- “人”和“情”优先
- 主线、支线、传记、碎片化叙事共同塑造角色
2. `轩辕剑` 型体验
- 历史 / 神话 /文化意象共同构成宏大冲突
- 大时代与个人成长同时成立
- 系统玩法本身带文化与世界观意味
3. `古剑` 型体验
- 世界观先行
- 主题先行
- 角色命题、支线补完、衍生叙事共同构成厚世界
4. `黑神话` 型体验
- 空间本身讲故事
- 物件、残痕、命名、演出都带文化锚点
- 旅程像一次带试炼感的穿行,而不是纯任务导航
5. `博德之门` 型体验
- 队友强反应
- 选择有代价
- 关系状态、立场差异、任务分支和系统规则一起驱动叙事
对当前项目来说,这意味着剧情引擎必须补的,不是更多文案,而是下面 10 项能力:
1. 世界线程图谱
2. 题材适配层
3. 角色命题与秘密档案
4. 队友反应与关系矩阵
5. 信息可见性裁剪
6. 地点 / 物件 / 文书 / 残痕的叙事编译
7. 情境导演与节奏控制
8. 线程 -> 合约 -> 信号推进
9. 回响与长期记忆
10. 当前框架可落地的 orchestration 主链
## 2. 经典作品拆解
## 2.1 从《仙剑》系列提炼什么
《仙剑》最值得提炼的,不是仙侠题材,而是:
1. 角色与情感先于设定说明
2. 玩家会因为“人”和“情”记住剧情
3. 主线之外的角色剧情、人物传记、碎片信息都在持续加深理解
4. 角色魅力是分阶段投放,不是一口气灌输
对引擎的要求就是:
1. 每个重点角色都必须有“高光入口”
2. 角色认知要通过多载体逐步加深
3. 情感关系不能只挂在恋爱线,要覆盖友情、亲情、师承、旧债、牺牲、错过
4. 碎片化叙事必须和主线、支线互文,而不是散落设定
## 2.2 从《轩辕剑》系列提炼什么
《轩辕剑》最值得提炼的,不是具体朝代,而是:
1. 历史与神话共存
2. 宏观时代与个人命运互相牵引
3. 每一代往往有明确中心主题
4. 世界里的神器、法宝、炼妖壶、符鬼等系统并不是纯玩法道具,而是世界观的一部分
对引擎的要求就是:
1. 每个世界必须有明确的大时代冲突
2. 每段主线必须能同时看到“苍生线”和“个人线”
3. 每一轮内容生成都要知道自己服务的是哪条主题轴
4. 系统物件、战斗能力、资源名称、任务机制都要与世界观绑定
## 2.3 从《古剑》系列提炼什么
《古剑》最值得提炼的,不是台词风格,而是:
1. 世界观不是背景板,而是叙事发动机
2. 主题是预先确定的,比如“重生”“问道”
3. 游戏中只展示世界的一角,但能感到背后有更大的历史层
4. 支线、小说、传记、远古设定共同构成世界厚度
对引擎的要求就是:
1. 先生成“世界故事图谱”,再生成角色和剧情
2. 每个世界必须有自己的主题母题
3. 重点角色都要有“命题”
4. 支线不能只是奖励入口,而要承担世界观展开和人物补完
## 2.4 从《黑神话:悟空》提炼什么
《黑神话:悟空》最值得提炼的,不是神话题材本身,而是:
1. 空间叙事强
2. 旅程感强
3. 文化意象直接进入地点、敌人、物件、建筑和命名
4. 玩家不是靠大段讲解理解世界,而是靠穿行、观察、搏斗和残痕去感受
对引擎的要求就是:
1. 场景必须有“故事残痕层”
2. 路线推进要像试炼 / 朝圣 / 追索 / 深入禁地,而不是只弹任务文本
3. 重点物件必须是文化锚点和旧事证人
4. 敌人与地标也应属于叙事载体,而不只是战斗内容
## 2.5 从《博德之门》提炼什么
《博德之门》最值得提炼的,不是西式奇幻题材,而是:
1. 队友是强叙事引擎
2. 不同角色对同一行为会有不同立场反应
3. 关系推进有信任门槛
4. 玩家选择会改变关系、任务理解和队伍结构
5. 系统层会用可控的“认可 / 不认可”“信任 / 不信任”来表达复杂反应,而不是无限写分支
对引擎的要求就是:
1. 每个重点队友都要有可追踪的立场轴和信任轴
2. 对同一选择,队友必须出现差异化反应
3. 关系状态要进入后续任务、聊天、协战、赠礼、剧情揭示
4. 系统必须允许“有限分支 + 强反馈”,而不是追求不可控的全分支写作
## 3. 对标能力矩阵
## 3.1 引擎必须支持的体验支柱
建议把“经典 RPG 体验”拆成下面 8 根支柱:
1. 角色羁绊支柱
2. 主题表达支柱
3. 世界厚度支柱
4. 路线试炼支柱
5. 选择后果支柱
6. 队友反应支柱
7. 叙事载体支柱
8. 回响记忆支柱
## 3.2 支柱与经典作品的对照
| 支柱 | 仙剑 | 轩辕剑 | 古剑 | 黑神话 | 博德之门 |
| --- | --- | --- | --- | --- | --- |
| 角色羁绊 | 强 | 中 | 强 | 中 | 强 |
| 主题表达 | 强 | 强 | 强 | 中到强 | 强 |
| 世界厚度 | 中到强 | 强 | 强 | 强 | 强 |
| 路线试炼 | 中 | 中 | 中 | 强 | 中 |
| 选择后果 | 中 | 中 | 中 | 轻 | 强 |
| 队友反应 | 中 | 中 | 中 | 轻 | 强 |
| 叙事载体 | 中 | 强 | 强 | 强 | 强 |
| 回响记忆 | 中 | 强 | 强 | 中 | 强 |
结论不是“每个游戏都做一样多”,而是:
**要想对标这些经典作品的总和体验,引擎不能只强其中一项。**
## 4. 面向当前项目的引擎能力设计
## 4.1 世界线程图谱
用于承载:
- 《轩辕剑》式的大时代冲突
- 《古剑》式的远古 / 旧史 / 深层设定
- 《仙剑》式的主线与碎片化叙事互文
最低要求:
1. 每个世界有 `明线线程`
2. 每个世界有 `暗线线程`
3. 每个世界有 `旧事伤痕`
4. 每个世界有 `主题母题`
## 4.2 角色命题与秘密档案
用于承载:
- 《仙剑》式角色高光与感情曲线
- 《古剑》式角色命题与主题性成长
- 《博德之门》式角色秘密与立场差异
每个重点角色必须有:
1. 外显身份
2. 当前压力
3. 表面目标
4. 真实目标
5. 已付代价
6. 关系负债
7. 禁区
8. 可触发反应的关键词
## 4.3 队友反应与关系矩阵
这是当前项目对标《博德之门》最需要补的一块。
建议每个重点队友都至少维护:
```ts
interface CompanionStanceProfile {
trust: number;
warmth: number;
ideologicalFit: number;
fearOrGuard: number;
loyalty: number;
currentConflictTag?: string | null;
recentApprovals: string[];
recentDisapprovals: string[];
}
```
作用:
1. `trust`
- 决定是否愿意跟随你深入风险。
2. `warmth`
- 决定情感表达密度。
3. `ideologicalFit`
- 决定在价值观选择上的反应强度。
4. `fearOrGuard`
- 决定低关系阶段是不是更容易误会、回避、抗拒。
5. `loyalty`
- 决定关键节点是否站队。
## 4.4 信息可见性层
这是对标《古剑》的世界层深度、对标《仙剑》的分段投放、对标《博德之门》的可控分支,以及修复当前自定义世界 prompt 泄露问题的共同底座。
核心要求:
1. 角色知道什么
2. 玩家知道什么
3. 角色此刻愿意说什么
4. 当前场景允许模型看到什么
必须分开建模。
## 4.5 叙事载体编译层
这是对标《黑神话》《轩辕剑》《古剑》最关键的一层。
载体不能只限于装备物品,应统一抽象为:
1. 遗物
2. 证物
3. 文书
4. 材料
5. 禁物
6. 装置
7. 记忆碎片
8. 残痕场景
每个载体都要有:
1. 可见线索
2. 见证痕
3. 未完成问题
4. 当前出现理由
5. 后续反应钩子
## 4.6 情境导演与节奏层
这是对标《仙剑》的情感推进、《黑神话》的旅程压迫、《博德之门》的分支张力时最重要的“节奏控制器”。
导演层要先判断:
1. 当前是情感深化回合,还是冲突升级回合
2. 当前应推进明线、暗线、关系线、还是主题线
3. 当前需要推前台的是角色、地点、还是物件
4. 当前披露预算应该是低、中还是高
没有导演层AI 文本会持续飘散。
## 4.7 线程 -> 合约 -> 信号推进
这是把经典单机 RPG 的作者性故事,转成 AI 原生可持续运行结构的关键。
统一抽象如下:
1. 线程
- 这段故事在讲哪条线。
2. 合约
- 当前线的阶段目标、参与者、条件、失败态与回报。
3. 信号
- 玩家做了什么,导致哪个关系、地点、物件、真相片段发生变化。
## 4.8 回响与长期记忆
这是决定剧情是否像“经典作品”的最后一层。
玩家之所以会觉得这些作品有余味,不是因为文本多,而是因为:
1. 旧事会回来
2. 旧物会再被提起
3. 旧误解会被翻案
4. 旧角色会因为你做过的事改变口风
因此系统必须维护:
1. 事件记忆
2. 关系记忆
3. 线索记忆
4. 误解记忆
5. 已揭示真相记忆
## 5. 当前框架接入方案
## 5.1 `src/services/customWorld.ts`
从“批量生成 NPC 和地标”升级为:
1. 先生成 `ThemePack`
2. 再生成 `WorldStoryGraph`
3. 再生成 `ActorNarrativeProfile`
4. 最后补 `backstoryReveal / skills / initialItems`
它将成为当前项目里最接近“古剑式世界观先行 + 轩辕剑式宏大主题”的入口。
## 5.2 `src/services/prompt.ts`
从“组装上下文”升级为:
1. 读取 `VisibilitySlice`
2. 读取 `SceneNarrativeDirective`
3. 只注入当前阶段可见、可说、可推的最小剧情上下文
它将成为:
- 对标《博德之门》强反应又可控
- 对标《仙剑》分阶段角色理解
- 修复当前全知视角泄露
的核心模块。
## 5.3 `src/data/npcInteractions.ts`
从“关系规则函数集合”升级为:
1. 首遇状态机
2. 队友立场矩阵
3. 认可 / 不认可反馈
4. 关系冲突 tag
5. 私聊 / 队伍 / 营地事件触发
它将承担当前项目里最接近《博德之门》队友系统和《仙剑》角色关系成长的部分。
## 5.4 `src/services/questDirector.ts`
从“任务导演”升级为:
1. 剧情线程导演
2. 合约生成器
3. 信号推进器
4. 阶段揭示器
它将承担对标《轩辕剑》宏观主线推进和《博德之门》分支任务反馈的主链。
## 5.5 `src/data/runtimeItemDirector.ts` / `src/data/runtimeItemNarrative.ts`
从“运行时奖励导演”升级为:
1. 通用叙事载体编译器
2. 重点物件故事指纹生成器
3. 角色 / 场景 / 势力 / 旧事的回响载体
它将承担对标《黑神话》物件文化锚点、对标《轩辕剑》器物世界观、对标《古剑》旧史残痕的能力。
## 5.6 `src/hooks/useStoryGeneration.ts`
从“剧情总 orchestrator”收束为
1. 读取导演结果
2. 发起对应 contract
3. 驱动生成
4. 回写记忆与信号
不要继续把越来越多叙事细节直接塞进这个 hook而是让它只做总线协调。
## 6. 体验验收标准
如果要说“能对标经典单机 RPG 体验”,至少要达到下面这些结果。
## 6.1 角色体验
1. 玩家在一次完整体验后,能明确记住至少 `3~5` 个队友 / 核心角色的个性、矛盾和关键旧事。
2. 低好感角色不会只是“冷淡”,而是会带明确压力、错位说辞和暗线钩子。
3. 高关系角色会在聊天、任务、协战、赠礼、事件节点中显著改变表达与立场。
## 6.2 世界体验
1. 玩家能感到世界背后有更深层的旧史与暗线,而不是所有信息都在主线上直接交代。
2. 场景、地点、支线、人物传记、物件描述之间会互相印证。
3. 世界主题在命名、系统、任务、人物冲突上持续一致。
## 6.3 选择体验
1. 玩家选择能被至少一名队友明确认可或反对。
2. 至少一部分选择会影响后续任务理解、关系推进、额外剧情或可见信息。
3. 系统既要让选择有重量,又不能因为分支爆炸而失控。
## 6.4 旅程体验
1. 场景推进要有“试炼 / 追索 / 深入 / 回望”的旅程感。
2. 不是所有叙事都靠对话框完成,空间和载体也必须承担讲故事的职责。
3. 玩家会因为一个地标、一个旧物、一次反应,主动去拼出暗线。
## 7. 推荐落地顺序
## 阶段 A先补对标经典作品的共同底座
优先做:
1. `ThemePack`
2. `WorldStoryGraph`
3. `ActorNarrativeProfile`
4. `VisibilitySlice`
## 阶段 B优先把当前项目最能出效果的两条链做强
1. 队友 / NPC 关系反应链
2. 运行时物件 / 场景残痕叙事链
这是当前最容易直接提升“像经典 RPG”的地方。
## 阶段 C把任务和主线推进改成线程化
重点补:
1. 线程
2. 合约
3. 信号
4. 阶段揭示
## 阶段 D补营地 / 旅途中队友事件
这是当前项目对标《仙剑》与《博德之门》体验非常关键的一步。
建议新增:
1. 营地对话
2. 旅途中短反应
3. 关键选择后的队友插话
4. 队友之间的互相评价
## 阶段 E做经典体验压力测试
至少要用 5 类体验场景去压测引擎:
1. 情感型主线
2. 历史 / 神话型大事件
3. 世界观层层揭示型流程
4. 旅程试炼型场景链
5. 队友强反应分支型流程
## 8. 一句话结论
要让当前项目的 AI 原生剧情引擎真正对标《仙剑》《轩辕剑》《古剑》《黑神话》《博德之门》这些经典作品,关键不是去模仿哪一种题材,而是让引擎同时具备:
- 让人记住角色的能力
- 让世界互相说话的能力
- 让选择产生后果的能力
- 让地点与物件承担叙事的能力
- 让长线回响沉淀下来的能力
只有这些能力一起成立当前框架里跑出来的体验才会从“AI 会写剧情”真正跨到“AI 能驱动经典 RPG 质感”。

View File

@@ -0,0 +1,554 @@
# AI 原生跨题材剧情引擎 PRD
更新时间:`2026-04-06`
## 0. 定位
这份 PRD 的目标不是为某一种题材写一套“更会编故事的文案系统”,而是设计一套:
**可适配奇幻、武侠、仙侠、科幻、悬疑、恐怖、末世、都市、校园、神话等多种题材的 AI 原生游戏剧情引擎。**
它应该解决的不是单一题材里的句子风格,而是更底层的问题:
1. 世界的明线、暗线如何被系统化组织。
2. 角色、地点、物件、文书、怪物、装置、尸体、遗迹这些叙事载体如何共同讲故事。
3. 玩家当前能知道什么、误以为知道什么、还不能知道什么,如何被稳定控制。
4. AI 如何负责叙事生成,本地规则如何负责边界、状态、可见性、推进信号与玩法编译。
一句话定位:
**它是一个“剧情引擎层”,不是一个“某题材内容包”。**
## 1. 设计目标
这套引擎要同时满足 6 个目标:
1. 跨题材
- 核心语义不写死在武侠、奇幻、修仙、蒸汽朋克等具体题材上。
2. AI 原生
- 剧情文本、现场张力、角色表述、线索回响由 AI 参与生成,而不是只做静态模板替换。
3. 规则可控
- 世界状态、信息泄露边界、关系推进、任务推进、奖励发放仍由本地规则约束。
4. 叙事网状化
- 故事不只存在于主线任务里,而是分布在角色、物件、地点、事件余波与传闻中。
5. 可扩展
- 新增题材时,优先新增“题材适配层”,而不是推翻剧情引擎本体。
6. 可验证
- 能明确验收“是否有故事感”“是否埋得住暗线”“是否越权泄露”“是否跨题材仍成立”。
## 2. 参考方法抽象
本次设计参考的是成熟叙事游戏的方法,不是照搬具体剧情。
## 2.1 可借鉴的方法来源
1. CRPG 方法
- 代表思路:角色秘密、阵营立场、任务分支、物件与角色的反应联动。
- 可借鉴点:角色不是独立设定卡,而是世界冲突中的活节点。
2. 沉浸式侦查 / 推理方法
- 代表思路:线索不是一次性交代,而是靠地点、证物、口供、误导与缺失共同成立。
- 可借鉴点:信息差、误判、再解释,是故事感的重要来源。
3. 系统叙事方法
- 代表思路:事件、状态、关系、资源变化会自然生成“像故事”的结果。
- 可借鉴点:引擎应先保证状态与因果,再让 AI 把它叙事化。
4. Roguelike / 重复游玩叙事方法
- 代表思路:角色关系、旧伤、遗物、失败记录、阶段揭示会在多轮体验中叠加意义。
- 可借鉴点:故事不是一次性讲完,而是通过回响与累积形成。
5. 强氛围题材方法
- 代表思路:名字、物件、俗称、禁忌称呼、残损意象本身就携带故事。
- 可借鉴点:叙事载体不只靠大段说明,也能靠命名与残痕表达。
## 2.2 抽象结论
综合这些方法后,这套引擎应固定采用下面这 5 个叙事原则:
1. 故事必须网状分布,不能只挂在主线任务上。
2. 信息披露必须分层,不能让模型默认全知。
3. 低关系、低信任、低理解阶段,不能减少故事密度,只能减少披露深度。
4. 物件与地点必须是故事证人,而不只是功能容器。
5. 题材差异应该主要落在“词汇、意象、制度、冲突形式”上,而不是改变剧情引擎基本语法。
## 3. 引擎核心原则
1. AI 负责叙事表达,本地负责规则裁决。
2. 世界先于角色,角色先于对白,状态先于文案。
3. 所有剧情都必须能回到“谁知道什么、谁想隐藏什么、谁正在承受什么”。
4. 明线、暗线、代价线、回响线是所有题材共通的最小叙事单元。
5. 信息可见性必须被数据化,而不能只靠 prompt 口头提醒。
6. 引擎关注“剧情语法”,题材包只负责“表现词汇”。
## 4. 引擎总架构
建议把 AI 原生剧情引擎拆成 8 个层:
1. 世界语义层
2. 题材适配层
3. 角色与阵营层
4. 信息可见性层
5. 情境导演层
6. 合约与信号推进层
7. 叙事载体编译层
8. 记忆与回响层
关系如下:
```text
世界语义层
-> 题材适配层
-> 角色与阵营层
-> 信息可见性层
-> 情境导演层
-> 合约与信号推进层
-> 叙事载体编译层
-> 记忆与回响层
```
## 5. 世界语义层
## 5.1 目标
让所有题材都先被翻译成一套统一的世界叙事骨架,而不是直接开始生成角色和对白。
## 5.2 建议的数据结构
```ts
interface StoryThread {
id: string;
title: string;
visibility: 'visible' | 'hidden';
summary: string;
conflictType: string;
stakes: string;
involvedFactionIds: string[];
involvedActorIds: string[];
relatedLocationIds: string[];
}
interface StoryScar {
id: string;
title: string;
pastEvent: string;
publicResidue: string;
hiddenTruth: string;
relatedActorIds: string[];
relatedLocationIds: string[];
}
interface StoryMotif {
id: string;
label: string;
semanticRole: 'institution' | 'ritual' | 'technology' | 'taboo' | 'ruin' | 'memory' | 'resource' | 'creature';
lexicalHints: string[];
}
interface WorldStoryGraph {
visibleThreads: StoryThread[];
hiddenThreads: StoryThread[];
scars: StoryScar[];
motifs: StoryMotif[];
}
```
## 5.3 为什么它必须在最前面
没有这层图谱,就会出现这类问题:
1. 角色各自有设定,但彼此没有共享暗线。
2. 物件命名很酷,但和世界冲突没有关系。
3. 场景有氛围,但和主要矛盾不互相印证。
4. 每次 AI 输出都像重新发明一个宇宙。
## 6. 题材适配层
## 6.1 目标
让题材差异变成一个可替换的“表现层”,而不是把剧情引擎本体写死成某一类世界观。
## 6.2 建议的数据结构
```ts
interface ThemePack {
id: string;
displayName: string;
toneRange: string[];
institutionLexicon: string[];
tabooLexicon: string[];
artifactClasses: string[];
actorArchetypes: string[];
conflictForms: string[];
clueForms: string[];
namingPatterns: string[];
revealStyles: string[];
}
```
## 6.3 题材适配层负责什么
它只负责:
1. 把“机构”翻译成宗门、财团、学会、调查局、帮派、舰队、公司、教团等。
2. 把“禁忌”翻译成邪术、封印、机密协议、校园旧规、污染区准则等。
3. 把“叙事载体”翻译成遗物、枪械、芯片、病例、证物、祭器、录像带、样本、航图等。
4. 把“冲突形式”翻译成宫廷斗争、公司内斗、调查失踪、阵营战争、神话追索、生存竞争等。
它不负责:
1. 决定角色到底知道什么。
2. 决定剧情推进是否合法。
3. 决定哪些信息此刻允许披露。
## 7. 角色与阵营层
## 7.1 角色不是“背景文本”,而是“叙事立场体”
每个角色都必须被拆成下面几个面向:
1. 外显身份
2. 当前处境
3. 表面目标
4. 真实目标
5. 隐藏关系
6. 已付代价
7. 不愿被碰的禁区
8. 会触发反应的关键词
## 7.2 建议的数据结构
```ts
interface ActorNarrativeProfile {
publicMask: string;
firstContactMask: string;
visibleLine: string;
hiddenLine: string;
contradiction: string;
debtOrBurden: string;
taboo: string;
immediatePressure: string;
relatedThreadIds: string[];
relatedScarIds: string[];
reactionHooks: string[];
}
```
## 7.3 低关系角色的引擎规则
低关系、低好感、低信任角色必须满足:
1. 有压力
2. 有保留
3. 有错位
4. 有反应钩子
不能只是:
1. 说得少
2. 更冷淡
3. 更短句
正确做法应该是:
- 披露深度更低
- 戏剧张力更高
- 错误说辞更多
- 观察与试探更明显
## 8. 信息可见性层
## 8.1 这是 AI 原生剧情引擎的核心
如果不把“可见性”数据化AI 叙事会天然滑向全知视角。
因此必须明确区分:
1. 事实是否存在
2. 玩家是否知道
3. 当前角色是否愿意说
4. 当前 prompt 是否允许注入
5. 玩家是否只是误以为知道
## 8.2 建议的数据结构
```ts
interface KnowledgeFact {
id: string;
content: string;
ownerActorIds: string[];
relatedThreadIds: string[];
visibility: 'public' | 'discoverable' | 'private' | 'forbidden';
}
interface VisibilitySlice {
factIds: string[];
sayableFactIds: string[];
inferredFactIds: string[];
forbiddenFactIds: string[];
misdirectionHints: string[];
}
```
## 8.3 运行时规则
1. prompt 只吃 `VisibilitySlice`
2. 未解锁章节不等于不存在,但不能进当前 prompt
3. 角色知道某事,不等于此刻愿意承认
4. 玩家接触到线索,不等于系统要直接盖章真相
## 9. 情境导演层
## 9.1 目标
每一轮剧情生成都不是“让 AI 自由写”,而是先由导演层判断:
1. 此刻最重要的压力是什么
2. 谁在主导场面
3. 当前最适合推进的是明线、暗线还是关系线
4. 哪些叙事载体应该被推到前台
## 9.2 导演层输入
- 当前场景
- 当前实体
- 当前关系状态
- 当前可见信息
- 最近信号变化
- 玩家上一步行动
- 尚未回响的故事线程
## 9.3 导演层输出
```ts
interface SceneNarrativeDirective {
primaryPressure: string;
activeThreadIds: string[];
foregroundActorIds: string[];
foregroundCarrierIds: string[];
revealBudget: 'low' | 'medium' | 'high';
emotionalCadence: 'tense' | 'curious' | 'hostile' | 'intimate' | 'tragic' | 'mysterious';
}
```
## 10. 合约与信号推进层
## 10.1 目标
让剧情推进不依赖纯脚本,而是依赖“意图 -> 合约 -> 信号”。
## 10.2 统一抽象
1. 意图
- 当前想推动什么关系、冲突、调查或获取。
2. 合约
- 把意图翻译成可追踪的步骤、条件、参与者与回报。
3. 信号
- 玩家行动、地点变化、物件获取、关系变化、战斗结果、情报拼接后触发推进。
## 10.3 为什么这层跨题材都成立
因为不管是:
- 武侠寻仇
- 科幻调查
- 校园秘密
- 末世生存
- 神话追索
它们最终都能抽象成:
- 某种线索被拿到
- 某个误会被确认或打破
- 某种关系被推进或撕裂
- 某个真相片段被解锁
## 11. 叙事载体编译层
## 11.1 不要只把“物品”当成装备
跨题材剧情引擎里,叙事载体不应仅仅是“装备 / 道具”,而应统一抽象成:
```ts
type NarrativeCarrierType =
| 'artifact'
| 'document'
| 'evidence'
| 'device'
| 'resource'
| 'corpse'
| 'sample'
| 'relic'
| 'ritual_object'
| 'memory_fragment';
```
## 11.2 每个载体必须包含的叙事指纹
```ts
interface CarrierStoryFingerprint {
visibleClue: string;
witnessMark: string;
unresolvedQuestion: string;
currentAppearanceReason: string;
relatedThreadIds: string[];
relatedScarIds: string[];
reactionHooks: string[];
}
```
## 11.3 编译规则
每个叙事载体都至少要能回答:
1. 它是谁、哪处、哪次事留下的痕迹?
2. 它为什么现在出现?
3. 它之后能让谁产生反应?
4. 它和哪条线程有关系?
这层的作用,是让:
- 奇幻里的遗物
- 武侠里的旧兵器
- 科幻里的芯片
- 悬疑里的口供
- 恐怖里的录像带
- 校园题材里的匿名纸条
都能进入同一套剧情引擎。
## 12. 记忆与回响层
## 12.1 目标
让世界对玩家行动和已获取的叙事载体产生长期回响,而不是每轮都像第一次发生。
## 12.2 记忆分层
建议至少拆成:
1. 事件记忆
2. 关系记忆
3. 线索记忆
4. 误解记忆
5. 已揭示真相记忆
## 12.3 回响规则
一个故事线程真正成立,不是因为它被写过一次,而是因为它能在后续这些地方重新出现:
1. 别的角色说法里
2. 新地点残痕里
3. 新载体命名里
4. 新任务前提里
5. 关系变化反应里
## 13. Prompt Contract 设计
## 13.1 建议拆成 6 类 contract
1. 世界图谱 contract
2. 角色叙事档案 contract
3. 章节解锁 contract
4. 场景导演 contract
5. 叙事载体意图 contract
6. 回响总结 contract
## 13.2 contract 总原则
1. AI 只拿当前阶段需要的最小上下文
2. AI 不直接决定数值、库存、状态迁移、任务合法性
3. AI 输出优先是“意图 / 钩子 / 视角 / 叙事指纹”,不是庞大成品对象
4. 所有未解锁信息都不能被默认注入
## 14. 与当前仓库的接入方式
这套引擎并不是脱离当前项目另起炉灶,而是可以沿着已有骨架往前升级。
## 14.1 可直接复用的现有基础
1. `customWorld.ts`
- 已有世界生成骨架,可升级成“世界图谱 + 角色叙事档案”生成入口。
2. `prompt.ts`
- 已有上下文组织能力,可升级成“基于可见性切片”的 prompt 裁剪器。
3. `questDirector.ts`
- 已有任务导演方向,可升级成“线程 -> 合约 -> 信号”的推进器。
4. `runtimeItemDirector.ts` / `runtimeItemNarrative.ts`
- 已有运行时奖励与叙事包装能力,可升级成“叙事载体编译器”。
5. `npcInteractions.ts`
- 已有关系状态和首遇逻辑,可升级成“关系与披露双轴控制器”。
## 14.2 建议新增的模块
- `src/services/storyEngine/themePack.ts`
- `src/services/storyEngine/worldStoryGraph.ts`
- `src/services/storyEngine/visibilityEngine.ts`
- `src/services/storyEngine/actorNarrativeDossier.ts`
- `src/services/storyEngine/sceneNarrativeDirector.ts`
- `src/services/storyEngine/carrierNarrativeCompiler.ts`
- `src/services/storyEngine/echoMemory.ts`
## 15. 验收标准
这套引擎至少要满足下面这些标准,才能算“跨题材 AI 原生剧情引擎”而不是“某一类题材文案增强器”。
1. 同一套引擎在至少 3 种明显不同的题材包里都能产出结构稳定的世界线程、角色秘密与叙事载体。
2. 低关系角色在不同题材下都能做到“有压力、有错位、有暗线钩子”,而不是只会变冷淡。
3. 未解锁信息不会在首遇、低披露或无关场景中提前进入 prompt。
4. 至少 `80%` 的重点叙事载体都能被玩家看出与某条故事线程、某个旧伤或某个角色关系有关。
5. 玩家在不看后台数据的情况下,仍能通过角色、物件、地点、任务描述拼出世界里的明线与暗线。
6. 新增一个题材时,主要工作量集中在 `ThemePack` 与词汇适配,而不是重写剧情主逻辑。
## 16. 推荐落地顺序
## 阶段 A先做引擎底层不先卷文案
先补:
- 世界图谱
- 可见性切片
- 角色叙事档案
## 阶段 B再接当前项目最需要的两个落点
1. 大世界 NPC 背景与首遇表达
2. 运行时物件名称、描述与回响
## 阶段 C再把任务、地点、关系、载体接成一张网
重点做:
- 线程推进
- 合约生成
- 信号触发
- 回响回写
## 阶段 D最后做多题材验证
至少选 3 种差异足够大的题材做压力测试:
1. 高奇幻 / 武侠神话类
2. 科幻 / 调查类
3. 悬疑 / 恐怖 / 校园 / 现代类
## 17. 最后结论
真正可复用的 AI 原生剧情引擎,不应该先问“这句像不像某个题材”,而应该先问:
1. 世界有哪些正在运行的明线和暗线?
2. 谁知道什么?谁不肯说什么?谁在承受什么?
3. 哪些角色、地点、物件、证物正在共同讲同一件事?
4. 当前这一轮,玩家应该感到的是压力、怀疑、诱惑、误导,还是揭示?
只有当这些问题被引擎层回答清楚之后,不同题材的外观、词汇和风格,才会真正长在同一套 AI 原生剧情框架之上。

View File

@@ -0,0 +1,734 @@
# AI 原生叙事线索驱动的物品命名与大世界 NPC 背景系统 PRD
更新时间:`2026-04-06`
## 0. 目标
这份 PRD 面向当前仓库,解决两个直接影响“故事感”的核心问题:
1. 实时生成的物品名称、描述虽然已经能贴合 build 和来源,但整体仍偏模板拼接,缺少“它背后发生过什么”的故事重量。
2. 大世界生成的 NPC 背景目前更像设定摘要,尤其是初始好感度低的角色,容易只剩“冷淡 / 戒备 / 不说”,却没有真正的戏剧张力和暗线钩子。
本次设计参考《博德之门 3》《黑神话悟空》这类 RPG 的叙事组织方式,但不照搬具体内容,而是提炼出适合当前项目的 4 个目标:
1. 每个 NPC 都不仅有“公开设定”,还要有能被逐步识别的明线、暗线、代价线与回响线。
2. 每个运行时物品都不仅是“当前 build 的奖励”,还要像某件旧事留下的证物、遗物、债物或禁物。
3. 初始好感度低不等于故事薄;低好感角色要更像“有东西不肯说”,而不是“没有东西可说”。
4. AI 继续负责叙事表达与意图,本地规则继续负责数值、合法性、状态迁移与编译。
一句话目标:
**让角色背景、物品名称、物品描述都成为世界主线与暗线的载体,而不是玩法奖励外面包的一层随机文案。**
## 1. 当前问题定位
## 1.1 运行时物品叙事目前仍偏模板拼接
当前实现已经有 `runtimeItemContext -> runtimeItemDirector -> runtimeItemCompiler -> runtimeItemNarrative` 这条主链,玩法层是成立的,但叙事密度仍然不够。
主要问题有 3 个:
1. `src/data/runtimeItemNarrative.ts`
- 名称仍然主要依赖 `来源词 + 关系词 + 功能词` 的固定拼接。
- 描述仍然主要是“谁留下的什么 + 为什么出现 + 偏向什么 build”的单模板句式。
2. `src/data/runtimeItemNarrative.ts`
- fallback 的 `reasonToAppear` 仍是“与最近局势把它推到了你面前”这一类通用解释。
- 这能解释“为什么它不是纯随机”,但解释不了“它到底见证过什么”。
3. `src/services/runtimeItemAiPrompt.ts`
- 当前 AI contract 仍聚焦 `shortNameSeed / sourcePhrase / reasonToAppear / relationHooks / desiredBuildTags`
- 它能产出“贴合当前局势”的轻量意图,但还不够支撑“证物感、传闻感、宿命感、旧债感”。
结果就是:
- 物品已经不再是纯随机装备,但还没有成为能埋故事线索的叙事节点。
- 玩家会知道“它适合当前 build”但不一定会对“它从哪段旧事里来”产生兴趣。
## 1.2 自定义世界 NPC 生成仍偏“字段补齐”,不够像故事角色
当前 `src/services/customWorld.ts` 的自定义世界生成已经能产出完整的:
- `backstory`
- `personality`
- `motivation`
- `combatStyle`
- `backstoryReveal`
- `skills`
- `initialItems`
但问题在于,它更像一份“角色设定卡 JSON”而不是“能在游玩里逐步显影的故事人物”。
当前的主要限制有:
1. 单次生成字段很多,但每个字段长度被压得很短。
2. `backstoryReveal` 固定 4 章,但更像摘要切片,而不是围绕世界冲突组织的叙事章节。
3. 生成要求强调“不要改定位、不要超字段、字符串尽量简洁”,这对结构稳定有帮助,但会明显牺牲戏剧性。
结果就是:
- NPC 有设定,但缺少真正的事件痕迹。
- NPC 有动机,但缺少真正的秘密、债务、误认、旧案、禁忌、未完成关系。
- NPC 有章节,但章节之间没有足够强的剧情递进与回响。
## 1.3 自定义世界 NPC 的信息边界仍在越权泄露
这部分是当前“低好感角色仍然不神秘”的一个关键根因。
目前在 `src/services/prompt.ts` 中,自定义世界 NPC 遭遇描述仍会注入:
1. 完整 `backstory`
2. 所有 `backstoryReveal.chapters`
3. 技能与初始物品细节
同时 `describeCustomWorldSection(...)` 还会把多名 NPC 的:
- 公开背景
- 完整背景
- 动机
- 技能
- 初始物品
- 章节 teaser
打成“自定义世界补充档案”整体注入。
这会直接导致两个问题:
1. 模型在第一次见面、低好感、甚至仅仅“面前遭遇”阶段,就已经站在“全知视角”上写角色。
2. 角色表面上虽然还在说得很少,但模型其实已经知道太多,所以写出来的话会天然带着“设定卡背书感”,而不是“此刻只肯露一角”。
## 1.4 初始低好感角色目前更多是“收口”,不是“施压”
低好感角色无聊,核心不在于他们说得少,而在于他们缺少下面这些东西:
1. 面前局势里的压力
2. 说辞与真实动机之间的错位
3. 对某个旧事件、旧物、旧人、旧地的条件反射
4. 让玩家感觉“这人知道点什么,但现在不肯给”的钩子
也就是说:
**当前低好感角色更多是信息减少了,但戏剧密度没有同步提高。**
## 2. 设计参考转译
本次设计参考的不是具体桥段,而是两类成熟 RPG 的叙事组织方法。
## 2.1 来自《博德之门 3》的可借鉴点
1. 角色秘密不是独立文本,而是会影响初见印象、后续关系、任务走向与物品反应。
2. 物品、书信、遗物、口供、尸体、地标都在一起构成线索网,而不是各说各话。
3. 真正有意思的角色,往往不是“设定很多”,而是“表面、欲望、隐瞒、代价”彼此错位。
## 2.2 来自《黑神话:悟空》的可借鉴点
1. 名称本身就带旧事与异感,不只是功能标签。
2. 很多信息不是直接说明,而是通过残缺线索、旧痕、俗称、误传、传闻去显影。
3. 角色和物件都会带一种“事已经发生过,但后劲还在”的叙事余震。
## 2.3 对当前项目的转译原则
转成当前仓库可落地的做法后,所有重点 NPC 与重点物品都必须携带四条线:
1. 明线
- 玩家当前就能感知到的表层目标、来意、用途、冲突位置。
2. 暗线
- 与世界冲突、旧事件、秘密关系、禁忌知识有关,但此刻不完全说透的线。
3. 代价线
- 角色或物品背后已经失去过什么、欠着什么、被谁盯上、为什么不能轻易松口。
4. 回响线
- 能与其他 NPC、场景、任务、物品互相印证的共享线索、意象、事件伤痕或势力痕迹。
## 3. 设计原则
1. AI 负责叙事层,本地负责规则层。
- 不能让模型直接决定数值、掉落预算、好感变化和状态迁移。
2. 首次接触只给“可见的一角”,不给“全量设定”。
- 低好感阶段更要严格限制 prompt 注入范围。
3. 低好感降低的是披露深度,不是故事密度。
- 对方可以不坦白,但必须有压力、矛盾、误导、观察和反应。
4. 物品必须先是叙事证物,再是功能容器。
- build 倾向依然重要,但需要嵌在“它是谁留下的、为什么出现在这里”之中。
5. 世界里的 NPC、物品、场景要共享同一套叙事词根与事件节点。
- 不允许每次生成都像一个独立小宇宙。
6. UI 展示保持克制。
- 不在面板里默认堆规则说明,只展示结果与线索感,符合项目已有的清爽游戏 UI 要求。
## 4. 核心系统结论
建议把当前“物品叙事 + 大世界 NPC 背景”升级为:
**世界叙事图谱 -> NPC 叙事档案 -> 运行时物品叙事指纹 -> prompt 可见性裁剪 -> 反应与回响回写**
它不是新起一套独立玩法,而是补在当前这些模块之上:
- `src/services/customWorld.ts`
- `src/services/prompt.ts`
- `src/data/runtimeItemContext.ts`
- `src/data/runtimeItemDirector.ts`
- `src/data/runtimeItemNarrative.ts`
- `src/services/runtimeItemAiPrompt.ts`
## 5. 世界级叙事图谱设计
## 5.1 目标
给自定义世界补一层比 `summary / tone / factions / landmarks` 更强的叙事骨架,让 NPC 和物品都不是凭空生出来,而是从同一套世界秘密与旧事件里长出来。
## 5.2 建议新增的数据结构
```ts
interface WorldNarrativeThread {
id: string;
title: string;
lineType: 'visible' | 'shadow';
summary: string;
involvedFactions: string[];
involvedNpcIds: string[];
relatedLandmarkIds: string[];
motifIds: string[];
}
interface WorldNarrativeScar {
id: string;
title: string;
pastEvent: string;
visibleResidue: string;
hiddenTruth: string;
relatedNpcIds: string[];
relatedLandmarkIds: string[];
}
interface WorldNarrativeMotif {
id: string;
label: string;
usage: 'name' | 'item' | 'dialogue' | 'landmark';
examples: string[];
}
interface CustomWorldNarrativeGraph {
visibleThreads: WorldNarrativeThread[];
shadowThreads: WorldNarrativeThread[];
scars: WorldNarrativeScar[];
motifs: WorldNarrativeMotif[];
}
```
并挂到:
```ts
interface CustomWorldProfile {
narrativeGraph?: CustomWorldNarrativeGraph;
}
```
## 5.3 叙事图谱最少产出要求
每个自定义世界最少应生成:
1. `3` 条明线线程
2. `4~6` 条暗线线程
3. `6~10` 个旧事件伤痕
4. `12~20` 个世界词根 / 意象母题
这些词根不是为了写百科,而是为了让:
- NPC 名字外的说话习惯
- 物品名称中的来源词
- 物品描述中的事件痕迹
- 地标描述中的遗留痕迹
能够反复互相印证。
## 6. 大世界 NPC 叙事档案设计
## 6.1 当前 `backstoryReveal` 的问题
当前 `backstoryReveal` 已经有“按好感解锁”的结构,但更接近“把背景拆成 4 段”,还不够像“有明线暗线的角色档案”。
建议保留现有外层结构,但新增一层更强的叙事骨架。
## 6.2 建议新增的数据结构
```ts
interface NpcNarrativeDossier {
publicMask: string;
firstContactMask: string;
visibleLine: string;
shadowLine: string;
contradiction: string;
debtOrOath: string;
hiddenFearOrTaboo: string;
scenePressure: string;
relatedThreadIds: string[];
relatedScarIds: string[];
motifIds: string[];
reactionHooks: string[];
linkedItemSeeds: string[];
}
```
建议挂到:
```ts
interface CustomWorldRoleProfile {
narrativeDossier?: NpcNarrativeDossier;
}
```
字段含义如下:
- `publicMask`
- 外人最容易听到或看到的版本。
- `firstContactMask`
- 第一次接触时这人会先拿出来挡在前面的那层说辞。
- `visibleLine`
- 当前出现在此地、此刻最能被玩家感知到的表层线。
- `shadowLine`
- 背后真正连着哪条暗线,但还不会直接说透。
- `contradiction`
- 这个角色最值得被玩家察觉的“说辞与事实错位”。
- `debtOrOath`
- 让角色更像活在故事中的关键债务、誓言、旧命令或未结关系。
- `hiddenFearOrTaboo`
- 这个角色不愿被提起的人、事、地、物或称谓。
- `scenePressure`
- 这个角色此刻为什么紧绷、拖延、转移、误导。
- `reactionHooks`
- 未来哪些人名、物名、伤痕、势力称呼会触发反应。
## 6.3 低初始好感角色的写法规则
低好感不是“少写”,而是换成“斜着写、卡着写、顶着写”。
建议按初始好感分 4 档处理。
| 初始好感区间 | 角色手感 | 必须出现的内容 | 禁止出现的内容 |
| --- | --- | --- | --- |
| `<= -10` | 敌意、误认、警惕、试图抢占叙事主导权 | 现场威胁、对玩家的判断、一个错误说辞、一个破绽 | 上来完整交代来历 |
| `-9 ~ 14` | 戒备、带压力的克制、把话题往表层拖 | 当前局势、表面理由、一个不愿明说的对象、一个观察细节 | 平铺直叙式自我介绍 |
| `15 ~ 39` | 正常交流但不交底 | 表层目标、与场景关联、一个旧事余波 | 直接说最终动机 |
| `>= 40` | 有合作空间但仍保留底牌 | 合作可能、旧债或旧誓、阶段性真相 | 一次性摊牌全部秘密 |
关键规则:
1. 低好感角色的首轮输出必须带“错位感”。
2. 至少要有一个让玩家觉得“这句话不全对”的缝隙。
3. 至少要有一个和此地事件、旧痕、物件、人物有关的具体钩子。
## 6.4 背景章节的重组方式
当前仍可保留 4 章,但不建议继续把它们仅仅当成“摘要 1、摘要 2、摘要 3、摘要 4”。
建议把 4 章改成稳定的功能分层:
1. `surface`
- 表层来意 / 当下伪装 / 公开身份切口
2. `scar`
- 旧事裂痕 / 已经失去的东西 / 不愿再提的一次失败
3. `bind`
- 关系债务 / 誓言 / 阵营绑缚 / 必须维护的错误
4. `truth`
- 真正目标 / 最终底牌 / 真相代价
保留 `id` 稳定,标题允许按世界主题动态生成,而不是固定文案。
## 6.5 章节文本的写法要求
每一章都必须同时回答两件事:
1. 这段旧事和世界哪条明线 / 暗线有关?
2. 它为什么会影响这个 NPC 今天的说话方式、站位、物品、关系或敌意?
也就是说,章节正文不该只是“以前发生了什么”,而要能连回“现在为什么会这样”。
## 7. 运行时物品叙事指纹设计
## 7.1 当前问题
当前运行时物品已经能贴合:
- 场景
- 遭遇
- build 缺口
- 关系锚点
但它更像“上下文化奖励”,还不够像“故事痕迹”。
## 7.2 建议新增的数据结构
```ts
interface RuntimeItemStoryFingerprint {
visibleClue: string;
witnessMark: string;
unfinishedBusiness: string;
hiddenHook: string;
relatedThreadIds: string[];
relatedScarIds: string[];
motifIds: string[];
reactionHooks: string[];
namingPattern:
| 'npc_relic'
| 'scene_relic'
| 'faction_issue'
| 'monster_trophy'
| 'quest_evidence'
| 'forbidden_object';
}
```
并挂到:
```ts
interface RuntimeItemMetadata {
storyFingerprint?: RuntimeItemStoryFingerprint;
}
```
## 7.3 每件重点物品必须携带的叙事要素
`rare` 及以上,或者 `narrativeWeight = medium / heavy` 的物品,至少要同时有:
1. 一个可见线索
- 玩家光看名字、描述或出处就能捕到的痕迹。
2. 一个见证痕
- 它像谁留下的、从哪次旧事里滚出来的、带着什么使用痕迹。
3. 一个未完成问题
- 这件物品背后还有什么没结掉。
4. 一个当前出现理由
- 为什么偏偏是现在、是这里、是你拿到它。
5. 一个可回响对象
- 哪个 NPC / 场景 / 势力 / 任务之后可能对它起反应。
## 7.4 命名系统升级
当前的三段式命名方向是对的,但需要从“词块拼装”升级为“叙事指纹编译”。
建议按来源分 6 种命名范式:
| 命名范式 | 适用来源 | 推荐结构 | 示例风格 |
| --- | --- | --- | --- |
| 人物遗物 | NPC 奖励、遗失物 | 旧称 / 誓约 / 功能物 | `断旗旧誓护心佩` |
| 场景遗物 | 宝藏、废墟、秘境 | 地标 / 灾痕 / 品类 | `沉炉灰纹短刃` |
| 势力制式 | 商店、军需、黑市 | 势力 / 制式 / 用途 | `巡河司缉印符` |
| 怪物战利 | 怪物掉落、生态素材 | 生态 / 异化 / 精粹 | `雾骨猎印精粹` |
| 任务证物 | 委托、追查、交付 | 事件 / 口供 / 信物 | `沉港失契信物` |
| 禁忌物 | 关键宝藏、暗线物 | 禁名 / 封痕 / 器类 | `烬名封缄骨匣` |
要求不是字数更长,而是:
- 名字里至少有一个世界词根
- 至少有一个事件痕或关系痕
- 最后才落到功能词或器类词
## 7.5 描述文案升级
当前描述模板需要升级为三层表达:
1. 第一层:可见痕迹
- 这件东西看起来像经历过什么。
2. 第二层:旧事牵连
- 它和谁、哪处、哪次旧事有关。
3. 第三层:当前局势意义
- 为什么此刻来到玩家手里,以及它偏向什么战斗或 build 方向。
示例结构:
```text
表面痕迹句。旧事牵连句。当前局势与玩法意义句。
```
要求:
1. 第二句必须有“谁 / 哪处 / 哪次事”的具体指向之一。
2. 不能只有“适合当前 build”这种系统性总结。
3. 允许保留一点空白,不要把暗线直接讲穿。
## 8. Prompt Contract 升级
## 8.1 自定义世界 NPC 生成 prompt 的问题
当前 prompt 的主要问题,不是字段不够,而是:
1. 生成阶段没有先产“世界叙事图谱”,导致 NPC 和物品共享词根不稳定。
2. 角色阶段过早要求完整字段,导致模型更像在补设定表。
3. 文本长度限制过严,压缩掉了故事张力。
## 8.2 建议改成三阶段生成
### 阶段 A世界叙事图谱
先产出:
- `visibleThreads`
- `shadowThreads`
- `scars`
- `motifs`
### 阶段 B角色骨架
只产出:
- `name`
- `title`
- `role`
- `description`
- `initialAffinity`
- `relationshipHooks`
- `tags`
- `narrativeDossier`
### 阶段 C背景章节、技能、初始物品
基于前两阶段结果,再补:
- `backstory`
- `backstoryReveal`
- `skills`
- `initialItems`
这样做的目的不是把流程变复杂,而是防止:
- 世界叙事图谱还没稳定,角色就先各自长成独立设定卡。
## 8.3 自定义世界 NPC 生成时的必填叙事约束
每个 NPC 必须明确写出:
1. 当前站在哪条明线上
2. 真正卷入哪条暗线
3. 一件已经发生过、仍在影响他的旧事
4. 一个不愿被直问的对象、地点、称呼或物件
5. 一个与玩家可能建立连接的切入口
## 8.4 运行时物品 AI contract 升级
建议在当前 `RuntimeItemAiIntent` 外增加叙事指纹字段:
```ts
interface RuntimeItemAiIntent {
shortNameSeed: string;
sourcePhrase: string;
reasonToAppear: string;
relationHooks: string[];
desiredBuildTags: string[];
desiredFunctionalBias: Array<'heal' | 'mana' | 'cooldown' | 'guard' | 'damage'>;
tone: 'grim' | 'mysterious' | 'martial' | 'ritual' | 'survival';
visibleClue?: string;
witnessMark?: string;
unfinishedBusiness?: string;
hiddenHook?: string;
reactionHooks?: string[];
namingPattern?: string;
}
```
AI 仍然不直接产出成品,只负责:
- 提供线索
- 提供见证感
- 提供未完成之事
- 提供命名范式建议
本地编译层再决定:
- 最终名称
- 最终描述
- metadata 回写
- 稀有度与 build 预算
## 9. Prompt 可见性裁剪规则
## 9.1 自定义世界 NPC 遭遇 prompt
必须修正为:
1. 首遇或低披露阶段
- 只注入 `publicSummary`
- 只注入 `firstContactMask / visibleLine / scenePressure`
- 只注入已解锁章节的 `contextSnippet`
2. 禁止直接注入
- 完整 `backstory`
- 未解锁 `backstoryReveal.content`
- 全量章节摘要
## 9.2 自定义世界总档案注入
`buildCustomWorldReferenceText(...)` 不能再把多名 NPC 的完整背景和章节提示整体塞进主 prompt。
建议改成:
1. 世界摘要
2. 叙事图谱摘要
3. 与当前场景 / 当前遭遇最相关的少量 NPC 索引
每名 NPC 只保留:
- 名称
- 身份
- 公开背景
- 所在线程
- 关键反应钩子
## 9.3 低好感阶段的 prompt 目标
模型应该知道的是:
- 这个角色此刻为什么不松口
- 他在盯什么
- 哪个问题会让他产生反应
模型不应该知道的是:
- 他完整的人生说明书
## 10. UI 表达建议
UI 侧保持项目当前的清爽方向,不默认堆规则说明文案。
建议只做这几种表达:
1. NPC 档案页
- 公开背景
- 已解锁章节
- 未解锁章节 teaser
- 不额外展示系统术语
2. 重点物品 tooltip / 描述区
- 名称
- 三层式描述
- 如需增加额外信息,只显示“来历”或“传闻”一行,不做规则说明板
3. 与物品 / 人物的回响
- 优先通过剧情文本、反应文本、任务追加线索体现
- 不优先通过 UI 标签硬显示“这是一条暗线”
## 11. 验收标准
做到以下几点,才算这次需求真正成立:
1. 随机抽样 `20` 个运行时重点物品时,至少 `80%` 的名称能看出明确来源词与事件痕,不再像纯功能拼接词。
2. 随机抽样 `20` 个运行时重点物品时,`100%` 的描述都能同时回答“它经历过什么”和“为什么此刻出现”。
3. 随机抽样 `20` 个自定义世界 NPC 时,`100%` 都能指出自己挂在哪条明线、暗线和哪道旧伤上。
4. 初始好感度低于 `15` 的 NPC首轮文本中至少有一个“说辞与真实意图错位”的点。
5. 首遇与低披露阶段的自定义世界 NPC prompt 中,不再直接注入完整 `backstory` 与未解锁章节。
6. 同一世界内物品名、场景名、NPC 章节和剧情文本之间,能够复用同一组词根与事件痕,而不是每次随机飘散。
7. 玩家在不看后台数据的情况下,也能从名字、描述、对话和档案 teaser 中隐约拼出世界里的明线与暗线。
## 12. 推荐落地顺序
## 阶段 A先修正信息边界与现状问题
优先改:
- `src/services/prompt.ts`
- `src/services/customWorld.ts`
- `src/services/customWorldReferenceText` 相关逻辑
目标:
- 堵住完整背景与未解锁章节的越权注入
- 让低好感阶段先重新成立
## 阶段 B补世界叙事图谱
新增:
- `CustomWorldNarrativeGraph`
- 世界线程、旧伤、意象词根
目标:
- 让 NPC 与物品共享同一套故事母题
## 阶段 C补 NPC 叙事档案
新增:
- `NpcNarrativeDossier`
调整:
- `backstoryReveal` 的生成逻辑
- 低初始好感 NPC 的首遇表达规则
目标:
- 让“低好感但有戏”成为稳定产物
## 阶段 D补运行时物品叙事指纹
新增:
- `RuntimeItemStoryFingerprint`
调整:
- `runtimeItemAiPrompt`
- `runtimeItemNarrative`
目标:
- 让物品名称与描述能承载旧事、见证与未完成问题
## 阶段 E做回响与反应
最后接:
- NPC 对特定物品的反应
- 任务对物品线索的承接
- 场景与旧伤的互相印证
目标:
- 让“名字和背景有故事”真正进入游玩闭环,而不是只停在文案层
## 13. 涉及文件建议
建议优先改动这些区域:
- `src/types/customWorld.ts`
- `src/types/runtimeItem.ts`
- `src/services/customWorld.ts`
- `src/services/prompt.ts`
- `src/services/runtimeItemAiPrompt.ts`
- `src/data/runtimeItemNarrative.ts`
- `src/data/runtimeItemDirector.ts`
建议新增这些模块:
- `src/services/customWorldNarrative.ts`
- `src/services/customWorldNarrativePrompt.ts`
- `src/data/runtimeItemStoryCompiler.ts`
## 14. 一句话结论
这次要做的,不是把物品文案写得更华丽,也不是把 NPC 背景写得更长,而是:
**让世界里的每个人、每件物都像被主线和暗线真正碰过,名字里有来路,描述里有旧事,对话里有保留,低好感时也能让玩家感觉到背后压着一整段没被说出来的故事。**