diff --git a/game/tests/README.md b/game/tests/README.md index 98c50e6e..a5785511 100644 --- a/game/tests/README.md +++ b/game/tests/README.md @@ -84,6 +84,7 @@ tests/ |---|---|---|---| | 伤害公式与掷点乘区 | 战斗结算 | `DamageMath.pool_damage()`/`roll_discount_percent()`/`rolled_component()` | `damage_math_test.gd:_test_pool_damage`;`damage_math_test.gd:_test_roll_discount_percent`;`roll_multiplier_test.gd:_smoke_armor_roll` | | 伤害单据编排与回放 | 战斗结算 | `DamageController.submit()`/`open_ticket()`/`flush()` | `damage_controller_test.gd:_run_recipe_sequence`;`damage_controller_test.gd:_test_five_recipes_and_replay` | +| 效果发射端口单点 | 战斗结算 | `BattleOrchestrator.begin_battle()`(真实装配链端口接线+合成行闭面守卫经 `DamageController.flush_submit()` 承伤路径) | `effect_port_test.gd:_smoke_effect_port_wiring`;`effect_port_test.gd:_test_biz_fact_face_closed`〔计划用例——锚定 `reports/需求卡片-效果端口与掷格分件批-2026-11-02.md`〕〔效果端口与掷格分件批 V1/V3〕 | | 轮内流与结算五段全序 | 战斗结算 | `BattleOrchestrator.begin_battle()` | `round_flow_skill_pool_test.gd:_smoke_full_battle`;`round_flow_skill_pool_test.gd:_smoke_settlement_segments` | | 技能池数据面与回放独立 | 敌人系统 | `BattleOrchestrator.begin_battle()`(技能池抽号/校验) | `round_flow_skill_pool_test.gd:_smoke_skill_pool_data`;`round_flow_skill_pool_test.gd:_smoke_skill_pool_replay_independence` | | Boss 半血解锁技门控 | 敌人系统 | `BattleOrchestrator.begin_battle()`(Boss 行) | `skill_unlock_test.gd:_test_gate_predicate`;`skill_unlock_test.gd:_test_latch_irreversible` | diff --git a/reports/需求卡片-效果端口与掷格分件批-2026-11-02.md b/reports/需求卡片-效果端口与掷格分件批-2026-11-02.md new file mode 100644 index 00000000..8223b002 --- /dev/null +++ b/reports/需求卡片-效果端口与掷格分件批-2026-11-02.md @@ -0,0 +1,312 @@ +# 需求卡片 · 效果端口与掷格分件批(v1.0) + +> **用途**:迭代实现批的实施合同。战斗域结构深拆第二波(轴戊):效果发射端口件+掷格件两新件建件,业务类零引擎直连、受理账面留守。**行为零变化结构重构**——游戏可观测行为逐位不变。 +> **来源**:待办清单「战斗域结构深拆批——效果发射端口+掷格/受理分件(2026-10-31 重立,来源=批次移交)」条目(状态=文档设计已闭合→已写卡)。 +> **唯一事实源**:`design/engineering/architecture/architecture.md` v1.29「战斗编排域拆件定稿」节**轴戊两件**条款(2026-11-02 用户裁甲案设计闭合;职责语句/账面归属/衔接面/行为零变化红线全在案)。与待办清单条目实况凭据一致。 +> **改动面**:游戏代码。 **估时**:≈2.63 个工作日(T0 0.25–0.5/T1 0.5–1/T2 0.5–1/T3 0.25–0.5/T4 0.25–0.5,中值合计 2.625)。 + +--- + +## 1. 目标与完成定义 + +**做什么**:按 architecture v1.29 轴戊建两件—— + +| 新件 | 职责语句(轴戊原文) | 承接 | +|---|---|---| +| **效果端口件**(EffectPort,`game/core/effect_port.gd`) | 战斗业务类唯一的效果发射出口:收事件与载荷、交效果引擎结算、取回命中批 | DamageController/ActionEconomy 各持端口引用(同一实例、组装期注入);EffectEngine/EffectContext 引用与计数器账面(value_counter 层值读后清零)收归宿主侧(BattleFxHost 域);facts 取值束与入站回调束随发射契约归宿主侧注入面;单实现零接口抽象 | +| **掷格件**(RewardRoller,`game/core/reward_roller.gd`) | 装配期格面资源摇号:池行装配、区间行 roll、权重合成(偏运硬币/board_skew 两路投影) | 自 RewardEconomy 迁出掷格面;受理账面(grant/身份查询/快照/retire/polarity_row)留守 RewardEconomy;衔接面=格→奖励身份映射(掷格件产出、受理件接收);经注入 RngServiceCore 取号(roll_board 流 scope=每次 install 批不变、取号序零漂移) | + +**什么状态算完**: + +1. 两新件在库、按本卡接口块落名;两宿主类与编排域改址全落; +2. **行为零变化**:既有 EV 锚与全战斗事件流逐位回放(round_flow 套件)、board_gen_reward_test 摇号受理面、variant_placement_test 指址换代后全绿——同种子逐位零漂移; +3. 结构断言全绿:端口单实例、业务类源文件零引擎直连 token、伤害域事实面闭集(新增 effect_port_test 三函数); +4. 存量触面 S13 键账只降不升:reward_economy 类变量 15→11、damage_controller 15→12、action_economy 11→10、battle_fx_host 14→15(贴顶 ≤15)、battle_orchestrator 15→15 持平; +5. 代码域闸全绿+产出物两份齐(实现报告/反哺点清单)+独立验收审计通过。 + +## 2. 设计事实源 + +`design/engineering/architecture/architecture.md` v1.29(2026-11-02)「战斗编排域拆件定稿」节**轴戊两件**条——两件职责语句、EffectEngine/EffectContext 引用与计数器账面归宿主侧(BattleFxHost 域)、facts 取值束(dig_streak/charge_layers/rider_tier_*)与入站回调束(reveal/ac)随发射契约归宿主侧注入面、端口单实现零接口抽象、端口持 BattleFxHost 引用或经注入 Callable 卡内定(跨件单向接线条款适用,禁双向强引用)、掷格件经注入 RngServiceCore 取号(roll_board 流 scope 与取号序零漂移);同节**「验收红线」全款**(命中序=声明序、明细行协议词表零触碰、`_ineffective_row` 载荷 deferred 自描述键观测面不回退、EV/MC 锚同种子逐位零漂移、S13 预算随卡三查、RewardEconomy 类变量只降不升、S7 B1 域文件集随批增行)与**「跨件单向接线」**「S13 键账收敛义务」「S7/S8 随动」各条。判据权威=`design/engineering/code-conventions.md` §1.1 拆分判据(拆分边界纪律)。 + +## 3. 现状事实(代码级,开工自行复核) + +| # | 事实 | 出处 | +|---|---|---| +| F1 | damage_controller 直持引擎:`_fx`/`_ctx` 成员;`set_fx(fx, ctx)` 注入口;发射漏斗 `_fx_hits`(体内 `_fx.dispatch(_ctx, event, payload, _fx_fact)` 单点);五事件发射点=attack_commit(open_ticket 内)/armor_roll/clamp_check(主角攻击面+棋盘克制直伤面两处)/kill/damage_taken | damage_controller.gd:46-47、:73-75、:79-82(:82)、:173、:299、:318、:552、:422、:512 | +| F2 | damage 计数器账面直触:`EffectEngine.counter(_ctx,…)` 层值读、`EffectEngine.counter_add(_ctx,…)` 两处施加——value_counter 层值「疼痛记忆/弃图兑换/陷阱本能」读后清零语义 | damage_controller.gd:364、:370、:533 | +| F3 | damage 注入束:`inject` 4 键(dig_streak/charge_layers/rider_tier_bonus/rider_tier_crit;写点=编排器 begin_battle rider 复位两行+combat_segment 轮内写两行);`fx_calls` 2 键(reveal/ac;写点=编排器 begin_battle);`_fx_fact` 事实闭面(charge_layers/dig_streak/hp_ratio 三答,余 null);`_ineffective_row` 行载荷 deferred 自描述键(迭代 48 卡① 甲案观测面) | damage_controller.gd:53、:56、:89-98、:689-691;battle_orchestrator.gd:348-349、:343-344;combat_segment.gd:34、:37、:64 | +| F4 | action_economy 直持引擎:`_fx`/`_ctx`;`set_fx`;三发射点=dig_paid/dig_stop/charge_full;账面直触 `EffectEngine.counter_add` 三处 | action_economy.gd:41-42、:46-48、:120、:178、:205、:125、:128、:183 | +| F5 | reward_economy 15 类变量恰在阈值线上(:8-24 主体 13 枚+board_skew+_treasure_unlocked);掷格面(roll_board 三批取号、池行装配 `_pool_rows`/`_rollable_rows`、权重合成 `_biased_cell_rows`/`_skewed_cell_rows` 族、选行算子 weighted_pick)与受理账面(grant/grant_clear/query_identity/query_full_snapshot/retire_mapping/polarity_row/is_mapped/phase/快照 `_snapshot`/行查 `_row_by_id`)两职责同居一件 | reward_economy.gd:8-24、:192、:318、:35-75、:56-57、:61、:63、:68、:71、:203、:221、:304、:132、:147、:105、:120、:160、:174、:101、:96、:360、:353 | +| F6 | 偏运查询缝拆先例:economy `luck_bias_query` 单枚注入口(消费 :53-55)→ BattleFxHost `luck_bias_for` 查询口(三参形 dispatch);注入点在编排器 _init | reward_economy.gd:24、:53-55;battle_fx_host.gd:61-66;battle_orchestrator.gd:231 | +| F7 | 接线现状:编排器 _init 内 `set_fx` 双注+luck_bias 注入;begin_battle 内 fx_calls 写两行+rider 复位两行;combat_segment 轮内 inject 写读三行 | battle_orchestrator.gd:229-231、:343-344、:348-349;combat_segment.gd:34、:37、:64 | +| F8 | economy 消费面(全受理域):BoardInstaller(roll_board 调用+board_skew 写两处)、RewardGrant(query_full_snapshot/polarity_row/grant_clear)、GrantSegment(grant ×3+query_full_snapshot)、BattleRefreshFlow(retire_mapping)、编排器(query_full_snapshot/polarity_row/set_treasure_unlocked 转发/retire_mapping ×2/query_identity/phase)、board_view(query_identity 只读回调,组合根注入) | board_installer.gd:65、:80、:86;reward_grant.gd:73、:190、:389;grant_segment.gd:347、:393、:426、:440;battle_refresh_flow.gd:38;battle_orchestrator.gd:437、:461、:501-502、:583、:630、:726、:745 | +| F9 | 事实词汇封闭(引擎 FACTS 19 项)与取值序(payload 同名字段优先→facts 回调);宿主事实链现状=charge_layers 自答+facts_cb 转宿主五事实(nest_count/round_index/battle_index/seals_count/hp_ratio);伤害侧发射事实闭面=damage `_fx_fact` 三答 | effect_engine.gd:58-62、:365-377;battle_fx_host.gd:168-171;battle_orchestrator.gd:114-133;damage_controller.gd:89-98 | +| F10 | 🔴 posture_bulwark 树规则现行形态:on=damage_taken、when battle_index==1 且 round_index==1;伤害侧发射的事实回调不答 battle_index/round_index → when 恒不通过=**现行行为是「每关首轮承伤不加盾不触发」**;事实面若扩成宿主全链(答 battle_index)该行即翻活=行为变化 | effect_engine.gd:150-155(_push_tree_rule posture_bulwark);damage_controller.gd:89-98 | +| F11 | 经济域三事件(dig_paid/dig_stop/charge_full)订阅行 when 词汇实测:feats/treasure 两表全部订阅行 when ∈ {counter}∪∅,ap_left/ap_cost 均载荷携带;skills.tres/status_effects.tres 零 triggers;树规则合成行六键订阅面=round_start/damage_taken/mine_hit(无经济域三事件)——订阅行全域与 facts 回调无交集,统一闭面对经济域发射无观测差 | feats.tres/treasure_items.tres/skills.tres/status_effects.tres+effect_engine.gd:147-161(2026-11-02 写卡段全量实测) | +| F12 | 测试与工具消费面:damage_controller_test 41 检查(夹具直构四参、不接 fx——零扰动路径);round_flow_skill_pool_test 100 检查(EV 锚+全战斗事件流逐位回放 ×2 关);roll_multiplier_test 42;variant_placement_test 43 检查(economy 直构+board_skew 写+luck_bias_query 断言+白盒 `_pool_weight`/`_biased_cell_rows` 四处);board_gen_reward_test(economy 直构+受理面全 API);seal_polarity_test/refresh_feats_test(受理面);minesweeper_rules_test(A3 直构单参);system_contracts_test(damage 直构四参);sim/reward_expectation(economy 直构+roll_board/is_mapped/query_identity) | damage_controller_test.gd:51-63;round_flow_skill_pool_test.gd(:60、:690-726 逐位);roll_multiplier_test.gd:245;variant_placement_test.gd:223、:227-242、:259、:264-265;board_gen_reward_test.gd:86-121;seal_polarity_test.gd:362、:729-748;refresh_feats_test.gd:113、:473;minesweeper_rules_test.gd:35;system_contracts_test.gd:99;reward_expectation.gd:18-58 | +| F13 | gdstyle 基线无本批触面键:`tools/gdstyle_baseline.txt` 仅 5 键(adventure_runner/level_runner/board_view)——三宿主与新件零基线登记(无「迁移即拆函数」义务源);S13 判定=基线外新违规红 | tools/gdstyle_baseline.txt:9-13 | +| F14 | 静态扫描两清单现状 12 文件(S8 await 集=S7 B1 域文件集同款清单独立两处);B4 符号清单 9 符号含 weighted_pick | game/tests/static_scan.sh:317、:361、S7 符号交替式段 | + +### 3.1 受影响既有断言面核查(逐面实测) + +| 既有断言面 | 核查结论 | +|---|---| +| damage_controller_test(41 检查) | 夹具不接 fx(_fx=null 路径)→ 本卡签名不变(bind_port 单独注入)→ **零改动照跑** | +| round_flow_skill_pool_test(100 检查,EV 锚+逐位回放) | 全链真实流 → **零改动照跑=行为零变化主证** | +| roll_multiplier_test(42 检查) | 消费 damage 公开面(clamped_pool/defender_ac)→ 零改动 | +| variant_placement_test(43 检查) | **四处白盒指址换代**(:227-242 board_skew→roller、:259 luck_bias_query→roller、:264-265 `_pool_weight`/`_biased_cell_rows`→roller)——判据零删改零放宽,申报为指址随件改写(非削弱,见 §9 申报表) | +| board_gen_reward_test/seal_polarity_test/refresh_feats_test/reward_expectation.gd | 全受理面(economy 保留 API、直构签名不变)→ 零改动照跑 | +| run_time_fatal_test | BoardInstaller 装配传 economy 引用(:75)+economy `_fatal` 直调(:83)——两触面均零变化 → 零改动照跑 | +| minesweeper_rules_test/system_contracts_test | A3/damage 直构签名不变 → 零改动 | +| adventure_feats_test :669-767 | 经 `fx_host().counters_ctx`/`fx_emit` 直发——host 公开面保留 → 零改动 | + +## 4. 设计契约(违反即返工;标 🔴 触碰=L3 停机呈报) + +1. 🔴 **行为零变化=本批最高契约**:RNG 消费序零漂移(roll_board 流 scope=每次 install 批不变、流开时点与取号序取号次数零变化;armor_roll/fx_reveal 流不动);命中序=声明序;明细行协议词表零触碰;`_ineffective_row` 载荷 deferred 自描述键观测面不回退;EV 锚同种子逐位一致。任何签名改动的观测面等价论证随对照表行在案。 +2. 🔴 **业务发射事实面闭集**:经端口发射的事实回调=闭集 {charge_layers, dig_streak, hp_ratio}(charge_layers=action_economy 现值活读、dig_streak=宿主侧轮内槽、hp_ratio=主角血量比例算式逐字),其余事实一律 null。battle_index/round_index/seals_count/nest_count **禁进**业务发射事实面——posture_bulwark 每关首轮窗在 damage_taken 的现行形态=事实未答不触发(F10),闭面守此现状;扩面=行为变化=L3。 +3. 🔴 **业务类零引擎直连零账面触碰**:damage_controller/action_economy 两文件禁出现 EffectEngine/EffectContext 引用与计数器静态调用;账面唯一宿主侧(counters_ctx 归 BattleFxHost),业务类读写经端口路由。 +4. 🔴 **跨件单向接线**:端口经注入 Callable 路由宿主,**禁持 BattleFxHost 对象引用**(宿主 bundle 已持两业务类——对象引用即成 host→damage→port→host 强引用环,两件均随每战新建=泄漏);掷格件零回指 economy(fatal 经构造注入 Callable 链);注入 Callable 同单向纪律,禁为省参数改持对象引用。 +5. **端口单实现零接口抽象**:无基类无多态(GDScript 无 interface,本案不需要——轴戊条款)。 +6. **拆分边界纪律**(判据权威=code-conventions §1.1,卡内执行条款):①每个拆出单元有可陈述的职责语句与单一变化原因(两件职责语句见 §1,实现报告逐件一行承载);②拆出单元有集体身份——命不出名字、只能说「剩下的成员」的打包=凑计数袋,禁;③「过阈值」只许作为发现信号,作为拆分或收拢的动机即违规;④按纪律拆不出内聚单元 → 停卡呈报走豁免/还债通道,禁硬拆凑绿。 +7. **S13 键账**:两新件零基线外新键(默认阈值内);存量触面只降不升(§14 预算表);禁为迎合机检文本保留或新增无效成员/别名——机检键与卡片契约互斥时实现段一律停卡呈报,不得自造第三条路。 +8. **数值纪律**:零数值改动(`data/tables/` 零触碰);无数值改动=零 MC——禁新跑 MC 批,行为零变化验收只用既有同种子锚与逐位对拍。 +9. **RNG 单点/Core 零 UI/fail-fast** 不因新增而绕:掷格件随机只经注入 RngServiceCore;两新件 RefCounted 纯逻辑零 UI;违约 fatal 通道沿 economy 现形(push_error+fatal_handler+assert)。 + +## 5. 范围边界 + +**做什么**:两新件建件、两业务类与宿主域换线、编排域与装板域改址、受影响测试指址换代、新增 effect_port_test、闸清单随动(static_scan.sh 三处)、README 能力行增行(已随本卡提交)。 + +**不做什么(明示不收件)**: +- 零行为改动:任何结算算序/取值/行词表/信号顺序改动一律不属本批(发现必要=上游设计缺陷,停卡呈报)。 +- BattleFxHost/RewardGrant 现有公开面不动(luck_bias_for/flush_pending_reveals/apply_ac_bonus/enqueue_reveal 原样——端口路由它们的既有方法)。 +- reward_grant/grant_segment 现存 `EffectEngine.counter_*(fx_host.counters_ctx,…)` 直触**不在本批收编**(轴戊点名面=DamageController/ActionEconomy 两业务类;授予件账面触面归后续批次,去向=待办清单本条实现批后按实况判断是否留尾)。 +- posture_bulwark 现行「不触发」形态是守卫对象不是缺陷——改它属行为变化,另走设计段。 +- posture_first_strike 等宿主侧事件事实链(fx_emit 全链)零触碰。 +- 文档面(architecture.md 换代注记/术语表)零直改——反哺点清单交付(§6)。 + +## 6. 关联文档同步面 + +| 文档 | 关联点(本批实现会改变它的哪条陈述) | 同步义务 | +|---|---|---| +| design/engineering/architecture/architecture.md | 「战斗编排域拆件定稿」节轴戊两条与功能层 B4 行/BattleFxHost 行/damage_controller 行:「实现批建件」待建注记换代为已建件现状(文件名、公开面、账面归属、facts_inject 注入面、S7/S8 增行实况);B4 行对外暴露 9 符号中 weighted_pick 迁属掷格件(改造为返行选行算子) | 列反哺点清单由文档批落文(architecture.md 的已建件换代注记归收尾反哺) | +| design/gdd/术语表.md | 「效果端口件」「掷格件」两机制名是否登记(首现定义在 architecture v1.29) | 列反哺点清单由文档批落文(登记与否由文档批裁决) | + +(`game/tests/README.md` 能力行=写卡段义务,已随本卡提交维护,不属实现批同步面;`game/tests/static_scan.sh` 清单字面值随动见 §7/T1/T2——工程随动,非文档。) + +## 7. 卡前文件结构图 + +| 文件 | 新增/复用 | 职责 | 行为改动标注 | +|---|---|---|---| +| game/core/effect_port.gd | **新增** | 效果端口件:业务类唯一发射出口窄门面(六 Callable 路由宿主) | 非行为改动(结构断言=对照表行 1-3) | +| game/core/reward_roller.gd | **新增** | 掷格件:装配期格面资源摇号(池行装配/区间行 roll/权重合成) | 非行为改动(对照表行 5-6) | +| game/core/battle_fx_host.gd | 修改 | +facts_inject 注入束、+fx_emit_biz/counter_read/counter_add/biz_fact 四法、_init 尾段端口组建与 bind_port 转注、头注换代 | 非行为改动(闭面守卫=行 3;账面路由=行 4) | +| game/core/damage_controller.gd | 修改 | _fx/_ctx/set_fx/inject/fx_calls/_fx_fact 退役;+_port/bind_port;五发射点与计数器三处、rider 两读、reveal/ac 两回调改端口路由;头注换代 | 非行为改动(行 1/2/4/8) | +| game/core/action_economy.gd | 修改 | _fx/_ctx/set_fx 退役;+_port/bind_port;三发射点与计数器三处改端口路由;头注换代 | 非行为改动(行 1/2/4/8) | +| game/core/reward_economy.gd | 修改 | 掷格面迁出(函数/常量/成员清单见 T2);roller 随件自建;fatal_handler 属性 setter 转注;roll_board 改装配序(守卫→复位→委托→收纳→ready);头注换代 | 非行为改动(行 5-6) | +| game/core/battle_orchestrator.gd | 修改 | 接线块换代:set_fx 两行/fx_calls 两行退役;rider 复位改 facts_inject;luck_bias 注入与 set_treasure_unlocked 改 roller 指址;头注指址换代 | 非行为改动(行 4/7) | +| game/core/board_installer.gd | 修改 | board_skew 写址两行改 roller(`reward_economy.roller.board_skew`) | 非行为改动(行 7) | +| game/core/settlement/combat_segment.gd | 修改 | dig_streak 写/读改 `oc.fx_host().facts_inject`;charge_layers 注入行退役(事实面活读) | 非行为改动(行 2/8) | +| game/tests/unit/battle_settlement/effect_port_test.gd | **新增** | 端口验收断言三函数(对照表行 1/2/3 载体) | 验收载体(行 1/2/3) | +| game/tests/run_all.gd | 修改 | manifest 登记 effect_port_test(expect 0)——套件可达性 | 非行为改动(行 9——闸 manifest 字面值随动) | +| game/tests/unit/battle_settlement/variant_placement_test.gd | 修改 | 四处白盒指址换代(判据零删改零放宽) | 非行为改动(行 5/7,§9 申报) | +| game/tests/static_scan.sh | 修改 | S7_B1_SET 增 effect_port.gd;S8_FILES 增 effect_port.gd+reward_roller.gd;B4 符号清单 weighted_pick 行随符号退役删除 | 非行为改动(行 9——闸清单字面值随动,判断依据=闸清单与架构文档同批对齐、判据口径不变) | +| game/tests/README.md | 修改 | 能力行增行「效果发射端口单点」(**已随本卡提交**——写卡段义务) | 非行为改动(义务清单维护) | + +(结构图与 §12 落位预检互指:两新增件均标「新增」,理由与唯一落点见 §12。) + +## 8. 任务卡 T0..T4 + +### 8.1 T0——开工复核与读数(估时 0.25–0.5 个工作日) + +**取用**:无(首卡)。 **产出**:复核结论与改前读数(实现报告读数节素材)。 + +- [ ] 逐条复核 §3 现状事实 F1-F14 的 file:line(五存量文件+combat_segment+五个测试件+sim 工具)——与卡不符处停该卡呈报(上游缺陷),禁内联求裁 +- [ ] 记录改前 gdstyle/S13 读数:battle_orchestrator 15 类变量、battle_fx_host 14、damage_controller 15(公开方法 9)、action_economy 11、reward_economy 15(公开方法 12)——写实现报告读数节,改后对读(T3 步核只降不升) +- [ ] 拷主区 `.godot/` 目录与字体 `.import` 边车(涉代码域跑闸批环境准备) + +### 8.2 T1——效果端口件与业务类换线(估时 0.5–1 个工作日) + +**取用**:§3 现状(F1-F4/F7/F9/F10)。 **产出**:`effect_port.gd` 全量;BattleFxHost 端口组建面(后续卡经 `orch.fx_host()` 取用);damage/action_economy 端口路由调用面;effect_port_test 三函数。 + +- [ ] 新建 `game/core/effect_port.gd`,头注=`##` 模块文档注释(端口职责+闭面红线+单向接线声明,零文档链接零变更叙事)。签名(全类型标注,S9 口径): + +```gdscript +class_name EffectPort +extends RefCounted + +var emit_cb: Callable # func(event: String, payload: Dictionary) -> Array——宿主 fx_emit_biz +var counter_read_cb: Callable # func(counter_id: String) -> int——宿主 counter_read +var counter_add_cb: Callable # func(counter_id: String, value: int, set_form: bool) -> void——宿主 counter_add +var fact_cb: Callable # func(name: String) -> Variant——宿主 biz_fact(伤害域闭面) +var reveal_cb: Callable # func() -> void——宿主 enqueue_reveal +var ac_cb: Callable # func(h: Dictionary) -> void——宿主 apply_ac_bonus + +func _init(bundle: Dictionary) -> void + # 六键 ["emit_cb","counter_read_cb","counter_add_cb","fact_cb","reveal_cb","ac_cb"] 缺一 assert(引用束注入先例) + +func emit(event: String, payload: Dictionary) -> Array # 未绑→[](零扰动缺省) +func counter_read(counter_id: String) -> int # 未绑→0 +func counter_add(counter_id: String, value: int, set_form: bool) -> void # 未绑→no-op +func fact(name: String) -> Variant # 未绑→null +func reveal_effect() -> void # 未绑→no-op +func apply_ac(h: Dictionary) -> void # 未绑→no-op +``` + +- [ ] `battle_fx_host.gd`:增 `var facts_inject := {"dig_streak": 0, "rider_tier_bonus": 0, "rider_tier_crit": false}`(类变量 14→15,贴顶);增四法——`fx_emit_biz(event, payload) -> Array`(`engine.dispatch(counters_ctx, event, payload, _biz_fact)`)、`counter_read(counter_id) -> int`(`EffectEngine.counter(counters_ctx, counter_id)`)、`counter_add(counter_id, value, set_form) -> void`(`EffectEngine.counter_add(counters_ctx, …)`)、`biz_fact(name) -> Variant` 闭面(charge_layers→`action_economy.charge_layers`;dig_streak→`int(facts_inject["dig_streak"])`;hp_ratio→逐字承 damage_controller.gd:96-97 算式读 `damage.units.hero_unit()`;余→null);`_init` 尾段=建 `EffectPort.new({六 Callable 绑 self 对应方法})` 后 `bundle.damage.bind_port(port)`+`bundle.action_economy.bind_port(port)`(端口不落 host 成员——单向接线,host 零回持);头注换代(封闭面 14→15、端口组建与转注、facts_inject 键集封闭声明) +- [ ] `damage_controller.gd`:删 `_fx`/`_ctx`/`set_fx`/`inject`/`fx_calls`/`_fx_fact`;增 `var _port: EffectPort`(**可空成员,缺省 null=未绑**——与原 `_fx == null` 零扰动同语义;不用成员初始化器建端口——`_init(bundle)` 必传参,无参可建不成立)与 `func bind_port(port: EffectPort) -> void`(组装期单点注入,BattleFxHost._init 调用);守卫写法:`_fx_hits` 漏斗体 `if _port == null: return []` 后 `return _port.emit(event, payload)`;未绑 fact 直读统一经私有 `_fact_or(name: String) -> Variant`(`return null if _port == null else _port.fact(name)`——调用点 `int(…)`/`bool(…)` 包裹,`int(null)=0`/`bool(null)=false` 与现行 inject 缺省值逐位同);计数器三处(:364/:370/:533)改 `_port.counter_read(…)`/`_port.counter_add(…)`(均在 hits 非空循环内——`_port` 已由漏斗守卫保证非空);open_ticket dig_streak 载荷键与 `_per_value` 回落、redeem rider 两读(:400/:408)改 `_fact_or` 路由;kill 揭示回调与 damage_taken ac 回调改 `_port.reveal_effect()`/`_port.apply_ac(h)`(均在 hits 非空分支内);头注换代(**头注含注释全域零 `EffectEngine`/`EffectContext` 英文类名 token**——V2 源码锚扫描面为全文) +- [ ] `action_economy.gd`:删 `_fx`/`_ctx`/`set_fx`;增 `var _port: EffectPort`(可空,同上语义)与 `bind_port`;三发射点(:120/:178/:205)外层守卫原位换写 `if _port != null:`(原 `if _fx != null`),体内改 `_port.emit(…)`;计数器三处(:125/:128/:183)改 `_port.counter_add(…)`(守卫外层已保证);头注换代(同零 token 口径) +- [ ] `battle_orchestrator.gd`::229-230 set_fx 两行与 :343-344 fx_calls 两行删除(host._init 组装期接线取代,先于任何发射——时序等价论证见 §12);:348-349 rider 复位改 `_fx_host.facts_inject["rider_tier_bonus"] = 0`/`["rider_tier_crit"] = false`;头注发射点指址换代(词表不动,指址须真) +- [ ] `combat_segment.gd`::34 改 `oc.fx_host().facts_inject["dig_streak"] = 0 if round_mine else round_safe`;:37 charge_layers 注入行删除(事实面活读等价——写值同一时刻读自同源);:64 改读 `int(oc.fx_host().facts_inject["dig_streak"])` +- [ ] `static_scan.sh`:S7_B1_SET 与 S8_FILES 两清单各增 `game/core/effect_port.gd` +- [ ] 新建 `game/tests/unit/battle_settlement/effect_port_test.gd`(extends SceneTree+Kit 收口样板,三函数): + - `_smoke_effect_port_wiring`〔效果端口与掷格分件批 V1〕:真实 `BattleOrchestrator.new(db, rng)` 装配链——①断言 `damage._port` 与 `action_economy._port` 同一实例(内部字段直查=受控例外,对照表行 1 注明理由:端口同一性无公开查询面);②attack_commit 经真实战斗流(begin_battle+自动玩家最小回合)取回命中批可观测(订阅效果命中在事件流/明细行可见); + - `_test_business_classes_engine_free`〔效果端口与掷格分件批 V2〕:源码契约锚(theme_projection_test 源码锚惯例)——读两业务文件全文,断言零 `EffectEngine`/`EffectContext` token(扫描面>0 前置:两文件读出非空字数); + - `_test_biz_fact_face_closed`〔效果端口与掷格分件批 V3〕:begin_battle 后直改 `db.crossrun.feats.rows` 追加两条合成行(调试注入两层·数据面粒度:行键 feat_id+triggers,on=damage_taken;rebuild 走持有 id 过滤真实表行——合成行经直改表行进入装载域)→ `rebuild_subscriptions([两条合成 feat_id], [], [])`(订阅面换代为合成行独占,确定性)→ 真实 `flush_submit` board_flat_hero 单据(base_value 足量避开伤害下限保底,shield 减量可观测):合成行 (a) when hp_ratio lte 100(载荷携带恒真——阳性对照,证 harness 能见命中)→ 断言承伤减量恰 1;合成行 (b) when battle_index eq 1 → 同路径断言零减量(闭面——F10 现状形态守卫) +- [ ] `run_all.gd` manifest 登记 effect_port_test(expect 0) +- [ ] 孤立验证:先跑 `--editor --quit` 刷新全局类名缓存(新 class_name 两枚——`--headless --script` 模式解析在册坑),再 effect_port_test 单套直跑绿(新断言先行,禁以全套闸首跑充当首验) + +### 8.3 T2——掷格件与 economy 收纳(估时 0.5–1 个工作日) + +**取用**:T1 产出无交叉依赖(奖励域与端口域独立);§3 F5/F6/F8。 **产出**:`reward_roller.gd` 全量;economy 收纳后形态;装板域与编排域改址。 + +- [ ] 新建 `game/core/reward_roller.gd`,头注=`##` 模块文档注释(掷格职责语句+取号流声明+零回指声明)。签名: + +```gdscript +class_name RewardRoller +extends RefCounted + +var db: ConfigDb +var rng: RngServiceCore +var fatal_cb: Callable = Callable() # 协作件违约→宿主 fatal 封印(BoardInstaller 先例) +var luck_bias_query: Callable = Callable() # 偏运合计查询口(缝拆形态随件迁移) +var board_skew: int = 0 # 图级偏斜旋钮(写点=BoardInstaller 装板决议) +var _treasure_unlocked := {} + +func _init(bundle: Dictionary) -> void # ["db","rng","fatal_cb"] 缺一 assert +func roll(eligible: Array) -> Dictionary[Variant, Variant] + # 批束键集封闭:{"mapping": Dictionary(cell→reward_id 行主序插入序), + # "power_roll": Dictionary(cell→int), "clear_identity": String, "clear_power": int} + # 体序=normalize(行主序+去重,零 RNG)→ 开 reward_roll 流(恰一 scope 批)→ luck_bias 查询 + # → 池行装配(_biased_cell_rows/_pool_rows)→ 身份批逐格取号 → 清图批尾 → 区间行追加取号 + # ——取号序与取号次数零变化(逐字承 reward_economy.gd:43-74 流程体) +func set_treasure_unlocked(ids: Dictionary) -> void +# 迁入:_normalize_eligible/_biased_cell_rows/_skewed_cell_rows/_skew_families/_apply_skew/ +# drops_total/_largest_remainder_split(static)/_rollable_rows/_pool_rows/_pool_weight +# 与常量 LUCK_BIAS_KINDS/SKEW_DOWN_KINDS/SKEW_UP_KINDS(逐字迁移) +# 改造:_weighted_pick_row(rows, r) -> Dictionary——weighted_pick 算术逐字(acc 累加/断言/fatal +# 通道),返回值由 reward_id 换为表行,调用侧取 row.reward_id;取号算术与次数零变化 +``` + +- [ ] `reward_economy.gd` 收纳:增 `var roller: RewardRoller`(_init 内自建自接线——`roller = RewardRoller.new({"db": db_ref, "rng": rng_ref, "fatal_cb": fatal_handler})`,BoardInstaller.generator 先例);`fatal_handler` 改属性 setter(赋值同拍转注——`set(v): fatal_handler = v; if roller != null: roller.fatal_cb = v`,构造序防:economy._init 不触该成员、编排器 :220 注入晚于 _init);`roll_board(eligible)` 保留签名改装配序=相位守卫(E5,先行——违约路径零取号不变)→ 空清单 fatal(E1)→ 受理面复位四拍(逐字)→ `var batch := roller.roll(eligible)` → 收纳 `_mapping = batch.mapping`/`_power_roll = batch.power_roll`/`_clear_identity = batch.clear_identity`/`_clear_power = batch.clear_power` → `_phase = "ready"`;退役清单=rng/_eligible/luck_bias_query/board_skew/_treasure_unlocked 五成员+set_treasure_unlocked/weighted_pick/drops_total/_pool_weight/_pool_rows/_rollable_rows/_normalize_eligible/_biased_cell_rows/_skewed_cell_rows/_skew_families/_apply_skew/_largest_remainder_split 十二函数+三常量(类变量 15→11、公开方法 12→9);保留面=_phase/_mapping/_power_roll/_clear_power/_granted/_clear_identity/_clear_granted/_clear_snapshot/is_mapped/query_identity/query_full_snapshot/grant/grant_clear/retire_mapping/polarity_row/phase/_row_by_id/_snapshot/_diag/_fatal;头注换代(两职责分家声明+衔接面批束键集) +- [ ] `battle_orchestrator.gd`::231 改 `_reward_economy.roller.luck_bias_query = _fx_host.luck_bias_for`;:501-502 `set_treasure_unlocked` 内部改 `_reward_economy.roller.set_treasure_unlocked(ids)`(编排器公开面不动——level_runner :522 零改址) +- [ ] `board_installer.gd`::80/:86 两行改 `reward_economy.roller.board_skew = …`(写时序在 roll_board 前——不变) +- [ ] `variant_placement_test.gd` 四处指址换代::227-242 board_skew 写改 `eco.roller.board_skew`;:259 luck_bias_query 断言改 `eco.roller.luck_bias_query`;:264-265 `_pool_weight`/`_biased_cell_rows` 改 `eco.roller.…`——判据零删改零放宽 +- [ ] `static_scan.sh`:S8_FILES 增 `game/core/reward_roller.gd`;S7 B4 符号清单 weighted_pick 行随符号退役删除(注释同步——符号全库零调用后清单与架构文档对齐) +- [ ] 孤立验证:board_gen_reward_test+variant_placement_test 单套直跑绿 + +### 8.4 T3——受影响面窄测与逐位主证(估时 0.25–0.5 个工作日) + +**取用**:T1/T2 产出。 **产出**:触碰面套件全绿记录+S13 改后对读数。 + +- [ ] 本批触碰面套件跑绿(卡内只测自己改的——选择依据=§3.1 受影响面并集):effect_port_test/board_gen_reward_test/variant_placement_test/round_flow_skill_pool_test(EV 锚+事件流逐位=行为零变化主证)/damage_controller_test/refresh_feats_test(计数器行为与受理链)/seal_polarity_test(受理面+reward_ineffective 观测面)/minesweeper_rules_test(A3 dig 路径端口换线)/settle_segments_test(段件链)/run_time_fatal_test(装板装配与 economy fatal 通道) +- [ ] 改后 gdstyle 读数对照:五存量文件 S13 键较 T0 读数只降不升(reward_economy 15→11、damage 15→12、action_economy 11→10、battle_fx_host 14→15、orchestrator 15→15 持平)——超预期即停卡呈报 +- [ ] 里程碑步(T3)出口跑一次代码域闸(按本批改动域选档);波内修复只做出红最小单元的最窄确认;新增测试先行孤立验证再并入全套 + +### 8.5 T4——产出物落位卡(估时 0.25–0.5 个工作日) + +**取用**:T0-T3 全部记录。 **产出**:reports/ 两份+审计。 + +- [ ] `reports/iter-实现报告-<日期>.md`:各卡状态+改前改后 S13 读数对照+实机目检总清单(本批行为零变化、无玩家可见面——**零新增目检项声明**)+实现决定记录(超白名单事项逐条记「决了什么/为什么/代价/撤销方式」,全部按卡执行则写一行「零拍板」) +- [ ] `reports/iter-反哺点清单-<日期>.md`:§6 同步面逐条落点(architecture.md 轴戊换代注记与 B4 行符号换代;术语表登记裁决)——每条写清哪个文件哪一节、改成什么、依据是什么 +- [ ] 独立验收审计:未参与实现的会话或子代理执行,报告落 `reports/iter-验收审计-<日期>.md`——逐行核对对照表(断言存在/真执行/尾缀可 grep/真实入口未绕改动点)、抽查判据绑定面、核对测试削弱申报与内部字段直查受控例外;**未过不得合并** +- [ ] 轻闸全绿后、合并前收口核对:能力清单行名与实际落地测试名一致(改名则回表) +- [ ] 产出物落位:两份报告+审计报告只落 `reports/`(实现批禁写 `design/` 与根规则文件、`game/tests/README.md`) + +## 9. 验证闸(改动—验收对照表) + +测试方式两分法:本批结构断言全部走自动化载体(单元套件+源码契约锚);**用户目检项=零新增**(行为零变化、无新玩家可见面——登记义务以本句声明了结,不阻塞)。断言尾缀统一〔效果端口与掷格分件批 Vn〕。措辞纪律:里程碑步出口跑一次全套闸;波内修复只做出红最小单元的最窄确认;新增测试先行孤立验证再并入全套;全量验证归合回后本地 CI。 + +| 需求点 | 改动(文件:行/行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 | +|---|---|---|---|---|---|---| +| R1 端口单实例与真实发射路由 | effect_port.gd 新件;battle_fx_host.gd _init 尾段组建与 bind_port;damage_controller.gd:79-82 漏斗改路由 | effect_port_test.gd:`_smoke_effect_port_wiring`〔效果端口与掷格分件批 V1〕:装配后 `damage._port == action_economy._port`;attack_commit 经真实回合命中可观测 | 单元 | `BattleOrchestrator.new()` 装配链+`begin_battle()` 真实战斗流 | 端口引用同一性+真实结算流命中效果(事件流/明细行可见)。内部字段直查受控例外:端口同一性无公开查询面,对照表本行注明理由 | 新增:效果发射端口单点 | +| R2 业务类零引擎直连零账面触碰 | damage_controller.gd(_fx/_ctx/inject/fx_calls/_fx_fact 退役)、action_economy.gd(_fx/_ctx/set_fx 退役)、两业务类计数器读写共六处改端口路由 | effect_port_test.gd:`_test_business_classes_engine_free`〔效果端口与掷格分件批 V2〕:两业务文件全文零 `EffectEngine`/`EffectContext` token(扫描面>0 前置) | 单元(源码契约锚) | 测试直读两业务文件源码(源码锚惯例) | 源码 token 面(直连/账面触碰的唯一机械可判面) | 新增:效果发射端口单点 | +| R3 伤害域事实面闭集 | battle_fx_host.gd fx_emit_biz/biz_fact 闭面;combat_segment.gd:34/:37/:64 注入改址与退役 | effect_port_test.gd:`_test_biz_fact_face_closed`〔效果端口与掷格分件批 V3〕:合成行 (a) hp_ratio 载荷 when → 承伤减量恰 1(阳性对照);(b) battle_index when → 零减量 | 单元 | `DamageController.flush_submit()` 真实单据流(board_flat_hero 承伤路径) | when 判定命中面(shield 是否入交付减量)——F10 现状形态的特有提交面 | 新增:效果发射端口单点 | +| R4 端口接线时序与入站路由等价 | battle_orchestrator.gd:229-230/:343-344 退役、:348-349 改 facts_inject;reveal/ac 回调组装期绑定(先于任何发射——发射点全部在 begin_battle 之后,接线提前不可观测) | 既有套件照跑:refresh_feats_test(计数器施加行为 test_reserve_on_accepted_dig 等)、effect_words_t1_test(词表接线)、adventure_feats_test(fx_host 直发面) | 单元 | `MinesweeperRules.settle_batch()`/`BattleOrchestrator.begin_battle()` 真实玩法路径 | 计数器桶值与事件流(既有断言原样执行即判据) | 既有:换图基座:挖格产 RAP 与刷新折扣;既有:换图系刷新链路与效果词表接线;既有:专长(奇物)授予与凭证保底 | +| R5 掷格件建件与摇号等价 | reward_roller.gd 新件(roll/选行算子改造/权重合成族迁入);reward_economy.gd:35-75 装配序换代 | 既有 board_gen_reward_test 照跑(roll_board 受理/双掷拒绝/五型封闭/清图批/受理幂等全 API);sim/reward_expectation.gd 静态核零改动(直构签名与受理 API 未变——无数值改动禁跑调参批量,不属当轮执行面) | 单元 | `BattleOrchestrator.begin_battle()`(install 链 roll_board 恰一次) | 摇号映射分布与受理面返回结构(既有断言原样执行即判据——判据不绑行号绑 API 行为) | 既有:同参数同种子逐位复现、异种子不退化;既有:装板与开局免费展开(封锁格无资源、计入清图) | +| R6 economy 收纳与键账只降不升 | reward_economy.gd 类变量 15→11、公开方法 12→9、掷格十二函数与三常量退役 | T3 读数步骤:改后 gdstyle 读数对照(只降不升)+既有 board_gen_reward_test/seal_polarity_test/refresh_feats_test 受理面照跑 | 单元+读数 | 既有受理 API 真实调用面 | 成员/函数计数(机检判据面)+受理行为逐位 | 既有:清图奖励双份与零路径 | +| R7 装板域与编排域改址 | board_installer.gd:80/:86、battle_orchestrator.gd:231/:501-502 改 roller 指址 | 既有 variant_placement_test 照跑(指址换代后 43 检查全绿——board_skew 投影/luck_bias 注入/白盒权重面判据原样) | 单元 | `BattleOrchestrator.begin_battle()`(全局变异决议→装板摇号链) | board_skew 投影行集与偏运注入回调同一性(既有断言原样执行即判据) | 既有:变异布置门控 | +| R8 行为零变化总证 | 全域(本批 diff 全部非行为改动——逐面等价论证见本表各行) | 既有 round_flow_skill_pool_test 照跑:EV 锚+全战斗事件流逐位回放 ×2 关(含 roll_board 取号序/armor_roll 流/命中序/明细行词表/_ineffective_row deferred 键观测面的链路覆盖) | 单元 | `BattleOrchestrator.begin_battle()` 全战斗回放 | 事件流 JSON 逐位等值(既有断言原样执行即判据) | 既有:轮内流与结算五段全序;既有:伤害单据编排与回放 | +| R9 闸清单随动与能力行维护 | static_scan.sh S7_B1_SET/S8_FILES/B4 符号清单三处;README 能力行增行(随卡提交);run_all.gd manifest 登记 | 代码域闸全绿(清单缺位自检通过=S7/S8 文件集存在性判据)+批末收尾核对能力行名与落地名一致 | 闸 | 静态扫描域闸(随代码域闸跑批执行) | 清单缺位判红面+manifest 套件可达性 | 新增:效果发射端口单点 | + +**测试削弱申报**:本卡允许的旧断言改动=variant_placement_test 四处**指址换代**(:227-242/:259/:264-265,economy→economy.roller——拆件随件改写,判据表达式与阈值零删改零放宽);此外零申报——实现期任何未申报的删断言/放宽阈值/缩小扫描面/换下层直调由独立验收审计判红。 + +## 10. 异常分级 + +- **L1 自救**:环境故障(.godot 缓存失配假红、user 目录隔离失败)——自愈重试(闸壳自检);测试夹具笔误(本卡新增测试自身红)——就地修复重跑。 +- **L2 记录继续**:既有套件红且初判与本批无关——记录现场、跑红件最小单元定位;确认无关后继续并在实现报告备案。 +- **L3 停机写报告等用户**:①🔴 任何行为零变化红线触发(逐位对拍出漂移/词表触碰/RNG 序漂移);②🔴 事实面闭集破(battle_index 等进业务发射面);③机检预算互斥——两新件或存量触面超出 §14 预算、或机检键与本卡契约互斥(含 S13 键不降反升、默认阈值出不落基线的新违规)=上游缺陷停卡退回写卡段,禁自造第三条路;④现状复核与 §3 不符(file:line 漂移改变设计前提);⑤拆分边界纪律第④款触发(拆不出内聚单元)。 + +## 11. 代拍板白名单(超清单必呈报) + +1. **暂名定名**:EffectPort→`effect_port.gd`/RewardRoller→`reward_roller.gd`(暂名即定名——两件均见名知意,architecture v1.29 暂名在案)。 +2. **端口持引用 vs 注入 Callable**:定**注入 Callable 束**。否决备选理由=端口持 BattleFxHost 对象引用:宿主 bundle 已持两业务类引用,端口再持宿主引用即成 host→damage→port→host 强引用环(轴戊禁双向强引用——两件均 RefCounted、随每战新建泄漏);Callable 注入=BattleFxHost emit_cb/facts_cb 既有接线形态。 +3. **端口注入时点**:BattleFxHost._init 尾段 bind_port(组装期单点)。否决备选理由=业务类构造函数注入:端口 Callable 绑宿主方法而宿主 bundle 又需两业务类引用——构造序成环;bind_port 与现行 set_fx 同为组装期一次接线、先于 begin_battle 任何发射(全部发射点在战斗流内),时序等价。 +4. **端口事实面形态**:单一伤害域闭面统一两业务类发射。否决备选理由=按事件分面(伤害域闭面/经济域无面):现状数据表下经济域三事件订阅行 when 词汇={counter}∪∅(F11 实测),counter 取值与 facts 回调无关、ap_left 载荷携带——分面机制无观测差,单面省一套事件分域机;闭面红线+V3 断言守扩面风险。 +5. **选行算子改造**:weighted_pick(返 reward_id)→掷格件内 `_weighted_pick_row`(返表行)。否决备选理由=掷格件自备 `_row_by_id` 副本:同逻辑两处落点违唯一落点;改造后取号算术逐字、取号次数零变化、economy 的 `_row_by_id` 保留受理面单点。 +6. **迁移成员清单微调权**:以职责语句为界——凡属摇号域(池行装配/区间行 roll/权重合成/投放门控)的成员与函数随 T2 实测归掷格件,受理域留守;边界争议以两件职责语句裁定,禁凑计数袋。 +7. **端口缺省未绑形态**:业务类 `_port` 为可空成员(缺省 null)+调用点 null 守卫。否决备选理由=成员初始化器自建端口(`var _port := EffectPort.new()`):`_init(bundle)` 必传参、无参可建不成立;且六 Callable 束缺键 assert 的引用束先例不为「空端口」开缺省口——可空成员与原 `_fx == null` 守卫同形态,忠实现状。 +8. 头注措辞、断言消息文案、测试内局部夹具命名——非契约面。 + +## 12. 落位与可复用性预检 + +**复用面(为什么不能复用既有那一个)**: +- **效果端口件为什么不复用 BattleFxHost 本尊**:BattleFxHost 是效果引擎机房(订阅重建/揭示兑付队列/AC 累积/桶计数器持有/树规则施加——architecture B1 域 BattleFxHost 行),业务类需要的是**收窄的发射出口**;轴戊条款即「业务类朝向机房的窄门面」——门面与机房分离。若业务类直持宿主引用,一者业务类面暴露整个宿主(可触 rebuild/flush 等非发射职责)破窄出口,二者宿主 bundle 已持两业务类引用、再加深引用即双向环(🔴4)。也不复用 luck_bias_query 缝拆形态(单枚注入 Callable):两业务类事件多且带计数器账面读写与事实束——缝拆不成立(待办清单条目实况在案,RewardEconomy 单查询口是能缝拆的最小样本、正是本条的对照面)。 +- **掷格件为什么不能复用 RewardEconomy**:两职责单一变化原因不同——摇号域(池结构/权重投影/取号流程)对受理域(幂等受理/快照/退役/极性字典)各自变化;15 类变量贴顶、加第 16 枚即出闸(条目登记起因);轴戊裁切向=掷格面迁出、受理账面留守。 +- **同义件排查(grep 在案)**:CurioDeck 抽签=秘宝域令牌池多重集抽签,非格面摇号;BoardGenerator=棋盘生成非资源摇号;`weighted_pick` 全库仅 economy 内部消费(F12)——两新件盘上无第二实现、无同名同义标识符冲突。 + +**唯一面**:端口组建单点(BattleFxHost._init);掷格件实例单点(economy 自建自持有,外部经 `reward_economy.roller` 组件出口直达——调用直连不留委托壳);伤害域事实面单点(host biz_fact);计数器账面单点(host counters_ctx);选行算子单点(roller `_weighted_pick_row`);行查 `_row_by_id` 单点(economy 受理面)。 + +**分层与交互面**:业务类→端口→(Callable)→宿主→引擎,单向无回指;economy→roller 单向(roller 零回指,fatal 经构造 Callable 链转宿主 fatal);BoardInstaller/编排器经 `reward_economy.roller` 组件出口写旋钮/接线(Middle Man 禁令下的直连形态);随机只经注入 RngServiceCore(roller 持 economy 传入的同一实例——流身份不变);Core 零 UI 不变;两新件 RefCounted 纯逻辑。 + +## 13. 拆分边界纪律适用性声明 + +本卡为结构类卡,§4 第 6 条全文载纪律四条款(判据权威=code-conventions §1.1,不复制定文)。逐件归属:效果端口件=职责语句「战斗业务类唯一的效果发射出口」、单一变化原因=发射契约与账面归属;掷格件=职责语句「装配期格面资源摇号」、单一变化原因=池结构与权重投影算法。两件命名即集体身份,非「剩下的成员」打包;「过阈值」(economy 15 贴顶、damage 15 贴顶)在卡内仅作发现信号记录(F5/F12),拆分动机=轴戊设计定稿。实现报告逐件一行承载职责语句执行情况。 + +## 14. 实现规范预算 + +**新件三查**(对照 gdstyle 默认阈值:公开方法 ≤20、类变量 ≤15、单函数分支 ≤8、返回 ≤6): + +| 新件 | 公开方法 | 类变量 | 迁移函数对账(tools/gdstyle_baseline.txt 源键) | +|---|---|---|---| +| effect_port.gd | 6(emit/counter_read/counter_add/fact/reveal_effect/apply_ac)≤20 ✓ | 6(六 Callable)≤15 ✓ | 零迁入函数——纯新件,对账空集 | +| reward_roller.gd | 2(roll/set_treasure_unlocked)≤20 ✓(board_skew/luck_bias_query 为公开成员变量非方法) | 6(db/rng/fatal_cb/luck_bias_query/board_skew/_treasure_unlocked)≤15 ✓ | 迁入十二函数对基线源键对账=**空集**(基线无 reward_economy 键,F13)——无「迁移即拆函数」义务源;`_weighted_pick_row` 为改造新名(算术逐字) | + +**存量触面一查**(S13 键较登记值只降不升;基线无登记键的文件按默认阈值判 NEW): + +| 存量文件 | 改前→改后预期 | 达成手段 | +|---|---|---| +| reward_economy.gd | 类变量 15→11、公开方法 12→9(只降) | 掷格五成员十二函数三常量迁出,零新增无效成员 | +| damage_controller.gd | 类变量 15→12(只降);公开方法 9→9(set_fx 退役/bind_port 增补相抵) | 四束成员退役、_port 单成员补位 | +| action_economy.gd | 类变量 11→10(只降) | 同上 | +| battle_fx_host.gd | 类变量 14→15(贴顶 ≤15 合格);公开方法 12→16 ≤20 ✓ | 仅 facts_inject 一成员与四法——头注封闭面清单随批换代 | +| battle_orchestrator.gd | 类变量 15→15(持平,零增——硬预期) | 端口随 host 自建、掷格件随 economy 自建——宿主零新成员 | +| board_installer.gd/combat_segment.gd | 计数不变 | 纯改址 | + +**指标例外申报**:无——零件超默认阈值;实现期出现超预算面=L3 停卡(§10)。 + +## 15. 待裁点 + +**零待裁点,开工门=U0**(轴戊全条款在案——两件职责语句/账面归属/衔接面/单向接线/取号流/红线全款已在 architecture v1.29 定稿;端口接线形态与迁移边界属 §11 代拍板白名单,附否决备选理由)。 + +## 16. 估时与切分预案 + +| T 卡 | 估时(工作日) | 中值 | +|---|---|---| +| T0 开工复核与读数 | 0.25–0.5 | 0.375 | +| T1 效果端口件与业务类换线 | 0.5–1 | 0.75 | +| T2 掷格件与 economy 收纳 | 0.5–1 | 0.75 | +| T3 受影响面窄测与逐位主证 | 0.25–0.5 | 0.375 | +| T4 产出物落位卡 | 0.25–0.5 | 0.375 | +| **合计** | 1.75–3.5 | **≈2.63** | + +**切分预案**(超软盒预挂):T1 与 T2 相互独立(端口域/奖励域零交叉),超时域闸口径下可拆两批各自合回——拆分点=T2 起手;拆出批仍按本卡对照表各自承接(R1-R4 归端口批、R5-R7 归掷格批、R8/R9 两批各自全跑)。迭代层估时与冻结线归编排段(iter-plan)核。 diff --git a/待办清单.md b/待办清单.md index cb94a750..89f754ed 100644 --- a/待办清单.md +++ b/待办清单.md @@ -120,9 +120,11 @@ **2026-11-02 设计闭合(用户两点裁甲案)**:端口形态=独立端口小件(单实现零接口抽象)、分件切向=掷格面迁出(受理账面留守)——[architecture](design/engineering/architecture/architecture.md) v1.29「战斗编排域拆件定稿」节轴戊定稿(职责语句/账面归属/衔接面/行为零变化红线全在案),缺口清零。 +**2026-11-02 写卡**:需求卡片 reports/需求卡片-效果端口与掷格分件批-2026-11-02.md(v1.0,零待裁点,开工门=U0);能力行「效果发射端口单点」同批入 game/tests/README.md 能力清单。 + **改动面**:游戏代码 -**状态**:文档设计已闭合 +**状态**:文档设计已闭合+已写卡 #### main.gd 拆件遗留死 preload 清理(2026-10-31 登记,来源=批次移交)