diff --git a/CLAUDE.md b/CLAUDE.md index 6facbd8b..5fae15db 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,6 +64,31 @@ 3. **豁免面(仅这两处允许主工作区直改)**:根目录 `active.md`(会话收口)与 `.zcode/`(钩子配置)——其余一律走 worktree;主工作区 `git commit` 会被钩子直接拦截 4. 本铁律由 PreToolUse 钩子 `.zcode/hooks/worktree-guard.sh` 强制兜底;钩子只约束 agent 侧,用户本人手工改动不受限 +## 批次工作流标准清单(2026-10-06 用户确认固化) + +每个开发/设计批次按以下十三条链路执行。本清单是总索引,各步细则散见上文各节与 `design/dev/` 子文档;与任一细则冲突时以细则为准,发现清单本身过时须同批修订。 + +**开工前** +1. **先检索后提问**——动手前先查在册记忆与正主文档,防止把已裁决事项当新问题重问,或重踩已记录的坑 +2. **任务书全列备选案**——批任务书里每个待拍板点列出全部备选方案与各自代价,交用户裁决,不在任务书内自行定夺 +3. **用户发开工令才动手**——任务书经用户批准(U0)后方开工 + +**环境与基线** +4. **独立 worktree + 拷导入缓存**——按分支纪律建 worktree;新 worktree 先拷主区 `.godot/` 目录与字体 `.import` 边车文件再跑测试,否则每套测试全量重新导入资源、近似卡死 +5. **改码前定基线三查**——记录改动前的冒烟全绿数目、单元测试套数、现行模拟基线读数是哪份;改完用同一口径对拍,红绿才能归因到本批改动 +6. **涉数值改动先采改前读数**——批次要改游戏数值的,改码前先采「改前」模拟读数(与改后同随机种子配对比较),并写明对拍口径;无数值改动禁跑模拟(触发细则见硬性红线) + +**实施** +7. **新功能必配新测试**——按任务书卡片写码,新增玩法/机制必须配套新增测试套件进 `tests/run_all`;没有对应测试的变更不许合并 +8. **验收改动先行声明**——逻辑变更对应的验收变化(模拟基线换代、探针用例增改、旧断言改判)在任务书里预先写明,不事后补 + +**收口** +9. **批末四闸全绿才合并**——冒烟 + 单元测试 + 场景探针 + 改前改后读数对拍,四项全绿方可在主工作区合并 +10. **合并后主区全量复跑**——合并本身可能引入冲突或环境差异,落回 master 后再跑一遍全套验证 +11. **会话收口落记**——`active.md` 快照与工作日志更新;跨会话有效的教训写入记忆库 +12. **实机目检归用户**——真机人闸由用户执行,用户亲口确认才算验收通过 +13. **中间产物吸收即死**——结论先迁入正主文档(GDD/待办清单/路线图),中间文件即删且永不提交(细则见硬性红线) + ## 硬性红线(勿违反) - **worktree 铁律**:主工作区禁止直接改动与提交(细则见上节;并行会话各占 worktree 互不踩踏) diff --git a/active.md b/active.md index 198590ab..3e61bbbd 100644 --- a/active.md +++ b/active.md @@ -11,6 +11,23 @@ - **工程路线图**:`production/roadmap.md`(2026-09-17 建——里程碑时间线 + 路线图;实时状态看下方快照节) ## 当前状态快照(收口批先更新本节,再追加台账;≤10 行) +- **更新于**:2026-10-07(**迭代18卡⑦两裁点全裁+调试浮窗目标态落籍 master 49ee98f**,worktree g-plan2-seedrule 单提交 56d996c+主区 merge --no-ff,工作日志 384;纯文档零代码零测试):种子三裁=首次冒险维持固定/测试调试接口显式指定/种子不进玩家UI(玩家入口随机生成);卡⑦扩为种子随机化+调试浮窗升级双件——浮窗目标态=独立最高层浮窗〔对局中可开,修订迭代15卡①P3甲〕+DS_DEBUG=1 默认打开+当局数据只读区块〔种子唯一查看位〕;现行考证六点在册〔角标门控/1189门禁/非最高层/非默认开/无当局数据面〕;卡①落地门随卡⓪落籍〔e5354366〕自然解除 +- **更新于**:2026-10-07(**迭代18增卡⑦新冒险种子随机化落籍 master 7a7d873**,worktree g-plan2-seedcard 单提交 997bcc9+主区 merge --no-ff,工作日志 382;纯文档零代码零测试):用户令——现状实查 main.gd:1210 种子框空硬编码 42=每次新冒险同种子全程复现;卡⑦=默认分支改随机生成+调试浮窗种子框保留手动覆盖,断点续玩/冒烟锚/⑥收官批三面零影响考证在册;裁点两项随任务书(首次冒险教学态是否随机/种子进 UI 展示位);⑥门改「内容卡①-⑤⑦出口」;下一步=迭代 18 任务书讨论 +- **更新于**:2026-10-06(**内容数据驱动扩展批实施完成落籍 master**(worktree iter17-dd 分支 iter17-dd-20261006,主区 merge --no-ff),工作日志 383〔预配号 381 撞号改登——381/382 系并行迭代 18 规划批与增卡⑦两批先落,先例同台账 374〕):迭代 17 卡⓪ 当日立项当日落地——T1 效果引擎框架+16 发射点(7d157b3)/T2 词汇落码(225765f)系前任完成,续跑会话接手 T3 断点三脏文件收口:T3 专长 27 行 triggers 全量迁移+旧 23 挂点枚举消亡+消费点拆除+文案入表+断言面随迁(e28a6cb,承接修正四处=perfect_defense 事实出域/has 算子语义/漏写 _harmful_adjacent/counter set 键 bool 吞零);T4+T5 秘宝六件按 id 硬编码清零(淬毒 clamp_bonus 模式路由/偏运经 reward_roll/晨令 round_start 订阅+when 行字段定影/惊巢 halve_pct/殉爆订阅开单/掘进蓄能受理闸 counter_spend 订阅)+令牌兑付 match 收敛进效果类型路由表(a33ddaf);闸=冒烟 800 项 0 失败+单元 17 套+探针闸含 click 全绿+MC 二档对拍 40 局同种子**逐位一致**(pre/post 锚 83000 段留盘 sim-data/);T7 文档换代=奇物 v2.8/秘宝 v1.0/敌人系统 v0.14+registry 两件+06 收口+roadmap 卡⓪行+出口报告(design/qa/iter17-datadrive-exit-report-2026-10-06.md);遗留人闸两项随报告(掘进蓄能模判定留编排器侧=词汇表边界/主动件 kind 词族全量 rename 不随批)+实机目检归用户。**下一步待用户=实机抽查**。 +- **更新于**:2026-10-06(**迭代18规划批落籍 master 018435e**,worktree g-plan2-iter18plan 单提交 0d80626+主区 merge --no-ff,工作日志 381;纯文档零代码零测试):用户令——M4 数值批移 Alpha 阶段最后=迭代 18 ⑥收官卡(门=内容卡①-⑤出口,内容补全先行、统一数值调整殿后)+五内容卡立项:①奇物内容补全·文档先行(charge 补件设计〔实查现恰 3 件〕+06 资源入池条收窄收口〔刷新件与门票实已随迭代 15②入池〕)/②呈现优化六件(与迭代 16 语义零重合=其 token 下游)/③武器锻造系统设计提案(06 新立专条)/④生涯实装(门=卡④提纲评审)/⑤混编遭遇首批投放(MC 取证顺延⑥收官批);06 七处+07 一处状态随迁;资产升档批优先级再降维持挂起;**下一步待用户=讨论迭代 18 各卡任务书**(B 文档批零代码成立、C 与迭代 16 零重合两问结论已呈报) +- **更新于**:2026-10-06(**暂停层只读入口批完成**,迭代 17 卡③功能性 UI 交互批——纯呈现零数值改动=零 MC,worktree iter17-pause-20261006 基点 44328b1,两会话接力〔前任完成 T0/T1+组 B 代码后额度中断,续跑会话收尾跑闸+T3-T5〕,全部提交在 worktree 内未合并,台账 380):战斗屏/关卡地图屏各一枚「暂停」常驻角标开只读层(余额徽+三柜台进度+「继续」,零购买零写通路;badge 已工厂化入 ui_kit,营地钥石徽同步迁移观感零变化);他模态开着角标置灰不可点(has_open_excluding)。闸=冒烟 788/0(基线 783/0)+run_all 17 套+四探针全绿(含输入锁 case⑯/只读巡检/零动效契约/对比度新对)+doc 三闸零新增;存量移交两项=熵增闸 design/diagrams 越界(00712d5 落籍漏步,主区同红)+06/07 gdd_lint 存量注记红(本批零触碰)。出口报告=design/qa/iter17-pause-exit-report-2026-10-06.md。**下一步待用户=实机目检**(design/qa/iter17-pause-human-gate-checklist-2026-10-06.md,重点含排播排空副作用确认项 D2);过闸后合并落 master、06 目标条销号。 +- **更新于**:2026-10-06(**生涯界面设计提纲批完成**,迭代 17 卡④纯设计批——零代码、零数据面、零测试执行,worktree iter17-career-20261006 基点 fc4dbde,全部提交在 worktree 内未合并):设计提纲已按 P0 甲裁定写成正式设计文档 design/gdd/systems/跨局循环/生涯.md v0.1(状态=范围草案),五节写全(信息架构/承载内容清单/与既有界面关系/分期实装建议/待裁点);P0-P5 六个已裁决结论全部落进对应章节,任务书 §2 十条现状事实(F1-F10)逐条对照代码复核全绿;复核新发现一项待用户裁决点「P6 营地底栏『最近冒险』条带去留」(数据源与生涯历史回看同一份存档摘要、条带拼行无上限,扩容后会爆——建议期二时撤下、逐局回看整体归生涯)。同批改动:02-系统清单.md 根索引补生涯行与 CAREER 代号行、06 生涯条状态行改「设计提纲已成文待评审」、roadmap 迭代 17 卡④行改已交付待评审、出口报告落 design/qa/iter17-career-exit-report-2026-10-06.md。**下一步待用户=评审设计提纲**(评审时随裁 P6);评审通过后期一/二/三实装批另行立项,本批未预占任何排期。 +- **更新于**:2026-10-06(**迭代17五任务书联审修订落籍 master 68b8f15**,纯文档零代码零测试,工作日志 378):五个 subagent 并行审查五份任务书(卡0①②③④),挖出约 50 处问题、8 处开工即停机级;用户十裁(Q1-Q10)全落——卡0 v1.1 效果闭集扩 2 型+charge_full 事件+level_clear 归登记+对拍判据两套(确定性复放=逐位/MC 统计量=同带 m4iter12 口径)+映射表五处修正;卡① v1.1 **P3 改裁丙撤投放**(一裁乙与首遇教学位纪律三面冲突)+P1 补遭遇骰交互(每行一骰·RNG 扩签先报批)+实例总数≤4 消歧+R6 行序契约+F7 勘误 19 行 9 种,估时 3.75d;卡② v1.2 P3 转正顺延重扫读数后+检算工具 WEAKEN_PCT 入同步面+T3/T5 并账+估时勘误段一 4.25d;卡③ v1.1 R4 快照口径收敛+Q10 甲 badge 工厂化(营地徽迁入 ui_kit)+P1 丁 06 Esc 登记补办+基线换代 iter16ui 后;卡④ v1.1 F4 事实改正(recent_summaries 现役 cap=10,非「无历史记录面」)+P2 甲口径重述(扩容+补用时埋点)+总局数/通关局数涉 save v6;roadmap 四行+06 四处+07 两条义务(T7 判读带案C 复核+后续内容批投放六面)随批换代;**五卡零待裁点重建,开工门=U0**。 +- **更新于**:2026-10-06(**迭代17四卡18项裁点全裁落籍 master d5df1f6**,纯文档零代码零测试,工作日志 376):卡①P1甲/P2甲/P3乙投放教学位行/P4乙否推荐Boss行亦放开(零内容投放不变,Boss多行内容归后续批);卡②P1甲维持2场·magnitude[60,85]当量锚复算/P2甲不对称有意/P3甲转正/P4甲不立直购价;卡③P1丁/P2甲/P3甲/P4甲;卡④甲×6;四任务书裁决回填+roadmap四行+06六处换代,**四卡全部零待裁点,开工门=U0**;四段发射prompt已交付chat(用户逐卡贴goal,卡0段本会话早前已交付)。 +- **更新于**:2026-10-06(**批次工作流标准清单固化进 CLAUDE.md**,worktree g-plan2-workflow-checklist 单提交 8438d4c+主区 merge --no-ff,工作日志 375;纯文档零代码零测试):用户确认十三条链路工作流无问题并选 A 案固化——CLAUDE.md 新增「批次工作流标准清单」节(开工前三步/环境与基线三步/实施两步/收口四步,每步附既往事故出处,总索引定位与细则冲突以细则为准)。 +- **更新于**:2026-10-06(**内容数据驱动扩展批任务书 v1.0 落籍 master 52c8bea**,迭代 17 卡⓪头卡插队,四卡顺延;纯文档零代码零测试,台账 373):用户盘问轮八裁决全落(发布订阅同步 emit/一个改写窗口/组合规则封冲突/旧 23 挂点枚举删除/现有实现全改造+06 在册只登记 3 事件/框架先行/一迭代四模块垂直切/术语纪律禁自造词);词汇表定稿=实施 17 事件+效果类型 16+组合规则 5+23 挂点逐行移植映射表;T0-T7≈4.5d **零待裁点,开工门=U0**;任务书=design/research/任务书-内容数据驱动扩展批-2026-10-06.md;roadmap 插队行+06 提案条目换代已立项。**下一步待用户=发令开工(U0)**。 +- **更新于**:2026-10-06(**营地内滚提前断流修落籍 master 7d3e88f**,worktree g-plan2-uialign 单提交 a650fd3+主区 merge --no-ff,工作日志 374〔撞号改登——373 系并行数据驱动任务书批先落,先例同台账 335/96/90〕):用户实测「列表没展示到框底、底下空一大段就出现滚动条」——根因=工坊/图鉴树两处 ScrollContainer 写死固定高(336/200),面板本体撑满中段全高,行只滚到固定高处断流;修=size_flags_vertical EXPAND_FILL 撑满面板余高(内容超出才滚动);闸=冒烟+probe_gate --click 全绿+审计探针问题=0+pair_2 重摄+主区复跑同绿。 +- **更新于**:2026-10-06(**用户实机营地两缺陷修落籍 master 6089e79**,worktree g-plan2-uialign 单提交 dd19c48+主区 merge --no-ff,工作日志 372):用户实机截图反馈两缺陷——①营地背景透出战斗画面(根因=T5 石纹降 0.14 后 0.45 半透 dim 失去遮蔽,对局中开营地底下棋盘/战斗提示条全透出;修=底改不透明暖亮档 BG_BRIGHT,demo #v-camp 实底同款)②右柜奖励格图鉴裁出屏(根因=柜台行标签按文字全宽算最小宽度,「累计通关 28/2 次·已解锁·能力封存中」长文案随档变长把三柜总宽撑爆 1248 可用宽;修=三柜 EXPAND_FILL 等宽分屏+五处行标签 clip+ellipsis 截断兜底+进度列 110→150+状态列 tooltip 全文兜底+锻造行距 12→8);判官(visual-judge)复判 pair_2 pass 零保留(三柜实测 406/359/441 均入屏、背景无透出);闸=冒烟 782/0+probe_gate --click 全绿+ui_audit_probe 问题=0+主区复跑同绿;审计报告 ②营地行换代(design/qa/ui-audit-vs-demo-2026-10-06.md)。 +- **更新于**:2026-10-06(**/goal UI 对齐批全落籍 master 7356105**,worktree g-plan2-uialign 分支 uialign-20261006 七提交 58e8903..98493c8,两轮主区 merge --no-ff 2ca6288/7356105,工作日志 371):goal 令「将游戏的 UI 对齐 demo 的 UI」收官——实机九屏对齐 demo v2.1(基准=artwork/ui-redesign-2026-10-04/src/ui-prototype.html md5 78ebbee0):①底栏 85px 定高单条(放弃 90 小钮+特性行限宽截断+tooltip 全文+深墨,爆栏根除)②结算 DIM_NIGHT 0.62/0.6 压暗+两列账行+紧凑钮 ③标题屏主操作反转(有档=继续上局主黄钮)④E5 回滚 blast→TG_MAGIC(TG_AOE 只接 aoe)⑤背景氛围层 UiKit 单点(石纹 0.14+径向暗角+斜光带+地图渐变 #4A3B2A→#3A2E22;start/map/camp 旧满铺石纹退役,换层 tint 保 0.14 档)⑥D1 竖卡 232×352+图标槽 76/60+类型脚注行 ⑦D2 商店全屏页 page_shell(56px 通栏顶条+爬价规则行实数投喂 shop_bp_visit_buys×shop_price_a+底注池源;三选一同壳根底档)⑧设置两列对齐+主音量滑轨实高修(HSlider 槽按 stylebox min 高绘制、零 margin=不可见实测)+减动效核证(reduced_motion 单设置不拆行)⑨地图顶栏关卡名框板+GP 烛光胶囊;**自验=goal 判官(visual-judge)两轮独立目检 9/9 pass**(初判 7pass/2partial/1fail→四修→复判全 pass,对比图 design/qa/ui-audit-2026-10-06/ real+pairs 终态);06 两条换代(D1/D2 三项已落地/审计差距清单六项闭环+五项留档)+审计报告批末判定节;闸=开工基线 782/0(4336fd2 净态)+每轮冒烟 782/0+批末 run_all 16 套+全探针绿+doc 三闸零新增(2 处 06 陈旧断链=基线随 merge 自愈)+主区复跑(冒烟 782/0+click 全绿+run_all 17 套);**并行冲突实录**=ff23023 三选一刷新批 mid-flight 落 master,curio_pick_overlay.gd 五处冲突 union 解(页壳+付费刷新两保,探针零改过闸);**遗留留档(06+报告 §批末判定)**=camp 面板明度/战斗棋盘底板(样张冻结域)/地图道具槽(台账 352 功能件)/稀有度脚注词(数据面无投喂)——实机终验归用户(F5 逐屏目检)。 +- **更新于**:2026-10-06(**三选一刷新对齐商店落籍 master**,worktree g-plan2-curio-refresh-gp 分支 m4-curio-refresh-gp 单提交,主区 merge --no-ff,工作日志 370):用户令「三选一对齐商店,得有刷新」——刷新钮此前只有免费双源(刷新件 X/冒险池 Y)且余量 0 整钮隐藏、无付费档=常态看不见;本批=免费位用尽转 GP 付费档(价 ceil(20×1.8^已付费刷) 读商店同两旋钮、扣同一冒险钱包、重抽同共享池、付费刷计数每停点清零)+钮常驻选择态(两源皆穷禁用不隐藏)+通关停点刷新重发直落选择面(不再重开胜利总结面);店内门票专长三选一维持无刷新(扩面须先裁门票保底计数交互,已注记);落文=秘宝.md 双源节+冒险结构 v0.16+06 商店刷新条目换代;闸=run_all 17 套(新增 curio_refresh_paid_test)+冒烟 782/0+probe_gate --click 全绿+主区复跑同绿;实机目检已过(2026-10-06 用户确认验收,会话收口)。 +- **更新于**:2026-10-06(**内容数据驱动扩展架构调研落籍 master ced5fb7**,worktree g-plan2-research-ext 分支 research-content-ext-20261006 单提交 d2dacc5,工作日志 369,零代码零测试):用户两问=改已有物品数值/名字/稀有度**纯配置成立**、换新效果/机制类新专长**现状必改代码**(专长 23 挂点封闭枚举+match 翻译段 battle_orchestrator.gd:101-179+消费点约 24 处散布;秘宝被动件按 id 硬编码六件;效果文案硬编码三处;**技能池=全数据驱动范本**);mod 游戏四共性(Minecraft 数据包/Factorio 原型/RimWorld Defs,前两者官方文档核对);备选案甲(清秘宝+文案入表≈1天)/乙(效果类型统一+时机埋点≈3-4天分期①专长②秘宝③令牌,荐)/丙(外挂脚本层挂账)三列全备选;**待用户**=四拍板点(立项选择/排期/词汇表范围/文案表值化)+疑似缺陷 main.gd:475 未传 attunement/tree_rules(营地装配疑似不生效,已复核传参面未实机复验)——两项均登记 06;报告=design/research/研究-内容数据驱动扩展架构-2026-10-06.md。 +- **更新于**:2026-10-06(**实机反馈三连修+全屏 UI 审计落籍 master 797bb2f**,工作日志 366-368):①标题屏玩法文字墙删除(用户令:界面禁玩法说明文字墙——教学内容归 Beta 帮助手册〔教学引导定稿批 P4 丙〕)+钮高纪律 BUTTON_MIN_H=40 单点+ui_struct_probe equal_height 同容器钮高断言族(f823abf,附 ui_contrast_probe .uid 侧车补 127a502);②续玩停点恢复 main.gd:717 崩值修(a2b7c21——resume 三选一停点分支先于 level_started 同步 emit,GP 总结 lambda 求值时 runner 未赋值;修=runner 缺位守卫+断点快照 gp 兜底;ui_struct_probe⑦resume 回归专项先红后绿实证);③全屏 UI 审计(f523036/797bb2f——实机十二屏窗口化截图 ui_audit_probe × demo v2.1 纯净模式浏览器现截九屏,逐屏并排对比图九组+差距报告=design/qa/ui-audit-vs-demo-2026-10-06.md);**下一步待用户裁决**=底栏按 demo 88px 重设计草案(放弃小钮/特性行限宽/淡墨换深墨)/E5 爆破带回滚(demo 紫带证据:blast 应归 TG_MAGIC)/标题屏主操作语义/结算仪式感/背景处理——清单已登记 06;并行在飞 worktree g-plan2-standee-tier(art-standee-20261006,非本会话)勿碰。 +- **更新于**:2026-10-06(**三选一空池降级修复落籍 master 02fcc55**,用户令 bug 修复 mini-loop,worktree fix-curio-empty-skip 单提交 059b545+主区 merge --no-ff,worktree 已清,工作日志 365):专长零配置形态下 Boss 后「战利品三选一」空池死面卡死修复——core skip_empty_curio 停点消化出口(仅空候选放行,零自动推进契约不破)+controller 三入口(通关停点/奖励节点/店内门票)空池不开面直落后续推进+跳过 toast;「有 3 显 3 有多少显多少」系既有行为零改动(本批唯一新增=0 件跳过);两呈报=feats.tres 在册 27 行与用户「零配置」口径不符(疑实机跑旧版本/旧档)+问号卡=feat_row 查空兜底「特性\n???」非空池形态(复发须用户给版本/档龄对拍 resume 的 bonus_deck 令牌);未跑闸(测试执行两入口铁规挂账),实机验证归用户(新档打第一关 Boss:总结点继续应直进下一关+跳过 toast)。 - **更新于**:2026-10-06(**迭代改号批:迭代 16=UI 设计对齐批、原四卡顺延迭代 17**,纯文档零代码零测试,主区直改未提交,工作日志 363):roadmap 原迭代 16 四行(敌人行集多行/M4 数值/暂停层只读/生涯设计)改号迭代 17,原位新立迭代 16=UI 设计对齐批(事实源=UI预览demo三路改进整合对照-2026-10-04.md;红线=零美术资源改动只改 UI 设计代码;状态=待办阶段待六分歧裁决与任务书);四份任务书 13 处+06 七处状态行同步改 17;UI 任务书 P1-P11 裁决落文审计(只读)=11 点全裁齐可发令,5 处陈旧标记待用户「清」令(标题草稿字样/§8.2 节头/P10·P11 小节「未裁」/T6 依赖列)。 - **更新于**:2026-10-05(**奇物词族对齐与渠道分离批 T0-T8 全落籍 master 3c6f2c9**,worktree curio-wordfamily 八提交 d4cde73..33f1842 含 master 同步,主区 merge --no-ff 3c6f2c9,台账 362):迭代15②号卡收官——三套词族代码层全换(feature_→curio_ 93 处;bonus_ 运行层→curio_ 全族+两文件改名;relic_→feat_ 三文件改名);渠道白名单分离(总池-渠道子集多级缓存形=用户裁);门票入表(provisional 20%/2/1/5+拿到即开+保底/限购/共享计数+autoplay 分支缺陷批内修复);refresh 入池(B案 heal 5→3 挤占);shop 补 heal 行;T2 裁点甲案=六存档键禁改(含漏登记 "relics" 键);charge 缺陷登记不硬补;reward_overlay 基线红红转绿(M7 bcard 成因)——**run_all 16 套历史首次全绿**;闸=主区冒烟 782/0(+10)+五探针绿+doc 三闸零新增+MC n200 只测不判(82000 锚留盘);遗留=07⑰ 目检+C1/C2/C3/C4 四移交;出口报告=design/qa/iter15-2-curio-exit-report-2026-10-05.md。 - **更新于**:2026-10-05(**迭代 16 四份任务书起草并登记入册**,纯文档批零代码零测试,worktree iter16-taskbooks-20261005 单提交 ffd45eb+主区 merge --no-ff 446d5f9,工作日志 361):四份任务书落 design/research/——①敌人表行集多行批(同一坐标行集恰一敌种校验放开,enemy_table.gd:108-112 校验点/config_db_resolver.gd ceil 解析/unit_container.gd:47 spawn 等代码考证在册;T0-T8≈4.25d,4 项待用户裁点〔混编数量语义/行集解析双算法处置/首行投放与否/Boss 行放开与否〕)②M4 数值批(削弱终值+数值点评五条+迭代 10 联动欠账①②+元进度定价剩余面四件合一,T6 四带复算与 T7 节奏重测两件读数义务随批,damage_probe A1 改前既有红灯顺路修;T0-T8≈6.0d,跑批一律先报批,4 项待裁点)③暂停层只读入口批(局内暂停层只读看钥石余额与三柜台进度,复用 UiKit 公共件零新素材;T0-T5≈3.0d,4 项待裁点)④生涯界面设计立项(只做设计提纲不做实装,T0-T2≈1.25d,6 项待裁点);roadmap M-4 表追加迭代 16 四行(门=迭代 15 出口)+迭代 15②卡标注修正为「文档+数据层落籍(c571af2),渠道分离与入池实施段+curio 换名回归迭代 15 继续完成(2026-10-05 用户令)」、迭代 15 整行保持开放;06 七条目状态行追加「已编入 roadmap 迭代 16(任务书已成文待 U0)」注记。**考证差异两处已呈报**:任务背景称 master 头=8944dca,起草时点实际=413c936(ui-struct-probe mini-loop 与 archify 两批在背景成文后落籍,任务书基线数字一律写 T0 重测回填);「调参批数值点评八条」与 06 条目现行登记五条不符,任务书以条目五条为准(任务书 §2.2 F6)。 @@ -832,3 +849,32 @@ 363. **迭代改号批:迭代 16=UI 设计对齐批、原四卡顺延迭代 17(2026-10-06,用户令「迭代 16 改实现 UI 优化需求、原四卡顺延 17」;纯文档零代码零测试,主区直改未提交)**:①roadmap.md——原迭代 16 四行(①敌人表行集多行②M4 数值③暂停层只读入口④生涯界面设计)逐行改号迭代 17,原位新立迭代 16=UI 设计对齐批行(事实源=design/research/UI预览demo三路改进整合对照-2026-10-04.md;**红线=不修改美术资源**:只改 ui_kit 色板 token/字号档/布局/对比度/抖动/panel_skinned 接线等设计代码;无分歧可直接收项与六分歧前置点写入行内;状态=待办阶段零任务书)②四份任务书「迭代 16」字样 13 处改「迭代 17」(含卡间互引卡③卡④)③06-待办清单 七处「已编入 roadmap 迭代 16」状态行改 17④active.md 两处「迭代 16」系历史工作日志记录未动(只呈报)⑤交付迭代 16 开发发射 prompt(chat,自包含含 U0 预批准+裁决点收口第〇步+worktree iter16ui 纪律+T0 重测回填)⑥UI 任务书 P1-P11 裁决落文审计(只读核查,另一会话落文):11 点全裁齐(P1=甲/P2=丁 10-05;P3 甲/P4 乙/P5 甲/P6 甲/P7 乙/P8 甲/P10 乙/P11 甲 10-06 采纳推荐;P9=注销清单)与 §8.3 三层优先级栈一致、估时重算≈6.75d 三段切分、迭代 17 改号已传导——**可发令开工**;5 处陈旧标记呈报待清(标题「v1.0 草稿待裁」/第 9 行「裁点未决前不实施」/§8.2 节头「待用户裁点」/P10·P11 小节标题「未裁」/T6 依赖列「**P3**」未改✅格式);知情两点=任务书基线 446d5f9 已漂至 3c6f2c9(T0 重测回填覆盖,15② 落籍后「不触碰在飞」停机点自动解除)+§8.3-D 三条结构级差异「未裁」系设计如此(已登记 06 不入本批)。**遗留**:5 处陈旧标记待用户发令清理;开发 session 发射待用户贴 prompt。 364. **UI 设计对齐批 T0-T11 全落籍(2026-10-06,迭代 16;开工令 prompt=U0 预批准;worktree g-plan2-iter16ui 分支 iter16-ui-20261005 十二提交 85ea024 T0/a55a302 T1/b38ae07 T2/4e7aa83 T3/af5f317 T4a/b26e3fb T4b/2f74da8 T5/e0fd7cb T6/1b2ecf6 T7/e5427af T8/ce3dab9 T9+T10/33defc0 T11 收口,主区 merge --no-ff fa54de1)**:任务书=design/research/任务书-UI设计对齐批-2026-10-05.md(v1.0 定稿,P1-P11 裁决已齐;五处陈旧标记随第〇步文档批擦除)。**第〇步文档批先行落籍(a39d044;临时分支 iter16-docbatch-20261006 承载提交后快进回 master——主区直提系 worktree 铁律守卫拦断面,按守卫「提交在任务分支完成」语义处置;七指名文档逐文件 diff 复核后指名 add)**。**T0 基线重测回填**=冒烟 782/0+run_all 16 套全绿+probe_gate --click 绿+layout exit0+doc 四闸基线定性(gdd_lint 2 文件/死链 2 真实+2 环境假红〔.zcode/plans+archive 不入库〕/term 218/熵增 diagrams——全部既有在册守零新增)+demo md5 零漂移(78ebbee0,Q9 丙留痕后未回读)+事实表 15 项复核(五漂移注记:clampi 实在 :254/RED_MINE 实值 #962C24/字号普查 78→107/INK_LABEL 定义 :19/_register_modal :346)+**七点对比度实机复算三处修法改向**(意图徽标直背=暗舞台底 1.5-3.3:1→demo「换墨降档」实机反向→改 PANEL_DATA 垫牌:atk 零换墨 5.27/buf 换 HERO_BLUE_DEEP #1E5A96=4.78/charge 换 GOLD_INK #74500B=4.88/debuff 随牌 4.66;槽两态字直背=亮石皮→demo「提亮 WARM_FAINT」反向→改深化 STONE_DEEP #554D41=5.60+INK_LABEL=10.40;RAP 实机 10.40 已合规=零改动,仅显式传色)+15② 交集零命中。**T1-T8 实施**=T1 status_slot_row 九枚 preload→load_tex(呈现层素材 preload=0);T2 七处修法落码+**ui_contrast_probe 新增入 probe_gate 默认段**(11 对测点 WCAG 实算+裸 Color8 白名单静态段+preload 红线段)+art-bible §7.4 合并豁免条款(P11 caption 12px+血数条带内读数同条);T3 站桩纵向余量(STANDEE_FOOT_MARGIN=12·名行基线 -1→-13·底座矩形收单一来源)+ui_struct_probe ⑥standee 断言族(内容矩形⊆舞台+底缘≥8px——首跑抓左右 6px 容器边距系断言语义过严改任务书口径);T4a 裸 Color8 全量 25 处/10 文件原值搬运收 ui_kit(任务书 N4a 记 5 处系漏记)+TG_* 五族立项+tg_band() 同源分族判定(Q4 甲三选一/货架同判;aoe/blast→TG_AOE #E25676=reward.tres 现成 spark_aoe/clear_blast 消费面;max_hp 归回复族);T4b P7 乙五 token 改向 demo 值(Δ≤6/255)+§7.2 五件对齐注记;T5 P4 乙 panel 切片 8→16(PANEL_SLICE 单点)+Q1 丙工厂 content margin clamp(9 消费面 7 处自动抬平调用点零改)+P5 甲 RADIUS_OBJECT=5 收敛 26 处(例外 17 底座/200 金印/100 脉冲环保留)+§7.1 两条注记;T6 P3 甲 FS_* 六档全库归档 107 位点(就近映射 40→34/26→24/22→24/20→18/13→14/11→12;11px 六处提 FS_CAPTION 并入豁免清单;settlement_fx span 错位写入两处当批发现当批修复)+art-bible §7.4 档位表+逐处豁免清单;T7 P6 甲缩放纪律(状态九枚常规档 32px=1×·mini 16 维持/柜台四枚 64 源 32px=0.5×/BCard 槽 40px=2×/地图头像定高 48 宽随源比=纵横异倍消除)+P10 乙记忆态换装「地板纹理+灰罩 α.66」(平涂退役;记忆墨对合成底 6.71:1 实测复算)+art-bible §8.2 例外登记+§6.5 记忆态条款换代;T8 P8 甲 bcard 卡顶 DitherBand Bayer 4×4 抖动带试点(平涂 ColorRect 退役;全站/B 区范围留后批)。**T9 重闸全绿**=冒烟 782/0(断言数持平)+run_all 16 套全绿+探针五件(click done=true 问题=0)+layout+doc 三闸/熵增零新增+素材零触碰(art/artwork 未改一字节)+MC 零跑(纯呈现批——核心/数据层 diff=0 实证)。**T10 人闸材料包**=design/qa/iter16-ui-human-gate-checklist-2026-10-06.md(A 整屏样张四项〔Q2 乙并入〕+B 八屏对比+C 十专项+D 减动效+E 代拍板追认八项〔RADIUS_OBJECT=5/三新墨/TG_AOE 归族/牌宽 8/抖动参数〕)——实机目检归用户。**T11 收口**=roadmap 迭代 16 行落籍标注+06 新登记「呈现层残余 <4.5:1 文字位」(站桩名行四墨/敌顺位徽标 GOLD_DEEP/RAP 反馈 STONE_FAINT——批外邻接发现待后批)+出口报告=design/qa/iter16-ui-exit-report-2026-10-06.md(T0 基线回填表/逐卡落点/N5 面板皮造册〔穿戏服 9 面+素面零补皮〕/重闸读数表/偏差八条汇总)。**美术红线全批合规**(素材字节零改动);预算 6.9d 实际单会话一次跑完(三段并一次合并=Q7 甲);worktree 待清。 + +365. **三选一空池降级修复落籍(2026-10-06,用户令 bug 修复 mini-loop;worktree g-plan2-curio-skip 分支 fix-curio-empty-skip 单提交 059b545,主区 merge --no-ff 02fcc55,worktree 已 remove+分支已删)**:用户实机报缺陷=打完 Boss 弹「战利品三选一」全问号且无法选择,进程卡死(用户令降级策略:向池子取备选项有多少显多少、0 个直接跳过环节)。**成因核证**=CurioPickOverlay 空候选死面:通关渠道白名单过滤后只出 relic 令牌(迭代15② T4),专长池空时 _draw_curio_candidates("level_clear") 返回空集仍挂三选一停点,open_pick(空 cards)=无卡无确认钮无继续钮。**修法四处**:①core adventure_runner.skip_empty_curio() 新增=停点空候选消化出口(仅空候选放行——continue_to_next_level「停点未兑付拒推进」守卫因此放行;非空停点拒绝=零自动推进契约不破,正常消化仍只走 pick_curio/apply_node_curio 兑付);②controller resume_if_pending() 空候选不开面,消化停点后按上下文直落后续推进(level_clear=推进下一关/reward_node=continue_after_node 回图选路/feat_ticket=只消化关面回店,与 on_continue 分派同构)+toast「可用战利品为空——本次三选一跳过」;③controller present_node_curio() 奖励节点空池不开面不挂节点停点,兑付确认直落回图选路(auto-sim「node_entered 内立即 continue_after_node」先例);④on_presented() feat_ticket 分支末道防御(core _redeem_feat_ticket 空集守卫外)。**「有 3 显 3 有多少显多少」系既有行为零改动**(洗牌收集至池空为止,上限 curio_candidate_count=3;卡片渲染按候选数遍历)——本批唯一新增=0 件跳过。**两呈报待用户**:①feats.tres 在册 27 行(迭代12 T1 批 2026-10-04 落),用户「还没配置任何专长」口径与仓库现状不符,疑实机跑旧版本/旧档;②「界面全是问号」唯一代码来源=feat_card_text 空 row 兜底「特性\n???」=候选非空但 feat_row 查空形态(疑旧档 bonus_deck 令牌 feat_id 与现行表不匹配)——空池跳过对该形态无效(候选非空不跳,但可点选兑付不卡死);复发须用户提供运行版本/存档档龄对拍。闸=未跑(测试执行两入口铁规——非 loop 批收口跑闸当场问,自主会话无法问故挂账);验证路径=实机新档打第一关 Boss:战斗总结点「继续」应直进下一关+跳过 toast(约 2 分钟)。存档键/存档格式/数值零改动。 + +366. **实机反馈两缺陷修(2026-10-06,用户截图裁决;worktree g-plan2-iter16fix 分支 iter16-fix-20261006 两提交 8e490b4/9fb8b74,主区 merge --no-ff f823abf/127a502)**:①标题屏「玩法说明」文字墙=用户痛斥**删除**(非修裁切——问题本体=文案不该存在;设计铁规:界面禁玩法说明文字墙,教学内容归 Beta 帮助手册〔教学引导定稿批 P4 丙「零再教」〕;ui_assembler 帮助牌块删+M2 注记换代,历史文案在 git);②钮高纪律 **BUTTON_MIN_H=40** 单点立(主/危/常规钮统一最小高——标题屏主钮 26px 对常规 38px 不齐治根;min_w=0 紧凑位豁免同宽度纪律)+ui_struct_probe **equal_height** 同容器钮高断言族(width 216 有纪律而 height 无纪律=漏机根因);轻闸=冒烟 782/0+三探针绿(equal_height 全屏首跑零红);附=ui_contrast_probe.gd.uid 侧车补提交(.uid 必须入库规约——worktree 编辑器导入才生成致上批漏随,主区跑测再生一份撞合并,diff 同哈希后删未跟踪件再并)。 +367. **续玩停点恢复 main.gd:717 崩值修(2026-10-06,用户实机 F5 抓到——真 bug 用例漏了;worktree g-plan2-iter16fix2 分支 iter16-fix2-20261006 单提交 c32bb6c,主区 merge --no-ff a2b7c21)**:resume_adventure 的 bonus_pending 分支在 level_started 之前同步 curio_choice_presented.emit(adventure_runner:250 提前 return true)→ 胜利总结 GP 行 lambda 求值时 runner 尚未赋值(null.gp 炸,调试器停 717);修=_gp_summary_text runner 缺位守卫+断点快照 gp 兜底(合计语义=恢复面合计即存档合计;与 _bonus_summary_text 同款守卫式);回归=ui_struct_probe⑦resume 专项**先红后绿实证**(夹具显式 main.runner=null 置全新启动态——首版夹具推过冒险残留 runner 非空,红因不合真机已修正;Nil.gp 同栈复现 resume_adventure:250←_resume_from_menu:437→修后总结面开+「合计 24」断言);轻闸=冒烟 782/0+run_all 16 套+probe_gate --click 全绿+主区复跑同绿。**用例漏了**的空档=呈现层「续玩恢复到停点」组合根链路此前零覆盖(core 断点往返有测、lambda 面无测)——凡新停点/恢复分支必须带呈现层 resume 断言。 +368. **全屏 UI 审计(2026-10-06 用户令返工「每个界面截图和 demo 对比」;worktree g-plan2-uiaudit 分支 uiaudit-20261006 两提交 34eb460/9d0410e,主区 merge --no-ff f523036/797bb2f;零代码修改)**:实机十二屏窗口化程序截图(新探针 game/smoke/ui_audit_probe.gd——shot_probe 十二屏扩展形,真实信号路径+拟真数据,2304×1440 DPI 归一 1280×800)×demo v2.1 浏览器现截九屏(本地 HTTP+纯净模式 body.clean+全视口 PIL 裁剪——IAB 同单元格连拍 clip 会平铺花屏的坑在册);九组并排对比图+逐屏差距报告=design/qa/ui-audit-vs-demo-2026-10-06.md(差距排序:①地图底栏〔demo 85px 单条+放弃小钮 vs 实机特性行爆栏+216 红砖+特性组暖白淡墨 §7.2 违例;重设计草案=定高 88/放弃小钮/特性行限宽+深墨,待裁〕②商店/三选一形态级 D1/D2 坐实③结算仪式感④背景全局差距〔demo 暖渐变聚焦 vs 实机全铺石纹〕⑤标题屏主操作语义反转⑥E5 反证=demo 爆破卡紫带应归 TG_MAGIC;demo 无映射三屏=调试浮窗/图例/确认框仅存档;demo 有玩法速览牌=实机按用户令删除不回补);06 新登记「全屏 UI 审计差距清单」条目汇总待裁项。 +369. **内容数据驱动扩展架构调研(2026-10-06 用户问「改物品设计是否只改配置/机制类专长能否零代码」并令学习 mod 扩展游戏实现;worktree g-plan2-research-ext 分支 research-content-ext-20261006 单提交 d2dacc5,主区 merge --no-ff ced5fb7,worktree 已清;零代码修改零测试)**:现状核证(Explore 代理全库扫描+本人抽查 feat_table.gd/battle_orchestrator.gd/treasure_item_table.gd 三面复核)——①改已有内容数值/名字/稀有度/投放=纯配置成立(全内容族 .tres 数据表+schema 封闭枚举校验+悬空引用加载致命,config_db.gd:12-31 统一装载);②新效果行为必改代码:专长 23 挂点封闭枚举(feat_table.gd:36-67)+求值段逐个 match 展开(battle_orchestrator.gd:101-179)+damage_controller/orchestrator 消费点约 24 处+流派档位质变硬编码:171-175;秘宝主动件按 effect_kinds 通用分发(battle_orchestrator.gd:648-666,好形态)但被动件按 id 硬编码六件(venom_edge/luck_coin/dawn_order/nest_counter/flag_burst/pick_refresh)+dig_charge 特判:643;奇物令牌 match 分发(adventure_runner.gd:412-449);效果文案硬编码三处(battle_narrator.gd:104-166/curio_interact_controller.gd:185-204)=新件无名无声根因;③**技能池=全数据驱动范本**(skills.tres 加行+enemy.tres pool 引用全链路零代码,Boss 半血解锁=unlock_hp_pct 字段)——差距=把专长/秘宝/令牌提到同等水平;④mod 游戏查证=minecraft.wiki/w/Data_pack+wiki.factorio.com/Modding 官方原文核对(rimworldwiki 反爬只取入口页按通行记载表述)——四共性:内容=数据/机制=通用解释器、语义事件时机+内容订阅、条件通用词汇跨内容复用、加载期注册+校验闸(我方已有);边界共识=「新机制」仍属代码但压成低频加类型,「新内容」高频零代码(Minecraft 数据包亦不能加新机制);⑤备选案三列=案甲最小修(清秘宝 id 硬编码+文案入表≈1 天)/案乙效果类型统一+时机埋点(专长秘宝令牌统一「触发时机+条件+效果类型+参数」数据形态+结算段语义事件点,≈3-4 天分期①专长②秘宝③令牌;MC 模拟器共用 battle_orchestrator 零改动自动受益;**荐**)/案丙外挂 GDScript 效果脚本层(真 mod 支持≈5 天+,Alpha 前挂账);**四拍板点+疑似缺陷两项登记 06**(数据驱动扩展四拍板点条〔立项/排期/词汇表范围/文案表值化〕+main.gd:475 未传 attunement/attune_refresh_y/tree_rules 疑似缺陷条〔runner 受理面 adventure_runner.gd:128-137 在、smoke 直呼参数面故未拦,本人已复核传参面、未实机复验待令〕);报告=design/research/研究-内容数据驱动扩展架构-2026-10-06.md(主区 Write 被 worktree 钩子拦,按钩子指路 worktree 执行合回)。 + +370. **三选一刷新对齐商店批(2026-10-06 用户令「三选一对齐商店,和商店的购买一样得有刷新」;worktree g-plan2-curio-refresh-gp 分支 m4-curio-refresh-gp 单提交,主区 merge --no-ff,worktree 已清)**:现状核证=CurioPickOverlay 刷新钮迭代11第①批 T5b 已在(X/Y 免费双源:刷新件+营地升级冒险池),但余量 0 整钮隐藏且无付费档=常态玩家永不看见(本次用户愤讯根因);需求检索=06「商店独立刷新机制」条只记商店侧 SR-1,三选一同款付费档全库零成文(口头裁决未落文丢失,本批当场落文)。机制落码四处:①adventure_runner——_curio_paid_refreshes 付费刷计数(每停点清零=与商店进店清零同构,两处停点创建位随 X 余量重置同点清)+refresh_curio 拆 _refresh_curio_common(paid)(免费/付费共用同一重抽体,池规则全保留)+新口 refresh_curio_paid/curio_refresh_gp_cost(读同 shop_refresh_cost_base/curve 两旋钮=与商店单点调参)/curio_stop_pending 谓词(付费扣费前置校验防空转烧钱包);②level_runner——spend_gp 钱包单口(与商店刷新同一 GP 冒险钱包,余额不足 false 零副作用);③curio_pick_overlay——set_refresh_info 换代 set_refresh_display(免费余量,GP价,可付) 与商店同名同构(钮常驻选择态、免费与付费两源皆穷=禁用不隐藏;角标=免费余量、免费行「免费·不消耗 AP」、穷尽后价签行「刷新 N 金币」、tooltip 含池源同源句)+结果态/替换子态/收屏三处收刷新钮(选择态专属,停点已兑付残钮零意图可点)+press_refresh 探针复用口;④curio_interact_controller——on_refresh 免费位先试、穷尽转 GP 付费档(spend_gp 成功才 refresh_curio_paid,拒绝 toast「金币不足——刷新失败」,成交后刷新呈现投喂)+_feed_refresh_display 投喂单点;连带修=通关停点刷新重发载荷带 refreshed 标、on_presented 直落选择面(原路径会重开胜利总结面=迭代11遗留呈现缺陷,免费位常态 0 故从未暴露)。边界=店内门票专长三选一维持无刷新(扩面须先裁门票保底「刷新后重新展示计入」的计数交互,秘宝.md 注记在册);断点快照不载付费计数(续玩回基价——商店进店清零同构口径,X/Y 本就不入档)。测试=新单测 curio_refresh_paid_test 六面(停点谓词/免费优先序 X→Y/付费零触碰免费位/价曲线 ceil(基价×曲线^已付费刷)/停点重建清零/spend_gp 三态)+reward_overlay_test 刷新面断言(投喂前隐藏/付费禁用态/免费点亮/余额可付点亮/press_refresh 外发/结果态收钮);闸=run_all 17 套全绿(历史首次)+冒烟 782/0 三闸过+probe_gate --click 全绿(input 十六断言/struct/contrast 11 测点/click done=true 问题=0/残留 Godot=0)+主区合并后复跑同绿。落文三处=秘宝.md「三选一刷新双源」节 GP 付费档段+冒险结构.md v0.16 行(共享池账本节同款句)+06「商店独立刷新机制」条换代(机制已落码态=SR-1+本批;剩=shop_refresh_free 与曲线两参数调参归 M4 数值批+天赋侧刷新增益形态未定)。worktree 坑在册=新工作树缺 .godot 导入缓存致 run_all 每套件全量重导近似卡死(解法=主区 .godot 整目录+gitignored 字体 .ttf.import 边车拷贝;probe_gate 有冷导入路径而 run_all 无)。 + +371. **地图伤害账殿后批(2026-10-06 用户裁案C——「主角先手攻击一次」歧义质询三轮触发;worktree g-plan2-mapdmg-combatend 分支 mapdmg-combatend 单提交 58dfb56,主区 merge --no-ff)**:用户质询结算顺序出处→考证=顺序自 initial commit(da157e3 前身 09-14 squash 前即成文)代码注释引「2026-09-11 用户裁决」但一手记录在 git 前不可出示;用户定性=「主角先基础攻击→怪物结算」句不含地图伤害时点、地图奖励伤害不算主角攻击(无此定义)、实施序非其设计,令「把有歧义的东西改对」;AskUserQuestion 两裁=时点案C(战斗段末怪物还手后)+修法全仓扫。**裁决落文**=每轮结算定死「①授予段兑非伤害账(资源/增益/治疗/行动点/GP/警觉;伤害形态单据暂存不兑)→②增益/减益→③战斗段〔主角先基础攻击→敌人按生成序逐个还手→地图伤害账殿后〕→④衰减→⑤判定」;主角死于敌方还手→暂存单整体不兑逐单留「未生效(主角已倒下)」行(forfeit=hero_down 载荷)。**切片**=battle_orchestrator 暂存闸 _defer_board_damage/_deferred_board+_submit 案C分流(sync 同闸——清图批 clear_blast 一致延后)+_outcome_of(暂存单「将兑付」回报——完美第二份/高光份数依据开单时点事实,实兑若目标已灭控制器自记未生效行)+授予段四处直提改走闸+战斗段末补提交块+授予段界 flush 保留空队防御位;smoke 分类器换代(damage 恒战斗段/reward_ineffective 按 forfeit 分流)+先手断言收紧(「存在或授予段终局」→「必存在」)+新增案C 段内序断言。**文档**=战斗结算 v0.26(五段表①③行/授予段节/兑付形态表 blast 正向 heal 负向时点/结账时机双模式改「段界结账+地图账殿后」/战斗段节/P0 闭环第5步;顺手清充能退役遗留=三分量句改单分量+三处「主角普通分量」改「基础分量」)+敌人系统(P0 第4步战斗段限定+地图账+死亡来源「授予段直伤」改「更早兑现的敌方伤害技」)+地图战斗资源(流程图/负向恒即时节/五步清单)+01-核心循环(「怪物的先手」用词相撞改「警觉」)+game/README 案C批节+registry entities 两处案C注记(map_reward 负向恒即时节/settlement 段内伤害序)+编排器头部注释。**闸**=冒烟 783/0 三闸过(782+1 新断言)+run_all 17 套全绿+probe_gate --click 全绿残留 0+gdd_lint 与基线一致(8 文件 2 存量);**旧行为 MC 锚全数作废**(授予段伤害致死短路先手语义消失——锚重采归数值批,未跑 MC 遵两入口铁规);**worktree 坑续录**=新树除 .godot 整目录外还须拷 .import 侧车族(gitignored,缺则 ui_font_asset_test「No loader found」红——find -exec cp --parents 解)。**另账在册**=清查报告附注四件(战斗结算 :204 三分量句已顺手清/architecture.md:190 退役 API 名/query_attack 系欠同步/docs/ 悬空路径四文档头注——后三件未动待用户发令);清查正主=歧义三处(战斗结算 :64 原点+五段总览①行+敌人系统 :205)+相邻一处(01-核心循环)全部随批闭合。 + +372. **另账修正批(2026-10-06 用户令「文档修改现在就修正,不需要独立迭代」;worktree g-plan2-docfix-ref 分支 docfix-ref-20261006 单提交 ffb0e36,主区 merge --no-ff ac61b34,worktree 已清)**:①悬空指路句 13 处改写——docs/proposal-skill-pool.md / proposal-status-vocabulary.md 两提案(7f24b640 三层收敛迁 design/research→a6833b6a 死亡清单批依吸收即死删除,引用点漏改=该批提交信息自登记「余 7 条全为搬前幽灵链」的遗留)现指路句〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 技能池节/词汇表〕,落点=敌人系统 4+战斗结算版本史 2+增益状态版本史 1+registry 2+game/README 批节头 2+unit_container/enemy_table/skill_table/smoke_all 代码注释 4;②architecture.md 活契约面旧 API 三处换代——:190 调用序(query_attack→apply_damage→advance → 按摇定技能行兑现→apply_damage→commit_action+案C 段内序注)、:152 数据流图示行(stock/advance 退役→伤害单据 submit)、:176 关卡末行(clear_inventory 随库存轴 2026-09-15 下线→通关结算指路冒险结构;:105 已自带待复核注记/:222/:252 系 OQ/ADR 历史记录不动);③06-待办清单新登记「旧行为 MC 行为锚全数作废——重锚归数值批」(现状/归属 M-4/状态三段式;用户确认系影响数值难度重要变量)。闸=gdd_lint 对基线零新增(8 文件 2 存量)+合并后主区冒烟 783/0 三闸过(run_all/probe 零行为改动不跑——纯文档+注释批)。 +373. **内容数据驱动扩展批任务书 v1.0 落籍(2026-10-06,用户令「生成任务书+改写 roadmap」;worktree g-plan2-taskbook-ext 分支 taskbook-datadriven-20261006 单提交 6f9e010,主区 merge --no-ff 52c8bea,worktree 已清;纯文档零代码零测试)**:继台账 369 调研落籍后的盘问轮裁决全落——架构模型定稿=发布订阅(结算管线同步 emit 事件→EffectEngine 遍历持有集匹配订阅→执行数据行声明的效果;非代码级插件非拦截器链,仅伤害单据一个改写窗口按声明序施加);用户八裁决 R1-R8 全录任务书 §二(含 R4 旧 23 挂点枚举删除/first_dig_spell 与 terrain_dig_double 退化条件行/R5 范围判据=现有实现全改造·06 在册只登记〔curio_roll/shop_purchase/gp_settle 三事件〕/R7 一个迭代四模块垂直切不分批/R8 术语纪律=事件 topic·效果类型·组合规则,禁 hook/合并律/挂账自造词);词汇表定稿=实施 17 事件(扫雷 5/战斗 6/轮结算 3/抽取经济 3)+效果类型 16+组合规则 5(加法/乘法/取真/按序改写/按序消耗)+fact 条件表 ~15;23 挂点逐行移植映射表+秘宝六件迁移落点全列(等价性对拍底单);数据行形态 on/when/do+存档零改动+护栏五条全保留(MINE_HOOKS 改同义校验);T0-T7≈4.5d 软盒、零待裁点、🔴 契约 5 条(存档零破坏/MC 对拍逐位等价/确定性/护栏保留/worktree 脏件)、停机点 S1 首流派对拍 S2 批末闸、代拍板白名单 4 项;**排期=迭代 17 卡⓪头卡插队(用户裁)**,原①-④卡顺延任务书与裁点原样保留;roadmap 插队行+06 提案条目换代「已立项待 U0」+疑似缺陷条保留。开工门=U0 用户发令。 + +373. **M4 数值批任务书 v1.1 案C增补(2026-10-06 用户问「文档改完可否出任务书转迭代」→考证=任务书 v1.0 已在(任务书-M4数值批-2026-10-05.md,编入 roadmap 迭代 17 卡②待 U0),增补而非新写;worktree g-plan2-m4tb11 分支 m4tb-v11-20261006 单提交,主区 merge --no-ff 0bfaef7,worktree 已清)**:①任务书 §2.6 新增案C 三事实——F18 行为序换代(案C 7ef18ac/战斗结算 v0.26)、F19 旧行为 MC 锚与对拍锚全作废(06 2026-10-06 条正主;四带复算跑批未发起零沉没成本)、F20 难度走向预判(案C 主角更安全=挖掘期不再先手致死+保底先手,通关率预期上行;越带归 R1 呈报禁自动调);②T0 推算稿四件→五件(第五件=对拍锚按案C 新序重推导;0.75→1.0d,合计 6.0→6.25d,切分点 T4 末不变=段一 T0-T4≈4.5d/段二 T5-T8≈2.0d);③T5 读数卡 T6 四带复算注明案C 新行为首采立新锚(注意:MC 复算代号 T6 在 T5 读数卡内,卡片 T6=damage_probe 修复——增补时锚点写错卡被断言拦下一轮改正);④基线纪律注换代(master 7d3e88fc/冒烟 783/0/run_all 17 套);⑤§10 留痕+3 条(06 正主条/编排器暂存闸载体)+§11 版本记录节新立;⑥06 状态行+roadmap 迭代 17 卡②行同步 v1.1 口径。闸=gdd_lint 对基线零新增(纯文档零代码零测试)。**转入迭代就绪态:待用户 U0 开工令+发令时顺带裁切分(整批 vs T4 末切段)**。 +375. **批次工作流标准清单固化进 CLAUDE.md(2026-10-06 用户确认 A 案「整条链成文」;worktree g-plan2-workflow-checklist 分支 doc/workflow-checklist 单提交 8438d4c,主区 merge --no-ff,worktree 已清;纯文档零代码零测试)**:起因=用户问 goal 执行日志「先读两份在册记忆+看 smoke 目录与 sim-data README」系何意→三轮问答厘清现行流程(在册记忆=既往事故教训防重踩;看 smoke/sim-data=定基线跑法;非 TDD 而是「规格先行、测试随批」)→用户确认流程无问题并选 A 案固化。CLAUDE.md 新增「批次工作流标准清单」节(插于分支纪律与硬性红线之间):十三条链路五段=开工前三步(先检索后提问/任务书全列备选案/用户发开工令才动手)+环境与基线三步(worktree 拷导入缓存/改码前基线三查/涉数值先采改前读数)+实施两步(新功能必配新测试/验收改动先行声明)+收口四步(批末四闸全绿才合并/合并后主区全量复跑/会话收口落记/实机目检归用户+中间产物吸收即死);每步附既往事故出处;定位=总索引,与细则冲突以细则为准。闸=gdd_lint 不适用(纯文档零测试)。 + +376. **迭代17四卡18项裁点全裁落文(2026-10-06,用户在本会话逐卡裁断:卡①P4乙否决推荐其余按推荐/卡②P1甲其余按推荐/卡③卡④全按推荐;worktree g-plan2-rulings4 分支 rulings-iter17-cards-20261006 单提交 de44416,主区 merge --no-ff d5df1f6,worktree 已清;纯文档零代码零测试)**:四份任务书 §8 待裁点逐项裁决回填——卡①敌人行集多行批 P1甲(每行各自 unit_count·行集总数≤4·UNIT_COUNT_MAX 改单种数语义)/P2甲(floor 版退役全仓统 ceil,工作日志 312 待裁销号)/P3乙(投放 L2 教学位混编行巨蜘蛛1+钩爪恐魔1·行值 provisional·MC 取证先报批)/P4乙(**用户否决推荐甲**——Boss 行集校验同样放开;知情代价=半血解锁/难度带读数面可被 Boss 行内容变量影响;本批零内容投放不变、Boss 多行内容投放归后续内容批、节奏重测义务 07 在册);卡②M4数值批 P1甲(维持 2 场·magnitude 终值当量锚复算 [60,85] 带内落定随出口追认)/P2甲(不对称有意落文设计立场零改动)/P3甲(钥石 3-3-3·2-2-3 转正)/P4甲(不立直购价维持次数阈值单一渠道);卡③暂停层 P1丁/P2甲/P3甲/P4甲;卡④生涯设计 P0甲/P1甲/P2甲(N=20/50 实装期定)/P3甲/P4甲/P5甲;roadmap 四行换代「✅ 裁点全裁+开工=U0」+06 状态行全收;**迭代17五卡全部零待裁点**(卡0 早零裁点),发射 prompt 四段交付 chat,用户逐卡串行贴 goal。 + +377. **M4 数值批移批迭代 18(2026-10-06 用户令「不要影响迭代17,放到迭代18中」;worktree g-plan2-m4move 分支 m4-to-iter18 单提交 c38489a7,主区 merge --no-ff ed38c54,worktree 已清)**:缘起=用户质询「迭代17已在另一会话开发且编排封板,你的修改是否影响别人开发」——查证=iter17dd worktree(⓪数据驱动扩展头卡在飞,基线 7d3e88f 零提交)与我的改动执行路径零交集但 roadmap/06 共享文档构成合并冲突候选+②M4 行被我 v1.1 增补时提前改写(封板越权);用户裁=移批。落面:①roadmap 迭代 17 删 ②M4 行(余 ⓪①③④,③④卡号维持原号不重排=零影响在飞引用),行尾新立迭代 18 行=M4 全量内容原样迁移(含 v1.1 案C 增补+用户四裁点全裁注记 P1甲/P2甲/P3甲/P4甲——该注记系并行会话 d5df1f6 迭代17四卡18项裁点全裁落籍所加,迁移时原样保留),前置列改「迭代 17 出口」;②任务书归属行改迭代 18;③06 五处「迭代 17 卡② M4 数值批」状态行改「迭代 18」+移出注记。**台账撞号史实**=master 现存两个 373(并行会话 373=数据驱动扩展任务书 v1.0、本线 373=M4 任务书 v1.1——本线未提交行被并行快照 9c6d5e2「原样落籍零丢失」吸收所致),不改史只登记;下个空号=377。闸=gdd_lint 对基线零新增(纯文档)。**现行编排态**=迭代 17(⓪①③④,iter17dd 在飞)→ 迭代 18(M4 数值批,任务书 v1.1+四裁点全裁,开工=U0 待发令)。 + + +379. **生涯界面设计提纲批完成(2026-10-06,迭代 17 卡④纯设计批——零代码、零数据面、零测试执行;worktree g-plan2-iter17-career 分支 iter17-career-20261006 基点 fc4dbde,提交 10df8ba 加批次收尾提交;本条为编排会话预配号,落籍序以合并为准)**:设计提纲写成正式设计文档 design/gdd/systems/跨局循环/生涯.md v0.1(状态=范围草案,2026-10-06 用户裁 P0 甲案)——五节写全:信息架构(主菜单一级入口与营地平级=P1 甲,局内不放入口、生涯内零跳转=P4 甲)、承载内容清单(跨局汇总/历史冒险回看/图鉴三段逐项标数据源与可行性,历史回看=P2 甲每局一行摘要加扩容加补用时埋点,图鉴=P3 甲敌人优先、奖励格图鉴只登记不激活,单局明细由 settlement_review_overlay 复活承接=P5 甲)、与既有界面的关系(营地/暂停层/结算屏/帮助手册/图例五界分工矩阵)、分期实装建议(期一汇总页/期二历史回看/期三图鉴,均为另行立项不预占排期,实装批前置义务=对齐迭代 16 UI 基线与 N 终值读数、用时埋点口径呈报)、待裁点(新发现 P6=营地底栏「最近冒险」条带去留,三案全列含淘汰依据,建议期二撤下归生涯——该条带与生涯历史回看读同一份存档摘要且拼行无上限,扩容后会把底栏撑爆,是本批复核发现的实际耦合)。任务书 §2 十条现状事实 F1-F10 逐条对照代码静态复核全绿(回看退役三处注记、settlement_review 遗留件在盘无引用、recent_summaries 现役 cap=10、用时零采集点、终身计数域缺失、营地最近冒险清单渲染行等全实证;一处既有滞后顺带记录=registry 存档条目版本注记停在 v4 而代码已 v5,不属本批范围移交后续对齐批)。同批落:02-系统清单.md 根索引补生涯行+CAREER 代号行+v1.12 版本行、06 生涯条状态行改「设计提纲已成文待评审」、roadmap 迭代 17 卡④行改已交付待评审、出口报告=design/qa/iter17-career-exit-report-2026-10-06.md(完成定义五条判定+事实复核差异记录+提交哈希)。**下一步待用户=评审设计提纲**(评审时随裁 P6);实装批另行立项。 +380. **暂停层只读入口批完成(2026-10-06,迭代 17 卡③功能性 UI 交互批——纯呈现零数值改动=零 MC;worktree g-plan2-iter17-pause 分支 iter17-pause-20261006,基点 44328b1,两会话接力:前任会话完成 T0/T1+组 B 代码后因额度中断,续跑会话收尾组 B 跑闸+T3-T5;本条为编排会话预配号,落籍序以合并为准)**:局内暂停层交付——战斗屏 HUD 右下角与关卡地图屏顶栏各一枚「暂停」常驻角标(P1 丁,debug 角标同形制紧凑钮;主菜单不误现),点击开只读层(P2 甲=登记 modal_stack 登记册设置层同档带 dim 遮罩、确认弹窗之前),层内=钥石余额徽+属性锻造三行已购等级+道具工坊六行解锁态+天赋树已购概览+「继续」关闭钮(P3 甲,行序复用营地屏同序),零购买零切换零写通路。badge 按 Q10 甲工厂化:UiKit 新增 KeystoneBadge 工厂件,营地顶栏钥石徽本地拼装同步迁工厂调用(观感零变化,消单点重复);零新素材零新色值零新字体。守卫=其他模态开着两角标置灰不可点(modal_stack 新增 has_open_excluding 排除式开态查询——地图屏自身开态不计;组合根 _on_pause_badge_pressed 处理器兜底+_sync_pause_entry 单一同步点挂模态开合单口与屏级互斥口五路径)。断言四面=ui_struct(pause 结构+只读性契约〔游戏语义信号十二名清单巡检,白名单外零连接,合法连接恰 2〕+零动效契约〔子树零 AnimationPlayer+源码零 tween〕)+ui_input case⑯(角标真点击开层/锁期挖掘零穿透/关后恢复无残留——R3)+ui_click(战斗屏开合守卫+地图屏一次性 case)+ui_contrast(钥石徽深墨/烛光黄对)+冒烟投影一致性 5 断言(展示值==MetaProgression 访问器逐项——R4)。续跑首轮两处 L1 探针红均系断言口径(全集巡检误判引擎布局生命线连接/域恰 2 枚时零无旗格可取),修正一轮全绿无复发。闸=冒烟 788/0(基线 783/0,+5 本批)+run_all 17 套+probe_gate --click 全绿+ui_layout exit0+doc 三闸零新增;存量移交两项(非本批产生,主区同红)=熵增闸 design/diagrams 越界〔00712d5 总图批落籍未同批修白名单〕+06/07 gdd_lint 存量注记红〔本分支零触碰〕。代拍板五项=关闭钮「继续」/层内行序营地同序/角标文案与落点/断言 case 命名/巡检信号清单(目检清单 F 节追认单)。出口报告=design/qa/iter17-pause-exit-report-2026-10-06.md;**人闸=用户实机目检** design/qa/iter17-pause-human-gate-checklist-2026-10-06.md(A-H 八节,重点=双屏入口/守卫态/只读性/零动效/排播队列排空副作用确认项 D2——开层瞬间待播明细行瞬时入带与「暂停」直觉之差系既有弹层同款行为,待用户确认可接受)。 +381. **迭代18规划批完成(2026-10-06,用户令——纯文档零代码零测试;worktree g-plan2-iter18plan 分支 iter18-plan-20261006 单提交 0d80626,主区 merge --no-ff 018435e,worktree 与分支已清)**:roadmap 迭代 18 重组——⑥M4 数值批移 Alpha 阶段最后=收官卡(门改「迭代 18 内容卡①-⑤出口——重锚窗口须收尽全部内容批行为变更」;标题增二次令注记=内容补全先行、统一数值调整殿后,内容卡若溢出后续迭代则本批随之顺延);新立五卡:①奇物内容补全·文档先行(charge 补件设计——实查 feats.tres 现恰 3 件〔整备军备/铁壁整备/厚积薄发,表注释自证〕;06「资源两个新种类入池」条收口核对——实查刷新次数〔treasure_items.tres 三选一刷新件行〕与专长获取门票〔shop.tres+adventure_table.gd 门票行〕均已随迭代 15②/三选一刷新批入池、该条现状文本滞后即日收窄;本卡纯文档零代码,落地批=纯 .tres 数据行估 0.5d 内,门=文档出口+用户裁件+迭代 17 卡⓪落籍避表结构撞车)/②呈现优化六件(与迭代 16 语义零重合=16 系样式对齐、本批系内容与观感改版,动的是 16 落好的 token 单点;(a) 流派名三处+档位呈现三路待裁/(b) L1 直画观感/(c) 高墙阴线/(d) 残余对比度三处/(e) L2L3 棋盘观感开工先定性素材轴 vs UI 轴/(f) 关卡节点图展示改版先出「哥布林弹珠」参考调研+方案人闸;随批义务=07⑮ 渲染像素五闸补跑)/③武器锻造系统设计提案(playnote 2026-10-06 用户提案=攻击力提升改造武器锻造〔剑/长枪/法杖×穿刺/魔法+轻甲→重甲/魔法甲〕,纯文档,06 新立专条,实装批另行立项,数值框架 provisional 终值归⑥)/④生涯界面实装(按卡④提纲 design/gdd/systems/跨局循环/生涯.md v0.1 实装,门=用户评审提纲+设计定稿)/⑤混编遭遇首批投放(07「后续内容批义务」落地,门=迭代 17 卡①落籍;读数义务随批登记 07、MC 取证跑批顺延⑥收官批统一执行);06 状态行七处随迁+07 混编义务编入注;同批用户裁决落文=B 奇物内容补充先文档(零代码结论成立)、C 呈现优化立项(与迭代 16 零重合)、D 资产升档优先级再降维持挂起(06 增降级注+playnote 立绘批评记为重启输入)、F 道具重设计同 B 先不处理、G 成长曲线/H L1 第二节点暂不讨论留 playnote;遗留提示=A 菜单开局传参缺陷〔main.gd:475 只传四键,本会话代码复核属实〕未入迭代 18(用户未议)——将来修复须排在⑥M4 之前,否则案C 后重锚再作废一次;**下一步待用户=讨论迭代 18 各卡任务书**。 +382. **迭代18增卡⑦新冒险种子随机化(2026-10-07,用户令——纯文档零代码零测试;worktree g-plan2-seedcard 分支 iter18-seedcard-20261007 单提交 997bcc9,主区 merge --no-ff 7a7d873,worktree 与分支已清)**:用户指出现状=每次展开冒险种子固定不随机(实查 main.gd:1210 _seed_value() 调试种子框为空时硬编码返回 42——每次新冒险同一种子全程可复现;同日答用户问=核心循环全固定的 roguelike 不存在,固定种子只在每日挑战/调试特设模式)→ 卡⑦=新冒险主种子改随机生成(默认分支),调试浮窗种子输入框保留手动覆盖(_seed_value 唯一消费点不变,迭代 15 卡①既有读取面);三面零影响考证在册=断点续玩存档已载 master_seed 回填〔main.gd:447 现役〕/冒烟·MC·探针全链显式传种子〔锚零作废〕/单种子内行为不变、与⑥收官批零耦合排序自由;裁点两项随任务书=①首次冒险是否也随机〔现行 42+固定模板 0=首次冒险全程复现的教学态;A6 首遇教学位系节点模板面不受种子影响,但板面雷位/资源摇号会随种子变〕②种子是否进 UI 展示〔局摘要已携带 master_seed,展示=分享/日后每日挑战位候选〕;⑥M4 门同步改「迭代 18 内容卡①-⑤⑦出口」;**下一步待用户=迭代 18 任务书讨论**。 +383. **内容数据驱动扩展批完成(2026-10-06/07,迭代 17 卡⓪——效果引擎框架落地;worktree g-plan2-iter17-dd 分支 iter17-dd-20261006 基点 fc4dbde,五提交 7d157b3/225765f/e28a6cb/a33ddaf/6e7b5d1,两会话接力〔前任完成 T1 效果引擎框架+16 发射点与 T2 词汇落码后额度中断,续跑会话接手 T3 断点三脏文件收口并完成 T4-T7〕;本条为编排会话代记——预配号 381 撞号改登 383,381/382 系并行迭代 18 规划批与增卡⑦两批先占,先例同台账 374)**:内容效果行为从结算代码硬编码搬进数据行「订阅事件 on+条件 when+效果 do」三字段,EffectEngine 发布订阅解释器统一执行——16 发射点埋点+19 行 23 效果类型+组合规则 6 条+改写窗口单点;专长 27 行全量迁 triggers 订阅形态、旧 23 挂点枚举消亡、伤害控制器 13 处消费点拆除、秘宝六件按 id 硬编码清零(淬毒/偏运经 reward_roll/晨令/惊巢/殉爆/掘进蓄能受理闸)、令牌兑付 match 收敛进效果类型路由表、树规则键同面、文案表值化、断言面随迁净增 17;续跑承接修正四处(perfect_defense 事实出域/has 算子语义/漏写 _harmful_adjacent/counter set 键 bool 吞零)。闸=冒烟 800/0(基线 783/0,+17 本批)+run_all 17 套+probe_gate 含 click 全绿+MC 二档对拍 40 局同种子逐位一致(pre/post 锚 83000 段留盘 sim-data/,判据两套全过:确定性复放与冒烟=逐位、MC 统计量=同带)+doc 三闸/熵增存量红零新增。文档换代=奇物 v2.8/秘宝 v1.0/敌人系统 v0.14+registry 两件+06 收口+roadmap 卡⓪行换代;出口报告=design/qa/iter17-datadrive-exit-report-2026-10-06.md(代拍板八项随报告)。遗留=掘进蓄能积点模判定留编排器侧(发射点施加架构下无法行内表达,行为逐位一致,彻底行内化归词汇表扩展批裁决)+主动件 kind 词族全量改名不随批(零行为收益)+实机目检归用户。 +384. **迭代18卡⑦两裁点全裁+调试浮窗目标态落文(2026-10-07,用户令——纯文档零代码零测试;worktree g-plan2-seedrule 分支 iter18-seedrule-20261007 单提交 56d996c,主区 merge --no-ff 49ee98f,worktree 与分支已清)**:用户裁种子两裁点=①首次冒险维持固定种子(教学态可复现)②测试脚本与调试接口显式指定种子(冒烟/MC/探针全链显式传种子零影响)③种子不进玩家 UI——玩家界面入口随机生成;调试浮窗目标态同令落文=独立于整体界面的最高层浮窗(对局中也可开——**修订迭代 15 卡① P3 甲「仅开始菜单可开·对局不可达」裁决**)+DS_DEBUG=1 调试态默认打开+新增当局数据只读区块(当前局种子/关卡/节点/HP/GP/钥石——种子唯一查看位);卡⑦随之扩为「种子随机化+调试浮窗升级」双件估≈1d。**现行考证六点(2026-10-07 实查)**:入口=开始覆盖层角标 main.gd:292-293(DS_DEBUG=1 才点亮——现行非「默认打开」,仅入口点亮);门禁=main.gd:1189 `if not _start_overlay.visible: return`(对局中不可达);形态=Dialog 走 ModalStack 模态栈登记(非最高层,登记序在确认弹窗之前);初始 visible=false(debug_overlay.gd:40);五区块=胜利演出/种子输入〔默认文本 42〕/数值滑条/批量模拟/两层调试入口——无当局数据面;对局发起随开随收〔main.gd:609〕。任务书待裁点五项在册(最高层 vs 模态栈登记制/输入吞噬边界/随开随收改向/ui_tune_probe 断言换代/入口形态)。同批核证:迭代 17 卡⓪数据驱动批已落籍 master e5354366〔台账 383,381/382 系本会话迭代18两批先占撞号自解〕——迭代 18 卡①落地门「卡⓪落籍」自然解除。 diff --git a/design/dev/architecture/architecture.md b/design/dev/architecture/architecture.md index ef3018e1..3d922ecb 100644 --- a/design/dev/architecture/architecture.md +++ b/design/dev/architecture/architecture.md @@ -149,7 +149,7 @@ Input(鼠标)→ D1 输入翻译 → 意图对象 → A2.settle_batch(唯 A2 ──cell_revealed / flag_judged / round_end(棋盘路径)──▶ B1(轮 facts 缓冲) A3 ──round_end(stopped 直发,payload 同构)──────────────▶ B1 B1 ──notify_settlement_done(next_round_mode) / notify_combat_end──▶ A3 -B1 ──apply_effect / apply_damage / grant / stock / advance...──▶ B3/B2/B4(唯一调用方) +B1 ──apply_effect / apply_damage / grant / 伤害单据 submit…──▶ B3/B2/B4(唯一调用方;段内伤害序=主角先手→逐敌还手→地图账殿后〔案C 2026-10-06〕) B1 ──install_board / retire_board──▶ A2 B1 ──generate(L,D)──▶ A1 D1 ◀──呈现数据源:A2 事件流 / B1 步骤流 / A3 ap-rap / ConfigDB display_name ``` @@ -173,7 +173,7 @@ D1 ◀──呈现数据源:A2 事件流 / B1 步骤流 / A3 ap-rap / ConfigDB → C2 begin_run:RngService.set_master_seed()(唯一接线方;两流计数器全零前提) → B1.begin_battle(encounter):set_reload_gate(true) → B3 开域 → B2 spawn → A2 install_board(A1.generate) + B4 roll_board(恰一次)→ A3 notify_round_start → 挖掘循环 …… → battle_over 清理序列 → set_reload_gate(false) - → 关卡末:level_clear()(clear_inventory 恰一次 + 快照计数) + → 关卡末:通关结算(钥石/GP/专长 level_clear 渠道——现行语义归 冒险结构 GDD;原 clear_inventory 随库存轴下线退役 2026-09-15) ``` --- @@ -187,7 +187,7 @@ D1 ◀──呈现数据源:A2 事件流 / B1 步骤流 / A3 ap-rap / ConfigDB | D1 → A2 | 意图经 `settle_batch(intents)` 提交;不直调 A3 | 单意图帧收集;stop 经批次委托 | 语义序仲裁 + voided 零副作用 | | A2 → A3 | 挖/插/拔 = 先棋盘面判定后 `try_spend`;A2 不可自行扣减 | 相位判定两侧一致(同一矩阵) | 原子事务(余量+扣减+仲裁一体) | | A2 → 下游 | 只外发三事件,payload 字段封闭(digit 恒真透传 / reward_eligible / variant_ids) | 增删事件 = 接口漂移 | 发射面封闭 + 规范序 | -| B1 → B2/B3/B4 | 全调用面集中 B1;调用序钉死(query_attack→apply_damage→advance;先兑付后衰减) | 禁第二调用方(静态扫描域:API 符号命中文件 ⊆ B1 文件集) | 恰一次编排纪律(roll_board/decay/清理全序) | +| B1 → B2/B3/B4 | 全调用面集中 B1;调用序钉死(按摇定技能行兑现→apply_damage→commit_action;先兑付后衰减;段内伤害序=主角先手→逐敌还手→地图账殿后〔案C 2026-10-06〕) | 禁第二调用方(静态扫描域:API 符号命中文件 ⊆ B1 文件集) | 恰一次编排纪律(roll_board/decay/清理全序) | | B1 ↔ A3 | 编排期代理 `request_refresh`(玩家意图,禁 RAP 满自动刷);回发 next_round_mode/battle_over | stopped/ap_exhausted 未刷新回发 new_board = 违约 fail-fast | 刷新受理契约(同 Waiting 多次合法) | | 各系统 → D2 | ConfigDB 类型化属性单点读取 | 禁绕过 ConfigDB load 表、禁缓存副本、禁玩法数值字面量、禁 randomize()/自建 RNG | 校验双通道 + fail-fast 致命 | | A1 → A2 | Board 产物 class_name 数据契约 | install 四不变式校验(violation = 拒绝 + 五类诊断枚举) | 生成期合法性闸(R₀≠S / clearable / min_safe) | diff --git a/design/gdd/01-核心循环.md b/design/gdd/01-核心循环.md index ebfdc5cf..78f9b96e 100644 --- a/design/gdd/01-核心循环.md +++ b/design/gdd/01-核心循环.md @@ -8,7 +8,7 @@ 1. 开局一张新雷图,每轮发给你若干行动点。 2. 用行动点做排雷决策:挖开安全格,拿增益和道具;控制(插旗)巢穴,削弱里面的怪物;点错惊动巢穴,怪物反而变强。 -3. 行动点用尽——或者你主动停手——这一轮结束,轮末自动结算:你这一轮扫雷攒下的因,逐行兑现成战斗的果。你拿的增益打了出去,你控制的巢穴变成怪物的虚弱,你惊动的巢穴变成怪物的先手。 +3. 行动点用尽——或者你主动停手——这一轮结束,轮末自动结算:你这一轮扫雷攒下的因,逐行兑现成战斗的果。你拿的增益打了出去,你控制的巢穴变成怪物的虚弱,你惊动的巢穴变成怪物的警觉(护甲变硬、伤害账轮末才落)。 4. 敌人没死光、你还活着,就进下一轮,同一张图接着挖;图开完了,发清图奖励,换一张新图接着打。 所以自动战斗不是过场,是阅卷:每一行结算都能指回你刚才的哪一步棋。 diff --git a/design/gdd/02-系统清单.md b/design/gdd/02-系统清单.md index 85e48c33..5da64eec 100644 --- a/design/gdd/02-系统清单.md +++ b/design/gdd/02-系统清单.md @@ -52,6 +52,7 @@ | 文件 | 一句话概要 | 状态 | |---|---|---| | `元进度.md` | 跨局成长(旧体系未单独成文) | 已定稿·已实现 | +| `生涯.md` | 主菜单一级的局外只读界面——跨局汇总、历史冒险回看、图鉴承接(冒险中数据回看的唯一承接处,P-C7 裁决) | 范围草案·待评审 | ### 表现与支撑(`systems/表现与支撑/`) @@ -99,6 +100,7 @@ | 冒险循环 | 难度曲线与Boss | `DIFFICULTY_CURVE` | c3 | | 冒险循环 | 奇物 | `ADVENTURE_CURIO` | `ADVENTURE_FEATURE`(v1.0)→ `ADVENTURE_REWARD`(v2.0,v2.4 退役) | | 跨局循环 | 元进度 | `META_PROGRESS` | c4 | +| 跨局循环 | 生涯 | `CAREER` | —(新立无旧代号,无代码实现面) | | 表现与支撑 | HUD框架 | `HUD_FRAMEWORK` | d1 | | 表现与支撑 | 数据配置 | `DATA_CONFIG` | d2 | | 表现与支撑 | 存档序列化 | `SAVE_SERIALIZATION` | d3 | @@ -137,3 +139,4 @@ | v1.9 | 2026-10-05 | 《奖励》改名为《奇物》并升 v2.4:顶层定名 Wondrous Item(DND 魔法物品九分类中的兜底杂类,官方中文译名「奇物」)。文档树冒险循环行改名;命名对照表 `ADVENTURE_REWARD` → `ADVENTURE_CURIO`(**不用 `reward_*`**——`reward_pick` 流 / `reward_node` context / `reward_preview` kind 已占用该词族) | | v1.10 | 2026-10-05 | 《奇物》v2.5:第三类定名「资源」(资源偏泛,故在命名出处节写死判据句——给的是可消耗或可重掷的余量,不是属性永久抬升);回复归属定案留在本类不拆第四类;修复 1.5 段末与渠道轴标题粘连 | | v1.11 | 2026-10-05 | 《奇物》v2.7 三个子节点正式定名:专长(Feat)/ 属性值提升(ASI)/ 资源——去「类」字后缀,此前是描述式叫法不是专名;新增 1.6 节「三层结构与命名」,定玩家可见的三层(奇物 / 三个分类 / 具体单件),并明确命名与呈现是两件事 | +| v1.12 | 2026-10-06 | 生涯设计立项批(迭代 17 卡④,纯文档):新建 `systems/跨局循环/生涯.md` 设计提纲 v0.1(信息架构 / 承载内容清单 / 与既有界面关系 / 分期实装建议 / 待裁点五节;状态=范围草案待评审——P0-P5 已裁结论落文,新发现待裁点 P6 营地最近冒险条带去留)。文档树跨局循环补行 + 命名对照表补 `CAREER` 行(无代码实现面,代号随实装批对齐) | diff --git a/design/gdd/06-待办清单.md b/design/gdd/06-待办清单.md index 07e22217..a06a4141 100644 --- a/design/gdd/06-待办清单.md +++ b/design/gdd/06-待办清单.md @@ -2,6 +2,32 @@ 本清单是全项目待办事项的**唯一登记处**:各系统文档不再自建「待办」条目节,只留一行指路存根;新增待办事项一律直接登记本清单。条目按四个循环分组,组内按系统分节;每条事项一个四级标题,其下用粗体属性名成段——**现状**(现状与失义原因,自各系统文档原样搬运、措辞不改;跨文档重复条目合并为一条,登记在建议归属主责方名下,其余来源在现状中注明出处;相关但不同面的条目各自保留,现状末尾互相指认;搬运时仅将原文中的「本文 / 本系统」替换为对应系统名,并把相对链接改写为本文件位置可用的路径)、**归属**、**状态**。事项解决后**从本清单移除**(2026-09-18 用户拍板改口径,原"改状态留痕"废止):去向与依据登记当批台账与 active.md 快照,git 历史即留痕。 +#### 旧行为 MC 行为锚全数作废——重锚归数值批(2026-10-06 登记) + +**现状**:案C 地图伤害账殿后批(git 7ef18ac,台账 371)改变每轮结算行为序——授予段不再兑付任何伤害(伤害形态单据暂存至战斗段末,段内序=主角先手→逐敌还手→地图账),主角死亡短路语义随之换代(「授予段伤害致死短路先手」路径消失)。既有全部 MC 行为锚与对拍锚所锚行为已不存在,**全数作废**;新段内序断言已入冒烟闸。按 sim-data MC 缓存层规则(无数值调整禁重跑),重锚不在改动批执行。 + +**归属**:M-4 数值批(用户 2026-10-06 确认此为影响数值难度的重要变量)。 + +**状态**:待数值批触发时重采;重锚前一切 MC 旧读数只作历史存档,不得与新行为对拍。——**已增补进 roadmap 迭代 18 M4 数值批〔2026-10-06 用户令自迭代 17 卡②移出——不影响 17 在飞开发〕任务书 v1.1(2026-10-06:T0 推算稿第五件+T6 四带复算按案C 新行为首采;design/research/任务书-M4数值批-2026-10-05.md)** + +#### 内容数据驱动扩展批——已立项迭代 17 头卡(2026-10-06 登记,同日全裁) + +**现状**:调研报告落籍 master(=[研究-内容数据驱动扩展架构-2026-10-06](../research/研究-内容数据驱动扩展架构-2026-10-06.md))——用户两问结论:①改已有物品数值/名字/稀有度/投放渠道=**纯配置成立**(全内容族 `.tres` 数据表+schema 加载校验+悬空引用致命);②换全新效果行为/加带新效果的新物品/加机制类新专长=**现状必改代码**(专长 23 挂点封闭枚举 `feat_table.gd:36-67`+求值段逐个 match 展开 `battle_orchestrator.gd:101-179`+消费点约 24 处散布;秘宝被动件按 id 硬编码六件;效果文案硬编码三处)。项目内已有完全数据驱动范本=技能池(`skills.tres` 加行+`enemy.tres` pool 引用零代码)。mod 成熟游戏四共性(Minecraft 数据包/Factorio 原型/RimWorld Defs,前两者官方文档核对):内容=数据+机制=通用解释器、语义事件时机+内容订阅、条件通用词汇复用、加载期注册校验闸。 + +**裁决实录(2026-10-06 用户盘问轮逐条裁定,全落任务书 §二)**:案乙当选;架构=发布订阅同步 emit(事件=`.on("xxx")` 的 topic;订阅后逻辑=效果类型;叠加=组合规则,禁自造词);仅一个改写窗口不做拦截器链;一事件多订阅常态、冲突由 5 条组合规则封死;旧 23 挂点枚举删除(2 个退化条件行);**范围判据=现有实现全改造(含 luck_coin 经 reward_roll 迁移+文案表值化),06 在册待办只登记不实施**(curio_roll/shop_purchase/gp_settle 三事件登记暂不做);框架先行发射点全埋;一个迭代四模块垂直切一次合并;词汇表定稿=实施 17 事件+效果类型 16+组合规则 5。 + +**归属**:迭代 17 卡⓪(头卡,用户令插队——①-④卡顺延);任务书=[任务书-内容数据驱动扩展批-2026-10-06](../research/任务书-内容数据驱动扩展批-2026-10-06.md)(T0-T7≈4.5d,零待裁点) + +**状态**:**已实施完成(2026-10-06 当日立项当日落地——worktree iter17-dd 分支待合并)**:T1 框架(效果引擎+16 发射点,commit 7d157b3)→T2 词汇落码(225765f)→T3 专长 27 行迁移+旧挂点枚举消亡(e28a6cb)→T4/T5 秘宝六件+掘进蓄能受理闸+令牌兑付收敛(a33ddaf)→T6 回归闸全绿(冒烟 800 项 0 失败、单元 17 套、探针闸含 click、MC 二档对拍 40 局同种子**逐位一致**——post 锚 `sim-data/runsum_iter17dd_post_{off,formed}_n20_20261006.jsonl` 留盘);出口报告=[iter17-datadrive-exit-report-2026-10-06](../qa/iter17-datadrive-exit-report-2026-10-06.md)。遗留人闸两项随报告呈报(dig_charge 积点模判定留编排器侧=词汇表边界;主动件 kind 词族全量 rename 不随批) + +#### 菜单开局未传同调装配与局外树规则——疑似缺陷待复验(2026-10-06 登记) + +**现状**:`main.gd:475` `_start_adventure` 参数面只传 `hero_override`/`first_script`/`treasure_unlocked`/`shop_mode` 四键,未传 `attunement`/`attune_refresh_y`/`tree_rules`;runner 受理面存在(`adventure_runner.gd:128-137`,缺省空数组=白板开局);营地装配持久化于 `save.attunement_equipped`(`meta_progression.gd:354-366`)但呈现层开局无传参调用——**主菜单开新局时,营地装配的同调件与局外天赋树规则键疑似不生效**。冒烟未拦系 smoke_all 直呼 runner 参数面(`smoke_all.gd:4304` 有传参)跳过了呈现层。2026-10-06 数据驱动架构调研顺带发现,本人已复核传参面代码,未实机复验。 + +**归属**:缺陷修复 mini-loop(复验+修复待用户令) + +**状态**:待办(疑似缺陷,未实机复验) + #### 双层 BD 延期与备选内容总登记(2026-10-03 立项——任务书退役保险) **目的**:任务书/整合定稿系中间文档,落地完成后按吸收即死退役——用户令「只删除已完成落地的内容」,本条自承载全部**未落地**内容防丢失(每项自包含,不依赖指针)。 @@ -67,7 +93,7 @@ **回复应在商店内**(用户 2026-10-05 定):回复不是计价物,套不上「计价物不自售」,按默认原则它应当在商店出现。现行 `shop.tres` 货架只有 attack / max_hp / armor / ap 四项,**现状与表格不对齐,须在实施批补上**。 **待拍板/待观察两项**:① ~~资源的命名~~ 与 ~~回复是否属该类~~ **已定(v2.5)=资源,回复留在其中不拆第四类**(它与金币同为即时兑付、判据同源;拆出去第四类只剩两项)。② **专长门票尚未入表**:出现概率 / 配额 / 全局限量三个参数都没有事实源,入池同样受档位结构锚约束。③ **专长总量与档位阈值的匹配——延后复裁**(用户 2026-10-05 定"先不调整"):终局 Boss 不给已定,一局确定性专长次数=终局前 Boss 个数;**三关结构下是两次,加门票最多三次**,而档位阈值是同标签 ≥2 档一 / ≥4 档二——**档二(完全体)在三关结构下几乎不可达**。用户决定不动,理由是**后续会有第四关**,且届时可能另做获取侧调整。**复裁触发:第四关内容批落地后实测,配合仍太少再调。** 先不调≠不成问题,登记于此防忘。 -④ **charge 标签件数不足(数据缺陷,独立登记,勿被上条盖掉)**:档二要求同标签 ≥4 件,而 `relics.tres` 里 charge 标签**只有 3 件**——即使获取次数无限,charge 流派也永远达不到档二。这与渠道结构无关,是数据层缺陷。 +④ **charge 标签件数不足(数据缺陷,独立登记,勿被上条盖掉)**:档二要求同标签 ≥4 件,而 `relics.tres` 里 charge 标签**只有 3 件**——即使获取次数无限,charge 流派也永远达不到档二。这与渠道结构无关,是数据层缺陷。**编入注(2026-10-06 用户令)**:补件设计编入 roadmap 迭代 18 卡①奇物内容补全批·文档先行(落地=纯 .tres 数据行,新件限现有效果 hook 则零代码) ⑤ ~~顶层命名待定~~ **已定(v2.4)=「奇物」Wondrous Item**:DND 魔法物品九分类中的兜底杂类,官方中文译名即「奇物」,专装"不好归类但有奇效之物"——正好对应三个互不相干的分类。落选:Treasure(偏财物,装不下专长)、Relic(品类惯例,用户否决「遗物」)、全部自造词(进益/造化/机缘/遇获)。**代码词族随之定为 `curio_*` / `ADVENTURE_CURIO`**——原计划的 `feature_* → reward_*` 对齐**作废**:`reward_pick` 流 / `reward_node` context / `reward_preview` kind 已占用 reward 词族,照做会撞车。 **归属**:[奇物](systems/冒险循环/奇物.md)(定义与交叉开关表)+ [node-reward](ux/node-reward.md)(令牌池与奖励面)+ [冒险结构](systems/冒险循环/冒险结构.md)(商店货架保底与货架过滤)+ [关卡节点地图](systems/冒险循环/关卡节点地图.md)(节点类型词表);与上条「双层 BD 延期与备选内容总登记」二期件组相关但不同面。 @@ -82,7 +108,15 @@ **归属**:[奇物](systems/冒险循环/奇物.md) 调参表 + [冒险结构](systems/冒险循环/冒险结构.md) 共享池账本节;数值所有权归 [难度曲线与Boss](systems/冒险循环/难度曲线与Boss.md) -**状态**:待排批——须在可跑冒烟的迭代批次内完成 +**状态**:待排批——须在可跑冒烟的迭代批次内完成——**实查更新+收窄+编入(2026-10-06)**:两项实已随迭代 15②/三选一刷新批入池(刷新次数=treasure_items.tres「三选一刷新件」行;专长获取=shop.tres+adventure_table.gd 门票行),本条收窄为边界核对与条目收口,**已编入 roadmap 迭代 18 卡①奇物内容补全批·文档先行〔2026-10-06 用户令:先文档后落地,数值只设计不调终值归⑥M4 收官批〕** + +#### 武器锻造系统——攻击/护甲成长差异化提案(2026-10-06 playnote 用户提案,同日令入迭代 18) + +**现状**:用户提案(playnote 2026-10-06):攻击力提升改造为「武器锻造」系统——① 剑(普通攻击):锻造升级增加攻击力,最高等级附带额外效果;② 长枪/法杖:后续新增武器,攻击类型分别变为穿刺/魔法,同样满级带特殊效果——实现武器间差异化;③ 护甲同理:初始只有轻甲(升级只提 AC),最高等级后可解锁重甲/魔法甲——基础 AC 各不相同、可继续升级、各带不同额外效果。 + +**归属**:待提案批定(候选=[奇物](systems/冒险循环/奇物.md) 属性值提升分类修订 + [战斗结算](systems/战斗循环/战斗结算.md) 攻击类型轴 + [元进度](systems/跨局循环/元进度.md) 营地柜台锻造经济) + +**状态**:已立项设计提案批=roadmap 迭代 18 卡③(2026-10-06 用户令;纯文档零代码,裁点呈用户裁决,实装批另行立项;数值框架 provisional 终值归迭代 18 卡⑥ M4 收官批统一调整) #### 内部分类禁写入玩家可见面——奇物呈现三处违规待改(2026-10-04 用户立意登记) @@ -92,7 +126,7 @@ **归属**:[battle.md](ux/battle.md) C 区 + [level-map.md](ux/level-map.md) 底栏 + [node-reward](ux/node-reward.md) 卡面文案;RelicsEngine 求值与档位机制零改动(纯呈现面)。 -**状态**:方向已明确(禁写类别),违规改法与档位呈现三路待讨论(2026-10-04 登记,后续展开) +**状态**:方向已明确(禁写类别),违规改法与档位呈现三路待讨论(2026-10-04 登记,后续展开)——**违规三处清除+档位呈现已编入 roadmap 迭代 18 卡②呈现优化批(2026-10-06 用户令;档位三路仍待裁,随任务书呈报)** #### 奇物命名统一后续——代码标识符与文件名的对齐(2026-10-05 立) @@ -110,7 +144,7 @@ **归属**:[排雷玩法](systems/排雷循环/排雷玩法.md)(动作语义与 8 向目标规则主责)+ [HUD 框架](systems/表现与支撑/HUD框架.md)(键盘光标与合法性反馈呈现)+ [设置与本地化](systems/表现与支撑/设置与本地化.md)(键位自定义,可选面) -**状态**:待办——排期 **Beta 阶段**(2026-10-04 登记;可行性已核毕,立项时按 roadmap 开卡) +**状态**:待办——排期 **Beta 阶段**(2026-10-04 登记;可行性已核毕,立项时按 roadmap 开卡)。**2026-10-06 增补登记**:暂停层 Esc 快捷通道并入本条随键盘批统一实施(迭代 17 卡③暂停层批 P1 裁决=常驻角标本批落码、Esc 键不单独开通道;批出全屏弹层后本条③「弹层界面现行全鼠标」清单 +1 项=暂停层) #### 挖掘风险不等式对账破线 @@ -128,7 +162,7 @@ **归属**:随[关卡节点地图](systems/冒险循环/关卡节点地图.md)定稿改键一并裁决 -**状态**:部分收口(2026-09-17)——同层多遭遇问题已由关卡节点地图 v1.0 的**节点编号升级**消解(`node_depth` 语义 = 关卡内唯一节点编号,同层不同节点各查各行,键结构零改动);**不设精英 / Boss 编码位**(Boss = 最大编号节点,精英 P2 内容承载)。剩余面继续待办并改归属:同场多敌种(同一编号行集多行)的行集校验放开 ——归属**敌人表行集多行批(战斗侧,排技能池实施批落地后)**(混编伏击上限取值口径面已随 2026-09-20 单轮上限删除消解)。**已编入 roadmap 迭代 17 卡①(2026-10-05——任务书已成文待 U0:design/research/任务书-敌人表行集多行批-2026-10-05.md;前置「技能池实施批落地」已于 2026-10-05 满足〔d3fa887〕,开工门已解,余 4 项待用户裁点)** +**状态**:部分收口(2026-09-17)——同层多遭遇问题已由关卡节点地图 v1.0 的**节点编号升级**消解(`node_depth` 语义 = 关卡内唯一节点编号,同层不同节点各查各行,键结构零改动);**不设精英 / Boss 编码位**(Boss = 最大编号节点,精英 P2 内容承载)。剩余面继续待办并改归属:同场多敌种(同一编号行集多行)的行集校验放开 ——归属**敌人表行集多行批(战斗侧,排技能池实施批落地后)**(混编伏击上限取值口径面已随 2026-09-20 单轮上限删除消解)。**已编入 roadmap 迭代 17 卡①(2026-10-05——任务书已成文待 U0:design/research/任务书-敌人表行集多行批-2026-10-05.md;前置「技能池实施批落地」已于 2026-10-05 满足〔d3fa887〕,开工门已解,4 项裁点已全裁并经 2026-10-06 联审二裁修订:P1甲〔Q8/Q9 补全=每行一骰·RNG 扩签先报批·实例总数≤4〕/P2甲/P3丙〔改裁撤投放——首混编行连同 Boss 多行归后续内容批,投时 MC 取证先报批+难度带读数义务随批登记 07〕/P4乙 Boss 行亦放开·零内容投放不变)** #### 技能池情境条件触发器 @@ -177,7 +211,7 @@ **归属**:战斗结算与地图战斗资源共裁(数值终值归状态层重设计批) -**状态**:待办(语义已定案,数值未闭合)——**已编入 roadmap 迭代 17 卡② M4 数值批(2026-10-05——任务书已成文待 U0:design/research/任务书-M4数值批-2026-10-05.md;duration 终值与当量锚复算 = 件①,跑批发起前须用户批准)** +**状态**:待办(语义已定案,数值未闭合)——**已编入 roadmap 迭代 18 M4 数值批〔2026-10-06 用户令自迭代 17 卡②移出——不影响 17 在飞开发〕(2026-10-05——裁点已全裁并经同日联审二裁修订:P1甲〔带内落定随出口追认〕/P2甲/P3甲〔转正顺延至重扫读数到账且带内〕/P4甲〕待 U0:design/research/任务书-M4数值批-2026-10-05.md〔v1.2〕;duration 终值与当量锚复算 = 件①,跑批发起前须用户批准)** #### 单体寻址的接口扩展位 @@ -223,7 +257,7 @@ **归属**:①元进度设计批 + 曲线调参批 ②曲线调参批(现行窗) ③~~秘宝同调实施批~~(已清偿 2026-10-03) -**状态**:已登记(2026-10-01,迭代 10 批随批)——**①②已编入 roadmap 迭代 17 卡② M4 数值批(2026-10-05——任务书已成文待 U0:design/research/任务书-M4数值批-2026-10-05.md;钥石产出重标 + 通关率带重扫 = 件③,跑批发起前须用户批准)** +**状态**:已登记(2026-10-01,迭代 10 批随批)——**①②已编入 roadmap 迭代 18 M4 数值批〔2026-10-06 用户令自迭代 17 卡②移出——不影响 17 在飞开发〕(2026-10-05——裁点已全裁〔2026-10-06 用户令:P1甲/P2甲/P3甲/P4甲〕待 U0:design/research/任务书-M4数值批-2026-10-05.md;钥石产出重标 + 通关率带重扫 = 件③,跑批发起前须用户批准)** #### 调参批数值点评登记(八型批随批登记) @@ -231,7 +265,7 @@ **归属**:[难度曲线与Boss](systems/冒险循环/难度曲线与Boss.md)难度曲线调参批 -**状态**:待办(随调参批一并裁决)——**已编入 roadmap 迭代 17 卡② M4 数值批(2026-10-05——任务书已成文待 U0:design/research/任务书-M4数值批-2026-10-05.md;五条点评逐条裁决 = 件②)** +**状态**:待办(随调参批一并裁决)——**已编入 roadmap 迭代 18 M4 数值批〔2026-10-06 用户令自迭代 17 卡②移出——不影响 17 在飞开发〕(2026-10-05——裁点已全裁〔2026-10-06 用户令:P1甲/P2甲/P3甲/P4甲〕待 U0:design/research/任务书-M4数值批-2026-10-05.md;五条点评逐条裁决 = 件②)** #### 基线通关率越带,加难杠杆扫描未执行 @@ -247,22 +281,22 @@ **归属**:元进度设计批 -**状态**:部分收口(2026-09-21 元进度实现批再收窄;2026-09-23 换算率标定批再收窄)——**三柜台结构与 provisional 定价已落地**(属性锻造三档血+3/攻+1/甲+1、base 10 公比 150;Boss 两档 L1 5/1·L2 8/2·L3 12/3;工坊六件阈值 1/1/2/2/3/3 按总通关次数——全部读 `data/tables/meta.tres`);换算率三行已投值 6/9/1(同批,冒险条件口径实测,护甲行强条目重测履行);剩余面 = 定价数字与 Boss 两档——**已编入 roadmap 迭代 17 卡② M4 数值批(2026-10-05——任务书已成文待 U0:design/research/任务书-M4数值批-2026-10-05.md;定价数字与 Boss 两档终值 = 件④。注:本条「base 10 公比 150」文本滞后——meta.tres 现行 forge base_price=20(2026-09-22 数值设计批对齿 10→20),随件④收口时刷新)** +**状态**:部分收口(2026-09-21 元进度实现批再收窄;2026-09-23 换算率标定批再收窄)——**三柜台结构与 provisional 定价已落地**(属性锻造三档血+3/攻+1/甲+1、base 10 公比 150;Boss 两档 L1 5/1·L2 8/2·L3 12/3;工坊六件阈值 1/1/2/2/3/3 按总通关次数——全部读 `data/tables/meta.tres`);换算率三行已投值 6/9/1(同批,冒险条件口径实测,护甲行强条目重测履行);剩余面 = 定价数字与 Boss 两档——**已编入 roadmap 迭代 18 M4 数值批〔2026-10-06 用户令自迭代 17 卡②移出——不影响 17 在飞开发〕(2026-10-05——裁点已全裁〔2026-10-06 用户令:P1甲/P2甲/P3甲/P4甲〕待 U0:design/research/任务书-M4数值批-2026-10-05.md;定价数字与 Boss 两档终值 = 件④。注:本条「base 10 公比 150」文本滞后——meta.tres 现行 forge base_price=20(2026-09-22 数值设计批对齿 10→20),随件④收口时刷新)** #### 解锁界面与钥石呈现的交互形态(残余:暂停层只读入口) **现状**:视觉终稿半已兑现——营地界面视觉终稿随 M-3 迭代 9 卡⑭落码(石纹全屏底+顶栏铭牌/钥石徽+三柜台列,前置样张闸过审);功能界面(营地入口/CampOverlay 三柜台/A 区顶栏钥石余额常驻/结算屏入账明细)2026-09-21 已落地。**剩余面=局内暂停层只读入口**:现行无暂停层,局内无可暂停查看钥石与解锁态的入口。 -**归属**:HUD 批(M-4,批未立项) +**归属**:HUD 批(M-4,批未立项)——已随迭代 17 卡③实施 -**状态**:待办(2026-10-03 06 清账批瘦身——视觉终稿半兑现销号,本条仅存暂停层残余)——**已编入 roadmap 迭代 17 卡③(2026-10-05——任务书已成文待 U0:design/research/任务书-暂停层只读入口批-2026-10-05.md;4 项待用户裁点〔入口位置/与既有弹窗层关系/展示范围/减动效联动〕,美术红线=复用现行公共件零新素材)** +**状态**:✅ 代码面已交付待实机目检(2026-10-06,迭代 17 卡③暂停层只读入口批——worktree g-plan2-iter17-pause 分支 iter17-pause-20261006,提交 c08a901/fc9eb1e/d44fdd5+收口笔;P1丁常驻角标/P2甲设置层同档 dim 遮罩/P3甲余额+三柜台进度/P4甲零动效直开直关全落;badge 已按 Q10 甲工厂化入 ui_kit 并同步迁营地钥石徽;机检=冒烟 788/0+run_all 17 套+四探针全绿〔含输入锁 case⑯/只读巡检/零动效契约/钥石徽对比度对〕+doc 三闸零新增;**人闸=用户实机目检** design/qa/iter17-pause-human-gate-checklist-2026-10-06.md——过闸后本条销号;Esc 快捷通道维持本清单键盘/手柄条登记随键盘批办理) #### 第一关亮墨数字直画观感优化(2026-10-01 T17 目检体感登记) **现状**:迭代 9 T4 数字承载切换后第一关=亮墨三档直画(白亮字直接印棕色地板、无色牌垫底),机制可读性过闸(用户实机确认三档冷暖可分、凑近与离屏均可读);同日 T17 目检体感「表现和描述一样,但非常难看」——直画字与地板的组合观感待优化。第二/三关米色小牌形态未获同类反馈。 **归属**:呈现优化批(与高墙墙底阴线观感同批裁量;排 M-4 后排,不卡迭代 9 出口) -**状态**:待办(2026-10-01 登记;机制/可读轴零触碰,纯观感轴)。**2026-10-05 注**:迭代 16「UI 设计对齐批」任务书已起草(`design/research/任务书-UI设计对齐批-2026-10-05.md`)——棋盘数字墨三档与色牌垫底属本批 §3-N1 对比度收项面(实查 `board_cell_unit.gd:238/262` 字号公式已合规、`:229-234` 垫牌在位),裁点裁决后随批一并呈报。本条维持待办。 +**状态**:待办(2026-10-01 登记;机制/可读轴零触碰,纯观感轴)。**2026-10-05 注**:迭代 16「UI 设计对齐批」任务书已起草(`design/research/任务书-UI设计对齐批-2026-10-05.md`)——棋盘数字墨三档与色牌垫底属本批 §3-N1 对比度收项面(实查 `board_cell_unit.gd:238/262` 字号公式已合规、`:229-234` 垫牌在位),裁点裁决后随批一并呈报。本条维持待办。**2026-10-06 编入注**:已编入 roadmap 迭代 18 卡②呈现优化批(观感重设计,机制/可读轴零触碰)。 #### 高墙墙底细阴线观感优化(2026-10-01 T17 目检体感登记) @@ -270,7 +304,7 @@ **归属**:呈现优化批(与数字直画观感同批;排 M-4 后排,不卡迭代 9 出口) -**状态**:待办(2026-10-01 登记;机制轴零触碰,纯观感轴)。**2026-10-05 注**:墙脚阴线色 `E_FACADE_FOOT_COL #342A20` 已列为迭代 16「UI 设计对齐批」§3-N4a 五处漏网裸色之一(`board_cell_unit.gd:79`),收进 `ui_kit.gd` 时原值搬运、零观感变化;任务书=`design/research/任务书-UI设计对齐批-2026-10-05.md`。本条维持待办。 +**状态**:待办(2026-10-01 登记;机制轴零触碰,纯观感轴)。**2026-10-05 注**:墙脚阴线色 `E_FACADE_FOOT_COL #342A20` 已列为迭代 16「UI 设计对齐批」§3-N4a 五处漏网裸色之一(`board_cell_unit.gd:79`),收进 `ui_kit.gd` 时原值搬运、零观感变化;任务书=`design/research/任务书-UI设计对齐批-2026-10-05.md`。本条维持待办。**2026-10-06 编入注**:已编入 roadmap 迭代 18 卡②呈现优化批(阴线形制改版;token 单点已就位)。 #### 未开格类型徽章呈现时机(2026-09-18 方向已拍板,待实施批) @@ -284,9 +318,9 @@ **现状**:迭代 16「UI 设计对齐批」T0 实机复算中,七点测点范围外的邻接文字位实测对直背景 <4.5:1(§7.2 无区域例外)——①B 区站桩名行/护甲徽标四墨(WARM_FAINT≈6.2 过/HERO_BLUE 3.23/GOLD_DEEP 3.26/WARM 过,直背=舞台底 `STAGE_BG #3E342A`);②敌顺位徽标 ①②③ `GOLD_DEEP` 13px;③RAP 反馈行 `STONE_FAINT` 对 PANEL_DATA(2.73)。均系批前既有呈现、非迭代 16 引入;迭代 16 七点修法面已收口(站桩血数/意图徽标四色/C区槽两态/RAP 主标签),本条为其漏出范围,未动。来源=`design/qa/iter16-ui-exit-report-2026-10-06.md` §T0。 -**归属**:呈现优化后续批(或 UI 设计对齐批第二轮);开工 = 用户点名 +**归属**:呈现优化后续批(或 UI 设计对齐批第二轮);开工 = 用户点名——**已编入 roadmap 迭代 18 卡②呈现优化批(2026-10-06 用户令)** -**状态**:待办 +**状态**:已编入迭代 18 卡②(待任务书) #### 棋盘格边长三方取值不一致——待扩板定档处置(2026-10-06 登记) @@ -302,7 +336,15 @@ **归属**:UI 设计对齐批后续专项(或独立呈现批);开工 = 用户点名 -**状态**:待办 +**状态**:**已全部落地(2026-10-06 /goal UI 对齐批,merge 2ca6288 链)**——①卡片 232×352=ui_kit BCard(D1,图标槽 76/60 随批)②商店全屏页=UiKit.page_shell(D2,56px 通栏顶条+爬价规则行+底注;三选一同壳)③地图底栏=85px 定高单条(放弃 90 小钮+特性行限宽截断+深墨)。用户令(/goal)立项即发令,无须另批。 + +#### 全屏 UI 审计差距清单(实机×demo 逐屏对比,2026-10-06 登记) + +**现状**:用户令全屏截图对比审计落籍(报告=[ui-audit-vs-demo-2026-10-06](../qa/ui-audit-vs-demo-2026-10-06.md),并排图=design/qa/ui-audit-2026-10-06/pairs/ 九组)——①**关卡地图底栏**:demo 85px 单条+放弃小钮 vs 实机特性行无限换行爆栏(130px+)+放弃 216 红砖+特性组暖白淡墨对亮板 §7.2 违例;重设计草案(定高 88px/放弃小钮/特性行限宽省略+深墨)在报告待裁;②**E5 证据更新**:demo「工兵爆破」卡=紫带(magic 族),T4a 的 blast→TG_AOE 判定与 demo 相反,建议回滚;③标题屏主操作语义反转(demo 继续上局=主黄钮);④结算仪式感(demo 全屏 dim+石皮框板+两列对齐账目);⑤背景全局差距(demo 暖渐变聚焦 vs 实机全屏铺石纹);⑥D1/D2(商店全屏化/卡片 232×352)截图坐实(原条目在册不重复)。审计截图与探针=game/smoke/ui_audit_probe.gd(窗口化十二屏)。 + +**归属**:UI 设计对齐批后续轮(或独立呈现批);开工 = 用户逐项裁决 + +**状态**:**六项已闭环(2026-10-06 /goal UI 对齐批)**——①底栏重设计全落(85px 定高+放弃 90 小钮+特性行限宽截断+深墨)②E5 已回滚(blast→TG_MAGIC,TG_AOE 只接 aoe 键)③主操作语义已反转(有档=继续上局主黄钮)④结算仪式感已落(DIM_NIGHT 0.62 压暗+两列账行+紧凑钮)⑤背景全局已落(石纹 0.14 弱质感+暗角+地图渐变+标题光带,UiKit 氛围层单点)⑥D1/D2 已落(见上条)。**遗留(已判定并留档)**:营地面板明度/行密度与关闭钮位(camp ⑦)、战斗棋盘底板融景(battle ⑤①)、道具槽段位(台账 352 功能欠账)、立绘比例(资产升档批在册)——归各自在册条目,不在 UI 对齐轮再开。复判记录=design/qa/ui-audit-vs-demo-2026-10-06.md 批末判定节。 #### 资产升档重产批(art-bible v3.0 中修挂账③,2026-09-29 登记) @@ -310,7 +352,7 @@ **归属**:[art-bible](../art/art-bible.md) §8.2 · 资产管线批 -**状态**:**已裁决案乙(2026-09-30 迭代 9 三裁决,用户「按推荐来」)**——本迭代不升档,先收 Alpha 真人读数再定(B 区舞台落影已改善存在感;T10 黄金图基准本迭代内建成不被作废);升档批=M-4 候选位,届时提灯点亮解禁候选一并重议;A4 尺寸基线冻结(立绘 46×48/小人源 32×32)维持有效。**2026-10-04 用户令:暂不做、先忽略**(真人读数虽已收,用户裁不编入当前迭代表、挂起不讨论;重启=用户点名)。**2026-10-06 方案二批收口注记**:v3.0 的「档位上调方向候选」已被 art-bible v3.7 八敌逐键定档(源档 36–94、显示 ×2 整数)**实质收口**——存在感问题改经方案二(B 区 270/立绘显示上限 188/血条状态落底排)解决,不再走 64/96/128 升档路线;本条剩余挂起项 = 提灯点亮解禁候选(重启=用户点名或 M-4 升档批)。 +**状态**:**已裁决案乙(2026-09-30 迭代 9 三裁决,用户「按推荐来」)**——本迭代不升档,先收 Alpha 真人读数再定(B 区舞台落影已改善存在感;T10 黄金图基准本迭代内建成不被作废);升档批=M-4 候选位,届时提灯点亮解禁候选一并重议;A4 尺寸基线冻结(立绘 46×48/小人源 32×32)维持有效。**2026-10-04 用户令:暂不做、先忽略**(真人读数虽已收,用户裁不编入当前迭代表、挂起不讨论;重启=用户点名)。**2026-10-06 用户令:优先级再降**(维持挂起;同日 playnote 立绘批评〔像素粒度过低看不清 / 怪物大小比例不合理〕留 playnote 在案,重启时并入设计输入——**该批评实已由同日「立绘逐键定档批」承接兑现**:①像素粒度过低 → 派生管线改「pixel-1 紧裁 → 等比缩至各自档高 → dom_block_resize」,八敌源档 36–94 全过可读门槛(实测 24 糊 / 32 可读 / 48 清楚);②怪物大小比例不合理 → 体量语义改**生物体型**(character-design §5.2,弃强度线性映射)+ 八敌逐键定档〔art-bible §8.2〕)。**2026-10-06 方案二批收口注记**:v3.0 的「档位上调方向候选」已被 art-bible v3.7 八敌逐键定档(源档 36–94、显示 ×2 整数)**实质收口**——存在感问题改经方案二(B 区 270 / 立绘显示上限 188 / 血条状态落底排)解决,不再走 64/96/128 升档路线;本条剩余挂起项 = 提灯点亮解禁候选(重启=用户点名或 M-4 升档批)。 #### 站桩帧动画——Alpha 定版闸前完成(2026-10-06 P0-3 延后登记,用户拍板) @@ -399,14 +441,14 @@ **归属**:新界面立项(用户发令后排批);退役落码随 UI 落码批 M8/M9 -**状态**:待办(生涯未做前,冒险中无任何历史回看入口——已知代价,用户知情裁决)——**已编入 roadmap 迭代 17 卡④(2026-10-05——设计提纲版任务书已成文待 U0:design/research/任务书-生涯界面设计立项-2026-10-05.md;只做设计不做实装,6 项待用户裁点,实装批另行立项)** +**状态**:待办(生涯未做前,冒险中无任何历史回看入口——已知代价,用户知情裁决)——**设计提纲已成文待评审**(2026-10-06 迭代 17 卡④设计批完成:提纲 = [systems/跨局循环/生涯.md](systems/跨局循环/生涯.md) v0.1 范围草案,五节成文〔信息架构/承载内容清单/与既有界面关系/分期实装建议/待裁点〕,现状描述经代码级复核;P0-P5 六裁点已全裁落文〔P0甲 提纲落正主 / P1甲 主菜单一级入口 / P2甲 每局一行摘要+cap 扩容 N+补用时埋点 / P3甲 敌人图鉴优先 / P4甲 局内不放入口+生涯零跳转 / P5甲 settlement_review 复活承接〕,新发现待裁点 P6 营地最近冒险条带去留随评审一并裁。**人闸 = 用户评审提纲**;实装批另行立项,期一/二/三均未预占排期)——**实装批已编入 roadmap 迭代 18 卡④生涯界面实装批(2026-10-06 用户令;门=本提纲用户评审+设计定稿)** ### GP 经济 #### 商店独立刷新机制——两参数待调参 + 机制文档回写 -**现状**:2026-10-04 用户裁决(P-C2+Q2 二次裁)推翻关卡节点地图 v1.5「无独立刷新、重抽绑购买」口径,定案=**基础免费 0 次 + GP 刷新(ceil(20×1.8^已付费刷) provisional)+ 池源同源(共享池,与三选一同一账本)**;**天赋/营地侧「刷新次数」增益=用户既有意向(口头裁决,曾因未落文丢失——Q2 追认),后续由用户调整添加,实现面预留 free_count=天赋增益位**。未定:天赋增益的具体形态与数值。机制回写面=[冒险结构](systems/冒险循环/冒险结构.md) 共享池账本节 + [关卡节点地图](systems/冒险循环/关卡节点地图.md) 商店节点行 v1.5 换代;实现面=shop_draw/runner(核心小批,非呈现批)。来源=同上 §七 +**现状**:2026-10-04 用户裁决(P-C2+Q2 二次裁)推翻关卡节点地图 v1.5「无独立刷新、重抽绑购买」口径,定案=**基础免费 0 次 + GP 刷新(ceil(20×1.8^已付费刷) provisional)+ 池源同源(共享池,与三选一同一账本)**;**天赋/营地侧「刷新次数」增益=用户既有意向(口头裁决,曾因未落文丢失——Q2 追认),后续由用户调整添加,实现面预留 free_count=天赋增益位**。未定:天赋增益的具体形态与数值。**机制已落码(SR-1 随 UI 落码批收官,2026-10-04/05)**。**三选一同款付费刷新(2026-10-06 用户令——「三选一对齐商店,得有刷新」)已落码**:免费双源(刷新件 X/冒险池 Y)用尽后 GP 付费刷新,同旋钮同曲线同钱包,钮常驻选择态、两源皆穷=禁用;机制登记归[秘宝](systems/排雷循环/秘宝.md)「三选一刷新双源」节。剩余待办=①调参(shop_refresh_free/曲线两参数 provisional);②天赋增益形态与数值;③机制文档回写(冒险结构共享池账本节/关卡节点地图商店行 v1.6 已记,本条收口时核对三处同步)。来源=同上 §七 -**归属**:商店刷新核心小批(UI 落码计划 SR-1(商店刷新批);开工待用户发令) +**归属**:M4 数值批(调参两参数随批);天赋增益随天赋侧内容批 -**状态**:待办(机制已裁、参数与文档回写未落) +**状态**:待办(机制已落码;调参、天赋增益与文档回写未落) diff --git a/design/gdd/07-体验验证清单.md b/design/gdd/07-体验验证清单.md index 836992c8..6ad25e8b 100644 --- a/design/gdd/07-体验验证清单.md +++ b/design/gdd/07-体验验证清单.md @@ -169,4 +169,6 @@ **已履行**(按序换代,旧读数各随其批作废留痕不入本单):①验收读数重测批(2026-09-19——Boss 单独出列/承伤带/通关率基线全量重测,硬化翻倍期·乏力死效果期·充能期旧读数作废);②迭代 5 基线重测(统一随机流后 MC+verifier 双窗);③**数值修复批新尺重测(2026-10-01)**——模拟档位重构后全量重测,新锚 `sim-data/runsum_numfix_final_n200_20261001.jsonl`,全旧带作废只测不判([numfix 报告](../qa/numfix-newruler-readings-2026-10-01.md));④**迭代 10 奖励重构批重锚(2026-10-01)**——四代锚 77000/78000/79000/80000(改前/重构后/共享池后/掷定前置后,各 400 局),普通 L1 41.8%/L2 10.2% 带内([读数报告](../qa/m3-iter10-clear-rate-readings-2026-10-01.md));⑤**T19 真人标定局收口(2026-10-03 用户裁)**——有效局 2(1 通关 1 死于 L3 首战,[读数报告](../qa/t19-real-playtest-readings-2026-10-02.md)),用户裁定**佐证级收口不再补局**;k 预期带同日立(**正常 >0.5、预警 <0.3**),字段随 81000 重锚入库后补算终值。 -**现行义务**:**T7 技能池节奏重测(2026-10-05 技能池执行批挂单)**——≥50 distinct seeds 节奏重测(过涝判读带=Boss 半血后特殊技密度上探;跑批须用户批准;S2 过涝越带→解锁技权重/倍率回调呈报禁自动调)→ ~~T19 真人标定局~~(⑤已收口)→ 真人侧读数换 81000 锚补算 k 终值(随迭代 9 出口重锚批出,模拟锚 2026-09-19 判读报告的现行锚地位由 81000 锚+真人两局承接)。工具面:`analyze_playtest.py` / `balance_verifier.py` 冒险段 / JSONL 日志现役。 +**现行义务**:**T7 技能池节奏重测(2026-10-05 技能池执行批挂单)**——≥50 distinct seeds 节奏重测(过涝判读带=Boss 半血后特殊技密度上探;跑批须用户批准;S2 过涝越带→解锁技权重/倍率回调呈报禁自动调;**2026-10-06 联审批注:判读带立单于案C 行为换代前,执行前须按案C 新行为复核「半血后」窗口与密度分母是否仍有效,失权即停机呈报重立带**)→ ~~T19 真人标定局~~(⑤已收口)→ 真人侧读数换 81000 锚补算 k 终值(随迭代 9 出口重锚批出,模拟锚 2026-09-19 判读报告的现行锚地位由 81000 锚+真人两局承接)。工具面:`analyze_playtest.py` / `balance_verifier.py` 冒险段 / JSONL 日志现役。 + +**后续内容批义务(2026-10-06 敌人表行集多行批 P3 丙二裁挂单)**:首个混编遭遇行投放(连同 Boss 多行内容,敌种候选与坐标由内容批裁)——投时 MC 取证先报批(数值验证场景二档起)、难度带读数义务随投登记、首遇教学位纪律与 expect_pools 断言面逐项复核(巨蜘蛛首遇位「固定 2 不加骰」等表注释为准)、Boss 多行投放时 Boss 名取首行显示/Boss 遭遇骰读首行区间/半血解锁/独占技/难度带六面读数逐项过。——**已编入 roadmap 迭代 18 卡⑤混编遭遇首批投放批(2026-10-06 用户令;读数义务维持随批登记,MC 取证跑批顺延迭代 18 卡⑥ M4 收官批统一执行〔数值统一调整殿后〕)** diff --git a/design/gdd/systems/冒险循环/冒险结构.md b/design/gdd/systems/冒险循环/冒险结构.md index 42b50a9a..a432a72e 100644 --- a/design/gdd/systems/冒险循环/冒险结构.md +++ b/design/gdd/systems/冒险循环/冒险结构.md @@ -151,7 +151,7 @@ GP(显示名「金币」)是冒险内的一次性属性提升货币:战斗 掉落掷定消费 gp_drop 封闭随机流(主种子派生、可回放)。**商店货架抽签消费 shop_draw 封闭随机流**(v0.13 扩签——每次抽 3〔进店+每次买后重抽〕恰一 scope,与 reward_pick 分签防取数序互扰)。掉落与价格的现行行值读商店表(registry 为准),数值所有权归[难度曲线与Boss](难度曲线与Boss.md)曲线调参批——本批骨架先行,全部数值 provisional。 -**共享池账本(v0.13 P1 共享池单点;v0.14 令牌库换代)**:商店货架与提升三选一是**同一个池**——货架候选 = 池∖{GP 袋}(用钱买钱恒负交易)= {攻击, 生命上限, 护甲, 行动点} 四种(秘宝件已随同调批出池);**库存 = 剩余令牌数**(TP0——「池消耗即上限」,种类次数上限退役,与奖励兑付摘牌同一令牌库承载,全冒险一次性跨关卡不补给):商店买一件,奖励面就少一枚;「无限购买」收敛为**钱尽或池空为止**(池空=硬闸、A 链爬价=软闸),空货架合法态。货架 = **令牌级抽签**(同 kind 去重每 kind 至多一卡,价格公式 tier-blind 维持批②裁定,购买消费在架令牌本体 kind+tier)。两渠道不得各自私记库存——消费走同一入口写同一令牌库(实现=AdventureRunner._bonus_deck 单点,冒险结构 owner;kind 维度派生计数 _bonus_taken 承载断点附随件与读数)。**货架独立刷新(v0.15 MC-1 2026-10-04——P-C2+Q2 裁)**:进店免费刷新次数(基础 0,天赋/营地侧增益位预留)用尽后 GP 付费刷新(ceil(20×1.8^已付费刷) provisional——调参表旋钮 shop_refresh_*);重抽=同 pool_draw 共享池抽签(shop_draw 恰一新 scope),池空=不可刷新合法态;进店/离开双向清零付费刷计数。 +**共享池账本(v0.13 P1 共享池单点;v0.14 令牌库换代)**:商店货架与提升三选一是**同一个池**——货架候选 = 池∖{GP 袋}(用钱买钱恒负交易)= {攻击, 生命上限, 护甲, 行动点} 四种(秘宝件已随同调批出池);**库存 = 剩余令牌数**(TP0——「池消耗即上限」,种类次数上限退役,与奖励兑付摘牌同一令牌库承载,全冒险一次性跨关卡不补给):商店买一件,奖励面就少一枚;「无限购买」收敛为**钱尽或池空为止**(池空=硬闸、A 链爬价=软闸),空货架合法态。货架 = **令牌级抽签**(同 kind 去重每 kind 至多一卡,价格公式 tier-blind 维持批②裁定,购买消费在架令牌本体 kind+tier)。两渠道不得各自私记库存——消费走同一入口写同一令牌库(实现=AdventureRunner._bonus_deck 单点,冒险结构 owner;kind 维度派生计数 _bonus_taken 承载断点附随件与读数)。**货架独立刷新(v0.15 MC-1 2026-10-04——P-C2+Q2 裁)**:进店免费刷新次数(基础 0,天赋/营地侧增益位预留)用尽后 GP 付费刷新(ceil(20×1.8^已付费刷) provisional——调参表旋钮 shop_refresh_*);重抽=同 pool_draw 共享池抽签(shop_draw 恰一新 scope),池空=不可刷新合法态;进店/离开双向清零付费刷计数。**三选一同款付费档(v0.16 2026-10-06 用户令——刷新对齐商店)**:提升三选一免费双源(刷新件 X/冒险池 Y,机制正主=[秘宝](../排雷循环/秘宝.md)三选一刷新双源节)用尽后 GP 付费刷新——读同 shop_refresh_* 两旋钮(单点调参)、扣同一 GP 冒险账本(LevelRunner.spend_gp 单口)、重抽同 reward_pick 共享池抽签;付费刷计数每停点清零(与进店清零同构);钮常驻选择态、免费与付费两源皆穷=禁用。 ### 关卡大奖励:通关回血 + 提升三选一(钥石退役出大奖励) @@ -332,3 +332,4 @@ GP(显示名「金币」)是冒险内的一次性属性提升货币:战斗 | v0.12 | 2026-10-01 | **关卡大奖励重构批(迭代 10——用户 2026-09-30 拍板案丙 + P-A~P-E 全裁毕;冒险表扩列 / meta.tres 重锚 / registry / 行动经济 / 元进度 / 01-核心循环 / 03-内容规模 / 难度曲线与Boss / 关卡节点地图 / 06 同批落文)**:①大奖励「固定三件套」退役改两层兑付——非末关通关 = 部分回血(clear_heal_pct 20 provisional)+ 提升三选一(池六种:攻击/生命上限/护甲/GP 袋/秘宝/行动点件;加权洗牌不放回 E-S weight-key 消费 reward_pick 封闭流;属性件上限各 3、AP 件上限 2,达上限先剔牌库);②统一冒险加算区三轴扩四轴 +行动点(AP 件 = 每场战斗开始首轮基线 +1);③钥石退役出大奖励改 Boss/精英击杀逐杀掉落(boss_keystone 重锚首杀 3/复杀 2,末关照常入账,精英掉 1 预留);④秘宝件受同调位约束(过渡键 transitional_sync_slots=1 provisional,正主 = 同调实施批谓词表;超位替换交互 N+1 选 N、战斗内渠道沉没不入);⑤断点附随件扩奖励选择态 + 件获取次数计数;⑥选择停点零自动推进/可中断。渲染层验证冻结(用户 2026-10-01 提前开工拍板——不合入 master、目检待批),目检清单随批交付 | | v0.13 | 2026-10-03 | **GP 商店房重构批(迭代 11 第②批——任务书 v1.3 全裁 P1 共享池/P1-a/P1-b 双甲/P2 甲/P3 甲/P4 ceil/P5 甲;06「GP 商店房重构」条 2026-10-02 用户裁决取代 09-20 无刷新裁决)**:①GP 冒险账本节商店购买行换代——价格公式 = ceil(基价 × b^(关序−1) × A^(本次进店已购次数))(A/b provisional 1.8/1.1;legacy 旧式随对照模式保留注记);②新增「共享池账本」段——商店货架 = 池∖{GP 袋}∖{秘宝件} 四种,库存=上限即库存全冒险一次性,两渠道同一账本(买一件=奖励面少一件),池空硬闸+空架合法态;③shop_draw 扩签登记;④统一冒险加算区段——pool 模式买上限不回血(池件=奖励件本体)、AP 件累加源并商店;⑤断点附随件扩商店货架态(shop_shelf/shop_visit_buys,P2 甲;确定性重放承载注记);⑥调参表 GP 经济参数行随表换代。实现=AdventureRunner/LevelRunner 池模式(缺省 legacy 对照承载,真实入口投 pool) | | v0.14 | 2026-10-03 | **令牌池落地批(迭代 12 TP0——node-reward v2.2 追认 b 实施批)**:①提升池改**固定配额令牌池**——普通 70/稀有 25/罕见 3=98 张〔罕见档原列「道具件 2」随 aux_item 池种退役〔迭代 11 第①批 T7,落地晚于追认 b〕收窄,池总量 100→98——台账自决披露项〕;②抽取机制换代——Fisher-Yates 不放回抽 3 同 kind 去重每 kind 至多一枚(取代 E-S 加权洗牌),选中踢池未选回池、池耗即枯竭、**种类次数上限退役**;③恢复件新入池(rare 档 +6 当即回血封顶生效上限)+ 数值对(小GP 20/大GP 50 等);④MC 策略 316-f 落地(autoplay 1 级最稀有/2 级最左);⑤共享池账本段换代——库存=剩余令牌数,货架=令牌级抽签(价格公式 tier-blind 维持批②裁定),消费=AdventureRunner._bonus_deck 单点;⑥断点附随件扩令牌池余库(bonus_deck 逐枚还原;旧断点缺键=满库重建防御口径单版本周期);⑦现行基线锚换代=runsum_m4iter12_tp0_n20_20261003(normal 档配对读数同带 0/5/0) | +| v0.16 | 2026-10-06 | **三选一刷新对齐商店批(用户令——「三选一对齐商店,得有刷新」;机制正主=秘宝.md 三选一刷新双源节同批落文)**:免费双源(刷新件 X/冒险池 Y)用尽后 GP 付费刷新——同 shop_refresh_* 两旋钮(curio_refresh_gp_cost 单点调参)、扣同一 GP 冒险账本(LevelRunner.spend_gp 新单口)、重抽同 reward_pick 共享池抽签;付费刷计数每停点清零;呈现=CurioPickOverlay.set_refresh_display 与商店 set_refresh_display 同构(钮常驻选择态、两源皆穷禁用不隐藏、角标=免费余量、价签行示 GP 价);通关停点刷新重发载荷带 refreshed 标直落选择面(不再重开胜利总结面)。店内门票专长三选一暂不挂刷新面(维持现状)。单测=tests/unit/core/curio_refresh_paid_test.gd | diff --git a/design/gdd/systems/冒险循环/奇物.md b/design/gdd/systems/冒险循环/奇物.md index 4ce8891e..7f809c0c 100644 --- a/design/gdd/systems/冒险循环/奇物.md +++ b/design/gdd/systems/冒险循环/奇物.md @@ -29,7 +29,7 @@ | 负责 | 不负责 | |---|---| -| 奇物的定义、三个分类、以及分类与渠道的交叉开关 | 每件效果怎么接入结算——归挂点所属文档([战斗结算](../战斗循环/战斗结算.md)、[行动经济](../排雷循环/行动经济.md)、[地形变异](../排雷循环/地形变异.md)) | +| 奇物的定义、三个分类、以及分类与渠道的交叉开关 | 每件效果怎么接入结算——归订阅事件所属文档([战斗结算](../战斗循环/战斗结算.md)、[行动经济](../排雷循环/行动经济.md)、[地形变异](../排雷循环/地形变异.md)) | | 生命周期边界:何时出现、何时生效、何时清零 | 卡面排版与界面呈现细则——见 [HUD框架](../表现与支撑/HUD框架.md) | | 获取渠道的口径与约束 | 魔法道具与元进度——归上表两份文档 | | 与相邻概念的边界 | 格面资源、关卡节点、商店本身的规则——归各自所属文档 | @@ -61,7 +61,7 @@ | 分类 | 它给的是什么 | 落点 | 事实源 | |---|---|---|---| -| **专长**(Feat) | 给规则。在结算管线某个挂点上给自己加一条规则;同一标签的持有件数达阈值时质变 | 各结算段挂点;标签与档位由求值服务统一算 | `relics.tres`(逐项定义) | +| **专长**(Feat) | 给规则。订阅结算管线的某个事件(订阅哪个事件 / 什么条件 / 什么效果三字段声明);同一标签的持有件数达阈值时质变 | 各结算段事件发射点;标签与档位由求值服务统一算 | `feats.tres`(逐项定义) | | **属性值提升**(ASI) | 给数字。抬高角色某项战斗属性的基础值 | 冒险加算区的生命上限 / AC / 攻击三轴 | `adventure.tres`(种类配额与值) | | **资源** | 给"可用的量"。不抬高战斗属性,也不改规则,而是扩充你能调动的余量与机会 | 行动经济、三选一界面、局内钱包;**不进加算区、多数不进持有集** | `adventure.tres`(种类配额与值) | @@ -272,8 +272,8 @@ |---|---| | [魔法道具](../排雷循环/秘宝.md) | 最需要分清的一对,判据是生命周期:**奇物当局清零,魔法道具跨冒险保留**。两者互不相通——奇物永不进同调位,魔法道具永不进当局随机渠道。 | | [元进度](../跨局循环/元进度.md)(树) | 树管账号永久的规则与容量,奇物管当局构筑,两层互不代替。 | -| [战斗结算](../战斗循环/战斗结算.md) | 专长的挂点位于结算管线的各段;挂点清单与语义归结算文档。 | -| [行动经济](../排雷循环/行动经济.md)、[地形变异](../排雷循环/地形变异.md) | 部分挂点落在行动点结转与地形处理上,语义归各自文档。 | +| [战斗结算](../战斗循环/战斗结算.md) | 专长订阅的事件发射点位于结算管线的各段;事件词汇与发射时机归结算文档。 | +| [行动经济](../排雷循环/行动经济.md)、[地形变异](../排雷循环/地形变异.md) | 部分事件(付费开掘 / 地形受伤)落在行动点收对价与地形处理上,语义归各自文档。 | | [关卡节点地图](关卡节点地图.md) | 是渠道的载体,不是奇物的属性。专长挂在 Boss 节点兑付面上,节点类型词表零扩类。 | | [地图战斗资源](../战斗循环/地图战斗资源.md)(格面资源) | 无关:格面资源是单格的一次性产出,不进持有集,也不是渠道给的。 | | [存档序列化](../表现与支撑/存档序列化.md) | 断点负载记录持有集与剩余候选池;键名与版本迁移策略归存档文档。 | @@ -299,7 +299,7 @@ | 参数 | 取值域 | 事实源 | |---|---|---| -| 专长定义行(标识 / 显示名 / 稀有度 / 标签 / 挂点 / 效果字典) | 各字段封闭枚举,行键白名单封闭 | `game/data/tables/feats.tres` | +| 专长定义行(标识 / 显示名 / 稀有度 / 标签 / 效果说明 / 订阅行〔订阅哪个事件 on / 什么条件 when / 什么效果 do〕) | 各字段封闭枚举,行键白名单封闭;事件 / 效果类型 / 条件事实 / 算子四词表封闭(效果引擎单一事实源) | `game/data/tables/feats.tres` | | 专长门票(出现概率 / 配额 / 全局限量) | 整数 | **尚未入表**,见待办 | | 属性值提升种类 | 封闭三项:生命上限 / AC / 攻击,扩项走文档修订 | `game/data/schemas/adventure_table.gd` | | 资源种类 | 行动点上限 / 金币 / 回复(**刷新次数与专长获取两项尚未入表**,见待办) | 同上 | @@ -345,6 +345,7 @@ | v2.1 | 2026-10-05 | **交叉表收口(用户拍板)**:Boss 渠道**只出专长**,属性值提升与资源在此均不出现——三条渠道的形状定为"一块独占 + 两块并行",专长靠打赢、其余靠抽取与购买。Boss 兑付明确为**终局前**的 Boss,终局 Boss 不给(无后续关卡可消费)。新增 2.4 节**「计价物不自售」规则**:金币不进商店既不拆分类也不开特例,而是写成两轴交叉处的一条约束——商店的计价物是金币,故金币不进商店;按"能不能进商店"反推分类等于用渠道轴去切分类轴,两根轴会缠回去。 | | v2.2 | 2026-10-05 | **门票形态 + 约束分层 + 默认原则**。① 用户明确:奖励节点与商店不直接给专长,而是给一个**「专长获取」道具**,拿到后开专长三选一——交叉表专长在两渠道由"不出现"改为"**门票形式**";门票归专长(给的是一件以前不会的事,只是把选择推迟),理由是防止用钱定点买构筑。② 用户质疑「计价物不自售」本质是特判——**承认成立**:它确实是在分类白名单之外加的一道过滤,与死特判的区别在于把"枚举例外"变成"**可计算谓词**"(新渠道/新奖励自动判定)。2.4 改写为**三层约束**(分类白名单 / 可计算谓词 / 显式例外清单),并给出判断标准:结构性原因可写成谓词,设计性原因只能进清单且每条须写理由;三个方案(单独分类 / 谓词 / 死特判)的代价并列摆出,不藏折中。③ 用户定**默认原则**:除已登记的特殊规则外,分类内的东西都要在池子里——据此**回复应在商店内**,现行 shop.tres 货架缺它是"现状与表格不对齐",须在实施批补上。 | | v2.3 | 2026-10-05 | **门票机制细化 + 默认原则表述修正**。① 门票**拿到即开、不可囤积**(用户定)——不进持有集,防止玩家把关键决策无限推迟,也防持有物语义从后门回来。② 门票的"罕见"**只在概率不在价格**,一局限购一次,并加**计步保底**:连续 5 次三选一展示未出现则第 6 次必出;计数跨奖励节点与商店共享、**刷新后的重新展示计入**(用户明定含刷新),限购用满后保底不再触发。③ **修正 v2.2 的误读**:把"分类在某渠道不出现"当成需要理由的缺格是错的——渠道出哪些分类是渠道轴的定位(Boss 只出专长),不是例外;默认原则只管**分类内部**的成员齐不齐。④ 专长总量与档位阈值的匹配按用户决定**延后**(待第四关内容批后实测复裁),另单独登记 charge 标签仅 3 件、永远达不到档二的数据缺陷。 | +| **v2.8** | 2026-10-06 | **效果行为数据驱动换代(内容数据驱动扩展批)**:专长的效果声明从「挂点+效果字典」换为「订阅行」——每件声明**订阅哪个事件(on)/ 什么条件(when)/ 什么效果(do)**,由效果引擎统一解释执行;旧挂点枚举消亡(事件名+效果类型取代)。配套事实:效果引擎 `game/core/effect_engine.gd`(事件 16 / 效果类型 24 / 条件事实 9 / 算子封闭词表);专长行 `feats.tres` 27 件全量迁移,秘宝被动六件同批订阅化;行为与迁移前逐位一致(MC 同种子 40 局逐位对拍实证)。 | **v2.7** | 2026-10-05 | **三个子节点正式定名(用户拍板:去「类」字后缀)**。此前三个分类只有带后缀的描述(机制类 / 属性提升类 / 资源类),是文档层临时叫法,既非专名也非玩家可读的词。现定专名:**专长**(Feat)/ **属性值提升**(ASI)/ **资源**。定名依据:前两个是 DND 规则书官方中文,且与用户 v1.1 原话一致(当时原话就是「属性提升(ASI)」与「专长」,v1.1 落文时被加了「类」字后缀而退化成描述)。「机制类」这个叫法本身还有硬伤——它的 28 个成员里没有一件是"机制",实际是"在结算挂点上改一条规则",读者看到会误解为游戏机制。 **新增 1.6 节「三层结构与命名」**(用户 2026-10-05 拍板做玩家可见的三层):奇物(总称)/三个分类/具体单件。明确**命名是本节规矩、呈现是另一件事**——分类名在卡面不出现(归 battle.md / node-reward.md 裁决),但帮百科、成就名、效果描述的指代三处都要用,所以它必须是玩家读一遍就懂的正式名词。连带:修掉批量替换撞出的五处「专长(专长)」类自撞、v2.0 历史行内的自撞并标注当时的旧称、registry 的「资源(命名待定)」过期标注。 | **v2.6** | 2026-10-05 | **修正核心循环位置图:奇物不是打一场战斗给一份**(用户纠错)。原图把"关内 排雷推理 → 战斗结算 → 通关 → 得到一份奇物"画成一条链,读起来就是每场战斗通关都给一份——**错在把关内普通战斗的通关与关底 Boss 的通关混为一谈**。按 [冒险结构](冒险结构.md) 的定义,**通关一关 = 该关关底 Boss 被击杀**,所以"击败 Boss"与"通过这一关"是同一个时刻、不是一条链上的两步。改法:① 图改为分支结构(普通战斗节点→不给、往下走;关底 Boss 击败→专长三选一+通关回血);② **一关只兑付一次**写成显式口径;③ 2.1 节渠道名由"终局前 Boss 击败后"改为"关底 Boss 击败(即通过这一关)",并写明触发点就是关卡推进本身那一刻;④ 文首引言加"兑付频率"条,交叉表渠道名与 registry 同步;⑤ 连带修掉一处污染——节点类型封闭枚举五种里的"奖励"曾被 v2.4 批量替换误伤成"奇物"(节点类型里没有奇物这一类),已复原。 | diff --git a/design/gdd/systems/战斗循环/地图战斗资源.md b/design/gdd/systems/战斗循环/地图战斗资源.md index 09dfe8b6..38c218b2 100644 --- a/design/gdd/systems/战斗循环/地图战斗资源.md +++ b/design/gdd/systems/战斗循环/地图战斗资源.md @@ -40,7 +40,7 @@ ## 在核心循环中的位置 ``` -装板 ──(一次摇号定全图资源身份 + 清图对象)──▶ 挖掘期(身份恒定可读,只揭晓不兑付)──▶ 轮末授予段(极性路由逐格兑付)──▶ 战斗段 +装板 ──(一次摇号定全图资源身份 + 清图对象)──▶ 挖掘期(身份恒定可读,只揭晓不兑付)──▶ 轮末授予段(极性路由逐格兑付;伤害形态单据暂存)──▶ 战斗段(段末地图伤害账统一落地——案C 2026-10-06 用户裁) ``` 本系统横跨挖掘期与轮末结算的头部:装板时全图资源身份一次摇定;挖掘期只揭晓身份,轮末授予段(结算里逐格兑付的段落)按极性矩阵把当轮的因兑现——正向当场生效,负向也当场生效(负向恒即时,无库存语义)。段序编排归[战斗结算](战斗结算.md)。 @@ -125,7 +125,7 @@ ### 负向恒即时:惩罚没有库存语义 -负向兑付全部在授予段当场执行,不入库、不出窗口——惩罚不设库存、不留存量。正向里的 percent 类(增益行)进[增益状态](增益状态.md)的容器;flat 类(群伤、治疗、直伤)当场结算;gp 正负两向(金币袋 / 破财)同样当场执行——记入战斗内 GP 缓冲,战斗完成随 gp_dropped 与钱包结算(账本归[冒险结构](../冒险循环/冒险结构.md)),同样无库存语义。叠层饱和由战斗结算既有池 clamp 承载;blast 负向(警觉)与 action 负向(迟滞)直授无上限。负向直伤使主角生命归零时,结算按战斗结算的致死短路规则处理。 +负向兑付的账户动作全部在授予段受理,不入库、不出窗口——惩罚不设库存、不留存量;其中**伤害形态(群伤/直伤类负向行)的扣血落地殿后到战斗段末**(2026-10-06 用户裁,单据暂存,见[战斗结算](战斗结算.md));主角在敌方还手中倒下则地图伤害账整体不兑(逐单留「未生效」行)。正向里的 percent 类(增益行)进[增益状态](增益状态.md)的容器;heal 正向当场结算;gp 正负两向(金币袋 / 破财)当场执行——记入战斗内 GP 缓冲,战斗完成随 gp_dropped 与钱包结算(账本归[冒险结构](../冒险循环/冒险结构.md)),同样无库存语义。叠层饱和由战斗结算既有池 clamp 承载;blast 负向(警觉)与 action 负向(迟滞)直授无上限。 举证:本节为兑付语义,无计算。 @@ -169,7 +169,7 @@ 1. 战斗结算装上新图后、玩家开挖前调用一次摇号:全部非封锁格的资源类型与清图对象身份摇定,映射进入只读。 2. 玩家挖掘:挖开的格子当场揭晓类型身份;已开格的身份随时可读。 -3. 轮末授予段按极性矩阵逐格兑付:正向 percent 挂状态、正向 flat 当场结算,负向全部当场兑付。 +3. 轮末授予段按极性矩阵逐格兑付:正向 percent 挂状态、heal 正向当场结算,非伤害负向当场兑付;伤害形态正负单据暂存,战斗段末统一落地。 4. 清图轮兑付清图奖励对象,恒非空;完美排雷追加第二份。 5. 战斗结束:板级映射退役。换图按新板重摇。 diff --git a/design/gdd/systems/战斗循环/增益状态.md b/design/gdd/systems/战斗循环/增益状态.md index 345b4af2..4a6c869a 100644 --- a/design/gdd/systems/战斗循环/增益状态.md +++ b/design/gdd/systems/战斗循环/增益状态.md @@ -254,6 +254,6 @@ empower 与 fortify 是敌方技能池增益技在容器侧的数据落点(技 | v0.7 | 2026-09-17 | **数值压缩批·文档同步(与 doc-conventions.md §5 数值纪律·强制收口条款同批)**:ambush 幅度 +3 flat → +1 flat(量纲未变、压缩倍数三阶下发,参见 `data/tables/status_effects.tres` 注释)。**双写失同步收口**:① 状态行表与调参表数值列改指路存根(指向 `data/tables/status_effects.tres` 与 `registry/entities.yaml` status_effect mvp_content);② 调参表删除「现行值」列(敌人系统 v0.6 先例),仅保留 schema 模板与取值域;③ 与 `地图战斗资源.md` v0.7 / `registry/entities.yaml` 同批落地——五步收口一并兑现 | | v0.8 | 2026-09-17 | **数值纪律收口批:正文与调参表 knob 现行值改指路存根(事实源 `data/tables/*.tres` 与 registry),8 处失同步值修正**——幅度例"两层 +50% 合计 +100"改"2 × 单行幅度(读表)";削弱例"−130 个百分点"改"−(2 × weaken 行幅度)(读表)";护甲例"AC10 → AC12(期望减免 21.25% → 28.75%)"改"基准 AC(读 hero 表)+ 层数"、派生读数删除;状态行表"时限/合并"列 1 次/×2/×16/×1 字面量改读表措辞;defense_up 退役条目删"−30%"数值;"两行 duration = 2"改"duration 表值(读表)";empower"×2"幅度与调参表"AC10 → 11 → 12"、"2 次 = 1 个有效增益拍"同步指路化 | | v0.9 | 2026-09-18 | **战斗数值结构修复批(2026-09-18 盘问会话用户拍板,与战斗结算 / registry / 切片代码同批)**:三行语义改写(数值全部 provisional 归状态层重设计批)——① off_guard(失衡)换装 AC 轴:unit percent→ac、退出 hero_defense 池(effect_pool_map 删键,池清空结构保留),状态行表与调参表同步,"标错反噬三池"措辞改"三行";② fortify(硬化)符号纠正:旧正值入池实际 ×2 翻倍与"硬化=减伤"语义相反,改负值后主角攻击折半(正负号语义成文:负值 = 减伤方向);③ weaken(削弱)叠层改续时:stack→refresh、duration 表值 2——同名重复授予只刷新剩余次数、层数恒 1,×2 叠满池触底 ×0 的免伤轮结构性根治;消费侧例子表"削弱叠两层压地板"改"暴走叠两层封顶 ×2"(削弱叠层读法退役);"叠层不续命"对削弱不再适用,行动经济联动句随之失效;调参表一次性/叠层上限两行措辞同步 | -| v0.10 | 2026-09-19 | **技能池新效应词汇批(用户拍板——设计权威 docs/proposal-status-vocabulary.md v1.0,与 敌人系统 / 战斗结算 / registry / 切片代码同批)**:off_guard(失衡)新增第二来源类别 = 敌方技能施放(妨害技"蛛网束缚"的容器落点——首实装于 L2 Boss 蛛化卓尔精灵池;duration 由技能行 buff_duration=2 覆盖 = 1 个有效结算,empower/fortify 同款口径);容器语义零改动——同构实例、side 路由照常,妨害技零新状态行(乏力 sapped 留作妨害第二词汇,暂无载体) | +| v0.10 | 2026-09-19 | **技能池新效应词汇批(用户拍板——设计权威 proposal-status-vocabulary.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 词汇表/增益状态.md〕 v1.0,与 敌人系统 / 战斗结算 / registry / 切片代码同批)**:off_guard(失衡)新增第二来源类别 = 敌方技能施放(妨害技"蛛网束缚"的容器落点——首实装于 L2 Boss 蛛化卓尔精灵池;duration 由技能行 buff_duration=2 覆盖 = 1 个有效结算,empower/fortify 同款口径);容器语义零改动——同构实例、side 路由照常,妨害技零新状态行(乏力 sapped 留作妨害第二词汇,暂无载体) | | v0.11 | 2026-09-20 | **格面资源八型批(用户拍板——与敌人系统 / 战斗结算 / registry / 切片代码同批)**:① 状态行表 ambush 行 → alert 警觉行(敌方护甲等级 +1,ac 量纲,side-wide);② 标空旗路由措辞纠正(2026-09-15 极性矩阵后已无三选一反噬,本批顺手收口);③ 敌人系统的伏击量纲定义/上限锚义务面随伏击退役移除 | | v0.12 | 2026-09-20 | **主角增益只续时不叠层批(用户拍板「主角的其实也一样,都是按照叙事的方式,不要有叠层;只续时不叠层,变相削弱主角性能可接受」;与 status_effects.tres / smoke / registry / 06 清单同批)**:① attack_up(增伤)/ ac_up(护甲强化)两行 stack_policy stack→refresh——重复授予层数恒 1、剩余结算次数重置,对齐削弱 2026-09-18 续时定案;至此主角可重复获取的增益与减益合并语义统一(叠层策略仅剩敌侧 alert / enrage / off_guard / sapped 等行);② duration 表值 1 与 max_stack 2 不动(refresh 下 max_stack 不消费——weaken 先例;dur=1 下续时表现为重复拿重置当轮倒数,跨轮延续随将来 duration 拉长自然显形,数值归状态层重设计批);③ 影响面:增伤不再 +100%(hero_offense 池单层封顶 +50)、护甲强化不再 +2 AC(局内可达 AC 上限 11,AC12 折扣档采样探针随行降为 AC11)、这两行不再可达满层拒绝行;④ 同名状态合并策略表/幅度量纲例子表/调参表两行/联动纪律句/验收"叠层不减钟"条改述;06 清单「叠层不续命的读感」条目解决移除;冒烟三闸全过(ac_up 行断言与 AC 探针改形,无净增断言) | diff --git a/design/gdd/systems/战斗循环/战斗结算.md b/design/gdd/systems/战斗循环/战斗结算.md index 60eeb63d..2dab5bdd 100644 --- a/design/gdd/systems/战斗循环/战斗结算.md +++ b/design/gdd/systems/战斗循环/战斗结算.md @@ -51,7 +51,7 @@ ### 轮末五段结算:一轮从收场走到判定 -玩家行动点花完、主动停手,或开完整张图时,[行动经济](../排雷循环/行动经济.md)宣布本轮结束,本系统随即自动发起一次整轮结算。结算严格按五段固定顺序执行;核心相对次序"增益减益先于战斗"不变——先合成修正再动刀,任何结局都能归因。 +玩家行动点花完、主动停手,或开完整张图时,[行动经济](../排雷循环/行动经济.md)宣布本轮结束,本系统随即自动发起一次整轮结算。结算严格按五段固定顺序执行;核心相对次序"增益减益先于战斗"不变——先合成修正再动刀,任何结局都能归因。地图奖励与惩罚中的**伤害形态兑付一律殿后到战斗段末**(2026-10-06 用户裁):授予段只清资源、增益、治疗、行动点、GP 等非伤害账,刀落完后地图伤害账才统一落地。 ``` ① 授予段 → ② 增益/减益段 → ③ 战斗段 → ④ 衰减段 → ⑤ 判定段 @@ -59,9 +59,9 @@ | 段 | 干什么 | |---|---| -| ① 授予段 | 结算队列先过滤再分批兑换:开格、旗判定、清图,按极性矩阵路由(详见授予段一节) | +| ① 授予段 | 结算队列先过滤再分批兑换:开格、旗判定、清图,按极性矩阵路由(资源/增益/治疗/行动点/GP 当场兑付;伤害形态单据暂存不兑,详见授予段一节) | | ② 增益/减益段 | 把主角与敌人身上的活跃效果一次性合成本结算的修正池(同侧百分比修正的加总桶) | -| ③ 战斗段 | 主角先手攻击一次,随后敌人按行序逐个还手(详见战斗段一节) | +| ③ 战斗段 | 主角先基础攻击一次,随后敌人按行序逐个结算攻击,最后地图奖励与惩罚的伤害兑付在段末统一落地(详见战斗段一节) | | ④ 衰减段 | 移除到期的临时效果;每轮恰好一次,含末轮——清图奖励先兑付、效果后衰减 | | ⑤ 判定段 | 先判主角倒下(负),再判敌人清空(胜),都不满足则继续下一轮 | @@ -139,16 +139,16 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 × | 旗判定批 | 标中则兑该雷格资源的正向,每颗雷的资格只兑一次;标空则记"标旗未中"归因行,并兑该安全格资源的负向 | | 清图批 | 清图奖励对象恰好发一次;完美排雷追加第二份同身份兑付 | -授予段内的伤害兑付(爆裂、直伤、反噬)以伤害单据入队,攒到授予段尾统一结账;清图批为段内同步结账点——开单前段内排队单先按开单序兑现(见[伤害单据与伤害控制器](#伤害单据与伤害控制器所有伤害走同一道门)一节)。 +授予段当场只兑非伤害形态(增益、治疗、行动点、GP 缓冲、警觉加层);**伤害形态兑付(爆裂、直伤、反噬)以伤害单据暂存,攒到战斗段末统一结账**(2026-10-06 用户裁——地图伤害账殿后,见[伤害单据与伤害控制器](#伤害单据与伤害控制器所有伤害走同一道门)与战斗段两节)。暂存单在授予段按「将兑付」回报——授予段内依赖兑付读数的流程决策(完美排雷第二份、高光份数)依据开单时点事实判定,该时点敌人存活有保证;战斗段末实际结账若目标已灭,控制器自记「奖励未生效」行。 **充能已退役(2026-09-19 用户拍板)**:原"挖格/标中旗给充能计数 → 战斗段折算穿刺/魔法分量"的弹药系统整体移除——主角攻击回归单分量,克制策略归冒险前配置(主角 attack_type 表值,营地换型 = 改字段;未来冒险前参数面承接策略位)。攻防两条抽象分离纪律保留:攻击类型是伤害的属性轴,棋盘事实只是奖励触发源。 | 兑付形态 | 覆盖 | 兑付方式 | 拒绝语义 | |---|---|---|---| | 增益(percent) | attack / defense / weaken 的正负两向 | 进入[增益状态](增益状态.md)的容器,本结算生效 | 叠层已满时拒收,记"奖励未生效"行 | -| 即时全体伤害(flat) | blast 正向 | 当场对全体存活敌各扣表值生命 | 场上无敌可打时零兑付,记"奖励未生效"行 | +| 即时全体伤害(flat) | blast 正向 | 战斗段末对全体存活敌各扣表值生命(暂存单殿后统一落地) | 场上无敌可打时零兑付,记"奖励未生效"行 | | 即时治疗(flat) | heal 正向 | 主角按表值回复并封顶上限 | 满血时零兑付,记"奖励未生效"行 | -| 直伤(flat) | heal 负向 | 主角当场扣表值生命,不经增益状态 | 生命归零即走致死短路 | +| 直伤(flat) | heal 负向 | 主角在战斗段末扣表值生命(暂存单殿后统一落地),不经增益状态 | 主角倒下则地图账整体不兑,逐单留"未生效"行 | | 警觉加层 | blast 负向 | 敌方全体护甲等级 +1/层(读 `reward.tres` enemy_alert 行 → `status_effects.tres` alert 行),入增益状态容器 | 直授无上限;战斗段由防守方护甲等级读数消费 | | GP 缓冲(flat) | gp 正 / 负向 | 金币袋 / 破财按 power 记入战斗内 GP 缓冲(+power / −power);缓冲随战斗完成折进 battle_finished 的 gp_dropped 统一到账(钱包账本归[冒险结构](../冒险循环/冒险结构.md)) | 无拒收态——缓冲只在战斗边界与钱包结算,战斗域零钱包状态 | | 行动点修正 | action 正/负向 | 下轮行动点 ±power(action_economy 排队,下一轮开始兑现后清零;下限 0) | 排队净值不跨轮保留;明细记效果行(effect_id=ap_delta) | @@ -181,13 +181,13 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 × **死亡事件由单位自行宣布**:单位生命归零的那一刻自己发出死亡事件(不由流程代查、不由控制器推断)。怪死 → 队列中该敌为来源的未结单据作废(死亡即不动手——静默零行、不进冷却、不进保底计数,同既有口径);主角死 → 兑付终止、余单作废,Game Over 仍归判定段的生命读数路径。 -**结账时机双模式(结构位,非调参项)**:单据开出后何时兑现由控制器政策承载。现行 = **段界结账**:授予段的伤害单攒到授予段尾统一兑现;清图批是段内同步结账点——开单前先按序结清段内排队单、随后即开即结(完美第二份与高光份数的流程依赖需要同步读数);战斗段的单攒到战斗段尾统一兑现。保留位 = **开单即结**:提交当场兑现,逐位等价 2026-09-24 之前的行为,作回切结构位。两模式同种子同操作均逐位复现(兑现次序 = 开单次序);同一输入在两模式下结果可不同(兑现时机的语义差异),历史基线读数以段界结账口径为准。切换 = 改控制器政策常量,走代码修订不走调参。 +**结账时机双模式(结构位,非调参项)**:单据开出后何时兑现由控制器政策承载。现行 = **段界结账 + 地图账殿后**(2026-10-06 用户裁):授予段不开具任何当场兑现的伤害单——全部伤害形态单据暂存(含清图批),战斗段按「主角先手 → 逐敌还手 → 地图账」的顺序补提交,攒到战斗段尾统一兑现;主角在敌方还手中倒下 → 暂存单整体不兑,逐单留「未生效(主角已倒下)」行。清图批的完美第二份与高光份数决策依据开单时点事实(该时点敌人存活有保证),暂存单按「将兑付」回报。保留位 = **开单即结**:提交当场兑现,逐位等价 2026-09-24 之前的行为,作回切结构位。两模式同种子同操作均逐位复现(兑现次序 = 开单次序);同一输入在两模式下结果可不同(兑现时机的语义差异),历史基线读数以 2026-10-06 之前的授予段结账口径为准——该口径已随案C退役,新读数一律按地图账殿后重采。切换 = 改控制器政策常量,走代码修订不走调参。 举证:本节为管线编排约定,无新计算;掷点、折扣、克制与池乘区的算术口径见战斗段一节。 ### 战斗段与胜负判定:谁先动手、怎么扣血、怎么定胜负 -战斗段是兑现的最后一刀。主角的基础攻击恒存在——这是项目拍板:若基线为零,百分比增益乘上去永远是零,增益系统就空转了。攻击力域 [1, 999]、0 于加载期拒绝,行值读主角表。顺序固定:主角先手攻击顺位最前的存活敌人,随后按行序逐个存活敌人按本拍摇定技能还手;每个敌人依次走"按技能行兑现、结算伤害、执行上报(技能进冷却 + 保底计数)"三步且恰行动一次,主角不在其列。主角中途倒下,剩余敌人全部停手,直接进判定段。本段只开单——主角先手单与逐敌伤害技单攒到战斗段尾统一结账(见[伤害单据与伤害控制器](#伤害单据与伤害控制器所有伤害走同一道门)一节);施放技不涉伤害、照旧当场落行。 +战斗段是兑现的最后一刀。主角的基础攻击恒存在——这是项目拍板:若基线为零,百分比增益乘上去永远是零,增益系统就空转了。攻击力域 [1, 999]、0 于加载期拒绝,行值读主角表。顺序固定:主角先基础攻击顺位最前的存活敌人,随后按行序逐个存活敌人按本拍摇定技能还手;每个敌人依次走"按技能行兑现、结算伤害、执行上报(技能进冷却 + 保底计数)"三步且恰行动一次,主角不在其列;**最后兑地图伤害账**——授予段暂存的全部伤害单按授予段事件序补提交,与主角单、敌方单一起攒到战斗段尾统一结账(见[伤害单据与伤害控制器](#伤害单据与伤害控制器所有伤害走同一道门)一节)。主角中途倒下,剩余敌人全部停手,地图伤害账整体不兑(逐单留「未生效(主角已倒下)」行),直接进判定段。施放技不涉伤害、照旧当场落行。案C 后授予段不产出伤害,主角进入战斗段时恒存活、场上恒有敌——先手必兑。 **开轮摇号与敌方技能兑现**(2026-09-16 敌人技能池批):敌人每拍做什么由开轮摇号决定——每轮轮次开始时,本系统在换算层排除冷却中技能,按行序逐敌从 `behavior_roll` 封闭流的独立子流摇定本拍动作写入实例池(首轮同样摇号;每轮恰摇一次;轮内刷新换图不重摇),随后冷却递减。摇定后、兑现前死亡 → 本拍技能作废、不动手(死亡即不动手)。结算战斗段只按技能行兑现:伤害技走三乘法链(见下),增益技记一行施放行并把状态效果按技能行持续回合挂入[增益状态](增益状态.md)容器——0 伤害动作不掷不折。池模板、技能行、冷却与保底语义归[敌人系统](敌人系统.md)(引用不转述)。 @@ -201,7 +201,7 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 × 增益池乘区(含敌方防御池)→ 向下取整 → 伤害下限 max(1,·) → 必中附加分量 rider(2026-09-19 新效应词汇批——伤害技 rider_mult 字段:max(伤害下限, floor(攻击基线 × rider_mult)),独立必中加项、不进乘区、不出暴击;无字段 = 无此步) ``` -主角先手攻击由三个分量逐项取整后相加:基础分量(攻击力经增益修正,可被池封顶压到零;**类型 = 主角 `attack_type` 表值**——2026-09-18 营地扩展前置批:基础攻击量与攻击类型抽象分离,基础分量不再恒属普通,缺省 normal 向后兼容)、穿刺分量、魔法分量。**攻击类型是伤害的属性轴**——它只决定这个分量查甲效表的哪一行,定义里不含任何棋盘来源。分量各自查表后取整相加;甲效表把攻击类型与敌方护甲类型交叉成一张 3×3 **克制修正矩阵(加法点数)**——2026-09-18 加法核换装:每个分量在掷点/暴击折算取整后 **+ 克制修正点数**(原百分比乘区退役): +主角先手攻击 = 单一基础分量(2026-09-19 充能退役批定稿;量 = 攻击力经增益修正,可被池封顶压到零;**类型 = 主角 `attack_type` 表值**——2026-09-18 营地扩展前置批:基础攻击量与攻击类型抽象分离,基础分量不再恒属普通,缺省 normal 向后兼容)。**攻击类型是伤害的属性轴**——它只决定这个分量查甲效表的哪一行,定义里不含任何棋盘来源。分量查表取整;甲效表把攻击类型与敌方护甲类型交叉成一张 3×3 **克制修正矩阵(加法点数)**——2026-09-18 加法核换装:每个分量在掷点/暴击折算取整后 **+ 克制修正点数**(原百分比乘区退役): | 攻击类型 \ 护甲类型 | 轻甲 | 魔法护甲 | 重甲 | 语义 | |---|---|---|---|---| @@ -213,7 +213,7 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 × 主角攻击 = 单一基础分量(2026-09-19 充能退役批):量 = 攻击力基线(表值),类型 = 主角表 `attack_type`(营地永久强化换型 = 改字段;未来冒险前参数面承接"按关配型"的策略位)。原"普通 + 穿刺/魔法充能三分量"模型整体退役——挖掘期与战斗期之间不再有弹药供给线,克制应对从局内攒桶改为冒险前配型 + 棋盘火力(穿刺弹/魔法矢等直伤奖励道具,消费面不变)。 -**护甲等级掷点:必中、非归零,只吃基础攻击分量**(2026-09-16 定稿落地,判定式与全部数值已拍板;判定对象措辞随敌人技能池批改述)。防守方由此多了一条数值轴——护甲等级(数据键 `armor_class`):攻击永远命中,没有 miss、没有零收益白回合;只有吃掷点的分量在掷点低于防守方护甲等级时按减伤档位打折,档位再重也不归零。判定式:攻击结算时掷单骰(骰面读表),掷点 ≥ 防守方护甲等级 + 攻击加成 → 全额;掷点 < 护甲等级按缺口(护甲等级 − 掷点)查减伤档位表打折——边界与档值读 `settlement.tres` 减伤档位表,三档皆非零档。暴击:天然最大面(读表)使该次基础攻击分量 ×暴击倍率(读表),双方同规,且天然最大面恒全额(暴击与折扣互斥;天然最大面〔nat20〕暴击是掷点层概念,与技能重击〔池技能〕严格区分——两层不得混述)。伤害下限:**任何来源的伤害落地**至少 damage_floor(读表,全局 max(damage_floor, ·)——2026-09-20 用户拍板扩全来源:攻击双方、棋盘直伤与爆裂、地形变异、负向反噬;伤害取值域 [1, +∞),**不设上限**)——"被减伤归零 v=0"零值伤害行语义随批退役,重档折扣的攻击行在行文本记"被压制"。掷点范围:只有基础攻击分量吃掷点——主角先手攻击的普通分量,敌方技能的基础伤害分量(= 攻击基线 × 技能伤害倍率,普通攻击倍率 1 为特例);无伤害分量的技能(增益/妨害类)不掷不折;棋盘来源一律无视护甲等级——爆裂、清障炸药、奖励即时伤害、技能必中附加分量(rider,2026-09-19)全部必中全额不掷,这是本机制的边界切口,与"奖励/惩罚必中"同构。护甲等级(数值轴,掷点打折)与护甲类型(属性轴,克制修正)严格分离:护甲类型不做护甲等级映射,克制修正点数读表、消费面不变,两概念在文档、表字段与代码键中不得混用。数值载体:主角与敌方护甲等级行值读 hero / enemy 表的 armor_class 列(正式行值归怪物设定批,登记[待办清单](../../06-待办清单.md)),ac_up(护甲强化)在其上加算(幅度/层数读 `status_effects.tres`)。攻击加成(`attack_bonus`)为结构位预留字段,切片恒 0,未来归元进度 / 武器附魔投值。掷点时机在结算战斗段内、每次基础攻击伤害计算时判定,消费 `armor_roll` 封闭流(逐次掷逐次取号,可逐位回放)。 +**护甲等级掷点:必中、非归零,只吃基础攻击分量**(2026-09-16 定稿落地,判定式与全部数值已拍板;判定对象措辞随敌人技能池批改述)。防守方由此多了一条数值轴——护甲等级(数据键 `armor_class`):攻击永远命中,没有 miss、没有零收益白回合;只有吃掷点的分量在掷点低于防守方护甲等级时按减伤档位打折,档位再重也不归零。判定式:攻击结算时掷单骰(骰面读表),掷点 ≥ 防守方护甲等级 + 攻击加成 → 全额;掷点 < 护甲等级按缺口(护甲等级 − 掷点)查减伤档位表打折——边界与档值读 `settlement.tres` 减伤档位表,三档皆非零档。暴击:天然最大面(读表)使该次基础攻击分量 ×暴击倍率(读表),双方同规,且天然最大面恒全额(暴击与折扣互斥;天然最大面〔nat20〕暴击是掷点层概念,与技能重击〔池技能〕严格区分——两层不得混述)。伤害下限:**任何来源的伤害落地**至少 damage_floor(读表,全局 max(damage_floor, ·)——2026-09-20 用户拍板扩全来源:攻击双方、棋盘直伤与爆裂、地形变异、负向反噬;伤害取值域 [1, +∞),**不设上限**)——"被减伤归零 v=0"零值伤害行语义随批退役,重档折扣的攻击行在行文本记"被压制"。掷点范围:只有基础攻击分量吃掷点——主角先手攻击的基础分量,敌方技能的基础伤害分量(= 攻击基线 × 技能伤害倍率,普通攻击倍率 1 为特例);无伤害分量的技能(增益/妨害类)不掷不折;棋盘来源一律无视护甲等级——爆裂、清障炸药、奖励即时伤害、技能必中附加分量(rider,2026-09-19)全部必中全额不掷,这是本机制的边界切口,与"奖励/惩罚必中"同构。护甲等级(数值轴,掷点打折)与护甲类型(属性轴,克制修正)严格分离:护甲类型不做护甲等级映射,克制修正点数读表、消费面不变,两概念在文档、表字段与代码键中不得混用。数值载体:主角与敌方护甲等级行值读 hero / enemy 表的 armor_class 列(正式行值归怪物设定批,登记[待办清单](../../06-待办清单.md)),ac_up(护甲强化)在其上加算(幅度/层数读 `status_effects.tres`)。攻击加成(`attack_bonus`)为结构位预留字段,切片恒 0,未来归元进度 / 武器附魔投值。掷点时机在结算战斗段内、每次基础攻击伤害计算时判定,消费 `armor_roll` 封闭流(逐次掷逐次取号,可逐位回放)。 EV 参考数不入正文(派生读数,需要时按判定式与现行表值自行复算);旧 defense_up 的减伤墙已整体退役换装为 AC(无双轨,见版本历史)。微调旋钮 = 档位边界与档位值,不动护甲等级值。换装点锚定:基准 AC(读 hero 表)自带的方向性偏移与[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)已登记的加难杠杆扫描合并处理;其生存链区间模型按掷点带宽口径复核(带宽含暴击档与折扣档),"零红档"验收保留。 @@ -227,7 +227,7 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值 护甲等级掷点:掷单骰(骰面读表),掷点 ≥ 护甲等级 + 攻击加成 → 全额;低掷按缺口查减伤档位表(边界与档值读表,无零档) 暴击:天然最大面(读表)→ 该次基础攻击分量 ×暴击倍率(读表)且恒全额(双方同规;掷点层概念,与技能重击严格区分) 伤害下限:任何来源的伤害落地 ≥ damage_floor(读表,全局 max(damage_floor, ·)——折扣与池归零都不产生 v=0 伤害行;2026-09-20 扩全来源,取值域 [1, +∞)) -吃掷点分量 = 主角普通分量 / 敌方技能基础伤害分量;穿刺、魔法、增益/妨害施放、必中附加分量与棋盘来源伤害不掷不折 +吃掷点分量 = 主角基础分量 / 敌方技能基础伤害分量;穿刺、魔法、增益/妨害施放、必中附加分量与棋盘来源伤害不掷不折 治疗量 = min( 当前生命 + 表值, 生命上限 ) ``` @@ -235,7 +235,7 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值 |---|---| | 修正池封顶 | 同侧百分比修正加算后每池独立收口 ±100 百分点;乘数可为 0——输出压制触池底时落地伤害由全局下限保底 damage_floor(读表)(叠满免伤归零的结构面已随护甲等级换装消失) | | 伤害下限 | 任何来源的伤害落地至少 damage_floor(读表,全局 max(damage_floor, ·)——2026-09-20 扩全来源:攻击双方/棋盘直伤/地形变异/负向反噬,取值域 [1, +∞) 不设上限);"被减伤归零 v=0"零值伤害行语义退役,重档折扣行文本记"被压制"——免伤与零输出仍逐行可见 | -| 护甲等级掷点 | 必中非归零:掷单骰(骰面读表)对防守方护甲等级 + 攻击加成(恒 0),低掷按缺口查减伤档位表(读表)三档折扣、档位不设零档;天然最大面暴击 ×暴击倍率(读表)恒全额;只吃基础攻击分量(主角普通分量、敌方技能基础伤害分量),棋盘来源伤害与增益施放不掷不折 | +| 护甲等级掷点 | 必中非归零:掷单骰(骰面读表)对防守方护甲等级 + 攻击加成(恒 0),低掷按缺口查减伤档位表(读表)三档折扣、档位不设零档;天然最大面暴击 ×暴击倍率(读表)恒全额;只吃基础攻击分量(主角基础分量、敌方技能基础伤害分量),棋盘来源伤害与增益施放不掷不折 | | 溢杀 | 一刀超出目标剩余生命的部分丢弃:不转移给下一个敌人、不存续、无负生命中间态 | | 治疗 | 生命不溢出上限;满血正向治疗零兑付,记"奖励未生效"行 | | 死亡判定 | 生命归 0 即倒下;敌人受击即死;主角倒下战斗段立即短路;判定段先判主角、再判敌人(防御性次序) | @@ -301,7 +301,7 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值 2. 挖掘期本系统只缓冲事实流(开格、标旗入结算队列),不做任何兑付。 3. 轮末自动触发五段结算,触发与事件到达序无关。 4. 授予段按判定基线过滤(被挖开的旗不入判定),再按极性矩阵三批兑现:开安全格兑正向(插旗安全格直挖 = 四情况④零奖励作废行)、开雷格 / 标错旗兑负向(插旗雷格直挖照常——四情况②)、清图发清图奖励对象(完美乘数读 settlement.tres p_perfect)。 -5. 增益合成、主角先手加逐敌按摇定技能兑付(伤害技三乘法链 / 增益技施放行)、衰减恰好一次。 +5. 增益合成、主角先手加逐敌按摇定技能兑付(伤害技三乘法链 / 增益技施放行)加地图伤害账殿后(暂存单随战斗段末统一结账)、衰减恰好一次。 6. 判定段先主角后敌人:继续通知同图、清图换图续轮、胜负走战斗结束清理(秘宝背包不清)。 7. 明细流水全程落行,供呈现、回放与导出。 @@ -344,7 +344,7 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值 - 完美排雷:零误标且至少一面标中时第二份兑付恰一次并发高光行;出现误标或零标中则无高光;计数换图重置。 - 开轮摇号:每轮恰摇一次(首轮同样;轮内刷新换图不重摇);摇定后兑现前死亡 → 本拍技能作废(零敌方行动行)、不进冷却不进保底计数;已兑现技能进冷却、保底计数按执行结果更新。 - 战斗段次序:主角先手后逐敌"按摇定技能行兑现、结算伤害、执行上报"各恰一次;主角倒下立即短路,剩余敌人不行动。 -- 护甲等级掷点:吃掷点分量(主角普通分量、敌方技能基础伤害分量)逐次掷单骰(骰面读表)——掷点 ≥ 防守方护甲等级 + 攻击加成全额兑付;低掷按缺口查减伤档位表(边界与档值读表),档位不设零档;天然最大面暴击该分量 ×暴击倍率(读表)且恒全额;穿刺/魔法分量与棋盘来源伤害(爆裂、清障炸药、奖励即时)、必中附加分量及增益/妨害施放不掷不折。 +- 护甲等级掷点:吃掷点分量(主角基础分量、敌方技能基础伤害分量)逐次掷单骰(骰面读表)——掷点 ≥ 防守方护甲等级 + 攻击加成全额兑付;低掷按缺口查减伤档位表(边界与档值读表),档位不设零档;天然最大面暴击该分量 ×暴击倍率(读表)且恒全额;穿刺/魔法分量与棋盘来源伤害(爆裂、清障炸药、奖励即时)、必中附加分量及增益/妨害施放不掷不折。 - 新效应词汇(2026-09-19):伤害技 rider_mult 行的每次攻击,伤害行携带 rider 值且终值 = max(伤害下限, 主分量链结果) + rider(停手轮零池下逐拍精确复算);rider 键只随带 rider_mult 的技能行出现;妨害技 = 施放行 + 状态按 target_side 落主角(投掷阈值随行可读下调),冷却/保底/死亡作废三闸无一例外。 - 伤害下限与留档:任何来源的伤害落地 ≥ damage_floor(读表,2026-09-20 扩全来源),全流水零 v=0 伤害行、零"被减伤归零"行;掷点读数、护甲等级与档位随伤害行留档,天然最大面(nat20)行带暴击标记。 - 数值边界:伤害下限保底后全程无零值/负值伤害;溢杀丢弃不转移;治疗封顶不溢出。 @@ -374,13 +374,13 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值 | v0.8 | 2026-09-15 | **护甲等级批(用户拍板登记,只改文档与 registry、不动代码)**:战斗段新增护甲等级掷点规则——必中、非归零,掷点低于防守方护甲等级按减伤档位折扣,没有 miss、没有零收益白回合;只吃基础攻击分量(主角普通分量 / 敌方攻击拍与重击拍基线),穿刺/魔法充能分量不吃;棋盘来源(伏击附加、道具、清障炸药、奖励即时)一律无视;护甲等级(数值轴,数据键 `armor_class`)与护甲类型(克制属性轴)命名区分成文。随机口径修订:结算换算层首次引入回合内随机,可回放契约从"结算路径零随机"改为"封闭随机流可回放"(同种子同输入逐位复现);开战掷骰属初始化随机维持不变。调参表新增护甲等级掷点 / 减伤档位表 / `armor_class` 三行(全部 provisional 未定值);伤害行补掷点读数与折扣档位随行留档(行型 10 类封闭不变);验收标准新增护甲等级掷点与折扣零值归路两条、可回放条改封闭随机流口径;EV 中性引入成文(期望伤害对齐现行表值,"零红档"验收保留);待拍板新增"掷点形式与数值域""掷点的随机流归属"两条 | | v0.9 | 2026-09-16 | 新增「呈现只读面」节(06 清单「呈现只读面契约登记缺口」闭合):把切片已实现的呈现面函数与信号登记为对外契约——呈现状态快照 `presentation_snapshot`、战斗进行中谓词 `is_battle_live`、奖励身份查询 `query_reward_identity`、结算快照信号 `settlement_snapshot`、战斗结束信号 `battle_finished` 五件成文(载荷字段、消费方、销毁前捕获语义),并登记"呈现层唯一状态读取出口、禁链式穿透"的出口纪律与诊断观测面的非契约定位;依赖与输出表 HUD 框架下游行补指路 | | v0.10 | 2026-09-16 | **护甲等级掷点批·定稿落地(用户拍板,数字即规范——与敌人系统 / 难度曲线与Boss / registry / 切片代码同批)**:判定式定稿——掷 1d20,掷点 ≥ 防守方护甲等级 + 攻击加成(结构位 `attack_bonus`,切片恒 0)→ 全额,低掷按缺口三档折扣(1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25,无零档);nat20 暴击该基础攻击分量 ×2 且恒全额(双方同规);伤害下限全局 max(1, ·)(`damage_floor` = 1)——"被减伤归零 v=0"零值伤害行语义退役,重档折扣行文本记"被压制";百分比减伤增益 defense_up 整体退役换装 ac_up(护甲强化 +1 AC/层 ×2 层,`unit=ac` 新量纲、不入任何池,无双轨),off_guard 与池结构零改动;护甲等级与护甲类型分离成文(数值轴 / 属性轴,不做映射)。数值载体:主角 armor_class 10(暂定)、敌方首关 0(暂定,正式行值归怪物设定批,登记待办清单);掷点流归属定稿 = `armor_roll` 封闭流(逐次掷逐次取号,可逐位回放),调参表掷点/档位/下限/加成五行由未定改拍板值;EV 参考数成文(AC10 21.25% / AC11 25% / AC12 28.75%,按判定式复算),换装点锚定(基准 AC10 自带约 −21% 敌方输出期望)与加难杠杆扫描合并处理;验收标准改写掷点条目 + 新增伤害下限与留档条目;切片同批:settlement.tres 七参数入表、战斗段掷点判定/暴击/下限/行留档(roll/ac/tier/crit 字段)、armor_roll 流接入 rng_service(黄金向量 m=42 scope0 = 4806095330042243845)、零值伤害行分支删除、冒烟护栏与掷点行为断言(nat20 种子 / AC12 档位分布 / 池触底保底 / 棋盘必中)、英雄卡与敌人卡 AC 展示、ac_up 状态槽图标、nat20 金框高亮 | -| v0.11 | 2026-09-16 | **敌人技能池批(用户拍板,设计权威 docs/proposal-skill-pool.md v1.0——与敌人系统 / 增益状态 / 难度曲线与Boss / 战斗表现 / HUD框架 / registry / 切片代码同批)**:① 随机口径两处 → 三处——技能摇号 `behavior_roll` 封闭流(回合内、开轮时点、步骤流之外,每敌按生成序派生独立子流、单敌轨迹可独立复放)入册,可回放契约扩写为"三处皆封闭流,同种子同输入逐位复现";② 战斗段成文开轮摇号与敌方技能兑现(每轮恰摇一次、轮内刷新不重摇、摇定后兑现前死亡作废)、增益技 = 施放行 + 状态授予(0 伤害不掷不折不兑伏击);③ 三乘法链成文(技能倍率乘在最前:基线 × 倍率 → AC 掷点 → 甲效 → 既有链不动);④ AC 判定对象措辞四处改述("敌方攻击拍与重击拍基线" → "敌方技能基础伤害分量",含正文/公式行/边界表/验收标准);⑤ 敌方防御池入乘区(`enemy_defense` 新池——硬化载体):主角攻击 Σ三分量后 ×(100+enemy_defense)/100,四池对称成文(hero_offense 现行走普通分量直乘的切片现状登记待办);⑥ 行型改型:蓄力行 `charge` → 施放行 `cast`(10 类封闭不变);⑦ 呈现快照敌人数组字段定稿(当前拍/行动环进度 → 摇定动作三字段 + 敌方增益 effects);验收标准新增开轮摇号条目、战斗段次序与可回放条目改写;切片同批:`_roll_round_actions` 开轮编排、技能摇号器独立服务类、单位容器实例池/冷却/执行上报、settlement.tres effect_pool_map 增 empower/fortify、施放行格式化、摇定动画位 | +| v0.11 | 2026-09-16 | **敌人技能池批(用户拍板,设计权威 proposal-skill-pool.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 技能池节〕 v1.0——与敌人系统 / 增益状态 / 难度曲线与Boss / 战斗表现 / HUD框架 / registry / 切片代码同批)**:① 随机口径两处 → 三处——技能摇号 `behavior_roll` 封闭流(回合内、开轮时点、步骤流之外,每敌按生成序派生独立子流、单敌轨迹可独立复放)入册,可回放契约扩写为"三处皆封闭流,同种子同输入逐位复现";② 战斗段成文开轮摇号与敌方技能兑现(每轮恰摇一次、轮内刷新不重摇、摇定后兑现前死亡作废)、增益技 = 施放行 + 状态授予(0 伤害不掷不折不兑伏击);③ 三乘法链成文(技能倍率乘在最前:基线 × 倍率 → AC 掷点 → 甲效 → 既有链不动);④ AC 判定对象措辞四处改述("敌方攻击拍与重击拍基线" → "敌方技能基础伤害分量",含正文/公式行/边界表/验收标准);⑤ 敌方防御池入乘区(`enemy_defense` 新池——硬化载体):主角攻击 Σ三分量后 ×(100+enemy_defense)/100,四池对称成文(hero_offense 现行走普通分量直乘的切片现状登记待办);⑥ 行型改型:蓄力行 `charge` → 施放行 `cast`(10 类封闭不变);⑦ 呈现快照敌人数组字段定稿(当前拍/行动环进度 → 摇定动作三字段 + 敌方增益 effects);验收标准新增开轮摇号条目、战斗段次序与可回放条目改写;切片同批:`_roll_round_actions` 开轮编排、技能摇号器独立服务类、单位容器实例池/冷却/执行上报、settlement.tres effect_pool_map 增 empower/fortify、施放行格式化、摇定动画位 | | v0.12 | 2026-09-17 | **数值纪律收口批:正文与调参表 knob 现行值改指路存根(事实源 `data/tables/*.tres` 与 registry),20 余处失同步值修正**——收益对照表参考敞口 12 / 完美乘数 200 改指路 settlement.tres(equivalence_ref_damage / p_perfect);伏击加层"+2 层"改"+stacks 层(读 reward.tres ambush_double 行)";充能供给表与 charge_grants 行"每格 1 / 上限 8 / 每雷 2"改 per_unit/cap 读表;三乘法链与甲效矩阵 1.5/1.0/0.5、150/100/50 改三档读表;掷点判定 1d20 / nat20 ×2 / 缺口档 ×0.75/×0.5/×0.25 / 边界 [3,7] / 档位 [75,50,25] / damage_floor=1 / AC 10·0 / ac_up +1×2 层全部改指路 settlement.tres·status_effects.tres·hero/enemy 表;EV 参考数(AC10 21.25% 等)与"旧 defense_up 满层 −60%"删除(派生读数 / 退役值,版本历史可查);调参表与验收标准同步 | | v0.13 | 2026-09-18 | **营地扩展前置批·主角双轴可配置(用户指令,与敌人系统 / registry / 切片代码同批)**:① hero 表新增 armor_type({light, ward, heavy},缺省 light)——主角护甲类型轴可配置可修改,消费面 = 甲效矩阵(现行敌方攻击恒 normal 档 = 数值中性的结构位,轴生效待敌方技能攻击类型批);② hero 表新增 attack_type({normal, pierce, magic},缺省 normal)——**基础攻击量与攻击类型抽象分离**:基础分量类型不再硬编码普通、改读主角表值,营地永久强化"换成魔法/穿刺攻击"= 改字段,atk 量轴与掷点口径零改动;③ 同桶充能照加语义成文(基础分量与充能同型时,充能分量保持不掷不折);正文三分量表述、甲效矩阵"普通"行注记、分量固有属性措辞三处同步 | | v0.14 | 2026-09-18 | **战斗数值结构修复批(2026-09-18 盘问会话用户拍板,与增益状态 / registry / 切片代码同批)**:① 主角侧乘区接线——hero_offense 池入主角攻击乘区:Σ三分量后 ×(100+hero_offense)/100 ×(100+enemy_defense)/100 两池串乘,增伤/乏力覆盖面 = 普通+穿刺+魔法 合计;attack_up 普通分量直乘旁路退役(正文甲效矩阵"普通"行注记、池乘区公式行、v0.11 切片现状注记三处同步销项);② off_guard(失衡)换装 AC 轴——unit percent→ac(幅度 provisional 归状态层重设计批),effect_pool_map 删 off_guard 键,hero_defense 池清空、结构保留,消费走既有掷点判定读法零改动;③ fortify(硬化)符号纠正——旧 +100 入敌方防御池实际 ×2 翻倍与"硬化=减伤"语义相反,改负值(读表)后主角攻击折半;④ weaken(削弱)叠层改续时——同名重复授予只刷新剩余结算数、层数恒 1,×2 叠满池触底 ×0 的免伤轮结构性根治(消饱和);duration 终值、off_guard 幅度、当量锚复算全部归状态层重设计批。切片同批:battle_orchestrator 池接线与直乘删除、status_effects.tres 三行、settlement.tres 删键、冒烟护栏(削弱续时 / 乘区接线 ×1.5·×0.75 / 硬化折半方向三组新断言,旧"削弱×2 池触底"采样通道随批退役) | | v0.15 | 2026-09-18 | **甲效矩阵用户调参批(用户逐格给值,事实源 settlement.tres 五格改值、四格不动,零代码)**:普通行 ward 100→120 / heavy 100→50;穿刺行 light 100→80 / ward 150→120 / heavy 50→80;魔法行与普通-轻甲不动——「克制/中性/被克」三档结构与「普通行恒中性」退役,改五档(克制 / 弱克制 / 中性 / 弱被克 / 被克,档值读表)。矩阵消费面成文 = 主角侧(奖励直伤道具 / 主角攻击三分量 / 充能分量),敌方攻击不查矩阵、主角 armor_type 轴仍休眠(v0.13「敌方攻击恒 normal 档故中性」的归因改述——轴休眠是结构性的非数值性的)。语义列两行同步(穿刺「破不开重甲」重压语义弱化为与轻甲同档折损)。切片同批:settlement.tres 批注、冒烟矩阵三行断言改五档读数 + 乘区接线穿刺分量 3→2(3 点 ×80% 取整)随批改形,冒烟 300 项 0 失败 + 四探针 PASS;registry(enemy/hero armor_type 两行注记)与 敌人系统 / 难度曲线与Boss 三档措辞同步 | | v0.16 | 2026-09-18 | **甲效矩阵加法核换装批(用户拍板数值语言——小整数加法核,与 01-核心循环 数值语言条款 / registry / 切片代码同批)**:量纲 percent→整数点数,三档(克制 +1/中性 0/被克 −1)九格改值〔normal 0/+1/−1、pierce −1/+1/0、magic 0/−1/+1〕,pierce×heavy = 0 中性为用户特别拍板;五档词表与百分比乘区退役(v0.15 的五档表随批短命退役,理由=个位数刻度假粒度);分量算术改述=折算取整后加修正点数;缺项回退 100→0;奖励直伤行 payload 键 effectiveness 保留、值语义改点数;池乘区/AC 折扣档/暴击保留百分比并成文粒度口径(数值语言条款:百分比仅此三处既有粗档+开发侧工具);切片同批:settlement.tres/settlement_table.gd/damage_math/battle_orchestrator/smoke/registry/01-核心循环同步 | -| v0.17 | 2026-09-19 | **技能池新效应词汇批(用户拍板——设计权威 docs/proposal-status-vocabulary.md v1.0,与 敌人系统 / 增益状态 / registry / 切片代码同批)**:① 三乘法链尾部 +必中附加分量 rider(伤害技 rider_mult 字段:max(伤害下限, floor(攻击基线 × rider_mult))——后置加法、必中全额、不进乘区不出暴击、价位锚 0.75 = AC 折扣档最轻档;"固定值伤害"改形——固定值不随基线缩放与共享技能表前提冲突);② 增益/妨害技 = 同族施放行(0 伤害不掷不折不兑伏击),妨害状态按状态行 target_side 落主角——零新授予通道;③ 必中家族清单 +rider(掷点边界切口);④ 公式行/吃掷点行/验收标准三处同步 + 新效应词汇验收条目(rider 逐拍精确复算 + rider 键封闭 + 妨害落主角端到端);⑤ 实装面 = 仅 L2 Boss 卓尔(毒爆 +rider、新行蛛网束缚妨害→失衡),其余敌种不动;多段命中经用户拍板退役不入词汇表 | +| v0.17 | 2026-09-19 | **技能池新效应词汇批(用户拍板——设计权威 proposal-status-vocabulary.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 词汇表/增益状态.md〕 v1.0,与 敌人系统 / 增益状态 / registry / 切片代码同批)**:① 三乘法链尾部 +必中附加分量 rider(伤害技 rider_mult 字段:max(伤害下限, floor(攻击基线 × rider_mult))——后置加法、必中全额、不进乘区不出暴击、价位锚 0.75 = AC 折扣档最轻档;"固定值伤害"改形——固定值不随基线缩放与共享技能表前提冲突);② 增益/妨害技 = 同族施放行(0 伤害不掷不折不兑伏击),妨害状态按状态行 target_side 落主角——零新授予通道;③ 必中家族清单 +rider(掷点边界切口);④ 公式行/吃掷点行/验收标准三处同步 + 新效应词汇验收条目(rider 逐拍精确复算 + rider 键封闭 + 妨害落主角端到端);⑤ 实装面 = 仅 L2 Boss 卓尔(毒爆 +rider、新行蛛网束缚妨害→失衡),其余敌种不动;多段命中经用户拍板退役不入词汇表 | | v0.18 | 2026-09-19 | **插旗不可逆批(用户拍板全套落地,2026-09-18 提案转正;与排雷玩法 v0.5 / 行动经济 v0.4 / 地图战斗资源 v0.12 / registry / 切片代码同批)**:① 授予段「抵消过滤」改述「判定基线」——撤旗动作退役,旗判定以轮末在场旗为基线,被挖开的旗不入基线;② 新增插旗不可逆四情况兑付表:①标中照常 / ②插旗雷格直挖只吃挖雷惩罚标中不发 / ③标空照常 / ④插旗安全格直挖零惩罚零奖励(正向作废 + reward_ineffective 归因行);③ 挖格批兑付表补四情况分支;④ 标中资格措辞改「旗无移除手段」(拔旗重插通道消失);⑤ P0 闭环第 2/4 步与验收标准同步(「抵消过滤」条改「挖旗格兑付改判」)。切片同批:minesweeper_rules 挖旗格放行 + flag_pull 出目录 + dug_under_flag 事件标记、battle_orchestrator 情况④零奖励抑制、action_economy 表 flag_pull_ap_cost 删除、冒烟 317→327 项 0 失败(四情况探针 `_smoke_flag_irreversible_quadrants`) | | v0.19 | 2026-09-20 | **伤害下限全来源批(用户拍板「任何伤害取值域 [1,+∞),最小值一定是 1」;与 06 清单「零伤害/无威胁组合的跨字段护栏」收口 / registry / 切片代码同批,台账 100)**:① 下限语义从"任何攻击落地伤害"扩为"任何来源的伤害落地"——攻击双方(原有)+棋盘克制直伤(counter_component 增 landed_floor 参数,空桶不吃修正恒 0 边界不变)+爆裂正向+负向反噬(穿刺陷阱/魔法反噬/heal 反噬)+地形变异 flat_damage,全部收进 damage_floor 闸内;② 取值域成文 [1, +∞) **不设上限**——「零伤害/无威胁组合」条目的跨表上界护栏半边随拍板裁决为不设(上不封顶),敌种攻击基线 0 的退化态经下限自然消解(0 基线攻击落地仍 ≥1,"无威胁敌"结构性不存在);③ 现行表值下零行为变更(棋盘直伤贴底值 2−1=1 本就在闸内),本批=结构保证+调参防误配;④ 判读口径/约束表/调参表/验收四处措辞同步;冒烟 352→355 项 0 失败(+贴底/穿透/空桶三断言)。版本号注记:v0.19 在伏击上限删除批(未合并分支)中已被占用,本条与之合并时以先落籍者为准顺延避让 | | v0.20 | 2026-09-20 | **伏击单轮上限删除批(用户拍板——与 敌人系统 / 地图战斗资源 / 难度曲线与Boss / registry / 切片代码同批)**:① 行型协议 10 类→9 类——溢出未生效行下线(唯一发射源 = 伏击上限截流,上限删除后零发射面);② 授予段伏击加层改直授无上限(反灾难护栏退役——一轮踩雷数由行动点预算天然封顶);③ 核验清单伏击守恒条改伏击池共享;④ 切片同批:enemy.tres 12 行字段删除、敌表校验/编排器截流/单位容器 F3 计划函数/日志视图 overflow 格式化删除、冒烟 blast 负向断言改直授累加(330 项 0 失败) | @@ -389,3 +389,4 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值 | v0.23 | 2026-09-20 | **GP+商店房批(用户拍板——与 关卡节点地图 v1.1 / 冒险结构 v0.5 / 地图战斗资源 v0.15 / 难度曲线与Boss v0.18 / registry / 切片代码同批;版本号注记:v0.22 已被充能退役批占用,表尾顺延)**:① 呈现只读面表 battle_finished 载荷 +gp_dropped(掉落掷定 + 关级加值 + 战斗内缓冲的合计——字段增删走本文修订条款兑现);② 授予段兑付形态表新增 GP 缓冲行:gp 正/负向(金币袋 / 破财——九型批)兑付 = 战斗内 GP 缓冲 ±power,战斗完成统一上抛入钱包账本(账本归冒险结构),战斗域零钱包状态;③ 随机口径六处→七处:战斗结束掉落掷定消费 `gp_drop` 封闭流(出口随机、步骤流之外);九型字典措辞与验收标准可回放条目同步 | | v0.24 | 2026-09-24 | **伤害管线单据化批(用户三裁决——与切片代码同批,06 台账 148)**:新增「伤害单据与伤害控制器」一节——伤害对象抽象补全:五族伤害来源收口伤害控制器单点管线、兑付配方封闭五种;三裁决落文——①结账时机双模式(现行 = 段界结账:授予段尾 / 清图批同步点 / 战斗段尾;开单即结保留为回切结构位,切换走代码不走调参)②目标结账时刻照规则解析③死亡事件由单位自行宣布(怪死其未结单据作废、主角死兑付终止);授予段兑付与战斗段两节补结账时点句;验收标准 +伤害单据管线条目。切片同批:`core/damage_controller.gd` 新建(单据构造/开单/结账/死亡事件消费/管线算术收口)、battle_orchestrator 六调用点改开单、掷点/折扣/克制/池乘/攻击行文本助手迁出(`_defender_ac`/`_clamped_pool` 留薄委托)、unit_container 增 `unit_died` 信号;冒烟 557 项 0 失败双模式全绿 + A/B 对拍四配置全确定且真实分岔(行数恒等、纯次序位移);历史基线读数自本批起以段界结账口径为准(重测归曲线调参批) | | v0.25 | 2026-10-05 | **挖掘风险不等式判式换代(迭代14②号卡 T3/T4——与行动经济/难度曲线与Boss/registry 同批)**:①收益对照表补 gp/ap 当量口径注记(gp=flat power 代理 3/2 假设 A5 口径空白登记、ap=flat 表值 1 机会成本口径仅登记假设 A6,精确口径归商店批复核);②验收标准 +条款3 对账条(聚合健康式+β 带逐 u 检算 / 旗溢价兑付三形态覆盖·标空与挖格不乘·同池零触碰);③T4 机制载体 = settlement.tres 新键 flag_hit_premium=125(percent 域 provisional)——标中旗正向兑付 ×(1+δ),整数四舍五入取整与伤害管线 floor 分域(registry 随批登记);DamageController 零改、雷安同池冻结不动、直伤雷 6/8/8 与 p_perfect 不动 | +| v0.26 | 2026-10-06 | **地图伤害账殿后批(案C,用户裁——歧义质询触发)**:①五段总览①③行改写——授予段只兑非伤害形态,伤害形态兑付(爆裂/直伤/反噬/地形变异/战旗军规/熔火反噬等)暂存到战斗段末、按「主角先手→逐敌还手→地图账」统一落地;②战斗段节补地图账殿后与死亡守卫(主角死于怪物→暂存单整体不兑,逐单留「未生效(主角已倒下)」行);③结账时机双模式改述:现行=段界结账+地图账殿后,清图批同步读数改「将兑付」回报口径(完美第二份依据开单时点事实),历史基线读数以 2026-10-06 前授予段结账口径封存;④兑付形态表 blast 正向/heal 负向兑付时点改战斗段末;⑤P0 闭环第 5 步补地图账;⑥顺手清充能退役遗留:三分量句改单分量、三处「主角普通分量」改「基础分量」。切片同批:battle_orchestrator 暂存闸 `_defer_board_damage`/`_deferred_board` + `_submit` 案C 分流 + `_outcome_of` + 战斗段末补提交块 + 授予段四处直提改走闸;冒烟 783 项 0 失败(新增案C 段内序断言+行相位分类器换代)+ run_all 17 套全绿 + probe_gate --click 全绿;旧行为 MC 锚全数作废,重锚归数值批 | diff --git a/design/gdd/systems/战斗循环/敌人系统.md b/design/gdd/systems/战斗循环/敌人系统.md index 48632e52..898f246c 100644 --- a/design/gdd/systems/战斗循环/敌人系统.md +++ b/design/gdd/systems/战斗循环/敌人系统.md @@ -110,7 +110,7 @@ |---|---|---| | skill_id | 全部 | 主键,池条目引用键 | | display_name | 全部 | 玩家可见名(明细行与动画共用) | -| type | 全部 | 封闭枚举 {普通攻击, 伤害, 增益, 妨害}——**新效应词汇批 2026-09-19 由三扩四**(设计权威 docs/proposal-status-vocabulary.md v1.0;多段命中经用户拍板退役不入枚举);新类型走本文修订 | +| type | 全部 | 封闭枚举 {普通攻击, 伤害, 增益, 妨害}——**新效应词汇批 2026-09-19 由三扩四**(设计权威 proposal-status-vocabulary.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 词汇表/增益状态.md〕 v1.0;多段命中经用户拍板退役不入枚举);新类型走本文修订 | | damage_mult | 仅伤害 | 伤害倍率;域 [1.1, 16] provisional。普通攻击恒 1、不可配 | | rider_mult | 仅伤害、可省 | **必中附加分量(2026-09-19 新增)**:域 (0, 1],缺省无字段 = 无附加;分量值 = max(伤害下限, floor(攻击基线 × rider_mult))——随施放者攻击基线缩放,后置加法、必中全额、不进乘区、不出暴击(后置加法同族);价位锚 0.75 = AC 折扣档最轻档"不掉档的稳定伤害" | | cooldown_beats | 全部 | 施放后连续 N 拍不可摇中;普通攻击恒 0、不可配 | @@ -141,7 +141,7 @@ 首轮同样摇号;每轮恰摇一次,轮内刷新换图不重摇。摇号随机走封闭随机流、每敌按生成序派生独立子流——单敌出招轨迹可独立复放、互不穿插;本系统自身语义零随机不变,单位容器收到的是确定结果(armor_roll 先例同构)。 -死亡即不动手(保留):摇定后、兑现前死亡(主角先手 / 授予段直伤),本拍技能作废——不动手、不进冷却、不进保底计数;其余存活敌人照常。拍结构保留:**每结算每存活敌恰行动一次、按行序、先作用后推进**——变的只是"轮到什么",由固定剧本改为摇号。同种敌人不再锁步:各自实例池独立摇号、独立冷却,怪物 1 的重击在冷却里不影响怪物 2,轨迹分歧是涌现,不设开战分配机制。 +死亡即不动手(保留):摇定后、兑现前死亡(主角先手 / 更早兑现的敌方伤害技),本拍技能作废——不动手、不进冷却、不进保底计数;其余存活敌人照常。拍结构保留:**每结算每存活敌恰行动一次、按行序、先作用后推进**——变的只是"轮到什么",由固定剧本改为摇号。同种敌人不再锁步:各自实例池独立摇号、独立冷却,怪物 1 的重击在冷却里不影响怪物 2,轨迹分歧是涌现,不设开战分配机制。 蓄力语义(2026-09-16 纠偏定稿):蓄力不是重击前奏——它是一行增益技:0 伤害动作,施加 empower 状态(幅度读 `status_effects.tres`),持续 1 个有效结算(provisional)。旧环"蓄力→猛击"的固定配对是哥布林弹珠的拷贝(给玩家输出窗口用的),随行为环一并退役;蓄力与重击之间**没有任何组合语义**——增益与技能倍率恰好同拍只是增益倍率 × 技能倍率的两层乘法共存(增益幅度 × 增益幅度 = 两层乘法读表可得),蓄力后摇中普通攻击则是增益倍率的加成平砍,两者同为平常事,不是设计的连招。技能冷却(几拍内不能再摇中,技能行属性)与增益持续(状态挂几回合,状态行属性)是两个计时器,互不相干。施放行 = 0 伤害动作,不掷不折(原"零兑付、不读伏击"的读怪奖励语义随伏击退役作废——2026-09-20 八型批)。 @@ -167,7 +167,7 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**三代错 即:普攻完全不可预测;特殊技有一次类型级预告,不可撤销,但给"继续挖还是停手"的决策依据。 -**半血解锁弹出规格(2026-10-05 技能池执行批,D8 用户裁)**:Boss 血量降至 `unlock_hp_pct` 阈值(池条目字段,域 [1,99],缺省无字段=无门槛)时,下一个开轮摇号前出一次归因明细行「XX 解锁了 YY」(高光键 `skill_unlock`,行型复用 highlight 零新增);呈现层对该行弹**一次性技能名 toast**(轻量提示,非演出——零动画资产、不改演出规格),只报技能名不报数值,池与权重不公开纪律不变。解锁=单向闩(治疗抬血不解锁);锁定条目双面过滤(不进可摇集合与保底特殊技集合——保底不得逼出未解锁技);解锁后冷却初值 0 即可摇。投放纪律=字段通用、仅 Boss 行投放(enemy 表无 boss 编码位,硬闸须跨表查节点类型——不做硬闸,本句即文档纪律登记)。旧环"下一拍全程亮出(意图徽标 + 环点)"的预告粒度随行为环退役。技能动画的攻/防系映射归[战斗表现](战斗表现.md),敌人侧摇定动画位归[HUD 框架](../表现与支撑/HUD框架.md)(呈现载体 = 站桩立绘,2026-09-20 全站桩拍板),本系统只锁上面这张三档规格。 +**半血解锁弹出规格(2026-10-05 技能池执行批,D8 用户裁;内容数据驱动扩展批 T1 补登记——解锁判定点=通用事件「血量阈值 `enemy_hp_threshold`」的发射点,对所有带 `unlock_hp_pct` 字段的敌人逐个发射、非 Boss 专属——Boss 语义留给订阅条件判定)**:Boss 血量降至 `unlock_hp_pct` 阈值(池条目字段,域 [1,99],缺省无字段=无门槛)时,下一个开轮摇号前出一次归因明细行「XX 解锁了 YY」(高光键 `skill_unlock`,行型复用 highlight 零新增);呈现层对该行弹**一次性技能名 toast**(轻量提示,非演出——零动画资产、不改演出规格),只报技能名不报数值,池与权重不公开纪律不变。解锁=单向闩(治疗抬血不解锁);锁定条目双面过滤(不进可摇集合与保底特殊技集合——保底不得逼出未解锁技);解锁后冷却初值 0 即可摇。投放纪律=字段通用、仅 Boss 行投放(enemy 表无 boss 编码位,硬闸须跨表查节点类型——不做硬闸,本句即文档纪律登记)。旧环"下一拍全程亮出(意图徽标 + 环点)"的预告粒度随行为环退役。技能动画的攻/防系映射归[战斗表现](战斗表现.md),敌人侧摇定动画位归[HUD 框架](../表现与支撑/HUD框架.md)(呈现载体 = 站桩立绘,2026-09-20 全站桩拍板),本系统只锁上面这张三档规格。 | 规格 | 含义 | |---|---| @@ -202,7 +202,7 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**三代错 1. 战斗开始:战斗结算按遭遇坐标调本系统生成单位——读表快照实例化本战数量的敌人(池模板快照成实例池、冷却全零),主角以血量入参生成。 2. 开轮摇号:战斗结算换算层排除冷却中技能、按行序逐敌摇定本拍动作写入实例池,随后冷却递减(首轮同样摇;轮内刷新换图不重摇)。 3. 挖掘期踩雷:事实经战斗结算转化为警觉施加——每颗雷恰一次、逐层记账(无上限)。 -4. 每轮结算:主角先手攻击顺位最前的存活敌人;每个存活敌人按摇定技能行兑付(伤害技 = 基线 × 倍率,增益技 = 施放行 + 状态授予)。 +4. 每轮结算战斗段:主角先基础攻击顺位最前的存活敌人,随后每个存活敌人按摇定技能行兑付(伤害技 = 基线 × 倍率,增益技 = 施放行 + 状态授予);地图奖励与惩罚的伤害兑付殿后到战斗段末统一落地(案C 2026-10-06 用户裁,见战斗结算)。 5. 受击上报、血量递减;归 0 即移出顺位(摇定后兑现前死亡 → 本拍作废不进账)。 6. 执行上报:已兑现的技能进冷却、保底计数更新(普攻 +1 / 特殊技归零)。 7. 敌全灭或主角归零,回流战斗结算作胜负判定素材。 @@ -215,7 +215,7 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**三代错 | P1 | 情境条件触发器(低血触发防御、玩家停手时偷袭技等) | 载体 = 技能行触发条件字段加列;登记[待办清单](../../06-待办清单.md) | | P1 | 个体化变体池(A 型好斗 / B 型守势、精英专属池) | 共识点 A 的 A2 案;登记[待办清单](../../06-待办清单.md) | | P1 | 全局特殊间隔旋钮 | 特殊技背靠背实测仍嫌时的备用闸;登记[待办清单](../../06-待办清单.md) | -| P1 | 新效应词汇批(妨害技 + 必中附加分量已实装 2026-09-19——设计权威 docs/proposal-status-vocabulary.md v1.0,实装面 = L2 Boss 卓尔;多段命中经用户拍板退役) | 妨害类型 + rider_mult 字段已落地;登记[待办清单](../../06-待办清单.md)(已解决移除,去向见台账) | +| P1 | 新效应词汇批(妨害技 + 必中附加分量已实装 2026-09-19——设计权威 proposal-status-vocabulary.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 词汇表/增益状态.md〕 v1.0,实装面 = L2 Boss 卓尔;多段命中经用户拍板退役) | 妨害类型 + rider_mult 字段已落地;登记[待办清单](../../06-待办清单.md)(已解决移除,去向见台账) | | P1 | 敌种名录扩容与投放节奏(含第二关池与投放) | 投放节奏是关卡命脉,归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) | | P1 | 单体寻址("只打某一个敌人"的目标指定) | 首个单体效果上线时随批裁决归集语义与呈现 | | P2 | 敌方技能图鉴 / 学习呈现(玩家侧技能知识系统) | 池不公开口径下的进阶信息面;登记[待办清单](../../06-待办清单.md) | @@ -289,12 +289,13 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**三代错 | v0.2 | 2026-09-15 | 教学位掷骰口径自洽(随[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)同批修订):unit_count 字段说明、地精敌种定位与现行行值句中"教学位固定 1 只/固定不掷"改为 ×[1,2] 开战掷定 | | v0.3 | 2026-09-15 | **护甲等级批(用户拍板登记,与战斗结算 / 难度曲线与Boss / registry 同批,不动代码)**:敌人表与主角表 schema 各增 armor_class(护甲等级)字段(取值域随掷点形式定稿,provisional);三敌种表补护甲等级列(行值未定);护甲等级(数值轴)与护甲类型(克制属性轴)命名区分成文;结构冻结清单"零随机"措辞按新口径修订——本系统自身语义零随机不变,掷点随机在战斗结算换算层按封闭随机流消费、可回放契约 = 同种子同输入逐位复现;待拍板新增"护甲等级行值未定"条目 | | v0.4 | 2026-09-16 | **护甲等级掷点批·定稿落地(与战斗结算 / 难度曲线与Boss / registry / 切片代码同批)**:armor_class 域定稿 [0, 999](缺省 0 向后兼容);主角行值 10(暂定)、三敌种首关行值 0(暂定)——"行值未定"改"暂定值 + 待办",敌方正式行值归怪物设定批(第一关怪物 AC 相对较低,值待怪物设定投值,登记待办清单);敌种表护甲等级列同步落值;调参表两行改域与现行值 | -| v0.5 | 2026-09-16 | **敌人技能池批(用户拍板,设计权威 docs/proposal-skill-pool.md v1.0;与战斗结算 / 增益状态 / 难度曲线与Boss / 战斗表现 / HUD框架 / registry / 切片代码同批)**:行为环章节整体退役——固定出招剧本、拍枚举(攻击/蓄力/重击)、环指针、同种锁步、heavy_multiplier 字段全部废除;技能池语义成文(三层对象〔技能行/池模板/实例池〕、技能行词汇表、池构成纪律、两道闸〔全技能各自冷却 + 保底〕、开轮摇号时序、死亡作废、独立子流);蓄力语义改写(纯攻击增益技、与重击无组合语义——纠偏二;施放行继承零兑付伏击);Boss 专属身份改专属池承载;意图呈现改三档可见性(普攻零表示 / 特殊技类型级预告 / 暴击掷点层);敌人表 schema pattern → pool;调参表增技能表与池 EV 锚(含闸长期速率实测登记);验收标准按摇号口径重写 | +| v0.5 | 2026-09-16 | **敌人技能池批(用户拍板,设计权威 proposal-skill-pool.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 技能池节〕 v1.0;与战斗结算 / 增益状态 / 难度曲线与Boss / 战斗表现 / HUD框架 / registry / 切片代码同批)**:行为环章节整体退役——固定出招剧本、拍枚举(攻击/蓄力/重击)、环指针、同种锁步、heavy_multiplier 字段全部废除;技能池语义成文(三层对象〔技能行/池模板/实例池〕、技能行词汇表、池构成纪律、两道闸〔全技能各自冷却 + 保底〕、开轮摇号时序、死亡作废、独立子流);蓄力语义改写(纯攻击增益技、与重击无组合语义——纠偏二;施放行继承零兑付伏击);Boss 专属身份改专属池承载;意图呈现改三档可见性(普攻零表示 / 特殊技类型级预告 / 暴击掷点层);敌人表 schema pattern → pool;调参表增技能表与池 EV 锚(含闸长期速率实测登记);验收标准按摇号口径重写 | | v0.6 | 2026-09-17 | **文档降级为模板批(用户拍板,与难度曲线与Boss / 战斗结算同批)**:数值纪律定稿——本文只锁字段、取值域与规则,全部具体行值(敌种表池权重 / 护甲等级行值 / 两道闸表值 / 伏击每层附加与上限 / 技能行倍率与冷却 / 主角三字段 / 三敌种行值与池 EV 实测读数)自本文移除、一律读配置表(切片 `data/tables/` 与 registry);调参表三表删除"现行值"列改纯 schema 模板;伏击公式与验算改符号化(a = 每层伏击表值);验收标准同步去数值。背景:切片 `.tres` 数值压缩批后文档行值已滞后,双写失同步被实证 | | v0.7 | 2026-09-17 | **数值纪律收口批:正文与调参表 knob 现行值改指路存根(事实源 `data/tables/*.tres` 与 registry),2 处失同步值修正**——共享技能表示例"熊地精 9×2=18"改符号化"基线 A × 技能倍率 M(行值读 enemy.tres / skills.tres)";蓄力语义"施加'攻击×2'状态""×2 遇 ×2 = ×4"改"施加 empower 状态(幅度读 status_effects.tres)",两层乘法组合语义保留符号表述 | | v0.8 | 2026-09-18 | **营地扩展前置批 + 护甲类型重配(用户指令,与战斗结算 / registry / 切片代码同批)**:① hero 表新增 armor_type / attack_type 两根可配置轴(缺省 light / normal;主角字段表同步两行)——护甲类型轴入矩阵消费面(敌方技能攻击类型轴未开前数值中性),基础攻击类型轴 = 量与类型抽象分离的营地换型落点;② 敌种护甲类型行值重配(数据面):关卡 1 全部轻甲(食人魔酋长 heavy→light、熊地精两行 ward→light),关卡 2 除 Boss 蛛化卓尔精灵 heavy→ward(魔法甲)外全部轻甲(熊地精四行 ward→light,巨蜘蛛既有 light 不动);现行克制三角读数效果 = L1 克制轴中性化、L2 仅 Boss 战非中性(魔法矢 ×150 / 穿刺弹 ×50 对 Boss) | | v0.9 | 2026-09-18 | **甲效矩阵用户调参批同步(零代码,事实源 settlement.tres 五格改值——详见[战斗结算](战斗结算.md) v0.15)**:主角字段表 armor_type 行注记归因改述——轴休眠的理由由「敌方攻击恒 normal 档故中性」(依赖普通行恒 100 的旧数值结构)改为「矩阵消费面 = 主角侧、敌方攻击不查克制矩阵」(结构性,与数值无关),轴生效仍待敌方技能攻击类型批。历史注记:v0.8 行「魔法矢 ×150 / 穿刺弹 ×50 对 Boss」与当时矩阵(穿刺×魔法甲=150 / 魔法×魔法甲=50)正反互倒,系当批笔误,按历史不改写留档,正确读数见战斗结算 | -| v0.10 | 2026-09-19 | **技能池新效应词汇批(用户拍板——设计权威 docs/proposal-status-vocabulary.md v1.0,与 战斗结算 / 增益状态 / registry / 切片代码同批)**:① 技能类型枚举三→四(+妨害 debuff——对主角施减益的 0 伤害施放动作,字段复用 buff_ref/buff_duration,落点由状态行 target_side 路由〔增益 → 敌方侧 / 妨害 → 主角侧,施放类 side 守卫加载拒绝〕,执行与增益技同族施放行语义——不掷不折不兑付伏击);② 伤害技新字段 rider_mult(必中附加分量——"固定值伤害"改形:固定值不随基线缩放与共享技能表前提冲突,必中小倍率承载"躲不掉的稳定伤害");③ 池构成纪律 +每池妨害 ≤1;④ 意图三档特殊技预告扩三族(攻击系 ⚔ / 增益系 ✦ / 妨害系 ☠);⑤ 三词收敛两词——多段命中经用户拍板退役(无防御磨损体系,无对照机制),P1 字段位「命中段数」保留;⑥ 实装面 = 仅 L2 Boss 蛛化卓尔精灵(venom_burst + rider_mult 0.75;新行 web_snare 蛛网束缚 debuff→off_guard,首发载荷 = 失衡〔用户拍板〕,乏力 sapped 留作第二词汇),其余敌种不动 | +| v0.10 | 2026-09-19 | **技能池新效应词汇批(用户拍板——设计权威 proposal-status-vocabulary.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 词汇表/增益状态.md〕 v1.0,与 战斗结算 / 增益状态 / registry / 切片代码同批)**:① 技能类型枚举三→四(+妨害 debuff——对主角施减益的 0 伤害施放动作,字段复用 buff_ref/buff_duration,落点由状态行 target_side 路由〔增益 → 敌方侧 / 妨害 → 主角侧,施放类 side 守卫加载拒绝〕,执行与增益技同族施放行语义——不掷不折不兑付伏击);② 伤害技新字段 rider_mult(必中附加分量——"固定值伤害"改形:固定值不随基线缩放与共享技能表前提冲突,必中小倍率承载"躲不掉的稳定伤害");③ 池构成纪律 +每池妨害 ≤1;④ 意图三档特殊技预告扩三族(攻击系 ⚔ / 增益系 ✦ / 妨害系 ☠);⑤ 三词收敛两词——多段命中经用户拍板退役(无防御磨损体系,无对照机制),P1 字段位「命中段数」保留;⑥ 实装面 = 仅 L2 Boss 蛛化卓尔精灵(venom_burst + rider_mult 0.75;新行 web_snare 蛛网束缚 debuff→off_guard,首发载荷 = 失衡〔用户拍板〕,乏力 sapped 留作第二词汇),其余敌种不动 | | v0.11 | 2026-09-20 | **伏击单轮上限删除批(用户拍板——与 战斗结算 / 地图战斗资源 / 难度曲线与Boss / registry / 切片代码同批)**:① 伏击节上限段与溢出可见行删除,一轮踩雷数由行动点预算天然封顶成文;② 字段表 advantage_per_round_cap 行删除(原 0 = 免疫伏击语义作废);③ 结构冻结联动纪律去单轮上限 × 攻击基线、数量 × 上限两条;④ 验收标准伏击授予与表校验条同步 | | v0.12 | 2026-09-20 | **格面资源八型批(用户拍板——与战斗结算 / 地图战斗资源 / 行动经济 / registry / 切片代码同批)**:① 伏击节整体退役改写(复利集中单血条与群伤奖励双重不对称,用户裁决);② 责任表/结构冻结/验收标准的伏击词条目拆除;③ 群伤负向现况 = 警觉(敌方护甲等级 +1),定义与承载移交增益状态 | +| v0.14 | 2026-10-06 | **事件发射点登记(内容数据驱动扩展批)**:半血解锁判定点升级为通用事件 `enemy_hp_threshold` 发射点(对所有带 unlock_hp_pct 字段的敌人逐个发射——非 Boss 专属,Boss 语义留给订阅者条件判定;本批零订阅者,登记归效果引擎事件词汇 16 发射点之一,后续内容批随批接线)。零行为变化。 | v0.13 | 2026-10-05 | **技能池执行批(迭代14④——D1-D8 全裁零待裁,与 skills.tres/enemy.tres/registry 同批)**:①池构成表 8 敌种全覆盖(补 L2+L3 五敌种行:巨蜘蛛/蛛化卓尔精灵/灰矮人/钩爪恐魔/石君王——池构成轮廓+定位列,权重数值不登)+护甲列改读表口径(失同步欠账清偿);②Boss 句改三代错峰口径;③意图呈现节补半血解锁弹出规格句(**Boss 半血解锁新独占伤害技**:字段 unlock_hp_pct〔1,99] 缺省无=无门槛/单向闩/双面过滤/保底不逼锁定/解锁出一次归因行+一次性技能名 toast——D8 只报名不报数值零演出;三 Boss=震地怒击 ×2.5/CD4·遏毒 ×2.0/CD3·岩崩 ×2.5/CD4 案甲 provisional 归调参);④首拍加权取消〔D2〕杂鱼纹理注销不立文 | diff --git a/design/gdd/systems/排雷循环/秘宝.md b/design/gdd/systems/排雷循环/秘宝.md index b7fee86f..23fa0b63 100644 --- a/design/gdd/systems/排雷循环/秘宝.md +++ b/design/gdd/systems/排雷循环/秘宝.md @@ -18,7 +18,7 @@ - **目录九件**(值全 provisional 归难度曲线与Boss):被动六件=淬毒刃纹(克制面穿刺/魔法直伤 +N)/标中殉爆(每标中一旗全体存活敌吃 N 群伤)/惊巢反制(挖雷惩罚减半+反弹 N)/工兵晨令(前 effective_rounds 轮每轮 +1 AP,P4 甲表值化)/偏运硬币(装板摇号稀有正向 kinds 权重 +N,池结构零触碰)/三选一刷新件(X 次/每碰三选一);主动三件=掘进蓄能(每开够 N 安全格积 1 点发起临时 AP,charges 2/冒险)/探巢摆钟(3×3 真视窄域,charges 2/冒险)/爆破工兵包(3×3 真视 + 4 AP + **中立移除雷格**,active_charged 装配件 charges 2/冒险——迭代 13 实装,见下「中立移除」注)。 > **中立移除(迭代 13 T7/T8 实装口径;裁决事实源=任务书-工程债与测试基建批-2026-10-04.md P1/P2 甲,随批吸收本节为正主)**:使用时对 3×3 区域做**中立移除**——只移除「未揭示、无旗、是雷」的格:不引爆、不揭示、不进连锁;被移除格退色降档呈现(暗淡记忆态同族)、不可挖掘不可插旗(拒绝零消耗);**周边邻接数字=原始雷数冻结不重算(P1 甲)**,移除格自身无数字可保留(未揭示雷格本无数字,P2 甲空集真)。清图判定零涉(分母只数安全格,雷格不入);旗判定基线零回溯(有旗格跳过移除)。获得渠道=营地同调位装配(不入掉落投放池——form 非空行永不入过渡渠道,模拟/自动档零涉,MC 读数零扰动)。 -- **三选一刷新双源**(P1 甲):X=刷新件每碰三选一重给(优先消耗、不结转)+ Y=营地刷新升级行(每冒险开局注入冒险周期池、用完即无;meta.tres attune_refresh_upgrade 行族);重抽消费 reward_pick 既有洗牌流,池规则全保留;autoplay 默认不刷(钓鱼归玩家)。 +- **三选一刷新双源**(P1 甲):X=刷新件每碰三选一重给(优先消耗、不结转)+ Y=营地刷新升级行(每冒险开局注入冒险周期池、用完即无;meta.tres attune_refresh_upgrade 行族);重抽消费 reward_pick 既有洗牌流,池规则全保留;autoplay 默认不刷(钓鱼归玩家)。**GP 付费档(2026-10-06 用户令——三选一刷新对齐商店独立刷新)**:免费双源用尽后可 GP 付费刷新,价=ceil(20×1.8^本次停点已付费刷) provisional(与商店货架刷新读同两个调参旋钮 shop_refresh_*,单点调参);付费刷计数每停点清零(与商店进店清零同构);钱包=冒险 GP 同一账本(LevelRunner.spend_gp 单口);余额不足=钮禁用(不隐藏,对齐商店)。店内门票专长三选一暂不挂刷新面(维持现状,扩面须先裁门票保底计数交互)。 - **🔴 承重契约**(schema 加载期强制):机制轴禁裸 flat 数值(行键白名单封闭);真视窄域 ≤3×3;核心层零 await;零随机;排雷玩法零特判;雷安同池冻结(硬币只动权重投影)。 - **验收分层**:attune=off 续用现行带;attune=on 带平移归难度曲线与Boss 投值(商店 shop=off/on 分层先例同法)。 @@ -176,6 +176,8 @@ 结构冻结(非调参项,改动走本文修订流程):真视窄域护栏、零信息目标不消耗、边缘窗口照常受理、使用零行动点成本、库存关卡域、用后不可逆、零随机。 +效果声明形态(内容数据驱动扩展批换代,2026-10-06):被动件的效果行为从按件 id 硬编码换为**订阅行**——行内声明**订阅哪个事件(on)/ 什么条件(when)/ 什么效果(do)**,由效果引擎统一解释执行(淬毒刃纹=克制判定事件、标中殉爆=标旗事件、惊巢反制=踩雷事件、工兵晨令=每轮开始事件、偏运硬币=资源掷定事件);主动件保持按效果类型通用分发(掘进蓄能的受理闸=使用事件订阅+积点计数器账本)。事件 / 效果类型 / 条件事实 / 算子四词表封闭(效果引擎单一事实源),卡面效果摘要=行 `desc` 字段直读(文案表值化)。 + playtest 预警(预注册,归难度曲线与Boss): | 预警 | 指标与读法 | @@ -214,4 +216,5 @@ playtest 预警(预注册,归难度曲线与Boss): | v0.6 | 2026-09-30 | **拍板点 1 收口批(2026-09-30 用户裁决甲:同调=终局方向)**:①新增「定位与形态沿革」节——现行正文定性=切片期过渡形态、终局=局外同调件(设计权威底稿=原提案八拍板,git 57da58c 已退役),两态关系=形态替换、衔接纪律与事故留痕入节;②正文口径回写四处:五维度表信息/规划/反制三行改现行态(罗盘退役/显影件封存/豁免面入正文)、覆盖检查改投放维度覆盖口径、掉落池获取节补解锁门控(2026-09-29 拍板③甲)、板级叠加层预读集改「机制在案无投放来源」;③验收两条(护栏/覆盖检查)与调参表投放态注记、地形变异依赖行同步;④实施批登记见 06「秘宝同调实施批」条。审计冲突 C19/C29 随批收口(全录见审计报告 git 提交 c095916,报告已退役) | | v0.7 | 2026-10-01 | **奖励节点渠道改写注记(迭代 10 追加批——用户拍板「奖励节点与通关三选一共享同池」)**:①「奖励节点三选一构筑点」形态(v0.5 ④)退役——奖励节点改提升三选一共享池(秘宝件为池中 1/6 权重选项,同调位满位替换交互随 BonusPickOverlay 共用面生效,spec:node-reward.md v2.0);②道具件经节点渠道入本关库存(来源归因 reward_node,与掉落/三选一渠道同受 transitional_sync_slots 过渡上限约束);③战斗掉落渠道零改动;④本批过渡约束形态与切换义务注记见 registry aux_items 条同日批注;同调实施批的全渠道统一收口义务不变 | | v0.8 | 2026-10-03 | **反制契约作废批(2026-10-03 用户终裁——层级错配失败设计)**:①五维度「反制」行与豁免面 {plan, info, counter} 的 counter 注记改终裁态(原框架契约「每变异≥1反制道具」作废,终裁理由见地形变异.md v0.6/本文件 v0.8 版本行(原 06 条目已收口移除));②上游地形变异依赖行、P2 后置行同步改「迭代 12 调研前置重定」;③地形变异.md v0.6 同批落义务作废注记;④迭代 11 范围裁定(同调→商店→迷雾三批)见 roadmap 迭代 11+ 行——反制批移迭代 12 | +| v1.0 | 2026-10-06 | **效果声明换代(内容数据驱动扩展批)**:被动六件(淬毒刃纹/偏运硬币/工兵晨令/惊巢反制/标中殉爆)改订阅行+掘进蓄能受理闸数据化(使用事件订阅+积点计数器)——按件 id 硬编码清零;结构冻结六条零触碰,行为与迁移前逐位一致(MC 同种子 40 局对拍实证)。 | v0.9 | 2026-10-05 | **消歧更名批(用户拍板 2026-10-05):系统更名「魔法道具」→「秘宝」**。**更名理由**:「魔法」一词在本作已被征用为敌方侧伤害/护甲语汇(`magic_bolt 魔法矢` / `magic_recoil 魔法反噬` / `magic 魔法族` 格面微光 / `ward 魔法护甲`),「魔法道具」易被读成"魔法属性的道具";「秘宝」取"神秘·强大·稀有"义,与九件内容气质(工兵/工程/手艺向)契合,且与局内道具(奇物)不冲突。**同调位机制名保留不变**(attunement 仍为 DND 词,机制血统不受更名影响)。**本批仅改文档称谓,代码不动**——代码标识符仍为 `aux_*` / `AUXILIARY_ITEMS`(auxiliary 词根为过渡形态遗留,与中文名不同步属已知欠债,另批处理);退役路径残留玩家可见文案(`battle_narrator.gd:117-118`、`bonus_interact_controller.gd:76`)亦留另批。历史定稿文档(research/ 提案与任务书、qa/ 检查单、archive/ 归档)按换名先例原样保留不改。 | diff --git a/design/gdd/systems/跨局循环/生涯.md b/design/gdd/systems/跨局循环/生涯.md new file mode 100644 index 00000000..b0491d2d --- /dev/null +++ b/design/gdd/systems/跨局循环/生涯.md @@ -0,0 +1,105 @@ +# 生涯 + +> **一句话定位**:主菜单里与营地平级的**局外只读界面**——营地管「花」(花钥石买永久强化),生涯管「看」(看自己跨局攒下的一切:累计战绩、历史冒险、图鉴收集)。冒险进行中不给任何数据回看(2026-10-04 用户裁决 P-C7 的延续),生涯是历史数据唯一的家。 +> **状态**:范围草案(2026-10-06 迭代 17 卡④设计提纲批成文,待用户评审;评审通过后期一/二/三实装批另行立项,本文不预占排期)。 +> **来源**:[06-待办清单「『生涯』界面——冒险中数据回看的唯一承接处」条](../../06-待办清单.md)(2026-10-04 用户裁决 P-C7——冒险中不给数据回看,历史战报与结算明细的查看去向 = 新「生涯」界面)。 + +## 一、信息架构 + +生涯入口是主菜单一级按钮,与营地平级(2026-10-06 用户裁,备选案与淘汰依据见任务书 P1)。现行主菜单按钮组由 `build_start_overlay` 构造(`game/presentation/ui_assembler.gd:104`):「冒险」组(继续上局 :138 / 开始冒险 :144)与「营地与系统」组(营地 :150 / 设置 :152 / 退出游戏 :156),另有右下角调试角标(DS_DEBUG=1 门控,与主按钮组非平级)。生涯按钮与营地平级同列一级;具体组帽归属与按钮排位属布局细节,归期一实装批(本文不画布局稿)。 + +``` +主菜单(开始覆盖层) + ├─ 冒险组:继续上局 · 开始冒险 + ├─ 营地与系统组:营地 · 生涯(本提纲新增) · 设置 · 退出游戏 + └─ 调试角标(DS_DEBUG=1 门控,非平级) + +生涯(局外只读面) + ├─ 跨局汇总(累计战绩与解锁总览) + ├─ 历史冒险回看(逐局摘要与单局明细) + └─ 图鉴(敌人优先,秘宝/奇物随后) +``` + +边界三条(均为 2026-10-06 用户裁 P4 甲): + +1. **局内不放生涯入口**。冒险进行中玩家只看暂停层(迭代 17 卡③并行批——局内只读查看钥石与解锁进度);若历史回看能从局内到达,等于绕回 P-C7 退役前的形态,禁止。 +2. **生涯内零跳转**。不放「去营地」之类的操作引导,生涯纯只读。 +3. **与 P-C7 的关系**:生涯的成立正是 P-C7 裁决的补偿面——「冒险中无回看」的已知代价由生涯承接(06 条目原文口径),本文不提议在冒险中恢复任何回看入口。 + +## 二、承载内容清单(逐段标数据可行性) + +生涯装三段内容:跨局汇总、历史冒险回看、图鉴。这三段是 06 条目给定的骨架,本节在其上逐项落到数据源。跨局汇总与图鉴的收集类数据大多现成;历史回看的数据面已在存(见下),缺口只有三件,已随 P2 甲裁决定方向。 + +### 跨局汇总(期一) + +| 内容 | 数据源 | 现状 | +|---|---|---| +| 冒险总局数 / 通关局数 | 无现成数据源——`first_adventure_done` 是布尔(`save_manager.gd:117`)、`level_clear_counts` 按关计次(`:109`)反推不出总局数、`recent_summaries` 有上限截断不能累总 | 须新增两个终身计数域,涉存档 schema 迁移(save v6 义务,2026-10-06 用户裁归期一实装批) | +| 各关通关次数 | `clear_count(level_index)`(`save_manager.gd:238`) | 现成 | +| Boss 首杀与总击杀 | `boss_first_kill_marked`(`save_manager.gd:242`)/ `boss_kill_count`(`:250`) | 现成 | +| 钥石余额 | `keystone_balance`(`save_manager.gd:202`) | 现成。累计收支流水不在存(加钥石与花钥石只动余额,`:206`/`:211`)——如要做属新增流水域,期一实装批另呈报,本文只登记 | +| 三柜台解锁完成度概览 | 锻造 `forge_level`(`save_manager.gd:218`)/ 工坊 `workshop_status`(`meta_progression.gd:90`)/ 天赋树 `tree_nodes`(`save_manager.gd:256`) | 现成只读投影 | + +### 历史冒险回看(期二) + +数据面现役事实:每局冒险收束时已有逐局摘要自动入档——`recent_summaries`(默认域 `save_manager.gd:116`、值拷贝访问器 `:316`、入账原语 `:325-338` 逐局插最前并按上限截断),上限 `SUMMARY_CAP := 10`(`:40`)。摘要字段在冒险收束时组装(`adventure_runner.gd:1005-1018`:结局 result、通关关数 levels_cleared、止步关 stopped_at、钥石合计 keystone_total、本局主种子 master_seed,另附逐杀掉落 keystone_drops 与终局特性 relics 等);摘要的内容口径 owner = [冒险结构](../冒险循环/冒险结构.md),本文不复述其口径。 + +2026-10-06 用户裁(P2 甲):每局一行摘要,行内给**结局 / 到达关卡 / 钥石收入 / 用时**四件事,上限存最近 N 局。对应三件实绩(本批代码级复核确认的缺口,非新建数据面): + +1. **扩容**:cap 10 → N。N 候选 20 / 50 / 维持 10,实装期按存档体积读数三选一;读数方法与列表形态的参数化维度由设计稿给出、实装期执行。 +2. **补「用时」埋点**:全仓现在没有任何用时采集点(`game/core/` 内 grep elapsed / ticks / duration / Time 零命中;摘要字段集 `adventure_runner.gd:1007-1018` 无此项)——属新增埋点,随期二。 +3. **旧档迁移**:旧档既有 ≤10 条摘要原样保留迁移,向后兼容。 + +单局结算明细回看:遗留件 `settlement_review_overlay.gd`(`game/presentation/views/`,文件本体在盘、全仓无实例化引用——仅 `ui_kit.gd:356` 样式注记提及)由期二实装批复活承接,改挂生涯(2026-10-06 用户裁 P5 甲)。 + +可选增强(不占期、不上列):关键构筑列——摘要已附终局特性集 relics(`adventure_runner.gd:1017`),实装期评估是否呈现。 + +### 图鉴(期三) + +- **敌人图鉴(优先,2026-10-06 用户裁 P3 甲)**:直接承接 06 在册「敌方技能图鉴 / 学习呈现」条(P2——见过 → 认得 → 记谱;归属 [HUD 框架](../表现与支撑/HUD框架.md) 与 [敌人系统](../战斗循环/敌人系统.md))。「见过」记账面 = 新增 meta 域,涉存档 schema 迁移(与期一终身计数域同属 schema 义务面;版本号按实装时点的现行版本顺延)。 +- **秘宝 / 奇物图鉴(随后)**:无在册条目,立项时另行登记。 +- **奖励格图鉴:只登记不激活**(P3 甲)。营地柜台三「奖励格图鉴」现况 = 柜台框在(`camp_overlay_view.gd:121`)、下段由天赋树行占用(`:128` 注记),图鉴本体空置;该柜台已随配比预设批整体挂起冻结(2026-10-05 用户令,06 在册),重启须用户另行发令。 + +## 三、与既有界面的关系 + +| 界面 | 管什么(现状指证) | 与生涯的边界 | +|---|---|---| +| 营地([camp UX spec](../../ux/camp.md)) | 跨局成长**操作**面(花钥石买强化 / 装配 / 点树)+ 底栏「最近冒险」条带——`camp_overlay_view.gd:431` `_rebuild_recent()` 逐局拼行(`:442`「完成/放弃/失败 · 通关 N 关 · 钥石 +N · 种子 N」,无截断上限),数据源 = `camp_shop_presenter.gd:85` 投影的同一份 `recent_summaries` | 营地不改生涯只读定位;生涯不做任何购买入口,也不放「去买」按钮。营地条带的去留是本提纲必答项——见 §五 P6 | +| 暂停层(迭代 17 卡③,并行批开发中) | **局内**只读查看这一局进行中的钥石与解锁进度 | 暂停层看当局当前值,生涯看跨局累计与历史;局内无生涯入口(P4 甲) | +| 结算屏([settlement-summary UX spec](../../ux/settlement-summary.md)) | 冒险收束当下的入账明细——`main.gd:537` `_on_adventure_finished` 收束时呈现摘要与钥石明细(复用终局面账行,`battle_end_overlay.gd:118-129`) | 收束当下的呈现保留不动;生涯承接的是收束之后的任意时刻回看 | +| 帮助手册(Beta 批,未实装——教学内容归 Beta 帮助手册,`ui_assembler.gd:99` 注记) | 讲「怎么玩」 | 生涯讲「我玩过什么」。图鉴条目文案与手册是否共用内容源,接口登记归 Beta 批一并裁 | +| 图例面板(legend_overlay) | 局内符号/徽标即时解码 | 不合并——图例是局内解码面,图鉴是跨局收集面 | + +## 四、分期实装建议 + +三期均为**另行立项**的实装批,下表是建议不是排期,roadmap 迭代位由用户逐期点名。本提纲不含布局稿、视觉稿、素材需求清单(美术由用户独立成批自理,2026-10-05 立规)。 + +| 期 | 内容 | 前置 | 建议窗口(候选) | +|---|---|---|---| +| 期一 | 跨局汇总页 + 主菜单生涯入口落位。总局数/通关局数两个终身计数域随该批做存档 schema 迁移 | 提纲评审通过。实装批前置义务 = 对齐迭代 16 UI 批后的现行基线:`FS_*` 六档字号 / `TG_*` 语义色 / `BUTTON_MIN_H=40`(`ui_kit.gd:437`)/ 色板单源 / `page_shell` 模态栈纪律(`ui_kit.gd:394`)——与卡③任务书同款红线,本文只登记、不展开 | Alpha 后最近一个 UI 窗口 | +| 期二 | 历史冒险回看整段:N 扩容 + 用时埋点 + 旧档摘要迁移 + 逐局列表 + 单局明细(`settlement_review_overlay.gd` 复活承接)+ 营地条带处置(P6 裁案落地) | 期一。实装批开工前须向用户呈报两件:N 终值读数(20 / 50 / 维持 10 三选一)与「用时」埋点口径(计时起点、暂停是否计入——字段级细节本文不做) | 期一落地后独立批 | +| 期三 | 图鉴承接(敌人优先,范围与顺序随 P3 甲);「见过」记账面 schema 义务随该批执行 | 期二或独立评估 | Beta 窗口候选(与教学引导同段评估——图鉴的「认得」轴与教学相关;06 教学引导节各条 2026-10-04 起已整体标 Beta 阶段) | + +本地化文案权:生涯界面本体按钮、页签、空态文案归[设置与本地化](../表现与支撑/设置与本地化.md)线(全仓无多语言实装——`game/` 内 grep TranslationServer 零命中;文案权先例 = 教学文案定稿权登记于 06),实装批承接。 + +## 五、待裁点 + +P0-P5 六点已于 2026-10-06 用户全裁(案甲 × 6,备选案与淘汰依据在任务书 §8),结论已落文上文对应节,不重复列。本批新发现的待裁点一项: + +### P6 · 营地底栏「最近冒险」条带去留(期二必答项) + +**现状与问题**:营地底栏「最近冒险」条带把传入的摘要逐行全部拼进一个 Label(`camp_overlay_view.gd:431-443`,`:442` 行模板,无截断上限),数据源与生涯历史回看是同一份 `recent_summaries`(`camp_shop_presenter.gd:85`)。生涯期二把上限扩到 N 后,条带按现行逻辑会把 N 行全拼进底栏——这是本批复核发现的实际耦合点,期二落地时必须给条带一个去处。 + +| 案 | 内容 | 淘汰/保留依据 | +|---|---|---| +| 甲(建议) | 期二生涯历史回看落地时,营地条带撤下、逐局回看整体归生涯;期一(汇总页上线、数据面未动)期间条带原样保留零改动 | 分工矩阵干净——「看」归生涯、「买」归营地;逐局历史只剩一个权威呈现面,去重问题自然消解;顺带解开 N 扩容后底栏无上限拼行的实耦合 | +| 乙 | 两处并存——营地留最近几条速览、生涯给完整列表 | 营地顺手一眼仍有价值;淘汰候选:同一数据两处呈现,玩家要猜哪边全;条带须另加截断逻辑 = 新增一处维护面;「看」留在操作面违反分工矩阵 | +| 丙 | 条带原样永久保留(固定存 10 条,不随 N 动) | 改动最小;淘汰候选:与生涯列表长期双轨,上限语义分裂(营地固定 10 / 生涯 N),N 大于 10 后两处看到的条数对不上 | + +裁决方式:随本提纲评审一并定案,期二实装批执行。 + +## 版本历史 + +| 版本 | 日期 | 说明 | +|---|---|---| +| v0.1 | 2026-10-06 | 设计提纲首版(迭代 17 卡④设计批,纯文档):五节成文;P0-P5 已裁结论落文;新发现待裁点 P6(营地最近冒险条带去留);现状描述全部经代码级复核(F1-F10,见 design/qa/iter17-career-exit-report-2026-10-06.md)。状态 = 范围草案待评审 | diff --git a/design/qa/iter17-career-exit-report-2026-10-06.md b/design/qa/iter17-career-exit-report-2026-10-06.md new file mode 100644 index 00000000..906b6a48 --- /dev/null +++ b/design/qa/iter17-career-exit-report-2026-10-06.md @@ -0,0 +1,61 @@ +# 出口报告:生涯界面设计立项(迭代 17 卡④设计批) + +> 批次:纯设计批(零代码、零数据面、零测试执行——两入口铁规:非 loop 实施批且用户未点名)。 +> worktree:`g-plan2-iter17-career`(分支 `iter17-career-20261006`,基点 = master fc4dbde)。 +> 任务书:`design/research/任务书-生涯界面设计立项-2026-10-05.md`(v1.1 零待裁点,U0 已发)。 +> 本报告按「吸收即死」纪律属中间文档,寿命 = 迭代寿命;五条判定与 F1-F10 复核结论已同步写进各正式文档(生涯.md / 06 / roadmap)。 + +## 一、完成定义五条逐条判定(任务书 §1.2) + +| # | 判定项 | 判定 | 依据 | +|---|---|---|---| +| 1 | 提纲成文 | ✅ | `design/gdd/systems/跨局循环/生涯.md` v0.1(P0 甲裁定的载体,状态=范围草案),五节齐:一、信息架构;二、承载内容清单;三、与既有界面的关系;四、分期实装建议;五、待裁点 | +| 2 | 事实对齐 | ✅ | §2 事实表 F1-F10 逐条代码级复核全绿(见本报告 §二);提纲中每句现状描述带 file:line 指证(R1) | +| 3 | 裁点齐备 | ✅ | P0-P5 已裁结论全部落文对应节;本批新发现待裁点一项(P6 营地最近冒险条带去留),三案全列含淘汰依据、有明确建议案;全文无「待定」悬空 | +| 4 | 零实装 | ✅ | diff 仅含:新建生涯.md、02-系统清单.md 三行(树行/代号行/版本行)、06 状态行、roadmap 卡④行、active.md、本报告;`game/` 零改动、存档零改动、零测试执行 | +| 5 | 人闸 | ✅(待用户执行) | 06 状态行已更新为「设计提纲已成文待评审」;**人闸 = 用户评审提纲**(评审时随裁 P6) | + +## 二、T0 事实复核 F1-F10(2026-10-06 静态阅读,行号以实核为准) + +| # | 事实 | 结论 | 实核证据(file:line) | +|---|---|---|---| +| F1 | 冒险中回看已退役 | ✅ 绿 | `battle_end_overlay.gd:9`(signal continue_pressed,注记 review_pressed 退役)、`:106`(明细回看钮退役注记)、`:90` 现为 GP 账目行 add_child——任务书 v1.0 的 :90 漂移勘误属实;`battle_log_view.gd:47`(「上轮摘要(回看归「生涯」」提示)、`:115`(带点击回看入口退役注记);`ui_assembler.gd:67`(明细回看 overlay 退役注记) | +| F2 | settlement_review_overlay.gd 在盘 | ✅ 绿 | `game/presentation/views/settlement_review_overlay.gd` 在盘(含 .uid);全仓无实例化引用——仅 `ui_kit.gd:356` 样式注记提及「settlement_review 同款」;无 .tscn 引用 | +| F3 | 跨局持久面 = save v5 meta 域 | ✅ 绿 | `save_manager.gd:39`(SAVE_FORMAT_VERSION=5 版本链注记)、meta 默认域键集 `:106-117`(keystone_balance/forge_levels/item_unlocks/level_clear_counts/boss_first_kill/boss_kill_count/attunement_equipped/attune_refresh_level/tree_nodes/fork_sides/recent_summaries/first_adventure_done)+ 访问器面 :202-:316 | +| F4 | recent_summaries 现役 + 三件真实缺口 | ✅ 绿 | `save_manager.gd:40` SUMMARY_CAP=10;`:316` 值拷贝访问器;`:325-338` apply_adventure_summary(push_front + cap 截断 + boss_drops 副本 :334);`meta_progression.gd:129` settle_adventure 经 `:133` 归档(:125 注记)。缺口①cap 扩容、②用时零采集点(game/core grep elapsed/ticks/duration/Time 零命中;摘要字段集 `adventure_runner.gd:1007-1018` 无用时)、③总局数/通关局数无 lifetime 域(first_adventure_done 布尔 :117、level_clear_counts 按关 :109)——三条全实证 | +| F5 | 当局结算呈现仍在 | ✅ 绿 | `main.gd:537` `_on_adventure_finished` 收束当下呈现摘要五件事+钥石入账明细;终局面三行账 `battle_end_overlay.gd:118-129`(open_victory 账行装配) | +| F6 | 跨局汇总数据源现状 | ✅ 绿 | 各关通关次数 `save_manager.gd:238`、Boss 首杀/总击杀 :242/:250、钥石余额 :202(累计收支无流水——:206/:211 只动余额)、锻造/工坊/天赋树访问器 :218/:230/:256;总局数/通关局数两格无数据源(同 F4③) | +| F7 | 图鉴承接候选内容 | ✅ 绿 | 06:192「敌方技能图鉴 / 学习呈现」(P2,见过→认得→记谱);奖励格图鉴柜台空置——06:244(随配比预设批挂起冻结)、`camp_overlay_view.gd:121`(柜台三框在)、`:128`(下段天赋树行占用注记) | +| F8 | 相邻界面分工现状 | ✅ 绿 | 营地 `camp_overlay_view.gd:431` `_rebuild_recent()`、`:442` 行模板「完成/放弃/失败 · 通关 N 关 · 钥石 +N · 种子 N」(无截断上限)、`camp_shop_presenter.gd:85` recent 投影;主菜单按钮组 `ui_assembler.gd:104-160`(营地 :150);设置面 :152;暂停层 = 迭代 17 卡③并行批(本 worktree 基点未含其实码,提纲按任务书口径引用) | +| F9 | 06 条目原文口径 | ✅ 绿 | 06:400「新「生涯」界面(跨局汇总/历史冒险回看/图鉴承接)」三段骨架原文在册 | +| F10 | 阶段语境 | ✅ 绿 | 06 教学引导节各条状态行 Beta 阶段注记(:375/:383/:391,2026-10-04 拍板改排);生涯条目(06:396-404)无阶段标注——提纲分期建议以候选窗口作答 | + +**差异记录(两处,均不阻塞)**: + +1. `ui_assembler.gd` 实际路径为 `game/presentation/ui_assembler.gd`(任务书 F1 只写文件名未带路径,无歧义;提纲已用全路径引证)。 +2. 复核顺带发现一处**既有**登记滞后(非本批引入、不属本批范围):`design/registry/entities.yaml` 存档条目 version 注记仍写「SAVE_FORMAT_VERSION=4 / v1→v4 迁移链」,代码已至 v5(`save_manager.gd:39`,v5=天赋树域,迭代 12);domains 行的 meta 域九件明列与 recent_summaries cap 10 记载仍准确。属 registry 与代码的同步欠账,owner 文档 = 存档序列化.md,建议归后续文档对齐批或 doc 一致性检查呈报——本批 diff 范围不含 registry,未动。 + +## 三、交付物与落点 + +| 交付物 | 落点 | 状态 | +|---|---|---| +| 设计提纲(本批主体) | `design/gdd/systems/跨局循环/生涯.md` v0.1 | 范围草案,待用户评审 | +| 根索引同步 | `design/gdd/02-系统清单.md`(跨局循环树行 + 命名对照表 CAREER 行 + v1.12 版本行) | 已落 | +| 06 状态行 | `design/gdd/06-待办清单.md` 生涯条 →「设计提纲已成文待评审」 | 已落 | +| roadmap 卡④行 | `roadmap.md` 迭代 17 卡④ →「✅ 设计提纲已成文待用户评审……人闸=用户评审提纲」 | 已落 | +| 会话记录 | `active.md`(快照节追加 + 台账 379) | 已落 | + +## 四、提交哈希清单 + +| 提交 | 内容 | +|---|---| +| 10df8ba | T1:生涯.md v0.1 成文 + 02 根索引三处(树行/CAREER 行/v1.12) | +| (本提交) | T2 加批次收尾:06 状态行、roadmap 卡④行、active.md、本报告 | + +终态 = worktree 内全部提交完成,**未合并、未 push**(合并落回 master 由用户/编排会话执行)。 + +## 五、遗留与移交 + +1. **人闸(唯一)**:用户评审设计提纲 `design/gdd/systems/跨局循环/生涯.md`,评审时随裁新发现的 P6(营地底栏「最近冒险」条带去留——建议甲:期二时撤下归生涯)。评审通过后期一/二/三实装批另行立项。 +2. 实装批前置义务已在提纲 §四登记(非本批工作):期一=对齐迭代 16 UI 基线 + save v6 两个终身计数域;期二=N 终值读数呈报(20/50/维持 10 三选一)与用时埋点口径呈报;期三=「见过」记账面 schema 义务。 +3. registry 存档条目 version 注记滞后(§二差异记录 2)——移交后续文档对齐批,非本批范围。 diff --git a/design/qa/iter17-datadrive-exit-report-2026-10-06.md b/design/qa/iter17-datadrive-exit-report-2026-10-06.md new file mode 100644 index 00000000..da256a4e --- /dev/null +++ b/design/qa/iter17-datadrive-exit-report-2026-10-06.md @@ -0,0 +1,70 @@ +# 出口报告——内容数据驱动扩展批(迭代 17 卡⓪,2026-10-06) + +状态:**批内全部提交完成、六闸全绿、终态=worktree(iter17-dd-20261006 分支)全部提交,待用户合并落 master**。 +执行事实源:[任务书-内容数据驱动扩展批-2026-10-06](../research/任务书-内容数据驱动扩展批-2026-10-06.md)(v1.1);设计权威:[研究-内容数据驱动扩展架构-2026-10-06](../research/研究-内容数据驱动扩展架构-2026-10-06.md)。 +执行形态:前任 subagent 完成 T0-T2 后因额度中断,续跑 subagent 接手 T3 断点(三个脏文件半成品)收口并完成 T4-T7。 + +## 一、提交清单(worktree 内全部提交,按卡) + +| 卡 | 提交 | 内容 | +|---|---|---| +| T1(前任) | `7d157b3` | 效果引擎框架:事件发布订阅解释器+16 发射点埋点+组合规则六族+计数器账本 | +| T2(前任) | `225765f` | 词汇表落码:feats/treasure 两表 trigger 订阅形态 schema 换代+护栏迁移 | +| T3 | `e28a6cb` | 专长迁移:27 行 triggers 全量生效+旧 23 挂点枚举消亡+_feat_flags_for 退役+消费点拆除(伤害控制器 13 处+编排器全部旗面)+文案入表(专长卡 desc)+断言面随迁(冒烟 ④-⑧ 全量改写) | +| T4+T5 | `a33ddaf` | 秘宝六件迁移(淬毒/偏运/晨令/惊巢/殉爆/掘进蓄能受理闸)+按 id 特判清零+pick_refresh kind 分发+curio 卡片文案入表;令牌兑付 match 收敛进效果类型路由表+树规则键同面收口 | +| T7 | 本批终提交 | 文档正主换代(奇物.md v2.8/秘宝.md v1.0/敌人系统.md v0.14+registry 两件+06 状态收口+roadmap 卡⓪行)+本报告 | + +T0 断点核查记录:前任已留 pre 锚(见 §三),T0 三查其余项随续跑开工复读完成。 + +## 二、闸读数(T6 回归闸全绿) + +| 闸 | 读数 | 结论 | +|---|---|---| +| 冒烟(三闸) | **800 项检查,0 项失败**,退出码 0,无 SCRIPT ERROR | 绿(断言数 783→800:T3 断言面随迁净增 17 项,漂移追认随 T3 commit 留痕) | +| 单元(run_all) | **17 套全绿** | 绿 | +| 探针闸(probe_gate --click) | ui_input/ui_struct/ui_contrast/click 四段全 PASS,批末残留 Godot=0 | 绿(本批触碰呈现层——battle_narrator/curio_interact 文案面,故带 click) | +| MC 二档对拍 | 见 §三 | 绿——逐位一致 | +| 确定性复放 | 同种子改前/改后两次采集逐位一致(40 局全对) | 绿(强于「同码两遍」口径) | +| doc 三闸 | doc_link_audit 过(exit 0);doc_term_audit 过(exit 0);gdd_lint 2 处失败=**基线既有**(06-待办清单/07-体验验证清单结构性指标,stash 对比实证改前改后同为 2 处零新增) | 绿(零新增) | +| 熵增闸(repo_structure_audit) | 1 处违例=design/diagrams 子目录越界——**基线既有**(前批「GDD系统架构总图批」00712d5 产物,非本批引入;归该批主自证或后续清理) | 绿(零新增) | + +## 三、MC 锚与对拍结论(二档合规) + +- 判据口径:契约 2 两套(确定性复放与冒烟=**逐位一致**;MC 统计量=**成型画像同带**,正主=design/qa/m4iter12-t7-batch1-readings-2026-10-04.md)。 +- 改前锚(前任 T0 采集,留盘未补跑):`sim-data/runsum_iter17dd_pre_off_n20_20261006.jsonl` / `runsum_iter17dd_pre_formed_n20_20261006.jsonl`(seed0=83000、N=20/档、normal 档、route=min、attune=off、off/formed 同种子配对;formed=七流派各 2 件档一成型注入画像——14 件全部系本批迁移行)。 +- 改后锚(T6 采集):`sim-data/runsum_iter17dd_post_off_n20_20261006.jsonl` / `runsum_iter17dd_post_formed_n20_20261006.jsonl`(同 seed0 同口径;新锚去前缀纯 JSONL 按种子升序)。 +- **结论:40 局(2×20)同种子逐位一致**——每局 RUNSUM 全字段(通关数/承伤/踩雷/旗经济/资源事件/钥匙石/逐战上限序列等 33 字段)改前改后零差。统计量同带判据自动满足(逐位一致蕴含同带):L1 通关率 off 20%(4/20)、formed 80%(16/20),与改前逐位同值。 +- 档位合规:总量 40(每档 20)=二档上限贴边;另有 T3 段末 S1 预检 2×5 局(三档只测不判,未留锚)——S1 停机点未触发。 + +## 四、代拍板记录(任务书 §八白名单四项+呈报不停机级,全部 commit 留痕) + +1. **fact 查询面落法**:`has` 算子=在场判定(value 缺省=非空;value 给定=数组包含)——地形伤/伤害型地形四行订阅的查询面(T3 commit)。 +2. **fact 查询面落法**:perfect_defense「全中」装不进封闭 9 项事实——when 落 flags_count≥1 保守判定,全中闸=flag_settle 发射点施加面承载(禁私开 fact 停机点纪律保持;T3 commit)。 +3. **事件载荷字段**:attack_commit 发射点从 resolve 内移至开单入口(任务书 §3.1 定稿位),命中随单据 context 携带;damage_taken 补 terrain_kind、flag_hit 改 terrain_kind=邻接伤害型变异集(T3/T4 commit)。 +4. **发射点微调(±1)**:round_start 发射位前移至行动经济轮切换之前(树规则 grant_ap queue 当轮兑现=先手压制首轮语义锚);attack_commit 单发射点、命中两处消费(编排器段序承载连珠矢开单位/控制器管线算术承载其余施加面——dispatch 纯读零副作用)。 +5. **效果类型落法对拍注记(映射表五处语义修正的随批追加,行为等价优先)**:猎巢人/矿工的执拗=必中附加通道(映射表标 modify_ticket/attack_bonus 与现状不符,落 add_ticket_rider per 载荷);整备军备双效果两行订阅;厚积薄发=满层持续状态(非沿触发);荆棘编织者=伤害兑付位第二份产出(非 ×2 乘区);完美布防=旗伤单值倍增(非 yield 乘区)——全部随 .tres 尾注+commit 留痕。 +6. **冒烟断言数漂移追认**:783→800(断言面随迁净增 17 项)。 +7. **文案微调**:掘进蓄能/失义件秘宝卡补实际摘要(原 kind 硬编码面无文案);其余 desc 措辞与旧映射逐字一致。 +8. **效果类型计数**:24 型(任务书标题 23 系笔误——T1 前任已代拍留痕,沿用)。 + +## 五、文档正主换代清单(T7) + +- 奇物.md **v2.8**:专长效果声明换代(挂点→订阅行)五处+版本行;事实源表 feats.tres 行结构换代。 +- 秘宝.md **v1.0**:效果声明形态节(被动件订阅行+主动件 kind 分发+掘进蓄能受理闸数据化+desc 文案表值化)+版本行。 +- 敌人系统.md **v0.14**:半血解锁判定点=通用事件 enemy_hp_threshold 发射点登记(非 Boss 专属,零行为变化)+版本行。 +- registry/entities.yaml:专长 data_sources 换代+effect_declaration 新条(四词表封闭/组合规则/遍历序/重建时机)。 +- registry/architecture.yaml:core_decisions 新条 effect_engine_interpreter(ADR-0011 候选)。 +- 06-待办清单:数据驱动批状态=已实施完成收口(提交号+闸读数+遗留两项)。 +- roadmap:迭代 17 卡⓪行换代(状态=已实施完成)。 + +## 六、遗留人闸项(呈报不停机级,均为显式拍板请求非缺陷) + +1. **掘进蓄能积点模判定留编排器侧**:use_treasure 受理闸已数据化(treasure_used counter_spend 订阅),但「每 charge_n 安全格积 1 点」的模判定留在战斗编排器授予段——「先增后判」在发射点施加架构下无法行内表达(when 于 dispatch 预评估,先增后判需引擎内联过账=会破坏矿脉直觉连击的施加序等价)。行为与迁移前逐位一致(charge_n 读行零字面量);若要求彻底行内化,需效果引擎增设「计数器种子/内联过账」机制,归后续词汇表扩展批裁决。 +2. **主动件 kind 词族全量 rename 不随批**:新词(reveal_tiles/preview/grant_ap/remove_mines/refresh_pick)已在闭集与旧词共存(T2),本批只完成 pick_refresh kind 分发合流;mine_reveal→reveal_tiles 等全词族 rename 波及库存/呈现标记/单测约 15 处零行为收益,显式不在本批(出口呈报,归后续清理批或随批裁决)。 +3. **实机目检归用户**(批次工作流第 12 步):呈现面改动=专长卡与秘宝卡文案源(desc 直读,措辞逐字迁移)——建议实机抽查一张专长卡+一张秘宝卡。 + +## 七、风险与边界声明 + +- 存档零触碰:存档键/格式/断点结构一字未改(持有集/装备存 id 不变,换的只是解释方式)。 +- 护栏全保留:四词表封闭校验、行键白名单、悬空引用致命、零裸 flat 禁令、流派扫雷触发铁律(MINE_EVENTS 同义形态)全部以新形态延续;本批新增封闭面=when value_from 悬空校验。 +- 主区零直改:全部改动在 worktree iter17-dd-20261006;并行在飞三线(iter17-pause 卡③/standee-tier/uialign)未触碰。 diff --git a/design/qa/iter17-pause-exit-report-2026-10-06.md b/design/qa/iter17-pause-exit-report-2026-10-06.md new file mode 100644 index 00000000..c4f652ad --- /dev/null +++ b/design/qa/iter17-pause-exit-report-2026-10-06.md @@ -0,0 +1,81 @@ +# 出口报告:暂停层只读入口批(迭代 17 卡③) + +> 批次:功能性 UI 交互新增(纯呈现零数值改动=零 MC;核心层零改动)。 +> worktree:`g-plan2-iter17-pause`(分支 `iter17-pause-20261006`,基点 = master 44328b1)。 +> 任务书:`design/research/任务书-暂停层只读入口批-2026-10-05.md`(v1.1 零待裁点,U0 已发;T0 基线回填已落任务书头注)。 +> 本报告按「吸收即死」纪律属中间文档,寿命=迭代寿命;结论已同步写进 06 / roadmap / active.md。 +> 执行备注:本批由两次会话接力完成——T0/T1 与组 B 大部系前任会话(因额度中断),断点续跑会话完成组 B 收尾(两处探针断言口径 L1 修正)与 T3-T5。 + +## 一、完成定义五条逐条判定(任务书 §1.2) + +| # | 判定项 | 判定 | 依据 | +|---|---|---|---| +| 1 | 入口可达 | ✅ | 战斗屏 HUD 右下角+关卡地图屏顶栏各一枚「暂停」常驻角标(P1 丁);主菜单零角标(`_sync_pause_entry` 以 `_level_active`∧屏态门可见性);ui_input case⑯ 角标真点击开层绿、ui_click 两屏 case 绿 | +| 2 | 只读数据正确 | ✅ | 冒烟 `_pause_overlay_projection` 5 断言(余额徽/锻造三行/工坊六行/天赋树概览 == MetaProgression 访问器逐项);结构探针只读巡检=层子树游戏语义信号连接白名单外零连接,合法连接恰 2(关闭钮+关层外发);层内唯一控件=「继续」 | +| 3 | 公共件复用 | ✅ | 全部构成件经 UiKit 工厂(modal_shell/panel_skinned/label/button/keystone_badge)+现行色板 token;badge 工厂件 Q10 甲收编入 ui_kit 并同步迁营地钥石徽(观感零变化);零新素材零新色值零新字体;ui_contrast 12 对含钥石徽深墨/烛光黄新对 | +| 4 | 模态纪律 | ✅ | PauseOverlay 登记进 main.gd 登记册设置层同档、确认弹窗之前(P2 甲);登记序=树序=z 序注记同步换代;带 dim 遮罩;开合走 `_open_modal`/`_close_modal` 单口;`has_open_excluding` 排除式开态查询(只读零副作用);ui_click 守卫 case 绿(他层开着=置灰+emit 兜底拒开) | +| 5 | 重闸全绿+人闸 | ✅(机检半)/⏳(人闸待用户) | 机检读数见 §三;实机目检清单=`design/qa/iter17-pause-human-gate-checklist-2026-10-06.md`(A-H 八节:双屏入口/守卫态/只读性数据正确/零动效与排播副作用观察/与设置面关系/代拍板追认/R4 口径披露/机检佐证),**人闸=用户实机目检** | + +## 二、任务卡完成态(T0-T5) + +| 卡 | 状态 | 提交/落点 | +|---|---|---| +| T0 | ✅(前任) | worktree 建立+基线回填任务书头注(冒烟 783/0·run_all 17 套·probe_gate 默认段绿·F1-F10 实核绿·迭代 15② 交集零命中);HEAD 时点=44328b1 | +| T1 | ✅(前任) | c08a901(组 A:pause_overlay.gd 本体+badge 工厂件收编+营地钥石徽迁移+main.gd 接线)+ fc9eb1e(pause_overlay.gd.uid 边车随批) | +| T2 | ✅(续跑会话收尾) | d44fdd5(组 B:两屏角标+has_open_excluding+守卫处理器与 _sync_pause_entry 单一同步点+四探针断言+冒烟投影断言+任务书 T0 注记随批)。前任中断于代码写完未跑闸态;续跑两处 L1 修正(见 §四)后全闸绿 | +| T3 | ✅(续跑会话) | 无代码改动,闸读数见 §三 | +| T4 | ✅(续跑会话) | `design/qa/iter17-pause-human-gate-checklist-2026-10-06.md` | +| T5 | ✅(续跑会话) | 本报告+06 状态行+roadmap 卡③行+active.md 快照/台账 380 | + +## 三、闸读数(T0 基线 → 批末) + +| 闸 | 基线(T0) | 批末 | 判 | +|---|---|---|---| +| 冒烟(run_smoke 全量) | 783/0 | **788/0**(+5=本批投影断言) | ✅ | +| run_all | 17 套全绿 | 17 套全绿(exit 0) | ✅ | +| ui_input_probe | 默认段绿 | **十七断言 PASS**(+⑯暂停层输入锁:真点击开层/锁期零穿透/关后恢复无残留) | ✅ | +| ui_struct_probe | 默认段绿 | 全屏零红(+pause 结构断言+只读性契约+零动效契约) | ✅ | +| ui_contrast_probe | 默认段绿 | **12 对测点 PASS**(+钥石徽深墨/烛光黄对,≥4.5:1) | ✅ | +| ui_click_probe | 轻闸三件外 | done=true 问题=0(+战斗屏开合守卫 case+地图屏一次性 case) | ✅ | +| ui_layout_probe | — | exit 0(观察型布局树工具,无断言判语) | ✅ | +| doc 三闸 | — | doc_link_audit 绿 / doc_term_audit 绿 / gdd_lint **存量红**(见 §五-2,本批零新增——06/07 两文件本分支零触碰) | ✅(零新增口径) | +| 熵增闸 | — | **存量红**(见 §五-1,主区同红佐证) | 移交披露 | +| MC | — | 零跑(纯呈现零数值改动——两入口铁规) | ✅ | + +## 四、L1 修正记录(T2 续跑首轮探针红两处,均为断言口径、非产品代码缺陷) + +1. **ui_struct_probe 只读巡检**:前任落码为「子树全控件 × get_signal_list() 全集」巡检——首跑把 Godot 容器系统对每控件自动建立的布局生命线连接(child_order_changed/item_rect_changed/visibility_changed/size_flags_changed/minimum/maximum_size_changed → 引擎内部脏标记方法)全数误判非法。修正=收敛为**游戏语义信号名清单**(pressed/button_down/button_up/toggled/text_changed/text_submitted/value_changed/item_selected/gui_input/focus_entered/focus_exited/closed 十二名)——这正是任务书 v1.1「巡检信号名清单 T2 落码时列全进探针」的本意;清单外任何新连接一律红须入册。 +2. **ui_input_probe case⑯ 取格**:原逻辑假设 case⑥ 后域内必余「无旗可挖格」——首跑实测踩中「域恰 2 枚」形态(flag_t 已插旗+dig_t 已揭开后域内零无旗格;旗不可拔=`flag_present` 拒绝在册)。修正=case 挪至全 case 之尾(板面扰动零波及)+域空时前沿移位兜底(真实点击 move 扩大可挖域,复用探针既有 `_pick_frontier` 手法)。修正后一轮全绿,无复发。 + +## 五、存量问题移交(均非本批产生,主区 master 同态) + +1. **熵增闸红:`design/diagrams` 子目录越界**——00712d5(2026-10-05 GDD 系统架构总图批)落籍时新增 `design/diagrams/`(JSON+HTML 两件)但未同批修订 CLAUDE.md Project Layout 与 `repo_structure_audit.py` 白名单,属该批收尾漏步;主区现跑同红已实证。处置建议=用户拍板「diagrams 收编为合法类别」或「两件归位」,随下一文档批或直接小批办理;本批不代修(最小批面)。 +2. **gdd_lint 存量红:06/07 两文件** R3 修订注记/删除线残留/R4 缺版本历史/R7 括号跨度超限——全为历史批次落文注记(本分支对两文件零 diff);doc 三闸「零新增」口径达成。 +3. **registry 存档版本注记滞后 v4→v5**——生涯批(台账 379)已登记移交,本批未触碰 registry,维持在册。 + +## 六、代拍板记录(白名单内决断,commit 留痕,目检可推翻——追认单见目检清单 F 节) + +| # | 项 | 决断 | 出处 | +|---|---|---|---| +| 1 | 关闭钮文案 | 「继续」(紧凑 90 宽返回钮,设置面先例形制) | T1 c08a901 | +| 2 | 层内行序 | 余额徽→属性锻造→道具工坊→天赋树(营地屏同序,MetaTable.FORGE_ATTRS 序) | T1 c08a901 | +| 3 | 角标文案与落点 | 「暂停」;战斗屏=HUD 右下角(角距 16px,debug 角标同形制);地图屏=顶栏 GP 徽右侧 | T2 d44fdd5 | +| 4 | 断言编号与 case 命名 | ui_input case⑯;ui_struct 契约 ⑧只读⑨零动效;冒烟 `_pause_overlay_projection` | T2 d44fdd5 | +| 5 | 只读巡检信号清单 | 十二名游戏语义信号(§四-1) | T2 d44fdd5 | + +## 七、提交哈希清单(worktree 内全部,含前任) + +| 提交 | 内容 | +|---|---| +| c08a901 | T1:暂停层组 A(pause_overlay 本体+badge 工厂收编+营地钥石徽迁移+main 接线) | +| fc9eb1e | T1:pause_overlay.gd.uid 边车随批 | +| d44fdd5 | T2:暂停层组 B(两屏角标+守卫+四探针断言+冒烟投影断言+任务书 T0 注记) | +| (收口提交) | T4+T5:目检清单+本报告+06 状态行+roadmap 卡③行+active.md | + +## 八、遗留人闸项 + +1. **实机目检**(唯一硬人闸):`design/qa/iter17-pause-human-gate-checklist-2026-10-06.md` A-H 八节,重点=双屏入口/守卫态/只读性/零动效/**排播队列排空副作用确认项**(D2——开层瞬间待播明细行瞬时入带,与「暂停」直觉之差,既有各弹层同款行为)。 +2. 存量移交三项(§五),均不阻塞本批。 +3. Esc 快捷通道=已在 06 键盘/手柄条登记(P1 丁裁点),随键盘批统一办理,本批不落码。 + +终态 = worktree 内全部提交,**未合并、未 push**(合并落回 master 由用户/编排会话执行)。 diff --git a/design/qa/iter17-pause-human-gate-checklist-2026-10-06.md b/design/qa/iter17-pause-human-gate-checklist-2026-10-06.md new file mode 100644 index 00000000..1698ccfe --- /dev/null +++ b/design/qa/iter17-pause-human-gate-checklist-2026-10-06.md @@ -0,0 +1,63 @@ +# 人闸目检清单:暂停层只读入口批(迭代 17 卡③)· 2026-10-06 + +> 执行=用户实机。启动:Godot 打开 `game/project.godot` 运行即可;逐项勾选,不过项随批登记返工。 +> 批区间:T0 基线 HEAD=44328b1 → 批末 d44fdd5(T1 c08a901 暂停层本体 / T2 d44fdd5 入口与断言)。 +> 任务书:`design/research/任务书-暂停层只读入口批-2026-10-05.md`(v1.1)。事实源=06-待办清单「解锁界面与钥石呈现的交互形态(残余:暂停层只读入口)」条。 + +## 怎么走到暂停层(操作路径) + +开始冒险 → 选一个战斗节点进战斗 → 战斗屏**右下角**「暂停」小钮(或:打完战斗到关卡地图屏 → 顶栏 GP 徽**右侧**「暂停」小钮)→ 弹出「暂停」只读层 → 看完点「继续」关闭。 + +## A · 双屏入口可达(P1 丁——常驻角标主入口) + +- [ ] A1 战斗屏 HUD 右下角有「暂停」角标,点击直接开层(无过渡动画) +- [ ] A2 关卡地图屏顶栏「暂停」角标可见可点,点击开层;层叠在地图屏之上,地图屏仍可见 +- [ ] A3 主菜单(开始菜单)**无**暂停角标——入口只在对局内出现 +- [ ] A4 冒险结束回主菜单后角标消失(离局熄灭,不误现) +- [ ] A5 暂停层开着时点「继续」原样回战斗屏/地图屏,可继续挖格/选路(无状态残留) + +## B · 入口守卫(他层开着=角标置灰不可点) + +- [ ] B1 战斗中先开图例(或触发三选一/商店/营地等任一弹层)→ 暂停角标置灰,点击无反应 +- [ ] B2 关闭该弹层后角标恢复可点 +- [ ] B3 地图屏自身开着时地图屏角标仍可点(地图屏是常态屏,不算「其他模态」) +- [ ] B4 暂停层自己开着时,再点任一处角标无反应(不可叠开第二层) + +## C · 只读性与数据正确(P3 甲——余额+三柜台进度) + +- [ ] C1 层内唯一可点控件=「继续」关闭钮;无任何购买/切换/滑杆类控件 +- [ ] C2 钥石余额徽与营地屏顶栏钥石徽同形制(烛光黄底+深墨字+钥石图标),数字=当前真实余额 +- [ ] C3 属性锻造三行(攻击/生命/防守族)= 已购等级「Lv n」,与营地柜台一读数一致 +- [ ] C4 道具工坊六行解锁态(未解锁/已解锁/已解锁·能力封存中/已退役)与累计通关进度分数,与营地柜台二一致 +- [ ] C5 天赋树概览=已购节点聚合文案(零已购时显示空态句「尚未购买任何节点」),与营地柜台三一致 +- [ ] C6 层开着时回营地柜台改数值(如买一级锻造)不可行(层无通路);关闭层后重新打开,显示为新值(实时读数) + +## D · 零动效与排播副作用观察(P4 甲 + T4 指定观察项) + +- [ ] D1 层进出均为直开直关——无淡入淡出/滑入等过渡动画 +- [ ] D2 **排播排空副作用**(确认项):战斗明细演播进行中(连续挖格后立刻)开暂停层——待播明细行会被瞬时排空入带(跳过演播节奏,行带直接补完整)。这是与「暂停」直觉的差异点:**本层只是查看层,不冻结战斗演算与演播**(既有各弹层同款行为)。确认此表现可接受,或登记异议 +- [ ] D3 减动效开关开/关两态下,层进出表现一致(本层零动效,开关无新增消费) + +## E · 与设置面的关系(P2 甲——登记同档) + +- [ ] E1 主菜单开设置面:无暂停角标出现(两层面不共存——设置入口在主菜单,暂停入口在对局内) +- [ ] E2 层级遮挡关系抽检:确认弹窗盖在暂停层之上、暂停层盖在图例/商店/营地之上(登记序=树序=遮挡序) + +## F · 代拍板追认单(白名单内已落,目检可推翻) + +| # | 项 | 已落值 | 推翻改法 | +|---|---|---|---| +| F1 | 关闭钮文案 | 「继续」(紧凑 90 宽返回钮,设置面先例形制) | 改「返回」等一词换文案一处 | +| F2 | 层内行序 | 余额徽 → 属性锻造 → 道具工坊 → 天赋树(营地屏同序) | 顺序对调=一处移动 | +| F3 | 角标文案与位置 | 「暂停」;战斗屏=HUD 右下角(角距 16px)、地图屏=顶栏 GP 徽右侧 | 换角/换角距=锚点一处 | +| F4 | 层标题与副题 | 「暂停」+「查看本局钥石与永久强化进度(只读——购买请回营地)」 | 文案一处 | +| F5 | 天赋树空态句 | 「尚未购买任何节点(天赋树在营地柜台三)」 | 文案一处 | +| F6 | 断言编号与 case 命名 | 探针 case⑯/struct 契约 ⑧⑨(机检面,不涉观感) | 仅重命名时改 | + +## G · 数据口径披露(R4 快照口径,机检佐证外的人闸知情项) + +暂停层展示值=**每次开层瞬间**实时读取元进度访问器拼装的快照(余额/锻造三行/工坊六行/天赋树概览七类访问器),快照只活到本次关层,重开重取——层开着期间外部数值不会变动(层内零写通路),不存在「层数字过期」问题。机检侧:冒烟投影一致性 5 断言(展示值==访问器逐项)+ 结构探针只读巡检(层子树游戏语义信号连接白名单外零连接,合法连接恰 2=关闭钮与关层外发)全绿。 + +## H · 机检佐证(已全绿,实机只抽核) + +冒烟 788/0(基线 783/0,+5=本批投影断言)· run_all 17 套全绿 · ui_input 17 断言(含⑯暂停层输入锁)/ui_struct 全屏零红(含 pause 结构+只读巡检+零动效契约)/ui_contrast 12 对测点(含钥石徽新对)/ui_click done=true 问题=0/ui_layout exit0 · doc 三闸零新增(06/07 存量注记红系历史批次落文,本批零触碰;design/diagrams 熵增越界系 00712d5 总图批落籍漏步,主区同红——两存量均已移交登记,非本批产生)· 素材零触碰(零新素材零新色值零新字体,全部构成件=UiKit 工厂件+现行色板 token)· MC 零跑(纯呈现零数值改动) diff --git a/design/qa/ui-audit-2026-10-06/demo/_map_bottom.png b/design/qa/ui-audit-2026-10-06/demo/_map_bottom.png new file mode 100644 index 00000000..277f904f Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/_map_bottom.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/demo_1_start.png b/design/qa/ui-audit-2026-10-06/demo/demo_1_start.png new file mode 100644 index 00000000..cf4df806 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/demo_1_start.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/demo_2_camp.png b/design/qa/ui-audit-2026-10-06/demo/demo_2_camp.png new file mode 100644 index 00000000..96f25437 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/demo_2_camp.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/demo_3_settings.png b/design/qa/ui-audit-2026-10-06/demo/demo_3_settings.png new file mode 100644 index 00000000..f4181b92 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/demo_3_settings.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/demo_4_map.png b/design/qa/ui-audit-2026-10-06/demo/demo_4_map.png new file mode 100644 index 00000000..52876d3d Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/demo_4_map.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/demo_5_battle.png b/design/qa/ui-audit-2026-10-06/demo/demo_5_battle.png new file mode 100644 index 00000000..eadff07d Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/demo_5_battle.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/demo_6_bonus.png b/design/qa/ui-audit-2026-10-06/demo/demo_6_bonus.png new file mode 100644 index 00000000..4a486c8c Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/demo_6_bonus.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/demo_7_shop.png b/design/qa/ui-audit-2026-10-06/demo/demo_7_shop.png new file mode 100644 index 00000000..2e6b8bb4 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/demo_7_shop.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/demo_8_end_defeat.png b/design/qa/ui-audit-2026-10-06/demo/demo_8_end_defeat.png new file mode 100644 index 00000000..5c8f3596 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/demo_8_end_defeat.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/demo_8_end_victory.png b/design/qa/ui-audit-2026-10-06/demo/demo_8_end_victory.png new file mode 100644 index 00000000..5dd47934 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/demo_8_end_victory.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/raw/raw_1_start.png b/design/qa/ui-audit-2026-10-06/demo/raw/raw_1_start.png new file mode 100644 index 00000000..144f7b52 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/raw/raw_1_start.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/raw/raw_2_camp.png b/design/qa/ui-audit-2026-10-06/demo/raw/raw_2_camp.png new file mode 100644 index 00000000..fae045a4 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/raw/raw_2_camp.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/raw/raw_3_settings.png b/design/qa/ui-audit-2026-10-06/demo/raw/raw_3_settings.png new file mode 100644 index 00000000..4b28d36e Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/raw/raw_3_settings.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/raw/raw_4_map.png b/design/qa/ui-audit-2026-10-06/demo/raw/raw_4_map.png new file mode 100644 index 00000000..edecee5a Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/raw/raw_4_map.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/raw/raw_5_battle.png b/design/qa/ui-audit-2026-10-06/demo/raw/raw_5_battle.png new file mode 100644 index 00000000..301b601b Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/raw/raw_5_battle.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/raw/raw_6_bonus.png b/design/qa/ui-audit-2026-10-06/demo/raw/raw_6_bonus.png new file mode 100644 index 00000000..5b34ea50 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/raw/raw_6_bonus.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/raw/raw_7_shop.png b/design/qa/ui-audit-2026-10-06/demo/raw/raw_7_shop.png new file mode 100644 index 00000000..3e04bac9 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/raw/raw_7_shop.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/raw/raw_8_end_defeat.png b/design/qa/ui-audit-2026-10-06/demo/raw/raw_8_end_defeat.png new file mode 100644 index 00000000..5cd97a1c Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/raw/raw_8_end_defeat.png differ diff --git a/design/qa/ui-audit-2026-10-06/demo/raw/raw_8_end_victory.png b/design/qa/ui-audit-2026-10-06/demo/raw/raw_8_end_victory.png new file mode 100644 index 00000000..fee6cc91 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/demo/raw/raw_8_end_victory.png differ diff --git a/design/qa/ui-audit-2026-10-06/pairs/pair_1_start.png b/design/qa/ui-audit-2026-10-06/pairs/pair_1_start.png new file mode 100644 index 00000000..644a5931 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/pairs/pair_1_start.png differ diff --git a/design/qa/ui-audit-2026-10-06/pairs/pair_2_camp.png b/design/qa/ui-audit-2026-10-06/pairs/pair_2_camp.png new file mode 100644 index 00000000..7a029376 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/pairs/pair_2_camp.png differ diff --git a/design/qa/ui-audit-2026-10-06/pairs/pair_3_settings.png b/design/qa/ui-audit-2026-10-06/pairs/pair_3_settings.png new file mode 100644 index 00000000..5ae31141 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/pairs/pair_3_settings.png differ diff --git a/design/qa/ui-audit-2026-10-06/pairs/pair_4_map.png b/design/qa/ui-audit-2026-10-06/pairs/pair_4_map.png new file mode 100644 index 00000000..450f4f09 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/pairs/pair_4_map.png differ diff --git a/design/qa/ui-audit-2026-10-06/pairs/pair_5_battle.png b/design/qa/ui-audit-2026-10-06/pairs/pair_5_battle.png new file mode 100644 index 00000000..81081c12 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/pairs/pair_5_battle.png differ diff --git a/design/qa/ui-audit-2026-10-06/pairs/pair_6_bonus.png b/design/qa/ui-audit-2026-10-06/pairs/pair_6_bonus.png new file mode 100644 index 00000000..a573ca41 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/pairs/pair_6_bonus.png differ diff --git a/design/qa/ui-audit-2026-10-06/pairs/pair_7_shop.png b/design/qa/ui-audit-2026-10-06/pairs/pair_7_shop.png new file mode 100644 index 00000000..b552e685 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/pairs/pair_7_shop.png differ diff --git a/design/qa/ui-audit-2026-10-06/pairs/pair_8_end_defeat.png b/design/qa/ui-audit-2026-10-06/pairs/pair_8_end_defeat.png new file mode 100644 index 00000000..42a3a5e9 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/pairs/pair_8_end_defeat.png differ diff --git a/design/qa/ui-audit-2026-10-06/pairs/pair_8_end_victory.png b/design/qa/ui-audit-2026-10-06/pairs/pair_8_end_victory.png new file mode 100644 index 00000000..ef47c706 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/pairs/pair_8_end_victory.png differ diff --git a/design/qa/ui-audit-2026-10-06/real/real_10_legend.png b/design/qa/ui-audit-2026-10-06/real/real_10_legend.png new file mode 100644 index 00000000..240ed8c5 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/real/real_10_legend.png differ diff --git a/design/qa/ui-audit-2026-10-06/real/real_11_confirm.png b/design/qa/ui-audit-2026-10-06/real/real_11_confirm.png new file mode 100644 index 00000000..86a59405 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/real/real_11_confirm.png differ diff --git a/design/qa/ui-audit-2026-10-06/real/real_1_start.png b/design/qa/ui-audit-2026-10-06/real/real_1_start.png new file mode 100644 index 00000000..804cee6d Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/real/real_1_start.png differ diff --git a/design/qa/ui-audit-2026-10-06/real/real_2_camp.png b/design/qa/ui-audit-2026-10-06/real/real_2_camp.png new file mode 100644 index 00000000..cf44b227 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/real/real_2_camp.png differ diff --git a/design/qa/ui-audit-2026-10-06/real/real_3_settings.png b/design/qa/ui-audit-2026-10-06/real/real_3_settings.png new file mode 100644 index 00000000..342f9a99 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/real/real_3_settings.png differ diff --git a/design/qa/ui-audit-2026-10-06/real/real_4_map.png b/design/qa/ui-audit-2026-10-06/real/real_4_map.png new file mode 100644 index 00000000..9cfcec0c Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/real/real_4_map.png differ diff --git a/design/qa/ui-audit-2026-10-06/real/real_5_battle.png b/design/qa/ui-audit-2026-10-06/real/real_5_battle.png new file mode 100644 index 00000000..a5bb45da Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/real/real_5_battle.png differ diff --git a/design/qa/ui-audit-2026-10-06/real/real_6_bonus.png b/design/qa/ui-audit-2026-10-06/real/real_6_bonus.png new file mode 100644 index 00000000..a8c4f335 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/real/real_6_bonus.png differ diff --git a/design/qa/ui-audit-2026-10-06/real/real_7_shop.png b/design/qa/ui-audit-2026-10-06/real/real_7_shop.png new file mode 100644 index 00000000..55c702d1 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/real/real_7_shop.png differ diff --git a/design/qa/ui-audit-2026-10-06/real/real_8_end_defeat.png b/design/qa/ui-audit-2026-10-06/real/real_8_end_defeat.png new file mode 100644 index 00000000..a085f343 Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/real/real_8_end_defeat.png differ diff --git a/design/qa/ui-audit-2026-10-06/real/real_8_end_victory.png b/design/qa/ui-audit-2026-10-06/real/real_8_end_victory.png new file mode 100644 index 00000000..e8349b3b Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/real/real_8_end_victory.png differ diff --git a/design/qa/ui-audit-2026-10-06/real/real_9_debug.png b/design/qa/ui-audit-2026-10-06/real/real_9_debug.png new file mode 100644 index 00000000..183fd6fa Binary files /dev/null and b/design/qa/ui-audit-2026-10-06/real/real_9_debug.png differ diff --git a/design/qa/ui-audit-vs-demo-2026-10-06.md b/design/qa/ui-audit-vs-demo-2026-10-06.md new file mode 100644 index 00000000..90d41538 --- /dev/null +++ b/design/qa/ui-audit-vs-demo-2026-10-06.md @@ -0,0 +1,96 @@ +# 全屏 UI 审计:实机 vs demo v2.1(2026-10-06 用户令返工) + +> 方法:实机十二屏窗口化程序截图(`game/smoke/ui_audit_probe.gd`,真实信号路径+拟真数据,1280×800 归一)× demo 原型 v2.1 浏览器现截九屏(纯净模式,与 HTML md5 `78ebbee0…` 同代现渲染)。逐屏并排图=`pairs/pair_*.png`(左实机右 demo),原图在 `real/` 与 `demo/`。 +> 输入:用户截图的红砖「放弃」按钮(第二层地图屏,特性持有态)+「底栏重设计是根本解、不是限制按钮」裁决。 + +## 逐屏差距(demo 八映射屏) + +### ① 关卡地图(pair_4_map)——用户点名核心屏,差距最大 + +| # | 差距 | demo | 实机 | +|---|---|---|---| +| 1 | **底栏** | 单条 **~85px** 紧凑亮条:头像+生命轨 \| 属性(含攻击加成副行) \| 本关加成(含持有行) \| 道具槽×2 \| **放弃=小号危险钮 ~90×38** | 亮条 + **放弃=216×50+ 危险钮**;特性持有行无限宽换行把栏爆到 130px+(用户截图实证),全栏跟着涨 | +| 2 | 底栏内容 | 五段齐装(含道具槽与持有行) | 少道具槽、少持有行展示态(特性行爆栏);空态大片留白由放弃钮填充 | +| 3 | 底栏淡墨 | 加成副行用 wood-deep 深棕(亮板上可读) | 特性组用 WARM/WARM_SOFT 暖白系(亮板上 ≈1:1,**不可读——§7.2 违例**) | +| 4 | 背景 | 暖棕渐变聚焦、无纹理平铺 | 全屏石纹平铺(/busy,视觉焦点弱) | +| 5 | 顶栏 | 关卡头与 GP 徽章均有框板(plate 感) | 裸文本浮在纹理上 | +| 6 | 节点图 | 分支树多行多列+连线,分支结构一目了然 | 第一层为单行线性排布(深层有分支但连线视觉弱) | + +**底栏重设计草案(待用户裁决后实施,本批未动代码)**:对齐 demo——①底栏定高 88px 单条,五个信息段一行排齐;②放弃改小号危险钮(~90×38,右端);③特性持有行限宽(固定栏宽内 1-2 行省略+tooltip 全文),杜绝爆栏;④特性组墨色换深墨系(INK_HINT/INK_TITLE)修 §7.2 违例;⑤道具槽段位预留(道具读数出口=台账 352 在册项)。 + +### ② 商店(pair_7_shop) + +demo=**全屏页**(顶部标题+刷新徽+GP 框+池源/涨价规则行)+**竖卡三张**(色带/图标/名/效/价/类型脚注);实机=**战斗屏上的小模态**(dim 0.6)+三横行(名+现价+已购档+购买钮),标题与 HUD 行叠压。→ 即 06 在册 D1(卡片 232×352)/D2(商店全屏化)结构级差异,本次截图实证差距量级。实机独有「已购 N 档」档位语义 demo 无。 + +### ③ 三选一(pair_6_bonus) + +demo=全屏页:标题+**GP 汇总行**+三张竖卡(色带/图标/名/效/类型脚注/**选中✓**)+确认选择钮+免费选择注记。实机=战斗内浮层:标题+三行引导文字(关卡奖励点/从提升池抽…/选择一件带走)+窄条卡(~140×220,无脚注无选中✓)+无确认钮(点卡即选)。 +**新证据(E5 追认)**:demo 的「工兵爆破」卡=**紫带(magic 族)**,实机按 T4a 归 TG_AOE=粉带——demo 自身的 blast 归属与 T4a 判定相反,建议回滚 E5(blast 归回 TG_MAGIC),随用户裁决。 + +### ④ 战斗结算·胜利(pair_8_end_victory;失败态同族) + +demo=全屏 dim(仪式感)+石皮框板卡:标题+三行**两列对齐账目**(战利品/钥石/本关加成)+紧凑主钮「下一节点」;实机=**无 dim**(战斗屏裸露)+素面板+居中堆叠文本行+全宽黄钮「返回」。仪式感与排版差距大。 + +### ⑤ 战斗(pair_5_battle) + +结构对齐度高(舞台/上轮摘要行/AP·RAP 行/左栏/提示条均在位)。差距:①demo 棋盘融进深棕场景、实机=亮米大平板+四周大片空白(观感最大点);②demo 左栏三段齐装(探险者+魔法道具槽有货+本关强化+流派件),实机左栏=探险者+秘宝空槽(强化/流派件编排不同源);③demo 站桩带名牌与头顶 HP 轨更紧凑。 + +### ⑥ 标题屏(pair_1_start) + +1. **主操作语义反转**:demo=「继续上局」黄色主钮(有存档时继续是首动作)、「开始冒险」石钮;实机=「开始冒险」主黄钮、「继续上局」石钮。 +2. 主标题:demo 粗像素体大字+烛光副题装饰线;实机细体。 +3. 背景:demo 暗棕渐变+聚光;实机全铺石纹。 +4. demo 有「玩法速览」牌——**实机已按用户令删除,不回补**。 +5. 立绘/敌人群比例 demo 更大更聚中。 + +### ⑦ 营地(pair_2_camp) + +面板明度关系相反(demo 暗石皮面板浮于暖渐变;实机亮米大板浮于石纹);demo 行带图标/Lv 徽章/价格按钮一格齐,实机行松、工坊行为纯文字行;关闭钮 demo 底部居中、实机左下;实机独有累计通关解锁进度列(功能差异非缺陷)。 + +### ⑧ 设置(pair_3_settings) + +demo 石皮框板+两列对齐行(label 列/控制列)+减动效行;实机素面板+行内流式布局无列对齐+**减动效行缺失**(实机仅 主音量/全屏/结算演出动画 三行——减动效语义由结算行承担与否待核);返回钮 demo 紧凑、实机 216 宽。 + +### demo 无映射的实机屏(三屏,无对比基线) + +调试浮窗(real_9_debug)· 图例(real_10_legend)· 确认框(real_11_confirm)——demo 八屏不含,维持现状,仅存档截图。 + +## 总体结论(差距量级排序) + +1. **关卡地图底栏**(用户点名)——放弃钮尺寸语义错+特性行爆栏+淡墨违例;根本解=按 demo 88px 单条重设计(草案见上)。 +2. **商店/三选一形态级差距**(D1/D2 在册):全屏化+竖卡 232×352——06 已登记待立项,本轮截图把量级坐实。 +3. **结算仪式感**(dim+石皮框板+两列账目)——呈现批量级。 +4. **背景处理全局差距**:demo 暖渐变聚焦、石纹只作局部质感;实机全屏铺石纹——跨屏共通改。 +5. **标题屏主操作语义**(继续上局应为主钮)。 +6. 战斗屏结构对齐度最高,仅棋盘底色处理与左栏编排差距。 +7. E5 证据更新:demo 爆破卡紫带 → 建议 blast 归回 TG_MAGIC(推翻 T4a 的 blast→TG_AOE),随用户裁决。 + +## 批末判定(2026-10-06 /goal UI 对齐批收口——逐项复判全记) + +/goal 令(「将游戏的 UI 对齐 demo 的 UI;完成后自验:ui_audit_probe 逐屏实机截图×demo 浏览器截图,重建并排对比图逐项判定;全部差距闭环或列明不可对齐原因」)执行完毕。判官=documents:visual-judge 两轮独立目检(初判 7 pass/2 partial/1 fail → 修复后复判 4 张全部转 pass),九组并排图已按合后态重摄重建(real/ 与 pairs/ 现内容=最终态)。 + +### 逐屏判定终态 + +| 屏 | 判定 | 闭环内容 | 留档差异(非本轮差距) | +|---|---|---|---| +| ①标题 | **pass** | 主操作语义反转(有档=继续上局主黄钮);背景=暖棕底+石纹 0.14+斜光带+暗角 | demo 玩法速览牌已按用户令删除,不回补 | +| ②营地 | **pass**(用户实机复检后二次修复) | 背景氛围层(可见处石纹微弱+暗角);**用户实机两缺陷修(2026-10-06)**:①底改不透明暖亮档(原 0.45 半透 dim 在石纹降 0.14 后底下对局画面透出——demo #v-camp=bg-bright 实底同款)②三柜 EXPAND_FILL 等宽分屏+行标签截断兜底(原行标签按文字全宽算 min,进度/状态长文案随档变长把三柜总宽撑爆 1248=右柜裁出屏)+状态列 tooltip 兜底 | 面板明度/行密度/关闭钮位=camp ⑦ 遗留(06 留档) | +| ③设置 | **pass** | 两列对齐(label 定宽 140)+返回钮紧凑 90+主音量滑轨实高修(HSlider 槽按 stylebox min 高绘制缺陷) | 减动效行不拆=reduced_motion 单设置同驱 motion_scale+结算演播(语义命名批裁决,demo 两行=同一设置) | +| ④地图 | **pass** | 底栏 85px 定高单条+放弃 90 小钮+特性行限宽截断+深墨;顶栏关卡名框板+GP 烛光胶囊+两极排布;背景暖棕渐变 | 道具槽段位=台账 352 功能欠账(非呈现层) | +| ⑤战斗 | **pass** | 背景氛围层成立;轮次条/上轮摘要结构一致 | 棋盘底板融景、左栏编排、站桩紧凑度=battle ⑤ 遗留(06 留档) | +| ⑥三选一 | **pass** | 全屏页(page_shell 根底档)+D1 竖卡 232×352+类型脚注行+底注 | 稀有度词缺=数据面无投喂(词表随数据批补) | +| ⑦商店 | **pass** | 全屏页(D2)+56px 通栏顶条+爬价规则行(实数投喂 shop_bp_visit_buys×shop_price_a)+D1 竖卡+底注池源 | 「已购 N 档」=实机独有档位语义(功能差异非缺陷) | +| ⑧结算·胜/败 | **pass** | DIM_NIGHT 0.62/0.6 全屏压暗(判官实测亮度 218→97)+两列对齐账行+紧凑主钮 | 账行文案词序差异=Narrator 文案域 | +| 无映射三屏 | 存档 | 调试浮窗/图例/确认框维持现状(demo 无基线) | — | + +### 不可对齐/留档原因清单 + +1. **营地面板明度与行密度、关闭钮位**:camp 布局属 M 批落码形制,重排=独立呈现批量级,本批只落背景氛围层;06 留档。 +2. **战斗棋盘底板**:棋盘视觉=样张 v2 机检冻结域(golden 断言),动底板需过样张闸;06 留档。 +3. **地图道具槽**:道具读数无关卡画面=成文出口(台账 352),功能件非呈现层。 +4. **稀有度脚注词**:候选数据面无稀有度字段投喂,随数据批补。 +5. **减动效独立行**:与结算演出动画=同一设置(语义命名批 T17 裁决合并),拆行=一设置双行重复,不拆。 + +### 闸门记录 + +开工基线冒烟 782/0(4336fd2 净态)→ 每轮轻闸冒烟 782/0+probe_gate --click 全绿 → 批末 run_all 16 套全绿+全探针绿+doc 三闸零新增(2 处 06 陈旧断链系基线随 merge 自愈,非本批引入)→ 合 master 2ca6288 主区复跑(冒烟 782/0+click 全绿+run_all 17 套全绿〔含并行批新落 curio_refresh_paid_test〕)→ 判官修复轮复验全绿。 diff --git a/design/registry/architecture.yaml b/design/registry/architecture.yaml index 6bfc7eb7..ad30407a 100644 --- a/design/registry/architecture.yaml +++ b/design/registry/architecture.yaml @@ -39,6 +39,12 @@ api_decisions: adr: ADR-0005 decision: SCHEMA_VERSION 比较逻辑归 存档序列化(存档版本 > 当前 = 拒读);RNG 快照 = master_seed + 各流 scope 计数器 + 意图/窗口选择序列,切出须在 scope 事件边界外(无在飞 open_stream 实例——战斗结算 编排保证) +core_decisions: + - name: effect_engine_interpreter + adr: ADR-0011(候选——内容数据驱动扩展批 2026-10-06) + decision: 内容效果行为=发布订阅解释器(EffectEngine)——结算管线同步 emit 事件(无队列无异步),内容行以「订阅哪个事件 on / 什么条件 when / 什么效果 do」声明订阅,解释器遍历持有订阅集返回按声明序排列的命中,发射点按组合规则施加;旧 23 挂点枚举与按件 id 特判消亡 + note: 事件/效果类型/条件事实/算子四词表封闭于 EffectEngine 常量(单一事实源,schema 校验引用);遍历序=数据表声明序写死保种子复放;订阅重建=每战 begin_battle(中途获得下一战生效);不做通用拦截器链,改写窗口=判定点就地按声明序施加(R2 裁决) + forbidden_patterns: - pattern: autoload_global_event_bus adr: ADR-0001 diff --git a/design/registry/entities.yaml b/design/registry/entities.yaml index 95014b70..e3cd448a 100644 --- a/design/registry/entities.yaml +++ b/design/registry/entities.yaml @@ -339,7 +339,7 @@ entities: pool_tag: enum {cell, clear_default}(cell = 格面资源单一池——安全格与雷格一视同仁同池同分布;clear_ 命名空间预留、校验拒绝——棋盘生成 elite_ 同款) # 2026-09-15 safe_cell→cell 更名 + active_inject 删除 tier: int [1, 8](knob)——强度档位序数(与当量零耦合) cross_field_validation: 加载期致命族——池空(cell 池 ≥1 正向行 weight>0)· 九型正向行齐备 · 九型负向行齐备(字典完整性——缺任一极性行致命——2026-09-20 八型批、同日 GP+商店房批随九型同步)· clear_default 恒正当量(≥1 活行 ∧ power≥1 ∨ effect_id 非空——行动经济 条款④"清图奖励>0"的行级兑现口径)· percent 行 effect_id 必填/flat 行恒空 · flat 行 power≥1 # 2026-09-15 重写——旧 floor_supply_missing / no_damage_row 随可选类删除失义退役 - mechanics: 格面资源身份 install 期预分配(roll_board 恰一次、行主序、消费 reward_roll 封闭流恰一 scope 批〔格批 + 清图批尾一号〕;全部非封锁格逐一摇定类型——安全格与雷格同池同分布、封锁格不入摇号清单;Ready 后只读、无重掷 API)· **极性不在资源内**——「动作 × 格型」矩阵(开安全格/标中雷 = 正向、开雷格/标错安全格 = 负向、插旗安全格直挖 = 零兑付〔四情况④〕;撤旗动作随插旗不可逆批 2026-09-19 退役)字典定义归 地图战斗资源、路由执行归 战斗结算 授予段(结算队列 + 轮末在场旗判定基线——被挖开的旗不入基线)· 授予期零随机(身份恒定跨轮;换图映射消亡)· 负向恒即时(不入库、无库存语义——库存已随可选类删除下线)· 满层零兑付 = 合法例外 + "奖励未生效"行 · 重复受理幂等(同格重复 grant / 同批次重复 grant_clear → 同一快照/身份 + 诊断;受理/兑付恰一次归 战斗结算×②③) + mechanics: 格面资源身份 install 期预分配(roll_board 恰一次、行主序、消费 reward_roll 封闭流恰一 scope 批〔格批 + 清图批尾一号〕;全部非封锁格逐一摇定类型——安全格与雷格同池同分布、封锁格不入摇号清单;Ready 后只读、无重掷 API)· **极性不在资源内**——「动作 × 格型」矩阵(开安全格/标中雷 = 正向、开雷格/标错安全格 = 负向、插旗安全格直挖 = 零兑付〔四情况④〕;撤旗动作随插旗不可逆批 2026-09-19 退役)字典定义归 地图战斗资源、路由执行归 战斗结算 授予段(结算队列 + 轮末在场旗判定基线——被挖开的旗不入基线)· 授予期零随机(身份恒定跨轮;换图映射消亡)· 负向恒即时(不入库、无库存语义——库存已随可选类删除下线)〔案C 2026-10-06 用户裁:伤害形态正负单据的扣血落地暂存至战斗段末统一执行——账户受理仍在授予段,兑付时点归 战斗结算〕· 满层零兑付 = 合法例外 + "奖励未生效"行 · 重复受理幂等(同格重复 grant / 同批次重复 grant_clear → 同一快照/身份 + 诊断;受理/兑付恰一次归 战斗结算×②③) api: 封闭枚举入口(provisional,实现期 ADR 定名)——roll_board / query_identity(返回 {reward_id, cell_type, tier} 引用级投影;未开格恒可查——秘宝 桥接结构前提;清单外坐标 → null + 诊断)/ grant(同格重复受理幂等 + 诊断)/ grant_clear(同批次重调幂等 + 诊断)/ retire_mapping / equivalence_supply(全活行投影 + tier_min_buff)+ weighted_pick 纯函数面;新增入口 = 接口漂移,新增入口须随附全输入域语义表 # 2026-09-15 删 stock/use/query_inventory/clear_inventory(库存轴下线) mvp_content: 十八行(2026-09-15 格面资源批十行;2026-09-18 伤害道具批增补四行;2026-09-20 格面资源八型批 ambush_double→enemy_alert 换装 + ap_up/ap_drain 两行新增;同日 GP+商店房批 +gp_gain/gp_loss 两行)——格面池正向九行(attack_up / ac_up〔2026-09-16 护甲等级掷点批换装,原 defense_up 减伤 −30% 退役;cell_type defense/权重/档位零改动〕/ spark_aoe / heal_minor / weaken / pierce_strike / magic_bolt〔attack_type 字段载攻击类型轴,查战斗结算克制矩阵、单目标必中全额〕/ ap_up action〔下轮行动点 +1——2026-09-20 八型批新增〕/ gp_gain gp 金币袋〔战斗内 GP 缓冲 +power,兑付编排归 战斗结算、钱包账本归 冒险结构——GP+商店房批新增〕)+ 负向九行纯字典(enrage / off_guard / heal_backlash 直伤〔不经增益状态〕/ enemy_alert blast〔effect_id=alert,敌方护甲等级 +1——2026-09-20 八型批 ambush_double 退役换装〕/ ap_drain action〔下轮行动点 −1〕/ sapped / pierce_snare / magic_recoil〔均主角直伤,不经增益状态〕/ gp_loss gp 破财〔战斗内 GP 缓冲 −power——GP+商店房批新增〕)+ clear_blast(clear_default);类型频次 = 正向行权重合计;tier_min_buff = 2。**2026-09-17 数值压缩批·文档同步——具体数值列(power/weight/power_min/power_max)改指路存根**:现行行值读 `data/tables/reward.tres`(压缩后锚行:spark_aoe power=1 / heal_minor power=[1,1] 定值(原 [2,6] 区间压缩)/ heal_backlash power=1 / clear_blast power_min=8 power_max=18 power=13 中点 / ambush_double stacks=2;类型频次合计 79→67〔heal 权重 15→3〕;2026-09-20 八型批后 67→74——attack 14 / defense 11 / blast 16 / heal 2 / weaken 8 / pierce 8 / magic 8 / 清障炸药 1 / action 6;同日 GP+商店房批九型后 74→76——+gp_gain weight 2) direction_precheck: **刀供给方向预检随可选类删除失义退役(2026-09-15)**——历史读数见底部修订记录;新对账锚 = 行动经济 条款②③ 左臂(正向格面资源当量供给)随格面资源批重推,义务归 难度曲线与Boss 调参批 @@ -373,7 +373,7 @@ entities: ac_roll: 护甲等级掷点(**2026-09-16 护甲等级掷点批定稿,数字即规范**——骰面 roll_faces=20、nat 最大面 crit_roll=20、暴击倍率 crit_multiplier=200、攻击加成 attack_bonus=0〔结构位,元进度/武器附魔未来投值〕、伤害下限 damage_floor=1、流名 armor_roll_stream="armor_roll";判定式:掷 1d20,掷点 ≥ 防守方护甲等级 + 攻击加成 → 全额、低掷按缺口查档〔见 ac_discount_tiers〕,nat20 暴击该基础攻击分量 ×2 且恒全额〔双方同规〕;任何攻击落地伤害 ≥ damage_floor〔**2026-09-20 伤害下限全来源批(用户拍板)**:语义扩为任何来源的伤害落地——攻击双方/棋盘克制直伤/爆裂/地形变异/负向反噬全部收进闸内,伤害取值域 [1, +∞) 不设上限;全局 max(1,·)——零值伤害行语义退役,重档折扣行文本记"被压制"〕;吃掷点分量 = 主角先手攻击普通分量 / 敌方技能基础伤害分量〔= 攻击基线 × 技能伤害倍率——2026-09-16 敌人技能池批改述,原"敌方攻击拍与重击拍基线伤害"退役;无伤害分量的增益技不掷不折〕,棋盘来源伤害〔爆裂 / 清障炸药 / 奖励即时——2026-09-19 充能退役后无充能分量;伏击附加项随 2026-09-20 八型批伏击退役移除〕必中全额不掷;回合内随机、消费 armor_roll 封闭流逐次掷逐次取号,可逐位回放) ac_discount_tiers: Dictionary 缺口档边界→档位值 percent(**2026-09-16 定稿**——roll_tier_bounds=[3,7] · roll_tier_discounts=[75,50,25],档数 = 边界数 + 1;缺口 1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25,档位不设零档〔非归零〕,取整沿全局 floor;EV 参考数〔按判定式复算〕= AC10 期望减免 21.25% / AC11 25% / AC12 28.75%;微调旋钮 = 边界与档位值,不动 AC 值;难度曲线与Boss 生存链区间带宽补扫含暴击档与折扣档、"零红档"验收保留) validation: segment_order 违例致命 · effect_pool_map 悬空致命 · p_perfect 域 {100,200}(2026-09-10 载体域——100 = "完美必须 ≥ 不加倍"机制语义下界,200 = 双份交付)——战斗结算 冒烟三行致命 manifest(segorder_violation / poolmap_dangling / pperfect_oob) - mechanics: 结算段序骨架唯一权威载体(授予→增益/减益→战斗→衰减→判定——2026-09-15 格面资源批五段,item_window/item 两段随可选类删除下线)· **随机口径(2026-09-15 护甲等级批修订)**:结算换算层引入回合内随机(ac_roll 护甲等级掷点——必中非归零、低掷按 ac_discount_tiers 档位折扣),掷点流归属未定〔rng_service 流封闭清单 board_gen/reward_roll/encounter 不含结算内消费——新增流须改代码+P1 支柱评审、复用 encounter 则改"实例化期恰一 scope"绑定,登记 战斗结算「待办」,rng_service 条目随定稿批修订〕· 可回放契约 = **封闭随机流可回放**(同 master_seed 同意图序列 ⇒ 步骤流逐位复现——数据配置 AC-15 落点,原"结算路径零随机 / 步骤流逐行相同"口径随批作废;开战掷区间 / 装板摇号属初始化随机维持不变)· 调用序钉死 按摇定技能行兑现→apply_damage→commit_action(2026-09-16 技能池批改型——原 query_attack→apply_damage→advance 随行为环退役;行型 10 类同批改型:蓄力行 charge → 施放行 cast〔增益技 0 伤害施放、具名技能、不掷不折——原"不兑伏击"语义随 2026-09-20 八型批伏击退役改述〕)· 衰减恒执行含末轮 · 高光双落载体(perfect_sweep P_perfect——2026-09-10 修订为真实双份兑付载体 / 标中旗 highlight 标记)· 流水行协议字段分层(公共 7 字段 + 行类型特有 target/effect_id/pool_value/聚合计数——2026-09-10 评审批修订;行类型封闭枚举 9 类(2026-09-20 伏击上限删除批下线溢出未生效行 overflow)——2026-09-15 格面资源批由 15 类收拢:删道具行 / 已消耗未生效行 / 使用被拒行 / 窗口打开锚行 window_open / 窗口跳过锚行 window_skip 五类随可选类删除下线〔2026-09-15 窗口锚行批曾由 13 扩为 15,同日回撤〕;行来源断言两层豁免集合同批修订 = 行型豁免 {decay, verdict} × 来源类别豁免 {hero_attack, enemy_action, clear_reward},豁免不等于免检——豁免行仍受公共字段与各自语义约束,清单改动须同步 战斗结算 明细流水一节与 battle_log_view 格式化)· **类型化克制结算规格 = 战斗结算 GDD F6(W3 补全,2026-09-14 重算批;2026-09-15 命名统一批;2026-09-19 充能退役批——挖掘期充能/战斗段消费半边废止,加法核点数修正)**——逐分量 armor_effectiveness 查表(奖励直伤道具消费方保留) + mechanics: 结算段序骨架唯一权威载体(授予→增益/减益→战斗→衰减→判定——2026-09-15 格面资源批五段,item_window/item 两段随可选类删除下线)· **随机口径(2026-09-15 护甲等级批修订)**:结算换算层引入回合内随机(ac_roll 护甲等级掷点——必中非归零、低掷按 ac_discount_tiers 档位折扣),掷点流归属未定〔rng_service 流封闭清单 board_gen/reward_roll/encounter 不含结算内消费——新增流须改代码+P1 支柱评审、复用 encounter 则改"实例化期恰一 scope"绑定,登记 战斗结算「待办」,rng_service 条目随定稿批修订〕· 可回放契约 = **封闭随机流可回放**(同 master_seed 同意图序列 ⇒ 步骤流逐位复现——数据配置 AC-15 落点,原"结算路径零随机 / 步骤流逐行相同"口径随批作废;开战掷区间 / 装板摇号属初始化随机维持不变)· 段内伤害序(案C 2026-10-06 用户裁)= 主角先手→逐敌还手→地图伤害账殿后(授予段伤害形态单据暂存、战斗段末按事件序补提交统一结账;主角倒下暂存单整体不兑逐单留「未生效」行)· 调用序钉死 按摇定技能行兑现→apply_damage→commit_action(2026-09-16 技能池批改型——原 query_attack→apply_damage→advance 随行为环退役;行型 10 类同批改型:蓄力行 charge → 施放行 cast〔增益技 0 伤害施放、具名技能、不掷不折——原"不兑伏击"语义随 2026-09-20 八型批伏击退役改述〕)· 衰减恒执行含末轮 · 高光双落载体(perfect_sweep P_perfect——2026-09-10 修订为真实双份兑付载体 / 标中旗 highlight 标记)· 流水行协议字段分层(公共 7 字段 + 行类型特有 target/effect_id/pool_value/聚合计数——2026-09-10 评审批修订;行类型封闭枚举 9 类(2026-09-20 伏击上限删除批下线溢出未生效行 overflow)——2026-09-15 格面资源批由 15 类收拢:删道具行 / 已消耗未生效行 / 使用被拒行 / 窗口打开锚行 window_open / 窗口跳过锚行 window_skip 五类随可选类删除下线〔2026-09-15 窗口锚行批曾由 13 扩为 15,同日回撤〕;行来源断言两层豁免集合同批修订 = 行型豁免 {decay, verdict} × 来源类别豁免 {hero_attack, enemy_action, clear_reward},豁免不等于免检——豁免行仍受公共字段与各自语义约束,清单改动须同步 战斗结算 明细流水一节与 battle_log_view 格式化)· **类型化克制结算规格 = 战斗结算 GDD F6(W3 补全,2026-09-14 重算批;2026-09-15 命名统一批;2026-09-19 充能退役批——挖掘期充能/战斗段消费半边废止,加法核点数修正)**——逐分量 armor_effectiveness 查表(奖励直伤道具消费方保留) closure_receipt: 行动经济 F1 条款①②③⑤闭合 owner = 战斗结算(2026-09-10 F0–F5 首版验算完成——F0 瞬时当量口径 + m_w 值域裁决见 status_effect.anchor_warning;条款③ clause3_supply 闭合回执;条款⑤ clause5_dominance 条目;actuals p10 交付后强制重跑) referenced_by: [HUD框架, 战斗表现, 音频, 教学引导, 难度曲线与Boss] @@ -600,7 +600,8 @@ entities: lifecycle: 候选池构建(冒险开局一次)→ 在渠道上出现(三选一形式)→ 选定(自池中剔除不再复现;选到「专长获取」时立刻再开一次专长 N 选一)→ 持有生效或即时兑付 → 冒险以任一结局收束时整集清零;断点记录持有集与剩余候选池 acquisition: 三条渠道的**目标分工(交叉开关,v2.2 收口)**——专长:主渠道=**关底 Boss 击败=通过这一关**(**唯一直接给的渠道**;不新增节点类型、挂 Boss 节点兑付面;末关的 Boss 击败后不给——无后续关卡可消费)。**兑付频率**:奇物不是打一场战斗给一份,关内普通战斗节点不给任何奇物,一关之内只兑付一次(奖励节点与商店同理,各节点各自兑付),奖励节点与商店**只给「专长获取」门票**——**拿到即开、不可囤积**,不进持有集(选中/买下同一时刻开专长三选一;可囤积会让玩家无限推迟关键决策,且把持有物语义从后门放回)。门票归专长(给的是一件以前不会的事,只是把选择推迟)。罕见**只在概率不在价格**,一局限购一次,**计步保底**:连续 5 次三选一展示未出现→第 6 次必出(计数=展示次数、刷新重展计入、奖励节点与商店共享、触发清零、限购用满后不再触发);属性值提升:奖励节点 + 商店(口径「训练」);资源:奖励节点 + 商店(**金币除外、回复在内**);后两类均不出现在 Boss 渠道。形状=「一块独占 + 两块并行」,专长靠打赢、其余靠抽取与购买。**默认原则**:除已登记的特殊规则外,分类内的东西都要在它该出现的渠道池子里——任何不出现都须登记理由**现行实现三渠道共用同一候选池、三类混编**,分工尚未实施,登记于 06-待办清单 BD 分层条目 boundary: 判据走生命周期——**一次冒险内获得、冒险收束即清零的是奇物;跨冒险跟着你走的是魔法道具**。奇物永不进同调位,魔法道具永不进当局随机渠道;与元进度天赋树两层互不代位 - data_sources: feats.tres(专长定义行)/ adventure.tres(属性值提升与资源的种类配额与值)/ shop.tres(货架取舍) + data_sources: feats.tres(专长定义行——效果声明=triggers 订阅行〔订阅哪个事件 on / 什么条件 when / 什么效果 do〕,旧挂点+效果字典形态 2026-10-06 内容数据驱动扩展批退役)/ adventure.tres(属性值提升与资源的种类配额与值)/ shop.tres(货架取舍) + effect_declaration: **效果行为=事件订阅(2026-10-06 内容数据驱动扩展批换代)**——事件/效果类型/条件事实/算子四词表封闭于效果引擎 `game/core/effect_engine.gd`(事件 16 发射点 / 效果类型 24 / 条件事实 9 / 算子封闭;四词表出域=加载期校验致命);组合规则六族(加法/乘法/任意真/按序改写/按序消耗/即时执行)为效果类型必填属性;遍历序=数据表声明序写死保种子复放;订阅重建=每战开始从持有集/装备集重建(中途获得下一战生效) charge_defect: **charge 档二不可达登记(T7 奇物批迭代15②——数据缺陷留痕,白名单4授权「只登记不硬补」)**:feats.tres 现行 charge 标签仅 3 件(<4),档二激活要求同标签 ≥4 件——charge 流派永远达不到档二,即使获取次数无限。补哪几件系内容裁决,转独立内容批(硬补占位件已否决);档二可达性复裁=第四关内容批落地后实测(C2 待裁点)。 pool_architecture: **池架构=总池-渠道子集(多级缓存形,用户 2026-10-05 裁 T6 时确立)**:总池=单一令牌库(TIER_QUOTAS 70/25/3=98 + 专长活行追加);三渠道按分类白名单从总池取**子集视图**(关底 Boss 击败=只出专长子集;奖励节点/商店=属性值提升+资源子集,专长只以门票形态出现)——渠道不各建物理池,白名单在候选视图层生效(T4 过滤式)。配额挤占重裁:heal rare 5→3、refresh rare 0→2(方案B,总量 98 锚不变;用户裁「渠道从总量里拿子集」时同步裁)。 refresh_boundary: **刷新次数边界写死(T6 奇物批迭代15②——防后续混淆结算)**:「商店独立刷新机制」(shop_refresh_free/shop_refresh_cost_base/shop_refresh_cost_curve 三旋钮——进店后的免费次数+GP 付费刷新)系**商店自身服务**,作用对象=货架重抽;奇物 refresh 件(资源类成员,作为奇物授予的重抽次数)作用对象=**三选一界面**(兑付即 _attune_refresh_x 增量,X 余量=三选一重抽次数),不进加算区(§2.2 加算区四轴不含它)。两者不是一回事,禁止混算。 @@ -815,7 +816,7 @@ entities: # ④ status_effect:defense_up 行退役换装 ac_up(护甲强化 +1 ac ×2 层,unit=ac 新量纲,不入任何池); # ⑤ reward_table:defense 型正向行 defense_up → ac_up(显示名暂定"护甲强化",权重/档位/cell_type 零改动); # ⑥ settlement_table.effect_pool_map 删 defense_up 键(hero_defense 池现仅 off_guard 正向行——×0 免伤墙结构面消失)。 -# 2026-09-16 敌人技能池批(用户拍板 2026-09-16,设计权威 docs/proposal-skill-pool.md v1.0——与 敌人系统 v0.5 / +# 2026-09-16 敌人技能池批(用户拍板 2026-09-16,设计权威 proposal-skill-pool.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 技能池节〕 v1.0——与 敌人系统 v0.5 / # 战斗结算 v0.11 / 增益状态 v0.5 / 难度曲线与Boss v0.6 / 战斗表现 v0.3 / HUD框架 v0.4 / 数据配置 / 03-内容规模 / # 02-系统清单 / 关卡节点地图 / 06-待办清单 / ux battle.md / architecture.md / 切片代码同批): # ① rng_service 增流 behavior_roll(开轮摇号——回合内随机、时点前置到每轮开轮;派生向量 = spawn_index×1000000 + @@ -901,7 +902,7 @@ entities: # 五档百分比词表退役);enemy/hero armor_type 两行注记同步; # ③ 切片同批:settlement.tres 九格/settlement_table.gd 域与哨兵/damage_math 两函数改加法/battle_orchestrator # _counter_delta 改名与回退 0/smoke 矩阵断言改形;元进度投值语言锁 flat;GP 提案档位词汇免议注记落 06 清单。 -# 2026-09-19 技能池新效应词汇批(用户拍板——设计权威 docs/proposal-status-vocabulary.md v1.0;与 敌人系统 / +# 2026-09-19 技能池新效应词汇批(用户拍板——设计权威 proposal-status-vocabulary.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 词汇表/增益状态.md〕 v1.0;与 敌人系统 / # 战斗结算 / 增益状态 / 06-待办清单 / 切片代码同批;06 清单「技能池新效应词汇批」解决移除): # ① skill 条目:type 枚举三→四(+debuff 妨害——多段命中经用户拍板退役不入枚举);新增 rider_mult 字段 # (必中附加分量——固定值伤害改形:不通用/跨关稀释析出,价位锚 0.75 = AC 折扣档最轻档);池纪律 + diff --git a/design/research/任务书-M4数值批-2026-10-05.md b/design/research/任务书-M4数值批-2026-10-05.md index 51f90d57..8bf05495 100644 --- a/design/research/任务书-M4数值批-2026-10-05.md +++ b/design/research/任务书-M4数值批-2026-10-05.md @@ -1,7 +1,7 @@ -# 任务书:M4 数值批(v1.0 定稿,待发令开工) +# 任务书:M4 数值批(v1.2 定稿——案C 增补+联审修订,开工门=U0 用户发令) -> 成文 2026-10-05。**性质**:四件数值收口合一批 + 两件读数义务随批跑 + 一件既有探针红灯顺路修复。 -> 归属迭代:**迭代 17 · 卡②**(迭代 17 四卡:①敌人表行集多行批 ②本批 ③暂停层只读入口批 ④生涯界面设计立项)。 +> 成文 2026-10-05,v1.2 修订 2026-10-06(五任务书联审:P3 转正顺延、检算工具入留痕、T3/T5 跑批并账、06 死数字补登记、R5/F19 调停、估时勘误)。**性质**:四件数值收口合一批 + 两件读数义务随批跑 + 一件既有探针红灯顺路修复。 +> 归属迭代:**迭代 18**(2026-10-06 用户令自迭代 17 卡②移出独立成批——不影响迭代 17 在飞开发;迭代 17 余卡=⓪内容数据驱动扩展 ①敌人表行集多行批 ③暂停层只读入口批 ④生涯界面设计立项)。 > 满足 loop engineering 八件(见 §9 逐条对照)。 > 🔴 **铁规前置(违反即返工)**:①本批一切 MC / verifier 跑批**发起前须用户批准**(先推算后重跑铁规 + MC 触发细则:数值验证场景才触发,四档硬闸在册);②**调参联动禁独立调参网络**——行动经济 F1 条款3 v0.8 已立 β 带 [1.2,2.5] 逐 u 检算口径,任何数值调整须按调参联动表同窗检算,禁单参数孤立改。 @@ -21,9 +21,9 @@ | 2 | 读数义务两件履行 | T6 MC 四带复算读数出报告;T7 技能池节奏重测读数出报告(均须用户批准后发起) | | 3 | damage_probe A1 转绿 | 「fresh 池 Boss 21 拍攻击链」断言修复或通过断言换代诚实关闭(换代须呈报依据),damage_probe 七项全绿 | | 4 | 文档三类同步 | 凡动数值:`.tres` + GDD 正文表格 + registry entities.yaml + 06 状态行四类同步(doc-conventions §5 双写失同步禁止条款) | -| 5 | 重闸全绿 + 人闸完成 | 冒烟全量(基线 T0 重测回填)· run_all 十五套件 · doc 三闸零新增 · 熵增闸 PASS;provisional 追认清单与读数报告交付用户 | +| 5 | 重闸全绿 + 人闸完成 | 冒烟全量(基线 T0 重测回填)· run_all 十七套件 · doc 三闸零新增 · 熵增闸 PASS;provisional 追认清单与读数报告交付用户 | -> ⚠️ **基线纪律**:起草时点 master 头 = 413c936,冒烟基线 772/0,run_all 14/15(reward_overlay_test 红 = 迭代 15② 奇物批遗留基线红,与本批无关,对照口径 = 零新增)。T0 重测回填,禁止沿用本文数字。 +> ⚠️ **基线纪律**:起草时点 master 头 = 413c936,冒烟基线 772/0,run_all 14/15(reward_overlay_test 红 = 迭代 15② 奇物批遗留基线红,与本批无关,对照口径 = 零新增)。**v1.1 增补时点(2026-10-06)master 头 = 7d3e88fc,冒烟 783/0,run_all 17 套全绿——案C 地图伤害账殿后批已换代每轮结算行为序,本批一切 MC 读数按新行为首采**。T0 重测回填,禁止沿用本文数字。 ## 2. 现状事实(代码级考证,2026-10-05 起草时点;T0 须自行复核) @@ -33,8 +33,9 @@ |---|---|---| | F1 | 现行表值 | `status_effects.tres`:`weaken` magnitude **−65**(percent)· `duration_settlements` **2** · `stack_policy` refresh(叠层改续时语义已定案,2026-09-18 盘问会话 + 结构修复批落地)· max_stack 16(续时语义下结构性不触顶) | | F2 | 欠账内容 | duration 终值(现值 2 为临时值)+ 当量锚复算(「标中旗必须比随便挖一格更值」的当量终值,战斗结算与地图战斗资源共裁) | -| F3 | 取值域 | [60,85](06 条目登记,源自旧体系裁决);复算须按 β 带 [1.2,2.5] 逐 u 检算口径(行动经济 F1 条款3 v0.8) | +| F3 | 取值域 | [60,85](06 条目登记,源自旧体系裁决);复算须按 β 带 [1.2,2.5] 逐 u 检算口径(行动经济 F1 条款3 v0.8)。⚠️ **带有效性未复核(v1.2 补)**:[60,85] 是案C 行为换代+收窄批两层变更前的旧裁决产物,weaken 计入收益侧、magnitude 越大 β 越高——T0 推算稿须给出「β 约束下的 magnitude 可行子区间」,若 85 处逐 u 破带则可行带收窄,按 L3 呈报用户裁带,禁默认全带可用 | | F4 | 关联新变量 | 2026-10-05 判式换代批已投 `flag_hit_premium=125`(provisional,旗溢价 ×1.25)——削弱当量复算须把旗溢价纳入同窗检算(难度曲线与Boss 调参联动表已登记 δ↔p_perfect↔ap_per_round 联动面) | +| F4b | 检算工具载体(v1.2 补) | 当量锚/β 带复算的常设重跑入口 = `tools/clear_dominance_audit.py`(行动经济.md:134 明文),该工具 **51 行硬编码 `WEAKEN_PCT = 65`**——magnitude 投到非 65 值而工具常量不同步,投值前后全部复算读数按旧值错算。**工具常量随投值同步**是 T1 完成判据之一,且该文件入 §10 留痕清单、常量刷新入四类同步面 | ### 2.2 件② 调参批数值点评(06「调参批数值点评登记」条) @@ -56,7 +57,7 @@ | # | 事实 | 读数 | |---|---|---| -| F11 | 柜台定价现值 | `meta.tres` forge_products 三行:`base_price` **20** · `price_ratio_pct` **150**(血+3/攻+1/甲+1 不变)——⚠️ **06 条目文本写「base 10 公比 150」,与表值不符**:2026-09-22 数值设计批(关卡成长×永久成长对齿)已把 base_price 10→20,06 条目文本滞后未刷新,本批随件④收口时一并刷新该句 | +| F11 | 柜台定价现值 | `meta.tres` forge_products 三行:`base_price` **20** · `price_ratio_pct` **150**(血+3/攻+1/甲+1 不变)——⚠️ **06 条目同一句有两处死文本**(v1.2 补全):「base 10 公比 150」(2026-09-22 数值设计批已 10→20,文本滞后)+「Boss 两档 L1 5/1·L2 8/2·L3 12/3」(迭代 10 重锚前死数字,现值 3/3/3·2/2/3)。T4 判据「06 文本滞后句刷新(F11)」按两句全刷执行,只刷 base 句出口后 06 仍带死值 | | F12 | Boss 两档现值 | 同 F7(首杀 3/3/3 · 复杀 2/2/3,provisional);剩余面 = 定价数字与 Boss 两档**终值**投定 | | F13 | 工坊六件 | 阈值 1/1/2/2/3/3 按总通关次数(channel=total_clears)已落地;06 注记「钥石直购价随定价批后补」——直购价是否立列随批裁(P4) | | F14 | 换算率 | conversion 三行已投 6/9/1(2026-09-23 标定批,冒险条件口径实测)——不在本批范围,除非重标读数破带 | @@ -65,10 +66,18 @@ | # | 事实 | 读数 | |---|---|---| -| F15 | 读数义务 A | **T6 MC 四带复算**(初始展开收窄批后置,2026-10-05 落籍时按切分预案挂 07 现行义务):收窄批(剃雷域 r=1 + R₀≤cap 重掷闸)后的四带通关率复算 | -| F16 | 读数义务 B | **T7 技能池节奏重测**(07 现行义务,技能池执行批挂单):≥50 distinct seeds 节奏重测,过涝判读带 = Boss 半血后特殊技密度上探;S2 过涝越带 → 解锁技权重/倍率回调**呈报禁自动调** | +| F15 | 读数义务 A | **T6 MC 四带复算**(初始展开收窄批后置,2026-10-05 落籍时按切分预案挂 07 现行义务):收窄批(剃雷域 r=1 + R₀≤cap 重掷闸)后的四带通关率复算。⚠️ **与 F8 同一测量(v1.2 并账)**:件③「通关率带重扫」与本义务口径同为现行构建四带通关率(balance_verifier 冒险段派生),**一次跑批双认账**,禁两个卡各跑一遍(R3 先推算后重跑)。另:07「现行义务」节尾还有「真人侧读数换 81000 锚补算 k 终值」一段,是否随 T5 履行 T0 呈报定性,禁默认顺延 | +| F16 | 读数义务 B | **T7 技能池节奏重测**(07 现行义务,技能池执行批挂单):≥50 distinct seeds 节奏重测,过涝判读带 = Boss 半血后特殊技密度上探;S2 过涝越带 → 解锁技权重/倍率回调**呈报禁自动调**。⚠️ **判读带换代复核(v1.2 补)**:判读带挂单于 2026-10-05(案C 之前),案C 移动地图伤害兑付时点 → 战斗轮数与 Boss 血量曲线移位 → 「半血后」窗口与密度分母都漂——T7 执行前先复核判读带在案C 新行为下是否仍有权,失权即停机呈报重立带 | | F17 | 顺路修复 | **damage_probe A1 红灯**(`smoke/damage_probe.gd:72`「A1 攻击链健康(fresh 池·掷点口径):非蓄力拍 d_tick = max(下限, 掷点档位基线+附加)」)——判式换代批出口报告 §四.7 在册:**改前既有红**(26374ac 复跑同败,非任何在册批引入),原登记「归独立 mini-loop 处置」,现按用户令编入本批顺路修复 | +### 2.6 案C 行为换代事实(v1.1 增补——2026-10-06 起草时点后发生) + +| # | 事实 | 出处 | +|---|---|---| +| F18 | **每轮结算行为序换代**(案C,用户裁 git 7ef18ac/58dfb56,台账 371):①授予段只兑非伤害账(资源/增益/治疗/行动点/GP/警觉;伤害形态单据暂存)→③战斗段=主角先基础攻击→敌人按生成序逐个还手→**地图伤害账殿后**统一结账;主角死于敌方还手→暂存单整体不兑逐单留「未生效(主角已倒下)」行(forfeit=hero_down)。事实源=战斗结算.md **v0.26** | 战斗结算.md v0.26 / 台账 371 | +| F19 | **旧行为 MC 锚与对拍锚全数作废**(06 条目 2026-10-06 登记)——「授予段伤害致死短路先手」路径消失,旧锚所锚行为已不存在;重锚前旧读数只作历史存档,不得与新行为对拍。四带复算跑批本就未发起,零沉没成本——直接按新行为首采立新锚 | 06-待办清单 2026-10-06 条 | +| F20 | 难度走向预判(供 T6 读数判读参照,非结论):案C 使主角更安全——挖掘期踩雷/反噬不再能先手致死+主角保底先手,同表值下通关率预期上行;若四带复算越带,调参方向归 T2/复算后按 R1 呈报禁自动调 | 本任务书判读框架 | + ## 3. 范围边界(🔴 不可越界) | 禁项 | 理由 | @@ -79,6 +88,7 @@ | **双写失同步禁止** | 动任何数值必须 `.tres` + GDD 正文 + registry + 06 状态行四类同步,缺步即视为未完成(doc-conventions §5) | | **非数值面零触碰** | 本批不改机制、不改呈现、不改存档 schema | | **迭代 15② 奇物批不交叉** | 渠道分离与入池实施段归迭代 15 继续完成,与本批无关;reward_overlay_test 基线红归②批处置,本批只守零新增 | +| **与迭代 17 卡⓪/卡① 的交叠声明(v1.2 补)** | 卡⓪重写 battle_orchestrator 事件挂点+授予/兑付分支、卡①放开行集多行改遭遇构成——两者都在本批 T6 damage_probe A1 归因、对拍锚重推导、四带通关率分母的同文件同管线上。时序安全(本批前置=迭代 17 出口),但:①A1 红灯归因与修法在卡⓪落地后可能变样,T6 执行时按卡⓪出口报告重对账;②T0 事实表复核清单含「按卡⓪/卡①出口报告重取受影响事实」,禁照抄本文行号 | ## 4. 设计契约(🔴 触碰 = L3 停机) @@ -88,23 +98,23 @@ | 🔴 R2 | **联动检算**:每个投值点附 β 带逐 u 检算行(u=1/2/3),破带不擅调、呈报 | 投值 commit 随附检算 | | 🔴 R3 | **先推算后重跑**:所有读数先给静态推算/预期区间,批不准不动模拟器 | 读数报告含推算节 | | 🔴 R4 | **provisional 纪律**:新投值一律 provisional 标注并归出口追认清单;摘除 provisional 须读数支撑 | 出口追认清单 | -| 🔴 R5 | **改前锚留盘禁补跑**:凡改前/改后对比,改前锚同 seed0 配对留盘,禁补跑禁覆盖 | sim-data 锚清单 | +| 🔴 R5 | **改前锚留盘禁补跑**:凡改前/改后对比,改前锚同 seed0 配对留盘,禁补跑禁覆盖。**口径调停(v1.2)**:本批合法的「改前锚」= T0 按案C 新行为新立的锚(F19:sim-data 在盘旧锚全作废、只作历史存档)——禁拿在盘旧锚充当改前配对,两节字面冲突以此为准 | sim-data 锚清单 | ## 5. 任务卡(逐卡一个 loop 轮次) | 卡 | 内容 | 估时 | 轻闸 | 完成判据 | |---|---|---|---|---| -| **T0** | 开工:worktree + 基线重测回填 + §2 事实表复核(重点 F1/F7/F11/F17)+ 四件欠账的静态推算稿(每件先给推算结论与建议值区间) | 0.75d | 冒烟基线 | 推算稿四件齐 + 基线回填;推算结论呈用户并**请批跑批计划**(R1/R3) | -| **T1** | 件①削弱终值:duration 终值与当量锚复算(推算 → 用户批跑批 → 投值)+ 文档四类同步 | 0.75d | 冒烟 | β 带检算行随 commit;provisional 归出口追认 | +| **T0** | 开工:worktree + 基线重测回填 + §2 事实表复核(重点 F1/F7/F11/F17/F19 + 按卡⓪/卡①出口报告重取受影响事实)+ 四件欠账的静态推算稿(每件先给推算结论与建议值区间;**件①含 β 约束下的 magnitude 可行子区间推算**——[60,85] 带有效性复核,v1.2 补)+ 对拍锚按案C 新序重推导(F19) | 1.0d | 冒烟基线 | 推算稿五件齐(含对拍锚按案C 新序重推导+可行子区间)+ 基线回填;推算结论呈用户并**请批跑批计划**(R1/R3) | +| **T1** | 件①削弱终值:duration 终值与当量锚复算(推算 → 用户批跑批 → 投值;**投值值非 65 时同步刷新 `tools/clear_dominance_audit.py` WEAKEN_PCT 常量**,v1.2 补)+ 文档四类同步 | 0.75d | 冒烟 | β 带检算行随 commit;工具常量同步或值=65 的说明随 commit;provisional 归出口追认 | | **T2** | 件②数值点评五条逐条裁决呈报(每条:维持/调整 + 依据;调整项推算先行) | 0.75d | 冒烟(动值轮) | 五条全有结论落文(难度曲线与Boss.md 或对应正主文档 + 06 状态行) | -| **T3** | 件③联动欠账:钥石产出重标(F7 读数推算 + 现行产出结构对账)+ 通关率带重扫(F8,须用户批跑批) | 1.0d | 冒烟 | ①②两条 06 状态行收口;重扫读数落报告 | -| **T4** | 件④定价剩余面:柜台定价数字终值 + Boss 两档终值(推算 → 批跑 → 投值)+ 06 文本滞后句刷新(F11) | 0.75d | 冒烟 | meta.tres 终值落 + 四类同步 | -| **T5** | 读数义务两件:T6 MC 四带复算 + T7 技能池节奏重测(用户批准后发起,可与其他卡窗口期并行等结果) | 0.75d | — | 两份读数报告落 design/qa/;S2 越带按呈报口径处理 | +| **T3** | 件③联动欠账:钥石产出重标(F7 读数推算 + 现行产出结构对账)+ 通关率带重扫(F8——**与 T5 的 T6 四带复算并账,一次跑批双认账,v1.2**;须用户批跑批) | 1.0d | 冒烟 | ①②两条 06 状态行收口;重扫读数落报告 | +| **T4** | 件④定价剩余面:柜台定价数字终值 + Boss 两档终值(推算 → 批跑 → 投值)+ 06 文本滞后句刷新(F11,**两句全刷**——base 句+Boss 两档死数字句) | 0.75d | 冒烟 | meta.tres 终值落 + 四类同步 | +| **T5** | 读数义务两件:T6 MC 四带复算(案C 新行为首采——旧锚全作废,新锚自本批立,F19;**与 T3 重扫并账同一次跑批**)+ T7 技能池节奏重测(用户批准后发起,判读带先按 F16 换代复核;可与其他卡窗口期并行等结果) | 0.75d | — | 两份读数报告落 design/qa/;S2 越带按呈报口径处理 | | **T6** | damage_probe A1 修复:归因(fresh 池 21 拍攻击链判式与判式换代后口径对账)→ 修探针或修判式换代断言 | 0.5d | damage_probe 七项 | 七项全绿;若判断言换代,换代依据落报告 | | **T7** | 重闸 + 人闸材料:全量闸 + provisional 追认清单 + 读数报告三件打包 | 0.5d | 重闸全绿 | §1.2 判定 5 | | **T8** | 收口五步 + 06 四条状态行收口 | 0.25d | — | 收口五步齐 | -> 估时合计 ≈ **6.0d**,显著超软时间盒 3 日上限——知情预挂:**天然切分点 = T4 末**(段一 T0-T4 四件欠账 ≈4.25d / 段二 T5-T8 读数与修复 ≈2.0d);超限即按 L3 呈报切点,不擅切。读数跑批的墙钟时间不计估时(用户批准后后台跑)。 +> 估时合计 ≈ **6.25d**(v1.1 增补 +0.25d),显著超软时间盒 3 日上限——知情预挂:**天然切分点 = T4 末**(段一 T0-T4 四件欠账 ≈**4.25d**——v1.2 算术勘误:1.0+0.75+0.75+1.0+0.75=4.25,v1.0/v1.1 写 4.5 系加总错误且已传播进 roadmap,同批修正 / 段二 T5-T8 读数与修复 ≈2.0d);超限即按 L3 呈报切点,不擅切。读数跑批的墙钟时间不计估时(用户批准后后台跑)。 ## 6. 验证闸 @@ -123,6 +133,8 @@ ### P1 · 削弱 duration 终值候选(推算后回填,起草期先呈框架) +**✅ 裁决(2026-10-06 用户令):案甲**——维持 2 场结算;magnitude 终值由 T1 当量锚复算在 [60,85] 带内落定,带由用户授权、具体值随出口 provisional 追认。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| | 甲 | 维持 2 场结算,magnitude 在 [60,85] 内按当量锚复算终值 | 改动面最小;续时语义下 2 场已覆盖大多数战斗长度 | @@ -133,6 +145,8 @@ ### P2 · 数值点评①②的「不对称是否有意」裁决方向 +**✅ 裁决(2026-10-06 用户令):案甲**——承认不对称有意(奖励厚于惩罚),落文为设计立场,零数值改动。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| | 甲 | 承认不对称是有意(奖励厚于惩罚),落文为设计立场 | 与「奖励驱动挖掘」的现行体验方向一致;零数值改动 | @@ -141,14 +155,18 @@ ### P3 · 钥石产出重标的终值方向 +**✅ 改裁(2026-10-06 二裁,用户令,Q4 甲):维持案甲方向、转正时点顺延**——首杀 3/3/3 · 复杀 2/2/3 维持、摘 provisional 的时点改为 **T3/T5 重扫读数到账且带内后随出口追认**。改裁依据:一裁的支撑读数「迭代 10 四代锚钥石 1.38/3.01」出自 F19 宣告全数作废的旧行为锚(且先于收窄批一层),按本批自家契约 R4「摘除 provisional 须读数支撑」,该依据在重扫读数到账前不成立;且 per-run 钥石收入=通关率×掉落值,案C 后通关率预期上行(F20),产出账须按新行为读数重验。**零改动落码不变,变的只是摘 provisional 的合法时点。** + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| -| 甲 | 维持首杀 3/3/3 · 复杀 2/2/3 转正 | 现行读数(迭代 10 四代锚钥石 1.38/3.01)未显示失衡;零改动 | +| 甲 | 维持首杀 3/3/3 · 复杀 2/2/3,转正顺延至重扫读数到账且带内(二裁生效) | 零改动;读数支撑面按 R4 合法补齐 | | 乙 | 按定价终值(P4 联动)倒推产出重锚 | 产出与定价同账收口最彻底;代价是定价未定前产出无法先定,两裁点强耦合 | | 丙 | 精英掉落行实装并纳入产出账 | 淘汰候选:精英属 P2 内容承载(06 已裁不设精英编码位),本批不引新内容轴 | ### P4 · 工坊六件是否立钥石直购价 +**✅ 裁决(2026-10-06 用户令):案甲**——维持总通关次数阈值单一渠道,不立直购价。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| | 甲 | 维持总通关次数阈值单一渠道,不立直购价 | 门槛语义清晰(玩得多自然解锁);零新定价面 | @@ -186,5 +204,16 @@ 4. design/gdd/systems/排雷循环/行动经济.md F1 条款3 v0.8(判式换代后现行口径) 5. design/gdd/07-体验验证清单.md「验收读数重测义务」节(T6/T7 挂单原文) 6. design/qa/m4iter14-mineq-exit-report-2026-10-05.md §四.7(damage_probe A1 改前既有红的在册登记) -7. design/qa/m4iter14-narrow 相关读数与 design/qa/m3-iter10-clear-rate-readings-2026-10-01.md(重扫基线) +7. design/qa/m4iter14-narrow 相关读数与 design/qa/m3-iter10-clear-rate-readings-2026-10-01.md(重扫基线——仅历史参照,锚已随 F19 作废) +8. 06-待办清单「旧行为 MC 行为锚全数作废——重锚归数值批」条(2026-10-06 登记——F19 正主) +9. game/core/battle_orchestrator.gd 暂存闸 `_defer_board_damage`/`_deferred_board`(案C 实现载体——T6 读数判读前须读案C 批提交 58dfb56 diff) +10. tools/clear_dominance_audit.py(β 带/当量锚复算常设工具,:51 WEAKEN_PCT 硬编码——F4b/T1 同步义务载体,v1.2 补) ``` + +## 11. 版本记录 + +| 版本 | 日期 | 记 | +|---|---|---| +| v1.0 | 2026-10-05 | 初稿定稿(四件欠账+两件读数+damage_probe A1 顺路修;P1-P4 拍板点框架) | +| v1.1 | 2026-10-06 | **案C 增补**:§2.6 三事实(F18 行为换代/F19 锚全作废/F20 难度走向预判);T0 推算稿四件→五件(+对拍锚按新序重推导,估时 0.75→1.0d,合计 6.0→6.25d);T5 读数卡 T6 项注明案C 新行为首采立新锚;基线纪律注换代(783/0+17 套全绿);§10 留痕 +3 条。增补动因=案C 地图伤害账殿后批(git 7ef18ac)换代结算行为序,06 2026-10-06 条登记重锚义务归本批 | +| v1.2 | 2026-10-06 | **五任务书联审修订**:P3 转正顺延至重扫读数后(一裁依据系 F19 作废锚);F3 带有效性复核+F4b 检算工具(clear_dominance_audit.py WEAKEN_PCT)入留痕与 T1 判据;F11 补 Boss 两档死数字、T4 两句全刷;F15 与 F8 并账一次跑批双认账;F16 判读带案C 换代复核;§3 交叠声明(卡⓪/卡①同管线);R5 与 F19 调停(改前锚=本批新立案C 锚);run_all 十五→十七;估时段一 4.5→4.25d 算术勘误(roadmap 同步)。裁决 Q4 甲随批落文 | diff --git a/design/research/任务书-内容数据驱动扩展批-2026-10-06.md b/design/research/任务书-内容数据驱动扩展批-2026-10-06.md new file mode 100644 index 00000000..6002eb1b --- /dev/null +++ b/design/research/任务书-内容数据驱动扩展批-2026-10-06.md @@ -0,0 +1,171 @@ +# 任务书-内容数据驱动扩展批(迭代 17 卡⓪)-2026-10-06 + +状态:任务书 v1.1(**全部裁决已落、零待裁点**,开工门=U0 用户发令)。设计权威=调研报告 [研究-内容数据驱动扩展架构-2026-10-06](研究-内容数据驱动扩展架构-2026-10-06.md)+本会话盘问裁决实录(台账 370)。 +排期:**迭代 17 头卡(2026-10-06 用户令插队)**,迭代 17 现=卡⓪①③④四卡(M4 数值批已移迭代 18,roadmap 现行口径)。 + +**v1.1 修订记录(2026-10-06 五任务书联审,用户裁决 Q2 甲/Q6/Q7 + 事实勘误)**:①效果类型闭集扩两型(`damage_mult` 伤害倍率修正、`open_ticket` 新建伤害单)——nest_counter/flag_burst/war_banner 三处现状行为原词汇表装不下;②事件词汇补 `charge_full`(charge 满层沿);③pick_refresh 改走主动件分发通道不入事件订阅;④level_clear 归登记不实施(用户裁 Q6,§3.1 起草越界已删);⑤对拍判据两套口径落文(Q7,正主读数档升格为引用件);⑥计数勘误(事件/效果类型/消费点三处);⑦映射表五处语义修正(luck_coin 归属、deep_drill 降级、armory_prep 转移、boss 血量阈值事件改名通用化、first_dig fact 改每战口径);⑧断言面随迁与订阅重建时机补列。 + +--- + +## 一、迭代目标与完成定义 + +把「内容的效果行为」从散布在结算代码里的硬编码分支,搬进内容数据行的三个字段——**订阅哪个事件(on)/ 什么条件(when)/ 什么效果(do)**——由一个通用解释器(EffectEngine)统一执行。完成后:加一件「已有效果类型」的新专长/新秘宝=只改 `.tres` 数据行,零代码。 + +**完成定义**:①旧 23 挂点枚举删除,27 行专长数据全部迁为事件订阅形态且行为等价(对拍判据两套,见 §六 T6 与 §八);②六件按 id 硬编码的被动秘宝迁毕、按 id 特判清零;③文案表值化(效果说明进数据行);④一个迭代内完成、一次合并登记。 + +## 二、裁决实录(2026-10-06 用户逐条裁定,全数有效) + +| # | 裁决 | 内容 | +|---|---|---| +| R1 | 架构模型 | 发布订阅:结算管线同步 emit 事件(无队列无异步,确定性优先)→ EffectEngine 遍历持有集匹配订阅 → 执行数据行里声明的效果。**不是**代码级插件/监听器类——订阅关系本身是配置 | +| R2 | 拦截能力 | 不做通用拦截器链;只保留**改写窗口**(伤害单据 open_ticket 后、submit 前,modify 类效果收集后单点按声明序施加)。断链/否决能力不建,将来需要时加 veto 位。**窗口归属澄清(v1.1)**:armor_roll_rewrite / roll_discount 的现行消费点在护甲掷骰与克制判定的判定点内部,不在 open→submit 单点窗口——它们随各自事件(armor_roll / clamp_check)在判定点就地按声明序施加;modify_ticket 的「单点窗口」仅指 attack_commit 系单据 | +| R3 | 多订阅与冲突 | 一事件多订阅=常态(持有集即订阅集);冲突在**效果类型层用组合规则封死**(见 §四),内容行只能选类型不能自定义合并行为。遍历顺序=数据表声明序,写死保种子复放 | +| R4 | 旧挂点处置 | 23 个 hook 枚举**删除**(概念消亡:事件名+效果类型取代);其中 first_dig_spell、terrain_dig_double 退化为纯条件行;dig_count_segments 与 round_dig_bonus 保留为**两件独立内容**(效果类型不同:攻击分段 vs 攻击加值),订阅同事件 | +| R5 | 范围判据 | **现有实现全部改造**(含 luck_coin 偏运硬币→reward_roll 事件、文案硬编码、令牌 match、树规则键);**06 在册待办只登记不实施**(curio_roll/shop_purchase/gp_settle/level_clear 四事件只登记事件名与发射点设计,哪个 06 内容开工时随批加) | +| R6 | 框架先行 | round_start / enemy_hp_threshold / treasure_used 等发射点一律埋进本批,不论当前有无订阅者 | +| R7 | 交付形态 | **一个迭代完成**,按模块垂直切需求卡(框架/专长/秘宝/令牌树),不分批不分迭代、批末一次合并 | +| R8 | 术语纪律 | 事件=`.on("xxx")` 的 "xxx";订阅后逻辑=效果类型;多订阅叠加规则=组合规则。禁用「挂点/hook/合并律/挂账」等自造词(存量文档随 T7 换代) | + +## 三、事件词汇表(定稿:实施 16,登记暂不做 4) + +### 3.1 本批实施(16 个发射点) + +| 事件名 | 发布时机(发射点,现有代码位置) | 载荷要点 | +|---|---|---| +| `dig_reveal` | 安全开格结算完成(battle_orchestrator 挖格段) | cell、round、is_first_dig_of_battle、terrain_kind | +| `dig_paid` | 付费开掘发起、动作经济闸收对价前 | cell、ap_cost、ap_surplus | +| `flag_hit` | 标中雷兑付时 | cell、round、adjacent_terrain | +| `mine_hit` | 踩雷时(含计数清零语义面) | cell、round、damage | +| `attack_commit` | 我方伤害单据开立(damage_controller.open_ticket 入口) | atk、target、matchup、feats_ctx | +| `clamp_check` | 克制关系判定修正点 | matchup(magic_vs_magic/magic_vs_heavy/pierce_vs_light/…) | +| `armor_roll` | 敌护甲掷骰判定 | target_ac、roll | +| `kill` | 敌人死亡确认 | target、overflow余量 | +| `damage_taken` | 我方受到伤害(kind ∈ {enemy_skill, terrain};雷伤走 mine_hit 不重复发) | kind、amount、source | +| `enemy_hp_threshold` | 敌人血量跌破其 unlock_hp_pct 阈值(unit_container.check_skill_unlocks 同点,开轮摇号前;**对所有带该字段的敌人逐个发射,非 Boss 专属**——Boss 语义留给订阅者 when 判定) | unit、pct、is_boss | +| `round_start` | 每轮开始 | round | +| `flag_settle` | 轮末旗伤结算(轮末序:旗伤开单→AP结转→积层反打,顺序显式在发射代码) | flags_hit_all、flags_count | +| `round_end` | 每轮结算收口 | round | +| `reward_roll` | 格面资源掷定(摇号前权重合成层,luck_coin 迁移落点) | cell、polarity、weights | +| `treasure_used` | 使用主动秘宝(use_treasure 受理面) | item_id、target | +| `charge_full` | charge 积层到达上限的沿(patient_burst 触发点,v1.1 补入) | unit、layers | +| (`curio_roll` 邻位)三选一候选生成现有钩位仅供**对拍观测**,不改发布面(观测行不计入发射点数) | —— | —— | + +### 3.2 登记暂不做(4 个,发射点设计一行,随对应 06 内容批实施;R5 裁决 + Q6 裁决) + +`curio_roll`(三选一候选生成时发布→服务「战利品鉴定」)/ `shop_purchase`(商店成交时发布→服务「符文重铸」)/ `gp_settle`(金币结算入账时发布→服务「贪婪契约」)/ `level_clear`(一张图清空兑付时发布,载荷含 round 与清图统计→服务后续内容批;**本批不写发射代码**——Q6 裁决:R5 原文有效,v1.0 §3.1 的实施行系起草越界,已删)。四者均系 06 在册内容件的前置,本批只在本文登记。 + +### 3.3 效果类型词汇表(定稿:19 行 23 个,解释器 switch 闭集;v1.0 计数「16」系勘误,v1.1 扩 `damage_mult`/`open_ticket` 两型) + +| 效果类型 | 语义 | 收编的现状 | +|---|---|---| +| `add_ticket_rider` | 单据追加固定伤 | hero_attack_rider、venom_edge | +| `modify_ticket` | 改写单据数值(改写窗口,按序施加;窗口归属见 R2 澄清) | clamp_modifier×3、roll_discount、nest_aura、hit_trade、armor_roll_rewrite | +| `damage_mult` | **伤害倍率修正(v1.1 新增)**:对指定来向伤害乘系数(乘法族,正负分桶) | nest_counter 的踩雷减半面(mine_hit_halve) | +| `open_ticket` | **新建一张伤害单(v1.1 新增)**:从事件现场按通道开新单(attack/board_blast 等),与 add_ticket_rider(向已有单追加)相区分 | flag_burst 爆板(open_ticket(KIND_BOARD_BLAST))、war_banner 战旗轮末自开单 | +| `attack_bonus` | 攻击力加值 | round_dig_bonus | +| `attack_segments` | 攻击拆多段 | dig_count_segments(连珠矢) | +| `yield_mult` | 开格产出乘区(正负分桶) | dig_yield_mult×3、terrain_dig_double(条件行) | +| `spawn_spell` | 生成一次必中魔法矢 | first_dig_spell(条件行) | +| `overflow` | 击杀溢出弹射 | kill_overflow | +| `grant_material` | 掉落格面资源 | flag_resource、terrain_refund | +| `grant_ap` | 给行动点。**转移形态声明(v1.1)**:`mode:"transfer"`=轮末剩余 AP 转蓄力层(上限 3)**替代** RAP 转存并作废 cleared 口径——是转移非加法,不入加法族、按序单独施加 | ap_carry | +| `grant_gp` / `bias_wallet` | 金币面(**本批无现状收编,预留型**;luck_coin 实际归 bias_rewards,见下行——v1.1 勘误) | —— | +| `shield` | 减伤层 | charge_shield | +| `spike` | 满层反打 | charge_spike(触发事件=charge_full) | +| `apply_status` | 授予状态(复用 status_effects.tres 词汇) | heavy_debuff | +| `counter_add` / `counter_spend` | 计数器增减(重置点数据声明:round_start/round_end/level) | round_dig 计数、variant_counter、dig_charge 积点(nest_counter 移出本行——其真身是减半+反打,见 damage_mult/open_ticket 两型,v1.1 勘误) | +| `rewrite_board` | 改写棋盘格状态 | flag_neutralize | +| `reveal_tiles` / `remove_mines` / `refresh_pick` | 显影/移除/刷新(**主动件 kind 分发通道**,不经事件订阅——刷新是玩家主动动作,v1.1 澄清) | mine_reveal、cell_removal、pick_refresh | +| `bias_rewards` / `preview` | 抽取面 | reward_weight_bias(=luck_coin 现行 effect_kinds,经 reward_roll)、bonus_refresh | + +### 3.4 组合规则(6 条,效果类型的必填属性,解释器内置) + +| 规则 | 语义 | 适用 | +|---|---|---| +| 加法族 | 数值相加(可交换,顺序无关) | add_ticket_rider、attack_bonus、grant_material、grant_ap(非 transfer)、counter_add、shield | +| 乘法族 | 收集后一次施加(正负分桶) | yield_mult、damage_mult | +| 任意真族 | 任一命中即生效 | spawn_spell、overflow、spike | +| 按序改写族 | 收集→单点按声明序施加 | modify_ticket 及一切改写窗口效果 | +| 按序消耗族 | 按订阅序消耗共享资源,耗尽即止、拒绝走 reason 通道;**可选声明 `fallback`**(v1.1):资源不足时改付声明的基础对价(deep_drill 降级面——AP 富余付全价积计数、不足只付常规基础 1) | counter_spend | +| 即时执行族 | 每条命中订阅各自执行一次,天然可叠加、执行序=声明序 | open_ticket、grant_ap(mode:transfer) | + +## 四、数据行目标形态与 schema + +``` +# feats.tres 一行(示例:奥术余烬 + 魔网织者) +{feat_id:"arcane_ember", rarity:"common", archetype:"magic", + desc:"普攻命中后追加 1 点必中魔法伤", + triggers:[ + {on:"attack_commit", do:{kind:"add_ticket_rider", value:1, dtype:"magic"}} ]} +{feat_id:"mana_weaver", rarity:"rare", archetype:"magic", + triggers:[ + {on:"clamp_check", when:[{fact:"matchup", op:"eq", value:"magic_vs_magic"}], + do:{kind:"modify_ticket", delta:-1}} ]} +``` + +- schema 校验照旧封闭且保留全部现有护栏:`on` ∈ 事件词汇、`do.kind` ∈ 效果词汇、`when.fact` ∈ fact 表、行键白名单、悬空引用加载致命、零裸 flat 禁令、流派扫雷触发点铁律(MINE_HOOKS 改为「每流派 ≥1 行订阅扫雷侧事件」同义校验)。 +- **存档零改动**:持有集/装备存 id 不变,换的只是解释方式——纯重构可对拍的前提。 +- fact 表(条件词汇,**封闭枚举 9 项**,v1.0「~15 等」开放写法系勘误):`matchup`、`is_first_dig_of_battle`(**现行口径=每战首次开格**,begin_battle 归零——v1.0 的 `is_first_dig_of_level` 与现行语义相反,已随 provisional 定案改正)、`terrain_kind`、`target_armor_class`、`flags_count`、`charge_layers`、`nest_count`、`round_index`、`counter`(按 counter_id 取值)。T2 落码时若发现 9 项装不下现状条件,属闭集缺口——停机呈报补表,禁在行内私开 fact。 + +## 五、23 挂点逐行移植映射表(等价性对拍底单) + +| 旧 hook | 持有内容(feats.tres 实据) | 新形态 | +|---|---|---| +| hero_attack_rider | arcane_ember | on:attack_commit → add_ticket_rider | +| clamp_modifier | mana_weaver / azure_oath / hunter_rapier(穿刺) | on:clamp_check + when(matchup) → modify_ticket | +| first_dig_spell | mana_resonance | on:dig_reveal + when(is_first_dig_of_battle) → spawn_spell | +| kill_overflow | pierce_shot | on:kill → overflow | +| dig_count_segments | chain_arrows | on:dig_reveal→counter_add;on:attack_commit→attack_segments | +| roll_discount | weak_instinct | on:clamp_check → modify_ticket(档位移) | +| round_dig_bonus | miner_stubborn | on:dig_reveal→counter_add;on:mine_hit→counter 清零;on:attack_commit→attack_bonus | +| hit_trade | miner_hammer | on:attack_commit → modify_ticket(攻受交换) | +| armor_roll_rewrite | focus_mind | on:armor_roll → modify_ticket(免掷恒折扣) | +| heavy_debuff | armor_crack | on:attack_commit + when(重甲) → apply_status | +| dig_yield_mult | greedy_lantern / vein_intuition / gambler_pact | on:dig_reveal → yield_mult(乘法族) | +| deep_dig | deep_drill | on:dig_paid + when(ap 富余) → counter_spend(对价,fallback=常规基础 1);深层产出配额=counter fact 条件(cap=4) | +| flag_resource | mine_atlas | on:flag_hit → grant_material | +| nest_aura | nest_hunter | on:attack_commit + fact(nest_count) → modify_ticket | +| flag_all_hit | perfect_defense | on:flag_settle + when(全中) → yield_mult(旗伤乘区) | +| flag_strike | war_banner | on:flag_settle → open_ticket(attack 通道)(v1.1 改:战旗是自开单,非向已有单追加) | +| ap_carry | armory_prep | on:round_end → grant_ap(mode:transfer)(转蓄力层上限 3、替代 RAP 转存——转移非加法,v1.1 改) | +| charge_shield | iron_wall | on:damage_taken + fact(charge_layers) → shield | +| charge_spike | patient_burst | on:charge_full(满层沿)→ spike(v1.1 改:charge 事件原不在词汇表,已补入) | +| terrain_refund | ley_harvest | on:damage_taken(kind=terrain) → grant_material | +| terrain_dig_double | thorn_weaver | on:dig_reveal + when(伤害型地形) → yield_mult(×2) | +| flag_neutralize | purge_sapper | on:flag_hit + when(邻伤害地形) → rewrite_board | +| variant_counter | magma_body | on:damage_taken→counter_add;on:round_end→counter_spend 反打 | + +秘宝侧六件:venom_edge(attack_commit→add_ticket_rider)、luck_coin(reward_roll→**bias_rewards**,现行 effect_kinds=reward_weight_bias;v1.1 勘误:原误归 bias_wallet)、dawn_order(round_start→grant_ap 队列)、dig_charge(dig_reveal→counter_add + dig_paid→counter_spend)、nest_counter(mine_hit→damage_mult(0.5) 减半 + open_ticket(reflect 通道) 反打——v1.1 改:原 counter_add 归类与现状不符,现实现无计数器)、flag_burst(flag_hit→open_ticket(board_blast));pick_refresh(**主动件 kind 分发→refresh_pick,不经事件订阅**)。 + +## 六、任务卡(T0-T7 ≈4.5d,软时间盒超限 L3 呈报切点) + +| 卡 | 内容 | 估时 | +|---|---|---| +| T0 | 开工三查:worktree+基线复读(冒烟/探针/run_all/MC 对拍锚)+git status 归属核对(在飞 mapdmg/standee/uialign 只标旗不碰) | 0.25d | +| T1 | EffectEngine 框架:事件枚举+发射点 16 处埋点+订阅装载(**重建时机(v1.1 补)=每战 begin_battle 从持有集/装备集重建 trigger 列表**——冒险中途获得/换取的专长下一战生效,同战内不热载,确定性优先)+组合规则+改写窗口+计数器账本+确定性声明序 | 1.0d | +| T2 | 词汇表落码:schema 换代(feats/treasure 两表 trigger 形态+校验规则换代+fact 表)+护栏迁移(MINE_HOOKS 同义校验等) | 0.5d | +| T3 | 专长迁移:27 行数据改写(§五映射表逐行)+旧枚举删除+_feat_flags_for 退役+消费点拆除(**口径=研究文档的 23 个消费点,v1.0「24 处」系勘误;执行时逐处 grep 清点对账**)+文案入表(battle_narrator.gd:142 一处;v1.0「两处」系与 T4 重复计数)+**断言面随迁(v1.1 补)**:smoke_all 直呼断言(含「27 件」计数断言与 _feat_flags_for/HOOKS 直呼)与 damage_math_test 6 处 hook 引用同步改写,不列修断言工作项即开工=必红 | 1.25d | +| T4 | 秘宝迁移:六件被动+dig_charge 特判+主动件 kind 分发合流+luck_coin 经 reward_roll+curio_interact_controller 文案入表 | 0.75d | +| T5 | 令牌/树收尾:_apply_curio_pick match 收敛进效果类型+meta_tree 规则键走同面 | 0.5d | +| T6 | 回归闸(**对拍判据两套,v1.1 落文(Q7 裁决)**:①确定性复放与冒烟断言=**逐位一致**;②MC 统计量(通关率/画像)=**成型画像同带**——统计量无「逐位」可言,禁混用。锚口径正主=`design/qa/m4iter12-t7-batch1-readings-2026-10-04.md`(runsum 锚、seed0=90000、N=20、off/formed 配对、L1 通关率判读带),T0 按该口径自采 pre 锚留盘、T6 采 post 锚配对)+冒烟+run_all+探针+确定性复放 | 0.5d | +| T7 | 文档正主换代+出口报告:奇物.md/秘宝.md/敌人系统.md 词汇换代+registry 条目+06 状态收口+退出人闸材料包 | 0.25d | + +## 七、契约红线(🔴 违反即停机呈报) + +1. **存档零破坏**:存档键/存档格式/断点结构一字不改;换的只是运行时解释方式。 +2. **数值等价**:迁移前后行为等价——判据两套(T6/v1.1):确定性复放与冒烟断言逐位一致;MC 统计量同带(m4iter12 口径)。发现必要偏差(如旧实现 bug 被顺带修正)必须停机呈报,禁静默吸收。 +3. **确定性**:全同步调用、无队列无异步无 signal 时序依赖;遍历序=数据声明序。 +4. **护栏保留**:封闭枚举校验/键白名单/悬空引用致命/零裸 flat 禁令/流派扫雷触发铁律全部以新形态延续,禁以「数据驱动」名义放开任何护栏。 +5. **battle_orchestrator 历史脏件**:全程 worktree 批、主区禁直改;并行在飞三线(mapdmg/standee/uialign)只标旗不碰,T0 重取基线。 + +## 八、停机点与验证闸 + +- **S1**(T3 段末):首个流派(魔法流)迁移完跑对拍——若确定性复放非逐位一致且定位超出映射表误差,停机呈报。 +- **S2**(T6):确定性复放不一致/成型画像越带/冒烟红——任一命中停机,禁带病合并。 +- 验证闸:冒烟全量+run_all 全套+五探针+MC 对拍+确定性复放(同种子两次逐位一致)。 +- 代拍板白名单:事件载荷字段命名、fact 表个别查询面落法、smoke 断言数漂移追认、发射点微调(±1 处)。 +- 呈报不停机级:文案措辞、注释层、台账撞号顺延。 + +## 九、范围边界(明确不做) + +- 4 个登记事件不实施(§3.2);外挂脚本层(调研案丙)不做;luck_coin 之外的数值重调不做(归 M4 数值批);relic/feat 存档键更名不做(06 在册命名统一条,存档键铁律);main.gd:475 疑似缺陷不随批(06 在册独立 mini-loop 待令)。 diff --git a/design/research/任务书-敌人表行集多行批-2026-10-05.md b/design/research/任务书-敌人表行集多行批-2026-10-05.md index 2ab5802e..1a5b4fb2 100644 --- a/design/research/任务书-敌人表行集多行批-2026-10-05.md +++ b/design/research/任务书-敌人表行集多行批-2026-10-05.md @@ -1,8 +1,9 @@ -# 任务书:敌人表行集多行批(v1.0 定稿,待发令开工) +# 任务书:敌人表行集多行批(v1.1 定稿,零待裁点,开工门=U0 用户发令) -> 成文 2026-10-05。**性质**:数据表校验与生成机制放开 + 配套呈现适配,允许同一场遭遇混编多种敌人。 +> 成文 2026-10-05,v1.1 修订 2026-10-06(五任务书联审:P3 改裁丙撤投放、P1 补遭遇骰机制与实例总数语义、F2/F5/F7 事实勘误、套件数与头注换代、expect_pools 与行序契约补列)。 +> **性质**:数据表校验与生成机制放开 + 配套呈现适配,允许同一场遭遇混编多种敌人。 > 事实源 = [06-待办清单](../gdd/06-待办清单.md)「同深度第二敌种与精英 / Boss 编码位」条剩余面(该条前半「同层多遭遇」已由关卡节点地图 v1.0 节点编号升级消解,剩余即本卡);设计权威 = [敌人系统](../gdd/systems/战斗循环/敌人系统.md) v0.13(技能池执行批已落籍,Boss 半血解锁技已入表)。 -> 归属迭代:**迭代 17 · 卡①**(迭代 17 四卡:①本批 ②M4 数值批 ③暂停层只读入口批 ④生涯界面设计立项)。 +> 归属迭代:**迭代 17 · 卡①**(迭代 17 现=卡⓪数据驱动扩展批+①本批+③暂停层只读入口批+④生涯界面设计立项;M4 数值批已移迭代 18,roadmap 现行口径)。 > 满足 loop engineering 八件(见 §9 逐条对照)。 > 开工门:**已解**——前置「技能池实施批落地」已于 2026-10-05 完成(技能池执行批合并进 master,提交 d3fa887,冒烟 772/0)。 @@ -21,7 +22,7 @@ | 1 | 校验放开 | 敌人表同一行集配两种以上敌人不再触发加载期致命错误;新的行集构成纪律(§5 R2 的具体条款,先随 P1/P4 裁决落文再落码)由加载期校验兜底 | | 2 | 生成链接通 | 混编行集开战后,各敌种按裁决后的数量语义(P1)实例化上场,各自实例池独立摇号;固定顺位、死亡顺延、范围效果全体现行语义不破 | | 3 | 零内容期零漂移 | 在未投放任何混编行的表状态下,MC 与冒烟数值断言对基线**逐位一致**(机制放开但表内容不变 = 行为必须不变) | -| 4 | 重闸全绿 | 冒烟全量(基线数字 T0 重测回填,禁止沿用本文历史数字)· run_all 十五套件 · 场景探针(触呈现加 ui_struct_probe / ui_click_probe / ui_layout_probe)· doc 三闸对基线零新增 · 熵增闸 PASS | +| 4 | 重闸全绿 | 冒烟全量(基线数字 T0 重测回填,禁止沿用本文历史数字)· run_all 十七套件 · 场景探针(触呈现加 ui_struct_probe / ui_click_probe / ui_layout_probe)· doc 三闸对基线零新增 · 熵增闸 PASS | | 5 | 人闸完成 | 混编遭遇实机目检清单过(若在 P3 裁「本批投放首个混编行」)+ 代拍板追认清单过;收口五步齐 | > ⚠️ **基线纪律**:本任务书起草时 master 头 = 413c936(ui-struct-probe mini-loop 合并后),冒烟基线 772/0。开工 T0 必须重测基线取实际值回填,禁止把本文数字当闸门。 @@ -31,12 +32,12 @@ | # | 事实 | 读数 | |---|---|---| | F1 | 「每行集恰一敌种」校验位置 | `game/data/schemas/enemy_table.gd:108-112`——按 `(level_index, node_depth)` 聚合,行集内出现第二个 `enemy_id` 即报 `rowset_rule` 类错误,加载期致命中止 | -| F2 | 行集解析双算法共存 | ①`EnemyTable.resolve_rowset`(`enemy_table.gd:121-132`)= **floor 语义**(取 ≤ 请求坐标的最近行),仅 `smoke_all.gd:3383/3385/3415/3418` 四处使用;②`ConfigDbResolver.resolve_enemy_rowset`(`config_db_resolver.gd:33-34` → `_resolve_progress`)= **ceil 语义**(向上取整保证偏难,2026-09-18 拍板),是全部生产消费面入口。两函数都**只返回单行** | +| F2 | 行集解析双算法共存 | ①`EnemyTable.resolve_rowset`(`enemy_table.gd:121-132`)= **floor 语义**(取 ≤ 请求坐标的最近行),仅 `smoke_all.gd:3452/3454/3484/3487` 四处使用(v1.0 行号 3383/3385/3415/3418 系起草后漂移,2026-10-06 复核);②`ConfigDbResolver.resolve_enemy_rowset`(`config_db_resolver.gd:33-34` → `_resolve_progress`)= **ceil 语义**(向上取整保证偏难,2026-09-18 拍板),是全部生产消费面入口。两函数都**只返回单行** | | F3 | 双算法处置是既有待裁项 | 工作日志 312(2026-10-02 代码质量修复批)呈报的七拍板点之一「enemy_table.resolve_rowset 双算法」至今未裁——本批动行集解析必然擦到这条,列入 §8 待裁点 P2 | | F4 | 生成消费点 | `unit_container.gd:47` `spawn()` 取单行后按 `unit_count`(或遭遇骰传入的数量)生成 count 个**同种**实例;数量域常量 `EnemyTable.UNIT_COUNT_MIN/MAX = [1,3]`,表校验与 spawn 守卫共用(`enemy_table.gd:18-21`) | -| F5 | 其余消费点 | `battle_orchestrator.gd:220`(战前预取行 erow_pre)· `battle_narrator.gd:75`(叙述取敌种)· `smoke/skill_rhythm_probe.gd:17` · `smoke/damage_probe.gd:270/421` · `smoke_all.gd` 十余处 · `tests/unit/data_config/config_db_test.gd:59-61`(双表精确行契约断言) | +| F5 | 其余消费点 | `battle_orchestrator.gd:225`(战前预取行 erow_pre;v1.0 写 220 系漂移,2026-10-06 复核)· `battle_narrator.gd:75`(叙述取敌种)· `smoke/skill_rhythm_probe.gd:17` · `smoke/damage_probe.gd:270/421` · `smoke_all.gd` 十余处 · `tests/unit/data_config/config_db_test.gd:59-61`(双表精确行契约断言) | | F6 | GDD 侧结构冻结 | 敌人系统.md 规则表「每个坐标只出一种敌种」(:75)+ 结构冻结条款含「每坐标恰一敌种」(:263)——属**结构冻结面**,修订须先走 GDD 修订流程落文再落码,禁止码先文后 | -| F7 | 表内容现状 | `enemy.tres` 共 21 行、8 敌种(地精/熊地精/食人魔酋长/巨蜘蛛/蛛化卓尔精灵/灰矮人/钩爪恐魔/灰矮人石君王),现行每行集恰一行 | +| F7 | 表内容现状 | `enemy.tres` 共 **19 行、9 敌种**(地精/熊地精/霍布地精/食人魔酋长/巨蜘蛛/蛛化卓尔精灵/灰矮人/钩爪恐魔/灰矮人石君王;v1.0 写「21 行 8 敌种」系事实错误,漏霍布地精,2026-10-06 复核 `grep -c '"enemy_id"'`=19,冒烟断言文案自证「十九行」),现行每行集恰一行 | | F8 | 遭遇骰与数量骰 | 数量区间(`unit_count`/`unit_count_max`)由战斗结算遭遇骰同流在 spawn 期掷定一次传入确定值(敌人系统零随机纪律——本系统只收确定结果);攻击区间同理。混编不改这条纪律 | | F9 | 难度修饰 | `resolve_difficulty_modifiers` 按 (L,D) 精确匹配返回 hp_mult/atk_mult,施加于行值快照之前(`unit_container.gd:61-63`)——混编后该修饰**对行集内全部敌种同乘**,不改变异 | | F10 | 范围效果语义 | 敌人系统.md:75「敌方效果对全体生效」规则的前提是「场上敌人全同种,范围效果作用于所有存活敌人是天然兼容的默认」——混编后「全体存活敌人共享」的结算语义**不需要改**(效果本就按存活单位逐一生效),但 GDD 该句前提描述须随批修订 | @@ -66,14 +67,14 @@ | 文件 | 改动 | |---|---| | `unit_container.gd` | `spawn()` 改受理行集:逐行按各自数量语义实例化,行主序生成序保持(`default_target` 固定顺位语义自然兼容);实例池快照逐行独立(现行逐实例快照逻辑天然兼容);数量域守卫改为行集级(上限随 P1) | -| `battle_orchestrator.gd:220` | 战前预取改行集形态 | +| `battle_orchestrator.gd:225` | 战前预取改行集形态 | | `battle_narrator.gd:75` | 叙述取敌种改行集形态(混编遭遇的开战叙述口径——逐种列名还是取首种,随批定,属呈现文案) | ### 组 D · 断言与探针 | 文件 | 改动 | |---|---| -| `smoke_all.gd` | 行集结构断言换代:「行集恰一敌种」相关断言(:2900 注释口径、L2/L3 定点行集断言 :3383-3420 等)改断言新构成纪律;「双表精确行契约」断言改「精确行集」口径;新增混编正反断言(违规构成定位到类别) | +| `smoke_all.gd` | 行集结构断言换代:「行集恰一敌种」相关断言(:2900 注释口径、L2/L3 定点行集断言 :3452-3487 段等)改断言新构成纪律;「双表精确行契约」断言改「精确行集」口径;**expect_pools 逐坐标池模板断言(:2973-2996)换代**——现行按 `"L_D"` 键每坐标一池精确比对,同坐标第二行即红,须改 `(L,D,enemy_id)` 键口径,且「十九行池模板」等文案随 F7 换代(v1.1 补列,原漏);新增混编正反断言(违规构成定位到类别) | | `config_db_test.gd:59-61` | 同通道断言换代 | | `skill_rhythm_probe.gd` / `damage_probe.gd` | 取数面随行集形态适配(行为零变更期逐位一致是验收条件) | @@ -82,10 +83,10 @@ | 禁项 | 理由 | |---|---| | **零混编内容期行为零漂移** | 本批的默认形态 = 机制放开 + 表内容不变。P3 若裁「本批投放首个混编行」,投放行本身属内容变更,须单独 MC 取证(先推算后重跑铁规:跑批发起前须用户批准) | -| **不改遭遇骰/数量骰随机流** | 敌人系统零随机纪律与 RNG 单点红线不变;混编的数量语义若需新摇号,必须走既有封闭流扩签并先经用户裁决(列入 P1 子项) | +| **不改遭遇骰/数量骰随机流** | 敌人系统零随机纪律与 RNG 单点红线不变;P1 已裁混编需新摇号(每行一骰),必须走既有封闭流扩签并先经用户批准(P1 裁决补充段已含此门) | | **不改键结构与存档** | `(L,D,enemy_id)` 键、断点存档、跨局存档零触碰 | | **不设精英/Boss 编码位** | 06 条目已裁「不设精英 / Boss 编码位」(精英 P2 内容承载),本批不重开 | -| **技能池构成纪律不动** | 每池恰一普攻、增益 ≤1、unlock_hp_pct 投放纪律(仅 Boss 行)原样 | +| **技能池构成纪律不动** | 每池恰一普攻、增益 ≤1、unlock_hp_pct 投放纪律(仅 Boss 行)原样。**知悉(v1.1)**:「原样」实为无硬闸——`enemy_table.gd:94-95` 注释明写纪律系文档登记非校验;P4 乙放开 Boss 多行后,「Boss 行带 unlock_hp_pct 可静默过校验」的无闸面随之扩大,后续内容批投放 Boss 多行时须逐行人工复核此面 | | **数值零触碰** | hp/atk/unit_count/权重行值一律不动(P3 投新行除外,且新行行值 provisional 归调参) | ## 5. 设计契约(🔴 触碰 = L3 停机) @@ -97,6 +98,7 @@ | 🔴 R3 | **fail-fast 语义保持**:行集构成违例仍加载期致命中止,错误类别可定位;禁软回退 | 组 D 正反断言 | | 🔴 R4 | **RNG 单点**:不新增 `randomize()`/自建随机源;如需新摇号点一律经 RngService 封闭流并先报批 | 静态审查 + rng 日志比对 | | 🔴 R5 | **双表精确行契约不破**:board_gen × enemy 同坐标覆盖义务(`smoke_all.gd:294-311`)在混编口径下仍成立 | 组 D 断言换代后全绿 | +| 🔴 R6 | **混编行文件行序纪律(v1.1 补)**:同一 (L,D) 的多行在表文件内必须相邻且行序=声明序=生成序——resolver 返回序、battle_narrator 取首行叙述、expect_pools 断言、spawn 行主序全部依赖文件行序,行序即契约 | 组 B 校验(同坐标行不相邻即报错)+ 组 D 断言 | ## 6. 任务卡(逐卡一个 loop 轮次) @@ -106,19 +108,19 @@ | **T1** | 组 A:GDD 修订(敌人系统.md 混编口径 + 结构冻结条款 + F10 前提句 + 版本历史)+ registry + 06 状态行 | 0.5d | doc 三闸 | 三闸零新增;修订句读者测试过(只读该节能答三问) | | **T2** | 组 B:schema 校验换代 + resolver 行集数组化(P1/P2 裁决后) | 0.75d | 冒烟 | 正反断言红绿各验;旧「恰一敌种」违例行按新纪律判定正确 | | **T3** | 组 C:spawn 行集受理 + battle_orchestrator/narrator 适配 | 0.75d | 冒烟 + ui_struct_probe | 零内容期冒烟逐位绿 | -| **T4** | 组 D:smoke/单测/探针断言面换代 + 新增混编构成断言 | 0.5d | 冒烟 + run_all | 十五套件绿;新断言负向验证红(故意配违规行集能抓到) | -| **T5** | (条件卡,P3 裁投放才执行)首个混编遭遇行投放 + 行值 provisional 登记 | 0.5d | 冒烟 + MC(先报批) | 混编遭遇开战/结算/胜利全链断言过 | -| **T6** | 重闸:全量轻闸 + doc 三闸 + 熵增闸 + MC 逐项比对(零内容期) | 0.5d | 重闸全绿 | §1.2 判定 3/4 | +| **T4** | 组 D:smoke/单测/探针断言面换代 + 新增混编构成断言 | 0.5d | 冒烟 + run_all | 十七套件绿;新断言负向验证红(故意配违规行集能抓到) | +| **T5** | (条件卡,**P3 二裁丙=本批不触发,不执行**——投放连同 Boss 多行归后续内容批,该批开工时按本卡口径执行+MC 取证先报批+难度带读数义务) | 0.5d | — | — | +| **T6** | 重闸:全量轻闸 + doc 三闸 + 熵增闸 + MC 逐项比对(零内容期——本批唯一 MC 面;先报批) | 0.5d | 重闸全绿 | §1.2 判定 3/4 | | **T7** | 人闸:目检清单(T5 执行时含混编遭遇实机目检)+ 代拍板追认 | 0.5d | 目检过 | §1.2 判定 5 | | **T8** | 收口五步(active.md 快照 → 工作日志 → roadmap 迭代 17 行标注 → 06 收口 → 闸序列留痕) | 0.25d | — | 收口五步齐 | -> 估时合计 ≈ **4.25d**(含 T5)/ **3.75d**(不含 T5),超 roadmap 软时间盒 3 日上限——知情预挂:T4 后按 L3 呈报切点,不擅切(沿迭代 9/秘宝批先例)。 +> 估时合计 ≈ **3.75d**(T5 撤投放后全批;v1.0「含 T5 4.25d」口径随 P3 改裁作废),超 roadmap 软时间盒 3 日上限——知情预挂:T4 后按 L3 呈报切点,不擅切(沿迭代 9/秘宝批先例)。 ## 7. 验证闸 - **轻闸**(每轮):run_smoke 全量 + ui_input_probe;触 presentation 的轮次(T3/T5)加 ui_click_probe + ui_layout_probe + ui_struct_probe(2026-10-05 起 ui_struct_probe 已入默认必跑段)。 - **重闸**(T6):全量轻闸 + doc 三闸(doc_link / gdd_lint / doc_term,对基线零新增)+ 熵增闸 + MC 逐项比对。 -- **MC 触发**:零内容期对拍属「结构位移类存档锚」,**发起前先向用户请示定性**(CLAUDE.md MC 触发细则);T5 投放行属数值验证场景,同样先报批。两入口铁规全程有效。 +- **MC 触发**:零内容期对拍属「结构位移类存档锚」,**发起前先向用户请示定性**(CLAUDE.md MC 触发细则);T5 本批不触发(P3 丙),后续内容批投放时属数值验证场景、同样先报批。两入口铁规全程有效。 ## 8. 代拍板白名单与待用户裁点 @@ -131,14 +133,20 @@ ### P1 · 混编数量语义 +**✅ 裁决(2026-10-06 用户令):案甲**——每行各自 unit_count 独立实例化;**行集实例总数(各行骰后数量求和)≤4**(Q9 二裁消歧:「总数」=实例数非行数,行数不另设上限);UNIT_COUNT_MAX 语义改「单种数」随批换代校验与 spawn 守卫。 + +**裁决补充·遭遇骰交互(Q8 二裁,v1.0 漏裁)**:混编行集落地后**每行各掷一次数量骰**(与「每行各自 unit_count」语义自洽),单一行集仍吃单个 `unit_count_override` 且仅作用于该行集整体传入值按行分配的现行路径不存在——override 命中时跳过行内掷骰直接取值;每行一骰=遭遇骰封闭流新增消费点,**实现前须按 R4 报批扩签**(随机流消费次数变化牵动全部种子面),未获批准前 T2/T3 不得落码。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| -| 甲(推荐) | 每行各自 `unit_count` 独立实例化,行集总数上限另立(如 ≤4);数量域 [1,3] 仍约束单行 | 表意最直白,作者按行写数即可;代价是 UNIT_COUNT_MAX 语义从「单场总数」改为「单种数」,须同步改校验与 spawn 守卫 | +| 甲(推荐,已裁) | 每行各自 `unit_count` 独立实例化,行集实例总数上限 ≤4;数量域 [1,3] 仍约束单行 | 表意最直白,作者按行写数即可;代价是 UNIT_COUNT_MAX 语义从「单场总数」改为「单种数」,须同步改校验与 spawn 守卫 | | 乙 | 行集总数仍 ≤3,按行权重或行序分配各种只数 | 场上规模完全不变、数值面最稳;淘汰理由:分配规则本身是新的隐性机制,作者无法直接表达「我要 1 蜘蛛 + 2 地精」,表意失真 | | 丙 | 每种恒 1 只 | 最简;淘汰理由:混编的主要价值(强弱搭配的数量结构)被阉割,等于只允许「2-3 种各 1 只」一种形态 | ### P2 · 行集解析双算法处置(工作日志 312 既有待裁项随批) +**✅ 裁决(2026-10-06 用户令):案甲**——floor 版退役,全仓统走 resolver ceil 版(工作日志 312 待裁项随之销号)。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| | 甲(推荐) | 本批顺带统一:`EnemyTable.resolve_rowset`(floor 版,仅冒烟四处用)退役,全仓统走 resolver ceil 版 | 行集解析本来就要换代,双算法一起收是最便宜时点;一次批内只多 4 处调用点改动 | @@ -146,13 +154,18 @@ ### P3 · 本批是否投放首个混编遭遇行 +**✅ 改裁(2026-10-06 二裁,用户令,推翻一裁案乙):案丙(撤投放)**——本批回到纯结构批,混编行只建机制不投内容;首个混编遭遇行(连同 Boss 多行内容)归后续内容批统一投放。改裁依据(联审实据):一裁案乙的「巨蜘蛛 1+钩爪恐魔 1」与现行首遇教学位纪律正面冲突——巨蜘蛛首遇位 (2,4) 表注释与冒烟断言均为「固定 2 只不加骰」,投 1 只直接违反;钩爪恐魔真首遇位在 L3 (3,1) 固定不加骰,L2 投放使其名存实亡;且 L2 每个 battle 坐标都有断言撞面((2,0) 撞首遇绿档 12 种子扫描、(2,4) 撞固定 2 断言、(2,5/6/7) 撞 expect_pools 精确池),一裁「L2 某普通节点」无可落地坐标。投放义务随批登记 07(后续内容批连同 Boss 多行一起投、投时 MC 取证先报批、难度带读数义务同登记)。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| -| 甲 | 本批只放开机制、零内容投放;首个混编遭遇归后续难度曲线/内容批 | 机制批零数值变更,MC 零跑或仅零扰动对拍,批面最干净 | -| 乙(推荐) | 本批顺带投放一个教学位混编行(候选:L2 某普通战斗节点,巨蜘蛛 1 + 钩爪恐魔 1,行值 provisional 归调参) | 机制没有内容验证等于没验收;一个定点行即覆盖全链(生成/叙述/结算/胜利);代价是需 MC 取证(先报批)与行值 provisional 登记 | +| 甲 | 本批只放开机制、零内容投放;首个混编遭遇归后续难度曲线/内容批 | 与丙同向但未点名承接批,义务易悬空 | +| 乙(一裁,已推翻) | 本批顺带投放教学位混编行(L2 某普通节点,巨蜘蛛 1 + 钩爪恐魔 1,行值 provisional) | 推翻理由见上:与首遇教学位纪律/表注释/冒烟断言三面冲突,无合法坐标 | +| 丙(二裁,生效) | 撤投放,纯结构批;混编行内容归后续内容批(与 Boss 多行同批),投放时 MC 取证先报批 + 难度带读数义务随批登记 07 | 机制批零数值变更零 MC;投放的教学价值不抵三面断言/纪律/读数连带代价 | ### P4 · Boss 行集是否允许多行 +**✅ 裁决(2026-10-06 用户令):案乙(否决推荐案甲)**——Boss 行集校验同样放开(多行合法)。知情代价随批登记:Boss 半血解锁/独占技/难度带读数面自此可被 Boss 行内容变量影响;**另有两个具体读数面(v1.1 补列)**:①`battle_narrator.gd:70-77` `boss_name()` 取行集**首个命中行**的 display_name——Boss 行集多行后 Boss 名显示可能变成护卫名(取决于文件行序,R6 纪律的呈现面后果);②Boss 节点遭遇骰同样读首行 `unit_count` 区间(`battle_orchestrator.gd:225`),多行后首行区间即全行集骰域。**本批零内容投放不变**(P3 已改裁丙=撤投放),Boss 多行的实际内容投放归后续内容批,投放时节奏重测义务 07 在册、上述读数面逐项复核。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| | 甲(推荐) | 本批仅普通战斗节点放开;Boss 节点行集仍恰一行(加载期校验保留 Boss 行单行约束) | Boss 半血解锁技刚落籍、节奏重测(T7 读数)未跑,Boss 战形态此时不应再加变量;护卫型 Boss 属内容设计,归后续批单议 | @@ -177,7 +190,7 @@ |---|---|---| | **L1** | 单轮轻闸红 | 一次重试(自查漏改/调用点遗漏);仍红 → L2 | | **L2** | 同类错误连续 3 轮 / 事实表与 §2 不符 | 记录进本任务书与工作日志,修订事实表后继续;不阻断全批 | -| **L3** | 任一 🔴 R1–R5 破 / P1–P4 久决不下 / 发现需要新增随机消费点或改存档 | 停机写报告(列事实 + 影响面 + 方案对比),等用户发令;不自行决断 | +| **L3** | 任一 🔴 R1–R6 破 / P1–P4 久决不下 / 发现需要新增随机消费点或改存档 | 停机写报告(列事实 + 影响面 + 方案对比),等用户发令;不自行决断 | ## 11. 现状留痕(供执行会话读序) diff --git a/design/research/任务书-暂停层只读入口批-2026-10-05.md b/design/research/任务书-暂停层只读入口批-2026-10-05.md index 1f506d70..84a0ad7a 100644 --- a/design/research/任务书-暂停层只读入口批-2026-10-05.md +++ b/design/research/任务书-暂停层只读入口批-2026-10-05.md @@ -1,8 +1,11 @@ -# 任务书:暂停层只读入口批(v1.0 定稿,待发令开工) +# 任务书:暂停层只读入口批(v1.1 定稿,零待裁点,开工门=U0 用户发令) -> 成文 2026-10-05。**性质**:功能性 UI 交互新增——局内暂停层,承载钥石余额与营地柜台解锁进度的**只读**查看。 +> **T0 基线回填(2026-10-06 执行会话实测,HEAD=44328b1)**:冒烟 **783/0**(§1.2 注记 782 系 ed38c54 时点参考值,卡④落籍后 +1,以本读数为对拍基线)· run_all **17 套全绿** · probe_gate 默认段(ui_input+ui_struct+ui_contrast)全绿。§2 事实表 F1-F10 逐条实核全绿(F2 登记册=main.gd:332、z 序注记=:242-243;F3 badge 工厂缺位+营地本地拼装=camp_overlay_view.gd:79-92;F5 访问器行号逐一符合、boss_kill_count=save_manager.gd:250 精确;F8 settings=main.gd:260;F10 dbgbtn=ui_assembler.gd:161-164)。**迭代 15② 交集核对:零命中**——卡②/卡③均已 2026-10-05 落籍(roadmap 现行口径,无在飞面);在飞 worktree 三件(iter17-dd 卡⓪〔尚无超前提交〕/uialign/standee〔均零 diff〕)与本批文件面零交集。 + +> 成文 2026-10-05,v1.1 修订 2026-10-06(五任务书联审:R4 快照口径收敛、badge 工厂路径裁决〔Q10 甲〕、P1 丁的 06 Esc 登记补办、基线换代 iter16ui 批后、F5/F10 事实勘误、入口守卫与只读断言口径补列)。 +> **性质**:功能性 UI 交互新增——局内暂停层,承载钥石余额与营地柜台解锁进度的**只读**查看。 > 事实源 = [06-待办清单](../gdd/06-待办清单.md)「解锁界面与钥石呈现的交互形态(残余:暂停层只读入口)」条。 -> 归属迭代:**迭代 17 · 卡③**(迭代 17 四卡:①敌人表行集多行批 ②M4 数值批 ③本批 ④生涯界面设计立项)。 +> 归属迭代:**迭代 17 · 卡③**(迭代 17 现=卡⓪数据驱动扩展批+①敌人表行集多行批+③本批+④生涯界面设计立项;M4 数值批已移迭代 18,roadmap 现行口径)。 > 满足 loop engineering 八件(见 §9 逐条对照)。 > **美术红线(2026-10-05 用户立规)**:本批不得包含观感类、素材类内容;呈现必须复用现行公共件(UiKit 工厂件)与 ui_kit 色板 token,**不新画任何素材、不新建任何色值**。功能性 UI 交互不算美术。 @@ -20,11 +23,11 @@ |---|---|---| | 1 | 入口可达 | 局内两个主屏(战斗屏 / 关卡地图屏)均可按 P1 裁决的形态打开暂停层;入口在对局外(主菜单)不误出现 | | 2 | 只读数据正确 | 暂停层展示的钥石余额、三柜台进度与 `MetaProgression` 访问器读数逐项一致;无任何可点购买/切换控件 | -| 3 | 公共件复用 | 全部构成件来自 UiKit 现行工厂(modal_shell / bcard / label / badge / icon_rect / verb_button 等)与色板 token;零新素材、零新色值、零新字体 | +| 3 | 公共件复用 | 全部构成件来自 UiKit 工厂(modal_shell / bcard / label / icon_rect / verb_button 等)与色板 token;**badge 由本批在 ui_kit 新增工厂件并同步迁入营地钥石徽(Q10 甲裁决)**;零新素材、零新色值、零新字体 | | 4 | 模态纪律 | 登记进 modal_stack 登记册(main.gd:332 同款),z 序与输入锁行为符合缺陷⑤纪律;与既有弹窗层的关系按 P2 裁决落实 | -| 5 | 重闸全绿 + 人闸 | 冒烟全量(基线 T0 重测回填)· run_all 十五套件 · ui_struct_probe / ui_click_probe / ui_input_probe 全绿 · doc 三闸零新增 · 熵增闸 PASS;实机目检清单过 | +| 5 | 重闸全绿 + 人闸 | 冒烟全量(基线 T0 重测回填)· run_all 十七套件 · ui_struct_probe / ui_click_probe / ui_input_probe / ui_contrast_probe 全绿 · doc 三闸零新增 · 熵增闸 PASS;实机目检清单过 | -> ⚠️ **基线纪律**:起草时点 master 头 = 413c936,冒烟 772/0,run_all 14/15(reward_overlay_test 基线红归迭代 15②批,本批守零新增)。T0 重测回填。 +> ⚠️ **基线纪律(v1.1 换代)**:起草时点 master 头 = 413c936(冒烟 772/0);**v1.1 修订时点现行头 = ed38c54**,中间落了整个 iter16ui 批(12+ 提交触 ui_kit.gd,F3 行号全部漂移失效)+ M4 移批,现行基线读数=冒烟 782/0、run_all 17 套件、**ui_contrast_probe 已入 probe_gate 默认必跑段**(新增文字层必须过对比度机检)。§2 行号系 413c936 时点考证,T0 按现行 HEAD 重取回填,禁止沿用本文行号与数字。 ## 2. 现状事实(代码级考证,2026-10-05 起草时点;T0 须自行复核) @@ -32,14 +35,14 @@ |---|---|---| | F1 | 现行无暂停层 | 全仓无 pause/暂停 实现;局内模态层 = 图例 / 节点兑付 / 关卡画面 / 三选一 / 终局 / 商店 / 营地 / 设置 / 确认弹窗 / 调试浮窗(main.gd:332 登记册 + :242 z 序注记) | | F2 | 模态基础设施现成 | `modal_stack.gd` 状态机 + `_open_modal`/`_close_modal` 单口(main.gd:353 起)+ 输入锁 = `modal_stack.has_open()` 开态查询;新层照登记册追加即可,零新机制 | -| F3 | 公共件清单 | `presentation/views/ui_kit.gd`:`modal_shell`(:229)· `verb_button`(:343,图标+动词+tooltip 三件套)· `bcard`(:455,BCard 竖卡:色带/图标槽/价签/底注参数化)· `switch_toggle`(:484)· `styled_slider`(:489)· label / badge / icon_rect 等;色板 token(WARM / STONE / INK_ON_GOLD / 烛光黄徽章形制等)同文件单点 | +| F3 | 公共件清单 | `presentation/views/ui_kit.gd`:`modal_shell` · `verb_button`(图标+动词+tooltip 三件套)· `bcard`(BCard 竖卡:色带/图标槽/价签/底注参数化)· `switch_toggle` · `styled_slider` · label / icon_rect 等;色板 token(WARM / STONE / INK_ON_GOLD / CANDLE 等)同文件单点。**badge 工厂现状不存在(v1.1 勘误)**:营地钥石徽系 camp_overlay_view.gd:80-93 本地拼装(panel_flat+icon_rect+label)非公共件——处置=Q10 甲裁决,见 §3 组 A | | F4 | 按钮宽度纪律 | `UiKit.button(text, min_w=216)` 全站最小宽度基准 + SIZE_FILL(2026-10-05 ui-struct-probe 批);行板/角标/图标钮豁免 9 处先例在册;ui_struct_probe 已入轻闸默认必跑段(按钮互压/等宽/出屏/零尺寸断言) | -| F5 | 数据面现成 | `meta_progression.gd` 只读访问器齐全:`keystone_balance()`(:28)· `forge_level/forge_flat/forge_price`(:34-44)· `workshop_status()`(:90)· `is_item_unlocked`(:85)· `attune_camp_status()`(:370)· `attune_refresh_charges()`(:397)· `tree_camp_rows()`(:260)· Boss 击杀计数面(`boss_kill_count` 等)——暂停层只读消费,零核心层改动 | -| F6 | 钥石呈现现状 | 营地屏顶栏钥石徽(camp_overlay_view.gd:82-93,烛光黄底+深墨字徽章形制)与结算屏入账明细已落地;**战斗屏内 GP/钥石已迁出**(hero_info_panel_view.gd:4-6 注记)——局内想看钥石余额没有任何入口,正是 06 条残余面的由来 | +| F5 | 数据面现成 | `meta_progression.gd` 只读访问器齐全:`keystone_balance()`(:28)· `forge_level/forge_flat/forge_price`(:34-44)· `workshop_status()`(:90)· `is_item_unlocked`(:85)· `attune_camp_status()`(:370)· `attune_refresh_charges()`(:397)· `tree_camp_rows()`(:260);**Boss 击杀计数 `boss_kill_count` 实际在 `core/save_manager.gd:250`(v1.0 误记 meta_progression.gd,2026-10-06 勘误)**——暂停层只读消费,零核心层改动 | +| F6 | 钥石呈现现状 | 营地屏顶栏钥石徽(camp_overlay_view.gd:80-93,烛光黄底+深墨字徽章形制,**本地拼装**——badge 工厂化后迁入 ui_kit)与结算屏入账明细已落地;**战斗屏内 GP/钥石已迁出**(hero_info_panel_view.gd:4-6 注记)——局内想看钥石余额没有任何入口,正是 06 条残余面的由来 | | F7 | 营地三柜台现行构成 | 属性锻造(forge 三行)· 道具工坊(workshop 六件按总通关次数解锁)· 天赋树(迭代 12 批次二营地树 UI);另有同调装配面(attune)。暂停层展示范围见 P3 裁点 | | F8 | 设置面先例 | `settings_overlay.gd` = 最小设置面(音量/全屏/减动效),SettingsOverlay 实例由 main.gd:260 创建并登记模态——暂停层与其关系见 P2 裁点 | | F9 | 减动效开关 | 已转正(迭代 8 卡④,settings_store 承载);排播队列与轮横幅在减动效开时延迟归零——暂停层进出呈现须遵守同一开关(P4 裁点) | -| F10 | 调试浮窗先例 | debug_overlay(迭代 15①)= 悬浮 Dialog 形态、DS_DEBUG 门控、仅开始菜单可开——其「角标入口 + Dialog 层」形态是暂停层的最近先例,但门控与可达域不同(暂停层 = 玩家常态功能,对局内可达) | +| F10 | 调试浮窗先例 | debug_overlay(**迭代 14.5 调试整合批,git 48c0b34**——v1.0 误记迭代 15①,2026-10-06 勘误)= 悬浮 Dialog 形态、DS_DEBUG 门控、仅开始菜单可开——其「角标入口 + Dialog 层」形态是暂停层的最近先例,但门控与可达域不同(暂停层 = 玩家常态功能,对局内可达) | ## 3. 改动清单(两组) @@ -47,15 +50,16 @@ | 文件 | 改动 | |---|---| -| `presentation/views/pause_overlay.gd`(新) | 只读覆盖层:标题区 + 钥石余额行(复用营地同款徽章形制:icon_rect(TEX_ICON_KEYSTONE) + label,色板 token 取现行值)+ 三柜台进度区(范围随 P3)+ 关闭钮;全部经 UiKit 工厂件拼装,零自绘 | +| `presentation/views/ui_kit.gd` | **新增 badge 工厂件(Q10 甲裁决)**:钥石徽形制(icon_rect+label 徽章)参数化收编;营地钥石徽(camp_overlay_view.gd:80-93 本地拼装)同步迁入工厂调用,消单点重复——营地侧改动仅替换拼装为工厂调用,观感零变化 | +| `presentation/views/pause_overlay.gd`(新) | 只读覆盖层:标题区 + 钥石余额行(badge 工厂件:icon_rect(TEX_ICON_KEYSTONE) + label,色板 token 取现行值)+ 三柜台进度区(范围随 P3)+ 关闭钮;全部经 UiKit 工厂件拼装,零自绘 | | `main.gd` | 实例化 + 登记 modal_stack 登记册(z 序位置随 P2)+ 打开/关闭接线 | ### 组 B · 入口与断言 | 文件 | 改动 | |---|---| -| 入口挂点(随 P1 裁决:战斗屏 HUD / 关卡地图屏 / Esc 通道) | 入口控件复用 verb_button 或角标先例(debug 角标形制),宽度纪律遵守 min_w=216 或申请豁免(角标类已有豁免先例) | -| `smoke/ui_struct_probe.gd` / `ui_click_probe.gd` / `ui_input_probe.gd` | 暂停层结构断言(不出屏/零尺寸/按钮宽度)+ 打开关闭行为断言 + 只读性断言(层内无购买信号连接) | +| 入口挂点(P1 已裁丁=**常驻角标**,Esc 不落码) | 战斗屏 HUD 与关卡地图屏各一角标(复用 debug 角标形制);**装配落点 T0 复核后在 ui_assembler / 两屏装配段中定点(白名单内决断)**;宽度纪律遵守 min_w=216 或申请豁免(角标类已有豁免先例)。**入口守卫(v1.1 补)**:其他模态(商店/三选一/营地/设置等)开启时角标置灰不可点(has_open 守卫)——iter16ui 后商店/三选一系全屏 page_shell 会盖住角标,守卫防「看得见点不着」态 | +| `smoke/ui_struct_probe.gd` / `ui_click_probe.gd` / `ui_input_probe.gd` | 暂停层结构断言(不出屏/零尺寸/按钮宽度)+ 打开关闭行为断言 + 只读性断言(**口径(v1.1 补)=巡检层内控件的 signal 连接清单(get_signal_connection_list),断言无任何指向购买/解锁/写入路径的连接;巡检信号名清单 T2 落码时列全进探针**) | | `smoke_all.gd` | 数据一致性断言(暂停层投影 = MetaProgression 访问器读数) | ## 4. 范围边界(🔴 不可越界) @@ -76,8 +80,8 @@ | 🔴 R1 | **公共件单点**:一切面板/按钮/徽章/卡片经 UiKit 工厂产出;禁裸色值字面量、禁自建 PanelContainer 样式 | ui_struct_probe + 静态审查 | | 🔴 R2 | **模态登记纪律**:新层必须登记 modal_stack 登记册,z 序=树序=登记序(缺陷⑤纪律);禁绕模态栈直开(迭代 6 卡⑨事故先例) | ui_click_probe 断言 | | 🔴 R3 | **输入锁语义**:暂停层开启时棋盘/节点输入锁定(has_open 开态查询),关闭后恢复原屏交互,无状态残留 | ui_input_probe 断言 | -| 🔴 R4 | **数据只读同源**:展示值一律实时读访问器,禁止开层时拷贝快照后长期持有(余额在层开启期间不变化的前提是层内无操作——快照可接受但须随 P3 一并披露口径) | 冒烟断言 | -| 🔴 R5 | **减动效遵守**:层进出若有动效,减动效开 = 动效归零(settings_store 现行开关消费) | 探针双态断言 | +| 🔴 R4 | **数据只读同源**:展示值一律实时读访问器;**快照口径(v1.1 收敛,原句先禁后允系起草矛盾)**——开层时预取快照仅允许在**单次开层生命周期**内使用,禁跨开层持有,采用快照时须在 T4 目检材料中披露口径(读哪几个访问器、何时取值) | 冒烟断言 | +| 🔴 R5 | **减动效遵守**:P4 已裁零动效直开直关——本契约收敛为「**零动效路径存在断言**」(探针断言层进出无过渡动画节点/时长归零路径),减动效开关无新增消费 | 探针断言 | ## 6. 任务卡(逐卡一个 loop 轮次) @@ -87,14 +91,14 @@ | **T1** | 组 A:pause_overlay.gd 本体(按 P1-P4 裁决落形态)+ main.gd 登记接线 | 1.0d | 冒烟 + ui_struct_probe | 层可开关;结构断言绿 | | **T2** | 组 B:入口挂点 + 三探针断言(结构/点击/输入)+ 冒烟数据一致性断言 | 0.75d | 冒烟 + 三探针 | §1.2 判定 1/2/4 | | **T3** | 重闸:全量轻闸(含 ui_struct/ui_click/ui_layout)+ doc 三闸 + 熵增闸 | 0.25d | 重闸全绿 | §1.2 判定 5 前半 | -| **T4** | 人闸:实机目检清单(双屏入口/只读性/数据正确/减动效双态/与设置面关系)+ 代拍板追认 | 0.5d | 目检过 | §1.2 判定 5 | +| **T4** | 人闸:实机目检清单(双屏入口/**其他模态开着时角标守卫态**/只读性/数据正确/零动效直开直关/**开层时排播队列排空副作用观察**〔_open_modal 开层前 _flush_row_queue 系既有层同款行为,与玩家「暂停」预期之差记入目检确认项〕/与设置面关系)+ 代拍板追认 | 0.5d | 目检过 | §1.2 判定 5 | | **T5** | 收口五步 + 06 状态行收口 | 0.25d | — | 收口五步齐 | > 估时合计 ≈ **3.0d**,贴软时间盒上限——知情预挂:T2 末为天然切分点,超限按 L3 呈报不擅切。 ## 7. 验证闸 -- **轻闸**(每轮):run_smoke 全量 + ui_input_probe + ui_struct_probe(本批触 presentation,三轮加 ui_click_probe + ui_layout_probe)。 +- **轻闸**(每轮):run_smoke 全量 + ui_input_probe + ui_struct_probe + **ui_contrast_probe(v1.1 补——已入 probe_gate 默认必跑段,本批新增文字层必须过对比度机检)**(本批触 presentation,三轮加 ui_click_probe + ui_layout_probe)。 - **重闸**(T3):全量轻闸 + doc 三闸零新增 + 熵增闸。 - **MC**:纯呈现批,零数值改动 → **零 MC**(两入口铁规 + 无数值改动禁跑 MC)。 @@ -109,6 +113,8 @@ ### P1 · 暂停入口位置与唤起形态 +**✅ 裁决(2026-10-06 用户令):案丁**——常驻角标为主入口,Esc 附加快捷通道登记 06 排期 Beta 随键盘批统一接(**06 登记已于 2026-10-06 联审批补办落文**——v1.0 裁后未执行,属行政漏步;另注:该登记同时使 06 键盘条目「弹层界面现行全鼠标」清单 +1 项,属本批连带披露)。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| | 甲 | 战斗屏与关卡地图屏各加一个角标按钮(复用 debug 角标形制,玩家常态可见、无门控) | 发现性最好,触屏/鼠标直点;代价是两个屏各加一个常驻控件 | @@ -118,6 +124,8 @@ ### P2 · 与既有弹窗层的关系(modal 登记序位置与遮罩) +**✅ 裁决(2026-10-06 用户令):案甲**——登记设置层同档带 dim 遮罩,开启时不可再开其他层。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| | 甲(推荐) | 登记在设置层同档(登记册尾段、确认弹窗之前),带 dim 遮罩(modal_shell 现行 dim 参数),开启时不可再开其他层 | 与设置/确认同语义档,z 序行为可预期;modal_shell 现成 | @@ -126,6 +134,8 @@ ### P3 · 展示内容范围 +**✅ 裁决(2026-10-06 用户令):案甲**——钥石余额 + 三柜台进度。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| | 甲(推荐) | 钥石余额 + 三柜台进度(属性锻造三行已购等级 / 道具工坊六件解锁态 / 天赋树已购概览) | 与 06 条「钥石余额与解锁态(营地三柜台进度)」字面一致 | @@ -134,6 +144,8 @@ ### P4 · 减动效联动口径 +**✅ 裁决(2026-10-06 用户令):案甲**——进出零动效直开直关。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| | 甲(推荐) | 暂停层进出默认零动效(直开直关),减动效开关无新增消费 | 最简且天然合规;只读查看层本无演出需求 | diff --git a/design/research/任务书-生涯界面设计立项-2026-10-05.md b/design/research/任务书-生涯界面设计立项-2026-10-05.md index 420d206c..f0f4d1ac 100644 --- a/design/research/任务书-生涯界面设计立项-2026-10-05.md +++ b/design/research/任务书-生涯界面设计立项-2026-10-05.md @@ -1,8 +1,9 @@ -# 任务书:生涯界面设计立项(v1.0 设计提纲版,只做设计不做实装) +# 任务书:生涯界面设计立项(v1.1 设计提纲版,零待裁点,开工门=U0 用户发令) -> 成文 2026-10-05。**性质**:纯设计批——产出「生涯」界面的设计提纲(信息架构 + 承载内容清单 + 与既有界面的关系 + 分期实装建议 + 待裁点),**本批零实装、零代码、零数据面改动**。 +> 成文 2026-10-05,v1.1 修订 2026-10-06(五任务书联审:F4 事实改正——recent_summaries 现役在存、P2 甲口径重述〔Q5 甲〕、总局数/通关局数标注涉存档 schema、营地既有最近冒险清单列入设计稿必答项、N 候选补「维持 10」、文件名与行号勘误、UiKit 对齐与 save v6 义务补登记)。 +> **性质**:纯设计批——产出「生涯」界面的设计提纲(信息架构 + 承载内容清单 + 与既有界面的关系 + 分期实装建议 + 待裁点),**本批零实装、零代码、零数据面改动**。 > 事实源 = [06-待办清单](../gdd/06-待办清单.md)「『生涯』界面——冒险中数据回看的唯一承接处」条(2026-10-04 用户裁决 P-C7:冒险中不给数据回看,历史战报/结算明细的查看去向 = 新「生涯」界面;生涯未做前冒险中无历史回看入口 = 已知代价,用户知情裁决)。 -> 归属迭代:**迭代 17 · 卡④**(迭代 17 四卡:①敌人表行集多行批 ②M4 数值批 ③暂停层只读入口批 ④本批)。 +> 归属迭代:**迭代 17 · 卡④**(迭代 17 现=卡⓪数据驱动扩展批+①敌人表行集多行批+③暂停层只读入口批+④本批;M4 数值批已移迭代 18,roadmap 现行口径)。 > **实装批另行立项**:本任务书只交付设计提纲文档与裁点呈报;任何实装(数据面/界面/存档域)须用户审毕提纲后另行发令立项。 > 满足 loop engineering 八件(按设计批形态适配,见 §9 逐条对照)。 @@ -28,14 +29,14 @@ | # | 事实 | 读数 | |---|---|---| -| F1 | 冒险中回看已退役 | M9(P-C7 裁)落码:`battle_end_overlay.gd:90` 明细回看钮退役、`review_pressed` 信号退役;`battle_log_view.gd:47` 战报带改单行「上轮摘要(回看归「生涯」)」、:115 带点击回看入口退役;`ui_assembler.gd:67` 注记「明细回看 overlay 已随 M9/P-C7 退役——冒险中回看归「生涯」06 登记」 | +| F1 | 冒险中回看已退役 | M9(P-C7 裁)落码:`battle_end_overlay.gd:106` 明细回看钮退役(v1.0 写 :90 系漂移,:90 现为 GP 账目行;signal 退役注记在 :9)、`review_pressed` 信号退役;`battle_log_view.gd:47` 战报带改单行「上轮摘要(回看归「生涯」)」、:115 带点击回看入口退役;`ui_assembler.gd:67` 注记「明细回看 overlay 已随 M9/P-C7 退役——冒险中回看归「生涯」06 登记」 | | F2 | 遗留文件 | `presentation/views/settlement_review_overlay.gd` 文件本体仍在,冒险中已无入口(处置见 P5 裁点:实装期复活承接还是退役删除) | -| F3 | 跨局持久面 = save v5 meta 域 | `keystone_balance` / `forge_levels` / `item_unlocks` / `level_clear_counts` / `boss_first_kill` / `boss_kill_count` / `tree_nodes` / `fork_sides` / `attunement_equipped` / `attune_refresh_level` / `first_adventure_done`(save_manager.gd:39 schema 注记 + meta_progression.gd 访问器面) | -| F4 | 🔴 **无历史冒险记录面** | `settle_adventure()`(meta_progression.gd:129)只做入账(钥石余额累加、工坊解锁刷新、通关计数),**每局摘要不留痕**——历史冒险回看需要新建数据面(每局摘要的持久化域),这是本设计提纲最大的数据设计题 | +| F3 | 跨局持久面 = save v5 meta 域 | `keystone_balance` / `forge_levels` / `item_unlocks` / `level_clear_counts` / `boss_first_kill` / `boss_kill_count` / `tree_nodes` / `fork_sides` / `attunement_equipped` / `attune_refresh_level` / `first_adventure_done` / **`recent_summaries`**(save_manager.gd:39 schema 注记 + meta_progression.gd 访问器面;v1.0 键集漏列 recent_summaries,v1.1 补) | +| F4 | ⚠️ **历史冒险记录面现状(v1.1 事实改正——v1.0 误记「无历史冒险记录面」)** | **`recent_summaries` 现役在存**:`save_manager.gd:40` `const SUMMARY_CAP := 10`(注释「最近冒险摘要保留条数(最新在前)」)、`:325-338` `apply_adventure_summary()` 逐局 push_front 归档(附 boss_drops 副本)、`:316` `recent_summaries()` 访问器;`settle_adventure()`(meta_progression.gd:129)**经** `apply_adventure_summary` 留痕(:125 注记「keystone_drops 副本随摘要入 recent_summaries」);registry entities.yaml:73 meta 域九件明列。**真实缺口只有三件**:①cap 10→N 扩容(P2 甲);②摘要五件事(结局/到达关卡/钥石合计/种子)**无「用时」字段**——全仓 grep elapsed/ticks/duration 零采集点,补用时=新增埋点;③**冒险总局数/通关局数无 lifetime 计数域**(`first_adventure_done` 是布尔、`level_clear_counts` 按关计次反推不出局数、cap 10 摘要不能累 lifetime)——总局数/通关局数须新增计数域,**涉存档 schema(save v6 迁移义务)**,归期一实装批 | | F5 | 当局结算呈现仍在 | 冒险收束时结算屏入账明细(钥石 +N、新解锁)现行保留——退役的只是「局后任意时刻回看」,不是收束当下 | -| F6 | 跨局汇总数据源现成 | 总通关次数(level_clear_counts 按关)、Boss 首杀/总击杀、钥石余额与累计流(余额在,累计收支无流水)、锻造/工坊/天赋树解锁态——「跨局汇总」页大部分字段可直接读访问器,零新数据面 | +| F6 | 跨局汇总数据源现状 | 总通关次数(level_clear_counts 按关)、Boss 首杀/总击杀、钥石余额与累计流(余额在,累计收支无流水)、锻造/工坊/天赋树解锁态——大半字段可直接读访问器;**「冒险总局数 / 通关局数」两格无数据源(v1.1 勘误——v1.0 误标「全现成·零新数据面」)**:须新增 lifetime 计数域,涉存档 schema,见 F4③ | | F7 | 图鉴承接的候选内容 | 06 在册相关条目:「敌方技能图鉴 / 学习呈现」(P2,见过→认得→记谱);奖励格图鉴柜台空置(2026-10-04 配比预设批裁决该柜台承接配比容量,后随配比预设整体挂起——柜台仍空);秘宝/奇物均无图鉴面 | -| F8 | 相邻界面分工现状 | 营地 = 跨局成长**操作**面(花钥石买强化);主菜单 = 入口面(新冒险/继续上局/营地/设置/调试);设置面 = 环境开关;教学「帮助」手册(Beta 批初裁:全部内容放一个文档,未实装);暂停层(迭代 17 卡③)= 局内只读查看钥石与解锁进度 | +| F8 | 相邻界面分工现状 | 营地 = 跨局成长**操作**面(花钥石买强化)**+ 既有只读「最近冒险」清单**(camp_overlay_view.gd:431 `_rebuild_recent()` 渲染逐局「完成/放弃/失败 · 通关 N 关 · 钥石 +N · 种子 N」,camp_shop_presenter.gd:85 投影 `"recent"`——生涯期二落地后此清单去留/迁移/去重是设计稿必答项,v1.1 补);主菜单 = 入口面(新冒险/继续上局/营地/设置/调试);设置面 = 环境开关;教学「帮助」手册(Beta 批初裁:全部内容放一个文档,未实装);暂停层(迭代 17 卡③)= 局内只读查看钥石与解锁进度 | | F9 | 06 条目原文口径 | 「跨局汇总 / 历史冒险回看 / 图鉴承接」三段内容清单是 06 条目给定骨架,本提纲在其上展开,不另起炉灶 | | F10 | 阶段语境 | 教学引导五条目已整体改排 Beta(2026-10-04 拍板);生涯未标阶段——分期建议须回答「生涯第几期落在 Alpha 后哪个窗口」 | @@ -61,15 +62,15 @@ | 段 | 内容 | 数据源 | 可行性 | |---|---|---|---| -| 跨局汇总 | 冒险总局数 / 通关局数 / 各关通关次数 / Boss 首杀与总击杀 / 钥石余额(累计收支流水可选)/ 三柜台解锁完成度概览 | F6 全现成(除累计流水) | **期一即可做,零新数据面**(累计流水若做需补记账点,随 P2) | -| 历史冒险回看 | 逐局摘要:第 N 局、结局(通关/失败/放弃)、走到哪、钥石收入、关键构筑(可选);单局结算明细回看(复活 settlement_review 承载) | F4 **需新建数据面**(每局摘要持久化) | **期二**,数据面范围是最大裁点(P2) | -| 图鉴承接 | 敌人图鉴(06 在册 P2 条目承接:见过→认得→记谱)/ 秘宝图鉴 / 奇物图鉴 / 奖励格图鉴(空置柜台) | 逐类立「见过」记账面 | **期三**,范围与顺序随 P3 | +| 跨局汇总 | 冒险总局数 / 通关局数 / 各关通关次数 / Boss 首杀与总击杀 / 钥石余额(累计收支流水可选)/ 三柜台解锁完成度概览 | F6 大半现成;**总局数/通关局数两格需新增 lifetime 计数域(涉存档 schema,F4③)** | 期一可做,但**非「零新数据面」**——两个计数域随期一实装批走 save v6 迁移(v1.1 勘误) | +| 历史冒险回看 | 逐局摘要:第 N 局、结局(通关/失败/放弃)、走到哪、钥石收入、关键构筑(可选)、用时;单局结算明细回看(复活 settlement_review 承接) | **F4 现役 recent_summaries(cap 10)+ 扩容 N + 补用时埋点(P2 甲口径重述)** | **期二**,数据面改动=扩容+加字段+迁移旧档摘要(P2) | +| 图鉴承接 | 敌人图鉴(06 在册 P2 条目承接:见过→认得→记谱)/ 秘宝图鉴 / 奇物图鉴 / 奖励格图鉴(空置柜台) | 逐类立「见过」记账面=**新增 meta 域,涉存档 schema(save v6 迁移义务,v1.1 补)** | **期三**,范围与顺序随 P3 | ### 3.3 与既有界面的关系(分工一句话矩阵) | 界面 | 管什么 | 与生涯的边界 | |---|---|---| -| 营地 | 跨局成长的**操作**(买/装配/点树) | 营地不改生涯只读定位;生涯不做任何购买入口,「去买」按钮也不放(跳转型入口随 P4) | +| 营地 | 跨局成长的**操作**(买/装配/点树)+ 既有只读「最近冒险」清单(F8) | 营地不改生涯只读定位;生涯不做任何购买入口,「去买」按钮也不放(跳转型入口随 P4);**营地「最近冒险」清单的去留/迁移/去重是设计稿必答项(v1.1 补)**——期二落地后两处逐局列表并存还是营地侧撤下并入生涯,设计稿须给明确建议 | | 暂停层(迭代 17 卡③) | **局内**只读看钥石与解锁进度 | 暂停层看「这一局进行中的当前值」,生涯看「跨局累计与历史」;局内是否给生涯入口随 P4 | | 结算屏 | 当局收束当下的入账明细 | 收束当下保留;生涯承接的是收束之后的任意时刻回看 | | 帮助手册(Beta 批) | 规则与玩法的说明文档 | 手册讲「怎么玩」,生涯讲「我玩过什么」;图鉴条目文案与手册条目是否共用内容源,归 Beta 批一并裁(本提纲只登记接口) | @@ -79,9 +80,11 @@ | 期 | 内容 | 前置 | 建议窗口 | |---|---|---|---| -| 期一 | 跨局汇总页(零新数据面,纯只读投影)+ 生涯入口落位 | 本提纲裁决完毕 | Alpha 后最近一个 UI 窗口(可与暂停层批同迭代序列后续) | -| 期二 | 历史冒险回看(新建每局摘要数据面 + 逐局列表 + 单局明细,settlement_review 复活承接随 P5) | 期一 + P2 数据面裁决 | 期一落地后独立批 | -| 期三 | 图鉴承接(范围与顺序随 P3) | P3 裁决 + 对应「见过」记账面 | Beta 窗口候选(与教学引导同阶段评估——「认得」轴与教学相关) | +| 期一 | 跨局汇总页(大半只读投影;总局数/通关局数两个 lifetime 计数域走 save v6 迁移)+ 生涯入口落位 | 本提纲裁决完毕 + **实装批前置义务(v1.1 补):对齐 iter16ui 后现行 UI 基线**——FS_* 六档字号/TG_* 语义色/BUTTON_MIN_H=40/ui_kit 色板单源/page_shell 模态栈纪律(与卡③任务书同款红线);UI 基线对齐义务由本提纲登记、实装批承接 | Alpha 后最近一个 UI 窗口(可与暂停层批同迭代序列后续) | +| 期二 | 历史冒险回看(recent_summaries 扩容 N+补用时埋点+旧档摘要迁移 + 逐局列表 + 单局明细,settlement_review 复活承接随 P5;营地「最近冒险」清单去留一并落地) | 期一 + P2 数据面裁决 | 期一落地后独立批 | +| 期三 | 图鉴承接(范围与顺序随 P3;「见过」记账面新增 meta 域=**save v6 迁移义务**) | P3 裁决 + 对应「见过」记账面 | Beta 窗口候选(与教学引导同阶段评估——「认得」轴与教学相关) | + +> 本地化文案权(v1.1 补):生涯界面本体按钮/页签/空态文案归「设置与本地化」线(全仓无 i18n 实装,先例=教学文案权登记),图鉴条目文案与手册共源已在 §3.3 登记接口——实装批承接。 ### 3.5 本批不做 @@ -131,13 +134,17 @@ ### P0 · 提纲与正主载体 +**✅ 裁决(2026-10-06 用户令)**:案甲——提纲直接落 design/gdd/systems/跨局循环/生涯.md(状态=范围草案),同步 02-系统清单.md(**根索引正名,v1.0 误写「02-系统清单索引」,全仓无此文件名**)。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| -| 甲(推荐) | 提纲成文即落 `design/gdd/systems/跨局循环/生涯.md`(状态=范围草案),同步 02-系统清单索引 | 「吸收即死」纪律:中间文档不留;正主早立早受益(R3) | +| 甲(推荐) | 提纲成文即落 `design/gdd/systems/跨局循环/生涯.md`(状态=范围草案),同步 02-系统清单.md 根索引 | 「吸收即死」纪律:中间文档不留;正主早立早受益(R3) | | 乙 | 提纲暂存 design/research/ 待评审后再转正 | 评审前不动 GDD 树;淘汰候选:与「吸收即死」纪律冲突,且评审意见最终还是要落正主,多一次搬运 | ### P1 · 生涯入口位置 +**✅ 裁决(2026-10-06 用户令)**:案甲——主菜单一级入口,与营地平级。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| | 甲(推荐) | 主菜单一级入口,与营地平级 | 「生涯 = 跨局数据的家」与营地同级最直白;主菜单按钮组有现行形制可复用(实装期) | @@ -146,14 +153,18 @@ ### P2 · 历史冒险回看的数据面范围(期二核心裁点,本批只呈框架) +**✅ 裁决(2026-10-06 用户令:案甲;同日 Q5 甲二裁口径重述)**:每局一行摘要(结局/到达关卡/钥石收入/用时),上限存最近 N 局;N=20/50 实装期按存档体积读数定。**口径重述(v1.1——一裁建立在 v1.0「无历史冒险记录面」误记上,实际 recent_summaries 现役 cap=10)**:本裁的实际工作量=①**扩容 cap 10→N**(非新建数据面,向后兼容);②**补「用时」字段埋点**(现无任何采集点,属新增数据面改动,随期二);③旧档既有 ≤10 条摘要原样保留迁移;④N 候选补第三案「**维持 10 不动**」——实装期读数三选一(20/50/维持 10),读数方法(谁测、体积门槛几何、何时测)与设计稿参数化维度(列表形态/滚动/分页/上限提示)由设计稿给出、实装期执行。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| -| 甲 | 每局一行摘要(结局/到达关卡/钥石收入/用时),上限存最近 N 局(N 候选 20/50) | 存储最小、实现最简;回看粒度到「局」不到「战」 | +| 甲 | 每局一行摘要(结局/到达关卡/钥石收入/用时),上限存最近 N 局(N 候选 20/50/维持 10) | 存储最小、实现最简;回看粒度到「局」不到「战」;现役面只需扩容+补字段 | | 乙 | 甲 + 每局完整结算明细留痕(逐行结算单据全存) | 回看最完整;淘汰候选:单局结算单据量大,存档体积与 schema 复杂度显著上升,且 99% 的回看诉求摘要即可满足 | | 丙 | 不存历史,历史回看段整段取消(生涯只做汇总+图鉴) | 淘汰候选:06 条目明列「历史冒险回看」,且 P-C7 裁决的代价(冒险中无回看)正是由它补偿 | ### P3 · 图鉴承接的内容范围与顺序(期三核心裁点) +**✅ 裁决(2026-10-06 用户令)**:案甲——敌人图鉴优先(06 在册条目承接),秘宝/奇物随后;奖励格图鉴只登记不激活。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| | 甲 | 敌人图鉴优先(06 在册 P2 条目「敌方技能图鉴/学习呈现」直接承接),秘宝/奇物随后 | 在册条目有先约,敌人是玩家信息需求最硬的一类 | @@ -162,6 +173,8 @@ ### P4 · 局内是否给生涯入口 / 生涯内是否放「去营地」跳转 +**✅ 裁决(2026-10-06 用户令)**:案甲——均不放:局内只看暂停层,生涯纯只读零跳转。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| | 甲(推荐) | 均不放:局内只看暂停层(卡③),生涯只在主菜单;生涯内纯只读零跳转 | 分工矩阵最干净;暂停层与生涯零重叠 | @@ -170,6 +183,8 @@ ### P5 · settlement_review_overlay.gd 遗留文件处置(实装期执行,本批只裁方向) +**✅ 裁决(2026-10-06 用户令)**:案甲——期二实装批复活承接(单局明细回看呈现载体,改挂生涯)。 + | 案 | 内容 | 淘汰/保留依据 | |---|---|---| | 甲(推荐) | 期二实装批复活承接(作为单局明细回看的呈现载体,改挂生涯) | 现成件,结算明细的排版逻辑都在;复活成本远低于新画 | @@ -203,6 +218,6 @@ 1. design/gdd/06-待办清单.md「『生涯』界面——冒险中数据回看的唯一承接处」条(P-C7 裁决原文指路) 2. design/research/UI重设计原型v2落码-差异对照与改动计划-2026-10-04.md §七(P-C7 裁决记录出处) 3. game/presentation/views/battle_end_overlay.gd / battle_log_view.gd / settlement_review_overlay.gd(退役与遗留实态) -4. game/core/meta_progression.gd settle_adventure + save_manager.gd:39(跨局数据面与「无历史记录面」考证点) +4. game/core/meta_progression.gd settle_adventure + save_manager.gd:39-40/:316/:325-338(跨局数据面与 recent_summaries 现役面考证点) 5. design/gdd/06-待办清单.md「敌方技能图鉴 / 学习呈现」条(图鉴承接的在册先约) ``` diff --git a/design/research/研究-内容数据驱动扩展架构-2026-10-06.md b/design/research/研究-内容数据驱动扩展架构-2026-10-06.md new file mode 100644 index 00000000..bea5c6ea --- /dev/null +++ b/design/research/研究-内容数据驱动扩展架构-2026-10-06.md @@ -0,0 +1,119 @@ +# 研究-内容数据驱动扩展架构-2026-10-06 + +状态:调研+提案,**零代码修改**,待用户拍板。 +问题原文(用户):「如果我们要对其物进行修改设计的话,是不是只要更改我们的配置文件就好了?如果是一些机制类的专长,也能够只改配置文件,不改动代码吗?我希望我们这里可以做得比较拓展一些……你可以学习一下各种支持mod扩展的游戏,他们是如何实现的。」 + +--- + +## 〇 结论(先答两问) + +1. **改已有物品的数值/名字/稀有度/投放渠道 → 是,只改配置表就能生效**(所有内容族都已是 `.tres` 数据表,加载期校验,运行时通用读取)。 +2. **给已有物品换一个全新效果行为 / 加一件带新效果的新物品 / 加机制类新专长 → 现状必须改代码**。因为「效果类型」是封闭枚举,每新增一个枚举值都要:改 schema 枚举 → 加一处 `match` 翻译分支 → 在结算代码里加消费点 → 在文案代码里加说明文字,共 3-4 处代码。 +3. 项目里**已经有一个完全做到「只改配置」的范本:技能池**。`skills.tres` 加一行技能、`enemy.tres` 的 `pool` 里引用它(Boss 半血解锁只需 `unlock_hp_pct: 50` 字段),全链路零代码。差距是把专长/秘宝/令牌提到同等水平。 + +--- + +## 一 现状盘点:各内容族「改配置能走多远」 + +内容全部在 `game/data/tables/*.tres`,schema 在 `game/data/schemas/`,统一清单装载于 `game/core/config_db.gd:12-31`;表里没有任何回调/脚本路径,行为全部用封闭枚举字符串键+参数字典表达。表内数据与「配置」的分离没有问题,问题出在代码侧对数据的解释方式。 + +| 内容族 | 定义处 | 只改配置能做什么 | 必须改代码才能做什么 | +|---|---|---|---| +| 技能(技能池) | `skills.tres` + `enemy.tres` pool | **加新技能整件**(伤害/增益/妨害、权重、冷却、Boss半血解锁)——全链路数据驱动 | 新的技能大类(现仅 basic/damage/buff/debuff 四类) | +| 状态效果 | `status_effects.tres` | 加新状态行(技能/奖励格引用它) | 新的状态机制形态 | +| 专长(奇物·规则改写类) | `feats.tres`(27行) | 加「挂点已存在」的新专长:数值、稀有度、流派标签随写随生效;同挂点多件自动累加 | 新挂点(hook)=4处代码;现 23 挂点封闭(`feat_table.gd:36-67`) | +| 秘宝/同调件 | `treasure_items.tres`(13行) | 加「效果类型已存在」的新件(如又一个 3×3 显影件);改充能/占槽/解锁谓词 | 新效果类型=改枚举+执行分支;**被动件按物品 id 硬编码**(见二B) | +| 奇物令牌(属性/资源) | `adventure.tres` | 改配额/数值 | 新令牌种类(分发键=代码 match,`adventure_table.gd:84-114`) | +| 格面资源 | `reward.tres` | 加新资源行(percent 类经 `effect_id` 路由进状态系统) | 新格面行为 | +| 局外天赋树 | `meta_tree.tres`(33节点) | 数值/价格/前置/上限 | 新规则键(现仅 2 个规则键 wired,`battle_orchestrator.gd:261-264`) | + +--- + +## 二 差距精确清单:效果行为硬编码在哪些代码位置 + +调查核证(file:line 均经抽查复核): + +**A. 专长:两段式,翻译段硬编码。** +- 求值段(半通用):`_feat_flags_for` 对 23 个 hook 逐个 `match` 展开(`battle_orchestrator.gd:101-179`)。 +- 消费段(硬编码):每个展开出的旗在结算代码里各有一处读取点——`damage_controller.gd` 13 处(rider_flat:253、nest_aura:257、round_dig:264、crack:276、shield:312、clamp:429-434、discount_shift:219-221、hit_trade:203/228、armor_roll_rewrite:209、spike:215/236 等);`battle_orchestrator.gd` 11 处(first_dig_spell:487、terrain_dig_double:519、flag_strike:537、dig_yield:604-615、chain_arrows:974-980 等)。 +- 流派档位(≥2件/≥4件质变)硬编码在 `battle_orchestrator.gd:171-175`。 + +**B. 秘宝:主动件半通用,被动件按 id 硬编码。** +- 主动件:`use_treasure` 按 `effect_kinds` 通用分发(`battle_orchestrator.gd:648-666`)——这部分是好的;但混有按 id 的特判(`dig_charge` 积点闸,:643-647)。 +- 被动件按件 id 硬编码:`venom_edge`(:248 → damage_controller.gd:225)、`luck_coin`(:249 → reward_economy.gd:195-207)、`dawn_order`(:272-273、:1098-1099)、`nest_counter`(:703-713)、`flag_burst`(:828-831)、`pick_refresh`(adventure_runner.gd:650)。 + +**C. 奇物令牌:** `_apply_curio_pick` 按 kind match(`adventure_runner.gd:412-449`)。 + +**D. 呈现文本硬编码(新内容会「无名无声」的直接原因):** +- 物品卡说明:`battle_narrator.gd:104-121`(令牌)、`:142-166`(专长逐 hook 文案)、`curio_interact_controller.gd:185-204`(秘宝只认 3 种 kind)。 +- 无本地化/文本表:`display_name` 内联在 .tres 里(这本身没问题),但**效果说明文字**没有一处在数据行里,全在代码里。 + +**E. 基础设施(新词=代码,但这是合理边界):** +- 谓词表计数键 `COUNTER_KEYS`(`attunement_growth_table.gd:22`)、存档域(版本迁移 `save_manager.gd:50-57`)、渠道白名单(`adventure_runner.gd:757-763`,设计规格 `design/gdd/systems/冒险循环/奇物.md:200-217`)。 + +**F. 已经在做对的事(不需要改,是改造的地基):** +- 加载期校验+交叉校验悬空引用即致命(`config_db.gd:106-148`、`config_db_cross_validator.gd`); +- 行键白名单/零裸flat护栏(`treasure_item_table.gd:51-53`、`feat_table.gd:76-79`)——这些封闭校验是设计护栏,改造要保留它们,改的只是「枚举值↔代码分支」的绑定方式; +- MC 模拟器直接实例化真实的 battle_orchestrator(`monte_carlo.gd:20-22`)——**效果执行一旦数据化,模拟器自动看到新内容,调平衡不用双写**。 + +--- + +## 三 支持 mod 的成熟游戏是怎么实现的(官方文档查证) + +| 游戏 | 内容加法 | 机制加法 | 机制要点 | +|---|---|---|---| +| Minecraft(数据包,官方) | 纯 JSON:配方/战利品表/进度/谓词/标签/附魔等几十类注册表 | 要写 Java mod | 战利品表=「条件对象+奖池条目」纯 JSON;进度=「触发器类型+条件+奖励」纯 JSON;谓词是跨系统复用的通用条件词汇 | +| Factorio(官方 mod API) | 数据阶段 prototype(Lua 表:物品/配方/实体)——官方内容本身就是 prototype | 要写运行阶段 Lua 脚本 | 数据/逻辑分两个阶段;运行期行为靠事件系统钩子 | +| RimWorld(官方 mod 体系) | XML Defs(defName 主键,加载期注册)+ XML 补丁操作改原版数据 | 要写 C# 程序集(Harmony 运行时补丁) | 纯数据 mod 能加特性/研究/动物等几乎所有「内容」;数据不够时才落代码,且补丁机制把改动隔离在数据层 | + +(来源:minecraft.wiki/w/Data_pack、wiki.factorio.com/Modding、rimworldwiki.com/wiki/Modding——前两者已逐条核对原文,RimWorld 维基反爬只取到入口页,机制按通行记载表述。) + +**四条共性,就是「改配置不改代码」的全部秘密:** + +1. **内容=数据,机制=解释器。** 代码里只有一套通用的「效果类型+参数」解释器;每件内容是一行数据,声明自己是什么类型、带什么参数。 +2. **游戏在稳定的时机对外喊一声(事件),内容声明「我在什么时机、什么条件下、发生什么效果」。** 时机词汇(开格/标旗/受击/击杀/轮末……)是封闭且稳定的,内容条目只是订阅者。 +3. **条件是通用词汇,跨内容复用**(Minecraft 谓词、RimWorld 的条件类、我们已有的谓词表雏形)。 +4. **加载期注册+校验闸兜底**:id 唯一、引用悬空、键出域,全部启动即报错——这一条我们已经在做。 +5. **边界同样存在**:Minecraft 数据包加不了新机制,Factorio 的新行为也要写 Lua。mod 游戏不是「零代码」,而是**把「新机制」压缩成「往词汇表里加一个类型」(一次性、低频),让「新内容」完全数据化**(高频、零代码)。 + +--- + +## 四 改造方案(备选案全列) + +### 案甲:最小修(清理性,约 1 天) +只消灭「按件 id 硬编码」(二B 被动件改造成 effect_kinds 通用分发)+ 把物品效果说明文字搬进数据行。 +- 收益:秘宝族达到「同类件纯配置」;新件不再无名无声。 +- 不解决:专长新挂点仍要改代码;令牌/树不变。 +- 风险:低。纯重构+文案搬家,冒烟+MC 对拍可验。 + +### 案乙:效果类型统一+时机埋点(推荐,约 3-4 天,可分期) +把专长/秘宝/令牌统一到「**触发时机 + 条件 + 效果类型+参数**」的数据形态: +1. 定一份跨内容通用的**效果类型词汇表**(伤害修正/开单/资源授予/状态授予/改写/计数器……)——现 23 hook+12 kind 是这份词汇表的初稿素材,也是主要工作量所在(熔火之躯的积层反打、矿工重锤的命中交换这类需要词汇表有计数器/轮末/改写能力)。 +2. 在结算段关键节点布**语义事件点**(开格/标旗/受击/击杀/轮末/Boss半血/使用道具……),一条内容行声明 `{on=时机, when=条件谓词, do=效果}`,由通用执行器解释。 +3. 保留全部现有护栏:封闭词表、键白名单、悬空引用致命、零裸flat禁令照旧,只是校验对象从「枚举在 schema 里」变为「词汇表条目存在且已实现」。 +4. MC 模拟器零改动自动受益(共用执行器)。 +- 分期建议:①专长(23 hook 收敛)→②秘宝被动件并入 →③令牌/树规则键。每期独立冒烟可落籍。 +- 风险:中。23 个消费点的等价性改写,需 MC 对拍锚+冒烟全绿兜底;触到 battle_orchestrator 历史脏件(惯例走 worktree 批)。 + +### 案丙:完整 mod 式(外挂脚本层,约 5 天以上) +案乙基础上允许数据行指向外挂 GDScript 效果脚本(Godot 支持运行时加载脚本),即真正意义上的第三方可扩展。 +- 收益:任何行为都可外挂,词汇表不再是边界。 +- 不建议现在做:导出安全、脚本沙箱、兼容矩阵、探针全覆盖都是额外工程;Alpha 前没有第三方 mod 需求,词汇表不够用时再加类型(案乙路径)成本远低于维护插件层。挂账待「mod 需求」立项再启。 + +**推荐:案乙,按 ①②③ 分期立项**(可并进现有 roadmap 排期,或作为迭代 17 候选卡)。 + +--- + +## 五 拍板点 + +1. **立项选择**:案甲(1天,只清秘宝)/ 案乙(3-4天,专长+秘宝+令牌全数据化,推荐)/ 案丙(挂账)。 +2. **排期位置**:并入迭代 17 候选序列,还是单独立卡插队。 +3. **案乙词汇表范围**:首批是否 23 hook+12 kind 全收敛,还是先收敛专长族试点后扩。 +4. **文本表值化是否随批**(牵 UI 呈现,battle_narrator/curio_interact_controller 两处)。 + +--- + +## 六 附:顺带发现(非本议题,仅登记) + +- 菜单开局路径 `main.gd:475` 未传 `attunement`/`tree_rules` 参数(营地装配后从主菜单开局可能丢同调装配/局外树规则;冒烟是直呼 runner 面故未拦住)——是否立项修复待用户令。 +- relic 词族与 feat 词族命名统一已在 `design/gdd/06-待办清单.md:101` 在册,案乙动 feats.tres 时可顺路处理或明确避让。 diff --git a/game/README.md b/game/README.md index d1d314aa..9b534113 100644 --- a/game/README.md +++ b/game/README.md @@ -494,7 +494,7 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/a --- -## 2026-09-16 敌人技能池批(批 1+2+3 合并落地——设计权威 docs/proposal-skill-pool.md v1.0;文档面 v0.5/v0.11/v0.5/v0.6/v0.3/v0.4 同批) +## 2026-09-16 敌人技能池批(批 1+2+3 合并落地——设计权威 proposal-skill-pool.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 技能池节〕 v1.0;文档面 v0.5/v0.11/v0.5/v0.6/v0.3/v0.4 同批) **机制**:确定性出招剧本(行为环:拍枚举/环指针/同种锁步/heavy_multiplier)整体退役,改技能池 + 开轮摇号——每轮轮次开始排除冷却中技能、按行序逐敌摇定本拍动作(behavior_roll 封闭流,每敌子流 scope = spawn_index×1000000+摇号序),战斗段按技能行兑现(伤害技 = 三乘法链〔基线×倍率→d20 掷点→甲效→伏击后置→池乘区→max(1,·)〕,增益技 = 施放行 cast + 状态授予〔0 伤害不掷不折不兑伏击〕);两道闸 = 全技能各自冷却(普攻恒 0 锁死)+ 保底 M=4(连续 4 拍执行普攻 → 下拍必出特殊技、不受冷却限制);死亡即不动手改"摇定后兑现前死亡 → 作废不进账";拍结构保留(每结算每存活敌恰行动一次/行序/先作用后推进)。 @@ -514,7 +514,7 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/a **实现决策登记(提案未定处的自行拍板,按封装纪律报告)**:① 保底 M 载体 = 技能表全局单字段 pity_threshold(不分敌种,难度曲线单旋钮);② 冷却"施放后连续 N 拍不可摇中"的实现 = 摇号按当前冷却值排除、递减在其后(与提案"递减→排除"的可摇集合逐拍恒等);③ 增益持续与技能冷却两计时器 = apply_effect duration_override 通道;④ 敌方防御池乘区落点 = 主角攻击 Σ三分量后(hero_offense 现行走普通分量直乘的切片现状不动——登记遗留);⑤ 摇号器类名 SkillLottery(core/skill_lottery.gd)。 -## 2026-09-19 技能池新效应词汇批(用户拍板——设计权威 docs/proposal-status-vocabulary.md v1.0) +## 2026-09-19 技能池新效应词汇批(用户拍板——设计权威 proposal-status-vocabulary.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 词汇表/增益状态.md〕 v1.0) **三词收敛两词**:妨害技(debuff 新类型)+ 必中附加分量(rider_mult,"固定值伤害"改形);多段命中经用户拍板退役(无防御磨损体系,无对照机制)。**实装面 = 仅 L2 Boss 蛛化卓尔精灵,其余敌种不动**: - 毒爆 venom_burst + `rider_mult = 0.75`——必中毒分量 = max(1, floor(atk × 0.75))(atk 开战掷定 2–4 → 毒 1–3 点),后置加法、必中全额、不进乘区不出暴击;伤害行 payload 增 `rider` 键 + 行文本「+附加N(必中)」。 @@ -548,3 +548,11 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://smoke/a **结账时机双模式(2026-09-24 用户三裁决之二三)**:目标 = 结账时刻照规则看现场解析(最前存活敌 / 主角 / 全体存活敌),开单不冻结对象;现行 = 段界结账(授予段尾 / 清图批同步点 / 战斗段尾统一兑现),开单即结保留为回切结构位(控制器 policy 常量一行切回=逐位还原迁移前行为)。同一输入两模式结果可不同(兑现时机语义差异),历史基线读数自本批以段界结账口径为准(重测归曲线调参批)。 **验证**:冒烟 557 项 0 失败双模式全绿三闸过;A/B 对拍探针四配置(L1/L2 × 双种子)两模式各自同种子逐位确定、四配置全部真实分岔(行数两两恒等、纯次序位移——分岔面 = 伤害行攒批位移与治疗满血判定时机,符合段界结账语义);三场景探针全 PASS。设计权威 = 战斗结算.md v0.24「伤害单据与伤害控制器」节。 + +## 2026-10-06 地图伤害账殿后批(案C,用户裁——「主角先手」歧义质询触发;设计权威 = 战斗结算.md v0.26) + +**裁决**:每轮结算内三者顺序定死 = ①授予段兑非伤害账(资源/增益/治疗/行动点/GP/警觉)→ ③战斗段「主角先基础攻击 → 敌人按生成序逐个还手 → 地图伤害账殿后」→ ④衰减 → ⑤判定。授予段不再当场兑付任何伤害——全部伤害形态单据(爆裂/直伤/反噬/地形变异/战旗军规/熔火反噬/惊巢反制/魔能共鸣/清障炸药等)暂存,战斗段怪物还手后按授予段事件序补提交统一结账;主角死于敌方还手 → 暂存单整体不兑,逐单留「未生效(主角已倒下)」行(载荷 forfeit=hero_down)。 + +**切片落点**:`battle_orchestrator.gd` 暂存闸 `_defer_board_damage`/`_deferred_board` + `_submit` 案C 分流(sync 同闸——清图批一致延后)+ `_outcome_of`(暂存单按「将兑付」回报——完美第二份/高光份数依据开单时点事实)+ 授予段四处直提改走闸 + 战斗段末补提交块(死亡守卫+留痕);授予段界 flush 保留(对空队 no-op 防御位);`smoke_all.gd` 行相位分类器换代(damage 恒战斗段/reward_ineffective 按 forfeit 分流)+ 先手断言收紧(「存在或授予段终局」→「必存在」)+ 新增案C 段内序断言(地图账不得先于主角先手或插入敌方还手中间)。 + +**验证**:冒烟 783 项 0 失败三闸过 + run_all 17 套全绿 + probe_gate(ui_input/ui_struct/ui_contrast/--click 行为走查)全绿、残留 0。**旧行为 MC 锚全数作废**(死亡短路语义变化:授予段伤害不再能致死短路先手;读数重采归数值批)。 diff --git a/game/core/action_economy.gd b/game/core/action_economy.gd index bceccfac..5b00fc60 100644 --- a/game/core/action_economy.gd +++ b/game/core/action_economy.gd @@ -35,19 +35,19 @@ var last_round_end: Dictionary: var _settlement_consumed := false # AC-14 门:notify_settlement_done 消费后才受理 round_start var _next_round_mode := "" # 调试观测量(同板续轮 / 换新图) var _pending_ap_delta := 0 # 下轮行动点修正排队(格面资源八型批 2026-09-20——action 型 ±1 排队,轮开始兑现后清零) -# 蓄力流运行态(迭代 12 T5——relic 件注入面;false=关着零扰动): -# feat_ap_carry=整备军备(轮末剩余 AP 转蓄力层替代 RAP 转存——cleared 作废口径改写的停手侧承载); -# feat_dig_extra=深挖钻头付费开掘对价(R4 甲非信息税形态;2026-10-04 试玩缺陷修复——对价挂付费 -# 开掘侧:AP 富余时挖掘付全价 1+extra 积付费计数、AP 不足时降级只付基础 1 照常开格不积计数—— -# 原「持有期所有挖掘无条件 +1」实现把对价错挂成普通挖掘税,AP=1 锁死开格);charge_layers=蓄力层账本。 -var feat_ap_carry := false -var feat_dig_extra := 0 +# 蓄力流运行态(迭代 12 T5 → 内容数据驱动扩展批 T3 换代:旧 feat_ap_carry/feat_dig_extra 注入旗 +# 退役——转蓄力=round_end grant_ap(mode:transfer) 订阅(finish_round_stop 定夺)、深挖对价=dig_paid +# counter_spend 订阅(request_dig 施加面);charge_layers=蓄力层账本) var _charge_layers := 0 var charge_layers: int: get: return _charge_layers -var _deep_paid_count := 0 -var deep_paid_count: int: - get: return _deep_paid_count +# 效果引擎(内容数据驱动扩展批 T1——dig_paid/charge_full 发射面;round_end 转存决策面) +var _fx: EffectEngine = null +var _last_stop_ap := 0 # 停手侧剩余 AP 记账(转存决策移至 round_end 发射点——grant_ap(transfer) 订阅定夺) + + +func set_fx(fx: EffectEngine) -> void: + _fx = fx func _init(db_ref: ConfigDb) -> void: @@ -87,17 +87,25 @@ func try_spend(action_kind: String) -> bool: # 排雷玩法 挖掘专用:chain_size = 连锁附加格数(不含首格);clears_map = 本次动作后全图安全格开完。 -# 深挖钻头付费开掘(2026-10-04 缺陷修复——对价挂付费侧):AP 富余(≥基础+extra)时本动作付全价 -# 并积付费开掘计数(orchestrator 深层产出配额取数面);AP 不足全价时**降级只付基础 1 照常开格**、 -# 不积计数——持有钻头永不锁死普通挖格(散件保底 B-2)。 +# 深挖钻头付费开掘(内容数据驱动扩展批 T3——dig_paid counter_spend 订阅施加面,2026-10-04 缺陷 +# 修复口径不变):按序消耗族——AP 富余(≥基础+对价)时本动作付全价并积本轮付费计数(效果引擎 +# 计数器=授予段追加产出配额取数面);AP 不足全价时 fallback 降级只付基础 1 照常开格、不积计数 +# ——持有钻头永不锁死普通挖格(散件保底 B-2)。 func request_dig(chain_size: int, clears_map: bool) -> bool: if _phase != Phase.ACTIVE: _reject("phase") return false var cost := _cost("dig") - if feat_dig_extra > 0 and _ap >= cost + feat_dig_extra: - cost += feat_dig_extra - _deep_paid_count += 1 + # dig_paid 发射+施加(内容数据驱动扩展批 T3——付费开掘发起、动作经济闸收对价前: + # counter_spend 命中=尝试加收对价并积计数;资源不足走 fallback/耗尽即止=只付常规基础对价) + if _fx != null: + for h in _fx.dispatch("dig_paid", {"ap_cost": cost, "ap_surplus": _ap - cost}): + if String(h.get("kind", "")) != "counter_spend": + continue + var extra := int(h.get("value", 1)) + if _ap >= cost + extra: + cost += extra + _fx.counter_add(String(h.get("counter_id", "")), 1) if _ap < cost: _reject("resource_face") return false @@ -137,21 +145,35 @@ func _spend_and_arbitrate(action_kind: String, clears_map: bool) -> bool: # 停手:剩余 AP 1:1 转存 RAP;行动经济 直发 stopped 路径 round_end(双发射源)。 # 整备军备改写(迭代 12 T5——feat_ap_carry=true 时剩余 AP 转蓄力层〔上限 3〕替代 RAP 转存: -# 「不动也是行动」——cleared 作废口径改写的停手侧承载;cleared 分支 AP 恒清零维持既有作废语义) +# 「不动也是行动」——cleared 作废口径改写的停手侧承载;cleared 分支 AP 恒清零维持既有作废语义)。 +# 内容数据驱动扩展批 T1:本方法改记账制——剩余 AP 存 _last_stop_ap 后清零,转存决策移至 +# round_end 发射点 finish_round_stop(grant_ap(mode:transfer) 订阅定夺;双轨期旧旗 feat_ap_carry +# 并行生效,行为与迁移前逐位一致)。 func request_stop() -> bool: if _phase != Phase.ACTIVE: _reject("phase") return false - if feat_ap_carry: - _charge_layers = mini(3, _charge_layers + _ap) - else: - _rap += _ap + _last_stop_ap = _ap _ap = 0 _finish_round("stopped", "same_board") round_end_stopped.emit({"path_tag": "stopped", "continuation_hint": "same_board"}) return true +# 轮末停手转存决策(round_end 发射点调用——编排器授予段尾):transfer=true=剩余 AP 转蓄力层 +# (上限 3,整备军备面);false=1:1 转存 RAP(缺省)。非停手轮(cleared/耗尽)_last_stop_ap=0 +# 两分支均零操作。转蓄后达上限的沿发 charge_full(R6 框架先行——现无订阅者)。 +func finish_round_stop(transfer: bool) -> void: + if transfer: + var prev_layers := _charge_layers + _charge_layers = mini(3, _charge_layers + _last_stop_ap) + if _fx != null and _charge_layers >= 3 and prev_layers < 3: + _fx.dispatch("charge_full", {"layers": _charge_layers}) + else: + _rap += _last_stop_ap + _last_stop_ap = 0 + + # 轮内刷新(2026-09-12 方案 A 用户拍板;等待期通道 2026-09-15 用户拍板整体移除):唯一入口 = # Active 相位——仅扣 RAP,相位与 AP 均不动。刷新不打断轮次、不触发结算;换图编排归 战斗结算 # (retire + install 立即执行,玩家同轮继续挖新图)。决策主体 = 玩家(经 HUD框架发起)纪律不变。 diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index 4f081e65..6f214914 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -75,6 +75,7 @@ var _treasure_unlocked := {} # aux 解锁门控集(迭代 7 var _attunement: Array = [] # 同调装配集(迭代 11 第①批 T5——params.attunement 接收面,start_level 外下传 LevelRunner) var _attune_refresh_x := 0 # 刷新件余量(迭代 11 第①批 T5b P1 甲——每碰三选一重置=X 表值;优先消耗、不结转) var _attune_refresh_y := 0 # Y 冒险周期池(开局参数面注入,用完即无——营地升级行表值) +var _curio_paid_refreshes := 0 # 本次停点已付费刷新次数(2026-10-06 用户令对齐商店 MC-1——GP 刷新价曲线输入,随停点创建清零=与商店进店清零同构) var attune_charges := {} # 同调主动件冒险充能账本(迭代 11 第①批 T6——跨关卡共享 dict;settlement use_treasure 扣减直改同引用;断点随 attunement_charges 键入档) var _save_manager = null # 断点承载注入(迭代 8 卡①——null = 不写断点;模拟/冒烟缺省) var _resume_bp := {} # 续玩快照暂存(resume_adventure → _start_current_level 单跳传递) @@ -424,19 +425,15 @@ func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String = _feats.append(feat_id) _curio_taken["relic"] = int(_curio_taken.get("relic", 0)) + 1 return true + # 加算形态路由(内容数据驱动扩展批 T5——match 收敛进效果类型:属性值提升四类走 + # adventure_table.CURIO_APPLY_ROUTES 声明路由(bonus 落点键),加新加算类令牌零代码) + var sink := db.adventure.curio_apply_route(kind) + if sink != "": + bonus[sink] = int(bonus.get(sink, 0)) + value + _curio_taken[kind] = int(_curio_taken.get(kind, 0)) + 1 + return true + # 通道面(副作用形态——显式分支) match kind: - "attack": - bonus["atk"] = int(bonus.get("atk", 0)) + value - _curio_taken["attack"] = int(_curio_taken.get("attack", 0)) + 1 - "max_hp": - bonus["max_hp"] = int(bonus.get("max_hp", 0)) + value - _curio_taken["max_hp"] = int(_curio_taken.get("max_hp", 0)) + 1 - "armor": - bonus["armor_class"] = int(bonus.get("armor_class", 0)) + value - _curio_taken["armor"] = int(_curio_taken.get("armor", 0)) + 1 - "ap": - bonus["ap"] = int(bonus.get("ap", 0)) + value - _curio_taken["ap"] = int(_curio_taken.get("ap", 0)) + 1 "gp": if _runner != null: _runner.grant_curio_gp(value) @@ -569,6 +566,7 @@ func node_curio_candidates() -> Dictionary: func present_node_curio(candidates: Array, treasure_detail: Array = []) -> void: _reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转) + _curio_paid_refreshes = 0 # 付费刷计数随停点清零(对齐商店进店清零——2026-10-06) _node_curio_pending = {"candidates": candidates.duplicate(true), "aux_detail": treasure_detail.duplicate(true)} @@ -647,7 +645,14 @@ func _seed_attune_charges() -> void: # 达上限剔牌/掷定前置直显;停点可中断)---- func _attune_refresh_x_total() -> int: - return int(db.treasure_item_row("pick_refresh").get("magnitude", 0)) if _attunement.has("pick_refresh") else 0 + # 内容数据驱动扩展批 T4——按 id 特判拆除:kind 分发查询(装配集内 effect_kinds 含 bonus_refresh + # 的行 magnitude 合计;刷新件类加件零 id 分支) + var total := 0 + for iid_v in _attunement: + var arow: Dictionary = db.treasure_item_row(String(iid_v)) + if (arow.get("effect_kinds", []) as Array).has("bonus_refresh"): + total += int(arow.get("magnitude", 0)) + return total func _reset_refresh_x() -> void: @@ -667,30 +672,53 @@ func node_curio_pending_state() -> Dictionary: } -# 三选一刷新(组合根唯一调用口;无停点/无余量 false 零副作用)。通关停点重抽后重发 +# 三选一刷新(免费位 X/Y——组合根唯一调用口;无停点/无余量 false 零副作用)。通关停点重抽后重发 # curio_choice_presented(呈现层重建卡片+刷新余量);节点停点由组合根读 node_curio_pending_state 重 present。 func refresh_curio() -> bool: - if _attune_refresh_x <= 0 and _attune_refresh_y <= 0: + return _refresh_curio_common(false) + + +# 三选一 GP 付费刷新(2026-10-06 用户令对齐商店 MC-1——免费位 X/Y 用尽后转付费,同旋钮同曲线同钱包; +# 组合根先经 curio_stop_pending+钱包支出再调,本口只做计数与重抽)。池规则全保留——不放回/达上限剔牌/掷定前置直显。 +func refresh_curio_paid() -> bool: + return _refresh_curio_common(true) + + +# 本次 GP 付费刷新价(与商店货架刷新同源:ceil(基价 × 曲线^本次停点已付费刷)——读同两个调参旋钮,单点调参) +func curio_refresh_gp_cost() -> int: + return int(ceil(float(db.adventure.shop_refresh_cost_base) + * pow(db.adventure.shop_refresh_cost_curve, float(_curio_paid_refreshes)))) + + +# 三选一停点在途谓词(付费路径扣费前置校验——无停点禁扣钱,防空转烧钱包) +func curio_stop_pending() -> bool: + return not _curio_pending.is_empty() or not _node_curio_pending.is_empty() + + +func _refresh_curio_common(paid: bool) -> bool: + if not curio_stop_pending(): return false - if not _curio_pending.is_empty(): + if paid: + _curio_paid_refreshes += 1 + else: + if _attune_refresh_x <= 0 and _attune_refresh_y <= 0: + return false if _attune_refresh_x > 0: _attune_refresh_x -= 1 else: _attune_refresh_y -= 1 + if not _curio_pending.is_empty(): var cands: Array = _draw_curio_candidates("level_clear") _curio_pending["candidates"] = cands.duplicate(true) _curio_pending["aux_detail"] = _last_draw_treasure.duplicate() var payload: Dictionary = (_curio_pending.get("payload", {}) as Dictionary).duplicate(true) payload["candidates"] = cands payload["aux_detail"] = _last_draw_treasure.duplicate() + payload["refreshed"] = true # 重发载荷带标——呈现层直落选择面,不再重开胜利总结面(2026-10-06) curio_choice_presented.emit(payload) # 同信号重 present(零自动推进契约不破——停点仍在途) _snapshot_breakpoint() return true if not _node_curio_pending.is_empty(): - if _attune_refresh_x > 0: - _attune_refresh_x -= 1 - else: - _attune_refresh_y -= 1 var cands2: Array = _draw_curio_candidates("reward_node") _node_curio_pending["candidates"] = cands2.duplicate(true) _node_curio_pending["aux_detail"] = _last_draw_treasure.duplicate() @@ -954,6 +982,7 @@ func _on_level_finished(payload: Dictionary) -> void: # 置位先于 emit(信号同步派发——处理器内立即确认时 _pending_next_level 必须已就位) _pending_next_level = true _reset_refresh_x() # 迭代 11 第①批 T5b——碰三选一重置 X 余量(不结转) + _curio_paid_refreshes = 0 # 付费刷计数随停点清零(对齐商店进店清零——2026-10-06) var pick_payload: Dictionary = { "level_index": level_index, "candidates": _draw_curio_candidates("level_clear"), # T4 渠道白名单——关底 Boss 击败=通关渠道只出专长 diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index adc16beb..e8415804 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -14,7 +14,10 @@ # 伤害管线单据化批(2026-09-24 用户三裁决,06 台账 148):伤害兑付全部收口 伤害控制器(DamageController # 单点管线)——本类六调用点(主角先手/敌方伤害技/棋盘克制直伤/群伤/反噬/地形变异)退化为开单点 # (流程决定何时开单给谁开单;单据怎么算归控制器)。结账时机开关在控制器 policy:IMMEDIATE(A=开单 -# 即结,逐位等价迁移前行为——回切位)/ SEGMENT(B=攒批到段界 flush:授予段尾〔含清图批前〕+ 战斗段尾)。 +# 即结,逐位等价迁移前行为——回切位)/ SEGMENT(B=攒批到段界 flush:战斗段尾统一兑现)。 +# 地图伤害账殿后批(案C 2026-10-06 用户裁——「主角先手」歧义质询触发):授予段不再当场兑付任何伤害—— +# 全部伤害形态单据经 _submit 暂存闸入 _deferred_board(保授予段事件序),战斗段「主角先手→逐敌还手」后 +# 补提交统一结账;主角死于敌方还手 → 暂存单整体不兑,逐单留「未生效(主角已倒下)」行(forfeit=hero_down)。 # 死亡事件 = 单位容器 unit_died(单位自行宣布)→ 控制器作废该敌未结单据〔死亡即不动手〕/ 主角死终止兑付。 # 切片登记:① 事件总线载体 = 直连信号(实现期 ADR 项,provisional)② Waiting 相位刷新通道 2026-09-15 用户拍板整体 # 拆除(仅 Active 挖掘期受理)③ 效果行协议 = 授予段逐 grant 行 + 增益/减益段续存实例组合行。 @@ -63,6 +66,8 @@ var _facts_reveals: Array[Dictionary] = [] var _facts_flags: Array[Dictionary] = [] var _facts_path := "" var _settling := false +var _defer_board_damage := false # 案C 暂存闸(2026-10-06 用户裁:地图奖励伤害兑付迁战斗段末——怪物还手后) +var _deferred_board: Array[Dictionary] = [] # 授予段暂存伤害单据(保授予段事件序;战斗段怪物还手后按序补提交统一结账) var _refresh_used_this_round := 0 # 每轮刷新计数(Active 相位合计,兜底上限 = ActionEconomyTable.refresh_cap_per_round) var _board_seq := 0 # 板序号(本战斗第几张板——快照归因用) var _board_from_midround_refresh := false # 装板 provenance:轮内刷新换来的图 = true(清图奖励守卫——刷新图不发清图奖励) @@ -70,113 +75,65 @@ var _flag_consumed := {} # 兑付资格以雷格实例为键 # 同调装配运行态(迭代 11 第①批 T5——begin_battle 经 enc.attunement 注入;off=空集全部效果零扰动) var attunement: Array = [] # 装备中同调件 item_id 集 var _attune_charges_ref: Dictionary = {} # 充能账本共享引用(迭代 11 第①批 T6——use_treasure 消费直改) -var _attune_bank := 0 # 掘进蓄能积点(battle 域——每 charge_n 安全格 +1 点) -var _attune_safe_seen := 0 # 掘进蓄能 battle 域安全开格计数 -# 特性运行态(迭代 12 T2——relic 持有集经 enc.feats 注入;求值旗注 damage_controller.feat_flags; -# 魔矢每战首次计数 battle 域。空集=零扰动) +var _attune_safe_seen := 0 # 掘进蓄能 battle 域安全开格计数(进度呈现取数面;积点账本=效果引擎计数器 dig_charge_bank——行 triggers counter_spend 订阅同账本) +# 特性运行态(内容数据驱动扩展批 T3——relic 持有集经 enc.feats 注入:效果行为=数据行 triggers 经 +# 效果引擎分发;魔矢每战首次计数 battle 域。空集=零扰动) var _feat_first_dig_used := false -var _feat_combo := 0 # 矿脉直觉连击计数(battle 域——safe +1/mine 清零) -var _feat_deep_given := 0 # 深挖钻头本轮已追加份数(battle 域——v2 本格双份制:safe 事件按付费数上限) -var _feat_paid_at_round_start := 0 # 轮初付费计数快照(v2——本轮付费次数=当前值−快照) -var _feat_magma_layers := 0 # 熔火之躯积层(battle 域——受地形伤 +1,轮末反打清零) +# 效果引擎(内容数据驱动扩展批 T1——发布订阅解释器;begin_battle 从持有集/装备集重建订阅。 +# T3 起施加面全量切换:发射点 dispatch 返回命中,发射点按组合规则施加——旧 feat_flags 旗注入消亡) +var _fx: EffectEngine + + +# 事件发射薄面(16 发射点统一入口——订阅空=零循环零扰动;确定性同步分发零 RNG) +func _fx_emit(event: String, payload: Dictionary) -> Array: + if _fx == null: + return [] + return _fx.dispatch(event, payload, Callable(self, "_fx_fact")) + + +# round_start 发射+施加(内容数据驱动扩展批 T3——grant_ap mode:queue=当轮入队,行动经济轮切换 +# 即兑现;计数器 round_start 重置点在 dispatch 内生效。调用点两处:begin_battle 首轮 / 轮始段续轮) +func _emit_round_start() -> void: + for h in _fx_emit("round_start", {"round": round_index}): + if String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue": + action_economy.queue_ap_delta(int(h.get("value", 0))) + + +# 补充事实面(fact 取值第二优先源——payload 未携带的运行态读数) +func _fx_fact(fact: String): + match fact: + "charge_layers": + return action_economy.charge_layers + "nest_count": + var n := 0 + for eff in _status_effects.query_active("enemy"): + if String(eff.effect_id) == "weaken": + n += 1 + return n + "round_index": + return round_index + "flags_count": + var hits_n := 0 + for fevt in _facts_flags: + if bool(fevt.get("hit", false)): + hits_n += 1 + return hits_n + return null func _attune_has(item_id: String) -> bool: return attunement.has(item_id) -# 装备件表值读取(magnitude/effective_rounds 等——TreasureItemTable 行;未装配不读表) +# 装备件表值读取(charge_n 等——TreasureItemTable 行;未装配不读表。magnitude 读数面随 T4 +# 秘宝订阅迁移退役——行值经 value_from 在 rebuild 定影,零运行期按件取值) func _attune_row(item_id: String) -> Dictionary: return db.treasure_item_row(item_id) if _attune_has(item_id) else {} -func _attune_mag(item_id: String) -> int: - return int(_attune_row(item_id).get("magnitude", 0)) - - -# 特性求值(迭代 12 T2——持有集 × feat_table → damage_controller.feat_flags 注入面): -# rider_flat = Σ rider 件 value(奥术余烬 +1 / 魔能共鸣 +2);magic 档一(≥2 件)rider +1、 -# 档二(≥4 件)rider 可暴击;clamp 两件直调键;first_dig_spell 旗(魔能共鸣第二效果——魔矢值 -# = 件 effects.value)。空集/空表 = 零旗零扰动。 -func _feat_flags_for(feat_ids: Array) -> void: - var flags: Dictionary = {} - if not feat_ids.is_empty() and db.feats != null and not db.feats.rows.is_empty(): - var hooks := FeatsEngine.hooks_for(feat_ids, db.feats) - var tiers := FeatsEngine.tiers_for(feat_ids, db.feats) - for h in hooks: - var hook: String = String(h.get("hook", "")) - var fx: Dictionary = h.get("effects", {}) as Dictionary - match hook: - "hero_attack_rider": - flags["rider_flat"] = int(flags.get("rider_flat", 0)) + int(fx.get("value", 0)) - "clamp_modifier": - # clamp 方向按件 note 词分流(魔网=magic-vs-magic 罚修正 / 湛蓝=magic-vs-heavy 加成) - if String(fx.get("polarity", "")) == "magic_vs_magic": - flags["clamp_magic_magic"] = int(flags.get("clamp_magic_magic", 0)) + int(fx.get("value", 0)) - elif String(fx.get("polarity", "")) == "magic_vs_heavy": - flags["clamp_magic_heavy"] = int(flags.get("clamp_magic_heavy", 0)) + int(fx.get("value", 0)) - elif String(fx.get("polarity", "")) == "pierce_vs_light": - flags["clamp_pierce_light"] = int(flags.get("clamp_pierce_light", 0)) + int(fx.get("value", 0)) - "first_dig_spell": - flags["first_dig_spell"] = true - flags["spell_value"] = int(fx.get("value", 0)) - "flag_resource": - flags["flag_resource_extra"] = int(fx.get("value", 0)) - "nest_aura": - flags["nest_aura_value"] = int(fx.get("value", 0)) - "flag_all_hit": - flags["flag_all_hit_mult"] = int(fx.get("value", 0)) - "flag_strike": - flags["flag_strike_value"] = int(fx.get("value", 0)) - "kill_overflow": - flags["kill_overflow"] = true - "dig_count_segments": - flags["chain_value"] = int(fx.get("value", 0)) - flags["chain_cap"] = int(fx.get("cap", 2)) - "roll_discount": - flags["discount_shift"] = int(fx.get("value", 0)) - "round_dig_bonus": - flags["round_dig_value"] = int(fx.get("value", 0)) - flags["round_dig_cap"] = int(fx.get("cap", 5)) - "hit_trade": - flags["hit_trade_atk"] = int(fx.get("trade_atk", 0)) - flags["hit_trade_hit"] = int(fx.get("trade_hit", 0)) - "armor_roll_rewrite": - flags["armor_roll_rewrite"] = int(fx.get("value", 0)) - "heavy_debuff": - flags["crack_value"] = int(fx.get("value", 0)) - "dig_yield_mult": - # 开格产出乘区族(贪婪矿灯/矿脉直觉/赌徒的血签——正负双向,同 hook 累加) - flags["dig_yield_pos_mult"] = int(flags.get("dig_yield_pos_mult", 0)) + int(fx.get("mult", 0)) - flags["dig_yield_neg_mult"] = int(flags.get("dig_yield_neg_mult", 0)) + int(fx.get("cost_mult", 0)) - if int(fx.get("cap", 0)) > 0: - flags["combo_cap"] = int(fx.get("cap", 0)) # 矿脉直觉连击上限 - "deep_dig": - flags["deep_dig_cost"] = int(fx.get("ap_cost", 0)) - flags["deep_dig_cap"] = int(fx.get("cap", 4)) - "ap_carry": - flags["ap_carry_value"] = int(fx.get("value", 0)) - "charge_shield": - flags["shield_per_layer"] = int(fx.get("shield_per_layer", 0)) - "charge_spike": - flags["spike_mult"] = int(fx.get("value", 0)) - "terrain_refund": - flags["terrain_refund"] = int(fx.get("value", 0)) - "terrain_dig_double": - flags["terrain_dig_double"] = int(fx.get("value", 0)) - "flag_neutralize": - flags["flag_neutralize"] = int(fx.get("value", 0)) - "variant_counter": - flags["variant_counter_value"] = int(fx.get("value", 0)) - var mt := int(tiers.get("magic", 0)) - if mt >= 1: - flags["rider_flat"] = int(flags.get("rider_flat", 0)) + 1 # 档一质变(≥2 件):rider +1(provisional) - if mt >= 2: - flags["rider_crit"] = true # 档二质变(≥4 件):rider 可暴击(crit 时乘暴击倍率) - _damage.feat_flags = flags - # 蓄力流/深挖钻头 action_economy 注入(求值即注入——冒烟直呼 _feat_flags_for 同面生效) - action_economy.feat_ap_carry = int(flags.get("ap_carry_value", 0)) > 0 - action_economy.feat_dig_extra = int(flags.get("deep_dig_cost", 0)) +# 特性求值(内容数据驱动扩展批 T3——_feat_flags_for 挂点求值退役:效果行为=数据行 triggers 订阅, +# 效果引擎 dispatch 分发;旧 23 hook 枚举与旗注入消亡。流派档位质变=FeatsEngine 纯函数求值 +# (档位机制本身系引擎基建非内容效果),两运行态注伤害控制器:档一 rider+1 / 档二 rider 可暴击) var _flag_miss_seen := {} # 标旗未中行去重(每安全格实例一次) var _perfect_hit_true := 0 # 板级完美排雷计数(install 重置) var _perfect_hit_false := 0 @@ -186,6 +143,7 @@ var _hero_atk := 0 # 主角攻击基值快照(encounte func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void: db = db_ref rng = rng_ref + _fx = EffectEngine.new(db_ref) _board_generator = BoardGenerator.new() action_economy = ActionEconomy.new(db) minesweeper = MinesweeperRules.new(db, action_economy) @@ -194,6 +152,9 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void: _reward_economy = RewardEconomy.new(db, rng) _damage = DamageController.new(db, rng, _enemy_system, _status_effects) _damage.emit_row_cb = _emit_row # 行协议归属本类(9 类行协议 + 来源断言不外泄) + _damage.set_fx(_fx) # 效果引擎注入(attack_commit/clamp_check/armor_roll/kill/damage_taken 发射面) + action_economy.set_fx(_fx) # dig_paid/charge_full 发射面 + _reward_economy.set_fx(_fx) # reward_roll 发射面 minesweeper.cell_revealed.connect(_on_cell_revealed) minesweeper.flag_judged.connect(_on_flag_judged) minesweeper.round_end_board.connect(_on_round_end) @@ -243,38 +204,44 @@ func begin_battle(enc: Dictionary) -> bool: # 伤害控制器、偏运 N 注入地图战斗资源——均须先于首板 install) attunement = (enc.get("attunement", []) as Array).duplicate() _attune_charges_ref = enc.get("attune_charges", {}) # 共享 dict 引用(level/adventure 同账本——use_treasure 扣减直改) - _attune_bank = 0 _attune_safe_seen = 0 - _damage.attune_venom = _attune_mag("venom_edge") if _attune_has("venom_edge") else 0 - _reward_economy.luck_bias = _attune_mag("luck_coin") if _attune_has("luck_coin") else 0 - # 特性运行态注入(迭代 12 T2 魔法流——enc.feats 携带持有集;FeatsEngine 求值后注伤害控制器 - # feat_flags;空集/空表=零扰动。魔矢每战首次开格计数同步归零) - _feat_flags_for((enc.get("feats", []) as Array)) + # 淬毒/偏运注入拆除(内容数据驱动扩展批 T4——两件被动改 triggers 订阅行:淬毒=clamp_check + # modify 窗口、偏运=reward_roll bias_rewards;value 行字段 value_from 定影,零 begin_battle 注入) + # 特性运行态(内容数据驱动扩展批 T3——enc.feats 携带持有集:效果行为=数据行 triggers 经效果 + # 引擎分发;流派档位=FeatsEngine 纯函数求值两运行态注伤害控制器。魔矢每战首次开格计数同步归零) + var feat_ids: Array = enc.get("feats", []) as Array + if not feat_ids.is_empty() and db.feats != null: + var tiers: Dictionary = FeatsEngine.tiers_for(feat_ids, db.feats) + var mt := int(tiers.get("magic", 0)) + _damage.rider_tier_bonus = 1 if mt >= 1 else 0 + _damage.rider_tier_crit = mt >= 2 + else: + _damage.rider_tier_bonus = 0 + _damage.rider_tier_crit = false _feat_first_dig_used = false - _feat_combo = 0 - _feat_deep_given = 0 - _feat_paid_at_round_start = 0 - _feat_magma_layers = 0 - # 树 rule 键消费(迭代 12 T10——Fork 激活侧 effects;真实接线首期两键:先手压制=战斗首轮 - # AP+1(queue_ap_delta——先手压制语义)/稳健拓荒=敌方攻击减 1(伤害控制器 flags bulwark); - # 其余 rule 键=stat 面投影不入战斗(T8 表内容 provisional 口径披露)) + # 树 rule 键消费(迭代 12 T10 → 内容数据驱动扩展批 T3 换代:Fork 激活侧效果=效果引擎合成 + # 订阅(rebuild 注入——round_start 首轮 AP+1 / damage_taken 减伤+1),旧直调拆除。其余 rule 键 + # =stat 面投影不入战斗(T8 表内容 provisional 口径披露)) var tree_rules: Array = enc.get("tree_rules", []) as Array - if tree_rules.has("posture_first_strike"): - action_economy.queue_ap_delta(1) - _damage.feat_flags["bulwark"] = 1 if tree_rules.has("posture_bulwark") else 0 + # 效果引擎订阅重建(内容数据驱动扩展批 T1——每战从持有集/装备集重建,中途获得下一战生效; + # 树规则合成订阅同面装载。装载序=feats 表序→treasure 表序→树规则,写死保种子复放) + _fx.rebuild(feat_ids, attunement, tree_rules) # 迭代 10 奖励重构批——跨冒险行动点加成轴(统一冒险加算区第四轴):经排队修正通道兑现 # 「每场战斗开始首轮 AP +加成值」(queue_ap_delta = 下轮开轮基线加算、notify_round_start 兑现后 # 清零——首轮即消费,后续轮次不重复兑现;语义锚 = 行动经济 GDD「跨冒险行动点加成轴」节) var ap_bonus: int = int(enc.get("hero_ap_bonus", 0)) if ap_bonus > 0: action_economy.queue_ap_delta(ap_bonus) - # 工兵晨令(迭代 11 同调批 T5——前 effective_rounds 轮每轮 +1 AP;首轮在此入队,后续轮在轮始段续队) - if _attune_has("dawn_order"): - action_economy.queue_ap_delta(_attune_mag("dawn_order")) + # 工兵晨令直调拆除(内容数据驱动扩展批 T4——round_start grant_ap(mode:queue) 订阅行承载, + # when round_index ≤ effective_rounds 行字段 value_from 定影;发射位先于轮切换=当轮兑现) + # round_start 发射+施加(内容数据驱动扩展批 T3——发射位先于行动经济轮切换:树规则 grant_ap + # mode:queue 当轮入队、notify_round_start 即兑现(先手压制=首轮 AP+1 语义锚,与迁移前 + # begin_battle 直调 queue_ap_delta 逐位一致);计数器 round_start 重置点同点生效) + round_index = 1 + _emit_round_start() if not action_economy.notify_round_start(): _fatal("行动经济 首轮 notify_round_start 被拒") return false - round_index = 1 _roll_round_actions() # 开轮摇号(敌人技能池批——首轮同样摇;先于 round_started,挖掘期动画位可读) round_started.emit(round_index) return true @@ -449,8 +416,9 @@ func _maybe_settle() -> void: func _settle() -> void: _settling = true + _defer_board_damage = true # 案C:授予段伤害单据只暂存不结账(2026-10-06 用户裁——兑付时点=战斗段末) var ri := round_index - # ① 授予段:结算队列(开格/旗判定/清图)→ 极性路由逐批兑付。 + # ① 授予段:结算队列(开格/旗判定/清图)→ 极性路由逐批兑付(伤害形态单据暂存——见 _submit 案C 闸)。 # 判定基线 = 轮末在场旗(发射面已过滤——被挖开的旗已随挖掘擦除零兑付,插旗不可逆批 2026-09-19; # 开格事实不可撤)。 for evt in _facts_reveals: @@ -465,45 +433,75 @@ func _settle() -> void: # 作废(归因行在案);标空判定已随旗擦除自然缺席(被挖的旗不在轮末在场基线)。 _emit_row("reward_ineffective", "safe_cell", evt.cell, ri, "奖励作废(插旗格直接挖开——零惩罚零奖励)", 0, {}, "") continue - _grant_routed(_feat_yield_adjust(snap, true), "positive", "safe_cell", evt.cell, ri, "") - # 开格流连击/深层产出(迭代 12 T5):矿脉直觉连击 = safe +1(mine 清零在负向分支); - # 深挖钻头深层产出 = 每 cap 格额外 grant 一份(收益面补偿额外 AP 成本——R4 甲非信息税对价) - _feat_combo += 1 - # 深挖钻头 v2(2026-10-04 二次修正——用户实测「付 2AP 无可见提升纯挨打」:v1 配额制 - # 4 次付费 8AP 换 1 份随机资源=净负期望+收益埋明细带不可感知):改**本格双份**—— - # 付费挖掘的本格资源直接双份(多花 1AP 买多一份同格产出,账目直白期望微正)。 - # 本轮付费次数 N=deep_paid_count−轮初快照;safe 事件按 N 上限逐格追加第二份(「深挖」)。 - var dd_cost := int(_damage.feat_flags.get("deep_dig_cost", 0)) - if dd_cost > 0: - var paid_this_round := int(action_economy.deep_paid_count) - _feat_paid_at_round_start - if paid_this_round > 0 and _feat_deep_given < paid_this_round: - _feat_deep_given += 1 + # dig_reveal 发射+施加(内容数据驱动扩展批 T3——桶序固定承载等价: + # 乘区收集 → 正向兑付 → 计数器 → 追加产出(深挖)→ 奥术矢) + var dr_hits := _fx_emit("dig_reveal", {"cell": evt.cell, "round": ri, + "is_first_dig_of_battle": not _feat_first_dig_used, + "terrain_kind": _harmful_variants(evt.get("variant_ids", []) as Array)}) + var pos_m := 0 + var neg_m := 0 + var combo_cap := -1 + for h in dr_hits: + if String(h.get("kind", "")) != "yield_mult": + continue + pos_m += int(h.get("mult", 0)) + neg_m += int(h.get("cost_mult", 0)) + if int(h.get("cap", 0)) > 0: + combo_cap = int(h.cap) # 矿脉直觉连击上限 + _grant_routed(_feat_yield_adjust(snap, true, pos_m, neg_m, combo_cap), "positive", "safe_cell", evt.cell, ri, "") + # 计数器执行(连击计数——加法族,乘区收集后执行=本格兑付用旧值,与迁移前逐位一致) + for h in dr_hits: + if String(h.get("kind", "")) == "counter_add": + var set_v = h.get("set", null) + _fx.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null) + # 追加产出(深挖钻头 v2——本格双份制:grant_material 命中=配额内追加第二份;标签随行) + for h in dr_hits: + if String(h.get("kind", "")) == "grant_material": var dd_snap = _reward_economy.grant(evt.cell) if dd_snap is Dictionary: - _grant_routed(dd_snap, "positive", "safe_cell", evt.cell, ri, "", "(深挖)") - # 魔能共鸣·每战首次开安全格追加魔法矢(迭代 12 T2——relic 件「每关首次开格必出魔法矢」 - # 落地为每战首次〔战斗域单事务无跨战状态——「每关」字面改「每战」台账披露待追认〕; - # board_typed magic 必中,扫雷结算触发点=relic 挂 first_dig_spell 承载红线①) - if bool(_damage.feat_flags.get("first_dig_spell", false)) and not _feat_first_dig_used: + _grant_routed(dd_snap, "positive", "safe_cell", evt.cell, ri, "", String(h.get("label", ""))) + # 奥术矢(魔能共鸣——每战首次开安全格追加魔法矢;任意真族单次生效) + var spell_hits: Array = _hits_of(dr_hits, "spawn_spell") + if not spell_hits.is_empty() and not _feat_first_dig_used: _feat_first_dig_used = true var spell_t := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell", - "奥术矢", "board", int(_damage.feat_flags.get("spell_value", 0)), + "奥术矢", "board", int((spell_hits[0] as Dictionary).get("value", 0)), DamageController.TARGET_FRONT_ALIVE, ri, evt.cell, "magic") - _damage.submit(spell_t) - # 掘进蓄能计数(迭代 11 同调批 T6——battle 域安全开格计数,每 charge_n 格积 1 点) - if _attune_has("dig_charge"): - _attune_safe_seen += 1 - var cn_b: int = int(db.treasure_item_row("dig_charge").get("charge_n", 999)) - if cn_b > 0 and _attune_safe_seen % cn_b == 0: - _attune_bank += 1 + _submit(spell_t, false) + # 掘进蓄能计数(迭代 11 同调批 T6 → 内容数据驱动扩展批 T4:积点账本迁效果引擎计数器 + # dig_charge_bank——行 triggers counter_spend(treasure_used) 受理闸同账本。模判定留编排器侧: + # 「每 N 格积 1 点」在发射点施加架构下无法行内表达先增后判(when 于 dispatch 预评估), + # 逐位等价优先——词汇表边界注记随 commit 留痕;charge_n 读行零字面量) + if _attune_has("dig_charge"): + _attune_safe_seen += 1 + var cn_b: int = int(db.treasure_item_row("dig_charge").get("charge_n", 999)) + if cn_b > 0 and _attune_safe_seen % cn_b == 0: + _fx.counter_add("dig_charge_bank", 1) elif evt.kind == "mine": # 插旗不可逆四情况②:插雷格再挖开 = 只受挖雷一层惩罚(mine_hit 照常兑付——惊动巢穴), # 标中不发(旗已随挖掘擦除、不入轮末判定基线)。 - _grant_routed(_feat_yield_adjust(snap, false), "negative", "mine_hit", evt.cell, ri, "") - _feat_combo = 0 # 矿脉直觉连击清零(踩雷断连——「推理密度直接奖励」的对侧) - # 地形变异 兑付段(授予段尾——触发枚举 on_open:挖开即伤、chain 通道同口径,轮末结算逐行兑现; - # 信息型 hide_number 不进结算——呈现层读取,见 board_view 数字遮蔽;雷格恒无变异——布置段排除, - # 双层叠罚不存在;插旗格直挖照吃变异伤害——地形后果同 mine_hit 口径,非奖励不作废) + # mine_hit 发射+施加(内容数据驱动扩展批 T3——桶序固定承载等价: + # 减半收集 → 负向兑付〔带减半系数〕→ 反打开单 → 连击清零计数器) + var mh_hits := _fx_emit("mine_hit", {"cell": evt.cell, "round": ri, + "damage": int((snap as Dictionary).get("power", 0))}) + var halve_pct := 0 + for h in mh_hits: + if String(h.get("kind", "")) == "damage_mult": + halve_pct += int(h.get("value", 0)) + for h in mh_hits: + if String(h.get("kind", "")) == "counter_add": + var set_v = h.get("set", null) + _fx.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null) + _grant_routed(_feat_yield_adjust(snap, false, 0, 0, -1), "negative", "mine_hit", evt.cell, ri, "", "", halve_pct) + for h in mh_hits: + if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "reflect": + var nct := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_nest_counter", "惊巢反制·反弹", "board", + int(h.get("value", 0)), DamageController.TARGET_ALL_ALIVE, ri, evt.cell, "", 0, "", {}) + _submit(nct, false) + # 地形变异 兑付段(授予段尾——触发枚举 on_open:挖开即伤、chain 通道同口径;伤害单据案C 暂存到 + # 战斗段末兑付〔2026-10-06 用户裁〕;信息型 hide_number 不进结算——呈现层读取,见 board_view 数字遮蔽; + # 雷格恒无变异——布置段排除,双层叠罚不存在;插旗格直挖照吃变异伤害——地形后果同 mine_hit 口径, + # 非奖励不作废;吞噬流资源兑付〔荆棘编织/地脉补偿〕中伤害形态行同样经案C闸暂存,非伤害形态照常当场) for evt in _facts_reveals: var vids: Array = evt.get("variant_ids", []) for vid in vids: @@ -513,63 +511,81 @@ func _settle() -> void: # 地形变异伤害单据(伤害管线单据化批 2026-09-24)——无类型必中,下限保底在控制器管线内 var vt := _damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, "地形变异", String(vrow.get("display_name", vid)), "board", \ int(vrow.get("effect_value", 0)), DamageController.TARGET_HERO, ri, evt.cell, "", 0, "", {"variant_id": String(vid)}) - _damage.submit(vt) - # 吞噬流四面(迭代 12 T6——地形从纯税到第三资源带;伤害仍发生=乙稿判据 3 承载): - # ①荆棘编织者=挖开伤害型地形格额外掉落一次格面资源(主动吃地形换资源) - if int(_damage.feat_flags.get("terrain_dig_double", 0)) > 0: - var tw_snap = _reward_economy.grant(evt.cell) - if tw_snap is Dictionary: - _grant_routed(tw_snap, "positive", "safe_cell", evt.cell, ri, "", "(荆棘编织)") - # ②地脉采撷=受地形伤掉落小额资源补偿(薄矿化——每次受伤兑一份) - if int(_damage.feat_flags.get("terrain_refund", 0)) > 0: - var lh_snap = _reward_economy.grant(evt.cell) - if lh_snap is Dictionary: - _grant_routed(lh_snap, "positive", "safe_cell", evt.cell, ri, "", "(地脉补偿)") - # ③熔火之躯=受地形伤积层(触发键=「受地形伤」通用不按种类特化——定稿红线承载) - if int(_damage.feat_flags.get("variant_counter_value", 0)) > 0: - _feat_magma_layers += 1 + _submit(vt, false) + # damage_taken(kind=terrain) 发射+施加(内容数据驱动扩展批 T3——荆棘/地脉 grant_material + # 即时执行 + 熔火积层 counter_add;行序随订阅声明序〔ley 表序在 thorn 前〕,兑付值逐位一致) + var dt_hits := _fx_emit("damage_taken", {"kind": "terrain", "amount": int(vrow.get("effect_value", 0)), + "source": String(vid), "cell": evt.cell, "round": ri, "terrain_kind": String(vid)}) + for h in dt_hits: + match String(h.get("kind", "")): + "grant_material": + var tw_snap = _reward_economy.grant(evt.cell) + if tw_snap is Dictionary: + _grant_routed(tw_snap, "positive", "safe_cell", evt.cell, ri, "", String(h.get("label", ""))) + "counter_add": + var set_v = h.get("set", null) + _fx.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null) for evt in _facts_flags: _judge_flag(evt, ri) # 战旗军规开单段(迭代 12 T3 标旗流——flag_judged 批后统一开单:轮末每面标中旗对最前存活敌 # 结算 board_typed 必中伤害〔复用零新配方;「旗所在巢穴敌」简化为 default_target 最前存活敌—— # 台账披露待追认〕;完美布防=本轮旗批全中(hit>0 且 miss==0)时旗伤 ×mult——全对推理奖励)。 - # 扫雷结算触发点=flag_strike/flag_all_hit/nest_aura 挂点承载红线①。 - if int(_damage.feat_flags.get("flag_strike_value", 0)) > 0: - var hits := 0 - var misses := 0 + # 伤害单据案C 暂存战斗段末兑付。 + # flag_settle 发射+施加(内容数据驱动扩展批 T3——战旗 open_ticket + 完美布防 modify_ticket + # 改写窗口:旗伤基值×完美倍率后逐旗开单,与迁移前算序一致) + var fs_hits := 0 + var fs_misses := 0 + for fevt in _facts_flags: + if bool(fevt.get("hit", false)): + fs_hits += 1 + else: + fs_misses += 1 + var fs_fx := _fx_emit("flag_settle", {"flags_hit_all": fs_hits > 0 and fs_misses == 0, + "flags_count": fs_hits, "round": ri}) + var banner_hits: Array = [] + for h in fs_fx: + if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "attack": + banner_hits.append(h) + if not banner_hits.is_empty(): + var strike_value := int((banner_hits[0] as Dictionary).get("value", 0)) + var flag_mult := 1 + for h in fs_fx: + if String(h.get("kind", "")) == "modify_ticket" and String(h.get("mode", "")) == "flag_mult": + flag_mult *= maxi(1, int(h.get("value", 1))) + if fs_misses == 0 and fs_hits > 0 and flag_mult > 1: + strike_value *= flag_mult # 完美布防:同轮全中旗伤倍增(全对推理奖励) for fevt in _facts_flags: - if bool(fevt.get("hit", false)): - hits += 1 - else: - misses += 1 - if hits > 0: - var mult := int(_damage.feat_flags.get("flag_all_hit_mult", 1)) - var strike_value := int(_damage.feat_flags.get("flag_strike_value", 0)) - if misses == 0 and mult > 1: - strike_value *= mult # 完美布防:同轮全中旗伤倍增(全对推理奖励) - for fevt in _facts_flags: - if not bool(fevt.get("hit", false)): - continue - var st_t := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_flag_strike", - "战旗军规", "board", strike_value, DamageController.TARGET_FRONT_ALIVE, ri, - fevt.cell, "normal") - _damage.submit(st_t) - # 熔火之躯轮末反打(迭代 12 T6 吞噬流——受地形伤积层,轮末每层对全体敌 ~1 必中反伤; - # 触发键=「受地形伤」通用不按地形种类特化——定稿红线承载;反打后清零) - var magma := int(_damage.feat_flags.get("variant_counter_value", 0)) - if magma > 0 and _feat_magma_layers > 0: - var mg_t := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_magma_body", - "熔火反噬 ×%d" % _feat_magma_layers, "board", magma * _feat_magma_layers, - DamageController.TARGET_ALL_ALIVE, ri, minesweeper.hero_pos(), "", 0, "", {}) - _damage.submit(mg_t) - _feat_magma_layers = 0 + if not bool(fevt.get("hit", false)): + continue + var st_t := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_flag_strike", + "战旗军规", "board", strike_value, DamageController.TARGET_FRONT_ALIVE, ri, + fevt.cell, "normal") + _submit(st_t, false) + # round_end 发射+施加(内容数据驱动扩展批 T3——每轮结算收口:熔火之躯反打单开立〔open_ticket + # 即时执行〕+积层清零〔counter_add set〕;整备转蓄力由 finish_round_stop 承载——停手侧 AP 已在 + # request_stop 记账,转存决策=grant_ap(mode:transfer) 订阅定夺,无订阅走 RAP 转存缺省) + var re_fx := _fx_emit("round_end", {"round": ri}) + for h in re_fx: + if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "blast": + var mg_layers := _fx.counter(String(h.get("per", ""))) + var mg_total := int(h.get("value", 0)) * mg_layers + if mg_total > 0: + var mg_t := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_magma_body", + "熔火反噬 ×%d" % mg_layers, "board", mg_total, + DamageController.TARGET_ALL_ALIVE, ri, minesweeper.hero_pos(), "", 0, "", {}) + _submit(mg_t, false) + for h in re_fx: + if String(h.get("kind", "")) == "counter_add": + var set_v = h.get("set", null) + _fx.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null) + action_economy.finish_round_stop(_fx != null and _fx.has_effect_on("round_end", "grant_ap", "transfer")) if _facts_path == "cleared": _grant_clear_reward(ri) - _damage.flush() # 授予段界结账(B 模式——段内开单攒批到此统一兑现;A 模式 no-op;清图批前另有同步结账点) + _damage.flush() # 授予段界结账(案C 后本段伤害单据全数暂存,此处对空队 no-op——保留作防御性段界) # ② 增益/减益段(续存实例组合行;本结算新授予的已在授予段逐 grant 出行——增益状态 义务⑦) _emit_composition_effects("hero", ri) _emit_composition_effects("enemy", ri) - # ③ 战斗段(调用序:掷点判定 → apply_damage → commit_action;hero hp=0 短路) + # ③ 战斗段(调用序:主角先手 → 逐敌还手 → 地图伤害账殿后〔案C 2026-10-06 用户裁〕;hero hp=0 短路) _combat_segment(ri) # ④ 衰减段(恰一次恒执行含末轮——先兑付后衰减) var k: int = _status_effects.decay_round() @@ -591,31 +607,62 @@ func _settle() -> void: return _emit_row("verdict", "", Vector2i(-1, -1), ri, "下一轮次", 0, {}, "") _exit_distribution() - # 深挖钻头 v2 轮初快照滚动(本轮付费次数已消费——下一轮 N=当前值−新快照) - _feat_paid_at_round_start = int(action_economy.deep_paid_count) _settling = false # ---- 授予段 helpers(极性路由)---- -# 开格产出乘区(迭代 12 T5 开格流——贪婪矿灯/矿脉直觉/赌徒的血签;duplicate 隔离不回写快照): +# 开格产出乘区(内容数据驱动扩展批 T3 参数化——贪婪矿灯/矿脉直觉/赌徒的血签事件收集器注入): # positive=power ×(1+静态 mult% + 矿脉直觉连击加成 min(cap,combo)×10%);negative=power ×(1+cost%)。 -# 零旗=原快照原样返回(零扰动)。 -func _feat_yield_adjust(snap: Dictionary, positive: bool) -> Dictionary: - var pos_m := int(_damage.feat_flags.get("dig_yield_pos_mult", 0)) - var neg_m := int(_damage.feat_flags.get("dig_yield_neg_mult", 0)) - if positive and pos_m <= 0 and not _damage.feat_flags.has("combo_cap"): +# 全零收集器=原快照原样返回(零扰动)。combo 读数=效果引擎 dig_combo 计数器(safe+1/mine 清零, +# 跨轮累计——与迁移前 _feat_combo 逐位一致)。 +func _feat_yield_adjust(snap: Dictionary, positive: bool, pos_m: int, neg_m: int, combo_cap: int) -> Dictionary: + var combo_val := _fx.counter("dig_combo") if _fx != null else 0 + if positive and pos_m <= 0 and combo_cap <= 0: return snap if not positive and neg_m <= 0: return snap var out: Dictionary = snap.duplicate() var power := int(out.get("power", int(_reward_economy.polarity_row(String(out.get("cell_type", "")), "positive" if positive else "negative").power))) var pct := pos_m if positive else neg_m - if positive and _damage.feat_flags.has("combo_cap"): - pct += mini(int(_damage.feat_flags.get("combo_cap", 5)), _feat_combo) * 10 + if positive and combo_cap > 0: + pct += mini(combo_cap, combo_val) * 10 out["power"] = maxi(1, int(round(float(power) * (1.0 + float(pct) / 100.0)))) return out + +# 伤害型变异过滤(terrain_kind fact 取数面——variation 表 effect=flat_damage 判据; +# 🔴 R4 甲永不对冲边界=仅限伤害型,信息型(hide_number 族)永不出现在本集) +func _harmful_variants(vids: Array) -> Array: + var out: Array = [] + for vid_v in vids: + var vid := String(vid_v) + var vrow: Dictionary = db.variation.row_by_id(vid) + if String(vrow.get("effect", "")) == "flat_damage": + out.append(vid) + return out + + +# 邻接伤害型变异收集(terrain_kind fact 取数面——flag_hit 发射点用:标中旗相邻八格的伤害型变异集; +# 🔴 R4 甲永不对冲边界=仅限伤害型,信息型〔hide_number 族〕不入集) +func _harmful_adjacent(cell: Vector2i) -> Array: + var out: Array = [] + var board: BoardData = minesweeper.current_board() + if board == null: + return out + for n in board.neighbors8(cell): + out.append_array(_harmful_variants(board.variant_ids(n))) + return out + + +# 效果命中过滤(按效果类型取命中子集——发射点分桶施加面) +func _hits_of(hits: Array, kind: String) -> Array: + var out: Array = [] + for h in hits: + if String((h as Dictionary).get("kind", "")) == kind: + out.append(h) + return out + # 「动作 × 格型」矩阵的执行面:snap = 缓冲期冻结身份快照(cell_type = 该格装板期摇定的类型身份), # polarity 由动作决定(开安全格/标中旗 = positive,开雷格/标错旗 = negative), # 兑付行 = 奖励字典(类型, 极性)行。返回 true = percent 行实际生效(flat 行恒 false——兑付结果由行协议承载)。 @@ -639,12 +686,14 @@ func use_treasure(handle: int, target: Vector2i) -> Dictionary: func(c: Vector2i): return not board.is_revealed(c)) if not bool(v.ok): return v # ② 拒绝零消耗 - # 掘进蓄能受理闸(迭代 11 同调批 T6——bank>0 才可发起,一行一点;积点不足拒绝零消耗) - if String(v.item_id) == "dig_charge" and _attune_has("dig_charge"): - if _attune_bank <= 0: - var cn_info: int = int(db.treasure_item_row("dig_charge").get("charge_n", 0)) - return {"ok": false, "reason": "掘进蓄能:安全格积点未达(%d/%d)" % [_attune_safe_seen % cn_info if cn_info > 0 else 0, cn_info]} - _attune_bank -= 1 + # treasure_used 发射+施加(内容数据驱动扩展批 T4——使用主动秘宝受理面:counter_spend 订阅=受理闸 + # (掘进蓄能积点账本),消耗成功才受理;积点不足拒绝零消耗。按 item_id 特判随本批拆除) + var tu_hits := _fx_emit("treasure_used", {"item_id": String(v.item_id), "target": target}) + for h in tu_hits: + if String(h.get("kind", "")) == "counter_spend": + if not _fx.counter_spend(String(h.get("counter_id", "")), int(h.get("value", 1))): + var cn_info: int = int(db.treasure_item_row("dig_charge").get("charge_n", 0)) + return {"ok": false, "reason": "掘进蓄能:安全格积点未达(%d/%d)" % [_attune_safe_seen % cn_info if cn_info > 0 else 0, cn_info]} var kinds: Array = v.kinds if kinds.has("mine_reveal"): treasure_inventory.apply_overlay("mine_reveal", v.resolved) @@ -678,7 +727,7 @@ func set_treasure_unlocked(ids: Dictionary) -> void: _reward_economy.set_treasure_unlocked(ids) -func _grant_routed(snap: Dictionary, polarity: String, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> bool: +func _grant_routed(snap: Dictionary, polarity: String, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "", halve_pct: int = 0) -> bool: # 迭代 11 第①批 T7——aux 授予分支随掉落/停点渠道退役拆除(reward 词表第十型 treasure_item 保留但零投放: # 无 aux 行入池、CURIO_KINDS 词表已除名);剩余类型走极性字典路由。 var row: Dictionary = _reward_economy.polarity_row(String(snap.get("cell_type", "")), polarity) @@ -698,18 +747,14 @@ func _grant_routed(snap: Dictionary, polarity: String, source_class: String, cel row = row.duplicate() row["power"] = int(round(int(row.power) * prem_pct / 100.0)) row["premium_pct"] = prem_pct - # 惊巢反制(迭代 11 同调批 T5——底稿 §3:挖开雷格该次惩罚减半 + 反弹 N;duplicate 隔离——减半 - # 不回写极性字典;仅 source_class=mine_hit 受理,标错旗/负向字典行走原通道) - if polarity == "negative" and source_class == "mine_hit" and _attune_has("nest_counter"): + # 惊巢反制(迭代 11 同调批 T5 → 内容数据驱动扩展批 T4:按 id 特判拆除——减半系数=mine_hit + # damage_mult 订阅命中(行 value 50=减 50%,取整 ceil 与迁移前逐位一致);反弹单=mine_hit + # open_ticket channel reflect 订阅施加(编排器 mine_hit 施加段);duplicate 隔离——减半不回写极性字典) + if halve_pct > 0 and polarity == "negative" and source_class == "mine_hit": row = row.duplicate() - var half: int = int(ceil(float(int(row.power)) / 2.0)) + var half: int = int(ceil(float(int(row.power)) * float(100 - halve_pct) / 100.0)) _emit_row("effect", source_class, cell, ri, "惊巢反制——挖雷惩罚减半(%d→%d)" % [int(row.power), half], 0, {"effect_id": "nest_counter"}, highlight) row.power = half - var reflect: int = _attune_mag("nest_counter") - if reflect > 0: - var nct := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_nest_counter", "惊巢反制·反弹", "board", - reflect, DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, highlight, {}) - _submit(nct, false) return _dispatch_row(row, source_class, cell, ri, highlight, label_suffix) @@ -775,7 +820,7 @@ func _apply_flat_row(row: Dictionary, source_class: String, cell: Vector2i, ri: if ctype == "blast": var t := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, source_class, display, "board", power, \ DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, highlight, {"batch_key": String(row.reward_id)}) - return _damage.outcome(_submit(t, sync)) + return _outcome_of(_submit(t, sync)) if ctype == "pierce" or ctype == "magic": # 伤害道具批(2026-09-18)语义不变:正向 = 单目标克制直伤(必中全额不掷点——棋盘来源伤害既有口径); # 负向 = 主角直伤(复用反噬通道)。 @@ -784,7 +829,7 @@ func _apply_flat_row(row: Dictionary, source_class: String, cell: Vector2i, ri: var atk_type: String = String(row.get("attack_type", ctype)) var t2 := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, source_class, display, "board", power, \ DamageController.TARGET_FRONT_ALIVE, ri, cell, atk_type, 0, highlight, {"batch_key": String(row.reward_id)}) - return _damage.outcome(_submit(t2, sync)) + return _outcome_of(_submit(t2, sync)) if ctype == "heal": if String(row.get("polarity", "positive")) == "negative": return _submit_flat_hero(source_class, display, power, cell, ri, highlight, sync) @@ -805,14 +850,23 @@ func _apply_flat_row(row: Dictionary, source_class: String, cell: Vector2i, ri: # 开单分流:sync(清图批)走控制器同步开单——先按序结清段内排队单据、再当场兑现本单; # 常规开单按结账时机政策(A 即结 / B 入队)。 func _submit(t: Dictionary, sync: bool) -> int: + if _defer_board_damage: + _deferred_board.append(t) # 案C:授予段伤害单据暂存——战斗段怪物还手后按事件序补提交(sync 同闸,清图批一致延后) + return -1 return _damage.flush_submit(t) if sync else _damage.submit(t) +# 案C 结账读数:暂存单(_submit 返回 -1)以「将兑付」回报——授予段兑付决策(清图完美第二份等)只能 +# 依据开单时点事实(该时点敌人存活有保证);战斗段末实际结账若目标已灭,控制器自记未生效行。 +func _outcome_of(ticket_id: int) -> String: + return "redeemed" if ticket_id < 0 else _damage.outcome(ticket_id) + + # 主角直伤单据(反噬/治疗包负向——无类型必中,下限保底在控制器管线内) func _submit_flat_hero(source_class: String, display: String, power: int, cell: Vector2i, ri: int, highlight: String, sync := false) -> String: var t := _damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, source_class, display, "board", power, \ DamageController.TARGET_HERO, ri, cell, "", 0, highlight, {}) - return _damage.outcome(_submit(t, sync)) + return _outcome_of(_submit(t, sync)) # 旗判定批:标中 = 该雷格资源的正向兑付(每雷实例一次;高光 = 强制检查点); @@ -824,15 +878,22 @@ func _judge_flag(evt: Dictionary, ri: int) -> void: return # 去重跳过(资格以雷格实例为键——排雷玩法 诚实重复上报的对侧);零明细行 _flag_consumed[cell] = true _perfect_hit_true += 1 - # 标中殉爆(迭代 11 同调批 T5——底稿 §3:每标中一面旗,全体存活敌吃 N 群伤;无类型走 blast 通道) - if _attune_has("flag_burst"): + # flag_hit 发射+施加(内容数据驱动扩展批 T3——桶序固定承载等价:殉爆〔T4 秘宝面〕→ + # 净化改写 → 图谱额外资源 → 基线兑付) + var fh_hits := _fx_emit("flag_hit", {"cell": cell, "round": ri, + "terrain_kind": _harmful_adjacent(cell)}) + # 标中殉爆(迭代 11 同调批 T5 → 内容数据驱动扩展批 T4:订阅施加——行 triggers open_ticket + # channel blast value_from magnitude;每标中一面旗全体存活敌吃 N 群伤,无类型走 blast 通道) + for h in _hits_of(fh_hits, "open_ticket"): + if String(h.get("channel", "")) != "blast": + continue var fbt := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_flag_burst", "标中殉爆", "board", - _attune_mag("flag_burst"), DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, "highlight", {}) + int(h.get("value", 0)), DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, "highlight", {}) _submit(fbt, false) # 净化工兵(迭代 12 T6 吞噬流——标中旗中和相邻伤害型地形格):🔴 R4 甲永不对冲边界= # **仅限伤害型**(thorn/magma/rockfall——variation 表 effect=flat_damage 判据),信息型 # (hide_number 族)永不清除(红线①看门税不可对冲);中和=变普通格(BoardData.clear_variants)。 - if int(_damage.feat_flags.get("flag_neutralize", 0)) > 0: + if not _hits_of(fh_hits, "rewrite_board").is_empty(): var board: BoardData = minesweeper.current_board() for n in board.neighbors8(cell): var vids_n: Array = board.variant_ids(n) @@ -847,10 +908,11 @@ func _judge_flag(evt: Dictionary, ri: int) -> void: _emit_row("terrain_neutralized", "feat_purge_sapper", n, ri, "净化工兵·地形中和", 0, {}, "") # 雷区图谱(迭代 12 T3 标旗流——标中雷额外获得一份格面资源:插旗开始产出对标开格; # 复用 grant 单点,额外兑付走同一 _grant_routed 正向路由) - if int(_damage.feat_flags.get("flag_resource_extra", 0)) > 0: - var extra_snap = _reward_economy.grant(cell) - if extra_snap is Dictionary: - _grant_routed(extra_snap, "positive", "flag_hit", cell, ri, "", "(雷区图谱)") + for h in fh_hits: + if String(h.get("kind", "")) == "grant_material": + var extra_snap = _reward_economy.grant(cell) + if extra_snap is Dictionary: + _grant_routed(extra_snap, "positive", "flag_hit", cell, ri, "", String(h.get("label", ""))) var snap = _reward_economy.grant(cell) if not (snap is Dictionary): return @@ -925,6 +987,15 @@ func _roll_round_actions() -> void: _emit_row("highlight", "enemy_action", Vector2i(-1, -1), round_index, "%s 解锁了 %s" % [String(u.display_name), String(srow_u.get("display_name", sid))], 0, {"skill_id": sid}, "skill_unlock") + # enemy_hp_threshold 发射点(内容数据驱动扩展批 T1——血量阈值沿;对所有带该字段敌人逐个发射, + # 非 Boss 专属——Boss 语义留给订阅者 when 判定。pct=本次解锁条目的 unlock_hp_pct 表值) + var pct_v := 0 + for pe in (u.get("pool", []) as Array): + if String((pe as Dictionary).get("skill_id", "")) == sid and (pe as Dictionary).has("unlock_hp_pct"): + pct_v = int((pe as Dictionary).unlock_hp_pct) + break + _fx_emit("enemy_hp_threshold", {"unit": String(u.enemy_id), "pct": pct_v, + "is_boss": String(encounter.get("node_type", "")) == "boss"}) var entries: Array = _enemy_system.rollable_skills(u) if _enemy_system.basic_streak(u) >= pity_m: var specials: Array = _enemy_system.special_skills(u) @@ -942,6 +1013,7 @@ func _roll_round_actions() -> void: # 停开(B 模式下若 flush 中主角倒下,由控制器死亡事件终止余单——两模式同一结果)。 func _combat_segment(ri: int) -> void: + _defer_board_damage = false # 案C 闸复位——战斗段起开单即入队(主角/敌方/补提交的地图单按开单序统一结账) # 本轮扫雷事实推导(迭代 12 T4——执拗 streak/连珠矢段数共用取数面):本轮 safe 事件数= # streak;踩雷即清零(mine_hit 监听同口——执拗「踩雷清零」承载)。攻击在战斗段=授予段后 # 同一次 _settle 内,facts 已定。 @@ -952,11 +1024,12 @@ func _combat_segment(ri: int) -> void: round_safe += 1 elif evt.kind == "mine": round_mine = true - _damage.feat_flags["dig_streak"] = 0 if round_mine else round_safe - # 蓄力层注入(迭代 12 T5——整备军备「每层下轮首段 +1」/厚积薄发满层 spike/铁壁整备减伤—— - # 三件同读 action_economy.charge_layers;battle 域每轮刷新) - _damage.feat_flags["charge_layers"] = action_economy.charge_layers - # ⓪ 主角先手攻击(基础攻击恒存在——2026-09-11 用户裁决;先于敌方行动;授予段负向直伤致死 → 致电短路不先手)。 + _damage.dig_streak = 0 if round_mine else round_safe + # 蓄力层注入(内容数据驱动扩展批 T3——整备军备「每层下轮首段 +1」/厚积薄发满层 spike/铁壁整备 + # 减伤——三件 fact charge_layers 同读 action_economy;battle 域每轮刷新) + _damage.charge_layers = action_economy.charge_layers + # ⓪ 主角先手攻击(基础攻击恒存在——2026-09-11 用户裁决;先于敌方行动。案C 后授予段不再产出伤害, + # 主角进入战斗段时恒存活——先手必兑,除非场上无敌)。 # 单分量伤害构成(2026-09-19 充能退役批·用户拍板):主角攻击 = 唯一基础分量(atk,吃掷点含暴击; # 类型 = 主角 attack_type 表值——2026-09-18 营地扩展前置批:量与类型抽象分离;查甲效矩阵对应行取克制修正)。 # 原"基础 + 穿刺/魔法充能三分量"模型随充能系统整体退役——克制策略归冒险前配置(营地换型 / 未来冒险前参数面), @@ -969,14 +1042,14 @@ func _combat_segment(ri: int) -> void: var ht := _damage.open_ticket(DamageController.KIND_HERO_ATTACK, "hero_attack", "主角攻击", "combat", _hero_atk, \ DamageController.TARGET_FRONT_ALIVE, ri, Vector2i(-1, -1), String(_enemy_system.hero_unit().get("attack_type", "normal"))) _damage.submit(ht) - # 连珠矢(迭代 12 T4 穿刺流——开格节奏变攻击段数):本轮每开 ~3 安全格 +1 段穿刺附加 - # (上限 cap;段值 provisional 固定值不乘 atk——台账披露)。扫描雷结算触发点=dig_count_segments。 - var chain_value := int(_damage.feat_flags.get("chain_value", 0)) - if chain_value > 0: - var segments := mini(int(_damage.feat_flags.get("chain_cap", 2)), int(_damage.feat_flags.get("dig_streak", 0)) / 3) + # 连珠矢(内容数据驱动扩展批 T3——attack_commit 订阅施加面:开格节奏变攻击段数,本轮每开 + # ~3 安全格 +1 段穿刺附加(上限 cap;段值 provisional 固定值不乘 atk——台账披露)。施加位 + # 紧随主角攻击单开立=迁移前开单位(先于敌方行动单入队——flush 兑现序逐位一致) + for h in _hits_of(ht.context.get("fx_attack_hits", []) as Array, "attack_segments"): + var segments := mini(int(h.get("cap", 2)), _damage.dig_streak / maxi(1, int(h.get("divisor", 3)))) for s in segments: var ct := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_chain_arrows", - "连珠矢", "board", chain_value, DamageController.TARGET_FRONT_ALIVE, ri, + "连珠矢", "board", int(h.get("value", 0)), DamageController.TARGET_FRONT_ALIVE, ri, minesweeper.hero_pos(), "pierce") _damage.submit(ct) # 敌方行动(敌人技能池批——本拍动作开轮已摇定,此处按技能行兑现;行序 = 生成序)。 @@ -1010,7 +1083,18 @@ func _combat_segment(ri: int) -> void: et["source_unit"] = u et["context"] = {"skill_id": skill_id, "baseline": baseline} _damage.submit(et) - _damage.flush() # 战斗段界结账(B 模式——段内开单攒批到此统一兑现;A 模式 no-op) + # 地图伤害兑付(案C 2026-10-06 用户裁——战斗段末:怪物还手后按授予段事件序补提交统一结账; + # 主角死于怪物 → 地图账不兑,逐单留「未生效」行不作废静默——没生效也要留痕口径; + # 补提交后若地图直伤反杀主角,控制器 _halted 作废余单——战斗已败,胜负归判定段) + if int(_enemy_system.hero_unit().get("hp", 0)) <= 0: + for t in _deferred_board: + _emit_row("reward_ineffective", String(t.get("source_class", "")), t.get("origin_cell", Vector2i(-1, -1)), ri, + "%s 未生效(主角已倒下)" % String(t.get("display", "")), 0, {"forfeit": "hero_down"}, "") + else: + for t in _deferred_board: + _damage.submit(t) + _deferred_board.clear() + _damage.flush() # 战斗段界结账(B 模式——主角单+敌方单+地图单按开单序统一兑现;A 模式地图单补提交时即结) # ---- 算术读数(伤害管线单据化批 2026-09-24 迁移注记)---- @@ -1094,14 +1178,14 @@ func _exit_distribution() -> void: if not _install_new_board(): _fatal("换图编排失败") return - # 工兵晨令续队(迭代 11 同调批 T5——upcoming 轮号 = round_index+1 ≤ effective_rounds 才续,P4 甲表值化) - if _attune_has("dawn_order") and round_index + 1 <= int(db.treasure_item_row("dawn_order").get("effective_rounds", 0)): - action_economy.queue_ap_delta(_attune_mag("dawn_order")) + # 工兵晨令续队直调拆除(内容数据驱动扩展批 T4——round_start 订阅行承载,见 begin_battle 注) action_economy.notify_settlement_done(mode) + # round_start 发射先于行动经济轮切换(queue 型 grant_ap 当轮入队当轮兑现——与首轮同口径) + round_index += 1 + _emit_round_start() action_economy.notify_round_start() _reset_round_facts() _refresh_used_this_round = 0 - round_index += 1 _roll_round_actions() # 开轮摇号——每轮恰摇一次(轮内刷新换图不重摇:refresh 不走本通道) round_started.emit(round_index) diff --git a/game/core/damage_controller.gd b/game/core/damage_controller.gd index 93fc496b..abd5002a 100644 --- a/game/core/damage_controller.gd +++ b/game/core/damage_controller.gd @@ -60,10 +60,16 @@ var _outcomes := {} # ticket_id → "redeemed"/"zero"/"voided"( var _seq := 0 # 流水号 var _halted := false # 主角倒下终止旗(死亡事件置位——flush 余单作废) var _hero_ac_override := -1 # 主角护甲等级合流入参(begin_battle 显式设定——-1 = 表值兜底,口径同战斗编排器) -# 特性求值旗(迭代 12 T2——begin_battle 编排器经 FeatsEngine 求值后注入;空字典=零扰动。 -# rider_flat=英雄侧必中附加值〔奥术余烬族〕/rider_crit=档二 rider 可暴击/first_dig_spell=每战首次 -# 开安全格追加魔法矢〔消费在编排器授予段〕/clamp_magic_light·clamp_magic_heavy=克制点直调) -var feat_flags: Dictionary = {} +# 效果引擎(内容数据驱动扩展批 T1/T3——attack_commit/clamp_check/armor_roll/kill/damage_taken +# 发射+施加面:dispatch 返回命中,本类按组合规则施加——T3 起旧 feat_flags 旗消费点全部拆除) +var _fx: EffectEngine = null +# 战斗段运行态事实(编排器每轮刷新——fact 取数面:attack_commit 载荷 dig_streak / +# charge_layers facts〔猎巢人 per/整备 attack_bonus per/厚积薄发 when/铁壁 shield per〕; +# 流派档位两运行态〔magic 档一 rider+1 / 档二 rider 可暴击〕) +var dig_streak := 0 +var charge_layers := 0 +var rider_tier_bonus := 0 +var rider_tier_crit := false func _init(db_ref, rng_ref, units_ref, effects_ref) -> void: @@ -79,14 +85,78 @@ func set_hero_ac_override(v: int) -> void: _hero_ac_override = v +# 效果引擎注入(战斗编排器 _init——事件发射面接线) +func set_fx(fx: EffectEngine) -> void: + _fx = fx + + +# 事件发射薄面(发射+命中收集统一入口——订阅空=零循环零扰动;命中由各施加面按组合规则施加) +func _fx_hits(event: String, payload: Dictionary) -> Array: + if _fx == null: + return [] + return _fx.dispatch(event, payload, Callable(self, "_fx_fact")) + + +# 事件发射薄面(免施加观测位——enemy_hp_threshold 等现无订阅者的事件保留零扰动面) +func _fx_dispatch(event: String, payload: Dictionary) -> void: + _fx_hits(event, payload) + + +# 补充事实面(fact 取值第二优先源——payload 未携带的运行态读数:charge_layers 编排器每轮注入) +func _fx_fact(fact: String): + match fact: + "charge_layers": + return charge_layers + "dig_streak": + return dig_streak + return null + + +# per 取数(per 字段解析——载荷同名字段优先,回落补充事实面;add_ticket_rider/attack_bonus/ +# shield/open_ticket 的倍数承载:贡献值 = value × per 读数,cap 对乘积封顶) +func _per_value(name: String, payload: Dictionary) -> int: + if name == "": + return 1 + if payload.has(name): + return int(payload[name]) + if _fx_fact(name) != null: + return int(_fx_fact(name)) + return 0 + + +# 巢穴控制数(fact nest_count 取数面——敌方 weaken 实例数,猎巢人订阅面) +func _nest_count() -> int: + var n := 0 + for eff in effects.query_active("enemy"): + if String(eff.effect_id) == "weaken": + n += 1 + return n + + # ---- 开单面(流程调用——编排器六调用点的唯一形态)---- +# 开单:A 模式当场结账;B 模式入队待 flush。返回流水号(outcome() 查询键)。 +func submit(t: Dictionary) -> int: + _seq += 1 + t["ticket_id"] = _seq + _outcomes[_seq] = "pending" + if policy == POLICY_IMMEDIATE: + _resolve(t) + else: + _queue.append(t) + return _seq + + # 单据构造(缺省填齐——调用点只写差异栏;context = kind 专用:enemy_skill 带 skill_id/baseline, -# board_flat_hero 的键并入行载荷,board_blast/board_typed 带 batch_key 供未生效行归因) +# board_flat_hero 的键并入行载荷,board_blast/board_typed 带 batch_key 供未生效行归因)。 +# attack_commit 发射点(内容数据驱动扩展批 T3——我方伤害单据开立入口,任务书 §3.1 定稿位; +# 命中挂单据 context 随行——施加面在 _resolve_hero_attack 管线算术前逐族施加,开单时点=主角攻击 +# 单恒为段内首单(结账现场与开单现场一致)。载荷 atk/target/matchup + 挖掘 streak 事实〔连珠矢/ +# 执拗订阅面〕+巢穴控制数〔猎巢人〕) func open_ticket(kind: String, source_class: String, display: String, channel: String, base_value: int, target_rule: String, ri: int, origin_cell: Vector2i = Vector2i(-1, -1), attack_type: String = "", rider: int = 0, highlight: String = "", context: Dictionary = {}) -> Dictionary: - return { + var t := { "kind": kind, "source_class": source_class, "origin_cell": origin_cell, @@ -102,18 +172,16 @@ func open_ticket(kind: String, source_class: String, display: String, channel: S "requires": -1, # 前置单据 id(-1 = 无依赖) "source_unit": {}, # enemy_skill 专用——来源敌句柄(作废判定 + commit) } - - -# 开单:A 模式当场结账;B 模式入队待 flush。返回流水号(outcome() 查询键)。 -func submit(t: Dictionary) -> int: - _seq += 1 - t["ticket_id"] = _seq - _outcomes[_seq] = "pending" - if policy == POLICY_IMMEDIATE: - _resolve(t) - else: - _queue.append(t) - return _seq + if kind == KIND_HERO_ATTACK: + var tgt = _target_for(target_rule) + var armor_b: String = String(tgt.get("armor_type", "light")) if tgt != null else "light" + var matchup_o: String = (attack_type + "_vs_" + armor_b) if attack_type != "" else "" + var ac_payload := {"atk": int(base_value), + "target": String(tgt.get("display_name", "")) if tgt != null else "", + "matchup": matchup_o, "dig_streak": dig_streak, "nest_count": _nest_count()} + t.context["fx_attack_hits"] = _fx_hits("attack_commit", ac_payload) + t.context["fx_attack_payload"] = ac_payload + return t # 同步开单(清图批专用——完美第二份与高光份数的流程依赖需要同步读数,开单权归流程 用户裁决③): @@ -189,6 +257,8 @@ func _resolve(t: Dictionary) -> void: # 主角先手攻击:掷点 → 折扣/暴击 → 克制点数 → hero_offense×enemy_defense 池乘区 → 下限 → 交付 +# (内容数据驱动扩展批 T3——attack_commit/clamp_check/armor_roll 订阅施加面:命中=开单入口随单 +# context 携带,本函数按迁移前算序逐族施加——命中序=数据表声明序,确定性) func _resolve_hero_attack(t: Dictionary) -> void: var target = _target_for(String(t.target_rule)) if target == null: @@ -196,47 +266,70 @@ func _resolve_hero_attack(t: Dictionary) -> void: return var armor: String = String(target.get("armor_type", "light")) var atk_type: String = String(t.attack_type) + var matchup := (atk_type + "_vs_" + armor) if atk_type != "" else "" + var ac_hits: Array = (t.context.get("fx_attack_hits", []) as Array) + var ac_payload: Dictionary = (t.context.get("fx_attack_payload", {}) as Dictionary) var roll_read: Dictionary = _armor_roll() var threshold: int = defender_ac(target) + int(db.settlement.attack_bonus) - # 矿工重锤·命中交换(迭代 12 T4 普攻流——正负双修原生形态:命中判定减成 trade_hit 点 - # 〔更易命中〕换攻击增伤 trade_atk%——高 AC 负期望/低 AC 正期望) - var trade_hit := int(feat_flags.get("hit_trade_hit", 0)) + # armor_roll 发射点(内容数据驱动扩展批 T1——敌护甲掷骰判定;免掷恒折扣订阅面) + var ar_hits := _fx_hits("armor_roll", {"target_ac": threshold, "roll": int(roll_read.roll)}) + # 按序改写族第一拍:矿工重锤命中交换(正负双修原生形态:命中判定减成 trade_hit 点〔更易命中〕 + # 换攻击增伤 trade_atk%——高 AC 负期望/低 AC 正期望;改写位先于掷点折扣读数,与迁移前一致) + var trade_hit := 0 + for h in ac_hits: + if String(h.get("kind", "")) == "modify_ticket" and int(h.get("trade_hit", 0)) > 0: + trade_hit += int(h.trade_hit) if trade_hit > 0: threshold = maxi(1, threshold - trade_hit) var crit: bool = bool(roll_read.crit) var discount: int = _roll_discount(int(roll_read.roll), threshold) - # 专注(迭代 12 T4 普攻流——免掷骰恒定折扣:方差买断成期望——稳定器人格;roll 记 0 归因) - var rewrite := int(feat_flags.get("armor_roll_rewrite", 0)) - if rewrite > 0: - discount = rewrite - crit = false - roll_read["roll"] = 0 - # 厚积薄发满层必中(迭代 12 T5 蓄力流——spike ready 时 discount 恒全额、暴击面走倍乘不加掷) - if int(feat_flags.get("spike_mult", 0)) > 0 and int(feat_flags.get("charge_layers", 0)) >= 3: - discount = 100 - # 猎弱本能(迭代 12 T4 穿刺流——对轻甲敌折扣档改善 value 档:对冲 pierce 列 L1 −1 开局罚; + # 按序改写族第二拍:专注免掷骰恒定折扣(方差买断成期望——稳定器人格;roll 记 0 归因) + for h in ar_hits: + if String(h.get("kind", "")) == "modify_ticket" and String(h.get("mode", "")) == "armor_rewrite": + discount = int(h.get("value", 0)) + crit = false + roll_read["roll"] = 0 + # 按序改写族第三拍:厚积薄发满层必中(spike ready 时 discount 恒全额、暴击面走倍乘不加掷; + # when charge_layers≥3 在 dispatch 判定——满层持续状态口径) + var spike_ready := false + for h in ac_hits: + if String(h.get("kind", "")) == "modify_ticket" and String(h.get("mode", "")) == "spike": + spike_ready = true + discount = 100 + # 按序改写族第四拍:猎弱本能对轻甲敌折扣档改善 value 档(对冲 pierce 列 L1 −1 开局罚; # 改善 = 折扣表向全额方向移档,nat 最大面/已全额不重复) - var shift := int(feat_flags.get("discount_shift", 0)) + var shift := 0 + var clamp_hits := _fx_hits("clamp_check", {"matchup": matchup}) + for h in clamp_hits: + if String(h.get("kind", "")) != "modify_ticket": + continue + if String(h.get("mode", "")) == "discount_shift": + shift += int(h.get("value", 0)) if shift > 0 and armor == "light" and discount < 100: discount = _discount_shift_up(discount, shift) - var cdelta: int = _counter_delta(atk_type, armor) + _feat_clamp_bonus(atk_type, armor) - # 淬毒刃纹(迭代 11 同调批 T5——底稿 §3:对被克制敌人的穿刺/魔法直伤 +N;条件 = 克制成立 × - # 攻击类型限定,主角攻击面专属——棋盘克制直伤走 _resolve_board_typed 不吃本加成) - if attune_venom > 0 and (atk_type == "pierce" or atk_type == "magic") and cdelta > 0: - cdelta += attune_venom + # 克制点数读值+订阅改写(魔网织者/湛蓝誓约/猎手刺剑 clamp_check modify delta——克制矩阵查表值 + # 后的直调;hero_attack 与 board_typed 两消费面同享) + var cdelta: int = _counter_delta(atk_type, armor) + for h in clamp_hits: + if String(h.get("kind", "")) == "modify_ticket" and h.has("delta"): + cdelta += int(h.delta) + # 淬毒刃纹(迭代 11 同调批 T5 → 内容数据驱动扩展批 T4:特判拆除——行 triggers clamp_check + # modify(mode:clamp_bonus) 订阅命中已并入上方 delta 合计;mode 路由=主角攻击面专享, + # 棋盘克制直伤窗只消费无模式 delta——棋盘不吃毒面与迁移前一致) var base_value := float(t.base_value) - var trade_atk := int(feat_flags.get("hit_trade_atk", 0)) - if trade_atk > 0: - base_value *= 1.0 + float(trade_atk) / 100.0 # 重锤增伤面(交换的正向侧) - # 蓄力流(迭代 12 T5):整备军备「每层下轮首段 +1」(层数×value 加算)+厚积薄发满层(≥3) - # spike 倍乘+强制必中(discount 100/crit 关闭掷骰面——方差买断的爆发出口) - var charge_layers := int(feat_flags.get("charge_layers", 0)) - if charge_layers > 0: - base_value += float(charge_layers * int(feat_flags.get("ap_carry_value", 0))) - var spike_mult := int(feat_flags.get("spike_mult", 0)) - var spike_ready := spike_mult > 0 and charge_layers >= 3 + # 按序改写族第五拍:矿工重锤增伤面(交换的正向侧) + for h in ac_hits: + if String(h.get("kind", "")) == "modify_ticket" and int(h.get("trade_atk", 0)) > 0: + base_value *= 1.0 + float(int(h.trade_atk)) / 100.0 + # 加法族:整备军备「每层下轮首段 +1」(层数×value 加算)——attack_bonus per charge_layers + for h in ac_hits: + if String(h.get("kind", "")) == "attack_bonus": + base_value += float(int(h.get("value", 0)) * _per_value(String(h.get("per", "")), ac_payload)) + # 任意真族:厚积薄发满层 spike 倍乘(≥3 层——强制必中已在折扣拍承载) if spike_ready: - base_value *= float(spike_mult) + for h in ac_hits: + if String(h.get("kind", "")) == "modify_ticket" and String(h.get("mode", "")) == "spike": + base_value *= float(int(h.get("value", 1))) var rolled: int = DamageMath.rolled_component(base_value, discount, crit, int(db.settlement.crit_multiplier), cdelta) var payload: Dictionary = {"target": String(target.display_name)} payload[atk_type] = rolled # 分量键 = 实际攻击类型(归因字段,单分量模型) @@ -247,53 +340,49 @@ func _resolve_hero_attack(t: Dictionary) -> void: payload["crit"] = true var pooled: int = DamageMath.pool_damage(rolled, clamped_pool("hero_offense"), clamped_pool("enemy_defense")) var landed: int = maxi(int(db.settlement.damage_floor), pooled) - # 英雄侧 rider(迭代 12 T2 魔法流——enemy_skill rider 对称位补齐):后置必中附加,独立于掷点 - # 与乘区(奥术余烬「普攻命中后恒定结算」语义);档二(magic 4 件)rider 可暴击=当次 crit 时 - # rider 值乘暴击倍率。rider 不改变下限语义(landed 含 rider 后不再过 floor)。 - var hero_rider := int(feat_flags.get("rider_flat", 0)) - # 猎巢人(迭代 12 T3 标旗流——巢穴控制数转伤害光环):控制数 = 敌方 weaken(削弱)实例数 - # 〔八型批 side-wide 状态行——标中旗控制巢穴的既有承载;原设计「对其中敌伤害」简化为全体 - # 先手攻击附加值——台账披露待追认〕,每控制 1 巢 rider +Z。 - var nest_aura := int(feat_flags.get("nest_aura_value", 0)) - if nest_aura > 0: - for eff in effects.query_active("enemy"): - if String(eff.effect_id) == "weaken": - hero_rider += nest_aura - # 矿工的执拗(迭代 12 T4 普攻流——「排雷排得多好,打得就有多疼」):本轮安全开格 streak - # 〔编排器授予段实时写入 feat_flags.dig_streak;踩雷清零〕×value,上限 cap。 - var rdig := int(feat_flags.get("round_dig_value", 0)) - if rdig > 0: - hero_rider += mini(int(feat_flags.get("round_dig_cap", 5)), int(feat_flags.get("dig_streak", 0)) * rdig) + # 加法族:英雄侧 rider(enemy_skill rider 对称位补齐口径)——后置必中附加,独立于掷点与乘区 + # (奥术余烬「普攻命中后恒定结算」语义);猎巢人巢控光环(per nest_count)/矿工的执拗开格加成 + # (per dig_streak、cap 封顶)同通道合计;档一质变 rider+1 / 档二(crit 时)rider 乘暴击倍率。 + # rider 不改变下限语义(landed 含 rider 后不再过 floor)。 + var hero_rider := rider_tier_bonus + for h in ac_hits: + if String(h.get("kind", "")) == "add_ticket_rider": + var contrib := int(h.get("value", 0)) * _per_value(String(h.get("per", "")), ac_payload) + if h.has("cap"): + contrib = mini(int(h.cap), contrib) + hero_rider += contrib if hero_rider > 0: - if crit and bool(feat_flags.get("rider_crit", false)): + if crit and rider_tier_crit: hero_rider = int(round(float(hero_rider) * float(db.settlement.crit_multiplier))) landed += hero_rider payload["hero_rider"] = hero_rider units.apply_damage(target, landed) payload["killed"] = not target.alive # 击杀标记在扣血后取(原语义不变) - # 碎甲震击(迭代 12 T4 普攻流——命中重甲附加「护甲裂解」):复用 off_guard(enemy percent - # +25 受伤加深——零新状态行);apply_effect 域闭/重复授予由状态容器自行守卫。 - var crack := int(feat_flags.get("crack_value", 0)) - if crack > 0 and armor == "heavy": - effects.apply_effect("off_guard", {"source_class": "feat_armor_crack"}, -1) - payload["cracked"] = true + # kill 发射+施加(内容数据驱动扩展批 T3——敌人死亡确认;贯穿射击溢出弹射订阅面:对下一存活敌 + # 以穿刺类型弹射本体 value%〔无次敌=弹射落空〕。B 模式入队段尾 flush 兑现;A 模式递归 resolve) + if bool(payload.get("killed", false)): + for h in _fx_hits("kill", {"target": String(target.display_name), "overflow": landed}): + if String(h.get("kind", "")) != "overflow": + continue + var next_target = units.default_target() + if next_target != null: + var of_t := open_ticket(DamageController.KIND_BOARD_TYPED, "feat_pierce_shot", + "贯穿射击", "board", maxi(int(db.settlement.damage_floor), int(landed * int(h.get("value", 50)) / 100)), + DamageController.TARGET_FRONT_ALIVE, int(t.round_index), t.origin_cell, "pierce") + if policy == POLICY_SEGMENT: + _queue.append(of_t) + else: + _resolve(of_t) + # 即时执行族:碎甲震击打重甲附加「护甲裂解」(复用 off_guard——零新状态行;apply_effect 域闭/ + # 重复授予由状态容器自行守卫) + for h in ac_hits: + if String(h.get("kind", "")) == "apply_status": + effects.apply_effect(String(h.get("status_id", "")), {"source_class": "feat_armor_crack"}, -1) + payload["cracked"] = true var label := _attack_label(String(t.display), crit, discount) if hero_rider > 0: label += "+奥术%d(必中)" % hero_rider emit_row_cb.call("damage", String(t.source_class), t.origin_cell, int(t.round_index), label, landed, payload, String(t.highlight)) - # 贯穿射击(迭代 12 T4 穿刺流——击杀溢出弹射):击杀时对下一存活敌以穿刺类型弹射 - # 本体半额〔原设计「溢出伤害」追踪在单分量模型不可行——半额弹射 provisional 裁定披露待追认; - # 无次敌=弹射落空〕。B 模式入队段尾 flush 兑现;A 模式递归 resolve(目标存活守卫内建)。 - if bool(payload.get("killed", false)) and int(feat_flags.get("kill_overflow", 0)) > 0: - var next_target = units.default_target() - if next_target != null: - var of_t := open_ticket(DamageController.KIND_BOARD_TYPED, "feat_pierce_shot", - "贯穿射击", "board", maxi(int(db.settlement.damage_floor), int(landed / 2)), - DamageController.TARGET_FRONT_ALIVE, int(t.round_index), t.origin_cell, "pierce") - if policy == POLICY_SEGMENT: - _queue.append(of_t) - else: - _resolve(of_t) _outcomes[int(t.ticket_id)] = "redeemed" @@ -308,11 +397,14 @@ func _resolve_enemy_skill(t: Dictionary) -> void: var rolled: int = DamageMath.rolled_component(float(t.base_value), discount, crit, int(db.settlement.crit_multiplier), 0) var pooled: int = DamageMath.pool_damage(rolled, clamped_pool("hero_defense"), clamped_pool("enemy_output")) var total: int = maxi(int(db.settlement.damage_floor), pooled) + int(t.rider) # rider 后置于伤害下限——独立必中加项,不进乘区 - # 铁壁整备(迭代 12 T5 蓄力流——每层下轮开场 S 点减伤;减后过下限保底) - var shield := int(feat_flags.get("shield_per_layer", 0)) * int(feat_flags.get("charge_layers", 0)) - # 稳健拓荒(迭代 12 T10——Fork posture B 侧 rule 键:受击减 1;两侧对称弱值=「不同强非强弱」) - if int(feat_flags.get("bulwark", 0)) > 0: - shield += 1 + # damage_taken 发射+施加(内容数据驱动扩展批 T3——kind=enemy_skill;铁壁整备减伤订阅面: + # shield 加法族合计 value×per(charge_layers),稳健拓荒树规则同面(rebuild 合成订阅); + # 减后过下限保底) + var shield := 0 + for h in _fx_hits("damage_taken", {"kind": "enemy_skill", "amount": total, + "source": String(t.context.get("skill_id", "")), "round": int(t.round_index)}): + if String(h.get("kind", "")) == "shield": + shield += int(h.get("value", 0)) * _per_value(String(h.get("per", "")), {}) if shield > 0: total = maxi(int(db.settlement.damage_floor), total - shield) units.apply_damage(hero, total) @@ -337,11 +429,20 @@ func _resolve_board_typed(t: Dictionary) -> void: _ineffective_row(t, "无存活敌人") _outcomes[int(t.ticket_id)] = "zero" return - var delta: int = _counter_delta(String(t.attack_type), String(target.get("armor_type", "light"))) + _feat_clamp_bonus(String(t.attack_type), String(target.get("armor_type", "light"))) - var dealt: int = DamageMath.counter_component(int(t.base_value), delta, int(db.settlement.damage_floor)) + var armor_b: String = String(target.get("armor_type", "light")) + var atk_b: String = String(t.attack_type) + # clamp_check 发射+施加(内容数据驱动扩展批 T3——克制判定修正点棋盘面;与 hero_attack 同表同点: + # modify delta 订阅改写合计进克制点数;discount_shift 档位面属主角攻击专享,本面不施加) + var cdelta: int = _counter_delta(atk_b, armor_b) + if atk_b != "": + for h in _fx_hits("clamp_check", {"matchup": atk_b + "_vs_" + armor_b}): + # 无模式 delta 才入棋盘窗(mode 路由:clamp_bonus=主角攻击面专享、discount_shift=折扣档面) + if String(h.get("kind", "")) == "modify_ticket" and h.has("delta") and String(h.get("mode", "")) == "": + cdelta += int(h.delta) + var dealt: int = DamageMath.counter_component(int(t.base_value), cdelta, int(db.settlement.damage_floor)) units.apply_damage(target, dealt) emit_row_cb.call("damage", String(t.source_class), t.origin_cell, int(t.round_index), "%s → %s" % [String(t.display), String(target.display_name)], dealt, - {"target": String(target.display_name), "attack_type": String(t.attack_type), "effectiveness": delta}, String(t.highlight)) + {"target": String(target.display_name), "attack_type": String(t.attack_type), "effectiveness": cdelta}, String(t.highlight)) _outcomes[int(t.ticket_id)] = "redeemed" @@ -418,23 +519,6 @@ func _counter_delta(attack_type: String, armor: String) -> int: return int(row.get(armor, 0)) -# 克制点直调(迭代 12 T2 魔法流/T4 穿刺流 clamp_modifier 件——克制矩阵查表值后的特性调整; -# 空旗=0 零扰动):魔网织者=magic vs magic(矩阵罚 −1 → 0/正);湛蓝誓约=magic vs heavy(+1 → +2); -# 猎手刺剑=pierce vs light(+1——对轻甲敌穿刺提升,对重甲无增益=正负双修)。hero_attack 与 -# board_typed 两消费面同享。 -func _feat_clamp_bonus(attack_type: String, armor: String) -> int: - var bonus := 0 - if attack_type == "magic": - if armor == "magic": - bonus += int(feat_flags.get("clamp_magic_magic", 0)) - elif armor == "heavy": - bonus += int(feat_flags.get("clamp_magic_heavy", 0)) - elif attack_type == "pierce": - if armor == "light": - bonus += int(feat_flags.get("clamp_pierce_light", 0)) - return bonus - - # 折扣档改善(迭代 12 T4 猎弱本能——折扣表向全额方向移 value 档;查不到当前档 = 不动防御口径) func _discount_shift_up(discount: int, shift: int) -> int: var tbl: Array = db.settlement.roll_tier_discounts @@ -444,9 +528,6 @@ func _discount_shift_up(discount: int, shift: int) -> int: return int(tbl[mini(tbl.size() - 1, idx + shift)]) -# 淬毒刃纹 N(迭代 11 同调批 T5——战斗结算 begin_battle 注入;0 = 未装配零扰动) -var attune_venom: int = 0 - # 池内加算 → clamp [-100,+100](池间乘算归 DamageMath.pool_damage 调用侧) func clamped_pool(pool: String) -> int: var total := 0 diff --git a/game/core/effect_engine.gd b/game/core/effect_engine.gd new file mode 100644 index 00000000..54bc60ad --- /dev/null +++ b/game/core/effect_engine.gd @@ -0,0 +1,343 @@ +# VERTICAL SLICE - NOT FOR PRODUCTION +# Date: 2026-10-06(迭代 17 卡⓪ 内容数据驱动扩展批 T1——效果引擎框架) +# 内容数据驱动扩展(任务书 design/research/任务书-内容数据驱动扩展批-2026-10-06.md;设计权威 +# design/research/研究-内容数据驱动扩展架构-2026-10-06.md):把「内容的效果行为」从散布在结算 +# 代码里的硬编码分支搬进内容数据行三个字段——订阅哪个事件(on)/ 什么条件(when)/ 什么效果(do) +# ——由本解释器统一执行。完成后加一件「已有效果类型」的新专长/新秘宝=只改 .tres 数据行,零代码。 +# 架构模型(R1 裁决):发布订阅——结算管线同步 emit 事件(无队列无异步,确定性优先)→ 本引擎遍历 +# 持有订阅集匹配 → 返回按声明序排列的效果命中(do 列表),由发射点按组合规则施加。 +# 多订阅与冲突(R3 裁决):一事件多订阅=常态;冲突在效果类型层用组合规则封死(§3.4 六族);遍历序 +# =数据表声明序(feats 表序→treasure 表序→树规则合成行,装载时定死),写死保种子复放。 +# 重建时机(v1.1 补):每战 begin_battle 从持有集/装备集重建 trigger 列表——冒险中途获得/换取的 +# 专长下一战生效,同战内不热载,确定性优先。计数器账本随重建清零(battle 域)。 +# 确定性契约:全同步调用、零 RNG、零 signal 时序依赖;同输入同命中序。 +# 护栏保留:事件/效果/事实/算子四词表封闭(本文件常量),行内出域=加载期校验致命(schema 侧), +# 运行期 dispatch 对未知事件/效果类型 fail-fast(push_error+断言)。 +class_name EffectEngine +extends RefCounted + +## 事件词汇(16 个发射点——任务书 §3.1 定稿;4 个登记暂不做事件不入本表) +const EVENTS := ["dig_reveal", "dig_paid", "flag_hit", "mine_hit", "attack_commit", "clamp_check", + "armor_roll", "kill", "damage_taken", "enemy_hp_threshold", "round_start", "flag_settle", + "round_end", "reward_roll", "treasure_used", "charge_full"] + +## 扫雷侧事件集(🔴 流派扫雷触发铁律新形态:每流派 ≥1 行订阅本集事件——schema 加载校验。 +## 原 MINE_HOOKS 的同义迁移:挖掘/标旗/轮末结算面事件) +const MINE_EVENTS := ["dig_reveal", "dig_paid", "flag_hit", "mine_hit", "flag_settle", "round_end", "reward_roll"] + +## 效果类型词汇(解释器 switch 闭集——任务书 §3.3 定稿 19 行;类型实数 24,含共享行拆型: +## grant_gp/bias_wallet、counter_add/counter_spend、reveal_tiles/remove_mines/refresh_pick、 +## bias_rewards/preview。任务书标题计 23 系笔误,落码按行内容全收——commit 留痕) +const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open_ticket", + "attack_bonus", "attack_segments", "yield_mult", "spawn_spell", "overflow", "grant_material", + "grant_ap", "grant_gp", "bias_wallet", "shield", "spike", "apply_status", "counter_add", + "counter_spend", "rewrite_board", "reveal_tiles", "remove_mines", "refresh_pick", + "bias_rewards", "preview"] + +## fact 条件词汇(封闭枚举 9 项——任务书 §四定稿) +const FACTS := ["matchup", "is_first_dig_of_battle", "terrain_kind", "target_armor_class", + "flags_count", "charge_layers", "nest_count", "round_index", "counter"] + +## when 算子词汇(封闭——fact 查询面落法随批登记:mod_zero=计数器取模判满〔掘进蓄能〕、 +## advantageous=克制矩阵正面判定〔淬毒刃纹〕、gte_ref/lt_ref=与另一计数器比较〔深挖钻头配额〕、 +## ends_with=后缀匹配〔对某甲族的泛条件〕。均系映射表现状条件装进 9 项 fact 的查询面落法, +## 代拍板白名单覆盖,commit 留痕) +const OPS := ["eq", "neq", "gt", "gte", "lt", "lte", "ends_with", "has", "mod_zero", + "advantageous", "gte_ref", "lt_ref"] + +## when 条件键白名单(trigger 行 when 数组元素键) +const WHEN_KEYS := ["fact", "op", "value", "id", "ref", "value_from"] + +## do 效果键白名单(trigger 行 do 字典键——零裸 flat 同族护栏:kind 外键全部参数引用面) +const DO_KEYS := ["kind", "value", "value_from", "delta", "delta_from", "mult", "mult_from", + "cap", "cap_from", "cost_mult", "per", "divisor", "dtype", "mode", "channel", "counter_id", + "reset_at", "set", "status_id", "label", "fallback", "target_kinds", "trade_atk", "trade_hit"] + +## 主动件分发通道(不经事件订阅——刷新/显影/移除是玩家主动动作;use_treasure 按 kind 分发) +const ACTIVE_KINDS := ["reveal_tiles", "preview", "grant_ap", "remove_mines", "refresh_pick"] + +# ---- 订阅集与计数器账本 ---- +var _triggers: Array = [] # [{source, id, on, when, do}](装载序=声明序,重建时定死) +var _counters: Dictionary = {} # counter_id → int(battle 域账本;重建清零;重置点由订阅行声明) +var _reset_on_round_start: Array[String] = [] # round_start 重置点计数器名单(装载时收集) +var db: ConfigDb # 矩阵/表读取(advantageous 算子消费)——可空(纯计数器用法零依赖) + + +func _init(db_ref: ConfigDb = null) -> void: + db = db_ref + + +## 订阅装载(每战 begin_battle 调用——重建时机 v1.1):feats 表序→treasure 表序(各按持有/装备 +## 集过滤)→树规则合成行。同一数据行的多个 trigger 按行内数组序。计数器账本同步清零并按订阅行 +## 声明收集重置点(round_start)。 +func rebuild(feat_ids: Array, item_ids: Array, tree_rules: Array = []) -> void: + _triggers.clear() + _counters.clear() + _reset_on_round_start.clear() + if db != null and db.feats != null: + for row in db.feats.rows: + var fid := String(row.get("feat_id", "")) + if not feat_ids.has(fid): + continue + for tr in (row.get("triggers", []) as Array): + _push_trigger("feat", fid, tr, row) + if db != null: + for row in db.treasure_attunement_rows(): + var iid := String(row.get("item_id", "")) + if not item_ids.has(iid): + continue + for tr in (row.get("triggers", []) as Array): + _push_trigger("attune", iid, tr, row) + # 树规则合成订阅(T5——规则键效果走同面:非数据行,词表同闭集) + for key_v in tree_rules: + var rk := String(key_v) + match rk: + "posture_first_strike": + _push_trigger("tree", rk, {"on": "round_start", + "when": [{"fact": "round_index", "op": "eq", "value": 1}], + "do": {"kind": "grant_ap", "mode": "queue", "value": 1}}, {}) + "posture_bulwark": + _push_trigger("tree", rk, {"on": "damage_taken", + "do": {"kind": "shield", "value": 1}}, {}) + + +func _push_trigger(source: String, id: String, tr: Dictionary, row: Dictionary) -> void: + var on := String(tr.get("on", "")) + assert(EVENTS.has(on), "[效果引擎] 订阅事件出域: %s(%s %s)" % [on, source, id]) + var do_d: Dictionary = (tr.get("do", {}) as Dictionary).duplicate(true) + # *_from 解析(rebuild 时把行字段引用定影为值——value_from:"magnitude" → value=行 magnitude) + for pair in [["value_from", "value"], ["delta_from", "delta"], ["mult_from", "mult"], ["cap_from", "cap"]]: + if do_d.has(pair[0]): + do_d[pair[1]] = int(row.get(String(do_d[pair[0]]), 0)) + do_d.erase(pair[0]) + # when value_from 同面解析(行字段引用条件——dawn_order effective_rounds 先例:字面量出行=数值 + # 单一事实源破面,行引用随 rebuild 定影) + var when_d: Array = (tr.get("when", []) as Array).duplicate(true) + for c_v in when_d: + var c: Dictionary = c_v as Dictionary + if c.has("value_from"): + c["value"] = int(row.get(String(c["value_from"]), 0)) + c.erase("value_from") + var t := {"source": source, "id": id, "on": on, + "when": when_d, + "do": do_d} + _triggers.append(t) + # 计数器重置点收集(counter_add/counter_spend 行声明 reset_at) + if do_d.has("counter_id") and String(do_d.get("reset_at", "")) == "round_start": + var cid := String(do_d.counter_id) + if not _reset_on_round_start.has(cid): + _reset_on_round_start.append(cid) + + +## 订阅查询面(发射点缺省行为仲裁用——如 round_end 无 transfer 订阅时走 RAP 转存) +func has_effect_on(event: String, kind: String, mode: String = "") -> bool: + for tr in _triggers: + if String(tr.on) != event: + continue + var d: Dictionary = tr.do + if String(d.get("kind", "")) == kind and (mode == "" or String(d.get("mode", "")) == mode): + # 有 when 时不能预判命中(发射点缺省行为保守按「可能命中」计——命中由 dispatch 定夺) + return true + return false + + +## 事件分发(发射点唯一入口):返回命中效果列表(装载序=声明序;零命中=空数组零扰动)。 +## payload = 事件现场(fact 取值第一优先源);facts = 补充事实面(charge_layers/nest_count 等 +## 运行态读数 Callable——payload 未带时回落)。 +func dispatch(event: String, payload: Dictionary, facts: Callable = Callable()) -> Array: + assert(EVENTS.has(event), "[效果引擎] 未知事件: %s(发射点出域)" % event) + if event == "round_start": + for cid in _reset_on_round_start: + _counters[cid] = 0 + var hits: Array = [] + for tr in _triggers: + if String(tr.on) != event: + continue + if not _when_ok(tr.when, payload, facts): + continue + hits.append(tr.do) + return hits + + +## 计数器读数(fact counter 与发射点配额面共用) +func counter(counter_id: String) -> int: + return int(_counters.get(counter_id, 0)) + + +func counter_add(counter_id: String, value: int, set_mode: bool = false) -> void: + if set_mode: + _counters[counter_id] = value + else: + _counters[counter_id] = int(_counters.get(counter_id, 0)) + value + + +## 计数器消耗(counter_spend 组合规则——按订阅序消耗共享资源,耗尽即止): +## 返回 true=消耗成功;false=资源不足(调用方按 reason 通道拒绝)。 +func counter_spend(counter_id: String, value: int) -> bool: + var cur := int(_counters.get(counter_id, 0)) + if cur < value: + return false + _counters[counter_id] = cur - value + return true + + +# ---- when 判定(条件谓词——零随机全确定) ---- + +func _when_ok(when: Array, payload: Dictionary, facts: Callable) -> bool: + for c_v in when: + if not _cond_ok(c_v as Dictionary, payload, facts): + return false + return true + + +func _cond_ok(c: Dictionary, payload: Dictionary, facts: Callable) -> bool: + var fact := String(c.get("fact", "")) + var op := String(c.get("op", "eq")) + var val = c.get("value", null) + var actual = _fact_value(fact, c, payload, facts) + match op: + "eq": + return str(actual) == str(val) + "neq": + return str(actual) != str(val) + "gt": + return int(actual) > int(val) + "gte": + return int(actual) >= int(val) + "lt": + return int(actual) < int(val) + "lte": + return int(actual) <= int(val) + "ends_with": + if val is Array: + for v in (val as Array): + if String(actual).ends_with(String(v)): + return true + return false + return String(actual).ends_with(String(val)) + "has": + # 在场判定(fact 查询面落法——代拍板白名单,commit 留痕):value 缺省=非空判定 + # (terrain_kind fact:伤害型变异集非空=挖开伤害型地形格/邻格有伤害型地形/地形伤种类在场); + # value 给定=数组包含判定(保留泛用面)。 + if c.has("value"): + if actual is Array: + return (actual as Array).has(val) + return false + if actual is Array: + return not (actual as Array).is_empty() + return String(actual) != "" + "mod_zero": + var m := int(val) + return m > 0 and int(actual) % m == 0 + "advantageous": + # 克制矩阵正面判定(matchup = "{attack_type}_vs_{armor}"——矩阵值 > 0 = 克制成立) + return _counter_delta_of(String(actual)) > 0 + "gte_ref": + return int(actual) >= counter(String(c.get("ref", ""))) + "lt_ref": + return int(actual) < counter(String(c.get("ref", ""))) + assert(false, "[效果引擎] when 算子出域: %s" % op) + return false + + +func _fact_value(fact: String, c: Dictionary, payload: Dictionary, facts: Callable): + match fact: + "counter": + return counter(String(c.get("id", ""))) + _: + # fact 取值序:payload 同名字段优先 → 补充事实面(发射点注入的运行态读数) + if payload.has(fact): + return payload[fact] + if facts.is_valid(): + return facts.call(fact) + return null + + +## 克制矩阵读值(advantageous 算子消费——与伤害控制器 _counter_delta 同源同表) +func _counter_delta_of(matchup: String) -> int: + if db == null or matchup == "" or not matchup.contains("_vs_"): + return 0 + var parts := matchup.split("_vs_") + if parts.size() != 2: + return 0 + var tbl: Dictionary = db.settlement.armor_effectiveness + var row: Dictionary = tbl.get(String(parts[0]), {}) + return int(row.get(String(parts[1]), 0)) + + +## ---- 加载期校验(schema 侧共用——单一事实源;悬空引用/词表出域=致命) ---- + +## trigger 行校验:返回该行是否含扫雷侧事件订阅(流派铁律聚合面)。 +## row 用于 value_from 类引用的字段存在性判定(悬空=加载致命)。 +static func validate_trigger_row(rid: String, row: Dictionary, triggers: Array, errs: Array) -> bool: + var mine_covered := false + for tr_v in triggers: + var tr: Dictionary = tr_v as Dictionary + for k in tr.keys(): + if not ["on", "when", "do"].has(String(k)): + errs.append({"category": "zero_flat", "message": "%s trigger 键出白名单: %s" % [rid, String(k)]}) + var on := String(tr.get("on", "")) + if not EVENTS.has(on): + errs.append({"category": "enum", "message": "%s 订阅事件出域(事件词汇封闭): %s" % [rid, on]}) + elif MINE_EVENTS.has(on): + mine_covered = true + for c_v in (tr.get("when", []) as Array): + var c: Dictionary = c_v as Dictionary + for k in c.keys(): + if not WHEN_KEYS.has(String(k)): + errs.append({"category": "zero_flat", "message": "%s when 键出白名单: %s" % [rid, String(k)]}) + var fact := String(c.get("fact", "")) + if not FACTS.has(fact): + errs.append({"category": "enum", "message": "%s fact 出域(事实词汇封闭 9 项): %s" % [rid, fact]}) + if not OPS.has(String(c.get("op", "eq"))): + errs.append({"category": "enum", "message": "%s when 算子出域: %s" % [rid, String(c.get("op", ""))]}) + var do_d: Dictionary = tr.get("do", {}) as Dictionary + for k in do_d.keys(): + if not DO_KEYS.has(String(k)): + errs.append({"category": "zero_flat", "message": "%s do 键出白名单: %s(机制轴禁无条件属性加项)" % [rid, String(k)]}) + var kind := String(do_d.get("kind", "")) + if not EFFECT_KINDS.has(kind): + errs.append({"category": "enum", "message": "%s 效果类型出域(解释器闭集): %s" % [rid, kind]}) + # value_from/delta_from/mult_from/cap_from 引用面:行字段存在性(悬空=加载致命)—— + # do 与 when 两侧同面(when value_from=dawn_order effective_rounds 先例) + for vf_k in ["value_from", "delta_from", "mult_from", "cap_from"]: + if do_d.has(vf_k): + var fld := String(do_d.get(vf_k, "")) + if fld != "" and not row.has(fld): + errs.append({"category": "dangling", "message": "%s do.%s 悬空(行无字段 %s)" % [rid, vf_k, fld]}) + for c_v in (tr.get("when", []) as Array): + var c_w: Dictionary = c_v as Dictionary + if c_w.has("value_from"): + var wfld := String(c_w.get("value_from", "")) + if wfld != "" and not row.has(wfld): + errs.append({"category": "dangling", "message": "%s when.value_from 悬空(行无字段 %s)" % [rid, wfld]}) + return mine_covered + + +## 组合规则(任务书 §3.4 六族——效果类型的必填属性,发射点施加面引用) + +## 加法族(数值相加,可交换):add_ticket_rider、attack_bonus、grant_material、grant_ap(非 transfer)、 +## counter_add、shield——发射点逐条累加收集器。 +## 乘法族(收集后一次施加,正负分桶):yield_mult、damage_mult。 +## 任意真族(任一命中即生效):spawn_spell、overflow、spike。 +## 按序改写族(收集→单点按声明序施加):modify_ticket 及一切改写窗口效果。 +## 按序消耗族(按订阅序消耗共享资源,耗尽即止;可选 fallback):counter_spend。 +## 即时执行族(每条命中各自执行一次,执行序=声明序):open_ticket、grant_ap(mode:transfer)、 +## rewrite_board、apply_status——发射点逐条执行。 +static func combo_of(kind: String) -> String: + match kind: + "add_ticket_rider", "attack_bonus", "grant_material", "counter_add", "shield": + return "additive" + "grant_ap": + return "additive" # mode:transfer 由发射点即时执行族承载(转移非加法) + "yield_mult", "damage_mult": + return "multiplicative" + "spawn_spell", "overflow", "spike": + return "any_true" + "modify_ticket": + return "ordered_write" + "counter_spend": + return "ordered_spend" + "open_ticket", "rewrite_board", "apply_status": + return "immediate" + return "immediate" # 默认即时执行(每条命中各自执行一次) diff --git a/game/core/effect_engine.gd.uid b/game/core/effect_engine.gd.uid new file mode 100644 index 00000000..d63bf9a6 --- /dev/null +++ b/game/core/effect_engine.gd.uid @@ -0,0 +1 @@ +uid://dgo4b5frip3e5 diff --git a/game/core/feats_engine.gd b/game/core/feats_engine.gd index 3b7a5ebb..f44526e9 100644 --- a/game/core/feats_engine.gd +++ b/game/core/feats_engine.gd @@ -30,21 +30,8 @@ static func tiers_for(feat_ids: Array, table: FeatTable) -> Dictionary: return out -## 挂点投影:持有集 → [{feat_id, hook, archetype, tier, effects}](表序稳定——同集合同投影, -## MC 逐位可复放承载) -static func hooks_for(feat_ids: Array, table: FeatTable) -> Array: - var out: Array = [] - for rid in feat_ids: - var row: Dictionary = table.row_by_id(String(rid)) if table != null else {} - if row.is_empty(): - continue - out.append({ - "feat_id": String(row.get("feat_id", "")), - "hook": String(row.get("hook", "")), - "archetype": String(row.get("archetype", "")), - "effects": (row.get("effects", {}) as Dictionary).duplicate(true), - }) - return out +## 挂点投影随 T3 退役(内容数据驱动扩展批——hook 词族消亡:订阅投影=效果引擎 rebuild 装载序 +## (feats 表序),投影稳定性由 EffectEngine._triggers 声明序承载) ## 成型判读(读数面口径):流派 tier ≥1 = 成型(档一即判——任务书完成定义「标签+档位激活引擎可判成型」) diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index e1c6776e..4ab2b4a1 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -865,6 +865,14 @@ func shop_refresh() -> bool: return true +# GP 冒险钱包单口支出(2026-10-06——三选一 GP 付费刷新与商店刷新共用同一钱包;余额不足 false 零副作用) +func spend_gp(amount: int) -> bool: + if amount <= 0 or gp < amount: + return false + gp -= amount + return true + + # 购买(pool 模式唯一购买口):在架(kind→在架令牌)/余额前置校验零副作用 → handler 消费在架 # 令牌本体(kind+tier——_apply_curio_pick 同口写加算区+账本+摘牌,跨渠道共库)→ 出账 → 进店购买 # 次计数 → wallet 事件 → 三件全重抽(未买不保留——重抽绑购买,无独立刷新动作) diff --git a/game/core/reward_economy.gd b/game/core/reward_economy.gd index 57943374..4322dc85 100644 --- a/game/core/reward_economy.gd +++ b/game/core/reward_economy.gd @@ -23,6 +23,12 @@ var _granted := {} # Vector2i → 快照(重复受理幂等返 var _clear_identity := "" var _clear_granted := false var _clear_snapshot := {} +# 效果引擎(内容数据驱动扩展批 T1——reward_roll 发射面;偏运硬币 bias_rewards 订阅面) +var _fx: EffectEngine = null + + +func set_fx(fx: EffectEngine) -> void: + _fx = fx func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void: @@ -59,7 +65,14 @@ func roll_board(eligible: Array) -> bool: _clear_granted = false _clear_snapshot = {} var stream: RandomNumberGenerator = rng.open_stream("reward_roll") # 恰一 scope 批(格批 + 清图批尾 + 区间行追加取号) - var cell_rows: Array = _biased_cell_rows() # cell 池正向行 weight>0(偏运硬币装配时稀有正向 kinds 权重 +N——池结构零触碰) + # reward_roll 发射+施加(内容数据驱动扩展批 T4——格面资源掷定、摇号前权重合成层: + # bias_rewards 订阅命中合计=偏运权重加算(行 value_from magnitude 定影);零命中=原行集零扰动) + var luck_bias := 0 + if _fx != null: + for h in _fx.dispatch("reward_roll", {"polarity": "positive", "cell_count": list.size()}): + if String(h.get("kind", "")) == "bias_rewards": + luck_bias += int(h.get("value", 0)) + var cell_rows: Array = _biased_cell_rows(luck_bias) # cell 池正向行 weight>0(偏运硬币装配时稀有正向 kinds 权重 +N——池结构零触碰) var clear_rows: Array = _pool_rows("clear_default") # ① 身份批:每非封锁格恰一类型身份(雷安同池——同一行集同一权重分布) var draft := {} @@ -174,8 +187,8 @@ func polarity_row(cell_type: String, polarity: String) -> Dictionary: # ---- F1 选行算子纯函数 ---- -# 偏运硬币 N(迭代 11 同调批 T5——战斗结算 begin_battle 注入;0 = 未装配零扰动) -var luck_bias: int = 0 +# 偏运硬币 N(迭代 11 同调批 T5 → 内容数据驱动扩展批 T4:begin_battle 注入拆除—— +# reward_roll bias_rewards 订阅命中合计经 _biased_cell_rows 入参;0 = 未装配零扰动) # 稀有正向资源 kinds(底稿 §3——权重投影面,池结构零触碰) const LUCK_BIAS_KINDS := ["heal", "gp", "pierce", "magic"] @@ -192,15 +205,15 @@ const SKEW_UP_KINDS := ["attack", "blast", "pierce", "magic"] # 偏运硬币权重投影(底稿 §3:只动池内权重、雷安仍同池同分布;luck_bias=0 逐位返回原行集——off 态零扰动) # 基座换源(首发批 2026-10-04):先经图级偏斜投影再叠件级加法——合成序锚=图级先、件级后 #(环境事实先定型、玩家构筑叠其上;两投影均为池内权重面,雷安同池与取号次数零变化) -func _biased_cell_rows() -> Array: +func _biased_cell_rows(bias: int) -> Array: var base: Array = _skewed_cell_rows() - if luck_bias <= 0: + if bias <= 0: return base var out: Array = [] for row in base: if LUCK_BIAS_KINDS.has(String(row.get("cell_type", ""))): var biased: Dictionary = row.duplicate() - biased["weight"] = int(row.weight) + luck_bias + biased["weight"] = int(row.weight) + bias out.append(biased) else: out.append(row) diff --git a/game/core/unit_container.gd b/game/core/unit_container.gd index 97ab9628..7daa9049 100644 --- a/game/core/unit_container.gd +++ b/game/core/unit_container.gd @@ -2,7 +2,7 @@ # Validation Question: 玩家能否无指导在一场完整战斗中经历「开战→挖掘规划→轮末结算逐行兑现→胜/负」并逐行归因(P2) # Date: 2026-09-16 # 敌人系统·战斗单位容器:hero/enemy 统一受伤死亡接口 · 实例池状态机(敌人技能池批——2026-09-16 用户拍板, -# 设计权威 docs/proposal-skill-pool.md v1.0)· 行值快照抗热重载 · 编排被动零事件。 +# 设计权威 proposal-skill-pool.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 技能池节〕 v1.0)· 行值快照抗热重载 · 编排被动零事件。 # 实例池三层对象(proposal §2.2):定义层技能行(SkillTable)· 定义层池模板(EnemyTable.pool)· # 本类持实例层——spawn 把模板快照为各实例池(冷却全零),此后冷却递减/待发动作/保底计数各自独立。 # 零随机零事件纪律(proposal 约束二):随机消费只在 战斗结算换算层抽签器(SkillLottery + behavior_roll 子流); diff --git a/game/data/schemas/adventure_table.gd b/game/data/schemas/adventure_table.gd index 3cc181b8..bd5dd0b4 100644 --- a/game/data/schemas/adventure_table.gd +++ b/game/data/schemas/adventure_table.gd @@ -80,6 +80,23 @@ const TIER_QUOTAS := {"common": 70, "rare": 25, "rarest": 3} @export var feat_ticket_pity_step: int = 5 # [1,99] knob(口径定死=5)——保底步数(连续未出展示次数) +## 令牌兑付路由表(内容数据驱动扩展批 T5——_apply_curio_pick match 收敛进效果类型: +## 属性值提升四类 = 「加算落点键」声明路由,加新加算类令牌 = 表行+配额/件值键,零代码; +## 通道面三类(gp 钱包/heal 台账/refresh 界面余量)+relic 持有集 = 副作用通道显式分支不进本表 +## ——路由表只承载「纯加算落点」形态,与效果引擎组合规则加法族同构) +const CURIO_APPLY_ROUTES := { + "attack": "atk", + "max_hp": "max_hp", + "armor": "armor_class", + "ap": "ap", +} + + +## 令牌加算路由读数(空 = 非加算形态——调用方走通道面分支) +func curio_apply_route(kind: String) -> String: + return String(CURIO_APPLY_ROUTES.get(kind, "")) + + ## 令牌配额读数(未列 kind×tier 组合 = 0——heal 无普通档/ap 无普通稀有档按 v2.2 构成) func curio_quota(kind: String, tier: String) -> int: match [kind, tier]: diff --git a/game/data/schemas/enemy_table.gd b/game/data/schemas/enemy_table.gd index 417be90d..a6b3b74f 100644 --- a/game/data/schemas/enemy_table.gd +++ b/game/data/schemas/enemy_table.gd @@ -3,7 +3,7 @@ # Date: 2026-09-10 # 数据配置 EnemyTable / HeroTable schema(敌人系统 回填值)。EnemyTable 键 (L,D,enemy_id),每行集恰一敌种; # HeroTable 单行全局(Rule 4 豁免)。依据 design/gdd/systems/战斗循环/敌人系统.md Schema 节。 -# 敌人技能池批(2026-09-16 用户拍板,设计权威 docs/proposal-skill-pool.md v1.0):pattern 行为环数组与 +# 敌人技能池批(2026-09-16 用户拍板,设计权威 proposal-skill-pool.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 技能池节〕 v1.0):pattern 行为环数组与 # heavy_multiplier 退役,改池模板 pool = [{skill_id, weight}](定义层——实例池快照与冷却状态在 单位容器)。 # 池构成纪律(每池恰一行普攻、增益 ≤1、skill_id 悬空致命)需跨表视野,校验在 ConfigDB 跨表通道。 class_name EnemyTable diff --git a/game/data/schemas/feat_table.gd b/game/data/schemas/feat_table.gd index 6f43a3d4..fcd0dab1 100644 --- a/game/data/schemas/feat_table.gd +++ b/game/data/schemas/feat_table.gd @@ -1,7 +1,8 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # Validation Question: 七核心流派各 ≥3 件入局内随机渠道后,标签+档位激活引擎能否机械判「成型」、 # 专长带能否挂进令牌池稀有/罕见档随机投放(P0——迭代 12 双层 BD 实施批 T1) -# Date: 2026-10-04(双层 BD 实施批 T1——任务书五裁定稿;设计权威 双层BD体系-整合定稿-2026-10-03 v3) +# Date: 2026-10-06(内容数据驱动扩展批 T3——行换代:hook/effects 挂点形态退役,triggers 事件订阅形态 +# 唯一合法行形——旧 23 挂点枚举消亡:事件名+效果类型取代,任务书 R4 裁决) # 概念指路:本表的专长,与 adventure.tres 的属性值提升、资源,同属一个对象—— # 「奇物」(一次冒险内各渠道给予玩家的增益总称)。定义见 design/gdd/systems/冒险循环/奇物.md。 # 它们是它分类轴上的枚举(专长 / 属性值提升 / 资源),不是三套系统;本表承载「专长」(专长 Feat)的逐项定义。 @@ -11,16 +12,15 @@ # 数据配置 FeatTable schema(奇物·专长定义表;行 = Dictionary 键 feat_id,Rule 4 豁免 # 定义表——TreasureItemTable 先例)。 # 行字段:feat_id 主键 / display_name / rarity ∈ {common, rare, rarest}(对齐令牌池三档命名—— -# 投放档映射:common/rare→稀有令牌档、rarest→罕见令牌档;relic 令牌永不入普通档=「挂稀有/罕见档」 -# 追认 b 接线口径)/ archetype ∈ ARCHETYPES 七流派标签 / hook ∈ HOOKS 挂点枚举(结算段槽——整合定稿 -# §2.1 哈迪斯恩赐槽同构)/ effects Dictionary(hook 消费参数,provisional 投值)。 -# 正负双修(整合定稿 B2 判据):effects 允许负向键(cost_* 族)——纯增益件须专项论证(反例 B-3),加载期 -# 仅校验键域不禁纯增益(专项论证件以 positive_only 批注登记,人工清单呈报)。 +# 投放档映射:common/rare→稀有令牌档、rarest→罕见令牌档;relic 令牌永不入普通档)/ +# archetype ∈ ARCHETYPES 七流派标签 / desc 效果说明(文案表值化——呈现层读行,零硬编码)/ +# triggers 订阅行数组(订阅哪个事件 on / 什么条件 when / 什么效果 do——效果引擎 EffectEngine +# 统一解释执行;四词表封闭校验=EffectEngine.validate_trigger_row 单一事实源)。 # 档位激活(整合定稿 设计工具 1——以撒套装/小骨铭文):同标签计数 ≥2→档一(流派核心规则)/≥4→档二 # (完全体);散件单体保底=每件效果独立生效,档位只做质变(反例 B-2)——引擎纯函数 FeatsEngine。 # 🔴 层级纪律(任务书红线 2):专长只走局内随机渠道(三选一/商店/格面)——本表行永不入同调位 -# (treasure_items 表)/局外树(T8 节点表);扫雷触发点铁律:每流派 ≥1 行 hook ∈ MINE_HOOKS(加载校验, -# 违反=红线①触碰面)。 +# (treasure_items 表)/局外树(T8 节点表);扫雷触发点铁律:每流派 ≥1 行订阅扫雷侧事件 +# (EffectEngine.MINE_EVENTS——加载校验,违反=红线①触碰面)。 class_name FeatTable extends Resource @@ -32,58 +32,20 @@ const ARCHETYPES := ["magic", "pierce", "normal", "dig", "flag", "charge", "devo ## 稀有度词表(对齐令牌池三档;投放档映射 relic→rare / rarest→rarest) const RARITIES := ["common", "rare", "rarest"] -## 挂点枚举(结算段槽——首批 23 点位;每 hook 恰一流派域消费,T2-T6 接线) -const HOOKS := [ - # 魔法流 - "hero_attack_rider", # 普攻命中后追加必中魔法伤(奥术余烬/魔能共鸣) - "clamp_modifier", # 克制点直调(魔网织者/湛蓝誓约/猎手刺剑) - "first_dig_spell", # 每关首次开格必出魔法矢(魔能共鸣第二效果) - # 穿刺流 - "kill_overflow", # 击杀溢出弹射(贯穿射击) - "dig_count_segments", # 开格计数转攻击段数(连珠矢) - "roll_discount", # 折扣档改写(猎弱本能前移一档) - # 普攻流 - "round_dig_bonus", # 轮内安全开格数转攻击加成(矿工的执拗;踩雷清零=mine_hit 监听同口) - "hit_trade", # 命中交换(矿工重锤——正负双修原生形态) - "armor_roll_rewrite", # 免掷骰恒折扣(专注) - "heavy_debuff", # 命中重甲附加护甲裂解(碎甲震击) - # 开格流 - "dig_yield_mult", # 开格产出乘区(贪婪矿灯/矿脉直觉/赌徒的血签——正负双向) - "deep_dig", # 深层资源掘取(深挖钻头——R4 甲非信息税形态:开掘额外 AP 代价) - # 标旗流 - "flag_resource", # 标中雷获格面资源(雷区图谱) - "nest_aura", # 控制巢穴数转伤害光环(猎巢人) - "flag_all_hit", # 同轮全中旗伤倍增(完美布防) - "flag_strike", # 轮末旗伤开单(战旗军规——复用 board_typed) - # 蓄力流 - "ap_carry", # 轮末 AP 结转蓄力(整备军备——改写 cleared 作废口径) - "charge_shield", # 蓄力层转减伤(铁壁整备) - "charge_spike", # 满层 spike(厚积薄发) - # 吞噬流 - "terrain_refund", # 受地形伤掉落资源补偿(地脉采撷——伤害仍发生) - "terrain_dig_double", # 挖开伤害型地形格双产出(荆棘编织者) - "flag_neutralize", # 标中旗中和相邻伤害型地形格(净化工兵——🔴 R4 甲仅限伤害型) - "variant_counter", # 受地形伤积层轮末反打(熔火之躯——触发键不按地形种类特化) -] +## 行键白名单(🔴 零裸 flat 同族护栏——键出域=加载致命;triggers/desc=T3 换代新形态) +const ROW_KEYS := ["feat_id", "display_name", "rarity", "archetype", "triggers", "desc", "note"] -## 扫雷结算触发点 hook 集(🔴 红线①承载:每流派 ≥1 行 hook 命中本集——加载校验) -const MINE_HOOKS := ["first_dig_spell", "dig_count_segments", "round_dig_bonus", "dig_yield_mult", - "deep_dig", "flag_resource", "nest_aura", "flag_all_hit", "flag_strike", "ap_carry", - "terrain_refund", "terrain_dig_double", "flag_neutralize", "variant_counter"] - -## effects 键白名单(🔴 零裸 flat 同族护栏——机制轴禁无条件属性加项;键出域=加载致命) -## 全部条件触发/机制修正参数;cost_* 族=正负双修负向面 -const EFFECT_KEYS := ["value", "value2", "tier_value", "per_stack", "cap", "ap_cost", "chance", - "mult", "cost_mult", "cost_flat", "polarity", "spell_kind", "segment_cap", "discount_shift", - "trade_atk", "trade_hit", "shield_per_layer", "spike_mult", "layers_to_shield", - "neutralize_range", "counter_damage", "resource_kind", "rider_kind", "note"] +## trigger 行键(订阅行三字段:on/when?/do) +const TRIGGER_KEYS := ["on", "when", "do"] func validate() -> Array: var errs: Array = [] if rows.is_empty(): - return errs # 空表合法态(T1 基建先行——T2 起逐流派填行) + return errs # 空表合法态(T1 基建先行——逐流派填行) var seen := {} + # 流派扫雷覆盖聚合(🔴 红线①铁律——每流派 ≥1 行订阅扫雷侧事件) + var arch_mine := {} # archetype → bool for row in rows: var rid: String = row.get("feat_id", "") if rid == "": @@ -100,32 +62,27 @@ func validate() -> Array: var arch := String(row.get("archetype", "")) if not ARCHETYPES.has(arch): errs.append({"category": "enum", "message": "%s archetype 出域 %s" % [rid, str(ARCHETYPES)]}) - var hook := String(row.get("hook", "")) - if not HOOKS.has(hook): - errs.append({"category": "enum", "message": "%s hook 出域(挂点枚举封闭): %s" % [rid, hook]}) + if not row.has("triggers"): + errs.append({"category": "domain", "message": "%s 缺 triggers(订阅形态唯一合法行形)" % rid}) + elif EffectEngine.validate_trigger_row(rid, row, row.get("triggers", []) as Array, errs): + arch_mine[arch] = true for k in row.keys(): - if not ["feat_id", "display_name", "rarity", "archetype", "hook", "effects", "note"].has(String(k)): + if not ROW_KEYS.has(String(k)): errs.append({"category": "zero_flat", "message": "%s 行键出白名单: %s" % [rid, String(k)]}) - var fx: Dictionary = row.get("effects", {}) as Dictionary - for ek in fx.keys(): - if not EFFECT_KEYS.has(String(ek)): - errs.append({"category": "zero_flat", "message": "%s effects 键出白名单: %s(机制轴禁无条件属性加项)" % [rid, String(ek)]}) - # 🔴 红线① 扫雷触发点铁律:**有行的流派**各 ≥1 行 hook ∈ MINE_HOOKS(部分填充合法——T2-T6 - # 逐流派渐进;七流派全量覆盖完成度由批次一出口断言承载,加载闸不卡渐进填行) - var archs_with_rows := {} - for row in rows: - archs_with_rows[String(row.get("archetype", ""))] = true - for arch in archs_with_rows.keys(): - var covered := false - for row in rows: - if String(row.get("archetype", "")) == String(arch) and MINE_HOOKS.has(String(row.get("hook", ""))): - covered = true - break - if not covered: - errs.append({"category": "load_bearing", "message": "流派 %s 无扫雷结算触发点行(🔴 红线①铁律——每流派 ≥1 件挂扫雷端)" % String(arch)}) + # 🔴 红线① 扫雷触发点铁律:**有行的流派**各 ≥1 行订阅扫雷侧事件(MINE_EVENTS 同义校验) + for arch in archs_with_rows(): + if not bool(arch_mine.get(arch, false)): + errs.append({"category": "load_bearing", "message": "流派 %s 无扫雷结算触发点行(🔴 红线①铁律——每流派 ≥1 件订阅扫雷侧事件)" % String(arch)}) return errs +func archs_with_rows() -> Array: + var out := {} + for row in rows: + out[String(row.get("archetype", ""))] = true + return out.keys() + + func row_by_id(feat_id: String) -> Dictionary: for row in rows: if String(row.get("feat_id", "")) == feat_id: diff --git a/game/data/schemas/skill_table.gd b/game/data/schemas/skill_table.gd index 42663ee8..38eb22b8 100644 --- a/game/data/schemas/skill_table.gd +++ b/game/data/schemas/skill_table.gd @@ -2,7 +2,7 @@ # Validation Question: 玩家能否无指导在一场完整战斗中经历「开战→挖掘规划→轮末结算逐行兑现→胜/负」并逐行归因(P2) # Date: 2026-09-16 # 数据配置 SkillTable schema(敌人技能池批——定义层"技能行",敌人系统 回填值;行 = Dictionary,键 skill_id;Rule 4 豁免)。 -# 设计权威:docs/proposal-skill-pool.md v1.0 §2.2 字段词汇表。 +# 设计权威:proposal-skill-pool.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 技能池节〕 v1.0 §2.2 字段词汇表。 # 三层对象:技能行(本表,多敌种共享)· 池模板(EnemyTable.pool:{skill_id, weight})· 实例池(单位容器 spawn 快照)。 # 池构成纪律的加载校验在 ConfigDB 跨表通道(每池恰一行普攻、增益 ≤1、skill_id 悬空致命——本表无池视野)。 class_name SkillTable diff --git a/game/data/schemas/treasure_item_table.gd b/game/data/schemas/treasure_item_table.gd index 58d5f8fd..e4dff0dc 100644 --- a/game/data/schemas/treasure_item_table.gd +++ b/game/data/schemas/treasure_item_table.gd @@ -38,19 +38,23 @@ const DIMENSIONS := ["info", "risk", "action", "plan", "counter"] # 同调批扩 enum(迭代 11 第①批 T1)——新增九 kind 全部条件触发/机制修正类;mine_reveal/ap_grant 语义不变。 # cell_removal = 爆破工兵包中立移除格(T7 原位换装时启用——T6 影响评估前置);mine_hit_halve 系固定 # 「减半」语义不消费 magnitude(反弹 N 由 reflect_damage 行 magnitude 承载)。 +# 内容数据驱动扩展批 T2 挂账:主动件 kind 词族换代(reveal_tiles/preview/grant_ap/remove_mines/ +# refresh_pick——效果引擎 ACTIVE_KINDS 同名闭集),被动件 effect_kinds 词族随 T4 秘宝迁移退役 +# (passive 行改 triggers 事件订阅形态);旧词保留共存至迁移完成。 const EFFECT_KINDS := ["mine_reveal", "reward_preview", "ap_grant", "venom_rider", "flag_burst", "mine_hit_halve", "reflect_damage", "ap_rounds_front", "ap_charge_safe", "cell_removal", - "reward_weight_bias", "bonus_refresh"] + "reward_weight_bias", "bonus_refresh", + "reveal_tiles", "preview", "grant_ap", "remove_mines", "refresh_pick"] const TARGETS := ["none", "area_3x3", "board_wide"] const USE_PHASES := ["active_dig"] const FORMS := ["", "passive", "active_charged"] -# magnitude 消费面(≥1 校验):ap_grant 与同调 N 值 kind;其余 kind 活行 magnitude 恒 0 +# magnitude 消费面(≥1 校验):grant_ap 与同调 N 值 kind;其余 kind 活行 magnitude 恒 0 const MAGNITUDE_KINDS := ["ap_grant", "venom_rider", "flag_burst", "reflect_damage", "ap_rounds_front", - "ap_charge_safe", "reward_weight_bias", "bonus_refresh"] + "ap_charge_safe", "reward_weight_bias", "bonus_refresh", "grant_ap"] # 🔴 零裸 flat 结构护栏:行键白名单封闭(键出域=加载致命)——机制轴禁无条件属性加项的表结构承载 const KEY_WHITELIST := ["item_id", "display_name", "dimension_tags", "effect_kinds", "target", "magnitude", "use_phase", "retired", "deferred", "form", "charges_per_adventure", "slot_cost", - "unlock_requires", "effective_rounds", "charge_n"] + "unlock_requires", "effective_rounds", "charge_n", "triggers", "desc"] func validate() -> Array: @@ -90,6 +94,13 @@ func validate() -> Array: for k in row.keys(): if not KEY_WHITELIST.has(String(k)): errs.append({"category": "zero_flat", "message": "%s 行键出白名单: %s(KEY_WHITELIST 外键禁入)" % [iid, String(k)]}) + # triggers 事件订阅行(内容数据驱动扩展批 T2/T4——被动件换代形态;与 effect_kinds 互斥校验 + # 面=passive 行(换代单形态——效果行为只走订阅);active_charged 行两形合法并存 + # (effect_kinds=主动分发通道 + triggers=受理闸订阅,掘进蓄能先例)) + if row.has("triggers"): + if (row.get("effect_kinds", []) as Array).size() > 0 and String(row.get("form", "")) != "active_charged" and not bool(row.get("retired", false)): + errs.append({"category": "domain", "message": "%s triggers 与 effect_kinds 并存(换代单形态——被动件迁移期禁双挂)" % iid}) + EffectEngine.validate_trigger_row(iid, row, row.get("triggers", []) as Array, errs) var form: String = String(row.get("form", "")) if not FORMS.has(form): errs.append({"category": "enum", "message": "%s form 出域 %s: %s" % [iid, str(FORMS), form]}) @@ -97,8 +108,8 @@ func validate() -> Array: # 退役/失义行:免 effect_kinds 非空与承重护栏校验(登记面保留,不入投放与 F2) continue # ---- 活行校验 ---- - if kinds.is_empty(): - errs.append({"category": "domain", "message": "%s effect_kinds 空(活行;失义件应标 deferred)" % iid}) + if kinds.is_empty() and not row.has("triggers"): + errs.append({"category": "domain", "message": "%s effect_kinds 空(活行;失义件应标 deferred,被动件应挂 triggers 订阅)" % iid}) var kind_seen := {} for k in kinds: var kind: String = String(k) @@ -123,8 +134,8 @@ func validate() -> Array: mag_consuming = true if mag_consuming and mag < 1: errs.append({"category": "cross_field", "message": "%s N 值 kind 行 magnitude 应 ≥1,实得 %d" % [iid, mag]}) - if not mag_consuming and mag != 0: - errs.append({"category": "cross_field", "message": "%s 非 N 值 kind 活行 magnitude 应 0,实得 %d" % [iid, mag]}) + if not mag_consuming and mag != 0 and not row.has("triggers"): + errs.append({"category": "cross_field", "message": "%s 非 N 值 kind 活行 magnitude 应 0(trigger 行 magnitude=value_from 取数源豁免),实得 %d" % [iid, mag]}) # ---- 同调行约束(form 非空——装配面;过渡行 form="" 不校验)---- if form != "": if int(row.get("slot_cost", 1)) != 1: @@ -139,8 +150,8 @@ func validate() -> Array: errs.append({"category": "domain", "message": "%s effective_rounds 越域 [0,999]: %d" % [iid, eff_rounds]}) if kind_seen.has("ap_rounds_front") and eff_rounds < 1: errs.append({"category": "cross_field", "message": "%s ap_rounds_front 行 effective_rounds 应 ≥1(P4 甲表值化),实得 %d" % [iid, eff_rounds]}) - if not kind_seen.has("ap_rounds_front") and eff_rounds != 0: - errs.append({"category": "cross_field", "message": "%s 非 ap_rounds_front 行 effective_rounds 应 0,实得 %d" % [iid, eff_rounds]}) + if not kind_seen.has("ap_rounds_front") and eff_rounds != 0 and not row.has("triggers"): + errs.append({"category": "cross_field", "message": "%s 非 ap_rounds_front 行 effective_rounds 应 0(trigger 行=when value_from 取数源豁免),实得 %d" % [iid, eff_rounds]}) var charge_n: int = int(row.get("charge_n", 0)) if charge_n < 0 or charge_n > 999: errs.append({"category": "domain", "message": "%s charge_n 越域 [0,999]: %d" % [iid, charge_n]}) diff --git a/game/data/tables/feats.tres b/game/data/tables/feats.tres index 29d21854..cfe6a52f 100644 --- a/game/data/tables/feats.tres +++ b/game/data/tables/feats.tres @@ -9,266 +9,564 @@ rows = [{ "display_name": "奥术余烬", "rarity": "common", "archetype": "magic", -"hook": "hero_attack_rider", -"effects": { +"desc": "普攻附加必中伤害", +"triggers": [{ +"on": "attack_commit", +"do": { +"kind": "add_ticket_rider", "value": 1 } +}] }, { "feat_id": "mana_weaver", "display_name": "魔网织者", "rarity": "rare", "archetype": "magic", -"hook": "clamp_modifier", -"effects": { -"value": 1, -"polarity": "magic_vs_magic" +"desc": "克制面强化", +"triggers": [{ +"on": "clamp_check", +"when": [{ +"fact": "matchup", +"op": "eq", +"value": "magic_vs_magic" +}], +"do": { +"kind": "modify_ticket", +"delta": 1 } +}] }, { "feat_id": "azure_oath", "display_name": "湛蓝誓约", "rarity": "rare", "archetype": "magic", -"hook": "clamp_modifier", -"effects": { -"value": 1, -"polarity": "magic_vs_heavy" +"desc": "克制面强化", +"triggers": [{ +"on": "clamp_check", +"when": [{ +"fact": "matchup", +"op": "eq", +"value": "magic_vs_heavy" +}], +"do": { +"kind": "modify_ticket", +"delta": 1 } +}] }, { "feat_id": "mana_resonance", "display_name": "魔能共鸣", "rarity": "rarest", "archetype": "magic", -"hook": "first_dig_spell", -"effects": { +"desc": "首格追加奥术矢", +"triggers": [{ +"on": "dig_reveal", +"when": [{ +"fact": "is_first_dig_of_battle", +"op": "eq", +"value": true +}], +"do": { +"kind": "spawn_spell", "value": 2 } +}] }, { "feat_id": "mine_atlas", "display_name": "雷区图谱", "rarity": "common", "archetype": "flag", -"hook": "flag_resource", -"effects": { -"value": 1 +"desc": "标中雷额外得资源", +"triggers": [{ +"on": "flag_hit", +"do": { +"kind": "grant_material", +"value": 1, +"label": "(雷区图谱)" } +}] }, { "feat_id": "nest_hunter", "display_name": "猎巢人", "rarity": "rare", "archetype": "flag", -"hook": "nest_aura", -"effects": { -"value": 1 +"desc": "控巢攻击光环", +"triggers": [{ +"on": "attack_commit", +"do": { +"kind": "add_ticket_rider", +"value": 1, +"per": "nest_count" } +}] }, { "feat_id": "perfect_defense", "display_name": "完美布防", "rarity": "rare", "archetype": "flag", -"hook": "flag_all_hit", -"effects": { -"value": 2 +"desc": "全中标旗伤加倍", +"triggers": [{ +"on": "flag_settle", +"when": [{ +"fact": "flags_count", +"op": "gte", +"value": 1 +}], +"do": { +"kind": "modify_ticket", +"value": 2, +"mode": "flag_mult" } +}] }, { "feat_id": "war_banner", "display_name": "战旗军规", "rarity": "rarest", "archetype": "flag", -"hook": "flag_strike", -"effects": { -"value": 2 +"desc": "标中旗必中打敌", +"triggers": [{ +"on": "flag_settle", +"when": [{ +"fact": "flags_count", +"op": "gte", +"value": 1 +}], +"do": { +"kind": "open_ticket", +"value": 2, +"channel": "attack" } +}] }, { "feat_id": "pierce_shot", "display_name": "贯穿射击", "rarity": "rarest", "archetype": "pierce", -"hook": "kill_overflow", -"effects": { -"value": 1 +"desc": "击杀半额弹射", +"triggers": [{ +"on": "kill", +"do": { +"kind": "overflow", +"value": 50 } +}] }, { "feat_id": "chain_arrows", "display_name": "连珠矢", "rarity": "rare", "archetype": "pierce", -"hook": "dig_count_segments", -"effects": { -"value": 2, -"cap": 2 +"desc": "开格积穿刺矢", +"triggers": [{ +"on": "dig_reveal", +"do": { +"kind": "counter_add", +"counter_id": "chain_digs", +"value": 1, +"reset_at": "round_start" } }, { +"on": "attack_commit", +"do": { +"kind": "attack_segments", +"value": 2, +"cap": 2, +"per": "dig_streak", +"divisor": 3 +} +}] +}, { "feat_id": "weak_instinct", "display_name": "猎弱本能", "rarity": "rare", "archetype": "pierce", -"hook": "roll_discount", -"effects": { -"value": 1 +"desc": "打轻甲更易命中", +"triggers": [{ +"on": "clamp_check", +"when": [{ +"fact": "matchup", +"op": "ends_with", +"value": "vs_light" +}], +"do": { +"kind": "modify_ticket", +"value": 1, +"mode": "discount_shift" } +}] }, { "feat_id": "hunter_rapier", "display_name": "猎手刺剑", "rarity": "common", "archetype": "pierce", -"hook": "clamp_modifier", -"effects": { -"value": 1, -"polarity": "pierce_vs_light" +"desc": "克制面强化", +"triggers": [{ +"on": "clamp_check", +"when": [{ +"fact": "matchup", +"op": "eq", +"value": "pierce_vs_light" +}], +"do": { +"kind": "modify_ticket", +"delta": 1 } +}] }, { "feat_id": "miner_stubborn", "display_name": "矿工的执拗", "rarity": "common", "archetype": "normal", -"hook": "round_dig_bonus", -"effects": { +"desc": "开格越多打得越疼", +"triggers": [{ +"on": "dig_reveal", +"do": { +"kind": "counter_add", +"counter_id": "stubborn_digs", "value": 1, -"cap": 5 +"reset_at": "round_start" } }, { +"on": "mine_hit", +"do": { +"kind": "counter_add", +"counter_id": "stubborn_digs", +"set": 0 +} +}, { +"on": "attack_commit", +"do": { +"kind": "add_ticket_rider", +"value": 1, +"cap": 5, +"per": "dig_streak" +} +}] +}, { "feat_id": "miner_hammer", "display_name": "矿工重锤", "rarity": "common", "archetype": "normal", -"hook": "hit_trade", -"effects": { +"desc": "加伤换命中", +"triggers": [{ +"on": "attack_commit", +"do": { +"kind": "modify_ticket", "trade_atk": 25, "trade_hit": 2 } +}] }, { "feat_id": "focus_mind", "display_name": "专注", "rarity": "rare", "archetype": "normal", -"hook": "armor_roll_rewrite", -"effects": { -"value": 75 +"desc": "攻击恒75%折扣", +"triggers": [{ +"on": "armor_roll", +"do": { +"kind": "modify_ticket", +"value": 75, +"mode": "armor_rewrite" } +}] }, { "feat_id": "armor_crack", "display_name": "碎甲震击", "rarity": "rare", "archetype": "normal", -"hook": "heavy_debuff", -"effects": { -"value": 25 +"desc": "打重甲附加破防", +"triggers": [{ +"on": "attack_commit", +"when": [{ +"fact": "matchup", +"op": "ends_with", +"value": "vs_heavy" +}], +"do": { +"kind": "apply_status", +"status_id": "off_guard" } +}] }, { "feat_id": "greedy_lantern", "display_name": "贪婪矿灯", "rarity": "common", "archetype": "dig", -"hook": "dig_yield_mult", -"effects": { +"desc": "开格产出放大", +"triggers": [{ +"on": "dig_reveal", +"do": { +"kind": "yield_mult", "mult": 25, "cost_mult": 25 } +}] }, { "feat_id": "vein_intuition", "display_name": "矿脉直觉", "rarity": "rare", "archetype": "dig", -"hook": "dig_yield_mult", -"effects": { +"desc": "开格产出放大", +"triggers": [{ +"on": "dig_reveal", +"do": { +"kind": "counter_add", +"counter_id": "dig_combo", +"value": 1 +} +}, { +"on": "mine_hit", +"do": { +"kind": "counter_add", +"counter_id": "dig_combo", +"set": 0 +} +}, { +"on": "dig_reveal", +"do": { +"kind": "yield_mult", "mult": 10, "cap": 5 } +}] }, { "feat_id": "gambler_pact", "display_name": "赌徒的血签", "rarity": "rarest", "archetype": "dig", -"hook": "dig_yield_mult", -"effects": { +"desc": "开格产出放大", +"triggers": [{ +"on": "dig_reveal", +"do": { +"kind": "yield_mult", "mult": 50, "cost_mult": 100 } +}] }, { "feat_id": "deep_drill", "display_name": "深挖钻头", "rarity": "rare", "archetype": "dig", -"hook": "deep_dig", -"effects": { -"ap_cost": 1, -"cap": 4 +"desc": "开格2AP·产出双份", +"triggers": [{ +"on": "dig_paid", +"do": { +"kind": "counter_spend", +"counter_id": "deep_paid_round", +"value": 1, +"reset_at": "round_start", +"fallback": 1 } }, { +"on": "dig_reveal", +"when": [{ +"fact": "counter", +"id": "deep_paid_round", +"op": "gte", +"value": 1 +}, { +"fact": "counter", +"id": "deep_given_round", +"op": "lt_ref", +"ref": "deep_paid_round" +}], +"do": { +"kind": "grant_material", +"label": "(深挖)" +} +}, { +"on": "dig_reveal", +"when": [{ +"fact": "counter", +"id": "deep_paid_round", +"op": "gte", +"value": 1 +}, { +"fact": "counter", +"id": "deep_given_round", +"op": "lt_ref", +"ref": "deep_paid_round" +}], +"do": { +"kind": "counter_add", +"counter_id": "deep_given_round", +"value": 1 +} +}] +}, { "feat_id": "armory_prep", "display_name": "整备军备", "rarity": "rarest", "archetype": "charge", -"hook": "ap_carry", -"effects": { -"value": 1, -"cap": 3 +"desc": "剩AP转下轮攻击", +"triggers": [{ +"on": "round_end", +"do": { +"kind": "grant_ap", +"mode": "transfer" } }, { +"on": "attack_commit", +"do": { +"kind": "attack_bonus", +"value": 1, +"per": "charge_layers" +} +}] +}, { "feat_id": "iron_wall", "display_name": "铁壁整备", "rarity": "rare", "archetype": "charge", -"hook": "charge_shield", -"effects": { -"shield_per_layer": 1 +"desc": "蓄力转减伤", +"triggers": [{ +"on": "damage_taken", +"do": { +"kind": "shield", +"value": 1, +"per": "charge_layers" } +}] }, { "feat_id": "patient_burst", "display_name": "厚积薄发", "rarity": "rare", "archetype": "charge", -"hook": "charge_spike", -"effects": { -"value": 2 +"desc": "蓄满下轮爆发", +"triggers": [{ +"on": "attack_commit", +"when": [{ +"fact": "charge_layers", +"op": "gte", +"value": 3 +}], +"do": { +"kind": "modify_ticket", +"value": 2, +"mode": "spike" } +}] }, { "feat_id": "ley_harvest", "display_name": "地脉采撷", "rarity": "common", "archetype": "devour", -"hook": "terrain_refund", -"effects": { -"value": 1 +"desc": "吃地形伤返资源", +"triggers": [{ +"on": "damage_taken", +"when": [{ +"fact": "terrain_kind", +"op": "has" +}], +"do": { +"kind": "grant_material", +"label": "(地脉补偿)" } +}] }, { "feat_id": "thorn_weaver", "display_name": "荆棘编织者", "rarity": "rare", "archetype": "devour", -"hook": "terrain_dig_double", -"effects": { -"value": 1 +"desc": "挖伤害格双产出", +"triggers": [{ +"on": "damage_taken", +"when": [{ +"fact": "terrain_kind", +"op": "has" +}], +"do": { +"kind": "grant_material", +"label": "(荆棘编织)" } +}] }, { "feat_id": "purge_sapper", "display_name": "净化工兵", "rarity": "rare", "archetype": "devour", -"hook": "flag_neutralize", -"effects": { -"value": 1 +"desc": "标旗中和伤害格", +"triggers": [{ +"on": "flag_hit", +"when": [{ +"fact": "terrain_kind", +"op": "has" +}], +"do": { +"kind": "rewrite_board" } +}] }, { "feat_id": "magma_body", "display_name": "熔火之躯", "rarity": "rarest", "archetype": "devour", -"hook": "variant_counter", -"effects": { +"desc": "吃地形伤反打全体", +"triggers": [{ +"on": "damage_taken", +"when": [{ +"fact": "terrain_kind", +"op": "has" +}], +"do": { +"kind": "counter_add", +"counter_id": "magma_layers", "value": 1 } +}, { +"on": "round_end", +"when": [{ +"fact": "counter", +"id": "magma_layers", +"op": "gte", +"value": 1 +}], +"do": { +"kind": "open_ticket", +"value": 1, +"per": "magma_layers", +"channel": "blast" +} +}, { +"on": "round_end", +"when": [{ +"fact": "counter", +"id": "magma_layers", +"op": "gte", +"value": 1 +}], +"do": { +"kind": "counter_add", +"counter_id": "magma_layers", +"set": 0 +} +}] }] -; 双层 BD 实施批(2026-10-04):规则改写型特性表——行 = 特性定义 -; (feat_id/display_name/rarity/archetype/hook/effects)。 -; T2 魔法流四件(整合定稿 §2.3 S1):奥术余烬〔普通·rider+1 必中附加〕/魔网织者〔稀有·magic vs -; magic 克制罚修正〕/湛蓝誓约〔稀有·magic vs heavy 克制加成〕/魔能共鸣〔罕见锚点·rider+2+每战首次 -; 开格奥术矢——「每关首次」字面落地为「每战首次」台账披露待追认〕;magic 档一(≥2 件)rider+1 / -; 档二(≥4 件)rider 可暴击——引擎 tiers_for 承载。T3-T6 逐流派续填(flag 4/pierce 4/normal 4/ -; dig 4/charge 3/devour 4——整合定稿 §2.3 七流派的件面)。 -; 稀有度→投放档映射:common/rare → 稀有令牌档、rarest → 罕见令牌档(relic 永不入普通令牌档 -; ——「规则改写型特性带挂稀有/罕见档」追认 b 接线口径,普通档 70 张纯数值维持)。 -; 🔴 层级纪律:本表行永不入同调位(treasure_items)/局外树(T8 节点表)——规则改写型特性只走局内随机渠道。 -; 数值全 provisional 归 M-4 数值批;effects 键白名单封闭(零裸 flat 同族护栏)。 - +; 内容数据驱动扩展批(2026-10-06 T3):专长表换代——行 = triggers 事件订阅形态 +; (feat_id/display_name/rarity/archetype/desc/triggers——订阅哪个事件 on / 什么条件 when / 什么效果 do)。 +; 旧 hook/effects 挂点形态随本批退役(挂点枚举消亡:事件名+效果类型取代——任务书 R4 裁决)。 +; 23 挂点逐行移植映射=任务书 §五(等价性对拍底单);desc=文案表值化(呈现层读行,零硬编码)。 +; 效果参数值直接写 do 字典(数值单一事实源=本表;效果引擎 DO_KEYS 白名单封闭)。 +; 🔴 护栏全部保留:事件/效果/fact/算子四词表封闭(EffectEngine 常量)、行键白名单、悬空引用 +; 致命、零裸 flat 禁令、流派扫雷触发铁律(每流派 ≥1 行订阅扫雷侧事件——MINE_EVENTS 同义校验)。 +; 映射表对拍注记(T3 落地语义修正,行为等价优先——commit 留痕): +; - terrain_dig_double(荆棘编织者):映射表标 yield_mult(×2),现行为=第二份同格产出兑付 +; (grant_material),×2 乘区对 percent 行不等价——落 grant_material。 +; - flag_all_hit(完美布防):映射表标 yield_mult(旗伤乘区),现行为=旗伤单值倍增(战旗单据 +; 数值改写)——落 modify_ticket(mode:flag_mult)。 +; - 连珠矢/矿工的执拗 streak 取数:映射表 on:dig_reveal counter_add 形态与现行为「轮内踩雷全轮 +; 清零」不等价(逐格清零后重积≠全轮判定)——计数器行保留(扫雷铁律承载),等价执行读 +; attack_commit 载荷 dig_streak(战斗段全轮推导,与迁移前逐位一致)。 +; - 厚积薄发:映射表 on:charge_full(满层沿)→spike,现行为=满层持续状态(层数无消耗点,沿触发 +; 一次≠持续增强)——落 on:attack_commit + when(charge_layers ≥3) → modify_ticket(mode:spike), +; charge_full 事件仍埋点(R6 框架先行)。 +; - 完美布防:「全中」条件装不进 fact 封闭 9 项(需 hits>0 且 misses==0 双读数)——when 落 +; flags_count ≥1(可命中保守判定),全中闸=flag_settle 发射点施加面承载(载荷 flags_hit_all +; 仅供观测)——禁私开 fact(停机点纪律),查询面落法=代拍板白名单。 +; - 猎巢人/矿工的执拗:映射表标 modify_ticket/attack_bonus,现行为=必中附加通道(伤害控制器 +; hero_rider 后置于乘区与下限)——落 add_ticket_rider(per 取数载荷),乘区外恒定兑付逐位一致。 +; - 整备军备:现行为双效果(轮末 AP 转蓄力层 + 每层下轮首段攻 +1)——两行订阅: +; round_end grant_ap(mode:transfer) + attack_commit attack_bonus(per:charge_layers)。 +; 数值全 provisional 归 M-4 数值批;desc 文案=旧 summaries 表迁移(措辞零变化)。 diff --git a/game/data/tables/treasure_items.tres b/game/data/tables/treasure_items.tres index 3da8a3f6..4aaf2ec3 100644 --- a/game/data/tables/treasure_items.tres +++ b/game/data/tables/treasure_items.tres @@ -10,6 +10,7 @@ rows = [{ "item_id": "probe_dust", "display_name": "显影尘", "dimension_tags": ["info", "counter"], +"desc": "效果失义待重定", "effect_kinds": [], "target": "none", "magnitude": 0, @@ -19,6 +20,7 @@ rows = [{ "item_id": "sapper_order", "display_name": "工兵令", "dimension_tags": ["action"], +"desc": "AP +2", "effect_kinds": ["ap_grant"], "target": "none", "magnitude": 2, @@ -28,6 +30,7 @@ rows = [{ "item_id": "loot_compass", "display_name": "探宝罗盘", "dimension_tags": ["plan"], +"desc": "预读 3×3", "effect_kinds": ["reward_preview"], "target": "area_3x3", "magnitude": 0, @@ -37,6 +40,7 @@ rows = [{ "item_id": "nest_pendulum", "display_name": "探巢摆钟", "dimension_tags": ["risk"], +"desc": "真视 3×3", "effect_kinds": ["mine_reveal"], "target": "area_3x3", "magnitude": 0, @@ -48,6 +52,7 @@ rows = [{ "item_id": "catacomb_scroll", "display_name": "地穴全图卷", "dimension_tags": ["info", "plan"], +"desc": "效果失义待重定", "effect_kinds": [], "target": "board_wide", "magnitude": 0, @@ -57,6 +62,7 @@ rows = [{ "item_id": "blast_kit", "display_name": "爆破工兵包", "dimension_tags": ["action", "risk"], +"desc": "AP +4 真视 3×3 移除雷格", "effect_kinds": ["ap_grant", "mine_reveal", "cell_removal"], "target": "area_3x3", "magnitude": 4, @@ -68,47 +74,108 @@ rows = [{ "item_id": "venom_edge", "display_name": "淬毒刃纹", "dimension_tags": ["counter"], -"effect_kinds": ["venom_rider"], +"desc": "克制正面直伤+2", "target": "none", "magnitude": 2, "use_phase": "active_dig", "form": "passive", -"slot_cost": 1 +"slot_cost": 1, +"triggers": [{ +"on": "clamp_check", +"when": [{ +"fact": "matchup", +"op": "eq", +"value": "pierce_vs_ward" +}], +"do": { +"kind": "modify_ticket", +"delta_from": "magnitude", +"mode": "clamp_bonus" +} +}, { +"on": "clamp_check", +"when": [{ +"fact": "matchup", +"op": "eq", +"value": "magic_vs_heavy" +}], +"do": { +"kind": "modify_ticket", +"delta_from": "magnitude", +"mode": "clamp_bonus" +} +}] }, { "item_id": "flag_burst", "display_name": "标中殉爆", "dimension_tags": ["action"], -"effect_kinds": ["flag_burst"], +"desc": "每标中一旗全体敌受2", "target": "none", "magnitude": 2, "use_phase": "active_dig", "form": "passive", -"slot_cost": 1 +"slot_cost": 1, +"triggers": [{ +"on": "flag_hit", +"do": { +"kind": "open_ticket", +"value_from": "magnitude", +"channel": "blast" +} +}] }, { "item_id": "nest_counter", "display_name": "惊巢反制", "dimension_tags": ["risk"], -"effect_kinds": ["mine_hit_halve", "reflect_damage"], +"desc": "踩雷惩罚减半+反弹2", "target": "none", "magnitude": 2, "use_phase": "active_dig", "form": "passive", -"slot_cost": 1 +"slot_cost": 1, +"triggers": [{ +"on": "mine_hit", +"do": { +"kind": "damage_mult", +"value": 50 +} +}, { +"on": "mine_hit", +"do": { +"kind": "open_ticket", +"value_from": "magnitude", +"channel": "reflect" +} +}] }, { "item_id": "dawn_order", "display_name": "工兵晨令", "dimension_tags": ["action"], -"effect_kinds": ["ap_rounds_front"], +"desc": "前3轮每轮AP+1", "target": "none", "magnitude": 1, "use_phase": "active_dig", "form": "passive", "slot_cost": 1, -"effective_rounds": 3 +"effective_rounds": 3, +"triggers": [{ +"on": "round_start", +"when": [{ +"fact": "round_index", +"op": "lte", +"value_from": "effective_rounds" +}], +"do": { +"kind": "grant_ap", +"mode": "queue", +"value_from": "magnitude" +} +}] }, { "item_id": "dig_charge", "display_name": "掘进蓄能", "dimension_tags": ["action"], +"desc": "积点换AP+1", "effect_kinds": ["ap_charge_safe"], "target": "none", "magnitude": 1, @@ -116,21 +183,37 @@ rows = [{ "form": "active_charged", "slot_cost": 1, "charges_per_adventure": 2, -"charge_n": 3 +"charge_n": 3, +"triggers": [{ +"on": "treasure_used", +"do": { +"kind": "counter_spend", +"counter_id": "dig_charge_bank", +"value": 1 +} +}] }, { "item_id": "luck_coin", "display_name": "偏运硬币", "dimension_tags": ["risk"], -"effect_kinds": ["reward_weight_bias"], +"desc": "稀有资源权重+2", "target": "none", "magnitude": 2, "use_phase": "active_dig", "form": "passive", -"slot_cost": 1 +"slot_cost": 1, +"triggers": [{ +"on": "reward_roll", +"do": { +"kind": "bias_rewards", +"value_from": "magnitude" +} +}] }, { "item_id": "pick_refresh", "display_name": "三选一刷新件", "dimension_tags": ["info"], +"desc": "三选一刷新+1", "effect_kinds": ["bonus_refresh"], "target": "none", "magnitude": 1, diff --git a/game/presentation/battle_narrator.gd b/game/presentation/battle_narrator.gd index f3b0b273..32e612b6 100644 --- a/game/presentation/battle_narrator.gd +++ b/game/presentation/battle_narrator.gd @@ -131,40 +131,16 @@ static func bonus_result_text(result: Dictionary) -> String: return "已选择:%s\n其余选项已丢弃(不可反悔)" % name -# 特性卡片文案(迭代 12 T1;2026-10-04 呈现补强——第三行=hook 效果摘要,用户反馈「不知道件是 -# 干嘛的」缺口承载;摘要=静态映射表 provisional 文案归代拍板白名单「件文案微调」) +# 特性卡片文案(迭代 12 T1;2026-10-04 呈现补强——第三行=效果摘要,用户反馈「不知道件是 +# 干嘛的」缺口承载。内容数据驱动扩展批 T3 文案表值化:摘要=数据行 desc 字段,零硬编码映射—— +# 新件写行即有名有姓,措辞与旧 summaries 表逐字一致) static func feat_card_text(row: Dictionary) -> String: if row.is_empty() or String(row.get("feat_id", "")) == "": return "特性\n???" var arch_names := {"magic": "魔法", "pierce": "穿刺", "normal": "普攻", "dig": "开格", "flag": "标旗", "charge": "蓄力", "devour": "吞噬"} var arch := String(arch_names.get(String(row.get("archetype", "")), "流派")) - var summaries := { - "hero_attack_rider": "普攻附加必中伤害", - "clamp_modifier": "克制面强化", - "first_dig_spell": "首格追加奥术矢", - "kill_overflow": "击杀半额弹射", - "dig_count_segments": "开格积穿刺矢", - "roll_discount": "打轻甲更易命中", - "round_dig_bonus": "开格越多打得越疼", - "hit_trade": "加伤换命中", - "armor_roll_rewrite": "攻击恒75%折扣", - "heavy_debuff": "打重甲附加破防", - "dig_yield_mult": "开格产出放大", - "deep_dig": "开格2AP·产出双份", - "flag_resource": "标中雷额外得资源", - "nest_aura": "控巢攻击光环", - "flag_all_hit": "全中标旗伤加倍", - "flag_strike": "标中旗必中打敌", - "ap_carry": "剩AP转下轮攻击", - "charge_shield": "蓄力转减伤", - "charge_spike": "蓄满下轮爆发", - "terrain_refund": "吃地形伤返资源", - "terrain_dig_double": "挖伤害格双产出", - "flag_neutralize": "标旗中和伤害格", - "variant_counter": "吃地形伤反打全体", - } - var summary := String(summaries.get(String(row.get("hook", "")), "")) + var summary := String(row.get("desc", "")) if summary != "": return "%s · %s\n%s" % [String(row.get("display_name", row.get("feat_id", ""))), arch, summary] return "特性 · %s\n%s" % [arch, String(row.get("display_name", row.get("feat_id", "")))] diff --git a/game/presentation/camp_shop_presenter.gd b/game/presentation/camp_shop_presenter.gd index cf799bec..e4f3ced4 100644 --- a/game/presentation/camp_shop_presenter.gd +++ b/game/presentation/camp_shop_presenter.gd @@ -153,6 +153,7 @@ func on_shop_buy(product_id: String) -> void: toast.show_toast("金币不足——买不起这件商品", "warn") return shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp)) + shop_overlay.set_visit_info(int(runner.shop_bp_visit_buys()), float(db.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 @@ -174,6 +175,7 @@ func on_shop_refresh() -> void: toast.show_toast("金币不足——刷新失败", "warn") return shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp)) + shop_overlay.set_visit_info(int(runner.shop_bp_visit_buys()), float(db.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() diff --git a/game/presentation/curio_interact_controller.gd b/game/presentation/curio_interact_controller.gd index fffbe0db..4cf3b92a 100644 --- a/game/presentation/curio_interact_controller.gd +++ b/game/presentation/curio_interact_controller.gd @@ -112,8 +112,9 @@ func on_presented(payload: Dictionary) -> void: toast.show_toast("上关剩余 %d 件秘宝已随关卡结束清空" % int(payload.get("aux_cleared", 0)), "info") # 迭代 7 T4 清空反馈 _pending_curio = payload.duplicate(true) _curio_context = "level_clear" - if bool(payload.get("replace_pending", false)): + if bool(payload.get("replace_pending", false)) or bool(payload.get("refreshed", false)): # 续玩:替换子交互在途中断——重建三选一面后直落替换子态(候选不重抽) + # 刷新重发(2026-10-06):refreshed 标直落选择面——不再重开胜利总结面 show_pick() return end_overlay.open_victory(Narrator.battle_end_text("victory", false), "继续", @@ -142,7 +143,7 @@ func present_node_curio() -> void: _open_modal.call(curio_overlay) curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(node_cands, node_draw.get("aux_detail", []) as Array)) adventure.present_node_curio(node_cands, node_draw.get("aux_detail", []) as Array) - curio_overlay.set_refresh_info(adventure.attune_refresh_left()) # 迭代 11 第①批 T5b——刷新余量投喂 + _feed_refresh_display(adventure) # 迭代 11 第①批 T5b + 2026-10-06 对齐商店——刷新面投喂单点 # 三选一面开出(战斗总结结算确认 / 续玩停点恢复;替换在途由 treasure_replace_presented 续发直落子态) @@ -151,10 +152,19 @@ func show_pick() -> void: _open_modal.call(curio_overlay) curio_overlay.open_pick(Narrator.curio_pick_header(payload), _curio_card_texts(payload.get("candidates", []) as Array, payload.get("aux_detail", []) as Array)) var adventure = _adventure_get.call() - curio_overlay.set_refresh_info(adventure.attune_refresh_left() if adventure != null else 0) # 迭代 11 第①批 T5b——刷新余量投喂 + if adventure != null: + _feed_refresh_display(adventure) # 迭代 11 第①批 T5b + 2026-10-06 对齐商店——刷新面投喂单点 _refresh_presentations.call() +# 刷新呈现投喂单点(免费余量 X/Y + GP 付费价 + 冒险钱包可付——读数与商店 set_refresh_display 同构) +func _feed_refresh_display(adventure) -> void: + var runner = _runner_get.call() + var gp := int(runner.gp) if runner != null else 0 + var cost: int = adventure.curio_refresh_gp_cost() + curio_overlay.set_refresh_display(adventure.attune_refresh_left(), cost, gp >= cost) + + func _curio_card_texts(candidates: Array, treasure_detail: Array = []) -> Array: # TP0 令牌池——候选 = {"kind","tier"} 令牌字典;卡片 id = "kind|tier|feat_id" 复合键(同 kind # 大小两档/同档多 relic 可同屏,kind 单键不构成唯一身份),确认回程拆键(on_pick_confirmed) @@ -181,41 +191,44 @@ func _curio_card_texts(candidates: Array, treasure_detail: Array = []) -> Array: return out -# 道具件卡片行(掷定前置——具体道具名 + 效果摘要;空 iid = 无预掷回退态显示通用文案) +# 道具件卡片行(掷定前置——具体道具名 + 效果摘要;空 iid = 无预掷回退态显示通用文案)。 +# 内容数据驱动扩展批 T4 文案表值化:效果摘要=数据行 desc 字段直读(零 kind 硬编码映射—— +# 新件写行即有名有姓) func _treasure_card_line(iid: String) -> String: if iid == "": return "" var row: Dictionary = db.treasure_item_row(iid) if row.is_empty(): return "" - var parts: Array[String] = [] - for k in (row.get("effect_kinds", []) as Array): - if String(k) == "mine_reveal": - parts.append("真视") - elif String(k) == "reward_preview": - parts.append("预读") - elif String(k) == "ap_grant": - parts.append("AP +%d" % int(row.get("magnitude", 0))) - var tgt := String(row.get("target", "none")) - if tgt == "area_3x3": - parts.append("3×3") - elif tgt == "board_wide": - parts.append("全图") - return "%s · %s" % [String(row.get("display_name", iid)), " ".join(parts)] + var desc := String(row.get("desc", "")) + if desc != "": + return "%s · %s" % [String(row.get("display_name", iid)), desc] + return String(row.get("display_name", iid)) -# 三选一刷新(迭代 11 第①批 T5b——P1 甲 X/Y 双源;通关停点经重发 curio_choice_presented 自然重建, -# 节点停点在此读在途态重 present——零自动推进契约不破,停点仍在途) +# 三选一刷新(迭代 11 第①批 T5b X/Y 双源 + 2026-10-06 用户令对齐商店 MC-1——免费位用尽转 GP 付费档: +# 同 shop_refresh_cost_base/curve 旋钮、同冒险钱包 runner.gp;通关停点经重发 curio_choice_presented 直落 +# 选择面,节点停点在此读在途态重 present——零自动推进契约不破,停点仍在途) func on_refresh() -> void: var adventure = _adventure_get.call() - if adventure == null: + if adventure == null or not adventure.curio_stop_pending(): return - if not adventure.refresh_curio(): + var runner = _runner_get.call() + var refreshed: bool = adventure.refresh_curio() + if not refreshed: + # 免费位(X/Y)已穷——GP 付费档(先扣钱包后重抽,余额不足拒绝零副作用) + var cost: int = adventure.curio_refresh_gp_cost() + if runner == null or not runner.spend_gp(cost): + toast.show_toast("金币不足——刷新失败", "warn") + return + refreshed = adventure.refresh_curio_paid() + _refresh_presentations.call() # 钱包变化上屏(对齐商店购买后刷新面) + if not refreshed: return if _curio_context == "reward_node": var st: Dictionary = adventure.node_curio_pending_state() curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(st.get("candidates", []) as Array, st.get("aux_detail", []) as Array)) - curio_overlay.set_refresh_info(adventure.attune_refresh_left()) + _feed_refresh_display(adventure) func on_pick_confirmed(item_id: String) -> void: diff --git a/game/presentation/main.gd b/game/presentation/main.gd index b48e27a2..6bc64d3c 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -117,6 +117,7 @@ var shop_overlay: ShopOverlay # 商店覆盖层(GP+商店房实施 var camp_overlay: CampOverlay # 营地覆盖层(元进度实现批——主菜单营地入口兑付呈现) var settings_store # 系统级设置独立存储(迭代 8 卡④——音量/全屏/减动效) var settings_overlay: SettingsOverlay # 设置覆盖层(迭代 8 卡④——最小设置面) +var pause_overlay: PauseOverlay # 局内暂停层·只读(迭代 17 卡③——钥石余额+营地三柜台进度查看;登记设置层同档) var debug_overlay: DebugOverlay # 调试浮窗(迭代 14.5 调试整合批——种子/数值调整/批量模拟/两层调试入口收一) var _modals = null # 模态层级状态机(modal_stack.gd——缺陷⑤纪律载体) @@ -223,6 +224,7 @@ func _build_ui() -> void: func() -> Variant: return settlement, func() -> Variant: return runner) board_zone.action_requested.connect(_on_board_action) + board_zone.pause_badge.pressed.connect(_on_pause_badge_pressed) # 暂停层入口(迭代 17 卡③——战斗屏 HUD 角标) info_panel.treasure_slot_clicked.connect(_treasure_ui.on_slot_clicked) # 迭代 7 T4——两步交互第一步 board_zone.stop_pressed.connect(_on_stop_pressed) board_zone.refresh_confirmed.connect(_on_refresh_confirmed) @@ -240,7 +242,7 @@ func _build_ui() -> void: logger.ap_per_round = int(db.action_economy.ap_per_round) # 模态覆盖层(登记序 = 树序 = z 遮挡序:图例 < 明细回看 < 节点 < 关卡画面 < 三选一 < 终局 - # < 商店 < 营地 < 设置 < 确认弹窗 < 调试浮窗) + # < 商店 < 营地 < 设置 < 暂停(只读) < 确认弹窗 < 调试浮窗) var overlays: Dictionary = UiAssembler.build_overlays(self, db) legend_overlay = overlays.legend_overlay node_overlay = overlays.node_overlay @@ -255,11 +257,17 @@ func _build_ui() -> void: legend_overlay.closed.connect(_on_modal_closed.bind(legend_overlay)) node_overlay.continue_pressed.connect(_on_node_continue) level_screen.route_chosen.connect(_on_route_chosen) + level_screen.pause_badge.pressed.connect(_on_pause_badge_pressed) # 暂停层入口(迭代 17 卡③——地图屏顶栏角标) level_screen.abandon_requested.connect(_on_abandon_requested) # 放弃入口(2026-09-21 用户拍板——台账 119) _end_overlay.continue_pressed.connect(_on_battle_end_continue) settings_overlay = SettingsOverlay.new(settings_store) # 迭代 8 卡④——最小设置面(音量/全屏/减动效) add_child(settings_overlay) settings_overlay.closed.connect(_on_settings_closed) + # 局内暂停层·只读(迭代 17 卡③——P2 甲:登记设置层同档带 dim 遮罩,确认弹窗之前; + # 数据=组合根开层实时读 MetaProgression 访问器喂入(R4),本层零写通路) + pause_overlay = PauseOverlay.new() + add_child(pause_overlay) + pause_overlay.closed.connect(_on_pause_closed) # 提升三选一覆盖层(迭代 10——z 序 = 节点层之上、终局层之下;确认后同拍推进) curio_overlay = CurioPickOverlay.new() add_child(curio_overlay) @@ -327,9 +335,10 @@ func _build_ui() -> void: debug_overlay.simulate_requested.connect(_on_tune_simulate) debug_overlay.debug_levels_requested.connect(_start_debug_levels) - # 模态登记(缺陷⑤纪律——登记序 = 树序:含 move_child 调位后的关卡画面/三选一终位) + # 模态登记(缺陷⑤纪律——登记序 = 树序:含 move_child 调位后的关卡画面/三选一终位; + # 暂停层设置层同档、确认弹窗之前——P2 甲) _modals = ModalStack.new() - for m: Control in [legend_overlay, node_overlay, level_screen, curio_overlay, _end_overlay, shop_overlay, camp_overlay, settings_overlay, confirm_dialog, debug_overlay]: + for m: Control in [legend_overlay, node_overlay, level_screen, curio_overlay, _end_overlay, shop_overlay, camp_overlay, settings_overlay, pause_overlay, confirm_dialog, debug_overlay]: _register_modal(m) _tune_sim = TuneSimController.new(db, debug_overlay, int(db.hero.max_hp)) @@ -360,10 +369,12 @@ func _open_modal(m: Control) -> void: # 打开时显式隐藏树序在其上方的可见层(遮挡物隐藏),关闭恢复——单一模态栈 _flush_row_queue() # 卡⑦——任何覆盖层开启前排空排播队列(明细带恒完整,回看/停点面不缺行) _modals.open(m) + _sync_pause_entry() # 迭代 17 卡③——暂停层入口角标可见性/置灰随开态同步 func _close_modal(m: Control) -> void: _modals.close(m) + _sync_pause_entry() # 迭代 17 卡③——关闭恢复角标可点态(无残留——R3 同源口径) func _on_modal_closed(m: Control) -> void: @@ -429,6 +440,7 @@ func _resume_from_menu() -> void: _start_overlay.visible = false _wire_adventure() _level_active = true + _sync_pause_entry() # 迭代 17 卡③——续玩恢复点亮暂停入口角标 level_screen.set_abandon_available(true) _reset_run_scope() log_view.clear() @@ -463,6 +475,7 @@ func _wire_adventure() -> void: func _start_adventure() -> void: _wire_adventure() _level_active = true + _sync_pause_entry() # 迭代 17 卡③——进对局点亮暂停入口角标 level_screen.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期——2026-09-21 用户拍板) _reset_run_scope() log_view.clear() @@ -521,9 +534,11 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void: _open_modal(shop_overlay) if runner.is_shop_pool(): shop_overlay.open_shelf(runner.shop_shelf(), int(runner.gp)) + shop_overlay.set_visit_info(int(runner.shop_bp_visit_buys()), float(db.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()) # MC-1 else: shop_overlay.open_shop(runner.shop_products(), int(runner.gp)) + shop_overlay.set_visit_info(-1, 1.0) # legacy 面无爬价行(off 对照承载) log_view.append_bb(Narrator.shop_line(int(runner.gp))) logger.log_node(node, info, runner.node_index) _refresh_presentations() @@ -537,6 +552,7 @@ func _on_adventure_finished(summary: Dictionary) -> void: var result: String = String(summary.get("result", "")) settlement = null _exit_level_screen() # 终局兜底:独立屏在开态(放弃入口)也先回战斗屏呈现摘要——屏级互斥单一出口 + _sync_pause_entry() # 迭代 17 卡③——离局熄灭暂停入口角标(防菜单误现) _sync_settlement_visuals() # → 全关 HudPresenter.battle_end_feedback(stage, ap_bar, false) # 灰化/idle 回正/状态全清——HUD 单通道(T3 收口批) # 存档入账(元进度实现批):settle = Boss 两档入账 + 工坊解锁 + 摘要入档 + 写盘(唯一写档点不变—— @@ -887,6 +903,7 @@ func _enter_level_screen() -> void: _level_screen_active = true _battle_root.visible = false _open_modal(level_screen) + _sync_pause_entry() # 迭代 17 卡③——屏级互斥切换同步(_open_modal 单口不识屏级状态) func _exit_level_screen() -> void: @@ -894,6 +911,7 @@ func _exit_level_screen() -> void: _battle_root.visible = true if level_screen.visible: _close_modal(level_screen) + _sync_pause_entry() # 迭代 17 卡③——同上 func _on_abandon_requested() -> void: @@ -922,6 +940,61 @@ func _on_settings_closed() -> void: _refresh_resume_button() +# ---- 局内暂停层·只读(迭代 17 卡③——入口角标在两屏装配段〔组 B〕;开合与数据喂入归组合根)---- + +# 暂停层快照投影(R4 数据只读同源:每次开层实时读 MetaProgression 访问器现值拼装—— +# 快照仅服务单次开层生命周期(open_pause 重建各行),跨开层零持有;零写调用) +func _pause_data() -> Dictionary: + var forge_rows: Array[Dictionary] = [] + for attr in MetaTable.FORGE_ATTRS: + var row: Dictionary = db.meta_forge_row(attr) + forge_rows.append({ + "attr": attr, + "display_name": String(row.get("display_name", "")), + "level": meta.forge_level(attr), + "flat": int(row.get("flat_value", 0)), + }) + return { + "balance": meta.keystone_balance(), + "forge": forge_rows, + "workshop": meta.workshop_status(), + "tree": meta.tree_camp_rows(), + "fork_groups": meta.tree_fork_groups(), + } + + +func _open_pause_layer() -> void: + _open_modal(pause_overlay) # 模态栈单口(开层前排空排播队列——既有层同款行为,目检确认项) + pause_overlay.open_pause(_pause_data()) + + +func _on_pause_closed() -> void: + _close_modal(pause_overlay) # 模态栈单口 + + +# 入口角标点击(两屏同款处理器):P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰, +# 探针 pressed.emit 绕过 disabled 时由此兜住;地图屏自身的登记开态不计——has_open_excluding) +func _on_pause_badge_pressed() -> void: + var map_on: bool = level_screen != null and level_screen.visible + if _modals == null or (map_on and _modals.has_open_excluding(level_screen)) \ + or (not map_on and _modals.has_open()): + return + _open_pause_layer() + + +# 入口角标可见性/置灰同步(P1 丁+守卫——单一同步点):战斗屏角标=对局内且地图屏未激活; +# 地图屏角标=随屏;置灰=登记册上除地图屏自身外任一层开着(防「看得见点不着」态)。 +# 调用面=模态开合单口与屏级互斥口(凡影响开态/屏态的路径必经其一)。 +func _sync_pause_entry() -> void: + if pause_overlay == null or _modals == null or board_zone == null or level_screen == null: + return + var map_on: bool = level_screen.visible + board_zone.pause_badge.visible = _level_active and not map_on + level_screen.pause_badge.visible = map_on + board_zone.pause_badge.disabled = _modals.has_open() + level_screen.pause_badge.disabled = _modals.has_open_excluding(level_screen) + + # ---- 探针缝保缝(T4/D4 拆件——click/ui_probe 营地段直呼组合根面;实现归 camp_shop_presenter.gd)---- func _on_camp_closed() -> void: diff --git a/game/presentation/modal_stack.gd b/game/presentation/modal_stack.gd index 642cf409..80f1ac55 100644 --- a/game/presentation/modal_stack.gd +++ b/game/presentation/modal_stack.gd @@ -41,3 +41,15 @@ func has_open() -> bool: if is_instance_valid(m) and m.visible: return true return false + + +# 排除式开态查询(迭代 17 卡③ 2026-10-06——暂停层入口守卫判据):skip 层自身的开态不计入 +#(关卡地图屏系登记册模态成员=常态屏态,其上的地图屏角标必须可点;其余任一层开着=角标置灰)。 +# 只读查询零副作用——登记序/开合记账语义零改动。 +func has_open_excluding(skip: Control) -> bool: + for m in _list: + if m == skip: + continue + if is_instance_valid(m) and m.visible: + return true + return false diff --git a/game/presentation/ui_assembler.gd b/game/presentation/ui_assembler.gd index 602f2d21..8c0dbfac 100644 --- a/game/presentation/ui_assembler.gd +++ b/game/presentation/ui_assembler.gd @@ -102,16 +102,13 @@ static func build_overlays(host: Control, db) -> Dictionary: static func build_start_overlay(host: Control, db = null) -> Dictionary: var overlay := overlay_bg(host, UiKit.BG_BRIGHT) overlay.theme = UiKit.tooltip_theme() - # 石纹底噪底层(样张 v2 冻结批 T2——z 序=dim 之上/内容之下;缺位不挂即回退平色) - var bd_tex := UiKit.load_tex(UiKit.TEX_BACKDROP_STONE) - if bd_tex != null: - var bd := TextureRect.new() - bd.texture = bd_tex - bd.stretch_mode = TextureRect.STRETCH_TILE - bd.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - bd.set_anchors_preset(Control.PRESET_FULL_RECT) - bd.mouse_filter = Control.MOUSE_FILTER_IGNORE + # 石纹弱质感+斜光带+暗角(UI 对齐批 T5 2026-10-06——demo .tilebg 0.14/.beams/.vignette 三层; + # 旧满铺石纹不透退役。缺位不挂即回退平色) + var bd := UiKit.backdrop_tile() + if bd != null: overlay.add_child(bd) + overlay.add_child(UiKit.beams_rect()) + overlay.add_child(UiKit.vignette_rect()) # 双列骨架(M2):左=操作列 56% / 右=舞台+速览 var cols := HBoxContainer.new() cols.set_anchors_preset(Control.PRESET_FULL_RECT) @@ -138,11 +135,14 @@ static func build_start_overlay(host: Control, db = null) -> Dictionary: # 组帽·冒险(M2 分组) box.add_child(UiKit.label("── 冒 险 ──",UiKit.FS_CAPTION, false, UiKit.WARM_FAINT)) # 唯一试玩入口 = 正式「开始冒险」(三关序列);单关测试直入口(L2/L3)退役(2026-09-23 用户拍板) - var resumebtn := UiKit.button("▶ 继续上局") # 迭代 8 卡②——有断点才亮(可见性与关数文案归组合根刷新) + var resumebtn := UiKit.primary_button("▶ 继续上局") # 迭代 8 卡②——有断点才亮(可见性与关数文案归组合根刷新);主操作钮=审计差距⑤对齐 demo resumebtn.tooltip_text = "自最近停点恢复" resumebtn.visible = false box.add_child(resumebtn) - var lvbtn := UiKit.primary_button("▶ 开始冒险", "三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城") + # 主操作语义对齐 demo(2026-10-06 审计差距⑤——有档时「继续上局」才是首动作=主黄钮, + # 「开始冒险」=常规石钮;tooltip 保留) + var lvbtn := UiKit.button("▶ 开始冒险") + lvbtn.tooltip_text = "三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城" box.add_child(lvbtn) # 组帽·营地与系统 diff --git a/game/presentation/views/battle_end_overlay.gd b/game/presentation/views/battle_end_overlay.gd index 445873cf..71a2b6ff 100644 --- a/game/presentation/views/battle_end_overlay.gd +++ b/game/presentation/views/battle_end_overlay.gd @@ -15,10 +15,16 @@ const COL_WARM := UiKit.WARM var _dim: ColorRect var _seal: Panel var _verdict: Label -var _rows_box: VBoxContainer # M9 三行账(GP/钥石/本关加成——demo ⑧;失败侧整行隐藏=P-C4 维持 OQ14) -var _gp_row: Label -var _ks_row: Label -var _bonus_row_l: Label +var _rows_box: VBoxContainer # M9 三行账(GP/钥石/本关加成——demo ⑧两列对齐 2026-10-06 审计差距④;失败侧整行隐藏=P-C4 维持 OQ14) +var _gp_row: HBoxContainer +var _gp_name: Label +var _gp_val: Label +var _ks_row: HBoxContainer +var _ks_name: Label +var _ks_val: Label +var _bonus_row_l: HBoxContainer +var _bonus_name: Label +var _bonus_val: Label var _cele: TextureRect # M9 庆祝立绘(idle 占位浮动——正式帧待资产批) var _btn: Button var _board_rect := Rect2() @@ -75,20 +81,32 @@ func _init() -> void: _rows_box.add_theme_constant_override("separation", 6) _rows_box.visible = false box.add_child(_rows_box) - _gp_row = UiKit.label("",UiKit.FS_SUB, true, UiKit.GOLD_DEEP) - _gp_row.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + # 两列对齐账行(审计差距④对齐 demo ⑧——项目名左/数值右,定宽 360) + _gp_row = _ledger_row() + _gp_name = _ledger_name(UiKit.FS_SUB, UiKit.GOLD_DEEP, true) + _gp_val = _ledger_value(UiKit.FS_SUB, UiKit.GOLD_DEEP, true) + _gp_row.add_child(_gp_name) + _gp_row.add_child(_gp_val) _rows_box.add_child(_gp_row) - _ks_row = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_LABEL) - _ks_row.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + _ks_row = _ledger_row() + _ks_name = _ledger_name(UiKit.FS_BODY, UiKit.INK_LABEL, false) + _ks_val = _ledger_value(UiKit.FS_BODY, UiKit.INK_LABEL, true) + _ks_row.add_child(_ks_name) + _ks_row.add_child(_ks_val) _ks_row.visible = false _rows_box.add_child(_ks_row) - _bonus_row_l = UiKit.label("",UiKit.FS_HINT, false, UiKit.INK_LABEL) - _bonus_row_l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + _bonus_row_l = _ledger_row() + _bonus_name = _ledger_name(UiKit.FS_HINT, UiKit.INK_LABEL, false) + _bonus_val = _ledger_value(UiKit.FS_HINT, UiKit.INK_LABEL, true) + _bonus_row_l.add_child(_bonus_name) + _bonus_row_l.add_child(_bonus_val) _bonus_row_l.visible = false _rows_box.add_child(_bonus_row_l) box.add_child(HSeparator.new()) # M9:明细回看钮退役(P-C7——冒险中回看归「生涯」) _btn = UiKit.primary_button("继续") + _btn.size_flags_horizontal = Control.SIZE_SHRINK_CENTER # 审计差距④——紧凑钮(原全宽) + _btn.custom_minimum_size.x = 150 _btn.pressed.connect(_emit_continue_pressed) box.add_child(_btn) visible = false @@ -99,14 +117,14 @@ func _init() -> void: # 传入(入账次序 = LevelRunner 入账先于组合根 handler,runner.gp 即战后总量);空串 = 不显示(失败侧恒空)。 func open_victory(verdict_line: String, continue_label: String, board_rect: Rect2, gp_summary: String = "", ks_summary: String = "", bonus_summary: String = "", gp_gain := -1, gp_total := -1) -> void: - _dim.color = Color(COL_WARM, 0.28) + _dim.color = Color(UiKit.DIM_NIGHT, 0.62) # 审计差距④——全屏压暗对齐 demo(原 0.28 暖纱透底) _verdict.text = verdict_line _seal.visible = true # 胜利徽记(面板头部静态) _seal.modulate = Color.WHITE - _gp_row.text = gp_summary - _ks_row.text = ks_summary + _set_ledger_row(_gp_name, _gp_val, gp_summary, true) # GP 行=固定名「战利品 GP」(滚动/整串均只入值列) + _set_ledger_row(_ks_name, _ks_val, ks_summary) _ks_row.visible = ks_summary != "" - _bonus_row_l.text = bonus_summary + _set_ledger_row(_bonus_name, _bonus_val, bonus_summary) _bonus_row_l.visible = bonus_summary != "" _rows_box.visible = gp_summary != "" or ks_summary != "" or bonus_summary != "" _btn.text = continue_label @@ -125,7 +143,7 @@ func open_victory(verdict_line: String, continue_label: String, board_rect: Rect func open_defeat(verdict_line: String, continue_label: String, board_rect: Rect2) -> void: # 明度不变的暮蓝色相氛围(全屏唯一暮蓝档——§2.5 最静一拍) - _dim.color = Color(COL_DUSK, 0.45) + _dim.color = Color(COL_DUSK, 0.6) # 审计差距④同族压暗(原 0.45) _verdict.text = verdict_line _board_rect = board_rect visible = true @@ -153,6 +171,44 @@ func verdict_text() -> String: return _verdict.text +# 两列账行工厂(审计差距④——名左/值右,定宽 360) +func _ledger_row() -> HBoxContainer: + var r := HBoxContainer.new() + r.custom_minimum_size = Vector2(360, 0) + return r + + +func _ledger_name(size: int, col: Color, _bold: bool) -> Label: + var l := UiKit.label("", size, true, col) + l.size_flags_horizontal = Control.SIZE_EXPAND_FILL + return l + + +func _ledger_value(size: int, col: Color, bold: bool) -> Label: + return UiKit.label("", size, bold, col) + + +# 拆名/值(组合根传入整串):gp_shape=固定名「战利品 GP」值取余串;其余行按「:」或「 · 」切 +#(全角冒号不入值列——T7 实机「:攻击 +1」缩进 Corrections);无分隔符=整串入值列、名空 +func _set_ledger_row(name_l: Label, val_l: Label, s: String, gp_shape := false) -> void: + if gp_shape and s.begins_with("战利品 GP"): + name_l.text = "战利品 GP" + val_l.text = s.substr(6).strip_edges() + return + var sep := s.find(":") + if sep >= 0: + name_l.text = s.substr(0, sep) + val_l.text = s.substr(sep + 1).strip_edges() + return + sep = s.find(" · ") + if sep >= 0: + name_l.text = s.substr(0, sep) + val_l.text = s.substr(sep).strip_edges() + else: + name_l.text = "" + val_l.text = s + + func _fx_center() -> Vector2: return get_viewport_rect().size * 0.5 @@ -254,11 +310,11 @@ func _play_victory_dust() -> void: # M9 GP 行数字滚动(demo ⑧——0.8s 三次缓出;减动效直出终值) func _roll_gp(gp_gain: int, gp_total: int) -> void: if UiKit.motion_scale < 1.0: - _gp_row.text = "战利品 GP +%d · 合计 %d" % [gp_gain, gp_total] + _gp_val.text = "+%d · 合计 %d" % [gp_gain, gp_total] return var tw := create_tween() tw.tween_method(func(v: float) -> void: - _gp_row.text = "战利品 GP +%d · 合计 %d" % [roundi(gp_gain * v), gp_total], + _gp_val.text = "+%d · 合计 %d" % [roundi(gp_gain * v), gp_total], 0.0, 1.0, UiKit.motion(0.8)).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT) diff --git a/game/presentation/views/board_zone_view.gd b/game/presentation/views/board_zone_view.gd index 776ce1a1..301bf3bb 100644 --- a/game/presentation/views/board_zone_view.gd +++ b/game/presentation/views/board_zone_view.gd @@ -11,6 +11,7 @@ var ap_bar: ApBarView var board_view: BoardView var toast: ToastStack var fx: BoardFxView +var pause_badge: Button # 暂停层入口角标(迭代 17 卡③ P1 丁——debug 角标同形制;连接归组合根) var _board_area: Control # resized 命名转发用(原 lambda 局部捕获——P1-3 转命名 2026-09-29) signal action_requested(cell: Vector2i, kind: String) @@ -41,6 +42,15 @@ func _init() -> void: board_area.add_child(fx) toast = ToastStack.new() board_area.add_child(toast) + # 暂停层入口角标(迭代 17 卡③ P1 丁——战斗屏 HUD 右下角常驻;debug 角标同形制:紧凑钮豁免 + # 宽度基准+字号降档+角距 16px。可见性/置灰归组合根 _sync_pause_entry(其他模态开着=置灰 + # 不可点——has_open 守卫);信号连接归组合根(ADR-0001——装配器/视图零连接) + pause_badge = UiKit.button("暂停", 0.0) # 角落小件豁免宽度基准(同调试角标) + pause_badge.add_theme_font_size_override("font_size", UiKit.FS_HINT) + pause_badge.tooltip_text = "查看钥石与永久强化进度(只读)" + pause_badge.visible = false # 显式初始化——非对局期不误现(可见性归组合根同步) + pause_badge.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_RIGHT, Control.PRESET_MODE_MINSIZE, 16) + board_area.add_child(pause_badge) # M8(UI 落码批):棋盘底常驻操作提示条(demo ⑦——文案经 board_view 输入代码证实; # 旧 chord 一次性 toast 提示退役归此常驻条;审计第 7 条用户批) var hint := PanelContainer.new() diff --git a/game/presentation/views/camp_overlay_view.gd b/game/presentation/views/camp_overlay_view.gd index 1feeb813..8a910815 100644 --- a/game/presentation/views/camp_overlay_view.gd +++ b/game/presentation/views/camp_overlay_view.gd @@ -38,17 +38,15 @@ var _recent_list: Label # 最近冒险(逐行拼接 label——只读 func _init() -> void: - var dim := UiKit.modal_dim(self, Color(0, 0, 0, 0.45)) # 方向稿=石纹底透出(浅 dim——石纹全屏底担当氛围) - # 石纹底噪(样张 v2 冻结件——start overlay 同款;缺位不挂即回退平 dim) - var bd_tex := UiKit.load_tex(UiKit.TEX_BACKDROP_STONE) - if bd_tex != null: - var bd := TextureRect.new() - bd.texture = bd_tex - bd.stretch_mode = TextureRect.STRETCH_TILE - bd.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - bd.set_anchors_preset(Control.PRESET_FULL_RECT) - bd.mouse_filter = Control.MOUSE_FILTER_IGNORE + # 底=不透明暖亮档(2026-10-06 用户实测缺陷修——原形制=0.45 半透 dim+满铺石纹遮底,T5 石纹降 0.14 + # 后底下屏幕透出〔用户实机:对局中开营地见棋盘/战斗提示条〕;demo #v-camp=bg-bright 实底, + # 遮蔽语义由组合根输入锁兜底) + var dim := UiKit.modal_dim(self, UiKit.BG_BRIGHT) + # 石纹弱质感+暗角(UI 对齐批 T5 2026-10-06——demo .tilebg 0.14/.vignette;缺位不挂即回退平色) + var bd := UiKit.backdrop_tile() + if bd != null: dim.add_child(bd) + dim.add_child(UiKit.vignette_rect()) var root := MarginContainer.new() root.set_anchors_preset(Control.PRESET_FULL_RECT) @@ -78,22 +76,17 @@ func _init() -> void: var spacer := Control.new() spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL top.add_child(spacer) - var badge := PanelContainer.new() - # M3(UI 落码批 2026-10-04):钥石徽章烛光黄底+深墨字(demo ②——旧暗底 CAMP_BADGE_PANEL 退役; - # BADGE_PANEL_COL 常量保留他用不动) - badge.add_theme_stylebox_override("panel", - UiKit.panel_flat(UiKit.CANDLE, UiKit.CAMP_HEADER_BAND,UiKit.RADIUS_OBJECT, 1, Vector4(14, 8, 14, 8))) + # M3(UI 落码批):钥石徽章烛光黄底+深墨字(demo ②——旧暗底 CAMP_BADGE_PANEL 退役; + # BADGE_PANEL_COL 常量保留他用不动)。Q10 甲(2026-10-06):本地拼装迁 UiKit.keystone_badge + # 工厂调用(badge 单点化——暂停层同款消费);形制参数逐项同原装配,观感零变化 + var badge := UiKit.keystone_badge("钥石 0") top.add_child(badge) - var bal_row := HBoxContainer.new() - bal_row.add_theme_constant_override("separation", 6) - badge.add_child(bal_row) - var keystone_icon := UiKit.icon_rect(UiKit.TEX_ICON_KEYSTONE, 32) # P6 甲②(2026-10-06):64 源 0.5×(原 20=0.3125× 违 §8.2);缺位 null → 纯文本回退 - if keystone_icon != null: - bal_row.add_child(keystone_icon) - _balance_label = UiKit.label("钥石 0",UiKit.FS_SUB, true, UiKit.INK_ON_GOLD) - bal_row.add_child(_balance_label) + _balance_label = badge.value_label # ── 三柜台列(方向稿同构:头带深棕+米色板身)── + # 三柜等宽分配(2026-10-06 用户实测缺陷修——原定宽 356+行标签按文字全宽算 min,进度行文案 + # 「累计通关 28/2 次·能力封存中」随档变长把面板撑爆,三柜总宽溢出 1248 可用宽=右柜裁出屏; + # 改=EXPAND_FILL 等比分屏宽+行标签截断兜底,min 恒 ≤ 定宽) var mid := HBoxContainer.new() mid.add_theme_constant_override("separation", 18) mid.alignment = BoxContainer.ALIGNMENT_CENTER @@ -101,18 +94,25 @@ func _init() -> void: col.add_child(mid) _forge_holder = VBoxContainer.new() _forge_holder.add_theme_constant_override("separation", 6) - mid.add_child(_counter_panel("属性锻造", UiKit.TEX_ICON_FORGE, _forge_holder, 356)) + var forge_panel := _counter_panel("属性锻造", UiKit.TEX_ICON_FORGE, _forge_holder, 356) + forge_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL + mid.add_child(forge_panel) # 迭代 11 第①批 T4 工坊扩容(P2 甲程序占位——同调装配段+Y 升级行入工坊柜台,行板复用;行数增长以 # 柜台内滚承载,冻结三柜台版式与另两柜台零触碰,视觉终稿归营地批) var ws_scroll := ScrollContainer.new() ws_scroll.custom_minimum_size = Vector2(340, 336) + ws_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL # 2026-10-06 用户实测修——纵向撑满面板余高(固定 336=行断流但面板下半空着,滚动条提前出现) ws_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED _workshop_holder = VBoxContainer.new() _workshop_holder.add_theme_constant_override("separation", 6) ws_scroll.add_child(_workshop_holder) - mid.add_child(_counter_panel("道具工坊", UiKit.TEX_ICON_WORKSHOP, ws_scroll, 356)) + var ws_panel := _counter_panel("道具工坊", UiKit.TEX_ICON_WORKSHOP, ws_scroll, 356) + ws_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL + mid.add_child(ws_panel) var atlas_holder := VBoxContainer.new() - mid.add_child(_counter_panel("奖励格图鉴", UiKit.TEX_ICON_ATLAS, atlas_holder, 356)) + var atlas_panel := _counter_panel("奖励格图鉴", UiKit.TEX_ICON_ATLAS, atlas_holder, 356) + atlas_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL + mid.add_child(atlas_panel) var lock_l := UiKit.label(" —— 柜台结构位预留:内容排 Alpha(地图战斗资源差异化批)",UiKit.FS_HINT, false, UiKit.STONE) lock_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART lock_l.custom_minimum_size = Vector2(300, 0) @@ -123,6 +123,7 @@ func _init() -> void: _tree_holder.add_theme_constant_override("separation", 6) var tree_scroll := ScrollContainer.new() tree_scroll.custom_minimum_size = Vector2(300, 200) + tree_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL # 2026-10-06 同修——树段内滚撑满图鉴柜余高 tree_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED tree_scroll.add_child(_tree_holder) atlas_holder.add_child(tree_scroll) @@ -277,6 +278,7 @@ func refresh_camp(data: Dictionary) -> void: st_col = UiKit.GOLD_DEEP card.status_l.text = st_text card.status_l.add_theme_color_override("font_color", st_col) + card.status_l.tooltip_text = st_text # 截断兜底(状态列 EXPAND_FILL 吸余宽——悬停看全文) # 同调装配段状态就地刷(迭代 11 第①批 T4——Slots 点亮/满位禁选/Y 升级行余额联动) var att: Dictionary = data.get("attune", {}) if _attune_header_l != null: @@ -309,7 +311,7 @@ func _build_forge_row(holder: VBoxContainer, row: Dictionary) -> Dictionary: var card := PanelContainer.new() card.add_theme_stylebox_override("panel", UiKit.panel_flat(ROW_PLATE_COL, UiKit.PANEL_BORDER,UiKit.RADIUS_OBJECT, 1, Vector4(10, 4, 10, 4))) var line := HBoxContainer.new() - line.add_theme_constant_override("separation", 12) + line.add_theme_constant_override("separation", 8) # 2026-10-06 营地溢出修(12→8——行 min 压回定宽内) card.add_child(line) var attr := String(row.get("attr", "")) # M3(UI 落码批):行首 20px 语义图标(图标+形状+颜色三通道——demo ②;子串映射容错 attr 键名演进) @@ -329,6 +331,8 @@ func _build_forge_row(holder: VBoxContainer, row: Dictionary) -> Dictionary: line.add_child(ic) var name_l := UiKit.label("%s %+d/级" % [String(row.get("display_name", "")), int(row.get("flat", 0))], 14, true, UiKit.INK) name_l.custom_minimum_size = Vector2(112, 0) + name_l.clip_text = true # 2026-10-06 营地溢出修——文字全宽不入 min(三柜等宽分配前提) + name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS line.add_child(name_l) var level_l := UiKit.label("",UiKit.FS_HINT, true, UiKit.INK_ON_DEEP) # M3:Lv 章化(木底小章——demo lvtag;Label normal 槽挂样式盒) @@ -337,6 +341,8 @@ func _build_forge_row(holder: VBoxContainer, row: Dictionary) -> Dictionary: line.add_child(level_l) var price_l := UiKit.label("",UiKit.FS_HINT, false, UiKit.STONE) price_l.custom_minimum_size = Vector2(64, 0) + price_l.clip_text = true + price_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS line.add_child(price_l) var btn := UiKit.button("锻造 +1 级", 0.0) # 柜台行板紧凑钮豁免宽度基准 btn.pressed.connect(_emit_buy_requested.bind(attr)) @@ -357,12 +363,19 @@ func _rebuild_workshop(data: Dictionary) -> void: card.add_child(line) var name_l := UiKit.label(String(row.get("display_name", "")),UiKit.FS_HINT, true, UiKit.INK) name_l.custom_minimum_size = Vector2(132, 0) + name_l.clip_text = true + name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS line.add_child(name_l) var progress_l := UiKit.label("",UiKit.FS_HINT, false, UiKit.STONE) - progress_l.custom_minimum_size = Vector2(110, 0) + progress_l.custom_minimum_size = Vector2(150, 0) # 2026-10-06 修——110 截掉阈值数字(「累计通关 28 / 2 次」≈154px) + progress_l.clip_text = true # 营地溢出修——文字全宽不撑柜 + progress_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS line.add_child(progress_l) # 状态色随卡⑭ 亮板双色墨制(深墨族替代暖白族)——色随刷新态条件赋值,非机械两行式(T4 收口保留) var status_l := UiKit.label("",UiKit.FS_HINT, true, UiKit.STONE) + status_l.clip_text = true # 同上——「已解锁 · 能力封存中」不撑柜(余宽吸收者) + status_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + status_l.size_flags_horizontal = Control.SIZE_EXPAND_FILL line.add_child(status_l) _workshop_holder.add_child(card) _workshop_rows.append({"item_id": String(row.get("item_id", "")), "progress_l": progress_l, "status_l": status_l}) @@ -384,6 +397,8 @@ func _rebuild_attune(data: Dictionary) -> void: line.add_theme_constant_override("separation", 6) var name_l := UiKit.label(String(row.get("display_name", "")),UiKit.FS_HINT, true, UiKit.INK) name_l.custom_minimum_size = Vector2(96, 0) + name_l.clip_text = true + name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS line.add_child(name_l) var form_l := UiKit.label("被动" if String(row.get("form", "")) == "passive" else "主动·充能",UiKit.FS_CAPTION, false, UiKit.STONE) form_l.custom_minimum_size = Vector2(58, 0) diff --git a/game/presentation/views/curio_pick_overlay.gd b/game/presentation/views/curio_pick_overlay.gd index a60a7b19..637c2bb3 100644 --- a/game/presentation/views/curio_pick_overlay.gd +++ b/game/presentation/views/curio_pick_overlay.gd @@ -20,7 +20,9 @@ var _continue_btn: Button # 继续钮(兑付反馈态唯一 var _refresh_btn: Button # 刷新钮(迭代 11 第①批 T5b——余量>0 才显示;M7 升顶条图标钮) var _refresh_badge: Label # M7 余量角标(图标钮右上——demo ⑥ 同构) var _refresh_l: Label # 刷新余量读数(X 优先 + Y 冒险池) -var _refresh_allowance := 0 # 组合根投喂余量(本视图零核心引用) +var _foot_hint: Label # T7 底注(demo .bonus-foot .hint——pick 态显隐) +var _refresh_allowance := 0 # 组合根投喂免费余量(X/Y——本视图零核心引用) +var _refresh_gp_cost := 0 # 组合根投喂 GP 刷新价(免费位用尽后的付费档读数) var _selected := "" # 点选暂存(确认前可改选) var _header := "" # 停点头部文案(选择态保持显示) var _card_meta: Array = [] # open_pick 缓存 [{id, text}]——高亮还原读面 @@ -28,12 +30,12 @@ var _bcards: Array = [] # M7 bcard 引用列(select 高亮 func _init() -> void: - # M7(UI 落码批):与商店同构(demo ⑥ 用户令两屏一致)——通栏顶条(标题+刷新图标钮/角标)+ - # 居中入账信息行 + bcard 三竖卡 + 底部唯一主钮「确认选择」;差异白名单=免费选择/✓章/无价签 - var box: VBoxContainer = UiKit.modal_shell(self, Color(0, 0, 0, 0.6)).box - var top := HBoxContainer.new() - top.add_theme_constant_override("separation", 12) - box.add_child(top) + # T7(UI 设计对齐批 2026-10-06):全屏页形态(demo ⑥ #v-bonus——与商店 T6 同壳 page_shell, + # 根底暗档)——56px 通栏顶条(标题+刷新图标钮/角标)+ 居中入账信息行(GP 汇总行承载)+ + # bcard 三竖卡(D1 232×352)+ 底部确认主钮与免费选择注记。差异白名单=免费选择/✓章/无价签。 + # 状态机不变:pick→[replace]→result。M7 同构裁决遗产保留。 + var page: Dictionary = UiKit.page_shell(self, false) + var top: HBoxContainer = page.top_row top.add_child(UiKit.label("战利品三选一",UiKit.FS_TITLE, true, UiKit.WARM)) var top_sp := Control.new() top_sp.size_flags_horizontal = Control.SIZE_EXPAND_FILL @@ -54,25 +56,34 @@ func _init() -> void: _refresh_l = UiKit.label("",UiKit.FS_CAPTION, false, UiKit.WARM_SOFT) _refresh_l.visible = false top.add_child(_refresh_l) + var box: VBoxContainer = page.box _label = UiKit.label("",UiKit.FS_BODY, false, UiKit.WARM_SOFT) _label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER box.add_child(_label) _choice_box = HBoxContainer.new() - _choice_box.add_theme_constant_override("separation", 20) + _choice_box.add_theme_constant_override("separation", 26) # demo .cards gap 26(T7 对齐) _choice_box.alignment = BoxContainer.ALIGNMENT_CENTER + _choice_box.size_flags_vertical = Control.SIZE_EXPAND_FILL box.add_child(_choice_box) + var foot := HBoxContainer.new() + foot.alignment = BoxContainer.ALIGNMENT_CENTER + foot.add_theme_constant_override("separation", 16) + box.add_child(foot) _confirm_btn = UiKit.primary_button("确认选择") _confirm_btn.pressed.connect(_on_confirm_pressed) _confirm_btn.visible = false - box.add_child(_confirm_btn) - _replace_box = VBoxContainer.new() - _replace_box.add_theme_constant_override("separation", 8) - _replace_box.alignment = BoxContainer.ALIGNMENT_CENTER - box.add_child(_replace_box) + foot.add_child(_confirm_btn) _continue_btn = UiKit.button("继续") _continue_btn.pressed.connect(_emit_continue_pressed) _continue_btn.visible = false - box.add_child(_continue_btn) + foot.add_child(_continue_btn) + _foot_hint = UiKit.label("免费选择 · 刷新不消耗 AP",UiKit.FS_HINT, false, UiKit.WARM_FAINT) + foot.add_child(_foot_hint) + _replace_box = VBoxContainer.new() + _replace_box.add_theme_constant_override("separation", 8) + _replace_box.alignment = BoxContainer.ALIGNMENT_CENTER + _replace_box.size_flags_vertical = Control.SIZE_EXPAND_FILL + box.add_child(_replace_box) visible = false @@ -94,7 +105,8 @@ func open_pick(header: String, cards: Array) -> void: var desc := "\n".join(lines.slice(1)) if lines.size() > 1 else "" var idl := cid.to_lower() var band := UiKit.tg_band(idl) # 类型色带单点(N4c 2026-10-06——TG_* 五族同源判定;aoe/blast 归 TG_AOE) - var card := UiKit.bcard(title, desc, band, _icon_for_id(idl)) + # 类型脚注(T7 判官 checkpoint:demo .bcard .src 底部类型行——实机缺=卡下半空底;词=demo 同款) + var card := UiKit.bcard(title, desc, band, _icon_for_id(idl), "", _type_label(idl)) card.gui_input.connect(func(ev: InputEvent) -> void: if ev is InputEventMouseButton and ev.pressed and ev.button_index == MOUSE_BUTTON_LEFT: _select_card(cid)) @@ -105,18 +117,23 @@ func open_pick(header: String, cards: Array) -> void: _confirm_btn.visible = false _replace_box.visible = false _continue_btn.visible = false - _refresh_btn.visible = false # 刷新余量随 set_refresh_info 投喂后现形(open 先归零防残留) + _foot_hint.visible = true # T7 底注=pick 态专属(replace/result 态让位) + _refresh_btn.visible = false # 刷新面随 set_refresh_display 投喂后现形(open 先归零防残留;店内门票面不投喂=无刷新) _refresh_l.text = "" visible = true -# 三选一刷新余量投喂(组合根读 adventure.attune_refresh_left()——0 = 刷新面隐藏) -func set_refresh_info(allowance: int) -> void: - _refresh_allowance = maxi(0, int(allowance)) - _refresh_btn.visible = _refresh_allowance > 0 +# 三选一刷新呈现投喂(2026-10-06 用户令对齐商店 MC-1 同构——free_left=X/Y 免费位余量〔基础 0〕, +# 免费位用尽转 GP 付费档:gp_cost=本次 GP 刷新价、can_afford=冒险钱包可付;钮常驻选择态,两源皆穷=禁用不隐藏) +func set_refresh_display(free_left: int, gp_cost: int, can_afford: bool) -> void: + _refresh_allowance = maxi(0, int(free_left)) + _refresh_gp_cost = maxi(0, int(gp_cost)) + _refresh_btn.visible = _refresh_allowance > 0 or _refresh_gp_cost > 0 _refresh_badge.text = str(_refresh_allowance) - _refresh_l.text = "免费 · 不消耗 AP" if _refresh_allowance > 0 else "" - _refresh_l.visible = _refresh_allowance > 0 + _refresh_l.text = "免费 · 不消耗 AP" if _refresh_allowance > 0 else ("刷新 %d 金币" % _refresh_gp_cost if _refresh_gp_cost > 0 else "") + _refresh_l.visible = _refresh_l.text != "" + _refresh_btn.tooltip_text = "刷新候选——换一批战利品\n免费 %d 次(刷新件 X 优先 + 冒险池 Y)\nGP 刷新 %d 金币\n池源=共享池,与商店货架同源" % [_refresh_allowance, _refresh_gp_cost] + _refresh_btn.disabled = _refresh_allowance <= 0 and not can_afford func _emit_refresh_requested() -> void: @@ -152,6 +169,11 @@ func press_continue() -> void: _continue_btn.pressed.emit() +# 探针/测试复用:以真实按钮路径触发刷新意图(信号外发——受理归组合根) +func press_refresh() -> void: + _refresh_btn.pressed.emit() + + func selected_id() -> String: return _selected @@ -163,6 +185,8 @@ func show_result(text: String) -> void: _confirm_btn.visible = false _replace_box.visible = false _continue_btn.visible = true + _refresh_btn.visible = false # 刷新=选择态专属功能(结果态停点已兑付——残钮零意图可点) + _foot_hint.visible = false visible = true @@ -184,6 +208,8 @@ func open_replace(items: Array, incoming_name: String, holding: int, slots: int) _confirm_btn.visible = false _replace_box.visible = true _continue_btn.visible = false + _refresh_btn.visible = false # 刷新=选择态专属功能(替换子态不换候选) + _foot_hint.visible = false visible = true @@ -193,6 +219,8 @@ func close_pick() -> void: _replace_box.visible = false _continue_btn.visible = false _confirm_btn.visible = false + _refresh_btn.visible = false + _foot_hint.visible = false visible = false @@ -219,3 +247,16 @@ func _icon_for_id(idl: String) -> String: if idl.contains("ap") or idl.contains("action"): return UiKit.TEX_ICON_POUCH return UiKit.TEX_ICON_COINBAG + + +# T7 类型脚注词(demo .bcard .src 同款五族——与 tg_band 同域子串判定,卡底填空对齐) +func _type_label(idl: String) -> String: + if idl.contains("heal"): + return "治疗" + if idl.contains("blast") or idl.contains("magic"): + return "魔法" + if idl.contains("pierce"): + return "穿甲" + if idl.contains("aoe"): + return "群伤" + return "通用增益" diff --git a/game/presentation/views/level_map_screen.gd b/game/presentation/views/level_map_screen.gd index 4f6bbdc3..a627361b 100644 --- a/game/presentation/views/level_map_screen.gd +++ b/game/presentation/views/level_map_screen.gd @@ -61,6 +61,7 @@ var _cand_ids: Array[int] = [] # 本停点候选 id 集(图上命 var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图拟合——画布 min 尺寸随它) var _hover_id := -1 # 悬停预承诺候选(-1 = 无) var abandon_btn: Button # 放弃入口(可见性归组合根 set_abandon_available——只在冒险模式开) +var pause_badge: Button # 暂停层入口角标(迭代 17 卡③ P1 丁——顶栏 GP 徽右侧;连接归组合根) var _abandon_timer := ArmedTimer.new() # 放弃确认窗(T3/D3 P4 乙——_process 帧驱动倒计时机制收口;窗口=CONFIRM_WINDOW_S) var _node_icons := {} # 节点图标贴图(load_tex 守卫——缺位 null → 字形回退,冒烟/无素材检出安全) var _noise_rect: TextureRect # 屏底石纹(换层色板渐变承载面——null = 纯色回退无挂点) @@ -72,42 +73,54 @@ func _init() -> void: # 节点图标贴图装载(样张 v1 冻结形制——load_tex 守卫,缺位回退现行字形) for t in TEX_NODE_ICONS: _node_icons[t] = UiKit.load_tex(TEX_NODE_ICONS[t]) - # 独立屏底(形态升格 2026-09-28——spec Layout:深底+石纹底噪铺满;底纹缺位回退纯色——load_tex 守卫) - var base := ColorRect.new() - base.color = UiKit.BG_ROOT - base.set_anchors_preset(Control.PRESET_FULL_RECT) + # 独立屏底(形态升格 2026-09-28——spec Layout;UI 对齐批 T5 2026-10-06:demo #v-map 暖棕竖向 + # 渐变(#4A3B2A 0%→#3A2E22 40%)+石纹弱质感 0.14+暗角聚焦;旧满铺石纹不透退役;底纹缺位回退纯色) + var base := UiKit.gradient_base(UiKit.BG_MAP_TOP, UiKit.BG_ROOT) add_child(base) - var noise := UiKit.load_tex(UiKit.TEX_BACKDROP_STONE) - if noise != null: - var tex := TextureRect.new() - tex.texture = noise - tex.stretch_mode = TextureRect.STRETCH_TILE - tex.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - tex.set_anchors_preset(Control.PRESET_FULL_RECT) - tex.mouse_filter = Control.MOUSE_FILTER_IGNORE + var tex := UiKit.backdrop_tile() + if tex != null: base.add_child(tex) _noise_rect = tex # 世界侧 tint 承载面(换层色板渐变挂点——§2.7 切片档) + base.add_child(UiKit.vignette_rect()) var center := CenterContainer.new() center.set_anchors_preset(Control.PRESET_FULL_RECT) base.add_child(center) var box := VBoxContainer.new() box.add_theme_constant_override("separation", 10) center.add_child(box) - # 顶栏语境(spec Layout:左关卡名 / 右 GP 常驻徽章——直角细线数据框) + # 顶栏语境(spec Layout:左关卡名 / 右 GP 常驻徽章——直角细线数据框; + # T5 判官 checkpoint 对齐 demo ④:关卡名入框板(裸文本退役)+GP 徽章=烛光胶囊(同商店金币框)+ + # 左右两极排布(原居中悬置)) var top := HBoxContainer.new() top.add_theme_constant_override("separation", 12) + top.size_flags_horizontal = Control.SIZE_EXPAND_FILL box.add_child(top) + var lvl_plate := PanelContainer.new() + lvl_plate.add_theme_stylebox_override("panel", + UiKit.panel_flat(UiKit.DARK_UPCOMING, UiKit.PANEL_BORDER, 0, 1, Vector4(12, 3, 12, 3))) + top.add_child(lvl_plate) _level_label = UiKit.label("",UiKit.FS_BODY, true, UiKit.WARM) - _level_label.custom_minimum_size.x = 420 - top.add_child(_level_label) + lvl_plate.add_child(_level_label) + var top_sp := Control.new() + top_sp.size_flags_horizontal = Control.SIZE_EXPAND_FILL + top.add_child(top_sp) var gp_box := PanelContainer.new() - gp_box.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.DARK_UPCOMING, UiKit.PANEL_BORDER, 0, 1)) + gp_box.add_theme_stylebox_override("panel", + UiKit.panel_flat(UiKit.CANDLE, UiKit.CAMP_HEADER_BAND,UiKit.RADIUS_OBJECT, 1, Vector4(12, 4, 12, 4))) top.add_child(gp_box) var gp_row := HBoxContainer.new() gp_row.add_theme_constant_override("separation", 6) gp_box.add_child(gp_row) - _gp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.GOLD) + _gp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_ON_GOLD) gp_row.add_child(_gp_value) + # 暂停层入口角标(迭代 17 卡③ P1 丁——顶栏常驻;debug 角标同形制:紧凑钮豁免宽度基准+字号降档。 + # 置灰守卫归组合根 _sync_pause_entry(其他模态开着=不可点;本屏自身的登记开态不计——has_open_excluding); + # 信号连接归组合根(ADR-0001——视图零连接)) + pause_badge = UiKit.button("暂停", 0.0) + pause_badge.add_theme_font_size_override("font_size", UiKit.FS_HINT) + pause_badge.tooltip_text = "查看钥石与永久强化进度(只读)" + pause_badge.size_flags_vertical = Control.SIZE_SHRINK_CENTER + top.add_child(pause_badge) _title = UiKit.label(TITLE_FORK,UiKit.FS_SUB, true, UiKit.WARM) _title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER box.add_child(_title) @@ -121,7 +134,11 @@ func _init() -> void: # 底部状态栏(M5 合并单条:亮米面板一站读齐=头像+生命+属性+本关加成/流派件——demo ④; # 道具槽不摆=道具读数无关卡画面成文出口(读快照私域越权),随 runner 出口批补——台账 352 注记; # _hp_value/_attr_value/_bonus_value/_feat_value 投喂键全部保留) + # 底栏定高纪律(2026-10-06 审计差距①对齐 demo——单条 ~85px 紧凑亮条;任何子件不再能把栏撑爆: + # 特性持有行限宽截断+tooltip 全文,旧「autowrap 无限宽→最小高度逐字竖排爆栏」形态根除) var bottom := PanelContainer.new() + bottom.custom_minimum_size = Vector2(0, 85) + bottom.size_flags_vertical = Control.SIZE_SHRINK_END bottom.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.MAP_BAR_PLATE, UiKit.CAMP_HEADER_BAND, 0, 2, Vector4(12, 6, 12, 6))) box.add_child(bottom) @@ -165,18 +182,22 @@ func _init() -> void: _bonus_row.add_child(bonus_box) var bonus_inner := VBoxContainer.new() bonus_box.add_child(bonus_inner) - var bonus_cap := UiKit.label("特性",UiKit.FS_CAPTION, false, UiKit.WARM_SOFT) + var bonus_cap := UiKit.label("特性",UiKit.FS_CAPTION, false, UiKit.INK_HINT) # 淡墨→深墨(亮板可读,审计差距①) bonus_inner.add_child(bonus_cap) - _bonus_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.WARM) + _bonus_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE) # 淡墨→深墨(同上) bonus_inner.add_child(_bonus_value) # 特性持有行(迭代 12 呈现补齐 2026-10-04——用户反馈关卡选择页看不到局内奖励;组合根投喂) - _feat_value = UiKit.label("",UiKit.FS_HINT, false, UiKit.WARM_SOFT) - _feat_value.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + # 审计差距①修(2026-10-06):限宽单行截断+tooltip 全文(旧 autowrap 无限宽=爆栏元凶); + # 墨色 WARM_SOFT→INK_HINT(暖白系对亮板 ≈1:1 不可读——§7.2 违例修复) + _feat_value = UiKit.label("",UiKit.FS_HINT, false, UiKit.INK_HINT) + _feat_value.custom_minimum_size = Vector2(240, 0) + _feat_value.clip_text = true + _feat_value.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS bonus_box.add_child(_feat_value) var spacer := Control.new() spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL strip.add_child(spacer) - abandon_btn = UiKit.danger_button("放弃") # 打磨包 2026-09-29 文案 + M5 危险钮皮(M10 公共件) + abandon_btn = UiKit.danger_button("放弃", "", 90.0) # 审计差距①对齐 demo:小号危险钮 ~90×38(原 216 宽砖);打磨包 2026-09-29 文案 + M5 危险钮皮 abandon_btn.pressed.connect(_on_abandon_pressed) abandon_btn.visible = false strip.add_child(abandon_btn) @@ -211,6 +232,7 @@ func _draw_hp_bar(bar: Control) -> void: # 特性持有投影(迭代 12 呈现补齐——组合根投喂聚合文案;空文案=行隐藏) func set_feat_holdings(text: String) -> void: _feat_value.text = text + _feat_value.tooltip_text = text # 截断全量兜底(限宽单行省略——悬停看全文) func set_abandon_available(v: bool) -> void: @@ -313,7 +335,8 @@ func _apply_level_tint(level_index: int) -> void: _tint_level = level_index var target: Color = BOARD_TINTS.get(level_index, Color(1, 1, 1)) var tw := create_tween() - tw.tween_property(_noise_rect, "modulate", target, UiKit.motion(0.45)) + tw.tween_property(_noise_rect, "modulate", + Color(target.r, target.g, target.b, UiKit.BACKDROP_TILE_ALPHA), UiKit.motion(0.45)) # 0.14 弱质感档随 tint 保持(T5) # 状态栏刷新(命令式纪律——组合根打开本屏/兑付确认回执后重查;零推送信号订阅) diff --git a/game/presentation/views/pause_overlay.gd b/game/presentation/views/pause_overlay.gd new file mode 100644 index 00000000..feaa36b2 --- /dev/null +++ b/game/presentation/views/pause_overlay.gd @@ -0,0 +1,166 @@ +# VERTICAL SLICE - NOT FOR PRODUCTION +# 局内暂停层·只读(迭代 17 卡③ 暂停层只读入口批 2026-10-06——任务书 v1.1;事实源=06-待办清单 +# 「解锁界面与钥石呈现的交互形态(残余:暂停层只读入口)」)。 +# 裁决落形:P1 丁=入口为战斗屏 HUD / 关卡地图屏常驻角标(入口装配在 ui_assembler/两屏,本类不含入口); +# P2 甲=登记 modal_stack 设置层同档(确认弹窗之前)带 dim 遮罩;P3 甲=展示钥石余额+三柜台进度 +# (属性锻造三行已购等级 / 道具工坊六件解锁态 / 天赋树已购概览——层内行序与分组复用营地屏同序); +# P4 甲=进出零动效直开直关(无 tween 无 AnimationPlayer——R5)。 +# 只读契约:R4 数据只读同源——账面快照由组合根 open_pause 喂入(每次开层实时读 MetaProgression +# 访问器重建,快照仅用单次开层生命周期,跨开层零持有);本类零核心访问、零写通路——唯一信号 +# closed(关闭意图,组合根经模态栈单口收口)。R1 公共件单点:全部构成件经 UiKit 工厂产出 +#(modal_shell/panel_skinned/label/button/keystone_badge),零裸色值零自建样式。 +class_name PauseOverlay +extends Control + +signal closed + +var _balance_badge: UiKit.KeystoneBadge # 钥石余额徽(badge 工厂件——Q10 甲) +var _forge_holder: VBoxContainer +var _workshop_holder: VBoxContainer +var _tree_label: Label + + +func _init() -> void: + var shell: Dictionary = UiKit.modal_shell(self, Color(UiKit.DIM, 0.55), Vector2(560, 0), 10, true, + UiKit.panel_skinned(Vector4(20, 16, 20, 16))) # 石板皮(缺位回退数据板——同设置面先例) + var box: VBoxContainer = shell.box + + var title := UiKit.label("暂停",UiKit.FS_TITLE, true, UiKit.INK_TITLE) + title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + box.add_child(title) + var hint := UiKit.label("查看本局钥石与永久强化进度(只读——购买请回营地)",UiKit.FS_HINT, false, UiKit.INK_HINT) + hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + box.add_child(hint) + + # 钥石余额行(badge 工厂件——与营地顶栏钥石徽同形制) + var bal_row := HBoxContainer.new() + bal_row.alignment = BoxContainer.ALIGNMENT_CENTER + box.add_child(bal_row) + _balance_badge = UiKit.keystone_badge("钥石 0") + bal_row.add_child(_balance_badge) + + box.add_child(HSeparator.new()) + _section_head(box, "属性锻造 · 已购等级") + _forge_holder = _section_holder(box) + + _section_head(box, "道具工坊 · 解锁态") + _workshop_holder = _section_holder(box) + + _section_head(box, "天赋树 · 已购概览") + _tree_label = UiKit.label("",UiKit.FS_HINT, false, UiKit.INK_LABEL) + _tree_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + box.add_child(_tree_label) + + box.add_child(HSeparator.new()) + var close_row := HBoxContainer.new() + close_row.alignment = BoxContainer.ALIGNMENT_CENTER + box.add_child(close_row) + var close_btn := UiKit.button("继续", 90.0) # 紧凑返回钮(设置面先例形制;文案=代拍板白名单「继续」级措辞) + close_btn.tooltip_text = "关闭暂停层,继续当前冒险(本层只读,不发生任何购买或修改)" + close_btn.pressed.connect(_emit_closed) + close_row.add_child(close_btn) + visible = false # 显式初始化——不靠树序巧合(缺陷⑤家族纪律) + + +# 分组小节头(营地屏分组同构——fs 副标题档深墨) +func _section_head(box: VBoxContainer, text: String) -> void: + var head := UiKit.label(text,UiKit.FS_BODY, true, UiKit.INK_LABEL) + box.add_child(head) + + +func _section_holder(box: VBoxContainer) -> VBoxContainer: + var holder := VBoxContainer.new() + holder.add_theme_constant_override("separation", 4) + box.add_child(holder) + return holder + + +# ---- 开层(组合根唯一调用口——快照随开随建,R4 单次开层生命周期口径)---- + +func open_pause(data: Dictionary) -> void: + _balance_badge.set_text("钥石 %d" % int(data.get("balance", 0))) + _rebuild_forge(data) + _rebuild_workshop(data) + _rebuild_tree(data) + visible = true + + +# 属性锻造三行(营地屏同序同文案族——名称+每级 flat / 已购等级;只读零价格零按钮) +func _rebuild_forge(data: Dictionary) -> void: + for c in _forge_holder.get_children(): + c.queue_free() + for row in (data.get("forge", []) as Array): + var line := HBoxContainer.new() + line.add_theme_constant_override("separation", 8) + var name_l := UiKit.label("%s %+d/级" % [String(row.get("display_name", "")), int(row.get("flat", 0))],UiKit.FS_HINT, true, UiKit.INK) + name_l.custom_minimum_size = Vector2(150, 0) + name_l.clip_text = true + name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + line.add_child(name_l) + var spacer := Control.new() + spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + line.add_child(spacer) + var lvl_l := UiKit.label("Lv %d" % int(row.get("level", 0)),UiKit.FS_HINT, true, UiKit.INK_TITLE) + line.add_child(lvl_l) + _forge_holder.add_child(line) + + +# 道具工坊六行(解锁态文案与营地柜台同口径——退役/封存/已解锁/未解锁;进度=解锁判定量读数) +func _rebuild_workshop(data: Dictionary) -> void: + for c in _workshop_holder.get_children(): + c.queue_free() + for row in (data.get("workshop", []) as Array): + var line := HBoxContainer.new() + line.add_theme_constant_override("separation", 8) + var name_l := UiKit.label(String(row.get("display_name", "")),UiKit.FS_HINT, true, UiKit.INK) + name_l.custom_minimum_size = Vector2(120, 0) + name_l.clip_text = true + name_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + line.add_child(name_l) + var prog_l := UiKit.label("累计通关 %d/%d 次" % [int(row.get("progress", 0)), int(row.get("threshold", 0))],UiKit.FS_HINT, false, UiKit.INK_HINT) + prog_l.custom_minimum_size = Vector2(150, 0) + prog_l.clip_text = true + prog_l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + line.add_child(prog_l) + var st_l := UiKit.label(_workshop_state_text(row),UiKit.FS_HINT, true, UiKit.INK_LABEL) + st_l.size_flags_horizontal = Control.SIZE_EXPAND_FILL + line.add_child(st_l) + _workshop_holder.add_child(line) + + +# 解锁态文案(camp_overlay_view.refresh_camp 同款判据——退役 > 封存 > 已解锁 > 未解锁) +func _workshop_state_text(row: Dictionary) -> String: + if bool(row.get("retired", false)): + return "已退役" + if bool(row.get("deferred", false)) and bool(row.get("unlocked", false)): + return "已解锁 · 能力封存中" + if bool(row.get("unlocked", false)): + return "已解锁" + return "未解锁" + + +# 天赋树已购概览(单条聚合文案——已购节点等级 + Fork 组激活侧;零已购=空态句) +func _rebuild_tree(data: Dictionary) -> void: + var owned: Array[String] = [] + for row in (data.get("tree", []) as Array): + var lvl := int(row.get("level", 0)) + if lvl > 0: + owned.append("%s Lv%d/%d" % [String(row.get("display_name", "")), lvl, int(row.get("cap", 1))]) + for fg in (data.get("fork_groups", []) as Array): + var active := String(fg.get("active_side", "")) + if active == "": + continue + var side_name := active + for sd in (fg.get("sides", []) as Array): + if String(sd.get("side", "")) == active: + side_name = String(sd.get("display_name", active)) + break + owned.append("%s·激活 %s" % [String(fg.get("display_name", "")), side_name]) + if owned.is_empty(): + _tree_label.text = "尚未购买任何节点(天赋树在营地柜台三)" + else: + _tree_label.text = "已购 %d 项:%s" % [owned.size(), " · ".join(owned)] + + +func _emit_closed() -> void: + closed.emit() diff --git a/game/presentation/views/pause_overlay.gd.uid b/game/presentation/views/pause_overlay.gd.uid new file mode 100644 index 00000000..10fa85f8 --- /dev/null +++ b/game/presentation/views/pause_overlay.gd.uid @@ -0,0 +1 @@ +uid://dgmv0kar3ualk diff --git a/game/presentation/views/settings_overlay.gd b/game/presentation/views/settings_overlay.gd index eb485886..96733f8a 100644 --- a/game/presentation/views/settings_overlay.gd +++ b/game/presentation/views/settings_overlay.gd @@ -34,7 +34,9 @@ func retarget(store) -> void: func _build() -> void: # M4(UI 落码批):石板面板化(demo ③)+行牌化(标签+拨杆+状态字+后果说明行); # 设置项零增减(P-C6:只有主音量——demo 音效音量行系原型发明已撤); - # 减动效与结算演出动画=同一设置(语义命名批合并),一行承载 + # 减动效与结算演出动画=同一设置(语义命名批合并),一行承载。 + # T8(UI 设计对齐批 2026-10-06):两列对齐(label 列定宽 140——demo .setrow label/控制两列)+ + # 后果说明内联随行(demo .why 同排)+返回钮紧凑化(demo .btn9 紧凑——原 216 宽砖)。 var shell: Dictionary = UiKit.modal_shell(self, UiKit.BG_BRIGHT, Vector2(560, 0), 8, true, UiKit.panel_skinned(Vector4(20, 16, 20, 16))) var box: VBoxContainer = shell.box @@ -51,9 +53,12 @@ func _build() -> void: var vol_row := HBoxContainer.new() vol_row.add_theme_constant_override("separation", 14) box.add_child(vol_row) - vol_row.add_child(UiKit.label("主音量",UiKit.FS_BODY, false, UiKit.INK_LABEL)) + var vol_lab := UiKit.label("主音量",UiKit.FS_BODY, false, UiKit.INK_LABEL) + vol_lab.custom_minimum_size = Vector2(140, 0) # T8 label 列定宽(两列对齐) + vol_row.add_child(vol_lab) _volume = UiKit.styled_slider(100.0, 0.0, 100.0) _volume.step = 1 + _volume.size_flags_horizontal = Control.SIZE_EXPAND_FILL _volume.value_changed.connect(_on_volume_changed) vol_row.add_child(_volume) _volume_val = UiKit.label("100%",UiKit.FS_BODY, true, UiKit.INK_TITLE) @@ -64,33 +69,38 @@ func _build() -> void: var fs_row := HBoxContainer.new() fs_row.add_theme_constant_override("separation", 14) box.add_child(fs_row) - fs_row.add_child(UiKit.label("全屏",UiKit.FS_BODY, false, UiKit.INK_LABEL)) + var fs_lab := UiKit.label("全屏",UiKit.FS_BODY, false, UiKit.INK_LABEL) + fs_lab.custom_minimum_size = Vector2(140, 0) # T8 同上 + fs_row.add_child(fs_lab) _fs_btn = UiKit.switch_toggle(false) _fs_btn.toggled.connect(_on_fullscreen_toggled) fs_row.add_child(_fs_btn) _fs_st = UiKit.label("关",UiKit.FS_HINT, true, UiKit.INK_LABEL) fs_row.add_child(_fs_st) - # 结算演出动画(=减动效实称——语义命名批 T17;拨杆 on=完整,off=快速时长减半) + # 结算演出动画(=减动效实称——语义命名批 T17;拨杆 on=完整,off=快速时长减半。 + # T8 核证:reduced_motion 单设置同驱 UiKit.motion_scale 与结算演播——demo「减动效」行与 + # 本行系同一设置,不另立行(拆两行=一设置双行重复,违语义命名批裁决) var motion_row := HBoxContainer.new() motion_row.add_theme_constant_override("separation", 14) box.add_child(motion_row) - motion_row.add_child(UiKit.label("结算演出动画",UiKit.FS_BODY, false, UiKit.INK_LABEL)) + var motion_lab := UiKit.label("结算演出动画",UiKit.FS_BODY, false, UiKit.INK_LABEL) + motion_lab.custom_minimum_size = Vector2(140, 0) # T8 同上 + motion_row.add_child(motion_lab) _motion_btn = UiKit.switch_toggle(true) _motion_btn.toggled.connect(_on_motion_toggled) motion_row.add_child(_motion_btn) _motion_st = UiKit.label("完整",UiKit.FS_HINT, true, UiKit.INK_LABEL) motion_row.add_child(_motion_st) var why := UiKit.label("快速 = 时长减半(运动敏感 / 演示用)",UiKit.FS_CAPTION, false, UiKit.INK_HINT) - why.position.y = 0 - box.add_child(why) + motion_row.add_child(why) # T8 内联随行(demo .why 同排;原独立行换排) box.add_child(HSeparator.new()) # 退出游戏已提级开始菜单(D1——设置面不再承载,用户裁决「退出只在一级菜单出现」) var close_row := HBoxContainer.new() close_row.alignment = BoxContainer.ALIGNMENT_CENTER box.add_child(close_row) - var close_btn := UiKit.button("返回") + var close_btn := UiKit.button("返回", 90.0) # T8 紧凑返回钮(demo 紧凑形;原 216 宽砖) close_btn.pressed.connect(_emit_closed) close_row.add_child(close_btn) diff --git a/game/presentation/views/shop_overlay_view.gd b/game/presentation/views/shop_overlay_view.gd index 54cf60aa..635dbe1f 100644 --- a/game/presentation/views/shop_overlay_view.gd +++ b/game/presentation/views/shop_overlay_view.gd @@ -20,21 +20,22 @@ const KIND_LABELS := {"attack": "攻击", "max_hp": "生命上限", "armor": " var _gp_label: Label var _refresh_btn: Button # M6 呈现位——MC-1 起由 set_refresh_display 投喂启用 signal refresh_requested # MC-1:刷新意图(pool 面;组合根接 presenter.on_shop_refresh) +var _visit_label: Label # T6 爬价规则行(demo .svisit——已购件数/现价倍数由 presenter 投喂) var _cards_box: VBoxContainer +var _foot_hint: Label # T6 底注(demo .shop-foot .hint) var _card_rows: Array[Dictionary] = [] # [{product_id, price, bought, btn}]——refresh_state 就地重刷(零重建回流) var _shelf_rows: Array[Dictionary] = [] # [{kind_id, btn}]——pool 面卡片(买后整架重建=重抽反馈) func _init() -> void: - # M6(UI 落码批):pool 面卡面 bcard 三竖卡同构化(demo ⑤/2026-10-02 同构裁决呈现侧闭环)—— - # 库存余量退役(关卡节点地图 v1.5-a 用户裁);刷新钮呈现位照摆(机制=MC-1 核心批投喂,占位禁用); - # 爬价总览行不摆=已购次数无成文出口(随 MC-1 shop_draw 投影补)。legacy 面(open_shop)原样保留。 - var box: VBoxContainer = UiKit.modal_shell(self, Color(0, 0, 0, 0.6)).box - var top := HBoxContainer.new() - top.add_theme_constant_override("separation", 12) - box.add_child(top) - var title := UiKit.label("商 店",UiKit.FS_TITLE, true, UiKit.WARM) - top.add_child(title) + # T6(UI 设计对齐批 2026-10-06):全屏页形态(demo ⑤ #v-shop——06 在册 D2 商店全屏化/D1 竖卡 + # 232×352 落地)——暖亮底+石纹弱质感+暗角(UiKit.page_shell)+56px 通栏顶条(标题+刷新图标钮/ + # 角标+GP 框)+ 爬价规则行 + bcard 三竖卡居中 + 底部离开钮与池源注记。 + # M6 遗产:库存余量退役(关卡节点地图 v1.5-a 用户裁);刷新钮呈现位照摆(机制=MC-1 投喂)。 + # legacy 面(open_shop)行卡同壳呈现(shop=off 对照承载原样)。高内聚边界不变:纯视图 setter 面。 + var page: Dictionary = UiKit.page_shell(self, true) + var top: HBoxContainer = page.top_row + top.add_child(UiKit.label("商 店",UiKit.FS_TITLE, true, UiKit.WARM)) var top_sp := Control.new() top_sp.size_flags_horizontal = Control.SIZE_EXPAND_FILL top.add_child(top_sp) @@ -56,15 +57,40 @@ func _init() -> void: top.add_child(gp_chip) _gp_label = UiKit.label("金币 0",UiKit.FS_BODY, true, UiKit.INK_ON_GOLD) gp_chip.add_child(_gp_label) + var box: VBoxContainer = page.box + _visit_label = UiKit.label("",UiKit.FS_BODY, false, UiKit.WARM_SOFT) + _visit_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + box.add_child(_visit_label) _cards_box = VBoxContainer.new() + _cards_box.size_flags_vertical = Control.SIZE_EXPAND_FILL _cards_box.add_theme_constant_override("separation", 8) box.add_child(_cards_box) + var foot := HBoxContainer.new() + foot.alignment = BoxContainer.ALIGNMENT_CENTER + foot.add_theme_constant_override("separation", 16) + box.add_child(foot) var leave_btn := UiKit.button("离开商店") leave_btn.pressed.connect(_emit_leave_pressed) - box.add_child(leave_btn) + foot.add_child(leave_btn) + _foot_hint = UiKit.label("货架与奖励三选一同一池 · 池空即空架关店",UiKit.FS_HINT, false, UiKit.WARM_FAINT) + foot.add_child(_foot_hint) visible = false +# 爬价规则行投喂(demo .svisit——buys<0 = legacy 面隐藏行;mult=pow(price_a, buys) 由 presenter 算) +func set_visit_info(buys: int, price_a: float) -> void: + if buys < 0: + _visit_label.text = "" + return + var mult := pow(price_a, float(buys)) + _visit_label.text = "本次进店已购 %d 件 · 现价 %s | 买 1 件 → 三件全换新 · 全场涨价 ×%s" % [ + buys, _xnum(mult), _xnum(price_a)] + + +func _xnum(v: float) -> String: + return "×%d" % int(round(v)) if absf(v - roundf(v)) < 0.005 else "×%.2f" % v + + # 打开商店(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(): @@ -137,7 +163,7 @@ func open_shelf(cards: Array, gp: int) -> void: _shelf_rows.clear() var row := HBoxContainer.new() row.alignment = BoxContainer.ALIGNMENT_CENTER - row.add_theme_constant_override("separation", 20) + row.add_theme_constant_override("separation", 26) # demo .cards gap 26(T6 对齐) _cards_box.add_child(row) for c in cards: _shelf_rows.append(_build_shelf_card(row, c)) diff --git a/game/presentation/views/ui_kit.gd b/game/presentation/views/ui_kit.gd index 82973d64..96d22fd0 100644 --- a/game/presentation/views/ui_kit.gd +++ b/game/presentation/views/ui_kit.gd @@ -52,14 +52,15 @@ const TG_MAGIC := Color8(138, 114, 196) # 魔法 const TG_PIERCE := Color8(87, 160, 160) # 穿甲 const TG_AOE := Color8(226, 86, 118) # 群伤/AOE #E25676(demo --tg-aoe) -# 类型色带分族判定(N4c/Q4 甲——三选一与商店货架同源同判:id/kind 小写子串匹配,默认=通用增益带; -# blast=爆裂群伤归 TG_AOE(reward.tres 现成 clear_blast/spark_aoe 消费面);max_hp 含 hp 归回复族) +# 类型色带分族判定(N4c/Q4 甲——三选一与商店货架同源同判:id/kind 小写子串匹配,默认=通用增益带。 +# E5 回滚 2026-10-06:审计实证 demo「工兵爆破」卡=紫带(magic 族)——blast 归回 TG_MAGIC, +# TG_AOE 只接 aoe 键(spark_aoe 消费面保留);max_hp 含 hp 归回复族) static func tg_band(id_lower: String) -> Color: if id_lower.contains("heal") or id_lower.contains("hp"): return TG_HEAL - if id_lower.contains("aoe") or id_lower.contains("blast"): + if id_lower.contains("aoe"): return TG_AOE - if id_lower.contains("magic"): + if id_lower.contains("blast") or id_lower.contains("magic"): return TG_MAGIC if id_lower.contains("pierce"): return TG_PIERCE @@ -83,6 +84,7 @@ const FOG_OUTLINE := Color8(30, 24, 18) # 雾面轮廓墨(原 board_ const ICON_TINT_ON_GOLD := Color(24.0 / 240.0, 20.0 / 226.0, 16.0 / 200.0) # 当前金底图标墨 = WARM 分量乘法调制(双色墨制——原 level_map_screen 局部) const DUSK := Color8(90, 108, 140) # 暮蓝(全作唯一暮蓝档——失败终局) +const DIM_NIGHT := Color8(24, 18, 12) # 终局压暗底 #18120C(demo 结算仪式暗纱——胜利侧 dim 基色) const WARM := Color8(240, 226, 200) # 暖白(暗底正文) const WARM_SOFT := Color8(232, 213, 168) # 暖白次级(说明文)#E8D5A8(M0 换代——灰米 #C8BCA6 退役,配色总纲「米色去灰」) @@ -215,6 +217,85 @@ static func load_tex(path: String) -> Texture2D: return null +# ── 屏底氛围层(UI 设计对齐批 T5 2026-10-06——demo .tilebg opacity .14/.vignette/.beams 对齐; +# 旧「石纹全屏满铺不透」退役——demo 石纹只作弱质感,聚焦靠暗角)── +const BACKDROP_TILE_ALPHA := 0.14 # demo .tilebg{opacity:.14} +const BG_MAP_TOP := Color8(74, 59, 42) # 地图屏渐变顶 #4A3B2A(demo #v-map 180deg 0%;低点 40% = BG_ROOT #3A2E22) + + +# 石纹弱质感全屏片(0.14;缺素材返回 null = 调用侧回退平色) +static func backdrop_tile() -> TextureRect: + var bd_tex := load_tex(TEX_BACKDROP_STONE) + if bd_tex == null: + return null + var bd := TextureRect.new() + bd.texture = bd_tex + bd.stretch_mode = TextureRect.STRETCH_TILE + bd.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + bd.modulate = Color(1, 1, 1, BACKDROP_TILE_ALPHA) + bd.set_anchors_preset(Control.PRESET_FULL_RECT) + bd.mouse_filter = Control.MOUSE_FILTER_IGNORE + return bd + + +# 径向暗角(demo .vignette——ellipse 90%×80% at 50% 42%,透明 58% → 黑 0.30 满角) +static func vignette_rect() -> TextureRect: + var g := Gradient.new() + g.offsets = PackedFloat32Array([0.0, 0.58, 1.0]) + g.colors = PackedColorArray([Color(0, 0, 0, 0), Color(0, 0, 0, 0), Color(0, 0, 0, 0.30)]) + var t := GradientTexture2D.new() + t.gradient = g + t.fill = GradientTexture2D.FILL_RADIAL + t.fill_from = Vector2(0.5, 0.42) + t.fill_to = Vector2(1.4, 0.42) + t.width = 256 + t.height = 256 + var tr := TextureRect.new() + tr.texture = t + tr.stretch_mode = TextureRect.STRETCH_SCALE + tr.set_anchors_preset(Control.PRESET_FULL_RECT) + tr.mouse_filter = Control.MOUSE_FILTER_IGNORE + return tr + + +# 斜光带(demo #v-start .beams——repeating-linear-gradient 102deg,暖白 5% 窄带 240-340/560px; +# 程序纹理循环带,确定性生成零随机) +static func beams_rect() -> TextureRect: + var img := Image.create(560, 560, false, Image.FORMAT_RGBA8) + var ca := cos(0.2094) # 12°(102deg 带轴的垂直倾角) + var sa := sin(0.2094) + for y in 560: + for x in 560: + var d := fposmod(x * ca + y * sa, 560.0) + if d >= 240.0 and d < 340.0: + img.set_pixel(x, y, Color(1.0, 236.0 / 255.0, 200.0 / 255.0, 0.05)) + var tr := TextureRect.new() + tr.texture = ImageTexture.create_from_image(img) + tr.stretch_mode = TextureRect.STRETCH_TILE + tr.set_anchors_preset(Control.PRESET_FULL_RECT) + tr.mouse_filter = Control.MOUSE_FILTER_IGNORE + return tr + + +# 竖向渐变底(demo #v-map linear-gradient(180deg, top 0%, low 40%)——stop 后恒 low) +static func gradient_base(top: Color, low: Color, stop := 0.4) -> TextureRect: + var g := Gradient.new() + g.offsets = PackedFloat32Array([0.0, stop]) + g.colors = PackedColorArray([top, low]) + var t := GradientTexture2D.new() + t.gradient = g + t.fill_from = Vector2(0.5, 0.0) + t.fill_to = Vector2(0.5, 1.0) + t.width = 64 + t.height = 256 + var tr := TextureRect.new() + tr.texture = t + tr.stretch_mode = TextureRect.STRETCH_SCALE + tr.set_anchors_preset(Control.PRESET_FULL_RECT) + tr.mouse_filter = Control.MOUSE_FILTER_IGNORE + return tr + + # CJK 随包字体(2026-09-27 用户拍板 Fusion Pixel OFL——CJK 覆盖+授权干净;命名遵 art-bible §8.3 # 全小写 snake_case 域前缀)。守卫装载同 load_tex 纪律:缺位返回 null(引擎回退字体接手,不炸)。 const FONT_UI := "res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf" @@ -305,6 +386,44 @@ static func modal_shell(parent: Control, dim_color: Color, min_size := Vector2.Z return {"dim": dim, "center": center, "panel": panel, "box": box} +# ── 全屏页壳(UI 设计对齐批 T6/T7 2026-10-06——demo #v-shop/#v-bonus 全屏页形态落地: +# 06 在册 D2 商店全屏化/D1 竖卡 232×352。暖底+石纹弱质感+暗角+56px 通栏顶条+内容列; +# 商店=暖亮底、三选一=根底,两屏同构差异仅底色与顶条工具件)── +const PAGE_TOP_H := 56.0 + +static func page_shell(host: Control, bright := true) -> Dictionary: + var page := Control.new() + page.set_anchors_preset(Control.PRESET_FULL_RECT) + host.add_child(page) + var base := ColorRect.new() + base.color = BG_BRIGHT if bright else BG_ROOT + base.set_anchors_preset(Control.PRESET_FULL_RECT) + base.mouse_filter = Control.MOUSE_FILTER_STOP # 页底挡点击——模态语义由组合根输入锁兜底 + page.add_child(base) + var tile := backdrop_tile() + if tile != null: + page.add_child(tile) + page.add_child(vignette_rect()) + var top := PanelContainer.new() + top.set_anchors_preset(Control.PRESET_TOP_WIDE) + top.custom_minimum_size = Vector2(0, PAGE_TOP_H) + top.add_theme_stylebox_override("panel", + panel_flat(Color8(38, 29, 20), Color8(85, 72, 58), 0, 1, Vector4(26, 6, 26, 6))) + page.add_child(top) + var top_row := HBoxContainer.new() + top_row.add_theme_constant_override("separation", 12) + top.add_child(top_row) + var box := VBoxContainer.new() + box.set_anchors_preset(Control.PRESET_FULL_RECT) + box.offset_top = PAGE_TOP_H + 20 + box.offset_left = 26 + box.offset_right = -26 + box.offset_bottom = -22 + box.add_theme_constant_override("separation", 10) + page.add_child(box) + return {"page": page, "base": base, "top_row": top_row, "box": box} + + # 属性百分比合成(main._debug_hero_override 与调试浮窗生效值预览共用单点——两处同式实现收口) static func scaled(base: int, pct: int) -> int: return maxi(1, int(round(float(base) * (100.0 + float(pct)) / 100.0))) @@ -384,6 +503,34 @@ static func icon_rect(tex_path: String, px: float) -> TextureRect: return tr +# 钥石徽(badge 工厂件——Q10 甲收编 2026-10-06:原营地顶栏钥石徽本地拼装〔camp_overlay_view〕 +# 参数化收编,消费方 = 营地覆盖层 + 暂停层钥石余额行(迭代 17 卡③)。形制 = 烛光黄底+木色描边 +# +钥石图标(icon_rect 守卫装载,缺位=纯文本回退)+深墨字;零新素材零新色值——值全取现行 token) +class KeystoneBadge: + extends PanelContainer + var value_label: Label + + func _init(text: String, icon_path: String, icon_px: float) -> void: + add_theme_stylebox_override("panel", + UiKit.panel_flat(UiKit.CANDLE, UiKit.CAMP_HEADER_BAND, UiKit.RADIUS_OBJECT, 1, Vector4(14, 8, 14, 8))) + var row := HBoxContainer.new() + row.add_theme_constant_override("separation", 6) + add_child(row) + if icon_path != "": + var ic := UiKit.icon_rect(icon_path, icon_px) + if ic != null: + row.add_child(ic) + value_label = UiKit.label(text, UiKit.FS_SUB, true, UiKit.INK_ON_GOLD) + row.add_child(value_label) + + func set_text(t: String) -> void: + value_label.text = t + + +static func keystone_badge(text: String, icon_path := TEX_ICON_KEYSTONE, icon_px := 32.0) -> KeystoneBadge: + return KeystoneBadge.new(text, icon_path, icon_px) + + # 按钮皮(常态/按压两态+墨字——缺位 = 引擎默认主题,素材批前逐位一致) static func _apply_button_skin(b: Button) -> void: var tex_n := load_tex(TEX_BUTTON_STONE) @@ -435,8 +582,8 @@ static func primary_button(text: String, tip := "") -> Button: # 危险钮(深红平涂——放弃/删除类;破坏性确认位置纪律=不得与常规确认同位) -static func danger_button(text: String, tip := "") -> Button: - var b := button(text) +static func danger_button(text: String, tip := "", min_w := 216.0) -> Button: + var b := button(text, min_w) var sb := panel_flat(Color8(180, 78, 54), Color8(110, 42, 28),RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2)) b.add_theme_stylebox_override("normal", sb) b.add_theme_stylebox_override("hover", panel_flat(Color8(196, 90, 62), Color8(110, 42, 28),RADIUS_OBJECT, 2, Vector4(10, 2, 10, 2))) @@ -490,7 +637,8 @@ class BCard: _sb_normal = UiKit.panel_skinned(Vector4(12, 10, 12, 10)) if tex != null else UiKit.panel_flat(PANEL_DATA, PANEL_BORDER,RADIUS_OBJECT, 1, Vector4(12, 10, 12, 10)) # 回退=数据板单点(P7 乙后同值,字面量退役) _sb_sel = UiKit.panel_flat(PANEL_DATA, Color8(138, 90, 52), RADIUS_OBJECT, 2, Vector4(12, 10, 12, 10)) # 选中底=数据板单点(P7 乙后同值) add_theme_stylebox_override("panel", _sb_normal) - custom_minimum_size = Vector2(216, 300) + custom_minimum_size = Vector2(232, 352) # D1(UI 设计对齐批 T6 2026-10-06——demo .bcard 232×352;原 216×300) + size_flags_vertical = Control.SIZE_SHRINK_CENTER # 行容器不纵向拉伸(卡恒 352 高居中——T7 实测 HBox fill 拉到 ~536 修) var box := VBoxContainer.new() box.add_theme_constant_override("separation", 8) add_child(box) @@ -501,16 +649,21 @@ class BCard: mark.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT mark.visible = false box.add_child(mark) + # 图标槽(demo .ic 76×76 槽底+图标 60=20px 源 3 整数倍——P6 甲④ 2× 档升 3× 随 D1 对齐) + var slot := PanelContainer.new() + slot.add_theme_stylebox_override("panel", UiKit.panel_flat(SLOT_BACKDROP, PANEL_BORDER, 0, 1)) + slot.custom_minimum_size = Vector2(76, 76) + slot.size_flags_horizontal = Control.SIZE_SHRINK_CENTER + box.add_child(slot) icon_tr = TextureRect.new() var itex := UiKit.load_tex(icon_path) if itex != null: icon_tr.texture = itex - icon_tr.custom_minimum_size = Vector2(40, 40) # P6 甲④(2026-10-06):20×20 源 2× 档(原 56=2.8× 违 §8.2) + icon_tr.custom_minimum_size = Vector2(60, 60) icon_tr.expand_mode = TextureRect.EXPAND_IGNORE_SIZE icon_tr.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED icon_tr.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - icon_tr.size_flags_horizontal = Control.SIZE_SHRINK_CENTER - box.add_child(icon_tr) + slot.add_child(icon_tr) name_l = UiKit.label(title,FS_SUB, true, INK_TITLE) name_l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER box.add_child(name_l) @@ -578,9 +731,13 @@ static func styled_slider(value: float, minv := 0.0, maxv := 100.0) -> HSlider: s.min_value = minv s.max_value = maxv s.value = value - s.custom_minimum_size = Vector2(200, 20) - s.add_theme_stylebox_override("slider", panel_flat(Color8(184, 165, 130), PANEL_BORDER, 0, 1)) - s.add_theme_stylebox_override("grabber_area", panel_flat(Color8(122, 82, 50), Color8(0, 0, 0, 0), 0, 0)) + s.custom_minimum_size = Vector2(200, 24) + # 槽轨/填充实高(2026-10-06 判官实checkpoint:HSlider 槽=按 stylebox min 高绘制——零 margin + # StyleBoxFlat min 高 0-2px=轨道整条不可见;6px 上下 content margin=12px 实高) + s.add_theme_stylebox_override("slider", + panel_flat(Color8(184, 165, 130), PANEL_BORDER, 0, 1, Vector4(0, 6, 0, 6))) + s.add_theme_stylebox_override("grabber_area", + panel_flat(Color8(122, 82, 50), Color8(0, 0, 0, 0), 0, 0, Vector4(0, 6, 0, 6))) return s diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index 25066713..353c03e0 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -619,17 +619,23 @@ func _smoke_feats() -> void: rarest_tokens += 1 check(feat_tokens == 27 and rarest_tokens == 6 and a_r0._curio_deck.size() == 125, "规则改写型特性带入池:27 件(rare 档 21 + rarest 锚点 6)——牌库 98+27=125") - check(_db.feats.validate().is_empty(), "feat_table.validate 零违例(有行流派扫雷端铁律——magic first_dig_spell 在案)") - # ② 档位激活引擎(纯函数直测——构造表 4 件 magic + 1 件 flag + 1 件 rarest 锚点) + check(_db.feats.validate().is_empty(), "feat_table.validate 零违例(有行流派扫雷端铁律——magic 魔能共鸣 dig_reveal 订阅在案)") + # ② 档位激活引擎(纯函数直测——构造表 4 件 magic + 1 件 flag;内容数据驱动扩展批 T3 断言面随迁: + # 行=triggers 事件订阅形态,挂点投影断言换代=效果引擎订阅装载序〔feats 表序〕) var rt := FeatTable.new() rt.rows = [ - {"feat_id": "m1", "display_name": "魔一", "rarity": "common", "archetype": "magic", "hook": "hero_attack_rider", "effects": {"value": 1}}, - {"feat_id": "m2", "display_name": "魔二", "rarity": "common", "archetype": "magic", "hook": "first_dig_spell", "effects": {"value": 1}}, - {"feat_id": "m3", "display_name": "魔三", "rarity": "rare", "archetype": "magic", "hook": "clamp_modifier", "effects": {"value": 1}}, - {"feat_id": "m4", "display_name": "魔四", "rarity": "rare", "archetype": "magic", "hook": "hero_attack_rider", "effects": {"tier_value": 2}}, - {"feat_id": "f1", "display_name": "旗一", "rarity": "common", "archetype": "flag", "hook": "flag_resource", "effects": {"value": 1}}, + {"feat_id": "m1", "display_name": "魔一", "rarity": "common", "archetype": "magic", "desc": "测试 rider", + "triggers": [{"on": "attack_commit", "do": {"kind": "add_ticket_rider", "value": 1}}]}, + {"feat_id": "m2", "display_name": "魔二", "rarity": "common", "archetype": "magic", "desc": "测试奥术矢", + "triggers": [{"on": "dig_reveal", "do": {"kind": "spawn_spell", "value": 1}}]}, + {"feat_id": "m3", "display_name": "魔三", "rarity": "rare", "archetype": "magic", "desc": "测试克制", + "triggers": [{"on": "clamp_check", "when": [{"fact": "matchup", "op": "eq", "value": "magic_vs_magic"}], "do": {"kind": "modify_ticket", "delta": 1}}]}, + {"feat_id": "m4", "display_name": "魔四", "rarity": "rare", "archetype": "magic", "desc": "测试加值", + "triggers": [{"on": "attack_commit", "do": {"kind": "attack_bonus", "value": 2}}]}, + {"feat_id": "f1", "display_name": "旗一", "rarity": "common", "archetype": "flag", "desc": "测试图谱", + "triggers": [{"on": "flag_hit", "do": {"kind": "grant_material", "value": 1}}]}, ] - check(rt.validate().is_empty(), "构造 relic 表 validate 零违例(magic 4 件含 first_dig_spell 扫雷端——红线①铁律过)") + check(rt.validate().is_empty(), "构造 relic 表 validate 零违例(magic 4 件含 dig_reveal 扫雷端订阅——红线①铁律过)") var tiers := FeatsEngine.tiers_for(["m1", "m2", "m3", "m4", "f1"], rt) check(int(tiers.get("magic", -1)) == 2 and int(tiers.get("flag", -1)) == 0, "档位激活:magic 4 件→档二 · flag 1 件→散件档 0(集 2 档一/4 档二——以撒套装/小骨铭文先例)") @@ -637,9 +643,6 @@ func _smoke_feats() -> void: "成型判读:magic 成型(tier≥1)· flag 未成型(散件单体保底——档位只质变不启动)") var t2 := FeatsEngine.tiers_for(["m1", "m2"], rt) check(int(t2.get("magic", -1)) == 1, "档位激活:2 件→档一(同集合同档位确定性——MC 逐位可复放承载)") - var hooks := FeatsEngine.hooks_for(["m1", "f1"], rt) - check(hooks.size() == 2 and String(hooks[0].get("hook", "")) == "hero_attack_rider", - "挂点投影:表序稳定(同集合→同投影)") # ③ 规则改写型特性带接线(临时表注入——relic 令牌入牌库/兑付入持有集/踢池/断点往返;测试尾还原现行表) var live_rows: FeatTable = _db.feats _db.feats = rt @@ -721,56 +724,121 @@ func _smoke_feats() -> void: check(bp_deck_m3 == 0, "断点余库:已兑付 relic 令牌逐枚还原(m3 零残留——续玩不重复获得)") sm_r.clear_breakpoint() _db.feats = live_rows # 还原现行表(共享 _db 防污染——后续冒烟段零感知) - # ④ T2 魔法流求值面(现行表 magic 四件——flags 组装直测;rider 落地行为=伤害控制器消费面, - # 既有战斗冒烟全绿=feat_flags 空 dict 零扰动实证;全链分岔读数归 T7 MC) + # ④ 魔法流订阅面(内容数据驱动扩展批 T3 断言面随迁——_feat_flags_for 挂点求值退役: + # 求值断言=效果引擎 rebuild+dispatch 命中面;落地行为=发射点施加面(本段下方各事件直测), + # 全链分岔读数归 T6 MC 对拍) var orch = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(73)) - orch._feat_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance"]) - var rf: Dictionary = orch._damage.feat_flags - check(int(rf.get("rider_flat", -1)) == 2 and bool(rf.get("rider_crit", false)), - "魔法流求值:rider_flat=余烬1+档一(4件)+1=2 · 档二 rider 可暴击(共鸣 rider 提升以奥术矢+档位承载——锚点双效果 provisional 披露)") - check(int(rf.get("clamp_magic_magic", -1)) == 1 and int(rf.get("clamp_magic_heavy", -1)) == 1 - and bool(rf.get("first_dig_spell", false)) and int(rf.get("spell_value", -1)) == 2, - "魔法流求值:魔网织者 magic-vs-magic 罚修正 +1 · 湛蓝誓约 magic-vs-heavy +1 · 魔能共鸣奥术矢 value 2") - # ⑤ T3 标旗流求值面(现行表 magic 4+flag 4——混合持有:flag 档一 4 件成型) - orch._feat_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance", - "mine_atlas", "nest_hunter", "perfect_defense", "war_banner"]) - var rf8: Dictionary = orch._damage.feat_flags - check(int(rf8.get("flag_resource_extra", -1)) == 1 and int(rf8.get("nest_aura_value", -1)) == 1 - and int(rf8.get("flag_all_hit_mult", -1)) == 2 and int(rf8.get("flag_strike_value", -1)) == 2, - "标旗流求值:雷区图谱额外资源 1 · 猎巢人光环 +1/巢 · 完美布防 ×2 · 战旗军规旗伤 2(复用 board_typed 零新配方)") - # ⑥ T4 穿刺/普攻流求值面(全 16 件持有——四流派档位成型) - orch._feat_flags_for(["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance", - "mine_atlas", "nest_hunter", "perfect_defense", "war_banner", + var magic4: Array = ["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance"] + orch._fx.rebuild(magic4, [], []) + var ac4: Array = orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "magic_vs_magic", "dig_streak": 0, "nest_count": 0}) + check(orch._hits_of(ac4, "add_ticket_rider").size() == 1 + and int((orch._hits_of(ac4, "add_ticket_rider")[0] as Dictionary).get("value", -1)) == 1, + "魔法流订阅:奥术余烬 attack_commit→add_ticket_rider value 1(必中附加通道)") + check(int((orch._hits_of(orch._fx_emit("clamp_check", {"matchup": "magic_vs_magic"}), "modify_ticket")[0] as Dictionary).get("delta", -1)) == 1 + and int((orch._hits_of(orch._fx_emit("clamp_check", {"matchup": "magic_vs_heavy"}), "modify_ticket")[0] as Dictionary).get("delta", -1)) == 1, + "魔法流订阅:魔网织者 magic-vs-magic +1 · 湛蓝誓约 magic-vs-heavy +1(clamp_check modify 窗口)") + var spell4: Array = orch._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": true, "terrain_kind": []}) + check(orch._hits_of(spell4, "spawn_spell").size() == 1 + and int((orch._hits_of(spell4, "spawn_spell")[0] as Dictionary).get("value", -1)) == 2, + "魔法流订阅:魔能共鸣每战首次开格→spawn_spell value 2(奥术矢)") + check(int(FeatsEngine.tiers_for(magic4, _db.feats).get("magic", -1)) == 2, + "魔法流档位:magic 4 件→档二(rider 可暴击——begin_battle 注伤害控制器两运行态)") + # ⑤ 标旗流订阅面(magic 4+flag 4——混合持有:flag 档一成型) + orch._fx.rebuild(magic4 + ["mine_atlas", "nest_hunter", "perfect_defense", "war_banner"], [], []) + check(orch._hits_of(orch._fx_emit("flag_hit", {"cell": Vector2i(1, 1), "round": 1, "terrain_kind": []}), "grant_material").size() == 1, + "标旗流订阅:雷区图谱 flag_hit→grant_material(标中雷额外资源)") + var nh5: Array = orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 3}) + var nh_hits: Array = orch._hits_of(nh5, "add_ticket_rider") + check(nh_hits.size() == 2 and String((nh_hits[1] as Dictionary).get("per", "")) == "nest_count", + "标旗流订阅:猎巢人 attack_commit→add_ticket_rider per nest_count(控巢光环 rider 通道)") + var fs5: Array = orch._fx_emit("flag_settle", {"flags_hit_all": true, "flags_count": 2, "round": 1}) + check(orch._hits_of(fs5, "open_ticket").size() == 1 + and int((orch._hits_of(fs5, "open_ticket")[0] as Dictionary).get("value", -1)) == 2 + and orch._hits_of(fs5, "modify_ticket").size() == 1, + "标旗流订阅:战旗军规 flag_settle→open_ticket value 2(attack 通道自开单)· 完美布防 flag_mult ×2") + check(orch._fx_emit("flag_settle", {"flags_hit_all": false, "flags_count": 0, "round": 1}).is_empty(), + "标旗流订阅:零标中旗→战旗/完美布防零命中(when flags_count 封闭判定)") + # ⑥ 穿刺/普攻流订阅面(16 件持有——四流派档位成型) + var ids16: Array = magic4 + ["mine_atlas", "nest_hunter", "perfect_defense", "war_banner", "pierce_shot", "chain_arrows", "weak_instinct", "hunter_rapier", - "miner_stubborn", "miner_hammer", "focus_mind", "armor_crack"]) - var rf16: Dictionary = orch._damage.feat_flags - check(bool(rf16.get("kill_overflow", false)) and int(rf16.get("chain_value", -1)) == 2 - and int(rf16.get("discount_shift", -1)) == 1 and int(rf16.get("clamp_pierce_light", -1)) == 1, - "穿刺流求值:贯穿射击弹射旗 · 连珠矢段值 2 上限 2 · 猎弱本能折扣改善 1 档 · 猎手刺剑 pierce-vs-light +1") - check(int(rf16.get("round_dig_value", -1)) == 1 and int(rf16.get("hit_trade_atk", -1)) == 25 - and int(rf16.get("hit_trade_hit", -1)) == 2 and int(rf16.get("armor_roll_rewrite", -1)) == 75 - and int(rf16.get("crack_value", -1)) == 25, - "普攻流求值:执拗 +1/格上限 5 · 重锤交换 +25%/−2AC · 专注恒 75% 免掷骰 · 碎甲震击 off_guard 授予旗") - check(bool(rf16.get("rider_crit", false)) and bool(rf16.get("rider_crit", false)), + "miner_stubborn", "miner_hammer", "focus_mind", "armor_crack"] + orch._fx.rebuild(ids16, [], []) + check(orch._hits_of(orch._fx_emit("kill", {"target": "敌", "overflow": 10}), "overflow").size() == 1, + "穿刺流订阅:贯穿射击 kill→overflow(击杀弹射旗)") + var dr6: Array = orch._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": false, "terrain_kind": []}) + check(orch._hits_of(dr6, "counter_add").size() == 2, + "穿刺/普攻流订阅:连珠矢+执拗 dig_reveal→counter_add(扫雷侧事件铁律承载——chain_digs/stubborn_digs)") + var ac6: Array = orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "pierce_vs_light", "dig_streak": 9, "nest_count": 0}) + var seg6: Array = orch._hits_of(ac6, "attack_segments") + check(seg6.size() == 1 and int((seg6[0] as Dictionary).get("value", -1)) == 2 + and int((seg6[0] as Dictionary).get("cap", -1)) == 2 and int((seg6[0] as Dictionary).get("divisor", -1)) == 3, + "穿刺流订阅:连珠矢 attack_commit→attack_segments(段值 2 上限 2 每 3 格 +1 段)") + var clamp6: Array = orch._fx_emit("clamp_check", {"matchup": "pierce_vs_light"}) + check(orch._hits_of(clamp6, "modify_ticket").size() == 2, + "穿刺流订阅:猎弱本能 ends_with vs_light→discount_shift · 猎手刺剑 pierce_vs_light→delta +1(同一事件双订阅按声明序)") + check(orch._hits_of(orch._fx_emit("armor_roll", {"target_ac": 5, "roll": 3}), "modify_ticket").size() == 1, + "普攻流订阅:专注 armor_roll→modify armor_rewrite 75(免掷恒折扣)") + var ac6n: Array = orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "normal_vs_heavy", "dig_streak": 0, "nest_count": 0}) + check(orch._hits_of(ac6n, "apply_status").size() == 1 + and String((orch._hits_of(ac6n, "apply_status")[0] as Dictionary).get("status_id", "")) == "off_guard", + "普攻流订阅:碎甲震击 ends_with vs_heavy→apply_status off_guard(复用零新状态行)") + var ham6: Array = orch._hits_of(ac6, "modify_ticket") + var has_trade := false + for h in ham6: + if int((h as Dictionary).get("trade_atk", 0)) == 25 and int((h as Dictionary).get("trade_hit", 0)) == 2: + has_trade = true + check(has_trade, "普攻流订阅:矿工重锤 attack_commit→modify trade_atk 25/trade_hit 2(命中交换)") + check(int(FeatsEngine.tiers_for(ids16, _db.feats).get("pierce", -1)) == 2 + and int(FeatsEngine.tiers_for(ids16, _db.feats).get("normal", -1)) == 2, "档位质变:magic/pierce/normal/flag 四流派档二(16 件混合——同标签 ≥4 判档二确定性)") - # ⑦ T5 开格/蓄力流求值面(追加七件) - var ids23: Array = ["arcane_ember", "mana_weaver", "azure_oath", "mana_resonance", - "mine_atlas", "nest_hunter", "perfect_defense", "war_banner", - "pierce_shot", "chain_arrows", "weak_instinct", "hunter_rapier", - "miner_stubborn", "miner_hammer", "focus_mind", "armor_crack", - "greedy_lantern", "vein_intuition", "gambler_pact", "deep_drill", - "armory_prep", "iron_wall", "patient_burst"] - orch._feat_flags_for(ids23) - var rf23: Dictionary = orch._damage.feat_flags - check(int(rf23.get("dig_yield_pos_mult", -1)) == 85 and int(rf23.get("dig_yield_neg_mult", -1)) == 125 - and int(rf23.get("combo_cap", -1)) == 5, - "开格流求值:正向乘区 25+10+50=85% · 负向加重 25+100=125% · 直觉连击上限 5(同 hook 累加——三件同持)") - check(int(rf23.get("deep_dig_cost", -1)) == 1 and int(rf23.get("deep_dig_cap", -1)) == 4 - and int(rf23.get("ap_carry_value", -1)) == 1 and int(rf23.get("shield_per_layer", -1)) == 1 - and int(rf23.get("spike_mult", -1)) == 2, - "深挖钻头=开掘 +1 AP/每 4 格深层产出(R4 甲非信息税对价)· 蓄力三件=层值 1/减伤 1/层/满层 ×2") - check(orch.action_economy.feat_ap_carry and orch.action_economy.feat_dig_extra == 1, - "蓄力/钻头 action_economy 注入(ap_carry 改写停手转存 + 付费开掘对价注入——关着 false/0 零扰动)") + # ⑦ 开格/蓄力流订阅面(追加七件) + var ids23: Array = ids16.duplicate() + ids23.append_array(["greedy_lantern", "vein_intuition", "gambler_pact", "deep_drill", + "armory_prep", "iron_wall", "patient_burst"]) + orch._fx.rebuild(ids23, [], []) + var pos_m := 0 + var neg_m := 0 + var combo_cap := -1 + for h in orch._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": false, "terrain_kind": []}): + if String(h.get("kind", "")) != "yield_mult": + continue + pos_m += int(h.get("mult", 0)) + neg_m += int(h.get("cost_mult", 0)) + if int(h.get("cap", 0)) > 0: + combo_cap = int(h.cap) + check(pos_m == 85 and neg_m == 125 and combo_cap == 5, + "开格流订阅:正向乘区 25+10+50=85% · 负向加重 25+100=125% · 直觉连击上限 5(dig_reveal yield_mult 乘法族三件同持收集)") + var dd7: Array = orch._fx_emit("dig_paid", {"ap_cost": 1, "ap_surplus": 1}) + check(orch._hits_of(dd7, "counter_spend").size() == 1 + and bool((orch._hits_of(dd7, "counter_spend")[0] as Dictionary).has("fallback")), + "开格流订阅:深挖钻头 dig_paid→counter_spend 对价(fallback 降级声明——AP 不足只付基础 1)") + var re7: Array = orch._fx_emit("round_end", {"round": 1}) + check(orch._hits_of(re7, "grant_ap").size() == 1 + and String((orch._hits_of(re7, "grant_ap")[0] as Dictionary).get("mode", "")) == "transfer", + "蓄力流订阅:整备军备 round_end→grant_ap transfer(剩 AP 转蓄力层——finish_round_stop 定夺)") + var ab7: Array = orch._hits_of(orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}), "attack_bonus") + check(ab7.size() == 1 and String((ab7[0] as Dictionary).get("per", "")) == "charge_layers", + "蓄力流订阅:整备军备 attack_commit→attack_bonus per charge_layers(每层下轮首段 +1)") + orch.action_economy._charge_layers = 2 + var sp7: Array = orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "normal_vs_light", "dig_streak": 0, "nest_count": 0}) + var spike7_off: Array = orch._hits_of(sp7, "modify_ticket") + var spike_off_n := 0 + for h in spike7_off: + if String((h as Dictionary).get("mode", "")) == "spike": + spike_off_n += 1 + check(spike_off_n == 0, + "蓄力流订阅:厚积薄发未满层(charge_layers 2<3)→零命中(when 封闭判定——满层持续状态口径)") + orch.action_economy._charge_layers = 3 + var sp7b: Array = orch._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "normal_vs_light", "dig_streak": 0, "nest_count": 0}) + var spike7: Array = [] + for h in orch._hits_of(sp7b, "modify_ticket"): + if String((h as Dictionary).get("mode", "")) == "spike": + spike7.append(h) + check(spike7.size() == 1 and int((spike7[0] as Dictionary).get("value", -1)) == 2, + "蓄力流订阅:厚积薄发满层→modify spike ×2(discount 恒全额+基值倍乘)") + orch.action_economy._charge_layers = 0 + check(orch._hits_of(orch._fx_emit("damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1}), "shield").size() == 1, + "蓄力流订阅:铁壁整备 damage_taken→shield per charge_layers(蓄力转减伤)") # ⑦c 特性持有呈现段(2026-10-04 用户反馈补齐——C 区「特性」节:聚合行+成型档位;空态隐藏) var panel = load("res://presentation/views/hero_info_panel_view.gd").new() panel.set_feat_holdings([]) @@ -778,30 +846,46 @@ func _smoke_feats() -> void: panel.set_feat_holdings(["魔法 ×2 · 档一", "开格 ×1 · 散件"]) check(panel._feat_section.visible and panel._feat_list.get_child_count() == 2, "特性段持有呈现(聚合行+成型档位常驻可读——「局内奖励选了什么看不到」缺口承载)") - # ⑦b 深挖钻头付费开掘回归(2026-10-04 试玩缺陷修复——对价挂付费侧):AP=1 降级照常开格 - # (用户实测「AP=1 无法开格只能标旗」的回归闸);AP=2 付全价积付费计数(深层产出配额取数面)。 + check(String(load("res://presentation/battle_narrator.gd").feat_card_text({"feat_id": "arcane_ember", "display_name": "奥术余烬", "archetype": "magic", "desc": "普攻附加必中伤害"})).contains("普攻附加必中伤害"), + "特性卡文案表值化:desc 行字段直读(T3 文案入表——零硬编码映射)") + # ⑦b 深挖钻头付费开掘回归(2026-10-04 试玩缺陷修复口径,T3 对价面换代=dig_paid 订阅施加): + # AP=1 降级照常开格(fallback 只付基础 1 不积计数);AP=2 付全价积付费计数(深层产出配额取数面)。 var ae_dd = load("res://core/action_economy.gd").new(_db) - ae_dd.feat_dig_extra = 1 + var fx_dd = EffectEngine.new(_db) + fx_dd.rebuild(["deep_drill"], [], []) + ae_dd.set_fx(fx_dd) ae_dd._phase = 1 if ae_dd.Phase.ACTIVE == 1 else ae_dd.Phase.ACTIVE # white-box 相位(冒烟专用) ae_dd._ap = 1 - check(ae_dd.request_dig(0, false) and ae_dd.deep_paid_count == 0, - "钻头降级:AP=1 照常开格(只付基础 1 不积付费——持有钻头永不锁死挖格,散件保底 B-2)") + check(ae_dd.request_dig(0, false) and fx_dd.counter("deep_paid_round") == 0, + "钻头降级:AP=1 照常开格(fallback 只付基础 1 不积付费——持有钻头永不锁死挖格,散件保底 B-2)") ae_dd._ap = 2 ae_dd._phase = ae_dd.Phase.ACTIVE # 首挖 AP 归 0 已翻轮(ap_exhausted 仲裁)——白盒复位续测 - check(ae_dd.request_dig(0, false) and ae_dd.deep_paid_count == 1, - "钻头付费开掘:AP=2 付全价(基础 1+对价 1——deep_paid_count 积数=深层产出配额取数面)") - # ⑧ T6 吞噬流求值面(27 件全量——七流派档位成型) + check(ae_dd.request_dig(0, false) and fx_dd.counter("deep_paid_round") == 1, + "钻头付费开掘:AP=2 付全价(基础 1+对价 1——deep_paid_round 计数器=授予段追加产出配额取数面)") + # ⑧ 吞噬流订阅面(27 件全量——七流派档位成型) var ids27: Array = ids23.duplicate() ids27.append_array(["ley_harvest", "thorn_weaver", "purge_sapper", "magma_body"]) - orch._feat_flags_for(ids27) - var rf27: Dictionary = orch._damage.feat_flags - check(int(rf27.get("terrain_refund", -1)) == 1 and int(rf27.get("terrain_dig_double", -1)) == 1 - and int(rf27.get("flag_neutralize", -1)) == 1 and int(rf27.get("variant_counter_value", -1)) == 1, - "吞噬流求值:地脉采撷补偿 1 · 荆棘编织双产出 · 净化工兵中和旗(🔴 仅伤害型——过滤在消费面)· 熔火反噬 1/层") + orch._fx.rebuild(ids27, [], []) + var dt8: Array = orch._fx_emit("damage_taken", {"kind": "terrain", "amount": 3, "source": "thorn_field", "round": 1, "terrain_kind": "thorn_field"}) + check(orch._hits_of(dt8, "grant_material").size() == 2 and orch._hits_of(dt8, "counter_add").size() == 1, + "吞噬流订阅:地脉采撷+荆棘编织 damage_taken(地形伤)→grant_material ×2 · 熔火之躯→counter_add 积层") + check(orch._hits_of(orch._fx_emit("flag_hit", {"cell": Vector2i(1, 1), "round": 1, "terrain_kind": ["thorn_field"]}), "rewrite_board").size() == 1 + and orch._hits_of(orch._fx_emit("flag_hit", {"cell": Vector2i(1, 1), "round": 1, "terrain_kind": []}), "rewrite_board").is_empty(), + "吞噬流订阅:净化工兵 flag_hit 邻伤害型地形→rewrite_board · 无伤害型邻格零命中(🔴 仅伤害型——terrain_kind has 在场判定)") + orch._fx.counter_add("magma_layers", 2, false) + var re8: Array = orch._fx_emit("round_end", {"round": 1}) + check(orch._hits_of(re8, "open_ticket").size() == 1 + and int((orch._hits_of(re8, "open_ticket")[0] as Dictionary).get("value", -1)) == 1 + and String((orch._hits_of(re8, "open_ticket")[0] as Dictionary).get("per", "")) == "magma_layers", + "吞噬流订阅:熔火之躯 round_end→open_ticket per magma_layers(受地形伤积层轮末反打)") + check(orch._hits_of(re8, "counter_add").size() == 1 + and (orch._hits_of(re8, "counter_add")[0] as Dictionary).has("set"), + "吞噬流订阅:熔火之躯反打后积层清零(counter_add set——即时执行族按声明序)") var orch0 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(74)) - orch0._feat_flags_for([]) - check((orch0._damage.feat_flags as Dictionary).is_empty(), - "零持有求值=零旗(relic 关着零扰动——伤害管线既有行为逐位不变)") + orch0._fx.rebuild([], [], []) + check(orch0._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}).is_empty() + and orch0._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": true, "terrain_kind": []}).is_empty(), + "零持有订阅=零命中(relic 关着零扰动——伤害管线既有行为逐位不变)") # 手工场景驱动:L1 全程 + L2 推进到商店房(_drive_level 复用 + 兑付确认/选路/大奖励确认手工编排) @@ -1458,10 +1542,10 @@ func _smoke_global_variant() -> void: var base_sum: int = eco._pool_weight(base_rows) check(base_sum == 77, "偏斜基线:正向池现行合计 77(挖掘风险批口径)") eco.board_skew = 0 - var off_rows: Array = eco._biased_cell_rows() + var off_rows: Array = eco._biased_cell_rows(0) # 内容数据驱动扩展批 T4:偏运权重改 reward_roll 订阅命中入参(luck_bias 注入位退役) check(off_rows.size() == base_rows.size() and _rows_weights_equal(off_rows, base_rows), "off 态零扰动:board_skew=0 行集与基线逐位相同(不进投影域)") eco.board_skew = 30 - var on_rows: Array = eco._biased_cell_rows() + var on_rows: Array = eco._biased_cell_rows(0) check(eco._pool_weight(on_rows) == 77, "合计守恒:偏斜后正向池合计 = 77(整数最大余数重归一)") var wmap := _rows_weight_map(on_rows) check(wmap.get("weaken", -1) == 8 and wmap.get("ap_up", -1) == 6 and wmap.get("gp_coin", -1) == 2, @@ -1470,16 +1554,14 @@ func _smoke_global_variant() -> void: check(wmap.get("attack_up", -1) == 15 and wmap.get("spark_aoe", -1) == 18 and wmap.get("pierce_strike", -1) == 9 and wmap.get("magic_bolt", -1) == 9, "进攻族上浮:attack 14→15 / blast 16→18 / pierce 8→9 / magic 8→9(v=30 构造例,等比上浮守恒)") eco.board_skew = 30 - eco.luck_bias = 2 - var combo_rows: Array = eco._biased_cell_rows() + var combo_rows: Array = eco._biased_cell_rows(2) # 件级加算 2=偏运硬币行 value(reward_roll bias_rewards 命中面同值) var cw := _rows_weight_map(combo_rows) check(eco._pool_weight(combo_rows) == 77 + 4 * 2, "合成序:图级偏斜先(乘法面)件级 luck_bias 后(加法 +2×四稀有类)——合计 77+8") check(cw.get("heal_minor", -1) == 5 and cw.get("gp_coin", -1) == 4 and cw.get("pierce_strike", -1) == 11 and cw.get("magic_bolt", -1) == 11, "合成序构造例:skew(heal 3)+2=5 / gp 2+2=4 / pierce 9+2=11 / magic 9+2=11(图级定型后玩家构筑叠加)") - eco.luck_bias = 0 eco.board_skew = 50 - var v50: Dictionary = _rows_weight_map(eco._biased_cell_rows()) - check(eco._pool_weight(eco._biased_cell_rows()) == 77 and int(v50.get("heal_minor", -1)) > 0, "域上界 v=50:守恒成立且逐行权重恒正(域 [0,50] 安全)") + var v50: Dictionary = _rows_weight_map(eco._biased_cell_rows(0)) + check(eco._pool_weight(eco._biased_cell_rows(0)) == 77 and int(v50.get("heal_minor", -1)) > 0, "域上界 v=50:守恒成立且逐行权重恒正(域 [0,50] 安全)") # 普通图摇号全链路确定性(off 路径)——同种子普通图资源映射逐位相同(golden 对拍另有批前快照) var nrng = load("res://core/rng_service.gd").new() nrng.set_master_seed(42) @@ -1665,9 +1747,9 @@ func _smoke_full_battle(L: int, D: int, label: String) -> void: if not (row.has(atk_t) and row.has("roll") and row.has("ac") and row.has("tier")): hero_breakdown_ok = false check(has_verdict, "%s 步骤流含判定行" % label) - # 伤害道具批(2026-09-18):单弱敌遭遇可被挖掘期兑付(穿刺弹/魔法矢/爆裂粉尘)在授予段单独击杀—— - # 敌全灭后战斗段整段跳过、零先手行为合法结局;先手规则本身由强敌场景(L1/L2 深节点)继续守护。 - check(hero_attacks >= 1 or run1.terminated, "%s 主角先手攻击行存在或授予段终局(2026-09-11 拍板① + 伤害道具批)×%d" % [label, hero_attacks]) + # 案C(2026-10-06 用户裁):授予段不再产出伤害——进入战斗段时主角恒存活且场上有敌, + # 先手必兑;旧「挖掘期兑付可在授予段单独击杀、零先手合法」结局随地图账迁战斗段末整体消失。 + check(hero_attacks >= 1, "%s 主角先手攻击行必存在(2026-09-11 拍板① + 案C 地图账殿后)×%d" % [label, hero_attacks]) check(hero_breakdown_ok, "%s 主角攻击行携带类型键与掷点读数(单分量——充能退役批)" % label) @@ -2784,6 +2866,32 @@ func _smoke_settlement_segments() -> void: if decays != 1 or verdicts != 1: decay_verdict_ok = false # 衰减/判定每结算恰一次 check(order_ok, "每轮行相位单调(授予→增益/减益→战斗→衰减→判定)") + # 案C 段内序(2026-10-06 用户裁):段内伤害序 = 主角先手 →〔连珠矢随先手段〕→ 敌方逐个 → 地图账殿后。 + # 地图账 = 全部棋盘来源伤害行 + 主角倒下不兑留痕行;违序(先于先手或插进敌方还手中间)即失败。 + var mapdmg_ok := true + for ri_key in by_round.keys(): + var rows_r: Array = by_round[ri_key] + var first_hero := -1 + var last_enemy := -1 + for i in rows_r.size(): + var rw: Dictionary = rows_r[i] + if String(rw.get("type", "")) != "damage": + continue + if String(rw.get("source_class", "")) == "hero_attack" and first_hero < 0: + first_hero = i + elif String(rw.get("source_class", "")) == "enemy_action": + last_enemy = i + for i in rows_r.size(): + var rw: Dictionary = rows_r[i] + var sc := String(rw.get("source_class", "")) + var is_board_acct: bool = (String(rw.get("type", "")) == "damage" \ + and not ["hero_attack", "enemy_action", "feat_chain_arrows"].has(sc)) \ + or String(rw.get("forfeit", "")) == "hero_down" + if not is_board_acct: + continue + if (first_hero >= 0 and i < first_hero) or (last_enemy >= 0 and i < last_enemy): + mapdmg_ok = false + check(mapdmg_ok, "案C 段内序:地图伤害账殿后(主角先手→敌方逐个→地图账;连珠矢随先手段)") check(decay_verdict_ok, "衰减/判定段每结算恰一段(先兑付后衰减——含末轮)") @@ -2797,9 +2905,9 @@ func _row_phase(row: Dictionary, ri: int) -> int: "cast": return 2 "damage": - if String(row.get("source_class", "")) in ["hero_attack", "enemy_action"]: - return 2 - return 0 # 授予段 flat 兑付(爆裂/直伤) + return 2 # 案C(2026-10-06 用户裁):全部伤害兑付=战斗段(主角先手/敌方还手/地图账殿后统一结账) + "reward_ineffective": + return 2 if String(row.get("forfeit", "")) == "hero_down" else 0 # 战斗段末地图账不兑留痕 vs 授予段未生效 match String(row.get("source_class", "")): "safe_cell", "mine_hit", "flag_hit", "flag_miss", "clear_reward": if int(row.get("round_index", ri)) < ri: @@ -2887,7 +2995,7 @@ func _battle_st(policy: String) -> Dictionary: # ==================== 敌人技能池批(2026-09-16 用户拍板——开轮摇号出招系统) ==================== -# 六项验收断言(docs/proposal-skill-pool.md §8 批 1):同种子回放逐位复现 / 单敌轨迹独立复放 / +# 六项验收断言(proposal-skill-pool.md〔提案已吸收退役 git a6833b6a——现行事实源=敌人系统.md 技能池节〕 §8 批 1):同种子回放逐位复现 / 单敌轨迹独立复放 / # EV 抽样对锚 / 死亡打断作废 / 空池不可能 / 加载校验拒绝。 func _smoke_skill_pool() -> void: @@ -4086,6 +4194,82 @@ func _smoke_meta() -> void: _meta_pricing_and_buy() _meta_settle_and_workshop() _meta_injection() + _pause_overlay_projection() + + +# ---- 暂停层只读投影一致性(迭代 17 卡③——R4 数据只读同源:暂停层展示值逐项 == MetaProgression +# 访问器读数;投影拼装与 main._pause_data 同构(同访问器族同序)。只读性连接巡检与零动效 +# 断言归 ui_struct_probe 呈现侧,本段只管数据面)---- +func _pause_overlay_projection() -> void: + print("--- 暂停层·只读投影一致性(隔离档 smoke_meta_buy 复用)---") + _meta_smoke_cleanup() + var sv = _meta_new_save(_META_SMOKE_PATHS[0]) + var mp = load("res://core/meta_progression.gd").new(_db, sv) + # 已购态夹具:余额/锻造两档非零 + 工坊首件解锁——投影可辨非全零白板 + sv.add_keystone(37) + sv.set_forge_level("atk", 2) + sv.set_forge_level("max_hp", 1) + var ws_meta: Array = _db.meta_workshop_items() + if not ws_meta.is_empty(): + sv.unlock_item(String(ws_meta[0].get("item_id", ""))) + sv.write_to_disk() + # 投影拼装(main._pause_data 同构)+ 视图喂入 + var data := {"balance": mp.keystone_balance(), "forge": [], "workshop": mp.workshop_status(), + "tree": mp.tree_camp_rows(), "fork_groups": mp.tree_fork_groups()} + for attr in MetaTable.FORGE_ATTRS: + var frow: Dictionary = _db.meta_forge_row(attr) + data["forge"].append({"attr": attr, "display_name": String(frow.get("display_name", "")), + "level": mp.forge_level(attr), "flat": int(frow.get("flat_value", 0))}) + var po = load("res://presentation/views/pause_overlay.gd").new() + po.open_pause(data) + check(po.visible, "暂停层开层可见(open_pause 直开——P4 甲零动效路径)") + check(String(po._balance_badge.value_label.text) == "钥石 %d" % mp.keystone_balance(), + "余额徽投影 = keystone_balance() 读数(%s)" % po._balance_badge.value_label.text) + var forge_ok: bool = po._forge_holder.get_child_count() == MetaTable.FORGE_ATTRS.size() + var fi := 0 + for attr in MetaTable.FORGE_ATTRS: + if fi >= po._forge_holder.get_child_count(): + forge_ok = false + break + var lbls: Array = [] + for ch in po._forge_holder.get_child(fi).get_children(): + if ch is Label: + lbls.append(ch) + forge_ok = forge_ok and lbls.size() == 2 \ + and String(lbls[0].text).begins_with(String(_db.meta_forge_row(attr).get("display_name", ""))) \ + and String(lbls[1].text) == "Lv %d" % mp.forge_level(attr) + fi += 1 + check(forge_ok, "锻造三行投影逐行 = display_name + forge_level() 读数(已购夹具 atk Lv2 / max_hp Lv1 在列)") + var ws: Array = mp.workshop_status() + var ws_ok: bool = po._workshop_holder.get_child_count() == ws.size() + for i in mini(ws.size(), po._workshop_holder.get_child_count()): + var row: Dictionary = ws[i] + var lbls2: Array = [] + for ch in po._workshop_holder.get_child(i).get_children(): + if ch is Label: + lbls2.append(ch) + var unlocked: bool = bool(row.get("unlocked", false)) + var expect_st := "未解锁" + if bool(row.get("retired", false)): + expect_st = "已退役" + elif bool(row.get("deferred", false)) and unlocked: + expect_st = "已解锁 · 能力封存中" + elif unlocked: + expect_st = "已解锁" + ws_ok = ws_ok and lbls2.size() == 3 \ + and String(lbls2[0].text) == String(row.get("display_name", "")) \ + and String(lbls2[1].text) == "累计通关 %d/%d 次" % [int(row.get("progress", 0)), int(row.get("threshold", 0))] \ + and String(lbls2[2].text) == expect_st + check(ws_ok, "工坊六行投影 = workshop_status() 逐项(名称/判定量进度/解锁态——首件解锁夹具在列)") + var owned := 0 + for trow in (mp.tree_camp_rows() as Array): + if int(trow.get("level", 0)) > 0: + owned += 1 + var tree_txt := String(po._tree_label.text) + check(tree_txt == "尚未购买任何节点(天赋树在营地柜台三)" or tree_txt.begins_with("已购 %d 项:" % owned), + "天赋树概览聚合文案与已购节点数一致(零已购=空态句)") + po.visible = false + po.free() # 冒烟隔离存档名册(段首清陈旧 + 段尾清理——契约:测试不污染 user://save.json 真实档) diff --git a/game/smoke/ui_audit_probe.gd b/game/smoke/ui_audit_probe.gd new file mode 100644 index 00000000..4ed59fb4 --- /dev/null +++ b/game/smoke/ui_audit_probe.gd @@ -0,0 +1,161 @@ +# VERTICAL SLICE - NOT FOR PRODUCTION +# 全屏 UI 审计截图探针(2026-10-06 用户令——实机每一屏 vs demo v2.1 逐屏对比;shot_probe 十二屏扩展形)。 +# 必须**窗口化**运行(真渲染面;headless 守卫同 shot_probe): +# Godot_console.exe --path . res://smoke/ui_audit_probe.tscn -- <输出目录> +# 覆盖 = demo 八映射屏(start/camp/settings/map/battle/bonus/shop/end 胜败两态)+ demo 无映射三屏 +# (legend 图例/confirm 确认框/debug 调试浮窗)。真实信号路径驱动 + 合成面开窗(ui_struct_probe 同款 +# 真实 open API+拟真数据);双帧 frame_post_draw 同步抓视口纹理。 +extends Node + +const ProbeUiUtil := preload("res://smoke/probe_ui_util.gd") +const Narrator := preload("res://presentation/battle_narrator.gd") + +var main: Node +var out_dir := "" +var _problems: Array = [] + + +func _ready() -> void: + if DisplayServer.get_name() == "headless": + print("[ui_audit] FAIL — 本探针必须窗口化运行(headless 无渲染面)") + get_tree().quit(1) + return + var args := OS.get_cmdline_user_args() + out_dir = args[0] if args.size() >= 1 and args[0] != "" else "user://ui-audit" + DirAccess.make_dir_recursive_absolute(out_dir) + await get_tree().process_frame + main = load("res://presentation/main.tscn").instantiate() + add_child(main) + await get_tree().process_frame + ProbeUiUtil.isolate_save(main, "smoke_ui_audit", true, true, false) + await get_tree().process_frame + # 断点种子(2026-10-06 判官 checkpoint——demo 标题屏=有档态「继续上局」主钮亮出;无种子= + # 续玩钮隐藏,主操作语义对齐⑤无从呈现。最小合法断点快照;后续「开始冒险」流程自然覆盖) + main.save_manager.set_breakpoint({ + "master_seed": 42, "rng_counter": 0, "hero_hp": 20, "gp": 24, "keystone": 4, + "level_index": 1, "bonus": {}, "shop_purchased": {}, "relics": [], + "bonus_aux_carry": [], "keystone_drops": [], "bonus_taken": {}, + }) + main._refresh_resume_button() + await get_tree().process_frame + + # 1 标题屏(启动态=有档态) + await _snap("1_start") + # 2 调试浮窗(P3 仅开始菜单可开——趁 _level_active=false 先截) + main._show_debug_overlay() + await _settle() + await _snap("9_debug") + main._close_debug_overlay() + await _settle() + # 3 设置 + main._open_settings() + await _settle() + await _snap("3_settings") + main._on_settings_closed() + await _settle() + # 4 营地 + var campbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "营地", "contains") + if campbtn != null: + campbtn.pressed.emit() + await _settle() + await _snap("2_camp") + var close_btn: Button = ProbeUiUtil.find_button(main.camp_overlay, "关闭营地", "contains") + if close_btn != null: + close_btn.pressed.emit() + await _settle() + # 5 关卡地图(开始冒险→停点出图) + # 收种子断点(有断点时「开始冒险」=清档确认互斥弹窗——不收则断流,5_battle 截不上) + main.save_manager.clear_breakpoint() + main._refresh_resume_button() + await get_tree().process_frame + var lvbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "开始冒险", "contains") + if lvbtn != null: + lvbtn.pressed.emit() + for i in 4: + await get_tree().process_frame + await _settle() + await _snap("4_map") + # 6 战斗(真实选路提交) + if main.level_screen != null and main.level_screen.visible: + main.level_screen.press_candidate(0) + for i in 10: + await get_tree().process_frame + await _settle() + await _snap("5_battle") + # 7 战斗结算·胜利(真实签名的拟真载荷——main.gd:697 同序) + main._end_overlay.open_victory(Narrator.battle_end_text("victory", true), "返回", + main.board_view.get_global_rect(), "战利品 GP +5 · 合计 24", + "钥石 · 累计 27", "本关加成:攻击 +1", 5, 24) + await _settle() + await _snap("8_end_victory") + main._end_overlay.close() + await _settle() + # 8 战斗结算·失败 + main._end_overlay.open_defeat(Narrator.battle_end_text("defeat", false), "返回", + main.board_view.get_global_rect()) + await _settle() + await _snap("8_end_defeat") + main._end_overlay.close() + await _settle() + # 9 三选一(拟真三卡) + main.curio_overlay.open_pick(Narrator.node_curio_header(), [ + {"id": "heal_rare", "text": "回血\n恢复 3 点"}, + {"id": "blast_rare", "text": "爆裂轰击\n下一击 +5"}, + {"id": "pierce_rare", "text": "穿甲\n无视护甲"}, + ]) + await _settle() + await _snap("6_bonus") + main.curio_overlay.visible = false + await _settle() + # 10 商店(买前态——pool 货架面=玩家正主形态,demo #v-shop 对比基线;T6 全屏页) + main.shop_overlay.open_shelf([ + {"kind_id": "attack", "display_name": "攻击强化", "value": 1, "price": 12}, + {"kind_id": "max_hp", "display_name": "生命强化", "value": 3, "price": 12}, + {"kind_id": "armor", "display_name": "护甲片", "value": 1, "price": 15}, + ], 27) + main.shop_overlay.set_visit_info(0, 1.8) # 爬价规则行(T6——presenter 同款投喂) + await _settle() + await _snap("7_shop") + main.shop_overlay.visible = false + await _settle() + # 11 图例 + main.legend_overlay.open() + await _settle() + await _snap("10_legend") + main.legend_overlay.close() + await _settle() + # 12 确认框(放弃二次确认实文案) + main.confirm_dialog.open("放弃冒险", "确认放弃本次冒险?已入账钥匙与 GP 将结算保留") + await _settle() + await _snap("11_confirm") + main.confirm_dialog.close() + await _settle() + + for s in ["1_start", "9_debug", "3_settings", "2_camp", "4_map", "5_battle", + "8_end_victory", "8_end_defeat", "6_bonus", "7_shop", "10_legend", "11_confirm"]: + var p := out_dir.path_join("real_%s.png" % s) + if not FileAccess.file_exists(p): + _problems.append("截图缺失: %s" % s) + else: + var fa := FileAccess.open(p, FileAccess.READ) + if fa == null or fa.get_length() < 1000: + _problems.append("截图疑似空文件: %s" % s) + print("[ui_audit] 完成 out=%s 问题=%d %s" % [out_dir, _problems.size(), str(_problems)]) + get_tree().quit(1 if _problems.size() > 0 else 0) + + +func _settle() -> void: + for i in 3: + await get_tree().process_frame + await RenderingServer.frame_post_draw + + +func _snap(key: String) -> void: + await RenderingServer.frame_post_draw + await RenderingServer.frame_post_draw + var img: Image = get_viewport().get_texture().get_image() + if img == null: + _problems.append("视口纹理为空: %s" % key) + return + img.save_png(out_dir.path_join("real_%s.png" % key)) + print("[ui_audit] saved %s (%dx%d)" % [key, img.get_width(), img.get_height()]) diff --git a/game/smoke/ui_audit_probe.gd.uid b/game/smoke/ui_audit_probe.gd.uid new file mode 100644 index 00000000..d90c0c00 --- /dev/null +++ b/game/smoke/ui_audit_probe.gd.uid @@ -0,0 +1 @@ +uid://ddqhkbsad4wno diff --git a/game/smoke/ui_audit_probe.tscn b/game/smoke/ui_audit_probe.tscn new file mode 100644 index 00000000..69689221 --- /dev/null +++ b/game/smoke/ui_audit_probe.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://smoke/ui_audit_probe.gd" id="1"] + +[node name="UIAuditProbe" type="Node"] +script = ExtResource("1") diff --git a/game/smoke/ui_click_probe.gd b/game/smoke/ui_click_probe.gd index ceb060c1..99b315c9 100644 --- a/game/smoke/ui_click_probe.gd +++ b/game/smoke/ui_click_probe.gd @@ -27,6 +27,8 @@ var _tested_review := false var _tested_end_review := false # 迭代 6 T7——终局面板回看入口(z 序修复回归) var _tested_refresh_chain := false var _tested_tooltip := false +var _tested_pause := false # 迭代 17 卡③——暂停层角标开合/守卫断言(每 run 一次) +var _tested_map_pause := false # 迭代 17 卡③——地图屏角标一次性 case(选路态零扰动) var _inter_confirms := 0 # 大奖励屏确认计数(冒险推进停点——首见确认推进 L2,再见 = L2 通关收口) var _settlement_confirms := 0 # 卡⑥ 胜利结算框确认计数(观测面——每场普通战斗胜利恰一) var _route_clicks := 0 # 选路覆盖层点击计数(岔路使用率观测面) @@ -220,6 +222,22 @@ func _ready() -> void: continue if main.level_screen.visible: # 节点图选路口(零自动推进停点)——图上命中路径交替选候选(岔路使用行为;布局决策 2 改造) + # 迭代 17 卡③——地图屏角标一次性 case:选路前经角标开/关暂停层(层在地图屏之上开合, + # 关闭后地图屏选路态零扰动——P2 甲层级语义) + if not _tested_map_pause and not main.pause_overlay.visible: + _tested_map_pause = true + var mpb: Button = main.level_screen.pause_badge + if mpb == null or not mpb.visible or mpb.disabled: + _problems.append("地图屏暂停角标缺失/未点亮/被置灰(P1 丁)") + else: + mpb.pressed.emit() + if not main.pause_overlay.visible or not main.level_screen.visible: + _problems.append("地图屏角标开暂停层失败或地图屏被误藏(P2 甲层级失守)") + var rbtn2: Button = ProbeUiUtil.find_button(main.pause_overlay, "继续", "prefix") + if rbtn2 != null: + rbtn2.pressed.emit() + if main.pause_overlay.visible or not main.level_screen.visible: + _problems.append("暂停层关闭后地图屏态失守(恢复语义破)") main.level_screen.press_candidate(_route_clicks % 2) _route_clicks += 1 _flags_placed.clear() @@ -249,7 +267,7 @@ func _ready() -> void: # 卡⑥ 战斗胜利总结结算框(真实信号路径——每场普通胜利到达):断言已拍板面后点「下一节点」 if not main._input_locked(): _problems.append("胜利结算框打开时输入未锁定") - var gp_line: String = main._end_overlay._gp_row.text + var gp_line: String = "%s %s" % [main._end_overlay._gp_name.text, main._end_overlay._gp_val.text] # 两列账行(审计差距④)——名/值列拼回整串断言 if not gp_line.contains("GP +%d" % main._last_gain_amount) or not gp_line.contains("合计 %d" % int(main.runner.gp)): _problems.append("胜利结算框 GP 增量/总量与钱包实态不符(%s|实态 +%d/%d)" % [gp_line, main._last_gain_amount, int(main.runner.gp)]) if main._end_overlay.verdict_text() == "": @@ -479,6 +497,39 @@ func _run_modal_probes_once() -> void: if main.legend_overlay.is_open() or main._input_locked(): _problems.append("图例关闭后未恢复输入/可见性") # ②/②b 明细回看模态探针退役(M9/P-C7——冒险中回看归「生涯」,06 登记) + # ⑤ 暂停层(迭代 17 卡③——P1 丁角标真实信号路径 + P2 甲模态语义 + P1 守卫 + R3 锁恢复): + # 角标打开 → 只读层可见+输入锁定+余额徽非空 → 「继续」关闭恢复;图例开着 → 角标置灰且点击被守卫兜住 + if not _tested_pause and not main.legend_overlay.is_open() and not main.pause_overlay.visible: + _tested_pause = true + var pbtn: Button = main.board_zone.pause_badge + if pbtn == null or not pbtn.visible or pbtn.disabled: + _problems.append("战斗屏暂停角标缺失/未点亮/被置灰(P1 丁入口失守 visible=%s disabled=%s)" + % [str(pbtn != null and pbtn.visible), str(pbtn != null and pbtn.disabled)]) + else: + pbtn.pressed.emit() # 真实按钮信号路径 → 组合根 _on_pause_badge_pressed + if not main.pause_overlay.visible: + _problems.append("暂停层未随角标打开(组合根接线失守)") + if not main._input_locked(): + _problems.append("暂停层打开时输入未锁定(R3)") + var bal_txt: String = main.pause_overlay._balance_badge.value_label.text + if not bal_txt.begins_with("钥石 "): + _problems.append("暂停层余额徽投影缺失(%s)" % bal_txt) + var resume_btn: Button = ProbeUiUtil.find_button(main.pause_overlay, "继续", "prefix") + if resume_btn == null: + _problems.append("暂停层关闭钮缺失(「继续」)") + else: + resume_btn.pressed.emit() + if main.pause_overlay.visible or main._input_locked(): + _problems.append("暂停层关闭后未恢复(visible=%s locked=%s——R3 残留)" + % [str(main.pause_overlay.visible), str(main._input_locked())]) + # P1 守卫:图例开着 → 两处入口角标置灰;置灰态点击被处理器兜住(探针 emit 绕过 disabled) + main.ap_bar._btn_legend.pressed.emit() + if not main.board_zone.pause_badge.disabled: + _problems.append("图例开着时战斗屏暂停角标未置灰(P1 守卫失守)") + main.board_zone.pause_badge.pressed.emit() + if main.pause_overlay.visible: + _problems.append("置灰态仍可开暂停层(守卫兜底失守)") + main.legend_overlay.close() # ③ tooltip(状态效果槽悬停——来源+语义+剩余):有槽可见时触发 if not _tested_tooltip: var row = main.stage.hero_standee.slot_row # 状态 tooltip 载体 2026-09-26 迁 B 区站桩 diff --git a/game/smoke/ui_contrast_probe.gd b/game/smoke/ui_contrast_probe.gd index 866c8662..34c5141a 100644 --- a/game/smoke/ui_contrast_probe.gd +++ b/game/smoke/ui_contrast_probe.gd @@ -28,6 +28,7 @@ func _pairs() -> Array: ["锚·正文墨/数据板", UiKit.INK, UiKit.PANEL_DATA, 1.0], # 全站锚点:正文墨对数据板 ["锚·暖白/屏根", UiKit.WARM, UiKit.BG_ROOT, 1.0], # 全站锚点:暗底正文 ["锚·暖白弱/舞台底", UiKit.WARM_FAINT, UiKit.STAGE_BG, 1.0], # 全站锚点:B区辅助墨 + ["钥石徽·深墨/烛光黄", UiKit.INK_ON_GOLD, UiKit.CANDLE, 1.0], # 迭代17卡③——badge 工厂件文字(营地徽+暂停层余额行同形制) ] diff --git a/game/smoke/ui_input_probe.gd b/game/smoke/ui_input_probe.gd index cf3c5803..f4cba8e5 100644 --- a/game/smoke/ui_input_probe.gd +++ b/game/smoke/ui_input_probe.gd @@ -415,8 +415,61 @@ func _ready() -> void: _problems.append("case⑮ 提灯关闭后雾参数未复原(lantern 应=0)") if int(bv.cells_probe()[ring_cell].fog_state) == 0: _problems.append("case⑮ 提灯关闭后 dist==R+1 格未复原遮蔽") + # ---- case⑯ 暂停层输入锁语义(迭代 17 卡③ R3——开层锁棋盘、关层恢复无残留;真实 push_input + # 路径:入口角标本体 press+release 真点击 → 暂停层开;锁期挖掘点击零穿透;「继续」关层后 + # 同格挖掘重新到达——锁语义与既有模态同款,无状态残留。置于全 case 之尾:取格若需前沿移位 + #(真实点击 move 扩大可挖域),其板面扰动不再波及任何既有断言——域恰 2 枚时 flag_t 带旗+ + # dig_t 揭开后域内零无旗格,正是首跑实测踩中的形态)---- + var pause_dig := Vector2i(-9, -9) + for c in bv.diggable_probe(): + if not ms.has_flag(c): + pause_dig = c + break + if pause_dig.x == -9: + var mv16 := _pick_frontier(bv, ms) + if mv16.x == -9: + _problems.append("case⑯ 板面无可迫近边界(锁断言取格中断)") + else: + _push_click(_pos(bv, mv16), MOUSE_BUTTON_LEFT) + await get_tree().process_frame + await get_tree().process_frame + for c in bv.diggable_probe(): + if not ms.has_flag(c): + pause_dig = c + break + if pause_dig.x == -9: + _problems.append("case⑯ 无可挖无旗格(板面异常——锁断言中断)") + else: + var pbtn16: Button = main.board_zone.pause_badge + var bpos: Vector2 = pbtn16.get_global_rect().get_center() + _push_click(bpos, MOUSE_BUTTON_LEFT) + _push_release(bpos, MOUSE_BUTTON_LEFT) # Button pressed 信号走 release 沿——按压+释放成对 + await get_tree().process_frame + if not main.pause_overlay.visible or not main._input_locked(): + _problems.append("case⑯ 角标真点击未开暂停层(visible=%s locked=%s——入口可达性失守)" + % [str(main.pause_overlay.visible), str(main._input_locked())]) + else: + _push_click(_pos(bv, pause_dig), MOUSE_BUTTON_LEFT) + await get_tree().process_frame + await get_tree().process_frame + if _has_action(pause_dig, "dig") or ms.is_revealed(pause_dig): + _problems.append("case⑯ 暂停层开启时挖掘穿透(R3 输入锁破——目标=%s)" % pause_dig) + var resume_btn: Button = ProbeUiUtil.find_button(main.pause_overlay, "继续", "prefix") + if resume_btn == null: + _problems.append("case⑯ 暂停层关闭钮缺失") + else: + resume_btn.pressed.emit() + await get_tree().process_frame + if main.pause_overlay.visible or main._input_locked(): + _problems.append("case⑯ 关闭后未恢复(visible=%s locked=%s——R3 残留)" + % [str(main.pause_overlay.visible), str(main._input_locked())]) + _push_click(_pos(bv, pause_dig), MOUSE_BUTTON_LEFT) + await get_tree().process_frame + await get_tree().process_frame + if not _has_action(pause_dig, "dig") or not ms.is_revealed(pause_dig): + _problems.append("case⑯ 关闭后挖掘未到达(恢复无残留验证失守)") if _problems.is_empty(): - print("[ui_input_probe] PASS — 悬停金框/禁入信号/路径预览/flag_place/chord/dig/英雄落位/影子构成/朝向/缩格重排/缺位回退/变异角标/视野迷雾 十六断言全绿(真实 push_input 路径——⑫图上命中⑬画布拟合 2026-09-28 增,⑭变异角标 2026-09-29 增,④b 旗纹理槽回归 2026-10-02 增,⑮视野迷雾 2026-10-03 增〔+提灯接线断言 2026-10-05 迭代14⑤〕)") + print("[ui_input_probe] PASS — 悬停金框/禁入信号/路径预览/flag_place/chord/dig/英雄落位/影子构成/朝向/缩格重排/缺位回退/变异角标/视野迷雾/暂停层输入锁 十七断言全绿(真实 push_input 路径——⑫图上命中⑬画布拟合 2026-09-28 增,⑭变异角标 2026-09-29 增,④b 旗纹理槽回归 2026-10-02 增,⑮视野迷雾 2026-10-03 增〔+提灯接线断言 2026-10-05 迭代14⑤〕,⑯暂停层输入锁 2026-10-06 迭代17卡③增)") get_tree().quit(0) else: for p in _problems: @@ -493,6 +546,16 @@ func _push_click(pos: Vector2, btn: int) -> void: main.get_viewport().push_input(ev, true) +# 释放沿(case⑯——BaseButton pressed 信号默认 release 触发:角标真点击须按压+释放成对) +func _push_release(pos: Vector2, btn: int) -> void: + var ev := InputEventMouseButton.new() + ev.position = pos + ev.global_position = pos + ev.button_index = btn + ev.pressed = false + main.get_viewport().push_input(ev, true) + + func _pre_fail(why: String) -> void: push_error("[ui_input_probe] FAIL — " + why) print("[ui_input_probe] FAIL — " + why) diff --git a/game/smoke/ui_probe.gd b/game/smoke/ui_probe.gd index f09dab87..eed02c8a 100644 --- a/game/smoke/ui_probe.gd +++ b/game/smoke/ui_probe.gd @@ -222,8 +222,8 @@ func _ready() -> void: main.meta = meta_saved if not main._end_overlay.is_open(): problems.append("冒险终局面板未开(合成摘要路径)") - elif not main._end_overlay._gp_row.visible or not main._end_overlay._gp_row.text.contains("57"): - problems.append("终局面板 GP 行缺失或取数不对(%s)" % main._end_overlay._gp_row.text) + elif not main._end_overlay._gp_row.visible or not main._end_overlay._gp_val.text.contains("57"): + problems.append("终局面板 GP 行缺失或取数不对(%s)" % main._end_overlay._gp_val.text) main._end_overlay.close() main._close_modal(main._end_overlay) if problems.is_empty(): diff --git a/game/smoke/ui_struct_probe.gd b/game/smoke/ui_struct_probe.gd index 743775e2..370068a3 100644 --- a/game/smoke/ui_struct_probe.gd +++ b/game/smoke/ui_struct_probe.gd @@ -72,6 +72,19 @@ func _ready() -> void: _settle(6) _audit("battle_hud", [main.board_zone, main.info_panel, main.stage]) _standeefit_contract(main) + # ---- 暂停层(迭代 17 卡③):组合根真实路径开层→结构断言→关闭。附只读性契约 + #(R4——子树信号连接白名单巡检)与零动效契约(R5/P4 甲——零 AnimationPlayer/零 tween)。 + # 巡检信号名清单=子树全控件的 get_signal_list() 全集(不放行任何未列名信号——新连接 + # 一律红,须先入册审查);白名单={视图自身 _emit_closed, 组合根 _on_pause_closed}。 + main._open_pause_layer() + _settle() + await _audit("pause", [main.pause_overlay]) + _pause_readonly_contract(main) + _pause_zero_motion_contract(main) + main._on_pause_closed() + _settle() + if main.pause_overlay.visible: + problems.append("[pause] 关闭后层未隐藏(模态栈单口失守)") else: problems.append("[battle_hud] 候选中无战斗节点(驱动中断——候选=%s)" % str(main.level_screen._cand_ids)) @@ -164,6 +177,61 @@ func _pick_battle_node(main: Node) -> int: return -1 +# ⑧pause 只读性契约(迭代 17 卡③ R4):暂停层子树全控件 × 游戏语义信号连接巡检——清单外零连接。 +# 巡检信号名清单(任务书 v1.1「巡检信号名清单落码时列全进探针」)= 玩家交互可携带写意图的通道 +# 全集(按钮按下/拨杆/文本/滑值/选项/键盘焦点)+ 本层外发信号 closed;引擎内部布局生命线信号 +# 不计(child_order_changed/item_rect_changed/visibility_changed/size_flags_changed/ +# minimum/maximum_size_changed 等系 Godot 容器系统对每控件自动建立的脏标记连接,与写通路无关 +#——首跑实测踩中:全集巡检把布局生命线误判为非法连接)。清单外任何连接(尤其指向 +# 购买/解锁/写入路径的)一律红,须先入册审查再改清单。 +const _PAUSE_READONLY_SIGNALS: Array[String] = [ + "pressed", "button_down", "button_up", "toggled", + "text_changed", "text_submitted", "value_changed", "item_selected", + "gui_input", "focus_entered", "focus_exited", "closed", +] + + +func _pause_readonly_contract(main: Node) -> void: + var overlay: Control = main.pause_overlay + var stack: Array = [overlay] + var conn_count := 0 + while not stack.is_empty(): + var n: Node = stack.pop_back() + for sig_name in _PAUSE_READONLY_SIGNALS: + for conn in n.get_signal_connection_list(sig_name): + conn_count += 1 + var cb: Callable = conn["callable"] + var host: Object = cb.get_object() + var m := String(cb.get_method()) + if host == overlay and m == "_emit_closed": + continue + if host == main and m == "_on_pause_closed": + continue + problems.append("[pause][readonly] 非法信号连接 %s.%s → %s.%s(零写通路巡检——白名单=视图 _emit_closed/组合根 _on_pause_closed)" + % [n.name, sig_name, host, m]) + for ch in n.get_children(): + stack.append(ch) + if conn_count != 2: + problems.append("[pause][readonly] 信号连接数=%d 应=2(关闭钮 pressed+closed 外发——多连接=写通路嫌疑)" % conn_count) + + +# ⑨pause 零动效契约(迭代 17 卡③ R5/P4 甲——零动效直开直关路径存在断言): +# 子树零 AnimationPlayer + 视图源码零 tween/动效包装调用(源码 token 扫描=ui_contrast 静态段同手法) +func _pause_zero_motion_contract(main: Node) -> void: + var overlay: Control = main.pause_overlay + var stack: Array = [overlay] + while not stack.is_empty(): + var n: Node = stack.pop_back() + if n is AnimationPlayer: + problems.append("[pause][motion] 子树含 AnimationPlayer(R5 零动效破)") + for ch in n.get_children(): + stack.append(ch) + var src := FileAccess.get_file_as_string("res://presentation/views/pause_overlay.gd") + for token in ["create_tween", "UiKit.motion"]: + if src.contains(token): + problems.append("[pause][motion] 视图源码含「%s」(R5 零动效直开直关破——P4 甲)" % token) + + # ⑥standee 专项:B 区站桩内容矩形 ⊆ 252px 舞台矩形(内缩 8px)且不被裁切 # (UI 设计对齐批 N2 2026-10-06——名行基线离盒底 1px 同型高危写法返修回归闸; # 内容矩形=StandeeBody.content_rect(),最低绘制元素=底座下缘) diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 388d5345..d3d9f022 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -34,6 +34,7 @@ func _suites() -> Array: {"path": "res://tests/unit/data_config/rng_service_test.gd", "expect": 0}, {"path": "res://tests/unit/core/treasure_items_test.gd", "expect": 0}, # 迭代 7 T7——秘宝结构冻结断言面 {"path": "res://tests/unit/core/breakpoint_resume_test.gd", "expect": 0}, # 迭代 8 卡①/T9——断点往返/续玩确定性/幂等/退出清断点 + {"path": "res://tests/unit/core/curio_refresh_paid_test.gd", "expect": 0}, # 2026-10-06——三选一刷新免费双源+GP 付费档(对齐商店 MC-1:价曲线/优先序/停点清零/钱包单口) {"path": "res://tests/unit/presentation/pacing_queue_test.gd", "expect": 0}, # 迭代 8 卡⑦ AC⑤/T9——排空零分叉/零丢行/延迟双态 {"path": "res://tests/unit/presentation/reward_overlay_test.gd", "expect": 0}, # 迭代 7 hotfix——奖励节点 UI 直验(盲区补漏) {"path": "res://tests/unit/battle_settlement/damage_math_test.gd", "expect": 0}, diff --git a/game/tests/unit/core/curio_refresh_paid_test.gd b/game/tests/unit/core/curio_refresh_paid_test.gd new file mode 100644 index 00000000..0d6167a5 --- /dev/null +++ b/game/tests/unit/core/curio_refresh_paid_test.gd @@ -0,0 +1,59 @@ +extends SceneTree +# 三选一刷新免费双源 + GP 付费档单测(2026-10-06 用户令——三选一刷新对齐商店 MC-1: +# 免费位 X/Y 用尽后转 GP 付费,同旋钮同曲线同钱包)。 +# 守面:付费价曲线(ceil(商店基价×曲线^本次停点已付费刷))/免费优先序 X→Y/付费刷新零触碰免费位/ +# 停点在途谓词(无停点禁扣钱)/停点重建清零付费计数(对齐商店进店清零)/钱包单口支出 spend_gp。 +# 运行:godot --headless --path game --script res://tests/unit/core/curio_refresh_paid_test.gd(expect 0) +const Kit := preload("res://tests/unit/test_kit.gd") + + +func _init() -> void: + var db = load("res://core/config_db.gd").new() + if not db.load_tables("res://data/tables", false): + print("[curio_refresh_paid_test] FAIL — 数值表加载失败") + quit(1) + return + var base: int = int(db.adventure.shop_refresh_cost_base) + var curve: float = float(db.adventure.shop_refresh_cost_curve) + Kit.check_quiet(base > 0 and curve > 1.0, "商店刷新旋钮在档(base=%d curve=%.2f——三选一付费档同源读取)" % [base, curve]) + + # ---- ① 停点在途谓词:无停点时免费/付费刷新皆拒绝(付费路径扣费前置校验防空转烧钱包)---- + var adr = load("res://core/adventure_runner.gd").new(db, load("res://core/rng_service.gd").new()) + Kit.check_quiet(adr.start_adventure(1234, {"hero_override": {}, "treasure_unlocked": {}}), "冒险启动(种子 1234——刷新重抽需真实令牌池)") + Kit.check_quiet(adr.curio_stop_pending() == false, "无停点=在途谓词 false") + Kit.check_quiet(adr.refresh_curio() == false and adr.refresh_curio_paid() == false, "无停点:免费/付费刷新皆拒绝零副作用") + + # ---- ② 免费位优先序 X→Y(白盒造通关停点——smoke_all ⑫ 同构)---- + adr._curio_pending = {"candidates": _cands(), "payload": {}} + Kit.check_quiet(adr.curio_stop_pending() == true, "通关停点在途=谓词 true") + adr._attune_refresh_x = 1 + adr._attune_refresh_y = 2 + Kit.check_quiet(adr.refresh_curio() and adr._attune_refresh_x == 0 and adr._attune_refresh_y == 2, "X 优先消耗(x1y2→x0y2)") + Kit.check_quiet(adr.refresh_curio() and adr._attune_refresh_y == 1, "X 穷转 Y 池(y2→y1)") + + # ---- ③ GP 付费档:计数递增爬价 + 零触碰免费位 ---- + Kit.check_quiet(adr.curio_refresh_gp_cost() == base, "首刷报价=基价(免费位尚余 y1,报价独立于免费位)") + Kit.check_quiet(adr.refresh_curio_paid() and adr._attune_refresh_y == 1, "付费刷新不触碰免费位(y1 保持)") + Kit.check_quiet(adr.curio_refresh_gp_cost() == int(ceil(float(base) * curve)), "二刷价爬升=ceil(基价×曲线)(已付费刷=1)") + Kit.check_quiet(adr.refresh_curio_paid() and adr.curio_refresh_gp_cost() == int(ceil(float(base) * curve * curve)), "三刷价再爬升=ceil(基价×曲线²)(已付费刷=2)") + + # ---- ④ 停点重建清零付费计数(对齐商店进店清零——不结转)---- + adr.present_node_curio(_cands()) + Kit.check_quiet(adr._curio_paid_refreshes == 0 and adr.curio_refresh_gp_cost() == base, "节点停点重建=付费计数清零回基价") + adr.refresh_curio_paid() + Kit.check_quiet(adr._curio_paid_refreshes == 1, "付费刷新计数在途递增(=1)") + adr.present_node_curio(_cands()) + Kit.check_quiet(adr.curio_refresh_gp_cost() == base, "再碰三选一付费计数仍清零(同 X 余量不结转口径)") + + # ---- ⑤ 钱包单口支出 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.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)") + + quit(Kit.finish("curio_refresh_paid")) + + +func _cands() -> Array: + return [{"kind": "gp", "tier": "common"}, {"kind": "attack", "tier": "common"}, {"kind": "max_hp", "tier": "common"}] diff --git a/game/tests/unit/core/curio_refresh_paid_test.gd.uid b/game/tests/unit/core/curio_refresh_paid_test.gd.uid new file mode 100644 index 00000000..31b84c71 --- /dev/null +++ b/game/tests/unit/core/curio_refresh_paid_test.gd.uid @@ -0,0 +1 @@ +uid://5ehkr1644tk8 diff --git a/game/tests/unit/presentation/reward_overlay_test.gd b/game/tests/unit/presentation/reward_overlay_test.gd index aa457cfd..2aefa086 100644 --- a/game/tests/unit/presentation/reward_overlay_test.gd +++ b/game/tests/unit/presentation/reward_overlay_test.gd @@ -38,6 +38,29 @@ func _run() -> void: if not _req("选择态应三卡呈现且确认/继续隐藏(btns=%d)" % btns, ov.visible and btns == 3 and not ov._confirm_btn.visible and not ov._continue_btn.visible): return + # ①b 刷新面投喂(2026-10-06 对齐商店 MC-1——投喂前隐藏/免费位点亮/GP 付费档禁用态/真实钮路径外发) + if not _req("投喂前刷新钮应隐藏(未投喂上下文如店内门票面无刷新)", + not ov._refresh_btn.visible): + return + var refreshed := [] + ov.refresh_requested.connect(func(): refreshed.append(true)) + ov.set_refresh_display(0, 20, false) + if not _req("GP 付费档:余量 0 价 20 余额不足=钮现形但禁用、角标 0、价签行可见", + ov._refresh_btn.visible and ov._refresh_btn.disabled and ov._refresh_badge.text == "0" + and ov._refresh_l.visible and ov._refresh_l.text == "刷新 20 金币"): + return + ov.set_refresh_display(2, 20, false) + if not _req("免费位余量 2:钮点亮、角标 2、免费行——真实按钮路径外发 refresh_requested", + ov._refresh_btn.visible and not ov._refresh_btn.disabled and ov._refresh_badge.text == "2" + and ov._refresh_l.text == "免费 · 不消耗 AP"): + return + ov.set_refresh_display(0, 20, true) + if not _req("付费档余额可付:免费位 0 但钮点亮(对齐商店——GP 档可刷)", + ov._refresh_btn.visible and not ov._refresh_btn.disabled): + return + ov.press_refresh() + if not _req("press_refresh 未外发 refresh_requested", refreshed.size() == 1): + return # ② 点选→高亮→确认外发(真实按钮路径);确认前改选以最后点选为准 ov.press_card(0) ov.press_card(1) @@ -63,9 +86,11 @@ func _run() -> void: if not _req("丢旧收新未外发 replace_chosen(0)(%s)" % str(replaced), replaced_first and replaced.size() == 1 and int(replaced[0]) == 0): return - # ④ 结果态:继续钮点亮、唯一出口 + # ④ 结果态:继续钮点亮、唯一出口(刷新钮随选择态收——停点已兑付残钮零意图可点) + ov.set_refresh_display(2, 20, false) ov.show_result("已选择:攻击力提升 · 攻击 +1") - if not _req("结果态应点亮继续且收替换面板", ov._continue_btn.visible and not ov._replace_box.visible): + if not _req("结果态应点亮继续且收替换面板与刷新钮", + ov._continue_btn.visible and not ov._replace_box.visible and not ov._refresh_btn.visible): return ov.press_continue() if not _req("继续未外发 continue_pressed", continued.size() == 1): diff --git a/roadmap.md b/roadmap.md index 7b4476ce..dc50e9d2 100644 --- a/roadmap.md +++ b/roadmap.md @@ -64,10 +64,17 @@ | 迭代 15 | **②奇物概念收口与词族对齐批(2026-10-05 用户令编入本迭代)**:概念层已由用户逐轮裁定定稿(《奇物》v1.0→v2.7,7 轮),本批只做**落地对齐**、不做二次设计。三批内容:**①三套旧词族全面对齐**(顶层已改名「奇物」而代码仍叫旧名=用户最初抱怨的"文档与代码分开"):`feature_*`→`curio_*`(103 处/7 文件)、`relic_*`→`feat_*`(162 处/12 文件)、运行层 `bonus_*`→`curio_*`(160 处/8 文件)。**硬约束:`reward_*` 已被 `reward_pick`/`reward_node`/`reward_preview` 占用,统一词族不得使用**;`relic_table.gd` 与两个控制器类改名须同步 Godot `.uid`/`.tscn`(非文本层无法自证)。**②三条渠道按分类分离**(现行三渠道共用同一个 98 张池、三类混编):关底 Boss 击败=通过这一关→只出专长;奖励节点/商店→属性值提升+资源、专长在此**只以门票形式**出现。**兑付频率红线**:奇物不是打一场战斗给一份,关内普通战斗节点打完不给任何奇物,一关之内只兑付一次(用户 2026-10-05 纠错)。**③两个新种类入池**=刷新次数(须先与"商店独立刷新机制"划清边界)+ 专长获取门票(拿到即开不可囤积/罕见只在概率/一局限购一次/计步保底:连续 5 次三选一展示未出→第 6 次必出,刷新重展计入、奖励节点与商店共享计数);附带 `shop.tres` 补回复(默认原则要求分类内成员都在该出现的渠道池里)与 charge 标签补至 ≥4 件(现仅 3 件,档二永远达不到——数据缺陷)。设计事实源=`design/gdd/systems/冒险循环/奇物.md` v2.7,任务书=`design/research/任务书-奇物词族对齐与渠道分离批-2026-10-05.md`(T0-T8≈4.5d,两段切分预案在册〔段一词族 T0-T3≈2d/段二渠道+入表 T4-T8≈2.5d〕,切分点=T3 出口;11 条设计契约含 6 条 🔴 停机级;12 条批末验收断言;代拍板白名单 5 项;待用户裁点 4 项〔玩家可见面违规/第四关档二复裁/门票四参数终值/存档键正式迁移〕)。**开工门**:与在途 M-4 各批(判式换代/全局变异/配比预设)撞同一批文件(`adventure_table.gd`/`level_runner.gd`/`adventure.tres`),须确认不并行或用户明示插队 | 卡②门:迭代 14 出口 | 卡①已落籍(原估 0.5d);卡② ✅ **已全批落籍(2026-10-05,worktree curio-wordfamily 七提交 d4cde73..049aa62+master 同步 23547b5,主区 merge 见台账;出口报告=[iter15-2-curio-exit-report](design/qa/iter15-2-curio-exit-report-2026-10-05.md))**:三套词族代码层全换(feature_→curio_ 93 处/bonus_ 运行层→curio_/relic_→feat_——feat_table.gd/feats.tres/feats_engine.gd 改名);渠道白名单分离(总池-渠道子集多级缓存形=用户裁);门票入表(四参数 provisional 20%/2/1/5+拿到即开+计步保底+限购 1+节点商店共享计数);refresh 入池(B 案 heal 5→3 挤占)+shop.tres 补 heal 行+门票行;T2 裁点甲案=六存档键禁改(含任务书漏登记的 "relics" 键);charge 缺陷登记不硬补;autoplay 门票分支缺陷批内实测修复(MC timeout 形态);闸=冒烟 782/0(+10)+run_all 16 套历史首次全绿(reward_overlay 红转绿=M7 bcard 成因修复)+五探针绿+doc 三闸零新增+MC n200 只测不判(normal 0.5%/perfect 77.5%);遗留=07⑰ 目检归用户+C1 呈现批/C2 第四关批/C3 调参批/C4 存档批四移交。原估 T0-T8≈4.5d(实际一次会话);迭代 15 三卡全落籍(①352/②本批/③8944dca 前身③批+本批) | | 迭代 15 | **③秘宝代码词族对齐批(2026-10-05 用户令编入本迭代)**:设计侧已完成(消歧更名批 `430419c`+`06e94b7`,26 个设计文档「魔法道具」→「**秘宝**」,同调位机制名保留不变),本批只做**代码侧对齐**、不做二次设计。内容:`aux_*`/`AuxItem*` 词族全族换词 **358 处 / 36 文件**(重灾 `adventure_runner.gd`79 / `level_runner.gd`27 / `bonus_interact_controller.gd`19 / `battle_orchestrator.gd`18 / `config_db.gd`14 / `main.gd`13),分五组落:**A 玩家可见文案(P0,5 处)**=`battle_narrator.gd:117-118` + `bonus_interact_controller.gd:76` + `hero_info_panel_view.gd:56`(槽位标题 `UiKit.label`)+ `:178`(tooltip)· B schema 与数据表(`aux_item_table.gd`/`aux_items.tres`/`aux_items.gd` 文件名+类名+`ConfigDB` 持有变量与访问器全链跟改,**须同步 Godot `.uid`/`.tscn` + `--import` 重刷类名缓存**——迭代 14.5 实证在册坑)· C 文档链接修复(含 `architecture.md` 两处既有断债顺带清偿)· D 中文注释层(**须分色**:`魔法矢`/`魔法反噬`/`魔法族`/`魔法护甲` 属敌方伤害护甲语汇🔴不动)· E 测试文件名+内部变量。**用户三项拍板**:全族彻底改(含数据表)· **断点存档键一律保留**(`bonus_aux_carry`/`aux_detail`/`aux_cleared`/`aux_cleared_last`/`aux_items` 断点数组/`attunement_charges` 共 34 处引用一字不改,破档即进度丢失)· registry 条目键 `aux_items` 不改(数据契约键)。设计事实源=`design/gdd/systems/排雷循环/秘宝.md` v0.9,任务书=`design/research/任务书-秘宝代码词族对齐批-2026-10-05.md`(T0-T9≈5.0d,**超软盒 3 日上限·知情预挂**:T7 段末闸后 L3 呈报切点不擅切;**三档降级面 G1/G2/G3 在册**〔G3 下限=仅组 A 完成〕;5 条设计契约含 5 条 🔴 停机级〔存档兼容零破坏/数值零漂移/schema 契约面零变更/零字段更名/核心层零 await〕;代拍板白名单 5 项;**C1/C2 已裁关闭·全改保一致·待裁点 0 项**)。**撞批已消解**:② 奇物批 2026-10-05 收口落库(合并 `c571af2`),并行风险解除,本批为迭代 15 唯一在途需求;**T0 须重取基线**(判式换代批 T5 读数 750/0、② 又动数据层,§1.2 的 747 为②前旧数禁沿用)并复核事实表。**✅ 裁决(用户 2026-10-05「全改保一致」)**:C1 `"aux_item"` 域串**照改**(实测连锁面收窄——`reward.tres` 已零 aux_item 数据行,池种随迭代 11 T7 退役;16 处字面量全在代码内闭合无存档依赖;唯一连锁项=`smoke_all.gd:3932` 断言须同步)· C2 `aux_items` 一名两义**拆分**(ConfigDB 持有变量+表注册键✅改 / 断点存档键+冒烟夹具🔴禁改——**「键名禁改」与「变量名禁改」是两回事**,禁改的只是落盘字面量)。新增**组 F**(分派语汇与域串 16 处),任务卡扩为 T0-T10≈5.5d。**开工门**:✅ 已无待裁点,可直接发 T0 | 卡③门:迭代 14 出口(**裁点已全决·可开工**) | 卡①已落籍(原估 0.5d);卡② ✅ 已全批落籍(2026-10-05 merge 3c6f2c9,台账 362——c571af2 文档+数据层之上的实施段全量补完);**卡③ ✅ 已全批落籍(2026-10-05,worktree m4iter15-mibao 九提交 b4b8bb3..收口笔,主区 merge --no-ff 哈希见台账/active.md 快照行;出口报告=[iter15-3-mibao-exit-report](design/qa/iter15-3-mibao-exit-report-2026-10-05.md))**:词族实数 364 处/36 文件全换尽(例外 43+2 处=存档键引号字面量+夹具,穷举在报告 §五);闸=冒烟 755/0+run_all 14/15(唯一红=②批遗留基线红 reward_overlay,对照零新增)+行为探针四件全绿+doc 三闸/熵增闸零新增+存档键六键普查零差异+MC 按 goal 令豁免(静态零漂移证明代);渲染像素五闸屏蔽挂账 07⑮;遗留=实机目检归用户(07⑮)+reward_overlay 基线红归②批处置 | | 迭代 16 | **UI 设计对齐批(2026-10-05 用户令:迭代 16 改编为 UI 优化需求实现,原四卡全数顺延迭代 17;美术线用户自理立规的例外面=本批只改 UI 设计代码、零美术资源改动)**:三路改进整合轴的代码侧落地——事实源=`design/research/UI预览demo三路改进整合对照-2026-10-04.md`(A 落码差异对照+OPT-A 机检+WB 变体三路整合,基线原型 v2.1)。**红线=不修改美术资源**:game/assets/art 与 artwork/ 均不动、不生新素材不重烘,只改 UI 设计代码(ui_kit 色板 token/字号档/布局纪律/对比度垫底/抖动呈现/panel_skinned 接线等)。无分歧可直接收项=对比度 7 处 FAIL 修法(血数 2.09→12.29 垫底等已复算)/站桩溢出 3.2px/4 处裸色收 ui_kit/status 图标 preload 违规/tg-* 类型色接线。**前置待裁=六分歧点**(①底色冷暖 ②棋盘格边长 32/44/45 ③字号档位 ④九宫格切片 ⑤圆角去留 ⑥图标尺寸解法,对照表 §三)+开放项(抖动范围/色板唯一来源,§五)。任务书=`design/research/任务书-UI设计对齐批-2026-10-05.md`(T0-T11≈7.25d,**超软盒 3d 预挂**,三段切分=段一 2.0d 无阻断〔T0-T4a〕/ 段二 4.0d 待裁〔T4b-T8〕/ 段三 1.0d 收口〔T9-T11〕;🔴 契约 7 条 R1-R7;无分歧收项 N1-N6 已逐处 file:line 考证;**待用户裁点 11 项**=六分歧 P1 底色冷暖/P2 棋盘格边长/P3 字号档位/P4 九宫格切片/P5 圆角去留/P6 图标尺寸解法 + 开放项 P7 色板唯一来源/P8 抖动范围/P9 §五 已决注销清单/P10 记忆态口径/P11 装饰 caption 豁免;**六分歧与开放项一律呈全备选案含淘汰依据**)。**事实校正(实查,2026-10-05)**:9 处色板漂移实测 4 已收 5 未收;`tg-*` 五族在 ui_kit 并不存在(以裸 Color8 内联于 bonus_pick_overlay/shop_overlay_view,缺第五族);panel_skinned 消费面实测 8 处+工厂 1 处;棋盘格边长=动态公式 clamp[26,46] 与 §8.1 锚点 45/36 三方互不等;手绘图标十枚 20×20 已入库,真问题转为 64×64 柜台四枚显示倍率;站桩溢出实机存在 1px 余量同型写法(standee_body.gd:476);demo 基线 md5 已再次漂移(实测 78ebbee0…)。状态=**任务书已起草(2026-10-05)+ 取向已定(2026-10-06)**:✅ P1=甲(维持暖化零改动)/ P2=丁(棋盘格边长零改动,46/45/44 不一致登记为已知偏差);✅ **其余八项取向已定**(用户采纳推荐)——**跟 demo**:P4 九宫格切片 8→16 / P7 五处色板向 demo / P10 记忆态纹理灰罩;**按条款解**:P3 字号立 FS_* 档(demo 34 处<14、54 处<16 不可跟)/ P6 图标显示档(demo 自身 0.3125× 违规)/ P8 抖动只做卡顶色带(demo 渐变+blur 违反像素纪律)/ P11 装饰 caption 12px 豁免;P5 两路同结论。**「以 demo 为准」三层优先级栈=任务书 §8.3**(①用户终裁>②冻结条款>③demo);三条结构级差异(卡片 232×352 / 商店全屏化 / 地图底栏合并)**不入本批、已登记 06 待立项**。估时 T0-T11≈**6.9d**(段一 2.15d 无阻断可发令 / 段二 3.75d / 段三 1.0d;三段同属一迭代、**只在段三末合并一次**);**拷打轮一 9 问已全部落地**(Q1 `panel_skinned` content margin clamp / Q2 不走独立样张闸改并进 T10 目检 / Q3 柜台图标统一 32px / Q4 商店补类型分族 / Q5 血数 11px 与装饰 caption 合并写一条豁免 / Q6 槽两态不新增通道 / Q7 一次合并 / Q8 已知偏差已登记 06 / Q9 demo 取值抄录表为唯一引用源)。**✅ 已全批落籍(2026-10-06 单会话,worktree g-plan2-iter16ui 分支 iter16-ui-20261005 十二提交 85ea024..T11 收口笔,主区 merge --no-ff 哈希见 active.md 快照行;出口报告=[iter16-ui-exit-report](design/qa/iter16-ui-exit-report-2026-10-06.md))**:T0-T10 全落(T10 人闸材料包=[iter16-ui-human-gate-checklist](design/qa/iter16-ui-human-gate-checklist-2026-10-06.md)——实机目检+代拍板追认八项归用户);闸=冒烟 782/0(断言数持平基线)+run_all 16 套全绿+探针五件全绿(新增 ui_contrast_probe 11 对测点+ui_struct⑥standee 断言族入默认段)+doc 三闸/熵增零新增+MC 零跑(纯呈现批——核心/数据层 diff=0 实证)+素材零触碰;T0 实机复算三处修法改向(意图徽标垫牌/槽位字深化/RAP 零改动——demo 口径在实机反向)+N4a 裸色实数 25 处/10 文件全收 ui_kit(任务书 5 处系漏记)+字号 107 位点归档 FS_* 六档;偏差八条在出口报告§汇总;遗留=06 新登记「呈现层残余 <4.5:1 文字位」一条(名行四墨/顺位徽标/RAP 反馈——批外邻接发现) | 迭代 15 出口 | 预算 T0-T11≈6.9d(三段切分)——**实际单会话一次跑完(2026-10-06)** | -| 迭代 17 | **①敌人表行集多行批(2026-10-05 用户令编入本迭代,纯功能与数值轴四卡之一)**:放开敌人表「同一(关卡序号 × 节点深度)行集恰一敌种」加载期校验(enemy_table.gd:108-112 考证在册),允许同场遭遇混编多种敌人——生成(unit_container spawn 行集受理)/编排(battle_orchestrator/narrator)/断言面全链接通 + GDD 结构冻结面修订先行(敌人系统.md:75/:263)。事实源=06「同深度第二敌种与精英/Boss 编码位」条剩余面(前半已由关卡节点地图 v1.0 消解);前置=技能池实施批已落籍(d3fa887)开工门已解。任务书=`design/research/任务书-敌人表行集多行批-2026-10-05.md`(T0-T8≈4.25d 含条件卡 T5 首行投放/3.75d 不含;🔴 契约 5 条含零内容期逐位零漂移;代拍板白名单 3 项;**待用户裁点 4 项**〔P1 混编数量语义三案/P2 行集解析双算法处置〔工作日志 312 既有待裁随批〕/P3 本批是否投首个混编行/P4 Boss 行是否放开〕——裁点裁决后 U0 开工) | 迭代 15 出口 | T0-T8≈4.25d(超软盒预挂,T4 末切分点) | -| 迭代 17 | **②M4 数值批(2026-10-05 用户令编入本迭代)**:四件数值欠账合一批——①削弱档位终值(weaken 现值 −65/duration 2/refresh 考证在册;叠层改续时语义已定案,差 duration 终值+当量锚复算)②调参批数值点评(06 条目现行登记五条——任务口径「八条」与条目五条不符,任务书 §2.2 已呈报差异以条目为准)③迭代 10 奖励重构联动欠账①②(钥石产出重标 boss_keystone 首杀 3/3/3·复杀 2/2/3 provisional + 通关率带重扫〔通关回血 20%+AP 件首轮 +1〕)④元进度定价剩余面(forge base 20/比 150 考证在册——06 条目文本「base 10」滞后于 2026-09-22 对齿批已呈报;Boss 两档终值)。读数义务随批=迭代 14 两件后置复算(T6 MC 四带复算+T7 技能池节奏重测,均挂 07);顺路修复 damage_probe A1 改前既有红灯(判式换代批出口报告 §四.7 在册)。**硬约束:一切 MC/verifier 跑批发起前须用户批准;调参联动禁独立调参网络(β 带 [1.2,2.5] 逐 u 检算)**。任务书=`design/research/任务书-M4数值批-2026-10-05.md`(T0-T8≈6.0d,天然切分点 T4 末;🔴 契约 5 条含跑批批准制;**待用户裁点 4 项**〔P1 削弱终值三案推算后裁/P2 点评不对称裁决方向/P3 钥石产出终值/P4 工坊直购价立否〕) | 迭代 15 出口 | T0-T8≈6.0d(超软盒预挂,段一 T0-T4≈4.25d/段二 T5-T8≈2.0d) | -| 迭代 17 | **③暂停层只读入口批(2026-10-05 用户令编入本迭代)**:新增局内暂停层,承载钥石余额与解锁态(营地三柜台进度)只读查看——事实源=06「解锁界面与钥石呈现的交互形态(残余:暂停层只读入口)」条。考证在册:现行无暂停层;模态栈/设置面/调试浮窗三先例齐;UiKit 公共件(modal_shell/bcard/verb_button/徽章形制)与色板 token 单点现成;MetaProgression 只读访问器面齐全;战斗屏 GP/钥石已迁出=局内零查看入口正是本批由来。**美术红线:复用现行公共件与色板,零新素材零新色值(2026-10-05 用户立规)**。任务书=`design/research/任务书-暂停层只读入口批-2026-10-05.md`(T0-T5≈3.0d;🔴 契约 5 条含公共件单点/模态登记纪律;**待用户裁点 4 项**〔P1 入口位置四案荐丁/P2 与既有弹窗层关系三案荐甲/P3 展示范围三案荐甲/P4 减动效联动两案荐甲〕) | 迭代 15 出口 | T0-T5≈3.0d(贴软盒,T2 末切分点) | -| 迭代 17 | **④生涯界面设计立项(2026-10-05 用户令编入本迭代;只做设计提纲不做实装)**:事实源=06「『生涯』界面——冒险中数据回看的唯一承接处」条(P-C7 裁:M9 已退役局内结算明细回看,历史数据现无承接面=用户知情裁决)。考证在册:回看退役已落码(battle_end_overlay/battle_log_view/ui_assembler 三处注记)+settlement_review_overlay.gd 遗留件在盘+跨局持久面=save v5 meta 域+**无历史冒险记录面**(settle_adventure 只入账不留痕=最大数据设计题)。任务书形态=设计提纲版:信息架构+承载内容清单(跨局汇总/历史冒险回看/图鉴承接)+与既有界面关系+三期实装建议+待裁点,标注「实装批另行立项」。任务书=`design/research/任务书-生涯界面设计立项-2026-10-05.md`(T0-T2≈1.25d 纯文档零测试;**待用户裁点 6 项**〔P0 载体/P1 入口位置/P2 历史数据面范围/P3 图鉴范围与顺序/P4 跳转边界/P5 遗留件处置〕) | 迭代 15 出口 | T0-T2≈1.25d(软盒内) | +| 迭代 17 | **⓪内容数据驱动扩展批(2026-10-06 用户令插队为头卡——①-④卡顺延,其任务书与裁点状态原样保留)**:把内容效果行为从结算代码硬编码搬进数据行「订阅事件(on)+条件(when)+效果类型(do)」,通用解释器 EffectEngine 统一执行——完成后加「已有效果类型」的新专长/秘宝=纯改 `.tres` 零代码。裁决八条全落(R1 发布订阅同步 emit 确定性优先/R2 仅一个改写窗口不做拦截器链/R3 一事件多订阅+组合规则封冲突面/R4 旧 23 挂点枚举删除·2 个退化条件行/R5 现有实现全改造+06 在册只登记〔curio_roll/shop_purchase/gp_settle 三事件登记不实施〕/R6 框架先行发射点不论有无订阅者全埋/R7 一个迭代四模块垂直切一次合并/R8 术语纪律:事件=发布订阅 topic、效果类型=订阅后逻辑、组合规则=叠加规则,禁 hook/合并律等自造词)。词汇表定稿=**实施 16 事件**+效果类型 19 行 23(v1.1 扩 damage_mult/open_ticket 两型装下减半/开单现状)+组合规则 6;存档零改动;文案表值化随批(现有实现判据);luck_coin 经 reward_roll 迁移随批。任务书=`design/research/任务书-内容数据驱动扩展批-2026-10-06.md`(v1.1 2026-10-06 联审修订:level_clear 归登记不实施/对拍判据两套〔确定性复放与冒烟=逐位、MC 统计量=成型画像同带 m4iter12 口径〕/映射表五处语义修正/断言面随迁与订阅重建时机补列;T0-T7≈4.5d,零待裁点,🔴 契约 5 条含行为等价两套判据+护栏保留+确定性;停机点 S1 首流派对拍/S2 批末闸;开工门=U0) | 迭代 15 出口(已解) | T0-T7≈4.5d |**状态**:**已实施完成(2026-10-06,worktree iter17-dd 待合并)**——T1 效果引擎框架+16 发射点(7d157b3)/T2 词汇落码(225765f)/T3 专长 27 行迁移+旧挂点消亡(e28a6cb)/T4+T5 秘宝六件+令牌树收尾(a33ddaf);六闸全绿(冒烟 800 项 0 失败/单元 17 套/探针含 click/MC 二档对拍 40 局同种子逐位一致);出口报告 design/qa/iter17-datadrive-exit-report-2026-10-06.md,遗留人闸两项随报告 | +| 迭代 17 | **①敌人表行集多行批(2026-10-05 用户令编入本迭代,纯功能与数值轴四卡之一)**:放开敌人表「同一(关卡序号 × 节点深度)行集恰一敌种」加载期校验(enemy_table.gd:108-112 考证在册),允许同场遭遇混编多种敌人——生成(unit_container spawn 行集受理)/编排(battle_orchestrator/narrator)/断言面全链接通 + GDD 结构冻结面修订先行(敌人系统.md:75/:263)。事实源=06「同深度第二敌种与精英/Boss 编码位」条剩余面(前半已由关卡节点地图 v1.0 消解);前置=技能池实施批已落籍(d3fa887)开工门已解。任务书=`design/research/任务书-敌人表行集多行批-2026-10-05.md`(**v1.1 2026-10-06 联审修订**:F7 勘误 19 行 9 敌种/P1 补遭遇骰交互〔每行一骰·RNG 扩签先报批〕与实例总数≤4 消歧/P3 改裁丙撤投放/新增契约 R6 混编行行序纪律/expect_pools 断言换代入组 D;T0-T8≈3.75d〔T5 撤投后,超软盒预挂,T4 末切分点〕;🔴 契约 6 条;**✅ 裁点全裁(2026-10-06 用户令:P1甲〔Q8/Q9 二裁补全〕/P2甲/P3丙〔Q3 二裁撤投放——一裁乙与首遇教学位纪律三面冲突〕/P4乙 Boss 行亦放开·知情代价六面在册);开工=U0**) | 迭代 15 出口 | T0-T8≈3.75d(超软盒预挂,T4 末切分点) | +| 迭代 17 | **③暂停层只读入口批(2026-10-05 用户令编入本迭代)**:新增局内暂停层,承载钥石余额与解锁态(营地三柜台进度)只读查看——事实源=06「解锁界面与钥石呈现的交互形态(残余:暂停层只读入口)」条。考证在册:现行无暂停层;模态栈/设置面/调试浮窗三先例齐;UiKit 公共件(modal_shell/bcard/verb_button/徽章形制)与色板 token 单点现成;MetaProgression 只读访问器面齐全;战斗屏 GP/钥石已迁出=局内零查看入口正是本批由来。**美术红线:复用现行公共件与色板,零新素材零新色值(2026-10-05 用户立规)**。任务书=`design/research/任务书-暂停层只读入口批-2026-10-05.md`(T0-T5≈3.0d;🔴 契约 5 条含公共件单点/模态登记纪律;**✅ 裁点全裁(2026-10-06 用户令全按推荐:P1丁/P2甲/P3甲/P4甲);开工=U0**) | 迭代 15 出口 | **✅ 已全批落码待人闸(2026-10-06,worktree g-plan2-iter17-pause 分支 iter17-pause-20261006 两会话接力完成,提交 c08a901/fc9eb1e/d44fdd5+收口笔;出口报告=[iter17-pause-exit-report](design/qa/iter17-pause-exit-report-2026-10-06.md))**:暂停层本体+badge 工厂件收编(Q10 甲,营地钥石徽同步迁移观感零变化)+两屏常驻角标入口与 has_open 守卫+四探针断言(结构/点击/输入锁 case⑯/只读巡检/零动效契约/对比度对)+冒烟投影一致性 5 断言;闸=冒烟 788/0(基线 783/0)+run_all 17 套+probe_gate --click 全绿+doc 三闸零新增+MC 零跑(纯呈现零数值);存量移交=熵增闸 design/diagrams 越界(00712d5 总图批落籍漏步,主区同红)+06/07 gdd_lint 存量注记红(本批零触碰);**人闸=用户实机目检**(design/qa/iter17-pause-human-gate-checklist-2026-10-06.md,重点含排播排空副作用确认项 D2。原估 T0-T5≈3.0d 贴软盒内) | +| 迭代 17 | **④生涯界面设计立项(2026-10-05 用户令编入本迭代;只做设计提纲不做实装)**:事实源=06「『生涯』界面——冒险中数据回看的唯一承接处」条(P-C7 裁:M9 已退役局内结算明细回看,历史数据现无承接面=用户知情裁决)。考证在册:回看退役已落码(battle_end_overlay/battle_log_view/ui_assembler 三处注记)+settlement_review_overlay.gd 遗留件在盘+跨局持久面=save v5 meta 域+recent_summaries 现役逐局留痕(cap=10,save_manager.gd SUMMARY_CAP——2026-10-06 联审勘误:原记「无历史冒险记录面」系事实错误;真实缺口=cap 扩容+用时埋点+总局数/通关局数 lifetime 计数域〔涉存档 schema〕+营地既有最近冒险清单的去留设计)。任务书形态=设计提纲版:信息架构+承载内容清单(跨局汇总/历史冒险回看/图鉴承接)+与既有界面关系+三期实装建议+待裁点,标注「实装批另行立项」。任务书=`design/research/任务书-生涯界面设计立项-2026-10-05.md`(**v1.1 2026-10-06 联审修订**:F4 改正 recent_summaries 现役/P2 甲口径重述=扩容+用时埋点/总局数通关局数标注涉 schema/营地清单去列入设计稿必答项/N 候选补「维持 10」;T0-T2≈1.25d 纯文档零测试;**✅ 裁点全裁(2026-10-06 用户令全按推荐:P0甲/P1甲/P2甲〔N 实装期定〕/P3甲/P4甲/P5甲);开工=U0**) | 迭代 15 出口 | ✅ **设计提纲已成文待用户评审(2026-10-06 卡④设计批完成,worktree iter17-career-20261006,纯文档零代码零测试)**:提纲=design/gdd/systems/跨局循环/生涯.md v0.1(范围草案——信息架构/承载内容清单/界面关系/分期建议/待裁点五节,P0-P5 已裁结论落文,新发现待裁点 P6 营地最近冒险条带去留随评审一并裁);02 根索引补行+06 状态行换代「设计提纲已成文待评审」。**人闸=用户评审提纲**;评审通过后期一/二/三实装批另行立项(不预占排期。原估 T0-T2≈1.25d 软盒内) | +| 迭代 18 | **①奇物内容补全批·文档先行(2026-10-06 用户令立项——先文档后落地,数值只设计不调、终值归⑥M4 收官批统一调整)**:两件——(a) charge 标签件数补足(06 在册数据缺陷:feats.tres 现恰 3 件〔整备军备/铁壁整备/厚积薄发,表注释 "dig 4/charge 3/devour 4" 自证〕、档二需同标签 ≥4,charge 流派永远达不到档二;迭代 15② 批登记不硬补):本卡设计补足件清单=名称/效果〔限现有效果 hook〕/标签/稀有度/配额占比/卡面文案,呈用户裁决后落地;(b) 06「资源两个新种类入池」条收口核对(实查 2026-10-06:刷新次数〔treasure_items.tres 三选一刷新件〕与专长获取门票〔shop.tres+adventure_table.gd 门票行〕均已随迭代 15②/三选一刷新批入池——该条现状文本滞后,残余=边界核对与条目收口,随本卡文档一并处置)。本卡=纯文档零代码;落地批(feats.tres 数据行)估 0.5d 内,门=本文档出口+用户裁件+迭代 17 卡⓪落籍〔卡⓪在飞改表结构避撞车;新件限现有效果 hook 则纯 .tres 数据行零代码〕 | 无硬门(可先行) | 估时随任务书 | +| 迭代 18 | **②呈现优化批(2026-10-06 用户令立项;与迭代 16 语义零重合——16=样式对齐〔色/字号/形制 token 化、观感原值搬运、P2 棋盘零改动〕,本批=内容与观感改版,动的正是 16 落好的 token 单点=其下游)**:六件——(a) 奇物内部分类禁写入玩家可见面三处违规清除〔06 在册:battle_narrator 卡面流派名/C 区聚合行/关卡选择页〕+档位呈现三路〔甲纯隐藏/乙一次性风味反馈/丙机制退役——仍待裁,随任务书呈报〕;(b) L1 亮墨数字直画观感重设计〔06 在册 T17 目检体感〕;(c) 高墙墙底阴线形制〔06 在册;色值已收 ui_kit token 单点〕;(d) 呈现层残余 <4.5:1 文字位三处〔06 在册,迭代 16 批外邻接漏出面〕;(e) L2/L3 棋盘观感「看着累」〔playnote 2026-10-06;开工先定性:素材缺位程序化回退=美术轴归用户自理,UI 代码轴才入本批〕;(f) 关卡节点图展示改版〔playnote 2026-10-06「参考哥布林弹珠的方式去移动」——先出参考调研+改版方案人闸后动手,结构级改版非样式对齐〕。随批义务=07⑮ 渲染像素五闸补跑挂账(golden/shot 等基线按换代流程重锚) | 无硬门;(f) 方案人闸随任务书 | 估时随任务书 | +| 迭代 18 | **③武器锻造系统设计提案批(2026-10-06 用户令入迭代 18;纯文档零代码——提案源=playnote 2026-10-06)**:用户提案=攻击力提升改造为「武器锻造」——剑(普攻)锻造升级加攻击、满级附带额外效果;后续增列长枪/法杖,攻击类型分别走穿刺/魔法;护甲由轻甲起级(先只提 AC)→解锁重甲/魔法甲(基础 AC 各异、可续升、满级各带额外效果)——武器/护甲差异化。产出=系统设计提案文档(与奇物属性值提升分类/护甲克制轴/营地柜台/锻造经济边界对齐,数值框架 provisional 终值归⑥统一调整),裁点呈用户裁决;实装批另行立项。06 已登记专条 | 无硬门 | 估时随任务书 | +| 迭代 18 | **④生涯界面实装批(2026-10-06 用户令入迭代 18)**:按迭代 17 卡④设计提纲(design/gdd/systems/跨局循环/生涯.md v0.1,master 44328b1)实装「生涯」界面——跨局汇总/历史冒险回看/图鉴承接;提纲在册要点=recent_summaries 现役 cap10 扩容+用时埋点/总局数与通关局数(涉存档 schema)/营地最近冒险清单去留(P6 必答项随评审定);分期建议随提纲 | 用户评审卡④提纲+设计定稿(人闸) | 估时随任务书 | +| 迭代 18 | **⑤混编遭遇首批投放批(2026-10-06 用户令入迭代 18;纯内容批——机制前置=迭代 17 卡①敌人表行集多行批)**:07「后续内容批义务」落地——首个混编遭遇行+Boss 多行内容投放(敌种候选与坐标=本批裁点);首遇教学位纪律与 expect_pools 断言面逐项复核(巨蜘蛛首遇位「固定 2 不加骰」等表注释为准);难度带读数义务随批登记 07,MC 取证跑批**顺延⑥M4 收官批统一执行**〔2026-10-06 用户令:数值统一调整殿后;投放本身不阻塞〕 | 迭代 17 卡①落籍 | 估时随任务书 | +| 迭代 18 | **⑦新冒险种子随机化+调试浮窗升级(2026-10-07 用户令入迭代 18,同日两裁点全裁+浮窗目标态已定)**:**(a) 种子随机化**——现状考证 main.gd:1210 `_seed_value()` 调试种子框为空时硬编码返回 42=每次新冒险同一种子全程可复现;改法=玩家界面入口(主菜单开新冒险)默认分支随机生成;**✅ 裁点全裁(2026-10-07 用户令):①首次冒险维持固定种子(教学态可复现)/②测试脚本与调试接口维持显式指定种子(冒烟·MC·探针全链显式传种子零影响,锚零作废)/③种子不进玩家 UI**;断点续玩零影响(存档已载 master_seed 回填,main.gd:447 现役);与⑥收官批零耦合、排序自由。**(b) 调试浮窗升级**(承载「调试浮窗可查当局数据」裁点=种子的唯一查看位;**本项修订迭代 15 卡① P3 甲「仅开始菜单可开·对局不可达」裁决**)——目标态三件:独立于整体界面的**最高层浮窗**(对局中也可开)/DS_DEBUG=1 调试态下**默认打开**/新增**当局数据只读区块**(当前局种子/关卡/节点/HP/GP/钥石等——玩家 UI 不展示、调试浮窗可查)。考证在册(2026-10-07 实查):现行=开始覆盖层角标入口〔main.gd:292-293,DS_DEBUG=1 点亮〕+main.gd:1189 门禁「仅开始菜单可开」+Dialog 形态走 ModalStack 模态栈登记〔非最高层〕+visible=false 初始非默认开+五区块(胜利演出/种子输入/数值滑条/批量模拟/两层调试)无当局数据面。任务书待裁点:最高层与模态栈登记制的关系〔置顶/登记序末+move_child 或独立层〕/输入吞噬边界〔浮窗矩形外透传游戏输入〕/对局发起「随开随收」逻辑改向/ui_tune_probe 断言面换代/入口形态(角标宿主迁移或热键) | 无硬门 | 小卡≈1d | +| 迭代 18 | **⑥M4 数值批(2026-10-05 用户令编入迭代 17 卡②;2026-10-06 用户令移出独立成批——不影响迭代 17 在飞开发,17 卡号 ③④ 维持原号不重排;同日用户令二次调整:移至 Alpha 阶段最后=本迭代收官卡——内容补全先行、统一数值调整殿后〔内容卡若溢出后续迭代则本批随之顺延〕)**:四件数值欠账合一批——①削弱档位终值(weaken 现值 −65/duration 2/refresh 考证在册;叠层改续时语义已定案,差 duration 终值+当量锚复算)②调参批数值点评(06 条目现行登记五条——任务口径「八条」与条目五条不符,任务书 §2.2 已呈报差异以条目为准)③迭代 10 奖励重构联动欠账①②(钥石产出重标 boss_keystone 首杀 3/3/3·复杀 2/2/3 provisional + 通关率带重扫〔通关回血 20%+AP 件首轮 +1〕)④元进度定价剩余面(forge base 20/比 150 考证在册——06 条目文本「base 10」滞后于 2026-09-22 对齿批已呈报;Boss 两档终值)。读数义务随批=迭代 14 两件后置复算(T6 MC 四带复算+T7 技能池节奏重测,均挂 07);顺路修复 damage_probe A1 改前既有红灯(判式换代批出口报告 §四.7 在册)。**硬约束:一切 MC/verifier 跑批发起前须用户批准;调参联动禁独立调参网络(β 带 [1.2,2.5] 逐 u 检算)**。任务书=`design/research/任务书-M4数值批-2026-10-05.md`(**v1.2 2026-10-06 案C 增补+联审修订**:v1.1 三事实与第五推算件照旧;联审=P3 转正顺延至重扫读数后/检算工具 WEAKEN_PCT 入同步面/F11 两句死文本全刷/T3·T5 四带复算并账一次跑批双认账/R5 改前锚=本批新立案C 锚/估时段一勘误 4.25d;案C 增补内容照旧=§2.6 三事实〔行为换代/锚全作废/难度预判〕+T0 推算稿第五件=对拍锚按新序重推导+T6 四带复算按案C 新行为首采;T0-T8≈6.25d,天然切分点 T4 末;🔴 契约 5 条含跑批批准制;**✅ 裁点全裁(2026-10-06 用户令:P1甲=维持 2 场·magnitude [60,85] 带内当量锚复算、具体值随出口 provisional 追认/P2甲 不对称有意/P3甲 3-3-3·2-2-3 维持、转正顺延至重扫读数到账且带内(Q4 二裁)/P4甲 不立直购价);开工=U0**) | 迭代 18 内容卡①-⑤⑦出口(**Alpha 收官位**——2026-10-06 用户令;重锚窗口须收尽全部内容批行为变更,故排一切内容批之后) | T0-T8≈6.25d(超软盒预挂,段一 T0-T4≈4.25d〔v1.2 勘误,原 4.5 系加总错误〕/段二 T5-T8≈2.0d) | ## 执行纪律(2026-09-27 用户立规)