Compare commits

..
Author SHA1 Message Date
DKChan 00d2f0503f 批件订正(迭代43): 并master(d7b8839f,迭代41测试债批)合入态更新——S10四头注与S11罕见词存量红已由该批清偿归绿、hp_value tween缺陷已修(补间目标改子对象),反哺点7转消解留档;合入态复跑样张30/30与单测18/18全绿 2026-10-29 23:38:27 +08:00
DKChan d7b8839fcf Merge branch 'master' into iter43 2026-10-29 23:35:12 +08:00
DKChan 938055df3b 批件(迭代43 T10): 实现报告/反哺点清单/代拍板追认单三件落reports——对照表V1–V10实际行号版(卡片行号因迭代41合入漂移,实质零冲突已实查)/实现决定记录12条/存量红判别(S10四头注与S11罕见均基准已在,零新增)/目检登记与遗留读数/反哺点12条(卡片预登记6+新发现6:hp_value tween点号路径缺陷/名字行12px档位归属/能力清单补行/换色合并执行状态/目检登记/根流程件换代) 2026-10-29 09:00:07 +08:00
DKChan 1d1f111219 实现(迭代43 T1–T4+T9+V1/V10): v3样张脚本扩展——draw_base两版画布出口(1280×800基座+1920×1080全局等比1.35×左右黑边96px,§8.2 1920档策略)/B区按代码252出样(§8.2文270差异只登记不修)/mockup镜像实装五项定值/C区次级文对齐§7.2双色墨制(STONE_DEEP/GOLD_INK在册墨,原裸值5.9与3.5不过门)/S3b色板变体第二套(deep_underdark方向候选,信息层换色后重画)/S5三组关键帧条(受击退场高光兑现各三帧,退场末帧灰化残位禁瞬消)/S6剪影跨舞台三语境+描边同屏对照(主角深墨40,19,13/敌方暖白族)+护甲三枚展示+遮挡对照组/整屏四项断言S1–S4(文字对比最弱5.34全过4.5/明度三档带+世界底层两子带实测0.036+0.089间隔0.053/首焦唯一能量占比数字区59%/素底牌平底+零地台板身色)+战斗表现五项P1–P5(零打扰签名色扫描/IoU 0.36-0.61/帧互异+残位/双舞台边界/遮挡计数)+A7护甲对比锚(8.08/12.25/4.65全过3)+V1两版尺寸+V10基线并排+A5b成稿净空;--gen-assets四件素材产出(地台件48×12三色工艺/护甲三枚24×24,工单§5.2/§5.3口径);--save-baseline基线存档形态;机检30/30全绿 2026-10-29 08:53:59 +08:00
DKChan 16d2176576 实现(迭代43 T5–T8+V5–V9): 战斗一屏五项定值落码——血条填充#A8321F经UiKit.HP_BAR_FILL新常量(§4.2语义物件显式句,#689CE8退役值全源零残留)/条底收HP_BAR_TRACK(视图原裸值单点化)/顺位徽标提档FS_HINT 14(§7.4清单外提档)/护甲图标三键换24×24 PNG(UiKit.TEX_ARMOR_*单点,缺位回退字形与立绘缺位链同构,挂载底=名字行素底)/名字行整行垫HP_BAR_BACK素底牌且基线上移至地台顶上方(§6.1/§7.2文字禁压地台,几何单点_name_plate_rect+name_line_probe扩几何与绑定面,ui_probe非空契约保持)/地台件TEX_STAGE_PLAZA九宫格横向拉伸接装(缺位回退程序化三段);三枚护甲PNG程序化产出入包;新单元套件standee_present_values_test(V5–V9真实入口断言18条:常量值+源码锚+探针几何+ResourceLoader装载面)登记run_all manifest;STAND_STONE_RIM保留(board_cell_unit口沿高光在役)与STAND_STONE_EDGE保留(缺位回退路径)——art-bible三处换色合并之地台半边经纹理件执行,常量彻底退役归文档批裁决(反哺点);随测发现既有潜在缺陷:hp_value tween点号子属性路径非法静默空转(本批未触碰该行,反哺点登记) 2026-10-29 08:53:42 +08:00
71 changed files with 3040 additions and 1901 deletions

No files matched your search

+1 -1
View File
@@ -131,7 +131,7 @@
### 3.5 营地场景图
- **要什么**:营地场景图 9 张——`scene_camp_bg`(背景)+ `scene_camp_gate / forge / chest / notebook / dummy / board / campfire`(七个可点物件)+ `scene_camp_tent`(承载件)。钥石顶栏图标沿用现役 `icon_keystone`。
- **来源/规格**:**✦ 插画级**(作者自跑远程生图管线);烛光黄 `#F6C667` + 木色 `#8A5A34`(art-bible §6.2);2× 源档出图、最近邻降采样到 1280×800;后处理=抠底/降采样/色板量化/描边统一。**出图登记照 [remote-gen-prompts](remote-gen-prompts.md) 头注轻量口径执行:用途+生成时间必注,其余参数有则记。**
- **来源/规格**:**✦ 插画级**(作者自跑远程生图管线);烛光黄 `#F6C667` + 木色 `#8A5A34`(art-bible §6.2);2× 源档出图、最近邻降采样到 1280×800;后处理=抠底/降采样/色板量化/描边统一。**每张出图须登记模型名+分辨率+seed+对话/日期**。
- **排期**:营地交互重做**美术自立批**(不占迭代位;UI 贴图重构批已搁置)。终稿落 `game/assets/art/scene/`,消费常量 `UiKit.TEX_CAMP_*`。
- **现状**:全占位。
+2 -4
View File
@@ -1,6 +1,6 @@
# 美术圣经(Art Bible)— Dungeon Sweeper
> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.23,历次修订明细见 git 提交史
> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**;现行为 v3.22,历次修订明细见 git 提交史
>
> **Art Director Sign-Off(AD-ART-BIBLE)**:历次批均未签核——此为 Lean/Solo 模式(非 PHASE-GATE)下的现行治理状态;full 门禁签核时机待用户表态。
> 风格基调:地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身。
@@ -59,8 +59,6 @@
**整屏样张闸**:格子样张验"格",整屏样张验"屏"——战斗屏/关卡屏/菜单屏/营地屏各出一张整屏样张,验收四项:①全部文字(含菜单/说明/tooltip/按钮)对其直接背景 ≥4.5:1(WCAG AA),大字(≥18px 或加粗档)底线 3:1、目标 4.5:1;②明度分层三档带成立(信息面层 > 世界中层 > 世界底层,层间灰度可分);③整屏焦点预算(§3.4:首焦唯一——数字区/立绘/主按钮,无第二焦点竞争;负空间服务焦点,不属空窗);④文字禁直接压纹理底(垫容器或罩层)。验收顺序 = **方向稿目检先行**(防"机检过闸但气质否决"式返工),目检过后机检冻结。执行时机 = 屏级视觉落码开工前;过闸前一切屏级视觉为占位临时态(§7.1 完成条款)。**样张判定物的产出手段与素材输入级别不进圣经定值**——圣经只定样张要验什么,不定样张以何种手段产出、也不定它取哪一级别素材输入。验收:机检〈整屏样张四项断言全绿,且目检先行过审〉
**战斗一屏样张登记(合并提交 3b66d1a6)**:战斗一屏样张批整屏四项机检全绿——抽样读数:文字对比最弱 5.34:1;舞台底 L0.036/已开格底 L0.089 落两子带、间隔 0.053;首焦=数字区高对比能量占比 59%;名字行素底牌平底零地台色。样张整体过闸待方向稿目检先行(真人检测,登记于体验验证清单 ㊶)
**样张现状**:格子样张已冻结,冻结凭据 = git 提交 7a847c7(机检报告)与 §4.1/§8.2 定值表;样张 v3 产物原件已丢失、未入 git。现行冻结态:色板与帧数档维持冻结;数字形制 = 亮墨三档直画 + 色牌后备(值见 §4.1);新增图类型/变异色板须重跑本节测试。验收:机检〈冻结项定值均以 §4.1/§8.2 对应 owner 表为准〉
---
@@ -376,7 +374,7 @@
### 6.1 战斗舞台背景(B 区环境)
- **双舞台常驻下的 B 区环境**:战斗画面区(站桩立绘所在)的背景 = 明亮地窟,与 §1 风格基调同源——暖倾向、无压抑;背景整体**低对比自然后退**(§3.4 焦点预算延伸):背景禁止构成完整凸形、禁止抢 B 区立绘与 D 区数字的焦点,识别层级恒为 立绘 > 背景。验收:目检〈背景无完整凸形、立绘最抢眼〉
- **舞台正面目标**:B 区 = **地窟看台**——立绘脚下地台/落影承载(世界中层),背景环境层低饱和后退、明度走世界底层档,允许光源氛围(洞顶光柱/暖光晕候选);角色禁止直接贴平色底。规格与终值归整屏样张闸(§1.4)冻结。**地台面上零文字**——暖白 `#F0E2C8` 对地台板身 `#968264` 实测仅 2.89:1,已越 §7.2 的 4.5:1 硬指标,落法按 §7.2「文字禁直接压纹理底」(垫容器、素牌或罩层),非新条款;站桩文字位改垫素底或落进结算明细带(实装=站桩名字行垫 `HP_BAR_BACK` 深档素底牌、文字基线上移至地台顶上方——2026-10-30 落地)。验收:目检〈立绘脚下有地台/落影,非贴平色底〉+机检〈B 区站桩文字位正下方像素非地台板身色 #968264〉
- **舞台正面目标**:B 区 = **地窟看台**——立绘脚下地台/落影承载(世界中层),背景环境层低饱和后退、明度走世界底层档,允许光源氛围(洞顶光柱/暖光晕候选);角色禁止直接贴平色底。规格与终值归整屏样张闸(§1.4)冻结。**地台面上零文字**——暖白 `#F0E2C8` 对地台板身 `#968264` 实测仅 2.89:1,已越 §7.2 的 4.5:1 硬指标,落法按 §7.2「文字禁直接压纹理底」(垫容器、素牌或罩层),非新条款;站桩文字位改垫素底或落进结算明细带。验收:目检〈立绘脚下有地台/落影,非贴平色底〉+机检〈B 区站桩文字位正下方像素非地台板身色 #968264〉
- **明度分层适用**:背景氛围的情绪表达仍禁止以压暗/渐晕实现(§2 禁令);空间纵深与洞穴感走明度分层允许域——世界底层档、光照氛围、色相饱和与形体承载(§3.2 同款逻辑)。验收:方向性
- **图类型联动**:背景 tint 走 §4.3 `wall_ambient` 槽,随三关换挡(方向候选见 §6.3);紫赤仅 Boss 战允许短暂进入背景氛围纹样(§2.6 色相独占的舞台侧延伸,高饱和独占不变),非 Boss 战高饱和紫赤禁入。验收:机检〈背景吸色无非 Boss 高饱和紫赤〉
- **资产渠道**:✦ 插画级(演出类场景贴图,清单 §4 联动登记);战斗一屏样张(§1.4 清单 1.2)含背景出样。**资产契约(尺寸候选,样张冻结终稿)**:基准一张 = `scene_stage_bg.png`(源 1280×270,B 区全高含明细带底;三关换挡走 §4.3 `wall_ambient` tint 槽,不按板出三张——换皮成本复用棋盘同款逻辑),显示 1×;呈现约束按本节各条(低对比后退/明度世界底层档/光柱氛围保留)。程序化平色+光柱现行使用 = 占位;插画工单归 PRD,交付后 art-status-checklist 同步 ✅。**出图只出远景 + 中景两带**:结算期明细带底 84px 会盖住 B 区 78%–100% 段(约 59px),近景带在结算期整条不可见——画进背景等于画一件永远看不见的东西。远景/中景的构图占比归整屏样张(§1.4)。验收:目检〈背景插画内零地台结构件〉
+3 -4
View File
@@ -1,4 +1,4 @@
# 生图提示词卡(角色级资产)v2.11
# 生图提示词卡(角色级资产)v2.9
> **性质**:美术清单生图通道(✦ 用户自跑,现走线上 API)的执行侧文件——每资产一段**自包含完整提示词**:正向 = 通用底座+主体+动作已拼妥、负向 = 通用底座+增补已并妥,整段复制直接贴进工作流即可,**不需要读其他文件、不需要拼装**。人物规格转写自 [character-design.md](character-design.md)(人物设计 v1.0,自 art-bible §5 迁出,圣经 §5 为存根),风格/色彩条款转写自 [art-bible](art-bible.md)(§4.1 主色板 / §4.2 语义色规则);冲突时人物规格以 character-design 为准、色彩以 art-bible 为准。
> **边界**:本文件只管**角色级**资产的生图通道。图标/格面/样张走脚本管线(⚙ 代理出),规格在 art-bible §4.6 与 [art-assets-checklist](art-assets-checklist.md),不需要提示词。**场景级资产(营地场景图的背景层与八个物件层)另立 [camp-scene-prompts](camp-scene-prompts.md),本文件不覆盖**;两卡的角色/场景分工以「谁的资产谁出卡」为界,同一段提示词不两处维护。战斗舞台的场景级两件与护甲类型图标三枚的**出图工单**登记在本文件 §5(camp-scene-prompts 只管营地场景图,战斗屏这两类无处可落);§5 只把各权威节的定值收进逐件工单,不立新值。意图动画三档是动画资产,等一屏样张锁方案后另立规格卡,本文件不覆盖。
@@ -289,8 +289,7 @@ Seamless tileable pixel art texture of dry dirt ground, top-down, for a dungeon
> **范围注**:本文件原定位角色级(见文件头边界句),§4bis 已有「地形类同库登记」的先例,本节循同一先例补登记战斗屏两类——战斗舞台的场景级两件(① ②)与护甲类型图标三枚(③),两组在营地卡与清单里都无处落工单;角色级立绘重产(④)是本文件的本职。四组的规格定值清单 §4 已有登记,本节补的是「逐件照着就能出件」那一层。
> **定值纪律(§8 头注 定值单点纪律)**:本节**指路不立值**——色值 = 圣经 §4.1(含场景环境子板)、尺寸与缩放 = §8.2、明度带 = §2、甲档 = character-design §5.3、命名 = §8.3 + asset-path-map。本节新增零定值;凡圣经未定的值,一律进本节 §5.5 缺口,不在本节填空。
> **验收标注纪律**:逐条按三档标 `←机检: <断言>` / `←目检: <判据>` / `←方向性`,照圣经头注 2026-10-06 用户拍板的写法。
> **流程**:出图前后一律走 §0 五步;出图登记义务照 remote-gen-prompts 头注轻量口径(用途+生成时间必注,其余参数有则记),随候选图存 `artwork/candidates/<资产名>/`;登记不作正稿遴选前置。
> **产出状态**:组二地台件与组三护甲三枚已按各节 ⑤「程序化产出即终稿」产出入包(`scene_stage_plaza.png` 48×12、`ui_armor_light/ward/heavy.png` 24×24,路径见各组 ① 字段;登记=战斗一屏样张批,合并提交 3b66d1a6);组一背景插画与组四立绘重产仍待出图。
> **流程**:出图前后一律走 §0 五步;出图登记义务(模型名 + 分辨率 + seed + 对话/日期)照 remote-gen-prompts 头注,随候选图存 `artwork/candidates/<资产名>/`,缺登记的候选不入正稿遴选。
### 5.1 组一 · B 区舞台背景插画(1 件)
@@ -445,7 +444,7 @@ Do not add any of the following: realistic, photorealistic or 3D rendering; pain
1. 出件顺序 = 先主角、再三 Boss(Alpha 范围 = 主角 + 三 Boss 重产,其余敌种当前可读则保留到 Beta)。
2. **源密度判据 = 三判据②**:显示 96–188px 下脸、铲、旗、物种可认——照这条反推源档即可,逐键定档已完成(§8.2),本批不必再猜档位。
3. 手绘起稿顺序 = 剪影(§5.1 archetype)→ 体块主色 → 暗部与受光 → 描边 → 落影底盘。
4. 逐件 2–3 版候选;登记照 remote-gen-prompts 头注轻量口径(用途+生成时间必注,手绘作者有则记),随候选图存 `artwork/candidates/<资产名>/`。
4. 逐件 2–3 版候选;登记义务项 = 手绘者 / 日期 / 源档尺寸(手绘件无模型名与 seed,按此三项替换 §0 的登记项),随候选图存 `artwork/candidates/<资产名>/`。
5. 候选评审 = 并排横评,**隐去文件名与来源后按三判据逐条判读**(§0 五步第 1 步「便于横评」的手绘侧落法),不靠记忆与印象拍板。
6. 出件 = 透明 PNG(Alpha 直出,禁先出白底图再抠);入包前过 §8.3 色板检查器(报新增色 / 近色告警 / 超 48 上限拦截)。
+1 -1
View File
@@ -2,7 +2,7 @@
> **渠道定位**:生图渠道 = **远程对话式生图为主,本地 ComfyUI 为备选**;本文件是**对话操作单**(怎么开对话/贴什么参考图/怎么续写),提示词正文词库归 [art-prompts](art-prompts.md)——分工:词库改词库、操作单改流程,同一段正文不双处维护。
> **冲突优先级**:画法与色彩以 [art-bible](art-bible.md) 为准,人物规格以 [character-design](character-design.md) 为准;本单为执行侧操作单,不另立规格。
> **出图登记义务(出处备查)**:每张产出随图注明 **用途**(备选/参考/其他什么用)与 **生成时间**(YYYY-MM-DD HH:MM 格式),随候选原图同存 `artwork/candidates/<资产名>/`;模型名、seed、分辨率等参数有则随手记、无则不填,分辨率这类文件属性里自带的值不必抄。登记不作正稿遴选前置——缺注明不落选,遴选时问清用途即可。
> **出图登记义务(复现性)**:每张产出登记 **模型名 + 分辨率 + seed(可取则取)+ 对话/日期**,随候选原图同存 `artwork/candidates/<资产名>/`;缺登记的候选不入正稿遴选。
> 开新对话出图:同一角色 2–3 版候选各开新对话(同对话续写=修图);同角色换姿势在同对话续写。分辨率开大档;候选原图存 `artwork/candidates/<资产名>/`。
## 1. 主角战斗立绘
@@ -2,8 +2,8 @@
## 文档状态
- 版本:1.26
- 最近更新:2026-10-30(迭代 42 反哺点落文——ShopSession 行对外暴露格按委托壳拆除后实测直连面重写:完整直连方法清单+set_shop_pool 调用面改述;沿革查 git)
- 版本:1.25
- 最近更新:2026-10-29(零豁免五键清偿设计闭合落文——S13 三条豁免登记注销〔main 组合根/battle_orchestrator 类变量/tune_sim 测量面〕;五案设计闭合、实现批建件:ui_registry 界面注册面、RoundCycle+BattleFacts、CellUnit 两绘制域件、StandeeBody 两状态件、tune_sim 信号步进状态机;gdstyle inner-class 计数截断缺陷登记;沿革查 git)
- 引擎:Godot 4.7.2-stable(GDScript 单语言;2026-09-07 锁定)
- 评审模式:solo(`review-mode.txt`,2026-09-12 用户拍板——TD/LP 门禁按 solo 跳过,用户授权架构产出自主推进、迭代期回调)
- 覆盖 GDD:现行全集(根索引=`design/gdd/02-系统清单.md`,文档树构成以根索引为准)
@@ -106,7 +106,7 @@
| **reward_economy**(B4) | 格面资源身份映射(install 期预分配、Ready 只读、换图映射消亡)、极性路由字典面((类型, 极性)→ 字典行查询;路由执行归 B1 授予段)、F6 当量供给投影 | 封闭 API:roll_board / query_identity / query_full_snapshot / grant / grant_clear / retire_mapping / polarity_row / phase / weighted_pick;另有 is_mapped 静默布尔投影(批量工具用) | ConfigDB(RewardTable);BattleFxHost 经注入 Callable `luck_bias_query` 取偏运合计(reward_roll 发射与 bias_rewards 求和移至效果层完成);reward_roll 流(scope = 每次 install 批) | 无 |
| **helper_items**(A5,切片已实装秘宝域) | HelperItemTable、探测显影语义 | (契约规范源:`../../gdd/systems/跨局循环/秘宝.md`——桥接位:A2 查询接口 + A4 counter_item_ids) | A2 / A4 / B4 | 无 |
| **CurioDeck**(`core/curio_deck.gd`,A5 秘宝域令牌池件,迭代 37 自 AdventureRunner 拆出) | 三选一/商店共享池的持久多重集、抽签(候选抽取)、cap 库存与专长凭证三计数与判定唯一所有者——件只管池与判定,停点编排与兑付推进留冒险编排器 | `draw_candidates(channel)` 抽签;`consume_deck_token` / `consume_pool_purchase` 池消耗;`cap_ok` 上限判定;`feat_ticket_consider` 凭证判定;`stock_snapshot` / `pool_stock` 只读投影;商店购买消费经注入 Callable 链至 CurioStop 兑付单点(迭代 39 起转纯池——bind_handlers 反向回调已退役) | ConfigDB(秘宝令牌行集);注入 RngServiceCore(reward_pick 封闭流,禁自建随机) | 无(RefCounted 纯逻辑) |
| **ShopSession**(`core/shop_session.gd`,商店会话件,迭代 37 自 LevelRunner 拆出) | 关卡商店双模式(legacy 固定三件加定价/pool 共享令牌池)的货架、定价、购买与刷新唯一宿主——账本/钱包/装配集归宿主 LevelRunner(共享引用经 `bind` 注入,出账直接落同一引用改写宿主账本);legacy 模式零 RNG 零池消费(off 对照承载) | 货架与定价只读面(`shop_products` / `shop_shelf` / `current_price` / `shop_refresh_cost`)+写入口(`buy_product` / `buy_pool_item` / `shop_refresh` / `player_request_refresh` / `open_pool`);外部调用面=调用方经宿主 LevelRunner 公开成员 `shop` 直连(调用直连不留委托壳,2026-10-26 Middle Man 裁定;迭代 42 拆除),直连消费的方法=`shop_products`/`current_price`/`purchased_tier`/`buy_product`/`player_request_refresh`/`set_shop_pool`/`pool_price`/`shop_shelf`/`shop_refresh_free_left`/`shop_refresh_cost`/`shop_refresh`/`spend_gp`/`buy_pool_item`/`is_shop_pool`/`shop_bp_shelf`/`shop_bp_visit_buys`(另白盒两入口仅测试侧:`_draw_shelf`/`_spend_discounted`);set_shop_pool 调用面=pool 注入经 LevelRunner 构造 ctx 供给(构造期内部直调,原委托块注释「AdventureRunner._start_current_level 唯一调用方」系陈旧表述、随壳删除作废),测试面直连 `shop.set_shop_pool` | ConfigDB(商店行集);注入 RngServiceCore;宿主钱包/账本引用;pool 模式令牌消耗与判定经注入 Callable 转 CurioDeck | 无 |
| **ShopSession**(`core/shop_session.gd`,商店会话件,迭代 37 自 LevelRunner 拆出) | 关卡商店双模式(legacy 固定三件加定价/pool 共享令牌池)的货架、定价、购买与刷新唯一宿主——账本/钱包/装配集归宿主 LevelRunner(共享引用经 `bind` 注入,出账直接落同一引用改写宿主账本);legacy 模式零 RNG 零池消费(off 对照承载) | 货架与定价只读面(`shop_products` / `shop_shelf` / `current_price` / `shop_refresh_cost`)+写入口(`buy_product` / `buy_pool_item` / `shop_refresh` / `player_request_refresh` / `open_pool`);外部调用面 2026-10-29 D3 裁改直连(LevelRunner 同签名委托壳删除——并排卡「level_runner 商店委托层拆除」迭代 42 承接,调用方改址) | ConfigDB(商店行集);注入 RngServiceCore;宿主钱包/账本引用;pool 模式令牌消耗与判定经注入 Callable 转 CurioDeck | 无 |
| **HeroLedger**(`core/hero_ledger.gd`,冒险域主角实体账,已实装(2026-10-27 迭代 39 拆件批)) | 冒险生命周期主角数值与获得记录唯一所有者:生命与 GP 钱包(gain/spend/clear 单写原语,钱包事件发射点归件)、锻造强化快照、调试百分比、攻击/甲类型轴快照、特性持有集(活引用注入——关内兑付当关生效)、本局钥石增量与逐杀明细、真视使用记录;获得记录三层结构=获得记录列表(三选一件/商店购买/战末奖励各一条)→属性增益明细(带来源指回)→四轴汇总(由明细推导);生效上限与回血封顶公式单点归件 | 构造注入;单写原语族+只读投影;断点自交片段(账本键+获得日记键) | ConfigDB(表值读取);由 AdventureRunner 冒险开始创建、注入 CurioStop/level_runner/ShopSession/战斗 encounter | 无(RefCounted 纯逻辑) |
| **AttuneLoadout**(`core/attune_loadout.gd`,同调配装件,已实装(2026-10-27 迭代 39 拆件批)) | 装备中的秘宝件对象集唯一所有者(每件内含充能单位与重置周期);同调充能的物化行为唯一事实源——种行/补行改写时同步(RunLedger 充能字典轨与差额对账函数退役);旧档 aux_items 同调行迁移归件 | 构造注入;充能查询/消耗/按周期重置原语;断点自交片段(attunement_charges 键,键名与策略不变) | ConfigDB(秘宝行集);TreasureItems(同调行投放面);注入战斗使用路径 | 无 |
| **CurioStop**(`core/curio_stop.gd`,三选一停点件,已实装(2026-10-27 迭代 39 拆件批)) | 三选一停点编排唯一所有者(架构原则 9 本域承载):停点三态(关卡完成三选一在途/已兑付待推进/奖励节点三选一在途)、刷新三源账(刷新件 X/营地池 Y/刷新券存量/付费刷新计数)、兑付方法族(pick/refresh/redeem/skip)、凭证出箱(RAP/时沙待注入);三选一信号面自带;零自动推进停点语义承载 | 对外=开停点/刷新/兑付/放行谓词/交断点片段五组入口+信号面(curio_choice_presented/curio_resolved);由 AdventureRunner 关卡完成受理时调用 | ConfigDB;注入 CurioDeck(抽签/摘牌)与 HeroLedger(落账);随机只经 CurioDeck 的 reward_pick 封闭流 | 无 |
@@ -144,7 +144,7 @@
- **attune 族处置(2026-10-29 零豁免裁改判,原「原位保留」失效)**:attune 状态两成员归 BattleFacts、行为函数留宿主——迭代 39 副卡(AttuneLoadout 充能单源化)语义领地不破,外部三面消费点零改址;RewardGrant 经宿主 attune 读投影取 _attune_safe_seen 形态不变。
- **呈现零变化**:两快照出口签名与载荷逐字不变,内部改读两件只读投影(快照本体不读揭示态——揭示态投影消费方=宿主 A2 订阅旁路判定);treasure_interact_controller 三触点分家——use_treasure 经组合根现值回调改注 RewardGrant(每战新建编排器,禁一次性直引防悬挂),minesweeper 读与 attune_convert_state 仍走宿主取值回调(双取值面);nest_preview_count 直读面零变化。
- **S13 键账收敛义务**:12 个超标键随函数迁入新文件=基线外 NEW(use_treasure 长度/分支/局部/返回×4、_grant_routed 参数 8、_dispatch_row 参数 6、_apply_percent_row 参数 6、_apply_gp_row 参数 6、_apply_flat_row 参数 7+返回 9、_submit_flat_hero 参数 7、_fx_fact 返回 7)——卡内同步收敛为本波义务(_grant_routed 等签名随引用束注入改形、use_treasure 体拆分),禁波 0 合并 refresh 吸收新文件未还键;新件 _init 引用束注入防 max-parameters 新键。
- **验收红线**:游戏行为零变化(RNG 消费序零漂移——fx_reveal 封闭流随件迁移取号序不变、信号顺序契约冻结、明细行协议词表零触碰、同种子逐位一致);battle_orchestrator 头注发射点指址随拆件改写(词表不动,指址须真);行数恶化债清偿义务(迭代 38 特批带红合并挂账 c4c69701——本波还债归绿);S13 改善非清零口径(宿主残留 8–10 键+随迁键卡内收敛后新件零残留为达标线)。
- **验收红线**:游戏行为零变化(RNG 消费序零漂移——fx_reveal 封闭流随件迁移取号序不变、信号顺序契约冻结、明细行协议词表零触碰、同种子逐位一致);battle_orchestrator 头注发射点指址随拆件改写(词表不动,指址须真);行数恶化债清偿义务(迭代 38 特批带红合并挂账 c4c69701——本波还债归绿);PARITY 触面(battle_orchestrator.gd 被改)实跑对拍须经用户批准、不得借机重锚;S13 改善非清零口径(宿主残留 8–10 键+随迁键卡内收敛后新件零残留为达标线)。
- **S7/S8 随动**:RewardGrant 调 B4 API,两新件一并登记进 S7 B1 域文件集与 S8 结算路径 await 文件集(static_scan.sh 两清单独立、同批更新——防 await 面漏闸);轴丙 BoardInstaller 同规(调 B4 roll_board,实现批随批增行)。
### 冒险域拆分(现状登记——2026-10-26 设计闭合,2026-10-27 迭代 39 拆件批实装,本节各条即现行形态)
@@ -238,14 +238,14 @@
| **run_system_test**(系统测试运行器) | `tools/run_system_test.py` | 系统测试总入口:headless 跑真实玩法路径断言面(宿主=`res://tests/system_test.gd`)+ 三闸判定(口径同 run_smoke,摘要行兼容两种宿主格式) | 进程退出码(0=PASS / 1=FAIL) | 三闸齐备(同 run_smoke:退出码/摘要失败数/脚本错误扫描;wall-clock 超时在位) |
| **run_all**(单元与集成测试) | `game/tests/run_all.gd` | 64 套统一入口(61 单元+3 集成,2026-10-28 实数):套件 manifest 逐条「套件 → 期望退出码」,致命类 expect=1 独立子进程实跑;`negative` 字段标负面套件(仅豁免错误扫描一闸);选跑态 `--only=<词[,词…]>`(直跑形态对套件路径子串匹配只跑命中套件,零命中=用法错 quit(2),仅供开发期最窄确认——不产闸回执、不作验收凭据,批末全量闸不经选跑分支) | 逐套件结果 + 汇总退出码(任一套件失败 quit(1)) | 套件级退出码协议在位;三硬闸齐备(入口内扫子套件输出 + 编排层扫 `unit.log`;`Kit.finish` 零断言返回专用码 2)。子进程输出须经系统命令解释器重定向落盘再读——`OS.execute` 的输出管道在本机恒空 |
| **probe_gate**(场景探针) | `tools/probe_gate.sh` | headless 场景探针段编排:默认段 ui_input_probe / ui_struct_probe / layout_probe / ui_contrast_probe / **screen_flow_probe**(屏壳切换),`--click` 加跑 ui_click_probe,`--full` 加跑 ui_probe / ui_tune_probe / t17_defer_probe / fx_probe;孤儿 Godot 预清 + 批末残留清点(必须 0) | 段级 FAIL 输出 + 汇总退出码 | 每段 timeout 守卫在位(挂死 124/137 = FAIL,不静默);判定统一走 `verdict` 函数——退出码为唯一权威判据,PASS 串降级为附加判据,逐段跑 `tools/error_scan.py` |
| **render_gate**(视觉 golden) | `tools/render_gate.sh` | 窗口化渲染闸:①shot_probe 七屏截图 → shot_checker A-D 语义断言;②fx_shot_probe / board_shot_probe 取证(超时/零字节/文件缺失任一 = FAIL);③棋盘 golden 逐像素容差比对(差 ≤16 / changed ≤2%,provisional)+ 菜单 golden;④批末孤儿残留清零;⑤三段探针日志逐段脚本错误扫描 | 段级 FAIL 输出 + 汇总退出码 | 查退出码在位(每段 timeout 守卫 + 退出码/取证文件双检);SCRIPT ERROR 扫描在位(逐段 `tools/error_scan.py`,标记与 run_smoke 同源);断言数下限不适用(判定主体 = golden 容差与取证文件存在性,非断言计数) |
| **render_gate**(视觉 golden) | `tools/render_gate.sh` | 窗口化渲染闸:①shot_probe 七屏截图 → shot_checker A-D 语义断言;②fx_shot_probe / board_shot_probe 取证(超时/零字节/文件缺失任一 = FAIL);③棋盘 golden 逐像素容差比对(差 ≤16 / changed ≤2%,provisional)+ 菜单 golden;④批末孤儿残留清零 | 段级 FAIL 输出 + 汇总退出码 | 查退出码在位(每段 timeout 守卫 + 退出码/取证文件双检);**SCRIPT ERROR 扫描缺失**;断言数下限不适用(判定主体 = golden 容差与取证文件存在性,非断言计数) |
| **static_scan**(静态扫描域) | `game/tests/static_scan.sh` | 静态扫描域 S1–S10 可执行载体(grep 规则 + `tools/audit_type_hints.py`〔S9〕+ `tools/audit_file_headers.py`〔S10〕) | fail 聚合退出码(任一硬失败规则命中即 exit 1) | 查退出码在位;SCRIPT ERROR 扫描与断言数下限不适用(逐规则命中即红,无运行时错误面与断言计数);S3 现为 warning 级、不参与 fail 判定(见下表) |
### 改动域划分(2026-10-17 用户裁「代码批只跑代码闸、文档批只跑文档闸」)
| 域 | 闸 | 本批改动落在这些路径即属该域 |
|---|---|---|
| **代码域** | run_smoke、run_system_test、run_all、probe_gate `--click`、probe_gate `--full`、render_gate、static_scan | **唯一触发=`game/` 下非 `.md` 的行为载体**(代码、测试脚本、探针、场景、数据表、素材)。`game/` 内的 `.md`(README、生成清单)归文档域——2026-10-26 用户裁:判据按文件性质,不按路径前缀。2026-10-18 用户裁「跨领域执法禁绝」(提交 `7bdf53e3`)后,原 `tools/`、`artwork/` 两前缀不再把改动拉进代码域 |
| **代码域** | run_smoke、run_system_test、run_all、probe_gate `--click`、probe_gate `--full`、render_gate、static_scan、回放确定性对拍(条件件) | **唯一触发=`game/` 下非 `.md` 的行为载体**(代码、测试脚本、探针、场景、数据表、素材)。`game/` 内的 `.md`(README、生成清单)归文档域——2026-10-26 用户裁:判据按文件性质,不按路径前缀。2026-10-18 用户裁「跨领域执法禁绝」(提交 `7bdf53e3`)后,原 `tools/`、`artwork/` 两前缀不再把改动拉进代码域 |
| **文档域** | gdd_lint、doc_link_audit、doc_term_audit、流水线闸 | `game/` 内的 `.md` 与 `game/` 外的一切路径:`design/`、根规则文件(`AGENTS.md`/`roadmap.md`/`待办清单.md`/`体验验证清单.md`)、`tools/` 工具脚本、`.zcode/` 配置、`sim-data/`、`reports/` |
| **两域都含** | repo_structure_audit(熵增闸:顶层目录/根文件/图像白名单/退役路径/design 子目录五查——文档批新增或挪动 `.md` 同样会绊它;纯文本扫描秒级) | — |
@@ -267,7 +267,7 @@
| 硬闸 | run_smoke | run_system_test | run_all | probe_gate | render_gate | static_scan |
|---|---|---|---|---|---|---|
| **退出码优先** | 已落实(闸①) | 已落实(闸①,口径同 run_smoke) | 已落实(套件 manifest expect 协议) | 已落实(`verdict` 函数逐段判退出码,PASS 串不作判据) | 已落实(段级退出码 + 取证双检) | 已落实(fail 聚合退出) |
| **SCRIPT ERROR 扫描** | 已落实(闸③,外部扫描——GDScript 无脚本错误陷阱 API,闸设在外部包装) | 已落实(闸③,标记清单与 run_smoke 同源) | 已落实(入口内扫子套件输出;子进程的 stdout/stderr 须先落盘——`OS.execute` 管道在本机恒空) | 已落实(逐探针段 `tools/error_scan.py`,标记与 run_smoke 同源) | 已落实(三段探针日志逐段 `tools/error_scan.py`,标记与 run_smoke 同源) | 不适用(静态域无运行时错误面) |
| **SCRIPT ERROR 扫描** | 已落实(闸③,外部扫描——GDScript 无脚本错误陷阱 API,闸设在外部包装) | 已落实(闸③,标记清单与 run_smoke 同源) | 已落实(入口内扫子套件输出;子进程的 stdout/stderr 须先落盘——`OS.execute` 管道在本机恒空) | 已落实(逐探针段 `tools/error_scan.py`,标记与 run_smoke 同源) | **缺失**(本批未收——判定主体是 golden 容差与取证存在性;登记待办清单) | 不适用(静态域无运行时错误面) |
| **断言数下限** | 无独立下限(闸②摘要行必须在案,防「跑一半仍 PASS」;脚本错误吞断言路径由闸③兜住) | 同 run_smoke(过渡期宿主摘要行必须在案) | 已落实(`Kit.finish` 零断言返回专用退出码 2——与「断言失败=1」区分,防致命类套件的 expect 协议放过) | 已落实(退出码 0 但无该探针签名标记亦判红——防静默退出) | 不适用(golden 容差 + 取证存在性判定) | 不适用(逐规则命中判定;另有扫描面为空即判红的零命中自检) |
背景事实(run_smoke 头注在案):GDScript 运行时脚本错误不改进程退出码、不计入冒烟失败数、且静默中断所在函数剩余断言——2026-09-19 状态记录 83 实证「309 项 0 失败」实为断言未跑全。这是 SCRIPT ERROR 扫描必须存在于判定层的原因。
+1 -1
View File
@@ -48,7 +48,7 @@
7. **验收改动先行声明**——验收变化(基线换代/探针增改/旧断言改判)预先写进需求卡片,禁事后补
**完成**
8. **批末按域验收才合并(worktree 批;直改车道无批末动作,验收在 commit 点由 git 钩子执行,见 §1 第 3 条)**——在任务 worktree 内跑 `tools/run_all_gates.sh`,**按本批改动域选档**:按文件性质判——代码面=`game/` 下非 `.md` 的行为载体(代码、测试脚本、探针、场景、数据表、素材)→ `--code`;文档件=`game/` 内 `.md`(README、生成清单)与 `game/` 外一切路径(`design/`、根规则文件、`tools/` 工具脚本、`.zcode/` 配置等)→ `--docs`;两类同批都有 → 全量档不带参数。代码域按**关联档选测**(2026-10-29 用户裁):02 单元与集成只跑「需求卡片申报 ∪ diff 推导」并集的套件——申报=在册需求卡片对照表「验收测试」列提到的测试文件名,推导=闸脚本内套件族映射表按改动文件归族;改动命中公共件/数据表/autoload/数值·存档·RNG·结算触面或映射不到 → 横切回落全部套件;00 冒烟/01 系统测试/09 静态扫描三件保险恒跑不降;03 场景探针与 05 渲染闸维持影响面口径(本批 game/ 改动全在测量面 game/tests、game/sim 时记 SKIP)。所选域全绿即可合回 master,全量回归由用户自发发起(合入后无自动兜底)。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。该脚本是全部验证闸的**唯一入口、唯一清单与唯一域划分(含选闸算法与套件族映射表)**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。跑完写回执 `game/tests/.gates/receipt.json`(含 `domain`、`scope`(full|related,02 跑子集=related)与 `dirty_game` 字段),合回时由 worktree-guard 按 diff 文件性质推域强制校验(六况拦截:缺回执/quick 模式/回执未覆盖分支尖=回执 commit 之后代码面又有改动、或跑闸时代码面带着未提交改动(`dirty_game`)/域不覆盖本次改动/含新增红/master HEAD 非回执 commit 祖先=合并将产出未测内容;guard 只验域覆盖与全绿,不复核选套件清单)。回执与分支尖的对齐判据是**代码面**不是 commit 号:回执之后只追加文档件(`game/` 内 `.md`、`reports/`、`design/` 等)提交,回执照样有效,不构成复跑理由。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占合回全量——其质量义务=落批时人工确认闸可跑通且判据口径未变,闸真崩溃时合回闸 fail-closed(跑不出回执即拦),不以合回全量为兜底
8. **批末按域验收才合并(worktree 批;直改车道无批末动作,验收在 commit 点由 git 钩子执行,见 §1 第 3 条)**——在任务 worktree 内跑 `tools/run_all_gates.sh`,**按本批改动域选档**:按文件性质判——代码面=`game/` 下非 `.md` 的行为载体(代码、测试脚本、探针、场景、数据表、素材)→ `--code`;文档件=`game/` 内 `.md`(README、生成清单)与 `game/` 外一切路径(`design/`、根规则文件、`tools/` 工具脚本、`.zcode/` 配置等)→ `--docs`;两类同批都有 → 全量档不带参数。代码域按**关联档选测**(2026-10-29 用户裁):02 单元与集成只跑「需求卡片申报 ∪ diff 推导」并集的套件——申报=在册需求卡片对照表「验收测试」列提到的测试文件名,推导=闸脚本内套件族映射表按改动文件归族;改动命中公共件/数据表/autoload/数值·存档·RNG·结算触面或映射不到 → 横切回落全部套件;00 冒烟/01 系统测试/09 静态扫描三件保险恒跑不降;03/04 探针与 05 渲染闸维持影响面口径(本批 game/ 改动全在测量面 game/tests、game/sim 时记 SKIP)。所选域全绿即可合回 master,全量回归由用户自发发起(合入后无自动兜底)。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。该脚本是全部验证闸的**唯一入口、唯一清单与唯一域划分(含选闸算法与套件族映射表)**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。跑完写回执 `game/tests/.gates/receipt.json`(含 `domain`、`scope`(full|related,02 跑子集=related)与 `dirty_game` 字段),合回时由 worktree-guard 按 diff 文件性质推域强制校验(六况拦截:缺回执/quick 模式/回执未覆盖分支尖=回执 commit 之后代码面又有改动、或跑闸时代码面带着未提交改动(`dirty_game`)/域不覆盖本次改动/含新增红/master HEAD 非回执 commit 祖先=合并将产出未测内容;guard 只验域覆盖与全绿,不复核选套件清单)。回执与分支尖的对齐判据是**代码面**不是 commit 号:回执之后只追加文档件(`game/` 内 `.md`、`reports/`、`design/` 等)提交,回执照样有效,不构成复跑理由。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占合回全量——其质量义务=落批时人工确认闸可跑通且判据口径未变,闸真崩溃时合回闸 fail-closed(跑不出回执即拦),不以合回全量为兜底
9. **合并后主区零复跑**(2026-10-24 用户裁:合回重跑验的是与回执同一份字节,零信息)——合回落地后 code 批只做两件秒级事:`--editor --quit` 重刷类名缓存(在册坑:.godot 缓存不入 git,与合并后代码失配会假红假绿)+冒烟启动确认可开机(合计约 1 分钟);docs 批零动作。全量验证**每批跑一次**=worktree 批末按域闸,顺序是「先并入 master、再跑闸」(§1 第 2 条);只有两种情形允许把这次跑作废重来——① 闸红且修法动了 `game/`,② 闸已开跑后 master 又被推进(合并将产出未测内容,由合回闸祖先判定拦下,§3.9 第六况)。「跑都跑了再跑一遍保险」「闸后顺手改点 `game/` 再补一跑」都不构成复跑理由;master 上永不跑全量
10. **批次完成后的登记义务**——合回 master 后按 doc-conventions §12.1 推进待办清单条目状态(做完整条删除),涉及迭代编排的在 roadmap 迭代总表与顶部「当前位」节换代——执行入口=项目技能 batch-closure(批后收尾与中间文档退役),跨会话教训写记忆库。批次过程与结论由 git 提交信息承载,禁止再写会话状态文件(见 doc-conventions 第 10 节)。`design/` 与根规则文件(`AGENTS.md`/`roadmap.md`/`待办清单.md`/`体验验证清单.md`)实现批禁写,见 §12
11. **文档同步闸(实现批禁写文档)**——实现批**禁写 `design/` 整个目录与根规则文件**,**另禁写 `game/tests/README.md`(能力清单,归写卡段维护,test-conventions §8.9)**,要往外交代的东西一律落 `reports/`:实现报告 `reports/iter<N>-实现报告-<日期>.md`(各卡状态+实机目检总清单+遗留读数义务+实现决定记录——超白名单/超卡片明列范围事项逐条记「决了什么/为什么/代价/撤销方式」,全部按卡执行时只写一行「零拍板」)、GDD 反哺点清单 `reports/iter<N>-反哺点-<日期>.md`(每条写清哪个文件哪一节、改成什么、依据是什么)。设计权威文档(GDD/art-bible)**任何批都不直改**:用户在会话中逐点裁决后,由工程/文档设计改动批(直改类)写入 `design/`(执行入口=doc-design 技能),并把反哺条目从 `reports/` 清掉(吸收即死;退役执行=batch-closure 技能)。机械面文档(roadmap 行/术语表/README 工具注)同样归工程/文档设计改动批在合并前更新。反哺点未落地期间相关 GDD 节视为待反哺,读前先查 `reports/` 与待办清单;需求卡片必列「关联文档同步面」按两层标注
+2 -2
View File
@@ -35,7 +35,7 @@
需求卡片 §8「改动—验收对照表」每一条都要落成可执行的验证;本文定统一写法,卡里定每批的具体内容(测什么/注入哪里)。写卡期查不出执行载体的条目=不可测,当场呈报,禁拖到实现期。
1. **执行载体五选一,逐条声明**:单元测试(`tests/run_all`)/系统测试断言(`tests/system_test.gd`)/场景探针(`probe/` 下 `*_probe`)/MC 同种子逐项比对/人工核对(仅限无法自动化的纪律类需求点,如命名锁定、数值占位)。行为类条目以自动化载体为主。
2. **粒度与对应关系**:每条清单条目至少一个自动断言,且断言必须从该条目的真实入口进入;多条条目可合用一个测试函数,但每条都要有自己单独可判红的判据(取证口径见 §8.4 唯一口径——禁变异/禁撤销)。断言失败消息末尾带编号尾缀〔卡短名 Vn〕(如〔秘宝批 V3〕)——断言与卡片清单可互相 grep 查找。**工具面局限备案(2026-10-27 迭代 39 验收审计)**:`Kit.check_quiet` 静默收集形态(计数不入判定即打印、失败消息进 problems 汇总)的断言消息不进 `tools/extract_checks.py` 扫描面——验收尾缀 grep 对该通道失效;如需覆盖属提取器改版(工程批)。
2. **粒度与对应关系**:每条清单条目至少一个自动断言,且断言必须从该条目的真实入口进入;多条条目可合用一个测试函数,但撤销对应改动时每条都要能单独变红。断言失败消息末尾带编号尾缀〔卡短名 Vn〕(如〔秘宝批 V3〕)——断言与卡片清单可互相 grep 查找。**工具面局限备案(2026-10-27 迭代 39 验收审计)**:`Kit.check_quiet` 静默收集形态(计数不入判定即打印、失败消息进 problems 汇总)的断言消息不进 `tools/extract_checks.py` 扫描面——验收尾缀 grep 对该通道失效;如需覆盖属提取器改版(工程批)。
3. **断言词汇按验证对象五选一**:
| 词汇 | 适用场景 |
@@ -67,7 +67,7 @@
- 「跑一遍没崩=通过」无效:测试绿只证明**当前代码没犯这个错**,不证明**犯了这个错会被抓住**;断言存在不等于断言执行。
- **脚本错误会静默中断所在函数的剩余断言,而套件仍报全绿**(机制事实):GDScript 无脚本错误陷阱 API,错误不改进程退出码、不计失败数。三处实证:①2026-09-19「309 项 0 失败」实为断言未跑全(状态记录 83);②敌人表行集纪律 10 条断言因定型数组赋值错误从未执行(迭代 29 修复后两条正向见红=夹具本缺锚行);③`Callable` 形参类型写错致解析函数返回 null、后续断言整段蒸发。
- 由此产生两条义务:**写闸件**按 §6 配三硬闸;**写验收函数**必须自证断言全数执行——真实入口断言在当轮执行并通过(§8.4)为主证,零命中自检(扫描面为空即判红)为辅证。
- **覆盖率不是目标**:不引入行覆盖率度量(Godot 无内建,第三方属新基建);现行补洞=能力清单覆盖域表+人工判定,判定可靠性靠上列自证手段,不靠覆盖率数字。
- **覆盖率不是目标**:行覆盖率度量缺失(Godot 无内建,第三方属新基建,是否引入待办清单在册待裁);现行补洞=零指名单测清单+人工判定,判定可靠性靠上列自证手段,不靠覆盖率数字。
## 8. 改动级验收闭合(2026-10-21 立)
@@ -1,7 +1,7 @@
# 战斗表现
> 一句话:战斗动画与演出(旧体系未单独成文)
> 状态:范围草案(待设计);现行为 v1.2
> 状态:范围草案(待设计);现行为 v1.1
## 设计目的
@@ -92,8 +92,6 @@
判定与回退:第 1 项不过即演出元素回炉,禁止以牺牲可读性换氛围(改演出、不动数字口径);第 2–4 项不过对应资产或方案回炉重交,已过项结果保留。第 5 项(敌层插入位置)不产生过/不过,产出一条裁决记录:插入位置结论由[敌人系统](敌人系统.md)的呈现契约登记(~~敌人卡形态对照~~半边已于 2026-09-20 提前裁决完成——全站桩定稿,结论已回写 battle.md 起草条款)。样张通过 = 角色剪影、动画方案、战斗空间、敌层插入位置四件锁定物冻结;通过前不启动任何终稿资产与动画设计。
**现行状态(2026-10-30 战斗一屏样张批)**:五项验收的机检断言已随该批全绿(机器代理口径与实测读数查战斗一屏样张批 git 提交史,合并提交 3b66d1a6);样张整体过闸仍差方向稿目检先行(art-bible §1.4 闸形,目检项登记体验验证清单 ㊶)。
### 演出服从信息与可读性
两条现行硬约束,将来任何动画方案都禁止越过。其一,数字可读性高于氛围:挖掘期画面无杂音,动画禁止遮挡数字、禁止在挖掘期抢焦;结算期的色彩与能量走向以 art-bible 第 2 节为权威。其二,结算逐行兑现是信息载体:battle.md 已承诺不可整体跳过、只提供减动效选项(时长减半或直达终态,逐行兑现可加速不可省略),本文将来兑现其动画侧。
+231 -55
View File
@@ -27,14 +27,19 @@ var levels_total := 0 # 序列总关数(读 level 表行
var master_seed := 0 # 本局主种子(摘要第五件事)
var summary := {} # 最近一次结算的冒险摘要(idle 后保留——重开才新生成)
var _runner: LevelRunner # 当前关卡行进器(每关新建——关卡域状态天然隔离)
var _pending_next_level := false # 大奖励已兑付、等 continue_to_next_level 确认
var _last_level_payload := {} # 最近一次关卡结束载荷(失败结算的败退节点明细来源——数值验证取数面)
var _last_treasure_cleared := 0 # additive(最近一次关卡末清空计数,summary 取数面)
var _treasure_unlocked := {} # aux 解锁门控集(params.treasure_unlocked 接收面)
var _attunement: Array[Variant] = [] # 同调装配集(params.attunement 接收面,start_level 外下传 LevelRunner)
var _save_manager = null # 断点承载注入(null = 不写断点;模拟/冒烟缺省)
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
var _loadout_seeded := false # 同调行已种标记(每冒险一次——首关 bind 时点投行;行跨关存活后续关零动作)
# ---- 三选一停点(编排迁 CurioStop 件——停点三态/刷新三源/凭证出箱/信号面)----
var _curio: CurioDeck = null # 令牌池件(_init 建;纯池——多重集/抽签/cap/凭证判定唯一宿主)
var _stop: CurioStop = null # 停点件(_init 建;每冒险一次——关卡换实例不换件)
var _refresh_bank: RefreshBank = null # 刷新账件(_init 建——三源账与付费报价;宿主预算注入与断点片段直收)
var _loadout: AttuneLoadout = null # 同调配装件(_init 建;充能单源化——物化行唯一事实源)
var _bp_store: BreakpointStore # 断点组装与回放件(_init 建;六成员+27 键登记单源+读写侧机组随件——宿主读写经件)
var adventure_inventory: TreasureItems # 冒险级秘宝库存(单源化——同调未用行跨关存活的承载面;关卡域语义归 level 使用面)
var _tree_rules: Array[Variant] = [] # 树 rule 键集(Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面)
var _shop_mode := "legacy" # 商店模式("legacy"=固定三件加定价〔off 对照承载,缺省零扰动〕/"pool"=共享池抽 3 买 1 重抽;真实入口 main.gd 显式投 pool)
@@ -52,8 +57,6 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
_snapshot_breakpoint)
_loadout = AttuneLoadout.new(db, _attunement)
adventure_inventory = TreasureItems.new(db)
_bp_store = BreakpointStore.new({"db": db, "rng": rng, "stats": _stats, "wallet": _wallet,
"journal": _journal, "curio": _curio, "stop": _stop, "bank": _refresh_bank, "loadout": _loadout})
# 件出口(组件交出非委托壳——呈现/测量面取主角账三件与令牌池单点;领域拆分批接口块)
@@ -104,7 +107,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
_reset_curio_state()
levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
level_index = 1
_bp_store._pending_next_level = false
_pending_next_level = false
_shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 缺省 legacy(off 对照零扰动)
_treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {}
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive
@@ -112,7 +115,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
_refresh_bank.set_refresh_y(maxi(0, int(params.get("attune_refresh_y", 0)))) # additive(Y 冒险周期池)
_loadout.set_attunement(_attunement) # 装配集注入(活引用——种行面)
adventure_inventory.reset() # 冒险级库存重置(新局空库存)
_bp_store._loadout_seeded = false # 同调行投行延至首关 bind 时点(库存就位后)
_loadout_seeded = false # 同调行投行延至首关 bind 时点(库存就位后)
adventure_counters = {} # 冒险桶清零(效果引擎 adv_ 计数器宿主——冒险开始归零)
# 刷新练习开局注入(refresh_stock_grant 装配即得一次性加仓:
# 装配集 kind 分发扫描;只入存量账、临时账不动=V8 契约;resume 不重注入〔存量随断点已在〕)
@@ -149,50 +152,99 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
return false
master_seed = int(bp.get("master_seed", 0))
rng.restore_adventure(master_seed, int(bp.get("rng_counter", 0)))
# ---- 断点回放(R2 整族归件——件侧语句入 _bp_store、宿主侧语句按原语句序留本壳,序逐步保形)----
_bp_store.restore_ledger(bp, params) # 续玩账面回填——主角账三件片段各还原各键(件侧;clamp 类夹 ≥0;日记在档=推导比对载入、缺键=旧档汇总直载)
summary = {}
levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
level_index = int(bp.get("level_index", 1))
_bp_store._pending_next_level = false
_reset_curio_state() # 续玩三选一态回填前置清理(池与件计数归件、停点态归本类;桶归宿主字典)
_bp_store.restore_curio_state(bp) # 续玩三选一态回填(池余库/获取计数/特性持有集——件侧;dual_merge/keep_on_null 策略见 BREAKPOINT_FIELDS)
_bp_store.apply_resume_params(params) # 续玩参数面——数值账规范化与越界钳制(件侧)
_shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 续玩参数面同 start_adventure(缺省 legacy)
_treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {}
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive
_refresh_bank.set_refresh_x_cap(_attune_refresh_x_total()) # 刷新账 X 基数预算注入(续玩参数面同 start)
_bp_store.restore_stop_bank_fragments(bp, params) # 停点件/刷新账片段回填(件侧——凭证计数;刷新存量/Y 池缺键回退参数面)
_tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # A/B 面 rule 键集(续玩参数面同 start_adventure 同式,消费面 set_tree_rules 下传)
# 续玩计数器回填(adventure 域计数——缺键 = 旧版断点防御空集;_reset_curio_state 已清零在先——桶归宿主字典)
var bp_counters = _bp_store._bp_read("adventure_counters", bp)
if bp_counters is Dictionary:
adventure_counters = (bp_counters as Dictionary).duplicate(true)
_resume_ledger(bp, params)
_resume_curio_state(bp)
_resume_params(bp, params)
_resume_counters(bp)
var bp_pending: Dictionary[Variant, Variant] = bp.get("bonus_pending", {}) as Dictionary
var bp_result: Dictionary[Variant, Variant] = bp.get("bonus_result", {}) as Dictionary
if not bp_pending.is_empty():
_bp_store._pending_next_level = true
_pending_next_level = true
_state = State.RUNNING
_stop.restore_pending(bp_pending) # 选择停点直回(续玩不重跑关卡——信号经停点件中继)
return true
if not bp_result.is_empty():
_bp_store._pending_next_level = true
_pending_next_level = true
_stop.restore_resolved(bp_result) # 已兑付不重兑付只重发(置态+重发收编一步——信号面归停点件)
_state = State.RUNNING
return true
_bp_store._resume_bp = bp.duplicate(true)
_resume_bp = bp.duplicate(true)
# 充能读侧以行集为源(键值仅校验读数——回填时点在首关 bind 行集就位后,经 _loadout_on_level_inventory)
_bp_store._loadout_seeded = false
_loadout_seeded = false
_loadout.set_attunement(_attunement)
adventure_inventory.reset()
_state = State.RUNNING
return _start_current_level()
# 续玩账面回填(生命/特性/钱包/加算区/已购档/钥石/逐杀明细/真视——主角账三件片段各还原各键;
# bonus 子键 clamp 类夹 ≥0;日记在档=推导比对载入、缺键=旧档汇总直载)
func _resume_ledger(bp: Dictionary, params: Dictionary) -> void:
_stats.restore_fragment(bp, params)
_wallet.restore_fragment(bp, params)
_journal.restore_fragment(bp, params)
summary = {}
levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
level_index = int(bp.get("level_index", 1))
_pending_next_level = false
# 续玩三选一态回填(池与件计数归件、停点态归本类;dual_merge/keep_on_null 策略见 BREAKPOINT_FIELDS)
func _resume_curio_state(bp: Dictionary) -> void:
_reset_curio_state()
var bp_taken: Dictionary[Variant, Variant] = bp.get("bonus_taken", {}) as Dictionary
for id in _curio.taken.keys():
_curio.taken[id] = maxi(0, int(bp_taken.get(id, 0)))
for id in bp_taken.keys():
_curio.taken[String(id)] = maxi(0, int(bp_taken.get(id, 0))) # 身份维键(relic:<feat_id>)随快照还原
# 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径;
# relic 令牌 feat_id 随还原)
var bp_deck = _bp_read("bonus_deck", bp)
if bp_deck is Array:
var restored: Array[Variant] = []
for t in (bp_deck as Array):
var tok: Dictionary[Variant, Variant] = t as Dictionary
var rtok := {"kind": String(tok.get("kind", "")), "tier": String(tok.get("tier", ""))}
if String(rtok["kind"]) == "relic":
rtok["feat_id"] = String(tok.get("feat_id", "")) # relic 令牌 feat_id 随还原
restored.append(rtok)
_curio.deck = restored
# 特性持有集还原(缺键 = 旧版断点防御空集——主角数值账字段单点)
_stats.feats = []
for r in (bp.get("relics", []) as Array):
_stats.feats.append(String(r))
# 续玩参数面回填(同 start_adventure 参数面;生命账本越界钳制在此落——clamp 类)
func _resume_params(bp: Dictionary, params: Dictionary) -> void:
_stats.rebase_override(params.get("hero_override", {})) # 续玩参数面同 start_adventure(规范化归数值账)
_stats.attack_type = String(params.get("attack_type", "")) # 续玩参数面同 start_adventure(账号层选中态随 resume 重注入)
_stats.armor_type = String(params.get("armor_type", ""))
# 生命账本越界钳制(T17——spawn 守卫 EC16 免疫):断点可能带出高于生效上限的账本值
#(如调试浮窗调高期写出的值,实例:hero_hp=28 > 基础 26)——恢复后钳到生效上限,
# 否则首战 spawn「hero_hp 入参越界」拒收 → 「敌人系统 spawn 被拒」fatal。钳制式单点归
# 数值账 clamp_hp_to_resume_cap(保守口径不含加算区——与原钳制式逐字同)。
_stats.clamp_hp_to_resume_cap(int(db.combat.hero.max_hp))
_shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 续玩参数面同 start_adventure(缺省 legacy)
_treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {}
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive
_refresh_bank.set_refresh_x_cap(_attune_refresh_x_total()) # 刷新账 X 基数预算注入(续玩参数面同 start)
_stop.restore_fragment(bp, params) # 停点件片段回填(凭证计数)
_refresh_bank.restore_fragment(bp, params) # 刷新账片段回填(刷新存量/Y 池——Y 缺键回退参数面 params_fallback 类)
_tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # A/B 面 rule 键集(续玩参数面同 start_adventure 同式,消费面 set_tree_rules 下传)
# 续玩计数器回填(冒险域计数——凭证计数/刷新存量/Y 池随停点件片段回填)
func _resume_counters(bp: Dictionary) -> void:
# adventure 域计数器回填(缺键 = 旧版断点防御空集;_reset_curio_state 已清零在先——桶归宿主字典)
var bp_counters = _bp_read("adventure_counters", bp)
if bp_counters is Dictionary:
adventure_counters = (bp_counters as Dictionary).duplicate(true)
func _reset_curio_state() -> void:
_curio.reset() # 池与件获取计数(池半归件;停点态归停点件下行清理)
_stop.reset_adventure_state()
_bp_store._loadout_seeded = false
_loadout_seeded = false
_stats.feats = []
adventure_counters = {}
@@ -211,7 +263,7 @@ func effective_max_hp() -> int:
# 断点承载注入(组合根冒险开始时调用;模拟/冒烟不注入 = 断点面整体静默)
func set_save_manager(sm: SaveManagerCore) -> void:
_bp_store._save_manager = sm
_save_manager = sm
# 当前关卡行进器读面(呈现层/冒烟用——每关新建实例,调用方须在 level_started 后重取)
@@ -226,13 +278,13 @@ func apply_rest(pct: int, hp: int, max_hp: int) -> int:
# 大奖励确认 → 进下一关(仅兑付后受理——零自动推进的冒险级停点;三选一未兑付拒绝推进)
func continue_to_next_level() -> bool:
if not _bp_store._pending_next_level:
if not _pending_next_level:
push_error("[冒险结构] continue_to_next_level 拒绝——无待确认的关卡推进")
return false
if _stop.curio_stop_pending():
push_error("[冒险结构] continue_to_next_level 拒绝——三选一停点未兑付(零自动推进契约)")
return false
_bp_store._pending_next_level = false
_pending_next_level = false
_stop.clear_resolved()
level_index += 1
return _start_current_level()
@@ -291,9 +343,9 @@ func _loadout_on_level_inventory() -> void:
var inv = _runner.treasure_inventory
if inv == null:
return
if not _bp_store._loadout_seeded:
_bp_store._loadout_seeded = true
var bp_charges: Variant = _bp_store._bp_read("attunement_charges", _bp_store._resume_bp, null) if not _bp_store._resume_bp.is_empty() else null
if not _loadout_seeded:
_loadout_seeded = true
var bp_charges: Variant = _bp_read("attunement_charges", _resume_bp, null) if not _resume_bp.is_empty() else null
if bp_charges is Dictionary:
_loadout.restore_fragment({"attunement_charges": bp_charges}, inv) # 旧档读侧:行集为源+键值校验读数
else:
@@ -316,8 +368,8 @@ func _loadout_on_level_inventory() -> void:
# apply_adventure_summary 逐关卡完成关次置位〕;末关 Boss 照常入账;明细随摘要 additive 键供结算面归因)
func _boss_keystone_drop(li: int) -> void:
var first := true
if _bp_store._save_manager != null:
first = not _bp_store._save_manager.meta_state.career_state().boss_first_kill.has(str(li))
if _save_manager != null:
first = not _save_manager.meta_state.career_state().boss_first_kill.has(str(li))
var row: Dictionary[Variant, Variant] = db.crossrun.meta.boss_keystone_row(li)
var amount: int = int(row.get("first_kill", 0)) if first else int(row.get("repeat_kill", 0))
_journal.record_keystone_drop({"level": li, "first": first, "amount": amount}) # 逐杀明细+累计单写原语
@@ -367,9 +419,9 @@ func _start_current_level() -> bool:
# 断点续玩分支:图由主种子+参数档重生成同图(产图与计数器号位无关,停点零重抽语义随之消解),
# resume_state 带停点句柄与已到序——重建到停点后经 present_resumed_choice 呈现(信号顺序契约同入口)。
var resume_state: Dictionary[Variant, Variant] = {}
if not _bp_store._resume_bp.is_empty():
resume_state = {"node_id": int(_bp_store._bp_read("node_id", _bp_store._resume_bp, -1)),
"visited_ids": _bp_store._bp_read("visited_ids", _bp_store._resume_bp, []) as Array}
if not _resume_bp.is_empty():
resume_state = {"node_id": int(_bp_read("node_id", _resume_bp, -1)),
"visited_ids": _bp_read("visited_ids", _resume_bp, []) as Array}
# 接口①开始关卡:生命经主角账共享交接(账面当前值即开局生命——冒险开始置满 / 大奖励回满已写好);
# 主角账三件与冒险桶注入(构造注入 ctx——拆件批 T3 收口形态)。
# defer_entry=true 起每关(含首关)——先 emit level_started(呈现层在
@@ -379,7 +431,7 @@ func _start_current_level() -> bool:
if ok:
_emit_level_started()
_present_level_entry(resume_state)
_bp_store._resume_bp = {}
_resume_bp = {}
return ok
@@ -403,7 +455,7 @@ func _present_level_entry(resume_state: Dictionary) -> void:
if resume_state.is_empty() or int(resume_state.get("node_id", -1)) < 0:
_runner.present_entry_choice()
else:
_runner.restore_treasure_items(_bp_store._bp_read("aux_items", _bp_store._resume_bp, []) as Array)
_runner.restore_treasure_items(_bp_read("aux_items", _resume_bp, []) as Array)
_runner.present_resumed_choice()
@@ -412,7 +464,7 @@ func _on_level_finished(payload: Dictionary) -> void:
if state != "running":
push_error("[冒险结构] 重复关卡结束宣布拒绝——state=%s(账本与摘要零变化)" % state)
return
_bp_store._last_level_payload = payload.duplicate(true)
_last_level_payload = payload.duplicate(true)
var result := String(payload.get("result", ""))
if result == "defeat":
_stats.hero_hp = 0 # 倒下记 0(生命账本战后回写口径)
@@ -421,7 +473,7 @@ func _on_level_finished(payload: Dictionary) -> void:
if result != "victory":
push_error("[冒险结构] 关卡结束结果出域: %s" % result)
return
_bp_store._last_treasure_cleared = int(payload.get("aux_cleared", 0)) # additive(末关 summary 取数面)
_last_treasure_cleared = int(payload.get("aux_cleared", 0)) # additive(末关 summary 取数面)
# Boss 钥石逐杀掉落(击杀掉落渠道,末关照常入账;首杀/复杀读 meta.tres boss_keystone)
_boss_keystone_drop(level_index)
if level_index < levels_total:
@@ -429,7 +481,7 @@ func _on_level_finished(payload: Dictionary) -> void:
#(信号同步派发——处理器内立即确认时 _pending_next_level 必须已就位);
# 停点件内部分回血先行 → 加权洗牌掷 3 → 三选一停点(顺序即契约——回血按当刻账本与上限结算,
# 选择停点零自动推进)。载荷_base 随附钱包读数(停点件零钱包依赖——GP 件入账回调仍走 grant_curio_gp)
_bp_store._pending_next_level = true
_pending_next_level = true
var stop_base: Dictionary[Variant, Variant] = payload.duplicate(true)
stop_base["gp"] = _wallet.gp
_stop.open_level_clear(stop_base)
@@ -447,7 +499,7 @@ func _settle(result: String) -> void:
"stopped_at": 0 if result == "completed" else level_index,
"keystone_total": _journal.keystone_gained(),
"master_seed": master_seed,
"aux_cleared_last": _bp_store._last_treasure_cleared, # additive 扩签(末关清空反馈;非末关走 bonus 载荷)
"aux_cleared_last": _last_treasure_cleared, # additive 扩签(末关清空反馈;非末关走 bonus 载荷)
"keystone_drops": _journal.keystone_drop_log(), # additive 扩签(逐杀掉落明细;settle 入账明细/复盘归因数据源)
"vision_used_delta": _journal.vision_used(), # additive 扩签(真视使用本局计数;settle 聚合入档)
"pool_stock": _pool_stock_snapshot(), # additive 扩签(终局池读数:清账前快照,商店买走 vs 奖励领走 vs 枯竭的终局面)
@@ -455,19 +507,19 @@ func _settle(result: String) -> void:
}
# 败退明细(数值验证工具取数面——tools/balance_verifier.py 止步节点分布数据源;
# 仅失败局附加,键缺省 = 未败退,既有五件事消费方零感知)
if result == "failed" and not _bp_store._last_level_payload.is_empty():
if result == "failed" and not _last_level_payload.is_empty():
summary["defeat"] = {
"level_index": level_index,
"node_id": int(_bp_store._last_level_payload.get("node_id", -1)),
"node_type": String(_bp_store._last_level_payload.get("node_type", "")),
"node_id": int(_last_level_payload.get("node_id", -1)),
"node_type": String(_last_level_payload.get("node_type", "")),
}
if _runner != null:
summary["gp_final"] = int(_runner.gp) # gp_final 清账前快照(终局面板取数源=runner 钱包单点)
_runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现)
_reset_curio_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化)
if _bp_store._save_manager != null:
_bp_store._save_manager.clear_breakpoint() # 三结局清断点(退出冒险=放弃流程+清除断点)
_bp_store._save_manager.write_to_disk()
if _save_manager != null:
_save_manager.clear_breakpoint() # 三结局清断点(退出冒险=放弃流程+清除断点)
_save_manager.write_to_disk()
_state = State.SETTLING
adventure_finished.emit(summary)
_state = State.IDLE
@@ -478,7 +530,131 @@ func _on_level_choices_presented(_candidates: Array) -> void:
_snapshot_breakpoint()
# ---- 断点快照委托(组装与读侧策略机组归 breakpoint_store 件——六成员+27 键登记单源随件;
# 本壳一行转发:CurioStop 构造注入的 Callable 目标与本类 _on_level_choices_presented 调用点零改)----
# ---- 断点快照(字段登记单源:写侧拼装与读侧回退策略同登记一表)----
# 键序即快照键序(27 顶层键——写侧/读侧/键集断言三面同源)。读侧回退策略词表:
# plain 无特殊回退面(缺键取代码内缺省)/clamp 数值夹 ≥0/keep_on_null 缺键或类型不符保持既有态
# dual_merge 双向合并(bonus_taken)/params_fallback 缺键回退参数面(attune_refresh_y)
# token_migration 旧版候选形态迁移(bonus_pending.candidates)
# 注入缺省 null = 整面静默(模拟/冒烟零扰动)。
const BREAKPOINT_FIELDS := {
"level_index": {"strategy": "plain"},
"node_id": {"strategy": "plain"},
"hero_hp": {"strategy": "clamp"},
"bonus": {"strategy": "clamp"},
"gp": {"strategy": "plain"},
"shop_purchased": {"strategy": "plain"},
"shop_shelf": {"strategy": "plain"},
"shop_visit_buys": {"strategy": "plain"},
"attunement_charges": {"strategy": "keep_on_null"},
"graph_gen_v2": {"strategy": "plain"},
"master_seed": {"strategy": "plain"},
"rng_counter": {"strategy": "plain"},
"keystone": {"strategy": "plain"},
"visited_ids": {"strategy": "plain"},
"aux_items": {"strategy": "plain"},
"bonus_taken": {"strategy": "dual_merge"},
"bonus_deck": {"strategy": "keep_on_null"},
"relics": {"strategy": "plain"},
"bonus_pending": {"strategy": "token_migration"},
"feat_ticket_pity": {"strategy": "plain"},
"feat_ticket_sold": {"strategy": "plain"},
"adventure_counters": {"strategy": "keep_on_null"},
"vision_counter_used": {"strategy": "clamp"},
"refresh_stock": {"strategy": "clamp"},
"attune_refresh_y": {"strategy": "params_fallback"},
"bonus_result": {"strategy": "plain"},
"keystone_drops": {"strategy": "plain"},
"gain_journal": {"strategy": "plain"}, # 获得日记(拆件批 T4 扩列——唯一声明的断点扩展;旧档缺键=汇总直载跳过比对)
}
func _snapshot_breakpoint() -> void:
_bp_store.snapshot(state, level_index, master_seed, _runner, adventure_counters)
if _save_manager == null or state != "running":
return
var parts: Dictionary[Variant, Variant] = _bp_parts()
var bp: Dictionary[Variant, Variant] = {}
for key in BREAKPOINT_FIELDS.keys():
bp[key] = parts[key]
_save_manager.set_breakpoint(bp)
_save_manager.write_to_disk()
# 断点写侧取值(键集以 BREAKPOINT_FIELDS 为准——未登记键即取值越界,fail-fast)
func _bp_parts() -> Dictionary[Variant, Variant]:
var stats_frag: Dictionary[Variant, Variant] = _stats.breakpoint_fragment()
var wallet_frag: Dictionary[Variant, Variant] = _wallet.breakpoint_fragment()
var journal_frag: Dictionary[Variant, Variant] = _journal.breakpoint_fragment()
var bank_frag: Dictionary[Variant, Variant] = _refresh_bank.breakpoint_fragment()
var stop_frag: Dictionary[Variant, Variant] = _stop.breakpoint_fragment()
var level_frag: Dictionary[Variant, Variant] = _runner.breakpoint_fragment() if _runner != null else {
"node_id": -1, "visited_ids": [] as Array, "aux_items": [] as Array,
"shop_shelf": [] as Array, "shop_visit_buys": 0} # 三选一停点续玩分支 _runner 为 null——节点域按缺省落档
# 该两态的续玩只消费 bonus_* 附随件(选择停点/已兑付停点快照)
var parts: Dictionary[Variant, Variant] = {
"level_index": level_index,
"node_id": int(level_frag.get("node_id", -1)),
"hero_hp": int(stats_frag.get("hero_hp", 0)),
"bonus": journal_frag.get("bonus", {}),
"gp": int(wallet_frag.get("gp", 0)),
"shop_purchased": journal_frag.get("shop_purchased", {}),
"shop_shelf": level_frag.get("shop_shelf", []), # 货架入断点(离开店/非店停点 = 空架合法态)
"shop_visit_buys": int(level_frag.get("shop_visit_buys", 0)), # 本次进店已购次数(A 链定价输入)
"attunement_charges": (_loadout.breakpoint_fragment(_runner.treasure_inventory) if _runner != null and _runner.treasure_inventory != null else {"attunement_charges": {}}).get("attunement_charges", {}), # 同调主动件充能余量随断点(行派生——save v4 域键名策略不变)
"graph_gen_v2": 1, # 生成图换代代差标记(缺键 = 换代前旧断点 → resume 显式拒绝)
"master_seed": master_seed,
"rng_counter": rng.counter(),
"keystone": int(journal_frag.get("keystone", 0)),
"visited_ids": level_frag.get("visited_ids", []),
"aux_items": level_frag.get("aux_items", []),
"bonus_taken": _curio.taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
"bonus_deck": _curio.deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
"relics": stats_frag.get("relics", []), # 特性持有集(局内随机渠道获得态续玩防丢失)
"bonus_pending": stop_frag.get("bonus_pending", {}), # 三选一停点态(空 = 无停点——选择停点可中断契约)
"feat_ticket_pity": int(stop_frag.get("feat_ticket_pity", 0)), # T5 专长凭证保底计数(扩列向后兼容——旧档缺键缺省 0)
"feat_ticket_sold": int(stop_frag.get("feat_ticket_sold", 0)), # T5 专长凭证限购计数(同上)
"adventure_counters": adventure_counters.duplicate(true), # adventure 域计数器随断点(续玩跨战账不丢——桶归宿主字典)
"vision_counter_used": int(journal_frag.get("vision_counter_used", 0)), # 真视使用计数随断点(续玩跨战账不丢)
"refresh_stock": int(bank_frag.get("refresh_stock", 0)), # 刷新存量账随断点
"attune_refresh_y": int(bank_frag.get("attune_refresh_y", 0)), # Y 冒险周期池余量随断点(扩列向后兼容——旧档缺键回退参数面)
"bonus_result": stop_frag.get("bonus_result", {}), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
"keystone_drops": journal_frag.get("keystone_drops", []), # 逐杀掉落明细(摘要归因续接)
"gain_journal": journal_frag.get("gain_journal", {}), # 获得日记(唯一声明的断点扩列——日记为现行事实源)
}
# 拼盘完整性自断言(fail-fast——片段并集与登记键集不符即中止落盘)
var stray: Array[Variant] = []
for key in parts.keys():
if not BREAKPOINT_FIELDS.has(key):
stray.append(key)
if not stray.is_empty():
push_error("[冒险结构] 断点拼盘含未登记键: %s(fail-fast——片段与登记单源失同步)" % str(stray))
return parts
# 读侧单点(BREAKPOINT_FIELDS 策略标签分发;未登记键 fail-fast 不静默按 plain 兜)。
# 二次消费面(_start_current_level 经 _resume_bp 取 node_id/visited_ids/aux_items)同走此口。
func _bp_read(key: String, bp: Dictionary, fallback: Variant = null) -> Variant:
if not BREAKPOINT_FIELDS.has(key):
push_error("[冒险结构] 断点字段未登记(读侧): %s" % key)
var v: Variant = bp.get(key, fallback)
if _bp_strategy(key) == "keep_on_null" and v != null and not (v is Dictionary or v is Array):
return null # 类型不符——保持既有态(调用方跳过覆盖)
return v
# 读侧策略标签(BREAKPOINT_FIELDS 读侧消费面单点)
func _bp_strategy(key: String) -> String:
return String((BREAKPOINT_FIELDS.get(key, {}) as Dictionary).get("strategy", "plain"))
# 数值类读侧(clamp 夹 ≥0 / params_fallback 断点缺键回退参数面整额注入 / plain 直取)
func _bp_read_int(key: String, bp: Dictionary, fallback: int, params: Dictionary = {}) -> int:
match _bp_strategy(key):
"clamp":
return maxi(0, int(bp.get(key, fallback)))
"params_fallback":
return maxi(0, int(bp.get(key, int(params.get(key, fallback)))))
_:
return int(bp.get(key, fallback))
+131 -13
View File
@@ -36,10 +36,10 @@ const SOURCE_EXEMPT_CLASSES := ["hero_attack", "enemy_action", "clear_reward", "
var db: ConfigDb
var rng: RngServiceCore
# E1 封装收口:协作对象 enemy_system/status_effects/reward_economy 私有化——跨层读取一律经 presentation_snapshot()/diagnostic_snapshot() 成文出口。
# E1 封装收口:协作对象 board_generator/enemy_system/status_effects/reward_economy 私有化——跨层读取一律经 presentation_snapshot()/diagnostic_snapshot() 成文出口。
# minesweeper/action_economy 保持公有 = 架构文档 API Boundaries 的 HUD框架消费面(意图提交 settle_batch + compute_move_path
# 只读;ap/rap/相位读取)——非本文档登记的契约面,勿增新用途。
# 装板域(BoardGenerator+装板六函数族)已拆 BoardInstaller——生成器实例随件自建,宿主零成员。
var _board_generator: BoardGenerator
var minesweeper: MinesweeperRules
var action_economy: ActionEconomy
var _enemy_system: UnitContainer
@@ -230,12 +230,13 @@ var _hero_atk := 0 # 主角攻击基值快照(encounte
func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
db = db_ref
rng = rng_ref
_board_generator = BoardGenerator.new()
action_economy = ActionEconomy.new(db)
minesweeper = MinesweeperRules.new(db, action_economy)
_enemy_system = UnitContainer.new(db)
_status_effects = StatusContainer.new(db)
_reward_economy = RewardEconomy.new(db, rng)
_reward_economy.fatal_handler = _fatal # 协作件违约→本类 _fatal 封印(单点接线)
_board_generator.fatal_handler = _fatal; _reward_economy.fatal_handler = _fatal # 协作件违约→本类 _fatal 封印(单点接线)
_damage = DamageController.new(db, rng, _enemy_system, _status_effects)
_damage.emit_row_cb = _emit_row # 行协议归属本类(9 类行协议 + 来源断言不外泄)
_fx_host = BattleFxHost.new({"db": db, "rng": rng, "minesweeper": minesweeper, "action_economy": action_economy,
@@ -267,7 +268,7 @@ func begin_battle(enc: Dictionary) -> bool:
return false
if not _begin_battle_accept(enc):
return false
if not _install_and_reset():
if not _install_new_board():
return false
_reset_round_facts() # 首板 install 的初始展开事件不入轮 facts(resource 恒 false 的双保险)
_begin_battle_fx_state(enc)
@@ -410,14 +411,34 @@ func _begin_battle_preview_economy(enc: Dictionary) -> void:
action_economy.grant_reserve_ap(voucher_rap)
# ---- 装板受理薄壳(BoardInstaller 拆件后的宿主承接面——begin_battle/中途刷新/轮内刷新三调用点唯一入口)----
# 件内装板(生成→全局变异决议→装板受理→资源摇号→变异布置)成功后执行宿主态复位:五处计数清零+
# 板序推进+board_changed 发射+秘宝叠加层退役——时序逐字;复位职责留宿主(成员单点),不随件。
func _install_and_reset(during_round := false) -> bool:
var installer := BoardInstaller.new({"db": db, "rng": rng, "minesweeper": minesweeper,
"reward_economy": _reward_economy, "encounter": encounter, "fatal_cb": _fatal})
if not installer.install(during_round):
func _install_new_board(during_round := false) -> bool:
var stream: RandomNumberGenerator = rng.open_stream("board_gen")
var board: BoardData = _board_generator.generate(int(encounter.level_index), int(encounter.node_depth), stream, db)
if board == null:
return false
# 全局作用域变异决议(装板序内前置于 roll_board:图级偏斜旋钮须在资源摇号前定型——详见 _resolve_global_variant)
_resolve_global_variant(board)
var res: Dictionary[Variant, Variant] = minesweeper.install_board(board, during_round)
if not res.accepted:
_fatal("排雷玩法 install 拒绝(不变式违例): %s" % str(res))
return false
# 地图战斗资源 义务⑥:roll_board 恰一次(install 后、挖掘前);资格格 = 全部非封锁格(安全格 + 危险格,雷安同池)
var r0 := {}
for c in board.compute_r0():
r0[c] = true
var eligible: Array[Dictionary] = []
for c in board.safe_cells():
if not r0.has(c):
eligible.append({"cell": c})
for c in board.mines.keys():
if not r0.has(c):
eligible.append({"cell": c})
if not _reward_economy.roll_board(eligible):
_fatal("地图战斗资源 roll_board 被拒")
return false
# 地形变异 布置段(棋盘设置顺序第 4 步:生成棋盘 → 打开状态初始化 →
# 战斗资源格设置 → 变异格设置)——布置在初始展开与资源格之后,变异天然不落已开区
_place_variants(board)
_seal_consumed.clear() # 换图新雷 = 新实例,资格自然重置(行动经济 Rule 8)
_seal_miss_seen.clear()
_perfect_hit_true = 0 # 整图完美计数随 install 重置
@@ -431,6 +452,103 @@ func _install_and_reset(during_round := false) -> bool:
return true
# ---- 全局作用域变异 决议(装板序内 roll_board 之前)----
# boss/elite 图命中全局型:写整图 board_variants(恰一行)+ 注入资源面偏斜旋钮(图级投影在
# RewardEconomy.roll_board 消费);不消费 variant_place 流(全局取用确定性——零取号合法态)。
# 全局池空(含 L1 Boss 免疫/unlock 门控未达):字段留空、旋钮归零——_place_variants 原路走
# region 布置 = 兜底回落合法态(防配置缺行静默裸奔——provisional)。
func _resolve_global_variant(board: BoardData) -> void:
board.board_variants.clear()
_reward_economy.board_skew = 0
var row: Dictionary[Variant, Variant] = db.combat.variation.global_variant_row(int(encounter.level_index), String(board.board_type),
String(encounter.get("node_type", "")))
if row.is_empty():
return
board.board_variants = [String(row.variant_id)]
_reward_economy.board_skew = int(row.get("effect_value", 0))
# ---- 地形变异 布置段(棋盘设置顺序第 4 步)----
# 候选池门控(unlock_level ≤ 关卡序号 + board_types 与本地形交集,∅ 通配)经 config_db.variant_pool;
# 池空 = 合法态,静默零布置(框架 P0 口径不变)。零特判:本段只写格子变异数据快照,不改扫雷规则本体。
# 候选格域 = 安全格 − 初始展开区(伤害型 placement=closed_only 的结构保障——布置发生在初始展开之后,
# 已开格天然排除);危险格不吃变异(挖雷一层惩罚 + 变异伤害双层叠罚污染逐行归因,首版排除——provisional)。
# 每图至多一种类型(候选池内均匀选一);数量 = round(候选池格数×percent) +
# (关卡序号−1)×per_level_bonus——基数为候选池而非总格数(percent 语义 = 占可挖格比例,
# 关卡递增由 per_level_bonus 绝对格数承载);连通片生长(8 邻),单片 ≤ pack_max。
# 替换制:整图 board_variants 非空 = 本图已带全局型,跳过 region 布置(详见 _resolve_global_variant)。
func _place_variants(board: BoardData) -> void:
if not board.board_variants.is_empty():
return
var pool: Array[Variant] = db.combat.variation.variant_pool(int(encounter.level_index), String(board.board_type))
if pool.is_empty():
return
var r0 := {}
for c in board.compute_r0():
r0[c] = true
var candidates: Array[Vector2i] = []
for c in board.safe_cells():
if not r0.has(c):
candidates.append(c)
if candidates.is_empty():
return
var stream: RandomNumberGenerator = rng.open_stream("variant_place") # 一板恰一 scope(换图/刷新图各取号一次)
var vrow: Dictionary[Variant, Variant] = pool[stream.randi_range(0, pool.size() - 1)]
var total: int = int(round(float(candidates.size()) * float(db.combat.variation.percent_of_cells) / 100.0)) \
+ (int(encounter.level_index) - 1) * int(db.combat.variation.per_level_bonus)
total = mini(total, candidates.size())
_place_variants_apply(board, candidates, stream, vrow, total)
# 变异片生长施加面(布置域=候选格,数量=total,vrow 单型贯穿;取号序=stream 顺序消费)
func _place_variants_apply(board: BoardData, candidates: Array[Vector2i], stream: RandomNumberGenerator, vrow: Dictionary, total: int) -> void:
var placed := {}
while placed.size() < total:
var pack_size: int = mini(total - placed.size(), stream.randi_range(2, int(db.combat.variation.pack_max)))
var free: Array[Vector2i] = []
for c in candidates:
# 新片种子/生长格禁与其他已布片 8 邻贴接——否则两片粘连成一个超 pack_max 的连通分量
# (R3 随机流下实测暴露:独立生长的相邻片合流击穿单片上限断言)
if placed.has(c) or _adjacent_to_packs(board, placed, c):
continue
free.append(c)
if free.is_empty():
break # 生长域耗尽——该片就地收束(数量不足总目标为合法态,候选域碎片化时触发)
var seed_c: Vector2i = free[stream.randi_range(0, free.size() - 1)]
placed[seed_c] = true
var pack: Array[Variant] = [seed_c]
var pack_set := {seed_c: true}
while pack.size() < pack_size:
var fringe := _variants_fringe(board, pack, candidates, placed, pack_set)
if fringe.is_empty():
break # 生长域耗尽——该片就地收束(数量不足总目标为合法态,候选域碎片化时触发)
var nxt: Vector2i = fringe[stream.randi_range(0, fringe.size() - 1)]
placed[nxt] = true
pack_set[nxt] = true
pack.append(nxt)
for c in placed.keys():
board.variants[c] = [String(vrow.variant_id)]
# 片生长待选格收集(本片内各生长格的 8 邻候选并集——未布置、未在途、不与他片贴接)
func _variants_fringe(board: BoardData, pack: Array, candidates: Array[Vector2i], placed: Dictionary, pack_set: Dictionary) -> Array[Vector2i]:
var fringe: Array[Vector2i] = []
for c in pack:
for n in board.neighbors8(c):
if candidates.has(n) and not placed.has(n) and not fringe.has(n) \
and not _adjacent_to_packs(board, placed, n, pack_set):
fringe.append(n)
return fringe
# 候选格是否与其他已布片(own_pack 之外的 placed 格)8 邻贴接——片间隔离保证连通分量 = 单片 ≤ pack_max
func _adjacent_to_packs(board: BoardData, placed: Dictionary, c: Vector2i, own_pack: Dictionary = {}) -> bool:
for m in board.neighbors8(c):
if placed.has(m) and not own_pack.has(m):
return true
return false
# ---- 事件缓冲(轮 facts 收集;结算触发 = Waiting 相位编排点,不依赖到达序)----
func _on_cell_revealed(evt: Dictionary) -> void:
@@ -680,7 +798,7 @@ func _exit_distribution() -> void:
if mode == "new_board":
_reward_economy.retire_mapping()
minesweeper.retire_board()
if not _install_and_reset():
if not _install_new_board():
_fatal("换图编排失败")
return
# 工兵晨令续队直调拆除(内容数据驱动扩展批 T4——round_start 订阅行承载,见 begin_battle 注)
@@ -714,7 +832,7 @@ func player_request_refresh() -> bool:
_refresh_used_this_round += 1
_reward_economy.retire_mapping() # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提)
minesweeper.retire_board(true) # 旧板退役(轮内通道)
return _install_and_reset(true) # 当场换图——失败即编排违例 fail-fast
return _install_new_board(true) # 当场换图——失败即编排违例 fail-fast
func _battle_over(result: String) -> void:
-167
View File
@@ -1,167 +0,0 @@
## 棋盘安装与变异布置域件(B1 域·BoardInstaller):生成受理、全局作用域变异决议、装板受理、
## 资源摇号资格域组装(roll_board 恰一次——install 后、挖掘前)、region 变异决议与连通片布置。
## 棋盘设置顺序 = 生成棋盘 → 打开状态初始化 → 战斗资源格设置 → 变异格设置(布置在初始展开与
## 资源格之后,变异天然不落已开区)。宿主侧装板后状态复位(封印/完美计数清零、板序推进、
## board_changed 发射、秘宝叠加层退役)留宿主薄壳——本件零复位职责。零 UI 零 await;
## 随机只经注入 RngServiceCore 的 board_gen/variant_place 两封闭流(流名与开流时点不变,
## 生成器只消费传入流——同种子同输入逐位复现)。
class_name BoardInstaller
extends RefCounted
var db: ConfigDb
var rng: RngServiceCore
var minesweeper: MinesweeperRules
var reward_economy: RewardEconomy
var encounter: Dictionary[String, Variant] # 宿主 encounter 同对象引用(assign 原地变异——跨战活引用)
var fatal_cb: Callable # 协作件违约→宿主 _fatal 封印(单点接线——BoardGenerator 同款)
var generator: BoardGenerator # 件内自建自接线(原宿主 _board_generator 成员随件迁移)
func _init(bundle: Dictionary) -> void:
for k in ["db", "rng", "minesweeper", "reward_economy", "encounter", "fatal_cb"]:
assert(bundle.has(k), "[装板域件] 引用束缺键: %s" % k)
db = bundle.db
rng = bundle.rng
minesweeper = bundle.minesweeper
reward_economy = bundle.reward_economy
encounter = bundle.encounter
fatal_cb = bundle.fatal_cb
generator = BoardGenerator.new()
generator.fatal_handler = fatal_cb # 协作件违约→宿主 _fatal 封印(单点接线)
func _fatal(msg: String) -> void:
if fatal_cb.is_valid():
fatal_cb.call(msg) # 组装层注入 orchestrator._fatal(封印传导,不绕第二中止通道)
else:
push_error(msg)
assert(false, msg)
# 装板单入口(生成 → 全局变异决议 → 装板受理 → 资源摇号 → 变异布置);
# 宿主态复位归宿主薄壳(_install_and_reset)——本件返回即装板完成、复位未行。
func install(during_round := false) -> bool:
var stream: RandomNumberGenerator = rng.open_stream("board_gen")
var board: BoardData = generator.generate(int(encounter.level_index), int(encounter.node_depth), stream, db)
if board == null:
return false
# 全局作用域变异决议(装板序内前置于 roll_board:图级偏斜旋钮须在资源摇号前定型——详见 _resolve_global_variant)
_resolve_global_variant(board)
var res: Dictionary[Variant, Variant] = minesweeper.install_board(board, during_round)
if not res.accepted:
_fatal("排雷玩法 install 拒绝(不变式违例): %s" % str(res))
return false
# 地图战斗资源 义务⑥:roll_board 恰一次(install 后、挖掘前);资格格 = 全部非封锁格(安全格 + 危险格,雷安同池)
var r0 := {}
for c in board.compute_r0():
r0[c] = true
var eligible: Array[Dictionary] = []
for c in board.safe_cells():
if not r0.has(c):
eligible.append({"cell": c})
for c in board.mines.keys():
if not r0.has(c):
eligible.append({"cell": c})
if not reward_economy.roll_board(eligible):
_fatal("地图战斗资源 roll_board 被拒")
return false
# 地形变异 布置段(棋盘设置顺序第 4 步)——布置在初始展开与资源格之后,变异天然不落已开区
_place_variants(board)
return true
# ---- 全局作用域变异 决议(装板序内 roll_board 之前)----
# boss/elite 图命中全局型:写整图 board_variants(恰一行)+ 注入资源面偏斜旋钮(图级投影在
# RewardEconomy.roll_board 消费);不消费 variant_place 流(全局取用确定性——零取号合法态)。
# 全局池空(含 L1 Boss 免疫/unlock 门控未达):字段留空、旋钮归零——_place_variants 原路走
# region 布置 = 兜底回落合法态(防配置缺行静默裸奔——provisional)。
func _resolve_global_variant(board: BoardData) -> void:
board.board_variants.clear()
reward_economy.board_skew = 0
var row: Dictionary[Variant, Variant] = db.combat.variation.global_variant_row(int(encounter.level_index), String(board.board_type),
String(encounter.get("node_type", "")))
if row.is_empty():
return
board.board_variants = [String(row.variant_id)]
reward_economy.board_skew = int(row.get("effect_value", 0))
# ---- 地形变异 布置段(棋盘设置顺序第 4 步)----
# 候选池门控(unlock_level ≤ 关卡序号 + board_types 与本地形交集,∅ 通配)经 config_db.variant_pool;
# 池空 = 合法态,静默零布置(框架 P0 口径不变)。零特判:本段只写格子变异数据快照,不改扫雷规则本体。
# 候选格域 = 安全格 − 初始展开区(伤害型 placement=closed_only 的结构保障——布置发生在初始展开之后,
# 已开格天然排除);危险格不吃变异(挖雷一层惩罚 + 变异伤害双层叠罚污染逐行归因,首版排除——provisional)。
# 每图至多一种类型(候选池内均匀选一);数量 = round(候选池格数×percent) +
# (关卡序号−1)×per_level_bonus——基数为候选池而非总格数(percent 语义 = 占可挖格比例,
# 关卡递增由 per_level_bonus 绝对格数承载);连通片生长(8 邻),单片 ≤ pack_max。
# 替换制:整图 board_variants 非空 = 本图已带全局型,跳过 region 布置(详见 _resolve_global_variant)。
func _place_variants(board: BoardData) -> void:
if not board.board_variants.is_empty():
return
var pool: Array[Variant] = db.combat.variation.variant_pool(int(encounter.level_index), String(board.board_type))
if pool.is_empty():
return
var r0 := {}
for c in board.compute_r0():
r0[c] = true
var candidates: Array[Vector2i] = []
for c in board.safe_cells():
if not r0.has(c):
candidates.append(c)
if candidates.is_empty():
return
var stream: RandomNumberGenerator = rng.open_stream("variant_place") # 一板恰一 scope(换图/刷新图各取号一次)
var vrow: Dictionary[Variant, Variant] = pool[stream.randi_range(0, pool.size() - 1)]
var total: int = int(round(float(candidates.size()) * float(db.combat.variation.percent_of_cells) / 100.0)) \
+ (int(encounter.level_index) - 1) * int(db.combat.variation.per_level_bonus)
total = mini(total, candidates.size())
_place_variants_apply(board, candidates, stream, vrow, total)
# 变异片生长施加面(布置域=候选格,数量=total,vrow 单型贯穿;取号序=stream 顺序消费)
func _place_variants_apply(board: BoardData, candidates: Array[Vector2i], stream: RandomNumberGenerator, vrow: Dictionary, total: int) -> void:
var placed := {}
while placed.size() < total:
var pack_size: int = mini(total - placed.size(), stream.randi_range(2, int(db.combat.variation.pack_max)))
var free: Array[Vector2i] = []
for c in candidates:
# 新片种子/生长格禁与其他已布片 8 邻贴接——否则两片粘连成一个超 pack_max 的连通分量
# (R3 随机流下实测暴露:独立生长的相邻片合流击穿单片上限断言)
if placed.has(c) or _adjacent_to_packs(board, placed, c):
continue
free.append(c)
if free.is_empty():
break # 生长域耗尽——该片就地收束(数量不足总目标为合法态,候选域碎片化时触发)
var seed_c: Vector2i = free[stream.randi_range(0, free.size() - 1)]
placed[seed_c] = true
var pack: Array[Variant] = [seed_c]
var pack_set := {seed_c: true}
while pack.size() < pack_size:
var fringe := _variants_fringe(board, pack, candidates, placed, pack_set)
if fringe.is_empty():
break # 生长域耗尽——该片就地收束(数量不足总目标为合法态,候选域碎片化时触发)
var nxt: Vector2i = fringe[stream.randi_range(0, fringe.size() - 1)]
placed[nxt] = true
pack_set[nxt] = true
pack.append(nxt)
for c in placed.keys():
board.variants[c] = [String(vrow.variant_id)]
# 片生长待选格收集(本片内各生长格的 8 邻候选并集——未布置、未在途、不与他片贴接)
func _variants_fringe(board: BoardData, pack: Array, candidates: Array[Vector2i], placed: Dictionary, pack_set: Dictionary) -> Array[Vector2i]:
var fringe: Array[Vector2i] = []
for c in pack:
for n in board.neighbors8(c):
if candidates.has(n) and not placed.has(n) and not fringe.has(n) \
and not _adjacent_to_packs(board, placed, n, pack_set):
fringe.append(n)
return fringe
# 候选格是否与其他已布片(own_pack 之外的 placed 格)8 邻贴接——片间隔离保证连通分量 = 单片 ≤ pack_max
func _adjacent_to_packs(board: BoardData, placed: Dictionary, c: Vector2i, own_pack: Dictionary = {}) -> bool:
for m in board.neighbors8(c):
if placed.has(m) and not own_pack.has(m):
return true
return false
-1
View File
@@ -1 +0,0 @@
uid://di6u170vqf8ae
-218
View File
@@ -1,218 +0,0 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 存档断点组装与回放唯一承载(AdventureRunner 拆件——单一变化原因=断点快照键面与回放策略):
## 断点快照拼盘、回放回填、读侧策略分发;BREAKPOINT_FIELDS 27 键登记单源随件(键名/策略标签/键序冻结)。
## 构造=引用束字典注入(db/rng/主角账三件/curio/stop/bank/loadout——件禁反向持宿主);
## 快照宿主输入(state/level_index/master_seed/runner/adventure_counters)逐调传参;
## resume_adventure 留宿主(壳+宿主侧回填语句——所有权缝分家,语句序逐步保形)。
class_name BreakpointStore
extends RefCounted
# ---- 断点快照(字段登记单源:写侧拼装与读侧回退策略同登记一表)----
# 键序即快照键序(27 顶层键——写侧/读侧/键集断言三面同源)。读侧回退策略词表:
# plain 无特殊回退面(缺键取代码内缺省)/clamp 数值夹 ≥0/keep_on_null 缺键或类型不符保持既有态
# dual_merge 双向合并(bonus_taken)/params_fallback 缺键回退参数面(attune_refresh_y)
# token_migration 旧版候选形态迁移(bonus_pending.candidates)
# 注入缺省 null = 整面静默(模拟/冒烟零扰动)。
const BREAKPOINT_FIELDS := {
"level_index": {"strategy": "plain"},
"node_id": {"strategy": "plain"},
"hero_hp": {"strategy": "clamp"},
"bonus": {"strategy": "clamp"},
"gp": {"strategy": "plain"},
"shop_purchased": {"strategy": "plain"},
"shop_shelf": {"strategy": "plain"},
"shop_visit_buys": {"strategy": "plain"},
"attunement_charges": {"strategy": "keep_on_null"},
"graph_gen_v2": {"strategy": "plain"},
"master_seed": {"strategy": "plain"},
"rng_counter": {"strategy": "plain"},
"keystone": {"strategy": "plain"},
"visited_ids": {"strategy": "plain"},
"aux_items": {"strategy": "plain"},
"bonus_taken": {"strategy": "dual_merge"},
"bonus_deck": {"strategy": "keep_on_null"},
"relics": {"strategy": "plain"},
"bonus_pending": {"strategy": "token_migration"},
"feat_ticket_pity": {"strategy": "plain"},
"feat_ticket_sold": {"strategy": "plain"},
"adventure_counters": {"strategy": "keep_on_null"},
"vision_counter_used": {"strategy": "clamp"},
"refresh_stock": {"strategy": "clamp"},
"attune_refresh_y": {"strategy": "params_fallback"},
"bonus_result": {"strategy": "plain"},
"keystone_drops": {"strategy": "plain"},
"gain_journal": {"strategy": "plain"}, # 获得日记(拆件批 T4 扩列——唯一声明的断点扩展;旧档缺键=汇总直载跳过比对)
}
# 引用束(宿主构造注入——成员名与宿主侧原名一致,迁移函数体逐字)
var db: ConfigDb
var rng: RngServiceCore
var _stats: HeroStats
var _wallet: GpWallet
var _journal: GainJournal
var _curio: CurioDeck = null # 令牌池件(池余库/获取计数随断点)
var _stop: CurioStop = null # 停点件(停点态/凭证计数片段)
var _refresh_bank: RefreshBank = null # 刷新账件(刷新存量/Y 池片段)
var _loadout: AttuneLoadout = null # 同调配装件(充能行集读侧)
# 断点域六成员(自宿主收编——宿主其余读写点经本件)
var _save_manager = null # 断点承载注入(null = 不写断点;模拟/冒烟缺省)
var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递)
var _pending_next_level := false # 大奖励已兑付、等 continue_to_next_level 确认
var _last_level_payload := {} # 最近一次关卡结束载荷(失败结算的败退节点明细来源——数值验证取数面)
var _last_treasure_cleared := 0 # additive(最近一次关卡末清空计数,summary 取数面)
var _loadout_seeded := false # 同调行已种标记(每冒险一次——首关 bind 时点投行;行跨关存活后续关零动作)
func _init(refs: Dictionary) -> void:
db = refs["db"]
rng = refs["rng"]
_stats = refs["stats"]
_wallet = refs["wallet"]
_journal = refs["journal"]
_curio = refs["curio"]
_stop = refs["stop"]
_refresh_bank = refs["bank"]
_loadout = refs["loadout"]
# 断点快照拼盘落盘(宿主 _snapshot_breakpoint 委托目标——宿主输入逐调传参)
func snapshot(state: String, level_index: int, master_seed: int, runner: LevelRunner, counters: Dictionary[Variant, Variant]) -> void:
if _save_manager == null or state != "running":
return
var parts: Dictionary[Variant, Variant] = _bp_parts(level_index, master_seed, runner, counters)
var bp: Dictionary[Variant, Variant] = {}
for key in BREAKPOINT_FIELDS.keys():
bp[key] = parts[key]
_save_manager.set_breakpoint(bp)
_save_manager.write_to_disk()
# 断点写侧取值(键集以 BREAKPOINT_FIELDS 为准——未登记键即取值越界,fail-fast)
func _bp_parts(level_index: int, master_seed: int, runner: LevelRunner, counters: Dictionary[Variant, Variant]) -> Dictionary[Variant, Variant]:
var stats_frag: Dictionary[Variant, Variant] = _stats.breakpoint_fragment()
var wallet_frag: Dictionary[Variant, Variant] = _wallet.breakpoint_fragment()
var journal_frag: Dictionary[Variant, Variant] = _journal.breakpoint_fragment()
var bank_frag: Dictionary[Variant, Variant] = _refresh_bank.breakpoint_fragment()
var stop_frag: Dictionary[Variant, Variant] = _stop.breakpoint_fragment()
var level_frag: Dictionary[Variant, Variant] = runner.breakpoint_fragment() if runner != null else {
"node_id": -1, "visited_ids": [] as Array, "aux_items": [] as Array,
"shop_shelf": [] as Array, "shop_visit_buys": 0} # 三选一停点续玩分支 runner 为 null——节点域按缺省落档
# 该两态的续玩只消费 bonus_* 附随件(选择停点/已兑付停点快照)
var parts: Dictionary[Variant, Variant] = {
"level_index": level_index,
"node_id": int(level_frag.get("node_id", -1)),
"hero_hp": int(stats_frag.get("hero_hp", 0)),
"bonus": journal_frag.get("bonus", {}),
"gp": int(wallet_frag.get("gp", 0)),
"shop_purchased": journal_frag.get("shop_purchased", {}),
"shop_shelf": level_frag.get("shop_shelf", []), # 货架入断点(离开店/非店停点 = 空架合法态)
"shop_visit_buys": int(level_frag.get("shop_visit_buys", 0)), # 本次进店已购次数(A 链定价输入)
"attunement_charges": (_loadout.breakpoint_fragment(runner.treasure_inventory) if runner != null and runner.treasure_inventory != null else {"attunement_charges": {}}).get("attunement_charges", {}), # 同调主动件充能余量随断点(行派生——save v4 域键名策略不变)
"graph_gen_v2": 1, # 生成图换代代差标记(缺键 = 换代前旧断点 → resume 显式拒绝)
"master_seed": master_seed,
"rng_counter": rng.counter(),
"keystone": int(journal_frag.get("keystone", 0)),
"visited_ids": level_frag.get("visited_ids", []),
"aux_items": level_frag.get("aux_items", []),
"bonus_taken": _curio.taken.duplicate(true), # 三选一件获取次数(kind 维度派生账本——断点附随件与读数承载)
"bonus_deck": _curio.deck.duplicate(true), # 令牌池余库(TP0 v2.2——池消耗即上限,续玩余库逐枚还原)
"relics": stats_frag.get("relics", []), # 特性持有集(局内随机渠道获得态续玩防丢失)
"bonus_pending": stop_frag.get("bonus_pending", {}), # 三选一停点态(空 = 无停点——选择停点可中断契约)
"feat_ticket_pity": int(stop_frag.get("feat_ticket_pity", 0)), # T5 专长凭证保底计数(扩列向后兼容——旧档缺键缺省 0)
"feat_ticket_sold": int(stop_frag.get("feat_ticket_sold", 0)), # T5 专长凭证限购计数(同上)
"adventure_counters": counters.duplicate(true), # adventure 域计数器随断点(续玩跨战账不丢——桶归宿主字典)
"vision_counter_used": int(journal_frag.get("vision_counter_used", 0)), # 真视使用计数随断点(续玩跨战账不丢)
"refresh_stock": int(bank_frag.get("refresh_stock", 0)), # 刷新存量账随断点
"attune_refresh_y": int(bank_frag.get("attune_refresh_y", 0)), # Y 冒险周期池余量随断点(扩列向后兼容——旧档缺键回退参数面)
"bonus_result": stop_frag.get("bonus_result", {}), # 已兑付待推进态(空 = 无——中断续玩不重兑付)
"keystone_drops": journal_frag.get("keystone_drops", []), # 逐杀掉落明细(摘要归因续接)
"gain_journal": journal_frag.get("gain_journal", {}), # 获得日记(唯一声明的断点扩列——日记为现行事实源)
}
# 拼盘完整性自断言(fail-fast——片段并集与登记键集不符即中止落盘)
var stray: Array[Variant] = []
for key in parts.keys():
if not BREAKPOINT_FIELDS.has(key):
stray.append(key)
if not stray.is_empty():
push_error("[冒险结构] 断点拼盘含未登记键: %s(fail-fast——片段与登记单源失同步)" % str(stray))
return parts
# 续玩账面回填(生命/特性/钱包/加算区/已购档/钥石/逐杀明细/真视——主角账三件片段各还原各键;
# bonus 子键 clamp 类夹 ≥0;日记在档=推导比对载入、缺键=旧档汇总直载)
#(宿主侧账面语句——summary/levels_total/level_index/_pending_next_level——按语句序留宿主壳)
func restore_ledger(bp: Dictionary, params: Dictionary) -> void:
_stats.restore_fragment(bp, params)
_wallet.restore_fragment(bp, params)
_journal.restore_fragment(bp, params)
# 续玩三选一态回填(池与件计数归件、停点态归宿主;dual_merge/keep_on_null 策略见 BREAKPOINT_FIELDS)
#(前置清理 _reset_curio_state 留宿主——桶归宿主字典;本函数承接其后的件侧还原语句,序逐步保形)
func restore_curio_state(bp: Dictionary) -> void:
var bp_taken: Dictionary[Variant, Variant] = bp.get("bonus_taken", {}) as Dictionary
for id in _curio.taken.keys():
_curio.taken[id] = maxi(0, int(bp_taken.get(id, 0)))
for id in bp_taken.keys():
_curio.taken[String(id)] = maxi(0, int(bp_taken.get(id, 0))) # 身份维键(relic:<feat_id>)随快照还原
# 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径;
# relic 令牌 feat_id 随还原)
var bp_deck = _bp_read("bonus_deck", bp)
if bp_deck is Array:
var restored: Array[Variant] = []
for t in (bp_deck as Array):
var tok: Dictionary[Variant, Variant] = t as Dictionary
var rtok := {"kind": String(tok.get("kind", "")), "tier": String(tok.get("tier", ""))}
if String(rtok["kind"]) == "relic":
rtok["feat_id"] = String(tok.get("feat_id", "")) # relic 令牌 feat_id 随还原
restored.append(rtok)
_curio.deck = restored
# 特性持有集还原(缺键 = 旧版断点防御空集——主角数值账字段单点)
_stats.feats = []
for r in (bp.get("relics", []) as Array):
_stats.feats.append(String(r))
# 续玩参数面——数值账规范化与越界钳制(件侧;宿主侧四重绑定与预算注入按语句序夹在两件侧调用之间)
func apply_resume_params(params: Dictionary) -> void:
_stats.rebase_override(params.get("hero_override", {})) # 续玩参数面同 start_adventure(规范化归数值账)
_stats.attack_type = String(params.get("attack_type", "")) # 续玩参数面同 start_adventure(账号层选中态随 resume 重注入)
_stats.armor_type = String(params.get("armor_type", ""))
# 生命账本越界钳制(T17——spawn 守卫 EC16 免疫):断点可能带出高于生效上限的账本值
#(如调试浮窗调高期写出的值,实例:hero_hp=28 > 基础 26)——恢复后钳到生效上限,
# 否则首战 spawn「hero_hp 入参越界」拒收 → 「敌人系统 spawn 被拒」fatal。钳制式单点归
# 数值账 clamp_hp_to_resume_cap(保守口径不含加算区——与原钳制式逐字同)。
_stats.clamp_hp_to_resume_cap(int(db.combat.hero.max_hp))
# 停点件/刷新账片段回填(凭证计数;刷新存量/Y 池——Y 缺键回退参数面 params_fallback 类)
func restore_stop_bank_fragments(bp: Dictionary, params: Dictionary) -> void:
_stop.restore_fragment(bp, params)
_refresh_bank.restore_fragment(bp, params)
# 读侧单点(BREAKPOINT_FIELDS 策略标签分发;未登记键 fail-fast 不静默按 plain 兜)。
# 二次消费面(_start_current_level 经 _resume_bp 取 node_id/visited_ids/aux_items)同走此口。
func _bp_read(key: String, bp: Dictionary, fallback: Variant = null) -> Variant:
if not BREAKPOINT_FIELDS.has(key):
push_error("[冒险结构] 断点字段未登记(读侧): %s" % key)
var v: Variant = bp.get(key, fallback)
if _bp_strategy(key) == "keep_on_null" and v != null and not (v is Dictionary or v is Array):
return null # 类型不符——保持既有态(调用方跳过覆盖)
return v
# 读侧策略标签(BREAKPOINT_FIELDS 读侧消费面单点)
func _bp_strategy(key: String) -> String:
return String((BREAKPOINT_FIELDS.get(key, {}) as Dictionary).get("strategy", "plain"))
# 数值类读侧(clamp 夹 ≥0 / params_fallback 断点缺键回退参数面整额注入 / plain 直取)
func _bp_read_int(key: String, bp: Dictionary, fallback: int, params: Dictionary = {}) -> int:
match _bp_strategy(key):
"clamp":
return maxi(0, int(bp.get(key, fallback)))
"params_fallback":
return maxi(0, int(bp.get(key, int(params.get(key, fallback)))))
_:
return int(bp.get(key, fallback))
-1
View File
@@ -1 +0,0 @@
uid://cw5qdd012h728
+94 -14
View File
@@ -41,7 +41,7 @@ var hero_hp: int:
var gp: int: # GP 冒险钱包(局内货币——冒险开始 = 0、收束清零;不入存档不入摘要)
get: return _wallet.gp
set(v): _wallet.gp = v
var shop: ShopSession = null # 商店件(_init 建;双模式货架/定价/购买/刷新唯一宿主)
var _shop: ShopSession = null # 商店件(_init 建;双模式货架/定价/购买/刷新唯一宿主)
# 协作对象私有(E1/E3 封装收口):settlement 读取经 current_battle() 成文出口
var _settlement: BattleOrchestrator = null # 当前战斗编排器(关卡结束置 null)
var treasure_inventory: TreasureItems = null # 秘宝 关卡域库存(TreasureItems;跨战斗延续,
@@ -96,10 +96,10 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore, ctx: Dictionary = {}) -> v
_hero_armor_type = String(ctx.get("hero_armor_type", ""))
_voucher_rap = maxi(0, int(ctx.get("voucher_rap", 0)))
_voucher_time_sand = maxi(0, int(ctx.get("voucher_time_sand", 0)))
shop = ShopSession.new(db, rng)
_shop = ShopSession.new(db, rng)
var pool: Dictionary[Variant, Variant] = ctx.get("pool_wiring", {}) as Dictionary
if String(ctx.get("shop_mode", "")) == "pool" and not pool.is_empty():
shop.set_shop_pool(pool.get("deck", []) as Array, pool.get("consume", Callable()) as Callable,
_shop.set_shop_pool(pool.get("deck", []) as Array, pool.get("consume", Callable()) as Callable,
pool.get("ticket", Callable()) as Callable, pool.get("cap", Callable()) as Callable)
_bind_shop()
@@ -302,8 +302,8 @@ func breakpoint_fragment() -> Dictionary[Variant, Variant]:
"node_id": current_node_id(),
"visited_ids": visited_ids(),
"aux_items": treasure_snapshot,
"shop_shelf": shop.shop_bp_shelf(),
"shop_visit_buys": shop.shop_bp_visit_buys(),
"shop_shelf": _shop.shop_bp_shelf(),
"shop_visit_buys": _shop.shop_bp_visit_buys(),
}
@@ -503,11 +503,11 @@ func _enter_node(node: Dictionary) -> bool:
"shop":
# 商店房兑付:到达即开店(覆盖层归呈现层),购买出账/
# 离开归玩家意图——零自动推进;离开确认经 continue_after_node 回选路口
var payload := {"gp": gp} # legacy 固定三件(off 对照承载——零 RNG 零池消费)
if shop.pool_mode:
shop.open_pool() # 进店抽 3(恰一 shop_draw scope;买后全重抽同签)
payload = {"gp": gp, "shelf": true}
node_entered.emit(node, payload)
if _shop.pool_mode:
_shop.open_pool() # 进店抽 3(恰一 shop_draw scope;买后全重抽同签)
node_entered.emit(node, {"gp": gp, "shelf": true})
return true
node_entered.emit(node, {"gp": gp}) # legacy 固定三件(off 对照承载——零 RNG 零池消费)
return true
push_error("[关卡节点地图] 未知节点类型: %s" % str(node))
return false
@@ -701,7 +701,7 @@ func continue_after_node() -> bool:
if t != "reward" and t != "rest" and t != "shop":
return false
if t == "shop":
shop.leave() # 离开店面货架态清零(断点快照 shop_shelf 键随离开落空架合法态)
_shop.leave() # 离开店面货架态清零(断点快照 shop_shelf 键随离开落空架合法态)
return _present_choices()
@@ -721,12 +721,12 @@ func close_wallet() -> void:
# ---- 商店房兑付(GP+商店房实施批:呈现层只读投影 + 购买出账)----
# 实装归 ShopSession 件(双模式货架/定价/购买/刷新唯一宿主);外部调用面=调用方经宿主公开
# 成员 shop 直连(调用直连不留委托壳——2026-10-26 Middle Man 裁定)。
# 实装归 ShopSession 件(双模式货架/定价/购买/刷新唯一宿主);本类保留同签名委托与件注入/
# 白盒入口——外部调用面与断点取数面零变化。
# 件注入(建件与宿主引用换代时调用——钱包/主角账三件/图行/装配集为共享引用,出账直接落同一引用)
func _bind_shop() -> void:
shop.bind({
_shop.bind({
"wallet": _wallet,
"journal": _journal,
"level_row": level_row,
@@ -746,3 +746,83 @@ func _emit_wallet_event(event: String, gp_now: int, extra: Dictionary) -> void:
# 购血(商店 max_hp 商品——当前生命 +Δ 并封顶新上限,与账本置满/关卡完成回血同式)
func _gain_max_hp_flat(flat: int) -> void:
_stats.hero_hp = mini(_stats.hero_hp + flat, _stats.effective_max_hp())
func shop_products() -> Array:
return _shop.shop_products()
func current_price(prow: Dictionary) -> int:
return _shop.current_price(prow)
func purchased_tier(product_id: String) -> int:
return _shop.purchased_tier(product_id)
func buy_product(product_id: String) -> bool:
return _shop.buy_product(product_id)
func player_request_refresh() -> bool:
return _shop.player_request_refresh()
# 散折价单点(测试取数面沿用本类调用名)
func _spend_discounted(price: int) -> int:
return _shop._spend_discounted(price)
# 注入(AdventureRunner._start_current_level 唯一调用方——pool 模式专用):令牌库共享引用 +
# 池消费 handler + 专长凭证判定 + cap 检查回调(cap 满 kind 剔出货架候选视图)。
func set_shop_pool(deck_ref: Array, consume_handler: Callable, ticket_consider: Callable = Callable(), cap_check: Callable = Callable()) -> void:
_shop.set_shop_pool(deck_ref, consume_handler, ticket_consider, cap_check)
# 货架抽签白盒入口(测试取数面沿用本类调用名)
func _shop_draw_shelf() -> void:
_shop._draw_shelf()
func pool_stock(kind: String) -> int:
return _shop.pool_stock(kind)
func pool_price(kind: String) -> int:
return _shop.pool_price(kind)
func shop_shelf() -> Array:
return _shop.shop_shelf()
func shop_refresh_free_left() -> int:
return _shop.shop_refresh_free_left()
func shop_refresh_cost() -> int:
return _shop.shop_refresh_cost()
func shop_refresh() -> bool:
return _shop.shop_refresh()
func spend_gp(amount: int) -> bool:
return _shop.spend_gp(amount)
func buy_pool_item(kind: String) -> bool:
return _shop.buy_pool_item(kind)
func is_shop_pool() -> bool:
return _shop.is_shop_pool()
func shop_bp_shelf() -> Array:
return _shop.shop_bp_shelf()
func shop_bp_visit_buys() -> int:
return _shop.shop_bp_visit_buys()
+1 -1
View File
@@ -1,7 +1,7 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 商店会话件:关卡商店双模式(legacy 固定三件加定价/pool 共享令牌池)的货架、定价、购买与刷新唯一宿主。
## 钱包/主角账/装配集归宿主(GpWallet/GainJournal 共享引用经 bind 注入——GP 读写走钱包、购买加算走日记单点);
## Core 层零 UI 依赖;外部调用面=调用方经宿主 LevelRunner 公开成员 shop 直连(调用直连不留委托壳);
## Core 层零 UI 依赖;公开面由 LevelRunner 同签名委托承载(外部调用面零变化);
## legacy 模式零 RNG 零池消费(off 对照承载)。
class_name ShopSession
extends RefCounted
+12 -12
View File
@@ -170,37 +170,37 @@ func on_shop_buy(product_id: String) -> void:
var runner = _runner_get.call()
if runner == null:
return
if runner.shop.is_shop_pool():
if runner.is_shop_pool():
# pool 面:买 1 → 核心全重抽——货架整架重投喂(重抽反馈;余额不足核心拒绝零副作用)
if not runner.shop.buy_pool_item(product_id):
if not runner.buy_pool_item(product_id):
logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"})
toast.show_toast("GP不足——买不起这件商品", "warn")
return
shop_overlay.open_shelf(runner.shop.shop_shelf(), int(runner.gp))
shop_overlay.set_visit_info(int(runner.shop.shop_bp_visit_buys()), float(db.run.shop.price_a)) # T6 爬价规则行
shop_overlay.set_refresh_display(runner.shop.shop_refresh_free_left(), runner.shop.shop_refresh_cost(), int(runner.gp) >= runner.shop.shop_refresh_cost())
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
shop_overlay.set_visit_info(int(runner.shop_bp_visit_buys()), float(db.run.shop.price_a)) # T6 爬价规则行
shop_overlay.set_refresh_display(runner.shop_refresh_free_left(), runner.shop_refresh_cost(), int(runner.gp) >= runner.shop_refresh_cost())
_refresh_presentations.call()
return
if not runner.shop.buy_product(product_id):
if not runner.buy_product(product_id):
# 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1)
logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"})
toast.show_toast("GP不足——买不起这件商品", "warn")
return
shop_overlay.refresh_state(runner.shop.shop_products(), int(runner.gp))
shop_overlay.refresh_state(runner.shop_products(), int(runner.gp))
_refresh_presentations.call()
# MC-1:刷新意图(shop_overlay.refresh_requested——核心 shop_refresh 受理后整架重投喂=重抽反馈)
func on_shop_refresh() -> void:
var runner = _runner_get.call()
if runner == null or not runner.shop.is_shop_pool():
if runner == null or not runner.is_shop_pool():
return
if not runner.shop.shop_refresh():
if not runner.shop_refresh():
toast.show_toast("GP不足——刷新失败", "warn")
return
shop_overlay.open_shelf(runner.shop.shop_shelf(), int(runner.gp))
shop_overlay.set_visit_info(int(runner.shop.shop_bp_visit_buys()), float(db.run.shop.price_a)) # T6 爬价规则行
shop_overlay.set_refresh_display(runner.shop.shop_refresh_free_left(), runner.shop.shop_refresh_cost(), int(runner.gp) >= runner.shop.shop_refresh_cost())
shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp))
shop_overlay.set_visit_info(int(runner.shop_bp_visit_buys()), float(db.run.shop.price_a)) # T6 爬价规则行
shop_overlay.set_refresh_display(runner.shop_refresh_free_left(), runner.shop_refresh_cost(), int(runner.gp) >= runner.shop_refresh_cost())
_refresh_presentations.call()
@@ -160,7 +160,7 @@ func on_refresh() -> void:
if not refreshed:
# 免费位(X/Y)已穷——GP 付费档(先扣钱包后重抽,余额不足拒绝零副作用)
var cost: int = adventure.refresh_bank().gp_cost()
if runner == null or not runner.shop.spend_gp(cost):
if runner == null or not runner.spend_gp(cost):
ctx.views.toast.show_toast("GP不足——刷新失败", "warn")
return
refreshed = adventure.stop().refresh_curio_paid()
+1 -1
View File
@@ -770,7 +770,7 @@ func _on_refresh_confirmed() -> void:
return
var ok: bool = false
if run_scope.level_active and run_scope.runner != null:
ok = run_scope.runner.shop.player_request_refresh()
ok = run_scope.runner.player_request_refresh()
elif run_scope.settlement != null:
ok = run_scope.settlement.player_request_refresh()
logger.log_action("refresh", Vector2i(-1, -1), ok)
+5 -5
View File
@@ -78,12 +78,12 @@ static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) -
# 购买/离开归玩家意图(buy_requested/leave_pressed 接线),零自动推进。
# pool 面 = 共享池货架三选一同构卡;legacy 面 = 固定三件(off 对照承载)
root._open_modal(root.shop_overlay)
if root.run_scope.runner.shop.is_shop_pool():
root.shop_overlay.open_shelf(root.run_scope.runner.shop.shop_shelf(), int(root.run_scope.runner.gp))
root.shop_overlay.set_visit_info(int(root.run_scope.runner.shop.shop_bp_visit_buys()), float(root.db.run.shop.price_a)) # 爬价规则行
root.shop_overlay.set_refresh_display(root.run_scope.runner.shop.shop_refresh_free_left(), root.run_scope.runner.shop.shop_refresh_cost(), int(root.run_scope.runner.gp) >= root.run_scope.runner.shop.shop_refresh_cost()) # MC-1
if root.run_scope.runner.is_shop_pool():
root.shop_overlay.open_shelf(root.run_scope.runner.shop_shelf(), int(root.run_scope.runner.gp))
root.shop_overlay.set_visit_info(int(root.run_scope.runner.shop_bp_visit_buys()), float(root.db.run.shop.price_a)) # 爬价规则行
root.shop_overlay.set_refresh_display(root.run_scope.runner.shop_refresh_free_left(), root.run_scope.runner.shop_refresh_cost(), int(root.run_scope.runner.gp) >= root.run_scope.runner.shop_refresh_cost()) # MC-1
else:
root.shop_overlay.open_shop(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp))
root.shop_overlay.open_shop(root.run_scope.runner.shop_products(), int(root.run_scope.runner.gp))
root.shop_overlay.set_visit_info(-1, 1.0) # legacy 面无爬价行(off 对照承载)
root.log_view.append_bb(root.Narrator.shop_line(int(root.run_scope.runner.gp)))
root.logger.log_node(node, info, root.run_scope.runner.node_index)
+2 -2
View File
@@ -132,10 +132,10 @@ static func _on_wallet_changed(root: Variant, event: String, gp: int, extra: Dic
if String(event) == "gain":
root.run_scope.last_gain_amount = int(extra.get("amount", 0)) # GP 增量取数(每战恰一笔 gain)
if root.shop_overlay.visible and root.run_scope.runner != null:
if root.run_scope.runner.shop.is_shop_pool():
if root.run_scope.runner.is_shop_pool():
root.shop_overlay.refresh_shelf_state(int(root.run_scope.runner.gp)) # pool 面余额联动(货架内容仅买后重抽变)
else:
root.shop_overlay.refresh_state(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp))
root.shop_overlay.refresh_state(root.run_scope.runner.shop_products(), int(root.run_scope.runner.gp))
root._refresh_presentations()
+368 -44
View File
@@ -1,31 +1,128 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## CellUnit——BoardView SubViewport 世界内逐格渲染件(Node2D),被动绘制壳:_draw 六步时序器
## +视野迷雾域+微光子件生命周期+格件几何;基座层与标识叠加两绘制域件 _init 自建持有
## (cell_base_layer/cell_mark_overlay——单一变化原因=迷雾规格与格件节点生命周期)。
## CellUnit——BoardView SubViewport 世界内逐格渲染件(Node2D),全部程序化 _draw 占位视觉(纹理槽位已留未装载)。
## 无 class_name(类名缓存面纪律)——使用方经 preload 引用。
## 探针直读冻结成员(glyph/fog_state/plate/glyph_color)与 _glow_layer 住宿主;
## hl_color/hl_visible/dot_visible/mutation_mark 探针读 u.face.*。
## 内部状态件(u3 子域拆件——契约 9 职责语句「单格绘制输入集」):本件为被动绘制壳,
## 状态件 _CellPaintState 承载全部绘制入参(单一变化原因=格面呈现),setter 触发宿主重绘语义
## 经 _owner 回引保持逐字;探针直读冻结成员(glyph/fog_state/plate/glyph_color)与 _glow_layer 住宿主。
extends Node2D
const _GLOW_LAYER := preload("res://presentation/views/cell_type_glow_layer.gd")
const _BASE_LAYER := preload("res://presentation/views/cell_base_layer.gd")
const _MARK_OVERLAY := preload("res://presentation/views/cell_mark_overlay.gd")
var edge := 46.0: # 格边长(BoardView.set_cell_area 单点驱动)
set(v):
edge = v
if _glow_layer != null and is_instance_valid(_glow_layer):
_glow_layer.setup(edge, glow_group)
queue_redraw()
var cell := Vector2i.ZERO # 板坐标(诊断观测面)
# ── 未开格类型微光(英雄 8 向相邻未开格的组粒度类型提示)
# 作用集 = 可挖域 ∩ 身份可查格,由 BoardView 侧派生下发;本件只承接绘制与子件生命周期。
# 静态:不呼吸、不入动效预算;相位门控(结算/道具窗口态隐藏)在 BoardView 侧。
var glow_group := 0:
set(v):
if glow_group != v:
glow_group = v
_sync_glow_layer()
# ── D 投影档绘制常量(研究件实测取色——立面高光/双段暗带/地面投影;黑罩 alpha 叠加实测等效)──
const FACADE_HIGHLIGHT_PX := 2.0 # 口沿高光线厚(像素)
const FACADE_NEAR_FRAC := 0.16 # 立面近段高(格边比——研究件 10/64)
const FACADE_FAR_FRAC := 0.25 # 立面远段高(格边比——研究件 16/64)
const FACADE_NEAR_COL := Color(0, 0, 0, 0.60) # 立面近段黑罩(墙顶 132→52 实测 α≈0.61)
const FACADE_FAR_COL := Color(0, 0, 0, 0.82) # 立面远段黑罩(墙顶 132→24 实测 α≈0.82)
const PROJECTION_NEAR_COL := Color(0, 0, 0, 0.28) # 投影贴墙段(地面 66→46 实测 α≈0.28)
const PROJECTION_MID_COL := Color(0, 0, 0, 0.16) # 投影中段(渐隐)
const PROJECTION_EDGE_COL := Color(0, 0, 0, 0.06) # 投影远段(渐隐收边)
# ── E 格高墙面(圣经 v3.5 冻结①——地面格南邻墙→南部 30% 承接墙面:墙顶纹理原亮度
# 直贴零暗档零阴影;墙脚阴线只画地面/墙面交界;(b)-(c) 交界
# 无边框由 BoardView 侧跳过墙格北向描边承载)──
const E_FACADE_FRAC := 0.30 # 墙面承接高(格边比 30%)
## 单格绘制输入集(被动绘制壳的全部绘制入参——BoardView 直写面;setter 触发宿主重绘语义逐字)
class _CellPaintState extends RefCounted:
var _owner: Node2D = null
var cell := Vector2i.ZERO # 板坐标(诊断观测面)
var edge := 46.0: # 格边长(BoardView.set_cell_area 单点驱动)
set(v):
edge = v
if _owner._glow_layer != null and is_instance_valid(_owner._glow_layer):
_owner._glow_layer.setup(edge, glow_group)
_owner.queue_redraw()
var bg_sb: StyleBoxFlat = null # 格底 StyleBox(BoardView 每次 paint 重建;呼吸微光就地改边框)
# ── 接触带微光绘制面(可挖域微光画在英雄格周界空间侧——art-bible §3.2 v2.8;
# 仅英雄格单元承载,BoardView.refresh 按可挖域写边/角码、_process 逐拍更新呼吸色)──
var contact_bands: Array[int] = [] # 正交接触边码 0=N/1=S/2=W/3=E(该侧整条边)
var contact_corners: Array[int] = [] # 斜向共享角码 0=NW/1=NE/2=SW/3=SE(角点+两侧各半条边)
var contact_col := Color(0, 0, 0, 0) # 当前呼吸色(alpha 0 = 不画)
var outline_sides: Array[int] = [] # 墙格地面朝向描边码 0=N/1=S/2=W/3=E(程序侧结构件)
# ── 高墙感 D 投影档(研究件四变体定 D=C+地面投影,ITB 同款高度线索)──
# 南立面 = 墙格南缘内侧「口沿高光线 + 双段暗带」(邻地面格才画,墙群内聚);地面投影 = 北邻墙格
# 在本已开格北缘投渐隐暗带。参数按研究件 64px 拼版实测比例换算(立面双段 0.16/0.25 格边、投影带
# 0.22 格边;坐标推导留档 commit)。素材缺位程序化回退同样服役(结构归程序——art-bible §8.5)。
var facade_south := false: # 南立面开关(BoardView paint 按「南邻已开格」写入)
set(v):
facade_south = v
_owner.queue_redraw()
var proj_band := 0.0: # 地面投影带宽(像素,0 = 不画——BoardView 按北邻墙判定写入)
set(v):
proj_band = v
_owner.queue_redraw()
var badge_glyph := "": # 奖励徽章文字(右下角金——封印之下、变异纹之上、不进数字区)
set(v):
badge_glyph = v
if _owner._glow_layer != null and is_instance_valid(_owner._glow_layer):
_owner._glow_layer.set_badge_mask(v != "")
_owner.queue_redraw()
var dot_visible := false: # BFS 路径预览点迹(中心 6×6 小人蓝)
set(v):
dot_visible = v
_owner.queue_redraw()
var hl_visible := false: # 焦点高亮(悬停态边框覆盖层)
set(v):
hl_visible = v
_owner.queue_redraw()
var hl_color := UiKit.GOLD: # 高亮色(金目标/封印朱/禁入灰禁红)
set(v):
hl_color = v
_owner.queue_redraw()
var font: Font = null # 字体槽位(null = ThemeDB.fallback_font——BoardView.set_world_font 注入)
var bevel := 0.0: # 顶面倒角强度(0=关;档位 0/0.35/0.6/1.0)
set(v):
bevel = v
_owner.queue_redraw()
# ── 素材槽位(现全 null = 程序化占位服役;art-bible §8.2 格面 32×32 暂定档)──
var tex_base: Texture2D = null # ① 砖面底
var tex_mutation: Texture2D = null # ② 变异纹带
var tex_glyph: Texture2D = null # ③ 数字
var tex_badge: Texture2D = null # ③' 徽章
var tex_seal: Texture2D = null: # ④ 封印(槽位可运行时换装〔封印〕——同族 facade/treasure_mark
set(v): # 均 setter 触发重绘(普通 var 赋值不重绘=封印插上不显示))
if tex_seal != v:
tex_seal = v
_owner.queue_redraw()
var tex_facade: Texture2D = null # ⑥ E 格墙面材质(v3.5——南邻墙顶 center 瓦片同源,BoardView 注入)
var facade_strip := false: # E 格开关(BoardView paint 按「已开格且南邻未开墙」写入——瓦片态专用)
set(v):
facade_strip = v
_owner.queue_redraw()
# 变异角标(程序化,tex_mutation 纹带槽暂留):
# "flat_damage" = 未开格伤害型变异,左下角危险红三角(开格有代价的策略纹理标记)
# 叠加层标记(秘宝整图叠加层的格面呈现;派生信息层零回写):
# "mine_reveal" = 真视格(危险红内缩框——「这格是雷」的确定决策标记,封印照常计费);
# "reward_preview" 格不复用本字段——预读徽章走 badge_glyph 注入(board_view refresh 注入 REWARD_BADGE 字形)
var treasure_mark := "":
set(v):
if treasure_mark != v:
treasure_mark = v
_owner.queue_redraw()
var mutation_mark := "":
set(v):
if mutation_mark != v:
mutation_mark = v
_owner.queue_redraw()
# ── 未开格类型微光(英雄 8 向相邻未开格的组粒度类型提示)
# 作用集 = 可挖域 ∩ 身份可查格,由 BoardView 侧派生下发;本件只承接绘制与子件生命周期。
# 静态:不呼吸、不入动效预算;相位门控(结算/道具窗口态隐藏)在 BoardView 侧。
var glow_group := 0:
set(v):
if glow_group != v:
glow_group = v
_owner._sync_glow_layer()
var removed_dim := false: # 中立移除退色层(BoardView paint 按排雷玩法 is_removed 写入)
set(v):
removed_dim = v
_owner.queue_redraw()
var st := _CellPaintState.new()
var glyph := "": # 文字位——数字/雷/封印/人/?(现行视觉封印与数字互斥,同位承载;探针直读冻结)
set(v):
glyph = v
@@ -44,51 +141,268 @@ var fog_state := 0: # 探针直读冻结
queue_redraw()
var _glow_layer: Node2D = null
var base = null # 格面基座层绘制域件(_init 自建)
var face = null # 格面标识与叠加层绘制域件(_init 自建)
func _init() -> void:
base = _BASE_LAYER.new(self)
face = _MARK_OVERLAY.new(self)
st._owner = self
func _draw() -> void:
var rect := Rect2(Vector2.ZERO, Vector2(edge, edge))
base.draw_base(self, rect) # 基座层整树(描边/立面/投影/接触带/倒角与其补画序)
face.draw_marks(self, rect) # 变异标记
face.draw_glyph_layer(self, rect, glyph, glyph_color, plate) # 字形/色牌/徽章/封印
face.draw_overlay_under(self, rect) # 点迹+高亮(雾层由宿主夹在其后)
_draw_fog_layer(rect) # 宿主迷雾域(原位——雾夹在 hl 与 removed 之间)
face.draw_removed_dim(self, rect) # 退色层
var rect := Rect2(Vector2.ZERO, Vector2(st.edge, st.edge))
_draw_base_layer(rect)
_draw_mutation_marks()
_draw_glyph_layer(rect)
_draw_overlay_layers(rect)
## ① 砖面底层域(占位 = StyleBoxFlat:格底色 + 暗化描边 + 3px 圆角;瓦片态描边覆盖层;
## 呼吸微光/主角蓝框由 BoardView 改 bg_sb 后触发重绘。素材缺位回退走 elif 程序化原样,零观感变化)
func _draw_base_layer(rect: Rect2) -> void:
if st.tex_base != null:
draw_texture_rect(st.tex_base, rect, false)
# 轮廓加强(轮廓线不明显):墙格地面朝向边补 2px 深墨线(§8.5 结构归程序——
# 素材 1px 墨线在 45/36px 非整数缩放下视觉衰减,程序线保底;墙-墙内界不画,保墙群内聚;
# 坐标推导:各边内贴 2px 矩形,相邻边矩形全长相交=周界闭合)
if not st.outline_sides.is_empty():
_draw_outline_sides()
_draw_south_facade()
_draw_north_projection()
_draw_e_facade()
# 瓦片态描边覆盖层:tex_base 在位时 BoardView 传入的 bg_sb
# 已降级为「透明底 + 描边」载体——呼吸微光/主角蓝框画在瓦片之上(art-bible §1.2 叠层序
# 不变:底 < 纹带 < 数字 < 封印 < 焦点高亮);素材缺位回退走 elif 程序化原样,零观感变化
if st.bg_sb != null:
draw_style_box(st.bg_sb, rect)
_draw_contact_or_fallback(rect)
## 接触带微光或平色回退域(英雄格周界空间侧——正交邻墙=整条边;斜向=共享角点+两侧各半条边;光不进墙格):
## 带宽 4px 贴格边内侧(坐标推导:N=[0,0,edge,4] S=[0,edge-4,edge,4] W=[0,0,4,edge] E=[edge-4,0,4,edge];
## 半条边=沿邻边自角点起 edge/2;角点圆 r=6 贴角点圆心,近白芯 r=2.5 提亮)
func _draw_contact_or_fallback(rect: Rect2) -> void:
if st.contact_col.a > 0.0:
_draw_north_projection() # 平色回退+微光并发的英雄格补画(瓦片路径已画——同像素重复无观感差)
_draw_e_facade() # E 格墙面补画(同上——英雄格兼 E 格时瓦片路径已画,重复无害)
_draw_contact_bands()
elif st.bg_sb != null:
draw_style_box(st.bg_sb, rect)
_draw_south_facade() # 平色回退路径——D 投影档两件同样服役(结构归程序)
_draw_north_projection()
_draw_bevel()
## ②③④ 中层域:变异纹带与标记/数字色牌与字形/徽章/封印
func _draw_glyph_layer(rect: Rect2) -> void:
# ② 变异纹带(无程序化纹带——槽位先行,贴图落位即用)
if st.tex_mutation != null:
draw_texture_rect(st.tex_mutation, rect, false)
# ③ 数字/徽章(数字 ≥ 格高 50% 墨族对格底 ≥4.5:1;徽章右下角金,遮蔽态 = "?")
if glyph != "":
if plate:
_draw_digit_plate()
if st.tex_glyph != null:
draw_texture_rect(st.tex_glyph, rect, false)
else:
_draw_glyph(glyph, glyph_color, maxi(18, int(ceil(st.edge * 0.5)))) # 字号下限 18(§7.4 冻结公式)
if st.badge_glyph != "":
if st.tex_badge != null:
draw_texture_rect(st.tex_badge, rect, false)
else:
_draw_badge(st.badge_glyph)
# ④ 封印(现行视觉封印文字在 ③ 文字位承载——独立槽位留给封印图)
if st.tex_seal != null:
draw_texture_rect(st.tex_seal, rect, false)
## 数字色牌后备域(圣经 v3.5 冻结④:数字亮墨直画已开格底须垫牌;
## 牌=后备件——雷/「?」维持牌载保读性(牌上深墨族 12:1);
## 牌尺寸 0.60/0.62 格边居中;封印=未开格不垫牌)
func _draw_digit_plate() -> void:
var pw := st.edge * 0.60
var ph := st.edge * 0.62
var pr := Rect2((st.edge - pw) * 0.5, (st.edge - ph) * 0.5, pw, ph)
draw_rect(pr, UiKit.DIGIT_PLATE, true)
draw_rect(pr, UiKit.DIGIT_PLATE_EDGE, false, 1.0)
## ⑤⑥⑦ 顶层域:路径点迹 + 焦点高亮 + 视野迷雾 + 中立移除退色
func _draw_overlay_layers(rect: Rect2) -> void:
# ⑤ 路径点迹 + 焦点高亮(悬停态覆盖层,不参与命中)
if st.dot_visible:
var ds := 6.0
draw_rect(Rect2(Vector2((st.edge - ds) * 0.5, (st.edge - ds) * 0.5), Vector2(ds, ds)), UiKit.HERO_BLUE, true)
if st.hl_visible:
# 悬停高亮减淡(金框太明显——绘制时 55% alpha,hl_color 属性与探针断言面不动)
draw_rect(Rect2(1, 1, st.edge - 2.0, st.edge - 2.0), Color(st.hl_color, 0.55), false, 2.0) # 2px 内描边
_draw_fog_layer()
# ⑦ 中立移除退色层(退色降档:暗幕压顶全层覆盖,暗淡记忆态同族呈现语义;
# 顶层于迷雾之上——移除格不再参与迷雾语义。本实现移除集=未揭示危险格(无数字无封印印))
if st.removed_dim:
draw_rect(Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)), Color(0.12, 0.10, 0.09, 0.62), true)
## ⑥ 视野迷雾层(最顶遮罩,§6.5 落文):
## 全黑=不透明纯黑盖全部叠层(含封印印/徽章/高亮——封印印记随格隐没,§6.5 显式推论);
## 记忆态=记忆板素色压平(纹理免疫——色牌/封印印/徽章随板隐没)+数字记忆墨重画(数字可读硬指标 ≥4.5:1)
func _draw_fog_layer(rect: Rect2) -> void:
func _draw_fog_layer() -> void:
if fog_state == 1:
draw_rect(rect, Color(0, 0, 0, 1.0), true)
draw_rect(Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)), Color(0, 0, 0, 1.0), true)
elif fog_state == 2:
_draw_fog_memory(rect)
_draw_fog_memory()
## 墙格轮廓域:地面朝向边补 2px 深墨线(各边内贴 2px 矩形,相邻边矩形全长相交=周界闭合)
func _draw_outline_sides() -> void:
var ow := 2.0
var oc := UiKit.FOG_OUTLINE
for b in st.outline_sides:
var or_ := Rect2()
match int(b):
0: or_ = Rect2(0, 0, st.edge, ow)
1: or_ = Rect2(0, st.edge - ow, st.edge, ow)
2: or_ = Rect2(0, 0, ow, st.edge)
3: or_ = Rect2(st.edge - ow, 0, ow, st.edge)
draw_rect(or_, oc, true)
## 接触带微光域:正交整条边 + 斜向共享角点与两侧半条边
func _draw_contact_bands() -> void:
var band := 4.0
var half := st.edge * 0.5
for b in st.contact_bands:
var r := Rect2()
match int(b):
0: r = Rect2(0, 0, st.edge, band)
1: r = Rect2(0, st.edge - band, st.edge, band)
2: r = Rect2(0, 0, band, st.edge)
3: r = Rect2(st.edge - band, 0, band, st.edge)
draw_rect(r, st.contact_col, true)
for cc in st.contact_corners:
_draw_contact_corner(int(cc), band, half)
## 斜向角点域:角点圆 r=6 贴角点圆心 + 近白芯 r=2.5 提亮 + 半条边(角点沿两条邻边各延伸 edge/2——
## 坐标推导:横带沿 N/S 自角点向板心侧 half;NW/SE 半带 x=y=角点坐标,NE/SE 半带自 edge-half 收进——
## 正交整边时重叠同色无害)
func _draw_contact_corner(cc: int, band: float, half: float) -> void:
var pt := Vector2.ZERO
match cc:
0: pt = Vector2(0, 0)
1: pt = Vector2(st.edge, 0)
2: pt = Vector2(0, st.edge)
3: pt = Vector2(st.edge, st.edge)
draw_circle(pt, 6.0, st.contact_col)
draw_circle(pt, 2.5, Color(1, 1, 1, minf(1.0, st.contact_col.a * 0.95)))
if cc == 0 or cc == 1:
# N 侧横带:NW 自 x=0、NE 自 x=edge-half
draw_rect(Rect2(pt.x if cc == 0 else pt.x - half, 0.0, half, band), st.contact_col, true)
if cc == 2 or cc == 3:
# S 侧横带:SW 自 x=0、SE 自 x=edge-half
draw_rect(Rect2(pt.x if cc == 2 else pt.x - half, st.edge - band, half, band), st.contact_col, true)
if cc == 0 or cc == 2:
# W 侧竖带:NW 自 y=0、SW 自 y=edge-half
draw_rect(Rect2(0.0, pt.y if cc == 0 else pt.y - half, band, half), st.contact_col, true)
if cc == 1 or cc == 3:
# E 侧竖带:NE 自 y=0、SE 自 y=edge-half
draw_rect(Rect2(st.edge - band, pt.y if cc == 1 else pt.y - half, band, half), st.contact_col, true)
## 顶面倒角域(光源左上——上/左内沿受光 2px、下/右内沿背光 2px;内缩 2px 让
## 呼吸微光/主角蓝边框保持最外沿可见;强度随档位)
func _draw_bevel() -> void:
if st.bevel <= 0.0:
return
var base: Color = st.bg_sb.bg_color
var lit: Color = base.lightened(0.22 * st.bevel)
var dark: Color = base.darkened(0.22 * st.bevel)
var t := 2.0
draw_rect(Rect2(t, t, st.edge - t * 2.0, t), lit, true)
draw_rect(Rect2(t, t, t, st.edge - t * 2.0), lit, true)
draw_rect(Rect2(t, st.edge - t * 2.0, st.edge - t * 2.0, t), dark, true)
draw_rect(Rect2(st.edge - t * 2.0, t, t, st.edge - t * 2.0), dark, true)
## 变异标记域:真视格=危险红内缩框/未开格伤害型变异=左下角危险红三角
func _draw_mutation_marks() -> void:
if st.treasure_mark == "mine_reveal":
var inset := st.edge * 0.14
draw_rect(Rect2(inset, inset, st.edge - inset * 2.0, st.edge - inset * 2.0),
Color(UiKit.RED_MINE, 0.9), false, 2.0)
if st.mutation_mark == "flat_damage":
var mw := st.edge * 0.28
var mpts := PackedVector2Array([Vector2(0, st.edge - mw), Vector2(0, st.edge), Vector2(mw, st.edge)])
draw_colored_polygon(mpts, Color(UiKit.RED_MINE, 0.85))
## 记忆态换装域(demo 口径「地板纹理 + 灰罩 α.66」:
## 地板纹理原样直画 + FOG_MEMORY_PLATE α.66 灰罩压顶;素材零改动、格边长零改动。
## 记忆墨 FOG_MEMORY_INK 对合成底归一后(真实渲染面)三关 5.71–5.93:1 ≥4.5:1(§7.2 硬指标;
## 测点行值与素材随动,见 ui_contrast_probe 迷雾段))
func _draw_fog_memory(rect: Rect2) -> void:
if base.tex_base != null:
draw_texture_rect(base.tex_base, rect, false)
draw_rect(rect, Color(UiKit.FOG_MEMORY_PLATE, 0.66), true)
func _draw_fog_memory() -> void:
if st.tex_base != null:
draw_texture_rect(st.tex_base, Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)), false)
draw_rect(Rect2(Vector2.ZERO, Vector2(st.edge, st.edge)), Color(UiKit.FOG_MEMORY_PLATE, 0.66), true)
if glyph != "":
face.draw_centered_text(self, rect, glyph, UiKit.FOG_MEMORY_INK, maxi(18, int(ceil(edge * 0.5)))) # 字号下限 18(§7.4 冻结公式同源)
_draw_glyph(glyph, UiKit.FOG_MEMORY_INK, maxi(18, int(ceil(st.edge * 0.5)))) # 字号下限 18(§7.4 冻结公式同源)
# 南立面(D 档件一——墙格南缘内侧,自底边向上:双段暗带在下、口沿高光线贴其上沿):
# 坐标推导(自格底边起算):远段=[edge−far, edge],近段=[edge−far−near, edge−far],
# 高光线=[edge−far−near−hl, edge−far−near]——与研究件「墙顶面→高光→暗段→更暗段→格界」下行序一致
func _draw_south_facade() -> void:
if not st.facade_south:
return
var far_h := st.edge * FACADE_FAR_FRAC
var near_h := st.edge * FACADE_NEAR_FRAC
var y_far := st.edge - far_h
var y_near := y_far - near_h
var y_hl := y_near - FACADE_HIGHLIGHT_PX
draw_rect(Rect2(0.0, y_hl, st.edge, FACADE_HIGHLIGHT_PX), UiKit.STAND_STONE_RIM, true) # 口沿高光(UiKit.STAND_STONE_RIM 并档——同值单点)
draw_rect(Rect2(0.0, y_near, st.edge, near_h), FACADE_NEAR_COL, true)
draw_rect(Rect2(0.0, y_far, st.edge, far_h), FACADE_FAR_COL, true)
# 地面投影(D 档件二——北邻墙格在本已开格北缘投渐隐暗带,自北缘向南三段衰减):
# 坐标推导:三等分 proj_band,α 0.28/0.16/0.06(研究件实测贴墙 α≈0.28、带末渐融入地面)
func _draw_north_projection() -> void:
if st.proj_band <= 0.0:
return
var seg := st.proj_band / 3.0
draw_rect(Rect2(0.0, 0.0, st.edge, seg), PROJECTION_NEAR_COL, true)
draw_rect(Rect2(0.0, seg, st.edge, seg), PROJECTION_MID_COL, true)
draw_rect(Rect2(0.0, seg * 2.0, st.edge, seg), PROJECTION_EDGE_COL, true)
# E 格高墙面(圣经 v3.5 冻结①——方向稿 draw_facade 移植):本格已开且南邻为墙 →
# 南部 30% 承接墙面。材质 = 墙顶 center 瓦片顶部 30% 区带**原亮度**直贴(零暗档零阴影);
# 墙脚阴线只画地面/墙面交界(strip 顶缘 1px);墙面底缘(格界处)不画任何线——直接接南侧墙顶
# (白边导致两截)。仅瓦片态服役(tex_facade 缺位 = 平色回退原语义不动)。
func _draw_e_facade() -> void:
if not st.facade_strip or st.tex_facade == null:
return
var strip_h := st.edge * E_FACADE_FRAC
var y0 := st.edge - strip_h
var ts := st.tex_facade.get_size()
draw_texture_rect_region(st.tex_facade, Rect2(0.0, y0, st.edge, strip_h),
Rect2(0.0, 0.0, ts.x, ts.y * E_FACADE_FRAC))
draw_rect(Rect2(0.0, y0, st.edge, 1.0), UiKit.E_FACADE_FOOT_COL, true)
# 文字位绘制:水平居中 + 垂直居中(基线 = (格高 − 字高)/2 + 上伸部)
func _draw_glyph(g: String, col: Color, fs: int) -> void:
var f := st.font if st.font != null else ThemeDB.fallback_font
if f == null:
return
var asc := f.get_ascent(fs)
var desc := f.get_descent(fs)
draw_string(f, Vector2(0.0, (st.edge - (asc + desc)) * 0.5 + asc), g,
HORIZONTAL_ALIGNMENT_CENTER, st.edge, fs, col)
# 微光子件生命周期:有组才建、组变即换、无组即撤——作用集 ≤8 格常驻,
# 其余格零节点零绘制成本。与父件 _draw() 分离的三条理由见本件头注(命令序列/重绘预算/材质挂点)。
func _sync_glow_layer() -> void:
if glow_group == 0:
if st.glow_group == 0:
if _glow_layer != null and is_instance_valid(_glow_layer):
_glow_layer.queue_free()
_glow_layer = null
@@ -96,5 +410,15 @@ func _sync_glow_layer() -> void:
if _glow_layer == null or not is_instance_valid(_glow_layer):
_glow_layer = _GLOW_LAYER.new()
add_child(_glow_layer)
_glow_layer.setup(edge, glow_group)
_glow_layer.set_badge_mask(face.badge_glyph != "")
_glow_layer.setup(st.edge, st.glow_group)
_glow_layer.set_badge_mask(st.badge_glyph != "")
# 徽章绘制:右下角金(文字贴角内缩 2–3px)
func _draw_badge(g: String) -> void:
var f := st.font if st.font != null else ThemeDB.fallback_font
if f == null:
return
var fs := maxi(10, int(st.edge * 0.28))
var sz := f.get_string_size(g, HORIZONTAL_ALIGNMENT_LEFT, -1, fs)
draw_string(f, Vector2(st.edge - sz.x - 2.0, st.edge - 3.0), g, HORIZONTAL_ALIGNMENT_LEFT, -1, fs, UiKit.GOLD)
-130
View File
@@ -1,130 +0,0 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 悬停预承诺呈现域件(BoardView 拆件——单一变化原因=悬停反馈语义):
## 三语义光标、目标格边框、BFS 路径预览、chord 双击首遇锚的唯一承载(自 board_view 收编 11 函数族)。
## 无 class_name(类名缓存面纪律)——经宿主 board_view preload 引用;构造=引用束注入
## (cells/minesweeper/diggable 共享引用+格命中换算/壳级光标写入/hover_forbidden 与 chord 发射注入 Callable——
## 输入经宿主三入站口委托,禁反向持宿主引用);_r0 出生展开区成员留宿主。
extends RefCounted
const COL_HL_TARGET := UiKit.GOLD # 悬停目标格边框(金)
const COL_SEAL := UiKit.SEAL_BLACK # 封印黑(与宿主 :658 双侧服役——各自 alias UiKit 单源)
const CURSOR_DIG := Input.CURSOR_CROSS # 挖——镐形自绘纹理载体
const CURSOR_MOVE_SEM := Input.CURSOR_ARROW # 移动——箭头(命名避开 Control.CURSOR_MOVE 原生成员遮蔽)
const CHORD_WINDOW_MS := 350
var _cells := {} # 共享引用(宿主容器同对象——rebuild 只 clear 不重绑)
var _minesweeper = null # 排雷玩法 只读查询引用(宿主 rebuild 重绑时同拍回写本成员)
var _diggable := {} # 共享引用(可挖域——呼吸微光作用集同源)
var _cb_cell_at: Callable # 格命中换算(宿主 _cell_at——整图公式单点留宿主)
var _cb_cursor: Callable # 壳级光标写入(宿主 mouse_default_cursor_shape 单点)
var _cb_forbidden: Callable # hover_forbidden 信号发射(首遇 toast 去重归组合根)
var _cb_chord: Callable # chord_attempted 信号发射(chord 肌肉记忆提示锚)
var _hover := Vector2i(-9, -9)
var _last_click := {"cell": Vector2i(-9, -9), "ms": -100000}
func _init(bundle: Dictionary) -> void:
_cells = bundle["cells"]
_minesweeper = bundle["minesweeper"]
_diggable = bundle["diggable"]
_cb_cell_at = bundle["cell_at"]
_cb_cursor = bundle["cursor"]
_cb_forbidden = bundle["forbidden"]
_cb_chord = bundle["chord"]
func _hover_at(pos: Vector2) -> void:
var c: Vector2i = _cb_cell_at.call(pos)
if c.x < 0:
if _hover.x >= 0:
_on_cell_unhover(_hover)
elif c != _hover:
if _hover.x >= 0:
_on_cell_unhover(_hover)
_on_cell_hover(c)
func _on_shell_mouse_exited() -> void:
if _hover.x >= 0:
_on_cell_unhover(_hover)
# chord 肌肉记忆首遇锚:同格双左击快开尝试(350ms 窗)——每 run 一次提示由组合根去重
func _detect_chord(c: Vector2i) -> void:
var now := Time.get_ticks_msec()
if _last_click.cell == c and now - int(_last_click.ms) <= CHORD_WINDOW_MS:
_cb_chord.call(c)
_last_click.ms = -100000
return
_last_click = {"cell": c, "ms": now}
func _on_cell_hover(c: Vector2i) -> void:
_hover = c
_apply_hover_visuals(c)
func _on_cell_unhover(c: Vector2i) -> void:
if _hover == c:
_hover = Vector2i(-9, -9)
_clear_hover_visuals(c)
func _refresh_hover_after_paint() -> void:
# 状态随 refresh 变化后光标/边框语义需重算(悬停格四格态可能已变)
if _hover.x >= 0 and _cells.has(_hover):
_apply_hover_visuals(_hover)
func _apply_hover_visuals(c: Vector2i) -> void:
if _minesweeper == null or not _cells.has(c):
return
_clear_hover_visuals(c)
var u: Node2D = _cells[c]
var revealed: bool = _minesweeper.is_revealed(c)
var seal: bool = _minesweeper.has_seal(c)
var adjacent: bool = _diggable.has(c)
if revealed:
u.face.hl_visible = true
u.face.hl_color = COL_HL_TARGET # 移动目标格边框(金)
_set_cursor_shape(c, CURSOR_MOVE_SEM)
_show_path_preview(c)
elif adjacent:
u.face.hl_visible = true
u.face.hl_color = COL_HL_TARGET # 挖掘目标格边框(相邻封印格同此——左键直接挖)
_set_cursor_shape(c, CURSOR_DIG)
elif seal:
u.face.hl_visible = true
u.face.hl_color = COL_SEAL # 已承诺封印标记(封印黑;无交互可供)
_set_cursor_shape(c, Input.CURSOR_FORBIDDEN)
else:
u.face.hl_visible = true
u.face.hl_color = UiKit.RED_FORBID # 不可及域灰禁框(禁止光标+首遇 toast 双通道)
_set_cursor_shape(c, Input.CURSOR_FORBIDDEN)
_cb_forbidden.call(c)
func _clear_hover_visuals(c: Vector2i) -> void:
if not _cells.has(c):
return
_cells[c].face.hl_visible = false
_hide_path_preview()
func _set_cursor_shape(c: Vector2i, shape: int) -> void:
_cb_cursor.call(shape) # 壳级光标(悬停格语义决定——单格悬停恒一)
func _show_path_preview(c: Vector2i) -> void:
# 合法性指示:悬停已开格 → BFS 路径蓝细线点迹(compute_move_path 只读面)
if _minesweeper == null:
return
var path: Array[Variant] = _minesweeper.compute_move_path(c)
for pc in path:
if _cells.has(pc):
_cells[pc].face.dot_visible = true
func _hide_path_preview() -> void:
for c in _cells:
_cells[c].face.dot_visible = false
@@ -1 +0,0 @@
uid://dojreaughx7tf
+176 -132
View File
@@ -12,7 +12,6 @@ const _CELL_UNIT := preload("res://presentation/views/board_cell_unit.gd")
const _BOARD_WORLD := preload("res://presentation/views/board_world.gd")
const _TILE_PALETTE := preload("res://presentation/views/tile_palette.gd")
const _HERO_SPRITE := preload("res://presentation/views/hero_sprite.gd")
const _BOARD_HOVER := preload("res://presentation/views/board_hover.gd")
const COL_BRICK := UiKit.BRICK # 未开——掩尘完整地砖(色板原语 #C9AF83)
const COL_FLOOR := UiKit.FLOOR # 已开——裸土 #B39A78(碎裂裸露地面)
@@ -21,13 +20,17 @@ const COL_HERO := UiKit.HERO_BLUE # 小人边框——小人蓝
const COL_SEAL := UiKit.SEAL_BLACK # 封印黑 #000000
const COL_GOLD := UiKit.GOLD # 奖励金 #F0A828
const COL_CONTACT := UiKit.CONTACT_GLOW # 接触带微光基色(蓝族浅一档·近白蓝——§3.2 v2.8)
const COL_HL_TARGET := UiKit.GOLD # 悬停目标格边框(金)
const REWARD_BADGE := { # 已开格格面资源身份 icon(cell_type 九型键)
"attack": "攻", "defense": "守", "heal": "医", "blast": "爆", "weaken": "弱", "pierce": "穿", "magic": "魔",
"action": "行", # 行 = 行动点型(ap_up/ap_drain 两行)
"gp": "金", # 金 = GP 型(gp_gain/gp_loss 两行;金色系 ~40° 相位,禁入带紫赤 300–345° 外,取色 UiKit.GOLD 徽标色单点)
}
const BADGE_PLACEHOLDER := "?" # 未知图标键占位纹样(HUD框架:禁静默空图标)
const CURSOR_DIG := Input.CURSOR_CROSS # 挖——镐形自绘纹理载体
const CURSOR_SEAL := Input.CURSOR_POINTING_HAND # 封印——封印形自绘纹理载体
const CURSOR_MOVE_SEM := Input.CURSOR_ARROW # 移动——箭头(命名避开 Control.CURSOR_MOVE 原生成员遮蔽)
const CHORD_WINDOW_MS := 350
const SEP := 2 # 格间距(格尺寸公式与命中换算共用)
# 棋盘立体参数
@@ -64,6 +67,8 @@ var _contact_unit: Node2D = null # 接触带承载单元(=英雄格 CellUnit
var _install_tw: Tween # 入场淡入 tween(rebuild 重入先杀)
var _ripple_tws: Array[Tween] = [] # R0 涟漪逐格 tween(rebuild 重入全杀)
var _dig_glow := false # 挖掘期才允许唯一在动元素(art-bible §2.1)
var _hover := Vector2i(-9, -9)
var _last_click := {"cell": Vector2i(-9, -9), "ms": -100000}
var _r0 := {} # 出生展开区 R₀(地图战斗资源 mapping 资格域 = S∖R₀——徽章查询跳过,防 E19 诊断噪音)
var _desat_mat: ShaderMaterial
var _desat_tween: Tween
@@ -72,7 +77,6 @@ var _cell := 46.0 # 当前格边长(set_cell_area 按容器尺寸重
# ── 拆出件(状态单点在件内)──
var _palette = _TILE_PALETTE.new() # 瓦片件:装载缓存+查表+明度/墨族静态算法
var _hero_sprite = _HERO_SPRITE.new() # 英雄精灵件:_init 挂为本件子级(tween 需在树上)
var _hover_comp: _BOARD_HOVER # 悬停预承诺呈现域件:三语义光标/目标格边框/路径预览/chord 首遇锚(_init 建为子件——HeroSprite 挂载先例)
# ── 件成员同名转发(探针直读面零改动:读 _palette.seal_tex、读写 _hero_sprite.tex_idle;其余为随迁状态单点再导出)──
# ---- 视野迷雾(纯呈现层;现行事实源=HUD框架「视野迷雾」节 v0.13,渲染规格=art-bible §6.5 v3.5)----
@@ -116,25 +120,6 @@ func _init() -> void:
_hero_sprite.tex_idle = UiKit.load_tex(UiKit.TEX_HERO_IDLE)
_hero_sprite.tex_walk = UiKit.load_tex(UiKit.TEX_HERO_WALK)
_palette.load_board_tiles() # 高墙/地面瓦片整批守卫装载(缺位静默回退——英雄同款纪律,禁刷屏)
_hover_comp = _BOARD_HOVER.new({
"cells": _cells, "minesweeper": _minesweeper, "diggable": _diggable,
"cell_at": _cell_at, "cursor": _set_shell_cursor,
"forbidden": _emit_hover_forbidden, "chord": _emit_chord_attempted,
})
# ---- 悬停域注入面(件回调宿主的壳级单点——Callable 目标)----
func _set_shell_cursor(shape: int) -> void:
mouse_default_cursor_shape = shape # 壳级光标(悬停格语义决定——单格悬停恒一)
func _emit_hover_forbidden(c: Vector2i) -> void:
hover_forbidden.emit(c) ## 非相邻未开格悬停(首遇 toast 归组合根去重)
func _emit_chord_attempted(c: Vector2i) -> void:
chord_attempted.emit(c) ## 双击快开尝试(chord 肌肉记忆提示锚)
func board() -> BoardData:
@@ -143,7 +128,7 @@ func board() -> BoardData:
func _ready() -> void:
_apply_custom_cursors()
mouse_exited.connect(_hover_comp._on_shell_mouse_exited)
mouse_exited.connect(_on_shell_mouse_exited)
# 结算期/道具窗口态:D 区去饱和 + 金橙偏移锁输入(明度不变——art-bible §2);
@@ -197,7 +182,7 @@ func set_treasure_overlays(reveal_cells: Array, preview_cells: Array) -> void:
func set_world_font(f: Font) -> void:
_world.board_font = f
for c in _cells:
_cells[c].face.font = f
_cells[c].st.font = f
_cells[c].queue_redraw()
@@ -216,7 +201,7 @@ func set_cell_area(avail: Vector2) -> void:
var stride := _cell + float(SEP)
for c in _cells:
var u: Node2D = _cells[c]
u.edge = _cell
u.st.edge = _cell
u.position = Vector2(c) * stride
if _world.hero != null and _hero_sprite.last.x >= 0:
_place_hero(_hero_sprite.last, _hero_sprite.last, true) # 格边长变更(窗口缩放)——英雄即时随格重置(杀位移 tween 防飞陈旧落点)
@@ -239,7 +224,6 @@ func _refresh_slab() -> void:
func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void:
_board = board
_minesweeper = minesweeper
_hover_comp._minesweeper = _minesweeper # 件内只读引用随宿主重绑同拍回写(每战新排雷玩法对象)
_palette.bind_board(_board, _minesweeper) # 查表族棋盘态随板同步(换板后首拍 refresh 前置)
for child in _world.get_children():
child.queue_free()
@@ -250,7 +234,7 @@ func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void:
_contact_unit = null
_hero_sprite.last = Vector2i(-99, -99)
_r0.clear()
_hover_comp._hover = Vector2i(-9, -9)
_hover = Vector2i(-9, -9)
for c in _board.compute_r0():
_r0[c] = true
# 瓦片套随板图型切换(board_gen 词表 v1.1 dungeon/underdark/deep_underdark;
@@ -262,30 +246,25 @@ func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void:
for y in _board.height:
for x in _board.width:
var c := Vector2i(x, y)
_cells[c] = _build_cell_unit(c, stride)
var u: Node2D = _CELL_UNIT.new()
u.st.cell = c
u.st.edge = _cell
u.position = Vector2(c) * stride
u.st.bevel = DEPTH_BEVEL # 新建格单元继承
u.use_parent_material = true # 去饱和材质挂 BoardWorld——单元随世界材质灰化
if _palette.tiles_ok:
# NEAREST 采样(32×32 源 × 45/36px 格边非整数缩放接受档,
# 与英雄小人 1.5× 豁免同级(圣经 §8.2);只在瓦片
# 模式启用——程序化回退路径渲染零扰动(含格面文字采样不回退不变化)
u.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
u.st.font = _world.board_font
_world.add_child(u)
_cells[c] = u
set_cell_area(size)
_install_entrance_fx()
refresh()
# 逐格构建器(rebuild 建格循环唯一出函面):.new() 与属性装配逐字迁入,循环体只调构建器
func _build_cell_unit(c: Vector2i, stride: float) -> Node2D:
var u: Node2D = _CELL_UNIT.new()
u.cell = c
u.edge = _cell
u.position = Vector2(c) * stride
u.base.bevel = DEPTH_BEVEL # 新建格单元继承
u.use_parent_material = true # 去饱和材质挂 BoardWorld——单元随世界材质灰化
if _palette.tiles_ok:
# NEAREST 采样(32×32 源 × 45/36px 格边非整数缩放接受档,
# 与英雄小人 1.5× 豁免同级(圣经 §8.2);只在瓦片
# 模式启用——程序化回退路径渲染零扰动(含格面文字采样不回退不变化)
u.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
u.face.font = _world.board_font
_world.add_child(u)
return u
# 逐格重绘(挖掘/结算后调用)
func refresh() -> void:
if _board == null or _minesweeper == null:
@@ -299,7 +278,7 @@ func refresh() -> void:
for y in _board.height:
for x in _board.width:
_paint_cell(Vector2i(x, y), hero)
_hover_comp._refresh_hover_after_paint()
_refresh_hover_after_paint()
_apply_contact_bands()
_update_hero(hero) # 英雄精灵 diff 驱动
@@ -449,18 +428,33 @@ func _update_world_size() -> void:
# 真实输入路径(Node2D 迁移批 Decision 3):壳层 _gui_input → 整图公式换算 → 复用既有 hover/点选/chord 出口
#(悬停族已拆 board_hover 件——本壳留三入站口委托)
func _gui_input(ev: InputEvent) -> void:
if _minesweeper == null:
return
if ev is InputEventMouseMotion:
_hover_comp._hover_at(ev.position)
_hover_at(ev.position)
elif ev is InputEventMouseButton and ev.pressed:
var c := _cell_at(ev.position)
if c.x >= 0:
_on_cell_input(c, ev)
func _hover_at(pos: Vector2) -> void:
var c := _cell_at(pos)
if c.x < 0:
if _hover.x >= 0:
_on_cell_unhover(_hover)
elif c != _hover:
if _hover.x >= 0:
_on_cell_unhover(_hover)
_on_cell_hover(c)
func _on_shell_mouse_exited() -> void:
if _hover.x >= 0:
_on_cell_unhover(_hover)
func _on_cell_input(c: Vector2i, ev: InputEvent) -> void:
if _minesweeper == null or not (ev is InputEventMouseButton and ev.pressed):
return
@@ -469,7 +463,7 @@ func _on_cell_input(c: Vector2i, ev: InputEvent) -> void:
action_requested.emit(c, "move")
else:
action_requested.emit(c, "dig")
_hover_comp._detect_chord(c)
_detect_chord(c)
elif ev.button_index == MOUSE_BUTTON_RIGHT:
if not _minesweeper.has_seal(c):
action_requested.emit(c, "seal_place")
@@ -477,6 +471,89 @@ func _on_cell_input(c: Vector2i, ev: InputEvent) -> void:
# 左键相邻封印格直接挖(规则引擎放行)。
# chord 肌肉记忆首遇锚:同格双左击快开尝试(350ms 窗)——每 run 一次提示由组合根去重
func _detect_chord(c: Vector2i) -> void:
var now := Time.get_ticks_msec()
if _last_click.cell == c and now - int(_last_click.ms) <= CHORD_WINDOW_MS:
chord_attempted.emit(c)
_last_click.ms = -100000
return
_last_click = {"cell": c, "ms": now}
# ---- 悬停预承诺(pattern 4):三语义光标 + 目标格边框 + BFS 路径预览 ----
func _on_cell_hover(c: Vector2i) -> void:
_hover = c
_apply_hover_visuals(c)
func _on_cell_unhover(c: Vector2i) -> void:
if _hover == c:
_hover = Vector2i(-9, -9)
_clear_hover_visuals(c)
func _refresh_hover_after_paint() -> void:
# 状态随 refresh 变化后光标/边框语义需重算(悬停格四格态可能已变)
if _hover.x >= 0 and _cells.has(_hover):
_apply_hover_visuals(_hover)
func _apply_hover_visuals(c: Vector2i) -> void:
if _minesweeper == null or not _cells.has(c):
return
_clear_hover_visuals(c)
var u: Node2D = _cells[c]
var revealed: bool = _minesweeper.is_revealed(c)
var seal: bool = _minesweeper.has_seal(c)
var adjacent: bool = _diggable.has(c)
if revealed:
u.st.hl_visible = true
u.st.hl_color = COL_HL_TARGET # 移动目标格边框(金)
_set_cursor_shape(c, CURSOR_MOVE_SEM)
_show_path_preview(c)
elif adjacent:
u.st.hl_visible = true
u.st.hl_color = COL_HL_TARGET # 挖掘目标格边框(相邻封印格同此——左键直接挖)
_set_cursor_shape(c, CURSOR_DIG)
elif seal:
u.st.hl_visible = true
u.st.hl_color = COL_SEAL # 已承诺封印标记(封印黑;无交互可供)
_set_cursor_shape(c, Input.CURSOR_FORBIDDEN)
else:
u.st.hl_visible = true
u.st.hl_color = UiKit.RED_FORBID # 不可及域灰禁框(禁止光标+首遇 toast 双通道)
_set_cursor_shape(c, Input.CURSOR_FORBIDDEN)
hover_forbidden.emit(c)
func _clear_hover_visuals(c: Vector2i) -> void:
if not _cells.has(c):
return
_cells[c].st.hl_visible = false
_hide_path_preview()
func _set_cursor_shape(c: Vector2i, shape: int) -> void:
mouse_default_cursor_shape = shape # 壳级光标(悬停格语义决定——单格悬停恒一)
func _show_path_preview(c: Vector2i) -> void:
# 合法性指示:悬停已开格 → BFS 路径蓝细线点迹(compute_move_path 只读面)
if _minesweeper == null:
return
var path: Array[Variant] = _minesweeper.compute_move_path(c)
for pc in path:
if _cells.has(pc):
_cells[pc].st.dot_visible = true
func _hide_path_preview() -> void:
for c in _cells:
_cells[c].st.dot_visible = false
# ---- 奖励徽章(已开格身份 icon——封印之下、纹带之上、不进数字区) ----
func _update_badge(c: Vector2i) -> String:
@@ -529,30 +606,16 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void:
var seal: bool = _minesweeper.has_seal(c)
var removed: bool = _minesweeper.is_removed(c) # 中立移除格(退色降档)
var is_hero: bool = c == hero
# 封印槽统一维护:封印格=封印印记纹理;其余格清 null(封印格挖开后残留在 tex_seal
# 会让 _draw 无条件把封印印画在已开格上——tex_seal 绘制无 revealed 条件)。缺盘走字形回退。
u.face.tex_seal = _palette.seal_tex if (seal and not revealed and _palette.seal_tex != null) else null
if removed:
revealed = false # 移除格固定未开呈现基座(退色暗幕层承载;危险格本无数字,周边数字冻结)
seal = false
u.face.tex_seal = null
# 绘制层五段(字形与色牌→基底瓦片→立面投影→秘宝/变异叠加→迷雾)——段间交换经单元状态面
#(u.base/u.face 域件与 u.glyph/u.glyph_color),唯一例外=基底色 bg 经 ink 段返回值交 base 段(无单元场域承载)
var bg: Color = _paint_cell_ink(c, u, revealed, seal)
_paint_cell_base(c, u, revealed, is_hero, bg)
_paint_cell_facade(c, u, revealed)
_paint_cell_overlays(c, u, revealed)
_paint_cell_fog(c, u, revealed, hero)
u.face.removed_dim = removed
u.queue_redraw()
# 字形与色牌段:reveal 记录→字形/墨色判定+u.glyph/u.glyph_color 终写+双峰纹理板落牌;
# 返回基底色 bg(平色回退 sb 构建的唯一消费方在基底段)
func _paint_cell_ink(c: Vector2i, u: Node2D, revealed: bool, seal: bool) -> Color:
var glyph := ""
var bg: Color
var digit_col := COL_DIGIT
# 封印槽统一维护:封印格=封印印记纹理;其余格清 null(封印格挖开后残留在 tex_seal
# 会让 _draw 无条件把封印印画在已开格上——tex_seal 绘制无 revealed 条件)。缺盘走字形回退。
u.st.tex_seal = _palette.seal_tex if (seal and not revealed and _palette.seal_tex != null) else null
if removed:
revealed = false # 移除格固定未开呈现基座(退色暗幕层承载;危险格本无数字,周边数字冻结)
seal = false
u.st.tex_seal = null
if revealed:
var rec: Dictionary[Variant, Variant] = _minesweeper.reveal_record(c) # 已开记录经查询面
if rec.get("kind", "") == "mine":
@@ -576,21 +639,6 @@ func _paint_cell_ink(c: Vector2i, u: Node2D, revealed: bool, seal: bool) -> Colo
digit_col = COL_SEAL
else:
bg = COL_BRICK
u.glyph = glyph
u.glyph_color = digit_col
# 色牌后备(数字直画退牌;「危」/「?」维持牌载保读性)+ v3.4:双峰纹理板
#(_palette.digit_plate——underdark 系亮白筋+近黑斑并存的直画不可救纹理)数字落牌,牌上深墨 12:1
var plate_digit: bool = _palette.digit_plate and glyph != "" and glyph != "危" and glyph != "?" and glyph != "封" and glyph != "人"
if plate_digit:
digit_col = COL_DIGIT # 牌上深墨族(素牌 12:1——双峰纹理免疫)
u.glyph_color = digit_col
u.plate = revealed and (glyph == "危" or glyph == "?" or plate_digit)
return bg
# 基底瓦片段:瓦片查表/平色回退 sb+英雄蓝框载体;英雄贴图缺位回退字形「人」在此段落
# u.glyph(先于叠加段消费,结果与原函数字形累计局部量逐字同值)
func _paint_cell_base(c: Vector2i, u: Node2D, revealed: bool, is_hero: bool, bg: Color) -> void:
# 瓦片模式:tex_base = 查表瓦片(未开=墙瓦片/已开=地面瓦片),
# bg_sb 降级为透明描边载体——只承载呼吸微光/主角蓝框(CellUnit 瓦片态画在瓦片之上),
# 瓦片分配=格坐标哈希,初始化落定后逐拍稳定(禁逐轮重掷——圣经原则三)
@@ -610,21 +658,26 @@ func _paint_cell_base(c: Vector2i, u: Node2D, revealed: bool, is_hero: bool, bg:
sb = UiKit.panel_flat(Color.TRANSPARENT, Color.TRANSPARENT,UiKit.RADIUS_OBJECT, 0)
sb.set_border_width_all(3)
sb.border_color = COL_HERO
if u.glyph == "" and _hero_sprite.tex_idle == null:
if glyph == "" and _hero_sprite.tex_idle == null:
# 贴图缺位回退字形「人」(项目纹理槽惯例——贴图在位=格内字形退役,单位本体由独立精灵承载)
u.glyph = "人"
u.glyph_color = COL_HERO
u.base.tex_base = tile
u.base.bg_sb = sb
# 立面投影段:未开墙格南缘立面/轮廓描边码;已开格北缘投影暗带/E 格高墙面——段内重置与赋值同序
func _paint_cell_facade(c: Vector2i, u: Node2D, revealed: bool) -> void:
u.base.outline_sides.assign([]) # typed Array[int](assign 保持引用语义)
u.base.facade_south = false
u.base.proj_band = 0.0
u.base.tex_facade = null
u.base.facade_strip = false
glyph = "人"
digit_col = COL_HERO
u.st.tex_base = tile
u.st.bg_sb = sb
u.glyph = glyph
u.glyph_color = digit_col
# 色牌后备(数字直画退牌;「危」/「?」维持牌载保读性)+ v3.4:双峰纹理板
#(_palette.digit_plate——underdark 系亮白筋+近黑斑并存的直画不可救纹理)数字落牌,牌上深墨 12:1
var plate_digit: bool = _palette.digit_plate and glyph != "" and glyph != "危" and glyph != "?" and glyph != "封" and glyph != "人"
if plate_digit:
digit_col = COL_DIGIT # 牌上深墨族(素牌 12:1——双峰纹理免疫)
u.glyph_color = digit_col
u.plate = revealed and (glyph == "危" or glyph == "?" or plate_digit)
u.st.outline_sides.assign([]) # typed Array[int](assign 保持引用语义)
u.st.facade_south = false
u.st.proj_band = 0.0
u.st.tex_facade = null
u.st.facade_strip = false
# 封印槽不在此重置:尾部重置会与此上方条件赋值构成双写踩踏(把封印纹理恒清 null=素材在位时封印不可见);
# 上方 else 分支已是全路径清残留,尾部重置系冗余且有害
var south_n: Vector2i = c + Vector2i(0, 1)
@@ -634,55 +687,44 @@ func _paint_cell_facade(c: Vector2i, u: Node2D, revealed: bool) -> void:
if not revealed:
# 高墙感 D 投影档:南邻地面格 → 墙格南缘立「口沿高光+双段暗带」立面
#(邻地面才画——墙-墙内界保墙群内聚);板外=墙不画
u.base.facade_south = south_in and _minesweeper.is_revealed(south_n)
u.st.facade_south = south_in and _minesweeper.is_revealed(south_n)
# 轮廓加强:墙格四邻中已开(地面)格朝向记描边码(板外=墙不描——保墙群外轮廓只在墙/地交界)
for d in [Vector2i(0, -1), Vector2i(0, 1), Vector2i(-1, 0), Vector2i(1, 0)]:
var n: Vector2i = c + d
if n.x >= 0 and n.y >= 0 and n.x < _board.width and n.y < _board.height and _minesweeper.is_revealed(n):
var code := 0 if d.y < 0 else (1 if d.y > 0 else (2 if d.x < 0 else 3))
if code == 1 and u.base.facade_south:
if code == 1 and u.st.facade_south:
continue # 南缘由立面件承载(高光+双段暗带)——2px 描边让位防三重叠黑
if code == 0 and _palette.tiles_ok:
continue # v3.5 冻结① (b)-(c) 交界无边框:墙面直贴接墙顶(E 格墙面件在邻格承载)——北向描边不画(平色回退态保留描边)
u.base.outline_sides.append(code)
u.st.outline_sides.append(code)
else:
# 地面投影:北邻墙格在本已开格北缘投渐隐暗带(ITB 同款高度线索——带宽按格边比例常量)
if north_in and not _minesweeper.is_revealed(north_n):
u.base.proj_band = u.edge * WALL_PROJECTION_FRAC
u.st.proj_band = u.st.edge * WALL_PROJECTION_FRAC
# E 格高墙面(圣经 v3.5 冻结①——方向稿 is_facade_cell 口径):已开格南邻未开墙 → 南部 30%
# 承接墙面(材质=同板墙顶 center 瓦片,原亮度直贴);仅瓦片态启用——平色回退保持原语义
if south_in and not _minesweeper.is_revealed(south_n) and _palette.tiles_ok:
u.base.facade_strip = true
u.base.tex_facade = _palette.tiles.get("wall_center")
# 秘宝/变异叠加段:真视红框/预读徽章/变异角标/奖励徽章/类型微光组号——glyph/digit_col 局部镜像
# 自 u.glyph/u.glyph_color(原函数字形累计局部量的段内重建,预读徽章分支的无效局部写入逐字保留)
func _paint_cell_overlays(c: Vector2i, u: Node2D, revealed: bool) -> void:
u.st.facade_strip = true
u.st.tex_facade = _palette.tiles.get("wall_center")
# ②( 秘宝叠加层:真视格=红内缩框;预读格=奖励身份徽章(identity_resolver 既有
# 只读面取字形——零数据复制,badge 位复用);已开格标记自然退位(refresh 逐拍重算)
var glyph: String = u.glyph
var digit_col: Color = u.glyph_color
if not revealed:
u.face.treasure_mark = "mine_reveal" if _treasure_reveal.has(c) else ""
u.st.treasure_mark = "mine_reveal" if _treasure_reveal.has(c) else ""
if _treasure_preview.has(c) and glyph == "":
var pid = _identity_cb.call(c) if _identity_cb.is_valid() else null
if pid != null and REWARD_BADGE.has(String(pid.get("cell_type", ""))):
glyph = String(REWARD_BADGE[String(pid.get("cell_type", ""))])
digit_col = COL_GOLD
u.face.badge_glyph = glyph
u.st.badge_glyph = glyph
# ②' 变异角标:未开格带伤害型变异 → 危险红角标(已开=威胁已兑现标记退位);
# 回调缺位 = 无标记(程序化角标,tex_mutation 纹带槽暂留)
if not revealed and _vmark_cb.is_valid():
u.face.mutation_mark = String(_vmark_cb.call(c))
u.st.mutation_mark = String(_vmark_cb.call(c))
else:
u.face.mutation_mark = ""
u.face.badge_glyph = _update_badge(c)
u.glow_group = int(_glow.get(c, UiKit.GLOW_NONE))
# 迷雾段:灯光区外未开格全黑/已开格记忆态灰显——纯呈现零逻辑(🔴 契约 1)
func _paint_cell_fog(c: Vector2i, u: Node2D, revealed: bool, hero: Vector2i) -> void:
u.st.mutation_mark = ""
u.st.badge_glyph = _update_badge(c)
u.st.glow_group = int(_glow.get(c, UiKit.GLOW_NONE))
# 视野迷雾呈态:曼哈顿距离 > 半径 → 区外——未开格全黑(现行事实源照抄)/
# 已开格记忆态(灰显可读);以英雄格为心逐格重算,refresh()/notify_hero_move() 既有重绘驱动=移动实时跟随。
# 纯呈现零逻辑——格态/数字真值/清图判定零触碰(🔴 契约 1);结算期去饱和叠加互斥由 CellUnit 记忆态终态档承载。
@@ -692,6 +734,8 @@ func _paint_cell_fog(c: Vector2i, u: Node2D, revealed: bool, hero: Vector2i) ->
u.fog_state = (2 if revealed else 1) if fogged else 0
else:
u.fog_state = 0
u.st.removed_dim = removed
u.queue_redraw()
# ---- 可挖域接触带呼吸微光(挖掘期唯一在动元素——§3.2 v2.8 画法:光画英雄格周界空间侧,
@@ -721,7 +765,7 @@ func _sync_glow_to_units() -> void:
for c in touched:
var u: Node2D = _cells.get(c)
if u != null:
u.glow_group = int(_glow.get(c, UiKit.GLOW_NONE))
u.st.glow_group = int(_glow.get(c, UiKit.GLOW_NONE))
func _process(delta: float) -> void:
@@ -733,14 +777,14 @@ func _process(delta: float) -> void:
if UiKit.motion_scale < 1.0:
return # 减动效:呼吸停格(refresh/enable 时已施静态色——静态可辨、无持续运动)
var k := 0.5 + 0.5 * sin(_breath_t * 3.0) # 3 rad/s≈0.48Hz——无 >3 次/秒频闪(AC 基线)
_contact_unit.base.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k)
_contact_unit.st.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k)
_contact_unit.queue_redraw()
# 带几何写入(refresh / set_dig_glow_enabled(true) 挂点):清旧承载件→按英雄格可挖域写边/角码→施色
func _apply_contact_bands() -> void:
if _contact_unit != null and is_instance_valid(_contact_unit) and (_cells.get(_minesweeper.hero_pos()) != _contact_unit if _minesweeper != null else true):
_contact_unit.base.contact_col = Color(0, 0, 0, 0)
_contact_unit.st.contact_col = Color(0, 0, 0, 0)
_contact_unit.queue_redraw()
_contact_unit = null
if _board == null or _minesweeper == null or not _dig_glow:
@@ -757,19 +801,19 @@ func _apply_contact_bands() -> void:
for d in [Vector2i(-1, -1), Vector2i(1, -1), Vector2i(-1, 1), Vector2i(1, 1)]:
if _diggable.has(_minesweeper.hero_pos() + d):
corners.append(0 if (d.x < 0 and d.y < 0) else (1 if d.x > 0 and d.y < 0 else (2 if d.x < 0 else 3)))
hu.base.contact_bands = bands
hu.base.contact_corners = corners
hu.st.contact_bands = bands
hu.st.contact_corners = corners
# 减动效:呼吸停格——refresh/enable 重放时施**静态**色(本件头注「已施静态色」的落実);
# 0.5 = 呼吸周期中值(正常态 k 的均值)。否则减动效下每次 refresh 都会按当前相位重施色,
# 带色在 refresh 间跳变=持续运动假象,且令 on/off 双帧取证无法隔离单一变量。
var k := 0.5 if UiKit.motion_scale < 1.0 else 0.5 + 0.5 * sin(_breath_t * 3.0)
hu.base.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k)
hu.st.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k)
hu.queue_redraw()
func _reset_breath() -> void:
if _contact_unit != null and is_instance_valid(_contact_unit):
_contact_unit.base.contact_col = Color(0, 0, 0, 0)
_contact_unit.st.contact_col = Color(0, 0, 0, 0)
_contact_unit.queue_redraw()
@@ -816,9 +860,9 @@ func diggable_probe() -> Dictionary:
return _diggable.duplicate()
# 悬停格只读出口(case① 悬停到达断言消费面——读件投影,返回语义零变化)
# 悬停格只读出口(case① 悬停到达断言消费面)
func hover_probe() -> Vector2i:
return _hover_comp._hover
return _hover
# 迷雾参数只读快照(case⑮ 参数漂移断言消费面)
@@ -844,5 +888,5 @@ func cell_rect_probe(c: Vector2i) -> Rect2:
if host.size.x <= 0.0 or host.size.y <= 0.0 or vp_size.x <= 0.0 or vp_size.y <= 0.0:
return Rect2()
var k: Vector2 = host.size / vp_size
var local := Rect2(u.global_position, Vector2(u.edge, u.edge))
var local := Rect2(u.global_position, Vector2(u.st.edge, u.st.edge))
return Rect2(host.position + local.position * k, local.size * k)
-204
View File
@@ -1,204 +0,0 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 格面基座层绘制域件(CellUnit 拆件——单一变化原因=基座层视觉规格):
## 底面/描边/高墙立面/地面投影/接触带微光/顶面倒角的全部绘制入参与绘制命令。
## 无 class_name(类名缓存面纪律)——经宿主 board_cell_unit preload 引用;宿主 _draw 按层序调
## draw_base(canvas, rect) 把绘制命令落到宿主画布;setter 经 _owner 回引触发宿主重绘语义逐字。
extends RefCounted
# ── D 投影档绘制常量(研究件实测取色——立面高光/双段暗带/地面投影;黑罩 alpha 叠加实测等效)──
const FACADE_HIGHLIGHT_PX := 2.0 # 口沿高光线厚(像素)
const FACADE_NEAR_FRAC := 0.16 # 立面近段高(格边比——研究件 10/64)
const FACADE_FAR_FRAC := 0.25 # 立面远段高(格边比——研究件 16/64)
const FACADE_NEAR_COL := Color(0, 0, 0, 0.60) # 立面近段黑罩(墙顶 132→52 实测 α≈0.61)
const FACADE_FAR_COL := Color(0, 0, 0, 0.82) # 立面远段黑罩(墙顶 132→24 实测 α≈0.82)
const PROJECTION_NEAR_COL := Color(0, 0, 0, 0.28) # 投影贴墙段(地面 66→46 实测 α≈0.28)
const PROJECTION_MID_COL := Color(0, 0, 0, 0.16) # 投影中段(渐隐)
const PROJECTION_EDGE_COL := Color(0, 0, 0, 0.06) # 投影远段(渐隐收边)
# ── E 格高墙面(圣经 v3.5 冻结①——地面格南邻墙→南部 30% 承接墙面:墙顶纹理原亮度
# 直贴零暗档零阴影;墙脚阴线只画地面/墙面交界;(b)-(c) 交界
# 无边框由 BoardView 侧跳过墙格北向描边承载)──
const E_FACADE_FRAC := 0.30 # 墙面承接高(格边比 30%)
var _owner: Node2D = null
var bg_sb: StyleBoxFlat = null # 格底 StyleBox(BoardView 每次 paint 重建;呼吸微光就地改边框)
# ── 接触带微光绘制面(可挖域微光画在英雄格周界空间侧——art-bible §3.2 v2.8;
# 仅英雄格单元承载,BoardView.refresh 按可挖域写边/角码、_process 逐拍更新呼吸色)──
var contact_bands: Array[int] = [] # 正交接触边码 0=N/1=S/2=W/3=E(该侧整条边)
var contact_corners: Array[int] = [] # 斜向共享角码 0=NW/1=NE/2=SW/3=SE(角点+两侧各半条边)
var contact_col := Color(0, 0, 0, 0) # 当前呼吸色(alpha 0 = 不画)
var outline_sides: Array[int] = [] # 墙格地面朝向描边码 0=N/1=S/2=W/3=E(程序侧结构件)
# ── 高墙感 D 投影档(研究件四变体定 D=C+地面投影,ITB 同款高度线索)──
# 南立面 = 墙格南缘内侧「口沿高光线 + 双段暗带」(邻地面格才画,墙群内聚);地面投影 = 北邻墙格
# 在本已开格北缘投渐隐暗带。参数按研究件 64px 拼版实测比例换算(立面双段 0.16/0.25 格边、投影带
# 0.22 格边;坐标推导留档 commit)。素材缺位程序化回退同样服役(结构归程序——art-bible §8.5)。
var facade_south := false: # 南立面开关(BoardView paint 按「南邻已开格」写入)
set(v):
facade_south = v
_owner.queue_redraw()
var proj_band := 0.0: # 地面投影带宽(像素,0 = 不画——BoardView 按北邻墙判定写入)
set(v):
proj_band = v
_owner.queue_redraw()
var bevel := 0.0: # 顶面倒角强度(0=关;档位 0/0.35/0.6/1.0)
set(v):
bevel = v
_owner.queue_redraw()
var tex_base: Texture2D = null # ① 砖面底(宿主迷雾记忆态公开单向读)
var tex_facade: Texture2D = null # ⑥ E 格墙面材质(v3.5——南邻墙顶 center 瓦片同源,BoardView 注入)
var facade_strip := false: # E 格开关(BoardView paint 按「已开格且南邻未开墙」写入——瓦片态专用)
set(v):
facade_strip = v
_owner.queue_redraw()
func _init(owner: Node2D) -> void:
_owner = owner
## ① 砖面底层域(占位 = StyleBoxFlat:格底色 + 暗化描边 + 3px 圆角;瓦片态描边覆盖层;
## 呼吸微光/主角蓝框由 BoardView 改 bg_sb 后触发重绘。素材缺位回退走 elif 程序化原样,零观感变化)
func draw_base(canvas: CanvasItem, rect: Rect2) -> void:
if tex_base != null:
canvas.draw_texture_rect(tex_base, rect, false)
# 轮廓加强(轮廓线不明显):墙格地面朝向边补 2px 深墨线(§8.5 结构归程序——
# 素材 1px 墨线在 45/36px 非整数缩放下视觉衰减,程序线保底;墙-墙内界不画,保墙群内聚;
# 坐标推导:各边内贴 2px 矩形,相邻边矩形全长相交=周界闭合)
if not outline_sides.is_empty():
_draw_outline_sides(canvas, rect)
_draw_south_facade(canvas, rect)
_draw_north_projection(canvas, rect)
_draw_e_facade(canvas, rect)
# 瓦片态描边覆盖层:tex_base 在位时 BoardView 传入的 bg_sb
# 已降级为「透明底 + 描边」载体——呼吸微光/主角蓝框画在瓦片之上(art-bible §1.2 叠层序
# 不变:底 < 纹带 < 数字 < 封印 < 焦点高亮);素材缺位回退走 elif 程序化原样,零观感变化
if bg_sb != null:
canvas.draw_style_box(bg_sb, rect)
_draw_contact_or_fallback(canvas, rect)
## 接触带微光或平色回退域(英雄格周界空间侧——正交邻墙=整条边;斜向=共享角点+两侧各半条边;光不进墙格):
## 带宽 4px 贴格边内侧(坐标推导:N=[0,0,edge,4] S=[0,edge-4,edge,4] W=[0,0,4,edge] E=[edge-4,0,4,edge];
## 半条边=沿邻边自角点起 edge/2;角点圆 r=6 贴角点圆心,近白芯 r=2.5 提亮)
func _draw_contact_or_fallback(canvas: CanvasItem, rect: Rect2) -> void:
if contact_col.a > 0.0:
_draw_north_projection(canvas, rect) # 平色回退+微光并发的英雄格补画(瓦片路径已画——同像素重复无观感差)
_draw_e_facade(canvas, rect) # E 格墙面补画(同上——英雄格兼 E 格时瓦片路径已画,重复无害)
_draw_contact_bands(canvas, rect)
elif bg_sb != null:
canvas.draw_style_box(bg_sb, rect)
_draw_south_facade(canvas, rect) # 平色回退路径——D 投影档两件同样服役(结构归程序)
_draw_north_projection(canvas, rect)
_draw_bevel(canvas, rect)
## 墙格轮廓域:地面朝向边补 2px 深墨线(各边内贴 2px 矩形,相邻边矩形全长相交=周界闭合)
func _draw_outline_sides(canvas: CanvasItem, rect: Rect2) -> void:
var ow := 2.0
var oc := UiKit.FOG_OUTLINE
for b in outline_sides:
var or_ := Rect2()
match int(b):
0: or_ = Rect2(0, 0, rect.size.x, ow)
1: or_ = Rect2(0, rect.size.x - ow, rect.size.x, ow)
2: or_ = Rect2(0, 0, ow, rect.size.x)
3: or_ = Rect2(rect.size.x - ow, 0, ow, rect.size.x)
canvas.draw_rect(or_, oc, true)
## 接触带微光域:正交整条边 + 斜向共享角点与两侧半条边
func _draw_contact_bands(canvas: CanvasItem, rect: Rect2) -> void:
var band := 4.0
var half := rect.size.x * 0.5
for b in contact_bands:
var r := Rect2()
match int(b):
0: r = Rect2(0, 0, rect.size.x, band)
1: r = Rect2(0, rect.size.x - band, rect.size.x, band)
2: r = Rect2(0, 0, band, rect.size.x)
3: r = Rect2(rect.size.x - band, 0, band, rect.size.x)
canvas.draw_rect(r, contact_col, true)
for cc in contact_corners:
_draw_contact_corner(canvas, rect, int(cc), band, half)
## 斜向角点域:角点圆 r=6 贴角点圆心 + 近白芯 r=2.5 提亮 + 半条边(角点沿两条邻边各延伸 edge/2——
## 坐标推导:横带沿 N/S 自角点向板心侧 half;NW/SE 半带 x=y=角点坐标,NE/SE 半带自 edge-half 收进——
## 正交整边时重叠同色无害)
func _draw_contact_corner(canvas: CanvasItem, rect: Rect2, cc: int, band: float, half: float) -> void:
var pt := Vector2.ZERO
match cc:
0: pt = Vector2(0, 0)
1: pt = Vector2(rect.size.x, 0)
2: pt = Vector2(0, rect.size.x)
3: pt = Vector2(rect.size.x, rect.size.x)
canvas.draw_circle(pt, 6.0, contact_col)
canvas.draw_circle(pt, 2.5, Color(1, 1, 1, minf(1.0, contact_col.a * 0.95)))
if cc == 0 or cc == 1:
# N 侧横带:NW 自 x=0、NE 自 x=edge-half
canvas.draw_rect(Rect2(pt.x if cc == 0 else pt.x - half, 0.0, half, band), contact_col, true)
if cc == 2 or cc == 3:
# S 侧横带:SW 自 x=0、SE 自 x=edge-half
canvas.draw_rect(Rect2(pt.x if cc == 2 else pt.x - half, rect.size.x - band, half, band), contact_col, true)
if cc == 0 or cc == 2:
# W 侧竖带:NW 自 y=0、SW 自 y=edge-half
canvas.draw_rect(Rect2(0.0, pt.y if cc == 0 else pt.y - half, band, half), contact_col, true)
if cc == 1 or cc == 3:
# E 侧竖带:NE 自 y=0、SE 自 y=edge-half
canvas.draw_rect(Rect2(rect.size.x - band, pt.y if cc == 1 else pt.y - half, band, half), contact_col, true)
## 顶面倒角域(光源左上——上/左内沿受光 2px、下/右内沿背光 2px;内缩 2px 让
## 呼吸微光/主角蓝边框保持最外沿可见;强度随档位)
func _draw_bevel(canvas: CanvasItem, rect: Rect2) -> void:
if bevel <= 0.0:
return
var base: Color = bg_sb.bg_color
var lit: Color = base.lightened(0.22 * bevel)
var dark: Color = base.darkened(0.22 * bevel)
var t := 2.0
canvas.draw_rect(Rect2(t, t, rect.size.x - t * 2.0, t), lit, true)
canvas.draw_rect(Rect2(t, t, t, rect.size.x - t * 2.0), lit, true)
canvas.draw_rect(Rect2(t, rect.size.x - t * 2.0, rect.size.x - t * 2.0, t), dark, true)
canvas.draw_rect(Rect2(rect.size.x - t * 2.0, t, t, rect.size.x - t * 2.0), dark, true)
# 南立面(D 档件一——墙格南缘内侧,自底边向上:双段暗带在下、口沿高光线贴其上沿):
# 坐标推导(自格底边起算):远段=[edge−far, edge],近段=[edge−far−near, edge−far],
# 高光线=[edge−far−near−hl, edge−far−near]——与研究件「墙顶面→高光→暗段→更暗段→格界」下行序一致
func _draw_south_facade(canvas: CanvasItem, rect: Rect2) -> void:
if not facade_south:
return
var far_h := rect.size.x * FACADE_FAR_FRAC
var near_h := rect.size.x * FACADE_NEAR_FRAC
var y_far := rect.size.x - far_h
var y_near := y_far - near_h
var y_hl := y_near - FACADE_HIGHLIGHT_PX
canvas.draw_rect(Rect2(0.0, y_hl, rect.size.x, FACADE_HIGHLIGHT_PX), UiKit.STAND_STONE_RIM, true) # 口沿高光(UiKit.STAND_STONE_RIM 并档——同值单点)
canvas.draw_rect(Rect2(0.0, y_near, rect.size.x, near_h), FACADE_NEAR_COL, true)
canvas.draw_rect(Rect2(0.0, y_far, rect.size.x, far_h), FACADE_FAR_COL, true)
# 地面投影(D 档件二——北邻墙格在本已开格北缘投渐隐暗带,自北缘向南三段衰减):
# 坐标推导:三等分 proj_band,α 0.28/0.16/0.06(研究件实测贴墙 α≈0.28、带末渐融入地面)
func _draw_north_projection(canvas: CanvasItem, rect: Rect2) -> void:
if proj_band <= 0.0:
return
var seg := proj_band / 3.0
canvas.draw_rect(Rect2(0.0, 0.0, rect.size.x, seg), PROJECTION_NEAR_COL, true)
canvas.draw_rect(Rect2(0.0, seg, rect.size.x, seg), PROJECTION_MID_COL, true)
canvas.draw_rect(Rect2(0.0, seg * 2.0, rect.size.x, seg), PROJECTION_EDGE_COL, true)
# E 格高墙面(圣经 v3.5 冻结①——方向稿 draw_facade 移植):本格已开且南邻为墙 →
# 南部 30% 承接墙面。材质 = 墙顶 center 瓦片顶部 30% 区带**原亮度**直贴(零暗档零阴影);
# 墙脚阴线只画地面/墙面交界(strip 顶缘 1px);墙面底缘(格界处)不画任何线——直接接南侧墙顶
# (白边导致两截)。仅瓦片态服役(tex_facade 缺位 = 平色回退原语义不动)。
func _draw_e_facade(canvas: CanvasItem, rect: Rect2) -> void:
if not facade_strip or tex_facade == null:
return
var strip_h := rect.size.x * E_FACADE_FRAC
var y0 := rect.size.x - strip_h
var ts := tex_facade.get_size()
canvas.draw_texture_rect_region(tex_facade, Rect2(0.0, y0, rect.size.x, strip_h),
Rect2(0.0, 0.0, ts.x, ts.y * E_FACADE_FRAC))
canvas.draw_rect(Rect2(0.0, y0, rect.size.x, 1.0), UiKit.E_FACADE_FOOT_COL, true)
@@ -1 +0,0 @@
uid://c2msg115y3oir
@@ -1,145 +0,0 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 格面标识与顶层叠加绘制域件(CellUnit 拆件——单一变化原因=标识与叠加层视觉规格):
## 变异标记/数字色牌与字形/奖励徽章/封印/路径点迹/焦点高亮/中立移除退色的全部绘制入参与绘制命令。
## 无 class_name(类名缓存面纪律)——经宿主 board_cell_unit preload 引用;宿主 _draw 按层序调
## draw_marks/draw_glyph_layer/draw_overlay_under/draw_removed_dim(雾层由宿主夹在 overlay_under 与 removed_dim 之间);
## setter 经 _owner 回引触发宿主重绘语义逐字(badge_glyph 的 glow mask 回引 _owner._glow_layer 原样)。
extends RefCounted
var _owner: Node2D = null
var badge_glyph := "": # 奖励徽章文字(右下角金——封印之下、变异纹之上、不进数字区)
set(v):
badge_glyph = v
if _owner._glow_layer != null and is_instance_valid(_owner._glow_layer):
_owner._glow_layer.set_badge_mask(v != "")
_owner.queue_redraw()
var dot_visible := false: # BFS 路径预览点迹(中心 6×6 小人蓝)
set(v):
dot_visible = v
_owner.queue_redraw()
var hl_visible := false: # 焦点高亮(悬停态边框覆盖层)
set(v):
hl_visible = v
_owner.queue_redraw()
var hl_color := UiKit.GOLD: # 高亮色(金目标/封印朱/禁入灰禁红)
set(v):
hl_color = v
_owner.queue_redraw()
var font: Font = null # 字体槽位(null = ThemeDB.fallback_font——BoardView.set_world_font 注入)
# ── 素材槽位(现全 null = 程序化占位服役;art-bible §8.2 格面 32×32 暂定档)──
var tex_mutation: Texture2D = null # ② 变异纹带
var tex_glyph: Texture2D = null # ③ 数字
var tex_badge: Texture2D = null # ③' 徽章
var tex_seal: Texture2D = null: # ④ 封印(槽位可运行时换装〔封印〕——同族 facade/treasure_mark
set(v): # 均 setter 触发重绘(普通 var 赋值不重绘=封印插上不显示))
if tex_seal != v:
tex_seal = v
_owner.queue_redraw()
# 变异角标(程序化,tex_mutation 纹带槽暂留):
# "flat_damage" = 未开格伤害型变异,左下角危险红三角(开格有代价的策略纹理标记)
# 叠加层标记(秘宝整图叠加层的格面呈现;派生信息层零回写):
# "mine_reveal" = 真视格(危险红内缩框——「这格是雷」的确定决策标记,封印照常计费);
# "reward_preview" 格不复用本字段——预读徽章走 badge_glyph 注入(board_view refresh 注入 REWARD_BADGE 字形)
var treasure_mark := "":
set(v):
if treasure_mark != v:
treasure_mark = v
_owner.queue_redraw()
var mutation_mark := "":
set(v):
if mutation_mark != v:
mutation_mark = v
_owner.queue_redraw()
var removed_dim := false: # 中立移除退色层(BoardView paint 按排雷玩法 is_removed 写入)
set(v):
removed_dim = v
_owner.queue_redraw()
func _init(owner: Node2D) -> void:
_owner = owner
## 变异标记域:真视格=危险红内缩框/未开格伤害型变异=左下角危险红三角
func draw_marks(canvas: CanvasItem, rect: Rect2) -> void:
if treasure_mark == "mine_reveal":
var inset := rect.size.x * 0.14
canvas.draw_rect(Rect2(inset, inset, rect.size.x - inset * 2.0, rect.size.x - inset * 2.0),
Color(UiKit.RED_MINE, 0.9), false, 2.0)
if mutation_mark == "flat_damage":
var mw := rect.size.x * 0.28
var mpts := PackedVector2Array([Vector2(0, rect.size.x - mw), Vector2(0, rect.size.x), Vector2(mw, rect.size.x)])
canvas.draw_colored_polygon(mpts, Color(UiKit.RED_MINE, 0.85))
## ②③④ 中层域:变异纹带与标记/数字色牌与字形/徽章/封印
##(glyph/glyph_color/plate 三者住宿主冻结面——探针直读成员,作参数传入)
func draw_glyph_layer(canvas: CanvasItem, rect: Rect2, glyph: String, glyph_color: Color, plate: bool) -> void:
# ② 变异纹带(无程序化纹带——槽位先行,贴图落位即用)
if tex_mutation != null:
canvas.draw_texture_rect(tex_mutation, rect, false)
# ③ 数字/徽章(数字 ≥ 格高 50% 墨族对格底 ≥4.5:1;徽章右下角金,遮蔽态 = "?")
if glyph != "":
if plate:
_draw_digit_plate(canvas, rect)
if tex_glyph != null:
canvas.draw_texture_rect(tex_glyph, rect, false)
else:
draw_centered_text(canvas, rect, glyph, glyph_color, maxi(18, int(ceil(rect.size.x * 0.5)))) # 字号下限 18(§7.4 冻结公式)
if badge_glyph != "":
if tex_badge != null:
canvas.draw_texture_rect(tex_badge, rect, false)
else:
_draw_badge(canvas, rect, badge_glyph)
# ④ 封印(现行视觉封印文字在 ③ 文字位承载——独立槽位留给封印图)
if tex_seal != null:
canvas.draw_texture_rect(tex_seal, rect, false)
## 数字色牌后备域(圣经 v3.5 冻结④:数字亮墨直画已开格底须垫牌;
## 牌=后备件——雷/「?」维持牌载保读性(牌上深墨族 12:1);
## 牌尺寸 0.60/0.62 格边居中;封印=未开格不垫牌)
func _draw_digit_plate(canvas: CanvasItem, rect: Rect2) -> void:
var pw := rect.size.x * 0.60
var ph := rect.size.x * 0.62
var pr := Rect2((rect.size.x - pw) * 0.5, (rect.size.x - ph) * 0.5, pw, ph)
canvas.draw_rect(pr, UiKit.DIGIT_PLATE, true)
canvas.draw_rect(pr, UiKit.DIGIT_PLATE_EDGE, false, 1.0)
## ⑤ 路径点迹 + 焦点高亮(悬停态覆盖层,不参与命中)——宿主时序器在其后插雾层
func draw_overlay_under(canvas: CanvasItem, rect: Rect2) -> void:
if dot_visible:
var ds := 6.0
canvas.draw_rect(Rect2(Vector2((rect.size.x - ds) * 0.5, (rect.size.x - ds) * 0.5), Vector2(ds, ds)), UiKit.HERO_BLUE, true)
if hl_visible:
# 悬停高亮减淡(金框太明显——绘制时 55% alpha,hl_color 属性与探针断言面不动)
canvas.draw_rect(Rect2(1, 1, rect.size.x - 2.0, rect.size.x - 2.0), Color(hl_color, 0.55), false, 2.0) # 2px 内描边
## ⑦ 中立移除退色层(退色降档:暗幕压顶全层覆盖,暗淡记忆态同族呈现语义;
## 顶层于迷雾之上——移除格不再参与迷雾语义。本实现移除集=未揭示危险格(无数字无封印印))
func draw_removed_dim(canvas: CanvasItem, rect: Rect2) -> void:
if removed_dim:
canvas.draw_rect(rect, Color(0.12, 0.10, 0.09, 0.62), true)
## 文字位绘制单点(宿主迷雾记忆态复用——同公式同字号;水平居中 + 垂直居中,基线 = (格高 − 字高)/2 + 上伸部)
func draw_centered_text(canvas: CanvasItem, rect: Rect2, text: String, col: Color, fs: int) -> void:
var f := font if font != null else ThemeDB.fallback_font
if f == null:
return
var asc := f.get_ascent(fs)
var desc := f.get_descent(fs)
canvas.draw_string(f, Vector2(0.0, (rect.size.x - (asc + desc)) * 0.5 + asc), text,
HORIZONTAL_ALIGNMENT_CENTER, rect.size.x, fs, col)
# 徽章绘制:右下角金(文字贴角内缩 2–3px)
func _draw_badge(canvas: CanvasItem, rect: Rect2, g: String) -> void:
var f := font if font != null else ThemeDB.fallback_font
if f == null:
return
var fs := maxi(10, int(rect.size.x * 0.28))
var sz := f.get_string_size(g, HORIZONTAL_ALIGNMENT_LEFT, -1, fs)
canvas.draw_string(f, Vector2(rect.size.x - sz.x - 2.0, rect.size.x - 3.0), g, HORIZONTAL_ALIGNMENT_LEFT, -1, fs, UiKit.GOLD)
@@ -1 +0,0 @@
uid://c60e3su2qd1yq
+2 -2
View File
@@ -85,7 +85,7 @@ func _xnum(v: float) -> String:
return "×%d" % int(round(v)) if absf(v - roundf(v)) < 0.005 else "×%.2f" % v
# 打开商店(products = runner.shop.shop_products() 投影——[{product_id, display_name, attr, flat_value, price, purchased}])
# 打开商店(products = runner.shop_products() 投影——[{product_id, display_name, attr, flat_value, price, purchased}])
func open_shop(products: Array, gp: int) -> void:
for child in _cards_box.get_children():
child.queue_free()
@@ -149,7 +149,7 @@ func _emit_buy_requested(p: Dictionary) -> void:
# ---- 共享池货架面(M-4 迭代 11 第②批 T4——P3 甲程序占位,三选一同构卡;UiKit 既有件)----
# 打开/重刷货架(cards = runner.shop.shop_shelf() 投影——[{kind_id, display_name, value, price, stock_left}])。
# 打开/重刷货架(cards = runner.shop_shelf() 投影——[{kind_id, display_name, value, price, stock_left}])。
# 买后组合根重投喂新货架 = 整架重建呈现(三件全重抽反馈——未买不保留);空架 = 池空硬闸合法态。
func open_shelf(cards: Array, gp: int) -> void:
for child in _cards_box.get_children():
+161 -46
View File
@@ -2,9 +2,9 @@
## 站桩本体:B 区主角/敌人无框立绘+底座+血条+顺位/意图徽标的世界精灵(Node2D)。
## 站桩形态为定案——禁回卡片式;立绘素材缺位回退字形占位;击杀呈现禁瞬消(灰化塌陷)。
## 无 class_name——经 preload 引用;壳层坐标换算单点在 BattleStageView.world_to_shell()。
## 内部状态两件(u4 拆件——st=身份命值态 standee_vitals、mo=资产演出态 standee_motion,_init 自建);
## 外部直访冻结成员(box/font/slot_row/has_world_hp_bar)与源码锚承载成员(_intent_charge——theme
## 源锚句所在行逐字保留)与三个演出探针计数器住宿主。
## 内部状态件(u4 子域拆件——契约 9 职责语句「站桩演出状态」):单一变化原因=站桩呈现与演出,
## 成员共同表达站桩的行进态/演出计数/资产挂点。外部直访冻结成员(box/font/slot_row/has_world_hp_bar)
## 与源码锚承载成员(_intent_charge——theme 源锚句所在行逐字保留)住宿主。
extends Node2D
const CHARGE_SKILL_IDS := ["heavy"]
@@ -16,28 +16,65 @@ const ARMOR_ICONS := {"light": "◇", "ward": "◆", "heavy": "▣"}
const DEATH_SHARD_N := 5 # 碎块数(§8.4 冻结值)
const HIT_SPARK_N := 6 # 命中火花数(打击瞬间可读性一拍)
const _VITALS := preload("res://presentation/views/standee_vitals.gd")
const _MOTION := preload("res://presentation/views/standee_motion.gd")
var st: _VITALS # 身份与命值态件实例(名字沿用——外部路径串 "st.hp_value" 与约 40 处引用零改)
var mo: _MOTION # 资产与演出态件实例(_init 自建)
## 站桩演出状态(全部内部态单点——含 setter 触发宿主重绘语义,_owner 回引保持逐字)
class _StandeeState extends RefCounted:
var _owner: Node2D = null
var _kind := "enemy"
var _portrait_sprite: Sprite2D = null # 资产挂点(null = 未落位,字形在位)
var _portrait_tex: Texture2D = null # 落位纹理引用(set_portrait_texture 装载;字形回退判据)
# 主角三态切换:state 名 → 纹理(battle_stage_view 组合点注册);缺态=保持现纹理
# (缺位回退链延伸——三态素材缺任一不炸不空)。敌站桩不注册不消费。
var _hero_tex := {}
var _hero_state: int = 0
var _state_revert_tw: Tween
var _order := 1
var _intent_glyph := ""
var _intent_col := Color.WHITE
var _intent_visible := false
var _intent_scale := 1.0: # 意图弹入动画参数
set(v):
_intent_scale = v
_owner.queue_redraw()
var _glyph_rot := 0.0: # 立绘前倾角(贴图态无此动画)
set(v):
_glyph_rot = v
_owner.queue_redraw()
var _display_name := ""
var _enemy_id := ""
var _armor := "light"
var _dead := false
var _hp_max := 1.0
var _hp_cur := 0 # 血量数字显示面(HUD框架 验收「血量数字可读」)
var hp_value := 1.0: # 血条值 0..1(直角递减——tween_property 目标,逐行刷新重启不累积)
set(v):
hp_value = clampf(v, 0.0, 1.0)
_owner.queue_redraw()
var _hp_tween: Tween
var _hp_flash_tw: Tween
var _death_shards := 0 # 探针出口计数(碎块节点回收后仍保留读数)
var _hit_sparks := 0 # 探针出口计数(命中火花累计)
var _flash_count := 0 # 探针出口计数(受击闪白触发次数——主角路由断言面)
var st := _StandeeState.new()
var box := Vector2(150, 176) # 站桩盒尺寸(实际高随壳层 row 布局注入;外部直访冻结)
var font: Font = null # 世界字体槽位(null = ThemeDB.fallback_font——world.world_font 注入;外部直访冻结)
var slot_row: StatusSlotRow # 状态效果唯一展示面 = 壳层伴随件(挂 BattleStageView 壳层,本体进世界;外部直访冻结)
var _intent_charge := false # 蓄力档(pid ∈ CHARGE_SKILL_IDS——意图三档 ③ 分支判据;源码锚承载成员住宿主)
# 血条存在性探针出口(世界直角血条常在;外部直访冻结)
var has_world_hp_bar := true
# 演出探针计数器(写入方=宿主演出函数、读取方=宿主 fx_probe 出口——仪器跟着驱动它的函数走)
var _death_shards := 0 # 探针出口计数(碎块节点回收后仍保留读数)
var _hit_sparks := 0 # 探针出口计数(命中火花累计)
var _flash_count := 0 # 探针出口计数(受击闪白触发次数——主角路由断言面)
func _init(kind: String = "enemy") -> void:
st = _VITALS.new(self)
mo = _MOTION.new(self)
st._owner = self
st._hero_state = HeroState.IDLE
st._kind = kind
mo._hero_state = _MOTION.HeroState.IDLE
enum HeroState { IDLE, RESET, ATTACK, HIT } # 主角三态+复位哨兵(外部 String 契约=register/set 形参零变化)
const _HS_NAME := {HeroState.IDLE: "idle", HeroState.RESET: "reset", HeroState.ATTACK: "attack", HeroState.HIT: "hit"}
const _HS_VALUE := {"idle": HeroState.IDLE, "reset": HeroState.RESET, "attack": HeroState.ATTACK, "hit": HeroState.HIT}
func _efont() -> Font:
@@ -55,7 +92,7 @@ func enemy_id() -> String:
# 意图徽标探针出口
func intent_probe() -> Dictionary:
return {"visible": mo._intent_visible, "text": mo._intent_glyph}
return {"visible": st._intent_visible, "text": st._intent_glyph}
# 名字行探针出口(ui_probe 呈现口径 + 本批 V6/V8 断言面:armor_icon = 绑定纹理路径〔缺位回退字形〕、
@@ -81,7 +118,7 @@ func set_data(d: Dictionary) -> void:
st._enemy_id = String(d.get("enemy_id", ""))
st._armor = String(d.get("armor_type", "light"))
st._hp_max = float(maxi(1, int(d.get("max_hp", 1))))
st.apply_hp(int(d.get("hp", 0)))
_set_hp(int(d.get("hp", 0)))
_set_order(int(d.get("order", 1)))
set_pending(String(d.get("pending_type", "")), String(d.get("pending_skill", "")))
set_effects(d.get("effects", []))
@@ -92,11 +129,21 @@ func set_data(d: Dictionary) -> void:
func set_hero(hp: int, max_hp: int) -> void:
st._hp_max = float(maxi(1, max_hp))
st.apply_hp(hp)
_set_hp(hp)
if st._dead and hp > 0:
_revive() # 新战斗开场 HP 回正——主角死亡残位随之复活(敌侧 set_data 同款语义)
# 血条短 tween 递减(直角递减占位);逐行刷新时重启 tween 不累积
func _set_hp(hp: int) -> void:
st._hp_cur = clampi(hp, 0, int(st._hp_max))
var target := float(st._hp_cur) / st._hp_max
if st._hp_tween != null and st._hp_tween.is_valid():
st._hp_tween.kill()
st._hp_tween = create_tween()
st._hp_tween.tween_property(st, "hp_value", target, UiKit.motion(0.15))
# 主角/敌方共用——状态效果唯一展示面(壳层伴随件转发;空态 = 图标行隐藏不占位)
func set_effects(effects: Array) -> void:
if slot_row == null:
@@ -135,12 +182,12 @@ func _set_order(i: int) -> void:
func set_pending(ptype: String, pid: String) -> void:
if st._dead:
_intent_charge = false
mo._intent_visible = false
st._intent_visible = false
queue_redraw()
return
if ptype == "" or ptype == "basic":
_intent_charge = false
mo._intent_visible = false # 普通攻击零表示(完全不可预测——§2.4)
st._intent_visible = false # 普通攻击零表示(完全不可预测——§2.4)
queue_redraw()
return
var glyph := "⚔" # 攻击系(damage)
@@ -154,15 +201,60 @@ func set_pending(ptype: String, pid: String) -> void:
_intent_charge = pid in CHARGE_SKILL_IDS
if _intent_charge:
col = UiKit.GOLD_INK # 重击类蓄力档(深金墨——浅垫牌上 ≥4.5:1)
mo._intent_glyph = glyph
mo._intent_col = col
mo._intent_visible = true
mo.play_intent_motion(_intent_charge)
st._intent_glyph = glyph
st._intent_col = col
st._intent_visible = true
_play_intent_motion()
# 资产挂点转调壳(签名外形零变——box 由宿主传入件内)
# 意图三档动作动画(§4.6.4 三档:①普通攻击=零表示〔set_pending 早退〕
# ②特殊准备=徽标弹入+二次弹跳(高举语义,运动手段 §2)③重击/硬化=缓推蓄力+定格微顿+回落;
# 三档运动签名互异,灰度/剪影层可辨)。摇定时一次性短动画,随后静帧保持。
func _play_intent_motion() -> void:
var tw := create_tween()
tw.tween_property(st, "_intent_scale", 1.6, UiKit.motion(0.10))
tw.tween_property(st, "_intent_scale", 1.0, UiKit.motion(0.16))
tw.parallel().tween_property(st, "_glyph_rot", 4.0, UiKit.motion(0.10))
tw.tween_property(st, "_glyph_rot", 0.0, UiKit.motion(0.14))
if _intent_charge:
tw.tween_property(st, "_intent_scale", 1.22, UiKit.motion(0.28)).set_trans(Tween.TRANS_SINE) # ③蓄力缓推
tw.tween_interval(UiKit.motion(0.40)) # 定格微顿
tw.tween_property(st, "_intent_scale", 1.0, UiKit.motion(0.18))
else:
tw.tween_property(st, "_intent_scale", 1.26, UiKit.motion(0.11)) # ②二次弹跳(高举)
tw.tween_property(st, "_intent_scale", 1.0, UiKit.motion(0.13))
# 资产挂点:tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。
# flip = 立绘朝向翻转(素材基准朝左时传 true 呈现朝右)。盒高 96px = 48px 立绘源整数 2×
# (圣经 §8.2 禁非整数缩放)——源换代时随源重算。
func set_portrait_texture(tex: Texture2D, flip: bool = false) -> void:
mo.set_portrait_texture(tex, flip, box)
if tex == null:
if st._portrait_sprite != null:
st._portrait_sprite.visible = false
queue_redraw()
return
st._portrait_tex = tex
if st._portrait_sprite == null:
st._portrait_sprite = Sprite2D.new()
st._portrait_sprite.centered = false
st._portrait_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST # 像素帧放大防糊
add_child(st._portrait_sprite)
st._portrait_sprite.texture = tex
st._portrait_sprite.flip_h = flip
st._portrait_sprite.visible = true
_place_portrait()
func _place_portrait() -> void:
# 立绘盒 = (0,44)-(box.x, box.y-36);48 源 ×2 整数档居中
if st._portrait_sprite == null or st._portrait_tex == null:
return
var box_h := box.y - 44.0 - 36.0
var w := st._portrait_tex.get_width() * 2.0
var h := st._portrait_tex.get_height() * 2.0
st._portrait_sprite.scale = Vector2(2.0, 2.0) # 整数 2×(C5)
st._portrait_sprite.position = Vector2((box.x - w) * 0.5, 44.0 + (box_h - h) * 0.5)
func mark_dead() -> void:
@@ -176,8 +268,8 @@ func mark_dead() -> void:
tw.tween_property(self, "rotation_degrees", 0.0, UiKit.motion(0.08))
_spawn_death_shards() # 碎裂退场(§4.6.4 语义硬规则:灰化+碎裂禁瞬间消失;§8.4 v2 冻结 5 块 0.45s)
_intent_charge = false
mo._intent_visible = false # 死亡残位意图退位
st.apply_hp(0)
st._intent_visible = false # 死亡残位意图退位
_set_hp(0)
if slot_row != null:
slot_row.visible = false # 状态行随 mark_dead 同拍隐藏
@@ -187,7 +279,7 @@ func mark_dead() -> void:
# 死亡碎块弹飞:立绘盒中心五块碎石抛物线散落(程序化色块——确定性散布,禁全局随机)
func _spawn_death_shards() -> void:
_death_shards = DEATH_SHARD_N
st._death_shards = DEATH_SHARD_N
var center := _portrait_rect().get_center()
for i in DEATH_SHARD_N:
_spawn_death_shard(i, center)
@@ -213,7 +305,7 @@ func _spawn_death_shard(i: int, center: Vector2) -> void:
# 演出探针出口(fx_probe——死亡碎裂/命中火花/闪白计数断言面)
func fx_probe() -> Dictionary:
return {"death_shards": _death_shards, "hit_sparks": _hit_sparks, "flash_count": _flash_count}
return {"death_shards": st._death_shards, "hit_sparks": st._hit_sparks, "flash_count": st._flash_count}
# 命中瞬间打击特效:立绘盒中心六枚短火花放射(程序化色块,确定性方向
@@ -227,7 +319,7 @@ func play_hit_impact() -> void:
## 单火花域(循环体提函数——放射弹道+渐隐+回收)
func _spawn_hit_spark(i: int, center: Vector2) -> void:
_hit_sparks += 1
st._hit_sparks += 1
var spark := ColorRect.new()
spark.color = UiKit.GOLD_LIT if i % 2 == 0 else UiKit.WARM
spark.size = Vector2(4, 3)
@@ -248,7 +340,7 @@ func is_dead() -> bool:
# 受击闪白(结算演出占位——§2.2 受击反馈;强度时长维持现行)
func flash_hit() -> void:
_flash_count += 1
st._flash_count += 1
if st._hp_flash_tw != null and st._hp_flash_tw.is_valid():
st._hp_flash_tw.kill()
st._hp_flash_tw = create_tween()
@@ -261,7 +353,8 @@ func _revive() -> void:
modulate = Color(1, 1, 1)
rotation_degrees = 0.0
if st._kind == "hero":
mo.reset_state()
st._hero_state = HeroState.RESET # 强制回 idle 态(绕过同态早退)
set_hero_state("idle")
if slot_row != null:
slot_row.visible = true
queue_redraw()
@@ -279,24 +372,45 @@ func _portrait_glyph() -> String:
# 立绘落位探针出口(站桩纹理非空断言面;缺位回退链保持的对照面)
func portrait_probe() -> Dictionary:
return {"has_tex": mo._portrait_sprite != null and mo._portrait_sprite.texture != null}
return {"has_tex": st._portrait_sprite != null and st._portrait_sprite.texture != null}
# ---- 主角三态切换(攻击演出→attack/受击→hit/结算回 idle;§8.4 帧数档挂点) ----
# 组合点注册转调壳(签名外形零变)
# 组合点注册(battle_stage_view _init——UiKit 常量 load_tex,缺位 null 不注册=该态保持现纹理)
func register_hero_state(state: String, tex: Texture2D) -> void:
mo.register_hero_state(state, tex)
if tex != null:
st._hero_tex[state] = tex
# 状态切换转调壳(签名外形零变——is_hero 由宿主 vitals 参判传入,早退序逐字在件内)
# 状态切换:现纹理换注册态(attack/hit);未注册态名或缺位 = 保持现纹理不炸。
# 非 idle 态自动短驻回归 idle(0.35s——演出一拍即回,结算收尾另有显式 idle 兜底)。
func set_hero_state(state: String) -> void:
mo.apply_hero_state(state, st._kind == "hero")
var target: int = _HS_VALUE.get(state, -1)
if st._kind != "hero" or target == st._hero_state:
return
if not st._hero_tex.has(state) and state != "idle":
return
st._hero_state = target
if st._portrait_sprite != null and st._hero_tex.has(state):
st._portrait_sprite.texture = st._hero_tex[state]
if st._state_revert_tw != null and st._state_revert_tw.is_valid():
st._state_revert_tw.kill()
if state != "idle":
st._state_revert_tw = create_tween()
st._state_revert_tw.tween_interval(UiKit.motion(0.35))
st._state_revert_tw.tween_callback(_settle_hero_idle)
# 三态探针出口(fx_probe——当前态名+注册态集合;读件投影外形零变)
func _settle_hero_idle() -> void:
st._hero_state = HeroState.IDLE
if st._portrait_sprite != null and st._hero_tex.has("idle"):
st._portrait_sprite.texture = st._hero_tex["idle"]
# 三态探针出口(fx_probe——当前态名+注册态集合)
func hero_state_probe() -> Dictionary:
return mo.state_probe()
return {"state": _HS_NAME.get(st._hero_state, "idle"), "registered": st._hero_tex.keys()}
func _portrait_color() -> Color:
@@ -421,7 +535,7 @@ func _draw_hp_bar(f: Font) -> void:
## 立绘域:贴图在位 = Sprite2D(set_portrait_texture 落位);缺位 = 字形回退
func _draw_portrait(f: Font) -> void:
if mo._portrait_sprite != null and mo._portrait_sprite.texture != null and mo._portrait_sprite.visible:
if st._portrait_sprite != null and st._portrait_sprite.texture != null and st._portrait_sprite.visible:
return
var prect := _portrait_rect()
var glyph := _portrait_glyph()
@@ -429,7 +543,7 @@ func _draw_portrait(f: Font) -> void:
# 坐标系注记:draw_set_transform 原点已置于 center——draw_string 的
# pos 是**变换后的局部坐标**,须传相对 center 的偏移(-w/2 水平居中、+0.35h 基线下移);
# 传绝对 center 会成双重偏移、把字形画到盒外。
draw_set_transform(center, deg_to_rad(mo._glyph_rot), Vector2.ONE)
draw_set_transform(center, deg_to_rad(st._glyph_rot), Vector2.ONE)
var ts := f.get_string_size(glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 42)
f.draw_string(get_canvas_item(), Vector2(-ts.x * 0.5, ts.y * 0.35), glyph,
HORIZONTAL_ALIGNMENT_LEFT, -1, 42, _portrait_color())
@@ -438,17 +552,18 @@ func _draw_portrait(f: Font) -> void:
## 意图徽标域(敌 only——立绘上半部居中悬浮,CENTER_TOP 74..98 等价):弹入 scale 动画
func _draw_intent_badge(f: Font) -> void:
if not mo._intent_visible:
if not st._intent_visible:
return
var icenter := Vector2(box.x * 0.5, 86.0)
draw_set_transform(icenter, 0.0, Vector2(mo._intent_scale, mo._intent_scale))
var its := f.get_string_size(mo._intent_glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 15)
draw_set_transform(icenter, 0.0, Vector2(st._intent_scale, st._intent_scale))
var its := f.get_string_size(st._intent_glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 15)
# 对比度垫牌(四色墨直压立绘纹理/暗舞台底 1.5-3.3:1 违 §7.2
# 「文字禁直压纹理底」;垫 PANEL_DATA 素牌后 atk 5.3/buf 4.8/debuff 4.7/charge 4.9 全 ≥4.5:1;
# atk/debuff 零换墨、buf 换 HERO_BLUE_DEEP、charge 换 GOLD_INK;随弹入 transform 同步缩放)
draw_rect(Rect2(-its.x * 0.5 - 4.0, -its.y * 0.75, its.x + 8.0, its.y * 1.1), UiKit.PANEL_DATA, true)
f.draw_string(get_canvas_item(), Vector2(-its.x * 0.5, its.y * 0.35), mo._intent_glyph,
HORIZONTAL_ALIGNMENT_LEFT, -1, 15, mo._intent_col) # 局部坐标(同上注记——双重偏移修复)
f.draw_string(get_canvas_item(), Vector2(-its.x * 0.5, its.y * 0.35), st._intent_glyph,
HORIZONTAL_ALIGNMENT_LEFT, -1, 15, st._intent_col) # 局部坐标(同上注记——双重偏移修复)
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
## 名字行域(敌 only——AC 数值不展示,类型图标化):整行垫 HP_BAR_BACK 素底牌(§6.1/§7.2
-128
View File
@@ -1,128 +0,0 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 站桩资产与演出态件(StandeeBody 拆件——单一变化原因=美术素材落位规则与演出编排设计):
## 站桩的资产挂点与一次性演出——立绘精灵落位与换装、主角三态注册与 0.35s 短驻回归、意图徽标弹入动画。
## 无 class_name(类名缓存面纪律)——经宿主 standee_body preload 引用(mo 实例);
## 两个属性 setter 经 _owner 回引触发宿主重绘语义逐字;tween 经 _owner 创建(同一 Node 生命绑定)。
extends RefCounted
enum HeroState { IDLE, RESET, ATTACK, HIT } # 主角三态+复位哨兵(外部 String 契约=register/set 形参零变化)
const _HS_NAME := {HeroState.IDLE: "idle", HeroState.RESET: "reset", HeroState.ATTACK: "attack", HeroState.HIT: "hit"}
const _HS_VALUE := {"idle": HeroState.IDLE, "reset": HeroState.RESET, "attack": HeroState.ATTACK, "hit": HeroState.HIT}
var _owner: Node2D = null
var _portrait_sprite: Sprite2D = null # 资产挂点(null = 未落位,字形在位)
var _portrait_tex: Texture2D = null # 落位纹理引用(set_portrait_texture 装载;字形回退判据)
# 主角三态切换:state 名 → 纹理(battle_stage_view 组合点注册);缺态=保持现纹理
# (缺位回退链延伸——三态素材缺任一不炸不空)。敌站桩不注册不消费。
var _hero_tex := {}
var _hero_state: int = 0
var _state_revert_tw: Tween
var _intent_glyph := ""
var _intent_col := Color.WHITE
var _intent_visible := false
var _intent_scale := 1.0: # 意图弹入动画参数
set(v):
_intent_scale = v
_owner.queue_redraw()
var _glyph_rot := 0.0: # 立绘前倾角(贴图态无此动画)
set(v):
_glyph_rot = v
_owner.queue_redraw()
func _init(owner: Node2D) -> void:
_owner = owner
# 资产挂点:tex 落位后字形隐退、纹理立绘顶位——零代码换装契约;tex=null 回退字形。
# flip = 立绘朝向翻转(素材基准朝左时传 true 呈现朝右)。盒高 96px = 48px 立绘源整数 2×
# (圣经 §8.2 禁非整数缩放)——源换代时随源重算。(box = 宿主冻结 var,由宿主传入)
func set_portrait_texture(tex: Texture2D, flip: bool, box: Vector2) -> void:
if tex == null:
if _portrait_sprite != null:
_portrait_sprite.visible = false
_owner.queue_redraw()
return
_portrait_tex = tex
if _portrait_sprite == null:
_portrait_sprite = Sprite2D.new()
_portrait_sprite.centered = false
_portrait_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST # 像素帧放大防糊
_owner.add_child(_portrait_sprite)
_portrait_sprite.texture = tex
_portrait_sprite.flip_h = flip
_portrait_sprite.visible = true
_place_portrait(box)
func _place_portrait(box: Vector2) -> void:
# 立绘盒 = (0,44)-(box.x, box.y-36);48 源 ×2 整数档居中
if _portrait_sprite == null or _portrait_tex == null:
return
var box_h := box.y - 44.0 - 36.0
var w := _portrait_tex.get_width() * 2.0
var h := _portrait_tex.get_height() * 2.0
_portrait_sprite.scale = Vector2(2.0, 2.0) # 整数 2×(C5)
_portrait_sprite.position = Vector2((box.x - w) * 0.5, 44.0 + (box_h - h) * 0.5)
# 组合点注册(battle_stage_view _init——UiKit 常量 load_tex,缺位 null 不注册=该态保持现纹理)
func register_hero_state(state: String, tex: Texture2D) -> void:
if tex != null:
_hero_tex[state] = tex
# 状态切换:现纹理换注册态(attack/hit);未注册态名或缺位 = 保持现纹理不炸。
# 非 idle 态自动短驻回归 idle(0.35s——演出一拍即回,结算收尾另有显式 idle 兜底)。
#(is_hero = 宿主 vitals._kind == "hero" 参判传入——三连早退条件序逐字)
func apply_hero_state(state: String, is_hero: bool) -> void:
var target: int = _HS_VALUE.get(state, -1)
if not is_hero or target == _hero_state:
return
if not _hero_tex.has(state) and state != "idle":
return
_hero_state = target
if _portrait_sprite != null and _hero_tex.has(state):
_portrait_sprite.texture = _hero_tex[state]
if _state_revert_tw != null and _state_revert_tw.is_valid():
_state_revert_tw.kill()
if state != "idle":
_state_revert_tw = _owner.create_tween()
_state_revert_tw.tween_interval(UiKit.motion(0.35))
_state_revert_tw.tween_callback(_settle_hero_idle)
func _settle_hero_idle() -> void:
_hero_state = HeroState.IDLE
if _portrait_sprite != null and _hero_tex.has("idle"):
_portrait_sprite.texture = _hero_tex["idle"]
# 复位强制绕同态早退两拍封装(原 _revive 内 hero 分支——先置 RESET 哨兵再回 idle)
func reset_state() -> void:
_hero_state = HeroState.RESET # 强制回 idle 态(绕过同态早退)
apply_hero_state("idle", true)
# 意图三档动作动画(§4.6.4 三档:①普通攻击=零表示〔set_pending 早退〕
# ②特殊准备=徽标弹入+二次弹跳(高举语义,运动手段 §2)③重击/硬化=缓推蓄力+定格微顿+回落;
# 三档运动签名互异,灰度/剪影层可辨)。摇定时一次性短动画,随后静帧保持。
#(charge = 宿主 _intent_charge 分档布尔参判传入)
func play_intent_motion(charge: bool) -> void:
var tw := _owner.create_tween()
tw.tween_property(self, "_intent_scale", 1.6, UiKit.motion(0.10))
tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.16))
tw.parallel().tween_property(self, "_glyph_rot", 4.0, UiKit.motion(0.10))
tw.tween_property(self, "_glyph_rot", 0.0, UiKit.motion(0.14))
if charge:
tw.tween_property(self, "_intent_scale", 1.22, UiKit.motion(0.28)).set_trans(Tween.TRANS_SINE) # ③蓄力缓推
tw.tween_interval(UiKit.motion(0.40)) # 定格微顿
tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.18))
else:
tw.tween_property(self, "_intent_scale", 1.26, UiKit.motion(0.11)) # ②二次弹跳(高举)
tw.tween_property(self, "_intent_scale", 1.0, UiKit.motion(0.13))
# 三态探针出口(fx_probe——当前态名+注册态集合;宿主 hero_state_probe 读件投影,外形零变)
func state_probe() -> Dictionary:
return {"state": _HS_NAME.get(_hero_state, "idle"), "registered": _hero_tex.keys()}
@@ -1 +0,0 @@
uid://cdfo2jgp2w7ei
-36
View File
@@ -1,36 +0,0 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 站桩身份与命值态件(StandeeBody 拆件——单一变化原因=战斗结算快照投影的字段形态):
## 站桩是谁、活没活、血量几何的唯一账面——身份五元组(种类/名/id/护甲/顺位)+存活位+血量账与两条血量系 tween 槽。
## 无 class_name(类名缓存面纪律)——经宿主 standee_body preload 引用(st 实例名沿用);
## hp_value 属性 setter 经 _owner 回引触发宿主重绘语义逐字。
extends RefCounted
var _owner: Node2D = null
var _kind := "enemy"
var _display_name := ""
var _enemy_id := ""
var _armor := "light"
var _order := 1
var _dead := false
var _hp_max := 1.0
var _hp_cur := 0 # 血量数字显示面(HUD框架 验收「血量数字可读」)
var hp_value := 1.0: # 血条值 0..1(直角递减——tween_property 目标,逐行刷新重启不累积)
set(v):
hp_value = clampf(v, 0.0, 1.0)
_owner.queue_redraw()
var _hp_tween: Tween
var _hp_flash_tw: Tween
func _init(owner: Node2D) -> void:
_owner = owner
# 血条短 tween 递减(直角递减占位);逐行刷新时重启 tween 不累积
func apply_hp(hp: int) -> void:
_hp_cur = clampi(hp, 0, int(_hp_max))
var target := float(_hp_cur) / _hp_max
if _hp_tween != null and _hp_tween.is_valid():
_hp_tween.kill()
_hp_tween = _owner.create_tween()
_hp_tween.tween_property(self, "hp_value", target, UiKit.motion(0.15))
@@ -1 +0,0 @@
uid://bjd7t5nmepbey
+11 -11
View File
@@ -231,8 +231,8 @@ func _ready() -> void:
if bv.hover_probe() != seal_t:
_problems.append("case① 悬停未到达(_hover=%s 应=%s)" % [bv.hover_probe(), seal_t])
var u1 = bv.cells_probe()[seal_t]
if not u1.face.hl_visible or u1.face.hl_color != UiKit.GOLD:
_problems.append("case① 悬停金框缺失(visible=%s color=%s)" % [str(u1.face.hl_visible), str(u1.face.hl_color)])
if not u1.st.hl_visible or u1.st.hl_color != UiKit.GOLD:
_problems.append("case① 悬停金框缺失(visible=%s color=%s)" % [str(u1.st.hl_visible), str(u1.st.hl_color)])
if bv.mouse_default_cursor_shape != Input.CURSOR_CROSS:
_problems.append("case① 挖掘光标未切十字(%d)" % bv.mouse_default_cursor_shape)
# ---- case② 悬停禁入:hover_forbidden 信号 + 灰禁红框 ----
@@ -240,14 +240,14 @@ func _ready() -> void:
await get_tree().process_frame
if _forbids.is_empty() or _forbids[_forbids.size() - 1] != forbid_t:
_problems.append("case② hover_forbidden 未达(%s)" % [str(_forbids)])
if bv.cells_probe()[forbid_t].face.hl_color != UiKit.RED_FORBID:
_problems.append("case② 禁入红框缺失(color=%s)" % str(bv.cells_probe()[forbid_t].face.hl_color))
if bv.cells_probe()[forbid_t].st.hl_color != UiKit.RED_FORBID:
_problems.append("case② 禁入红框缺失(color=%s)" % str(bv.cells_probe()[forbid_t].st.hl_color))
# ---- case③ 悬停已开格:路径预览点迹 + 移动光标 ----
_push_motion(_pos(bv, reveal_t))
await get_tree().process_frame
var dots := 0
for c in bv.cells_probe():
if bv.cells_probe()[c].face.dot_visible:
if bv.cells_probe()[c].st.dot_visible:
dots += 1
if dots < 1:
_problems.append("case③ 路径预览点迹未亮(dots=0)")
@@ -266,8 +266,8 @@ func _ready() -> void:
# 掩盖)——有盘断言纹理在槽、缺盘断言字形回退,两分支防双写踩踏复活 ----
var seal_unit = bv.cells_probe().get(seal_t)
if bv._palette.seal_tex != null:
if seal_unit == null or seal_unit.face.tex_seal != bv._palette.seal_tex:
_problems.append("case④b 封印纹理槽未挂封印印记纹理(tex_seal=%s)" % ["无单元" if seal_unit == null else str(seal_unit.face.tex_seal)])
if seal_unit == null or seal_unit.st.tex_seal != bv._palette.seal_tex:
_problems.append("case④b 封印纹理槽未挂封印印记纹理(tex_seal=%s)" % ["无单元" if seal_unit == null else str(seal_unit.st.tex_seal)])
elif seal_unit == null or seal_unit.glyph != "封":
_problems.append("case④b 缺盘字形回退缺失(glyph=%s)" % ["无单元" if seal_unit == null else String(seal_unit.glyph)])
# ---- case⑤ 双击 chord:禁入格双左击(核心拒挖零副作用)→ chord_attempted ----
@@ -371,12 +371,12 @@ func _ready() -> void:
if mark_t.x >= 0:
bv.set_variant_mark_resolver(func(_c): return "flat_damage")
bv.refresh()
if String(bv.cells_probe()[mark_t].face.mutation_mark) != "flat_damage":
_problems.append("case⑭ 变异角标未落位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].face.mutation_mark))
if String(bv.cells_probe()[mark_t].st.mutation_mark) != "flat_damage":
_problems.append("case⑭ 变异角标未落位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].st.mutation_mark))
bv.set_variant_mark_resolver(Callable())
bv.refresh()
if String(bv.cells_probe()[mark_t].face.mutation_mark) != "":
_problems.append("case⑭ 角标未随 resolver 复位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].face.mutation_mark))
if String(bv.cells_probe()[mark_t].st.mutation_mark) != "":
_problems.append("case⑭ 角标未随 resolver 复位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].st.mutation_mark))
else:
_problems.append("case⑭ 无未开格可验(板面异常)")
# ---- case⑮ 视野迷雾(三态呈态/半径圈/移动跟随/开关两态零扰动/提灯插值位;
+8 -8
View File
@@ -332,13 +332,13 @@ static func _on_adventure_curio_resolved(result: Dictionary, st: Dictionary, adr
# 商店贪心买齐:GP 局内死钱——买得起就买( affordable 集里挑最便宜的
# 反复买到达不上任何商品为止,guard 防死循环)。确定性(零随机流);购买出账/加算区入账全走
# runner.shop.buy_product 核心编排(与呈现层购买同一出账路径,wallet_changed 信号照常采集)。
# runner.buy_product 核心编排(与呈现层购买同一出账路径,wallet_changed 信号照常采集)。
static func _autoplay_shop(r, st: Dictionary) -> void:
var guard := 30
while guard > 0:
guard -= 1
var affordable: Array = []
for p in r.shop.shop_products():
for p in r.shop_products():
if r.gp >= int(p.get("price", 1 << 30)):
affordable.append(p)
if affordable.is_empty():
@@ -347,20 +347,20 @@ static func _autoplay_shop(r, st: Dictionary) -> void:
for p in affordable:
if int(p.get("price", 1 << 30)) < int(pick.get("price", 1 << 30)):
pick = p
if not r.shop.buy_product(String(pick.get("product_id", ""))):
if not r.buy_product(String(pick.get("product_id", ""))):
return
st.shop_buys = int(st.get("shop_buys", 0)) + 1
# 商店共享池贪心:货架三选一同构面贪心买齐——affordable 集挑最便宜反复买
# (每次买后全重抽、货架随买随换,枯竭/不可负担即止,guard 防死循环)。购买走 runner.shop.buy_pool_item
# (每次买后全重抽、货架随买随换,枯竭/不可负担即止,guard 防死循环)。购买走 runner.buy_pool_item
# 核心编排(与呈现层购买同一出账+池消费路径,wallet_changed 信号照常采集)。确定性(零随机流)。
static func _autoplay_shop_pool(r, st: Dictionary) -> void:
var guard := 30
while guard > 0:
guard -= 1
var affordable: Array = []
for c in r.shop.shop_shelf():
for c in r.shop_shelf():
if r.gp >= int(c.get("price", 1 << 30)):
affordable.append(c)
if affordable.is_empty():
@@ -369,7 +369,7 @@ static func _autoplay_shop_pool(r, st: Dictionary) -> void:
for c in affordable:
if int(c.get("price", 1 << 30)) < int(pick.get("price", 1 << 30)):
pick = c
if not r.shop.buy_pool_item(String(pick.get("kind_id", ""))):
if not r.buy_pool_item(String(pick.get("kind_id", ""))):
return
st.shop_buys = int(st.get("shop_buys", 0)) + 1
@@ -415,10 +415,10 @@ static func _route_pick(runner, route: String, st: Dictionary) -> int:
s = 10.0
var cheapest := 1 << 30
if String(st.get("shop_mode", "legacy")) == "pool":
for c in runner.shop.shop_shelf(): # pool 面:货架现价
for c in runner.shop_shelf(): # pool 面:货架现价
cheapest = mini(cheapest, int(c.get("price", 1 << 30)))
else:
for p in runner.shop.shop_products():
for p in runner.shop_products():
cheapest = mini(cheapest, int(p.get("price", 1 << 30)))
if cheapest < (1 << 30) and runner.gp >= cheapest:
s = 80.0
+12 -4
View File
@@ -1,5 +1,5 @@
{
"note": "存量基线·闸况变化时本脚本会判红,需人工确认后重生成;2026-10-29 二次记账=03/04/05 三闸按 80b49c0f 先例经用户裁决记入现行红项指纹(03=8a3cae0d0446/04=591b27250b36/05=de89589e49ed)——master 同值复现非迭代42新增(stash 对照法取证,承「迭代 41 测试债修复」条目扩展);原 2026-10-17 06 条目人工重生成记录见 git 提交史",
"note": "存量基线·首次自动生成;闸况变化时本脚本会判红,需人工确认后重生成;2026-10-17 人工确认重生成 06 条目:af94ce97 把 gdd_lint 记为存量红(01 篇 R5),该红已由 fcb8c3f8 补流程图消除,实测 --docs 域 06 exit=0,故基线改回绿。2026-10-18 新增闸件 08c 流水线闸(tools/pipeline_gate.py 注册上电),手工加入绿记录(roadmap 压缩批实测 --docs 域 exit=0),其余条目原样保留。2026-10-22 测试术语与层级重构批 A 改键:01 冒烟全量→01 系统测试(同一断言面 smoke_all.gd,运行器换 tools/run_system_test.py,过渡期判定口径不变)、02 单元套件→02 单元与集成测试(同一 run_all.gd 入口,名从实)、新增 00 冒烟测试(tools/run_smoke.py+game/tests/smoke_boot.tscn,重写后单跑实测 17 项 0 失败 PASS);09 静态扫描基线 exit=1(fp=4354b2aa72a7)系滞后红,本批单跑实测 exit=0(与 2026-10-22 16:20 回执一致),基线改回绿;其余条目原样保留。;2026-10-23 主会话记 07 doc_link 为存量红(用户令忽略:锻造工坊.md 死链的修复在另一会话未合入,合回闸豁免;外部修复合入后应重生成基线归绿);2026-10-26 用户特批带红合并(迭代 38 收口):09 静态扫描记存量红——gdstyle 恶化 2 条(battle_orchestrator.gd max-file-length 1314>1302=卡④头注必载内容、level_map_screen.gd max-class-variables 22>21=卡①缓存成员,均为卡片义务的记账漂移非缺陷,压缩还债经计算不可行),用户裁「不管引入的恶化直接提交、特许免测试重跑」;后续批次若触及两文件可顺带还债或经批准重生成基线归绿;2026-10-27 用户裁「标准是尺、不以现状为尺」:09 存量红照实记(exit=1+现行红项指纹,两条恶化各自点名可核),gdstyle 基线登记值回退 1302/21(波 0 的 18 条陈旧键清理保留),gdstyle_gate 逐条违规行加 FAIL 前缀并加「refresh 禁抬高登记值」守卫——存量红期间任何新增违规或进一步恶化都会改指纹被拦;阈值口径不变=gdstyle 默认值(文件 1000 行/类变量 15 等),还债归迭代 40 卡①(battle_orchestrator 1314→≤1000)与迭代 41 卡③(level_map_screen 22→≤15),还清后重新生成基线归绿;2026-10-27 用户特批带红合并(迭代39 拆件批):02 记存量红(fp f74b9a48ef4a=round_flow EV 锚 goblin 0.9148∉[1.00,1.20],触发器批有意行为改动,旧带宽锚触发器前设计,重锚归后续数值批待用户发起);08d 记存量红(red-nokeys=生涯.md:55 引 adventure_runner.gd:814 行号漂移,B1 拆分后 660 行,另 :50/:60 与 元进度.md:70 建议级,修复归合并后文档批,反哺点在册);07 doc_link 基线红→现绿改善随下次基线重生成记账;09 指纹经 S9 注解修复(f96632de)回归登记值 91d45583cf6d;2026-10-28 迭代40 收口记账(用户裁「刷」):卡① 拆件还债——battle_orchestrator 行数红项消失(实测 1036≤登记 1302;契约2 残余 36 行系合并适配带入 master 迭代39 内容,用户裁登记残余债归待办清单),09 红项由两条收敛为一条(level_map_screen 类变量 22>21=迭代38 存量,还债归迭代41 卡③),指纹 91d45583cf6d → 8467b4588a20(收口会话按闸壳同款算法对用户探跑日志实算,全量闸复验);02 存量红已随 master 侧 EV 重锚批(ba5105aa)归绿并入本合并;2026-10-28 EV 锚重锚批(工程改动·worktree ev-anchor-remeasure-20261028,用户特许会话内执行):02 基线红→现绿归绿——round_flow EV 锚 goblin 带宽按无情境口径(模拟器不过滤 trigger 行)重锚 [0.82,1.02](迭代39触发器批孤注守势入池稀释,实测 0.9148,重锚注记 enemy.tres 与测试注释;旧带 [1.00,1.20] 随池构成换代作废),撤销证明=带宽回退 [1.00,1.20] 断言复红;08d 存量红不变(生涯.md:55 行号漂移,修复归文档批);2026-10-29 文档清理批(批 5)实测 --docs 域 07/08d 皆绿,两条目归绿;2026-10-29 能力审计闸上电(tools/capability_audit.py 注册文档域 08e,查 GDD 纯度+能力清单与覆盖域表结构):手工加入绿记录(批 5 纯度红项清零后孤立实测 exit=0),其余条目原样保留;2026-10-29 合回闸关联档批实测重生成 02/09 两键(人工确认凭据=待办清单「迭代 41 测试债修复」条 2026-10-29 用户裁带红合并、代码域现存红全部由该条目承接):02 记存量红 fp f9865f1a5a44=9 套件失败(config_db 解析错 cattr/config_db_fatal_oob 退出码 2≠1/table_structure_guard 缺 meta_state/treasure_items 缺 crossrun/standee_fx_sweep set_trans 空引用/forge_workbench 120s 超时挂死/tree_requires_level 空基址/save_manager_domain 缺 _Line truncated
"gates": {
"00 冒烟测试": {
"exit": "0",
@@ -13,13 +13,17 @@
"exit": "1",
"fp": "f9865f1a5a44"
},
"03 场景探针": {
"03 探针 --click": {
"exit": "0",
"fp": "green"
},
"04 探针 --full": {
"exit": "0",
"fp": "green"
},
"05 渲染闸": {
"exit": "1",
"fp": "de89589e49ed"
"exit": "0",
"fp": "green"
},
"06 gdd_lint": {
"exit": "0",
@@ -49,6 +53,10 @@
"exit": "0",
"fp": "green"
},
"11 回放确定性对拍": {
"exit": "SKIP",
"fp": "skip"
},
"08d 引用可核": {
"exit": "0",
"fp": "green"
+11 -3
View File
@@ -33,10 +33,11 @@ tests/
| **单元测试** | 领域类的原子行为对不对(公式/状态机/纯逻辑)——从对外方法进、断言钉对外可观测面(写法判据见 test-conventions §8.2) | `tests/unit/` | `run_all.gd` manifest(闸 02) |
| **集成测试** | 跨系统的闭环对不对 | `tests/integration/` | 同上(与单元测试同一入口、同一协议) |
| **系统测试** | 启动真实工程走真实玩法路径 | 过渡期 `game/probe/smoke_all.gd`(断言迁移批后=`tests/system_test.gd`) | `run_system_test.py`(闸 01) |
| **UI 探针+渲染 golden** | 跑起来之后 UI 与场景切换对不对;画面基线 | `game/probe/*.tscn` 探针 | `probe_gate.sh`(闸 03)、`render_gate.sh`(闸 05) |
| **UI 探针+渲染 golden** | 跑起来之后 UI 与场景切换对不对;画面基线 | `game/probe/*.tscn` 探针 | `probe_gate.sh`(闸 03/04)、`render_gate.sh`(闸 05) |
> **integration/ 已建承载**(迭代 29 T3——此前只有章程句无实体,空壳曾于迭代 19 卡① T4 删过一次)。
> 首个集成用例 = `save_rng_roundtrip_test.gd`(存档⇄RNG 快照往返)。章程登记的四类集成用例里,
> 回放确定性已由 `tools/parity_anchor.py` 承担并收编为全量域闸 11 号条件闸件;
> Core-Core 与 A1×A2 端到端仍随属主批增量登记;各 GDD 登记的 deferral 集成用例以对侧登记为生效条件。
>
> **分层判据**:能不能在 `--script` 模式下不加载 autoload 就验完?能 = 单元测试;
@@ -147,6 +148,12 @@ tests/
| 系统间契约词表与受理口径:五配方互异/掷点标签成员性/增益布尔受理 | 战斗结算×数据配置×增益状态 | `DamageController.open_ticket()`/`StatusContainer.apply_effect()` | `system_contracts_test.gd:_test_damage_controller_vocabularies`;`system_contracts_test.gd:_test_rng_tag_closure`;`system_contracts_test.gd:_test_status_boolean_acceptance` |
| 敌人技能池跨表链接校验 | 数据配置×敌人系统 | `ConfigDbCrossValidator.validate_skill_pool_links()` | `config_db_cross_validator_test.gd:_test_all_links_valid`;`config_db_cross_validator_test.gd:_test_dangling_skill_id`;`config_db_cross_validator_test.gd:_test_cast_side_mismatch` |
**跨域条件闸**
| 能力 | 系统 | 真实入口 | 代表用例 |
|---|---|---|---|
| 回放确定性(同种子三变体逐位) | 全玩法链 | `tools/parity_anchor.py`(闸 11 条件闸,按批发起) | `tools/parity_anchor.py`(整件为条件闸) |
**覆盖域表**(`design/gdd/systems/` 逐篇对照——状态三档:已覆盖/部分覆盖/未覆盖;部分与未覆盖行的缺口指待办清单条目,覆盖面提升时同批改本表):
| 域 | 系统文档 | 能力行 | 状态 | 缺口 |
@@ -194,8 +201,9 @@ tests/
| `tools/run_system_test.py`(系统测试) | 真实玩法路径的行为断言面(推理→战斗→结算→进度推进) | 退出码+脚本错误扫描 | 玩法链路改动的调试期确认 |
| `tests/run_all.gd`(单元与集成) | 公式/状态机/契约/跨系统闭环(65 套件 manifest) | 退出码+三硬闸(见下);每套件子进程带截止时间守护——超时先 taskkill /T 整树回收再判该套件 FAIL、批继续(默认 120s,环境变量 `RUN_ALL_SUITE_TIMEOUT_SEC` 可覆盖);连续 3 个套件超时=红树整批挂死,第 3 个即中止整轮、汇总已见失败与超时清单后非 0 退出(阈值=可调常量,不连续的超时照旧逐个回收) | 新套件单跑调试;逻辑改动的最小单元复跑;开发期选跑——追加 `-- --only=<词[,词…]>` 只跑路径命中词的套件(零命中=用法错 exit 2;此形态仅供开发期最窄确认,不产闸回执、不作验收凭据);闸机关联档选跑——同一过滤机制加 `-- --only-source=gates`,由 `tools/run_all_gates.sh` 在 02 闸传入(回执 scope=related,是验收跑批本体) |
| `tools/probe_gate.sh`(场景探针,`--click`/`--full` 两模式) | UI 可达性、可见性、状态——真实实例化场景+真实输入路径 | 探针自检+退出码 | 新增 UI 区域的配套探针直跑与断言复核 |
| `tools/render_gate.sh`(渲染 golden) | 画面基线——截图与基线图逐像素容差比对 | golden 比对脚本(依赖 PIL)+退出码/取证双检/三段探针日志脚本错误扫描(`error_scan.py`) | 主题/素材/排版类改动;窗口化运行,需开显示器 |
| `tools/render_gate.sh`(渲染 golden) | 画面基线——截图与基线图逐像素容差比对 | golden 比对脚本(依赖 PIL) | 主题/素材/排版类改动;窗口化运行,需开显示器 |
| `game/tests/static_scan.sh`(S 域) | 项目纪律+第三方质量规则:RNG 单点/core 纯度/类型标注/文件头/注释禁词/旧符号残留;S13=gdstyle 质量规则(重复 load、循环内 await、循环内分配、不可达代码等真缺陷类;基线键=文件|规则|函数名(类/文件级=文件|规则,无数字条目带消息),数字漂移不假红,同键指标恶化超基线登记值即红) | 退出码(逐规则 FAIL 行) | 改产品码前后的快速自检;新增纪律先补扫描规则再修码 |
| `tools/parity_anchor.py`(回放对拍) | 同种子下三变体逐位确定性 | 工具脚本逐位比对 | 本批触及数值/存档/RNG/结算面时;须用户批准发起 |
| 测量件(`game/sim/`,逐件见下「游离脚本台账」) | 期望/分布读数——只给读数,不判对错 | 人工读 stdout 或配套工具脚本 | 调参与归因;MC 类一律用户发起(档位表见 `sim-data/README.md`「MC 采集档位表」) |
> 单件直跑是调试期最窄确认手段,**不构成验收**——验收只有 `tools/run_all_gates.sh` 一个入口(判定三硬闸见下「本地闸」节)。
@@ -259,7 +267,7 @@ godot --headless --path . res://probe/<probe>.tscn
| 脚本 | 定性 | 用途与触发方式 | 结果由谁判 |
|---|---|---|---|
| `adventure_monte_carlo.gd` | **在役**(`AGENTS.md` 在册入口) | 全冒险期望 MC | 人工读 stdout |
| `adventure_monte_carlo.gd` | **在役**(`AGENTS.md` 在册入口) | 全冒险期望 MC;`tools/parity_anchor.py` 的比对载体 | 工具脚本逐位比对 |
| `verifier_batch.gd` | **在役**(`AGENTS.md` 在册入口) | `tools/balance_verifier.py` 的采集端(数值验证) | 报告与原始数据落 `sim-data/` |
| `reward_expectation.gd` | **在役**(`AGENTS.md` 在册入口) | 奖励期望测量(非封锁格分布/资源类型频次) | 人工读 stdout |
| `monte_carlo.gd` | 在役 | 战斗期望 MC(胜轮数/承伤/清图/开格分布) | 人工读 stdout |
+166
View File
@@ -0,0 +1,166 @@
{
"variants": {
"base": {
"runsum": [
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":3,\"choices\":2,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":8,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71000\",\"1#71000\",\"1#71000\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":4,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":5,\"route\":\"min\",\"seal_hit\":22,\"seal_hit_gain\":79,\"seal_miss\":3,\"seal_miss_penalty\":8,\"seal_placed\":25,\"seed\":71000,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":24,\"visited_count\":3,\"wallet_count\":4}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":3,\"choices\":2,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":10,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71001\",\"1#71001\",\"1#71001\"],\"levels_cleared\":0,\"mine_dmg\":0,\"mine_hits\":6,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":6,\"route\":\"min\",\"seal_hit\":21,\"seal_hit_gain\":-296,\"seal_miss\":7,\"seal_miss_penalty\":16,\"seal_placed\":31,\"seed\":71001,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":10,\"visited_count\":3,\"wallet_count\":4}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":2,\"choices\":1,\"cleared\":false,\"encounter_max_hp_seq\":[26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":5,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71002\",\"1#71002\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":2,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":5,\"route\":\"min\",\"seal_hit\":17,\"seal_hit_gain\":391,\"seal_miss\":1,\"seal_miss_penalty\":8,\"seal_placed\":19,\"seed\":71002,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":21,\"visited_count\":2,\"wallet_count\":3}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":5,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":15,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71003\",\"1#71003\",\"1#71003\",\"1#71003\",\"1#71003\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":6,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":13,\"route\":\"min\",\"seal_hit\":28,\"seal_hit_gain\":317,\"seal_miss\":9,\"seal_miss_penalty\":24,\"seal_placed\":44,\"seed\":71003,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":23,\"visited_count\":5,\"wallet_count\":6}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":6,\"choices\":6,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,32],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":39,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71004\",\"1#71004\",\"1#71004\",\"1#71004\",\"1#71004\",\"1#71004\",\"1#71004\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":17,\"policy\":\"normal\",\"refreshes\":1,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":28,\"route\":\"min\",\"seal_hit\":70,\"seal_hit_gain\":23,\"seal_miss\":17,\"seal_miss_penalty\":8,\"seal_placed\":93,\"seed\":71004,\"shop_gp_seq\":[19],\"stopped_at\":1,\"taken\":47,\"visited_count\":7,\"wallet_count\":11}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":5,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":26,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71005\",\"1#71005\",\"1#71005\",\"1#71005\",\"1#71005\"],\"levels_cleared\":0,\"mine_dmg\":22,\"mine_hits\":8,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":15,\"route\":\"min\",\"seal_hit\":39,\"seal_hit_gain\":284,\"seal_miss\":11,\"seal_miss_penalty\":0,\"seal_placed\":56,\"seed\":71005,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":48,\"visited_count\":5,\"wallet_count\":6}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":5,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":17,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71006\",\"1#71006\",\"1#71006\",\"1#71006\",\"1#71006\"],\"levels_cleared\":0,\"mine_dmg\":6,\"mine_hits\":6,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":13,\"route\":\"min\",\"seal_hit\":39,\"seal_hit_gain\":291,\"seal_miss\":5,\"seal_miss_penalty\":6,\"seal_placed\":50,\"seed\":71006,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":23,\"visited_count\":5,\"wallet_count\":6}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":6,\"choices\":6,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,35],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":28,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71007\",\"1#71007\",\"1#71007\",\"1#71007\",\"1#71007\",\"1#71007\",\"1#71007\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":8,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":24,\"route\":\"min\",\"seal_hit\":59,\"seal_hit_gain\":-70,\"seal_miss\":11,\"seal_miss_penalty\":12,\"seal_placed\":74,\"seed\":71007,\"shop_gp_seq\":[39],\"stopped_at\":1,\"taken\":44,\"visited_count\":7,\"wallet_count\":14}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":7,\"choices\":7,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,32,32,32],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":38,\"hp_pct\":0,\"keystone_total\":3,\"level_maps\":[\"1#71008\",\"1#71008\",\"1#71008\",\"1#71008\",\"1#71008\",\"1#71008\",\"1#71008\",\"2#71008\",\"2#71008\"],\"levels_cleared\":1,\"mine_dmg\":16,\"mine_hits\":18,\"policy\":\"normal\",\"refreshes\":1,\"relics\":[\"hard_hat\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"hard_hat\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":22,\"heal_gained\":6,\"hp_after\":11,\"hp_before\":5,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"hard_hat\",\"feat_name\":\"安全帽\",\"hp_after\":11,\"hp_before\":11,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":33,\"route\":\"min\",\"seal_hit\":83,\"seal_hit_gain\":483,\"seal_miss\":14,\"seal_miss_penalty\":8,\"seal_placed\":102,\"seed\":71008,\"shop_gp_seq\":[19],\"stopped_at\":2,\"taken\":54,\"visited_count\":9,\"wallet_count\":12}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":4,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":18,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71009\",\"1#71009\",\"1#71009\",\"1#71009\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":3,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":9,\"route\":\"min\",\"seal_hit\":30,\"seal_hit_gain\":-28,\"seal_miss\":4,\"seal_miss_penalty\":8,\"seal_placed\":35,\"seed\":71009,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":26,\"visited_count\":4,\"wallet_count\":5}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":9,\"choices\":8,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":60,\"hp_pct\":0,\"keystone_total\":3,\"level_maps\":[\"1#71010\",\"1#71010\",\"1#71010\",\"1#71010\",\"1#71010\",\"1#71010\",\"1#71010\",\"2#71010\",\"2#71010\",\"2#71010\"],\"levels_cleared\":1,\"mine_dmg\":24,\"mine_hits\":13,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[\"hard_hat\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"hard_hat\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"breaker_strike\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":13,\"heal_gained\":7,\"hp_after\":18,\"hp_before\":11,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"hard_hat\",\"feat_name\":\"安全帽\",\"hp_after\":18,\"hp_before\":18,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":47,\"route\":\"min\",\"seal_hit\":109,\"seal_hit_gain\":309,\"seal_miss\":14,\"seal_miss_penalty\":8,\"seal_placed\":129,\"seed\":71010,\"shop_gp_seq\":[27],\"stopped_at\":2,\"taken\":84,\"visited_count\":10,\"wallet_count\":16}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":5,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":18,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71011\",\"1#71011\",\"1#71011\",\"1#71011\",\"1#71011\"],\"levels_cleared\":0,\"mine_dmg\":0,\"mine_hits\":7,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":15,\"route\":\"min\",\"seal_hit\":47,\"seal_hit_gain\":-119,\"seal_miss\":9,\"seal_miss_penalty\":24,\"seal_placed\":61,\"seed\":71011,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":18,\"visited_count\":5,\"wallet_count\":6}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":4,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":15,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71012\",\"1#71012\",\"1#71012\",\"1#71012\"],\"levels_cleared\":0,\"mine_dmg\":0,\"mine_hits\":8,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":11,\"route\":\"min\",\"seal_hit\":34,\"seal_hit_gain\":89,\"seal_miss\":10,\"seal_miss_penalty\":14,\"seal_placed\":47,\"seed\":71012,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":15,\"visited_count\":4,\"wallet_count\":5}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":2,\"choices\":1,\"cleared\":false,\"encounter_max_hp_seq\":[26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":6,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71013\",\"1#71013\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":4,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":3,\"route\":\"min\",\"seal_hit\":10,\"seal_hit_gain\":-21,\"seal_miss\":2,\"seal_miss_penalty\":8,\"seal_placed\":15,\"seed\":71013,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":22,\"visited_count\":2,\"wallet_count\":3}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":5,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":24,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71014\",\"1#71014\",\"1#71014\",\"1#71014\",\"1#71014\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":6,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":11,\"route\":\"min\",\"seal_hit\":32,\"seal_hit_gain\":-62,\"seal_miss\":2,\"seal_miss_penalty\":0,\"seal_placed\":40,\"seed\":71014,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":32,\"visited_count\":5,\"wallet_count\":6}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":5,\"choices\":6,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,32],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":24,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71015\",\"1#71015\",\"1#71015\",\"1#71015\",\"1#71015\",\"1#71015\",\"1#71015\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":8,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":17,\"route\":\"min\",\"seal_hit\":51,\"seal_hit_gain\":8,\"seal_miss\":9,\"seal_miss_penalty\":14,\"seal_placed\":68,\"seed\":71015,\"shop_gp_seq\":[25],\"stopped_at\":1,\"taken\":40,\"visited_count\":7,\"wallet_count\":11}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":5,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":25,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71016\",\"1#71016\",\"1#71016\",\"1#71016\",\"1#71016\"],\"levels_cleared\":0,\"mine_dmg\":6,\"mine_hits\":4,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":14,\"route\":\"min\",\"seal_hit\":50,\"seal_hit_gain\":421,\"seal_miss\":6,\"seal_miss_penalty\":4,\"seal_placed\":61,\"seed\":71016,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":31,\"visited_count\":5,\"wallet_count\":6}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":1,\"choices\":0,\"cleared\":false,\"encounter_max_hp_seq\":[26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":1,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71017\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":2,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":2,\"route\":\"min\",\"seal_hit\":7,\"seal_hit_gain\":7,\"seal_miss\":4,\"seal_miss_penalty\":16,\"seal_placed\":11,\"seed\":71017,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":17,\"visited_count\":1,\"wallet_count\":2}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":4,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":33,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71018\",\"1#71018\",\"1#71018\",\"1#71018\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":3,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":11,\"route\":\"min\",\"seal_hit\":40,\"seal_hit_gain\":416,\"seal_miss\":2,\"seal_miss_penalty\":-2,\"seal_placed\":45,\"seed\":71018,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":41,\"visited_count\":4,\"wallet_count\":5}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":2,\"choices\":1,\"cleared\":false,\"encounter_max_hp_seq\":[26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":7,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71019\",\"1#71019\"],\"levels_cleared\":0,\"mine_dmg\":0,\"mine_hits\":10,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":5,\"route\":\"min\",\"seal_hit\":11,\"seal_hit_gain\":-85,\"seal_miss\":10,\"seal_miss_penalty\":24,\"seal_placed\":22,\"seed\":71019,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":7,\"visited_count\":2,\"wallet_count\":3}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":20,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,41,41,41,41,41,41,41,41,44],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":154,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#71020\",\"1#71020\",\"1#71020\",\"1#71020\",\"1#71020\",\"1#71020\",\"1#71020\",\"2#71020\",\"2#71020\",\"2#71020\",\"2#71020\",\"2#71020\",\"2#71020\",\"2#71020\",\"2#71020\",\"2#71020\",\"2#71020\",\"3#71020\",\"3#71020\",\"3#71020\",\"3#71020\",\"3#71020\",\"3#71020\",\"3#71020\",\"3#71020\",\"3#71020\",\"3#71020\",\"3#71020\",\"3#71020\",\"3#71020\",\"3#71020\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":2,\"relics\":[\"pain_memory\",\"map_exchange\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"pain_memory\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":20,\"heal_gained\":7,\"hp_after\":25,\"hp_before\":18,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"pain_memory\",\"feat_name\":\"疼痛记忆\",\"hp_after\":25,\"hp_before\":25,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"map_exchange\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":27,\"heal_gained\":6,\"hp_after\":41,\"hp_before\":35,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"map_exchange\",\"feat_name\":\"弃图兑换\",\"hp_after\":41,\"hp_before\":41,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":102,\"route\":\"min\",\"seal_hit\":345,\"seal_hit_gain\":1539,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":362,\"seed\":71020,\"shop_gp_seq\":[27,62,86],\"stopped_at\":0,\"taken\":154,\"visited_count\":31,\"wallet_count\":38}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":23,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,38,38,38,38,38,38,38,38,38],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":139,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#71021\",\"1#71021\",\"1#71021\",\"1#71021\",\"1#71021\",\"1#71021\",\"1#71021\",\"2#71021\",\"2#71021\",\"2#71021\",\"2#71021\",\"2#71021\",\"2#71021\",\"2#71021\",\"2#71021\",\"2#71021\",\"2#71021\",\"3#71021\",\"3#71021\",\"3#71021\",\"3#71021\",\"3#71021\",\"3#71021\",\"3#71021\",\"3#71021\",\"3#71021\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":2,\"relics\":[\"executioner\",\"seal_echo\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":16,\"heal_gained\":3,\"hp_after\":35,\"hp_before\":32,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"executioner\",\"feat_name\":\"处刑台\",\"hp_after\":35,\"hp_before\":35,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"seal_echo\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":16,\"heal_gained\":0,\"hp_after\":38,\"hp_before\":38,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"seal_echo\",\"feat_name\":\"封印回响\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":85,\"route\":\"min\",\"seal_hit\":282,\"seal_hit_gain\":1672,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":307,\"seed\":71021,\"shop_gp_seq\":[34,47],\"stopped_at\":3,\"taken\":139,\"visited_count\":26,\"wallet_count\":31}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":17,\"choices\":19,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,41,41,41,41,41,41],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":117,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#71022\",\"1#71022\",\"1#71022\",\"1#71022\",\"1#71022\",\"1#71022\",\"1#71022\",\"2#71022\",\"2#71022\",\"2#71022\",\"2#71022\",\"2#71022\",\"2#71022\",\"2#71022\",\"2#71022\",\"2#71022\",\"2#71022\",\"3#71022\",\"3#71022\",\"3#71022\",\"3#71022\",\"3#71022\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"breaker_strike\",\"trap_burst\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"breaker_strike\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":25,\"heal_gained\":3,\"hp_after\":35,\"hp_before\":32,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"breaker_strike\",\"feat_name\":\"破封一击\",\"hp_after\":35,\"hp_before\":35,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"trap_burst\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":23,\"heal_gained\":2,\"hp_after\":41,\"hp_before\":39,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"trap_burst\",\"feat_name\":\"陷阱爆发\",\"hp_after\":41,\"hp_before\":41,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":78,\"route\":\"min\",\"seal_hit\":269,\"seal_hit_gain\":1054,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":288,\"seed\":71022,\"shop_gp_seq\":[39,61],\"stopped_at\":3,\"taken\":117,\"visited_count\":22,\"wallet_count\":30}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":16,\"choices\":19,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,35,35,35,35,35,35,38,38,38,38,38,38],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":131,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#71023\",\"1#71023\",\"1#71023\",\"1#71023\",\"1#71023\",\"1#71023\",\"1#71023\",\"2#71023\",\"2#71023\",\"2#71023\",\"2#71023\",\"2#71023\",\"2#71023\",\"2#71023\",\"2#71023\",\"2#71023\",\"2#71023\",\"3#71023\",\"3#71023\",\"3#71023\",\"3#71023\",\"3#71023\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"blood_honey\",\"skip_mining\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":10,\"heal_gained\":5,\"hp_after\":35,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"blood_honey\",\"feat_name\":\"血蜜\",\"hp_after\":35,\"hp_before\":35,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"skip_mining\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":16,\"heal_gained\":0,\"hp_after\":38,\"hp_before\":38,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"skip_mining\",\"feat_name\":\"跳格采掘\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":80,\"route\":\"min\",\"seal_hit\":255,\"seal_hit_gain\":650,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":274,\"seed\":71023,\"shop_gp_seq\":[26,37],\"stopped_at\":3,\"taken\":131,\"visited_count\":22,\"wallet_count\":26}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,32,32,32,32,32,32,38,38,38,38,38,38,38,38,44],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":142,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#71024\",\"1#71024\",\"1#71024\",\"1#71024\",\"1#71024\",\"1#71024\",\"1#71024\",\"2#71024\",\"2#71024\",\"2#71024\",\"2#71024\",\"2#71024\",\"2#71024\",\"2#71024\",\"2#71024\",\"2#71024\",\"2#71024\",\"3#71024\",\"3#71024\",\"3#71024\",\"3#71024\",\"3#71024\",\"3#71024\",\"3#71024\",\"3#71024\",\"3#71024\",\"3#71024\",\"3#71024\",\"3#71024\",\"3#71024\",\"3#71024\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":2,\"relics\":[\"chain_traps\",\"map_exchange\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"chain_traps\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":21,\"heal_gained\":1,\"hp_after\":32,\"hp_before\":31,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"chain_traps\",\"feat_name\":\"连环陷阱\",\"hp_after\":32,\"hp_before\":32,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"map_exchange\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"seal_echo\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":16,\"heal_gained\":5,\"hp_after\":38,\"hp_before\":33,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"map_exchange\",\"feat_name\":\"弃图兑换\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":102,\"route\":\"min\",\"seal_hit\":338,\"seal_hit_gain\":1807,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":354,\"seed\":71024,\"shop_gp_seq\":[16,68,68],\"stopped_at\":0,\"taken\":142,\"visited_count\":31,\"wallet_count\":35}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,41,41,41,41,41,41,41,47],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":139,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#71025\",\"1#71025\",\"1#71025\",\"1#71025\",\"1#71025\",\"1#71025\",\"1#71025\",\"2#71025\",\"2#71025\",\"2#71025\",\"2#71025\",\"2#71025\",\"2#71025\",\"2#71025\",\"2#71025\",\"2#71025\",\"2#71025\",\"3#71025\",\"3#71025\",\"3#71025\",\"3#71025\",\"3#71025\",\"3#71025\",\"3#71025\",\"3#71025\",\"3#71025\",\"3#71025\",\"3#71025\",\"3#71025\",\"3#71025\",\"3#71025\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":0,\"relics\":[\"market_close\",\"warning_beacon\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":16,\"heal_gained\":5,\"hp_after\":35,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"market_close\",\"feat_name\":\"收盘\",\"hp_after\":35,\"hp_before\":35,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":42,\"heal_gained\":8,\"hp_after\":25,\"hp_before\":17,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"warning_beacon\",\"feat_name\":\"示警烟火\",\"hp_after\":25,\"hp_before\":25,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":96,\"route\":\"min\",\"seal_hit\":329,\"seal_hit_gain\":1701,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":350,\"seed\":71025,\"shop_gp_seq\":[33,79,101],\"stopped_at\":0,\"taken\":139,\"visited_count\":31,\"wallet_count\":38}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":20,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,32,41,41,41,41,41,41,41,41,47],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":131,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#71026\",\"1#71026\",\"1#71026\",\"1#71026\",\"1#71026\",\"1#71026\",\"1#71026\",\"2#71026\",\"2#71026\",\"2#71026\",\"2#71026\",\"2#71026\",\"2#71026\",\"2#71026\",\"2#71026\",\"2#71026\",\"2#71026\",\"3#71026\",\"3#71026\",\"3#71026\",\"3#71026\",\"3#71026\",\"3#71026\",\"3#71026\",\"3#71026\",\"3#71026\",\"3#71026\",\"3#71026\",\"3#71026\",\"3#71026\",\"3#71026\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":0,\"relics\":[\"market_close\",\"execution_scholar\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":19,\"heal_gained\":4,\"hp_after\":32,\"hp_before\":28,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"market_close\",\"feat_name\":\"收盘\",\"hp_after\":32,\"hp_before\":32,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":19,\"heal_gained\":7,\"hp_after\":41,\"hp_before\":34,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"execution_scholar\",\"feat_name\":\"斩首学者\",\"hp_after\":41,\"hp_before\":41,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":92,\"route\":\"min\",\"seal_hit\":299,\"seal_hit_gain\":1397,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":323,\"seed\":71026,\"shop_gp_seq\":[22,72,114],\"stopped_at\":0,\"taken\":131,\"visited_count\":31,\"wallet_count\":39}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":16,\"choices\":18,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,32,38,38,38,38,38],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":119,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#71027\",\"1#71027\",\"1#71027\",\"1#71027\",\"1#71027\",\"1#71027\",\"1#71027\",\"2#71027\",\"2#71027\",\"2#71027\",\"2#71027\",\"2#71027\",\"2#71027\",\"2#71027\",\"2#71027\",\"2#71027\",\"2#71027\",\"3#71027\",\"3#71027\",\"3#71027\",\"3#71027\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"hard_hat\",\"fuse_wire\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"hard_hat\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"chain_traps\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":12,\"heal_gained\":6,\"hp_after\":20,\"hp_before\":14,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"hard_hat\",\"feat_name\":\"安全帽\",\"hp_after\":20,\"hp_before\":20,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"fuse_wire\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"venom_edge\",\"kind\":\"relic\",\"tier\":\"rare\"}],\"gp\":15,\"heal_gained\":7,\"hp_after\":35,\"hp_before\":28,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"fuse_wire\",\"feat_name\":\"保险丝\",\"hp_after\":35,\"hp_before\":35,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":75,\"route\":\"min\",\"seal_hit\":255,\"seal_hit_gain\":1770,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":272,\"seed\":71027,\"shop_gp_seq\":[20,50],\"stopped_at\":3,\"taken\":119,\"visited_count\":21,\"wallet_count\":27}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":5,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":38,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#71028\",\"1#71028\",\"1#71028\",\"1#71028\",\"1#71028\"],\"levels_cleared\":0,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":16,\"route\":\"min\",\"seal_hit\":60,\"seal_hit_gain\":-8,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":62,\"seed\":71028,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":38,\"visited_count\":5,\"wallet_count\":6}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":15,\"choices\":18,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,38,38,38,38,38],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":105,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#71029\",\"1#71029\",\"1#71029\",\"1#71029\",\"1#71029\",\"1#71029\",\"1#71029\",\"2#71029\",\"2#71029\",\"2#71029\",\"2#71029\",\"2#71029\",\"2#71029\",\"2#71029\",\"2#71029\",\"2#71029\",\"2#71029\",\"3#71029\",\"3#71029\",\"3#71029\",\"3#71029\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"last_drop\",\"twin_pick\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":24,\"heal_gained\":4,\"hp_after\":32,\"hp_before\":28,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"last_drop\",\"feat_name\":\"一滴不剩\",\"hp_after\":32,\"hp_before\":32,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":29,\"heal_gained\":7,\"hp_after\":37,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"twin_pick\",\"feat_name\":\"双管镐\",\"hp_after\":37,\"hp_before\":37,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":64,\"route\":\"min\",\"seal_hit\":244,\"seal_hit_gain\":1307,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":262,\"seed\":71029,\"shop_gp_seq\":[24,57],\"stopped_at\":3,\"taken\":105,\"visited_count\":21,\"wallet_count\":26}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":15,\"choices\":19,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,38,38,38,38,38],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":114,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#71030\",\"1#71030\",\"1#71030\",\"1#71030\",\"1#71030\",\"1#71030\",\"1#71030\",\"2#71030\",\"2#71030\",\"2#71030\",\"2#71030\",\"2#71030\",\"2#71030\",\"2#71030\",\"2#71030\",\"2#71030\",\"2#71030\",\"3#71030\",\"3#71030\",\"3#71030\",\"3#71030\",\"3#71030\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"market_close\",\"chain_traps\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":13,\"heal_gained\":2,\"hp_after\":32,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"market_close\",\"feat_name\":\"收盘\",\"hp_after\":32,\"hp_before\":32,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"chain_traps\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":42,\"heal_gained\":7,\"hp_after\":35,\"hp_before\":28,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"chain_traps\",\"feat_name\":\"连环陷阱\",\"hp_after\":35,\"hp_before\":35,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":71,\"route\":\"min\",\"seal_hit\":234,\"seal_hit_gain\":1765,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":255,\"seed\":71030,\"shop_gp_seq\":[23,54],\"stopped_at\":3,\"taken\":114,\"visited_count\":22,\"wallet_count\":26}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":23,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,32,32,32,32,32,32,38,38,38,38,38,38,38,38,38],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":142,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#71031\",\"1#71031\",\"1#71031\",\"1#71031\",\"1#71031\",\"1#71031\",\"1#71031\",\"2#71031\",\"2#71031\",\"2#71031\",\"2#71031\",\"2#71031\",\"2#71031\",\"2#71031\",\"2#71031\",\"2#71031\",\"2#71031\",\"3#71031\",\"3#71031\",\"3#71031\",\"3#71031\",\"3#71031\",\"3#71031\",\"3#71031\",\"3#71031\",\"3#71031\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":2,\"relics\":[\"chain_traps\",\"seal_echo\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"chain_traps\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":28,\"heal_gained\":6,\"hp_after\":31,\"hp_before\":25,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"chain_traps\",\"feat_name\":\"连环陷阱\",\"hp_after\":31,\"hp_before\":31,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"seal_echo\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"skip_mining\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":23,\"heal_gained\":7,\"hp_after\":37,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"seal_echo\",\"feat_name\":\"封印回响\",\"hp_after\":37,\"hp_before\":37,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":89,\"route\":\"min\",\"seal_hit\":303,\"seal_hit_gain\":1540,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":324,\"seed\":71031,\"shop_gp_seq\":[21,69],\"stopped_at\":3,\"taken\":142,\"visited_count\":26,\"wallet_count\":31}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,41,41,41,41,41,41,41,44],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":144,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#71032\",\"1#71032\",\"1#71032\",\"1#71032\",\"1#71032\",\"1#71032\",\"1#71032\",\"2#71032\",\"2#71032\",\"2#71032\",\"2#71032\",\"2#71032\",\"2#71032\",\"2#71032\",\"2#71032\",\"2#71032\",\"2#71032\",\"3#71032\",\"3#71032\",\"3#71032\",\"3#71032\",\"3#71032\",\"3#71032\",\"3#71032\",\"3#71032\",\"3#71032\",\"3#71032\",\"3#71032\",\"3#71032\",\"3#71032\",\"3#71032\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":3,\"relics\":[\"executioner\",\"market_close\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":19,\"heal_gained\":5,\"hp_after\":35,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"executioner\",\"feat_name\":\"处刑台\",\"hp_after\":35,\"hp_before\":35,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":16,\"heal_gained\":3,\"hp_after\":41,\"hp_before\":38,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"market_close\",\"feat_name\":\"收盘\",\"hp_after\":41,\"hp_before\":41,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":105,\"route\":\"min\",\"seal_hit\":330,\"seal_hit_gain\":1031,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":362,\"seed\":71032,\"shop_gp_seq\":[27,64,78],\"stopped_at\":0,\"taken\":144,\"visited_count\":31,\"wallet_count\":36}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":16,\"choices\":19,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,32,32,32,32,32,32,38,38,38,38,38,38],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":117,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#71033\",\"1#71033\",\"1#71033\",\"1#71033\",\"1#71033\",\"1#71033\",\"1#71033\",\"2#71033\",\"2#71033\",\"2#71033\",\"2#71033\",\"2#71033\",\"2#71033\",\"2#71033\",\"2#71033\",\"2#71033\",\"2#71033\",\"3#71033\",\"3#71033\",\"3#71033\",\"3#71033\",\"3#71033\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"map_exchange\",\"skip_mining\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"map_exchange\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":16,\"heal_gained\":5,\"hp_after\":32,\"hp_before\":27,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"map_exchange\",\"feat_name\":\"弃图兑换\",\"hp_after\":32,\"hp_before\":32,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"skip_mining\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":23,\"heal_gained\":5,\"hp_after\":38,\"hp_before\":33,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"skip_mining\",\"feat_name\":\"跳格采掘\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":78,\"route\":\"min\",\"seal_hit\":240,\"seal_hit_gain\":654,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":255,\"seed\":71033,\"shop_gp_seq\":[20,45],\"stopped_at\":3,\"taken\":117,\"visited_count\":22,\"wallet_count\":26}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,41,41,41,41,41,41,41,44],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":151,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#71034\",\"1#71034\",\"1#71034\",\"1#71034\",\"1#71034\",\"1#71034\",\"1#71034\",\"2#71034\",\"2#71034\",\"2#71034\",\"2#71034\",\"2#71034\",\"2#71034\",\"2#71034\",\"2#71034\",\"2#71034\",\"2#71034\",\"3#71034\",\"3#71034\",\"3#71034\",\"3#71034\",\"3#71034\",\"3#71034\",\"3#71034\",\"3#71034\",\"3#71034\",\"3#71034\",\"3#71034\",\"3#71034\",\"3#71034\",\"3#71034\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"executioner\",\"blood_insight\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":22,\"heal_gained\":7,\"hp_after\":30,\"hp_before\":23,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"executioner\",\"feat_name\":\"处刑台\",\"hp_after\":30,\"hp_before\":30,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":13,\"heal_gained\":5,\"hp_after\":41,\"hp_before\":36,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"blood_insight\",\"feat_name\":\"见血开窍\",\"hp_after\":41,\"hp_before\":41,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":106,\"route\":\"min\",\"seal_hit\":332,\"seal_hit_gain\":1732,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":351,\"seed\":71034,\"shop_gp_seq\":[29,74,76],\"stopped_at\":0,\"taken\":151,\"visited_count\":31,\"wallet_count\":37}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,41,41,41,41,41,41,41,47],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":144,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#71035\",\"1#71035\",\"1#71035\",\"1#71035\",\"1#71035\",\"1#71035\",\"1#71035\",\"2#71035\",\"2#71035\",\"2#71035\",\"2#71035\",\"2#71035\",\"2#71035\",\"2#71035\",\"2#71035\",\"2#71035\",\"2#71035\",\"3#71035\",\"3#71035\",\"3#71035\",\"3#71035\",\"3#71035\",\"3#71035\",\"3#71035\",\"3#71035\",\"3#71035\",\"3#71035\",\"3#71035\",\"3#71035\",\"3#71035\",\"3#71035\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"trap_instinct\",\"blast_accounting\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"trap_instinct\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":13,\"heal_gained\":4,\"hp_after\":35,\"hp_before\":31,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"trap_instinct\",\"feat_name\":\"陷阱本能\",\"hp_after\":35,\"hp_before\":35,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":33,\"heal_gained\":6,\"hp_after\":41,\"hp_before\":35,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"blast_accounting\",\"feat_name\":\"爆破会计\",\"hp_after\":41,\"hp_before\":41,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":96,\"route\":\"min\",\"seal_hit\":316,\"seal_hit_gain\":1841,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":340,\"seed\":71035,\"shop_gp_seq\":[32,58,100],\"stopped_at\":0,\"taken\":144,\"visited_count\":31,\"wallet_count\":38}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,32,38,38,38,38,38,38,38,44],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":148,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#71036\",\"1#71036\",\"1#71036\",\"1#71036\",\"1#71036\",\"1#71036\",\"1#71036\",\"2#71036\",\"2#71036\",\"2#71036\",\"2#71036\",\"2#71036\",\"2#71036\",\"2#71036\",\"2#71036\",\"2#71036\",\"2#71036\",\"3#71036\",\"3#71036\",\"3#71036\",\"3#71036\",\"3#71036\",\"3#71036\",\"3#71036\",\"3#71036\",\"3#71036\",\"3#71036\",\"3#71036\",\"3#71036\",\"3#71036\",\"3#71036\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"skip_mining\",\"frontier_miner\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"skip_mining\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":26,\"heal_gained\":6,\"hp_after\":23,\"hp_before\":17,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"skip_mining\",\"feat_name\":\"跳格采掘\",\"hp_after\":23,\"hp_before\":23,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":23,\"heal_gained\":7,\"hp_after\":38,\"hp_before\":31,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"frontier_miner\",\"feat_name\":\"边陲矿工\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":100,\"route\":\"min\",\"seal_hit\":331,\"seal_hit_gain\":1293,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":354,\"seed\":71036,\"shop_gp_seq\":[18,68,74],\"stopped_at\":0,\"taken\":148,\"visited_count\":31,\"wallet_count\":35}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":17,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,29,29,29,29,29,35,35,35,35,35,35,35,41],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":155,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#71037\",\"1#71037\",\"1#71037\",\"1#71037\",\"1#71037\",\"1#71037\",\"1#71037\",\"2#71037\",\"2#71037\",\"2#71037\",\"2#71037\",\"2#71037\",\"2#71037\",\"2#71037\",\"2#71037\",\"2#71037\",\"2#71037\",\"3#71037\",\"3#71037\",\"3#71037\",\"3#71037\",\"3#71037\",\"3#71037\",\"3#71037\",\"3#71037\",\"3#71037\",\"3#71037\",\"3#71037\",\"3#71037\",\"3#71037\",\"3#71037\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":2,\"relics\":[\"breaker_strike\",\"field_dressing\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":3,\"candidates\":[{\"feat_id\":\"breaker_strike\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":15,\"heal_gained\":1,\"hp_after\":29,\"hp_before\":28,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"breaker_strike\",\"feat_name\":\"破封一击\",\"hp_after\":29,\"hp_before\":29,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":35,\"heal_gained\":7,\"hp_after\":35,\"hp_before\":28,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"field_dressing\",\"feat_name\":\"战地包扎\",\"hp_after\":35,\"hp_before\":35,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":95,\"route\":\"min\",\"seal_hit\":290,\"seal_hit_gain\":191,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":310,\"seed\":71037,\"shop_gp_seq\":[9,43,95],\"stopped_at\":0,\"taken\":155,\"visited_count\":31,\"wallet_count\":32}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,38,38,38,38,38,38,38,44],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":127,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#71038\",\"1#71038\",\"1#71038\",\"1#71038\",\"1#71038\",\"1#71038\",\"1#71038\",\"2#71038\",\"2#71038\",\"2#71038\",\"2#71038\",\"2#71038\",\"2#71038\",\"2#71038\",\"2#71038\",\"2#71038\",\"2#71038\",\"3#71038\",\"3#71038\",\"3#71038\",\"3#71038\",\"3#71038\",\"3#71038\",\"3#71038\",\"3#71038\",\"3#71038\",\"3#71038\",\"3#71038\",\"3#71038\",\"3#71038\",\"3#71038\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":0,\"relics\":[\"last_stand\",\"blast_accounting\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"last_stand\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":19,\"heal_gained\":6,\"hp_after\":35,\"hp_before\":29,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"last_stand\",\"feat_name\":\"背水\",\"hp_after\":35,\"hp_before\":35,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":20,\"heal_gained\":6,\"hp_after\":38,\"hp_before\":32,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"blast_accounting\",\"feat_name\":\"爆破会计\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":97,\"route\":\"min\",\"seal_hit\":316,\"seal_hit_gain\":941,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":335,\"seed\":71038,\"shop_gp_seq\":[30,60,95],\"stopped_at\":0,\"taken\":127,\"visited_count\":31,\"wallet_count\":37}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,41,41,41,41,41,41,41,47],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":141,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#71039\",\"1#71039\",\"1#71039\",\"1#71039\",\"1#71039\",\"1#71039\",\"1#71039\",\"2#71039\",\"2#71039\",\"2#71039\",\"2#71039\",\"2#71039\",\"2#71039\",\"2#71039\",\"2#71039\",\"2#71039\",\"2#71039\",\"3#71039\",\"3#71039\",\"3#71039\",\"3#71039\",\"3#71039\",\"3#71039\",\"3#71039\",\"3#71039\",\"3#71039\",\"3#71039\",\"3#71039\",\"3#71039\",\"3#71039\",\"3#71039\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"skip_mining\",\"last_drop\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"skip_mining\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"map_exchange\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":16,\"heal_gained\":7,\"hp_after\":23,\"hp_before\":16,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"skip_mining\",\"feat_name\":\"跳格采掘\",\"hp_after\":23,\"hp_before\":23,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":34,\"heal_gained\":8,\"hp_after\":36,\"hp_before\":28,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"last_drop\",\"feat_name\":\"一滴不剩\",\"hp_after\":36,\"hp_before\":36,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":91,\"route\":\"min\",\"seal_hit\":316,\"seal_hit_gain\":955,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":332,\"seed\":71039,\"shop_gp_seq\":[39,76,84],\"stopped_at\":0,\"taken\":141,\"visited_count\":31,\"wallet_count\":38}"
],
"jsonl": {
"normal": {
"sha256": "19ee7ccdfb3780f5152b30f830c1168852c4fb4df73e2c27463a3a4695ef35ed",
"rows": 9216
},
"perfect": {
"sha256": "962b6f68440bcf95f2b21ac4cc07a8523655f044ae6355aa674d0db27bdecc1d",
"rows": 60774
}
}
},
"attune": {
"runsum": [
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":8,\"choices\":7,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":38,\"hp_pct\":0,\"keystone_total\":3,\"level_maps\":[\"1#72000\",\"1#72000\",\"1#72000\",\"1#72000\",\"1#72000\",\"1#72000\",\"1#72000\",\"2#72000\",\"2#72000\"],\"levels_cleared\":1,\"mine_dmg\":22,\"mine_hits\":11,\"policy\":\"normal\",\"refreshes\":1,\"relics\":[\"seal_stake\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":18,\"heal_gained\":7,\"hp_after\":14,\"hp_before\":7,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"seal_stake\",\"feat_name\":\"封印桩\",\"hp_after\":14,\"hp_before\":14,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":32,\"route\":\"min\",\"seal_hit\":91,\"seal_hit_gain\":738,\"seal_miss\":9,\"seal_miss_penalty\":-2,\"seal_placed\":108,\"seed\":72000,\"shop_gp_seq\":[29],\"stopped_at\":2,\"taken\":60,\"visited_count\":9,\"wallet_count\":15}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":5,\"choices\":6,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,32,32],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":39,\"hp_pct\":0,\"keystone_total\":3,\"level_maps\":[\"1#72001\",\"1#72001\",\"1#72001\",\"1#72001\",\"1#72001\",\"1#72001\",\"1#72001\",\"2#72001\"],\"levels_cleared\":1,\"mine_dmg\":14,\"mine_hits\":10,\"policy\":\"normal\",\"refreshes\":1,\"relics\":[\"skip_mining\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"skip_mining\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"stone_skin\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":27,\"heal_gained\":6,\"hp_after\":9,\"hp_before\":3,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"skip_mining\",\"feat_name\":\"跳格采掘\",\"hp_after\":9,\"hp_before\":9,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":23,\"route\":\"min\",\"seal_hit\":63,\"seal_hit_gain\":556,\"seal_miss\":9,\"seal_miss_penalty\":20,\"seal_placed\":78,\"seed\":72001,\"shop_gp_seq\":[17],\"stopped_at\":2,\"taken\":53,\"visited_count\":8,\"wallet_count\":10}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":3,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":9,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72002\",\"1#72002\",\"1#72002\",\"1#72002\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":4,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":5,\"route\":\"min\",\"seal_hit\":11,\"seal_hit_gain\":-127,\"seal_miss\":5,\"seal_miss_penalty\":8,\"seal_placed\":23,\"seed\":72002,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":25,\"visited_count\":4,\"wallet_count\":4}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":5,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":28,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72003\",\"1#72003\",\"1#72003\",\"1#72003\",\"1#72003\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":7,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":16,\"route\":\"min\",\"seal_hit\":60,\"seal_hit_gain\":234,\"seal_miss\":6,\"seal_miss_penalty\":8,\"seal_placed\":70,\"seed\":72003,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":36,\"visited_count\":5,\"wallet_count\":6}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":4,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":13,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72004\",\"1#72004\",\"1#72004\",\"1#72004\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":9,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":8,\"route\":\"min\",\"seal_hit\":21,\"seal_hit_gain\":323,\"seal_miss\":5,\"seal_miss_penalty\":6,\"seal_placed\":28,\"seed\":72004,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":29,\"visited_count\":4,\"wallet_count\":5}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":4,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":21,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72005\",\"1#72005\",\"1#72005\",\"1#72005\"],\"levels_cleared\":0,\"mine_dmg\":6,\"mine_hits\":7,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":12,\"route\":\"min\",\"seal_hit\":35,\"seal_hit_gain\":321,\"seal_miss\":3,\"seal_miss_penalty\":4,\"seal_placed\":42,\"seed\":72005,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":27,\"visited_count\":4,\"wallet_count\":5}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":5,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":17,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72006\",\"1#72006\",\"1#72006\",\"1#72006\",\"1#72006\"],\"levels_cleared\":0,\"mine_dmg\":6,\"mine_hits\":8,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":16,\"route\":\"min\",\"seal_hit\":48,\"seal_hit_gain\":316,\"seal_miss\":7,\"seal_miss_penalty\":14,\"seal_placed\":68,\"seed\":72006,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":23,\"visited_count\":5,\"wallet_count\":6}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":3,\"choices\":2,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":10,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72007\",\"1#72007\",\"1#72007\"],\"levels_cleared\":0,\"mine_dmg\":0,\"mine_hits\":2,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":7,\"route\":\"min\",\"seal_hit\":26,\"seal_hit_gain\":363,\"seal_miss\":3,\"seal_miss_penalty\":14,\"seal_placed\":34,\"seed\":72007,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":10,\"visited_count\":3,\"wallet_count\":4}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":3,\"choices\":2,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":8,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72008\",\"1#72008\",\"1#72008\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":3,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":8,\"route\":\"min\",\"seal_hit\":27,\"seal_hit_gain\":-20,\"seal_miss\":3,\"seal_miss_penalty\":14,\"seal_placed\":34,\"seed\":72008,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":16,\"visited_count\":3,\"wallet_count\":4}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":5,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":35,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72009\",\"1#72009\",\"1#72009\",\"1#72009\",\"1#72009\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":11,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":16,\"route\":\"min\",\"seal_hit\":51,\"seal_hit_gain\":281,\"seal_miss\":8,\"seal_miss_penalty\":0,\"seal_placed\":63,\"seed\":72009,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":43,\"visited_count\":5,\"wallet_count\":6}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":4,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":11,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72010\",\"1#72010\",\"1#72010\",\"1#72010\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":4,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":8,\"route\":\"min\",\"seal_hit\":21,\"seal_hit_gain\":-53,\"seal_miss\":4,\"seal_miss_penalty\":8,\"seal_placed\":30,\"seed\":72010,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":19,\"visited_count\":4,\"wallet_count\":5}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":7,\"choices\":7,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,32,32,32],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":44,\"hp_pct\":0,\"keystone_total\":3,\"level_maps\":[\"1#72011\",\"1#72011\",\"1#72011\",\"1#72011\",\"1#72011\",\"1#72011\",\"1#72011\",\"2#72011\",\"2#72011\"],\"levels_cleared\":1,\"mine_dmg\":30,\"mine_hits\":16,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[\"map_exchange\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"map_exchange\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":21,\"heal_gained\":6,\"hp_after\":17,\"hp_before\":11,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"map_exchange\",\"feat_name\":\"弃图兑换\",\"hp_after\":17,\"hp_before\":17,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":34,\"route\":\"min\",\"seal_hit\":80,\"seal_hit_gain\":200,\"seal_miss\":15,\"seal_miss_penalty\":24,\"seal_placed\":101,\"seed\":72011,\"shop_gp_seq\":[16],\"stopped_at\":2,\"taken\":74,\"visited_count\":9,\"wallet_count\":12}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":3,\"choices\":2,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":5,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72012\",\"1#72012\",\"1#72012\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":3,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":6,\"route\":\"min\",\"seal_hit\":18,\"seal_hit_gain\":107,\"seal_miss\":5,\"seal_miss_penalty\":14,\"seal_placed\":25,\"seed\":72012,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":13,\"visited_count\":3,\"wallet_count\":4}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":4,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":14,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72013\",\"1#72013\",\"1#72013\",\"1#72013\",\"1#72013\"],\"levels_cleared\":0,\"mine_dmg\":14,\"mine_hits\":10,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":11,\"route\":\"min\",\"seal_hit\":38,\"seal_hit_gain\":289,\"seal_miss\":14,\"seal_miss_penalty\":16,\"seal_placed\":54,\"seed\":72013,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":28,\"visited_count\":5,\"wallet_count\":5}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":7,\"choices\":7,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,32,32,32],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":34,\"hp_pct\":0,\"keystone_total\":3,\"level_maps\":[\"1#72014\",\"1#72014\",\"1#72014\",\"1#72014\",\"1#72014\",\"1#72014\",\"1#72014\",\"2#72014\",\"2#72014\"],\"levels_cleared\":1,\"mine_dmg\":16,\"mine_hits\":7,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[\"seal_echo\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"seal_echo\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"trap_instinct\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":18,\"heal_gained\":6,\"hp_after\":22,\"hp_before\":16,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"seal_echo\",\"feat_name\":\"封印回响\",\"hp_after\":22,\"hp_before\":22,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":32,\"route\":\"min\",\"seal_hit\":85,\"seal_hit_gain\":373,\"seal_miss\":11,\"seal_miss_penalty\":12,\"seal_placed\":104,\"seed\":72014,\"shop_gp_seq\":[19],\"stopped_at\":2,\"taken\":50,\"visited_count\":9,\"wallet_count\":12}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":3,\"choices\":2,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":7,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72015\",\"1#72015\",\"1#72015\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":4,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":5,\"route\":\"min\",\"seal_hit\":22,\"seal_hit_gain\":243,\"seal_miss\":3,\"seal_miss_penalty\":8,\"seal_placed\":26,\"seed\":72015,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":23,\"visited_count\":3,\"wallet_count\":4}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":6,\"choices\":6,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,32,32],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":28,\"hp_pct\":0,\"keystone_total\":3,\"level_maps\":[\"1#72016\",\"1#72016\",\"1#72016\",\"1#72016\",\"1#72016\",\"1#72016\",\"1#72016\",\"2#72016\"],\"levels_cleared\":1,\"mine_dmg\":16,\"mine_hits\":11,\"policy\":\"normal\",\"refreshes\":1,\"relics\":[\"perfect_insurance\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"perfect_insurance\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":29,\"heal_gained\":6,\"hp_after\":11,\"hp_before\":5,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"perfect_insurance\",\"feat_name\":\"完美保险\",\"hp_after\":11,\"hp_before\":11,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":25,\"route\":\"min\",\"seal_hit\":78,\"seal_hit_gain\":740,\"seal_miss\":10,\"seal_miss_penalty\":24,\"seal_placed\":92,\"seed\":72016,\"shop_gp_seq\":[15],\"stopped_at\":2,\"taken\":44,\"visited_count\":8,\"wallet_count\":11}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":4,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":18,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72017\",\"1#72017\",\"1#72017\",\"1#72017\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":9,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":9,\"route\":\"min\",\"seal_hit\":30,\"seal_hit_gain\":-413,\"seal_miss\":9,\"seal_miss_penalty\":8,\"seal_placed\":40,\"seed\":72017,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":26,\"visited_count\":4,\"wallet_count\":5}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":1,\"choices\":0,\"cleared\":false,\"encounter_max_hp_seq\":[26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":2,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72018\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":2,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":1,\"route\":\"min\",\"seal_hit\":2,\"seal_hit_gain\":64,\"seal_miss\":3,\"seal_miss_penalty\":8,\"seal_placed\":7,\"seed\":72018,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":18,\"visited_count\":1,\"wallet_count\":2}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":3,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":9,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#72019\",\"1#72019\",\"1#72019\",\"1#72019\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":4,\"policy\":\"normal\",\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":7,\"route\":\"min\",\"seal_hit\":24,\"seal_hit_gain\":319,\"seal_miss\":5,\"seal_miss_penalty\":22,\"seal_placed\":33,\"seed\":72019,\"shop_gp_seq\":[],\"stopped_at\":1,\"taken\":17,\"visited_count\":4,\"wallet_count\":4}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":17,\"choices\":20,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,32,38,38,38,38,38,38],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":128,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#72020\",\"1#72020\",\"1#72020\",\"1#72020\",\"1#72020\",\"1#72020\",\"1#72020\",\"2#72020\",\"2#72020\",\"2#72020\",\"2#72020\",\"2#72020\",\"2#72020\",\"2#72020\",\"2#72020\",\"2#72020\",\"2#72020\",\"3#72020\",\"3#72020\",\"3#72020\",\"3#72020\",\"3#72020\",\"3#72020\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"geologist\",\"skip_mining\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":19,\"heal_gained\":6,\"hp_after\":32,\"hp_before\":26,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"geologist\",\"feat_name\":\"地质学家\",\"hp_after\":32,\"hp_before\":32,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"skip_mining\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":19,\"heal_gained\":1,\"hp_after\":38,\"hp_before\":37,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"skip_mining\",\"feat_name\":\"跳格采掘\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":79,\"route\":\"min\",\"seal_hit\":267,\"seal_hit_gain\":884,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":285,\"seed\":72020,\"shop_gp_seq\":[14,63],\"stopped_at\":3,\"taken\":128,\"visited_count\":23,\"wallet_count\":28}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,32,41,41,41,41,41,41,41,44],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":160,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#72021\",\"1#72021\",\"1#72021\",\"1#72021\",\"1#72021\",\"1#72021\",\"1#72021\",\"2#72021\",\"2#72021\",\"2#72021\",\"2#72021\",\"2#72021\",\"2#72021\",\"2#72021\",\"2#72021\",\"2#72021\",\"2#72021\",\"3#72021\",\"3#72021\",\"3#72021\",\"3#72021\",\"3#72021\",\"3#72021\",\"3#72021\",\"3#72021\",\"3#72021\",\"3#72021\",\"3#72021\",\"3#72021\",\"3#72021\",\"3#72021\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"breaker_strike\",\"map_exchange\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"breaker_strike\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"hard_hat\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":33,\"heal_gained\":6,\"hp_after\":27,\"hp_before\":21,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"breaker_strike\",\"feat_name\":\"破封一击\",\"hp_after\":27,\"hp_before\":27,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"map_exchange\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":21,\"heal_gained\":4,\"hp_after\":41,\"hp_before\":37,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"map_exchange\",\"feat_name\":\"弃图兑换\",\"hp_after\":41,\"hp_before\":41,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":97,\"route\":\"min\",\"seal_hit\":352,\"seal_hit_gain\":1330,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":373,\"seed\":72021,\"shop_gp_seq\":[22,77,90],\"stopped_at\":0,\"taken\":160,\"visited_count\":31,\"wallet_count\":37}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":17,\"choices\":19,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,32,38,38,38,38,38,38],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":124,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#72022\",\"1#72022\",\"1#72022\",\"1#72022\",\"1#72022\",\"1#72022\",\"1#72022\",\"2#72022\",\"2#72022\",\"2#72022\",\"2#72022\",\"2#72022\",\"2#72022\",\"2#72022\",\"2#72022\",\"2#72022\",\"2#72022\",\"3#72022\",\"3#72022\",\"3#72022\",\"3#72022\",\"3#72022\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":2,\"relics\":[\"hard_hat\",\"frontier_miner\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"hard_hat\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":26,\"heal_gained\":1,\"hp_after\":32,\"hp_before\":31,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"hard_hat\",\"feat_name\":\"安全帽\",\"hp_after\":32,\"hp_before\":32,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":29,\"heal_gained\":7,\"hp_after\":37,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"frontier_miner\",\"feat_name\":\"边陲矿工\",\"hp_after\":37,\"hp_before\":37,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":81,\"route\":\"min\",\"seal_hit\":300,\"seal_hit_gain\":1123,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":329,\"seed\":72022,\"shop_gp_seq\":[17,73],\"stopped_at\":3,\"taken\":124,\"visited_count\":22,\"wallet_count\":29}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":17,\"choices\":19,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,38,38,38,38,38,38],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":115,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#72023\",\"1#72023\",\"1#72023\",\"1#72023\",\"1#72023\",\"1#72023\",\"1#72023\",\"2#72023\",\"2#72023\",\"2#72023\",\"2#72023\",\"2#72023\",\"2#72023\",\"2#72023\",\"2#72023\",\"2#72023\",\"2#72023\",\"3#72023\",\"3#72023\",\"3#72023\",\"3#72023\",\"3#72023\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"blood_honey\",\"executioner\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":11,\"heal_gained\":5,\"hp_after\":35,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"blood_honey\",\"feat_name\":\"血蜜\",\"hp_after\":35,\"hp_before\":35,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":31,\"heal_gained\":4,\"hp_after\":38,\"hp_before\":34,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"executioner\",\"feat_name\":\"处刑台\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":75,\"route\":\"min\",\"seal_hit\":286,\"seal_hit_gain\":643,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":304,\"seed\":72023,\"shop_gp_seq\":[34,48],\"stopped_at\":3,\"taken\":115,\"visited_count\":22,\"wallet_count\":29}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":20,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,38,38,38,38,38,38,38,38,41],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":75,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#72024\",\"1#72024\",\"1#72024\",\"1#72024\",\"1#72024\",\"1#72024\",\"1#72024\",\"2#72024\",\"2#72024\",\"2#72024\",\"2#72024\",\"2#72024\",\"2#72024\",\"2#72024\",\"2#72024\",\"2#72024\",\"2#72024\",\"3#72024\",\"3#72024\",\"3#72024\",\"3#72024\",\"3#72024\",\"3#72024\",\"3#72024\",\"3#72024\",\"3#72024\",\"3#72024\",\"3#72024\",\"3#72024\",\"3#72024\",\"3#72024\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":0,\"relics\":[\"seal_burst\",\"perfect_insurance\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"seal_burst\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":11,\"heal_gained\":7,\"hp_after\":32,\"hp_before\":25,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"seal_burst\",\"feat_name\":\"封印爆裂\",\"hp_after\":32,\"hp_before\":32,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"perfect_insurance\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":20,\"heal_gained\":2,\"hp_after\":38,\"hp_before\":36,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"perfect_insurance\",\"feat_name\":\"完美保险\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":55,\"route\":\"min\",\"seal_hit\":229,\"seal_hit_gain\":-27,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":256,\"seed\":72024,\"shop_gp_seq\":[34,42,59],\"stopped_at\":0,\"taken\":75,\"visited_count\":31,\"wallet_count\":35}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":23,\"choices\":26,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,32,38,38,38,38,38,38,38,38,38,38,38,47],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":150,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#72025\",\"1#72025\",\"1#72025\",\"1#72025\",\"1#72025\",\"1#72025\",\"1#72025\",\"2#72025\",\"2#72025\",\"2#72025\",\"2#72025\",\"2#72025\",\"2#72025\",\"2#72025\",\"2#72025\",\"2#72025\",\"2#72025\",\"3#72025\",\"3#72025\",\"3#72025\",\"3#72025\",\"3#72025\",\"3#72025\",\"3#72025\",\"3#72025\",\"3#72025\",\"3#72025\",\"3#72025\",\"3#72025\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":0,\"relics\":[\"field_dressing\",\"hard_hat\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":15,\"heal_gained\":6,\"hp_after\":29,\"hp_before\":23,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"field_dressing\",\"feat_name\":\"战地包扎\",\"hp_after\":29,\"hp_before\":29,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"hard_hat\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":27,\"heal_gained\":0,\"hp_after\":38,\"hp_before\":38,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"hard_hat\",\"feat_name\":\"安全帽\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":103,\"route\":\"min\",\"seal_hit\":408,\"seal_hit_gain\":2142,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":435,\"seed\":72025,\"shop_gp_seq\":[22,64,116],\"stopped_at\":0,\"taken\":150,\"visited_count\":29,\"wallet_count\":42}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":15,\"choices\":17,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,38,38,38,38],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":121,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#72026\",\"1#72026\",\"1#72026\",\"1#72026\",\"1#72026\",\"1#72026\",\"1#72026\",\"2#72026\",\"2#72026\",\"2#72026\",\"2#72026\",\"2#72026\",\"2#72026\",\"2#72026\",\"2#72026\",\"2#72026\",\"2#72026\",\"3#72026\",\"3#72026\",\"3#72026\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"frontier_miner\",\"perfect_insurance\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":15,\"heal_gained\":7,\"hp_after\":23,\"hp_before\":16,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"frontier_miner\",\"feat_name\":\"边陲矿工\",\"hp_after\":23,\"hp_before\":23,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"perfect_insurance\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"trap_instinct\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":26,\"heal_gained\":3,\"hp_after\":38,\"hp_before\":35,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"perfect_insurance\",\"feat_name\":\"完美保险\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":64,\"route\":\"min\",\"seal_hit\":227,\"seal_hit_gain\":370,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":245,\"seed\":72026,\"shop_gp_seq\":[35,51],\"stopped_at\":3,\"taken\":121,\"visited_count\":20,\"wallet_count\":27}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":18,\"choices\":21,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,32,38,38,38,38,38,38,38],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":125,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#72027\",\"1#72027\",\"1#72027\",\"1#72027\",\"1#72027\",\"1#72027\",\"1#72027\",\"2#72027\",\"2#72027\",\"2#72027\",\"2#72027\",\"2#72027\",\"2#72027\",\"2#72027\",\"2#72027\",\"2#72027\",\"2#72027\",\"3#72027\",\"3#72027\",\"3#72027\",\"3#72027\",\"3#72027\",\"3#72027\",\"3#72027\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":0,\"relics\":[\"geologist\",\"last_drop\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":20,\"heal_gained\":2,\"hp_after\":32,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"geologist\",\"feat_name\":\"地质学家\",\"hp_after\":32,\"hp_before\":32,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":24,\"heal_gained\":6,\"hp_after\":38,\"hp_before\":32,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"last_drop\",\"feat_name\":\"一滴不剩\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":78,\"route\":\"min\",\"seal_hit\":277,\"seal_hit_gain\":1113,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":301,\"seed\":72027,\"shop_gp_seq\":[23,55],\"stopped_at\":3,\"taken\":125,\"visited_count\":24,\"wallet_count\":29}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":15,\"choices\":17,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,38,38,38,38],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":102,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#72028\",\"1#72028\",\"1#72028\",\"1#72028\",\"1#72028\",\"1#72028\",\"1#72028\",\"2#72028\",\"2#72028\",\"2#72028\",\"2#72028\",\"2#72028\",\"2#72028\",\"2#72028\",\"2#72028\",\"2#72028\",\"2#72028\",\"3#72028\",\"3#72028\",\"3#72028\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":0,\"relics\":[\"executioner\",\"seal_echo\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":14,\"heal_gained\":7,\"hp_after\":28,\"hp_before\":21,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"executioner\",\"feat_name\":\"处刑台\",\"hp_after\":28,\"hp_before\":28,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"seal_echo\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"breaker_strike\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":27,\"heal_gained\":4,\"hp_after\":38,\"hp_before\":34,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"seal_echo\",\"feat_name\":\"封印回响\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":67,\"route\":\"min\",\"seal_hit\":244,\"seal_hit_gain\":1907,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":262,\"seed\":72028,\"shop_gp_seq\":[26,50],\"stopped_at\":3,\"taken\":102,\"visited_count\":20,\"wallet_count\":27}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,32,38,38,38,38,38,38,38,44],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":124,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#72029\",\"1#72029\",\"1#72029\",\"1#72029\",\"1#72029\",\"1#72029\",\"1#72029\",\"2#72029\",\"2#72029\",\"2#72029\",\"2#72029\",\"2#72029\",\"2#72029\",\"2#72029\",\"2#72029\",\"2#72029\",\"2#72029\",\"3#72029\",\"3#72029\",\"3#72029\",\"3#72029\",\"3#72029\",\"3#72029\",\"3#72029\",\"3#72029\",\"3#72029\",\"3#72029\",\"3#72029\",\"3#72029\",\"3#72029\",\"3#72029\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":0,\"relics\":[\"frontier_miner\",\"warning_beacon\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":22,\"heal_gained\":2,\"hp_after\":32,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"frontier_miner\",\"feat_name\":\"边陲矿工\",\"hp_after\":32,\"hp_before\":32,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":22,\"heal_gained\":7,\"hp_after\":37,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"warning_beacon\",\"feat_name\":\"示警烟火\",\"hp_after\":37,\"hp_before\":37,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":87,\"route\":\"min\",\"seal_hit\":345,\"seal_hit_gain\":1813,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":369,\"seed\":72029,\"shop_gp_seq\":[13,77,90],\"stopped_at\":0,\"taken\":124,\"visited_count\":31,\"wallet_count\":36}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,41,41,41,41,41,41,41,47],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":125,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#72030\",\"1#72030\",\"1#72030\",\"1#72030\",\"1#72030\",\"1#72030\",\"1#72030\",\"2#72030\",\"2#72030\",\"2#72030\",\"2#72030\",\"2#72030\",\"2#72030\",\"2#72030\",\"2#72030\",\"2#72030\",\"2#72030\",\"3#72030\",\"3#72030\",\"3#72030\",\"3#72030\",\"3#72030\",\"3#72030\",\"3#72030\",\"3#72030\",\"3#72030\",\"3#72030\",\"3#72030\",\"3#72030\",\"3#72030\",\"3#72030\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"executioner\",\"trap_instinct\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":16,\"heal_gained\":7,\"hp_after\":34,\"hp_before\":27,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"executioner\",\"feat_name\":\"处刑台\",\"hp_after\":34,\"hp_before\":34,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"trap_instinct\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":30,\"heal_gained\":2,\"hp_after\":41,\"hp_before\":39,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"trap_instinct\",\"feat_name\":\"陷阱本能\",\"hp_after\":41,\"hp_before\":41,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":92,\"route\":\"min\",\"seal_hit\":335,\"seal_hit_gain\":1855,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":358,\"seed\":72030,\"shop_gp_seq\":[32,90,103],\"stopped_at\":0,\"taken\":125,\"visited_count\":31,\"wallet_count\":39}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":16,\"choices\":18,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,32,38,38,38,38,38],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":121,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#72031\",\"1#72031\",\"1#72031\",\"1#72031\",\"1#72031\",\"1#72031\",\"1#72031\",\"2#72031\",\"2#72031\",\"2#72031\",\"2#72031\",\"2#72031\",\"2#72031\",\"2#72031\",\"2#72031\",\"2#72031\",\"2#72031\",\"3#72031\",\"3#72031\",\"3#72031\",\"3#72031\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":2,\"relics\":[\"hard_hat\",\"perfect_insurance\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"hard_hat\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":17,\"heal_gained\":1,\"hp_after\":32,\"hp_before\":31,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"hard_hat\",\"feat_name\":\"安全帽\",\"hp_after\":32,\"hp_before\":32,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"perfect_insurance\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":23,\"heal_gained\":7,\"hp_after\":35,\"hp_before\":28,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"perfect_insurance\",\"feat_name\":\"完美保险\",\"hp_after\":35,\"hp_before\":35,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":72,\"route\":\"min\",\"seal_hit\":249,\"seal_hit_gain\":1056,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":273,\"seed\":72031,\"shop_gp_seq\":[20,50],\"stopped_at\":3,\"taken\":121,\"visited_count\":21,\"wallet_count\":27}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,32,38,38,38,38,38,38,38,44],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":176,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#72032\",\"1#72032\",\"1#72032\",\"1#72032\",\"1#72032\",\"1#72032\",\"1#72032\",\"2#72032\",\"2#72032\",\"2#72032\",\"2#72032\",\"2#72032\",\"2#72032\",\"2#72032\",\"2#72032\",\"2#72032\",\"2#72032\",\"3#72032\",\"3#72032\",\"3#72032\",\"3#72032\",\"3#72032\",\"3#72032\",\"3#72032\",\"3#72032\",\"3#72032\",\"3#72032\",\"3#72032\",\"3#72032\",\"3#72032\",\"3#72032\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":3,\"relics\":[\"seal_echo\",\"executioner\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"seal_echo\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":18,\"heal_gained\":6,\"hp_after\":22,\"hp_before\":16,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"seal_echo\",\"feat_name\":\"封印回响\",\"hp_after\":22,\"hp_before\":22,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":27,\"heal_gained\":5,\"hp_after\":38,\"hp_before\":33,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"executioner\",\"feat_name\":\"处刑台\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":104,\"route\":\"min\",\"seal_hit\":406,\"seal_hit_gain\":523,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":434,\"seed\":72032,\"shop_gp_seq\":[12,58,112],\"stopped_at\":0,\"taken\":176,\"visited_count\":31,\"wallet_count\":36}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":12,\"choices\":15,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,38],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":105,\"hp_pct\":0,\"keystone_total\":3,\"level_maps\":[\"1#72033\",\"1#72033\",\"1#72033\",\"1#72033\",\"1#72033\",\"1#72033\",\"1#72033\",\"2#72033\",\"2#72033\",\"2#72033\",\"2#72033\",\"2#72033\",\"2#72033\",\"2#72033\",\"2#72033\",\"2#72033\",\"2#72033\"],\"levels_cleared\":1,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":0,\"relics\":[\"geologist\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":13,\"heal_gained\":7,\"hp_after\":34,\"hp_before\":27,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"geologist\",\"feat_name\":\"地质学家\",\"hp_after\":34,\"hp_before\":34,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":51,\"route\":\"min\",\"seal_hit\":181,\"seal_hit_gain\":512,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":195,\"seed\":72033,\"shop_gp_seq\":[36,46],\"stopped_at\":2,\"taken\":105,\"visited_count\":17,\"wallet_count\":24}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":23,\"choices\":26,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,32,41,41,41,41,41,41,41,41,41,41,41,47],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":127,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#72034\",\"1#72034\",\"1#72034\",\"1#72034\",\"1#72034\",\"1#72034\",\"1#72034\",\"2#72034\",\"2#72034\",\"2#72034\",\"2#72034\",\"2#72034\",\"2#72034\",\"2#72034\",\"2#72034\",\"2#72034\",\"2#72034\",\"3#72034\",\"3#72034\",\"3#72034\",\"3#72034\",\"3#72034\",\"3#72034\",\"3#72034\",\"3#72034\",\"3#72034\",\"3#72034\",\"3#72034\",\"3#72034\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":0,\"relics\":[\"map_exchange\",\"seal_burst\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"map_exchange\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":11,\"heal_gained\":6,\"hp_after\":32,\"hp_before\":26,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"map_exchange\",\"feat_name\":\"弃图兑换\",\"hp_after\":32,\"hp_before\":32,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"chain_traps\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"seal_burst\",\"kind\":\"relic\",\"tier\":\"rare\"}],\"gp\":23,\"heal_gained\":7,\"hp_after\":41,\"hp_before\":34,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"seal_burst\",\"feat_name\":\"封印爆裂\",\"hp_after\":41,\"hp_before\":41,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":80,\"route\":\"min\",\"seal_hit\":312,\"seal_hit_gain\":2332,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":350,\"seed\":72034,\"shop_gp_seq\":[25,72,101],\"stopped_at\":0,\"taken\":127,\"visited_count\":29,\"wallet_count\":42}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":17,\"choices\":19,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,41,41,41,41,41,41],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":125,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#72035\",\"1#72035\",\"1#72035\",\"1#72035\",\"1#72035\",\"1#72035\",\"1#72035\",\"2#72035\",\"2#72035\",\"2#72035\",\"2#72035\",\"2#72035\",\"2#72035\",\"2#72035\",\"2#72035\",\"2#72035\",\"2#72035\",\"3#72035\",\"3#72035\",\"3#72035\",\"3#72035\",\"3#72035\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"seal_echo\",\"perfect_insurance\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"seal_echo\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":12,\"heal_gained\":7,\"hp_after\":32,\"hp_before\":25,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"seal_echo\",\"feat_name\":\"封印回响\",\"hp_after\":32,\"hp_before\":32,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"perfect_insurance\",\"kind\":\"relic\",\"tier\":\"rare\"}],\"gp\":23,\"heal_gained\":1,\"hp_after\":41,\"hp_before\":40,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"perfect_insurance\",\"feat_name\":\"完美保险\",\"hp_after\":41,\"hp_before\":41,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":73,\"route\":\"min\",\"seal_hit\":281,\"seal_hit_gain\":2343,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":308,\"seed\":72035,\"shop_gp_seq\":[28,71],\"stopped_at\":3,\"taken\":125,\"visited_count\":22,\"wallet_count\":30}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,38,38,38,38,38,38,38,38,44],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":137,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#72036\",\"1#72036\",\"1#72036\",\"1#72036\",\"1#72036\",\"1#72036\",\"1#72036\",\"2#72036\",\"2#72036\",\"2#72036\",\"2#72036\",\"2#72036\",\"2#72036\",\"2#72036\",\"2#72036\",\"2#72036\",\"2#72036\",\"3#72036\",\"3#72036\",\"3#72036\",\"3#72036\",\"3#72036\",\"3#72036\",\"3#72036\",\"3#72036\",\"3#72036\",\"3#72036\",\"3#72036\",\"3#72036\",\"3#72036\",\"3#72036\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"stone_skin\",\"executioner\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"stone_skin\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":19,\"heal_gained\":4,\"hp_after\":35,\"hp_before\":31,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"stone_skin\",\"feat_name\":\"石肤\",\"hp_after\":35,\"hp_before\":35,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":21,\"heal_gained\":7,\"hp_after\":36,\"hp_before\":29,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"executioner\",\"feat_name\":\"处刑台\",\"hp_after\":36,\"hp_before\":36,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":97,\"route\":\"min\",\"seal_hit\":358,\"seal_hit_gain\":948,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":385,\"seed\":72036,\"shop_gp_seq\":[38,48,70],\"stopped_at\":0,\"taken\":137,\"visited_count\":31,\"wallet_count\":35}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":23,\"choices\":26,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,32,32,32,32,32,32,38,38,38,38,38,38,38,38,38,38,38,47],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":108,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#72037\",\"1#72037\",\"1#72037\",\"1#72037\",\"1#72037\",\"1#72037\",\"1#72037\",\"2#72037\",\"2#72037\",\"2#72037\",\"2#72037\",\"2#72037\",\"2#72037\",\"2#72037\",\"2#72037\",\"2#72037\",\"2#72037\",\"3#72037\",\"3#72037\",\"3#72037\",\"3#72037\",\"3#72037\",\"3#72037\",\"3#72037\",\"3#72037\",\"3#72037\",\"3#72037\",\"3#72037\",\"3#72037\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"market_close\",\"blood_tax\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":6,\"candidates\":[{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":18,\"heal_gained\":6,\"hp_after\":22,\"hp_before\":16,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"market_close\",\"feat_name\":\"收盘\",\"hp_after\":22,\"hp_before\":22,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":12,\"candidates\":[{\"feat_id\":\"blood_tax\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"skip_mining\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"map_exchange\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":17,\"heal_gained\":2,\"hp_after\":38,\"hp_before\":36,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"blood_tax\",\"feat_name\":\"血税\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":86,\"route\":\"min\",\"seal_hit\":346,\"seal_hit_gain\":1916,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":387,\"seed\":72037,\"shop_gp_seq\":[17,70,125],\"stopped_at\":0,\"taken\":108,\"visited_count\":29,\"wallet_count\":42}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":20,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,41,41,41,41,41,41,41,41,47],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":159,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#72038\",\"1#72038\",\"1#72038\",\"1#72038\",\"1#72038\",\"1#72038\",\"1#72038\",\"2#72038\",\"2#72038\",\"2#72038\",\"2#72038\",\"2#72038\",\"2#72038\",\"2#72038\",\"2#72038\",\"2#72038\",\"2#72038\",\"3#72038\",\"3#72038\",\"3#72038\",\"3#72038\",\"3#72038\",\"3#72038\",\"3#72038\",\"3#72038\",\"3#72038\",\"3#72038\",\"3#72038\",\"3#72038\",\"3#72038\",\"3#72038\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"map_exchange\",\"skip_mining\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"map_exchange\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":10,\"heal_gained\":5,\"hp_after\":35,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"map_exchange\",\"feat_name\":\"弃图兑换\",\"hp_after\":35,\"hp_before\":35,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"skip_mining\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":22,\"heal_gained\":6,\"hp_after\":41,\"hp_before\":35,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"skip_mining\",\"feat_name\":\"跳格采掘\",\"hp_after\":41,\"hp_before\":41,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":97,\"route\":\"min\",\"seal_hit\":378,\"seal_hit_gain\":2246,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":401,\"seed\":72038,\"shop_gp_seq\":[32,55,100],\"stopped_at\":0,\"taken\":159,\"visited_count\":31,\"wallet_count\":39}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"on\",\"battles\":15,\"choices\":17,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,35,35,35,35,35,35,41,41,41,41],\"equipped\":[\"morning_order\",\"wide_scout_band\",\"trial_pick\"],\"feat_mode\":\"off\",\"hero_dmg\":100,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#72039\",\"1#72039\",\"1#72039\",\"1#72039\",\"1#72039\",\"1#72039\",\"1#72039\",\"2#72039\",\"2#72039\",\"2#72039\",\"2#72039\",\"2#72039\",\"2#72039\",\"2#72039\",\"2#72039\",\"2#72039\",\"2#72039\",\"3#72039\",\"3#72039\",\"3#72039\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"refreshes\":1,\"relics\":[\"geologist\",\"pain_memory\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":9,\"candidates\":[{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":20,\"heal_gained\":6,\"hp_after\":35,\"hp_before\":29,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"geologist\",\"feat_name\":\"地质学家\",\"hp_after\":35,\"hp_before\":35,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"stone_skin\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"pain_memory\",\"kind\":\"relic\",\"tier\":\"rare\"}],\"gp\":29,\"heal_gained\":6,\"hp_after\":41,\"hp_before\":35,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"pain_memory\",\"feat_name\":\"疼痛记忆\",\"hp_after\":41,\"hp_before\":41,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":62,\"route\":\"min\",\"seal_hit\":224,\"seal_hit_gain\":711,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":244,\"seed\":72039,\"shop_gp_seq\":[32,63],\"stopped_at\":3,\"taken\":100,\"visited_count\":20,\"wallet_count\":28}"
],
"jsonl": {
"normal": {
"sha256": "b732bfeda9521de4896ebc11936670bd015032131bc871036896c1761ded12c8",
"rows": 9670
},
"perfect": {
"sha256": "06cb4dcc26c56343c61bc8d972238be81b88e3132d7e9b274399f4cfd04cb56e",
"rows": 62765
}
}
},
"shop": {
"runsum": [
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":3,\"choices\":2,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":9,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73000\",\"1#73000\",\"1#73000\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":4,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":5,\"route\":\"min\",\"seal_hit\":19,\"seal_hit_gain\":-30,\"seal_miss\":5,\"seal_miss_penalty\":8,\"seal_placed\":27,\"seed\":73000,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":17,\"visited_count\":3,\"wallet_count\":4}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":5,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":19,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73001\",\"1#73001\",\"1#73001\",\"1#73001\",\"1#73001\"],\"levels_cleared\":0,\"mine_dmg\":0,\"mine_hits\":6,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":15,\"route\":\"min\",\"seal_hit\":43,\"seal_hit_gain\":437,\"seal_miss\":8,\"seal_miss_penalty\":6,\"seal_placed\":56,\"seed\":73001,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":19,\"visited_count\":5,\"wallet_count\":6}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":5,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":15,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73002\",\"1#73002\",\"1#73002\",\"1#73002\",\"1#73002\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":6,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":9,\"route\":\"min\",\"seal_hit\":35,\"seal_hit_gain\":-120,\"seal_miss\":6,\"seal_miss_penalty\":8,\"seal_placed\":46,\"seed\":73002,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":23,\"visited_count\":5,\"wallet_count\":6}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":3,\"choices\":2,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":6,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73003\",\"1#73003\",\"1#73003\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":3,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":5,\"route\":\"min\",\"seal_hit\":16,\"seal_hit_gain\":-158,\"seal_miss\":4,\"seal_miss_penalty\":14,\"seal_placed\":22,\"seed\":73003,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":14,\"visited_count\":3,\"wallet_count\":4}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":4,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":13,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73004\",\"1#73004\",\"1#73004\",\"1#73004\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":8,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":10,\"route\":\"min\",\"seal_hit\":30,\"seal_hit_gain\":-37,\"seal_miss\":8,\"seal_miss_penalty\":8,\"seal_placed\":47,\"seed\":73004,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":29,\"visited_count\":4,\"wallet_count\":5}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":5,\"choices\":4,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":20,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73005\",\"1#73005\",\"1#73005\",\"1#73005\",\"1#73005\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":5,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":14,\"route\":\"min\",\"seal_hit\":36,\"seal_hit_gain\":596,\"seal_miss\":3,\"seal_miss_penalty\":6,\"seal_placed\":43,\"seed\":73005,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":28,\"visited_count\":5,\"wallet_count\":6}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":6,\"choices\":6,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,41],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":19,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73006\",\"1#73006\",\"1#73006\",\"1#73006\",\"1#73006\",\"1#73006\",\"1#73006\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":12,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":19},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":21,\"route\":\"min\",\"seal_hit\":61,\"seal_hit_gain\":393,\"seal_miss\":14,\"seal_miss_penalty\":22,\"seal_placed\":81,\"seed\":73006,\"shop_buys\":3,\"shop_gp_seq\":[36],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":27,\"visited_count\":7,\"wallet_count\":10}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":3,\"choices\":2,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":9,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73007\",\"1#73007\",\"1#73007\"],\"levels_cleared\":0,\"mine_dmg\":0,\"mine_hits\":5,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":6,\"route\":\"min\",\"seal_hit\":20,\"seal_hit_gain\":-92,\"seal_miss\":5,\"seal_miss_penalty\":22,\"seal_placed\":25,\"seed\":73007,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":9,\"visited_count\":3,\"wallet_count\":4}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":5,\"choices\":6,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,31],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":25,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73008\",\"1#73008\",\"1#73008\",\"1#73008\",\"1#73008\",\"1#73008\",\"1#73008\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":7,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":21,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":21},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":21,\"route\":\"min\",\"seal_hit\":63,\"seal_hit_gain\":398,\"seal_miss\":8,\"seal_miss_penalty\":14,\"seal_placed\":73,\"seed\":73008,\"shop_buys\":2,\"shop_gp_seq\":[23],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":41,\"visited_count\":7,\"wallet_count\":8}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":4,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":23,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73009\",\"1#73009\",\"1#73009\",\"1#73009\"],\"levels_cleared\":0,\"mine_dmg\":0,\"mine_hits\":2,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":11,\"route\":\"min\",\"seal_hit\":34,\"seal_hit_gain\":4,\"seal_miss\":3,\"seal_miss_penalty\":6,\"seal_placed\":40,\"seed\":73009,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":23,\"visited_count\":4,\"wallet_count\":5}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":3,\"choices\":2,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":11,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73010\",\"1#73010\",\"1#73010\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":7,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":10,\"route\":\"min\",\"seal_hit\":25,\"seal_hit_gain\":469,\"seal_miss\":6,\"seal_miss_penalty\":14,\"seal_placed\":37,\"seed\":73010,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":19,\"visited_count\":3,\"wallet_count\":4}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":7,\"choices\":7,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":39,\"hp_pct\":0,\"keystone_total\":3,\"level_maps\":[\"1#73011\",\"1#73011\",\"1#73011\",\"1#73011\",\"1#73011\",\"1#73011\",\"1#73011\",\"2#73011\",\"2#73011\"],\"levels_cleared\":1,\"mine_dmg\":8,\"mine_hits\":6,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":21,\"attack\":22,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[\"blood_honey\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":0,\"candidates\":[{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":17,\"heal_gained\":5,\"hp_after\":21,\"hp_before\":16,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"blood_honey\",\"feat_name\":\"血蜜\",\"hp_after\":21,\"hp_before\":21,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":28,\"route\":\"min\",\"seal_hit\":79,\"seal_hit_gain\":0,\"seal_miss\":6,\"seal_miss_penalty\":16,\"seal_placed\":89,\"seed\":73011,\"shop_buys\":2,\"shop_gp_seq\":[17],\"shop_mode\":\"pool\",\"stopped_at\":2,\"taken\":47,\"visited_count\":9,\"wallet_count\":10}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":5,\"choices\":6,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,31],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":23,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73012\",\"1#73012\",\"1#73012\",\"1#73012\",\"1#73012\",\"1#73012\",\"1#73012\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":8,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":21},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":21,\"route\":\"min\",\"seal_hit\":57,\"seal_hit_gain\":-348,\"seal_miss\":10,\"seal_miss_penalty\":4,\"seal_placed\":70,\"seed\":73012,\"shop_buys\":1,\"shop_gp_seq\":[10],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":39,\"visited_count\":7,\"wallet_count\":7}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":1,\"choices\":0,\"cleared\":false,\"encounter_max_hp_seq\":[26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":4,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73013\"],\"levels_cleared\":0,\"mine_dmg\":14,\"mine_hits\":4,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":2,\"route\":\"min\",\"seal_hit\":7,\"seal_hit_gain\":114,\"seal_miss\":2,\"seal_miss_penalty\":14,\"seal_placed\":9,\"seed\":73013,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":18,\"visited_count\":1,\"wallet_count\":2}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":2,\"choices\":1,\"cleared\":false,\"encounter_max_hp_seq\":[26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":4,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73014\",\"1#73014\"],\"levels_cleared\":0,\"mine_dmg\":22,\"mine_hits\":4,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":3,\"route\":\"min\",\"seal_hit\":10,\"seal_hit_gain\":240,\"seal_miss\":3,\"seal_miss_penalty\":0,\"seal_placed\":15,\"seed\":73014,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":26,\"visited_count\":2,\"wallet_count\":3}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":4,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":19,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73015\",\"1#73015\",\"1#73015\",\"1#73015\"],\"levels_cleared\":0,\"mine_dmg\":8,\"mine_hits\":8,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":8,\"route\":\"min\",\"seal_hit\":24,\"seal_hit_gain\":127,\"seal_miss\":6,\"seal_miss_penalty\":12,\"seal_placed\":32,\"seed\":73015,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":27,\"visited_count\":4,\"wallet_count\":5}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":3,\"choices\":2,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":14,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73016\",\"1#73016\",\"1#73016\"],\"levels_cleared\":0,\"mine_dmg\":14,\"mine_hits\":5,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":10,\"route\":\"min\",\"seal_hit\":20,\"seal_hit_gain\":86,\"seal_miss\":4,\"seal_miss_penalty\":-2,\"seal_placed\":26,\"seed\":73016,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":28,\"visited_count\":3,\"wallet_count\":4}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":2,\"choices\":1,\"cleared\":false,\"encounter_max_hp_seq\":[26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":5,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73017\",\"1#73017\"],\"levels_cleared\":0,\"mine_dmg\":16,\"mine_hits\":3,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":4,\"route\":\"min\",\"seal_hit\":10,\"seal_hit_gain\":320,\"seal_miss\":2,\"seal_miss_penalty\":8,\"seal_placed\":14,\"seed\":73017,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":21,\"visited_count\":2,\"wallet_count\":3}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":4,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":13,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73018\",\"1#73018\",\"1#73018\",\"1#73018\"],\"levels_cleared\":0,\"mine_dmg\":0,\"mine_hits\":3,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":10,\"route\":\"min\",\"seal_hit\":24,\"seal_hit_gain\":509,\"seal_miss\":5,\"seal_miss_penalty\":20,\"seal_placed\":33,\"seed\":73018,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":13,\"visited_count\":4,\"wallet_count\":5}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":4,\"choices\":3,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":17,\"hp_pct\":0,\"keystone_total\":0,\"level_maps\":[\"1#73019\",\"1#73019\",\"1#73019\",\"1#73019\"],\"levels_cleared\":0,\"mine_dmg\":14,\"mine_hits\":2,\"policy\":\"normal\",\"pool_stock\":{\"ap\":3,\"armor\":22,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[],\"result\":\"failed\",\"reward_events\":[],\"rounds\":8,\"route\":\"min\",\"seal_hit\":26,\"seal_hit_gain\":387,\"seal_miss\":3,\"seal_miss_penalty\":6,\"seal_placed\":35,\"seed\":73019,\"shop_buys\":0,\"shop_gp_seq\":[],\"shop_mode\":\"pool\",\"stopped_at\":1,\"taken\":31,\"visited_count\":4,\"wallet_count\":5}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":17,\"choices\":19,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,26,26,26,26,26,26,36,36,36,36,36,36],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":131,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73020\",\"1#73020\",\"1#73020\",\"1#73020\",\"1#73020\",\"1#73020\",\"1#73020\",\"2#73020\",\"2#73020\",\"2#73020\",\"2#73020\",\"2#73020\",\"2#73020\",\"2#73020\",\"2#73020\",\"2#73020\",\"2#73020\",\"3#73020\",\"3#73020\",\"3#73020\",\"3#73020\",\"3#73020\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":20,\"attack\":20,\"feat_ticket\":0,\"heal\":3,\"max_hp\":20},\"refreshes\":0,\"relics\":[\"executioner\",\"field_dressing\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":0,\"candidates\":[{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"chain_traps\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":30,\"heal_gained\":5,\"hp_after\":21,\"hp_before\":16,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"executioner\",\"feat_name\":\"处刑台\",\"hp_after\":21,\"hp_before\":21,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":10,\"candidates\":[{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":19,\"heal_gained\":7,\"hp_after\":35,\"hp_before\":28,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"field_dressing\",\"feat_name\":\"战地包扎\",\"hp_after\":35,\"hp_before\":35,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":79,\"route\":\"min\",\"seal_hit\":263,\"seal_hit_gain\":1207,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":278,\"seed\":73020,\"shop_buys\":7,\"shop_gp_seq\":[43,60],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":131,\"visited_count\":22,\"wallet_count\":25}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":16,\"choices\":18,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,31,31,31,31,31,31,36,36,36,36,36],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":141,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73021\",\"1#73021\",\"1#73021\",\"1#73021\",\"1#73021\",\"1#73021\",\"1#73021\",\"2#73021\",\"2#73021\",\"2#73021\",\"2#73021\",\"2#73021\",\"2#73021\",\"2#73021\",\"2#73021\",\"2#73021\",\"2#73021\",\"3#73021\",\"3#73021\",\"3#73021\",\"3#73021\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":20,\"attack\":21,\"feat_ticket\":0,\"heal\":3,\"max_hp\":20},\"refreshes\":2,\"relics\":[\"skip_mining\",\"venom_edge\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":5,\"candidates\":[{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"skip_mining\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":24,\"heal_gained\":1,\"hp_after\":31,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"skip_mining\",\"feat_name\":\"跳格采掘\",\"hp_after\":31,\"hp_before\":31,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":10,\"candidates\":[{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"venom_edge\",\"kind\":\"relic\",\"tier\":\"rare\"}],\"gp\":40,\"heal_gained\":6,\"hp_after\":36,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"venom_edge\",\"feat_name\":\"淬毒刃纹\",\"hp_after\":36,\"hp_before\":36,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":83,\"route\":\"min\",\"seal_hit\":291,\"seal_hit_gain\":2396,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":316,\"seed\":73021,\"shop_buys\":6,\"shop_gp_seq\":[22,79],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":141,\"visited_count\":21,\"wallet_count\":23}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":21,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,31,31,31,31,31,31,41,41,41,41,41,41,41,41],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":141,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73022\",\"1#73022\",\"1#73022\",\"1#73022\",\"1#73022\",\"1#73022\",\"1#73022\",\"2#73022\",\"2#73022\",\"2#73022\",\"2#73022\",\"2#73022\",\"2#73022\",\"2#73022\",\"2#73022\",\"2#73022\",\"2#73022\",\"3#73022\",\"3#73022\",\"3#73022\",\"3#73022\",\"3#73022\",\"3#73022\",\"3#73022\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":19,\"attack\":22,\"feat_ticket\":0,\"heal\":3,\"max_hp\":20},\"refreshes\":2,\"relics\":[\"breaker_strike\",\"trap_instinct\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":5,\"candidates\":[{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"breaker_strike\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":23,\"heal_gained\":6,\"hp_after\":23,\"hp_before\":17,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"breaker_strike\",\"feat_name\":\"破封一击\",\"hp_after\":23,\"hp_before\":23,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"trap_instinct\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":26,\"heal_gained\":8,\"hp_after\":39,\"hp_before\":31,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"trap_instinct\",\"feat_name\":\"陷阱本能\",\"hp_after\":39,\"hp_before\":39,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":101,\"route\":\"min\",\"seal_hit\":359,\"seal_hit_gain\":1765,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":374,\"seed\":73022,\"shop_buys\":6,\"shop_gp_seq\":[17,63],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":141,\"visited_count\":24,\"wallet_count\":26}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":21,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,26,26,26,26,26,26,36,36,36,36,36,36,36,36],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":152,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73023\",\"1#73023\",\"1#73023\",\"1#73023\",\"1#73023\",\"1#73023\",\"1#73023\",\"2#73023\",\"2#73023\",\"2#73023\",\"2#73023\",\"2#73023\",\"2#73023\",\"2#73023\",\"2#73023\",\"2#73023\",\"2#73023\",\"3#73023\",\"3#73023\",\"3#73023\",\"3#73023\",\"3#73023\",\"3#73023\",\"3#73023\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":20,\"attack\":19,\"feat_ticket\":0,\"heal\":3,\"max_hp\":21},\"refreshes\":0,\"relics\":[\"map_exchange\",\"perfect_insurance\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":0,\"candidates\":[{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"map_exchange\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":21,\"heal_gained\":5,\"hp_after\":21,\"hp_before\":16,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"map_exchange\",\"feat_name\":\"弃图兑换\",\"hp_after\":21,\"hp_before\":21,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":10,\"candidates\":[{\"feat_id\":\"seal_echo\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"perfect_insurance\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":29,\"heal_gained\":7,\"hp_after\":35,\"hp_before\":28,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"perfect_insurance\",\"feat_name\":\"完美保险\",\"hp_after\":35,\"hp_before\":35,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":87,\"route\":\"min\",\"seal_hit\":285,\"seal_hit_gain\":1841,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":308,\"seed\":73023,\"shop_buys\":7,\"shop_gp_seq\":[30,71],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":152,\"visited_count\":24,\"wallet_count\":27}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":16,\"choices\":19,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,36,36,36,36,36,46,46,46,46,46,46],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":100,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73024\",\"1#73024\",\"1#73024\",\"1#73024\",\"1#73024\",\"1#73024\",\"1#73024\",\"2#73024\",\"2#73024\",\"2#73024\",\"2#73024\",\"2#73024\",\"2#73024\",\"2#73024\",\"2#73024\",\"2#73024\",\"2#73024\",\"3#73024\",\"3#73024\",\"3#73024\",\"3#73024\",\"3#73024\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":21,\"attack\":20,\"feat_ticket\":0,\"heal\":3,\"max_hp\":19},\"refreshes\":2,\"relics\":[\"blood_honey\",\"seal_burst\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":10,\"candidates\":[{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":9,\"heal_gained\":7,\"hp_after\":36,\"hp_before\":29,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"blood_honey\",\"feat_name\":\"血蜜\",\"hp_after\":36,\"hp_before\":36,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":20,\"candidates\":[{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"seal_burst\",\"kind\":\"relic\",\"tier\":\"rare\"}],\"gp\":11,\"heal_gained\":9,\"hp_after\":32,\"hp_before\":23,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"seal_burst\",\"feat_name\":\"封印爆裂\",\"hp_after\":32,\"hp_before\":32,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":65,\"route\":\"min\",\"seal_hit\":218,\"seal_hit_gain\":416,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":241,\"seed\":73024,\"shop_buys\":7,\"shop_gp_seq\":[25,55],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":100,\"visited_count\":22,\"wallet_count\":24}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,36,36,36,36,36,36,41,41,41,41,41,41,41,51],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":157,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#73025\",\"1#73025\",\"1#73025\",\"1#73025\",\"1#73025\",\"1#73025\",\"1#73025\",\"2#73025\",\"2#73025\",\"2#73025\",\"2#73025\",\"2#73025\",\"2#73025\",\"2#73025\",\"2#73025\",\"2#73025\",\"2#73025\",\"3#73025\",\"3#73025\",\"3#73025\",\"3#73025\",\"3#73025\",\"3#73025\",\"3#73025\",\"3#73025\",\"3#73025\",\"3#73025\",\"3#73025\",\"3#73025\",\"3#73025\",\"3#73025\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":19,\"attack\":19,\"feat_ticket\":0,\"heal\":3,\"max_hp\":17},\"refreshes\":3,\"relics\":[\"last_stand\",\"seal_stake\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":10,\"candidates\":[{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"last_stand\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":26,\"heal_gained\":7,\"hp_after\":30,\"hp_before\":23,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"last_stand\",\"feat_name\":\"背水\",\"hp_after\":30,\"hp_before\":30,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":44,\"heal_gained\":8,\"hp_after\":38,\"hp_before\":30,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"seal_stake\",\"feat_name\":\"封印桩\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":118,\"route\":\"min\",\"seal_hit\":367,\"seal_hit_gain\":1643,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":385,\"seed\":73025,\"shop_buys\":12,\"shop_gp_seq\":[33,72,116],\"shop_mode\":\"pool\",\"stopped_at\":0,\"taken\":157,\"visited_count\":31,\"wallet_count\":32}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":15,\"choices\":17,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,36,36,36,36,36,36,36,36,36,36],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":140,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73026\",\"1#73026\",\"1#73026\",\"1#73026\",\"1#73026\",\"1#73026\",\"1#73026\",\"2#73026\",\"2#73026\",\"2#73026\",\"2#73026\",\"2#73026\",\"2#73026\",\"2#73026\",\"2#73026\",\"2#73026\",\"2#73026\",\"3#73026\",\"3#73026\",\"3#73026\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":19,\"attack\":21,\"feat_ticket\":0,\"heal\":3,\"max_hp\":20},\"refreshes\":1,\"relics\":[\"geologist\",\"seal_echo\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":10,\"candidates\":[{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":19,\"heal_gained\":7,\"hp_after\":31,\"hp_before\":24,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"geologist\",\"feat_name\":\"地质学家\",\"hp_after\":31,\"hp_before\":31,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":10,\"candidates\":[{\"feat_id\":\"seal_echo\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"trap_instinct\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":29,\"heal_gained\":7,\"hp_after\":23,\"hp_before\":16,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"seal_echo\",\"feat_name\":\"封印回响\",\"hp_after\":23,\"hp_before\":23,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":81,\"route\":\"min\",\"seal_hit\":267,\"seal_hit_gain\":1267,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":280,\"seed\":73026,\"shop_buys\":7,\"shop_gp_seq\":[29,72],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":140,\"visited_count\":20,\"wallet_count\":23}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":15,\"choices\":18,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,26,26,26,26,26,36,36,36,36,36],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":126,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73027\",\"1#73027\",\"1#73027\",\"1#73027\",\"1#73027\",\"1#73027\",\"1#73027\",\"2#73027\",\"2#73027\",\"2#73027\",\"2#73027\",\"2#73027\",\"2#73027\",\"2#73027\",\"2#73027\",\"2#73027\",\"2#73027\",\"3#73027\",\"3#73027\",\"3#73027\",\"3#73027\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":19,\"attack\":22,\"feat_ticket\":0,\"heal\":3,\"max_hp\":20},\"refreshes\":2,\"relics\":[\"last_drop\",\"executioner\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":0,\"candidates\":[{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":12,\"heal_gained\":1,\"hp_after\":26,\"hp_before\":25,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"last_drop\",\"feat_name\":\"一滴不剩\",\"hp_after\":26,\"hp_before\":26,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":10,\"candidates\":[{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":60,\"heal_gained\":7,\"hp_after\":33,\"hp_before\":26,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"executioner\",\"feat_name\":\"处刑台\",\"hp_after\":33,\"hp_before\":33,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":78,\"route\":\"min\",\"seal_hit\":276,\"seal_hit_gain\":954,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":289,\"seed\":73027,\"shop_buys\":6,\"shop_gp_seq\":[18,82],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":126,\"visited_count\":21,\"wallet_count\":22}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":15,\"choices\":17,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,31,31,31,31,31,31,31,31,31,31],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":127,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73028\",\"1#73028\",\"1#73028\",\"1#73028\",\"1#73028\",\"1#73028\",\"1#73028\",\"2#73028\",\"2#73028\",\"2#73028\",\"2#73028\",\"2#73028\",\"2#73028\",\"2#73028\",\"2#73028\",\"2#73028\",\"2#73028\",\"3#73028\",\"3#73028\",\"3#73028\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":18,\"attack\":21,\"feat_ticket\":0,\"heal\":3,\"max_hp\":21},\"refreshes\":0,\"relics\":[\"geologist\",\"market_close\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":5,\"candidates\":[{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":17,\"heal_gained\":5,\"hp_after\":31,\"hp_before\":26,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"geologist\",\"feat_name\":\"地质学家\",\"hp_after\":31,\"hp_before\":31,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":5,\"candidates\":[{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":30,\"heal_gained\":6,\"hp_after\":28,\"hp_before\":22,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"market_close\",\"feat_name\":\"收盘\",\"hp_after\":28,\"hp_before\":28,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":87,\"route\":\"min\",\"seal_hit\":225,\"seal_hit_gain\":1838,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":244,\"seed\":73028,\"shop_buys\":7,\"shop_gp_seq\":[27,67],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":127,\"visited_count\":20,\"wallet_count\":23}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":15,\"choices\":17,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,31,31,31,31,31,31,31,31,31,31],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":107,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73029\",\"1#73029\",\"1#73029\",\"1#73029\",\"1#73029\",\"1#73029\",\"1#73029\",\"2#73029\",\"2#73029\",\"2#73029\",\"2#73029\",\"2#73029\",\"2#73029\",\"2#73029\",\"2#73029\",\"2#73029\",\"2#73029\",\"3#73029\",\"3#73029\",\"3#73029\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":18,\"attack\":21,\"feat_ticket\":0,\"heal\":3,\"max_hp\":21},\"refreshes\":1,\"relics\":[\"breaker_strike\",\"stone_skin\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":5,\"candidates\":[{\"feat_id\":\"breaker_strike\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":28,\"heal_gained\":6,\"hp_after\":12,\"hp_before\":6,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"breaker_strike\",\"feat_name\":\"破封一击\",\"hp_after\":12,\"hp_before\":12,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":5,\"candidates\":[{\"feat_id\":\"stone_skin\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":16,\"heal_gained\":6,\"hp_after\":25,\"hp_before\":19,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"stone_skin\",\"feat_name\":\"石肤\",\"hp_after\":25,\"hp_before\":25,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":66,\"route\":\"min\",\"seal_hit\":233,\"seal_hit_gain\":1341,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":248,\"seed\":73029,\"shop_buys\":7,\"shop_gp_seq\":[40,58],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":107,\"visited_count\":20,\"wallet_count\":23}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":16,\"choices\":18,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,31,31,31,31,31,31,51,51,51,51,51],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":156,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73030\",\"1#73030\",\"1#73030\",\"1#73030\",\"1#73030\",\"1#73030\",\"1#73030\",\"2#73030\",\"2#73030\",\"2#73030\",\"2#73030\",\"2#73030\",\"2#73030\",\"2#73030\",\"2#73030\",\"2#73030\",\"2#73030\",\"3#73030\",\"3#73030\",\"3#73030\",\"3#73030\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":20,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":17},\"refreshes\":2,\"relics\":[\"blood_insight\",\"seal_smith\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":5,\"candidates\":[{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":37,\"heal_gained\":6,\"hp_after\":19,\"hp_before\":13,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"blood_insight\",\"feat_name\":\"见血开窍\",\"hp_after\":19,\"hp_before\":19,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":25,\"candidates\":[{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"seal_smith\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":57,\"heal_gained\":10,\"hp_after\":39,\"hp_before\":29,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"seal_smith\",\"feat_name\":\"封印匠\",\"hp_after\":39,\"hp_before\":39,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":93,\"route\":\"min\",\"seal_hit\":300,\"seal_hit_gain\":1749,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":313,\"seed\":73030,\"shop_buys\":7,\"shop_gp_seq\":[38,89],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":156,\"visited_count\":21,\"wallet_count\":24}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":15,\"choices\":18,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,31,31,31,31,31,41,41,41,41,41],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":138,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73031\",\"1#73031\",\"1#73031\",\"1#73031\",\"1#73031\",\"1#73031\",\"1#73031\",\"2#73031\",\"2#73031\",\"2#73031\",\"2#73031\",\"2#73031\",\"2#73031\",\"2#73031\",\"2#73031\",\"2#73031\",\"2#73031\",\"3#73031\",\"3#73031\",\"3#73031\",\"3#73031\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":19,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":19},\"refreshes\":1,\"relics\":[\"perfect_insurance\",\"market_close\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":5,\"candidates\":[{\"feat_id\":\"perfect_insurance\",\"kind\":\"relic\",\"tier\":\"rare\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"executioner\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":31,\"heal_gained\":6,\"hp_after\":31,\"hp_before\":25,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"perfect_insurance\",\"feat_name\":\"完美保险\",\"hp_after\":31,\"hp_before\":31,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":38,\"heal_gained\":8,\"hp_after\":34,\"hp_before\":26,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"market_close\",\"feat_name\":\"收盘\",\"hp_after\":34,\"hp_before\":34,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":85,\"route\":\"min\",\"seal_hit\":281,\"seal_hit_gain\":635,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":295,\"seed\":73031,\"shop_buys\":6,\"shop_gp_seq\":[21,66],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":138,\"visited_count\":21,\"wallet_count\":22}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":17,\"choices\":19,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,31,31,31,31,31,31,36,36,36,36,36,36],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":124,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73032\",\"1#73032\",\"1#73032\",\"1#73032\",\"1#73032\",\"1#73032\",\"1#73032\",\"2#73032\",\"2#73032\",\"2#73032\",\"2#73032\",\"2#73032\",\"2#73032\",\"2#73032\",\"2#73032\",\"2#73032\",\"2#73032\",\"3#73032\",\"3#73032\",\"3#73032\",\"3#73032\",\"3#73032\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":19,\"attack\":21,\"feat_ticket\":0,\"heal\":3,\"max_hp\":20},\"refreshes\":0,\"relics\":[\"execution_scholar\",\"venom_edge\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":5,\"candidates\":[{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":21,\"heal_gained\":6,\"hp_after\":23,\"hp_before\":17,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"execution_scholar\",\"feat_name\":\"斩首学者\",\"hp_after\":23,\"hp_before\":23,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":10,\"candidates\":[{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"venom_edge\",\"kind\":\"relic\",\"tier\":\"rare\"}],\"gp\":49,\"heal_gained\":7,\"hp_after\":31,\"hp_before\":24,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"venom_edge\",\"feat_name\":\"淬毒刃纹\",\"hp_after\":31,\"hp_before\":31,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}}],\"rounds\":82,\"route\":\"min\",\"seal_hit\":284,\"seal_hit_gain\":931,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":309,\"seed\":73032,\"shop_buys\":7,\"shop_gp_seq\":[28,90],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":124,\"visited_count\":22,\"wallet_count\":25}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":19,\"choices\":23,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,41,41,41,41,41,41,41,41,41,41,41,41,41,41],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":164,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73033\",\"1#73033\",\"1#73033\",\"1#73033\",\"1#73033\",\"1#73033\",\"1#73033\",\"2#73033\",\"2#73033\",\"2#73033\",\"2#73033\",\"2#73033\",\"2#73033\",\"2#73033\",\"2#73033\",\"2#73033\",\"2#73033\",\"3#73033\",\"3#73033\",\"3#73033\",\"3#73033\",\"3#73033\",\"3#73033\",\"3#73033\",\"3#73033\",\"3#73033\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":18,\"attack\":22,\"feat_ticket\":0,\"heal\":3,\"max_hp\":20},\"refreshes\":1,\"relics\":[\"blood_insight\",\"last_drop\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":24,\"heal_gained\":8,\"hp_after\":28,\"hp_before\":20,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"blood_insight\",\"feat_name\":\"见血开窍\",\"hp_after\":28,\"hp_before\":28,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":30,\"heal_gained\":1,\"hp_after\":41,\"hp_before\":40,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"last_drop\",\"feat_name\":\"一滴不剩\",\"hp_after\":41,\"hp_before\":41,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":99,\"route\":\"min\",\"seal_hit\":336,\"seal_hit_gain\":1256,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":352,\"seed\":73033,\"shop_buys\":7,\"shop_gp_seq\":[35,66],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":164,\"visited_count\":26,\"wallet_count\":27}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":17,\"choices\":20,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,36,36,36,36,36,36,36,36,36,36,36,36],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":142,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73034\",\"1#73034\",\"1#73034\",\"1#73034\",\"1#73034\",\"1#73034\",\"1#73034\",\"2#73034\",\"2#73034\",\"2#73034\",\"2#73034\",\"2#73034\",\"2#73034\",\"2#73034\",\"2#73034\",\"2#73034\",\"2#73034\",\"3#73034\",\"3#73034\",\"3#73034\",\"3#73034\",\"3#73034\",\"3#73034\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":18,\"attack\":21,\"feat_ticket\":0,\"heal\":3,\"max_hp\":21},\"refreshes\":1,\"relics\":[\"last_drop\",\"field_dressing\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":10,\"candidates\":[{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":30,\"heal_gained\":7,\"hp_after\":31,\"hp_before\":24,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"last_drop\",\"feat_name\":\"一滴不剩\",\"hp_after\":31,\"hp_before\":31,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":10,\"candidates\":[{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_insight\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"market_close\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":38,\"heal_gained\":2,\"hp_after\":36,\"hp_before\":34,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"field_dressing\",\"feat_name\":\"战地包扎\",\"hp_after\":36,\"hp_before\":36,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":83,\"route\":\"min\",\"seal_hit\":286,\"seal_hit_gain\":1785,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":302,\"seed\":73034,\"shop_buys\":7,\"shop_gp_seq\":[38,75],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":142,\"visited_count\":23,\"wallet_count\":25}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":17,\"choices\":21,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,31,31,31,31,31,31,31,31,31,31,31,31],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":124,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73035\",\"1#73035\",\"1#73035\",\"1#73035\",\"1#73035\",\"1#73035\",\"1#73035\",\"2#73035\",\"2#73035\",\"2#73035\",\"2#73035\",\"2#73035\",\"2#73035\",\"2#73035\",\"2#73035\",\"2#73035\",\"2#73035\",\"3#73035\",\"3#73035\",\"3#73035\",\"3#73035\",\"3#73035\",\"3#73035\",\"3#73035\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":21,\"attack\":18,\"feat_ticket\":0,\"heal\":3,\"max_hp\":21},\"refreshes\":0,\"relics\":[\"hard_hat\",\"blast_accounting\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":5,\"candidates\":[{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"hard_hat\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"seal_stake\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":17,\"heal_gained\":6,\"hp_after\":20,\"hp_before\":14,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"hard_hat\",\"feat_name\":\"安全帽\",\"hp_after\":20,\"hp_before\":20,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":5,\"candidates\":[{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"geologist\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":17,\"heal_gained\":6,\"hp_after\":30,\"hp_before\":24,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"blast_accounting\",\"feat_name\":\"爆破会计\",\"hp_after\":30,\"hp_before\":30,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":80,\"route\":\"min\",\"seal_hit\":273,\"seal_hit_gain\":304,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":284,\"seed\":73035,\"shop_buys\":7,\"shop_gp_seq\":[34,56],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":124,\"visited_count\":24,\"wallet_count\":25}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":18,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,36,36,36,36,36,36,51,51,51,51,51,51,56],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":131,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#73036\",\"1#73036\",\"1#73036\",\"1#73036\",\"1#73036\",\"1#73036\",\"1#73036\",\"2#73036\",\"2#73036\",\"2#73036\",\"2#73036\",\"2#73036\",\"2#73036\",\"2#73036\",\"2#73036\",\"2#73036\",\"2#73036\",\"3#73036\",\"3#73036\",\"3#73036\",\"3#73036\",\"3#73036\",\"3#73036\",\"3#73036\",\"3#73036\",\"3#73036\",\"3#73036\",\"3#73036\",\"3#73036\",\"3#73036\",\"3#73036\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":19,\"attack\":19,\"feat_ticket\":0,\"heal\":3,\"max_hp\":17},\"refreshes\":0,\"relics\":[\"frontier_miner\",\"blood_honey\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":10,\"candidates\":[{\"feat_id\":\"frontier_miner\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":17,\"heal_gained\":7,\"hp_after\":35,\"hp_before\":28,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"frontier_miner\",\"feat_name\":\"边陲矿工\",\"hp_after\":35,\"hp_before\":35,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":25,\"candidates\":[{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"twin_pick\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":26,\"heal_gained\":10,\"hp_after\":38,\"hp_before\":28,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"blood_honey\",\"feat_name\":\"血蜜\",\"hp_after\":38,\"hp_before\":38,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":93,\"route\":\"min\",\"seal_hit\":302,\"seal_hit_gain\":720,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":328,\"seed\":73036,\"shop_buys\":12,\"shop_gp_seq\":[26,106,76],\"shop_mode\":\"pool\",\"stopped_at\":0,\"taken\":131,\"visited_count\":31,\"wallet_count\":31}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":16,\"choices\":18,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":108,\"hp_pct\":0,\"keystone_total\":6,\"level_maps\":[\"1#73037\",\"1#73037\",\"1#73037\",\"1#73037\",\"1#73037\",\"1#73037\",\"1#73037\",\"2#73037\",\"2#73037\",\"2#73037\",\"2#73037\",\"2#73037\",\"2#73037\",\"2#73037\",\"2#73037\",\"2#73037\",\"2#73037\",\"3#73037\",\"3#73037\",\"3#73037\",\"3#73037\"],\"levels_cleared\":2,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":17,\"attack\":21,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":1,\"relics\":[\"blood_honey\",\"stone_skin\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":0,\"candidates\":[{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":14,\"heal_gained\":5,\"hp_after\":19,\"hp_before\":14,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"blood_honey\",\"feat_name\":\"血蜜\",\"hp_after\":19,\"hp_before\":19,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":0,\"candidates\":[{\"feat_id\":\"execution_scholar\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"stone_skin\",\"kind\":\"relic\",\"tier\":\"excellent\"}],\"gp\":51,\"heal_gained\":5,\"hp_after\":21,\"hp_before\":16,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"stone_skin\",\"feat_name\":\"石肤\",\"hp_after\":21,\"hp_before\":21,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":78,\"route\":\"min\",\"seal_hit\":250,\"seal_hit_gain\":1410,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":265,\"seed\":73037,\"shop_buys\":7,\"shop_gp_seq\":[27,73],\"shop_mode\":\"pool\",\"stopped_at\":3,\"taken\":108,\"visited_count\":21,\"wallet_count\":24}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":20,\"choices\":28,\"cleared\":true,\"encounter_max_hp_seq\":[26,26,26,26,26,26,26,26,26,26,26,41,41,41,41,41,41,41,41,41],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":177,\"hp_pct\":0,\"keystone_total\":9,\"level_maps\":[\"1#73038\",\"1#73038\",\"1#73038\",\"1#73038\",\"1#73038\",\"1#73038\",\"1#73038\",\"2#73038\",\"2#73038\",\"2#73038\",\"2#73038\",\"2#73038\",\"2#73038\",\"2#73038\",\"2#73038\",\"2#73038\",\"2#73038\",\"3#73038\",\"3#73038\",\"3#73038\",\"3#73038\",\"3#73038\",\"3#73038\",\"3#73038\",\"3#73038\",\"3#73038\",\"3#73038\",\"3#73038\",\"3#73038\",\"3#73038\",\"3#73038\"],\"levels_cleared\":3,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":19,\"attack\":18,\"feat_ticket\":0,\"heal\":3,\"max_hp\":19},\"refreshes\":2,\"relics\":[\"trap_burst\",\"field_dressing\"],\"result\":\"completed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":0,\"candidates\":[{\"feat_id\":\"trap_instinct\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"seal_echo\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"trap_burst\",\"kind\":\"relic\",\"tier\":\"rare\"}],\"gp\":27,\"heal_gained\":5,\"hp_after\":25,\"hp_before\":20,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"trap_burst\",\"feat_name\":\"陷阱爆发\",\"hp_after\":25,\"hp_before\":25,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"rare\",\"value\":0}},{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":15,\"candidates\":[{\"feat_id\":\"field_dressing\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blood_honey\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"warning_beacon\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":32,\"heal_gained\":8,\"hp_after\":37,\"hp_before\":29,\"keystone_gained\":3,\"keystone_total\":6,\"level_index\":2}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"field_dressing\",\"feat_name\":\"战地包扎\",\"hp_after\":37,\"hp_before\":37,\"level_index\":2,\"picked\":\"relic\",\"tier\":\"common\",\"value\":0}}],\"rounds\":111,\"route\":\"min\",\"seal_hit\":364,\"seal_hit_gain\":1711,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":388,\"seed\":73038,\"shop_buys\":11,\"shop_gp_seq\":[26,70,106],\"shop_mode\":\"pool\",\"stopped_at\":0,\"taken\":177,\"visited_count\":31,\"wallet_count\":32}",
"RUNSUM {\"atk_pct\":0,\"attune\":\"off\",\"battles\":9,\"choices\":8,\"cleared\":false,\"encounter_max_hp_seq\":[26,26,26,26,26,26,26,26,26],\"equipped\":[],\"feat_mode\":\"off\",\"hero_dmg\":58,\"hp_pct\":0,\"keystone_total\":3,\"level_maps\":[\"1#73039\",\"1#73039\",\"1#73039\",\"1#73039\",\"1#73039\",\"1#73039\",\"1#73039\",\"2#73039\",\"2#73039\",\"2#73039\"],\"levels_cleared\":1,\"mine_dmg\":0,\"mine_hits\":0,\"policy\":\"perfect\",\"pool_stock\":{\"ap\":3,\"armor\":20,\"attack\":23,\"feat_ticket\":0,\"heal\":3,\"max_hp\":22},\"refreshes\":0,\"relics\":[\"breaker_strike\"],\"result\":\"failed\",\"reward_events\":[{\"curio_pick\":{\"aux_cleared\":0,\"bonus_max_hp\":0,\"candidates\":[{\"feat_id\":\"breaker_strike\",\"kind\":\"relic\",\"tier\":\"excellent\"},{\"feat_id\":\"last_drop\",\"kind\":\"relic\",\"tier\":\"common\"},{\"feat_id\":\"blast_accounting\",\"kind\":\"relic\",\"tier\":\"common\"}],\"gp\":21,\"heal_gained\":5,\"hp_after\":21,\"hp_before\":16,\"keystone_gained\":3,\"keystone_total\":3,\"level_index\":1}},{\"curio_resolved\":{\"feat_archetype\":\"\",\"feat_id\":\"breaker_strike\",\"feat_name\":\"破封一击\",\"hp_after\":21,\"hp_before\":21,\"level_index\":1,\"picked\":\"relic\",\"tier\":\"excellent\",\"value\":0}}],\"rounds\":43,\"route\":\"min\",\"seal_hit\":139,\"seal_hit_gain\":2048,\"seal_miss\":0,\"seal_miss_penalty\":0,\"seal_placed\":149,\"seed\":73039,\"shop_buys\":2,\"shop_gp_seq\":[23],\"shop_mode\":\"pool\",\"stopped_at\":2,\"taken\":58,\"visited_count\":10,\"wallet_count\":12}"
],
"jsonl": {
"normal": {
"sha256": "b39d5a5a659776e6e3a25a7a1f24af4d8a98b138b2c52cec846bfd7d394364fb",
"rows": 7124
},
"perfect": {
"sha256": "c3d0d37c5f40d208fb35d323b4c7a11c4f7ebe77f9f0eaba0fc3445f00971277",
"rows": 59435
}
}
}
}
}
+2 -77
View File
@@ -4,14 +4,6 @@
# 零命中/空值=用法错 quit(2)。两个来源分开定性——开发期直跑(默认)仅供最窄确认,不产闸回执、
# 不作验收凭据;闸机关联档选跑(--only-source=gates,由 tools/run_all_gates.sh 在 02 闸传入)
# 是收口验收跑批本体,回执 scope=related。无任何选跑参数=全量态,行为与全量口径完全一致。
# 分片态:追加 `-- --shard=k --shard-count=n`(须成对,1≤k≤n,n≥2 才有意义)——把选套集
# (--only 过滤后)切成 n 片互斥子集,本进程只跑第 k 片;闸机据此多进程并行缩短 02 闸墙钟。
# 分片分配走「串行道优先」:开 RNG 事件日志会话的套件(SERIAL_PATTERNS,new_adventure 调用方)
# 全部钉第 1 片——事件日志按种子命名同文件追加(user://logs/rng_log_<seed>.jsonl),跨片并行
# 写同一文件互踩内容,断言日志纯净序的套件(cross_system_flow)必读错行。新增日志会话套件必须
# 同步登记 SERIAL_PATTERNS,漏登记=并行期日志互踩假红。空片合法(--only 小集 × 多片):空片
# 直接 quit(0) 放行,判定职责由其余片承担。各片独立退出码,由闸机聚合;片内三硬闸与
# 快速失败语义不变,逐套件日志名加 _s<k> 段防跨片覆写。
# 协议:套件清单逐条"套件 → 期望退出码";致命类独立进程(expect=1)。
# 执行形态:逐套件起独立 Godot 子进程实跑
#(OS.get_executable_path() 解析本引擎二进制——--script 模式无 autoload/全局标识符,
@@ -38,23 +30,6 @@ const SUITE_TIMEOUT_DEFAULT_SEC := 120
# 快速失败阈值(可调常量,初值 3):连续 N 个套件超时即中止整轮。
const CONSECUTIVE_TIMEOUT_ABORT := 3
# 串行道套件(路径子串匹配):开 RNG 事件日志会话(new_adventure)的调用方——事件日志按种子
# 命名同文件追加(user://logs/rng_log_<seed>.jsonl),跨分片并行写同一文件互踩;断言日志内容
# 的套件(cross_system_flow)必须读到本会话纯净追加序。分片分配时全部钉第 1 片(见头注分片态)。
# 新增日志会话套件时同步登记此处,漏登记=并行期假红。
const SERIAL_PATTERNS: Array[String] = [
"board_generator_test",
"node_map_generator_test",
"minesweeper_rules_test",
"rng_stream_boot_test",
"cross_system_flow_test",
"node_map_determinism_test",
]
# 分片态成员:0=未分片(单进程原路径);>0=第 _shard_k/_shard_n 片(_run_suite 取名用)。
var _shard_k := 0
var _shard_n := 0
func _init() -> void:
# 选跑态解析:--only=<词[,词…]>(经 -- 分隔的 user args)——对套件 res:// 路径做子串匹配;
@@ -71,22 +46,6 @@ func _init() -> void:
only_terms.append(term)
elif a == "--only-source=gates":
only_from_gates = true
elif a.begins_with("--shard="):
var sv := a.trim_prefix("--shard=").strip_edges()
if sv.is_valid_int():
_shard_k = int(sv)
elif a.begins_with("--shard-count="):
var sn := a.trim_prefix("--shard-count=").strip_edges()
if sn.is_valid_int():
_shard_n = int(sn)
if (_shard_k == 0) != (_shard_n == 0):
push_error("[run_all] --shard 与 --shard-count 必须成对出现")
quit(2)
return
if _shard_n < 0 or (_shard_n > 0 and (_shard_k < 1 or _shard_k > _shard_n)):
push_error("[run_all] 分片参数非法(须 1≤k≤n,n≥1):k=%d n=%d" % [_shard_k, _shard_n])
quit(2)
return
if has_only and only_terms.is_empty():
push_error("[run_all] --only= 空值——用法错(选跑禁把零套件当全量)")
quit(2)
@@ -105,14 +64,6 @@ func _init() -> void:
print("[run_all] GATE FILTERED RUN — 闸机关联档选跑 %d/%d 套件(--only=%s)" % [suites.size(), manifest.size(), ",".join(only_terms)])
else:
print("[run_all] FILTERED RUN — 选跑 %d/%d 套件(--only=%s)——仅供开发期最窄确认,不产闸回执、不作验收凭据" % [suites.size(), manifest.size(), ",".join(only_terms)])
var sharded := _shard_n > 1
if sharded:
suites = _shard_suites(suites, _shard_k, _shard_n)
if suites.is_empty():
print("[run_all] SHARD %d/%d — 本片零套件,空片放行(判定职责由其余片承担)" % [_shard_k, _shard_n])
quit(0)
return
print("[run_all] SHARD %d/%d — 本片 %d 套件" % [_shard_k, _shard_n, suites.size()])
print("[run_all] unit/contract suite entry — %d suites" % suites.size())
if DisplayServer.get_name() != "headless":
push_warning("[run_all] 建议以 --headless 运行(D2 冒烟协议)")
@@ -150,10 +101,7 @@ func _init() -> void:
push_error("[run_all] %d 个套件失败" % failures)
quit(1)
return
var tag := ",filtered" if filtered else ""
if sharded:
tag += ",shard %d/%d" % [_shard_k, _shard_n]
print("[run_all] all green (%d suites%s)" % [suites.size(), tag])
print("[run_all] all green (%d suites%s)" % [suites.size(), ",filtered" if filtered else ""])
quit(0)
@@ -255,8 +203,7 @@ func _run_suite(suite: Dictionary, idx: int, filtered: bool = false) -> Dictiona
# 后又没有退出码读取 API,故由 cmd /V:ON 在套件退出后把 !ERRORLEVEL! 回显落盘,
# 值与阻塞式 execute 经 cmd 回传的同一。
var proj := ProjectSettings.globalize_path("res://")
var shard_tag := "" if _shard_n == 0 else "_s%d" % _shard_k
var log := proj + "/tests/.gates/run_all%s%s_%02d.log" % ["_filter" if filtered else "", shard_tag, idx]
var log := proj + "/tests/.gates/run_all%s_%02d.log" % ["_filter" if filtered else "", idx]
var codefile := log + ".code"
if FileAccess.file_exists(codefile):
DirAccess.remove_absolute(codefile) # 陈值防护:上轮临界残件不许冒充本轮退出码
@@ -346,28 +293,6 @@ func _filter_suites(manifest: Array, terms: PackedStringArray) -> Array:
return out
# 分片分配:串行道套件(SERIAL_PATTERNS 命中)全钉第 1 片;其余按片轮转(片内相对序 j%n+1)。
# 分配对每个 k 确定性、互斥且完备——各片并起来=传入全集零重复零遗漏。
func _shard_suites(suites: Array, k: int, n: int) -> Array:
var out: Array = []
var j := 0
for suite in suites:
var path: String = suite.get("path", "")
var is_serial := false
for pat in SERIAL_PATTERNS:
if path.contains(pat):
is_serial = true
break
if is_serial:
if k == 1:
out.append(suite)
else:
if j % n + 1 == k:
out.append(suite)
j += 1
return out
# 引擎二进制必须取 console 版:OS.get_executable_path() 返回的是 GUI 子系统 exe
#(Godot_v4.7.2-stable_win64.exe),它起的子进程 stdout/stderr 句柄无效——输出全丢,
# 脚本错误扫描就成了扫空气。GODOT_BIN 优先,其次按命名推导 *_console.exe。
+3 -3
View File
@@ -308,13 +308,13 @@ fi
# 可回放纯逻辑步骤流(同种子同输入逐位复现),await 会打断确定性——「模块归属」B1 行已写明
# 「禁 await——切片 88 冒烟实证」。本闸为防回归防线(2026-10-24 人工 grep 盘面干净)。
# 扫描面:B1 域文件集(与 architecture.md「模块归属」B1 行同口径)=battle_orchestrator.gd
# + battle_fx_host.gd + reward_grant.gd + board_installer.gd 三拆件 + settlement/ 五段段类 + damage_controller.gd,共 10 文件,显式清单见 S8_FILES。
# + battle_fx_host.gd + reward_grant.gd 两拆件 + settlement/ 五段段类 + damage_controller.gd,共 9 文件,显式清单见 S8_FILES。
# 执行侧字面值豁免:脚本必须带字面值才能工作;文件集与架构文档冲突时以 B1 域文件集为准。
# 判据:文件集内出现 await 关键字即红(词边界 \bawait\b,任何形态——await 信号/协程/
# get_tree());排除注释行(行首 #/##)与字符串字面量(双引号包裹段内的 await 非代码),
# 口径仿 S1/S2 的 grep 排除手法。
# 自检:清单内文件缺位(路径写错/被改名)即判红——防扫面为空静默绿。
S8_FILES="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/damage_controller.gd"
S8_FILES="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/damage_controller.gd"
S8_MISS=0
for f in $S8_FILES; do
if [ ! -f "$f" ]; then
@@ -356,7 +356,7 @@ fi
# 自检:扫描面为空即判红(require_scan_face);B1 文件集与三族自家文件缺位即判红
# (文件被改名=豁免名单失效,宁可红不可静默放行)。
if require_scan_face "S7" "$DIR_CORE"; then
S7_B1_SET="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/damage_controller.gd"
S7_B1_SET="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/damage_controller.gd"
S7_MISS=0
for f in $S7_B1_SET game/core/unit_container.gd game/core/status_container.gd game/core/reward_economy.gd; do
if [ ! -f "$f" ]; then
+1 -1
View File
@@ -361,7 +361,7 @@ func _smoke_adventure() -> void:
if int(pb.get("level_index", -1)) == 2:
l2_price_bonus = int(pb.get("bonus", 0))
Kit.check("进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" % [hp_price + l2_price_bonus, hp_price, l2_price_bonus], gp0 >= hp_price + l2_price_bonus)
Kit.check("购买 hp_up 受理(出账 + 加算区)", r8.shop.buy_product("hp_up"))
Kit.check("购买 hp_up 受理(出账 + 加算区)", r8.buy_product("hp_up"))
var acc_shop: int = a8.journal().bonus_total("max_hp") # L1 三选一后 + 商店 3(首件随种子)
Kit.check("统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" % acc_shop, acc_shop >= 3)
var l2_cap := {"payload": {}} # 公开信号面采集(curio_choice_presented 停点载荷——容器写回规避 lambda 按值捕获)
@@ -8,7 +8,6 @@ extends SceneTree
const SaveManagerScript := preload("res://core/save_manager.gd")
const AdventureRunnerScript := preload("res://core/adventure_runner.gd")
const BreakpointStoreScript := preload("res://core/breakpoint_store.gd")
const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:check/汇总退出样板单点(静默收集通道)
var _bp_path := "user://test_breakpoint_resume.json"
@@ -183,7 +182,7 @@ func _init() -> void:
"P7 同种子重开局同图(开局入口复现承载)〔v7〕")
# ---- Part 8:断点字段登记单源(登记键集 == 写侧实拼字典键集;键名增删即红)----
var fields: Dictionary = BreakpointStoreScript.BREAKPOINT_FIELDS # 断点域拆件改址——登记单源随件(判据零变化)
var fields: Dictionary = AdventureRunnerScript.BREAKPOINT_FIELDS
var reg_keys: Array = fields.keys()
reg_keys.sort()
var bp_keys: Array = bp_j.keys()
@@ -49,9 +49,9 @@ func _init() -> void:
# ---- ⑤ 钱包单口支出 spend_gp(LevelRunner——三选一付费档与商店刷新共用同一冒险钱包)----
var lr = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new())
lr.gp = 30
Kit.check_quiet(lr.shop.spend_gp(0) == false and int(lr.gp) == 30, "零额支出拒绝零副作用")
Kit.check_quiet(lr.shop.spend_gp(31) == false and int(lr.gp) == 30, "余额不足拒绝零副作用")
Kit.check_quiet(lr.shop.spend_gp(20) == true and int(lr.gp) == 10, "足额支出扣账(30→10)")
Kit.check_quiet(lr.spend_gp(0) == false and int(lr.gp) == 30, "零额支出拒绝零副作用")
Kit.check_quiet(lr.spend_gp(31) == false and int(lr.gp) == 30, "余额不足拒绝零副作用")
Kit.check_quiet(lr.spend_gp(20) == true and int(lr.gp) == 10, "足额支出扣账(30→10)")
# ---- ⑥ Y 断点随存(缺陷锚:续玩自参数面整额重注入=用完 Y 退菜单再续玩满血复活)----
var bp_y := {"level_index": 1, "hero_hp": 10, "attune_refresh_y": 2, "graph_gen_v2": 1}
+2 -2
View File
@@ -57,13 +57,13 @@ func _init() -> void:
# ---- hr1-s:商店属性商品加算走日记单写原语(购买后 bonus_total 与旧径同值)----
runner._gain_wallet(1000) # 钱包注入(测试直调写口——测试构造惯例)
var products: Array = runner.shop.shop_products()
var products: Array = runner.shop_products()
var bought := false
for p in products:
if int(p.get("price", 1 << 30)) <= 1000 and String(p.get("attr", "")) != "":
var attr := String(p.get("attr", ""))
var before: int = runner.journal().bonus_total(attr)
bought = runner.shop.buy_product(String(p.get("product_id", "")))
bought = runner.buy_product(String(p.get("product_id", "")))
if bought:
Kit.check_quiet(runner.journal().bonus_total(attr) == before + int(p.get("flat_value", 0)),
"hr1-s 商店购买后 bonus_total 增量 = 商品 flat(加算走 gain_bonus 单写原语——同值承载)")
+27 -96
View File
@@ -251,7 +251,6 @@ func _init() -> void:
_smoke_shop_graph()
_smoke_multi_terminal_settlement()
_smoke_shop_pool()
_test_shop_node_entered_payload()
_smoke_generated_coverage()
quit(Kit.finish("level_shop_economy_test"))
@@ -1025,17 +1024,17 @@ func _smoke_shop_room() -> void:
return
Kit.check("到店开店态(覆盖层数据源就位——离开前不出选路口,零自动推进)", not runner.awaiting_choice() and runner.next_candidate_ids().is_empty())
# 现价 = 基价 + price_level_bonus(L2)=2 + 步长 × 已购档数
var prods: Array = runner.shop.shop_products()
var prods: Array = runner.shop_products()
Kit.check("三商品全陈列(atk_up/hp_up/armor_up——无刷新无摇号)", prods.size() == 3)
var by_id := _shop_products_by_id(prods)
Kit.check("L2 首购现价 = 基价 + 关级加值 2(atk_up 6 / hp_up 4 / armor_up 6)", int(by_id["atk_up"]["price"]) == 6 and int(by_id["hp_up"]["price"]) == 4 and int(by_id["armor_up"]["price"]) == 6)
# 购买 atk_up:钱包 −6 · 加算区 atk +1 · 二购现价 8
var gp0: int = runner.gp
Kit.check("购买 atk_up 受理(现价 6)", runner.shop.buy_product("atk_up"))
Kit.check("购买 atk_up 受理(现价 6)", runner.buy_product("atk_up"))
Kit.check("钱包扣现价 6(%d → %d)" % [gp0, runner.gp], runner.gp == gp0 - 6)
# 加算区住加算日记
Kit.check("统一冒险加算区 atk +1(加算区住加算日记)", runner.journal().bonus_total("atk") == 1 and runner.journal().bonus_total("max_hp") == 0 and runner.journal().bonus_total("armor_class") == 0)
by_id = _shop_products_by_id(runner.shop.shop_products())
by_id = _shop_products_by_id(runner.shop_products())
Kit.check("atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)", int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1)
# 行进段百分比归零(购买/生效断言按加算区裸值口径——调试浮窗百分比后乘;hp 快照钳到新口径上限防 spawn 越界,测试通道)
runner.set_hero_override(0, 0)
@@ -1043,28 +1042,28 @@ func _smoke_shop_room() -> void:
# hp_up:上限与当前生命同步 +3(封顶新上限)
var hp_before: int = runner.hero_hp
var mhp_before: int = runner.effective_max_hp()
Kit.check("购买 hp_up 受理(现价 4)", runner.shop.buy_product("hp_up"))
Kit.check("购买 hp_up 受理(现价 4)", runner.buy_product("hp_up"))
Kit.check("钱包扣 4(%d)" % runner.gp, runner.gp == gp0 - 6 - 4)
Kit.check("生命上限 +3(%d → %d——加算区 max_hp 轴)" % [mhp_before, runner.effective_max_hp()], runner.effective_max_hp() == mhp_before + 3)
Kit.check("当前生命同步 +3 封顶新上限(%d → %d)" % [hp_before, runner.hero_hp], runner.hero_hp == mini(hp_before + 3, runner.effective_max_hp()))
# armor_up:AC 10+1
Kit.check("购买 armor_up 受理(现价 6)", runner.shop.buy_product("armor_up"))
Kit.check("购买 armor_up 受理(现价 6)", runner.buy_product("armor_up"))
Kit.check("AC = 10+1(armor_class 加算区轴,无百分比通道)", runner.effective_ac() == int(_db.combat.hero.armor_class) + 1)
# 余额不足购买被拒(零副作用):hp_up 连购至不可负担后兜底拒绝
var guard_drain := 20
var hp_buys := 1 # 夹具前段已购 1 笔(购买 hp_up 受理断言)——总数 = 前段 + 本循环
while guard_drain > 0:
guard_drain -= 1
var hp_price: int = int(_shop_products_by_id(runner.shop.shop_products())["hp_up"]["price"])
var hp_price: int = int(_shop_products_by_id(runner.shop_products())["hp_up"]["price"])
if runner.gp < hp_price:
break
if not runner.shop.buy_product("hp_up"):
if not runner.buy_product("hp_up"):
break
hp_buys += 1
var gp_dry: int = runner.gp
var tier_dry: int = runner.shop.purchased_tier("hp_up")
var hp_dry: int = int(_shop_products_by_id(runner.shop.shop_products())["hp_up"]["price"])
Kit.check("余额不足购买被拒(gp=%d < 现价 %d——零扣费零档位变动)" % [gp_dry, hp_dry], not runner.shop.buy_product("hp_up") and runner.gp == gp_dry and runner.shop.purchased_tier("hp_up") == tier_dry)
var tier_dry: int = runner.purchased_tier("hp_up")
var hp_dry: int = int(_shop_products_by_id(runner.shop_products())["hp_up"]["price"])
Kit.check("余额不足购买被拒(gp=%d < 现价 %d——零扣费零档位变动)" % [gp_dry, hp_dry], not runner.buy_product("hp_up") and runner.gp == gp_dry and runner.purchased_tier("hp_up") == tier_dry)
# 离开 → 回选路口(候选 = 该店出边——恰一条,指向终点 Boss)
Kit.check("离开商店 → 回选路口(候选 = 店出边 %s——零自动推进收口)" % str(shop_outs),
runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(shop_outs))
@@ -1253,7 +1252,7 @@ func _smoke_shop_pool() -> void:
if not _adv_drive_to_shop(a8, _adv_st(), autoplay):
continue
scen = {"a": a8, "s": s, "r": a8.runner()}
first_shelf_a = (a8.runner().shop.shop_shelf() as Array).duplicate(true)
first_shelf_a = (a8.runner().shop_shelf() as Array).duplicate(true)
break
Kit.check("池模式商店房场景可达(seed=%s)" % str(scen.get("s", -1)), not scen.is_empty())
if not scen.is_empty():
@@ -1262,19 +1261,19 @@ func _smoke_shop_pool() -> void:
a8b.set_hero_override(900, 400)
if a8b.start_adventure(int(scen["s"]), {"shop_mode": "pool"}) \
and _adv_drive_to_shop(a8b, _adv_st(), autoplay):
first_shelf_b = (a8b.runner().shop.shop_shelf() as Array).duplicate(true)
first_shelf_b = (a8b.runner().shop_shelf() as Array).duplicate(true)
Kit.check("进店抽 3(货架 %d 卡——同构交互)" % first_shelf_a.size(), first_shelf_a.size() == 3)
# MC-1 商店独立刷新(P-C2+Q2 裁——基础免费 0;GP 刷新 ceil(20×1.8^n) provisional;重抽同源)
if not scen.is_empty():
var r8 = scen["r"]
var gp0: int = int(r8.gp)
var cost0: int = r8.shop.shop_refresh_cost()
var cost0: int = r8.shop_refresh_cost()
Kit.check("刷新首价=基价(%d)" % cost0, cost0 == int(_db.run.adventure.shop_refresh_cost_base))
Kit.check("基础免费 0 次(Q2——天赋增益位后续添加)", r8.shop.shop_refresh_free_left() == 0)
var shelf_pre: Array = (r8.shop.shop_shelf() as Array).duplicate(true)
Kit.check("GP 刷新受理(gp=%d cost=%d)" % [gp0, cost0], r8.shop.shop_refresh())
Kit.check("基础免费 0 次(Q2——天赋增益位后续添加)", r8.shop_refresh_free_left() == 0)
var shelf_pre: Array = (r8.shop_shelf() as Array).duplicate(true)
Kit.check("GP 刷新受理(gp=%d cost=%d)" % [gp0, cost0], r8.shop_refresh())
Kit.check("GP 刷新扣费(%d→%d)" % [gp0, int(r8.gp)], int(r8.gp) == gp0 - cost0)
Kit.check("刷新爬价 ×curve(%d)" % int(r8.shop.shop_refresh_cost()), int(r8.shop.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.run.adventure.shop_refresh_cost_curve))))
Kit.check("刷新爬价 ×curve(%d)" % int(r8.shop_refresh_cost()), int(r8.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.run.adventure.shop_refresh_cost_curve))))
var same_shelf := first_shelf_a.size() == first_shelf_b.size()
if same_shelf:
for i in first_shelf_a.size():
@@ -1290,10 +1289,10 @@ func _smoke_shop_pool() -> void:
# ② 货架投影形制:kind 词表封闭 + 现价公式推导(ceil(base×b^(关序−1)×A^0)——核心现算,呈现零复算)
var l2 := int(r8.level_row.level_index)
# 现价等价证明(d11——消费端读表推导 vs 迁移前硬编码字面量推导:同输入逐位一致)
var relic_price_now: int = r8.shop.pool_price("relic")
var relic_price_now: int = r8.pool_price("relic")
var relic_price_pre: int = int(ceil(15.0 * pow(_db.run.shop.price_b, float(l2 - 1)) * 1.0))
Kit.check("relic 现价 = ceil(15.0×b^(关序−1))(读表与迁移前字面量逐位一致,%d)〔wd11〕" % relic_price_now, relic_price_now == relic_price_pre)
for c in r8.shop.shop_shelf():
for c in r8.shop_shelf():
# T6 商店保底后 relic 卡合法入架(保底占位)——词表断言放行 relic;价格断言分叉
# (数值卡走 shop_pool_row 推导 / relic 卡走表 knob relic_base_price——同公式不同基价)
var ckind := String(c.get("kind_id", ""))
@@ -1308,7 +1307,7 @@ func _smoke_shop_pool() -> void:
Kit.check("首购现价 = ceil(基价×b^(关序−1))(%s: %d = %d 推导)" % [ckind, int(c.get("price", -1)), expect_p], int(c.get("price", -1)) == expect_p)
# ③ 买 1 全重抽:出账+加算区+账本三落点 + 现价 A 链递增(买后 visit 计数=1;TP0——消费在架令牌本体)
var buy_c: Dictionary = {}
for c in r8.shop.shop_shelf():
for c in r8.shop_shelf():
if int(c.get("price", 1 << 30)) <= int(r8.gp):
buy_c = c
break
@@ -1320,7 +1319,7 @@ func _smoke_shop_pool() -> void:
var gp0: int = int(r8.gp)
var taken0: int = int(a8._curio.taken.get(kind, 0))
var val0: int = a8.journal().bonus_total(String(val_map[kind]))
Kit.check("购买受理(pool 口——%s|%s)" % [kind, tier], r8.shop.buy_pool_item(kind))
Kit.check("购买受理(pool 口——%s|%s)" % [kind, tier], r8.buy_pool_item(kind))
Kit.check("钱包扣现价(%d → %d)" % [gp0, int(r8.gp)], int(r8.gp) == gp0 - int(buy_c.get("price", 0)))
Kit.check("加算区 +令牌档位件值(池件=奖励件本体——%s|%s %+d)" % [kind, tier, int(_db.run.adventure.curio_value(kind, tier))], a8.journal().bonus_total(String(val_map[kind])) == val0 + int(_db.run.adventure.curio_value(kind, tier)))
Kit.check("共享池账本 -1(跨渠道单点——taken %d → %d)" % [taken0, int(a8.deck().taken.get(kind, 0))], int(a8.deck().taken.get(kind, 0)) == taken0 + 1)
@@ -1328,7 +1327,7 @@ func _smoke_shop_pool() -> void:
# A 链:白盒抬 GP 保证第二购场景确定构造(直改测试基建——非生产行为)
r8.gp = int(r8.gp) + 64
var second: Dictionary = {}
for c in r8.shop.shop_shelf():
for c in r8.shop_shelf():
if int(c.get("price", 1 << 30)) <= int(r8.gp):
second = c
break
@@ -1336,14 +1335,14 @@ func _smoke_shop_pool() -> void:
var prow2: Dictionary = _db.run.shop.pool_row(String(second.get("kind_id", "")))
var expect_p2: int = int(ceil(float(int(prow2.get("base_price", 0))) * pow(_db.run.shop.price_b, float(l2 - 1)) * _db.run.shop.price_a))
Kit.check("A 链爬价 = ceil(base×b^(关序−1)×A^1)(进店第 2 购——%d)" % expect_p2, int(second.get("price", -1)) == expect_p2)
Kit.check("同 visit 第二购受理(A 链软闸在价不在限)", r8.shop.buy_pool_item(String(second.get("kind_id", ""))))
Kit.check("同 visit 第二购受理(A 链软闸在价不在限)", r8.buy_pool_item(String(second.get("kind_id", ""))))
else:
Kit.check("A 链爬价场景未构造(抬 GP 后货架仍无可购第二卡)——场景绑定失败", false)
# ④ 切边零入店(白-box 重抽 40 次——GP 袋/规则改写型特性永不在架;重抽消费 shop_draw 签,场景尾不回写主时间线)
var cut_seen := false
for i in 40:
r8.shop._draw_shelf()
for c in r8.shop.shop_shelf():
r8._shop_draw_shelf()
for c in r8.shop_shelf():
if String(c.get("kind_id", "")) == "gp" or String(c.get("kind_id", "")) == "treasure_item":
cut_seen = true
Kit.check("切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)", not cut_seen)
@@ -1353,8 +1352,8 @@ func _smoke_shop_pool() -> void:
a8._curio.deck.remove_at(di8)
var attack_seen := false
for i in 40:
r8.shop._draw_shelf()
if (r8.shop.shop_shelf() as Array).any(func(c): return String(c.get("kind_id", "")) == "attack"):
r8._shop_draw_shelf()
if (r8.shop_shelf() as Array).any(func(c): return String(c.get("kind_id", "")) == "attack"):
attack_seen = true
break
Kit.check("库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)", not attack_seen)
@@ -1392,74 +1391,6 @@ func _smoke_shop_pool() -> void:
Kit.check("终局池余量在域 [0,配额](%s)" % str(ps), ps_ok)
# ---- 商店房 node_entered 载荷(尾波 V1——_enter_node pool/legacy 双发射路径归一后的行为保持断言)----
func _test_shop_node_entered_payload() -> void:
print("--- 商店房 node_entered 载荷(pool 含 shelf:true+gp 现值/legacy 无 shelf 键)---")
var autoplay = load("res://sim/level_autoplay.gd")
# pool 态:载荷字典经信号闭包当场取样(字典为引用累积器——lambda 按值捕获标量的绕行惯例)
var cap_pool := {"payload": {}, "gp": -1}
var pool_ok := false
for s in range(42, 92):
var pr = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(s))
pr.set_hero_override(900, 400)
if not pr.start_adventure(s, {"shop_mode": "pool"}):
continue
var rp = pr.runner()
rp.node_entered.connect(func(node, payload):
if String(node.get("type", "")) == "shop":
cap_pool["payload"] = payload
cap_pool["gp"] = int(rp.gp))
if not _adv_drive_to_shop(pr, _adv_st(), autoplay):
continue
pool_ok = not (cap_pool["payload"] as Dictionary).is_empty()
if pool_ok:
break
Kit.check("pool 态进店信号可达(seed 扫描 42–91)", pool_ok)
Kit.check("pool 态 node_entered 载荷含 shelf:true〔尾波 V1〕", pool_ok and bool((cap_pool["payload"] as Dictionary).get("shelf", false)))
Kit.check("pool 态载荷 gp = 进店钱包现值(%d)〔尾波 V1〕" % int(cap_pool["gp"]), pool_ok and int((cap_pool["payload"] as Dictionary).get("gp", -1)) == int(cap_pool["gp"]))
# legacy 态:载荷无 shelf 键(两 payload 字典逐字保持的另一半——直构 LevelRunner 链)
var cap_leg := {"payload": {}, "gp": -1}
var leg_ok := false
for s in range(42, 92):
var lr = load("res://core/level_runner.gd").new(_db, _fresh_rng(s))
lr.set_hero_override(900, 400)
lr.start_level(2, s)
lr.node_entered.connect(func(node, payload):
if String(node.get("type", "")) == "shop":
cap_leg["payload"] = payload
cap_leg["gp"] = int(lr.gp))
var st := _battle_st("normal")
var guard := 60
while String(lr.level_result) == "" and String(lr.current_node().get("type", "")) != "shop" and guard > 0:
guard -= 1
if lr.awaiting_choice():
var ids: Array = lr.next_candidate_ids()
ids.sort()
lr.pick_next_node(int(ids[0]))
continue
var t := String(lr.current_node().get("type", ""))
if t == "reward" or t == "rest":
lr.continue_after_node()
continue
var b = lr.current_battle()
if b == null:
break
var fin := {}
b.battle_finished.connect(func(p): fin["p"] = p)
var g2 := 80
while lr.current_battle() == b and not fin.has("p") and g2 > 0:
g2 -= 1
autoplay._autoplay_battle(lr, b, st, null)
if String(lr.level_result) == "" and String(lr.current_node().get("type", "")) == "shop":
leg_ok = not (cap_leg["payload"] as Dictionary).is_empty()
if leg_ok:
break
Kit.check("legacy 态进店信号可达(seed 扫描 42–91)", leg_ok)
Kit.check("legacy 态 node_entered 载荷无 shelf 键〔尾波 V1〕", leg_ok and not (cap_leg["payload"] as Dictionary).has("shelf"))
Kit.check("legacy 态载荷 gp = 进店钱包现值(%d)〔尾波 V1〕" % int(cap_leg["gp"]), leg_ok and int((cap_leg["payload"] as Dictionary).get("gp", -1)) == int(cap_leg["gp"]))
# 入口→Boss 全路径枚举(DFS 迭代栈——图合法性校验保证单入口无环,路径有限)
@@ -732,12 +732,12 @@ func _smoke_tree_rules() -> void:
var lr = load("res://core/level_runner.gd").new(_db, _fresh_rng(777004),
{"tree_rules": []}) # 构造注入(拆件批 T3——setter 群退役)
Kit.check("散未激活:商品标价原价(无 economy_spend 键)",
int(lr.shop.current_price({"base_price": 100, "price_increment": 0})) == 100 + int(_db.run.shop.price_level_bonus_at(0)))
int(lr.current_price({"base_price": 100, "price_increment": 0})) == 100 + int(_db.run.shop.price_level_bonus_at(0)))
lr = load("res://core/level_runner.gd").new(_db, _fresh_rng(777005),
{"tree_rules": ["economy_spend"]}) # 构造注入(拆件批 T3——setter 群退役)
Kit.check("散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)",
int(lr.shop.current_price({"base_price": 100, "price_increment": 0})) == maxi(1, int(floor(float(100 + int(_db.run.shop.price_level_bonus_at(0))) * float(_db.run.shop.spend_discount))))
and int(lr.shop.current_price({"base_price": 1, "price_increment": 0})) == maxi(1, int(floor(float(1 + int(_db.run.shop.price_level_bonus_at(0))) * float(_db.run.shop.spend_discount)))))
int(lr.current_price({"base_price": 100, "price_increment": 0})) == maxi(1, int(floor(float(100 + int(_db.run.shop.price_level_bonus_at(0))) * float(_db.run.shop.spend_discount))))
and int(lr.current_price({"base_price": 1, "price_increment": 0})) == maxi(1, int(floor(float(1 + int(_db.run.shop.price_level_bonus_at(0))) * float(_db.run.shop.spend_discount)))))
# 财富之道·聚:战斗掉落乘算·入账前(编排 _roll_gp_drop——同种子配对对拍 1.5×)
var orch3 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(777005))
Kit.check("聚 rig:基线编排启动(无增益侧)",
+3 -3
View File
@@ -42,9 +42,9 @@ func _init() -> void:
Kit.check_quiet(not e.stop().skip_empty_curio(),
"空配置闸反例:非空停点拒绝跳过(零自动推进契约不破)〔专长批 T10〕")
var lr = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new())
lr.shop.set_shop_pool([], Callable(), Callable(), Callable())
lr.shop._draw_shelf()
Kit.check_quiet((lr.shop.shop_bp_shelf() as Array).is_empty(),
lr.set_shop_pool([], Callable(), Callable(), Callable())
lr._shop_draw_shelf()
Kit.check_quiet((lr.shop_bp_shelf() as Array).is_empty(),
"空配置闸:商店空货架=合法态(牌库空=零扰动不崩)〔专长批 T10〕")
quit(Kit.finish("pool_case_d"))
+1 -6
View File
@@ -14,7 +14,6 @@ const RngScript := preload("res://core/rng_service.gd")
const BoardGeneratorScript := preload("res://core/board_generator.gd")
const RewardEconomyScript := preload("res://core/reward_economy.gd")
const OrchestratorScript := preload("res://core/battle_orchestrator.gd")
const BoardInstallerScript := preload("res://core/board_installer.gd")
const TAG_OK := "board_gen" # 在册合法标签(PURPOSE_TAGS 含;wr7 必须用合法标签——未知标签检查排在播种检查之前)
@@ -65,15 +64,11 @@ func _default_form(db) -> bool:
# ①wr5(p5):两协作件违约经组装点接线传导 → orchestrator.battle_result 落 "fatal_error"
# (棋盘生成半边:装板域拆件后生成器随 BoardInstaller 件内自建——组装点=引用束 fatal_cb 接线,
# 经新组装点构建后触发生成器违约,封印传导断言不变)
func _seal_propagation(db) -> bool:
var rng_a = RngScript.new()
rng_a.set_master_seed(5)
var oc_bg = OrchestratorScript.new(db, rng_a)
var bi_bg = BoardInstallerScript.new({"db": db, "rng": rng_a, "minesweeper": oc_bg.minesweeper,
"reward_economy": oc_bg._reward_economy, "encounter": oc_bg.encounter, "fatal_cb": oc_bg._fatal})
bi_bg.generator._fatal("测试违约①棋盘生成")
oc_bg._board_generator._fatal("测试违约①棋盘生成")
if oc_bg.battle_result != "fatal_error":
push_error("[run_time_fatal_test/wr5] 棋盘生成违约未传导封印(battle_result=%s)" % str(oc_bg.battle_result))
return false
+1 -1
View File
@@ -1,6 +1,6 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# 结算五段 Segment 单测(Segment 类直接 new() + 编排器段序
# 接线 + 真实一轮结算走段序出裁定/行)。
# 接线 + 真实一轮结算走段序出裁定/行)。段体逐位一致性由 parity_anchor 三变体对拍承载,
# 本测兜段类可独立实例化与编排器装配面。
# 运行:godot --headless --path game --script res://tests/unit/core/settle_segments_test.gd(expect 0)
extends SceneTree
@@ -226,11 +226,11 @@ func _init() -> void:
# ⑩ 刷新执照(商店免费刷新 +2/店——读数叠加)
var lv10 = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new())
Kit.check_quiet(int(lv10.shop.shop_refresh_free_left()) == int(db.run.adventure.shop_refresh_free),
Kit.check_quiet(int(lv10.shop_refresh_free_left()) == int(db.run.adventure.shop_refresh_free),
"刷新执照基线:未装配=表值(%d)" % int(db.run.adventure.shop_refresh_free))
lv10 = load("res://core/level_runner.gd").new(db, load("res://core/rng_service.gd").new(),
{"attunement": ["refresh_license"]}) # 构造注入(拆件批 T3——setter 群退役)
Kit.check_quiet(int(lv10.shop.shop_refresh_free_left()) == int(db.run.adventure.shop_refresh_free) + 2,
Kit.check_quiet(int(lv10.shop_refresh_free_left()) == int(db.run.adventure.shop_refresh_free) + 2,
"刷新执照正常:免费刷新 +2/店(读数即本店可用——进店重置生命周期同 knob)")
# ⑪ 天眼(3×3 真视+安全格直开/危险格不开/封印格跳过)
+1 -1
View File
@@ -3,7 +3,7 @@
# 在册词表组合;本测试直构 rebuild 同构合成行(与 effect_engine.gd rebuild 树规则段逐字段同源)
# 调 validate_trigger_row 断零违例。实现侧扩闭集未呈报时,本断言以在册词表为准转红。
# 聚散两键(economy_hoard/economy_spend)不入事件面:消费点=tree_rules 成员判定
# (battle_orchestrator._roll_gp_drop GP 掉落乘算 / shop_session._spend_discounted 商品折价),
# (battle_orchestrator._roll_gp_drop GP 掉落乘算 / level_runner._spend_discounted 商品折价),
# rebuild 树规则段对两键零合成行——此状态在此被显式断言(旧版对两键取空夹具静默跳过=假绿):
# rebuild 实装面行数对照——四键逐键与夹具同行数(rebuild 分支被删/漂移即转红),
# 两键断零行(将来被加合成行即转红,提示先复核效果闭集再同步夹具与实现)。
@@ -675,9 +675,8 @@ func _smoke_contrast_inks() -> void:
Kit.check("V4 RAP 反馈行 STONE_DEEP 对数据板 %.2f:1 ≥4.5:1" % r_rap, r_rap >= 4.5)
# 消费点源码锚定:新墨在位、旧裸用点清零、语义岗常量不动
var sb_src: String = FileAccess.get_file_as_string("res://presentation/views/standee_body.gd")
Kit.check("V4 顺位徽标①②③ 墨=GOLD_BRIGHT(FS_HINT 14 提档原位——迭代 43 V7 换代替代,旧裸 13 清零)",
sb_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.GOLD_BRIGHT")
and not sb_src.contains("-1, 13, UiKit.GOLD_BRIGHT"))
Kit.check("V4 顺位徽标①②③ 墨=GOLD_BRIGHT(13px 行原位)",
sb_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, 13, UiKit.GOLD_BRIGHT"))
Kit.check("V4 魔法护甲徽标墨=HERO_BLUE_BRIGHT", sb_src.contains("col = UiKit.HERO_BLUE_BRIGHT"))
Kit.check("V4 重甲徽标墨=GOLD_BRIGHT", sb_src.contains("col = UiKit.GOLD_BRIGHT"))
Kit.check("V4 意图蓄力档判据=分档布尔(standee_body:179 CHARGE_SKILL_IDS 判定;旧 GOLD_DEEP 恒不可达判据清零)〔wd2〕",
@@ -0,0 +1,21 @@
# 迭代 43 代拍板追认单——战斗一屏样张批(2026-10-29)
> 接收方:待办清单条目「战斗一屏样张 P0 未开工」(随实现报告一并呈报,用户追认后随反哺点一并处置)。
> 卡片 §10 代拍板白名单四项内行使的记录+白名单边缘判断的追认请求。超白名单事项零(无越权裁断)。
## 白名单内行使记录
1. **样张脚本内临时变量命名、辅助函数拆分方式**:`draw_base()`/`_grad_top_cells()`/`_region_wcag()`/`_mask_maxch_dist()`/`_resample_mask()`/`_mask_iou()`/`_outline_underlay()`/`_ink_outline_polygon()` 等拆分;实装侧 `_armor_tex_path()`/`_name_plate_rect()`/`_name_baseline()`/`_draw_plinth_stretched()`。
2. **断言在 CHECKS 内的 id 命名与登记顺序**:S1–S4(整屏四项)、P1–P5(战斗表现五项)、A7(护甲对比锚)、V1(两版出口)、V10(基线并排)、A5b(成稿净空)。
3. **程序化产出素材的绘制实现细节**(颜色全取 v3.22 在册值、零新增色):地台件按工单 §5.2 工艺逐行(1px 上描边 #4C4038+2px 顶缘受光线 #C9AF83+8px 板身 #968264+1px 下描边,全周闭合);护甲三枚按 §5.3.1–5.3.3 形状(轻甲=斜跨皮带+扣件、魔甲=符纹带+两枚 3×3 符点、重甲=肩两块+胸一块甲板,深墨 #33261B 1px 描边)。
4. **两版样张输出文件名与落盘目录**:`artwork/mockups/sample_battle_screen_{1280x800,1920x1080}.png`;附属:基线 `sample_battle_screen_baseline_1280.png`、并排 `sample_battle_screen_before_after.png`、装配稿沿用 `sample-batch-v3-final-2026-09-30.png`。
## 追认请求(白名单边缘判断,已按实现决定记录备案)
5. **S3 首焦唯一的机器代理换型**:首版「最高对比点簇 ∈ 单区 ≥85%」被文字字形边打穿(红),按卡片 §9 L1(断言红修实现不修判据原则的反向面——判据为本批新建,代理形态属白名单 2「断言实现细节」)改为「高对比能量占比 winner ≥55% 且 ∈{数字区,B 区}」。语义等价(无第二焦点竞争),实测数字区 59%。
6. **P2 剪影灰度 IoU 阈值 0.35**:新建断言的初始阈值,按三语境实测(0.36/0.37/0.61)与「同角色体块计划过半重合」的设计判断设定。
7. **mockup C 区次级文换墨**:原裸值 (140,90,30)/(120,110,98) 对 C 区板 4.06:1/3.5:1 违整屏①——换在册墨 GOLD_INK(5.34:1)/STONE_DEEP(6.13:1),值单点不新增色。
8. **mockup 意图徽标补 PANEL_DATA 垫牌**:实装已有素牌而 mockup 漏镜像(裸字形对舞台底 2.36:1 违①),补牌对齐实装。
9. **A5b 补充断言**:对装配后成稿补跑画布净空(既有 A5 历史形态跑在空白底板上)——首跑即抓出注记长行溢出 6px,已拆行修复。
10. **单测承载选型**:V5–V9 落新套件 `standee_present_values_test.gd`(manifest 登记一行)而非并入 standee_fx_sweep——域命名与 grep 锚更干净,套件族映射自动覆盖。
11. **数据保真从紧**:三敌甲档按数据面全 light,护甲三枚展示移 S6 附录——不为展示效果改 mockup 数据面。
+21
View File
@@ -0,0 +1,21 @@
# 迭代 43 反哺点清单——战斗一屏样张批(2026-10-29)
> 接收方:待办清单(文档批落文裁决用)。用户在会话中逐条裁决后由文档批写入 `design/` 与根流程件,并把该条清掉;实现批对 `design/` 与根规则文件只读。
## 卡片 §6 预登记的关联文档同步面
1. **`design/gdd/ux/battle.md`**——B 区高度:文 270 vs 代码 252 的 18px 差异。本批样张按代码 252 出(样张反映实机),实装对齐与否待用户裁决(对齐=需求改动走写卡通道)。
2. **`design/art/art-bible.md`(v3.22)**——§1.4 整屏四项闸的通过事实与抽样读数(文字对比最弱 5.34:1;舞台底 L0.036/格底 L0.089 落两子带间隔 0.053;首焦=数字区能量占比 59%;名字行素底牌平底零地台色);§6.1/§7.2 站桩素底深档 `#282220` 实装落地事实(名字行素底牌+基线上移地台顶上方)。
3. **`design/art/art-prompts.md` §5**——组二地台件、组三护甲三枚已「程序化产出即终稿」并入包(`scene_stage_plaza.png` 48×12、`ui_armor_{light,ward,heavy}.png` 24×24);组一背景插画、组四立绘重产仍待出图(状态不变)。
4. **`design/gdd/systems/战斗循环/战斗表现.md`**——「一屏样张的验收标准」五项过闸结果(P1–P5 断言全绿;判据机器代理口径见实现报告 §3.2–3.3)。注意:本批样张为**方向稿**,目检先行——气质终审通过后才算样张整体过闸(§1.4 闸形)。
5. **`design/engineering/test-conventions.md`**——§8.4 撤销证明条文与 2026-10-28 新验证口径(禁变异/禁撤销取证)冲突,旧句待清。
6. **`AGENTS.md`**——「验收闭合」红线句仍保留「撤销该改动能让对应断言变红」旧措辞,与新口径冲突。
## 本批新发现(实现期产出,非卡片预登记)
7. **血条递减 tween 静默空转(既有缺陷)——已消解**——本批随测发现 `standee_body.gd` `_set_hp()` 补间用点号子属性路径(应为子对象/冒号形态);收口并 master 时已由迭代 41 测试债批修复(补间目标改子对象 `tween_property(st, "hp_value", ...)`,提交 215da315),合入态复跑单测零 ERROR。本条无需落文,留档备查。
8. **站桩名字行 12px 的字号档位归属**——`standee_body.gd` 名字行绘制取 `UiKit.FS_CAPTION`(12,本批仅收常量不改值);art-bible §7.4 字号档位表下该位既不在①条带内读数豁免、也不在②装饰 caption 在册面,属「清单外 11–13px」。处置二选一由用户裁:提档(FS_HINT 14,与顺位徽标同款)或增补 caption 在册面。
9. **能力清单缺本批预留行**——`game/tests/README.md` 能力清单无本批 V5–V9 代表用例行(卡片成文早于清单 2026-10-29 改版定稿)。归写卡段按 test-conventions §8.3 补行(代表用例=`game/tests/unit/presentation/standee_present_values_test.gd:_init`,真实入口=`UiKit.load_tex()`/`StandeeBody.name_line_probe()`)。
10. **art-bible §4.1 换色合并三处的执行状态**——地台绘制点半边已由地台件纹理执行(顶缘受光线 `#C9AF83`、描边 `#4C4038` 落 `scene_stage_plaza.png`);残余:① `STAGE_BG`(#3E342A→#3E362C)未执行(本批不触 `battle_stage_view.gd` 底色);② `STAND_STONE_RIM`(#C4AC84)仍被 `board_cell_unit.gd:359` 口沿高光在役引用;③ `STAND_STONE_EDGE`(#5A4C3A)由地台缺位回退路径在役引用。常量彻底退役与否归文档批裁决。
11. **体验验证清单登记**——「战斗一屏样张方向稿目检」项(判据与材料见实现报告 §5;真人检测只登记不阻塞)。
12. **根流程件换代**——待办清单条目「战斗一屏样张 P0 未开工」状态推进(本批实现完成后按 doc-conventions §12.1 整条删除);roadmap 迭代 43 行状态与实现报告列、「当前位」三行换代;样张产物归档位=`artwork/archive/战斗一屏样张批-2026-10-29/`(batch-closure 执行)。
+82
View File
@@ -0,0 +1,82 @@
# 迭代 43 实现报告——战斗一屏样张批(2026-10-29)
> 接收方:待办清单条目「战斗一屏样张 P0 未开工」;roadmap 迭代 43 行。
> 分支 iter43(基准 ee4efad5)· 单工作区单卡串行 · 实现报告/追认单/反哺点清单三件同日落 `reports/`。
## 1. 各卡状态
| 卡 | 内容 | 状态 |
|---|---|---|
| T0 | 开工准备(worktree+素材导入+user 目录+v3 脚本基线跑) | 完成(基线 17/17 全绿存档 `artwork/mockups/sample_battle_screen_baseline_1280.png`) |
| T1 | 样张底板两版画布 | 完成(`draw_base()`+两版出口,机检 V1 绿) |
| T2 | 内容清单铺画 | 完成(色板变体第二套 S3b/三组关键帧条 S5/剪影三语境+描边对照+遮挡对照组 S6/mockup 镜像实装定值) |
| T3 | 整屏四项断言进 CHECKS | 完成(S1–S4 全绿) |
| T4 | 战斗表现五项断言进 CHECKS | 完成(P1–P5 全绿+A7 护甲对比锚) |
| T5 | 血条填充换 #A8321F(经 UiKit 常量)、条底收常量 | 完成 |
| T6 | 名字行垫 HP_BAR_BACK 素底、不压地台 | 完成 |
| T7 | 顺位徽标提档 UiKit.FS_HINT | 完成 |
| T8 | 护甲图标由字形换三枚 24×24 PNG | 完成 |
| T9 | 地台件与护甲三枚程序化产出并入包 | 完成(`--gen-assets` 四件,工单 §5.2/§5.3 口径) |
| T10 | 三份批件+改前/改后并排对拍图 | 完成(并排图机检 V10 绿) |
**样张机检:30/30 全绿**(17 条既有 F/A 断言+S1–S4+P1–P5+A7+V1+V10+A5b)。
**单元套件:standee_present_values 18/18 全绿**(exit 0)。
## 2. 实际改动—验收对照表
> 卡片成文(2026-10-28)早于迭代 41 呈现层结构收敛合入(2026-10-29),卡片 §3/§8 的 `standee_body.gd` 行号普遍漂移;实质判据 15 处实查零冲突(开工检查记录见 git 提交史)。本表按**实际行号**记载。
| # | 需求点 | 改动(实际 file:line) | 验收测试(文件:行+尾缀) | 层级 | 真实入口 | 判据绑定面 | 结果 |
|---|---|---|---|---|---|---|---|
| V1 | 样张两版产出 | `tools/render_sample_sheet_v3.py` main() 两版出口(1280×800 基座;1920×1080=1.35×+黑边各 96px) | `tools/render_sample_sheet_v3.py` CHECKS 条目 V1 | 探针 | `python tools/render_sample_sheet_v3.py` 真实跑批 | 两版位图尺寸 1280×800 / 1920×1080 | 绿(ed1 (1280,800)/ed2 (1920,1080)) |
| V2 | 文字对比 ≥4.5:1 | 同上(S1 断言+mockup C 区次级文换在册墨 STONE_DEEP/GOLD_INK) | CHECKS 条目 S1(最弱对 5.34:1) | 探针 | 同上 | 八组文字面实际用色 WCAG 对表 | 绿 |
| V3 | 明度三档带成立(含两子带) | 同上(S2 断言) | CHECKS 条目 S2 | 探针 | 同上 | 舞台底 L0.036/已开格底 L0.089 落两子带、间隔 0.053≥0.02;信息 0.78>中层 0.23 | 绿 |
| V4 | 首焦唯一 | 同上(S3 断言) | CHECKS 条目 S3 | 探针 | 同上 | 高对比能量占比:数字区 59%(≥55% 且 winner∈候选区) | 绿 |
| V5 | 血条=危险红 #A8321F | `standee_body.gd:525` 填充引 `UiKit.HP_BAR_FILL`;`:522` 条底引 `HP_BAR_TRACK`;`ui_kit.gd:194-195` 两常量 | `standee_present_values_test.gd:28-35`〔样张批 V5〕 | 单元 | 冒烟套真实跑批(run_all manifest) | 常量值 to_html=="a8321f"+源码锚(绘制点引用+全源零 #689CE8/零裸 Color(0.41,0.61,0.91)) | 绿(headless 无渲染面,像素采样以常量单点+源码锚承载——观测面注记) |
| V6 | 名字行垫素底、不压地台 | `standee_body.gd:445` `_name_plate_rect()` 几何单点(牌底沿=地台顶)、`:449` `_name_baseline()`、`:570-588` 绘制域;`name_line_probe()`(`:100-109`)扩几何出口 | `standee_present_values_test.gd:45-51`〔样张批 V6〕 | 单元 | 同上 | probe 几何:基线 y=149 < 地台顶 y=158;牌色 #282220;样张侧 S4 断言(平底 100%+零地台板身色) | 绿 |
| V7 | 顺位徽标提档 14 | `standee_body.gd:519` 裸 13 → `UiKit.FS_HINT` | `standee_present_values_test.gd:55-56`〔样张批 V7〕 | 单元 | 同上 | FS_HINT==14+绘制点源码锚(grep 该处零裸字号) | 绿 |
| V8 | 护甲图标换三枚 PNG | `standee_body.gd:432` `_armor_tex_path()`(三键→`UiKit.TEX_ARMOR_*`)、`:570-588` draw_texture_rect 24×24(缺位回退字形);`ui_kit.gd:253-255` 路径单点 | `standee_present_values_test.gd:64-71`〔样张批 V8〕 | 单元 | 同上 | 三键 ResourceLoader 装载 24×24+probe armor_icon 出绑定路径 | 绿 |
| V9 | 地台件与护甲三枚入包 | `game/assets/art/scene/scene_stage_plaza.png`(48×12,不入 git)+`game/assets/ui/icons/ui_armor_{light,ward,heavy}.png`(24×24,入 git);`standee_body.gd:493`/`:502` 九宫格横向拉伸接装 | `standee_present_values_test.gd:76-81`〔样张批 V9〕 | 接口 | 同上 | 资源可加载且尺寸 48×12 / 24×24 | 绿 |
| V10 | 改前/改后对拍图产出 | T0 存基线(1280×800);main() 生成并排图 | CHECKS 条目 V10 | 探针 | 样张脚本真实跑批 | 基线版与改后版两源文件尺寸一致+并排图落盘 | 绿(`sample_battle_screen_before_after.png` 2570×810) |
**反向覆盖**:diff 内全部行为改动均上表有行;非行为改动=`run_all.gd` manifest 一行登记(套件注册,非游戏行为)、测试 `.uid` 边车、样张脚本注释与文档头扩展。孤儿断言:无(样张 CHECKS 全部挂 V1–V4/V10/S/P/A7;单测 18 条全挂 V5–V9)。
## 3. 实现决定记录(代拍板白名单内行使+边界判断备案)
1. **断言 id 命名与登记顺序**(白名单 2):整屏四项=S1–S4、战斗表现五项=P1–P5、护甲对比锚=A7、两版出口=V1、基线并排=V10、成稿净空=A5b。
2. **S3 首焦唯一的机器代理**:首版「最高对比点簇 ∈ 单区 ≥85%」被文字字形边打穿(各文字面对比同档,簇分裂 23%/25%)。改用**高对比能量占比**(top10% 梯度格按区归堆,winner ≥55% 且 ∈{数字区,B 区立绘})——语义=「对比内容量集中单区,无第二焦点竞争」,与 battle.md「挖掘期视线锚棋盘区」同轴。实测:数字区 59%/B 区 20%/C 区 17%。
3. **P2 IoU 阈值 0.35 校准依据**:三语境剪影两两 IoU 实测 0.36/0.37/0.61(棋盘×明细同源资产最高;舞台立绘×棋盘小人跨源档最低)。0.35=「过半体块计划重合」档,按同角色 chibi 渲染的设计判断设定,非放宽既有断言(本断言为新建)。
4. **HP_BAR_TRACK 新常量**=视图原裸值 `Color(0.10,0.10,0.11)` 原样收口(值不属色板新增——现渲染值单点化,卡片 §7.1「条底收常量」的直接执行)。
5. **STAND_STONE_RIM/EDGE 两常量保留**:RIM 另有 `board_cell_unit.gd:359` 口沿高光在役引用(本批不触该文件);EDGE 由地台缺位回退路径在役引用。art-bible §4.1 换色合并三处的**地台绘制点半边**已由地台件纹理(#C9AF83/#4C4038)执行;常量彻底退役与 STAGE_BG 合并半边归文档批裁决(反哺点 10)。
6. **意图徽标垫牌镜像补齐**:实装 `standee_body.gd` 意图徽标有 PANEL_DATA 素牌(四色墨对牌 4.7–5.8:1),mockup 原裸画字形(对舞台底 2.36:1 违 S1)——mockup 补牌对齐实装。
7. **护甲三枚数据保真**:三敌甲档按数据面全 light(goblin/bugbear/ogre_chief),不为展示图标改甲档;三枚图标的展示落 S6 附录(挂 HP_BAR_BACK 素底,与实装挂载底同口径)。
8. **观测面注记(test-conventions §8.4 出口)**:headless 无渲染面,V5/V7 的「像素采样色值」以常量值断言+绘制点源码锚+探针几何承载;V6 判据「基线 y < 地台顶 y」绑 `name_line_probe()` 几何出口(公开方法,非内部字段直查)。
9. **A5b 补充断言**:既有 A5(画布四缘净空)跑在空白底板上(v3.5 冻结形态),本批补 A5b 对**装配后成稿**跑同一判据——首跑抓出 S4 注记长行溢出右缘 6px(已拆行修复),确证其价值。
10. **样张输出文件名与落盘目录**(白名单 4):两版=`artwork/mockups/sample_battle_screen_{1280x800,1920x1080}.png`;基线=`sample_battle_screen_baseline_1280.png`;并排=`sample_battle_screen_before_after.png`;装配稿=原 `sample-batch-v3-final-2026-09-30.png`(1560×2080)。
11. **单测承载选型**:V5–V9 落新套件 `standee_present_values_test.gd`(单元面按 §8.2 从领域类对外方法进出)而非既有 standee_fx_sweep(其 _init 为缺陷扫掠专用段)——manifest 登记一行;闸映射 `game/presentation/*`→presentation 族自动覆盖。
12. **能力清单缺口**:本批 V5–V9 代表用例行在 `game/tests/README.md` 能力清单**无预留行**(卡片成文早于清单改版定稿)——实现批禁写 README,归写卡段补行(反哺点 9);测试函数名以卡片 V1–V10 为唯一锚。
## 4. 存量红判别(闸前自检;合入态更新)
- S10 文件头 4 处(`config_db.gd`/`config_domain_{combat,crossrun,run}.gd`)与 S11 禁用词 1 处(`adventure_table.gd:55`「罕见」):本批快照(ee4efad5)实证为存量;**收口并 master(迭代 41 测试债批 215da315)时已被该批清偿归绿**——合入态两键零红。
- gdstyle S13:基线外新违规 0、较登记值恶化 0(`standee_body.gd` 类变量计数未越基线 19——纹理装载走 `UiKit.load_tex` 即取即绘,零新增成员)。
- **本批零新增红。**
## 5. 实机目检总清单(真人检测只登记不阻塞)
| 项 | 判据 | 材料 |
|---|---|---|
| 方向稿目检(卡片 §4(3)——目检先行,机检冻结归目检通过后) | 气质=明亮地窟看台、立绘最抢眼、无压抑;构图/分区/明度三档带/首焦落点与设计意图一致 | `artwork/mockups/sample_battle_screen_1280x800.png`、`sample_battle_screen_1920x1080.png`(批末归档 `artwork/archive/战斗一屏样张批-2026-10-29/`) |
| 改前/改后对照 | 五项定值前后差异可辨(血条色/素底牌/徽标/护甲图标/地台件) | `sample_battle_screen_before_after.png` |
登记义务同步进反哺点清单(体验验证清单归文档批登记——实现批禁写根流程清单)。
## 6. 遗留读数义务与观测记录
- MC/verifier:**零跑**(卡片未要求 MC 对拍;本批无玩法数值改动)。
- 全量回归:合回后由用户自发(2026-10-29 关联档选测口径,无自动兜底)。
- 既有潜在缺陷(随测发现):`standee_body.gd` 血条 tween `tween_property(self, "st.hp_value", ...)` 点号子属性路径静默空转——**收口并 master 时已由迭代 41 测试债批修复**(补间目标改子对象 `tween_property(st, "hp_value", ...)`,215da315),合入态复跑单测无该 ERROR,无遗留义务。
## 7. 独立验收审计结论
(合并前由未参与实现的子代理执行,结论见 `reports/iter43-验收审计-2026-10-29.md`;未通过不得合并。)
@@ -0,0 +1,230 @@
# 需求卡片——gdstyle 尾波清偿批
> **版本状态**:v2.0 定稿——设计段裁决已闭合(D1–D6 裁决记录见第 6 节,2026-10-29 用户裁),零待裁点,可编排。
> **接收方**:待办清单条目「gdstyle 尾波清偿批——基线余量 11 键定向整改(2026-10-29 登记)」。
> **来源**:gdstyle 尾波清偿分析(2026-10-29 基线重锚后余量清点)。**唯一事实源**:`tools/gdstyle_baseline.txt`(2026-10-29 重锚版,余量 16 键)+ `reports/审计-gdstyle存量债清点与清偿方案-2026-10-26.md`(§三波次方案、§六文档设计段裁定记录——裁定全部继续有效)。
## 1. 目标与完成定义
这批做什么:把 gdstyle 静态质量闸(S13)基线余量 16 键中的 11 键定向清偿——6 键降到阈值内(闸不再记它,下称归绿),5 键按第 6 节裁决维持或降值后走 architecture 豁免登记(批末随实测值落文)。手段只有两类:存量文件内部拆私有函数(呈现层先例)、按职责拆协作件(核心层先例)。
什么状态算完:
1. 11 键逐键达到第 7 节写明的预期结果(归绿 6/豁免 5——豁免键实测值落 architecture 豁免登记段)。
2. 游戏行为零变化:同种子下事件流、步骤流、断点快照、棋盘渲染逐位一致(第 4 节红线)。
3. 批末代码域闸全绿(冒烟+单元+集成+探针+渲染 golden+静态扫描),键账对账完成(第 13 节)。
4. 实现报告与反哺点清单落 `reports/`(末卡产出物落位卡)。
## 2. 设计事实源
本卡属需求性工程条目(结构整改、改游戏代码面),按五段流水线凭工程债证据写卡;四个拆件项的设计段裁决于 2026-10-29 经用户裁闭合(记录见第 6 节)。证据指认:
- `tools/gdstyle_baseline.txt`——2026-10-29 人工重锚后的现行基线,16 键;本卡 11 键逐条列于第 3 节,键名与本卡逐字一致。
- `reports/审计-gdstyle存量债清点与清偿方案-2026-10-26.md` §六裁定记录 7 条(段组织、波 2/波 6 形态、config_db 定性、判定口径、独立审查折入、2026-10-27 三卡归并)——继续有效,本卡沿用其中对呈现层「内部拆私有函数、不拆构建步件」与「全部波不改游戏规则与玩家可见行为」的裁定。
- `design/engineering/architecture/architecture.md` v1.24——轴丙 BoardInstaller(装板域拆件)已随本卡设计闭合落文(模块归属功能层新行+拆件定稿节增补),实现照册施工。
- 并排执行卡:`reports/需求卡片-level_runner商店委托层拆除-2026-10-29.md` v1.1(键 7 拆除执行合同——D6 并排编队,见第 6 节)。
## 3. 现状事实(代码级,开工自行复核;行号为 2026-10-29 实查)
基线 16 键中本批承接 11 键,其余 5 键(`main.gd` 类变量 49、`tune_sim_controller.gd` 循环内 await、`board_cell_unit.gd` 类变量 30、`standee_body.gd` 类变量 19、`battle_orchestrator.gd` 类变量 38)走豁免通道另行讨论,不在本卡。
| # | 基线键 | 现状/阈值 | 涉案位置 |
|---|---|---|---|
| 1 | `board_view.gd\|quality/max-function-length\|_paint_cell = 139` | 139/50 | `game/presentation/views/board_view.gd:601-739` |
| 2 | `board_view.gd\|quality/max-branches\|_paint_cell = 24` | 24/8 | 同上 |
| 3 | `board_view.gd\|quality/max-local-variables\|_paint_cell = 22` | 22/10 | 同上 |
| 4 | `board_view.gd\|quality/allocation-in-loop`(裸键) | 命中 | `board_view.gd:249`——rebuild() 建格循环内 `_CELL_UNIT.new()` |
| 5 | `board_view.gd\|quality/max-class-variables = 32` | 32/15 | `board_view.gd:49-85`(var 成员 31 + `cell_size` 只读属性 1,以闸实测为准) |
| 6 | `board_view.gd\|quality/max-public-methods = 27` | 27/20 | 全文件:11 setter+6 探针出口+4 几何契约出口+3 入站事件+2 生命周期(rebuild/refresh)+1 访问器(board()) |
| 7 | `level_runner.gd\|quality/max-public-methods = 50` | 50/20 | `game/core/level_runner.gd`——其中商店同签名委托 17 个公开方法(751-829 行)+3 个私有转发(`_bind_shop`:728、`_spend_discounted`:772、`_shop_draw_shelf`:783)+`_shop` 成员(:44) |
| 8 | `level_runner.gd\|quality/max-class-variables = 30` | 30/15 | `level_runner.gd` 类成员区(含 `hero_hp`/`gp` 两属性) |
| 9 | `level_runner.gd\|quality/max-returns\|_enter_node = 7` | 7/6 | `level_runner.gd:486-513`——shop 分支 pool/legacy 双 return(:509、:511) |
| 10 | `battle_orchestrator.gd\|quality/max-file-length = 1035` | 1035/1000 | `game/core/battle_orchestrator.gd`——装板与变异布置域六函数合计约 140 行(:414-549) |
| 11 | `adventure_runner.gd\|quality/max-class-variables = 28` | 28/15 | `game/core/adventure_runner.gd:13-45` |
支撑事实:
- board_view 公开方法 27 个全是契约面:setter 是组合根注入面(依赖单向纪律),探针出口(cells_probe/diggable_probe/hover_probe/fog_params_probe/type_glow_probe/cell_rect_probe)是探针断言面,几何出口与入站事件是演出与输入契约。可降空间极小。
- battle_orchestrator 装板域六函数全仓零外部调用,仅本文件三处调用(:271 begin_battle、:801 _exit_distribution、:835 player_request_refresh)——拆件不触任何外部调用面。
- 六函数现行均无超标键(基线无其函数级键);`_install_new_board` 体内 442-451 行为宿主态复位块(五处计数清零+板序推进+board_changed 发射+秘宝叠加层退役)——按第 6 节 D1 裁决留宿主薄壳。
- `_board_generator` 全文件仅四处使用(:42 声明、:233 创建、:239 fatal 接线、:416 装板消费)——装板族独占,随件迁移后宿主成员退役(38→37)。
- level_runner 商店委托面被呈现层 5 文件(camp_shop_presenter/main_adventure_flow/main_overlay_flow/main_ui_build/shop_overlay_view)、`game/sim/level_autoplay.gd`、测试 4 套件(level_shop_economy/pool_case_d/tree_rules_v11/hero_ledger)引用——shop_overlay_view 三处命中均为注释投影引用(:15/:88/:152 实查),代码调用面在 presenter/组合根拆件/sim 侧。
- `tools/run_all_gates.sh` PARITY_TOUCH_FACES 含 `game/core/battle_orchestrator.gd`——本批必触面。
- level_runner 商店委托面的完整行级调用点清单(19 壳含 2 死壳、跨类同名陷阱、呈现/sim/测试逐点行号)以并排卡 v1.1 §3 为准——本卡不重复罗列。
## 4. 设计契约(违反即返工的红线条款)
- 🔴 **行为零变化**:本批全部改动为结构整改,游戏行为零变化——同种子下事件流/步骤流/断点快照/存档往返/棋盘渲染逐位一致;玩家可见面零变化;`design/gdd/systems/` 零修订预期。
- 🔴 **RNG 取号序零漂移**:禁 `randomize()`/自建随机;battle_orchestrator 装板域拆件时 `board_gen`/`variant_place` 两封闭流随件迁移**取号序不变**(流名、开流时点、消费序零漂移——先例句「fx_reveal 封闭流随件迁移取号序不变」同款)。
- 🔴 **PARITY 触面**:battle_orchestrator.gd 触 PARITY_TOUCH_FACES——实跑对拍须经用户批准、不得借机重锚。
- 🔴 **呈现零变化**:board_view 改动的画面判据=`game/tests/golden/` 既有三张棋盘基线(L1 dungeon/L2 underdark/L3 deep_underdark),对拍差异非零即红;重建基线只许用户批准,实现批禁自行换锚。
- 🔴 **拆分边界纪律**(判据权威=code-conventions §1.1,此处立卡内执行条款):①每个拆出单元须有可陈述的职责语句与单一变化原因(实现报告逐件一行承载);②拆出单元须有集体身份——命不出名字、只能说「剩下的成员」的打包=凑计数袋,禁;③「过阈值」只许作为发现信号,作为拆分或收拢的动机即违规;④按纪律拆不出内聚单元 → 停卡呈报走豁免/还债通道,禁硬拆凑绿;⑤拆分沿职责轴不沿阈值轴。
- 🔴 **禁为机检文本保留无效成员**:禁止为迎合机检消息文本(含计数)而保留或新增无效成员/别名——装板域拆件后宿主 `_board_generator` 成员随之退役(38→37);机检键与卡片契约互斥时,实现段一律停卡呈报,不得自造第三条路。
- 🔴 **基线纪律**:禁为过闸 refresh 换绿;还债=数值下降自然绿;出现恶化=停卡呈报(细则第 13 节)。
- 🔴 **attune 族与 begin_battle 序列禁动**:battle_orchestrator 的 attune 族原位保留(迭代 39 副卡语义领地);begin_battle 受理段序与五段驱动逻辑禁动——装板域拆件只改 `_install_new_board()` 调用点的取件改址。
## 5. 范围边界
做什么:第 3 节 11 键中本卡直接执行的三项改动(第 7 节逐键方案、第 12 节任务卡)——board_view 拆分(键 1–4)、_enter_node 载荷合并(键 9)、装板域拆件(键 10);键 7 的拆除执行合同=并排卡「level_runner 商店委托层拆除」v1.1(D6 裁定并排编队,见第 6 节)。
不做什么:
- 商店委托壳拆除的执行——D6 裁并排编队:执行合同=在册定稿卡 v1.1(零待裁点),本卡不重复执行、只承接键账与豁免登记。
- 悬停域拆件与断点域拆件——D2/D4 裁不拆(第 6 节),键 5/6/11 直送豁免终局。
- 5 个豁免讨论键(main.gd/tune_sim_controller/board_cell_unit/standee_body/battle_orchestrator 类变量)——去向:豁免通道批末与本批 5 键合并一次落文,本卡不落文、不落反哺点。
- 波次方案其余文件(审计报告 §三波 1–8)——已由迭代 39/40/41 清偿,不在本卡。
- `game/tests/README.md`——实现批禁写;GP 钱包行真实入口列已随本卡 v2.0 改为 ShopSession 直连(计划入口,闸双通道认卡),批末核对表上名字与实际落地一致。
- 数值表、RNG 流词表、信号载荷 schema——零触碰。
## 6. 设计段裁决记录(2026-10-29 用户裁——D1–D6 全部闭合)
| 编号 | 裁决 | 依据(推导链摘要) |
|---|---|---|
| D1 | ✅ 装板域拆新件 BoardInstaller,两修正 | 六函数构成可命名内聚单元(生成→决议→装板→摇号→布置,单一变化原因=棋盘设置顺序与变异布置算法);三调用点全宿主内实查零外泄;不在 v1.22 保留面;文件长键唯一归绿路径。**修正一**:复位块(442-451 行)留宿主薄壳 `_install_and_reset`,非逐字搬运;**修正二**:`_board_generator` 装板族独占(四处实查)随件迁移,宿主成员退役 38→37。已落 architecture v1.24 轴丙。 |
| D2 | ❌ 悬停域不拆 | 键 5/6 无一可由此归绿(类变量 32→30 仍红、公开方法持平 27);波 6 既定裁=普通视图内部拆私有函数、不立周边件;悬停 11 函数全部低于阈值(无 S13 标的),拆分仅剩计数动机、踩 §1.1-③ 灰区;新件+束注入+入站口委托的代价不成比例。键 5/6 直送豁免;`_r0` 归属认定随之作废。 |
| D3 | ✅ 商店域候选 A(删委托壳),`_shop` 公开化留宿主 | ShopSession 迭代 37 已建(33 公开方法实查),委托面=纯 Middle Man(2026-10-26 真拆分设计会话已裁「纯转传委托=反模式」);拆除方向 2026-10-26 已在册登记,本卡系执行既定发令;公开方法 50→33。`_shop` 公开化留宿主——`_enter_node` shop 分支与断点片段仍需持引用,非无效成员;类变量 30 不降,归豁免。执行合同=并排卡「level_runner 商店委托层拆除」v1.1(零待裁点)——其契约精度高于本卡初案(19 壳含 2 死壳行级清单、`_shop`→公开成员更名形态、两段式中间态、跨类同名判别),按其施工。 |
| D4 | ❌ 断点域不拆 | 键 11 无键可绿(28→22 仍红);现位置不违反任何已立边界——SlotStore 拆分立的界约束的是 save_manager 不吞断点逻辑,非要求断点逻辑离宿主,断点协议住在冒险生命周期 owner 内本身内聚;`_save_manager` 非断点独占(:371 boss 首杀读、:521 abandon 清断点写盘实查),件化须双持引用、束复杂度超收益;27 键冻结契约扰动收益为零。若未来断点协议自身扩展,届时按职责轴立件。键 11 直送豁免。 |
| D5 | ✅ 五个降值键豁免登记终局 | board_view 类变量 32/公开方法 27、level_runner 公开方法 33(删壳后)/类变量 30、adventure_runner 类变量 28——可降途径已按代价裁完,残留面全为契约承载或运行本态;无偿还计划的债=未注记豁免,不如把理由落权威文档。批末按实测值落 architecture「测试与质量域」S13 豁免登记段(先例=battle_orchestrator 第三条形态:键永久基线内、禁再恶化、豁免不等于了结)。 |
| D6 | ✅ 商店域并排编队(写卡期修正:原「吸收」案否决) | v2.0 初稿裁「本卡吸收在册条目、删除其卡」;落笔时发现该域已有定稿执行卡——「level_runner 商店委托层拆除」v1.1(2026-10-29,复核门通过零待裁点),契约精度高于本卡 T6 粗案,销定稿卡重写粗案=无收益损耗。改判:**两卡同迭代并排编队**(编排段并列排入、执行顺序先后皆可——level_runner 交叠为不相邻区段零冲突),本卡承接键账与豁免登记、不重复执行拆除;在册条目与其卡维持,随其自身批完成删除。 |
## 7. 逐键方案(11 键:现状值/阈值/方案方向/预期结果)
预期分两档:**归绿**(拆后实测低于阈值,下批重锚后键消失)与**豁免终局**(维持或降值,批末随实测落 architecture 豁免登记段)。
| 键 | 现状/阈值 | 方案方向 | 预期结果(档位) |
|---|---|---|---|
| 1 board_view _paint_cell 长度 | 139/50 | 拆分层私有函数族(键 1–3 同一改动,见 T1)——五段=基底瓦片→字形与色牌→立面投影→秘宝/变异叠加→迷雾,段间只经 u.st/glyph/digit_col 传值;段内分支仍超 8 时沿绘制层轴继续下钻 | ≤50,**归绿** |
| 2 board_view _paint_cell 分支 | 24/8 | 同上 | ≤8,**归绿** |
| 3 board_view _paint_cell 局部变量 | 22/10 | 同上 | ≤10,**归绿** |
| 4 board_view allocation-in-loop | 命中 | `.new()` 移入逐项构建私有函数 `_build_cell_unit(c, stride)`,循环体只调构建器(先例=battle_end_overlay._seal_img/_ledger_row 族) | 消除,**归绿** |
| 5 board_view 类变量 | 32/15 | 无拆分动作(D2 裁不拆——第 6 节) | 32 维持,**豁免终局** |
| 6 board_view 公开方法 | 27/20 | 无动作(27 个逐类核对全为探针契约面与依赖单向纪律承载面) | 27 维持,**豁免终局** |
| 7 level_runner 公开方法 | 50/20 | 执行合同=并排卡「level_runner 商店委托层拆除」v1.1(19 委托壳删除+`_shop` 公开化;本卡不重复执行——D6) | 约 33,**降值后豁免终局**(豁免登记随本卡批末落文) |
| 8 level_runner 类变量 | 30/15 | 无动作(`_shop` 留宿主——D3 裁,非无效成员) | 30 维持,**豁免终局** |
| 9 level_runner _enter_node 返回数 | 7/6 | shop 分支 if/else 双 return 合并为载荷变量单 return;两个 payload 字典逐字保持(pool 分支 `{"gp": gp, "shelf": true}`、legacy 分支 `{"gp": gp}`),零契约风险。备选(code-conventions §1.1-③ 红线):若实现期发现合并构成纯计数扭曲则留债呈报——本卡按「合并属同一停点语义的两条发射路径归一」认定有内聚收益 | 6,**归绿** |
| 10 battle_orchestrator 文件长 | 1035/1000 | D1 装板域拆件:六函数迁出+宿主薄壳承接复位块+`_board_generator` 成员退役 | 约 910–920(拆后实测),**归绿** |
| 11 adventure_runner 类变量 | 28/15 | 无动作(D4 裁不拆——第 6 节) | 28 维持,**豁免终局** |
## 8. 关联文档同步面
| 文档 | 关联点(本批实现会改变它的哪条陈述) | 同步义务 |
|---|---|---|
| design/engineering/architecture/architecture.md | 「战斗编排域拆件定稿」节轴丙与功能层 BoardInstaller 行——**已随本卡 v2.0 设计闭合落文(v1.24)**,实现照册施工;level_runner 行「外部调用面由同签名委托承载」句(D3 候选 A 实现后失真);「测试与质量域」S13 豁免登记段(D5 五键——批末按实测值落文) | 设计权威文档——实现批禁直改;level_runner 句与豁免登记逐条列进 `reports/iter<N>-反哺点-<日期>.md`,用户裁决后归文档批落文 |
| game/tests/README.md | 「GP 钱包/escrow/商店收口/共享池」行真实入口列——已随本卡 v2.0 改为 ShopSession 直连(计划入口,闸双通道认卡);「棋盘悬停反馈」行已随 v1.0 入表(能力独立成立,不随 D2 裁定变化) | 机械面文档——实现批禁写;批末收尾核对表上名字与实际落地一致(改名则回表) |
| design/gdd/systems/(全树) | 无关联文档——结构整改不改游戏规则与玩家可见行为(审计报告 §六.5 判定口径沿用) | 无修订预期;若实现中发现契约句被触碰,停卡呈报走反哺点通道 |
## 9. 卡前文件结构图
| 文件 | 职责 | 改动性质 |
|---|---|---|
| game/presentation/views/board_view.gd | 棋盘视图壳层(格态渲染入口、入站事件、探针出口) | 修改(非行为改动——_paint_cell 拆分层函数族、rebuild 构建器;验收=对照表行 V1) |
| game/core/level_runner.gd | 关卡域生命周期编排者 | 修改(非行为改动——仅 _enter_node 载荷合并〔载荷逐字保持〕;商店委托壳区段归并排卡,本卡不触——验收=对照表行 V3) |
| game/core/battle_orchestrator.gd | 战斗结算编排中枢 | 修改(非行为改动——装板域六函数迁出、宿主薄壳承接复位块、`_board_generator` 成员退役、三调用点改址;验收=对照表行 V4) |
| game/core/board_installer.gd | 棋盘安装与变异布置域件(新增,architecture v1.24 功能层在册) | 新增(非行为改动;验收=对照表行 V4) |
| game/tests/unit/core/level_shop_economy_test.gd | 关卡图与商店宿主套件 | 修改(仅新增断言 _test_shop_node_entered_payload〔尾波 V1〕;委托面调用点改址归并排卡) |
| game/tests/static_scan.sh | 静态扫描域承载 | 修改(S7 B1 域文件集与 S8 await 文件集增行 board_installer——工程随动,architecture 拆件定稿节 S7/S8 随动条在册) |
新增件「为什么不能复用」理由与唯一落点核查见第 10 节;对不上即卡片不成立。
## 10. 落位与可复用性预检
- **board_installer.gd(新增)**:复用面核查——装板与变异布置逻辑现行只存在于 battle_orchestrator(全仓 grep 六函数名仅本文件三处调用,无第二实现),无既有件可复用;不能并入 BattleFxHost(效果引擎宿主域,变化原因不同)也不能并入 RewardGrant(授予管线域)。落点唯一:B1 域新文件,`game/core/`。分层:core 层 RefCounted,零 UI、零 await,经引用束注入取协作对象,禁裸引用 autoload(S5);BoardGenerator 实例件内自建并接线 fatal 回调。
- **新增函数**:_build_cell_unit、_paint_cell_* 段函数族、宿主薄壳 `_install_and_reset`——全部是既有函数体的逐字搬运或循环体内联体出函,无新增逻辑;迁移函数对账见第 11 节。**零新增数据件、零新增常量表**。
- **调用路径显式声明**:board_installer 只被 battle_orchestrator 三调用点(经宿主薄壳)调用——禁反向持有宿主引用(注入回调单向,先例=BattleFxHost 兑付回调)。
## 11. 实现规范预算(新件对照 gdstyle 默认阈值;写码前对账,超预算面=停卡退回写卡段)
| 新件 | 公开方法 ≤20 | 类变量 ≤15 | 迁移函数对账(基线源函数键) |
|---|---|---|---|
| board_installer.gd | 预算 1(install) | 预算 7(引用束成员 rng/db/minesweeper/reward_economy/encounter 引用/fatal Callable+自建 BoardGenerator) | 六函数现行均无超标键(基线无其函数级键);`_install_new_board` 拆件体与宿主薄壳两段合计行数逐字=计数不变=新文件零新增键 |
| 新增私有函数(board_view/level_runner 内) | —(私有不计数) | 零新增成员(类变量只降不升) | _paint_cell 三键随拆分消除;段函数逐个低于默认阈值(超 8 分支的段沿绘制层轴下钻,见第 7 节键 1) |
存量触面一查:涉案存量文件 T0 读数写明「S13 键较登记值只降不升」预期——board_view 6 键(3 归绿+1 消除+2 维持不升)、level_runner `_enter_node` 键归绿(键 7/8 归并排卡与豁免通道,本卡不触)、battle_orchestrator 文件长归绿(类变量 38→37 只降);adventure_runner 本批零触碰(键 11 维持)。`_init` 一律引用束注入(字典束),防 max-parameters 新键(先例=迭代 40 卡①)。
## 12. 任务卡 T0..T7(估时为区间;工作量限制只属迭代层)
**T0 读数复核与对账(0.25–0.5 工作日)**
- [ ] 以当轮 master 重测 11 键实测值,与基线登记值逐键对账(审计报告 §四.6 纪律:禁按本卡行号直接施工);对不上即停卡呈报
- [ ] 复核涉案文件行数与第 3/7/9/11 节全部 file:line 引用;漂移处就地改卡并记录
**T1 board_view 拆分——4 键归绿(1–1.5 工作日)**(取用:T0 读数;产出:归绿键 1–4)
- [ ] `_paint_cell` 拆五段分层函数族 `_paint_cell_base/_paint_cell_ink/_paint_cell_facade/_paint_cell_overlays/_paint_cell_fog`(骨架见第 7 节键 1;主函数留格态读取与终写),函数体逐字搬运、段内超 8 分支沿绘制层轴下钻
- [ ] rebuild() 建格循环抽 `_build_cell_unit(c: Vector2i, stride: float) -> Node2D`,`.new()` 与属性装配逐字迁入
- [ ] 孤立验证:`godot --headless --script res://tests/run_all.gd -- --only=type_glow,theme_projection,player_copy` 全绿+`tools/probe_gate.sh --click` 全绿(出红只跑红件最小单元)
- [ ] `tools/render_gate.sh` 棋盘 golden 三张对拍差异 0.00%(非零即停机呈报)
**T2 level_runner _enter_node 载荷合并——键 9 归绿(0.25 工作日)**
- [ ] shop 分支 pool/legacy 双 return 合并为载荷变量单 return,两 payload 字典逐字保持(pool `{"gp": gp, "shelf": true}`/legacy `{"gp": gp}`)
- [ ] 新增断言 `level_shop_economy_test.gd:_test_shop_node_entered_payload`(规格见对照表 V3)
- [ ] 孤立验证:`-- --only=level_shop_economy` 全绿
**T3 battle_orchestrator 装板域拆件——键 10 归绿(1–1.5 工作日)**(前置:architecture v1.24 轴丙在册)
- [ ] 建 `game/core/board_installer.gd`(引用束注入,第 10 节落位预检),装板逻辑六函数迁件;`_install_new_board` 拆件体=生成→决议→装板→摇号→布置(415-441 行段),宿主薄壳 `_install_and_reset` 承载复位块(442-451 行段:五处计数清零+板序推进+board_changed 发射+秘宝叠加层退役),时序逐字
- [ ] 宿主三调用点(:271/:801/:835)改调薄壳;删宿主 `_board_generator` 成员(38→37,禁无效成员)
- [ ] static_scan.sh S7 B1 域文件集与 S8 await 文件集增行
- [ ] 孤立验证:`-- --only=variant_placement,round_flow,seal_polarity` 全绿+`python tools/run_smoke.py`
- [ ] 文件长实测 ≤1000(键归绿);类变量实测 37
**T6 批末对账与收口(0.5 工作日)**
- [ ] 全量闸:`bash tools/run_all_gates.sh --code`(改动全为代码面)全绿;闸 11 触面按第 4 节 PARITY 条款由用户裁决
- [ ] 键账对账:11 键终值对照第 7 节预期逐键核(归绿 6/豁免 5;键 7 终值视并排卡落地时序——同批编队即见 33);重锚由用户发令人工 `--refresh`(第 13 节);豁免登记 5 条+既有讨论 5 键按实测值落 architecture(反哺点通道)
- [ ] 产出物落位卡:`reports/iter<N>-实现报告-<日期>.md`(各卡状态+实机目检总清单+实现决定记录+逐拆出单元职责语句一行)与 `reports/iter<N>-反哺点-<日期>.md`(第 8 节同步面逐条落点)写齐方算收口
合计估时约 3–4.25 工作日(切分预案:T3 拆件裁决被证伪时按留债口径 −1~1.5 并回写第 7 节预期;并排卡 v1.1 的 ≈1.5 工作日不占本卡估时,编队总量由编排段合并计)。
## 13. 验证闸——改动—验收对照表
域口径:本批改动全为 `game/` 下非 .md 行为载体——批末跑代码域闸(`tools/run_all_gates.sh --code`);里程碑步(T 段)出口跑一次全套闸,波内修复只做出红最小单元的最窄确认,新增测试先行孤立验证再并入全套。测试执行两入口铁规不破。总判据=每行改动以「既有断言面全绿+golden 零差异+(V4)同种子对拍」证明零行为漂移,无新增玩家可见面;全量闸电池=冒烟 run_smoke.py+系统测试 run_system_test.py+`run_all.gd` 全量+probe_gate --click --full+render_gate+static_scan(含 S13 对账)。
| 行 | 需求点(键) | 改动(文件:行) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 |
|---|---|---|---|---|---|---|---|
| V1 | 键 1/2/3/4——_paint_cell 拆分层函数族+rebuild 构建器 | board_view.gd:601-739 拆五段、:246-262 抽 _build_cell_unit | 复用 theme_projection_test.gd:_smoke_type_glow、type_glow_test.gd、player_copy_sweep_test.gd:_sweep_board_glyph(既有断言零改动);新增断言零条;render_gate 棋盘 golden 三张 | 单元+探针+golden | BoardView.rebuild()→refresh()→type_glow_probe();board_shot_probe 真实场景帧 | 既有套件断言面+golden 逐像素(差 ≤16/changed ≤2%)——**既有三张基线即改前基线,对拍差异非零即红;重建基线须用户批准** | 既有:未开格类型微光(8 邻作用集/五组色相/零玩法写入);既有:玩家面文案与危险格格面字形 |
| V3 | 键 9——_enter_node 载荷合并 | level_runner.gd:503-511 双 return 并单 return | 新增 level_shop_economy_test.gd:_test_shop_node_entered_payload〔尾波 V1〕:pool 态 node_entered 载荷含 `shelf: true` 且 gp 等于进店时钱包现值;legacy 态载荷无 `shelf` 键。复用该套件商店房段既有断言 | 单元 | LevelRunner.start_level_external→pick_next_node 进 shop 节点 | node_entered 信号载荷(两 payload 字典逐字保持是本改动判据本体) | 既有:GP 钱包/escrow/商店收口/共享池 |
| V4 | 键 10——装板域拆件 | board_installer.gd 新建;battle_orchestrator.gd:414-549 迁出、:442-451 复位块留薄壳、:271/:801/:835 改址 | 复用 variant_placement_test.gd:_smoke_variant_placement/_smoke_global_variant、round_flow_skill_pool_test.gd:_smoke_full_battle、seal_polarity_test.gd 全套;新增断言零条;PARITY 对拍按第 4 节条款 | 单元+集成+MC 对拍(须批准) | BattleOrchestrator.begin_battle()/player_request_refresh() | 同种子变异布置逐位断言面+全战斗步骤流逐行回放断言;board_gen/variant_place 取号序零漂移由同种子断言钉住 | 既有:变异布置门控;既有:轮内流与结算五段全序 |
反向核对:diff 中每个行为改动均有行——本批申报行为改动仅 V3 一处(载荷合并,行为保持);其余行均为非行为改动(结构整改,判据=零漂移证明)。孤儿断言:无(新增断言仅一条:V3 行〔尾波 V1〕,已挂行)。
## 14. 基线纪律(S13)
1. 禁为过闸 refresh 换绿:基线重生成只由用户发令,批末键账对账后人工 `python tools/gdstyle_gate.py --refresh` 一次执行(禁闸内自动重生成、禁实现批中途 refresh)。
2. 还债=数值下降自然绿:拆件后同键数值下降不判红,无需动基线;归绿键在下批重锚后自然消失。
3. 出现恶化(同键数值较登记值上升、基线外新键)=停卡呈报,禁自行消化。
4. 重锚守卫:新键数较旧基线少逾 10% 须 `--force` 显式确认——本批预期归绿 6 键(16→约 10,降幅约 37%;键 7 随并排卡降值 50→33,同批编队时同一次重锚即含),须带 `--force` 由用户执行。
5. 豁免键范围:既有豁免 5 键(main.gd 类变量等——不在本批对账范围;battle_orchestrator 类变量 38→37 为已登记豁免键,只降不升)+本批 D5 裁定 5 键(批末按实测值落文——键 7 实测值视并排卡落地时序;落文前照常按基线对账)。
## 15. MC/数值与 RNG 零触碰声明
本批零数值改动、零数据表改动、零 RNG 流词表改动、零玩法字面量引入——结构整改不改数值与 RNG 消费序。board_gen/variant_place 封闭流随件迁移取号序不变(流名、开流时点、消费序零漂移——同种子逐位一致)。数值与 RNG 零触碰则 MC 对拍不补跑;battle_orchestrator.gd 触 PARITY_TOUCH_FACES——实跑对拍须经用户批准、不得借机重锚(批末闸 11 判 NEED-APPROVAL 时由用户裁决:批准实跑或裁跳过,卡内不定案)。
## 16. 异常分级
- **L1 自救**:迁移函数编译错、探针断言调用面改址漏改——就地修复重跑最窄确认。
- **L2 记录继续**:实测值与第 7 节预期区间偏离但在同档位内(如降值幅度略小)——记入实现报告「实现决定记录」。
- **L3 停机写报告等用户**:①拆分边界纪律④触发——按职责轴拆不出内聚单元(T1 五段/T3 装板域形态被证伪);②机检预算互斥——新件实数超第 11 节预算或迁移函数现出超标键=上游缺陷停卡退回写卡段;③golden 对拍差异非零;④S13 键恶化或基线外新键;⑤PARITY 触面处置分歧;⑥断言面被下游兜底吸收致判据绑不上(按 test-conventions §8.4 出口呈报)。
## 17. 测试削弱申报(允许改动的旧断言及理由)
零申报——本批不删任何断言、不改判据阈值、不缩扫描面、不把真实入口换成下层直调;唯一新增断言=V3 行〔尾波 V1〕(既有断言零改动)。商店委托面的测试改址归并排卡 v1.1 管辖(其自报零申报——只改调用地址,断言条件与期望值零变化)。未申报的削弱由独立验收审计判红。
## 18. 独立验收审计
合并前由未参与实现的会话或子代理执行;报告落 `reports/iter<N>-验收审计-<日期>.md`。审计至少做四件事(test-conventions §8.7):逐行核对第 13 节对照表(测试存在、真执行、尾缀可 grep、真实入口未绕过改动点);抽查判据绑定面;检查测试削弱(对照第 17 节申报);核对 V4 行「非行为改动」判定依据(零漂移证明是否在当轮真实跑批中成立)。未过不得合并。
## 19. 代拍板白名单(允许 agent 自行拍板的微决策)
1. 迁移函数头注注释的指址微调(词表与语义不动——「指址须真」纪律)。
2. 新件私有 helper 命名在职责语义不变前提下的微调。
3. 断言失败消息尾缀编号分配与措辞(〔尾波 Vn〕系列)。
4. 调用面改址的机械改写(import/preload 顺序、空行排版)。
5. 第 7 节键 1 段函数在五段骨架内的下钻粒度(沿绘制层轴)。
超清单决策记入实现报告「实现决定记录」节。
## 20. 裁决记录重申
D1–D6 已全部裁决闭合(2026-10-29 用户裁,第 6 节):装板域拆新件(两修正)/悬停域不拆/商店域候选 A 并排编队(执行合同=并排卡 v1.1)/断点域不拆/五降值键豁免终局——**零待裁点,卡片可编排**。豁免登记 5 条与既有讨论 5 键合并,批末按实测值经反哺点通道一次落文 architecture。
@@ -0,0 +1,185 @@
# 需求卡片——level_runner 商店委托层拆除批
- 版本:v1.1(2026-10-29,grilling 复核门通过——修正四项:跨类同名扫描判类归属、`_shop` 词级归零判据、set_shop_pool 陈旧调用方陈述订正、测试面精确计数)
- 来源:待办清单「level_runner 商店委托层拆除——迭代 37 冻结契约期过渡件」(2026-10-26 登记,来源=批次移交;需求性工程条目,改动面=游戏代码)
- 执行通道:需求改动——经迭代编排编入迭代后在独立 worktree 施工(仪式归 /iter);本卡估时合计 ≈1.5 个工作日(区间 1.5–2,超 2 停批呈报)
- 迭代号:由编排段落位(本卡 `iter<N>` 占位符届时代换)
## 1. 目标与完成定义
拆除 LevelRunner 的商店同签名委托层(19 个纯转传方法+「冻结公开面」契约注释),全部外部调用方直连 ShopSession;游戏行为零改动。
完成定义(全部满足才算完):
1. level_runner.gd 壳块(:722–728 区块注释涉委托句+:751–828 十九个委托)归零;宿主商店成员以公开名 `shop` 承载取件。
2. 产品码、sim、测试面全部调用点改为 `<runner 表达式>.shop.<方法>` 直连,全仓无旧委托调用残留。
3. 全部单元套件、冒烟、代码域闸绿;现有断言读数逐位不变(零新增断言、零断言删除)。
4. 架构文档同步点入反哺点清单;实现报告与验收审计报告落 `reports/`。
## 2. 设计事实源
跳过设计线的证据指认(需求性工程条目,第〇步核验放行 2026-10-29):
- 待办清单条目实况:委托壳=迭代 37 卡④ ShopSession 拆分时立的「冻结公开面」契约过渡件;架构登记=`design/engineering/architecture/architecture.md:109`「外部调用面由 LevelRunner 同签名委托承载(外部零变化)」。
- 拆除形态权威=`architecture.md:152`「调用直连:不留中间人委托壳(纯转传=Middle Man 反模式,2026-10-26 用户裁定)」。
- 2026-10-29 写卡会话裁决:改址定式四条(公开成员取件/零调用死壳删/测试直连私有面/契约注释随壳消失)+ spend_gp 归属确认非问题(共享冒险钱包的校验扣款原语,宿主 ShopSession 语义自洽、不迁移)。
## 3. 现状事实(代码级,开工自行复核)
委托壳块:
1. level_runner.gd:722–724 区块注释(「本类保留同签名委托」句)+:725(「白盒入口——外部调用面与断点取数面零变化」)+:751–828 共 19 个同签名转传方法:shop_products:751/current_price:755/purchased_tier:759/buy_product:763/player_request_refresh:767/_spend_discounted:772/set_shop_pool:778/_shop_draw_shelf:783/pool_stock:787/pool_price:791/shop_shelf:795/shop_refresh_free_left:799/shop_refresh_cost:803/shop_refresh:807/spend_gp:811/buy_pool_item:815/is_shop_pool:819/shop_bp_shelf:823/shop_bp_visit_buys:827。
2. 零外部调用方死壳 2 枚:`_spend_discounted`(全库仅 tree_rules_v11_test.gd:6 注释提及)、`pool_stock`(外部同名调用全为 CurioDeck.pool_stock,如 adventure_feats_test.gd:545——同名不同件)。
外部调用面(产品码与 sim):
3. 呈现层:camp_shop_presenter.gd:173–203(12 处)/main_adventure_flow.gd:81–86(6 处)/main_overlay_flow.gd:135/138/main.gd:773(player_request_refresh)/curio_interact_controller.gd:163(spend_gp——奇物三选一付费刷新出账)。
4. sim:level_autoplay.gd:341/350/363/372/418/421(6 处)+:335 注释。
5. 场景 .tscn 零引用、tests/system_test.gd 零引用(2026-10-29 全仓扫描)。**跨类同名方法不属本批面**(残留扫描判类归属的依据):BattleOrchestrator.player_request_refresh(round_flow_skill_pool_test ×5、seal_polarity_test:185、main.gd:775)与 CurioDeck.pool_stock(adventure_feats_test ×3、feat_vouchers_test ×2、pool_case_d_test ×3、level_shop_economy_test:1360/1361)——17 公开名 grep 命中须按接收方类归属判别,禁误改这两处同名面。
外部调用面(测试):
6. level_shop_economy_test.gd 实改址 25+白盒 `_shop_draw_shelf` :1344/:1355(公开名 grep 命中 27,内含跨类同名 `deck().pool_stock` :1360/:1361 非本批面)/hero_ledger_test.gd:60/66/adventure_feats_test.gd:364/pool_case_d_test.gd:45–47(:47=shop_bp_shelf)/treasure_effects_test.gd:229/233/curio_refresh_paid_test.gd:52–54/meta_progression_forge_test.gd:735–740。
保留面(非委托,仅随成员更名):
7. 宿主内部使用:level_runner.gd:99/102/104/305–306/506–507/704/729(建件、pool 注入、断点取数、进离店编排);钱包生命周期 `close_wallet`:718 归宿主自有(写 gp+发 clear 事件,不经商店)。
8. 商店件本体 shop_session.gd 公开面现成(spend_gp:262 等),测试直连其私有 `_draw_shelf`:133/`_spend_discounted`:59 属测试侧改址。
登记与账面:
9. 架构:architecture.md:109(委托承载句+公开面格)/:152(直连原则);`design/` 全目录仅此一处委托登记(grep 考证 2026-10-29)。
10. gdstyle 基线:tools/gdstyle_baseline.txt:11–13——level_runner 三键 max-class-variables=30/max-public-methods=50/max-returns|_enter_node=7。
11. 能力清单:game/tests/README.md 旅程段四「GP 钱包/escrow/商店收口/共享池」行真实入口列引用委托名——已随本卡写作更新为直连形态(写卡段义务,实现批禁写该文件)。
12. shop_session.gd:4 头注「公开面由 LevelRunner 同签名委托承载」句随拆除失效(商店件唯一注释级 diff,见契约 3)。
## 4. 设计契约(违反即返工;🔴=触碰停机呈报)
1. 🔴 直连不留壳:19 个委托全删(含死壳 2 枚),禁保留任何同签名别名/转发/兼容壳;外部取件唯一形态=公开成员 `LevelRunner.shop`(level_runner.gd:44 私有 `_shop` 更名,公开面=成员本身,不设访问器方法)。
2. 🔴 行为零改动:商店双模式定价、货架、RNG 取号序列、钱包账面、事件发射逐项同前;全部现有断言同种子逐位不变;`data/tables`、RngService、ConfigDB 消费零 diff;无数值改动禁跑 MC。
3. ShopSession 行为零改动:shop_session.gd 不改任何可执行行;唯一允许 diff=头注 :4 委托承载句随批更新(非行为)。
4. 测试直连私有面仅限白盒两入口(shop_session `_draw_shelf`/`_spend_discounted`),依据=「测试/探针直连件读写」(architecture.md:152 裁定段);产品码禁直连任何 `_` 前缀私有成员。
5. 拆分边界纪律(判据权威=code-conventions §1.1,本批为拆除/收敛向):本批零拆出新件——结构变化仅壳删除与成员公开化两项;保留单元(LevelRunner 商店房编排段、ShopSession、呈现控制器)职责语句不变;凡「为达标保留的转发别名」按凑计数袋处理,禁。
6. 🔴 禁为机检文本保留无效成员(code-conventions §1.1 同款):机检键与本卡壳删除契约互斥时(如任何键要求公开方法数不得下降),实现段一律停卡呈报,不得自造第三条路。
7. 测试削弱申报:零申报——不删任何断言、不改判据阈值、不缩扫描面;测试只改调用地址。实现期发现必须改判的断言=上游缺陷,停卡退回写卡段。
## 5. 范围边界
做:壳块与死壳删除;`_shop`→`shop` 公开化(含内部引用点);产品码/sim/测试调用点改址;注释同步六处(shop_session.gd:4、level_runner.gd:722–724 区块句、level_autoplay.gd:335、shop_overlay_view.gd:88/152、tree_rules_v11_test.gd:6);level_runner 头注涉委托表述随批核查同步。
不做(去向):
1. spend_gp 迁 GpWallet 与三处内联扣账去重——2026-10-29 用户裁「可选微整理、不进本批」;日后要做属游戏代码重构,另立卡走流水线。
2. gdstyle 基线重锚(`python tools/gdstyle_gate.py --refresh` 人工专用)——本批公开方法数自然下降不触发重锚;基线死键清账时机归用户发令(待办清单在册)。
3. architecture.md:109 改写——实现批禁写 `design/`,入反哺点清单(§6)。
## 6. 关联文档同步面
| 文档 | 关联点(本批实现会改变它的哪条陈述) | 同步义务 |
|---|---|---|
| design/engineering/architecture/architecture.md | :109 ShopSession 行——「外部调用面由 LevelRunner 同签名委托承载(外部零变化)」句失效;公开面格需随改址后实际对外面重写:补 spend_gp(奇物付费刷新对外出账口)与 current_price/pool_price/purchased_tier/shop_refresh_free_left/is_shop_pool/shop_bp_shelf/shop_bp_visit_buys 等实际被直连方法;set_shop_pool 调用面改述:pool 注入经 LevelRunner 构造 ctx 供给(level_runner.gd:101–102 内部直调 ShopSession.set_shop_pool;原委托块注释「AdventureRunner._start_current_level 唯一调用方」系陈旧表述——全仓 grep 证实产品码零调用方,随壳删除作废),测试面直连 shop.set_shop_pool | 反哺点清单(`reports/iter<N>-反哺点-<日期>.md`),用户裁决后归文档批落文;实现批禁直改 |
其余文档无关联点:GDD 商店/秘宝/天赋树各节机制陈述(扣账口唯一、刷新双源、折价边界、爬价公式)不因调用面搬家而变(grep 考证 2026-10-29)。
## 7. 落位与可复用性预检
- **新增件清点**:零新增文件、零新增函数、零新增数据件。唯一「新增面」=LevelRunner 公开成员 `shop`——更名既有私有成员 `_shop`(level_runner.gd:44),复用既有 ShopSession 实例与 `_bind_shop` 装配(:728),零新装配、零新引用、零新职责。
- **唯一面核查**:grep 19 个方法名全仓——委托职责拆除后调用面唯一落点=ShopSession(第二承载不存在);`_shop_draw_shelf`/`_spend_discounted` 转发壳删除后,白盒入口唯一落点=shop_session 私有方法本体。
- **复用面核查**:不新增任何 helper/访问器——「更名公开」即全部;呈现层持件沿用既有 `_runner_get` 取值回调(camp_shop_presenter.gd:170 现形态),不新引入注入面。
- **分层与交互面**:调用方向不变——views→(信号)→组合根→core;呈现控制器经取值回调持 runner 后以 `runner.shop` 取件(组合根拆件直连=架构「模块归属·呈现层」既有口径);sim 属测量面直构既有惯例;测试直连 core 公开成员+白盒两私有入口(契约 4)。
## 8. 实现规范预算
- **新件三查**:零新增文件——空转。
- **存量触面一查**(level_runner.gd,基线 tools/gdstyle_baseline.txt:11–13):max-public-methods 预期 50→33(删 17 公开委托、增 0;私有委托 2 枚同删不入此数);max-class-variables=30 不变(成员更名不改数);max-returns|_enter_node=7 不变(`_enter_node` 零触碰)。T0 读数步核对「S13 键较登记值只降不升」,基线值本批不动。
- **shop_session.gd**:零可执行行 diff,键面不变。
- **指标例外申报**:无。
## 9. 文件结构图(卡前;全部「复用既有」——零新增件,与 §7 互指)
```
game/core/level_runner.gd 壳块删除+成员公开化+内部引用更名+区块注释【非行为——R2/R3/R4】
game/core/shop_session.gd 仅头注 :4 委托句更新(零可执行行 diff)【非行为——R4】
game/presentation/camp_shop_presenter.gd 调用点改址 12 处【非行为——R1】
game/presentation/main_adventure_flow.gd 调用点改址 6 处【非行为——R1】
game/presentation/main_overlay_flow.gd 调用点改址 2 处【非行为——R1】
game/presentation/main.gd 调用点改址 1 处【非行为——R1】
game/presentation/curio_interact_controller.gd 调用点改址 1 处【非行为——R1】
game/presentation/views/shop_overlay_view.gd 注释同步 2 处【非行为——R4】
game/sim/level_autoplay.gd 调用点改址 6 处+注释 1 处【非行为——R1/R4】
game/tests/unit/core/level_shop_economy_test.gd 改址 25+白盒 2(grep 全量核对)【非行为——R1】
game/tests/unit/core/hero_ledger_test.gd 改址 2 处【非行为——R1】
game/tests/unit/core/adventure_feats_test.gd 改址 1 处【非行为——R1】
game/tests/unit/core/pool_case_d_test.gd 改址 3 处【非行为——R1】
game/tests/unit/core/treasure_effects_test.gd 改址 2 处【非行为——R1】
game/tests/unit/core/curio_refresh_paid_test.gd 改址 3 处【非行为——R1】
game/tests/unit/core/meta_progression_forge_test.gd 改址 3 处【非行为——R1】
game/tests/unit/core/tree_rules_v11_test.gd 头注同步 1 处【非行为——R4】
```
## 10. 任务卡 T0..T2
施工遵循闸机轮次纪律:里程碑步(T1)出口跑一次全套闸;波内修复只做出红最小单元的最窄确认;新增测试/工具先行孤立验证再并入全套。
### T0 调用面直连(壳仍在位——中间态全绿) 估时 0.5–1 个工作日
- 取用:—(首批)。
- 产出:全部调用点 `*.shop.*` 直连形态(壳在位,行为零改动的中间态);T1 依赖「删壳后零编译错」以本产出为完整性基线。
- [ ] level_runner.gd:44 成员更名 `var shop: ShopSession`(行注释同步),内部引用 :99/:102/:104/:305–306/:506–507/:704/:729 全部随更名;核查=grep `_shop`(词级)于 level_runner.gd 仅剩保留件 `_bind_shop`(:65/:104/:728——宿主回调装配面,非委托)
- [ ] 产品码改址六件(形态=`<runner 表达式>.shop.<方法>`,方法名与实参逐字不变):camp_shop_presenter.gd:173–203/main_adventure_flow.gd:81–86/main_overlay_flow.gd:135/138/main.gd:773/curio_interact_controller.gd:163/shop_overlay_view.gd:88/152 仅注释
- [ ] sim 改址:level_autoplay.gd:341/350/363/372/418/421+:335 注释
- [ ] 测试面改址七件:level_shop_economy_test.gd(grep 全量核对;白盒 :1344/:1355 改 `r8.shop._draw_shelf()`)/hero_ledger_test.gd:60/66/adventure_feats_test.gd:364/pool_case_d_test.gd:45–47(:46 改 `lr.shop._draw_shelf()`、:47 改 `lr.shop.shop_bp_shelf()`)/treasure_effects_test.gd:229/233/curio_refresh_paid_test.gd:52–54(`lr.shop.spend_gp(...)`)/meta_progression_forge_test.gd:735–740(`lr.shop.current_price(...)`)
- [ ] 注释同步:tree_rules_v11_test.gd:6 头注 `level_runner._spend_discounted`→`shop_session._spend_discounted`;level_autoplay.gd:335 注释调用名同步
- [ ] 验证:`python tools/run_smoke.py` 绿;单元与集成 `--script res://tests/run_all.gd` 全绿(中间态=直连可编译可过+行为零改动初证);level_runner 三键读数核对只降不升(§8 预期值)
### T1 壳拆除与完整性证明 估时 0.5 个工作日
- 取用:T0 全调用点直连形态在位。
- 产出:终态 level_runner(壳归零);T2 的闸与审计对象。
- [ ] 删除 level_runner.gd:722–724 区块注释涉委托句+:725 契约注释(保留「商店房兑付」节头与件注入说明)+:751–828 十九个委托全量(含死壳 `_spend_discounted`:772/`pool_stock`:787)
- [ ] shop_session.gd:4 头注委托承载句更新(「外部调用面=调用方经宿主公开成员 `shop` 直连」表述)
- [ ] 验证:`python tools/run_smoke.py` 绿+全单元套件绿(壳删后绿=T0 改址无遗漏的编译级证明)
- [ ] 残留扫描:grep 19 个方法名于 `game/`(排除 shop_session.gd 本体定义、level_runner.gd 内部 `shop.` 使用点、跨类同名方法——BattleOrchestrator.player_request_refresh 与 CurioDeck.pool_stock 按现状事实 5 清单判接收方类归属)——产品码与测试面零命中,输出留实现报告
- [ ] 注释收尾核查:level_runner 头注涉委托表述已同步(§5 做项)
- [ ] 里程碑出口:`tools/run_all_gates.sh --code` 全绿、回执落 `game/tests/.gates/receipt.json`
### T2 收口(产出物落位卡) 估时 0.5 个工作日
- 取用:T1 终态(壳归零、套件绿、回执在盘)。
- 产出:三份过程件+合回呈报。
- [ ] 反哺点清单 `reports/iter<N>-反哺点-<日期>.md`:§6 表逐条落点——architecture.md:109 ShopSession 行改写内容全文随单;无其他落点
- [ ] 实现报告 `reports/iter<N>-实现报告-<日期>.md`:各卡状态+实现决定记录+实机目检总清单(本批零玩家可观测改动——写明「无新增目检项」)+遗留读数义务(无)+残留扫描输出留痕
- [ ] 独立验收审计 `reports/iter<N>-验收审计-<日期>.md`:未参与实现的会话或子代理执行(test-conventions §8.7 四事);审计在迭代 loop 内启动的子代理算 loop
- [ ] 审计通过后呈报用户合回(worktree-guard 按 diff 文件性质推域校验回执;审计判红→修复→出红最小单元最窄确认→闸重跑)
- [ ] 批后收尾(待办条目整条删除、roadmap 换代)归 batch-closure——不在本卡任务内,此处仅登记去向
## 11. 验证闸(改动—验收对照表)
本卡零新增断言——对照表全行由既有断言与静态扫描承接,无〔卡短名 Vn〕尾缀需求。
| 需求点 | 改动(文件:行/行为) | 验收测试(名称+落点) | 层级 | 真实入口 | 判据绑定面 | 能力行 |
|---|---|---|---|---|---|---|
| R1 调用面直连改址(非行为改动——调用目标/实参/返回/副作用逐项同前,仅取件路径文本变化) | §9 结构图产品码 6 件+sim 1 件+测试 7 件全部调用点 | 既有套件回归两轮全绿:T0 中间态轮+T1 壳删后轮(level_shop_economy/hero_ledger/adventure_feats/pool_case_d/treasure_effects/curio_refresh_paid/meta_progression_forge 全套+冒烟) | 单元+接口 | 各套件既有真实入口(直构 LevelRunner/AdventureRunner 链) | 既有断言逐位不变(行为零改动正向证据)+壳删后编译通过(改址无遗漏) | 既有:GP 钱包/escrow/商店收口/共享池;既有:奖励三选一候选/刷新付费/呈现 |
| R2 壳块拆除(非行为改动——纯转传方法与契约注释删除,行为承载在 ShopSession 不变) | level_runner.gd:722–724 句+:725+:751–828(含死壳 `_spend_discounted`:772/`pool_stock`:787) | 残留扫描零命中(T1 静态步)+T1 套件绿 | 静态+单元 | grep 全仓(19 方法名清单固定)+套件真实入口 | 扫描面>0 前置(grep 命令对 19 名单非空可执行)+壳删后零引用可编译;命中行按接收方类归属判别(现状事实 5 跨类同名清单) | 既有:GP 钱包/escrow/商店收口/共享池 |
| R3 宿主成员公开化(非行为改动——私有 `_shop` 更名公开 `shop`,装配与读写路径不变) | level_runner.gd:44+内部引用 :99/:102/:104/:305–306/:506–507/:704/:729 | 随 R1 两轮套件绿 | 单元 | 套件真实入口 | 同 R1(内部使用点行为不变) | 既有:GP 钱包/escrow/商店收口/共享池 |
| R4 注释同步六处(非行为改动——注释文本,无运行时可观测面) | shop_session.gd:4/level_runner.gd:722–724/level_autoplay.gd:335/shop_overlay_view.gd:88/152/tree_rules_v11_test.gd:6 | 随批代码域闸绿(静态扫描覆盖文件头纪律面) | 静态 | —(静态判据) | 观测面局限申报:注释无运行时提交面,判据=静态扫描+评审注记 | 既有:GP 钱包/escrow/商店收口/共享池 |
## 12. 异常分级
- **L1 自救**:更名/改址漏点编译错→就地补改后重跑最窄确认(冒烟或单套件)。
- **L2 记录继续**:套件出红且根因属本批改址笔误→出红最小单元最窄确认修复+实现报告记录;连续 3 次同红灯升级 L3。
- **L3 停机呈报**:①出红根因=上游缺陷(非本批改动所致)→停卡退回写卡段;②机检键与本卡契约互斥(如需保留壳/别名才达标)→停机呈报,禁自造第三条路(机检预算互斥条款);③改址中发现任何行为差异(同种子读数漂移)→立即停批(行为零改动契约被打破);④ShopSession 需要可执行行改动才能达成改址→停机呈报(契约 3 被打破)。
## 13. 代拍板白名单
1. 注释措辞与所在行随改址微调(不改语义)。
2. 测试内局部变量随调用链展开的机械适配(不改断言判据)。
3. 残留扫描的 grep 命令形态与输出留痕方式(实现报告正文承载)。
4. level_runner 头注与区块注释涉委托表述的同步措辞。
5. 单元套件运行的分批执行方式(闸机轮次纪律内的最窄确认选择)。
超白名单的微决策记入实现报告「实现决定记录」节。
## 14. 待裁点
零待裁点,开工门=U0。(改址定式与 spend_gp 归属均经 2026-10-29 会话裁决,见 §2。)
@@ -0,0 +1,336 @@
# 需求卡片:战斗一屏样张批(整屏样张判定物首跑 · 四屏样张排程 · 防不满意工序重排)
- 版本:**v1.4**(2026-10-17 开工前预授权复查:本卡**不适用** U1 预授权——两处人闸(T1 方向稿选划/T3 气质终审)是防不满意机制本体,取消即拆机制;四项 L3 经分类**全部属「触既有裁决面/超出本批面」类,维持停机呈报**,详见 §6 分类结论注);**v1.3**(2026-10-17 文档同步闸 v3 口径修订,纯文档批〔分支 carddoc-20261017〕按用户六条裁定执行——①本批(代码实现批,此处含样张工具面)禁写 `design/` 整目录与根规则文件,§8 同步面的同步义务一律执行为「列进 `reports/iter30-反哺点-<完成日>.md`(哪个文件哪一节、写成什么、依据),用户会话裁决后归文档批落文」;冻结记录段全文、目标规格清单、H1–H11 逐项处置、徽章明度档复核转正记录等**成文内容本体随反哺点条目交付**;②产出物落位换代:实现报告/代拍板追认单/反哺点清单三份落 `reports/`,不再 `design/qa/`;③T5 末卡改「产出物落位卡」(三份 reports/ 文件写齐才算收口);④闸口径按域选档:重闸 = `tools/run_all_gates.sh` 按改动域选档(本批触碰 `tools/` 样张面 → `--code`),卡内不再用「全量档全绿」类字样;⑤实机目检项写进 `reports/iter30-实现报告-<完成日>.md` 目检总清单,「入册 07」转反哺点;§7 裁决留痕原文不动,其落文类表述的执行口径随本版换代);**v1.2**(2026-10-16 用户裁决回写:Q1–Q6 六项全部已裁——裁决来源统一措辞「2026-10-16 用户令:三卡全部待裁项按推荐方案裁决(用户将裁权授出,由主线会话代拍板留痕)」,§7 逐条留痕;本版改动:§7 由「待裁点」改为「裁决记录」(选项原文保留+裁定案+一句理由),排期改实(迭代 30 已编入、roadmap 同步在批;三卡串行位第 2=徽章→样张→观感),§0/§1/§2/§3/§6/§7.5/§8 按裁定口径改实,T0 承接 Q5 甲案死指涉处置(指代稿候选 v31/v32/v33-draft 实查拍板),T1 新增 Q3 乙案试验件前闸(半天,不过闸退甲案预设退路),T2/T3 承接徽章卡 Q2 甲案之明度档复核转正义务(§7.5 D1 改实),T4 随 Q2 甲案退役为登记行(06 三屏排队行=本批交付义务,写入文档时点=批次出口),估时随 Q3 乙案重算为 **≈5–6d** 并改软盒拆批线,全卡不留已裁 Q 项的「待裁」字样)。v1.1(2026-10-16 grilling 对抗审查修订;v1.0 同日成文)v1.0→v1.1 改动八项,逐条证据见文末「grilling 审查记录」:①**事实面订正七处**——整屏四项的机检代码并非「从无」(`tools/shot_checker.py` 头注自述承载 §1.4 整屏闸机检,迭代 9 T8 已首跑实测 PASS,git 053baeee;缺的是样张判定物与③焦点预算);死指涉行号 527→**562**;v1 四否决原因补全四条(②封印色冲突、④微光格只留微光此前漏列);playnote 引文改逐字;P5「worktree 不可直接跑」证伪(两级回退+自动建目录);旧锚 §5.1 另存于 06:41(此前只登记看板一处);战斗表现内容清单仍写「四区」(现行三区 B/C/D)。②新增 **P10**(关卡屏样张 v1 已冻结 2026-09-28,系立闸前产物)与 **P11**(§1.4 的执行时机已被屏级落码批越过,本批性质=闸后补跑)。③新增 **§0.5 样张批继承的既有待裁义务清单**(全库检索「待样张批冻结/终审/追认」得十项,本批禁止静默丢弃)。④新增 **§0.6 已裁项核收表**(Q 清单据此收敛,已裁面不复问;同时关闭本卡三处隐性重问)。⑤Q1 补乙案连带(A4 尺寸基线须显式解冻重校)、Q2 补关卡屏既有件、目标句与完成定义去掉「并集单闸」预设(合体方式归 Q6 未裁)。⑥新增 **§7.5 跨卡依赖**(与迭代 30 并行两卡的次序耦合+触碰面重叠禁三线并行)。⑦估时加总订正(T4 上限与合计重算)+补先例出处。⑧验收 V 条逐条改挂实存工具,判不了项降级并注明手段。
- **指路订正(2026-10-20)**:徽章卡(迭代 30 卡①)与测试分层卡(迭代 29)已按 doc-conventions §11「吸收即死」退役,本卡对两卡的三处指路句改为结论内联+提交号(`7ffd1dc0`/`fff4f120`),裁决内容零变更。
- 来源:[06-待办清单](../design/gdd/06-待办清单.md)「战斗一屏样张 P0 未开工——界面成品是否过审的那把尺子还没建(2026-10-13 登记,UI 美化三层诊断 · 第二层)」条(:37-43;注:该条「尺子到现在还没造出来」的措辞与 P1 读数有差——机检代码在位,缺的是样张判定物,T0 订正面连带此句);依赖素材取自同清单相邻条「UI 贴图重构批」(:21-27)「UI 字体补完」(:29-35)「资产升档重产批」(:465-471)「立绘清晰度回退评估」(:473 起)「迭代 26 反哺点④」(:78)「美术资产链路审计两项 P0」(:609-615)
- 归属:**美术线**(06 原条归属栏:战斗屏样张的验收标准归[战斗表现](../design/gdd/systems/战斗循环/战斗表现.md)与[美术圣经](../design/art/art-bible.md)共裁);样张生产脚本与机检面归呈现层工具面(工程)
- 排期:**迭代 30 已编入**(2026-10-16 用户令:三卡全部待裁项按推荐方案裁决(用户将裁权授出,由主线会话代拍板留痕),[roadmap](../roadmap.md) 编入行已在案、批内状态刷新列反哺点);三卡串行序位第 2(徽章→样张→观感,§7.5 D3 改实);开工门 = U0〔用户 `/iter` 发令——Q1–Q6 已全部裁决(见 §7),无遗留待裁项〕;v1.1 之「Q1 必须先裁」前提随其裁决(丙案)关闭,本批依赖链与任务顺序已按丙案口径改实(见 §0/§3)
- **立场前提(用户原话,本卡每环节据此设计)**:「现在做优化就是因为它丑;如果还是用原有方式产出,很可能还是不满意。」⇒ 本卡对现状的改动不是「再跑一次程序化产图」,而是**工序重排 + 选型交用户裁决 + 否决成本前移**;每张任务卡自带「相对现状新增什么」与「防重蹈不满意怎么防」两栏。
---
## 0. 前置依赖与并行性
**串行位**:按 roadmap 执行纪律「一迭代一 loop、单迭代串行」(roadmap.md:93),本批**已编入迭代 30**(2026-10-16 用户令,roadmap 编入行已在案、状态刷新列反哺点),前置=迭代 29(需求卡片-测试分层与门禁语义修正批(迭代 29,已入库合并 fff4f120,卡片已退役))完成后开工;三卡串行序位第 2(徽章→样张→观感,2026-10-16 用户令裁定,§7.5 D3 改实)。**两处限定(v1.1 补)**:①迭代 29 已于 2026-10-16 合入 master(合并提交 fff4f120,roadmap.md:88 状态=✅ 已入库;本卡原「迭代 29 U0 未解」读数随之作废,见 §7.5 D4),该前置已满足;本批开工时点仍取决于用户 `/iter` 发令,禁把「迭代 29 完成后」读成排期承诺;②触碰面重叠除 iter29(`tools/`、06、08、07、roadmap、active.md)外,**还包括另两张迭代 30 卡**(徽章卡动 `presentation/views`+06/08/07+art-bible 修订行;棋盘观感卡动 `tools/recolor_terrain_tiles.py`+golden 基线+art-bible §4.1/§4.3+08 表现行)——三卡各写 06/08/07/art-bible/roadmap/active.md,序位既裁则「禁三线并行」转为逐批串行执行,详见 §7.5。**v1.3 注**:三卡文档直写义务已按 2026-10-17 文档同步闸 v3 全部转为反哺点(落 `reports/`,见 §8),上列汇聚文件重叠随之消解,但 2026-10-16 串行序位裁定不因此失效——序内仍有 golden 基线先后(§4B/§7.5 D2)与 art-bible 冻结内容先后(D1)依赖,禁三线并行维持。
**闸已立、机检面已跑过实机帧、样张判定物从未出**:整屏样张闸条款已于 art-bible v3.0(2026-09-29)写入 §1.4(L64)——四屏各出一张整屏样张、验收四项、闸形「方向稿目检先行」、执行时机 = 屏级视觉落码开工前;§7.1 占位完成条款同步在位(L411,程序化占位 = 临时态,终稿闸 = §1.4 整屏样张过闸,占位态禁止跨里程碑)。**机检面并非空白**:`tools/shot_checker.py` 头注自述「整屏样张闸机检(迭代 9 卡⑤/T8 2026-09-30)」,四项中 ①文字对比 ②明度三档带 ④文字禁压纹理 已有实现(B/C/D 段,另 F 段补「文字在判据」),③整屏焦点预算明文「语义面无法机检 → 归实机目检人闸」(E 段);该断言集 2026-09-30 已对**实机渲染帧**首跑实测 PASS(git 提交 053baeee:「实测 PASS——WCAG 5.79-13.14/三档 204>79>55/骨架四屏过;焦点预算=目检项归段闸人闸」),随闸三裁决亦已裁毕(见 §0.6;呈报单原文件 design/qa/m3-iter9-t8-rulings-2026-09-30.md 已按吸收即死纪律在提交 8e02dcf0 删除,结论以各权威文档现行为准),现挂在 `tools/render_gate.sh` ①段周期跑。**本批要补的是剩下的半截**:出 §1.4 意义上的整屏**样张判定物**(目标图,非现状截图)、走「方向稿目检先行→精制→冻结」的闸形、补 ③焦点预算的判定、按屏扩取样点位、立四屏冻结记录(**v1.2 按已裁范围收窄:Q2 裁甲案,本批只出战斗屏判定物与其冻结记录,余三屏登记 06 排队行归后续批**)。**不新立闸条款。**
**前置事实(2026-10-16 实读)**:
| # | 事实 | 出处 |
|---|---|---|
| P1 | 格子样张 v3 已冻结(2026-09-30,机检 17/17 全绿,四冻结①④重冻生效)。整屏四项的**机检代码在位且已对实机渲染帧跑过**(B 文字对比/C 明度三档带/D 文字禁压纹理+F 文字在判据),**唯 ③整屏焦点预算明文不可机检(E 段归人闸)**;§1.4 意义上的「整屏样张判定物」与四屏冻结记录两件从未产出 | [art-bible](../design/art/art-bible.md) §1.4(L64 闸条款/L68 v3 冻结记录);[shot_checker.py](../tools/shot_checker.py) 头注 L1-25;git 提交 053baeee(iter9-T8 卡⑤ 首跑实测 PASS);[render_gate.sh](../tools/render_gate.sh) ①段 L11-13 |
| P2 | 现行 v3 脚本头注自称「战斗一屏样张方向稿生成脚本 v3-draft」,实际出的是 B/C/D 三区 1280×800 全内容合成图 + S2/S3/S4 附录 + A1–A6 程序化审计——但**冻结记录登记在格子样张闸名下**(圣经 §1.4 v3 段),且它验的是「格+一张合成 mockup」,不是「屏级气质判定物 + 四项逐项断言 + 冻结记录」三件齐 | [render_sample_sheet_v3.py](../tools/render_sample_sheet_v3.py) 头注 L2、L28「一屏样张 = B/C/D 三区全内容(1280×800 1:1 实比)」、L1100-1144 版式与断言清单;art-bible §1.4 L68 |
| P3 | 看板十五件:✅4 / ◐8 / ⬜3(实点计数核过);「战斗一屏样张」行 = ⬜ 未开始,需求文档指针为战斗表现 + art-bible §5.1。**注:§5 已抽离至 [character-design.md](../design/art/character-design.md)(v1.7 批),指针系旧锚;同型旧锚另有 06:41「归属」栏一处**(写作「验收标准归战斗表现与美术圣经 §5.1 共裁」——正确锚应为 §1.4 整屏闸+§7.2 文字硬指标,§5.1 只管剪影轮廓),T0 两处一并订正 | [art-status-checklist.md](../design/art/art-status-checklist.md) L5/L13-14;06:41 |
| P4 | 旧冻结样张产物 `sample-batch-v3-final-2026-09-30.png` 磁盘实测不在盘(`artwork/mockups/` 现存 v0/v2/v31/v32/v33/v34/v35-draft 等同系列图与 level-map-sheet-v1),06 在册 P0①(死指涉条,其原记状态至 2026-10-16 前悬而未决)**已由 Q5 裁甲案(2026-10-16 用户令),处置落 T0**(2026-10-14 审计会话存档 :290/:316 仍记「未读到」)。**订正:06:611 原文写「art-bible 第 68/527 行」,现行为 68 与 562**(527 行实为 §9.1 正面参考表,不含该指涉;562=v3.2 登记行)——T0 订正面对 art-bible:68/:562 与 06:611 的行号自身一并核。**同类死指涉另检出一处(v1.1 新增)**:art-bible §8.2 L490 指路 `design/qa/m3-iter9-t8-rulings-2026-09-30.md`(迭代 9 三裁决呈报单)该文件已在提交 8e02dcf0「存量超期清理批」删除,盘上不存在——裁决结论本身已由 art-bible §8.2/§8.4 与 06:465-471 承载,指路须改指现行文档或改「描述性出处」 | 2026-10-16 目录实查;06:609-615;reports/美术审计-cursor会话存档-2026-10-14.md:290,:316;git 只读查询(053baeee 建件/8e02dcf0 删件) |
| P5 | 素材不入 git(归档纪律 art-bible §8.3 L500),v3 脚本 `asset_root()` 做「本仓库→主检出 D:/code/g-plan2」两级回退,且 OUT_PNG 落笔前 `os.makedirs(..., exist_ok=True)`——**故 worktree 分支内可直接跑(v1.0 的「不可直接跑」判断证伪)**;真实约束是**产物落进该 worktree 的 `artwork/mockups/`,而 `artwork/` 整目录不入库**,冻结件不随合并进 master,须按 §8.3 归档纪律打包(这亦是 P4 丢件的成因)。素材在盘实测:board 瓦片 64 件、character 立绘/小人、`game/assets/ui/icons` 状态图标 18 件、fusion-pixel 字体齐 | 脚本头注 L31 + `asset_root` L50-56 + L62/L1147-1149;art-bible §8.3 L500;2026-10-16 目录实查 |
| P6 | 样张 v1 曾被用户**四原因整体否决**(2026-09-28),四条已全部断言化。**权威映射表在脚本写报告的那段代码里(L1156-1160),不在头注**——头注只逐条标了 ①③ 的编号,②④ 需从映射表补:①**显示太丑(总纲)**→ A6 观感密度+装饰变体 ≥2 种;②**封印色冲突**→ 封印黑 #000000(A1a 色板对表自动覆盖);③**立体感不够**→ A3 图底分离阈值上调+F6 顶面/立面双明度带 Δ≥18+F7 整板悬崖 DEPTH_PX 与游戏渲染常量同源;④**微光格只留微光**→ F5c 微光段零 draw_inlay(另见 art-bible v2.12 角嵌退役批)。注:脚本头注 L23 仍写 A3 Δ≥55、L1157 仍写「整板明度 std ≥50.9」,两处均是**旧口径残留文本**,现行定值以圣经 §1.4 v3 记录为准(A3b Δ≥30、A6 明度梯度均值 ≥4.70) | 脚本 L12-26(F1–F7/A1–A6 头注)+L1156-1160(四否决映射表);art-bible L68(Δ≥30 定值)/L570(v2.13「v1 四否决原因断言化收官」)/L569(v2.12 ④) |
| P7 | 用户气质否决另有两例点名先例:①站桩排版旧格式「盒顶固定 y 与盒内居中在高盒下拉开血条-立绘空洞」2026-10-06 用户点名否决,由 art-bible §8.2「站桩立绘排版 = 方案二」代替(L496);②playnote 立绘批评,**逐字原文**:「现在的立绘太丑了,像素化的粒度太低了,导致根本看不清立绘;看是否可以改回用较为高清的图片?」(playnote.md:9,文件本身无日期;日期系 06「资产升档重产批」条 2026-10-06 注所记「同日 playnote 立绘批评」,裁定日为 2026-10-13 归美术线) | [playnote.md](../playnote.md):9;art-bible §8.2 L496;06:465-471/06「立绘清晰度回退评估」条 |
| P8 | 氛围层两派并存(平滑渐变件 vs Bayer 有序抖动)——**已裁半**:vignette/gradient_base/beams 三件保平滑连续 alpha(2026-10-13 grilling Q5 裁,已入 §7.6(三)+§8.2 LINEAR 豁免清单);**未裁半** = 两派是否统一,明文「留整屏样张批统一裁决」——本批承接的正是这未裁半(**Q3 已裁乙为主**:统一结论在引擎实拍图上呈报、落 T3 裁决记录,见 §7) | art-bible §7.6(三) L465、§8.2 L486;06:78(迭代 26 反哺点④)/06:82(用户裁「全部按呈报写入文档」) |
| P9 | 渲染手段现状:**全仓 `.gdshader` 实测仅一枚**(`game/presentation/views/desaturation.gdshader`,结算去饱和),`ShaderMaterial` 消费面实测仅 `board_view.gd` 一处(:111 声明/:170-172 创建/:194 tween 参数),`.tscn` 内无 shader 节点;样张面全部为 PIL 代码绘制;「Godot shader 能否胜任特效」是用户已提出的未验证疑问(已由 Q3 裁定乙案承接:开工先半天试验件坐实,见 §7;另卡亦载,见 §7.5) | 2026-10-16 全仓检索(`find *.gdshader`+grep `ShaderMaterial`);徽章卡(迭代 30 卡①,已入库合并 7ffd1dc0,卡片已退役)F5/用户立场前提行 |
| P10 | **关卡屏并非零样张**:关卡画面样张 v1 已于 2026-09-28 冻结(机检 11/11 全绿,闸形 A=机检全绿即冻结,气质终审走 M-2 出口闸真人目检八项过),产物 `artwork/mockups/level-map-sheet-v1-2026-09-28.png` **在盘**。它立闸于整屏样张闸条款(v3.0,2026-09-29)之前,**从未按整屏四项判定过**——Q2 若裁含关卡屏,动作是「按四项补判既有样张」而非从零出图 | art-bible v2.14 L565、§7.2 L418;[level-map.md](../design/gdd/ux/level-map.md):3/:222/:244;roadmap.md:21(M-2 出口闸目检通过);2026-10-16 目录实查 |
| P11 | **闸的执行时机已被越过,本批性质=闸后补跑**:§1.4 明文执行时机 =「屏级视觉落码开工前」,而屏级落码已发生四轮(迭代 9 视觉落码段、迭代 16 UI 设计对齐、迭代 25 全局主题、迭代 26 交互与观感),Alpha 亦已于 2026-10-03 到手(M-3 关闭);08「三 · 界面」多行已标在役、五 · 表现仍载「战斗一屏样张=待做」——即 §7.1「占位态禁止跨里程碑」在事实上已被跨。此定性直接影响完成定义第 1/3 条的语义(过闸≠事前定方向,而是对已在役形态出目标图判定物)与 T3 回炉半径 | art-bible §1.4 L64、§7.1 L411;roadmap.md:53/:68/:84-85;08:100-118/:158;美术审计-cursor会话存档-2026-10-14.md:168 |
**与相邻 06 条的依赖关系(Q1 已裁丙案——样张即设计目标图,本节按丙案口径改实,Q1 原文见 §7)**:
- 「UI 贴图重构批」(营地与元进度件出终稿,08 标待做 08:156,06:21-27 状态待办)——判例前置件;**Q1 丙案裁定下不构成本批前置**:样张不待其终稿,反之本卡目标规格清单(尺寸档/色板/明度档逐件成文)反向给其重产/终稿提要求;
- 「资产升档重产批」(06:465-471:**已裁案乙**〔2026-09-30 迭代 9 三裁决,本迭代不升档〕+2026-10-04 用户令暂不做、重启=用户点名+2026-10-06 优先级再降;v1.0 的「档位上调方向候选」已被 v3.7 八敌逐键定档实质完成,剩余挂起项仅「提灯点亮解禁候选」)与「立绘清晰度回退评估」(08「五 · 表现」L146 待裁+08 §7 未决项第 7 行 L210,方向待裁)——素材线两个闸口,**性质不同**:前者=已裁且挂起(本批禁代裁、禁重开),后者=真未裁;**Q1 丙案裁定下两者均不再是样张前置**,本卡反提规格反而是其重启时的输入;
- 「UI 字体补完」(08:157 标待做,06:29-35 归属栏载明须先裁「补不补拉丁专件、要不要上第二个字重」)——**Q4 已裁丙案**:样张按「目标字重效果」示意出图、本批按样张补真件;「补不补拉丁专件、上不上第二字重」仍归该条自身未裁点,本卡不代裁(见 §7)。
### 0.5 样张批继承的既有待裁义务清单(v1.1 新增——全库检索「待样张批冻结/终审/追认/重议」所得)
**为什么要这一节**:这些事项不是本卡新造的议题,而是各权威文档**明文把裁决动作挂名到「样张批」**的既有欠账——本卡若不受理,就会在「本批做完了」的读数里静默丢掉十件在册义务;若受理,就必须逐项写清「本批冻结/本批不冻结并说明去向」。**受理方式已随 §7 已裁 Q 项改实(v1.2)**:Q1 丙案——H1–H4 的尺寸欠账由「目标规格清单随样张冻结产出」承接;Q2 甲案——本批只判战斗屏;Q3 乙案——H10 之氛围统一结论在实拍图上呈报;Q6 乙案——两闸分跑。**本表对 H 项内容本身仍不预设结论——逐项最终处置(冻结/不冻结+去向批次)按契约 7 落 T3 冻结记录。**
| # | 欠账项 | 现行状态与出处 | 与本批的关系 |
|---|---|---|---|
| H1 | 19 枚格面奖励徽章**尺寸**(源 40×40;显示 20/40 双档候选) | art-bible §4.6.2 L309「尺寸候选,样张批冻结」 | 整屏样张要放徽章位即触到该尺寸;未放则显式写「本批不冻结,归贴图批」 |
| H2 | 地形变异纹带族**尺寸**(源 64×64 无缝) | art-bible §4.6.3 L328 同口径 | 同上 |
| H3 | B 区舞台背景 `stage_bg.png` **尺寸**(源 1280×270 基准一张) | art-bible §6.1 L364 同口径;§6.1 L361 地窟看台「规格与终值归整屏样张闸冻结」 | 战斗屏样张的 B 区背景件,正面触到 |
| H4 | 护甲类型(light/ward/heavy)视觉区分方案 | art-bible §4.6.4 L335/§5 存根 L350/[character-design.md](../design/art/character-design.md) §5.3 L27「候选方案,样张批冻结」 | 站桩名行/徽标呈现触到;是否本批裁归用户 |
| H5 | 类型微光**组间互辨抽测**义务(含明度档) | 06:431 裁决④「样张批保留组间互辨抽测义务」;§4.2 L218 组间明度档值从未定义 | **与徽章卡 Q2 直接耦合**(见 §7.5 跨卡 D1) |
| H6 | 面板间距/内边距 4px 步进档**终值** | art-bible §7.3 L426「终值归样张批」 | 整屏构图量取即触到 |
| H7 | 棋盘小人源档与格边长占比摩擦终值(36px 板 × 32px 源超 ≤0.75 红线) | art-bible §8.2 L494、v1.9 待决注记 L581 | 格边长三方取值不一致另条在册(06「棋盘格边长三方取值不一致」),本批禁代裁,只登记 |
| H8 | 镜像与「旗面恒画右」纪律相容口径(代拟待追认) | art-bible §8.4 L508「代拟口径,样张批追认」 | 冻结记录逐条写明已追认/未追认 |
| H9 | 钩爪恐魔「宽≥高」豁免(待样张批终审) | character-design L71 | 站桩样张含该敌种即触到终审位 |
| H10 | 记忆态/明度带随深度微移/提灯点亮形制/滑轨两段可辨 等 provisional 读数 | art-bible §6.5 L392(「样张/实机自检锚」)、§2.7 L129(「候选,整屏样张冻结」)、§7.6(二) L456(滑轨归样张目检)、character-design L45(提灯点亮形制归整屏样张批重议) | 整屏样张即其判定物;本批须逐项在冻结记录写「裁了/留哪一批」 |
| H11 | 「圣经口号型条款机检化批」前置整备(约 55% 条款属口号型无可机检值,分章推进) | 06:513-515「随整屏样张闸前整备批启动」(历史梳理稿 I15 行同义在册,该稿已按吸收即死退役,沿革查 git 提交史) | 跨卡次序已改实(§7.5 D3 已裁三卡串行序位=徽章→样张→观感,本卡位第 2);本整备批是否纳入本批开工门仍属排期题,归用户呈报,本卡不自裁 |
### 0.6 已裁项核收表(v1.1 新增——铁律「禁再问已裁项」的检索留档)
下列事项**已被用户明文裁决**,本卡全部 Q 项一律**禁止重开**;表中同时是本卡相关表述的执法依据。
| 事项 | 现行裁决 | 出处 |
|---|---|---|
| 格子样张 v2 四冻结(砖面/色板/动画/数字形制) | v2 冻结(2026-09-28)→ v3.0 解冻①④ → **v3 重冻生效(2026-09-30,机检 17/17)**;②色板③动画全程未动 | art-bible §1.4 L66/L68、v3.2 L562 |
| 类型微光五组**色相** | **样张 v2 定稿(2026-09-28)**;HUD框架:140 同记「五组色相经样张 v2 冻结」——06:431「候选值,样张批冻结」系滞后措辞,**冻结为真实现状**;本批不重裁色相值 | art-bible §4.2 L218;[HUD框架](../design/gdd/systems/表现与支撑/HUD框架.md)「未开格类型微光」节实装状态行;详见 §7.5 D1 |
| 微光组粒度 | v3.12 批已裁:组间五色相、组内不细分 | art-bible v3.12 L551、§4.2 L218 |
| 未开格类型微光作用圈 | 英雄周围 8 格(非全板常显),2026-09-30 用户裁;不随灯光区扩 | 06:431 口径校正;HUD框架同节;art-bible §6.5 L390 两圈分立注 |
| 站桩形态(卡片式退役) | 2026-09-20 用户裁决全站桩式,对照义务解除 | [战斗表现](../design/gdd/systems/战斗循环/战斗表现.md):77/:83/:93;[battle.md](../design/gdd/ux/battle.md):395/:437 |
| 站桩排版 | 2026-10-06 用户拍板方案二(旧「盒顶固定 y+盒内居中」点名否决退役) | art-bible §8.2 L496 |
| 八敌立绘源档 | 2026-10-06 逐键定档批完成(36–94,显示 ×2);「48/64/96 三档候选」一并完成 | art-bible §8.2 L495、v3.7 L556 |
| 资产升档重产 | 已裁案乙(本迭代不升档)+2026-10-04 令暂不做+2026-10-06 优先级再降;重启=用户点名 | 06:465-471 |
| 1920 档缩放策略/动画帧数规格/资产升档与否(迭代 9「随闸三裁决」) | **三件全部已裁**:1920=案甲全局等比(v3.2);帧数=样张 v2 ③冻结(§8.4 L505-507);升档=案乙 Alpha 后(06 已裁) | art-bible §8.2 L490、v3.2 L562、§8.4 L505;git 提交 053baeee(呈报单原文件已按吸收即死纪律删除,见 P4 附注);本卡禁以「随样张裁」名义重列 |
| 氛围层三件保平滑 | 2026-10-13 grilling Q5 已裁(统一与否另裁,见 P8) | art-bible §7.6(三) L465、§8.2 L486 |
| 封印色 | 封印黑 #000000(2026-09-28 用户拍板;紫→黑因色相冲突) | art-bible §4.1 L196、v2.11 L568 |
| 角嵌/微光格附加图形 | 2026-09-28 用户裁「微光格只留微光」,角嵌彻底退役 | art-bible v2.12 L569、§3.2 L168 |
| 关卡屏视觉终稿 | 样张 v1 机检 11/11 冻结(2026-09-28)+M-2 出口闸真人目检八项过 | art-bible v2.14 L565;level-map.md:3/:222;roadmap.md:21 |
| 按钮五态/全局 Nearest/主题资源 | 迭代 25/26 入库并经用户 2026-10-14 裁「全部按呈报写入文档」 | art-bible §7.6、v3.14 L549;08:117-118/:155 |
---
## 1. 目标与完成定义
**目标(一句话)**:把「判定界面美术算不算过关的那把尺子」补齐并按它跑通战斗屏——**尺子 = art-bible §1.4 整屏闸四项 + [战斗表现](../design/gdd/systems/战斗循环/战斗表现.md)「一屏样张的验收标准」节五项(:79-93)构成的断言集,两套口径的合体方式已由 Q6 裁定乙案——两闸分跑(见 §7);跑通 = 方向稿目检先行 → 精制 → 机检 → 用户终审 → 冻结记录写入文档**。
**相对现状,新增了什么(批次级;v1.1 按 P1 读数重写第 1/4 条)**:
1. 现状:**格子闸执行并冻结过;整屏闸的四项机检代码也已存在并对实机渲染帧跑过**(迭代 9 T8,shot_checker B/C/D+F),但**从未产出「整屏样张判定物」、从未立整屏冻结记录、③焦点预算从未判定**——本批新增=样张判定物本体+整屏冻结记录首条(战斗屏;四屏全套随 Q2 甲案余三屏登记 06 排队行归后续批)+③焦点项的目检判定+取样点位按屏扩面(T2);
2. 现状产图工序 = 脚本一次合成单路线成品直接呈报——本批新增**方向稿先行**硬工序(本批战斗屏先出 2–3 张低成本方向稿供用户选划,方向判完才准精制,T1;后续各屏各自立此闸);
3. 现状渲染手段默认锁死「PIL 程序化代码绘制」——本批把代码绘制 / shader / 新素材三路线的选型呈用户裁决(Q3),**已裁乙为主、预设甲案退路(2026-10-16 用户令)**:Godot 引擎实拍+shader 为目标路线,开工先半天试验件坐实,不过闸即退甲案纯代码管线(退路预裁定,见 §7);
4. 现状两套标准并存未合体(§1.4 整屏四项 / 战斗表现专项五项)——本批合成一张逐项比对表(T2),合体方式**已由 Q6 裁定乙案:两闸分跑**(专项第 5 项只产裁决记录)。
**防产出重蹈不满意(批次级,三道防线)**:
- **防线一(工序)**:否决成本前移——方向稿阶段否决只损失半天,精制阶段否决按战斗表现判定回退条款只回炉不过项(继承既有口径「已过项结果保留」,战斗表现:93);禁止跳过方向稿直接出精制版(V1:无目检记录即开工精制 = 违红线)。
- **防线二(判据)**:P6 四条否决原因(①显示太丑/②封印色冲突/③立体感不够/④微光格只留微光)+P7 两例(站桩排版空洞、立绘像素粒度)逐条映射为整屏断言集内的具名项;断言不过不出图(「生成即验,不过闸不出图」纪律沿 v3 脚本原文 L12)。
- **防线三(终审)**:气质终审权恒归用户目检,机检全绿不等于过闸。先例:v3 冻结时气质终审走的是「验收豁免」(art-bible L68,2026-09-29 用户验收豁免,状态记录 281),本批**不申请豁免**——此为**本批自我从严的执行纪律,不是对用户裁决的改动**;用户若主动以豁免方式判定本批完成,从其裁决。
**完成定义**(全部满足才算本批完成):
1. 战斗屏样张过闸:方向稿用户选划记录在案 + **按 Q6 已裁乙案两闸分跑、两闸全绿**(含 ③焦点预算的目检判定记录;专项第 5 项只产裁决记录不产过/不过) + 用户终审「过」记录;
2. 冻结记录**成文**(内容 = art-bible §1.4 新增整屏样张冻结记录段全文——设计权威文档零直改,**仅过闸后成文**):同段逐条写明「本样张冻结了什么、没冻结什么」,并对 §0.5 的 H1–H11 **逐项给出处置**(本批冻结/本批不冻结并写明去向批次),禁留空;**同段另须产出两件交付物:① 目标规格清单(Q1 已裁丙案:尺寸档/色板/明度档逐件成文,反向给素材重产批提要求,承接 H1–H4 尺寸欠账);② 徽章卡 Q2 甲案 provisional 明度档之复核转正记录(H5 组间明度档复核+组间互辨抽测,复核转正义务按 §7.5 D1 落本卡冻结件,为本卡交付义务)**——全文随 `reports/iter30-反哺点-<完成日>.md` 对应条目交付(「改成什么」栏即全文),「写入 art-bible §1.4」列反哺点,落文归文档批;
3. 状态回写义务列反哺点(判据语义不变;**条款在 art-bible §7.1 L411**;08 侧的行是「五 · 表现」表「战斗一屏样张」行 L158,08 无 §7.1 小节——v1.0 此处指路写错已订正):
- 反哺点条目:08「五 · 表现」表「战斗一屏样张(界面美术的终稿闸)」行:待做 → 在役;
- 反哺点条目:[art-status-checklist](../design/art/art-status-checklist.md)「战斗一屏样张」行:⬜ → ✅;
- **边界条款**(写进反哺点条目本身):样张过闸只是**判据生效**,不是各屏占位素材的转正条件——看板其余 ◐ 行(营地件、格面徽章、演出类等)仍须各自素材交付后才可改 ✅,禁止把「样张过闸」扩解为「全部占位转在役」;样张同时按**已裁 Q1 丙案**冻结目标规格清单、反向给素材重产批提规格要求(尺寸档/色板/明度档逐件成文),转正仍逐件走闸;
- **P11 定性连带**(写进反哺点条目本身):本批属闸后补跑,回写禁写成「落码前定方向」口径,须如实注记「屏级落码已先行四轮,本样张为对已在役形态的目标图判定与冻结」;
4. **Q2 已裁甲案**:本批仅出战斗屏;关卡/菜单/营地三屏的 **06 排队行登记内容属本批交付义务**(排队行措辞含 P10 注记与营地屏「待目标规格清单反提要求」注记),随反哺点清单交付、落文归文档批;关卡屏既有 v1 冻结样张保留为注记(**其后续批是「对既有 v1 冻结样张补判整屏四项」,不是从零出图**,P10);
5. §8 同步面清单每条在反哺点清单有落点;权威文档与 06/08/看板零直改(v3 口径)。
---
## 2. 范围边界
**做**:战斗屏样张全流程(方向稿→精制→双口径机检→终审→冻结);四屏排程落地按 **Q2 已裁甲案**(本批仅战斗屏+余三屏 06 排队行登记内容,随反哺点清单交付);整屏断言集的**承载与扩面**——**优先复用 `tools/shot_checker.py`(已有 B/C/D/F)+ `game/smoke/shot_probe.gd` 的 shot_meta.json 坐标源**,缺口(③焦点判定、逐点位覆盖、色数上限计数)才新建,禁另起一套并行判据;§0.5 H1–H11 继承义务的**逐项受理与处置登记**(冻结或写明去向,不自裁内容);氛围层两派统一裁决的**裁决与登记**(**Q3 已裁乙为主**:统一结论在引擎实拍图上呈报、落 T3 裁决记录,写入文档列反哺点,落码归后续呈现批);P3 看板与 06:41 两处旧锚订正、P4 死指涉处置(**Q5 已裁甲案**:改指在盘最近稿 v31/v32/v33-draft 之一并注「指代稿」,具体指哪份 T0 实查盘面后定)+ 06:611 行号读数自身订正、战斗表现内容清单「四区」旧措辞订正——以上文档订正一律以反哺点条目交付(写清文件/节/改成什么/依据),零直改。
**不做**(明示去向):
- **不重产任何正式素材**——终稿素材出图归 UI 贴图重构批 / 资产升档批各自立项;
- **不改游戏运行时渲染代码**——样张是判定物不是实现物;**Q3 已裁乙案(shader/实拍路线)**,样张侧 shader 试验件与实拍管线只进样张管线,游戏侧落码另批;
- **不执行格子闸重跑**——格底明度档、数字承载面已随 v3 冻结(art-bible §1.4 L68),整屏样张直接在其上合成;
- **不重裁 §0.6 表内任何已裁项**——尤其微光五组色相(v2 已定稿)、1920 档策略(案甲)、动画帧数(v2 ③冻结)、资产升档(案乙+挂起)、站桩形态与排版(2026-09-20/2026-10-06 两裁);
- **不代裁 08 未决项**——「站桩帧动画进不进 Alpha」(08 §7 第 5 行 L208)、「素材升档重产进不进 Alpha」(第 6 行 L209)、「立绘清晰度回退评估」(第 7 行 L210)三行归各自条,本卡只登记连带,禁以「样张过闸时顺带」名义吃掉(v1.0 把站桩帧动画写成「Beta/后批事项」属定性失实,已订正:它是 08 在册待裁,不是已排 Beta);
- **不动玩法规则与数值**——纯呈现层判定批,MC 零跑(零数值零行为);
- 键盘输入/手柄(08:191 已明列「六 · Alpha 不含」,排 Beta)不涉。
---
## 3. 任务分解
> 估时按 Claude 工作日口径;每卡自带两栏:**相对现状新增什么 / 怎么防重蹈不满意**。**Q1–Q6 已全部裁决(2026-10-16 用户令,见 §7),开工门 U0(用户 `/iter` 发令)后即可按任务序开工;T0 事实坐实段零路线依赖,可先行**。
### T0 事实坐实与旧账处置(≈0.5d)
- 实跑现行 [render_sample_sheet_v3.py](../tools/render_sample_sheet_v3.py) 与 `tools/render_gate.sh`(worktree 内可直接跑,见 P5 两级回退+自动建目录;跑前须防**覆盖在盘产物与机检报告**——OUT_PNG/OUT_REPORT 是硬编码旧文件名 L62-63,开工日一律改出到临时区或加日期后缀,禁写回 `sample-batch-v3-final-2026-09-30.png` 与 `sample-v3-final-机检报告-2026-09-30.md`),确认管线今天仍可出图、A1–A6+F1–F7 全绿基线读数冻结进开工记录(A4 尺寸基线硬等在盘实测成立:立绘 46×48、棋盘小人 32×32);
- P4 死指涉按 **Q5 已裁甲案**处置:T0 实查盘面**定稿具体指哪份代稿**(v31/v32/v33-draft 择一,该实查拍板为本批动作);其落文动作——art-bible:68/:562 与看板:13 三处死指涉改指代稿并注「指代稿」、同族死指涉(§8.2 L490 呈报单死路径)按同案口径一并处置、P3 两处旧锚(art-status-checklist:14 与 06:41 的 §5.1 → 正确锚 art-bible §1.4/§7.2 或 character-design)订正、06:611 行号读数(68/527 → 68/562)订正、战斗表现:83/:116「四区」→「三区 B/C/D」措辞订正(事实源 battle.md:126/:184)——逐条列进 `reports/iter30-反哺点-<完成日>.md`(v3 口径零直改,归文档批落文);新冻结记录按 art-bible §8.3 描述性出处纪律成文;
- §0.5 H1–H11 继承义务清单**逐条复核是否仍然在册**(漂移即订正本表);Q 项已全部裁决(§7),不再随 Q 呈报,清单逐项处置落 T3 冻结记录(契约 7);
- 复核本卡 §0 全部行号与事实,漂移即订正卡片。
- **新增**:开工日可跑性证据+现有机检面读数基线(现状:shot_checker 周期在跑,样张脚本自 2026-09-30 后无跑动记录)。**防不满意**:先验证旧管线还活着、先量出现有机检代码到底判到哪一项,再决定它在新工序里的角色——避免「新工序设计了,旧工具已哑火」或「重复造一套已存在的判据」两种空转。
### T1 方向稿(战斗屏 2–3 张,目检先行)(≈1.5–2d 含 Q3 乙案试验件前闸 0.5d)
- **Q3 乙案前闸(开工第一个动作,半天)**:Godot 引擎实拍+shader 试验件坐实发光类特效能力边界(光带/泛光/呼吸感)——过闸即走实拍目标路线;**试验件不过闸即按 §7 Q3 预设退路退甲案纯代码管线重出(退路预裁定已写入,不回炉问用户,仅留痕)**;
- 战斗屏方向稿 2–3 张:内容骨架按 [battle.md](../design/gdd/ux/battle.md) 现行 B/C/D 三区布局(:126/:184;**战斗表现:83/:116 仍写「四区」系旧措辞,T0 订正**)与 [战斗表现](../design/gdd/systems/战斗循环/战斗表现.md) 一屏样张内容清单(:83——站桩单版〔卡片式已 2026-09-20 裁退,§0.6〕、三语境剪影同图、受击/退场/高光三组动画关键帧条、敌层遮挡对照组),**气质方向分岔**(Q3 已裁乙为主:A 引擎实拍目标形态〔主线〕/ B 实拍+氛围层统一后形态(两派差异在实拍图上直接可见,统一结论呈报落 T3)/ C 代码绘制甲案基线对照张〔试验件不过闸时 C 升主线〕);
- 方向稿张数与气质分岔在 2–3 内自定(2026-10-16 用户令已把三卡各 Q 项全数裁定,方向稿层无待裁项遗留);
- 方向稿允许低成本形态(构图级即可,细节参数不必全冻结),每张附「本方向解决了哪条否决先例」标注(P6/P7 逐条);
- **用户选划 = 人闸**:判完方向才准进 T2;选划记录(哪张、为什么、否决了哪张)登记开工单。
- **新增**:现状无此工序(v1–v3 均为单路线成品一次呈报)。**防不满意**:把「不满意」拦在最便宜的阶段;多路线并呈使用户从「验收者」变「选择者」,气质分歧在方向稿层解决而不是精制品层返工。
### T2 战斗屏精制版 + 双口径断言集(≈2–2.5d,含实拍静态定格工序新建:shot_probe 现出「现状图」,目标构图定格工序为本批新建——§7 Q3 乙案真实成本)
- 按选划方向精制战斗屏 1280×800 整屏样张(1:1 实比);
- 断言集扩展(**先复用后新建**:`tools/shot_checker.py` 的 B/C/D/F 段已承载整屏 ①②④,`game/smoke/shot_probe.gd` 已产 shot_meta.json 坐标源;本批缺口=①的逐点位覆盖(现行 B 段只判 meta 登记的已知文字框三处,禁把「三处」报成「全部文字」)、②的非战斗屏取样(现行 C 段只算战斗屏 C 区/棋盘/舞台三点——**Q2 已裁甲案,本批不动非战斗屏,取样面仅扩战斗屏逐点位**)、③焦点预算(明文归人闸,**禁伪装成机检**)、色板 48 色上限计数(现行 A1a 是「对表圣经冻结色值+禁入带零命中」,**不是 48 色计数**;全仓无 48 色工具,`tools/` 实查零命中,须本批新建,且与 art-bible §8.3 计划的入包色板检查器分工不同——本批只做全图色数计数,不建入包闸);落点扩旧脚本 vs 建新脚本按工程就近原则批内自定):
- **整屏四项**(art-bible §1.4 L64 闸条款,与 §7.2 L416 同源):①全部文字(含菜单/说明/tooltip/按钮)对直接背景 WCAG ≥4.5:1,大字〔≥18px 或加粗档〕底线 3:1、目标 4.5:1;②明度三档带成立(信息面层 > 世界中层 > 世界底层,层间灰度可分);③整屏焦点预算(§3.4 首焦唯一,无第二焦点竞争);④文字禁直接压纹理底(须垫容器或罩层);
- **A1–A6 口径上屏**(脚本 L21-26+L925-1031 现行义,**逐项按实义引,禁改名义**):A1 色板对表冻结色值+禁入带 300°–345° 全图扫描、**A2 拼缝周界闭合(墙↔地墨线带亮度)**、A3/A3b 图底分离 Δ≥30(L23 头注的 Δ≥55 与 L1157 的「std ≥50.9」是旧文本,现行以圣经 §1.4 L68 定值为准)、A4 混排相融(语义色 == ui_kit 常量+在库原件可载+**尺寸基线硬等 立绘 46×48/小人源 32×32**)、A5 画布四缘净空、A6 观感密度梯度均值 ≥4.70;
- **战斗屏专项五项**([战斗表现](../design/gdd/systems/战斗循环/战斗表现.md):85-93:棋盘区零打扰/剪影三语境灰度可辨/三组动画关键帧可读/双舞台战斗空间/敌层插入位置对照组可裁)——与整屏四项的**合体方式已裁乙案:两闸分跑**(专项闸与整屏闸各跑各退,:93 已成文);battle.md:152 另载一条挂在样张上的验收项「徽章不进数字区」,一并入表;
- **H5 复核项(v1.2 新增,承接 §7.5 D1 已裁义务)**:徽章卡 Q2 甲案 provisional 明度档的复核转正+组间互辨抽测(06:431 裁决④保留给样张批的义务),结论随 T3 终审一并呈报、写入冻结记录;
- 生成即验,不过闸不出图(沿用 v3 脚本纪律原文)。
- **新增**:整屏样张判定物本体+③焦点项的判定+逐点位取样覆盖(跨屏取样随 Q2 甲案归后续批)(v1.0 所称「该四项只存在于条款文字、无对应代码」经 v1.1 实查证伪,见 P1)。**防不满意**:机检只拦「可量化的丑」(对比度、抢焦、色板越限),拦不住的部分强制留给 T3 目检——机检绿不豁免终审。
### T3 气质终审(用户目检,闸形 A)(≈0.5d 含往返)
- 精制版 + 断言报告一并呈用户终审;「过」→ 冻结;「不过」→ 按战斗表现判定回退条款分面回炉(演出/构图面回 T1 或 T2 对应项,已过项保留);
- 过闸同时产出三笔裁决记录:①敌人层绘制优先级插入位置(战斗表现:75 登记原句「随美术测试一并裁决,不在本文提前定」保留,清偿时点经 Q6 裁定随本样张一并——T3 即其清偿点);②氛围层两派统一结论(Q3 已裁乙为主:在实拍图上呈报;若方向稿层未完成,此层完成即为最迟期限);③徽章明度档 provisional 复核转正结论(H5,承接 §7.5 D1 已裁义务)。**三笔记录本体写进 `reports/`(实现报告+追认单),其文档落位(战斗表现:75 登记、art-bible §7.6(三) 改写、冻结记录段)列反哺点**。
- **新增**:明确不申请验收豁免(v3 先例有豁免,本批关闭该通道)。**防不满意**:终审语义 = 用户说不过就是不过,无「机检全绿应视为过」的反向论证空间。
### T4 后续三屏排程(Q2 已裁甲案:本卡退役为登记行)(≈0d,06 排队行登记动作并入 T5)
- **执行 Q2 甲案裁定案**:本批仅出战斗屏;关卡/菜单/营地三屏的 **06 排队行登记内容属本批交付义务**(动作列入同步面 T5/§8,随反哺点清单交付、落文归文档批);
- 关卡屏既有件保留为注记(P10):v1 冻结样张已在盘(2026-09-28,机检 11/11),排队行措辞须注明「其后续批=对既有 v1 冻结样张按整屏四项补判,非从零出图」;
- 营地屏连带改实:Q1 已裁丙案(非乙案),「等贴图批终稿」串行分支不发生;营地排队行注明「待本卡目标规格清单反提要求,终稿后再出样」;
- **跨卡次序改实(§7.5 D2/D3)**:三卡串行序位已随 2026-10-16 用户令裁定=徽章→样张→观感,本卡位第 2——观感卡排本卡之后,其 L2/L3 落地对本卡冻结读数的复核归该卡义务,本批不为其预留参数域、不被其阻塞。
- **新增**:现状四屏闸只立了条款、机检面跑过实机帧,但四屏**样张**零张(唯关卡屏有立闸前的 v1 一张)。**防不满意**:逐屏目检逐屏冻结,禁止「一套断言四处套用」把首屏过闸经验未经验证直接外推(本批只判战斗屏,余屏按排队行各自立闸)。
### T5 产出物落位卡(≈0.5d,末卡——三份 reports/ 文件写齐才算收口)
轻闸全绿后、合并前,把本批三份 `reports/` 产出物写齐(权威文档与 06/08/看板零直改;`active.md` 状态记录照常更新):
1. `reports/iter30-实现报告-<完成日>.md`——各卡状态+**实机目检总清单**(T3 终审轮次记录、V1 方向稿三段记录表〔方向稿→选划→精制〕随附)+遗留读数义务(本批冻结基线读数供观感卡对表,§7.5 D2)+变异验证记录表(新建断言段的注入实验记录);
2. `reports/iter30-代拍板追认单-<完成日>.md`——冻结记录段系列编号口径等白名单内代拍项逐条留痕;
3. `reports/iter30-反哺点-<完成日>.md`——§8 同步面每条一落点(哪个文件哪一节、写成什么、依据),其中:
- **art-bible §1.4 冻结记录段全文**(新段,沿 v2/v3 记录格式;**段内系列编号口径属批内代拍**——格子样张序列已用到 v3,整屏样张是否续称 v4 还是自成一列〔关卡屏已用「样张 v1」名〕在冻结记录里一次说清,禁两处各按一种叫法);§0.5 H1–H11 逐项处置写入该段;**随段两件交付物(v1.2 新增):目标规格清单(Q1 丙案,尺寸档/色板/明度档逐件成文)+徽章明度档复核转正记录(H5/§7.5 D1)**——三件全文即反哺点条目的「写成什么」栏;
- art-bible §7.6(三) 氛围层并存条改写为已裁决口径(经 T3 记录);
- [art-status-checklist](../design/art/art-status-checklist.md) 战斗屏行 ✅+余三屏行刷新为「入 06 排队」态;08「五 · 表现」行待做→在役;06 对应条注销(去向登记当批状态记录);**06 为关卡/菜单/营地三屏新立排队行(Q2 甲案交付义务;措辞含 P10 注记「后续批=对既有 v1 冻结样张补判整屏四项,非从零出图」与营地屏「待目标规格清单反提要求」注记)**;
- roadmap 迭代 30 行状态刷新(编入笔已在案,只刷状态);完成定义第 3 条「转正边界条款」与 P11 定性连带注记随 08 行条目交付(防「样张过闸」被误读为占位素材全体转正)。
- **防不满意**:冻结记录段全文逐条写明「本样张冻结了什么、没冻结什么」,后续批对表施工,杜绝各自解释。
**估时合计 ≈5–6d(v1.2 按已裁口径重算)**:Q2 甲案 → T4 退役为登记行(0d);Q3 乙案 → 新增试验件 0.5d(T1 前闸)+实拍静态定格工序新建 ≈0.5d(T2 增量)。T0 0.5 + T1 1.5–2(含试验件 0.5)+ T2 2–2.5 + T3 0.5 + T4 0 + T5 0.5 = **下限 5d、上限 6d**;**若试验件不过闸退预设甲案退路,回退 ≈4.5–5.5d**(扣实拍新建增量,已耗 0.5d 试验件保留)。**软盒拆批线改实**:v1.1 的「四屏全推超 7d 拆两批」分支随 Q2 甲案不再发生——本批 5–6d 在 7d 软盒内单批跑完;若实拍路线超支逼近 7d,仍在 T3 末截断(战斗屏已过闸可单独交付见效),余三屏已列 06 排队行无批内拆分需求。
**估时构成依据(先例出处,v1.1 补、v1.2 沿用)**:整屏四项机检的建造+三裁决呈报=迭代 9 T8 实测 3–4h 量级(.zcode/plans/m3-iter9-loop-plan-2026-09-30.md:65)——故 T2 走「复用+扩面」而非重建,增量只花在实拍定格工序新建;同窗格子样张机检冻结=迭代 9 批 A T1 估 2–3h(同计划书:58);方向稿多轮呈报的先例工时见迭代 26(估 2–2.5d 实际单会话)、迭代 16(估 6.9d 实际单会话)、迭代 13(估 4.8–5.3d)、迭代 29(估 5.5–7d);本批估时主要吃在「方向稿+用户往返目检+实拍管线新建」,roadmap 迭代粒度界定已裁「迭代周期 ≈ 一周左右」(2026-10-11 用户裁定,roadmap.md:101),本批单屏口径恰在一迭代盒内。
天然切分点:T3 末(战斗屏已过闸可单独交付见效)。
---
## 4. 验收(逐任务对应,全绿 + 记录齐备才走 T5)
| # | 需求点 | 测什么 | 落点/判定手段(实存工具,2026-10-16 核) |
|---|---|---|---|
| V1 | 方向稿先行工序 | 每张精制样张有对应「方向稿 + 用户选划记录」在案;无记录直接精制 = 违红线 L3 呈报 | **人闸留痕,无工具可判**(降级为件核:开工单/实现报告附「方向稿 → 选划 → 精制」三段记录表;禁称机检化) |
| V2 | 整屏四项 | ①文字对底 ≥4.5:1 逐点位(大字档 3:1 底线)②三档明度带层间灰度可分 ③首焦唯一 ④无文字裸压纹理 | ①②④=`tools/shot_checker.py` B/C/D 段(+F 段),坐标源 `game/smoke/shot_probe.gd` 产的 shot_meta.json,**须先按屏扩取样点位**;**③=人闸**(shot_checker E 段明文「语义面无法机检」),本批以目检记录判定并留档 |
| V3 | A1–A6 上屏 | A1 色板对表+禁入带全图扫描、A2 拼缝周界闭合、A3b Δ≥30、A4 混排相融(含尺寸基线硬等)、A5 四缘净空、A6 密度 ≥4.70;**另新建 48 色计数**(现行无此工具,art-bible §8.3 入包色板检查器另属他批) | 同上脚本机检段(`render_sample_sheet_v3.py` L925-1031 复用扩展),生成即验 |
| V4 | 战斗屏专项五项 | 按 Q6 已裁乙案**两闸分跑**执行(专项第 5 项只产裁决记录);敌层插入位置随本样张一并清偿,产出裁决记录一条 | 第 1 项借 V2/V3 断言(数字占位区零侵入沿 F5 族);第 2 项三语境灰度可辨**须新建**(可仿 §1.4 验收 2 灰度分类法);第 3/4/5 项=图上元素存在性+判读,**归 T3 目检**;裁决记录(含 H5 徽章明度档复核转正结论)落 T3 |
| V5 | 否决先例映射表 | P6 四条 + P7 两例逐条对到具名断言项(映射底表=脚本 L1156-1160),表格随样张一并归档 | 人工产物,实现报告附件(T2 出表、T3 随终审一并呈) |
| V6 | 用户终审 | 「过」字记录;不过则回炉轮次计数留痕 | T3 人闸(active.md 开工单/状态记录留痕) |
| V7 | 产出物落位 | `reports/` 三份写齐且互一致:反哺点清单 §8 每条有落点(08 行/看板行/06 注销/art-bible 冻结段全文四处对应),边界条款与 §0.5 处置表、06 三屏排队行(Q2 甲案)与目标规格清单(Q1 丙案)内容在册;文档落文与链接/废止词复核归文档批(不进本批域) | 件核:反哺点清单逐条对表 §8;闸面由 `tools/run_all_gates.sh` 按改动域选档承载(唯一闸入口与唯一闸清单,卡内不另列名单) |
**闸口径**:本批零玩法零数值——MC 零跑;批末重闸 = `tools/run_all_gates.sh` 按本批改动域选档(本批触碰 `tools/` 样张面与断言扩面、零 `game/` 运行时改动 → `--code`)全绿才合并;**样张脚本不入门禁常设闸表**(周期件属性,本批零闸件增删),跑动记录留档。卡内不另列闸件名单(脚本即唯一清单)。
---
## 5. 设计契约(红线)
🔴 **契约 1 · 零游戏运行时改动**:本批只产图、只动样张工具面与文档;任何「要改 game/ 代码才能让样张成立」的发现 = L3 停机呈报,改动另批。
🔴 **契约 2 · 方向稿不可跳**:目检先行是闸形(art-bible §1.4 原文),跳过方向稿直接精制 = 本批最高级违例。
🔴 **契约 3 · 卡片不代拍路线**:渲染手段、素材输入级别、四屏范围、字重前提——全部以 Q 项呈用户,批内任何任务禁止预设某路线已成立。**v1.2 闭合**:六项已全部经用户令裁定(2026-10-16 用户令:三卡全部待裁项按推荐方案裁决〔用户将裁权授出,由主线会话代拍板留痕〕,见 §7),本契约转为执法面——各卡按已裁口径执行,批内禁止自行升档、改线或另开路线。
🔴 **契约 4 · 机检不豁免目检**:断言全绿不构成过闸;终审权恒归用户;本批不申请验收豁免。
🔴 **契约 5 · 权威文档零直改(v1.3 换代)**:art-bible §1.4 冻结段、08、06 等一切文档回写义务一律转为反哺点清单条目——**过闸后成文**(未过闸状态下清单内对应条目只许注记「进行中」),用户会话裁决后归文档批落文;实现批唯一可写目录 = `reports/`(`active.md` 唯一例外)。
🔴 **契约 6 · 已裁项不可重开(v1.1 新增,用户红线)**:§0.6 核收表所列事项一律禁止以「样张冻结须总览」「再确认一遍」名义重列备选或再出推荐;本批 Q 项若与 §0.6 任一行相撞,以 §0.6 为准,该 Q 项当场收缩或删除并登记删除依据。
🔴 **契约 7 · 继承义务禁静默丢弃(v1.1 新增)**:§0.5 H1–H11 必须在冻结记录里逐项给「冻结/不冻结+去向批次」;留空即视为本批未完成,禁止以「不在本批范围」一句带过。
---
## 6. 异常分级
- **L1(自救)**:脚本路径/字体回退(素材两级回退失效时改主检出跑)、断言阈值取数笔误、图片命名不合归档纪律、样张产物误写回旧硬编码文件名(T0 已禁)。
- **L2(记录继续)**:方向稿张数在 2–3 内自选、断言实现落点(扩 shot_checker/旧脚本 vs 建新脚本)、精制版像素级微调不触发重目检的边界(构图级 = 需重目检,参数级 = 免)、冻结记录段的样张系列编号口径(见 T5 代拍注记)、**Q3 试验件不过闸 → 按 §7 预设退路退甲案纯代码管线重出(预裁定已成立,留痕即可,禁回炉问用户)**。
- **L3(停机呈报)**:T0 实跑发现 v3 管线或 shot_checker 链已不可跑通且修复超出样张面;方向稿全部被否决(触发 Q1 口径复核呈报——Q1 已裁丙案,改动须重新裁决留痕,禁批内自开重裁);发现整屏断言与格子 v3 冻结参数数学冲突(先例:v3.0 时代 Δ≥55 与明度分档带数学冲突退役);**本卡目标规格清单(Q1 已裁丙案)反提后,素材重产批若发生档位换代,触 A4 尺寸基线(立绘 46×48/小人源 32×32 硬等)变更——圣经 §1.4 L68 明文「资产升档批换代须显式解冻重校」,解冻须走 §1.4 修订,禁批内自改,本批样张侧亦禁预先解冻**;(v1.2 修订:原「Q1 裁乙案但前置批逾期未出终稿」等待场景随丙案裁定不发生,条目删除);**§7.5 跨卡前提冲突**(任一后续卡的前提在执行中被否定——三卡串行序位已裁=徽章→样张→观感,本条保留为序内冲突兜底)。
- **L3 分类结论注(2026-10-17 用户裁 U1 预授权时一并复查,留痕防重复呈报)**:本卡四项 L3 **全部归入「触既有裁决面/超出本批面」类,一律维持停机呈报**——①v3 管线不可跑通且修复超样张面=超出本批面;②方向稿全否=回 Q1 口径复核(Q1 已裁丙案,重裁须用户);③A4 尺寸基线解冻=圣经 §1.4 L68 明文须走修订;④跨卡前提冲突=序位裁定面。本卡无一属于「可自动处置类」,故 U1 预授权对本卡零改动。**两处人闸(T1 方向稿选划/T3 气质终审)同属不可取消项**:契约 2「跳过方向稿=本批最高级违例」、契约 4「机检全绿不构成过闸且本批关闭豁免通道」,取消即拆掉防不满意机制本体。
---
## 7. 裁决记录(v1.2 改实:本卡 Q1–Q6 全部已裁。裁决来源统一措辞逐条留痕:「2026-10-16 用户令:三卡全部待裁项按推荐方案裁决(用户将裁权授出,由主线会话代拍板留痕)」;本节保留选项原文+裁定案+一句理由,v1.1 的「不带推荐案、逐条亲裁」呈报框架随用户授出裁权关闭,备选案文字仅存档供对表)
> **v1.3 执行口径注(2026-10-17 文档同步闸 v3)**:本节裁定文中「目标规格清单…落 §1 完成定义 2/T5/§8」「三屏登记 06 排队条的动作列入同步面,写入文档时点=批次出口(T5/§8)」等落文类表述,一律执行为「成文内容随 `reports/iter30-反哺点-<完成日>.md` 交付,用户会话裁决后归文档批落文」——裁定内容(做什么)不变,落文方式随 v3 换代。
| # | 事项 | 备选案与影响 |
|---|---|---|
| **Q1** | **素材输入级别(本卡核心裁点)**:样张用什么料出? | **甲 · 纯现有占位资产合成**:最快(T1 可直接开跑),零前置依赖;代价 = 量的是占位态的屏——素材重产后样张作废须重跑,且「丑」的一半源头恰是占位素材本身,用户不满可能原样复现。**乙 · 等贴图重构批与素材终稿出件后再出样张**:样张一次到位量真料;代价 = 串行依赖两批(UI 贴图重构批 06:21-27 待办;立绘清晰度回退评估 08 §7 第 7 行待裁——**注意:「资产升档重产」不是未裁方向,而是 2026-09-30 已裁案乙+2026-10-04 令暂不做、重启=用户点名,本案只能等其被点名,**禁止代裁****),周期以周计起步,期间「判定尺子没造出来」状态持续;**连带硬账**:素材换代即触 art-bible §1.4 L68「A4 尺寸基线…资产升档批换代须显式解冻重校」,样张的 A4 硬等断言(立绘 46×48/小人源 32×32)须先解冻(走 §1.4 修订),不是零成本串行。**丙 · 样张即设计目标图**:样张承担定稿目标职能,冻结的规格反向给素材重产批提要求(尺寸档/色板/明度档逐件成文,正对 §0.5 H1–H4 的尺寸欠账);代价 = 要求方向稿本身已接近目标气质,用户对既有程序化气质不满时,方向稿的路线选型(Q3)权重陡增;素材批按样张规格返工的工程量未估,**且本案出的「规格建议单」不改变 08 未决项 6/7 的待裁性质(升档与否、回退高清与否仍归各条用户裁)**。**三案可混合**(如战斗屏走甲/丙、营地屏走乙)——混合粒度亦请一并裁定 **〔已裁:丙——2026-10-16 用户令:三卡全部待裁项按推荐方案裁决(用户将裁权授出,由主线会话代拍板留痕)〕裁定案:样张即设计目标图,样张承担定稿目标职能,冻结规格反向给素材重产批提要求。理由:唯丙案直接服务立场前提(因丑而改),且消除对已挂起素材闸口的串行等待。任务面同步(v1.2):① 新增交付物「目标规格清单随样张冻结产出(尺寸档/色板/明度档逐件成文)」,落 §1 完成定义 2/T5/§8;② 「三案可混」语义关闭——战斗屏适用丙案,其余三屏按 Q2 裁定(甲案)不在本批,无混合对象;③ 乙案「A4 尺寸基线须显式解冻重校」连带仅于规格清单引发后续素材档位换代时在另批触发(§6 L3 留兜底)** |
| **Q2** | **四屏范围与次序**:本批做几屏? | 战斗屏第一优先的依据已成文(06:37-43「最高优先级」+ 战斗表现:73「一屏样张是硬前置」+ 看板 ⬜ 现状),不复议;剩余 = 关卡/菜单/营地三屏:**甲 · 本批仅战斗屏**,余三屏 06 立排队行;**乙 · 战斗+营地**(营地是全作最暖屏、贴图债最重,Q1 裁乙案时此组合依赖最重);**丙 · 四屏全本批**(按 v1.1 重算估时 ≈8.5–11d,超 7d 软盒,按 T4 拆分点强制拆两批)。**次序连带(v1.1 补,与 §7.5 D2 同读)**:关卡屏已有立闸前的 v1 冻结样张(P10),若排在本批之后且其 L2/L3 换色/重产落地,本批冻结的明度三档带与观感密度读数即作废须重跑——四屏的先后不是纯排期题,是作废范围题 **〔已裁:甲——2026-10-16 用户令:三卡全部待裁项按推荐方案裁决(用户将裁权授出,由主线会话代拍板留痕)〕裁定案:本批仅战斗屏;关卡/菜单/营地三屏登记 06 排队条的动作列入同步面,写入文档时点=批次出口(T5/§8)。理由:单批 5–6d 在迭代盒内跑透一屏一闸,胜过四屏平摊超盒。关卡屏已有 v1 冻结样张(P10)事实保留为注记;乙/丙案及其作废范围连带段仅存档不执行** |
| **Q3** | **渲染手段选型**:样张怎么画出来?(用户已质疑 Godot shader 做特效的能力边界——选项、影响、代价列全,已裁见右栏) | **甲 · 纯代码绘制(PIL,现行管线)**:零新依赖、A1–A6 断言体系现成;已知上限 = v1 四否决原因全部产自该路线,氛围类效果(光带/泛光/呼吸感)只能静态模拟。**乙 · Godot 引擎实拍 + shader**:样张=引擎真实渲染(截屏管线先例 **`game/smoke/shot_probe.gd`+`tools/shot_checker.py`**——v1.0 误写为 `tools/shot_probe.gd`,已按实存路径订正;`tools/render_gate.sh` ①段即为该通路),发光/抖动/去饱和与实机逐位一致,氛围层两派并存(§7.6(三) L465)可在实拍层直接看出统一效果;代价 = 全仓 shader 仅 desaturation.gdshader 一枚先例(P9 实测),发光类特效能力边界未验证,须先半天试验件坐实「能不能」再谈「好不好」,且实机依赖动画与光照时序、静态定格取样工序需新建(shot_probe 现按「真实按钮信号路径驱动+双帧抓视口纹理」出帧,无「目标构图」概念——它出的是现状图,样张要的是目标图,这层差距即本案的真实成本)。**丙 · 新素材合成**(贴图批/远程生图件入样张画布):质感上限最高;代价 = 即 Q1 乙案语义,本选项在 Q1 裁甲/丙时不可用。**丁 · 混合**(底板引擎实拍 + 标注层脚本,或方向稿用甲精制版换乙):灵活;代价 = 两套管线双份维护与断言口径分岔。〔氛围层两派统一(平滑 vs 抖动)随本案一并裁:选了乙,两派差异在实拍图上直接可见,裁决成本最低〕 **〔已裁:乙为主、预设甲案退路——2026-10-16 用户令:三卡全部待裁项按推荐方案裁决(用户将裁权授出,由主线会话代拍板留痕)〕裁定案:Godot 引擎实拍+shader 为目标路线;开工先半天试验件坐实发光类特效能力——试验件不过闸即退甲案纯代码管线重出。理由:目标图须与实机渲染同语系,而退路预裁定已写入本栏,失败面不再回炉问用户(留痕即可,见 §6 L2)。连带:氛围层两派统一结论在实拍图上呈报、落 T3 裁决记录;丙案即 Q1 乙案语义,随 Q1 裁丙不可用;估时已按本案重算(§3)** |
| **Q4** | **字重与拉丁专件是否入样张前提**:06:29-35「UI 字体补完」条载明现行单字重、层级只能靠字号档差一个手段(art-bible §7.4 L434 字重纪律定的口径是**字号档差〔相邻层级 ≥1.25×〕+墨色深浅+字重两档**三件齐用)——样张用现行单字重出,还是先补字重再出? | 甲 · 现行单字重出样张:零前置;风险 = 样张冻结的层级观感建立在缺一个手段的基座上,字重补齐后层级重排可能追改样张;**另一处连带读数**:§1.4/§7.2 的大字档判据写作「≥18px **或加粗档**」,现行单字重下「加粗档」这一腿在实机上不存在(FS_TITLE 24/FS_H1 34 只吃字号档,§7.4 L432 六档),故本案直接决定整屏①项能不能按原文全判。乙 · 字体补完先行:样张一次到位;代价 = 又添一条串行前置(字体条本身含「补不补拉丁专件、上不上第二字重」未裁点——该未裁点归字体条 06:33,本 Q 只裁先后次序,禁在本卡代裁)。丙 · 样张按「目标字重效果」示意出图(方向稿级),字体批按样张补真件(即 Q1 丙案语义在字体面的特化) **〔已裁:丙——2026-10-16 用户令:三卡全部待裁项按推荐方案裁决(用户将裁权授出,由主线会话代拍板留痕)〕裁定案:样张按「目标字重效果」示意出图,字体补完批按样张补真件——即 Q1 丙案语义在字体面的特化,两案口径一致。理由:样张即目标图口径下层级观感不必建在缺手段的基座上,且零串行前置。「补不补拉丁专件、上不上第二字重」仍归字体条自身裁点(06:33),本卡不代裁;甲/乙案文字仅存档** |
| **Q5** | **旧样张死指涉处置**(06:609-615 在册 P0①,本 Q5 已裁,处置落 T0):art-bible **L68 与 L562**(06 原文写「68/527 行」,527 行现为 §9.1 参考表——**行号读数已过期,T0 连 06 该行一并订正**)与看板 L13 仍引用磁盘已不在盘的 `sample-batch-v3-final-2026-09-30.png`;同系列 v0/v2/v31/v32/v33/v34/v35-draft 在盘。 | 甲 · 死指涉改指在盘最近稿并加注「冻结原件失管,指代稿」;乙 · 判过期——旧引用随本批整屏样张冻结记录一并换代重写;丙 · 随本批重跑后新产物自然替代(不做单独修复动作)。〔登记纪律注:art-bible §8.3 L500 归档纪律本就规定主文档禁引用 artwork/ 散文件路径——无论何案,新冻结记录按「描述性出处」写,不再挂散路径;**同条 P0②(主角棋盘小人未走像素管线,重产未在册)属 06 同条另一未裁事项,本卡禁把 P0② 一并吃掉**〕 **〔已裁:甲——2026-10-16 用户令:三卡全部待裁项按推荐方案裁决(用户将裁权授出,由主线会话代拍板留痕)〕裁定案:art-bible 68/562 行与看板 13 行三处死指涉改指在盘最近稿 v31/v32/v33-draft 之一并注「指代稿」,具体指哪份由 T0 实查盘面后定;新冻结记录按 §8.3 描述性出处纪律。理由:指代稿保留判定物演进链可追溯,优于判过期换代重写。P0② 与本裁分离,仍归 06 条;乙/丙案文字仅存档** |
| **Q6** | **双口径断言集合体方式**:整屏四项(art-bible §1.4 L64)与战斗屏专项五项([战斗表现](../design/gdd/systems/战斗循环/战斗表现.md):85-93)合并为一张断言集一次判定,还是两闸分跑(先专项后整屏,各自回退语义不同——专项第 5 项只产裁决记录不产过/不过,:93 已成文)? | 甲 · 并集单闸:一次出图一次判定,报告最简;乙 · 两闸分跑:与两文档各自 owner 语义对齐,回退定位更准;代价 = 双份跑批记录。**注(v1.1):本案只裁「两套既有口径怎么合体」,不重裁两套口径本身的内容——四项与五项均是既有条款。** 另附裁:敌层插入位置裁决义务是否随本样张一并清偿(战斗表现:75 登记「随美术测试一并裁决,不在本文提前定」——该义务本就挂在本样张上,本 Q 只裁清偿时点,不裁是否要做) **〔已裁:乙——2026-10-16 用户令:三卡全部待裁项按推荐方案裁决(用户将裁权授出,由主线会话代拍板留痕)〕裁定案:整屏四项与战斗表现专项五项两闸分跑,各自回退语义独立;专项第 5 项(敌层插入位置对照组可裁)只产裁决记录、不产过/不过。理由:两口径各有 owner 文档,分闸回退定位更准。敌层绘制优先级插入位置「随本样张一并清偿」的原句保留(战斗表现:75),清偿点=T3;甲案文字仅存档** |
**开工门(v1.2 改实)**:Q1–Q6 已全部裁决(2026-10-16 用户令:三卡全部待裁项按推荐方案裁决〔用户将裁权授出,由主线会话代拍板留痕〕;v1.1 检索结论——六项当时均未被既有条款裁过、且关闭三处隐性重问(①「微光五组色相是否重冻」归 §0.6/徽章卡 Q2,②「1920 档/帧数规格/资产升档」随闸三裁决已闭,③「敌层插入位置要不要裁」已登记为样张义务)——保留为本次裁决的对表依据)。U0 剩余条件 = 用户 `/iter` 发令。Q3 已裁乙为主:半天试验件置于 T1 最前作前闸,试验件不过闸即按 §7 预设退路退甲案,**不再回炉请裁**(v1.1 原文「复核亦归用户裁」随退路预裁定关闭)。T0 事实坐实段(含 Q5 指代稿的实查拍板)凭本卡即可先行(零路线依赖)。**排期状态**:本卡已编入迭代 30(2026-10-16 用户令,[roadmap](../roadmap.md) 编入行已在案、状态刷新列反哺点);迭代 29 已于 2026-10-16 合入 master(fff4f120,roadmap.md:88 状态=✅ 已入库,见 §7.5 D4),「迭代 29 完成后开工」的前置已满足,但仍不构成排期承诺(见 §0)。
### 7.5 跨卡依赖(v1.1 新增——三卡次序耦合;v1.2 改实:三卡已编入迭代 30,串行序位已随 2026-10-16 用户令裁定=徽章→样张→观感,禁各自假设对方状态)
| # | 耦合点 | 谁的前提 | 次序与理由 |
|---|---|---|---|
| D1 | **类型微光色值与明度档的冻结口径** | 徽章卡(迭代 30 卡①,已入库合并 7ffd1dc0,卡片已退役)Q2 与其 S6 行 | **已改实(2026-10-16 用户令:三卡全部待裁项按推荐方案裁决〔用户将裁权授出,由主线会话代拍板留痕〕——徽章卡 Q2 已裁甲案:明度档取 provisional 值)**:「徽章卡 Q2 先裁」义务随其裁定关闭;**其复核转正义务落本卡样张冻结件**——本卡 T2 新增 H5 组间明度档复核+组间互辨抽测、T3 呈转正结论、写入完成定义第 2 条交付义务(见 §1/§3)。事实核对保留为对表依据:五组**色相**确已随样张 v2 定稿(art-bible §4.2 L218「样张 v2 定稿(2026-09-28)」+HUD框架:140 同记——「样张 v2 已冻结」与 08「战斗一屏样张=待做」并不矛盾:前者是**格子样张**序列(§1.4 L66/L68),后者是**整屏样张**序列(§1.4 L64),两条序列各有其闸),本卡不承接色相重冻义务;「徽章卡若裁乙(等本卡总闸)」分支不再发生。**本卡禁把色相值列进 Q 项**(已按契约 6 关闭) |
| D2 | **棋盘瓦片重产 vs 换色**(素材线同一入口) | [二三关棋盘观感卡](需求卡片-二三关棋盘观感批-2026-10-16.md) Q2(甲 recolor 换色 / 乙 远程生图重产) | **已改实(2026-10-16 用户令)**:序位裁定=徽章→样张→观感、本卡位第 2——观感卡排本卡之后,「同窗呈报两案代价」义务关闭;该卡 L2/L3 落地对本卡整屏②明度三档带与 A6 观感密度读数的复核,属该卡开工时按其 V2(A6 ≥4.70 保持)自跑的复验项,本卡冻结记录注明本样张基线读数供其对表即可;本卡 Q1 已裁丙案(非乙案),与其 Q2 各案不再有工期串行耦合 |
| D3 | **触碰面重叠与合入次序** | 三张卡共同 | 三卡都写 06/08/07/art-bible 的 git 提交史/roadmap/active.md,徽章卡另动 `presentation/views`+冒烟,本卡另动 `tools/` 样张面。按 roadmap.md:93「单迭代串行」与 roadmap.md:101「一迭代 = 一次合并登记」⇒ 三卡禁止三线并行。**已改实(2026-10-16 用户令):三卡串行序位=徽章→样张→观感,本卡位第 2**;「一次合并、批内分波」组织方式随序位裁定确定,v1.1「不预设结论、归用户排期令」呈报框架关闭。**v1.3 注**:三卡文档直写义务已按文档同步闸 v3 全部转反哺点(落 `reports/`),汇聚文件重叠消解,但串行序位裁定不失效——golden 基线与冻结内容的先后依赖仍在(D1/D2) |
| D4 | **门禁语义改动的先后** | 迭代 29 测试分层与门禁语义修正批 | **已结案(2026-10-16 合入 master,合并提交 fff4f120;roadmap.md:88 状态=✅ 已入库)**:该批改的 `tools/run_all_gates.sh`/探针判定/退出码权威语义(其契约 2「三硬闸不可绕过」)已在现行闸内生效,本卡 V7 的闸口径直接按当期语义执行(本批 → `--code`)并按其当期读数采基线;原「该批先行/本卡先行」两种分支随之消解,本卡仍不改写任何判定脚本。(原两分支表述留档供沿革查 git log) |
| D5 | **08 表现表同一表的多行改写** | 三卡+08 v1.6 定稿纪律 | 08 头注 L5 明文「Alpha 内容范围冻结,增删条目须经用户裁决」——三卡都只有**改状态列**的权限,**无一行可新增/删除**;本卡 T5 回写仅动「战斗一屏样张」一行,若需要为新条目动集合,一律 L3 呈报。**v1.3 注**:该「回写」执行为反哺点条目(仅动该行的改写要求照录进清单),零直改 |
---
## 8. 关联文档同步面
> **v1.3 执行口径(2026-10-17 文档同步闸 v3)**:本批**禁写 `design/` 整目录与根规则文件**,唯一可写目录 = `reports/`(`active.md` 唯一例外)。下表「同步义务」列一律执行为「列进 `reports/iter30-反哺点-<完成日>.md`:哪个文件哪一节、写成什么、依据,用户会话裁决后归文档批落文」;成文内容本体(冻结记录段全文、目标规格清单、处置表等)随对应反哺点条目交付。
| 文档 | 关联点 | 同步义务(v1.3 全部=反哺点列清单,另注者除外) |
|---|---|---|
| [art-bible.md](../design/art/art-bible.md) §1.4 | 新增**整屏样张冻结记录段**(编号口径见 T5 代拍注记——格子序列已用 v2/v3、关卡屏自成一列,禁两处各叫各的);段内随附**目标规格清单(Q1 已裁丙案:尺寸档/色板/明度档逐件成文)+徽章明度档复核转正记录(H5/§7.5 D1)**两件交付物;§7.6(三) 氛围层并存条改写为已裁决口径(经 T3 实拍图记录,Q3 乙案);§0.5 H1–H11 逐项处置写进该段;死指涉三处按 Q5 甲案改指「指代稿」(T0) | 反哺点 · 仅 T3 过闸后成文(契约 5) |
| [art-status-checklist.md](../design/art/art-status-checklist.md) | 战斗一屏样张行 ⬜→✅;:14 指针旧锚 §5.1 订正;余屏行按 Q2 刷新 | 反哺点 · 过闸后列清单 |
| [06-待办清单.md](../design/gdd/06-待办清单.md) | 战斗一屏样张条(:37-43)注销(去向登记当批状态记录)+:41 归属栏旧锚订正+:39「尺子还没造出来」措辞按 P1 读数订正;**关卡/菜单/营地三屏新立排队行(Q2 已裁甲案之本批交付义务)**;P0① 死指涉条(:609-615)按 **Q5 已裁甲案**处置(改指指代稿并注)+该行「68/527」行号读数订正 | 反哺点 · 列清单待裁 |
| [08-Alpha版本标准.md](../design/gdd/08-Alpha版本标准.md) | 「五 · 表现」战斗一屏样张行(:158)待做→在役(过闸后);完成定义边界条款注记(样张过闸 ≠ 占位素材全体转正)。**禁动项**:头注 :5 已定稿、增删条目须用户裁;「七 · 未决项」:208-210 三行(站桩帧动画/素材升档/立绘回退)本批一律不改 | 反哺点 · 过闸后列清单 |
| [战斗表现.md](../design/gdd/systems/战斗循环/战斗表现.md) | 敌层插入位置裁决记录登记(Q6 附带,:75/:93 既有义务);「一屏样张的验收标准」节(:79-93)状态刷新;**:83/:116「四区」旧措辞订正为三区 B/C/D** | 反哺点 · 随裁决列清单 |
| [character-design.md](../design/art/character-design.md) | **Q1 已裁丙案**:样张反提的立绘/小人规格(目标规格清单)回写其档位节;H4(§5.3 护甲三档视觉区分)与 H9(钩爪恐魔宽≥高豁免)处置登记 | 反哺点 · 随冻结记录列清单 |
| [HUD框架.md](../design/gdd/systems/表现与支撑/HUD框架.md) | **D1 已改实(徽章卡 Q2 裁甲案)**:本卡承接微光组间明度档复核转正/互辨抽测(H5)义务,按其「唯一事实源」纪律回报结论,定义与口径零复述 | 反哺点 · 义务已成立,结论列清单 |
| `tools/shot_checker.py`/`tools/render_sample_sheet_v3.py`/`game/smoke/shot_probe.gd` | 取样点位与断言扩面(T2);**闸件集合本身不改**(增减只走 `tools/run_all_gates.sh`);样张脚本仍不入常设闸表 | 工程面 · 批内更新(可写) |
| [roadmap.md](../roadmap.md) | 迭代 30 行编入笔已发(2026-10-16 用户令,行已在案);批内状态刷新 | 反哺点 · 列清单待裁 |
| active.md | 状态快照与批次记录 | **本批更新(唯一例外 · 会话状态源)** |
| [07-体验验证清单.md](../design/gdd/07-体验验证清单.md) | T3 目检轮次与后续实机目检衔接项登记(**现行 07 无样张类条目,实查 2026-10-16 零命中——本批为首次登记,属新增条目须走 07 自身纪律**) | 反哺点 · 列清单待裁(目检项另进 reports/ 实现报告目检总清单) |
---
## 9. grilling 审查记录(v1.1,2026-10-16,对抗审查员;逐攻击面给结论/证据/处置)
**审查方法**:卡片每一处引用逐一开文件核行号与措辞;Q1–Q6 逐条全库检索既有条款与裁决(design/gdd/06、08 及其 git 提交史、design/art/art-bible.md、character-design.md、art-status-checklist.md、systems/战斗循环/战斗表现.md、systems/表现与支撑/HUD框架.md、ux/battle.md、playnote.md(仓库根)、roadmap.md、active 侧状态记录残留、`tools/` 与 `game/` 实码、git 只读历史);与迭代 30 另两张并行卡逐 Q 比对互斥面。**只编辑本卡,未动任何其他文件;git 仅只读查询。**
### G1 事实引用核实(攻击面 1)
| 引用 | 核实结论 | 证据 | v1.1 处置 |
|---|---|---|---|
| art-bible §1.4 整屏样张闸 | **真** | §1.4 L64 全文(四屏/四项/闸形方向稿先行/执行时机=屏级视觉落码开工前) | 保留并补 L64 行号 |
| art-bible §7.1 占位完成条款 | **真** | §7.1 L411 | 完成定义 3 原写「08 §7.1 指路」**错**(08 无 §7.1),已改指 art-bible §7.1 L411 + 08:158 行 |
| art-bible §7.4 字重三件口径 | **真** | §7.4 L434(字号档差 ≥1.25×+墨色深浅+字重两档)、L432 六档表 | 保留,补 L434;并新增连带读数:「加粗档」在单字重下不存在,直接决定整屏①项可判面(Q4) |
| v3 脚本 A1–A6 | **半真** | 头注 L21-26 确有 A1–A6;**A2 拼缝周界闭合被 v1.0 漏列**;A1 实义=色板对表冻结值+禁入带(L928-930),**不是 48 色计数**;A4 含尺寸基线硬等(L1029-1031);L23「Δ≥55」与 L1157「std ≥50.9」是旧文本(现行定值 A3b Δ≥30,L35 与圣经 L68) | V3 与 T2 逐名义重写,48 色单列为新建项 |
| 「v1 四否决原因」 | **v1.0 只列两条编号,漏 ②④** | 脚本 L1156-1160 映射表:①显示太丑→A6;②封印色冲突→封印黑(A1a);③立体感→A3/F6/F7;④微光格只留微光→F5c;头注 L12-26 仅标 ①③ | P6 补全四条+标注权威表位置;V5 底表指到 L1156-1160 |
| 08 当前行状态 | **真** | 08:158「战斗一屏样张=待做」;:156 UI 贴图重构批待做;:157 UI 字体补完待做;:146 立绘清晰度回退待裁;:154 素材升档待裁;§7 未决项 :208-210 | 保留;「站桩帧动画」原被本卡写作「Beta/后批事项」**定性失实**(实为 08 未决项第 5 行待裁),已订正并入「不做」禁代裁清单 |
| 看板 13 行/§5.1 旧锚 | **真,但 v1.0 漏了 06:41 同型旧锚** | art-status-checklist:14;06:41 | P3 补记;T0 订正面扩为两处 |
| 死指涉行号 | **v1.0 沿用 06:611 的过期读数** | 06:611 写「68/527」;art-bible grep 实测指涉在 **L68 与 L562**,L527 现为 §9.1 参考表 | P4/Q5 改为 68/562,并把 06:611 自身列入 T0 订正面 |
| 2026-10-06 playnote 引文 | **原文被压缩改写** | playnote.md:9 逐字:「现在的立绘太丑了,像素化的粒度太低了,导致根本看不清立绘;看是否可以改回用较为高清的图片?」;日期出处=06:465-471「同日 playnote 立绘批评」 | P7 改逐字引+注明文件本身无日期 |
### G2 「v2 已冻结/样张批冻结候选值/08 待做」三者是否矛盾(攻击面 2)
**结论:不矛盾,v1.0 与并行徽章卡都需把两条序列分开。**
- 格子样张序列:v2 冻结(2026-09-28,圣经 §1.4 L66,四冻结含②色板)→ v3.0 解冻①④ → **v3 重冻生效(2026-09-30,L68,机检 17/17)**;类型微光五组**色相**属②色板面,§4.2 L218 明文「样张 v2 定稿(2026-09-28)」,HUD框架:140 同记「五组色相经样张 v2 冻结」——**冻结为真实现状**。
- 整屏样张序列:条款 2026-09-29 v3.0 才立(L64),**从未出过一张整屏样张判定物,四屏冻结记录零条**——08:158「待做」与看板 ⬜ 因此为真。
- 「候选值,样张批冻结」的出处是 06:431 里 **2026-09-22 那次更新的历史文本**,滞后于 v2/v3 两次冻结;06:431 同时**明文保留**「样张批组间互辨抽测义务」,且**组间明度档值从未定义**——这才是真挂在样张批上的东西。
- **v1.1 处置**:①新增 §0.6 关闭「重裁色相」的可能;②新增 §0.5 H5 承接互辨抽测与明度档;③新增 §7.5 D1 写明徽章卡 Q2 与本卡的先后与理由。
- **另两处状态读数订正(本攻击面的真发现)**:(a) 整屏四项的**机检代码已存在并跑过实机渲染帧**——shot_checker.py 头注自述「整屏样张闸机检(迭代 9 卡⑤/T8 2026-09-30)」,git 053baeee 记「实测 PASS(WCAG 5.79-13.14/三档 204>79>55/骨架四屏过)」,现挂 render_gate.sh ①段周期跑;**③焦点预算 E 段明文「无法机检→人闸」**。v1.0 的「四项从未作为断言集执行」「只存在于条款文字、无对应代码」「本批=该闸首次执行」三句按此改写为「判定物与冻结记录从未出、③从未判」。(b) 新增 P11:§1.4 的执行时机(屏级落码开工前)已被迭代 9/16/25/26 四轮落码越过、Alpha 已于 2026-10-03 到手,§7.1「占位禁止跨里程碑」事实上已被跨——本批定性=**闸后补跑**,冻结语义随之注明,禁写成事前定方向。(c) 新增 P10:关卡屏已有立闸前的 v1 冻结样张(2026-09-28,11/11,产物在盘),Q2/T4/估时三处按「补判四项」而非从零改写。
### G3 「全仓 shader 仅一枚先例」(攻击面 3)
**结论:真。** `find -name *.gdshader` 全仓命中 1 件(`game/presentation/views/desaturation.gdshader`);`ShaderMaterial` 消费面仅 `board_view.gd`(:111 声明/:170-172 创建/:194 参数 tween),`.tscn` 内无 shader 节点;样张与机检面全为 PIL 代码绘制。**处置**:P9 补实测细节;Q3 乙案的「能力边界未验证」措辞保留,另补 shot_probe 出的是「现状图」不是「目标图」这一真实成本(防把「实拍管线已有」误读为「样张管线已有」)。
### G4 Q1 三案依赖链与「混合粒度」(攻击面 4)
- 「UI 贴图重构批」= 06:21-27 待办(Alpha 需完成)+08:156 待做 → **v1.0 读数真**。
- 「资产升档重产批」= **不是未裁**:06:465-471 载明已裁案乙(2026-09-30 迭代 9 三裁决)+2026-10-04 令暂不做(重启=用户点名)+2026-10-06 优先级再降,且 v3.0「档位上调方向候选」已被 v3.7 八敌逐键定档实质完成;08:154/§7 第 6 行的「待裁」只问**进不进 Alpha**。v1.0 把它与「立绘清晰度回退评估」并列为「两个未裁闸口」**不准确** → Q1 乙案改写为「一已裁挂起+一真未裁」,并补 A4 尺寸基线须显式解冻重校的连带硬账(圣经 L68)。
- 「立绘清晰度回退评估」= 08:146 待裁+§7 第 7 行真未裁 → **v1.0 读数真**。
- 「三案可混合」的粒度:全库无既裁边界(样张序列只做整屏闸,从未有文档规定四屏可用不同素材级别),**故保留为请裁项**,不属再问已裁。
- **处置**:Q1 文字改写;新增契约 6/7;「不做」新增禁代裁 08 未决项三条。
### G5 估时 4–7d 的构成依据(攻击面 5)
**结论:v1.0 无出处且加总错误。** T4 自述「逐屏 1.5–2d × 三屏」= 4.5–6d,却把 T4 上限定为 3d;六卡相加实为下限 4d/上限 11d。**处置**:T4 上限改 0–6d,合计改「≈4–11d(单跑战斗屏 4–5d,四屏全推超盒拆两批)」,并补先例出处:迭代 9 T8 整屏机检建造=3–4h(`.zcode/plans/m3-iter9-loop-plan-2026-09-30.md:65`,故 T2 走复用不重建)、迭代 9 批 A T1 样张机检冻结=2–3h(同计划书:58)、迭代 13 4.8–5.3d/迭代 16 6.9d/迭代 26 2–2.5d/迭代 29 5.5–7d(roadmap.md:63/:68/:85/:88),周期上限口径=「迭代周期 ≈ 一周左右」(2026-10-11 用户裁,roadmap.md:101)。
### G6 与另两张并行卡的互斥面(攻击面 6)
**结论:存在三处「一张卡裁了另一张卡前提」的次序耦合,v1.0 完全未写。** 已新增 §7.5 D1–D5:D1 徽章卡 Q2(色值冻结口径)须先于本卡(它决定本卡是否继承色相重冻义务——核查结论:不继承,但继承 H5 明度档与互辨抽测);D2 棋盘观感卡 Q2(重产 vs 换色)与本卡 Q1 指同一条素材线、且其落地会使本卡冻结的明度带/密度读数作废(**双向作废关系,须同窗呈报**);D3 三卡共写 06/08/07/art-bible/roadmap/active ⇒ 单迭代串行纪律下禁三线并行;D4 迭代 29 改的正是本卡 V7 依赖的门禁语义;D5 08 定稿纪律下三卡都只有改状态列权限、无增删条目权限。
### G7 验收 V 条可执行性(攻击面 7)
逐条对 `tools/` 实存脚本核:**V1 无工具**(方向稿留痕不可机检)→ 从「机检化」降级为件核;**V2 ①②④ = shot_checker B/C/D(+F)**,但 B 段只判 meta 登记的已知文字框、C 段只有战斗屏三点 → 标「须先扩取样点位」,**③焦点 = 人闸**(E 段明文);**V3 = 样张脚本机检段复用扩展+48 色新建**(全仓无 48 色工具,grep 零命中);**V4 五项中第 1 项可借既有断言、第 2 项须新建、第 3/4/5 项目检**;**V7 原写「跑 doc 三闸+熵增对基线」违反 AGENTS.md:19/59「run_all_gates.sh 是唯一闸入口与唯一闸清单,文档禁另列清单」** → 改为指路该脚本,并声明本批零闸件增删。附带核实:`game/smoke/shot_probe.gd`、`tools/golden_compare.py`、`tools/golden_baseline_refresh.sh`、`tools/repo_structure_audit.py`、`tools/doc_link_audit.py` 均实存(07 无样张条目为实查所得,已注明本批首登)。
### G8 红线自检:卡片有没有替用户拍板(攻击面 8)
- **发现两处预设**:①目标句把尺子写成「§1.4 + 战斗表现五项的**并集**断言集」,完成定义第 1 条同样预设并集——而合体方式正是 Q6 未裁项 → 改为「两套口径的合体方式归 Q6 裁决(本卡不预设并集或分闸)」。②完成定义第 2 条写死「新增 **v4** 冻结记录段」——v4 这一编号预设了「整屏续用格子序列编号」,而关卡屏已自成一列(「样张 v1」)→ 改为「编号口径属批内代拍,在冻结记录一次说清」。
- **一处定性失实而非拍板**:「站桩帧动画」被写成 Beta/后批事项(实为 08 待裁)→ 已改,并列入「不做」的禁代裁清单。
- **审过判为保留(不属代拍)**:防线三「本批不申请验收豁免」= 本批自我从严的执行纪律,非改动用户裁决;Q2「战斗屏第一优先不复议」= 引用既成文依据(06:37-43/战斗表现:73);§0.5 各 H 项与 §7.5 只呈代价与次序、不给推荐案;Q1–Q6 全条无推荐语(历批「推荐案被推翻」纪律维持)。
- 本批 Q 项清单净变动(v1.1 记录;**六项已全部经 2026-10-16 用户令裁定,见 §7,本节当时呈报的六项全保留口径现为裁决对表依据**):**Q1–Q6 六项全保留**(无一被既有条款裁过,逐条检索见 §0.6 与本节 G2/G4),另关闭本卡内三处隐性重问(色相重冻/随闸三裁决/敌层义务存废),依据已写入开工门。
- 词面规范扫描:v1.1 全文按本次审查指定的十项废止词逐一扫描,初稿命中五处(否定副词一项四处、完成动词一项一处)已全部换代,复扫零命中。
### G9 审查中判定为「本卡无权处置、已登记呈报」的文档面
`tools/render_sample_sheet_v3.py` 头注 L23/L1157 旧口径残留文本;06:39「尺子还没造出来」与 shot_checker 现状有差;06:41/checklist:14 的 §5.1 旧锚;06:611 行号读数(68/527 应为 68/562);**art-bible §8.2 L490 指路 m3-iter9-t8-rulings 文件已在 8e02dcf0 删除(死指涉第二例,属 Q5 同族,处置口径与 Q5 同案在本批一并呈报)**;战斗表现:83/:116「四区」旧措辞;art-bible 头注 L3 仍写「现行为 v3.11」而当时文末变更表已到 v3.14。**以上均为他文件文本,本审查一律未改**,只作为本批 T0 订正面对表登记;其中头注版本号与他文档措辞是否订正,归各文档自身纪律与用户裁决。
> **v1.3 口径换代注(2026-10-17 文档同步闸 v3)**:本节为 v1.1 时点的审查实录,原文不重写;其中「doc 三闸」一类闸件枚举字样与「写入文档/登记 06」类落文表述,按 v3 口径只读为历史记录——闸清单与域划分的唯一出处恒为 `tools/run_all_gates.sh`(本批按改动域跑 `--code` 档),文档落文一律列进 `reports/iter30-反哺点-<完成日>.md` 归文档批。
@@ -0,0 +1,229 @@
# 需求卡片:战斗一屏样张批(2026-10-28)
> **来源**:待办清单条目「战斗一屏样张 P0 未开工」(设计面=美术轨;状态=美术设计已闭合+待写卡)
> **唯一事实源**:`design/art/art-bible.md` **v3.22** + `design/art/art-prompts.md` §5「资产出图工单(四组九件)」
> **规则指针**:`AGENTS.md` 硬性红线(含 2026-10-28 新立的「禁变异类测试与撤销类取证」验证口径)/`design/engineering/process-conventions.md` §1–§3/`code-conventions.md`/`test-conventions.md` §8/`doc-conventions.md` §12
> **旧卡说明**:`reports/需求卡片-战斗一屏样张批-2026-10-16.md`(v1.4)于 2026-10-17 被裁定不满足开发条件、终止开工,属非有效卡,本卡不继承其任何裁决;其考证过的代码现状与断言设计可作参考。
---
## 1. 目标与完成定义
**做什么**:把已经定稿的战斗一屏美术规格,变成一张可判定的样张和一批可运行的实装。
1. 用 PIL 代码绘制产出**战斗一屏样张**(1280×800 基座 + 1920×1080 全局等比 1.35× 两版),内容覆盖 `art-bible` §1.4 整屏内容清单与战斗表现内容清单。
2. 把 §1.4 整屏四项闸与战斗表现五项标准,写成**可在样张脚本内执行的机检断言**。
3. 把五项设计定值**落进实装代码**(血条换色、名字行垫素底、顺位徽标提档、护甲图标换件、站桩文字不压地台)。
4. 程序化产出两件素材并入包(地台件、护甲图标三枚)。
**完成定义(全部满足才算完)**:
- 样张两版产出,方向稿目检通过(用户判定),目检通过后机检冻结;
- 整屏四项断言 + 战斗表现五项断言在样张脚本内全绿;
- 五项代码定值落码完成,对应断言在冒烟面内通过;
- 地台件与护甲三枚 PNG 入包并被实装引用;
- 三份批件写齐(实现报告/代拍板追认单/反哺点清单)。
---
## 2. 设计事实源(必填)
**样张过审 + art-bible 条款版本**:
| 依据 | 版本/节 | 本卡取用 |
|---|---|---|
| `design/art/art-bible.md` | **v3.22** | §1.4 整屏样张闸四项;§2 明度三档带与世界底层两子带;§4.1 色板(余量 0,第二道门=不得三通道全 <8);§4.2 血条=危险红 `#A8321F`;§6.1 背景契约与地台独立件;§7.2 文字可读性;§7.4 字号六档与豁免清单;§8.2 尺寸档与甲/乙类缩放;§8.4 方向集 |
| `design/art/art-prompts.md` | §5 资产出图工单(四组九件) | 本卡素材需求的直接来源:工单七字段(路径命名/尺寸档/色板约束/明度归属/程序化与否/验收判据/出图要点) |
| `design/gdd/ux/battle.md` | 现行 | **屏级分区布局与格边长公式的唯一裁决源**——本卡涉及分区尺寸只指路,不复述数值 |
**已定死、本卡不再讨论的定值**:站桩素底=深档 `#282220`;五处换色合并(铲刃→`#C4C4C4`、提灯→`#B07A14`、食人魔皮→`#B8A582`、石君王灰皮→`#786E62`、轻甲木色→`#CDB88C`);新增两枚(`#868C9A` 重甲冷灰、`#A79CB8` 卓尔浅紫灰皮);48 色余量已归零;渲染手段=PIL 代码绘制;素材输入级别=丙(样张即设计目标图)。
---
## 3. 现状事实(代码级实查,每条带 file:line)
1. **样张脚本现役**:`tools/render_sample_sheet_v3.py`(1174 行)已有断言框架——`CHECKS` 列表定义于 `:113`,断言登记于 `:117`,全绿判定于 `:1033`,报告输出于 `:1164`。另存 `tools/render_sample_sheet.py`(1009 行)为旧版。本批在 v3 脚本内扩展。
2. **血条填充是裸字面量**:`game/presentation/views/standee_body.gd:444` 画填充用 `Color(0.41, 0.61, 0.91)`,即 `#689CE8`(蓝族,v3.22 §4.2 已登记为违例退役值),**未经 UiKit 常量**。
3. **血条条底同为裸字面量**:`standee_body.gd:441` 用 `Color(0.10, 0.10, 0.11)`(引擎默认主题深底近似)。
4. **素底牌已在役**:`game/presentation/views/ui_kit.gd:193` `HP_BAR_BACK := Color8(40, 34, 32)`(即 `#282220`),血数垫牌绘制于 `standee_body.gd:451`。**深档素底色无需新增,直接复用本常量。**
5. **顺位徽标违规**:`standee_body.gd:437-438` 用裸值 `13` 绘制①②③(`UiKit.GOLD_BRIGHT`),既不在 §7.4 ①条带内读数在册面、也不在②装饰 caption 在册面,属「清单外 11–13px 文字」,v3.22 已裁定提档 `FS_HINT` 14。
6. **护甲图标是字形占位**:`standee_body.gd:8-10` `const ARMOR_ICONS := {"light": "◇", "ward": "◆", "heavy": "▣"}`;绘制于 `:485` 与 `:496`(`:496` 用 `12` 绘制护甲字形,与名字同排)。
7. **名字行与地台重叠(实装债)**:名字行基线 `standee_body.gd:495` = `Vector2(..., box.y - 1.0 - STANDEE_FOOT_MARGIN)`,而 `STANDEE_FOOT_MARGIN := 12.0` 定义于 `:13` → **基线 y = box.y - 13**。地台矩形 `standee_body.gd:405` 返回 `Rect2(18.0, box.y - 18.0, box.x - 36.0, 12.0)`,纵向覆盖 **box.y-18 .. box.y-6**。名字行基线落在地台带内,**重叠确认**(非传闻)。
8. **B 区高度文码不符**:`game/presentation/views/battle_stage_view.gd:28` `custom_minimum_size = Vector2(0, 252)`(代码 252),而 `art-bible` §8.2 站桩立绘排版方案二①定 B 区高 **270**。二者差 18px。
9. **血数 11px 属豁免**:`standee_body.gd:453-454` 以 `11` 绘制血数并垫 `HP_BAR_BACK` 牌,合 §7.4 豁免①(14.3:1),**不在本批改动范围**。
10. **素材在盘但不入 git、且是旧档**:立绘与瓦片位于主检出 `game/assets/art/`(`character/` 26 件含八敌与主角 `character_hero_idle`/`attack`/`hit`/`portrait`;`board/` 瓦片),**整个 `game/assets/art/` 不入 git**(`git ls-files game/assets/art` 计数 0),fresh worktree 内缺位。取用靠 `tools/render_sample_sheet_v3.py:50-56` 的 `asset_root()` 两级回退(worktree → 主检出),脚本可直接取到,**样张可画**。
11. **立绘为旧档占位、且未接线进实装**:`character_hero_idle.png` 仅 1220 字节(2026-09-25),属旧档占位,非精细手绘终稿(重产工单在 `art-prompts` §5.4,属后续素材批);同时 `standee_body.gd:20-21` 立绘挂点为 `null`、`set_portrait_texture`(`:208`)未被调用,实装站桩仍是**字形回退**态。本批不修接线。
---
## 4. 设计契约(违反=返工红线)
**(1)🔴 实现批禁写 `design/` 与根规则文件**:本批唯一可写目录=`reports/`;`art-bible`、`art-prompts`、`battle.md` 等一律只读。所有「文档该改什么」进反哺点清单,由用户裁决后归文档批落文。
**(2)🔴 样张必过 §1.4 整屏四项闸**:文字对比 ≥4.5:1/明度三档带成立(含世界底层两子带)/首焦唯一/文字不压纹理底。
**(3)🔴 方向稿目检先行**:目检通过后才做机检冻结;机检过了但气质被否决的,以目检为准返工。
**(4)🔴 禁删断言、禁放宽阈值换绿**:断言红就修实现,不动判据。
**(5)🔴 禁变异类测试与撤销类取证**(2026-10-28 用户裁,前口径已废):不许写「注入缺陷看断言是否变红」的变异验证,不许写「撤销改动看断言是否变红」的撤销证明。证明方式=每个需求点与每个行为改动都要有**从真实入口进入的验收断言**,可 grep 定位、**在当轮真实跑批中执行并通过**,判据绑被测改动特有的可观测提交面。证明做不出来=**停卡呈报**。
**(6)🔴 数值单一事实源**:色值、字号一律收 `UiKit` 常量,禁在视图里写裸 `Color(...)` 与裸字号字面量(本批正是要清掉 `:441`、`:444`、`:437`、`:496` 这四处)。
**(7)🔴 48 色余量已为 0**:本批**不新增任何色值**,只使用 v3.22 在册色;需要色值处一律引 `UiKit` 现有常量。
**(8)🔴 RNG 单点**:禁 `randomize()`/自建 `RandomNumberGenerator`;样张脚本的确定性散布沿用现役写法(`standee_body.gd:275, 302`)。
**(9)🔴 Core 层零 UI 依赖**:本批只动呈现层与工具脚本,core 侧零触碰,完整战斗仍须可 `--headless --script` 跑通。
**(10)🔴 样张判定对象不含立绘精细度**:样张以现有旧档立绘出方向稿(见第 3 节第 10、11 条),判定对象=布局分区、明度三档带、文字对比、首焦唯一、构图关系;**立绘自身的清晰度与精细度不作本批判定对象**,也不得据本批样张给立绘下结论。立绘重产(`art-prompts` §5.4)落位并完成站桩接线后,须**重出样张复验**——本条进反哺点清单。
---
## 5. 范围边界
### 5.1 做
1. PIL 画一屏样张两版(1280×800 基座;1920×1080=全局等比 1.35× + 左右黑边),内容按 §1.4 整屏内容清单 + 战斗表现内容清单(含类型微光呈现组、图类型色板变体组至少两套、三组关键帧条、主角与敌方描边同屏对照)。
2. 整屏四项 + 战斗表现五项的机检断言进 `tools/render_sample_sheet_v3.py` 的 `CHECKS`。
3. 五项代码定值落码 + 断言:血条换 `#A8321F`(清 `#689CE8`)、站桩名字行垫 `HP_BAR_BACK` 素底且不压地台、顺位徽标提档 `FS_HINT`、护甲图标由字形换三枚 24×24 PNG、地台件独立引用。
4. 两件素材程序化产出并入包:地台件 `scene_stage_plaza.png`(源 48×12 九宫格)、护甲图标三枚 `ui_armor_light/ward/heavy.png`(源 24×24)。
5. 三份批件:实现报告/代拍板追认单/反哺点清单。
6. **改前/改后并排对拍图**:改动前先用同一脚本、同一参数存一版**基线样张**,改动后与基线并排成图,作为用户目检的对照材料。基线留存是真实前后对照,不是人为制造红灯,不违反「禁变异类测试与撤销类取证」红线——红线禁的是「故意改坏看断言是否变红」,本项是「改好之后与原样并排」。
### 5.2 不做(含去向)
| 不做的项 | 去向 |
|---|---|
| B 区舞台背景插画 `scene_stage_bg.png` 正式出图 | 样张内按远景/中景两带用既有色值程序化铺出**设计目标态**;正式出图工单仍在 `art-prompts` §5.1,由后续素材批执行 → 进反哺点清单 |
| 主角与三 Boss 立绘重产 | `art-prompts` §5.4 工单仍在,由后续素材批执行 → 进反哺点清单 |
| 三关色板终值 | 待「三关色相 × 五组微光互辨抽测」条目(待办清单已登记,状态=待美术设计) |
| 营地场景图 | 独立条目,与本批无关 |
| B 区高度 252→270 的实装对齐 | 本批**只登记不修**:样张按代码现状 252 出(样张须反映实机)→ 进反哺点清单 |
---
## 6. 关联文档同步面(必填)
| 文档 | 关联点(本批实现会改变它的哪条陈述) | 同步义务 |
|---|---|---|
| `design/art/art-bible.md`(v3.22) | §1.4 整屏四闸的**通过事实**与抽样读数;§4.1 站桩素底深档 `#282220` 的实装落地;§6.1/§7.2 站桩文字位机检断言 | 进 `reports/iter<N>-反哺点-<日期>.md`,由用户裁决后归文档批落文 |
| `design/art/art-prompts.md` §5 | 组二地台件、组三护甲三枚已「程序化产出即终稿」;组一背景、组四立绘仍待出图 | 同上 |
| `design/gdd/ux/battle.md` | B 区高度:文 270 vs 代码 252 的 18px 差异 | 同上 |
| `design/gdd/systems/战斗循环/战斗表现.md` | 「一屏样张的验收标准」五项过闸结果 | 同上 |
| `design/engineering/test-conventions.md` | §8.4 撤销证明条文与 2026-10-28 新验证口径相冲突(旧口径已废) | 同上 |
| `AGENTS.md` | 「验收闭合」红线句仍保留「撤销该改动能让对应断言变红」旧措辞,与新口径冲突 | 同上 |
---
## 7. 任务卡 T0..Tn
### 7.0 文件结构图
| 文件 | 职责 | 复用/新增 | 改动性质 | 验证闸行号 |
|---|---|---|---|---|
| `tools/render_sample_sheet_v3.py` | 样张绘制与机检断言宿主 | 复用既有(扩展出口与 `CHECKS`) | 行为改动 | V1–V9 |
| `game/presentation/views/standee_body.gd` | 站桩绘制:血条、素底、徽标、护甲、地台引用 | 复用既有(就地改) | 行为改动 | V5–V8 |
| `game/presentation/views/ui_kit.gd` | 色值与字号常量单点 | 复用既有(`HP_BAR_BACK`、`FS_HINT` 已在) | 非行为改动(如需补常量则标行为) | V5、V7 |
| `game/assets/art/scene/scene_stage_plaza.png` | 地台九宫格件 | 新增(程序化产出) | 非行为改动(新增素材) | V9 |
| `game/assets/ui/icons/ui_armor_{light,ward,heavy}.png` | 护甲三档图标 | 新增(程序化产出) | 行为改动(替换字形占位) | V8 |
| `game/tests/` 冒烟面 | 五项定值的验收断言落点 | 复用既有冒烟段 | 行为改动(新增断言) | V5–V8 |
| `reports/` 三份批件 | 实现报告/代拍板追认单/反哺点清单 | 新增 | 非行为改动 | — |
### 7.1 卡片与估时
| 卡 | 内容 | 估时(工作日) |
|---|---|---|
| T0 | 开工准备:worktree、`.godot` 与字体 `.import` 边车拷贝、确认 v3 脚本可跑 | 0.25 |
| T1 | 样张底板:两版画布、B/C/D 分区(数值按 `battle.md` 指路,B 区按代码 252)、格边长 | 0.5 |
| T2 | 内容清单铺画:类型微光组、色板变体组、关键帧条、描边同屏对照 | 1.5 |
| T3 | 整屏四项断言进 `CHECKS` | 1.0 |
| T4 | 战斗表现五项断言进 `CHECKS` | 1.0 |
| T5 | 血条填充换 `#A8321F`(经 `UiKit` 常量)、条底收常量 | 0.25 |
| T6 | 站桩名字行垫 `HP_BAR_BACK` 素底,且文字位不压地台(消 5px 重叠) | 0.5 |
| T7 | 顺位徽标提档 `UiKit.FS_HINT` | 0.25 |
| T8 | 护甲图标由字形换三枚 24×24 PNG | 0.5 |
| T9 | 地台件与护甲三枚程序化产出并入包 | 0.5 |
| T10 | 产出物落位卡:三份批件写齐 + 改前/改后并排对拍图产出 | 0.25 |
| | **合计** | **≈6.5(区间 5.5–7.5)** |
**卡间接口**:
- T1 **产出**:`draw_base(canvas_w, canvas_h, b_h) -> Image`(B 区高由参数传入,T2/T3 依赖)
- T3/T4 **取用**:T1 的 `Image` 与分区矩形;**产出**:`CHECKS` 内断言条目(供 T10 汇总)
- T5–T8 **取用**:`UiKit.HP_BAR_BACK`、`UiKit.FS_HINT`、`UiKit` 新增血条填充常量(由 T5 定义,T6 复用)
- T9 **取用**:T8 定义的护甲三档键 `{"light","ward","heavy"}`
**步骤要求**:一步=一个动作 + 一个可核查结果,`- [ ]` 记步。实装步骤给确切签名与 file:line,**实现体不写**。
---
## 8. 验证闸(改动—验收对照表)
| # | 需求点 | 改动(文件:行 / 行为) | 验收测试(名称 + 落点) | 层级 | 真实入口 | 判据绑定面 | 证明方式 |
|---|---|---|---|---|---|---|---|
| V1 | 样张两版产出 | `tools/render_sample_sheet_v3.py` 新增两版出口 | `sample_sheet_two_editions` 落冒烟面 | 探针 | `python tools/render_sample_sheet_v3.py` 真实跑批 | 两版位图文件尺寸(1280×800 / 1920×1080) | 产物两版尺寸分别为 1280×800 与 1920×1080,跑批通过 |
| V2 | 文字对比 ≥4.5:1 | 同上(断言进 `CHECKS`) | `splash_text_contrast` 落 `CHECKS` | 探针 | 样张脚本真实跑批 | 样张位图文字像素区域对比度读数 | 断言在跑批内取绿 |
| V3 | 明度三档带成立(含两子带) | 同上 | `splash_luma_three_bands` 落 `CHECKS` | 探针 | 同上 | 样张位图逐区明度带读数(世界底 / 中 / 顶层) | 断言在跑批内取绿 |
| V4 | 首焦唯一 | 同上 | `splash_single_focus` 落 `CHECKS` | 探针 | 同上 | 样张位图对比度最高点分布(唯一性) | 断言在跑批内取绿 |
| V5 | 血条=危险红 `#A8321F` | `standee_body.gd:444` 填充改引 `UiKit` 常量;`:441` 条底收常量 | `hp_bar_fill_is_danger_red` 落冒烟面 | 单元 | 冒烟套真实跑批 | 血条填充像素采样色值 | 断言在跑批内取绿;全仓 grep 零 `#689CE8` 与零裸 `Color(0.41, 0.61, 0.91)` |
| V6 | 站桩名字行垫素底、不压地台 | `standee_body.gd:495-497` 基线改为地台上方 + 垫 `HP_BAR_BACK` | `standee_name_not_on_plinth` 落冒烟面 | 单元 | 冒烟套真实跑批 | 名字行绘制基线 y 与地台顶 y 坐标 | 断言在跑批内取绿(名字行基线 y < 地台顶 y) |
| V7 | 顺位徽标提档 14 | `standee_body.gd:437-438` 裸 `13` → `UiKit.FS_HINT` | `order_badge_uses_fs_hint` 落冒烟面 | 单元 | 冒烟套真实跑批 | 顺位徽标字号取值(FontFile 大小) | 断言在跑批内取绿;grep 该处零裸字号 |
| V8 | 护甲图标换三枚 PNG | `standee_body.gd:8-10` 常量改路径;`:485`/`:496` 绘制改贴图 | `armor_icon_textures_bound` 落冒烟面 | 单元 | 冒烟套真实跑批 | 三档键 → 对应 PNG 资源引用(ResourceLoader 加载结果) | 断言在跑批内取绿;三档键各绑到对应 PNG |
| V9 | 地台件与护甲三枚入包 | 产出入 `game/assets/` 并被实装引用 | `plinth_and_armor_assets_loaded` 落冒烟面 | 接口 | 冒烟套真实跑批 | `game/assets` 下资源文件存在且被实装引用(尺寸 48×12 / 24×24) | 断言在跑批内取绿(资源可加载且尺寸 48×12 / 24×24) |
| V10 | 改前/改后对拍图产出 | T1 存基线样张;T10 生成并排图 | `sample_sheet_before_after` 落冒烟面 | 探针 | 样张脚本真实跑批 | 基线版与改后版两张源样张文件 + 并排图输出文件 | 断言在跑批内取绿:基线版与改后版两张源文件均存在、尺寸一致、并排图已生成 |
- **方向稿目检**(用户判定)在 V1–V4 之前执行,目检通过后才冻结机检。
- **独立验收审计**:入口 `reports/iter<N>-验收审计-<日期>.md`,由未参与实现的会话或子代理执行,未过不得合并。
- **用户目检项一律「登记不阻塞」**:登记进实现报告即完成卡内义务,不作为合并或下一迭代开工的前置。
- 重闸表述:批末按本批改动域选档跑 `tools/run_all_gates.sh`(本批含代码面,走代码域)。
---
## 9. 异常分级
- **L1 自救**:单条断言红且定位明确 → 一次重试修实现,不改动判据。
- **L2 记录继续**:同一红灯连续三次 → 记录进实现报告,继续推进其余卡,不停批。
- **L3 停机写报告等用户**:触碰第 4 节任何一条 🔴 红线;或样张四闸中任一闸无论如何都过不去(可能意味着规格本身有缺口);或需要新增色值(余量为 0,须呈报作者裁)。
---
## 10. 代拍板白名单
1. 样张脚本内的临时变量命名、辅助函数拆分方式。
2. 断言在 `CHECKS` 内的 id 命名与登记顺序。
3. 程序化产出素材时的绘制实现细节(颜色取自 v3.22 在册值、不新增色)。
4. 两版样张的输出文件名与落盘目录(落 `artwork/` 在途区)。
超此清单者一律呈报。
---
## 11. 待裁点
**零待裁点,开工门=U0。**
说明:B 区高度文 270 vs 代码 252 的差异已在第 5.2 节定死处置(本批按代码 252 出样张、只登记不修),不构成待裁点;样张所需色值全部已在 v3.22 在册,本批零新增色,故无额度类分叉。
---
## 12. 落位与可复用性预检
**复用面**:
- 样张绘制**复用既有** `tools/render_sample_sheet_v3.py`(1174 行,已有画布、分区、`CHECKS` 框架),不新建脚本——新脚本会与既有断言体系并行,形成两套事实源。
- 色值与字号**复用既有** `UiKit` 常量(`HP_BAR_BACK`、`FS_HINT`、血条填充新常量同址登记),禁在视图内写裸字面量。
- 站桩绘制**就地改** `standee_body.gd`,不新建站桩视图。
**唯一面**:
- 血条填充色只落在 `UiKit` 一处常量,`:444` 与 `:441` 两处绘制点均引它,禁各自写字面量。
- 护甲三档键 `{"light","ward","heavy"}` 只在 `standee_body.gd` 常量处定义一次,`:485` 与 `:496` 共用。
- 地台几何已在 `:405` 单一来源(注释明写「防两处漂移」),本批不另立第二处几何。
**分层与交互面**:
- 样张脚本(`tools/`)→ 只产出图与断言报告,不进游戏运行时;与呈现层的交互=读取 `UiKit` 常量值,只读不写。
- 呈现层(`standee_body.gd`)→ 经 `UiKit` 取色与字号,不直连数据表;数值只经 ConfigDB 单点(本批无玩法数值改动)。
- 新增素材(PNG)→ 落 `game/assets/` 被呈现层按路径加载,路径串只在常量处定义一次。
- 跨层红线不因新增而绕:Core 零 UI、随机只经 RngService(本批无随机改动)、数值只经 ConfigDB。
@@ -19,7 +19,7 @@
- `reports/设计-战斗编排类变量归约-2026-10-29.md`——设计讨论段定稿全文。逐成员去向(38 项五去向)、两新件形态与耦合边界、attune 重裁决、已否决切法、随迁键义务、行为零变化风险点、键账预期、改址盘点,全部以该文档对应节为唯一合同;本卡不复制其表格,T0 逐名对账时引用。
- `design/engineering/architecture/architecture.md` v1.25——「战斗编排域拆件定稿」节轴丁两件(RoundCycle/BattleFacts,跨件单向接线、段类随动、attune 改判、S13 键账收敛义务、验收红线)与「模块归属·功能层」两行、「测试与质量域」S13 豁免第三条注销句——实现照册施工。
- 五待裁点裁决(2026-10-29 用户裁,对应设计文档 §13):①encounter 归战斗事实件=采纳;②treasure_inventory 注入改址=追认(「外部赋值路径零改址」句失效);③attune 重裁决=采纳「状态归 facts、行为留宿主」;④完美账写口升级=采纳(grant_segment 三处直写改经 facts 三方法);⑤对拍闸已随 2026-10-30 工程批退役(用户裁:整局重放对拍不再作验收载体)——原 PARITY 批准待裁点就此注销。
- 五待裁点裁决(2026-10-29 用户裁,对应设计文档 §13):①encounter 归战斗事实件=采纳;②treasure_inventory 注入改址=追认(「外部赋值路径零改址」句失效);③attune 重裁决=采纳「状态归 facts、行为留宿主」;④完美账写口升级=采纳(grant_segment 三处直写改经 facts 三方法);⑤PARITY 对拍批准=批内实跑须用户点头、不重锚。
## 3. 现状事实(代码级,2026-10-29 实查;开工 T0 按当轮 master 重测复核)
@@ -33,7 +33,7 @@
- attune 外部三面(零改址标的):`attune_has` 公有面消费=table_structure_guard_test 起战断言(:162/:166)与 breakpoint_resume_test;`attune_param_sum`=main.gd:567 与 treasure_effects_test;`attune_convert_state`=treasure_interact_controller.gd:125。三面全部留宿主公有面。
- 冻结面:`nest_preview_count` 外部直读=main.gd:536-537+treasure_effects_test:153;`round_index` 公有直读=system_test.gd:447-462(st.round_index)等四套件约 15 处。
- **设计文档 §11 漏列一处(T0 实查补入)**:`settle_segments_test.gd:27` 接线断言 `oc._grant_seg != null and oc._grant_seg.oc == oc`——段件迁 RoundCycle 后必红,须改指 `oc._rounds._grant_seg`。
- 对拍闸(原闸 11 与 PARITY_TOUCH_FACES 触面判定)已随 2026-10-30 工程批退役——本批无触面批准事项;T0 复核以此为准。
- `tools/run_all_gates.sh` PARITY_TOUCH_FACES(:161 定义区)含 `game/core/battle_orchestrator.gd`——本批必触面。
- `game/tests/static_scan.sh`——S8 await 文件集(:317,9 文件)与 S7 B1 域文件集(:359,9 文件)均未含两新件路径;round_cycle.gd 经 oc 调 B2 符号(`_enemy_system` 族)须随批增行,battle_facts.gd 零协作调用不入清单。
## 4. 设计契约(违反即返工的红线条款)
@@ -43,7 +43,7 @@
- 🔴 **信号顺序契约冻结**:五信号(step_row/battle_finished/round_started/board_changed/settlement_snapshot)留宿主;发射语句零迁移、发射时序零变化;A2 三 connect(:251-254)逐字。
- 🔴 **两快照出口签名与载荷逐字不变**:presentation_snapshot/diagnostic_snapshot 字段名、嵌套结构、值口径逐字——内部读数改指两件投影后值恒等(round_index/attune_safe_seen/完美三账/hero_atk 派生读均受理后不变量)。
- 🔴 **明细行协议词表零触碰**:基础 9 类+扩展行词表、SOURCE_EXEMPT_TYPES/SOURCE_EXEMPT_CLASSES 两常量、`_emit_row`/`_assert_row_source` 行发射基建留宿主零迁移。
- 🔴 **行为零变化承载(对拍闸退役后)**:同种子逐位一致由关联套件的既有回放断言与两快照恒等断言承载;对拍闸(原闸 11)已随 2026-10-30 工程批退役,无闸级对拍批准事项。
- 🔴 **PARITY 触面**:battle_orchestrator.gd 触 PARITY_TOUCH_FACES——实跑对拍须经用户批准、不得借机重锚。
- 🔴 **拆分边界纪律**(判据权威=code-conventions §1.1,此处立卡内执行条款):①每个拆出单元须有可陈述的职责语句与单一变化原因(实现报告逐件一行承载);②拆出单元须有集体身份——命不出名字、只能说「剩下的成员」的打包=凑计数袋,禁;③「过阈值」只许作为发现信号,作为拆分或收拢的动机即违规;④按纪律拆不出内聚单元 → 停卡呈报走豁免/还债通道,禁硬拆凑绿;⑤拆分沿职责轴不沿阈值轴。两新件职责语句见第 8 节(RoundCycle=轮结算周期的推进结构;BattleFacts=战斗事实的记录与查询口径)。
- 🔴 **禁为机检文本保留无效成员**:22 成员随件迁出+treasure_inventory/_hero_atk 两成员退役=宿主删 24 项(37−22−2+RoundCycle/BattleFacts 两新件接线引用=宿主 15,账平——grilling 复核订正原算术),禁保留别名、别名转发属性或空壳;机检键与卡片契约互斥时,实现段一律停卡呈报,不得自造第三条路。
- 🔴 **随迁键收敛义务**:迁移函数逐字纪律——`roll_round_actions`(45 行/分支≈8 贴线)、`settle_if_due`、`reset_round`、三 mark 方法迁移与改指**禁顺手增分支/局部**;函数迁入新文件若出超标键=卡内同步收敛(拆助手函数,拆分不改语义;先例=迭代 40 卡① 12 随迁键收敛),禁带新键进批末闸。
@@ -157,7 +157,7 @@ aura/decay/verdict 三段、battle_fx_host、reward_grant、damage_controller、
| 行 | 需求点 | 改动(文件:行/行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 |
|---|---|---|---|---|---|---|---|
| V0 | 行为零变化(本批总判据) | 全部结构改动(第 7 节清单) | 关联套件零改址应绿(对拍闸已随 2026-10-30 工程批退役——总判据由 V1–V5 各行能力行并集承载);静态判据申报同上(键账收敛与 S7/S8 两清单增行) | 集成 | 真实局 begin_battle→挖掘→结算→终局 | 关联套件全绿且改址面零失败——出红且无改址归因即停机呈报 | 既有:轮内流与结算五段全序;既有:技能池数据面与回放独立;既有:伤害公式与掷点乘区;既有:秘宝结构冻结与 12 件效果 |
| V0 | 行为零变化(本批总判据) | 全部结构改动(第 7 节清单) | PARITY 同种子完整局对拍(`tools/parity_anchor.py`,用户批准后实跑、禁重锚);关联套件零改址应绿 | 集成+对拍 | 真实局 begin_battle→挖掘→结算→终局 | step_row/settlement_snapshot 逐位一致——差异非零即停机呈报 | 既有:回放确定性(同种子三变体逐位) |
| V1 | 两新件建件接线(T1/T2) | battle_facts.gd/round_cycle.gd 新建;宿主 _init 建件接线;五段构造迁 round_cycle | 新增 settle_segments_test.gd 接线检查段内嵌〔编排归约 V1〕:`oc._rounds` 非空且 `_rounds.oc == oc`;五段段件齐在 `_rounds`;`oc._facts` 非空 | 单元 | BattleOrchestrator 直构(settle_segments_test 现行夹具同款) | 编排器接线面(新件在座与宿主回指——接线断言本体) | 既有:轮内流与结算五段全序 |
| V2 | reset_round 复位边界(险点) | battle_facts.reset_round 只清三缓冲;宿主三调用点改调 | 新增 round_flow_skill_pool_test.gd:_smoke_full_battle 内嵌〔编排归约 V2〕:终局后 `presentation_snapshot()["refresh_used"]` == 末轮结算 snapshot `economy.refresh_used` | 单元 | begin_battle→轮内刷新→停手→结算→终局 | 两快照同键值恒等(复位不碰 refresh_used 的可观测提交面) | 既有:轮内流与结算五段全序;既有:停手转存储备与轮内刷新(上限/AP 不重置/刷新图零清图奖励) |
| V3 | 段驱动与摇号迁移(重入门闸/settle_if_due/roll_round_actions/mines_hits 账) | 三函数迁出宿主;宿主调用点改址;trigger_cond_test 3 处改指 | 复用 round_flow_skill_pool_test 全套(_smoke_full_battle/_smoke_settlement_segments/_smoke_skill_pool_replay_independence)、settle_segments_test V4-3 真实一轮、skill_unlock_test、trigger_cond_test(改指后应绿)——零新增断言 | 单元 | begin_battle;stop→WAITING→settle_if_due | 五段裁定行序+settling 闸+终局早退+同种子摇定逐位(behavior_roll 流随件零漂移由既有回放断言钉住)+mines_hits 转存读数 | 既有:轮内流与结算五段全序;既有:技能池数据面与回放独立;既有:Boss 半血解锁技门控 |
@@ -177,13 +177,13 @@ aura/decay/verdict 三段、battle_fx_host、reward_grant、damage_controller、
## 13. MC/数值与 RNG 零触碰声明
本批零数值改动、零数据表改动、零 RNG 流词表改动、零玩法字面量引入——结构整改不改数值与 RNG 消费序。behavior_roll 封闭流随件迁移取号序不变(流名、开流时点、取号序逐字)。数值与 RNG 零触碰则 MC 零跑;对拍闸已随 2026-10-30 工程批退役,无闸级对拍事项。
本批零数值改动、零数据表改动、零 RNG 流词表改动、零玩法字面量引入——结构整改不改数值与 RNG 消费序。behavior_roll 封闭流随件迁移取号序不变(流名、开流时点、取号序逐字)。数值与 RNG 零触碰则 MC 零跑;battle_orchestrator.gd 触 PARITY_TOUCH_FACES——实跑对拍须经用户批准、不得借机重锚(批末闸 11 判 NEED-APPROVAL 时由用户裁决:批准实跑或裁跳过,卡内不定案)。
## 14. 异常分级
- **L1 自救**:改指漏改编译错、测试寻址漏改——就地修复重跑最窄确认。
- **L2 记录继续**:实测行数/改指处数与设计文档「约」值区间偏离、头注指址微调——记入实现报告「实现决定记录」。
- **L3 停机写报告等用户**:①机检预算互斥——两新件实数超第 9 节预算、BattleFacts 需增员、或迁移函数现出超标键=上游缺陷停卡退回写卡段(禁自造第三条路);②encounter 归属被实现期证伪——受理事实与轮 facts 复位节奏冲突实锤、且设计文档 §6.3 两条砍路均破冻结面(宿主 16 不可达)=停卡呈报走判据④通道;③T0 对账不符(成员去向对不上、基线出现函数级键、file:line 漂移过大);④V0 行为零变化被证伪(关联套件出红且无改址归因);⑤断言面被下游兜底吸收致判据绑不上(按 test-conventions §8.4 出口呈报);⑥S13 键恶化或基线外新键。
- **L3 停机写报告等用户**:①机检预算互斥——两新件实数超第 9 节预算、BattleFacts 需增员、或迁移函数现出超标键=上游缺陷停卡退回写卡段(禁自造第三条路);②encounter 归属被实现期证伪——受理事实与轮 facts 复位节奏冲突实锤、且设计文档 §6.3 两条砍路均破冻结面(宿主 16 不可达)=停卡呈报走判据④通道;③T0 对账不符(成员去向对不上、基线出现函数级键、file:line 漂移过大);④V0 对拍差异非零(行为零变化被证伪);⑤PARITY 触面处置分歧;⑥断言面被下游兜底吸收致判据绑不上(按 test-conventions §8.4 出口呈报);⑦S13 键恶化或基线外新键。
## 15. 测试削弱申报(允许改动的旧断言及理由)
@@ -191,7 +191,7 @@ aura/decay/verdict 三段、battle_fx_host、reward_grant、damage_controller、
## 16. 独立验收审计
合并前由未参与实现的会话或子代理执行;报告落 `reports/iter<N>-验收审计-<日期>.md`。审计至少做四件事(test-conventions §8.7):逐行核对第 11 节对照表(测试存在、真执行、尾缀可 grep、真实入口未绕过改动点);抽查判据绑定面(V2 两快照同键、V4 攻击单基值、V5 引用同一性——当轮真实跑批执行过);检查测试削弱(对照第 15 节零申报);核对全部「非行为改动」判定依据(零漂移证明是否在当轮真实跑批中成立)。未过不得合并。
合并前由未参与实现的会话或子代理执行;报告落 `reports/iter<N>-验收审计-<日期>.md`。审计至少做四件事(test-conventions §8.7):逐行核对第 11 节对照表(测试存在、真执行、尾缀可 grep、真实入口未绕过改动点);抽查判据绑定面(V2 两快照同键、V4 攻击单基值、V5 引用同一性——当轮真实跑批执行过);检查测试削弱(对照第 15 节零申报);核对全部「非行为改动」判定依据(零漂移证明是否在当轮真实跑批中成立、PARITY 批准凭据在案)。未过不得合并。
## 17. 代拍板白名单(允许 agent 自行拍板的微决策)
@@ -1,36 +1,36 @@
# 需求卡片——测速循环批(tune_sim_controller 循环内 await 真实消除)
> **版本状态**:v1.1 定稿——零待裁点(2026-10-30 停卡重裁:实现段 T0 三重证据定谳 v1.0 的「潜伏红」前提已被迭代 41 测试债批消化〔215da315——运行态收尾归还 run〕,按停卡呈报建议案剥离已申报行为改动 V3、T0 义务改现状复核、夹具申报差异两处减一处;2026-10-29 原四点裁决中②作废,①③④仍有效——裁决记录见第 15 节)。
> **版本状态**:v1.0 定稿——零待裁点(2026-10-29 用户裁:信号步进状态机形态、收尾置假还原作为已申报行为改动、双实现对拍入批、错误路径差异接受并文档化)。
> **接收方**:待办清单条目「零豁免五键清偿批——S13 余量五键真修四卡(2026-10-29 登记,来源=会话提及)」——本卡为其四卡中的测速循环批卡。
> **来源**:reports/设计-测速循环去await-2026-10-29.md(2026-10-29 设计闭合;§2.3 潜伏红推演与 §10 待裁点 1 的「还原」申报已随停卡重裁作废,§3–§7 形态与等价性论证仍有效)。**唯一事实源**:`reports/设计-测速循环去await-2026-10-29.md` + `design/engineering/architecture/architecture.md` v1.25「测试与质量域」S13 测量面豁免注销句(2026-10-29 零豁免裁:原循环内 `await` 豁免〔2026-10-27〕注销——tune_sim_controller 改 process_frame 信号常驻连接+步进状态机,每帧一局、帧相位逐点对齐;注销对象是 await 豁免、不依赖行为改动申报,句保持;句内「随批申报行为改动一处」半句已失效,随实现批现状化时一并修正——见第 6 节)+ 停卡呈报三重证据(iter44 worktree 分支 `reports/iter44-实现报告-2026-10-30.md` §5——重裁依据)。
> **来源**:reports/设计-测速循环去await-2026-10-29.md(2026-10-29 设计闭合)。**唯一事实源**:`reports/设计-测速循环去await-2026-10-29.md` + `design/engineering/architecture/architecture.md` v1.25「测试与质量域」S13 测量面豁免注销句(2026-10-29 零豁免裁:原循环内 `await` 豁免〔2026-10-27〕注销——tune_sim_controller 改 process_frame 信号常驻连接+步进状态机,每帧一局、帧相位逐点对齐;随批申报行为改动一处:运行态收尾由「每局置假」还原为「整批收尾单次置假」,消除与 wd8/ui_tune_probe 断言面的静态矛盾)。
## 1. 目标与完成定义
这批做什么:把 S13 静态质量闸(gdstyle)在册裸键 `game/presentation/tune_sim_controller.gd|quality/await-in-loop` 真实消除——`run(n)` 的 for 循环内 `await process_frame` 改为 process_frame 信号常驻连接+步进状态机(每帧回调执行一局、步尽次帧收尾断连),`run()` 只留函数级一次 `await _batch_finished` 保持协程身份;改后全文件零 for/while,该键从文本形态根除、此文件对这个键结构性免疫。随批执行一项已裁事项:新增双实现对拍套件,把帧相位等价论证变成当轮真实执行证据。**本批零行为改动**——收尾置假已在 run() 尾每批一次执行(迭代 41 测试债批 215da315 归还后的现行形态),本批收尾四语句同帧同相位迁入 `_finish_batch()`,语句集与语义零变化(第 3、4 节)。
这批做什么:把 S13 静态质量闸(gdstyle)在册裸键 `game/presentation/tune_sim_controller.gd|quality/await-in-loop` 真实消除——`run(n)` 的 for 循环内 `await process_frame` 改为 process_frame 信号常驻连接+步进状态机(每帧回调执行一局、步尽次帧收尾断连),`run()` 只留函数级一次 `await _batch_finished` 保持协程身份;改后全文件零 for/while,该键从文本形态根除、此文件对这个键结构性免疫。随批执行两项已裁事项:把「每局置假」还原为「整批收尾单次置假」(已申报行为改动——消除与两个在库断言面的静态矛盾);新增双实现对拍套件,把帧相位等价论证变成当轮真实执行证据。
什么状态算完:
1. `tune_sim_controller.gd` 全文件零循环、`await` 仅 `run()` 函数级一处;`python tools/gdstyle_gate.py` 对账该裸键消失(消失键不参与红判定)、无基线外新键。
2. 测量语义逐点等价(第 4 节四判据)经双实现对拍在当轮真实跑批中证明——五面对拍全等;wd8 三断言与 ui_tune_probe ⑤ 零改动保持绿。
3. diff 中行为改动零处——收尾四语句迁移为非行为改动(同帧同相位、语义零变化,对照表 V3 行),无任何行为漂移。
2. 测量语义逐点等价(第 4 节四判据)经双实现对拍在当轮真实跑批中证明——五面对拍全等;wd8 三断言与 ui_tune_probe ⑤ 零改动转绿。
3. diff 中已申报行为改动恰一处(收尾置假还原,对照表 V3 行),无第二处行为改动。
4. 脚本错误中断路径差异作为声明缺口写入实现报告(禁静默)。
5. 批末代码域闸全绿;实现报告与反哺点清单写齐方算收口。
## 2. 设计事实源
设计依据唯一出处=`reports/设计-测速循环去await-2026-10-29.md`(2026-10-29 设计闭合,零待裁点):§3 形态评估(process_frame 信号步进状态机采纳)、§4 推荐案(伪代码结构)、§5 等价性证明清单(四判据+已声明缺口)、§6 键账预期——以上各节不受前提消化影响、仍是本卡依据;该文档 §2.3 潜伏红推演与 §10 待裁点 1「还原」申报已作废(前提消化证据见停卡呈报)。架构面凭据=`design/engineering/architecture/architecture.md` v1.25「测试与质量域」S13 测量面豁免注销句(2026-10-29 零豁免裁落文)——豁免注销句保持,句内行为改动申报半句随实现批现状化修正(第 6 节同步面)。前提消化与重裁凭据=iter44 worktree 分支 `reports/iter44-实现报告-2026-10-30.md` §5 停卡呈报(git 历史证据 215da315+T0 两面实跑绿)。待办清单条目「零豁免五键清偿批」状态=已写卡+已编迭代 44(本卡 v1.1 重裁段产出)。
设计依据唯一出处=`reports/设计-测速循环去await-2026-10-29.md`(2026-10-29 设计闭合,零待裁点):§4 推荐案(process_frame 信号步进状态机伪代码结构)、§5 等价性证明清单(四判据+已声明缺口)、§6 键账预期、§7 行为零变化风险点。架构面凭据=`design/engineering/architecture/architecture.md` v1.25「测试与质量域」S13 测量面豁免注销句(2026-10-29 零豁免裁落文)——豁免注销、行为改动申报面与本卡一致。待办清单条目「零豁免五键清偿批」状态=文档设计已闭合+待写卡(本卡即其中一卡的写卡段产出)。
## 3. 现状事实(代码级,2026-10-30 实查;开工 T0 按当轮施工基面重锚——行号基线为本日 master)
## 3. 现状事实(代码级,2026-10-29 实查;开工 T0 按当轮 master 重测重锚——行号基线为本日 master)
- `game/presentation/tune_sim_controller.gd`(全件 68 行):`run(n)` 在 :36-54——:37 置运行态真、:40-42 开共享 JSONL 日志会话、:43 `panel.set_sim_running(true)`、:44 进度 0/n、:45 建 stats 六键束、:46 取自动策略驱动、:47-50 for 循环体(`_run_one_level` → 进度串 → :50 `await panel.get_tree().process_frame`——命中键的语句)、:51-54 收尾四语句(`close_session()` :51 → 报告注入 :52 → `panel.set_sim_running(false)` :53 → `_running = false` :54)——**收尾置假在 run() 尾、for 循环外、每批恰一次**。`_run_one_level` 在 :57 注释/:58-68(5 参),尾部 :66-68 为败局节点记账(defeat 分支)——非置假。类成员 5(db/panel/_base_hp/_driver/_running)+常量 2(:7 SEED_BASE=4200、:8 PlaytestLogger preload);头注 4 行。
- 前提消化定谳(v1.0「静态矛盾推演」作废依据):git 历史——迭代 41 测试债批提交 215da315(2026-10-29 合回)提交信息明载「两个产品回归随批修(……tune_sim 运行态收尾归还 run)」,收尾置假两行由该批归还 run() 尾并随该批申报;v1.0 成文(2026-10-29)与该批合回同日,其「现状考证」被上游批施工消化变质(读法 A;读法 B「考证当时即误读」与 215da315 提交信息矛盾,停卡呈报 §5 不采)。T0 实跑三重证据(iter44 worktree,停卡呈报 §5):wd8 三断言(现位 :145-147)全 PASS(套件 18 checks 全绿);ui_tune_probe 全链路 PASS(⑤ 守卫环 1800 帧预算内退出+输出四串在)。**潜伏红不存在**——两面现行全绿,「零改动转绿」改「零改动保持绿」。
- 断言面一〔wd8〕`game/tests/unit/presentation/standee_fx_sweep_test.gd:139-147`:FakePanel(内类 Node,:20-40——setter 面并入树供 get_tree)+FakeDriver(:44-55——`run_level` 回调内读 `ctrl.is_running()` 逐局记 `seen`);:142 直构控制器(字典夹具 db)、:144 `await ctrl.run(2)`(run 必须可等待、挂起至整批完成的契约)、:145-147 三断言——`drv.seen == [true, true]`/收尾 `not ctrl.is_running() and not panel.running`/报告非空。套件在 run_all.gd manifest 在册(res://tests/unit/presentation/standee_fx_sweep_test.gd,expect 0,manifest :188)。
- 断言面二〔探针 ⑤〕`game/probe/ui_tune_probe.gd:63-83`::65-66 设滑条与 N=10、:67 `press_simulate_for_probe()`(真实按钮 pressed 信号链)、:68-71 守卫环(`while main.ui.modals.debug_overlay.is_running()` 逐帧等——SIM_GUARD_FRAMES=1800 在 :16)、:72-73 预算内未完成判红、:74-83 输出四串断言(关卡完成率/关卡完成剩余 HP 均〔口径换代 V7〕/败北节点/N=10)。面板语义面 `debug_overlay.gd`:`set_sim_running` :81-83 → `_sync_sim_button` :107-110(模拟与两层调试两钮禁用+「模拟中…」文案——浮窗运行态贯穿整批的可见面)、`is_running` :77-79。
- 调用面 `game/presentation/main_debug_flow.gd`:`_on_tune_simulate` :72-77——守卫(活跃结算/冒险早退)后 :77 `root._tune_sim.run(n)` **不 await 调用**(返回的协程态丢弃——新形态必须保持此调用形态可用、零改动);`_start_debug_levels` :84 守卫读 `root._tune_sim.is_running()`(两层调试入口互查守卫)。
- 键账:`tools/gdstyle_baseline.txt:15` = 该文件唯一在册键(await-in-loop 裸键,基线 16 键之一)。S13 扫描面=产品码三目录(game/core+data+presentation——probe/sim/tests 不在面,`game/tests/static_scan.sh` S13 头注在案);S8 文件集=**十文件**显式清单(static_scan.sh :317 现行实查——不含本件);S9/S10 扫描面同不含 game/tests/。
- `game/presentation/tune_sim_controller.gd`(全件 68 行):`run(n)` 在 :36-52——:37 置运行态真、:40-42 开共享 JSONL 日志会话、:43 `panel.set_sim_running(true)`、:44 进度 0/n、:45 建 stats 六键束、:46 取自动策略驱动、:47-50 for 循环体(`_run_one_level` → 进度串 → :50 `await panel.get_tree().process_frame`——命中键的语句)、:51-52 收尾(close_session+报告注入)。`_run_one_level` 在 :56-68(5 参),**尾部两行 :67 `panel.set_sim_running(false)`/:68 `_running = false` 每局执行一次**(潜伏红本体,见下)。类成员 5(db/panel/_base_hp/_driver/_running)+常量 2(:7 SEED_BASE=4200、:8 PlaytestLogger preload);头注 4 行。
- 断言面一〔wd8〕`game/tests/unit/presentation/standee_fx_sweep_test.gd:126-134`:FakePanel(内类 Node,:20-40——setter 面并入树供 get_tree)+FakeDriver(:44-55——`run_level` 回调内读 `ctrl.is_running()` 逐局记 `seen`);:129 直构控制器(字典夹具 db)、:131 `await ctrl.run(2)`(run 必须可等待、挂起至整批完成的契约)、:132-134 三断言——`drv.seen == [true, true]`/收尾 `not ctrl.is_running() and not panel.running`/报告非空。套件在 run_all.gd manifest 在册(res://tests/unit/presentation/standee_fx_sweep_test.gd,expect 0)。
- 断言面二〔探针 ⑤〕`game/probe/ui_tune_probe.gd:63-83`::65-66 设滑条与 N=10、:67 `press_simulate_for_probe()`(真实按钮 pressed 信号链)、:68-71 守卫环(`while main.debug_overlay.is_running()` 逐帧等——SIM_GUARD_FRAMES=1800 在 :16)、:72-73 预算内未完成判红、:74-83 输出四串断言(关卡完成率/关卡完成剩余 HP 均〔口径换代 V7〕/败北节点/N=10)。面板语义面 `debug_overlay.gd`:`set_sim_running` :81-83 → `_sync_sim_button` :107-110(模拟与两层调试两钮禁用+「模拟中…」文案——浮窗运行态贯穿整批的可见面)、`is_running` :77-79。
- 调用面 `game/presentation/main_debug_flow.gd`:`_on_tune_simulate` :72-77——守卫(活跃结算/冒险早退)后 `root._tune_sim.run(n)` **不 await 调用**(返回的协程态丢弃——新形态必须保持此调用形态可用、零改动);`_start_debug_levels` :84 守卫读 `root._tune_sim.is_running()`(两层调试入口互查守卫)。
- 静态矛盾推演(设计文档 §2.3——T0 实跑定谳)::67-68 每局执行 → wd8 首局完成即把两个运行态置假 → `drv.seen` 实测 `[true, false]` 必红;探针按下返回后首局已在同一同步段内跑完、运行态已假 → 守卫环立即退出 → 输出停在进度串 → 四串断言全红。推断这两行原在循环之后(「循环体提函数」搬运时被卷入提取体——设计文档 §2.3),wd8 写于 d8 批且当时必绿可反证;两面均不在近期批次的关联档选测覆盖内(套件名与涉案文件无字面关联、probe_gate --full 非恒跑三保险),潜伏红因此未暴露。
- 键账:`tools/gdstyle_baseline.txt:15` = 该文件唯一在册键(await-in-loop 裸键,基线 16 键之一)。S13 扫描面=产品码三目录(game/core+data+presentation——probe/sim/tests 不在面,`game/tests/static_scan.sh` S13 头注在案);S8 文件集=九文件显式清单(static_scan.sh :317——不含本件);S9/S10 扫描面同不含 game/tests/。
- 产品码 process_frame 订阅面(T0 实查项):本件 :50(循环内 await)+`settlement_fx_view.gd:94`(play_damage_number 内一次性 await 等文本度量——战斗结算呈现期,与模拟批不并存)。现库无「run 途中新增订阅者」场景。
- 报告渲染面 `game/presentation/hud_presenter.gd` :197-218 `tune_sim_report`(静态函数,零触碰计划):空数组 `_mean`/`_p90` 双守卫(:221-227/:230-235)、完成率分母 `maxi(1, n)`(:206)——n=0 退化路径渲染安全(本批对拍 n=0 案的前提,实查在案)。
- 能力表现状:`game/tests/README.md` 能力表「调参测速批量模拟(逐帧步进与统计读数)」行已入表(:131——写卡段 v1.0 同批义务,真实入口=批量模拟入口,代表用例=对拍套件);断言宿主 wd8 套件锚既有行「站桩敌人呈现与结算演出落点」(:130)。
- 能力表现状:`game/tests/README.md` 能力表零 tune_sim/调试浮窗行;锚到本批断言宿主套件的唯一既有行=「站桩敌人呈现与结算演出落点」(代表用例 `standee_fx_sweep_test.gd:_init`——wd8 断言所在函数)。
## 4. 设计契约(违反即返工的红线条款,标 🔴 触碰=L3 停机呈报)
@@ -40,30 +40,30 @@
③ 终止条件与终止时读数不变——终止=计数走满 n;close_session/运行态复位/报告注入在同帧执行;
④ 输出逐字不变——报告=未触碰的 `HudPresenter.tune_sim_report`;stats 是 (种子, atk_pct, hp_pct) 的纯函数、帧时点不进测量值;JSONL 由未触碰的自动策略逐行写、close 时点同帧。
违反即返工;等价性对拍失败=L3 停机呈报。
- 🔴 **行为改动零处(收尾语义零变化)**:收尾置假已在 run() 尾每批一次执行(:53-54,215da315 归还后的现行形态)——本批 `_run_one_level`(:58-68)零触碰、运行态复位语义零变化;收尾四语句(:51-54)迁入 `_finish_batch()` 为同帧同相位语句迁移(判据③承载——非行为改动,对照表 V3 行)。禁任何行为漂移——`_on_tune_simulate` 不加运行态早退守卫(设计文档待裁点 3 备选否决)、发布态零触碰、探针①–④⑥–⑩ 段零触碰、`HudPresenter`/`PlaytestLogger`/`sim/level_autoplay.gd`/`debug_overlay.gd` 零触碰。
- 🔴 **行为改动唯一=收尾置假还原**(已申报——对照表 V3 行):`_run_one_level` 尾部两行(:67-68)删除,运行态复位收进 `_finish_batch()` 收尾单次执行;禁任何其他行为漂移——`_on_tune_simulate` 不加运行态早退守卫(设计文档待裁点 3 备选否决)、发布态零触碰、探针①–④⑥–⑩ 段零触碰、`HudPresenter`/`PlaytestLogger`/`sim/level_autoplay.gd`/`debug_overlay.gd` 零触碰。
- 🔴 **脚本错误中断路径差异=声明缺口**(设计文档 §5 缺口 1,接受并文档化):GDScript 无错误陷阱 API、差异不可消除——旧形态步进体内脚本错误=协程死亡静默停摆(运行态永真、报告永不落);新形态=计数未推进、次帧重试同一步(确定性错误逐帧重试,探针 1800 帧守卫可抓;生产态=持续重试的可见坏,与旧「静默卡死」同级不同形)。实现报告文档化义务(T3),禁静默、禁造第三条路(错误陷阱/守卫兜底均已否决);测量批遇错本就整批作废,两形态都不产出可用读数。
- 🔴 **S8 结算路径 await 零命中面不触碰**:本件不在 S8 文件集(十文件显式清单实查不含本件);本批零触碰 S8 文件集与一切闸件。
- 🔴 **S8 结算路径 await 零命中面不触碰**:本件不在 S8 文件集(九文件显式清单实查不含本件);本批零触碰 S8 文件集与一切闸件。
- 🔴 **拆分边界纪律**(判据权威=code-conventions §1.1,卡内执行条款):本批零拆出单元——步进状态机收编本件(职责语句与不拆理由见第 8 节);①拆出单元须有可陈述职责语句与单一变化原因;②命不出名字、只能说「剩下的成员」的打包=凑计数袋,禁;③「过阈值」只作发现信号——本批键消除靠形态根除、不靠拆件凑数;④按纪律拆不出内聚单元→停卡呈报;⑤禁沿阈值轴拆。
- 🔴 **RNG 与种子零触碰**:SEED_BASE=4200 与逐局种子切分(SEED_BASE+i)零变化——stats 是种子纯函数是等价性论证的前提。
- 🔴 **注释纪律**:新增头注/注释零变更叙事(「由 await 改为信号」类句禁——沿革归 git);S11 注释禁词由术语表解析供给,新增注释用白话描述现行形态与信号契约。
## 5. 范围边界
做什么:第 3 节四件事——T0 现状复核(两面实跑应绿)、`tune_sim_controller.gd` 步进状态机改造(收尾语义零变化)、双实现对拍套件、键账对账与收口。
做什么:第 3 节四件事——T0 现状定谳(实跑两断言面)、`tune_sim_controller.gd` 步进状态机改造+收尾还原、双实现对拍套件、键账对账与收口。
不做什么(明示去向):
- 零豁免其余四键(main.gd 类变量 49、battle_orchestrator 类变量 38、board_cell_unit 30、standee_body 19)——各归组合根注册面批/战斗编排归约批/呈现层状态件批(同条目四卡,待办清单在册)。
- `tools/gdstyle_baseline.txt` 禁实现批中途刷新——批末由用户人工重生成(第 6 节)。
- `_on_tune_simulate` 运行态早退守卫——不收件(+1 行为面、system_test 前置断言随之需复核,代价大于收益,设计文档待裁点 3 否决)。
- `design/` 整个目录、两份根流程清单——实现批禁写;该改的点列反哺点清单。`game/tests/README.md`:测量面专属能力行已随本卡 v1.0 写卡段同批入表(「调参测速批量模拟」行),实现批禁写红线不变。
- `design/` 整个目录、两份根流程清单——实现批禁写;该改的点列反哺点清单。`game/tests/README.md`:测量面专属能力行已随本卡写卡段同批入表(「调参测速批量模拟」行——写卡同批义务,grilling 复核订正),实现批禁写红线不变。
- 探针其余段与套件其余断言族(standee_fx_sweep 的 wd2/wd4/wd5/wd12 面)零触碰;`architecture.md` 其余豁免条目(battle_orchestrator 第三条注销句现状化归战斗编排归约批反哺点)零触碰。
## 6. 关联文档同步面
| 文档 | 关联点(本批实现会改变它的哪条陈述) | 同步义务 |
|---|---|---|
| design/engineering/architecture/architecture.md | 「测试与质量域」S13 测量面豁免注销句(v1.25——「设计闭合、实现批建件」批后现状化;句保持——注销对象是 await 豁免、不依赖行为改动申报;句内「随批申报行为改动一处(收尾置假还原)」半句已失效〔前提被迭代 41 消化、申报已剥离〕,现状化时一并修正为收尾现行陈述) | 设计权威文档——实现批禁直改;逐条列进 `reports/iter44-反哺点-2026-10-30.md`(豁免注销句现状化,该清单 #6 已登记暂停点、随本卡 v1.1 恢复),用户裁决后归文档批落文 |
| design/engineering/architecture/architecture.md | 「测试与质量域」S13 测量面豁免注销句(v1.25——「设计闭合、实现批建件」批后现状化;随批申报行为改动句随之成为已实施事实) | 设计权威文档——实现批禁直改;逐条列进 `reports/iter<N>-反哺点-<日期>.md`(豁免注销句现状化),用户裁决后归文档批落文 |
| game/tests/README.md | 能力表——测量面专属能力行「调参测速批量模拟(逐帧步进与统计读数)」已随本卡写卡段同批入表(真实入口=批量模拟入口,代表用例=本卡对拍套件);manifest 计数行随套件增减浮动(自助声明,零义务) | 机械面文档——实现批禁写;入表已完成于写卡段(本卡 v1.0 同批),实现期零改动 |
| tools/gdstyle_baseline.txt | tune_sim await-in-loop 裸键(:15,该文件唯一在册键)随键消失成死键 | 重生成为人工专用——批末由用户执行 `python tools/gdstyle_gate.py --refresh`(键数 16→15,降幅 <10% 不需 --force);禁实现批中途刷新 |
| design/gdd/(全目录) | 无关联文档——测量工具私有机制改造,不改游戏规则与玩家可见行为(tune_sim 全链路发布态不可达,设计文档 §7.3) | 无修订预期;契约句被触碰=停卡呈报走反哺点通道 |
@@ -72,18 +72,18 @@
| 文件 | 新增/复用 | 职责 | 行为改动 |
|---|---|---|---|
| game/presentation/tune_sim_controller.gd | 复用(改造) | 批量模拟控制器——process_frame 信号常驻连接+步进状态机;收尾单点 `_finish_batch`(四语句同帧同相位迁入);全文件零循环 | 非行为改动(结构改形——收尾语义零变化,零漂移由对拍证;回归锚面=对照表 V3 行) |
| game/tests/unit/presentation/tune_sim_batch_parity_test.gd | 新增 | 双实现对拍配对套件——现行 await 形态夹具(内类,收尾在 run() 尾的现行形态)+五面对拍断言 | 非行为改动(测试面) |
| game/presentation/tune_sim_controller.gd | 复用(改造) | 批量模拟控制器——process_frame 信号常驻连接+步进状态机;收尾单点 `_finish_batch`;全文件零循环 | 行为改动一处(V3 收尾还原)+结构改形(V1——零漂移由对拍证) |
| game/tests/unit/presentation/tune_sim_batch_parity_test.gd | 新增 | 双实现对拍配对套件——遗留 await 形态夹具(内类)+五面对拍断言 | 非行为改动(测试面) |
| game/tests/run_all.gd | 复用(改造) | manifest 增一行(新套件注册,expect 0) | 非行为改动(测试基建) |
结构图与第 8 节互指:新增件一件(对拍套件),其「为什么不能复用/落点唯一」理由见第 8 节;其余两行均为既有文件改造。行为改动列全为零——每行的验收落点见对照表 V1/V2/V3 行。
结构图与第 8 节互指:新增件一件(对拍套件),其「为什么不能复用/落点唯一」理由见第 8 节;其余两行均为既有文件改造。
## 8. 落位与可复用性预检
- **步进状态机不引入新文件(本件内改造)——为什么不拆新件**:单一职责已在件内——`tune_sim_controller` 的职责就是批量模拟控制,步进机制(每帧一局的节律与帧相位承载)是它的私有运行机制,随批生灭(run 期活成员,批末失效下次 run 重写),没有独立于批量模拟的存在意义;拆出「步进机」件命不出独立职责语句=凑计数袋(code-conventions §1.1 判据②④禁)。复用面核查:全仓 grep 无信号步进机同职责实现可复用;其余四形态(SceneTreeTimer 链/Timer 节点/递归协程/线程)已被设计文档 §3 逐一否决(相位不同或键不消)。
- **对拍套件落 `game/tests/unit/presentation/` 的唯一性与命名**:落点唯一——与断言宿主 wd8 同目录同协议(extends SceneTree+Kit 收尾),单元层承载;命名 `tune_sim_batch_parity_test.gd`(领域命名、无 ID 前缀);grep 同名与同义标识符实查无第二处。**遗留 await 形态副本全仓只许此一份**——产品码禁留第二实现(旧形态只活在测试夹具内);tests/ 不在 S13/S8/S9/S10 扫描面(扫描面=产品码三目录与 S8 文件集,实查在案),夹具内循环 await 合法在册。
- **对拍套件落 `game/tests/unit/presentation/` 的唯一性与命名**:落点唯一——与断言宿主 wd8 同目录同协议(extends SceneTree+Kit 收尾),单元层承载;命名 `tune_sim_batch_parity_test.gd`(领域命名、无 ID 前缀);grep 同名与同义标识符实查无第二处。**遗留 await 形态副本全仓只许此一份**——产品码禁留第二实现(旧形态只活在测试夹具内);tests/ 不在 S13/S8/S9/S10 扫描面(扫描面=产品码三目录与 B1 九文件清单,实查在案),夹具内循环 await 合法在册。
- **复用面**:夹具的假面板/假驱动复用 wd8 的形态先例(FakePanel/FakeDriver 同形——代码在各套件内自建,不跨套件共享 helper,禁散落);收尾复用 `test_kit.gd` 的 Kit.finish(零断言专用退出码协议)。
- **分层与交互**:控制器保持 RefCounted、零 Node 入树;process_frame 连接经 `panel.get_tree()` 取树(与现状同一出口);对拍套件 `--script` 模式零 autoload 依赖(db 用字典夹具,同 wd8 :142 先例);零新增跨层出口、零数值表读取、零随机。
- **分层与交互**:控制器保持 RefCounted、零 Node 入树;process_frame 连接经 `panel.get_tree()` 取树(与现状同一出口);对拍套件 `--script` 模式零 autoload 依赖(db 用字典夹具,同 wd8 :129 先例);零新增跨层出口、零数值表读取、零随机。
## 9. 实现规范预算(gdstyle 默认阈值对账:公开方法 ≤20、类变量 ≤15、单函数分支 ≤8、返回 ≤6、参数 ≤5、局部 ≤10、函数 ≤50 行)
@@ -93,7 +93,7 @@
|---|---|---|
| quality/await-in-loop(裸键,本案标的) | **消除** | 全文件零 for/while;await 仅 `run()` 函数级一处(`await _batch_finished`)——结构性免疫 |
| quality/allocation-in-loop | 不命中 | 文件零循环;`PlaytestLogger.new()` 在 run 顶层(现状即如此) |
| max-function-length ≤50 | 达标 | run 约 22 行、`_step_frame` 约 12 行、`_finish_batch` 约 6 行、`_run_one_level` 现行 11 行零触碰 |
| max-function-length ≤50 | 达标 | run 约 22 行、`_step_frame` 约 12 行、`_run_one_level` 删两行后约 10 行 |
| max-branches ≤8 | 达标 | `_step_frame` 一个 if/else;run 一个 if/else |
| max-local-variables ≤10 | 达标 | run 局部 3(logger/stats/autoplay——余者转成员);`_step_frame` 局部 1 |
| max-returns ≤6 | 达标 | 全 void |
@@ -108,15 +108,15 @@
## 11. 任务卡 T0..T3(估时为区间;工作量限制只属迭代层——合计 1–1.5 工作日)
**T0 现状复核与读数重锚(0.25 工作日)**
**T0 现状定谳与读数重锚(0.25 工作日)**
- [ ] 实跑 wd8 单套件(开发期最窄确认,不作验收凭据):`godot --headless --path game --script res://tests/run_all.gd -- --only=standee_fx_sweep`——记录 :145-147 三断言实况,**应绿**(绿=前提已被迭代 41 测试债批消化,按无行为改动形态开工)。套件若因在册无关债先红于 wd8 断言,先核该债修复状态、不得借本批顺手修非申报面——读数以「wd8 断言可达性」为准呈报(L2 记录)
- [ ] 实跑 ui_tune_probe 单探针:`…console.exe --headless --path game res://probe/ui_tune_probe.tscn`——记录 ⑤ 守卫环与输出四串实况,**应在**
- [ ] 判读:两面实跑全绿=重裁前提成立开工;任一面红=现状与重裁前提失配(迭代 41 修复被回退或新潜伏红)——L3 停卡呈报(第 13 节)
- [ ] 按当轮施工基面重锚第 3 节全部 file:line 与成员计数;漂移处就地改卡并记录(本卡行号基线=2026-10-30 master;同迭代卡①改址过 main_debug_flow/ui_tune_probe——两件相关行实测未漂移,仍以 T0 重锚为准)
- [ ] 实跑 wd8 单套件(开发期最窄确认,不作验收凭据):`godot --headless --path game --script res://tests/run_all.gd -- --only=standee_fx_sweep`——记录 :132-134 三断言实况;红=潜伏红定谳成立。套件若因在册无关缺陷先红于 wd8 断言(待办清单「迭代 41 测试债修复」在册的 set_trans 一类),先核该债修复状态、不得借本批顺手修非申报面——读数以「wd8 断言可达性」为准呈报(L2 记录)
- [ ] 实跑 ui_tune_probe 单探针:`…console.exe --headless --path game res://probe/ui_tune_probe.tscn`——记录 ⑤ 守卫环与输出四串实况
- [ ] 判读:两面按第 3 节静态推演红=前提成立开工;两面实跑全绿=潜伏红证伪=设计前提崩塌——L3 停卡呈报(第 13 节)
- [ ] 按当轮 master 重锚第 3 节全部 file:line 与成员计数;漂移处就地改卡并记录
- [ ] 实查产品码 process_frame 订阅面——预期两处(本件 :50+settlement_fx_view.gd:94);出现 run 途中新增订阅者场景=L2 记录+代码评审注记(设计文档 §5 缺口 2)
产出:现状复核读数(两面绿——前提消化链条的当轮证据,独立验收审计要核对);重锚后行号。
产出:潜伏红定谳读数(改动前基线证据,独立验收审计要核对);重锚后行号。
**T1 步进状态机改造(0.25–0.5 工作日)**
@@ -124,16 +124,16 @@
- [ ] 增 `signal _batch_finished` 与 6 个 run 期活成员(名字按设计文档 §4.1 逐字:`_sim_n: int`/`_sim_i: int`/`_sim_stats: Dictionary`/`_sim_autoplay`/`_sim_logger`/`_finished: bool`)
- [ ] `run(n)` 改形:setup 段(:37-46 语句逐字保持);n>0 分支——`is_connected` 守卫 connect `panel.get_tree().process_frame`(恰好连接一次,迭代 39 先例)→ `_step_frame()` 首步同步执行(与 await 形态「第 0 局在调用栈内跑完」对齐)→ `await _batch_finished`(函数级单次 await——键消除落点);n=0 分支直调 `_finish_batch()`(循环零进同步收尾,与 await 形态一致不挂起)
- [ ] 新增 `_step_frame() -> void`(0 参):`_sim_i < _sim_n` 分支——`_run_one_level(_sim_autoplay, _sim_stats, _sim_i, panel, _sim_logger)`+`panel.set_sim_progress("模拟中:%d/%d …" % [_sim_i + 1, _sim_n])`(串逐字保持现码 :49)+`_sim_i += 1`;else 分支——先 disconnect(防再入)→ `_finish_batch()` → `_batch_finished.emit()`
- [ ] 新增 `_finish_batch() -> void`(0 参):收尾四语句由 run() 尾 :51-54 迁入(`close_session()`→报告注入→`panel.set_sim_running(false)`→`_running = false`,同帧同相位——判据③;`_finished = true` 置位为新增首句,对两断言面不可观测——白名单 5)
- [ ] `_run_one_level` **零触碰**(:58-68 逐字保持——败局节点记账非置假;本批行为零改动的锚面)
- [ ] 孤立验证:`--only=standee_fx_sweep` 全绿(wd8 三断言零改动保持绿=收尾语义零变化实证)+ui_tune_probe 单探针绿(probe_gate --full 段承载)
- [ ] 新增 `_finish_batch() -> void`(0 参):`_finished = true` → `close_session()` → `panel.set_sim_running(false)` → `_running = false` → `panel.set_sim_report(HudPresenter.tune_sim_report(_sim_n, panel.atk_pct(), panel.hp_pct(), _sim_stats, _base_hp))`(四语句相对次序按此草图,对两断言面不可观测——白名单 5)
- [ ] `_run_one_level` 删尾部两行(:67-68),其余逐字保持(5 参签名不变)
- [ ] 孤立验证:`--only=standee_fx_sweep` 全绿(wd8 三断言零改动转绿=潜伏红消除实证)+ui_tune_probe 单探针绿(probe_gate --full 段承载)
- [ ] 自检:新增头注/注释零变更叙事、S11 禁词零命中
取用:T0 重锚行号。产出:`_batch_finished` 信号与 `_step_frame`/`_finish_batch` 签名(T2 对拍新形态即产品码本体);wd8 保持绿读数。
取用:T0 重锚行号。产出:`_batch_finished` 信号与 `_step_frame`/`_finish_batch` 签名(T2 对拍新形态即产品码本体);wd8 转绿读数。
**T2 双实现对拍夹具(0.25–0.5 工作日)**
- [ ] 建 `game/tests/unit/presentation/tune_sim_batch_parity_test.gd`(extends SceneTree,Kit 收尾,全类型标注):内类 FakePanel(Node——wd8 同形 setter 面,另带进度串/报告记录数组)+FakeDriver(RefCounted——固定返回值两档〔victory/defeat,同 wd8 :53-55 取值〕,零随机;逐局记录帧索引与运行态读数,帧索引经宿主注入的计数 Callable 读取)+AwaitLoopRunner(RefCounted——现 `run()` await 形态逐字副本,含循环内 await 与收尾四语句在 run() 尾的现行形态(:47-54 语句逐字),含自身 `is_running()` 镜像;**与产品码新形态的申报差异仅一处**:批末把 stats 六键束存夹具成员供对拍读取——产品码 stats 经报告公开面观测,第 17 节直查受控例外申报)
- [ ] 建 `game/tests/unit/presentation/tune_sim_batch_parity_test.gd`(extends SceneTree,Kit 收尾,全类型标注):内类 FakePanel(Node——wd8 同形 setter 面,另带进度串/报告记录数组)+FakeDriver(RefCounted——固定返回值两档〔victory/defeat,同 wd8 :53-55 取值〕,零随机;逐局记录帧索引与运行态读数,帧索引经宿主注入的计数 Callable 读取)+AwaitLoopRunner(RefCounted——现 `run()` await 形态逐字副本,含自身 `is_running()` 镜像;**与产品码的申报差异仅两处**:尾部两行按还原语义置收尾、批末把 stats 六键束存夹具成员供对拍读取)
- [ ] 对拍驱动:两形态各经 `run(n)` 真实入口驱动(新形态=真 TuneSimScript 直构,旧形态=夹具直构;同参假面板/假驱动各自实例);拓扑钉死——测试宿主先连 process_frame 帧计数、后启动两形态(两形态下「计数先于步进体」相对次序同构,序列可比);n=3 常规+n=0 退化两案
- [ ] 五面对拍断言(落点 `_init`,尾缀〔测速 V1〕..〔测速 V5〕):V1 帧索引序列——两形态逐局记录序列逐位相等(每步恰一帧+相位同点的执行证据);V2 运行态序列——驱动内 `is_running()` 读数序列逐位相等+收尾双运行态假相等;V3 进度串序列——面板记录的 set_sim_progress 串序列逐位相等;V4 stats 六键逐位——夹具暴露 stats 与测试内按假驱动固定返回值重算的六键期望(wins/dmg_all/hp_left_win/hp_boss/rounds_all/death_nodes)逐键相等;V5 报告全文逐字——两形态面板记录的 set_sim_report 串逐字相等,n=3 案含「关卡完成率/关卡完成剩余 HP 均/败北节点/N=」渲染面(n=0 案只断逐字相等——空 stats 渲染无剩余 HP 行,`_mean`/`_p90` 空守卫实查在案)
- [ ] `run_all.gd` manifest 增行 `{"path": "res://tests/unit/presentation/tune_sim_batch_parity_test.gd", "expect": 0}`
@@ -147,27 +147,27 @@
- [ ] 关联档复跑:`--only=standee_fx_sweep,tune_sim_batch_parity`+ui_tune_probe(probe_gate --full 段)全绿
- [ ] 批末代码域闸全绿:`tools/run_all_gates.sh --code`(改动含 game/ 下非 .md 行为载体——`--code` 档)
- [ ] 错误路径声明缺口写入实现报告(第 4 节 🔴 条全文迁入——禁静默)
- [ ] 产出物落位卡:`reports/iter44-实现报告-2026-10-30.md`(各卡状态+实机目检总清单〔本批零目检项〕+遗留读数义务+实现决定记录)与 `reports/iter44-反哺点清单-2026-10-30.md`(第 6 节同步面逐条落点)写齐,方算收口
- [ ] 产出物落位卡:`reports/iter<N>-实现报告-<日期>.md`(各卡状态+实机目检总清单〔本批零目检项〕+遗留读数义务+实现决定记录)与 `reports/iter<N>-反哺点清单-<日期>.md`(第 6 节同步面逐条落点)写齐,方算收口
编队注记:本卡与零豁免其余三卡无产品码触面交集(本卡触面=tune_sim_controller.gd+game/tests/ 两件);run_all.gd manifest 行与并行批可能交叠——编队归编排段。本卡在 iter44 worktree 施工(卡①卡② 已收口冻结在分支),T0 重锚以 worktree 基面为准。
编队注记:本卡与零豁免其余三卡无产品码触面交集(本卡触面=tune_sim_controller.gd+game/tests/ 两件);run_all.gd manifest 行与并行批可能交叠——编队归编排段(待办清单条目口径:编队待迭代 42 合回)。
## 12. 验证闸——改动—验收对照表
域口径:本批改动全为 `game/` 下非 .md 行为载体——批末跑代码域闸;里程碑步(T 段)出口跑一次全套闸,波内修复只做出红最小单元的最窄确认,新增测试先行孤立验证再并入全套(测试执行两入口铁规不破)。三件保险(冒烟/系统测试/静态扫描)恒跑;02 单元与集成按关联档选套件——**本卡显式申报套件:standee_fx_sweep_test(wd8——套件名与涉案文件无字面关联,diff 推导可能漏选,特此申报)+tune_sim_batch_parity_test(新增)**。测试方式两分法:本批全部调试接口自动化(假驱动/假面板直构+真实按钮信号链探针),零用户目检项(tune_sim 全链路发布态不可达——登记不阻塞条款零登记项)。**静态判据申报(不入对照表,test-conventions §8.4 出口)**:S13 await-in-loop 裸键消除无独立运行期提交面——判据=`python tools/gdstyle_gate.py` 与 `game/tests/static_scan.sh` 退出码(T3 执行),属静态判据+评审注记申报观测面局限;对拍夹具不在 S13/S8/S9/S10 扫描面的声明同此口径(扫描面=产品码三目录与 S8 文件集,static_scan.sh 实查在案)。**声明缺口(不入对照表——证明做不出出口)**:脚本错误中断路径差异不可消除,见第 4 节 🔴 条,实现报告文档化(T3)。**能力清单**:本批新增能力一行「调参测速批量模拟(逐帧步进与统计读数)」已随本卡 v1.0 写卡段同批入表(README :131)——对照表三行能力列锚该行;manifest 计数行随套件增减浮动。
域口径:本批改动全为 `game/` 下非 .md 行为载体——批末跑代码域闸;里程碑步(T 段)出口跑一次全套闸,波内修复只做出红最小单元的最窄确认,新增测试先行孤立验证再并入全套(测试执行两入口铁规不破)。三件保险(冒烟/系统测试/静态扫描)恒跑;02 单元与集成按关联档选套件——**本卡显式申报套件:standee_fx_sweep_test(wd8——套件名与涉案文件无字面关联,diff 推导可能漏选,特此申报)+tune_sim_batch_parity_test(新增)**。测试方式两分法:本批全部调试接口自动化(假驱动/假面板直构+真实按钮信号链探针),零用户目检项(tune_sim 全链路发布态不可达——登记不阻塞条款零登记项)。**静态判据申报(不入对照表,test-conventions §8.4 出口)**:S13 await-in-loop 裸键消除无独立运行期提交面——判据=`python tools/gdstyle_gate.py` 与 `game/tests/static_scan.sh` 退出码(T3 执行),属静态判据+评审注记申报观测面局限;对拍夹具不在 S13/S8/S9/S10 扫描面的声明同此口径(扫描面=产品码三目录与 B1 九文件清单,static_scan.sh 实查在案)。**声明缺口(不入对照表——证明做不出出口)**:脚本错误中断路径差异不可消除,见第 4 节 🔴 条,实现报告文档化(T3)。**能力清单**:本批新增能力一行「调参测速批量模拟(逐帧步进与统计读数)」已随本卡写卡段同批入表(写卡同批义务,grilling 复核订正原「既有名锚定+后续入表」悬置形态)——对照表三行能力列改锚该行;manifest 计数行随套件增减浮动。
| 行 | 需求点 | 改动(文件:行/行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 |
|---|---|---|---|---|---|---|---|
| V1 | 步进状态机改造(循环内 await 消除的落码面) | tune_sim_controller.gd :36-54 改形(run 函数级单次 `await _batch_finished`,全文件零循环;收尾四语句 :51-54 迁入 `_finish_batch`)+:58 起新增 `_step_frame`/`_finish_batch` | 复用 wd8 驱动形态 `await ctrl.run(2)`(:144——run 协程身份契约,零改动);新增对拍 V4/V5(stats 六键逐位+报告逐字,落点 tune_sim_batch_parity_test.gd:_init,尾缀〔测速 V4/V5〕) | 单元 | TuneSimScript 直构(字典夹具 db+假面板+假驱动,wd8 :142 同款)经 `run(n)` 真实入口;对拍两形态同入口 | run 可等待(await 挂起=整批完成放行)+stats 六键逐位+报告全文逐字 | 新增:调参测速批量模拟(逐帧步进与统计读数) |
| V1 | 步进状态机改造(循环内 await 消除的落码面) | tune_sim_controller.gd :36-52 改形(run 函数级单次 `await _batch_finished`,全文件零循环)+:56-68 拆出 `_step_frame`/`_finish_batch` | 复用 wd8 驱动形态 `await ctrl.run(2)`(:131——run 协程身份契约,零改动);新增对拍 V4/V5(stats 六键逐位+报告逐字,落点 tune_sim_batch_parity_test.gd:_init,尾缀〔测速 V4/V5〕) | 单元 | TuneSimScript 直构(字典夹具 db+假面板+假驱动,wd8 :129 同款)经 `run(n)` 真实入口;对拍两形态同入口 | run 可等待(await 挂起=整批完成放行)+stats 六键逐位+报告全文逐字 | 新增:调参测速批量模拟(逐帧步进与统计读数) |
| V2 | 双实现对拍(帧相位等价的执行化证据) | tune_sim_batch_parity_test.gd 新建+run_all.gd manifest 增行 | 新增五面对拍断言〔测速 V1–V3〕:帧索引序列/运行态序列/进度串序列逐位相等(落点 tune_sim_batch_parity_test.gd:_init) | 单元 | 两形态各经 `run(n)` 真实入口(n=3 常规+n=0 退化);帧索引经测试宿主 process_frame 计数 | 两形态五面记录序列逐位相等(每步恰一帧+相位同点的当轮执行证据——判据本体) | 新增:调参测速批量模拟(逐帧步进与统计读数) |
| V3 | 运行态语义零变化(收尾迁移的回归锚面——非行为改动行) | 收尾四语句(:51-54)迁入 `_finish_batch` 同帧同相位——非行为改动判断依据:语句集与语义零变化、对两断言面不可观测,等价证据由对拍 V2/V5 承载;`_run_one_level` :58-68 零触碰 | 复用 wd8 三断言〔wd8〕(:145-147——`drv.seen == [true, true]`/收尾双运行态假/报告非空——零改动**保持绿**)+ui_tune_probe ⑤ 守卫环与输出四串(:63-83——零改动保持绿) | 单元+探针 | wd8 假驱动回调读 `is_running()`(`await ctrl.run(2)`);探针 `press_simulate_for_probe()` → 按钮 pressed 信号链 → `run(10)` → 守卫环逐帧复查 | 驱动逐局运行态读数序列 [true, true]+收尾双运行态假+守卫环 1800 帧预算内退出+`output_text()` 四关键串(浮窗运行态贯穿整批的可观测提交面——绿=保持绿,非转绿) | 新增:调参测速批量模拟(逐帧步进与统计读数) |
| V3 | 行为改动——收尾置假还原(已申报唯一行为改动) | tune_sim_controller.gd :67-68 删(每局置假)→ `_finish_batch` 收尾单次置假 | 复用 wd8 三断言〔wd8〕(`drv.seen == [true, true]`/收尾 `ctrl.is_running()` 假+`panel.running` 假/报告非空——零改动)+ui_tune_probe ⑤ 守卫环与输出四串(:63-83——零改动) | 单元+探针 | wd8 假驱动回调读 `is_running()`(`await ctrl.run(2)`);探针 `press_simulate_for_probe()` → 按钮 pressed 信号链 → `run(10)` → 守卫环逐帧复查 | 驱动逐局运行态读数序列 [true, true]+收尾双运行态假+守卫环 1800 帧预算内退出+`output_text()` 四关键串(浮窗运行态贯穿整批的可观测提交面) | 新增:调参测速批量模拟(逐帧步进与统计读数) |
反向核对:diff 中行为改动共零处;其余为非行为改动——V1 结构改形(零漂移判据=对拍五面全等+wd8/probe 零改动保持绿)、V2 测试面新增、V3 回归锚面行(非行为改动)、manifest 增行(测试基建)。孤儿断言:对拍五断言挂 V1/V2 两行,wd8/probe 既有断言挂 V3 行,无孤儿。
反向核对:diff 中行为改动共一处(V3 行);其余为非行为改动——V1 结构改形(零漂移判据=对拍五面全等+wd8/probe 零改动转绿)、V2 测试面新增、manifest 增行(测试基建)。孤儿断言:对拍五断言挂 V1/V2 两行,无孤儿。
## 13. 异常分级
- **L1 自救**:编译错、断言尾缀措辞、夹具记录数组名笔误——就地修复,重跑最窄单元确认。
- **L2 记录继续**:T0 套件因在册无关债先红于 wd8 断言(记录可达性事实,不借批顺手修);行号重锚漂移(就地改卡记录);产品码 process_frame 订阅面发现第三处(记录+评审注记——非 run 途中新增场景不红)。
- **L3 停机写报告等用户**:①T0 两面任一红=现状与重裁前提失配(迭代 41 修复被回退或新潜伏红),停卡呈报;②等价性对拍失败——五面任一不等=测量语义漂移,设计前提崩塌;③机检预算互斥——改造后实测键超第 9 节预算或基线外新键=上游缺陷停卡退回写卡段(含禁为机检保留无效成员条款冲突);④S13 对账 await 键未消或冒新键;⑤判据绑不上可观测提交面(证明做不出=停卡呈报,禁自造注入破坏或回退取证)。
- **L3 停机写报告等用户**:①T0 潜伏红证伪——两面实跑全绿=「每局置假系搬运卷入」前提崩塌,停卡退回写卡段重裁行为改动申报面;②等价性对拍失败——五面任一不等=测量语义漂移,设计前提崩塌;③机检预算互斥——改造后实测键超第 9 节预算或基线外新键=上游缺陷停卡退回写卡段(含禁为机检保留无效成员条款冲突);④S13 对账 await 键未消或冒新键;⑤判据绑不上可观测提交面(证明做不出=停卡呈报,禁自造注入破坏或回退取证)。
## 14. 代拍板白名单(逐条列,超清单记实现报告「实现决定记录」)
@@ -175,20 +175,20 @@
2. 头注 `_batch_finished` 契约行措辞(信号语义与发射时点不变)。
3. `_finished` 成员零读者确认后删去(成员 11→10——键账只降不升,行为与断言面零变化)。
4. 夹具假面板/假驱动成员命名微调(记录语义不变;与 wd8 同形自建、不跨套件共享)。
5. 收尾段语句相对次序——现行 run() 尾次序(close→报告→置假→_running)与设计草图次序(close→置假→_running→报告)对两个断言面均不可观测,两种皆可落码(实现期查 git 历史原序可进一步对齐);`_finished = true` 置位为新增首句(成员写,不可观测)。
5. `_finish_batch` 收尾段四语句相对次序按第 11 节 T1 草图次序落码(对两个断言面不可观测——实现期查 git 历史原序可进一步对齐)。
6. 排版机械改写(空行、preload 顺序、注释换行)。
## 15. 待裁点
零待裁点,开工门=U0。(裁决记录:2026-10-29 用户裁四点——①信号步进状态机形态采纳〔SceneTreeTimer 链/Timer 节点/递归协程/线程四形态淘汰依据见设计文档 §3〕;②收尾置假还原作为已申报行为改动随批实施【已作废——前提被迭代 41 测试债批 215da315 消化,v1.1 剥离】;③双实现对拍入批;④脚本错误中断路径差异接受并文档化。2026-10-30 停卡重裁(用户裁,按停卡呈报建议案逐条落卡):剥离行为改动申报 V3、T0 义务改现状复核、夹具申报差异两处减一处;重裁沿用 2026-10-29 grilling 复核门结论(reports/复核-grilling-零豁免五卡-2026-10-29.md,修正全落),重裁差异面未新增设计裁决。)
零待裁点,开工门=U0。(裁决记录:2026-10-29 用户裁四点——①信号步进状态机形态采纳〔SceneTreeTimer 链/Timer 节点/递归协程/线程四形态淘汰依据见设计文档 §3〕;②收尾置假还原作为已申报行为改动随批实施;③双实现对拍入批;④脚本错误中断路径差异接受并文档化。)
## 16. 测试削弱申报
无申报项——wd8 三断言与 ui_tune_probe ⑤ 零改动零削弱(保持绿);对拍五断言为新增非改判;夹具与产品码新形态的申报差异仅一处(夹具暴露 stats 供对拍读取)系新增件自身设计、设计文档 §5 在案,非既有断言削弱。禁删断言、禁放宽阈值、禁缩小扫描面、禁把真实入口换成下层直调;未申报的削弱由独立验收审计判红。
无申报项——wd8 三断言与 ui_tune_probe ⑤ 零改动零削弱;对拍五断言为新增非改判;夹具与产品码的两处申报差异(收尾还原语义+stats 暴露成员)系新增件自身设计、设计文档 §5 在案,非既有断言削弱。禁删断言、禁放宽阈值、禁缩小扫描面、禁把真实入口换成下层直调;未申报的削弱由独立验收审计判红。
## 17. 独立验收审计
合并前由未参与实现的会话或子代理执行;报告落 `reports/iter44-验收审计-2026-10-30.md`,未过不得合并。审计至少做五件事:①逐行核对第 12 节对照表(测试存在、真的执行、尾缀可 grep、真实入口未绕过改动点);②抽查判据绑定面——帧索引序列是否真从 process_frame 发射点记录、对拍拓扑两形态是否同构可比、V3 行判据是否钉在运行态提交面(保持绿证据,非终态兜底);③核对现状复核证据在案(T0 两面实跑绿读数+停卡呈报三重证据引注〔215da315 提交信息〕——前提消化链条完整,缺 T0 复核读数=审计不过);④核对行为改动零处——diff 中任何行为改动(含收尾四语句语义漂移)=审计不过,收尾迁移的等价性由对拍 V2 运行态序列+V5 报告逐字承载;⑤检查测试削弱(对照第 16 节零申报)与内部字段直查受控例外(本批零直查——新形态 stats 经报告公开面观测)。
合并前由未参与实现的会话或子代理执行;报告落 `reports/iter<N>-验收审计-<日期>.md`,未过不得合并。审计至少做五件事:①逐行核对第 12 节对照表(测试存在、真的执行、尾缀可 grep、真实入口未绕过改动点);②抽查判据绑定面——帧索引序列是否真从 process_frame 发射点记录、对拍拓扑两形态是否同构可比、V3 行判据是否钉在运行态提交面而非终态兜底;③核对 T0 定谳证据在案(改动前 wd8/probe 红读数——潜伏红链条完整,缺 T0 读数=审计不过);④核对行为改动唯一性——diff 中除 V3 外无第二处行为改动;⑤检查测试削弱(对照第 16 节零申报)与内部字段直查受控例外(本批零直查——新形态 stats 经报告公开面观测)。
## 18. MC/数值与 RNG 零触碰声明
+5 -5
View File
@@ -5,8 +5,8 @@
## 当前位(只回答「现在该做什么」;三行内随批次换代,禁止在此扩写成状态快照)
- **阶段**:M3——Alpha 版本的完整实现(2026-10-09 立规:未明令宣布完成前停留 M3,迭代只排 M3 需求,M4 不排期;根目录 stage.txt 为旧阶段开关仅存档)。
- **迭代位**:迭代 1–43 全部已完成——迭代 43(战斗一屏样张批)2026-10-30 合回(合并提交 3b66d1a6),同日完成批后收尾(反哺点 13 条处置闭合:落文四处 art-bible v3.23/art-prompts v2.10/战斗表现 v1.2/test-conventions 旧句清偿,消解三条,移交待办清单两条目;产出物与两卡需求卡片退役;方向稿目检登记体验验证清单 ㊶)。迭代 42 收尾记录见 git 提交 7ec2af58。
- **下一步**:迭代 44 待开工(组合根注册面+战斗编排归约+测速循环三卡,编队前置=迭代 42 合回已满足)——开工前宜裁 PARITY 对拍漂移查因/重锚(闸 11 触面)与迭代 43 批后待裁三点(待办清单在册)。待编池:营地交互重做卡⑤(排卡④之后)、卡④(已退回美术设计段,美术设计闭合后重立卡)、冒险前资源配比预设(冻结备选)、迭代 30 原卡②③(2026-10-17 已剔出迭代 30 规划、退回待设计,待美术/设计定稿后重新立卡)。批后可裁事项:gdstyle 基线 refresh 收账随零豁免各批合回一次人工 `--refresh --force`(待办清单在册)。
- **迭代位**:迭代 1–41 全部已完成——迭代 41 两卡(核心数据面结构收敛批+呈现层结构收敛批)2026-10-29 合回(合并提交 eb9bb537,测试特许移交+用户裁直接合回),同日完成批后收尾(反哺点落文+产出物退役+账面换代)。
- **下一步**:待编池:营地交互重做卡⑤(排卡④之后)、卡④(已退回美术设计段,美术设计闭合后重立卡)、冒险前资源配比预设(冻结备选)、迭代 30 原卡②③(2026-10-17 已剔出迭代 30 规划、退回待设计,待美术/设计定稿后重新立卡)。批后可裁事项:gdstyle 基线 152 死键 refresh 清账(`python tools/gdstyle_gate.py --refresh`,待办清单在册)——用户发令时机。
## 里程碑时间线
@@ -75,15 +75,15 @@
| 迭代 39 | 已完成(2026-10-27) | 已退役(git 历史可考) | 2026-10-26 | ≈10.15 个工作日〔用户特批 2026-10-26 超 10 个工作日〕 | 已退役(git 历史可考) |
| 迭代 40 | 已完成(2026-10-28) | 已退役(git 历史可考) | 2026-10-27 | ≈6.375 个工作日 | 已退役(git 历史可考) |
| 迭代 41 | 已完成(2026-10-29) | 已退役(git 历史可考) | 2026-10-27 | ≈13.15 个工作日〔用户特批 2026-10-27 超 10 个工作日〕 | 已退役(git 历史可考) |
| 迭代 42 | 已完成(2026-10-29) | 已退役(git 历史可考) | 2026-10-29 | ≈5.375 个工作日 | 已退役(git 历史可考) |
| 迭代 43 | 已完成(2026-10-30) | 已退役(git 历史可考) | 2026-10-29 | ≈6.5 个工作日 | 已退役(git 历史可考) |
| 迭代 42 | 进行中 | reports/需求卡片-gdstyle尾波清偿批-2026-10-29.md<br>reports/需求卡片-level_runner商店委托层拆除-2026-10-29.md | 2026-10-29 | ≈5.375 个工作日 | — |
| 迭代 43 | 待开工 | reports/需求卡片-战斗一屏样张批-2026-10-28.md | 2026-10-29 | ≈6.5 个工作日 | — |
| 迭代 44 | 待开工 | reports/需求卡片-组合根注册面批-2026-10-29.md<br>reports/需求卡片-战斗编排归约批-2026-10-29.md<br>reports/需求卡片-测速循环批-2026-10-29.md | 2026-10-29 | ≈7.875 个工作日 | — |
| 迭代 45 | 待开工 | reports/需求卡片-呈现层状态件批-2026-10-29.md<br>reports/需求卡片-gdstyle尾波补件批-2026-10-29.md | 2026-10-29 | ≈7 个工作日 | — |
- 迭代 38 收口为带红合并〔用户特批 2026-10-26〕:09 静态扫描 S13 gdstyle 恶化 2 条经批准记入基线存量红(git 提交 c4c69701),卡② 停卡退上游,处方已于 2026-10-26 修订定稿 v1.3(git 提交 b3ea7532),随「gdstyle 存量债全量清偿」条目波 7 待编排。
- 迭代 39 收口为带红合并〔用户特批 2026-10-27〕:02 EV 锚 goblin 0.9148∉[1.00,1.20](触发器批有意行为改动,fp f74b9a48ef4a)已随 EV 锚重锚批换带归绿(git 提交 ba5105aa);08d 生涯.md:55 行号漂移已随 2026-10-28 批后收尾反哺落文修复归绿。三卡拆分后终局 gdstyle 恶化 2 条(battle_orchestrator max-file-length、level_map_screen max-class-variables)归迭代 40 卡①还债与迭代 41 存量债序列。
- 迭代 40 收口为用户特许免闸直合〔2026-10-28〕:全量域验收回执未取得(回执环节断于用户特许,验收审计报告如实记档不作绿报;独立审计结论=通过,F1 一行修 iter40 8a319369 先行入库)。卡① 契约 2 宿主行数残余债(实测 1036 行,合并适配带入 36 行)经用户裁登记归后续批清偿(待办清单「战斗域结构深拆批」条目承载);09 存量红剩 level_map_screen 类变量一条归迭代 41 卡③。
- 迭代 41 收口为测试特许移交+用户裁直接合回〔2026-10-28/2026-10-29〕:全部套件与闸停跑(gdstyle 结构闸除外——开发期读数反馈在跑),验收断言、golden 对拍与独立验收审计一并移交用户执行,2026-10-29 用户裁直接合回(合并提交 eb9bb537);golden 改前锚未建让步与四项实机目检已迁体验验证清单 ㊵;gdstyle 基线 152 死键 refresh 已执行(2026-10-29 人工重锚 168→16 键,git 提交 fafb4fee)。
- 迭代 41 收口为测试特许移交+用户裁直接合回〔2026-10-28/2026-10-29〕:全部套件与闸停跑(gdstyle 结构闸除外——开发期读数反馈在跑),验收断言、golden 对拍与独立验收审计一并移交用户执行,2026-10-29 用户裁直接合回(合并提交 eb9bb537);golden 改前锚未建让步与四项实机目检已迁体验验证清单 ㊵;gdstyle 基线 152 死键 refresh 待用户发令(待办清单在册)。
- 历史行的「—」= 该列事实不可考:迭代 1~8 早于需求卡片制;编排时点与预计周期不再回溯,细节查 git 提交史。
- 「已退役(git 历史可考)」= 需求卡片或实现报告曾存在,已按「证据只留最新」退役;已完成迭代的实现报告列只填 reports/ 现存路径或本保留字,不填「—」。
- 新迭代行的编排与各列判据见 doc-conventions §12.2 与 iter-plan 技能,由流水线闸(`tools/pipeline_gate.py`)机检。
+203
View File
@@ -0,0 +1,203 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""parity_anchor.py — 「逐位一致对拍」锚工具(架构重构前后逐位一致验收;原需求卡已按「吸收即死」退役,凭据见 git 提交史)
载体 = adventure_monte_carlo.gd 同种子批量完整局(SceneTree headless,自持 ConfigDB,零渲染)。
三变体钉死(种子面错开,覆盖 base / 同调账 / 商店-GP 账三类 T2/T3 重构面):
base : -- 20 71000 both min off off off
attune : -- 20 72000 both min on off off
shop : -- 20 73000 both min off on off
锚双面:
A. RUNSUM 逐行块(stdout 局级摘要面,字符串列表原样入库 anchor.json——红灯时可打印首条差异行);
B. playtest JSONL 全事件流(剔除带墙钟的 session/diagnosis 两类行;文件名本身带时间戳不入比对面)。
比对口径 = 字节级逐位一致(同引擎二进制+同种子确定性,冻结前已实测两跑全同)。
原始面不入库(体量 MB 级):红灯取证用 dump 模式对重构前后各落一份原始数据再 diff——
旧提交随时可重生成同字节原始面(确定性保证),锚只存 sha256 清单。
用法:
python tools/parity_anchor.py freeze # 冻结锚(重构前基线,写入 game/tests/evidence/parity/anchor.json)
python tools/parity_anchor.py check # 对拍:重跑同参批,与锚逐位比对(全同退出 0,差异退出 1 并打印首差异)
python tools/parity_anchor.py dump --out DIR # 只跑批落原始面到 DIR(红灯取证)
可选 --variants base,attune,shop / --quick(只跑 base)
"""
import argparse
import hashlib
import json
import re
import subprocess
import sys
import time
from pathlib import Path
from godot_user_dir import resolve_user_dir
ROOT = Path(__file__).resolve().parent.parent
GODOT = r"D:\code\godot\Godot_v4.7.2-stable_win64_console.exe"
GAME_DIR = ROOT / "game"
ANCHOR_PATH = ROOT / "game" / "tests" / "evidence" / "parity" / "anchor.json"
USER_LOGS = resolve_user_dir(ROOT) / "logs"
# 变体表:名字 → (seed0, attune, shop)。(N=20/策略 both/route min/feat off 全变体钉死。)
VARIANTS = {
"base": (71000, "off", "off"),
"attune": (72000, "on", "off"),
"shop": (73000, "off", "on"),
}
N_RUNS = 20
BATCH_RE = re.compile(r"^BATCH (\w+) N=(\d+) 终局计数=(.*) 日志=user://logs/(playtest_\d+_\S+\.jsonl)\s*$")
TS_ROW_KINDS = ("session", "diagnosis") # 带墙钟 ts 的两类行——剔出比对面
def run_variant(name: str, seed0: int, attune: str, shop: str, timeout: int = 560):
"""跑一个变体批,返回 (runsum_lines, {policy: jsonl绝对路径})。"""
before = set(p.name for p in USER_LOGS.glob("playtest_*.jsonl")) if USER_LOGS.is_dir() else set()
cmd = [GODOT, "--headless", "--path", str(GAME_DIR), "--script",
"res://sim/adventure_monte_carlo.gd", "--",
str(N_RUNS), str(seed0), "both", "min", attune, shop, "off"]
proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8",
errors="replace", timeout=timeout)
out = (proc.stdout or "") + (proc.stderr or "")
runsum = [ln for ln in out.splitlines() if ln.startswith("RUNSUM ")]
logs = {}
for ln in out.splitlines():
m = BATCH_RE.match(ln)
if m:
logs[m.group(1)] = m.group(4)
after = set(p.name for p in USER_LOGS.glob("playtest_*.jsonl")) if USER_LOGS.is_dir() else set()
fresh = after - before
resolved = {}
for policy, fname in logs.items():
p = USER_LOGS / fname
if p.is_file():
resolved[policy] = p
else:
cand = [f for f in fresh if f.startswith("playtest_%d_" % seed0)]
if len(cand) == 1:
resolved[policy] = USER_LOGS / cand[0]
else:
print("[parity] FAIL:变体 %s 的 %s 日志文件定位失败(fresh=%s)" % (name, policy, cand))
sys.exit(2)
expected_policies = 2
if len(runsum) != N_RUNS * expected_policies or len(resolved) != expected_policies:
print("[parity] FAIL:变体 %s 产出不完整(RUNSUM %d 行 / 日志 %d 份)" % (name, len(runsum), len(resolved)))
print(out[-3000:])
sys.exit(2)
return runsum, resolved
def filter_jsonl(path: Path) -> bytes:
"""剔除带 ts 行,保留原始字节行序(bit-exact;不做任何再序列化)。"""
kept = []
with open(path, "rb") as f:
for raw in f.read().split(b"\n"):
if not raw.strip():
continue
head = raw[:48]
if any(('{"kind":"%s"' % k).encode() in head for k in TS_ROW_KINDS):
continue
kept.append(raw)
return b"\n".join(kept) + b"\n"
def collect(variants, quick=False):
if quick:
variants = [v for v in variants if v == "base"] or ["base"]
data = {}
for v in variants:
seed0, attune, shop = VARIANTS[v]
t0 = time.time()
print("[parity] 跑批 %s(N=%d×2 策略,seed0=%d,attune=%s,shop=%s)…" % (v, N_RUNS, seed0, attune, shop))
runsum, logs = run_variant(v, seed0, attune, shop)
faces = {"runsum": runsum, "jsonl": {}}
for policy in sorted(logs):
body = filter_jsonl(logs[policy])
faces["jsonl"][policy] = {
"sha256": hashlib.sha256(body).hexdigest(),
"rows": body.count(b"\n"),
}
data[v] = faces
print("[parity] %s 完成(%.0fs,RUNSUM %d 行,JSONL %s)" %
(v, time.time() - t0, len(runsum),
", ".join("%s=%s" % (p, faces["jsonl"][p]["sha256"][:12]) for p in sorted(faces["jsonl"]))))
return data
def dump_variant_raw(variants, outdir: Path):
outdir.mkdir(parents=True, exist_ok=True)
for v in variants:
seed0, attune, shop = VARIANTS[v]
runsum, logs = run_variant(v, seed0, attune, shop)
(outdir / ("%s_runsum.txt" % v)).write_text("\n".join(runsum) + "\n", encoding="utf-8", newline="\n")
for policy, p in logs.items():
(outdir / ("%s_%s.jsonl" % (v, policy))).write_bytes(filter_jsonl(p))
print("[parity] dump %s → %s" % (v, outdir))
def main():
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
ap = argparse.ArgumentParser(description="迭代27重构逐位一致对拍锚")
ap.add_argument("mode", choices=["freeze", "check", "dump"])
ap.add_argument("--variants", default="base,attune,shop")
ap.add_argument("--quick", action="store_true", help="只跑 base 变体")
ap.add_argument("--out", default="", help="dump 模式输出目录")
args = ap.parse_args()
variants = [v.strip() for v in args.variants.split(",") if v.strip() in VARIANTS]
if not variants:
print("[parity] FAIL:无有效变体(可选 %s)" % list(VARIANTS))
return 2
if args.mode == "dump":
dump_variant_raw(variants, Path(args.out if args.out else str(ROOT / "tools" / "parity_dump")))
return 0
data = collect(variants, quick=args.quick)
if args.mode == "freeze":
ANCHOR_PATH.parent.mkdir(parents=True, exist_ok=True)
anchor = {"variants": {v: data[v] for v in variants}}
ANCHOR_PATH.write_text(json.dumps(anchor, ensure_ascii=False, indent=1), encoding="utf-8", newline="\n")
print("[parity] 锚已冻结 → %s(变体 %s)" % (ANCHOR_PATH, variants))
return 0
# check
if not ANCHOR_PATH.is_file():
print("[parity] FAIL:锚不存在(先 freeze)→ %s" % ANCHOR_PATH)
return 2
anchor = json.loads(ANCHOR_PATH.read_text(encoding="utf-8"))
bad = 0
for v in variants:
if v not in anchor["variants"]:
print("[parity] FAIL:锚无变体 %s(锚含 %s)" % (v, list(anchor["variants"])))
bad += 1
continue
old = anchor["variants"][v]
new = data[v]
if old["runsum"] != new["runsum"]:
bad += 1
print("[parity] ✗ %s RUNSUM 漂移(%d 行 vs %d 行)" % (v, len(old["runsum"]), len(new["runsum"])))
for i, (a, b) in enumerate(zip(old["runsum"], new["runsum"])):
if a != b:
print("[parity] 首差异第 %d 行:" % i)
print("[parity] 锚: %s" % a[:600])
print("[parity] 今: %s" % b[:600])
break
for policy in sorted(old["jsonl"]):
o, n = old["jsonl"][policy], new["jsonl"].get(policy)
if n is None:
bad += 1
print("[parity] ✗ %s/%s 本次缺日志" % (v, policy))
elif o["sha256"] != n["sha256"]:
bad += 1
print("[parity] ✗ %s/%s JSONL 逐位漂移(锚 %s… 行数 %d vs 今 %s… 行数 %d)" %
(v, policy, o["sha256"][:12], o["rows"], n["sha256"][:12], n["rows"]))
if bad:
print("[parity] 逐位一致对拍 ✗ 失败 %d 项——该波回炉(V1 红线)" % bad)
return 1
print("[parity] 逐位一致对拍 ✓ 全同(变体 %s,RUNSUM+JSONL 双面逐位一致)" % variants)
return 0
if __name__ == "__main__":
sys.exit(main())
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""V12 同种子复放比对器(卡② 效果引擎无状态化批):解析两次 MC 采集的 RUNSUM 行按 seed 配对判读。
两判据模式——parity:整条记录逐位相等(off 组,未受影响路径须透明);
input-parity:输入面八字段 + 顶层字段集恒等、种子集与局数一致(formed/rules 组,输出面允许 RNG 级联位移)。
退出码 0 绿 / 1 红 / 2 用法或解析错。字段集从基线文件推导,禁硬编码。"""
import sys, json, argparse
INPUT_KEYS = ["policy", "seed", "atk_pct", "hp_pct", "route", "attune", "equipped", "feat_mode"]
def load(path):
recs = {}
with open(path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line.startswith("RUNSUM "):
continue
try:
d = json.loads(line[len("RUNSUM "):])
except json.JSONDecodeError:
print("解析失败行:", line[:80])
sys.exit(2)
recs[d.get("seed")] = d
return recs
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--mode", choices=["parity", "input-parity"], required=True)
ap.add_argument("--base", required=True)
ap.add_argument("--post", required=True)
ap.add_argument("--expect", type=int, required=True)
a = ap.parse_args()
base, post = load(a.base), load(a.post)
if len(base) != a.expect or len(post) != a.expect:
print("局数不符:base=%d post=%d expect=%d" % (len(base), len(post), a.expect))
return 1
if set(base) != set(post):
print("种子集不一致")
return 1
red = False
if a.mode == "parity":
diff = [s for s in base if base[s] != post[s]]
if diff:
red = True
print("off 组逐位差异 %d 局: %s" % (len(diff), diff[:10]))
for s in diff[:3]:
k = [kk for kk in base[s] if base[s].get(kk) != post[s].get(kk)]
print(" seed", s, "差异字段:", k)
else:
bkeys = set(next(iter(base.values())).keys())
pkeys = set(next(iter(post.values())).keys())
if bkeys != pkeys:
red = True
print("顶层字段集不一致 base-p=%s post-b=%s" % (bkeys - pkeys, pkeys - bkeys))
for s in base:
for kk in INPUT_KEYS:
if base[s].get(kk) != post[s].get(kk):
red = True
print("输入面字段 %s 在 seed %s 不一致: %r vs %r" % (kk, s, base[s].get(kk), post[s].get(kk)))
# levels_cleared 直方图仅报告(方向不作判据)
lc = post[s].get("levels_cleared") if post else None
print("判读:%s" % ("FAIL" if red else "PASS"))
return 1 if red else 0
if __name__ == "__main__":
sys.exit(main())
-16
View File
@@ -18,10 +18,6 @@
# ④ 基准档 1280 单档(Q4)。1920 档注记:16:9 全屏下 stretch=canvas_items/aspect=keep 等比
# 1.35×+左右黑边各 96px,构图与基准档一致(分辨率案甲)——分辨率维度不重复建闸。
# (1920 布局机检面现无承载,待补。)
# ⑤ 脚本错误扫描(三硬闸之二):shot/fx/board 三段 Godot 日志逐段过 tools/error_scan.py,
# 命中 SCRIPT ERROR/Parse Error 即红(标记与 run_smoke.py 同源);python 工具自身输出不扫。
# 此类错误不改退出码、不计失败数,且会中断所在函数的剩余断言——只能由外部扫日志。
# 新增日志段必须同步加扫描调用,漏加=该段静默错误漏网。
# 过程产物纪律(Q2):一切截图 PNG 落 mktemp 临时区,闸毕即弃——任何过程 PNG 禁入 git;
# 例外=game/tests/golden/ 基线(测试资产入库,.gitignore 已核不挡)。
# 窗口化运行会短暂弹 Godot 窗口(本机可开窗口径);禁改 --headless 跑本闸
@@ -45,15 +41,6 @@ kill_orphans() {
local p; for p in $(orphans); do taskkill //F //PID "$p" >/dev/null 2>&1; done
}
scan_log() { # scan_log <段名> <日志>——脚本错误扫描;日志不可读(error_scan exit 2)同判红
local name="$1" log="$2"
if ! python "$REPO/tools/error_scan.py" "$log" >"$TMP/err_scan.txt" 2>&1; then
echo "[render_gate] FAIL — $name 脚本错误扫描未通过(命中或日志不可读):"
sed -n '1,6p' "$TMP/err_scan.txt" | sed 's/^/ /'
_rc=1
fi
}
# 冷检出刷导入(.godot 不入 git,fresh worktree 首跑一次性付)
if [ ! -f "$GAME/.godot/uid_cache.bin" ]; then
echo "[render_gate] 冷检出:editor --quit 刷导入(一次性,可到 1-2 分钟)"
@@ -79,7 +66,6 @@ if [ "$_rc" -eq 0 ]; then
_rc="${PIPESTATUS[0]}"
[ "$_rc" -ne 0 ] && echo "[render_gate] FAIL — shot_checker A-D 断言存在红(S1 停机点:首跑存量 FAIL 全量呈报登记待办清单)"
fi
scan_log "shot_probe" "$TMP/shot.log"
echo "======== fx_shot_probe 三时刻取证(Q7) ========"
FX="$TMP/fxshots"
@@ -91,7 +77,6 @@ if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then
elif [ "$st" -ne 0 ]; then
echo "[render_gate] FAIL — fx_shot_probe 退出码=$st(取证失败=闸红)"; _rc=1
fi
scan_log "fx_shot_probe" "$TMP/fx.log"
fx_evidence() { # 取证双检:模式文件存在且非零字节(零字节/缺失任一即 FAIL——Q7)
local pat="$1" min="$2" n=0 f
for f in "$FX"/$pat; do
@@ -116,7 +101,6 @@ if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then
elif [ "$st" -ne 0 ]; then
echo "[render_gate] FAIL — board_shot_probe 退出码=$st(取证失败=闸红)"; _rc=1
fi
scan_log "board_shot_probe" "$TMP/bd.log"
for b in board_L1_dungeon.png board_L2_underdark.png board_L3_deep_underdark.png board_meta.json; do
if [ ! -s "$BD/$b" ]; then echo "[render_gate] FAIL — 取证缺失/零字节: $b"; _rc=1; fi
done
+57 -77
View File
@@ -11,11 +11,11 @@
# 回执即作废须重跑;跑闸时若代码面还带着未提交改动,回执会标 dirty_game,合回闸拒绝采信。
#
# 用法:tools/run_all_gates.sh [--code|--docs|--quick]
# --code 只跑代码域闸(00 冒烟测试/01 系统测试/02 单元与集成测试/03 场景探针/
# 05 渲染闸/09 静态扫描+10 熵增闸)
# --code 只跑代码域闸(00 冒烟测试/01 系统测试/02 单元与集成测试/03 探针 --click/
# 04 探针 --full/05 渲染闸/09 静态扫描/11 回放确定性对拍+10 熵增闸)
# ——本批改动含 game/ 下非 .md 的行为载体(代码/测试脚本/探针/场景/数据表/素材)时用。
# 关联档选闸(2026-10-29 用户裁,推广自 2026-10-28 影响面选闸):00 冒烟/01 系统测试/
# 09 静态扫描三件保险恒跑不降;03 场景探针与 05 渲染闸维持影响面口径(本批 game/ 改动
# 09 静态扫描三件保险恒跑不降;03/04 探针与 05 渲染闸维持影响面口径(本批 game/ 改动
# 全在测量面 game/tests、game/sim 时记 SKIP);02 单元与集成改「选套件跑」——套件集=
# 需求卡片申报 ∪ diff 推导并集(算法与套件族映射表见脚本中部),横切面(公共件/数据表/
# autoload/数值·存档·RNG·结算触面)回落全套件;回执 scope 字段记 full|related。
@@ -35,7 +35,7 @@
# 基线红 + 现红且清单未变 → 「存量红·对基线零新增」,不判失败(但会在回执里留名)
# 基线红 + 现红但清单新增 → 红(新增红项)
# 任意闸 exit 与基线不符 → 红
# 本次未跑的闸(--quick 跳过/影响面选闸 SKIP)→ 不比闸况,只记进回执 not_run
# 本次未跑的闸(--quick 跳过/条件闸件未触面)→ 不比闸况,只记进回执 not_run
# 基线文件首次自动生成(把当次结果当基线),此后每次跑批与之比。
# 分域跑批只比本域跑过的闸——域外闸不入 NAMES,不产生「基线绿 → 现红」的假变化。
#
@@ -157,8 +157,8 @@ fi
declare -a NAMES=() EXITS=() LOGS=()
FAILED=0
# 横切触面表——关联档选闸的横切回落判定清单(数值·存档·RNG·结算触面;单点定义,勿在别处复制)。
CROSSCUT_TOUCH_FACES="
# PARITY 触面表——gate 11 数值·存档·RNG·结算触面判定与关联档横切判定共用同一清单(单点定义,勿在别处复制)。
PARITY_TOUCH_FACES="
game/data/tables/
game/core/rng_service.gd
game/core/save_manager.gd
@@ -173,7 +173,7 @@ game/sim/adventure_monte_carlo.gd
# ── 套件族映射表(关联档·推导半边的唯一事实源)──
# game/ 代码面文件 → tests/ 套件族词(--only 子串词,词=套件目录名)。机械近似:同族内
# 套件覆盖面仍可能宽于被测文件,保守方向=宁多勿漏。横切判定(公共件前缀/project.godot/
# 横切触面表)先于本表执行,故表中不收 damage_math/damage_controller/battle_orchestrator/
# PARITY 触面表)先于本表执行,故表中不收 damage_math/damage_controller/battle_orchestrator/
# settlement/** 等数值·存档·RNG·结算触面文件——它们恒走横切回落全量。映射不到的文件返回空=横切处理。
suite_terms_for() { # suite_terms_for <仓库相对路径> → stdout: 套件族词(空格分隔,可空)
case "$1" in
@@ -217,8 +217,8 @@ suite_terms_for() { # suite_terms_for <仓库相对路径> → stdout: 套件
}
# ── 关联档选闸(2026-10-29 用户裁,推广自 2026-10-28 影响面选闸)──
# 三件保险恒跑不降:00 冒烟/01 系统测试/09 静态扫描(10 熵增闸照旧两域都跑)。
# 03 场景探针与 05 渲染闸维持影响面口径:本批 diff 相对 master 分叉点的 game/ 改动若全部落在
# 三件保险恒跑不降:00 冒烟/01 系统测试/09 静态扫描(10 熵增闸照旧两域都跑,11 数值·存档·RNG·结算触面判定照旧)。
# 03/04 探针与 05 渲染闸维持影响面口径:本批 diff 相对 master 分叉点的 game/ 改动若全部落在
# 测量面(game/tests/**、game/sim/**——测试与模拟驱动件,不触场景树与渲染输出)则记 SKIP。
# 02 单元与集成改「选套件跑」:套件集=申报 ∪ 推导 取并集——
# 申报:在册需求卡片(本批 diff 触及的 reports/需求卡片*.md ∪ roadmap「进行中」迭代行登记的
@@ -228,7 +228,7 @@ suite_terms_for() { # suite_terms_for <仓库相对路径> → stdout: 套件
# 映射不到的文件一律按横切处理。
# 横切回落全量:diff 命中 game/data/tables/**、公共件前缀(config_db*/rng_service*/
# save_manager*/meta_progression*/main*——main* 覆盖组合根拆件与主场景)、
# autoload 注册面(game/project.godot)、或下方横切触面表任一 → 02 跑全部套件。
# autoload 注册面(game/project.godot)、或下方 PARITY 触面表任一 → 02 跑全部套件。
# 测量面批(game/ 改动全在 tests/sim):02 照旧全套件跑(不加过滤)。
# 回执 scope 字段:full|related(02 跑子集=related);映射表只在本脚本一处定义。
NONBEHAVIOR=0
@@ -241,7 +241,7 @@ if in_domain code; then
_behavior="$(echo "$_gfiles" | grep -vE '^(game/tests/|game/sim/)' || true)"
if [ -n "$_gfiles" ] && [ -z "$_behavior" ]; then
NONBEHAVIOR=1
echo "[gates] 影响面选闸:game/ 改动全在测量面(tests/sim)——03 场景探针/05 渲染闸 → SKIP(00/01/09 三件保险恒跑)"
echo "[gates] 影响面选闸:game/ 改动全在测量面(tests/sim)——03/04 探针/05 渲染闸 → SKIP(00/01/09 三件保险恒跑)"
fi
# ── 02 选套件推导(只对行为面代码文件做——测量面文件不进推导,其验证就是 02/09 自身)──
_codefiles="$(echo "$_gfiles" | grep -viE '\.md$' | grep -vE '^(game/tests/|game/sim/)' || true)"
@@ -257,7 +257,7 @@ if in_domain code; then
CROSSCUT=1; break ;;
esac
[ "$_f" = "game/project.godot" ] && { CROSSCUT=1; break; } # autoload 注册面
for _pat in $CROSSCUT_TOUCH_FACES; do
for _pat in $PARITY_TOUCH_FACES; do
case "$_f" in
"$_pat"*) CROSSCUT=1; break 2 ;;
esac
@@ -300,24 +300,6 @@ if in_domain code; then
fi
fi
# ── 代码域起手自检(闸承载:只省烧闸,不做合回判定——真判定在 00–11 与合回 guard)──
if in_domain code; then
# worktree 素材同步:素材走 gitignore 不入 git,worktree 冷检出缺件=呈现断言红的环境成因;
# 主工作区直跑=脚本内自动无操作。
bash "$REPO/tools/sync_worktree_assets.sh"
# 熵增预检:垃圾目录/越界文件先于长跑批暴露(熵增闸在批末,撞上才红=整轮白烧)。
if ! "$PY" "$REPO/tools/repo_structure_audit.py" >"$STAMP_DIR/entropy_preflight.log" 2>&1; then
echo "[gates] 起手自检红:熵增预检不过——顶层目录越界/根文件异动,先清理再跑闸(日志 $STAMP_DIR/entropy_preflight.log)"
grep -aE "越界|✗|违例" "$STAMP_DIR/entropy_preflight.log" | tail -5 | sed 's/^/ | /'
exit 1
fi
# master 推进预警(worktree 才会触发):master 已越过本分支=合回 guard 第 6 况必拦,
# 先并 master 再出闸,省一轮白烧;本闸回执照出(开发期跑批不受阻)。
if [ -f "$RECEIPT" ] && ! git -C "$REPO" merge-base --is-ancestor master HEAD 2>/dev/null; then
echo "[gates] 警告:master 已推进至本分支之外——本闸回执照出,但合回前须并 master 重出闸。"
fi
fi
echo "闸机跑批 · 域=$DOMAIN · 仓库 $REPO"
echo "起跑 commit: $(git -C "$REPO" rev-parse --short HEAD 2>/dev/null || echo '非git目录')"
echo "python: $PY"
@@ -340,58 +322,24 @@ gate() { # gate <名称> <日志名> <命令...>
return 0
}
gate_unit() { # run_all 是 --script 模式,退出码为唯一判据;分片态=多 Godot 进程各跑一片
gate_unit() { # run_all 是 --script 模式,退出码为唯一判据
local name="02 单元与集成测试"
# 关联档选跑:把套件词列表经 --only= 传给 run_all(闸机来源标记 --only-source=gates,
# 入口据此打关联档横幅、不落「仅供开发期」免责句);全量态不传任何选跑参数。
local -a _user_args=()
if [ "$SCOPE" = related ] && [ -n "$ONLY_TERMS" ]; then
_user_args+=("--only=${ONLY_TERMS// /,}" "--only-source=gates")
_user_args=(-- "--only=${ONLY_TERMS// /,}" --only-source=gates)
fi
local _shards="${GATE_SHARDS:-4}"
echo ""
echo "════════════════════════════════════════════"
echo " $name(--script 模式,退出码为唯一判据)"
if [ "$SCOPE" = related ] && [ -n "$ONLY_TERMS" ]; then
echo " 关联档选跑:--only=$ONLY_TERMS"
fi
if [ "$_shards" -gt 1 ]; then
echo " 分片并行:$_shards 进程(GATE_SHARDS 可调,=1 回落单进程原路径)"
fi
echo "════════════════════════════════════════════"
local rc=0
if [ "$_shards" -le 1 ]; then
local -a _cmd=(--headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd)
[ ${#_user_args[@]} -gt 0 ] && _cmd+=(-- "${_user_args[@]}")
( cd "$REPO" && "$GODOT" "${_cmd[@]}" ) \
>"$STAMP_DIR/unit.log" 2>&1
rc=$?
else
# 分片并行:串行道套件由 run_all 内部分配钉第 1 片(RNG 日志会话跨片互踩,见其头注);
# 各片独立日志,退出码聚合(任一片非 0=闸红);日志按片序合并供错误扫描与指纹取材
#(指纹只取 FAIL 行排序集合、绿闸记 green,片序与合并不改变指纹)。
local -a _pids=()
local k
for k in $(seq 1 "$_shards"); do
( cd "$REPO" && "$GODOT" --headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd \
-- "${_user_args[@]}" "--shard=$k" "--shard-count=$_shards" ) \
>"$STAMP_DIR/unit_shard_$k.log" 2>&1 &
_pids+=($!)
done
local _shard_rc _any=0
for k in $(seq 1 "$_shards"); do
wait "${_pids[$((k - 1))]}"
_shard_rc=$?
echo "[run_all] 分片 $k/$_shards 退出码 $_shard_rc"
[ "$_shard_rc" -ne 0 ] && _any=1
done
[ "$_any" -ne 0 ] && rc=1
: >"$STAMP_DIR/unit.log"
for k in $(seq 1 "$_shards"); do
echo "── unit 分片 $k/$_shards ──" >>"$STAMP_DIR/unit.log"
cat "$STAMP_DIR/unit_shard_$k.log" >>"$STAMP_DIR/unit.log"
done
fi
( cd "$REPO" && "$GODOT" --headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd "${_user_args[@]}" ) \
>"$STAMP_DIR/unit.log" 2>&1
local rc=$?
# 三硬闸之二(脚本错误扫描):子套件的 SCRIPT ERROR/Parse Error 只写 stderr,入口内收不到,
# 只能在编排层扫本闸日志。命中即红——此类错误不改退出码、且会中断所在函数的剩余断言。
if [ "$rc" -eq 0 ] && ! "$PY" "$REPO/tools/error_scan.py" "$STAMP_DIR/unit.log" >>"$STAMP_DIR/unit.log" 2>&1; then
@@ -412,12 +360,15 @@ gate "00 冒烟测试" "smoke_boot.log" "$PY" "$REPO/tools/run_smoke.py"
gate "01 系统测试" "system_test.log" "$PY" "$REPO/tools/run_system_test.py"
# ── 2. 单元与集成测试 ──
gate_unit
# ── 3. 场景探针(走查+断言十件一次跑齐;iter27 漏跑断言段事故后固化为无条件整闸)──
# ── 3. 场景探针 · 行为走查段 ──
# ── 4. 场景探针 · 断言段(iter27 漏跑的就是这一段)──
if [ $NONBEHAVIOR -eq 1 ]; then
NAMES+=("03 场景探针"); EXITS+=("SKIP"); LOGS+=("")
echo ""; echo "[gates] 03 场景探针 → SKIP(影响面选闸:测量面改动不触场景树与探针断言面)"
NAMES+=("03 探针 --click"); EXITS+=("SKIP"); LOGS+=("")
NAMES+=("04 探针 --full"); EXITS+=("SKIP"); LOGS+=("")
echo ""; echo "[gates] 03/04 探针 → SKIP(影响面选闸:测量面改动不触场景树与探针断言面)"
else
gate "03 场景探针" "probe.log" bash "$REPO/tools/probe_gate.sh" "$GAME" --click --full
gate "03 探针 --click" "probe_click.log" bash "$REPO/tools/probe_gate.sh" "$GAME" --click
gate "04 探针 --full" "probe_full.log" bash "$REPO/tools/probe_gate.sh" "$GAME" --full
fi
# ── 5. 渲染面(窗口化,依赖 PIL)──
if [ $QUICK -eq 1 ] || [ $NONBEHAVIOR -eq 1 ]; then
@@ -441,6 +392,35 @@ gate "08d 引用可核" "doc_ref.log" "$PY" "$REPO/tools/doc_ref_audit.py" --str
# ── 8e. 能力审计(GDD 纯度+能力清单与覆盖域表结构,判据=test-conventions §8.9+doc-conventions §5)──
gate "08e 能力审计" "capability.log" "$PY" "$REPO/tools/capability_audit.py" --strict
fi
# ── 6c. 回放确定性对拍(条件闸件;属代码域)──
if in_domain code; then
# 触面判定:本批 diff 相对 master 分叉点,命中 PARITY_TOUCH_FACES(脚本上部单点定义)之一即「触面」。
# 触面也**不自动跑**——对拍调的是 MC 脚本,属「MC/统计类跑批发起前须用户批准」红线管辖;
# 闸只负责每次跑批都把触面判定做一遍并显式要求批准,杜绝「该跑却没人记得」。
# 用户批准后设 PARITY_APPROVED=1 重跑本脚本即实跑三变体对拍。
_base="$(git -C "$REPO" merge-base master HEAD 2>/dev/null || true)"
_changed_files="$(git -C "$REPO" diff --name-only "${_base:-HEAD}" HEAD 2>/dev/null || true)"
_touch_hit=0
for _f in $_changed_files; do
for _pat in $PARITY_TOUCH_FACES; do
case "$_f" in
"$_pat"*) _touch_hit=1 ;;
esac
done
done
_parity_name="11 回放确定性对拍"
if [ "$_touch_hit" -eq 0 ]; then
NAMES+=("$_parity_name"); EXITS+=("SKIP"); LOGS+=("")
echo ""; echo "[gates] $_parity_name → SKIP(本批 diff 未触数值/存档/RNG/结算面)"
elif [ "${PARITY_APPROVED:-0}" = "1" ]; then
gate "$_parity_name" "parity.log" "$PY" "$REPO/tools/parity_anchor.py" check
else
NAMES+=("$_parity_name"); EXITS+=("NEED-APPROVAL"); LOGS+=("")
echo ""
echo "[gates] $_parity_name → 本批 diff 触及数值/存档/RNG/结算面,对拍需用户批准发起"
echo " (批准后设 PARITY_APPROVED=1 重跑本脚本即实跑;不得借机重锚)"
fi
fi
# ── 7. 静态扫描(代码域)+ 熵增闸(两域都跑:它查顶层目录/根文件/图像白名单/退役路径,
# 文档批新增或挪动 design/ 下的文件同样会绊它;纯文本扫描,秒级)──
@@ -491,9 +471,9 @@ def fingerprint(exit_code, log):
"""红项指纹:只取 exit!=0 时的 FAIL 关键行集合,忽略时序/耗时噪声
(超时挂死行的 pid=NNN 随跑批变化,归一化为 pid=N 后再入哈希——否则存量红指纹逐轮漂移)。
绿闸一律记 'green'——绿闸日志里的「零红」「全绿」等字样不是红项,采进来只会造噪声。"""
# SKIP 属「本闸未实跑」状态(--quick/影响面选闸),指纹一律记 skip——
# 没跑不是闸况变化,只有「未跑 → 跑出结果」或「绿 → 红」才算变化。
if exit_code in ("0", "SKIP"):
# SKIP / NEED-APPROVAL 都属「本闸未实跑」状态,指纹一律记 skip——
# 两者互变不是闸况变化(同是没跑),只有「未跑 → 跑出结果」或「绿 → 红」才算变化。
if exit_code in ("0", "SKIP", "NEED-APPROVAL"):
return "green" if exit_code == "0" else "skip"
if not log:
return "red-nolog"
@@ -534,7 +514,7 @@ for n in names:
if b["exit"] == c["exit"] and b["fp"] == c["fp"]:
continue
if c["fp"] == "skip":
# 本闸这次没跑(--quick 跳过/影响面选闸 SKIP)——没有结果可比,不判闸况变化。
# 本闸这次没跑(--quick 跳过/条件闸件未触面)——没有结果可比,不判闸况变化。
# SKIP 不得落进「基线绿 → 现红」分支——否则任何 --quick 回执都自带假红。
not_run.append(n)
continue
-54
View File
@@ -1,54 +0,0 @@
#!/usr/bin/env bash
# worktree 素材同步:主工作区 → 本 worktree 增量拷贝 gitignore 面(game/assets/art/ 终稿与 .import 边车)。
# 素材不入 git,worktree 冷检出与主区新收素材之间必然缺件——呈现/样张断言红的环境成因。
# 只在 worktree 生效(主工作区直跑=无操作);有新拷件或缺 .godot 时跑一次编辑器导入刷新
#(headless --script 模式不触发 import,素材在盘而 .ctex 缺位照样加载失败)。
# 用法:tools/sync_worktree_assets.sh(闸机 code 域起手自动调用;可单独手跑)
# 纪律:拷贝一律相对路径定位(在源根 cd 后按相对路径落盘)——绝对路径 --parents 会在
# 目标仓顶层误产盘符目录树(杂散目录即此误产,熵增闸判红)。
set -u
REPO="$(cd "$(dirname "$0")/.." && pwd)"
GITDIR="$(git -C "$REPO" rev-parse --git-dir 2>/dev/null)"
COMMON="$(git -C "$REPO" rev-parse --git-common-dir 2>/dev/null)"
if [ -z "$GITDIR" ] || [ -z "$COMMON" ] || [ "$GITDIR" = "$COMMON" ]; then
echo "[assets] 主工作区:无同步面"
exit 0
fi
MAIN="$(git -C "$REPO" worktree list --porcelain 2>/dev/null | sed -n 's/^worktree //p' | head -1)"
if [ -z "$MAIN" ]; then
echo "[assets] 取不到主工作区路径(git worktree list 为空),跳过同步"
exit 0
fi
MAIN="$(cd "$MAIN" && pwd)"
[ "$MAIN" = "$REPO" ] && exit 0
_copied=0
if [ -d "$MAIN/game/assets/art" ]; then
_new="$(comm -13 <(cd "$REPO/game/assets/art" 2>/dev/null && find . -type f | sort) \
<(cd "$MAIN/game/assets/art" && find . -type f | sort) | wc -l)"
if [ "$_new" -gt 0 ]; then
mkdir -p "$REPO/game/assets/art"
cp -ru "$MAIN/game/assets/art/." "$REPO/game/assets/art/"
_copied=$((_copied + _new))
fi
fi
_imports="$(comm -13 <(cd "$REPO/game" 2>/dev/null && find . -name '*.import' -type f | sort) \
<(cd "$MAIN/game" && find . -name '*.import' -type f | sort) | wc -l)"
if [ "$_imports" -gt 0 ]; then
(cd "$MAIN/game" && find . -name '*.import' -type f -print0) | while IFS= read -r -d '' f; do
if [ ! -f "$REPO/game/$f" ]; then
mkdir -p "$REPO/game/$(dirname "$f")"
cp "$MAIN/game/$f" "$REPO/game/$f"
fi
done
_copied=$((_copied + _imports))
fi
if [ "$_copied" -gt 0 ] || [ ! -d "$REPO/game/.godot" ]; then
echo "[assets] worktree 素材同步:补拷 $_copied 件(含 .import 边车),刷新导入缓存"
"${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}" \
--headless --editor --quit --path "$REPO/game" >/dev/null 2>&1
echo "[assets] 导入刷新完成"
else
echo "[assets] worktree 素材同步:无缺件"
fi
exit 0
+1 -14
View File
@@ -293,7 +293,7 @@
## ㉘ 未开格类型徽章批——类型微光实机目检(迭代 30 卡①顺带登记;能力验证半=双帧 glow 作用集取证 2/4/4·outside=0+golden off 帧 0.10–0.14%+探针 case⑰+单测五段 51 检查)
> 类型微光实装(默认开、组合根布尔可切)后,玩家可见面=棋盘格内五组类型微光。机检只能证明「作用集正确/开帧一致/关帧逐位一致」,真机上「五组分不分得清、看着脏不脏」仍需人眼。**群伤组明度档为 provisional 值**(art-bible v3.15 登记,明细见其 git 提交史),观感复核归战斗一屏样张批(该批 2026-10-30 已合回——迭代 43,方向稿目检登记 ㊶、复核随其一并执行)——本节第 5 项读数作该复核的输入。登记不阻塞(2026-10-12 真人检测立规)。
> 类型微光实装(默认开、组合根布尔可切)后,玩家可见面=棋盘格内五组类型微光。机检只能证明「作用集正确/开帧一致/关帧逐位一致」,真机上「五组分不分得清、看着脏不脏」仍需人眼。**群伤组明度档为 provisional 值**(art-bible v3.15 登记,明细见其 git 提交史),观感复核归战斗一屏样张批(迭代 30 卡②)——本节第 5 项读数作该复核的输入。登记不阻塞(2026-10-12 真人检测立规)。
| # | 目检项 | 判据 |
|---|---|---|
@@ -479,16 +479,3 @@
| 4 | 营地三柜台与同调交互回归 | 树淬炼/锻造工坊/秘宝匣三柜台可购可交互,同调充能与改前一致 |
- **红灯信号**:任一屏/浮层布局错位、交互无响应或读数与改前不一致即报缺陷。
- **结论回流**:呈现缺陷归待办清单登记新条目。
## ㊶ 迭代 43——战斗一屏样张批方向稿目检(2026-10-30 随批后收尾登记)
> 单卡合回(2026-10-30,git 提交 3b66d1a6):战斗一屏样张批——五项实装定值(血条危险红/条底收常量/顺位徽标提档/护甲三枚 24×24 PNG/名字行素底牌不压地台+地台件九宫格接装)+样张脚本扩展(两版画布出口、整屏四项与战斗表现五项机检 30/30 全绿、单元套件 18 checks 全绿)。**方向稿目检先行**(art-bible §1.4 闸形)——机检已全绿,气质终审通过才算样张整体过闸;通过前终稿资产与动画设计不启动。登记不阻塞(2026-10-12 真人检测立规);读数与论证详见实现报告(git 提交 3b66d1a6 一线可考)。
| # | 目检项 | 判据 | 材料 |
|---|---|---|---|
| 1 | 方向稿气质与构图 | 气质=明亮地窟看台、立绘最抢眼、无压抑;构图/分区/明度三档带/首焦落点与设计意图一致 | `artwork/mockups/sample_battle_screen_1280x800.png`、`sample_battle_screen_1920x1080.png`(归档位 `artwork/archive/战斗一屏样张批-2026-10-29/`) |
| 2 | 改前/改后五项定值对照 | 血条色/素底牌/徽标/护甲图标/地台件五项前后差异可辨 | `sample_battle_screen_before_after.png` |
| 3 | 类型微光 provisional 明度档观感复核(原归本批承接,见 ㉘ 节) | 五组微光在样张渲染下分得清、看着不脏;复核结论回流 art-bible §4.2 微光色带行 | 同 1(样张含五组微光渲染) |
- **红灯信号**:气质否决(压抑/抢焦/看台感不成立)、任一定值前后不可辨或微光混读即报缺陷回炉。
- **结论回流**:呈现缺陷归待办清单登记新条目;目检通过=样张整体过闸,art-bible §1.4 与战斗表现.md 的过闸状态随之换代。
+85 -66
View File
@@ -2,39 +2,6 @@
本清单是流水线的唯一登记处:要做的事先在此登记一条,随流水线段推进改状态,做完整条删除;各系统文档的「待办」节只留一行指路存根,不再自建条目。条目格式(标题=登记元数据含可选优先级,正文=实况/缺口/改动面/状态四属性)与状态机以 [doc-conventions §12.1](design/engineering/doc-conventions.md) 为权威。
#### 迭代 42 批末移交小件——工具与注释四条(2026-10-30 登记,来源=批次移交)
**实况**:证据=迭代 42 实现报告 §5-1/§5-5/§5-6 与验收审计报告表述性瑕疵 3/4(已按吸收即死退役,git 历史可考)。逐条:
| # | 事项 | 要点 |
|---|---|---|
| 1 | run_all.gd --only 逗号形态解析缺陷 | 头注文档形态 `--only=<词[,词…]>` 与闸脚本传参形态(tools/run_all_gates.sh 关联档选跑逗号拼接单旗标)自 6caf56b5 起与解析不符——整串当一个词对套件路径做子串匹配,多词必零命中 quit(2);修法二选一:run_all.gd 补逗号拆分,或闸脚本改逐词重复 `--only=` 传参(迭代 42 施工曾以逐词重复形态绕过) |
| 2 | 坑速查补条 | .zcode/skills/iter/SKILL.md「坑速查」节补条:worktree 冷检出 --editor --quit 首跑 --path 误指仓库根(未建 .godot 类缓存致首跑解析红),改指 game 切片目录重刷后正常 |
| 3 | level_runner.gd:12 node_entered 信号注释陈旧 | 注释 `shop={gp}` 与 pool 态实际载荷 `{gp, shelf:true}` 不符(迭代 42 改动前即如此,本批未触载荷) |
| 4 | static_scan.sh S8 ok 消息陈旧字面量 | :337「扫描面 B1 文件集 9 个 .gd」——S8_FILES 实含 10 文件(board_installer.gd 随迭代 42 入集),扫面无缩小,仅消息文本过期 |
**改动面**:游戏代码+工具代码
**状态**:待执行
#### 迭代 43 批后待裁三点——B 区高度对齐/名字行字号档位/换色常量退役(2026-10-30 登记,来源=批次移交)
**实况**:证据=迭代 43 反哺点清单 1/8/10(已按吸收即死退役,git 历史可考)。①**B 区高度 270 vs 252**——battle.md 区表文 270、代码 252,18px 差异;样张按代码 252 出(样张反映实机)。②**站桩名字行 12px 档位归属**——standee_body.gd 名字行取 `UiKit.FS_CAPTION`(12px),art-bible §7.4 字号档位表下属「清单外 11–13px」。③**art-bible §4.1 换色合并残余三处**——`STAGE_BG` 换值(#3E342A→#3E362C)未执行;`STAND_STONE_RIM`(#C4AC84)仍被 board_cell_unit 口沿高光在役引用;`STAND_STONE_EDGE`(#5A4C3A)由地台缺位回退路径在役引用。
**缺口**:三点各差一次用户裁决——①B 区高度对齐方向(改实装=需求改动走写卡/改文档=battle.md 区表落 252 归文档批落文);②名字行 12px 提档 `FS_HINT`(14,与顺位徽标同款)或增补装饰 caption 在册面;③三常量彻底退役与否(涉实装件改指与 art-bible §4.1 登记句换代)。裁后各归其段。
**改动面**:文档+游戏代码
**状态**:待文档设计
#### 迭代 43 批后移交小件——能力清单补行+四色墨断言(2026-10-30 登记,来源=批次移交)
**实况**:证据=迭代 43 反哺点清单 9/13(已按吸收即死退役,git 历史可考)。逐条:①**能力清单补行**——game/tests/README.md 能力清单无战斗一屏样张批 V5–V9 代表用例行(卡片成文早于清单 2026-10-29 改版定稿),归写卡段随下一写卡会话按 test-conventions §8.3 补行(能力=站桩呈现定值与素材绑定;代表用例=`game/tests/unit/presentation/standee_present_values_test.gd:_init`,真实入口=`UiKit.load_tex()`/`StandeeBody.name_line_probe()`),并同步覆盖域表「战斗表现.md」行。②**意图徽标四色墨补机检断言**——样张脚本 S1 文字对比为绘图用色值对表,意图徽标四色墨(对 PANEL_DATA 垫牌)未入 CHECKS;独立审计复算 4.97–5.62 全 ≥4.5 事实成立仅缺机检断言,`tools/render_sample_sheet_v3.py` CHECKS 补一条对表断言(values 全在册,零新增色)。
**改动面**:游戏代码+工具代码
**状态**:待执行
#### 架构审查优化讨论——全库架构审计读数的逐条裁决与整改分批(2026-10-29 登记,来源=审计发现)
**实况**:2026-10-29 全库架构静态审计完成,读数见 reports/审计-全库架构审查-2026-10-29.md(七节:总评/健康面/P0/P1/P2/待裁点/取证口径;零测试执行零引擎运行,全部结论带 文件:行号)。健康面已逐条核干净:Core 零 UI 八项判据全零命中、core 内 RNG 单点 100% 成立、字符串式信号连接全库 0 处、core 层零超长函数、19 表 ↔ 19 schema 一一对应、无循环 preload。问题集中在三条「写了没闸守」的纪律被实际破坏:①数值单一事实源被 18 处硬编码击穿,最危险两处是 `presentation/main.gd:787` 与 `:790` 把 `rap_refresh_cost=8` 与 `refresh_cap_per_round=2` 写死进提示文案(紧邻 783 行是正确读表的),表改文案不改、玩家直接受错误数字;另有 4 组代码自标「机制结构常量/产出约束」的字面量(`node_map_generator.gd:12` `MAX_WIDTH`、`action_economy.gd:14` `CHARGE_LAYER_CAP`、`board_data.gd:9` `SPAWN_AREA_CELLS`、`node_map_generator.gd:83-94` 域字面量)是否入表待裁。②`game/sim/` 与 `game/core/` 四组平行实现——扫雷推理引擎双份(`board_generator.gd` `measure_inferability` 族 vs `auto_player.gd` `_deduce` 族,输入集口径不同:前者含 safe_set oracle、后者 no-peek)、伤害掷点期望重算(`damage_probe.gd:196-201`)、档位排序与驱动循环双份(`level_autoplay.gd` vs `treasure_item_table.gd:44` 与 `battle_driver.gd:17`)、FakeDB 伪造表喂生成器——直接威胁「模拟读数 = 游戏真实数值」这一数值验证前提。③RNG 单点纪律在 core 外失守(sim 4 处/probe 1 处/tests 3 处自建 `RandomNumberGenerator`,均显式定种、确定性尚存),而 `static_scan.sh` 的 S1/S2 扫描面只含 `DIR_CORE` + `DIR_PRES`。守卫面另有三缺口:S8 是 9 文件显式清单(新增结算文件忘登记=静默不扫,缺位自检只防删改不防增)、S9 类型闸与 S13 质量闸扫描面止步于 core+data+presentation(sim/probe/tests 三层除 S11/S12 外零闸)、S13 三处豁免常态化到最大户文件(`battle_orchestrator`/`main.gd`/`tune_sim_controller`);另有四个 core 文件零测试点名(`attune_loadout`/`battle_fx_host`/`meta_state_store`/`slot_store`)。P2 级:`core/settlement/` 五段类直读写编排器私有成员 20+ 处(`oc._damage.*`/`oc._enemy_system.*`,GDScript 无编译期检查、改名静默断裂);架构文档三处漂移(`architecture.md:110` 登记的 `core/hero_ledger.gd` 盘上不存在且 `hero_ledger_test.gd:171` 显式断言它必须不存在、`:135` 行数过期、EC8 热重载门后 `ConfigDb.reload()` 零调用方且走 `load()` 拿不到磁盘新值);class_name 级双向依赖(`core/config_db` → `data/schemas/board_gen_table` → `core/board_data.SPAWN_AREA_CELLS`)与 `NODE_TYPES` 词表双份;`MetaProgression` 是唯一用 `get_node("/root/...")` 取依赖、唯一跨 Autoload 直调的单例。
@@ -92,14 +59,22 @@
**状态**:待执行
#### 闸基线 02/05/09 三键换代——存量红指纹已消除或现行不复现(2026-10-29 登记、2026-10-30 并入 05,来源=批次移交)
#### 闸基线 02/09 两键换代——存量红指纹已随迭代 41 测试债批归绿(2026-10-29 登记,来源=批次移交)
**实况**:证据=迭代 41 测试债批代码域闸实测(worktree 分支 `testdebt-20261029`,回执 commit afd1a41f、域=code、scope=full、零新增红),02 单元与集成与 09 静态扫描两闸判「基线红 → 现绿(改善)」;05 渲染闸在 2026-10-30 脚本错误扫描批主区真跑 `bash tools/render_gate.sh` exit=0 全绿(shot/fx/board 三段扫描零命中、残留 Godot=0、golden changed 0.00%),其红键在现行主区不复现。基线 `game/tests/.gates/baseline.json` 里这三键仍记着历史红指纹(02 fp f9865f1a5a44=9 套件失败;05 fp de89589e49ed;09 fp 1a9ad773489f=S9 24 处+S10 4 处+S11 1 处),留着不动的后果=日后同类红回归会被判「存量红·对基线零新增」静默放行。待做=三键按归绿实测重生成(写 exit=0/绿指纹),改的是 `game/` 下非 .md 文件=代码面,须随下一个代码批在 worktree 内一并落、禁主区直改;05 须在素材齐盘预检后跑出真值(冷检出空盘态下的渲染红不作数)。人工确认凭据=上述回执、reports/迭代41测试债批-实现报告-2026-10-29.md 第三节与本批提交说明。本条目无设计段(机械重锚),不走写卡与编排。
**实况**:证据=迭代 41 测试债批代码域闸实测(worktree 分支 `testdebt-20261029`,回执 commit afd1a41f、域=code、scope=full、零新增红),02 单元与集成与 09 静态扫描两闸判「基线红 → 现绿(改善)」;基线 `game/tests/.gates/baseline.json` 里这两键仍记着已消除的存量红指纹(02 fp f9865f1a5a44=9 套件失败;09 fp 1a9ad773489f=S9 24 处+S10 4 处+S11 1 处),留着不动的后果=日后同类红回归会被判「存量红·对基线零新增」静默放行。待做=两键按归绿实测重生成(写 exit=0/绿指纹),改的是 `game/` 下非 .md 文件=代码面,须随下一个代码批在 worktree 内一并落、禁主区直改;人工确认凭据=上述回执与 reports/迭代41测试债批-实现报告-2026-10-29.md 第三节。本条目无设计段(机械重锚),不走写卡与编排。
**改动面**:游戏代码
**状态**:待执行
#### gdstyle 尾波清偿批——基线余量 11 键定向整改(2026-10-29 登记,来源=审计发现)
**实况**:证据=`tools/gdstyle_baseline.txt`(2026-10-29 人工重锚后余量 16 键)+reports/审计-gdstyle存量债清点与清偿方案-2026-10-26.md(§三波次方案与 §六文档设计段裁定记录,裁定全部继续有效)。迭代 39/40/41 三批清偿后余 16 键:本批承接 11 键——board_view 6(_paint_cell 长度 139/分支 24/局部 22、循环内 new、类变量 32、公开方法 27)/level_runner 3(公开方法 50、类变量 30、_enter_node 返回 7)/battle_orchestrator 文件长 1035/adventure_runner 类变量 28;另 5 键走豁免通道另行讨论不在本批(main.gd 类变量 49、tune_sim_controller 循环内 await、board_cell_unit 类变量 30、standee_body 类变量 19、battle_orchestrator 类变量 38——其中三条已有架构豁免登记)。需求卡片 v2.0 定稿(零待裁点):reports/需求卡片-gdstyle尾波清偿批-2026-10-29.md 设计段裁决 2026-10-29 用户裁闭合(D1 装板域拆新件 BoardInstaller 两修正·architecture v1.24 轴丙已落文/D2 悬停域不拆/D3 商店域候选 A/D4 断点域不拆/D5 五降值键豁免终局/D6 商店域并排编队——执行合同=并排卡「level_runner 商店委托层拆除」v1.1,本卡不重复执行),零待裁点。预期两档:归绿 6 键(_paint_cell×3、循环内 new、_enter_node、文件长);豁免终局 5 键(board_view 32/27、level_runner 50→33/30、adventure 28——批末按实测值落 architecture 豁免登记,键 7 实测值视并排卡时序)。本卡估时 3–4.25 工作日(并排卡 ≈1.5 工作日另计)。MC/数值零触碰(结构整改不改数值与 RNG 消费序);battle_orchestrator 触 PARITY_TOUCH_FACES——实跑对拍须经用户批准、不得借机重锚。S13 键账对账在批末,基线重锚由用户发令人工 --refresh(预期降幅逾 10% 须 --force)。2026-10-29 用户再裁(零豁免延伸):D5 五降值键改留债——本卡批末「落 architecture 豁免登记」步骤**不执行**(键留基线冻结禁恶化、不落豁免登记);D2 悬停域与 D4 断点域改判拆件,由新条目「gdstyle 尾波补件批」承接(需求卡片已写),本卡对应任务不施工。
**改动面**:游戏代码
**状态**:文档设计已闭合+已写卡+已编迭代 42
#### gdstyle 尾波补件批——悬停域与断点域拆件(2026-10-29 登记,来源=会话提及)
**实况**:尾波卡 v2.0 裁去的 D2/D4 两拆件项经用户再裁恢复(2026-10-29,零豁免延伸+D5 留债)。设计闭合凭据=architecture.md v1.25(呈现层表 board_hover 行/核心层表 breakpoint_store 行/「冒险域拆分」节随动句)。需求卡片:reports/需求卡片-gdstyle尾波补件批-2026-10-29.md(v1.0 定稿零待裁点)。范围:board_view 拆 board_hover(11 函数族+2 成员,`_r0` 留宿主)+adventure_runner 拆 breakpoint_store(6 成员+`BREAKPOINT_FIELDS` 27 键冻结函数族,`resume_adventure` 留宿主薄壳)。键账预期:board_view 类变量 32→约30、公开方法 27 持平、adventure_runner 类变量 28→约22——三键**留债**(键留基线冻结禁恶化、禁落豁免登记)。编队:待迭代 42 合回后编入;与呈现层状态件批同代时其先行(u.st 写入面改型后 hover 拆件在新面上施工)。估时 2.5–3.5 工作日。
@@ -110,15 +85,15 @@
#### 零豁免五键清偿批——S13 余量五键真修四卡(2026-10-29 登记,来源=会话提及)
**实况**:用户 2026-10-29 裁「不允许任何豁免」——S13 三条豁免注销(architecture.md v1.25 落文),五键真实修复至阈值内:main.gd 类变量 49→15(组合根注册面:ui_registry 一门面+三域容器,约 867 处探针/系统测试引用改址 `main.ui.<域>.<句柄>`,7 别名/转发成员消灭)/battle_orchestrator 类变量 38→15(RoundCycle+BattleFacts 两件,叠加迭代 42 轴丙 BoardInstaller;encounter 归 BattleFacts、attune 改判状态两成员随件)/board_cell_unit 类变量 30→10+standee_body 类变量 19→10(各拆两独立文件,gdstyle inner-class 展平计数口径)/tune_sim 循环内 await 消除(process_frame 信号步进状态机;**零行为改动**——v1.0 申报的收尾置假还原已被迭代 41 测试债批 215da315 先行完成并申报,2026-10-30 实现段按卡片预设停机点停卡、三重证据定谳前提已消化,按停卡呈报建议案重裁剥离行为改动申报:iter44 worktree 分支 reports/iter44-实现报告-2026-10-30.md §5)。设计闭合凭据=architecture.md v1.25+五份设计文档(reports/ 下五件 设计-组合根注册面-2026-10-29.md/设计-战斗编排类变量归约-2026-10-29.md/设计-单格绘制状态件-2026-10-29.md/设计-站桩状态件-2026-10-29.md/设计-测速循环去await-2026-10-29.md)。四卡已成文(均零待裁点开工门 U0;测速循环批卡 2026-10-30 停卡重裁 v1.1——前提消化后剥离行为改动申报、T0 改现状复核、夹具申报差异两处减一处,仍零待裁点):
**实况**:用户 2026-10-29 裁「不允许任何豁免」——S13 三条豁免注销(architecture.md v1.25 落文),五键真实修复至阈值内:main.gd 类变量 49→15(组合根注册面:ui_registry 一门面+三域容器,约 867 处探针/系统测试引用改址 `main.ui.<域>.<句柄>`,7 别名/转发成员消灭)/battle_orchestrator 类变量 38→15(RoundCycle+BattleFacts 两件,叠加迭代 42 轴丙 BoardInstaller;encounter 归 BattleFacts、attune 改判状态两成员随件)/board_cell_unit 类变量 30→10+standee_body 类变量 19→10(各拆两独立文件,gdstyle inner-class 展平计数口径)/tune_sim 循环内 await 消除(process_frame 信号步进状态机;含**申报行为改动**一处:运行态收尾由每局置假还原为整批收尾单次置假——消除与 wd8/ui_tune_probe 断言面的静态矛盾,T0 实证定谳)。设计闭合凭据=architecture.md v1.25+五份设计文档(reports/ 下五件 设计-组合根注册面-2026-10-29.md/设计-战斗编排类变量归约-2026-10-29.md/设计-单格绘制状态件-2026-10-29.md/设计-站桩状态件-2026-10-29.md/设计-测速循环去await-2026-10-29.md)。四卡已成文(均零待裁点开工门 U0):
关联文档清单:
- 组合根注册面批 reports/需求卡片-组合根注册面批-2026-10-29.md
- 呈现层状态件批 reports/需求卡片-呈现层状态件批-2026-10-29.md(单格+站桩两设计并一卡)
- 测速循环批 reports/需求卡片-测速循环批-2026-10-29.md(v1.1,2026-10-30 停卡重裁)
- 测速循环批 reports/需求卡片-测速循环批-2026-10-29.md
- 战斗编排归约批 reports/需求卡片-战斗编排归约批-2026-10-29.md(接收方=在册条目「战斗域结构深拆批」宿主瘦身子项,不在本条目记账)
编队待迭代 42 合回(battle_orchestrator/board_view 触面碰撞约束),具体迭代归属归编排段;编入前置=各卡过 grilling 复核门。gdstyle inner-class 计数截断缺陷已在册(architecture v1.25)——基线登记值欠估,各批按真实口径施工;各批全部合回后一次人工 --refresh --force 收账(一并清迭代 42 归绿 6 键离账——批末实测已归绿而基线仍记 16 键:board_view 四键+level_runner `_enter_node`+battle_orchestrator 文件长;各留债键按重锚时实测重登记,冻结禁恶化口径不变)。2026-10-29 编排(用户裁编法):卡 A 组合根注册面批+卡 B 战斗编排归约批+卡 D 测速循环批编入**迭代 44**(≈7.875 个工作日,中值合计);卡 C 呈现层状态件批编入**迭代 45**(与补件批同批,卡 C 先于其 board_hover 半边施工)。grilling 复核门已过(reports/复核-grilling-零豁免五卡-2026-10-29.md,五卡有条件成立、修正全落,提交 9855bf0a)。
编队待迭代 42 合回(battle_orchestrator/board_view 触面碰撞约束),具体迭代归属归编排段;编入前置=各卡过 grilling 复核门。gdstyle inner-class 计数截断缺陷已在册(architecture v1.25)——基线登记值欠估,各批按真实口径施工;各批全部合回后一次人工 --refresh --force 收账。2026-10-29 编排(用户裁编法):卡 A 组合根注册面批+卡 B 战斗编排归约批+卡 D 测速循环批编入**迭代 44**(≈7.875 个工作日,中值合计);卡 C 呈现层状态件批编入**迭代 45**(与补件批同批,卡 C 先于其 board_hover 半边施工)。grilling 复核门已过(reports/复核-grilling-零豁免五卡-2026-10-29.md,五卡有条件成立、修正全落,提交 9855bf0a)。
**改动面**:游戏代码
@@ -126,7 +101,7 @@
#### 数值标定窗口总挂账——原九条数值类条目合并(2026-10-28 登记,来源=会话提及)
**实况**:2026-10-28 用户令把九条数值类挂账合并为一条:削弱档位的最终数值/迭代 10 奖励重构联动欠账/调参批数值点评登记/三方向各自的解锁清单与定价/GP 经济换算失衡/天赋树实装批登记欠账余两件/商店独立刷新机制两参数/基线通关率越带加难杠杆扫描/成长系数节奏——原九条同步删除,内容全量迁入汇总文档,git 历史可考(本提交)。共同背景:M4 数值批卡已随 2026-10-16 删卡令退役;2026-10-18 用户裁=各条留待办清单、未设计未启动、不另行归属,未来数值标定窗口重开时重走设计段再写卡——本条沿用该裁定,重启条件=用户发令开窗口。合并动机=九条共享同一重启条件与同一执行形态(窗口内一次 MC 批量统一覆盖各条对比需求:全冒险期望+奖励期望),分散登记徒增维护面。逐条挂账明细、裁定史与相关文档见汇总文档。
**实况**:2026-10-28 用户令把九条数值类挂账合并为一条:削弱档位的最终数值/迭代 10 奖励重构联动欠账/调参批数值点评登记/三方向各自的解锁清单与定价/GP 经济换算失衡/天赋树实装批登记欠账余两件/商店独立刷新机制两参数/基线通关率越带加难杠杆扫描/成长系数节奏——原九条同步删除,内容全量迁入汇总文档,git 历史可考(本提交)。共同背景:M4 数值批卡已随 2026-10-16 删卡令退役;2026-10-18 用户裁=各条留待办清单、未设计未启动、不另行归属,未来数值标定窗口重开时重走设计段再写卡——本条沿用该裁定,重启条件=用户发令开窗口。合并动机=九条共享同一重启条件与同一执行形态(窗口内一次 MC 批量统一覆盖各条对比需求:全冒险期望+奖励期望+对拍锚重锚),分散登记徒增维护面。逐条挂账明细、裁定史与相关文档见汇总文档。
关联文档清单:
- 汇总文档 reports/汇总-数值标定窗口挂账-2026-10-28.md(2026-10-28,九条全量内容+统一执行形态建议序:设计段逐条裁决→按域拆卡→实现批三处同步→测量批一次跑批)
@@ -152,6 +127,14 @@
**状态**:待文档设计+已写卡+已编迭代 44
#### level_runner 商店委托层拆除——迭代 37 冻结契约期过渡件(2026-10-26 登记,来源=批次移交)
**实况**:证据=迭代 37 卡④ ShopSession 拆分时立「冻结公开面」契约,level_runner 保留 buy_product/shop_refresh 等同签名委托方法(架构模块表登记为「外部调用面由宿主同签名委托承载」)。2026-10-26 AdventureRunner 真拆分设计会话裁定新原则:纯转传委托=Middle Man 反模式,新件一律调用方直连不留壳(godot-master 技能库审查同判);本批不动该委托层(真拆分条目范围仅撤 8 条白盒转发属性),其拆除与调用文件改址由本条承接。需求卡片:reports/需求卡片-level_runner商店委托层拆除-2026-10-29.md(v1.1,2026-10-29,复核门通过零待裁点)。
**改动面**:游戏代码
**状态**:已写卡+已编迭代 42
#### 超预算图策略升级路径——等比缩放与滚动视口两案待裁(2026-10-24 登记,来源=批次移交)
**实况**:迭代 34 卡④(信息迷雾可见性批,已合回 master,提交 16a7ec80)按「参数限图」交付,图包络约 10 层×8 槽即当前冻结画布的屏预算上限。若后续定值批需要更大的完整可见图,两条升级路径都要动冻结件,均未设计:①等比缩放——动「节点图标 20px 烘焙 1:1」与「通道折线冻结 2/3/4px」两条冻结值;②滚动视口——新增视口交互范式。迭代 35 生成图实测深度域 L3 达 0–13,已超出该上限(level-map 已登记随动缺口,本批不裁方案)。
@@ -187,6 +170,23 @@
**状态**:待美术设计(备选)
#### 战斗一屏样张 P0 未开工——界面成品是否过审的那把尺子还没建(2026-10-13 登记,来源=审计发现)
**实况**:[美术圣经](design/art/art-bible.md) §7.1 明文写着"程序化占位=临时态,任何屏的终稿闸=§1.4 整屏样张过闸;占位态禁止跨里程碑"。而 [美术素材状态清单](design/art/art-status-checklist.md) 把「战斗一屏样张」这件最高优先级的素材标的是**未开始**——也就是说,**判定界面美术算不算过关的那把尺子到现在还没造出来**。2026-10-16 曾编入迭代 30 卡②(UI 呈现三卡批序位②),2026-10-17 用户裁定不满足「迭代=代码实现批」的开发条件、剔除回待设计(理由=样张交付物本身就是设计规格,正式字体与素材均未定稿;同日样张批开工已被用户终止)。v1.4 旧卡留 reports/需求卡片-战斗一屏样张批-2026-10-16.md 作素材,非已写卡状态,重启不得直接开工。Alpha 出口裁决:列入 Alpha 硬前置——站桩帧动画的定版前置与主角+三 Boss 重产都依赖它。
**2026-10-28 美术设计段闭合**:差的那轮「设计讨论→需求澄清→美术定稿」已完成并落文——样张即设计目标图,五张规格表(尺寸档/色板/明度档/字号档/素材清单)全定、样张构图与内容清单全定、验收判据清单全定、16 条待裁点全数清零(最后一条 B-1 色板第二道门口径经 2026-10-28 用户裁取乙案宽松口径);样张的产出手段与素材输入级别已定,样张开工门复判 PASS。原缺口三件的前两件(目标规格清单、屏内素材规格)由此闭合,第三件(重启须重新立卡)转为本条的下一动作。
关联文档清单:
- 美术线闭合凭据(art-bible 条款版本):[美术圣经](design/art/art-bible.md) v3.22(2026-10-28)——§1.4 样张产出手段与素材输入级别登记句、§2 世界底层两子带、§4.1 第二道门口径改写与色板转正/换色合并登记、§4.2 血条填充显式句与蓝族违例值退役、§6.1 舞台背景路径订正与近景地台独立件、§6.3 三关色板终值取值程序、§7.2 文字禁直接压地台、§7.4 顺位徽标不增列与字重重开钩子注销、§7.6(三) 氛围两派维持不统一、§8.2 护甲图标与地台件规格与棋盘小人 32px 源档终值、§8.4 方向集口径追认
- 美术线设计轮定值与依据:2026-10-28 设计轮(过程稿已按「吸收即死」删除、不入库,结论已吸收进上一条,推导沿革见 commit 50702ea5 与 10d7c82b)
- 配套资产登记:[美术资源依赖清单](design/art/art-assets-checklist.md)(2026-10-28,§3.7 护甲类型图标三枚与 §4 舞台背景/地台件两行)· [素材路径分工约定](design/art/asset-path-map.md)(2026-10-28,`scene/` 子目录现役列)
- 相关文档:美术线(战斗一屏样张的验收标准归 [战斗表现](design/gdd/systems/战斗循环/战斗表现.md) 与 [美术圣经](design/art/art-bible.md) §1.4 共裁)
- 需求卡片:reports/需求卡片-战斗一屏样张批-2026-10-28.md(2026-10-28 重新立卡,十二节齐备,旧卡 v1.4 已作废;待编入迭代,编入后由编排段记「已编迭代 N」)
**改动面**:美术素材
**状态**:已写卡+已编迭代 43
#### UI 氛围层与九宫格切片观感修——光柱硬边与面板切片错位(2026-10-13 登记,来源=审计发现)
**实况**:原两处,一处已修一处存续。①光柱硬边**已修**(2026-10-14 迭代 26 T4:`ui_kit.gd` 第 263 行起的光柱贴片改带内 alpha 0→0.05→0 三角过渡+纵向衰减上亮下暗,grilling Q7 裁连续 alpha 方案;LINEAR 白名单已随反哺入册 art-bible v3.14 §8.2)。②九宫格切片错位**存续**——结算屏与图例屏的面板边缘可见错位小方块,成因是九宫格切片参数与素材尺寸不匹配(仪式石框 258 字节小图按 12 像素切片、石板亮面板 314 字节按 16 像素切片,铺到四百多像素宽面板中段拉出错位),须先核素材尺寸能否撑住新切片值(border_stone/panel_stone 均 48×48),可能要重出素材,归「UI 贴图重构批」后续贴图批次。
@@ -316,7 +316,7 @@
**实况**:来源=playnote 用户试玩记录「UI 优化,第二第三关的棋盘看着太累了;不知道是因为亮度还是对比度的原因,看着非常费劲,非常不舒服」。2026-10-13 用户裁定:**这是需要做的**,且本就属版本标准的覆盖范围——[08-Alpha版本标准](design/gdd/08-Alpha版本标准.md)「五 · 表现」已新增「棋盘观感(第二 / 三关亮度与对比度)」行,标待做。**开工前须先定性(沿用既有裁决)**:是**素材轴**问题(棋盘瓦片与格面图标本身的明度与色相),还是 **UI 轴**问题(舞台底色与叠加层的对比关系)——两轴修法完全不同,定性错了等于白改一轮;该定性要求(原备忘 (e) 项)是本条的开工前置。2026-10-16 曾编入迭代 30 卡③(UI 呈现三卡批序位③),2026-10-17 用户裁定剔除回待设计(自身挂「素材轴/UI 轴」定性人闸+甲/乙〔recolor/远程生图重产〕素材裁点;若裁乙,其瓦片规格依赖战斗一屏样张批的目标规格清单产出)。v1.4 旧卡留 reports/需求卡片-二三关棋盘观感批-2026-10-16.md 作素材,非已写卡状态。与已完成注销的「呈现层残余 <4.5:1 文字位」分工:本条管**棋盘区域整体的观感**(亮度/对比度/费劲感,体感问题),彼条管**具体文字位的对比度读数**(机检可判硬指标),两条不同面不互相替代。Alpha 出口裁决:Alpha 必须,先做素材轴/UI 轴对照定性;出口=用户实机目检不再判「看着费劲」。
**缺口**:还差「素材轴 / UI 轴」定性设计讨论与甲/乙素材裁点裁决;素材轴若走重产,还依赖战斗一屏样张批的目标规格清单产出。旧卡 v1.4(reports/需求卡片-二三关棋盘观感批-2026-10-16.md)在册但非已写卡状态,其前置未完成三件:①「素材轴 / UI 轴」定性未做,这是既有条款规定的开工前置;②甲(recolor 现瓦片)/乙(远程生图重产)素材裁点未裁;③若裁乙,瓦片规格压在样张批的目标规格清单上——该清单已随战斗一屏样张批落定(art-bible v3.22 五张规格表,2026-10-30 合回)。
**缺口**:还差「素材轴 / UI 轴」定性设计讨论与甲/乙素材裁点裁决;素材轴若走重产,还依赖战斗一屏样张批的目标规格清单产出。旧卡 v1.4(reports/需求卡片-二三关棋盘观感批-2026-10-16.md)在册但非已写卡状态,其前置未完成三件:①「素材轴 / UI 轴」定性未做,这是既有条款规定的开工前置;②甲(recolor 现瓦片)/乙(远程生图重产)素材裁点未裁;③若裁乙,瓦片规格压在样张批的目标规格清单上,而那份清单的前置正是上一条未完成的样张设计轮——本条第三项压在卡②身上。
- 相关文档:[HUD框架](design/gdd/systems/表现与支撑/HUD框架.md)(UI 轴:舞台底与叠加层)+ [美术圣经](design/art/art-bible.md)(素材轴:瓦片与格面图标的明度色相)——定性前两方共裁
**改动面**:美术素材+游戏代码
@@ -327,11 +327,9 @@
**实况**:扩板已落地(四档板型 10/12/13/14 在跑),2026-10-26 文档面已按单点维护归一:①格边长口径的唯一出处收敛到 [战斗屏规格](design/gdd/ux/battle.md)「棋盘格尺寸公式」——公式含格间距与垂直预留,可读下限 30px,1280 档算例 43/36/33/30 标注为随布局变化的非契约值;②[美术圣经](design/art/art-bible.md) §8.1 改为指路 battle.md、只锁资产侧两条比例,§8.2 甲类整数倍纪律已剔除砖面 tile(瓦片随格边长整格铺满,允许非整数倍);③[HUD框架](design/gdd/systems/表现与支撑/HUD框架.md) 只留装板可行性不等式,删去 36/45 定值档与旧算例;④demo 原型 44px 那一路随 `src/` 退役消失。剩余为代码面两处与口径不同步:`game/probe/layout_probe.gd:15` 的格子可读门槛 `MIN_CELL := 36.0` 仍取 12×12 的旧锚点值,比 battle.md 的可读下限 30px 严——现探针只进 10×10 的 L1 故仍全绿,一旦覆盖 13×13(33px)与 14×14(30px)即会误报红;`game/tests/unit/core/board_gen_reward_test.gd:53` 的格边长冒烟只跑 L1/L2,13×13 与 14×14 无覆盖。断言改动后按 `game/tests/README.md`「能力清单」核对受影响行(验证集清单已退役——test-conventions §8.3)。
- 需求卡片:[需求卡片-棋盘格边长口径收口批-2026-10-30](reports/需求卡片-棋盘格边长口径收口批-2026-10-30.md)(2026-10-30 已写卡,零待裁点——算式单点提取经 `BoardView._cell_for`/探针门槛 36→30 对齐 battle.md 可读下限/冒烟扩 13×13 与 14×14;探针不扩到 L3,最大板可读面改由实板面积推定断言承担)
**改动面**:游戏代码
**状态**:已写卡
**状态**:待写卡
#### 立绘清晰度回退评估——是否改用高清图(2026-10-13 登记,来源=playnote)
@@ -366,6 +364,15 @@
**状态**:待美术设计(备选)
#### 在途草稿整理批——3349 个失管草稿(2026-10-06 登记,来源=审计发现)
**实况**:审计 P2-1——`artwork/ui-reuse-2026-09-23/` 独占 3043 个草稿(91%),vN 命名率 0%,冗余 41 文件,残留非素材文件(.url ×8 / .ogg ×6 / .ai ×2 / .swf ×1);根因=脚本链覆盖「生成→验证」但**缺「草稿整理」段**。整理涉及**批量文件移动/删除**,按用户惯例须先 dry-run 呈报清单、经确认再分批执行。执行窗口随实现批;开工=用户点名。
- 相关文档:[art-bible](design/art/art-bible.md) §8.3 归档纪律(原则已写入)· 资产管线批(工具+文件操作)
**改动面**:美术素材+工具代码
**状态**:待写卡
#### 战斗区背景图替换——B 区舞台插画 + 临时光柱退役(2026-10-07 登记,来源=会话提及)
**实况**:B 区舞台现行=程序化平色底(`UiKit.STAGE_BG`,`battle_stage_view.gd:47`)+ **顶光柱氛围层**(`:55-63` 四柱,x 基准 110/900/1055/1210@1280,60 段叠层矩形自顶向下渐宽窄渐隐〔顶 α26→0〕,层序=bg < 光柱 < 世界)——二者均系 §6.1 方向稿的**临时占位**,用户口径「光柱只是临时方案,换正式战斗背景后效果更好」。正式件已立资产契约:`stage/stage_bg.png`(源 1280×270,B 区全高含明细带底;三关换挡走 §4.3 `wall_ambient` tint 槽,不按板出三张),属审计 P1-2 查得的「B 区舞台背景插画 0 张」缺口。**光柱在背景到位后顺带退役或吸收进背景绘制**(不再作为独立程序层)。**与立绘探顶解耦**:大体档「允许向上溢出盒顶 N px」的判定(2026-10-07 用户裁甲案)**不依赖光柱是否存在**,换背景零返工。
@@ -377,29 +384,6 @@
**状态**:待美术设计
#### 棋盘封印印记图形换代——现件仍是扫雷旗形,改法阵或锁链待设计(2026-10-30 登记,来源=会话提及)
**实况**:用户口径=棋盘封印图标现在还是按旧扫雷的做法画成一个旗子,希望换成法阵那样的封印、或锁链;形未定,先做设计讨论再出图。现状证据:
- 现件 `game/assets/art/board/seal_mark.png`(32×32 纯黑剪影)=经典扫雷旗形(旗面 + 旗杆)。2026-09-28 那次只定了色(旗朱→封印紫→封印黑 `#000000`),图形一直没动。
- 消费面两处,换形同时受影响:棋盘侧 `game/presentation/views/tile_palette.gd:19`(`TILE_SEAL_MARK` 常量)→ `board_view.gd:616` 未开格封印槽绘制,盘上缺图时回落单字「封」(`board_view.gd:654-658`);战末弹层的「金蜡封徽记」复用同一张图(金底 52×52 面板 `battle_end_overlay.gd:65-69`,叠 32px 印记 `:84`)——蜡封徽记要不要跟着换是独立一裁。
- 文档侧口径仍以旗为形:art-bible §4.1 封印黑条内含「旗杆米白保可读」这一旗形前提、§3.2 封印印记条只锁色与格内栈位;art-assets-checklist §4 封印特效行写「印记旗面走封印黑」「印记常显形态可与 §3.3 旗面像素化合并出」;ux/battle.md:128 呈现术语注记与 :270 悬停三态光标「封印(印记形)」。
- 术语层已闭合,本条只欠呈现层:「旗」概念 2026-10-18 用户裁全库退役(动作=封印、印记=封印印记、板面单字=「封」),图形停在退役前的形态。
关联文档清单:
- 相关文档 [美术圣经](design/art/art-bible.md) v3.23——§3.2 封印印记条(:167)· §4.1 封印黑条(:202)· §1.2 格内优先级栈 · §1.4 格子样张
- 相关文档 [美术资源依赖清单](design/art/art-assets-checklist.md)——§3.3 雷/封印印记/数字格面像素化(:118)· §4 封印特效行(:162)
- 相关文档 [美术素材状态清单](design/art/art-status-checklist.md)——P2「雷/封印印记/数字砖面全套」◐ · P3「封印特效」⬜
- 相关文档 [战斗屏 UX](design/gdd/ux/battle.md)(Last Updated 2026-10-29)——:128 呈现术语注记 · :270 悬停预承诺三态光标 · :261 未开格封印交互行
- 相关文档 [术语表](design/gdd/术语表.md) v1.25——「封印」条(:15)· 用语规范禁用字面条(:184)
- 相关目检项 [体验验证清单](体验验证清单.md) 第 4 条「封印印记贴图三关加载」(:317,现判据只验贴图是否加载、不验形状)
**缺口**:还差一次封印印记图形的设计讨论(形未定,法阵/锁链两案并列,禁代裁),讨论须一并裁五点:①形=法阵(圆形阵纹)还是锁链(束缚感),或第三种方案;与危险格巢穴纹、奖励徽章的剪影语言(art-bible §3.1 直线几何体)互辨是否成立。②色=现封印黑 `#000000` 是 §4.1 不变槽成员,法阵若带发光或色相即与该条口径冲突,须重登记不变槽与对比度机检判据(现判据=未开格墙顶对纯黑 ≥4.4:1)。③尺寸与遮挡=32×32 内保持可读,格内优先级栈「数字 > 小人 > 封印印记」下法阵铺满整格是否侵入数字字形区(§3.2 验收句禁止)。④连带件=战末金蜡封徽记与悬停「封印(印记形)」光标是否同步换,还是各留一件。⑤与「封印特效=施印瞬间特效 + 印记常显形态」两件的关系=合并出一件还是分两件。裁决写入 art-bible §3.2/§4.1 与两份美术清单条款后,才可出样张与终稿(终稿文件名沿用代码消费名 `seal_mark.png`,除非裁决换名)。
**改动面**:文档+美术素材
**状态**:待美术设计
### 教学引导
> **节头注记(2026-10-04 用户拍板)**:教学引导定稿批整体改排 **Beta 阶段**——当前 Alpha 版不做教学相关工作,本节五条全部随之改排(2026-09-19 的 Alpha 排期拍板被本拍板取代)。同日五问讨论已形成初裁与设计提案,提案已归档 `archive/` 目录(磁盘留存、不入 git,Beta 批立项底稿,引用以其「归档状态与初裁记录」节为准)。初裁摘要:①教学形态=丙1 混合(首次冒险编排式首遇提示〔挂 first_script 锚、不锁操作〕+其余全程情景触发);②「因果教学顺序」问题**注销**——只教扫雷操作,结算读法不设教学(用户裁:结算只是表达、展现、演出,无策略内容;展示层每行带来源标注已自带归因;教学引导.md 旧前提〔设计目的句/P0 候选归因提示/验收第 2 条〕随 Beta 批文档定稿清除);③图例面板按循环段落分组、无结算段;④不做「再教一次」——改走独立「帮助」手册面(全部内容放一个文档);⑤教学文案内容权归设置与本地化(两文档负责表对齐随 Beta 批)。**未裁三疑点**(Beta 立项时先裁):增益叠层节奏条目去留/帮助入口位置(设置面为推荐)/图例面板与帮助手册的内容分工。
@@ -469,6 +453,17 @@
### 文档与结构
#### qa/ 实现报告收尾·孤儿遗留七项余二项(2026-10-14 登记,来源=批次移交)
**实况**:qa 收尾批核查 iter19 系列出口报告遗留移交项时发现**不在待办清单在册**的七项(acceptance-package §三.2/card0 §5.3/followup §七.3/uibackfill §6.2-4/verify §6.1/3/7)。已结案的(tree_rules 受理闭环、X/Y 双源、S1 七项违例修复、提灯目检项、tests.yml 休眠等)已在各报告标注结案证据,不入本条。2026-10-24 处置:第②③④项随迭代 36 卡C 落地销号(营地 STONE 深档六处、迷雾三关派生行、开场后缀条件加算区),第⑥项按用户 2026-10-24 裁整条注销(TG_* 装饰带非文字,不另立探针)。余二项均为挂号备查/顺路件,无排期承诺(第①项 design/diagrams 越界 2026-10-28 实查核销——目录磁盘与 git 均已不存在,越界实体消失,白名单甲/乙两案失去对象):
5. **字面量墨收测前置**(iter19 卡②遗留):bcard 属性描述/类型脚注/✓章/primary_button 字四处内联 Color8 无常量形态,收测需先在 ui_kit 立常量(呈现批顺路件,改 ui_kit 越当批面)。落点=呈现优化批(ui_kit 常量面)
7. **商店房截图纳屏**(iter19 卡①遗留):商店房截图纳屏留后续呈现批(考证结论在 shot_probe 头注)。本项原并列的「`数据配置.md:223` 历史行死指」半句已于 2026-10-21 实查核销——禁写变更记录规约生效后该文件的版本史行整段已不存在(现全文 212 行,文内零版本史行),被指对象随之消亡。落点=呈现批
**改动面**:游戏代码+工具代码
**状态**:待写卡(备选)
#### 主角棋盘小人像素管线重产——原「美术资产链路审计两项 P0」之 P0②(2026-10-09 登记,来源=审计发现)
**实况**:主角棋盘小人未走像素管线(插值糊图),重产事项未在任何登记处在册;审计报告 HTML 件从未进版本库、现已删除,现状以本条正文为准。2026-10-13 用户令随「UI 美化三层诊断」登记:本条属第三层「观感提升」,Alpha 阶段需完成。原 P0①(文档面:两处引用已丢失的冻结样张路径)2026-10-28 按用户裁乙销号——art-bible §1.4 冻结记录与 art-status-checklist P0 样张行删路径、留「产物原件已丢失」,机检报告凭据(git 提交 7a847c7)保留。
@@ -479,6 +474,30 @@
**状态**:待美术设计
#### 技术债观察登记——长函数余量与 lambda 连接防回归(2026-10-15 登记,来源=批次移交)
**实况**:证据=迭代 27 需求卡片两项「登记待办清单观察」义务批内漏登、卡片核对时补登——①**长函数 17 个**中非结算段的装配/校验函数(`level_map_screen.gd` 选路装配 155 行/`camp_overlay_view.gd` 152 行/`debug_overlay.gd` 114 行/`treasure_item_table.gd` schema 校验群等;`main._build_ui` 已随场景拆分自然解体、`_settle`/`_resolve_hero_attack` 已随波二拆段=17 中 3 件已消解,余 14 件);②**lambda 连接 76 处**(presentation 6/smoke 62/tests 8——实读无泄漏,登记防回归)。长函数拆分无独立需求驱动不动;lambda 连接数若有审计工具化需求归类型闸后续。
**改动面**:游戏代码
**状态**:待写卡(备选)
#### 测试域遗留(2026-10-16 登记,来源=批次移交)
**实况**:证据=迭代 29 批内未收余量(交付报告与验收审计申报,报告已按吸收即死退役、git 历史可考)。迭代 29 把验收语义从「跑一遍没崩=通过」扭到「三层完备+判定可靠」,三硬闸(退出码/脚本错误扫描/断言数下限)已落地,L2/L3 从零建起承载。本批未收的余量集中登记在此,均不阻塞、不排期:
| # | 事项 | 落点与要点 |
|---|---|---|
| 1 | **余下 3 个 core 模块仍零指名单测**:`board_data`/`reward_economy`/`skill_lottery` | 测试基建批。本批补了 minesweeper_rules/board_generator/config_db_resolver/config_db_cross_validator 四件;余三件按同一手法补(判据=能否在 `--script` 模式不加载 autoload 验完) |
| 2 | **render_gate 的 SCRIPT ERROR 扫描仍缺失** | 测试基建批。判定主体是 golden 容差与取证文件存在性,脚本错误不改退出码时无扫描面;本批只把矩阵状态如实记为「缺失」,收它的写法待裁 |
| 3 | **E 级卫生件**(test_kit static var 串台风险、4 套件自建断言出口、157 处 untyped 容器、76 处 lambda) | 工程债批。本批只统一了**新写**断言的出口,存量未动;test_kit 的 static var 是「并发跑批」的前置障碍(并发分层方案的前提) |
| 4 | **行覆盖率度量缺失** | 基建评估项。Godot 无内建、第三方属新基建;现行补洞靠「零指名清单 + 人工判定」,是否引入度量待裁 |
| 5 | **parity 逐位一致比对的触面判定为粗粒度路径匹配** | 测试基建批。触面表含 `battle_orchestrator.gd`,改该文件即要求批准发起 MC;若嫌过重可精化到「是否改动取数/结算数值」的语义面 |
**改动面**:游戏代码
**状态**:待写卡(备选)
#### 营地交互重做·五张需求卡片(2026-10-18 登记,来源=会话提及)
**实况**:2026-10-17~18 需求讨论澄清会话产出结论文档,用户令按工作内容维度拆卡、卡片不预占迭代号。五卡已写(顺序=执行顺序):①存档制数据层改造批 ②主菜单三入口化批 ③选档界面新建批 ④营地场景图新建批 ⑤Esc 浮窗与营地顶栏批(各卡估时与内容见卡片自身)。三道复核:grilling 子 Agent 对抗审查(五卡各一)→v1.1;用户裁定「事项按推荐的来」→v1.2;用户两次订正(**卡④留在批内不踢出**;前置归用户自立批,Agent 不代跑不代产——曾越权起草的点选反馈设计稿 v0.1 已撤回删除)→卡④ v2.2;**卡④ 2026-10-21 用户令退回美术设计段(v2.3,非有效卡,美术设计闭合后重立卡)**。①②③⑤ 零待裁点可编工程波次:②③ 硬耦合同波(「进入存档」钮落点=选档界面),① 是 ③ 前置;⑤ 随 ④ 代码波同行。Alpha 出口裁决(2026-10-21 用户裁):五卡整体列入 Alpha——08「三 · 界面」已开条目,Alpha 出口依赖五卡完工与卡④三个用户侧前置销号。**工程波次一已落地(2026-10-22)**:卡①②③ 随迭代 33 实现批合回 master(合并提交 ebf40947/5c2a7bcf/508f190b,总收口 6e8f72b2),三卡需求卡片与批次产出物已按「吸收即死」退役(设计口径已吸收进 存档序列化.md/生涯.md/camp.md/08-Alpha版本标准.md v1.13/art-bible.md v3.18,反哺落文凭据=文档批 git 提交 1864f157+9f7b3e8a);卡④(营地场景图)退回美术设计段待重立卡、卡⑤(Esc 浮窗)随卡④ 代码波同行,两卡未编排——编入哪一迭代由用户经迭代编排段发令。关联文档: