# longevity-world
**Repository Path**: luckSnow/longevity-world
## Basic Information
- **Project Name**: longevity-world
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-10-20
- **Last Updated**: 2026-10-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 《长生界·九灯纪》
> 东方玄幻回合制 RPG(Godot 4.7 / GDScript)· **辰东《长生界》非商业同人**
> 玩家扮演萧晨,走「寿元 / 缘 / 阵法 / 破绽」四条差异化系统线,通向 6 个结局。
  
**这不是商业作品,是同人致敬项目。** 仓库内不含任何商业游戏素材,美术 / 音频 / 程序 / 文本改编均为自制再创作,详见 [声明](#声明)。
本仓库包含**从设计到可运行包体的完整链路**:13 份设计文档 → 机读数据层 → 运行时与测试 → 验收审计 → Android 调试包。
---
## 画面
| 标题 · 序幕 | 世界 · 城市街区 |
|---|---|
|  |  |
| 战斗 · Boss 四阶段 | 战斗 · 结算 |
|---|---|
|  |  |
| 菜单 · 装备页 | 对话 · 关键场次 |
|---|---|
|  |  |
| 大地图 · 九州 | 结局 · 隐藏真结局 E6 |
|---|---|
|  |  |
> 全部 46 张逐区域截图在 [`docs/screenshots/`](docs/screenshots/),1280×720,均由 `scripts/tools/shot.gd` 实机渲染。
---
## 玩法与系统
- **九幕可通关战役** —— 36 张可走地图按出口链串起,幕末推进、层间通道 5 条,另有 9 座驿站与 9 处隐藏传送点
- **回合制战斗** —— 4 人小队、前排 2 / 后排 2,阵位承伤与输出系数生效;指令分五类(攻击 / 技能 / 道具 / 守势 / 撤退)
- **四条差异化系统线**
- **寿元** —— 燃寿三档、兵解;**寿元归零不构成游戏结束**
- **缘(bond)** —— 10 节点 45 对关系值驱动合击(≥40)、代伤(≥60)、羁绊技(≥80)、不灭(≥90)
- **阵法** —— 5 种阵,每场限 1 次,消耗阵旗与神识,直接改变战场地形
- **破绽** —— 命中弱点 +25 / 命中抗性 −10,满 100 开启队伍增益并令首领失衡
- **气力条** —— 每回合 +25(上限 100):50 解锁合击、75 绝招、100 觉醒技
- **六维派生** —— VIT / SPI / AGI / STR / WIL / LUK → 派生属性,公式唯一实现在 `scripts/core/stats.gd`,UI / 战斗 / AI 共用
- **12 种状态** —— 增益 / 减益各 3 槽,同名不叠加取剩余回合长者,含抵抗判定、持续伤害、控制跳过、破防削防、护体吸收、狂暴加攻
- **元素克制** —— 五行相克 + 特殊系环克 + 抗性标签封顶,系数 1.30 / 0.77 / 0.60
- **Boss 四阶段** —— 阶段表 + 破绽窗口,半祖威压每 4 回合一次减伤(反击池 2.5× 结算)
- **队伍与宠物** —— 8 名可操控角色 + 2 只宠物,另有 6 名可招募 NPC(柳暮、绝刀、托蒂、诸葛亮、清清、一真)可入队
- **装备与灵药** —— 389 件物品、45 条词条池、5 档稀有度;灵药条款逐句解析,**兑现不了的整件拒绝且不扣道具**
- **商店与经济** —— 8 家商店,声望 / 章节门槛生效,物价随「封井」分支 +30%
- **任务与抉择** —— 65 条任务(主线 9 / 支线 36 / 阵营 12 / 隐藏 8),9 组抉择点写入 30 个旗标并影响结局
- **6 个结局** —— E1–E6 可判定,E6(隐藏真结局)为复合谓词入口;22 场关键对话、12 名 NPC 专属死亡演出
- **存档** —— 3 个手动槽位,读写 round-trip 全等
- **触屏适配** —— 方向键 + 确定 / 取消,战斗界面分组指令面板,支持直接触屏游玩
---
## 环境准备
### 1. 下载 Godot 4.7.2
本项目基于 **Godot 4.7.2 stable** 开发(`project.godot` 记录 `features = 4.7`,渲染器为 GL Compatibility,逻辑视口 480×270 整数倍放大到 1280×720)。
| 用途 | 下载地址 |
|---|---|
| 引擎本体(推荐) | |
| 历史版本与模板包 | (找 `4.7.2-stable`) |
Windows 下选 **`Godot_v4.7.2-stable_win64.exe.zip`**(约 50 MB,解压即用,无需安装)。包里有两个可执行文件:
- `Godot_v4.7.2-stable_win64.exe` —— 带窗口,**运行游戏与截图自检必须用它**
- `Godot_v4.7.2-stable_win64_console.exe` —— 带控制台输出,**跑测试与导出用它**
> 无头模式(`--headless`)没有可渲染视口,截图会静默产出空图,别用它做画面复核。
### 2. 安装导出模板(打包 APK 才需要)
引擎本体不含导出模板。打开编辑器 → **Editor → Manage Export Templates → Download**,选择与引擎**完全同版本**的 4.7.2 模板;或从上面 GitHub Releases 页下载 `Godot_v4.7.2-stable_export_templates.tpz` 后同入口 **Install** 安装。
模板装好后位于 `~/AppData/Roaming/Godot/export_templates/4.7.2.stable/`(Windows)。
版本不匹配会导致导出静默失败或产出跑不起来的包。
### 3. 其他依赖
| 依赖 | 版本 | 用途 |
|---|---|---|
| Node.js | 18+ | 跑 `tools/ingest_*.js`(9 个数据摄入器)与 `tools/acceptance_report.js`、`tools/audit_*.js`。**只用标准库**,不装 npm 包 |
| Python | 3.10+ | 跑 `tools/*.py`(素材生成 / 量化 / 审计 / 校验),依赖 `Pillow` + `numpy` |
| `ffmpeg` | 任意近期版本 | BGM 母带循环拼接(`tools/extend_bgm_loop.py`)、格式转码 |
| 生图 / 生乐通道 | ModelScope / MiniMax / Pollinations 任一 | AI 量产立绘、背景层、物品图标与 BGM 母带;额度受限时程序化管线可独立重建全部素材 |
> 素材生成链路不需要 `npm install`:`tools/` 下全部脚本只用 Node 标准库与 Python 第三方库。
---
## 启动游戏
### 方式一:编辑器(开发调试)
```powershell
git clone git@gitee.com:luckSnow/longevity-world.git
cd longevity-world
# 首次必须导入一次:新增素材没有 .import 会加载失败
godot --headless --path . --import
# 用编辑器打开仓库根(project.godot 就在这里)
godot --editor --path .
```
编辑器里按 **F5** 运行(主场景 `res://scenes/main.tscn`)。
### 方式二:命令行直接跑
```powershell
godot --path .
```
### 方式三:导出后运行
见下一节,Windows 桌面产物双击即玩。
### 调试用工具场景
```powershell
# 四层测试(不依赖 .ps1 封装时)
godot --headless --path . res://tests/test_runner.tscn
# 逐区域截图(**不要加 --headless**,否则产出空图)
godot --path . scenes/shot.tscn
godot --path . scenes/shot_battle_fx.tscn
godot --path . scenes/shot_world_map.tscn
```
---
## 编译与打包
**先决条件**:装好对应平台的导出模板;导出前必须 `--import` 一次,否则新素材不进包。
```powershell
# 0. 导入 + 跑测试(门槛:0 failed / 0 suite errors)
godot --headless --path . --import
.\tools\run_tests.ps1
```
### Android(APK)
```powershell
godot --headless --path . --export-debug "Android" export/changshengjie_niudengji.apk
```
- **调试包可安装试玩,但不是商店发布包。** 要出 release 包,需在 `export_presets.cfg` 的 `keystore/release` 填上你自己的密钥库、别名与口令(口令不要提交进仓库)。缺密钥库时 `--export-release` 会在签名阶段失败 —— 这条已登记为遗留项 ISS-05。
- 未启用 gradle 构建(`use_gradle_build=false`),因此**不需要 Android SDK/NDK**。
- 导出日志会打印 `apksigner ... --ks <路径> --ks-pass pass:<口令>`,分享日志前务必遮蔽。
### Windows 桌面
```powershell
godot --headless --path . --export-release "Windows Desktop" export/changshengjie_niudengji.exe
```
### 导出后必须验产物
**导出失败也可能已经覆盖上一个可用产物**(写文件在前、签名失败在后),所以每次导出后都验:
```powershell
python tools\verify_apk.py export\changshengjie_niudengji.apk
```
判据:zip 完整性通过、条目数与上次同量级、`META-INF` 里有 `.SF`/`.RSA`/`MANIFEST.MF`、`lib//*.so` 存在。
**随包字体也要验**(导出包里没字体 = 手机上满屏豆腐块,而桌面一切正常,靠眼睛看不出来):
`assets/fonts/GameFont.ttf`(正文子集,约 756 KB)与 `DisplayFont.ttf`(像素显示字体,约 30 KB)必须进包,
且三套导出预设的 `exclude_filter` 不得排掉 `assets/fonts/*.ttf`。
---
## 项目结构
**仓库根即 Godot 项目根**,`project.godot` 就在这一层,没有嵌套。
```
.
├── project.godot # Godot 项目配置(480×270 逻辑视口,GL Compatibility)
├── export_presets.cfg # Android / Windows Desktop 两套导出预设
├── scenes/ # 标题 / 序幕 / 世界 / 战斗 / 菜单 / HUD / 对话 / 结局 / 致谢 + 截图工具场景
├── scripts/
│ ├── core/ # 数据加载 / 存档 / 队伍 / 背包 / 任务 / 区域 / 派生子属性 / 音频 / 字体
│ ├── battle/ # 战斗运行时 / 状态 / 界面 / 特效 / 音效
│ ├── world/ # 世界场景 / NPC / 市民 / 建筑 / 出入口漩涡 / 放置
│ ├── ui/ # 标题 / 序幕 / 菜单 / HUD / 对话框 / 结局 / 致谢 / 触屏
│ └── tools/ # 逐区域截图工具
├── data/ # 19 份机读 JSON(18 份由摄入器产出 + 1 份提示词库)
├── assets/ # 美术与音频(32 色板 C01–C32 硬约束)+ 内嵌字体子集
├── tests/ # 四层测试(unit / data / integration / assets),41 个套件
├── tools/ # 摄入器 / 素材生成器 / 生图生乐管线 / 审计与验证脚本
├── docs/
│ ├── design/ # 13 份创作文档(剧情大纲 + ①~⑫)—— 唯一可编辑源
│ ├── specs/ # 3 份规格(阶段 0 选型 / 主规格 42 项需求 / 阶段 2 计划拆分)
│ ├── lore/ # 百科正文
│ ├── audit/ # 需求台账 / 授权台账 / 遗留问题台账 / 验收报告
│ └── screenshots/ # 本 README 用图 + 46 张逐区域取证截图
└── export/ # 构建产物(已 gitignore,本地复现)
```
### 数据规模
| 表 | 条目 | 单一数据源 |
|---|---|---|
| 物品 `data/items.json` | 389(词条池 45) | `tools/ingest_catalog.js` ← ⑨道具装备图鉴 |
| 敌人 `data/enemies.json` | 168 | `tools/ingest_balance.js` ← ⑩战斗数值表 |
| 技能 `data/skills.json` | 50 | `tools/ingest_balance.js` / `ingest_battle_fx.js` |
| 地图 `data/maps.json` | 36 | `tools/ingest_world.js` ← ③地图设计 |
| 任务 `data/quests.json` | 65 | `tools/ingest_world.js` ← ④支线全清单 |
| 对话 `data/dialogues.json` | 22 场 / 965 节点 | `tools/ingest_placement.js` ← ⑤关键对话剧本 |
| NPC `data/npcs.json` | 113(死亡演出 12) | `tools/ingest_placement.js` ← ⑥配角 NPC 全名单 |
| 角色 `data/characters.json` | 8 可操控 + 2 宠物 | `tools/ingest_catalog.js` ← ①角色图谱 |
| 缘 `data/bonds.json` | 10 节点 / 45 对 / 羁绊技 19 | `tools/ingest_catalog.js` ← ⑦角色关系网 |
| 结局 `data/endings.json` | 6 | `tools/ingest_world.js` |
| 商店 `data/shops.json` | 8 | `tools/ingest_catalog.js` |
| 市民 `data/citizens.json` | 85 | `tools/ingest_citizens.js` ← ⑫市民图鉴 |
| 音频 `data/audio.json` | BGM 24 / 音效 20 类 / 环境音 8 | `tools/ingest_audio.js` ← ⑧配乐与视觉设计 |
| 特效 `data/battle_fx.json` | 27 种 | `tools/ingest_battle_fx.js` |
| 提示词 `data/prompts.json` | 749 条 | 唯一机读源,生图器按 `prompt_id` 查表 |
**`data/*.json` 不要手改** —— 改 `docs/design/` 里的表格再重跑摄入器,否则下次生成会覆盖你的修改。
摄入器内部会自检(例如假谓词扫描、id 唯一性),不通过就抛异常、不落盘。
### 9 个摄入器
| 摄入器 | 产出 |
|---|---|
| `tools/ingest_world.js` | endings / items / maps / quests |
| `tools/ingest_catalog.js` | items / enemies / maps / quests / characters / bonds / shops / npcs |
| `tools/ingest_balance.js` | enemies / maps / quests / skills |
| `tools/ingest_placement.js` | dialogues / placements / npcs / maps / quests / items / enemies |
| `tools/ingest_audio.js` | audio |
| `tools/ingest_citizens.js` | citizens |
| `tools/ingest_drops.js` | drop_tables / items / enemies |
| `tools/ingest_battle_fx.js` | battle_fx / skills |
| `tools/ingest_world_maps.js` | world_maps |
幂等自检:5 个摄入器连续两轮产物逐字节一致(SHA256 比对)。
```powershell
python tools\check_idempotency.py
```
---
## 素材与音频是怎么生成的
**图像以 AI 生成稿为主、程序化生成为底**,音频与地图数据由脚本产出 —— 任何一张图、任何一个音都能从零重跑出来。
### 图像:提示词 → 原图 → 引擎贴图
| 阶段 | 代码 | 提示词在哪 | 产出 |
|---|---|---|---|
| ① AI 生图 | `tools/gen_ai_assets.py` | `data/prompts.json`(749 条,按 `prompt_id` 查表,槽位缺值即硬失败) | `assets/portraits/`、`assets/battle/`、`assets/icons/`、`assets/backgrounds/` |
| ② 量化落盘 | `tools/assetlib.py` | — | 统一量化到 **32 色板 C01–C32** + 2px 描边,单图 ≤48 色、尺寸为显示网格整数倍 |
| ② 地面 / 建筑 | `tools/gen_map_ground.py`、`tools/gen_buildings.py` | — | 36 张每图专属地面贴图 + 20 张中式建筑精灵(**项目自有程序化生成**) |
| ② 行走图 | `tools/gen_char_sprites.py` | — | 135 张 32×64 人物行走图(外部 CC0 池转译,见下节) |
| ② 敌人像(多人网格) | `tools/gen_enemy_sprite_sheets.py` | `data/enemies.json` + `data/prompts.json`(`enemy_` 形象描述) | **一张图出多个敌人**:每张 2 行×3 列素材表切成 6 个 32×64 像素像,自动检测角色数与面板底色 |
| ② 特效 | `tools/gen_vfx.py` | — | 27 种特效(24 种量产 + 3 种切片),每种 32 帧 × 256×256,清单 `assets/fx/manifest.json` |
| ② 出入口漩涡 | `tools/gen_portal.py` | — | `assets/sprites/portal/frame_00..11.png`(32×32 × 12 帧) |
| ② UI / 图块 | `tools/gen_ui.py`、`tools/gen_tiles.py` | — | UI 皮肤、57 张图块 |
| ③ 随包字体 | `tools/build_font_subset.py`、`tools/build_display_font.py` | — | `assets/fonts/GameFont.ttf`(正文子集)、`DisplayFont.ttf`(像素显示) |
生图统一走 `--provider modelscope | pollinations | mcode`,额度受限时可随时切换:
```powershell
python tools\gen_ai_assets.py --manifest assets\style_refs\production\requests_icon.json --provider modelscope --force
python tools\auto_resume_generation.py # 续跑器:一条通道额度耗尽自动退到下一条,每成功一件即记账
python tools\fill_remaining_assets.py # 补齐剩余素材(先探针,额度不足即退出)
```
敌人像素像走**多人素材表**通道(一张图出 6 个敌人,183 个敌人才跑 32 次生图):
```powershell
python tools\gen_enemy_sprite_sheets.py --dry-run # 只排清单与提示词
python tools\gen_enemy_sprite_sheets.py --all # 全量跑(断点续跑:原图在本地就只切图)
python tools\gen_enemy_sprite_sheets.py --slice-only # 不生图,只把已落盘的原图重切一遍
python tools\gen_enemy_sprite_sheets.py --only boss_qilao,enc_a6_02_01 # 只重画指定敌人
```
产物:`assets/walk/enemy_.png`(32×64 透明底像素像)、
`_ref/raw/enemy_sheet_NN.jpg`(原始素材表)、
`docs/screenshots/enemy_sprites_preview.png`(拼版预览)、
`assets/style_refs/provenance.json`(逐张登记来源与通道)。
**色板是硬约束**:所有素材只能使用 `assets/palette.json` 里的 32 个色号,中间色必须量化得到,不新增色号。
`python tools/check_art_budget.py` 校验单图色数、色板严格性、尺寸网格与图鉴并集上限。
行走图(程序化的 NPC / 市民)单图 ≤ 8 色;**敌人行走像是 AI 多人素材表切出来的,原稿细节多,给到 16 色**(仍严格落在 32 色板内)。
### 地图与数据:纯程序生成,无美术文件
| 内容 | 代码 | 说明 |
|---|---|---|
| 地图形态与地面基调 | `tools/gen_map_ground.py` | 每图一套独立拓扑(河谷 / 村邑 / 城郭 / 迷宫),地面色逐张不同 |
| 出入口漩涡 | `tools/gen_portal.py` | 12 帧旋转动画,幂等 + 自检(色板纯度 / 帧间差异 / 首尾衔接) |
| 图鉴 389 件 | `tools/ingest_catalog.js` | 单一数据源,产出 `data/items.json` |
| 敌人数值 168 种 | `tools/ingest_balance.js` | ⑩ 战斗数值表是唯一真源 |
| 65 条任务与失败条件 | `tools/ingest_world.js` | 按 ④§1.4.1 规则表确定性推导并落库 |
| 9 组抉择点 | `tools/ingest_world.js` | 写入 30 个旗标,结局路线由 `data/endings.json` 求解 |
### 音频:本地合成 + AI 母带
| 内容 | 代码 | 参数 |
|---|---|---|
| 24 首 BGM | `tools/gen_bgm.py` + `tools/extend_bgm_loop.py` | AI 母带(`assets/style_refs/bgm_provenance.json` 24/24 记录 node_id);音乐接口无 `duration` 参数、模型单次上限约 60 s,偏短曲目由 ffmpeg 按**实测循环点**拼接循环区间补足到设计时长(母带本体未改) |
| 20 类音效 + 8 条环境音 | `tools/gen_sfx.py` | 程序化合成,命名空间 `sfx_*` / `env_*` 分离;环境音经音频总监裁定**不计入**「音效 20 类」口径 |
择优与自检:
```powershell
python tools\pick_longer_bgm.py # 同 prompt 多版本择优 + 不变量自检(RAW 与母带逐字节一致)
python tools\sync_bgm_measured.py # 实测时长 / 循环点回写 ⑧§3.5 与 data/audio.json
python tools\extend_bgm_loop.py --rollback # 一键回退到模型原始母带
```
### 外部素材只有一类
- `assets/walk/` 135 张人物行走图(32×64)—— 本地 CC0《Ancient Chinese Characters Pack 1》(OpenGameArt,CC0 1.0),经 `tools/gen_char_sprites.py` 缩放、量化到 32 色板并加 2px 描边改绘;来源页 ,逐件说明见 `assets/walk/README.md`
- `assets/refs_cc0/` 外部 CC0 候选池(UI 图标 / 纹样 / 屋檐)—— 未接入运行时,仅作候选;
需要转译成 32px 网格 / 32 色板 / 2px 描边时用 `python tools/adapt_cc0_assets.py`(源池在仓库外,不入库)
两处的来源信息都只写在 `docs/audit/asset-overrides.json`,台账由 `python tools/gen_asset_ledger.py` 自动生成(当前 **1180 条**,逐条登记来源 / 作者 / 许可证 / 可否商用 / 是否修改)。
---
## 文档
设计文档全部在 [`docs/design/`](docs/design/):**剧情大纲 + ①~⑫ 共 13 份** —— 唯一可编辑源。
其中 ①~⑫ 是 SOP 阶段 1a 校验的 C0–C11 结构集,⑫《市民图鉴》是追加文档。
| 文档 | 内容 |
|---|---|
| [剧情大纲](docs/design/长生界_剧情大纲.md) | C0:主线骨架、情感内核、关键角色 |
| [① 角色图谱](docs/design/长生界_①角色图谱.md) | C1:8 名可操控角色 + 2 只宠物 + 阵营 / 反派 / 弧光 |
| [② 战斗系统详解](docs/design/长生界_②战斗系统详解.md) | C2:六维派生、伤害公式、元素克制、阵法 / 破绽 / 寿元 / 缘 |
| [③ 地图设计](docs/design/长生界_③地图设计.md) | C3:36 张地图、九幕结构、跨层通道 |
| [④ 支线全清单](docs/design/长生界_④支线全清单.md) | C4:65 条任务与失败条件 |
| [⑤ 关键对话剧本](docs/design/长生界_⑤关键对话剧本.md) | C5:22 场对话脚本、死亡旗标写入点 |
| [⑥ 配角 NPC 全名单](docs/design/长生界_⑥配角NPC全名单.md) | C6:113 名 NPC、12 名专属死亡演出 |
| [⑦ 角色关系网深度](docs/design/长生界_⑦角色关系网深度.md) | C7:10 节点 45 对缘值矩阵 |
| [⑧ 配乐与视觉设计](docs/design/长生界_⑧配乐与视觉设计.md) | C8:32 色板、BGM / 音效规格、量产清单 |
| [⑨ 道具装备图鉴](docs/design/长生界_⑨道具装备图鉴.md) | C9:389 件全量数值 |
| [⑩ 战斗数值表](docs/design/长生界_⑩战斗数值表.md) | C10:168 种敌人数值与公式 |
| [⑪ 同人 PV 脚本](docs/design/长生界_⑪同人PV脚本.md) | C11:PV 分镜与节奏 |
| [⑫ 市民图鉴](docs/design/长生界_⑫市民图鉴.md) | 追加:85 名市民与固定台词 |
其他:
| 文档 | 内容 |
|---|---|
| [阶段 0 选型](docs/specs/2026-10-20-stage0-engine-selection.md) | 引擎与素材管线选型决议 |
| **[改造设计规格](docs/specs/2026-10-20-longevity-world-design.md)** | **42 项编号需求 + 冲突决议 + 验收分类** |
| [阶段 2 计划拆分](docs/specs/2026-10-20-stage2-plan-breakdown.md) | 分阶段实施计划 |
| [百科正文](docs/lore/长生界_百科正文.md) | 设定百科 |
| [需求台账](docs/audit/requirement-ledger.json) | 52 条逐条判定(COMPLETE 必须带 `file:line` 或截图证据) |
| [授权台账](docs/audit/asset-ledger.csv) | 1180 条素材来源登记 |
| [遗留问题台账](docs/audit/known-issues.md) | 9 条遗留(含阻塞原因与解决步骤) |
| [验收报告](docs/audit/acceptance-report.md) | 阶段 6 验收审计,数字全部由脚本当场采集 |
---
## 测试体系
自研轻量 runner(不引第三方框架),分四层:
| 层 | 职责 |
|---|---|
| `unit/` | 纯函数:派生属性、伤害公式、存档 round-trip、装备与词条、寿元、掉落 |
| `data/` | 引用完整性与数量下限:id 唯一、外键存在、图鉴 ↔ 文档一致、对话指针无悬空 |
| `integration/` | 端到端:世界加载、地图推进与出口、战斗到结算、切图、商店、菜单、结局路线、触屏版式 |
| `assets/` | 被引用贴图存在、尺寸是格子整数倍、音频可解码 |
runner 带三道守卫,防止测试自我欺骗:**无断言检测**(跑完一次断言都没有的测试判失败)、
**未登记套件检测**、**带外文件计数比对**。`run_tests.ps1` 另加一道**逐文件解析检查**闸门 ——
`--check-only` 下引用 autoload 全局名必然报 "Identifier not found",脚本只把「除已知全局名以外的编译错误」判为真失败。
```powershell
.\tools\run_tests.ps1
# === 533 tests / 65940 asserts / 0 failed / 0 suite errors ===
# [guard 3] 带外计数:磁盘 41 个套件,runner 登记 41 个
```
> Godot 路径默认取 `D:\java\Godot_4.7.2\Godot_v4.7.2-stable_win64_console.exe`,可用环境变量 `$env:GODOT_BIN` 覆盖。
门槛:**每阶段结束必须四层全绿才允许提交。** 只看汇总行的计数。
---
## 开发流程与验收口径
本项目按 `rpg-game-production-sop` 的七阶段 SOP 推进:选型 → 规格 → 计划拆分 → 素材管线 → 开发 → 四层测试 → **验收审计** → 构建发布。
验收审计是核心纪律:**测试全绿 ≠ 功能可用**。每条需求都要追一次运行时调用链
(玩家动作 → 渲染 → 状态变更 → 可见反馈),拿不出 `file:line` 证据就判未完成。
| 闸门 | 命令 | 判据 |
|---|---|---|
| 阶段 1a | `node /scripts/check_creative_set.js docs/design` | exit 0(12 份文档的必备小节 / 章号对齐 / 占位回填) |
| 阶段 1b | `node /scripts/check_asset_briefs.js data/prompts.json` | exit 0(美术 / 音频两张生成规格表 + prompt_id 可解析,749 条) |
| 数据幂等 | `python tools/check_idempotency.py` | 5 个摄入器 × 2 轮逐字节一致 |
| 四层测试 | `.\tools\run_tests.ps1` | 533 tests / 65940 asserts / 0 failed / 0 suite errors,41 套件,三道守卫全过 |
| 空实现审计 | `node /scripts/audit_stubs.js .` | empty=0 selfcert=0 orphan=0 |
| 运行时接线审计 | `node tools/audit_runtime_wiring.js` | 列出「只在单元测试里被调用、运行时代码从未调用」的 public 函数 |
| 假谓词审计 | `node tools/audit_raw_predicates.js` | raw 残留 0 处 |
| 美术色数预算 | `python tools/check_art_budget.py` | 单图色数 / 32 色板严格性 / 尺寸网格 / 并集上限 |
| 需求台账 | `node /scripts/requirement_ledger.js check docs/audit/requirement-ledger.json` | 52/52 COMPLETE 放行 |
| 遗留台账 | `python tools/known_issues.py` | 渲染件与唯一可编辑源一致 |
| APK 产物 | `python tools/verify_apk.py export\changshengjie_niudengji.apk` | 4167 条目 / META-INF 签名 / 三 ABI(arm64-v8a、armeabi-v7a、x86) |
一站式复核(把上面几条收成一条命令):
```powershell
python tools\verify_sop_gates.py --with-idempotency
```
> `--with-idempotency` 会重跑摄入器两轮,**会写 `data/*.json`**;只想看闸门状态就去掉该开关。
阶段 6 验收报告可随时重出(数字全部当场采集,无手写结论):
```powershell
node tools\acceptance_report.js
```
---
## 已知限制
逐条证据与判定见 [`docs/audit/known-issues.md`](docs/audit/known-issues.md)(唯一可编辑源 `docs/audit/known-issues.json`,
渲染件由 `python tools/known_issues.py` 生成,**禁止手改**)。当前 **9 条 / 未闭环 6 条**(待办 3 / 受阻 2 / 缓办 1)。
| id | 严重度 | 类别 | 状态 | 标题 |
|---|---|---|---|---|
| ISS-01 | 高 | 外部依赖 | 已闭环 | 10/24 首 BGM 母带短于设计目标时长的 60%(ffmpeg 循环拼接已补足) |
| ISS-02 | 高 | 资产 | 已闭环 | 27 张战斗背景层未经人工画风确认(2026-10-22 已全 9 幕复核通过) |
| ISS-09 | 中 | 外部依赖 | 已闭环 | 敌方战斗立绘 133/133 与物品图标 389/389 全部换真稿(2026-10-23 收官) |
| ISS-03 | 中 | 平衡 | 缓办 | 敌方 dodge 目前不影响玩家命中率 |
| ISS-05 | 低 | 打包 | 受阻 | APK 只有调试包,出不了 release 包 |
| ISS-06 | 低 | 审计 | 受阻 | BGM 母带 provenance 24 首中 1 首缺模型 node_id(bgm_09) |
| ISS-04 | 低 | 数据 | 待办 | `data/characters.json` 里 8 名可操控角色的 expr 仍是英文 id |
| ISS-07 | 低 | 数据 | 待办 | 市民名册里 3 名「退场」市民没有绑定运行时触发条件 |
| ISS-08 | 低 | 资产 | 待办 | PV 素材包 61 件未逐张人工画风确认(换模型后仅抽样复核 4 件) |
### 已了结的既有缺陷(留档,避免重踩)
- **抗性 / 闪避的单位口径错配**:`stats.gd` 的 `res_at()` / `dodge_at()` 返回**比例**,而状态抵抗与命中公式按**百分点**消费 —— 装备的抗性 / 闪避加成在战斗中几乎不生效。已新增 `dodge_points()` / `res_points()` 两个百分点入口并改由战斗侧调用;实测 LV40 萧晨抗性 25.5 点 → 敌方状态生效概率由 98.98% 降到 40%,穿上毕业装后敌方命中率降到 55.0%
- **战斗界面浮着世界层常驻 UI**:战斗场景挂在世界场景下,任务栏 / 寿元金币栏 / 菜单 / 方向键全压在战斗画面上。已由 `world.gd::_set_world_ui_visible(false)` 在开战时收起,战斗结束自动恢复
- **`Control.anchors_preset = ...` 是仅编辑器属性**:运行时赋值是空操作,必须用 `set_anchors_preset()`。曾因此保持 0×0,「继续」按钮跑到屏幕外点不到
- **结算后 1.6 秒自动回世界**:改成画面停在结算,玩家点任意键或随手点一下屏幕才回
- **点存档槽位直接读档**:点一下就把当前进度顶掉太危险。改成点槽位只切换槽位,读档 / 存档 / 删除各有各的按钮
- **地图出口条件死锁**:36 张图里 27 张的出口条件是 `flag(pf_<本图>)`,若没有运行时写入路径出口会永久锁死(2026-10-21 手机端实测:站在河滩出口上进不去)。三条规则写在 `scripts/core/region_manager.gd` 与 `scripts/world/world.gd`,未通时给玩家看的是人话(「先了结本图之事(击败 黄河刀客)」),不出现旗标 id
- **导出包里没有字体**:三套导出预设的 `exclude_filter` 曾写着 `assets/fonts/*.otf`,16 MB 级原字体从没进过任何导出包;安卓的 fallback 缺 `)+-` 就成了豆腐块。现在随包的是裁剪后的 756 KB 子集,字体加载失败时改为 `push_error` 不再静默
### 2026-10-24 玩家实测一轮(18 条)修完的缺陷
- **界面字号不统一 / 底部提示溢出面板**:菜单与 HUD 原为 12px,在 480×270 逻辑画布上过大,底部提示行直接溢出面板。已统一到**游戏底部信息条同号(9px)**,面板随之收小(菜单 448×236、HUD 合并成一块 240×46)
- **菜单栏乱码**:内嵌字体子集是旧的,`scripts/**` 与 `data/**` 里已用但子集里没有的 **407 个字**(房 / 棺 / 途 / 偷 …)全是豆腐块。已用 `tools/build_font_subset.py` 按当前源码重建(2591 → 3011 字,887 KB)
- **关闭按钮压住页签**:页签行原本 394px 宽挤 9 个页签,最右一个被「关闭」按钮盖住。页签精简为 7 个(队伍 / 装备 / 道具 / 商店 / 任务 / 大地图 / 存档)后不再重叠
- **致谢入口每页都有**:只在最后一个页签(存档)出现
- **图鉴 / 回顾页签没了**:内容收进「任务」页的两个子模式(列表末尾的「【图鉴】」「【回顾】」)
- **道具页点左边不换右边**:点条目原本直接走「使用 / 换装」,药一口喝了、装备直接穿上,右侧说明还停在上一件。已改成点条目只切换选中,右侧详情跟着刷新
- **装备页看不到属性**:部位页右侧现在给的是装备说明 + **全部属性说明**(物攻 / 法攻 / 气血 / 暴击 / 词条 …),原来的操作说明下沉到说明下面
- **HUD 占屏**:左上任务栏与右上「寿元 / 金币」两块面板合成一块(左列地点 / 任务 / 目标,右列寿元 / 金币)
- **地图上的宝箱与剧情战点是色块**:宝箱换成 `assets/sprites/props/chest_closed / chest_open` 两张贴图;剧情战点换成敌人自己的 32×64 像素像,头顶挂品阶分色的血条与名字
- **主角在地图上不是人物像**:改用主角专属立绘转出的 48×96 行走像(`assets/walk/hero_xiaochen.png`),素材缺失时也有程序化兜底,不再退化成纯色方块
- **黄河刀客打完不消失**:战点收起判据原来只看「关联任务是否完成」,侧线任务没做完人就一直站着。现在**打赢即撤**(战点旗标置位即收起),并且剧情战点改摆到**出口前面**(`tools/ingest_placement.js` 按出口内侧 3 格摆位)
- **进第二张图人物出现在地图正中**:落点改为按离开方位推算的**入口侧**(往南离开 → 从目标图北侧进来),并在图内吸附到最近的可通行格
- **点退出键直接退出游戏**:连按两次返回键原本直接回标题,现在先弹**确认框**(取消 / 确定)
- **驿站委托一上来全摆出来**:委托板改为按委托自己的前置条件(剧情推进旗标 / 等级 / 声望 / 金币)逐步开放;委托窗口补了「关闭」按钮,点面板外暗处与返回键也能关
- **靠近 NPC 还要按 E**:改为**一走近自动开口**,提示条里的「按 E / 点此」全部去掉;没有专属对话场的人说 `data/npcs.json` 里的**地图闲谈**(65 个 NPC / 130 条),不再出现「无话可说」
- **宝箱提示停太久**:取得物品的提示条从 3.5 秒缩到 1.6 秒
- **字体子集重建后游戏里还是方框**:`assets/fonts/GameFont.ttf` 换过之后必须再跑一次 `godot --headless --path . --import`,否则 Godot 用的还是导入缓存里的旧子集(实测 HUD 里「黄河河滩」整片都是豆腐块)
- **立绘重新生成后对话里是空框**:同样必须重新 `--import` —— 换过源图的贴图,其 `.import` 会失效被删掉,`ResourceLoader.exists()` 解析不到,13 个 NPC 的对话立绘集体消失(`test_dialogues::test_all_portrait_references_resolve_to_art` 会红)。已确认重新导入后 79 张立绘全部带 `.import`,测试转绿
- **运行时新建的 Label 拿不到全局字体**:主题只沿 Control 树传递,挂在 Node2D / CanvasLayer 下的 Label 回退到系统字体,9px 下复杂汉字糊成方块。现在除 `root.theme` 外还改写 `ThemeDB` 默认主题,并在世界层标签上显式指定内嵌字体
- **Control 挂在 Node2D 下尺寸会被重置**:`Control` 进树时按最小尺寸重算,`ColorRect` 最小尺寸为 0 → 剧情战点的血条整条不可见;`Label` 尺寸塌成 0 → 居中失效。现在血条与标签都在 `add_child` 之后设尺寸
- **世界层浮动文字用 9px 读不出来**:480×270 画布里 9px 的汉字栅格化后再放大到屏幕,笔画会糊掉(菜单/HUD 按定的 9px 保留,世界层的敌人血条名字与节点标签放大到 12px / 10px)
- **立绘主体出框(6 张)**:模型无视 aspect_ratio(1:2 请求回 2816×1536 横幅),固定 ROI + 居中裁切把人物切到画框外。`tools/gen_ai_assets.py` 改为**主体感知取景**(抠底后按不透明像素的稠密核心定位人物,再按目标比例取景,并用与测试同口径的「中心方框主体占比」自检),6 张立绘重出后占比 0.15 → 0.21~0.98
- **敌人像批量出图(一张图出多个敌人)**:183 个敌人原本是 `tools/gen_map_sprites.py` 程序化拼的人形色块,只能分出品阶。新增 `tools/gen_enemy_sprite_sheets.py`:**一张素材表排 2 行×3 列(6 个敌人)**,切完自动缩到 32×64、量化 16 色、加 2px 墨黑描边。踩过的坑都固化在脚本里:
- 模型不听「3×3」格子数(要 6 个常给 5~6 个),所以**不按固定格数等分**,改成自动检测角色数;少画的角色**回队首**在下一张表接着画
- 面板底色不是纯洋红而是**粉红渐变**,固定色距会漏底、角色背后留一块红。改成 **HSV 色相带**抠底(同一面板渐变色相不变,一次抠干净)
- 面板之间的**白色分隔线**会把相邻两个角色连成一块 → 开运算去掉细线
- 填洞只补「不是面板色」的洞(白袍 / 白胡子要填实),否则会把身后那块粉底填进角色身上
- 直接缩放会在角色轮廓外缘染一圈粉边 → 先用**最近邻填色**把面板像素换成最近的角色颜色再缩放
- 缩放按**围住的方框**等分会让刀剑把包围盒撑宽、人物只占 42/64 高 → 改为**按身高定标 + 以主体为中心裁横向**,所有敌人一样高
- 某格没检出角色时,后面格子的图会**张冠李戴** → 改成按**面板格子**分配角色,缺哪格就重画哪个
- 「碑灵」这类名字会被模型画成**石碑**,12 个格子翻车 → `DESC_OVERRIDE` 里手写人形描述重画
- ModelScope 轮询任务状态时偶发 socket 超时会让整批中断 → `ai_image_providers.py` 把超时/网络错误也纳入退避重试
---
## 技术栈
Godot 4.7.2(GDScript)· Node.js 标准库摄入器 · Python 3 + Pillow/numpy 素材管线 ·
`ffmpeg` 音频转码与循环拼接 · AI 生图(ModelScope / MiniMax / Pollinations,可切换)·
AI 生乐母带(MiniMax 音乐通道)· 程序化音效与 UI 皮肤 · 32 色板硬约束量化
---
## 声明
- 本仓库是**非商业同人致敬作品**。《长生界》版权归辰东及相应权利人所有;原作专有名词仅作事实性引用,
出处已在游戏内致谢页声明(`scenes/credits.tscn`)。
- 美术、音频、程序、文本改编**均为自制再创作,未使用任何商业游戏素材**;每件素材的来源 / 作者 / 许可证 /
可否商用逐条登记在 [`docs/audit/asset-ledger.csv`](docs/audit/asset-ledger.csv)(1180 条)。
- 外部素材只有一类:`assets/walk/` 135 张行走图,来自 OpenGameArt 的《Ancient Chinese Characters Pack 1》
(CC0 1.0,可商用 / 可修改 / 不要求署名),经缩放、量化与描边改绘。
- 中文字体子集由 `tools/build_font_subset.py` 从 Noto Sans SC 裁出,遵循 SIL Open Font License 1.1。
- `export/` 为构建产物(APK 约 171 MB,超 Gitee 单文件上限)故不入库,用上面的导出命令本地复现。
- 若您是原作权利人且认为内容有侵权,请提 Issue 联系,我会立即处理。