diff --git a/design/art/art-bible.md b/design/art/art-bible.md index b06632fa..1d812125 100644 --- a/design/art/art-bible.md +++ b/design/art/art-bible.md @@ -395,7 +395,7 @@ - **交付形态**:**背景层 1 张(不透明全幅)+ 围墙层 1 张 + 八个物件层 = 10 张**,其中 9 张透明底、1 张不透明全幅 + 一份坐标表(贴图矩形/命中矩形/z 序)。背景层由**远景片 `bg_far`(2560×734)与地面片 `bg_floor`(2560×866)两片出图、按 y734 拼成一张**——整张一次出图时模型把远山挤到画面下半部,被围墙挡住后完全消失(实测三张里两张山体上缘落在墙顶以下)。接缝整条落在围墙背后,画面无接缝。**篝火例外**:命中矩形只取下半(火堆),上半(火焰)只作特效展示。围墙层落盘按细条裁,不做全幅(全幅约 3.9 MB 显存,细条约 0.35 MB);**实装落盘矩形 `(0, 203, 1280, 164)`、底锚 y367**(桩身 150px、门柱 164px 终稿锁定值,落带内〔围墙带 (0,203,1280,164)〕)。 - **色彩与氛围依据**:营地整体偏暖、安全温馨,与冒险中的地下城形成对比;现行口径 = 本文件 §2.8「全作最暖」与本节「情绪与色域」条,远景淡而退后、近景暖调为主。本节不复述色值;新增环境色按 §4.1 场景环境子板条款登记(每屏一套、≤32 色、过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门)。 - **交互四态(零新素材,全部程序化)**:悬停=**轮廓亮边 + 浮动**(浮动须整像素吸附);按下=位移 1px;**禁用/未解锁=降明度 + 去饱和**(§2 已缩窄至情绪域,状态表达不受限),**仅锻炉(锻造工坊)与宝箱(秘宝匣)两件会有此态**;已解锁=常态。 -- **钥石余额**:常驻画面顶部的一行小 HUD 栏(高 48px,**浮层覆盖,不挤压画面**)。 +- **钥石余额**:常驻画面顶部的一行小 HUD 栏(高 48px,**浮层覆盖,不挤压画面**)。栏体底=`CAMP_HEADER_BAND` `#6E4526`(§7.2 五 token 对齐表在册色)× 不透明度 0.80;栏内只钥石徽章一件、右位顶格,左位不放件。呈现定值的唯一事实源=[camp](../gdd/ux/camp.md) §顶栏,本节不复述判据。验收:目检〈顶栏成一条可辨窄带,徽章落在带内〉 - **事实源边界**:解锁条件/价格/存档写入归元进度 GDD 与 `MetaProgression`;素材命名与路径归 `asset-path-map`;清单登记归 `art-assets-checklist` §3.5。 - **已裁(2026-11-01 用户裁)**:① **营地环境子板色值**——不前置拍板:量化场景实际用色后按 §4.1 场景环境子板条款登记(每屏一套、≤32 色、过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门);② **门扇与门洞的高宽比例**——门扇贴图 240×138(锁定 240×147),随门洞 y283–367 压扁重画,坐标按门洞 x520–760 对齐;③ **帐篷的落地尺寸**——251×170 落位 (982, 335),上端遮住围墙下部。 diff --git a/design/engineering/test-conventions.md b/design/engineering/test-conventions.md index 4c249458..f8ef1ef2 100644 --- a/design/engineering/test-conventions.md +++ b/design/engineering/test-conventions.md @@ -102,6 +102,8 @@ **判据出处行锚(2026-11-05 立,自 2026-11-05 起对文件名日期不早于该日的需求卡片生效;旧卡不回头改,被编入迭代开工前由写卡段按本粒度补挂)**:对照表「需求点」列每行必须带**判据出处行锚**——该行验收判据所依据的现行文档句子,格式「`<文档相对路径>:<行号>`」。现行文档=`design/` 全域现行文档(含 `ux/` 交互规格与 art-bible 的判据行);已退役的需求卡片不是现行文档,不得充当判据出处。作用=断言预期值从规格句抄出,不从实现反推:实现行为与规格句冲突时断言红;规格缺席的行写不出锚,即停卡退回写卡段(那是需求澄清缺口,不是测试缺口)。机检=`tools/pipeline_gate.py` 判据 C5(锚格式与指向存在性,按卡文件名日期分流)+`tools/capability_audit.py` 链式锚检查(断言→套件→能力行→系统文档,见 §8.9)。 +**测试函数的判据出处注记(2026-11-06 立)**:`game/tests/**` 与 `game/probe/**` 内每个**验证函数**(函数体含 `check(`/`check_quiet(`/`_problems.append(`/`push_error(` 判定调用的函数——定义行与 `_check(` 前缀不作判定计)必须带至少一条**判据出处注记**,格式「`# oracle: :<行号> <判据一句>`」——指针在前、判据摘要句在后,断言预期值的出处随断言走:断言红时读注记即达出处文档句,零查表。指针空间=`design/` 全域现行文档(行为测试锚 owner 系统文档「验收标准」句或 `ux/` 规格句;惯例类测试〔封装扫描、判定通道纪律、驱动契约〕锚规约条款行);`game/sim/` 不进扫描面(测量面直构惯例)。与链式锚的分工:链式锚(断言→套件→能力行→系统文档)不变、属义务清单层;本注记属执行证据层、写在断言文件内,非独立账本(§8.9 法则 4 不变)。写新测试函数时当场挂注记;被指规格句改行号时随批维护指针——死指针即红。局限如实声明:注记是被动文本,句内容改而行数不变时机检抓不到(与卡片判据 C5 同限)。机检=`tools/capability_audit.py` 判据出处注记道(覆盖检查+指针存在性;扫描面零文件自检红)。规格缺席写不出锚=发现规格缺口:记发现不造锚、收尾呈报,禁锚到非现行文档凑数。 + **验收档申报(2026-11-04 立,自 2026-11-05 起对文件名日期不早于该日的需求卡片生效;旧卡不回头改)**:每张需求卡片在头部信息区(与设计事实源声明同区)申报一个验收档,三档:高/中/低。判据=失败代价单维——这条改动做错了,后果随存档或结算传播的(存档写读与迁移、RNG 流、战斗结算公式与乘区、经济入账、数值表结构校验)=高;后果玩家可见但不损持久状态的(常规玩法逻辑、门控、交互逻辑、呈现数据面)=中;后果即看即知且无状态残留的(文案、纯呈现样式、布局微调)=低。一张卡触多档按最高档申报;申报由写卡段落笔,用户批卡(U0)时确认或改判。档位只调节 §8.7 审计深度——闸机与 CI 的域划分、选闸算法、全量强制档一概不因档位变化,真实入口、判据绑定面、禁削弱等验收闭合判据全部照旧。未申报档位的卡一律按高档全量审计。机检=`tools/pipeline_gate.py` 判据 C4:卡内须含「验收档:高/中/低」申报行。 **能力清单维护锚(2026-10-29 用户裁改版定稿)**:`game/tests/README.md`「能力清单」是**实现指导+阅读导航+义务声明**——只宣布「要验证什么」,不记「是否已验证/是否跑过」;落地账归现行验收闭合链(改动—验收对照表逐条挂断言+合回前独立验收审计),本清单不承载验证状态。表为四列:能力|系统|真实入口|代表用例(`文件:函数`)。**维护归写卡段,当场入表**:写需求卡片时,本批新增能力当场写入能力行——真实入口取自设计文档已定的入口,代表用例=卡里定死的计划测试(`文件:函数`);受影响存量行(入口/用例名变更)同批更新;能力退役删行。**实现照单落名**:测试函数名以清单为唯一依据,名实不符=卡片没写清=停卡退回。**收尾核对**:批末收尾只核对表上名字与实际落地一致(改名则回表),不刷任何批次字段;实现批禁写该文件的现行红线不变(process-conventions §3 第 11 条),核对义务归收尾提交。三层模型见 §8.9。改动—验收对照表纪律不变:新增或改写的每条断言仍须挂一行(上文「测试侧」);断言改判仍受 §8.4 真实入口口径约束。 @@ -177,4 +179,4 @@ **文档纯度**:验收意图与能力描述只写玩家或系统可观测面,禁写代码路径(`.gd`/`.py`/`.sh`)、测试文件名、断言名;唯一例外=配置文件索引(`game/data/tables/*.tres` 这类表路径与表名)。规则正文见 doc-conventions §5「禁实现内容」。 -**机器闸**:本节的结构由文档域闸「能力审计」机检——GDD 纯度(同款禁实现内容口径)、系统文档验收标准节非空、能力行四列与非空、代表用例(`文件:函数`)与真实入口可核(双通道:代码在或卡在——计划中的用例与入口由需求卡片锚定)、覆盖域表与能力行对得上;红项按新增红处理。闸件清单与域划分的唯一出处=`tools/run_all_gates.sh` 头部。 +**机器闸**:本节的结构由文档域闸「能力审计」机检——GDD 纯度(同款禁实现内容口径)、系统文档验收标准节非空、能力行四列与非空、代表用例(`文件:函数`)与真实入口可核(双通道:代码在或卡在——计划中的用例与入口由需求卡片锚定)、覆盖域表与能力行对得上、判据出处注记覆盖与指针可核(§8.3 测试函数的判据出处注记);红项按新增红处理。闸件清单与域划分的唯一出处=`tools/run_all_gates.sh` 头部。 diff --git a/design/gdd/systems/冒险循环/难度曲线与Boss.md b/design/gdd/systems/冒险循环/难度曲线与Boss.md index e465f2dd..18f3b352 100644 --- a/design/gdd/systems/冒险循环/难度曲线与Boss.md +++ b/design/gdd/systems/冒险循环/难度曲线与Boss.md @@ -1,7 +1,7 @@ # 难度曲线与Boss > 一句话:难度曲线、期望值带与 Boss 设计 -> 状态:已定稿;现行为 v1.5 +> 状态:已定稿;现行为 v1.6 ## 设计目的 @@ -55,7 +55,7 @@ |---|---|---| | 关间台阶(关卡序号) | 每关至少投放一项新内容(新敌种或既有池新技能),加基准数值跳升 | 台阶必须能归因到看得见的变化,不是纯数字膨胀;投放断档是内容生产缺口,调参修补不了 | | 关内坡度(节点深度) | 同敌种数值平缓上移,深度也可表达为敌人成群 | 数量是比单体质变更优先的深度表达:保持技能池 EV 读数不变(权重即旋钮、EV 重锚顺带登记)、总量受控 | -| Boss 与精英修饰(预留第三轴) | Boss 走最深深度直填行;精英只有字段命名空间预留 | Boss 不走修饰表;修饰乘数表 MVP 为空表、零行为,首个使用场景 = 精英节点(随[关卡节点地图](关卡节点地图.md)定稿联动);数值所有权在本系统,[棋盘生成](../排雷循环/棋盘生成.md)只按表生成。精英怪设定已登记预留(与其他普通小怪同一立绘 + 附加特征标记 + 数值更强;视觉特征三件 = 名称前后缀「精英·」+ 底座光环 + 描边变色,色板合规与样张闸义务随怪物设定批实装) | +| Boss 与精英修饰(预留第三轴) | Boss 走最深深度直填行;精英怪本体走敌人表精英行(独立行+池继承附加,语义见[敌人系统](../战斗循环/敌人系统.md)「精英行」节),精英节点修饰只有字段命名空间预留 | Boss 不走修饰表;修饰乘数表 MVP 为空表、零行为,首个使用场景 = 精英节点(随[关卡节点地图](关卡节点地图.md)定稿联动);数值所有权在本系统(含精英行数值与附加技权重),[棋盘生成](../排雷循环/棋盘生成.md)只按表生成。精英怪设定:与其他普通小怪同一立绘 + 附加特征标记 + 数值更强;视觉特征三件 = 名称前缀「精英·」(由精英行名字行值承载)+ 底座光环 + 描边变色,后两件的色板合规与样张闸义务随怪物设定批实装 | **结论**:关内坡度 = 每深一档同敌种基准上移,或按"深度也可表达为敌人成群"改由数量承载——单体生命随深度的具体走向以 `enemy.tres` 行值为准(数量优先于单体;s_hp 线性式为建模工具,现行行值不承诺随深度单调上移);关间台阶 = 相邻关同敌种基准乘 1.3–1.6(域语义如上;s_hp / k_L 现行行值读 `enemy.tres`,取值域归敌人表字段定义)。 diff --git a/design/gdd/systems/战斗循环/敌人系统.md b/design/gdd/systems/战斗循环/敌人系统.md index ca486875..a2b474a4 100644 --- a/design/gdd/systems/战斗循环/敌人系统.md +++ b/design/gdd/systems/战斗循环/敌人系统.md @@ -1,7 +1,7 @@ # 敌人系统 > 一句话:敌人数据、技能池出招、意图呈现 -> 状态:已定稿;现行为 v1.8 +> 状态:已定稿;现行为 v1.9 ## 设计目的 @@ -138,6 +138,20 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错 举证:**摇定结果 = 可摇集合按权重份额归一的一次抽签;当前可摇集合 = 实例池排除冷却中与触发条件不满足的技能(保底命中时 = 条件满足的全部特殊技)。**连带可判别:把权重当固定百分比、把冷却当只锁高伤技、把蓄力当重击前奏,都是与本规约相悖的旧口径。 +### 精英行:现役敌种的强化版,池=继承基础池+附加技 + +精英怪是某个现役敌种的强化版本:玩家看到的是同一种怪(同立绘、名字带「精英·」前缀),数值更强、出招多几样。它在敌人表里是独立的一行——自己的 enemy_id、自己的数值、自己的专属池,和基础敌种的行互不覆盖;外显与内部定义因此可以是一对多(一个外观对应基础行与精英行两份定义)。精英身份由行类承载:敌人表行类新增 elite 值。 + +| 规则 | 含义 | +|---|---| +| 独立行 | 精英=敌人表行类为 elite 的一行,enemy_id 独立、不与基础敌种同名;名字前缀「精英·」由该行名字行值承载。数值字段(生命、攻击、护甲两轴、数量)与战斗行同一套定义与取值域 | +| 基础敌种引用 | 精英行用基础敌种引用字段指向一个基础敌种的 enemy_id;被指敌种必须在同关战斗行类里存在,指向不存在的敌种或另一条精英行,加载期拒绝 | +| 专属池=继承+附加 | 精英行不写自己的池模板,只写附加技清单({skill_id, weight} 数组);它的池模板=基础池+附加技合并。基础池取自同关、战斗行类、该基础敌种深度带最小的那一行的池模板——继承不复制:基础池调参自动传到精英。附加技的 skill_id 与基础池重复,加载期拒绝 | +| 合并池过同一纪律 | 池构成纪律(恰一普攻、增益 ≤1、妨害 ≤1、至少一行无条件特殊技、悬空引用致命、权重域)校验的对象是合并后的池;开战快照、实例池、冷却、保底、触发条件语义全部不变——精英实例拿到的就是合并后池的快照 | +| 暂不接线 | 精英行落表即合法;开战寻址只按节点类型取战斗或 Boss 行集,精英行在接线前不会被任何节点读到,静置合法、不返回错误。精英遭遇怎么投放到节点,归待办清单「战斗节点遭遇编排方式」条目 | + +举证:**精英池模板 = 基础池条目 ∪ 附加技条目(两者 skill_id 不相交)**;中签率公式不变,可摇权重合计含附加技权重——加附加技会摊薄基础池各技能的中签率,EV 重锚归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)。各精英行的数值与附加技行值读敌人表(`enemy.tres`),本文不登记。 + ### 触发条件:情境技与闩型解锁共用一套门槛 技能行可以带一个触发条件:条件不满足的拍,这行技能不进可摇集合——与冷却中的排除同一待遇;条件满足时,按正常权重照摇。它服务两类出招:情境技(自身低血才用的防御、上轮玩家停手才会出的偷袭等)让怪物像在"看情况出招",停手与压血因此有了代价;闩型解锁(半血解锁)让大招只在血量跌破阈值后才可被摇出。两类用例、一套机制——条件只管"现在能不能被摇到",不改权重、不强制出手。 @@ -236,7 +250,8 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错 | 优先级 | 内容 | 后置原因 | |---|---|---| | P1 | 情境条件触发器(低血触发防御、玩家停手时偷袭技等) | 已实装(三 Boss 闩型+两示范投放,见「触发条件」节与「首批落地」段) | -| P1 | 个体化变体池(A 型好斗 / B 型守势、精英专属池) | 共识点 A 的 A2 案;登记[待办清单](../../../../待办清单.md) | +| P1 | 个体化变体池(同种个体分型:A 型好斗 / B 型守势) | 共识点 A 的 A2 案;排期 Beta,登记[待办清单](../../../../待办清单.md) | +| P1 | 精英遭遇接线、精英底座光环与描边变色、精英钥石掉落 | 精英行已落表静置(见「精英行」节);接线归[待办清单](../../../../待办清单.md)「战斗节点遭遇编排方式」条目,视觉二件随怪物设定批,钥石随接线批 | | P1 | 全局特殊间隔旋钮 | 特殊技背靠背实测仍嫌时的备用闸;登记[待办清单](../../../../待办清单.md) | | P1 | 新效应词汇(妨害技 + 必中附加分量已实装——实装面 = L2 Boss 卓尔;多段命中经用户拍板退役) | 妨害类型 + rider_mult 字段已落地;登记[待办清单](../../../../待办清单.md)(已解决移除,去向见状态记录) | | P1 | 敌种扩容与投放节奏(含第二关池与投放) | 投放节奏是关卡命脉,归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) | @@ -253,7 +268,7 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错 | 参数 | 取值域 | 说明 | |---|---|---| | level_index(关卡序号) | int ≥ 1 | 行集坐标第一维:第几关;每关必有战斗类、深度带 0 的一行(锚行)与至少一行 Boss 类行——加载校验强制;关间台阶的坐标轴,行值投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) | -| row_kind | {battle, boss} | 行类:battle = 战斗节点配池(非 Boss 的战斗节点一律取此类),boss = 终点行集——Boss 战按终点配置,多终点各配一行(走哪条路决定打谁);开战按节点类型取对应类的行集 | +| row_kind | {battle, boss, elite} | 行类:battle = 战斗节点配池(非 Boss 的战斗节点一律取此类),boss = 终点行集——Boss 战按终点配置,多终点各配一行(走哪条路决定打谁);elite = 精英行(见核心规则「精英行」节,接线前不被任何节点读到);开战按节点类型取对应类的行集 | | depth_band | int ≥ 0 | 深度带 = 节点深度所在的区间(定义见核心规则「行集构成纪律」):配池按带不按点,难度坡随带递增;节点深度落在带与带之间时取首个 ≥ 深度的带键(偏难一侧);越顶带加载校验判为调参事故;带键投放归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) | | unit_count | [1, 3] | 遭遇数量区间下界;有上界的行开战时在区间内掷定,教学位节点同样掷骰 | | unit_count_max | [unit_count, 3],可省略 | 数量区间上界;开战时在区间内掷定本战实际数量;缺省 = 定值 | @@ -263,7 +278,9 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错 | armor_class(护甲等级) | int [0, 999],缺省 0 | 防守方护甲等级——基础攻击掷点的折扣判定读数,掷点结算归[战斗结算](战斗结算.md);与 armor_type(克制属性轴)两概念禁止混用;行值投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) | | hp | [1, 999] | 单位生命基线(难度修饰前) | | atk | [0, 999] | 攻击基线 | -| pool(池模板) | {skill_id, weight} 数组,weight ∈ (0, 9999] | 技能行引用在技能表;池构成纪律与悬空引用加载期拒绝(校验在数据配置跨表通道) | +| pool(池模板) | {skill_id, weight} 数组,weight ∈ (0, 9999] | battle / boss 行必填、elite 行禁填;技能行引用在技能表;池构成纪律与悬空引用加载期拒绝(校验在数据配置跨表通道) | +| base_ref(基础敌种引用) | enemy_id 字符串 | 仅 elite 行、必填(其余行类禁填);须指向同关 battle 行类存在的敌种,悬空或指向 elite 行加载期拒绝 | +| pool_extra(附加技清单) | {skill_id, weight} 数组,weight ∈ (0, 9999],非空 | 仅 elite 行、必填(其余行类禁填);skill_id 与基础池不相交;合并后池过池构成纪律 | 技能表(全局单表,技能行主键 skill_id): @@ -305,6 +322,7 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错 - 表校验:越域值、池权重 ≤ 0、空池、普攻配倍率/冷却、池增益 > 1、skill_id 悬空、行集构成违例(行集内同敌种第二行 / 同带键多行不相邻)均在加载期返回错误并止动;域内合法极端(攻击基线 0)不拒;主角表恰一行、攻击配 0 拒绝。 - 零随机回放:同一动作与受击序列驱动两个独立实例,存活投影、逐实例摇定轨迹、顺位返回序列逐项相同。 - 表实一致:敌人表、主角表、技能表三张配置表的字段与各自字段定义逐字段一致。 +- 精英行:elite 行的池模板 = 基础池(同关、战斗行类、基础敌种深度带最小行)+附加技清单,开战快照拿到合并后池;基础池改表后精英合并池随之改变;elite 行落表时任何战斗节点与 Boss 节点开战都不读到它;elite 行缺基础敌种引用或附加技清单、基础敌种引用悬空或指向 elite 行、精英 enemy_id 与基础敌种引用同名、附加技与基础池 skill_id 重复、合并池违反池构成纪律、elite 行写自己的池模板、非 elite 行写基础敌种引用或附加技清单——均在加载期返回错误并止动。 ## 待办 diff --git a/design/gdd/systems/表现与支撑/HUD框架.md b/design/gdd/systems/表现与支撑/HUD框架.md index 3ee13988..50deaeb7 100644 --- a/design/gdd/systems/表现与支撑/HUD框架.md +++ b/design/gdd/systems/表现与支撑/HUD框架.md @@ -87,7 +87,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻 | toast | 非模态 | 自动消退 2.5 秒,不打断挖掘 | | 图例面板 / 明细回看 / 胜负终局 | 模态 | 同一张登记序栈:统一入栈出栈、后开置顶、关闭逐层恢复 | -两条纪律横跨两族。其一,"第一次遇到"类提示(本作没有双击快开、挖不相邻格要先走近)走 toast,每场冒险至多弹一次。其二,拒绝反馈不属于一次性族——每次被拒都要可感知,且图标与文案双通道冗余(不靠颜色单独承载);棋盘面拒绝(动作不合棋盘规则,目标格边框闪)与资源面拒绝(行动点或储备不足,数字脉冲)必须可区分。图例面板恒可随时打开回看,不设解锁门。 +三条纪律横跨两族。其一,"第一次遇到"类提示(本作没有双击快开、挖不相邻格要先走近)走 toast,每场冒险至多弹一次。其二,拒绝反馈不属于一次性族——每次被拒都要可感知,且图标与文案双通道冗余(不靠颜色单独承载);棋盘面拒绝(动作不合棋盘规则,目标格边框闪)与资源面拒绝(行动点或储备不足,数字脉冲)必须可区分。图例面板恒可随时打开回看,不设解锁门。其三,**确认弹窗的按钮位次定值**:推进操作的钮在左、维持现状的钮(取消/留下/否)在右,三钮形态的次推进钮居中;全项目确认弹窗共用同一件、同一序,四处消费方(删档确认/营地大门有断点的三出口/营地大门无断点是否开局/篝火离场)皆然。**位次不随文案变**,语义由文案承担。 ### 呈现层订阅纪律:界面不自持状态、订阅面只留两条线 diff --git a/design/gdd/ux/camp.md b/design/gdd/ux/camp.md index a37ebf0e..6db4ea18 100644 --- a/design/gdd/ux/camp.md +++ b/design/gdd/ux/camp.md @@ -1,6 +1,6 @@ # 营地场景 UX spec(camp) -> 状态:v2.7(现行) +> 状态:v2.8(现行) > 事实源:本 spec(呈现)+ [元进度](../systems/跨局循环/元进度.md)(机制) > 美术画法:[art-bible](../../art/art-bible.md) §6.2/§7.5;素材命名与路径:[asset-path-map](../../art/asset-path-map.md) > 相邻:[level-map.md](level-map.md)(主菜单语境) @@ -38,7 +38,7 @@ ## 顶栏 -钥石余额常驻画面顶部一行小 HUD 栏(高 48px、全宽窄条、**浮层覆盖在场景图上层,不挤压画面**)。刷新入口已实装=营地刷新单口(进档与营地内写口尾触发,现开界面与顶栏余额同步取现值)。 +钥石余额常驻画面顶部一行小 HUD 栏:高 48px、全宽窄条、**浮层覆盖在场景图上层,不挤压画面**。栏体底=营地头带深棕 `#6E4526`(token `CAMP_HEADER_BAND`,色值出处 [art-bible](../../art/art-bible.md) §7.2 五 token 对齐表)× **不透明度 0.80**——半透明系定值,底下画面透出 20%。栏内**只放钥石徽章一件**,右位、顶格落座(徽章自高 48px=栏高,无顶距可言),左位不放件。验收:目检〈顶栏成一条可辨窄带、徽章落在带内〉+机检〈栏体呈色对其下方落位带底色对比 ≥3:1〉。刷新入口已实装=营地刷新单口(进档与营地内写口尾触发,现开界面与顶栏余额同步取现值)。 ## 叠层与开合 diff --git a/game/probe/board_shot_probe.gd b/game/probe/board_shot_probe.gd index 17bd56b0..76dcdade 100644 --- a/game/probe/board_shot_probe.gd +++ b/game/probe/board_shot_probe.gd @@ -26,6 +26,7 @@ var _cur_settlement = null # 当关 settlement(身份回调读取面—— func _ready() -> void: +# oracle: design/engineering/test-conventions.md:84 (棋盘截图取证——渲染闸采集组) # 运行前提=窗口化真渲染面(shot_probe 同款护栏——headless 下 frame_post_draw 永不触发+视口纹理恒空) if DisplayServer.get_name() == "headless": print("[board_shot] FAIL — 本探针必须窗口化运行(headless 无渲染面,截图必空);现行事实源=tools/render_gate.sh 取证段") @@ -181,6 +182,7 @@ func _ready() -> void: # 判据:环外径像素 ÷ 格边 落 0.82±1px;封印格最外圈 1px 框最暗值与相邻未封印未开格同位框最暗值差 ≤1/255 #(halo 在 0.455×格边归零——格边框不应被压暗;相邻未开格同砖纹理同套瓦片为无封印参照)。 func _seal_evidence(tag: String, view: Node, settlement: Object, off_img: Image, out: String) -> void: +# oracle: design/engineering/test-conventions.md:84 (封印印记像素取证组) var ms = settlement.minesweeper var seal_cell := Vector2i(-9, -9) var ref_cell := Vector2i(-9, -9) @@ -288,6 +290,7 @@ func _probe_identity_lookup(c: Vector2i): # 双帧逐像素比对:所有变化像素必须落在生效格的内缘带内 # (de < cell÷8——带外含砖心零侵入=禁整格染色/禁越圈);每个生效格须有实绘像素(不空转)。 func _check_dual_frames(tag: String, view, glow_cells: Dictionary, off_img: Image, on_img: Image) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) var board = view.board() if board == null: _problems.append("%s 双帧比对取板失败(view.board() 为 null)" % tag) diff --git a/game/probe/fx_probe.gd b/game/probe/fx_probe.gd index b26f509b..5ea3d1ad 100644 --- a/game/probe/fx_probe.gd +++ b/game/probe/fx_probe.gd @@ -8,6 +8,7 @@ extends Node func _ready() -> void: +# oracle: design/gdd/systems/战斗循环/战斗表现.md:148 (结算演出——挖掘期零打扰组) await get_tree().process_frame var problems: Array = [] diff --git a/game/probe/fx_shot_probe.gd b/game/probe/fx_shot_probe.gd index 14075ee3..b7e15910 100644 --- a/game/probe/fx_shot_probe.gd +++ b/game/probe/fx_shot_probe.gd @@ -21,6 +21,7 @@ var _problems: Array = [] func _ready() -> void: +# oracle: design/engineering/test-conventions.md:84 (特效截图取证——渲染闸采集组) var args := OS.get_cmdline_user_args() out_dir = args[0] if args.size() >= 1 and args[0] != "" else "user://qa-fx" DirAccess.make_dir_recursive_absolute(out_dir) @@ -107,6 +108,7 @@ func _ready() -> void: func _snap(tag: String) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) await RenderingServer.frame_post_draw var img: Image = get_viewport().get_texture().get_image() if img == null or img.is_empty(): @@ -116,6 +118,7 @@ func _snap(tag: String) -> void: func _fail(msg: String) -> void: +# oracle: design/engineering/test-conventions.md:56 (判定上报通道——验证函数组) _problems.append(msg) push_error("[fx_shot_probe] FAIL — " + msg) get_tree().quit(1) diff --git a/game/probe/layout_probe.gd b/game/probe/layout_probe.gd index cdeebcdc..cb68fd60 100644 --- a/game/probe/layout_probe.gd +++ b/game/probe/layout_probe.gd @@ -19,6 +19,7 @@ var _est_reads: Dictionary = {} # 逐档 13/14 板推定读数留痕(label func _ready() -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264 (战斗屏三区几何与棋盘格边长——分辨率档位组) await get_tree().process_frame var main: Node = load("res://presentation/main.tscn").instantiate() add_child(main) @@ -58,6 +59,7 @@ func _ready() -> void: # 一份不变量断言,逐档代入——代码量与档数无关 func _check_one(label: String, main: Node) -> void: +# oracle: design/gdd/systems/表现与支撑/HUD框架.md:264 (单档几何断言组) var vr: Rect2 = get_viewport().get_visible_rect() if vr.size != Vector2(VIEWPORTS[0]["size"]) and vr.size.y < 1.0: _problems.append("[%s] 逻辑视口退化 %s" % [label, vr.size]) diff --git a/game/probe/level_map_shot_probe.gd b/game/probe/level_map_shot_probe.gd index 0f920158..a201979c 100644 --- a/game/probe/level_map_shot_probe.gd +++ b/game/probe/level_map_shot_probe.gd @@ -11,6 +11,7 @@ extends Node const SEED_L3 := 910212 # 深图固定种子(与 level_map_viewport_probe 同源——图确定性承载) func _ready() -> void: +# oracle: design/engineering/test-conventions.md:84 (关卡画面截图取证——载入守卫组) var args := OS.get_cmdline_user_args() var out_dir := args[0] if args.size() > 0 else "user://" await get_tree().process_frame @@ -44,6 +45,7 @@ func _settle() -> void: func _make_runner(entry: bool, node_id: int): +# oracle: design/engineering/test-conventions.md:84 (判定上报通道——验证函数组) var rng = load("res://core/rng_service.gd").new() rng.new_adventure(SEED_L3) var runner = load("res://core/level_runner.gd").new(get_node("/root/ConfigDB"), rng) @@ -83,6 +85,7 @@ func _deepest_with_outs(screen, runner) -> int: func _shot(out_dir: String, fname: String) -> void: +# oracle: design/engineering/test-conventions.md:84 (判定上报通道——验证函数组) DirAccess.make_dir_recursive_absolute(out_dir) # 落盘目录自建(save_png 对缺目录静默失败——刷新链不预建) var img := get_viewport().get_texture().get_image() var err := img.save_png(out_dir.path_join(fname)) diff --git a/game/probe/level_map_viewport_probe.gd b/game/probe/level_map_viewport_probe.gd index 6c1ede3a..6b9a3031 100644 --- a/game/probe/level_map_viewport_probe.gd +++ b/game/probe/level_map_viewport_probe.gd @@ -22,12 +22,14 @@ var _runner # 当前停点的单关 runner(open_choices 驱动随记—— func _check(desc: String, ok: bool) -> void: +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) _checks += 1 if not ok: _problems.append(desc) func _ready() -> void: +# oracle: design/gdd/ux/level-map.md:3 (关卡画面视口跟随——卷轴视口组) await get_tree().process_frame await get_tree().process_frame var host := Control.new() @@ -60,6 +62,7 @@ func _ready() -> void: ## R1 浅图整幅居中无裁切+画布入视口件承载(画布=视口首子件——setup 注入序) func _probe_shallow_no_crop(screen, vp) -> void: +# oracle: design/gdd/ux/level-map.md:3 (浅层不裁切组) _runner = _make_runner_entry(1, SEED_L1) screen.open_choices(_cand_nodes(_runner), _runner) await get_tree().process_frame @@ -78,6 +81,7 @@ func _probe_shallow_no_crop(screen, vp) -> void: ## R5 前置+深图入口停点:锚点=入口组包围盒中心夹紧(spec :101——多入口并列取包围盒居中) func _probe_deep_entry_clamp(screen, vp) -> void: +# oracle: design/gdd/ux/level-map.md:3 (深层入口钳制组) _runner = _make_runner_entry(3, SEED_L3) screen.open_choices(_cand_nodes(_runner), _runner) await get_tree().process_frame @@ -171,6 +175,7 @@ func _probe_clamp_bounds_and_columns(screen, vp) -> void: ## R6 拖拽不触发选路提交;阈值另一侧完整点击仍提交(提交语义零改动) func _probe_drag_no_submit(screen, vp) -> void: +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) # 重建到中段可居中停点(候选=停点出边——候选节点居屏中可拖可达) var stops := _centerable_stops(screen, vp) if stops.size() < 3: @@ -209,6 +214,7 @@ func _probe_drag_no_submit(screen, vp) -> void: ## R2 停点切换后当前节点居中(视口中线投影 x=640±2——视口满屏宽即屏中线) func _probe_follow_anchor(screen, vp) -> void: +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) var stops := _centerable_stops(screen, vp) if stops.size() < 3: _problems.append("R2 可居中停点不足(=%d)" % stops.size()) @@ -224,6 +230,7 @@ func _probe_follow_anchor(screen, vp) -> void: ## R7 平滑滚动有 tween 中间帧;减动效开=位置即达(画布坐标域判定) func _probe_smooth_scroll_reduced_motion(screen, vp) -> void: +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) var saved_scale: float = UiKit.motion_scale UiKit.motion_scale = 1.0 var stops := _centerable_stops(screen, vp) @@ -263,6 +270,7 @@ func _probe_smooth_scroll_reduced_motion(screen, vp) -> void: ## R8 指引件可见性与几何(深图超窗——像素级判据②③) func _probe_guidance_chrome(screen, vp) -> void: +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) var band_l: Control = vp.get_node_or_null(NodePath("FadeL")) var band_r: Control = vp.get_node_or_null(NodePath("FadeR")) var chip: Control = vp.get_node_or_null(NodePath("PanHint")) @@ -294,6 +302,7 @@ func _probe_shallow_guidance_hidden(screen, vp) -> void: ## 入口停点 runner(defer_entry——选路态挂起、候选=入口节点) func _make_runner_entry(level_index: int, seed_val: int): +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) var rng = load("res://core/rng_service.gd").new() rng.new_adventure(seed_val) var runner = load("res://core/level_runner.gd").new(get_node("/root/ConfigDB"), rng) @@ -304,6 +313,7 @@ func _make_runner_entry(level_index: int, seed_val: int): ## 中段停点 runner(断点续玩重建——图由种子+参数档重生成同图,零 RNG 消耗) func _make_runner_at_stop(level_index: int, seed_val: int, node_id: int): +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) var rng = load("res://core/rng_service.gd").new() rng.new_adventure(seed_val) var runner = load("res://core/level_runner.gd").new(get_node("/root/ConfigDB"), rng) @@ -378,6 +388,7 @@ func _extreme_node(pos: Dictionary, nodes: Array, first: bool) -> Dictionary: func _finish() -> void: +# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组) if _checks == 0: _problems.append("零断言执行(断言数下限闸——三硬闸③)") if _problems.is_empty(): diff --git a/game/probe/probe_coverage_recorder.gd b/game/probe/probe_coverage_recorder.gd index c7f2f27f..c13abde8 100644 --- a/game/probe/probe_coverage_recorder.gd +++ b/game/probe/probe_coverage_recorder.gd @@ -199,6 +199,7 @@ func _seen_into(node: Node, keys: Array[String]) -> void: # ---- 落盘(JSONP 式 .js——file:// 页面