清理:删除无用文档
This commit is contained in:
1 parent
4cd9b85c3a
commit
c4f9e5375a
12 files changed
+316
-5898
No files matched your search
@@ -8,8 +8,7 @@
|
||||
<strong>一款 AIGC 驱动、高自由度文字体验、修仙世界观的开源游戏项目。</strong>
|
||||
</p>
|
||||
|
||||
> 本仓库当前实现为 `Hono + React SPA`。
|
||||
> 这里的说明以现有代码为准,已不再适用于旧版 Next.js 架构。
|
||||
> 本仓库当前实现为 `Hono + React SPA`。这里的说明以现有代码为准,已不再适用于旧版 Next.js 架构。
|
||||
|
||||
---
|
||||
|
||||
@@ -43,18 +42,18 @@
|
||||
<img src="https://page-r2.daoyou.org/index/Xnip2026-02-02_19-02-21.png" alt="云游坊市" width="260" />
|
||||
</p>
|
||||
|
||||
## 当前技术栈
|
||||
## 技术概览
|
||||
|
||||
- 服务端:`Hono 4` + `Bun`
|
||||
- 前端:`React 19` + `React Router 7` + `Vite 8`
|
||||
- 样式:`Tailwind CSS 4`
|
||||
- 数据库:`PostgreSQL` + `Drizzle ORM`
|
||||
- 认证:`Better Auth`
|
||||
- 缓存 / 分布式协调:`Redis`
|
||||
- 消息与实时广播:`NATS JetStream` + `NATS Core`
|
||||
- 认证:`Better Auth`
|
||||
- AI 能力:`AI SDK` + `DeepSeek`
|
||||
|
||||
## 当前目录结构
|
||||
## 仓库布局
|
||||
|
||||
```text
|
||||
.
|
||||
@@ -64,292 +63,14 @@
|
||||
├── src/shared/ # 共享引擎、配置、类型、契约
|
||||
├── drizzle/ # 业务表 Drizzle migrations
|
||||
├── drizzle-auth/ # Better Auth Drizzle migrations
|
||||
├── drizzle.auth.config.ts # Better Auth 独立迁移配置
|
||||
├── scripts/ # 部署脚本与生产/NATS Compose
|
||||
├── docker/Dockerfile.app # Bun 主服务镜像
|
||||
└── vite.config.ts
|
||||
```
|
||||
|
||||
## 运行方式
|
||||
## 本地开发与部署
|
||||
|
||||
这个仓库不是 SSR 应用。
|
||||
|
||||
- `src/react-app` 使用 `BrowserRouter` 管理前端路由
|
||||
- `src/server/app.ts` 提供 `/api/*` 和 `/internal/*` 接口
|
||||
- `src/index.ts` 在生产环境注册 Bun 内置 cron;Cron 只向 NATS WorkQueue 发布后台 command
|
||||
- 前端 SPA 独立部署到 Cloudflare Pages;后端 Docker 不再服务 `index.html` 或静态资源
|
||||
|
||||
当前路由约定:
|
||||
|
||||
- `/api/*`:游戏与后台 API
|
||||
- `/api/auth/*`:Better Auth
|
||||
- `/internal/cron/*`:内部定时任务接口
|
||||
- `/api/health-check`:健康检查
|
||||
- 其余如 `/login`、`/game`、`/admin`:Cloudflare Pages 上的前端 SPA 路由
|
||||
|
||||
## 环境要求
|
||||
|
||||
- `Bun 1.3+`
|
||||
- `PostgreSQL`
|
||||
- `Redis`:在线对局、邀请、截止时间、恢复索引和 API 部分能力的权威存储
|
||||
- `NATS`:进程启动硬依赖;JetStream 承载领域事件、异步投影、后台 command、战斗演算指针、终态清理和回放归档,Core 只承载可丢失的跨实例实时提示
|
||||
|
||||
说明:
|
||||
|
||||
- 仓库脚本默认围绕 `bun` / `bunx` 编写,不建议继续沿用旧的 `npm + Next.js` 使用方式
|
||||
- 开发模式默认端口是 `5173`
|
||||
- 构建后服务默认端口是 `3000`
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
bun install
|
||||
cp .env.example .env.local
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
### 启动时必需
|
||||
|
||||
这些变量缺失时,服务会在启动阶段或鉴权初始化阶段直接报错:
|
||||
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `DATABASE_URL` | PostgreSQL 连接串 |
|
||||
| `BETTER_AUTH_SECRET` | Better Auth 密钥 |
|
||||
| `BETTER_AUTH_URL` | Better Auth 后端对外基准地址;生产填 API 域名,如 `https://api.example.com` |
|
||||
| `NATS_SERVERS` | NATS 服务地址,多个地址使用逗号分隔 |
|
||||
| `NATS_USER` / `NATS_PASSWORD` | NATS 应用用户凭据 |
|
||||
|
||||
实时战斗由 Bun/Hono 主服务直接承载;生产环境还必须配置:
|
||||
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `REDIS_URL` | 在线对局唯一权威状态、邀请、凭据、截止时间与恢复索引 |
|
||||
| `NATS_SERVERS` / `NATS_USER` / `NATS_PASSWORD` | 战斗演算、终态清理和回放归档使用的 JetStream,以及跨实例状态提示使用的 NATS Core |
|
||||
|
||||
客户端通过已认证的 session API 获取 60 秒有效的一次性 WebSocket ticket,不能自行声明玩家身份。实时战斗的数据边界固定为:`battle-v5` 只做确定性规则解析;Bun 主服务负责协议、调度和广播;进行中的权威状态、选招、锁定、邀请、演出战报和回放素材只在 Redis。主服务通过 JetStream 小型指针任务分配统一演算和终态清理;回放归档 consumer 再从 Redis 组装 `BattleReplayV1`,异步、幂等写入 `wanjiedaoyou_battle_replay_archives`。NATS 消息不承载完整 battle save 或战报;玩家 command 链路不查询或写入 PostgreSQL,也不使用 Redis Stream。
|
||||
|
||||
### 建议同时配置
|
||||
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `REDIS_URL` | Redis 连接串;缺失时相关功能会在运行时失败 |
|
||||
| `API_IP_RATE_LIMIT_WINDOW_SECONDS` | `/api/*` 全局 IP 令牌桶补充周期秒数;默认 `60` |
|
||||
| `API_IP_RATE_LIMIT_MAX_REQUESTS` | `/api/*` 同 IP 令牌桶容量和每周期补充 token 数;默认 `300` |
|
||||
| `PUBLIC_WEB_ORIGINS` | 允许访问 API 的前端 origin,逗号分隔,如 `https://app.example.com,http://localhost:5173` |
|
||||
| `BETTER_AUTH_COOKIE_DOMAIN` | 可选;同站子域部署时可填 `.example.com` 启用跨子域 cookie |
|
||||
| `ADMIN_EMAILS` | 管理员邮箱白名单,逗号分隔 |
|
||||
| `ADMIN_USER_IDS` | Better Auth 管理员用户 ID 白名单,逗号分隔;账号管理工具必须配置 |
|
||||
|
||||
### 生产 cron 必需
|
||||
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `CRON_SECRET` | 保护 `/internal/cron/*` 接口的 Bearer 密钥;生产环境必须配置,调度器调用时也要携带它 |
|
||||
|
||||
### 登录 / 注册相关
|
||||
|
||||
当前鉴权中,以下接口会强制要求 ALTCHA PoW payload:
|
||||
|
||||
- `/api/auth/sign-in/email`
|
||||
- `/api/auth/sign-up/email`
|
||||
- `/api/auth/request-password-reset`
|
||||
- `/api/auth/email-otp/send-verification-otp`
|
||||
|
||||
前端通过 `/api/captcha/challenge` 获取带场景和过期时间的 challenge,完成 PoW
|
||||
后把 payload 发送给认证接口。服务端会验证签名、场景和有效期,并通过 Redis
|
||||
原子记录 challenge 的单次消费状态,防止重放。
|
||||
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `VITE_API_BASE_URL` | 前端构建时注入的后端 API 基地址,如 `https://api.example.com` |
|
||||
| `ALTCHA_HMAC_SECRET` | 服务端签发和验证 ALTCHA challenge 的独立 HMAC 密钥;生产环境必须配置 |
|
||||
|
||||
ALTCHA 不需要前端 site key。认证 CAPTCHA 启用时 Redis 也是强依赖;Redis 不可用时,
|
||||
受保护的认证请求会失败关闭,避免 challenge 被重复使用。
|
||||
|
||||
### 邮件能力
|
||||
|
||||
邮箱验证码、密码注册验证邮件、重置密码邮件、后台邮件广播都会使用SMTP。密码注册必须完成邮箱验证后才能登录;验证链接完成后会自动登录:
|
||||
|
||||
| 变量 | 说明 |
|
||||
| ----------------------------------------- | ------------- |
|
||||
| `SMTP_HOST` / `SMTP_PORT` / `SMTP_SECURE` | SMTP 连接配置 |
|
||||
| `SMTP_USER` / `SMTP_PASS` | SMTP 认证信息 |
|
||||
| `MAIL_FROM` | 发件人 |
|
||||
|
||||
### AI 能力
|
||||
|
||||
AI 相关功能只调用 DeepSeek 官方 API。服务端默认配置:
|
||||
|
||||
- `DEEPSEEK_API_KEY`:服务端 DeepSeek API Key
|
||||
- `DEEPSEEK_MODEL`:服务端默认模型,未配置时使用 `deepseek-chat`
|
||||
|
||||
玩家也可以在游戏设置中保存自己的 DeepSeek API Key 与模型。BYOK 配置只保存在当前浏览器;请求携带的配置不完整或格式无效时,服务端会返回 400,不会静默消耗服务器额度。
|
||||
|
||||
## 数据库初始化
|
||||
|
||||
首次启动通常要做两件事:
|
||||
|
||||
1. 应用业务表迁移
|
||||
2. Better Auth 表迁移
|
||||
|
||||
```bash
|
||||
bunx drizzle-kit migrate
|
||||
bun run auth:migrate
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 运行这些命令前,请先确保 `DATABASE_URL`、`BETTER_AUTH_SECRET`、`BETTER_AUTH_URL` 已在当前进程环境中可见
|
||||
- `drizzle/` 目录下已经存在业务表迁移文件
|
||||
- `drizzle/` 只管理 `wanjiedaoyou_*` 业务表
|
||||
- `drizzle-auth/` 只管理固定 `better_auth` schema,并使用独立迁移历史表
|
||||
- `bun run auth:migrate` 使用 `drizzle.auth.config.ts` 执行认证迁移
|
||||
- `bun run auth:generate` 用于认证 Drizzle schema 变更后生成迁移,不是每次启动都要执行
|
||||
- 升级部署时先执行 `bun run auth:migrate` 建立认证基线,再部署使用共享 Bun SQL 连接池的新版本
|
||||
|
||||
## 本地开发
|
||||
|
||||
1. 准备好 `.env.local`
|
||||
2. 确保数据库、Redis 和 NATS JetStream 可连接
|
||||
3. 执行迁移
|
||||
4. 启动开发服务器
|
||||
|
||||
```bash
|
||||
docker compose -f scripts/docker-compose.nats.yml up -d
|
||||
bun run dev
|
||||
```
|
||||
|
||||
本地 NATS 容器监听 `4222`,监控端口为 `8222`,开发凭据与 `.env.example` 一致。持久数据保存在 Docker volume `nats-data` 中。
|
||||
|
||||
生产硬切顺序、Stream/consumer、DLQ 和故障检查参见 [`docs/nats-domain-events.md`](docs/nats-domain-events.md)。
|
||||
|
||||
访问:
|
||||
|
||||
- 前端页面:`http://localhost:5173`
|
||||
- 健康检查:`http://localhost:5173/api/health-check`
|
||||
|
||||
`bun run dev` 会同时启动 Vite 前端和 Bun/Hono 主服务;Vite 将 `/api`、`/internal` 与 WebSocket 升级代理到 Bun 服务。
|
||||
|
||||
本地 NATS 使用 JetStream 文件卷保存消息和 durable consumer 的投递位点。启动时发现历史消息是预期行为;回放归档和事务消息会在 PostgreSQL 可用后继续消费。若 PostgreSQL 暂时不可用,事务消息恢复器会以 5 秒至 60 秒退避重试,避免连接超时期间持续打满连接池;不应通过删除 NATS 数据卷来规避数据库故障。
|
||||
|
||||
## 构建与运行
|
||||
|
||||
| 命令 | 作用 |
|
||||
| --- | --- |
|
||||
| `bun run dev` | 启动 Vite 与 Bun/Hono 主服务 |
|
||||
| `bun run build` | 依次构建前端与服务端 |
|
||||
| `bun run build:client` | 构建 Cloudflare Pages 使用的前端 SPA |
|
||||
| `bun run build:server` | 构建 Docker 使用的 Bun/Hono 后端 |
|
||||
| `bun run preview` | 先构建,再运行 `dist/index.js` |
|
||||
| `bun run start` | 直接运行已构建产物 |
|
||||
| `bun run lint` | ESLint 检查 |
|
||||
| `bun run test` | Vitest |
|
||||
| `bun run battle:smoke` | 实时战斗 2v2/4v4、超时默认出招、协议、负载与 Worker 故障注入验收 |
|
||||
| `REDIS_URL=redis://127.0.0.1:6379/15 bun run battle:e2e:redis` | 使用本机隔离 Redis DB 验收重启、多实例、幂等和归档 staging |
|
||||
| `bun run auth:generate` | 生成 `better_auth` Drizzle 迁移 |
|
||||
| `bun run auth:migrate` | 执行 `better_auth` 独立迁移流 |
|
||||
|
||||
构建产物:
|
||||
|
||||
- `build:client` 产出前端 SPA
|
||||
- `build:server` 产出 Bun 运行的 Hono 服务入口 `dist/index.js`
|
||||
|
||||
## Docker
|
||||
|
||||
React SPA 继续独立部署到 Cloudflare Pages,不进入后端镜像。`app`(`3000`)使用 Bun,同时承载 Hono API 与实时战斗 WebSocket;`battle-v5` 仍是无框架依赖的纯战斗引擎。PostgreSQL 回放归档由应用侧 NATS consumer 完成。
|
||||
|
||||
本地构建镜像:
|
||||
|
||||
```bash
|
||||
docker build -t daoyou-app:local -f docker/Dockerfile.app .
|
||||
```
|
||||
|
||||
运行镜像:
|
||||
|
||||
```bash
|
||||
docker run --rm -p 3000:3000 \
|
||||
--env-file /path/to/.env.production \
|
||||
daoyou-app:local
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- `VITE_API_BASE_URL` 是前端 Pages 构建期变量,不进入后端 Docker 镜像
|
||||
- 服务运行时环境变量通过 shell、容器环境或 `--env-file` 注入
|
||||
|
||||
## 仓库内现成部署脚本
|
||||
|
||||
### Hono API 蓝绿发布
|
||||
|
||||
```bash
|
||||
APP_IMAGE=swkzymlyy/daoyou-app:<version> \
|
||||
ENV_FILE=/root/daoyou/.env.production \
|
||||
./scripts/blue-green-app.sh
|
||||
```
|
||||
|
||||
这个脚本会:
|
||||
|
||||
- 在 `daoyou-app-blue` / `daoyou-app-green` 间部署闲置颜色
|
||||
- 同时验证 Docker health 与宿主机 `/api/health-check`
|
||||
- 通过 `nginx -t` 后原子切换 OpenResty upstream
|
||||
- 短暂 drain 后停止旧颜色容器
|
||||
|
||||
生产 Compose 只定义 `app-blue` 和 `app-green`;实时战斗与 API 随同一 Bun 主服务蓝绿发布; `blue-green-app.sh` 通过 Compose 启动闲置 app profile 并切换 OpenResty。React SPA 仍由 Cloudflare Pages 独立部署。
|
||||
|
||||
## 生产 cron 配置方式
|
||||
|
||||
当前仓库默认采用两层设计:
|
||||
|
||||
- 生产环境中 `src/index.ts` 注册 Bun 内置 cron,Cron 只发布 JetStream command,durable Worker consumer 执行 job runner
|
||||
- `/internal/cron/*` 仍然保留,便于手动触发、联调,或后续切回外部调度器
|
||||
|
||||
- `GET /internal/cron/auction-expire`
|
||||
- `GET /internal/cron/bet-battle-expire`
|
||||
- `GET /internal/cron/rank-rewards`
|
||||
- `GET /internal/cron/market-refresh`
|
||||
- `GET /internal/cron/tower-enemy-sets`
|
||||
- `GET /internal/cron/player-state-events-cleanup`
|
||||
- `GET /internal/cron/expired-data-cleanup`
|
||||
|
||||
当前内置调度频率:
|
||||
|
||||
- `auction-expire`:每 2 分钟
|
||||
- `bet-battle-expire`:每 2 分钟
|
||||
- `rank-rewards`:每天 `00:00 Asia/Shanghai`
|
||||
- `market-refresh`:每 5 分钟
|
||||
- `tower-enemy-sets`:每小时
|
||||
- `player-state-events-cleanup`:每天 `02:30 Asia/Shanghai`
|
||||
- `expired-data-cleanup`:每天 `02:45 Asia/Shanghai`
|
||||
|
||||
说明:
|
||||
|
||||
- Bun 内置 cron 仍运行在 Web 进程内,但实际任务已与调度回调解耦;发布成功的 command 由 JetStream 持久化并可跨应用重启继续执行
|
||||
- Bun 的 cron 表达式按 `UTC` 解释,所以 `rank-rewards` 在代码里配置为 `0 16 * * *`,对应北京时间次日 `00:00`
|
||||
- 内置调度不直接调用 job runner,也不走 HTTP;它发布 `daoyou.command.cron.>` command
|
||||
- `/internal/cron/*` 接口继续要求 `Authorization: Bearer ${CRON_SECRET}`,适合人工补跑或外部调度
|
||||
- 这些任务内部带 Redis 分布式锁与幂等保护,重复触发会返回 `skipped`
|
||||
|
||||
如果你想改回外部 HTTP 调度,可使用:
|
||||
|
||||
```cron
|
||||
*/2 * * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/auction-expire
|
||||
*/2 * * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/bet-battle-expire
|
||||
0 0 * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/rank-rewards
|
||||
*/5 * * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/market-refresh
|
||||
0 * * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/tower-enemy-sets
|
||||
30 2 * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/player-state-events-cleanup
|
||||
45 2 * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/expired-data-cleanup
|
||||
```
|
||||
|
||||
## CI / 镜像发布
|
||||
|
||||
当前仓库的 [`.github/workflows/deploy.yml`](.github/workflows/deploy.yml) 会在 `master` 分支推送时:
|
||||
|
||||
- 构建 Docker 镜像
|
||||
- 推送到 Docker Hub
|
||||
环境要求、环境变量、数据库初始化、本地开发、构建、Docker、生产 cron 与部署脚本说明已整理到 [docs/development.md](docs/development.md)。
|
||||
|
||||
## 贡献指南
|
||||
|
||||
@@ -361,10 +82,7 @@ ENV_FILE=/root/daoyou/.env.production \
|
||||
4. 推送到分支 (`git push origin feature/NewFeature`)。
|
||||
5. 提交 Pull Request。
|
||||
|
||||
- **架构原则**:
|
||||
- 引擎层(`engine/`)完全独立于 UI 和框架
|
||||
- 业务逻辑放在 Service 层
|
||||
- 数据访问使用 Repository 模式
|
||||
开发与部署的完整约定请参见 [本地开发与部署](#本地开发与部署)。
|
||||
|
||||
## 💬 交流群
|
||||
|
||||
@@ -376,6 +94,12 @@ ENV_FILE=/root/daoyou/.env.production \
|
||||
|
||||
感谢每一位帮助《万界道友》持续维护与成长的道友。新的赞助统一通过 [爱发电](https://afdian.com/u/baef2b20501311f09da252540025c377) 进行;具体规则请见 [赞助说明](SPONSORING.md),公开赞助人名单与历史鸣谢请见 [SPONSORS.md](SPONSORS.md)。
|
||||
|
||||
<p align="center">
|
||||
<a href="https://afdian.com/u/baef2b20501311f09da252540025c377">
|
||||
<img src="sponsorkit/sponsors.svg" alt="爱发电赞助人名单" width="720" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
赞助名单不会展示支付信息或具体金额,赞助不会影响游戏数值、账号权益或项目决策权。
|
||||
|
||||
## 🤝 致谢
|
||||
|
||||
@@ -1,888 +0,0 @@
|
||||
# battle-v5 与宗门机制解耦优化重构方案
|
||||
|
||||
> - 状态:历史实施方案,已实施并通过验证(2026-07-22)
|
||||
> - 分析基线:2026-07-22 当前工作区
|
||||
> - 当前架构提示:本文记录的是旧 `BattleEngineV5` 1v1 主流程下的宗门解耦决策。该引擎已被删除;当前生产基线为 `Team/Roster + BattleRuntime + Checkpoint + BattleRoundResolver`。本文只用于追溯宗门通用原语的取舍,不再是战斗主流程的实现依据。
|
||||
> - 适用范围:`src/shared/engine/battle-v5`、`src/shared/engine/sect`、宗门战斗日志展示与相关测试
|
||||
> - 重点宗门:幽都、天衍圣地;红尘剑宗与无相宗作为回归样本
|
||||
> - 实施原则:不重做战斗架构,不修改持久化模型,不改变稳定宗门/流派/节点/能力 ID
|
||||
> - 实施报告:[battle-v5-sect-decoupling-refactor-report.md](./battle-v5-sect-decoupling-refactor-report.md)
|
||||
|
||||
## 1. 背景与结论
|
||||
|
||||
当前四个生产宗门按 `src/shared/engine/sect/content/productionRuntime.ts` 的注册顺序为:
|
||||
|
||||
1. 红尘剑宗:`lingxiao`
|
||||
2. 无相宗:`wuxiang`
|
||||
3. 天衍圣地:`tianyan`
|
||||
4. 幽都:`youdu`
|
||||
|
||||
现状没有在 battle-v5 生产核心中按上述宗门 ID、能力 ID 或“蚀魂、魂火、法印”等内容语义执行 `if/switch`,也没有为幽都新增灵魂生命值、专属伤害系统或多目标战斗分支。总体架构仍然可控。
|
||||
|
||||
风险主要来自另一种更隐蔽的耦合:用中性字段名包装只有一个宗门需要的执行阶段、运行时状态或展示参数,并让它们贯穿 `AbilityConfig`、Factory、DamageSystem、日志、心法成长和 UI。此类接口虽然没有出现宗门 ID,仍会扩大 battle-v5 的长期维护面。
|
||||
|
||||
本方案作出以下核心决策:
|
||||
|
||||
- 删除幽都驱动的冗余执行阶段 `postDamageEffects`,复用现有 `completionEffects`。
|
||||
- 删除伤害核心中的 `DamageDisplayMetadata/display/tone`,不让战斗事件携带颜色和宗门美术语义。
|
||||
- 删除仅由幽都终结技使用的 `buffLayerScalar`,在宗门编译期生成四层和五层两个固定 effect plan。
|
||||
- 删除仅由天衍使用的 `element_history` 与 `BattleRuntimeState.elementHistories`,用宗门内容层隐藏 marker 和通用 runtime counter 表达不同元素历史。
|
||||
- 删除天衍驱动的 `buff_periodic_settlement` 与 `manualSettlementEffects`,用分层 DOT 和固定反应伤害近似“立即结算剩余持续伤害”。
|
||||
- 保留具有清晰通用语义的分层状态、逐层驱散、受治疗削弱、伤害行为开关、命中策略、effect plan、能力模式和监听预算。
|
||||
- 扩大架构守卫覆盖四个生产宗门,并增加依赖方向、移除项和中性测试门禁。
|
||||
|
||||
## 2. 目标与非目标
|
||||
|
||||
### 2.1 目标
|
||||
|
||||
1. battle-v5 只理解伤害、治疗、Buff、层数、资源、命中、行动、事件、条件和监听预算等通用战斗事实。
|
||||
2. 宗门世界观语义、流派规则、节点开关和复杂效果组合只存在于 `sect/content/<sect-id>`。
|
||||
3. 玩家关键决策语义保持不变;无法无代价精确复刻时,优先采用可验证的近似效果。
|
||||
4. 每个重构阶段可独立提交、验证和回滚,不要求一次完成全部工作。
|
||||
5. 战斗与展示继续读取同一个 `SectCompiledBuild`,不在 UI、Service 或 Adapter 重新解释倍率。
|
||||
6. 后续新增宗门能够依据本方案的准入门禁判断是否应扩展 battle-v5。
|
||||
|
||||
### 2.2 非目标
|
||||
|
||||
- 当时不修改 `BattleEngineV5` 严格 1v1 的单位、目标、回合、胜负和 AI 模型;该非目标仅适用于本文的历史实施批次,不能用于约束当前 Team/Roster 引擎。
|
||||
- 不新增 `DamageType.SOUL` 或其他宗门专属枚举。
|
||||
- 不改变 `Cultivator.attributes` 的五维持久化边界。
|
||||
- 不持久化 `battleProjection`、宗门战斗资源、Buff、运行时 counter 或 marker。
|
||||
- 不修改宗门、心法、能力、流派、节点和战术稳定 ID。
|
||||
- 不重做 `EffectRegistry`、`EventBus`、`AbilityFactory` 或整个日志架构。
|
||||
- 不在本轮统一重写四个宗门的数值平衡。
|
||||
- 不借机整理无关代码或处理工作区中的其他用户改动。
|
||||
|
||||
## 3. 目标分层与依赖方向
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["宗门 definition / nodes / Build Facade"] --> B["宗门内容编译器"]
|
||||
B --> C["AbilityConfig / BuffConfig / EffectConfig"]
|
||||
C --> D["battle-v5 通用执行核心"]
|
||||
D --> E["结构化战斗事件与日志"]
|
||||
E --> F["LogPresenter / React 展示"]
|
||||
|
||||
G["GameplayTags"] --> B
|
||||
G --> D
|
||||
|
||||
F -. "禁止反向影响计算" .-> D
|
||||
D -. "禁止依赖具体宗门内容" .-> B
|
||||
```
|
||||
|
||||
### 3.1 battle-v5 核心允许承担的职责
|
||||
|
||||
- 通用伤害请求与结算。
|
||||
- 通用治疗、护盾、资源和属性修改。
|
||||
- Buff 生命周期、层数、驱散和控制抗性。
|
||||
- 主动能力命中、费用、冷却和效果顺序。
|
||||
- 通用监听范围、条件、优先级和触发预算。
|
||||
- 不含内容句子的结构化日志事实。
|
||||
|
||||
### 3.2 宗门内容层允许承担的职责
|
||||
|
||||
- 把“魂伤”编译为真实伤害、不可暴击、不可吸血和幽都机制标签。
|
||||
- 把“蚀魂”编译为一个分层 Debuff 及其监听器。
|
||||
- 把“法印反应”编译为 effect plan、隐藏 marker、counter 和通用效果链。
|
||||
- 根据心法等级、流派、节点和战术生成固定的运行时配置。
|
||||
- 在编译期展开少量枚举分支,例如四层/五层终结技。
|
||||
|
||||
### 3.3 展示层允许承担的职责
|
||||
|
||||
- 能力名称、机制名称、说明文本、颜色、图标和日志排版。
|
||||
- 从结构化 `cause`、能力、Buff 和资源事件生成玩家文本。
|
||||
- 对 debug marker、内部 counter 和稳定 ID 做隐藏处理。
|
||||
|
||||
展示层不得向战斗核心增加 `tone`、CSS 色名、Tailwind 类名或宗门主题字段。
|
||||
|
||||
## 4. 通用原语保留与退出清单
|
||||
|
||||
### 4.1 明确保留
|
||||
|
||||
| 原语 | 当前消费者 | 保留理由 | 必须满足的边界 |
|
||||
| --- | --- | --- | --- |
|
||||
| `BuffLayerChangedEvent` | 幽都及通用分层状态 | 完整表达 0→N、叠层、减层、驱散和阈值 | 事件字段只包含层数、原因、来源和通用对象 |
|
||||
| `ApplyBuffParams.layers` | 幽都 | 一次添加多层是 Buff 的自然能力 | 缺省为 1,受 `maxLayers` 限制 |
|
||||
| `AttributeModifierConfig.valueByLayer` | 幽都 | 非线性层级属性是通用状态能力 | 不写宗门名称;clone、增减层正确重挂 |
|
||||
| `BuffConfig.dispelMode` | 幽都 | 逐层驱散是通用 Buff 策略 | 缺省 `whole`,旧内容行为不变 |
|
||||
| `HEAL_RECEIVED_REDUCTION` | 幽都等状态 | 受治疗削弱属于基础属性 | 统一在 `Unit.heal()` 生效,不影响 MP、护盾、免死 |
|
||||
| `DamageParams.canCrit/canLifesteal` | 幽都 | 伤害包级行为开关可复用 | 缺省 `true`,不能改变其他真实伤害 |
|
||||
| `AbilityConfig.hitPolicy` | 红尘剑宗、幽都 | 能力自身必然命中具有通用语义 | 不按目标宗门或状态动态改写全局命中 |
|
||||
| `effectLayers/effectPlans` | 无相、天衍、幽都 | 已有多个独立消费者 | 在施法准备阶段快照;计划只引用固定效果层 |
|
||||
| `AbilityMode` | 无相 | “接下来 N 次能力使用某模式”足够通用 | battle-v5 不理解佛相、魔相、无相 |
|
||||
| listener `budget/group` | 四宗门中的多个监听器 | 通用防重和共享预算 | group 只作为运行时 key,不包含执行特判 |
|
||||
| `refund_paid_cost` | 天衍、幽都 | 已有两个独立消费者 | 只读取本次施法快照中的实际支付值 |
|
||||
| `mechanic_log` | 天衍 | 当前仅发布结构化日志,不参与计算 | 暂保留但冻结能力,不允许加入内容句子模板或计算副作用 |
|
||||
|
||||
### 4.2 必须删除
|
||||
|
||||
| 原语 | 当前问题 | 替代方案 |
|
||||
| --- | --- | --- |
|
||||
| `AbilityConfig.postDamageEffects` | 与 `completionEffects` 语义重叠,迫使 Factory、能力分析、心法成长和展示理解新阶段 | 合并进有序的 `completionEffects` |
|
||||
| `DamageDisplayMetadata`、伤害 `display`、`PresentedLogPart.tone` | 将“魂伤”“术伤”和 `violet` 贯穿伤害核心到 React | 使用标准伤害日志;必要时复用已有 `LogCauseRef.displayName`,不传颜色 |
|
||||
| `DamageParams.buffLayerScalar` | 只有幽都终结技使用,使 DamageEffect 读取任意 Buff 层数 | 宗门编译期生成四层/五层两个固定伤害计划 |
|
||||
| `ElementHistoryParams`、`element_history`、`elementHistories` | 将天衍的元素历史固化进通用运行时 | 天衍隐藏 marker + 通用 runtime counter |
|
||||
| `BuffConfig.manualSettlementEffects`、`buff_periodic_settlement` | 外部效果读取 Buff 内部周期配置,增加反向耦合并扩散到心法成长 | 分层 DOT + 固定反应伤害 + 清除 Buff |
|
||||
|
||||
### 4.3 暂不扩展
|
||||
|
||||
- 不新增任意的 `afterHitEffects`、`afterDamageEffects`、`afterControlEffects` 等更多能力生命周期数组。
|
||||
- 不新增通用“读取另一个 EffectConfig 并重新执行”的反射型效果。
|
||||
- 不新增通用元素集合、宗门状态机或专属日志样式注册到 battle-v5。
|
||||
- 不为单个节点增加新的 DamageSystem 分支、事件字段或伤害类型。
|
||||
|
||||
## 5. 行为保真等级
|
||||
|
||||
实施时按以下等级标记每一项验收:
|
||||
|
||||
| 等级 | 定义 | 适用项 |
|
||||
| --- | --- | --- |
|
||||
| E:精确等价 | 输入、结算顺序、事件数量和数值完全一致 | 幽都效果顺序、终结技四/五层伤害、魂火消费、失魂与归窍 |
|
||||
| S:语义等价 | 玩家决策、反制窗口和强弱关系一致,允许内部表示改变 | 天衍不同元素历史改为 marker/counter |
|
||||
| A:可接受近似 | 保留用途和战术选择,允许小幅数值或展示变化 | 天衍提前结算 DOT、魂伤日志颜色 |
|
||||
|
||||
以下内容不得降级:
|
||||
|
||||
- 幽都魂火必须在伤害请求后消费,不能消费本次新获得且未参与增伤的魂火。
|
||||
- 幽都终结技必须按伤害前的四层或五层计算,随后才清层或保留两层。
|
||||
- 幽都失魂成功、抵抗、免疫和首次解控必须收束到既定层数并进入归窍。
|
||||
- 天衍元素反应仍必须由有效的元素组合触发,普通装备、功法和其他宗门不能误触发。
|
||||
- 所有伤害、控制、治疗、状态和资源仍须通过现有 Factory 与事件管道。
|
||||
|
||||
## 6. 阶段 0:冻结基线与建立对照
|
||||
|
||||
### 6.1 目的
|
||||
|
||||
在删除接口前固化现有行为,避免重构过程中把已有机制 Bug 误当成新设计。
|
||||
|
||||
### 6.2 操作
|
||||
|
||||
1. 记录当前工作区状态,不覆盖或回滚任何用户未提交改动。
|
||||
2. 运行当前 battle-v5 与宗门测试,保存命令和结果。
|
||||
3. 为后续将要改写的机制补足“行为契约测试”,测试应面向结果而不是字段存在性。
|
||||
4. 对日志允许变化的部分单独记录快照,避免把颜色删除误报为战斗语义变化。
|
||||
|
||||
### 6.3 基线测试清单
|
||||
|
||||
幽都:
|
||||
|
||||
- 《一叹》伤害后增加 1 层蚀魂。
|
||||
- 《离魂引》按施法前层数选择伤害,伤害后消费魂火并增加 2 层蚀魂。
|
||||
- 《忘川潮》按“直接伤害→蚀魂→忘川”执行。
|
||||
- 《夺魄》《镇魂》发布术伤和魂伤两个独立请求,随后加层并施加状态。
|
||||
- 混合伤害首段致死时,不发布第二次伤害请求,不遗留后续状态。
|
||||
- “先定其形”只被铺层神通消费,每场一次,终结技和照影不能误消费。
|
||||
- 《魂兮不归》四层、五层及相关节点组合的伤害值、请求数和完成效果。
|
||||
|
||||
天衍:
|
||||
|
||||
- 相同元素重复不能计为三种不同元素。
|
||||
- 三种不同元素触发后历史重置。
|
||||
- 燎原立即追加一跳但不减少原灼烧剩余跳数。
|
||||
- 蒸发按剩余灼烧跳数追加伤害并移除灼烧。
|
||||
- 反应伤害不会再次触发法印、DOT 或同类宗门反应链。
|
||||
|
||||
通用引擎:
|
||||
|
||||
- 已死亡目标不接受后续 `damage` effect,且该测试不得出现任何宗门 ID。
|
||||
- `completionEffects` 在所有计划主效果之后、计划层完成效果之前执行。
|
||||
- listener budget 的共享 group 在 action、source_action、round、battle 和 buff_lifetime 下正确重置。
|
||||
|
||||
### 6.4 门禁
|
||||
|
||||
```bash
|
||||
bunx vitest run src/shared/engine/battle-v5/tests
|
||||
bunx vitest run src/shared/engine/sect/content/youdu
|
||||
bunx vitest run src/shared/engine/sect/content/tianyan
|
||||
bunx vitest run src/shared/engine/sect
|
||||
```
|
||||
|
||||
只有基线通过后才进入阶段 1。若基线本身失败,应先记录为独立 Bug,不在重构提交中混修。
|
||||
|
||||
## 7. 阶段 1:删除幽都驱动的 `postDamageEffects`
|
||||
|
||||
### 7.1 目标
|
||||
|
||||
移除新的全局生命周期阶段,同时保持幽都所有能力的伤害、魂火、加层和控制顺序精确等价。
|
||||
|
||||
### 7.2 目标执行顺序
|
||||
|
||||
`LayeredDataDrivenActiveSkill` 继续只执行:
|
||||
|
||||
```text
|
||||
plan.effects
|
||||
→ plan.completionEffects
|
||||
→ consumeModeKey
|
||||
```
|
||||
|
||||
`resolveAbilityEffectPlan()` 当前将完成效果组合为:
|
||||
|
||||
```text
|
||||
base completionEffects
|
||||
→ selected layer completionEffects
|
||||
```
|
||||
|
||||
幽都可以利用这一既有顺序,让基础加层先发生,再结算《镇魂》的高低层控制完成效果;计划选择仍在施法准备时完成,不会被本次加层改变。
|
||||
|
||||
### 7.3 能力迁移表
|
||||
|
||||
| 能力 | 当前主效果 | 迁移后的 `completionEffects` 顺序 | 保真等级 |
|
||||
| --- | --- | --- | --- |
|
||||
| 一叹 | 条件术伤 | 加 1 层蚀魂→先定其形额外层→清 pending marker | E |
|
||||
| 离魂引 | 四/五层计划魂伤 | 消费魂火→加 2 层蚀魂→先定其形→清 marker | E |
|
||||
| 忘川潮 | 直接魂伤 | 加 1 层蚀魂→先定其形→目标存活时施加忘川 | E |
|
||||
| 夺魄 | 术伤→魂伤→消费魂火 | 加 2 层蚀魂→先定其形→目标存活时施加降攻 | E |
|
||||
| 镇魂 | 术伤→魂伤→消费魂火 | 加 2 层蚀魂→先定其形→计划层控制/减速 | E |
|
||||
|
||||
### 7.4 文件范围
|
||||
|
||||
删除字段及透传:
|
||||
|
||||
- `src/shared/engine/battle-v5/core/configs.ts`
|
||||
- `src/shared/engine/battle-v5/abilities/LayeredDataDrivenActiveSkill.ts`
|
||||
- `src/shared/engine/battle-v5/factories/AbilityFactory.ts`
|
||||
- `src/shared/engine/battle-v5/factories/AbilityCapabilityAnalyzer.ts`
|
||||
- `src/shared/engine/sect/core/authoring/SectAbilityFactory.ts`
|
||||
- `src/shared/engine/sect/core/authoring/StandardSectMethodGrowthPolicy.ts`
|
||||
- `src/shared/engine/sect/core/presentation/abilityFacts.ts`
|
||||
|
||||
迁移内容与测试:
|
||||
|
||||
- `src/shared/engine/sect/content/youdu/base/YouduBaseCompiler.ts`
|
||||
- `src/shared/engine/battle-v5/tests/core/AbilityEffectPlans.test.ts`
|
||||
- `src/shared/engine/sect/content/youdu/tests/*.test.ts`
|
||||
- `src/shared/engine/sect/core/tests/methodGrowth.test.ts`
|
||||
|
||||
### 7.5 实施约束
|
||||
|
||||
- 不新增同义字段替代 `postDamageEffects`。
|
||||
- 不把顺序逻辑写进 DamageSystem 或宗门 ID 分支。
|
||||
- `firstShadowPending` 仍只由命中事件创建,并在技能完成阶段清理。
|
||||
- 目标死亡时,加层、忘川、降攻和控制必须被存活条件阻止;marker 清理仍需执行。
|
||||
- 能力详情继续展示“命中后”,但这是 `abilityFacts` 对 completion 中具体效果的描述,不代表新增运行时阶段。
|
||||
|
||||
### 7.6 验收
|
||||
|
||||
```bash
|
||||
rg -n "postDamageEffects" src
|
||||
bunx vitest run src/shared/engine/battle-v5/tests/core/AbilityEffectPlans.test.ts
|
||||
bunx vitest run src/shared/engine/sect/core/tests/methodGrowth.test.ts
|
||||
bunx vitest run src/shared/engine/sect/content/youdu
|
||||
```
|
||||
|
||||
完成条件:
|
||||
|
||||
- `rg` 无结果。
|
||||
- 幽都伤害请求数、魂火消费、蚀魂层数、状态顺序与基线一致。
|
||||
- battle-v5 不新增其他生命周期数组。
|
||||
|
||||
## 8. 阶段 2:移除伤害核心中的展示元数据
|
||||
|
||||
### 8.1 目标
|
||||
|
||||
让 DamageEffect、DamageSystem 和伤害事件只传递影响计算或结构化归因的字段,不传递“术伤”“魂伤”和 `violet`。
|
||||
|
||||
### 8.2 决策
|
||||
|
||||
本轮默认采用标准伤害日志:
|
||||
|
||||
- 术伤继续由 `DamageType.MAGICAL` 计算。
|
||||
- 魂伤继续由 `DamageType.TRUE + soulDamageTag + canCrit=false + canLifesteal=false` 计算。
|
||||
- 同一技能的两个伤害包仍由现有 action span 聚合。
|
||||
- 删除紫色染色,视为 A 级允许变化。
|
||||
|
||||
如果产品验收明确要求展示“魂伤”文字,只允许复用已有 `LogCauseRef`:
|
||||
|
||||
```ts
|
||||
cause: {
|
||||
kind: 'mechanic',
|
||||
id: 'sect.youdu.soul-damage',
|
||||
displayName: '魂伤',
|
||||
}
|
||||
```
|
||||
|
||||
`cause` 是已有结构化归因,不得再携带颜色、CSS 类、句子模板或宗门主题对象。若现有 Presenter 不能在不增加核心字段的前提下展示该名称,本轮接受标准日志,不继续扩展。
|
||||
|
||||
### 8.3 文件范围
|
||||
|
||||
- `src/shared/engine/battle-v5/core/configs.ts`
|
||||
- `src/shared/engine/battle-v5/core/events.ts`
|
||||
- `src/shared/engine/battle-v5/effects/DamageEffect.ts`
|
||||
- `src/shared/engine/battle-v5/systems/DamageSystem.ts`
|
||||
- `src/shared/engine/battle-v5/systems/log/LogCollector.ts`
|
||||
- `src/shared/engine/battle-v5/systems/log/LogPresenter.ts`
|
||||
- `src/shared/engine/battle-v5/systems/log/types.ts`
|
||||
- `src/react-app/components/feature/battle/v5/CombatActionLog.tsx`
|
||||
- `src/shared/engine/sect/content/youdu/base/YouduBaseCompiler.ts`
|
||||
- 对应日志与幽都测试
|
||||
|
||||
### 8.4 验收
|
||||
|
||||
必须删除:
|
||||
|
||||
- `DamageDisplayMetadata`
|
||||
- `DamageParams.display`
|
||||
- 三类伤害事件中的 `display`
|
||||
- `DamageEntryData.display`
|
||||
- `PresentedLogPart.tone`
|
||||
- `CombatActionLog` 中的 `part.tone === 'violet'`
|
||||
|
||||
验证命令:
|
||||
|
||||
```bash
|
||||
rg -n "DamageDisplayMetadata|tone.*violet|display: \{ label: '(魂伤|术伤)'" src
|
||||
bunx vitest run src/shared/engine/battle-v5/tests/systems/log
|
||||
bunx vitest run src/shared/engine/sect/content/youdu
|
||||
bun run build
|
||||
```
|
||||
|
||||
完成条件:
|
||||
|
||||
- 搜索无结果。
|
||||
- 术伤、魂伤数值和请求数量不变。
|
||||
- React 不再根据 battle-v5 核心传来的颜色枚举选择 CSS。
|
||||
- 允许日志颜色和分量前缀与基线不同,但不允许伤害总值和分组错误。
|
||||
|
||||
## 9. 阶段 3:用固定 effect plan 替代 `buffLayerScalar`
|
||||
|
||||
### 9.1 目标
|
||||
|
||||
精确保留《魂兮不归》的单次伤害请求和四/五层公式,同时让 DamageEffect 不读取任意 Buff 层数。
|
||||
|
||||
### 9.2 编译方案
|
||||
|
||||
在 `compileAbilities()` 中根据最终 `YouduBuildSettings` 计算:
|
||||
|
||||
```ts
|
||||
const coefficientAtFour =
|
||||
settings.finishBaseCoefficient + settings.finishPerLayerCoefficient * 4;
|
||||
const coefficientAtFive =
|
||||
settings.finishBaseCoefficient + settings.finishPerLayerCoefficient * 5;
|
||||
```
|
||||
|
||||
生成两个效果层:
|
||||
|
||||
```text
|
||||
finish-four:一次 coefficientAtFour 的魂伤
|
||||
finish-five:一次 coefficientAtFive 的魂伤
|
||||
```
|
||||
|
||||
生成两个计划,五层计划优先:
|
||||
|
||||
```text
|
||||
priority 20:施法前至少5层 → finish-five
|
||||
priority 10:施法前至少4层且少于5层 → finish-four
|
||||
```
|
||||
|
||||
保持既有 `castConditions >= 4`。伤害完成后继续执行原有 `finishCompletion`:
|
||||
|
||||
1. 清除蚀魂或设置为 2 层。
|
||||
2. 目标存活时施加不归。
|
||||
3. 可选施加忘川。
|
||||
4. 消费魂火。
|
||||
5. 处理一名一判、名落幽都等节点。
|
||||
|
||||
### 9.3 文件范围
|
||||
|
||||
- `src/shared/engine/sect/content/youdu/base/YouduBaseCompiler.ts`
|
||||
- `src/shared/engine/battle-v5/core/configs.ts`
|
||||
- `src/shared/engine/battle-v5/effects/DamageEffect.ts`
|
||||
- `src/shared/engine/sect/core/presentation/abilityFacts.ts`
|
||||
- `src/shared/engine/battle-v5/tests/effects/LayeredStatusAndSoulPrimitives.test.ts`
|
||||
- `src/shared/engine/sect/content/youdu/tests/*.test.ts`
|
||||
|
||||
### 9.4 测试矩阵
|
||||
|
||||
至少覆盖:
|
||||
|
||||
| 蚀魂层数 | 基础配置 | 司命判词 | 七寸断魂 | 期望 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 3 | 任意 | 任意 | 任意 | 不可施放 |
|
||||
| 4 | 默认 | 关 | 关 | `base + perLayer × 4`,一个请求 |
|
||||
| 5 | 默认 | 关 | 关 | `base + perLayer × 5`,一个请求 |
|
||||
| 4 | 开 | 开/关 | 开/关 | 使用节点后的编译系数,一个请求 |
|
||||
| 5 | 开 | 开/关 | 开/关 | 使用节点后的编译系数,一个请求 |
|
||||
| 4/5 | 不归亦不散 | 任意 | 任意 | 伤害后保留 2 层 |
|
||||
|
||||
还需验证《照影》易伤读取终结前层数,本次伤害之后才清层。
|
||||
|
||||
### 9.5 验收
|
||||
|
||||
```bash
|
||||
rg -n "buffLayerScalar" src
|
||||
bunx vitest run src/shared/engine/battle-v5/tests/core/AbilityEffectPlans.test.ts
|
||||
bunx vitest run src/shared/engine/sect/content/youdu
|
||||
```
|
||||
|
||||
完成条件:
|
||||
|
||||
- 搜索无结果。
|
||||
- 四层和五层最终伤害与基线一致。
|
||||
- 每次终结仍只有一个 `DamageRequestEvent`。
|
||||
- DamageEffect 不新增等价的状态层数动态读取字段。
|
||||
|
||||
## 10. 阶段 4:将天衍元素历史下沉到内容层
|
||||
|
||||
### 10.1 目标
|
||||
|
||||
删除 battle-v5 中的元素集合状态,同时精确保留“不同元素累计到三种后触发”的玩家语义。
|
||||
|
||||
### 10.2 内容层表示
|
||||
|
||||
天衍为每个需要记录的元素创建隐藏 marker Buff:
|
||||
|
||||
```text
|
||||
sect.tianyan.element-history.fire
|
||||
sect.tianyan.element-history.water
|
||||
sect.tianyan.element-history.wood
|
||||
sect.tianyan.element-history.earth
|
||||
sect.tianyan.element-history.metal
|
||||
```
|
||||
|
||||
marker 约束:
|
||||
|
||||
- `logVisibility: debug`
|
||||
- `statusVisibility: hidden`
|
||||
- `countsAsStatus: false`
|
||||
- `dispelPolicy: protected`
|
||||
- `duration: -1`
|
||||
- 使用 `GameplayTags.STATUS.SECT.state(...)` 构造具体标签和共同清理标签
|
||||
- 只存在于战斗运行时,不进入宗门持久状态
|
||||
|
||||
每套历史使用一个通用 runtime counter,例如:
|
||||
|
||||
```text
|
||||
sect.tianyan.strategy.element-count
|
||||
sect.tianyan.hetu.three-talents-count
|
||||
```
|
||||
|
||||
### 10.3 记录流程
|
||||
|
||||
对一次合法天衍元素反应:
|
||||
|
||||
1. 若施术者尚无当前元素 marker,则执行 `runtime_counter_modify(add 1, max 3)`。
|
||||
2. 在该 counter effect 的后续效果中施加当前元素 marker。
|
||||
3. counter 达到 3 时触发对应节点/机制效果。
|
||||
4. 清除共同标签匹配的全部元素 marker。
|
||||
5. reset 对应 runtime counter。
|
||||
|
||||
相同元素重复时,第一步的 `has_not_tag` 条件失败,不增加 counter。
|
||||
|
||||
天衍策略不再调用 `readElementHistory()`,只读取通用 `readRuntimeCounter()` 或当前 marker 标签。策略仍位于 `sect/content/tianyan`,因此可以理解天衍元素 ID。
|
||||
|
||||
### 10.4 删除范围
|
||||
|
||||
- `ElementHistoryParams`
|
||||
- `EffectConfig` 中的 `element_history`
|
||||
- `src/shared/engine/battle-v5/effects/ElementHistoryEffect.ts`
|
||||
- 对应 effect export/registry 引用
|
||||
- `BattleRuntimeState.elementHistories`
|
||||
- `rememberElement/readElementHistory/clearElementHistory`
|
||||
- BattleStateRecorder 中对元素历史的快照字段或复制逻辑
|
||||
- 天衍编译器和策略中的 `element_history`/`readElementHistory` 使用
|
||||
|
||||
### 10.5 文件范围
|
||||
|
||||
- `src/shared/engine/battle-v5/core/configs.ts`
|
||||
- `src/shared/engine/battle-v5/core/runtimeState.ts`
|
||||
- `src/shared/engine/battle-v5/effects/ElementHistoryEffect.ts`
|
||||
- `src/shared/engine/battle-v5/effects/index.ts`
|
||||
- `src/shared/engine/battle-v5/systems/state/*`
|
||||
- `src/shared/engine/sect/content/tianyan/base/TianyanBaseCompiler.ts`
|
||||
- `src/shared/engine/sect/content/tianyan/strategy.ts`
|
||||
- `src/shared/engine/sect/content/tianyan/tests/*`
|
||||
- 对应 battle-v5 原语测试
|
||||
|
||||
### 10.6 测试矩阵
|
||||
|
||||
- 火→火→火:counter 保持 1,不触发。
|
||||
- 火→水→火:counter 为 2,不触发。
|
||||
- 火→水→木:第三种触发一次,随后 counter=0、marker 全清。
|
||||
- 触发后再次火→水→木:可再次触发。
|
||||
- 两套不同 key 的历史互不影响。
|
||||
- marker 不显示在玩家状态栏、不计入普通 Buff 数量、不受驱散。
|
||||
- 战斗状态克隆/录制后不出现旧 `elementHistories` 字段;marker 与 counter 按现有通用规则恢复或复制。
|
||||
- 其他宗门的元素技能不能写入天衍 marker。
|
||||
|
||||
### 10.7 验收
|
||||
|
||||
```bash
|
||||
rg -n "ElementHistory|element_history|elementHistories|readElementHistory|rememberElement|clearElementHistory" src
|
||||
bunx vitest run src/shared/engine/battle-v5/tests/core/RuntimePrimitives.test.ts
|
||||
bunx vitest run src/shared/engine/battle-v5/tests/systems/BattleStateRecorder.test.ts
|
||||
bunx vitest run src/shared/engine/sect/content/tianyan
|
||||
```
|
||||
|
||||
完成条件:搜索无结果;不同元素语义与基线一致;battle-v5 runtime state 不再出现元素领域名词。
|
||||
|
||||
## 11. 阶段 5:删除天衍周期 Buff 的反射式手动结算
|
||||
|
||||
### 11.1 目标
|
||||
|
||||
移除外部效果读取并重新执行 Buff 内部 `manualSettlementEffects` 的能力,保留燎原和蒸发“提前兑现灼烧价值”的战术用途。
|
||||
|
||||
### 11.2 允许的语义近似
|
||||
|
||||
原设计要求蒸发读取原灼烧来源、剩余完整跳数和原周期效果。精确保留这些内部细节会继续要求通用引擎反射式读取 Buff 配置,不符合本方案边界。
|
||||
|
||||
本阶段采用 A 级近似:
|
||||
|
||||
- 灼烧剩余层数仍决定蒸发额外伤害强弱。
|
||||
- 燎原仍额外造成一跳等价伤害且不消耗原灼烧。
|
||||
- 蒸发仍立即兑现剩余灼烧并清除灼烧。
|
||||
- 允许立即伤害使用天衍编译器中的标准灼烧系数,而不是运行时反查原 Buff 的完整 EffectConfig。
|
||||
- 允许日志归因为燎原/蒸发的直接追击,而不是“手动触发原 Buff”。
|
||||
|
||||
### 11.3 灼烧表示
|
||||
|
||||
仅将天衍灼烧改为层数代表剩余跳数:
|
||||
|
||||
- 初始 `layers = 2`
|
||||
- `maxLayers = 2`
|
||||
- 每次目标行动前:造成一跳 DOT,然后减少 1 层
|
||||
- 0 层时移除
|
||||
- 重新施加时恢复到 2 层并刷新持续时间
|
||||
- Buff 仍保留正常的周期监听器,普通自然结算不走新引擎分支
|
||||
|
||||
熔岩等不参与燎原/蒸发提前结算的 DOT 不必改为同一表示。
|
||||
|
||||
### 11.4 反应编译
|
||||
|
||||
燎原:
|
||||
|
||||
```text
|
||||
本次主伤害与基础效果
|
||||
→ 施加/刷新2层灼烧
|
||||
→ 追加一次标准灼烧系数的 DELAYED/FOLLOW_UP 伤害
|
||||
→ 不减少灼烧层数
|
||||
```
|
||||
|
||||
蒸发:
|
||||
|
||||
```text
|
||||
若目标灼烧至少2层:追加2倍标准灼烧系数伤害
|
||||
若目标灼烧恰好1层:追加1倍标准灼烧系数伤害
|
||||
随后清除灼烧
|
||||
```
|
||||
|
||||
两档使用现有 effect plan 或带层数条件的两个互斥 EffectConfig,不新增“按剩余持续时间读取 Buff 配置”的引擎能力。
|
||||
|
||||
### 11.5 删除范围
|
||||
|
||||
- `BuffPeriodicSettlementParams`
|
||||
- `EffectConfig` 中的 `buff_periodic_settlement`
|
||||
- `src/shared/engine/battle-v5/effects/BuffPeriodicSettlementEffect.ts`
|
||||
- `BuffConfig.manualSettlementEffects`
|
||||
- `DataDrivenBuff` 对该字段的暴露用途(若删除后无其他消费者)
|
||||
- `StandardSectMethodGrowthPolicy` 对 `manualSettlementEffects` 的递归投影
|
||||
- 相关 affix text 分支与 EffectRegistry 注册
|
||||
|
||||
### 11.6 测试矩阵
|
||||
|
||||
- 新灼烧为 2 层,第一跳后 1 层,第二跳后移除。
|
||||
- 重复施加恢复 2 层,不超过 2 层。
|
||||
- 燎原额外造成一跳,灼烧仍保持 2 层。
|
||||
- 一层灼烧蒸发追加一档伤害并清除。
|
||||
- 两层灼烧蒸发追加两档伤害并清除。
|
||||
- 蒸发不会结算熔岩或其他 DOT。
|
||||
- 目标死亡后不追加后续伤害、不遗留新法印或 DOT。
|
||||
- 反应追加伤害不再次触发天衍法印与宗门反应。
|
||||
- 相关心法成长与节点倍率仍能投影到新的固定反应伤害。
|
||||
|
||||
### 11.7 验收
|
||||
|
||||
```bash
|
||||
rg -n "BuffPeriodicSettlement|buff_periodic_settlement|manualSettlementEffects" src
|
||||
bunx vitest run src/shared/engine/battle-v5/tests/effects/TianyanEngineExtensions.test.ts
|
||||
bunx vitest run src/shared/engine/sect/content/tianyan
|
||||
bunx vitest run src/shared/engine/sect/core/tests/methodGrowth.test.ts
|
||||
```
|
||||
|
||||
完成条件:搜索无结果;燎原和蒸发的用途、相对强弱和清除规则保留;不新增等价的反射式 Buff 配置读取接口。
|
||||
|
||||
## 12. 阶段 6:加强架构与准入守卫
|
||||
|
||||
### 12.1 扩大现有架构守卫
|
||||
|
||||
修改 `src/shared/engine/sect/testing/architecture/architectureGuard.test.ts`:
|
||||
|
||||
1. 对 battle-v5 非测试核心扫描全部生产宗门 ID 和展示名称,而不是只扫描幽都。
|
||||
2. `battle-v5/adapters` 作为明确集成边界可以依赖 `sect` 投影;其余 battle-v5 目录禁止导入 `sect/content`。
|
||||
3. `sect/core` 禁止依赖 `sect/content` 和具体宗门 ID。
|
||||
4. 通用 React 战斗组件禁止出现宗门 ID;宗门主题展示必须通过数据或已有结构化日志进入。
|
||||
5. 禁止重新出现本方案删除的字段和 effect type。
|
||||
|
||||
建议扫描集合至少包括:
|
||||
|
||||
```text
|
||||
lingxiao / 红尘剑宗 / 凌霄
|
||||
wuxiang / 无相
|
||||
tianyan / 天衍
|
||||
youdu / 幽都
|
||||
sect.lingxiao / sect.wuxiang / sect.tianyan / sect.youdu
|
||||
```
|
||||
|
||||
测试夹具和宗门内容目录本身不参与禁止扫描。
|
||||
|
||||
### 12.2 依赖方向守卫
|
||||
|
||||
可先用架构测试实现,不强制本轮改 ESLint 配置:
|
||||
|
||||
```text
|
||||
battle-v5/core|abilities|buffs|effects|factories|systems|units
|
||||
不得 import sect/content、react-app
|
||||
|
||||
sect/core
|
||||
不得 import sect/content
|
||||
|
||||
sect/content
|
||||
可以 import battle-v5、sect/core、GameplayTags
|
||||
|
||||
react-app
|
||||
可以 import battle-v5 日志与只读展示类型
|
||||
不得向 battle-v5 注入颜色或宗门计算规则
|
||||
```
|
||||
|
||||
### 12.3 新增核心原语准入模板
|
||||
|
||||
以后任何宗门要求扩展 battle-v5,都必须在设计稿中回答:
|
||||
|
||||
1. 现有 Effect/Listener/Condition 为什么不能组合?
|
||||
2. 为什么不能在宗门编译期展开成有限个固定 plan?
|
||||
3. 为什么不能用隐藏 Buff、GameplayTag 或通用 counter 表达?
|
||||
4. 可接受的近似效果是什么,为什么仍不能满足?
|
||||
5. 是否已有第二个独立生产消费者?
|
||||
6. 默认值是否严格保持旧内容行为?
|
||||
7. 是否有不引用任何宗门 ID 的原子测试?
|
||||
8. 是否穿透 DamageSystem、Unit、行动流、胜负、持久化或展示层?
|
||||
|
||||
准入判断:
|
||||
|
||||
- 已有两个独立消费者:可考虑通用原语。
|
||||
- 修复明确的全局战斗不变量:可考虑通用原语。
|
||||
- 只有一个宗门且存在近似方案:拒绝扩展,采用内容层组合或近似。
|
||||
- 涉及单位模型、目标、胜负、持久化或伤害主管道:单独立项,不得随宗门实现夹带。
|
||||
- “未来可能复用”不能单独作为准入理由。
|
||||
|
||||
### 12.4 验收
|
||||
|
||||
```bash
|
||||
bunx vitest run src/shared/engine/sect/testing/architecture/architectureGuard.test.ts
|
||||
```
|
||||
|
||||
应额外用故障注入方式验证守卫有效:临时在本地向 battle-v5 核心加入一个生产宗门 ID 或 `sect/content` import,确认测试失败后撤销该临时改动。故障注入不得提交。
|
||||
|
||||
## 13. 阶段 7:文档与作者指南同步
|
||||
|
||||
代码验收后更新:
|
||||
|
||||
- `docs/sect-authoring-guide.md`
|
||||
- `docs/youdu-battle-v5-implementation-plan.md`
|
||||
- `docs/youdu-sect-design.md`
|
||||
- `docs/tianyan-sacred-land-design.md`
|
||||
- 必要时更新 `src/shared/engine/sect/README.md`
|
||||
|
||||
文档必须反映最终实现,不再把以下接口列为有效能力:
|
||||
|
||||
- `postDamageEffects`
|
||||
- `DamageDisplayMetadata/display/tone`
|
||||
- `buffLayerScalar`
|
||||
- `element_history`
|
||||
- `manualSettlementEffects`
|
||||
- `buff_periodic_settlement`
|
||||
|
||||
宗门接入指南应新增“核心原语准入模板”和“优先编译固定 plan”的规则。
|
||||
|
||||
## 14. 分阶段验证矩阵
|
||||
|
||||
| 阶段 | 最小测试 | 宗门回归 | 通用回归 | 构建门禁 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 0 基线 | 现有聚焦测试 | 幽都、天衍 | battle-v5、sect | 无代码改动时可不 build |
|
||||
| 1 删除 post phase | AbilityEffectPlans、methodGrowth | 幽都全量 | battle-v5 core | `bun run build` |
|
||||
| 2 删除 display/tone | log tests | 幽都日志/机制 | battle-v5 log | `bun run build` |
|
||||
| 3 固定终结计划 | AbilityEffectPlans | 幽都全量 | battle-v5 effects | `bun run build` |
|
||||
| 4 元素历史下沉 | RuntimePrimitives、Recorder | 天衍全量 | battle-v5 state | `bun run build` |
|
||||
| 5 DOT 结算简化 | TianyanEngineExtensions、methodGrowth | 天衍全量 | battle-v5 effects | `bun run build` |
|
||||
| 6 架构守卫 | architectureGuard | 四宗门编译 | battle-v5、sect | `bun run lint` |
|
||||
| 最终 | 所有上述测试 | 四宗门 | creation-v2、全项目 | lint、test、build |
|
||||
|
||||
最终命令:
|
||||
|
||||
```bash
|
||||
bunx vitest run src/shared/engine/battle-v5/tests
|
||||
bunx vitest run src/shared/engine/sect
|
||||
bunx vitest run src/shared/engine/creation-v2
|
||||
bun run lint
|
||||
bun run test
|
||||
bun run build
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `bun run build` 不会运行测试,不能替代 `bun run test`。
|
||||
- Vitest 当前使用 node 环境;日志和纯展示逻辑优先使用纯函数测试,不为此次重构新增不必要的 DOM 测试。
|
||||
- 每阶段先运行最近的聚焦测试,再运行更广测试;不能只依赖最终全量测试。
|
||||
|
||||
## 15. 最终验收标准
|
||||
|
||||
### 15.1 代码边界
|
||||
|
||||
- battle-v5 核心不存在具体生产宗门 ID、名称、技能 ID 和状态 ID。
|
||||
- 除明确的 Adapter 集成边界外,battle-v5 不依赖 `sect/content`。
|
||||
- `sect/core` 不依赖具体宗门内容。
|
||||
- 所有宗门能力继续通过 `SectAbilityFactory` 和 `AbilityFactory`。
|
||||
- 运行时标签继续来自 `GameplayTags`,不手写运行时标签字符串。
|
||||
- 没有新持久化字段、数据库迁移或 `battleProjection` 落库。
|
||||
|
||||
### 15.2 删除项
|
||||
|
||||
以下搜索必须无结果:
|
||||
|
||||
```bash
|
||||
rg -n --glob '!src/shared/engine/sect/testing/architecture/architectureGuard.test.ts' "postDamageEffects|DamageDisplayMetadata|buffLayerScalar" src
|
||||
rg -n --glob '!src/shared/engine/sect/testing/architecture/architectureGuard.test.ts' "ElementHistory|element_history|elementHistories" src
|
||||
rg -n --glob '!src/shared/engine/sect/testing/architecture/architectureGuard.test.ts' "BuffPeriodicSettlement|buff_periodic_settlement|manualSettlementEffects" src
|
||||
rg -n "tone.*violet|display: \{ label: '(魂伤|术伤)'" src
|
||||
```
|
||||
|
||||
架构守卫测试本身保留上述历史符号作为禁止项正则和自检样本,因此搜索生产源时显式排除该测试文件;除此以外实际结果必须为 0。
|
||||
|
||||
### 15.3 幽都行为
|
||||
|
||||
- 五层蚀魂、非线性削弱、逐层驱散和受治疗削弱保留。
|
||||
- 失魂与归窍四条收束路径保留。
|
||||
- 魂火每来源行动一次、三点增伤和伤害后消费顺序保留。
|
||||
- 混合技能仍是两个伤害包,共享一次命中。
|
||||
- 《魂兮不归》四层/五层伤害精确等价且只有一个伤害请求。
|
||||
- 先定其形、名落幽都、一名一判等节点不因执行阶段调整误触发。
|
||||
- 允许魂伤紫色和分量前缀被删除。
|
||||
|
||||
### 15.4 天衍行为
|
||||
|
||||
- 不同元素累计和触发重置语义保留。
|
||||
- 法印、化生、冲克和三数资源不泄漏给其他宗门。
|
||||
- 燎原仍提供额外一跳价值且不消耗原灼烧。
|
||||
- 蒸发仍按剩余灼烧强弱兑现并清除灼烧。
|
||||
- 允许提前结算的内部伤害来源和日志表述变化,但反应用途与相对强弱必须保留。
|
||||
- 天衍策略不再依赖 battle-v5 的元素专用运行时 API。
|
||||
|
||||
### 15.5 其他宗门回归
|
||||
|
||||
- 红尘剑宗的蓄势、调息、资源、追击和节点组合测试通过。
|
||||
- 无相宗的佛相、魔相、无相 effect plan、AbilityMode 和节点矩阵测试通过。
|
||||
- 四宗门编译、详情投影和战斗投影继续读取同一 `SectCompiledBuild`。
|
||||
|
||||
## 16. 提交拆分与回滚策略
|
||||
|
||||
建议拆成以下独立提交,不把多个阶段压成一个大提交:
|
||||
|
||||
1. `test(battle): freeze sect combat refactor contracts`
|
||||
2. `refactor(battle): remove redundant post-damage phase`
|
||||
3. `refactor(battle): keep damage logs presentation-neutral`
|
||||
4. `refactor(sect): compile youdu finisher by layer plans`
|
||||
5. `refactor(sect): move tianyan element history into content state`
|
||||
6. `refactor(sect): simplify tianyan periodic reactions`
|
||||
7. `test(sect): strengthen battle-content architecture guards`
|
||||
8. `docs(sect): align authoring guides with engine boundaries`
|
||||
|
||||
每个提交要求:
|
||||
|
||||
- 只包含本阶段列出的文件和必要测试。
|
||||
- 聚焦测试通过。
|
||||
- `git diff --check` 通过。
|
||||
- 不顺带清理无关导入、格式或旧代码。
|
||||
|
||||
回滚策略:
|
||||
|
||||
- 各阶段不修改稳定 ID 和持久化数据,因此可以按提交逆序回滚,不需要数据库回滚。
|
||||
- 如果阶段 4 或阶段 5 的天衍近似效果未通过产品验收,只回滚对应天衍阶段,不回滚已经完成的幽都和架构守卫阶段。
|
||||
- 不为开发环境旧战斗快照增加兼容代码;battle runtime 和 `battleProjection` 不持久化。
|
||||
|
||||
## 17. 风险与处理
|
||||
|
||||
| 风险 | 表现 | 处理 |
|
||||
| --- | --- | --- |
|
||||
| completion 顺序变化 | 幽都先加层后控制或魂火消费顺序错误 | 用事件序列测试记录 DamageRequest、ResourceChange、BuffLayerChanged、BuffApplied 顺序 |
|
||||
| effect plan 读取实时层数 | 终结伤害被本次完成效果影响 | 保持 prepareCast 快照,测试施法前四/五层 |
|
||||
| marker 被玩家看到 | 天衍元素历史污染状态栏 | hidden/debug/countsAsStatus=false,并加状态展示测试 |
|
||||
| counter 与 marker 不一致 | 重复元素误计数或触发后残留 | 让 counter 修改、marker 施加、触发和清理处于同一有序效果链 |
|
||||
| 灼烧层数与持续时间错位 | 多跳、少跳或重施不刷新 | 对 2→1→0、重施、自然过期逐行动测试 |
|
||||
| 日志快照大面积变化 | 把展示差异误认为机制回归 | 单独批准颜色/前缀变化,伤害值和 action span 仍严格断言 |
|
||||
| 删除类型影响 creation-v2 | 共享 AbilityConfig 类型编译失败 | 最终运行 creation-v2 测试和双端 build |
|
||||
| 架构守卫误伤 Adapter | 合法宗门投影无法导入 | 明确排除 `battle-v5/adapters`,其余核心保持禁止 |
|
||||
|
||||
## 18. 完成定义
|
||||
|
||||
本方案仅在以下条件全部满足时视为完成:
|
||||
|
||||
1. 阶段 1 至阶段 7 全部完成,或未完成阶段有明确延期记录和原因。
|
||||
2. 第 15 节所有代码、删除项和行为验收通过。
|
||||
3. 最终测试、lint 和 build 全部通过。
|
||||
4. `docs/sect-authoring-guide.md`、幽都和天衍设计稿与最终代码一致。
|
||||
5. 没有为了恢复精确文案而重新引入同义的核心字段或 Effect。
|
||||
6. 新增宗门原语准入规则已经由架构测试或作者指南固化。
|
||||
7. 实施报告列出实际命令、结果、允许的行为差异和所有跳过项。
|
||||
|
||||
完成后的目标状态不是“battle-v5 没有任何复杂能力”,而是:每个核心能力都具有独立、稳定、可复用的战斗语义;宗门特色由内容编译组合产生;展示差异不会反向塑造伤害和运行时模型。
|
||||
|
||||
## 19. 实施与验证记录
|
||||
|
||||
完整结果见 [battle-v5-sect-decoupling-refactor-report.md](./battle-v5-sect-decoupling-refactor-report.md)。后续修改应继续使用本节模板记录验证;如果实现偏离本文,必须记录偏离原因、替代设计和重新评估后的耦合风险。
|
||||
|
||||
### 19.1 阶段进度
|
||||
|
||||
- [x] 阶段 0:冻结基线并补齐行为契约测试
|
||||
- [x] 阶段 1:删除 `postDamageEffects`
|
||||
- [x] 阶段 2:删除伤害展示元数据与 `violet` 透传
|
||||
- [x] 阶段 3:用固定 effect plan 替代 `buffLayerScalar`
|
||||
- [x] 阶段 4:将天衍元素历史下沉到内容层
|
||||
- [x] 阶段 5:删除周期 Buff 反射式手动结算
|
||||
- [x] 阶段 6:加强架构与准入守卫
|
||||
- [x] 阶段 7:同步作者指南与宗门设计稿
|
||||
- [x] 最终全量验证
|
||||
|
||||
### 19.2 每阶段实施记录
|
||||
|
||||
```md
|
||||
## 阶段 N:<名称>
|
||||
|
||||
- 实施提交:<commit>
|
||||
- 实际修改文件:
|
||||
- `<path>`
|
||||
- 删除的核心接口:
|
||||
- `<symbol>`
|
||||
- 新增的核心接口:无 / `<symbol + 准入理由>`
|
||||
- 行为保真等级:E / S / A
|
||||
- 允许差异:无 / `<差异>`
|
||||
- 非预期差异:无 / `<差异与处理>`
|
||||
|
||||
### 验证
|
||||
|
||||
| 命令 | 结果 | 测试数量/错误摘要 |
|
||||
| ----------- | -------------- | ----------------- |
|
||||
| `<command>` | 通过/失败/跳过 | `<detail>` |
|
||||
|
||||
### 搜索门禁
|
||||
|
||||
| 搜索项 | 期望 | 实际 |
|
||||
| ------------------ | ---- | --------- |
|
||||
| `<removed symbol>` | 0 | `<count>` |
|
||||
|
||||
### 偏离方案
|
||||
|
||||
- 无;或说明偏离原因、替代实现、影响范围和批准结论。
|
||||
```
|
||||
|
||||
### 19.3 最终差异审计
|
||||
|
||||
最终验收报告必须回答:
|
||||
|
||||
1. 本方案列出的五类删除项是否全部从 `src` 消失?
|
||||
2. 是否新增了同义但改名的生命周期阶段、动态 Buff 读取或展示字段?
|
||||
3. battle-v5 核心是否出现任何具体宗门 ID、状态 ID、节点 ID 或中文宗门术语?
|
||||
4. 是否有只被一个宗门使用的新 `EffectConfig` 类型、runtime state 字段或事件字段?
|
||||
5. 幽都和天衍的允许差异是否仍限定在本文批准的范围?
|
||||
6. 红尘剑宗、无相宗、creation-v2 和双端 build 是否完成回归?
|
||||
7. 是否有命令被跳过;如果跳过,原因和剩余风险是什么?
|
||||
|
||||
任一问题无法给出明确答案时,不应将重构标记为完成。
|
||||
@@ -1,143 +0,0 @@
|
||||
# battle-v5 与宗门机制解耦实施报告
|
||||
|
||||
> - 对应方案:[battle-v5-sect-decoupling-refactor-plan.md](./battle-v5-sect-decoupling-refactor-plan.md)
|
||||
> - 实施与验收日期:2026-07-22
|
||||
> - 结论:阶段 0~7 全部完成,最终测试、lint、build、格式和搜索门禁全部通过
|
||||
> - 数据边界:无数据库迁移、无持久化模型变化、无稳定宗门/道途/节点/能力 ID 变化
|
||||
> - 当前架构提示:这是旧 `BattleEngineV5` 1v1 基线上的历史实施报告。旧引擎现已删除,当前生产主流程为 `Team/Roster + BattleRuntime + Checkpoint + BattleRoundResolver`;报告中的测试数量和 1v1 架构陈述不代表当前状态。
|
||||
|
||||
## 1. 最终结论
|
||||
|
||||
本轮删除了五组由单宗门需求向 battle-v5 核心扩散的抽象,并用现有通用原语在宗门内容编译层重组:
|
||||
|
||||
1. `postDamageEffects` 被既有有序 `completionEffects` 替代。
|
||||
2. 伤害 `display/tone` 透传被删除,计算层和 React 展示层不再理解“魂伤紫色”等宗门美术语义。
|
||||
3. `buffLayerScalar` 被幽都四层/五层两个互斥固定 effect plan 替代。
|
||||
4. `element_history` 专用 Effect 和 runtime state 被天衍隐藏 marker Buff 与通用 runtime counter 替代。
|
||||
5. `buff_periodic_settlement/manualSettlementEffects` 被分层灼烧与固定反应追伤替代。
|
||||
|
||||
该批次当时没有改写 `BattleEngineV5` 的 1v1、行动、事件、伤害、Buff 或胜负架构。后续 Team/Roster 重构已经替换并删除这条旧主流程;本报告仍可用于追溯“battle-v5 非 Adapter 生产核心不依赖具体宗门内容”的解耦决策。
|
||||
|
||||
## 2. 分阶段实施结果
|
||||
|
||||
### 2.1 阶段 0:冻结基线
|
||||
|
||||
- battle-v5 基线:32 个测试文件、280 项测试通过。
|
||||
- 幽都基线:5 个测试文件、90 项测试通过。
|
||||
- 天衍基线:9 个测试文件、131 项测试通过。
|
||||
- 四宗门基线:35 个测试文件、478 项测试通过。
|
||||
|
||||
基线用于确认删除抽象前后的行为边界;本阶段没有数据或架构改动。
|
||||
|
||||
### 2.2 阶段 1:合并冗余执行阶段
|
||||
|
||||
- 从 `AbilityConfig`、分层主动技能、Factory、能力分析、宗门编写/成长/展示和幽都编译器删除 `postDamageEffects`。
|
||||
- 幽都效果顺序统一为:计划 effects → 基础 completion → 层 completion → 资源消费。
|
||||
- 未增加同义生命周期字段。
|
||||
- 聚焦验证:7 个测试文件、128 项测试通过;删除项搜索为 0。
|
||||
|
||||
### 2.3 阶段 2:伤害展示中性化
|
||||
|
||||
- 删除 `DamageDisplayMetadata`、`DamageParams.display`、伤害事件与日志数据的 `display`、`PresentedLogPart.tone`。
|
||||
- 删除 React 对 `violet` 的特殊样式分支。
|
||||
- 幽都混合伤害仍发布法术/真实两个标准伤害请求,但玩家日志使用中性分段展示。
|
||||
- 聚焦验证:10 个测试文件、147 项测试通过;双端 build 通过。
|
||||
|
||||
### 2.4 阶段 3:固定计划替代动态 Buff 读层
|
||||
|
||||
- 从伤害配置、DamageEffect、能力事实和测试删除 `buffLayerScalar`。
|
||||
- 幽都编译器生成 `finish-four` 与 `finish-five` 两个固定层和互斥计划;五层优先级 20,四层优先级 10 且要求 `< 5`。
|
||||
- 每个分支只发布一个真实伤害请求,之后再执行清层/保留两层与“不归”状态。
|
||||
- 聚焦验证:7 个测试文件、110 项测试通过;删除项搜索为 0。
|
||||
|
||||
### 2.5 阶段 4:天衍元素历史下沉
|
||||
|
||||
- 删除 `ElementHistoryParams`、`element_history`、`ElementHistoryEffect`、`BattleRuntimeState.elementHistories` 及其读写 API。
|
||||
- 每个不同反应元素由天衍内容层创建隐藏、受保护、`countsAsStatus=false` 的 marker Buff。
|
||||
- 通用 runtime counter 负责计数;重复元素不增加,第三种不同元素触发后清理全部 marker 并重置 counter。
|
||||
- `creation-v2` 的稳定词缀“万象归一”改为通用的每三次激活触发,不重新引入动态元素作者模型。
|
||||
- 聚焦验证:天衍/核心 12 个测试文件、170 项测试;creation-v2 18 个测试文件、133 项测试;build 通过。
|
||||
|
||||
### 2.6 阶段 5:移除周期 Buff 反射式结算
|
||||
|
||||
- 删除 `BuffPeriodicSettlementParams`、`buff_periodic_settlement`、`BuffPeriodicSettlementEffect`、`BuffConfig.manualSettlementEffects` 及成长/词缀文本投影支持。
|
||||
- 灼烧改为 2 层、上限 2 层;每次目标 `ActionPre` 结算一次 `0.16 × MAGIC_ATK` DOT 后减少一层,按 2→1→0 结束;重施恢复并封顶到 2 层且刷新持续时间。
|
||||
- 燎原发布固定 `0.16 × MAGIC_ATK` 的 `FOLLOW_UP`,不消耗灼烧。
|
||||
- 蒸发保留主伤害 80% 追伤;另按 1/2 层灼烧发布固定 `0.16/0.32 × MAGIC_ATK` 追伤,随后只清除灼烧。熔岩独立存在。
|
||||
- 聚焦验证:11 个测试文件、167 项测试通过;build 通过。
|
||||
|
||||
### 2.7 阶段 6:架构守卫
|
||||
|
||||
`architectureGuard.test.ts` 现覆盖:
|
||||
|
||||
- battle-v5 非 Adapter 生产核心不得包含四宗门 ID、名称或中文内容语义;
|
||||
- battle-v5 核心不得导入宗门内容层或 React;
|
||||
- `sect/core` 不得依赖生产宗门内容或稳定宗门 ID;
|
||||
- 通用战斗 React UI 不得包含生产宗门 ID/名称;
|
||||
- 五组已删除扩展不得回到生产源码;
|
||||
- 守卫正则通过代表性非法输入自检。
|
||||
|
||||
正常验证为 1 个测试文件、11 项测试通过。另执行了一次未提交的故障注入:临时向 battle-v5 核心加入 `sect.youdu`,测试按预期失败,随后删除探针文件并再次验证通过。
|
||||
|
||||
### 2.8 阶段 7:文档与准入规则
|
||||
|
||||
已同步:
|
||||
|
||||
- `docs/sect-authoring-guide.md`
|
||||
- `docs/youdu-battle-v5-implementation-plan.md`
|
||||
- `docs/youdu-sect-design.md`
|
||||
- `docs/tianyan-sacred-land-design.md`
|
||||
- `src/shared/engine/sect/README.md`
|
||||
|
||||
作者指南新增八问准入模板,并明确优先使用固定 plan、marker、counter 和内容组合。只有第二个独立生产消费者或明确全局不变量才能支持新增 battle-v5 原语;单宗门生命周期、动态 Buff 反射和展示字段不得进入核心。
|
||||
|
||||
## 3. 允许的行为差异
|
||||
|
||||
| 范围 | 最终差异 | 接受理由 |
|
||||
| --- | --- | --- |
|
||||
| 幽都日志 | 删除魂伤紫色、术伤/魂伤分量前缀,改用标准中性日志 | 展示颜色不应穿透伤害核心、事件和 React 通用组件 |
|
||||
| 幽都终结 | 四/五层总系数作为一个固定 coefficient 统一取整;一项既有用例出现 1 点气血差 | 固定计划的数学语义稳定,避免 DamageEffect 读取任意 Buff 层数 |
|
||||
| 天衍元素记录 | `creation-v2` 的“万象归一”由三种不同元素近似为每三次激活 | 通用造物内容无法在不恢复元素专用核心 API 的情况下动态生成每元素 marker 配置 |
|
||||
| 天衍燎原/蒸发 | 不再读取并重放灼烧 Buff;改为固定 `0.16` 与按层 `0.16/0.32` 追伤 | 保留额外一跳、按剩余灼烧强弱兑现和清除灼烧的战术用途,同时消除反射式 Buff 耦合 |
|
||||
|
||||
没有发现超出以上批准范围的非预期差异。
|
||||
|
||||
## 4. 最终验证记录
|
||||
|
||||
| 命令 | 结果 | 明细 |
|
||||
| --- | --- | --- |
|
||||
| `bunx vitest run src/shared/engine/sect/testing/architecture/architectureGuard.test.ts` | 通过 | 1 文件,11 测试 |
|
||||
| `bunx vitest run src/shared/engine/battle-v5/tests` | 通过 | 32 文件,277 测试 |
|
||||
| `bunx vitest run src/shared/engine/sect` | 通过 | 35 文件,484 测试 |
|
||||
| `bunx vitest run src/shared/engine/creation-v2` | 通过 | 18 文件,133 测试 |
|
||||
| `bun run lint` | 通过 | ESLint 无错误 |
|
||||
| `bun run test` | 通过 | 245 文件,1795 测试 |
|
||||
| `bun run build` | 通过 | client 与 server TypeScript/Vite 构建均通过 |
|
||||
| `bunx prettier --check <七个同步文档>` | 通过 | 全部符合 Prettier |
|
||||
| `git diff --check` | 通过 | 无空白错误 |
|
||||
|
||||
### 4.1 删除项搜索门禁
|
||||
|
||||
架构守卫测试必须保留历史符号作为禁止项正则和自检样本,因此下列生产源码搜索排除该测试文件:
|
||||
|
||||
```bash
|
||||
rg -n --glob '!src/shared/engine/sect/testing/architecture/architectureGuard.test.ts' "postDamageEffects|DamageDisplayMetadata|buffLayerScalar" src
|
||||
rg -n --glob '!src/shared/engine/sect/testing/architecture/architectureGuard.test.ts' "ElementHistory|element_history|elementHistories" src
|
||||
rg -n --glob '!src/shared/engine/sect/testing/architecture/architectureGuard.test.ts' "BuffPeriodicSettlement|buff_periodic_settlement|manualSettlementEffects" src
|
||||
rg -n "tone.*violet|display: \{ label: '(魂伤|术伤)'" src
|
||||
```
|
||||
|
||||
四项结果均为 0。代码审计也未发现同用途改名替代:没有新的宗门专用生命周期阶段、动态 Buff 配置执行器、元素专用 runtime state、伤害展示字段或单宗门 Effect 类型。
|
||||
|
||||
## 5. 完成审计
|
||||
|
||||
1. 五组删除项已从生产源码消失:是。
|
||||
2. 是否新增同义替代抽象:否。
|
||||
3. battle-v5 非 Adapter 生产核心是否含具体宗门 ID、状态 ID、节点 ID 或中文宗门术语:否,由架构测试约束。
|
||||
4. 是否新增仅单宗门使用的 EffectConfig、runtime state 或事件字段:否。
|
||||
5. 幽都和天衍差异是否仅限批准范围:是,见第 3 节。
|
||||
6. 红尘剑宗、无相宗、creation-v2 和双端 build 是否完成回归:是。
|
||||
7. 是否有跳过命令:否。
|
||||
8. 是否存在数据迁移、持久化兼容或稳定 ID 风险:否。
|
||||
|
||||
本轮重构达到方案完成定义。后续宗门若需要类似复杂机制,应先依据作者指南的八问模板证明其通用性;无法证明时,继续优先采用内容层固定计划、隐藏 marker、通用 counter 或可接受的近似语义。
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,721 +0,0 @@
|
||||
# 「天地灵气」统一体力系统 — 重构方案
|
||||
|
||||
## 一、背景与动机
|
||||
|
||||
### 1.1 现状问题
|
||||
|
||||
《万界道友》当前各玩法采用**独立的限制机制**:副本每天 2 次、天骄榜每天 10 次、闭关修炼每天消耗 200 年寿元上限等。这些限制各自为政,带来了几个核心问题:
|
||||
|
||||
- **玩家体验僵硬**:每种玩法的次数相互独立,玩家无法按自己的偏好分配游戏时间。想多刷副本的玩家无法把天骄榜的次数"转移"过来。
|
||||
- **成本管控分散**:每个需要 LLM 参与的玩法都有自己的限制器(`dungeonLimiter`、`characterGenerationLimiter` 等),缺乏统一的成本视角。
|
||||
- **扩展性差**:新增一个玩法就要写一个新的 Limiter,数值调整需要逐个系统修改。
|
||||
- **付费设计困难**:独立的次数限制难以设计统一的付费补充方案。
|
||||
|
||||
### 1.2 设计目标
|
||||
|
||||
引入一个**统一的「天地灵气」资源系统**,实现:
|
||||
|
||||
1. **统一成本度量**:将所有涉及 LLM 调用或重要游戏进展的行为,统一折算为「灵气」消耗。
|
||||
2. **玩家自主分配**:取消各玩法的独立次数限制,玩家持有一个灵气池,自由选择参与什么玩法。
|
||||
3. **每日懒刷新**:每天按需恢复灵气(玩家操作或登录时触发),不做全表批量刷新,保证日活节奏的同时避免百万级角色的性能问题。
|
||||
4. **付费扩展预留**:可通过道具(如「聚灵丹」)补充灵气,为后续商业化留口子。
|
||||
5. **反作弊保底**:保留必要的冷却时间和并发锁,防止短时间内恶意刷取。
|
||||
|
||||
---
|
||||
|
||||
## 二、核心概念设计
|
||||
|
||||
### 2.1 名称与世界观融合
|
||||
|
||||
推荐名称:**「天地灵气」**(简称「灵气」)
|
||||
|
||||
世界观包装:修仙世界中,天地间的灵气是万物修行的根基。每一次探索秘境、炼制丹药、推演天机,都需要汲取天地灵气。灵气会随着时间自然汇聚,也可通过灵石阵法或丹药加速恢复。
|
||||
|
||||
选择理由:
|
||||
- 「灵气」是修仙世界观中最通用的概念,天然适合做统一资源。
|
||||
- 比「体力值」更有沉浸感,比「疲劳值」更正面积极。
|
||||
- 与现有的灵石、修为形成三层资源体系:**灵石(货币)→ 灵气(行动力)→ 修为(进度)**。
|
||||
|
||||
### 2.2 资源定义
|
||||
|
||||
| 属性 | 值 | 说明 |
|
||||
|------|------|------|
|
||||
| 资源名称 | 天地灵气(Qi) | 统一体力资源 |
|
||||
| 存储位置 | `cultivators.qi` (integer) | 新增数据库列 |
|
||||
| 上限(Max Qi) | **200**(全角色统一) | 不随境界变化,所有玩家公平一致 |
|
||||
| 每日恢复量 | **200**(全角色统一) | 每日恢复至上限,等效 4 次副本 |
|
||||
| 恢复策略 | **懒刷新**(无批量 cron) | 玩家执行操作或登录时按需刷新,不对全表做定时 UPDATE |
|
||||
| 溢出规则 | 基础上限 200 | 自然恢复不超过上限,道具补充可临时超出上限(不超过 300,即 150%) |
|
||||
|
||||
### 2.3 统一上限设计说明
|
||||
|
||||
所有角色不论境界,灵气上限和每日恢复量均为 **200 点**。
|
||||
|
||||
设计理由:
|
||||
|
||||
- **公平性**:新玩家与老玩家拥有相同的每日行动力,降低了新玩家的追赶压力。
|
||||
- **简单直观**:不需要向玩家解释"为什么我的灵气上限比他少",一个数字走天下。
|
||||
- **成本可控**:统一的灵气上限意味着每个角色的 LLM 成本上限一致,便于运营预算。
|
||||
- **境界差异化体现在别处**:高境界角色在战斗数值、可用玩法、装备品质上有优势,灵气层面保持平等。
|
||||
|
||||
> 注:如果未来希望给高境界玩家更多灵气,只需在配置文件中引入境界映射表即可,当前设计不做过度预留。
|
||||
|
||||
### 2.4 灵气突破上限(道具补充溢出)
|
||||
|
||||
通过付费道具(「聚灵丹」等)补充灵气时,允许临时超出基础上限,但设有**溢出上限**:
|
||||
|
||||
- 溢出上限 = 200 × 150% = **300**
|
||||
- 自然恢复只在低于 200 时生效
|
||||
- 灵气会随时间消耗逐渐回到基础上限以内
|
||||
|
||||
这个设计参考了主流手游(如原神的浓缩树脂、明日方舟的理智)的做法,既保护了付费意愿,又防止无限堆积。
|
||||
|
||||
---
|
||||
|
||||
## 三、各玩法灵气消耗定价
|
||||
|
||||
### 3.1 定价原则
|
||||
|
||||
1. **LLM 成本正比原则**:LLM 调用越重的玩法,灵气消耗越高。
|
||||
2. **进度价值原则**:对角色成长影响越大的行为,消耗越高。
|
||||
3. **低门槛保底**:基础体验行为(如查看信息、世界聊天)不消耗灵气。
|
||||
4. **PvP 公平性**:PvP 战斗本身的确定性部分(天骄榜挑战、赌战)消耗较低,但附带 LLM 的战前/战后叙事则增加消耗。
|
||||
|
||||
### 3.2 灵气消耗表
|
||||
|
||||
| 玩法 | 行为 | 灵气消耗 | LLM 调用 | 说明 |
|
||||
|------|------|---------|---------|------|
|
||||
| **副本探索** | 开始一次副本 | **50** | 是(多轮叙事生成) | 原限制 2 次/天 → 现等效 4 次,LLM 最重的玩法 |
|
||||
| **造物炼器** | 创建一件物品 | **8** | 是(LLM 生成) | 含法宝、功法、神通,含叙事润色 |
|
||||
| **炼丹(自由)** | 自由炼丹一次 | **15** | 是(LLM 属性解析) | 高 LLM 消耗,需完整解析玩家输入 |
|
||||
| **炼丹(丹方)** | 按丹方炼丹 | **8** | 轻度(叙事润色) | LLM 消耗较轻,与造物齐平 |
|
||||
| **丹方解析** | 解析未知丹方 | **5** | 是(LLM 分析) | 保留 30 秒冷却防刷 |
|
||||
| **幻境之塔** | 开始新一轮 | **30** | 是(敌人生成) | 赛季制,LLM 调用较重 |
|
||||
| **闭关修炼** | 每消耗 10 年寿元 | **5** | 是(突破故事) | 原限制 200 年/天,现等效可闭关 400 年 |
|
||||
| **突破** | 尝试破境 | **15** | 是(破境叙事) | 重要节点,单独定价 |
|
||||
| **历练收益** | 领取离线收益 | **8** | 是(收益故事 + 材料生成) | 每日 1-2 次 |
|
||||
| **天骄榜挑战** | 发起一次挑战 | **8** | 否 | 确定性战斗,轻度消耗 |
|
||||
| **赌战** | 创建/参与赌战 | **8** | 否 | 确定性战斗 |
|
||||
| **命格重塑** | 重塑命格 | **30** | 是(LLM 生成) | 重要行为,高消耗 |
|
||||
| **天机推演** | 每日运势 | **5** | 是(LLM 生成) | 每日 1 次,轻量 |
|
||||
| **坊市鉴定** | 鉴定物品 | **5** | 是(LLM 评估) | 低消耗 |
|
||||
| **灵眼之泉疗养** | 恢复 HP/MP | **0** | 否 | 纯数值操作,不消耗灵气 |
|
||||
| **服用丹药** | 使用消耗品 | **0** | 否 | 纯数值操作 |
|
||||
| **拍卖行** | 买/卖物品 | **0** | 否 | 玩家间经济 |
|
||||
| **世界聊天** | 发送消息 | **0** | 否 | 保留 60 秒冷却即可 |
|
||||
| **问法寻卷** | 抽卡 | **0** | 否(间接) | 已有符箓消耗做门槛 |
|
||||
|
||||
### 3.3 不消耗灵气但保留限制的行为
|
||||
|
||||
以下行为不涉及 LLM 或已用其他资源做门槛,无需纳入灵气系统:
|
||||
|
||||
- **角色创建**:保持每日 6 次 / 邮箱 / IP 的限制(防止批量注册滥用)
|
||||
- **兑换码**:保持现有机制
|
||||
- **日常任务**:任务本身不消耗灵气(它们是被动追踪的行为)
|
||||
|
||||
---
|
||||
|
||||
## 四、替代现有限制器的迁移策略
|
||||
|
||||
### 4.1 被替代的独立限制器
|
||||
|
||||
| 现有限制器 | 原限制 | 迁移方案 |
|
||||
|-----------|-------|---------|
|
||||
| `dungeonLimiter` | 2 次/天 | **废弃**。由灵气系统统一管控,副本 50 灵气/次,等效 4 次 |
|
||||
| 每日寿元上限 | 200 年/天 | **已删除**。闭关改为消耗灵气(5 灵气/10 年),寿元仍按角色实际寿元扣除 |
|
||||
| `rankings daily challenges` | 10 次/天 | **废弃**。天骄榜挑战改为每次消耗 8 灵气 |
|
||||
|
||||
### 4.2 保留的限制器
|
||||
|
||||
| 限制器 | 原因 |
|
||||
|--------|------|
|
||||
| `characterGenerationLimiter` | 防批量注册,与游戏内体力无关 |
|
||||
| `worldChatLimiter`(60 秒冷却) | 防刷屏,保留 |
|
||||
| `alchemyFormulaCooldown`(30 秒冷却) | 防脚本刷解析,保留作为反刷机制 |
|
||||
| 并发锁(`retreatLock`、challenge lock 等) | 防并发冲突,保留 |
|
||||
| 拍卖行上架数量上限(5 个) | 经济平衡,保留 |
|
||||
| 赌战上限(1 个) | 经济平衡,保留 |
|
||||
|
||||
### 4.3 迁移方式:直接切换
|
||||
|
||||
本系统采用**一次性直接切换**,不设过渡期:
|
||||
|
||||
1. 执行数据库迁移,为所有角色新增灵气列,初始值设为 200(满灵气)。
|
||||
2. 在同一个版本中,所有玩法入口统一接入灵气消耗检查。
|
||||
3. 同步废弃旧限制器(`dungeonLimiter`、每日寿元上限、`rankings daily challenges`)。
|
||||
4. 旧限制器的 Redis 键会在次日自然过期,无需手动清理。
|
||||
|
||||
选择直接切换的原因:
|
||||
|
||||
- 灵气系统在各玩法的等效次数上不低于旧限制(副本从 2 次提升到 4 次),玩家体验只会变好。
|
||||
- 灰度并存阶段会让代码复杂度翻倍(既要检查灵气又要检查旧限制),增加出错风险。
|
||||
- 数值配置化设计,如有问题可快速调整灵气消耗值来应对。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库与技术方案
|
||||
|
||||
### 5.1 数据库变更
|
||||
|
||||
**新增列(迁移文件 0048+):**
|
||||
|
||||
```sql
|
||||
ALTER TABLE wanjiedaoyou_cultivators
|
||||
ADD COLUMN qi INTEGER NOT NULL DEFAULT 200,
|
||||
ADD COLUMN qi_max INTEGER NOT NULL DEFAULT 200,
|
||||
ADD COLUMN qi_last_refreshed_at TIMESTAMPTZ DEFAULT NOW();
|
||||
```
|
||||
|
||||
| 列名 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `qi` | INTEGER | 当前灵气值,默认 200(满灵气) |
|
||||
| `qi_max` | INTEGER | 灵气上限,当前统一 200,预留给未来可能的调整 |
|
||||
| `qi_last_refreshed_at` | TIMESTAMPTZ | 上次灵气刷新时间,用于懒刷新判断 |
|
||||
|
||||
**新增灵气日志表(可选但推荐):**
|
||||
|
||||
```sql
|
||||
CREATE TABLE wanjiedaoyou_qi_logs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
cultivator_id UUID NOT NULL REFERENCES wanjiedaoyou_cultivators(id),
|
||||
action VARCHAR(50) NOT NULL, -- 'dungeon_start', 'retreat', 'craft', etc.
|
||||
qi_cost INTEGER NOT NULL, -- 消耗量(正数)
|
||||
qi_gain INTEGER NOT NULL DEFAULT 0,-- 获得量(道具补充等)
|
||||
qi_before INTEGER NOT NULL,
|
||||
qi_after INTEGER NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_qi_logs_cultivator_date
|
||||
ON wanjiedaoyou_qi_logs(cultivator_id, created_at DESC);
|
||||
```
|
||||
|
||||
这张表用于审计、数据分析和反作弊。可以定期清理超过 30 天的日志。
|
||||
|
||||
### 5.2 核心服务层设计
|
||||
|
||||
**新增 `QiService`(`src/server/lib/services/QiService.ts`):**
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 天地灵气管理服务
|
||||
*
|
||||
* 核心职责:
|
||||
* 1. 检查灵气是否充足
|
||||
* 2. 扣除灵气(原子操作)
|
||||
* 3. 补充灵气(道具/付费)
|
||||
* 4. 懒刷新(操作/登录时按需触发)
|
||||
*/
|
||||
|
||||
// 灵气消耗配置表
|
||||
const QI_COSTS: Record<string, number> = {
|
||||
dungeon_start: 50,
|
||||
retreat_per_10_years: 5,
|
||||
breakthrough: 15,
|
||||
yield_claim: 8,
|
||||
craft_creation: 8,
|
||||
alchemy_improvised: 15,
|
||||
alchemy_formula: 8,
|
||||
alchemy_formula_analysis: 5,
|
||||
tower_start: 30,
|
||||
ranking_challenge: 8,
|
||||
bet_battle: 8,
|
||||
divine_fortune: 5,
|
||||
fate_reshape: 30,
|
||||
market_appraisal: 5,
|
||||
};
|
||||
|
||||
// 统一灵气上限与每日恢复量
|
||||
const QI_MAX = 200;
|
||||
const QI_DAILY_REFRESH = 200;
|
||||
```
|
||||
|
||||
**核心方法:**
|
||||
|
||||
```typescript
|
||||
class QiService {
|
||||
/**
|
||||
* 检查灵气是否充足(不扣除)
|
||||
* 内部会先触发懒刷新(如跨天)
|
||||
*/
|
||||
async checkQi(cultivatorId: string, action: string): Promise<{
|
||||
sufficient: boolean;
|
||||
current: number;
|
||||
required: number;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* 消耗灵气(原子操作,含并发保护)
|
||||
* 执行前自动触发懒刷新(如跨天)
|
||||
* 返回扣除后的灵气值
|
||||
*/
|
||||
async consumeQi(cultivatorId: string, action: string): Promise<{
|
||||
success: boolean;
|
||||
qiBefore: number;
|
||||
qiAfter: number;
|
||||
consumed: number;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* 补充灵气(道具/付费)
|
||||
* 允许溢出至 300(QI_MAX * 1.5)
|
||||
*/
|
||||
async restoreQi(cultivatorId: string, amount: number, source: string): Promise<{
|
||||
success: boolean;
|
||||
qiBefore: number;
|
||||
qiAfter: number;
|
||||
restored: number;
|
||||
overflowCap: number;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* 懒刷新:检查是否需要跨天恢复灵气
|
||||
* 在执行灵气操作或角色登录时调用
|
||||
* 如果 qi_last_refreshed_at 不是今天(Asia/Shanghai),则恢复至上限
|
||||
*/
|
||||
async refreshIfNeeded(cultivatorId: string): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 每日刷新策略:纯懒刷新
|
||||
|
||||
**不使用定时 cron 任务**,完全依赖懒刷新。
|
||||
|
||||
原因:当角色数量达到数十万甚至百万级别时,每日全表 UPDATE 不仅成本高昂(大量无用写操作),而且会产生明显的执行延迟。绝大多数角色可能长期不登录,批量刷新纯属浪费。
|
||||
|
||||
**懒刷新机制:**
|
||||
|
||||
1. **触发时机**:每次玩家执行灵气消耗操作前,或角色登录加载信息时。
|
||||
2. **判断逻辑**:比较 `qi_last_refreshed_at` 与当前时间(Asia/Shanghai 时区)是否为同一天。如果跨天,则执行一次刷新。
|
||||
3. **刷新行为**:如果当前灵气 < 上限(200),则恢复至上限;如果灵气已 >= 上限(如道具补充溢出),则保持不变,只更新时间戳。
|
||||
4. **原子性**:刷新和灵气扣除在同一次数据库操作中完成,避免并发问题。
|
||||
|
||||
**懒刷新伪代码:**
|
||||
|
||||
```typescript
|
||||
async function refreshAndConsume(cultivatorId: string, action: string) {
|
||||
const cost = QI_COSTS[action];
|
||||
if (!cost) throw new Error(`Unknown action: ${action}`);
|
||||
|
||||
const today = getTodayString('Asia/Shanghai'); // 'YYYY-MM-DD'
|
||||
|
||||
// 一次 SQL 同时完成懒刷新 + 扣除(利用 CASE WHEN 保证原子性)
|
||||
const result = await db.execute(sql`
|
||||
UPDATE wanjiedaoyou_cultivators
|
||||
SET
|
||||
qi = CASE
|
||||
WHEN DATE(qi_last_refreshed_at AT TIME ZONE 'Asia/Shanghai') < ${today}::date
|
||||
THEN LEAST(${QI_MAX}, GREATEST(qi, ${QI_MAX})) - ${cost}
|
||||
ELSE qi - ${cost}
|
||||
END,
|
||||
qi_last_refreshed_at = CASE
|
||||
WHEN DATE(qi_last_refreshed_at AT TIME ZONE 'Asia/Shanghai') < ${today}::date
|
||||
THEN NOW()
|
||||
ELSE qi_last_refreshed_at
|
||||
END
|
||||
WHERE id = ${cultivatorId}
|
||||
AND (
|
||||
CASE
|
||||
WHEN DATE(qi_last_refreshed_at AT TIME ZONE 'Asia/Shanghai') < ${today}::date
|
||||
THEN LEAST(${QI_MAX}, GREATEST(qi, ${QI_MAX}))
|
||||
ELSE qi
|
||||
END
|
||||
) >= ${cost}
|
||||
RETURNING qi, qi_last_refreshed_at
|
||||
`);
|
||||
|
||||
if (result.length === 0) {
|
||||
// 灵气不足,查询当前值返回给调用方
|
||||
return { success: false, ... };
|
||||
}
|
||||
return { success: true, qiAfter: result[0].qi, consumed: cost };
|
||||
}
|
||||
```
|
||||
|
||||
**登录时预刷新(可选优化):**
|
||||
|
||||
在角色信息加载接口中调用 `refreshIfNeeded()`,让玩家登录时就能看到正确的灵气值,而不是等到操作时才发现跨天了。这只是一个展示优化,核心逻辑仍由操作时的原子刷新保证。
|
||||
|
||||
**为什么不需要 cron 兜底:**
|
||||
|
||||
- 灵气数据只在玩家操作时才被读取和消费,不活跃的角色的灵气值不需要是最新的。
|
||||
- 排行榜等读取场景只关心排名数据(战斗结果),不依赖灵气值做排序。
|
||||
- 如果需要查看不活跃玩家的灵气值(如管理后台),可以在查询层做日期判断后展示"明日恢复"标签。
|
||||
|
||||
### 5.4 与现有 Redis 限制器的关系
|
||||
|
||||
灵气系统**主要使用数据库**存储,不依赖 Redis 做核心计数。原因:
|
||||
|
||||
- 灵气值需要与角色数据一起参与事务(扣灵气 + 执行玩法 = 一个事务)。
|
||||
- 数据库操作可以保证原子性和一致性。
|
||||
- Redis 限制器的模式(`{feature}:daily:{id}:{date}`)不再需要,可以随旧限制器一起清理。
|
||||
|
||||
但仍保留 Redis 用于:
|
||||
|
||||
- **并发锁**:防止同一玩家同时发起多个消耗灵气的操作。
|
||||
- **短冷却**:炼丹公式解析 30 秒、世界聊天 60 秒等。
|
||||
|
||||
---
|
||||
|
||||
## 六、付费设计预留
|
||||
|
||||
### 6.1 灵气补充道具
|
||||
|
||||
| 道具名称 | 效果 | 获取方式 | 设计意图 |
|
||||
|---------|------|---------|---------|
|
||||
| **聚灵丹(小)** | 恢复 50 灵气 | 商城购买 / 活动奖励 | 等效 1 次副本 |
|
||||
| **聚灵丹(中)** | 恢复 100 灵气 | 商城购买 / 周卡奖励 | 等效半日行动力 |
|
||||
| **聚灵丹(大)** | 恢复 200 灵气 | 商城购买 / 月卡奖励 | 等效一整管灵气 |
|
||||
| **天地灵泉** | 立即回满至上限 | 稀有活动 / 高价位商城 | 应急使用 |
|
||||
|
||||
### 6.2 付费边界原则
|
||||
|
||||
- **绝不售卖灵气本身**:灵气只能通过游戏内自然恢复或道具补充,道具可以通过游戏内途径获得。
|
||||
- **付费加速而非付费获胜**:付费只增加每日可做的事情量,不影响单次玩法的收益。
|
||||
- **防沉迷底线**:每日道具补充灵气有次数上限(如聚灵丹每天最多使用 3 次),防止无限制氪金刷取。
|
||||
- **VIP/月卡体系**:可设计「道友令牌」月卡,效果为每日灵气上限 +40(即 240)、每日自动恢复量 +40。
|
||||
|
||||
### 6.3 未来扩展接口
|
||||
|
||||
道具补充灵气的接口设计为通用的 `restoreQi(cultivatorId, amount, source)` 方法,`source` 参数可以是:
|
||||
|
||||
- `'item'`:使用背包中的聚灵丹
|
||||
- `'shop'`:商城直接购买
|
||||
- `'monthly_card'`:月卡每日自动补充
|
||||
- `'gm'`:管理员手动补充
|
||||
- `'compensation'`:系统补偿(如维护补偿邮件附带灵气)
|
||||
|
||||
---
|
||||
|
||||
## 七、前端交互设计
|
||||
|
||||
### 7.1 灵气显示
|
||||
|
||||
在游戏主界面(角色信息面板)顶部显示灵气状态:
|
||||
|
||||
```
|
||||
天地灵气 ████████████████░░░░ 144/200
|
||||
```
|
||||
|
||||
- 进度条颜色:>60% 绿色,30-60% 黄色,<30% 红色
|
||||
- 点击可展开灵气详情面板
|
||||
|
||||
### 7.2 灵气详情面板
|
||||
|
||||
展示内容:
|
||||
|
||||
- 当前灵气 / 上限
|
||||
- 今日恢复量 / 已消耗量
|
||||
- 各玩法消耗明细(今日)
|
||||
- 道具补充入口(聚灵丹按钮)
|
||||
- 下次自然恢复时间(次日 00:00)
|
||||
|
||||
### 7.3 玩法入口拦截
|
||||
|
||||
当灵气不足时,各玩法入口按钮变为灰色,显示:
|
||||
|
||||
```
|
||||
灵气不足(需要 50,当前 12)
|
||||
[使用聚灵丹] [等待恢复]
|
||||
```
|
||||
|
||||
### 7.4 灵气消耗预览
|
||||
|
||||
在玩家确认执行操作前,显示灵气消耗提示:
|
||||
|
||||
```
|
||||
┌─ 副本探索 ──────────────────┐
|
||||
│ 消耗灵气:50 │
|
||||
│ 当前灵气:144 → 94 │
|
||||
│ │
|
||||
│ [确认出发] │
|
||||
└────────────────────────────┘
|
||||
```
|
||||
|
||||
### 7.5 前端 Contract 变更
|
||||
|
||||
在 `src/shared/contracts/` 中新增:
|
||||
|
||||
```typescript
|
||||
// 灵气相关 contract
|
||||
export interface QiState {
|
||||
current: number;
|
||||
max: number;
|
||||
dailyRefresh: number;
|
||||
lastRefreshedAt: string; // ISO datetime
|
||||
todayConsumed: number; // 今日已消耗(可选,用于详情展示)
|
||||
todayRestored: number; // 今日已补充(道具)
|
||||
}
|
||||
|
||||
export interface QiConsumeResult {
|
||||
success: boolean;
|
||||
qiBefore: number;
|
||||
qiAfter: number;
|
||||
consumed: number;
|
||||
action: string;
|
||||
message?: string; // 灵气不足时的提示
|
||||
}
|
||||
|
||||
export interface QiRestoreResult {
|
||||
success: boolean;
|
||||
qiBefore: number;
|
||||
qiAfter: number;
|
||||
restored: number;
|
||||
overflowCap: number;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、数值平衡与经济影响
|
||||
|
||||
### 8.1 等效日常活动量对比
|
||||
|
||||
以**200 灵气/天**为例,与当前系统对比:
|
||||
|
||||
| 玩法 | 当前限制 | 新系统下的等效量 | 变化 |
|
||||
|------|---------|----------------|------|
|
||||
| 副本 | 2 次/天 | 200 ÷ 50 = **4 次**(纯副本的话) | 翻倍 |
|
||||
| 天骄榜 | 10 次/天 | 200 ÷ 8 = **25 次**(纯挑战的话) | 大幅增加 |
|
||||
| 闭关 | 200 年/天 | 200 ÷ 5 × 10 = **400 年**(纯闭关的话) | 翻倍 |
|
||||
| 造物 | 无上限 | 200 ÷ 8 = **25 次**(纯造物的话) | 明确上限 |
|
||||
|
||||
**但这正是设计的核心意图**:玩家不可能把所有灵气都花在一个玩法上。灵气系统的本质是让玩家**在多种玩法之间分配有限的行动力**,而不是在每种玩法上都无限制。
|
||||
|
||||
一个典型玩家的日活分配可能如下:
|
||||
|
||||
| 行为 | 次数 | 灵气消耗 | 小计 |
|
||||
|------|------|---------|------|
|
||||
| 领取离线收益 | 1 次 | 8 | 8 |
|
||||
| 天机推演 | 1 次 | 5 | 5 |
|
||||
| 闭关修炼 30 年 | 1 次 | 15 | 15 |
|
||||
| 副本探索 | 2 次 | 50 | 100 |
|
||||
| 炼丹 | 1 次 | 8 | 8 |
|
||||
| 天骄榜挑战 | 3 次 | 8 | 24 |
|
||||
| 幻境之塔 | 1 次 | 30 | 30 |
|
||||
| 造物 | 1 次 | 8 | 8 |
|
||||
| **合计** | | | **198 / 200** |
|
||||
|
||||
几乎用满了灵气,但玩家可以根据自己的需求灵活调整——今天想多刷一次副本?放弃天骄榜和幻境之塔就行。
|
||||
|
||||
### 8.2 LLM 成本预估
|
||||
|
||||
按上述典型日活计算 LLM 调用次数:
|
||||
|
||||
| 行为 | LLM 调用 | 每日次数 | 总调用 |
|
||||
|------|---------|---------|-------|
|
||||
| 离线收益 | 1 次 | 1 | 1 |
|
||||
| 天机推演 | 1 次 | 1 | 1 |
|
||||
| 闭关突破故事 | 1 次 | ~0.3(约 3 天 1 次突破) | 0.3 |
|
||||
| 副本叙事(3-5 轮/次) | 3-5 次 | 2 | 8 |
|
||||
| 炼丹 | 1 次 | 1 | 1 |
|
||||
| 幻境之塔敌人生成 | 1 次 | 1 | 1 |
|
||||
| 造物 | 2-3 次 | 1 | 2.5 |
|
||||
| 天骄榜(无 LLM) | 0 次 | 3 | 0 |
|
||||
| **合计** | | | **~14.8 次/天/玩家** |
|
||||
|
||||
对比旧系统:
|
||||
|
||||
| 行为 | 旧系统每日 LLM 调用上限 |
|
||||
|------|---------------------|
|
||||
| 副本叙事 | 2 × 5 = 10 |
|
||||
| 闭关突破 | ~1(受寿元限制) |
|
||||
| 离线收益 | 1 |
|
||||
| 天机推演 | 1 |
|
||||
| 炼丹 | 无上限(实际 ~2-3) |
|
||||
| 造物 | 无上限(实际 ~1-2) |
|
||||
| 幻境之塔 | 无上限 |
|
||||
| **合计** | **~17-20 次/天/玩家** |
|
||||
|
||||
灵气系统下 LLM 成本**反而略有下降**(从 ~18 次降至 ~15 次),因为副本虽然从 2 次增到 4 次(上限),但典型玩家通常只跑 2 次,而造物/天骄榜等低消耗玩法的增加不会带来额外 LLM 成本。灵气系统提供了更精确的成本控制——灵气上限 200 就是一个硬性的每日 LLM 预算天花板。
|
||||
|
||||
### 8.3 灵石经济影响
|
||||
|
||||
灵气系统与灵石经济正交。灵气控制行动次数,灵石控制物品价值。两者不会产生直接冲突。
|
||||
|
||||
需要注意的是:当玩家可以更多地刷副本(从 2 次增到最多 4 次),副本产出的材料/灵石总量会增加。建议同步审视副本掉落表,确保经济不会通胀。如有必要,可以在高灵气消耗下降低单次副本的掉落量。
|
||||
|
||||
---
|
||||
|
||||
## 九、数值配置化设计
|
||||
|
||||
所有灵气相关数值应集中在配置文件中,便于后续调整:
|
||||
|
||||
**新增文件 `src/shared/config/qiSystem.ts`:**
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 天地灵气系统配置
|
||||
* 所有数值集中管理,方便策划调整
|
||||
*/
|
||||
|
||||
/** 统一灵气上限(所有角色一致) */
|
||||
export const QI_MAX = 200;
|
||||
|
||||
/** 每日灵气恢复量(所有角色一致) */
|
||||
export const QI_DAILY_REFRESH = 200;
|
||||
|
||||
/** 各玩法灵气消耗 */
|
||||
export const QI_ACTION_COSTS = {
|
||||
dungeon_start: 50,
|
||||
retreat_per_10_years: 5,
|
||||
breakthrough: 15,
|
||||
yield_claim: 8,
|
||||
craft_creation: 8,
|
||||
alchemy_improvised: 15,
|
||||
alchemy_formula: 8,
|
||||
alchemy_formula_analysis: 5,
|
||||
tower_start: 30,
|
||||
ranking_challenge: 8,
|
||||
bet_battle: 8,
|
||||
divine_fortune: 5,
|
||||
fate_reshape: 30,
|
||||
market_appraisal: 5,
|
||||
} as const;
|
||||
|
||||
/** 道具补充灵气的溢出系数(相对于基础上限) */
|
||||
export const QI_OVERFLOW_RATIO = 1.5;
|
||||
|
||||
/** 溢出上限(QI_MAX * QI_OVERFLOW_RATIO) */
|
||||
export const QI_OVERFLOW_MAX = 300;
|
||||
|
||||
/** 每日道具补充次数上限 */
|
||||
export const QI_DAILY_RESTORE_LIMIT = 3;
|
||||
|
||||
/** 灵气刷新时区 */
|
||||
export const QI_REFRESH_TIMEZONE = 'Asia/Shanghai';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、API 设计
|
||||
|
||||
### 10.1 新增接口
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| `GET` | `/api/cultivator/qi` | 获取当前灵气状态 |
|
||||
| `POST` | `/api/cultivator/qi/restore` | 使用道具恢复灵气 |
|
||||
| `GET` | `/api/cultivator/qi/logs` | 查看今日灵气消耗明细 |
|
||||
|
||||
### 10.2 现有接口变更
|
||||
|
||||
所有需要消耗灵气的接口,在执行主逻辑之前,统一调用 `QiService.consumeQi()` 进行灵气扣除。扣除失败则返回 `402 Qi Insufficient` 错误码(自定义),附带所需灵气量和当前灵气量。
|
||||
|
||||
**错误响应示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "QI_INSUFFICIENT",
|
||||
"message": "灵气不足,无法探索副本",
|
||||
"required": 50,
|
||||
"current": 12,
|
||||
"action": "dungeon_start"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十一、实施计划
|
||||
|
||||
### Phase 0:准备(1-2 天)
|
||||
|
||||
- 创建 `qiSystem.ts` 配置文件
|
||||
- 新增 `QiService` 服务(含懒刷新逻辑)
|
||||
- 编写数据库迁移文件
|
||||
- 新增灵气相关 Contract 类型
|
||||
|
||||
### Phase 1:核心玩法接入(3-5 天)
|
||||
|
||||
- 数据库迁移(新增列 + 日志表)
|
||||
- `QiService` 实现(消耗、恢复、懒刷新)
|
||||
- 改造副本系统接入灵气消耗(50/次)
|
||||
- 改造闭关/突破系统接入灵气消耗
|
||||
- 改造天骄榜接入灵气消耗
|
||||
- **同步废弃**旧限制器(`dungeonLimiter`、每日寿元上限、rankings daily)
|
||||
|
||||
### Phase 2:全玩法接入(3-5 天)
|
||||
|
||||
- 改造造物/炼丹系统(8/15)
|
||||
- 改造幻境之塔(30)
|
||||
- 改造历练收益(8)
|
||||
- 改造命格重塑(30)
|
||||
- 改造天机推演(5)
|
||||
- 改造坊市鉴定(5)
|
||||
|
||||
### Phase 3:前端适配(3-5 天)
|
||||
|
||||
- 灵气状态栏 UI
|
||||
- 灵气详情面板
|
||||
- 各玩法入口拦截与消耗预览
|
||||
- 道具补充入口
|
||||
|
||||
### Phase 4:付费道具(后续版本)
|
||||
|
||||
- 实现聚灵丹道具(走现有消耗品系统)
|
||||
- 商城上架
|
||||
- 月卡/VIP 灵气加成
|
||||
|
||||
---
|
||||
|
||||
## 十二、风险与对策
|
||||
|
||||
### 12.1 风险清单
|
||||
|
||||
| 风险 | 影响 | 概率 | 对策 |
|
||||
|------|------|------|------|
|
||||
| 灵气消耗定价不合理 | 某些玩法被冷落或过度使用 | 高 | 配置化设计便于快速调整,上线后根据数据微调 |
|
||||
| LLM 成本意外上升 | 运营费用增加 | 低 | 灵气系统天然限制了每日 LLM 调用总量(~15 次/天),比旧系统更可控 |
|
||||
| 老玩家不适应 | 社区负面反馈 | 中 | 提前公告,副本从 2 次提升到 4 次,整体体验只升不降 |
|
||||
| 副本产出通胀 | 灵石/材料经济失衡 | 中 | 同步审视副本掉落表,必要时按灵气消耗比例降低单次掉落 |
|
||||
| 灵气刷新时区问题 | 海外玩家体验差 | 低 | 当前以 Asia/Shanghai 为准,后续可考虑按玩家时区个性化 |
|
||||
|
||||
### 12.2 回滚方案
|
||||
|
||||
灵气系统通过数据库列和独立服务实现,与旧限制器解耦。如需回滚:
|
||||
|
||||
1. 在配置中关闭灵气检查(`QiService.consumeQi` 变为 no-op)
|
||||
2. 重新启用旧限制器
|
||||
3. 灵气数据保留不删除,便于后续重新上线
|
||||
|
||||
---
|
||||
|
||||
## 十三、改动文件汇总
|
||||
|
||||
| 文件 | 操作 | 说明 |
|
||||
|------|------|------|
|
||||
| `drizzle/0048_qi_system.sql` | 新增 | 数据库迁移:灵气列 + 日志表 |
|
||||
| `src/shared/config/qiSystem.ts` | 新增 | 灵气系统数值配置 |
|
||||
| `src/shared/types/qi.ts` | 新增 | 灵气相关类型定义 |
|
||||
| `src/shared/contracts/qi.ts` | 新增 | 灵气 API Contract |
|
||||
| `src/server/lib/services/QiService.ts` | 新增 | 灵气管理核心服务 |
|
||||
| `src/server/routes/api/cultivator.ts` | 修改 | 新增灵气查询/恢复接口 |
|
||||
| `src/server/lib/dungeon/dungeonLimiter.ts` | 废弃 | 由灵气系统替代 |
|
||||
| `src/server/lib/redis/retreatLock.ts` | 保留 | 仅用于闭关/突破并发锁,不再承载每日寿元限制 |
|
||||
| `src/server/lib/redis/rankings.ts` | 修改 | 移除每日挑战计数逻辑 |
|
||||
| `src/server/lib/services/DungeonService.ts` | 修改 | 接入灵气消耗 |
|
||||
| `src/server/lib/services/CultivationService.ts` | 修改 | 闭关/突破接入灵气消耗 |
|
||||
| `src/server/lib/services/RankingService.ts` | 修改 | 天骄榜接入灵气消耗 |
|
||||
| `src/server/lib/services/AlchemyService.ts` | 修改 | 炼丹接入灵气消耗 |
|
||||
| `src/server/lib/services/CraftService.ts` | 修改 | 造物接入灵气消耗 |
|
||||
| `src/server/lib/services/TowerService.ts` | 修改 | 幻境之塔接入灵气消耗 |
|
||||
| `src/server/lib/services/MarketService.ts` | 修改 | 鉴定接入灵气消耗 |
|
||||
| `src/server/lib/services/FateReshapeService.ts` | 修改 | 命格重塑接入灵气消耗 |
|
||||
| `src/server/lib/services/DivineFortuneService.ts` | 修改 | 天机推演接入灵气消耗 |
|
||||
| `src/server/lib/services/YieldService.ts` | 修改 | 历练收益接入灵气消耗 |
|
||||
| `src/react-app/` | 修改 | 前端灵气 UI 适配 |
|
||||
| `docs/design-qi-system.md` | 新增 | 本设计文档 |
|
||||
|
||||
---
|
||||
|
||||
## 十四、总结
|
||||
|
||||
「天地灵气」统一体力系统的核心价值在于:
|
||||
|
||||
**对玩家**:从「每种玩法各有限制」变为「一个灵气池自由分配」,游戏体验更自由、更有策略深度。选择今天多刷副本还是多炼丹,成为了有意义的决策。
|
||||
|
||||
**对开发者**:从「N 个独立限制器」变为「1 个灵气配置表」,新增玩法只需在 `QI_ACTION_COSTS` 中加一行配置。数值调整只需改一个文件。
|
||||
|
||||
**对运营**:灵气系统提供了精确的成本控制能力——每个玩家的每日 LLM 调用上限由灵气上限和消耗配置决定,可以精确计算和调整。同时为付费道具(聚灵丹)和会员体系(月卡灵气加成)预留了清晰的接口。
|
||||
|
||||
整套方案在现有架构上改动可控,核心依赖已有的数据库事务和并发锁机制,懒刷新设计避免了百万级角色的批量 UPDATE 问题,不需要引入新的中间件或定时任务。
|
||||
@@ -0,0 +1,303 @@
|
||||
# 本地开发与部署
|
||||
|
||||
> 本页整理《万界道友》的本地开发、环境变量、数据库、构建、Docker、部署脚本与生产 cron 配置。项目简介、玩法、截图与赞助信息见 [README](../README.md)。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```text
|
||||
.
|
||||
├── src/index.ts # Bun 后端入口,导出 Hono API 与 WebSocket 配置
|
||||
├── src/server/ # Hono API、认证、服务层、数据库访问
|
||||
├── src/react-app/ # React SPA
|
||||
├── src/shared/ # 共享引擎、配置、类型、契约
|
||||
├── drizzle/ # 业务表 Drizzle migrations
|
||||
├── drizzle-auth/ # Better Auth Drizzle migrations
|
||||
├── drizzle.auth.config.ts # Better Auth 独立迁移配置
|
||||
├── scripts/ # 部署脚本与生产/NATS Compose
|
||||
├── docker/Dockerfile.app # Bun 主服务镜像
|
||||
└── vite.config.ts
|
||||
```
|
||||
|
||||
## 运行方式
|
||||
|
||||
这个仓库不是 SSR 应用。
|
||||
|
||||
- `src/react-app` 使用 `BrowserRouter` 管理前端路由
|
||||
- `src/server/app.ts` 提供 `/api/*` 和 `/internal/*` 接口
|
||||
- `src/index.ts` 在生产环境注册 Bun 内置 cron;Cron 只向 NATS WorkQueue 发布后台 command
|
||||
- 前端 SPA 独立部署到 Cloudflare Pages;后端 Docker 不再服务 `index.html` 或静态资源
|
||||
|
||||
当前路由约定:
|
||||
|
||||
- `/api/*`:游戏与后台 API
|
||||
- `/api/auth/*`:Better Auth
|
||||
- `/internal/cron/*`:内部定时任务接口
|
||||
- `/api/health-check`:健康检查
|
||||
- 其余如 `/login`、`/game`、`/admin`:Cloudflare Pages 上的前端 SPA 路由
|
||||
|
||||
## 环境要求
|
||||
|
||||
- `Bun 1.3+`
|
||||
- `PostgreSQL`
|
||||
- `Redis`:在线对局、邀请、截止时间、恢复索引和 API 部分能力的权威存储
|
||||
- `NATS`:进程启动硬依赖;JetStream 承载领域事件、异步投影、后台 command、战斗演算指针、终态清理和回放归档,Core 只承载可丢失的跨实例实时提示
|
||||
|
||||
说明:
|
||||
|
||||
- 仓库脚本默认围绕 `bun` / `bunx` 编写,不建议继续沿用旧的 `npm + Next.js` 使用方式
|
||||
- 开发模式默认端口是 `5173`
|
||||
- 构建后服务默认端口是 `3000`
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
bun install
|
||||
cp .env.example .env.local
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
### 启动时必需
|
||||
|
||||
这些变量缺失时,服务会在启动阶段或鉴权初始化阶段直接报错:
|
||||
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `DATABASE_URL` | PostgreSQL 连接串 |
|
||||
| `BETTER_AUTH_SECRET` | Better Auth 密钥 |
|
||||
| `BETTER_AUTH_URL` | Better Auth 后端对外基准地址;生产填 API 域名,如 `https://api.example.com` |
|
||||
| `NATS_SERVERS` | NATS 服务地址,多个地址使用逗号分隔 |
|
||||
| `NATS_USER` / `NATS_PASSWORD` | NATS 应用用户凭据 |
|
||||
|
||||
实时战斗由 Bun/Hono 主服务直接承载;生产环境还必须配置:
|
||||
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `REDIS_URL` | 在线对局唯一权威状态、邀请、凭据、截止时间与恢复索引 |
|
||||
| `NATS_SERVERS` / `NATS_USER` / `NATS_PASSWORD` | 战斗演算、终态清理和回放归档使用的 JetStream,以及跨实例状态提示使用的 NATS Core |
|
||||
|
||||
客户端通过已认证的 session API 获取 60 秒有效的一次性 WebSocket ticket,不能自行声明玩家身份。实时战斗的数据边界固定为:`battle-v5` 只做确定性规则解析;Bun 主服务负责协议、调度和广播;进行中的权威状态、选招、锁定、邀请、演出战报和回放素材只在 Redis。主服务通过 JetStream 小型指针任务分配统一演算和终态清理;回放归档 consumer 再从 Redis 组装 `BattleReplayV1`,异步、幂等写入 `wanjiedaoyou_battle_replay_archives`。NATS 消息不承载完整 battle save 或战报;玩家 command 链路不查询或写入 PostgreSQL,也不使用 Redis Stream。
|
||||
|
||||
### 建议同时配置
|
||||
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `REDIS_URL` | Redis 连接串;缺失时相关功能会在运行时失败 |
|
||||
| `API_IP_RATE_LIMIT_WINDOW_SECONDS` | `/api/*` 全局 IP 令牌桶补充周期秒数;默认 `60` |
|
||||
| `API_IP_RATE_LIMIT_MAX_REQUESTS` | `/api/*` 同 IP 令牌桶容量和每周期补充 token 数;默认 `300` |
|
||||
| `PUBLIC_WEB_ORIGINS` | 允许访问 API 的前端 origin,逗号分隔,如 `https://app.example.com,http://localhost:5173` |
|
||||
| `BETTER_AUTH_COOKIE_DOMAIN` | 可选;同站子域部署时可填 `.example.com` 启用跨子域 cookie |
|
||||
| `ADMIN_EMAILS` | 管理员邮箱白名单,逗号分隔 |
|
||||
| `ADMIN_USER_IDS` | Better Auth 管理员用户 ID 白名单,逗号分隔;账号管理工具必须配置 |
|
||||
|
||||
### 生产 cron 必需
|
||||
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `CRON_SECRET` | 保护 `/internal/cron/*` 接口的 Bearer 密钥;生产环境必须配置,调度器调用时也要携带它 |
|
||||
|
||||
### 登录 / 注册相关
|
||||
|
||||
当前鉴权中,以下接口会强制要求 ALTCHA PoW payload:
|
||||
|
||||
- `/api/auth/sign-in/email`
|
||||
- `/api/auth/sign-up/email`
|
||||
- `/api/auth/request-password-reset`
|
||||
- `/api/auth/email-otp/send-verification-otp`
|
||||
|
||||
前端通过 `/api/captcha/challenge` 获取带场景和过期时间的 challenge,完成 PoW 后把 payload 发送给认证接口。服务端会验证签名、场景和有效期,并通过 Redis 原子记录 challenge 的单次消费状态,防止重放。
|
||||
|
||||
| 变量 | 说明 |
|
||||
| --- | --- |
|
||||
| `VITE_API_BASE_URL` | 前端构建时注入的后端 API 基地址,如 `https://api.example.com` |
|
||||
| `ALTCHA_HMAC_SECRET` | 服务端签发和验证 ALTCHA challenge 的独立 HMAC 密钥;生产环境必须配置 |
|
||||
|
||||
ALTCHA 不需要前端 site key。认证 CAPTCHA 启用时 Redis 也是强依赖;Redis 不可用时,受保护的认证请求会失败关闭,避免 challenge 被重复使用。
|
||||
|
||||
### 邮件能力
|
||||
|
||||
邮箱验证码、密码注册验证邮件、重置密码邮件、后台邮件广播都会使用SMTP。密码注册必须完成邮箱验证后才能登录;验证链接完成后会自动登录:
|
||||
|
||||
| 变量 | 说明 |
|
||||
| ----------------------------------------- | ------------- |
|
||||
| `SMTP_HOST` / `SMTP_PORT` / `SMTP_SECURE` | SMTP 连接配置 |
|
||||
| `SMTP_USER` / `SMTP_PASS` | SMTP 认证信息 |
|
||||
| `MAIL_FROM` | 发件人 |
|
||||
|
||||
### AI 能力
|
||||
|
||||
AI 相关功能只调用 DeepSeek 官方 API。服务端默认配置:
|
||||
|
||||
- `DEEPSEEK_API_KEY`:服务端 DeepSeek API Key
|
||||
- `DEEPSEEK_MODEL`:服务端默认模型,未配置时使用 `deepseek-chat`
|
||||
|
||||
玩家也可以在游戏设置中保存自己的 DeepSeek API Key 与模型。BYOK 配置只保存在当前浏览器;请求携带的配置不完整或格式无效时,服务端会返回 400,不会静默消耗服务器额度。
|
||||
|
||||
## 数据库初始化
|
||||
|
||||
首次启动通常要做两件事:
|
||||
|
||||
1. 应用业务表迁移
|
||||
2. Better Auth 表迁移
|
||||
|
||||
```bash
|
||||
bunx drizzle-kit migrate
|
||||
bun run auth:migrate
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 运行这些命令前,请先确保 `DATABASE_URL`、`BETTER_AUTH_SECRET`、`BETTER_AUTH_URL` 已在当前进程环境中可见
|
||||
- `drizzle/` 目录下已经存在业务表迁移文件
|
||||
- `drizzle/` 只管理 `wanjiedaoyou_*` 业务表
|
||||
- `drizzle-auth/` 只管理固定 `better_auth` schema,并使用独立迁移历史表
|
||||
- `bun run auth:migrate` 使用 `drizzle.auth.config.ts` 执行认证迁移
|
||||
- `bun run auth:generate` 用于认证 Drizzle schema 变更后生成迁移,不是每次启动都要执行
|
||||
- 升级部署时先执行 `bun run auth:migrate` 建立认证基线,再部署使用共享 Bun SQL 连接池的新版本
|
||||
|
||||
## 本地开发
|
||||
|
||||
1. 准备好 `.env.local`
|
||||
2. 确保数据库、Redis 和 NATS JetStream 可连接
|
||||
3. 执行迁移
|
||||
4. 启动开发服务器
|
||||
|
||||
```bash
|
||||
docker compose -f scripts/docker-compose.nats.yml up -d
|
||||
bun run dev
|
||||
```
|
||||
|
||||
本地 NATS 容器监听 `4222`,监控端口为 `8222`,开发凭据与 `.env.example` 一致。持久数据保存在 Docker volume `nats-data` 中。
|
||||
|
||||
生产硬切顺序、Stream/consumer、DLQ 和故障检查参见 [nats-domain-events.md](nats-domain-events.md)。
|
||||
|
||||
访问:
|
||||
|
||||
- 前端页面:`http://localhost:5173`
|
||||
- 健康检查:`http://localhost:5173/api/health-check`
|
||||
|
||||
`bun run dev` 会同时启动 Vite 前端和 Bun/Hono 主服务;Vite 将 `/api`、`/internal` 与 WebSocket 升级代理到 Bun 服务。
|
||||
|
||||
本地 NATS 使用 JetStream 文件卷保存消息和 durable consumer 的投递位点。启动时发现历史消息是预期行为;回放归档和事务消息会在 PostgreSQL 可用后继续消费。若 PostgreSQL 暂时不可用,事务消息恢复器会以 5 秒至 60 秒退避重试,避免连接超时期间持续打满连接池;不应通过删除 NATS 数据卷来规避数据库故障。
|
||||
|
||||
## 构建与运行
|
||||
|
||||
| 命令 | 作用 |
|
||||
| --- | --- |
|
||||
| `bun run dev` | 启动 Vite 与 Bun/Hono 主服务 |
|
||||
| `bun run build` | 依次构建前端与服务端 |
|
||||
| `bun run build:client` | 构建 Cloudflare Pages 使用的前端 SPA |
|
||||
| `bun run build:server` | 构建 Docker 使用的 Bun/Hono 后端 |
|
||||
| `bun run preview` | 先构建,再运行 `dist/index.js` |
|
||||
| `bun run start` | 直接运行已构建产物 |
|
||||
| `bun run lint` | ESLint 检查 |
|
||||
| `bun run test` | Vitest |
|
||||
| `bun run battle:smoke` | 实时战斗 2v2/4v4、超时默认出招、协议、负载与 Worker 故障注入验收 |
|
||||
| `REDIS_URL=redis://127.0.0.1:6379/15 bun run battle:e2e:redis` | 使用本机隔离 Redis DB 验收重启、多实例、幂等和归档 staging |
|
||||
| `bun run auth:generate` | 生成 `better_auth` Drizzle 迁移 |
|
||||
| `bun run auth:migrate` | 执行 `better_auth` 独立迁移流 |
|
||||
|
||||
构建产物:
|
||||
|
||||
- `build:client` 产出前端 SPA
|
||||
- `build:server` 产出 Bun 运行的 Hono 服务入口 `dist/index.js`
|
||||
|
||||
## Docker
|
||||
|
||||
React SPA 继续独立部署到 Cloudflare Pages,不进入后端镜像。`app`(`3000`)使用 Bun,同时承载 Hono API 与实时战斗 WebSocket;`battle-v5` 仍是无框架依赖的纯战斗引擎。PostgreSQL 回放归档由应用侧 NATS consumer 完成。
|
||||
|
||||
本地构建镜像:
|
||||
|
||||
```bash
|
||||
docker build -t daoyou-app:local -f docker/Dockerfile.app .
|
||||
```
|
||||
|
||||
运行镜像:
|
||||
|
||||
```bash
|
||||
docker run --rm -p 3000:3000 \
|
||||
--env-file /path/to/.env.production \
|
||||
daoyou-app:local
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- `VITE_API_BASE_URL` 是前端 Pages 构建期变量,不进入后端 Docker 镜像
|
||||
- 服务运行时环境变量通过 shell、容器环境或 `--env-file` 注入
|
||||
|
||||
## 仓库内现成部署脚本
|
||||
|
||||
### Hono API 蓝绿发布
|
||||
|
||||
```bash
|
||||
APP_IMAGE=swkzymlyy/daoyou-app:<version> \
|
||||
ENV_FILE=/root/daoyou/.env.production \
|
||||
./scripts/blue-green-app.sh
|
||||
```
|
||||
|
||||
这个脚本会:
|
||||
|
||||
- 在 `daoyou-app-blue` / `daoyou-app-green` 间部署闲置颜色
|
||||
- 同时验证 Docker health 与宿主机 `/api/health-check`
|
||||
- 通过 `nginx -t` 后原子切换 OpenResty upstream
|
||||
- 短暂 drain 后停止旧颜色容器
|
||||
|
||||
生产 Compose 只定义 `app-blue` 和 `app-green`;实时战斗与 API 随同一 Bun 主服务蓝绿发布; `blue-green-app.sh` 通过 Compose 启动闲置 app profile 并切换 OpenResty。React SPA 仍由 Cloudflare Pages 独立部署。
|
||||
|
||||
## 生产 cron 配置方式
|
||||
|
||||
当前仓库默认采用两层设计:
|
||||
|
||||
- 生产环境中 `src/index.ts` 注册 Bun 内置 cron,Cron 只发布 JetStream command,durable Worker consumer 执行 job runner
|
||||
- `/internal/cron/*` 仍然保留,便于手动触发、联调,或后续切回外部调度器
|
||||
|
||||
- `GET /internal/cron/auction-expire`
|
||||
- `GET /internal/cron/bet-battle-expire`
|
||||
- `GET /internal/cron/rank-rewards`
|
||||
- `GET /internal/cron/market-refresh`
|
||||
- `GET /internal/cron/tower-enemy-sets`
|
||||
- `GET /internal/cron/player-state-events-cleanup`
|
||||
- `GET /internal/cron/expired-data-cleanup`
|
||||
|
||||
当前内置调度频率:
|
||||
|
||||
- `auction-expire`:每 2 分钟
|
||||
- `bet-battle-expire`:每 2 分钟
|
||||
- `rank-rewards`:每天 `00:00 Asia/Shanghai`
|
||||
- `market-refresh`:每 5 分钟
|
||||
- `tower-enemy-sets`:每小时
|
||||
- `player-state-events-cleanup`:每天 `02:30 Asia/Shanghai`
|
||||
- `expired-data-cleanup`:每天 `02:45 Asia/Shanghai`
|
||||
|
||||
说明:
|
||||
|
||||
- Bun 内置 cron 仍运行在 Web 进程内,但实际任务已与调度回调解耦;发布成功的 command 由 JetStream 持久化并可跨应用重启继续执行
|
||||
- Bun 的 cron 表达式按 `UTC` 解释,所以 `rank-rewards` 在代码里配置为 `0 16 * * *`,对应北京时间次日 `00:00`
|
||||
- 内置调度不直接调用 job runner,也不走 HTTP;它发布 `daoyou.command.cron.>` command
|
||||
- `/internal/cron/*` 接口继续要求 `Authorization: Bearer ${CRON_SECRET}`,适合人工补跑或外部调度
|
||||
- 这些任务内部带 Redis 分布式锁与幂等保护,重复触发会返回 `skipped`
|
||||
|
||||
如果你想改回外部 HTTP 调度,可使用:
|
||||
|
||||
```cron
|
||||
*/2 * * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/auction-expire
|
||||
*/2 * * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/bet-battle-expire
|
||||
0 0 * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/rank-rewards
|
||||
*/5 * * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/market-refresh
|
||||
0 * * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/tower-enemy-sets
|
||||
30 2 * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/player-state-events-cleanup
|
||||
45 2 * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/expired-data-cleanup
|
||||
```
|
||||
|
||||
## CI / 镜像发布
|
||||
|
||||
当前仓库的 [`.github/workflows/deploy.yml`](../.github/workflows/deploy.yml) 会在 `master` 分支推送时:
|
||||
|
||||
- 构建 Docker 镜像
|
||||
- 推送到 Docker Hub
|
||||
|
||||
## 架构原则
|
||||
|
||||
- 引擎层(`src/shared/engine`)完全独立于 UI 和框架
|
||||
- 业务逻辑放在 Service 层
|
||||
- 数据访问使用 Repository 模式
|
||||
@@ -1,58 +0,0 @@
|
||||
# 红尘剑宗(`lingxiao`)V4.2 平衡实测
|
||||
|
||||
## 测试口径
|
||||
|
||||
- 使用 battle-v5 实际投影、实际技能蓝耗、心法常驻属性与 AI 选招。
|
||||
- 筑基初期、金丹圆满、化神圆满的心法等级分别为 25、60、100。
|
||||
- 每个组合固定种子运行 100 局,并交替先手;无装备和灵根优势。
|
||||
- 另以 1 级、180 级各 5 局执行边界冒烟。
|
||||
|
||||
## V4.2 机制基线
|
||||
|
||||
- 神通固定蓝耗,不再随境界变化。
|
||||
- 神通伤害、护盾、治疗、状态强度和显式声明的持续时间由所属心法统一投影。
|
||||
- 《一剑破妄》的驱散数量随《观微剑意》成长档由 1 个提高至 7 个,最终配置只保留普通整数。
|
||||
- 剑痕与裂甲均为 3 层、3–6 回合状态;剑痕逐层增伤,裂甲逐层降低物防。
|
||||
- 快剑每连续两次自身行动未造成直接伤害才衰减 1 点剑意;重剑以裂甲、护盾破裂和后发重击组织循环。
|
||||
- 回燕代表构筑使用留痕,装配《剑荡山河》《藏锋听雷》《踏雪无痕》《此剑平生》;极势在裂甲不足 2 层时先补裂甲。
|
||||
- 普通敌对神通的效果链统一要求命中;基础与重剑《藏锋听雷》改为自身蓄势,施展时登记必中、不可打断的后发《听雷》。
|
||||
- 宗门节点卡片通过运行时 resolver 展示当前心法等级的实际数值,静态定义只保留机制摘要。
|
||||
|
||||
## 编译边界收尾
|
||||
|
||||
- 心法成长声明只存在于 sect authoring 阶段,最终投影会剥离全部宗门元数据,battle-v5 的 `BuffConfig` 只保留已结算数值。
|
||||
- 编译顺序固定为“基础神通 → 流派变体 → 经脉节点 → 一次成长投影 → 展示分析”,不再用 `methodGrowthApplied` 保存编译阶段状态。
|
||||
- 成长策略对 Effect 联合类型执行穷尽分类;新增 Effect 时必须显式决定其倍率、状态或固定数值语义。
|
||||
- 分层属性修改器使用稳定标识,仅在层数变化时重挂载依赖层数的 modifier,避免随机标识改变固定种子结果。
|
||||
|
||||
## 三境界代表对局
|
||||
|
||||
格式为“快剑胜率 / 平均回合 / 平局率”。
|
||||
|
||||
| 代表对局 | 筑基初期(25级) | 金丹圆满(60级) | 化神圆满(100级) |
|
||||
| --- | --: | --: | --: |
|
||||
| 追风快剑 vs 后发重剑 | 52% / 8.25 / 0% | 79% / 5.58 / 0% | 20% / 4.36 / 0% |
|
||||
| 连势快剑 vs 极势重剑 | 17% / 7.56 / 0% | 29% / 5.09 / 0% | 88% / 4.72 / 0% |
|
||||
| 回燕快剑 vs 守山重剑 | 0% / 14.69 / 0% | 1% / 11.89 / 0% | 0% / 7.25 / 0% |
|
||||
|
||||
3×3 交叉矩阵实测范围:
|
||||
|
||||
| 境界 | 快剑胜率范围 | 平均回合范围 | 平局率 |
|
||||
| -------- | -----------: | -----------: | -----: |
|
||||
| 筑基初期 | 0%–97% | 7.07–14.69 | 0% |
|
||||
| 金丹圆满 | 1%–96% | 4.64–11.89 | 0% |
|
||||
| 化神圆满 | 0%–88% | 4.06–7.25 | 0% |
|
||||
|
||||
## 门禁结论
|
||||
|
||||
成长、驱散数量、蓝耗、命中语义、状态覆盖、被动嵌套成长、动态节点文案、authoring 元数据剥离、36 节点运行时矩阵和 729 套组合编译均已进入自动化测试。严格 3×3 门禁也已改为默认执行。
|
||||
|
||||
当前固定种子平衡门禁仍未通过:只有筑基“追风 vs 后发”满足代表胜率与回合要求;其余代表对局及多数交叉对局越界,筑基和化神“回燕 vs 守山”仍为 0% 胜率。按本轮“保留九个流派基础神通倍率、只围绕指定身份节点以 5% 粒度校准”的边界,现有早期流派装配已经形成 0%–97% 的克制跨度,不能标记为平衡验收完成。继续收敛需要放开至少一项边界:代表装配/属性分配、流派基础神通数值,或接受更宽的交叉胜率区间。
|
||||
|
||||
严格门禁命令:
|
||||
|
||||
```bash
|
||||
bunx vitest run src/shared/engine/sect/content/lingxiao/tests/balanceSimulation.test.ts
|
||||
```
|
||||
|
||||
该命令当前会按上述实测差距失败,避免未达标数据被误标为平衡验收完成。
|
||||
@@ -1,31 +0,0 @@
|
||||
# Player State Realtime
|
||||
|
||||
Date: 2026-07-06
|
||||
|
||||
## Current Transport
|
||||
|
||||
- Main character state now initializes through `GET /api/player/state`.
|
||||
- Live player-state updates use the `player-state` channel on `GET /api/realtime` WebSocket.
|
||||
- `GET /api/player/state/events?after=` is still active as JSON backfill for WebSocket reconnect/version gaps. It is not an SSE stream.
|
||||
- Retreat/yield `text/event-stream` code is action story streaming, not global player-state synchronization.
|
||||
|
||||
## Current Code
|
||||
|
||||
- `src/server/routes/player.router.ts`: `GET /api/player/state` is the authoritative snapshot endpoint.
|
||||
- `src/server/routes/player.router.ts`: `GET /api/player/state/events?after=` is required for WebSocket gap recovery.
|
||||
- `src/server/lib/services/playerStateBroadcaster.ts` publishes committed state events to live WebSocket subscribers.
|
||||
- `src/react-app/lib/player-state/store.ts` applies WebSocket events and uses `/state/events` only as reconnect backfill.
|
||||
- `src/server/routes/api/realtime.router.ts` owns the WebSocket connection, heartbeat, channel subscription, and per-user/cultivator/IP connection caps.
|
||||
- `src/shared/contracts/realtime.ts` defines the active realtime channels: `player-state` and `world-chat`.
|
||||
|
||||
## Removed Legacy Paths
|
||||
|
||||
- `GET /api/player/active` has been removed. Use `GET /api/player/state`.
|
||||
- Legacy `/api/player/active` contract types have been removed from `src/shared/contracts/player.ts`.
|
||||
- The obsolete SSE redesign document has been removed. Do not use old SSE examples as current architecture.
|
||||
|
||||
## Migration Notes
|
||||
|
||||
- Keep `/api/player/state/events?after=` unless WebSocket resume tokens or replay-on-connect replace the current HTTP backfill path.
|
||||
- Do not reintroduce `EventSource` or SSE for global player-state synchronization.
|
||||
- New player-state mutations should go through `commitPlayerStateMutation()` so API responses and WebSocket subscribers receive the same versioned events.
|
||||
@@ -1,110 +0,0 @@
|
||||
# PostgreSQL 死锁与锁优化方案
|
||||
|
||||
## 目标
|
||||
|
||||
解决生产环境偶发的 PostgreSQL 死锁、连接池耗尽和全站请求卡住问题。修复范围只覆盖锁、事务边界和必要的可观测性,不借机扩展业务模型。
|
||||
|
||||
## 已确认的主要风险
|
||||
|
||||
### 事务内重新申请数据库连接
|
||||
|
||||
服务进入事务后,如果下层 Repository 或 Service 忽略传入的 `DbTransaction`,再次通过全局 executor 发起查询,就会额外占用连接。并发请求较多时,外层事务占住连接等待内层查询,而内层查询又在等待连接池,最终形成连接池自锁。
|
||||
|
||||
处理原则:
|
||||
|
||||
- 写链路显式传递 `DbTransaction` / `DbExecutor`。
|
||||
- 事务内禁止重新获取全局 executor。
|
||||
- 查询、校验和写入尽量复用同一个事务 executor。
|
||||
|
||||
### 在数据库事务中等待外部系统
|
||||
|
||||
LLM、Redis、世界广播等外部调用耗时不可控。如果在事务内执行,会延长行锁和连接占用时间。
|
||||
|
||||
处理原则:
|
||||
|
||||
- LLM 推演、Redis 读取等放在事务开始前。
|
||||
- 数据库事务只执行必要的条件校验和写入。
|
||||
- Redis 缓存、广播等副作用在提交后执行。
|
||||
- 提交后的副作用失败不回滚已经完成的数据库事务。
|
||||
|
||||
### 多资源写入顺序不稳定
|
||||
|
||||
拍卖、赌战、好友等涉及多个玩家或多条记录时,如果并发事务按不同顺序获取锁,容易形成 PostgreSQL 死锁。
|
||||
|
||||
处理原则:
|
||||
|
||||
- 多个 ID 在进入数据库写入前排序。
|
||||
- Repository 按稳定顺序读取和更新。
|
||||
- 资源扣减使用带余额或版本条件的原子更新。
|
||||
- 对 PostgreSQL `40P01`、`40001`、`55P03` 做有限次数、短退避重试。
|
||||
|
||||
## Redis 与 PostgreSQL 的职责边界
|
||||
|
||||
PostgreSQL 保存不可丢失、需要审计或必须参与原子交易的最终状态,例如角色、库存、货币、拍卖成交和战斗结算。
|
||||
|
||||
Redis 保存可重建、带 TTL 或只用于并发协调的状态,例如:
|
||||
|
||||
- 排行榜及每日挑战次数
|
||||
- 造物待确认结果
|
||||
- 命格重塑临时会话
|
||||
- 坊市本轮个人购买标记
|
||||
- 分布式锁
|
||||
- 周期缓存与冷却状态
|
||||
|
||||
本次明确不新增以下 PostgreSQL 持久化:
|
||||
|
||||
- 排行榜状态表和挑战次数表
|
||||
- 排行奖励快照、明细和结算表
|
||||
- 临时玩家操作会话表
|
||||
- 坊市购买账本
|
||||
|
||||
Redis 临时数据过期或丢失时,接受对应临时流程重置;不为此引入双写、回填或补偿框架。
|
||||
|
||||
## 保留的修复
|
||||
|
||||
- PostgreSQL 连接池连接、空闲、语句、锁和空闲事务超时。
|
||||
- 连接池等待数与慢事务、失败事务日志。
|
||||
- 统一 Redis 锁实现、稳定多 key 排序、租约续期和租约失效检查。
|
||||
- 玩家状态写入的事务 executor 传播。
|
||||
- Qi、拍卖、赌战、炼丹、造物、地下城等写链路的条件更新和稳定锁顺序。
|
||||
- LLM、Redis 与广播移出数据库事务。
|
||||
- 活跃角色唯一约束,防止同一用户出现多个活跃角色。
|
||||
|
||||
## 不采用的扩展
|
||||
|
||||
- 不为了幂等或临时状态新增业务账本。
|
||||
- 不把 Redis 数据复制到 PostgreSQL 作为“权威备份”。
|
||||
- 不给只读接口统一套玩家级分布式锁。
|
||||
- 不增加读取时自动恢复、跨存储双写修复和多阶段补偿流程。
|
||||
- 不为无法稳定复现的外部并发时序堆叠架构测试。
|
||||
|
||||
## 生产验证
|
||||
|
||||
发布后重点观察:
|
||||
|
||||
- PostgreSQL `deadlock detected`、`lock timeout`、`statement timeout`
|
||||
- pool waiting 是否持续大于 0
|
||||
- 慢事务持续时间、重试次数和 PostgreSQL 错误码
|
||||
- Redis 锁竞争、续期失败和租约丢失
|
||||
- 请求 409、429、503 的变化
|
||||
|
||||
若再次发生卡死,应优先收集:
|
||||
|
||||
```sql
|
||||
select
|
||||
pid,
|
||||
usename,
|
||||
application_name,
|
||||
state,
|
||||
wait_event_type,
|
||||
wait_event,
|
||||
xact_start,
|
||||
query_start,
|
||||
pg_blocking_pids(pid) as blocking_pids,
|
||||
query
|
||||
from pg_stat_activity
|
||||
where datname = current_database()
|
||||
order by xact_start nulls last, query_start;
|
||||
```
|
||||
|
||||
同时保存 PostgreSQL deadlock 日志中的完整 wait graph,用具体 SQL 和调用链定位剩余的锁顺序问题。
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,605 +0,0 @@
|
||||
# 幽都 × battle-v5 机制实现分析
|
||||
|
||||
> 配套设计:`docs/youdu-sect-design.md`
|
||||
>
|
||||
> 本文是 2026-07-22 旧 `BattleEngineV5` 1v1 基线上的历史实现分析,用于追溯幽都通用原语与内容层取舍,不再定义当前战斗主流程。旧引擎及其 1v1 限制已经删除;当前生产基线为 `Team/Roster + BattleRuntime + Checkpoint + BattleRoundResolver`。涉及多目标、行动、胜负、回放与运行时接口时必须以当前代码和 `docs/sect-authoring-guide.md` 为准。
|
||||
>
|
||||
> 分析基线:2026-07-22 的 battle-v5 能力审计。`controlHitBonus`、`statusVisibility` 与 `stackPriority` 等既有通用能力按可复用接口计入;实施时仍须以当前代码和测试复核,不得复制接口或覆盖其他宗门的实现。
|
||||
>
|
||||
> `docs/youdu-sect-design.md` 已按本文结论收敛。下文“原设计”仅指收敛前提案;实施时以主设计稿中的玩家规则和本文的引擎依据共同验收。
|
||||
|
||||
## 1. 结论
|
||||
|
||||
幽都可以建立在 battle-v5 现有的 AbilityConfig、Buff、Listener、CombatResource 和 Effect 链上,不需要推翻战斗架构,但不能把设计稿逐字直接翻译成现有配置。
|
||||
|
||||
推荐边界如下:
|
||||
|
||||
- 保留:蚀魂五层、非线性层级削弱、失魂、归窍、受治疗削弱、魂火、首次解控、术伤与魂伤混合技能、四层终结。
|
||||
- 做通用小扩展:分层状态变更事件、一次添加多层、按层取值、逐层驱散、受治疗削弱、不可暴击/不可吸血伤害、主动技能必然命中;终结技的四层/五层差异复用既有 effect plan,不扩展伤害模型。
|
||||
- 改为近似效果:魂伤使用现有真实伤害管道,只绕过物法防御,不绕过统一百分比减伤;混合技能产生两次伤害请求;忘川 DOT 使用实时法术攻击而非施加时快照。
|
||||
- 当时的 V1 删除项:三目标忘川、向其他敌人扩散蚀魂、百鬼同哭影响全体。当时生产主流程严格 1v1,因此没有为单个宗门提前扩建队伍战斗;这条历史降级理由不再代表当前 Team/Roster 引擎能力。
|
||||
- 暂不实现:设计稿已经标记为暂缓的动画与声音表现。
|
||||
|
||||
这套方案新增的是通用原语,不让 battle-v5 认识“幽都”“蚀魂”“忘川”或“魂火”。
|
||||
|
||||
### 1.1 引擎变更准入
|
||||
|
||||
后续实施先按变更等级判断,不以“能做出来”作为接入理由:
|
||||
|
||||
| 等级 | 定义 | 幽都 V1 处理 |
|
||||
| --- | --- | --- |
|
||||
| 内容配置 | 复用 Ability、Buff、Listener、Effect、CombatResource 和标签组合,不改变通用语义 | 优先采用,幽都专属 ID 只允许出现在宗门内容目录 |
|
||||
| 通用原语扩展 | 为现有模型补充可复用参数、事件或属性;默认值保持旧内容行为不变 | 允许,但必须使用中性命名、无宗门特判,并先有不引用幽都 ID 的原子测试 |
|
||||
| 架构级改造 | 改变单位模型、行动流、目标选择、胜负、持久化或伤害主管道 | V1 禁止;用近似效果替代,等待对应系统统一升级 |
|
||||
|
||||
每项机制的实施顺序固定为:检查现有原语 → 能组合则只写内容配置 → 确有通用缺口才扩展 → 无法保持兼容则采用本文降级方案。禁止在 `battle-v5` 中判断 `sectId === 'youdu'`、幽都能力 ID 或幽都状态 ID。
|
||||
|
||||
## 2. 实施前 battle-v5 能力基线
|
||||
|
||||
本节记录的是判断“直接复用、通用扩展或降级”的审计基线。若表中的缺口已经在当前分支由通用实现补齐,应把它视为复用与回归验证项,不得再次新增平行字段或专属接口。
|
||||
|
||||
| 领域 | 当前事实 | 对幽都的影响 |
|
||||
| --- | --- | --- |
|
||||
| 战斗人数(历史基线) | 当时的 `BattleEngineV5` 只持有 `player` 与 `opponent`,目标选择系统未接入主行动流 | 此限制已随旧引擎删除;当前多单位能力必须按 Team/Roster 代码重新验收 |
|
||||
| 技能命中 | 一次 `SkillCastEvent` 决定整条主动效果链是否执行 | 术伤、魂伤、加层和控制天然共用一次命中 |
|
||||
| 多段伤害 | 每个 `damage` effect 分别发布 `DamageRequestEvent` | 混合伤害可以实现,但术伤与魂伤是两个伤害包,不是一个混合包 |
|
||||
| 真实伤害 | `DamageType.TRUE` 不读取物防/法防,仍进入统一增减伤、境界、暴击、随机浮动和护盾管道 | 不能直接满足“无视一切减伤、不可暴击” |
|
||||
| Buff 层数 | `STACK_LAYER`、`maxLayers`、层数修改与刷新持续时间已存在 | 能承载蚀魂,但一次 Apply 默认只加 1 层,且没有层变更事件 |
|
||||
| 分层属性 | `scaleByLayer` 只支持线性倍率 | 无法直接表达 3%/5%/8%/12% 的非线性曲线 |
|
||||
| 驱散 | 普通驱散会移除整个 Buff | 无法直接表达“一次净化一层蚀魂” |
|
||||
| 持续时间 | Buff 在宿主每次行动后减 1;当次行动新施加的 Buff 由运行时标记阻止立即 tick | 与“按目标自身行动计 3 回合”一致,1 回合保护状态无需配置成 2 |
|
||||
| DOT | Buff 可监听 `ActionPreEvent` 造成伤害 | 忘川两次结算可直接实现,但默认读取实时施术属性 |
|
||||
| 治疗 | `HEAL_AMPLIFY` 只读取治疗者;所有气血恢复最终都会调用 `Unit.heal()` | 没有受治疗削弱,但适合在 `Unit.heal()` 统一补齐 |
|
||||
| 控制 | `BuffType.CONTROL` 会读取控制命中/抗性;`NO_ACTION`、`NO_SKILL` 已接入行动流 | 失魂与镇魂钉可复用现有控制体系 |
|
||||
| 控制加值 | 当前工作区已有 `ApplyBuffParams.controlHitBonus` | “幽都铁律”可直接使用,无需临时修改施术者属性 |
|
||||
| 监听预算 | 支持每行动、来源行动、回合、战斗和 Buff 生命周期预算 | 魂火每行动一次、首次解控和节点每场一次都可实现 |
|
||||
| 战斗资源 | 支持定义、增减、全部消费、溢出和事件 | 魂火无需新资源系统 |
|
||||
| 条件与计划 | 支持 Buff 层数、资源、血量等条件;effect plan 在准备施法时快照 | 镇魂按施法前层数决定 1/2 回合可直接实现 |
|
||||
| 技能命中策略 | 引擎事件支持 `guaranteed`,但普通 AbilityConfig 不能声明 | 《照影》必然命中需要一个很小的配置扩展 |
|
||||
|
||||
## 3. 已纳入主设计稿的 V1 契约
|
||||
|
||||
### 3.1 魂伤改用“标准真实伤害”
|
||||
|
||||
主设计稿已经收敛为:
|
||||
|
||||
> 魂伤属于真实伤害,忽略物理防御与法术防御;仍受统一百分比减伤、境界威压、伤害免疫、护盾与免死影响。魂伤不可暴击,也不能被吸血。
|
||||
|
||||
原因:
|
||||
|
||||
- 现有真实伤害已经稳定经过统一伤害、护盾、日志和免死管道。
|
||||
- 新增“绕过所有减伤”会要求每个 DamageRequest 继续区分哪些规则是普通减伤、哪些是规则级保护,很容易出现部分监听先减伤、部分监听后减伤的顺序漏洞。
|
||||
- 幽都已经同时拥有防御绕过、属性削弱、禁疗和控制,不需要再取消通用减伤这一层反制。
|
||||
|
||||
不要新增 `DamageType.SOUL`。魂伤用 `DamageType.TRUE`,并以幽都能力机制标签加 `damageType === TRUE` 共同识别。这样 battle-v5 不需要理解宗门专属伤害枚举。
|
||||
|
||||
### 3.2 混合技能允许两个伤害包
|
||||
|
||||
《夺魄》《镇魂》的术伤与魂伤建议按两个连续 `damage` effect 实现:
|
||||
|
||||
1. 术伤发布一次 `DamageRequestEvent(MAGICAL)`。
|
||||
2. 魂伤发布一次 `DamageRequestEvent(TRUE)`。
|
||||
3. 两者共享同一次命中;任一技能闪避时整条效果链不执行。
|
||||
4. 两个伤害参数不携带宗门标签或色调;日志聚合器按同一 ability span 使用标准中性伤害行,内容详情仍可把真实伤害通道称为“魂伤”。
|
||||
|
||||
V1 不实现“一个 DamageRequest 内同时具有法术与真实两种结算方式”。那会要求 `DamageComponent` 自己携带 damageType,并重写防御、减伤、暴击、免疫和日志归类,属于不必要的伤害管道重构。
|
||||
|
||||
主设计稿已经明确混合技能的监听边界:
|
||||
|
||||
- 每个伤害包可以分别触发现有通用受击监听。
|
||||
- 幽都自己的魂火、节点和每行动效果必须用 listener budget 保证只触发一次。
|
||||
- 多个监听器共享次数时使用通用 budget `group`;监听器 ID 仍保持唯一,作为调试与追踪标识。
|
||||
|
||||
### 3.3 忘川改为单体、实时取值
|
||||
|
||||
V1《忘川潮》:
|
||||
|
||||
- 目标由“最多 3 名敌人”改为敌方单体。
|
||||
- 持续 2 回合,每次目标行动前结算。
|
||||
- 每次结算读取施术者当时的法术攻击,不做施加时数值快照。
|
||||
|
||||
真实三目标战斗需要同时改造:
|
||||
|
||||
- 当时 `BattleEngineV5` 的 player/opponent 模型(已删除)
|
||||
- 行动排序和默认目标
|
||||
- AbilityContainer 的候选目标
|
||||
- TargetSelectionSystem 接入
|
||||
- 胜负判定
|
||||
- 战斗状态快照和日志聚合
|
||||
- 宗门自动战术的多目标上下文
|
||||
|
||||
这不是内容层扩展,本轮明确不做。
|
||||
|
||||
### 3.4 单目标替代条款
|
||||
|
||||
| 原设计 | V1 替代 |
|
||||
| --- | --- |
|
||||
| 《忘川潮》最多三目标 | 单体 |
|
||||
| `tide-spread` 分摊多个目标 | 改为 `tide-cycle`,优先维持忘川与 3 层蚀魂 |
|
||||
| `long-night` 同时维持两个目标 | 改为延后终结,维持单目标 4 层压力 |
|
||||
| 「渡口回声」向随机另一敌人加层 | 每回合第一次对至少 4 层目标结算忘川时,追加 `0.12 × 法攻`魂伤 |
|
||||
| 「百鬼同哭」给其余敌人加层 | 每场第一次触发失魂时,对该目标追加 `0.30 × 法攻`魂伤 |
|
||||
| 「末渡无舟」失去 15%当前法力 | 改为失去 10%最大法力,复用现有 `targetMaxMpRatio` |
|
||||
| 镇魄司命对照影目标绝对命中 | 改为自身对照影目标的魂伤每层额外提高 1%,最多 5% |
|
||||
| 「名落幽都」击杀返还 | 改为终结后目标气血低于 20%时获得 3 魂火并返还 2 回合冷却,每场一次 |
|
||||
|
||||
这些替代保留流派意图,同时避免队伍战斗、动态命中改写、当前法力比例和精确击杀归因四项额外扩展。
|
||||
|
||||
## 4. 需要补齐或复用的通用原语
|
||||
|
||||
以下字段名和事件语义是幽都的兼容契约。若当前 battle-v5 已有同名能力,直接复用;若尚无,才按本节做最小通用扩展。所有新增字段必须给出保持旧行为的默认值。
|
||||
|
||||
### 4.1 分层状态原语
|
||||
|
||||
这是幽都最重要、也最值得做成通用能力的一组扩展。
|
||||
|
||||
#### A. 一次施加多层
|
||||
|
||||
为 `ApplyBuffParams` 增加:
|
||||
|
||||
```ts
|
||||
layers?: number
|
||||
```
|
||||
|
||||
- 新状态以该层数入场。
|
||||
- 已有 `STACK_LAYER` 状态增加该层数,而不是固定增加 1。
|
||||
- 仍受 `maxLayers` 限制。
|
||||
- 省去在一个技能里重复写两次 `apply_buff`,也避免重复应用日志与阈值监听。
|
||||
|
||||
#### B. 层数变化事件
|
||||
|
||||
新增通用事件:
|
||||
|
||||
```ts
|
||||
interface BuffLayerChangedEvent extends CombatEvent {
|
||||
type: 'BuffLayerChangedEvent';
|
||||
target: Unit;
|
||||
buff: Buff;
|
||||
source?: Unit;
|
||||
ability?: Ability;
|
||||
previousLayer: number;
|
||||
currentLayer: number;
|
||||
delta: number;
|
||||
reason: 'apply' | 'stack' | 'effect' | 'dispel';
|
||||
}
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
- 只在层数实际变化时发布;5→5 的刷新不发布。
|
||||
- 初次施加按 0→初始层数发布。
|
||||
- `BuffContainer` 成为层变更的统一出口;`BuffLayerModifyEffect` 和逐层驱散都要透传来源与 ability。
|
||||
- Buff 监听器必须在初次 0→N 事件发布前完成挂载。
|
||||
|
||||
用途:
|
||||
|
||||
- 4→5 时触发失魂。
|
||||
- 实际加层时获取魂火,满层刷新不获取。
|
||||
- 归窍将 5 层钳制回 4 层。
|
||||
- 洗魂有价识别驱散掉层。
|
||||
- 战斗日志精确显示旧层数与新层数。
|
||||
|
||||
这是事件模型的自然补全,不是幽都专属钩子。
|
||||
|
||||
#### C. 非线性按层属性
|
||||
|
||||
为 `AttributeModifierConfig` 增加一种互斥配置:
|
||||
|
||||
```ts
|
||||
valueByLayer?: readonly number[]
|
||||
```
|
||||
|
||||
约定数组下标 0 对应 1 层,超过数组长度沿用末项。不能与 `scaleByLayer` 同时使用。
|
||||
|
||||
蚀魂可直接配置:
|
||||
|
||||
```ts
|
||||
ATK/MAGIC_ATK/DEF/MAGIC_DEF/SPEED:
|
||||
[-0.03, -0.05, -0.08, -0.12, -0.12]
|
||||
```
|
||||
|
||||
`DataDrivenBuff.onLayerChanged()` 已经会重新挂载线性层数 modifier,只需把取值策略从“value × layer”扩展为“按层取值”。
|
||||
|
||||
注意:降低 `SPEED` 会按现有派生公式间接影响物攻、物防、暴击和闪避,因此玩家实际面板中的攻击、防御变化会略大于表格名义值。这是当前五维属性架构的自然结果,不应为幽都新增独立“行动速度”属性。
|
||||
|
||||
#### D. 逐层驱散
|
||||
|
||||
为 `BuffConfig` 增加:
|
||||
|
||||
```ts
|
||||
dispelMode?: 'whole' | 'one_layer'
|
||||
```
|
||||
|
||||
缺省 `whole` 保持兼容。`one_layer` 且当前层数大于 1 时:
|
||||
|
||||
- 普通驱散只减 1 层。
|
||||
- 发布 `BuffLayerChangedEvent(reason: 'dispel')` 与成功的 `DispelEvent`。
|
||||
- 降到 0 时才真正发布 `BuffRemovedEvent`。
|
||||
- 明确写“净化全部层数”的能力继续通过 `buff_layer_modify clear` 完整移除。
|
||||
|
||||
不要把蚀魂拆成五个独立 Buff。那会把加层、持续时间、状态栏、阈值和终结技读取都变成五状态组合问题。
|
||||
|
||||
### 4.2 受治疗削弱
|
||||
|
||||
新增外部注入型二级属性:
|
||||
|
||||
```ts
|
||||
AttributeType.HEAL_RECEIVED_REDUCTION;
|
||||
```
|
||||
|
||||
- 底座为 0。
|
||||
- 所有状态以 `ModifierType.FIXED` 写入削弱值,modifier 相加后在 `[0, 1]` 截断;不能使用会乘在零底座上的 `ADD`。
|
||||
- 只影响气血恢复,不影响法力与护盾。
|
||||
|
||||
在 `Unit.heal(amount)` 统一应用:
|
||||
|
||||
```text
|
||||
requested = 治疗效果根据施术者 HEAL_AMPLIFY 计算后的值
|
||||
reduction = clamp(target.HEAL_RECEIVED_REDUCTION, 0, 1)
|
||||
received = round(requested × (1 - reduction))
|
||||
applied = min(缺失气血, received)
|
||||
```
|
||||
|
||||
这样可以自然覆盖当前所有气血恢复来源:
|
||||
|
||||
- `HealEffect`
|
||||
- `LifestealEffect`
|
||||
- `DamageMemoryEffect` 的治疗释放
|
||||
- `ResourceDrainEffect` 的气血恢复
|
||||
- 后续新增但正确调用 `Unit.heal()` 的恢复
|
||||
|
||||
免死继续调用 `setHp(..., 'death_prevent')`,不受禁疗影响。现有 `HealEvent.healAmount/appliedAmount` 已能分别表达请求值与实得值,无需新增一条 HealingSystem 管道。
|
||||
|
||||
各状态配置:
|
||||
|
||||
| 状态 | 受治疗削弱 |
|
||||
| ------ | -------------------------------------- |
|
||||
| 蚀魂 | `[0, 0.15, 0.30, 0.50, 1.00]` 按层取值 |
|
||||
| 忘川 | `+0.20` |
|
||||
| 不归 | `+0.80` |
|
||||
| 镇魂钉 | `+1.00` |
|
||||
|
||||
统一在 100% 封顶,符合设计稿的叠加规则。
|
||||
|
||||
### 4.3 伤害行为开关
|
||||
|
||||
为 `DamageParams` 和对应伤害事件增加并透传:
|
||||
|
||||
```ts
|
||||
canCrit?: boolean // 缺省 true
|
||||
canLifesteal?: boolean // 缺省 true
|
||||
```
|
||||
|
||||
- 魂伤配置 `false/false`。
|
||||
- `DamageSystem` 在 `canCrit === false` 时跳过暴击。
|
||||
- `DamageTakenEvent` 继续携带 `canLifesteal`,`LifestealEffect` 在 false 时退出。
|
||||
- 不修改现有其他真实伤害的默认行为。
|
||||
|
||||
不要全局规定所有 `DamageType.TRUE` 都不能暴击或吸血;那会无意改变现有造物、命格和其他宗门内容。
|
||||
|
||||
### 4.4 用固定 effect plan 表达四层/五层终结
|
||||
|
||||
不为 `DamageParams` 增加“读取任意 Buff 层数”的动态伤害分量。《魂兮不归》只允许在 4 层或 5 层施放,因此由幽都编译器预先计算两个固定系数:
|
||||
|
||||
- `finish-four`:`base + perLayer × 4`;
|
||||
- `finish-five`:`base + perLayer × 5`,优先级高于四层计划。
|
||||
|
||||
两个计划分别使用 `buff_layer_at_least` / `buff_layer_below` 选择,且都只包含一个标准 `damage` effect。《魂兮不归》按以下顺序执行:
|
||||
|
||||
1. 施法准备阶段选择四层或五层计划。
|
||||
2. 计划发布一个真实伤害请求。
|
||||
3. completion effect 清除蚀魂;“不归亦不散”则改为设置到 2 层。
|
||||
4. 施加不归。
|
||||
|
||||
这样既不会产生四到五个独立伤害包,也不要求 DamageEffect 反向读取宗门 Buff。基础系数或逐层系数被节点修改时,编译器重新生成两个固定计划。
|
||||
|
||||
伤害应在清层前结算,使《照影》仍能按终结前的蚀魂层数放大本次伤害。
|
||||
|
||||
### 4.5 主动技能命中策略
|
||||
|
||||
为 `AbilityConfig`、`ActiveSkillConfig` 和 `SectAbilityFactory` 增加:
|
||||
|
||||
```ts
|
||||
hitPolicy?: 'normal' | 'guaranteed'
|
||||
```
|
||||
|
||||
由 `AbilityContainer._prepareCast()` 写入 `SkillPreCastEvent`。现有 DamageSystem 已支持 `guaranteed`,后续无需改动命中公式。
|
||||
|
||||
V1 仅《照影》本身使用必然命中。照影将目标 `EVASION_RATE` 以 OVERRIDE 置 0,但其他能力仍遵守全局最低 3%闪避;因此玩家文案必须写成“闪避属性按 0 计算,仍保留最低闪避”,不能写成“后续攻击必定命中”。镇魄司命不再额外改写后续技能命中,采用第 3.4 节的近似增伤。
|
||||
|
||||
## 5. 各核心机制实现
|
||||
|
||||
### 5.1 蚀魂
|
||||
|
||||
Buff 配置要点:
|
||||
|
||||
- `type: DEBUFF`
|
||||
- `duration: 3`
|
||||
- `stackRule: STACK_LAYER`
|
||||
- `maxLayers: 5`
|
||||
- `dispelMode: one_layer`
|
||||
- 层级 modifier 使用 `valueByLayer`
|
||||
- 治疗削弱使用 `HEAL_RECEIVED_REDUCTION.valueByLayer`
|
||||
- 运行时标签由 `GameplayTags.BUFF.SECT.namespace('youdu', 'soul-erosion')` 和对应状态构造器产生
|
||||
|
||||
技能添加 2 层时使用 `apply_buff.layers = 2`,不要重复两个 ApplyBuffEffect。
|
||||
|
||||
### 5.2 失魂与归窍
|
||||
|
||||
推荐事件流程:
|
||||
|
||||
```text
|
||||
BuffLayerChangedEvent(蚀魂 4→5)
|
||||
├─ 若目标有归窍:高优先级把蚀魂设为4,结束
|
||||
└─ 否则施加失魂控制
|
||||
├─ 成功:目标下一行动因 NO_ACTION 跳过
|
||||
│ ControlledSkipEvent 时蚀魂设为3,并施加归窍保护窗口
|
||||
├─ 控制抵抗:onResistEffects 将蚀魂设为3,并施加归窍
|
||||
├─ 控制免疫:监听 BuffImmuneEvent,将蚀魂设为3,并施加归窍
|
||||
└─ 被心死神活解除:监听失魂 BuffRemovedEvent,将蚀魂设为3,并施加归窍
|
||||
```
|
||||
|
||||
失魂 Buff:
|
||||
|
||||
- `type: CONTROL`
|
||||
- `statusTags: [NO_ACTION]`
|
||||
- `duration: 1`
|
||||
- `HEAL_RECEIVED_REDUCTION +1.0`
|
||||
- `dispelPolicy: protected`
|
||||
|
||||
失魂不再接受普通净化直接移除。它的主要反制是:在 5 层前逐层净化蚀魂、控制抵抗、控制免疫,以及《心死神活》这类明确的控制解除。这样避免为一个控制状态新增通用 `onRemoveEffects` 生命周期协议。
|
||||
|
||||
归窍 Buff:
|
||||
|
||||
- 内部保护状态,玩家可见,语义上持续 1 个完整反制窗口。
|
||||
- 运行时 duration 配置为 1。`BuffContainer` 会调用 `markBuffAppliedAtCurrentAction`,`shouldTickBuffDuration` 会阻止新状态在施加它的同一次行动中立刻扣减,因此无需通过 duration 加一补偿。控制抵抗、免疫和首次解控分支统一使用同一配置。
|
||||
- 监听蚀魂的 `BuffLayerChangedEvent`,优先级高于失魂触发器。
|
||||
- 若当前层数超过 4,立即设回 4。
|
||||
- 不直接免疫蚀魂,因此 3→5 的技能仍能实际推进到 4,而不是整次加层被拒绝。
|
||||
|
||||
### 5.3 魂火
|
||||
|
||||
定义:
|
||||
|
||||
```ts
|
||||
{ id: 'sect.youdu.soul-fire', initial: 0, max: 3 }
|
||||
```
|
||||
|
||||
获取:
|
||||
|
||||
- 蚀魂监听 `BuffLayerChangedEvent`。
|
||||
- `delta > 0` 时将上下文 caster 映射为该 Buff 来源。
|
||||
- listener budget 使用 `source_action: 1`;分支监听器通过同一 `group` 共享次数,无论一次加 1 层还是 2 层都只获得 1 点。
|
||||
- 5→5 刷新没有 layer event,因此不获取。
|
||||
|
||||
消费与增伤:
|
||||
|
||||
- 指定消费技能带幽都 `soul-fire-consumer` 机制标签。
|
||||
- 常驻被动监听自身 `DamageRequestEvent`。
|
||||
- 条件:魂火至少 3、伤害类型 TRUE、能力带 consumer 标签。
|
||||
- 写入 `percent_damage_modifier increase 0.25`,节点可改为 0.35。
|
||||
- 加层消费技在直接魂伤请求之后、施加蚀魂之前执行 `consume_all`;终结技在伤害后执行。这样施法前 2 点魂火不会被本技能刚产生的第 3 点无增伤地误消费,而施法前满魂火的技能可在消费后由本次加层重新开始积累。
|
||||
- 忘川不带 consumer 标签,因此不会误消费。
|
||||
|
||||
### 5.4 心死神活
|
||||
|
||||
控制抗性 `+0.30 FIXED` 已可直接作为被动 modifier。
|
||||
|
||||
首次解控无需新增“解除最近控制”效果,可以由数据驱动监听组成:
|
||||
|
||||
1. 被动监听 `BuffAppliedEvent`,scope 为 owner_as_target。
|
||||
2. 分别匹配三类功能控制标签:`NO_ACTION`、`NO_SKILL`、`NO_BASIC`。
|
||||
3. 条件要求自身没有隐藏的永久 marker `heart-dead-used`。
|
||||
4. 用 `buff_layer_modify clear` 清除刚落地的对应控制;该操作可明确越过普通 dispelPolicy。
|
||||
5. 添加 marker,确保三个分类监听共享“每场一次”。
|
||||
6. 添加持续 1 回合的分类免疫 Buff;其 `BuffAddEvent` 监听使用现有 `buff_immunity` 拦截相同功能标签。
|
||||
|
||||
分类免疫运行时配置 `duration: 1`;复用 `markBuffAppliedAtCurrentAction` 的当次行动免 tick 规则,对玩家也显示 1 回合,不额外延长。
|
||||
|
||||
“同类控制”在 V1 按行动限制语义定义:
|
||||
|
||||
- 眩晕、失魂等不能行动效果归为 `NO_ACTION`。
|
||||
- 封印、沉默等不能施法效果归为 `NO_SKILL`。
|
||||
- 不能普攻效果归为 `NO_BASIC`。
|
||||
|
||||
不增加睡眠、定身等只有名称没有行动语义的新控制枚举。若未来确实出现位移或站位系统,再扩充标准控制轴。
|
||||
|
||||
### 5.5 忘川
|
||||
|
||||
忘川是标准 DataDrivenBuff:
|
||||
|
||||
- 单体、`duration: 2`、`REFRESH_DURATION`
|
||||
- 监听宿主 `ActionPreEvent`
|
||||
- 造成 `0.14 × source.MAGIC_ATK` 的 TRUE 伤害
|
||||
- `canCrit: false`、`canLifesteal: false`
|
||||
- 增加 20%受治疗削弱
|
||||
- 不加蚀魂,不触发主动加层魂火
|
||||
|
||||
招魂渡夜的“长夜回潮”不再刷新蚀魂持续时间,改为:
|
||||
|
||||
> 忘川对至少 3 层蚀魂目标造成的持续魂伤提高 20%;每回合第一次造成有效气血伤害时获得 1 点魂火。
|
||||
|
||||
这样完全复用伤害条件、百分比修正和回合预算,不新增“直接刷新任意现有 Buff 时长”的效果。
|
||||
|
||||
### 5.6 镇魂钉
|
||||
|
||||
- 控制 Buff 使用 `NO_SKILL`,AbilityContainer 会自动回退到默认攻击。
|
||||
- 受治疗削弱为 100%,与控制 Buff 同生共灭。
|
||||
- 控制被抵抗时不会添加 Buff,所以没有禁疗。
|
||||
- 1/2 回合版本放入两个 effect layer;effect plan 在施法准备阶段按目标施法前蚀魂层数选择其中一个。
|
||||
- 节点“幽都铁律”直接使用当前工作区已有的 `controlHitBonus: 0.15`。
|
||||
|
||||
若同一次《镇魂》先令目标进入 5 层:
|
||||
|
||||
- 失魂提供一次 NO_ACTION。
|
||||
- 镇魂钉提供 NO_SKILL。
|
||||
- 目标被跳过的行动结束后,两种状态都各自按宿主行动计时;2 回合镇魂钉最多在下一次实际行动继续迫使目标普攻。
|
||||
- 不把两个控制合并为一个“三回合硬控”。
|
||||
|
||||
### 5.7 魂兮不归
|
||||
|
||||
- `castConditions`: 目标蚀魂至少 4 层。
|
||||
- 编译器生成互斥的 `finish-four`、`finish-five` 两个固定 effect plan,每个计划只有一次 damage。
|
||||
- 基础系数与每层系数由 Build Facade 提供,节点只改参数;编译期分别计算四层与五层总系数。
|
||||
- 伤害后基础态 clear;“不归亦不散” set 到 2。
|
||||
- 最后添加不归的速度与受治疗 modifier。
|
||||
|
||||
“名落幽都”采用目标结算后低于 20%气血的替代条件,不监听击杀。当前 `UnitDeadEvent` 没有完整 ability 归因和 listener mapping;为了一个终极节点扩充死亡归因收益太低。
|
||||
|
||||
### 5.8 照影
|
||||
|
||||
- 能力本身 `hitPolicy: guaranteed`。
|
||||
- 照影 Buff 对 `EVASION_RATE` 使用 `OVERRIDE 0`。
|
||||
- 蚀魂 Buff 监听宿主受到的 `DamageRequestEvent`;目标同时有照影时,用现有 `percent_damage_modifier` 和 `scaleByBuffLayer` 增加 `0.02 × 蚀魂层数`伤害。
|
||||
- 镇魄司命额外为自身魂伤追加 `0.01 × 蚀魂层数`,不改变队友收益。
|
||||
- 蚀魂清除后,其监听随 Buff 移除,易伤自然立即消失。
|
||||
|
||||
### 5.9 夺魄与层级属性
|
||||
|
||||
夺魄的独立降攻直接使用 ATK/MAGIC_ATK 的 ADD modifier。它会与蚀魂的同类 ADD modifier在现有属性阶段相加。
|
||||
|
||||
不要修改五项持久化基础属性,也不要给蚀魂创建旧式 `persistent_statuses`。
|
||||
|
||||
## 6. 36 个节点的实现评估
|
||||
|
||||
### 6.1 招魂渡夜
|
||||
|
||||
| 节点 | 结论 | 实现方式 |
|
||||
| --- | --- | --- |
|
||||
| 一水忘川 | 直接支持 | 编译时修改忘川两个伤害系数 |
|
||||
| 唤名成痕 | 直接支持 | 《一叹》条件伤害或 DamageRequest listener |
|
||||
| 魂灯初照 | 直接支持 | BattleInit/首次 layer event + battle marker |
|
||||
| 潮声不歇 | 直接支持 | 忘川 duration 2→3 |
|
||||
| 彼岸无医 | 依赖受治疗扩展 | modifier 0.20→0.30 |
|
||||
| 黑水浸魄 | 直接支持 | 忘川增加 SPEED ADD -0.08 |
|
||||
| 三魂皆远 | 依赖按层取值 | 替换蚀魂 valueByLayer 曲线 |
|
||||
| 药石难入 | 依赖按层取值与受治疗扩展 | 替换治疗削弱曲线 |
|
||||
| 渡口回声 | 原效果不支持 | 改为单目标每回合一次追加魂伤 |
|
||||
| 江流不返 | 直接支持 | 3 层时 DOT 总计提高 20%,4 层时总计提高 30% |
|
||||
| 洗魂有价 | 依赖层变更事件 | reason=dispel 时每行动一次伤害 |
|
||||
| 两岸俱失 | 直接支持 | 不归 SPEED -0.30→-0.40 |
|
||||
| 百鬼同哭 | 原效果不支持 | 改为首次失魂对当前目标追加魂伤 |
|
||||
| 魂梦相侵 | 直接支持 | 失魂触发时重施 REFRESH_DURATION 忘川 |
|
||||
| 末渡无舟 | 使用近似 | `mana_burn` 按 10%最大法力 |
|
||||
| 不归亦不散 | 依赖单次层数缩放 | 伤害后 set 蚀魂为2 |
|
||||
| 楚些成悲 | 依赖单次层数缩放 | perLayer 0.20→0.24 |
|
||||
| 黑潮送行 | 直接支持 | 终结 completion 添加忘川 |
|
||||
|
||||
### 6.2 镇魄司命
|
||||
|
||||
| 节点 | 结论 | 实现方式 |
|
||||
| --- | --- | --- |
|
||||
| 铁入其影 | 直接支持 | 修改两个伤害 effect 系数 |
|
||||
| 见影知名 | 直接支持 | 照影 duration 3→4 |
|
||||
| 守神如城 | 直接支持 | CONTROL_RESISTANCE FIXED +0.10 |
|
||||
| 一魄先夺 | 直接支持 | 夺魄 duration 2→3 |
|
||||
| 钉下无声 | 直接支持 | Build Facade 修改 MP 55→45 |
|
||||
| 狱火照名 | 直接支持 | 魂火 listener 0.25→0.35 |
|
||||
| 三魂离座 | 直接支持 | 离魂引高层条件系数 0.78→0.884 |
|
||||
| 先定其形 | 依赖层变更原语 | 每场一次,仅铺层神通在伤害后 apply 额外1层 |
|
||||
| 心寂反照 | 直接支持 | 首次解控资源 +1→+2 |
|
||||
| 四门皆闭 | 直接支持 | 高层镇魂额外 SPEED -0.20 Buff |
|
||||
| 魂刑有度 | 直接支持 | `onResistEffects` 添加攻速减益 |
|
||||
| 幽都铁律 | 当前工作区已支持 | `controlHitBonus: 0.15` |
|
||||
| 五魄俱散 | 直接支持 | 失魂回落 effect 添加降攻 Buff |
|
||||
| 神归有垣 | 直接支持 | 首次解控添加 10%最大气血护盾 |
|
||||
| 一名一判 | 当前工作区已支持近似 | 终结后 `refund_paid_cost` 返还 20 法力并消费 marker |
|
||||
| 司命判词 | 依赖单次层数缩放 | base 0.70→0.85 |
|
||||
| 七寸断魂 | 依赖单次层数缩放 | perLayer 0.20→0.25 |
|
||||
| 名落幽都 | 原击杀归因不便 | 改为终结后目标低于20%气血时触发 |
|
||||
|
||||
## 7. 不应采用的实现方式
|
||||
|
||||
- 不在 DamageSystem 中判断能力或宗门 ID 来绕过减伤。
|
||||
- 不新增第二条灵魂生命值或在 Unit 上保存 soulHp。
|
||||
- 不把蚀魂拆成五个独立状态。
|
||||
- 不用五套 if/switch 在宗门编译器集中判断节点;参数进入 Build Facade,行为节点使用独立插件。
|
||||
- 不为忘川接入一个只认识幽都的多目标列表。
|
||||
- 不把 `battleProjection`、蚀魂层数、魂火或首次解控 marker 写入持久化模型。
|
||||
- 不用负 `HEAL_AMPLIFY` 冒充受治疗削弱;它读取的是治疗者,在未来队伍战斗中语义会错误。
|
||||
- 不用 `resolved_final` 实现魂伤;它会连统一增伤、境界、随机与标准计算管道一起绕过。
|
||||
- 不让《照影》通过给目标写入超大负闪避来突破全局最低闪避;能力自身使用 guaranteed,后续能力遵守全局规则。
|
||||
- 不在幽都实现期间整理、回滚或补完当前天衍工作区的无关改动。
|
||||
|
||||
## 8. 开发顺序
|
||||
|
||||
### 阶段一:通用原语
|
||||
|
||||
1. 分层状态:layers、BuffLayerChangedEvent、valueByLayer、one_layer dispel。
|
||||
2. `HEAL_RECEIVED_REDUCTION` 与 `Unit.heal()` 统一应用。
|
||||
3. `canCrit/canLifesteal`。
|
||||
4. AbilityConfig hitPolicy。
|
||||
5. 复用既有 effect plan 的条件与优先级,补四层/五层互斥选择测试,不新增伤害参数。
|
||||
|
||||
每项先补 battle-v5 原子测试,不引用幽都 ID。
|
||||
|
||||
### 阶段二:幽都基础态
|
||||
|
||||
1. definition、ids、六心法、魂火。
|
||||
2. 蚀魂、失魂、归窍和心死神活。
|
||||
3. 七门神通。
|
||||
4. 当时的 1v1 自动战术。
|
||||
|
||||
先完成无节点基础战斗矩阵,再进入道途。
|
||||
|
||||
### 阶段三:双道途
|
||||
|
||||
1. Build Facade 收集数值和行为特征。
|
||||
2. 招魂渡夜 18 节点。
|
||||
3. 镇魄司命 18 节点。
|
||||
4. 节点代表组合和固定种子平衡模拟。
|
||||
|
||||
### 阶段四:展示与注册
|
||||
|
||||
1. 入门演出和现有本地资产。
|
||||
2. 宗门地图、设施主题和组织主题。
|
||||
3. 生产 Runtime 注册。
|
||||
4. 状态栏、日志和能力详情;动画与声音按设计稿暂缓。
|
||||
|
||||
## 9. 验证矩阵
|
||||
|
||||
### 9.1 通用引擎测试
|
||||
|
||||
- 一次 apply 2 层只发布一次 layer change,delta=2。
|
||||
- 5 层刷新不发布 layer change。
|
||||
- valueByLayer 在增层、减层和 clone 后正确重挂 modifier。
|
||||
- one_layer 驱散 4→3,不移除 Buff;1→0 才移除。
|
||||
- 受治疗削弱覆盖 Heal、Lifesteal、DamageMemory 和 ResourceDrain,MP/护盾/免死不受影响。
|
||||
- canCrit=false 在高暴击率下仍不暴击。
|
||||
- canLifesteal=false 不触发吸血。
|
||||
- 四层/五层固定计划互斥命中,且任一计划只发布一次 DamageRequest。
|
||||
- guaranteed 能从 AbilityConfig 走完整施法流程。
|
||||
|
||||
### 9.2 幽都基础测试
|
||||
|
||||
- 蚀魂完整状态矩阵:0→1、1→3、3→5、归窍期3→5钳制为4、5→3。
|
||||
- 失魂成功、控制抵抗、控制免疫和心死神活解除四条收束路径都回到3层并进入归窍。
|
||||
- 心死神活只触发一次,按 NO_ACTION/NO_SKILL/NO_BASIC 免疫同类控制。
|
||||
- 魂火只在实际加层时获得,每来源行动最多1点,满层刷新不获取。
|
||||
- 混合技能共用命中,但发布魔法与真实两个伤害包。
|
||||
- 镇魂钉被抵抗时不产生禁疗。
|
||||
- 终结技按4层/5层读取系数并在伤害后正确 clear/set2。
|
||||
- 照影易伤实时跟随蚀魂层数。
|
||||
|
||||
### 9.3 历史批次暂不测试项
|
||||
|
||||
- 三目标忘川与多敌扩散:当时的 1v1 架构没有生产语义;当前 Team/Roster 架构下需要重新立项验证。
|
||||
- 双幽都共享蚀魂:当时生产战斗没有同队多单位;当前仍缺少明确玩法规则,不能直接视为已支持。
|
||||
- 动画、音效和受击表现:设计稿已明确暂缓。
|
||||
|
||||
### 9.4 完整实现后的命令
|
||||
|
||||
```bash
|
||||
bunx vitest run src/shared/engine/battle-v5/tests
|
||||
bunx vitest run src/shared/engine/sect/content/youdu
|
||||
bunx vitest run src/shared/engine/sect
|
||||
bun run lint
|
||||
bun run test
|
||||
bun run build
|
||||
```
|
||||
|
||||
## 10. 最终判断
|
||||
|
||||
幽都真正需要 battle-v5 补齐的是“通用分层状态”和“受治疗削弱”两块基础能力;它们都与现有 Buff/Attribute 架构一致,属于自然扩展。
|
||||
|
||||
魂伤绕过全部减伤、真实多目标、施加时 DOT 快照、一个事件混合两种伤害通道、按击杀精确返还等设计,不值得在 V1 为单宗门扩大战斗引擎。采用本文近似效果后,幽都仍然完整保留“越防、蚕食、禁疗、失魂、终结”的核心体验,同时不会让 battle-v5 永久背负宗门专属语义。
|
||||
@@ -1,221 +0,0 @@
|
||||
推荐分成 3 个独立提交修复:先保证战斗正确性,再修通用展示层,最后补战术与平衡验收。不要在 battle-v5 中增加幽都 ID 特判。
|
||||
|
||||
## 1. 先修战斗正确性
|
||||
|
||||
### 1.1 修复 4→5 层漏发魂火
|
||||
|
||||
根因是监听顺序:
|
||||
|
||||
- 归窍钳制:1000
|
||||
- 失魂触发:500
|
||||
- 魂火获取:200
|
||||
|
||||
失魂抵抗后会先生成归窍,导致魂火监听误以为目标施法前就在归窍期。
|
||||
|
||||
建议把两个魂火监听提高到失魂触发之前、归窍钳制之后,例如:
|
||||
|
||||
```ts
|
||||
const YOUDU_LAYER_PRIORITY = {
|
||||
RETURNING_CLAMP: 1_000,
|
||||
FIFTH_LAYER_NODE: 600,
|
||||
SOUL_FIRE_GAIN: 550,
|
||||
SOUL_LOST: 500,
|
||||
} as const;
|
||||
```
|
||||
|
||||
这样:
|
||||
|
||||
- 普通 4→5:先获得魂火,再处理失魂。
|
||||
- 已有归窍的 4→5:先被钳制,仍不获得魂火。
|
||||
- 已有归窍的 3→5:最终实际变为4层,仍获得1点魂火。
|
||||
- 失魂抵抗/免疫不会反向影响已经成立的实际加层。
|
||||
|
||||
修改位置:[YouduBaseCompiler.ts](/Users/churcht/daoyou/Daoyou/src/shared/engine/sect/content/youdu/base/YouduBaseCompiler.ts:386)
|
||||
|
||||
至少补四组测试:
|
||||
|
||||
- 无归窍 4→5,失魂成功,获得魂火。
|
||||
- 无归窍 4→5,失魂抵抗,获得魂火。
|
||||
- 无归窍 4→5,失魂免疫,获得魂火。
|
||||
- 有归窍 4→5→4,不获得魂火;3→5→4,获得魂火。
|
||||
|
||||
### 1.2 按伤害请求区分 MAGIC/TRUE
|
||||
|
||||
不要移除混合技能的 MAGIC/TRUE 能力标签;`AbilityFactory` 的标签契约是合理的。应修复伤害结算阶段的匹配方式。
|
||||
|
||||
在 [DamageImmunityEffect.ts](/Users/churcht/daoyou/Daoyou/src/shared/engine/battle-v5/effects/DamageImmunityEffect.ts:28) 中:
|
||||
|
||||
- MAGIC/TRUE/PHYSICAL 等伤害通道标签,只匹配 `event.damageType`。
|
||||
- 非伤害通道标签,才匹配 `event.ability.tags`、`event.buff.tags` 或 `damageTags`。
|
||||
|
||||
类似:
|
||||
|
||||
```ts
|
||||
function matchesDamageTag(event: DamageEvent, tag: string): boolean {
|
||||
if (tag === GameplayTags.ABILITY.CHANNEL.MAGIC) {
|
||||
return event.damageType === DamageType.MAGICAL;
|
||||
}
|
||||
if (tag === GameplayTags.ABILITY.CHANNEL.TRUE) {
|
||||
return event.damageType === DamageType.TRUE;
|
||||
}
|
||||
if (tag === GameplayTags.ABILITY.CHANNEL.PHYSICAL) {
|
||||
return event.damageType === DamageType.PHYSICAL;
|
||||
}
|
||||
|
||||
return (
|
||||
event.ability?.tags.hasTag(tag) ||
|
||||
event.buff?.tags.hasTag(tag) ||
|
||||
event.damageTags?.includes(tag)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
同时全局搜索监听 `DamageRequestEvent/DamageEvent/DamageTakenEvent` 时使用 `ability_has_tag(CHANNEL.*)` 的代码。比如“摄魂”应改为:
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'damage_type_is',
|
||||
params: { damageType: DamageType.MAGICAL },
|
||||
}
|
||||
```
|
||||
|
||||
位置:[artifactAffixes.ts](/Users/churcht/daoyou/Daoyou/src/shared/engine/creation-v2/affixes/definitions/artifactAffixes.ts:801)
|
||||
|
||||
回归测试需要证明:
|
||||
|
||||
- 法术免疫只阻挡《夺魄》的术伤包。
|
||||
- 真实伤害免疫只阻挡魂伤包。
|
||||
- 法术伤害回蓝只触发一次,不响应魂伤。
|
||||
- 魂伤仍不能暴击、不能吸血。
|
||||
|
||||
## 2. 修复通用展示架构
|
||||
|
||||
### 2.1 移除通用层中的“佛相”硬编码
|
||||
|
||||
[abilityFacts.ts](/Users/churcht/daoyou/Daoyou/src/shared/engine/sect/core/presentation/abilityFacts.ts:590) 不应理解佛相、魔相、无相。
|
||||
|
||||
建议给 `AbilityEffectLayerConfig` 增加可选展示名称:
|
||||
|
||||
```ts
|
||||
interface AbilityEffectLayerConfig {
|
||||
id: string;
|
||||
displayName?: string;
|
||||
effects?: EffectConfig[];
|
||||
completionEffects?: EffectConfig[];
|
||||
}
|
||||
```
|
||||
|
||||
通用层规则:
|
||||
|
||||
- 有 `displayName`:使用展示名。
|
||||
- 无 `displayName`:根据计划条件生成“施法前至少3层蚀魂”等描述。
|
||||
- 永远不直接展示 `sever-high`、`pin-low` 等内部 ID。
|
||||
- 无相宗自己配置“佛相/魔相/无相”,通用层不再猜测。
|
||||
|
||||
幽都可配置为:
|
||||
|
||||
```ts
|
||||
{ id: 'sever-low', displayName: '目标施法前少于3层蚀魂' }
|
||||
{ id: 'sever-high', displayName: '目标施法前至少3层蚀魂' }
|
||||
```
|
||||
|
||||
### 2.2 让展示层认识新的通用原语
|
||||
|
||||
[abilityFacts.ts](/Users/churcht/daoyou/Daoyou/src/shared/engine/sect/core/presentation/abilityFacts.ts:418) 当前仍按“重复几个 apply_buff”计算层数,应改为累计:
|
||||
|
||||
```ts
|
||||
const layers = matches.reduce(
|
||||
(total, effect) => total + (effect.params.layers ?? 1),
|
||||
0,
|
||||
);
|
||||
```
|
||||
|
||||
还应展示 effect 条件,特别是:
|
||||
|
||||
```text
|
||||
拥有3点魂火时:本次魂伤提高25%,伤害后消耗全部魂火
|
||||
```
|
||||
|
||||
否则 `consume_all` 会被错误描述成无条件消费。
|
||||
|
||||
### 2.3 明确处理手写 detailRows
|
||||
|
||||
当前 [SectCompiler.ts](/Users/churcht/daoyou/Daoyou/src/shared/engine/sect/core/compilation/SectCompiler.ts:241) 会完全覆盖内容编译器传入的 `detailRows`。
|
||||
|
||||
建议二选一:
|
||||
|
||||
- 推荐:有手写 `detailRows` 时作为权威内容,自动事实只作补充。
|
||||
- 或删除 `SectAbilityFactory` 的 `detailRows` 参数,避免继续产生死代码。
|
||||
|
||||
例如:
|
||||
|
||||
```ts
|
||||
const authoredFacts = ability.detailRows ?? [];
|
||||
const detailRows = Array.from(
|
||||
new Set([...authoredFacts, ...configFacts, ...modifierFacts].filter(Boolean)),
|
||||
);
|
||||
```
|
||||
|
||||
随后把幽都详情全部改成基于 `settings` 动态生成,避免节点强化后仍显示基础值。
|
||||
|
||||
### 2.4 展示 valueByLayer
|
||||
|
||||
在 [buffText.ts](/Users/churcht/daoyou/Daoyou/src/shared/engine/battle-v5/effects/affixText/buffText.ts:95) 中增加 `valueByLayer` 格式化。
|
||||
|
||||
推荐把相同曲线的属性合并:
|
||||
|
||||
```text
|
||||
物攻、法攻、物防、法防、身法:
|
||||
1层-3%,2层-5%,3层-8%,4~5层-12%
|
||||
|
||||
受治疗削弱:
|
||||
1层0%,2层15%,3层30%,4层50%,5层100%
|
||||
|
||||
普通驱散每次只移除1层
|
||||
```
|
||||
|
||||
同时使用 `isPercentageAttributeType()` 判断 FIXED 概率属性,避免把 `0.2` 显示成“+0.2”而不是“20%”。
|
||||
|
||||
展示回归测试应断言:
|
||||
|
||||
- 不出现 `佛相`、`sever-high`、`pin-low`。
|
||||
- 离魂引和镇魂显示增加2层。
|
||||
- 蚀魂详情不包含 `+0`。
|
||||
- 节点强化后显示真实的70%、0.22、40%控制抗性等数值。
|
||||
- 魂火消费条件和25%/35%增幅可见。
|
||||
|
||||
## 3. 补齐战术与平衡验收
|
||||
|
||||
### 3.1 自动战术
|
||||
|
||||
`healer-drown` 可以检查对手能力:
|
||||
|
||||
- `selectionProfile.intents` 是否包含 `heal`。
|
||||
- 治疗技能 `currentCooldown <= 1` 时提高镇魂优先级。
|
||||
- 没有治疗能力时退化为普通 `tide-cycle`,避免战术名与行为不符。
|
||||
|
||||
“预计终结足够”不建议在幽都策略中复制完整伤害公式。更稳妥的是增加一个通用只读伤害估算接口,供所有宗门 AI 使用;若本轮不希望扩大战斗架构,应明确将设计稿改成“目标低于45%或魂火已满”的启发式规则。
|
||||
|
||||
位置:[strategy.ts](/Users/churcht/daoyou/Daoyou/src/shared/engine/sect/content/youdu/strategy.ts:45)
|
||||
|
||||
### 3.2 平衡测试
|
||||
|
||||
当前测试只是冒烟,应增加可失败的数值边界:
|
||||
|
||||
- 基础终结4层/5层精确倍率。
|
||||
- 各终极节点允许的理论最高倍率。
|
||||
- 短、中、长战的合理回合区间。
|
||||
- 六套战术的终结次数、失魂次数、魂火溢出率。
|
||||
- 忘川流派的持续魂伤占比。
|
||||
- 默认攻击占比,防止 AI 因法力或条件错误长期只用《一叹》。
|
||||
- 同种子结果稳定。
|
||||
- 两条道途不能同时获得对方节点效果。
|
||||
|
||||
## 推荐提交顺序
|
||||
|
||||
1. `fix(battle): isolate mixed damage channels and soul-fire timing`
|
||||
2. `fix(sect): make layered ability presentation domain-neutral`
|
||||
3. `fix(battle-ui): render layered modifiers and dispel modes`
|
||||
4. `test(youdu): cover tactics and balance contracts`
|
||||
|
||||
前两个提交完成前不建议上线;后两个完成后,《幽都》才基本达到设计稿的可验收状态。
|
||||
Reference in new issue
Block a user