# 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 个结局。 ![engine](https://img.shields.io/badge/Godot-4.7.2-orange) ![tests](https://img.shields.io/badge/%E5%9B%9B%E5%B1%82%E6%B5%8B%E8%AF%95-533%20tests%20%C2%B7%200%20failed-brightgreen) ![usage](https://img.shields.io/badge/%E7%94%A8%E9%80%94-%E9%9D%9E%E5%95%86%E4%B8%9A%E5%90%8C%E4%BA%BA-lightgrey) **这不是商业作品,是同人致敬项目。** 仓库内不含任何商业游戏素材,美术 / 音频 / 程序 / 文本改编均为自制再创作,详见 [声明](#声明)。 本仓库包含**从设计到可运行包体的完整链路**:13 份设计文档 → 机读数据层 → 运行时与测试 → 验收审计 → Android 调试包。 --- ## 画面 | 标题 · 序幕 | 世界 · 城市街区 | |---|---| | ![title](docs/screenshots/title_a0.png) | ![world](docs/screenshots/world_city_buildings.png) | | 战斗 · Boss 四阶段 | 战斗 · 结算 | |---|---| | ![boss](docs/screenshots/battle_a0_boss.png) | ![result](docs/screenshots/battle_a0_result.png) | | 菜单 · 装备页 | 对话 · 关键场次 | |---|---| | ![menu](docs/screenshots/menu_a0_equip.png) | ![dialogue](docs/screenshots/dialogue_c2_zhugeliang.png) | | 大地图 · 九州 | 结局 · 隐藏真结局 E6 | |---|---| | ![worldmap](docs/screenshots/world_map_jiuzhou.png) | ![ending](docs/screenshots/ending_e6.png) | > 全部 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 联系,我会立即处理。