# idle-wuxia
**Repository Path**: aowind/idle-wuxia
## Basic Information
- **Project Name**: idle-wuxia
- **Description**: 江湖 · 山门 —— 挂机武侠:山门建设 / 装备系统(品质·词条·套装·传奇威能)/ 仓库 / 编队 / 技能书 / 挂机掉落
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-10-07
- **Last Updated**: 2026-10-23
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 江湖 · 山门(idle-wuxia)
> 一款用浏览器打开的**武侠挂机游戏** —— 挂机推图、营建山门、招募门人、参悟功法。
> 后端是一个**权威世界**,前端只是一扇**渲染视窗**。
>
> 🎮 **线上体验:**
---
## 一、这是一款什么游戏
玩家是掌门,三条线同时推进:
| 线 | 内容 |
|---|---|
| **挂机线** | 门人在「江湖游历」大地图的 **71 个关卡**(12 区域)自动战斗,打怪拿经验 / 金币 / 装备 / 材料 / 功法,一路往深处推 |
| **经营线** | 回到山门(**2D 折纸山**),8 类建筑开局即就位,靠**升级建筑等级**提升产能与名额 |
| **养成线** | 人物属性 + 装备 8 槽 + 功法 7 格,三层养成叠加出战力 |
**核心原则:后端权威。** 所有状态存服务器(`server/worlds//`),前端只负责展示;
掉落、升级、结算全在后端算 —— 刷新页面不丢进度,多标签页不会打架。
---
## 二、核心循环
```
挂机推图 ──► 掉落入仓 ──► 养成变强 ──► 招募扩编 ──► 升级建筑 ──┐
71 关自动战斗 装备/材料/功法 穿装备·学功法 大殿刷候选 三材升级 1→5 级
打怪结算经验金币 后端 roll·入仓 人物升级 厢房招新门人 产能/名额提升
▲ │
└────────────────────────────────────────────────────────────────────┘
```
推动循环的两大引擎:**关卡推进**(解锁更强的怪与掉落档位)与**声望积累**
(决定建筑能升到几级 —— 关卡进度就是同一把尺子)。
---
## 三、系统一览
| 系统 | 要点 | 文档 |
|---|---|---|
| 江湖游历 | 12 区域 × 71 关;每关刷小怪,杀满可打 Boss;区域末关必掉 4 件金以上装备 | `敌人图鉴与关卡生态设计.html` |
| 山门建筑 | 8 类建筑(大殿 / 武器铺 / 仓库 / 四种资源田 / 厢房),各可升 **1→5 级** | `建筑图鉴.html` · `建造与资源.html` |
| 门人 | **18 职业 × 男女**;资质 8 档(甲→辛)决定属性池;口粮 50/人/游戏天 | `人物养成.html` · `厢房系统设计.html` |
| 装备 | 8 槽;品质白→绿→蓝→金→传奇;加工 +1~+10 / 鉴定 / 铭刻 / 萃取 | `装备图鉴.html` · `装备强化与加工.html` |
| 功法 | 内功 2 + 外功 2 + 武器功法 3 = 7 格;总量 **240 条**;五品阶 | `功法收集与升级.html` |
| 声望 | 历练声望 + 门派声望;只决定建筑等级上限(0 / 260 / 980 / 2350 / 4800) | `声望系统设计.html` |
| 资源 | 顶栏铁石木粮(田产)+ 金币(战斗掉);仓库材料 纸 / 玄铁 / 玄晶碎片 / 灵玉 | `建造与资源.html` |
| 管理后台 | `GET /admin`:注册账号 / 在线情况 / 每位玩家的进度·门人·装备·功法(**只读**) | `server/admin.mjs` |
| 运营数据 | 在管理后台内:按天/按区间的 **DAU**、**在线时长**、**来源 IP 与地区** | 见下方「运营统计」 |
**运营统计**(管理后台 →「运营数据」,2026-10-10 上线):
| 指标 | 口径 |
|---|---|
| 在线时长 | 相邻两次请求间隔 **≤ 120 秒**的那部分之和。超时视为离开,中间空档**一分钟都不算** —— 「开着页面去睡觉」不会刷出 8 小时在线 |
| DAU | 当天有过请求的账号数(按**本地日期**分桶,服务器在 CST 才有"今天"的直觉) |
| 来源 IP | 优先读 `X-Forwarded-For`(生产是 nginx 全量反代,直接用 `socket.remoteAddress` 会**全是 127.0.0.1**);地区走 ip-api.com 批量接口,一个 IP 只查一次、永久缓存,可关 |
| 保留 | 180 天自动裁剪;落在 `server/stats/analytics.json`(**活数据,发布时不同步**) |
> ⚠️ **统计从上线那天开始攒**:账号表里只有注册时间、存档里只有"最后一次写入时间",
> 历史 DAU / 在线时长**回溯不出来**(当时没存)。唯一能回溯的是「当日新增账号」
> (由 `createdAt` 推导)—— 所以上线首日图上会看到"前几天有新增折线、DAU 柱子却是空的",
> 这是对的,不是 bug。
**时间模型**:倍率 ×60 —— 1 真实秒 = 1 游戏分钟,**24 真实分钟 = 1 游戏天**(开局清晨 07:30)。
资源田每游戏小时产出,囤满停产、满仓自动收;离线最多补 24 真实分钟。
---
## 四、跑起来
```bash
npm install
# 开发:后端(127.0.0.1:8000) + 前端(127.0.0.1:5180),前端走 vite 代理打 /api
node tools/dev.mjs
# 也可以分开起: npm run server | npm run dev
# 发布:打包编译出 dist/(8000 端口伺服的发布形态)
npm run build
```
| 端口 | 用途 |
|---|---|
| **5180** | vite dev,改动即时生效 —— **开发用这个** |
| **8000** | node 后端;同时伺服 `dist/` 构建产物 —— **发布形态**,改源码不会自动更新,要重新 build |
> 🔴 8000 端口伺服的是 **dist/**(构建产物),不是源码。改完源码必须 `npm run build` 才生效。
---
## 五、技术架构
### 5.1 后端权威 + 前端插值
- 每个账号一套世界:`server/worlds//{state,resources,heroes,equips,arts,buildings,idle,squads}.json`
- 鉴权:邮箱 + 密码(scrypt 加盐),会话 token 由服务端持有(**故意不用 JWT** —— 要能立刻踢会话)。
除 `health / config / auth` 外全部接口要 `Authorization: Bearer `,
服务端从 token 反查 userId 再取世界 ⇒ **数据隔离是结构性的**,不靠每个接口自觉过滤。
- 存储:**JSON 文件 + tmp→rename 原子写**(重试 3 次防杀软 EPERM)。单机规模,不引数据库。
- 时间:**锚点式**,只存 `anchorReal + anchorHour + speed`,当前时刻由公式推导。
不跑定时器 ⇒ 重启后自动连续、跨天计数不归零。
### 5.2 为什么后端是 Node 而不是 Python
本机安全策略**拦截 python 进程对外提供 HTTP 服务**:服务 LISTENING 正常,
但任何客户端连接都 ETIMEDOUT(换端口 / 路径 / 进程名 / 绑定地址全试过无效),
而 node 同方式起服务 curl 直接 200。⇒ 后端用 Node(零依赖 `node:http`),
Python 只做**离线工具**(生图、生视频、Blender 脚本)。
### 5.3 判据外置(前后端共用一份真源)
写死在代码里的断言要整理成对应的 JSON:
| 判据归属 | json 位置 | 谁读 |
|---|---|---|
| 前端模块 `src/<模块>.js` | `src/specs/<模块>.json` | vite `import`,构建时内联进 bundle |
| 页面 `docs/<页面>.html` | `docs/specs/<页面>.json` | 生成器读 + 构建时内联 |
| 验收脚本 `tools/<脚本>.mjs` | `tools/specs/<脚本>.json` | `tools/spec.mjs` 的 `spec(name)` |
| 前后端共用(如开号名字 2–8 字) | `src/specs/starter.json` | 前端 import **+** 后端 `fs.readFileSync`(同一份,杜绝"前端放行后端 400") |
### 5.4 发布时剥离 HTML 注释
源码里的 `` 是**开发笔记**(写清了每个区块的来历与踩过的坑),
是资产、但不该随页面发给外人。所以 **源码一个字不动**,只在"出门"的两个环节剥掉:
- 构建:`tools/build.mjs` → `strip_html_comments.mjs` 处理 `dist/*.html`
- 伺服:`server/index.mjs` 的 `/docs/*` 对 `.html` 剥(带 mtime 缓存)
两处**共用同一份实现**(`tools/strip_html_comments.mjs`,状态机,`script`/`style` 内容原样搬运),
杜绝"构建剥了、docs 没剥"这种不一致。
---
## 六、目录结构
```
index.html 游戏主页面(单页,HUD + 浮窗都在这)
game-docs.html 游戏文档索引页(左目录右内容,独立入口)
action_viewer.html 动作精灵表查看器(开发工具页)
src/ 前端(Three.js + 原生 ESM,无框架)
main.js 入口:场景装配、相机/灯光/雾、HUD、浮窗接线
api.js 统一注入 token、统一处理 401
login.js 进入山门浮窗(注册 / 登录已合并)
hall.js 大殿:招募候选 + 门人装配浮窗
warehouse.js 仓库:装备/材料,加工·鉴定·铭刻·萃取
resources.js 资源系统前端表现层(田产圆圈、收取)
journey.js 江湖游历大地图浮窗(71 节点)
flatScene.js 2D 折纸美术层(山 + 云,取代 3D 六边形山门)
flatBuildings.js 折纸山建筑感应区(点击进功能界面 + 等级标签)
daylight.js 日夜光照:10 关键点采样 → 光照 / 雾色 / 染色插值
timeClient.js 时间客户端:后端权威 + 本地插值
celestial.js 日月行天
confirm.js 统一确认浮层(别用裸 window.confirm)
idle/ 挂机这一条线
idleWorld.js 江湖世界数据层
idleBattle.js 战斗逻辑(纯逻辑,不做绘制)
idleBand.js 底部条带(2D 图片滚动)+ 战斗表现
idleDrops.js 掉落物落地 + 自动拾取飞行
anim.js 动作状态机(纯逻辑)
journeyTips.js 游历提示
server/ 后端(Node 零依赖,只用内置模块)
index.mjs HTTP 服务与路由(59 个端点)+ dist/ · /docs/ · /admin 的静态伺服
accounts.mjs 账号与会话(scrypt)
admin.mjs 管理后台:独立凭据 + **只读**玩家索引(不实例化任何 Store)
admin.html 管理后台单页(GET /admin 伺服,原生 JS 零依赖)
analytics.mjs 运营统计:在线时长 / DAU / 来源 IP(**只写自己的 server/stats/**)
geoip.mjs IP 归属地解析(ip-api.com 批量接口,异步 + 永久缓存,可关)
world.mjs 世界管理:每个账号惰性一套 Store
clock.mjs 锚点式游戏时钟(权威时间)
buildings.mjs 建筑实例与升级
buildingLevel.mjs 建筑等级数值**唯一真源**
heroes.mjs 门人:属性、经验、口粮关联
jobs.mjs 十八职业定义
resources.mjs 资源与田产产出 / 收取
upkeep.mjs 口粮消耗
equips.mjs 装备实例:加工 / 鉴定 / 铭刻 / 萃取
equipDefs.mjs 装备模板、词条、套装、威能(静态表)
arts.mjs 功法定义与掉落分层
artsStore.mjs 功法仓库与穿戴
skills.mjs 技能表
squads.mjs 编队(单小队最多 3 人)
idle.mjs 挂机进度(第几关 / 杀了几只 / Boss / 自动进关)
prestige.mjs 声望(纯函数推导,不落盘)
ranks.mjs 资质阶梯与招募经济
journeyNodes.mjs 游历节点数值快照(纯函数,零存储)
atomicFile.mjs 原子写(tmp + rename + 重试)
docs/ 游戏文档(24 篇,游戏内可浏览)
catalog.json 分类清单;game-docs.html 索引页读它分组,正文走 /docs/* 由后端伺服
tools/ 离线工具与验收脚本
build.mjs 发布构建(清 dist → vite build → 剥 HTML 注释 → 体积报告)
dev.mjs 一键起后端 + 前端
strip_html_comments.mjs HTML 注释剥离(构建与伺服共用)
spec.mjs 判据 json 加载器
_shot_*.mjs 无头截图(登录页 / 大殿 / 文档页 / 管理后台 …)
verify_admin.mjs 管理后台验收(71 项:鉴权 / 分页 / 排序 / 汇总自洽 / 两套 token 隔离 / 运营统计)
_ui_helpers.mjs UI 自测公共引导(注册 → 建主角)
gen_*.py 生图 / 生视频 / Blender 出模脚本
deploy/prod/ 生产部署(阿里云)
push.sh 一键发布:构建 → 打包 → 上传 → 重启
ecosystem.config.cjs PM2 进程配置
nginx-idle-wuxia.conf 站点配置
verify.mjs 线上验收(HTTP 全链路)
verify_ui.mjs 线上验收(真实浏览器 + 出图)
```
---
## 七、部署到服务器
**服务器**:`8.134.249.230`(阿里云 Alibaba Cloud Linux 4 / 2C1.6G)
**代码根**:`/opt/idle-wuxia` **SSH 别名**:`menpai-aliyun`
### 7.1 服务架构
```
公网 :80 ──► Nginx ──► 127.0.0.1:8000 的 node 服务
(全量反代,静态不直出 —— 理由见 nginx 配置里的注释)
后端由 PM2 守护(进程名 idle-wuxia)→ pm2-root.service 开机自启
```
> **为什么 Nginx 不做静态直出**:① 缓存策略(哈希资源 `immutable` / 其余 `no-cache`)
> 只有 node 一处真相源,两边各写一份迟早漂;② `/docs/*` 的注释剥离在 node 里做,
> Nginx 直出会绕过去,内部笔记直接送到浏览器;③ 未登录可见性由后端路由表判,
> 直出会制造绕鉴权的旁路。代价是多一次用户态转发 —— 这台机器流量不大,值。
### 7.2 发布
```bash
# 日常更新(本机构建 + 上传 + 平滑重载)
bash deploy/prod/push.sh
# 用现有 dist,只同步后端代码与文档
bash deploy/prod/push.sh --no-build
# 首次部署 / 换机器:额外接管 8000 端口并安装 nginx 站点
bash deploy/prod/push.sh --init
```
发布后跑一遍验收(**两条都要绿**):
```bash
node deploy/prod/verify.mjs # HTTP 全链路 25 项
API=http://8.134.249.230 WEB_URL=http://8.134.249.230/ \
node deploy/prod/verify_ui.mjs # 真实浏览器 + 出图
```
> 后端**零依赖**,服务器上不需要 `npm install` —— 前端在本机构建好再上传。
> 上传约 190M(dist 里是全部美术资产)。
### 7.3 固化措施
| 措施 | 位置 | 作用 |
|---|---|---|
| `pm2 save` + `pm2-root` 开机自启 | 系统服务 | 机器重启后自动拉起 |
| `autorestart` + `max_restarts: 20` + `min_uptime: 20s` | ecosystem | 崩溃自愈;启动即崩则停下,不再刷日志把 bug 伪装成"服务在跑" |
| `kill_timeout: 15s` | ecosystem | 停止时留时间把存档落盘,不丢进度 |
| `max_memory_restart: 600M` | ecosystem | 内存护栏(存档在盘上,重启不丢) |
| `instances: 1`(**不许开 cluster**) | ecosystem | 世界状态是进程内缓存,多实例会互相覆盖存档 |
| `--init` 里先探活再切 nginx | push.sh | 发布过程不出现 502 |
**实测**:`kill -9` 掉后端进程,PM2 在 **4 秒内**自动拉起,探活恢复 200。
> 🔐 **管理后台(`/admin`)的凭据不在代码里**(2026-10-10 起)
>
> 页面是公开的(登录框得能打开),**看得到**全部账号、进度与在线情况,所以
> nginx 全量反代之下,密码就是唯一的门。
>
> - 凭据只从环境变量读:`WUXIA_ADMIN_USER`(默认 `admin`)/ `WUXIA_ADMIN_PASS`(**必填**)。
> - 🔴 代码里**没有任何默认密码**:`WUXIA_ADMIN_PASS` 没配 ⇒ 管理登录整体
> **禁用**(fail-closed,`/api/admin/login` 一律 400),不会回落到某个"默认值"。
> 靠默认值上线 = 把一把全服钥匙挂在公网上,这个坑不留。
> - 放密码的地方(都不进仓库,`.gitignore` 已挡):
> - 本机:项目根 **`.env`**(`server/index.mjs` 启动时自动读)
> - 生产:服务器 **`/opt/idle-wuxia/.env.prod`**(一行 `KEY=VALUE`,
> 由 `deploy/prod/ecosystem.config.cjs` 注入 PM2 env)
>
> ```bash
> # 服务器上首次配置(然后必须 --update-env 才生效)
> echo 'WUXIA_ADMIN_PASS=换成一个只有你知道的密码' > /opt/idle-wuxia/.env.prod
> pm2 restart idle-wuxia --update-env
> ```
>
> ⚠️ 管理 token 只在进程内存里,重启即失效 —— 管理员重登即可,不是故障。
> 📊 **运营统计是"活数据",发布时不会被覆盖**
>
> `server/stats/analytics.json` 与 `server/worlds/` 同性质 —— 是服务器上跑出来的东西,
> **不在 `push.sh` 的打包列表里**(那里只收 `server/*.mjs` 与 `server/admin.html`)。
> 覆盖它 = 历史 DAU / 在线时长 / IP 归属全没。
>
> 想手工清空统计(比如上线前想把自测产生的数据抹掉):
> ```bash
> rm /opt/idle-wuxia/server/stats/analytics.json && pm2 restart idle-wuxia
> # 下次心跳会从当天重新开始记;账号表与存档不受影响
> ```
> 关掉外网 IP 归属查询(隐私考虑):env 里给 `WUXIA_GEO=0` 再 `--update-env`。
### 7.4 回滚
「门派经营」的代码与配置**原样保留**,没有被删除:
```bash
# 1) 恢复 nginx 站点
mv /etc/nginx/conf.d/menpai.conf.disabled-<时间戳> /etc/nginx/conf.d/menpai.conf
rm /etc/nginx/conf.d/idle-wuxia.conf
systemctl reload nginx
# 2) 停江湖、起门派经营(两者都占 8000,不能同时跑)
pm2 stop idle-wuxia
pm2 start menpai-game
pm2 save
```
---
## 八、游戏文档
游戏内点右上角「**游戏文档**」按钮,或直接开
(左目录右内容,新标签页复用登录态)。
文档分六类,清单在 `docs/catalog.json`:
| 类目 | 文档 |
|---|---|
| 游戏总览 | 游戏总览 |
| 玩法系统 | 建筑图鉴 / 建造与资源 / 声望系统设计 / 厢房系统设计 / 大殿设计 / 敌人图鉴与关卡生态设计 |
| 升级与收集 | 功法图鉴 / 装备图鉴 / 功法收集与升级 / 装备强化与加工 / 装备合成机制 / 装备合成界面规范 / 人物养成 / 功法系统设计 / 装备系统设计 |
| 美术资产 | 门人动作展示 / 门人形象设计 / 门人职业与形象设计 / 敌人动作检查 / 敌人设定图检查 / 人物动作提示词-H3.md |
| 技术方案 | 技术架构总览 / 优化方案-代码与架构 / 本地生图方案 / 本地H3动作生成方案 |
| 规格契约 | equip-impl-spec.md |
**加一篇文档**:把 HTML 放进 `docs/`,再到 `catalog.json` 对应类目的 `files` 里补一行文件名即可
(没登记的会落进「未分类」,不会丢)。
---
## 九、开发约定(踩过的坑,别再踩)
1. **发布必须走 `npm run build`**,不要直接 `npx vite build`:
`vite.config.js` 里 `emptyOutDir: false`,清理权只归 `tools/build.mjs`
(它用 .NET 静态调用删 dist,绕开本机 safe-delete 钩子;vite 自带的清理会被
按「批量删除」拦下 → `Build failed in prepare-out-dir`)。
2. **并发开发**:`git add <明确路径>`,**绝不 `git add -A`**(多条线同时在改)。
3. **验收断言别写死 CSS 类名 / DOM 结构 / 具体 px** —— 另一条线会改界面。
4. **计时 / 飘字 / 特效走真实时间**,不乘游戏时间倍率;动作触发判**上升沿**不判电平。
5. **"随时间累积"的东西一律挂 `clock.gameSeconds()`**;"已花掉的账"必须落账幂等。
6. **确认框用 `src/confirm.js`**,别用裸 `window.confirm`(无头环境下会被自动 dismiss)。
7. **自测脚本必须走账号引导**(`tools/_ui_helpers.mjs` 的 `registerViaUI` → `passStarterUI`):
新账号不再自动发主角,跳过这步会拿到 `undefined` 然后在某句 `hero.job` 上死掉,
报错看起来像"脚本写错了"。
8. **Playwright 开多页要自己建 context**:`browser.newPage()` 每次另起一个 context,
localStorage 不共享 —— 用它开 game-docs.html 会看到"未登录"(那不是功能坏了)。
9. **PM2 配置文件必须是 `.cjs`**:项目 `"type": "module"`,`.js` 会被当 ESM 加载,
而 PM2 按 CommonJS 读 → `ReferenceError: module is not defined`。
10. **索引页必须叫 `game-docs.html` 而不是 `docs.html`**:vite 的对象式代理按路径前缀匹配,
`/docs` 会把 `/docs.html` 也劫走转发给后端(详见 `vite.config.js` 的注释)。
11. **`_gen_*` / `_out` / `_shots` / `dist` 不进版本库**(`.gitignore` 里);`docs/` 的 HTML 进。
12. **改服务端存档文件前先停服务**:`AccountStore` 等 Store 在内存里持有数据并会全量回写,
直接改磁盘文件会被覆盖(改完 `pm2 restart` 让服务重新读盘)。
---
## 十、后续
- **门人重做**:十八职业 × 男女 = 36 人,重新设计(设定图)+ 全跑动作。
技能先出透明背景 PNG,再拿它做动作;门人全套动作**朝右**(敌人朝左,方向相对才成对战)。
- 美术资产管线已全部本地化(ComfyUI + Z-Image 生图 / MiniMax H3 生视频),
不再依赖云接口 —— 详见 `docs/本地生图方案.html` 与 `docs/技术架构总览.html`。
---
## 许可
代码以 **MIT License** 开源,见 [LICENSE](LICENSE)。
`public/assets/` 与 `docs/` 里的美术、音频、设计文档是这款游戏的作品资产,
版权归作者所有(**不自动跟随 MIT 授权**);如需复用素材或整包商用,请先联系作者。
---
## 附录:早期 3D 山门路线的踩坑存档
现在的山门是 **2D 折纸层**(`src/flatScene.js`,`main.js` 里 `FLAT_ART = true`)。
3D 那套(六边形地块山峰 / 实体云海 / Blender 植被)代码**仍然保留**在
`FLAT_ART = false` 分支(`hexmap.js` / `clouds.js` / `plants.js` / `backdrop.js`),
将来若要回 3D 或做混合,这几条是当时踩出来的:
- `InstancedMesh` 容量必须在构造时给对(事后改 `.count` 不扩容缓冲,`setMatrixAt` 静默越界)
- 六棱柱半径用**外接圆** `HEX_SIZE`;`CylinderGeometry` 首顶点在 +z,不要再 `rotateY(π/6)`
- 侧壁贴图要用 `onBeforeCompile` 按**世界高度**取样(吃几何 UV 会随柱高拉伸)
- 每根柱子要给随机 UV 偏移,否则相邻纹理逐字重复,并排像栅栏
- 柱壁分两段画:上段不透明、下段 alpha 淡出(近侧柱壁 y=0~-10 段没有云能遮住)
- 环幕:`CylinderGeometry(R,R,H,96,1,true)` + `BackSide` + `fog:false`,
`H = πR / 3.2` 让横纵像素密度一致;顶边必须出画,否则抬头看到"天花板"
- 生图边缘常带卷轴条带,镜像平铺后会变成竖分界线 → 平铺前先裁左右各 1.5%
- Blender 出模:**颜色烘焙进顶点色**(多材质会被 GLTF 拆成多个 primitive,没法单个 InstancedMesh 批量画)
- GLB 的 accessor min/max ≠ 真实尺寸(节点上还有缩放),要用 `Box3().setFromObject()` 量
- 光照配比决定"有没有阴影":太阳当主角、环境光只托底;`shadow.normalBias` 别贪大(0.028)
更完整的当时版本在 git 历史里(`git log README.md`)。
---
美术资产全部由本地模型生成;游戏文档(`docs/`)随源码一起进版本库。